summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.buildpacks1
-rw-r--r--.gitlab-ci.yml49
-rw-r--r--CHANGELOG.md7
-rw-r--r--Procfile2
-rw-r--r--config/config.exs12
-rw-r--r--config/dokku.exs25
-rw-r--r--docs/api/pleroma_api.md14
-rw-r--r--docs/config.md7
-rw-r--r--elixir_buildpack.config2
-rwxr-xr-xinstallation/pleroma-mongooseim.cfg932
-rw-r--r--lib/pleroma/conversation.ex2
-rw-r--r--lib/pleroma/notification.ex50
-rw-r--r--lib/pleroma/object.ex33
-rw-r--r--lib/pleroma/reverse_proxy.ex6
-rw-r--r--lib/pleroma/user.ex2
-rw-r--r--lib/pleroma/user/info.ex23
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub.ex109
-rw-r--r--lib/pleroma/web/activity_pub/transmogrifier.ex41
-rw-r--r--lib/pleroma/web/activity_pub/utils.ex19
-rw-r--r--lib/pleroma/web/activity_pub/visibility.ex3
-rw-r--r--lib/pleroma/web/common_api/common_api.ex53
-rw-r--r--lib/pleroma/web/common_api/utils.ex95
-rw-r--r--lib/pleroma/web/mastodon_api/mastodon_api_controller.ex137
-rw-r--r--lib/pleroma/web/mastodon_api/views/conversation_view.ex7
-rw-r--r--lib/pleroma/web/mastodon_api/views/status_view.ex59
-rw-r--r--lib/pleroma/web/router.ex8
-rw-r--r--lib/pleroma/web/templates/layout/app.html.eex44
-rw-r--r--lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex8
-rw-r--r--lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex16
-rw-r--r--lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex4
-rw-r--r--lib/pleroma/web/templates/o_auth/o_auth/show.html.eex34
-rw-r--r--lib/pleroma/web/twitter_api/views/user_view.ex7
-rw-r--r--mix.exs25
-rw-r--r--priv/repo/migrations/20190525071417_add_non_follows_and_non_followers_fields_to_notification_settings.exs10
-rw-r--r--priv/repo/migrations/20190603162018_add_object_in_reply_to_index.exs7
-rw-r--r--priv/repo/migrations/20190603173419_add_tag_index_to_objects.exs8
-rw-r--r--test/fixtures/httpoison_mock/emelie.json1
-rw-r--r--test/fixtures/httpoison_mock/rinpatch.json64
-rw-r--r--test/fixtures/mastodon-question-activity.json99
-rw-r--r--test/fixtures/mastodon-vote.json16
-rw-r--r--test/notification_test.exs41
-rw-r--r--test/support/http_request_mock.ex8
-rw-r--r--test/web/activity_pub/activity_pub_controller_test.exs71
-rw-r--r--test/web/activity_pub/activity_pub_test.exs29
-rw-r--r--test/web/activity_pub/transmogrifier_test.exs130
-rw-r--r--test/web/activity_pub/visibilty_test.exs4
-rw-r--r--test/web/mastodon_api/account_view_test.exs6
-rw-r--r--test/web/mastodon_api/mastodon_api_controller_test.exs251
-rw-r--r--test/web/mastodon_api/status_view_test.exs103
-rw-r--r--test/web/twitter_api/util_controller_test.exs9
-rw-r--r--test/web/twitter_api/views/user_view_test.exs2
51 files changed, 2417 insertions, 278 deletions
diff --git a/.buildpacks b/.buildpacks
new file mode 100644
index 000000000..31dd57096
--- /dev/null
+++ b/.buildpacks
@@ -0,0 +1 @@
+https://github.com/hashnuke/heroku-buildpack-elixir
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 8b5131dc3..58c9de167 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -52,8 +52,7 @@ unit-testing:
- mix deps.get
- mix ecto.create
- mix ecto.migrate
- - mix test --trace --preload-modules
- - mix coveralls
+ - mix coveralls --trace --preload-modules
unit-testing-rum:
stage: test
@@ -95,3 +94,49 @@ docs-deploy:
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- rsync -hrvz --delete -e "ssh -p ${SSH_PORT}" priv/static/doc/ "${SSH_USER_HOST_LOCATION}/${CI_COMMIT_REF_NAME}"
+
+review_app:
+ image: alpine:3.9
+ stage: deploy
+ before_script:
+ - apk update && apk add openssh-client git
+ when: manual
+ environment:
+ name: review/$CI_COMMIT_REF_NAME
+ url: https://$CI_ENVIRONMENT_SLUG.pleroma.online/
+ on_stop: stop_review_app
+ only:
+ - branches
+ except:
+ - master
+ - develop
+ script:
+ - echo "$CI_ENVIRONMENT_SLUG"
+ - mkdir -p ~/.ssh
+ - eval $(ssh-agent -s)
+ - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
+ - ssh-keyscan -H "pleroma.online" >> ~/.ssh/known_hosts
+ - (ssh -t dokku@pleroma.online -- apps:create "$CI_ENVIRONMENT_SLUG") || true
+ - ssh -t dokku@pleroma.online -- config:set "$CI_ENVIRONMENT_SLUG" APP_NAME="$CI_ENVIRONMENT_SLUG" APP_HOST="$CI_ENVIRONMENT_SLUG.pleroma.online" MIX_ENV=dokku
+ - (ssh -t dokku@pleroma.online -- postgres:create $(echo $CI_ENVIRONMENT_SLUG | sed -e 's/-/_/g')_db) || true
+ - (ssh -t dokku@pleroma.online -- postgres:link $(echo $CI_ENVIRONMENT_SLUG | sed -e 's/-/_/g')_db "$CI_ENVIRONMENT_SLUG") || true
+ - (ssh -t dokku@pleroma.online -- certs:add "$CI_ENVIRONMENT_SLUG" /home/dokku/server.crt /home/dokku/server.key) || true
+ - git push -f dokku@pleroma.online:$CI_ENVIRONMENT_SLUG $CI_COMMIT_SHA:refs/heads/master
+
+stop_review_app:
+ image: alpine:3.9
+ stage: deploy
+ before_script:
+ - apk update && apk add openssh-client git
+ when: manual
+ environment:
+ name: review/$CI_COMMIT_REF_NAME
+ action: stop
+ script:
+ - echo "$CI_ENVIRONMENT_SLUG"
+ - mkdir -p ~/.ssh
+ - eval $(ssh-agent -s)
+ - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
+ - ssh-keyscan -H "pleroma.online" >> ~/.ssh/known_hosts
+ - ssh -t dokku@pleroma.online -- --force apps:destroy "$CI_ENVIRONMENT_SLUG"
+ - ssh -t dokku@pleroma.online -- --force postgres:destroy $(echo $CI_ENVIRONMENT_SLUG | sed -e 's/-/_/g')_db
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f8ed529ae..9d4ec8b67 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,7 +17,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Mix Tasks: `mix pleroma.database remove_embedded_objects`
- Mix Tasks: `mix pleroma.database update_users_following_followers_counts`
- Mix Tasks: `mix pleroma.user toggle_confirmed`
+- Federation: Support for `Question` and `Answer` objects
- Federation: Support for reports
+- Configuration: `poll_limits` option
- Configuration: `safe_dm_mentions` option
- Configuration: `link_name` option
- Configuration: `fetch_initial_posts` option
@@ -38,6 +40,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Mastodon API: `/api/v1/pleroma/accounts/:id/favourites` (API extension)
- Mastodon API: [Reports](https://docs.joinmastodon.org/api/rest/reports/)
- Mastodon API: `POST /api/v1/accounts` (account creation API)
+- Mastodon API: [Polls](https://docs.joinmastodon.org/api/rest/polls/)
- ActivityPub C2S: OAuth endpoints
- Metadata: RelMe provider
- OAuth: added support for refresh tokens
@@ -120,6 +123,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## Removed
- Configuration: `config :pleroma, :fe` in favor of the more flexible `config :pleroma, :frontend_configurations`
+## [0.9.99999] - 2019-05-31
+### Security
+- Mastodon API: Fix lists leaking private posts
+
## [0.9.9999] - 2019-04-05
### Security
- Mastodon API: Fix content warnings skipping HTML sanitization
diff --git a/Procfile b/Procfile
new file mode 100644
index 000000000..7ac187baa
--- /dev/null
+++ b/Procfile
@@ -0,0 +1,2 @@
+web: mix phx.server
+release: mix ecto.migrate
diff --git a/config/config.exs b/config/config.exs
index 68168b279..2c71f4a27 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -208,6 +208,12 @@ config :pleroma, :instance,
avatar_upload_limit: 2_000_000,
background_upload_limit: 4_000_000,
banner_upload_limit: 4_000_000,
+ poll_limits: %{
+ max_options: 20,
+ max_option_chars: 200,
+ min_expiration: 0,
+ max_expiration: 365 * 24 * 60 * 60
+ },
registrations_open: true,
federating: true,
federation_reachability_timeout_days: 7,
@@ -453,7 +459,11 @@ config :pleroma, :ldap,
config :esshd,
enabled: false
-oauth_consumer_strategies = String.split(System.get_env("OAUTH_CONSUMER_STRATEGIES") || "")
+oauth_consumer_strategies =
+ System.get_env("OAUTH_CONSUMER_STRATEGIES")
+ |> to_string()
+ |> String.split()
+ |> Enum.map(&hd(String.split(&1, ":")))
ueberauth_providers =
for strategy <- oauth_consumer_strategies do
diff --git a/config/dokku.exs b/config/dokku.exs
new file mode 100644
index 000000000..9ea0ec450
--- /dev/null
+++ b/config/dokku.exs
@@ -0,0 +1,25 @@
+use Mix.Config
+
+config :pleroma, Pleroma.Web.Endpoint,
+ http: [
+ port: String.to_integer(System.get_env("PORT") || "4000"),
+ protocol_options: [max_request_line_length: 8192, max_header_value_length: 8192]
+ ],
+ protocol: "http",
+ secure_cookie_flag: false,
+ url: [host: System.get_env("APP_HOST"), scheme: "https", port: 443],
+ secret_key_base: "+S+ULgf7+N37c/lc9K66SMphnjQIRGklTu0BRr2vLm2ZzvK0Z6OH/PE77wlUNtvP"
+
+database_url =
+ System.get_env("DATABASE_URL") ||
+ raise """
+ environment variable DATABASE_URL is missing.
+ For example: ecto://USER:PASS@HOST/DATABASE
+ """
+
+config :pleroma, Pleroma.Repo,
+ # ssl: true,
+ url: database_url,
+ pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")
+
+config :pleroma, :instance, name: "#{System.get_env("APP_NAME")} CI Instance"
diff --git a/docs/api/pleroma_api.md b/docs/api/pleroma_api.md
index 4d99a2d2b..edc62727a 100644
--- a/docs/api/pleroma_api.md
+++ b/docs/api/pleroma_api.md
@@ -126,20 +126,6 @@ Request parameters can be passed via [query strings](https://en.wikipedia.org/wi
## `/api/pleroma/admin/`…
See [Admin-API](Admin-API.md)
-## `/api/v1/pleroma/flavour/:flavour`
-* Method `POST`
-* Authentication: required
-* Response: JSON string. Returns the user flavour or the default one on success, otherwise returns `{"error": "error_msg"}`
-* Example response: "glitch"
-* Note: This is intended to be used only by mastofe
-
-## `/api/v1/pleroma/flavour`
-* Method `GET`
-* Authentication: required
-* Response: JSON string. Returns the user flavour or the default one.
-* Example response: "glitch"
-* Note: This is intended to be used only by mastofe
-
## `/api/pleroma/notifications/read`
### Mark a single notification as read
* Method `POST`
diff --git a/docs/config.md b/docs/config.md
index 67b062fe9..e37ae6b95 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -71,6 +71,11 @@ config :pleroma, Pleroma.Emails.Mailer,
* `avatar_upload_limit`: File size limit of user’s profile avatars
* `background_upload_limit`: File size limit of user’s profile backgrounds
* `banner_upload_limit`: File size limit of user’s profile banners
+* `poll_limits`: A map with poll limits for **local** polls
+ * `max_options`: Maximum number of options
+ * `max_option_chars`: Maximum number of characters per option
+ * `min_expiration`: Minimum expiration time (in seconds)
+ * `max_expiration`: Maximum expiration time (in seconds)
* `registrations_open`: Enable registrations for anyone, invitations can be enabled when false.
* `invites_enabled`: Enable user invitations for admins (depends on `registrations_open: false`).
* `account_activation_required`: Require users to confirm their emails before signing in.
@@ -492,7 +497,7 @@ Authentication / authorization settings.
* `auth_template`: authentication form template. By default it's `show.html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/show.html.eex`.
* `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.
+* `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>`).
## OAuth consumer mode
diff --git a/elixir_buildpack.config b/elixir_buildpack.config
new file mode 100644
index 000000000..c23b08fb8
--- /dev/null
+++ b/elixir_buildpack.config
@@ -0,0 +1,2 @@
+elixir_version=1.8.2
+erlang_version=21.3.7
diff --git a/installation/pleroma-mongooseim.cfg b/installation/pleroma-mongooseim.cfg
new file mode 100755
index 000000000..d7567321f
--- /dev/null
+++ b/installation/pleroma-mongooseim.cfg
@@ -0,0 +1,932 @@
+%%%
+%%% ejabberd configuration file
+%%%
+%%%'
+
+%%% The parameters used in this configuration file are explained in more detail
+%%% in the ejabberd Installation and Operation Guide.
+%%% Please consult the Guide in case of doubts, it is included with
+%%% your copy of ejabberd, and is also available online at
+%%% http://www.process-one.net/en/ejabberd/docs/
+
+%%% This configuration file contains Erlang terms.
+%%% In case you want to understand the syntax, here are the concepts:
+%%%
+%%% - The character to comment a line is %
+%%%
+%%% - Each term ends in a dot, for example:
+%%% override_global.
+%%%
+%%% - A tuple has a fixed definition, its elements are
+%%% enclosed in {}, and separated with commas:
+%%% {loglevel, 4}.
+%%%
+%%% - A list can have as many elements as you want,
+%%% and is enclosed in [], for example:
+%%% [http_poll, web_admin, tls]
+%%%
+%%% Pay attention that list elements are delimited with commas,
+%%% but no comma is allowed after the last list element. This will
+%%% give a syntax error unlike in more lenient languages (e.g. Python).
+%%%
+%%% - A keyword of ejabberd is a word in lowercase.
+%%% Strings are enclosed in "" and can contain spaces, dots, ...
+%%% {language, "en"}.
+%%% {ldap_rootdn, "dc=example,dc=com"}.
+%%%
+%%% - This term includes a tuple, a keyword, a list, and two strings:
+%%% {hosts, ["jabber.example.net", "im.example.com"]}.
+%%%
+%%% - This config is preprocessed during release generation by a tool which
+%%% interprets double curly braces as substitution markers, so avoid this
+%%% syntax in this file (though it's valid Erlang).
+%%%
+%%% So this is OK (though arguably looks quite ugly):
+%%% { {s2s_addr, "example-host.net"}, {127,0,0,1} }.
+%%%
+%%% And I can't give an example of what's not OK exactly because
+%%% of this rule.
+%%%
+
+
+%%%. =======================
+%%%' OVERRIDE STORED OPTIONS
+
+%%
+%% Override the old values stored in the database.
+%%
+
+%%
+%% Override global options (shared by all ejabberd nodes in a cluster).
+%%
+%%override_global.
+
+%%
+%% Override local options (specific for this particular ejabberd node).
+%%
+%%override_local.
+
+%%
+%% Remove the Access Control Lists before new ones are added.
+%%
+%%override_acls.
+
+
+%%%. =========
+%%%' DEBUGGING
+
+%%
+%% loglevel: Verbosity of log files generated by ejabberd.
+%% 0: No ejabberd log at all (not recommended)
+%% 1: Critical
+%% 2: Error
+%% 3: Warning
+%% 4: Info
+%% 5: Debug
+%%
+{loglevel, 3}.
+
+%%%. ================
+%%%' SERVED HOSTNAMES
+
+%%
+%% hosts: Domains served by ejabberd.
+%% You can define one or several, for example:
+%% {hosts, ["example.net", "example.com", "example.org"]}.
+%%
+{hosts, ["pleroma.soykaf.com"] }.
+
+%%
+%% route_subdomains: Delegate subdomains to other XMPP servers.
+%% For example, if this ejabberd serves example.org and you want
+%% to allow communication with an XMPP server called im.example.org.
+%%
+%%{route_subdomains, s2s}.
+
+
+%%%. ===============
+%%%' LISTENING PORTS
+
+%%
+%% listen: The ports ejabberd will listen on, which service each is handled
+%% by and what options to start it with.
+%%
+{listen,
+ [
+ %% BOSH and WS endpoints over HTTP
+ { 5280, ejabberd_cowboy, [
+ {num_acceptors, 10},
+ {transport_options, [{max_connections, 1024}]},
+ {modules, [
+
+ {"_", "/http-bind", mod_bosh},
+ {"_", "/ws-xmpp", mod_websockets, [{ejabberd_service, [
+ {access, all},
+ {shaper_rule, fast},
+ {ip, {127, 0, 0, 1}},
+ {password, "secret"}]}
+ %% Uncomment to enable connection dropping or/and server-side pings
+ %{timeout, 600000}, {ping_rate, 2000}
+ ]}
+ %% Uncomment to serve static files
+ %{"_", "/static/[...]", cowboy_static,
+ % {dir, "/var/www", [{mimetypes, cow_mimetypes, all}]}
+ %},
+
+ %% Example usage of mod_revproxy
+
+ %% {"_", "/[...]", mod_revproxy, [{timeout, 5000},
+ %% % time limit for upstream to respond
+ %% {body_length, 8000000},
+ %% % maximum body size (may be infinity)
+ %% {custom_headers, [{<<"header">>,<<"value">>}]}
+ %% % list of extra headers that are send to upstream
+ %% ]}
+
+ %% Example usage of mod_cowboy
+
+ %% {"_", "/[...]", mod_cowboy, [{http, mod_revproxy,
+ %% [{timeout, 5000},
+ %% % time limit for upstream to respond
+ %% {body_length, 8000000},
+ %% % maximum body size (may be infinity)
+ %% {custom_headers, [{<<"header">>,<<"value">>}]}
+ %% % list of extra headers that are send to upstream
+ %% ]},
+ %% {ws, xmpp, mod_websockets}
+ %% ]}
+ ]}
+ ]},
+
+ %% BOSH and WS endpoints over HTTPS
+ { 5285, ejabberd_cowboy, [
+ {num_acceptors, 10},
+ {transport_options, [{max_connections, 1024}]},
+ {ssl, [{certfile, "priv/ssl/fullchain.pem"}, {keyfile, "priv/ssl/privkey.pem"}, {password, ""}]},
+ {modules, [
+ {"_", "/http-bind", mod_bosh},
+ {"_", "/ws-xmpp", mod_websockets, [
+ %% Uncomment to enable connection dropping or/and server-side pings
+ %{timeout, 600000}, {ping_rate, 60000}
+ ]}
+ %% Uncomment to serve static files
+ %{"_", "/static/[...]", cowboy_static,
+ % {dir, "/var/www", [{mimetypes, cow_mimetypes, all}]}
+ %},
+ ]}
+ ]},
+
+ %% MongooseIM HTTP API it's important to start it on localhost
+ %% or some private interface only (not accessible from the outside)
+ %% At least start it on different port which will be hidden behind firewall
+
+ { {8088, "127.0.0.1"} , ejabberd_cowboy, [
+ {num_acceptors, 10},
+ {transport_options, [{max_connections, 1024}]},
+ {modules, [
+ {"localhost", "/api", mongoose_api_admin, []}
+ ]}
+ ]},
+
+ { 8089 , ejabberd_cowboy, [
+ {num_acceptors, 10},
+ {transport_options, [{max_connections, 1024}]},
+ {protocol_options, [{compress, true}]},
+ {ssl, [{certfile, "priv/ssl/fullchain.pem"}, {keyfile, "priv/ssl/privkey.pem"}, {password, ""}]},
+ {modules, [
+ {"_", "/api/sse", lasse_handler, [mongoose_client_api_sse]},
+ {"_", "/api/messages/[:with]", mongoose_client_api_messages, []},
+ {"_", "/api/contacts/[:jid]", mongoose_client_api_contacts, []},
+ {"_", "/api/rooms/[:id]", mongoose_client_api_rooms, []},
+ {"_", "/api/rooms/[:id]/config", mongoose_client_api_rooms_config, []},
+ {"_", "/api/rooms/:id/users/[:user]", mongoose_client_api_rooms_users, []},
+ {"_", "/api/rooms/[:id]/messages", mongoose_client_api_rooms_messages, []}
+ ]}
+ ]},
+
+ %% Following HTTP API is deprected, the new one abouve should be used instead
+
+ { {5288, "127.0.0.1"} , ejabberd_cowboy, [
+ {num_acceptors, 10},
+ {transport_options, [{max_connections, 1024}]},
+ {modules, [
+ {"localhost", "/api", mongoose_api, [{handlers, [mongoose_api_metrics,
+ mongoose_api_users]}]}
+ ]}
+ ]},
+
+ { 5222, ejabberd_c2s, [
+
+ %%
+ %% If TLS is compiled in and you installed a SSL
+ %% certificate, specify the full path to the
+ %% file and uncomment this line:
+ %%
+ {certfile, "priv/ssl/both.pem"}, starttls,
+
+ %%{zlib, 10000},
+ %% https://www.openssl.org/docs/apps/ciphers.html#CIPHER_STRINGS
+ %% {ciphers, "DEFAULT:!EXPORT:!LOW:!SSLv2"},
+ {access, c2s},
+ {shaper, c2s_shaper},
+ {max_stanza_size, 65536},
+ {protocol_options, ["no_sslv3"]}
+
+ ]},
+
+
+
+ %%
+ %% To enable the old SSL connection method on port 5223:
+ %%
+ %%{5223, ejabberd_c2s, [
+ %% {access, c2s},
+ %% {shaper, c2s_shaper},
+ %% {certfile, "/path/to/ssl.pem"}, tls,
+ %% {max_stanza_size, 65536}
+ %% ]},
+
+ { 5269, ejabberd_s2s_in, [
+ {shaper, s2s_shaper},
+ {max_stanza_size, 131072},
+ {protocol_options, ["no_sslv3"]}
+
+ ]}
+
+ %%
+ %% ejabberd_service: Interact with external components (transports, ...)
+ %%
+ ,{8888, ejabberd_service, [
+ {access, all},
+ {shaper_rule, fast},
+ {ip, {127, 0, 0, 1}},
+ {password, "secret"}
+ ]}
+
+ %%
+ %% ejabberd_stun: Handles STUN Binding requests
+ %%
+ %%{ {3478, udp}, ejabberd_stun, []}
+
+ ]}.
+
+%%
+%% s2s_use_starttls: Enable STARTTLS + Dialback for S2S connections.
+%% Allowed values are: false optional required required_trusted
+%% You must specify a certificate file.
+%%
+{s2s_use_starttls, optional}.
+%%
+%% s2s_certfile: Specify a certificate file.
+%%
+{s2s_certfile, "priv/ssl/both.pem"}.
+
+%% https://www.openssl.org/docs/apps/ciphers.html#CIPHER_STRINGS
+%% {s2s_ciphers, "DEFAULT:!EXPORT:!LOW:!SSLv2"}.
+
+%%
+%% domain_certfile: Specify a different certificate for each served hostname.
+%%
+%%{domain_certfile, "example.org", "/path/to/example_org.pem"}.
+%%{domain_certfile, "example.com", "/path/to/example_com.pem"}.
+
+%%
+%% S2S whitelist or blacklist
+%%
+%% Default s2s policy for undefined hosts.
+%%
+{s2s_default_policy, deny }.
+
+%%
+%% Allow or deny communication with specific servers.
+%%
+%%{ {s2s_host, "goodhost.org"}, allow}.
+%%{ {s2s_host, "badhost.org"}, deny}.
+
+{outgoing_s2s_port, 5269 }.
+
+%%
+%% IP addresses predefined for specific hosts to skip DNS lookups.
+%% Ports defined here take precedence over outgoing_s2s_port.
+%% Examples:
+%%
+%% { {s2s_addr, "example-host.net"}, {127,0,0,1} }.
+%% { {s2s_addr, "example-host.net"}, { {127,0,0,1}, 5269 } }.
+%% { {s2s_addr, "example-host.net"}, { {127,0,0,1}, 5269 } }.
+
+%%
+%% Outgoing S2S options
+%%
+%% Preferred address families (which to try first) and connect timeout
+%% in milliseconds.
+%%
+%%{outgoing_s2s_options, [ipv4, ipv6], 10000}.
+%%
+%%%. ==============
+%%%' SESSION BACKEND
+
+%%{sm_backend, {mnesia, []}}.
+
+%% Requires {redis, global, default, ..., ...} outgoing pool
+%%{sm_backend, {redis, []}}.
+
+{sm_backend, {mnesia, []} }.
+
+
+%%%. ==============
+%%%' AUTHENTICATION
+
+%% Advertised SASL mechanisms
+{sasl_mechanisms, [cyrsasl_plain]}.
+
+%%
+%% auth_method: Method used to authenticate the users.
+%% The default method is the internal.
+%% If you want to use a different method,
+%% comment this line and enable the correct ones.
+%%
+%% {auth_method, internal }.
+{auth_method, http }.
+{auth_opts, [
+ {http, global, auth, [{workers, 50}], [{server, "https://pleroma.soykaf.com"}]},
+ {password_format, plain} % default
+ %% {password_format, scram}
+
+ %% {scram_iterations, 4096} % default
+
+ %%
+ %% For auth_http:
+ %% {basic_auth, "user:password"}
+ %% {path_prefix, "/"} % default
+ %% auth_http requires {http, Host | global, auth, ..., ...} outgoing pool.
+ %%
+ %% For auth_external
+ %%{extauth_program, "/path/to/authentication/script"}.
+ %%
+ %% For auth_jwt
+ %% {jwt_secret_source, "/path/to/file"},
+ %% {jwt_algorithm, "RS256"},
+ %% {jwt_username_key, user}
+ %% For cyrsasl_external
+ %% {authenticate_with_cn, false}
+ {cyrsasl_external, standard}
+ ]}.
+
+%%
+%% Authentication using external script
+%% Make sure the script is executable by ejabberd.
+%%
+%%{auth_method, external}.
+
+%%
+%% Authentication using RDBMS
+%% Remember to setup a database in the next section.
+%%
+%%{auth_method, rdbms}.
+
+%%
+%% Authentication using LDAP
+%%
+%%{auth_method, ldap}.
+%%
+
+%% List of LDAP servers:
+%%{ldap_servers, ["localhost"]}.
+%%
+%% Encryption of connection to LDAP servers:
+%%{ldap_encrypt, none}.
+%%{ldap_encrypt, tls}.
+%%
+%% Port to connect to on LDAP servers:
+%%{ldap_port, 389}.
+%%{ldap_port, 636}.
+%%
+%% LDAP manager:
+%%{ldap_rootdn, "dc=example,dc=com"}.
+%%
+%% Password of LDAP manager:
+%%{ldap_password, "******"}.
+%%
+%% Search base of LDAP directory:
+%%{ldap_base, "dc=example,dc=com"}.
+%%
+%% LDAP attribute that holds user ID:
+%%{ldap_uids, [{"mail", "%u@mail.example.org"}]}.
+%%
+%% LDAP filter:
+%%{ldap_filter, "(objectClass=shadowAccount)"}.
+
+%%
+%% Anonymous login support:
+%% auth_method: anonymous
+%% anonymous_protocol: sasl_anon | login_anon | both
+%% allow_multiple_connections: true | false
+%%
+%%{host_config, "public.example.org", [{auth_method, anonymous},
+%% {allow_multiple_connections, false},
+%% {anonymous_protocol, sasl_anon}]}.
+%%
+%% To use both anonymous and internal authentication:
+%%
+%%{host_config, "public.example.org", [{auth_method, [internal, anonymous]}]}.
+
+
+%%%. ==============
+%%%' OUTGOING CONNECTIONS (e.g. DB)
+
+%% Here you may configure all outgoing connections used by MongooseIM,
+%% e.g. to RDBMS (such as MySQL), Riak or external HTTP components.
+%% Default MongooseIM configuration uses only Mnesia (non-Mnesia extensions are disabled),
+%% so no options here are uncommented out of the box.
+%% This section includes configuration examples; for comprehensive guide
+%% please consult MongooseIM documentation, page "Outgoing connections":
+%% - doc/advanced-configuration/outgoing-connections.md
+%% - https://mongooseim.readthedocs.io/en/latest/advanced-configuration/outgoing-connections/
+
+
+{outgoing_pools, [
+% {riak, global, default, [{workers, 5}], [{address, "127.0.0.1"}, {port, 8087}]},
+% {elastic, global, default, [], [{host, "elastic.host.com"}, {port, 9042}]},
+ {http, global, auth, [{workers, 50}], [{server, "https://pleroma.soykaf.com"}]}
+% {cassandra, global, default, [{workers, 100}], [{servers, [{"server1", 9042}]}, {keyspace, "big_mongooseim"}]},
+% {rdbms, global, default, [{workers, 10}], [{server, {mysql, "server", 3306, "database", "username", "password"}}]}
+]}.
+
+%% More examples that may be added to outgoing_pools list:
+%%
+%% == MySQL ==
+%% {rdbms, global, default, [{workers, 10}],
+%% [{server, {mysql, "server", 3306, "database", "username", "password"}},
+%% {keepalive_interval, 10}]},
+%% keepalive_interval is optional
+
+%% == PostgreSQL ==
+%% {rdbms, global, default, [{workers, 10}],
+%% [{server, {pgsql, "server", 5432, "database", "username", "password"}}]},
+
+%% == ODBC (MSSQL) ==
+%% {rdbms, global, default, [{workers, 10}],
+%% [{server, "DSN=mongooseim;UID=mongooseim;PWD=mongooseim"}]},
+
+%% == Elastic Search ==
+%% {elastic, global, default, [], [{host, "elastic.host.com"}, {port, 9042}]},
+
+%% == Riak ==
+%% {riak, global, default, [{workers, 20}], [{address, "127.0.0.1"}, {port, 8087}]},
+
+%% == HTTP ==
+%% {http, global, conn1, [{workers, 50}], [{server, "http://server:8080"}]},
+
+%% == Cassandra ==
+%% {cassandra, global, default, [{workers, 100}],
+%% [
+%% {servers, [
+%% {"cassandra_server1.example.com", 9042},
+%% {"cassandra_server2.example.com", 9042},
+%% {"cassandra_server3.example.com", 9042},
+%% {"cassandra_server4.example.com", 9042}
+%% ]},
+%% {keyspace, "big_mongooseim"}
+%% ]}
+
+%% == Extra options ==
+%%
+%% If you use PostgreSQL, have a large database, and need a
+%% faster but inexact replacement for "select count(*) from users"
+%%
+%%{pgsql_users_number_estimate, true}.
+%%
+%% rdbms_server_type specifies what database is used over the RDBMS layer
+%% Can take values mssql, pgsql, mysql
+%% In some cases (for example for MAM with pgsql) it is required to set proper value.
+%%
+%% {rdbms_server_type, pgsql}.
+
+%%%. ===============
+%%%' TRAFFIC SHAPERS
+
+%%
+%% The "normal" shaper limits traffic speed to 1000 B/s
+%%
+{shaper, normal, {maxrate, 1000}}.
+
+%%
+%% The "fast" shaper limits traffic speed to 50000 B/s
+%%
+{shaper, fast, {maxrate, 50000}}.
+
+%%
+%% This option specifies the maximum number of elements in the queue
+%% of the FSM. Refer to the documentation for details.
+%%
+{max_fsm_queue, 1000}.
+
+%%%. ====================
+%%%' ACCESS CONTROL LISTS
+
+%%
+%% The 'admin' ACL grants administrative privileges to XMPP accounts.
+%% You can put here as many accounts as you want.
+%%
+%{acl, admin, {user, "alice", "localhost"}}.
+%{acl, admin, {user, "a", "localhost"}}.
+
+%%
+%% Blocked users
+%%
+%%{acl, blocked, {user, "baduser", "example.org"}}.
+%%{acl, blocked, {user, "test"}}.
+
+%%
+%% Local users: don't modify this line.
+%%
+{acl, local, {user_regexp, ""}}.
+
+%%
+%% More examples of ACLs
+%%
+%%{acl, jabberorg, {server, "jabber.org"}}.
+%%{acl, aleksey, {user, "aleksey", "jabber.ru"}}.
+%%{acl, test, {user_regexp, "^test"}}.
+%%{acl, test, {user_glob, "test*"}}.
+
+%%
+%% Define specific ACLs in a virtual host.
+%%
+%%{host_config, "localhost",
+%% [
+%% {acl, admin, {user, "bob-local", "localhost"}}
+%% ]
+%%}.
+
+%%%. ============
+%%%' ACCESS RULES
+
+%% Maximum number of simultaneous sessions allowed for a single user:
+{access, max_user_sessions, [{10, all}]}.
+
+%% Maximum number of offline messages that users can have:
+{access, max_user_offline_messages, [{5000, admin}, {100, all}]}.
+
+%% This rule allows access only for local users:
+{access, local, [{allow, local}]}.
+
+%% Only non-blocked users can use c2s connections:
+{access, c2s, [{deny, blocked},
+ {allow, all}]}.
+
+%% For C2S connections, all users except admins use the "normal" shaper
+{access, c2s_shaper, [{none, admin},
+ {normal, all}]}.
+
+%% All S2S connections use the "fast" shaper
+{access, s2s_shaper, [{fast, all}]}.
+
+%% Admins of this server are also admins of the MUC service:
+{access, muc_admin, [{allow, admin}]}.
+
+%% Only accounts of the local ejabberd server can create rooms:
+{access, muc_create, [{allow, local}]}.
+
+%% All users are allowed to use the MUC service:
+{access, muc, [{allow, all}]}.
+
+%% In-band registration allows registration of any possible username.
+%% To disable in-band registration, replace 'allow' with 'deny'.
+{access, register, [{allow, all}]}.
+
+%% By default the frequency of account registrations from the same IP
+%% is limited to 1 account every 10 minutes. To disable, specify: infinity
+{registration_timeout, infinity}.
+
+%% Default settings for MAM.
+%% To set non-standard value, replace 'default' with 'allow' or 'deny'.
+%% Only user can access his/her archive by default.
+%% An online user can read room's archive by default.
+%% Only an owner can change settings and purge messages by default.
+%% Empty list (i.e. `[]`) means `[{deny, all}]`.
+{access, mam_set_prefs, [{default, all}]}.
+{access, mam_get_prefs, [{default, all}]}.
+{access, mam_lookup_messages, [{default, all}]}.
+{access, mam_purge_single_message, [{default, all}]}.
+{access, mam_purge_multiple_messages, [{default, all}]}.
+
+%% 1 command of the specified type per second.
+{shaper, mam_shaper, {maxrate, 1}}.
+%% This shaper is primeraly for Mnesia overload protection during stress testing.
+%% The limit is 1000 operations of each type per second.
+{shaper, mam_global_shaper, {maxrate, 1000}}.
+
+{access, mam_set_prefs_shaper, [{mam_shaper, all}]}.
+{access, mam_get_prefs_shaper, [{mam_shaper, all}]}.
+{access, mam_lookup_messages_shaper, [{mam_shaper, all}]}.
+{access, mam_purge_single_message_shaper, [{mam_shaper, all}]}.
+{access, mam_purge_multiple_messages_shaper, [{mam_shaper, all}]}.
+
+{access, mam_set_prefs_global_shaper, [{mam_global_shaper, all}]}.
+{access, mam_get_prefs_global_shaper, [{mam_global_shaper, all}]}.
+{access, mam_lookup_messages_global_shaper, [{mam_global_shaper, all}]}.
+{access, mam_purge_single_message_global_shaper, [{mam_global_shaper, all}]}.
+{access, mam_purge_multiple_messages_global_shaper, [{mam_global_shaper, all}]}.
+
+%%
+%% Define specific Access Rules in a virtual host.
+%%
+%%{host_config, "localhost",
+%% [
+%% {access, c2s, [{allow, admin}, {deny, all}]},
+%% {access, register, [{deny, all}]}
+%% ]
+%%}.
+
+%%%. ================
+%%%' DEFAULT LANGUAGE
+
+%%
+%% language: Default language used for server messages.
+%%
+{language, "en"}.
+
+%%
+%% Set a different default language in a virtual host.
+%%
+%%{host_config, "localhost",
+%% [{language, "ru"}]
+%%}.
+
+%%%. ================
+%%%' MISCELLANEOUS
+
+{all_metrics_are_global, false }.
+
+%%%. ========
+%%%' SERVICES
+
+%% Unlike modules, services are started per node and provide either features which are not
+%% related to any particular host, or backend stuff which is used by modules.
+%% This is handled by `mongoose_service` module.
+
+{services,
+ [
+ {service_admin_extra, [{submods, [node, accounts, sessions, vcard,
+ roster, last, private, stanza, stats]}]}
+ ]
+}.
+
+%%%. =======
+%%%' MODULES
+
+%%
+%% Modules enabled in all mongooseim virtual hosts.
+%% For list of possible modules options, check documentation.
+%%
+{modules,
+ [
+
+ %% The format for a single route is as follows:
+ %% {Host, Path, Method, Upstream}
+ %%
+ %% "_" can be used as wildcard for Host, Path and Method
+ %% Upstream can be either host (just http(s)://host:port) or uri
+ %% The difference is that host upstreams append whole path while
+ %% uri upstreams append only remainder that follows the matched Path
+ %% (this behaviour is similar to nginx's proxy_pass rules)
+ %%
+ %% Bindings can be used to match certain parts of host or path.
+ %% They will be later overlaid with parts of the upstream uri.
+ %%
+ %% {mod_revproxy,
+ %% [{routes, [{"www.erlang-solutions.com", "/admin", "_",
+ %% "https://www.erlang-solutions.com/"},
+ %% {":var.com", "/:var", "_", "http://localhost:8080/"},
+ %% {":domain.com", "/", "_", "http://localhost:8080/:domain"}]
+ %% }]},
+
+% {mod_http_upload, [
+ %% Set max file size in bytes. Defaults to 10 MB.
+ %% Disabled if value is `undefined`.
+% {max_file_size, 1024},
+ %% Use S3 storage backend
+% {backend, s3},
+ %% Set options for S3 backend
+% {s3, [
+% {bucket_url, "http://s3-eu-west-1.amazonaws.com/konbucket2"},
+% {region, "eu-west-1"},
+% {access_key_id, "AKIAIAOAONIULXQGMOUA"},
+% {secret_access_key, "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"}
+% ]}
+% ]},
+
+ {mod_adhoc, []},
+
+ {mod_disco, [{users_can_see_hidden_services, false}]},
+ {mod_commands, []},
+ {mod_muc_commands, []},
+ {mod_muc_light_commands, []},
+ {mod_last, []},
+ {mod_stream_management, [
+ % default 100
+ % size of a buffer of unacked messages
+ % {buffer_max, 100}
+
+ % default 1 - server sends the ack request after each stanza
+ % {ack_freq, 1}
+
+ % default: 600 seconds
+ % {resume_timeout, 600}
+ ]},
+ %% {mod_muc_light, [{host, "muclight.@HOST@"}]},
+ %% {mod_muc, [{host, "muc.@HOST@"},
+ %% {access, muc},
+ %% {access_create, muc_create}
+ %% ]},
+ %% {mod_muc_log, [
+ %% {outdir, "/tmp/muclogs"},
+ %% {access_log, muc}
+ %% ]},
+ {mod_offline, [{access_max_user_messages, max_user_offline_messages}]},
+ {mod_privacy, []},
+ {mod_blocking, []},
+ {mod_private, []},
+% {mod_private, [{backend, mnesia}]},
+% {mod_private, [{backend, rdbms}]},
+% {mod_register, [
+% %%
+% %% Set the minimum informational entropy for passwords.
+% %%
+% %%{password_strength, 32},
+%
+% %%
+% %% After successful registration, the user receives
+% %% a message with this subject and body.
+% %%
+% {welcome_message, {""}},
+%
+% %%
+% %% When a user registers, send a notification to
+% %% these XMPP accounts.
+% %%
+%
+%
+% %%
+% %% Only clients in the server machine can register accounts
+% %%
+% {ip_access, [{allow, "127.0.0.0/8"},
+% {deny, "0.0.0.0/0"}]},
+%
+% %%
+% %% Local c2s or remote s2s users cannot register accounts
+% %%
+% %%{access_from, deny},
+%
+% {access, register}
+% ]},
+ {mod_roster, []},
+ {mod_sic, []},
+ {mod_vcard, [%{matches, 1},
+%{search, true},
+%{ldap_search_operator, 'or'}, %% either 'or' or 'and'
+%{ldap_binary_search_fields, [<<"PHOTO">>]},
+%% list of binary search fields (as in vcard after mapping)
+{host, "vjud.@HOST@"}
+]},
+ {mod_bosh, []},
+ {mod_carboncopy, []}
+
+ %%
+ %% Message Archive Management (MAM, XEP-0313) for registered users and
+ %% Multi-User chats (MUCs).
+ %%
+
+% {mod_mam_meta, [
+ %% Use RDBMS backend (default)
+% {backend, rdbms},
+
+ %% Do not store user preferences (default)
+% {user_prefs_store, false},
+ %% Store user preferences in RDBMS
+% {user_prefs_store, rdbms},
+ %% Store user preferences in Mnesia (recommended).
+ %% The preferences store will be called each time, as a message is routed.
+ %% That is why Mnesia is better suited for this job.
+% {user_prefs_store, mnesia},
+
+ %% Enables a pool of asynchronous writers. (default)
+ %% Messages will be grouped together based on archive id.
+% {async_writer, true},
+
+ %% Cache information about users (default)
+% {cache_users, true},
+
+ %% Enable archivization for private messages (default)
+% {pm, [
+ %% Top-level options can be overriden here if needed, for example:
+% {async_writer, false}
+% ]},
+
+ %%
+ %% Message Archive Management (MAM) for multi-user chats (MUC).
+ %% Enable XEP-0313 for "muc.@HOST@".
+ %%
+% {muc, [
+% {host, "muc.@HOST@"}
+ %% As with pm, top-level options can be overriden for MUC archive
+% ]},
+%
+ %% Do not use a <stanza-id/> element (by default stanzaid is used)
+% no_stanzaid_element,
+% ]},
+
+
+ %%
+ %% MAM configuration examples
+ %%
+
+ %% Only MUC, no user-defined preferences, good performance.
+% {mod_mam_meta, [
+% {backend, rdbms},
+% {pm, false},
+% {muc, [
+% {host, "muc.@HOST@"}
+% ]}
+% ]},
+
+ %% Only archives for c2c messages, good performance.
+% {mod_mam_meta, [
+% {backend, rdbms},
+% {pm, [
+% {user_prefs_store, mnesia}
+% ]}
+% ]},
+
+ %% Basic configuration for c2c messages, bad performance, easy to debug.
+% {mod_mam_meta, [
+% {backend, rdbms},
+% {async_writer, false},
+% {cache_users, false}
+% ]},
+
+ %% Cassandra archive for c2c and MUC conversations.
+ %% No custom settings supported (always archive).
+% {mod_mam_meta, [
+% {backend, cassandra},
+% {user_prefs_store, cassandra},
+% {muc, [{host, "muc.@HOST@"}]}
+% ]}
+
+% {mod_event_pusher, [
+% {backends, [
+% %%
+% %% Configuration for Amazon SNS notifications.
+% %%
+% {sns, [
+% %% AWS credentials, region and host configuration
+% {access_key_id, "AKIAJAZYHOIPY6A2PESA"},
+% {secret_access_key, "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"},
+% {region, "eu-west-1"},
+% {account_id, "251423380551"},
+% {region, "eu-west-1"},
+% {sns_host, "sns.eu-west-1.amazonaws.com"},
+%
+% %% Messages from this MUC host will be sent to the SNS topic
+% {muc_host, "muc.@HOST@"},
+%
+% %% Plugin module for defining custom message attributes and user identification
+% {plugin_module, mod_event_pusher_sns_defaults},
+%
+% %% Topic name configurations. Removing a topic will disable this specific SNS notification
+% {presence_updates_topic, "user_presence_updated-dev-1"}, %% For presence updates
+% {pm_messages_topic, "user_message_sent-dev-1"}, %% For private chat messages
+% {muc_messages_topic, "user_messagegroup_sent-dev-1"} %% For group chat messages
+%
+% %% Pool options
+% {pool_size, 100}, %% Worker pool size for publishing notifications
+% {publish_retry_count, 2}, %% Retry count in case of publish error
+% {publish_retry_time_ms, 50} %% Base exponential backoff time (in ms) for publish errors
+% ]}
+% ]}
+
+]}.
+
+
+%%
+%% Enable modules with custom options in a specific virtual host
+%%
+%%{host_config, "localhost",
+%% [{ {add, modules},
+%% [
+%% {mod_some_module, []}
+%% ]
+%% }
+%% ]}.
+
+%%%.
+%%%'
+
+%%% $Id$
+
+%%% Local Variables:
+%%% mode: erlang
+%%% End:
+%%% vim: set filetype=erlang tabstop=8 foldmarker=%%%',%%%. foldmethod=marker:
+%%%.
diff --git a/lib/pleroma/conversation.ex b/lib/pleroma/conversation.ex
index 238c1acf2..bc97b39ca 100644
--- a/lib/pleroma/conversation.ex
+++ b/lib/pleroma/conversation.ex
@@ -49,7 +49,7 @@ defmodule Pleroma.Conversation do
with true <- Pleroma.Web.ActivityPub.Visibility.is_direct?(activity),
"Create" <- activity.data["type"],
object <- Pleroma.Object.normalize(activity),
- "Note" <- object.data["type"],
+ true <- object.data["type"] in ["Note", "Question"],
ap_id when is_binary(ap_id) and byte_size(ap_id) > 0 <- object.data["context"] do
{:ok, conversation} = create_for_ap_id(ap_id)
diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex
index 844264307..46f2107b1 100644
--- a/lib/pleroma/notification.ex
+++ b/lib/pleroma/notification.ex
@@ -127,10 +127,15 @@ defmodule Pleroma.Notification do
def create_notifications(%Activity{data: %{"to" => _, "type" => type}} = activity)
when type in ["Create", "Like", "Announce", "Follow"] do
- users = get_notified_from_activity(activity)
-
- notifications = Enum.map(users, fn user -> create_notification(activity, user) end)
- {:ok, notifications}
+ object = Object.normalize(activity)
+
+ unless object && object.data["type"] == "Answer" do
+ users = get_notified_from_activity(activity)
+ notifications = Enum.map(users, fn user -> create_notification(activity, user) end)
+ {:ok, notifications}
+ else
+ {:ok, []}
+ end
end
def create_notifications(_), do: {:ok, []}
@@ -166,7 +171,16 @@ defmodule Pleroma.Notification do
def get_notified_from_activity(_, _local_only), do: []
def skip?(activity, user) do
- [:self, :blocked, :local, :muted, :followers, :follows, :recently_followed]
+ [
+ :self,
+ :blocked,
+ :muted,
+ :followers,
+ :follows,
+ :non_followers,
+ :non_follows,
+ :recently_followed
+ ]
|> Enum.any?(&skip?(&1, activity, user))
end
@@ -179,12 +193,6 @@ defmodule Pleroma.Notification do
User.blocks?(user, %{ap_id: actor})
end
- def skip?(:local, %{local: true}, %{info: %{notification_settings: %{"local" => false}}}),
- do: true
-
- def skip?(:local, %{local: false}, %{info: %{notification_settings: %{"remote" => false}}}),
- do: true
-
def skip?(:muted, activity, user) do
actor = activity.data["actor"]
@@ -201,12 +209,32 @@ defmodule Pleroma.Notification do
User.following?(follower, user)
end
+ def skip?(
+ :non_followers,
+ activity,
+ %{info: %{notification_settings: %{"non_followers" => false}}} = user
+ ) do
+ actor = activity.data["actor"]
+ follower = User.get_cached_by_ap_id(actor)
+ !User.following?(follower, user)
+ end
+
def skip?(:follows, activity, %{info: %{notification_settings: %{"follows" => false}}} = user) do
actor = activity.data["actor"]
followed = User.get_cached_by_ap_id(actor)
User.following?(user, followed)
end
+ def skip?(
+ :non_follows,
+ activity,
+ %{info: %{notification_settings: %{"non_follows" => false}}} = user
+ ) do
+ actor = activity.data["actor"]
+ followed = User.get_cached_by_ap_id(actor)
+ !User.following?(user, followed)
+ end
+
def skip?(:recently_followed, %{data: %{"type" => "Follow"}} = activity, user) do
actor = activity.data["actor"]
diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex
index cc6fc9c5d..4b181ec59 100644
--- a/lib/pleroma/object.ex
+++ b/lib/pleroma/object.ex
@@ -35,6 +35,9 @@ defmodule Pleroma.Object do
|> unique_constraint(:ap_id, name: :objects_unique_apid_index)
end
+ def get_by_id(nil), do: nil
+ def get_by_id(id), do: Repo.get(Object, id)
+
def get_by_ap_id(nil), do: nil
def get_by_ap_id(ap_id) do
@@ -195,4 +198,34 @@ defmodule Pleroma.Object do
_ -> {:error, "Not found"}
end
end
+
+ def increase_vote_count(ap_id, name) do
+ with %Object{} = object <- Object.normalize(ap_id),
+ "Question" <- object.data["type"] do
+ multiple = Map.has_key?(object.data, "anyOf")
+
+ options =
+ (object.data["anyOf"] || object.data["oneOf"] || [])
+ |> Enum.map(fn
+ %{"name" => ^name} = option ->
+ Kernel.update_in(option["replies"]["totalItems"], &(&1 + 1))
+
+ option ->
+ option
+ end)
+
+ data =
+ if multiple do
+ Map.put(object.data, "anyOf", options)
+ else
+ Map.put(object.data, "oneOf", options)
+ end
+
+ object
+ |> Object.change(%{data: data})
+ |> update_and_set_cache()
+ else
+ _ -> :noop
+ end
+ end
end
diff --git a/lib/pleroma/reverse_proxy.ex b/lib/pleroma/reverse_proxy.ex
index 983e156f5..285d57309 100644
--- a/lib/pleroma/reverse_proxy.ex
+++ b/lib/pleroma/reverse_proxy.ex
@@ -61,8 +61,6 @@ defmodule Pleroma.ReverseProxy do
* `http`: options for [hackney](https://github.com/benoitc/hackney).
"""
- @hackney Pleroma.Config.get(:hackney, :hackney)
-
@default_hackney_options []
@inline_content_types [
@@ -148,7 +146,7 @@ defmodule Pleroma.ReverseProxy do
Logger.debug("#{__MODULE__} #{method} #{url} #{inspect(headers)}")
method = method |> String.downcase() |> String.to_existing_atom()
- case @hackney.request(method, url, headers, "", hackney_opts) do
+ case :hackney.request(method, url, headers, "", hackney_opts) do
{:ok, code, headers, client} when code in @valid_resp_codes ->
{:ok, code, downcase_headers(headers), client}
@@ -198,7 +196,7 @@ defmodule Pleroma.ReverseProxy do
duration,
Keyword.get(opts, :max_read_duration, @max_read_duration)
),
- {:ok, data} <- @hackney.stream_body(client),
+ {:ok, data} <- :hackney.stream_body(client),
{:ok, duration} <- increase_read_duration(duration),
sent_so_far = sent_so_far + byte_size(data),
:ok <- body_size_constraint(sent_so_far, Keyword.get(opts, :max_body_size)),
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 5c91dc7d4..474cd8c1a 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -758,7 +758,7 @@ defmodule Pleroma.User do
from(s in subquery(boost_search_rank_query(distinct_query, for_user)),
order_by: [desc: s.search_rank],
- limit: 20
+ limit: 40
)
end
diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex
index 37cdf2fa7..fb9ab92ab 100644
--- a/lib/pleroma/user/info.ex
+++ b/lib/pleroma/user/info.ex
@@ -42,13 +42,17 @@ defmodule Pleroma.User.Info do
field(:hide_follows, :boolean, default: false)
field(:hide_favorites, :boolean, default: true)
field(:pinned_activities, {:array, :string}, default: [])
- field(:flavour, :string, default: nil)
field(:mascot, :map, default: nil)
field(:emoji, {:array, :map}, default: [])
field(:pleroma_settings_store, :map, default: %{})
field(:notification_settings, :map,
- default: %{"remote" => true, "local" => true, "followers" => true, "follows" => true}
+ default: %{
+ "followers" => true,
+ "follows" => true,
+ "non_follows" => true,
+ "non_followers" => true
+ }
)
# Found in the wild
@@ -69,10 +73,15 @@ defmodule Pleroma.User.Info do
end
def update_notification_settings(info, settings) do
+ settings =
+ settings
+ |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
+ |> Map.new()
+
notification_settings =
info.notification_settings
|> Map.merge(settings)
- |> Map.take(["remote", "local", "followers", "follows"])
+ |> Map.take(["followers", "follows", "non_follows", "non_followers"])
params = %{notification_settings: notification_settings}
@@ -243,14 +252,6 @@ defmodule Pleroma.User.Info do
|> validate_required([:settings])
end
- def mastodon_flavour_update(info, flavour) do
- params = %{flavour: flavour}
-
- info
- |> cast(params, [:flavour])
- |> validate_required([:flavour])
- end
-
def mascot_update(info, url) do
params = %{mascot: url}
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index bdd7e78d2..45feae25a 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -108,6 +108,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
def decrease_replies_count_if_reply(_object), do: :noop
+ def increase_poll_votes_if_vote(%{
+ "object" => %{"inReplyTo" => reply_ap_id, "name" => name},
+ "type" => "Create"
+ }) do
+ Object.increase_vote_count(reply_ap_id, name)
+ end
+
+ def increase_poll_votes_if_vote(_create_data), do: :noop
+
def insert(map, local \\ true, fake \\ false) when is_map(map) do
with nil <- Activity.normalize(map),
map <- lazy_put_activity_defaults(map, fake),
@@ -183,40 +192,42 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
public = "https://www.w3.org/ns/activitystreams#Public"
if activity.data["type"] in ["Create", "Announce", "Delete"] do
- Pleroma.Web.Streamer.stream("user", activity)
- Pleroma.Web.Streamer.stream("list", activity)
+ object = Object.normalize(activity)
+ # Do not stream out poll replies
+ unless object.data["type"] == "Answer" do
+ Pleroma.Web.Streamer.stream("user", activity)
+ Pleroma.Web.Streamer.stream("list", activity)
- if Enum.member?(activity.data["to"], public) do
- Pleroma.Web.Streamer.stream("public", activity)
+ if Enum.member?(activity.data["to"], public) do
+ Pleroma.Web.Streamer.stream("public", activity)
- if activity.local do
- Pleroma.Web.Streamer.stream("public:local", activity)
- end
-
- if activity.data["type"] in ["Create"] do
- object = Object.normalize(activity)
+ if activity.local do
+ Pleroma.Web.Streamer.stream("public:local", activity)
+ end
- object.data
- |> Map.get("tag", [])
- |> Enum.filter(fn tag -> is_bitstring(tag) end)
- |> Enum.each(fn tag -> Pleroma.Web.Streamer.stream("hashtag:" <> tag, activity) end)
+ if activity.data["type"] in ["Create"] do
+ object.data
+ |> Map.get("tag", [])
+ |> Enum.filter(fn tag -> is_bitstring(tag) end)
+ |> Enum.each(fn tag -> Pleroma.Web.Streamer.stream("hashtag:" <> tag, activity) end)
- if object.data["attachment"] != [] do
- Pleroma.Web.Streamer.stream("public:media", activity)
+ if object.data["attachment"] != [] do
+ Pleroma.Web.Streamer.stream("public:media", activity)
- if activity.local do
- Pleroma.Web.Streamer.stream("public:local:media", activity)
+ if activity.local do
+ Pleroma.Web.Streamer.stream("public:local:media", activity)
+ end
end
end
+ else
+ # TODO: Write test, replace with visibility test
+ if !Enum.member?(activity.data["cc"] || [], public) &&
+ !Enum.member?(
+ activity.data["to"],
+ User.get_cached_by_ap_id(activity.data["actor"]).follower_address
+ ),
+ do: Pleroma.Web.Streamer.stream("direct", activity)
end
- else
- # TODO: Write test, replace with visibility test
- if !Enum.member?(activity.data["cc"] || [], public) &&
- !Enum.member?(
- activity.data["to"],
- User.get_cached_by_ap_id(activity.data["actor"]).follower_address
- ),
- do: Pleroma.Web.Streamer.stream("direct", activity)
end
end
end
@@ -235,6 +246,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
{:ok, activity} <- insert(create_data, local, fake),
{:fake, false, activity} <- {:fake, fake, activity},
_ <- increase_replies_count_if_reply(create_data),
+ _ <- increase_poll_votes_if_vote(create_data),
# Changing note count prior to enqueuing federation task in order to avoid
# race conditions on updating user.info
{:ok, _actor} <- increase_note_count_if_public(actor, activity),
@@ -476,6 +488,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
if opts["user"], do: [opts["user"].ap_id | opts["user"].following] ++ public, else: public
from(activity in Activity)
+ |> maybe_preload_objects(opts)
|> restrict_blocked(opts)
|> restrict_recipients(recipients, opts["user"])
|> where(
@@ -488,6 +501,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
^context
)
)
+ |> exclude_poll_votes(opts)
|> order_by([activity], desc: activity.id)
end
@@ -495,7 +509,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
def fetch_activities_for_context(context, opts \\ %{}) do
context
|> fetch_activities_for_context_query(opts)
- |> Activity.with_preloaded_object()
|> Repo.all()
end
@@ -503,7 +516,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
Pleroma.FlakeId.t() | nil
def fetch_latest_activity_id_for_context(context, opts \\ %{}) do
context
- |> fetch_activities_for_context_query(opts)
+ |> fetch_activities_for_context_query(Map.merge(%{"skip_preload" => true}, opts))
|> limit(1)
|> select([a], a.id)
|> Repo.one()
@@ -649,20 +662,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_tag(query, _), do: query
- defp restrict_to_cc(query, recipients_to, recipients_cc) do
- from(
- activity in query,
- where:
- fragment(
- "(?->'to' \\?| ?) or (?->'cc' \\?| ?)",
- activity.data,
- ^recipients_to,
- activity.data,
- ^recipients_cc
- )
- )
- end
-
defp restrict_recipients(query, [], _user), do: query
defp restrict_recipients(query, recipients, nil) do
@@ -816,6 +815,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_muted_reblogs(query, _), do: query
+ defp exclude_poll_votes(query, %{"include_poll_votes" => "true"}), do: query
+
+ defp exclude_poll_votes(query, _) do
+ if has_named_binding?(query, :object) do
+ from([activity, object: o] in query,
+ where: fragment("not(?->>'type' = ?)", o.data, "Answer")
+ )
+ else
+ query
+ end
+ end
+
defp maybe_preload_objects(query, %{"skip_preload" => true}), do: query
defp maybe_preload_objects(query, _) do
@@ -877,6 +888,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> restrict_pinned(opts)
|> restrict_muted_reblogs(opts)
|> Activity.restrict_deactivated_users()
+ |> exclude_poll_votes(opts)
end
def fetch_activities(recipients, opts \\ %{}) do
@@ -885,9 +897,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> Enum.reverse()
end
- def fetch_activities_bounded(recipients_to, recipients_cc, opts \\ %{}) do
+ def fetch_activities_bounded_query(query, recipients, recipients_with_public) do
+ from(activity in query,
+ where:
+ fragment("? && ?", activity.recipients, ^recipients) or
+ (fragment("? && ?", activity.recipients, ^recipients_with_public) and
+ "https://www.w3.org/ns/activitystreams#Public" in activity.recipients)
+ )
+ end
+
+ def fetch_activities_bounded(recipients, recipients_with_public, opts \\ %{}) do
fetch_activities_query([], opts)
- |> restrict_to_cc(recipients_to, recipients_cc)
+ |> fetch_activities_bounded_query(recipients, recipients_with_public)
|> Pagination.fetch_paginated(opts)
|> Enum.reverse()
end
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 5edd8ccc7..66fa7c0b3 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -35,6 +35,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> fix_likes
|> fix_addressing
|> fix_summary
+ |> fix_type
end
def fix_summary(%{"summary" => nil} = object) do
@@ -65,7 +66,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
end
- def fix_explicit_addressing(%{"to" => to, "cc" => cc} = object, explicit_mentions) do
+ def fix_explicit_addressing(
+ %{"to" => to, "cc" => cc} = object,
+ explicit_mentions,
+ follower_collection
+ ) do
explicit_to =
to
|> Enum.filter(fn x -> x in explicit_mentions end)
@@ -76,6 +81,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
final_cc =
(cc ++ explicit_cc)
+ |> Enum.reject(fn x -> String.ends_with?(x, "/followers") and x != follower_collection end)
|> Enum.uniq()
object
@@ -83,7 +89,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> Map.put("cc", final_cc)
end
- def fix_explicit_addressing(object, _explicit_mentions), do: object
+ def fix_explicit_addressing(object, _explicit_mentions, _followers_collection), do: object
# if directMessage flag is set to true, leave the addressing alone
def fix_explicit_addressing(%{"directMessage" => true} = object), do: object
@@ -93,10 +99,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
object
|> Utils.determine_explicit_mentions()
- explicit_mentions = explicit_mentions ++ ["https://www.w3.org/ns/activitystreams#Public"]
+ follower_collection = User.get_cached_by_ap_id(Containment.get_actor(object)).follower_address
- object
- |> fix_explicit_addressing(explicit_mentions)
+ explicit_mentions =
+ explicit_mentions ++ ["https://www.w3.org/ns/activitystreams#Public", follower_collection]
+
+ fix_explicit_addressing(object, explicit_mentions, follower_collection)
end
# if as:Public is addressed, then make sure the followers collection is also addressed
@@ -133,7 +141,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> fix_addressing_list("cc")
|> fix_addressing_list("bto")
|> fix_addressing_list("bcc")
- |> fix_explicit_addressing
+ |> fix_explicit_addressing()
|> fix_implicit_addressing(followers_collection)
end
@@ -328,6 +336,18 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def fix_content_map(object), do: object
+ def fix_type(%{"inReplyTo" => reply_id} = object) when is_binary(reply_id) do
+ reply = Object.normalize(reply_id)
+
+ if reply.data["type"] == "Question" and object["name"] do
+ Map.put(object, "type", "Answer")
+ else
+ object
+ end
+ end
+
+ def fix_type(object), do: object
+
defp mastodon_follow_hack(%{"id" => id, "actor" => follower_id}, followed) do
with true <- id =~ "follows",
%User{local: true} = follower <- User.get_cached_by_ap_id(follower_id),
@@ -398,7 +418,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
# - tags
# - emoji
def handle_incoming(%{"type" => "Create", "object" => %{"type" => objtype} = object} = data)
- when objtype in ["Article", "Note", "Video", "Page"] do
+ when objtype in ["Article", "Note", "Video", "Page", "Question", "Answer"] do
actor = Containment.get_actor(data)
data =
@@ -731,6 +751,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> set_reply_to_uri
|> strip_internal_fields
|> strip_internal_tags
+ |> set_type
end
# @doc
@@ -895,6 +916,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
Map.put(object, "sensitive", "nsfw" in tags)
end
+ def set_type(%{"type" => "Answer"} = object) do
+ Map.put(object, "type", "Note")
+ end
+
+ def set_type(object), do: object
+
def add_attributed_to(object) do
attributed_to = object["attributedTo"] || object["actor"]
diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index ca8a0844b..b292d7d8d 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -19,7 +19,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
require Logger
- @supported_object_types ["Article", "Note", "Video", "Page"]
+ @supported_object_types ["Article", "Note", "Video", "Page", "Question", "Answer"]
@supported_report_states ~w(open closed resolved)
@valid_visibilities ~w(public unlisted private direct)
@@ -789,4 +789,21 @@ defmodule Pleroma.Web.ActivityPub.Utils do
[to, cc, recipients]
end
end
+
+ def get_existing_votes(actor, %{data: %{"id" => id}}) do
+ query =
+ from(
+ [activity, object: object] in Activity.with_preloaded_object(Activity),
+ where: fragment("(?)->>'actor' = ?", activity.data, ^actor),
+ where:
+ fragment(
+ "(?)->'inReplyTo' = ?",
+ object.data,
+ ^to_string(id)
+ ),
+ where: fragment("(?)->>'type' = 'Answer'", object.data)
+ )
+
+ Repo.all(query)
+ end
end
diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex
index 93b50ee47..8965e3253 100644
--- a/lib/pleroma/web/activity_pub/visibility.ex
+++ b/lib/pleroma/web/activity_pub/visibility.ex
@@ -66,6 +66,9 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
Enum.any?(to, &String.contains?(&1, "/followers")) ->
"private"
+ object.data["directMessage"] == true ->
+ "direct"
+
length(cc) > 0 ->
"private"
diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex
index 5a312d673..5212d5ce5 100644
--- a/lib/pleroma/web/common_api/common_api.ex
+++ b/lib/pleroma/web/common_api/common_api.ex
@@ -119,6 +119,53 @@ defmodule Pleroma.Web.CommonAPI do
end
end
+ def vote(user, object, choices) do
+ with "Question" <- object.data["type"],
+ {:author, false} <- {:author, object.data["actor"] == user.ap_id},
+ {:existing_votes, []} <- {:existing_votes, Utils.get_existing_votes(user.ap_id, object)},
+ {options, max_count} <- get_options_and_max_count(object),
+ option_count <- Enum.count(options),
+ {:choice_check, {choices, true}} <-
+ {:choice_check, normalize_and_validate_choice_indices(choices, option_count)},
+ {:count_check, true} <- {:count_check, Enum.count(choices) <= max_count} do
+ answer_activities =
+ Enum.map(choices, fn index ->
+ answer_data = make_answer_data(user, object, Enum.at(options, index)["name"])
+
+ ActivityPub.create(%{
+ to: answer_data["to"],
+ actor: user,
+ context: object.data["context"],
+ object: answer_data,
+ additional: %{"cc" => answer_data["cc"]}
+ })
+ end)
+
+ object = Object.get_cached_by_ap_id(object.data["id"])
+ {:ok, answer_activities, object}
+ else
+ {:author, _} -> {:error, "Poll's author can't vote"}
+ {:existing_votes, _} -> {:error, "Already voted"}
+ {:choice_check, {_, false}} -> {:error, "Invalid indices"}
+ {:count_check, false} -> {:error, "Too many choices"}
+ end
+ end
+
+ defp get_options_and_max_count(object) do
+ if Map.has_key?(object.data, "anyOf") do
+ {object.data["anyOf"], Enum.count(object.data["anyOf"])}
+ else
+ {object.data["oneOf"], 1}
+ end
+ end
+
+ defp normalize_and_validate_choice_indices(choices, count) do
+ Enum.map_reduce(choices, true, fn index, valid ->
+ index = if is_binary(index), do: String.to_integer(index), else: index
+ {index, if(valid, do: index < count, else: valid)}
+ end)
+ 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)}
@@ -154,6 +201,7 @@ defmodule Pleroma.Web.CommonAPI do
data,
visibility
),
+ {poll, poll_emoji} <- make_poll_data(data),
{to, cc} <- to_for_user_and_mentions(user, mentions, in_reply_to, visibility),
context <- make_context(in_reply_to),
cw <- data["spoiler_text"] || "",
@@ -171,13 +219,14 @@ defmodule Pleroma.Web.CommonAPI do
tags,
cw,
cc,
- sensitive
+ sensitive,
+ poll
),
object <-
Map.put(
object,
"emoji",
- Formatter.get_emoji_map(full_payload)
+ Map.merge(Formatter.get_emoji_map(full_payload), poll_emoji)
) do
res =
ActivityPub.create(
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index d93c0d46e..f35ed36ab 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -102,6 +102,72 @@ defmodule Pleroma.Web.CommonAPI.Utils do
end
end
+ def make_poll_data(%{"poll" => %{"options" => options, "expires_in" => expires_in}} = data)
+ when is_list(options) do
+ %{max_expiration: max_expiration, min_expiration: min_expiration} =
+ limits = Pleroma.Config.get([:instance, :poll_limits])
+
+ # XXX: There is probably a cleaner way of doing this
+ try do
+ # In some cases mastofe sends out strings instead of integers
+ expires_in = if is_binary(expires_in), do: String.to_integer(expires_in), else: expires_in
+
+ if Enum.count(options) > limits.max_options do
+ raise ArgumentError, message: "Poll can't contain more than #{limits.max_options} options"
+ end
+
+ {poll, emoji} =
+ Enum.map_reduce(options, %{}, fn option, emoji ->
+ if String.length(option) > limits.max_option_chars do
+ raise ArgumentError,
+ message:
+ "Poll options cannot be longer than #{limits.max_option_chars} characters each"
+ end
+
+ {%{
+ "name" => option,
+ "type" => "Note",
+ "replies" => %{"type" => "Collection", "totalItems" => 0}
+ }, Map.merge(emoji, Formatter.get_emoji_map(option))}
+ end)
+
+ case expires_in do
+ expires_in when expires_in > max_expiration ->
+ raise ArgumentError, message: "Expiration date is too far in the future"
+
+ expires_in when expires_in < min_expiration ->
+ raise ArgumentError, message: "Expiration date is too soon"
+
+ _ ->
+ :noop
+ end
+
+ end_time =
+ NaiveDateTime.utc_now()
+ |> NaiveDateTime.add(expires_in)
+ |> NaiveDateTime.to_iso8601()
+
+ poll =
+ if Pleroma.Web.ControllerHelper.truthy_param?(data["poll"]["multiple"]) do
+ %{"type" => "Question", "anyOf" => poll, "closed" => end_time}
+ else
+ %{"type" => "Question", "oneOf" => poll, "closed" => end_time}
+ end
+
+ {poll, emoji}
+ rescue
+ e in ArgumentError -> e.message
+ end
+ end
+
+ def make_poll_data(%{"poll" => poll}) when is_map(poll) do
+ "Invalid poll"
+ end
+
+ def make_poll_data(_data) do
+ {%{}, %{}}
+ end
+
def make_content_html(
status,
attachments,
@@ -224,7 +290,8 @@ defmodule Pleroma.Web.CommonAPI.Utils do
tags,
cw \\ nil,
cc \\ [],
- sensitive \\ false
+ sensitive \\ false,
+ merge \\ %{}
) do
object = %{
"type" => "Note",
@@ -239,12 +306,15 @@ defmodule Pleroma.Web.CommonAPI.Utils do
"tag" => tags |> Enum.map(fn {_, tag} -> tag end) |> Enum.uniq()
}
- 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
+ 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
+
+ Map.merge(object, merge)
end
def format_naive_asctime(date) do
@@ -421,4 +491,15 @@ defmodule Pleroma.Web.CommonAPI.Utils do
{:error, "No such conversation"}
end
end
+
+ def make_answer_data(%User{ap_id: ap_id}, object, name) do
+ %{
+ "type" => "Answer",
+ "actor" => ap_id,
+ "cc" => [object.data["actor"]],
+ "to" => [],
+ "name" => name,
+ "inReplyTo" => object.data["id"]
+ }
+ end
end
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index ce3e149cc..fe2fdcea1 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -205,7 +205,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
languages: ["en"],
registrations: Pleroma.Config.get([:instance, :registrations_open]),
# Extra (not present in Mastodon):
- max_toot_chars: Keyword.get(instance, :limit)
+ max_toot_chars: Keyword.get(instance, :limit),
+ poll_limits: Keyword.get(instance, :poll_limits)
}
json(conn, response)
@@ -417,6 +418,53 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
end
+ def get_poll(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+ with %Object{} = object <- Object.get_by_id(id),
+ %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
+ true <- Visibility.visible_for_user?(activity, user) do
+ conn
+ |> put_view(StatusView)
+ |> try_render("poll.json", %{object: object, for: user})
+ else
+ nil ->
+ conn
+ |> put_status(404)
+ |> json(%{error: "Record not found"})
+
+ false ->
+ conn
+ |> put_status(404)
+ |> json(%{error: "Record not found"})
+ end
+ end
+
+ def poll_vote(%{assigns: %{user: user}} = conn, %{"id" => id, "choices" => choices}) do
+ with %Object{} = object <- Object.get_by_id(id),
+ true <- object.data["type"] == "Question",
+ %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
+ true <- Visibility.visible_for_user?(activity, user),
+ {:ok, _activities, object} <- CommonAPI.vote(user, object, choices) do
+ conn
+ |> put_view(StatusView)
+ |> try_render("poll.json", %{object: object, for: user})
+ else
+ nil ->
+ conn
+ |> put_status(404)
+ |> json(%{error: "Record not found"})
+
+ false ->
+ conn
+ |> put_status(404)
+ |> json(%{error: "Record not found"})
+
+ {:error, message} ->
+ conn
+ |> put_status(422)
+ |> json(%{error: message})
+ end
+ end
+
def scheduled_statuses(%{assigns: %{user: user}} = conn, params) do
with scheduled_activities <- MastodonAPI.get_scheduled_activities(user, params) do
conn
@@ -480,12 +528,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
params
|> Map.put("in_reply_to_status_id", params["in_reply_to_id"])
- idempotency_key =
- case get_req_header(conn, "idempotency-key") do
- [key] -> key
- _ -> Ecto.UUID.generate()
- end
-
scheduled_at = params["scheduled_at"]
if scheduled_at && ScheduledActivity.far_enough?(scheduled_at) do
@@ -498,17 +540,40 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
else
params = Map.drop(params, ["scheduled_at"])
- {:ok, activity} =
- Cachex.fetch!(:idempotency_cache, idempotency_key, fn _ ->
- CommonAPI.post(user, params)
- end)
-
- conn
- |> put_view(StatusView)
- |> try_render("status.json", %{activity: activity, for: user, as: :activity})
+ case get_cached_status_or_post(conn, params) do
+ {:ignore, message} ->
+ conn
+ |> put_status(422)
+ |> json(%{error: message})
+
+ {:error, message} ->
+ conn
+ |> put_status(422)
+ |> json(%{error: message})
+
+ {_, activity} ->
+ conn
+ |> put_view(StatusView)
+ |> try_render("status.json", %{activity: activity, for: user, as: :activity})
+ end
end
end
+ defp get_cached_status_or_post(%{assigns: %{user: user}} = conn, params) do
+ idempotency_key =
+ case get_req_header(conn, "idempotency-key") do
+ [key] -> key
+ _ -> Ecto.UUID.generate()
+ end
+
+ Cachex.fetch(:idempotency_cache, idempotency_key, fn _ ->
+ case CommonAPI.post(user, params) do
+ {:ok, activity} -> activity
+ {:error, message} -> {:ignore, message}
+ end
+ end)
+ end
+
def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
json(conn, %{})
@@ -1354,8 +1419,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
accounts =
Map.put(%{}, user.id, AccountView.render("account.json", %{user: user, for: user}))
- flavour = get_user_flavour(user)
-
initial_state =
%{
meta: %{
@@ -1374,6 +1437,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
max_toot_chars: limit,
mascot: User.get_mascot(user)["url"]
},
+ poll_limits: Config.get([:instance, :poll_limits]),
rights: %{
delete_others_notice: present?(user.info.is_moderator),
admin: present?(user.info.is_admin)
@@ -1441,7 +1505,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
conn
|> put_layout(false)
|> put_view(MastodonView)
- |> render("index.html", %{initial_state: initial_state, flavour: flavour})
+ |> render("index.html", %{initial_state: initial_state})
else
conn
|> put_session(:return_to, conn.request_path)
@@ -1464,43 +1528,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
end
- @supported_flavours ["glitch", "vanilla"]
-
- def set_flavour(%{assigns: %{user: user}} = conn, %{"flavour" => flavour} = _params)
- when flavour in @supported_flavours do
- flavour_cng = User.Info.mastodon_flavour_update(user.info, flavour)
-
- with changeset <- Ecto.Changeset.change(user),
- changeset <- Ecto.Changeset.put_embed(changeset, :info, flavour_cng),
- {:ok, user} <- User.update_and_set_cache(changeset),
- flavour <- user.info.flavour do
- json(conn, flavour)
- else
- e ->
- conn
- |> put_resp_content_type("application/json")
- |> send_resp(500, Jason.encode!(%{"error" => inspect(e)}))
- end
- end
-
- def set_flavour(conn, _params) do
- conn
- |> put_status(400)
- |> json(%{error: "Unsupported flavour"})
- end
-
- def get_flavour(%{assigns: %{user: user}} = conn, _params) do
- json(conn, get_user_flavour(user))
- end
-
- defp get_user_flavour(%User{info: %{flavour: flavour}}) when flavour in @supported_flavours do
- flavour
- end
-
- defp get_user_flavour(_) do
- "glitch"
- end
-
def login(%{assigns: %{user: %User{}}} = conn, _params) do
redirect(conn, to: local_mastodon_root_path(conn))
end
diff --git a/lib/pleroma/web/mastodon_api/views/conversation_view.ex b/lib/pleroma/web/mastodon_api/views/conversation_view.ex
index 8e8f7cf31..af1dcf66d 100644
--- a/lib/pleroma/web/mastodon_api/views/conversation_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/conversation_view.ex
@@ -22,9 +22,14 @@ defmodule Pleroma.Web.MastodonAPI.ConversationView do
last_status = StatusView.render("status.json", %{activity: activity, for: user})
+ # Conversations return all users except the current user.
+ users =
+ participation.conversation.users
+ |> Enum.reject(&(&1.id == user.id))
+
accounts =
AccountView.render("accounts.json", %{
- users: participation.conversation.users,
+ users: users,
as: :user
})
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index 84ab20a1c..6836d331a 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -240,6 +240,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
spoiler_text: summary_html,
visibility: get_visibility(object),
media_attachments: attachments,
+ poll: render("poll.json", %{object: object, for: opts[:for]}),
mentions: mentions,
tags: build_tags(tags),
application: %{
@@ -329,6 +330,64 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
}
end
+ def render("poll.json", %{object: object} = opts) do
+ {multiple, options} =
+ case object.data do
+ %{"anyOf" => options} when is_list(options) -> {true, options}
+ %{"oneOf" => options} when is_list(options) -> {false, options}
+ _ -> {nil, nil}
+ end
+
+ if options do
+ end_time =
+ (object.data["closed"] || object.data["endTime"])
+ |> NaiveDateTime.from_iso8601!()
+
+ expired =
+ end_time
+ |> NaiveDateTime.compare(NaiveDateTime.utc_now())
+ |> case do
+ :lt -> true
+ _ -> false
+ end
+
+ voted =
+ if opts[:for] do
+ existing_votes =
+ Pleroma.Web.ActivityPub.Utils.get_existing_votes(opts[:for].ap_id, object)
+
+ existing_votes != [] or opts[:for].ap_id == object.data["actor"]
+ else
+ false
+ end
+
+ {options, votes_count} =
+ Enum.map_reduce(options, 0, fn %{"name" => name} = option, count ->
+ current_count = option["replies"]["totalItems"] || 0
+
+ {%{
+ title: HTML.strip_tags(name),
+ votes_count: current_count
+ }, current_count + count}
+ end)
+
+ %{
+ # Mastodon uses separate ids for polls, but an object can't have
+ # more than one poll embedded so object id is fine
+ id: object.id,
+ expires_at: Utils.to_masto_date(end_time),
+ expired: expired,
+ multiple: multiple,
+ votes_count: votes_count,
+ options: options,
+ voted: voted,
+ emojis: build_emojis(object.data["emoji"])
+ }
+ else
+ nil
+ end
+ end
+
def get_reply_to(activity, %{replied_to_activities: replied_to_activities}) do
object = Object.normalize(activity)
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 352268b96..e699f6ae2 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -309,8 +309,6 @@ defmodule Pleroma.Web.Router do
post("/conversations/:id/read", MastodonAPIController, :conversation_read)
get("/endorsements", MastodonAPIController, :empty_array)
-
- get("/pleroma/flavour", MastodonAPIController, :get_flavour)
end
scope [] do
@@ -335,6 +333,8 @@ defmodule Pleroma.Web.Router do
put("/scheduled_statuses/:id", MastodonAPIController, :update_scheduled_status)
delete("/scheduled_statuses/:id", MastodonAPIController, :delete_scheduled_status)
+ post("/polls/:id/votes", MastodonAPIController, :poll_vote)
+
post("/media", MastodonAPIController, :upload)
put("/media/:id", MastodonAPIController, :update_media)
@@ -350,8 +350,6 @@ defmodule Pleroma.Web.Router do
put("/filters/:id", MastodonAPIController, :update_filter)
delete("/filters/:id", MastodonAPIController, :delete_filter)
- post("/pleroma/flavour/:flavour", MastodonAPIController, :set_flavour)
-
get("/pleroma/mascot", MastodonAPIController, :get_mascot)
put("/pleroma/mascot", MastodonAPIController, :set_mascot)
@@ -426,6 +424,8 @@ defmodule Pleroma.Web.Router do
get("/statuses/:id", MastodonAPIController, :get_status)
get("/statuses/:id/context", MastodonAPIController, :get_context)
+ get("/polls/:id", MastodonAPIController, :get_poll)
+
get("/accounts/:id/statuses", MastodonAPIController, :user_statuses)
get("/accounts/:id/followers", MastodonAPIController, :followers)
get("/accounts/:id/following", MastodonAPIController, :following)
diff --git a/lib/pleroma/web/templates/layout/app.html.eex b/lib/pleroma/web/templates/layout/app.html.eex
index 85ec4d76c..b3cf9ed11 100644
--- a/lib/pleroma/web/templates/layout/app.html.eex
+++ b/lib/pleroma/web/templates/layout/app.html.eex
@@ -63,13 +63,14 @@
.scopes-input {
display: flex;
+ flex-direction: column;
margin-top: 1em;
text-align: left;
color: #89898a;
}
.scopes-input label:first-child {
- flex-basis: 40%;
+ height: 2em;
}
.scopes {
@@ -80,13 +81,22 @@
}
.scope {
- flex-basis: 100%;
display: flex;
+ flex-basis: 100%;
height: 2em;
align-items: center;
}
+ .scope:before {
+ color: #b9b9ba;
+ content: "✔\fe0e";
+ margin-left: 1em;
+ margin-right: 1em;
+ }
+
[type="checkbox"] + label {
+ display: none;
+ cursor: pointer;
margin: 0.5em;
}
@@ -95,10 +105,12 @@
}
[type="checkbox"] + label:before {
+ cursor: pointer;
display: inline-block;
color: white;
background-color: #121a24;
border: 4px solid #121a24;
+ box-shadow: 0px 0px 1px 0 #d8a070;
box-sizing: border-box;
width: 1.2em;
height: 1.2em;
@@ -128,7 +140,8 @@
border-radius: 4px;
border: none;
padding: 10px;
- margin-top: 30px;
+ margin-top: 20px;
+ margin-bottom: 20px;
text-transform: uppercase;
font-size: 16px;
box-shadow: 0px 0px 2px 0px black,
@@ -147,8 +160,8 @@
box-sizing: border-box;
width: 100%;
background-color: #931014;
+ border: 1px solid #a06060;
border-radius: 4px;
- border: none;
padding: 10px;
margin-top: 20px;
font-weight: 500;
@@ -171,12 +184,27 @@
margin-top: 0
}
- .scopes-input {
- flex-direction: column;
+ .scope {
+ flex-basis: 0%;
}
- .scope {
- flex-basis: 50%;
+ .scope:before {
+ content: "";
+ margin-left: 0em;
+ margin-right: 1em;
+ }
+
+ .scope:first-child:before {
+ margin-left: 1em;
+ content: "✔\fe0e";
+ }
+
+ .scope:after {
+ content: ",";
+ }
+
+ .scope:last-child:after {
+ content: "";
}
}
.form-row {
diff --git a/lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex b/lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex
index ac63811d1..3325beca1 100644
--- a/lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex
+++ b/lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex
@@ -8,7 +8,7 @@
</title>
<link rel="icon" type="image/png" href="/favicon.png"/>
<script crossorigin='anonymous' src="/packs/locales.js"></script>
-<script crossorigin='anonymous' src="/packs/locales/<%= @flavour %>/en.js"></script>
+<script crossorigin='anonymous' src="/packs/locales/glitch/en.js"></script>
<link rel='preload' as='script' crossorigin='anonymous' href='/packs/features/getting_started.js'>
<link rel='preload' as='script' crossorigin='anonymous' href='/packs/features/compose.js'>
@@ -19,10 +19,10 @@
<script src="/packs/core/common.js"></script>
<link rel="stylesheet" media="all" href="/packs/core/common.css" />
-<script src="/packs/flavours/<%= @flavour %>/common.js"></script>
-<link rel="stylesheet" media="all" href="/packs/flavours/<%= @flavour %>/common.css" />
+<script src="/packs/flavours/glitch/common.js"></script>
+<link rel="stylesheet" media="all" href="/packs/flavours/glitch/common.css" />
-<script src="/packs/flavours/<%= @flavour %>/home.js"></script>
+<script src="/packs/flavours/glitch/home.js"></script>
</head>
<body class='app-body no-reduce-motion system-font'>
<div class='app-holder' data-props='{&quot;locale&quot;:&quot;en&quot;}' id='mastodon'>
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex
index e6cfe108b..c9ec1ecbf 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex
@@ -1,13 +1,19 @@
<div class="scopes-input">
- <%= label @form, :scope, "Permissions" %>
-
+ <%= label @form, :scope, "The following permissions will be granted" %>
<div class="scopes">
<%= for scope <- @available_scopes do %>
<%# Note: using hidden input with `unchecked_value` in order to distinguish user's empty selection from `scope` param being omitted %>
- <div class="scope">
+ <%= if scope in @scopes do %>
+ <div class="scope">
+ <%= checkbox @form, :"scope_#{scope}", value: scope in @scopes && scope, checked_value: scope, unchecked_value: "", name: "authorization[scope][]" %>
+ <%= label @form, :"scope_#{scope}", String.capitalize(scope) %>
+ <%= if scope in @scopes && scope do %>
+ <%= String.capitalize(scope) %>
+ <% end %>
+ </div>
+ <% else %>
<%= checkbox @form, :"scope_#{scope}", value: scope in @scopes && scope, checked_value: scope, unchecked_value: "", name: "authorization[scope][]" %>
- <%= label @form, :"scope_#{scope}", String.capitalize(scope) %>
- </div>
+ <% end %>
<% end %>
</div>
</div>
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
index 4bcda7300..4a0718851 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
@@ -1,7 +1,9 @@
<h2>Sign in with external provider</h2>
<%= form_for @conn, o_auth_path(@conn, :prepare_request), [as: "authorization", method: "get"], fn f -> %>
- <%= render @view_module, "_scopes.html", Map.put(assigns, :form, f) %>
+ <div style="display: none">
+ <%= render @view_module, "_scopes.html", Map.merge(assigns, %{form: f}) %>
+ </div>
<%= hidden_input f, :client_id, value: @client_id %>
<%= hidden_input f, :redirect_uri, value: @redirect_uri %>
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
index 3e360a52c..b17142ff8 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
@@ -6,26 +6,38 @@
<% end %>
<h2>OAuth Authorization</h2>
-
<%= form_for @conn, o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %>
-<div class="input">
- <%= label f, :name, "Name or email" %>
- <%= text_input f, :name %>
-</div>
-<div class="input">
- <%= label f, :password, "Password" %>
- <%= password_input f, :password %>
-</div>
-<%= render @view_module, "_scopes.html", Map.merge(assigns, %{form: f}) %>
+<%= if @params["registration"] in ["true", true] do %>
+ <h3>This is the first time you visit! Please enter your Pleroma handle.</h3>
+ <p>Choose carefully! You won't be able to change this later. You will be able to change your display name, though.</p>
+ <div class="input">
+ <%= label f, :nickname, "Pleroma Handle" %>
+ <%= text_input f, :nickname, placeholder: "lain" %>
+ </div>
+ <%= hidden_input f, :name, value: @params["name"] %>
+ <%= hidden_input f, :password, value: @params["password"] %>
+ <br>
+<% else %>
+ <div class="input">
+ <%= label f, :name, "Username" %>
+ <%= text_input f, :name %>
+ </div>
+ <div class="input">
+ <%= label f, :password, "Password" %>
+ <%= password_input f, :password %>
+ </div>
+ <%= submit "Log In" %>
+ <%= render @view_module, "_scopes.html", Map.merge(assigns, %{form: f}) %>
+<% end %>
<%= hidden_input f, :client_id, value: @client_id %>
<%= hidden_input f, :response_type, value: @response_type %>
<%= hidden_input f, :redirect_uri, value: @redirect_uri %>
<%= hidden_input f, :state, value: @state %>
-<%= submit "Authorize" %>
<% end %>
<%= if Pleroma.Config.oauth_consumer_enabled?() do %>
<%= render @view_module, Pleroma.Web.Auth.Authenticator.oauth_consumer_template(), assigns %>
<% end %>
+
diff --git a/lib/pleroma/web/twitter_api/views/user_view.ex b/lib/pleroma/web/twitter_api/views/user_view.ex
index f0a4ddbd3..550f35f5f 100644
--- a/lib/pleroma/web/twitter_api/views/user_view.ex
+++ b/lib/pleroma/web/twitter_api/views/user_view.ex
@@ -121,6 +121,7 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
"tags" => user.tags
}
|> maybe_with_activation_status(user, for_user)
+ |> with_notification_settings(user, for_user)
}
|> maybe_with_user_settings(user, for_user)
|> maybe_with_role(user, for_user)
@@ -132,6 +133,12 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
end
end
+ defp with_notification_settings(data, %User{id: user_id} = user, %User{id: user_id}) do
+ Map.put(data, "notification_settings", user.info.notification_settings)
+ end
+
+ defp with_notification_settings(data, _, _), do: data
+
defp maybe_with_activation_status(data, user, %User{info: %{is_admin: true}}) do
Map.put(data, "deactivated", user.info.deactivated)
end
diff --git a/mix.exs b/mix.exs
index b2017ef9b..df1a7ced4 100644
--- a/mix.exs
+++ b/mix.exs
@@ -51,16 +51,27 @@ defmodule Pleroma.Mixfile do
defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"]
+ # Specifies OAuth dependencies.
+ defp oauth_deps do
+ oauth_strategy_packages =
+ System.get_env("OAUTH_CONSUMER_STRATEGIES")
+ |> to_string()
+ |> String.split()
+ |> Enum.map(fn strategy_entry ->
+ with [_strategy, dependency] <- String.split(strategy_entry, ":") do
+ dependency
+ else
+ [strategy] -> "ueberauth_#{strategy}"
+ end
+ end)
+
+ for s <- oauth_strategy_packages, do: {String.to_atom(s), ">= 0.0.0"}
+ end
+
# Specifies your project dependencies.
#
# Type `mix help deps` for examples and options.
defp deps do
- oauth_strategies = String.split(System.get_env("OAUTH_CONSUMER_STRATEGIES") || "")
-
- oauth_deps =
- for s <- oauth_strategies,
- do: {String.to_atom("ueberauth_#{s}"), ">= 0.0.0"}
-
[
{:phoenix, "~> 1.4.1"},
{:plug_cowboy, "~> 2.0"},
@@ -121,7 +132,7 @@ defmodule Pleroma.Mixfile do
{:ex_rated, "~> 1.2"},
{:plug_static_index_html, "~> 1.0.0"},
{:excoveralls, "~> 0.11.1", only: :test}
- ] ++ oauth_deps
+ ] ++ oauth_deps()
end
# Aliases are shortcuts or tasks specific to the current project.
diff --git a/priv/repo/migrations/20190525071417_add_non_follows_and_non_followers_fields_to_notification_settings.exs b/priv/repo/migrations/20190525071417_add_non_follows_and_non_followers_fields_to_notification_settings.exs
new file mode 100644
index 000000000..a88b0ea61
--- /dev/null
+++ b/priv/repo/migrations/20190525071417_add_non_follows_and_non_followers_fields_to_notification_settings.exs
@@ -0,0 +1,10 @@
+defmodule Pleroma.Repo.Migrations.AddNonFollowsAndNonFollowersFieldsToNotificationSettings do
+ use Ecto.Migration
+
+ def change do
+ execute("""
+ update users set info = jsonb_set(info, '{notification_settings}', '{"local": true, "remote": true, "follows": true, "followers": true, "non_follows": true, "non_followers": true}')
+ where local=true
+ """)
+ end
+end
diff --git a/priv/repo/migrations/20190603162018_add_object_in_reply_to_index.exs b/priv/repo/migrations/20190603162018_add_object_in_reply_to_index.exs
new file mode 100644
index 000000000..df4ac7782
--- /dev/null
+++ b/priv/repo/migrations/20190603162018_add_object_in_reply_to_index.exs
@@ -0,0 +1,7 @@
+defmodule Pleroma.Repo.Migrations.AddObjectInReplyToIndex do
+ use Ecto.Migration
+
+ def change do
+ create index(:objects, ["(data->>'inReplyTo')"], name: :objects_in_reply_to_index)
+ end
+end
diff --git a/priv/repo/migrations/20190603173419_add_tag_index_to_objects.exs b/priv/repo/migrations/20190603173419_add_tag_index_to_objects.exs
new file mode 100644
index 000000000..c915a0213
--- /dev/null
+++ b/priv/repo/migrations/20190603173419_add_tag_index_to_objects.exs
@@ -0,0 +1,8 @@
+defmodule Pleroma.Repo.Migrations.AddTagIndexToObjects do
+ use Ecto.Migration
+
+ def change do
+ drop_if_exists index(:activities, ["(data #> '{\"object\",\"tag\"}')"], using: :gin, name: :activities_tags)
+ create index(:objects, ["(data->'tag')"], using: :gin, name: :objects_tags)
+ end
+end
diff --git a/test/fixtures/httpoison_mock/emelie.json b/test/fixtures/httpoison_mock/emelie.json
new file mode 100644
index 000000000..592fc0e4e
--- /dev/null
+++ b/test/fixtures/httpoison_mock/emelie.json
@@ -0,0 +1 @@
+{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"manuallyApprovesFollowers":"as:manuallyApprovesFollowers","toot":"http://joinmastodon.org/ns#","featured":{"@id":"toot:featured","@type":"@id"},"alsoKnownAs":{"@id":"as:alsoKnownAs","@type":"@id"},"movedTo":{"@id":"as:movedTo","@type":"@id"},"schema":"http://schema.org#","PropertyValue":"schema:PropertyValue","value":"schema:value","Hashtag":"as:Hashtag","Emoji":"toot:Emoji","IdentityProof":"toot:IdentityProof","focalPoint":{"@container":"@list","@id":"toot:focalPoint"}}],"id":"https://mastodon.social/users/emelie","type":"Person","following":"https://mastodon.social/users/emelie/following","followers":"https://mastodon.social/users/emelie/followers","inbox":"https://mastodon.social/users/emelie/inbox","outbox":"https://mastodon.social/users/emelie/outbox","featured":"https://mastodon.social/users/emelie/collections/featured","preferredUsername":"emelie","name":"emelie 🎨","summary":"\u003cp\u003e23 / \u003ca href=\"https://mastodon.social/tags/sweden\" class=\"mention hashtag\" rel=\"tag\"\u003e#\u003cspan\u003eSweden\u003c/span\u003e\u003c/a\u003e / \u003ca href=\"https://mastodon.social/tags/artist\" class=\"mention hashtag\" rel=\"tag\"\u003e#\u003cspan\u003eArtist\u003c/span\u003e\u003c/a\u003e / \u003ca href=\"https://mastodon.social/tags/equestrian\" class=\"mention hashtag\" rel=\"tag\"\u003e#\u003cspan\u003eEquestrian\u003c/span\u003e\u003c/a\u003e / \u003ca href=\"https://mastodon.social/tags/gamedev\" class=\"mention hashtag\" rel=\"tag\"\u003e#\u003cspan\u003eGameDev\u003c/span\u003e\u003c/a\u003e\u003c/p\u003e\u003cp\u003eIf I ain\u0026apos;t spending time with my pets, I\u0026apos;m probably drawing. 🐴 🐱 🐰\u003c/p\u003e","url":"https://mastodon.social/@emelie","manuallyApprovesFollowers":false,"publicKey":{"id":"https://mastodon.social/users/emelie#main-key","owner":"https://mastodon.social/users/emelie","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu3CWs1oAJPE3ZJ9sj6Ut\n/Mu+mTE7MOijsQc8/6c73XVVuhIEomiozJIH7l8a7S1n5SYL4UuiwcubSOi7u1bb\nGpYnp5TYhN+Cxvq/P80V4/ncNIPSQzS49it7nSLeG5pA21lGPDA44huquES1un6p\n9gSmbTwngVX9oe4MYuUeh0Z7vijjU13Llz1cRq/ZgPQPgfz+2NJf+VeXnvyDZDYx\nZPVBBlrMl3VoGbu0M5L8SjY35559KCZ3woIvqRolcoHXfgvJMdPcJgSZVYxlCw3d\nA95q9jQcn6s87CPSUs7bmYEQCrDVn5m5NER5TzwBmP4cgJl9AaDVWQtRd4jFZNTx\nlQIDAQAB\n-----END PUBLIC KEY-----\n"},"tag":[{"type":"Hashtag","href":"https://mastodon.social/explore/sweden","name":"#sweden"},{"type":"Hashtag","href":"https://mastodon.social/explore/gamedev","name":"#gamedev"},{"type":"Hashtag","href":"https://mastodon.social/explore/artist","name":"#artist"},{"type":"Hashtag","href":"https://mastodon.social/explore/equestrian","name":"#equestrian"}],"attachment":[{"type":"PropertyValue","name":"Ko-fi","value":"\u003ca href=\"https://ko-fi.com/emeliepng\" rel=\"me nofollow noopener\" target=\"_blank\"\u003e\u003cspan class=\"invisible\"\u003ehttps://\u003c/span\u003e\u003cspan class=\"\"\u003eko-fi.com/emeliepng\u003c/span\u003e\u003cspan class=\"invisible\"\u003e\u003c/span\u003e\u003c/a\u003e"},{"type":"PropertyValue","name":"Instagram","value":"\u003ca href=\"https://www.instagram.com/emelie_png/\" rel=\"me nofollow noopener\" target=\"_blank\"\u003e\u003cspan class=\"invisible\"\u003ehttps://www.\u003c/span\u003e\u003cspan class=\"\"\u003einstagram.com/emelie_png/\u003c/span\u003e\u003cspan class=\"invisible\"\u003e\u003c/span\u003e\u003c/a\u003e"},{"type":"PropertyValue","name":"Carrd","value":"\u003ca href=\"https://emelie.carrd.co/\" rel=\"me nofollow noopener\" target=\"_blank\"\u003e\u003cspan class=\"invisible\"\u003ehttps://\u003c/span\u003e\u003cspan class=\"\"\u003eemelie.carrd.co/\u003c/span\u003e\u003cspan class=\"invisible\"\u003e\u003c/span\u003e\u003c/a\u003e"},{"type":"PropertyValue","name":"Artstation","value":"\u003ca href=\"https://emiri.artstation.com\" rel=\"me nofollow noopener\" target=\"_blank\"\u003e\u003cspan class=\"invisible\"\u003ehttps://\u003c/span\u003e\u003cspan class=\"\"\u003eemiri.artstation.com\u003c/span\u003e\u003cspan class=\"invisible\"\u003e\u003c/span\u003e\u003c/a\u003e"}],"endpoints":{"sharedInbox":"https://mastodon.social/inbox"},"icon":{"type":"Image","mediaType":"image/png","url":"https://files.mastodon.social/accounts/avatars/000/015/657/original/e7163f98280da1a4.png"},"image":{"type":"Image","mediaType":"image/png","url":"https://files.mastodon.social/accounts/headers/000/015/657/original/847f331f3dd9e38b.png"}} \ No newline at end of file
diff --git a/test/fixtures/httpoison_mock/rinpatch.json b/test/fixtures/httpoison_mock/rinpatch.json
new file mode 100644
index 000000000..59311ecb6
--- /dev/null
+++ b/test/fixtures/httpoison_mock/rinpatch.json
@@ -0,0 +1,64 @@
+{
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ "https://w3id.org/security/v1",
+ {
+ "manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
+ "toot": "http://joinmastodon.org/ns#",
+ "featured": {
+ "@id": "toot:featured",
+ "@type": "@id"
+ },
+ "alsoKnownAs": {
+ "@id": "as:alsoKnownAs",
+ "@type": "@id"
+ },
+ "movedTo": {
+ "@id": "as:movedTo",
+ "@type": "@id"
+ },
+ "schema": "http://schema.org#",
+ "PropertyValue": "schema:PropertyValue",
+ "value": "schema:value",
+ "Hashtag": "as:Hashtag",
+ "Emoji": "toot:Emoji",
+ "IdentityProof": "toot:IdentityProof",
+ "focalPoint": {
+ "@container": "@list",
+ "@id": "toot:focalPoint"
+ }
+ }
+ ],
+ "id": "https://mastodon.sdf.org/users/rinpatch",
+ "type": "Person",
+ "following": "https://mastodon.sdf.org/users/rinpatch/following",
+ "followers": "https://mastodon.sdf.org/users/rinpatch/followers",
+ "inbox": "https://mastodon.sdf.org/users/rinpatch/inbox",
+ "outbox": "https://mastodon.sdf.org/users/rinpatch/outbox",
+ "featured": "https://mastodon.sdf.org/users/rinpatch/collections/featured",
+ "preferredUsername": "rinpatch",
+ "name": "rinpatch",
+ "summary": "<p>umu</p>",
+ "url": "https://mastodon.sdf.org/@rinpatch",
+ "manuallyApprovesFollowers": false,
+ "publicKey": {
+ "id": "https://mastodon.sdf.org/users/rinpatch#main-key",
+ "owner": "https://mastodon.sdf.org/users/rinpatch",
+ "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1vbhYKDopb5xzfJB2TZY\n0ZvgxqdAhbSKKkQC5Q2b0ofhvueDy2AuZTnVk1/BbHNlqKlwhJUSpA6LiTZVvtcc\nMn6cmSaJJEg30gRF5GARP8FMcuq8e2jmceiW99NnUX17MQXsddSf2JFUwD0rUE8H\nBsgD7UzE9+zlA/PJOTBO7fvBEz9PTQ3r4sRMTJVFvKz2MU/U+aRNTuexRKMMPnUw\nfp6VWh1F44VWJEQOs4tOEjGiQiMQh5OfBk1w2haT3vrDbQvq23tNpUP1cRomLUtx\nEBcGKi5DMMBzE1RTVT1YUykR/zLWlA+JSmw7P6cWtsHYZovs8dgn8Po3X//6N+ng\nTQIDAQAB\n-----END PUBLIC KEY-----\n"
+ },
+ "tag": [],
+ "attachment": [],
+ "endpoints": {
+ "sharedInbox": "https://mastodon.sdf.org/inbox"
+ },
+ "icon": {
+ "type": "Image",
+ "mediaType": "image/jpeg",
+ "url": "https://mastodon.sdf.org/system/accounts/avatars/000/067/580/original/bf05521bf711b7a0.jpg?1533238802"
+ },
+ "image": {
+ "type": "Image",
+ "mediaType": "image/gif",
+ "url": "https://mastodon.sdf.org/system/accounts/headers/000/067/580/original/a99b987e798f7063.gif?1533278217"
+ }
+}
diff --git a/test/fixtures/mastodon-question-activity.json b/test/fixtures/mastodon-question-activity.json
new file mode 100644
index 000000000..ac329c7d5
--- /dev/null
+++ b/test/fixtures/mastodon-question-activity.json
@@ -0,0 +1,99 @@
+{
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ {
+ "ostatus": "http://ostatus.org#",
+ "atomUri": "ostatus:atomUri",
+ "inReplyToAtomUri": "ostatus:inReplyToAtomUri",
+ "conversation": "ostatus:conversation",
+ "sensitive": "as:sensitive",
+ "Hashtag": "as:Hashtag",
+ "toot": "http://joinmastodon.org/ns#",
+ "Emoji": "toot:Emoji",
+ "focalPoint": {
+ "@container": "@list",
+ "@id": "toot:focalPoint"
+ }
+ }
+ ],
+ "id": "https://mastodon.sdf.org/users/rinpatch/statuses/102070944809637304/activity",
+ "type": "Create",
+ "actor": "https://mastodon.sdf.org/users/rinpatch",
+ "published": "2019-05-10T09:03:36Z",
+ "to": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "cc": [
+ "https://mastodon.sdf.org/users/rinpatch/followers"
+ ],
+ "object": {
+ "id": "https://mastodon.sdf.org/users/rinpatch/statuses/102070944809637304",
+ "type": "Question",
+ "summary": null,
+ "inReplyTo": null,
+ "published": "2019-05-10T09:03:36Z",
+ "url": "https://mastodon.sdf.org/@rinpatch/102070944809637304",
+ "attributedTo": "https://mastodon.sdf.org/users/rinpatch",
+ "to": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "cc": [
+ "https://mastodon.sdf.org/users/rinpatch/followers"
+ ],
+ "sensitive": false,
+ "atomUri": "https://mastodon.sdf.org/users/rinpatch/statuses/102070944809637304",
+ "inReplyToAtomUri": null,
+ "conversation": "tag:mastodon.sdf.org,2019-05-10:objectId=15095122:objectType=Conversation",
+ "content": "<p>Why is Tenshi eating a corndog so cute?</p>",
+ "contentMap": {
+ "en": "<p>Why is Tenshi eating a corndog so cute?</p>"
+ },
+ "endTime": "2019-05-11T09:03:36Z",
+ "closed": "2019-05-11T09:03:36Z",
+ "attachment": [],
+ "tag": [],
+ "replies": {
+ "id": "https://mastodon.sdf.org/users/rinpatch/statuses/102070944809637304/replies",
+ "type": "Collection",
+ "first": {
+ "type": "CollectionPage",
+ "partOf": "https://mastodon.sdf.org/users/rinpatch/statuses/102070944809637304/replies",
+ "items": []
+ }
+ },
+ "oneOf": [
+ {
+ "type": "Note",
+ "name": "Dunno",
+ "replies": {
+ "type": "Collection",
+ "totalItems": 0
+ }
+ },
+ {
+ "type": "Note",
+ "name": "Everyone knows that!",
+ "replies": {
+ "type": "Collection",
+ "totalItems": 1
+ }
+ },
+ {
+ "type": "Note",
+ "name": "25 char limit is dumb",
+ "replies": {
+ "type": "Collection",
+ "totalItems": 0
+ }
+ },
+ {
+ "type": "Note",
+ "name": "I can't even fit a funny",
+ "replies": {
+ "type": "Collection",
+ "totalItems": 1
+ }
+ }
+ ]
+ }
+}
diff --git a/test/fixtures/mastodon-vote.json b/test/fixtures/mastodon-vote.json
new file mode 100644
index 000000000..c2c5f40c0
--- /dev/null
+++ b/test/fixtures/mastodon-vote.json
@@ -0,0 +1,16 @@
+{
+ "@context": "https://www.w3.org/ns/activitystreams",
+ "actor": "https://mastodon.sdf.org/users/rinpatch",
+ "id": "https://mastodon.sdf.org/users/rinpatch#votes/387/activity",
+ "nickname": "rin",
+ "object": {
+ "attributedTo": "https://mastodon.sdf.org/users/rinpatch",
+ "id": "https://mastodon.sdf.org/users/rinpatch#votes/387",
+ "inReplyTo": "https://testing.uguu.ltd/objects/9d300947-2dcb-445d-8978-9a3b4b84fa14",
+ "name": "suya..",
+ "to": "https://testing.uguu.ltd/users/rin",
+ "type": "Note"
+ },
+ "to": "https://testing.uguu.ltd/users/rin",
+ "type": "Create"
+}
diff --git a/test/notification_test.exs b/test/notification_test.exs
index 581db58a8..be292abd9 100644
--- a/test/notification_test.exs
+++ b/test/notification_test.exs
@@ -78,33 +78,6 @@ defmodule Pleroma.NotificationTest do
assert nil == Notification.create_notification(activity, muter)
end
- test "it disables notifications from people on remote instances" do
- user = insert(:user, info: %{notification_settings: %{"remote" => false}})
- other_user = insert(:user)
-
- create_activity = %{
- "@context" => "https://www.w3.org/ns/activitystreams",
- "type" => "Create",
- "to" => ["https://www.w3.org/ns/activitystreams#Public"],
- "actor" => other_user.ap_id,
- "object" => %{
- "type" => "Note",
- "content" => "Hi @#{user.nickname}",
- "attributedTo" => other_user.ap_id
- }
- }
-
- {:ok, %{local: false} = activity} = Transmogrifier.handle_incoming(create_activity)
- assert nil == Notification.create_notification(activity, user)
- end
-
- test "it disables notifications from people on the local instance" do
- user = insert(:user, info: %{notification_settings: %{"local" => false}})
- other_user = insert(:user)
- {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"})
- assert nil == Notification.create_notification(activity, user)
- end
-
test "it disables notifications from followers" do
follower = insert(:user)
followed = insert(:user, info: %{notification_settings: %{"followers" => false}})
@@ -113,6 +86,13 @@ defmodule Pleroma.NotificationTest do
assert nil == Notification.create_notification(activity, followed)
end
+ test "it disables notifications from non-followers" do
+ follower = insert(:user)
+ followed = insert(:user, info: %{notification_settings: %{"non_followers" => false}})
+ {:ok, activity} = CommonAPI.post(follower, %{"status" => "hey @#{followed.nickname}"})
+ assert nil == Notification.create_notification(activity, followed)
+ end
+
test "it disables notifications from people the user follows" do
follower = insert(:user, info: %{notification_settings: %{"follows" => false}})
followed = insert(:user)
@@ -122,6 +102,13 @@ defmodule Pleroma.NotificationTest do
assert nil == Notification.create_notification(activity, follower)
end
+ test "it disables notifications from people the user does not follow" do
+ follower = insert(:user, info: %{notification_settings: %{"non_follows" => false}})
+ followed = insert(:user)
+ {:ok, activity} = CommonAPI.post(followed, %{"status" => "hey @#{follower.nickname}"})
+ assert nil == Notification.create_notification(activity, follower)
+ end
+
test "it doesn't create a notification for user if he is the activity author" do
activity = insert(:note_activity)
author = User.get_cached_by_ap_id(activity.data["actor"])
diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex
index 66d7d5ba9..36b9265e7 100644
--- a/test/support/http_request_mock.ex
+++ b/test/support/http_request_mock.ex
@@ -52,6 +52,14 @@ defmodule HttpRequestMock do
}}
end
+ def get("https://mastodon.sdf.org/users/rinpatch", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/httpoison_mock/rinpatch.json")
+ }}
+ end
+
def get(
"https://mastodon.social/.well-known/webfinger?resource=https://mastodon.social/users/emelie",
_,
diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs
index 30adfda36..8b3233729 100644
--- a/test/web/activity_pub/activity_pub_controller_test.exs
+++ b/test/web/activity_pub/activity_pub_controller_test.exs
@@ -11,6 +11,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ObjectView
alias Pleroma.Web.ActivityPub.UserView
+ alias Pleroma.Web.ActivityPub.Utils
setup_all do
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
@@ -234,13 +235,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
end
describe "/users/:nickname/inbox" do
- test "it inserts an incoming activity into the database", %{conn: conn} do
- user = insert(:user)
-
+ setup do
data =
File.read!("test/fixtures/mastodon-post-activity.json")
|> Poison.decode!()
- |> Map.put("bcc", [user.ap_id])
+
+ [data: data]
+ end
+
+ test "it inserts an incoming activity into the database", %{conn: conn, data: data} do
+ user = insert(:user)
+ data = Map.put(data, "bcc", [user.ap_id])
conn =
conn
@@ -253,16 +258,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
assert Activity.get_by_ap_id(data["id"])
end
- test "it accepts messages from actors that are followed by the user", %{conn: conn} do
+ test "it accepts messages from actors that are followed by the user", %{
+ conn: conn,
+ data: data
+ } do
recipient = insert(:user)
actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
{:ok, recipient} = User.follow(recipient, actor)
- data =
- File.read!("test/fixtures/mastodon-post-activity.json")
- |> Poison.decode!()
-
object =
data["object"]
|> Map.put("attributedTo", actor.ap_id)
@@ -309,13 +313,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
assert response(conn, 200) =~ note_activity.data["object"]["content"]
end
- test "it clears `unreachable` federation status of the sender", %{conn: conn} do
+ test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
user = insert(:user)
-
- data =
- File.read!("test/fixtures/mastodon-post-activity.json")
- |> Poison.decode!()
- |> Map.put("bcc", [user.ap_id])
+ data = Map.put(data, "bcc", [user.ap_id])
sender_host = URI.parse(data["actor"]).host
Instances.set_consistently_unreachable(sender_host)
@@ -330,6 +330,47 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
assert "ok" == json_response(conn, 200)
assert Instances.reachable?(sender_host)
end
+
+ test "it removes all follower collections but actor's", %{conn: conn} do
+ [actor, recipient] = insert_pair(:user)
+
+ data =
+ File.read!("test/fixtures/activitypub-client-post-activity.json")
+ |> Poison.decode!()
+
+ object = Map.put(data["object"], "attributedTo", actor.ap_id)
+
+ data =
+ data
+ |> Map.put("id", Utils.generate_object_id())
+ |> Map.put("actor", actor.ap_id)
+ |> Map.put("object", object)
+ |> Map.put("cc", [
+ recipient.follower_address,
+ actor.follower_address
+ ])
+ |> Map.put("to", [
+ recipient.ap_id,
+ recipient.follower_address,
+ "https://www.w3.org/ns/activitystreams#Public"
+ ])
+
+ conn
+ |> assign(:valid_signature, true)
+ |> put_req_header("content-type", "application/activity+json")
+ |> post("/users/#{recipient.nickname}/inbox", data)
+ |> json_response(200)
+
+ activity = Activity.get_by_ap_id(data["id"])
+
+ assert activity.id
+ assert actor.follower_address in activity.recipients
+ assert actor.follower_address in activity.data["cc"]
+
+ refute recipient.follower_address in activity.recipients
+ refute recipient.follower_address in activity.data["cc"]
+ refute recipient.follower_address in activity.data["to"]
+ end
end
describe "/users/:nickname/outbox" do
diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs
index f743f380b..76586ee4a 100644
--- a/test/web/activity_pub/activity_pub_test.exs
+++ b/test/web/activity_pub/activity_pub_test.exs
@@ -1186,4 +1186,33 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
def data_uri do
File.read!("test/fixtures/avatar_data_uri")
end
+
+ describe "fetch_activities_bounded" do
+ test "fetches private posts for followed users" do
+ user = insert(:user)
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{
+ "status" => "thought I looked cute might delete later :3",
+ "visibility" => "private"
+ })
+
+ [result] = ActivityPub.fetch_activities_bounded([user.follower_address], [])
+ assert result.id == activity.id
+ end
+
+ test "fetches only public posts for other users" do
+ user = insert(:user)
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe", "visibility" => "public"})
+
+ {:ok, _private_activity} =
+ CommonAPI.post(user, %{
+ "status" => "why is tenshi eating a corndog so cute?",
+ "visibility" => "private"
+ })
+
+ [result] = ActivityPub.fetch_activities_bounded([], [user.follower_address])
+ assert result.id == activity.id
+ end
+ end
end
diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs
index c24b50f8c..89c8f79c9 100644
--- a/test/web/activity_pub/transmogrifier_test.exs
+++ b/test/web/activity_pub/transmogrifier_test.exs
@@ -113,6 +113,55 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
assert Enum.at(object.data["tag"], 2) == "moo"
end
+ test "it works for incoming questions" do
+ data = File.read!("test/fixtures/mastodon-question-activity.json") |> Poison.decode!()
+
+ {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
+
+ object = Object.normalize(activity)
+
+ assert Enum.all?(object.data["oneOf"], fn choice ->
+ choice["name"] in [
+ "Dunno",
+ "Everyone knows that!",
+ "25 char limit is dumb",
+ "I can't even fit a funny"
+ ]
+ end)
+ end
+
+ test "it rewrites Note votes to Answers and increments vote counters on question activities" do
+ user = insert(:user)
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{
+ "status" => "suya...",
+ "poll" => %{"options" => ["suya", "suya.", "suya.."], "expires_in" => 10}
+ })
+
+ object = Object.normalize(activity)
+
+ data =
+ File.read!("test/fixtures/mastodon-vote.json")
+ |> Poison.decode!()
+ |> Kernel.put_in(["to"], user.ap_id)
+ |> Kernel.put_in(["object", "inReplyTo"], object.data["id"])
+ |> Kernel.put_in(["object", "to"], user.ap_id)
+
+ {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
+ answer_object = Object.normalize(activity)
+ assert answer_object.data["type"] == "Answer"
+ object = Object.get_by_ap_id(object.data["id"])
+
+ assert Enum.any?(
+ object.data["oneOf"],
+ fn
+ %{"name" => "suya..", "replies" => %{"totalItems" => 1}} -> true
+ _ -> false
+ end
+ )
+ end
+
test "it works for incoming notices with contentMap" do
data =
File.read!("test/fixtures/mastodon-post-activity-contentmap.json") |> Poison.decode!()
@@ -1209,4 +1258,85 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
{:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
end
end
+
+ test "Rewrites Answers to Notes" do
+ user = insert(:user)
+
+ {:ok, poll_activity} =
+ CommonAPI.post(user, %{
+ "status" => "suya...",
+ "poll" => %{"options" => ["suya", "suya.", "suya.."], "expires_in" => 10}
+ })
+
+ poll_object = Object.normalize(poll_activity)
+ # TODO: Replace with CommonAPI vote creation when implemented
+ data =
+ File.read!("test/fixtures/mastodon-vote.json")
+ |> Poison.decode!()
+ |> Kernel.put_in(["to"], user.ap_id)
+ |> Kernel.put_in(["object", "inReplyTo"], poll_object.data["id"])
+ |> Kernel.put_in(["object", "to"], user.ap_id)
+
+ {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
+ {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
+
+ assert data["object"]["type"] == "Note"
+ end
+
+ describe "fix_explicit_addressing" do
+ setup do
+ user = insert(:user)
+ [user: user]
+ end
+
+ test "moves non-explicitly mentioned actors to cc", %{user: user} do
+ explicitly_mentioned_actors = [
+ "https://pleroma.gold/users/user1",
+ "https://pleroma.gold/user2"
+ ]
+
+ object = %{
+ "actor" => user.ap_id,
+ "to" => explicitly_mentioned_actors ++ ["https://social.beepboop.ga/users/dirb"],
+ "cc" => [],
+ "tag" =>
+ Enum.map(explicitly_mentioned_actors, fn href ->
+ %{"type" => "Mention", "href" => href}
+ end)
+ }
+
+ fixed_object = Transmogrifier.fix_explicit_addressing(object)
+ assert Enum.all?(explicitly_mentioned_actors, &(&1 in fixed_object["to"]))
+ refute "https://social.beepboop.ga/users/dirb" in fixed_object["to"]
+ assert "https://social.beepboop.ga/users/dirb" in fixed_object["cc"]
+ end
+
+ test "does not move actor's follower collection to cc", %{user: user} do
+ object = %{
+ "actor" => user.ap_id,
+ "to" => [user.follower_address],
+ "cc" => []
+ }
+
+ fixed_object = Transmogrifier.fix_explicit_addressing(object)
+ assert user.follower_address in fixed_object["to"]
+ refute user.follower_address in fixed_object["cc"]
+ end
+
+ test "removes recipient's follower collection from cc", %{user: user} do
+ recipient = insert(:user)
+
+ object = %{
+ "actor" => user.ap_id,
+ "to" => [recipient.ap_id, "https://www.w3.org/ns/activitystreams#Public"],
+ "cc" => [user.follower_address, recipient.follower_address]
+ }
+
+ fixed_object = Transmogrifier.fix_explicit_addressing(object)
+
+ assert user.follower_address in fixed_object["cc"]
+ refute recipient.follower_address in fixed_object["cc"]
+ refute recipient.follower_address in fixed_object["to"]
+ end
+ end
end
diff --git a/test/web/activity_pub/visibilty_test.exs b/test/web/activity_pub/visibilty_test.exs
index e2584f635..466d980dc 100644
--- a/test/web/activity_pub/visibilty_test.exs
+++ b/test/web/activity_pub/visibilty_test.exs
@@ -117,4 +117,8 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do
assert Visibility.get_visibility(direct) == "direct"
assert Visibility.get_visibility(unlisted) == "unlisted"
end
+
+ test "get_visibility with directMessage flag" do
+ assert Visibility.get_visibility(%{data: %{"directMessage" => true}}) == "direct"
+ end
end
diff --git a/test/web/mastodon_api/account_view_test.exs b/test/web/mastodon_api/account_view_test.exs
index 5e6f1d00b..1611d937e 100644
--- a/test/web/mastodon_api/account_view_test.exs
+++ b/test/web/mastodon_api/account_view_test.exs
@@ -78,10 +78,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
user = insert(:user)
notification_settings = %{
- "remote" => true,
- "local" => true,
"followers" => true,
- "follows" => true
+ "follows" => true,
+ "non_follows" => true,
+ "non_followers" => true
}
privacy = user.info.default_scope
diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs
index 59a7967bd..b0cde649d 100644
--- a/test/web/mastodon_api/mastodon_api_controller_test.exs
+++ b/test/web/mastodon_api/mastodon_api_controller_test.exs
@@ -146,6 +146,103 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
refute id == third_id
end
+ describe "posting polls" do
+ test "posting a poll", %{conn: conn} do
+ user = insert(:user)
+ time = NaiveDateTime.utc_now()
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/statuses", %{
+ "status" => "Who is the #bestgrill?",
+ "poll" => %{"options" => ["Rei", "Asuka", "Misato"], "expires_in" => 420}
+ })
+
+ response = json_response(conn, 200)
+
+ assert Enum.all?(response["poll"]["options"], fn %{"title" => title} ->
+ title in ["Rei", "Asuka", "Misato"]
+ end)
+
+ assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430
+ refute response["poll"]["expred"]
+ end
+
+ test "option limit is enforced", %{conn: conn} do
+ user = insert(:user)
+ limit = Pleroma.Config.get([:instance, :poll_limits, :max_options])
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/statuses", %{
+ "status" => "desu~",
+ "poll" => %{"options" => Enum.map(0..limit, fn _ -> "desu" end), "expires_in" => 1}
+ })
+
+ %{"error" => error} = json_response(conn, 422)
+ assert error == "Poll can't contain more than #{limit} options"
+ end
+
+ test "option character limit is enforced", %{conn: conn} do
+ user = insert(:user)
+ limit = Pleroma.Config.get([:instance, :poll_limits, :max_option_chars])
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/statuses", %{
+ "status" => "...",
+ "poll" => %{
+ "options" => [Enum.reduce(0..limit, "", fn _, acc -> acc <> "." end)],
+ "expires_in" => 1
+ }
+ })
+
+ %{"error" => error} = json_response(conn, 422)
+ assert error == "Poll options cannot be longer than #{limit} characters each"
+ end
+
+ test "minimal date limit is enforced", %{conn: conn} do
+ user = insert(:user)
+ limit = Pleroma.Config.get([:instance, :poll_limits, :min_expiration])
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/statuses", %{
+ "status" => "imagine arbitrary limits",
+ "poll" => %{
+ "options" => ["this post was made by pleroma gang"],
+ "expires_in" => limit - 1
+ }
+ })
+
+ %{"error" => error} = json_response(conn, 422)
+ assert error == "Expiration date is too soon"
+ end
+
+ test "maximum date limit is enforced", %{conn: conn} do
+ user = insert(:user)
+ limit = Pleroma.Config.get([:instance, :poll_limits, :max_expiration])
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/statuses", %{
+ "status" => "imagine arbitrary limits",
+ "poll" => %{
+ "options" => ["this post was made by pleroma gang"],
+ "expires_in" => limit + 1
+ }
+ })
+
+ %{"error" => error} = json_response(conn, 422)
+ assert error == "Expiration date is too far in the future"
+ end
+ end
+
test "posting a sensitive status", %{conn: conn} do
user = insert(:user)
@@ -317,12 +414,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
test "Conversations", %{conn: conn} do
user_one = insert(:user)
user_two = insert(:user)
+ user_three = insert(:user)
{:ok, user_two} = User.follow(user_two, user_one)
{:ok, direct} =
CommonAPI.post(user_one, %{
- "status" => "Hi @#{user_two.nickname}!",
+ "status" => "Hi @#{user_two.nickname}, @#{user_three.nickname}!",
"visibility" => "direct"
})
@@ -348,7 +446,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
}
] = response
+ account_ids = Enum.map(res_accounts, & &1["id"])
assert length(res_accounts) == 2
+ assert user_two.id in account_ids
+ assert user_three.id in account_ids
assert is_binary(res_id)
assert unread == true
assert res_last_status["id"] == direct.id
@@ -2598,7 +2699,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
"stats" => _,
"thumbnail" => _,
"languages" => _,
- "registrations" => _
+ "registrations" => _,
+ "poll_limits" => _
} = result
assert email == from_config_email
@@ -2912,31 +3014,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
end
end
- test "flavours switching (Pleroma Extension)", %{conn: conn} do
- user = insert(:user)
-
- get_old_flavour =
- conn
- |> assign(:user, user)
- |> get("/api/v1/pleroma/flavour")
-
- assert "glitch" == json_response(get_old_flavour, 200)
-
- set_flavour =
- conn
- |> assign(:user, user)
- |> post("/api/v1/pleroma/flavour/vanilla")
-
- assert "vanilla" == json_response(set_flavour, 200)
-
- get_new_flavour =
- conn
- |> assign(:user, user)
- |> post("/api/v1/pleroma/flavour/vanilla")
-
- assert json_response(set_flavour, 200) == json_response(get_new_flavour, 200)
- end
-
describe "reports" do
setup do
reporter = insert(:user)
@@ -3522,4 +3599,124 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert json_response(conn, 403) == %{"error" => "Rate limit exceeded."}
end
end
+
+ describe "GET /api/v1/polls/:id" do
+ test "returns poll entity for object id", %{conn: conn} do
+ user = insert(:user)
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{
+ "status" => "Pleroma does",
+ "poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20}
+ })
+
+ object = Object.normalize(activity)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/polls/#{object.id}")
+
+ response = json_response(conn, 200)
+ id = object.id
+ assert %{"id" => ^id, "expired" => false, "multiple" => false} = response
+ end
+
+ test "does not expose polls for private statuses", %{conn: conn} do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{
+ "status" => "Pleroma does",
+ "poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20},
+ "visibility" => "private"
+ })
+
+ object = Object.normalize(activity)
+
+ conn =
+ conn
+ |> assign(:user, other_user)
+ |> get("/api/v1/polls/#{object.id}")
+
+ assert json_response(conn, 404)
+ end
+ end
+
+ describe "POST /api/v1/polls/:id/votes" do
+ test "votes are added to the poll", %{conn: conn} do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{
+ "status" => "A very delicious sandwich",
+ "poll" => %{
+ "options" => ["Lettuce", "Grilled Bacon", "Tomato"],
+ "expires_in" => 20,
+ "multiple" => true
+ }
+ })
+
+ object = Object.normalize(activity)
+
+ conn =
+ conn
+ |> assign(:user, other_user)
+ |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1, 2]})
+
+ assert json_response(conn, 200)
+ object = Object.get_by_id(object.id)
+
+ assert Enum.all?(object.data["anyOf"], fn %{"replies" => %{"totalItems" => total_items}} ->
+ total_items == 1
+ end)
+ end
+
+ test "author can't vote", %{conn: conn} do
+ user = insert(:user)
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{
+ "status" => "Am I cute?",
+ "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20}
+ })
+
+ object = Object.normalize(activity)
+
+ assert conn
+ |> assign(:user, user)
+ |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [1]})
+ |> json_response(422) == %{"error" => "Poll's author can't vote"}
+
+ object = Object.get_by_id(object.id)
+
+ refute Enum.at(object.data["oneOf"], 1)["replies"]["totalItems"] == 1
+ end
+
+ test "does not allow multiple choices on a single-choice question", %{conn: conn} do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{
+ "status" => "The glass is",
+ "poll" => %{"options" => ["half empty", "half full"], "expires_in" => 20}
+ })
+
+ object = Object.normalize(activity)
+
+ assert conn
+ |> assign(:user, other_user)
+ |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1]})
+ |> json_response(422) == %{"error" => "Too many choices"}
+
+ object = Object.get_by_id(object.id)
+
+ refute Enum.any?(object.data["oneOf"], fn %{"replies" => %{"totalItems" => total_items}} ->
+ total_items == 1
+ end)
+ end
+ end
end
diff --git a/test/web/mastodon_api/status_view_test.exs b/test/web/mastodon_api/status_view_test.exs
index d7c800e83..ec75150ab 100644
--- a/test/web/mastodon_api/status_view_test.exs
+++ b/test/web/mastodon_api/status_view_test.exs
@@ -103,6 +103,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
muted: false,
pinned: false,
sensitive: false,
+ poll: nil,
spoiler_text: HtmlSanitizeEx.basic_html(note.data["object"]["summary"]),
visibility: "public",
media_attachments: [],
@@ -341,4 +342,106 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
StatusView.render("card.json", %{page_url: page_url, rich_media: card})
end
end
+
+ describe "poll view" do
+ test "renders a poll" do
+ user = insert(:user)
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{
+ "status" => "Is Tenshi eating a corndog cute?",
+ "poll" => %{
+ "options" => ["absolutely!", "sure", "yes", "why are you even asking?"],
+ "expires_in" => 20
+ }
+ })
+
+ object = Object.normalize(activity)
+
+ expected = %{
+ emojis: [],
+ expired: false,
+ id: object.id,
+ multiple: false,
+ options: [
+ %{title: "absolutely!", votes_count: 0},
+ %{title: "sure", votes_count: 0},
+ %{title: "yes", votes_count: 0},
+ %{title: "why are you even asking?", votes_count: 0}
+ ],
+ voted: false,
+ votes_count: 0
+ }
+
+ result = StatusView.render("poll.json", %{object: object})
+ expires_at = result.expires_at
+ result = Map.delete(result, :expires_at)
+
+ assert result == expected
+
+ expires_at = NaiveDateTime.from_iso8601!(expires_at)
+ assert NaiveDateTime.diff(expires_at, NaiveDateTime.utc_now()) in 15..20
+ end
+
+ test "detects if it is multiple choice" do
+ user = insert(:user)
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{
+ "status" => "Which Mastodon developer is your favourite?",
+ "poll" => %{
+ "options" => ["Gargron", "Eugen"],
+ "expires_in" => 20,
+ "multiple" => true
+ }
+ })
+
+ object = Object.normalize(activity)
+
+ assert %{multiple: true} = StatusView.render("poll.json", %{object: object})
+ end
+
+ test "detects emoji" do
+ user = insert(:user)
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{
+ "status" => "What's with the smug face?",
+ "poll" => %{
+ "options" => [":blank: sip", ":blank::blank: sip", ":blank::blank::blank: sip"],
+ "expires_in" => 20
+ }
+ })
+
+ object = Object.normalize(activity)
+
+ assert %{emojis: [%{shortcode: "blank"}]} =
+ StatusView.render("poll.json", %{object: object})
+ end
+
+ test "detects vote status" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{
+ "status" => "Which input devices do you use?",
+ "poll" => %{
+ "options" => ["mouse", "trackball", "trackpoint"],
+ "multiple" => true,
+ "expires_in" => 20
+ }
+ })
+
+ object = Object.normalize(activity)
+
+ {:ok, _, object} = CommonAPI.vote(other_user, object, [1, 2])
+
+ result = StatusView.render("poll.json", %{object: object, for: other_user})
+
+ assert result[:voted] == true
+ assert Enum.at(result[:options], 1)[:votes_count] == 1
+ assert Enum.at(result[:options], 2)[:votes_count] == 1
+ end
+ end
end
diff --git a/test/web/twitter_api/util_controller_test.exs b/test/web/twitter_api/util_controller_test.exs
index 2cd82b3e7..cab9e5d90 100644
--- a/test/web/twitter_api/util_controller_test.exs
+++ b/test/web/twitter_api/util_controller_test.exs
@@ -102,7 +102,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
conn
|> assign(:user, user)
|> put("/api/pleroma/notification_settings", %{
- "remote" => false,
"followers" => false,
"bar" => 1
})
@@ -110,8 +109,12 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
user = Repo.get(User, user.id)
- assert %{"remote" => false, "local" => true, "followers" => false, "follows" => true} ==
- user.info.notification_settings
+ assert %{
+ "followers" => false,
+ "follows" => true,
+ "non_follows" => true,
+ "non_followers" => true
+ } == user.info.notification_settings
end
end
diff --git a/test/web/twitter_api/views/user_view_test.exs b/test/web/twitter_api/views/user_view_test.exs
index 74526673c..a48fc9b78 100644
--- a/test/web/twitter_api/views/user_view_test.exs
+++ b/test/web/twitter_api/views/user_view_test.exs
@@ -112,9 +112,11 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do
as_user = UserView.render("show.json", %{user: user, for: user})
assert as_user["default_scope"] == user.info.default_scope
assert as_user["no_rich_text"] == user.info.no_rich_text
+ assert as_user["pleroma"]["notification_settings"] == user.info.notification_settings
as_stranger = UserView.render("show.json", %{user: user})
refute as_stranger["default_scope"]
refute as_stranger["no_rich_text"]
+ refute as_stranger["pleroma"]["notification_settings"]
end
test "A user for a given other follower", %{user: user} do