Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ It is a partial implementation of ActivityPub and of the Mastodon client API —
- 📦 **Migration** — a page of its own behind your account: **Export** takes your profile, follows, followers, blocks, mutes, bookmarks, likes and every post you have written as a zip (your private key is deliberately not in it), **Import** reads one back — including an archive from `occ user:export` — and a third section brings your follows over from Mastodon, Pixelfed, GoToSocial or Akkoma via their `following_accounts.csv`. It says plainly which networks cannot be imported from, and leaves a whole-account `Move` to `occ social:account:alias` and `occ social:account:move`, since that federates and cannot be undone.
- ✍️ **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.
- 📊 **Polls** — write one in the composer (up to four options, single or multiple choice, 30 minutes to a week), and view and vote on federated ones. Votes travel 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`).
- 🖼️ **Media attachments** — images (JPEG, PNG, GIF, WebP, AVIF, and **HEIC/HEIF** from an iPhone, transcoded to JPEG on the way in where the server has Imagick with libheif), video (MP4, WebM, QuickTime) and audio (MP3, MP4/AAC, OGG/Opus, WAV, FLAC); `filterMimeTypes()` in `lib/Service/CacheDocumentService.php` is the exact list. Up to **ten** per post. Video and audio are stored as-is — **no transcoding** — but a video gets a **poster frame** where the server has ffmpeg, so a page of videos can be scrolled without downloading one, and `/media/{uuid}` answers **byte ranges**, so a video can actually be seeked. Video has a ceiling of its own (`max_video_size`, 2 GB) rather than the 10 MB `max_size` a picture is held to, because a video is streamed to storage a chunk at a time and never held in memory; the largest uploads go through `POST /api/v1/media/from-file`, where the Files app has already done the chunking. Clients upload through `POST /api/v2/media` (or v1), with alt text via `description`, editable with `PUT /api/v1/media/{id}` and attached to a status with `media_ids`. A **focal point** (`focus`, Mastodon's `x,y`) says where the subject is, so a square crop keeps it in frame; it federates as `focalPoint`.
- 🖼️ **Media attachments** — images (JPEG, PNG, GIF, WebP, AVIF, and **HEIC/HEIF** from an iPhone, transcoded to JPEG on the way in where the server has Imagick with libheif), video (MP4, WebM, QuickTime), audio (MP3, MP4/AAC, OGG/Opus, WAV, FLAC) — **and files**: PDF, plain text, Markdown, CSV, ZIP, EPUB and the ODF and Office document formats, shown as a named download card under the post and federated as an ActivityPub `Document` with their mime (Mastodon shows them as a link). Nothing a browser would run — no HTML, SVG or scripts — and the picture ceiling (`max_size`) applies; `filterMimeTypes()` in `lib/Service/CacheDocumentService.php` is the exact list. Up to **ten** per post. Video and audio are stored as-is — **no transcoding** — but a video gets a **poster frame** where the server has ffmpeg, so a page of videos can be scrolled without downloading one, and `/media/{uuid}` answers **byte ranges**, so a video can actually be seeked. Video has a ceiling of its own (`max_video_size`, 2 GB) rather than the 10 MB `max_size` a picture is held to, because a video is streamed to storage a chunk at a time and never held in memory; the largest uploads go through `POST /api/v1/media/from-file`, where the Files app has already done the chunking. Clients upload through `POST /api/v2/media` (or v1), with alt text via `description`, editable with `PUT /api/v1/media/{id}` and attached to a status with `media_ids`. A **focal point** (`focus`, Mastodon's `x,y`) says where the subject is, so a square crop keeps it in frame; it federates as `focalPoint`.
- 🔒 **Metadata stripped on upload** — a photo off a phone carries an Exif block, and that block routinely carries the coordinates it was taken at. Every uploaded picture has its Exif, XMP, IPTC and comment blocks removed before it is stored or federated (`lib/Service/ImageMetadataService.php`). Done on the container rather than by re-encoding, so nothing loses a generation of quality, and the ICC colour profile is deliberately kept. A photo that says it is rotated is turned the right way up first, since the orientation tag goes with the rest.
- 🎨 **Filters** — eight mild adjustments in the composer, previewed live and baked into the copy that is posted.
- 🔳 **Profile grid** — a profile draws as a grid of squares or as a timeline, whichever you last chose. The crop follows each picture's focal point.
Expand Down Expand Up @@ -72,7 +72,6 @@ It is a partial implementation of ActivityPub and of the Mastodon client API —
These are absent from the code today, not merely rough edges:

- **No status translation.** The `translate` action returns the post unchanged (`lib/Service/ActionService.php`).
- **No document or file attachments.** Images, video and audio only — anything else is refused by `filterMimeTypes()` (`lib/Service/CacheDocumentService.php`).
- **No custom emoji of this instance's own.** Emoji from other servers render; `/api/v1/custom_emojis` returns an empty list (`lib/Controller/ApiController.php`, `customEmojis()`).
- **No streaming API and no push subscriptions.** Third-party clients poll. (The web client does get live timelines when [notify_push](https://github.com/nextcloud/notify_push) is installed — that is a Nextcloud channel, not a Mastodon one.)

Expand Down
2 changes: 1 addition & 1 deletion appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ What it does:
- 📱 A layout for phones: the post takes the width of the screen, the face inside the card, the composer's toolbar wrapping

You can pin your own posts to the top of your profile, bookmark any post, and see which hashtags the instance is using most. This is a partial implementation of ActivityPub and of the Mastodon client API. Blocking, muting and reporting (with a moderation panel in the administration settings) are supported, as are locked accounts with approvable follow requests. Polls are fully supported: create your own, and view and vote on federated ones. Profiles carry an avatar, a banner image and up to four profile metadata fields. Posts that link somewhere get a link preview card. Image (JPEG, PNG, GIF, WebP, AVIF, and HEIC from an iPhone where the server can read it), video (MP4, WebM, QuickTime) and audio attachments are supported, up to ten per post, each with alt text and a focal point. It does not offer lists.]]></description>
<version>0.19.51</version>
<version>0.19.53</version>
<licence>agpl</licence>
<author mail="benedikt.schaechner@web.de" homepage="https://benedikt.xn--schchner-2za.de">Benedikt Schächner</author>
<namespace>Social</namespace>
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "nextcloud/social",
"description": "Social app",
"license": "AGPL-3.0-or-later",
"version": "0.19.51",
"version": "0.19.53",
"minimum-stability": "stable",
"authors": [
{
Expand Down
2 changes: 1 addition & 1 deletion docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,7 @@ remote one, as on Mastodon: both describe a user of *this* instance.

| Method | Route | Auth | Parameters | Description |
|--------|-------|------|------------|-------------|
| POST | `/api/v1/media` | public, no-csrf (viewer required, `write` scope) | Multipart: `file` (required, read from `$_FILES['file']`), `description` (the alt text) | Stores an upload and returns the `MediaAttachment` (images get a resized preview and blurhash; video/audio are stored as-is with the media itself as `preview_url`). Refuses a mime type outside `CacheDocumentService::filterMimeTypes()`, and a file over its ceiling with a 422. **Two ceilings**: `max_size` (10 MB by default) for everything, and `max_video_size` (2048 MB) for video, because an image is read whole into memory to be stripped and resized while a video is copied to storage a chunk at a time and never held. The request-time check can only go on the type the *client* declared, so it is applied again after the content is sniffed — a file that claimed to be a video to get past the first check is refused by the second. The stored row is **not** public: `public` is what lets the unauthenticated `/media/{uuid}` route serve the file, and it is only set later, when a post attaches the media and that post is `public` or `unlisted`. `focus` is accepted and not stored. Rate-limited per user. |
| POST | `/api/v1/media` | public, no-csrf (viewer required, `write` scope) | Multipart: `file` (required, read from `$_FILES['file']`), `description` (the alt text) | Stores an upload and returns the `MediaAttachment` (images get a resized preview and blurhash; video/audio are stored as-is with the media itself as `preview_url`). Refuses a mime type outside `CacheDocumentService::filterMimeTypes()` — images, video, audio and the document kinds in `DOCUMENT_MIME_TYPES` (PDF, text, Markdown, CSV, ZIP, EPUB, ODF, Office), which come back as `type: "unknown"` with no `preview_url` and, when no `description` was sent, the upload's file name as the description — and a file over its ceiling with a 422. **Two ceilings**: `max_size` (10 MB by default) for everything, and `max_video_size` (2048 MB) for video, because an image is read whole into memory to be stripped and resized while a video is copied to storage a chunk at a time and never held. The request-time check can only go on the type the *client* declared, so it is applied again after the content is sniffed — a file that claimed to be a video to get past the first check is refused by the second. The stored row is **not** public: `public` is what lets the unauthenticated `/media/{uuid}` route serve the file, and it is only set later, when a post attaches the media and that post is `public` or `unlisted`. `focus` is accepted and not stored. Rate-limited per user. |
| POST | `/api/v2/media` | public, no-csrf (viewer required, `write` scope) | Same as v1 | The same upload; modern clients POST v2 and only fall back to v1 on a 404. |
| POST | `/api/v1/media/from-file` | public, no-csrf (viewer required, `write` scope) | Body (JSON or form-encoded): `path` (required, relative to the viewer's own files), `description` (the alt text) | **Nextcloud extension, not a Mastodon route.** Attaches a file the viewer already has in Nextcloud, so a picture that is already on the server does not have to be downloaded and uploaded back. The path is resolved inside the viewer's own user folder and nowhere else — a share they can read is fair game, a traversal is a 422 `no such file`, and so is a folder. The bytes are copied, not referenced: a post keeps the picture it was published with, so moving or deleting the original later cannot empty a post that has already federated. Everything after that is the upload path — the same size ceiling, the same mime filter, the same resizing — and the answer is the same `MediaAttachment`, equally not public until a post says so. Rate-limited per user. |
| GET | `/api/v1/media/{nid}` | public, no-csrf | `nid` (path), `preview` (default `''`, ignored) | One of the viewer's own attachments, by the id the upload returned. 404 for an unknown id or someone else's attachment. |
Expand Down
2 changes: 1 addition & 1 deletion docs/Architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Nextcloud Social is a federated social networking app built on the W3C ActivityP
**App ID:** `social`
**Namespace:** `OCA\Social`
**License:** AGPL-3.0-or-later
**App version:** 0.19.51
**App version:** 0.19.53
**Supported Nextcloud versions:** 35 – 36
**Supported PHP versions:** 8.3 – 8.5

Expand Down
Loading
Loading