forked from AkkomaGang/akkoma
Merge branch 'develop' of https://git.pleroma.social/pleroma/pleroma into develop
This commit is contained in:
commit
d4348eeb62
177 changed files with 5312 additions and 1827 deletions
2
.mailmap
Normal file
2
.mailmap
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Ariadne Conill <ariadne@dereferenced.org> <nenolod@dereferenced.org>
|
||||||
|
Ariadne Conill <ariadne@dereferenced.org> <nenolod@gmail.com>
|
403
CC-BY-NC-ND-4.0
403
CC-BY-NC-ND-4.0
|
@ -1,403 +0,0 @@
|
||||||
Attribution-NonCommercial-NoDerivatives 4.0 International
|
|
||||||
|
|
||||||
=======================================================================
|
|
||||||
|
|
||||||
Creative Commons Corporation ("Creative Commons") is not a law firm and
|
|
||||||
does not provide legal services or legal advice. Distribution of
|
|
||||||
Creative Commons public licenses does not create a lawyer-client or
|
|
||||||
other relationship. Creative Commons makes its licenses and related
|
|
||||||
information available on an "as-is" basis. Creative Commons gives no
|
|
||||||
warranties regarding its licenses, any material licensed under their
|
|
||||||
terms and conditions, or any related information. Creative Commons
|
|
||||||
disclaims all liability for damages resulting from their use to the
|
|
||||||
fullest extent possible.
|
|
||||||
|
|
||||||
Using Creative Commons Public Licenses
|
|
||||||
|
|
||||||
Creative Commons public licenses provide a standard set of terms and
|
|
||||||
conditions that creators and other rights holders may use to share
|
|
||||||
original works of authorship and other material subject to copyright
|
|
||||||
and certain other rights specified in the public license below. The
|
|
||||||
following considerations are for informational purposes only, are not
|
|
||||||
exhaustive, and do not form part of our licenses.
|
|
||||||
|
|
||||||
Considerations for licensors: Our public licenses are
|
|
||||||
intended for use by those authorized to give the public
|
|
||||||
permission to use material in ways otherwise restricted by
|
|
||||||
copyright and certain other rights. Our licenses are
|
|
||||||
irrevocable. Licensors should read and understand the terms
|
|
||||||
and conditions of the license they choose before applying it.
|
|
||||||
Licensors should also secure all rights necessary before
|
|
||||||
applying our licenses so that the public can reuse the
|
|
||||||
material as expected. Licensors should clearly mark any
|
|
||||||
material not subject to the license. This includes other CC-
|
|
||||||
licensed material, or material used under an exception or
|
|
||||||
limitation to copyright. More considerations for licensors:
|
|
||||||
wiki.creativecommons.org/Considerations_for_licensors
|
|
||||||
|
|
||||||
Considerations for the public: By using one of our public
|
|
||||||
licenses, a licensor grants the public permission to use the
|
|
||||||
licensed material under specified terms and conditions. If
|
|
||||||
the licensor's permission is not necessary for any reason--for
|
|
||||||
example, because of any applicable exception or limitation to
|
|
||||||
copyright--then that use is not regulated by the license. Our
|
|
||||||
licenses grant only permissions under copyright and certain
|
|
||||||
other rights that a licensor has authority to grant. Use of
|
|
||||||
the licensed material may still be restricted for other
|
|
||||||
reasons, including because others have copyright or other
|
|
||||||
rights in the material. A licensor may make special requests,
|
|
||||||
such as asking that all changes be marked or described.
|
|
||||||
Although not required by our licenses, you are encouraged to
|
|
||||||
respect those requests where reasonable. More considerations
|
|
||||||
for the public:
|
|
||||||
wiki.creativecommons.org/Considerations_for_licensees
|
|
||||||
|
|
||||||
=======================================================================
|
|
||||||
|
|
||||||
Creative Commons Attribution-NonCommercial-NoDerivatives 4.0
|
|
||||||
International Public License
|
|
||||||
|
|
||||||
By exercising the Licensed Rights (defined below), You accept and agree
|
|
||||||
to be bound by the terms and conditions of this Creative Commons
|
|
||||||
Attribution-NonCommercial-NoDerivatives 4.0 International Public
|
|
||||||
License ("Public License"). To the extent this Public License may be
|
|
||||||
interpreted as a contract, You are granted the Licensed Rights in
|
|
||||||
consideration of Your acceptance of these terms and conditions, and the
|
|
||||||
Licensor grants You such rights in consideration of benefits the
|
|
||||||
Licensor receives from making the Licensed Material available under
|
|
||||||
these terms and conditions.
|
|
||||||
|
|
||||||
|
|
||||||
Section 1 -- Definitions.
|
|
||||||
|
|
||||||
a. Adapted Material means material subject to Copyright and Similar
|
|
||||||
Rights that is derived from or based upon the Licensed Material
|
|
||||||
and in which the Licensed Material is translated, altered,
|
|
||||||
arranged, transformed, or otherwise modified in a manner requiring
|
|
||||||
permission under the Copyright and Similar Rights held by the
|
|
||||||
Licensor. For purposes of this Public License, where the Licensed
|
|
||||||
Material is a musical work, performance, or sound recording,
|
|
||||||
Adapted Material is always produced where the Licensed Material is
|
|
||||||
synched in timed relation with a moving image.
|
|
||||||
|
|
||||||
b. Copyright and Similar Rights means copyright and/or similar rights
|
|
||||||
closely related to copyright including, without limitation,
|
|
||||||
performance, broadcast, sound recording, and Sui Generis Database
|
|
||||||
Rights, without regard to how the rights are labeled or
|
|
||||||
categorized. For purposes of this Public License, the rights
|
|
||||||
specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
|
||||||
Rights.
|
|
||||||
|
|
||||||
c. Effective Technological Measures means those measures that, in the
|
|
||||||
absence of proper authority, may not be circumvented under laws
|
|
||||||
fulfilling obligations under Article 11 of the WIPO Copyright
|
|
||||||
Treaty adopted on December 20, 1996, and/or similar international
|
|
||||||
agreements.
|
|
||||||
|
|
||||||
d. Exceptions and Limitations means fair use, fair dealing, and/or
|
|
||||||
any other exception or limitation to Copyright and Similar Rights
|
|
||||||
that applies to Your use of the Licensed Material.
|
|
||||||
|
|
||||||
e. Licensed Material means the artistic or literary work, database,
|
|
||||||
or other material to which the Licensor applied this Public
|
|
||||||
License.
|
|
||||||
|
|
||||||
f. Licensed Rights means the rights granted to You subject to the
|
|
||||||
terms and conditions of this Public License, which are limited to
|
|
||||||
all Copyright and Similar Rights that apply to Your use of the
|
|
||||||
Licensed Material and that the Licensor has authority to license.
|
|
||||||
|
|
||||||
g. Licensor means the individual(s) or entity(ies) granting rights
|
|
||||||
under this Public License.
|
|
||||||
|
|
||||||
h. NonCommercial means not primarily intended for or directed towards
|
|
||||||
commercial advantage or monetary compensation. For purposes of
|
|
||||||
this Public License, the exchange of the Licensed Material for
|
|
||||||
other material subject to Copyright and Similar Rights by digital
|
|
||||||
file-sharing or similar means is NonCommercial provided there is
|
|
||||||
no payment of monetary compensation in connection with the
|
|
||||||
exchange.
|
|
||||||
|
|
||||||
i. Share means to provide material to the public by any means or
|
|
||||||
process that requires permission under the Licensed Rights, such
|
|
||||||
as reproduction, public display, public performance, distribution,
|
|
||||||
dissemination, communication, or importation, and to make material
|
|
||||||
available to the public including in ways that members of the
|
|
||||||
public may access the material from a place and at a time
|
|
||||||
individually chosen by them.
|
|
||||||
|
|
||||||
j. Sui Generis Database Rights means rights other than copyright
|
|
||||||
resulting from Directive 96/9/EC of the European Parliament and of
|
|
||||||
the Council of 11 March 1996 on the legal protection of databases,
|
|
||||||
as amended and/or succeeded, as well as other essentially
|
|
||||||
equivalent rights anywhere in the world.
|
|
||||||
|
|
||||||
k. You means the individual or entity exercising the Licensed Rights
|
|
||||||
under this Public License. Your has a corresponding meaning.
|
|
||||||
|
|
||||||
|
|
||||||
Section 2 -- Scope.
|
|
||||||
|
|
||||||
a. License grant.
|
|
||||||
|
|
||||||
1. Subject to the terms and conditions of this Public License,
|
|
||||||
the Licensor hereby grants You a worldwide, royalty-free,
|
|
||||||
non-sublicensable, non-exclusive, irrevocable license to
|
|
||||||
exercise the Licensed Rights in the Licensed Material to:
|
|
||||||
|
|
||||||
a. reproduce and Share the Licensed Material, in whole or
|
|
||||||
in part, for NonCommercial purposes only; and
|
|
||||||
|
|
||||||
b. produce and reproduce, but not Share, Adapted Material
|
|
||||||
for NonCommercial purposes only.
|
|
||||||
|
|
||||||
2. Exceptions and Limitations. For the avoidance of doubt, where
|
|
||||||
Exceptions and Limitations apply to Your use, this Public
|
|
||||||
License does not apply, and You do not need to comply with
|
|
||||||
its terms and conditions.
|
|
||||||
|
|
||||||
3. Term. The term of this Public License is specified in Section
|
|
||||||
6(a).
|
|
||||||
|
|
||||||
4. Media and formats; technical modifications allowed. The
|
|
||||||
Licensor authorizes You to exercise the Licensed Rights in
|
|
||||||
all media and formats whether now known or hereafter created,
|
|
||||||
and to make technical modifications necessary to do so. The
|
|
||||||
Licensor waives and/or agrees not to assert any right or
|
|
||||||
authority to forbid You from making technical modifications
|
|
||||||
necessary to exercise the Licensed Rights, including
|
|
||||||
technical modifications necessary to circumvent Effective
|
|
||||||
Technological Measures. For purposes of this Public License,
|
|
||||||
simply making modifications authorized by this Section 2(a)
|
|
||||||
(4) never produces Adapted Material.
|
|
||||||
|
|
||||||
5. Downstream recipients.
|
|
||||||
|
|
||||||
a. Offer from the Licensor -- Licensed Material. Every
|
|
||||||
recipient of the Licensed Material automatically
|
|
||||||
receives an offer from the Licensor to exercise the
|
|
||||||
Licensed Rights under the terms and conditions of this
|
|
||||||
Public License.
|
|
||||||
|
|
||||||
b. No downstream restrictions. You may not offer or impose
|
|
||||||
any additional or different terms or conditions on, or
|
|
||||||
apply any Effective Technological Measures to, the
|
|
||||||
Licensed Material if doing so restricts exercise of the
|
|
||||||
Licensed Rights by any recipient of the Licensed
|
|
||||||
Material.
|
|
||||||
|
|
||||||
6. No endorsement. Nothing in this Public License constitutes or
|
|
||||||
may be construed as permission to assert or imply that You
|
|
||||||
are, or that Your use of the Licensed Material is, connected
|
|
||||||
with, or sponsored, endorsed, or granted official status by,
|
|
||||||
the Licensor or others designated to receive attribution as
|
|
||||||
provided in Section 3(a)(1)(A)(i).
|
|
||||||
|
|
||||||
b. Other rights.
|
|
||||||
|
|
||||||
1. Moral rights, such as the right of integrity, are not
|
|
||||||
licensed under this Public License, nor are publicity,
|
|
||||||
privacy, and/or other similar personality rights; however, to
|
|
||||||
the extent possible, the Licensor waives and/or agrees not to
|
|
||||||
assert any such rights held by the Licensor to the limited
|
|
||||||
extent necessary to allow You to exercise the Licensed
|
|
||||||
Rights, but not otherwise.
|
|
||||||
|
|
||||||
2. Patent and trademark rights are not licensed under this
|
|
||||||
Public License.
|
|
||||||
|
|
||||||
3. To the extent possible, the Licensor waives any right to
|
|
||||||
collect royalties from You for the exercise of the Licensed
|
|
||||||
Rights, whether directly or through a collecting society
|
|
||||||
under any voluntary or waivable statutory or compulsory
|
|
||||||
licensing scheme. In all other cases the Licensor expressly
|
|
||||||
reserves any right to collect such royalties, including when
|
|
||||||
the Licensed Material is used other than for NonCommercial
|
|
||||||
purposes.
|
|
||||||
|
|
||||||
|
|
||||||
Section 3 -- License Conditions.
|
|
||||||
|
|
||||||
Your exercise of the Licensed Rights is expressly made subject to the
|
|
||||||
following conditions.
|
|
||||||
|
|
||||||
a. Attribution.
|
|
||||||
|
|
||||||
1. If You Share the Licensed Material, You must:
|
|
||||||
|
|
||||||
a. retain the following if it is supplied by the Licensor
|
|
||||||
with the Licensed Material:
|
|
||||||
|
|
||||||
i. identification of the creator(s) of the Licensed
|
|
||||||
Material and any others designated to receive
|
|
||||||
attribution, in any reasonable manner requested by
|
|
||||||
the Licensor (including by pseudonym if
|
|
||||||
designated);
|
|
||||||
|
|
||||||
ii. a copyright notice;
|
|
||||||
|
|
||||||
iii. a notice that refers to this Public License;
|
|
||||||
|
|
||||||
iv. a notice that refers to the disclaimer of
|
|
||||||
warranties;
|
|
||||||
|
|
||||||
v. a URI or hyperlink to the Licensed Material to the
|
|
||||||
extent reasonably practicable;
|
|
||||||
|
|
||||||
b. indicate if You modified the Licensed Material and
|
|
||||||
retain an indication of any previous modifications; and
|
|
||||||
|
|
||||||
c. indicate the Licensed Material is licensed under this
|
|
||||||
Public License, and include the text of, or the URI or
|
|
||||||
hyperlink to, this Public License.
|
|
||||||
|
|
||||||
For the avoidance of doubt, You do not have permission under
|
|
||||||
this Public License to Share Adapted Material.
|
|
||||||
|
|
||||||
2. You may satisfy the conditions in Section 3(a)(1) in any
|
|
||||||
reasonable manner based on the medium, means, and context in
|
|
||||||
which You Share the Licensed Material. For example, it may be
|
|
||||||
reasonable to satisfy the conditions by providing a URI or
|
|
||||||
hyperlink to a resource that includes the required
|
|
||||||
information.
|
|
||||||
|
|
||||||
3. If requested by the Licensor, You must remove any of the
|
|
||||||
information required by Section 3(a)(1)(A) to the extent
|
|
||||||
reasonably practicable.
|
|
||||||
|
|
||||||
|
|
||||||
Section 4 -- Sui Generis Database Rights.
|
|
||||||
|
|
||||||
Where the Licensed Rights include Sui Generis Database Rights that
|
|
||||||
apply to Your use of the Licensed Material:
|
|
||||||
|
|
||||||
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
|
|
||||||
to extract, reuse, reproduce, and Share all or a substantial
|
|
||||||
portion of the contents of the database for NonCommercial purposes
|
|
||||||
only and provided You do not Share Adapted Material;
|
|
||||||
|
|
||||||
b. if You include all or a substantial portion of the database
|
|
||||||
contents in a database in which You have Sui Generis Database
|
|
||||||
Rights, then the database in which You have Sui Generis Database
|
|
||||||
Rights (but not its individual contents) is Adapted Material; and
|
|
||||||
|
|
||||||
c. You must comply with the conditions in Section 3(a) if You Share
|
|
||||||
all or a substantial portion of the contents of the database.
|
|
||||||
|
|
||||||
For the avoidance of doubt, this Section 4 supplements and does not
|
|
||||||
replace Your obligations under this Public License where the Licensed
|
|
||||||
Rights include other Copyright and Similar Rights.
|
|
||||||
|
|
||||||
|
|
||||||
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
|
||||||
|
|
||||||
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
|
|
||||||
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
|
|
||||||
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
|
|
||||||
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
|
|
||||||
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
|
|
||||||
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
||||||
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
|
|
||||||
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
|
|
||||||
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
|
|
||||||
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
|
|
||||||
|
|
||||||
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
|
|
||||||
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
|
|
||||||
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
|
|
||||||
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
|
|
||||||
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
|
|
||||||
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
|
|
||||||
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
|
|
||||||
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
|
|
||||||
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
|
|
||||||
|
|
||||||
c. The disclaimer of warranties and limitation of liability provided
|
|
||||||
above shall be interpreted in a manner that, to the extent
|
|
||||||
possible, most closely approximates an absolute disclaimer and
|
|
||||||
waiver of all liability.
|
|
||||||
|
|
||||||
|
|
||||||
Section 6 -- Term and Termination.
|
|
||||||
|
|
||||||
a. This Public License applies for the term of the Copyright and
|
|
||||||
Similar Rights licensed here. However, if You fail to comply with
|
|
||||||
this Public License, then Your rights under this Public License
|
|
||||||
terminate automatically.
|
|
||||||
|
|
||||||
b. Where Your right to use the Licensed Material has terminated under
|
|
||||||
Section 6(a), it reinstates:
|
|
||||||
|
|
||||||
1. automatically as of the date the violation is cured, provided
|
|
||||||
it is cured within 30 days of Your discovery of the
|
|
||||||
violation; or
|
|
||||||
|
|
||||||
2. upon express reinstatement by the Licensor.
|
|
||||||
|
|
||||||
For the avoidance of doubt, this Section 6(b) does not affect any
|
|
||||||
right the Licensor may have to seek remedies for Your violations
|
|
||||||
of this Public License.
|
|
||||||
|
|
||||||
c. For the avoidance of doubt, the Licensor may also offer the
|
|
||||||
Licensed Material under separate terms or conditions or stop
|
|
||||||
distributing the Licensed Material at any time; however, doing so
|
|
||||||
will not terminate this Public License.
|
|
||||||
|
|
||||||
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
|
|
||||||
License.
|
|
||||||
|
|
||||||
|
|
||||||
Section 7 -- Other Terms and Conditions.
|
|
||||||
|
|
||||||
a. The Licensor shall not be bound by any additional or different
|
|
||||||
terms or conditions communicated by You unless expressly agreed.
|
|
||||||
|
|
||||||
b. Any arrangements, understandings, or agreements regarding the
|
|
||||||
Licensed Material not stated herein are separate from and
|
|
||||||
independent of the terms and conditions of this Public License.
|
|
||||||
|
|
||||||
|
|
||||||
Section 8 -- Interpretation.
|
|
||||||
|
|
||||||
a. For the avoidance of doubt, this Public License does not, and
|
|
||||||
shall not be interpreted to, reduce, limit, restrict, or impose
|
|
||||||
conditions on any use of the Licensed Material that could lawfully
|
|
||||||
be made without permission under this Public License.
|
|
||||||
|
|
||||||
b. To the extent possible, if any provision of this Public License is
|
|
||||||
deemed unenforceable, it shall be automatically reformed to the
|
|
||||||
minimum extent necessary to make it enforceable. If the provision
|
|
||||||
cannot be reformed, it shall be severed from this Public License
|
|
||||||
without affecting the enforceability of the remaining terms and
|
|
||||||
conditions.
|
|
||||||
|
|
||||||
c. No term or condition of this Public License will be waived and no
|
|
||||||
failure to comply consented to unless expressly agreed to by the
|
|
||||||
Licensor.
|
|
||||||
|
|
||||||
d. Nothing in this Public License constitutes or may be interpreted
|
|
||||||
as a limitation upon, or waiver of, any privileges and immunities
|
|
||||||
that apply to the Licensor or You, including from the legal
|
|
||||||
processes of any jurisdiction or authority.
|
|
||||||
|
|
||||||
=======================================================================
|
|
||||||
|
|
||||||
Creative Commons is not a party to its public
|
|
||||||
licenses. Notwithstanding, Creative Commons may elect to apply one of
|
|
||||||
its public licenses to material it publishes and in those instances
|
|
||||||
will be considered the “Licensor.” The text of the Creative Commons
|
|
||||||
public licenses is dedicated to the public domain under the CC0 Public
|
|
||||||
Domain Dedication. Except for the limited purpose of indicating that
|
|
||||||
material is shared under a Creative Commons public license or as
|
|
||||||
otherwise permitted by the Creative Commons policies published at
|
|
||||||
creativecommons.org/policies, Creative Commons does not authorize the
|
|
||||||
use of the trademark "Creative Commons" or any other trademark or logo
|
|
||||||
of Creative Commons without its prior written consent including,
|
|
||||||
without limitation, in connection with any unauthorized modifications
|
|
||||||
to any of its public licenses or any other arrangements,
|
|
||||||
understandings, or agreements concerning use of licensed material. For
|
|
||||||
the avoidance of doubt, this paragraph does not form part of the
|
|
||||||
public licenses.
|
|
||||||
|
|
||||||
Creative Commons may be contacted at creativecommons.org.
|
|
||||||
|
|
45
CHANGELOG.md
45
CHANGELOG.md
|
@ -4,29 +4,48 @@ All notable changes to this project will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
### Added
|
### Changed
|
||||||
- MRF: Support for priming the mediaproxy cache (`Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`)
|
- **Breaking:** Configuration: A setting to explicitly disable the mailer was added, defaulting to true, if you are using a mailer add `config :pleroma, Pleroma.Emails.Mailer, enabled: true` to your config
|
||||||
Configuration: `federation_incoming_replies_max_depth` option
|
- Configuration: OpenGraph and TwitterCard providers enabled by default
|
||||||
- Mastodon API: Support for the [`tagged` filter](https://github.com/tootsuite/mastodon/pull/9755) in [`GET /api/v1/accounts/:id/statuses`](https://docs.joinmastodon.org/api/rest/accounts/#get-api-v1-accounts-id-statuses)
|
- Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text
|
||||||
- Mastodon API, streaming: Add support for passing the token in the `Sec-WebSocket-Protocol` header
|
- Federation: Return 403 errors when trying to request pages from a user's follower/following collections if they have `hide_followers`/`hide_follows` set
|
||||||
- Mastodon API, extension: Ability to reset avatar, profile banner, and background
|
- NodeInfo: Return `skipThreadContainment` in `metadata` for the `skip_thread_containment` option
|
||||||
- Admin API: Return users' tags when querying reports
|
- Mastodon API: Unsubscribe followers when they unfollow a user
|
||||||
- Admin API: Return avatar and display name when querying users
|
|
||||||
- Admin API: Allow querying user by ID
|
|
||||||
- Added synchronization of following/followers counters for external users
|
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Not being able to pin unlisted posts
|
- Not being able to pin unlisted posts
|
||||||
- Metadata rendering errors resulting in the entire page being inaccessible
|
- Metadata rendering errors resulting in the entire page being inaccessible
|
||||||
|
- 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: 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: Embedded relationships not being properly rendered in the Account entity of Status entity
|
||||||
|
- Mastodon API: Add `account_id`, `type`, `offset`, and `limit` to search API (`/api/v1/search` and `/api/v2/search`)
|
||||||
|
- ActivityPub C2S: follower/following collection pages being inaccessible even when authentifucated if `hide_followers`/ `hide_follows` was set
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- MRF: Support for priming the mediaproxy cache (`Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`)
|
||||||
|
- MRF: Support for excluding specific domains from Transparency.
|
||||||
|
- Configuration: `federation_incoming_replies_max_depth` option
|
||||||
|
- Mastodon API: Support for the [`tagged` filter](https://github.com/tootsuite/mastodon/pull/9755) in [`GET /api/v1/accounts/:id/statuses`](https://docs.joinmastodon.org/api/rest/accounts/#get-api-v1-accounts-id-statuses)
|
||||||
|
- Mastodon API, streaming: Add support for passing the token in the `Sec-WebSocket-Protocol` header
|
||||||
|
- Mastodon API, extension: Ability to reset avatar, profile banner, and background
|
||||||
|
- Mastodon API: Add support for categories for custom emojis by reusing the group feature. <https://github.com/tootsuite/mastodon/pull/11196>
|
||||||
|
- Mastodon API: Add support for muting/unmuting notifications
|
||||||
|
- Admin API: Return users' tags when querying reports
|
||||||
|
- Admin API: Return avatar and display name when querying users
|
||||||
|
- Admin API: Allow querying user by ID
|
||||||
|
- Admin API: Added support for `tuples`.
|
||||||
|
- 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.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Configuration: OpenGraph and TwitterCard providers enabled by default
|
|
||||||
- Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text
|
- Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text
|
||||||
|
- Admin API: changed json structure for saving config settings.
|
||||||
|
- RichMedia: parsers and their order are configured in `rich_media` config.
|
||||||
|
|
||||||
### Changed
|
## [1.0.1] - 2019-07-14
|
||||||
- NodeInfo: Return `skipThreadContainment` in `metadata` for the `skip_thread_containment` option
|
### Security
|
||||||
|
- OStatus: fix an object spoofing vulnerability.
|
||||||
|
|
||||||
## [1.0.0] - 2019-06-29
|
## [1.0.0] - 2019-06-29
|
||||||
### Security
|
### Security
|
||||||
|
|
|
@ -194,6 +194,8 @@
|
||||||
send_user_agent: true,
|
send_user_agent: true,
|
||||||
adapter: [
|
adapter: [
|
||||||
ssl_options: [
|
ssl_options: [
|
||||||
|
# Workaround for remote server certificate chain issues
|
||||||
|
partial_chain: &:hackney_connect.partial_chain/1,
|
||||||
# We don't support TLS v1.3 yet
|
# We don't support TLS v1.3 yet
|
||||||
versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"]
|
versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"]
|
||||||
]
|
]
|
||||||
|
@ -238,6 +240,7 @@
|
||||||
"text/bbcode"
|
"text/bbcode"
|
||||||
],
|
],
|
||||||
mrf_transparency: true,
|
mrf_transparency: true,
|
||||||
|
mrf_transparency_exclusions: [],
|
||||||
autofollowed_nicknames: [],
|
autofollowed_nicknames: [],
|
||||||
max_pinned_statuses: 1,
|
max_pinned_statuses: 1,
|
||||||
no_attachment_links: false,
|
no_attachment_links: false,
|
||||||
|
@ -250,13 +253,7 @@
|
||||||
skip_thread_containment: true,
|
skip_thread_containment: true,
|
||||||
limit_to_local_content: :unauthenticated,
|
limit_to_local_content: :unauthenticated,
|
||||||
dynamic_configuration: false,
|
dynamic_configuration: false,
|
||||||
external_user_synchronization: [
|
external_user_synchronization: true
|
||||||
enabled: false,
|
|
||||||
# every 2 hours
|
|
||||||
interval: 60 * 60 * 2,
|
|
||||||
max_retries: 3,
|
|
||||||
limit: 500
|
|
||||||
]
|
|
||||||
|
|
||||||
config :pleroma, :markup,
|
config :pleroma, :markup,
|
||||||
# XXX - unfortunately, inline images must be enabled by default right now, because
|
# XXX - unfortunately, inline images must be enabled by default right now, because
|
||||||
|
@ -342,7 +339,12 @@
|
||||||
config :pleroma, :rich_media,
|
config :pleroma, :rich_media,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
ignore_hosts: [],
|
ignore_hosts: [],
|
||||||
ignore_tld: ["local", "localdomain", "lan"]
|
ignore_tld: ["local", "localdomain", "lan"],
|
||||||
|
parsers: [
|
||||||
|
Pleroma.Web.RichMedia.Parsers.TwitterCard,
|
||||||
|
Pleroma.Web.RichMedia.Parsers.OGP,
|
||||||
|
Pleroma.Web.RichMedia.Parsers.OEmbed
|
||||||
|
]
|
||||||
|
|
||||||
config :pleroma, :media_proxy,
|
config :pleroma, :media_proxy,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
@ -501,7 +503,7 @@
|
||||||
|
|
||||||
config :pleroma, :auth, oauth_consumer_strategies: oauth_consumer_strategies
|
config :pleroma, :auth, oauth_consumer_strategies: oauth_consumer_strategies
|
||||||
|
|
||||||
config :pleroma, Pleroma.Emails.Mailer, adapter: Swoosh.Adapters.Sendmail
|
config :pleroma, Pleroma.Emails.Mailer, adapter: Swoosh.Adapters.Sendmail, enabled: false
|
||||||
|
|
||||||
config :prometheus, Pleroma.Web.Endpoint.MetricsExporter, path: "/api/pleroma/app_metrics"
|
config :prometheus, Pleroma.Web.Endpoint.MetricsExporter, path: "/api/pleroma/app_metrics"
|
||||||
|
|
||||||
|
@ -525,7 +527,9 @@
|
||||||
|
|
||||||
config :pleroma, :rate_limit,
|
config :pleroma, :rate_limit,
|
||||||
search: [{1000, 10}, {1000, 30}],
|
search: [{1000, 10}, {1000, 30}],
|
||||||
app_account_creation: {1_800_000, 25}
|
app_account_creation: {1_800_000, 25},
|
||||||
|
statuses_actions: {10_000, 15},
|
||||||
|
status_id_action: {60_000, 3}
|
||||||
|
|
||||||
# Import environment specific config. This must remain at the bottom
|
# Import environment specific config. This must remain at the bottom
|
||||||
# of this file so it overrides the configuration defined above.
|
# of this file so it overrides the configuration defined above.
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
|
|
||||||
config :pleroma, Pleroma.Uploaders.Local, uploads: "test/uploads"
|
config :pleroma, Pleroma.Uploaders.Local, uploads: "test/uploads"
|
||||||
|
|
||||||
config :pleroma, Pleroma.Emails.Mailer, adapter: Swoosh.Adapters.Test
|
config :pleroma, Pleroma.Emails.Mailer, adapter: Swoosh.Adapters.Test, enabled: true
|
||||||
|
|
||||||
config :pleroma, :instance,
|
config :pleroma, :instance,
|
||||||
email: "admin@example.com",
|
email: "admin@example.com",
|
||||||
|
@ -65,7 +65,9 @@
|
||||||
total_user_limit: 3,
|
total_user_limit: 3,
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
||||||
config :pleroma, :rate_limit, app_account_creation: {10_000, 5}
|
config :pleroma, :rate_limit,
|
||||||
|
search: [{1000, 30}, {1000, 30}],
|
||||||
|
app_account_creation: {10_000, 5}
|
||||||
|
|
||||||
config :pleroma, :http_security, report_uri: "https://endpoint.com"
|
config :pleroma, :http_security, report_uri: "https://endpoint.com"
|
||||||
|
|
||||||
|
|
|
@ -573,7 +573,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
||||||
configs: [
|
configs: [
|
||||||
{
|
{
|
||||||
"group": string,
|
"group": string,
|
||||||
"key": string,
|
"key": string or string with leading `:` for atoms,
|
||||||
"value": string or {} or [] or {"tuple": []}
|
"value": string or {} or [] or {"tuple": []}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -583,10 +583,11 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
||||||
## `/api/pleroma/admin/config`
|
## `/api/pleroma/admin/config`
|
||||||
### Update config settings
|
### Update config settings
|
||||||
Module name can be passed as string, which starts with `Pleroma`, e.g. `"Pleroma.Upload"`.
|
Module name can be passed as string, which starts with `Pleroma`, e.g. `"Pleroma.Upload"`.
|
||||||
Atom or boolean value can be passed with `:` in the beginning, e.g. `":true"`, `":upload"`. For keys it is not needed.
|
Atom keys and values can be passed with `:` in the beginning, e.g. `":upload"`.
|
||||||
Integer with `i:`, e.g. `"i:150"`.
|
Tuples can be passed as `{"tuple": ["first_val", Pleroma.Module, []]}`.
|
||||||
Tuple with more than 2 values with `{"tuple": ["first_val", Pleroma.Module, []]}`.
|
|
||||||
`{"tuple": ["some_string", "Pleroma.Some.Module", []]}` will be converted to `{"some_string", Pleroma.Some.Module, []}`.
|
`{"tuple": ["some_string", "Pleroma.Some.Module", []]}` will be converted to `{"some_string", Pleroma.Some.Module, []}`.
|
||||||
|
Keywords can be passed as lists with 2 child tuples, e.g.
|
||||||
|
`[{"tuple": ["first_val", Pleroma.Module]}, {"tuple": ["second_val", true]}]`.
|
||||||
|
|
||||||
Compile time settings (need instance reboot):
|
Compile time settings (need instance reboot):
|
||||||
- all settings by this keys:
|
- all settings by this keys:
|
||||||
|
@ -603,7 +604,7 @@ Compile time settings (need instance reboot):
|
||||||
- Params:
|
- Params:
|
||||||
- `configs` => [
|
- `configs` => [
|
||||||
- `group` (string)
|
- `group` (string)
|
||||||
- `key` (string)
|
- `key` (string or string with leading `:` for atoms)
|
||||||
- `value` (string, [], {} or {"tuple": []})
|
- `value` (string, [], {} or {"tuple": []})
|
||||||
- `delete` = true (optional, if parameter must be deleted)
|
- `delete` = true (optional, if parameter must be deleted)
|
||||||
]
|
]
|
||||||
|
@ -616,24 +617,25 @@ Compile time settings (need instance reboot):
|
||||||
{
|
{
|
||||||
"group": "pleroma",
|
"group": "pleroma",
|
||||||
"key": "Pleroma.Upload",
|
"key": "Pleroma.Upload",
|
||||||
"value": {
|
"value": [
|
||||||
"uploader": "Pleroma.Uploaders.Local",
|
{"tuple": [":uploader", "Pleroma.Uploaders.Local"]},
|
||||||
"filters": ["Pleroma.Upload.Filter.Dedupe"],
|
{"tuple": [":filters", ["Pleroma.Upload.Filter.Dedupe"]]},
|
||||||
"link_name": ":true",
|
{"tuple": [":link_name", true]},
|
||||||
"proxy_remote": ":false",
|
{"tuple": [":proxy_remote", false]},
|
||||||
"proxy_opts": {
|
{"tuple": [":proxy_opts", [
|
||||||
"redirect_on_failure": ":false",
|
{"tuple": [":redirect_on_failure", false]},
|
||||||
"max_body_length": "i:1048576",
|
{"tuple": [":max_body_length", 1048576]},
|
||||||
"http": {
|
{"tuple": [":http": [
|
||||||
"follow_redirect": ":true",
|
{"tuple": [":follow_redirect", true]},
|
||||||
"pool": ":upload"
|
{"tuple": [":pool", ":upload"]},
|
||||||
}
|
]]}
|
||||||
},
|
]
|
||||||
"dispatch": {
|
]},
|
||||||
|
{"tuple": [":dispatch", {
|
||||||
"tuple": ["/api/v1/streaming", "Pleroma.Web.MastodonAPI.WebsocketHandler", []]
|
"tuple": ["/api/v1/streaming", "Pleroma.Web.MastodonAPI.WebsocketHandler", []]
|
||||||
}
|
}]}
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -644,7 +646,7 @@ Compile time settings (need instance reboot):
|
||||||
configs: [
|
configs: [
|
||||||
{
|
{
|
||||||
"group": string,
|
"group": string,
|
||||||
"key": string,
|
"key": string or string with leading `:` for atoms,
|
||||||
"value": string or {} or [] or {"tuple": []}
|
"value": string or {} or [] or {"tuple": []}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -46,14 +46,6 @@ Has these additional fields under the `pleroma` object:
|
||||||
- `settings_store`: A generic map of settings for frontends. Opaque to the backend. Only returned in `verify_credentials` and `update_credentials`
|
- `settings_store`: A generic map of settings for frontends. Opaque to the backend. Only returned in `verify_credentials` and `update_credentials`
|
||||||
- `chat_token`: The token needed for Pleroma chat. Only returned in `verify_credentials`
|
- `chat_token`: The token needed for Pleroma chat. Only returned in `verify_credentials`
|
||||||
|
|
||||||
### Extensions for PleromaFE
|
|
||||||
|
|
||||||
These endpoints added for controlling PleromaFE features over the Mastodon API
|
|
||||||
|
|
||||||
- PATCH `/api/v1/accounts/update_avatar`: Set/clear user avatar image
|
|
||||||
- PATCH `/api/v1/accounts/update_banner`: Set/clear user banner image
|
|
||||||
- PATCH `/api/v1/accounts/update_background`: Set/clear user background image
|
|
||||||
|
|
||||||
### Source
|
### Source
|
||||||
|
|
||||||
Has these additional fields under the `pleroma` object:
|
Has these additional fields under the `pleroma` object:
|
||||||
|
|
|
@ -238,6 +238,13 @@ See [Admin-API](Admin-API.md)
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## `/api/v1/pleroma/accounts/update_*`
|
||||||
|
### Set and clear account avatar, banner, and background
|
||||||
|
|
||||||
|
- PATCH `/api/v1/pleroma/accounts/update_avatar`: Set/clear user avatar image
|
||||||
|
- PATCH `/api/v1/pleroma/accounts/update_banner`: Set/clear user banner image
|
||||||
|
- PATCH `/api/v1/pleroma/accounts/update_background`: Set/clear user background image
|
||||||
|
|
||||||
## `/api/v1/pleroma/mascot`
|
## `/api/v1/pleroma/mascot`
|
||||||
### Gets user mascot image
|
### Gets user mascot image
|
||||||
* Method `GET`
|
* Method `GET`
|
||||||
|
|
|
@ -41,6 +41,7 @@ This filter replaces the filename (not the path) of an upload. For complete obfu
|
||||||
## Pleroma.Emails.Mailer
|
## Pleroma.Emails.Mailer
|
||||||
* `adapter`: one of the mail adapters listed in [Swoosh readme](https://github.com/swoosh/swoosh#adapters), or `Swoosh.Adapters.Local` for in-memory mailbox.
|
* `adapter`: one of the mail adapters listed in [Swoosh readme](https://github.com/swoosh/swoosh#adapters), or `Swoosh.Adapters.Local` for in-memory mailbox.
|
||||||
* `api_key` / `password` and / or other adapter-specific settings, per the above documentation.
|
* `api_key` / `password` and / or other adapter-specific settings, per the above documentation.
|
||||||
|
* `enabled`: Allows enable/disable send emails. Default: `false`.
|
||||||
|
|
||||||
An example for Sendgrid adapter:
|
An example for Sendgrid adapter:
|
||||||
|
|
||||||
|
@ -105,6 +106,7 @@ config :pleroma, Pleroma.Emails.Mailer,
|
||||||
* `managed_config`: Whenether the config for pleroma-fe is configured in this config or in ``static/config.json``
|
* `managed_config`: Whenether the config for pleroma-fe is configured in this config or in ``static/config.json``
|
||||||
* `allowed_post_formats`: MIME-type list of formats allowed to be posted (transformed into HTML)
|
* `allowed_post_formats`: MIME-type list of formats allowed to be posted (transformed into HTML)
|
||||||
* `mrf_transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo).
|
* `mrf_transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo).
|
||||||
|
* `mrf_transparency_exclusions`: Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.
|
||||||
* `scope_copy`: Copy the scope (private/unlisted/public) in replies to posts by default.
|
* `scope_copy`: Copy the scope (private/unlisted/public) in replies to posts by default.
|
||||||
* `subject_line_behavior`: Allows changing the default behaviour of subject lines in replies. Valid values:
|
* `subject_line_behavior`: Allows changing the default behaviour of subject lines in replies. Valid values:
|
||||||
* "email": Copy and preprend re:, as in email.
|
* "email": Copy and preprend re:, as in email.
|
||||||
|
@ -125,11 +127,7 @@ config :pleroma, Pleroma.Emails.Mailer,
|
||||||
* `skip_thread_containment`: Skip filter out broken threads. The default is `false`.
|
* `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`.
|
* `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.
|
* `dynamic_configuration`: Allow transferring configuration to DB with the subsequent customization from Admin api.
|
||||||
* `external_user_synchronization`: Following/followers counters synchronization settings.
|
* `external_user_synchronization`: Enabling following/followers counters synchronization for external users.
|
||||||
* `enabled`: Enables synchronization
|
|
||||||
* `interval`: Interval between synchronization.
|
|
||||||
* `max_retries`: Max rettries for host. After exceeding the limit, the check will not be carried out for users from this host.
|
|
||||||
* `limit`: Users batch size for processing in one time.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -427,6 +425,7 @@ This config contains two queues: `federator_incoming` and `federator_outgoing`.
|
||||||
* `enabled`: if enabled the instance will parse metadata from attached links to generate link previews
|
* `enabled`: if enabled the instance will parse metadata from attached links to generate link previews
|
||||||
* `ignore_hosts`: list of hosts which will be ignored by the metadata parser. For example `["accounts.google.com", "xss.website"]`, defaults to `[]`.
|
* `ignore_hosts`: list of hosts which will be ignored by the metadata parser. For example `["accounts.google.com", "xss.website"]`, defaults to `[]`.
|
||||||
* `ignore_tld`: list TLDs (top-level domains) which will ignore for parse metadata. default is ["local", "localdomain", "lan"]
|
* `ignore_tld`: list TLDs (top-level domains) which will ignore for parse metadata. default is ["local", "localdomain", "lan"]
|
||||||
|
* `parsers`: list of Rich Media parsers
|
||||||
|
|
||||||
## :fetch_initial_posts
|
## :fetch_initial_posts
|
||||||
* `enabled`: if enabled, when a new user is federated with, fetch some of their latest posts
|
* `enabled`: if enabled, when a new user is federated with, fetch some of their latest posts
|
||||||
|
@ -643,3 +642,10 @@ A keyword list of rate limiters where a key is a limiter name and value is the l
|
||||||
It is also possible to have different limits for unauthenticated and authenticated users: the keyword value must be a list of two tuples where the first one is a config for unauthenticated users and the second one is for authenticated.
|
It is also possible to have different limits for unauthenticated and authenticated users: the keyword value must be a list of two tuples where the first one is a config for unauthenticated users and the second one is for authenticated.
|
||||||
|
|
||||||
See [`Pleroma.Plugs.RateLimiter`](Pleroma.Plugs.RateLimiter.html) documentation for examples.
|
See [`Pleroma.Plugs.RateLimiter`](Pleroma.Plugs.RateLimiter.html) documentation for examples.
|
||||||
|
|
||||||
|
Supported rate limiters:
|
||||||
|
|
||||||
|
* `:search` for the search requests (account & status search etc.)
|
||||||
|
* `:app_account_creation` for registering user accounts from the same IP address
|
||||||
|
* `:statuses_actions` for create / delete / fav / unfav / reblog / unreblog actions on any statuses
|
||||||
|
* `:status_id_action` for fav / unfav or reblog / unreblog actions on the same status by the same user
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Healthcheck do
|
defmodule Pleroma.Healthcheck do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Module collects metrics about app and assign healthy status.
|
Module collects metrics about app and assign healthy status.
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
Postgrex.Types.define(
|
Postgrex.Types.define(
|
||||||
Pleroma.PostgresTypes,
|
Pleroma.PostgresTypes,
|
||||||
[] ++ Ecto.Adapters.Postgres.extensions(),
|
[] ++ Ecto.Adapters.Postgres.extensions(),
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Mix.Tasks.Pleroma.Benchmark do
|
defmodule Mix.Tasks.Pleroma.Benchmark do
|
||||||
import Mix.Pleroma
|
import Mix.Pleroma
|
||||||
use Mix.Task
|
use Mix.Task
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Mix.Tasks.Pleroma.Config do
|
defmodule Mix.Tasks.Pleroma.Config do
|
||||||
use Mix.Task
|
use Mix.Task
|
||||||
import Mix.Pleroma
|
import Mix.Pleroma
|
||||||
|
|
|
@ -34,6 +34,8 @@ defmodule Mix.Tasks.Pleroma.Instance do
|
||||||
- `--db-configurable Y/N` - Allow/disallow configuring instance from admin part
|
- `--db-configurable Y/N` - Allow/disallow configuring instance from admin part
|
||||||
- `--uploads-dir` - the directory uploads go in when using a local uploader
|
- `--uploads-dir` - the directory uploads go in when using a local uploader
|
||||||
- `--static-dir` - the directory custom public files should be read from (custom emojis, frontend bundle overrides, robots.txt, etc.)
|
- `--static-dir` - the directory custom public files should be read from (custom emojis, frontend bundle overrides, robots.txt, etc.)
|
||||||
|
- `--listen-ip` - the ip the app should listen to, defaults to 127.0.0.1
|
||||||
|
- `--listen-port` - the port the app should listen to, defaults to 4000
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def run(["gen" | rest]) do
|
def run(["gen" | rest]) do
|
||||||
|
@ -56,7 +58,9 @@ def run(["gen" | rest]) do
|
||||||
indexable: :string,
|
indexable: :string,
|
||||||
db_configurable: :string,
|
db_configurable: :string,
|
||||||
uploads_dir: :string,
|
uploads_dir: :string,
|
||||||
static_dir: :string
|
static_dir: :string,
|
||||||
|
listen_ip: :string,
|
||||||
|
listen_port: :string
|
||||||
],
|
],
|
||||||
aliases: [
|
aliases: [
|
||||||
o: :output,
|
o: :output,
|
||||||
|
@ -146,6 +150,22 @@ def run(["gen" | rest]) do
|
||||||
"n"
|
"n"
|
||||||
) === "y"
|
) === "y"
|
||||||
|
|
||||||
|
listen_port =
|
||||||
|
get_option(
|
||||||
|
options,
|
||||||
|
:listen_port,
|
||||||
|
"What port will the app listen to (leave it if you are using the default setup with nginx)?",
|
||||||
|
4000
|
||||||
|
)
|
||||||
|
|
||||||
|
listen_ip =
|
||||||
|
get_option(
|
||||||
|
options,
|
||||||
|
:listen_ip,
|
||||||
|
"What ip will the app listen to (leave it if you are using the default setup with nginx)?",
|
||||||
|
"127.0.0.1"
|
||||||
|
)
|
||||||
|
|
||||||
uploads_dir =
|
uploads_dir =
|
||||||
get_option(
|
get_option(
|
||||||
options,
|
options,
|
||||||
|
@ -186,7 +206,9 @@ def run(["gen" | rest]) do
|
||||||
db_configurable?: db_configurable?,
|
db_configurable?: db_configurable?,
|
||||||
static_dir: static_dir,
|
static_dir: static_dir,
|
||||||
uploads_dir: uploads_dir,
|
uploads_dir: uploads_dir,
|
||||||
rum_enabled: rum_enabled
|
rum_enabled: rum_enabled,
|
||||||
|
listen_ip: listen_ip,
|
||||||
|
listen_port: listen_port
|
||||||
)
|
)
|
||||||
|
|
||||||
result_psql =
|
result_psql =
|
||||||
|
|
|
@ -344,5 +344,5 @@ def restrict_deactivated_users(query) do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
defdelegate search(user, query), to: Pleroma.Activity.Search
|
defdelegate search(user, query, options \\ []), to: Pleroma.Activity.Search
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,14 +5,17 @@
|
||||||
defmodule Pleroma.Activity.Search do
|
defmodule Pleroma.Activity.Search do
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Object.Fetcher
|
alias Pleroma.Object.Fetcher
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Pagination
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.Visibility
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
|
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
def search(user, search_query) do
|
def search(user, search_query, options \\ []) do
|
||||||
index_type = if Pleroma.Config.get([:database, :rum_enabled]), do: :rum, else: :gin
|
index_type = if Pleroma.Config.get([:database, :rum_enabled]), do: :rum, else: :gin
|
||||||
|
limit = Enum.min([Keyword.get(options, :limit), 40])
|
||||||
|
offset = Keyword.get(options, :offset, 0)
|
||||||
|
author = Keyword.get(options, :author)
|
||||||
|
|
||||||
Activity
|
Activity
|
||||||
|> Activity.with_preloaded_object()
|
|> Activity.with_preloaded_object()
|
||||||
|
@ -20,15 +23,23 @@ def search(user, search_query) do
|
||||||
|> restrict_public()
|
|> restrict_public()
|
||||||
|> query_with(index_type, search_query)
|
|> query_with(index_type, search_query)
|
||||||
|> maybe_restrict_local(user)
|
|> maybe_restrict_local(user)
|
||||||
|> Repo.all()
|
|> maybe_restrict_author(author)
|
||||||
|
|> Pagination.fetch_paginated(%{"offset" => offset, "limit" => limit}, :offset)
|
||||||
|> maybe_fetch(user, search_query)
|
|> maybe_fetch(user, search_query)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def maybe_restrict_author(query, %User{} = author) do
|
||||||
|
from([a, o] in query,
|
||||||
|
where: a.actor == ^author.ap_id
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def maybe_restrict_author(query, _), do: query
|
||||||
|
|
||||||
defp restrict_public(q) do
|
defp restrict_public(q) do
|
||||||
from([a, o] in q,
|
from([a, o] in q,
|
||||||
where: fragment("?->>'type' = 'Create'", a.data),
|
where: fragment("?->>'type' = 'Create'", a.data),
|
||||||
where: "https://www.w3.org/ns/activitystreams#Public" in a.recipients,
|
where: "https://www.w3.org/ns/activitystreams#Public" in a.recipients
|
||||||
limit: 40
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -151,11 +151,7 @@ def start(_type, _args) do
|
||||||
start: {Pleroma.Web.Endpoint, :start_link, []},
|
start: {Pleroma.Web.Endpoint, :start_link, []},
|
||||||
type: :supervisor
|
type: :supervisor
|
||||||
},
|
},
|
||||||
%{id: Pleroma.Gopher.Server, start: {Pleroma.Gopher.Server, :start_link, []}},
|
%{id: Pleroma.Gopher.Server, start: {Pleroma.Gopher.Server, :start_link, []}}
|
||||||
%{
|
|
||||||
id: Pleroma.User.SynchronizationWorker,
|
|
||||||
start: {Pleroma.User.SynchronizationWorker, :start_link, []}
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
|
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.BBS.Authenticator do
|
defmodule Pleroma.BBS.Authenticator do
|
||||||
use Sshd.PasswordAuthenticator
|
use Sshd.PasswordAuthenticator
|
||||||
alias Comeonin.Pbkdf2
|
alias Comeonin.Pbkdf2
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.BBS.Handler do
|
defmodule Pleroma.BBS.Handler do
|
||||||
use Sshd.ShellHandler
|
use Sshd.ShellHandler
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Bookmark do
|
defmodule Pleroma.Bookmark do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Captcha do
|
defmodule Pleroma.Captcha do
|
||||||
|
import Pleroma.Web.Gettext
|
||||||
|
|
||||||
alias Calendar.DateTime
|
alias Calendar.DateTime
|
||||||
alias Plug.Crypto.KeyGenerator
|
alias Plug.Crypto.KeyGenerator
|
||||||
alias Plug.Crypto.MessageEncryptor
|
alias Plug.Crypto.MessageEncryptor
|
||||||
|
@ -83,10 +85,11 @@ def handle_call({:validate, token, captcha, answer_data}, _from, state) do
|
||||||
with {:ok, data} <- MessageEncryptor.decrypt(answer_data, secret, sign_secret),
|
with {:ok, data} <- MessageEncryptor.decrypt(answer_data, secret, sign_secret),
|
||||||
%{at: at, answer_data: answer_md5} <- :erlang.binary_to_term(data) do
|
%{at: at, answer_data: answer_md5} <- :erlang.binary_to_term(data) do
|
||||||
try do
|
try do
|
||||||
if DateTime.before?(at, valid_if_after), do: throw({:error, "CAPTCHA expired"})
|
if DateTime.before?(at, valid_if_after),
|
||||||
|
do: throw({:error, dgettext("errors", "CAPTCHA expired")})
|
||||||
|
|
||||||
if not is_nil(Cachex.get!(:used_captcha_cache, token)),
|
if not is_nil(Cachex.get!(:used_captcha_cache, token)),
|
||||||
do: throw({:error, "CAPTCHA already used"})
|
do: throw({:error, dgettext("errors", "CAPTCHA already used")})
|
||||||
|
|
||||||
res = method().validate(token, captcha, answer_md5)
|
res = method().validate(token, captcha, answer_md5)
|
||||||
# Throw if an error occurs
|
# Throw if an error occurs
|
||||||
|
@ -101,7 +104,7 @@ def handle_call({:validate, token, captcha, answer_data}, _from, state) do
|
||||||
:throw, e -> e
|
:throw, e -> e
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
_ -> {:error, "Invalid answer data"}
|
_ -> {:error, dgettext("errors", "Invalid answer data")}
|
||||||
end
|
end
|
||||||
|
|
||||||
{:reply, result, state}
|
{:reply, result, state}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Captcha.Kocaptcha do
|
defmodule Pleroma.Captcha.Kocaptcha do
|
||||||
|
import Pleroma.Web.Gettext
|
||||||
alias Pleroma.Captcha.Service
|
alias Pleroma.Captcha.Service
|
||||||
@behaviour Service
|
@behaviour Service
|
||||||
|
|
||||||
|
@ -12,7 +13,7 @@ def new do
|
||||||
|
|
||||||
case Tesla.get(endpoint <> "/new") do
|
case Tesla.get(endpoint <> "/new") do
|
||||||
{:error, _} ->
|
{:error, _} ->
|
||||||
%{error: "Kocaptcha service unavailable"}
|
%{error: dgettext("errors", "Kocaptcha service unavailable")}
|
||||||
|
|
||||||
{:ok, res} ->
|
{:ok, res} ->
|
||||||
json_resp = Jason.decode!(res.body)
|
json_resp = Jason.decode!(res.body)
|
||||||
|
@ -32,6 +33,6 @@ def validate(_token, captcha, answer_data) do
|
||||||
if not is_nil(captcha) and
|
if not is_nil(captcha) and
|
||||||
:crypto.hash(:md5, captcha) |> Base.encode16() == String.upcase(answer_data),
|
:crypto.hash(:md5, captcha) |> Base.encode16() == String.upcase(answer_data),
|
||||||
do: :ok,
|
do: :ok,
|
||||||
else: {:error, "Invalid CAPTCHA"}
|
else: {:error, dgettext("errors", "Invalid CAPTCHA")}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Config.TransferTask do
|
defmodule Pleroma.Config.TransferTask do
|
||||||
use Task
|
use Task
|
||||||
alias Pleroma.Web.AdminAPI.Config
|
alias Pleroma.Web.AdminAPI.Config
|
||||||
|
|
|
@ -3,11 +3,58 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Emails.Mailer do
|
defmodule Pleroma.Emails.Mailer do
|
||||||
use Swoosh.Mailer, otp_app: :pleroma
|
@moduledoc """
|
||||||
|
Defines the Pleroma mailer.
|
||||||
|
|
||||||
|
The module contains functions to delivery email using Swoosh.Mailer.
|
||||||
|
"""
|
||||||
|
|
||||||
|
alias Swoosh.DeliveryError
|
||||||
|
|
||||||
|
@otp_app :pleroma
|
||||||
|
@mailer_config [otp: :pleroma]
|
||||||
|
|
||||||
|
@spec enabled?() :: boolean()
|
||||||
|
def enabled?, do: Pleroma.Config.get([__MODULE__, :enabled])
|
||||||
|
|
||||||
|
@doc "add email to queue"
|
||||||
def deliver_async(email, config \\ []) do
|
def deliver_async(email, config \\ []) do
|
||||||
PleromaJobQueue.enqueue(:mailer, __MODULE__, [:deliver_async, email, config])
|
PleromaJobQueue.enqueue(:mailer, __MODULE__, [:deliver_async, email, config])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc "callback to perform send email from queue"
|
||||||
def perform(:deliver_async, email, config), do: deliver(email, config)
|
def perform(:deliver_async, email, config), do: deliver(email, config)
|
||||||
|
|
||||||
|
@spec deliver(Swoosh.Email.t(), Keyword.t()) :: {:ok, term} | {:error, term}
|
||||||
|
def deliver(email, config \\ [])
|
||||||
|
|
||||||
|
def deliver(email, config) do
|
||||||
|
case enabled?() do
|
||||||
|
true -> Swoosh.Mailer.deliver(email, parse_config(config))
|
||||||
|
false -> {:error, :deliveries_disabled}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec deliver!(Swoosh.Email.t(), Keyword.t()) :: term | no_return
|
||||||
|
def deliver!(email, config \\ [])
|
||||||
|
|
||||||
|
def deliver!(email, config) do
|
||||||
|
case deliver(email, config) do
|
||||||
|
{:ok, result} -> result
|
||||||
|
{:error, reason} -> raise DeliveryError, reason: reason
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@on_load :validate_dependency
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
def validate_dependency do
|
||||||
|
parse_config([])
|
||||||
|
|> Keyword.get(:adapter)
|
||||||
|
|> Swoosh.Mailer.validate_dependency()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp parse_config(config) do
|
||||||
|
Swoosh.Mailer.parse_config(@otp_app, __MODULE__, @mailer_config, config)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -29,7 +29,7 @@ def new(opts \\ []) do
|
||||||
|
|
||||||
# fetch Hackney options
|
# fetch Hackney options
|
||||||
#
|
#
|
||||||
defp hackney_options(opts) do
|
def hackney_options(opts) do
|
||||||
options = Keyword.get(opts, :adapter, [])
|
options = Keyword.get(opts, :adapter, [])
|
||||||
adapter_options = Pleroma.Config.get([:http, :adapter], [])
|
adapter_options = Pleroma.Config.get([:http, :adapter], [])
|
||||||
proxy_url = Pleroma.Config.get([:http, :proxy_url], nil)
|
proxy_url = Pleroma.Config.get([:http, :proxy_url], nil)
|
||||||
|
|
|
@ -65,10 +65,7 @@ defp process_sni_options(options, url) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_request_options(options) do
|
def process_request_options(options) do
|
||||||
case Pleroma.Config.get([:http, :proxy_url]) do
|
Keyword.merge(Pleroma.HTTP.Connection.hackney_options([]), options)
|
||||||
nil -> options
|
|
||||||
proxy -> options ++ [proxy: proxy]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Instances do
|
defmodule Pleroma.Instances do
|
||||||
@moduledoc "Instances context."
|
@moduledoc "Instances context."
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Instances.Instance do
|
defmodule Pleroma.Instances.Instance do
|
||||||
@moduledoc "Instance."
|
@moduledoc "Instance."
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,6 @@ defmodule Pleroma.Notification do
|
||||||
alias Pleroma.Pagination
|
alias Pleroma.Pagination
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.CommonAPI
|
|
||||||
alias Pleroma.Web.CommonAPI.Utils
|
alias Pleroma.Web.CommonAPI.Utils
|
||||||
alias Pleroma.Web.Push
|
alias Pleroma.Web.Push
|
||||||
alias Pleroma.Web.Streamer
|
alias Pleroma.Web.Streamer
|
||||||
|
@ -32,31 +31,47 @@ def changeset(%Notification{} = notification, attrs) do
|
||||||
|> cast(attrs, [:seen])
|
|> cast(attrs, [:seen])
|
||||||
end
|
end
|
||||||
|
|
||||||
def for_user_query(user) do
|
def for_user_query(user, opts) do
|
||||||
Notification
|
query =
|
||||||
|> where(user_id: ^user.id)
|
Notification
|
||||||
|> where(
|
|> where(user_id: ^user.id)
|
||||||
[n, a],
|
|> where(
|
||||||
fragment(
|
[n, a],
|
||||||
"? not in (SELECT ap_id FROM users WHERE info->'deactivated' @> 'true')",
|
|
||||||
a.actor
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|> join(:inner, [n], activity in assoc(n, :activity))
|
|
||||||
|> join(:left, [n, a], object in Object,
|
|
||||||
on:
|
|
||||||
fragment(
|
fragment(
|
||||||
"(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)",
|
"? not in (SELECT ap_id FROM users WHERE info->'deactivated' @> 'true')",
|
||||||
object.data,
|
a.actor
|
||||||
a.data
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|> preload([n, a, o], activity: {a, object: o})
|
|> join(:inner, [n], activity in assoc(n, :activity))
|
||||||
|
|> join(:left, [n, a], object in Object,
|
||||||
|
on:
|
||||||
|
fragment(
|
||||||
|
"(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)",
|
||||||
|
object.data,
|
||||||
|
a.data
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|> preload([n, a, o], activity: {a, object: o})
|
||||||
|
|
||||||
|
if opts[:with_muted] do
|
||||||
|
query
|
||||||
|
else
|
||||||
|
where(query, [n, a], a.actor not in ^user.info.muted_notifications)
|
||||||
|
|> where([n, a], a.actor not in ^user.info.blocks)
|
||||||
|
|> where(
|
||||||
|
[n, a],
|
||||||
|
fragment("substring(? from '.*://([^/]*)')", a.actor) not in ^user.info.domain_blocks
|
||||||
|
)
|
||||||
|
|> join(:left, [n, a], tm in Pleroma.ThreadMute,
|
||||||
|
on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data)
|
||||||
|
)
|
||||||
|
|> where([n, a, o, tm], is_nil(tm.id))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def for_user(user, opts \\ %{}) do
|
def for_user(user, opts \\ %{}) do
|
||||||
user
|
user
|
||||||
|> for_user_query()
|
|> for_user_query(opts)
|
||||||
|> Pagination.fetch_paginated(opts)
|
|> Pagination.fetch_paginated(opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -179,11 +194,10 @@ def get_notified_from_activity(
|
||||||
|
|
||||||
def get_notified_from_activity(_, _local_only), do: []
|
def get_notified_from_activity(_, _local_only), do: []
|
||||||
|
|
||||||
|
@spec skip?(Activity.t(), User.t()) :: boolean()
|
||||||
def skip?(activity, user) do
|
def skip?(activity, user) do
|
||||||
[
|
[
|
||||||
:self,
|
:self,
|
||||||
:blocked,
|
|
||||||
:muted,
|
|
||||||
:followers,
|
:followers,
|
||||||
:follows,
|
:follows,
|
||||||
:non_followers,
|
:non_followers,
|
||||||
|
@ -193,21 +207,11 @@ def skip?(activity, user) do
|
||||||
|> Enum.any?(&skip?(&1, activity, user))
|
|> Enum.any?(&skip?(&1, activity, user))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec skip?(atom(), Activity.t(), User.t()) :: boolean()
|
||||||
def skip?(:self, activity, user) do
|
def skip?(:self, activity, user) do
|
||||||
activity.data["actor"] == user.ap_id
|
activity.data["actor"] == user.ap_id
|
||||||
end
|
end
|
||||||
|
|
||||||
def skip?(:blocked, activity, user) do
|
|
||||||
actor = activity.data["actor"]
|
|
||||||
User.blocks?(user, %{ap_id: actor})
|
|
||||||
end
|
|
||||||
|
|
||||||
def skip?(:muted, activity, user) do
|
|
||||||
actor = activity.data["actor"]
|
|
||||||
|
|
||||||
User.mutes?(user, %{ap_id: actor}) or CommonAPI.thread_muted?(user, activity)
|
|
||||||
end
|
|
||||||
|
|
||||||
def skip?(
|
def skip?(
|
||||||
:followers,
|
:followers,
|
||||||
activity,
|
activity,
|
||||||
|
|
|
@ -48,6 +48,9 @@ def contain_origin(id, %{"actor" => _actor} = params) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def contain_origin(id, %{"attributedTo" => actor} = params),
|
||||||
|
do: contain_origin(id, Map.put(params, "actor", actor))
|
||||||
|
|
||||||
def contain_origin_from_id(_id, %{"id" => nil}), do: :error
|
def contain_origin_from_id(_id, %{"id" => nil}), do: :error
|
||||||
|
|
||||||
def contain_origin_from_id(id, %{"id" => other_id} = _params) do
|
def contain_origin_from_id(id, %{"id" => other_id} = _params) do
|
||||||
|
@ -60,4 +63,9 @@ def contain_origin_from_id(id, %{"id" => other_id} = _params) do
|
||||||
:error
|
:error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def contain_child(%{"object" => %{"id" => id, "attributedTo" => _} = object}),
|
||||||
|
do: contain_origin(id, object)
|
||||||
|
|
||||||
|
def contain_child(_), do: :ok
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Object.Fetcher do
|
defmodule Pleroma.Object.Fetcher do
|
||||||
alias Pleroma.HTTP
|
alias Pleroma.HTTP
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
|
@ -28,33 +32,39 @@ def fetch_object_from_id(id, options \\ []) do
|
||||||
else
|
else
|
||||||
Logger.info("Fetching #{id} via AP")
|
Logger.info("Fetching #{id} via AP")
|
||||||
|
|
||||||
with {:ok, data} <- fetch_and_contain_remote_object_from_id(id),
|
with {:fetch, {:ok, data}} <- {:fetch, fetch_and_contain_remote_object_from_id(id)},
|
||||||
nil <- Object.normalize(data, false),
|
{:normalize, nil} <- {:normalize, Object.normalize(data, false)},
|
||||||
params <- %{
|
params <- %{
|
||||||
"type" => "Create",
|
"type" => "Create",
|
||||||
"to" => data["to"],
|
"to" => data["to"],
|
||||||
"cc" => data["cc"],
|
"cc" => data["cc"],
|
||||||
|
# Should we seriously keep this attributedTo thing?
|
||||||
"actor" => data["actor"] || data["attributedTo"],
|
"actor" => data["actor"] || data["attributedTo"],
|
||||||
"object" => data
|
"object" => data
|
||||||
},
|
},
|
||||||
:ok <- Containment.contain_origin(id, params),
|
{:containment, :ok} <- {:containment, Containment.contain_origin(id, params)},
|
||||||
{:ok, activity} <- Transmogrifier.handle_incoming(params, options),
|
{:ok, activity} <- Transmogrifier.handle_incoming(params, options),
|
||||||
{:object, _data, %Object{} = object} <-
|
{:object, _data, %Object{} = object} <-
|
||||||
{:object, data, Object.normalize(activity, false)} do
|
{:object, data, Object.normalize(activity, false)} do
|
||||||
{:ok, object}
|
{:ok, object}
|
||||||
else
|
else
|
||||||
|
{:containment, _} ->
|
||||||
|
{:error, "Object containment failed."}
|
||||||
|
|
||||||
{:error, {:reject, nil}} ->
|
{:error, {:reject, nil}} ->
|
||||||
{:reject, nil}
|
{:reject, nil}
|
||||||
|
|
||||||
{:object, data, nil} ->
|
{:object, data, nil} ->
|
||||||
reinject_object(data)
|
reinject_object(data)
|
||||||
|
|
||||||
object = %Object{} ->
|
{:normalize, object = %Object{}} ->
|
||||||
{:ok, object}
|
{:ok, object}
|
||||||
|
|
||||||
_e ->
|
_e ->
|
||||||
|
# Only fallback when receiving a fetch/normalization error with ActivityPub
|
||||||
Logger.info("Couldn't get object via AP, trying out OStatus fetching...")
|
Logger.info("Couldn't get object via AP, trying out OStatus fetching...")
|
||||||
|
|
||||||
|
# FIXME: OStatus Object Containment?
|
||||||
case OStatus.fetch_activity_from_url(id) do
|
case OStatus.fetch_activity_from_url(id) do
|
||||||
{:ok, [activity | _]} -> {:ok, Object.normalize(activity, false)}
|
{:ok, [activity | _]} -> {:ok, Object.normalize(activity, false)}
|
||||||
e -> e
|
e -> e
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.ObjectTombstone do
|
defmodule Pleroma.ObjectTombstone do
|
||||||
@enforce_keys [:id, :formerType, :deleted]
|
@enforce_keys [:id, :formerType, :deleted]
|
||||||
defstruct [:id, :formerType, :deleted, type: "Tombstone"]
|
defstruct [:id, :formerType, :deleted, type: "Tombstone"]
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Pagination do
|
defmodule Pleroma.Pagination do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Implements Mastodon-compatible pagination.
|
Implements Mastodon-compatible pagination.
|
||||||
|
@ -10,16 +14,28 @@ defmodule Pleroma.Pagination do
|
||||||
|
|
||||||
@default_limit 20
|
@default_limit 20
|
||||||
|
|
||||||
def fetch_paginated(query, params) do
|
def fetch_paginated(query, params, type \\ :keyset)
|
||||||
|
|
||||||
|
def fetch_paginated(query, params, :keyset) do
|
||||||
options = cast_params(params)
|
options = cast_params(params)
|
||||||
|
|
||||||
query
|
query
|
||||||
|> paginate(options)
|
|> paginate(options, :keyset)
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
|> enforce_order(options)
|
|> enforce_order(options)
|
||||||
end
|
end
|
||||||
|
|
||||||
def paginate(query, options) do
|
def fetch_paginated(query, params, :offset) do
|
||||||
|
options = cast_params(params)
|
||||||
|
|
||||||
|
query
|
||||||
|
|> paginate(options, :offset)
|
||||||
|
|> Repo.all()
|
||||||
|
end
|
||||||
|
|
||||||
|
def paginate(query, options, method \\ :keyset)
|
||||||
|
|
||||||
|
def paginate(query, options, :keyset) do
|
||||||
query
|
query
|
||||||
|> restrict(:min_id, options)
|
|> restrict(:min_id, options)
|
||||||
|> restrict(:since_id, options)
|
|> restrict(:since_id, options)
|
||||||
|
@ -28,11 +44,18 @@ def paginate(query, options) do
|
||||||
|> restrict(:limit, options)
|
|> restrict(:limit, options)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def paginate(query, options, :offset) do
|
||||||
|
query
|
||||||
|
|> restrict(:offset, options)
|
||||||
|
|> restrict(:limit, options)
|
||||||
|
end
|
||||||
|
|
||||||
defp cast_params(params) do
|
defp cast_params(params) do
|
||||||
param_types = %{
|
param_types = %{
|
||||||
min_id: :string,
|
min_id: :string,
|
||||||
since_id: :string,
|
since_id: :string,
|
||||||
max_id: :string,
|
max_id: :string,
|
||||||
|
offset: :integer,
|
||||||
limit: :integer
|
limit: :integer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,6 +89,10 @@ defp restrict(query, :order, _options) do
|
||||||
order_by(query, [u], fragment("? desc nulls last", u.id))
|
order_by(query, [u], fragment("? desc nulls last", u.id))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp restrict(query, :offset, %{offset: offset}) do
|
||||||
|
offset(query, ^offset)
|
||||||
|
end
|
||||||
|
|
||||||
defp restrict(query, :limit, options) do
|
defp restrict(query, :limit, options) do
|
||||||
limit = Map.get(options, :limit, @default_limit)
|
limit = Map.get(options, :limit, @default_limit)
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
defmodule Pleroma.Plugs.EnsureAuthenticatedPlug do
|
defmodule Pleroma.Plugs.EnsureAuthenticatedPlug do
|
||||||
import Plug.Conn
|
import Plug.Conn
|
||||||
|
import Pleroma.Web.TranslationHelpers
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
def init(options) do
|
def init(options) do
|
||||||
|
@ -16,8 +17,7 @@ def call(%{assigns: %{user: %User{}}} = conn, _) do
|
||||||
|
|
||||||
def call(conn, _) do
|
def call(conn, _) do
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("application/json")
|
|> render_error(:forbidden, "Invalid credentials.")
|
||||||
|> send_resp(403, Jason.encode!(%{error: "Invalid credentials."}))
|
|
||||||
|> halt
|
|> halt
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug do
|
defmodule Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug do
|
||||||
|
import Pleroma.Web.TranslationHelpers
|
||||||
import Plug.Conn
|
import Plug.Conn
|
||||||
alias Pleroma.Config
|
alias Pleroma.Config
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
@ -23,8 +24,7 @@ def call(conn, _) do
|
||||||
|
|
||||||
{false, _} ->
|
{false, _} ->
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("application/json")
|
|> render_error(:forbidden, "This resource requires authentication.")
|
||||||
|> send_resp(403, Jason.encode!(%{error: "This resource requires authentication."}))
|
|
||||||
|> halt
|
|> halt
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
defmodule Pleroma.Plugs.OAuthScopesPlug do
|
defmodule Pleroma.Plugs.OAuthScopesPlug do
|
||||||
import Plug.Conn
|
import Plug.Conn
|
||||||
|
import Pleroma.Web.Gettext
|
||||||
|
|
||||||
@behaviour Plug
|
@behaviour Plug
|
||||||
|
|
||||||
|
@ -30,11 +31,14 @@ def call(%Plug.Conn{assigns: assigns} = conn, %{scopes: scopes} = options) do
|
||||||
|
|
||||||
true ->
|
true ->
|
||||||
missing_scopes = scopes -- token.scopes
|
missing_scopes = scopes -- token.scopes
|
||||||
error_message = "Insufficient permissions: #{Enum.join(missing_scopes, " #{op} ")}."
|
permissions = Enum.join(missing_scopes, " #{op} ")
|
||||||
|
|
||||||
|
error_message =
|
||||||
|
dgettext("errors", "Insufficient permissions: %{permissions}.", permissions: permissions)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("application/json")
|
|> put_resp_content_type("application/json")
|
||||||
|> send_resp(403, Jason.encode!(%{error: error_message}))
|
|> send_resp(:forbidden, Jason.encode!(%{error: error_message}))
|
||||||
|> halt()
|
|> halt()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -31,12 +31,28 @@ defmodule Pleroma.Plugs.RateLimiter do
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
AllowedSyntax:
|
||||||
|
|
||||||
|
plug(Pleroma.Plugs.RateLimiter, :limiter_name)
|
||||||
|
plug(Pleroma.Plugs.RateLimiter, {:limiter_name, options})
|
||||||
|
|
||||||
|
Allowed options:
|
||||||
|
|
||||||
|
* `bucket_name` overrides bucket name (e.g. to have a separate limit for a set of actions)
|
||||||
|
* `params` appends values of specified request params (e.g. ["id"]) to bucket name
|
||||||
|
|
||||||
Inside a controller:
|
Inside a controller:
|
||||||
|
|
||||||
plug(Pleroma.Plugs.RateLimiter, :one when action == :one)
|
plug(Pleroma.Plugs.RateLimiter, :one when action == :one)
|
||||||
plug(Pleroma.Plugs.RateLimiter, :two when action in [:two, :three])
|
plug(Pleroma.Plugs.RateLimiter, :two when action in [:two, :three])
|
||||||
|
|
||||||
or inside a router pipiline:
|
plug(
|
||||||
|
Pleroma.Plugs.RateLimiter,
|
||||||
|
{:status_id_action, bucket_name: "status_id_action:fav_unfav", params: ["id"]}
|
||||||
|
when action in ~w(fav_status unfav_status)a
|
||||||
|
)
|
||||||
|
|
||||||
|
or inside a router pipeline:
|
||||||
|
|
||||||
pipeline :api do
|
pipeline :api do
|
||||||
...
|
...
|
||||||
|
@ -44,39 +60,61 @@ defmodule Pleroma.Plugs.RateLimiter do
|
||||||
...
|
...
|
||||||
end
|
end
|
||||||
"""
|
"""
|
||||||
|
import Pleroma.Web.TranslationHelpers
|
||||||
import Phoenix.Controller, only: [json: 2]
|
|
||||||
import Plug.Conn
|
import Plug.Conn
|
||||||
|
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
def init(limiter_name) do
|
def init(limiter_name) when is_atom(limiter_name) do
|
||||||
|
init({limiter_name, []})
|
||||||
|
end
|
||||||
|
|
||||||
|
def init({limiter_name, opts}) do
|
||||||
case Pleroma.Config.get([:rate_limit, limiter_name]) do
|
case Pleroma.Config.get([:rate_limit, limiter_name]) do
|
||||||
nil -> nil
|
nil -> nil
|
||||||
config -> {limiter_name, config}
|
config -> {limiter_name, config, opts}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# do not limit if there is no limiter configuration
|
# Do not limit if there is no limiter configuration
|
||||||
def call(conn, nil), do: conn
|
def call(conn, nil), do: conn
|
||||||
|
|
||||||
def call(conn, opts) do
|
def call(conn, settings) do
|
||||||
case check_rate(conn, opts) do
|
case check_rate(conn, settings) do
|
||||||
{:ok, _count} -> conn
|
{:ok, _count} ->
|
||||||
{:error, _count} -> render_error(conn)
|
conn
|
||||||
|
|
||||||
|
{:error, _count} ->
|
||||||
|
render_throttled_error(conn)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp check_rate(%{assigns: %{user: %User{id: user_id}}}, {limiter_name, [_, {scale, limit}]}) do
|
defp bucket_name(conn, limiter_name, opts) do
|
||||||
ExRated.check_rate("#{limiter_name}:#{user_id}", scale, limit)
|
bucket_name = opts[:bucket_name] || limiter_name
|
||||||
|
|
||||||
|
if params_names = opts[:params] do
|
||||||
|
params_values = for p <- Enum.sort(params_names), do: conn.params[p]
|
||||||
|
Enum.join([bucket_name] ++ params_values, ":")
|
||||||
|
else
|
||||||
|
bucket_name
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp check_rate(conn, {limiter_name, [{scale, limit} | _]}) do
|
defp check_rate(
|
||||||
ExRated.check_rate("#{limiter_name}:#{ip(conn)}", scale, limit)
|
%{assigns: %{user: %User{id: user_id}}} = conn,
|
||||||
|
{limiter_name, [_, {scale, limit}], opts}
|
||||||
|
) do
|
||||||
|
bucket_name = bucket_name(conn, limiter_name, opts)
|
||||||
|
ExRated.check_rate("#{bucket_name}:#{user_id}", scale, limit)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp check_rate(conn, {limiter_name, {scale, limit}}) do
|
defp check_rate(conn, {limiter_name, [{scale, limit} | _], opts}) do
|
||||||
check_rate(conn, {limiter_name, [{scale, limit}]})
|
bucket_name = bucket_name(conn, limiter_name, opts)
|
||||||
|
ExRated.check_rate("#{bucket_name}:#{ip(conn)}", scale, limit)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp check_rate(conn, {limiter_name, {scale, limit}, opts}) do
|
||||||
|
check_rate(conn, {limiter_name, [{scale, limit}, {scale, limit}], opts})
|
||||||
end
|
end
|
||||||
|
|
||||||
def ip(%{remote_ip: remote_ip}) do
|
def ip(%{remote_ip: remote_ip}) do
|
||||||
|
@ -85,10 +123,9 @@ def ip(%{remote_ip: remote_ip}) do
|
||||||
|> Enum.join(".")
|
|> Enum.join(".")
|
||||||
end
|
end
|
||||||
|
|
||||||
defp render_error(conn) do
|
defp render_throttled_error(conn) do
|
||||||
conn
|
conn
|
||||||
|> put_status(:too_many_requests)
|
|> render_error(:too_many_requests, "Throttled")
|
||||||
|> json(%{error: "Throttled"})
|
|
||||||
|> halt()
|
|> halt()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
63
lib/pleroma/plugs/set_locale_plug.ex
Normal file
63
lib/pleroma/plugs/set_locale_plug.ex
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
# NOTE: this module is based on https://github.com/smeevil/set_locale
|
||||||
|
defmodule Pleroma.Plugs.SetLocalePlug do
|
||||||
|
import Plug.Conn, only: [get_req_header: 2, assign: 3]
|
||||||
|
|
||||||
|
def init(_), do: nil
|
||||||
|
|
||||||
|
def call(conn, _) do
|
||||||
|
locale = get_locale_from_header(conn) || Gettext.get_locale()
|
||||||
|
Gettext.put_locale(locale)
|
||||||
|
assign(conn, :locale, locale)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp get_locale_from_header(conn) do
|
||||||
|
conn
|
||||||
|
|> extract_accept_language()
|
||||||
|
|> Enum.find(&supported_locale?/1)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp extract_accept_language(conn) do
|
||||||
|
case get_req_header(conn, "accept-language") do
|
||||||
|
[value | _] ->
|
||||||
|
value
|
||||||
|
|> String.split(",")
|
||||||
|
|> Enum.map(&parse_language_option/1)
|
||||||
|
|> Enum.sort(&(&1.quality > &2.quality))
|
||||||
|
|> Enum.map(& &1.tag)
|
||||||
|
|> Enum.reject(&is_nil/1)
|
||||||
|
|> ensure_language_fallbacks()
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp supported_locale?(locale) do
|
||||||
|
Pleroma.Web.Gettext
|
||||||
|
|> Gettext.known_locales()
|
||||||
|
|> Enum.member?(locale)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp parse_language_option(string) do
|
||||||
|
captures = Regex.named_captures(~r/^\s?(?<tag>[\w\-]+)(?:;q=(?<quality>[\d\.]+))?$/i, string)
|
||||||
|
|
||||||
|
quality =
|
||||||
|
case Float.parse(captures["quality"] || "1.0") do
|
||||||
|
{val, _} -> val
|
||||||
|
:error -> 1.0
|
||||||
|
end
|
||||||
|
|
||||||
|
%{tag: captures["tag"], quality: quality}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp ensure_language_fallbacks(tags) do
|
||||||
|
Enum.flat_map(tags, fn tag ->
|
||||||
|
[language | _] = String.split(tag, "-")
|
||||||
|
if Enum.member?(tags, language), do: [tag], else: [tag, language]
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
|
@ -7,6 +7,7 @@ defmodule Pleroma.Plugs.UploadedMedia do
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import Plug.Conn
|
import Plug.Conn
|
||||||
|
import Pleroma.Web.Gettext
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
@behaviour Plug
|
@behaviour Plug
|
||||||
|
@ -45,7 +46,7 @@ def call(%{request_path: <<"/", @path, "/", file::binary>>} = conn, opts) do
|
||||||
else
|
else
|
||||||
_ ->
|
_ ->
|
||||||
conn
|
conn
|
||||||
|> send_resp(500, "Failed")
|
|> send_resp(:internal_server_error, dgettext("errors", "Failed"))
|
||||||
|> halt()
|
|> halt()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -64,7 +65,7 @@ defp get_media(conn, {:static_dir, directory}, _, opts) do
|
||||||
conn
|
conn
|
||||||
else
|
else
|
||||||
conn
|
conn
|
||||||
|> send_resp(404, "Not found")
|
|> send_resp(:not_found, dgettext("errors", "Not found"))
|
||||||
|> halt()
|
|> halt()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -84,7 +85,7 @@ defp get_media(conn, unknown, _, _) do
|
||||||
Logger.error("#{__MODULE__}: Unknown get startegy: #{inspect(unknown)}")
|
Logger.error("#{__MODULE__}: Unknown get startegy: #{inspect(unknown)}")
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> send_resp(500, "Internal Error")
|
|> send_resp(:internal_server_error, dgettext("errors", "Internal Error"))
|
||||||
|> halt()
|
|> halt()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Plugs.UserIsAdminPlug do
|
defmodule Pleroma.Plugs.UserIsAdminPlug do
|
||||||
|
import Pleroma.Web.TranslationHelpers
|
||||||
import Plug.Conn
|
import Plug.Conn
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
|
@ -16,8 +17,7 @@ def call(%{assigns: %{user: %User{info: %{is_admin: true}}}} = conn, _) do
|
||||||
|
|
||||||
def call(conn, _) do
|
def call(conn, _) do
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("application/json")
|
|> render_error(:forbidden, "User is not admin.")
|
||||||
|> send_resp(403, Jason.encode!(%{error: "User is not admin."}))
|
|
||||||
|> halt
|
|> halt
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.ReverseProxy.Client do
|
defmodule Pleroma.ReverseProxy.Client do
|
||||||
@callback request(atom(), String.t(), [tuple()], String.t(), list()) ::
|
@callback request(atom(), String.t(), [tuple()], String.t(), list()) ::
|
||||||
{:ok, pos_integer(), [tuple()], reference() | map()}
|
{:ok, pos_integer(), [tuple()], reference() | map()}
|
||||||
|
|
|
@ -61,7 +61,7 @@ defmodule Pleroma.ReverseProxy do
|
||||||
* `http`: options for [hackney](https://github.com/benoitc/hackney).
|
* `http`: options for [hackney](https://github.com/benoitc/hackney).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@default_hackney_options []
|
@default_hackney_options [pool: :media]
|
||||||
|
|
||||||
@inline_content_types [
|
@inline_content_types [
|
||||||
"image/gif",
|
"image/gif",
|
||||||
|
@ -94,7 +94,8 @@ def call(_conn, _url, _opts \\ [])
|
||||||
|
|
||||||
def call(conn = %{method: method}, url, opts) when method in @methods do
|
def call(conn = %{method: method}, url, opts) when method in @methods do
|
||||||
hackney_opts =
|
hackney_opts =
|
||||||
@default_hackney_options
|
Pleroma.HTTP.Connection.hackney_options([])
|
||||||
|
|> Keyword.merge(@default_hackney_options)
|
||||||
|> Keyword.merge(Keyword.get(opts, :http, []))
|
|> Keyword.merge(Keyword.get(opts, :http, []))
|
||||||
|> HTTP.process_request_options()
|
|> HTTP.process_request_options()
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Uploaders.Uploader do
|
defmodule Pleroma.Uploaders.Uploader do
|
||||||
|
import Pleroma.Web.Gettext
|
||||||
|
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Defines the contract to put and get an uploaded file to any backend.
|
Defines the contract to put and get an uploaded file to any backend.
|
||||||
"""
|
"""
|
||||||
|
@ -66,7 +68,7 @@ defp handle_callback(uploader, upload) do
|
||||||
{:error, error}
|
{:error, error}
|
||||||
end
|
end
|
||||||
after
|
after
|
||||||
30_000 -> {:error, "Uploader callback timeout"}
|
30_000 -> {:error, dgettext("errors", "Uploader callback timeout")}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -52,6 +52,7 @@ defmodule Pleroma.User do
|
||||||
field(:avatar, :map)
|
field(:avatar, :map)
|
||||||
field(:local, :boolean, default: true)
|
field(:local, :boolean, default: true)
|
||||||
field(:follower_address, :string)
|
field(:follower_address, :string)
|
||||||
|
field(:following_address, :string)
|
||||||
field(:search_rank, :float, virtual: true)
|
field(:search_rank, :float, virtual: true)
|
||||||
field(:search_type, :integer, virtual: true)
|
field(:search_type, :integer, virtual: true)
|
||||||
field(:tags, {:array, :string}, default: [])
|
field(:tags, {:array, :string}, default: [])
|
||||||
|
@ -107,6 +108,10 @@ def ap_id(%User{nickname: nickname}) do
|
||||||
def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
|
def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
|
||||||
def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
|
def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
|
||||||
|
|
||||||
|
@spec ap_following(User.t()) :: Sring.t()
|
||||||
|
def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
|
||||||
|
def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
|
||||||
|
|
||||||
def user_info(%User{} = user, args \\ %{}) do
|
def user_info(%User{} = user, args \\ %{}) do
|
||||||
following_count =
|
following_count =
|
||||||
if args[:following_count], do: args[:following_count], else: following_count(user)
|
if args[:following_count], do: args[:following_count], else: following_count(user)
|
||||||
|
@ -128,6 +133,7 @@ def set_info_cache(user, args) do
|
||||||
Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user, args))
|
Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user, args))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
|
||||||
def restrict_deactivated(query) do
|
def restrict_deactivated(query) do
|
||||||
from(u in query,
|
from(u in query,
|
||||||
where: not fragment("? \\? 'deactivated' AND ?->'deactivated' @> 'true'", u.info, u.info)
|
where: not fragment("? \\? 'deactivated' AND ?->'deactivated' @> 'true'", u.info, u.info)
|
||||||
|
@ -162,9 +168,10 @@ def remote_user_creation(params) do
|
||||||
|
|
||||||
if changes.valid? do
|
if changes.valid? do
|
||||||
case info_cng.changes[:source_data] do
|
case info_cng.changes[:source_data] do
|
||||||
%{"followers" => followers} ->
|
%{"followers" => followers, "following" => following} ->
|
||||||
changes
|
changes
|
||||||
|> put_change(:follower_address, followers)
|
|> put_change(:follower_address, followers)
|
||||||
|
|> put_change(:following_address, following)
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
followers = User.ap_followers(%User{nickname: changes.changes[:nickname]})
|
followers = User.ap_followers(%User{nickname: changes.changes[:nickname]})
|
||||||
|
@ -196,7 +203,14 @@ def upgrade_changeset(struct, params \\ %{}) do
|
||||||
|> User.Info.user_upgrade(params[:info])
|
|> User.Info.user_upgrade(params[:info])
|
||||||
|
|
||||||
struct
|
struct
|
||||||
|> cast(params, [:bio, :name, :follower_address, :avatar, :last_refreshed_at])
|
|> cast(params, [
|
||||||
|
:bio,
|
||||||
|
:name,
|
||||||
|
:follower_address,
|
||||||
|
:following_address,
|
||||||
|
:avatar,
|
||||||
|
:last_refreshed_at
|
||||||
|
])
|
||||||
|> unique_constraint(:nickname)
|
|> unique_constraint(:nickname)
|
||||||
|> validate_format(:nickname, local_nickname_regex())
|
|> validate_format(:nickname, local_nickname_regex())
|
||||||
|> validate_length(:bio, max: 5000)
|
|> validate_length(:bio, max: 5000)
|
||||||
|
@ -735,10 +749,13 @@ def get_recipients_from_activity(%Activity{recipients: to}) do
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
||||||
def mute(muter, %User{ap_id: ap_id}) do
|
@spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
|
||||||
|
def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
|
||||||
|
info = muter.info
|
||||||
|
|
||||||
info_cng =
|
info_cng =
|
||||||
muter.info
|
User.Info.add_to_mutes(info, ap_id)
|
||||||
|> User.Info.add_to_mutes(ap_id)
|
|> User.Info.add_to_muted_notifications(info, ap_id, notifications?)
|
||||||
|
|
||||||
cng =
|
cng =
|
||||||
change(muter)
|
change(muter)
|
||||||
|
@ -748,9 +765,11 @@ def mute(muter, %User{ap_id: ap_id}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def unmute(muter, %{ap_id: ap_id}) do
|
def unmute(muter, %{ap_id: ap_id}) do
|
||||||
|
info = muter.info
|
||||||
|
|
||||||
info_cng =
|
info_cng =
|
||||||
muter.info
|
User.Info.remove_from_mutes(info, ap_id)
|
||||||
|> User.Info.remove_from_mutes(ap_id)
|
|> User.Info.remove_from_muted_notifications(info, ap_id)
|
||||||
|
|
||||||
cng =
|
cng =
|
||||||
change(muter)
|
change(muter)
|
||||||
|
@ -846,6 +865,12 @@ def unblock(blocker, %{ap_id: ap_id}) do
|
||||||
def mutes?(nil, _), do: false
|
def mutes?(nil, _), do: false
|
||||||
def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.info.mutes, ap_id)
|
def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.info.mutes, ap_id)
|
||||||
|
|
||||||
|
@spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
|
||||||
|
def muted_notifications?(nil, _), do: false
|
||||||
|
|
||||||
|
def muted_notifications?(user, %{ap_id: ap_id}),
|
||||||
|
do: Enum.member?(user.info.muted_notifications, ap_id)
|
||||||
|
|
||||||
def blocks?(%User{info: info} = _user, %{ap_id: ap_id}) do
|
def blocks?(%User{info: info} = _user, %{ap_id: ap_id}) do
|
||||||
blocks = info.blocks
|
blocks = info.blocks
|
||||||
domain_blocks = info.domain_blocks
|
domain_blocks = info.domain_blocks
|
||||||
|
@ -937,6 +962,8 @@ def delete(%User{} = user),
|
||||||
|
|
||||||
@spec perform(atom(), User.t()) :: {:ok, User.t()}
|
@spec perform(atom(), User.t()) :: {:ok, User.t()}
|
||||||
def perform(:delete, %User{} = user) do
|
def perform(:delete, %User{} = user) do
|
||||||
|
{:ok, _user} = ActivityPub.delete(user)
|
||||||
|
|
||||||
# Remove all relationships
|
# Remove all relationships
|
||||||
{:ok, followers} = User.get_followers(user)
|
{:ok, followers} = User.get_followers(user)
|
||||||
|
|
||||||
|
@ -953,8 +980,8 @@ def perform(:delete, %User{} = user) do
|
||||||
end)
|
end)
|
||||||
|
|
||||||
delete_user_activities(user)
|
delete_user_activities(user)
|
||||||
|
invalidate_cache(user)
|
||||||
{:ok, _user} = Repo.delete(user)
|
Repo.delete(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec perform(atom(), User.t()) :: {:ok, User.t()}
|
@spec perform(atom(), User.t()) :: {:ok, User.t()}
|
||||||
|
@ -1010,42 +1037,20 @@ def perform(:follow_import, %User{} = follower, followed_identifiers)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec sync_follow_counter() :: :ok
|
@spec external_users_query() :: Ecto.Query.t()
|
||||||
def sync_follow_counter,
|
def external_users_query do
|
||||||
do: PleromaJobQueue.enqueue(:background, __MODULE__, [:sync_follow_counters])
|
User.Query.build(%{
|
||||||
|
external: true,
|
||||||
@spec perform(:sync_follow_counters) :: :ok
|
active: true,
|
||||||
def perform(:sync_follow_counters) do
|
order_by: :id
|
||||||
{:ok, _pid} = Agent.start_link(fn -> %{} end, name: :domain_errors)
|
})
|
||||||
config = Pleroma.Config.get([:instance, :external_user_synchronization])
|
|
||||||
|
|
||||||
:ok = sync_follow_counters(config)
|
|
||||||
Agent.stop(:domain_errors)
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec sync_follow_counters(keyword()) :: :ok
|
|
||||||
def sync_follow_counters(opts \\ []) do
|
|
||||||
users = external_users(opts)
|
|
||||||
|
|
||||||
if length(users) > 0 do
|
|
||||||
errors = Agent.get(:domain_errors, fn state -> state end)
|
|
||||||
{last, updated_errors} = User.Synchronization.call(users, errors, opts)
|
|
||||||
Agent.update(:domain_errors, fn _state -> updated_errors end)
|
|
||||||
sync_follow_counters(max_id: last.id, limit: opts[:limit])
|
|
||||||
else
|
|
||||||
:ok
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec external_users(keyword()) :: [User.t()]
|
@spec external_users(keyword()) :: [User.t()]
|
||||||
def external_users(opts \\ []) do
|
def external_users(opts \\ []) do
|
||||||
query =
|
query =
|
||||||
User.Query.build(%{
|
external_users_query()
|
||||||
external: true,
|
|> select([u], struct(u, [:id, :ap_id, :info]))
|
||||||
active: true,
|
|
||||||
order_by: :id,
|
|
||||||
select: [:id, :ap_id, :info]
|
|
||||||
})
|
|
||||||
|
|
||||||
query =
|
query =
|
||||||
if opts[:max_id],
|
if opts[:max_id],
|
||||||
|
|
|
@ -24,6 +24,7 @@ defmodule Pleroma.User.Info do
|
||||||
field(:domain_blocks, {:array, :string}, default: [])
|
field(:domain_blocks, {:array, :string}, default: [])
|
||||||
field(:mutes, {:array, :string}, default: [])
|
field(:mutes, {:array, :string}, default: [])
|
||||||
field(:muted_reblogs, {:array, :string}, default: [])
|
field(:muted_reblogs, {:array, :string}, default: [])
|
||||||
|
field(:muted_notifications, {:array, :string}, default: [])
|
||||||
field(:subscribers, {:array, :string}, default: [])
|
field(:subscribers, {:array, :string}, default: [])
|
||||||
field(:deactivated, :boolean, default: false)
|
field(:deactivated, :boolean, default: false)
|
||||||
field(:no_rich_text, :boolean, default: false)
|
field(:no_rich_text, :boolean, default: false)
|
||||||
|
@ -120,6 +121,16 @@ def set_mutes(info, mutes) do
|
||||||
|> validate_required([:mutes])
|
|> validate_required([:mutes])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec set_notification_mutes(Changeset.t(), [String.t()], boolean()) :: Changeset.t()
|
||||||
|
def set_notification_mutes(changeset, muted_notifications, notifications?) do
|
||||||
|
if notifications? do
|
||||||
|
put_change(changeset, :muted_notifications, muted_notifications)
|
||||||
|
|> validate_required([:muted_notifications])
|
||||||
|
else
|
||||||
|
changeset
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def set_blocks(info, blocks) do
|
def set_blocks(info, blocks) do
|
||||||
params = %{blocks: blocks}
|
params = %{blocks: blocks}
|
||||||
|
|
||||||
|
@ -136,14 +147,31 @@ def set_subscribers(info, subscribers) do
|
||||||
|> validate_required([:subscribers])
|
|> validate_required([:subscribers])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec add_to_mutes(Info.t(), String.t()) :: Changeset.t()
|
||||||
def add_to_mutes(info, muted) do
|
def add_to_mutes(info, muted) do
|
||||||
set_mutes(info, Enum.uniq([muted | info.mutes]))
|
set_mutes(info, Enum.uniq([muted | info.mutes]))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec add_to_muted_notifications(Changeset.t(), Info.t(), String.t(), boolean()) ::
|
||||||
|
Changeset.t()
|
||||||
|
def add_to_muted_notifications(changeset, info, muted, notifications?) do
|
||||||
|
set_notification_mutes(
|
||||||
|
changeset,
|
||||||
|
Enum.uniq([muted | info.muted_notifications]),
|
||||||
|
notifications?
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec remove_from_mutes(Info.t(), String.t()) :: Changeset.t()
|
||||||
def remove_from_mutes(info, muted) do
|
def remove_from_mutes(info, muted) do
|
||||||
set_mutes(info, List.delete(info.mutes, muted))
|
set_mutes(info, List.delete(info.mutes, muted))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec remove_from_muted_notifications(Changeset.t(), Info.t(), String.t()) :: Changeset.t()
|
||||||
|
def remove_from_muted_notifications(changeset, info, muted) do
|
||||||
|
set_notification_mutes(changeset, List.delete(info.muted_notifications, muted), true)
|
||||||
|
end
|
||||||
|
|
||||||
def add_to_block(info, blocked) do
|
def add_to_block(info, blocked) do
|
||||||
set_blocks(info, Enum.uniq([blocked | info.blocks]))
|
set_blocks(info, Enum.uniq([blocked | info.blocks]))
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.User.Search do
|
defmodule Pleroma.User.Search do
|
||||||
|
alias Pleroma.Pagination
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
@ -18,8 +19,7 @@ def search(query_string, opts \\ []) do
|
||||||
|
|
||||||
for_user = Keyword.get(opts, :for_user)
|
for_user = Keyword.get(opts, :for_user)
|
||||||
|
|
||||||
# Strip the beginning @ off if there is a query
|
query_string = format_query(query_string)
|
||||||
query_string = String.trim_leading(query_string, "@")
|
|
||||||
|
|
||||||
maybe_resolve(resolve, for_user, query_string)
|
maybe_resolve(resolve, for_user, query_string)
|
||||||
|
|
||||||
|
@ -33,13 +33,24 @@ def search(query_string, opts \\ []) do
|
||||||
|
|
||||||
query_string
|
query_string
|
||||||
|> search_query(for_user, following)
|
|> search_query(for_user, following)
|
||||||
|> paginate(result_limit, offset)
|
|> Pagination.fetch_paginated(%{"offset" => offset, "limit" => result_limit}, :offset)
|
||||||
|> Repo.all()
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
results
|
results
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp format_query(query_string) do
|
||||||
|
# Strip the beginning @ off if there is a query
|
||||||
|
query_string = String.trim_leading(query_string, "@")
|
||||||
|
|
||||||
|
with [name, domain] <- String.split(query_string, "@"),
|
||||||
|
formatted_domain <- String.replace(domain, ~r/[!-\-|@|[-`|{-~|\/|:]+/, "") do
|
||||||
|
name <> "@" <> to_string(:idna.encode(formatted_domain))
|
||||||
|
else
|
||||||
|
_ -> query_string
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp search_query(query_string, for_user, following) do
|
defp search_query(query_string, for_user, following) do
|
||||||
for_user
|
for_user
|
||||||
|> base_query(following)
|
|> base_query(following)
|
||||||
|
@ -76,10 +87,6 @@ defp filter_blocked_domains(query, %User{info: %{domain_blocks: domain_blocks}})
|
||||||
|
|
||||||
defp filter_blocked_domains(query, _), do: query
|
defp filter_blocked_domains(query, _), do: query
|
||||||
|
|
||||||
defp paginate(query, limit, offset) do
|
|
||||||
from(q in query, limit: ^limit, offset: ^offset)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp union_subqueries({fts_subquery, trigram_subquery}) do
|
defp union_subqueries({fts_subquery, trigram_subquery}) do
|
||||||
from(s in trigram_subquery, union_all: ^fts_subquery)
|
from(s in trigram_subquery, union_all: ^fts_subquery)
|
||||||
end
|
end
|
||||||
|
@ -151,7 +158,7 @@ defp boost_search_rank_query(query, for_user) do
|
||||||
defp fts_search_subquery(query, term) do
|
defp fts_search_subquery(query, term) do
|
||||||
processed_query =
|
processed_query =
|
||||||
String.trim_trailing(term, "@" <> local_domain())
|
String.trim_trailing(term, "@" <> local_domain())
|
||||||
|> String.replace(~r/\W+/, " ")
|
|> String.replace(~r/[!-\/|@|[-`|{-~|:-?]+/, " ")
|
||||||
|> String.trim()
|
|> String.trim()
|
||||||
|> String.split()
|
|> String.split()
|
||||||
|> Enum.map(&(&1 <> ":*"))
|
|> Enum.map(&(&1 <> ":*"))
|
||||||
|
|
|
@ -1,60 +0,0 @@
|
||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.User.Synchronization do
|
|
||||||
alias Pleroma.HTTP
|
|
||||||
alias Pleroma.User
|
|
||||||
|
|
||||||
@spec call([User.t()], map(), keyword()) :: {User.t(), map()}
|
|
||||||
def call(users, errors, opts \\ []) do
|
|
||||||
do_call(users, errors, opts)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp do_call([user | []], errors, opts) do
|
|
||||||
updated = fetch_counters(user, errors, opts)
|
|
||||||
{user, updated}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp do_call([user | others], errors, opts) do
|
|
||||||
updated = fetch_counters(user, errors, opts)
|
|
||||||
do_call(others, updated, opts)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp fetch_counters(user, errors, opts) do
|
|
||||||
%{host: host} = URI.parse(user.ap_id)
|
|
||||||
|
|
||||||
info = %{}
|
|
||||||
{following, errors} = fetch_counter(user.ap_id <> "/following", host, errors, opts)
|
|
||||||
info = if following, do: Map.put(info, :following_count, following), else: info
|
|
||||||
|
|
||||||
{followers, errors} = fetch_counter(user.ap_id <> "/followers", host, errors, opts)
|
|
||||||
info = if followers, do: Map.put(info, :follower_count, followers), else: info
|
|
||||||
|
|
||||||
User.set_info_cache(user, info)
|
|
||||||
errors
|
|
||||||
end
|
|
||||||
|
|
||||||
defp available_domain?(domain, errors, opts) do
|
|
||||||
max_retries = Keyword.get(opts, :max_retries, 3)
|
|
||||||
not (Map.has_key?(errors, domain) && errors[domain] >= max_retries)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp fetch_counter(url, host, errors, opts) do
|
|
||||||
with true <- available_domain?(host, errors, opts),
|
|
||||||
{:ok, %{body: body, status: code}} when code in 200..299 <-
|
|
||||||
HTTP.get(
|
|
||||||
url,
|
|
||||||
[{:Accept, "application/activity+json"}]
|
|
||||||
),
|
|
||||||
{:ok, data} <- Jason.decode(body) do
|
|
||||||
{data["totalItems"], errors}
|
|
||||||
else
|
|
||||||
false ->
|
|
||||||
{nil, errors}
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
{nil, Map.update(errors, host, 1, &(&1 + 1))}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,32 +0,0 @@
|
||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-onl
|
|
||||||
|
|
||||||
defmodule Pleroma.User.SynchronizationWorker do
|
|
||||||
use GenServer
|
|
||||||
|
|
||||||
def start_link do
|
|
||||||
config = Pleroma.Config.get([:instance, :external_user_synchronization])
|
|
||||||
|
|
||||||
if config[:enabled] do
|
|
||||||
GenServer.start_link(__MODULE__, interval: config[:interval])
|
|
||||||
else
|
|
||||||
:ignore
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def init(opts) do
|
|
||||||
schedule_next(opts)
|
|
||||||
{:ok, opts}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_info(:sync_follow_counters, opts) do
|
|
||||||
Pleroma.User.sync_follow_counter()
|
|
||||||
schedule_next(opts)
|
|
||||||
{:noreply, opts}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp schedule_next(opts) do
|
|
||||||
Process.send_after(self(), :sync_follow_counters, opts[:interval])
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.User.WelcomeMessage do
|
defmodule Pleroma.User.WelcomeMessage do
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
alias Pleroma.Conversation
|
alias Pleroma.Conversation
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.Object.Containment
|
||||||
alias Pleroma.Object.Fetcher
|
alias Pleroma.Object.Fetcher
|
||||||
alias Pleroma.Pagination
|
alias Pleroma.Pagination
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
|
@ -126,6 +127,7 @@ def insert(map, local \\ true, fake \\ false) when is_map(map) do
|
||||||
{:ok, map} <- MRF.filter(map),
|
{:ok, map} <- MRF.filter(map),
|
||||||
{recipients, _, _} = get_recipients(map),
|
{recipients, _, _} = get_recipients(map),
|
||||||
{:fake, false, map, recipients} <- {:fake, fake, map, recipients},
|
{:fake, false, map, recipients} <- {:fake, fake, map, recipients},
|
||||||
|
:ok <- Containment.contain_child(map),
|
||||||
{:ok, map, object} <- insert_full_object(map) do
|
{:ok, map, object} <- insert_full_object(map) do
|
||||||
{:ok, activity} =
|
{:ok, activity} =
|
||||||
Repo.insert(%Activity{
|
Repo.insert(%Activity{
|
||||||
|
@ -405,6 +407,19 @@ def unfollow(follower, followed, activity_id \\ nil, local \\ true) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def delete(%User{ap_id: ap_id, follower_address: follower_address} = user) do
|
||||||
|
with data <- %{
|
||||||
|
"to" => [follower_address],
|
||||||
|
"type" => "Delete",
|
||||||
|
"actor" => ap_id,
|
||||||
|
"object" => %{"type" => "Person", "id" => ap_id}
|
||||||
|
},
|
||||||
|
{:ok, activity} <- insert(data, true, true),
|
||||||
|
:ok <- maybe_federate(activity) do
|
||||||
|
{:ok, user}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, local \\ true) do
|
def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, local \\ true) do
|
||||||
user = User.get_cached_by_ap_id(actor)
|
user = User.get_cached_by_ap_id(actor)
|
||||||
to = (object.data["to"] || []) ++ (object.data["cc"] || [])
|
to = (object.data["to"] || []) ++ (object.data["cc"] || [])
|
||||||
|
@ -981,6 +996,7 @@ defp object_to_user_data(data) do
|
||||||
avatar: avatar,
|
avatar: avatar,
|
||||||
name: data["name"],
|
name: data["name"],
|
||||||
follower_address: data["followers"],
|
follower_address: data["followers"],
|
||||||
|
following_address: data["following"],
|
||||||
bio: data["summary"]
|
bio: data["summary"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,9 +31,8 @@ def relay_active?(conn, _) do
|
||||||
conn
|
conn
|
||||||
else
|
else
|
||||||
conn
|
conn
|
||||||
|> put_status(404)
|
|> render_error(:not_found, "not found")
|
||||||
|> json(%{error: "not found"})
|
|> halt()
|
||||||
|> halt
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -104,43 +103,57 @@ def activity(conn, %{"uuid" => uuid}) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def following(conn, %{"nickname" => nickname, "page" => page}) do
|
def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
|
||||||
with %User{} = user <- User.get_cached_by_nickname(nickname),
|
with %User{} = user <- User.get_cached_by_nickname(nickname),
|
||||||
{:ok, user} <- User.ensure_keys_present(user) do
|
{user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
|
||||||
|
{:show_follows, true} <-
|
||||||
|
{:show_follows, (for_user && for_user == user) || !user.info.hide_follows} do
|
||||||
{page, _} = Integer.parse(page)
|
{page, _} = Integer.parse(page)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_resp_header("content-type", "application/activity+json")
|
|> put_resp_header("content-type", "application/activity+json")
|
||||||
|> json(UserView.render("following.json", %{user: user, page: page}))
|
|> json(UserView.render("following.json", %{user: user, page: page, for: for_user}))
|
||||||
|
else
|
||||||
|
{:show_follows, _} ->
|
||||||
|
conn
|
||||||
|
|> put_resp_header("content-type", "application/activity+json")
|
||||||
|
|> send_resp(403, "")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def following(conn, %{"nickname" => nickname}) do
|
def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
|
||||||
with %User{} = user <- User.get_cached_by_nickname(nickname),
|
with %User{} = user <- User.get_cached_by_nickname(nickname),
|
||||||
{:ok, user} <- User.ensure_keys_present(user) do
|
{user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
|
||||||
conn
|
conn
|
||||||
|> put_resp_header("content-type", "application/activity+json")
|
|> put_resp_header("content-type", "application/activity+json")
|
||||||
|> json(UserView.render("following.json", %{user: user}))
|
|> json(UserView.render("following.json", %{user: user, for: for_user}))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def followers(conn, %{"nickname" => nickname, "page" => page}) do
|
def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
|
||||||
with %User{} = user <- User.get_cached_by_nickname(nickname),
|
with %User{} = user <- User.get_cached_by_nickname(nickname),
|
||||||
{:ok, user} <- User.ensure_keys_present(user) do
|
{user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
|
||||||
|
{:show_followers, true} <-
|
||||||
|
{:show_followers, (for_user && for_user == user) || !user.info.hide_followers} do
|
||||||
{page, _} = Integer.parse(page)
|
{page, _} = Integer.parse(page)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_resp_header("content-type", "application/activity+json")
|
|> put_resp_header("content-type", "application/activity+json")
|
||||||
|> json(UserView.render("followers.json", %{user: user, page: page}))
|
|> json(UserView.render("followers.json", %{user: user, page: page, for: for_user}))
|
||||||
|
else
|
||||||
|
{:show_followers, _} ->
|
||||||
|
conn
|
||||||
|
|> put_resp_header("content-type", "application/activity+json")
|
||||||
|
|> send_resp(403, "")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def followers(conn, %{"nickname" => nickname}) do
|
def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
|
||||||
with %User{} = user <- User.get_cached_by_nickname(nickname),
|
with %User{} = user <- User.get_cached_by_nickname(nickname),
|
||||||
{:ok, user} <- User.ensure_keys_present(user) do
|
{user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
|
||||||
conn
|
conn
|
||||||
|> put_resp_header("content-type", "application/activity+json")
|
|> put_resp_header("content-type", "application/activity+json")
|
||||||
|> json(UserView.render("followers.json", %{user: user}))
|
|> json(UserView.render("followers.json", %{user: user, for: for_user}))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -190,7 +203,7 @@ def inbox(conn, params) do
|
||||||
Logger.info(inspect(conn.req_headers))
|
Logger.info(inspect(conn.req_headers))
|
||||||
end
|
end
|
||||||
|
|
||||||
json(conn, "error")
|
json(conn, dgettext("errors", "error"))
|
||||||
end
|
end
|
||||||
|
|
||||||
def relay(conn, _params) do
|
def relay(conn, _params) do
|
||||||
|
@ -218,9 +231,15 @@ def read_inbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = par
|
||||||
|> put_resp_header("content-type", "application/activity+json")
|
|> put_resp_header("content-type", "application/activity+json")
|
||||||
|> json(UserView.render("inbox.json", %{user: user, max_id: params["max_id"]}))
|
|> json(UserView.render("inbox.json", %{user: user, max_id: params["max_id"]}))
|
||||||
else
|
else
|
||||||
|
err =
|
||||||
|
dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}",
|
||||||
|
nickname: nickname,
|
||||||
|
as_nickname: user.nickname
|
||||||
|
)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_status(:forbidden)
|
|> put_status(:forbidden)
|
||||||
|> json("can't read inbox of #{nickname} as #{user.nickname}")
|
|> json(err)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -246,7 +265,7 @@ def handle_user_activity(user, %{"type" => "Delete"} = params) do
|
||||||
{:ok, delete} <- ActivityPub.delete(object) do
|
{:ok, delete} <- ActivityPub.delete(object) do
|
||||||
{:ok, delete}
|
{:ok, delete}
|
||||||
else
|
else
|
||||||
_ -> {:error, "Can't delete object"}
|
_ -> {:error, dgettext("errors", "Can't delete object")}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -255,12 +274,12 @@ def handle_user_activity(user, %{"type" => "Like"} = params) do
|
||||||
{:ok, activity, _object} <- ActivityPub.like(user, object) do
|
{:ok, activity, _object} <- ActivityPub.like(user, object) do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
else
|
else
|
||||||
_ -> {:error, "Can't like object"}
|
_ -> {:error, dgettext("errors", "Can't like object")}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_user_activity(_, _) do
|
def handle_user_activity(_, _) do
|
||||||
{:error, "Unhandled activity type"}
|
{:error, dgettext("errors", "Unhandled activity type")}
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_outbox(
|
def update_outbox(
|
||||||
|
@ -288,22 +307,28 @@ def update_outbox(
|
||||||
|> json(message)
|
|> json(message)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
err =
|
||||||
|
dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}",
|
||||||
|
nickname: nickname,
|
||||||
|
as_nickname: user.nickname
|
||||||
|
)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_status(:forbidden)
|
|> put_status(:forbidden)
|
||||||
|> json("can't update outbox of #{nickname} as #{user.nickname}")
|
|> json(err)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def errors(conn, {:error, :not_found}) do
|
def errors(conn, {:error, :not_found}) do
|
||||||
conn
|
conn
|
||||||
|> put_status(404)
|
|> put_status(:not_found)
|
||||||
|> json("Not found")
|
|> json(dgettext("errors", "Not found"))
|
||||||
end
|
end
|
||||||
|
|
||||||
def errors(conn, _e) do
|
def errors(conn, _e) do
|
||||||
conn
|
conn
|
||||||
|> put_status(500)
|
|> put_status(:internal_server_error)
|
||||||
|> json("error")
|
|> json(dgettext("errors", "error"))
|
||||||
end
|
end
|
||||||
|
|
||||||
defp set_requester_reachable(%Plug.Conn{} = conn, _) do
|
defp set_requester_reachable(%Plug.Conn{} = conn, _) do
|
||||||
|
@ -314,4 +339,17 @@ defp set_requester_reachable(%Plug.Conn{} = conn, _) do
|
||||||
|
|
||||||
conn
|
conn
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
|
||||||
|
{:ok, new_user} = User.ensure_keys_present(user)
|
||||||
|
|
||||||
|
for_user =
|
||||||
|
if new_user != user and match?(%User{}, for_user) do
|
||||||
|
User.get_cached_by_nickname(for_user.nickname)
|
||||||
|
else
|
||||||
|
for_user
|
||||||
|
end
|
||||||
|
|
||||||
|
{new_user, for_user}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,8 +9,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrepended do
|
||||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||||
|
|
||||||
@reply_prefix Regex.compile!("^re:[[:space:]]*", [:caseless])
|
@reply_prefix Regex.compile!("^re:[[:space:]]*", [:caseless])
|
||||||
|
|
||||||
def filter_by_summary(
|
def filter_by_summary(
|
||||||
%{"summary" => parent_summary} = _parent,
|
%{data: %{"summary" => parent_summary}} = _in_reply_to,
|
||||||
%{"summary" => child_summary} = child
|
%{"summary" => child_summary} = child
|
||||||
)
|
)
|
||||||
when not is_nil(child_summary) and byte_size(child_summary) > 0 and
|
when not is_nil(child_summary) and byte_size(child_summary) > 0 and
|
||||||
|
@ -24,17 +25,13 @@ def filter_by_summary(
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter_by_summary(_parent, child), do: child
|
def filter_by_summary(_in_reply_to, child), do: child
|
||||||
|
|
||||||
def filter(%{"type" => activity_type} = object) when activity_type == "Create" do
|
|
||||||
child = object["object"]
|
|
||||||
in_reply_to = Object.normalize(child["inReplyTo"])
|
|
||||||
|
|
||||||
|
def filter(%{"type" => "Create", "object" => child_object} = object) do
|
||||||
child =
|
child =
|
||||||
if(in_reply_to,
|
child_object["inReplyTo"]
|
||||||
do: filter_by_summary(in_reply_to.data, child),
|
|> Object.normalize(child_object["inReplyTo"])
|
||||||
else: child
|
|> filter_by_summary(child_object)
|
||||||
)
|
|
||||||
|
|
||||||
object = Map.put(object, "object", child)
|
object = Map.put(object, "object", child)
|
||||||
|
|
||||||
|
|
|
@ -10,19 +10,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoPlaceholderTextPolicy do
|
||||||
def filter(
|
def filter(
|
||||||
%{
|
%{
|
||||||
"type" => "Create",
|
"type" => "Create",
|
||||||
"object" => %{"content" => content, "attachment" => _attachment} = child_object
|
"object" => %{"content" => content, "attachment" => _} = _child_object
|
||||||
} = object
|
} = object
|
||||||
)
|
)
|
||||||
when content in [".", "<p>.</p>"] do
|
when content in [".", "<p>.</p>"] do
|
||||||
child_object =
|
{:ok, put_in(object, ["object", "content"], "")}
|
||||||
child_object
|
|
||||||
|> Map.put("content", "")
|
|
||||||
|
|
||||||
object =
|
|
||||||
object
|
|
||||||
|> Map.put("object", child_object)
|
|
||||||
|
|
||||||
{:ok, object}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
|
|
|
@ -8,18 +8,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkup do
|
||||||
|
|
||||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||||
|
|
||||||
def filter(%{"type" => activity_type} = object) when activity_type == "Create" do
|
def filter(%{"type" => "Create", "object" => child_object} = object) do
|
||||||
scrub_policy = Pleroma.Config.get([:mrf_normalize_markup, :scrub_policy])
|
scrub_policy = Pleroma.Config.get([:mrf_normalize_markup, :scrub_policy])
|
||||||
|
|
||||||
child = object["object"]
|
|
||||||
|
|
||||||
content =
|
content =
|
||||||
child["content"]
|
child_object["content"]
|
||||||
|> HTML.filter_tags(scrub_policy)
|
|> HTML.filter_tags(scrub_policy)
|
||||||
|
|
||||||
child = Map.put(child, "content", content)
|
object = put_in(object, ["object", "content"], content)
|
||||||
|
|
||||||
object = Map.put(object, "object", child)
|
|
||||||
|
|
||||||
{:ok, object}
|
{:ok, object}
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,46 +3,42 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do
|
defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do
|
||||||
alias Pleroma.User
|
|
||||||
@moduledoc "Rejects non-public (followers-only, direct) activities"
|
@moduledoc "Rejects non-public (followers-only, direct) activities"
|
||||||
|
|
||||||
|
alias Pleroma.Config
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||||
|
|
||||||
|
@public "https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def filter(%{"type" => "Create"} = object) do
|
def filter(%{"type" => "Create"} = object) do
|
||||||
user = User.get_cached_by_ap_id(object["actor"])
|
user = User.get_cached_by_ap_id(object["actor"])
|
||||||
public = "https://www.w3.org/ns/activitystreams#Public"
|
|
||||||
|
|
||||||
# Determine visibility
|
# Determine visibility
|
||||||
visibility =
|
visibility =
|
||||||
cond do
|
cond do
|
||||||
public in object["to"] -> "public"
|
@public in object["to"] -> "public"
|
||||||
public in object["cc"] -> "unlisted"
|
@public in object["cc"] -> "unlisted"
|
||||||
user.follower_address in object["to"] -> "followers"
|
user.follower_address in object["to"] -> "followers"
|
||||||
true -> "direct"
|
true -> "direct"
|
||||||
end
|
end
|
||||||
|
|
||||||
policy = Pleroma.Config.get(:mrf_rejectnonpublic)
|
policy = Config.get(:mrf_rejectnonpublic)
|
||||||
|
|
||||||
case visibility do
|
cond do
|
||||||
"public" ->
|
visibility in ["public", "unlisted"] ->
|
||||||
{:ok, object}
|
{:ok, object}
|
||||||
|
|
||||||
"unlisted" ->
|
visibility == "followers" and Keyword.get(policy, :allow_followersonly) ->
|
||||||
{:ok, object}
|
{:ok, object}
|
||||||
|
|
||||||
"followers" ->
|
visibility == "direct" and Keyword.get(policy, :allow_direct) ->
|
||||||
with true <- Keyword.get(policy, :allow_followersonly) do
|
{:ok, object}
|
||||||
{:ok, object}
|
|
||||||
else
|
|
||||||
_e -> {:reject, nil}
|
|
||||||
end
|
|
||||||
|
|
||||||
"direct" ->
|
true ->
|
||||||
with true <- Keyword.get(policy, :allow_direct) do
|
{:reject, nil}
|
||||||
{:ok, object}
|
|
||||||
else
|
|
||||||
_e -> {:reject, nil}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -19,12 +19,17 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
|
||||||
- `mrf_tag:disable-any-subscription`: Reject any follow requests
|
- `mrf_tag:disable-any-subscription`: Reject any follow requests
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@public "https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
|
||||||
defp get_tags(%User{tags: tags}) when is_list(tags), do: tags
|
defp get_tags(%User{tags: tags}) when is_list(tags), do: tags
|
||||||
defp get_tags(_), do: []
|
defp get_tags(_), do: []
|
||||||
|
|
||||||
defp process_tag(
|
defp process_tag(
|
||||||
"mrf_tag:media-force-nsfw",
|
"mrf_tag:media-force-nsfw",
|
||||||
%{"type" => "Create", "object" => %{"attachment" => child_attachment} = object} = message
|
%{
|
||||||
|
"type" => "Create",
|
||||||
|
"object" => %{"attachment" => child_attachment} = object
|
||||||
|
} = message
|
||||||
)
|
)
|
||||||
when length(child_attachment) > 0 do
|
when length(child_attachment) > 0 do
|
||||||
tags = (object["tag"] || []) ++ ["nsfw"]
|
tags = (object["tag"] || []) ++ ["nsfw"]
|
||||||
|
@ -41,7 +46,10 @@ defp process_tag(
|
||||||
|
|
||||||
defp process_tag(
|
defp process_tag(
|
||||||
"mrf_tag:media-strip",
|
"mrf_tag:media-strip",
|
||||||
%{"type" => "Create", "object" => %{"attachment" => child_attachment} = object} = message
|
%{
|
||||||
|
"type" => "Create",
|
||||||
|
"object" => %{"attachment" => child_attachment} = object
|
||||||
|
} = message
|
||||||
)
|
)
|
||||||
when length(child_attachment) > 0 do
|
when length(child_attachment) > 0 do
|
||||||
object = Map.delete(object, "attachment")
|
object = Map.delete(object, "attachment")
|
||||||
|
@ -52,19 +60,22 @@ defp process_tag(
|
||||||
|
|
||||||
defp process_tag(
|
defp process_tag(
|
||||||
"mrf_tag:force-unlisted",
|
"mrf_tag:force-unlisted",
|
||||||
%{"type" => "Create", "to" => to, "cc" => cc, "actor" => actor} = message
|
%{
|
||||||
|
"type" => "Create",
|
||||||
|
"to" => to,
|
||||||
|
"cc" => cc,
|
||||||
|
"actor" => actor,
|
||||||
|
"object" => object
|
||||||
|
} = message
|
||||||
) do
|
) do
|
||||||
user = User.get_cached_by_ap_id(actor)
|
user = User.get_cached_by_ap_id(actor)
|
||||||
|
|
||||||
if Enum.member?(to, "https://www.w3.org/ns/activitystreams#Public") do
|
if Enum.member?(to, @public) do
|
||||||
to =
|
to = List.delete(to, @public) ++ [user.follower_address]
|
||||||
List.delete(to, "https://www.w3.org/ns/activitystreams#Public") ++ [user.follower_address]
|
cc = List.delete(cc, user.follower_address) ++ [@public]
|
||||||
|
|
||||||
cc =
|
|
||||||
List.delete(cc, user.follower_address) ++ ["https://www.w3.org/ns/activitystreams#Public"]
|
|
||||||
|
|
||||||
object =
|
object =
|
||||||
message["object"]
|
object
|
||||||
|> Map.put("to", to)
|
|> Map.put("to", to)
|
||||||
|> Map.put("cc", cc)
|
|> Map.put("cc", cc)
|
||||||
|
|
||||||
|
@ -82,19 +93,22 @@ defp process_tag(
|
||||||
|
|
||||||
defp process_tag(
|
defp process_tag(
|
||||||
"mrf_tag:sandbox",
|
"mrf_tag:sandbox",
|
||||||
%{"type" => "Create", "to" => to, "cc" => cc, "actor" => actor} = message
|
%{
|
||||||
|
"type" => "Create",
|
||||||
|
"to" => to,
|
||||||
|
"cc" => cc,
|
||||||
|
"actor" => actor,
|
||||||
|
"object" => object
|
||||||
|
} = message
|
||||||
) do
|
) do
|
||||||
user = User.get_cached_by_ap_id(actor)
|
user = User.get_cached_by_ap_id(actor)
|
||||||
|
|
||||||
if Enum.member?(to, "https://www.w3.org/ns/activitystreams#Public") or
|
if Enum.member?(to, @public) or Enum.member?(cc, @public) do
|
||||||
Enum.member?(cc, "https://www.w3.org/ns/activitystreams#Public") do
|
to = List.delete(to, @public) ++ [user.follower_address]
|
||||||
to =
|
cc = List.delete(cc, @public)
|
||||||
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 =
|
object =
|
||||||
message["object"]
|
object
|
||||||
|> Map.put("to", to)
|
|> Map.put("to", to)
|
||||||
|> Map.put("cc", cc)
|
|> Map.put("cc", cc)
|
||||||
|
|
||||||
|
@ -123,7 +137,8 @@ defp process_tag(
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp process_tag("mrf_tag:disable-any-subscription", %{"type" => "Follow"}), do: {:reject, nil}
|
defp process_tag("mrf_tag:disable-any-subscription", %{"type" => "Follow"}),
|
||||||
|
do: {:reject, nil}
|
||||||
|
|
||||||
defp process_tag(_, message), do: {:ok, message}
|
defp process_tag(_, message), do: {:ok, message}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,12 @@ defp filter_by_list(%{"actor" => actor} = object, allow_list) do
|
||||||
@impl true
|
@impl true
|
||||||
def filter(%{"actor" => actor} = object) do
|
def filter(%{"actor" => actor} = object) do
|
||||||
actor_info = URI.parse(actor)
|
actor_info = URI.parse(actor)
|
||||||
allow_list = Config.get([:mrf_user_allowlist, String.to_atom(actor_info.host)], [])
|
|
||||||
|
allow_list =
|
||||||
|
Config.get(
|
||||||
|
[:mrf_user_allowlist, String.to_atom(actor_info.host)],
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
filter_by_list(object, allow_list)
|
filter_by_list(object, allow_list)
|
||||||
end
|
end
|
|
@ -641,7 +641,7 @@ def handle_incoming(
|
||||||
# an error or a tombstone. This would allow us to verify that a deletion actually took
|
# an error or a tombstone. This would allow us to verify that a deletion actually took
|
||||||
# place.
|
# place.
|
||||||
def handle_incoming(
|
def handle_incoming(
|
||||||
%{"type" => "Delete", "object" => object_id, "actor" => _actor, "id" => _id} = data,
|
%{"type" => "Delete", "object" => object_id, "actor" => actor, "id" => _id} = data,
|
||||||
_options
|
_options
|
||||||
) do
|
) do
|
||||||
object_id = Utils.get_ap_id(object_id)
|
object_id = Utils.get_ap_id(object_id)
|
||||||
|
@ -653,7 +653,30 @@ def handle_incoming(
|
||||||
{:ok, activity} <- ActivityPub.delete(object, false) do
|
{:ok, activity} <- ActivityPub.delete(object, false) do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
else
|
else
|
||||||
_e -> :error
|
nil ->
|
||||||
|
case User.get_cached_by_ap_id(object_id) do
|
||||||
|
%User{ap_id: ^actor} = user ->
|
||||||
|
{:ok, followers} = User.get_followers(user)
|
||||||
|
|
||||||
|
Enum.each(followers, fn follower ->
|
||||||
|
User.unfollow(follower, user)
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:ok, friends} = User.get_friends(user)
|
||||||
|
|
||||||
|
Enum.each(friends, fn followed ->
|
||||||
|
User.unfollow(user, followed)
|
||||||
|
end)
|
||||||
|
|
||||||
|
User.invalidate_cache(user)
|
||||||
|
Repo.delete(user)
|
||||||
|
|
||||||
|
nil ->
|
||||||
|
:error
|
||||||
|
end
|
||||||
|
|
||||||
|
_e ->
|
||||||
|
:error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1064,6 +1087,10 @@ def upgrade_user_from_ap_id(ap_id) do
|
||||||
PleromaJobQueue.enqueue(:transmogrifier, __MODULE__, [:user_upgrade, user])
|
PleromaJobQueue.enqueue(:transmogrifier, __MODULE__, [:user_upgrade, user])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if Pleroma.Config.get([:instance, :external_user_synchronization]) do
|
||||||
|
update_following_followers_counters(user)
|
||||||
|
end
|
||||||
|
|
||||||
{:ok, user}
|
{:ok, user}
|
||||||
else
|
else
|
||||||
%User{} = user -> {:ok, user}
|
%User{} = user -> {:ok, user}
|
||||||
|
@ -1096,4 +1123,27 @@ def maybe_fix_user_object(data) do
|
||||||
data
|
data
|
||||||
|> maybe_fix_user_url
|
|> maybe_fix_user_url
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_following_followers_counters(user) do
|
||||||
|
info = %{}
|
||||||
|
|
||||||
|
following = fetch_counter(user.following_address)
|
||||||
|
info = if following, do: Map.put(info, :following_count, following), else: info
|
||||||
|
|
||||||
|
followers = fetch_counter(user.follower_address)
|
||||||
|
info = if followers, do: Map.put(info, :follower_count, followers), else: info
|
||||||
|
|
||||||
|
User.set_info_cache(user, info)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp fetch_counter(url) do
|
||||||
|
with {:ok, %{body: body, status: code}} when code in 200..299 <-
|
||||||
|
Pleroma.HTTP.get(
|
||||||
|
url,
|
||||||
|
[{:Accept, "application/activity+json"}]
|
||||||
|
),
|
||||||
|
{:ok, data} <- Jason.decode(body) do
|
||||||
|
data["totalItems"]
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -98,29 +98,31 @@ def render("user.json", %{user: user}) do
|
||||||
|> Map.merge(Utils.make_json_ld_header())
|
|> Map.merge(Utils.make_json_ld_header())
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("following.json", %{user: user, page: page}) do
|
def render("following.json", %{user: user, page: page} = opts) do
|
||||||
|
showing = (opts[:for] && opts[:for] == user) || !user.info.hide_follows
|
||||||
query = User.get_friends_query(user)
|
query = User.get_friends_query(user)
|
||||||
query = from(user in query, select: [:ap_id])
|
query = from(user in query, select: [:ap_id])
|
||||||
following = Repo.all(query)
|
following = Repo.all(query)
|
||||||
|
|
||||||
total =
|
total =
|
||||||
if !user.info.hide_follows do
|
if showing do
|
||||||
length(following)
|
length(following)
|
||||||
else
|
else
|
||||||
0
|
0
|
||||||
end
|
end
|
||||||
|
|
||||||
collection(following, "#{user.ap_id}/following", page, !user.info.hide_follows, total)
|
collection(following, "#{user.ap_id}/following", page, showing, total)
|
||||||
|> Map.merge(Utils.make_json_ld_header())
|
|> Map.merge(Utils.make_json_ld_header())
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("following.json", %{user: user}) do
|
def render("following.json", %{user: user} = opts) do
|
||||||
|
showing = (opts[:for] && opts[:for] == user) || !user.info.hide_follows
|
||||||
query = User.get_friends_query(user)
|
query = User.get_friends_query(user)
|
||||||
query = from(user in query, select: [:ap_id])
|
query = from(user in query, select: [:ap_id])
|
||||||
following = Repo.all(query)
|
following = Repo.all(query)
|
||||||
|
|
||||||
total =
|
total =
|
||||||
if !user.info.hide_follows do
|
if showing do
|
||||||
length(following)
|
length(following)
|
||||||
else
|
else
|
||||||
0
|
0
|
||||||
|
@ -130,34 +132,43 @@ def render("following.json", %{user: user}) do
|
||||||
"id" => "#{user.ap_id}/following",
|
"id" => "#{user.ap_id}/following",
|
||||||
"type" => "OrderedCollection",
|
"type" => "OrderedCollection",
|
||||||
"totalItems" => total,
|
"totalItems" => total,
|
||||||
"first" => collection(following, "#{user.ap_id}/following", 1, !user.info.hide_follows)
|
"first" =>
|
||||||
|
if showing do
|
||||||
|
collection(following, "#{user.ap_id}/following", 1, !user.info.hide_follows)
|
||||||
|
else
|
||||||
|
"#{user.ap_id}/following?page=1"
|
||||||
|
end
|
||||||
}
|
}
|
||||||
|> Map.merge(Utils.make_json_ld_header())
|
|> Map.merge(Utils.make_json_ld_header())
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("followers.json", %{user: user, page: page}) do
|
def render("followers.json", %{user: user, page: page} = opts) do
|
||||||
|
showing = (opts[:for] && opts[:for] == user) || !user.info.hide_followers
|
||||||
|
|
||||||
query = User.get_followers_query(user)
|
query = User.get_followers_query(user)
|
||||||
query = from(user in query, select: [:ap_id])
|
query = from(user in query, select: [:ap_id])
|
||||||
followers = Repo.all(query)
|
followers = Repo.all(query)
|
||||||
|
|
||||||
total =
|
total =
|
||||||
if !user.info.hide_followers do
|
if showing do
|
||||||
length(followers)
|
length(followers)
|
||||||
else
|
else
|
||||||
0
|
0
|
||||||
end
|
end
|
||||||
|
|
||||||
collection(followers, "#{user.ap_id}/followers", page, !user.info.hide_followers, total)
|
collection(followers, "#{user.ap_id}/followers", page, showing, total)
|
||||||
|> Map.merge(Utils.make_json_ld_header())
|
|> Map.merge(Utils.make_json_ld_header())
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("followers.json", %{user: user}) do
|
def render("followers.json", %{user: user} = opts) do
|
||||||
|
showing = (opts[:for] && opts[:for] == user) || !user.info.hide_followers
|
||||||
|
|
||||||
query = User.get_followers_query(user)
|
query = User.get_followers_query(user)
|
||||||
query = from(user in query, select: [:ap_id])
|
query = from(user in query, select: [:ap_id])
|
||||||
followers = Repo.all(query)
|
followers = Repo.all(query)
|
||||||
|
|
||||||
total =
|
total =
|
||||||
if !user.info.hide_followers do
|
if showing do
|
||||||
length(followers)
|
length(followers)
|
||||||
else
|
else
|
||||||
0
|
0
|
||||||
|
@ -168,7 +179,11 @@ def render("followers.json", %{user: user}) do
|
||||||
"type" => "OrderedCollection",
|
"type" => "OrderedCollection",
|
||||||
"totalItems" => total,
|
"totalItems" => total,
|
||||||
"first" =>
|
"first" =>
|
||||||
collection(followers, "#{user.ap_id}/followers", 1, !user.info.hide_followers, total)
|
if showing do
|
||||||
|
collection(followers, "#{user.ap_id}/followers", 1, showing, total)
|
||||||
|
else
|
||||||
|
"#{user.ap_id}/followers?page=1"
|
||||||
|
end
|
||||||
}
|
}
|
||||||
|> Map.merge(Utils.make_json_ld_header())
|
|> Map.merge(Utils.make_json_ld_header())
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
# 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.Visibility do
|
defmodule Pleroma.Web.ActivityPub.Visibility do
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
|
|
|
@ -160,9 +160,7 @@ def right_add(conn, %{"permission_group" => permission_group, "nickname" => nick
|
||||||
end
|
end
|
||||||
|
|
||||||
def right_add(conn, _) do
|
def right_add(conn, _) do
|
||||||
conn
|
render_error(conn, :not_found, "No such permission_group")
|
||||||
|> put_status(404)
|
|
||||||
|> json(%{error: "No such permission_group"})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def right_get(conn, %{"nickname" => nickname}) do
|
def right_get(conn, %{"nickname" => nickname}) do
|
||||||
|
@ -184,9 +182,7 @@ def right_delete(
|
||||||
)
|
)
|
||||||
when permission_group in ["moderator", "admin"] do
|
when permission_group in ["moderator", "admin"] do
|
||||||
if admin_nickname == nickname do
|
if admin_nickname == nickname do
|
||||||
conn
|
render_error(conn, :forbidden, "You can't revoke your own admin status.")
|
||||||
|> put_status(403)
|
|
||||||
|> json(%{error: "You can't revoke your own admin status."})
|
|
||||||
else
|
else
|
||||||
user = User.get_cached_by_nickname(nickname)
|
user = User.get_cached_by_nickname(nickname)
|
||||||
|
|
||||||
|
@ -207,9 +203,7 @@ def right_delete(
|
||||||
end
|
end
|
||||||
|
|
||||||
def right_delete(conn, _) do
|
def right_delete(conn, _) do
|
||||||
conn
|
render_error(conn, :not_found, "No such permission_group")
|
||||||
|> put_status(404)
|
|
||||||
|> json(%{error: "No such permission_group"})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_activation_status(conn, %{"nickname" => nickname, "status" => status}) do
|
def set_activation_status(conn, %{"nickname" => nickname, "status" => status}) do
|
||||||
|
@ -377,13 +371,13 @@ def config_update(conn, %{"configs" => configs}) do
|
||||||
if Pleroma.Config.get([:instance, :dynamic_configuration]) do
|
if Pleroma.Config.get([:instance, :dynamic_configuration]) do
|
||||||
updated =
|
updated =
|
||||||
Enum.map(configs, fn
|
Enum.map(configs, fn
|
||||||
%{"group" => group, "key" => key, "value" => value} ->
|
|
||||||
{:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
|
|
||||||
config
|
|
||||||
|
|
||||||
%{"group" => group, "key" => key, "delete" => "true"} ->
|
%{"group" => group, "key" => key, "delete" => "true"} ->
|
||||||
{:ok, _} = Config.delete(%{group: group, key: key})
|
{:ok, _} = Config.delete(%{group: group, key: key})
|
||||||
nil
|
nil
|
||||||
|
|
||||||
|
%{"group" => group, "key" => key, "value" => value} ->
|
||||||
|
{:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
|
||||||
|
config
|
||||||
end)
|
end)
|
||||||
|> Enum.reject(&is_nil(&1))
|
|> Enum.reject(&is_nil(&1))
|
||||||
|
|
||||||
|
@ -401,26 +395,26 @@ def config_update(conn, %{"configs" => configs}) do
|
||||||
|
|
||||||
def errors(conn, {:error, :not_found}) do
|
def errors(conn, {:error, :not_found}) do
|
||||||
conn
|
conn
|
||||||
|> put_status(404)
|
|> put_status(:not_found)
|
||||||
|> json("Not found")
|
|> json(dgettext("errors", "Not found"))
|
||||||
end
|
end
|
||||||
|
|
||||||
def errors(conn, {:error, reason}) do
|
def errors(conn, {:error, reason}) do
|
||||||
conn
|
conn
|
||||||
|> put_status(400)
|
|> put_status(:bad_request)
|
||||||
|> json(reason)
|
|> json(reason)
|
||||||
end
|
end
|
||||||
|
|
||||||
def errors(conn, {:param_cast, _}) do
|
def errors(conn, {:param_cast, _}) do
|
||||||
conn
|
conn
|
||||||
|> put_status(400)
|
|> put_status(:bad_request)
|
||||||
|> json("Invalid parameters")
|
|> json(dgettext("errors", "Invalid parameters"))
|
||||||
end
|
end
|
||||||
|
|
||||||
def errors(conn, _) do
|
def errors(conn, _) do
|
||||||
conn
|
conn
|
||||||
|> put_status(500)
|
|> put_status(:internal_server_error)
|
||||||
|> json("Something went wrong")
|
|> json(dgettext("errors", "Something went wrong"))
|
||||||
end
|
end
|
||||||
|
|
||||||
defp page_params(params) do
|
defp page_params(params) do
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
defmodule Pleroma.Web.AdminAPI.Config do
|
defmodule Pleroma.Web.AdminAPI.Config do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
import Pleroma.Web.Gettext
|
||||||
alias __MODULE__
|
alias __MODULE__
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
|
|
||||||
|
@ -57,104 +58,95 @@ def delete(params) do
|
||||||
with %Config{} = config <- Config.get_by_params(params) do
|
with %Config{} = config <- Config.get_by_params(params) do
|
||||||
Repo.delete(config)
|
Repo.delete(config)
|
||||||
else
|
else
|
||||||
nil -> {:error, "Config with params #{inspect(params)} not found"}
|
nil ->
|
||||||
|
err =
|
||||||
|
dgettext("errors", "Config with params %{params} not found", params: inspect(params))
|
||||||
|
|
||||||
|
{:error, err}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec from_binary(binary()) :: term()
|
@spec from_binary(binary()) :: term()
|
||||||
def from_binary(value), do: :erlang.binary_to_term(value)
|
def from_binary(binary), do: :erlang.binary_to_term(binary)
|
||||||
|
|
||||||
@spec from_binary_to_map(binary()) :: any()
|
@spec from_binary_with_convert(binary()) :: any()
|
||||||
def from_binary_to_map(binary) do
|
def from_binary_with_convert(binary) do
|
||||||
from_binary(binary)
|
from_binary(binary)
|
||||||
|> do_convert()
|
|> do_convert()
|
||||||
end
|
end
|
||||||
|
|
||||||
defp do_convert([{k, v}] = value) when is_list(value) and length(value) == 1,
|
defp do_convert(entity) when is_list(entity) do
|
||||||
do: %{k => do_convert(v)}
|
for v <- entity, into: [], do: do_convert(v)
|
||||||
|
end
|
||||||
|
|
||||||
defp do_convert(values) when is_list(values), do: for(val <- values, do: do_convert(val))
|
defp do_convert(entity) when is_map(entity) do
|
||||||
|
for {k, v} <- entity, into: %{}, do: {do_convert(k), do_convert(v)}
|
||||||
|
end
|
||||||
|
|
||||||
defp do_convert({k, v} = value) when is_tuple(value),
|
defp do_convert({:dispatch, [entity]}), do: %{"tuple" => [":dispatch", [inspect(entity)]]}
|
||||||
do: %{k => do_convert(v)}
|
|
||||||
|
|
||||||
defp do_convert(value) when is_tuple(value), do: %{"tuple" => do_convert(Tuple.to_list(value))}
|
defp do_convert(entity) when is_tuple(entity),
|
||||||
|
do: %{"tuple" => do_convert(Tuple.to_list(entity))}
|
||||||
|
|
||||||
defp do_convert(value) when is_binary(value) or is_map(value) or is_number(value), do: value
|
defp do_convert(entity) when is_boolean(entity) or is_number(entity) or is_nil(entity),
|
||||||
|
do: entity
|
||||||
|
|
||||||
defp do_convert(value) when is_atom(value) do
|
defp do_convert(entity) when is_atom(entity) do
|
||||||
string = to_string(value)
|
string = to_string(entity)
|
||||||
|
|
||||||
if String.starts_with?(string, "Elixir."),
|
if String.starts_with?(string, "Elixir."),
|
||||||
do: String.trim_leading(string, "Elixir."),
|
do: do_convert(string),
|
||||||
else: value
|
else: ":" <> string
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp do_convert("Elixir." <> module_name), do: module_name
|
||||||
|
|
||||||
|
defp do_convert(entity) when is_binary(entity), do: entity
|
||||||
|
|
||||||
@spec transform(any()) :: binary()
|
@spec transform(any()) :: binary()
|
||||||
def transform(%{"tuple" => _} = entity), do: :erlang.term_to_binary(do_transform(entity))
|
def transform(entity) when is_binary(entity) or is_map(entity) or is_list(entity) do
|
||||||
|
:erlang.term_to_binary(do_transform(entity))
|
||||||
def transform(entity) when is_map(entity) do
|
|
||||||
tuples =
|
|
||||||
for {k, v} <- entity,
|
|
||||||
into: [],
|
|
||||||
do: {if(is_atom(k), do: k, else: String.to_atom(k)), do_transform(v)}
|
|
||||||
|
|
||||||
Enum.reject(tuples, fn {_k, v} -> is_nil(v) end)
|
|
||||||
|> Enum.sort()
|
|
||||||
|> :erlang.term_to_binary()
|
|
||||||
end
|
|
||||||
|
|
||||||
def transform(entity) when is_list(entity) do
|
|
||||||
list = Enum.map(entity, &do_transform(&1))
|
|
||||||
:erlang.term_to_binary(list)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def transform(entity), do: :erlang.term_to_binary(entity)
|
def transform(entity), do: :erlang.term_to_binary(entity)
|
||||||
|
|
||||||
defp do_transform(%Regex{} = value) when is_map(value), do: value
|
defp do_transform(%Regex{} = entity) when is_map(entity), do: entity
|
||||||
|
|
||||||
defp do_transform(%{"tuple" => [k, values] = entity}) when length(entity) == 2 do
|
defp do_transform(%{"tuple" => [":dispatch", [entity]]}) do
|
||||||
{do_transform(k), do_transform(values)}
|
cleaned_string = String.replace(entity, ~r/[^\w|^{:,[|^,|^[|^\]^}|^\/|^\.|^"]^\s/, "")
|
||||||
|
{dispatch_settings, []} = Code.eval_string(cleaned_string, [], requires: [], macros: [])
|
||||||
|
{:dispatch, [dispatch_settings]}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp do_transform(%{"tuple" => values}) do
|
defp do_transform(%{"tuple" => entity}) do
|
||||||
Enum.reduce(values, {}, fn val, acc -> Tuple.append(acc, do_transform(val)) end)
|
Enum.reduce(entity, {}, fn val, acc -> Tuple.append(acc, do_transform(val)) end)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp do_transform(value) when is_map(value) do
|
defp do_transform(entity) when is_map(entity) do
|
||||||
values = for {key, val} <- value, into: [], do: {String.to_atom(key), do_transform(val)}
|
for {k, v} <- entity, into: %{}, do: {do_transform(k), do_transform(v)}
|
||||||
|
|
||||||
Enum.sort(values)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp do_transform(value) when is_list(value) do
|
defp do_transform(entity) when is_list(entity) do
|
||||||
Enum.map(value, &do_transform(&1))
|
for v <- entity, into: [], do: do_transform(v)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp do_transform(entity) when is_list(entity) and length(entity) == 1, do: hd(entity)
|
defp do_transform(entity) when is_binary(entity) do
|
||||||
|
String.trim(entity)
|
||||||
defp do_transform(value) when is_binary(value) do
|
|
||||||
String.trim(value)
|
|
||||||
|> do_transform_string()
|
|> do_transform_string()
|
||||||
end
|
end
|
||||||
|
|
||||||
defp do_transform(value), do: value
|
defp do_transform(entity), do: entity
|
||||||
|
|
||||||
defp do_transform_string(value) when byte_size(value) == 0, do: nil
|
defp do_transform_string("~r/" <> pattern) do
|
||||||
|
pattern = String.trim_trailing(pattern, "/")
|
||||||
|
~r/#{pattern}/
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_transform_string(":" <> atom), do: String.to_atom(atom)
|
||||||
|
|
||||||
defp do_transform_string(value) do
|
defp do_transform_string(value) do
|
||||||
cond do
|
if String.starts_with?(value, "Pleroma") or String.starts_with?(value, "Phoenix"),
|
||||||
String.starts_with?(value, "Pleroma") or String.starts_with?(value, "Phoenix") ->
|
do: String.to_existing_atom("Elixir." <> value),
|
||||||
String.to_existing_atom("Elixir." <> value)
|
else: value
|
||||||
|
|
||||||
String.starts_with?(value, ":") ->
|
|
||||||
String.replace(value, ":", "") |> String.to_existing_atom()
|
|
||||||
|
|
||||||
String.starts_with?(value, "i:") ->
|
|
||||||
String.replace(value, "i:", "") |> String.to_integer()
|
|
||||||
|
|
||||||
true ->
|
|
||||||
value
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.AdminAPI.ConfigView do
|
defmodule Pleroma.Web.AdminAPI.ConfigView do
|
||||||
use Pleroma.Web, :view
|
use Pleroma.Web, :view
|
||||||
|
|
||||||
|
@ -11,7 +15,7 @@ def render("show.json", %{config: config}) do
|
||||||
%{
|
%{
|
||||||
key: config.key,
|
key: config.key,
|
||||||
group: config.group,
|
group: config.group,
|
||||||
value: Pleroma.Web.AdminAPI.Config.from_binary_to_map(config.value)
|
value: Pleroma.Web.AdminAPI.Config.from_binary_with_convert(config.value)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,6 +13,7 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.ActivityPub.Visibility
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
|
|
||||||
|
import Pleroma.Web.Gettext
|
||||||
import Pleroma.Web.CommonAPI.Utils
|
import Pleroma.Web.CommonAPI.Utils
|
||||||
|
|
||||||
def follow(follower, followed) do
|
def follow(follower, followed) do
|
||||||
|
@ -30,7 +31,8 @@ def follow(follower, followed) do
|
||||||
|
|
||||||
def unfollow(follower, unfollowed) do
|
def unfollow(follower, unfollowed) do
|
||||||
with {:ok, follower, _follow_activity} <- User.unfollow(follower, unfollowed),
|
with {:ok, follower, _follow_activity} <- User.unfollow(follower, unfollowed),
|
||||||
{:ok, _activity} <- ActivityPub.unfollow(follower, unfollowed) do
|
{:ok, _activity} <- ActivityPub.unfollow(follower, unfollowed),
|
||||||
|
{:ok, _unfollowed} <- User.unsubscribe(follower, unfollowed) do
|
||||||
{:ok, follower}
|
{:ok, follower}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -74,7 +76,7 @@ def delete(activity_id, user) do
|
||||||
{:ok, delete}
|
{:ok, delete}
|
||||||
else
|
else
|
||||||
_ ->
|
_ ->
|
||||||
{:error, "Could not delete"}
|
{:error, dgettext("errors", "Could not delete")}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -85,7 +87,7 @@ def repeat(id_or_ap_id, user) do
|
||||||
ActivityPub.announce(user, object)
|
ActivityPub.announce(user, object)
|
||||||
else
|
else
|
||||||
_ ->
|
_ ->
|
||||||
{:error, "Could not repeat"}
|
{:error, dgettext("errors", "Could not repeat")}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -95,7 +97,7 @@ def unrepeat(id_or_ap_id, user) do
|
||||||
ActivityPub.unannounce(user, object)
|
ActivityPub.unannounce(user, object)
|
||||||
else
|
else
|
||||||
_ ->
|
_ ->
|
||||||
{:error, "Could not unrepeat"}
|
{:error, dgettext("errors", "Could not unrepeat")}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -106,7 +108,7 @@ def favorite(id_or_ap_id, user) do
|
||||||
ActivityPub.like(user, object)
|
ActivityPub.like(user, object)
|
||||||
else
|
else
|
||||||
_ ->
|
_ ->
|
||||||
{:error, "Could not favorite"}
|
{:error, dgettext("errors", "Could not favorite")}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -116,7 +118,7 @@ def unfavorite(id_or_ap_id, user) do
|
||||||
ActivityPub.unlike(user, object)
|
ActivityPub.unlike(user, object)
|
||||||
else
|
else
|
||||||
_ ->
|
_ ->
|
||||||
{:error, "Could not unfavorite"}
|
{:error, dgettext("errors", "Could not unfavorite")}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -148,10 +150,10 @@ def vote(user, object, choices) do
|
||||||
object = Object.get_cached_by_ap_id(object.data["id"])
|
object = Object.get_cached_by_ap_id(object.data["id"])
|
||||||
{:ok, answer_activities, object}
|
{:ok, answer_activities, object}
|
||||||
else
|
else
|
||||||
{:author, _} -> {:error, "Poll's author can't vote"}
|
{:author, _} -> {:error, dgettext("errors", "Poll's author can't vote")}
|
||||||
{:existing_votes, _} -> {:error, "Already voted"}
|
{:existing_votes, _} -> {:error, dgettext("errors", "Already voted")}
|
||||||
{:choice_check, {_, false}} -> {:error, "Invalid indices"}
|
{:choice_check, {_, false}} -> {:error, dgettext("errors", "Invalid indices")}
|
||||||
{:count_check, false} -> {:error, "Too many choices"}
|
{:count_check, false} -> {:error, dgettext("errors", "Too many choices")}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -248,9 +250,14 @@ def post(user, %{"status" => status} = data) do
|
||||||
|
|
||||||
res
|
res
|
||||||
else
|
else
|
||||||
{:private_to_public, true} -> {:error, "The message visibility must be direct"}
|
{:private_to_public, true} ->
|
||||||
{:error, _} = e -> e
|
{:error, dgettext("errors", "The message visibility must be direct")}
|
||||||
e -> {:error, e}
|
|
||||||
|
{:error, _} = e ->
|
||||||
|
e
|
||||||
|
|
||||||
|
e ->
|
||||||
|
{:error, e}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -301,7 +308,7 @@ def pin(id_or_ap_id, %{ap_id: user_ap_id} = user) do
|
||||||
{:error, err}
|
{:error, err}
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
{:error, "Could not pin"}
|
{:error, dgettext("errors", "Could not pin")}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -318,7 +325,7 @@ def unpin(id_or_ap_id, user) do
|
||||||
{:error, err}
|
{:error, err}
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
{:error, "Could not unpin"}
|
{:error, dgettext("errors", "Could not unpin")}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -326,7 +333,7 @@ def add_mute(user, activity) do
|
||||||
with {:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]) do
|
with {:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]) do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
else
|
else
|
||||||
{:error, _} -> {:error, "conversation is already muted"}
|
{:error, _} -> {:error, dgettext("errors", "conversation is already muted")}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -371,8 +378,8 @@ def report(user, data) do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
else
|
else
|
||||||
{:error, err} -> {:error, err}
|
{:error, err} -> {:error, err}
|
||||||
{:account_id, %{}} -> {:error, "Valid `account_id` required"}
|
{:account_id, %{}} -> {:error, dgettext("errors", "Valid `account_id` required")}
|
||||||
{:account, nil} -> {:error, "Account not found"}
|
{:account, nil} -> {:error, dgettext("errors", "Account not found")}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -381,14 +388,9 @@ def update_report_state(activity_id, state) do
|
||||||
{:ok, activity} <- Utils.update_report_state(activity, state) do
|
{:ok, activity} <- Utils.update_report_state(activity, state) do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
else
|
else
|
||||||
nil ->
|
nil -> {:error, :not_found}
|
||||||
{:error, :not_found}
|
{:error, reason} -> {:error, reason}
|
||||||
|
_ -> {:error, dgettext("errors", "Could not update state")}
|
||||||
{:error, reason} ->
|
|
||||||
{:error, reason}
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
{:error, "Could not update state"}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -398,11 +400,8 @@ def update_activity_scope(activity_id, opts \\ %{}) do
|
||||||
{:ok, activity} <- set_visibility(activity, opts) do
|
{:ok, activity} <- set_visibility(activity, opts) do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
else
|
else
|
||||||
nil ->
|
nil -> {:error, :not_found}
|
||||||
{:error, :not_found}
|
{:error, reason} -> {:error, reason}
|
||||||
|
|
||||||
{:error, reason} ->
|
|
||||||
{:error, reason}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.CommonAPI.Utils do
|
defmodule Pleroma.Web.CommonAPI.Utils do
|
||||||
|
import Pleroma.Web.Gettext
|
||||||
|
|
||||||
alias Calendar.Strftime
|
alias Calendar.Strftime
|
||||||
alias Comeonin.Pbkdf2
|
alias Comeonin.Pbkdf2
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
|
@ -372,7 +374,7 @@ def confirm_current_password(user, password) do
|
||||||
true <- Pbkdf2.checkpw(password, db_user.password_hash) do
|
true <- Pbkdf2.checkpw(password, db_user.password_hash) do
|
||||||
{:ok, db_user}
|
{:ok, db_user}
|
||||||
else
|
else
|
||||||
_ -> {:error, "Invalid password."}
|
_ -> {:error, dgettext("errors", "Invalid password.")}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -455,7 +457,8 @@ def make_report_content_html(comment) do
|
||||||
if String.length(comment) <= max_size do
|
if String.length(comment) <= max_size do
|
||||||
{:ok, format_input(comment, "text/plain")}
|
{:ok, format_input(comment, "text/plain")}
|
||||||
else
|
else
|
||||||
{:error, "Comment must be up to #{max_size} characters"}
|
{:error,
|
||||||
|
dgettext("errors", "Comment must be up to %{max_size} characters", max_size: max_size)}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -490,7 +493,7 @@ def conversation_id_to_context(id) do
|
||||||
context
|
context
|
||||||
else
|
else
|
||||||
_e ->
|
_e ->
|
||||||
{:error, "No such conversation"}
|
{:error, dgettext("errors", "No such conversation")}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -512,10 +515,10 @@ def validate_character_limit(full_payload, attachments, limit) do
|
||||||
if length > 0 or Enum.count(attachments) > 0 do
|
if length > 0 or Enum.count(attachments) > 0 do
|
||||||
:ok
|
:ok
|
||||||
else
|
else
|
||||||
{:error, "Cannot post an empty status without attachments"}
|
{:error, dgettext("errors", "Cannot post an empty status without attachments")}
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
{:error, "The status is over the character limit"}
|
{:error, dgettext("errors", "The status is over the character limit")}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,13 +7,9 @@ defmodule Pleroma.Web.Endpoint do
|
||||||
|
|
||||||
socket("/socket", Pleroma.Web.UserSocket)
|
socket("/socket", Pleroma.Web.UserSocket)
|
||||||
|
|
||||||
# Serve at "/" the static files from "priv/static" directory.
|
plug(Pleroma.Plugs.SetLocalePlug)
|
||||||
#
|
|
||||||
# You should set gzip to true if you are running phoenix.digest
|
|
||||||
# when deploying your static files in production.
|
|
||||||
plug(CORSPlug)
|
plug(CORSPlug)
|
||||||
plug(Pleroma.Plugs.HTTPSecurityPlug)
|
plug(Pleroma.Plugs.HTTPSecurityPlug)
|
||||||
|
|
||||||
plug(Pleroma.Plugs.UploadedMedia)
|
plug(Pleroma.Plugs.UploadedMedia)
|
||||||
|
|
||||||
@static_cache_control "public, no-cache"
|
@static_cache_control "public, no-cache"
|
||||||
|
@ -30,6 +26,10 @@ defmodule Pleroma.Web.Endpoint do
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Serve at "/" the static files from "priv/static" directory.
|
||||||
|
#
|
||||||
|
# You should set gzip to true if you are running phoenix.digest
|
||||||
|
# when deploying your static files in production.
|
||||||
plug(
|
plug(
|
||||||
Plug.Static,
|
Plug.Static,
|
||||||
at: "/",
|
at: "/",
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
|
defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
@ -49,7 +53,7 @@ def get_notifications(user, params \\ %{}) do
|
||||||
options = cast_params(params)
|
options = cast_params(params)
|
||||||
|
|
||||||
user
|
user
|
||||||
|> Notification.for_user_query()
|
|> Notification.for_user_query(options)
|
||||||
|> restrict(:exclude_types, options)
|
|> restrict(:exclude_types, options)
|
||||||
|> Pagination.fetch_paginated(params)
|
|> Pagination.fetch_paginated(params)
|
||||||
end
|
end
|
||||||
|
@ -63,7 +67,8 @@ def get_scheduled_activities(user, params \\ %{}) do
|
||||||
defp cast_params(params) do
|
defp cast_params(params) do
|
||||||
param_types = %{
|
param_types = %{
|
||||||
exclude_types: {:array, :string},
|
exclude_types: {:array, :string},
|
||||||
reblogs: :boolean
|
reblogs: :boolean,
|
||||||
|
with_muted: :boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
changeset = cast({%{}, param_types}, params, Map.keys(param_types))
|
changeset = cast({%{}, param_types}, params, Map.keys(param_types))
|
||||||
|
|
|
@ -15,6 +15,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Pagination
|
alias Pleroma.Pagination
|
||||||
|
alias Pleroma.Plugs.RateLimiter
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.ScheduledActivity
|
alias Pleroma.ScheduledActivity
|
||||||
alias Pleroma.Stats
|
alias Pleroma.Stats
|
||||||
|
@ -46,8 +47,24 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
plug(Pleroma.Plugs.RateLimiter, :app_account_creation when action == :account_register)
|
@rate_limited_status_actions ~w(reblog_status unreblog_status fav_status unfav_status
|
||||||
plug(Pleroma.Plugs.RateLimiter, :search when action in [:search, :search2, :account_search])
|
post_status delete_status)a
|
||||||
|
|
||||||
|
plug(
|
||||||
|
RateLimiter,
|
||||||
|
{:status_id_action, bucket_name: "status_id_action:reblog_unreblog", params: ["id"]}
|
||||||
|
when action in ~w(reblog_status unreblog_status)a
|
||||||
|
)
|
||||||
|
|
||||||
|
plug(
|
||||||
|
RateLimiter,
|
||||||
|
{:status_id_action, bucket_name: "status_id_action:fav_unfav", params: ["id"]}
|
||||||
|
when action in ~w(fav_status unfav_status)a
|
||||||
|
)
|
||||||
|
|
||||||
|
plug(RateLimiter, :statuses_actions when action in @rate_limited_status_actions)
|
||||||
|
plug(RateLimiter, :app_account_creation when action == :account_register)
|
||||||
|
plug(RateLimiter, :search when action in [:search, :search2, :account_search])
|
||||||
|
|
||||||
@local_mastodon_name "Mastodon-Local"
|
@local_mastodon_name "Mastodon-Local"
|
||||||
|
|
||||||
|
@ -160,10 +177,7 @@ def update_credentials(%{assigns: %{user: user}} = conn, params) do
|
||||||
AccountView.render("account.json", %{user: user, for: user, with_pleroma_settings: true})
|
AccountView.render("account.json", %{user: user, for: user, with_pleroma_settings: true})
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
_e ->
|
_e -> render_error(conn, :forbidden, "Invalid request")
|
||||||
conn
|
|
||||||
|> put_status(403)
|
|
||||||
|> json(%{error: "Invalid request"})
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -258,10 +272,7 @@ def user(%{assigns: %{user: for_user}} = conn, %{"id" => nickname_or_id}) do
|
||||||
account = AccountView.render("account.json", %{user: user, for: for_user})
|
account = AccountView.render("account.json", %{user: user, for: for_user})
|
||||||
json(conn, account)
|
json(conn, account)
|
||||||
else
|
else
|
||||||
_e ->
|
_e -> render_error(conn, :not_found, "Can't find user")
|
||||||
conn
|
|
||||||
|> put_status(404)
|
|
||||||
|> json(%{error: "Can't find user"})
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -305,7 +316,9 @@ defp mastodonized_emoji do
|
||||||
"static_url" => url,
|
"static_url" => url,
|
||||||
"visible_in_picker" => true,
|
"visible_in_picker" => true,
|
||||||
"url" => url,
|
"url" => url,
|
||||||
"tags" => tags
|
"tags" => tags,
|
||||||
|
# Assuming that a comma is authorized in the category name
|
||||||
|
"category" => (tags -- ["Custom"]) |> Enum.join(",")
|
||||||
}
|
}
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
@ -509,15 +522,8 @@ def get_poll(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
|> put_view(StatusView)
|
|> put_view(StatusView)
|
||||||
|> try_render("poll.json", %{object: object, for: user})
|
|> try_render("poll.json", %{object: object, for: user})
|
||||||
else
|
else
|
||||||
nil ->
|
nil -> render_error(conn, :not_found, "Record not found")
|
||||||
conn
|
false -> render_error(conn, :not_found, "Record not found")
|
||||||
|> put_status(404)
|
|
||||||
|> json(%{error: "Record not found"})
|
|
||||||
|
|
||||||
false ->
|
|
||||||
conn
|
|
||||||
|> put_status(404)
|
|
||||||
|> json(%{error: "Record not found"})
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -546,18 +552,14 @@ def poll_vote(%{assigns: %{user: user}} = conn, %{"id" => id, "choices" => choic
|
||||||
|> try_render("poll.json", %{object: object, for: user})
|
|> try_render("poll.json", %{object: object, for: user})
|
||||||
else
|
else
|
||||||
nil ->
|
nil ->
|
||||||
conn
|
render_error(conn, :not_found, "Record not found")
|
||||||
|> put_status(404)
|
|
||||||
|> json(%{error: "Record not found"})
|
|
||||||
|
|
||||||
false ->
|
false ->
|
||||||
conn
|
render_error(conn, :not_found, "Record not found")
|
||||||
|> put_status(404)
|
|
||||||
|> json(%{error: "Record not found"})
|
|
||||||
|
|
||||||
{:error, message} ->
|
{:error, message} ->
|
||||||
conn
|
conn
|
||||||
|> put_status(422)
|
|> put_status(:unprocessable_entity)
|
||||||
|> json(%{error: message})
|
|> json(%{error: message})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -646,10 +648,7 @@ def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
|
with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
|
||||||
json(conn, %{})
|
json(conn, %{})
|
||||||
else
|
else
|
||||||
_e ->
|
_e -> render_error(conn, :forbidden, "Can't delete this post")
|
||||||
conn
|
|
||||||
|> put_status(403)
|
|
||||||
|> json(%{error: "Can't delete this post"})
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -697,8 +696,8 @@ def pin_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||||
else
|
else
|
||||||
{:error, reason} ->
|
{:error, reason} ->
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("application/json")
|
|> put_status(:bad_request)
|
||||||
|> send_resp(:bad_request, Jason.encode!(%{"error" => reason}))
|
|> json(%{"error" => reason})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -774,8 +773,8 @@ def get_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params)
|
||||||
else
|
else
|
||||||
{:error, reason} ->
|
{:error, reason} ->
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("application/json")
|
|> put_status(:forbidden)
|
||||||
|> send_resp(403, Jason.encode!(%{"error" => reason}))
|
|> json(%{"error" => reason})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -790,8 +789,8 @@ def dismiss_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _para
|
||||||
else
|
else
|
||||||
{:error, reason} ->
|
{:error, reason} ->
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("application/json")
|
|> put_status(:forbidden)
|
||||||
|> send_resp(403, Jason.encode!(%{"error" => reason}))
|
|> json(%{"error" => reason})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -869,9 +868,7 @@ def set_mascot(%{assigns: %{user: user}} = conn, %{"file" => file}) do
|
||||||
conn
|
conn
|
||||||
|> json(rendered)
|
|> json(rendered)
|
||||||
else
|
else
|
||||||
conn
|
render_error(conn, :unsupported_media_type, "mascots can only be images")
|
||||||
|> put_resp_content_type("application/json")
|
|
||||||
|> send_resp(415, Jason.encode!(%{"error" => "mascots can only be images"}))
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1000,8 +997,8 @@ def authorize_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}
|
||||||
else
|
else
|
||||||
{:error, message} ->
|
{:error, message} ->
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("application/json")
|
|> put_status(:forbidden)
|
||||||
|> send_resp(403, Jason.encode!(%{"error" => message}))
|
|> json(%{error: message})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1014,8 +1011,8 @@ def reject_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) d
|
||||||
else
|
else
|
||||||
{:error, message} ->
|
{:error, message} ->
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("application/json")
|
|> put_status(:forbidden)
|
||||||
|> send_resp(403, Jason.encode!(%{"error" => message}))
|
|> json(%{error: message})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1032,8 +1029,8 @@ def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
|
||||||
|
|
||||||
{:error, message} ->
|
{:error, message} ->
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("application/json")
|
|> put_status(:forbidden)
|
||||||
|> send_resp(403, Jason.encode!(%{"error" => message}))
|
|> json(%{error: message})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1050,8 +1047,8 @@ def follow(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do
|
||||||
|
|
||||||
{:error, message} ->
|
{:error, message} ->
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("application/json")
|
|> put_status(:forbidden)
|
||||||
|> send_resp(403, Jason.encode!(%{"error" => message}))
|
|> json(%{error: message})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1071,17 +1068,22 @@ def unfollow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def mute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
|
def mute(%{assigns: %{user: muter}} = conn, %{"id" => id} = params) do
|
||||||
|
notifications =
|
||||||
|
if Map.has_key?(params, "notifications"),
|
||||||
|
do: params["notifications"] in [true, "True", "true", "1"],
|
||||||
|
else: true
|
||||||
|
|
||||||
with %User{} = muted <- User.get_cached_by_id(id),
|
with %User{} = muted <- User.get_cached_by_id(id),
|
||||||
{:ok, muter} <- User.mute(muter, muted) do
|
{:ok, muter} <- User.mute(muter, muted, notifications) do
|
||||||
conn
|
conn
|
||||||
|> put_view(AccountView)
|
|> put_view(AccountView)
|
||||||
|> render("relationship.json", %{user: muter, target: muted})
|
|> render("relationship.json", %{user: muter, target: muted})
|
||||||
else
|
else
|
||||||
{:error, message} ->
|
{:error, message} ->
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("application/json")
|
|> put_status(:forbidden)
|
||||||
|> send_resp(403, Jason.encode!(%{"error" => message}))
|
|> json(%{error: message})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1094,8 +1096,8 @@ def unmute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
|
||||||
else
|
else
|
||||||
{:error, message} ->
|
{:error, message} ->
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("application/json")
|
|> put_status(:forbidden)
|
||||||
|> send_resp(403, Jason.encode!(%{"error" => message}))
|
|> json(%{error: message})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1116,8 +1118,8 @@ def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
|
||||||
else
|
else
|
||||||
{:error, message} ->
|
{:error, message} ->
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("application/json")
|
|> put_status(:forbidden)
|
||||||
|> send_resp(403, Jason.encode!(%{"error" => message}))
|
|> json(%{error: message})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1131,8 +1133,8 @@ def unblock(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
|
||||||
else
|
else
|
||||||
{:error, message} ->
|
{:error, message} ->
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("application/json")
|
|> put_status(:forbidden)
|
||||||
|> send_resp(403, Jason.encode!(%{"error" => message}))
|
|> json(%{error: message})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1166,8 +1168,8 @@ def subscribe(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
else
|
else
|
||||||
{:error, message} ->
|
{:error, message} ->
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("application/json")
|
|> put_status(:forbidden)
|
||||||
|> send_resp(403, Jason.encode!(%{"error" => message}))
|
|> json(%{error: message})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1180,8 +1182,8 @@ def unsubscribe(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
else
|
else
|
||||||
{:error, message} ->
|
{:error, message} ->
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("application/json")
|
|> put_status(:forbidden)
|
||||||
|> send_resp(403, Jason.encode!(%{"error" => message}))
|
|> json(%{error: message})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1229,13 +1231,8 @@ def user_favourites(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params
|
||||||
|> put_view(StatusView)
|
|> put_view(StatusView)
|
||||||
|> render("index.json", %{activities: activities, for: for_user, as: :activity})
|
|> render("index.json", %{activities: activities, for: for_user, as: :activity})
|
||||||
else
|
else
|
||||||
nil ->
|
nil -> {:error, :not_found}
|
||||||
{:error, :not_found}
|
true -> render_error(conn, :forbidden, "Can't get favorites")
|
||||||
|
|
||||||
true ->
|
|
||||||
conn
|
|
||||||
|> put_status(403)
|
|
||||||
|> json(%{error: "Can't get favorites"})
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1267,10 +1264,7 @@ def get_list(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
res = ListView.render("list.json", list: list)
|
res = ListView.render("list.json", list: list)
|
||||||
json(conn, res)
|
json(conn, res)
|
||||||
else
|
else
|
||||||
_e ->
|
_e -> render_error(conn, :not_found, "Record not found")
|
||||||
conn
|
|
||||||
|> put_status(404)
|
|
||||||
|> json(%{error: "Record not found"})
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1286,7 +1280,7 @@ def delete_list(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
json(conn, %{})
|
json(conn, %{})
|
||||||
else
|
else
|
||||||
_e ->
|
_e ->
|
||||||
json(conn, "error")
|
json(conn, dgettext("errors", "error"))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1337,7 +1331,7 @@ def rename_list(%{assigns: %{user: user}} = conn, %{"id" => id, "title" => title
|
||||||
json(conn, res)
|
json(conn, res)
|
||||||
else
|
else
|
||||||
_e ->
|
_e ->
|
||||||
json(conn, "error")
|
json(conn, dgettext("errors", "error"))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1361,10 +1355,7 @@ def list_timeline(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params)
|
||||||
|> put_view(StatusView)
|
|> put_view(StatusView)
|
||||||
|> render("index.json", %{activities: activities, for: user, as: :activity})
|
|> render("index.json", %{activities: activities, for: user, as: :activity})
|
||||||
else
|
else
|
||||||
_e ->
|
_e -> render_error(conn, :forbidden, "Error.")
|
||||||
conn
|
|
||||||
|> put_status(403)
|
|
||||||
|> json(%{error: "Error."})
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1483,8 +1474,8 @@ def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _para
|
||||||
else
|
else
|
||||||
e ->
|
e ->
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("application/json")
|
|> put_status(:internal_server_error)
|
||||||
|> send_resp(500, Jason.encode!(%{"error" => inspect(e)}))
|
|> json(%{error: inspect(e)})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1652,20 +1643,18 @@ def errors(conn, {:error, %Changeset{} = changeset}) do
|
||||||
|> Enum.map_join(", ", fn {_k, v} -> v end)
|
|> Enum.map_join(", ", fn {_k, v} -> v end)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_status(422)
|
|> put_status(:unprocessable_entity)
|
||||||
|> json(%{error: error_message})
|
|> json(%{error: error_message})
|
||||||
end
|
end
|
||||||
|
|
||||||
def errors(conn, {:error, :not_found}) do
|
def errors(conn, {:error, :not_found}) do
|
||||||
conn
|
render_error(conn, :not_found, "Record not found")
|
||||||
|> put_status(404)
|
|
||||||
|> json(%{error: "Record not found"})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def errors(conn, _) do
|
def errors(conn, _) do
|
||||||
conn
|
conn
|
||||||
|> put_status(500)
|
|> put_status(:internal_server_error)
|
||||||
|> json("Something went wrong")
|
|> json(dgettext("errors", "Something went wrong"))
|
||||||
end
|
end
|
||||||
|
|
||||||
def suggestions(%{assigns: %{user: user}} = conn, _) do
|
def suggestions(%{assigns: %{user: user}} = conn, _) do
|
||||||
|
@ -1785,21 +1774,17 @@ def account_register(
|
||||||
else
|
else
|
||||||
{:error, errors} ->
|
{:error, errors} ->
|
||||||
conn
|
conn
|
||||||
|> put_status(400)
|
|> put_status(:bad_request)
|
||||||
|> json(Jason.encode!(errors))
|
|> json(errors)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def account_register(%{assigns: %{app: _app}} = conn, _params) do
|
def account_register(%{assigns: %{app: _app}} = conn, _params) do
|
||||||
conn
|
render_error(conn, :bad_request, "Missing parameters")
|
||||||
|> put_status(400)
|
|
||||||
|> json(%{error: "Missing parameters"})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def account_register(conn, _) do
|
def account_register(conn, _) do
|
||||||
conn
|
render_error(conn, :forbidden, "Invalid credentials")
|
||||||
|> put_status(403)
|
|
||||||
|> json(%{error: "Invalid credentials"})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def conversations(%{assigns: %{user: user}} = conn, params) do
|
def conversations(%{assigns: %{user: user}} = conn, params) do
|
||||||
|
@ -1829,21 +1814,14 @@ def conversation_read(%{assigns: %{user: user}} = conn, %{"id" => participation_
|
||||||
|
|
||||||
def try_render(conn, target, params)
|
def try_render(conn, target, params)
|
||||||
when is_binary(target) do
|
when is_binary(target) do
|
||||||
res = render(conn, target, params)
|
case render(conn, target, params) do
|
||||||
|
nil -> render_error(conn, :not_implemented, "Can't display this activity")
|
||||||
if res == nil do
|
res -> res
|
||||||
conn
|
|
||||||
|> put_status(501)
|
|
||||||
|> json(%{error: "Can't display this activity"})
|
|
||||||
else
|
|
||||||
res
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def try_render(conn, _, _) do
|
def try_render(conn, _, _) do
|
||||||
conn
|
render_error(conn, :not_implemented, "Can't display this activity")
|
||||||
|> put_status(501)
|
|
||||||
|> json(%{error: "Can't display this activity"})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp present?(nil), do: false
|
defp present?(nil), do: false
|
||||||
|
|
|
@ -4,61 +4,18 @@
|
||||||
|
|
||||||
defmodule Pleroma.Web.MastodonAPI.SearchController do
|
defmodule Pleroma.Web.MastodonAPI.SearchController do
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Plugs.RateLimiter
|
||||||
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web
|
alias Pleroma.Web
|
||||||
|
alias Pleroma.Web.ControllerHelper
|
||||||
alias Pleroma.Web.MastodonAPI.AccountView
|
alias Pleroma.Web.MastodonAPI.AccountView
|
||||||
alias Pleroma.Web.MastodonAPI.StatusView
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
|
|
||||||
alias Pleroma.Web.ControllerHelper
|
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
plug(RateLimiter, :search when action in [:search, :search2, :account_search])
|
||||||
plug(Pleroma.Plugs.RateLimiter, :search when action in [:search, :search2, :account_search])
|
|
||||||
|
|
||||||
def search2(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
|
|
||||||
accounts = with_fallback(fn -> User.search(query, search_options(params, user)) end, [])
|
|
||||||
statuses = with_fallback(fn -> Activity.search(user, query) end, [])
|
|
||||||
tags_path = Web.base_url() <> "/tag/"
|
|
||||||
|
|
||||||
tags =
|
|
||||||
query
|
|
||||||
|> String.split()
|
|
||||||
|> Enum.uniq()
|
|
||||||
|> Enum.filter(fn tag -> String.starts_with?(tag, "#") end)
|
|
||||||
|> Enum.map(fn tag -> String.slice(tag, 1..-1) end)
|
|
||||||
|> Enum.map(fn tag -> %{name: tag, url: tags_path <> tag} end)
|
|
||||||
|
|
||||||
res = %{
|
|
||||||
"accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user),
|
|
||||||
"statuses" =>
|
|
||||||
StatusView.render("index.json", activities: statuses, for: user, as: :activity),
|
|
||||||
"hashtags" => tags
|
|
||||||
}
|
|
||||||
|
|
||||||
json(conn, res)
|
|
||||||
end
|
|
||||||
|
|
||||||
def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
|
|
||||||
accounts = with_fallback(fn -> User.search(query, search_options(params, user)) end, [])
|
|
||||||
statuses = with_fallback(fn -> Activity.search(user, query) end, [])
|
|
||||||
|
|
||||||
tags =
|
|
||||||
query
|
|
||||||
|> String.split()
|
|
||||||
|> Enum.uniq()
|
|
||||||
|> Enum.filter(fn tag -> String.starts_with?(tag, "#") end)
|
|
||||||
|> Enum.map(fn tag -> String.slice(tag, 1..-1) end)
|
|
||||||
|
|
||||||
res = %{
|
|
||||||
"accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user),
|
|
||||||
"statuses" =>
|
|
||||||
StatusView.render("index.json", activities: statuses, for: user, as: :activity),
|
|
||||||
"hashtags" => tags
|
|
||||||
}
|
|
||||||
|
|
||||||
json(conn, res)
|
|
||||||
end
|
|
||||||
|
|
||||||
def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
|
def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
|
||||||
accounts = User.search(query, search_options(params, user))
|
accounts = User.search(query, search_options(params, user))
|
||||||
|
@ -67,17 +24,86 @@ def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) d
|
||||||
json(conn, res)
|
json(conn, res)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def search2(conn, params), do: do_search(:v2, conn, params)
|
||||||
|
def search(conn, params), do: do_search(:v1, conn, params)
|
||||||
|
|
||||||
|
defp do_search(version, %{assigns: %{user: user}} = conn, %{"q" => query} = params) do
|
||||||
|
options = search_options(params, user)
|
||||||
|
timeout = Keyword.get(Repo.config(), :timeout, 15_000)
|
||||||
|
default_values = %{"statuses" => [], "accounts" => [], "hashtags" => []}
|
||||||
|
|
||||||
|
result =
|
||||||
|
default_values
|
||||||
|
|> Enum.map(fn {resource, default_value} ->
|
||||||
|
if params["type"] == nil or params["type"] == resource do
|
||||||
|
{resource, fn -> resource_search(version, resource, query, options) end}
|
||||||
|
else
|
||||||
|
{resource, fn -> default_value end}
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|> Task.async_stream(fn {resource, f} -> {resource, with_fallback(f)} end,
|
||||||
|
timeout: timeout,
|
||||||
|
on_timeout: :kill_task
|
||||||
|
)
|
||||||
|
|> Enum.reduce(default_values, fn
|
||||||
|
{:ok, {resource, result}}, acc ->
|
||||||
|
Map.put(acc, resource, result)
|
||||||
|
|
||||||
|
_error, acc ->
|
||||||
|
acc
|
||||||
|
end)
|
||||||
|
|
||||||
|
json(conn, result)
|
||||||
|
end
|
||||||
|
|
||||||
defp search_options(params, user) do
|
defp search_options(params, user) do
|
||||||
[
|
[
|
||||||
resolve: params["resolve"] == "true",
|
resolve: params["resolve"] == "true",
|
||||||
following: params["following"] == "true",
|
following: params["following"] == "true",
|
||||||
limit: ControllerHelper.fetch_integer_param(params, "limit"),
|
limit: ControllerHelper.fetch_integer_param(params, "limit"),
|
||||||
offset: ControllerHelper.fetch_integer_param(params, "offset"),
|
offset: ControllerHelper.fetch_integer_param(params, "offset"),
|
||||||
|
type: params["type"],
|
||||||
|
author: get_author(params),
|
||||||
for_user: user
|
for_user: user
|
||||||
]
|
]
|
||||||
|
|> Enum.filter(&elem(&1, 1))
|
||||||
end
|
end
|
||||||
|
|
||||||
defp with_fallback(f, fallback) do
|
defp resource_search(_, "accounts", query, options) do
|
||||||
|
accounts = with_fallback(fn -> User.search(query, options) end)
|
||||||
|
AccountView.render("accounts.json", users: accounts, for: options[:for_user], as: :user)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp resource_search(_, "statuses", query, options) do
|
||||||
|
statuses = with_fallback(fn -> Activity.search(options[:for_user], query, options) end)
|
||||||
|
StatusView.render("index.json", activities: statuses, for: options[:for_user], as: :activity)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp resource_search(:v2, "hashtags", query, _options) do
|
||||||
|
tags_path = Web.base_url() <> "/tag/"
|
||||||
|
|
||||||
|
query
|
||||||
|
|> prepare_tags()
|
||||||
|
|> Enum.map(fn tag ->
|
||||||
|
tag = String.trim_leading(tag, "#")
|
||||||
|
%{name: tag, url: tags_path <> tag}
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp resource_search(:v1, "hashtags", query, _options) do
|
||||||
|
query
|
||||||
|
|> prepare_tags()
|
||||||
|
|> Enum.map(fn tag -> String.trim_leading(tag, "#") end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp prepare_tags(query) do
|
||||||
|
query
|
||||||
|
|> String.split()
|
||||||
|
|> Enum.uniq()
|
||||||
|
|> Enum.filter(fn tag -> String.starts_with?(tag, "#") end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp with_fallback(f, fallback \\ []) do
|
||||||
try do
|
try do
|
||||||
f.()
|
f.()
|
||||||
rescue
|
rescue
|
||||||
|
@ -86,4 +112,9 @@ defp with_fallback(f, fallback) do
|
||||||
fallback
|
fallback
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp get_author(%{"account_id" => account_id}) when is_binary(account_id),
|
||||||
|
do: User.get_cached_by_id(account_id)
|
||||||
|
|
||||||
|
defp get_author(_params), do: nil
|
||||||
end
|
end
|
||||||
|
|
|
@ -59,13 +59,13 @@ def delete(%{assigns: %{user: user, token: token}} = conn, _params) do
|
||||||
#
|
#
|
||||||
def errors(conn, {:error, :not_found}) do
|
def errors(conn, {:error, :not_found}) do
|
||||||
conn
|
conn
|
||||||
|> put_status(404)
|
|> put_status(:not_found)
|
||||||
|> json("Not found")
|
|> json(dgettext("errors", "Not found"))
|
||||||
end
|
end
|
||||||
|
|
||||||
def errors(conn, _) do
|
def errors(conn, _) do
|
||||||
conn
|
conn
|
||||||
|> put_status(500)
|
|> put_status(:internal_server_error)
|
||||||
|> json("Something went wrong")
|
|> json(dgettext("errors", "Something went wrong"))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -52,7 +52,7 @@ def render("relationship.json", %{user: %User{} = user, target: %User{} = target
|
||||||
followed_by: User.following?(target, user),
|
followed_by: User.following?(target, user),
|
||||||
blocking: User.blocks?(user, target),
|
blocking: User.blocks?(user, target),
|
||||||
muting: User.mutes?(user, target),
|
muting: User.mutes?(user, target),
|
||||||
muting_notifications: false,
|
muting_notifications: User.muted_notifications?(user, target),
|
||||||
subscribing: User.subscribed_to?(user, target),
|
subscribing: User.subscribed_to?(user, target),
|
||||||
requested: requested,
|
requested: requested,
|
||||||
domain_blocking: false,
|
domain_blocking: false,
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.MastodonAPI.ConversationView do
|
defmodule Pleroma.Web.MastodonAPI.ConversationView do
|
||||||
use Pleroma.Web, :view
|
use Pleroma.Web, :view
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
||||||
import Pleroma.Web.ActivityPub.Visibility, only: [get_visibility: 1]
|
import Pleroma.Web.ActivityPub.Visibility, only: [get_visibility: 1]
|
||||||
|
|
||||||
# TODO: Add cached version.
|
# TODO: Add cached version.
|
||||||
|
defp get_replied_to_activities([]), do: %{}
|
||||||
|
|
||||||
defp get_replied_to_activities(activities) do
|
defp get_replied_to_activities(activities) do
|
||||||
activities
|
activities
|
||||||
|> Enum.map(fn
|
|> Enum.map(fn
|
||||||
|
@ -147,8 +149,14 @@ def render("status.json", %{activity: %{data: %{"object" => _object}} = activity
|
||||||
tags = object.data["tag"] || []
|
tags = object.data["tag"] || []
|
||||||
sensitive = object.data["sensitive"] || Enum.member?(tags, "nsfw")
|
sensitive = object.data["sensitive"] || Enum.member?(tags, "nsfw")
|
||||||
|
|
||||||
|
tag_mentions =
|
||||||
|
tags
|
||||||
|
|> Enum.filter(fn tag -> is_map(tag) and tag["type"] == "Mention" end)
|
||||||
|
|> Enum.map(fn tag -> tag["href"] end)
|
||||||
|
|
||||||
mentions =
|
mentions =
|
||||||
activity.recipients
|
(object.data["to"] ++ tag_mentions)
|
||||||
|
|> Enum.uniq()
|
||||||
|> Enum.map(fn ap_id -> User.get_cached_by_ap_id(ap_id) end)
|
|> Enum.map(fn ap_id -> User.get_cached_by_ap_id(ap_id) end)
|
||||||
|> Enum.filter(& &1)
|
|> Enum.filter(& &1)
|
||||||
|> Enum.map(fn user -> AccountView.render("mention.json", %{user: user}) end)
|
|> Enum.map(fn user -> AccountView.render("mention.json", %{user: user}) end)
|
||||||
|
|
|
@ -3,68 +3,71 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.MediaProxy do
|
defmodule Pleroma.Web.MediaProxy do
|
||||||
|
alias Pleroma.Config
|
||||||
|
alias Pleroma.Web
|
||||||
|
|
||||||
@base64_opts [padding: false]
|
@base64_opts [padding: false]
|
||||||
|
|
||||||
def url(nil), do: nil
|
def url(url) when is_nil(url) or url == "", do: nil
|
||||||
|
|
||||||
def url(""), do: nil
|
|
||||||
|
|
||||||
def url("/" <> _ = url), do: url
|
def url("/" <> _ = url), do: url
|
||||||
|
|
||||||
def url(url) do
|
def url(url) do
|
||||||
if !enabled?() or local?(url) or whitelisted?(url) do
|
if disabled?() or local?(url) or whitelisted?(url) do
|
||||||
url
|
url
|
||||||
else
|
else
|
||||||
encode_url(url)
|
encode_url(url)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp enabled?, do: Pleroma.Config.get([:media_proxy, :enabled], false)
|
defp disabled?, do: !Config.get([:media_proxy, :enabled], false)
|
||||||
|
|
||||||
defp local?(url), do: String.starts_with?(url, Pleroma.Web.base_url())
|
defp local?(url), do: String.starts_with?(url, Pleroma.Web.base_url())
|
||||||
|
|
||||||
defp whitelisted?(url) do
|
defp whitelisted?(url) do
|
||||||
%{host: domain} = URI.parse(url)
|
%{host: domain} = URI.parse(url)
|
||||||
|
|
||||||
Enum.any?(Pleroma.Config.get([:media_proxy, :whitelist]), fn pattern ->
|
Enum.any?(Config.get([:media_proxy, :whitelist]), fn pattern ->
|
||||||
String.equivalent?(domain, pattern)
|
String.equivalent?(domain, pattern)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def encode_url(url) do
|
def encode_url(url) do
|
||||||
secret = Pleroma.Config.get([Pleroma.Web.Endpoint, :secret_key_base])
|
|
||||||
base64 = Base.url_encode64(url, @base64_opts)
|
base64 = Base.url_encode64(url, @base64_opts)
|
||||||
sig = :crypto.hmac(:sha, secret, base64)
|
|
||||||
sig64 = sig |> Base.url_encode64(@base64_opts)
|
sig64 =
|
||||||
|
base64
|
||||||
|
|> signed_url
|
||||||
|
|> Base.url_encode64(@base64_opts)
|
||||||
|
|
||||||
build_url(sig64, base64, filename(url))
|
build_url(sig64, base64, filename(url))
|
||||||
end
|
end
|
||||||
|
|
||||||
def decode_url(sig, url) do
|
def decode_url(sig, url) do
|
||||||
secret = Pleroma.Config.get([Pleroma.Web.Endpoint, :secret_key_base])
|
with {:ok, sig} <- Base.url_decode64(sig, @base64_opts),
|
||||||
sig = Base.url_decode64!(sig, @base64_opts)
|
signature when signature == sig <- signed_url(url) do
|
||||||
local_sig = :crypto.hmac(:sha, secret, url)
|
|
||||||
|
|
||||||
if local_sig == sig do
|
|
||||||
{:ok, Base.url_decode64!(url, @base64_opts)}
|
{:ok, Base.url_decode64!(url, @base64_opts)}
|
||||||
else
|
else
|
||||||
{:error, :invalid_signature}
|
_ -> {:error, :invalid_signature}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp signed_url(url) do
|
||||||
|
:crypto.hmac(:sha, Config.get([Web.Endpoint, :secret_key_base]), url)
|
||||||
|
end
|
||||||
|
|
||||||
def filename(url_or_path) do
|
def filename(url_or_path) do
|
||||||
if path = URI.parse(url_or_path).path, do: Path.basename(path)
|
if path = URI.parse(url_or_path).path, do: Path.basename(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_url(sig_base64, url_base64, filename \\ nil) do
|
def build_url(sig_base64, url_base64, filename \\ nil) do
|
||||||
[
|
[
|
||||||
Pleroma.Config.get([:media_proxy, :base_url], Pleroma.Web.base_url()),
|
Pleroma.Config.get([:media_proxy, :base_url], Web.base_url()),
|
||||||
"proxy",
|
"proxy",
|
||||||
sig_base64,
|
sig_base64,
|
||||||
url_base64,
|
url_base64,
|
||||||
filename
|
filename
|
||||||
]
|
]
|
||||||
|> Enum.filter(fn value -> value end)
|
|> Enum.filter(& &1)
|
||||||
|> Path.join()
|
|> Path.join()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,7 +13,7 @@ def remote(conn, %{"sig" => sig64, "url" => url64} = params) do
|
||||||
with config <- Pleroma.Config.get([:media_proxy], []),
|
with config <- Pleroma.Config.get([:media_proxy], []),
|
||||||
true <- Keyword.get(config, :enabled, false),
|
true <- Keyword.get(config, :enabled, false),
|
||||||
{:ok, url} <- MediaProxy.decode_url(sig64, url64),
|
{:ok, url} <- MediaProxy.decode_url(sig64, url64),
|
||||||
:ok <- filename_matches(Map.has_key?(params, "filename"), conn.request_path, url) do
|
:ok <- filename_matches(params, conn.request_path, url) do
|
||||||
ReverseProxy.call(conn, url, Keyword.get(config, :proxy_opts, @default_proxy_opts))
|
ReverseProxy.call(conn, url, Keyword.get(config, :proxy_opts, @default_proxy_opts))
|
||||||
else
|
else
|
||||||
false ->
|
false ->
|
||||||
|
@ -27,18 +27,15 @@ def remote(conn, %{"sig" => sig64, "url" => url64} = params) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def filename_matches(has_filename, path, url) do
|
def filename_matches(%{"filename" => _} = _, path, url) do
|
||||||
filename =
|
filename = MediaProxy.filename(url)
|
||||||
url
|
|
||||||
|> MediaProxy.filename()
|
|
||||||
|> URI.decode()
|
|
||||||
|
|
||||||
path = URI.decode(path)
|
if filename && Path.basename(path) != filename do
|
||||||
|
|
||||||
if has_filename && filename && Path.basename(path) != filename do
|
|
||||||
{:wrong_filename, filename}
|
{:wrong_filename, filename}
|
||||||
else
|
else
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def filename_matches(_, _, _), do: :ok
|
||||||
end
|
end
|
|
@ -9,6 +9,7 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
|
||||||
alias Pleroma.Web.Metadata.Utils
|
alias Pleroma.Web.Metadata.Utils
|
||||||
|
|
||||||
@behaviour Provider
|
@behaviour Provider
|
||||||
|
@media_types ["image", "audio", "video"]
|
||||||
|
|
||||||
@impl Provider
|
@impl Provider
|
||||||
def build_tags(%{
|
def build_tags(%{
|
||||||
|
@ -81,26 +82,19 @@ defp build_attachments(%{data: %{"attachment" => attachments}}) do
|
||||||
Enum.reduce(attachments, [], fn attachment, acc ->
|
Enum.reduce(attachments, [], fn attachment, acc ->
|
||||||
rendered_tags =
|
rendered_tags =
|
||||||
Enum.reduce(attachment["url"], [], fn url, acc ->
|
Enum.reduce(attachment["url"], [], fn url, acc ->
|
||||||
media_type =
|
|
||||||
Enum.find(["image", "audio", "video"], fn media_type ->
|
|
||||||
String.starts_with?(url["mediaType"], media_type)
|
|
||||||
end)
|
|
||||||
|
|
||||||
# TODO: Add additional properties to objects when we have the data available.
|
# TODO: Add additional properties to objects when we have the data available.
|
||||||
# Also, Whatsapp only wants JPEG or PNGs. It seems that if we add a second og:image
|
# Also, Whatsapp only wants JPEG or PNGs. It seems that if we add a second og:image
|
||||||
# object when a Video or GIF is attached it will display that in Whatsapp Rich Preview.
|
# object when a Video or GIF is attached it will display that in Whatsapp Rich Preview.
|
||||||
case media_type do
|
case Utils.fetch_media_type(@media_types, url["mediaType"]) do
|
||||||
"audio" ->
|
"audio" ->
|
||||||
[
|
[
|
||||||
{:meta,
|
{:meta, [property: "og:audio", content: Utils.attachment_url(url["href"])], []}
|
||||||
[property: "og:" <> media_type, content: Utils.attachment_url(url["href"])], []}
|
|
||||||
| acc
|
| acc
|
||||||
]
|
]
|
||||||
|
|
||||||
"image" ->
|
"image" ->
|
||||||
[
|
[
|
||||||
{:meta,
|
{:meta, [property: "og:image", content: Utils.attachment_url(url["href"])], []},
|
||||||
[property: "og:" <> media_type, content: Utils.attachment_url(url["href"])], []},
|
|
||||||
{:meta, [property: "og:image:width", content: 150], []},
|
{:meta, [property: "og:image:width", content: 150], []},
|
||||||
{:meta, [property: "og:image:height", content: 150], []}
|
{:meta, [property: "og:image:height", content: 150], []}
|
||||||
| acc
|
| acc
|
||||||
|
@ -108,8 +102,7 @@ defp build_attachments(%{data: %{"attachment" => attachments}}) do
|
||||||
|
|
||||||
"video" ->
|
"video" ->
|
||||||
[
|
[
|
||||||
{:meta,
|
{:meta, [property: "og:video", content: Utils.attachment_url(url["href"])], []}
|
||||||
[property: "og:" <> media_type, content: Utils.attachment_url(url["href"])], []}
|
|
||||||
| acc
|
| acc
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.Metadata.PlayerView do
|
defmodule Pleroma.Web.Metadata.PlayerView do
|
||||||
use Pleroma.Web, :view
|
use Pleroma.Web, :view
|
||||||
import Phoenix.HTML.Tag, only: [content_tag: 3, tag: 2]
|
import Phoenix.HTML.Tag, only: [content_tag: 3, tag: 2]
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.Metadata.Providers.RelMe do
|
defmodule Pleroma.Web.Metadata.Providers.RelMe do
|
||||||
alias Pleroma.Web.Metadata.Providers.Provider
|
alias Pleroma.Web.Metadata.Providers.Provider
|
||||||
@behaviour Provider
|
@behaviour Provider
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
# Pleroma: A lightweight social networking server
|
# Pleroma: A lightweight social networking server
|
||||||
|
|
||||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
@ -9,13 +10,10 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do
|
||||||
alias Pleroma.Web.Metadata.Utils
|
alias Pleroma.Web.Metadata.Utils
|
||||||
|
|
||||||
@behaviour Provider
|
@behaviour Provider
|
||||||
|
@media_types ["image", "audio", "video"]
|
||||||
|
|
||||||
@impl Provider
|
@impl Provider
|
||||||
def build_tags(%{
|
def build_tags(%{activity_id: id, object: object, user: user}) do
|
||||||
activity_id: id,
|
|
||||||
object: object,
|
|
||||||
user: user
|
|
||||||
}) do
|
|
||||||
attachments = build_attachments(id, object)
|
attachments = build_attachments(id, object)
|
||||||
scrubbed_content = Utils.scrub_html_and_truncate(object)
|
scrubbed_content = Utils.scrub_html_and_truncate(object)
|
||||||
# Zero width space
|
# Zero width space
|
||||||
|
@ -27,21 +25,12 @@ def build_tags(%{
|
||||||
end
|
end
|
||||||
|
|
||||||
[
|
[
|
||||||
{:meta,
|
title_tag(user),
|
||||||
[
|
{:meta, [property: "twitter:description", content: content], []}
|
||||||
property: "twitter:title",
|
|
||||||
content: Utils.user_name_string(user)
|
|
||||||
], []},
|
|
||||||
{:meta,
|
|
||||||
[
|
|
||||||
property: "twitter:description",
|
|
||||||
content: content
|
|
||||||
], []}
|
|
||||||
] ++
|
] ++
|
||||||
if attachments == [] or Metadata.activity_nsfw?(object) do
|
if attachments == [] or Metadata.activity_nsfw?(object) do
|
||||||
[
|
[
|
||||||
{:meta,
|
image_tag(user),
|
||||||
[property: "twitter:image", content: Utils.attachment_url(User.avatar_url(user))], []},
|
|
||||||
{:meta, [property: "twitter:card", content: "summary_large_image"], []}
|
{:meta, [property: "twitter:card", content: "summary_large_image"], []}
|
||||||
]
|
]
|
||||||
else
|
else
|
||||||
|
@ -53,30 +42,28 @@ def build_tags(%{
|
||||||
def build_tags(%{user: user}) do
|
def build_tags(%{user: user}) do
|
||||||
with truncated_bio = Utils.scrub_html_and_truncate(user.bio || "") do
|
with truncated_bio = Utils.scrub_html_and_truncate(user.bio || "") do
|
||||||
[
|
[
|
||||||
{:meta,
|
title_tag(user),
|
||||||
[
|
|
||||||
property: "twitter:title",
|
|
||||||
content: Utils.user_name_string(user)
|
|
||||||
], []},
|
|
||||||
{:meta, [property: "twitter:description", content: truncated_bio], []},
|
{:meta, [property: "twitter:description", content: truncated_bio], []},
|
||||||
{:meta, [property: "twitter:image", content: Utils.attachment_url(User.avatar_url(user))],
|
image_tag(user),
|
||||||
[]},
|
|
||||||
{:meta, [property: "twitter:card", content: "summary"], []}
|
{:meta, [property: "twitter:card", content: "summary"], []}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp title_tag(user) do
|
||||||
|
{:meta, [property: "twitter:title", content: Utils.user_name_string(user)], []}
|
||||||
|
end
|
||||||
|
|
||||||
|
def image_tag(user) do
|
||||||
|
{:meta, [property: "twitter:image", content: Utils.attachment_url(User.avatar_url(user))], []}
|
||||||
|
end
|
||||||
|
|
||||||
defp build_attachments(id, %{data: %{"attachment" => attachments}}) do
|
defp build_attachments(id, %{data: %{"attachment" => attachments}}) do
|
||||||
Enum.reduce(attachments, [], fn attachment, acc ->
|
Enum.reduce(attachments, [], fn attachment, acc ->
|
||||||
rendered_tags =
|
rendered_tags =
|
||||||
Enum.reduce(attachment["url"], [], fn url, acc ->
|
Enum.reduce(attachment["url"], [], fn url, acc ->
|
||||||
media_type =
|
|
||||||
Enum.find(["image", "audio", "video"], fn media_type ->
|
|
||||||
String.starts_with?(url["mediaType"], media_type)
|
|
||||||
end)
|
|
||||||
|
|
||||||
# TODO: Add additional properties to objects when we have the data available.
|
# TODO: Add additional properties to objects when we have the data available.
|
||||||
case media_type do
|
case Utils.fetch_media_type(@media_types, url["mediaType"]) do
|
||||||
"audio" ->
|
"audio" ->
|
||||||
[
|
[
|
||||||
{:meta, [property: "twitter:card", content: "player"], []},
|
{:meta, [property: "twitter:card", content: "player"], []},
|
||||||
|
|
|
@ -39,4 +39,11 @@ def user_name_string(user) do
|
||||||
"(@#{user.nickname})"
|
"(@#{user.nickname})"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec fetch_media_type(list(String.t()), String.t()) :: String.t() | nil
|
||||||
|
def fetch_media_type(supported_types, media_type) do
|
||||||
|
Enum.find(supported_types, fn support_type ->
|
||||||
|
String.starts_with?(media_type, support_type)
|
||||||
|
end)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -29,7 +29,7 @@ def check_password(conn, %{"user" => username, "pass" => password}) do
|
||||||
else
|
else
|
||||||
false ->
|
false ->
|
||||||
conn
|
conn
|
||||||
|> put_status(403)
|
|> put_status(:forbidden)
|
||||||
|> json(false)
|
|> json(false)
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
|
|
|
@ -34,8 +34,11 @@ def schemas(conn, _params) do
|
||||||
def raw_nodeinfo do
|
def raw_nodeinfo do
|
||||||
stats = Stats.get_stats()
|
stats = Stats.get_stats()
|
||||||
|
|
||||||
|
exclusions = Config.get([:instance, :mrf_transparency_exclusions])
|
||||||
|
|
||||||
mrf_simple =
|
mrf_simple =
|
||||||
Config.get(:mrf_simple)
|
Config.get(:mrf_simple)
|
||||||
|
|> Enum.map(fn {k, v} -> {k, Enum.reject(v, fn v -> v in exclusions end)} end)
|
||||||
|> Enum.into(%{})
|
|> Enum.into(%{})
|
||||||
|
|
||||||
# This horror is needed to convert regex sigils to strings
|
# This horror is needed to convert regex sigils to strings
|
||||||
|
@ -86,7 +89,8 @@ def raw_nodeinfo do
|
||||||
mrf_simple: mrf_simple,
|
mrf_simple: mrf_simple,
|
||||||
mrf_keyword: mrf_keyword,
|
mrf_keyword: mrf_keyword,
|
||||||
mrf_user_allowlist: mrf_user_allowlist,
|
mrf_user_allowlist: mrf_user_allowlist,
|
||||||
quarantined_instances: quarantined
|
quarantined_instances: quarantined,
|
||||||
|
exclusions: length(exclusions) > 0
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
%{}
|
%{}
|
||||||
|
@ -201,8 +205,6 @@ def nodeinfo(conn, %{"version" => "2.1"}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def nodeinfo(conn, _) do
|
def nodeinfo(conn, _) do
|
||||||
conn
|
render_error(conn, :not_found, "Nodeinfo schema version not handled")
|
||||||
|> put_status(404)
|
|
||||||
|> json(%{error: "Nodeinfo schema version not handled"})
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,21 +9,24 @@ defmodule Pleroma.Web.OAuth.FallbackController do
|
||||||
def call(conn, {:register, :generic_error}) do
|
def call(conn, {:register, :generic_error}) do
|
||||||
conn
|
conn
|
||||||
|> put_status(:internal_server_error)
|
|> put_status(:internal_server_error)
|
||||||
|> put_flash(:error, "Unknown error, please check the details and try again.")
|
|> put_flash(
|
||||||
|
:error,
|
||||||
|
dgettext("errors", "Unknown error, please check the details and try again.")
|
||||||
|
)
|
||||||
|> OAuthController.registration_details(conn.params)
|
|> OAuthController.registration_details(conn.params)
|
||||||
end
|
end
|
||||||
|
|
||||||
def call(conn, {:register, _error}) do
|
def call(conn, {:register, _error}) do
|
||||||
conn
|
conn
|
||||||
|> put_status(:unauthorized)
|
|> put_status(:unauthorized)
|
||||||
|> put_flash(:error, "Invalid Username/Password")
|
|> put_flash(:error, dgettext("errors", "Invalid Username/Password"))
|
||||||
|> OAuthController.registration_details(conn.params)
|
|> OAuthController.registration_details(conn.params)
|
||||||
end
|
end
|
||||||
|
|
||||||
def call(conn, _error) do
|
def call(conn, _error) do
|
||||||
conn
|
conn
|
||||||
|> put_status(:unauthorized)
|
|> put_status(:unauthorized)
|
||||||
|> put_flash(:error, "Invalid Username/Password")
|
|> put_flash(:error, dgettext("errors", "Invalid Username/Password"))
|
||||||
|> OAuthController.authorize(conn.params)
|
|> OAuthController.authorize(conn.params)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -90,7 +90,7 @@ defp handle_existing_authorization(
|
||||||
redirect(conn, external: url)
|
redirect(conn, external: url)
|
||||||
else
|
else
|
||||||
conn
|
conn
|
||||||
|> put_flash(:error, "Unlisted redirect_uri.")
|
|> put_flash(:error, dgettext("errors", "Unlisted redirect_uri."))
|
||||||
|> redirect(external: redirect_uri(conn, redirect_uri))
|
|> redirect(external: redirect_uri(conn, redirect_uri))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -128,7 +128,7 @@ def after_create_authorization(%Plug.Conn{} = conn, %Authorization{} = auth, %{
|
||||||
redirect(conn, external: url)
|
redirect(conn, external: url)
|
||||||
else
|
else
|
||||||
conn
|
conn
|
||||||
|> put_flash(:error, "Unlisted redirect_uri.")
|
|> put_flash(:error, dgettext("errors", "Unlisted redirect_uri."))
|
||||||
|> redirect(external: redirect_uri(conn, redirect_uri))
|
|> redirect(external: redirect_uri(conn, redirect_uri))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -142,7 +142,7 @@ defp handle_create_authorization_error(
|
||||||
# Per https://github.com/tootsuite/mastodon/blob/
|
# Per https://github.com/tootsuite/mastodon/blob/
|
||||||
# 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L39
|
# 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L39
|
||||||
conn
|
conn
|
||||||
|> put_flash(:error, "This action is outside the authorized scopes")
|
|> put_flash(:error, dgettext("errors", "This action is outside the authorized scopes"))
|
||||||
|> put_status(:unauthorized)
|
|> put_status(:unauthorized)
|
||||||
|> authorize(params)
|
|> authorize(params)
|
||||||
end
|
end
|
||||||
|
@ -155,7 +155,7 @@ defp handle_create_authorization_error(
|
||||||
# Per https://github.com/tootsuite/mastodon/blob/
|
# Per https://github.com/tootsuite/mastodon/blob/
|
||||||
# 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76
|
# 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76
|
||||||
conn
|
conn
|
||||||
|> put_flash(:error, "Your login is missing a confirmed e-mail address")
|
|> put_flash(:error, dgettext("errors", "Your login is missing a confirmed e-mail address"))
|
||||||
|> put_status(:forbidden)
|
|> put_status(:forbidden)
|
||||||
|> authorize(params)
|
|> authorize(params)
|
||||||
end
|
end
|
||||||
|
@ -176,9 +176,7 @@ def token_exchange(
|
||||||
|
|
||||||
json(conn, Token.Response.build(user, token, response_attrs))
|
json(conn, Token.Response.build(user, token, response_attrs))
|
||||||
else
|
else
|
||||||
_error ->
|
_error -> render_invalid_credentials_error(conn)
|
||||||
put_status(conn, 400)
|
|
||||||
|> json(%{error: "Invalid credentials"})
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -192,9 +190,7 @@ def token_exchange(%Plug.Conn{} = conn, %{"grant_type" => "authorization_code"}
|
||||||
|
|
||||||
json(conn, Token.Response.build(user, token, response_attrs))
|
json(conn, Token.Response.build(user, token, response_attrs))
|
||||||
else
|
else
|
||||||
_error ->
|
_error -> render_invalid_credentials_error(conn)
|
||||||
put_status(conn, 400)
|
|
||||||
|> json(%{error: "Invalid credentials"})
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -214,18 +210,13 @@ def token_exchange(
|
||||||
{:auth_active, false} ->
|
{:auth_active, false} ->
|
||||||
# Per https://github.com/tootsuite/mastodon/blob/
|
# Per https://github.com/tootsuite/mastodon/blob/
|
||||||
# 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76
|
# 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76
|
||||||
conn
|
render_error(conn, :forbidden, "Your login is missing a confirmed e-mail address")
|
||||||
|> put_status(:forbidden)
|
|
||||||
|> json(%{error: "Your login is missing a confirmed e-mail address"})
|
|
||||||
|
|
||||||
{:user_active, false} ->
|
{:user_active, false} ->
|
||||||
conn
|
render_error(conn, :forbidden, "Your account is currently disabled")
|
||||||
|> put_status(:forbidden)
|
|
||||||
|> json(%{error: "Your account is currently disabled"})
|
|
||||||
|
|
||||||
_error ->
|
_error ->
|
||||||
put_status(conn, 400)
|
render_invalid_credentials_error(conn)
|
||||||
|> json(%{error: "Invalid credentials"})
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -247,9 +238,7 @@ def token_exchange(%Plug.Conn{} = conn, %{"grant_type" => "client_credentials"}
|
||||||
{:ok, token} <- Token.exchange_token(app, auth) do
|
{:ok, token} <- Token.exchange_token(app, auth) do
|
||||||
json(conn, Token.Response.build_for_client_credentials(token))
|
json(conn, Token.Response.build_for_client_credentials(token))
|
||||||
else
|
else
|
||||||
_error ->
|
_error -> render_invalid_credentials_error(conn)
|
||||||
put_status(conn, 400)
|
|
||||||
|> json(%{error: "Invalid credentials"})
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -271,9 +260,7 @@ def token_revoke(%Plug.Conn{} = conn, params), do: bad_request(conn, params)
|
||||||
|
|
||||||
# Response for bad request
|
# Response for bad request
|
||||||
defp bad_request(%Plug.Conn{} = conn, _) do
|
defp bad_request(%Plug.Conn{} = conn, _) do
|
||||||
conn
|
render_error(conn, :internal_server_error, "Bad request")
|
||||||
|> put_status(500)
|
|
||||||
|> json(%{error: "Bad request"})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "Prepares OAuth request to provider for Ueberauth"
|
@doc "Prepares OAuth request to provider for Ueberauth"
|
||||||
|
@ -304,9 +291,11 @@ def prepare_request(%Plug.Conn{} = conn, %{
|
||||||
def request(%Plug.Conn{} = conn, params) do
|
def request(%Plug.Conn{} = conn, params) do
|
||||||
message =
|
message =
|
||||||
if params["provider"] do
|
if params["provider"] do
|
||||||
"Unsupported OAuth provider: #{params["provider"]}."
|
dgettext("errors", "Unsupported OAuth provider: %{provider}.",
|
||||||
|
provider: params["provider"]
|
||||||
|
)
|
||||||
else
|
else
|
||||||
"Bad OAuth request."
|
dgettext("errors", "Bad OAuth request.")
|
||||||
end
|
end
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|
@ -320,7 +309,10 @@ def callback(%Plug.Conn{assigns: %{ueberauth_failure: failure}} = conn, params)
|
||||||
message = Enum.join(messages, "; ")
|
message = Enum.join(messages, "; ")
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_flash(:error, "Failed to authenticate: #{message}.")
|
|> put_flash(
|
||||||
|
:error,
|
||||||
|
dgettext("errors", "Failed to authenticate: %{message}.", message: message)
|
||||||
|
)
|
||||||
|> redirect(external: redirect_uri(conn, params["redirect_uri"]))
|
|> redirect(external: redirect_uri(conn, params["redirect_uri"]))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -350,7 +342,7 @@ def callback(%Plug.Conn{} = conn, params) do
|
||||||
Logger.debug(inspect(["OAUTH_ERROR", error, conn.assigns]))
|
Logger.debug(inspect(["OAUTH_ERROR", error, conn.assigns]))
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_flash(:error, "Failed to set up user account.")
|
|> put_flash(:error, dgettext("errors", "Failed to set up user account."))
|
||||||
|> redirect(external: redirect_uri(conn, params["redirect_uri"]))
|
|> redirect(external: redirect_uri(conn, params["redirect_uri"]))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -468,4 +460,8 @@ def default_redirect_uri(%App{} = app) do
|
||||||
|> String.split()
|
|> String.split()
|
||||||
|> Enum.at(0)
|
|> Enum.at(0)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp render_invalid_credentials_error(conn) do
|
||||||
|
render_error(conn, :bad_request, "Invalid credentials")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.OAuth.Token.Response do
|
defmodule Pleroma.Web.OAuth.Token.Response do
|
||||||
@moduledoc false
|
@moduledoc false
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.OAuth.Token.Strategy.RefreshToken do
|
defmodule Pleroma.Web.OAuth.Token.Strategy.RefreshToken do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Functions for dealing with refresh token strategy.
|
Functions for dealing with refresh token strategy.
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.OAuth.Token.Strategy.Revoke do
|
defmodule Pleroma.Web.OAuth.Token.Strategy.Revoke do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Functions for dealing with revocation.
|
Functions for dealing with revocation.
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.OAuth.Token.Utils do
|
defmodule Pleroma.Web.OAuth.Token.Utils do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Auxiliary functions for dealing with tokens.
|
Auxiliary functions for dealing with tokens.
|
||||||
|
|
|
@ -245,14 +245,10 @@ defp represent_activity(conn, _, activity, user) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def errors(conn, {:error, :not_found}) do
|
def errors(conn, {:error, :not_found}) do
|
||||||
conn
|
render_error(conn, :not_found, "Not found")
|
||||||
|> put_status(404)
|
|
||||||
|> text("Not found")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def errors(conn, _) do
|
def errors(conn, _) do
|
||||||
conn
|
render_error(conn, :internal_server_error, "Something went wrong")
|
||||||
|> put_status(500)
|
|
||||||
|> text("Something went wrong")
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,12 +3,6 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.RichMedia.Parser do
|
defmodule Pleroma.Web.RichMedia.Parser do
|
||||||
@parsers [
|
|
||||||
Pleroma.Web.RichMedia.Parsers.OGP,
|
|
||||||
Pleroma.Web.RichMedia.Parsers.TwitterCard,
|
|
||||||
Pleroma.Web.RichMedia.Parsers.OEmbed
|
|
||||||
]
|
|
||||||
|
|
||||||
@hackney_options [
|
@hackney_options [
|
||||||
pool: :media,
|
pool: :media,
|
||||||
recv_timeout: 2_000,
|
recv_timeout: 2_000,
|
||||||
|
@ -16,6 +10,10 @@ defmodule Pleroma.Web.RichMedia.Parser do
|
||||||
with_body: true
|
with_body: true
|
||||||
]
|
]
|
||||||
|
|
||||||
|
defp parsers do
|
||||||
|
Pleroma.Config.get([:rich_media, :parsers])
|
||||||
|
end
|
||||||
|
|
||||||
def parse(nil), do: {:error, "No URL provided"}
|
def parse(nil), do: {:error, "No URL provided"}
|
||||||
|
|
||||||
if Pleroma.Config.get(:env) == :test do
|
if Pleroma.Config.get(:env) == :test do
|
||||||
|
@ -48,7 +46,7 @@ defp parse_url(url) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp maybe_parse(html) do
|
defp maybe_parse(html) do
|
||||||
Enum.reduce_while(@parsers, %{}, fn parser, acc ->
|
Enum.reduce_while(parsers(), %{}, fn parser, acc ->
|
||||||
case parser.parse(html, acc) do
|
case parser.parse(html, acc) do
|
||||||
{:ok, data} -> {:halt, data}
|
{:ok, data} -> {:halt, data}
|
||||||
{:error, _msg} -> {:cont, acc}
|
{:error, _msg} -> {:cont, acc}
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.RichMedia.Parsers.MetaTagsParser do
|
defmodule Pleroma.Web.RichMedia.Parsers.MetaTagsParser do
|
||||||
def parse(html, data, prefix, error_message, key_name, value_name \\ "content") do
|
def parse(html, data, prefix, error_message, key_name, value_name \\ "content") do
|
||||||
meta_data =
|
meta_data =
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.RichMedia.Parsers.OEmbed do
|
defmodule Pleroma.Web.RichMedia.Parsers.OEmbed do
|
||||||
def parse(html, _data) do
|
def parse(html, _data) do
|
||||||
with elements = [_ | _] <- get_discovery_data(html),
|
with elements = [_ | _] <- get_discovery_data(html),
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.RichMedia.Parsers.OGP do
|
defmodule Pleroma.Web.RichMedia.Parsers.OGP do
|
||||||
def parse(html, data) do
|
def parse(html, data) do
|
||||||
Pleroma.Web.RichMedia.Parsers.MetaTagsParser.parse(
|
Pleroma.Web.RichMedia.Parsers.MetaTagsParser.parse(
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.RichMedia.Parsers.TwitterCard do
|
defmodule Pleroma.Web.RichMedia.Parsers.TwitterCard do
|
||||||
def parse(html, data) do
|
def parse(html, data) do
|
||||||
Pleroma.Web.RichMedia.Parsers.MetaTagsParser.parse(
|
Pleroma.Web.RichMedia.Parsers.MetaTagsParser.parse(
|
||||||
|
|
|
@ -322,10 +322,6 @@ defmodule Pleroma.Web.Router do
|
||||||
|
|
||||||
patch("/accounts/update_credentials", MastodonAPIController, :update_credentials)
|
patch("/accounts/update_credentials", MastodonAPIController, :update_credentials)
|
||||||
|
|
||||||
patch("/accounts/update_avatar", MastodonAPIController, :update_avatar)
|
|
||||||
patch("/accounts/update_banner", MastodonAPIController, :update_banner)
|
|
||||||
patch("/accounts/update_background", MastodonAPIController, :update_background)
|
|
||||||
|
|
||||||
post("/statuses", MastodonAPIController, :post_status)
|
post("/statuses", MastodonAPIController, :post_status)
|
||||||
delete("/statuses/:id", MastodonAPIController, :delete_status)
|
delete("/statuses/:id", MastodonAPIController, :delete_status)
|
||||||
|
|
||||||
|
@ -360,6 +356,10 @@ defmodule Pleroma.Web.Router do
|
||||||
put("/filters/:id", MastodonAPIController, :update_filter)
|
put("/filters/:id", MastodonAPIController, :update_filter)
|
||||||
delete("/filters/:id", MastodonAPIController, :delete_filter)
|
delete("/filters/:id", MastodonAPIController, :delete_filter)
|
||||||
|
|
||||||
|
patch("/pleroma/accounts/update_avatar", MastodonAPIController, :update_avatar)
|
||||||
|
patch("/pleroma/accounts/update_banner", MastodonAPIController, :update_banner)
|
||||||
|
patch("/pleroma/accounts/update_background", MastodonAPIController, :update_background)
|
||||||
|
|
||||||
get("/pleroma/mascot", MastodonAPIController, :get_mascot)
|
get("/pleroma/mascot", MastodonAPIController, :get_mascot)
|
||||||
put("/pleroma/mascot", MastodonAPIController, :set_mascot)
|
put("/pleroma/mascot", MastodonAPIController, :set_mascot)
|
||||||
|
|
||||||
|
@ -623,8 +623,6 @@ defmodule Pleroma.Web.Router do
|
||||||
# XXX: not really ostatus
|
# XXX: not really ostatus
|
||||||
pipe_through(:ostatus)
|
pipe_through(:ostatus)
|
||||||
|
|
||||||
get("/users/:nickname/followers", ActivityPubController, :followers)
|
|
||||||
get("/users/:nickname/following", ActivityPubController, :following)
|
|
||||||
get("/users/:nickname/outbox", ActivityPubController, :outbox)
|
get("/users/:nickname/outbox", ActivityPubController, :outbox)
|
||||||
get("/objects/:uuid/likes", ActivityPubController, :object_likes)
|
get("/objects/:uuid/likes", ActivityPubController, :object_likes)
|
||||||
end
|
end
|
||||||
|
@ -656,6 +654,12 @@ defmodule Pleroma.Web.Router do
|
||||||
pipe_through(:oauth_write)
|
pipe_through(:oauth_write)
|
||||||
post("/users/:nickname/outbox", ActivityPubController, :update_outbox)
|
post("/users/:nickname/outbox", ActivityPubController, :update_outbox)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
scope [] do
|
||||||
|
pipe_through(:oauth_read_or_public)
|
||||||
|
get("/users/:nickname/followers", ActivityPubController, :followers)
|
||||||
|
get("/users/:nickname/following", ActivityPubController, :following)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/relay", Pleroma.Web.ActivityPub do
|
scope "/relay", Pleroma.Web.ActivityPub do
|
||||||
|
|
17
lib/pleroma/web/translation_helpers.ex
Normal file
17
lib/pleroma/web/translation_helpers.ex
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.TranslationHelpers do
|
||||||
|
defmacro render_error(conn, status, msgid, bindings \\ Macro.escape(%{})) do
|
||||||
|
quote do
|
||||||
|
require Pleroma.Web.Gettext
|
||||||
|
|
||||||
|
unquote(conn)
|
||||||
|
|> Plug.Conn.put_status(unquote(status))
|
||||||
|
|> Phoenix.Controller.json(%{
|
||||||
|
error: Pleroma.Web.Gettext.dgettext("errors", unquote(msgid), unquote(bindings))
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -192,6 +192,13 @@ def dm_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def notifications(%{assigns: %{user: user}} = conn, params) do
|
def notifications(%{assigns: %{user: user}} = conn, params) do
|
||||||
|
params =
|
||||||
|
if Map.has_key?(params, "with_muted") do
|
||||||
|
Map.put(params, :with_muted, params["with_muted"] in [true, "True", "true", "1"])
|
||||||
|
else
|
||||||
|
params
|
||||||
|
end
|
||||||
|
|
||||||
notifications = Notification.for_user(user, params)
|
notifications = Notification.for_user(user, params)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.UploaderController do
|
defmodule Pleroma.Web.UploaderController do
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
@ -8,7 +12,7 @@ def callback(conn, %{"upload_path" => upload_path} = params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def callbacks(conn, _) do
|
def callbacks(conn, _) do
|
||||||
send_resp(conn, 400, "bad request")
|
render_error(conn, :bad_request, "bad request")
|
||||||
end
|
end
|
||||||
|
|
||||||
defp process_callback(conn, pid, params) when is_pid(pid) do
|
defp process_callback(conn, pid, params) when is_pid(pid) do
|
||||||
|
@ -20,6 +24,6 @@ defp process_callback(conn, pid, params) when is_pid(pid) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp process_callback(conn, _, _) do
|
defp process_callback(conn, _, _) do
|
||||||
send_resp(conn, 400, "bad request")
|
render_error(conn, :bad_request, "bad request")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -23,9 +23,11 @@ defmodule Pleroma.Web do
|
||||||
def controller do
|
def controller do
|
||||||
quote do
|
quote do
|
||||||
use Phoenix.Controller, namespace: Pleroma.Web
|
use Phoenix.Controller, namespace: Pleroma.Web
|
||||||
|
|
||||||
import Plug.Conn
|
import Plug.Conn
|
||||||
import Pleroma.Web.Gettext
|
import Pleroma.Web.Gettext
|
||||||
import Pleroma.Web.Router.Helpers
|
import Pleroma.Web.Router.Helpers
|
||||||
|
import Pleroma.Web.TranslationHelpers
|
||||||
|
|
||||||
plug(:set_put_layout)
|
plug(:set_put_layout)
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Phoenix.Transports.WebSocket.Raw do
|
defmodule Phoenix.Transports.WebSocket.Raw do
|
||||||
import Plug.Conn,
|
import Plug.Conn,
|
||||||
only: [
|
only: [
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue