Extract auth actions from `MastodonAPIController` to `AuthController`
[akkoma] / lib / pleroma / web / mastodon_api / controllers / mastodon_api_controller.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
6 use Pleroma.Web, :controller
7
8 import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
9
10 alias Pleroma.Bookmark
11 alias Pleroma.Config
12 alias Pleroma.Pagination
13 alias Pleroma.Stats
14 alias Pleroma.User
15 alias Pleroma.Web
16 alias Pleroma.Web.ActivityPub.ActivityPub
17 alias Pleroma.Web.CommonAPI
18 alias Pleroma.Web.MastodonAPI.AccountView
19 alias Pleroma.Web.MastodonAPI.MastodonView
20 alias Pleroma.Web.MastodonAPI.StatusView
21
22 require Logger
23
24 action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
25
26 @mastodon_api_level "2.7.2"
27
28 def masto_instance(conn, _params) do
29 instance = Config.get(:instance)
30
31 response = %{
32 uri: Web.base_url(),
33 title: Keyword.get(instance, :name),
34 description: Keyword.get(instance, :description),
35 version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})",
36 email: Keyword.get(instance, :email),
37 urls: %{
38 streaming_api: Pleroma.Web.Endpoint.websocket_url()
39 },
40 stats: Stats.get_stats(),
41 thumbnail: Web.base_url() <> "/instance/thumbnail.jpeg",
42 languages: ["en"],
43 registrations: Pleroma.Config.get([:instance, :registrations_open]),
44 # Extra (not present in Mastodon):
45 max_toot_chars: Keyword.get(instance, :limit),
46 poll_limits: Keyword.get(instance, :poll_limits)
47 }
48
49 json(conn, response)
50 end
51
52 def peers(conn, _params) do
53 json(conn, Stats.get_peers())
54 end
55
56 defp mastodonized_emoji do
57 Pleroma.Emoji.get_all()
58 |> Enum.map(fn {shortcode, %Pleroma.Emoji{file: relative_url, tags: tags}} ->
59 url = to_string(URI.merge(Web.base_url(), relative_url))
60
61 %{
62 "shortcode" => shortcode,
63 "static_url" => url,
64 "visible_in_picker" => true,
65 "url" => url,
66 "tags" => tags,
67 # Assuming that a comma is authorized in the category name
68 "category" => (tags -- ["Custom"]) |> Enum.join(",")
69 }
70 end)
71 end
72
73 def custom_emojis(conn, _params) do
74 mastodon_emoji = mastodonized_emoji()
75 json(conn, mastodon_emoji)
76 end
77
78 def follows(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do
79 with {_, %User{} = followed} <- {:followed, User.get_cached_by_nickname(uri)},
80 {_, true} <- {:followed, follower.id != followed.id},
81 {:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do
82 conn
83 |> put_view(AccountView)
84 |> render("show.json", %{user: followed, for: follower})
85 else
86 {:followed, _} ->
87 {:error, :not_found}
88
89 {:error, message} ->
90 conn
91 |> put_status(:forbidden)
92 |> json(%{error: message})
93 end
94 end
95
96 def mutes(%{assigns: %{user: user}} = conn, _) do
97 with muted_accounts <- User.muted_users(user) do
98 res = AccountView.render("index.json", users: muted_accounts, for: user, as: :user)
99 json(conn, res)
100 end
101 end
102
103 def blocks(%{assigns: %{user: user}} = conn, _) do
104 with blocked_accounts <- User.blocked_users(user) do
105 res = AccountView.render("index.json", users: blocked_accounts, for: user, as: :user)
106 json(conn, res)
107 end
108 end
109
110 def favourites(%{assigns: %{user: user}} = conn, params) do
111 params =
112 params
113 |> Map.put("type", "Create")
114 |> Map.put("favorited_by", user.ap_id)
115 |> Map.put("blocking_user", user)
116
117 activities =
118 ActivityPub.fetch_activities([], params)
119 |> Enum.reverse()
120
121 conn
122 |> add_link_headers(activities)
123 |> put_view(StatusView)
124 |> render("index.json", %{activities: activities, for: user, as: :activity})
125 end
126
127 def bookmarks(%{assigns: %{user: user}} = conn, params) do
128 user = User.get_cached_by_id(user.id)
129
130 bookmarks =
131 Bookmark.for_user_query(user.id)
132 |> Pagination.fetch_paginated(params)
133
134 activities =
135 bookmarks
136 |> Enum.map(fn b -> Map.put(b.activity, :bookmark, Map.delete(b, :activity)) end)
137
138 conn
139 |> add_link_headers(bookmarks)
140 |> put_view(StatusView)
141 |> render("index.json", %{activities: activities, for: user, as: :activity})
142 end
143
144 def index(%{assigns: %{user: user}} = conn, _params) do
145 token = get_session(conn, :oauth_token)
146
147 if user && token do
148 mastodon_emoji = mastodonized_emoji()
149
150 limit = Config.get([:instance, :limit])
151
152 accounts = Map.put(%{}, user.id, AccountView.render("show.json", %{user: user, for: user}))
153
154 initial_state =
155 %{
156 meta: %{
157 streaming_api_base_url: Pleroma.Web.Endpoint.websocket_url(),
158 access_token: token,
159 locale: "en",
160 domain: Pleroma.Web.Endpoint.host(),
161 admin: "1",
162 me: "#{user.id}",
163 unfollow_modal: false,
164 boost_modal: false,
165 delete_modal: true,
166 auto_play_gif: false,
167 display_sensitive_media: false,
168 reduce_motion: false,
169 max_toot_chars: limit,
170 mascot: User.get_mascot(user)["url"]
171 },
172 poll_limits: Config.get([:instance, :poll_limits]),
173 rights: %{
174 delete_others_notice: present?(user.info.is_moderator),
175 admin: present?(user.info.is_admin)
176 },
177 compose: %{
178 me: "#{user.id}",
179 default_privacy: user.info.default_scope,
180 default_sensitive: false,
181 allow_content_types: Config.get([:instance, :allowed_post_formats])
182 },
183 media_attachments: %{
184 accept_content_types: [
185 ".jpg",
186 ".jpeg",
187 ".png",
188 ".gif",
189 ".webm",
190 ".mp4",
191 ".m4v",
192 "image\/jpeg",
193 "image\/png",
194 "image\/gif",
195 "video\/webm",
196 "video\/mp4"
197 ]
198 },
199 settings:
200 user.info.settings ||
201 %{
202 onboarded: true,
203 home: %{
204 shows: %{
205 reblog: true,
206 reply: true
207 }
208 },
209 notifications: %{
210 alerts: %{
211 follow: true,
212 favourite: true,
213 reblog: true,
214 mention: true
215 },
216 shows: %{
217 follow: true,
218 favourite: true,
219 reblog: true,
220 mention: true
221 },
222 sounds: %{
223 follow: true,
224 favourite: true,
225 reblog: true,
226 mention: true
227 }
228 }
229 },
230 push_subscription: nil,
231 accounts: accounts,
232 custom_emojis: mastodon_emoji,
233 char_limit: limit
234 }
235 |> Jason.encode!()
236
237 conn
238 |> put_layout(false)
239 |> put_view(MastodonView)
240 |> render("index.html", %{initial_state: initial_state})
241 else
242 conn
243 |> put_session(:return_to, conn.request_path)
244 |> redirect(to: "/web/login")
245 end
246 end
247
248 def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _params) do
249 with {:ok, _} <- User.update_info(user, &User.Info.mastodon_settings_update(&1, settings)) do
250 json(conn, %{})
251 else
252 e ->
253 conn
254 |> put_status(:internal_server_error)
255 |> json(%{error: inspect(e)})
256 end
257 end
258
259 # Stubs for unimplemented mastodon api
260 #
261 def empty_array(conn, _) do
262 Logger.debug("Unimplemented, returning an empty array")
263 json(conn, [])
264 end
265
266 def empty_object(conn, _) do
267 Logger.debug("Unimplemented, returning an empty object")
268 json(conn, %{})
269 end
270
271 defp present?(nil), do: false
272 defp present?(false), do: false
273 defp present?(_), do: true
274 end