Skip to content

Latest commit

Β 

History

3,770 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Nextcloud Social πŸš€βœ¨

Nextcloud Social is an ActivityPub app that connects your Nextcloud account to the Fediverse. Your instance acts as a lightweight federated social server: each user gets a Person actor, can write and edit posts, follow accounts on other servers, and like, boost and reply to what they receive.

Screenshot

It is a partial implementation of ActivityPub and of the Mastodon client API β€” enough for posting, following and reading timelines, but far from feature parity with Mastodon. See "Not implemented yet" below before deploying it as someone's only Fediverse client.

πŸ”§ Features

  • 🧭 Timelines β€” Home, Local, Global (federated), Direct messages, Liked posts, Notifications, per-account and per-hashtag timelines.
  • ✍️ Composer β€” write posts and replies, pick a visibility (public, unlisted, followers-only, direct), insert emoji, and attach images. @mentions and #hashtags typed by hand are extracted from the text and turned into real recipients and tags.
  • ✏️ Edit posts β€” edit your own local posts inline; the change is saved and federated as an ActivityPub Update (lib/Service/PostService.php, editPost()).
  • πŸ—‘οΈ Delete posts β€” delete your own posts.
  • ⚠️ Content warnings β€” put a warning on a post in the composer and the body is folded away behind it until a reader asks to see it (it is not even in the page until then). Carried as the ActivityPub object's summary and as spoiler_text on the client API, so warnings written elsewhere in the Fediverse are honoured here and vice versa.
  • πŸ‘ πŸ” πŸ’¬ Post actions β€” like/unlike, boost/unboost (Announce) and reply.
  • πŸ‘₯ Following β€” follow and unfollow local and remote accounts, and browse followers/following lists.
  • πŸ“Œ Pinned posts β€” pin up to five of your own posts to the top of your profile (pin/unpin on the status-action endpoint, ?pinned=true on the account statuses route). Pins are published in the actor's featured collection, so other Fediverse servers show them too; pinned posts of remote accounts are not fetched.
  • πŸ–ΌοΈ Profiles β€” avatar, uploadable banner/header image, a profile description (note) and up to four editable profile metadata fields (the name/value table under the bio), federated as PropertyValue attachments on the actor and shown for remote accounts too.
  • 🌐 Federation β€” signed HTTP delivery of Create, Update, Delete, Like, Announce, Follow, Accept and Undo activities, an outbound request queue and a stream queue for resolving incoming objects, both drained by background jobs and by occ social:queue:process.
  • πŸ” Inbox forwarding β€” a reply to one of your posts that arrives from a stranger's instance is passed on to your followers, so everyone reading the thread sees the same one. Forwarded untouched and only when the reply carries its author's linked-data signature, so the servers receiving it verify the original author rather than trusting this one; private posts and their replies are never fanned out.
  • πŸ”” Live timelines with notify_push β€” when the notify_push app is installed, new timeline entries reach open web clients as push events and polling drops to a five-minute safety net; without it the client polls every 30 seconds.
  • πŸ”Ž Discovery & search β€” WebFinger lookups, remote actor resolution and caching, search over known accounts and hashtags, and full-text search of visible posts through Nextcloud's unified search (own posts, public content and messages addressed to you; a plain database substring match, no external search engine needed).
  • πŸ“ˆ Trending hashtags β€” the tags used most on the instance, counted per window (1 h to 10 days) by the same cron job that maintains the hashtag index, listed in the app's sidebar and served as Mastodon Tag entities at /api/v1/trends/tags.
  • πŸ”– Bookmarks β€” bookmark any post from its menu and read them back under Bookmarks in the sidebar. Purely local, never federated.
  • πŸ”— Link previews β€” a post that links somewhere gets a preview card (OpenGraph, with the plain title/description as fallback) read by a background job, so nothing waits for a stranger's web server. The fetch is HTTP(S)-only on every hop, refuses local addresses, is size- and time-capped and obeys the instance access list. Cards are never federated β€” like Mastodon, every instance reads the page itself.
  • 🧹 Retention β€” remote statuses older than retention_days (default: disabled) that no local user interacted with are pruned together with their cached attachments, from cron or occ social:stream:prune; local content is never touched. Configurable in the Social section of the administration settings.
  • 🧹 Accounts are deleted together β€” removing a Nextcloud user takes their Fediverse account with it: the actor is tombstoned, what belongs to it is dropped, and a Delete is federated so the servers that cached it drop their copies too. Previously the Social account outlived the user, kept resolving over WebFinger and kept receiving deliveries.
  • 🩺 Federation health β€” the administration settings show what the outbound queue is doing: how many deliveries are waiting, how many keep failing, which instances they are stacked up against and how close each is to being given up on (a delivery is abandoned after 15 attempts, previously without a word to anyone). occ social:queue:status prints the same summary.
  • πŸ›‘οΈ Instance access list β€” an allow-list or deny-list of remote hosts, enforced on incoming activities and outgoing requests. Managed with occ social:fediverse; see docs/OCC-Commands.md for the details and its limits.
  • πŸ”‘ Mastodon-compatible API β€” the Mastodon client API's core surface plus OAuth 2 authorization: third-party clients can log in, read every timeline, post (with media and polls), follow/unfollow, favourite/boost/bookmark, search (/api/v2/search), manage follow requests and report. No streaming endpoint or push subscriptions β€” clients poll. See docs/API.md for exactly which routes exist.

🚧 Not implemented yet

These are absent from the code today, not merely rough edges:

  • Blocking and muting are supported: block an account to sever the relationship in both directions and hide it everywhere (federated as a Block activity unless occ config:app:set social federate_blocks --value 0); mute an account to hide it from your timelines β€” and optionally notifications β€” without it ever knowing. Blocking and muting are done from an account's profile menu, and Settings β†’ Blocked and muted accounts in the app's left sidebar lists both, with unblock/unmute inline. Exposed over the Mastodon API too (/api/v1/accounts/{id}/block|unblock|mute|unmute, /api/v1/blocks, /api/v1/mutes).

  • Reporting is supported: POST /api/v1/reports files a report, incoming federated Flag activities are stored the same way, admins are notified and review reports (and manage the Fediverse access list) in the Social section of the administration settings. Reports are never forwarded to the reported account's instance.

  • Locked accounts / approvable follow requests are supported: PATCH /api/v1/accounts/update_credentials with locked toggles manuallyApprovesFollowers; incoming follows towards a locked account stay pending (with a follow_request notification) until the owner authorizes or rejects them via /api/v1/follow_requests (lib/Interfaces/Object/FollowInterface.php).

  • Profile metadata fields are supported: up to four name/value pairs, edited on your own profile page or through PATCH /api/v1/accounts/update_credentials with fields_attributes, stored on the local actor row, federated as PropertyValue attachments and read back from remote actors (lib/Model/ActivityPub/Actor/Person.php). They carry no link verification β€” verified_at is always null.

  • Polls are fully supported: write your own in the composer (up to four options, single or multiple choice, 30 minutes to a week), and view and vote on federated ones β€” votes federate to the poll's author as ActivityPub vote notes, incoming votes are counted and the new totals federate back as Update{Question} (lib/Service/PollService.php). No lists.

  • Media attachments cover images (JPEG, PNG, GIF, WebP β€” images get a resized preview and a blurhash), video (MP4, WebM, QuickTime) and audio (MP3, MP4/AAC, OGG/Opus, WAV, FLAC); see filterMimeTypes() in lib/Service/CacheDocumentService.php for the exact list. Video and audio are stored as-is (no transcoding, no thumbnail β€” the player is the preview) and remote copies respect the max_size app setting (default 10 MB). No document/file attachments.

  • Custom emoji from other instances render β€” Emoji tags on remote statuses and actors survive the cache (via the stored wire source) and are served in the emojis field of status and account entities; the web client shows them inline in content and display names. The instance has no custom emoji of its own: /api/v1/custom_emojis returns an empty list (lib/Controller/ApiController.php, customEmojis()).

  • No status translation. The translate action returns the post unchanged (lib/Service/ActionService.php).

  • Media uploads go through POST /api/v2/media (or v1) in lib/Controller/ApiController.php: jpeg/gif/png, with alt text via description, editable with PUT /api/v1/media/{id} and attachable to statuses via media_ids.

πŸ“¦ Quickstart (install & develop)

  1. Clone this repository into your Nextcloud apps/ directory.
  2. Install the dependencies and build the frontend:
cd /var/www/nextcloud/apps/social
composer install     # PHP dependencies
npm ci
npm run build        # production bundle into js/
  1. Enable the app in Nextcloud (occ app:enable social) and check the install with occ social:check:install.
  2. While working on the UI, use npm run dev for a development build or npm run watch to rebuild on change. The Makefile wraps the same scripts (make build-js, make build-js-production, make watch-js, make lint).
  3. To produce a release archive, run ./build-package.sh. It runs composer install --no-dev, npm run build, copies the app without the dev files and writes build/artifacts/social.tar.gz. make appstore builds the same archive through the Makefile; despite the sign_dir name it only stages and tars, it does not sign anything.

🧭 ".well-known/webfinger isn't properly set up!" β€” Troubleshooting

That banner has two quite different causes, and the app now tells them apart.

The first is the one it names: the server does not answer /.well-known/webfinger. Follow the documented redirects.

The second is that Social is set up for a different address than the server now uses. Social reads overwrite.cli.url once, the first time the app is opened, and builds every account id, post id and WebFinger answer from that stored copy (social.cloud_url). Change the server's URL afterwards and the two drift apart in silence: WebFinger answers for a host nobody asks about, and the app blames .well-known when .well-known is fine.

Social reports the mismatch with both addresses but will not correct it, because the stored address is baked into every id already written. Either point overwrite.cli.url back at the address Social knows, or accept the rename and run occ social:reset --uri=<new address>, which deletes everything Social holds.

To see the two values:

occ config:app:get social cloud_url
occ config:system:get overwrite.cli.url

πŸ–ΌοΈ Banner / Header upload β€” Troubleshooting

Banner/header uploads work: the image is stored in the app's document cache, the local actor's cached header is updated, and the change is federated as an actor Update (lib/Controller/LocalController.php, uploadBanner()). A banner can also be set from a URL. Only JPEG, GIF and PNG survive the mime filter.

If an uploaded banner does not appear immediately:

  • Reload the profile page (clear the browser cache if necessary).
  • Check the server log for [LocalController] uploadBanner failed and for Failed to federate banner change, which is only a warning and does not undo the local upload.
  • Re-fetch the account data, or run occ social:cache:refresh, to refresh the cached actor.

βœ… Tests

Backend and frontend unit tests run without a Nextcloud server or database.

composer install            # PHPUnit + OCP interface stubs
composer run test:unit      # PHP: vendor/bin/phpunit -c tests/phpunit.xml

npm ci
npm test                    # JS: vitest run (tests/js/** and src/**/*.test.js)
npm run test:coverage       # with a coverage report in coverage/js

PHP tests live in tests/ mirroring lib/ (lib/Service/PostService.php β†’ tests/Service/PostServiceTest.php). Everything a class needs is mocked; code that reaches the container statically finds a TestContainer behind \OC::$server (see tests/Helper/TestContainer.php). Frontend tests use Vitest with @vue/test-utils and jsdom; tests/js/setup.js provides the Nextcloud globals (t, n, OC, OCA, localStorage, router webroots).

A second PHP suite in tests/Integration/ runs against a real Nextcloud and database β€” migrations, query SQL and storage boundaries the mocks cannot reach. CI runs it on SQLite, MySQL and PostgreSQL; locally, point it at an installed server:

NEXTCLOUD_ROOT=/path/to/nextcloud composer run test:integration

See tests/Integration/README.md for what it covers.

πŸ› οΈ Contributing

  • Contributions welcome β€” open a pull request and run the build and tests locally first (npm run lint, npm test, composer run test:unit).
  • Reset local Social data for development with:
occ social:reset

This prompts twice and then empties every Social table. occ social:reset --uninstall additionally drops the tables, migrations, background jobs and app config. See docs/OCC-Commands.md for all commands.

License

See the repository's license files in the LICENSES/ directory.

Releases

Packages

Used by

Contributors

Languages