Compare commits

...

214 Commits

Author SHA1 Message Date
Rachel Fae Fox (foxiepaws) c275572317 Merge branch 'develop' of https://git.pleroma.social/pleroma/pleroma into develop 2019-08-18 19:03:14 -04:00
kaniini 608773c5ef Merge branch 'chore/fix-ap-muted-tests' into 'develop'
tests: activitypub: fix muted thread tests

See merge request pleroma/pleroma!1584
2019-08-18 22:33:51 +00:00
Ariadne Conill a2fdc32368 tests: activitypub: fix typo 2019-08-18 22:31:59 +00:00
kaniini 475fcb3e6c Merge branch 'fix/bugfix-blocked-user-follow-reqs' into 'develop'
Clear follow requests when blocking a user

See merge request pleroma/pleroma!1583
2019-08-18 21:59:22 +00:00
kaniini 18c8c8d176 Merge branch 'feature/hide-muted-threads-from-timeline' into 'develop'
Hide muted theads from home/public timelines unless `with_muted` is set

See merge request pleroma/pleroma!1575
2019-08-18 21:57:55 +00:00
Sadposter 58c1391c4d use commonAPI in tests 2019-08-18 22:54:40 +01:00
Sadposter 94e336d9d5 clear follow requests when blocking a user 2019-08-18 20:30:57 +01:00
lain 7ab2dbbdb6 Merge branch 'pleroma-conversations' into 'develop'
Extended Pleroma Conversations

See merge request pleroma/pleroma!1535
2019-08-16 12:55:33 +00:00
rinpatch 0f24edcd8d Merge branch 'fix/oauth-cleanup-worker-unconditional-start' into 'develop'
Fix OAuth cleanup worker unconditionally starting

See merge request pleroma/pleroma!1578
2019-08-15 17:51:30 +00:00
rinpatch fba3c16d20 Fix OAuth cleanup worker unconditionally starting
!1576 removed enabled/disabled check from the worker, in favor of just
not starting it in application.ex if disabled. However a line
unconditionally starting the worker was removed
2019-08-15 20:36:20 +03:00
kaniini 1c8f58a30e Merge branch 'features/formatting-sub_sup' into 'develop'
html.ex: Allow sub and sup elements by default

Closes #1191

See merge request pleroma/pleroma!1572
2019-08-15 17:34:16 +00:00
lain aefb9cdee8 Merge branch 'feature/parallel-rendering' into 'develop'
Parallelize template rendering

See merge request pleroma/pleroma!1576
2019-08-15 15:51:11 +00:00
rinpatch 1ad71592ad Parallelize template rendering 2019-08-15 18:05:50 +03:00
rinpatch a4a3e3becd Hide muted theads from home/public timelines unless `with_muted` is set 2019-08-15 17:37:30 +03:00
lain 9fb71ce7f4 Merge branch 'cache-follow-state' into 'develop'
Cache follow state

Closes #1180

See merge request pleroma/pleroma!1573
2019-08-15 14:06:27 +00:00
rinpatch 27b747546a Merge branch 'fix/stats-send-after' into 'develop'
Collect stats immediately after init

See merge request pleroma/pleroma!1574
2019-08-14 22:10:44 +00:00
rinpatch e8a8d50138 Collect stats immediately after init 2019-08-15 01:01:13 +03:00
rinpatch bd5ad0af78 Cache follow state 2019-08-15 00:47:30 +03:00
rinpatch a9e75fa6a4 Add a task to benchmark timeline rendering 2019-08-15 00:43:02 +03:00
Haelwenn (lanodan) Monnier a6a814420d
html.ex: Allow sub and sup elements by default
Closes: https://git.pleroma.social/pleroma/pleroma/issues/1191
2019-08-14 22:49:13 +02:00
rinpatch 31d576de0c Merge branch 'misc-otp-issues' into 'develop'
Misc OTP issues

See merge request pleroma/pleroma!1567
2019-08-14 19:22:15 +00:00
rinpatch dc902ad3df Merge branch 'fix/http-signatures-post' into 'develop'
activitypub: publisher: add (request-target) to http signature when POSTing

See merge request pleroma/pleroma!1569
2019-08-14 19:16:52 +00:00
kaniini 1754f8ce6d Apply suggestion to lib/pleroma/web/activity_pub/publisher.ex 2019-08-14 19:05:44 +00:00
rinpatch 4be71c91e0 Merge branch 'fix/mrf-describe-keyword-lists' into 'develop'
MRF: fix up unserializable option lists in describe implementations

See merge request pleroma/pleroma!1568
2019-08-14 19:03:39 +00:00
Ariadne Conill 5bb418a90d activitypub: publisher: add (request-target) to http signature when POSTing 2019-08-14 19:01:51 +00:00
Ariadne Conill 626e094589 MRF: fix up unserializable option lists in describe implementations 2019-08-14 18:54:17 +00:00
stwf c43152f6c1 fix formatting 2019-08-14 14:01:11 -04:00
stwf d81f63845a Implement Pleroma.Stats as GenServer 2019-08-14 11:59:33 -04:00
stwf 574856ef01 streamline Streamer pings 2019-08-14 11:58:32 -04:00
stwf 15ef521009 Isolate OAuth.Token.CleanWorker 2019-08-14 11:57:50 -04:00
stwf 51bdf0cab6 use default child_specs 2019-08-14 11:55:17 -04:00
lain 4a5b0965a0 Merge branch 'fix/revert-tzdata-1.8' into 'develop'
Switch to pre-1.8 version of tzdata.

Closes #1183

See merge request pleroma/pleroma!1566
2019-08-14 15:07:03 +00:00
lain d3af9e19ed Conversations: Load relations in one query. 2019-08-14 17:01:11 +02:00
rinpatch 6a101f0361 Switch to pre-1.8 version of tzdata.
tzdata 1.0.0 requires Elixir 1.8.0, but we target 1.7. Fortunately
tzdata issues bugfix releases for pre-1.8.0 version.
2019-08-14 17:05:21 +03:00
lain f73212b2a3 Conversation: Render new participation on update. 2019-08-14 15:56:15 +02:00
lain df81abb68c Conversations: Use correct oauth paths for extended api. 2019-08-14 15:55:43 +02:00
lain 560dbad538 Merge remote-tracking branch 'origin/develop' into pleroma-conversations 2019-08-14 15:30:40 +02:00
rinpatch 744e2ce8b4 Merge branch 'backport/describe-api-changelog' into 'develop'
Backport the describe API changelog entry to develop

See merge request pleroma/pleroma!1564
2019-08-14 02:12:09 +00:00
Ariadne Conill 8fab9c5c1c update changelog to cover MRF describe API. 2019-08-14 04:59:31 +03:00
kaniini 5c35d2f1d8 Merge branch 'preload-user-timelines' into 'develop'
Preload thread mutes/bookmarks in user_statuses

See merge request pleroma/pleroma!1563
2019-08-14 01:04:07 +00:00
kaniini fe42844bda Merge branch 'get-context-optimizations' into 'develop'
Preload thread mutes/bookmarks in get_context

See merge request pleroma/pleroma!1562
2019-08-14 01:03:54 +00:00
rinpatch 8202f1634a Preload thread mutes/bookmarks in user_statuses 2019-08-14 03:02:09 +03:00
rinpatch f4e087ee48 Preload thread mutes/bookmarks in get_context
Also removes filtering for creates (was done on the database side
already) and filtering for the requested activity (moved to the database
side) from application side.
2019-08-14 02:36:54 +03:00
rinpatch 9ef31767f2 Merge branch 'feature/mrf-vocabulary' into 'develop'
MRF Vocabulary

See merge request pleroma/pleroma!1559
2019-08-13 22:51:15 +00:00
kaniini c3a54cc34d Merge branch 'feature/mrf-describe' into 'develop'
MRF describe API

See merge request pleroma/pleroma!1561
2019-08-13 22:46:19 +00:00
Ariadne Conill 5983f98f26 docs tweak 2019-08-13 22:40:18 +00:00
rinpatch 83a3de8cc4 Merge branch 'length-limit-bio' into 'develop'
Add configurable length limits for `User.bio` and `User.name`

See merge request pleroma/pleroma!1515
2019-08-13 22:40:13 +00:00
Ariadne Conill abfbcfdcb3 mrf_vocabulary: add describe API support 2019-08-13 22:39:26 +00:00
Ariadne Conill 4244e17de0 fix credo 2019-08-13 22:36:24 +00:00
Haelwenn 47c20ab796 Merge branch 'fix/mastoapi-more-object-preloads' into 'develop'
Mastodon API: Preloading and normalization optimizations

See merge request pleroma/pleroma!1558
2019-08-13 22:32:49 +00:00
Ariadne Conill 10fef2fcee tests: fix up nodeinfo tests 2019-08-13 22:32:40 +00:00
kaniini f5aacaf4d3 Merge branch 'feature/nicer-safe-render-errors' into 'develop'
Nicer formatting for safe_render errors

See merge request pleroma/pleroma!1560
2019-08-13 22:25:15 +00:00
Ariadne Conill dd0b71ea6d tests: add tests for MRF.describe() 2019-08-13 22:19:15 +00:00
rinpatch 46d7bef7e7 Nicer formatting for safe_render errors 2019-08-14 01:15:18 +03:00
Ariadne Conill f305e97eeb nodeinfo: use MRF.describe() instead of hardcoded MRF transparency stuff 2019-08-13 22:09:02 +00:00
Ariadne Conill c574b7a1fc MRF: add describe() to all modules, add base MRF configuration to base describe() 2019-08-13 22:08:58 +00:00
Ariadne Conill 7089400675 test: add mock MRF module for describe() testing 2019-08-13 21:29:15 +00:00
Ariadne Conill 694bc43123 MRF: add describe() for gathering and describing the MRF configuration 2019-08-13 21:26:24 +00:00
kaniini 943da97d6b Merge branch 'test/web_activity_pub_relay' into 'develop'
tests for Web/ActivityPub/Relay

See merge request pleroma/pleroma!1557
2019-08-13 21:12:59 +00:00
Maksim fea4d89e9f tests for Web/ActivityPub/Relay 2019-08-13 21:12:59 +00:00
kaniini 39e6b16432 Merge branch 'test/web_mastodon_api' into 'develop'
tests for /web/mastodon_api/mastodon_api.ex

See merge request pleroma/pleroma!1554
2019-08-13 21:12:37 +00:00
Maksim 04da1166db tests for /web/mastodon_api/mastodon_api.ex 2019-08-13 21:12:37 +00:00
Ariadne Conill 369d9cf03b update changelog for mrf_vocabulary 2019-08-13 21:00:23 +00:00
Ariadne Conill f7e3b7ff75 tests: add tests for mrf_vocabulary 2019-08-13 20:55:42 +00:00
Ariadne Conill 3cfaac39e2 docs: document mrf_vocabulary module settings 2019-08-13 20:55:42 +00:00
Ariadne Conill 3fdbeb7087 MRF: add vocabulary policy module 2019-08-13 20:55:38 +00:00
rinpatch c1b6952d2a Mastodon API: Preloading and normalization optimizations
- Try to normalize the activity instead of object wherever possible
- Put the `user` key on non-home timelines as well so bookmarks and
thread mutes are preloaded there as well
- Skip trying to get the user when rendering mentions if the id ==
as:Public or user's follower collection
- Preload the object when getting replied to activities and do not crash
if it's not present

This almost solves the problem of Pleroma hammering the db with a lot
of queries when rendering timelines, the things left are
1. When rendering mentions and the user is not in cache, save it for
later and request all uncached users in one go
2. Somehow get rid of needing to get the latest follow activity to
detect the value of `requested` in a relationship. (create a database
view for user relationship and cache it maybe?)
2019-08-13 20:34:34 +03:00
kaniini 984d7be1a4 Merge branch 'feature/remove-disable-blocks' into 'develop'
config: remove legacy activitypub accept_blocks setting

See merge request pleroma/pleroma!1556
2019-08-13 02:45:47 +00:00
Ariadne Conill f46cd7e9c7 config: remove legacy activitypub accept_blocks setting
Anyone who is interested in dropping blocks can write their own MRF
policy at this point.  This setting predated the MRF framework.

Disabling the side effect (unsubscription) is still a config option
per policy.
2019-08-13 02:15:21 +00:00
rinpatch b0fad153e1 Merge branch 'update/admin-fe' into 'develop'
Update AdminFE

See merge request pleroma/pleroma!1555
2019-08-12 21:07:11 +00:00
Mark Felder 24a731a9a6 Update AdminFE
Now permits server configuration. Consider this ALPHA.
2019-08-12 15:00:03 -05:00
lain 2674db14a2 Modify Changelog. 2019-08-12 14:31:12 +02:00
lain 511ccea5aa ConversationView: Align parameter names with other views. 2019-08-12 14:23:06 +02:00
lain 60231ec7bd Conversation: Add endpoint to get a conversation by id. 2019-08-12 13:58:04 +02:00
lain 23c46f7e72 Conversations: Use 'recipients' for accounts in conversation view.
According to gargron, this is the intended usage.
2019-08-12 12:51:08 +02:00
rinpatch b9578bb8a0 Merge branch 'chore/changelog-update' into 'develop'
Add a changelog entry for !1552

See merge request pleroma/pleroma!1553
2019-08-11 20:25:17 +00:00
rinpatch d4d31ffdc4 Add a changelog entry for !1552 2019-08-11 23:19:20 +03:00
rinpatch 9a8a01837e Merge branch 'fix/fix-type-depth' into 'develop'
Do not fetch the reply object in `fix_type` unless the object has the `name` key and use a depth limit when fetching it

See merge request pleroma/pleroma!1552
2019-08-11 20:01:10 +00:00
rinpatch 92479c6f48 Do not fetch the reply object in `fix_type` unless the object has the
`name` key and use a depth limit when fetching it
2019-08-11 22:49:55 +03:00
kaniini 779e32a879 Merge branch 'fix/mrf-subdomain-case-insensitive' into 'develop'
MRF: ensure that subdomain_match calls are case-insensitive

See merge request pleroma/pleroma!1550
2019-08-10 21:33:19 +00:00
Ariadne Conill 9cfc289594 MRF: ensure that subdomain_match calls are case-insensitive 2019-08-10 21:19:26 +00:00
kaniini 17d5564a9c Merge branch 'fix/hide-follows-counters' into 'develop'
Mastodon API: Set follower/following counters to 0 when hiding followers/following is enabled

See merge request pleroma/pleroma!1544
2019-08-10 18:59:24 +00:00
kaniini 84808e1697 Merge branch 'develop' into 'fix/hide-follows-counters'
# Conflicts:
#   CHANGELOG.md
2019-08-10 18:49:04 +00:00
kaniini 708e7f0942 Merge branch 'fix/crash-favourited-by-ordered-collections' into 'develop'
Strip internal fields including likes from incoming and outgoing activities

Closes #1159

See merge request pleroma/pleroma!1533
2019-08-10 18:47:40 +00:00
Sergey Suprunenko af4cf35e20 Strip internal fields including likes from incoming and outgoing activities 2019-08-10 18:47:40 +00:00
kaniini 5aa62b8581 Merge branch 'test/pleroma_uploaders' into 'develop'
tests for Pleroma.Uploaders

See merge request pleroma/pleroma!1543
2019-08-10 18:46:26 +00:00
Maksim 11d08c2de0 tests for Pleroma.Uploaders 2019-08-10 18:46:26 +00:00
kaniini 390329a303 Merge branch 'fix/mastoapi-threadmute-detection' into 'develop'
Mastodon API: Fix thread mute detection

See merge request pleroma/pleroma!1548
2019-08-10 18:29:16 +00:00
rinpatch 0802a08871 Mastodon API: Fix thread mute detection
It was calling CommonAPI.thread_muted? with post author's account
instead of viewer's one.
2019-08-10 16:27:46 +03:00
rinpatch 337edb3e50 Merge branch 'feature/uploader_s3' into 'develop'
Uploader.S3 added support stream uploads

See merge request pleroma/pleroma!1545
2019-08-10 11:28:00 +00:00
Maksim bb9c539580 Uploader.S3 added support stream uploads 2019-08-10 11:27:59 +00:00
rinpatch 409bcad54b Mastodon API: Set follower/following counters to 0 when hiding
followers/following is enabled

We are already doing that in AP representation, so I think we should do
it here as well for consistency.
2019-08-09 16:53:55 +03:00
lain 29807ef6a5 Merge branch 'feature/digest-email' into 'develop'
Feature/digest email

See merge request pleroma/pleroma!1078
2019-08-08 14:38:33 +00:00
lain a2b98f6d58 Merge remote-tracking branch 'origin/develop' into pleroma-conversations 2019-08-08 16:04:20 +02:00
rinpatch b18234e04c Merge branch 'patch-3' into 'develop'
Return profile URL when available instead of actor URI for MastodonAPI mention URL

Closes #1165

See merge request pleroma/pleroma!1541
2019-08-07 22:14:02 +00:00
Thibaut Girka 9c0da1009a Return profile URL in MastodonAPI's `url` field 2019-08-07 21:40:53 +00:00
Thibaut Girka 089d53a961 Simplify logic to mention.js `url` field
`User.profile_url` already fallbacks to ap_id
2019-08-07 20:55:37 +00:00
Thibaut Girka a10c840aba Return profile URL when available instead of actor URI for MastodonAPI mention URL
Fixes #1165
2019-08-07 20:29:30 +00:00
Haelwenn aa718ab8f6 Merge branch 'fix/object-reembeds' into 'develop'
Do not rembed the object after updating it

Closes #1142

See merge request pleroma/pleroma!1538
2019-08-06 22:14:00 +00:00
rinpatch 4f1b9c54b9 Do not rembed the object after updating it 2019-08-07 01:02:29 +03:00
rinpatch 5329e84d62 OStatus tests: stop relying on embedded objects 2019-08-07 00:58:48 +03:00
rinpatch 32018a4ee0 ActivityPub tests: remove assertions of embedded object being updated,
because the objects are no longer supposed to be embedded
2019-08-07 00:36:13 +03:00
rinpatch 03ad31328c OStatus Announce Representer: Do not depend on the object being embedded
in the Create activity
2019-08-07 00:23:58 +03:00
rinpatch 73d8d5c49f Stop depending on the embedded object in restrict_favorited_by 2019-08-07 00:12:42 +03:00
kaniini dea3bd02ce Merge branch 'issue/1150' into 'develop'
[#1150] fixed parser TwitterCard

See merge request pleroma/pleroma!1537
2019-08-06 20:19:28 +00:00
Maksim 139b196bc0 [#1150] fixed parser TwitterCard 2019-08-06 20:19:28 +00:00
lain e4a01d253e Conversation: Rename function to better express what it does. 2019-08-06 15:06:19 +02:00
lain a49c92f6ae Participation: Setting recipients will always add the owner. 2019-08-06 14:51:17 +02:00
kaniini 486b474327 Merge branch 'test/common_api_utils.ex' into 'develop'
tests for CommonApi/Utils

See merge request pleroma/pleroma!1534
2019-08-05 15:37:05 +00:00
Maksim bdc9a7222c tests for CommonApi/Utils 2019-08-05 15:37:05 +00:00
lain d6fe220e32 Linting. 2019-08-05 16:11:23 +02:00
lain ddabe7784b Pleroma Conversations: Document differences. 2019-08-05 15:58:14 +02:00
lain b64b6fee2a CommonAPI: Replies to conversations also get the correct context id. 2019-08-05 15:33:22 +02:00
lain 3af6d14da7 Pleroma Conversations API: Add a way to set recipients. 2019-08-05 15:09:19 +02:00
Egor Kislitsyn bbd9ed0257 Update CHANGELOG 2019-08-05 15:33:34 +07:00
kaniini fd60fd4713 Merge branch 'report-email-from-remote-user' into 'develop'
Remove Reply-To from report emails

Closes #1141

See merge request pleroma/pleroma!1531
2019-08-04 22:24:50 +00:00
Eugenij 96028cd585 Remove Reply-To from report emails 2019-08-04 22:24:50 +00:00
kaniini f7fc902c29 Merge branch 'feature/relay-list-task' into 'develop'
tasks: relay: add list task

Closes #1101

See merge request pleroma/pleroma!1528
2019-08-04 17:14:37 +00:00
kaniini bedabf7db2 Merge branch 'fix/wrong-next-key-likes-json' into 'develop'
Do not add the "next" key to likes.json if there is no more items

See merge request pleroma/pleroma!1530
2019-08-04 17:13:06 +00:00
Sergey Suprunenko e8ad116c2a Do not add the "next" key to likes.json if there is no more items 2019-08-04 17:13:06 +00:00
kaniini 9e7c633b39 Merge branch 'patch-2' into 'develop'
Replace "impode" with "implode"

See merge request pleroma/pleroma!1532
2019-08-04 17:05:17 +00:00
x0rz3q 3411f506b3 Replace "impode" with "implode" for 2019-08-04 14:35:45 +00:00
kaniini cee0d35566 Merge branch 'remove-longfox-emoji-set' into 'develop'
Remove longfox emoji set

See merge request pleroma/pleroma!1525
2019-08-04 04:32:45 +00:00
Hakaba Hitoyo 39c7bbe18f Remove longfox emoji set 2019-08-04 04:32:45 +00:00
kaniini b9497ead35 Merge branch 'develop' into 'develop'
Fix a stale link and a few typos

See merge request pleroma/pleroma!1529
2019-08-04 04:32:09 +00:00
Pierce McGoran 9b9453ceaf Fix some typos and rework a few lines in howto_mediaproxy.md 2019-08-04 03:12:38 +00:00
Pierce McGoran c10a3e035b Update link in README.md 2019-08-04 03:01:21 +00:00
Ariadne Conill cef3af5536 tasks: relay: add list task 2019-08-03 23:17:17 +00:00
kaniini b1fb5b93ae Merge branch 'bugfix/password-reset-link-styling' into 'develop'
templates/layout/app.html.eex: Style anchors

See merge request pleroma/pleroma!1527
2019-08-03 21:29:39 +00:00
Haelwenn (lanodan) Monnier a035ab8c1d
templates/layout/app.html.eex: Style anchors
[ci skip]
2019-08-03 23:18:22 +02:00
kaniini 86fffe9c27 Merge branch 'patch-2' into 'develop'
Add preferredUsername to service actors so Mastodon can resolve them

See merge request pleroma/pleroma!1526
2019-08-03 18:55:09 +00:00
Thibaut Girka 1fce56c7df Refactor 2019-08-03 18:37:20 +00:00
Thibaut Girka 16cfb89240 Only add `preferredUsername` to service actor json when the underlying user actually has a username 2019-08-03 18:28:08 +00:00
kaniini 4ae0a6652f Merge branch 'admin-fixes' into 'develop'
Admin fixes

See merge request pleroma/pleroma!1524
2019-08-03 18:16:09 +00:00
Alexander Strizhakov de0f3b73dd Admin fixes 2019-08-03 18:16:09 +00:00
kaniini 99ce6d6044 Merge branch 'fix/search-space-error' into 'develop'
Remove spaces from the domain search

Closes #1154

See merge request pleroma/pleroma!1521
2019-08-03 18:13:21 +00:00
Sergey Suprunenko 040347b248 Remove spaces from the domain search 2019-08-03 18:13:20 +00:00
kaniini 7319b41e34 Merge branch 'fix/with-reject-nil-clause' into 'develop'
Handle MRF rejections of incoming AP activities

Closes #1095

See merge request pleroma/pleroma!1520
2019-08-03 18:12:39 +00:00
Sergey Suprunenko 8b2fa31fed Handle MRF rejections of incoming AP activities 2019-08-03 18:12:38 +00:00
kaniini ba72d02f86 Merge branch 'official-docker-support' into 'develop'
Add Dockerfile

See merge request pleroma/pleroma!1523
2019-08-03 18:12:03 +00:00
Ashlynn Anderson 4007717534 Run mix format 2019-08-03 13:42:57 -04:00
Thibaut Girka a187dbb326 Add preferredUsername to service actors so Mastodon can resolve them 2019-08-03 17:24:57 +00:00
Ashlynn Anderson 04327721d7 Add .dockerignore 2019-08-03 00:21:54 -04:00
Ashlynn Anderson c86db959cb Optimize Dockerfile
Just merging RUNs to decrease the number of layers
2019-08-03 00:21:54 -04:00
Ashlynn Anderson 4a418698db Create docker.exs and docker-entrypoint + round out Dockerfile
At this point, the implementation is completely working and has been
tested running live and federating with other instances.
2019-08-03 00:21:54 -04:00
Ashlynn Anderson 7efca4317b Basic working Dockerfile
No fancy script or minit automatic migration, etc, but if you start
the docker image and go in and manually do everything, it works.
2019-08-03 00:21:54 -04:00
Haelwenn e553a17e98 Merge branch 'docfix/mix_task_options' into 'develop'
tasks/pleroma/user.ex: Fix documentation of --max-use and --expire-at

Closes #1155

See merge request pleroma/pleroma!1522
2019-08-02 21:38:28 +00:00
Haelwenn (lanodan) Monnier 8815f07058
tasks/pleroma/user.ex: Fix documentation of --max-use and --expire-at
Closes: https://git.pleroma.social/pleroma/pleroma/issues/1155

[ci skip]
2019-08-02 23:38:02 +02:00
lain eee98aaa73 Pleroma API: Add endpoint to get conversation statuses. 2019-08-02 19:53:08 +02:00
feld 5eec0abe09 Merge branch 'fix/mediaproxy-whitelist-base_url' into 'develop'
Fix/mediaproxy whitelist base url

See merge request pleroma/pleroma!1486
2019-08-02 17:07:09 +00:00
feld d93d777915 Fix/mediaproxy whitelist base url 2019-08-02 17:07:09 +00:00
Roman Chvanikov 9d4f34fbcb Merge branch 'develop' into feature/digest-email 2019-08-02 18:16:04 +03:00
lain 56b1c3af13 CommonAPI: Extend api with conversation replies. 2019-08-02 15:05:27 +02:00
lain 5ff8f07ca9 Merge branch 'feature/hide-follows-remote' into 'develop'
Refactor Follows/Followers counter syncronization and set hide_followers/hide_follows for remote users

See merge request pleroma/pleroma!1411
2019-08-02 11:23:07 +00:00
lain f88560accd Conversations: Add recipient list to conversation participation.
This enables to address the same group of people every time.
2019-08-02 11:55:41 +02:00
lain fd4b7239cd nothing 2019-08-01 17:25:46 +02:00
Egor Kislitsyn 9ca4506355 Add configurable length limits for `User.bio` and `User.name` 2019-08-01 15:53:37 +07:00
rinpatch 301ea0dc04 Add tests for counters being updated on follow 2019-07-31 21:09:13 +03:00
lain 7483679a7b StatusView: Return direct conversation id. 2019-07-31 15:12:29 +02:00
rinpatch f42719506c Fix credo issues 2019-07-31 14:20:34 +03:00
rinpatch c88a5d3251 Merge branch 'develop' into feature/hide-follows-remote 2019-07-31 14:12:29 +03:00
rinpatch 41e0304757 Merge branch 'develop' into feature/hide-follows-remote 2019-07-25 18:43:30 +03:00
Roman Chvanikov d2da3d30f3 Merge branch 'develop' into feature/digest-email 2019-07-24 16:37:52 +03:00
Roman Chvanikov afc7708dbe Fix pleroma_job_queue version 2019-07-21 00:01:58 +03:00
rinpatch 196cad46f3 Resolve merge conflicts 2019-07-20 22:04:47 +03:00
rinpatch c3ecaea64d Apply suggestion to lib/pleroma/object/fetcher.ex 2019-07-20 18:53:00 +00:00
rinpatch d4ee76ab63 Apply suggestion to lib/pleroma/user.ex 2019-07-20 18:52:41 +00:00
Roman Chvanikov 8292331b35 Merge branch 'develop' into feature/digest-email 2019-07-20 16:41:58 +03:00
Roman Chvanikov ae4fc58589 Remove flavour from userinfo 2019-07-20 01:24:01 +03:00
Roman Chvanikov 36049f08ef Merge develop 2019-07-20 01:03:25 +03:00
Roman Chvanikov e7c175c943 Use PleromaJobQueue for scheduling 2019-07-16 16:49:50 +03:00
Roman Chvanikov 9bca70b10a Merge develop 2019-07-16 16:19:19 +03:00
Roman Chvanikov b052a9d4d0 Update DigestEmailWorker to compile and send emails via queue 2019-07-14 22:32:11 +03:00
Roman Chvanikov 168dc97c37 Make opts optional in Pleroma.Notification.for_user_query/2 2019-07-14 22:04:55 +03:00
Roman Chvanikov c729883936 Merge branch 'develop' into feature/digest-email 2019-07-14 21:43:30 +03:00
rinpatch 0c2dcb4c69 Add follow information refetching after following/unfollowing 2019-07-14 01:58:39 +03:00
rinpatch 183da33e00 Add tests for fetch_follow_information_for_user and check object type
when fetching the page
2019-07-14 00:56:02 +03:00
rinpatch d06d1b751d Use atoms when updating user info 2019-07-14 00:21:35 +03:00
rinpatch e5b850a991 Refactor fetching follow information to a separate function 2019-07-13 23:56:10 +03:00
rinpatch e8fa477793 Refactor Follows/Followers counter syncronization
- Actually sync counters in the database instead of info cache (which got
overriden after user update was finished anyway)
- Add following count field to user info
- Set hide_followers/hide_follows for remote users based on http status
codes for the first collection page
2019-07-13 19:27:49 +03:00
Roman Chvanikov 0384459ce5 Update mix.lock 2019-07-12 18:16:54 +03:00
Roman Chvanikov eae991b06a merge develop 2019-07-12 18:08:27 +03:00
Roman Chvanikov 371d39e160 Merge develop 2019-07-09 21:21:09 +03:00
Roman Chvanikov d2cb18b2a3 Merge branch 'develop' into feature/digest-email 2019-06-30 21:23:35 +03:00
Roman Chvanikov 657277ffc0 Resolve conflicts 2019-06-29 00:52:50 +03:00
Roman Chvanikov c0fa000147 Set default config for digest to false 2019-06-07 01:22:35 +03:00
Roman Chvanikov 01fe5abad1 Resolve conflicts 2019-06-07 01:20:50 +03:00
Roman Chvanikov b1b1a270e8 Fix conflict 2019-06-05 02:45:21 +03:00
Roman Chvanikov f6036ce3b9 Fix tests 2019-06-04 03:38:53 +03:00
Roman Chvanikov 7718a215e9 Update changelog 2019-06-04 03:08:00 +03:00
Roman Chvanikov bd325132ca Fix tests 2019-06-04 03:07:49 +03:00
Roman Chvanikov 3e17610587 Add task to test emails 2019-06-04 02:48:21 +03:00
Mark Felder 6ef145b4fc Merge branch 'develop' into feature/digest-email 2019-06-03 15:29:53 -05:00
Roman Chvanikov 5cee2fe9fe Replace Application.get_env/2 with Pleroma.Config.get/1 2019-05-29 21:31:27 +03:00
Roman Chvanikov ce47017c89 Merge develop 2019-05-29 18:18:22 +03:00
Roman Chvanikov f1f7a11222 Merge develop 2019-05-11 14:45:54 +07:00
Roman Chvanikov b6b5b16ba4 Merge develop 2019-05-08 17:08:06 +07:00
Roman Chvanikov 0f0cc2703b Merge develop 2019-04-30 20:17:52 +07:00
Roman Chvanikov b87ad13803 Move comments for email_notifications config to docs 2019-04-21 19:36:31 +07:00
Roman Chvanikov f1d90ee942 Remove debug code 2019-04-21 16:40:05 +07:00
Roman Chvanikov 2359ee38b3 Set digest emails to false by default 2019-04-21 16:36:25 +07:00
Roman Chvanikov 724311e151 Fix Credo warnings 2019-04-20 19:57:43 +07:00
Roman Chvanikov 60f213bb1a Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into feature/digest-email 2019-04-20 19:43:06 +07:00
Roman Chvanikov 05cdb2f238 Do not track coverage files 2019-04-20 19:42:50 +07:00
Roman Chvanikov 64a2c6a041 Digest emails 2019-04-20 19:42:19 +07:00
Roman Chvanikov 73407f4eea Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into feature/digest-email 2019-04-19 23:27:13 +07:00
Roman Chvanikov bc7862106d Fix tests 2019-04-19 23:26:41 +07:00
Roman Chvanikov 8add119444 Add User.list_inactive_users_query/1 2019-04-19 22:19:00 +07:00
Roman Chvanikov aeafa0b2ef Add Notification.for_user_since/2 2019-04-19 22:16:17 +07:00
Roman Chvanikov 2f0203a4a1 Resolve conflicts 2019-04-17 16:59:05 +07:00
Roman Chvanikov 87013f8438 Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into feature/digest-email 2019-04-17 16:58:08 +07:00
Roman Chvanikov dc21181f65 Update updated_at field on notification read 2019-04-14 22:29:05 +07:00
Roman Chvanikov 0cd4b6024d Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into feature/digest-email 2019-04-14 14:05:21 +07:00
Roman Chvanikov 371a4aed2c Add User.Info.email_notifications 2019-04-13 17:40:42 +07:00
204 changed files with 4524 additions and 842 deletions

12
.dockerignore Normal file
View File

@ -0,0 +1,12 @@
.*
*.md
AGPL-3
CC-BY-SA-4.0
COPYING
*file
elixir_buildpack.config
docs/
test/
# Required to get version
!.git

View File

@ -21,10 +21,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Fixed
- Not being able to pin unlisted posts
- Objects being re-embedded to activities after being updated (e.g faved/reposted). Running 'mix pleroma.database prune_objects' again is advised.
- Metadata rendering errors resulting in the entire page being inaccessible
- `federation_incoming_replies_max_depth` option being ignored in certain cases
- Federation/MediaProxy not working with instances that have wrong certificate order
- Mastodon API: Handling of search timeouts (`/api/v1/search` and `/api/v2/search`)
- Mastodon API: Embedded relationships not being properly rendered in the Account entity of Status entity
- Mastodon API: follower/following counters not being nullified, when `hide_follows`/`hide_followers` is set
- Mastodon API: `muted` in the Status entity, using author's account to determine if the tread was muted
- Mastodon API: Add `account_id`, `type`, `offset`, and `limit` to search API (`/api/v1/search` and `/api/v2/search`)
- Mastodon API, streaming: Fix filtering of notifications based on blocks/mutes/thread mutes
- ActivityPub C2S: follower/following collection pages being inaccessible even when authentifucated if `hide_followers`/ `hide_follows` was set
@ -33,13 +37,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Rich Media: The crawled URL is now spliced into the rich media data.
- ActivityPub S2S: sharedInbox usage has been mostly aligned with the rules in the AP specification.
- ActivityPub S2S: remote user deletions now work the same as local user deletions.
- ActivityPub S2S: POST requests are now signed with `(request-target)` pseudo-header.
- Not being able to access the Mastodon FE login page on private instances
- Invalid SemVer version generation, when the current branch does not have commits ahead of tag/checked out on a tag
- Pleroma.Upload base_url was not automatically whitelisted by MediaProxy. Now your custom CDN or file hosting will be accessed directly as expected.
- Report email not being sent to admins when the reporter is a remote user
- MRF: ensure that subdomain_match calls are case-insensitive
- MRF: fix use of unserializable keyword lists in describe() implementations
### Added
- Conversations: Add Pleroma-specific conversation endpoints and status posting extensions. Run the `bump_all_conversations` task again to create the necessary data.
- **Breaking:** MRF describe API, which adds support for exposing configuration information about MRF policies to NodeInfo.
Custom modules will need to be updated by adding, at the very least, `def describe, do: {:ok, %{}}` to the MRF policy modules.
- MRF: Support for priming the mediaproxy cache (`Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`)
- MRF: Support for excluding specific domains from Transparency.
- MRF: Support for filtering posts based on who they mention (`Pleroma.Web.ActivityPub.MRF.MentionPolicy`)
- MRF: Support for filtering posts based on ActivityStreams vocabulary (`Pleroma.Web.ActivityPub.MRF.VocabularyPolicy`)
- MRF (Simple Policy): Support for wildcard domains.
- Support for wildcard domains in user domain blocks setting.
- Configuration: `quarantined_instances` support wildcard domains.
@ -62,12 +75,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Added synchronization of following/followers counters for external users
- Configuration: `enabled` option for `Pleroma.Emails.Mailer`, defaulting to `false`.
- Configuration: Pleroma.Plugs.RateLimiter `bucket_name`, `params` options.
- Configuration: `user_bio_length` and `user_name_length` options.
- Addressable lists
- Twitter API: added rate limit for `/api/account/password_reset` endpoint.
- ActivityPub: Add an internal service actor for fetching ActivityPub objects.
- ActivityPub: Optional signing of ActivityPub object fetches.
- Admin API: Endpoint for fetching latest user's statuses
- Pleroma API: Add `/api/v1/pleroma/accounts/confirmation_resend?email=<email>` for resending account confirmation.
- Relays: Added a task to list relay subscriptions.
- Mix Tasks: `mix pleroma.database fix_likes_collections`
- Federation: Remove `likes` from objects.
### Changed
- Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text
@ -75,6 +92,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- RichMedia: parsers and their order are configured in `rich_media` config.
- RichMedia: add the rich media ttl based on image expiration time.
### Removed
- Emoji: Remove longfox emojis.
- Remove `Reply-To` header from report emails for admins.
- ActivityPub: The `accept_blocks` configuration setting.
## [1.0.1] - 2019-07-14
### Security
- OStatus: fix an object spoofing vulnerability.
@ -85,6 +107,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Rich media: Do not crawl private IP ranges
### Added
- Digest email for inactive users
- Add a generic settings store for frontends / clients to use.
- Explicit addressing option for posting.
- Optional SSH access mode. (Needs `erlang-ssh` package on some distributions).
@ -111,6 +134,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Configuration: `notify_email` option
- Configuration: Media proxy `whitelist` option
- Configuration: `report_uri` option
- Configuration: `email_notifications` option
- Configuration: `limit_to_local_content` option
- Pleroma API: User subscriptions
- Pleroma API: Healthcheck endpoint

39
Dockerfile Normal file
View File

@ -0,0 +1,39 @@
FROM rinpatch/elixir:1.9.0-rc.0-alpine as build
COPY . .
ENV MIX_ENV=prod
RUN apk add git gcc g++ musl-dev make &&\
echo "import Mix.Config" > config/prod.secret.exs &&\
mix local.hex --force &&\
mix local.rebar --force &&\
mix deps.get --only prod &&\
mkdir release &&\
mix release --path release
FROM alpine:latest
ARG HOME=/opt/pleroma
ARG DATA=/var/lib/pleroma
RUN echo "http://nl.alpinelinux.org/alpine/latest-stable/community" >> /etc/apk/repositories &&\
apk update &&\
apk add ncurses postgresql-client &&\
adduser --system --shell /bin/false --home ${HOME} pleroma &&\
mkdir -p ${DATA}/uploads &&\
mkdir -p ${DATA}/static &&\
chown -R pleroma ${DATA} &&\
mkdir -p /etc/pleroma &&\
chown -R pleroma /etc/pleroma
USER pleroma
COPY --from=build --chown=pleroma:0 /release ${HOME}
COPY ./config/docker.exs /etc/pleroma/config.exs
COPY ./docker-entrypoint.sh ${HOME}
EXPOSE 4000
ENTRYPOINT ["/opt/pleroma/docker-entrypoint.sh"]

View File

@ -25,7 +25,7 @@ If you want to run your own server, feel free to contact us at @lain@pleroma.soy
Currently Pleroma is not packaged by any OS/Distros, but feel free to reach out to us at [#pleroma-dev on freenode](https://webchat.freenode.net/?channels=%23pleroma-dev) or via matrix at <https://matrix.heldscal.la/#/room/#freenode_#pleroma-dev:matrix.org> for assistance. If you want to change default options in your Pleroma package, please **discuss it with us first**.
### Docker
While we dont provide docker files, other people have written very good ones. Take a look at <https://github.com/angristan/docker-pleroma> or <https://github.com/sn0w/pleroma-docker>.
While we dont provide docker files, other people have written very good ones. Take a look at <https://github.com/angristan/docker-pleroma> or <https://glitch.sh/sn0w/pleroma-docker>.
### Dependencies

View File

@ -253,6 +253,8 @@ config :pleroma, :instance,
skip_thread_containment: true,
limit_to_local_content: :unauthenticated,
dynamic_configuration: false,
user_bio_length: 5000,
user_name_length: 100,
external_user_synchronization: true
config :pleroma, :markup,
@ -302,7 +304,6 @@ config :pleroma, :assets,
default_mascot: :pleroma_fox_tan
config :pleroma, :activitypub,
accept_blocks: true,
unfollow_blocked: true,
outgoing_blocks: true,
follow_handshake_timeout: 500,
@ -337,6 +338,10 @@ config :pleroma, :mrf_keyword,
config :pleroma, :mrf_subchain, match_actor: %{}
config :pleroma, :mrf_vocabulary,
accept: [],
reject: []
config :pleroma, :rich_media,
enabled: true,
ignore_hosts: [],
@ -514,6 +519,14 @@ config :pleroma, Pleroma.ScheduledActivity,
total_user_limit: 300,
enabled: true
config :pleroma, :email_notifications,
digest: %{
active: false,
schedule: "0 0 * * 0",
interval: 7,
inactivity_threshold: 7
}
config :pleroma, :oauth2,
token_expires_in: 600,
issue_new_refresh_token: true,

68
config/docker.exs Normal file
View File

@ -0,0 +1,68 @@
import Config
config :pleroma, Pleroma.Web.Endpoint,
url: [host: System.get_env("DOMAIN", "localhost"), scheme: "https", port: 443],
http: [ip: {0, 0, 0, 0}, port: 4000]
config :pleroma, :instance,
name: System.get_env("INSTANCE_NAME", "Pleroma"),
email: System.get_env("ADMIN_EMAIL"),
notify_email: System.get_env("NOTIFY_EMAIL"),
limit: 5000,
registrations_open: false,
dynamic_configuration: true
config :pleroma, Pleroma.Repo,
adapter: Ecto.Adapters.Postgres,
username: System.get_env("DB_USER", "pleroma"),
password: System.fetch_env!("DB_PASS"),
database: System.get_env("DB_NAME", "pleroma"),
hostname: System.get_env("DB_HOST", "db"),
pool_size: 10
# Configure web push notifications
config :web_push_encryption, :vapid_details, subject: "mailto:#{System.get_env("NOTIFY_EMAIL")}"
config :pleroma, :database, rum_enabled: false
config :pleroma, :instance, static_dir: "/var/lib/pleroma/static"
config :pleroma, Pleroma.Uploaders.Local, uploads: "/var/lib/pleroma/uploads"
# We can't store the secrets in this file, since this is baked into the docker image
if not File.exists?("/var/lib/pleroma/secret.exs") do
secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
signing_salt = :crypto.strong_rand_bytes(8) |> Base.encode64() |> binary_part(0, 8)
{web_push_public_key, web_push_private_key} = :crypto.generate_key(:ecdh, :prime256v1)
secret_file =
EEx.eval_string(
"""
import Config
config :pleroma, Pleroma.Web.Endpoint,
secret_key_base: "<%= secret %>",
signing_salt: "<%= signing_salt %>"
config :web_push_encryption, :vapid_details,
public_key: "<%= web_push_public_key %>",
private_key: "<%= web_push_private_key %>"
""",
secret: secret,
signing_salt: signing_salt,
web_push_public_key: Base.url_encode64(web_push_public_key, padding: false),
web_push_private_key: Base.url_encode64(web_push_private_key, padding: false)
)
File.write("/var/lib/pleroma/secret.exs", secret_file)
end
import_config("/var/lib/pleroma/secret.exs")
# For additional user config
if File.exists?("/var/lib/pleroma/config.exs"),
do: import_config("/var/lib/pleroma/config.exs"),
else:
File.write("/var/lib/pleroma/config.exs", """
import Config
# For additional configuration outside of environmental variables
""")

View File

@ -1,30 +1,2 @@
firefox, /emoji/Firefox.gif, Gif,Fun
blank, /emoji/blank.png, Fun
f_00b, /emoji/f_00b.png
f_00b11b, /emoji/f_00b11b.png
f_00b33b, /emoji/f_00b33b.png
f_00h, /emoji/f_00h.png
f_00t, /emoji/f_00t.png
f_01b, /emoji/f_01b.png
f_03b, /emoji/f_03b.png
f_10b, /emoji/f_10b.png
f_11b, /emoji/f_11b.png
f_11b00b, /emoji/f_11b00b.png
f_11b22b, /emoji/f_11b22b.png
f_11h, /emoji/f_11h.png
f_11t, /emoji/f_11t.png
f_12b, /emoji/f_12b.png
f_21b, /emoji/f_21b.png
f_22b, /emoji/f_22b.png
f_22b11b, /emoji/f_22b11b.png
f_22b33b, /emoji/f_22b33b.png
f_22h, /emoji/f_22h.png
f_22t, /emoji/f_22t.png
f_23b, /emoji/f_23b.png
f_30b, /emoji/f_30b.png
f_32b, /emoji/f_32b.png
f_33b, /emoji/f_33b.png
f_33b00b, /emoji/f_33b00b.png
f_33b22b, /emoji/f_33b22b.png
f_33h, /emoji/f_33h.png
f_33t, /emoji/f_33t.png

View File

@ -35,7 +35,8 @@ config :pleroma, :instance,
email: "admin@example.com",
notify_email: "noreply@example.com",
skip_thread_containment: false,
federating: false
federating: false,
external_user_synchronization: false
config :pleroma, :activitypub, sign_object_fetches: false
@ -86,6 +87,8 @@ rum_enabled = System.get_env("RUM_ENABLED") == "true"
config :pleroma, :database, rum_enabled: rum_enabled
IO.puts("RUM enabled: #{rum_enabled}")
config :joken, default_signer: "yU8uHKq+yyAkZ11Hx//jcdacWc8yQ1bxAAGrplzB0Zwwjkp35v0RK9SO8WTPr6QZ"
config :pleroma, Pleroma.ReverseProxy.Client, Pleroma.ReverseProxy.ClientMock
try do

14
docker-entrypoint.sh Executable file
View File

@ -0,0 +1,14 @@
#!/bin/ash
set -e
echo "-- Waiting for database..."
while ! pg_isready -U ${DB_USER:-pleroma} -d postgres://${DB_HOST:-db}:5432/${DB_NAME:-pleroma} -t 1; do
sleep 1s
done
echo "-- Running migrations..."
$HOME/bin/pleroma_ctl migrate
echo "-- Starting!"
exec $HOME/bin/pleroma start

View File

@ -627,6 +627,9 @@ Tuples can be passed as `{"tuple": ["first_val", Pleroma.Module, []]}`.
Keywords can be passed as lists with 2 child tuples, e.g.
`[{"tuple": ["first_val", Pleroma.Module]}, {"tuple": ["second_val", true]}]`.
If value contains list of settings `[subkey: val1, subkey2: val2, subkey3: val3]`, it's possible to remove only subkeys instead of all settings passing `subkeys` parameter. E.g.:
{"group": "pleroma", "key": "some_key", "delete": "true", "subkeys": [":subkey", ":subkey3"]}.
Compile time settings (need instance reboot):
- all settings by this keys:
- `:hackney_pools`
@ -645,6 +648,7 @@ Compile time settings (need instance reboot):
- `key` (string or string with leading `:` for atoms)
- `value` (string, [], {} or {"tuple": []})
- `delete` = true (optional, if parameter must be deleted)
- `subkeys` [(string with leading `:` for atoms)] (optional, works only if `delete=true` parameter is passed, otherwise will be ignored)
]
- Request (example):

View File

@ -59,12 +59,19 @@ 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
## Conversations
Has an additional field under the `pleroma` object:
- `recipients`: The list of the recipients of this Conversation. These will be addressed when replying to this conversation.
## Account Search
Behavior has changed:
- `/api/v1/accounts/search`: Does not require authentication
## Notifications
Has these additional fields under the `pleroma` object:
@ -79,6 +86,7 @@ Additional parameters can be added to the JSON body/Form data:
- `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.
- `to`: A list of nicknames (like `lain@soykaf.club` or `lain` on the local server) that will be used to determine who is going to be addressed by this post. Using this will disable the implicit addressing by mentioned names in the `status` body, only the people in the `to` list will be addressed. The normal rules for for post visibility are not affected by this and will still apply.
- `visibility`: string, besides standard MastoAPI values (`direct`, `private`, `unlisted` or `public`) it can be used to address a List by setting it to `list:LIST_ID`.
- `in_reply_to_conversation_id`: Will reply to a given conversation, addressing only the people who are part of the recipient set of that conversation. Sets the visibility to `direct`.
## PATCH `/api/v1/update_credentials`

View File

@ -319,3 +319,38 @@ See [Admin-API](Admin-API.md)
"healthy": true # Instance state
}
```
# Pleroma Conversations
Pleroma Conversations have the same general structure that Mastodon Conversations have. The behavior differs in the following ways when using these endpoints:
1. Pleroma Conversations never add or remove recipients, unless explicitly changed by the user.
2. Pleroma Conversations statuses can be requested by Conversation id.
3. Pleroma Conversations can be replied to.
Conversations have the additional field "recipients" under the "pleroma" key. This holds a list of all the accounts that will receive a message in this conversation.
The status posting endpoint takes an additional parameter, `in_reply_to_conversation_id`, which, when set, will set the visiblity to direct and address only the people who are the recipients of that Conversation.
## `GET /api/v1/pleroma/conversations/:id/statuses`
### Timeline for a given conversation
* Method `GET`
* Authentication: required
* Params: Like other timelines
* Response: JSON, statuses (200 - healthy, 503 unhealthy).
## `GET /api/v1/pleroma/conversations/:id`
### The conversation with the given ID.
* Method `GET`
* Authentication: required
* Params: None
* Response: JSON, statuses (200 - healthy, 503 unhealthy).
## `PATCH /api/v1/pleroma/conversations/:id`
### Update a conversation. Used to change the set of recipients.
* Method `PATCH`
* Authentication: required
* Params:
* `recipients`: A list of ids of users that should receive posts to this conversation. This will replace the current list of recipients, so submit the full list. The owner of owner of the conversation will always be part of the set of recipients, though.
* Response: JSON, statuses (200 - healthy, 503 unhealthy)

View File

@ -18,6 +18,7 @@ Note: `strip_exif` has been replaced by `Pleroma.Upload.Filter.Mogrify`.
## Pleroma.Uploaders.S3
* `bucket`: S3 bucket name
* `bucket_namespace`: S3 bucket namespace
* `public_endpoint`: S3 endpoint that the user finally accesses(ex. "https://s3.dualstack.ap-northeast-1.amazonaws.com")
* `truncated_namespace`: If you use S3 compatible service such as Digital Ocean Spaces or CDN, set folder name or "" etc.
For example, when using CDN to S3 virtual host format, set "".
@ -25,7 +26,7 @@ At this time, write CNAME to CDN in public_endpoint.
## Pleroma.Upload.Filter.Mogrify
* `args`: List of actions for the `mogrify` command like `"strip"` or `["strip", "auto-orient", {"impode", "1"}]`.
* `args`: List of actions for the `mogrify` command like `"strip"` or `["strip", "auto-orient", {"implode", "1"}]`.
## Pleroma.Upload.Filter.Dedupe
@ -102,6 +103,7 @@ config :pleroma, Pleroma.Emails.Mailer,
* `Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy`: Rejects posts from likely spambots by rejecting posts from new users that contain links.
* `Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`: Crawls attachments using their MediaProxy URLs so that the MediaProxy cache is primed.
* `Pleroma.Web.ActivityPub.MRF.MentionPolicy`: Drops posts mentioning configurable users. (see `:mrf_mention` section)
* `Pleroma.Web.ActivityPub.MRF.VocabularyPolicy`: Restricts activities to a configured set of vocabulary. (see `:mrf_vocabulary` section)
* `public`: Makes the client API in authentificated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network.
* `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``
@ -125,6 +127,8 @@ config :pleroma, Pleroma.Emails.Mailer,
* `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``.
* `remote_post_retention_days`: The default amount of days to retain remote posts when pruning the database.
* `user_bio_length`: A user bio maximum length (default: `5000`)
* `user_name_length`: A user name maximum length (default: `100`)
* `skip_thread_containment`: Skip filter out broken threads. The default is `false`.
* `limit_to_local_content`: Limit unauthenticated users to search for local statutes and users only. Possible values: `:unauthenticated`, `:all` and `false`. The default is `:unauthenticated`.
* `dynamic_configuration`: Allow transferring configuration to DB with the subsequent customization from Admin api.
@ -275,6 +279,10 @@ config :pleroma, :mrf_subchain,
## :mrf_mention
* `actors`: A list of actors, for which to drop any posts mentioning.
## :mrf_vocabulary
* `accept`: A list of ActivityStreams terms to accept. If empty, all supported messages are accepted.
* `reject`: A list of ActivityStreams terms to reject. If empty, no messages are rejected.
## :media_proxy
* `enabled`: Enables proxying of remote media to the instances 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.
@ -328,7 +336,6 @@ config :pleroma, Pleroma.Web.Endpoint,
This will make Pleroma listen on `127.0.0.1` port `8080` and generate urls starting with `https://example.com:2020`
## :activitypub
* ``accept_blocks``: Whether to accept incoming block activities from other instances
* ``unfollow_blocked``: Whether blocks result in people getting unfollowed
* ``outgoing_blocks``: Whether to federate blocks to other instances
* ``deny_follow_blocked``: Whether to disallow following an account that has blocked the user in question
@ -536,6 +543,18 @@ Authentication / authorization settings.
* `oauth_consumer_template`: OAuth consumer mode authentication form template. By default it's `consumer.html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex`.
* `oauth_consumer_strategies`: the list of enabled OAuth consumer strategies; by default it's set by `OAUTH_CONSUMER_STRATEGIES` environment variable. Each entry in this space-delimited string should be of format `<strategy>` or `<strategy>:<dependency>` (e.g. `twitter` or `keycloak:ueberauth_keycloak_strategy` in case dependency is named differently than `ueberauth_<strategy>`).
## :email_notifications
Email notifications settings.
- digest - emails of "what you've missed" for users who have been
inactive for a while.
- active: globally enable or disable digest emails
- schedule: When to send digest email, in [crontab format](https://en.wikipedia.org/wiki/Cron).
"0 0 * * 0" is the default, meaning "once a week at midnight on Sunday morning"
- interval: Minimum interval between digest emails to one user
- inactivity_threshold: Minimum user inactivity threshold
## OAuth consumer mode
OAuth consumer mode allows sign in / sign up via external OAuth providers (e.g. Twitter, Facebook, Google, Microsoft, etc.).

View File

@ -1,8 +1,8 @@
# How to activate mediaproxy
## Explanation
Without the `mediaproxy` function, Pleroma don't store any remote content like pictures, video etc. locally. So every time you open Pleroma, the content is loaded from the source server, from where the post is coming. This can result in slowly loading content or/and increased bandwidth usage on the source server.
With the `mediaproxy` function you can use the cache ability of nginx, to cache these content, so user can access it faster, cause it's loaded from your server.
Without the `mediaproxy` function, Pleroma doesn't store any remote content like pictures, video etc. locally. So every time you open Pleroma, the content is loaded from the source server, from where the post is coming. This can result in slowly loading content or/and increased bandwidth usage on the source server.
With the `mediaproxy` function you can use nginx to cache this content, so users can access it faster, because it's loaded from your server.
## Activate it

View File

@ -26,4 +26,48 @@ defmodule Mix.Tasks.Pleroma.Benchmark do
end
})
end
def run(["render_timeline", nickname]) do
start_pleroma()
user = Pleroma.User.get_by_nickname(nickname)
activities =
%{}
|> Map.put("type", ["Create", "Announce"])
|> Map.put("blocking_user", user)
|> Map.put("muting_user", user)
|> Map.put("user", user)
|> Map.put("limit", 80)
|> Pleroma.Web.ActivityPub.ActivityPub.fetch_public_activities()
|> Enum.reverse()
inputs = %{
"One activity" => Enum.take_random(activities, 1),
"Ten activities" => Enum.take_random(activities, 10),
"Twenty activities" => Enum.take_random(activities, 20),
"Forty activities" => Enum.take_random(activities, 40),
"Eighty activities" => Enum.take_random(activities, 80)
}
Benchee.run(
%{
"Parallel rendering" => fn activities ->
Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{
activities: activities,
for: user,
as: :activity
})
end,
"Standart rendering" => fn activities ->
Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{
activities: activities,
for: user,
as: :activity,
parallel: false
})
end
},
inputs: inputs
)
end
end

View File

@ -36,6 +36,10 @@ defmodule Mix.Tasks.Pleroma.Database do
## Remove duplicated items from following and update followers count for all users
mix pleroma.database update_users_following_followers_counts
## Fix the pre-existing "likes" collections for all objects
mix pleroma.database fix_likes_collections
"""
def run(["remove_embedded_objects" | args]) do
{options, [], []} =
@ -125,4 +129,36 @@ defmodule Mix.Tasks.Pleroma.Database do
)
end
end
def run(["fix_likes_collections"]) do
import Ecto.Query
start_pleroma()
from(object in Object,
where: fragment("(?)->>'likes' is not null", object.data),
select: %{id: object.id, likes: fragment("(?)->>'likes'", object.data)}
)
|> Pleroma.RepoStreamer.chunk_stream(100)
|> Stream.each(fn objects ->
ids =
objects
|> Enum.filter(fn object -> object.likes |> Jason.decode!() |> is_map() end)
|> Enum.map(& &1.id)
Object
|> where([object], object.id in ^ids)
|> update([object],
set: [
data:
fragment(
"jsonb_set(?, '{likes}', '[]'::jsonb, true)",
object.data
)
]
)
|> Repo.update_all([], timeout: :infinity)
end)
|> Stream.run()
end
end

View File

@ -0,0 +1,33 @@
defmodule Mix.Tasks.Pleroma.Digest do
use Mix.Task
@shortdoc "Manages digest emails"
@moduledoc """
Manages digest emails
## Send digest email since given date (user registration date by default)
ignoring user activity status.
``mix pleroma.digest test <nickname> <since_date>``
Example: ``mix pleroma.digest test donaldtheduck 2019-05-20``
"""
def run(["test", nickname | opts]) do
Mix.Pleroma.start_pleroma()
user = Pleroma.User.get_by_nickname(nickname)
last_digest_emailed_at =
with [date] <- opts,
{:ok, datetime} <- Timex.parse(date, "{YYYY}-{0M}-{0D}") do
datetime
else
_ -> user.inserted_at
end
patched_user = %{user | last_digest_emailed_at: last_digest_emailed_at}
_user = Pleroma.DigestEmailWorker.perform(patched_user)
Mix.shell().info("Digest email have been sent to #{nickname} (#{user.email})")
end
end

View File

@ -183,6 +183,7 @@ defmodule Mix.Tasks.Pleroma.Instance do
)
secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
jwt_secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
signing_salt = :crypto.strong_rand_bytes(8) |> Base.encode64() |> binary_part(0, 8)
{web_push_public_key, web_push_private_key} = :crypto.generate_key(:ecdh, :prime256v1)
template_dir = Application.app_dir(:pleroma, "priv") <> "/templates"
@ -200,6 +201,7 @@ defmodule Mix.Tasks.Pleroma.Instance do
dbuser: dbuser,
dbpass: dbpass,
secret: secret,
jwt_secret: jwt_secret,
signing_salt: signing_salt,
web_push_public_key: Base.url_encode64(web_push_public_key, padding: false),
web_push_private_key: Base.url_encode64(web_push_private_key, padding: false),

View File

@ -5,6 +5,7 @@
defmodule Mix.Tasks.Pleroma.Relay do
use Mix.Task
import Mix.Pleroma
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Relay
@shortdoc "Manages remote relays"
@ -22,6 +23,10 @@ defmodule Mix.Tasks.Pleroma.Relay do
``mix pleroma.relay unfollow <relay_url>``
Example: ``mix pleroma.relay unfollow https://example.org/relay``
## List relay subscriptions
``mix pleroma.relay list``
"""
def run(["follow", target]) do
start_pleroma()
@ -44,4 +49,19 @@ defmodule Mix.Tasks.Pleroma.Relay do
{:error, e} -> shell_error("Error while following #{target}: #{inspect(e)}")
end
end
def run(["list"]) do
start_pleroma()
with %User{} = user <- Relay.get_actor() do
user.following
|> Enum.each(fn entry ->
URI.parse(entry)
|> Map.get(:host)
|> shell_info()
end)
else
e -> shell_error("Error while fetching relay subscription list: #{inspect(e)}")
end
end
end

View File

@ -31,8 +31,8 @@ defmodule Mix.Tasks.Pleroma.User do
mix pleroma.user invite [OPTION...]
Options:
- `--expires_at DATE` - last day on which token is active (e.g. "2019-04-05")
- `--max_use NUMBER` - maximum numbers of token uses
- `--expires-at DATE` - last day on which token is active (e.g. "2019-04-05")
- `--max-use NUMBER` - maximum numbers of token uses
## List generated invites

View File

@ -96,6 +96,7 @@ defmodule Pleroma.Activity do
from([a] in query,
left_join: tm in ThreadMute,
on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data),
as: :thread_mute,
select: %Activity{a | thread_muted?: not is_nil(tm.id)}
)
end
@ -224,6 +225,29 @@ defmodule Pleroma.Activity do
def get_create_by_object_ap_id(_), do: nil
def create_by_object_ap_id_with_object(ap_ids) when is_list(ap_ids) do
from(
activity in Activity,
where:
fragment(
"coalesce((?)->'object'->>'id', (?)->>'object') = ANY(?)",
activity.data,
activity.data,
^ap_ids
),
where: fragment("(?)->>'type' = 'Create'", activity.data),
inner_join: o in Object,
on:
fragment(
"(?->>'id') = COALESCE(?->'object'->>'id', ?->>'object')",
o.data,
activity.data,
activity.data
),
preload: [object: o]
)
end
def create_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do
from(
activity in Activity,
@ -263,8 +287,8 @@ defmodule Pleroma.Activity do
defp get_in_reply_to_activity_from_object(_), do: nil
def get_in_reply_to_activity(%Activity{data: %{"object" => object}}) do
get_in_reply_to_activity_from_object(Object.normalize(object))
def get_in_reply_to_activity(%Activity{} = activity) do
get_in_reply_to_activity_from_object(Object.normalize(activity))
end
def normalize(obj) when is_map(obj), do: get_by_ap_id_with_object(obj["id"])

View File

@ -3,11 +3,14 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Application do
import Cachex.Spec
use Application
@name Mix.Project.config()[:name]
@version Mix.Project.config()[:version]
@repository Mix.Project.config()[:source_url]
@env Mix.env()
def name, do: @name
def version, do: @version
def named_version, do: @name <> " " <> @version
@ -21,116 +24,24 @@ defmodule Pleroma.Application do
# See http://elixir-lang.org/docs/stable/elixir/Application.html
# for more information on OTP Applications
def start(_type, _args) do
import Cachex.Spec
Pleroma.Config.DeprecationWarnings.warn()
setup_instrumenters()
# Define workers and child supervisors to be supervised
children =
[
# Start the Ecto repository
%{id: Pleroma.Repo, start: {Pleroma.Repo, :start_link, []}, type: :supervisor},
%{id: Pleroma.Config.TransferTask, start: {Pleroma.Config.TransferTask, :start_link, []}},
%{id: Pleroma.Emoji, start: {Pleroma.Emoji, :start_link, []}},
%{id: Pleroma.Captcha, start: {Pleroma.Captcha, :start_link, []}},
%{
id: :cachex_used_captcha_cache,
start:
{Cachex, :start_link,
[
:used_captcha_cache,
[
ttl_interval:
:timer.seconds(Pleroma.Config.get!([Pleroma.Captcha, :seconds_valid]))
]
]}
},
%{
id: :cachex_user,
start:
{Cachex, :start_link,
[
:user_cache,
[
default_ttl: 25_000,
ttl_interval: 1000,
limit: 2500
]
]}
},
%{
id: :cachex_object,
start:
{Cachex, :start_link,
[
:object_cache,
[
default_ttl: 25_000,
ttl_interval: 1000,
limit: 2500
]
]}
},
%{
id: :cachex_rich_media,
start:
{Cachex, :start_link,
[
:rich_media_cache,
[
default_ttl: :timer.minutes(120),
limit: 5000
]
]}
},
%{
id: :cachex_scrubber,
start:
{Cachex, :start_link,
[
:scrubber_cache,
[
limit: 2500
]
]}
},
%{
id: :cachex_idem,
start:
{Cachex, :start_link,
[
:idempotency_cache,
[
expiration:
expiration(
default: :timer.seconds(6 * 60 * 60),
interval: :timer.seconds(60)
),
limit: 2500
]
]}
},
%{id: Pleroma.FlakeId, start: {Pleroma.FlakeId, :start_link, []}},
%{
id: Pleroma.ScheduledActivityWorker,
start: {Pleroma.ScheduledActivityWorker, :start_link, []}
}
Pleroma.Repo,
Pleroma.Config.TransferTask,
Pleroma.Emoji,
Pleroma.Captcha,
Pleroma.FlakeId,
Pleroma.ScheduledActivityWorker
] ++
cachex_children() ++
hackney_pool_children() ++
[
%{
id: Pleroma.Web.Federator.RetryQueue,
start: {Pleroma.Web.Federator.RetryQueue, :start_link, []}
},
%{
id: Pleroma.Web.OAuth.Token.CleanWorker,
start: {Pleroma.Web.OAuth.Token.CleanWorker, :start_link, []}
},
%{
id: Pleroma.Stats,
start: {Pleroma.Stats, :start_link, []}
},
Pleroma.Web.Federator.RetryQueue,
Pleroma.Stats,
%{
id: :web_push_init,
start: {Task, :start_link, [&Pleroma.Web.Push.init/0]},
@ -147,22 +58,20 @@ defmodule Pleroma.Application do
restart: :temporary
}
] ++
streamer_child() ++
chat_child() ++
oauth_cleanup_child(oauth_cleanup_enabled?()) ++
streamer_child(@env) ++
chat_child(@env, chat_enabled?()) ++
[
# Start the endpoint when the application starts
%{
id: Pleroma.Web.Endpoint,
start: {Pleroma.Web.Endpoint, :start_link, []},
type: :supervisor
},
%{id: Pleroma.Gopher.Server, start: {Pleroma.Gopher.Server, :start_link, []}}
Pleroma.Web.Endpoint,
Pleroma.Gopher.Server
]
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: Pleroma.Supervisor]
Supervisor.start_link(children, opts)
result = Supervisor.start_link(children, opts)
:ok = after_supervisor_start()
result
end
defp setup_instrumenters do
@ -199,32 +108,71 @@ defmodule Pleroma.Application do
end
end
if Pleroma.Config.get(:env) == :test do
defp streamer_child, do: []
defp chat_child, do: []
else
defp streamer_child do
[%{id: Pleroma.Web.Streamer, start: {Pleroma.Web.Streamer, :start_link, []}}]
end
defp chat_child do
if Pleroma.Config.get([:chat, :enabled]) do
[
%{
id: Pleroma.Web.ChatChannel.ChatChannelState,
start: {Pleroma.Web.ChatChannel.ChatChannelState, :start_link, []}
}
]
else
[]
end
end
defp cachex_children do
[
build_cachex("used_captcha", ttl_interval: seconds_valid_interval()),
build_cachex("user", default_ttl: 25_000, ttl_interval: 1000, limit: 2500),
build_cachex("object", default_ttl: 25_000, ttl_interval: 1000, limit: 2500),
build_cachex("rich_media", default_ttl: :timer.minutes(120), limit: 5000),
build_cachex("scrubber", limit: 2500),
build_cachex("idempotency", expiration: idempotency_expiration(), limit: 2500)
]
end
defp idempotency_expiration,
do: expiration(default: :timer.seconds(6 * 60 * 60), interval: :timer.seconds(60))
defp seconds_valid_interval,
do: :timer.seconds(Pleroma.Config.get!([Pleroma.Captcha, :seconds_valid]))
defp build_cachex(type, opts),
do: %{
id: String.to_atom("cachex_" <> type),
start: {Cachex, :start_link, [String.to_atom(type <> "_cache"), opts]},
type: :worker
}
defp chat_enabled?, do: Pleroma.Config.get([:chat, :enabled])
defp oauth_cleanup_enabled?,
do: Pleroma.Config.get([:oauth2, :clean_expired_tokens], false)
defp streamer_child(:test), do: []
defp streamer_child(_) do
[Pleroma.Web.Streamer]
end
defp oauth_cleanup_child(true),
do: [Pleroma.Web.OAuth.Token.CleanWorker]
defp oauth_cleanup_child(_), do: []
defp chat_child(:test, _), do: []
defp chat_child(_env, true) do
[Pleroma.Web.ChatChannel.ChatChannelState]
end
defp chat_child(_, _), do: []
defp hackney_pool_children do
for pool <- enabled_hackney_pools() do
options = Pleroma.Config.get([:hackney_pools, pool])
:hackney_pool.child_spec(pool, options)
end
end
defp after_supervisor_start do
with digest_config <- Application.get_env(:pleroma, :email_notifications)[:digest],
true <- digest_config[:active] do
PleromaJobQueue.schedule(
digest_config[:schedule],
:digest_emails,
Pleroma.DigestEmailWorker
)
end
:ok
end
end

View File

@ -12,7 +12,7 @@ defmodule Pleroma.Captcha do
use GenServer
@doc false
def start_link do
def start_link(_) do
GenServer.start_link(__MODULE__, [], name: __MODULE__)
end

View File

@ -6,7 +6,7 @@ defmodule Pleroma.Config.TransferTask do
use Task
alias Pleroma.Web.AdminAPI.Config
def start_link do
def start_link(_) do
load_and_update_env()
if Pleroma.Config.get(:env) == :test, do: Ecto.Adapters.SQL.Sandbox.checkin(Pleroma.Repo)
:ignore

View File

@ -4,6 +4,7 @@
defmodule Pleroma.Conversation do
alias Pleroma.Conversation.Participation
alias Pleroma.Conversation.Participation.RecipientShip
alias Pleroma.Repo
alias Pleroma.User
use Ecto.Schema
@ -39,6 +40,15 @@ defmodule Pleroma.Conversation do
Repo.get_by(__MODULE__, ap_id: ap_id)
end
def maybe_create_recipientships(participation, activity) do
participation = Repo.preload(participation, :recipients)
if participation.recipients |> Enum.empty?() do
recipients = User.get_all_by_ap_id(activity.recipients)
RecipientShip.create(recipients, participation)
end
end
@doc """
This will
1. Create a conversation if there isn't one already
@ -60,6 +70,7 @@ defmodule Pleroma.Conversation do
{:ok, participation} =
Participation.create_for_user_and_conversation(user, conversation, opts)
maybe_create_recipientships(participation, activity)
participation
end)

View File

@ -5,6 +5,7 @@
defmodule Pleroma.Conversation.Participation do
use Ecto.Schema
alias Pleroma.Conversation
alias Pleroma.Conversation.Participation.RecipientShip
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
@ -17,6 +18,9 @@ defmodule Pleroma.Conversation.Participation do
field(:read, :boolean, default: false)
field(:last_activity_id, Pleroma.FlakeId, virtual: true)
has_many(:recipient_ships, RecipientShip)
has_many(:recipients, through: [:recipient_ships, :user])
timestamps()
end
@ -65,6 +69,14 @@ defmodule Pleroma.Conversation.Participation do
|> Pleroma.Pagination.fetch_paginated(params)
end
def for_user_and_conversation(user, conversation) do
from(p in __MODULE__,
where: p.user_id == ^user.id,
where: p.conversation_id == ^conversation.id
)
|> Repo.one()
end
def for_user_with_last_activity_id(user, params \\ %{}) do
for_user(user, params)
|> Enum.map(fn participation ->
@ -81,4 +93,46 @@ defmodule Pleroma.Conversation.Participation do
end)
|> Enum.filter(& &1.last_activity_id)
end
def get(_, _ \\ [])
def get(nil, _), do: nil
def get(id, params) do
query =
if preload = params[:preload] do
from(p in __MODULE__,
preload: ^preload
)
else
__MODULE__
end
Repo.get(query, id)
end
def set_recipients(participation, user_ids) do
user_ids =
[participation.user_id | user_ids]
|> Enum.uniq()
Repo.transaction(fn ->
query =
from(r in RecipientShip,
where: r.participation_id == ^participation.id
)
Repo.delete_all(query)
users =
from(u in User,
where: u.id in ^user_ids
)
|> Repo.all()
RecipientShip.create(users, participation)
:ok
end)
{:ok, Repo.preload(participation, :recipients, force: true)}
end
end

View File

@ -0,0 +1,34 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Conversation.Participation.RecipientShip do
use Ecto.Schema
alias Pleroma.Conversation.Participation
alias Pleroma.Repo
alias Pleroma.User
import Ecto.Changeset
schema "conversation_participation_recipient_ships" do
belongs_to(:user, User, type: Pleroma.FlakeId)
belongs_to(:participation, Participation)
end
def creation_cng(struct, params) do
struct
|> cast(params, [:user_id, :participation_id])
|> validate_required([:user_id, :participation_id])
end
def create(%User{} = user, participation), do: create([user], participation)
def create(users, participation) do
Enum.each(users, fn user ->
%__MODULE__{}
|> creation_cng(%{user_id: user.id, participation_id: participation.id})
|> Repo.insert!()
end)
end
end

View File

@ -0,0 +1,35 @@
defmodule Pleroma.DigestEmailWorker do
import Ecto.Query
@queue_name :digest_emails
def perform do
config = Pleroma.Config.get([:email_notifications, :digest])
negative_interval = -Map.fetch!(config, :interval)
inactivity_threshold = Map.fetch!(config, :inactivity_threshold)
inactive_users_query = Pleroma.User.list_inactive_users_query(inactivity_threshold)
now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
from(u in inactive_users_query,
where: fragment(~s(? #> '{"email_notifications","digest"}' @> 'true'), u.info),
where: u.last_digest_emailed_at < datetime_add(^now, ^negative_interval, "day"),
select: u
)
|> Pleroma.Repo.all()
|> Enum.each(&PleromaJobQueue.enqueue(@queue_name, __MODULE__, [&1]))
end
@doc """
Send digest email to the given user.
Updates `last_digest_emailed_at` field for the user and returns the updated user.
"""
@spec perform(Pleroma.User.t()) :: Pleroma.User.t()
def perform(user) do
with %Swoosh.Email{} = email <- Pleroma.Emails.UserEmail.digest_email(user) do
Pleroma.Emails.Mailer.deliver_async(email)
end
Pleroma.User.touch_last_digest_emailed_at(user)
end
end

View File

@ -63,7 +63,6 @@ defmodule Pleroma.Emails.AdminEmail do
new()
|> to({to.name, to.email})
|> from({instance_name(), instance_notify_email()})
|> reply_to({reporter.name, reporter.email})
|> subject("#{instance_name()} Report")
|> html_body(html_body)
end

View File

@ -5,7 +5,7 @@
defmodule Pleroma.Emails.UserEmail do
@moduledoc "User emails"
import Swoosh.Email
use Phoenix.Swoosh, view: Pleroma.Web.EmailView, layout: {Pleroma.Web.LayoutView, :email}
alias Pleroma.Web.Endpoint
alias Pleroma.Web.Router
@ -87,4 +87,73 @@ defmodule Pleroma.Emails.UserEmail do
|> subject("#{instance_name()} account confirmation")
|> html_body(html_body)
end
@doc """
Email used in digest email notifications
Includes Mentions and New Followers data
If there are no mentions (even when new followers exist), the function will return nil
"""
@spec digest_email(Pleroma.User.t()) :: Swoosh.Email.t() | nil
def digest_email(user) do
new_notifications =
Pleroma.Notification.for_user_since(user, user.last_digest_emailed_at)
|> Enum.reduce(%{followers: [], mentions: []}, fn
%{activity: %{data: %{"type" => "Create"}, actor: actor} = activity} = notification,
acc ->
new_mention = %{
data: notification,
object: Pleroma.Object.normalize(activity),
from: Pleroma.User.get_by_ap_id(actor)
}
%{acc | mentions: [new_mention | acc.mentions]}
%{activity: %{data: %{"type" => "Follow"}, actor: actor} = activity} = notification,
acc ->
new_follower = %{
data: notification,
object: Pleroma.Object.normalize(activity),
from: Pleroma.User.get_by_ap_id(actor)
}
%{acc | followers: [new_follower | acc.followers]}
_, acc ->
acc
end)
with [_ | _] = mentions <- new_notifications.mentions do
html_data = %{
instance: instance_name(),
user: user,
mentions: mentions,
followers: new_notifications.followers,
unsubscribe_link: unsubscribe_url(user, "digest")
}
new()
|> to(recipient(user))
|> from(sender())
|> subject("Your digest from #{instance_name()}")
|> render_body("digest.html", html_data)
else
_ ->
nil
end
end
@doc """
Generate unsubscribe link for given user and notifications type.
The link contains JWT token with the data, and subscription can be modified without
authorization.
"""
@spec unsubscribe_url(Pleroma.User.t(), String.t()) :: String.t()
def unsubscribe_url(user, notifications_type) do
token =
%{"sub" => user.id, "act" => %{"unsubscribe" => notifications_type}, "exp" => false}
|> Pleroma.JWT.generate_and_sign!()
|> Base.encode64()
Router.Helpers.subscription_url(Pleroma.Web.Endpoint, :unsubscribe, token)
end
end

View File

@ -24,7 +24,7 @@ defmodule Pleroma.Emoji do
@ets_options [:ordered_set, :protected, :named_table, {:read_concurrency, true}]
@doc false
def start_link do
def start_link(_) do
GenServer.start_link(__MODULE__, [], name: __MODULE__)
end

View File

@ -98,7 +98,7 @@ defmodule Pleroma.FlakeId do
def autogenerate, do: get()
# -- GenServer API
def start_link do
def start_link(_) do
:gen_server.start_link({:local, :flake}, __MODULE__, [], [])
end

View File

@ -6,7 +6,7 @@ defmodule Pleroma.Gopher.Server do
use GenServer
require Logger
def start_link do
def start_link(_) do
config = Pleroma.Config.get(:gopher, [])
ip = Keyword.get(config, :ip, {0, 0, 0, 0})
port = Keyword.get(config, :port, 1234)

View File

@ -203,6 +203,8 @@ defmodule Pleroma.HTML.Scrubber.Default do
Meta.allow_tag_with_these_attributes("p", [])
Meta.allow_tag_with_these_attributes("pre", [])
Meta.allow_tag_with_these_attributes("strong", [])
Meta.allow_tag_with_these_attributes("sub", [])
Meta.allow_tag_with_these_attributes("sup", [])
Meta.allow_tag_with_these_attributes("u", [])
Meta.allow_tag_with_these_attributes("ul", [])

9
lib/pleroma/jwt.ex Normal file
View File

@ -0,0 +1,9 @@
defmodule Pleroma.JWT do
use Joken.Config
@impl true
def token_config do
default_claims(skip: [:aud])
|> add_claim("aud", &Pleroma.Web.Endpoint.url/0, &(&1 == Pleroma.Web.Endpoint.url()))
end
end

View File

@ -18,6 +18,8 @@ defmodule Pleroma.Notification do
import Ecto.Query
import Ecto.Changeset
@type t :: %__MODULE__{}
schema "notifications" do
field(:seen, :boolean, default: false)
belongs_to(:user, User, type: Pleroma.FlakeId)
@ -31,7 +33,7 @@ defmodule Pleroma.Notification do
|> cast(attrs, [:seen])
end
def for_user_query(user, opts) do
def for_user_query(user, opts \\ []) do
query =
Notification
|> where(user_id: ^user.id)
@ -75,6 +77,25 @@ defmodule Pleroma.Notification do
|> Pagination.fetch_paginated(opts)
end
@doc """
Returns notifications for user received since given date.
## Examples
iex> Pleroma.Notification.for_user_since(%Pleroma.User{}, ~N[2019-04-13 11:22:33])
[%Pleroma.Notification{}, %Pleroma.Notification{}]
iex> Pleroma.Notification.for_user_since(%Pleroma.User{}, ~N[2019-04-15 11:22:33])
[]
"""
@spec for_user_since(Pleroma.User.t(), NaiveDateTime.t()) :: [t()]
def for_user_since(user, date) do
from(n in for_user_query(user),
where: n.updated_at > ^date
)
|> Repo.all()
end
def set_read_up_to(%{id: user_id} = _user, id) do
query =
from(
@ -82,7 +103,10 @@ defmodule Pleroma.Notification do
where: n.user_id == ^user_id,
where: n.id <= ^id,
update: [
set: [seen: true]
set: [
seen: true,
updated_at: ^NaiveDateTime.utc_now()
]
]
)

View File

@ -114,7 +114,7 @@ defmodule Pleroma.Object.Fetcher do
end
end
def fetch_and_contain_remote_object_from_id(id) do
def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do
Logger.info("Fetching object #{id} via AP")
date =
@ -141,4 +141,9 @@ defmodule Pleroma.Object.Fetcher do
{:error, e}
end
end
def fetch_and_contain_remote_object_from_id(%{"id" => id}),
do: fetch_and_contain_remote_object_from_id(id)
def fetch_and_contain_remote_object_from_id(_id), do: {:error, "id must be a string"}
end

View File

@ -16,7 +16,7 @@ defmodule Pleroma.ScheduledActivityWorker do
@schedule_interval :timer.minutes(1)
def start_link do
def start_link(_) do
GenServer.start_link(__MODULE__, nil)
end

View File

@ -7,31 +7,56 @@ defmodule Pleroma.Stats do
alias Pleroma.Repo
alias Pleroma.User
def start_link do
agent = Agent.start_link(fn -> {[], %{}} end, name: __MODULE__)
spawn(fn -> schedule_update() end)
agent
use GenServer
@interval 1000 * 60 * 60
def start_link(_) do
GenServer.start_link(__MODULE__, initial_data(), name: __MODULE__)
end
def force_update do
GenServer.call(__MODULE__, :force_update)
end
def get_stats do
Agent.get(__MODULE__, fn {_, stats} -> stats end)
%{stats: stats} = GenServer.call(__MODULE__, :get_state)
stats
end
def get_peers do
Agent.get(__MODULE__, fn {peers, _} -> peers end)
%{peers: peers} = GenServer.call(__MODULE__, :get_state)
peers
end
def schedule_update do
spawn(fn ->
# 1 hour
Process.sleep(1000 * 60 * 60)
schedule_update()
end)
update_stats()
def init(args) do
Process.send(self(), :run_update, [])
{:ok, args}
end
def update_stats do
def handle_call(:force_update, _from, _state) do
new_stats = get_stat_data()
{:reply, new_stats, new_stats}
end
def handle_call(:get_state, _from, state) do
{:reply, state, state}
end
def handle_info(:run_update, _state) do
new_stats = get_stat_data()
Process.send_after(self(), :run_update, @interval)
{:noreply, new_stats}
end
defp initial_data do
%{peers: [], stats: %{}}
end
defp get_stat_data do
peers =
from(
u in User,
@ -52,8 +77,9 @@ defmodule Pleroma.Stats do
user_count = Repo.aggregate(User.Query.build(%{local: true, active: true}), :count, :id)
Agent.update(__MODULE__, fn _ ->
{peers, %{domain_count: domain_count, status_count: status_count, user_count: user_count}}
end)
%{
peers: peers,
stats: %{domain_count: domain_count, status_count: status_count, user_count: user_count}
}
end
end

View File

@ -11,7 +11,7 @@ defmodule Pleroma.Uploaders.Local do
def put_file(upload) do
{local_path, file} =
case Enum.reverse(String.split(upload.path, "/", trim: true)) do
case Enum.reverse(Path.split(upload.path)) do
[file] ->
{upload_path(), file}
@ -23,7 +23,7 @@ defmodule Pleroma.Uploaders.Local do
result_file = Path.join(local_path, file)
unless File.exists?(result_file) do
if not File.exists?(result_file) do
File.cp!(upload.tempfile, result_file)
end

View File

@ -3,6 +3,8 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Uploaders.MDII do
@moduledoc "Represents uploader for https://github.com/hakaba-hitoyo/minimal-digital-image-infrastructure"
alias Pleroma.Config
alias Pleroma.HTTP

View File

@ -6,10 +6,12 @@ defmodule Pleroma.Uploaders.S3 do
@behaviour Pleroma.Uploaders.Uploader
require Logger
alias Pleroma.Config
# The file name is re-encoded with S3's constraints here to comply with previous
# links with less strict filenames
def get_file(file) do
config = Pleroma.Config.get([__MODULE__])
config = Config.get([__MODULE__])
bucket = Keyword.fetch!(config, :bucket)
bucket_with_namespace =
@ -34,15 +36,15 @@ defmodule Pleroma.Uploaders.S3 do
end
def put_file(%Pleroma.Upload{} = upload) do
config = Pleroma.Config.get([__MODULE__])
config = Config.get([__MODULE__])
bucket = Keyword.get(config, :bucket)
{:ok, file_data} = File.read(upload.tempfile)
s3_name = strict_encode(upload.path)
op =
ExAws.S3.put_object(bucket, s3_name, file_data, [
upload.tempfile
|> ExAws.S3.Upload.stream_file()
|> ExAws.S3.upload(bucket, s3_name, [
{:acl, :public_read},
{:content_type, upload.content_type}
])

View File

@ -21,6 +21,7 @@ defmodule Pleroma.User do
alias Pleroma.Web
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
alias Pleroma.Web.OAuth
alias Pleroma.Web.OStatus
@ -57,6 +58,7 @@ defmodule Pleroma.User do
field(:search_type, :integer, virtual: true)
field(:tags, {:array, :string}, default: [])
field(:last_refreshed_at, :naive_datetime_usec)
field(:last_digest_emailed_at, :naive_datetime)
has_many(:notifications, Notification)
has_many(:registrations, Registration)
embeds_one(:info, User.Info)
@ -114,7 +116,9 @@ defmodule Pleroma.User do
def user_info(%User{} = user, args \\ %{}) do
following_count =
if args[:following_count], do: args[:following_count], else: following_count(user)
if args[:following_count],
do: args[:following_count],
else: user.info.following_count || following_count(user)
follower_count =
if args[:follower_count], do: args[:follower_count], else: user.info.follower_count
@ -129,6 +133,28 @@ defmodule Pleroma.User do
|> Map.put(:follower_count, follower_count)
end
def follow_state(%User{} = user, %User{} = target) do
follow_activity = Utils.fetch_latest_follow(user, target)
if follow_activity,
do: follow_activity.data["state"],
# Ideally this would be nil, but then Cachex does not commit the value
else: false
end
def get_cached_follow_state(user, target) do
key = "follow_state:#{user.ap_id}|#{target.ap_id}"
Cachex.fetch!(:user_cache, key, fn _ -> {:commit, follow_state(user, target)} end)
end
def set_follow_state_cache(user_ap_id, target_ap_id, state) do
Cachex.put(
:user_cache,
"follow_state:#{user_ap_id}|#{target_ap_id}",
state
)
end
def set_info_cache(user, args) do
Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user, args))
end
@ -149,10 +175,10 @@ defmodule Pleroma.User do
end
def remote_user_creation(params) do
params =
params
|> Map.put(:info, params[:info] || %{})
bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
params = Map.put(params, :info, params[:info] || %{})
info_cng = User.Info.remote_user_creation(%User.Info{}, params[:info])
changes =
@ -161,8 +187,8 @@ defmodule Pleroma.User do
|> validate_required([:name, :ap_id])
|> unique_constraint(:nickname)
|> validate_format(:nickname, @email_regex)
|> validate_length(:bio, max: 5000)
|> validate_length(:name, max: 100)
|> validate_length(:bio, max: bio_limit)
|> validate_length(:name, max: name_limit)
|> put_change(:local, false)
|> put_embed(:info, info_cng)
@ -185,22 +211,23 @@ defmodule Pleroma.User do
end
def update_changeset(struct, params \\ %{}) do
bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
struct
|> cast(params, [:bio, :name, :avatar, :following])
|> unique_constraint(:nickname)
|> validate_format(:nickname, local_nickname_regex())
|> validate_length(:bio, max: 5000)
|> validate_length(:name, min: 1, max: 100)
|> validate_length(:bio, max: bio_limit)
|> validate_length(:name, min: 1, max: name_limit)
end
def upgrade_changeset(struct, params \\ %{}) do
params =
params
|> Map.put(:last_refreshed_at, NaiveDateTime.utc_now())
bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
info_cng =
struct.info
|> User.Info.user_upgrade(params[:info])
params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
info_cng = User.Info.user_upgrade(struct.info, params[:info])
struct
|> cast(params, [
@ -213,8 +240,8 @@ defmodule Pleroma.User do
])
|> unique_constraint(:nickname)
|> validate_format(:nickname, local_nickname_regex())
|> validate_length(:bio, max: 5000)
|> validate_length(:name, max: 100)
|> validate_length(:bio, max: bio_limit)
|> validate_length(:name, max: name_limit)
|> put_embed(:info, info_cng)
end
@ -241,6 +268,9 @@ defmodule Pleroma.User do
end
def register_changeset(struct, params \\ %{}, opts \\ []) do
bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
need_confirmation? =
if is_nil(opts[:need_confirmation]) do
Pleroma.Config.get([:instance, :account_activation_required])
@ -261,8 +291,8 @@ defmodule Pleroma.User do
|> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
|> validate_format(:nickname, local_nickname_regex())
|> validate_format(:email, @email_regex)
|> validate_length(:bio, max: 1000)
|> validate_length(:name, min: 1, max: 100)
|> validate_length(:bio, max: bio_limit)
|> validate_length(:name, min: 1, max: name_limit)
|> put_change(:info, info_change)
changeset =
@ -406,6 +436,8 @@ defmodule Pleroma.User do
{1, [follower]} = Repo.update_all(q, [])
follower = maybe_update_following_count(follower)
{:ok, _} = update_follower_count(followed)
set_cache(follower)
@ -425,6 +457,8 @@ defmodule Pleroma.User do
{1, [follower]} = Repo.update_all(q, [])
follower = maybe_update_following_count(follower)
{:ok, followed} = update_follower_count(followed)
set_cache(follower)
@ -452,6 +486,13 @@ defmodule Pleroma.User do
Repo.get_by(User, ap_id: ap_id)
end
def get_all_by_ap_id(ap_ids) do
from(u in __MODULE__,
where: u.ap_id in ^ap_ids
)
|> Repo.all()
end
# This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
# of the ap_id and the domain and tries to get that user
def get_by_guessed_nickname(ap_id) do
@ -709,32 +750,73 @@ defmodule Pleroma.User do
|> update_and_set_cache()
end
def update_follower_count(%User{} = user) do
follower_count_query =
User.Query.build(%{followers: user, deactivated: false})
|> select([u], %{count: count(u.id)})
def maybe_fetch_follow_information(user) do
with {:ok, user} <- fetch_follow_information(user) do
user
else
e ->
Logger.error("Follower/Following counter update for #{user.ap_id} failed.\n#{inspect(e)}")
User
|> where(id: ^user.id)
|> join(:inner, [u], s in subquery(follower_count_query))
|> update([u, s],
set: [
info:
fragment(
"jsonb_set(?, '{follower_count}', ?::varchar::jsonb, true)",
u.info,
s.count
)
]
)
|> select([u], u)
|> Repo.update_all([])
|> case do
{1, [user]} -> set_cache(user)
_ -> {:error, user}
user
end
end
def fetch_follow_information(user) do
with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
info_cng = User.Info.follow_information_update(user.info, info)
changeset =
user
|> change()
|> put_embed(:info, info_cng)
update_and_set_cache(changeset)
else
{:error, _} = e -> e
e -> {:error, e}
end
end
def update_follower_count(%User{} = user) do
if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
follower_count_query =
User.Query.build(%{followers: user, deactivated: false})
|> select([u], %{count: count(u.id)})
User
|> where(id: ^user.id)
|> join(:inner, [u], s in subquery(follower_count_query))
|> update([u, s],
set: [
info:
fragment(
"jsonb_set(?, '{follower_count}', ?::varchar::jsonb, true)",
u.info,
s.count
)
]
)
|> select([u], u)
|> Repo.update_all([])
|> case do
{1, [user]} -> set_cache(user)
_ -> {:error, user}
end
else
{:ok, maybe_fetch_follow_information(user)}
end
end
def maybe_update_following_count(%User{local: false} = user) do
if Pleroma.Config.get([:instance, :external_user_synchronization]) do
{:ok, maybe_fetch_follow_information(user)}
else
user
end
end
def maybe_update_following_count(user), do: user
def remove_duplicated_following(%User{following: following} = user) do
uniq_following = Enum.uniq(following)
@ -833,6 +915,13 @@ defmodule Pleroma.User do
blocker
end
# clear any requested follows as well
blocked =
case CommonAPI.reject_follow_request(blocked, blocker) do
{:ok, %User{} = updated_blocked} -> updated_blocked
nil -> blocked
end
blocker =
if subscribed_to?(blocked, blocker) do
{:ok, blocker} = unsubscribe(blocked, blocker)
@ -1372,6 +1461,80 @@ defmodule Pleroma.User do
target.ap_id not in user.info.muted_reblogs
end
@doc """
The function returns a query to get users with no activity for given interval of days.
Inactive users are those who didn't read any notification, or had any activity where
the user is the activity's actor, during `inactivity_threshold` days.
Deactivated users will not appear in this list.
## Examples
iex> Pleroma.User.list_inactive_users()
%Ecto.Query{}
"""
@spec list_inactive_users_query(integer()) :: Ecto.Query.t()
def list_inactive_users_query(inactivity_threshold \\ 7) do
negative_inactivity_threshold = -inactivity_threshold
now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
# Subqueries are not supported in `where` clauses, join gets too complicated.
has_read_notifications =
from(n in Pleroma.Notification,
where: n.seen == true,
group_by: n.id,
having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
select: n.user_id
)
|> Pleroma.Repo.all()
from(u in Pleroma.User,
left_join: a in Pleroma.Activity,
on: u.ap_id == a.actor,
where: not is_nil(u.nickname),
where: fragment("not (?->'deactivated' @> 'true')", u.info),
where: u.id not in ^has_read_notifications,
group_by: u.id,
having:
max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
is_nil(max(a.inserted_at))
)
end
@doc """
Enable or disable email notifications for user
## Examples
iex> Pleroma.User.switch_email_notifications(Pleroma.User{info: %{email_notifications: %{"digest" => false}}}, "digest", true)
Pleroma.User{info: %{email_notifications: %{"digest" => true}}}
iex> Pleroma.User.switch_email_notifications(Pleroma.User{info: %{email_notifications: %{"digest" => true}}}, "digest", false)
Pleroma.User{info: %{email_notifications: %{"digest" => false}}}
"""
@spec switch_email_notifications(t(), String.t(), boolean()) ::
{:ok, t()} | {:error, Ecto.Changeset.t()}
def switch_email_notifications(user, type, status) do
info = Pleroma.User.Info.update_email_notifications(user.info, %{type => status})
change(user)
|> put_embed(:info, info)
|> update_and_set_cache()
end
@doc """
Set `last_digest_emailed_at` value for the user to current time
"""
@spec touch_last_digest_emailed_at(t()) :: t()
def touch_last_digest_emailed_at(user) do
now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
{:ok, updated_user} =
user
|> change(%{last_digest_emailed_at: now})
|> update_and_set_cache()
updated_user
end
@spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
def toggle_confirmation(%User{} = user) do
need_confirmation? = !user.info.confirmation_pending

View File

@ -16,6 +16,8 @@ defmodule Pleroma.User.Info do
field(:source_data, :map, default: %{})
field(:note_count, :integer, default: 0)
field(:follower_count, :integer, default: 0)
# Should be filled in only for remote users
field(:following_count, :integer, default: nil)
field(:locked, :boolean, default: false)
field(:confirmation_pending, :boolean, default: false)
field(:confirmation_token, :string, default: nil)
@ -43,6 +45,7 @@ defmodule Pleroma.User.Info do
field(:hide_follows, :boolean, default: false)
field(:hide_favorites, :boolean, default: true)
field(:pinned_activities, {:array, :string}, default: [])
field(:email_notifications, :map, default: %{"digest" => false})
field(:mascot, :map, default: nil)
field(:emoji, {:array, :map}, default: [])
field(:pleroma_settings_store, :map, default: %{})
@ -93,6 +96,30 @@ defmodule Pleroma.User.Info do
|> validate_required([:notification_settings])
end
@doc """
Update email notifications in the given User.Info struct.
Examples:
iex> update_email_notifications(%Pleroma.User.Info{email_notifications: %{"digest" => false}}, %{"digest" => true})
%Pleroma.User.Info{email_notifications: %{"digest" => true}}
"""
@spec update_email_notifications(t(), map()) :: Ecto.Changeset.t()
def update_email_notifications(info, settings) do
email_notifications =
info.email_notifications
|> Map.merge(settings)
|> Map.take(["digest"])
params = %{email_notifications: email_notifications}
fields = [:email_notifications]
info
|> cast(params, fields)
|> validate_required(fields)
end
def add_to_note_count(info, number) do
set_note_count(info, info.note_count + number)
end
@ -223,7 +250,11 @@ defmodule Pleroma.User.Info do
:uri,
:hub,
:topic,
:salmon
:salmon,
:hide_followers,
:hide_follows,
:follower_count,
:following_count
])
end
@ -234,7 +265,11 @@ defmodule Pleroma.User.Info do
:source_data,
:banner,
:locked,
:magic_key
:magic_key,
:follower_count,
:following_count,
:hide_follows,
:hide_followers
])
end
@ -348,4 +383,14 @@ defmodule Pleroma.User.Info do
cast(info, params, [:muted_reblogs])
end
def follow_information_update(info, params) do
info
|> cast(params, [
:hide_followers,
:hide_follows,
:follower_count,
:following_count
])
end
end

View File

@ -44,7 +44,7 @@ defmodule Pleroma.User.Search do
query_string = String.trim_leading(query_string, "@")
with [name, domain] <- String.split(query_string, "@"),
formatted_domain <- String.replace(domain, ~r/[!-\-|@|[-`|{-~|\/|:]+/, "") do
formatted_domain <- String.replace(domain, ~r/[!-\-|@|[-`|{-~|\/|:|\s]+/, "") do
name <> "@" <> to_string(:idna.encode(formatted_domain))
else
_ -> query_string

View File

@ -267,6 +267,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
else
{:fake, true, activity} ->
{:ok, activity}
{:error, message} ->
{:error, message}
end
end
@ -385,7 +388,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
def follow(follower, followed, activity_id \\ nil, local \\ true) do
with data <- make_follow_data(follower, followed, activity_id),
{:ok, activity} <- insert(data, local),
:ok <- maybe_federate(activity) do
:ok <- maybe_federate(activity),
_ <- User.set_follow_state_cache(follower.ap_id, followed.ap_id, activity.data["state"]) do
{:ok, activity}
end
end
@ -515,6 +519,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
from(activity in Activity)
|> maybe_preload_objects(opts)
|> maybe_preload_bookmarks(opts)
|> maybe_set_thread_muted_field(opts)
|> restrict_blocked(opts)
|> restrict_recipients(recipients, opts["user"])
|> where(
@ -528,6 +534,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
)
)
|> exclude_poll_votes(opts)
|> exclude_id(opts)
|> order_by([activity], desc: activity.id)
end
@ -620,6 +627,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
params =
params
|> Map.put("type", ["Create", "Announce"])
|> Map.put("user", reading_user)
|> Map.put("actor_id", user.ap_id)
|> Map.put("whole_db", true)
|> Map.put("pinned_activity_ids", user.info.pinned_activities)
@ -746,8 +754,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_favorited_by(query, %{"favorited_by" => ap_id}) do
from(
activity in query,
where: fragment(~s(? <@ (? #> '{"object","likes"}'\)), ^ap_id, activity.data)
[_activity, object] in query,
where: fragment("(?)->'likes' \\? (?)", object.data, ^ap_id)
)
end
@ -783,14 +791,20 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_muted(query, %{"with_muted" => val}) when val in [true, "true", "1"], do: query
defp restrict_muted(query, %{"muting_user" => %User{info: info}}) do
defp restrict_muted(query, %{"muting_user" => %User{info: info}} = opts) do
mutes = info.mutes
from(
activity in query,
where: fragment("not (? = ANY(?))", activity.actor, ^mutes),
where: fragment("not (?->'to' \\?| ?)", activity.data, ^mutes)
)
query =
from([activity] in query,
where: fragment("not (? = ANY(?))", activity.actor, ^mutes),
where: fragment("not (?->'to' \\?| ?)", activity.data, ^mutes)
)
unless opts["skip_preload"] do
from([thread_mute: tm] in query, where: is_nil(tm))
else
query
end
end
defp restrict_muted(query, _), do: query
@ -867,6 +881,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
defp exclude_id(query, %{"exclude_id" => id}) when is_binary(id) do
from(activity in query, where: activity.id != ^id)
end
defp exclude_id(query, _), do: query
defp maybe_preload_objects(query, %{"skip_preload" => true}), do: query
defp maybe_preload_objects(query, _) do
@ -885,7 +905,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp maybe_set_thread_muted_field(query, opts) do
query
|> Activity.with_set_thread_muted_field(opts["user"])
|> Activity.with_set_thread_muted_field(opts["muting_user"] || opts["user"])
end
defp maybe_order(query, %{order: :desc}) do
@ -1009,10 +1029,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
user_data = %{
ap_id: data["id"],
info: %{
"ap_enabled" => true,
"source_data" => data,
"banner" => banner,
"locked" => locked
ap_enabled: true,
source_data: data,
banner: banner,
locked: locked
},
avatar: avatar,
name: data["name"],
@ -1036,6 +1056,71 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
{:ok, user_data}
end
def fetch_follow_information_for_user(user) do
with {:ok, following_data} <-
Fetcher.fetch_and_contain_remote_object_from_id(user.following_address),
following_count when is_integer(following_count) <- following_data["totalItems"],
{:ok, hide_follows} <- collection_private(following_data),
{:ok, followers_data} <-
Fetcher.fetch_and_contain_remote_object_from_id(user.follower_address),
followers_count when is_integer(followers_count) <- followers_data["totalItems"],
{:ok, hide_followers} <- collection_private(followers_data) do
{:ok,
%{
hide_follows: hide_follows,
follower_count: followers_count,
following_count: following_count,
hide_followers: hide_followers
}}
else
{:error, _} = e ->
e
e ->
{:error, e}
end
end
defp maybe_update_follow_information(data) do
with {:enabled, true} <-
{:enabled, Pleroma.Config.get([:instance, :external_user_synchronization])},
{:ok, info} <- fetch_follow_information_for_user(data) do
info = Map.merge(data.info, info)
Map.put(data, :info, info)
else
{:enabled, false} ->
data
e ->
Logger.error(
"Follower/Following counter update for #{data.ap_id} failed.\n" <> inspect(e)
)
data
end
end
defp collection_private(data) do
if is_map(data["first"]) and
data["first"]["type"] in ["CollectionPage", "OrderedCollectionPage"] do
{:ok, false}
else
with {:ok, %{"type" => type}} when type in ["CollectionPage", "OrderedCollectionPage"] <-
Fetcher.fetch_and_contain_remote_object_from_id(data["first"]) do
{:ok, false}
else
{:error, {:ok, %{status: code}}} when code in [401, 403] ->
{:ok, true}
{:error, _} = e ->
e
e ->
{:error, e}
end
end
end
def user_data_from_user_object(data) do
with {:ok, data} <- MRF.filter(data),
{:ok, data} <- object_to_user_data(data) do
@ -1047,7 +1132,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
def fetch_and_prepare_user_from_ap_id(ap_id) do
with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id),
{:ok, data} <- user_data_from_user_object(data) do
{:ok, data} <- user_data_from_user_object(data),
data <- maybe_update_follow_information(data) do
{:ok, data}
else
e -> Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")

View File

@ -28,11 +28,43 @@ defmodule Pleroma.Web.ActivityPub.MRF do
@spec subdomains_regex([String.t()]) :: [Regex.t()]
def subdomains_regex(domains) when is_list(domains) do
for domain <- domains, do: ~r(^#{String.replace(domain, "*.", "(.*\\.)*")}$)
for domain <- domains, do: ~r(^#{String.replace(domain, "*.", "(.*\\.)*")}$)i
end
@spec subdomain_match?([Regex.t()], String.t()) :: boolean()
def subdomain_match?(domains, host) do
Enum.any?(domains, fn domain -> Regex.match?(domain, host) end)
end
@callback describe() :: {:ok | :error, Map.t()}
def describe(policies) do
{:ok, policy_configs} =
policies
|> Enum.reduce({:ok, %{}}, fn
policy, {:ok, data} ->
{:ok, policy_data} = policy.describe()
{:ok, Map.merge(data, policy_data)}
_, error ->
error
end)
mrf_policies =
get_policies()
|> Enum.map(fn policy -> to_string(policy) |> String.split(".") |> List.last() end)
exclusions = Pleroma.Config.get([:instance, :mrf_transparency_exclusions])
base =
%{
mrf_policies: mrf_policies,
exclusions: length(exclusions) > 0
}
|> Map.merge(policy_configs)
{:ok, base}
end
def describe, do: get_policies() |> describe()
end

View File

@ -62,4 +62,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy do
@impl true
def filter(message), do: {:ok, message}
@impl true
def describe, do: {:ok, %{}}
end

View File

@ -5,6 +5,8 @@
defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do
alias Pleroma.User
@behaviour Pleroma.Web.ActivityPub.MRF
require Logger
# has the user successfully posted before?
@ -22,6 +24,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do
defp contains_links?(_), do: false
@impl true
def filter(%{"type" => "Create", "actor" => actor, "object" => object} = message) do
with {:ok, %User{} = u} <- User.get_or_fetch_by_ap_id(actor),
{:contains_links, true} <- {:contains_links, contains_links?(object)},
@ -45,4 +48,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do
# in all other cases, pass through
def filter(message), do: {:ok, message}
@impl true
def describe, do: {:ok, %{}}
end

View File

@ -12,4 +12,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.DropPolicy do
Logger.info("REJECTING #{inspect(object)}")
{:reject, object}
end
@impl true
def describe, do: {:ok, %{}}
end

View File

@ -39,4 +39,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrepended do
end
def filter(object), do: {:ok, object}
def describe, do: {:ok, %{}}
end

View File

@ -90,4 +90,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
@impl true
def filter(message), do: {:ok, message}
@impl true
def describe,
do: {:ok, %{mrf_hellthread: Pleroma.Config.get(:mrf_hellthread) |> Enum.into(%{})}}
end

View File

@ -96,4 +96,36 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
@impl true
def filter(message), do: {:ok, message}
@impl true
def describe do
# This horror is needed to convert regex sigils to strings
mrf_keyword =
Pleroma.Config.get(:mrf_keyword, [])
|> Enum.map(fn {key, value} ->
{key,
Enum.map(value, fn
{pattern, replacement} ->
%{
"pattern" =>
if not is_binary(pattern) do
inspect(pattern)
else
pattern
end,
"replacement" => replacement
}
pattern ->
if not is_binary(pattern) do
inspect(pattern)
else
pattern
end
end)}
end)
|> Enum.into(%{})
{:ok, %{mrf_keyword: mrf_keyword}}
end
end

View File

@ -53,4 +53,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do
@impl true
def filter(message), do: {:ok, message}
@impl true
def describe, do: {:ok, %{}}
end

View File

@ -21,4 +21,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.MentionPolicy do
@impl true
def filter(message), do: {:ok, message}
@impl true
def describe, do: {:ok, %{}}
end

View File

@ -19,4 +19,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoPlaceholderTextPolicy do
@impl true
def filter(object), do: {:ok, object}
@impl true
def describe, do: {:ok, %{}}
end

View File

@ -10,4 +10,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoOpPolicy do
def filter(object) do
{:ok, object}
end
@impl true
def describe, do: {:ok, %{}}
end

View File

@ -21,4 +21,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkup do
end
def filter(object), do: {:ok, object}
def describe, do: {:ok, %{}}
end

View File

@ -44,4 +44,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do
@impl true
def filter(object), do: {:ok, object}
@impl true
def describe,
do: {:ok, %{mrf_rejectnonpublic: Pleroma.Config.get(:mrf_rejectnonpublic) |> Enum.into(%{})}}
end

View File

@ -177,4 +177,16 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
end
def filter(object), do: {:ok, object}
@impl true
def describe do
exclusions = Pleroma.Config.get([:instance, :mrf_transparency_exclusions])
mrf_simple =
Pleroma.Config.get(:mrf_simple)
|> Enum.map(fn {k, v} -> {k, Enum.reject(v, fn v -> v in exclusions end)} end)
|> Enum.into(%{})
{:ok, %{mrf_simple: mrf_simple}}
end
end

View File

@ -37,4 +37,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicy do
@impl true
def filter(message), do: {:ok, message}
@impl true
def describe, do: {:ok, %{}}
end

View File

@ -165,4 +165,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
@impl true
def filter(message), do: {:ok, message}
@impl true
def describe, do: {:ok, %{}}
end

View File

@ -32,4 +32,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do
end
def filter(object), do: {:ok, object}
@impl true
def describe do
mrf_user_allowlist =
Config.get([:mrf_user_allowlist], [])
|> Enum.into(%{}, fn {k, v} -> {k, length(v)} end)
{:ok, %{mrf_user_allowlist: mrf_user_allowlist}}
end
end

View File

@ -0,0 +1,37 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do
@moduledoc "Filter messages which belong to certain activity vocabularies"
@behaviour Pleroma.Web.ActivityPub.MRF
def filter(%{"type" => "Undo", "object" => child_message} = message) do
with {:ok, _} <- filter(child_message) do
{:ok, message}
else
{:reject, nil} ->
{:reject, nil}
end
end
def filter(%{"type" => message_type} = message) do
with accepted_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :accept]),
rejected_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :reject]),
true <-
length(accepted_vocabulary) == 0 || Enum.member?(accepted_vocabulary, message_type),
false <-
length(rejected_vocabulary) > 0 && Enum.member?(rejected_vocabulary, message_type),
{:ok, _} <- filter(message["object"]) do
{:ok, message}
else
_ -> {:reject, nil}
end
end
def filter(message), do: {:ok, message}
def describe,
do: {:ok, %{mrf_vocabulary: Pleroma.Config.get(:mrf_vocabulary) |> Enum.into(%{})}}
end

View File

@ -46,7 +46,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
"""
def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = params) do
Logger.info("Federating #{id} to #{inbox}")
host = URI.parse(inbox).host
%{host: host, path: path} = URI.parse(inbox)
digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
@ -56,6 +56,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
signature =
Pleroma.Signature.sign(actor, %{
"(request-target)": "post #{path}",
host: host,
"content-length": byte_size(json),
digest: digest,

View File

@ -14,6 +14,7 @@ defmodule Pleroma.Web.ActivityPub.Relay do
|> User.get_or_create_service_actor_by_ap_id()
end
@spec follow(String.t()) :: {:ok, Activity.t()} | {:error, any()}
def follow(target_instance) do
with %User{} = local_user <- get_actor(),
{:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_instance),
@ -21,12 +22,17 @@ defmodule Pleroma.Web.ActivityPub.Relay do
Logger.info("relay: followed instance: #{target_instance}; id=#{activity.data["id"]}")
{:ok, activity}
else
{:error, _} = error ->
Logger.error("error: #{inspect(error)}")
error
e ->
Logger.error("error: #{inspect(e)}")
{:error, e}
end
end
@spec unfollow(String.t()) :: {:ok, Activity.t()} | {:error, any()}
def unfollow(target_instance) do
with %User{} = local_user <- get_actor(),
{:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_instance),
@ -34,20 +40,27 @@ defmodule Pleroma.Web.ActivityPub.Relay do
Logger.info("relay: unfollowed instance: #{target_instance}: id=#{activity.data["id"]}")
{:ok, activity}
else
{:error, _} = error ->
Logger.error("error: #{inspect(error)}")
error
e ->
Logger.error("error: #{inspect(e)}")
{:error, e}
end
end
@spec publish(any()) :: {:ok, Activity.t(), Object.t()} | {:error, any()}
def publish(%Activity{data: %{"type" => "Create"}} = activity) do
with %User{} = user <- get_actor(),
%Object{} = object <- Object.normalize(activity) do
ActivityPub.announce(user, object, nil, true, false)
else
e -> Logger.error("error: #{inspect(e)}")
e ->
Logger.error("error: #{inspect(e)}")
{:error, inspect(e)}
end
end
def publish(_), do: nil
def publish(_), do: {:error, "Not implemented"}
end

View File

@ -26,6 +26,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
"""
def fix_object(object, options \\ []) do
object
|> strip_internal_fields
|> fix_actor
|> fix_url
|> fix_attachments
@ -34,7 +35,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> fix_emoji
|> fix_tag
|> fix_content_map
|> fix_likes
|> fix_addressing
|> fix_summary
|> fix_type(options)
@ -151,20 +151,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> Map.put("actor", Containment.get_actor(%{"actor" => actor}))
end
# Check for standardisation
# This is what Peertube does
# curl -H 'Accept: application/activity+json' $likes | jq .totalItems
# Prismo returns only an integer (count) as "likes"
def fix_likes(%{"likes" => likes} = object) when not is_map(likes) do
object
|> Map.put("likes", [])
|> Map.put("like_count", 0)
end
def fix_likes(object) do
object
end
def fix_in_reply_to(object, options \\ [])
def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options)
@ -347,13 +333,15 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def fix_type(object, options \\ [])
def fix_type(%{"inReplyTo" => reply_id} = object, options) when is_binary(reply_id) do
def fix_type(%{"inReplyTo" => reply_id, "name" => _} = object, options)
when is_binary(reply_id) do
reply =
if Federator.allowed_incoming_reply_depth?(options[:depth]) do
Object.normalize(reply_id, true)
with true <- Federator.allowed_incoming_reply_depth?(options[:depth]),
{:ok, object} <- get_obj_helper(reply_id, options) do
object
end
if reply && (reply.data["type"] == "Question" and object["name"]) do
if reply && reply.data["type"] == "Question" do
Map.put(object, "type", "Answer")
else
object
@ -608,13 +596,13 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier 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)
banner = new_user_data[:info]["banner"]
locked = new_user_data[:info]["locked"] || false
banner = new_user_data[:info][:banner]
locked = new_user_data[:info][:locked] || false
update_data =
new_user_data
|> Map.take([:name, :bio, :avatar])
|> Map.put(:info, %{"banner" => banner, "locked" => locked})
|> Map.put(:info, %{banner: banner, locked: locked})
actor
|> User.upgrade_changeset(update_data)
@ -713,8 +701,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
} = _data,
_options
) do
with true <- Pleroma.Config.get([:activitypub, :accept_blocks]),
%User{local: true} = blocked <- User.get_cached_by_ap_id(blocked),
with %User{local: true} = blocked <- User.get_cached_by_ap_id(blocked),
{:ok, %User{} = blocker} <- User.get_or_fetch_by_ap_id(blocker),
{:ok, activity} <- ActivityPub.unblock(blocker, blocked, id, false) do
User.unblock(blocker, blocked)
@ -728,8 +715,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
%{"type" => "Block", "object" => blocked, "actor" => blocker, "id" => id} = _data,
_options
) do
with true <- Pleroma.Config.get([:activitypub, :accept_blocks]),
%User{local: true} = blocked = User.get_cached_by_ap_id(blocked),
with %User{local: true} = blocked = User.get_cached_by_ap_id(blocked),
{:ok, %User{} = blocker} = User.get_or_fetch_by_ap_id(blocker),
{:ok, activity} <- ActivityPub.block(blocker, blocked, id, false) do
User.unfollow(blocker, blocked)
@ -784,7 +770,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> add_mention_tags
|> add_emoji_tags
|> add_attributed_to
|> add_likes
|> prepare_attachments
|> set_conversation
|> set_reply_to_uri
@ -971,22 +956,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> Map.put("attributedTo", attributed_to)
end
def add_likes(%{"id" => id, "like_count" => likes} = object) do
likes = %{
"id" => "#{id}/likes",
"first" => "#{id}/likes?page=1",
"type" => "OrderedCollection",
"totalItems" => likes
}
object
|> Map.put("likes", likes)
end
def add_likes(object) do
object
end
def prepare_attachments(object) do
attachments =
(object["attachment"] || [])
@ -1002,6 +971,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
defp strip_internal_fields(object) do
object
|> Map.drop([
"likes",
"like_count",
"announcements",
"announcement_count",
@ -1076,10 +1046,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
PleromaJobQueue.enqueue(:transmogrifier, __MODULE__, [:user_upgrade, user])
end
if Pleroma.Config.get([:instance, :external_user_synchronization]) do
update_following_followers_counters(user)
end
{:ok, user}
else
%User{} = user -> {:ok, user}
@ -1112,27 +1078,4 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
data
|> maybe_fix_user_url
end
def update_following_followers_counters(user) do
info = %{}
following = fetch_counter(user.following_address)
info = if following, do: Map.put(info, :following_count, following), else: info
followers = fetch_counter(user.follower_address)
info = if followers, do: Map.put(info, :follower_count, followers), else: info
User.set_info_cache(user, info)
end
defp fetch_counter(url) do
with {:ok, %{body: body, status: code}} when code in 200..299 <-
Pleroma.HTTP.get(
url,
[{:Accept, "application/activity+json"}]
),
{:ok, data} <- Jason.decode(body) do
data["totalItems"]
end
end
end

View File

@ -251,20 +251,6 @@ defmodule Pleroma.Web.ActivityPub.Utils do
def insert_full_object(map), do: {:ok, map, nil}
def update_object_in_activities(%{data: %{"id" => id}} = object) do
# TODO
# Update activities that already had this. Could be done in a seperate process.
# Alternatively, just don't do this and fetch the current object each time. Most
# could probably be taken from cache.
relevant_activities = Activity.get_all_create_by_object_ap_id(id)
Enum.map(relevant_activities, fn activity ->
new_activity_data = activity.data |> Map.put("object", object.data)
changeset = Changeset.change(activity, data: new_activity_data)
Repo.update(changeset)
end)
end
#### Like-related helpers
@doc """
@ -347,8 +333,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|> Map.put("#{property}_count", length(element))
|> Map.put("#{property}s", element),
changeset <- Changeset.change(object, data: new_data),
{:ok, object} <- Object.update_and_set_cache(changeset),
_ <- update_object_in_activities(object) do
{:ok, object} <- Object.update_and_set_cache(changeset) do
{:ok, object}
end
end
@ -389,6 +374,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
[state, actor, object]
)
User.set_follow_state_cache(actor, object, state)
activity = Activity.get_by_id(activity.id)
{:ok, activity}
rescue
@ -397,12 +383,16 @@ defmodule Pleroma.Web.ActivityPub.Utils do
end
end
def update_follow_state(%Activity{} = activity, state) do
def update_follow_state(
%Activity{data: %{"actor" => actor, "object" => object}} = activity,
state
) do
with new_data <-
activity.data
|> Map.put("state", state),
changeset <- Changeset.change(activity, data: new_data),
{:ok, activity} <- Repo.update(changeset) do
{:ok, activity} <- Repo.update(changeset),
_ <- User.set_follow_state_cache(actor, object, state) do
{:ok, activity}
end
end

View File

@ -66,8 +66,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectView do
"orderedItems" => items
}
if offset < total do
if offset + length(items) < total do
Map.put(map, "next", "#{iri}?page=#{page + 1}")
else
map
end
end
end

View File

@ -65,7 +65,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
do: render("service.json", %{user: user})
def render("user.json", %{user: %User{nickname: "internal." <> _} = user}),
do: render("service.json", %{user: user})
do: render("service.json", %{user: user}) |> Map.put("preferredUsername", user.nickname)
def render("user.json", %{user: user}) do
{:ok, user} = User.ensure_keys_present(user)

View File

@ -402,9 +402,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
if Pleroma.Config.get([:instance, :dynamic_configuration]) do
updated =
Enum.map(configs, fn
%{"group" => group, "key" => key, "delete" => "true"} ->
{:ok, _} = Config.delete(%{group: group, key: key})
nil
%{"group" => group, "key" => key, "delete" => "true"} = params ->
{:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
config
%{"group" => group, "key" => key, "value" => value} ->
{:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})

View File

@ -55,8 +55,19 @@ defmodule Pleroma.Web.AdminAPI.Config do
@spec delete(map()) :: {:ok, Config.t()} | {:error, Changeset.t()}
def delete(params) do
with %Config{} = config <- Config.get_by_params(params) do
Repo.delete(config)
with %Config{} = config <- Config.get_by_params(Map.delete(params, :subkeys)) do
if params[:subkeys] do
updated_value =
Keyword.drop(
:erlang.binary_to_term(config.value),
Enum.map(params[:subkeys], &do_transform_string(&1))
)
Config.update(config, %{value: updated_value})
else
Repo.delete(config)
{:ok, nil}
end
else
nil ->
err =

View File

@ -33,9 +33,11 @@ defmodule Pleroma.Web.ChatChannel do
end
defmodule Pleroma.Web.ChatChannel.ChatChannelState do
use Agent
@max_messages 20
def start_link do
def start_link(_) do
Agent.start_link(fn -> %{max_id: 1, messages: []} end, name: __MODULE__)
end

View File

@ -4,6 +4,7 @@
defmodule Pleroma.Web.CommonAPI do
alias Pleroma.Activity
alias Pleroma.Conversation.Participation
alias Pleroma.Formatter
alias Pleroma.Object
alias Pleroma.ThreadMute
@ -171,21 +172,25 @@ defmodule Pleroma.Web.CommonAPI do
end)
end
def get_visibility(%{"visibility" => visibility}, in_reply_to)
def get_visibility(_, _, %Participation{}) do
{"direct", "direct"}
end
def get_visibility(%{"visibility" => visibility}, in_reply_to, _)
when visibility in ~w{public unlisted private direct},
do: {visibility, get_replied_to_visibility(in_reply_to)}
def get_visibility(%{"visibility" => "list:" <> list_id}, in_reply_to) do
def get_visibility(%{"visibility" => "list:" <> list_id}, in_reply_to, _) do
visibility = {:list, String.to_integer(list_id)}
{visibility, get_replied_to_visibility(in_reply_to)}
end
def get_visibility(_, in_reply_to) when not is_nil(in_reply_to) do
def get_visibility(_, in_reply_to, _) when not is_nil(in_reply_to) do
visibility = get_replied_to_visibility(in_reply_to)
{visibility, visibility}
end
def get_visibility(_, in_reply_to), do: {"public", get_replied_to_visibility(in_reply_to)}
def get_visibility(_, in_reply_to, _), do: {"public", get_replied_to_visibility(in_reply_to)}
def get_replied_to_visibility(nil), do: nil
@ -201,7 +206,9 @@ defmodule Pleroma.Web.CommonAPI do
with status <- String.trim(status),
attachments <- attachments_from_ids(data),
in_reply_to <- get_replied_to_activity(data["in_reply_to_status_id"]),
{visibility, in_reply_to_visibility} <- get_visibility(data, in_reply_to),
in_reply_to_conversation <- Participation.get(data["in_reply_to_conversation_id"]),
{visibility, in_reply_to_visibility} <-
get_visibility(data, in_reply_to, in_reply_to_conversation),
{_, false} <-
{:private_to_public, in_reply_to_visibility == "direct" && visibility != "direct"},
{content_html, mentions, tags} <-
@ -214,8 +221,9 @@ defmodule Pleroma.Web.CommonAPI do
mentioned_users <- for({_, mentioned_user} <- mentions, do: mentioned_user.ap_id),
addressed_users <- get_addressed_users(mentioned_users, data["to"]),
{poll, poll_emoji} <- make_poll_data(data),
{to, cc} <- get_to_and_cc(user, addressed_users, in_reply_to, visibility),
context <- make_context(in_reply_to),
{to, cc} <-
get_to_and_cc(user, addressed_users, in_reply_to, visibility, in_reply_to_conversation),
context <- make_context(in_reply_to, in_reply_to_conversation),
cw <- data["spoiler_text"] || "",
sensitive <- data["sensitive"] || Enum.member?(tags, {"#nsfw", "nsfw"}),
full_payload <- String.trim(status <> cw),

View File

@ -8,6 +8,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
alias Calendar.Strftime
alias Pleroma.Activity
alias Pleroma.Config
alias Pleroma.Conversation.Participation
alias Pleroma.Formatter
alias Pleroma.Object
alias Pleroma.Plugs.AuthenticationPlug
@ -47,31 +48,60 @@ defmodule Pleroma.Web.CommonAPI.Utils do
def get_replied_to_activity(_), do: nil
def attachments_from_ids(data) do
if Map.has_key?(data, "descriptions") do
attachments_from_ids_descs(data["media_ids"], data["descriptions"])
else
attachments_from_ids_no_descs(data["media_ids"])
end
def attachments_from_ids(%{"media_ids" => ids, "descriptions" => desc} = _) do
attachments_from_ids_descs(ids, desc)
end
def attachments_from_ids_no_descs(ids) do
Enum.map(ids || [], fn media_id ->
Repo.get(Object, media_id).data
end)
def attachments_from_ids(%{"media_ids" => ids} = _) do
attachments_from_ids_no_descs(ids)
end
def attachments_from_ids(_), do: []
def attachments_from_ids_no_descs([]), do: []
def attachments_from_ids_no_descs(ids) do
Enum.map(ids, fn media_id ->
case Repo.get(Object, media_id) do
%Object{data: data} = _ -> data
_ -> nil
end
end)
|> Enum.filter(& &1)
end
def attachments_from_ids_descs([], _), do: []
def attachments_from_ids_descs(ids, descs_str) do
{_, descs} = Jason.decode(descs_str)
Enum.map(ids || [], fn media_id ->
Map.put(Repo.get(Object, media_id).data, "name", descs[media_id])
Enum.map(ids, fn media_id ->
case Repo.get(Object, media_id) do
%Object{data: data} = _ ->
Map.put(data, "name", descs[media_id])
_ ->
nil
end
end)
|> Enum.filter(& &1)
end
@spec get_to_and_cc(User.t(), list(String.t()), Activity.t() | nil, String.t()) ::
@spec get_to_and_cc(
User.t(),
list(String.t()),
Activity.t() | nil,
String.t(),
Participation.t() | nil
) ::
{list(String.t()), list(String.t())}
def get_to_and_cc(user, mentioned_users, inReplyTo, "public") do
def get_to_and_cc(_, _, _, _, %Participation{} = participation) do
participation = Repo.preload(participation, :recipients)
{Enum.map(participation.recipients, & &1.ap_id), []}
end
def get_to_and_cc(user, mentioned_users, inReplyTo, "public", _) do
to = [Pleroma.Constants.as_public() | mentioned_users]
cc = [user.follower_address]
@ -82,7 +112,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
end
end
def get_to_and_cc(user, mentioned_users, inReplyTo, "unlisted") do
def get_to_and_cc(user, mentioned_users, inReplyTo, "unlisted", _) do
to = [user.follower_address | mentioned_users]
cc = [Pleroma.Constants.as_public()]
@ -93,12 +123,12 @@ defmodule Pleroma.Web.CommonAPI.Utils do
end
end
def get_to_and_cc(user, mentioned_users, inReplyTo, "private") do
{to, cc} = get_to_and_cc(user, mentioned_users, inReplyTo, "direct")
def get_to_and_cc(user, mentioned_users, inReplyTo, "private", _) do
{to, cc} = get_to_and_cc(user, mentioned_users, inReplyTo, "direct", nil)
{[user.follower_address | to], cc}
end
def get_to_and_cc(_user, mentioned_users, inReplyTo, "direct") do
def get_to_and_cc(_user, mentioned_users, inReplyTo, "direct", _) do
if inReplyTo do
{Enum.uniq([inReplyTo.data["actor"] | mentioned_users]), []}
else
@ -106,7 +136,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
end
end
def get_to_and_cc(_user, mentions, _inReplyTo, {:list, _}), do: {mentions, []}
def get_to_and_cc(_user, mentions, _inReplyTo, {:list, _}, _), do: {mentions, []}
def get_addressed_users(_, to) when is_list(to) do
User.get_ap_ids_by_nicknames(to)
@ -236,8 +266,12 @@ defmodule Pleroma.Web.CommonAPI.Utils do
defp maybe_add_nsfw_tag(data, _), do: data
def make_context(%Activity{data: %{"context" => context}}), do: context
def make_context(_), do: Utils.generate_context_id()
def make_context(_, %Participation{} = participation) do
Repo.preload(participation, :conversation).conversation.ap_id
end
def make_context(%Activity{data: %{"context" => context}}, _), do: context
def make_context(_, _), do: Utils.generate_context_id()
def maybe_add_attachments(parsed, _attachments, true = _no_links), do: parsed
@ -247,20 +281,18 @@ defmodule Pleroma.Web.CommonAPI.Utils do
end
def add_attachments(text, attachments) do
attachment_text =
Enum.map(attachments, fn
%{"url" => [%{"href" => href} | _]} = attachment ->
name = attachment["name"] || URI.decode(Path.basename(href))
href = MediaProxy.url(href)
"<a href=\"#{href}\" class='attachment'>#{shortname(name)}</a>"
_ ->
""
end)
attachment_text = Enum.map(attachments, &build_attachment_link/1)
Enum.join([text | attachment_text], "<br>")
end
defp build_attachment_link(%{"url" => [%{"href" => href} | _]} = attachment) do
name = attachment["name"] || URI.decode(Path.basename(href))
href = MediaProxy.url(href)
"<a href=\"#{href}\" class='attachment'>#{shortname(name)}</a>"
end
defp build_attachment_link(_), do: ""
def format_input(text, format, options \\ [])
@doc """
@ -320,7 +352,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
sensitive \\ false,
merge \\ %{}
) do
object = %{
%{
"type" => "Note",
"to" => to,
"cc" => cc,
@ -330,18 +362,20 @@ defmodule Pleroma.Web.CommonAPI.Utils do
"context" => context,
"attachment" => attachments,
"actor" => actor,
"tag" => tags |> Enum.map(fn {_, tag} -> tag end) |> Enum.uniq()
"tag" => Keyword.values(tags) |> Enum.uniq()
}
|> add_in_reply_to(in_reply_to)
|> Map.merge(merge)
end
object =
with false <- is_nil(in_reply_to),
%Object{} = in_reply_to_object <- Object.normalize(in_reply_to) do
Map.put(object, "inReplyTo", in_reply_to_object.data["id"])
else
_ -> object
end
defp add_in_reply_to(object, nil), do: object
Map.merge(object, merge)
defp add_in_reply_to(object, in_reply_to) do
with %Object{} = in_reply_to_object <- Object.normalize(in_reply_to) do
Map.put(object, "inReplyTo", in_reply_to_object.data["id"])
else
_ -> object
end
end
def format_naive_asctime(date) do
@ -373,17 +407,16 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|> String.replace(~r/(\.\d+)?$/, ".000Z", global: false)
end
def to_masto_date(date) do
try do
date
|> NaiveDateTime.from_iso8601!()
|> NaiveDateTime.to_iso8601()
|> String.replace(~r/(\.\d+)?$/, ".000Z", global: false)
rescue
_e -> ""
def to_masto_date(date) when is_binary(date) do
with {:ok, date} <- NaiveDateTime.from_iso8601(date) do
to_masto_date(date)
else
_ -> ""
end
end
def to_masto_date(_), do: ""
defp shortname(name) do
if String.length(name) < 30 do
name
@ -428,7 +461,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
object_data =
cond do
!is_nil(object) ->
not is_nil(object) ->
object.data
is_map(data["object"]) ->
@ -472,9 +505,9 @@ defmodule Pleroma.Web.CommonAPI.Utils do
def maybe_extract_mentions(%{"tag" => tag}) do
tag
|> Enum.filter(fn x -> is_map(x) end)
|> Enum.filter(fn x -> x["type"] == "Mention" end)
|> Enum.filter(fn x -> is_map(x) && x["type"] == "Mention" end)
|> Enum.map(fn x -> x["href"] end)
|> Enum.uniq()
end
def maybe_extract_mentions(_), do: []

View File

@ -33,4 +33,80 @@ defmodule Pleroma.Web.ControllerHelper do
end
defp param_to_integer(_, default), do: default
def add_link_headers(
conn,
method,
activities,
param \\ nil,
params \\ %{},
func3 \\ nil,
func4 \\ nil
) do
params =
conn.params
|> Map.drop(["since_id", "max_id", "min_id"])
|> Map.merge(params)
last = List.last(activities)
func3 = func3 || (&mastodon_api_url/3)
func4 = func4 || (&mastodon_api_url/4)
if last do
max_id = last.id
limit =
params
|> Map.get("limit", "20")
|> String.to_integer()
min_id =
if length(activities) <= limit do
activities
|> List.first()
|> Map.get(:id)
else
activities
|> Enum.at(limit * -1)
|> Map.get(:id)
end
{next_url, prev_url} =
if param do
{
func4.(
Pleroma.Web.Endpoint,
method,
param,
Map.merge(params, %{max_id: max_id})
),
func4.(
Pleroma.Web.Endpoint,
method,
param,
Map.merge(params, %{min_id: min_id})
)
}
else
{
func3.(
Pleroma.Web.Endpoint,
method,
Map.merge(params, %{max_id: max_id})
),
func3.(
Pleroma.Web.Endpoint,
method,
Map.merge(params, %{min_id: min_id})
)
}
end
conn
|> put_resp_header("link", "<#{next_url}>; rel=\"next\", <#{prev_url}>; rel=\"prev\"")
else
conn
end
end
end

View File

@ -13,7 +13,7 @@ defmodule Pleroma.Web.Federator.RetryQueue do
{:ok, %{args | queue_table: queue_table, running_jobs: :sets.new()}}
end
def start_link do
def start_link(_) do
enabled =
if Pleroma.Config.get(:env) == :test,
do: true,

View File

@ -0,0 +1,20 @@
defmodule Pleroma.Web.Mailer.SubscriptionController do
use Pleroma.Web, :controller
alias Pleroma.JWT
alias Pleroma.Repo
alias Pleroma.User
def unsubscribe(conn, %{"token" => encoded_token}) do
with {:ok, token} <- Base.decode64(encoded_token),
{:ok, claims} <- JWT.verify_and_validate(token),
%{"act" => %{"unsubscribe" => type}, "sub" => uid} <- claims,
%User{} = user <- Repo.get(User, uid),
{:ok, _user} <- User.switch_email_notifications(user, type, false) do
render(conn, "unsubscribe_success.html", email: user.email)
else
_err ->
render(conn, "unsubscribe_failure.html")
end
end
end

View File

@ -13,10 +13,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
alias Pleroma.User
alias Pleroma.Web.CommonAPI
@spec follow(User.t(), User.t(), map) :: {:ok, User.t()} | {:error, String.t()}
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)
@ -24,19 +22,25 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
{: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
with {:ok, follower, _followed, _} <- result do
options = cast_params(params)
case reblogs_visibility(options[:reblogs], result) do
{:ok, follower} -> {:ok, follower}
_ -> {:ok, follower}
end
end
end
defp reblogs_visibility(false, {:ok, follower, followed, _}) do
CommonAPI.hide_reblogs(follower, followed)
end
defp reblogs_visibility(_, {:ok, follower, followed, _}) do
CommonAPI.show_reblogs(follower, followed)
end
@spec get_followers(User.t(), map()) :: list(User.t())
def get_followers(user, params \\ %{}) do
user
|> User.get_followers_query()

View File

@ -5,7 +5,8 @@
defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
import Pleroma.Web.ControllerHelper,
only: [json_response: 3, add_link_headers: 5, add_link_headers: 4, add_link_headers: 3]
alias Ecto.Changeset
alias Pleroma.Activity
@ -342,71 +343,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
json(conn, mastodon_emoji)
end
defp add_link_headers(conn, method, activities, param \\ nil, params \\ %{}) do
params =
conn.params
|> Map.drop(["since_id", "max_id", "min_id"])
|> Map.merge(params)
last = List.last(activities)
if last do
max_id = last.id
limit =
params
|> Map.get("limit", "20")
|> String.to_integer()
min_id =
if length(activities) <= limit do
activities
|> List.first()
|> Map.get(:id)
else
activities
|> Enum.at(limit * -1)
|> Map.get(:id)
end
{next_url, prev_url} =
if param do
{
mastodon_api_url(
Pleroma.Web.Endpoint,
method,
param,
Map.merge(params, %{max_id: max_id})
),
mastodon_api_url(
Pleroma.Web.Endpoint,
method,
param,
Map.merge(params, %{min_id: min_id})
)
}
else
{
mastodon_api_url(
Pleroma.Web.Endpoint,
method,
Map.merge(params, %{max_id: max_id})
),
mastodon_api_url(
Pleroma.Web.Endpoint,
method,
Map.merge(params, %{min_id: min_id})
)
}
end
conn
|> put_resp_header("link", "<#{next_url}>; rel=\"next\", <#{prev_url}>; rel=\"prev\"")
else
conn
end
end
def home_timeline(%{assigns: %{user: user}} = conn, params) do
params =
params
@ -435,6 +371,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|> Map.put("local_only", local_only)
|> Map.put("blocking_user", user)
|> Map.put("muting_user", user)
|> Map.put("user", user)
|> ActivityPub.fetch_public_activities()
|> Enum.reverse()
@ -496,12 +433,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
activities <-
ActivityPub.fetch_activities_for_context(activity.data["context"], %{
"blocking_user" => user,
"user" => user
"user" => user,
"exclude_id" => activity.id
}),
activities <-
activities |> Enum.filter(fn %{id: aid} -> to_string(aid) != to_string(id) end),
activities <-
activities |> Enum.filter(fn %{data: %{"type" => type}} -> type == "Create" end),
grouped_activities <- Enum.group_by(activities, fn %{id: id} -> id < activity.id end) do
result = %{
ancestors:
@ -536,8 +470,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|> put_view(StatusView)
|> try_render("poll.json", %{object: object, for: user})
else
nil -> render_error(conn, :not_found, "Record not found")
false -> render_error(conn, :not_found, "Record not found")
error when is_nil(error) or error == false ->
render_error(conn, :not_found, "Record not found")
end
end
@ -885,8 +819,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with %Activity{data: %{"object" => object}} <- Activity.get_by_id(id),
%Object{data: %{"likes" => likes}} <- Object.normalize(object) do
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
%Object{data: %{"likes" => likes}} <- Object.normalize(activity) do
q = from(u in User, where: u.ap_id in ^likes)
users =
@ -902,8 +836,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
def reblogged_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with %Activity{data: %{"object" => object}} <- Activity.get_by_id(id),
%Object{data: %{"announcements" => announces}} <- Object.normalize(object) do
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
%Object{data: %{"announcements" => announces}} <- Object.normalize(activity) do
q = from(u in User, where: u.ap_id in ^announces)
users =
@ -944,6 +878,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|> Map.put("local_only", local_only)
|> Map.put("blocking_user", user)
|> Map.put("muting_user", user)
|> Map.put("user", user)
|> Map.put("tag", tags)
|> Map.put("tag_all", tag_all)
|> Map.put("tag_reject", tag_reject)
@ -1350,6 +1285,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
params
|> Map.put("type", "Create")
|> Map.put("blocking_user", user)
|> Map.put("user", user)
|> Map.put("muting_user", user)
# we must filter the following list for the user to avoid leaking statuses the user
@ -1690,45 +1626,35 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|> String.replace("{{user}}", user)
with {:ok, %{status: 200, body: body}} <-
HTTP.get(
url,
[],
adapter: [
recv_timeout: timeout,
pool: :default
]
),
HTTP.get(url, [], adapter: [recv_timeout: timeout, pool: :default]),
{:ok, data} <- Jason.decode(body) do
data =
data
|> Enum.slice(0, limit)
|> Enum.map(fn x ->
Map.put(
x,
"id",
case User.get_or_fetch(x["acct"]) do
{:ok, %User{id: id}} -> id
_ -> 0
end
)
end)
|> Enum.map(fn x ->
Map.put(x, "avatar", MediaProxy.url(x["avatar"]))
end)
|> Enum.map(fn x ->
Map.put(x, "avatar_static", MediaProxy.url(x["avatar_static"]))
x
|> Map.put("id", fetch_suggestion_id(x))
|> Map.put("avatar", MediaProxy.url(x["avatar"]))
|> Map.put("avatar_static", MediaProxy.url(x["avatar_static"]))
end)
conn
|> json(data)
json(conn, data)
else
e -> Logger.error("Could not retrieve suggestions at fetch #{url}, #{inspect(e)}")
e ->
Logger.error("Could not retrieve suggestions at fetch #{url}, #{inspect(e)}")
end
else
json(conn, [])
end
end
defp fetch_suggestion_id(attrs) do
case User.get_or_fetch(attrs["acct"]) do
{:ok, %User{id: id}} -> id
_ -> 0
end
end
def status_card(%{assigns: %{user: user}} = conn, %{"id" => status_id}) do
with %Activity{} = activity <- Activity.get_by_id(status_id),
true <- Visibility.visible_for_user?(activity, user) do
@ -1807,7 +1733,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
conversations =
Enum.map(participations, fn participation ->
ConversationView.render("participation.json", %{participation: participation, user: user})
ConversationView.render("participation.json", %{participation: participation, for: user})
end)
conn
@ -1820,7 +1746,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
Repo.get_by(Participation, id: participation_id, user_id: user.id),
{:ok, participation} <- Participation.mark_as_read(participation) do
participation_view =
ConversationView.render("participation.json", %{participation: participation, user: user})
ConversationView.render("participation.json", %{participation: participation, for: user})
conn
|> json(participation_view)

View File

@ -28,7 +28,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
id: to_string(user.id),
acct: user.nickname,
username: username_from_nickname(user.nickname),
url: user.ap_id
url: User.profile_url(user)
}
end
@ -37,11 +37,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
end
def render("relationship.json", %{user: %User{} = user, target: %User{} = target}) do
follow_activity = Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(user, target)
follow_state = User.get_cached_follow_state(user, target)
requested =
if follow_activity && !User.following?(target, user) do
follow_activity.data["state"] == "pending"
if follow_state && !User.following?(user, target) do
follow_state == "pending"
else
false
end
@ -72,6 +72,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
image = User.avatar_url(user) |> MediaProxy.url()
header = User.banner_url(user) |> MediaProxy.url()
user_info = User.get_cached_user_info(user)
following_count =
((!user.info.hide_follows or opts[:for] == user) && user_info.following_count) || 0
followers_count =
((!user.info.hide_followers or opts[:for] == user) && user_info.follower_count) || 0
bot = (user.info.source_data["type"] || "Person") in ["Application", "Service"]
emojis =
@ -102,11 +109,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
display_name: display_name,
locked: user_info.locked,
created_at: Utils.to_masto_date(user.inserted_at),
followers_count: user_info.follower_count,
following_count: user_info.following_count,
followers_count: followers_count,
following_count: following_count,
statuses_count: user_info.note_count,
note: bio || "",
url: user.ap_id,
url: User.profile_url(user),
avatar: image,
avatar_static: image,
header: header,

View File

@ -11,8 +11,8 @@ defmodule Pleroma.Web.MastodonAPI.ConversationView do
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.StatusView
def render("participation.json", %{participation: participation, user: user}) do
participation = Repo.preload(participation, conversation: :users)
def render("participation.json", %{participation: participation, for: user}) do
participation = Repo.preload(participation, conversation: [], recipients: [])
last_activity_id =
with nil <- participation.last_activity_id do
@ -28,7 +28,7 @@ defmodule Pleroma.Web.MastodonAPI.ConversationView do
# Conversations return all users except the current user.
users =
participation.conversation.users
participation.recipients
|> Enum.reject(&(&1.id == user.id))
accounts =

View File

@ -5,7 +5,11 @@
defmodule Pleroma.Web.MastodonAPI.StatusView do
use Pleroma.Web, :view
require Pleroma.Constants
alias Pleroma.Activity
alias Pleroma.Conversation
alias Pleroma.Conversation.Participation
alias Pleroma.HTML
alias Pleroma.Object
alias Pleroma.Repo
@ -24,19 +28,19 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
defp get_replied_to_activities(activities) do
activities
|> Enum.map(fn
%{data: %{"type" => "Create", "object" => object}} ->
object = Object.normalize(object)
object.data["inReplyTo"] != "" && object.data["inReplyTo"]
%{data: %{"type" => "Create"}} = activity ->
object = Object.normalize(activity)
object && object.data["inReplyTo"] != "" && object.data["inReplyTo"]
_ ->
nil
end)
|> Enum.filter(& &1)
|> Activity.create_by_object_ap_id()
|> Activity.create_by_object_ap_id_with_object()
|> Repo.all()
|> Enum.reduce(%{}, fn activity, acc ->
object = Object.normalize(activity)
Map.put(acc, object.data["id"], activity)
if object, do: Map.put(acc, object.data["id"], activity), else: acc
end)
end
@ -68,12 +72,14 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
def render("index.json", opts) do
replied_to_activities = get_replied_to_activities(opts.activities)
parallel = unless is_nil(opts[:parallel]), do: opts[:parallel], else: true
opts.activities
|> safe_render_many(
StatusView,
"status.json",
Map.put(opts, :replied_to_activities, replied_to_activities)
Map.put(opts, :replied_to_activities, replied_to_activities),
parallel
)
end
@ -88,6 +94,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
reblogged_activity =
Activity.create_by_object_ap_id(activity_object.data["id"])
|> Activity.with_preloaded_bookmark(opts[:for])
|> Activity.with_set_thread_muted_field(opts[:for])
|> Repo.one()
reblogged = render("status.json", Map.put(opts, :activity, reblogged_activity))
@ -142,6 +149,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
object = Object.normalize(activity)
user = get_user(activity.data["actor"])
user_follower_address = user.follower_address
like_count = object.data["like_count"] || 0
announcement_count = object.data["announcement_count"] || 0
@ -157,7 +165,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
mentions =
(object.data["to"] ++ tag_mentions)
|> Enum.uniq()
|> Enum.map(fn ap_id -> User.get_cached_by_ap_id(ap_id) end)
|> Enum.map(fn
Pleroma.Constants.as_public() -> nil
^user_follower_address -> nil
ap_id -> User.get_cached_by_ap_id(ap_id)
end)
|> Enum.filter(& &1)
|> Enum.map(fn user -> AccountView.render("mention.json", %{user: user}) end)
@ -168,7 +180,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
thread_muted? =
case activity.thread_muted? do
thread_muted? when is_boolean(thread_muted?) -> thread_muted?
nil -> CommonAPI.thread_muted?(user, activity)
nil -> (opts[:for] && CommonAPI.thread_muted?(opts[:for], activity)) || false
end
attachment_data = object.data["attachment"] || []
@ -225,6 +237,19 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
object.data["url"] || object.data["external_url"] || object.data["id"]
end
direct_conversation_id =
with {_, true} <- {:include_id, opts[:with_direct_conversation_id]},
{_, %User{} = for_user} <- {:for_user, opts[:for]},
%{data: %{"context" => context}} when is_binary(context) <- activity,
%Conversation{} = conversation <- Conversation.get_for_ap_id(context),
%Participation{id: participation_id} <-
Participation.for_user_and_conversation(for_user, conversation) do
participation_id
else
_e ->
nil
end
%{
id: to_string(activity.id),
uri: object.data["id"],
@ -262,7 +287,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
conversation_id: get_context_id(activity),
in_reply_to_account_acct: reply_to_user && reply_to_user.nickname,
content: %{"text/plain" => content_plaintext},
spoiler_text: %{"text/plain" => summary_plaintext}
spoiler_text: %{"text/plain" => summary_plaintext},
direct_conversation_id: direct_conversation_id
}
}
end

View File

@ -4,6 +4,7 @@
defmodule Pleroma.Web.MediaProxy do
alias Pleroma.Config
alias Pleroma.Upload
alias Pleroma.Web
@base64_opts [padding: false]
@ -26,7 +27,18 @@ defmodule Pleroma.Web.MediaProxy do
defp whitelisted?(url) do
%{host: domain} = URI.parse(url)
Enum.any?(Config.get([:media_proxy, :whitelist]), fn pattern ->
mediaproxy_whitelist = Config.get([:media_proxy, :whitelist])
upload_base_url_domain =
if !is_nil(Config.get([Upload, :base_url])) do
[URI.parse(Config.get([Upload, :base_url])).host]
else
[]
end
whitelist = mediaproxy_whitelist ++ upload_base_url_domain
Enum.any?(whitelist, fn pattern ->
String.equivalent?(domain, pattern)
end)
end

View File

@ -34,64 +34,18 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
def raw_nodeinfo do
stats = Stats.get_stats()
exclusions = Config.get([:instance, :mrf_transparency_exclusions])
mrf_simple =
Config.get(:mrf_simple)
|> Enum.map(fn {k, v} -> {k, Enum.reject(v, fn v -> v in exclusions end)} end)
|> Enum.into(%{})
# This horror is needed to convert regex sigils to strings
mrf_keyword =
Config.get(:mrf_keyword, [])
|> Enum.map(fn {key, value} ->
{key,
Enum.map(value, fn
{pattern, replacement} ->
%{
"pattern" =>
if not is_binary(pattern) do
inspect(pattern)
else
pattern
end,
"replacement" => replacement
}
pattern ->
if not is_binary(pattern) do
inspect(pattern)
else
pattern
end
end)}
end)
|> Enum.into(%{})
mrf_policies =
MRF.get_policies()
|> Enum.map(fn policy -> to_string(policy) |> String.split(".") |> List.last() end)
quarantined = Config.get([:instance, :quarantined_instances], [])
staff_accounts =
User.all_superusers()
|> Enum.map(fn u -> u.ap_id end)
mrf_user_allowlist =
Config.get([:mrf_user_allowlist], [])
|> Enum.into(%{}, fn {k, v} -> {k, length(v)} end)
federation_response =
if Config.get([:instance, :mrf_transparency]) do
%{
mrf_policies: mrf_policies,
mrf_simple: mrf_simple,
mrf_keyword: mrf_keyword,
mrf_user_allowlist: mrf_user_allowlist,
quarantined_instances: quarantined,
exclusions: length(exclusions) > 0
}
{:ok, data} = MRF.describe()
data
|> Map.merge(%{quarantined_instances: quarantined})
else
%{}
end

View File

@ -6,36 +6,30 @@ defmodule Pleroma.Web.OAuth.Token.CleanWorker do
@moduledoc """
The module represents functions to clean an expired oauth tokens.
"""
use GenServer
@ten_seconds 10_000
@one_day 86_400_000
# 10 seconds
@start_interval 10_000
@interval Pleroma.Config.get(
# 24 hours
[:oauth2, :clean_expired_tokens_interval],
86_400_000
@one_day
)
@queue :background
alias Pleroma.Web.OAuth.Token
def start_link, do: GenServer.start_link(__MODULE__, nil)
def start_link(_), do: GenServer.start_link(__MODULE__, %{})
def init(_) do
if Pleroma.Config.get([:oauth2, :clean_expired_tokens], false) do
Process.send_after(self(), :perform, @start_interval)
{:ok, nil}
else
:ignore
end
Process.send_after(self(), :perform, @ten_seconds)
{:ok, nil}
end
@doc false
def handle_info(:perform, state) do
Token.delete_expired_tokens()
Process.send_after(self(), :perform, @interval)
PleromaJobQueue.enqueue(@queue, __MODULE__, [:clean])
{:noreply, state}
end
# Job Worker Callbacks
def perform(:clean), do: Token.delete_expired_tokens()
end

View File

@ -183,6 +183,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: []
retweeted_activity = Activity.get_create_by_object_ap_id(activity.data["object"])
retweeted_object = Object.normalize(retweeted_activity)
retweeted_user = User.get_cached_by_ap_id(retweeted_activity.data["actor"])
retweeted_xml = to_simple_form(retweeted_activity, retweeted_user, true)
@ -197,7 +198,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
{:"activity:verb", ['http://activitystrea.ms/schema/1.0/share']},
{:id, h.(activity.data["id"])},
{:title, ['#{user.nickname} repeated a notice']},
{:content, [type: 'html'], ['RT #{retweeted_activity.data["object"]["content"]}']},
{:content, [type: 'html'], ['RT #{retweeted_object.data["content"]}']},
{:published, h.(inserted_at)},
{:updated, h.(updated_at)},
{:"ostatus:conversation", [ref: h.(activity.data["context"])],

View File

@ -0,0 +1,73 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 7]
alias Pleroma.Conversation.Participation
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.MastodonAPI.ConversationView
alias Pleroma.Web.MastodonAPI.StatusView
def conversation(%{assigns: %{user: user}} = conn, %{"id" => participation_id}) do
with %Participation{} = participation <- Participation.get(participation_id),
true <- user.id == participation.user_id do
conn
|> put_view(ConversationView)
|> render("participation.json", %{participation: participation, for: user})
end
end
def conversation_statuses(
%{assigns: %{user: user}} = conn,
%{"id" => participation_id} = params
) do
params =
params
|> Map.put("blocking_user", user)
|> Map.put("muting_user", user)
|> Map.put("user", user)
participation =
participation_id
|> Participation.get(preload: [:conversation])
if user.id == participation.user_id do
activities =
participation.conversation.ap_id
|> ActivityPub.fetch_activities_for_context(params)
|> Enum.reverse()
conn
|> add_link_headers(
:conversation_statuses,
activities,
participation_id,
params,
nil,
&pleroma_api_url/4
)
|> put_view(StatusView)
|> render("index.json", %{activities: activities, for: user, as: :activity})
end
end
def update_conversation(
%{assigns: %{user: user}} = conn,
%{"id" => participation_id, "recipients" => recipients}
) do
participation =
participation_id
|> Participation.get()
with true <- user.id == participation.user_id,
{:ok, participation} <- Participation.set_recipients(participation, recipients) do
conn
|> put_view(ConversationView)
|> render("participation.json", %{participation: participation, for: user})
end
end
end

View File

@ -3,13 +3,20 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.RichMedia.Parsers.TwitterCard do
alias Pleroma.Web.RichMedia.Parsers.MetaTagsParser
@spec parse(String.t(), map()) :: {:ok, map()} | {:error, String.t()}
def parse(html, data) do
Pleroma.Web.RichMedia.Parsers.MetaTagsParser.parse(
html,
data,
"twitter",
"No twitter card metadata found",
"name"
)
data
|> parse_name_attrs(html)
|> parse_property_attrs(html)
end
defp parse_name_attrs(data, html) do
MetaTagsParser.parse(html, data, "twitter", %{}, "name")
end
defp parse_property_attrs({_, data}, html) do
MetaTagsParser.parse(html, data, "twitter", "No twitter card metadata found", "property")
end
end

View File

@ -259,6 +259,21 @@ defmodule Pleroma.Web.Router do
end
end
scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do
pipe_through(:authenticated_api)
scope [] do
pipe_through(:oauth_read)
get("/conversations/:id/statuses", PleromaAPIController, :conversation_statuses)
get("/conversations/:id", PleromaAPIController, :conversation)
end
scope [] do
pipe_through(:oauth_write)
patch("/conversations/:id", PleromaAPIController, :update_conversation)
end
end
scope "/api/v1", Pleroma.Web.MastodonAPI do
pipe_through(:authenticated_api)
@ -608,6 +623,8 @@ defmodule Pleroma.Web.Router do
post("/push/hub/:nickname", Websub.WebsubController, :websub_subscription_request)
get("/push/subscriptions/:id", Websub.WebsubController, :websub_subscription_confirmation)
post("/push/subscriptions/:id", Websub.WebsubController, :websub_incoming)
get("/mailer/unsubscribe/:token", Mailer.SubscriptionController, :unsubscribe)
end
pipeline :activitypub do

View File

@ -18,7 +18,7 @@ defmodule Pleroma.Web.Streamer do
@keepalive_interval :timer.seconds(30)
def start_link do
def start_link(_) do
GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
end
@ -35,28 +35,21 @@ defmodule Pleroma.Web.Streamer do
end
def init(args) do
spawn(fn ->
# 30 seconds
Process.sleep(@keepalive_interval)
GenServer.cast(__MODULE__, %{action: :ping})
end)
Process.send_after(self(), %{action: :ping}, @keepalive_interval)
{:ok, args}
end
def handle_cast(%{action: :ping}, topics) do
Map.values(topics)
def handle_info(%{action: :ping}, topics) do
topics
|> Map.values()
|> List.flatten()
|> Enum.each(fn socket ->
Logger.debug("Sending keepalive ping")
send(socket.transport_pid, {:text, ""})
end)
spawn(fn ->
# 30 seconds
Process.sleep(@keepalive_interval)
GenServer.cast(__MODULE__, %{action: :ping})
end)
Process.send_after(self(), %{action: :ping}, @keepalive_interval)
{:noreply, topics}
end
@ -120,8 +113,7 @@ defmodule Pleroma.Web.Streamer do
|> Map.get("#{topic}:#{item.user_id}", [])
|> Enum.each(fn socket ->
with %User{} = user <- User.get_cached_by_ap_id(socket.assigns[:user].ap_id),
true <- should_send?(user, item),
false <- CommonAPI.thread_muted?(user, item.activity) do
true <- should_send?(user, item) do
send(
socket.transport_pid,
{:text, represent_notification(socket.assigns[:user], item)}
@ -209,7 +201,7 @@ defmodule Pleroma.Web.Streamer do
payload:
Pleroma.Web.MastodonAPI.ConversationView.render("participation.json", %{
participation: participation,
user: participation.user
for: participation.user
})
|> Jason.encode!()
}
@ -243,7 +235,8 @@ defmodule Pleroma.Web.Streamer do
%{host: parent_host} <- URI.parse(parent.data["actor"]),
false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, item_host),
false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, parent_host),
true <- thread_containment(item, user) do
true <- thread_containment(item, user),
false <- CommonAPI.thread_muted?(user, item) do
true
else
_ -> false

View File

@ -0,0 +1,20 @@
<h1>Hey <%= @user.nickname %>, here is what you've missed!</h1>
<h2>New Mentions:</h2>
<ul>
<%= for %{data: mention, object: object, from: from} <- @mentions do %>
<li><%= link from.nickname, to: mention.activity.actor %>: <%= raw object.data["content"] %></li>
<% end %>
</ul>
<%= if @followers != [] do %>
<h2><%= length(@followers) %> New Followers:</h2>
<ul>
<%= for %{data: follow, from: from} <- @followers do %>
<li><%= link from.nickname, to: follow.activity.actor %></li>
<% end %>
</ul>
<% end %>
<p>You have received this email because you have signed up to receive digest emails from <b><%= @instance %></b> Pleroma instance.</p>
<p>The email address you are subscribed as is <%= @user.email %>. To unsubscribe, please go <%= link "here", to: @unsubscribe_link %>.</p>

View File

@ -36,6 +36,11 @@
margin-bottom: 20px;
}
a {
color: color: #d8a070;
text-decoration: none;
}
form {
width: 100%;
}

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title><%= @email.subject %></title>
</head>
<body>
<%= render @view_module, @view_template, assigns %>
</body>
</html>

View File

@ -0,0 +1 @@
<h1>UNSUBSCRIBE FAILURE</h1>

View File

@ -0,0 +1 @@
<h1>UNSUBSCRIBE SUCCESSFUL</h1>

View File

@ -0,0 +1,5 @@
defmodule Pleroma.Web.EmailView do
use Pleroma.Web, :view
import Phoenix.HTML
import Phoenix.HTML.Link
end

View File

@ -0,0 +1,3 @@
defmodule Pleroma.Web.Mailer.SubscriptionView do
use Pleroma.Web, :view
end

View File

@ -58,17 +58,31 @@ defmodule Pleroma.Web do
rescue
error ->
Logger.error(
"#{__MODULE__} failed to render #{inspect({view, template})}: #{inspect(error)}"
"#{__MODULE__} failed to render #{inspect({view, template})}\n" <>
Exception.format(:error, error, __STACKTRACE__)
)
Logger.error(inspect(__STACKTRACE__))
nil
end
@doc """
Same as `render_many/4` but wrapped in rescue block.
Same as `render_many/4` but wrapped in rescue block and parallelized (unless disabled by passing false as a fifth argument).
"""
def safe_render_many(collection, view, template, assigns \\ %{}) do
def safe_render_many(collection, view, template, assigns \\ %{}, parallel \\ true)
def safe_render_many(collection, view, template, assigns, true) do
Enum.map(collection, fn resource ->
Task.async(fn ->
as = Map.get(assigns, :as) || view.__resource__
assigns = Map.put(assigns, as, resource)
safe_render(view, template, assigns)
end)
end)
|> Enum.map(&Task.await(&1, :infinity))
|> Enum.filter(& &1)
end
def safe_render_many(collection, view, template, assigns, false) do
Enum.map(collection, fn resource ->
as = Map.get(assigns, :as) || view.__resource__
assigns = Map.put(assigns, as, resource)

View File

@ -95,7 +95,7 @@ defmodule Pleroma.Mixfile do
defp deps do
[
{:phoenix, "~> 1.4.8"},
{:tzdata, "~> 1.0"},
{:tzdata, "~> 0.5.21"},
{:plug_cowboy, "~> 2.0"},
{:phoenix_pubsub, "~> 1.1"},
{:phoenix_ecto, "~> 4.0"},
@ -114,8 +114,9 @@ defmodule Pleroma.Mixfile do
{:tesla, "~> 1.2"},
{:jason, "~> 1.0"},
{:mogrify, "~> 0.6.1"},
{:ex_aws, "~> 2.0"},
{:ex_aws, "~> 2.1"},
{:ex_aws_s3, "~> 2.0"},
{:sweet_xml, "~> 0.6.6"},
{:earmark, "~> 1.3"},
{:bbcode, "~> 0.1.1"},
{:ex_machina, "~> 2.3", only: :test},
@ -127,6 +128,7 @@ defmodule Pleroma.Mixfile do
{:ex_doc, "~> 0.20.2", only: :dev, runtime: false},
{:web_push_encryption, "~> 0.2.1"},
{:swoosh, "~> 0.23.2"},
{:phoenix_swoosh, "~> 0.2"},
{:gen_smtp, "~> 0.13"},
{:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test},
{:floki, "~> 0.20.0"},
@ -139,7 +141,7 @@ defmodule Pleroma.Mixfile do
{:http_signatures,
git: "https://git.pleroma.social/pleroma/http_signatures.git",
ref: "293d77bb6f4a67ac8bde1428735c3b42f22cbb30"},
{:pleroma_job_queue, "~> 0.2.0"},
{:pleroma_job_queue, "~> 0.3"},
{:telemetry, "~> 0.3"},
{:prometheus_ex, "~> 3.0"},
{:prometheus_plugs, "~> 1.1"},
@ -147,6 +149,7 @@ defmodule Pleroma.Mixfile do
{:prometheus_ecto, "~> 1.4"},
{:recon, github: "ferd/recon", tag: "2.4.0"},
{:quack, "~> 0.1.1"},
{:joken, "~> 2.0"},
{:benchee, "~> 1.0"},
{:esshd, "~> 0.1.0", runtime: Application.get_env(:esshd, :enabled, false)},
{:ex_rated, "~> 1.3"},

Some files were not shown because too many files have changed in this diff Show More