forked from AkkomaGang/akkoma
Merge remote-tracking branch 'origin/develop' into pleroma-conversations
This commit is contained in:
commit
560dbad538
105 changed files with 1794 additions and 257 deletions
13
CHANGELOG.md
13
CHANGELOG.md
|
@ -23,9 +23,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- Not being able to pin unlisted posts
|
||||
- Objects being re-embedded to activities after being updated (e.g faved/reposted). Running 'mix pleroma.database prune_objects' again is advised.
|
||||
- Metadata rendering errors resulting in the entire page being inaccessible
|
||||
- `federation_incoming_replies_max_depth` option being ignored in certain cases
|
||||
- Federation/MediaProxy not working with instances that have wrong certificate order
|
||||
- Mastodon API: Handling of search timeouts (`/api/v1/search` and `/api/v2/search`)
|
||||
- Mastodon API: Embedded relationships not being properly rendered in the Account entity of Status entity
|
||||
- Mastodon API: follower/following counters not being nullified, when `hide_follows`/`hide_followers` is set
|
||||
- Mastodon API: `muted` in the Status entity, using author's account to determine if the tread was muted
|
||||
- Mastodon API: Add `account_id`, `type`, `offset`, and `limit` to search API (`/api/v1/search` and `/api/v2/search`)
|
||||
- Mastodon API, streaming: Fix filtering of notifications based on blocks/mutes/thread mutes
|
||||
- ActivityPub C2S: follower/following collection pages being inaccessible even when authentifucated if `hide_followers`/ `hide_follows` was set
|
||||
|
@ -38,12 +41,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- Invalid SemVer version generation, when the current branch does not have commits ahead of tag/checked out on a tag
|
||||
- Pleroma.Upload base_url was not automatically whitelisted by MediaProxy. Now your custom CDN or file hosting will be accessed directly as expected.
|
||||
- Report email not being sent to admins when the reporter is a remote user
|
||||
- MRF: ensure that subdomain_match calls are case-insensitive
|
||||
|
||||
### Added
|
||||
- Conversations: Add Pleroma-specific conversation endpoints and status posting extensions. Run the `bump_all_conversations` task again to create the necessary data.
|
||||
- **Breaking:** MRF describe API, which adds support for exposing configuration information about MRF policies to NodeInfo.
|
||||
Custom modules will need to be updated by adding, at the very least, `def describe, do: {:ok, %{}}` to the MRF policy modules.
|
||||
- MRF: Support for priming the mediaproxy cache (`Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`)
|
||||
- MRF: Support for excluding specific domains from Transparency.
|
||||
- MRF: Support for filtering posts based on who they mention (`Pleroma.Web.ActivityPub.MRF.MentionPolicy`)
|
||||
- MRF: Support for filtering posts based on ActivityStreams vocabulary (`Pleroma.Web.ActivityPub.MRF.VocabularyPolicy`)
|
||||
- MRF (Simple Policy): Support for wildcard domains.
|
||||
- Support for wildcard domains in user domain blocks setting.
|
||||
- Configuration: `quarantined_instances` support wildcard domains.
|
||||
|
@ -66,6 +73,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- Added synchronization of following/followers counters for external users
|
||||
- Configuration: `enabled` option for `Pleroma.Emails.Mailer`, defaulting to `false`.
|
||||
- Configuration: Pleroma.Plugs.RateLimiter `bucket_name`, `params` options.
|
||||
- Configuration: `user_bio_length` and `user_name_length` options.
|
||||
- Addressable lists
|
||||
- Twitter API: added rate limit for `/api/account/password_reset` endpoint.
|
||||
- ActivityPub: Add an internal service actor for fetching ActivityPub objects.
|
||||
|
@ -73,6 +81,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- Admin API: Endpoint for fetching latest user's statuses
|
||||
- Pleroma API: Add `/api/v1/pleroma/accounts/confirmation_resend?email=<email>` for resending account confirmation.
|
||||
- Relays: Added a task to list relay subscriptions.
|
||||
- Mix Tasks: `mix pleroma.database fix_likes_collections`
|
||||
- Federation: Remove `likes` from objects.
|
||||
|
||||
### Changed
|
||||
- Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text
|
||||
|
@ -83,6 +93,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
### Removed
|
||||
- Emoji: Remove longfox emojis.
|
||||
- Remove `Reply-To` header from report emails for admins.
|
||||
- ActivityPub: The `accept_blocks` configuration setting.
|
||||
|
||||
## [1.0.1] - 2019-07-14
|
||||
### Security
|
||||
|
@ -94,6 +105,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- Rich media: Do not crawl private IP ranges
|
||||
|
||||
### Added
|
||||
- Digest email for inactive users
|
||||
- Add a generic settings store for frontends / clients to use.
|
||||
- Explicit addressing option for posting.
|
||||
- Optional SSH access mode. (Needs `erlang-ssh` package on some distributions).
|
||||
|
@ -120,6 +132,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- Configuration: `notify_email` option
|
||||
- Configuration: Media proxy `whitelist` option
|
||||
- Configuration: `report_uri` option
|
||||
- Configuration: `email_notifications` option
|
||||
- Configuration: `limit_to_local_content` option
|
||||
- Pleroma API: User subscriptions
|
||||
- Pleroma API: Healthcheck endpoint
|
||||
|
|
|
@ -253,6 +253,8 @@
|
|||
skip_thread_containment: true,
|
||||
limit_to_local_content: :unauthenticated,
|
||||
dynamic_configuration: false,
|
||||
user_bio_length: 5000,
|
||||
user_name_length: 100,
|
||||
external_user_synchronization: true
|
||||
|
||||
config :pleroma, :markup,
|
||||
|
@ -302,7 +304,6 @@
|
|||
default_mascot: :pleroma_fox_tan
|
||||
|
||||
config :pleroma, :activitypub,
|
||||
accept_blocks: true,
|
||||
unfollow_blocked: true,
|
||||
outgoing_blocks: true,
|
||||
follow_handshake_timeout: 500,
|
||||
|
@ -337,6 +338,10 @@
|
|||
|
||||
config :pleroma, :mrf_subchain, match_actor: %{}
|
||||
|
||||
config :pleroma, :mrf_vocabulary,
|
||||
accept: [],
|
||||
reject: []
|
||||
|
||||
config :pleroma, :rich_media,
|
||||
enabled: true,
|
||||
ignore_hosts: [],
|
||||
|
@ -514,6 +519,14 @@
|
|||
total_user_limit: 300,
|
||||
enabled: true
|
||||
|
||||
config :pleroma, :email_notifications,
|
||||
digest: %{
|
||||
active: false,
|
||||
schedule: "0 0 * * 0",
|
||||
interval: 7,
|
||||
inactivity_threshold: 7
|
||||
}
|
||||
|
||||
config :pleroma, :oauth2,
|
||||
token_expires_in: 600,
|
||||
issue_new_refresh_token: true,
|
||||
|
|
|
@ -81,6 +81,8 @@
|
|||
config :pleroma, :database, rum_enabled: rum_enabled
|
||||
IO.puts("RUM enabled: #{rum_enabled}")
|
||||
|
||||
config :joken, default_signer: "yU8uHKq+yyAkZ11Hx//jcdacWc8yQ1bxAAGrplzB0Zwwjkp35v0RK9SO8WTPr6QZ"
|
||||
|
||||
config :pleroma, Pleroma.ReverseProxy.Client, Pleroma.ReverseProxy.ClientMock
|
||||
|
||||
try do
|
||||
|
|
|
@ -18,6 +18,7 @@ Note: `strip_exif` has been replaced by `Pleroma.Upload.Filter.Mogrify`.
|
|||
|
||||
## Pleroma.Uploaders.S3
|
||||
* `bucket`: S3 bucket name
|
||||
* `bucket_namespace`: S3 bucket namespace
|
||||
* `public_endpoint`: S3 endpoint that the user finally accesses(ex. "https://s3.dualstack.ap-northeast-1.amazonaws.com")
|
||||
* `truncated_namespace`: If you use S3 compatible service such as Digital Ocean Spaces or CDN, set folder name or "" etc.
|
||||
For example, when using CDN to S3 virtual host format, set "".
|
||||
|
@ -102,6 +103,7 @@ config :pleroma, Pleroma.Emails.Mailer,
|
|||
* `Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy`: Rejects posts from likely spambots by rejecting posts from new users that contain links.
|
||||
* `Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`: Crawls attachments using their MediaProxy URLs so that the MediaProxy cache is primed.
|
||||
* `Pleroma.Web.ActivityPub.MRF.MentionPolicy`: Drops posts mentioning configurable users. (see `:mrf_mention` section)
|
||||
* `Pleroma.Web.ActivityPub.MRF.VocabularyPolicy`: Restricts activities to a configured set of vocabulary. (see `:mrf_vocabulary` section)
|
||||
* `public`: Makes the client API in authentificated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network.
|
||||
* `quarantined_instances`: List of ActivityPub instances where private(DMs, followers-only) activities will not be send.
|
||||
* `managed_config`: Whenether the config for pleroma-fe is configured in this config or in ``static/config.json``
|
||||
|
@ -125,6 +127,8 @@ config :pleroma, Pleroma.Emails.Mailer,
|
|||
* `safe_dm_mentions`: If set to true, only mentions at the beginning of a post will be used to address people in direct messages. This is to prevent accidental mentioning of people when talking about them (e.g. "@friend hey i really don't like @enemy"). Default: `false`.
|
||||
* `healthcheck`: If set to true, system data will be shown on ``/api/pleroma/healthcheck``.
|
||||
* `remote_post_retention_days`: The default amount of days to retain remote posts when pruning the database.
|
||||
* `user_bio_length`: A user bio maximum length (default: `5000`)
|
||||
* `user_name_length`: A user name maximum length (default: `100`)
|
||||
* `skip_thread_containment`: Skip filter out broken threads. The default is `false`.
|
||||
* `limit_to_local_content`: Limit unauthenticated users to search for local statutes and users only. Possible values: `:unauthenticated`, `:all` and `false`. The default is `:unauthenticated`.
|
||||
* `dynamic_configuration`: Allow transferring configuration to DB with the subsequent customization from Admin api.
|
||||
|
@ -275,6 +279,10 @@ config :pleroma, :mrf_subchain,
|
|||
## :mrf_mention
|
||||
* `actors`: A list of actors, for which to drop any posts mentioning.
|
||||
|
||||
## :mrf_vocabulary
|
||||
* `accept`: A list of ActivityStreams terms to accept. If empty, all supported messages are accepted.
|
||||
* `reject`: A list of ActivityStreams terms to reject. If empty, no messages are rejected.
|
||||
|
||||
## :media_proxy
|
||||
* `enabled`: Enables proxying of remote media to the instance’s proxy
|
||||
* `base_url`: The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts.
|
||||
|
@ -328,7 +336,6 @@ config :pleroma, Pleroma.Web.Endpoint,
|
|||
This will make Pleroma listen on `127.0.0.1` port `8080` and generate urls starting with `https://example.com:2020`
|
||||
|
||||
## :activitypub
|
||||
* ``accept_blocks``: Whether to accept incoming block activities from other instances
|
||||
* ``unfollow_blocked``: Whether blocks result in people getting unfollowed
|
||||
* ``outgoing_blocks``: Whether to federate blocks to other instances
|
||||
* ``deny_follow_blocked``: Whether to disallow following an account that has blocked the user in question
|
||||
|
@ -536,6 +543,18 @@ Authentication / authorization settings.
|
|||
* `oauth_consumer_template`: OAuth consumer mode authentication form template. By default it's `consumer.html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex`.
|
||||
* `oauth_consumer_strategies`: the list of enabled OAuth consumer strategies; by default it's set by `OAUTH_CONSUMER_STRATEGIES` environment variable. Each entry in this space-delimited string should be of format `<strategy>` or `<strategy>:<dependency>` (e.g. `twitter` or `keycloak:ueberauth_keycloak_strategy` in case dependency is named differently than `ueberauth_<strategy>`).
|
||||
|
||||
## :email_notifications
|
||||
|
||||
Email notifications settings.
|
||||
|
||||
- digest - emails of "what you've missed" for users who have been
|
||||
inactive for a while.
|
||||
- active: globally enable or disable digest emails
|
||||
- schedule: When to send digest email, in [crontab format](https://en.wikipedia.org/wiki/Cron).
|
||||
"0 0 * * 0" is the default, meaning "once a week at midnight on Sunday morning"
|
||||
- interval: Minimum interval between digest emails to one user
|
||||
- inactivity_threshold: Minimum user inactivity threshold
|
||||
|
||||
## OAuth consumer mode
|
||||
|
||||
OAuth consumer mode allows sign in / sign up via external OAuth providers (e.g. Twitter, Facebook, Google, Microsoft, etc.).
|
||||
|
|
|
@ -36,6 +36,10 @@ defmodule Mix.Tasks.Pleroma.Database do
|
|||
## Remove duplicated items from following and update followers count for all users
|
||||
|
||||
mix pleroma.database update_users_following_followers_counts
|
||||
|
||||
## Fix the pre-existing "likes" collections for all objects
|
||||
|
||||
mix pleroma.database fix_likes_collections
|
||||
"""
|
||||
def run(["remove_embedded_objects" | args]) do
|
||||
{options, [], []} =
|
||||
|
@ -125,4 +129,36 @@ def run(["prune_objects" | args]) do
|
|||
)
|
||||
end
|
||||
end
|
||||
|
||||
def run(["fix_likes_collections"]) do
|
||||
import Ecto.Query
|
||||
|
||||
start_pleroma()
|
||||
|
||||
from(object in Object,
|
||||
where: fragment("(?)->>'likes' is not null", object.data),
|
||||
select: %{id: object.id, likes: fragment("(?)->>'likes'", object.data)}
|
||||
)
|
||||
|> Pleroma.RepoStreamer.chunk_stream(100)
|
||||
|> Stream.each(fn objects ->
|
||||
ids =
|
||||
objects
|
||||
|> Enum.filter(fn object -> object.likes |> Jason.decode!() |> is_map() end)
|
||||
|> Enum.map(& &1.id)
|
||||
|
||||
Object
|
||||
|> where([object], object.id in ^ids)
|
||||
|> update([object],
|
||||
set: [
|
||||
data:
|
||||
fragment(
|
||||
"jsonb_set(?, '{likes}', '[]'::jsonb, true)",
|
||||
object.data
|
||||
)
|
||||
]
|
||||
)
|
||||
|> Repo.update_all([], timeout: :infinity)
|
||||
end)
|
||||
|> Stream.run()
|
||||
end
|
||||
end
|
||||
|
|
33
lib/mix/tasks/pleroma/digest.ex
Normal file
33
lib/mix/tasks/pleroma/digest.ex
Normal file
|
@ -0,0 +1,33 @@
|
|||
defmodule Mix.Tasks.Pleroma.Digest do
|
||||
use Mix.Task
|
||||
|
||||
@shortdoc "Manages digest emails"
|
||||
@moduledoc """
|
||||
Manages digest emails
|
||||
|
||||
## Send digest email since given date (user registration date by default)
|
||||
ignoring user activity status.
|
||||
|
||||
``mix pleroma.digest test <nickname> <since_date>``
|
||||
|
||||
Example: ``mix pleroma.digest test donaldtheduck 2019-05-20``
|
||||
"""
|
||||
def run(["test", nickname | opts]) do
|
||||
Mix.Pleroma.start_pleroma()
|
||||
|
||||
user = Pleroma.User.get_by_nickname(nickname)
|
||||
|
||||
last_digest_emailed_at =
|
||||
with [date] <- opts,
|
||||
{:ok, datetime} <- Timex.parse(date, "{YYYY}-{0M}-{0D}") do
|
||||
datetime
|
||||
else
|
||||
_ -> user.inserted_at
|
||||
end
|
||||
|
||||
patched_user = %{user | last_digest_emailed_at: last_digest_emailed_at}
|
||||
|
||||
_user = Pleroma.DigestEmailWorker.perform(patched_user)
|
||||
Mix.shell().info("Digest email have been sent to #{nickname} (#{user.email})")
|
||||
end
|
||||
end
|
|
@ -183,6 +183,7 @@ def run(["gen" | rest]) do
|
|||
)
|
||||
|
||||
secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
|
||||
jwt_secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
|
||||
signing_salt = :crypto.strong_rand_bytes(8) |> Base.encode64() |> binary_part(0, 8)
|
||||
{web_push_public_key, web_push_private_key} = :crypto.generate_key(:ecdh, :prime256v1)
|
||||
template_dir = Application.app_dir(:pleroma, "priv") <> "/templates"
|
||||
|
@ -200,6 +201,7 @@ def run(["gen" | rest]) do
|
|||
dbuser: dbuser,
|
||||
dbpass: dbpass,
|
||||
secret: secret,
|
||||
jwt_secret: jwt_secret,
|
||||
signing_salt: signing_salt,
|
||||
web_push_public_key: Base.url_encode64(web_push_public_key, padding: false),
|
||||
web_push_private_key: Base.url_encode64(web_push_private_key, padding: false),
|
||||
|
|
|
@ -224,6 +224,29 @@ def get_create_by_object_ap_id(ap_id) when is_binary(ap_id) do
|
|||
|
||||
def get_create_by_object_ap_id(_), do: nil
|
||||
|
||||
def create_by_object_ap_id_with_object(ap_ids) when is_list(ap_ids) do
|
||||
from(
|
||||
activity in Activity,
|
||||
where:
|
||||
fragment(
|
||||
"coalesce((?)->'object'->>'id', (?)->>'object') = ANY(?)",
|
||||
activity.data,
|
||||
activity.data,
|
||||
^ap_ids
|
||||
),
|
||||
where: fragment("(?)->>'type' = 'Create'", activity.data),
|
||||
inner_join: o in Object,
|
||||
on:
|
||||
fragment(
|
||||
"(?->>'id') = COALESCE(?->'object'->>'id', ?->>'object')",
|
||||
o.data,
|
||||
activity.data,
|
||||
activity.data
|
||||
),
|
||||
preload: [object: o]
|
||||
)
|
||||
end
|
||||
|
||||
def create_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do
|
||||
from(
|
||||
activity in Activity,
|
||||
|
@ -263,8 +286,8 @@ defp get_in_reply_to_activity_from_object(%Object{data: %{"inReplyTo" => ap_id}}
|
|||
|
||||
defp get_in_reply_to_activity_from_object(_), do: nil
|
||||
|
||||
def get_in_reply_to_activity(%Activity{data: %{"object" => object}}) do
|
||||
get_in_reply_to_activity_from_object(Object.normalize(object))
|
||||
def get_in_reply_to_activity(%Activity{} = activity) do
|
||||
get_in_reply_to_activity_from_object(Object.normalize(activity))
|
||||
end
|
||||
|
||||
def normalize(obj) when is_map(obj), do: get_by_ap_id_with_object(obj["id"])
|
||||
|
|
|
@ -162,7 +162,9 @@ def start(_type, _args) do
|
|||
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
|
||||
# for other strategies and supported options
|
||||
opts = [strategy: :one_for_one, name: Pleroma.Supervisor]
|
||||
Supervisor.start_link(children, opts)
|
||||
result = Supervisor.start_link(children, opts)
|
||||
:ok = after_supervisor_start()
|
||||
result
|
||||
end
|
||||
|
||||
defp setup_instrumenters do
|
||||
|
@ -227,4 +229,17 @@ defp hackney_pool_children do
|
|||
:hackney_pool.child_spec(pool, options)
|
||||
end
|
||||
end
|
||||
|
||||
defp after_supervisor_start do
|
||||
with digest_config <- Application.get_env(:pleroma, :email_notifications)[:digest],
|
||||
true <- digest_config[:active] do
|
||||
PleromaJobQueue.schedule(
|
||||
digest_config[:schedule],
|
||||
:digest_emails,
|
||||
Pleroma.DigestEmailWorker
|
||||
)
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
|
35
lib/pleroma/digest_email_worker.ex
Normal file
35
lib/pleroma/digest_email_worker.ex
Normal file
|
@ -0,0 +1,35 @@
|
|||
defmodule Pleroma.DigestEmailWorker do
|
||||
import Ecto.Query
|
||||
|
||||
@queue_name :digest_emails
|
||||
|
||||
def perform do
|
||||
config = Pleroma.Config.get([:email_notifications, :digest])
|
||||
negative_interval = -Map.fetch!(config, :interval)
|
||||
inactivity_threshold = Map.fetch!(config, :inactivity_threshold)
|
||||
inactive_users_query = Pleroma.User.list_inactive_users_query(inactivity_threshold)
|
||||
|
||||
now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
|
||||
|
||||
from(u in inactive_users_query,
|
||||
where: fragment(~s(? #> '{"email_notifications","digest"}' @> 'true'), u.info),
|
||||
where: u.last_digest_emailed_at < datetime_add(^now, ^negative_interval, "day"),
|
||||
select: u
|
||||
)
|
||||
|> Pleroma.Repo.all()
|
||||
|> Enum.each(&PleromaJobQueue.enqueue(@queue_name, __MODULE__, [&1]))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Send digest email to the given user.
|
||||
Updates `last_digest_emailed_at` field for the user and returns the updated user.
|
||||
"""
|
||||
@spec perform(Pleroma.User.t()) :: Pleroma.User.t()
|
||||
def perform(user) do
|
||||
with %Swoosh.Email{} = email <- Pleroma.Emails.UserEmail.digest_email(user) do
|
||||
Pleroma.Emails.Mailer.deliver_async(email)
|
||||
end
|
||||
|
||||
Pleroma.User.touch_last_digest_emailed_at(user)
|
||||
end
|
||||
end
|
|
@ -5,7 +5,7 @@
|
|||
defmodule Pleroma.Emails.UserEmail do
|
||||
@moduledoc "User emails"
|
||||
|
||||
import Swoosh.Email
|
||||
use Phoenix.Swoosh, view: Pleroma.Web.EmailView, layout: {Pleroma.Web.LayoutView, :email}
|
||||
|
||||
alias Pleroma.Web.Endpoint
|
||||
alias Pleroma.Web.Router
|
||||
|
@ -87,4 +87,73 @@ def account_confirmation_email(user) do
|
|||
|> subject("#{instance_name()} account confirmation")
|
||||
|> html_body(html_body)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Email used in digest email notifications
|
||||
Includes Mentions and New Followers data
|
||||
If there are no mentions (even when new followers exist), the function will return nil
|
||||
"""
|
||||
@spec digest_email(Pleroma.User.t()) :: Swoosh.Email.t() | nil
|
||||
def digest_email(user) do
|
||||
new_notifications =
|
||||
Pleroma.Notification.for_user_since(user, user.last_digest_emailed_at)
|
||||
|> Enum.reduce(%{followers: [], mentions: []}, fn
|
||||
%{activity: %{data: %{"type" => "Create"}, actor: actor} = activity} = notification,
|
||||
acc ->
|
||||
new_mention = %{
|
||||
data: notification,
|
||||
object: Pleroma.Object.normalize(activity),
|
||||
from: Pleroma.User.get_by_ap_id(actor)
|
||||
}
|
||||
|
||||
%{acc | mentions: [new_mention | acc.mentions]}
|
||||
|
||||
%{activity: %{data: %{"type" => "Follow"}, actor: actor} = activity} = notification,
|
||||
acc ->
|
||||
new_follower = %{
|
||||
data: notification,
|
||||
object: Pleroma.Object.normalize(activity),
|
||||
from: Pleroma.User.get_by_ap_id(actor)
|
||||
}
|
||||
|
||||
%{acc | followers: [new_follower | acc.followers]}
|
||||
|
||||
_, acc ->
|
||||
acc
|
||||
end)
|
||||
|
||||
with [_ | _] = mentions <- new_notifications.mentions do
|
||||
html_data = %{
|
||||
instance: instance_name(),
|
||||
user: user,
|
||||
mentions: mentions,
|
||||
followers: new_notifications.followers,
|
||||
unsubscribe_link: unsubscribe_url(user, "digest")
|
||||
}
|
||||
|
||||
new()
|
||||
|> to(recipient(user))
|
||||
|> from(sender())
|
||||
|> subject("Your digest from #{instance_name()}")
|
||||
|> render_body("digest.html", html_data)
|
||||
else
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Generate unsubscribe link for given user and notifications type.
|
||||
The link contains JWT token with the data, and subscription can be modified without
|
||||
authorization.
|
||||
"""
|
||||
@spec unsubscribe_url(Pleroma.User.t(), String.t()) :: String.t()
|
||||
def unsubscribe_url(user, notifications_type) do
|
||||
token =
|
||||
%{"sub" => user.id, "act" => %{"unsubscribe" => notifications_type}, "exp" => false}
|
||||
|> Pleroma.JWT.generate_and_sign!()
|
||||
|> Base.encode64()
|
||||
|
||||
Router.Helpers.subscription_url(Pleroma.Web.Endpoint, :unsubscribe, token)
|
||||
end
|
||||
end
|
||||
|
|
9
lib/pleroma/jwt.ex
Normal file
9
lib/pleroma/jwt.ex
Normal file
|
@ -0,0 +1,9 @@
|
|||
defmodule Pleroma.JWT do
|
||||
use Joken.Config
|
||||
|
||||
@impl true
|
||||
def token_config do
|
||||
default_claims(skip: [:aud])
|
||||
|> add_claim("aud", &Pleroma.Web.Endpoint.url/0, &(&1 == Pleroma.Web.Endpoint.url()))
|
||||
end
|
||||
end
|
|
@ -18,6 +18,8 @@ defmodule Pleroma.Notification do
|
|||
import Ecto.Query
|
||||
import Ecto.Changeset
|
||||
|
||||
@type t :: %__MODULE__{}
|
||||
|
||||
schema "notifications" do
|
||||
field(:seen, :boolean, default: false)
|
||||
belongs_to(:user, User, type: Pleroma.FlakeId)
|
||||
|
@ -31,7 +33,7 @@ def changeset(%Notification{} = notification, attrs) do
|
|||
|> cast(attrs, [:seen])
|
||||
end
|
||||
|
||||
def for_user_query(user, opts) do
|
||||
def for_user_query(user, opts \\ []) do
|
||||
query =
|
||||
Notification
|
||||
|> where(user_id: ^user.id)
|
||||
|
@ -75,6 +77,25 @@ def for_user(user, opts \\ %{}) do
|
|||
|> Pagination.fetch_paginated(opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns notifications for user received since given date.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Pleroma.Notification.for_user_since(%Pleroma.User{}, ~N[2019-04-13 11:22:33])
|
||||
[%Pleroma.Notification{}, %Pleroma.Notification{}]
|
||||
|
||||
iex> Pleroma.Notification.for_user_since(%Pleroma.User{}, ~N[2019-04-15 11:22:33])
|
||||
[]
|
||||
"""
|
||||
@spec for_user_since(Pleroma.User.t(), NaiveDateTime.t()) :: [t()]
|
||||
def for_user_since(user, date) do
|
||||
from(n in for_user_query(user),
|
||||
where: n.updated_at > ^date
|
||||
)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
def set_read_up_to(%{id: user_id} = _user, id) do
|
||||
query =
|
||||
from(
|
||||
|
@ -82,7 +103,10 @@ def set_read_up_to(%{id: user_id} = _user, id) do
|
|||
where: n.user_id == ^user_id,
|
||||
where: n.id <= ^id,
|
||||
update: [
|
||||
set: [seen: true]
|
||||
set: [
|
||||
seen: true,
|
||||
updated_at: ^NaiveDateTime.utc_now()
|
||||
]
|
||||
]
|
||||
)
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ def get_file(_) do
|
|||
|
||||
def put_file(upload) do
|
||||
{local_path, file} =
|
||||
case Enum.reverse(String.split(upload.path, "/", trim: true)) do
|
||||
case Enum.reverse(Path.split(upload.path)) do
|
||||
[file] ->
|
||||
{upload_path(), file}
|
||||
|
||||
|
@ -23,7 +23,7 @@ def put_file(upload) do
|
|||
|
||||
result_file = Path.join(local_path, file)
|
||||
|
||||
unless File.exists?(result_file) do
|
||||
if not File.exists?(result_file) do
|
||||
File.cp!(upload.tempfile, result_file)
|
||||
end
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Uploaders.MDII do
|
||||
@moduledoc "Represents uploader for https://github.com/hakaba-hitoyo/minimal-digital-image-infrastructure"
|
||||
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.HTTP
|
||||
|
||||
|
|
|
@ -6,10 +6,12 @@ defmodule Pleroma.Uploaders.S3 do
|
|||
@behaviour Pleroma.Uploaders.Uploader
|
||||
require Logger
|
||||
|
||||
alias Pleroma.Config
|
||||
|
||||
# The file name is re-encoded with S3's constraints here to comply with previous
|
||||
# links with less strict filenames
|
||||
def get_file(file) do
|
||||
config = Pleroma.Config.get([__MODULE__])
|
||||
config = Config.get([__MODULE__])
|
||||
bucket = Keyword.fetch!(config, :bucket)
|
||||
|
||||
bucket_with_namespace =
|
||||
|
@ -34,15 +36,15 @@ def get_file(file) do
|
|||
end
|
||||
|
||||
def put_file(%Pleroma.Upload{} = upload) do
|
||||
config = Pleroma.Config.get([__MODULE__])
|
||||
config = Config.get([__MODULE__])
|
||||
bucket = Keyword.get(config, :bucket)
|
||||
|
||||
{:ok, file_data} = File.read(upload.tempfile)
|
||||
|
||||
s3_name = strict_encode(upload.path)
|
||||
|
||||
op =
|
||||
ExAws.S3.put_object(bucket, s3_name, file_data, [
|
||||
upload.tempfile
|
||||
|> ExAws.S3.Upload.stream_file()
|
||||
|> ExAws.S3.upload(bucket, s3_name, [
|
||||
{:acl, :public_read},
|
||||
{:content_type, upload.content_type}
|
||||
])
|
||||
|
|
|
@ -57,6 +57,7 @@ defmodule Pleroma.User do
|
|||
field(:search_type, :integer, virtual: true)
|
||||
field(:tags, {:array, :string}, default: [])
|
||||
field(:last_refreshed_at, :naive_datetime_usec)
|
||||
field(:last_digest_emailed_at, :naive_datetime)
|
||||
has_many(:notifications, Notification)
|
||||
has_many(:registrations, Registration)
|
||||
embeds_one(:info, User.Info)
|
||||
|
@ -151,10 +152,10 @@ def following_count(%User{} = user) do
|
|||
end
|
||||
|
||||
def remote_user_creation(params) do
|
||||
params =
|
||||
params
|
||||
|> Map.put(:info, params[:info] || %{})
|
||||
bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
|
||||
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
|
||||
|
||||
params = Map.put(params, :info, params[:info] || %{})
|
||||
info_cng = User.Info.remote_user_creation(%User.Info{}, params[:info])
|
||||
|
||||
changes =
|
||||
|
@ -163,8 +164,8 @@ def remote_user_creation(params) do
|
|||
|> validate_required([:name, :ap_id])
|
||||
|> unique_constraint(:nickname)
|
||||
|> validate_format(:nickname, @email_regex)
|
||||
|> validate_length(:bio, max: 5000)
|
||||
|> validate_length(:name, max: 100)
|
||||
|> validate_length(:bio, max: bio_limit)
|
||||
|> validate_length(:name, max: name_limit)
|
||||
|> put_change(:local, false)
|
||||
|> put_embed(:info, info_cng)
|
||||
|
||||
|
@ -187,22 +188,23 @@ def remote_user_creation(params) do
|
|||
end
|
||||
|
||||
def update_changeset(struct, params \\ %{}) do
|
||||
bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
|
||||
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
|
||||
|
||||
struct
|
||||
|> cast(params, [:bio, :name, :avatar, :following])
|
||||
|> unique_constraint(:nickname)
|
||||
|> validate_format(:nickname, local_nickname_regex())
|
||||
|> validate_length(:bio, max: 5000)
|
||||
|> validate_length(:name, min: 1, max: 100)
|
||||
|> validate_length(:bio, max: bio_limit)
|
||||
|> validate_length(:name, min: 1, max: name_limit)
|
||||
end
|
||||
|
||||
def upgrade_changeset(struct, params \\ %{}) do
|
||||
params =
|
||||
params
|
||||
|> Map.put(:last_refreshed_at, NaiveDateTime.utc_now())
|
||||
bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
|
||||
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
|
||||
|
||||
info_cng =
|
||||
struct.info
|
||||
|> User.Info.user_upgrade(params[:info])
|
||||
params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
|
||||
info_cng = User.Info.user_upgrade(struct.info, params[:info])
|
||||
|
||||
struct
|
||||
|> cast(params, [
|
||||
|
@ -215,8 +217,8 @@ def upgrade_changeset(struct, params \\ %{}) do
|
|||
])
|
||||
|> unique_constraint(:nickname)
|
||||
|> validate_format(:nickname, local_nickname_regex())
|
||||
|> validate_length(:bio, max: 5000)
|
||||
|> validate_length(:name, max: 100)
|
||||
|> validate_length(:bio, max: bio_limit)
|
||||
|> validate_length(:name, max: name_limit)
|
||||
|> put_embed(:info, info_cng)
|
||||
end
|
||||
|
||||
|
@ -243,6 +245,9 @@ def reset_password(%User{id: user_id} = user, data) do
|
|||
end
|
||||
|
||||
def register_changeset(struct, params \\ %{}, opts \\ []) do
|
||||
bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
|
||||
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
|
||||
|
||||
need_confirmation? =
|
||||
if is_nil(opts[:need_confirmation]) do
|
||||
Pleroma.Config.get([:instance, :account_activation_required])
|
||||
|
@ -263,8 +268,8 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
|
|||
|> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
|
||||
|> validate_format(:nickname, local_nickname_regex())
|
||||
|> validate_format(:email, @email_regex)
|
||||
|> validate_length(:bio, max: 1000)
|
||||
|> validate_length(:name, min: 1, max: 100)
|
||||
|> validate_length(:bio, max: bio_limit)
|
||||
|> validate_length(:name, min: 1, max: name_limit)
|
||||
|> put_change(:info, info_change)
|
||||
|
||||
changeset =
|
||||
|
@ -1426,6 +1431,80 @@ def showing_reblogs?(%User{} = user, %User{} = target) do
|
|||
target.ap_id not in user.info.muted_reblogs
|
||||
end
|
||||
|
||||
@doc """
|
||||
The function returns a query to get users with no activity for given interval of days.
|
||||
Inactive users are those who didn't read any notification, or had any activity where
|
||||
the user is the activity's actor, during `inactivity_threshold` days.
|
||||
Deactivated users will not appear in this list.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Pleroma.User.list_inactive_users()
|
||||
%Ecto.Query{}
|
||||
"""
|
||||
@spec list_inactive_users_query(integer()) :: Ecto.Query.t()
|
||||
def list_inactive_users_query(inactivity_threshold \\ 7) do
|
||||
negative_inactivity_threshold = -inactivity_threshold
|
||||
now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
|
||||
# Subqueries are not supported in `where` clauses, join gets too complicated.
|
||||
has_read_notifications =
|
||||
from(n in Pleroma.Notification,
|
||||
where: n.seen == true,
|
||||
group_by: n.id,
|
||||
having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
|
||||
select: n.user_id
|
||||
)
|
||||
|> Pleroma.Repo.all()
|
||||
|
||||
from(u in Pleroma.User,
|
||||
left_join: a in Pleroma.Activity,
|
||||
on: u.ap_id == a.actor,
|
||||
where: not is_nil(u.nickname),
|
||||
where: fragment("not (?->'deactivated' @> 'true')", u.info),
|
||||
where: u.id not in ^has_read_notifications,
|
||||
group_by: u.id,
|
||||
having:
|
||||
max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
|
||||
is_nil(max(a.inserted_at))
|
||||
)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Enable or disable email notifications for user
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Pleroma.User.switch_email_notifications(Pleroma.User{info: %{email_notifications: %{"digest" => false}}}, "digest", true)
|
||||
Pleroma.User{info: %{email_notifications: %{"digest" => true}}}
|
||||
|
||||
iex> Pleroma.User.switch_email_notifications(Pleroma.User{info: %{email_notifications: %{"digest" => true}}}, "digest", false)
|
||||
Pleroma.User{info: %{email_notifications: %{"digest" => false}}}
|
||||
"""
|
||||
@spec switch_email_notifications(t(), String.t(), boolean()) ::
|
||||
{:ok, t()} | {:error, Ecto.Changeset.t()}
|
||||
def switch_email_notifications(user, type, status) do
|
||||
info = Pleroma.User.Info.update_email_notifications(user.info, %{type => status})
|
||||
|
||||
change(user)
|
||||
|> put_embed(:info, info)
|
||||
|> update_and_set_cache()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Set `last_digest_emailed_at` value for the user to current time
|
||||
"""
|
||||
@spec touch_last_digest_emailed_at(t()) :: t()
|
||||
def touch_last_digest_emailed_at(user) do
|
||||
now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
|
||||
|
||||
{:ok, updated_user} =
|
||||
user
|
||||
|> change(%{last_digest_emailed_at: now})
|
||||
|> update_and_set_cache()
|
||||
|
||||
updated_user
|
||||
end
|
||||
|
||||
@spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
|
||||
def toggle_confirmation(%User{} = user) do
|
||||
need_confirmation? = !user.info.confirmation_pending
|
||||
|
|
|
@ -45,6 +45,7 @@ defmodule Pleroma.User.Info do
|
|||
field(:hide_follows, :boolean, default: false)
|
||||
field(:hide_favorites, :boolean, default: true)
|
||||
field(:pinned_activities, {:array, :string}, default: [])
|
||||
field(:email_notifications, :map, default: %{"digest" => false})
|
||||
field(:mascot, :map, default: nil)
|
||||
field(:emoji, {:array, :map}, default: [])
|
||||
field(:pleroma_settings_store, :map, default: %{})
|
||||
|
@ -95,6 +96,30 @@ def update_notification_settings(info, settings) do
|
|||
|> validate_required([:notification_settings])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Update email notifications in the given User.Info struct.
|
||||
|
||||
Examples:
|
||||
|
||||
iex> update_email_notifications(%Pleroma.User.Info{email_notifications: %{"digest" => false}}, %{"digest" => true})
|
||||
%Pleroma.User.Info{email_notifications: %{"digest" => true}}
|
||||
|
||||
"""
|
||||
@spec update_email_notifications(t(), map()) :: Ecto.Changeset.t()
|
||||
def update_email_notifications(info, settings) do
|
||||
email_notifications =
|
||||
info.email_notifications
|
||||
|> Map.merge(settings)
|
||||
|> Map.take(["digest"])
|
||||
|
||||
params = %{email_notifications: email_notifications}
|
||||
fields = [:email_notifications]
|
||||
|
||||
info
|
||||
|> cast(params, fields)
|
||||
|> validate_required(fields)
|
||||
end
|
||||
|
||||
def add_to_note_count(info, number) do
|
||||
set_note_count(info, info.note_count + number)
|
||||
end
|
||||
|
|
|
@ -518,6 +518,8 @@ defp fetch_activities_for_context_query(context, opts) do
|
|||
|
||||
from(activity in Activity)
|
||||
|> maybe_preload_objects(opts)
|
||||
|> maybe_preload_bookmarks(opts)
|
||||
|> maybe_set_thread_muted_field(opts)
|
||||
|> restrict_blocked(opts)
|
||||
|> restrict_recipients(recipients, opts["user"])
|
||||
|> where(
|
||||
|
@ -531,6 +533,7 @@ defp fetch_activities_for_context_query(context, opts) do
|
|||
)
|
||||
)
|
||||
|> exclude_poll_votes(opts)
|
||||
|> exclude_id(opts)
|
||||
|> order_by([activity], desc: activity.id)
|
||||
end
|
||||
|
||||
|
@ -623,6 +626,7 @@ def fetch_user_activities(user, reading_user, params \\ %{}) do
|
|||
params =
|
||||
params
|
||||
|> Map.put("type", ["Create", "Announce"])
|
||||
|> Map.put("user", reading_user)
|
||||
|> Map.put("actor_id", user.ap_id)
|
||||
|> Map.put("whole_db", true)
|
||||
|> Map.put("pinned_activity_ids", user.info.pinned_activities)
|
||||
|
@ -870,6 +874,12 @@ defp exclude_poll_votes(query, _) do
|
|||
end
|
||||
end
|
||||
|
||||
defp exclude_id(query, %{"exclude_id" => id}) when is_binary(id) do
|
||||
from(activity in query, where: activity.id != ^id)
|
||||
end
|
||||
|
||||
defp exclude_id(query, _), do: query
|
||||
|
||||
defp maybe_preload_objects(query, %{"skip_preload" => true}), do: query
|
||||
|
||||
defp maybe_preload_objects(query, _) do
|
||||
|
|
|
@ -28,11 +28,43 @@ defp get_policies(_), do: []
|
|||
|
||||
@spec subdomains_regex([String.t()]) :: [Regex.t()]
|
||||
def subdomains_regex(domains) when is_list(domains) do
|
||||
for domain <- domains, do: ~r(^#{String.replace(domain, "*.", "(.*\\.)*")}$)
|
||||
for domain <- domains, do: ~r(^#{String.replace(domain, "*.", "(.*\\.)*")}$)i
|
||||
end
|
||||
|
||||
@spec subdomain_match?([Regex.t()], String.t()) :: boolean()
|
||||
def subdomain_match?(domains, host) do
|
||||
Enum.any?(domains, fn domain -> Regex.match?(domain, host) end)
|
||||
end
|
||||
|
||||
@callback describe() :: {:ok | :error, Map.t()}
|
||||
|
||||
def describe(policies) do
|
||||
{:ok, policy_configs} =
|
||||
policies
|
||||
|> Enum.reduce({:ok, %{}}, fn
|
||||
policy, {:ok, data} ->
|
||||
{:ok, policy_data} = policy.describe()
|
||||
{:ok, Map.merge(data, policy_data)}
|
||||
|
||||
_, error ->
|
||||
error
|
||||
end)
|
||||
|
||||
mrf_policies =
|
||||
get_policies()
|
||||
|> Enum.map(fn policy -> to_string(policy) |> String.split(".") |> List.last() end)
|
||||
|
||||
exclusions = Pleroma.Config.get([:instance, :mrf_transparency_exclusions])
|
||||
|
||||
base =
|
||||
%{
|
||||
mrf_policies: mrf_policies,
|
||||
exclusions: length(exclusions) > 0
|
||||
}
|
||||
|> Map.merge(policy_configs)
|
||||
|
||||
{:ok, base}
|
||||
end
|
||||
|
||||
def describe, do: get_policies() |> describe()
|
||||
end
|
||||
|
|
|
@ -62,4 +62,7 @@ def filter(%{"type" => "Follow", "actor" => actor_id} = message) do
|
|||
|
||||
@impl true
|
||||
def filter(message), do: {:ok, message}
|
||||
|
||||
@impl true
|
||||
def describe, do: {:ok, %{}}
|
||||
end
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do
|
||||
alias Pleroma.User
|
||||
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||
|
||||
require Logger
|
||||
|
||||
# has the user successfully posted before?
|
||||
|
@ -22,6 +24,7 @@ defp contains_links?(%{"content" => content} = _object) do
|
|||
|
||||
defp contains_links?(_), do: false
|
||||
|
||||
@impl true
|
||||
def filter(%{"type" => "Create", "actor" => actor, "object" => object} = message) do
|
||||
with {:ok, %User{} = u} <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:contains_links, true} <- {:contains_links, contains_links?(object)},
|
||||
|
@ -45,4 +48,7 @@ def filter(%{"type" => "Create", "actor" => actor, "object" => object} = message
|
|||
|
||||
# in all other cases, pass through
|
||||
def filter(message), do: {:ok, message}
|
||||
|
||||
@impl true
|
||||
def describe, do: {:ok, %{}}
|
||||
end
|
||||
|
|
|
@ -12,4 +12,7 @@ def filter(object) do
|
|||
Logger.info("REJECTING #{inspect(object)}")
|
||||
{:reject, object}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def describe, do: {:ok, %{}}
|
||||
end
|
||||
|
|
|
@ -39,4 +39,6 @@ def filter(%{"type" => "Create", "object" => child_object} = object) do
|
|||
end
|
||||
|
||||
def filter(object), do: {:ok, object}
|
||||
|
||||
def describe, do: {:ok, %{}}
|
||||
end
|
||||
|
|
|
@ -90,4 +90,7 @@ def filter(%{"type" => "Create"} = message) do
|
|||
|
||||
@impl true
|
||||
def filter(message), do: {:ok, message}
|
||||
|
||||
@impl true
|
||||
def describe, do: {:ok, %{mrf_hellthread: Pleroma.Config.get([:mrf_hellthread])}}
|
||||
end
|
||||
|
|
|
@ -96,4 +96,36 @@ def filter(%{"type" => "Create", "object" => %{"content" => _content}} = message
|
|||
|
||||
@impl true
|
||||
def filter(message), do: {:ok, message}
|
||||
|
||||
@impl true
|
||||
def describe do
|
||||
# This horror is needed to convert regex sigils to strings
|
||||
mrf_keyword =
|
||||
Pleroma.Config.get(:mrf_keyword, [])
|
||||
|> Enum.map(fn {key, value} ->
|
||||
{key,
|
||||
Enum.map(value, fn
|
||||
{pattern, replacement} ->
|
||||
%{
|
||||
"pattern" =>
|
||||
if not is_binary(pattern) do
|
||||
inspect(pattern)
|
||||
else
|
||||
pattern
|
||||
end,
|
||||
"replacement" => replacement
|
||||
}
|
||||
|
||||
pattern ->
|
||||
if not is_binary(pattern) do
|
||||
inspect(pattern)
|
||||
else
|
||||
pattern
|
||||
end
|
||||
end)}
|
||||
end)
|
||||
|> Enum.into(%{})
|
||||
|
||||
{:ok, %{mrf_keyword: mrf_keyword}}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -53,4 +53,7 @@ def filter(
|
|||
|
||||
@impl true
|
||||
def filter(message), do: {:ok, message}
|
||||
|
||||
@impl true
|
||||
def describe, do: {:ok, %{}}
|
||||
end
|
||||
|
|
|
@ -21,4 +21,7 @@ def filter(%{"type" => "Create"} = message) do
|
|||
|
||||
@impl true
|
||||
def filter(message), do: {:ok, message}
|
||||
|
||||
@impl true
|
||||
def describe, do: {:ok, %{}}
|
||||
end
|
||||
|
|
|
@ -19,4 +19,7 @@ def filter(
|
|||
|
||||
@impl true
|
||||
def filter(object), do: {:ok, object}
|
||||
|
||||
@impl true
|
||||
def describe, do: {:ok, %{}}
|
||||
end
|
||||
|
|
|
@ -10,4 +10,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoOpPolicy do
|
|||
def filter(object) do
|
||||
{:ok, object}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def describe, do: {:ok, %{}}
|
||||
end
|
||||
|
|
|
@ -21,4 +21,6 @@ def filter(%{"type" => "Create", "object" => child_object} = object) do
|
|||
end
|
||||
|
||||
def filter(object), do: {:ok, object}
|
||||
|
||||
def describe, do: {:ok, %{}}
|
||||
end
|
||||
|
|
|
@ -44,4 +44,7 @@ def filter(%{"type" => "Create"} = object) do
|
|||
|
||||
@impl true
|
||||
def filter(object), do: {:ok, object}
|
||||
|
||||
@impl true
|
||||
def describe, do: {:ok, %{mrf_rejectnonpublic: Pleroma.Config.get([:mrf_rejectnonpublic])}}
|
||||
end
|
||||
|
|
|
@ -177,4 +177,16 @@ def filter(%{"id" => actor, "type" => obj_type} = object)
|
|||
end
|
||||
|
||||
def filter(object), do: {:ok, object}
|
||||
|
||||
@impl true
|
||||
def describe do
|
||||
exclusions = Pleroma.Config.get([:instance, :mrf_transparency_exclusions])
|
||||
|
||||
mrf_simple =
|
||||
Pleroma.Config.get(:mrf_simple)
|
||||
|> Enum.map(fn {k, v} -> {k, Enum.reject(v, fn v -> v in exclusions end)} end)
|
||||
|> Enum.into(%{})
|
||||
|
||||
{:ok, %{mrf_simple: mrf_simple}}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -37,4 +37,7 @@ def filter(%{"actor" => actor} = message) do
|
|||
|
||||
@impl true
|
||||
def filter(message), do: {:ok, message}
|
||||
|
||||
@impl true
|
||||
def describe, do: {:ok, %{}}
|
||||
end
|
||||
|
|
|
@ -165,4 +165,7 @@ def filter(%{"actor" => actor, "type" => "Create"} = message),
|
|||
|
||||
@impl true
|
||||
def filter(message), do: {:ok, message}
|
||||
|
||||
@impl true
|
||||
def describe, do: {:ok, %{}}
|
||||
end
|
||||
|
|
|
@ -32,4 +32,13 @@ def filter(%{"actor" => actor} = object) do
|
|||
end
|
||||
|
||||
def filter(object), do: {:ok, object}
|
||||
|
||||
@impl true
|
||||
def describe do
|
||||
mrf_user_allowlist =
|
||||
Config.get([:mrf_user_allowlist], [])
|
||||
|> Enum.into(%{}, fn {k, v} -> {k, length(v)} end)
|
||||
|
||||
{:ok, %{mrf_user_allowlist: mrf_user_allowlist}}
|
||||
end
|
||||
end
|
||||
|
|
36
lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex
Normal file
36
lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex
Normal file
|
@ -0,0 +1,36 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do
|
||||
@moduledoc "Filter messages which belong to certain activity vocabularies"
|
||||
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||
|
||||
def filter(%{"type" => "Undo", "object" => child_message} = message) do
|
||||
with {:ok, _} <- filter(child_message) do
|
||||
{:ok, message}
|
||||
else
|
||||
{:reject, nil} ->
|
||||
{:reject, nil}
|
||||
end
|
||||
end
|
||||
|
||||
def filter(%{"type" => message_type} = message) do
|
||||
with accepted_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :accept]),
|
||||
rejected_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :reject]),
|
||||
true <-
|
||||
length(accepted_vocabulary) == 0 || Enum.member?(accepted_vocabulary, message_type),
|
||||
false <-
|
||||
length(rejected_vocabulary) > 0 && Enum.member?(rejected_vocabulary, message_type),
|
||||
{:ok, _} <- filter(message["object"]) do
|
||||
{:ok, message}
|
||||
else
|
||||
_ -> {:reject, nil}
|
||||
end
|
||||
end
|
||||
|
||||
def filter(message), do: {:ok, message}
|
||||
|
||||
def describe, do: {:ok, %{mrf_vocabulary: Pleroma.Config.get(:mrf_vocabulary)}}
|
||||
end
|
|
@ -14,6 +14,7 @@ def get_actor do
|
|||
|> User.get_or_create_service_actor_by_ap_id()
|
||||
end
|
||||
|
||||
@spec follow(String.t()) :: {:ok, Activity.t()} | {:error, any()}
|
||||
def follow(target_instance) do
|
||||
with %User{} = local_user <- get_actor(),
|
||||
{:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_instance),
|
||||
|
@ -21,12 +22,17 @@ def follow(target_instance) do
|
|||
Logger.info("relay: followed instance: #{target_instance}; id=#{activity.data["id"]}")
|
||||
{:ok, activity}
|
||||
else
|
||||
{:error, _} = error ->
|
||||
Logger.error("error: #{inspect(error)}")
|
||||
error
|
||||
|
||||
e ->
|
||||
Logger.error("error: #{inspect(e)}")
|
||||
{:error, e}
|
||||
end
|
||||
end
|
||||
|
||||
@spec unfollow(String.t()) :: {:ok, Activity.t()} | {:error, any()}
|
||||
def unfollow(target_instance) do
|
||||
with %User{} = local_user <- get_actor(),
|
||||
{:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_instance),
|
||||
|
@ -34,20 +40,27 @@ def unfollow(target_instance) do
|
|||
Logger.info("relay: unfollowed instance: #{target_instance}: id=#{activity.data["id"]}")
|
||||
{:ok, activity}
|
||||
else
|
||||
{:error, _} = error ->
|
||||
Logger.error("error: #{inspect(error)}")
|
||||
error
|
||||
|
||||
e ->
|
||||
Logger.error("error: #{inspect(e)}")
|
||||
{:error, e}
|
||||
end
|
||||
end
|
||||
|
||||
@spec publish(any()) :: {:ok, Activity.t(), Object.t()} | {:error, any()}
|
||||
def publish(%Activity{data: %{"type" => "Create"}} = activity) do
|
||||
with %User{} = user <- get_actor(),
|
||||
%Object{} = object <- Object.normalize(activity) do
|
||||
ActivityPub.announce(user, object, nil, true, false)
|
||||
else
|
||||
e -> Logger.error("error: #{inspect(e)}")
|
||||
e ->
|
||||
Logger.error("error: #{inspect(e)}")
|
||||
{:error, inspect(e)}
|
||||
end
|
||||
end
|
||||
|
||||
def publish(_), do: nil
|
||||
def publish(_), do: {:error, "Not implemented"}
|
||||
end
|
||||
|
|
|
@ -26,6 +26,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
"""
|
||||
def fix_object(object, options \\ []) do
|
||||
object
|
||||
|> strip_internal_fields
|
||||
|> fix_actor
|
||||
|> fix_url
|
||||
|> fix_attachments
|
||||
|
@ -34,7 +35,6 @@ def fix_object(object, options \\ []) do
|
|||
|> fix_emoji
|
||||
|> fix_tag
|
||||
|> fix_content_map
|
||||
|> fix_likes
|
||||
|> fix_addressing
|
||||
|> fix_summary
|
||||
|> fix_type(options)
|
||||
|
@ -151,20 +151,6 @@ def fix_actor(%{"attributedTo" => actor} = object) do
|
|||
|> Map.put("actor", Containment.get_actor(%{"actor" => actor}))
|
||||
end
|
||||
|
||||
# Check for standardisation
|
||||
# This is what Peertube does
|
||||
# curl -H 'Accept: application/activity+json' $likes | jq .totalItems
|
||||
# Prismo returns only an integer (count) as "likes"
|
||||
def fix_likes(%{"likes" => likes} = object) when not is_map(likes) do
|
||||
object
|
||||
|> Map.put("likes", [])
|
||||
|> Map.put("like_count", 0)
|
||||
end
|
||||
|
||||
def fix_likes(object) do
|
||||
object
|
||||
end
|
||||
|
||||
def fix_in_reply_to(object, options \\ [])
|
||||
|
||||
def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options)
|
||||
|
@ -347,13 +333,15 @@ def fix_content_map(object), do: object
|
|||
|
||||
def fix_type(object, options \\ [])
|
||||
|
||||
def fix_type(%{"inReplyTo" => reply_id} = object, options) when is_binary(reply_id) do
|
||||
def fix_type(%{"inReplyTo" => reply_id, "name" => _} = object, options)
|
||||
when is_binary(reply_id) do
|
||||
reply =
|
||||
if Federator.allowed_incoming_reply_depth?(options[:depth]) do
|
||||
Object.normalize(reply_id, true)
|
||||
with true <- Federator.allowed_incoming_reply_depth?(options[:depth]),
|
||||
{:ok, object} <- get_obj_helper(reply_id, options) do
|
||||
object
|
||||
end
|
||||
|
||||
if reply && (reply.data["type"] == "Question" and object["name"]) do
|
||||
if reply && reply.data["type"] == "Question" do
|
||||
Map.put(object, "type", "Answer")
|
||||
else
|
||||
object
|
||||
|
@ -713,8 +701,7 @@ def handle_incoming(
|
|||
} = _data,
|
||||
_options
|
||||
) do
|
||||
with true <- Pleroma.Config.get([:activitypub, :accept_blocks]),
|
||||
%User{local: true} = blocked <- User.get_cached_by_ap_id(blocked),
|
||||
with %User{local: true} = blocked <- User.get_cached_by_ap_id(blocked),
|
||||
{:ok, %User{} = blocker} <- User.get_or_fetch_by_ap_id(blocker),
|
||||
{:ok, activity} <- ActivityPub.unblock(blocker, blocked, id, false) do
|
||||
User.unblock(blocker, blocked)
|
||||
|
@ -728,8 +715,7 @@ def handle_incoming(
|
|||
%{"type" => "Block", "object" => blocked, "actor" => blocker, "id" => id} = _data,
|
||||
_options
|
||||
) do
|
||||
with true <- Pleroma.Config.get([:activitypub, :accept_blocks]),
|
||||
%User{local: true} = blocked = User.get_cached_by_ap_id(blocked),
|
||||
with %User{local: true} = blocked = User.get_cached_by_ap_id(blocked),
|
||||
{:ok, %User{} = blocker} = User.get_or_fetch_by_ap_id(blocker),
|
||||
{:ok, activity} <- ActivityPub.block(blocker, blocked, id, false) do
|
||||
User.unfollow(blocker, blocked)
|
||||
|
@ -784,7 +770,6 @@ def prepare_object(object) do
|
|||
|> add_mention_tags
|
||||
|> add_emoji_tags
|
||||
|> add_attributed_to
|
||||
|> add_likes
|
||||
|> prepare_attachments
|
||||
|> set_conversation
|
||||
|> set_reply_to_uri
|
||||
|
@ -971,22 +956,6 @@ def add_attributed_to(object) do
|
|||
|> Map.put("attributedTo", attributed_to)
|
||||
end
|
||||
|
||||
def add_likes(%{"id" => id, "like_count" => likes} = object) do
|
||||
likes = %{
|
||||
"id" => "#{id}/likes",
|
||||
"first" => "#{id}/likes?page=1",
|
||||
"type" => "OrderedCollection",
|
||||
"totalItems" => likes
|
||||
}
|
||||
|
||||
object
|
||||
|> Map.put("likes", likes)
|
||||
end
|
||||
|
||||
def add_likes(object) do
|
||||
object
|
||||
end
|
||||
|
||||
def prepare_attachments(object) do
|
||||
attachments =
|
||||
(object["attachment"] || [])
|
||||
|
@ -1002,6 +971,7 @@ def prepare_attachments(object) do
|
|||
defp strip_internal_fields(object) do
|
||||
object
|
||||
|> Map.drop([
|
||||
"likes",
|
||||
"like_count",
|
||||
"announcements",
|
||||
"announcement_count",
|
||||
|
|
20
lib/pleroma/web/mailer/subscription_controller.ex
Normal file
20
lib/pleroma/web/mailer/subscription_controller.ex
Normal file
|
@ -0,0 +1,20 @@
|
|||
defmodule Pleroma.Web.Mailer.SubscriptionController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
alias Pleroma.JWT
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
||||
def unsubscribe(conn, %{"token" => encoded_token}) do
|
||||
with {:ok, token} <- Base.decode64(encoded_token),
|
||||
{:ok, claims} <- JWT.verify_and_validate(token),
|
||||
%{"act" => %{"unsubscribe" => type}, "sub" => uid} <- claims,
|
||||
%User{} = user <- Repo.get(User, uid),
|
||||
{:ok, _user} <- User.switch_email_notifications(user, type, false) do
|
||||
render(conn, "unsubscribe_success.html", email: user.email)
|
||||
else
|
||||
_err ->
|
||||
render(conn, "unsubscribe_failure.html")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -13,10 +13,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
|
|||
alias Pleroma.User
|
||||
alias Pleroma.Web.CommonAPI
|
||||
|
||||
@spec follow(User.t(), User.t(), map) :: {:ok, User.t()} | {:error, String.t()}
|
||||
def follow(follower, followed, params \\ %{}) do
|
||||
options = cast_params(params)
|
||||
reblogs = options[:reblogs]
|
||||
|
||||
result =
|
||||
if not User.following?(follower, followed) do
|
||||
CommonAPI.follow(follower, followed)
|
||||
|
@ -24,19 +22,25 @@ def follow(follower, followed, params \\ %{}) do
|
|||
{:ok, follower, followed, nil}
|
||||
end
|
||||
|
||||
with {:ok, follower, followed, _} <- result do
|
||||
reblogs
|
||||
|> case do
|
||||
false -> CommonAPI.hide_reblogs(follower, followed)
|
||||
_ -> CommonAPI.show_reblogs(follower, followed)
|
||||
end
|
||||
|> case do
|
||||
with {:ok, follower, _followed, _} <- result do
|
||||
options = cast_params(params)
|
||||
|
||||
case reblogs_visibility(options[:reblogs], result) do
|
||||
{:ok, follower} -> {:ok, follower}
|
||||
_ -> {:ok, follower}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp reblogs_visibility(false, {:ok, follower, followed, _}) do
|
||||
CommonAPI.hide_reblogs(follower, followed)
|
||||
end
|
||||
|
||||
defp reblogs_visibility(_, {:ok, follower, followed, _}) do
|
||||
CommonAPI.show_reblogs(follower, followed)
|
||||
end
|
||||
|
||||
@spec get_followers(User.t(), map()) :: list(User.t())
|
||||
def get_followers(user, params \\ %{}) do
|
||||
user
|
||||
|> User.get_followers_query()
|
||||
|
|
|
@ -371,6 +371,7 @@ def public_timeline(%{assigns: %{user: user}} = conn, params) do
|
|||
|> Map.put("local_only", local_only)
|
||||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("muting_user", user)
|
||||
|> Map.put("user", user)
|
||||
|> ActivityPub.fetch_public_activities()
|
||||
|> Enum.reverse()
|
||||
|
||||
|
@ -432,12 +433,9 @@ def get_context(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
|||
activities <-
|
||||
ActivityPub.fetch_activities_for_context(activity.data["context"], %{
|
||||
"blocking_user" => user,
|
||||
"user" => user
|
||||
"user" => user,
|
||||
"exclude_id" => activity.id
|
||||
}),
|
||||
activities <-
|
||||
activities |> Enum.filter(fn %{id: aid} -> to_string(aid) != to_string(id) end),
|
||||
activities <-
|
||||
activities |> Enum.filter(fn %{data: %{"type" => type}} -> type == "Create" end),
|
||||
grouped_activities <- Enum.group_by(activities, fn %{id: id} -> id < activity.id end) do
|
||||
result = %{
|
||||
ancestors:
|
||||
|
@ -472,8 +470,8 @@ def get_poll(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
|||
|> put_view(StatusView)
|
||||
|> try_render("poll.json", %{object: object, for: user})
|
||||
else
|
||||
nil -> render_error(conn, :not_found, "Record not found")
|
||||
false -> render_error(conn, :not_found, "Record not found")
|
||||
error when is_nil(error) or error == false ->
|
||||
render_error(conn, :not_found, "Record not found")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -821,8 +819,8 @@ def get_mascot(%{assigns: %{user: user}} = conn, _params) do
|
|||
end
|
||||
|
||||
def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
with %Activity{data: %{"object" => object}} <- Activity.get_by_id(id),
|
||||
%Object{data: %{"likes" => likes}} <- Object.normalize(object) do
|
||||
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
||||
%Object{data: %{"likes" => likes}} <- Object.normalize(activity) do
|
||||
q = from(u in User, where: u.ap_id in ^likes)
|
||||
|
||||
users =
|
||||
|
@ -838,8 +836,8 @@ def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
|||
end
|
||||
|
||||
def reblogged_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
with %Activity{data: %{"object" => object}} <- Activity.get_by_id(id),
|
||||
%Object{data: %{"announcements" => announces}} <- Object.normalize(object) do
|
||||
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
||||
%Object{data: %{"announcements" => announces}} <- Object.normalize(activity) do
|
||||
q = from(u in User, where: u.ap_id in ^announces)
|
||||
|
||||
users =
|
||||
|
@ -880,6 +878,7 @@ def hashtag_timeline(%{assigns: %{user: user}} = conn, params) do
|
|||
|> Map.put("local_only", local_only)
|
||||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("muting_user", user)
|
||||
|> Map.put("user", user)
|
||||
|> Map.put("tag", tags)
|
||||
|> Map.put("tag_all", tag_all)
|
||||
|> Map.put("tag_reject", tag_reject)
|
||||
|
@ -1286,6 +1285,7 @@ def list_timeline(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params)
|
|||
params
|
||||
|> Map.put("type", "Create")
|
||||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("user", user)
|
||||
|> Map.put("muting_user", user)
|
||||
|
||||
# we must filter the following list for the user to avoid leaking statuses the user
|
||||
|
@ -1626,45 +1626,35 @@ def suggestions(%{assigns: %{user: user}} = conn, _) do
|
|||
|> String.replace("{{user}}", user)
|
||||
|
||||
with {:ok, %{status: 200, body: body}} <-
|
||||
HTTP.get(
|
||||
url,
|
||||
[],
|
||||
adapter: [
|
||||
recv_timeout: timeout,
|
||||
pool: :default
|
||||
]
|
||||
),
|
||||
HTTP.get(url, [], adapter: [recv_timeout: timeout, pool: :default]),
|
||||
{:ok, data} <- Jason.decode(body) do
|
||||
data =
|
||||
data
|
||||
|> Enum.slice(0, limit)
|
||||
|> Enum.map(fn x ->
|
||||
Map.put(
|
||||
x,
|
||||
"id",
|
||||
case User.get_or_fetch(x["acct"]) do
|
||||
{:ok, %User{id: id}} -> id
|
||||
_ -> 0
|
||||
end
|
||||
)
|
||||
end)
|
||||
|> Enum.map(fn x ->
|
||||
Map.put(x, "avatar", MediaProxy.url(x["avatar"]))
|
||||
end)
|
||||
|> Enum.map(fn x ->
|
||||
Map.put(x, "avatar_static", MediaProxy.url(x["avatar_static"]))
|
||||
x
|
||||
|> Map.put("id", fetch_suggestion_id(x))
|
||||
|> Map.put("avatar", MediaProxy.url(x["avatar"]))
|
||||
|> Map.put("avatar_static", MediaProxy.url(x["avatar_static"]))
|
||||
end)
|
||||
|
||||
conn
|
||||
|> json(data)
|
||||
json(conn, data)
|
||||
else
|
||||
e -> Logger.error("Could not retrieve suggestions at fetch #{url}, #{inspect(e)}")
|
||||
e ->
|
||||
Logger.error("Could not retrieve suggestions at fetch #{url}, #{inspect(e)}")
|
||||
end
|
||||
else
|
||||
json(conn, [])
|
||||
end
|
||||
end
|
||||
|
||||
defp fetch_suggestion_id(attrs) do
|
||||
case User.get_or_fetch(attrs["acct"]) do
|
||||
{:ok, %User{id: id}} -> id
|
||||
_ -> 0
|
||||
end
|
||||
end
|
||||
|
||||
def status_card(%{assigns: %{user: user}} = conn, %{"id" => status_id}) do
|
||||
with %Activity{} = activity <- Activity.get_by_id(status_id),
|
||||
true <- Visibility.visible_for_user?(activity, user) do
|
||||
|
|
|
@ -72,6 +72,13 @@ defp do_render("account.json", %{user: user} = opts) do
|
|||
image = User.avatar_url(user) |> MediaProxy.url()
|
||||
header = User.banner_url(user) |> MediaProxy.url()
|
||||
user_info = User.get_cached_user_info(user)
|
||||
|
||||
following_count =
|
||||
((!user.info.hide_follows or opts[:for] == user) && user_info.following_count) || 0
|
||||
|
||||
followers_count =
|
||||
((!user.info.hide_followers or opts[:for] == user) && user_info.follower_count) || 0
|
||||
|
||||
bot = (user.info.source_data["type"] || "Person") in ["Application", "Service"]
|
||||
|
||||
emojis =
|
||||
|
@ -102,8 +109,8 @@ defp do_render("account.json", %{user: user} = opts) do
|
|||
display_name: display_name,
|
||||
locked: user_info.locked,
|
||||
created_at: Utils.to_masto_date(user.inserted_at),
|
||||
followers_count: user_info.follower_count,
|
||||
following_count: user_info.following_count,
|
||||
followers_count: followers_count,
|
||||
following_count: following_count,
|
||||
statuses_count: user_info.note_count,
|
||||
note: bio || "",
|
||||
url: User.profile_url(user),
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
defmodule Pleroma.Web.MastodonAPI.StatusView do
|
||||
use Pleroma.Web, :view
|
||||
|
||||
require Pleroma.Constants
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Conversation
|
||||
alias Pleroma.Conversation.Participation
|
||||
|
@ -26,19 +28,19 @@ defp get_replied_to_activities([]), do: %{}
|
|||
defp get_replied_to_activities(activities) do
|
||||
activities
|
||||
|> Enum.map(fn
|
||||
%{data: %{"type" => "Create", "object" => object}} ->
|
||||
object = Object.normalize(object)
|
||||
object.data["inReplyTo"] != "" && object.data["inReplyTo"]
|
||||
%{data: %{"type" => "Create"}} = activity ->
|
||||
object = Object.normalize(activity)
|
||||
object && object.data["inReplyTo"] != "" && object.data["inReplyTo"]
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end)
|
||||
|> Enum.filter(& &1)
|
||||
|> Activity.create_by_object_ap_id()
|
||||
|> Activity.create_by_object_ap_id_with_object()
|
||||
|> Repo.all()
|
||||
|> Enum.reduce(%{}, fn activity, acc ->
|
||||
object = Object.normalize(activity)
|
||||
Map.put(acc, object.data["id"], activity)
|
||||
if object, do: Map.put(acc, object.data["id"], activity), else: acc
|
||||
end)
|
||||
end
|
||||
|
||||
|
@ -90,6 +92,7 @@ def render(
|
|||
reblogged_activity =
|
||||
Activity.create_by_object_ap_id(activity_object.data["id"])
|
||||
|> Activity.with_preloaded_bookmark(opts[:for])
|
||||
|> Activity.with_set_thread_muted_field(opts[:for])
|
||||
|> Repo.one()
|
||||
|
||||
reblogged = render("status.json", Map.put(opts, :activity, reblogged_activity))
|
||||
|
@ -144,6 +147,7 @@ def render("status.json", %{activity: %{data: %{"object" => _object}} = activity
|
|||
object = Object.normalize(activity)
|
||||
|
||||
user = get_user(activity.data["actor"])
|
||||
user_follower_address = user.follower_address
|
||||
|
||||
like_count = object.data["like_count"] || 0
|
||||
announcement_count = object.data["announcement_count"] || 0
|
||||
|
@ -159,7 +163,11 @@ def render("status.json", %{activity: %{data: %{"object" => _object}} = activity
|
|||
mentions =
|
||||
(object.data["to"] ++ tag_mentions)
|
||||
|> Enum.uniq()
|
||||
|> Enum.map(fn ap_id -> User.get_cached_by_ap_id(ap_id) end)
|
||||
|> Enum.map(fn
|
||||
Pleroma.Constants.as_public() -> nil
|
||||
^user_follower_address -> nil
|
||||
ap_id -> User.get_cached_by_ap_id(ap_id)
|
||||
end)
|
||||
|> Enum.filter(& &1)
|
||||
|> Enum.map(fn user -> AccountView.render("mention.json", %{user: user}) end)
|
||||
|
||||
|
@ -170,7 +178,7 @@ def render("status.json", %{activity: %{data: %{"object" => _object}} = activity
|
|||
thread_muted? =
|
||||
case activity.thread_muted? do
|
||||
thread_muted? when is_boolean(thread_muted?) -> thread_muted?
|
||||
nil -> CommonAPI.thread_muted?(user, activity)
|
||||
nil -> (opts[:for] && CommonAPI.thread_muted?(opts[:for], activity)) || false
|
||||
end
|
||||
|
||||
attachment_data = object.data["attachment"] || []
|
||||
|
|
|
@ -34,64 +34,18 @@ def schemas(conn, _params) do
|
|||
def raw_nodeinfo do
|
||||
stats = Stats.get_stats()
|
||||
|
||||
exclusions = Config.get([:instance, :mrf_transparency_exclusions])
|
||||
|
||||
mrf_simple =
|
||||
Config.get(:mrf_simple)
|
||||
|> Enum.map(fn {k, v} -> {k, Enum.reject(v, fn v -> v in exclusions end)} end)
|
||||
|> Enum.into(%{})
|
||||
|
||||
# This horror is needed to convert regex sigils to strings
|
||||
mrf_keyword =
|
||||
Config.get(:mrf_keyword, [])
|
||||
|> Enum.map(fn {key, value} ->
|
||||
{key,
|
||||
Enum.map(value, fn
|
||||
{pattern, replacement} ->
|
||||
%{
|
||||
"pattern" =>
|
||||
if not is_binary(pattern) do
|
||||
inspect(pattern)
|
||||
else
|
||||
pattern
|
||||
end,
|
||||
"replacement" => replacement
|
||||
}
|
||||
|
||||
pattern ->
|
||||
if not is_binary(pattern) do
|
||||
inspect(pattern)
|
||||
else
|
||||
pattern
|
||||
end
|
||||
end)}
|
||||
end)
|
||||
|> Enum.into(%{})
|
||||
|
||||
mrf_policies =
|
||||
MRF.get_policies()
|
||||
|> Enum.map(fn policy -> to_string(policy) |> String.split(".") |> List.last() end)
|
||||
|
||||
quarantined = Config.get([:instance, :quarantined_instances], [])
|
||||
|
||||
staff_accounts =
|
||||
User.all_superusers()
|
||||
|> Enum.map(fn u -> u.ap_id end)
|
||||
|
||||
mrf_user_allowlist =
|
||||
Config.get([:mrf_user_allowlist], [])
|
||||
|> Enum.into(%{}, fn {k, v} -> {k, length(v)} end)
|
||||
|
||||
federation_response =
|
||||
if Config.get([:instance, :mrf_transparency]) do
|
||||
%{
|
||||
mrf_policies: mrf_policies,
|
||||
mrf_simple: mrf_simple,
|
||||
mrf_keyword: mrf_keyword,
|
||||
mrf_user_allowlist: mrf_user_allowlist,
|
||||
quarantined_instances: quarantined,
|
||||
exclusions: length(exclusions) > 0
|
||||
}
|
||||
{:ok, data} = MRF.describe()
|
||||
|
||||
data
|
||||
|> Map.merge(%{quarantined_instances: quarantined})
|
||||
else
|
||||
%{}
|
||||
end
|
||||
|
|
|
@ -619,6 +619,8 @@ defmodule Pleroma.Web.Router do
|
|||
post("/push/hub/:nickname", Websub.WebsubController, :websub_subscription_request)
|
||||
get("/push/subscriptions/:id", Websub.WebsubController, :websub_subscription_confirmation)
|
||||
post("/push/subscriptions/:id", Websub.WebsubController, :websub_incoming)
|
||||
|
||||
get("/mailer/unsubscribe/:token", Mailer.SubscriptionController, :unsubscribe)
|
||||
end
|
||||
|
||||
pipeline :activitypub do
|
||||
|
|
20
lib/pleroma/web/templates/email/digest.html.eex
Normal file
20
lib/pleroma/web/templates/email/digest.html.eex
Normal file
|
@ -0,0 +1,20 @@
|
|||
<h1>Hey <%= @user.nickname %>, here is what you've missed!</h1>
|
||||
|
||||
<h2>New Mentions:</h2>
|
||||
<ul>
|
||||
<%= for %{data: mention, object: object, from: from} <- @mentions do %>
|
||||
<li><%= link from.nickname, to: mention.activity.actor %>: <%= raw object.data["content"] %></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
|
||||
<%= if @followers != [] do %>
|
||||
<h2><%= length(@followers) %> New Followers:</h2>
|
||||
<ul>
|
||||
<%= for %{data: follow, from: from} <- @followers do %>
|
||||
<li><%= link from.nickname, to: follow.activity.actor %></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% end %>
|
||||
|
||||
<p>You have received this email because you have signed up to receive digest emails from <b><%= @instance %></b> Pleroma instance.</p>
|
||||
<p>The email address you are subscribed as is <%= @user.email %>. To unsubscribe, please go <%= link "here", to: @unsubscribe_link %>.</p>
|
10
lib/pleroma/web/templates/layout/email.html.eex
Normal file
10
lib/pleroma/web/templates/layout/email.html.eex
Normal file
|
@ -0,0 +1,10 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title><%= @email.subject %></title>
|
||||
</head>
|
||||
<body>
|
||||
<%= render @view_module, @view_template, assigns %>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1 @@
|
|||
<h1>UNSUBSCRIBE FAILURE</h1>
|
|
@ -0,0 +1 @@
|
|||
<h1>UNSUBSCRIBE SUCCESSFUL</h1>
|
5
lib/pleroma/web/views/email_view.ex
Normal file
5
lib/pleroma/web/views/email_view.ex
Normal file
|
@ -0,0 +1,5 @@
|
|||
defmodule Pleroma.Web.EmailView do
|
||||
use Pleroma.Web, :view
|
||||
import Phoenix.HTML
|
||||
import Phoenix.HTML.Link
|
||||
end
|
3
lib/pleroma/web/views/mailer/subscription_view.ex
Normal file
3
lib/pleroma/web/views/mailer/subscription_view.ex
Normal file
|
@ -0,0 +1,3 @@
|
|||
defmodule Pleroma.Web.Mailer.SubscriptionView do
|
||||
use Pleroma.Web, :view
|
||||
end
|
|
@ -58,10 +58,10 @@ def safe_render(view, template, assigns \\ %{}) do
|
|||
rescue
|
||||
error ->
|
||||
Logger.error(
|
||||
"#{__MODULE__} failed to render #{inspect({view, template})}: #{inspect(error)}"
|
||||
"#{__MODULE__} failed to render #{inspect({view, template})}\n" <>
|
||||
Exception.format(:error, error, __STACKTRACE__)
|
||||
)
|
||||
|
||||
Logger.error(inspect(__STACKTRACE__))
|
||||
nil
|
||||
end
|
||||
|
||||
|
|
7
mix.exs
7
mix.exs
|
@ -114,8 +114,9 @@ defp deps do
|
|||
{:tesla, "~> 1.2"},
|
||||
{:jason, "~> 1.0"},
|
||||
{:mogrify, "~> 0.6.1"},
|
||||
{:ex_aws, "~> 2.0"},
|
||||
{:ex_aws, "~> 2.1"},
|
||||
{:ex_aws_s3, "~> 2.0"},
|
||||
{:sweet_xml, "~> 0.6.6"},
|
||||
{:earmark, "~> 1.3"},
|
||||
{:bbcode, "~> 0.1.1"},
|
||||
{:ex_machina, "~> 2.3", only: :test},
|
||||
|
@ -127,6 +128,7 @@ defp deps do
|
|||
{:ex_doc, "~> 0.20.2", only: :dev, runtime: false},
|
||||
{:web_push_encryption, "~> 0.2.1"},
|
||||
{:swoosh, "~> 0.23.2"},
|
||||
{:phoenix_swoosh, "~> 0.2"},
|
||||
{:gen_smtp, "~> 0.13"},
|
||||
{:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test},
|
||||
{:floki, "~> 0.20.0"},
|
||||
|
@ -139,7 +141,7 @@ defp deps do
|
|||
{:http_signatures,
|
||||
git: "https://git.pleroma.social/pleroma/http_signatures.git",
|
||||
ref: "293d77bb6f4a67ac8bde1428735c3b42f22cbb30"},
|
||||
{:pleroma_job_queue, "~> 0.2.0"},
|
||||
{:pleroma_job_queue, "~> 0.3"},
|
||||
{:telemetry, "~> 0.3"},
|
||||
{:prometheus_ex, "~> 3.0"},
|
||||
{:prometheus_plugs, "~> 1.1"},
|
||||
|
@ -147,6 +149,7 @@ defp deps do
|
|||
{:prometheus_ecto, "~> 1.4"},
|
||||
{:recon, github: "ferd/recon", tag: "2.4.0"},
|
||||
{:quack, "~> 0.1.1"},
|
||||
{:joken, "~> 2.0"},
|
||||
{:benchee, "~> 1.0"},
|
||||
{:esshd, "~> 0.1.0", runtime: Application.get_env(:esshd, :enabled, false)},
|
||||
{:ex_rated, "~> 1.3"},
|
||||
|
|
18
mix.lock
18
mix.lock
|
@ -5,16 +5,17 @@
|
|||
"bbcode": {:hex, :bbcode, "0.1.1", "0023e2c7814119b2e620b7add67182e3f6019f92bfec9a22da7e99821aceba70", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"benchee": {:hex, :benchee, "1.0.1", "66b211f9bfd84bd97e6d1beaddf8fc2312aaabe192f776e8931cb0c16f53a521", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"},
|
||||
"cachex": {:hex, :cachex, "3.0.2", "1351caa4e26e29f7d7ec1d29b53d6013f0447630bbf382b4fb5d5bad0209f203", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"cachex": {:hex, :cachex, "3.0.3", "4e2d3e05814a5738f5ff3903151d5c25636d72a3527251b753f501ad9c657967", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"calendar": {:hex, :calendar, "0.17.6", "ec291cb2e4ba499c2e8c0ef5f4ace974e2f9d02ae9e807e711a9b0c7850b9aee", [:mix], [{:tzdata, "~> 0.5.20 or ~> 0.1.201603 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm"},
|
||||
"comeonin": {:hex, :comeonin, "4.1.1", "c7304fc29b45b897b34142a91122bc72757bc0c295e9e824999d5179ffc08416", [:mix], [{:argon2_elixir, "~> 1.2", [hex: :argon2_elixir, repo: "hexpm", optional: true]}, {:bcrypt_elixir, "~> 0.12.1 or ~> 1.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: true]}, {:pbkdf2_elixir, "~> 0.12", [hex: :pbkdf2_elixir, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"comeonin": {:hex, :comeonin, "4.1.2", "3eb5620fd8e35508991664b4c2b04dd41e52f1620b36957be837c1d7784b7592", [:mix], [{:argon2_elixir, "~> 1.2", [hex: :argon2_elixir, repo: "hexpm", optional: true]}, {:bcrypt_elixir, "~> 0.12.1 or ~> 1.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: true]}, {:pbkdf2_elixir, "~> 0.12", [hex: :pbkdf2_elixir, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"},
|
||||
"cors_plug": {:hex, :cors_plug, "1.5.2", "72df63c87e4f94112f458ce9d25800900cc88608c1078f0e4faddf20933eda6e", [:mix], [{:plug, "~> 1.3 or ~> 1.4 or ~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"cowboy": {:hex, :cowboy, "2.6.3", "99aa50e94e685557cad82e704457336a453d4abcb77839ad22dbe71f311fcc06", [:rebar3], [{:cowlib, "~> 2.7.3", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"cowlib": {:hex, :cowlib, "2.7.3", "a7ffcd0917e6d50b4d5fb28e9e2085a0ceb3c97dea310505f7460ff5ed764ce9", [:rebar3], [], "hexpm"},
|
||||
"credo": {:hex, :credo, "0.9.3", "76fa3e9e497ab282e0cf64b98a624aa11da702854c52c82db1bf24e54ab7c97a", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"crontab": {:hex, :crontab, "1.1.7", "b9219f0bdc8678b94143655a8f229716c5810c0636a4489f98c0956137e53985", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"crypt": {:git, "https://github.com/msantos/crypt", "1f2b58927ab57e72910191a7ebaeff984382a1d3", [ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"]},
|
||||
"db_connection": {:hex, :db_connection, "2.0.6", "bde2f85d047969c5b5800cb8f4b3ed6316c8cb11487afedac4aa5f93fd39abfa", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"decimal": {:hex, :decimal, "1.8.0", "ca462e0d885f09a1c5a342dbd7c1dcf27ea63548c65a65e67334f4b61803822e", [:mix], [], "hexpm"},
|
||||
|
@ -43,14 +44,15 @@
|
|||
"httpoison": {:hex, :httpoison, "1.2.0", "2702ed3da5fd7a8130fc34b11965c8cfa21ade2f232c00b42d96d4967c39a3a3", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"jose": {:hex, :jose, "1.8.4", "7946d1e5c03a76ac9ef42a6e6a20001d35987afd68c2107bcd8f01a84e75aa73", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"joken": {:hex, :joken, "2.0.1", "ec9ab31bf660f343380da033b3316855197c8d4c6ef597fa3fcb451b326beb14", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"jose": {:hex, :jose, "1.9.0", "4167c5f6d06ffaebffd15cdb8da61a108445ef5e85ab8f5a7ad926fdf3ada154", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm"},
|
||||
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
|
||||
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"},
|
||||
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"},
|
||||
"mochiweb": {:hex, :mochiweb, "2.15.0", "e1daac474df07651e5d17cc1e642c4069c7850dc4508d3db7263a0651330aacc", [:rebar3], [], "hexpm"},
|
||||
"mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"},
|
||||
"mock": {:hex, :mock, "0.3.3", "42a433794b1291a9cf1525c6d26b38e039e0d3a360732b5e467bfc77ef26c914", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"mogrify": {:hex, :mogrify, "0.6.1", "de1b527514f2d95a7bbe9642eb556061afb337e220cf97adbf3a4e6438ed70af", [:mix], [], "hexpm"},
|
||||
"mox": {:hex, :mox, "0.5.1", "f86bb36026aac1e6f924a4b6d024b05e9adbed5c63e8daa069bd66fb3292165b", [:mix], [], "hexpm"},
|
||||
|
@ -61,24 +63,24 @@
|
|||
"phoenix_ecto": {:hex, :phoenix_ecto, "4.0.0", "c43117a136e7399ea04ecaac73f8f23ee0ffe3e07acfcb8062fe5f4c9f0f6531", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"phoenix_html": {:hex, :phoenix_html, "2.13.1", "fa8f034b5328e2dfa0e4131b5569379003f34bc1fafdaa84985b0b9d2f12e68b", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.2", "496c303bdf1b2e98a9d26e89af5bba3ab487ba3a3735f74bf1f4064d2a845a3e", [:mix], [], "hexpm"},
|
||||
"pleroma_job_queue": {:hex, :pleroma_job_queue, "0.2.0", "879e660aa1cebe8dc6f0aaaa6aa48b4875e89cd961d4a585fd128e0773b31a18", [:mix], [], "hexpm"},
|
||||
"phoenix_swoosh": {:hex, :phoenix_swoosh, "0.2.0", "a7e0b32077cd6d2323ae15198839b05d9caddfa20663fd85787479e81f89520e", [:mix], [{:phoenix, "~> 1.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.2", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:swoosh, "~> 0.1", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"pleroma_job_queue": {:hex, :pleroma_job_queue, "0.3.0", "b84538d621f0c3d6fcc1cff9d5648d3faaf873b8b21b94e6503428a07a48ec47", [:mix], [{:crontab, "~> 1.1", [hex: :crontab, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"plug": {:hex, :plug, "1.8.2", "0bcce1daa420f189a6491f3940cc77ea7fb1919761175c9c3b59800d897440fc", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"plug_cowboy": {:hex, :plug_cowboy, "2.1.0", "b75768153c3a8a9e8039d4b25bb9b14efbc58e9c4a6e6a270abff1cd30cbe320", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"},
|
||||
"plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
|
||||
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm"},
|
||||
"postgrex": {:hex, :postgrex, "0.14.3", "5754dee2fdf6e9e508cbf49ab138df964278700b764177e8f3871e658b345a1e", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"prometheus": {:hex, :prometheus, "4.4.1", "1e96073b3ed7788053768fea779cbc896ddc3bdd9ba60687f2ad50b252ac87d6", [:mix, :rebar3], [], "hexpm"},
|
||||
"prometheus_ecto": {:hex, :prometheus_ecto, "1.4.1", "6c768ea9654de871e5b32fab2eac348467b3021604ebebbcbd8bcbe806a65ed5", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"prometheus_ex": {:hex, :prometheus_ex, "3.0.5", "fa58cfd983487fc5ead331e9a3e0aa622c67232b3ec71710ced122c4c453a02f", [:mix], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"prometheus_phoenix": {:hex, :prometheus_phoenix, "1.3.0", "c4b527e0b3a9ef1af26bdcfbfad3998f37795b9185d475ca610fe4388fdd3bb5", [:mix], [{:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.3 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"prometheus_plugs": {:hex, :prometheus_plugs, "1.1.5", "25933d48f8af3a5941dd7b621c889749894d8a1082a6ff7c67cc99dec26377c5", [:mix], [{:accept, "~> 0.1", [hex: :accept, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}, {:prometheus_process_collector, "~> 1.1", [hex: :prometheus_process_collector, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"prometheus_process_collector": {:hex, :prometheus_process_collector, "1.4.0", "6dbd39e3165b9ef1c94a7a820e9ffe08479f949dcdd431ed4aaea7b250eebfde", [:rebar3], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"quack": {:hex, :quack, "0.1.1", "cca7b4da1a233757fdb44b3334fce80c94785b3ad5a602053b7a002b5a8967bf", [:mix], [{:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
|
||||
"recon": {:git, "https://github.com/ferd/recon.git", "75d70c7c08926d2f24f1ee6de14ee50fe8a52763", [tag: "2.4.0"]},
|
||||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"},
|
||||
"sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm"},
|
||||
"swoosh": {:hex, :swoosh, "0.23.2", "7dda95ff0bf54a2298328d6899c74dae1223777b43563ccebebb4b5d2b61df38", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"syslog": {:git, "https://github.com/Vagabond/erlang-syslog.git", "4a6c6f2c996483e86c1320e9553f91d337bcb6aa", [tag: "1.0.5"]},
|
||||
"telemetry": {:hex, :telemetry, "0.4.0", "8339bee3fa8b91cb84d14c2935f8ecf399ccd87301ad6da6b71c09553834b2ab", [:rebar3], [], "hexpm"},
|
||||
|
@ -88,7 +90,7 @@
|
|||
"tzdata": {:hex, :tzdata, "1.0.1", "f6027a331af7d837471248e62733c6ebee86a72e57c613aa071ebb1f750fc71a", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"ueberauth": {:hex, :ueberauth, "0.6.1", "9e90d3337dddf38b1ca2753aca9b1e53d8a52b890191cdc55240247c89230412", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"},
|
||||
"unsafe": {:hex, :unsafe, "1.0.0", "7c21742cd05380c7875546b023481d3a26f52df8e5dfedcb9f958f322baae305", [:mix], [], "hexpm"},
|
||||
"unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm"},
|
||||
"web_push_encryption": {:hex, :web_push_encryption, "0.2.1", "d42cecf73420d9dc0053ba3299cc8c8d6ff2be2487d67ca2a57265868e4d9a98", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:poison, "~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"websocket_client": {:git, "https://github.com/jeremyong/websocket_client.git", "9a6f65d05ebf2725d62fb19262b21f1805a59fbf", []},
|
||||
}
|
||||
|
|
20
priv/repo/migrations/20190412052952_add_user_info_fields.exs
Normal file
20
priv/repo/migrations/20190412052952_add_user_info_fields.exs
Normal file
|
@ -0,0 +1,20 @@
|
|||
defmodule Pleroma.Repo.Migrations.AddEmailNotificationsToUserInfo do
|
||||
use Ecto.Migration
|
||||
|
||||
def up do
|
||||
execute("
|
||||
UPDATE users
|
||||
SET info = info || '{
|
||||
\"email_notifications\": {
|
||||
\"digest\": false
|
||||
}
|
||||
}'")
|
||||
end
|
||||
|
||||
def down do
|
||||
execute("
|
||||
UPDATE users
|
||||
SET info = info - 'email_notifications'
|
||||
")
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
defmodule Pleroma.Repo.Migrations.AddSigninAndLastDigestDatesToUser do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table(:users) do
|
||||
add(:last_digest_emailed_at, :naive_datetime, default: fragment("now()"))
|
||||
end
|
||||
end
|
||||
end
|
1
priv/static/adminfe/chunk-0e18.e12401fb.css
Normal file
1
priv/static/adminfe/chunk-0e18.e12401fb.css
Normal file
|
@ -0,0 +1 @@
|
|||
header[data-v-71c7ded0]{-webkit-box-align:center;-ms-flex-align:center;align-items:center;display:-webkit-box;display:-ms-flexbox;display:flex;margin:22px 0;padding-left:15px}header h1[data-v-71c7ded0]{margin:0 0 0 10px}table[data-v-71c7ded0]{margin:10px 0 0 15px}table .name-col[data-v-71c7ded0]{width:150px}.el-table--border[data-v-71c7ded0]:after,.el-table--group[data-v-71c7ded0]:after,.el-table[data-v-71c7ded0]:before{background-color:transparent}.poll ul[data-v-71c7ded0]{list-style-type:none;padding:0;width:30%}.image[data-v-71c7ded0]{width:20%}.image img[data-v-71c7ded0]{width:100%}.statuses[data-v-71c7ded0]{padding-right:20px}.show-private[data-v-71c7ded0]{text-align:right;line-height:67px;padding-right:20px}
|
|
@ -1 +1 @@
|
|||
.status-account{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.status-avatar-img{width:15px;height:15px;margin-right:5px}.status-account-name{margin:0;height:22px}.status-body{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.status-content{font-size:15px}.status-card{margin-bottom:15px}.status-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}@media (min-device-width:768px) and (max-device-width:1024px),only screen and (max-width:760px){.el-message{min-width:80%}.el-message-box{width:80%}.status-card .el-card__header{padding:10px 17px}.status-card .el-tag{margin:3px 4px 3px 0}.status-card .status-account-container{margin-bottom:5px}.status-card .status-actions-button{margin:3px 0}.status-card .status-actions{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.status-card .status-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}}.account{text-decoration:underline}.avatar-img{vertical-align:bottom;width:15px;height:15px;margin-left:5px}.el-card__body{padding:17px}.el-card__header{background-color:#fafafa;padding:10px 20px}.el-collapse{border-bottom:none}.el-collapse-item__header{height:46px;font-size:14px}.el-collapse-item__content{padding-bottom:7px}.el-icon-arrow-right{margin-right:6px}.el-icon-close{padding:10px 5px 10px 10px;cursor:pointer}h4{margin:0;height:17px}.header-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline;height:40px}.id{color:grey;margin-top:6px}.line{width:100%;height:0;border:.5px solid #ebeef5;margin:15px 0}.new-note p{font-size:14px;font-weight:500;height:17px;margin:13px 0 7px}.note{-webkit-box-shadow:0 2px 5px 0 rgba(0,0,0,.1);box-shadow:0 2px 5px 0 rgba(0,0,0,.1);margin-bottom:10px}.no-notes{font-style:italic;color:grey}.report-row-key{font-weight:500;font-size:14px}.report-title{margin:0}.statuses{margin-top:15px}.submit-button{display:block;margin:7px 0 17px auto}.timestamp{margin:0;font-style:italic;color:grey}@media (min-device-width:768px) and (max-device-width:1024px),only screen and (max-width:760px){.timeline-item-container .header-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;height:80px}.timeline-item-container .id{margin:6px 0 0}}.select-field[data-v-bb4390da]{width:350px}@media (min-device-width:768px) and (max-device-width:1024px),only screen and (max-width:760px){.select-field[data-v-bb4390da]{width:100%;margin-bottom:5px}}.reports-container .el-timeline[data-v-e32c7dc6]{margin:45px 45px 45px 19px;padding:0}.reports-container .filter-container[data-v-e32c7dc6]{margin:22px 15px;padding-bottom:0}.reports-container h1[data-v-e32c7dc6]{margin:22px 0 0 15px}.reports-container .no-reports-message[data-v-e32c7dc6]{color:grey;margin-left:19px}@media (min-device-width:768px) and (max-device-width:1024px),only screen and (max-width:760px){.reports-container h1[data-v-e32c7dc6]{margin:7px 10px 15px}.reports-container .filter-container[data-v-e32c7dc6]{margin:0 10px}.reports-container .timeline[data-v-e32c7dc6]{margin:20px 20px 20px 18px}}
|
||||
.status-account{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.status-avatar-img{width:15px;height:15px;margin-right:5px}.status-account-name{margin:0;height:22px}.status-body{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.status-content{font-size:15px}.status-card{margin-bottom:15px}.status-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}@media (min-device-width:768px) and (max-device-width:1024px),only screen and (max-width:760px){.el-message{min-width:80%}.el-message-box{width:80%}.status-card .el-card__header{padding:10px 17px}.status-card .el-tag{margin:3px 4px 3px 0}.status-card .status-account-container{margin-bottom:5px}.status-card .status-actions-button{margin:3px 0}.status-card .status-actions{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.status-card .status-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}}.account{text-decoration:underline}.avatar-img{vertical-align:bottom;width:15px;height:15px;margin-left:5px}.el-card__body{padding:17px}.el-card__header{background-color:#fafafa;padding:10px 20px}.el-collapse{border-bottom:none}.el-collapse-item__header{height:46px;font-size:14px}.el-collapse-item__content{padding-bottom:7px}.el-icon-arrow-right{margin-right:6px}.el-icon-close{padding:10px 5px 10px 10px;cursor:pointer}h4{margin:0;height:17px}.header-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline;height:40px}.id{color:grey;margin-top:6px}.line{width:100%;height:0;border:.5px solid #ebeef5;margin:15px 0}.new-note p{font-size:14px;font-weight:500;height:17px;margin:13px 0 7px}.note{-webkit-box-shadow:0 2px 5px 0 rgba(0,0,0,.1);box-shadow:0 2px 5px 0 rgba(0,0,0,.1);margin-bottom:10px}.no-notes{font-style:italic;color:grey}.report-row-key{font-weight:500;font-size:14px}.report-title{margin:0}.statuses{margin-top:15px}.submit-button{display:block;margin:7px 0 17px auto}.timestamp{margin:0;font-style:italic;color:grey}@media (min-device-width:768px) and (max-device-width:1024px),only screen and (max-width:760px){.timeline-item-container .header-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;height:80px}.timeline-item-container .id{margin:6px 0 0}}.select-field[data-v-07695bc4]{width:350px}@media (min-device-width:768px) and (max-device-width:1024px),only screen and (max-width:760px){.select-field[data-v-07695bc4]{width:100%;margin-bottom:5px}}.reports-container .el-timeline[data-v-e32c7dc6]{margin:45px 45px 45px 19px;padding:0}.reports-container .filter-container[data-v-e32c7dc6]{margin:22px 15px;padding-bottom:0}.reports-container h1[data-v-e32c7dc6]{margin:22px 0 0 15px}.reports-container .no-reports-message[data-v-e32c7dc6]{color:grey;margin-left:19px}@media (min-device-width:768px) and (max-device-width:1024px),only screen and (max-width:760px){.reports-container h1[data-v-e32c7dc6]{margin:7px 10px 15px}.reports-container .filter-container[data-v-e32c7dc6]{margin:0 10px}.reports-container .timeline[data-v-e32c7dc6]{margin:20px 20px 20px 18px}}
|
1
priv/static/adminfe/chunk-5e57.ac97b15a.css
Normal file
1
priv/static/adminfe/chunk-5e57.ac97b15a.css
Normal file
|
@ -0,0 +1 @@
|
|||
a{text-decoration:underline}.code{background-color:rgba(173,190,214,.48);border-radius:3px;font-family:monospace;padding:0 3px}.el-form-item{margin-right:30px}.el-select{width:100%}.esshd-list{margin:0}.expl{color:#666;font-size:13px;line-height:22px;margin:5px 0 0;overflow-wrap:break-word}.highlight{background-color:#e6e6e6}.limit-button-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.limit-expl{margin-left:10px}.limit-input{width:48%;margin:0 0 5px 8px}.line{width:100%;height:0;border:1px solid #eee;margin-bottom:22px}.mascot-container{margin-bottom:15px}.mascot-input{margin-bottom:7px}.mascot-name-container{display:-webkit-box;display:-ms-flexbox;display:flex;margin-bottom:7px}.mascot-name-input{margin-right:10px}.name-input{width:30%;margin-right:8px}.options-paragraph{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;font-weight:700;line-height:20px;margin:0 0 14px}.options-paragraph-container{overflow-wrap:break-word;margin-bottom:0}.pattern-input{width:20%;margin-right:8px}.setting-input{display:-webkit-box;display:-ms-flexbox;display:flex;margin-bottom:10px}.single-input{margin-right:10px}.scale-input{width:48%;margin:0 8px 5px 0}.replacement-input{width:80%;margin-left:8px;margin-right:10px}.text{line-height:20px;margin-right:15px}.upload-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.value-input{width:70%;margin-left:8px;margin-right:10px}@media (min-device-width:768px) and (max-device-width:1024px),only screen and (max-width:760px){.el-form-item{margin-right:15px}.el-input__inner{padding:0 5px}.el-form-item__label:not(.no-top-margin){padding-left:3px;padding-right:10px;line-height:22px;margin-top:7px}.el-message{min-width:80%}.el-select__tags{overflow:hidden}.name-input{width:40%;margin-right:5px}p.expl{line-height:20px}.pattern-input{width:40%;margin-right:4px}.replacement-input{width:60%;margin-left:4px;margin-right:5px}.top-margin{position:absolute;top:25%}.value-input{width:60%;margin-left:5px;margin-right:8px}}.settings-container .el-tabs[data-v-729534ce]{margin-top:20px}.settings-container h1[data-v-729534ce]{margin:22px 0 0 15px}
|
|
@ -1 +1 @@
|
|||
.select-field[data-v-71bc6b38]{width:350px}@media (min-device-width:768px) and (max-device-width:1024px),only screen and (max-width:760px){.select-field[data-v-71bc6b38]{width:100%;margin-bottom:5px}}.actions-button[data-v-94227b1e]{text-align:left;width:350px;padding:10px}.actions-button-container[data-v-94227b1e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.el-dropdown[data-v-94227b1e]{float:right}.el-icon-edit[data-v-94227b1e]{margin-right:5px}.tag-container[data-v-94227b1e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.tag-text[data-v-94227b1e]{padding-right:20px}.no-hover[data-v-94227b1e]:hover{color:#606266;background-color:#fff;cursor:auto}@media (min-device-width:768px) and (max-device-width:1024px),only screen and (max-width:760px){.create-user-dialog{width:80%}.create-account-form-item{margin-bottom:30px}.el-dialog__body{padding:20px 20px 0}}.actions-button[data-v-3ffddd00]{text-align:left;width:350px;padding:10px}.actions-container[data-v-3ffddd00]{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:0 15px 10px}.active-tag[data-v-3ffddd00]{color:#409eff;font-weight:700}.active-tag .el-icon-check[data-v-3ffddd00]{color:#409eff;float:right;margin:7px 0 0 15px}.el-dropdown-link[data-v-3ffddd00]:hover{cursor:pointer;color:#409eff}.el-icon-plus[data-v-3ffddd00]{margin-right:5px}.users-container h1[data-v-3ffddd00]{margin:22px 0 0 15px}.users-container .pagination[data-v-3ffddd00]{margin:25px 0;text-align:center}.users-container .search[data-v-3ffddd00]{width:350px;float:right}.users-container .filter-container[data-v-3ffddd00]{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:22px 15px 15px}.users-container .user-count[data-v-3ffddd00]{color:grey;font-size:28px}@media (min-device-width:768px) and (max-device-width:1024px),only screen and (max-width:760px){.users-container h1[data-v-3ffddd00]{margin:7px 10px 15px}.users-container .actions-container[data-v-3ffddd00]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;margin:0 10px 7px}.users-container .create-account[data-v-3ffddd00]{width:100%}.users-container .el-icon-arrow-down[data-v-3ffddd00]{font-size:12px}.users-container .search[data-v-3ffddd00]{width:100%}.users-container .filter-container[data-v-3ffddd00]{display:-webkit-box;display:-ms-flexbox;display:flex;height:82px;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;margin:0 10px}.users-container .el-tag[data-v-3ffddd00]{width:30px;display:inline-block;margin-bottom:4px;font-weight:700}.users-container .el-tag.el-tag--danger[data-v-3ffddd00],.users-container .el-tag.el-tag--success[data-v-3ffddd00]{padding-left:8px}}
|
||||
.select-field[data-v-71bc6b38]{width:350px}@media (min-device-width:768px) and (max-device-width:1024px),only screen and (max-width:760px){.select-field[data-v-71bc6b38]{width:100%;margin-bottom:5px}}.actions-button[data-v-94227b1e]{text-align:left;width:350px;padding:10px}.actions-button-container[data-v-94227b1e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.el-dropdown[data-v-94227b1e]{float:right}.el-icon-edit[data-v-94227b1e]{margin-right:5px}.tag-container[data-v-94227b1e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.tag-text[data-v-94227b1e]{padding-right:20px}.no-hover[data-v-94227b1e]:hover{color:#606266;background-color:#fff;cursor:auto}@media (min-device-width:768px) and (max-device-width:1024px),only screen and (max-width:760px){.create-user-dialog{width:80%}.create-account-form-item{margin-bottom:30px}.el-dialog__body{padding:20px 20px 0}}.actions-button[data-v-c51cd8ee]{text-align:left;width:350px;padding:10px}.actions-container[data-v-c51cd8ee]{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:0 15px 10px}.active-tag[data-v-c51cd8ee]{color:#409eff;font-weight:700}.active-tag .el-icon-check[data-v-c51cd8ee]{color:#409eff;float:right;margin:7px 0 0 15px}.el-dropdown-link[data-v-c51cd8ee]:hover{cursor:pointer;color:#409eff}.el-icon-plus[data-v-c51cd8ee]{margin-right:5px}.users-container h1[data-v-c51cd8ee]{margin:22px 0 0 15px}.users-container .pagination[data-v-c51cd8ee]{margin:25px 0;text-align:center}.users-container .search[data-v-c51cd8ee]{width:350px;float:right}.users-container .filter-container[data-v-c51cd8ee]{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:22px 15px 15px}.users-container .user-count[data-v-c51cd8ee]{color:grey;font-size:28px}@media (min-device-width:768px) and (max-device-width:1024px),only screen and (max-width:760px){.users-container h1[data-v-c51cd8ee]{margin:7px 10px 15px}.users-container .actions-container[data-v-c51cd8ee]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;margin:0 10px 7px}.users-container .create-account[data-v-c51cd8ee]{width:100%}.users-container .el-icon-arrow-down[data-v-c51cd8ee]{font-size:12px}.users-container .search[data-v-c51cd8ee]{width:100%}.users-container .filter-container[data-v-c51cd8ee]{display:-webkit-box;display:-ms-flexbox;display:flex;height:82px;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;margin:0 10px}.users-container .el-tag[data-v-c51cd8ee]{width:30px;display:inline-block;margin-bottom:4px;font-weight:700}.users-container .el-tag.el-tag--danger[data-v-c51cd8ee],.users-container .el-tag.el-tag--success[data-v-c51cd8ee]{padding-left:8px}}
|
1
priv/static/adminfe/chunk-elementUI.e5cd8da6.css
Normal file
1
priv/static/adminfe/chunk-elementUI.e5cd8da6.css
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1 +1 @@
|
|||
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=renderer content=webkit><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><title>Admin FE</title><link rel="shortcut icon" href=favicon.ico><link href=chunk-elementUI.f74c256b.css rel=stylesheet><link href=chunk-libs.4e8c4664.css rel=stylesheet><link href=app.34fc670f.css rel=stylesheet></head><body><script src=/pleroma/admin/static/tinymce4.7.5/tinymce.min.js></script><div id=app></div><script type=text/javascript src=static/js/runtime.d8d12c12.js></script><script type=text/javascript src=static/js/chunk-elementUI.1fa5434b.js></script><script type=text/javascript src=static/js/chunk-libs.d5609760.js></script><script type=text/javascript src=static/js/app.4137ad8f.js></script></body></html>
|
||||
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=renderer content=webkit><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><title>Admin FE</title><link rel="shortcut icon" href=favicon.ico><link href=chunk-elementUI.e5cd8da6.css rel=stylesheet><link href=chunk-libs.4e8c4664.css rel=stylesheet><link href=app.34fc670f.css rel=stylesheet></head><body><script src=/pleroma/admin/static/tinymce4.7.5/tinymce.min.js></script><div id=app></div><script type=text/javascript src=static/js/runtime.f40c8ec4.js></script><script type=text/javascript src=static/js/chunk-elementUI.1911151b.js></script><script type=text/javascript src=static/js/chunk-libs.fb0b7f4a.js></script><script type=text/javascript src=static/js/app.8e186193.js></script></body></html>
|
Binary file not shown.
BIN
priv/static/adminfe/static/fonts/element-icons.535877f.woff
Normal file
BIN
priv/static/adminfe/static/fonts/element-icons.535877f.woff
Normal file
Binary file not shown.
Binary file not shown.
BIN
priv/static/adminfe/static/fonts/element-icons.732389d.ttf
Normal file
BIN
priv/static/adminfe/static/fonts/element-icons.732389d.ttf
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
1
priv/static/adminfe/static/js/app.8e186193.js
Normal file
1
priv/static/adminfe/static/js/app.8e186193.js
Normal file
File diff suppressed because one or more lines are too long
1
priv/static/adminfe/static/js/chunk-0e18.208cd826.js
Normal file
1
priv/static/adminfe/static/js/chunk-0e18.208cd826.js
Normal file
|
@ -0,0 +1 @@
|
|||
(window.webpackJsonp=window.webpackJsonp||[]).push([["chunk-0e18"],{"4bFr":function(t,e,s){"use strict";s.r(e);var a={name:"UsersShow",data:function(){return{showPrivate:!1}},computed:{loading:function(){return this.$store.state.userProfile.loading},user:function(){return this.$store.state.userProfile.user},statuses:function(){return this.$store.state.userProfile.statuses}},mounted:function(){this.$store.dispatch("FetchData",{id:this.$route.params.id,godmode:!1})},methods:{optionPercent:function(t,e){var s=t.options.reduce(function(t,e){return t+e.votes_count},0);return 0===s?0:+(e.votes_count/s*100).toFixed(1)},createdAtLocaleString:function(t){var e=new Date(t);return"".concat(e.toLocaleDateString()," ").concat(e.toLocaleTimeString())},onTogglePrivate:function(){console.log(this.showPrivate),this.$store.dispatch("FetchData",{id:this.$route.params.id,godmode:this.showPrivate})}}},r=(s("QG2t"),s("KHd+")),l=Object(r.a)(a,function(){var t=this,e=t.$createElement,s=t._self._c||e;return t.loading?t._e():s("main",[s("header",[s("el-avatar",{attrs:{src:t.user.avatar,size:"large"}}),t._v(" "),s("h1",[t._v(t._s(t.user.display_name))])],1),t._v(" "),s("el-row",[s("el-col",{attrs:{span:6}},[s("div",{staticClass:"el-table el-table--fit el-table--enable-row-hover el-table--enable-row-transition el-table--medium"},[s("table",{staticClass:"el-table__body"},[s("tbody",[s("tr",{staticClass:"el-table__row"},[s("td",{staticClass:"name-col"},[t._v("ID")]),t._v(" "),s("td",{staticClass:"value-col"},[t._v("\n "+t._s(t.user.id)+"\n ")])]),t._v(" "),s("tr",{staticClass:"el-table__row"},[s("td",[t._v(t._s(t.$t("userProfile.tags")))]),t._v(" "),s("td",[t._l(t.user.tags,function(e){return s("el-tag",{key:e},[t._v(t._s(e))])}),t._v(" "),0===t.user.tags.length?s("span",[t._v("None")]):t._e()],2)]),t._v(" "),s("tr",{staticClass:"el-table__row"},[s("td",[t._v(t._s(t.$t("userProfile.moderator")))]),t._v(" "),s("td",[t.user.roles.moderator?s("el-tag",{attrs:{type:"success"}},[s("i",{staticClass:"el-icon-check"})]):t._e(),t._v(" "),t.user.roles.moderator?t._e():s("el-tag",{attrs:{type:"danger"}},[s("i",{staticClass:"el-icon-error"})])],1)]),t._v(" "),s("tr",{staticClass:"el-table__row"},[s("td",[t._v(t._s(t.$t("userProfile.admin")))]),t._v(" "),s("td",[t.user.roles.admin?s("el-tag",{attrs:{type:"success"}},[s("i",{staticClass:"el-icon-check"})]):t._e(),t._v(" "),t.user.roles.admin?t._e():s("el-tag",{attrs:{type:"danger"}},[s("i",{staticClass:"el-icon-error"})])],1)]),t._v(" "),s("tr",{staticClass:"el-table__row"},[s("td",[t._v(t._s(t.$t("userProfile.local")))]),t._v(" "),s("td",[t.user.local?s("el-tag",{attrs:{type:"success"}},[s("i",{staticClass:"el-icon-check"})]):t._e(),t._v(" "),t.user.local?t._e():s("el-tag",{attrs:{type:"danger"}},[s("i",{staticClass:"el-icon-error"})])],1)]),t._v(" "),s("tr",{staticClass:"el-table__row"},[s("td",[t._v(t._s(t.$t("userProfile.deactivated")))]),t._v(" "),s("td",[t.user.deactivated?s("el-tag",{attrs:{type:"success"}},[s("i",{staticClass:"el-icon-check"})]):t._e(),t._v(" "),t.user.deactivated?t._e():s("el-tag",{attrs:{type:"danger"}},[s("i",{staticClass:"el-icon-error"})])],1)]),t._v(" "),s("tr",{staticClass:"el-table__row"},[s("td",[t._v(t._s(t.$t("userProfile.nickname")))]),t._v(" "),s("td",[t._v("\n "+t._s(t.user.nickname)+"\n ")])])])])])]),t._v(" "),s("el-row",{staticClass:"row-bg",attrs:{type:"flex",justify:"space-between"}},[s("el-col",{attrs:{span:18}},[s("h2",[t._v(t._s(t.$t("userProfile.recentStatuses")))])]),t._v(" "),s("el-col",{staticClass:"show-private",attrs:{span:6}},[s("el-checkbox",{on:{change:t.onTogglePrivate},model:{value:t.showPrivate,callback:function(e){t.showPrivate=e},expression:"showPrivate"}},[t._v("\n "+t._s(t.$t("userProfile.showPrivateStatuses"))+"\n ")])],1)],1),t._v(" "),s("el-col",{attrs:{span:18}},[s("el-timeline",{staticClass:"statuses"},t._l(t.statuses,function(e){return s("el-timeline-item",{key:e.id,attrs:{timestamp:t.createdAtLocaleString(e.created_at)}},[s("el-card",[e.spoiler_text?s("strong",[t._v(t._s(e.spoiler_text))]):t._e(),t._v(" "),e.content?s("p",{domProps:{innerHTML:t._s(e.content)}}):t._e(),t._v(" "),e.poll?s("div",{staticClass:"poll"},[s("ul",t._l(e.poll.options,function(a,r){return s("li",{key:r},[t._v("\n "+t._s(a.title)+"\n "),s("el-progress",{attrs:{percentage:t.optionPercent(e.poll,a)}})],1)}),0)]):t._e(),t._v(" "),t._l(e.media_attachments,function(t,e){return s("div",{key:e,staticClass:"image"},[s("img",{attrs:{src:t.preview_url}})])})],2)],1)}),1)],1)],1)],1)},[],!1,null,"71c7ded0",null);l.options.__file="show.vue";e.default=l.exports},QG2t:function(t,e,s){"use strict";var a=s("R7Mx");s.n(a).a},R7Mx:function(t,e,s){}}]);
|
1
priv/static/adminfe/static/js/chunk-1fbf.616fb309.js
Normal file
1
priv/static/adminfe/static/js/chunk-1fbf.616fb309.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
priv/static/adminfe/static/js/chunk-5e57.7313703a.js
Normal file
1
priv/static/adminfe/static/js/chunk-5e57.7313703a.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
priv/static/adminfe/static/js/chunk-7fe2.458f9da5.js
Normal file
1
priv/static/adminfe/static/js/chunk-7fe2.458f9da5.js
Normal file
File diff suppressed because one or more lines are too long
1
priv/static/adminfe/static/js/chunk-e547.d57d1b91.js
Normal file
1
priv/static/adminfe/static/js/chunk-e547.d57d1b91.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1 +0,0 @@
|
|||
!function(e){function t(t){for(var r,o,a=t[0],i=t[1],f=t[2],l=0,h=[];l<a.length;l++)o=a[l],u[o]&&h.push(u[o][0]),u[o]=0;for(r in i)Object.prototype.hasOwnProperty.call(i,r)&&(e[r]=i[r]);for(s&&s(t);h.length;)h.shift()();return c.push.apply(c,f||[]),n()}function n(){for(var e,t=0;t<c.length;t++){for(var n=c[t],r=!0,o=1;o<n.length;o++){var i=n[o];0!==u[i]&&(r=!1)}r&&(c.splice(t--,1),e=a(a.s=n[0]))}return e}var r={},o={runtime:0},u={runtime:0},c=[];function a(t){if(r[t])return r[t].exports;var n=r[t]={i:t,l:!1,exports:{}};return e[t].call(n.exports,n,n.exports,a),n.l=!0,n.exports}a.e=function(e){var t=[];o[e]?t.push(o[e]):0!==o[e]&&{"chunk-56c9":1,"chunk-5eaf":1,"chunk-18e1":1,"chunk-8b70":1,"chunk-f018":1}[e]&&t.push(o[e]=new Promise(function(t,n){for(var r=({}[e]||e)+"."+{"7zzA":"31d6cfe0",JEtC:"31d6cfe0","chunk-02a0":"31d6cfe0","chunk-56c9":"c27dac5e","chunk-0620":"31d6cfe0","chunk-5eaf":"1a04e979","chunk-18e1":"6aaab273","chunk-8b70":"9ba0945c","chunk-f018":"0d22684d"}[e]+".css",o=a.p+r,u=document.getElementsByTagName("link"),c=0;c<u.length;c++){var i=(l=u[c]).getAttribute("data-href")||l.getAttribute("href");if("stylesheet"===l.rel&&(i===r||i===o))return t()}var f=document.getElementsByTagName("style");for(c=0;c<f.length;c++){var l;if((i=(l=f[c]).getAttribute("data-href"))===r||i===o)return t()}var s=document.createElement("link");s.rel="stylesheet",s.type="text/css",s.onload=t,s.onerror=function(t){var r=t&&t.target&&t.target.src||o,u=new Error("Loading CSS chunk "+e+" failed.\n("+r+")");u.request=r,n(u)},s.href=o,document.getElementsByTagName("head")[0].appendChild(s)}).then(function(){o[e]=0}));var n=u[e];if(0!==n)if(n)t.push(n[2]);else{var r=new Promise(function(t,r){n=u[e]=[t,r]});t.push(n[2]=r);var c,i=document.createElement("script");i.charset="utf-8",i.timeout=120,a.nc&&i.setAttribute("nonce",a.nc),i.src=function(e){return a.p+"static/js/"+({}[e]||e)+"."+{"7zzA":"e1ae1c94",JEtC:"f9ba4594","chunk-02a0":"db6ec114","chunk-56c9":"28e35fc3","chunk-0620":"c765c190","chunk-5eaf":"5b76e416","chunk-18e1":"7f9c377c","chunk-8b70":"46525646","chunk-f018":"e1a7a454"}[e]+".js"}(e),c=function(t){i.onerror=i.onload=null,clearTimeout(f);var n=u[e];if(0!==n){if(n){var r=t&&("load"===t.type?"missing":t.type),o=t&&t.target&&t.target.src,c=new Error("Loading chunk "+e+" failed.\n("+r+": "+o+")");c.type=r,c.request=o,n[1](c)}u[e]=void 0}};var f=setTimeout(function(){c({type:"timeout",target:i})},12e4);i.onerror=i.onload=c,document.head.appendChild(i)}return Promise.all(t)},a.m=e,a.c=r,a.d=function(e,t,n){a.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,t){if(1&t&&(e=a(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(a.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)a.d(n,r,function(t){return e[t]}.bind(null,r));return n},a.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(t,"a",t),t},a.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},a.p="",a.oe=function(e){throw console.error(e),e};var i=window.webpackJsonp=window.webpackJsonp||[],f=i.push.bind(i);i.push=t,i=i.slice();for(var l=0;l<i.length;l++)t(i[l]);var s=f;n()}([]);
|
1
priv/static/adminfe/static/js/runtime.f40c8ec4.js
Normal file
1
priv/static/adminfe/static/js/runtime.f40c8ec4.js
Normal file
|
@ -0,0 +1 @@
|
|||
!function(e){function n(n){for(var r,u,a=n[0],f=n[1],i=n[2],l=0,s=[];l<a.length;l++)u=a[l],c[u]&&s.push(c[u][0]),c[u]=0;for(r in f)Object.prototype.hasOwnProperty.call(f,r)&&(e[r]=f[r]);for(h&&h(n);s.length;)s.shift()();return o.push.apply(o,i||[]),t()}function t(){for(var e,n=0;n<o.length;n++){for(var t=o[n],r=!0,u=1;u<t.length;u++){var f=t[u];0!==c[f]&&(r=!1)}r&&(o.splice(n--,1),e=a(a.s=t[0]))}return e}var r={},u={runtime:0},c={runtime:0},o=[];function a(n){if(r[n])return r[n].exports;var t=r[n]={i:n,l:!1,exports:{}};return e[n].call(t.exports,t,t.exports,a),t.l=!0,t.exports}a.e=function(e){var n=[];u[e]?n.push(u[e]):0!==u[e]&&{"chunk-1fbf":1,"chunk-e547":1,"chunk-0e18":1,"chunk-18e1":1,"chunk-2325":1,"chunk-5e57":1,"chunk-8b70":1}[e]&&n.push(u[e]=new Promise(function(n,t){for(var r=({}[e]||e)+"."+{"7zzA":"31d6cfe0",JEtC:"31d6cfe0","chunk-02a0":"31d6cfe0","chunk-1fbf":"d7a1893c","chunk-0620":"31d6cfe0","chunk-e547":"e4b6230b","chunk-0e18":"e12401fb","chunk-18e1":"6aaab273","chunk-2325":"0d22684d","chunk-7fe2":"31d6cfe0","chunk-5e57":"ac97b15a","chunk-8b70":"9ba0945c"}[e]+".css",u=a.p+r,c=document.getElementsByTagName("link"),o=0;o<c.length;o++){var f=(l=c[o]).getAttribute("data-href")||l.getAttribute("href");if("stylesheet"===l.rel&&(f===r||f===u))return n()}var i=document.getElementsByTagName("style");for(o=0;o<i.length;o++){var l;if((f=(l=i[o]).getAttribute("data-href"))===r||f===u)return n()}var h=document.createElement("link");h.rel="stylesheet",h.type="text/css",h.onload=n,h.onerror=function(n){var r=n&&n.target&&n.target.src||u,c=new Error("Loading CSS chunk "+e+" failed.\n("+r+")");c.request=r,t(c)},h.href=u,document.getElementsByTagName("head")[0].appendChild(h)}).then(function(){u[e]=0}));var t=c[e];if(0!==t)if(t)n.push(t[2]);else{var r=new Promise(function(n,r){t=c[e]=[n,r]});n.push(t[2]=r);var o,f=document.createElement("script");f.charset="utf-8",f.timeout=120,a.nc&&f.setAttribute("nonce",a.nc),f.src=function(e){return a.p+"static/js/"+({}[e]||e)+"."+{"7zzA":"e1ae1c94",JEtC:"f9ba4594","chunk-02a0":"db6ec114","chunk-1fbf":"616fb309","chunk-0620":"c765c190","chunk-e547":"d57d1b91","chunk-0e18":"208cd826","chunk-18e1":"7f9c377c","chunk-2325":"154a537b","chunk-7fe2":"458f9da5","chunk-5e57":"7313703a","chunk-8b70":"46525646"}[e]+".js"}(e),o=function(n){f.onerror=f.onload=null,clearTimeout(i);var t=c[e];if(0!==t){if(t){var r=n&&("load"===n.type?"missing":n.type),u=n&&n.target&&n.target.src,o=new Error("Loading chunk "+e+" failed.\n("+r+": "+u+")");o.type=r,o.request=u,t[1](o)}c[e]=void 0}};var i=setTimeout(function(){o({type:"timeout",target:f})},12e4);f.onerror=f.onload=o,document.head.appendChild(f)}return Promise.all(n)},a.m=e,a.c=r,a.d=function(e,n,t){a.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:t})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,n){if(1&n&&(e=a(e)),8&n)return e;if(4&n&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(a.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var r in e)a.d(t,r,function(n){return e[n]}.bind(null,r));return t},a.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(n,"a",n),n},a.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},a.p="",a.oe=function(e){throw console.error(e),e};var f=window.webpackJsonp=window.webpackJsonp||[],i=f.push.bind(f);f.push=n,f=f.slice();for(var l=0;l<f.length;l++)n(f[l]);var h=i;t()}([]);
|
|
@ -68,3 +68,5 @@ config :pleroma, Pleroma.Uploaders.Local, uploads: "<%= uploads_dir %>"
|
|||
# For using third-party S3 clones like wasabi, also do:
|
||||
# config :ex_aws, :s3,
|
||||
# host: "s3.wasabisys.com"
|
||||
|
||||
config :joken, default_signer: "<%= jwt_secret %>"
|
||||
|
|
51
test/mix/tasks/pleroma.digest_test.exs
Normal file
51
test/mix/tasks/pleroma.digest_test.exs
Normal file
|
@ -0,0 +1,51 @@
|
|||
defmodule Mix.Tasks.Pleroma.DigestTest do
|
||||
use Pleroma.DataCase
|
||||
|
||||
import Pleroma.Factory
|
||||
import Swoosh.TestAssertions
|
||||
|
||||
alias Pleroma.Web.CommonAPI
|
||||
|
||||
setup_all do
|
||||
Mix.shell(Mix.Shell.Process)
|
||||
|
||||
on_exit(fn ->
|
||||
Mix.shell(Mix.Shell.IO)
|
||||
end)
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
describe "pleroma.digest test" do
|
||||
test "Sends digest to the given user" do
|
||||
user1 = insert(:user)
|
||||
user2 = insert(:user)
|
||||
|
||||
Enum.each(0..10, fn i ->
|
||||
{:ok, _activity} =
|
||||
CommonAPI.post(user1, %{
|
||||
"status" => "hey ##{i} @#{user2.nickname}!"
|
||||
})
|
||||
end)
|
||||
|
||||
yesterday =
|
||||
NaiveDateTime.add(
|
||||
NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second),
|
||||
-60 * 60 * 24,
|
||||
:second
|
||||
)
|
||||
|
||||
{:ok, yesterday_date} = Timex.format(yesterday, "%F", :strftime)
|
||||
|
||||
:ok = Mix.Tasks.Pleroma.Digest.run(["test", user2.nickname, yesterday_date])
|
||||
|
||||
assert_receive {:mix_shell, :info, [message]}
|
||||
assert message =~ "Digest email have been sent"
|
||||
|
||||
assert_email_sent(
|
||||
to: {user2.name, user2.email},
|
||||
html_body: ~r/new mentions:/i
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -4,13 +4,15 @@
|
|||
|
||||
defmodule Pleroma.NotificationTest do
|
||||
use Pleroma.DataCase
|
||||
|
||||
import Pleroma.Factory
|
||||
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.Streamer
|
||||
alias Pleroma.Web.TwitterAPI.TwitterAPI
|
||||
import Pleroma.Factory
|
||||
|
||||
describe "create_notifications" do
|
||||
test "notifies someone when they are directly addressed" do
|
||||
|
@ -352,6 +354,51 @@ test "it sets all notifications as read up to a specified notification ID" do
|
|||
end
|
||||
end
|
||||
|
||||
describe "for_user_since/2" do
|
||||
defp days_ago(days) do
|
||||
NaiveDateTime.add(
|
||||
NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second),
|
||||
-days * 60 * 60 * 24,
|
||||
:second
|
||||
)
|
||||
end
|
||||
|
||||
test "Returns recent notifications" do
|
||||
user1 = insert(:user)
|
||||
user2 = insert(:user)
|
||||
|
||||
Enum.each(0..10, fn i ->
|
||||
{:ok, _activity} =
|
||||
CommonAPI.post(user1, %{
|
||||
"status" => "hey ##{i} @#{user2.nickname}!"
|
||||
})
|
||||
end)
|
||||
|
||||
{old, new} = Enum.split(Notification.for_user(user2), 5)
|
||||
|
||||
Enum.each(old, fn notification ->
|
||||
notification
|
||||
|> cast(%{updated_at: days_ago(10)}, [:updated_at])
|
||||
|> Pleroma.Repo.update!()
|
||||
end)
|
||||
|
||||
recent_notifications_ids =
|
||||
user2
|
||||
|> Notification.for_user_since(
|
||||
NaiveDateTime.add(NaiveDateTime.utc_now(), -5 * 86_400, :second)
|
||||
)
|
||||
|> Enum.map(& &1.id)
|
||||
|
||||
Enum.each(old, fn %{id: id} ->
|
||||
refute id in recent_notifications_ids
|
||||
end)
|
||||
|
||||
Enum.each(new, fn %{id: id} ->
|
||||
assert id in recent_notifications_ids
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
describe "notification target determination" do
|
||||
test "it sends notifications to addressed users in new messages" do
|
||||
user = insert(:user)
|
||||
|
|
|
@ -9,7 +9,8 @@ def build(data \\ %{}) do
|
|||
nickname: "testname",
|
||||
password_hash: Comeonin.Pbkdf2.hashpwsalt("test"),
|
||||
bio: "A tester.",
|
||||
ap_id: "some id"
|
||||
ap_id: "some id",
|
||||
last_digest_emailed_at: NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
|
||||
}
|
||||
|
||||
Map.merge(user, data)
|
||||
|
|
|
@ -31,7 +31,8 @@ def user_factory do
|
|||
nickname: sequence(:nickname, &"nick#{&1}"),
|
||||
password_hash: Comeonin.Pbkdf2.hashpwsalt("test"),
|
||||
bio: sequence(:bio, &"Tester Number #{&1}"),
|
||||
info: %{}
|
||||
info: %{},
|
||||
last_digest_emailed_at: NaiveDateTime.utc_now()
|
||||
}
|
||||
|
||||
%{
|
||||
|
|
13
test/support/mrf_module_mock.ex
Normal file
13
test/support/mrf_module_mock.ex
Normal file
|
@ -0,0 +1,13 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule MRFModuleMock do
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||
|
||||
@impl true
|
||||
def filter(message), do: {:ok, message}
|
||||
|
||||
@impl true
|
||||
def describe, do: {:ok, %{mrf_module_mock: "some config data"}}
|
||||
end
|
|
@ -3,8 +3,11 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Mix.Tasks.Pleroma.DatabaseTest do
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.CommonAPI
|
||||
|
||||
use Pleroma.DataCase
|
||||
|
||||
import Pleroma.Factory
|
||||
|
@ -46,4 +49,37 @@ test "following and followers count are updated" do
|
|||
assert user.info.follower_count == 0
|
||||
end
|
||||
end
|
||||
|
||||
describe "running fix_likes_collections" do
|
||||
test "it turns OrderedCollection likes into empty arrays" do
|
||||
[user, user2] = insert_pair(:user)
|
||||
|
||||
{:ok, %{id: id, object: object}} = CommonAPI.post(user, %{"status" => "test"})
|
||||
{:ok, %{object: object2}} = CommonAPI.post(user, %{"status" => "test test"})
|
||||
|
||||
CommonAPI.favorite(id, user2)
|
||||
|
||||
likes = %{
|
||||
"first" =>
|
||||
"http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes?page=1",
|
||||
"id" => "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes",
|
||||
"totalItems" => 3,
|
||||
"type" => "OrderedCollection"
|
||||
}
|
||||
|
||||
new_data = Map.put(object2.data, "likes", likes)
|
||||
|
||||
object2
|
||||
|> Ecto.Changeset.change(%{data: new_data})
|
||||
|> Repo.update()
|
||||
|
||||
assert length(Object.get_by_id(object.id).data["likes"]) == 1
|
||||
assert is_map(Object.get_by_id(object2.id).data["likes"])
|
||||
|
||||
assert :ok == Mix.Tasks.Pleroma.Database.run(["fix_likes_collections"])
|
||||
|
||||
assert length(Object.get_by_id(object.id).data["likes"]) == 1
|
||||
assert Enum.empty?(Object.get_by_id(object2.id).data["likes"])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
32
test/uploaders/local_test.exs
Normal file
32
test/uploaders/local_test.exs
Normal file
|
@ -0,0 +1,32 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Uploaders.LocalTest do
|
||||
use Pleroma.DataCase
|
||||
alias Pleroma.Uploaders.Local
|
||||
|
||||
describe "get_file/1" do
|
||||
test "it returns path to local folder for files" do
|
||||
assert Local.get_file("") == {:ok, {:static_dir, "test/uploads"}}
|
||||
end
|
||||
end
|
||||
|
||||
describe "put_file/1" do
|
||||
test "put file to local folder" do
|
||||
file_path = "local_upload/files/image.jpg"
|
||||
|
||||
file = %Pleroma.Upload{
|
||||
name: "image.jpg",
|
||||
content_type: "image/jpg",
|
||||
path: file_path,
|
||||
tempfile: Path.absname("test/fixtures/image_tmp.jpg")
|
||||
}
|
||||
|
||||
assert Local.put_file(file) == :ok
|
||||
|
||||
assert Path.join([Local.upload_path(), file_path])
|
||||
|> File.exists?()
|
||||
end
|
||||
end
|
||||
end
|
50
test/uploaders/mdii_test.exs
Normal file
50
test/uploaders/mdii_test.exs
Normal file
|
@ -0,0 +1,50 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Uploaders.MDIITest do
|
||||
use Pleroma.DataCase
|
||||
alias Pleroma.Uploaders.MDII
|
||||
import Tesla.Mock
|
||||
|
||||
describe "get_file/1" do
|
||||
test "it returns path to local folder for files" do
|
||||
assert MDII.get_file("") == {:ok, {:static_dir, "test/uploads"}}
|
||||
end
|
||||
end
|
||||
|
||||
describe "put_file/1" do
|
||||
setup do
|
||||
file_upload = %Pleroma.Upload{
|
||||
name: "mdii-image.jpg",
|
||||
content_type: "image/jpg",
|
||||
path: "test_folder/mdii-image.jpg",
|
||||
tempfile: Path.absname("test/fixtures/image_tmp.jpg")
|
||||
}
|
||||
|
||||
[file_upload: file_upload]
|
||||
end
|
||||
|
||||
test "save file", %{file_upload: file_upload} do
|
||||
mock(fn
|
||||
%{method: :post, url: "https://mdii.sakura.ne.jp/mdii-post.cgi?jpg"} ->
|
||||
%Tesla.Env{status: 200, body: "mdii-image"}
|
||||
end)
|
||||
|
||||
assert MDII.put_file(file_upload) ==
|
||||
{:ok, {:url, "https://mdii.sakura.ne.jp/mdii-image.jpg"}}
|
||||
end
|
||||
|
||||
test "save file to local if MDII isn`t available", %{file_upload: file_upload} do
|
||||
mock(fn
|
||||
%{method: :post, url: "https://mdii.sakura.ne.jp/mdii-post.cgi?jpg"} ->
|
||||
%Tesla.Env{status: 500}
|
||||
end)
|
||||
|
||||
assert MDII.put_file(file_upload) == :ok
|
||||
|
||||
assert Path.join([Pleroma.Uploaders.Local.upload_path(), file_upload.path])
|
||||
|> File.exists?()
|
||||
end
|
||||
end
|
||||
end
|
90
test/uploaders/s3_test.exs
Normal file
90
test/uploaders/s3_test.exs
Normal file
|
@ -0,0 +1,90 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Uploaders.S3Test do
|
||||
use Pleroma.DataCase
|
||||
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Uploaders.S3
|
||||
|
||||
import Mock
|
||||
import ExUnit.CaptureLog
|
||||
|
||||
setup do
|
||||
config = Config.get([Pleroma.Uploaders.S3])
|
||||
|
||||
Config.put([Pleroma.Uploaders.S3],
|
||||
bucket: "test_bucket",
|
||||
public_endpoint: "https://s3.amazonaws.com"
|
||||
)
|
||||
|
||||
on_exit(fn ->
|
||||
Config.put([Pleroma.Uploaders.S3], config)
|
||||
end)
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
describe "get_file/1" do
|
||||
test "it returns path to local folder for files" do
|
||||
assert S3.get_file("test_image.jpg") == {
|
||||
:ok,
|
||||
{:url, "https://s3.amazonaws.com/test_bucket/test_image.jpg"}
|
||||
}
|
||||
end
|
||||
|
||||
test "it returns path without bucket when truncated_namespace set to ''" do
|
||||
Config.put([Pleroma.Uploaders.S3],
|
||||
bucket: "test_bucket",
|
||||
public_endpoint: "https://s3.amazonaws.com",
|
||||
truncated_namespace: ""
|
||||
)
|
||||
|
||||
assert S3.get_file("test_image.jpg") == {
|
||||
:ok,
|
||||
{:url, "https://s3.amazonaws.com/test_image.jpg"}
|
||||
}
|
||||
end
|
||||
|
||||
test "it returns path with bucket namespace when namespace is set" do
|
||||
Config.put([Pleroma.Uploaders.S3],
|
||||
bucket: "test_bucket",
|
||||
public_endpoint: "https://s3.amazonaws.com",
|
||||
bucket_namespace: "family"
|
||||
)
|
||||
|
||||
assert S3.get_file("test_image.jpg") == {
|
||||
:ok,
|
||||
{:url, "https://s3.amazonaws.com/family:test_bucket/test_image.jpg"}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
describe "put_file/1" do
|
||||
setup do
|
||||
file_upload = %Pleroma.Upload{
|
||||
name: "image-tet.jpg",
|
||||
content_type: "image/jpg",
|
||||
path: "test_folder/image-tet.jpg",
|
||||
tempfile: Path.absname("test/fixtures/image_tmp.jpg")
|
||||
}
|
||||
|
||||
[file_upload: file_upload]
|
||||
end
|
||||
|
||||
test "save file", %{file_upload: file_upload} do
|
||||
with_mock ExAws, request: fn _ -> {:ok, :ok} end do
|
||||
assert S3.put_file(file_upload) == {:ok, {:file, "test_folder/image-tet.jpg"}}
|
||||
end
|
||||
end
|
||||
|
||||
test "returns error", %{file_upload: file_upload} do
|
||||
with_mock ExAws, request: fn _ -> {:error, "S3 Upload failed"} end do
|
||||
assert capture_log(fn ->
|
||||
assert S3.put_file(file_upload) == {:error, "S3 Upload failed"}
|
||||
end) =~ "Elixir.Pleroma.Uploaders.S3: {:error, \"S3 Upload failed\"}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
24
test/user_info_test.exs
Normal file
24
test/user_info_test.exs
Normal file
|
@ -0,0 +1,24 @@
|
|||
defmodule Pleroma.UserInfoTest do
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User.Info
|
||||
|
||||
use Pleroma.DataCase
|
||||
|
||||
import Pleroma.Factory
|
||||
|
||||
describe "update_email_notifications/2" do
|
||||
setup do
|
||||
user = insert(:user, %{info: %{email_notifications: %{"digest" => true}}})
|
||||
|
||||
{:ok, user: user}
|
||||
end
|
||||
|
||||
test "Notifications are updated", %{user: user} do
|
||||
true = user.info.email_notifications["digest"]
|
||||
changeset = Info.update_email_notifications(user.info, %{"digest" => false})
|
||||
assert changeset.valid?
|
||||
{:ok, result} = Ecto.Changeset.apply_action(changeset, :insert)
|
||||
assert result.email_notifications["digest"] == false
|
||||
end
|
||||
end
|
||||
end
|
|
@ -193,7 +193,14 @@ test "works with URIs" do
|
|||
user = User.get_cached_by_ap_id("http://mastodon.example.org/users/admin")
|
||||
|
||||
assert length(results) == 1
|
||||
assert user == result |> Map.put(:search_rank, nil) |> Map.put(:search_type, nil)
|
||||
|
||||
expected =
|
||||
result
|
||||
|> Map.put(:search_rank, nil)
|
||||
|> Map.put(:search_type, nil)
|
||||
|> Map.put(:last_digest_emailed_at, nil)
|
||||
|
||||
assert user == expected
|
||||
end
|
||||
|
||||
test "excludes a blocked users from search result" do
|
||||
|
|
|
@ -525,7 +525,10 @@ test "it has required fields" do
|
|||
end
|
||||
|
||||
test "it restricts some sizes" do
|
||||
[bio: 5000, name: 100]
|
||||
bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
|
||||
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
|
||||
|
||||
[bio: bio_limit, name: name_limit]
|
||||
|> Enum.each(fn {field, size} ->
|
||||
string = String.pad_leading(".", size)
|
||||
cs = User.remote_user_creation(Map.put(@valid_remote, field, string))
|
||||
|
@ -1237,6 +1240,109 @@ test "follower count is updated when a follower is blocked" do
|
|||
assert Map.get(user_show, "followers_count") == 2
|
||||
end
|
||||
|
||||
describe "list_inactive_users_query/1" do
|
||||
defp days_ago(days) do
|
||||
NaiveDateTime.add(
|
||||
NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second),
|
||||
-days * 60 * 60 * 24,
|
||||
:second
|
||||
)
|
||||
end
|
||||
|
||||
test "Users are inactive by default" do
|
||||
total = 10
|
||||
|
||||
users =
|
||||
Enum.map(1..total, fn _ ->
|
||||
insert(:user, last_digest_emailed_at: days_ago(20), info: %{deactivated: false})
|
||||
end)
|
||||
|
||||
inactive_users_ids =
|
||||
Pleroma.User.list_inactive_users_query()
|
||||
|> Pleroma.Repo.all()
|
||||
|> Enum.map(& &1.id)
|
||||
|
||||
Enum.each(users, fn user ->
|
||||
assert user.id in inactive_users_ids
|
||||
end)
|
||||
end
|
||||
|
||||
test "Only includes users who has no recent activity" do
|
||||
total = 10
|
||||
|
||||
users =
|
||||
Enum.map(1..total, fn _ ->
|
||||
insert(:user, last_digest_emailed_at: days_ago(20), info: %{deactivated: false})
|
||||
end)
|
||||
|
||||
{inactive, active} = Enum.split(users, trunc(total / 2))
|
||||
|
||||
Enum.map(active, fn user ->
|
||||
to = Enum.random(users -- [user])
|
||||
|
||||
{:ok, _} =
|
||||
Pleroma.Web.TwitterAPI.TwitterAPI.create_status(user, %{
|
||||
"status" => "hey @#{to.nickname}"
|
||||
})
|
||||
end)
|
||||
|
||||
inactive_users_ids =
|
||||
Pleroma.User.list_inactive_users_query()
|
||||
|> Pleroma.Repo.all()
|
||||
|> Enum.map(& &1.id)
|
||||
|
||||
Enum.each(active, fn user ->
|
||||
refute user.id in inactive_users_ids
|
||||
end)
|
||||
|
||||
Enum.each(inactive, fn user ->
|
||||
assert user.id in inactive_users_ids
|
||||
end)
|
||||
end
|
||||
|
||||
test "Only includes users with no read notifications" do
|
||||
total = 10
|
||||
|
||||
users =
|
||||
Enum.map(1..total, fn _ ->
|
||||
insert(:user, last_digest_emailed_at: days_ago(20), info: %{deactivated: false})
|
||||
end)
|
||||
|
||||
[sender | recipients] = users
|
||||
{inactive, active} = Enum.split(recipients, trunc(total / 2))
|
||||
|
||||
Enum.each(recipients, fn to ->
|
||||
{:ok, _} =
|
||||
Pleroma.Web.TwitterAPI.TwitterAPI.create_status(sender, %{
|
||||
"status" => "hey @#{to.nickname}"
|
||||
})
|
||||
|
||||
{:ok, _} =
|
||||
Pleroma.Web.TwitterAPI.TwitterAPI.create_status(sender, %{
|
||||
"status" => "hey again @#{to.nickname}"
|
||||
})
|
||||
end)
|
||||
|
||||
Enum.each(active, fn user ->
|
||||
[n1, _n2] = Pleroma.Notification.for_user(user)
|
||||
{:ok, _} = Pleroma.Notification.read_one(user, n1.id)
|
||||
end)
|
||||
|
||||
inactive_users_ids =
|
||||
Pleroma.User.list_inactive_users_query()
|
||||
|> Pleroma.Repo.all()
|
||||
|> Enum.map(& &1.id)
|
||||
|
||||
Enum.each(active, fn user ->
|
||||
refute user.id in inactive_users_ids
|
||||
end)
|
||||
|
||||
Enum.each(inactive, fn user ->
|
||||
assert user.id in inactive_users_ids
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
describe "toggle_confirmation/1" do
|
||||
test "if user is confirmed" do
|
||||
user = insert(:user, info: %{confirmation_pending: false})
|
||||
|
|
|
@ -4,8 +4,8 @@ defmodule Pleroma.Web.ActivityPub.MRFTest do
|
|||
|
||||
test "subdomains_regex/1" do
|
||||
assert MRF.subdomains_regex(["unsafe.tld", "*.unsafe.tld"]) == [
|
||||
~r/^unsafe.tld$/,
|
||||
~r/^(.*\.)*unsafe.tld$/
|
||||
~r/^unsafe.tld$/i,
|
||||
~r/^(.*\.)*unsafe.tld$/i
|
||||
]
|
||||
end
|
||||
|
||||
|
@ -13,7 +13,7 @@ test "subdomains_regex/1" do
|
|||
test "common domains" do
|
||||
regexes = MRF.subdomains_regex(["unsafe.tld", "unsafe2.tld"])
|
||||
|
||||
assert regexes == [~r/^unsafe.tld$/, ~r/^unsafe2.tld$/]
|
||||
assert regexes == [~r/^unsafe.tld$/i, ~r/^unsafe2.tld$/i]
|
||||
|
||||
assert MRF.subdomain_match?(regexes, "unsafe.tld")
|
||||
assert MRF.subdomain_match?(regexes, "unsafe2.tld")
|
||||
|
@ -24,7 +24,7 @@ test "common domains" do
|
|||
test "wildcard domains with one subdomain" do
|
||||
regexes = MRF.subdomains_regex(["*.unsafe.tld"])
|
||||
|
||||
assert regexes == [~r/^(.*\.)*unsafe.tld$/]
|
||||
assert regexes == [~r/^(.*\.)*unsafe.tld$/i]
|
||||
|
||||
assert MRF.subdomain_match?(regexes, "unsafe.tld")
|
||||
assert MRF.subdomain_match?(regexes, "sub.unsafe.tld")
|
||||
|
@ -35,12 +35,52 @@ test "wildcard domains with one subdomain" do
|
|||
test "wildcard domains with two subdomains" do
|
||||
regexes = MRF.subdomains_regex(["*.unsafe.tld"])
|
||||
|
||||
assert regexes == [~r/^(.*\.)*unsafe.tld$/]
|
||||
assert regexes == [~r/^(.*\.)*unsafe.tld$/i]
|
||||
|
||||
assert MRF.subdomain_match?(regexes, "unsafe.tld")
|
||||
assert MRF.subdomain_match?(regexes, "sub.sub.unsafe.tld")
|
||||
refute MRF.subdomain_match?(regexes, "sub.anotherunsafe.tld")
|
||||
refute MRF.subdomain_match?(regexes, "sub.unsafe.tldanother")
|
||||
end
|
||||
|
||||
test "matches are case-insensitive" do
|
||||
regexes = MRF.subdomains_regex(["UnSafe.TLD", "UnSAFE2.Tld"])
|
||||
|
||||
assert regexes == [~r/^UnSafe.TLD$/i, ~r/^UnSAFE2.Tld$/i]
|
||||
|
||||
assert MRF.subdomain_match?(regexes, "UNSAFE.TLD")
|
||||
assert MRF.subdomain_match?(regexes, "UNSAFE2.TLD")
|
||||
assert MRF.subdomain_match?(regexes, "unsafe.tld")
|
||||
assert MRF.subdomain_match?(regexes, "unsafe2.tld")
|
||||
|
||||
refute MRF.subdomain_match?(regexes, "EXAMPLE.COM")
|
||||
refute MRF.subdomain_match?(regexes, "example.com")
|
||||
end
|
||||
end
|
||||
|
||||
describe "describe/0" do
|
||||
test "it works as expected with noop policy" do
|
||||
expected = %{
|
||||
mrf_policies: ["NoOpPolicy"],
|
||||
exclusions: false
|
||||
}
|
||||
|
||||
{:ok, ^expected} = MRF.describe()
|
||||
end
|
||||
|
||||
test "it works as expected with mock policy" do
|
||||
config = Pleroma.Config.get([:instance, :rewrite_policy])
|
||||
Pleroma.Config.put([:instance, :rewrite_policy], [MRFModuleMock])
|
||||
|
||||
expected = %{
|
||||
mrf_policies: ["MRFModuleMock"],
|
||||
mrf_module_mock: "some config data",
|
||||
exclusions: false
|
||||
}
|
||||
|
||||
{:ok, ^expected} = MRF.describe()
|
||||
|
||||
Pleroma.Config.put([:instance, :rewrite_policy], config)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
123
test/web/activity_pub/mrf/vocabulary_policy_test.exs
Normal file
123
test/web/activity_pub/mrf/vocabulary_policy_test.exs
Normal file
|
@ -0,0 +1,123 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicyTest do
|
||||
use Pleroma.DataCase
|
||||
|
||||
alias Pleroma.Web.ActivityPub.MRF.VocabularyPolicy
|
||||
|
||||
describe "accept" do
|
||||
test "it accepts based on parent activity type" do
|
||||
config = Pleroma.Config.get([:mrf_vocabulary, :accept])
|
||||
Pleroma.Config.put([:mrf_vocabulary, :accept], ["Like"])
|
||||
|
||||
message = %{
|
||||
"type" => "Like",
|
||||
"object" => "whatever"
|
||||
}
|
||||
|
||||
{:ok, ^message} = VocabularyPolicy.filter(message)
|
||||
|
||||
Pleroma.Config.put([:mrf_vocabulary, :accept], config)
|
||||
end
|
||||
|
||||
test "it accepts based on child object type" do
|
||||
config = Pleroma.Config.get([:mrf_vocabulary, :accept])
|
||||
Pleroma.Config.put([:mrf_vocabulary, :accept], ["Create", "Note"])
|
||||
|
||||
message = %{
|
||||
"type" => "Create",
|
||||
"object" => %{
|
||||
"type" => "Note",
|
||||
"content" => "whatever"
|
||||
}
|
||||
}
|
||||
|
||||
{:ok, ^message} = VocabularyPolicy.filter(message)
|
||||
|
||||
Pleroma.Config.put([:mrf_vocabulary, :accept], config)
|
||||
end
|
||||
|
||||
test "it does not accept disallowed child objects" do
|
||||
config = Pleroma.Config.get([:mrf_vocabulary, :accept])
|
||||
Pleroma.Config.put([:mrf_vocabulary, :accept], ["Create", "Note"])
|
||||
|
||||
message = %{
|
||||
"type" => "Create",
|
||||
"object" => %{
|
||||
"type" => "Article",
|
||||
"content" => "whatever"
|
||||
}
|
||||
}
|
||||
|
||||
{:reject, nil} = VocabularyPolicy.filter(message)
|
||||
|
||||
Pleroma.Config.put([:mrf_vocabulary, :accept], config)
|
||||
end
|
||||
|
||||
test "it does not accept disallowed parent types" do
|
||||
config = Pleroma.Config.get([:mrf_vocabulary, :accept])
|
||||
Pleroma.Config.put([:mrf_vocabulary, :accept], ["Announce", "Note"])
|
||||
|
||||
message = %{
|
||||
"type" => "Create",
|
||||
"object" => %{
|
||||
"type" => "Note",
|
||||
"content" => "whatever"
|
||||
}
|
||||
}
|
||||
|
||||
{:reject, nil} = VocabularyPolicy.filter(message)
|
||||
|
||||
Pleroma.Config.put([:mrf_vocabulary, :accept], config)
|
||||
end
|
||||
end
|
||||
|
||||
describe "reject" do
|
||||
test "it rejects based on parent activity type" do
|
||||
config = Pleroma.Config.get([:mrf_vocabulary, :reject])
|
||||
Pleroma.Config.put([:mrf_vocabulary, :reject], ["Like"])
|
||||
|
||||
message = %{
|
||||
"type" => "Like",
|
||||
"object" => "whatever"
|
||||
}
|
||||
|
||||
{:reject, nil} = VocabularyPolicy.filter(message)
|
||||
|
||||
Pleroma.Config.put([:mrf_vocabulary, :reject], config)
|
||||
end
|
||||
|
||||
test "it rejects based on child object type" do
|
||||
config = Pleroma.Config.get([:mrf_vocabulary, :reject])
|
||||
Pleroma.Config.put([:mrf_vocabulary, :reject], ["Note"])
|
||||
|
||||
message = %{
|
||||
"type" => "Create",
|
||||
"object" => %{
|
||||
"type" => "Note",
|
||||
"content" => "whatever"
|
||||
}
|
||||
}
|
||||
|
||||
{:reject, nil} = VocabularyPolicy.filter(message)
|
||||
|
||||
Pleroma.Config.put([:mrf_vocabulary, :reject], config)
|
||||
end
|
||||
|
||||
test "it passes through objects that aren't disallowed" do
|
||||
config = Pleroma.Config.get([:mrf_vocabulary, :reject])
|
||||
Pleroma.Config.put([:mrf_vocabulary, :reject], ["Like"])
|
||||
|
||||
message = %{
|
||||
"type" => "Announce",
|
||||
"object" => "whatever"
|
||||
}
|
||||
|
||||
{:ok, ^message} = VocabularyPolicy.filter(message)
|
||||
|
||||
Pleroma.Config.put([:mrf_vocabulary, :reject], config)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -5,11 +5,71 @@
|
|||
defmodule Pleroma.Web.ActivityPub.RelayTest do
|
||||
use Pleroma.DataCase
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Relay
|
||||
|
||||
import Pleroma.Factory
|
||||
|
||||
test "gets an actor for the relay" do
|
||||
user = Relay.get_actor()
|
||||
assert user.ap_id == "#{Pleroma.Web.Endpoint.url()}/relay"
|
||||
end
|
||||
|
||||
assert user.ap_id =~ "/relay"
|
||||
describe "follow/1" do
|
||||
test "returns errors when user not found" do
|
||||
assert Relay.follow("test-ap-id") == {:error, "Could not fetch by AP id"}
|
||||
end
|
||||
|
||||
test "returns activity" do
|
||||
user = insert(:user)
|
||||
service_actor = Relay.get_actor()
|
||||
assert {:ok, %Activity{} = activity} = Relay.follow(user.ap_id)
|
||||
assert activity.actor == "#{Pleroma.Web.Endpoint.url()}/relay"
|
||||
assert user.ap_id in activity.recipients
|
||||
assert activity.data["type"] == "Follow"
|
||||
assert activity.data["actor"] == service_actor.ap_id
|
||||
assert activity.data["object"] == user.ap_id
|
||||
end
|
||||
end
|
||||
|
||||
describe "unfollow/1" do
|
||||
test "returns errors when user not found" do
|
||||
assert Relay.unfollow("test-ap-id") == {:error, "Could not fetch by AP id"}
|
||||
end
|
||||
|
||||
test "returns activity" do
|
||||
user = insert(:user)
|
||||
service_actor = Relay.get_actor()
|
||||
ActivityPub.follow(service_actor, user)
|
||||
assert {:ok, %Activity{} = activity} = Relay.unfollow(user.ap_id)
|
||||
assert activity.actor == "#{Pleroma.Web.Endpoint.url()}/relay"
|
||||
assert user.ap_id in activity.recipients
|
||||
assert activity.data["type"] == "Undo"
|
||||
assert activity.data["actor"] == service_actor.ap_id
|
||||
assert activity.data["to"] == [user.ap_id]
|
||||
end
|
||||
end
|
||||
|
||||
describe "publish/1" do
|
||||
test "returns error when activity not `Create` type" do
|
||||
activity = insert(:like_activity)
|
||||
assert Relay.publish(activity) == {:error, "Not implemented"}
|
||||
end
|
||||
|
||||
test "returns error when activity not public" do
|
||||
activity = insert(:direct_note_activity)
|
||||
assert Relay.publish(activity) == {:error, false}
|
||||
end
|
||||
|
||||
test "returns announce activity" do
|
||||
service_actor = Relay.get_actor()
|
||||
note = insert(:note_activity)
|
||||
assert {:ok, %Activity{} = activity, %Object{} = obj} = Relay.publish(note)
|
||||
assert activity.data["type"] == "Announce"
|
||||
assert activity.data["actor"] == service_actor.ap_id
|
||||
assert activity.data["object"] == obj.data["id"]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue