Merge develop
|
@ -3,7 +3,6 @@
|
||||||
/db
|
/db
|
||||||
/deps
|
/deps
|
||||||
/*.ez
|
/*.ez
|
||||||
/uploads
|
|
||||||
/test/uploads
|
/test/uploads
|
||||||
/.elixir_ls
|
/.elixir_ls
|
||||||
/test/fixtures/test_tmp.txt
|
/test/fixtures/test_tmp.txt
|
||||||
|
|
22
CHANGELOG.md
|
@ -16,14 +16,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Configuration: `link_name` option
|
- Configuration: `link_name` option
|
||||||
- Configuration: `fetch_initial_posts` option
|
- Configuration: `fetch_initial_posts` option
|
||||||
- Configuration: `notify_email` option
|
- Configuration: `notify_email` option
|
||||||
- Pleroma API: User subscribtions
|
- Configuration: Media proxy `whitelist` option
|
||||||
|
- Pleroma API: User subscriptions
|
||||||
|
- Pleroma API: Healthcheck endpoint
|
||||||
- Admin API: Endpoints for listing/revoking invite tokens
|
- Admin API: Endpoints for listing/revoking invite tokens
|
||||||
- Admin API: Endpoints for making users follow/unfollow each other
|
- Admin API: Endpoints for making users follow/unfollow each other
|
||||||
- Mastodon API: [Scheduled statuses](https://docs.joinmastodon.org/api/rest/scheduled-statuses/)
|
- Mastodon API: [Scheduled statuses](https://docs.joinmastodon.org/api/rest/scheduled-statuses/)
|
||||||
- Mastodon API: `/api/v1/notifications/destroy_multiple` (glitch-soc extension)
|
- Mastodon API: `/api/v1/notifications/destroy_multiple` (glitch-soc extension)
|
||||||
|
- Mastodon API: `/api/v1/pleroma/accounts/:id/favourites` (API extension)
|
||||||
- Mastodon API: [Reports](https://docs.joinmastodon.org/api/rest/reports/)
|
- Mastodon API: [Reports](https://docs.joinmastodon.org/api/rest/reports/)
|
||||||
- ActivityPub C2S: OAuth endpoints
|
- ActivityPub C2S: OAuth endpoints
|
||||||
- Metadata RelMe provider
|
- Metadata RelMe provider
|
||||||
|
- Emoji packs and emoji pack manager
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- **Breaking:** Configuration: move from Pleroma.Mailer to Pleroma.Emails.Mailer
|
- **Breaking:** Configuration: move from Pleroma.Mailer to Pleroma.Emails.Mailer
|
||||||
|
@ -37,11 +41,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Configuration: Dedupe enabled by default
|
- Configuration: Dedupe enabled by default
|
||||||
- Configuration: Added `extra_cookie_attrs` for setting non-standard cookie attributes. Defaults to ["SameSite=Lax"] so that remote follows work.
|
- Configuration: Added `extra_cookie_attrs` for setting non-standard cookie attributes. Defaults to ["SameSite=Lax"] so that remote follows work.
|
||||||
- Pleroma API: Support for emoji tags in `/api/pleroma/emoji` resulting in a breaking API change
|
- Pleroma API: Support for emoji tags in `/api/pleroma/emoji` resulting in a breaking API change
|
||||||
|
- Timelines: Messages involving people you have blocked will be excluded from the timeline in all cases instead of just repeats.
|
||||||
- Mastodon API: Support for `exclude_types`, `limit` and `min_id` in `/api/v1/notifications`
|
- Mastodon API: Support for `exclude_types`, `limit` and `min_id` in `/api/v1/notifications`
|
||||||
- Mastodon API: Add `languages` and `registrations` to `/api/v1/instance`
|
- Mastodon API: Add `languages` and `registrations` to `/api/v1/instance`
|
||||||
- Mastodon API: Provide plaintext versions of cw/content in the Status entity
|
- Mastodon API: Provide plaintext versions of cw/content in the Status entity
|
||||||
- Mastodon API: Add `pleroma.conversation_id` field to the Status entity
|
- Mastodon API: Add `pleroma.conversation_id`, `pleroma.in_reply_to_account_acct` fields to the Status entity
|
||||||
- Mastodon API: Add `pleroma.tags`, `pleroma.relationship{}`, `pleroma.is_moderator`, `pleroma.is_admin`, `pleroma.confirmation_pending` fields to the User entity
|
- Mastodon API: Add `pleroma.tags`, `pleroma.relationship{}`, `pleroma.is_moderator`, `pleroma.is_admin`, `pleroma.confirmation_pending`, `pleroma.hide_followers`, `pleroma.hide_follows`, `pleroma.hide_favorites` fields to the User entity
|
||||||
|
- Mastodon API: Add `pleroma.show_role`, `pleroma.no_rich_text` fields to the Source subentity
|
||||||
|
- Mastodon API: Add support for updating `no_rich_text`, `hide_followers`, `hide_follows`, `hide_favorites`, `show_role` in `PATCH /api/v1/update_credentials`
|
||||||
- Mastodon API: Add `pleroma.is_seen` to the Notification entity
|
- Mastodon API: Add `pleroma.is_seen` to the Notification entity
|
||||||
- Mastodon API: Add `pleroma.local` to the Status entity
|
- Mastodon API: Add `pleroma.local` to the Status entity
|
||||||
- Mastodon API: Add `preview` parameter to `POST /api/v1/statuses`
|
- Mastodon API: Add `preview` parameter to `POST /api/v1/statuses`
|
||||||
|
@ -50,6 +57,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Mastodon API: Remove attachment limit in the Status entity
|
- Mastodon API: Remove attachment limit in the Status entity
|
||||||
- Deps: Updated Cowboy to 2.6
|
- Deps: Updated Cowboy to 2.6
|
||||||
- Deps: Updated Ecto to 3.0.7
|
- Deps: Updated Ecto to 3.0.7
|
||||||
|
- Don't ship finmoji by default, they can be installed as an emoji pack
|
||||||
|
- Mastodon API: Added support max_id & since_id for bookmark timeline endpoints.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Followers counter not being updated when a follower is blocked
|
- Followers counter not being updated when a follower is blocked
|
||||||
|
@ -64,13 +73,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Federation: Cope with missing or explicitly nulled address lists
|
- Federation: Cope with missing or explicitly nulled address lists
|
||||||
- Federation: Explicitly ensure activities addressed to `as:Public` become addressed to the followers collection
|
- Federation: Explicitly ensure activities addressed to `as:Public` become addressed to the followers collection
|
||||||
- Federation: Better cope with actors which do not declare a followers collection and use `as:Public` with these semantics
|
- Federation: Better cope with actors which do not declare a followers collection and use `as:Public` with these semantics
|
||||||
|
- Federation: Follow requests from remote users who have been blocked will be automatically rejected if appropriate
|
||||||
- MediaProxy: Parse name from content disposition headers even for non-whitelisted types
|
- MediaProxy: Parse name from content disposition headers even for non-whitelisted types
|
||||||
- MediaProxy: S3 link encoding
|
- MediaProxy: S3 link encoding
|
||||||
- Rich Media: Reject any data which cannot be explicitly encoded into JSON
|
- Rich Media: Reject any data which cannot be explicitly encoded into JSON
|
||||||
|
- Pleroma API: Importing follows from Mastodon 2.8+
|
||||||
|
- Twitter API: Exposing default scope, `no_rich_text` of the user to anyone
|
||||||
|
- Twitter API: Returning the `role` object in user entity despite `show_role = false`
|
||||||
- Mastodon API: `/api/v1/favourites` serving only public activities
|
- Mastodon API: `/api/v1/favourites` serving only public activities
|
||||||
- Mastodon API: Reblogs having `in_reply_to_id` - `null` even when they are replies
|
- Mastodon API: Reblogs having `in_reply_to_id` - `null` even when they are replies
|
||||||
- Mastodon API: Streaming API broadcasting wrong activity id
|
- Mastodon API: Streaming API broadcasting wrong activity id
|
||||||
- Mastodon API: 500 errors when requesting a card for a private conversation
|
- Mastodon API: 500 errors when requesting a card for a private conversation
|
||||||
|
- Mastodon API: Handling of `reblogs` in `/api/v1/accounts/:id/follow`
|
||||||
|
- Mastodon API: Correct `reblogged`, `favourited`, and `bookmarked` values in the reblog status JSON
|
||||||
|
- Mastodon API: Exposing default scope of the user to anyone
|
||||||
|
|
||||||
## [0.9.9999] - 2019-04-05
|
## [0.9.9999] - 2019-04-05
|
||||||
### Security
|
### Security
|
||||||
|
|
7
COPYING
|
@ -39,10 +39,3 @@ does not include the right to compile photos from Unsplash to replicate
|
||||||
a similar or competing service.
|
a similar or competing service.
|
||||||
|
|
||||||
priv/static/images/city.jpg
|
priv/static/images/city.jpg
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
The files present under the priv/static/finmoji directory are copyright
|
|
||||||
Finland <https://finland.fi/emoji/>, and are distributed under the Creative
|
|
||||||
Commons Attribution-NonCommercial-NoDerivatives 4.0 International license, you
|
|
||||||
should have received a copy of the license file as CC-BY-NC-ND-4.0.
|
|
||||||
|
|
|
@ -100,9 +100,9 @@
|
||||||
shortcode_globs: ["/emoji/custom/**/*.png"],
|
shortcode_globs: ["/emoji/custom/**/*.png"],
|
||||||
groups: [
|
groups: [
|
||||||
# Put groups that have higher priority than defaults here. Example in `docs/config/custom_emoji.md`
|
# Put groups that have higher priority than defaults here. Example in `docs/config/custom_emoji.md`
|
||||||
Finmoji: "/finmoji/128px/*-128.png",
|
Custom: ["/emoji/*.png", "/emoji/**/*.png"]
|
||||||
Custom: ["/emoji/*.png", "/emoji/custom/*.png"]
|
],
|
||||||
]
|
default_manifest: "https://git.pleroma.social/pleroma/emoji-index/raw/master/index.json"
|
||||||
|
|
||||||
config :pleroma, :uri_schemes,
|
config :pleroma, :uri_schemes,
|
||||||
valid_schemes: [
|
valid_schemes: [
|
||||||
|
@ -221,9 +221,9 @@
|
||||||
allowed_post_formats: [
|
allowed_post_formats: [
|
||||||
"text/plain",
|
"text/plain",
|
||||||
"text/html",
|
"text/html",
|
||||||
"text/markdown"
|
"text/markdown",
|
||||||
|
"text/bbcode"
|
||||||
],
|
],
|
||||||
finmoji_enabled: true,
|
|
||||||
mrf_transparency: true,
|
mrf_transparency: true,
|
||||||
autofollowed_nicknames: [],
|
autofollowed_nicknames: [],
|
||||||
max_pinned_statuses: 1,
|
max_pinned_statuses: 1,
|
||||||
|
@ -231,7 +231,8 @@
|
||||||
welcome_user_nickname: nil,
|
welcome_user_nickname: nil,
|
||||||
welcome_message: nil,
|
welcome_message: nil,
|
||||||
max_report_comment_size: 1000,
|
max_report_comment_size: 1000,
|
||||||
safe_dm_mentions: false
|
safe_dm_mentions: false,
|
||||||
|
healthcheck: false
|
||||||
|
|
||||||
config :pleroma, :markup,
|
config :pleroma, :markup,
|
||||||
# XXX - unfortunately, inline images must be enabled by default right now, because
|
# XXX - unfortunately, inline images must be enabled by default right now, because
|
||||||
|
@ -326,7 +327,8 @@
|
||||||
follow_redirect: true,
|
follow_redirect: true,
|
||||||
pool: :media
|
pool: :media
|
||||||
]
|
]
|
||||||
]
|
],
|
||||||
|
whitelist: []
|
||||||
|
|
||||||
config :pleroma, :chat, enabled: true
|
config :pleroma, :chat, enabled: true
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ Has these additional fields under the `pleroma` object:
|
||||||
|
|
||||||
- `local`: true if the post was made on the local instance.
|
- `local`: true if the post was made on the local instance.
|
||||||
- `conversation_id`: the ID of the conversation the status is associated with (if any)
|
- `conversation_id`: the ID of the conversation the status is associated with (if any)
|
||||||
|
- `in_reply_to_account_acct`: the `acct` property of User entity for replied user (if any)
|
||||||
- `content`: a map consisting of alternate representations of the `content` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain`
|
- `content`: a map consisting of alternate representations of the `content` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain`
|
||||||
- `spoiler_text`: a map consisting of alternate representations of the `spoiler_text` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain`
|
- `spoiler_text`: a map consisting of alternate representations of the `spoiler_text` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain`
|
||||||
|
|
||||||
|
@ -37,9 +38,18 @@ Has these additional fields under the `pleroma` object:
|
||||||
|
|
||||||
- `tags`: Lists an array of tags for the user
|
- `tags`: Lists an array of tags for the user
|
||||||
- `relationship{}`: Includes fields as documented for Mastodon API https://docs.joinmastodon.org/api/entities/#relationship
|
- `relationship{}`: Includes fields as documented for Mastodon API https://docs.joinmastodon.org/api/entities/#relationship
|
||||||
- `is_moderator`: boolean, true if user is a moderator
|
- `is_moderator`: boolean, nullable, true if user is a moderator
|
||||||
- `is_admin`: boolean, true if user is an admin
|
- `is_admin`: boolean, nullable, true if user is an admin
|
||||||
- `confirmation_pending`: boolean, true if a new user account is waiting on email confirmation to be activated
|
- `confirmation_pending`: boolean, true if a new user account is waiting on email confirmation to be activated
|
||||||
|
- `hide_followers`: boolean, true when the user has follower hiding enabled
|
||||||
|
- `hide_follows`: boolean, true when the user has follow hiding enabled
|
||||||
|
|
||||||
|
### Source
|
||||||
|
|
||||||
|
Has these additional fields under the `pleroma` object:
|
||||||
|
|
||||||
|
- `show_role`: boolean, nullable, true when the user wants his role (e.g admin, moderator) to be shown
|
||||||
|
- `no_rich_text` - boolean, nullable, true when html tags are stripped from all statuses requested from the API
|
||||||
|
|
||||||
## Account Search
|
## Account Search
|
||||||
|
|
||||||
|
@ -58,3 +68,15 @@ Has these additional fields under the `pleroma` object:
|
||||||
Additional parameters can be added to the JSON body/Form data:
|
Additional parameters can be added to the JSON body/Form data:
|
||||||
|
|
||||||
- `preview`: boolean, if set to `true` the post won't be actually posted, but the status entitiy would still be rendered back. This could be useful for previewing rich text/custom emoji, for example.
|
- `preview`: boolean, if set to `true` the post won't be actually posted, but the status entitiy would still be rendered back. This could be useful for previewing rich text/custom emoji, for example.
|
||||||
|
- `content_type`: string, contain the MIME type of the status, it is transformed into HTML by the backend. You can get the list of the supported MIME types with the nodeinfo endpoint.
|
||||||
|
|
||||||
|
## PATCH `/api/v1/update_credentials`
|
||||||
|
|
||||||
|
Additional parameters can be added to the JSON body/Form data:
|
||||||
|
|
||||||
|
- `no_rich_text` - if true, html tags are stripped from all statuses requested from the API
|
||||||
|
- `hide_followers` - if true, user's followers will be hidden
|
||||||
|
- `hide_follows` - if true, user's follows will be hidden
|
||||||
|
- `hide_favorites` - if true, user's favorites timeline will be hidden
|
||||||
|
- `show_role` - if true, user's role (e.g admin, moderator) will be exposed to anyone in the API
|
||||||
|
- `default_scope` - the scope returned under `privacy` key in Source subentity
|
||||||
|
|
|
@ -77,7 +77,7 @@ Request parameters can be passed via [query strings](https://en.wikipedia.org/wi
|
||||||
* `token`: invite token required when the registrations aren't public.
|
* `token`: invite token required when the registrations aren't public.
|
||||||
* Response: JSON. Returns a user object on success, otherwise returns `{"error": "error_msg"}`
|
* Response: JSON. Returns a user object on success, otherwise returns `{"error": "error_msg"}`
|
||||||
* Example response:
|
* Example response:
|
||||||
```
|
```json
|
||||||
{
|
{
|
||||||
"background_image": null,
|
"background_image": null,
|
||||||
"cover_photo": "https://pleroma.soykaf.com/images/banner.png",
|
"cover_photo": "https://pleroma.soykaf.com/images/banner.png",
|
||||||
|
@ -187,6 +187,62 @@ See [Admin-API](Admin-API.md)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## `/api/v1/pleroma/accounts/:id/favourites`
|
||||||
|
### Returns favorites timeline of any user
|
||||||
|
* Method `GET`
|
||||||
|
* Authentication: not required
|
||||||
|
* Params:
|
||||||
|
* `id`: the id of the account for whom to return results
|
||||||
|
* `limit`: optional, the number of records to retrieve
|
||||||
|
* `since_id`: optional, returns results that are more recent than the specified id
|
||||||
|
* `max_id`: optional, returns results that are older than the specified id
|
||||||
|
* Response: JSON, returns a list of Mastodon Status entities on success, otherwise returns `{"error": "error_msg"}`
|
||||||
|
* Example response:
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"account": {
|
||||||
|
"id": "9hptFmUF3ztxYh3Svg",
|
||||||
|
"url": "https://pleroma.example.org/users/nick2",
|
||||||
|
"username": "nick2",
|
||||||
|
...
|
||||||
|
},
|
||||||
|
"application": {"name": "Web", "website": null},
|
||||||
|
"bookmarked": false,
|
||||||
|
"card": null,
|
||||||
|
"content": "This is :moominmamma: note 0",
|
||||||
|
"created_at": "2019-04-15T15:42:15.000Z",
|
||||||
|
"emojis": [],
|
||||||
|
"favourited": false,
|
||||||
|
"favourites_count": 1,
|
||||||
|
"id": "9hptFmVJ02khbzYJaS",
|
||||||
|
"in_reply_to_account_id": null,
|
||||||
|
"in_reply_to_id": null,
|
||||||
|
"language": null,
|
||||||
|
"media_attachments": [],
|
||||||
|
"mentions": [],
|
||||||
|
"muted": false,
|
||||||
|
"pinned": false,
|
||||||
|
"pleroma": {
|
||||||
|
"content": {"text/plain": "This is :moominmamma: note 0"},
|
||||||
|
"conversation_id": 13679,
|
||||||
|
"local": true,
|
||||||
|
"spoiler_text": {"text/plain": "2hu"}
|
||||||
|
},
|
||||||
|
"reblog": null,
|
||||||
|
"reblogged": false,
|
||||||
|
"reblogs_count": 0,
|
||||||
|
"replies_count": 0,
|
||||||
|
"sensitive": false,
|
||||||
|
"spoiler_text": "2hu",
|
||||||
|
"tags": [{"name": "2hu", "url": "/tag/2hu"}],
|
||||||
|
"uri": "https://pleroma.example.org/objects/198ed2a1-7912-4482-b559-244a0369e984",
|
||||||
|
"url": "https://pleroma.example.org/notice/9hptFmVJ02khbzYJaS",
|
||||||
|
"visibility": "public"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
## `/api/pleroma/notification_settings`
|
## `/api/pleroma/notification_settings`
|
||||||
### Updates user notification settings
|
### Updates user notification settings
|
||||||
* Method `PUT`
|
* Method `PUT`
|
||||||
|
@ -197,3 +253,20 @@ See [Admin-API](Admin-API.md)
|
||||||
* `remote`: BOOLEAN field, receives notifications from people on remote instances
|
* `remote`: BOOLEAN field, receives notifications from people on remote instances
|
||||||
* `local`: BOOLEAN field, receives notifications from people on the local instance
|
* `local`: BOOLEAN field, receives notifications from people on the local instance
|
||||||
* Response: JSON. Returns `{"status": "success"}` if the update was successful, otherwise returns `{"error": "error_msg"}`
|
* Response: JSON. Returns `{"status": "success"}` if the update was successful, otherwise returns `{"error": "error_msg"}`
|
||||||
|
|
||||||
|
## `/api/pleroma/healthcheck`
|
||||||
|
### Healthcheck endpoint with additional system data.
|
||||||
|
* Method `GET`
|
||||||
|
* Authentication: not required
|
||||||
|
* Params: none
|
||||||
|
* Response: JSON, statuses (200 - healthy, 503 unhealthy).
|
||||||
|
* Example response:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pool_size": 0, # database connection pool
|
||||||
|
"active": 0, # active processes
|
||||||
|
"idle": 0, # idle processes
|
||||||
|
"memory_used": 0.00, # Memory used
|
||||||
|
"healthy": true # Instance state
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
|
@ -87,7 +87,6 @@ config :pleroma, Pleroma.Emails.Mailer,
|
||||||
* `quarantined_instances`: List of ActivityPub instances where private(DMs, followers-only) activities will not be send.
|
* `quarantined_instances`: List of ActivityPub instances where private(DMs, followers-only) activities will not be send.
|
||||||
* `managed_config`: Whenether the config for pleroma-fe is configured in this config or in ``static/config.json``
|
* `managed_config`: Whenether the config for pleroma-fe is configured in this config or in ``static/config.json``
|
||||||
* `allowed_post_formats`: MIME-type list of formats allowed to be posted (transformed into HTML)
|
* `allowed_post_formats`: MIME-type list of formats allowed to be posted (transformed into HTML)
|
||||||
* `finmoji_enabled`: Whenether to enable the finmojis in the custom emojis.
|
|
||||||
* `mrf_transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo).
|
* `mrf_transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo).
|
||||||
* `scope_copy`: Copy the scope (private/unlisted/public) in replies to posts by default.
|
* `scope_copy`: Copy the scope (private/unlisted/public) in replies to posts by default.
|
||||||
* `subject_line_behavior`: Allows changing the default behaviour of subject lines in replies. Valid values:
|
* `subject_line_behavior`: Allows changing the default behaviour of subject lines in replies. Valid values:
|
||||||
|
@ -104,6 +103,7 @@ config :pleroma, Pleroma.Emails.Mailer,
|
||||||
* `welcome_user_nickname`: The nickname of the local user that sends the welcome message.
|
* `welcome_user_nickname`: The nickname of the local user that sends the welcome message.
|
||||||
* `max_report_comment_size`: The maximum size of the report comment (Default: `1000`)
|
* `max_report_comment_size`: The maximum size of the report comment (Default: `1000`)
|
||||||
* `safe_dm_mentions`: If set to true, only mentions at the beginning of a post will be used to address people in direct messages. This is to prevent accidental mentioning of people when talking about them (e.g. "@friend hey i really don't like @enemy"). (Default: `false`)
|
* `safe_dm_mentions`: If set to true, only mentions at the beginning of a post will be used to address people in direct messages. This is to prevent accidental mentioning of people when talking about them (e.g. "@friend hey i really don't like @enemy"). (Default: `false`)
|
||||||
|
* `healthcheck`: if set to true, system data will be shown on ``/api/pleroma/healthcheck``.
|
||||||
|
|
||||||
## :logger
|
## :logger
|
||||||
* `backends`: `:console` is used to send logs to stdout, `{ExSyslogger, :ex_syslogger}` to log to syslog, and `Quack.Logger` to log to Slack
|
* `backends`: `:console` is used to send logs to stdout, `{ExSyslogger, :ex_syslogger}` to log to syslog, and `Quack.Logger` to log to Slack
|
||||||
|
@ -205,6 +205,7 @@ This section is used to configure Pleroma-FE, unless ``:managed_config`` in ``:i
|
||||||
* `enabled`: Enables proxying of remote media to the instance’s proxy
|
* `enabled`: Enables proxying of remote media to the instance’s proxy
|
||||||
* `base_url`: The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts.
|
* `base_url`: The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts.
|
||||||
* `proxy_opts`: All options defined in `Pleroma.ReverseProxy` documentation, defaults to `[max_body_length: (25*1_048_576)]`.
|
* `proxy_opts`: All options defined in `Pleroma.ReverseProxy` documentation, defaults to `[max_body_length: (25*1_048_576)]`.
|
||||||
|
* `whitelist`: List of domains to bypass the mediaproxy
|
||||||
|
|
||||||
## :gopher
|
## :gopher
|
||||||
* `enabled`: Enables the gopher interface
|
* `enabled`: Enables the gopher interface
|
||||||
|
@ -499,3 +500,8 @@ config :ueberauth, Ueberauth,
|
||||||
microsoft: {Ueberauth.Strategy.Microsoft, [callback_params: []]}
|
microsoft: {Ueberauth.Strategy.Microsoft, [callback_params: []]}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## :emoji
|
||||||
|
* `shortcode_globs`: Location of custom emoji files. `*` can be used as a wildcard. Example `["/emoji/custom/**/*.png"]`
|
||||||
|
* `groups`: Emojis are ordered in groups (tags). This is an array of key-value pairs where the key is the groupname and the value the location or array of locations. `*` can be used as a wildcard. Example `[Custom: ["/emoji/*.png", "/emoji/custom/*.png"]]`
|
||||||
|
* `default_manifest`: Location of the JSON-manifest. This manifest contains information about the emoji-packs you can download. Currently only one manifest can be added (no arrays).
|
||||||
|
|
|
@ -1,15 +1,25 @@
|
||||||
# Custom Emoji
|
# Custom Emoji
|
||||||
|
|
||||||
|
Before you add your own custom emoji, check if they are available in an existing pack.
|
||||||
|
See `Mix.Tasks.Pleroma.Emoji` for information about emoji packs.
|
||||||
|
|
||||||
To add custom emoji:
|
To add custom emoji:
|
||||||
* Add the image file(s) to `priv/static/emoji/custom`
|
* Create the `STATIC-DIR/emoji/` directory if it doesn't exist
|
||||||
* In case of conflicts: add the desired shortcode with the path to `config/custom_emoji.txt`, comma-separated and one per line
|
(`STATIC-DIR` is configurable, `instance/static/` by default)
|
||||||
* Force recompilation (``mix clean && mix compile``)
|
* Create a directory with whatever name you want (custom is a good name to show the purpose of it).
|
||||||
|
This will create a local emoji pack.
|
||||||
|
* Put your `.png` emoji files in that directory. In case of conflicts, you can create an `emoji.txt`
|
||||||
|
file in that directory and specify a custom shortcode using the following format:
|
||||||
|
`shortcode, file-path, tag1, tag2, etc`. One emoji per line. Note that if you do so,
|
||||||
|
you'll have to list all other emojis in the pack too.
|
||||||
|
* Either restart pleroma or connect to the iex session pleroma's running and
|
||||||
|
run `Pleroma.Emoji.reload/0` in it.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
image files (in `/priv/static/emoji/custom`): `happy.png` and `sad.png`
|
image files (in `instance/static/emoji/custom`): `happy.png` and `sad.png`
|
||||||
|
|
||||||
content of `config/custom_emoji.txt`:
|
content of `emoji.txt`:
|
||||||
```
|
```
|
||||||
happy, /emoji/custom/happy.png, Tag1,Tag2
|
happy, /emoji/custom/happy.png, Tag1,Tag2
|
||||||
sad, /emoji/custom/sad.png, Tag1
|
sad, /emoji/custom/sad.png, Tag1
|
||||||
|
@ -18,6 +28,11 @@ foo, /emoji/custom/foo.png
|
||||||
|
|
||||||
The files should be PNG (APNG is okay with `.png` for `image/png` Content-type) and under 50kb for compatibility with mastodon.
|
The files should be PNG (APNG is okay with `.png` for `image/png` Content-type) and under 50kb for compatibility with mastodon.
|
||||||
|
|
||||||
|
Default file extentions and locations for emojis are set in `config.exs`. To use different locations or file-extentions, add the `shortcode_globs` to your secrets file (`prod.secret.exs` or `dev.secret.exs`) and edit it. Note that not all fediverse-software will show emojis with other file extentions:
|
||||||
|
```elixir
|
||||||
|
config :pleroma, :emoji, shortcode_globs: ["/emoji/custom/**/*.png", "/emoji/custom/**/*.gif"]
|
||||||
|
```
|
||||||
|
|
||||||
## Emoji tags (groups)
|
## Emoji tags (groups)
|
||||||
|
|
||||||
Default tags are set in `config.exs`. To set your own tags, copy the structure to your secrets file (`prod.secret.exs` or `dev.secret.exs`) and edit it.
|
Default tags are set in `config.exs`. To set your own tags, copy the structure to your secrets file (`prod.secret.exs` or `dev.secret.exs`) and edit it.
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
defmodule Pleroma.Healthcheck do
|
||||||
|
@moduledoc """
|
||||||
|
Module collects metrics about app and assign healthy status.
|
||||||
|
"""
|
||||||
|
alias Pleroma.Healthcheck
|
||||||
|
alias Pleroma.Repo
|
||||||
|
|
||||||
|
defstruct pool_size: 0,
|
||||||
|
active: 0,
|
||||||
|
idle: 0,
|
||||||
|
memory_used: 0,
|
||||||
|
healthy: true
|
||||||
|
|
||||||
|
@type t :: %__MODULE__{
|
||||||
|
pool_size: non_neg_integer(),
|
||||||
|
active: non_neg_integer(),
|
||||||
|
idle: non_neg_integer(),
|
||||||
|
memory_used: number(),
|
||||||
|
healthy: boolean()
|
||||||
|
}
|
||||||
|
|
||||||
|
@spec system_info() :: t()
|
||||||
|
def system_info do
|
||||||
|
%Healthcheck{
|
||||||
|
memory_used: Float.round(:erlang.memory(:total) / 1024 / 1024, 2)
|
||||||
|
}
|
||||||
|
|> assign_db_info()
|
||||||
|
|> check_health()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp assign_db_info(healthcheck) do
|
||||||
|
database = Application.get_env(:pleroma, Repo)[:database]
|
||||||
|
|
||||||
|
query =
|
||||||
|
"select state, count(pid) from pg_stat_activity where datname = '#{database}' group by state;"
|
||||||
|
|
||||||
|
result = Repo.query!(query)
|
||||||
|
pool_size = Application.get_env(:pleroma, Repo)[:pool_size]
|
||||||
|
|
||||||
|
db_info =
|
||||||
|
Enum.reduce(result.rows, %{active: 0, idle: 0}, fn [state, cnt], states ->
|
||||||
|
if state == "active" do
|
||||||
|
Map.put(states, :active, states.active + cnt)
|
||||||
|
else
|
||||||
|
Map.put(states, :idle, states.idle + cnt)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|> Map.put(:pool_size, pool_size)
|
||||||
|
|
||||||
|
Map.merge(healthcheck, db_info)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec check_health(Healthcheck.t()) :: Healthcheck.t()
|
||||||
|
def check_health(%{pool_size: pool_size, active: active} = check)
|
||||||
|
when active >= pool_size do
|
||||||
|
%{check | healthy: false}
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_health(check), do: check
|
||||||
|
end
|
|
@ -0,0 +1,293 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Mix.Tasks.Pleroma.Emoji do
|
||||||
|
use Mix.Task
|
||||||
|
|
||||||
|
@shortdoc "Manages emoji packs"
|
||||||
|
@moduledoc """
|
||||||
|
Manages emoji packs
|
||||||
|
|
||||||
|
## ls-packs
|
||||||
|
|
||||||
|
mix pleroma.emoji ls-packs [OPTION...]
|
||||||
|
|
||||||
|
Lists the emoji packs and metadata specified in the manifest.
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
- `-m, --manifest PATH/URL` - path to a custom manifest, it can
|
||||||
|
either be an URL starting with `http`, in that case the
|
||||||
|
manifest will be fetched from that address, or a local path
|
||||||
|
|
||||||
|
## get-packs
|
||||||
|
|
||||||
|
mix pleroma.emoji get-packs [OPTION...] PACKS
|
||||||
|
|
||||||
|
Fetches, verifies and installs the specified PACKS from the
|
||||||
|
manifest into the `STATIC-DIR/emoji/PACK-NAME`
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
- `-m, --manifest PATH/URL` - same as ls-packs
|
||||||
|
|
||||||
|
## gen-pack
|
||||||
|
|
||||||
|
mix pleroma.emoji gen-pack PACK-URL
|
||||||
|
|
||||||
|
Creates a new manifest entry and a file list from the specified
|
||||||
|
remote pack file. Currently, only .zip archives are recognized
|
||||||
|
as remote pack files and packs are therefore assumed to be zip
|
||||||
|
archives. This command is intended to run interactively and will
|
||||||
|
first ask you some basic questions about the pack, then download
|
||||||
|
the remote file and generate an SHA256 checksum for it, then
|
||||||
|
generate an emoji file list for you.
|
||||||
|
|
||||||
|
The manifest entry will either be written to a newly created
|
||||||
|
`index.json` file or appended to the existing one, *replacing*
|
||||||
|
the old pack with the same name if it was in the file previously.
|
||||||
|
|
||||||
|
The file list will be written to the file specified previously,
|
||||||
|
*replacing* that file. You _should_ check that the file list doesn't
|
||||||
|
contain anything you don't need in the pack, that is, anything that is
|
||||||
|
not an emoji (the whole pack is downloaded, but only emoji files
|
||||||
|
are extracted).
|
||||||
|
"""
|
||||||
|
|
||||||
|
@default_manifest Pleroma.Config.get!([:emoji, :default_manifest])
|
||||||
|
|
||||||
|
def run(["ls-packs" | args]) do
|
||||||
|
Application.ensure_all_started(:hackney)
|
||||||
|
|
||||||
|
{options, [], []} = parse_global_opts(args)
|
||||||
|
|
||||||
|
manifest =
|
||||||
|
fetch_manifest(if options[:manifest], do: options[:manifest], else: @default_manifest)
|
||||||
|
|
||||||
|
Enum.each(manifest, fn {name, info} ->
|
||||||
|
to_print = [
|
||||||
|
{"Name", name},
|
||||||
|
{"Homepage", info["homepage"]},
|
||||||
|
{"Description", info["description"]},
|
||||||
|
{"License", info["license"]},
|
||||||
|
{"Source", info["src"]}
|
||||||
|
]
|
||||||
|
|
||||||
|
for {param, value} <- to_print do
|
||||||
|
IO.puts(IO.ANSI.format([:bright, param, :normal, ": ", value]))
|
||||||
|
end
|
||||||
|
|
||||||
|
# A newline
|
||||||
|
IO.puts("")
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def run(["get-packs" | args]) do
|
||||||
|
Application.ensure_all_started(:hackney)
|
||||||
|
|
||||||
|
{options, pack_names, []} = parse_global_opts(args)
|
||||||
|
|
||||||
|
manifest_url = if options[:manifest], do: options[:manifest], else: @default_manifest
|
||||||
|
|
||||||
|
manifest = fetch_manifest(manifest_url)
|
||||||
|
|
||||||
|
for pack_name <- pack_names do
|
||||||
|
if Map.has_key?(manifest, pack_name) do
|
||||||
|
pack = manifest[pack_name]
|
||||||
|
src_url = pack["src"]
|
||||||
|
|
||||||
|
IO.puts(
|
||||||
|
IO.ANSI.format([
|
||||||
|
"Downloading ",
|
||||||
|
:bright,
|
||||||
|
pack_name,
|
||||||
|
:normal,
|
||||||
|
" from ",
|
||||||
|
:underline,
|
||||||
|
src_url
|
||||||
|
])
|
||||||
|
)
|
||||||
|
|
||||||
|
binary_archive = Tesla.get!(src_url).body
|
||||||
|
archive_sha = :crypto.hash(:sha256, binary_archive) |> Base.encode16()
|
||||||
|
|
||||||
|
sha_status_text = ["SHA256 of ", :bright, pack_name, :normal, " source file is ", :bright]
|
||||||
|
|
||||||
|
if archive_sha == String.upcase(pack["src_sha256"]) do
|
||||||
|
IO.puts(IO.ANSI.format(sha_status_text ++ [:green, "OK"]))
|
||||||
|
else
|
||||||
|
IO.puts(IO.ANSI.format(sha_status_text ++ [:red, "BAD"]))
|
||||||
|
|
||||||
|
raise "Bad SHA256 for #{pack_name}"
|
||||||
|
end
|
||||||
|
|
||||||
|
# The url specified in files should be in the same directory
|
||||||
|
files_url = Path.join(Path.dirname(manifest_url), pack["files"])
|
||||||
|
|
||||||
|
IO.puts(
|
||||||
|
IO.ANSI.format([
|
||||||
|
"Fetching the file list for ",
|
||||||
|
:bright,
|
||||||
|
pack_name,
|
||||||
|
:normal,
|
||||||
|
" from ",
|
||||||
|
:underline,
|
||||||
|
files_url
|
||||||
|
])
|
||||||
|
)
|
||||||
|
|
||||||
|
files = Tesla.get!(files_url).body |> Poison.decode!()
|
||||||
|
|
||||||
|
IO.puts(IO.ANSI.format(["Unpacking ", :bright, pack_name]))
|
||||||
|
|
||||||
|
pack_path =
|
||||||
|
Path.join([
|
||||||
|
Pleroma.Config.get!([:instance, :static_dir]),
|
||||||
|
"emoji",
|
||||||
|
pack_name
|
||||||
|
])
|
||||||
|
|
||||||
|
files_to_unzip =
|
||||||
|
Enum.map(
|
||||||
|
files,
|
||||||
|
fn {_, f} -> to_charlist(f) end
|
||||||
|
)
|
||||||
|
|
||||||
|
{:ok, _} =
|
||||||
|
:zip.unzip(binary_archive,
|
||||||
|
cwd: pack_path,
|
||||||
|
file_list: files_to_unzip
|
||||||
|
)
|
||||||
|
|
||||||
|
IO.puts(IO.ANSI.format(["Writing emoji.txt for ", :bright, pack_name]))
|
||||||
|
|
||||||
|
emoji_txt_str =
|
||||||
|
Enum.map(
|
||||||
|
files,
|
||||||
|
fn {shortcode, path} ->
|
||||||
|
emojo_path = Path.join("/emoji/#{pack_name}", path)
|
||||||
|
"#{shortcode}, #{emojo_path}"
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|> Enum.join("\n")
|
||||||
|
|
||||||
|
File.write!(Path.join(pack_path, "emoji.txt"), emoji_txt_str)
|
||||||
|
else
|
||||||
|
IO.puts(IO.ANSI.format([:bright, :red, "No pack named \"#{pack_name}\" found"]))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def run(["gen-pack", src]) do
|
||||||
|
Application.ensure_all_started(:hackney)
|
||||||
|
|
||||||
|
proposed_name = Path.basename(src) |> Path.rootname()
|
||||||
|
name = String.trim(IO.gets("Pack name [#{proposed_name}]: "))
|
||||||
|
# If there's no name, use the default one
|
||||||
|
name = if String.length(name) > 0, do: name, else: proposed_name
|
||||||
|
|
||||||
|
license = String.trim(IO.gets("License: "))
|
||||||
|
homepage = String.trim(IO.gets("Homepage: "))
|
||||||
|
description = String.trim(IO.gets("Description: "))
|
||||||
|
|
||||||
|
proposed_files_name = "#{name}.json"
|
||||||
|
files_name = String.trim(IO.gets("Save file list to [#{proposed_files_name}]: "))
|
||||||
|
files_name = if String.length(files_name) > 0, do: files_name, else: proposed_files_name
|
||||||
|
|
||||||
|
default_exts = [".png", ".gif"]
|
||||||
|
default_exts_str = Enum.join(default_exts, " ")
|
||||||
|
|
||||||
|
exts =
|
||||||
|
String.trim(
|
||||||
|
IO.gets("Emoji file extensions (separated with spaces) [#{default_exts_str}]: ")
|
||||||
|
)
|
||||||
|
|
||||||
|
exts =
|
||||||
|
if String.length(exts) > 0 do
|
||||||
|
String.split(exts, " ")
|
||||||
|
|> Enum.filter(fn e -> e |> String.trim() |> String.length() > 0 end)
|
||||||
|
else
|
||||||
|
default_exts
|
||||||
|
end
|
||||||
|
|
||||||
|
IO.puts("Downloading the pack and generating SHA256")
|
||||||
|
|
||||||
|
binary_archive = Tesla.get!(src).body
|
||||||
|
archive_sha = :crypto.hash(:sha256, binary_archive) |> Base.encode16()
|
||||||
|
|
||||||
|
IO.puts("SHA256 is #{archive_sha}")
|
||||||
|
|
||||||
|
pack_json = %{
|
||||||
|
name => %{
|
||||||
|
license: license,
|
||||||
|
homepage: homepage,
|
||||||
|
description: description,
|
||||||
|
src: src,
|
||||||
|
src_sha256: archive_sha,
|
||||||
|
files: files_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp_pack_dir = Path.join(System.tmp_dir!(), "emoji-pack-#{name}")
|
||||||
|
|
||||||
|
{:ok, _} =
|
||||||
|
:zip.unzip(
|
||||||
|
binary_archive,
|
||||||
|
cwd: tmp_pack_dir
|
||||||
|
)
|
||||||
|
|
||||||
|
emoji_map = Pleroma.Emoji.make_shortcode_to_file_map(tmp_pack_dir, exts)
|
||||||
|
|
||||||
|
File.write!(files_name, Poison.encode!(emoji_map, pretty: true))
|
||||||
|
|
||||||
|
IO.puts("""
|
||||||
|
|
||||||
|
#{files_name} has been created and contains the list of all found emojis in the pack.
|
||||||
|
Please review the files in the remove those not needed.
|
||||||
|
""")
|
||||||
|
|
||||||
|
if File.exists?("index.json") do
|
||||||
|
existing_data = File.read!("index.json") |> Poison.decode!()
|
||||||
|
|
||||||
|
File.write!(
|
||||||
|
"index.json",
|
||||||
|
Poison.encode!(
|
||||||
|
Map.merge(
|
||||||
|
existing_data,
|
||||||
|
pack_json
|
||||||
|
),
|
||||||
|
pretty: true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
IO.puts("index.json file has been update with the #{name} pack")
|
||||||
|
else
|
||||||
|
File.write!("index.json", Poison.encode!(pack_json, pretty: true))
|
||||||
|
|
||||||
|
IO.puts("index.json has been created with the #{name} pack")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp fetch_manifest(from) do
|
||||||
|
Poison.decode!(
|
||||||
|
if String.starts_with?(from, "http") do
|
||||||
|
Tesla.get!(from).body
|
||||||
|
else
|
||||||
|
File.read!(from)
|
||||||
|
end
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp parse_global_opts(args) do
|
||||||
|
OptionParser.parse(
|
||||||
|
args,
|
||||||
|
strict: [
|
||||||
|
manifest: :string
|
||||||
|
],
|
||||||
|
aliases: [
|
||||||
|
m: :manifest
|
||||||
|
]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
|
@ -162,7 +162,7 @@ def run(["new", nickname, email | rest]) do
|
||||||
def run(["rm", nickname]) do
|
def run(["rm", nickname]) do
|
||||||
Common.start_pleroma()
|
Common.start_pleroma()
|
||||||
|
|
||||||
with %User{local: true} = user <- User.get_by_nickname(nickname) do
|
with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
|
||||||
User.delete(user)
|
User.delete(user)
|
||||||
Mix.shell().info("User #{nickname} deleted.")
|
Mix.shell().info("User #{nickname} deleted.")
|
||||||
else
|
else
|
||||||
|
@ -174,7 +174,7 @@ def run(["rm", nickname]) do
|
||||||
def run(["toggle_activated", nickname]) do
|
def run(["toggle_activated", nickname]) do
|
||||||
Common.start_pleroma()
|
Common.start_pleroma()
|
||||||
|
|
||||||
with %User{} = user <- User.get_by_nickname(nickname) do
|
with %User{} = user <- User.get_cached_by_nickname(nickname) do
|
||||||
{:ok, user} = User.deactivate(user, !user.info.deactivated)
|
{:ok, user} = User.deactivate(user, !user.info.deactivated)
|
||||||
|
|
||||||
Mix.shell().info(
|
Mix.shell().info(
|
||||||
|
@ -189,7 +189,7 @@ def run(["toggle_activated", nickname]) do
|
||||||
def run(["reset_password", nickname]) do
|
def run(["reset_password", nickname]) do
|
||||||
Common.start_pleroma()
|
Common.start_pleroma()
|
||||||
|
|
||||||
with %User{local: true} = user <- User.get_by_nickname(nickname),
|
with %User{local: true} = user <- User.get_cached_by_nickname(nickname),
|
||||||
{:ok, token} <- Pleroma.PasswordResetToken.create_token(user) do
|
{:ok, token} <- Pleroma.PasswordResetToken.create_token(user) do
|
||||||
Mix.shell().info("Generated password reset token for #{user.nickname}")
|
Mix.shell().info("Generated password reset token for #{user.nickname}")
|
||||||
|
|
||||||
|
@ -211,14 +211,14 @@ def run(["reset_password", nickname]) do
|
||||||
def run(["unsubscribe", nickname]) do
|
def run(["unsubscribe", nickname]) do
|
||||||
Common.start_pleroma()
|
Common.start_pleroma()
|
||||||
|
|
||||||
with %User{} = user <- User.get_by_nickname(nickname) do
|
with %User{} = user <- User.get_cached_by_nickname(nickname) do
|
||||||
Mix.shell().info("Deactivating #{user.nickname}")
|
Mix.shell().info("Deactivating #{user.nickname}")
|
||||||
User.deactivate(user)
|
User.deactivate(user)
|
||||||
|
|
||||||
{:ok, friends} = User.get_friends(user)
|
{:ok, friends} = User.get_friends(user)
|
||||||
|
|
||||||
Enum.each(friends, fn friend ->
|
Enum.each(friends, fn friend ->
|
||||||
user = User.get_by_id(user.id)
|
user = User.get_cached_by_id(user.id)
|
||||||
|
|
||||||
Mix.shell().info("Unsubscribing #{friend.nickname} from #{user.nickname}")
|
Mix.shell().info("Unsubscribing #{friend.nickname} from #{user.nickname}")
|
||||||
User.unfollow(user, friend)
|
User.unfollow(user, friend)
|
||||||
|
@ -226,7 +226,7 @@ def run(["unsubscribe", nickname]) do
|
||||||
|
|
||||||
:timer.sleep(500)
|
:timer.sleep(500)
|
||||||
|
|
||||||
user = User.get_by_id(user.id)
|
user = User.get_cached_by_id(user.id)
|
||||||
|
|
||||||
if Enum.empty?(user.following) do
|
if Enum.empty?(user.following) do
|
||||||
Mix.shell().info("Successfully unsubscribed all followers from #{user.nickname}")
|
Mix.shell().info("Successfully unsubscribed all followers from #{user.nickname}")
|
||||||
|
@ -250,7 +250,7 @@ def run(["set", nickname | rest]) do
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
with %User{local: true} = user <- User.get_by_nickname(nickname) do
|
with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
|
||||||
user =
|
user =
|
||||||
case Keyword.get(options, :moderator) do
|
case Keyword.get(options, :moderator) do
|
||||||
nil -> user
|
nil -> user
|
||||||
|
@ -277,7 +277,7 @@ def run(["set", nickname | rest]) do
|
||||||
def run(["tag", nickname | tags]) do
|
def run(["tag", nickname | tags]) do
|
||||||
Common.start_pleroma()
|
Common.start_pleroma()
|
||||||
|
|
||||||
with %User{} = user <- User.get_by_nickname(nickname) do
|
with %User{} = user <- User.get_cached_by_nickname(nickname) do
|
||||||
user = user |> User.tag(tags)
|
user = user |> User.tag(tags)
|
||||||
|
|
||||||
Mix.shell().info("Tags of #{user.nickname}: #{inspect(tags)}")
|
Mix.shell().info("Tags of #{user.nickname}: #{inspect(tags)}")
|
||||||
|
@ -290,7 +290,7 @@ def run(["tag", nickname | tags]) do
|
||||||
def run(["untag", nickname | tags]) do
|
def run(["untag", nickname | tags]) do
|
||||||
Common.start_pleroma()
|
Common.start_pleroma()
|
||||||
|
|
||||||
with %User{} = user <- User.get_by_nickname(nickname) do
|
with %User{} = user <- User.get_cached_by_nickname(nickname) do
|
||||||
user = user |> User.untag(tags)
|
user = user |> User.untag(tags)
|
||||||
|
|
||||||
Mix.shell().info("Tags of #{user.nickname}: #{inspect(tags)}")
|
Mix.shell().info("Tags of #{user.nickname}: #{inspect(tags)}")
|
||||||
|
@ -379,7 +379,7 @@ def run(["revoke_invite", token]) do
|
||||||
def run(["delete_activities", nickname]) do
|
def run(["delete_activities", nickname]) do
|
||||||
Common.start_pleroma()
|
Common.start_pleroma()
|
||||||
|
|
||||||
with %User{local: true} = user <- User.get_by_nickname(nickname) do
|
with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
|
||||||
User.delete_user_activities(user)
|
User.delete_user_activities(user)
|
||||||
Mix.shell().info("User #{nickname} statuses deleted.")
|
Mix.shell().info("User #{nickname} statuses deleted.")
|
||||||
else
|
else
|
||||||
|
|
|
@ -39,7 +39,7 @@ def used_changeset(struct) do
|
||||||
|
|
||||||
def reset_password(token, data) do
|
def reset_password(token, data) do
|
||||||
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
|
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
|
||||||
%User{} = user <- User.get_by_id(token.user_id),
|
%User{} = user <- User.get_cached_by_id(token.user_id),
|
||||||
{:ok, _user} <- User.reset_password(user, data),
|
{:ok, _user} <- User.reset_password(user, data),
|
||||||
{:ok, token} <- Repo.update(used_changeset(token)) do
|
{:ok, token} <- Repo.update(used_changeset(token)) do
|
||||||
{:ok, token}
|
{:ok, token}
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
defmodule Pleroma.Bookmark do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
import Ecto.Changeset
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Bookmark
|
||||||
|
alias Pleroma.FlakeId
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
|
@type t :: %__MODULE__{}
|
||||||
|
|
||||||
|
schema "bookmarks" do
|
||||||
|
belongs_to(:user, User, type: FlakeId)
|
||||||
|
belongs_to(:activity, Activity, type: FlakeId)
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec create(FlakeId.t(), FlakeId.t()) :: {:ok, Bookmark.t()} | {:error, Changeset.t()}
|
||||||
|
def create(user_id, activity_id) do
|
||||||
|
attrs = %{
|
||||||
|
user_id: user_id,
|
||||||
|
activity_id: activity_id
|
||||||
|
}
|
||||||
|
|
||||||
|
%Bookmark{}
|
||||||
|
|> cast(attrs, [:user_id, :activity_id])
|
||||||
|
|> validate_required([:user_id, :activity_id])
|
||||||
|
|> unique_constraint(:activity_id, name: :bookmarks_user_id_activity_id_index)
|
||||||
|
|> Repo.insert()
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec for_user_query(FlakeId.t()) :: Ecto.Query.t()
|
||||||
|
def for_user_query(user_id) do
|
||||||
|
Bookmark
|
||||||
|
|> where(user_id: ^user_id)
|
||||||
|
|> join(:inner, [b], activity in assoc(b, :activity))
|
||||||
|
|> preload([b, a], activity: a)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get(user_id, activity_id) do
|
||||||
|
Bookmark
|
||||||
|
|> where(user_id: ^user_id)
|
||||||
|
|> where(activity_id: ^activity_id)
|
||||||
|
|> Repo.one()
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec destroy(FlakeId.t(), FlakeId.t()) :: {:ok, Bookmark.t()} | {:error, Changeset.t()}
|
||||||
|
def destroy(user_id, activity_id) do
|
||||||
|
from(b in Bookmark,
|
||||||
|
where: b.user_id == ^user_id,
|
||||||
|
where: b.activity_id == ^activity_id
|
||||||
|
)
|
||||||
|
|> Repo.one()
|
||||||
|
|> Repo.delete()
|
||||||
|
end
|
||||||
|
end
|
|
@ -6,7 +6,7 @@ defmodule Pleroma.Emoji do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
The emojis are loaded from:
|
The emojis are loaded from:
|
||||||
|
|
||||||
* the built-in Finmojis (if enabled in configuration),
|
* emoji packs in INSTANCE-DIR/emoji
|
||||||
* the files: `config/emoji.txt` and `config/custom_emoji.txt`
|
* the files: `config/emoji.txt` and `config/custom_emoji.txt`
|
||||||
* glob paths, nested folder is used as tag name for grouping e.g. priv/static/emoji/custom/nested_folder
|
* glob paths, nested folder is used as tag name for grouping e.g. priv/static/emoji/custom/nested_folder
|
||||||
|
|
||||||
|
@ -14,6 +14,8 @@ defmodule Pleroma.Emoji do
|
||||||
"""
|
"""
|
||||||
use GenServer
|
use GenServer
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
@type pattern :: Regex.t() | module() | String.t()
|
@type pattern :: Regex.t() | module() | String.t()
|
||||||
@type patterns :: pattern() | [pattern()]
|
@type patterns :: pattern() | [pattern()]
|
||||||
@type group_patterns :: keyword(patterns())
|
@type group_patterns :: keyword(patterns())
|
||||||
|
@ -79,95 +81,94 @@ def code_change(_old_vsn, state, _extra) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp load do
|
defp load do
|
||||||
finmoji_enabled = Keyword.get(Application.get_env(:pleroma, :instance), :finmoji_enabled)
|
emoji_dir_path =
|
||||||
|
Path.join(
|
||||||
|
Pleroma.Config.get!([:instance, :static_dir]),
|
||||||
|
"emoji"
|
||||||
|
)
|
||||||
|
|
||||||
|
case File.ls(emoji_dir_path) do
|
||||||
|
{:error, :enoent} ->
|
||||||
|
# The custom emoji directory doesn't exist,
|
||||||
|
# don't do anything
|
||||||
|
nil
|
||||||
|
|
||||||
|
{:error, e} ->
|
||||||
|
# There was some other error
|
||||||
|
Logger.error("Could not access the custom emoji directory #{emoji_dir_path}: #{e}")
|
||||||
|
|
||||||
|
{:ok, packs} ->
|
||||||
|
# Print the packs we've found
|
||||||
|
Logger.info("Found emoji packs: #{Enum.join(packs, ", ")}")
|
||||||
|
|
||||||
|
emojis =
|
||||||
|
Enum.flat_map(
|
||||||
|
packs,
|
||||||
|
fn pack -> load_pack(Path.join(emoji_dir_path, pack)) end
|
||||||
|
)
|
||||||
|
|
||||||
|
true = :ets.insert(@ets, emojis)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Compat thing for old custom emoji handling & default emoji,
|
||||||
|
# it should run even if there are no emoji packs
|
||||||
shortcode_globs = Application.get_env(:pleroma, :emoji)[:shortcode_globs] || []
|
shortcode_globs = Application.get_env(:pleroma, :emoji)[:shortcode_globs] || []
|
||||||
|
|
||||||
emojis =
|
emojis =
|
||||||
(load_finmoji(finmoji_enabled) ++
|
(load_from_file("config/emoji.txt") ++
|
||||||
load_from_file("config/emoji.txt") ++
|
|
||||||
load_from_file("config/custom_emoji.txt") ++
|
load_from_file("config/custom_emoji.txt") ++
|
||||||
load_from_globs(shortcode_globs))
|
load_from_globs(shortcode_globs))
|
||||||
|> Enum.reject(fn value -> value == nil end)
|
|> Enum.reject(fn value -> value == nil end)
|
||||||
|
|
||||||
true = :ets.insert(@ets, emojis)
|
true = :ets.insert(@ets, emojis)
|
||||||
|
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
@finmoji [
|
defp load_pack(pack_dir) do
|
||||||
"a_trusted_friend",
|
pack_name = Path.basename(pack_dir)
|
||||||
"alandislands",
|
|
||||||
"association",
|
|
||||||
"auroraborealis",
|
|
||||||
"baby_in_a_box",
|
|
||||||
"bear",
|
|
||||||
"black_gold",
|
|
||||||
"christmasparty",
|
|
||||||
"crosscountryskiing",
|
|
||||||
"cupofcoffee",
|
|
||||||
"education",
|
|
||||||
"fashionista_finns",
|
|
||||||
"finnishlove",
|
|
||||||
"flag",
|
|
||||||
"forest",
|
|
||||||
"four_seasons_of_bbq",
|
|
||||||
"girlpower",
|
|
||||||
"handshake",
|
|
||||||
"happiness",
|
|
||||||
"headbanger",
|
|
||||||
"icebreaker",
|
|
||||||
"iceman",
|
|
||||||
"joulutorttu",
|
|
||||||
"kaamos",
|
|
||||||
"kalsarikannit_f",
|
|
||||||
"kalsarikannit_m",
|
|
||||||
"karjalanpiirakka",
|
|
||||||
"kicksled",
|
|
||||||
"kokko",
|
|
||||||
"lavatanssit",
|
|
||||||
"losthopes_f",
|
|
||||||
"losthopes_m",
|
|
||||||
"mattinykanen",
|
|
||||||
"meanwhileinfinland",
|
|
||||||
"moominmamma",
|
|
||||||
"nordicfamily",
|
|
||||||
"out_of_office",
|
|
||||||
"peacemaker",
|
|
||||||
"perkele",
|
|
||||||
"pesapallo",
|
|
||||||
"polarbear",
|
|
||||||
"pusa_hispida_saimensis",
|
|
||||||
"reindeer",
|
|
||||||
"sami",
|
|
||||||
"sauna_f",
|
|
||||||
"sauna_m",
|
|
||||||
"sauna_whisk",
|
|
||||||
"sisu",
|
|
||||||
"stuck",
|
|
||||||
"suomimainittu",
|
|
||||||
"superfood",
|
|
||||||
"swan",
|
|
||||||
"the_cap",
|
|
||||||
"the_conductor",
|
|
||||||
"the_king",
|
|
||||||
"the_voice",
|
|
||||||
"theoriginalsanta",
|
|
||||||
"tomoffinland",
|
|
||||||
"torillatavataan",
|
|
||||||
"unbreakable",
|
|
||||||
"waiting",
|
|
||||||
"white_nights",
|
|
||||||
"woollysocks"
|
|
||||||
]
|
|
||||||
|
|
||||||
defp load_finmoji(true) do
|
emoji_txt = Path.join(pack_dir, "emoji.txt")
|
||||||
Enum.map(@finmoji, fn finmoji ->
|
|
||||||
file_name = "/finmoji/128px/#{finmoji}-128.png"
|
if File.exists?(emoji_txt) do
|
||||||
group = match_extra(@groups, file_name)
|
load_from_file(emoji_txt)
|
||||||
{finmoji, file_name, to_string(group)}
|
else
|
||||||
end)
|
Logger.info(
|
||||||
|
"No emoji.txt found for pack \"#{pack_name}\", assuming all .png files are emoji"
|
||||||
|
)
|
||||||
|
|
||||||
|
make_shortcode_to_file_map(pack_dir, [".png"])
|
||||||
|
|> Enum.map(fn {shortcode, rel_file} ->
|
||||||
|
filename = Path.join("/emoji/#{pack_name}", rel_file)
|
||||||
|
|
||||||
|
{shortcode, filename, [to_string(match_extra(@groups, filename))]}
|
||||||
|
end)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp load_finmoji(_), do: []
|
def make_shortcode_to_file_map(pack_dir, exts) do
|
||||||
|
find_all_emoji(pack_dir, exts)
|
||||||
|
|> Enum.map(&Path.relative_to(&1, pack_dir))
|
||||||
|
|> Enum.map(fn f -> {f |> Path.basename() |> Path.rootname(), f} end)
|
||||||
|
|> Enum.into(%{})
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_all_emoji(dir, exts) do
|
||||||
|
Enum.reduce(
|
||||||
|
File.ls!(dir),
|
||||||
|
[],
|
||||||
|
fn f, acc ->
|
||||||
|
filepath = Path.join(dir, f)
|
||||||
|
|
||||||
|
if File.dir?(filepath) do
|
||||||
|
acc ++ find_all_emoji(filepath, exts)
|
||||||
|
else
|
||||||
|
acc ++ [filepath]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|> Enum.filter(fn f -> Path.extname(f) in exts end)
|
||||||
|
end
|
||||||
|
|
||||||
defp load_from_file(file) do
|
defp load_from_file(file) do
|
||||||
if File.exists?(file) do
|
if File.exists?(file) do
|
||||||
|
@ -182,11 +183,11 @@ defp load_from_file_stream(stream) do
|
||||||
|> Stream.map(&String.trim/1)
|
|> Stream.map(&String.trim/1)
|
||||||
|> Stream.map(fn line ->
|
|> Stream.map(fn line ->
|
||||||
case String.split(line, ~r/,\s*/) do
|
case String.split(line, ~r/,\s*/) do
|
||||||
[name, file, tags] ->
|
|
||||||
{name, file, tags}
|
|
||||||
|
|
||||||
[name, file] ->
|
[name, file] ->
|
||||||
{name, file, to_string(match_extra(@groups, file))}
|
{name, file, [to_string(match_extra(@groups, file))]}
|
||||||
|
|
||||||
|
[name, file | tags] ->
|
||||||
|
{name, file, tags}
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
nil
|
nil
|
||||||
|
@ -209,7 +210,7 @@ defp load_from_globs(globs) do
|
||||||
tag = match_extra(@groups, Path.join("/", Path.relative_to(path, static_path)))
|
tag = match_extra(@groups, Path.join("/", Path.relative_to(path, static_path)))
|
||||||
shortcode = Path.basename(path, Path.extname(path))
|
shortcode = Path.basename(path, Path.extname(path))
|
||||||
external_path = Path.join("/", Path.relative_to(path, static_path))
|
external_path = Path.join("/", Path.relative_to(path, static_path))
|
||||||
{shortcode, external_path, to_string(tag)}
|
{shortcode, external_path, [to_string(tag)]}
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -76,7 +76,7 @@ def render_activities(activities) do
|
||||||
|> Enum.map(fn activity ->
|
|> Enum.map(fn activity ->
|
||||||
user = User.get_cached_by_ap_id(activity.data["actor"])
|
user = User.get_cached_by_ap_id(activity.data["actor"])
|
||||||
|
|
||||||
object = Object.normalize(activity.data["object"])
|
object = Object.normalize(activity)
|
||||||
like_count = object["like_count"] || 0
|
like_count = object["like_count"] || 0
|
||||||
announcement_count = object["announcement_count"] || 0
|
announcement_count = object["announcement_count"] || 0
|
||||||
|
|
||||||
|
|
|
@ -106,7 +106,14 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do
|
||||||
|
|
||||||
# links
|
# links
|
||||||
Meta.allow_tag_with_uri_attributes("a", ["href", "data-user", "data-tag"], @valid_schemes)
|
Meta.allow_tag_with_uri_attributes("a", ["href", "data-user", "data-tag"], @valid_schemes)
|
||||||
Meta.allow_tag_with_these_attributes("a", ["name", "title", "class"])
|
|
||||||
|
Meta.allow_tag_with_this_attribute_values("a", "class", [
|
||||||
|
"hashtag",
|
||||||
|
"u-url",
|
||||||
|
"mention",
|
||||||
|
"u-url mention",
|
||||||
|
"mention u-url"
|
||||||
|
])
|
||||||
|
|
||||||
Meta.allow_tag_with_this_attribute_values("a", "rel", [
|
Meta.allow_tag_with_this_attribute_values("a", "rel", [
|
||||||
"tag",
|
"tag",
|
||||||
|
@ -115,12 +122,15 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do
|
||||||
"noreferrer"
|
"noreferrer"
|
||||||
])
|
])
|
||||||
|
|
||||||
|
Meta.allow_tag_with_these_attributes("a", ["name", "title"])
|
||||||
|
|
||||||
# paragraphs and linebreaks
|
# paragraphs and linebreaks
|
||||||
Meta.allow_tag_with_these_attributes("br", [])
|
Meta.allow_tag_with_these_attributes("br", [])
|
||||||
Meta.allow_tag_with_these_attributes("p", [])
|
Meta.allow_tag_with_these_attributes("p", [])
|
||||||
|
|
||||||
# microformats
|
# microformats
|
||||||
Meta.allow_tag_with_these_attributes("span", ["class"])
|
Meta.allow_tag_with_this_attribute_values("span", "class", ["h-card"])
|
||||||
|
Meta.allow_tag_with_these_attributes("span", [])
|
||||||
|
|
||||||
# allow inline images for custom emoji
|
# allow inline images for custom emoji
|
||||||
@allow_inline_images Keyword.get(@markup, :allow_inline_images)
|
@allow_inline_images Keyword.get(@markup, :allow_inline_images)
|
||||||
|
@ -155,7 +165,14 @@ defmodule Pleroma.HTML.Scrubber.Default do
|
||||||
Meta.strip_comments()
|
Meta.strip_comments()
|
||||||
|
|
||||||
Meta.allow_tag_with_uri_attributes("a", ["href", "data-user", "data-tag"], @valid_schemes)
|
Meta.allow_tag_with_uri_attributes("a", ["href", "data-user", "data-tag"], @valid_schemes)
|
||||||
Meta.allow_tag_with_these_attributes("a", ["name", "title", "class"])
|
|
||||||
|
Meta.allow_tag_with_this_attribute_values("a", "class", [
|
||||||
|
"hashtag",
|
||||||
|
"u-url",
|
||||||
|
"mention",
|
||||||
|
"u-url mention",
|
||||||
|
"mention u-url"
|
||||||
|
])
|
||||||
|
|
||||||
Meta.allow_tag_with_this_attribute_values("a", "rel", [
|
Meta.allow_tag_with_this_attribute_values("a", "rel", [
|
||||||
"tag",
|
"tag",
|
||||||
|
@ -164,6 +181,8 @@ defmodule Pleroma.HTML.Scrubber.Default do
|
||||||
"noreferrer"
|
"noreferrer"
|
||||||
])
|
])
|
||||||
|
|
||||||
|
Meta.allow_tag_with_these_attributes("a", ["name", "title"])
|
||||||
|
|
||||||
Meta.allow_tag_with_these_attributes("abbr", ["title"])
|
Meta.allow_tag_with_these_attributes("abbr", ["title"])
|
||||||
|
|
||||||
Meta.allow_tag_with_these_attributes("b", [])
|
Meta.allow_tag_with_these_attributes("b", [])
|
||||||
|
@ -177,11 +196,13 @@ defmodule Pleroma.HTML.Scrubber.Default do
|
||||||
Meta.allow_tag_with_these_attributes("ol", [])
|
Meta.allow_tag_with_these_attributes("ol", [])
|
||||||
Meta.allow_tag_with_these_attributes("p", [])
|
Meta.allow_tag_with_these_attributes("p", [])
|
||||||
Meta.allow_tag_with_these_attributes("pre", [])
|
Meta.allow_tag_with_these_attributes("pre", [])
|
||||||
Meta.allow_tag_with_these_attributes("span", ["class"])
|
|
||||||
Meta.allow_tag_with_these_attributes("strong", [])
|
Meta.allow_tag_with_these_attributes("strong", [])
|
||||||
Meta.allow_tag_with_these_attributes("u", [])
|
Meta.allow_tag_with_these_attributes("u", [])
|
||||||
Meta.allow_tag_with_these_attributes("ul", [])
|
Meta.allow_tag_with_these_attributes("ul", [])
|
||||||
|
|
||||||
|
Meta.allow_tag_with_this_attribute_values("span", "class", ["h-card"])
|
||||||
|
Meta.allow_tag_with_these_attributes("span", [])
|
||||||
|
|
||||||
@allow_inline_images Keyword.get(@markup, :allow_inline_images)
|
@allow_inline_images Keyword.get(@markup, :allow_inline_images)
|
||||||
|
|
||||||
if @allow_inline_images do
|
if @allow_inline_images do
|
||||||
|
|
|
@ -80,7 +80,7 @@ def get_lists_from_activity(%Activity{actor: ap_id}) do
|
||||||
|
|
||||||
# Get lists to which the account belongs.
|
# Get lists to which the account belongs.
|
||||||
def get_lists_account_belongs(%User{} = owner, account_id) do
|
def get_lists_account_belongs(%User{} = owner, account_id) do
|
||||||
user = User.get_by_id(account_id)
|
user = User.get_cached_by_id(account_id)
|
||||||
|
|
||||||
query =
|
query =
|
||||||
from(
|
from(
|
||||||
|
|
|
@ -220,7 +220,7 @@ def skip?(
|
||||||
|
|
||||||
def skip?(:follows, activity, %{info: %{notification_settings: %{"follows" => false}}} = user) do
|
def skip?(:follows, activity, %{info: %{notification_settings: %{"follows" => false}}} = user) do
|
||||||
actor = activity.data["actor"]
|
actor = activity.data["actor"]
|
||||||
followed = User.get_by_ap_id(actor)
|
followed = User.get_cached_by_ap_id(actor)
|
||||||
User.following?(user, followed)
|
User.following?(user, followed)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ def fetch_object_from_id(id) do
|
||||||
Logger.info("Couldn't get object via AP, trying out OStatus fetching...")
|
Logger.info("Couldn't get object via AP, trying out OStatus fetching...")
|
||||||
|
|
||||||
case OStatus.fetch_activity_from_url(id) do
|
case OStatus.fetch_activity_from_url(id) do
|
||||||
{:ok, [activity | _]} -> {:ok, Object.normalize(activity.data["object"], false)}
|
{:ok, [activity | _]} -> {:ok, Object.normalize(activity, false)}
|
||||||
e -> e
|
e -> e
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,6 +10,7 @@ defmodule Pleroma.User do
|
||||||
|
|
||||||
alias Comeonin.Pbkdf2
|
alias Comeonin.Pbkdf2
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Bookmark
|
||||||
alias Pleroma.Formatter
|
alias Pleroma.Formatter
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
|
@ -53,9 +54,9 @@ defmodule Pleroma.User do
|
||||||
field(:search_rank, :float, virtual: true)
|
field(:search_rank, :float, virtual: true)
|
||||||
field(:search_type, :integer, virtual: true)
|
field(:search_type, :integer, virtual: true)
|
||||||
field(:tags, {:array, :string}, default: [])
|
field(:tags, {:array, :string}, default: [])
|
||||||
field(:bookmarks, {:array, :string}, default: [])
|
|
||||||
field(:last_refreshed_at, :naive_datetime_usec)
|
field(:last_refreshed_at, :naive_datetime_usec)
|
||||||
field(:last_digest_emailed_at, :naive_datetime)
|
field(:last_digest_emailed_at, :naive_datetime)
|
||||||
|
has_many(:bookmarks, Bookmark)
|
||||||
has_many(:notifications, Notification)
|
has_many(:notifications, Notification)
|
||||||
has_many(:registrations, Registration)
|
has_many(:registrations, Registration)
|
||||||
embeds_one(:info, Pleroma.User.Info)
|
embeds_one(:info, Pleroma.User.Info)
|
||||||
|
@ -270,6 +271,7 @@ defp autofollow_users(user) do
|
||||||
def register(%Ecto.Changeset{} = changeset) do
|
def register(%Ecto.Changeset{} = changeset) do
|
||||||
with {:ok, user} <- Repo.insert(changeset),
|
with {:ok, user} <- Repo.insert(changeset),
|
||||||
{:ok, user} <- autofollow_users(user),
|
{:ok, user} <- autofollow_users(user),
|
||||||
|
{:ok, user} <- set_cache(user),
|
||||||
{:ok, _} <- Pleroma.User.WelcomeMessage.post_welcome_message_to_user(user),
|
{:ok, _} <- Pleroma.User.WelcomeMessage.post_welcome_message_to_user(user),
|
||||||
{:ok, _} <- try_send_confirmation_email(user) do
|
{:ok, _} <- try_send_confirmation_email(user) do
|
||||||
{:ok, user}
|
{:ok, user}
|
||||||
|
@ -454,10 +456,13 @@ def get_by_guessed_nickname(ap_id) do
|
||||||
name = List.last(String.split(ap_id, "/"))
|
name = List.last(String.split(ap_id, "/"))
|
||||||
nickname = "#{name}@#{domain}"
|
nickname = "#{name}@#{domain}"
|
||||||
|
|
||||||
get_by_nickname(nickname)
|
get_cached_by_nickname(nickname)
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_cache(user) do
|
def set_cache({:ok, user}), do: set_cache(user)
|
||||||
|
def set_cache({:error, err}), do: {:error, err}
|
||||||
|
|
||||||
|
def set_cache(%User{} = user) do
|
||||||
Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
|
Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
|
||||||
Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
|
Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
|
||||||
Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user))
|
Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user))
|
||||||
|
@ -545,6 +550,7 @@ def get_or_fetch_by_nickname(nickname) do
|
||||||
with [_nick, _domain] <- String.split(nickname, "@"),
|
with [_nick, _domain] <- String.split(nickname, "@"),
|
||||||
{:ok, user} <- fetch_by_nickname(nickname) do
|
{:ok, user} <- fetch_by_nickname(nickname) do
|
||||||
if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
|
if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
|
||||||
|
# TODO turn into job
|
||||||
{:ok, _} = Task.start(__MODULE__, :fetch_initial_posts, [user])
|
{:ok, _} = Task.start(__MODULE__, :fetch_initial_posts, [user])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1003,7 +1009,7 @@ def block(blocker, %User{ap_id: ap_id} = blocked) do
|
||||||
|
|
||||||
# helper to handle the block given only an actor's AP id
|
# helper to handle the block given only an actor's AP id
|
||||||
def block(blocker, %{ap_id: ap_id}) do
|
def block(blocker, %{ap_id: ap_id}) do
|
||||||
block(blocker, User.get_by_ap_id(ap_id))
|
block(blocker, get_cached_by_ap_id(ap_id))
|
||||||
end
|
end
|
||||||
|
|
||||||
def unblock(blocker, %{ap_id: ap_id}) do
|
def unblock(blocker, %{ap_id: ap_id}) do
|
||||||
|
@ -1033,7 +1039,7 @@ def blocks?(user, %{ap_id: ap_id}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def subscribed_to?(user, %{ap_id: ap_id}) do
|
def subscribed_to?(user, %{ap_id: ap_id}) do
|
||||||
with %User{} = target <- User.get_by_ap_id(ap_id) do
|
with %User{} = target <- get_cached_by_ap_id(ap_id) do
|
||||||
Enum.member?(target.info.subscribers, user.ap_id)
|
Enum.member?(target.info.subscribers, user.ap_id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1208,7 +1214,7 @@ def fetch_by_ap_id(ap_id) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_or_fetch_by_ap_id(ap_id) do
|
def get_or_fetch_by_ap_id(ap_id) do
|
||||||
user = get_by_ap_id(ap_id)
|
user = get_cached_by_ap_id(ap_id)
|
||||||
|
|
||||||
if !is_nil(user) and !User.needs_update?(user) do
|
if !is_nil(user) and !User.needs_update?(user) do
|
||||||
user
|
user
|
||||||
|
@ -1231,7 +1237,7 @@ def get_or_fetch_by_ap_id(ap_id) do
|
||||||
def get_or_create_instance_user do
|
def get_or_create_instance_user do
|
||||||
relay_uri = "#{Pleroma.Web.Endpoint.url()}/relay"
|
relay_uri = "#{Pleroma.Web.Endpoint.url()}/relay"
|
||||||
|
|
||||||
if user = get_by_ap_id(relay_uri) do
|
if user = get_cached_by_ap_id(relay_uri) do
|
||||||
user
|
user
|
||||||
else
|
else
|
||||||
changes =
|
changes =
|
||||||
|
@ -1278,13 +1284,11 @@ defp blank?(""), do: nil
|
||||||
defp blank?(n), do: n
|
defp blank?(n), do: n
|
||||||
|
|
||||||
def insert_or_update_user(data) do
|
def insert_or_update_user(data) do
|
||||||
data =
|
data
|
||||||
data
|
|> Map.put(:name, blank?(data[:name]) || data[:nickname])
|
||||||
|> Map.put(:name, blank?(data[:name]) || data[:nickname])
|
|> remote_user_creation()
|
||||||
|
|> Repo.insert(on_conflict: :replace_all, conflict_target: :nickname)
|
||||||
cs = User.remote_user_creation(data)
|
|> set_cache()
|
||||||
|
|
||||||
Repo.insert(cs, on_conflict: :replace_all, conflict_target: :nickname)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def ap_enabled?(%User{local: true}), do: true
|
def ap_enabled?(%User{local: true}), do: true
|
||||||
|
@ -1300,8 +1304,8 @@ def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
|
||||||
# this is because we have synchronous follow APIs and need to simulate them
|
# this is because we have synchronous follow APIs and need to simulate them
|
||||||
# with an async handshake
|
# with an async handshake
|
||||||
def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
|
def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
|
||||||
with %User{} = a <- User.get_by_id(a.id),
|
with %User{} = a <- User.get_cached_by_id(a.id),
|
||||||
%User{} = b <- User.get_by_id(b.id) do
|
%User{} = b <- User.get_cached_by_id(b.id) do
|
||||||
{:ok, a, b}
|
{:ok, a, b}
|
||||||
else
|
else
|
||||||
_e ->
|
_e ->
|
||||||
|
@ -1311,8 +1315,8 @@ def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
|
||||||
|
|
||||||
def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
|
def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
|
||||||
with :ok <- :timer.sleep(timeout),
|
with :ok <- :timer.sleep(timeout),
|
||||||
%User{} = a <- User.get_by_id(a.id),
|
%User{} = a <- User.get_cached_by_id(a.id),
|
||||||
%User{} = b <- User.get_by_id(b.id) do
|
%User{} = b <- User.get_cached_by_id(b.id) do
|
||||||
{:ok, a, b}
|
{:ok, a, b}
|
||||||
else
|
else
|
||||||
_e ->
|
_e ->
|
||||||
|
@ -1351,7 +1355,7 @@ def tag(user_identifiers, tags) when is_list(user_identifiers) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def tag(nickname, tags) when is_binary(nickname),
|
def tag(nickname, tags) when is_binary(nickname),
|
||||||
do: tag(User.get_by_nickname(nickname), tags)
|
do: tag(get_by_nickname(nickname), tags)
|
||||||
|
|
||||||
def tag(%User{} = user, tags),
|
def tag(%User{} = user, tags),
|
||||||
do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
|
do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
|
||||||
|
@ -1363,7 +1367,7 @@ def untag(user_identifiers, tags) when is_list(user_identifiers) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def untag(nickname, tags) when is_binary(nickname),
|
def untag(nickname, tags) when is_binary(nickname),
|
||||||
do: untag(User.get_by_nickname(nickname), tags)
|
do: untag(get_by_nickname(nickname), tags)
|
||||||
|
|
||||||
def untag(%User{} = user, tags),
|
def untag(%User{} = user, tags),
|
||||||
do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
|
do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
|
||||||
|
@ -1377,22 +1381,6 @@ defp update_tags(%User{} = user, new_tags) do
|
||||||
updated_user
|
updated_user
|
||||||
end
|
end
|
||||||
|
|
||||||
def bookmark(%User{} = user, status_id) do
|
|
||||||
bookmarks = Enum.uniq(user.bookmarks ++ [status_id])
|
|
||||||
update_bookmarks(user, bookmarks)
|
|
||||||
end
|
|
||||||
|
|
||||||
def unbookmark(%User{} = user, status_id) do
|
|
||||||
bookmarks = Enum.uniq(user.bookmarks -- [status_id])
|
|
||||||
update_bookmarks(user, bookmarks)
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_bookmarks(%User{} = user, bookmarks) do
|
|
||||||
user
|
|
||||||
|> change(%{bookmarks: bookmarks})
|
|
||||||
|> update_and_set_cache
|
|
||||||
end
|
|
||||||
|
|
||||||
defp normalize_tags(tags) do
|
defp normalize_tags(tags) do
|
||||||
[tags]
|
[tags]
|
||||||
|> List.flatten()
|
|> List.flatten()
|
||||||
|
|
|
@ -40,6 +40,7 @@ defmodule Pleroma.User.Info do
|
||||||
field(:salmon, :string, default: nil)
|
field(:salmon, :string, default: nil)
|
||||||
field(:hide_followers, :boolean, default: false)
|
field(:hide_followers, :boolean, default: false)
|
||||||
field(:hide_follows, :boolean, default: false)
|
field(:hide_follows, :boolean, default: false)
|
||||||
|
field(:hide_favorites, :boolean, default: true)
|
||||||
field(:pinned_activities, {:array, :string}, default: [])
|
field(:pinned_activities, {:array, :string}, default: [])
|
||||||
field(:flavour, :string, default: nil)
|
field(:flavour, :string, default: nil)
|
||||||
field(:email_notifications, :map, default: %{"digest" => false})
|
field(:email_notifications, :map, default: %{"digest" => false})
|
||||||
|
@ -229,6 +230,7 @@ def profile_update(info, params) do
|
||||||
:banner,
|
:banner,
|
||||||
:hide_follows,
|
:hide_follows,
|
||||||
:hide_followers,
|
:hide_followers,
|
||||||
|
:hide_favorites,
|
||||||
:background,
|
:background,
|
||||||
:show_role
|
:show_role
|
||||||
])
|
])
|
||||||
|
@ -252,14 +254,6 @@ def confirmation_changeset(info, params) do
|
||||||
cast(info, params, [:confirmation_pending, :confirmation_token])
|
cast(info, params, [:confirmation_pending, :confirmation_token])
|
||||||
end
|
end
|
||||||
|
|
||||||
def mastodon_profile_update(info, params) do
|
|
||||||
info
|
|
||||||
|> cast(params, [
|
|
||||||
:locked,
|
|
||||||
:banner
|
|
||||||
])
|
|
||||||
end
|
|
||||||
|
|
||||||
def mastodon_settings_update(info, settings) do
|
def mastodon_settings_update(info, settings) do
|
||||||
params = %{settings: settings}
|
params = %{settings: settings}
|
||||||
|
|
||||||
|
|
|
@ -168,7 +168,7 @@ def stream_out(activity) do
|
||||||
public = "https://www.w3.org/ns/activitystreams#Public"
|
public = "https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
|
||||||
if activity.data["type"] in ["Create", "Announce", "Delete"] do
|
if activity.data["type"] in ["Create", "Announce", "Delete"] do
|
||||||
object = Object.normalize(activity.data["object"])
|
object = Object.normalize(activity)
|
||||||
Pleroma.Web.Streamer.stream("user", activity)
|
Pleroma.Web.Streamer.stream("user", activity)
|
||||||
Pleroma.Web.Streamer.stream("list", activity)
|
Pleroma.Web.Streamer.stream("list", activity)
|
||||||
|
|
||||||
|
@ -197,7 +197,7 @@ def stream_out(activity) do
|
||||||
if !Enum.member?(activity.data["cc"] || [], public) &&
|
if !Enum.member?(activity.data["cc"] || [], public) &&
|
||||||
!Enum.member?(
|
!Enum.member?(
|
||||||
activity.data["to"],
|
activity.data["to"],
|
||||||
User.get_by_ap_id(activity.data["actor"]).follower_address
|
User.get_cached_by_ap_id(activity.data["actor"]).follower_address
|
||||||
),
|
),
|
||||||
do: Pleroma.Web.Streamer.stream("direct", activity)
|
do: Pleroma.Web.Streamer.stream("direct", activity)
|
||||||
end
|
end
|
||||||
|
@ -889,7 +889,7 @@ def fetch_and_prepare_user_from_ap_id(ap_id) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def make_user_from_ap_id(ap_id) do
|
def make_user_from_ap_id(ap_id) do
|
||||||
if _user = User.get_by_ap_id(ap_id) do
|
if _user = User.get_cached_by_ap_id(ap_id) do
|
||||||
Transmogrifier.upgrade_user_from_ap_id(ap_id)
|
Transmogrifier.upgrade_user_from_ap_id(ap_id)
|
||||||
else
|
else
|
||||||
with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id) do
|
with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id) do
|
||||||
|
|
|
@ -438,20 +438,46 @@ def handle_incoming(
|
||||||
with %User{local: true} = followed <- User.get_cached_by_ap_id(followed),
|
with %User{local: true} = followed <- User.get_cached_by_ap_id(followed),
|
||||||
%User{} = follower <- User.get_or_fetch_by_ap_id(follower),
|
%User{} = follower <- User.get_or_fetch_by_ap_id(follower),
|
||||||
{:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do
|
{:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do
|
||||||
if not User.locked?(followed) do
|
with deny_follow_blocked <- Pleroma.Config.get([:user, :deny_follow_blocked]),
|
||||||
|
{:user_blocked, false} <-
|
||||||
|
{:user_blocked, User.blocks?(followed, follower) && deny_follow_blocked},
|
||||||
|
{:user_locked, false} <- {:user_locked, User.locked?(followed)},
|
||||||
|
{:follow, {:ok, follower}} <- {:follow, User.follow(follower, followed)} do
|
||||||
ActivityPub.accept(%{
|
ActivityPub.accept(%{
|
||||||
to: [follower.ap_id],
|
to: [follower.ap_id],
|
||||||
actor: followed,
|
actor: followed,
|
||||||
object: data,
|
object: data,
|
||||||
local: true
|
local: true
|
||||||
})
|
})
|
||||||
|
else
|
||||||
|
{:user_blocked, true} ->
|
||||||
|
{:ok, _} = Utils.update_follow_state(activity, "reject")
|
||||||
|
|
||||||
User.follow(follower, followed)
|
ActivityPub.reject(%{
|
||||||
|
to: [follower.ap_id],
|
||||||
|
actor: followed,
|
||||||
|
object: data,
|
||||||
|
local: true
|
||||||
|
})
|
||||||
|
|
||||||
|
{:follow, {:error, _}} ->
|
||||||
|
{:ok, _} = Utils.update_follow_state(activity, "reject")
|
||||||
|
|
||||||
|
ActivityPub.reject(%{
|
||||||
|
to: [follower.ap_id],
|
||||||
|
actor: followed,
|
||||||
|
object: data,
|
||||||
|
local: true
|
||||||
|
})
|
||||||
|
|
||||||
|
{:user_locked, true} ->
|
||||||
|
:noop
|
||||||
end
|
end
|
||||||
|
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
else
|
else
|
||||||
_e -> :error
|
_e ->
|
||||||
|
:error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -537,7 +563,7 @@ def handle_incoming(
|
||||||
data
|
data
|
||||||
)
|
)
|
||||||
when object_type in ["Person", "Application", "Service", "Organization"] do
|
when object_type in ["Person", "Application", "Service", "Organization"] do
|
||||||
with %User{ap_id: ^actor_id} = actor <- User.get_by_ap_id(object["id"]) do
|
with %User{ap_id: ^actor_id} = actor <- User.get_cached_by_ap_id(object["id"]) do
|
||||||
{:ok, new_user_data} = ActivityPub.user_data_from_user_object(object)
|
{:ok, new_user_data} = ActivityPub.user_data_from_user_object(object)
|
||||||
|
|
||||||
banner = new_user_data[:info]["banner"]
|
banner = new_user_data[:info]["banner"]
|
||||||
|
@ -964,7 +990,7 @@ def perform(:user_upgrade, user) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def upgrade_user_from_ap_id(ap_id) do
|
def upgrade_user_from_ap_id(ap_id) do
|
||||||
with %User{local: false} = user <- User.get_by_ap_id(ap_id),
|
with %User{local: false} = user <- User.get_cached_by_ap_id(ap_id),
|
||||||
{:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id),
|
{:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id),
|
||||||
already_ap <- User.ap_enabled?(user),
|
already_ap <- User.ap_enabled?(user),
|
||||||
{:ok, user} <- user |> User.upgrade_changeset(data) |> User.update_and_set_cache() do
|
{:ok, user} <- user |> User.upgrade_changeset(data) |> User.update_and_set_cache() do
|
||||||
|
|
|
@ -19,7 +19,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||||
action_fallback(:errors)
|
action_fallback(:errors)
|
||||||
|
|
||||||
def user_delete(conn, %{"nickname" => nickname}) do
|
def user_delete(conn, %{"nickname" => nickname}) do
|
||||||
User.get_by_nickname(nickname)
|
User.get_cached_by_nickname(nickname)
|
||||||
|> User.delete()
|
|> User.delete()
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|
@ -27,8 +27,8 @@ def user_delete(conn, %{"nickname" => nickname}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def user_follow(conn, %{"follower" => follower_nick, "followed" => followed_nick}) do
|
def user_follow(conn, %{"follower" => follower_nick, "followed" => followed_nick}) do
|
||||||
with %User{} = follower <- User.get_by_nickname(follower_nick),
|
with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
|
||||||
%User{} = followed <- User.get_by_nickname(followed_nick) do
|
%User{} = followed <- User.get_cached_by_nickname(followed_nick) do
|
||||||
User.follow(follower, followed)
|
User.follow(follower, followed)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -37,8 +37,8 @@ def user_follow(conn, %{"follower" => follower_nick, "followed" => followed_nick
|
||||||
end
|
end
|
||||||
|
|
||||||
def user_unfollow(conn, %{"follower" => follower_nick, "followed" => followed_nick}) do
|
def user_unfollow(conn, %{"follower" => follower_nick, "followed" => followed_nick}) do
|
||||||
with %User{} = follower <- User.get_by_nickname(follower_nick),
|
with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
|
||||||
%User{} = followed <- User.get_by_nickname(followed_nick) do
|
%User{} = followed <- User.get_cached_by_nickname(followed_nick) do
|
||||||
User.unfollow(follower, followed)
|
User.unfollow(follower, followed)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ def user_create(
|
||||||
end
|
end
|
||||||
|
|
||||||
def user_show(conn, %{"nickname" => nickname}) do
|
def user_show(conn, %{"nickname" => nickname}) do
|
||||||
with %User{} = user <- User.get_by_nickname(nickname) do
|
with %User{} = user <- User.get_cached_by_nickname(nickname) do
|
||||||
conn
|
conn
|
||||||
|> json(AccountView.render("show.json", %{user: user}))
|
|> json(AccountView.render("show.json", %{user: user}))
|
||||||
else
|
else
|
||||||
|
@ -76,7 +76,7 @@ def user_show(conn, %{"nickname" => nickname}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def user_toggle_activation(conn, %{"nickname" => nickname}) do
|
def user_toggle_activation(conn, %{"nickname" => nickname}) do
|
||||||
user = User.get_by_nickname(nickname)
|
user = User.get_cached_by_nickname(nickname)
|
||||||
|
|
||||||
{:ok, updated_user} = User.deactivate(user, !user.info.deactivated)
|
{:ok, updated_user} = User.deactivate(user, !user.info.deactivated)
|
||||||
|
|
||||||
|
@ -131,7 +131,7 @@ defp maybe_parse_filters(filters) do
|
||||||
|
|
||||||
def right_add(conn, %{"permission_group" => permission_group, "nickname" => nickname})
|
def right_add(conn, %{"permission_group" => permission_group, "nickname" => nickname})
|
||||||
when permission_group in ["moderator", "admin"] do
|
when permission_group in ["moderator", "admin"] do
|
||||||
user = User.get_by_nickname(nickname)
|
user = User.get_cached_by_nickname(nickname)
|
||||||
|
|
||||||
info =
|
info =
|
||||||
%{}
|
%{}
|
||||||
|
@ -156,7 +156,7 @@ def right_add(conn, _) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def right_get(conn, %{"nickname" => nickname}) do
|
def right_get(conn, %{"nickname" => nickname}) do
|
||||||
user = User.get_by_nickname(nickname)
|
user = User.get_cached_by_nickname(nickname)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> json(%{
|
|> json(%{
|
||||||
|
@ -178,7 +178,7 @@ def right_delete(
|
||||||
|> put_status(403)
|
|> put_status(403)
|
||||||
|> json(%{error: "You can't revoke your own admin status."})
|
|> json(%{error: "You can't revoke your own admin status."})
|
||||||
else
|
else
|
||||||
user = User.get_by_nickname(nickname)
|
user = User.get_cached_by_nickname(nickname)
|
||||||
|
|
||||||
info =
|
info =
|
||||||
%{}
|
%{}
|
||||||
|
@ -204,7 +204,7 @@ def right_delete(conn, _) do
|
||||||
|
|
||||||
def set_activation_status(conn, %{"nickname" => nickname, "status" => status}) do
|
def set_activation_status(conn, %{"nickname" => nickname, "status" => status}) do
|
||||||
with {:ok, status} <- Ecto.Type.cast(:boolean, status),
|
with {:ok, status} <- Ecto.Type.cast(:boolean, status),
|
||||||
%User{} = user <- User.get_by_nickname(nickname),
|
%User{} = user <- User.get_cached_by_nickname(nickname),
|
||||||
{:ok, _} <- User.deactivate(user, !status),
|
{:ok, _} <- User.deactivate(user, !status),
|
||||||
do: json_response(conn, :no_content, "")
|
do: json_response(conn, :no_content, "")
|
||||||
end
|
end
|
||||||
|
@ -277,7 +277,7 @@ def revoke_invite(conn, %{"token" => token}) do
|
||||||
|
|
||||||
@doc "Get a password reset token (base64 string) for given nickname"
|
@doc "Get a password reset token (base64 string) for given nickname"
|
||||||
def get_password_reset(conn, %{"nickname" => nickname}) do
|
def get_password_reset(conn, %{"nickname" => nickname}) do
|
||||||
(%User{local: true} = user) = User.get_by_nickname(nickname)
|
(%User{local: true} = user) = User.get_cached_by_nickname(nickname)
|
||||||
{:ok, token} = Pleroma.PasswordResetToken.create_token(user)
|
{:ok, token} = Pleroma.PasswordResetToken.create_token(user)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|
|
|
@ -24,7 +24,7 @@ defmodule Pleroma.Web.UserSocket do
|
||||||
def connect(%{"token" => token}, socket) do
|
def connect(%{"token" => token}, socket) do
|
||||||
with true <- Pleroma.Config.get([:chat, :enabled]),
|
with true <- Pleroma.Config.get([:chat, :enabled]),
|
||||||
{:ok, user_id} <- Phoenix.Token.verify(socket, "user socket", token, max_age: 84_600),
|
{:ok, user_id} <- Phoenix.Token.verify(socket, "user socket", token, max_age: 84_600),
|
||||||
%User{} = user <- Pleroma.User.get_by_id(user_id) do
|
%User{} = user <- Pleroma.User.get_cached_by_id(user_id) do
|
||||||
{:ok, assign(socket, :user_name, user.nickname)}
|
{:ok, assign(socket, :user_name, user.nickname)}
|
||||||
else
|
else
|
||||||
_e -> :error
|
_e -> :error
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
defmodule Pleroma.Web.CommonAPI do
|
defmodule Pleroma.Web.CommonAPI do
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Bookmark
|
||||||
alias Pleroma.Formatter
|
alias Pleroma.Formatter
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.ThreadMute
|
alias Pleroma.ThreadMute
|
||||||
|
@ -282,9 +283,18 @@ def thread_muted?(user, activity) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def bookmarked?(user, activity) do
|
||||||
|
with %Bookmark{} <- Bookmark.get(user.id, activity.id) do
|
||||||
|
true
|
||||||
|
else
|
||||||
|
_ ->
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def report(user, data) do
|
def report(user, data) do
|
||||||
with {:account_id, %{"account_id" => account_id}} <- {:account_id, data},
|
with {:account_id, %{"account_id" => account_id}} <- {:account_id, data},
|
||||||
{:account, %User{} = account} <- {:account, User.get_by_id(account_id)},
|
{:account, %User{} = account} <- {:account, User.get_cached_by_id(account_id)},
|
||||||
{:ok, {content_html, _, _}} <- make_report_content_html(data["comment"]),
|
{:ok, {content_html, _, _}} <- make_report_content_html(data["comment"]),
|
||||||
{:ok, statuses} <- get_report_statuses(account, data),
|
{:ok, statuses} <- get_report_statuses(account, data),
|
||||||
{:ok, activity} <-
|
{:ok, activity} <-
|
||||||
|
|
|
@ -182,6 +182,18 @@ def format_input(text, "text/plain", options) do
|
||||||
end).()
|
end).()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Formatting text as BBCode.
|
||||||
|
"""
|
||||||
|
def format_input(text, "text/bbcode", options) do
|
||||||
|
text
|
||||||
|
|> String.replace(~r/\r/, "")
|
||||||
|
|> Formatter.html_escape("text/plain")
|
||||||
|
|> BBCode.to_html()
|
||||||
|
|> (fn {:ok, html} -> html end).()
|
||||||
|
|> Formatter.linkify(options)
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Formatting text to html.
|
Formatting text to html.
|
||||||
"""
|
"""
|
||||||
|
@ -226,7 +238,7 @@ def make_note_data(
|
||||||
}
|
}
|
||||||
|
|
||||||
if in_reply_to do
|
if in_reply_to do
|
||||||
in_reply_to_object = Object.normalize(in_reply_to.data["object"])
|
in_reply_to_object = Object.normalize(in_reply_to)
|
||||||
|
|
||||||
object
|
object
|
||||||
|> Map.put("inReplyTo", in_reply_to_object.data["id"])
|
|> Map.put("inReplyTo", in_reply_to_object.data["id"])
|
||||||
|
@ -284,7 +296,7 @@ defp shortname(name) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def confirm_current_password(user, password) do
|
def confirm_current_password(user, password) do
|
||||||
with %User{local: true} = db_user <- User.get_by_id(user.id),
|
with %User{local: true} = db_user <- User.get_cached_by_id(user.id),
|
||||||
true <- Pbkdf2.checkpw(password, db_user.password_hash) do
|
true <- Pbkdf2.checkpw(password, db_user.password_hash) do
|
||||||
{:ok, db_user}
|
{:ok, db_user}
|
||||||
else
|
else
|
||||||
|
|
|
@ -186,7 +186,7 @@ def perform(type, _) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def ap_enabled_actor(id) do
|
def ap_enabled_actor(id) do
|
||||||
user = User.get_by_ap_id(id)
|
user = User.get_cached_by_ap_id(id)
|
||||||
|
|
||||||
if User.ap_enabled?(user) do
|
if User.ap_enabled?(user) do
|
||||||
{:ok, user}
|
{:ok, user}
|
||||||
|
|
|
@ -7,6 +7,31 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
|
||||||
alias Pleroma.Pagination
|
alias Pleroma.Pagination
|
||||||
alias Pleroma.ScheduledActivity
|
alias Pleroma.ScheduledActivity
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
|
def follow(follower, followed, params \\ %{}) do
|
||||||
|
options = cast_params(params)
|
||||||
|
reblogs = options[:reblogs]
|
||||||
|
|
||||||
|
result =
|
||||||
|
if not User.following?(follower, followed) do
|
||||||
|
CommonAPI.follow(follower, followed)
|
||||||
|
else
|
||||||
|
{:ok, follower, followed, nil}
|
||||||
|
end
|
||||||
|
|
||||||
|
with {:ok, follower, followed, _} <- result do
|
||||||
|
reblogs
|
||||||
|
|> case do
|
||||||
|
false -> CommonAPI.hide_reblogs(follower, followed)
|
||||||
|
_ -> CommonAPI.show_reblogs(follower, followed)
|
||||||
|
end
|
||||||
|
|> case do
|
||||||
|
{:ok, follower} -> {:ok, follower}
|
||||||
|
_ -> {:ok, follower}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def get_followers(user, params \\ %{}) do
|
def get_followers(user, params \\ %{}) do
|
||||||
user
|
user
|
||||||
|
@ -37,7 +62,8 @@ def get_scheduled_activities(user, params \\ %{}) do
|
||||||
|
|
||||||
defp cast_params(params) do
|
defp cast_params(params) do
|
||||||
param_types = %{
|
param_types = %{
|
||||||
exclude_types: {:array, :string}
|
exclude_types: {:array, :string},
|
||||||
|
reblogs: :boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
changeset = cast({%{}, param_types}, params, Map.keys(param_types))
|
changeset = cast({%{}, param_types}, params, Map.keys(param_types))
|
||||||
|
|
|
@ -6,6 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
alias Ecto.Changeset
|
alias Ecto.Changeset
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Bookmark
|
||||||
alias Pleroma.Config
|
alias Pleroma.Config
|
||||||
alias Pleroma.Filter
|
alias Pleroma.Filter
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
|
@ -35,7 +36,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||||
alias Pleroma.Web.OAuth.Authorization
|
alias Pleroma.Web.OAuth.Authorization
|
||||||
alias Pleroma.Web.OAuth.Token
|
alias Pleroma.Web.OAuth.Token
|
||||||
|
|
||||||
import Pleroma.Web.ControllerHelper, only: [oauth_scopes: 2]
|
alias Pleroma.Web.ControllerHelper
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
@ -46,7 +47,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||||
action_fallback(:errors)
|
action_fallback(:errors)
|
||||||
|
|
||||||
def create_app(conn, params) do
|
def create_app(conn, params) do
|
||||||
scopes = oauth_scopes(params, ["read"])
|
scopes = ControllerHelper.oauth_scopes(params, ["read"])
|
||||||
|
|
||||||
app_attrs =
|
app_attrs =
|
||||||
params
|
params
|
||||||
|
@ -96,8 +97,13 @@ def update_credentials(%{assigns: %{user: user}} = conn, params) do
|
||||||
end)
|
end)
|
||||||
|
|
||||||
info_params =
|
info_params =
|
||||||
%{}
|
[:no_rich_text, :locked, :hide_followers, :hide_follows, :hide_favorites, :show_role]
|
||||||
|> add_if_present(params, "locked", :locked, fn value -> {:ok, value == "true"} end)
|
|> Enum.reduce(%{}, fn key, acc ->
|
||||||
|
add_if_present(acc, params, to_string(key), key, fn value ->
|
||||||
|
{:ok, ControllerHelper.truthy_param?(value)}
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|> add_if_present(params, "default_scope", :default_scope)
|
||||||
|> add_if_present(params, "header", :banner, fn value ->
|
|> add_if_present(params, "header", :banner, fn value ->
|
||||||
with %Plug.Upload{} <- value,
|
with %Plug.Upload{} <- value,
|
||||||
{:ok, object} <- ActivityPub.upload(value, type: :banner) do
|
{:ok, object} <- ActivityPub.upload(value, type: :banner) do
|
||||||
|
@ -107,7 +113,7 @@ def update_credentials(%{assigns: %{user: user}} = conn, params) do
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
info_cng = User.Info.mastodon_profile_update(user.info, info_params)
|
info_cng = User.Info.profile_update(user.info, info_params)
|
||||||
|
|
||||||
with changeset <- User.update_changeset(user, user_params),
|
with changeset <- User.update_changeset(user, user_params),
|
||||||
changeset <- Ecto.Changeset.put_embed(changeset, :info, info_cng),
|
changeset <- Ecto.Changeset.put_embed(changeset, :info, info_cng),
|
||||||
|
@ -190,7 +196,7 @@ defp mastodonized_emoji do
|
||||||
"static_url" => url,
|
"static_url" => url,
|
||||||
"visible_in_picker" => true,
|
"visible_in_picker" => true,
|
||||||
"url" => url,
|
"url" => url,
|
||||||
"tags" => String.split(tags, ",")
|
"tags" => tags
|
||||||
}
|
}
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
@ -279,6 +285,8 @@ def home_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||||
|> ActivityPub.contain_timeline(user)
|
|> ActivityPub.contain_timeline(user)
|
||||||
|> Enum.reverse()
|
|> Enum.reverse()
|
||||||
|
|
||||||
|
user = Repo.preload(user, bookmarks: :activity)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> add_link_headers(:home_timeline, activities)
|
|> add_link_headers(:home_timeline, activities)
|
||||||
|> put_view(StatusView)
|
|> put_view(StatusView)
|
||||||
|
@ -297,6 +305,8 @@ def public_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||||
|> ActivityPub.fetch_public_activities()
|
|> ActivityPub.fetch_public_activities()
|
||||||
|> Enum.reverse()
|
|> Enum.reverse()
|
||||||
|
|
||||||
|
user = Repo.preload(user, bookmarks: :activity)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> add_link_headers(:public_timeline, activities, false, %{"local" => local_only})
|
|> add_link_headers(:public_timeline, activities, false, %{"local" => local_only})
|
||||||
|> put_view(StatusView)
|
|> put_view(StatusView)
|
||||||
|
@ -304,7 +314,8 @@ def public_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do
|
def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do
|
||||||
with %User{} = user <- User.get_by_id(params["id"]) do
|
with %User{} = user <- User.get_cached_by_id(params["id"]),
|
||||||
|
reading_user <- Repo.preload(reading_user, :bookmarks) do
|
||||||
activities = ActivityPub.fetch_user_activities(user, reading_user, params)
|
activities = ActivityPub.fetch_user_activities(user, reading_user, params)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|
@ -331,6 +342,8 @@ def dm_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||||
|> ActivityPub.fetch_activities_query(params)
|
|> ActivityPub.fetch_activities_query(params)
|
||||||
|> Pagination.fetch_paginated(params)
|
|> Pagination.fetch_paginated(params)
|
||||||
|
|
||||||
|
user = Repo.preload(user, bookmarks: :activity)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> add_link_headers(:dm_timeline, activities)
|
|> add_link_headers(:dm_timeline, activities)
|
||||||
|> put_view(StatusView)
|
|> put_view(StatusView)
|
||||||
|
@ -338,8 +351,10 @@ def dm_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
with %Activity{} = activity <- Activity.get_by_id(id),
|
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
||||||
true <- Visibility.visible_for_user?(activity, user) do
|
true <- Visibility.visible_for_user?(activity, user) do
|
||||||
|
user = Repo.preload(user, bookmarks: :activity)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_view(StatusView)
|
|> put_view(StatusView)
|
||||||
|> try_render("status.json", %{activity: activity, for: user})
|
|> try_render("status.json", %{activity: activity, for: user})
|
||||||
|
@ -487,7 +502,10 @@ def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def reblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
def reblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||||
with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user) do
|
with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user),
|
||||||
|
%Activity{} = announce <- Activity.normalize(announce.data) do
|
||||||
|
user = Repo.preload(user, bookmarks: :activity)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_view(StatusView)
|
|> put_view(StatusView)
|
||||||
|> try_render("status.json", %{activity: announce, for: user, as: :activity})
|
|> try_render("status.json", %{activity: announce, for: user, as: :activity})
|
||||||
|
@ -496,7 +514,9 @@ def reblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||||
|
|
||||||
def unreblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
def unreblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||||
with {:ok, _unannounce, %{data: %{"id" => id}}} <- CommonAPI.unrepeat(ap_id_or_id, user),
|
with {:ok, _unannounce, %{data: %{"id" => id}}} <- CommonAPI.unrepeat(ap_id_or_id, user),
|
||||||
%Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do
|
%Activity{} = activity <- Activity.get_create_by_object_ap_id_with_object(id) do
|
||||||
|
user = Repo.preload(user, bookmarks: :activity)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_view(StatusView)
|
|> put_view(StatusView)
|
||||||
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
|
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
|
||||||
|
@ -544,10 +564,11 @@ def unpin_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||||
|
|
||||||
def bookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
def bookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
||||||
%Object{} = object <- Object.normalize(activity),
|
%User{} = user <- User.get_cached_by_nickname(user.nickname),
|
||||||
%User{} = user <- User.get_by_nickname(user.nickname),
|
|
||||||
true <- Visibility.visible_for_user?(activity, user),
|
true <- Visibility.visible_for_user?(activity, user),
|
||||||
{:ok, user} <- User.bookmark(user, object.data["id"]) do
|
{:ok, _bookmark} <- Bookmark.create(user.id, activity.id) do
|
||||||
|
user = Repo.preload(user, bookmarks: :activity)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_view(StatusView)
|
|> put_view(StatusView)
|
||||||
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
|
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
|
||||||
|
@ -556,10 +577,11 @@ def bookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
|
|
||||||
def unbookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
def unbookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
||||||
%Object{} = object <- Object.normalize(activity),
|
%User{} = user <- User.get_cached_by_nickname(user.nickname),
|
||||||
%User{} = user <- User.get_by_nickname(user.nickname),
|
|
||||||
true <- Visibility.visible_for_user?(activity, user),
|
true <- Visibility.visible_for_user?(activity, user),
|
||||||
{:ok, user} <- User.unbookmark(user, object.data["id"]) do
|
{:ok, _bookmark} <- Bookmark.destroy(user.id, activity.id) do
|
||||||
|
user = Repo.preload(user, bookmarks: :activity)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_view(StatusView)
|
|> put_view(StatusView)
|
||||||
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
|
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
|
||||||
|
@ -749,7 +771,7 @@ def hashtag_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def followers(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do
|
def followers(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do
|
||||||
with %User{} = user <- User.get_by_id(id),
|
with %User{} = user <- User.get_cached_by_id(id),
|
||||||
followers <- MastodonAPI.get_followers(user, params) do
|
followers <- MastodonAPI.get_followers(user, params) do
|
||||||
followers =
|
followers =
|
||||||
cond do
|
cond do
|
||||||
|
@ -766,7 +788,7 @@ def followers(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def following(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do
|
def following(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do
|
||||||
with %User{} = user <- User.get_by_id(id),
|
with %User{} = user <- User.get_cached_by_id(id),
|
||||||
followers <- MastodonAPI.get_friends(user, params) do
|
followers <- MastodonAPI.get_friends(user, params) do
|
||||||
followers =
|
followers =
|
||||||
cond do
|
cond do
|
||||||
|
@ -791,7 +813,7 @@ def follow_requests(%{assigns: %{user: followed}} = conn, _params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def authorize_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do
|
def authorize_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do
|
||||||
with %User{} = follower <- User.get_by_id(id),
|
with %User{} = follower <- User.get_cached_by_id(id),
|
||||||
{:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do
|
{:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do
|
||||||
conn
|
conn
|
||||||
|> put_view(AccountView)
|
|> put_view(AccountView)
|
||||||
|
@ -805,7 +827,7 @@ def authorize_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}
|
||||||
end
|
end
|
||||||
|
|
||||||
def reject_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do
|
def reject_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do
|
||||||
with %User{} = follower <- User.get_by_id(id),
|
with %User{} = follower <- User.get_cached_by_id(id),
|
||||||
{:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do
|
{:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do
|
||||||
conn
|
conn
|
||||||
|> put_view(AccountView)
|
|> put_view(AccountView)
|
||||||
|
@ -821,8 +843,7 @@ def reject_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) d
|
||||||
def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
|
def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
|
||||||
with {_, %User{} = followed} <- {:followed, User.get_cached_by_id(id)},
|
with {_, %User{} = followed} <- {:followed, User.get_cached_by_id(id)},
|
||||||
{_, true} <- {:followed, follower.id != followed.id},
|
{_, true} <- {:followed, follower.id != followed.id},
|
||||||
false <- User.following?(follower, followed),
|
{:ok, follower} <- MastodonAPI.follow(follower, followed, conn.params) do
|
||||||
{:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do
|
|
||||||
conn
|
conn
|
||||||
|> put_view(AccountView)
|
|> put_view(AccountView)
|
||||||
|> render("relationship.json", %{user: follower, target: followed})
|
|> render("relationship.json", %{user: follower, target: followed})
|
||||||
|
@ -830,19 +851,6 @@ def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
|
||||||
{:followed, _} ->
|
{:followed, _} ->
|
||||||
{:error, :not_found}
|
{:error, :not_found}
|
||||||
|
|
||||||
true ->
|
|
||||||
followed = User.get_cached_by_id(id)
|
|
||||||
|
|
||||||
{:ok, follower} =
|
|
||||||
case conn.params["reblogs"] do
|
|
||||||
true -> CommonAPI.show_reblogs(follower, followed)
|
|
||||||
false -> CommonAPI.hide_reblogs(follower, followed)
|
|
||||||
end
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> put_view(AccountView)
|
|
||||||
|> render("relationship.json", %{user: follower, target: followed})
|
|
||||||
|
|
||||||
{:error, message} ->
|
{:error, message} ->
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("application/json")
|
|> put_resp_content_type("application/json")
|
||||||
|
@ -885,7 +893,7 @@ def unfollow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def mute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
|
def mute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
|
||||||
with %User{} = muted <- User.get_by_id(id),
|
with %User{} = muted <- User.get_cached_by_id(id),
|
||||||
{:ok, muter} <- User.mute(muter, muted) do
|
{:ok, muter} <- User.mute(muter, muted) do
|
||||||
conn
|
conn
|
||||||
|> put_view(AccountView)
|
|> put_view(AccountView)
|
||||||
|
@ -899,7 +907,7 @@ def mute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def unmute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
|
def unmute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
|
||||||
with %User{} = muted <- User.get_by_id(id),
|
with %User{} = muted <- User.get_cached_by_id(id),
|
||||||
{:ok, muter} <- User.unmute(muter, muted) do
|
{:ok, muter} <- User.unmute(muter, muted) do
|
||||||
conn
|
conn
|
||||||
|> put_view(AccountView)
|
|> put_view(AccountView)
|
||||||
|
@ -920,7 +928,7 @@ def mutes(%{assigns: %{user: user}} = conn, _) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
|
def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
|
||||||
with %User{} = blocked <- User.get_by_id(id),
|
with %User{} = blocked <- User.get_cached_by_id(id),
|
||||||
{:ok, blocker} <- User.block(blocker, blocked),
|
{:ok, blocker} <- User.block(blocker, blocked),
|
||||||
{:ok, _activity} <- ActivityPub.block(blocker, blocked) do
|
{:ok, _activity} <- ActivityPub.block(blocker, blocked) do
|
||||||
conn
|
conn
|
||||||
|
@ -935,7 +943,7 @@ def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def unblock(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
|
def unblock(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
|
||||||
with %User{} = blocked <- User.get_by_id(id),
|
with %User{} = blocked <- User.get_cached_by_id(id),
|
||||||
{:ok, blocker} <- User.unblock(blocker, blocked),
|
{:ok, blocker} <- User.unblock(blocker, blocked),
|
||||||
{:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do
|
{:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do
|
||||||
conn
|
conn
|
||||||
|
@ -1094,21 +1102,65 @@ def favourites(%{assigns: %{user: user}} = conn, params) do
|
||||||
ActivityPub.fetch_activities([], params)
|
ActivityPub.fetch_activities([], params)
|
||||||
|> Enum.reverse()
|
|> Enum.reverse()
|
||||||
|
|
||||||
|
user = Repo.preload(user, bookmarks: :activity)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> add_link_headers(:favourites, activities)
|
|> add_link_headers(:favourites, activities)
|
||||||
|> put_view(StatusView)
|
|> put_view(StatusView)
|
||||||
|> render("index.json", %{activities: activities, for: user, as: :activity})
|
|> render("index.json", %{activities: activities, for: user, as: :activity})
|
||||||
end
|
end
|
||||||
|
|
||||||
def bookmarks(%{assigns: %{user: user}} = conn, _) do
|
def user_favourites(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do
|
||||||
user = User.get_by_id(user.id)
|
with %User{} = user <- User.get_by_id(id),
|
||||||
|
false <- user.info.hide_favorites do
|
||||||
|
params =
|
||||||
|
params
|
||||||
|
|> Map.put("type", "Create")
|
||||||
|
|> Map.put("favorited_by", user.ap_id)
|
||||||
|
|> Map.put("blocking_user", for_user)
|
||||||
|
|
||||||
|
recipients =
|
||||||
|
if for_user do
|
||||||
|
["https://www.w3.org/ns/activitystreams#Public"] ++
|
||||||
|
[for_user.ap_id | for_user.following]
|
||||||
|
else
|
||||||
|
["https://www.w3.org/ns/activitystreams#Public"]
|
||||||
|
end
|
||||||
|
|
||||||
|
activities =
|
||||||
|
recipients
|
||||||
|
|> ActivityPub.fetch_activities(params)
|
||||||
|
|> Enum.reverse()
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> add_link_headers(:favourites, activities)
|
||||||
|
|> put_view(StatusView)
|
||||||
|
|> render("index.json", %{activities: activities, for: for_user, as: :activity})
|
||||||
|
else
|
||||||
|
nil ->
|
||||||
|
{:error, :not_found}
|
||||||
|
|
||||||
|
true ->
|
||||||
|
conn
|
||||||
|
|> put_status(403)
|
||||||
|
|> json(%{error: "Can't get favorites"})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def bookmarks(%{assigns: %{user: user}} = conn, params) do
|
||||||
|
user = User.get_cached_by_id(user.id)
|
||||||
|
user = Repo.preload(user, bookmarks: :activity)
|
||||||
|
|
||||||
|
bookmarks =
|
||||||
|
Bookmark.for_user_query(user.id)
|
||||||
|
|> Pagination.fetch_paginated(params)
|
||||||
|
|
||||||
activities =
|
activities =
|
||||||
user.bookmarks
|
bookmarks
|
||||||
|> Enum.map(fn id -> Activity.get_create_by_object_ap_id(id) end)
|
|> Enum.map(fn b -> b.activity end)
|
||||||
|> Enum.reverse()
|
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|
|> add_link_headers(:bookmarks, bookmarks)
|
||||||
|> put_view(StatusView)
|
|> put_view(StatusView)
|
||||||
|> render("index.json", %{activities: activities, for: user, as: :activity})
|
|> render("index.json", %{activities: activities, for: user, as: :activity})
|
||||||
end
|
end
|
||||||
|
@ -1158,7 +1210,7 @@ def add_to_list(%{assigns: %{user: user}} = conn, %{"id" => id, "account_ids" =>
|
||||||
accounts
|
accounts
|
||||||
|> Enum.each(fn account_id ->
|
|> Enum.each(fn account_id ->
|
||||||
with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
|
with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
|
||||||
%User{} = followed <- User.get_by_id(account_id) do
|
%User{} = followed <- User.get_cached_by_id(account_id) do
|
||||||
Pleroma.List.follow(list, followed)
|
Pleroma.List.follow(list, followed)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
@ -1170,7 +1222,7 @@ def remove_from_list(%{assigns: %{user: user}} = conn, %{"id" => id, "account_id
|
||||||
accounts
|
accounts
|
||||||
|> Enum.each(fn account_id ->
|
|> Enum.each(fn account_id ->
|
||||||
with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
|
with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
|
||||||
%User{} = followed <- Pleroma.User.get_by_id(account_id) do
|
%User{} = followed <- Pleroma.User.get_cached_by_id(account_id) do
|
||||||
Pleroma.List.unfollow(list, followed)
|
Pleroma.List.unfollow(list, followed)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
@ -1214,6 +1266,8 @@ def list_timeline(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params)
|
||||||
|> ActivityPub.fetch_activities_bounded(following, params)
|
|> ActivityPub.fetch_activities_bounded(following, params)
|
||||||
|> Enum.reverse()
|
|> Enum.reverse()
|
||||||
|
|
||||||
|
user = Repo.preload(user, bookmarks: :activity)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_view(StatusView)
|
|> put_view(StatusView)
|
||||||
|> render("index.json", %{activities: activities, for: user, as: :activity})
|
|> render("index.json", %{activities: activities, for: user, as: :activity})
|
||||||
|
@ -1463,7 +1517,7 @@ def logout(conn, _) do
|
||||||
def relationship_noop(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
def relationship_noop(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
Logger.debug("Unimplemented, returning unmodified relationship")
|
Logger.debug("Unimplemented, returning unmodified relationship")
|
||||||
|
|
||||||
with %User{} = target <- User.get_by_id(id) do
|
with %User{} = target <- User.get_cached_by_id(id) do
|
||||||
conn
|
conn
|
||||||
|> put_view(AccountView)
|
|> put_view(AccountView)
|
||||||
|> render("relationship.json", %{user: user, target: target})
|
|> render("relationship.json", %{user: user, target: target})
|
||||||
|
|
|
@ -68,7 +68,7 @@ def render("relationships.json", %{user: user, targets: targets}) do
|
||||||
defp do_render("account.json", %{user: user} = opts) do
|
defp do_render("account.json", %{user: user} = opts) do
|
||||||
image = User.avatar_url(user) |> MediaProxy.url()
|
image = User.avatar_url(user) |> MediaProxy.url()
|
||||||
header = User.banner_url(user) |> MediaProxy.url()
|
header = User.banner_url(user) |> MediaProxy.url()
|
||||||
user_info = User.user_info(user)
|
user_info = User.get_cached_user_info(user)
|
||||||
bot = (user.info.source_data["type"] || "Person") in ["Application", "Service"]
|
bot = (user.info.source_data["type"] || "Person") in ["Application", "Service"]
|
||||||
|
|
||||||
emojis =
|
emojis =
|
||||||
|
@ -113,21 +113,23 @@ defp do_render("account.json", %{user: user} = opts) do
|
||||||
bot: bot,
|
bot: bot,
|
||||||
source: %{
|
source: %{
|
||||||
note: "",
|
note: "",
|
||||||
privacy: user_info.default_scope,
|
sensitive: false,
|
||||||
sensitive: false
|
pleroma: %{}
|
||||||
},
|
},
|
||||||
|
|
||||||
# Pleroma extension
|
# Pleroma extension
|
||||||
pleroma:
|
pleroma: %{
|
||||||
%{
|
confirmation_pending: user_info.confirmation_pending,
|
||||||
confirmation_pending: user_info.confirmation_pending,
|
tags: user.tags,
|
||||||
tags: user.tags,
|
hide_followers: user.info.hide_followers,
|
||||||
is_moderator: user.info.is_moderator,
|
hide_follows: user.info.hide_follows,
|
||||||
is_admin: user.info.is_admin,
|
hide_favorites: user.info.hide_favorites,
|
||||||
relationship: relationship
|
relationship: relationship
|
||||||
}
|
}
|
||||||
|> with_notification_settings(user, opts[:for])
|
|
||||||
}
|
}
|
||||||
|
|> maybe_put_role(user, opts[:for])
|
||||||
|
|> maybe_put_settings(user, opts[:for], user_info)
|
||||||
|
|> maybe_put_notification_settings(user, opts[:for])
|
||||||
end
|
end
|
||||||
|
|
||||||
defp username_from_nickname(string) when is_binary(string) do
|
defp username_from_nickname(string) when is_binary(string) do
|
||||||
|
@ -136,9 +138,37 @@ defp username_from_nickname(string) when is_binary(string) do
|
||||||
|
|
||||||
defp username_from_nickname(_), do: nil
|
defp username_from_nickname(_), do: nil
|
||||||
|
|
||||||
defp with_notification_settings(data, %User{id: user_id} = user, %User{id: user_id}) do
|
defp maybe_put_settings(
|
||||||
Map.put(data, :notification_settings, user.info.notification_settings)
|
data,
|
||||||
|
%User{id: user_id} = user,
|
||||||
|
%User{id: user_id},
|
||||||
|
user_info
|
||||||
|
) do
|
||||||
|
data
|
||||||
|
|> Kernel.put_in([:source, :privacy], user_info.default_scope)
|
||||||
|
|> Kernel.put_in([:source, :pleroma, :show_role], user.info.show_role)
|
||||||
|
|> Kernel.put_in([:source, :pleroma, :no_rich_text], user.info.no_rich_text)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp with_notification_settings(data, _, _), do: data
|
defp maybe_put_settings(data, _, _, _), do: data
|
||||||
|
|
||||||
|
defp maybe_put_role(data, %User{info: %{show_role: true}} = user, _) do
|
||||||
|
data
|
||||||
|
|> Kernel.put_in([:pleroma, :is_admin], user.info.is_admin)
|
||||||
|
|> Kernel.put_in([:pleroma, :is_moderator], user.info.is_moderator)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_put_role(data, %User{id: user_id} = user, %User{id: user_id}) do
|
||||||
|
data
|
||||||
|
|> Kernel.put_in([:pleroma, :is_admin], user.info.is_admin)
|
||||||
|
|> Kernel.put_in([:pleroma, :is_moderator], user.info.is_moderator)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_put_role(data, _, _), do: data
|
||||||
|
|
||||||
|
defp maybe_put_notification_settings(data, %User{id: user_id} = user, %User{id: user_id}) do
|
||||||
|
Kernel.put_in(data, [:pleroma, :notification_settings], user.info.notification_settings)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_put_notification_settings(data, _, _), do: data
|
||||||
end
|
end
|
||||||
|
|
|
@ -31,7 +31,7 @@ defp get_replied_to_activities(activities) do
|
||||||
|> Activity.create_by_object_ap_id()
|
|> Activity.create_by_object_ap_id()
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
|> Enum.reduce(%{}, fn activity, acc ->
|
|> Enum.reduce(%{}, fn activity, acc ->
|
||||||
object = Object.normalize(activity.data["object"])
|
object = Object.normalize(activity)
|
||||||
Map.put(acc, object.data["id"], activity)
|
Map.put(acc, object.data["id"], activity)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
@ -83,6 +83,11 @@ def render(
|
||||||
reblogged_activity = Activity.get_create_by_object_ap_id(object)
|
reblogged_activity = Activity.get_create_by_object_ap_id(object)
|
||||||
reblogged = render("status.json", Map.put(opts, :activity, reblogged_activity))
|
reblogged = render("status.json", Map.put(opts, :activity, reblogged_activity))
|
||||||
|
|
||||||
|
activity_object = Object.normalize(activity)
|
||||||
|
favorited = opts[:for] && opts[:for].ap_id in (activity_object.data["likes"] || [])
|
||||||
|
|
||||||
|
bookmarked = opts[:for] && CommonAPI.bookmarked?(opts[:for], reblogged_activity)
|
||||||
|
|
||||||
mentions =
|
mentions =
|
||||||
activity.recipients
|
activity.recipients
|
||||||
|> Enum.map(fn ap_id -> User.get_cached_by_ap_id(ap_id) end)
|
|> Enum.map(fn ap_id -> User.get_cached_by_ap_id(ap_id) end)
|
||||||
|
@ -103,8 +108,8 @@ def render(
|
||||||
replies_count: 0,
|
replies_count: 0,
|
||||||
favourites_count: 0,
|
favourites_count: 0,
|
||||||
reblogged: reblogged?(reblogged_activity, opts[:for]),
|
reblogged: reblogged?(reblogged_activity, opts[:for]),
|
||||||
favourited: false,
|
favourited: present?(favorited),
|
||||||
bookmarked: false,
|
bookmarked: present?(bookmarked),
|
||||||
muted: false,
|
muted: false,
|
||||||
pinned: pinned?(activity, user),
|
pinned: pinned?(activity, user),
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
|
@ -144,7 +149,7 @@ def render("status.json", %{activity: %{data: %{"object" => _object}} = activity
|
||||||
|
|
||||||
favorited = opts[:for] && opts[:for].ap_id in (object.data["likes"] || [])
|
favorited = opts[:for] && opts[:for].ap_id in (object.data["likes"] || [])
|
||||||
|
|
||||||
bookmarked = opts[:for] && object.data["id"] in opts[:for].bookmarks
|
bookmarked = opts[:for] && CommonAPI.bookmarked?(opts[:for], activity)
|
||||||
|
|
||||||
attachment_data = object.data["attachment"] || []
|
attachment_data = object.data["attachment"] || []
|
||||||
attachments = render_many(attachment_data, StatusView, "attachment.json", as: :attachment)
|
attachments = render_many(attachment_data, StatusView, "attachment.json", as: :attachment)
|
||||||
|
@ -234,6 +239,7 @@ def render("status.json", %{activity: %{data: %{"object" => _object}} = activity
|
||||||
pleroma: %{
|
pleroma: %{
|
||||||
local: activity.local,
|
local: activity.local,
|
||||||
conversation_id: get_context_id(activity),
|
conversation_id: get_context_id(activity),
|
||||||
|
in_reply_to_account_acct: reply_to_user && reply_to_user.nickname,
|
||||||
content: %{"text/plain" => content_plaintext},
|
content: %{"text/plain" => content_plaintext},
|
||||||
spoiler_text: %{"text/plain" => summary_plaintext}
|
spoiler_text: %{"text/plain" => summary_plaintext}
|
||||||
}
|
}
|
||||||
|
@ -312,7 +318,7 @@ def render("attachment.json", %{attachment: attachment}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_reply_to(activity, %{replied_to_activities: replied_to_activities}) do
|
def get_reply_to(activity, %{replied_to_activities: replied_to_activities}) do
|
||||||
object = Object.normalize(activity.data["object"])
|
object = Object.normalize(activity)
|
||||||
|
|
||||||
with nil <- replied_to_activities[object.data["inReplyTo"]] do
|
with nil <- replied_to_activities[object.data["inReplyTo"]] do
|
||||||
# If user didn't participate in the thread
|
# If user didn't participate in the thread
|
||||||
|
|
|
@ -90,7 +90,7 @@ defp allow_request(stream, nil) when stream in @anonymous_streams do
|
||||||
# Authenticated streams.
|
# Authenticated streams.
|
||||||
defp allow_request(stream, {"access_token", access_token}) when stream in @streams do
|
defp allow_request(stream, {"access_token", access_token}) when stream in @streams do
|
||||||
with %Token{user_id: user_id} <- Repo.get_by(Token, token: access_token),
|
with %Token{user_id: user_id} <- Repo.get_by(Token, token: access_token),
|
||||||
user = %User{} <- User.get_by_id(user_id) do
|
user = %User{} <- User.get_cached_by_id(user_id) do
|
||||||
{:ok, user}
|
{:ok, user}
|
||||||
else
|
else
|
||||||
_ -> {:error, 403}
|
_ -> {:error, 403}
|
||||||
|
|
|
@ -13,32 +13,44 @@ def url("/" <> _ = url), do: url
|
||||||
|
|
||||||
def url(url) do
|
def url(url) do
|
||||||
config = Application.get_env(:pleroma, :media_proxy, [])
|
config = Application.get_env(:pleroma, :media_proxy, [])
|
||||||
|
domain = URI.parse(url).host
|
||||||
|
|
||||||
if !Keyword.get(config, :enabled, false) or String.starts_with?(url, Pleroma.Web.base_url()) do
|
cond do
|
||||||
url
|
!Keyword.get(config, :enabled, false) or String.starts_with?(url, Pleroma.Web.base_url()) ->
|
||||||
else
|
|
||||||
secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base]
|
|
||||||
|
|
||||||
# Must preserve `%2F` for compatibility with S3
|
|
||||||
# https://git.pleroma.social/pleroma/pleroma/issues/580
|
|
||||||
replacement = get_replacement(url, ":2F:")
|
|
||||||
|
|
||||||
# The URL is url-decoded and encoded again to ensure it is correctly encoded and not twice.
|
|
||||||
base64 =
|
|
||||||
url
|
url
|
||||||
|> String.replace("%2F", replacement)
|
|
||||||
|> URI.decode()
|
|
||||||
|> URI.encode()
|
|
||||||
|> String.replace(replacement, "%2F")
|
|
||||||
|> Base.url_encode64(@base64_opts)
|
|
||||||
|
|
||||||
sig = :crypto.hmac(:sha, secret, base64)
|
Enum.any?(Pleroma.Config.get([:media_proxy, :whitelist]), fn pattern ->
|
||||||
sig64 = sig |> Base.url_encode64(@base64_opts)
|
String.equivalent?(domain, pattern)
|
||||||
|
end) ->
|
||||||
|
url
|
||||||
|
|
||||||
build_url(sig64, base64, filename(url))
|
true ->
|
||||||
|
encode_url(url)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def encode_url(url) do
|
||||||
|
secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base]
|
||||||
|
|
||||||
|
# Must preserve `%2F` for compatibility with S3
|
||||||
|
# https://git.pleroma.social/pleroma/pleroma/issues/580
|
||||||
|
replacement = get_replacement(url, ":2F:")
|
||||||
|
|
||||||
|
# The URL is url-decoded and encoded again to ensure it is correctly encoded and not twice.
|
||||||
|
base64 =
|
||||||
|
url
|
||||||
|
|> String.replace("%2F", replacement)
|
||||||
|
|> URI.decode()
|
||||||
|
|> URI.encode()
|
||||||
|
|> String.replace(replacement, "%2F")
|
||||||
|
|> Base.url_encode64(@base64_opts)
|
||||||
|
|
||||||
|
sig = :crypto.hmac(:sha, secret, base64)
|
||||||
|
sig64 = sig |> Base.url_encode64(@base64_opts)
|
||||||
|
|
||||||
|
build_url(sig64, base64, filename(url))
|
||||||
|
end
|
||||||
|
|
||||||
def decode_url(sig, url) do
|
def decode_url(sig, url) do
|
||||||
secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base]
|
secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base]
|
||||||
sig = Base.url_decode64!(sig, @base64_opts)
|
sig = Base.url_decode64!(sig, @base64_opts)
|
||||||
|
|
|
@ -23,6 +23,12 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
||||||
|
|
||||||
action_fallback(Pleroma.Web.OAuth.FallbackController)
|
action_fallback(Pleroma.Web.OAuth.FallbackController)
|
||||||
|
|
||||||
|
# Note: this definition is only called from error-handling methods with `conn.params` as 2nd arg
|
||||||
|
def authorize(conn, %{"authorization" => _} = params) do
|
||||||
|
{auth_attrs, params} = Map.pop(params, "authorization")
|
||||||
|
authorize(conn, Map.merge(params, auth_attrs))
|
||||||
|
end
|
||||||
|
|
||||||
def authorize(%{assigns: %{token: %Token{} = token}} = conn, params) do
|
def authorize(%{assigns: %{token: %Token{} = token}} = conn, params) do
|
||||||
if ControllerHelper.truthy_param?(params["force_login"]) do
|
if ControllerHelper.truthy_param?(params["force_login"]) do
|
||||||
do_authorize(conn, params)
|
do_authorize(conn, params)
|
||||||
|
@ -44,21 +50,20 @@ def authorize(%{assigns: %{token: %Token{} = token}} = conn, params) do
|
||||||
|
|
||||||
def authorize(conn, params), do: do_authorize(conn, params)
|
def authorize(conn, params), do: do_authorize(conn, params)
|
||||||
|
|
||||||
defp do_authorize(conn, %{"authorization" => auth_attrs}), do: do_authorize(conn, auth_attrs)
|
defp do_authorize(conn, params) do
|
||||||
|
app = Repo.get_by(App, client_id: params["client_id"])
|
||||||
defp do_authorize(conn, auth_attrs) do
|
|
||||||
app = Repo.get_by(App, client_id: auth_attrs["client_id"])
|
|
||||||
available_scopes = (app && app.scopes) || []
|
available_scopes = (app && app.scopes) || []
|
||||||
scopes = oauth_scopes(auth_attrs, nil) || available_scopes
|
scopes = oauth_scopes(params, nil) || available_scopes
|
||||||
|
|
||||||
|
# Note: `params` might differ from `conn.params`; use `@params` not `@conn.params` in template
|
||||||
render(conn, Authenticator.auth_template(), %{
|
render(conn, Authenticator.auth_template(), %{
|
||||||
response_type: auth_attrs["response_type"],
|
response_type: params["response_type"],
|
||||||
client_id: auth_attrs["client_id"],
|
client_id: params["client_id"],
|
||||||
available_scopes: available_scopes,
|
available_scopes: available_scopes,
|
||||||
scopes: scopes,
|
scopes: scopes,
|
||||||
redirect_uri: auth_attrs["redirect_uri"],
|
redirect_uri: params["redirect_uri"],
|
||||||
state: auth_attrs["state"],
|
state: params["state"],
|
||||||
params: auth_attrs
|
params: params
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -138,7 +143,7 @@ def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do
|
||||||
fixed_token = fix_padding(params["code"]),
|
fixed_token = fix_padding(params["code"]),
|
||||||
%Authorization{} = auth <-
|
%Authorization{} = auth <-
|
||||||
Repo.get_by(Authorization, token: fixed_token, app_id: app.id),
|
Repo.get_by(Authorization, token: fixed_token, app_id: app.id),
|
||||||
%User{} = user <- User.get_by_id(auth.user_id),
|
%User{} = user <- User.get_cached_by_id(auth.user_id),
|
||||||
{:ok, token} <- Token.exchange_token(app, auth),
|
{:ok, token} <- Token.exchange_token(app, auth),
|
||||||
{:ok, inserted_at} <- DateTime.from_naive(token.inserted_at, "Etc/UTC") do
|
{:ok, inserted_at} <- DateTime.from_naive(token.inserted_at, "Etc/UTC") do
|
||||||
response = %{
|
response = %{
|
||||||
|
|
|
@ -27,7 +27,7 @@ defmodule Pleroma.Web.OAuth.Token do
|
||||||
def exchange_token(app, auth) do
|
def exchange_token(app, auth) do
|
||||||
with {:ok, auth} <- Authorization.use_token(auth),
|
with {:ok, auth} <- Authorization.use_token(auth),
|
||||||
true <- auth.app_id == app.id do
|
true <- auth.app_id == app.id do
|
||||||
create_token(app, User.get_by_id(auth.user_id), auth.scopes)
|
create_token(app, User.get_cached_by_id(auth.user_id), auth.scopes)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -84,7 +84,7 @@ def to_simple_form(activity, user, with_author \\ false)
|
||||||
def to_simple_form(%{data: %{"type" => "Create"}} = activity, user, with_author) do
|
def to_simple_form(%{data: %{"type" => "Create"}} = activity, user, with_author) do
|
||||||
h = fn str -> [to_charlist(str)] end
|
h = fn str -> [to_charlist(str)] end
|
||||||
|
|
||||||
object = Object.normalize(activity.data["object"])
|
object = Object.normalize(activity)
|
||||||
|
|
||||||
updated_at = object.data["published"]
|
updated_at = object.data["published"]
|
||||||
inserted_at = object.data["published"]
|
inserted_at = object.data["published"]
|
||||||
|
|
|
@ -294,7 +294,7 @@ def make_user(uri, update \\ false) do
|
||||||
}
|
}
|
||||||
|
|
||||||
with false <- update,
|
with false <- update,
|
||||||
%User{} = user <- User.get_by_ap_id(data.ap_id) do
|
%User{} = user <- User.get_cached_by_ap_id(data.ap_id) do
|
||||||
{:ok, user}
|
{:ok, user}
|
||||||
else
|
else
|
||||||
_e -> User.insert_or_update_user(data)
|
_e -> User.insert_or_update_user(data)
|
||||||
|
|
|
@ -21,8 +21,10 @@ defmodule Pleroma.Web.Push.Impl do
|
||||||
@doc "Performs sending notifications for user subscriptions"
|
@doc "Performs sending notifications for user subscriptions"
|
||||||
@spec perform(Notification.t()) :: list(any) | :error
|
@spec perform(Notification.t()) :: list(any) | :error
|
||||||
def perform(
|
def perform(
|
||||||
%{activity: %{data: %{"type" => activity_type}, id: activity_id}, user_id: user_id} =
|
%{
|
||||||
notif
|
activity: %{data: %{"type" => activity_type}, id: activity_id} = activity,
|
||||||
|
user_id: user_id
|
||||||
|
} = notif
|
||||||
)
|
)
|
||||||
when activity_type in @types do
|
when activity_type in @types do
|
||||||
actor = User.get_cached_by_ap_id(notif.activity.data["actor"])
|
actor = User.get_cached_by_ap_id(notif.activity.data["actor"])
|
||||||
|
@ -30,13 +32,14 @@ def perform(
|
||||||
type = Activity.mastodon_notification_type(notif.activity)
|
type = Activity.mastodon_notification_type(notif.activity)
|
||||||
gcm_api_key = Application.get_env(:web_push_encryption, :gcm_api_key)
|
gcm_api_key = Application.get_env(:web_push_encryption, :gcm_api_key)
|
||||||
avatar_url = User.avatar_url(actor)
|
avatar_url = User.avatar_url(actor)
|
||||||
|
object = Object.normalize(activity)
|
||||||
|
|
||||||
for subscription <- fetch_subsriptions(user_id),
|
for subscription <- fetch_subsriptions(user_id),
|
||||||
get_in(subscription.data, ["alerts", type]) do
|
get_in(subscription.data, ["alerts", type]) do
|
||||||
%{
|
%{
|
||||||
title: format_title(notif),
|
title: format_title(notif),
|
||||||
access_token: subscription.token.token,
|
access_token: subscription.token.token,
|
||||||
body: format_body(notif, actor),
|
body: format_body(notif, actor, object),
|
||||||
notification_id: notif.id,
|
notification_id: notif.id,
|
||||||
notification_type: type,
|
notification_type: type,
|
||||||
icon: avatar_url,
|
icon: avatar_url,
|
||||||
|
@ -95,25 +98,25 @@ def build_sub(subscription) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def format_body(
|
def format_body(
|
||||||
%{activity: %{data: %{"type" => "Create", "object" => %{"content" => content}}}},
|
%{activity: %{data: %{"type" => "Create"}}},
|
||||||
actor
|
actor,
|
||||||
|
%{data: %{"content" => content}}
|
||||||
) do
|
) do
|
||||||
"@#{actor.nickname}: #{Utils.scrub_html_and_truncate(content, 80)}"
|
"@#{actor.nickname}: #{Utils.scrub_html_and_truncate(content, 80)}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def format_body(
|
def format_body(
|
||||||
%{activity: %{data: %{"type" => "Announce", "object" => activity_id}}},
|
%{activity: %{data: %{"type" => "Announce"}}},
|
||||||
actor
|
actor,
|
||||||
|
%{data: %{"content" => content}}
|
||||||
) do
|
) do
|
||||||
%Activity{data: %{"object" => %{"id" => object_id}}} = Activity.get_by_ap_id(activity_id)
|
|
||||||
%Object{data: %{"content" => content}} = Object.get_by_ap_id(object_id)
|
|
||||||
|
|
||||||
"@#{actor.nickname} repeated: #{Utils.scrub_html_and_truncate(content, 80)}"
|
"@#{actor.nickname} repeated: #{Utils.scrub_html_and_truncate(content, 80)}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def format_body(
|
def format_body(
|
||||||
%{activity: %{data: %{"type" => type}}},
|
%{activity: %{data: %{"type" => type}}},
|
||||||
actor
|
actor,
|
||||||
|
_object
|
||||||
)
|
)
|
||||||
when type in ["Follow", "Like"] do
|
when type in ["Follow", "Like"] do
|
||||||
case type do
|
case type do
|
||||||
|
|
|
@ -135,6 +135,7 @@ defmodule Pleroma.Web.Router do
|
||||||
post("/password_reset", UtilController, :password_reset)
|
post("/password_reset", UtilController, :password_reset)
|
||||||
get("/emoji", UtilController, :emoji)
|
get("/emoji", UtilController, :emoji)
|
||||||
get("/captcha", UtilController, :captcha)
|
get("/captcha", UtilController, :captcha)
|
||||||
|
get("/healthcheck", UtilController, :healthcheck)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/api/pleroma", Pleroma.Web do
|
scope "/api/pleroma", Pleroma.Web do
|
||||||
|
@ -394,6 +395,8 @@ defmodule Pleroma.Web.Router do
|
||||||
get("/accounts/:id", MastodonAPIController, :user)
|
get("/accounts/:id", MastodonAPIController, :user)
|
||||||
|
|
||||||
get("/search", MastodonAPIController, :search)
|
get("/search", MastodonAPIController, :search)
|
||||||
|
|
||||||
|
get("/pleroma/accounts/:id/favourites", MastodonAPIController, :user_favourites)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -81,7 +81,7 @@ def handle_cast(%{action: :stream, topic: "list", item: item}, topics) do
|
||||||
_ ->
|
_ ->
|
||||||
Pleroma.List.get_lists_from_activity(item)
|
Pleroma.List.get_lists_from_activity(item)
|
||||||
|> Enum.filter(fn list ->
|
|> Enum.filter(fn list ->
|
||||||
owner = User.get_by_id(list.user_id)
|
owner = User.get_cached_by_id(list.user_id)
|
||||||
|
|
||||||
Visibility.visible_for_user?(item, owner)
|
Visibility.visible_for_user?(item, owner)
|
||||||
end)
|
end)
|
||||||
|
|
|
@ -22,7 +22,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
||||||
|
|
||||||
def show_password_reset(conn, %{"token" => token}) do
|
def show_password_reset(conn, %{"token" => token}) do
|
||||||
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
|
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
|
||||||
%User{} = user <- User.get_by_id(token.user_id) do
|
%User{} = user <- User.get_cached_by_id(token.user_id) do
|
||||||
render(conn, "password_reset.html", %{
|
render(conn, "password_reset.html", %{
|
||||||
token: token,
|
token: token,
|
||||||
user: user
|
user: user
|
||||||
|
@ -113,13 +113,13 @@ defp is_status?(acct) do
|
||||||
def do_remote_follow(conn, %{
|
def do_remote_follow(conn, %{
|
||||||
"authorization" => %{"name" => username, "password" => password, "id" => id}
|
"authorization" => %{"name" => username, "password" => password, "id" => id}
|
||||||
}) do
|
}) do
|
||||||
followee = User.get_by_id(id)
|
followee = User.get_cached_by_id(id)
|
||||||
avatar = User.avatar_url(followee)
|
avatar = User.avatar_url(followee)
|
||||||
name = followee.nickname
|
name = followee.nickname
|
||||||
|
|
||||||
with %User{} = user <- User.get_cached_by_nickname(username),
|
with %User{} = user <- User.get_cached_by_nickname(username),
|
||||||
true <- Pbkdf2.checkpw(password, user.password_hash),
|
true <- Pbkdf2.checkpw(password, user.password_hash),
|
||||||
%User{} = _followed <- User.get_by_id(id),
|
%User{} = _followed <- User.get_cached_by_id(id),
|
||||||
{:ok, follower} <- User.follow(user, followee),
|
{:ok, follower} <- User.follow(user, followee),
|
||||||
{:ok, _activity} <- ActivityPub.follow(follower, followee) do
|
{:ok, _activity} <- ActivityPub.follow(follower, followee) do
|
||||||
conn
|
conn
|
||||||
|
@ -141,7 +141,7 @@ def do_remote_follow(conn, %{
|
||||||
end
|
end
|
||||||
|
|
||||||
def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}}) do
|
def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}}) do
|
||||||
with %User{} = followee <- User.get_by_id(id),
|
with %User{} = followee <- User.get_cached_by_id(id),
|
||||||
{:ok, follower} <- User.follow(user, followee),
|
{:ok, follower} <- User.follow(user, followee),
|
||||||
{:ok, _activity} <- ActivityPub.follow(follower, followee) do
|
{:ok, _activity} <- ActivityPub.follow(follower, followee) do
|
||||||
conn
|
conn
|
||||||
|
@ -286,7 +286,7 @@ def emoji(conn, _params) do
|
||||||
emoji =
|
emoji =
|
||||||
Emoji.get_all()
|
Emoji.get_all()
|
||||||
|> Enum.map(fn {short_code, path, tags} ->
|
|> Enum.map(fn {short_code, path, tags} ->
|
||||||
{short_code, %{image_url: path, tags: String.split(tags, ",")}}
|
{short_code, %{image_url: path, tags: tags}}
|
||||||
end)
|
end)
|
||||||
|> Enum.into(%{})
|
|> Enum.into(%{})
|
||||||
|
|
||||||
|
@ -363,4 +363,22 @@ def delete_account(%{assigns: %{user: user}} = conn, params) do
|
||||||
def captcha(conn, _params) do
|
def captcha(conn, _params) do
|
||||||
json(conn, Pleroma.Captcha.new())
|
json(conn, Pleroma.Captcha.new())
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def healthcheck(conn, _params) do
|
||||||
|
info =
|
||||||
|
if Pleroma.Config.get([:instance, :healthcheck]) do
|
||||||
|
Pleroma.Healthcheck.system_info()
|
||||||
|
else
|
||||||
|
%{}
|
||||||
|
end
|
||||||
|
|
||||||
|
conn =
|
||||||
|
if info[:healthy] do
|
||||||
|
conn
|
||||||
|
else
|
||||||
|
Plug.Conn.put_status(conn, :service_unavailable)
|
||||||
|
end
|
||||||
|
|
||||||
|
json(conn, info)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -240,7 +240,7 @@ def get_user(user \\ nil, params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
%{"screen_name" => nickname} ->
|
%{"screen_name" => nickname} ->
|
||||||
case User.get_by_nickname(nickname) do
|
case User.get_cached_by_nickname(nickname) do
|
||||||
nil -> {:error, "No user with such screen_name"}
|
nil -> {:error, "No user with such screen_name"}
|
||||||
target -> {:ok, target}
|
target -> {:ok, target}
|
||||||
end
|
end
|
||||||
|
|
|
@ -434,7 +434,7 @@ def password_reset(conn, params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def confirm_email(conn, %{"user_id" => uid, "token" => token}) do
|
def confirm_email(conn, %{"user_id" => uid, "token" => token}) do
|
||||||
with %User{} = user <- User.get_by_id(uid),
|
with %User{} = user <- User.get_cached_by_id(uid),
|
||||||
true <- user.local,
|
true <- user.local,
|
||||||
true <- user.info.confirmation_pending,
|
true <- user.info.confirmation_pending,
|
||||||
true <- user.info.confirmation_token == token,
|
true <- user.info.confirmation_token == token,
|
||||||
|
@ -587,7 +587,7 @@ def friend_requests(conn, params) do
|
||||||
|
|
||||||
def approve_friend_request(conn, %{"user_id" => uid} = _params) do
|
def approve_friend_request(conn, %{"user_id" => uid} = _params) do
|
||||||
with followed <- conn.assigns[:user],
|
with followed <- conn.assigns[:user],
|
||||||
%User{} = follower <- User.get_by_id(uid),
|
%User{} = follower <- User.get_cached_by_id(uid),
|
||||||
{:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do
|
{:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do
|
||||||
conn
|
conn
|
||||||
|> put_view(UserView)
|
|> put_view(UserView)
|
||||||
|
@ -599,7 +599,7 @@ def approve_friend_request(conn, %{"user_id" => uid} = _params) do
|
||||||
|
|
||||||
def deny_friend_request(conn, %{"user_id" => uid} = _params) do
|
def deny_friend_request(conn, %{"user_id" => uid} = _params) do
|
||||||
with followed <- conn.assigns[:user],
|
with followed <- conn.assigns[:user],
|
||||||
%User{} = follower <- User.get_by_id(uid),
|
%User{} = follower <- User.get_cached_by_id(uid),
|
||||||
{:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do
|
{:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do
|
||||||
conn
|
conn
|
||||||
|> put_view(UserView)
|
|> put_view(UserView)
|
||||||
|
@ -632,7 +632,7 @@ def raw_empty_array(conn, _params) do
|
||||||
|
|
||||||
defp build_info_cng(user, params) do
|
defp build_info_cng(user, params) do
|
||||||
info_params =
|
info_params =
|
||||||
["no_rich_text", "locked", "hide_followers", "hide_follows", "show_role"]
|
["no_rich_text", "locked", "hide_followers", "hide_follows", "hide_favorites", "show_role"]
|
||||||
|> Enum.reduce(%{}, fn key, res ->
|
|> Enum.reduce(%{}, fn key, res ->
|
||||||
if value = params[key] do
|
if value = params[key] do
|
||||||
Map.put(res, key, value == "true")
|
Map.put(res, key, value == "true")
|
||||||
|
|
|
@ -74,58 +74,49 @@ defp do_render("user.json", %{user: user = %User{}} = assigns) do
|
||||||
|> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
|
|> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
|
||||||
|> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
|
|> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
|
||||||
|
|
||||||
data = %{
|
|
||||||
"created_at" => user.inserted_at |> Utils.format_naive_asctime(),
|
|
||||||
"description" => HTML.strip_tags((user.bio || "") |> String.replace("<br>", "\n")),
|
|
||||||
"description_html" => HTML.filter_tags(user.bio, User.html_filter_policy(for_user)),
|
|
||||||
"favourites_count" => 0,
|
|
||||||
"followers_count" => user_info[:follower_count],
|
|
||||||
"following" => following,
|
|
||||||
"follows_you" => follows_you,
|
|
||||||
"statusnet_blocking" => statusnet_blocking,
|
|
||||||
"friends_count" => user_info[:following_count],
|
|
||||||
"id" => user.id,
|
|
||||||
"name" => user.name || user.nickname,
|
|
||||||
"name_html" =>
|
|
||||||
if(user.name,
|
|
||||||
do: HTML.strip_tags(user.name) |> Formatter.emojify(emoji),
|
|
||||||
else: user.nickname
|
|
||||||
),
|
|
||||||
"profile_image_url" => image,
|
|
||||||
"profile_image_url_https" => image,
|
|
||||||
"profile_image_url_profile_size" => image,
|
|
||||||
"profile_image_url_original" => image,
|
|
||||||
"rights" => %{
|
|
||||||
"delete_others_notice" => !!user.info.is_moderator,
|
|
||||||
"admin" => !!user.info.is_admin
|
|
||||||
},
|
|
||||||
"screen_name" => user.nickname,
|
|
||||||
"statuses_count" => user_info[:note_count],
|
|
||||||
"statusnet_profile_url" => user.ap_id,
|
|
||||||
"cover_photo" => User.banner_url(user) |> MediaProxy.url(),
|
|
||||||
"background_image" => image_url(user.info.background) |> MediaProxy.url(),
|
|
||||||
"is_local" => user.local,
|
|
||||||
"locked" => user.info.locked,
|
|
||||||
"default_scope" => user.info.default_scope,
|
|
||||||
"no_rich_text" => user.info.no_rich_text,
|
|
||||||
"hide_followers" => user.info.hide_followers,
|
|
||||||
"hide_follows" => user.info.hide_follows,
|
|
||||||
"fields" => fields,
|
|
||||||
|
|
||||||
# Pleroma extension
|
|
||||||
"pleroma" =>
|
|
||||||
%{
|
|
||||||
"confirmation_pending" => user_info.confirmation_pending,
|
|
||||||
"tags" => user.tags
|
|
||||||
}
|
|
||||||
|> maybe_with_activation_status(user, for_user)
|
|
||||||
}
|
|
||||||
|
|
||||||
data =
|
data =
|
||||||
if(user.info.is_admin || user.info.is_moderator,
|
%{
|
||||||
do: maybe_with_role(data, user, for_user),
|
"created_at" => user.inserted_at |> Utils.format_naive_asctime(),
|
||||||
else: data
|
"description" => HTML.strip_tags((user.bio || "") |> String.replace("<br>", "\n")),
|
||||||
)
|
"description_html" => HTML.filter_tags(user.bio, User.html_filter_policy(for_user)),
|
||||||
|
"favourites_count" => 0,
|
||||||
|
"followers_count" => user_info[:follower_count],
|
||||||
|
"following" => following,
|
||||||
|
"follows_you" => follows_you,
|
||||||
|
"statusnet_blocking" => statusnet_blocking,
|
||||||
|
"friends_count" => user_info[:following_count],
|
||||||
|
"id" => user.id,
|
||||||
|
"name" => user.name || user.nickname,
|
||||||
|
"name_html" =>
|
||||||
|
if(user.name,
|
||||||
|
do: HTML.strip_tags(user.name) |> Formatter.emojify(emoji),
|
||||||
|
else: user.nickname
|
||||||
|
),
|
||||||
|
"profile_image_url" => image,
|
||||||
|
"profile_image_url_https" => image,
|
||||||
|
"profile_image_url_profile_size" => image,
|
||||||
|
"profile_image_url_original" => image,
|
||||||
|
"screen_name" => user.nickname,
|
||||||
|
"statuses_count" => user_info[:note_count],
|
||||||
|
"statusnet_profile_url" => user.ap_id,
|
||||||
|
"cover_photo" => User.banner_url(user) |> MediaProxy.url(),
|
||||||
|
"background_image" => image_url(user.info.background) |> MediaProxy.url(),
|
||||||
|
"is_local" => user.local,
|
||||||
|
"locked" => user.info.locked,
|
||||||
|
"hide_followers" => user.info.hide_followers,
|
||||||
|
"hide_follows" => user.info.hide_follows,
|
||||||
|
"fields" => fields,
|
||||||
|
|
||||||
|
# Pleroma extension
|
||||||
|
"pleroma" =>
|
||||||
|
%{
|
||||||
|
"confirmation_pending" => user_info.confirmation_pending,
|
||||||
|
"tags" => user.tags
|
||||||
|
}
|
||||||
|
|> maybe_with_activation_status(user, for_user)
|
||||||
|
}
|
||||||
|
|> maybe_with_user_settings(user, for_user)
|
||||||
|
|> maybe_with_role(user, for_user)
|
||||||
|
|
||||||
if assigns[:token] do
|
if assigns[:token] do
|
||||||
Map.put(data, "token", token_string(assigns[:token]))
|
Map.put(data, "token", token_string(assigns[:token]))
|
||||||
|
@ -141,15 +132,35 @@ defp maybe_with_activation_status(data, user, %User{info: %{is_admin: true}}) do
|
||||||
defp maybe_with_activation_status(data, _, _), do: data
|
defp maybe_with_activation_status(data, _, _), do: data
|
||||||
|
|
||||||
defp maybe_with_role(data, %User{id: id} = user, %User{id: id}) do
|
defp maybe_with_role(data, %User{id: id} = user, %User{id: id}) do
|
||||||
Map.merge(data, %{"role" => role(user), "show_role" => user.info.show_role})
|
Map.merge(data, %{
|
||||||
|
"role" => role(user),
|
||||||
|
"show_role" => user.info.show_role,
|
||||||
|
"rights" => %{
|
||||||
|
"delete_others_notice" => !!user.info.is_moderator,
|
||||||
|
"admin" => !!user.info.is_admin
|
||||||
|
}
|
||||||
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
defp maybe_with_role(data, %User{info: %{show_role: true}} = user, _user) do
|
defp maybe_with_role(data, %User{info: %{show_role: true}} = user, _user) do
|
||||||
Map.merge(data, %{"role" => role(user)})
|
Map.merge(data, %{
|
||||||
|
"role" => role(user),
|
||||||
|
"rights" => %{
|
||||||
|
"delete_others_notice" => !!user.info.is_moderator,
|
||||||
|
"admin" => !!user.info.is_admin
|
||||||
|
}
|
||||||
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
defp maybe_with_role(data, _, _), do: data
|
defp maybe_with_role(data, _, _), do: data
|
||||||
|
|
||||||
|
defp maybe_with_user_settings(data, %User{info: info, id: id} = _user, %User{id: id}) do
|
||||||
|
data
|
||||||
|
|> Kernel.put_in(["default_scope"], info.default_scope)
|
||||||
|
|> Kernel.put_in(["no_rich_text"], info.no_rich_text)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_with_user_settings(data, _, _), do: data
|
||||||
defp role(%User{info: %{:is_admin => true}}), do: "admin"
|
defp role(%User{info: %{:is_admin => true}}), do: "admin"
|
||||||
defp role(%User{info: %{:is_moderator => true}}), do: "moderator"
|
defp role(%User{info: %{:is_moderator => true}}), do: "moderator"
|
||||||
defp role(_), do: "member"
|
defp role(_), do: "member"
|
||||||
|
|
|
@ -37,7 +37,7 @@ def webfinger(resource, fmt) when fmt in ["XML", "JSON"] do
|
||||||
regex = ~r/(acct:)?(?<username>\w+)@#{host}/
|
regex = ~r/(acct:)?(?<username>\w+)@#{host}/
|
||||||
|
|
||||||
with %{"username" => username} <- Regex.named_captures(regex, resource),
|
with %{"username" => username} <- Regex.named_captures(regex, resource),
|
||||||
%User{} = user <- User.get_by_nickname(username) do
|
%User{} = user <- User.get_cached_by_nickname(username) do
|
||||||
{:ok, represent_user(user, fmt)}
|
{:ok, represent_user(user, fmt)}
|
||||||
else
|
else
|
||||||
_e ->
|
_e ->
|
||||||
|
|
1
mix.exs
|
@ -84,6 +84,7 @@ defp deps do
|
||||||
{:ex_aws, "~> 2.0"},
|
{:ex_aws, "~> 2.0"},
|
||||||
{:ex_aws_s3, "~> 2.0"},
|
{:ex_aws_s3, "~> 2.0"},
|
||||||
{:earmark, "~> 1.3"},
|
{:earmark, "~> 1.3"},
|
||||||
|
{:bbcode, "~> 0.1"},
|
||||||
{:ex_machina, "~> 2.3", only: :test},
|
{:ex_machina, "~> 2.3", only: :test},
|
||||||
{:credo, "~> 0.9.3", only: [:dev, :test]},
|
{:credo, "~> 0.9.3", only: [:dev, :test]},
|
||||||
{:mock, "~> 0.3.1", only: :test},
|
{:mock, "~> 0.3.1", only: :test},
|
||||||
|
|
1
mix.lock
|
@ -2,6 +2,7 @@
|
||||||
"accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm"},
|
"accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm"},
|
||||||
"auto_linker": {:git, "https://git.pleroma.social/pleroma/auto_linker.git", "90613b4bae875a3610c275b7056b61ffdd53210d", [ref: "90613b4bae875a3610c275b7056b61ffdd53210d"]},
|
"auto_linker": {:git, "https://git.pleroma.social/pleroma/auto_linker.git", "90613b4bae875a3610c275b7056b61ffdd53210d", [ref: "90613b4bae875a3610c275b7056b61ffdd53210d"]},
|
||||||
"base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"},
|
"base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"},
|
||||||
|
"bbcode": {:hex, :bbcode, "0.1.0", "400e618b640b635261611d7fb7f79d104917fc5b084aae371ab6b08477cb035b", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"},
|
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"},
|
||||||
"cachex": {:hex, :cachex, "3.0.3", "4e2d3e05814a5738f5ff3903151d5c25636d72a3527251b753f501ad9c657967", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm"},
|
"cachex": {:hex, :cachex, "3.0.3", "4e2d3e05814a5738f5ff3903151d5c25636d72a3527251b753f501ad9c657967", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"calendar": {:hex, :calendar, "0.17.5", "0ff5b09a60b9677683aa2a6fee948558660501c74a289103ea099806bc41a352", [:mix], [{:tzdata, "~> 0.5.20 or ~> 0.1.201603", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
|
"calendar": {:hex, :calendar, "0.17.5", "0ff5b09a60b9677683aa2a6fee948558660501c74a289103ea099806bc41a352", [:mix], [{:tzdata, "~> 0.5.20 or ~> 0.1.201603", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.CreateBookmarks do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create table(:bookmarks) do
|
||||||
|
add(:user_id, references(:users, type: :uuid, on_delete: :delete_all))
|
||||||
|
add(:activity_id, references(:activities, type: :uuid, on_delete: :delete_all))
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
create(unique_index(:bookmarks, [:user_id, :activity_id]))
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,29 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.MigrateOldBookmarks do
|
||||||
|
use Ecto.Migration
|
||||||
|
import Ecto.Query
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Bookmark
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Repo
|
||||||
|
|
||||||
|
def change do
|
||||||
|
query =
|
||||||
|
from(u in User,
|
||||||
|
where: u.local == true,
|
||||||
|
where: fragment("array_length(bookmarks, 1)") > 0,
|
||||||
|
select: %{id: u.id, bookmarks: fragment("bookmarks")}
|
||||||
|
)
|
||||||
|
|
||||||
|
Repo.stream(query)
|
||||||
|
|> Enum.each(fn %{id: user_id, bookmarks: bookmarks} ->
|
||||||
|
Enum.each(bookmarks, fn ap_id ->
|
||||||
|
activity = Activity.get_create_by_object_ap_id(ap_id)
|
||||||
|
unless is_nil(activity), do: {:ok, _} = Bookmark.create(user_id, activity.id)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
alter table(:users) do
|
||||||
|
remove(:bookmarks)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Before Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 77 KiB |
Before Width: | Height: | Size: 225 KiB |
Before Width: | Height: | Size: 87 KiB |
Before Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 94 KiB |
Before Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 236 KiB |
Before Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 127 KiB |
Before Width: | Height: | Size: 76 KiB |
Before Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 118 KiB |
Before Width: | Height: | Size: 125 KiB |
Before Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 104 KiB |
Before Width: | Height: | Size: 113 KiB |
Before Width: | Height: | Size: 165 KiB |
Before Width: | Height: | Size: 97 KiB |
Before Width: | Height: | Size: 69 KiB |
Before Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 69 KiB |
Before Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 96 KiB |
Before Width: | Height: | Size: 73 KiB |
Before Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 66 KiB |