Commit 6c21f5aa authored by rinpatch's avatar rinpatch

Merge branch 'develop' into feature/keyword-policy

parents 8a0b755c d84392c9
Pipeline #7256 passed with stages
in 3 minutes and 25 seconds
......@@ -25,6 +25,7 @@ erl_crash.dump
# secrets files as long as you replace their contents by environment
# variables.
/config/*.secret.exs
/config/generated_config.exs
# Database setup file, some may forget to delete it
/config/setup_db.psql
......
......@@ -10,15 +10,14 @@ For clients it supports both the [GNU Social API with Qvitter extensions](https:
Client applications that are committed to supporting Pleroma:
* Mastalab (Android)
* Tusky (Android)
* Twidere (Android)
* Mastalab (Android, Streaming Ready)
* Tusky (Android, No Streaming)
* Twidere (Android, No Streaming)
* Mast (iOS)
* Amaroq (iOS)
Client applications that are known to work well:
* Pawoo (Android + iOS)
* Tootdon (Android + iOS)
* Tootle (iOS)
* Whalebird (Windows + Mac + Linux)
......
......@@ -115,7 +115,7 @@ config :logger, :console,
config :logger, :ex_syslogger,
level: :debug,
ident: "Pleroma",
format: "$date $time $metadata[$level] $message",
format: "$metadata[$level] $message",
metadata: [:request_id]
config :mime, :types, %{
......
......@@ -100,6 +100,26 @@ config :pleroma, Pleroma.Mailer,
## :logger
* `backends`: `:console` is used to send logs to stdout, `{ExSyslogger, :ex_syslogger}` to log to syslog
An example to enable ONLY ExSyslogger (f/ex in ``prod.secret.exs``) with info and debug suppressed:
```
config :logger,
backends: [{ExSyslogger, :ex_syslogger}]
config :logger, :ex_syslogger,
level: :warn
```
Another example, keeping console output and adding the pid to syslog output:
```
config :logger,
backends: [:console, {ExSyslogger, :ex_syslogger}]
config :logger, :ex_syslogger,
level: :warn,
option: [:pid, :ndelay]
```
See: [logger’s documentation](https://hexdocs.pm/logger/Logger.html) and [ex_syslogger’s documentation](https://hexdocs.pm/ex_syslogger/)
......
......@@ -52,6 +52,14 @@ defmodule Mix.Tasks.Pleroma.User do
- `--locked`/`--no-locked` - whether the user's account is locked
- `--moderator`/`--no-moderator` - whether the user is a moderator
- `--admin`/`--no-admin` - whether the user is an admin
## Add tags to a user.
mix pleroma.user tag NICKNAME TAGS
## Delete tags from a user.
mix pleroma.user untag NICKNAME TAGS
"""
def run(["new", nickname, email | rest]) do
{options, [], []} =
......@@ -249,6 +257,32 @@ defmodule Mix.Tasks.Pleroma.User do
end
end
def run(["tag", nickname | tags]) do
Common.start_pleroma()
with %User{} = user <- User.get_by_nickname(nickname) do
user = user |> User.tag(tags)
Mix.shell().info("Tags of #{user.nickname}: #{inspect(tags)}")
else
_ ->
Mix.shell().error("Could not change user tags for #{nickname}")
end
end
def run(["untag", nickname | tags]) do
Common.start_pleroma()
with %User{} = user <- User.get_by_nickname(nickname) do
user = user |> User.untag(tags)
Mix.shell().info("Tags of #{user.nickname}: #{inspect(tags)}")
else
_ ->
Mix.shell().error("Could not change user tags for #{nickname}")
end
end
def run(["invite"]) do
Common.start_pleroma()
......
......@@ -12,8 +12,10 @@ defmodule Pleroma.Config.DeprecationWarnings do
You are using the old configuration mechanism for the frontend. Please check config.md.
""")
end
end
if Pleroma.Config.get(:mrf_hellthread, :threshold) do
def check_hellthread_threshold do
if Pleroma.Config.get([:mrf_hellthread, :threshold]) do
Logger.warn("""
!!!DEPRECATION WARNING!!!
You are using the old configuration mechanism for the hellthread filter. Please check config.md.
......@@ -23,5 +25,6 @@ defmodule Pleroma.Config.DeprecationWarnings do
def warn do
check_frontend_config_mechanism()
check_hellthread_threshold()
end
end
......@@ -59,6 +59,8 @@ defmodule Pleroma.HTML do
end)
end
def extract_first_external_url(_, nil), do: {:error, "No content"}
def extract_first_external_url(object, content) do
key = "URL|#{object.id}"
......
......@@ -23,6 +23,7 @@ defmodule Pleroma.User.Info do
field(:ap_enabled, :boolean, default: false)
field(:is_moderator, :boolean, default: false)
field(:is_admin, :boolean, default: false)
field(:show_role, :boolean, default: true)
field(:keys, :string, default: nil)
field(:settings, :map, default: nil)
field(:magic_key, :string, default: nil)
......@@ -30,7 +31,8 @@ defmodule Pleroma.User.Info do
field(:topic, :string, default: nil)
field(:hub, :string, default: nil)
field(:salmon, :string, default: nil)
field(:hide_network, :boolean, default: false)
field(:hide_followers, :boolean, default: false)
field(:hide_follows, :boolean, default: false)
field(:pinned_activities, {:array, :string}, default: [])
# Found in the wild
......@@ -143,8 +145,10 @@ defmodule Pleroma.User.Info do
:no_rich_text,
:default_scope,
:banner,
:hide_network,
:background
:hide_follows,
:hide_followers,
:background,
:show_role
])
end
......@@ -194,7 +198,8 @@ defmodule Pleroma.User.Info do
info
|> cast(params, [
:is_moderator,
:is_admin
:is_admin,
:show_role
])
end
......
......@@ -521,7 +521,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_actor(query, _), do: query
defp restrict_type(query, %{"type" => type}) when is_binary(type) do
restrict_type(query, %{"type" => [type]})
from(activity in query, where: fragment("?->>'type' = ?", activity.data, ^type))
end
defp restrict_type(query, %{"type" => type}) do
......
......@@ -198,6 +198,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
end
end
def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
conn
|> put_resp_header("content-type", "application/activity+json")
|> json(UserView.render("user.json", %{user: user}))
end
def whoami(_conn, _params), do: {:error, :not_found}
def read_inbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = params) do
if nickname == user.nickname do
conn
......
# 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.TagPolicy do
alias Pleroma.User
@behaviour Pleroma.Web.ActivityPub.MRF
defp get_tags(%User{tags: tags}) when is_list(tags), do: tags
defp get_tags(_), do: []
defp process_tag(
"mrf_tag:media-force-nsfw",
%{"type" => "Create", "object" => %{"attachment" => child_attachment} = object} = message
)
when length(child_attachment) > 0 do
tags = (object["tag"] || []) ++ ["nsfw"]
object =
object
|> Map.put("tags", tags)
|> Map.put("sensitive", true)
message = Map.put(message, "object", object)
{:ok, message}
end
defp process_tag(
"mrf_tag:media-strip",
%{"type" => "Create", "object" => %{"attachment" => child_attachment} = object} = message
)
when length(child_attachment) > 0 do
object = Map.delete(object, "attachment")
message = Map.put(message, "object", object)
{:ok, message}
end
defp process_tag(
"mrf_tag:force-unlisted",
%{"type" => "Create", "to" => to, "cc" => cc, "actor" => actor} = message
) do
user = User.get_cached_by_ap_id(actor)
if Enum.member?(to, "https://www.w3.org/ns/activitystreams#Public") do
to =
List.delete(to, "https://www.w3.org/ns/activitystreams#Public") ++ [user.follower_address]
cc =
List.delete(cc, user.follower_address) ++ ["https://www.w3.org/ns/activitystreams#Public"]
object =
message["object"]
|> Map.put("to", to)
|> Map.put("cc", cc)
message =
message
|> Map.put("to", to)
|> Map.put("cc", cc)
|> Map.put("object", object)
{:ok, message}
else
{:ok, message}
end
end
defp process_tag(
"mrf_tag:sandbox",
%{"type" => "Create", "to" => to, "cc" => cc, "actor" => actor} = message
) do
user = User.get_cached_by_ap_id(actor)
if Enum.member?(to, "https://www.w3.org/ns/activitystreams#Public") or
Enum.member?(cc, "https://www.w3.org/ns/activitystreams#Public") do
to =
List.delete(to, "https://www.w3.org/ns/activitystreams#Public") ++ [user.follower_address]
cc = List.delete(cc, "https://www.w3.org/ns/activitystreams#Public")
object =
message["object"]
|> Map.put("to", to)
|> Map.put("cc", cc)
message =
message
|> Map.put("to", to)
|> Map.put("cc", cc)
|> Map.put("object", object)
{:ok, message}
else
{:ok, message}
end
end
defp process_tag(
"mrf_tag:disable-remote-subscription",
%{"type" => "Follow", "actor" => actor} = message
) do
user = User.get_cached_by_ap_id(actor)
if user.local == true do
{:ok, message}
else
{:reject, nil}
end
end
defp process_tag("mrf_tag:disable-any-subscription", %{"type" => "Follow"}), do: {:reject, nil}
defp process_tag(_, message), do: {:ok, message}
def filter_message(actor, message) do
User.get_cached_by_ap_id(actor)
|> get_tags()
|> Enum.reduce({:ok, message}, fn
tag, {:ok, message} ->
process_tag(tag, message)
_, error ->
error
end)
end
@impl true
def filter(%{"object" => target_actor, "type" => "Follow"} = message),
do: filter_message(target_actor, message)
@impl true
def filter(%{"actor" => actor, "type" => "Create"} = message),
do: filter_message(actor, message)
@impl true
def filter(message), do: {:ok, message}
end
......@@ -313,6 +313,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> Map.put("tag", combined)
end
def fix_tag(%{"tag" => %{} = tag} = object), do: Map.put(object, "tag", [tag])
def fix_tag(object), do: object
# content map usually only has one language so this will do for now.
......
......@@ -86,7 +86,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
query = from(user in query, select: [:ap_id])
following = Repo.all(query)
collection(following, "#{user.ap_id}/following", page, !user.info.hide_network)
collection(following, "#{user.ap_id}/following", page, !user.info.hide_follows)
|> Map.merge(Utils.make_json_ld_header())
end
......@@ -99,7 +99,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"id" => "#{user.ap_id}/following",
"type" => "OrderedCollection",
"totalItems" => length(following),
"first" => collection(following, "#{user.ap_id}/following", 1, !user.info.hide_network)
"first" => collection(following, "#{user.ap_id}/following", 1, !user.info.hide_follows)
}
|> Map.merge(Utils.make_json_ld_header())
end
......@@ -109,7 +109,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
query = from(user in query, select: [:ap_id])
followers = Repo.all(query)
collection(followers, "#{user.ap_id}/followers", page, !user.info.hide_network)
collection(followers, "#{user.ap_id}/followers", page, !user.info.hide_followers)
|> Map.merge(Utils.make_json_ld_header())
end
......@@ -122,7 +122,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"id" => "#{user.ap_id}/followers",
"type" => "OrderedCollection",
"totalItems" => length(followers),
"first" => collection(followers, "#{user.ap_id}/followers", 1, !user.info.hide_network)
"first" => collection(followers, "#{user.ap_id}/followers", 1, !user.info.hide_followers)
}
|> Map.merge(Utils.make_json_ld_header())
end
......@@ -239,6 +239,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
if offset < total do
Map.put(map, "next", "#{iri}?page=#{page + 1}")
else
map
end
end
end
......@@ -605,7 +605,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
followers =
cond do
for_user && user.id == for_user.id -> followers
user.info.hide_network -> []
user.info.hide_followers -> []
true -> followers
end
......@@ -621,7 +621,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
followers =
cond do
for_user && user.id == for_user.id -> followers
user.info.hide_network -> []
user.info.hide_follows -> []
true -> followers
end
......
......@@ -182,18 +182,24 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
end
def render("card.json", %{rich_media: rich_media, page_url: page_url}) do
page_url_data = URI.parse(page_url)
page_url_data =
if rich_media[:url] != nil do
URI.merge(URI.parse(page_url), URI.parse(rich_media[:url]))
URI.merge(page_url_data, URI.parse(rich_media[:url]))
else
page_url
page_url_data
end
page_url = page_url_data |> to_string
image_url =
URI.merge(page_url_data, URI.parse(rich_media[:image]))
|> to_string
if rich_media[:image] != nil do
URI.merge(page_url_data, URI.parse(rich_media[:image]))
|> to_string
else
nil
end
site_name = rich_media[:site_name] || page_url_data.host
......
......@@ -54,22 +54,12 @@ defmodule Pleroma.Web.RichMedia.Parser do
{:error, "Found metadata was invalid or incomplete: #{inspect(data)}"}
end
defp string_is_valid_unicode(data) when is_binary(data) do
data
|> :unicode.characters_to_binary()
|> clean_string()
end
defp string_is_valid_unicode(data), do: {:ok, data}
defp clean_string({:error, _, _}), do: {:error, "Invalid data"}
defp clean_string(data), do: {:ok, data}
defp clean_parsed_data(data) do
data
|> Enum.reject(fn {_, val} ->
case string_is_valid_unicode(val) do
{:ok, _} -> false
|> Enum.reject(fn {key, val} ->
with {:ok, _} <- Jason.encode(%{key => val}) do
false
else
_ -> true
end
end)
......
......@@ -454,6 +454,7 @@ defmodule Pleroma.Web.Router do
scope "/", Pleroma.Web.ActivityPub do
pipe_through([:activitypub_client])
get("/api/ap/whoami", ActivityPubController, :whoami)
get("/users/:nickname/inbox", ActivityPubController, :read_inbox)
post("/users/:nickname/outbox", ActivityPubController, :update_outbox)
end
......
......@@ -24,7 +24,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
conn
|> put_view(UserView)
|> render("show.json", %{user: user, token: token})
|> render("show.json", %{user: user, token: token, for: user})
end
def status_update(%{assigns: %{user: user}} = conn, %{"status" => _} = status_data) do
......@@ -503,7 +503,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
followers =
cond do
for_user && user.id == for_user.id -> followers
user.info.hide_network -> []
user.info.hide_followers -> []
true -> followers
end
......@@ -523,7 +523,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
friends =
cond do
for_user && user.id == for_user.id -> friends
user.info.hide_network -> []
user.info.hide_follows -> []
true -> friends
end
......@@ -618,7 +618,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
defp build_info_cng(user, params) do
info_params =
["no_rich_text", "locked", "hide_network"]
["no_rich_text", "locked", "hide_followers", "hide_follows", "show_role"]
|> Enum.reduce(%{}, fn key, res ->
if value = params[key] do
Map.put(res, key, value == "true")
......
......@@ -108,7 +108,8 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
"locked" => user.info.locked,
"default_scope" => user.info.default_scope,
"no_rich_text" => user.info.no_rich_text,
"hide_network" => user.info.hide_network,
"hide_followers" => user.info.hide_followers,
"hide_follows" => user.info.hide_follows,
"fields" => fields,
# Pleroma extension
......@@ -118,6 +119,12 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
}
}
data =
if(user.info.is_admin || user.info.is_moderator,
do: maybe_with_role(data, user, for_user),
else: data
)
if assigns[:token] do
Map.put(data, "token", token_string(assigns[:token]))
else
......@@ -125,6 +132,20 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
end
end
defp maybe_with_role(data, %User{id: id} = user, %User{id: id}) do
Map.merge(data, %{"role" => role(user), "show_role" => user.info.show_role})
end
defp maybe_with_role(data, %User{info: %{show_role: true}} = user, _user) do
Map.merge(data, %{"role" => role(user)})
end
defp maybe_with_role(data, _, _), do: data
defp role(%User{info: %{:is_admin => true}}), do: "admin"
defp role(%User{info: %{:is_moderator => true}}), do: "moderator"
defp role(_), do: "member"
defp image_url(%{"url" => [%{"href" => href} | _]}), do: href
defp image_url(_), do: nil
......
defmodule Pleroma.Repo.Migrations.SplitHideNetwork do
use Ecto.Migration
def up do
execute("UPDATE users SET info = jsonb_set(info, '{hide_network}'::text[], 'false'::jsonb) WHERE NOT(info::jsonb ? 'hide_network')")
execute("UPDATE users SET info = jsonb_set(info, '{hide_followings}'::text[], info->'hide_network')")
execute("UPDATE users SET info = jsonb_set(info, '{hide_followers}'::text[], info->'hide_network')")
end
def down do
end
end
defmodule Pleroma.Repo.Migrations.AddCorrectDMIndex do
use Ecto.Migration
@disable_ddl_transaction true
def up do
drop_if_exists(
index(:activities, ["activity_visibility(actor, recipients, data)"],
name: :activities_visibility_index
)
)
create(
index(:activities, ["activity_visibility(actor, recipients, data)", "id DESC NULLS LAST"],
name: :activities_visibility_index,
concurrently: true,
where: "data->>'type' = 'Create'"
)
)
end
def down do
drop(
index(:activities, ["activity_visibility(actor, recipients, data)", "id DESC"],
name: :activities_visibility_index,
concurrently: true,
where: "data->>'type' = 'Create'"
)
)
end
end
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Pleroma</title><!--server-generated-meta--><link rel=icon type=image/png href=/favicon.png><link rel=stylesheet href=/static/font/css/fontello.css><link rel=stylesheet href=/static/font/css/animation.css><link href=/static/css/app.42c43da15d7ab16ad8e42d21fcfc5a43.css rel=stylesheet></head><body style="display: none"><div id=app></div><script type=text/javascript src=/static/js/manifest.6aa5664a1a2c0832ce7b.js></script><script type=text/javascript src=/static/js/vendor.56a115a1d7339d6811a0.js></script><script type=text/javascript src=/static/js/app.59ebcfb47f86a7a5ba3f.js></script></body></html>
\ No newline at end of file
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Pleroma</title><!--server-generated-meta--><link rel=icon type=image/png href=/favicon.png><link rel=stylesheet href=/static/font/css/fontello.css><link rel=stylesheet href=/static/font/css/animation.css><link href=/static/css/app.d75cda10f04aeefec7b657f8244070e9.css rel=stylesheet></head><body style="display: none"><div id=app></div><script type=text/javascript src=/static/js/manifest.f00ab54db04706aab2c9.js></script><script type=text/javascript src=/static/js/vendor.5173dfeead1ded3d1f46.js></script><script type=text/javascript src=/static/js/app.0e4952ec8d775da840f1.js></script></body></html>
\ No newline at end of file
......@@ -19,8 +19,5 @@
"loginMethod": "password",
"webPushNotifications": false,
"noAttachmentLinks": false,
"nsfwCensorImage": "",
"useOneClickNsfw": true,
"playVideosInline": false,
"useContainFit": false
"nsfwCensorImage": ""
}
.timeline .loadmore-text{opacity:1}.new-status-notification{position:relative;margin-top:-1px;font-size:1.1em;border-width:1px 0 0;border-style:solid;border-color:var(--border,#222);padding:10px;z-index:1;background-color:#182230;background-color:var(--panel,#182230)}.status-body{-ms-flex:1;flex:1;min-width:0}.status-preview.status-el{border-color:#222;border:1px solid var(--border,#222)}.status-preview-container{position:relative;max-width:100%}.status-preview{position:absolute;max-width:95%;display:-ms-flexbox;display:flex;background-color:#121a24;background-color:var(--bg,#121a24);border-color:#222;border:1px solid var(--border,#222);border-radius:5px;border-radius:var(--tooltipRadius,5px);box-shadow:2px 2px 3px rgba(0,0,0,.5);box-shadow:var(--popupShadow);margin-top:.25em;margin-left:.5em;z-index:50}.status-preview .status{-ms-flex:1;flex:1;border:0;min-width:15em}.status-preview-loading{display:block;min-width:15em;padding:1em;text-align:center;border-width:1px;border-style:solid}.status-preview-loading i{font-size:2em}.status-el{-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;overflow-wrap:break-word;word-wrap:break-word;word-break:break-word;border-left-width:0;line-height:18px;min-width:0;border-color:#222;border-color:var(--border,#222);border-left:4px red;border-left:4px var(--cRed,red)}.status-el_focused{background-color:#151e2a;background-color:var(--lightBg,#151e2a)}.timeline .status-el{border-bottom-width:1px;border-bottom-style:solid}.status-el .media-body{-ms-flex:1;flex:1;padding:0;margin:0 0 .25em .8em}.status-el .usercard{margin-bottom:.7em}.status-el .media-heading{-ms-flex-wrap:nowrap;flex-wrap:nowrap;line-height:18px}.status-el .media-heading-left{padding:0;vertical-align:bottom;-ms-flex-preferred-size:100%;flex-basis:100%}.status-el .media-heading-left a{display:inline-block;word-break:break-all}.status-el .media-heading-left small{font-weight:lighter}.status-el .media-heading-left h4{white-space:nowrap;font-size:14px;margin-right:.25em;overflow:hidden;text-overflow:ellipsis}.status-el .media-heading-left .name-and-links{padding:0;-ms-flex:1 0;flex:1 0;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:baseline;align-items:baseline}.status-el .media-heading-left .name-and-links .user-name{margin-right:.45em}.status-el .media-heading-left .name-and-links .user-name img{width:14px;height:14px;vertical-align:middle;object-fit:contain}.status-el .media-heading-left .links{display:-ms-flexbox;display:flex;font-size:12px;color:#d8a070;color:var(--link,#d8a070);max-width:100%}.status-el .media-heading-left .links a{max-width:100%;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.status-el .media-heading-left .reply-info{display:-ms-flexbox;display:flex}.status-el .media-heading-left .replies{line-height:16px}.status-el .media-heading-left .reply-link{margin-right:.2em}.status-el .media-heading-right{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-negative:0;flex-shrink:0;-ms-flex-wrap:nowrap;flex-wrap:nowrap;margin-left:.25em;-ms-flex-item-align:baseline;align-self:baseline}.status-el .media-heading-right .timeago{margin-right:.2em;font-size:12px;-ms-flex-item-align:last baseline;-ms-grid-row-align:last baseline;align-self:last baseline}.status-el .media-heading-right>*{margin-left:.2em}.status-el .media-heading-right a:hover i{color:#b9b9ba;color:var(--text,#b9b9ba)}.status-el .tall-status{position:relative;height:220px;overflow-x:hidden;overflow-y:hidden}.status-el .tall-status-hider{display:inline-block;word-break:break-all;position:absolute;height:70px;margin-top:150px;width:100%;text-align:center;line-height:110px;background:linear-gradient(180deg,transparent,#121a24 80%);background:linear-gradient(180deg,transparent,var(--bg,#121a24) 80%)}.status-el .tall-status-hider_focused{background:linear-gradient(180deg,transparent,#151e2a 80%);background:linear-gradient(180deg,transparent,var(--lightBg,#151e2a) 80%)}.status-el .cw-status-hider,.status-el .status-unhider{width:100%;text-align:center;display:inline-block;word-break:break-all}.status-el .status-content{margin-right:.5em;font-family:var(--postFont,sans-serif)}.status-el .status-content img,.status-el .status-content video{max-width:100%;max-height:400px;vertical-align:middle;object-fit:contain}.status-el .status-content blockquote{margin:.2em 0 .2em 2em;font-style:italic}.status-el .status-content pre{overflow:auto}.status-el .status-content code,.status-el .status-content kbd,.status-el .status-content pre,.status-el .status-content samp,.status-el .status-content var{font-family:var(--postCodeFont,monospace)}.status-el .status-content p{margin:0;margin-top:.2em;margin-bottom:.5em}.status-el .status-content h1{font-size:1.1em;line-height:1.2em;margin:1.4em 0}.status-el .status-content h2{font-size:1.1em;margin:1em 0}.status-el .status-content h3{font-size:1em;margin:1.2em 0}.status-el .status-content h4{margin:1.1em 0}.status-el .retweet-info{padding:.4em .6em 0;margin:0}.status-el .retweet-info .avatar{border-radius:10px;border-radius:var(--avatarAltRadius,10px);margin-left:28px;width:20px;height:20px}.status-el .retweet-info .media-body{font-size:1em;line-height:22px;display:-ms-flexbox;display:flex;-ms-flex-line-pack:center;align-content:center;-ms-flex-wrap:wrap;flex-wrap:wrap}.status-el .retweet-info .media-body .user-name{font-weight:700}.status-el .retweet-info .media-body .user-name img{width:14px;height:14px;vertical-align:middle;object-fit:contain}.status-el .retweet-info .media-body i{padding:0 .2em}.status-el .retweet-info .media-body a{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.status-fadein{animation-duration:.4s;animation-name:fadein}@keyframes fadein{0%{opacity:0}to{opacity:1}}.greentext{color:green}.status-conversation{border-left-style:solid}.status-actions{width:100%;display:-ms-flexbox;display:flex}.status-actions div,.status-actions favorite-button{padding-top:.25em;max-width:6em;-ms-flex:1;flex:1}.icon-reply.icon-reply-active,.icon-reply:hover{color:#0095ff;color:var(--cBlue,#0095ff)}.status .avatar-compact{width:32px;height:32px;box-shadow:var(--avatarStatusShadow);border-radius:10px;border-radius:var(--avatarAltRadius,10px)}.status .avatar-compact.better-shadow{box-shadow:var(--avatarStatusShadowInset);filter:var(--avatarStatusShadowFilter)}.avatar.still-image{width:48px;height:48px;box-shadow:var(--avatarStatusShadow);border-radius:4px;border-radius:var(--avatarRadius,4px);overflow:hidden;position:relative}.avatar.still-image.better-shadow{box-shadow:var(--avatarStatusShadowInset);filter:var(--avatarStatusShadowFilter)}.avatar.still-image img{width:100%;height:100%}.avatar.still-image.animated:before,.status:hover .animated.avatar canvas{display:none}.status:hover .animated.avatar img{visibility:visible}.status{display:-ms-flexbox;display:flex;padding:.6em}.status.is-retweet{padding-top:.1em}.status-conversation:last-child{border-bottom:none}.muted{padding:.25em .5em}.muted button{margin-left:auto}.muted .muteWords{margin-left:10px}a.unmute{display:block;margin-left:auto}.reply-left{-ms-flex:0;flex:0;min-width:48px}.reply-body{-ms-flex:1;flex:1}.timeline>.status-el:last-child{border-bottom-radius:0 0 10px 10px;border-radius:0 0 var(--panelRadius,10px) var(--panelRadius,10px);border-bottom:none}@media (max-width:800px){.status-el .retweet-info .avatar{margin-left:20px}.status{max-width:100%}.status .avatar{width:40px;height:40px}.status .avatar-compact{width:32px;height:32px}}.attachments{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.attachments .attachment.media-upload-container{-ms-flex:0 0 auto;flex:0 0 auto;max-height:200px;max-width:100%;display:-ms-flexbox;display:flex}.attachments .attachment.media-upload-container video{max-width:100%}.attachments .placeholder{margin-right:8px;margin-bottom:4px}.attachments .nsfw-placeholder{cursor:pointer}.attachments .nsfw-placeholder.loading{cursor:progress}.attachments .attachment{position:relative;margin:.5em .5em 0 0;-ms-flex-item-align:start;align-self:flex-start;line-height:0;border-radius:10px;border-radius:var(--attachmentRadius,10px);border-color:#222;border:1px solid var(--border,#222);overflow:hidden}.attachments .non-gallery.attachment.video{-ms-flex:1 0 40%;flex:1 0 40%}.attachments .non-gallery.attachment .nsfw{height:260px}.attachments .non-gallery.attachment .small{height:120px;-ms-flex-positive:0;flex-grow:0}.attachments .non-gallery.attachment .video{height:260px;display:-ms-flexbox;display:flex}.attachments .non-gallery.attachment video{max-height:100%;object-fit:contain}.attachments .fullwidth{-ms-flex-preferred-size:100%;flex-basis:100%}.attachments.video{line-height:0}.attachments .video-container{display:-ms-flexbox;display:flex;max-height:100%}.attachments .video{width:100%}.attachments .play-icon{position:absolute;font-size:64px;top:calc(50% - 32px);left:calc(50% - 32px);color:hsla(0,0%,100%,.75);text-shadow:0 0 2px rgba(0,0,0,.4)}.attachments .play-icon:before{margin:0}.attachments.html{-ms-flex-preferred-size:90%;flex-basis:90%;width:100%;display:-ms-flexbox;display:flex}.attachments .hider{position:absolute;white-space:nowrap;margin:10px;padding:5px;background:hsla(0,0%,90%,.6);font-weight:700;z-index:4;line-height:1;border-radius:5px;border-radius:var(--tooltipRadius,5px)}.attachments video{z-index:0}.attachments audio{width:100%}.attachments img.media-upload{line-height:0;max-height:200px;max-width:100%}.attachments .oembed{line-height:1.2em;-ms-flex:1 0 100%;flex:1 0 100%;width:100%;margin-right:15px;display:-ms-flexbox;display:flex}.attachments .oembed img{width:100%}.attachments .oembed .image{-ms-flex:1;flex:1}.attachments .oembed .image img{border:0;border-radius:5px;height:100%;object-fit:cover}.attachments .oembed .text{-ms-flex:2;flex:2;margin:8px;word-break:break-all}.attachments .oembed .text h1{font-size:14px;margin:0}.attachments .image-attachment{width:100%;height:100%}.attachments .image-attachment.hidden{display:none}.attachments .image-attachment .nsfw{object-fit:cover;width:100%;height:100%}.attachments .image-attachment img{image-orientation:from-image}.still-image{position:relative;line-height:0;overflow:hidden;width:100%;height:100%}.still-image:hover canvas{display:none}.still-image img{width:100%;height:100%;object-fit:contain}.still-image.animated:hover:before,.still-image.animated img{visibility:hidden}.still-image.animated:hover img{visibility:visible}.still-image.animated:before{content:"gif";position:absolute;line-height:10px;font-size:10px;top:5px;left:5px;background:hsla(0,0%,50%,.5);color:#fff;display:block;padding:2px 4px;border-radius:5px;border-radius:var(--tooltipRadius,5px);z-index:2}.still-image canvas{position:absolute;top:0;bottom:0;left:0;right:0;width:100%;height:100%;object-fit:contain}.fav-active{cursor:pointer;animation-duration:.6s}.fav-active:hover,.favorite-button.icon-star{color:orange;color:var(--cOrange,orange)}.rt-active{cursor:pointer;animation-duration:.6s}.icon-retweet.retweeted,.rt-active:hover{color:#0fa00f;color:var(--cGreen,#0fa00f)}.delete-status,.icon-cancel{cursor:pointer}.delete-status:hover,.icon-cancel:hover{color:red;color:var(--cRed,red)}.tribute-container ul{padding:0}.tribute-container ul li{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.tribute-container img{padding:3px;width:16px;height:16px;border-radius:10px;border-radius:var(--avatarAltRadius,10px)}.login .form-bottom,.post-status-form .form-bottom{display:-ms-flexbox;display:flex;padding:.5em;height:32px}.login .form-bottom button,.post-status-form .form-bottom button{width:10em}.login .form-bottom p,.post-status-form .form-bottom p{margin:.35em;padding:.35em;display:-ms-flexbox;display:flex}.login .error,.post-status-form .error{text-align:center}.login .media-upload-wrapper,.post-status-form .media-upload-wrapper{-ms-flex:0 0 auto;flex:0 0 auto;max-width:100%;min-width:50px;margin-right:.2em;margin-bottom:.5em}.login .media-upload-wrapper .icon-cancel,.post-status-form .media-upload-wrapper .icon-cancel{display:inline-block;position:static;margin:0;padding-bottom:0;margin-left:10px;margin-left:var(--attachmentRadius,10px);background-color:#182230;background-color:var(--btn,#182230);border-bottom-left-radius:0;border-bottom-right-radius:0}.login .attachments,.post-status-form .attachments{padding:0 .5em}.login .attachments .attachment,.post-status-form .attachments .attachment{margin:0;position:relative;-ms-flex:0 0 auto;flex:0 0 auto;border:1px solid #222;border:1px solid var(--border,#222);text-align:center}.login .attachments .attachment audio,.post-status-form .attachments .attachment audio{min-width:300px;-ms-flex:1 0 auto;flex:1 0 auto}.login .attachments .attachment a,.post-status-form .attachments .attachment a{display:block;text-align:left;line-height:1.2;padding:.5em}.login .attachments i,.post-status-form .attachments i{position:absolute;margin:10px;padding:5px;background:hsla(0,0%,90%,.6);border-radius:10px;border-radius:var(--attachmentRadius,10px);font-weight:700}.login form,.post-status-form form{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding:.6em}.login .form-group,.post-status-form .form-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding:.3em .5em .6em;line-height:24px}.login form textarea.form-control,.login form textarea.form-cw,.post-status-form form textarea.form-control,.post-status-form form textarea.form-cw{line-height:16px;resize:none;overflow:hidden;transition:min-height .2s .1s;min-height:1px}.login form textarea.form-control,.post-status-form form textarea.form-control{box-sizing:content-box}.login form textarea.form-control:focus,.post-status-form form textarea.form-control:focus{min-height:48px}.login .btn,.post-status-form .btn{cursor:pointer}.login .btn[disabled],.post-status-form .btn[disabled]{cursor:not-allowed}.login .icon-cancel,.post-status-form .icon-cancel{cursor:pointer;z-index:4}.login .autocomplete-panel,.post-status-form .autocomplete-panel{margin:0 .5em;border-radius:5px;border-radius:var(--tooltipRadius,5px);position:absolute;z-index:1;box-shadow:1px 2px 4px rgba(0,0,0,.5);box-shadow:var(--popupShadow);min-width:75%;background:#121a24;background:var(--bg,#121a24);color:#b9b9ba;color:var(--lightText,#b9b9ba)}.login .autocomplete,.post-status-form .autocomplete{cursor:pointer;padding:.2em .4em;border-bottom:1px solid rgba(0,0,0,.4);display:-ms-flexbox;display:flex}.login .autocomplete img,.post-status-form .autocomplete img{width:24px;height:24px;border-radius:4px;border-radius:var(--avatarRadius,4px);object-fit:contain}.login .autocomplete span,.post-status-form .autocomplete span{line-height:24px;margin:0 .1em 0 .2em}.login .autocomplete small,.post-status-form .autocomplete small{margin-left:.5em;color:hsla(240,1%,73%,.5);color:var(--faint,hsla(240,1%,73%,.5))}.login .autocomplete.highlighted,.post-status-form .autocomplete.highlighted{background-color:#182230;background-color:var(--lightBg,#182230)}.media-upload{font-size:26px;-ms-flex:1;flex:1}.icon-upload{cursor:pointer}.profile-panel-background{background-size:cover;border-radius:10px;border-radius:var(--panelRadius,10px);overflow:hidden;border-bottom-left-radius:0;border-bottom-right-radius:0}.profile-panel-background .panel-heading{padding:.5em 0;text-align:center;box-shadow:none}.profile-panel-body{word-wrap:break-word;background:linear-gradient(180deg,transparent,#121a24 80%);background:linear-gradient(180deg,transparent,var(--bg,#121a24) 80%)}.profile-panel-body .profile-bio{text-align:center}.user-info{color:#b9b9ba;color:var(--lightText,#b9b9ba);padding:0 26px}.user-info .container{padding:16px 0 6px;display:-ms-flexbox;display:flex;max-height:56px}.user-info .container .avatar{border-radius:4px;border-radius:var(--avatarRadius,4px);-ms-flex:1 0 100%;flex:1 0 100%;width:56px;height:56px;box-shadow:0 1px 8px rgba(0,0,0,.75);box-shadow:var(--avatarShadow);object-fit:cover}.user-info .container .avatar.better-shadow{box-shadow:var(--avatarShadowInset);filter:var(--avatarShadowFilter)}.user-info .container .avatar.animated:before,.user-info:hover .animated.avatar canvas{display:none}.user-info:hover .animated.avatar img{visibility: