forked from AkkomaGang/akkoma
Merge branch 'develop' into feature/digest-email
This commit is contained in:
commit
c729883936
41 changed files with 1049 additions and 627 deletions
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.
|
|
||||||
|
|
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -8,32 +8,39 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- **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
|
- **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: OpenGraph and TwitterCard providers enabled by default
|
- 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
|
||||||
|
- Federation: Return 403 errors when trying to request pages from a user's follower/following collections if they have `hide_followers`/`hide_follows` set
|
||||||
- NodeInfo: Return `skipThreadContainment` in `metadata` for the `skip_thread_containment` option
|
- NodeInfo: Return `skipThreadContainment` in `metadata` for the `skip_thread_containment` option
|
||||||
|
|
||||||
### 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`)
|
- 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
|
### Added
|
||||||
- MRF: Support for priming the mediaproxy cache (`Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`)
|
- MRF: Support for priming the mediaproxy cache (`Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`)
|
||||||
Configuration: `federation_incoming_replies_max_depth` option
|
- 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: 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, 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, 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 users' tags when querying reports
|
||||||
- Admin API: Return avatar and display name when querying users
|
- Admin API: Return avatar and display name when querying users
|
||||||
- Admin API: Allow querying user by ID
|
- Admin API: Allow querying user by ID
|
||||||
- Admin API: Added support for `tuples`.
|
- Admin API: Added support for `tuples`.
|
||||||
- Added synchronization of following/followers counters for external users
|
- Added synchronization of following/followers counters for external users
|
||||||
- Configuration: `enabled` option for `Pleroma.Emails.Mailer`, defaulting to `false`.
|
- Configuration: `enabled` option for `Pleroma.Emails.Mailer`, defaulting to `false`.
|
||||||
- Mastodon API: Add support for categories for custom emojis by reusing the group feature. <https://github.com/tootsuite/mastodon/pull/11196>
|
- Configuration: Pleroma.Plugs.RateLimiter `bucket_name`, `params` options.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- 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.
|
- Admin API: changed json structure for saving config settings.
|
||||||
|
- RichMedia: parsers and their order are configured in `rich_media` config.
|
||||||
|
|
||||||
## [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,
|
||||||
|
@ -336,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,
|
||||||
|
@ -527,7 +535,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.
|
||||||
|
|
|
@ -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`
|
||||||
|
|
|
@ -106,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.
|
||||||
|
@ -424,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
|
||||||
|
@ -652,3 +654,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
|
||||||
|
|
|
@ -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 """
|
||||||
|
|
|
@ -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
|
||||||
|
@ -34,31 +33,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
|
||||||
|
|
||||||
|
@ -203,11 +218,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,
|
||||||
|
@ -217,21 +231,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,
|
||||||
|
|
|
@ -32,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
|
||||||
|
|
|
@ -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
|
||||||
...
|
...
|
||||||
|
@ -49,33 +65,56 @@ defmodule Pleroma.Plugs.RateLimiter do
|
||||||
|
|
||||||
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_throttled_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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -750,10 +750,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)
|
||||||
|
@ -763,9 +766,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)
|
||||||
|
@ -861,6 +866,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
|
||||||
|
|
|
@ -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)
|
||||||
|
@ -146,6 +147,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}
|
||||||
|
|
||||||
|
@ -162,14 +173,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
|
||||||
|
|
|
@ -103,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
|
||||||
|
|
||||||
|
@ -325,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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -53,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
|
||||||
|
@ -67,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"
|
||||||
|
|
||||||
|
@ -1051,9 +1068,14 @@ 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})
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -28,12 +28,7 @@ def remote(conn, %{"sig" => sig64, "url" => url64} = params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def filename_matches(has_filename, path, url) do
|
def filename_matches(has_filename, path, url) do
|
||||||
filename =
|
filename = url |> MediaProxy.filename()
|
||||||
url
|
|
||||||
|> MediaProxy.filename()
|
|
||||||
|> URI.decode()
|
|
||||||
|
|
||||||
path = URI.decode(path)
|
|
||||||
|
|
||||||
if has_filename && filename && Path.basename(path) != filename do
|
if has_filename && filename && Path.basename(path) != filename do
|
||||||
{:wrong_filename, filename}
|
{:wrong_filename, filename}
|
||||||
|
|
|
@ -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,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
|
||||||
|
|
|
@ -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
|
||||||
%{}
|
%{}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
@ -625,8 +625,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
|
||||||
|
@ -658,6 +656,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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.CopyMutedToMutedNotifications do
|
||||||
|
use Ecto.Migration
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
|
def change do
|
||||||
|
query =
|
||||||
|
User.Query.build(%{
|
||||||
|
local: true,
|
||||||
|
active: true,
|
||||||
|
order_by: :id
|
||||||
|
})
|
||||||
|
|
||||||
|
Pleroma.Repo.stream(query)
|
||||||
|
|> Enum.each(fn
|
||||||
|
%{info: %{mutes: mutes} = info} = user ->
|
||||||
|
info_cng =
|
||||||
|
Ecto.Changeset.cast(info, %{muted_notifications: mutes}, [:muted_notifications])
|
||||||
|
|
||||||
|
Ecto.Changeset.change(user)
|
||||||
|
|> Ecto.Changeset.put_embed(:info, info_cng)
|
||||||
|
|> Pleroma.Repo.update()
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
|
@ -88,10 +88,10 @@ test "validates signature" do
|
||||||
assert decode_url(sig, base64) == {:error, :invalid_signature}
|
assert decode_url(sig, base64) == {:error, :invalid_signature}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "filename_matches matches url encoded paths" do
|
test "filename_matches preserves the encoded or decoded path" do
|
||||||
assert MediaProxyController.filename_matches(
|
assert MediaProxyController.filename_matches(
|
||||||
true,
|
true,
|
||||||
"/Hello%20world.jpg",
|
"/Hello world.jpg",
|
||||||
"http://pleroma.social/Hello world.jpg"
|
"http://pleroma.social/Hello world.jpg"
|
||||||
) == :ok
|
) == :ok
|
||||||
|
|
||||||
|
@ -100,19 +100,11 @@ test "filename_matches matches url encoded paths" do
|
||||||
"/Hello%20world.jpg",
|
"/Hello%20world.jpg",
|
||||||
"http://pleroma.social/Hello%20world.jpg"
|
"http://pleroma.social/Hello%20world.jpg"
|
||||||
) == :ok
|
) == :ok
|
||||||
end
|
|
||||||
|
|
||||||
test "filename_matches matches non-url encoded paths" do
|
|
||||||
assert MediaProxyController.filename_matches(
|
|
||||||
true,
|
|
||||||
"/Hello world.jpg",
|
|
||||||
"http://pleroma.social/Hello%20world.jpg"
|
|
||||||
) == :ok
|
|
||||||
|
|
||||||
assert MediaProxyController.filename_matches(
|
assert MediaProxyController.filename_matches(
|
||||||
true,
|
true,
|
||||||
"/Hello world.jpg",
|
"/my%2Flong%2Furl%2F2019%2F07%2FS.jpg",
|
||||||
"http://pleroma.social/Hello world.jpg"
|
"http://pleroma.social/my%2Flong%2Furl%2F2019%2F07%2FS.jpg"
|
||||||
) == :ok
|
) == :ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -76,26 +76,37 @@ test "it creates a notification for user and send to the 'user' and the 'user:no
|
||||||
Task.await(task_user_notification)
|
Task.await(task_user_notification)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it doesn't create a notification for user if the user blocks the activity author" do
|
test "it creates a notification for user if the user blocks the activity author" do
|
||||||
activity = insert(:note_activity)
|
activity = insert(:note_activity)
|
||||||
author = User.get_cached_by_ap_id(activity.data["actor"])
|
author = User.get_cached_by_ap_id(activity.data["actor"])
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
{:ok, user} = User.block(user, author)
|
{:ok, user} = User.block(user, author)
|
||||||
|
|
||||||
refute Notification.create_notification(activity, user)
|
assert Notification.create_notification(activity, user)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it doesn't create a notificatin for the user if the user mutes the activity author" do
|
test "it creates a notificatin for the user if the user mutes the activity author" do
|
||||||
muter = insert(:user)
|
muter = insert(:user)
|
||||||
muted = insert(:user)
|
muted = insert(:user)
|
||||||
{:ok, _} = User.mute(muter, muted)
|
{:ok, _} = User.mute(muter, muted)
|
||||||
muter = Repo.get(User, muter.id)
|
muter = Repo.get(User, muter.id)
|
||||||
{:ok, activity} = CommonAPI.post(muted, %{"status" => "Hi @#{muter.nickname}"})
|
{:ok, activity} = CommonAPI.post(muted, %{"status" => "Hi @#{muter.nickname}"})
|
||||||
|
|
||||||
refute Notification.create_notification(activity, muter)
|
assert Notification.create_notification(activity, muter)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it doesn't create a notification for an activity from a muted thread" do
|
test "notification created if user is muted without notifications" do
|
||||||
|
muter = insert(:user)
|
||||||
|
muted = insert(:user)
|
||||||
|
|
||||||
|
{:ok, muter} = User.mute(muter, muted, false)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(muted, %{"status" => "Hi @#{muter.nickname}"})
|
||||||
|
|
||||||
|
assert Notification.create_notification(activity, muter)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it creates a notification for an activity from a muted thread" do
|
||||||
muter = insert(:user)
|
muter = insert(:user)
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
{:ok, activity} = CommonAPI.post(muter, %{"status" => "hey"})
|
{:ok, activity} = CommonAPI.post(muter, %{"status" => "hey"})
|
||||||
|
@ -107,7 +118,7 @@ test "it doesn't create a notification for an activity from a muted thread" do
|
||||||
"in_reply_to_status_id" => activity.id
|
"in_reply_to_status_id" => activity.id
|
||||||
})
|
})
|
||||||
|
|
||||||
refute Notification.create_notification(activity, muter)
|
assert Notification.create_notification(activity, muter)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it disables notifications from followers" do
|
test "it disables notifications from followers" do
|
||||||
|
@ -579,4 +590,98 @@ test "replying to a deleted post without tagging does not generate a notificatio
|
||||||
assert Enum.empty?(Notification.for_user(user))
|
assert Enum.empty?(Notification.for_user(user))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "for_user" do
|
||||||
|
test "it returns notifications for muted user without notifications" do
|
||||||
|
user = insert(:user)
|
||||||
|
muted = insert(:user)
|
||||||
|
{:ok, user} = User.mute(user, muted, false)
|
||||||
|
|
||||||
|
{:ok, _activity} = TwitterAPI.create_status(muted, %{"status" => "hey @#{user.nickname}"})
|
||||||
|
|
||||||
|
assert length(Notification.for_user(user)) == 1
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it doesn't return notifications for muted user with notifications" do
|
||||||
|
user = insert(:user)
|
||||||
|
muted = insert(:user)
|
||||||
|
{:ok, user} = User.mute(user, muted)
|
||||||
|
|
||||||
|
{:ok, _activity} = TwitterAPI.create_status(muted, %{"status" => "hey @#{user.nickname}"})
|
||||||
|
|
||||||
|
assert Notification.for_user(user) == []
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it doesn't return notifications for blocked user" do
|
||||||
|
user = insert(:user)
|
||||||
|
blocked = insert(:user)
|
||||||
|
{:ok, user} = User.block(user, blocked)
|
||||||
|
|
||||||
|
{:ok, _activity} = TwitterAPI.create_status(blocked, %{"status" => "hey @#{user.nickname}"})
|
||||||
|
|
||||||
|
assert Notification.for_user(user) == []
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it doesn't return notificatitons for blocked domain" do
|
||||||
|
user = insert(:user)
|
||||||
|
blocked = insert(:user, ap_id: "http://some-domain.com")
|
||||||
|
{:ok, user} = User.block_domain(user, "some-domain.com")
|
||||||
|
|
||||||
|
{:ok, _activity} = TwitterAPI.create_status(blocked, %{"status" => "hey @#{user.nickname}"})
|
||||||
|
|
||||||
|
assert Notification.for_user(user) == []
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it doesn't return notifications for muted thread" do
|
||||||
|
user = insert(:user)
|
||||||
|
another_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} =
|
||||||
|
TwitterAPI.create_status(another_user, %{"status" => "hey @#{user.nickname}"})
|
||||||
|
|
||||||
|
{:ok, _} = Pleroma.ThreadMute.add_mute(user.id, activity.data["context"])
|
||||||
|
assert Notification.for_user(user) == []
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it returns notifications for muted user with notifications and with_muted parameter" do
|
||||||
|
user = insert(:user)
|
||||||
|
muted = insert(:user)
|
||||||
|
{:ok, user} = User.mute(user, muted)
|
||||||
|
|
||||||
|
{:ok, _activity} = TwitterAPI.create_status(muted, %{"status" => "hey @#{user.nickname}"})
|
||||||
|
|
||||||
|
assert length(Notification.for_user(user, %{with_muted: true})) == 1
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it returns notifications for blocked user and with_muted parameter" do
|
||||||
|
user = insert(:user)
|
||||||
|
blocked = insert(:user)
|
||||||
|
{:ok, user} = User.block(user, blocked)
|
||||||
|
|
||||||
|
{:ok, _activity} = TwitterAPI.create_status(blocked, %{"status" => "hey @#{user.nickname}"})
|
||||||
|
|
||||||
|
assert length(Notification.for_user(user, %{with_muted: true})) == 1
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it returns notificatitons for blocked domain and with_muted parameter" do
|
||||||
|
user = insert(:user)
|
||||||
|
blocked = insert(:user, ap_id: "http://some-domain.com")
|
||||||
|
{:ok, user} = User.block_domain(user, "some-domain.com")
|
||||||
|
|
||||||
|
{:ok, _activity} = TwitterAPI.create_status(blocked, %{"status" => "hey @#{user.nickname}"})
|
||||||
|
|
||||||
|
assert length(Notification.for_user(user, %{with_muted: true})) == 1
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it returns notifications for muted thread with_muted parameter" do
|
||||||
|
user = insert(:user)
|
||||||
|
another_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} =
|
||||||
|
TwitterAPI.create_status(another_user, %{"status" => "hey @#{user.nickname}"})
|
||||||
|
|
||||||
|
{:ok, _} = Pleroma.ThreadMute.add_mute(user.id, activity.data["context"])
|
||||||
|
assert length(Notification.for_user(user, %{with_muted: true})) == 1
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,6 +9,7 @@ defmodule Pleroma.Object.FetcherTest do
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Object.Fetcher
|
alias Pleroma.Object.Fetcher
|
||||||
import Tesla.Mock
|
import Tesla.Mock
|
||||||
|
import Mock
|
||||||
|
|
||||||
setup do
|
setup do
|
||||||
mock(fn
|
mock(fn
|
||||||
|
@ -26,16 +27,31 @@ defmodule Pleroma.Object.FetcherTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "actor origin containment" do
|
describe "actor origin containment" do
|
||||||
test "it rejects objects with a bogus origin" do
|
test_with_mock "it rejects objects with a bogus origin",
|
||||||
|
Pleroma.Web.OStatus,
|
||||||
|
[:passthrough],
|
||||||
|
[] do
|
||||||
{:error, _} = Fetcher.fetch_object_from_id("https://info.pleroma.site/activity.json")
|
{:error, _} = Fetcher.fetch_object_from_id("https://info.pleroma.site/activity.json")
|
||||||
|
|
||||||
|
refute called(Pleroma.Web.OStatus.fetch_activity_from_url(:_))
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it rejects objects when attributedTo is wrong (variant 1)" do
|
test_with_mock "it rejects objects when attributedTo is wrong (variant 1)",
|
||||||
|
Pleroma.Web.OStatus,
|
||||||
|
[:passthrough],
|
||||||
|
[] do
|
||||||
{:error, _} = Fetcher.fetch_object_from_id("https://info.pleroma.site/activity2.json")
|
{:error, _} = Fetcher.fetch_object_from_id("https://info.pleroma.site/activity2.json")
|
||||||
|
|
||||||
|
refute called(Pleroma.Web.OStatus.fetch_activity_from_url(:_))
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it rejects objects when attributedTo is wrong (variant 2)" do
|
test_with_mock "it rejects objects when attributedTo is wrong (variant 2)",
|
||||||
|
Pleroma.Web.OStatus,
|
||||||
|
[:passthrough],
|
||||||
|
[] do
|
||||||
{:error, _} = Fetcher.fetch_object_from_id("https://info.pleroma.site/activity3.json")
|
{:error, _} = Fetcher.fetch_object_from_id("https://info.pleroma.site/activity3.json")
|
||||||
|
|
||||||
|
refute called(Pleroma.Web.OStatus.fetch_activity_from_url(:_))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -10,12 +10,13 @@ defmodule Pleroma.Plugs.RateLimiterTest do
|
||||||
|
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
|
||||||
@limiter_name :testing
|
# Note: each example must work with separate buckets in order to prevent concurrency issues
|
||||||
|
|
||||||
test "init/1" do
|
test "init/1" do
|
||||||
Pleroma.Config.put([:rate_limit, @limiter_name], {1, 1})
|
limiter_name = :test_init
|
||||||
|
Pleroma.Config.put([:rate_limit, limiter_name], {1, 1})
|
||||||
|
|
||||||
assert {@limiter_name, {1, 1}} == RateLimiter.init(@limiter_name)
|
assert {limiter_name, {1, 1}, []} == RateLimiter.init(limiter_name)
|
||||||
assert nil == RateLimiter.init(:foo)
|
assert nil == RateLimiter.init(:foo)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -24,14 +25,15 @@ test "ip/1" do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it restricts by opts" do
|
test "it restricts by opts" do
|
||||||
|
limiter_name = :test_opts
|
||||||
scale = 1000
|
scale = 1000
|
||||||
limit = 5
|
limit = 5
|
||||||
|
|
||||||
Pleroma.Config.put([:rate_limit, @limiter_name], {scale, limit})
|
Pleroma.Config.put([:rate_limit, limiter_name], {scale, limit})
|
||||||
|
|
||||||
opts = RateLimiter.init(@limiter_name)
|
opts = RateLimiter.init(limiter_name)
|
||||||
conn = conn(:get, "/")
|
conn = conn(:get, "/")
|
||||||
bucket_name = "#{@limiter_name}:#{RateLimiter.ip(conn)}"
|
bucket_name = "#{limiter_name}:#{RateLimiter.ip(conn)}"
|
||||||
|
|
||||||
conn = RateLimiter.call(conn, opts)
|
conn = RateLimiter.call(conn, opts)
|
||||||
assert {1, 4, _, _, _} = ExRated.inspect_bucket(bucket_name, scale, limit)
|
assert {1, 4, _, _, _} = ExRated.inspect_bucket(bucket_name, scale, limit)
|
||||||
|
@ -65,18 +67,78 @@ test "it restricts by opts" do
|
||||||
refute conn.halted
|
refute conn.halted
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "`bucket_name` option overrides default bucket name" do
|
||||||
|
limiter_name = :test_bucket_name
|
||||||
|
scale = 1000
|
||||||
|
limit = 5
|
||||||
|
|
||||||
|
Pleroma.Config.put([:rate_limit, limiter_name], {scale, limit})
|
||||||
|
base_bucket_name = "#{limiter_name}:group1"
|
||||||
|
opts = RateLimiter.init({limiter_name, bucket_name: base_bucket_name})
|
||||||
|
|
||||||
|
conn = conn(:get, "/")
|
||||||
|
default_bucket_name = "#{limiter_name}:#{RateLimiter.ip(conn)}"
|
||||||
|
customized_bucket_name = "#{base_bucket_name}:#{RateLimiter.ip(conn)}"
|
||||||
|
|
||||||
|
RateLimiter.call(conn, opts)
|
||||||
|
assert {1, 4, _, _, _} = ExRated.inspect_bucket(customized_bucket_name, scale, limit)
|
||||||
|
assert {0, 5, _, _, _} = ExRated.inspect_bucket(default_bucket_name, scale, limit)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "`params` option appends specified params' values to bucket name" do
|
||||||
|
limiter_name = :test_params
|
||||||
|
scale = 1000
|
||||||
|
limit = 5
|
||||||
|
|
||||||
|
Pleroma.Config.put([:rate_limit, limiter_name], {scale, limit})
|
||||||
|
opts = RateLimiter.init({limiter_name, params: ["id"]})
|
||||||
|
id = "1"
|
||||||
|
|
||||||
|
conn = conn(:get, "/?id=#{id}")
|
||||||
|
conn = Plug.Conn.fetch_query_params(conn)
|
||||||
|
|
||||||
|
default_bucket_name = "#{limiter_name}:#{RateLimiter.ip(conn)}"
|
||||||
|
parametrized_bucket_name = "#{limiter_name}:#{id}:#{RateLimiter.ip(conn)}"
|
||||||
|
|
||||||
|
RateLimiter.call(conn, opts)
|
||||||
|
assert {1, 4, _, _, _} = ExRated.inspect_bucket(parametrized_bucket_name, scale, limit)
|
||||||
|
assert {0, 5, _, _, _} = ExRated.inspect_bucket(default_bucket_name, scale, limit)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it supports combination of options modifying bucket name" do
|
||||||
|
limiter_name = :test_options_combo
|
||||||
|
scale = 1000
|
||||||
|
limit = 5
|
||||||
|
|
||||||
|
Pleroma.Config.put([:rate_limit, limiter_name], {scale, limit})
|
||||||
|
base_bucket_name = "#{limiter_name}:group1"
|
||||||
|
opts = RateLimiter.init({limiter_name, bucket_name: base_bucket_name, params: ["id"]})
|
||||||
|
id = "100"
|
||||||
|
|
||||||
|
conn = conn(:get, "/?id=#{id}")
|
||||||
|
conn = Plug.Conn.fetch_query_params(conn)
|
||||||
|
|
||||||
|
default_bucket_name = "#{limiter_name}:#{RateLimiter.ip(conn)}"
|
||||||
|
parametrized_bucket_name = "#{base_bucket_name}:#{id}:#{RateLimiter.ip(conn)}"
|
||||||
|
|
||||||
|
RateLimiter.call(conn, opts)
|
||||||
|
assert {1, 4, _, _, _} = ExRated.inspect_bucket(parametrized_bucket_name, scale, limit)
|
||||||
|
assert {0, 5, _, _, _} = ExRated.inspect_bucket(default_bucket_name, scale, limit)
|
||||||
|
end
|
||||||
|
|
||||||
test "optional limits for authenticated users" do
|
test "optional limits for authenticated users" do
|
||||||
|
limiter_name = :test_authenticated
|
||||||
Ecto.Adapters.SQL.Sandbox.checkout(Pleroma.Repo)
|
Ecto.Adapters.SQL.Sandbox.checkout(Pleroma.Repo)
|
||||||
|
|
||||||
scale = 1000
|
scale = 1000
|
||||||
limit = 5
|
limit = 5
|
||||||
Pleroma.Config.put([:rate_limit, @limiter_name], [{1, 10}, {scale, limit}])
|
Pleroma.Config.put([:rate_limit, limiter_name], [{1, 10}, {scale, limit}])
|
||||||
|
|
||||||
opts = RateLimiter.init(@limiter_name)
|
opts = RateLimiter.init(limiter_name)
|
||||||
|
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
conn = conn(:get, "/") |> assign(:user, user)
|
conn = conn(:get, "/") |> assign(:user, user)
|
||||||
bucket_name = "#{@limiter_name}:#{user.id}"
|
bucket_name = "#{limiter_name}:#{user.id}"
|
||||||
|
|
||||||
conn = RateLimiter.call(conn, opts)
|
conn = RateLimiter.call(conn, opts)
|
||||||
assert {1, 4, _, _, _} = ExRated.inspect_bucket(bucket_name, scale, limit)
|
assert {1, 4, _, _, _} = ExRated.inspect_bucket(bucket_name, scale, limit)
|
||||||
|
|
|
@ -879,6 +879,42 @@ def get(
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get("https://info.pleroma.site/activity.json", _, _, Accept: "application/activity+json") do
|
||||||
|
{:ok,
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/tesla_mock/https__info.pleroma.site_activity.json")
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
|
||||||
|
def get("https://info.pleroma.site/activity.json", _, _, _) do
|
||||||
|
{:ok, %Tesla.Env{status: 404, body: ""}}
|
||||||
|
end
|
||||||
|
|
||||||
|
def get("https://info.pleroma.site/activity2.json", _, _, Accept: "application/activity+json") do
|
||||||
|
{:ok,
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/tesla_mock/https__info.pleroma.site_activity2.json")
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
|
||||||
|
def get("https://info.pleroma.site/activity2.json", _, _, _) do
|
||||||
|
{:ok, %Tesla.Env{status: 404, body: ""}}
|
||||||
|
end
|
||||||
|
|
||||||
|
def get("https://info.pleroma.site/activity3.json", _, _, Accept: "application/activity+json") do
|
||||||
|
{:ok,
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/tesla_mock/https__info.pleroma.site_activity3.json")
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
|
||||||
|
def get("https://info.pleroma.site/activity3.json", _, _, _) do
|
||||||
|
{:ok, %Tesla.Env{status: 404, body: ""}}
|
||||||
|
end
|
||||||
|
|
||||||
def get(url, query, body, headers) do
|
def get(url, query, body, headers) do
|
||||||
{:error,
|
{:error,
|
||||||
"Not implemented the mock response for get #{inspect(url)}, #{query}, #{inspect(body)}, #{
|
"Not implemented the mock response for get #{inspect(url)}, #{query}, #{inspect(body)}, #{
|
||||||
|
|
|
@ -687,10 +687,12 @@ test "it mutes people" do
|
||||||
muted_user = insert(:user)
|
muted_user = insert(:user)
|
||||||
|
|
||||||
refute User.mutes?(user, muted_user)
|
refute User.mutes?(user, muted_user)
|
||||||
|
refute User.muted_notifications?(user, muted_user)
|
||||||
|
|
||||||
{:ok, user} = User.mute(user, muted_user)
|
{:ok, user} = User.mute(user, muted_user)
|
||||||
|
|
||||||
assert User.mutes?(user, muted_user)
|
assert User.mutes?(user, muted_user)
|
||||||
|
assert User.muted_notifications?(user, muted_user)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it unmutes users" do
|
test "it unmutes users" do
|
||||||
|
@ -701,6 +703,20 @@ test "it unmutes users" do
|
||||||
{:ok, user} = User.unmute(user, muted_user)
|
{:ok, user} = User.unmute(user, muted_user)
|
||||||
|
|
||||||
refute User.mutes?(user, muted_user)
|
refute User.mutes?(user, muted_user)
|
||||||
|
refute User.muted_notifications?(user, muted_user)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it mutes user without notifications" do
|
||||||
|
user = insert(:user)
|
||||||
|
muted_user = insert(:user)
|
||||||
|
|
||||||
|
refute User.mutes?(user, muted_user)
|
||||||
|
refute User.muted_notifications?(user, muted_user)
|
||||||
|
|
||||||
|
{:ok, user} = User.mute(user, muted_user, false)
|
||||||
|
|
||||||
|
assert User.mutes?(user, muted_user)
|
||||||
|
refute User.muted_notifications?(user, muted_user)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
alias Pleroma.Web.ActivityPub.ObjectView
|
alias Pleroma.Web.ActivityPub.ObjectView
|
||||||
alias Pleroma.Web.ActivityPub.UserView
|
alias Pleroma.Web.ActivityPub.UserView
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
setup_all do
|
setup_all do
|
||||||
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
||||||
|
@ -551,7 +552,7 @@ test "it returns the followers in a collection", %{conn: conn} do
|
||||||
assert result["first"]["orderedItems"] == [user.ap_id]
|
assert result["first"]["orderedItems"] == [user.ap_id]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it returns returns empty if the user has 'hide_followers' set", %{conn: conn} do
|
test "it returns returns a uri if the user has 'hide_followers' set", %{conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
user_two = insert(:user, %{info: %{hide_followers: true}})
|
user_two = insert(:user, %{info: %{hide_followers: true}})
|
||||||
User.follow(user, user_two)
|
User.follow(user, user_two)
|
||||||
|
@ -561,8 +562,35 @@ test "it returns returns empty if the user has 'hide_followers' set", %{conn: co
|
||||||
|> get("/users/#{user_two.nickname}/followers")
|
|> get("/users/#{user_two.nickname}/followers")
|
||||||
|> json_response(200)
|
|> json_response(200)
|
||||||
|
|
||||||
assert result["first"]["orderedItems"] == []
|
assert is_binary(result["first"])
|
||||||
assert result["totalItems"] == 0
|
end
|
||||||
|
|
||||||
|
test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is not authenticated",
|
||||||
|
%{conn: conn} do
|
||||||
|
user = insert(:user, %{info: %{hide_followers: true}})
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> get("/users/#{user.nickname}/followers?page=1")
|
||||||
|
|
||||||
|
assert result.status == 403
|
||||||
|
assert result.resp_body == ""
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it renders the page, if the user has 'hide_followers' set and the request is authenticated with the same user",
|
||||||
|
%{conn: conn} do
|
||||||
|
user = insert(:user, %{info: %{hide_followers: true}})
|
||||||
|
other_user = insert(:user)
|
||||||
|
{:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/users/#{user.nickname}/followers?page=1")
|
||||||
|
|> json_response(200)
|
||||||
|
|
||||||
|
assert result["totalItems"] == 1
|
||||||
|
assert result["orderedItems"] == [other_user.ap_id]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it works for more than 10 users", %{conn: conn} do
|
test "it works for more than 10 users", %{conn: conn} do
|
||||||
|
@ -606,7 +634,7 @@ test "it returns the following in a collection", %{conn: conn} do
|
||||||
assert result["first"]["orderedItems"] == [user_two.ap_id]
|
assert result["first"]["orderedItems"] == [user_two.ap_id]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it returns returns empty if the user has 'hide_follows' set", %{conn: conn} do
|
test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do
|
||||||
user = insert(:user, %{info: %{hide_follows: true}})
|
user = insert(:user, %{info: %{hide_follows: true}})
|
||||||
user_two = insert(:user)
|
user_two = insert(:user)
|
||||||
User.follow(user, user_two)
|
User.follow(user, user_two)
|
||||||
|
@ -616,8 +644,35 @@ test "it returns returns empty if the user has 'hide_follows' set", %{conn: conn
|
||||||
|> get("/users/#{user.nickname}/following")
|
|> get("/users/#{user.nickname}/following")
|
||||||
|> json_response(200)
|
|> json_response(200)
|
||||||
|
|
||||||
assert result["first"]["orderedItems"] == []
|
assert is_binary(result["first"])
|
||||||
assert result["totalItems"] == 0
|
end
|
||||||
|
|
||||||
|
test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is not authenticated",
|
||||||
|
%{conn: conn} do
|
||||||
|
user = insert(:user, %{info: %{hide_follows: true}})
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> get("/users/#{user.nickname}/following?page=1")
|
||||||
|
|
||||||
|
assert result.status == 403
|
||||||
|
assert result.resp_body == ""
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it renders the page, if the user has 'hide_follows' set and the request is authenticated with the same user",
|
||||||
|
%{conn: conn} do
|
||||||
|
user = insert(:user, %{info: %{hide_follows: true}})
|
||||||
|
other_user = insert(:user)
|
||||||
|
{:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/users/#{user.nickname}/following?page=1")
|
||||||
|
|> json_response(200)
|
||||||
|
|
||||||
|
assert result["totalItems"] == 1
|
||||||
|
assert result["orderedItems"] == [other_user.ap_id]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it works for more than 10 users", %{conn: conn} do
|
test "it works for more than 10 users", %{conn: conn} do
|
||||||
|
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
|
||||||
|
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.UserView
|
alias Pleroma.Web.ActivityPub.UserView
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
test "Renders a user, including the public key" do
|
test "Renders a user, including the public key" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
@ -82,4 +83,28 @@ test "instance users do not expose oAuth endpoints" do
|
||||||
refute result["endpoints"]["oauthTokenEndpoint"]
|
refute result["endpoints"]["oauthTokenEndpoint"]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "followers" do
|
||||||
|
test "sets totalItems to zero when followers are hidden" do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
{:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
|
||||||
|
assert %{"totalItems" => 1} = UserView.render("followers.json", %{user: user})
|
||||||
|
info = Map.put(user.info, :hide_followers, true)
|
||||||
|
user = Map.put(user, :info, info)
|
||||||
|
assert %{"totalItems" => 0} = UserView.render("followers.json", %{user: user})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "following" do
|
||||||
|
test "sets totalItems to zero when follows are hidden" do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
{:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
|
||||||
|
assert %{"totalItems" => 1} = UserView.render("following.json", %{user: user})
|
||||||
|
info = Map.put(user.info, :hide_follows, true)
|
||||||
|
user = Map.put(user, :info, info)
|
||||||
|
assert %{"totalItems" => 0} = UserView.render("following.json", %{user: user})
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -593,7 +593,7 @@ test "user avatar can be set", %{conn: conn} do
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|> assign(:user, user)
|
|> assign(:user, user)
|
||||||
|> patch("/api/v1/accounts/update_avatar", %{img: avatar_image})
|
|> patch("/api/v1/pleroma/accounts/update_avatar", %{img: avatar_image})
|
||||||
|
|
||||||
user = refresh_record(user)
|
user = refresh_record(user)
|
||||||
|
|
||||||
|
@ -618,7 +618,7 @@ test "user avatar can be reset", %{conn: conn} do
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|> assign(:user, user)
|
|> assign(:user, user)
|
||||||
|> patch("/api/v1/accounts/update_avatar", %{img: ""})
|
|> patch("/api/v1/pleroma/accounts/update_avatar", %{img: ""})
|
||||||
|
|
||||||
user = User.get_cached_by_id(user.id)
|
user = User.get_cached_by_id(user.id)
|
||||||
|
|
||||||
|
@ -633,7 +633,7 @@ test "can set profile banner", %{conn: conn} do
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|> assign(:user, user)
|
|> assign(:user, user)
|
||||||
|> patch("/api/v1/accounts/update_banner", %{"banner" => @image})
|
|> patch("/api/v1/pleroma/accounts/update_banner", %{"banner" => @image})
|
||||||
|
|
||||||
user = refresh_record(user)
|
user = refresh_record(user)
|
||||||
assert user.info.banner["type"] == "Image"
|
assert user.info.banner["type"] == "Image"
|
||||||
|
@ -647,7 +647,7 @@ test "can reset profile banner", %{conn: conn} do
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|> assign(:user, user)
|
|> assign(:user, user)
|
||||||
|> patch("/api/v1/accounts/update_banner", %{"banner" => ""})
|
|> patch("/api/v1/pleroma/accounts/update_banner", %{"banner" => ""})
|
||||||
|
|
||||||
user = refresh_record(user)
|
user = refresh_record(user)
|
||||||
assert user.info.banner == %{}
|
assert user.info.banner == %{}
|
||||||
|
@ -661,7 +661,7 @@ test "background image can be set", %{conn: conn} do
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|> assign(:user, user)
|
|> assign(:user, user)
|
||||||
|> patch("/api/v1/accounts/update_background", %{"img" => @image})
|
|> patch("/api/v1/pleroma/accounts/update_background", %{"img" => @image})
|
||||||
|
|
||||||
user = refresh_record(user)
|
user = refresh_record(user)
|
||||||
assert user.info.background["type"] == "Image"
|
assert user.info.background["type"] == "Image"
|
||||||
|
@ -674,7 +674,7 @@ test "background image can be reset", %{conn: conn} do
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|> assign(:user, user)
|
|> assign(:user, user)
|
||||||
|> patch("/api/v1/accounts/update_background", %{"img" => ""})
|
|> patch("/api/v1/pleroma/accounts/update_background", %{"img" => ""})
|
||||||
|
|
||||||
user = refresh_record(user)
|
user = refresh_record(user)
|
||||||
assert user.info.background == %{}
|
assert user.info.background == %{}
|
||||||
|
@ -1274,6 +1274,71 @@ test "destroy multiple", %{conn: conn} do
|
||||||
result = json_response(conn_res, 200)
|
result = json_response(conn_res, 200)
|
||||||
assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
|
assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "doesn't see notifications after muting user with notifications", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
user2 = insert(:user)
|
||||||
|
|
||||||
|
{:ok, _, _, _} = CommonAPI.follow(user, user2)
|
||||||
|
{:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"})
|
||||||
|
|
||||||
|
conn = assign(conn, :user, user)
|
||||||
|
|
||||||
|
conn = get(conn, "/api/v1/notifications")
|
||||||
|
|
||||||
|
assert length(json_response(conn, 200)) == 1
|
||||||
|
|
||||||
|
{:ok, user} = User.mute(user, user2)
|
||||||
|
|
||||||
|
conn = assign(build_conn(), :user, user)
|
||||||
|
conn = get(conn, "/api/v1/notifications")
|
||||||
|
|
||||||
|
assert json_response(conn, 200) == []
|
||||||
|
end
|
||||||
|
|
||||||
|
test "see notifications after muting user without notifications", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
user2 = insert(:user)
|
||||||
|
|
||||||
|
{:ok, _, _, _} = CommonAPI.follow(user, user2)
|
||||||
|
{:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"})
|
||||||
|
|
||||||
|
conn = assign(conn, :user, user)
|
||||||
|
|
||||||
|
conn = get(conn, "/api/v1/notifications")
|
||||||
|
|
||||||
|
assert length(json_response(conn, 200)) == 1
|
||||||
|
|
||||||
|
{:ok, user} = User.mute(user, user2, false)
|
||||||
|
|
||||||
|
conn = assign(build_conn(), :user, user)
|
||||||
|
conn = get(conn, "/api/v1/notifications")
|
||||||
|
|
||||||
|
assert length(json_response(conn, 200)) == 1
|
||||||
|
end
|
||||||
|
|
||||||
|
test "see notifications after muting user with notifications and with_muted parameter", %{
|
||||||
|
conn: conn
|
||||||
|
} do
|
||||||
|
user = insert(:user)
|
||||||
|
user2 = insert(:user)
|
||||||
|
|
||||||
|
{:ok, _, _, _} = CommonAPI.follow(user, user2)
|
||||||
|
{:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"})
|
||||||
|
|
||||||
|
conn = assign(conn, :user, user)
|
||||||
|
|
||||||
|
conn = get(conn, "/api/v1/notifications")
|
||||||
|
|
||||||
|
assert length(json_response(conn, 200)) == 1
|
||||||
|
|
||||||
|
{:ok, user} = User.mute(user, user2)
|
||||||
|
|
||||||
|
conn = assign(build_conn(), :user, user)
|
||||||
|
conn = get(conn, "/api/v1/notifications", %{"with_muted" => "true"})
|
||||||
|
|
||||||
|
assert length(json_response(conn, 200)) == 1
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "reblogging" do
|
describe "reblogging" do
|
||||||
|
@ -2105,25 +2170,52 @@ test "following / unfollowing errors" do
|
||||||
assert %{"error" => "Record not found"} = json_response(conn_res, 404)
|
assert %{"error" => "Record not found"} = json_response(conn_res, 404)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "muting / unmuting a user", %{conn: conn} do
|
describe "mute/unmute" do
|
||||||
user = insert(:user)
|
test "with notifications", %{conn: conn} do
|
||||||
other_user = insert(:user)
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|> assign(:user, user)
|
|> assign(:user, user)
|
||||||
|> post("/api/v1/accounts/#{other_user.id}/mute")
|
|> post("/api/v1/accounts/#{other_user.id}/mute")
|
||||||
|
|
||||||
assert %{"id" => _id, "muting" => true} = json_response(conn, 200)
|
response = json_response(conn, 200)
|
||||||
|
|
||||||
user = User.get_cached_by_id(user.id)
|
assert %{"id" => _id, "muting" => true, "muting_notifications" => true} = response
|
||||||
|
user = User.get_cached_by_id(user.id)
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
build_conn()
|
build_conn()
|
||||||
|> assign(:user, user)
|
|> assign(:user, user)
|
||||||
|> post("/api/v1/accounts/#{other_user.id}/unmute")
|
|> post("/api/v1/accounts/#{other_user.id}/unmute")
|
||||||
|
|
||||||
assert %{"id" => _id, "muting" => false} = json_response(conn, 200)
|
response = json_response(conn, 200)
|
||||||
|
assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response
|
||||||
|
end
|
||||||
|
|
||||||
|
test "without notifications", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/accounts/#{other_user.id}/mute", %{"notifications" => "false"})
|
||||||
|
|
||||||
|
response = json_response(conn, 200)
|
||||||
|
|
||||||
|
assert %{"id" => _id, "muting" => true, "muting_notifications" => false} = response
|
||||||
|
user = User.get_cached_by_id(user.id)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/accounts/#{other_user.id}/unmute")
|
||||||
|
|
||||||
|
response = json_response(conn, 200)
|
||||||
|
assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "subscribing / unsubscribing to a user", %{conn: conn} do
|
test "subscribing / unsubscribing to a user", %{conn: conn} do
|
||||||
|
|
33
test/web/metadata/player_view_test.exs
Normal file
33
test/web/metadata/player_view_test.exs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
# 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.PlayerViewTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
alias Pleroma.Web.Metadata.PlayerView
|
||||||
|
|
||||||
|
test "it renders audio tag" do
|
||||||
|
res =
|
||||||
|
PlayerView.render(
|
||||||
|
"player.html",
|
||||||
|
%{"mediaType" => "audio", "href" => "test-href"}
|
||||||
|
)
|
||||||
|
|> Phoenix.HTML.safe_to_string()
|
||||||
|
|
||||||
|
assert res ==
|
||||||
|
"<audio controls><source src=\"test-href\" type=\"audio\">Your browser does not support audio playback.</audio>"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it renders videos tag" do
|
||||||
|
res =
|
||||||
|
PlayerView.render(
|
||||||
|
"player.html",
|
||||||
|
%{"mediaType" => "video", "href" => "test-href"}
|
||||||
|
)
|
||||||
|
|> Phoenix.HTML.safe_to_string()
|
||||||
|
|
||||||
|
assert res ==
|
||||||
|
"<video controls loop><source src=\"test-href\" type=\"video\">Your browser does not support video playback.</video>"
|
||||||
|
end
|
||||||
|
end
|
123
test/web/metadata/twitter_card_test.exs
Normal file
123
test/web/metadata/twitter_card_test.exs
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.Metadata.Providers.TwitterCardTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
alias Pleroma.Web.Endpoint
|
||||||
|
alias Pleroma.Web.Metadata.Providers.TwitterCard
|
||||||
|
alias Pleroma.Web.Metadata.Utils
|
||||||
|
alias Pleroma.Web.Router
|
||||||
|
|
||||||
|
test "it renders twitter card for user info" do
|
||||||
|
user = insert(:user, name: "Jimmy Hendriks", bio: "born 19 March 1994")
|
||||||
|
avatar_url = Utils.attachment_url(User.avatar_url(user))
|
||||||
|
res = TwitterCard.build_tags(%{user: user})
|
||||||
|
|
||||||
|
assert res == [
|
||||||
|
{:meta, [property: "twitter:title", content: Utils.user_name_string(user)], []},
|
||||||
|
{:meta, [property: "twitter:description", content: "born 19 March 1994"], []},
|
||||||
|
{:meta, [property: "twitter:image", content: avatar_url], []},
|
||||||
|
{:meta, [property: "twitter:card", content: "summary"], []}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it does not render attachments if post is nsfw" do
|
||||||
|
Pleroma.Config.put([Pleroma.Web.Metadata, :unfurl_nsfw], false)
|
||||||
|
user = insert(:user, name: "Jimmy Hendriks", bio: "born 19 March 1994")
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "HI"})
|
||||||
|
|
||||||
|
note =
|
||||||
|
insert(:note, %{
|
||||||
|
data: %{
|
||||||
|
"actor" => user.ap_id,
|
||||||
|
"tag" => [],
|
||||||
|
"id" => "https://pleroma.gov/objects/whatever",
|
||||||
|
"content" => "pleroma in a nutshell",
|
||||||
|
"sensitive" => true,
|
||||||
|
"attachment" => [
|
||||||
|
%{
|
||||||
|
"url" => [%{"mediaType" => "image/png", "href" => "https://pleroma.gov/tenshi.png"}]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
"url" => [
|
||||||
|
%{
|
||||||
|
"mediaType" => "application/octet-stream",
|
||||||
|
"href" => "https://pleroma.gov/fqa/badapple.sfc"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
"url" => [
|
||||||
|
%{"mediaType" => "video/webm", "href" => "https://pleroma.gov/about/juche.webm"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
result = TwitterCard.build_tags(%{object: note, user: user, activity_id: activity.id})
|
||||||
|
|
||||||
|
assert [
|
||||||
|
{:meta, [property: "twitter:title", content: Utils.user_name_string(user)], []},
|
||||||
|
{:meta, [property: "twitter:description", content: "“pleroma in a nutshell”"], []},
|
||||||
|
{:meta, [property: "twitter:image", content: "http://localhost:4001/images/avi.png"],
|
||||||
|
[]},
|
||||||
|
{:meta, [property: "twitter:card", content: "summary_large_image"], []}
|
||||||
|
] == result
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it renders supported types of attachments and skips unknown types" do
|
||||||
|
user = insert(:user, name: "Jimmy Hendriks", bio: "born 19 March 1994")
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "HI"})
|
||||||
|
|
||||||
|
note =
|
||||||
|
insert(:note, %{
|
||||||
|
data: %{
|
||||||
|
"actor" => user.ap_id,
|
||||||
|
"tag" => [],
|
||||||
|
"id" => "https://pleroma.gov/objects/whatever",
|
||||||
|
"content" => "pleroma in a nutshell",
|
||||||
|
"attachment" => [
|
||||||
|
%{
|
||||||
|
"url" => [%{"mediaType" => "image/png", "href" => "https://pleroma.gov/tenshi.png"}]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
"url" => [
|
||||||
|
%{
|
||||||
|
"mediaType" => "application/octet-stream",
|
||||||
|
"href" => "https://pleroma.gov/fqa/badapple.sfc"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
"url" => [
|
||||||
|
%{"mediaType" => "video/webm", "href" => "https://pleroma.gov/about/juche.webm"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
result = TwitterCard.build_tags(%{object: note, user: user, activity_id: activity.id})
|
||||||
|
|
||||||
|
assert [
|
||||||
|
{:meta, [property: "twitter:title", content: Utils.user_name_string(user)], []},
|
||||||
|
{:meta, [property: "twitter:description", content: "“pleroma in a nutshell”"], []},
|
||||||
|
{:meta, [property: "twitter:card", content: "summary_large_image"], []},
|
||||||
|
{:meta, [property: "twitter:player", content: "https://pleroma.gov/tenshi.png"], []},
|
||||||
|
{:meta, [property: "twitter:card", content: "player"], []},
|
||||||
|
{:meta,
|
||||||
|
[
|
||||||
|
property: "twitter:player",
|
||||||
|
content: Router.Helpers.o_status_url(Endpoint, :notice_player, activity.id)
|
||||||
|
], []},
|
||||||
|
{:meta, [property: "twitter:player:width", content: "480"], []},
|
||||||
|
{:meta, [property: "twitter:player:height", content: "480"], []}
|
||||||
|
] == result
|
||||||
|
end
|
||||||
|
end
|
|
@ -83,4 +83,47 @@ test "it returns the safe_dm_mentions feature if enabled", %{conn: conn} do
|
||||||
|
|
||||||
Pleroma.Config.put([:instance, :safe_dm_mentions], option)
|
Pleroma.Config.put([:instance, :safe_dm_mentions], option)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it shows MRF transparency data if enabled", %{conn: conn} do
|
||||||
|
option = Pleroma.Config.get([:instance, :mrf_transparency])
|
||||||
|
Pleroma.Config.put([:instance, :mrf_transparency], true)
|
||||||
|
|
||||||
|
simple_config = %{"reject" => ["example.com"]}
|
||||||
|
Pleroma.Config.put(:mrf_simple, simple_config)
|
||||||
|
|
||||||
|
response =
|
||||||
|
conn
|
||||||
|
|> get("/nodeinfo/2.1.json")
|
||||||
|
|> json_response(:ok)
|
||||||
|
|
||||||
|
assert response["metadata"]["federation"]["mrf_simple"] == simple_config
|
||||||
|
|
||||||
|
Pleroma.Config.put([:instance, :mrf_transparency], option)
|
||||||
|
Pleroma.Config.put(:mrf_simple, %{})
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it performs exclusions from MRF transparency data if configured", %{conn: conn} do
|
||||||
|
option = Pleroma.Config.get([:instance, :mrf_transparency])
|
||||||
|
Pleroma.Config.put([:instance, :mrf_transparency], true)
|
||||||
|
|
||||||
|
exclusions = Pleroma.Config.get([:instance, :mrf_transparency_exclusions])
|
||||||
|
Pleroma.Config.put([:instance, :mrf_transparency_exclusions], ["other.site"])
|
||||||
|
|
||||||
|
simple_config = %{"reject" => ["example.com", "other.site"]}
|
||||||
|
expected_config = %{"reject" => ["example.com"]}
|
||||||
|
|
||||||
|
Pleroma.Config.put(:mrf_simple, simple_config)
|
||||||
|
|
||||||
|
response =
|
||||||
|
conn
|
||||||
|
|> get("/nodeinfo/2.1.json")
|
||||||
|
|> json_response(:ok)
|
||||||
|
|
||||||
|
assert response["metadata"]["federation"]["mrf_simple"] == expected_config
|
||||||
|
assert response["metadata"]["federation"]["exclusions"] == true
|
||||||
|
|
||||||
|
Pleroma.Config.put([:instance, :mrf_transparency], option)
|
||||||
|
Pleroma.Config.put([:instance, :mrf_transparency_exclusions], exclusions)
|
||||||
|
Pleroma.Config.put(:mrf_simple, %{})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -521,6 +521,38 @@ test "with credentials", %{conn: conn, user: current_user} do
|
||||||
for: current_user
|
for: current_user
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "muted user", %{conn: conn, user: current_user} do
|
||||||
|
other_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, current_user} = User.mute(current_user, other_user)
|
||||||
|
|
||||||
|
{:ok, _activity} =
|
||||||
|
ActivityBuilder.insert(%{"to" => [current_user.ap_id]}, %{user: other_user})
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> with_credentials(current_user.nickname, "test")
|
||||||
|
|> get("/api/qvitter/statuses/notifications.json")
|
||||||
|
|
||||||
|
assert json_response(conn, 200) == []
|
||||||
|
end
|
||||||
|
|
||||||
|
test "muted user with with_muted parameter", %{conn: conn, user: current_user} do
|
||||||
|
other_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, current_user} = User.mute(current_user, other_user)
|
||||||
|
|
||||||
|
{:ok, _activity} =
|
||||||
|
ActivityBuilder.insert(%{"to" => [current_user.ap_id]}, %{user: other_user})
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> with_credentials(current_user.nickname, "test")
|
||||||
|
|> get("/api/qvitter/statuses/notifications.json", %{"with_muted" => "true"})
|
||||||
|
|
||||||
|
assert length(json_response(conn, 200)) == 1
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "POST /api/qvitter/statuses/notifications/read" do
|
describe "POST /api/qvitter/statuses/notifications/read" do
|
||||||
|
|
Loading…
Reference in a new issue