Merge branch 'split-masto-api/auth' into 'develop'
[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 upload_limit: Keyword.get(instance, :upload_limit),
48 avatar_upload_limit: Keyword.get(instance, :avatar_upload_limit),
49 background_upload_limit: Keyword.get(instance, :background_upload_limit),
50 banner_upload_limit: Keyword.get(instance, :banner_upload_limit)
51 }
52
53 json(conn, response)
54 end
55
56 def peers(conn, _params) do
57 json(conn, Stats.get_peers())
58 end
59
60 defp mastodonized_emoji do
61 Pleroma.Emoji.get_all()
62 |> Enum.map(fn {shortcode, %Pleroma.Emoji{file: relative_url, tags: tags}} ->
63 url = to_string(URI.merge(Web.base_url(), relative_url))
64
65 %{
66 "shortcode" => shortcode,
67 "static_url" => url,
68 "visible_in_picker" => true,
69 "url" => url,
70 "tags" => tags,
71 # Assuming that a comma is authorized in the category name
72 "category" => (tags -- ["Custom"]) |> Enum.join(",")
73 }
74 end)
75 end
76
77 def custom_emojis(conn, _params) do
78 mastodon_emoji = mastodonized_emoji()
79 json(conn, mastodon_emoji)
80 end
81
82 def follows(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do
83 with {_, %User{} = followed} <- {:followed, User.get_cached_by_nickname(uri)},
84 {_, true} <- {:followed, follower.id != followed.id},
85 {:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do
86 conn
87 |> put_view(AccountView)
88 |> render("show.json", %{user: followed, for: follower})
89 else
90 {:followed, _} ->
91 {:error, :not_found}
92
93 {:error, message} ->
94 conn
95 |> put_status(:forbidden)
96 |> json(%{error: message})
97 end
98 end
99
100 def mutes(%{assigns: %{user: user}} = conn, _) do
101 with muted_accounts <- User.muted_users(user) do
102 res = AccountView.render("index.json", users: muted_accounts, for: user, as: :user)
103 json(conn, res)
104 end
105 end
106
107 def blocks(%{assigns: %{user: user}} = conn, _) do
108 with blocked_accounts <- User.blocked_users(user) do
109 res = AccountView.render("index.json", users: blocked_accounts, for: user, as: :user)
110 json(conn, res)
111 end
112 end
113
114 def favourites(%{assigns: %{user: user}} = conn, params) do
115 params =
116 params
117 |> Map.put("type", "Create")
118 |> Map.put("favorited_by", user.ap_id)
119 |> Map.put("blocking_user", user)
120
121 activities =
122 ActivityPub.fetch_activities([], params)
123 |> Enum.reverse()
124
125 conn
126 |> add_link_headers(activities)
127 |> put_view(StatusView)
128 |> render("index.json", %{activities: activities, for: user, as: :activity})
129 end
130
131 def bookmarks(%{assigns: %{user: user}} = conn, params) do
132 user = User.get_cached_by_id(user.id)
133
134 bookmarks =
135 Bookmark.for_user_query(user.id)
136 |> Pagination.fetch_paginated(params)
137
138 activities =
139 bookmarks
140 |> Enum.map(fn b -> Map.put(b.activity, :bookmark, Map.delete(b, :activity)) end)
141
142 conn
143 |> add_link_headers(bookmarks)
144 |> put_view(StatusView)
145 |> render("index.json", %{activities: activities, for: user, as: :activity})
146 end
147
148 def index(%{assigns: %{user: user}} = conn, _params) do
149 token = get_session(conn, :oauth_token)
150
151 if user && token do
152 mastodon_emoji = mastodonized_emoji()
153
154 limit = Config.get([:instance, :limit])
155
156 accounts = Map.put(%{}, user.id, AccountView.render("show.json", %{user: user, for: user}))
157
158 initial_state =
159 %{
160 meta: %{
161 streaming_api_base_url: Pleroma.Web.Endpoint.websocket_url(),
162 access_token: token,
163 locale: "en",
164 domain: Pleroma.Web.Endpoint.host(),
165 admin: "1",
166 me: "#{user.id}",
167 unfollow_modal: false,
168 boost_modal: false,
169 delete_modal: true,
170 auto_play_gif: false,
171 display_sensitive_media: false,
172 reduce_motion: false,
173 max_toot_chars: limit,
174 mascot: User.get_mascot(user)["url"]
175 },
176 poll_limits: Config.get([:instance, :poll_limits]),
177 rights: %{
178 delete_others_notice: present?(user.info.is_moderator),
179 admin: present?(user.info.is_admin)
180 },
181 compose: %{
182 me: "#{user.id}",
183 default_privacy: user.info.default_scope,
184 default_sensitive: false,
185 allow_content_types: Config.get([:instance, :allowed_post_formats])
186 },
187 media_attachments: %{
188 accept_content_types: [
189 ".jpg",
190 ".jpeg",
191 ".png",
192 ".gif",
193 ".webm",
194 ".mp4",
195 ".m4v",
196 "image\/jpeg",
197 "image\/png",
198 "image\/gif",
199 "video\/webm",
200 "video\/mp4"
201 ]
202 },
203 settings:
204 user.info.settings ||
205 %{
206 onboarded: true,
207 home: %{
208 shows: %{
209 reblog: true,
210 reply: true
211 }
212 },
213 notifications: %{
214 alerts: %{
215 follow: true,
216 favourite: true,
217 reblog: true,
218 mention: true
219 },
220 shows: %{
221 follow: true,
222 favourite: true,
223 reblog: true,
224 mention: true
225 },
226 sounds: %{
227 follow: true,
228 favourite: true,
229 reblog: true,
230 mention: true
231 }
232 }
233 },
234 push_subscription: nil,
235 accounts: accounts,
236 custom_emojis: mastodon_emoji,
237 char_limit: limit
238 }
239 |> Jason.encode!()
240
241 conn
242 |> put_layout(false)
243 |> put_view(MastodonView)
244 |> render("index.html", %{initial_state: initial_state})
245 else
246 conn
247 |> put_session(:return_to, conn.request_path)
248 |> redirect(to: "/web/login")
249 end
250 end
251
252 def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _params) do
253 with {:ok, _} <- User.update_info(user, &User.Info.mastodon_settings_update(&1, settings)) do
254 json(conn, %{})
255 else
256 e ->
257 conn
258 |> put_status(:internal_server_error)
259 |> json(%{error: inspect(e)})
260 end
261 end
262
263 # Stubs for unimplemented mastodon api
264 #
265 def empty_array(conn, _) do
266 Logger.debug("Unimplemented, returning an empty array")
267 json(conn, [])
268 end
269
270 def empty_object(conn, _) do
271 Logger.debug("Unimplemented, returning an empty object")
272 json(conn, %{})
273 end
274
275 defp present?(nil), do: false
276 defp present?(false), do: false
277 defp present?(_), do: true
278 end