Merge remote-tracking branch 'origin/develop' into mastodon-quote-id-api
Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
commit
e0ab2c9c9c
129 changed files with 2128 additions and 362 deletions
|
|
@ -16,9 +16,15 @@ workflow:
|
|||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
- if: $CI_COMMIT_BRANCH == "develop"
|
||||
- if: $CI_COMMIT_BRANCH == "stable"
|
||||
- if: $CI_PIPELINE_SOURCE == "web"
|
||||
- if: $CI_COMMIT_TAG
|
||||
- if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS
|
||||
when: never
|
||||
|
||||
# Default artifacts configuration
|
||||
.default_artifacts: &default_artifacts
|
||||
expire_in: 30 days
|
||||
|
||||
cache: &global_cache_policy
|
||||
key: $CI_JOB_IMAGE-$CI_COMMIT_SHORT_SHA
|
||||
paths:
|
||||
|
|
@ -56,6 +62,7 @@ check-changelog:
|
|||
before_script: ''
|
||||
after_script: ''
|
||||
cache: {}
|
||||
artifacts: *default_artifacts
|
||||
script:
|
||||
- apk add git
|
||||
- sh ./tools/check-changelog
|
||||
|
|
@ -71,6 +78,7 @@ check-changelog:
|
|||
.using-ci-base:
|
||||
tags:
|
||||
- amd64
|
||||
artifacts: *default_artifacts
|
||||
|
||||
build-1.15.8-otp-26:
|
||||
extends:
|
||||
|
|
@ -101,8 +109,12 @@ spec-build:
|
|||
artifacts:
|
||||
paths:
|
||||
- spec.json
|
||||
reports:
|
||||
dotenv: build.env
|
||||
expire_in: 42 years
|
||||
script:
|
||||
- mix pleroma.openapi_spec spec.json
|
||||
- echo "SPEC_BUILD_JOB_ID=$CI_JOB_ID" >> build.env
|
||||
|
||||
benchmark:
|
||||
extends:
|
||||
|
|
@ -153,6 +165,7 @@ unit-testing-1.15.8-otp-26:
|
|||
- su testuser -c "HOME=/home/testuser mix pleroma.test_runner --cover --preload-modules"
|
||||
coverage: '/^Line total: ([^ ]*%)$/'
|
||||
artifacts:
|
||||
expire_in: 30 days
|
||||
reports:
|
||||
coverage_report:
|
||||
coverage_format: cobertura
|
||||
|
|
@ -171,6 +184,7 @@ unit-testing-1.18.3-otp-27:
|
|||
|
||||
formatting-1.15:
|
||||
extends: .build_changes_policy
|
||||
artifacts: *default_artifacts
|
||||
image: &formatting_elixir elixir:1.15-alpine
|
||||
stage: lint
|
||||
cache: *testing_cache_policy
|
||||
|
|
@ -185,6 +199,7 @@ formatting-1.15:
|
|||
|
||||
cycles-1.15:
|
||||
extends: .build_changes_policy
|
||||
artifacts: *default_artifacts
|
||||
image: *formatting_elixir
|
||||
stage: lint
|
||||
cache: {}
|
||||
|
|
@ -208,7 +223,7 @@ dialyzer:
|
|||
- .using-ci-base
|
||||
stage: lint
|
||||
allow_failure: true
|
||||
when: manual
|
||||
when: manual
|
||||
cache: *testing_cache_policy
|
||||
tags:
|
||||
- feld
|
||||
|
|
@ -217,15 +232,13 @@ dialyzer:
|
|||
|
||||
docs-deploy:
|
||||
stage: deploy
|
||||
cache: *testing_cache_policy
|
||||
image: alpine:latest
|
||||
trigger:
|
||||
project: pleroma/docs
|
||||
branch: master
|
||||
strategy: depend
|
||||
only:
|
||||
- stable@pleroma/pleroma
|
||||
- develop@pleroma/pleroma
|
||||
before_script:
|
||||
- apk add curl
|
||||
script:
|
||||
- curl --fail-with-body -X POST -F"token=$DOCS_PIPELINE_TRIGGER" -F'ref=master' -F"variables[BRANCH]=$CI_COMMIT_REF_NAME" https://git.pleroma.social/api/v4/projects/673/trigger/pipeline
|
||||
review_app:
|
||||
image: alpine:3.9
|
||||
stage: deploy
|
||||
|
|
@ -241,6 +254,7 @@ review_app:
|
|||
except:
|
||||
- master
|
||||
- develop
|
||||
artifacts: *default_artifacts
|
||||
script:
|
||||
- echo "$CI_ENVIRONMENT_SLUG"
|
||||
- mkdir -p ~/.ssh
|
||||
|
|
@ -257,21 +271,19 @@ review_app:
|
|||
|
||||
spec-deploy:
|
||||
stage: deploy
|
||||
artifacts:
|
||||
paths:
|
||||
- spec.json
|
||||
trigger:
|
||||
project: pleroma/api-docs
|
||||
branch: master
|
||||
strategy: depend
|
||||
only:
|
||||
- develop@pleroma/pleroma
|
||||
image: alpine:latest
|
||||
before_script:
|
||||
- apk add curl
|
||||
script:
|
||||
- curl --fail-with-body -X POST -F"token=$API_DOCS_PIPELINE_TRIGGER" -F'ref=master' -F"variables[BRANCH]=$CI_COMMIT_REF_NAME" -F"variables[JOB_REF]=$CI_JOB_ID" https://git.pleroma.social/api/v4/projects/1130/trigger/pipeline
|
||||
|
||||
variables:
|
||||
SPEC_BUILD_JOB_ID: $SPEC_BUILD_JOB_ID
|
||||
|
||||
stop_review_app:
|
||||
image: alpine:3.9
|
||||
stage: deploy
|
||||
artifacts: *default_artifacts
|
||||
before_script:
|
||||
- apk update && apk add openssh-client git
|
||||
when: manual
|
||||
|
|
|
|||
1
changelog.d/authorized_fetch.fix
Normal file
1
changelog.d/authorized_fetch.fix
Normal file
|
|
@ -0,0 +1 @@
|
|||
Fix fetching public keys with authorized fetch enabled
|
||||
1
changelog.d/blocked-muted-swagger.change
Normal file
1
changelog.d/blocked-muted-swagger.change
Normal file
|
|
@ -0,0 +1 @@
|
|||
Use separate schemas for muted/blocked accounts lists
|
||||
1
changelog.d/changelog-checker.skip
Normal file
1
changelog.d/changelog-checker.skip
Normal file
|
|
@ -0,0 +1 @@
|
|||
Fix CI changelog checker
|
||||
0
changelog.d/ci-artifacts.skip
Normal file
0
changelog.d/ci-artifacts.skip
Normal file
1
changelog.d/description.skip
Normal file
1
changelog.d/description.skip
Normal file
|
|
@ -0,0 +1 @@
|
|||
Use :list_behaviour_implementations for LanguageDetector and Translation providers
|
||||
1
changelog.d/endorsements-api.change
Normal file
1
changelog.d/endorsements-api.change
Normal file
|
|
@ -0,0 +1 @@
|
|||
Support new Mastodon API for endorsed accounts
|
||||
1
changelog.d/fediindex.change
Normal file
1
changelog.d/fediindex.change
Normal file
|
|
@ -0,0 +1 @@
|
|||
Allow FediIndex crawler bot by default
|
||||
1
changelog.d/filter-user-capabilities.add
Normal file
1
changelog.d/filter-user-capabilities.add
Normal file
|
|
@ -0,0 +1 @@
|
|||
Allow filtering users with `accepts_chat_messages` capability
|
||||
1
changelog.d/instance-view-timeline-access.add
Normal file
1
changelog.d/instance-view-timeline-access.add
Normal file
|
|
@ -0,0 +1 @@
|
|||
Add `timelines_access` to InstanceView
|
||||
1
changelog.d/local-nickname-regex.fix
Normal file
1
changelog.d/local-nickname-regex.fix
Normal file
|
|
@ -0,0 +1 @@
|
|||
Use end-of-string in regex for local `get_by_nickname`
|
||||
1
changelog.d/lookup-restrict-unauthenticated.fix
Normal file
1
changelog.d/lookup-restrict-unauthenticated.fix
Normal file
|
|
@ -0,0 +1 @@
|
|||
Respect restrict_unauthenticated in /api/v1/accounts/lookup
|
||||
1
changelog.d/mastodon-quotes-updates.change
Normal file
1
changelog.d/mastodon-quotes-updates.change
Normal file
|
|
@ -0,0 +1 @@
|
|||
Use Mastodon-compatible route for quotes list and param for quotes count
|
||||
1
changelog.d/mrf-inlinequotes-mastodon.fix
Normal file
1
changelog.d/mrf-inlinequotes-mastodon.fix
Normal file
|
|
@ -0,0 +1 @@
|
|||
MRF InlineQuotePolicy: Don't inline quoted post URL in Mastodon quote posts
|
||||
1
changelog.d/nodeinfo-content-type.fix
Normal file
1
changelog.d/nodeinfo-content-type.fix
Normal file
|
|
@ -0,0 +1 @@
|
|||
Fix NodeInfo content-type
|
||||
1
changelog.d/normalize-actor-image-hrefs.fix
Normal file
1
changelog.d/normalize-actor-image-hrefs.fix
Normal file
|
|
@ -0,0 +1 @@
|
|||
Add Actor images normalization from array of urls to string
|
||||
0
changelog.d/notification-cleanup.skip
Normal file
0
changelog.d/notification-cleanup.skip
Normal file
1
changelog.d/notification-view-deduplicate.skip
Normal file
1
changelog.d/notification-view-deduplicate.skip
Normal file
|
|
@ -0,0 +1 @@
|
|||
remove duplicated code from notificationview
|
||||
1
changelog.d/order-favourites-reblogs.change
Normal file
1
changelog.d/order-favourites-reblogs.change
Normal file
|
|
@ -0,0 +1 @@
|
|||
Order favourites and reblogs list from newest to oldest
|
||||
1
changelog.d/outgoing-follow-requests.add
Normal file
1
changelog.d/outgoing-follow-requests.add
Normal file
|
|
@ -0,0 +1 @@
|
|||
Add /api/v1/pleroma/outgoing_follow_requests
|
||||
1
changelog.d/pin-chats.fix
Normal file
1
changelog.d/pin-chats.fix
Normal file
|
|
@ -0,0 +1 @@
|
|||
Allow to pin/unpip chats
|
||||
1
changelog.d/plaroma.skip
Normal file
1
changelog.d/plaroma.skip
Normal file
|
|
@ -0,0 +1 @@
|
|||
i don't think it's called plaroma
|
||||
1
changelog.d/preferred-frontend.add
Normal file
1
changelog.d/preferred-frontend.add
Normal file
|
|
@ -0,0 +1 @@
|
|||
Allow users to select preferred frontend
|
||||
1
changelog.d/remote-url.fix
Normal file
1
changelog.d/remote-url.fix
Normal file
|
|
@ -0,0 +1 @@
|
|||
`remote_url` links to unproxied URL
|
||||
1
changelog.d/rich-media-user-agent.add
Normal file
1
changelog.d/rich-media-user-agent.add
Normal file
|
|
@ -0,0 +1 @@
|
|||
Allow setting custom user-agent for fetching rich media content
|
||||
1
changelog.d/rss-redirect.change
Normal file
1
changelog.d/rss-redirect.change
Normal file
|
|
@ -0,0 +1 @@
|
|||
Redirect /users/:nickname.rss to /users/:nickname/feed.rss instead of .atom
|
||||
1
changelog.d/scrobbles-scope.change
Normal file
1
changelog.d/scrobbles-scope.change
Normal file
|
|
@ -0,0 +1 @@
|
|||
Add `write:scrobbles` and `read:scrobbles` scope for scrobbling
|
||||
1
changelog.d/scrubber-inline-quotes-mastodon.add
Normal file
1
changelog.d/scrubber-inline-quotes-mastodon.add
Normal file
|
|
@ -0,0 +1 @@
|
|||
Scrubber: Allow `quote-inline` class in <p> tags used by Mastodon quotes
|
||||
1
changelog.d/scrubber-span-classes.change
Normal file
1
changelog.d/scrubber-span-classes.change
Normal file
|
|
@ -0,0 +1 @@
|
|||
Allow "invisible" and "ellipsis" classes for span tags to match Mastodon behavior
|
||||
1
changelog.d/status-push-notification.fix
Normal file
1
changelog.d/status-push-notification.fix
Normal file
|
|
@ -0,0 +1 @@
|
|||
Send push notifications for statuses from subscribed accounts
|
||||
1
changelog.d/stream-marker-updates.add
Normal file
1
changelog.d/stream-marker-updates.add
Normal file
|
|
@ -0,0 +1 @@
|
|||
Stream marker updates
|
||||
1
changelog.d/translation-provider-mozhi.add
Normal file
1
changelog.d/translation-provider-mozhi.add
Normal file
|
|
@ -0,0 +1 @@
|
|||
Support Mozhi translation provider
|
||||
1
changelog.d/translation-provider-translatelocally.add
Normal file
1
changelog.d/translation-provider-translatelocally.add
Normal file
|
|
@ -0,0 +1 @@
|
|||
Support translateLocally translation provider
|
||||
1
changelog.d/url-encoding-pt2.fix
Normal file
1
changelog.d/url-encoding-pt2.fix
Normal file
|
|
@ -0,0 +1 @@
|
|||
Fix sometimes incorrect URI percent encoding
|
||||
|
|
@ -2131,6 +2131,11 @@ config :pleroma, :config_description, [
|
|||
description:
|
||||
"Amount of milliseconds after which the HTTP request is forcibly terminated.",
|
||||
suggestions: [5_000]
|
||||
},
|
||||
%{
|
||||
key: :user_agent,
|
||||
type: :string,
|
||||
description: "Custom User-Agent header to be used when fetching rich media content."
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -3328,6 +3333,12 @@ config :pleroma, :config_description, [
|
|||
description:
|
||||
"A map containing available frontends and parameters for their installation.",
|
||||
children: frontend_options
|
||||
},
|
||||
%{
|
||||
key: :pickable,
|
||||
type: {:list, :string},
|
||||
description:
|
||||
"A list containing all frontends users can pick as their preference, format is :name/:ref, e.g pleroma-fe/stable."
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -3534,9 +3545,7 @@ config :pleroma, :config_description, [
|
|||
%{
|
||||
key: :provider,
|
||||
type: :module,
|
||||
suggestions: [
|
||||
Pleroma.Language.LanguageDetector.Fasttext
|
||||
]
|
||||
suggestions: {:list_behaviour_implementations, Pleroma.Language.LanguageDetector.Provider}
|
||||
},
|
||||
%{
|
||||
group: {:subgroup, Pleroma.Language.LanguageDetector.Fasttext},
|
||||
|
|
@ -3556,10 +3565,7 @@ config :pleroma, :config_description, [
|
|||
%{
|
||||
key: :provider,
|
||||
type: :module,
|
||||
suggestions: [
|
||||
Pleroma.Language.Translation.Deepl,
|
||||
Pleroma.Language.Translation.Libretranslate
|
||||
]
|
||||
suggestions: {:list_behaviour_implementations, Pleroma.Language.Translation.Provider}
|
||||
},
|
||||
%{
|
||||
group: {:subgroup, Pleroma.Language.Translation.Deepl},
|
||||
|
|
@ -3588,6 +3594,27 @@ config :pleroma, :config_description, [
|
|||
label: "LibreTranslate API Key",
|
||||
type: :string,
|
||||
suggestions: ["YOUR_API_KEY"]
|
||||
},
|
||||
%{
|
||||
group: {:subgroup, Pleroma.Language.Translation.TranslateLocally},
|
||||
key: :intermediary_language,
|
||||
label:
|
||||
"translateLocally intermediary language (used when direct source->target model is not available)",
|
||||
type: :string,
|
||||
suggestions: ["en"]
|
||||
},
|
||||
%{
|
||||
group: {:subgroup, Pleroma.Language.Translation.Mozhi},
|
||||
key: :base_url,
|
||||
label: "Mozhi instance URL",
|
||||
type: :string
|
||||
},
|
||||
%{
|
||||
group: {:subgroup, Pleroma.Language.Translation.Mozhi},
|
||||
key: :engine,
|
||||
label: "Engine used for Mozhi",
|
||||
type: :string,
|
||||
suggestions: ["libretranslate"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,9 +66,9 @@ Returned data:
|
|||
"username": "somenick",
|
||||
...
|
||||
},
|
||||
"id" : "1",
|
||||
"unread" : 2,
|
||||
"last_message" : {...}, // The last message in that chat
|
||||
"id": "1",
|
||||
"unread": 2,
|
||||
"last_message": {...}, // The last message in that chat
|
||||
"updated_at": "2020-04-21T15:11:46.000Z"
|
||||
}
|
||||
```
|
||||
|
|
@ -93,8 +93,8 @@ Returned data:
|
|||
"username": "somenick",
|
||||
...
|
||||
},
|
||||
"id" : "1",
|
||||
"unread" : 0,
|
||||
"id": "1",
|
||||
"unread": 0,
|
||||
"updated_at": "2020-04-21T15:11:46.000Z"
|
||||
}
|
||||
```
|
||||
|
|
@ -111,7 +111,7 @@ The modified chat message
|
|||
|
||||
### Getting a list of Chats
|
||||
|
||||
`GET /api/v1/pleroma/chats`
|
||||
`GET /api/v2/pleroma/chats`
|
||||
|
||||
This will return a list of chats that you have been involved in, sorted by their
|
||||
last update (so new chats will be at the top).
|
||||
|
|
@ -119,6 +119,7 @@ last update (so new chats will be at the top).
|
|||
Parameters:
|
||||
|
||||
- with_muted: Include chats from muted users (boolean).
|
||||
- pinned: Include only pinned chats (boolean).
|
||||
|
||||
Returned data:
|
||||
|
||||
|
|
@ -130,16 +131,16 @@ Returned data:
|
|||
"username": "somenick",
|
||||
...
|
||||
},
|
||||
"id" : "1",
|
||||
"unread" : 2,
|
||||
"last_message" : {...}, // The last message in that chat
|
||||
"id": "1",
|
||||
"unread": 2,
|
||||
"last_message": {...}, // The last message in that chat
|
||||
"updated_at": "2020-04-21T15:11:46.000Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
The recipient of messages that are sent to this chat is given by their AP ID.
|
||||
No pagination is implemented for now.
|
||||
The usual pagination options are implemented.
|
||||
|
||||
### Getting the messages for a Chat
|
||||
|
||||
|
|
@ -226,6 +227,32 @@ Deleting a chat message for given Chat id works like this:
|
|||
|
||||
Returned data is the deleted message.
|
||||
|
||||
### Pinning a chat
|
||||
|
||||
Pinning a chat works like this:
|
||||
|
||||
`POST /api/v1/pleroma/chats/:id/pin`
|
||||
|
||||
Returned data:
|
||||
|
||||
```json
|
||||
{
|
||||
"account": {
|
||||
"id": "someflakeid",
|
||||
"username": "somenick",
|
||||
...
|
||||
},
|
||||
"id": "1",
|
||||
"unread": 0,
|
||||
"updated_at": "2020-04-21T15:11:46.000Z",
|
||||
"pinned": true,
|
||||
}
|
||||
```
|
||||
|
||||
To unpin a pinned chat, use:
|
||||
|
||||
`POST /api/v1/pleroma/chats/:id/unpin`
|
||||
|
||||
### Notifications
|
||||
|
||||
There's a new `pleroma:chat_mention` notification, which has this form. It is not given out in the notifications endpoint by default, you need to explicitly request it with `include_types[]=pleroma:chat_mention`:
|
||||
|
|
|
|||
|
|
@ -39,7 +39,6 @@ Has these additional fields under the `pleroma` object:
|
|||
- `emoji_reactions`: A list with emoji / reaction maps. The format is `{name: "☕", count: 1, me: true}`. Contains no information about the reacting users, for that use the `/statuses/:id/reactions` endpoint.
|
||||
- `parent_visible`: If the parent of this post is visible to the user or not.
|
||||
- `pinned_at`: a datetime (iso8601) when status was pinned, `null` otherwise.
|
||||
- `quotes_count`: the count of status quotes.
|
||||
- `bookmark_folder`: the ID of the folder bookmark is stored within (if any).
|
||||
- `list_id`: the ID of the list the post is addressed to (if any, only returned to author).
|
||||
|
||||
|
|
|
|||
|
|
@ -684,6 +684,7 @@ Audio scrobbling in Pleroma is **deprecated**.
|
|||
### Creates a new Listen activity for an account
|
||||
* Method `POST`
|
||||
* Authentication: required
|
||||
* OAuth scope: `write:scrobbles`
|
||||
* Params:
|
||||
* `title`: the title of the media playing
|
||||
* `album`: the album of the media playing [optional]
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ Note: the packages are not required with the current default settings of Pleroma
|
|||
|
||||
It is required for the following Pleroma features:
|
||||
|
||||
* `Pleroma.Upload.Filters.Mogrify`, `Pleroma.Upload.Filters.Mogrifun` upload filters (related config: `Plaroma.Upload/filters` in `config/config.exs`)
|
||||
* `Pleroma.Upload.Filters.Mogrify`, `Pleroma.Upload.Filters.Mogrifun` upload filters (related config: `Pleroma.Upload/filters` in `config/config.exs`)
|
||||
* Media preview proxy for still images (related config: `media_preview_proxy/enabled` in `config/config.exs`)
|
||||
|
||||
## `ffmpeg`
|
||||
|
|
@ -33,5 +33,5 @@ It is required for the following Pleroma features:
|
|||
|
||||
It is required for the following Pleroma features:
|
||||
|
||||
* `Pleroma.Upload.Filters.Exiftool.StripLocation` upload filter (related config: `Plaroma.Upload/filters` in `config/config.exs`)
|
||||
* `Pleroma.Upload.Filters.Exiftool.ReadDescription` upload filter (related config: `Plaroma.Upload/filters` in `config/config.exs`)
|
||||
* `Pleroma.Upload.Filters.Exiftool.StripLocation` upload filter (related config: `Pleroma.Upload/filters` in `config/config.exs`)
|
||||
* `Pleroma.Upload.Filters.Exiftool.ReadDescription` upload filter (related config: `Pleroma.Upload/filters` in `config/config.exs`)
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ defmodule Pleroma.Chat do
|
|||
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
||||
field(:recipient, :string)
|
||||
|
||||
field(:pinned, :boolean)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
|
|
@ -94,4 +96,16 @@ defmodule Pleroma.Chat do
|
|||
order_by: [desc: c.updated_at]
|
||||
)
|
||||
end
|
||||
|
||||
def pin(%__MODULE__{} = chat) do
|
||||
chat
|
||||
|> cast(%{pinned: true}, [:pinned])
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
def unpin(%__MODULE__{} = chat) do
|
||||
chat
|
||||
|> cast(%{pinned: false}, [:pinned])
|
||||
|> Repo.update()
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -132,6 +132,13 @@ defmodule Pleroma.Constants do
|
|||
do: ~r/^[^[:cntrl:] ()<>@,;:\\"\/\[\]?=]+\/[^[:cntrl:] ()<>@,;:\\"\/\[\]?=]+(; .*)?$/
|
||||
)
|
||||
|
||||
# List of allowed chars in the path segment of a URI
|
||||
# unreserved, sub-delims, ":", "@" and "/" allowed as the separator in path
|
||||
# https://datatracker.ietf.org/doc/html/rfc3986
|
||||
const(uri_path_allowed_reserved_chars,
|
||||
do: ~c"!$&'()*+,;=/:@"
|
||||
)
|
||||
|
||||
const(upload_object_types, do: ["Document", "Image"])
|
||||
|
||||
const(activity_json_canonical_mime_type,
|
||||
|
|
|
|||
|
|
@ -157,6 +157,16 @@ defmodule Pleroma.FollowingRelationship do
|
|||
|> Repo.all()
|
||||
end
|
||||
|
||||
def get_outgoing_follow_requests(%User{id: id}) do
|
||||
__MODULE__
|
||||
|> join(:inner, [r], f in assoc(r, :following))
|
||||
|> where([r], r.state == ^:follow_pending)
|
||||
|> where([r], r.follower_id == ^id)
|
||||
|> where([r, f], f.is_active == true)
|
||||
|> select([r, f], f)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
def following?(%User{id: follower_id}, %User{id: followed_id}) do
|
||||
__MODULE__
|
||||
|> where(follower_id: ^follower_id, following_id: ^followed_id, state: ^:follow_accept)
|
||||
|
|
|
|||
|
|
@ -131,31 +131,4 @@ defmodule Pleroma.HTTP do
|
|||
|
||||
defp default_middleware,
|
||||
do: [Tesla.Middleware.FollowRedirects, Pleroma.Tesla.Middleware.EncodeUrl]
|
||||
|
||||
def encode_url(url) when is_binary(url) do
|
||||
URI.parse(url)
|
||||
|> then(fn parsed ->
|
||||
path = encode_path(parsed.path)
|
||||
query = encode_query(parsed.query)
|
||||
|
||||
%{parsed | path: path, query: query}
|
||||
end)
|
||||
|> URI.to_string()
|
||||
end
|
||||
|
||||
defp encode_path(nil), do: nil
|
||||
|
||||
defp encode_path(path) when is_binary(path) do
|
||||
path
|
||||
|> URI.decode()
|
||||
|> URI.encode()
|
||||
end
|
||||
|
||||
defp encode_query(nil), do: nil
|
||||
|
||||
defp encode_query(query) when is_binary(query) do
|
||||
query
|
||||
|> URI.decode_query()
|
||||
|> URI.encode_query()
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -16,7 +16,12 @@ defmodule Pleroma.HTTP.AdapterHelper.Hackney do
|
|||
|
||||
config_opts = Pleroma.Config.get([:http, :adapter], [])
|
||||
|
||||
url_encoding =
|
||||
Keyword.new()
|
||||
|> Keyword.put(:path_encode_fun, fn path -> path end)
|
||||
|
||||
@defaults
|
||||
|> Keyword.merge(url_encoding)
|
||||
|> Keyword.merge(config_opts)
|
||||
|> Keyword.merge(connection_opts)
|
||||
|> add_scheme_opts(uri)
|
||||
|
|
|
|||
109
lib/pleroma/language/translation/mozhi.ex
Normal file
109
lib/pleroma/language/translation/mozhi.ex
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Language.Translation.Mozhi do
|
||||
import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1]
|
||||
|
||||
alias Pleroma.Language.Translation.Provider
|
||||
|
||||
use Provider
|
||||
|
||||
@behaviour Provider
|
||||
|
||||
@name "Mozhi"
|
||||
|
||||
@impl Provider
|
||||
def configured?, do: not_empty_string(base_url()) and not_empty_string(engine())
|
||||
|
||||
@impl Provider
|
||||
def translate(content, source_language, target_language) do
|
||||
endpoint =
|
||||
base_url()
|
||||
|> URI.merge("/api/translate")
|
||||
|> URI.to_string()
|
||||
|
||||
case Pleroma.HTTP.get(
|
||||
endpoint <>
|
||||
"?" <>
|
||||
URI.encode_query(%{
|
||||
engine: engine(),
|
||||
text: content,
|
||||
from: source_language,
|
||||
to: target_language
|
||||
}),
|
||||
[{"Accept", "application/json"}]
|
||||
) do
|
||||
{:ok, %{status: 200} = res} ->
|
||||
%{
|
||||
"translated-text" => content,
|
||||
"source_language" => source_language
|
||||
} = Jason.decode!(res.body)
|
||||
|
||||
{:ok,
|
||||
%{
|
||||
content: content,
|
||||
detected_source_language: source_language,
|
||||
provider: @name
|
||||
}}
|
||||
|
||||
_ ->
|
||||
{:error, :internal_server_error}
|
||||
end
|
||||
end
|
||||
|
||||
@impl Provider
|
||||
def supported_languages(type) when type in [:source, :target] do
|
||||
path =
|
||||
case type do
|
||||
:source -> "/api/source_languages"
|
||||
:target -> "/api/target_languages"
|
||||
end
|
||||
|
||||
endpoint =
|
||||
base_url()
|
||||
|> URI.merge(path)
|
||||
|> URI.to_string()
|
||||
|
||||
case Pleroma.HTTP.get(
|
||||
endpoint <>
|
||||
"?" <>
|
||||
URI.encode_query(%{
|
||||
engine: engine()
|
||||
}),
|
||||
[{"Accept", "application/json"}]
|
||||
) do
|
||||
{:ok, %{status: 200} = res} ->
|
||||
languages =
|
||||
Jason.decode!(res.body)
|
||||
|> Enum.map(fn %{"Id" => language} -> language end)
|
||||
|
||||
{:ok, languages}
|
||||
|
||||
_ ->
|
||||
{:error, :internal_server_error}
|
||||
end
|
||||
end
|
||||
|
||||
@impl Provider
|
||||
def languages_matrix do
|
||||
with {:ok, source_languages} <- supported_languages(:source),
|
||||
{:ok, target_languages} <- supported_languages(:target) do
|
||||
{:ok,
|
||||
Map.new(source_languages, fn language -> {language, target_languages -- [language]} end)}
|
||||
else
|
||||
{:error, error} -> {:error, error}
|
||||
end
|
||||
end
|
||||
|
||||
@impl Provider
|
||||
def name, do: @name
|
||||
|
||||
defp base_url do
|
||||
Pleroma.Config.get([__MODULE__, :base_url])
|
||||
end
|
||||
|
||||
defp engine do
|
||||
Pleroma.Config.get([__MODULE__, :engine])
|
||||
end
|
||||
end
|
||||
129
lib/pleroma/language/translation/translate_locally.ex
Normal file
129
lib/pleroma/language/translation/translate_locally.ex
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Language.Translation.TranslateLocally do
|
||||
alias Pleroma.Language.Translation.Provider
|
||||
|
||||
use Provider
|
||||
|
||||
@behaviour Provider
|
||||
|
||||
@name "translateLocally"
|
||||
|
||||
@impl Provider
|
||||
def missing_dependencies do
|
||||
if Pleroma.Utils.command_available?("translateLocally") do
|
||||
[]
|
||||
else
|
||||
["translateLocally"]
|
||||
end
|
||||
end
|
||||
|
||||
@impl Provider
|
||||
def configured?, do: is_map(models())
|
||||
|
||||
@impl Provider
|
||||
def translate(content, source_language, target_language) do
|
||||
model =
|
||||
models()
|
||||
|> Map.get(source_language, %{})
|
||||
|> Map.get(target_language)
|
||||
|
||||
models =
|
||||
if model do
|
||||
[model]
|
||||
else
|
||||
[
|
||||
models()
|
||||
|> Map.get(source_language, %{})
|
||||
|> Map.get(intermediary_language()),
|
||||
models()
|
||||
|> Map.get(intermediary_language(), %{})
|
||||
|> Map.get(target_language)
|
||||
]
|
||||
end
|
||||
|
||||
translated_content =
|
||||
Enum.reduce(models, content, fn model, content ->
|
||||
text_path = Path.join(System.tmp_dir!(), "translateLocally-#{Ecto.UUID.generate()}")
|
||||
|
||||
File.write(text_path, content)
|
||||
|
||||
translated_content =
|
||||
case System.cmd("translateLocally", ["-m", model, "-i", text_path, "--html"]) do
|
||||
{content, _} -> content
|
||||
_ -> nil
|
||||
end
|
||||
|
||||
File.rm(text_path)
|
||||
|
||||
translated_content
|
||||
end)
|
||||
|
||||
{:ok,
|
||||
%{
|
||||
content: translated_content,
|
||||
detected_source_language: source_language,
|
||||
provider: @name
|
||||
}}
|
||||
end
|
||||
|
||||
@impl Provider
|
||||
def supported_languages(:source) do
|
||||
languages =
|
||||
languages_matrix()
|
||||
|> elem(1)
|
||||
|> Map.keys()
|
||||
|
||||
{:ok, languages}
|
||||
end
|
||||
|
||||
@impl Provider
|
||||
def supported_languages(:target) do
|
||||
languages =
|
||||
languages_matrix()
|
||||
|> elem(1)
|
||||
|> Map.values()
|
||||
|> List.flatten()
|
||||
|> Enum.uniq()
|
||||
|
||||
{:ok, languages}
|
||||
end
|
||||
|
||||
@impl Provider
|
||||
def languages_matrix do
|
||||
languages =
|
||||
models()
|
||||
|> Map.to_list()
|
||||
|> Enum.map(fn {key, value} -> {key, Map.keys(value)} end)
|
||||
|> Enum.into(%{})
|
||||
|
||||
matrix =
|
||||
if intermediary_language() do
|
||||
languages
|
||||
|> Map.to_list()
|
||||
|> Enum.map(fn {key, value} ->
|
||||
with_intermediary =
|
||||
(((value ++ languages[intermediary_language()])
|
||||
|> Enum.uniq()) --
|
||||
[key])
|
||||
|> Enum.sort()
|
||||
|
||||
{key, with_intermediary}
|
||||
end)
|
||||
|> Enum.into(%{})
|
||||
else
|
||||
languages
|
||||
end
|
||||
|
||||
{:ok, matrix}
|
||||
end
|
||||
|
||||
@impl Provider
|
||||
def name, do: @name
|
||||
|
||||
defp models, do: Pleroma.Config.get([__MODULE__, :models])
|
||||
|
||||
defp intermediary_language, do: Pleroma.Config.get([__MODULE__, :intermediary_language])
|
||||
end
|
||||
|
|
@ -282,10 +282,15 @@ defmodule Pleroma.Notification do
|
|||
select: n.id
|
||||
)
|
||||
|
||||
Multi.new()
|
||||
|> Multi.update_all(:ids, query, set: [seen: true, updated_at: NaiveDateTime.utc_now()])
|
||||
|> Marker.multi_set_last_read_id(user, "notifications")
|
||||
|> Repo.transaction()
|
||||
{:ok, %{marker: marker}} =
|
||||
Multi.new()
|
||||
|> Multi.update_all(:ids, query, set: [seen: true, updated_at: NaiveDateTime.utc_now()])
|
||||
|> Marker.multi_set_last_read_id(user, "notifications")
|
||||
|> Repo.transaction()
|
||||
|
||||
Streamer.stream(["user", "user:notification"], marker)
|
||||
|
||||
{:ok, %{marker: marker}}
|
||||
end
|
||||
|
||||
@spec read_one(User.t(), String.t()) ::
|
||||
|
|
@ -526,9 +531,7 @@ defmodule Pleroma.Notification do
|
|||
%Activity{data: %{"type" => "Create"}} = activity,
|
||||
local_only
|
||||
) do
|
||||
notification_enabled_ap_ids =
|
||||
[]
|
||||
|> Utils.maybe_notify_subscribers(activity)
|
||||
notification_enabled_ap_ids = Utils.get_notified_subscribers(activity)
|
||||
|
||||
potential_receivers =
|
||||
User.get_users_from_set(notification_enabled_ap_ids, local_only: local_only)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.ReverseProxy do
|
||||
alias Pleroma.Utils.URIEncoding
|
||||
|
||||
@range_headers ~w(range if-range)
|
||||
@keep_req_headers ~w(accept accept-encoding cache-control if-modified-since) ++
|
||||
~w(if-unmodified-since if-none-match) ++ @range_headers
|
||||
|
|
@ -155,11 +157,12 @@ defmodule Pleroma.ReverseProxy do
|
|||
end
|
||||
|
||||
defp request(method, url, headers, opts) do
|
||||
Logger.debug("#{__MODULE__} #{method} #{url} #{inspect(headers)}")
|
||||
method = method |> String.downcase() |> String.to_existing_atom()
|
||||
|
||||
url = maybe_encode_url(url)
|
||||
|
||||
Logger.debug("#{__MODULE__} #{method} #{url} #{inspect(headers)}")
|
||||
|
||||
case client().request(method, url, headers, "", opts) do
|
||||
{:ok, code, headers, client} when code in @valid_resp_codes ->
|
||||
{:ok, code, downcase_headers(headers), client}
|
||||
|
|
@ -459,9 +462,9 @@ defmodule Pleroma.ReverseProxy do
|
|||
# Also do it for test environment
|
||||
defp maybe_encode_url(url) do
|
||||
case Application.get_env(:tesla, :adapter) do
|
||||
Tesla.Adapter.Hackney -> Pleroma.HTTP.encode_url(url)
|
||||
{Tesla.Adapter.Finch, _} -> Pleroma.HTTP.encode_url(url)
|
||||
Tesla.Mock -> Pleroma.HTTP.encode_url(url)
|
||||
Tesla.Adapter.Hackney -> URIEncoding.encode_url(url)
|
||||
{Tesla.Adapter.Finch, _} -> URIEncoding.encode_url(url)
|
||||
Tesla.Mock -> URIEncoding.encode_url(url)
|
||||
_ -> url
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,6 +7,11 @@ defmodule Pleroma.ReverseProxy.Client.Hackney do
|
|||
|
||||
@impl true
|
||||
def request(method, url, headers, body, opts \\ []) do
|
||||
opts =
|
||||
Keyword.put_new(opts, :path_encode_fun, fn path ->
|
||||
path
|
||||
end)
|
||||
|
||||
:hackney.request(method, url, headers, body, opts)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ defmodule Pleroma.Signature do
|
|||
|
||||
def fetch_public_key(conn) do
|
||||
with {:ok, actor_id} <- get_actor_id(conn),
|
||||
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
|
||||
{:ok, public_key} <- User.get_or_fetch_public_key_for_ap_id(actor_id) do
|
||||
{:ok, public_key}
|
||||
else
|
||||
e ->
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ defmodule Pleroma.Tesla.Middleware.EncodeUrl do
|
|||
|
||||
@impl Tesla.Middleware
|
||||
def call(%Tesla.Env{url: url} = env, next, _) do
|
||||
url = Pleroma.HTTP.encode_url(url)
|
||||
url = Pleroma.Utils.URIEncoding.encode_url(url)
|
||||
|
||||
env = %{env | url: url}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ defmodule Pleroma.Upload do
|
|||
"""
|
||||
alias Ecto.UUID
|
||||
alias Pleroma.Maps
|
||||
alias Pleroma.Utils.URIEncoding
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
require Logger
|
||||
|
||||
|
|
@ -230,11 +231,18 @@ defmodule Pleroma.Upload do
|
|||
tmp_path
|
||||
end
|
||||
|
||||
# Encoding the whole path here is fine since the path is in a
|
||||
# UUID/<file name> form.
|
||||
# The file at this point isn't %-encoded, so the path shouldn't
|
||||
# be decoded first like Pleroma.Utils.URIEncoding.encode_url/1 does.
|
||||
defp url_from_spec(%__MODULE__{name: name}, base_url, {:file, path}) do
|
||||
encode_opts = [bypass_decode: true, bypass_parse: true]
|
||||
|
||||
path =
|
||||
URI.encode(path, &char_unescaped?/1) <>
|
||||
URIEncoding.encode_url(path, encode_opts) <>
|
||||
if Pleroma.Config.get([__MODULE__, :link_name], false) do
|
||||
"?name=#{URI.encode(name, &char_unescaped?/1)}"
|
||||
enum = %{name: name}
|
||||
"?#{URI.encode_query(enum)}"
|
||||
else
|
||||
""
|
||||
end
|
||||
|
|
|
|||
|
|
@ -233,8 +233,8 @@ defmodule Pleroma.User do
|
|||
for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
|
||||
@user_relationships_config do
|
||||
# `def blocked_users_relation/2`, `def muted_users_relation/2`,
|
||||
# `def reblog_muted_users_relation/2`, `def notification_muted_users/2`,
|
||||
# `def subscriber_users/2`, `def endorsed_users_relation/2`
|
||||
# `def reblog_muted_users_relation/2`, `def notification_muted_users_relation/2`,
|
||||
# `def subscriber_users_relation/2`, `def endorsed_users_relation/2`
|
||||
def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
|
||||
target_users_query = assoc(user, unquote(outgoing_relation_target))
|
||||
|
||||
|
|
@ -288,6 +288,7 @@ defmodule Pleroma.User do
|
|||
defdelegate following?(follower, followed), to: FollowingRelationship
|
||||
defdelegate following_ap_ids(user), to: FollowingRelationship
|
||||
defdelegate get_follow_requests(user), to: FollowingRelationship
|
||||
defdelegate get_outgoing_follow_requests(user), to: FollowingRelationship
|
||||
defdelegate search(query, opts \\ []), to: User.Search
|
||||
|
||||
@doc """
|
||||
|
|
@ -1357,7 +1358,7 @@ defmodule Pleroma.User do
|
|||
@spec get_by_nickname(String.t()) :: User.t() | nil
|
||||
def get_by_nickname(nickname) do
|
||||
Repo.get_by(User, nickname: nickname) ||
|
||||
if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
|
||||
if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()}$)i, nickname) do
|
||||
Repo.get_by(User, nickname: local_nickname(nickname))
|
||||
end
|
||||
end
|
||||
|
|
@ -2307,6 +2308,15 @@ defmodule Pleroma.User do
|
|||
|
||||
def public_key(_), do: {:error, "key not found"}
|
||||
|
||||
def get_or_fetch_public_key_for_ap_id(ap_id) do
|
||||
with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
|
||||
{:ok, public_key} <- public_key(user) do
|
||||
{:ok, public_key}
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
def get_public_key_for_ap_id(ap_id) do
|
||||
with %User{} = user <- get_cached_by_ap_id(ap_id),
|
||||
{:ok, public_key} <- public_key(user) do
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ defmodule Pleroma.User.Search do
|
|||
following = Keyword.get(opts, :following, false)
|
||||
result_limit = Keyword.get(opts, :limit, @limit)
|
||||
offset = Keyword.get(opts, :offset, 0)
|
||||
capabilities = Keyword.get(opts, :capabilities, [])
|
||||
|
||||
for_user = Keyword.get(opts, :for_user)
|
||||
|
||||
|
|
@ -32,7 +33,7 @@ defmodule Pleroma.User.Search do
|
|||
|
||||
results =
|
||||
query_string
|
||||
|> search_query(for_user, following, top_user_ids)
|
||||
|> search_query(for_user, following, top_user_ids, capabilities)
|
||||
|> Pagination.fetch_paginated(%{"offset" => offset, "limit" => result_limit}, :offset)
|
||||
|
||||
results
|
||||
|
|
@ -80,7 +81,7 @@ defmodule Pleroma.User.Search do
|
|||
end
|
||||
end
|
||||
|
||||
defp search_query(query_string, for_user, following, top_user_ids) do
|
||||
defp search_query(query_string, for_user, following, top_user_ids, capabilities) do
|
||||
for_user
|
||||
|> base_query(following)
|
||||
|> filter_blocked_user(for_user)
|
||||
|
|
@ -94,6 +95,7 @@ defmodule Pleroma.User.Search do
|
|||
|> subquery()
|
||||
|> order_by(desc: :search_rank)
|
||||
|> maybe_restrict_local(for_user)
|
||||
|> maybe_restrict_accepting_chat_messages(capabilities)
|
||||
|> filter_deactivated_users()
|
||||
end
|
||||
|
||||
|
|
@ -214,6 +216,14 @@ defmodule Pleroma.User.Search do
|
|||
end
|
||||
end
|
||||
|
||||
defp maybe_restrict_accepting_chat_messages(query, capabilities) do
|
||||
if "accepts_chat_messages" in capabilities do
|
||||
from(q in query, where: q.accepts_chat_messages == true)
|
||||
else
|
||||
query
|
||||
end
|
||||
end
|
||||
|
||||
defp limit, do: Pleroma.Config.get([:instance, :limit_to_local_content], :unauthenticated)
|
||||
|
||||
defp restrict_local(q), do: where(q, [u], u.local == true)
|
||||
|
|
|
|||
142
lib/pleroma/utils/uri_encoding.ex
Normal file
142
lib/pleroma/utils/uri_encoding.ex
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2025 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Utils.URIEncoding do
|
||||
@moduledoc """
|
||||
Utility functions for dealing with URI encoding of paths and queries
|
||||
with support for query-encoding quirks.
|
||||
"""
|
||||
require Pleroma.Constants
|
||||
|
||||
# We don't always want to decode the path first, like is the case in
|
||||
# Pleroma.Upload.url_from_spec/3.
|
||||
@doc """
|
||||
Wraps URI encoding/decoding functions from Elixir's standard library to fix usually unintended side-effects.
|
||||
|
||||
Supports two URL processing options in the optional 2nd argument with the default being `false`:
|
||||
|
||||
* `bypass_parse` - Bypasses `URI.parse` stage, useful when it's not desirable to parse to URL first
|
||||
before encoding it. Supports only encoding as the Path segment of a URI.
|
||||
* `bypass_decode` - Bypasses `URI.decode` stage for the Path segment of a URI. Used when a URL
|
||||
has to be double %-encoded for internal reasons.
|
||||
|
||||
Options must be specified as a Keyword with tuples with booleans, otherwise
|
||||
`{:error, :invalid_opts}` is returned. Example:
|
||||
`encode_url(url, [bypass_parse: true, bypass_decode: true])`
|
||||
"""
|
||||
@spec encode_url(String.t(), Keyword.t()) :: String.t() | {:error, :invalid_opts}
|
||||
def encode_url(url, opts \\ []) when is_binary(url) and is_list(opts) do
|
||||
bypass_parse = Keyword.get(opts, :bypass_parse, false)
|
||||
bypass_decode = Keyword.get(opts, :bypass_decode, false)
|
||||
|
||||
with true <- is_boolean(bypass_parse),
|
||||
true <- is_boolean(bypass_decode) do
|
||||
cond do
|
||||
bypass_parse ->
|
||||
encode_path(url, bypass_decode)
|
||||
|
||||
true ->
|
||||
URI.parse(url)
|
||||
|> then(fn parsed ->
|
||||
path = encode_path(parsed.path, bypass_decode)
|
||||
|
||||
query = encode_query(parsed.query, parsed.host)
|
||||
|
||||
%{parsed | path: path, query: query}
|
||||
end)
|
||||
|> URI.to_string()
|
||||
end
|
||||
else
|
||||
_ -> {:error, :invalid_opts}
|
||||
end
|
||||
end
|
||||
|
||||
defp encode_path(nil, _bypass_decode), do: nil
|
||||
|
||||
# URI.encode/2 deliberately does not encode all chars that are forbidden
|
||||
# in the path component of a URI. It only encodes chars that are forbidden
|
||||
# in the whole URI. A predicate in the 2nd argument is used to fix that here.
|
||||
# URI.encode/2 uses the predicate function to determine whether each byte
|
||||
# (in an integer representation) should be encoded or not.
|
||||
defp encode_path(path, bypass_decode) when is_binary(path) do
|
||||
path =
|
||||
cond do
|
||||
bypass_decode ->
|
||||
path
|
||||
|
||||
true ->
|
||||
URI.decode(path)
|
||||
end
|
||||
|
||||
path
|
||||
|> URI.encode(fn byte ->
|
||||
URI.char_unreserved?(byte) ||
|
||||
Enum.any?(
|
||||
Pleroma.Constants.uri_path_allowed_reserved_chars(),
|
||||
fn char ->
|
||||
char == byte
|
||||
end
|
||||
)
|
||||
end)
|
||||
end
|
||||
|
||||
# Order of kv pairs in query is not preserved when using URI.decode_query.
|
||||
# URI.query_decoder/2 returns a stream which so far appears to not change order.
|
||||
# Immediately switch to a list to prevent breakage for sites that expect
|
||||
# the order of query keys to be always the same.
|
||||
defp encode_query(query, host) when is_binary(query) do
|
||||
query
|
||||
|> URI.query_decoder()
|
||||
|> Enum.to_list()
|
||||
|> do_encode_query(host)
|
||||
end
|
||||
|
||||
defp encode_query(nil, _), do: nil
|
||||
|
||||
# Always uses www_form encoding.
|
||||
# Taken from Elixir's URI module.
|
||||
defp do_encode_query(enumerable, host) do
|
||||
Enum.map_join(enumerable, "&", &maybe_apply_query_quirk(&1, host))
|
||||
end
|
||||
|
||||
# https://git.pleroma.social/pleroma/pleroma/-/issues/1055
|
||||
defp maybe_apply_query_quirk({key, value}, "i.guim.co.uk" = _host) do
|
||||
case key do
|
||||
"precrop" ->
|
||||
query_encode_kv_pair({key, value}, ~c":,")
|
||||
|
||||
key ->
|
||||
query_encode_kv_pair({key, value})
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_apply_query_quirk({key, value}, _), do: query_encode_kv_pair({key, value})
|
||||
|
||||
# Taken from Elixir's URI module and modified to support quirks.
|
||||
defp query_encode_kv_pair({key, value}, rules \\ []) when is_list(rules) do
|
||||
cond do
|
||||
length(rules) > 0 ->
|
||||
# URI.encode_query/2 does not appear to follow spec and encodes all parts
|
||||
# of our URI path Constant. This appears to work outside of edge-cases
|
||||
# like The Guardian Rich Media Cards, keeping behavior same as with
|
||||
# URI.encode_query/2 unless otherwise specified via rules.
|
||||
(URI.encode_www_form(Kernel.to_string(key)) <>
|
||||
"=" <>
|
||||
URI.encode(value, fn byte ->
|
||||
URI.char_unreserved?(byte) ||
|
||||
Enum.any?(
|
||||
rules,
|
||||
fn char ->
|
||||
char == byte
|
||||
end
|
||||
)
|
||||
end))
|
||||
|> String.replace("%20", "+")
|
||||
|
||||
true ->
|
||||
URI.encode_www_form(Kernel.to_string(key)) <>
|
||||
"=" <> URI.encode_www_form(Kernel.to_string(value))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1569,7 +1569,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|
||||
defp get_actor_url(_url), do: nil
|
||||
|
||||
defp normalize_image(%{"url" => url} = data) do
|
||||
defp normalize_image(%{"url" => url} = data) when is_binary(url) do
|
||||
%{
|
||||
"type" => "Image",
|
||||
"url" => [%{"href" => url}]
|
||||
|
|
@ -1577,6 +1577,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|> maybe_put_description(data)
|
||||
end
|
||||
|
||||
defp normalize_image(%{"url" => urls}) when is_list(urls) do
|
||||
url = urls |> List.first()
|
||||
|
||||
%{"url" => url}
|
||||
|> normalize_image()
|
||||
end
|
||||
|
||||
defp normalize_image(urls) when is_list(urls), do: urls |> List.first() |> normalize_image()
|
||||
defp normalize_image(_), do: nil
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy do
|
|||
content =~ quote_url -> true
|
||||
# Does the content already have a .quote-inline span?
|
||||
content =~ "<span class=\"quote-inline\">" -> true
|
||||
# Does the content already have a .quote-inline p? (Mastodon)
|
||||
content =~ "<p class=\"quote-inline\">" -> true
|
||||
# No inline quote found
|
||||
true -> false
|
||||
end
|
||||
|
|
|
|||
|
|
@ -151,7 +151,8 @@ defmodule Pleroma.Web.ApiSpec do
|
|||
"Suggestions",
|
||||
"Announcements",
|
||||
"Remote interaction",
|
||||
"Others"
|
||||
"Others",
|
||||
"Preferred frontends"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -398,6 +398,28 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
|
|||
}
|
||||
end
|
||||
|
||||
def endorsements_operation do
|
||||
%Operation{
|
||||
tags: ["Retrieve account information"],
|
||||
summary: "Endorsements",
|
||||
description: "Returns endorsed accounts",
|
||||
operationId: "AccountController.endorsements",
|
||||
parameters: [
|
||||
with_relationships_param(),
|
||||
%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}
|
||||
],
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response(
|
||||
"Array of Accounts",
|
||||
"application/json",
|
||||
array_of_accounts()
|
||||
),
|
||||
404 => Operation.response("Not Found", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def remove_from_followers_operation do
|
||||
%Operation{
|
||||
tags: ["Account actions"],
|
||||
|
|
@ -461,7 +483,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
|
|||
security: [%{"oAuth" => ["follow", "read:mutes"]}],
|
||||
parameters: [with_relationships_param() | pagination_params()],
|
||||
responses: %{
|
||||
200 => Operation.response("Accounts", "application/json", array_of_accounts())
|
||||
200 => Operation.response("Accounts", "application/json", array_of_muted_accounts())
|
||||
}
|
||||
}
|
||||
end
|
||||
|
|
@ -475,7 +497,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
|
|||
security: [%{"oAuth" => ["read:blocks"]}],
|
||||
parameters: [with_relationships_param() | pagination_params()],
|
||||
responses: %{
|
||||
200 => Operation.response("Accounts", "application/json", array_of_accounts())
|
||||
200 => Operation.response("Accounts", "application/json", array_of_blocked_accounts())
|
||||
}
|
||||
}
|
||||
end
|
||||
|
|
@ -495,16 +517,17 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
|
|||
],
|
||||
responses: %{
|
||||
200 => Operation.response("Account", "application/json", Account),
|
||||
401 => Operation.response("Error", "application/json", ApiError),
|
||||
404 => Operation.response("Error", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def endorsements_operation do
|
||||
def own_endorsements_operation do
|
||||
%Operation{
|
||||
tags: ["Retrieve account information"],
|
||||
summary: "Endorsements",
|
||||
operationId: "AccountController.endorsements",
|
||||
operationId: "AccountController.own_endorsements",
|
||||
description: "Returns endorsed accounts",
|
||||
security: [%{"oAuth" => ["read:accounts"]}],
|
||||
responses: %{
|
||||
|
|
@ -874,6 +897,54 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
|
|||
}
|
||||
end
|
||||
|
||||
def array_of_muted_accounts do
|
||||
%Schema{
|
||||
title: "ArrayOfMutedAccounts",
|
||||
type: :array,
|
||||
items: %Schema{
|
||||
title: "MutedAccount",
|
||||
description: "Response schema for a muted account",
|
||||
allOf: [
|
||||
Account,
|
||||
%Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
mute_expires_at: %Schema{type: :string, format: "date-time", nullable: true}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
example: [
|
||||
Account.schema().example
|
||||
|> Map.put("mute_expires_at", "2025-11-29T16:23:13Z")
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
def array_of_blocked_accounts do
|
||||
%Schema{
|
||||
title: "ArrayOfBlockedAccounts",
|
||||
type: :array,
|
||||
items: %Schema{
|
||||
title: "BlockedAccount",
|
||||
description: "Response schema for a blocked account",
|
||||
allOf: [
|
||||
Account,
|
||||
%Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
block_expires_at: %Schema{type: :string, format: "date-time", nullable: true}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
example: [
|
||||
Account.schema().example
|
||||
|> Map.put("block_expires_at", "2025-11-29T16:23:13Z")
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
defp array_of_relationships do
|
||||
%Schema{
|
||||
title: "ArrayOfRelationships",
|
||||
|
|
|
|||
|
|
@ -142,7 +142,8 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do
|
|||
:query,
|
||||
BooleanLike.schema(),
|
||||
"Include chats from muted users"
|
||||
)
|
||||
),
|
||||
Operation.parameter(:pinned, :query, BooleanLike.schema(), "Include only pinned chats")
|
||||
],
|
||||
responses: %{
|
||||
200 => Operation.response("The chats of the user", "application/json", chats_response())
|
||||
|
|
@ -166,7 +167,8 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do
|
|||
:query,
|
||||
BooleanLike.schema(),
|
||||
"Include chats from muted users"
|
||||
)
|
||||
),
|
||||
Operation.parameter(:pinned, :query, BooleanLike.schema(), "Include only pinned chats")
|
||||
| pagination_params()
|
||||
],
|
||||
responses: %{
|
||||
|
|
@ -257,6 +259,44 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do
|
|||
}
|
||||
end
|
||||
|
||||
def pin_operation do
|
||||
%Operation{
|
||||
tags: ["Chats"],
|
||||
summary: "Pin a chat",
|
||||
operationId: "ChatController.pin",
|
||||
parameters: [
|
||||
Operation.parameter(:id, :path, :string, "The id of the chat", required: true)
|
||||
],
|
||||
responses: %{
|
||||
200 => Operation.response("The existing chat", "application/json", Chat)
|
||||
},
|
||||
security: [
|
||||
%{
|
||||
"oAuth" => ["write:chats"]
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
def unpin_operation do
|
||||
%Operation{
|
||||
tags: ["Chats"],
|
||||
summary: "Unpin a chat",
|
||||
operationId: "ChatController.unpin",
|
||||
parameters: [
|
||||
Operation.parameter(:id, :path, :string, "The id of the chat", required: true)
|
||||
],
|
||||
responses: %{
|
||||
200 => Operation.response("The existing chat", "application/json", Chat)
|
||||
},
|
||||
security: [
|
||||
%{
|
||||
"oAuth" => ["write:chats"]
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
def chats_response do
|
||||
%Schema{
|
||||
title: "ChatsResponse",
|
||||
|
|
|
|||
|
|
@ -64,25 +64,6 @@ defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do
|
|||
}
|
||||
end
|
||||
|
||||
def endorsements_operation do
|
||||
%Operation{
|
||||
tags: ["Retrieve account information"],
|
||||
summary: "Endorsements",
|
||||
description: "Returns endorsed accounts",
|
||||
operationId: "PleromaAPI.AccountController.endorsements",
|
||||
parameters: [with_relationships_param(), id_param()],
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response(
|
||||
"Array of Accounts",
|
||||
"application/json",
|
||||
AccountOperation.array_of_accounts()
|
||||
),
|
||||
404 => Operation.response("Not Found", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def subscribe_operation do
|
||||
%Operation{
|
||||
deprecated: true,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ApiSpec.PleromaFollowRequestOperation do
|
||||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Schemas.Account
|
||||
|
||||
def open_api_operation(action) do
|
||||
operation = String.to_existing_atom("#{action}_operation")
|
||||
apply(__MODULE__, operation, [])
|
||||
end
|
||||
|
||||
def outgoing_operation do
|
||||
%Operation{
|
||||
tags: ["Follow requests"],
|
||||
summary: "Retrieve outgoing follow requests",
|
||||
security: [%{"oAuth" => ["read:follows", "follow"]}],
|
||||
operationId: "PleromaFollowRequestController.outgoing",
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("Array of Account", "application/json", %Schema{
|
||||
type: :array,
|
||||
items: Account,
|
||||
example: [Account.schema().example]
|
||||
})
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
defmodule Pleroma.Web.ApiSpec.PleromaFrontendSettingsOperation do
|
||||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Schema
|
||||
import Pleroma.Web.ApiSpec.Helpers
|
||||
|
||||
@spec open_api_operation(atom) :: Operation.t()
|
||||
def open_api_operation(action) do
|
||||
operation = String.to_existing_atom("#{action}_operation")
|
||||
apply(__MODULE__, operation, [])
|
||||
end
|
||||
|
||||
def available_frontends_operation do
|
||||
%Operation{
|
||||
tags: ["Preferred frontends"],
|
||||
summary: "Frontend settings profiles",
|
||||
description: "List frontend setting profiles",
|
||||
operationId: "PleromaAPI.FrontendSettingsController.available_frontends",
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("Frontends", "application/json", %Schema{
|
||||
type: :array,
|
||||
items: %Schema{
|
||||
type: :string
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def update_preferred_frontend_operation do
|
||||
%Operation{
|
||||
tags: ["Preferred frontends"],
|
||||
summary: "Update preferred frontend setting",
|
||||
description: "Store preferred frontend in cookies",
|
||||
operationId: "PleromaAPI.FrontendSettingsController.update_preferred_frontend",
|
||||
requestBody:
|
||||
request_body(
|
||||
"Frontend",
|
||||
%Schema{
|
||||
type: :object,
|
||||
required: [:frontend_name],
|
||||
properties: %{
|
||||
frontend_name: %Schema{
|
||||
type: :string,
|
||||
description: "Frontend name"
|
||||
}
|
||||
}
|
||||
},
|
||||
required: true
|
||||
),
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("Preferred frontend", "application/json", %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
frontend_name: %Schema{
|
||||
type: :string,
|
||||
description: "Frontend name"
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
@ -20,7 +20,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaScrobbleOperation do
|
|||
%Operation{
|
||||
tags: ["Scrobbles"],
|
||||
summary: "Creates a new Listen activity for an account",
|
||||
security: [%{"oAuth" => ["write"]}],
|
||||
security: [%{"oAuth" => ["write:scrobbles"]}],
|
||||
operationId: "PleromaAPI.ScrobbleController.create",
|
||||
deprecated: true,
|
||||
requestBody: request_body("Parameters", create_request(), required: true),
|
||||
|
|
@ -39,7 +39,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaScrobbleOperation do
|
|||
parameters: [
|
||||
%Reference{"$ref": "#/components/parameters/accountIdOrNickname"} | pagination_params()
|
||||
],
|
||||
security: [%{"oAuth" => ["read"]}],
|
||||
security: [%{"oAuth" => ["read:scrobbles"]}],
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("Array of Scrobble", "application/json", %Schema{
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ defmodule Pleroma.Web.ApiSpec.PleromaStatusOperation do
|
|||
%Operation{
|
||||
tags: ["Retrieve status information"],
|
||||
summary: "Quoted by",
|
||||
description: "View quotes for a given status",
|
||||
deprecated: true,
|
||||
description: "View quotes for a given status. Use /api/v1/statuses/:id/quotes instead.",
|
||||
operationId: "PleromaAPI.StatusController.quotes",
|
||||
parameters: [id_param() | pagination_params()],
|
||||
security: [%{"oAuth" => ["read:statuses"]}],
|
||||
|
|
|
|||
|
|
@ -46,6 +46,12 @@ defmodule Pleroma.Web.ApiSpec.SearchOperation do
|
|||
:query,
|
||||
%Schema{allOf: [BooleanLike], default: false},
|
||||
"Only include accounts that the user is following"
|
||||
),
|
||||
Operation.parameter(
|
||||
:capabilities,
|
||||
:query,
|
||||
%Schema{type: :array, items: %Schema{type: :string, enum: ["accepts_chat_messages"]}},
|
||||
"Only include accounts with given capabilities"
|
||||
)
|
||||
],
|
||||
responses: %{
|
||||
|
|
|
|||
|
|
@ -549,6 +549,27 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
|
|||
}
|
||||
end
|
||||
|
||||
def quotes_operation do
|
||||
%Operation{
|
||||
tags: ["Retrieve status information"],
|
||||
summary: "Quoted by",
|
||||
description: "View quotes for a given status",
|
||||
operationId: "StatusController.quotes",
|
||||
parameters: [id_param() | pagination_params()],
|
||||
security: [%{"oAuth" => ["read:statuses"]}],
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response(
|
||||
"Array of Status",
|
||||
"application/json",
|
||||
array_of_statuses()
|
||||
),
|
||||
403 => Operation.response("Forbidden", "application/json", ApiError),
|
||||
404 => Operation.response("Not Found", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def array_of_statuses do
|
||||
%Schema{type: :array, items: Status, example: [Status.schema().example]}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -33,8 +33,6 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
|||
header: %Schema{type: :string, format: :uri},
|
||||
id: FlakeID,
|
||||
locked: %Schema{type: :boolean},
|
||||
mute_expires_at: %Schema{type: :string, format: "date-time", nullable: true},
|
||||
block_expires_at: %Schema{type: :string, format: "date-time", nullable: true},
|
||||
note: %Schema{type: :string, format: :html},
|
||||
statuses_count: %Schema{type: :integer},
|
||||
url: %Schema{type: :string, format: :uri},
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Chat do
|
|||
account: %Schema{type: :object},
|
||||
unread: %Schema{type: :integer},
|
||||
last_message: ChatMessage,
|
||||
updated_at: %Schema{type: :string, format: :"date-time"}
|
||||
updated_at: %Schema{type: :string, format: :"date-time"},
|
||||
pinned: %Schema{type: :boolean}
|
||||
},
|
||||
example: %{
|
||||
"account" => %{
|
||||
|
|
@ -69,7 +70,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Chat do
|
|||
"id" => "1",
|
||||
"unread" => 2,
|
||||
"last_message" => ChatMessage.schema().example,
|
||||
"updated_at" => "2020-04-21T15:06:45.000Z"
|
||||
"updated_at" => "2020-04-21T15:06:45.000Z",
|
||||
"pinned" => false
|
||||
}
|
||||
})
|
||||
end
|
||||
|
|
|
|||
|
|
@ -219,7 +219,9 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
|
|||
},
|
||||
quotes_count: %Schema{
|
||||
type: :integer,
|
||||
description: "How many statuses quoted this status"
|
||||
deprecated: true,
|
||||
description:
|
||||
"How many statuses quoted this status. Deprecated, use `quotes_count` from parent object instead."
|
||||
},
|
||||
local: %Schema{
|
||||
type: :boolean,
|
||||
|
|
@ -259,6 +261,10 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
|
|||
}
|
||||
},
|
||||
poll: %Schema{allOf: [Poll], nullable: true, description: "The poll attached to the status"},
|
||||
quotes_count: %Schema{
|
||||
type: :integer,
|
||||
description: "How many statuses quoted this status."
|
||||
},
|
||||
reblog: %Schema{
|
||||
allOf: [%OpenApiSpex.Reference{"$ref": "#/components/schemas/Status"}],
|
||||
nullable: true,
|
||||
|
|
@ -385,6 +391,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
|
|||
"quotes_count" => 0
|
||||
},
|
||||
"poll" => nil,
|
||||
"quotes_count" => 0,
|
||||
"reblog" => nil,
|
||||
"reblogged" => false,
|
||||
"reblogs_count" => 0,
|
||||
|
|
|
|||
|
|
@ -402,28 +402,6 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
|
||||
def maybe_notify_mentioned_recipients(recipients, _), do: recipients
|
||||
|
||||
def maybe_notify_subscribers(
|
||||
recipients,
|
||||
%Activity{data: %{"actor" => actor, "type" => "Create"}} = activity
|
||||
) do
|
||||
# Do not notify subscribers if author is making a reply
|
||||
with %Object{data: object} <- Object.normalize(activity, fetch: false),
|
||||
nil <- object["inReplyTo"],
|
||||
%User{} = user <- User.get_cached_by_ap_id(actor) do
|
||||
subscriber_ids =
|
||||
user
|
||||
|> User.subscriber_users()
|
||||
|> Enum.filter(&Visibility.visible_for_user?(activity, &1))
|
||||
|> Enum.map(& &1.ap_id)
|
||||
|
||||
recipients ++ subscriber_ids
|
||||
else
|
||||
_e -> recipients
|
||||
end
|
||||
end
|
||||
|
||||
def maybe_notify_subscribers(recipients, _), do: recipients
|
||||
|
||||
def maybe_notify_followers(recipients, %Activity{data: %{"type" => "Move"}} = activity) do
|
||||
with %User{} = user <- User.get_cached_by_ap_id(activity.actor) do
|
||||
user
|
||||
|
|
@ -437,6 +415,27 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
|
||||
def maybe_notify_followers(recipients, _), do: recipients
|
||||
|
||||
def get_notified_subscribers(
|
||||
%Activity{data: %{"actor" => actor, "type" => "Create"}} = activity
|
||||
) do
|
||||
# Do not notify subscribers if author is making a reply
|
||||
with %Object{data: object} <- Object.normalize(activity, fetch: false),
|
||||
nil <- object["inReplyTo"],
|
||||
%User{} = user <- User.get_cached_by_ap_id(actor) do
|
||||
subscriber_ids =
|
||||
user
|
||||
|> User.subscriber_users()
|
||||
|> Enum.filter(&Visibility.visible_for_user?(activity, &1))
|
||||
|> Enum.map(& &1.ap_id)
|
||||
|
||||
subscriber_ids
|
||||
else
|
||||
_e -> []
|
||||
end
|
||||
end
|
||||
|
||||
def get_notified_subscribers(_), do: []
|
||||
|
||||
def maybe_extract_mentions(%{"tag" => tag}) do
|
||||
tag
|
||||
|> Enum.filter(fn x -> is_map(x) && x["type"] == "Mention" end)
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ defmodule Pleroma.Web.Fallback.RedirectController do
|
|||
end
|
||||
|
||||
def redirector(conn, _params, code \\ 200) do
|
||||
{:ok, index_content} = File.read(index_file_path())
|
||||
{:ok, index_content} = File.read(index_file_path(conn))
|
||||
|
||||
response =
|
||||
index_content
|
||||
|
|
@ -51,7 +51,7 @@ defmodule Pleroma.Web.Fallback.RedirectController do
|
|||
end
|
||||
|
||||
def redirector_with_meta(conn, params) do
|
||||
{:ok, index_content} = File.read(index_file_path())
|
||||
{:ok, index_content} = File.read(index_file_path(conn))
|
||||
tags = build_tags(conn, params)
|
||||
preloads = preload_data(conn, params)
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ defmodule Pleroma.Web.Fallback.RedirectController do
|
|||
end
|
||||
|
||||
def redirector_with_preload(conn, params) do
|
||||
{:ok, index_content} = File.read(index_file_path())
|
||||
{:ok, index_content} = File.read(index_file_path(conn))
|
||||
preloads = preload_data(conn, params)
|
||||
|
||||
response =
|
||||
|
|
@ -91,8 +91,10 @@ defmodule Pleroma.Web.Fallback.RedirectController do
|
|||
|> text("")
|
||||
end
|
||||
|
||||
defp index_file_path do
|
||||
Pleroma.Web.Plugs.InstanceStatic.file_path("index.html")
|
||||
defp index_file_path(conn) do
|
||||
frontend_type = Pleroma.Web.Plugs.FrontendStatic.preferred_or_fallback(conn, :primary)
|
||||
|
||||
Pleroma.Web.Plugs.InstanceStatic.file_path("index.html", frontend_type)
|
||||
end
|
||||
|
||||
defp build_tags(conn, params) do
|
||||
|
|
|
|||
|
|
@ -28,9 +28,12 @@ defmodule Pleroma.Web.Feed.UserController do
|
|||
ActivityPubController.call(conn, :user)
|
||||
end
|
||||
|
||||
def feed_redirect(conn, %{"nickname" => nickname}) do
|
||||
def feed_redirect(%{assigns: assigns} = conn, %{"nickname" => nickname}) do
|
||||
format = Map.get(assigns, :format, "atom")
|
||||
format = if format in ["atom", "rss"], do: format, else: "atom"
|
||||
|
||||
with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
|
||||
redirect(conn, external: "#{Routes.user_feed_url(conn, :feed, user.nickname)}.atom")
|
||||
redirect(conn, external: "#{Routes.user_feed_url(conn, :feed, user.nickname)}.#{format}")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
defmodule Pleroma.Web.FrontendSwitcher.FrontendSwitcherController do
|
||||
use Pleroma.Web, :controller
|
||||
alias Pleroma.Config
|
||||
|
||||
@doc "GET /frontend_switcher"
|
||||
def switch(conn, _params) do
|
||||
pickable = Config.get([:frontends, :pickable], [])
|
||||
|
||||
conn
|
||||
|> put_view(Pleroma.Web.FrontendSwitcher.FrontendSwitcherView)
|
||||
|> render("switch.html", choices: pickable)
|
||||
end
|
||||
|
||||
@doc "POST /frontend_switcher"
|
||||
def do_switch(conn, params) do
|
||||
conn
|
||||
|> put_resp_cookie("preferred_frontend", params["frontend"])
|
||||
|> html(~s(<meta http-equiv="refresh" content="0; url=/">))
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
defmodule Pleroma.Web.FrontendSwitcher.FrontendSwitcherView do
|
||||
use Pleroma.Web, :view
|
||||
|
||||
import Phoenix.HTML.Form
|
||||
end
|
||||
|
|
@ -31,14 +31,14 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
|||
|
||||
plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false)
|
||||
|
||||
plug(:skip_auth when action in [:create, :lookup])
|
||||
plug(:skip_auth when action in [:create])
|
||||
|
||||
plug(:skip_public_check when action in [:show, :statuses])
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{fallback: :proceed_unauthenticated, scopes: ["read:accounts"]}
|
||||
when action in [:show, :followers, :following]
|
||||
when action in [:show, :followers, :following, :lookup, :endorsements]
|
||||
)
|
||||
|
||||
plug(
|
||||
|
|
@ -50,7 +50,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
|||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["read:accounts"]}
|
||||
when action in [:verify_credentials, :endorsements]
|
||||
when action in [:verify_credentials, :endorsements, :own_endorsements]
|
||||
)
|
||||
|
||||
plug(
|
||||
|
|
@ -89,7 +89,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
|||
@relationship_actions [:follow, :unfollow, :remove_from_followers]
|
||||
@needs_account ~W(
|
||||
followers following lists follow unfollow mute unmute block unblock
|
||||
note endorse unendorse remove_from_followers
|
||||
note endorse unendorse endorsements remove_from_followers
|
||||
)a
|
||||
|
||||
plug(
|
||||
|
|
@ -555,6 +555,22 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
|||
end
|
||||
end
|
||||
|
||||
@doc "GET /api/v1/accounts/:id/endorsements"
|
||||
def endorsements(%{assigns: %{user: for_user, account: user}} = conn, params) do
|
||||
users =
|
||||
user
|
||||
|> User.endorsed_users_relation(_restrict_deactivated = true)
|
||||
|> Pleroma.Repo.all()
|
||||
|
||||
conn
|
||||
|> render("index.json",
|
||||
for: for_user,
|
||||
users: users,
|
||||
as: :user,
|
||||
embed_relationships: embed_relationships?(params)
|
||||
)
|
||||
end
|
||||
|
||||
@doc "POST /api/v1/accounts/:id/remove_from_followers"
|
||||
def remove_from_followers(%{assigns: %{user: %{id: id}, account: %{id: id}}}, _params) do
|
||||
{:error, "Can not unfollow yourself"}
|
||||
|
|
@ -619,8 +635,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
|||
end
|
||||
|
||||
@doc "GET /api/v1/accounts/lookup"
|
||||
def lookup(%{private: %{open_api_spex: %{params: %{acct: nickname}}}} = conn, _params) do
|
||||
with %User{} = user <- User.get_by_nickname(nickname) do
|
||||
def lookup(
|
||||
%{assigns: %{user: for_user}, private: %{open_api_spex: %{params: %{acct: nickname}}}} =
|
||||
conn,
|
||||
_params
|
||||
) do
|
||||
with %User{} = user <- User.get_by_nickname(nickname),
|
||||
:visible <- User.visible_for(user, for_user) do
|
||||
render(conn, "show.json",
|
||||
user: user,
|
||||
skip_visibility_check: true
|
||||
|
|
@ -631,7 +652,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
|||
end
|
||||
|
||||
@doc "GET /api/v1/endorsements"
|
||||
def endorsements(%{assigns: %{user: user}} = conn, params) do
|
||||
def own_endorsements(%{assigns: %{user: user}} = conn, params) do
|
||||
users =
|
||||
user
|
||||
|> User.endorsed_users_relation(_restrict_deactivated = true)
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
|
|||
limit: min(params[:limit], @search_limit),
|
||||
offset: params[:offset],
|
||||
type: params[:type],
|
||||
capabilities: params[:capabilities],
|
||||
author: get_author(params),
|
||||
embed_relationships: ControllerHelper.embed_relationships?(params),
|
||||
for_user: user
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
|||
only: [try_render: 3, add_link_headers: 2]
|
||||
|
||||
require Ecto.Query
|
||||
require Pleroma.Constants
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Bookmark
|
||||
|
|
@ -41,7 +42,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
|||
:show,
|
||||
:context,
|
||||
:show_history,
|
||||
:show_source
|
||||
:show_source,
|
||||
:quotes
|
||||
]
|
||||
)
|
||||
|
||||
|
|
@ -488,6 +490,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
|||
users =
|
||||
User
|
||||
|> Ecto.Query.where([u], u.ap_id in ^likes)
|
||||
|> Ecto.Query.order_by([u], fragment("array_position(?, ?)", ^likes, u.ap_id))
|
||||
|> Repo.all()
|
||||
|> Enum.filter(&(not User.blocks?(user, &1)))
|
||||
|
||||
|
|
@ -523,6 +526,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
|||
users =
|
||||
User
|
||||
|> Ecto.Query.where([u], u.ap_id in ^announces)
|
||||
|> Ecto.Query.order_by([u], fragment("array_position(?, ?)", ^announces, u.ap_id))
|
||||
|> Repo.all()
|
||||
|> Enum.filter(&(not User.blocks?(user, &1)))
|
||||
|
||||
|
|
@ -629,6 +633,45 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
|||
)
|
||||
end
|
||||
|
||||
@doc "GET /api/v1/statuses/:id/quotes"
|
||||
def quotes(
|
||||
%{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id} = params}}} =
|
||||
conn,
|
||||
_
|
||||
) do
|
||||
with %Activity{object: object} = activity <- Activity.get_by_id_with_object(id),
|
||||
true <- Visibility.visible_for_user?(activity, user) do
|
||||
params =
|
||||
params
|
||||
|> Map.put(:type, "Create")
|
||||
|> Map.put(:blocking_user, user)
|
||||
|> Map.put(:quote_url, object.data["id"])
|
||||
|
||||
recipients =
|
||||
if user do
|
||||
[Pleroma.Constants.as_public()] ++ [user.ap_id | User.following(user)]
|
||||
else
|
||||
[Pleroma.Constants.as_public()]
|
||||
end
|
||||
|
||||
activities =
|
||||
recipients
|
||||
|> ActivityPub.fetch_activities(params)
|
||||
|> Enum.reverse()
|
||||
|
||||
conn
|
||||
|> add_link_headers(activities)
|
||||
|> render("index.json",
|
||||
activities: activities,
|
||||
for: user,
|
||||
as: :activity
|
||||
)
|
||||
else
|
||||
nil -> {:error, :not_found}
|
||||
false -> {:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
||||
defp put_application(params, %{assigns: %{token: %Token{user: %User{} = user} = token}} = _conn) do
|
||||
if user.disclose_client do
|
||||
%{client_name: client_name, website: website} = Repo.preload(token, :app).app
|
||||
|
|
|
|||
|
|
@ -146,6 +146,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
|
|||
"pleroma_emoji_reactions",
|
||||
"pleroma_custom_emoji_reactions",
|
||||
"pleroma_chat_messages",
|
||||
"pleroma:pin_chats",
|
||||
if Config.get([:instance, :show_reactions]) do
|
||||
"exposable_reactions"
|
||||
end,
|
||||
|
|
@ -257,10 +258,34 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
|
|||
vapid: %{
|
||||
public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
|
||||
},
|
||||
translation: %{enabled: Pleroma.Language.Translation.configured?()}
|
||||
translation: %{enabled: Pleroma.Language.Translation.configured?()},
|
||||
timelines_access: %{
|
||||
live_feeds: timelines_access(),
|
||||
hashtag_feeds: timelines_access(),
|
||||
# not implemented in Pleroma
|
||||
trending_link_feeds: %{
|
||||
local: "disabled",
|
||||
remote: "disabled"
|
||||
}
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
defp timelines_access do
|
||||
%{
|
||||
local: timeline_access(:local),
|
||||
remote: timeline_access(:federated)
|
||||
}
|
||||
end
|
||||
|
||||
defp timeline_access(kind) do
|
||||
if Config.restrict_unauthenticated_access?(:timelines, kind) do
|
||||
"authenticated"
|
||||
else
|
||||
"public"
|
||||
end
|
||||
end
|
||||
|
||||
defp pleroma_configuration(instance) do
|
||||
base_urls = %{}
|
||||
|
||||
|
|
|
|||
|
|
@ -106,27 +106,15 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
|
|||
}
|
||||
|
||||
case notification.type do
|
||||
"mention" ->
|
||||
type when type in ["mention", "status", "poll"] ->
|
||||
put_status(response, activity, reading_user, status_render_opts)
|
||||
|
||||
"status" ->
|
||||
put_status(response, activity, reading_user, status_render_opts)
|
||||
|
||||
"favourite" ->
|
||||
put_status(response, parent_activity_fn.(), reading_user, status_render_opts)
|
||||
|
||||
"reblog" ->
|
||||
put_status(response, parent_activity_fn.(), reading_user, status_render_opts)
|
||||
|
||||
"update" ->
|
||||
type when type in ["favourite", "reblog", "update"] ->
|
||||
put_status(response, parent_activity_fn.(), reading_user, status_render_opts)
|
||||
|
||||
"move" ->
|
||||
put_target(response, activity, reading_user, %{})
|
||||
|
||||
"poll" ->
|
||||
put_status(response, activity, reading_user, status_render_opts)
|
||||
|
||||
"pleroma:emoji_reaction" ->
|
||||
response
|
||||
|> put_status(parent_activity_fn.(), reading_user, status_render_opts)
|
||||
|
|
|
|||
|
|
@ -447,6 +447,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
application: build_application(object.data["generator"]),
|
||||
language: get_language(object),
|
||||
emojis: build_emojis(object.data["emoji"]),
|
||||
quotes_count: object.data["quotesCount"] || 0,
|
||||
pleroma: %{
|
||||
local: activity.local,
|
||||
conversation_id: get_context_id(activity),
|
||||
|
|
@ -602,7 +603,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
def render("attachment.json", %{attachment: attachment}) do
|
||||
[attachment_url | _] = attachment["url"]
|
||||
media_type = attachment_url["mediaType"] || attachment_url["mimeType"] || "image"
|
||||
href = attachment_url["href"] |> MediaProxy.url()
|
||||
href_remote = attachment_url["href"]
|
||||
href = href_remote |> MediaProxy.url()
|
||||
href_preview = attachment_url["href"] |> MediaProxy.preview_url()
|
||||
meta = render("attachment_meta.json", %{attachment: attachment})
|
||||
|
||||
|
|
@ -641,7 +643,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
%{
|
||||
id: attachment_id,
|
||||
url: href,
|
||||
remote_url: href,
|
||||
remote_url: href_remote,
|
||||
preview_url: href_preview,
|
||||
text_url: href,
|
||||
type: type,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ defmodule Pleroma.Web.MediaProxy do
|
|||
alias Pleroma.Config
|
||||
alias Pleroma.Helpers.UriHelper
|
||||
alias Pleroma.Upload
|
||||
alias Pleroma.Utils.URIEncoding
|
||||
alias Pleroma.Web.Endpoint
|
||||
alias Pleroma.Web.MediaProxy.Invalidation
|
||||
|
||||
|
|
@ -99,13 +100,21 @@ defmodule Pleroma.Web.MediaProxy do
|
|||
{base64, sig64}
|
||||
end
|
||||
|
||||
# The URL coming into MediaProxy from the outside might have wrong %-encoding
|
||||
# (like older Pleroma versions).
|
||||
# This would cause an inconsistency with the encoded URL here and the requested
|
||||
# URL fixed with Pleroma.Tesla.Middleware.EncodeUrl.
|
||||
# End result is a failing HEAD request in
|
||||
# Pleroma.Web.MediaProxy.MediaProxyController.handle_preview/2
|
||||
def encode_url(url) do
|
||||
url = URIEncoding.encode_url(url)
|
||||
{base64, sig64} = base64_sig64(url)
|
||||
|
||||
build_url(sig64, base64, filename(url))
|
||||
end
|
||||
|
||||
def encode_preview_url(url, preview_params \\ []) do
|
||||
url = URIEncoding.encode_url(url)
|
||||
{base64, sig64} = base64_sig64(url)
|
||||
|
||||
build_preview_url(sig64, base64, filename(url), preview_params)
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
|
|||
conn
|
||||
|> put_resp_header(
|
||||
"content-type",
|
||||
"application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.0#; charset=utf-8"
|
||||
"application/json; profile=\"http://nodeinfo.diaspora.software/ns/schema/#{version}#\"; charset=utf-8"
|
||||
)
|
||||
|> json(node_info)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
|
|||
only: [
|
||||
json_response: 3,
|
||||
add_link_headers: 2,
|
||||
embed_relationships?: 1,
|
||||
assign_account_by_id: 2
|
||||
]
|
||||
|
||||
|
|
@ -45,12 +44,6 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
|
|||
%{scopes: ["read:favourites"], fallback: :proceed_unauthenticated} when action == :favourites
|
||||
)
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{fallback: :proceed_unauthenticated, scopes: ["read:accounts"]}
|
||||
when action == :endorsements
|
||||
)
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["read:accounts"]} when action == :birthdays
|
||||
|
|
@ -60,7 +53,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
|
|||
|
||||
plug(
|
||||
:assign_account_by_id
|
||||
when action in [:favourites, :endorsements, :subscribe, :unsubscribe]
|
||||
when action in [:favourites, :subscribe, :unsubscribe]
|
||||
)
|
||||
|
||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaAccountOperation
|
||||
|
|
@ -109,22 +102,6 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
|
|||
)
|
||||
end
|
||||
|
||||
@doc "GET /api/v1/pleroma/accounts/:id/endorsements"
|
||||
def endorsements(%{assigns: %{user: for_user, account: user}} = conn, params) do
|
||||
users =
|
||||
user
|
||||
|> User.endorsed_users_relation(_restrict_deactivated = true)
|
||||
|> Pleroma.Repo.all()
|
||||
|
||||
conn
|
||||
|> render("index.json",
|
||||
for: for_user,
|
||||
users: users,
|
||||
as: :user,
|
||||
embed_relationships: embed_relationships?(params)
|
||||
)
|
||||
end
|
||||
|
||||
@doc "POST /api/v1/pleroma/accounts/:id/subscribe"
|
||||
def subscribe(%{assigns: %{user: user, account: subscription_target}} = conn, _params) do
|
||||
with {:ok, _subscription} <- User.subscribe(user, subscription_target) do
|
||||
|
|
|
|||
|
|
@ -29,7 +29,9 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
|
|||
:create,
|
||||
:mark_as_read,
|
||||
:mark_message_as_read,
|
||||
:delete_message
|
||||
:delete_message,
|
||||
:pin,
|
||||
:unpin
|
||||
]
|
||||
)
|
||||
|
||||
|
|
@ -199,8 +201,16 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
|
|||
user_id
|
||||
|> Chat.for_user_query()
|
||||
|> where([c], c.recipient not in ^exclude_users)
|
||||
|> restrict_pinned(params)
|
||||
end
|
||||
|
||||
defp restrict_pinned(query, %{pinned: pinned}) when is_boolean(pinned) do
|
||||
query
|
||||
|> where([c], c.pinned == ^pinned)
|
||||
end
|
||||
|
||||
defp restrict_pinned(query, _), do: query
|
||||
|
||||
def create(%{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, _) do
|
||||
with %User{ap_id: recipient} <- User.get_cached_by_id(id),
|
||||
{:ok, %Chat{} = chat} <- Chat.get_or_create(user.id, recipient) do
|
||||
|
|
@ -214,6 +224,20 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
|
|||
end
|
||||
end
|
||||
|
||||
def pin(%{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, _) do
|
||||
with {:ok, chat} <- Chat.get_by_user_and_id(user, id),
|
||||
{:ok, chat} <- Chat.pin(chat) do
|
||||
render(conn, "show.json", chat: chat)
|
||||
end
|
||||
end
|
||||
|
||||
def unpin(%{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, _) do
|
||||
with {:ok, chat} <- Chat.get_by_user_and_id(user, id),
|
||||
{:ok, chat} <- Chat.unpin(chat) do
|
||||
render(conn, "show.json", chat: chat)
|
||||
end
|
||||
end
|
||||
|
||||
defp idempotency_key(conn) do
|
||||
case get_req_header(conn, "idempotency-key") do
|
||||
[key] -> key
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.PleromaAPI.FollowRequestController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.Plugs.OAuthScopesPlug
|
||||
|
||||
plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false)
|
||||
|
||||
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
|
||||
|
||||
plug(OAuthScopesPlug, %{scopes: ["follow", "read:follows"]})
|
||||
|
||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaFollowRequestOperation
|
||||
|
||||
@doc "GET /api/v1/pleroma/outgoing_follow_requests"
|
||||
def outgoing(%{assigns: %{user: follower}} = conn, _params) do
|
||||
follow_requests = User.get_outgoing_follow_requests(follower)
|
||||
|
||||
conn
|
||||
|> put_view(Pleroma.Web.MastodonAPI.FollowRequestView)
|
||||
|> render("index.json", for: follower, users: follow_requests, as: :user)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
defmodule Pleroma.Web.PleromaAPI.FrontendSettingsController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
alias Pleroma.Web.Plugs.OAuthScopesPlug
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{fallback: :proceed_unauthenticated, scopes: []}
|
||||
when action in [
|
||||
:available_frontends,
|
||||
:update_preferred_frontend
|
||||
]
|
||||
)
|
||||
|
||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaFrontendSettingsOperation
|
||||
|
||||
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
|
||||
|
||||
@doc "GET /api/v1/pleroma/preferred_frontend/available"
|
||||
def available_frontends(conn, _params) do
|
||||
available = Pleroma.Config.get([:frontends, :pickable])
|
||||
|
||||
conn
|
||||
|> json(available)
|
||||
end
|
||||
|
||||
@doc "PUT /api/v1/pleroma/preferred_frontend"
|
||||
def update_preferred_frontend(
|
||||
%{body_params: %{frontend_name: preferred_frontend}} = conn,
|
||||
_params
|
||||
) do
|
||||
conn
|
||||
|> put_resp_cookie("preferred_frontend", preferred_frontend)
|
||||
|> json(%{frontend_name: preferred_frontend})
|
||||
end
|
||||
end
|
||||
|
|
@ -16,10 +16,10 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleController do
|
|||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["read"], fallback: :proceed_unauthenticated} when action == :index
|
||||
%{scopes: ["read:scrobbles"], fallback: :proceed_unauthenticated} when action == :index
|
||||
)
|
||||
|
||||
plug(OAuthScopesPlug, %{scopes: ["write"]} when action == :create)
|
||||
plug(OAuthScopesPlug, %{scopes: ["write:scrobbles"]} when action == :create)
|
||||
|
||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaScrobbleOperation
|
||||
|
||||
|
|
|
|||
|
|
@ -5,16 +5,9 @@
|
|||
defmodule Pleroma.Web.PleromaAPI.StatusController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
|
||||
|
||||
require Ecto.Query
|
||||
require Pleroma.Constants
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
alias Pleroma.Web.Plugs.OAuthScopesPlug
|
||||
|
||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||
|
|
@ -29,38 +22,9 @@ defmodule Pleroma.Web.PleromaAPI.StatusController do
|
|||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaStatusOperation
|
||||
|
||||
@doc "GET /api/v1/pleroma/statuses/:id/quotes"
|
||||
def quotes(%{assigns: %{user: user}} = conn, %{id: id} = params) do
|
||||
with %Activity{object: object} = activity <- Activity.get_by_id_with_object(id),
|
||||
true <- Visibility.visible_for_user?(activity, user) do
|
||||
params =
|
||||
params
|
||||
|> Map.put(:type, "Create")
|
||||
|> Map.put(:blocking_user, user)
|
||||
|> Map.put(:quote_url, object.data["id"])
|
||||
|
||||
recipients =
|
||||
if user do
|
||||
[Pleroma.Constants.as_public()] ++ [user.ap_id | User.following(user)]
|
||||
else
|
||||
[Pleroma.Constants.as_public()]
|
||||
end
|
||||
|
||||
activities =
|
||||
recipients
|
||||
|> ActivityPub.fetch_activities(params)
|
||||
|> Enum.reverse()
|
||||
|
||||
conn
|
||||
|> add_link_headers(activities)
|
||||
|> put_view(StatusView)
|
||||
|> render("index.json",
|
||||
activities: activities,
|
||||
for: user,
|
||||
as: :activity
|
||||
)
|
||||
else
|
||||
nil -> {:error, :not_found}
|
||||
false -> {:error, :not_found}
|
||||
end
|
||||
def quotes(conn, _params) do
|
||||
conn
|
||||
|> put_view(Pleroma.Web.MastodonAPI.StatusView)
|
||||
|> Pleroma.Web.MastodonAPI.StatusController.call(:quotes)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@ defmodule Pleroma.Web.PleromaAPI.ChatView do
|
|||
last_message:
|
||||
last_message &&
|
||||
MessageReferenceView.render("show.json", chat_message_reference: last_message),
|
||||
updated_at: Utils.to_masto_date(chat.updated_at)
|
||||
updated_at: Utils.to_masto_date(chat.updated_at),
|
||||
pinned: chat.pinned
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -5,17 +5,23 @@
|
|||
defmodule Pleroma.Web.Plugs.FrontendStatic do
|
||||
require Pleroma.Constants
|
||||
|
||||
@frontend_cookie_name "preferred_frontend"
|
||||
|
||||
@moduledoc """
|
||||
This is a shim to call `Plug.Static` but with runtime `from` configuration`. It dispatches to the different frontends.
|
||||
"""
|
||||
@behaviour Plug
|
||||
|
||||
def file_path(path, frontend_type \\ :primary) do
|
||||
if configuration = Pleroma.Config.get([:frontends, frontend_type]) do
|
||||
instance_static_path = Pleroma.Config.get([:instance, :static_dir], "instance/static")
|
||||
defp instance_static_path do
|
||||
Pleroma.Config.get([:instance, :static_dir], "instance/static")
|
||||
end
|
||||
|
||||
def file_path(path, frontend_type \\ :primary)
|
||||
|
||||
def file_path(path, frontend_type) when is_atom(frontend_type) do
|
||||
if configuration = Pleroma.Config.get([:frontends, frontend_type]) do
|
||||
Path.join([
|
||||
instance_static_path,
|
||||
instance_static_path(),
|
||||
"frontends",
|
||||
configuration["name"],
|
||||
configuration["ref"],
|
||||
|
|
@ -26,6 +32,15 @@ defmodule Pleroma.Web.Plugs.FrontendStatic do
|
|||
end
|
||||
end
|
||||
|
||||
def file_path(path, frontend_type) when is_binary(frontend_type) do
|
||||
Path.join([
|
||||
instance_static_path(),
|
||||
"frontends",
|
||||
frontend_type,
|
||||
path
|
||||
])
|
||||
end
|
||||
|
||||
def init(opts) do
|
||||
opts
|
||||
|> Keyword.put(:from, "__unconfigured_frontend_static_plug")
|
||||
|
|
@ -36,7 +51,8 @@ defmodule Pleroma.Web.Plugs.FrontendStatic do
|
|||
def call(conn, opts) do
|
||||
with false <- api_route?(conn.path_info),
|
||||
false <- invalid_path?(conn.path_info),
|
||||
frontend_type <- Map.get(opts, :frontend_type, :primary),
|
||||
fallback_frontend_type <- Map.get(opts, :frontend_type, :primary),
|
||||
frontend_type <- preferred_or_fallback(conn, fallback_frontend_type),
|
||||
path when not is_nil(path) <- file_path("", frontend_type) do
|
||||
call_static(conn, opts, path)
|
||||
else
|
||||
|
|
@ -45,6 +61,31 @@ defmodule Pleroma.Web.Plugs.FrontendStatic do
|
|||
end
|
||||
end
|
||||
|
||||
def preferred_frontend(conn) do
|
||||
%{req_cookies: cookies} =
|
||||
conn
|
||||
|> Plug.Conn.fetch_cookies()
|
||||
|
||||
Map.get(cookies, @frontend_cookie_name)
|
||||
end
|
||||
|
||||
# Only override primary frontend
|
||||
def preferred_or_fallback(conn, :primary) do
|
||||
case preferred_frontend(conn) do
|
||||
nil ->
|
||||
:primary
|
||||
|
||||
frontend ->
|
||||
if Enum.member?(Pleroma.Config.get([:frontends, :pickable], []), frontend) do
|
||||
frontend
|
||||
else
|
||||
:primary
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def preferred_or_fallback(_conn, fallback), do: fallback
|
||||
|
||||
defp invalid_path?(list) do
|
||||
invalid_path?(list, :binary.compile_pattern(["/", "\\", ":", "\0"]))
|
||||
end
|
||||
|
|
|
|||
|
|
@ -13,11 +13,11 @@ defmodule Pleroma.Web.Plugs.InstanceStatic do
|
|||
"""
|
||||
@behaviour Plug
|
||||
|
||||
def file_path(path) do
|
||||
def file_path(path, frontend_type \\ :primary) do
|
||||
instance_path =
|
||||
Path.join(Pleroma.Config.get([:instance, :static_dir], "instance/static/"), path)
|
||||
|
||||
frontend_path = Pleroma.Web.Plugs.FrontendStatic.file_path(path, :primary)
|
||||
frontend_path = Pleroma.Web.Plugs.FrontendStatic.file_path(path, frontend_type)
|
||||
|
||||
(File.exists?(instance_path) && instance_path) ||
|
||||
(frontend_path && File.exists?(frontend_path) && frontend_path) ||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ defmodule Pleroma.Web.Push.Subscription do
|
|||
end
|
||||
|
||||
# credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
|
||||
@supported_alert_types ~w[follow favourite mention reblog poll pleroma:chat_mention pleroma:emoji_reaction]a
|
||||
@supported_alert_types ~w[follow favourite mention status reblog poll pleroma:chat_mention pleroma:emoji_reaction]a
|
||||
|
||||
defp alerts(%{data: %{alerts: alerts}}) do
|
||||
alerts = Map.take(alerts, @supported_alert_types)
|
||||
|
|
|
|||
|
|
@ -126,6 +126,11 @@ defmodule Pleroma.Web.RichMedia.Helpers do
|
|||
end
|
||||
|
||||
defp req_headers do
|
||||
[{"user-agent", Pleroma.Application.user_agent() <> "; Bot"}]
|
||||
user_agent = Config.get([:rich_media, :user_agent], :default)
|
||||
|
||||
case user_agent do
|
||||
:default -> [{"user-agent", Pleroma.Application.user_agent() <> "; Bot"}]
|
||||
custom -> [{"user-agent", custom}]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -561,6 +561,18 @@ defmodule Pleroma.Web.Router do
|
|||
get("/apps", AppController, :index)
|
||||
get("/statuses/:id/reactions/:emoji", EmojiReactionController, :index)
|
||||
get("/statuses/:id/reactions", EmojiReactionController, :index)
|
||||
|
||||
get(
|
||||
"/preferred_frontend/available",
|
||||
FrontendSettingsController,
|
||||
:available_frontends
|
||||
)
|
||||
|
||||
put(
|
||||
"/preferred_frontend",
|
||||
FrontendSettingsController,
|
||||
:update_preferred_frontend
|
||||
)
|
||||
end
|
||||
|
||||
scope "/api/v0/pleroma", Pleroma.Web.PleromaAPI do
|
||||
|
|
@ -581,6 +593,8 @@ defmodule Pleroma.Web.Router do
|
|||
delete("/chats/:id/messages/:message_id", ChatController, :delete_message)
|
||||
post("/chats/:id/read", ChatController, :mark_as_read)
|
||||
post("/chats/:id/messages/:message_id/read", ChatController, :mark_message_as_read)
|
||||
post("/chats/:id/pin", ChatController, :pin)
|
||||
post("/chats/:id/unpin", ChatController, :unpin)
|
||||
|
||||
get("/conversations/:id/statuses", ConversationController, :statuses)
|
||||
get("/conversations/:id", ConversationController, :show)
|
||||
|
|
@ -603,12 +617,13 @@ defmodule Pleroma.Web.Router do
|
|||
post("/bookmark_folders", BookmarkFolderController, :create)
|
||||
patch("/bookmark_folders/:id", BookmarkFolderController, :update)
|
||||
delete("/bookmark_folders/:id", BookmarkFolderController, :delete)
|
||||
|
||||
get("/outgoing_follow_requests", FollowRequestController, :outgoing)
|
||||
end
|
||||
|
||||
scope [] do
|
||||
pipe_through(:api)
|
||||
get("/accounts/:id/favourites", AccountController, :favourites)
|
||||
get("/accounts/:id/endorsements", AccountController, :endorsements)
|
||||
|
||||
get("/statuses/:id/quotes", StatusController, :quotes)
|
||||
end
|
||||
|
|
@ -637,6 +652,11 @@ defmodule Pleroma.Web.Router do
|
|||
get("/accounts/:id/scrobbles", ScrobbleController, :index)
|
||||
end
|
||||
|
||||
scope "/api/v1/pleroma", Pleroma.Web.MastodonAPI do
|
||||
pipe_through(:api)
|
||||
get("/accounts/:id/endorsements", AccountController, :endorsements)
|
||||
end
|
||||
|
||||
scope "/api/v2/pleroma", Pleroma.Web.PleromaAPI do
|
||||
scope [] do
|
||||
pipe_through(:authenticated_api)
|
||||
|
|
@ -653,7 +673,7 @@ defmodule Pleroma.Web.Router do
|
|||
get("/accounts/relationships", AccountController, :relationships)
|
||||
get("/accounts/familiar_followers", AccountController, :familiar_followers)
|
||||
get("/accounts/:id/lists", AccountController, :lists)
|
||||
get("/endorsements", AccountController, :endorsements)
|
||||
get("/endorsements", AccountController, :own_endorsements)
|
||||
get("/blocks", AccountController, :blocks)
|
||||
get("/mutes", AccountController, :mutes)
|
||||
|
||||
|
|
@ -667,6 +687,8 @@ defmodule Pleroma.Web.Router do
|
|||
post("/accounts/:id/note", AccountController, :note)
|
||||
post("/accounts/:id/pin", AccountController, :endorse)
|
||||
post("/accounts/:id/unpin", AccountController, :unendorse)
|
||||
post("/accounts/:id/endorse", AccountController, :endorse)
|
||||
post("/accounts/:id/unendorse", AccountController, :unendorse)
|
||||
post("/accounts/:id/remove_from_followers", AccountController, :remove_from_followers)
|
||||
|
||||
get("/conversations", ConversationController, :index)
|
||||
|
|
@ -742,6 +764,7 @@ defmodule Pleroma.Web.Router do
|
|||
post("/statuses/:id/mute", StatusController, :mute_conversation)
|
||||
post("/statuses/:id/unmute", StatusController, :unmute_conversation)
|
||||
post("/statuses/:id/translate", StatusController, :translate)
|
||||
get("/statuses/:id/quotes", StatusController, :quotes)
|
||||
|
||||
post("/push/subscription", SubscriptionController, :create)
|
||||
get("/push/subscription", SubscriptionController, :show)
|
||||
|
|
@ -782,6 +805,7 @@ defmodule Pleroma.Web.Router do
|
|||
get("/accounts/:id/statuses", AccountController, :statuses)
|
||||
get("/accounts/:id/followers", AccountController, :followers)
|
||||
get("/accounts/:id/following", AccountController, :following)
|
||||
get("/accounts/:id/endorsements", AccountController, :endorsements)
|
||||
get("/accounts/:id", AccountController, :show)
|
||||
|
||||
post("/accounts", AccountController, :create)
|
||||
|
|
@ -894,7 +918,11 @@ defmodule Pleroma.Web.Router do
|
|||
|
||||
scope "/", Pleroma.Web do
|
||||
pipe_through(:browser)
|
||||
|
||||
get("/mailer/unsubscribe/:token", Mailer.SubscriptionController, :unsubscribe)
|
||||
|
||||
get("/frontend_switcher", FrontendSwitcher.FrontendSwitcherController, :switch)
|
||||
post("/frontend_switcher", FrontendSwitcher.FrontendSwitcherController, :do_switch)
|
||||
end
|
||||
|
||||
pipeline :ap_service_actor do
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ defmodule Pleroma.Web.Streamer do
|
|||
alias Pleroma.Chat.MessageReference
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Conversation.Participation
|
||||
alias Pleroma.Marker
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.User
|
||||
|
|
@ -321,6 +322,16 @@ defmodule Pleroma.Web.Streamer do
|
|||
end)
|
||||
end
|
||||
|
||||
defp do_stream(topic, %Marker{} = marker) do
|
||||
Registry.dispatch(@registry, "#{topic}:#{marker.user_id}", fn list ->
|
||||
Enum.each(list, fn {pid, _auth} ->
|
||||
text = StreamerView.render("marker.json", marker)
|
||||
|
||||
send(pid, {:text, text})
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
defp do_stream(topic, item) do
|
||||
Logger.debug("Trying to push to #{topic}")
|
||||
Logger.debug("Pushing item to #{topic}")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
<h2>Switch frontend</h2>
|
||||
|
||||
<%= form_for @conn, Routes.frontend_switcher_path(@conn, :do_switch), fn f -> %>
|
||||
<%= select(f, :frontend, @choices) %>
|
||||
|
||||
<%= submit do: "submit" %>
|
||||
<% end %>
|
||||
|
|
@ -7,6 +7,7 @@ defmodule Pleroma.Web.StreamerView do
|
|||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Conversation.Participation
|
||||
alias Pleroma.Marker
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.MastodonAPI.NotificationView
|
||||
|
|
@ -164,6 +165,19 @@ defmodule Pleroma.Web.StreamerView do
|
|||
|> Jason.encode!()
|
||||
end
|
||||
|
||||
def render("marker.json", %Marker{} = marker) do
|
||||
%{
|
||||
event: "marker",
|
||||
payload:
|
||||
Pleroma.Web.MastodonAPI.MarkerView.render(
|
||||
"markers.json",
|
||||
markers: [marker]
|
||||
)
|
||||
|> Jason.encode!()
|
||||
}
|
||||
|> Jason.encode!()
|
||||
end
|
||||
|
||||
def render("pleroma_respond.json", %{type: type, result: result} = params) do
|
||||
%{
|
||||
event: "pleroma:respond",
|
||||
|
|
|
|||
11
priv/repo/migrations/20220222203933_add_pinned_to_chats.exs
Normal file
11
priv/repo/migrations/20220222203933_add_pinned_to_chats.exs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
defmodule Pleroma.Repo.Migrations.AddPinnedToChats do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table(:chats) do
|
||||
add(:pinned, :boolean, default: false, null: false)
|
||||
end
|
||||
|
||||
create(index(:chats, [:pinned]))
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
defmodule Pleroma.Repo.Migrations.AddAcceptsChatMessagesIndexToUsers do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create(index(:users, [:accepts_chat_messages]))
|
||||
end
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue