diff --git a/CC-BY-4.0 b/CC-BY-4.0 new file mode 100644 index 000000000..4ea99c213 --- /dev/null +++ b/CC-BY-4.0 @@ -0,0 +1,395 @@ +Attribution 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 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 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. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. 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. + + d. 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. + + e. 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. + + f. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + g. 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. + + h. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + 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; and + + b. produce, reproduce, and Share Adapted Material. + + 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. + + +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 (including in modified + form), 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. + + 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. + + 4. If You Share Adapted Material You produce, the Adapter's + License You apply must not prevent recipients of the Adapted + Material from complying with this Public License. + + +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; + + 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. diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f85cc302..e61b1d144 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,32 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## Unreleased + +### Added +- Experimental websocket-based federation between Pleroma instances. + +### Changed + +- Renamed `:await_up_timeout` in `:connections_pool` namespace to `:connect_timeout`, old name is deprecated. +- Renamed `:timeout` in `pools` namespace to `:recv_timeout`, old name is deprecated. +- The `discoverable` field in the `User` struct will now add a NOINDEX metatag to profile pages when false. +- Users with the `discoverable` field set to false will not show up in searches. +- Minimum lifetime for ephmeral activities changed to 10 minutes and made configurable (`:min_lifetime` option). + +### Added +- Media preview proxy (requires media proxy be enabled; see `:media_preview_proxy` config for more details). +- Pleroma API: Importing the mutes users from CSV files. + +### Removed + +- **Breaking:** `Pleroma.Workers.Cron.StatsWorker` setting from Oban `:crontab` (moved to a simpler implementation). +- **Breaking:** `Pleroma.Workers.Cron.ClearOauthTokenWorker` setting from Oban `:crontab` (moved to scheduled jobs). +- **Breaking:** `Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker` setting from Oban `:crontab` (moved to scheduled jobs). +- Removed `:managed_config` option. In practice, it was accidentally removed with 2.0.0 release when frontends were +switched to a new configuration mechanism, however it was not officially removed until now. + + ## [2.1.2] - 2020-09-17 ### Security @@ -40,6 +66,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Added - Rich media failure tracking (along with `:failure_backoff` option). +
+ Admin API Changes + +- Add `PATCH /api/pleroma/admin/instance_document/:document_name` to modify the Terms of Service and Instance Panel HTML pages via Admin API +
+ ### Fixed - Default HTTP adapter not respecting pool setting, leading to possible OOM. - Fixed uploading webp images when the Exiftool Upload Filter is enabled by skipping them diff --git a/Dockerfile b/Dockerfile index aa50e27ec..c210cf79c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,7 +31,7 @@ LABEL maintainer="ops@pleroma.social" \ ARG HOME=/opt/pleroma ARG DATA=/var/lib/pleroma -RUN echo "http://nl.alpinelinux.org/alpine/latest-stable/community" >> /etc/apk/repositories &&\ +RUN echo "https://nl.alpinelinux.org/alpine/latest-stable/community" >> /etc/apk/repositories &&\ apk update &&\ apk add exiftool imagemagick ncurses postgresql-client &&\ adduser --system --shell /bin/false --home ${HOME} pleroma &&\ diff --git a/README.md b/README.md index 6ca3118fb..7a05b9e48 100644 --- a/README.md +++ b/README.md @@ -18,15 +18,16 @@ If you are running Linux (glibc or musl) on x86/arm, the recommended way to inst ### From Source If your platform is not supported, or you just want to be able to edit the source code easily, you may install Pleroma from source. -- [Debian-based](https://docs-develop.pleroma.social/backend/installation/debian_based_en/) -- [Debian-based (jp)](https://docs-develop.pleroma.social/backend/installation/debian_based_jp/) - [Alpine Linux](https://docs-develop.pleroma.social/backend/installation/alpine_linux_en/) - [Arch Linux](https://docs-develop.pleroma.social/backend/installation/arch_linux_en/) +- [CentOS 7](https://docs-develop.pleroma.social/backend/installation/centos7_en/) +- [Debian-based](https://docs-develop.pleroma.social/backend/installation/debian_based_en/) +- [Debian-based (jp)](https://docs-develop.pleroma.social/backend/installation/debian_based_jp/) +- [FreeBSD](https://docs-develop.pleroma.social/backend/installation/freebsd_en/) - [Gentoo Linux](https://docs-develop.pleroma.social/backend/installation/gentoo_en/) - [NetBSD](https://docs-develop.pleroma.social/backend/installation/netbsd_en/) - [OpenBSD](https://docs-develop.pleroma.social/backend/installation/openbsd_en/) - [OpenBSD (fi)](https://docs-develop.pleroma.social/backend/installation/openbsd_fi/) -- [CentOS 7](https://docs-develop.pleroma.social/backend/installation/centos7_en/) ### OS/Distro packages Currently Pleroma is not packaged by any OS/Distros, but if you want to package it for one, we can guide you through the process on our [community channels](#community-channels). If you want to change default options in your Pleroma package, please **discuss it with us first**. diff --git a/SECURITY.md b/SECURITY.md index c212a2505..8617c1434 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,7 +6,7 @@ Currently, Pleroma offers bugfixes and security patches only for the latest mino | Version | Support |---------| -------- -| 2.0 | Bugfixes and security patches +| 2.1 | Bugfixes and security patches ## Reporting a vulnerability diff --git a/config/config.exs b/config/config.exs index 88f6125e5..00624bf00 100644 --- a/config/config.exs +++ b/config/config.exs @@ -130,6 +130,7 @@ dispatch: [ {:_, [ + {"/api/fedsocket/v1", Pleroma.Web.FedSockets.IncomingHandler, []}, {"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []}, {"/websocket", Phoenix.Endpoint.CowboyWebSocket, {Phoenix.Transports.WebSocket, @@ -148,6 +149,16 @@ "SameSite=Lax" ] +config :pleroma, :fed_sockets, + enabled: false, + connection_duration: :timer.hours(8), + rejection_duration: :timer.minutes(15), + fed_socket_fetches: [ + default: 12_000, + interval: 3_000, + lazy: false + ] + # Configures Elixir's Logger config :logger, :console, level: :debug, @@ -216,7 +227,6 @@ allow_relay: true, public: true, quarantined_instances: [], - managed_config: true, static_dir: "instance/static/", allowed_post_formats: [ "text/plain", @@ -424,6 +434,8 @@ proxy_opts: [ redirect_on_failure: false, max_body_length: 25 * 1_048_576, + # Note: max_read_duration defaults to Pleroma.ReverseProxy.max_read_duration_default/1 + max_read_duration: 30_000, http: [ follow_redirect: true, pool: :media @@ -438,6 +450,14 @@ config :pleroma, Pleroma.Web.MediaProxy.Invalidation.Script, script_path: nil +# Note: media preview proxy depends on media proxy to be enabled +config :pleroma, :media_preview_proxy, + enabled: false, + thumbnail_max_width: 600, + thumbnail_max_height: 600, + image_quality: 85, + min_content_length: 100 * 1024 + config :pleroma, :chat, enabled: true config :phoenix, :format_encoders, json: Jason @@ -530,8 +550,10 @@ log: false, queues: [ activity_expiration: 10, + token_expiration: 5, federator_incoming: 50, federator_outgoing: 50, + ingestion_queue: 50, web_push: 50, mailer: 10, transmogrifier: 20, @@ -543,9 +565,6 @@ ], plugins: [Oban.Plugins.Pruner], crontab: [ - {"0 0 * * *", Pleroma.Workers.Cron.ClearOauthTokenWorker}, - {"0 * * * *", Pleroma.Workers.Cron.StatsWorker}, - {"* * * * *", Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker}, {"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker}, {"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker} ] @@ -656,7 +675,7 @@ account_confirmation_resend: {8_640_000, 5}, ap_routes: {60_000, 15} -config :pleroma, Pleroma.ActivityExpiration, enabled: true +config :pleroma, Pleroma.Workers.PurgeExpiredActivity, enabled: true, min_lifetime: 600 config :pleroma, Pleroma.Plugs.RemoteIp, enabled: true @@ -670,7 +689,7 @@ # With no frontend configuration, the bundled files from the `static` directory will # be used. # -# config :pleroma, :frontends, +# config :pleroma, :frontends, # primary: %{"name" => "pleroma-fe", "ref" => "develop"}, # admin: %{"name" => "admin-fe", "ref" => "stable"}, # available: %{...} @@ -734,28 +753,28 @@ max_connections: 250, max_idle_time: 30_000, retry: 0, - await_up_timeout: 5_000 + connect_timeout: 5_000 config :pleroma, :pools, federation: [ size: 50, max_waiting: 10, - timeout: 10_000 + recv_timeout: 10_000 ], media: [ size: 50, - max_waiting: 10, - timeout: 10_000 + max_waiting: 20, + recv_timeout: 15_000 ], upload: [ size: 25, max_waiting: 5, - timeout: 15_000 + recv_timeout: 15_000 ], default: [ size: 10, max_waiting: 2, - timeout: 5_000 + recv_timeout: 5_000 ] config :pleroma, :hackney_pools, @@ -790,6 +809,8 @@ config :ex_aws, http_client: Pleroma.HTTP.ExAws +config :web_push_encryption, http_client: Pleroma.HTTP + config :pleroma, :instances_favicons, enabled: false config :floki, :html_parser, Floki.HTMLParser.FastHtml diff --git a/config/description.exs b/config/description.exs index 5e08ba109..ac3dfbb2b 100644 --- a/config/description.exs +++ b/config/description.exs @@ -270,6 +270,19 @@ } ] }, + %{ + group: :pleroma, + key: :fed_sockets, + type: :group, + description: "Websocket based federation", + children: [ + %{ + key: :enabled, + type: :boolean, + description: "Enable FedSockets" + } + ] + }, %{ group: :pleroma, key: Pleroma.Emails.Mailer, @@ -764,12 +777,6 @@ "*.quarantined.com" ] }, - %{ - key: :managed_config, - type: :boolean, - description: - "Whenether the config for pleroma-fe is configured in this config or in static/config.json" - }, %{ key: :static_dir, type: :string, @@ -1880,6 +1887,7 @@ suggestions: [ redirect_on_failure: false, max_body_length: 25 * 1_048_576, + max_read_duration: 30_000, http: [ follow_redirect: true, pool: :media @@ -1900,6 +1908,11 @@ "Limits the content length to be approximately the " <> "specified length. It is validated with the `content-length` header and also verified when proxying." }, + %{ + key: :max_read_duration, + type: :integer, + description: "Timeout (in milliseconds) of GET request to remote URI." + }, %{ key: :http, label: "HTTP", @@ -1946,6 +1959,43 @@ } ] }, + %{ + group: :pleroma, + key: :media_preview_proxy, + type: :group, + description: "Media preview proxy", + children: [ + %{ + key: :enabled, + type: :boolean, + description: + "Enables proxying of remote media preview to the instance's proxy. Requires enabled media proxy." + }, + %{ + key: :thumbnail_max_width, + type: :integer, + description: + "Max width of preview thumbnail for images (video preview always has original dimensions)." + }, + %{ + key: :thumbnail_max_height, + type: :integer, + description: + "Max height of preview thumbnail for images (video preview always has original dimensions)." + }, + %{ + key: :image_quality, + type: :integer, + description: "Quality of the output. Ranges from 0 (min quality) to 100 (max quality)." + }, + %{ + key: :min_content_length, + type: :integer, + description: + "Min content length to perform preview, in bytes. If greater than 0, media smaller in size will be served as is, without thumbnailing." + } + ] + }, %{ group: :pleroma, key: Pleroma.Web.MediaProxy.Invalidation.Http, @@ -2290,9 +2340,6 @@ type: {:list, :tuple}, description: "Settings for cron background jobs", suggestions: [ - {"0 0 * * *", Pleroma.Workers.Cron.ClearOauthTokenWorker}, - {"0 * * * *", Pleroma.Workers.Cron.StatsWorker}, - {"* * * * *", Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker}, {"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker}, {"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker} ] @@ -2398,7 +2445,7 @@ %{ group: :pleroma, key: Pleroma.Formatter, - label: "Auto Linker", + label: "Linkify", type: :group, description: "Configuration for Pleroma's link formatter which parses mentions, hashtags, and URLs.", @@ -2475,14 +2522,20 @@ }, %{ group: :pleroma, - key: Pleroma.ActivityExpiration, + key: Pleroma.Workers.PurgeExpiredActivity, type: :group, - description: "Expired activity settings", + description: "Expired activities settings", children: [ %{ key: :enabled, type: :boolean, - description: "Whether expired activities will be sent to the job queue to be deleted" + description: "Enables expired activities addition & deletion" + }, + %{ + key: :min_lifetime, + type: :integer, + description: "Minimum lifetime for ephemeral activity (in seconds)", + suggestions: [600] } ] }, @@ -3378,7 +3431,7 @@ suggestions: [250] }, %{ - key: :await_up_timeout, + key: :connect_timeout, type: :integer, description: "Timeout while `gun` will wait until connection is up. Default: 5000ms.", suggestions: [5000] @@ -3416,6 +3469,12 @@ description: "Maximum number of requests waiting for other requests to finish. After this number is reached, the pool will start returning errrors when a new request is made", suggestions: [10] + }, + %{ + key: :recv_timeout, + type: :integer, + description: "Timeout for the pool while gun will wait for response", + suggestions: [10_000] } ] } diff --git a/config/test.exs b/config/test.exs index e9c2273e8..93a0e2a61 100644 --- a/config/test.exs +++ b/config/test.exs @@ -19,6 +19,11 @@ level: :warn, format: "\n[$level] $message\n" +config :pleroma, :fed_sockets, + enabled: false, + connection_duration: 5, + rejection_duration: 5 + config :pleroma, :auth, oauth_consumer_strategies: [] config :pleroma, Pleroma.Upload, @@ -114,8 +119,6 @@ config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: true -config :pleroma, :instances_favicons, enabled: false - config :pleroma, Pleroma.Uploaders.S3, bucket: nil, streaming_enabled: true, diff --git a/docs/API/admin_api.md b/docs/API/admin_api.md index c0ea074f0..7992db58f 100644 --- a/docs/API/admin_api.md +++ b/docs/API/admin_api.md @@ -1334,3 +1334,166 @@ Loads json generated from `config/descriptions.exs`. { } ``` + +## GET /api/pleroma/admin/users/:nickname/chats + +### List a user's chats + +- Params: None + +- Response: + +```json +[ + { + "sender": { + "id": "someflakeid", + "username": "somenick", + ... + }, + "receiver": { + "id": "someflakeid", + "username": "somenick", + ... + }, + "id" : "1", + "unread" : 2, + "last_message" : {...}, // The last message in that chat + "updated_at": "2020-04-21T15:11:46.000Z" + } +] +``` + +## GET /api/pleroma/admin/chats/:chat_id + +### View a single chat + +- Params: None + +- Response: + +```json +{ + "sender": { + "id": "someflakeid", + "username": "somenick", + ... + }, + "receiver": { + "id": "someflakeid", + "username": "somenick", + ... + }, + "id" : "1", + "unread" : 2, + "last_message" : {...}, // The last message in that chat + "updated_at": "2020-04-21T15:11:46.000Z" +} +``` + +## GET /api/pleroma/admin/chats/:chat_id/messages + +### List the messages in a chat + +- Params: `max_id`, `min_id` + +- Response: + +```json +[ + { + "account_id": "someflakeid", + "chat_id": "1", + "content": "Check this out :firefox:", + "created_at": "2020-04-21T15:11:46.000Z", + "emojis": [ + { + "shortcode": "firefox", + "static_url": "https://dontbulling.me/emoji/Firefox.gif", + "url": "https://dontbulling.me/emoji/Firefox.gif", + "visible_in_picker": false + } + ], + "id": "13", + "unread": true + }, + { + "account_id": "someflakeid", + "chat_id": "1", + "content": "Whats' up?", + "created_at": "2020-04-21T15:06:45.000Z", + "emojis": [], + "id": "12", + "unread": false + } +] +``` + +## DELETE /api/pleroma/admin/chats/:chat_id/messages/:message_id + +### Delete a single message + +- Params: None + +- Response: + +```json +{ + "account_id": "someflakeid", + "chat_id": "1", + "content": "Check this out :firefox:", + "created_at": "2020-04-21T15:11:46.000Z", + "emojis": [ + { + "shortcode": "firefox", + "static_url": "https://dontbulling.me/emoji/Firefox.gif", + "url": "https://dontbulling.me/emoji/Firefox.gif", + "visible_in_picker": false + } + ], + "id": "13", + "unread": false +} +``` + +## `GET /api/pleroma/admin/instance_document/:document_name` + +### Get an instance document + +- Authentication: required + +- Response: + +Returns the content of the document + +```html +

Instance panel

+``` + +## `PATCH /api/pleroma/admin/instance_document/:document_name` +- Params: + - `file` (the file to be uploaded, using multipart form data.) + +### Update an instance document + +- Authentication: required + +- Response: + +``` json +{ + "url": "https://example.com/instance/panel.html" +} +``` + +## `DELETE /api/pleroma/admin/instance_document/:document_name` + +### Delete an instance document + +- Response: + +``` json +{ + "url": "https://example.com/instance/panel.html" +} +``` diff --git a/docs/API/pleroma_api.md b/docs/API/pleroma_api.md index 4e97d26c0..94b6a4fda 100644 --- a/docs/API/pleroma_api.md +++ b/docs/API/pleroma_api.md @@ -44,6 +44,22 @@ Request parameters can be passed via [query strings](https://en.wikipedia.org/wi * Response: HTTP 200 on success, 500 on error * Note: Users that can't be followed are silently skipped. +## `/api/pleroma/blocks_import` +### Imports your blocks. +* Method: `POST` +* Authentication: required +* Params: + * `list`: STRING or FILE containing a whitespace-separated list of accounts to block +* Response: HTTP 200 on success, 500 on error + +## `/api/pleroma/mutes_import` +### Imports your mutes. +* Method: `POST` +* Authentication: required +* Params: + * `list`: STRING or FILE containing a whitespace-separated list of accounts to mute +* Response: HTTP 200 on success, 500 on error + ## `/api/pleroma/captcha` ### Get a new captcha * Method: `GET` diff --git a/docs/administration/updating.md b/docs/administration/updating.md index c994f3f16..ef2c9218c 100644 --- a/docs/administration/updating.md +++ b/docs/administration/updating.md @@ -18,9 +18,10 @@ su pleroma -s $SHELL -lc "./bin/pleroma_ctl migrate" 1. Go to the working directory of Pleroma (default is `/opt/pleroma`) 2. Run `git pull`. This pulls the latest changes from upstream. -3. Run `mix deps.get`. This pulls in any new dependencies. +3. Run `mix deps.get` [^1]. This pulls in any new dependencies. 4. Stop the Pleroma service. -5. Run `mix ecto.migrate`[^1]. This task performs database migrations, if there were any. +5. Run `mix ecto.migrate` [^1] [^2]. This task performs database migrations, if there were any. 6. Start the Pleroma service. -[^1]: Prefix with `MIX_ENV=prod` to run it using the production config file. +[^1]: Depending on which install guide you followed (for example on Debian/Ubuntu), you want to run `mix` tasks as `pleroma` user by adding `sudo -Hu pleroma` before the command. +[^2]: Prefix with `MIX_ENV=prod` to run it using the production config file. diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index b2980793d..42e5fe808 100644 --- a/docs/configuration/cheatsheet.md +++ b/docs/configuration/cheatsheet.md @@ -18,7 +18,7 @@ To add configuration to your config file, you can copy it from the base config. * `notify_email`: Email used for notifications. * `description`: The instance’s description, can be seen in nodeinfo and ``/api/v1/instance``. * `limit`: Posts character limit (CW/Subject included in the counter). -* `discription_limit`: The character limit for image descriptions. +* `description_limit`: The character limit for image descriptions. * `chat_limit`: Character limit of the instance chat messages. * `remote_limit`: Hard character limit beyond which remote posts will be dropped. * `upload_limit`: File size limit of uploads (except for avatar, background, banner). @@ -40,7 +40,6 @@ To add configuration to your config file, you can copy it from the base config. * `allow_relay`: Enable Pleroma’s Relay, which makes it possible to follow a whole instance. * `public`: Makes the client API in authenticated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network. Note that there is a dependent setting restricting or allowing unauthenticated access to specific resources, see `restrict_unauthenticated` for more details. * `quarantined_instances`: List of ActivityPub instances where private (DMs, followers-only) activities will not be send. -* `managed_config`: Whenether the config for pleroma-fe is configured in [:frontend_configurations](#frontend_configurations) or in ``static/config.json``. * `allowed_post_formats`: MIME-type list of formats allowed to be posted (transformed into HTML). * `extended_nickname_format`: Set to `true` to use extended local nicknames format (allows underscores/dashes). This will break federation with older software for theses nicknames. @@ -115,6 +114,7 @@ To add configuration to your config file, you can copy it from the base config. * `Pleroma.Web.ActivityPub.MRF.VocabularyPolicy`: Restricts activities to a configured set of vocabulary. (See [`:mrf_vocabulary`](#mrf_vocabulary)). * `Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy`: Rejects or delists posts based on their age when received. (See [`:mrf_object_age`](#mrf_object_age)). * `Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy`: Sets a default expiration on all posts made by users of the local instance. Requires `Pleroma.ActivityExpiration` to be enabled for processing the scheduled delections. + * `Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy`: Makes all bot posts to disappear from public timelines. * `transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo). * `transparency_exclusions`: Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value. @@ -225,6 +225,16 @@ Enables the worker which processes posts scheduled for deletion. Pinned posts ar * `enabled`: whether expired activities will be sent to the job queue to be deleted +## FedSockets +FedSockets is an experimental feature allowing for Pleroma backends to federate using a persistant websocket connection as opposed to making each federation a seperate http connection. This feature is currently off by default. It is configurable throught he following options. + +### :fedsockets +* `enabled`: Enables FedSockets for this instance. `false` by default. +* `connection_duration`: Time an idle websocket is kept open. +* `rejection_duration`: Failures to connect via FedSockets will not be retried for this period of time. +* `fed_socket_fetches` and `fed_socket_rejections`: Settings passed to `cachex` for the fetch registry, and rejection stacks. See `Pleroma.Web.FedSockets` for more details. + + ## Frontends ### :frontend_configurations @@ -314,6 +324,14 @@ This section describe PWA manifest instance-specific values. Currently this opti * `enabled`: Enables purge cache * `provider`: Which one of the [purge cache strategy](#purge-cache-strategy) to use. +## :media_preview_proxy + +* `enabled`: Enables proxying of remote media preview to the instance’s proxy. Requires enabled media proxy (`media_proxy/enabled`). +* `thumbnail_max_width`: Max width of preview thumbnail for images (video preview always has original dimensions). +* `thumbnail_max_height`: Max height of preview thumbnail for images (video preview always has original dimensions). +* `image_quality`: Quality of the output. Ranges from 0 (min quality) to 100 (max quality). +* `min_content_length`: Min content length to perform preview, in bytes. If greater than 0, media smaller in size will be served as is, without thumbnailing. + ### Purge cache strategy #### Pleroma.Web.MediaProxy.Invalidation.Script @@ -496,7 +514,7 @@ Settings for HTTP connection pool. * `:connection_acquisition_wait` - Timeout to acquire a connection from pool.The total max time is this value multiplied by the number of retries. * `connection_acquisition_retries` - Number of attempts to acquire the connection from the pool if it is overloaded. Each attempt is timed `:connection_acquisition_wait` apart. * `:max_connections` - Maximum number of connections in the pool. -* `:await_up_timeout` - Timeout to connect to the host. +* `:connect_timeout` - Timeout to connect to the host. * `:reclaim_multiplier` - Multiplied by `:max_connections` this will be the maximum number of idle connections that will be reclaimed in case the pool is overloaded. ### :pools @@ -515,7 +533,7 @@ There are four pools used: For each pool, the options are: * `:size` - limit to how much requests can be concurrently executed. -* `:timeout` - timeout while `gun` will wait for response +* `:recv_timeout` - timeout while `gun` will wait for response * `:max_waiting` - limit to how much requests can be waiting for others to finish, after this is reached, subsequent requests will be dropped. ## Captcha @@ -690,9 +708,8 @@ Pleroma has the following queues: Pleroma has these periodic job workers: -`Pleroma.Workers.Cron.ClearOauthTokenWorker` - a job worker to cleanup expired oauth tokens. - -Example: +* `Pleroma.Workers.Cron.DigestEmailsWorker` - digest emails for users with new mentions and follows +* `Pleroma.Workers.Cron.NewUsersDigestWorker` - digest emails for admins with new registrations ```elixir config :pleroma, Oban, @@ -704,7 +721,8 @@ config :pleroma, Oban, federator_outgoing: 50 ], crontab: [ - {"0 0 * * *", Pleroma.Workers.Cron.ClearOauthTokenWorker} + {"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker}, + {"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker} ] ``` @@ -971,7 +989,7 @@ Configure OAuth 2 provider capabilities: * `token_expires_in` - The lifetime in seconds of the access token. * `issue_new_refresh_token` - Keeps old refresh token or generate new refresh token when to obtain an access token. -* `clean_expired_tokens` - Enable a background job to clean expired oauth tokens. Defaults to `false`. Interval settings sets in configuration periodic jobs [`Oban.Cron`](#obancron) +* `clean_expired_tokens` - Enable a background job to clean expired oauth tokens. Defaults to `false`. ## Link parsing @@ -1090,3 +1108,10 @@ config :pleroma, :frontends, ``` This would serve the frontend from the the folder at `$instance_static/frontends/pleroma/stable`. You have to copy the frontend into this folder yourself. You can choose the name and ref any way you like, but they will be used by mix tasks to automate installation in the future, the name referring to the project and the ref referring to a commit. + +## Ephemeral activities (Pleroma.Workers.PurgeExpiredActivity) + +Settings to enable and configure expiration for ephemeral activities + +* `:enabled` - enables ephemeral activities creation +* `:min_lifetime` - minimum lifetime for ephemeral activities (in seconds). Default: 10 minutes. diff --git a/installation/pleroma.nginx b/installation/pleroma.nginx index d301ca615..d613befd2 100644 --- a/installation/pleroma.nginx +++ b/installation/pleroma.nginx @@ -9,6 +9,12 @@ proxy_cache_path /tmp/pleroma-media-cache levels=1:2 keys_zone=pleroma_media_cache:10m max_size=10g inactive=720m use_temp_path=off; +# this is explicitly IPv4 since Pleroma.Web.Endpoint binds on IPv4 only +# and `localhost.` resolves to [::0] on some systems: see issue #930 +upstream phoenix { + server 127.0.0.1:4000 max_fails=5 fail_timeout=60s; +} + server { server_name example.tld; @@ -63,19 +69,16 @@ server { # the nginx default is 1m, not enough for large media uploads client_max_body_size 16m; + ignore_invalid_headers off; + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; location / { - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_set_header Host $http_host; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - - # this is explicitly IPv4 since Pleroma.Web.Endpoint binds on IPv4 only - # and `localhost.` resolves to [::0] on some systems: see issue #930 - proxy_pass http://127.0.0.1:4000; - - client_max_body_size 16m; + proxy_pass http://phoenix; } location ~ ^/(media|proxy) { @@ -83,12 +86,16 @@ server { slice 1m; proxy_cache_key $host$uri$is_args$args$slice_range; proxy_set_header Range $slice_range; - proxy_http_version 1.1; proxy_cache_valid 200 206 301 304 1h; proxy_cache_lock on; proxy_ignore_client_abort on; proxy_buffering on; chunked_transfer_encoding on; - proxy_pass http://127.0.0.1:4000; + proxy_pass http://phoenix; + } + + location /api/fedsocket/v1 { + proxy_request_buffering off; + proxy_pass http://phoenix/api/fedsocket/v1; } } diff --git a/lib/mix/pleroma.ex b/lib/mix/pleroma.ex index fe9b0d16c..49ba2aae4 100644 --- a/lib/mix/pleroma.ex +++ b/lib/mix/pleroma.ex @@ -18,6 +18,7 @@ defmodule Mix.Pleroma do @doc "Common functions to be reused in mix tasks" def start_pleroma do Pleroma.Config.Holder.save_default() + Pleroma.Config.Oban.warn() Application.put_env(:phoenix, :serve_endpoints, false, persistent: true) if Pleroma.Config.get(:env) != :test do diff --git a/lib/mix/tasks/pleroma/benchmark.ex b/lib/mix/tasks/pleroma/benchmark.ex index dd2b9c8f2..a607d5d4f 100644 --- a/lib/mix/tasks/pleroma/benchmark.ex +++ b/lib/mix/tasks/pleroma/benchmark.ex @@ -91,20 +91,17 @@ def run(["adapters"]) do "Without conn and without pool" => fn -> {:ok, %Tesla.Env{}} = Pleroma.HTTP.get("https://httpbin.org/stream-bytes/1500", [], - adapter: [pool: :no_pool, receive_conn: false] + pool: :no_pool, + receive_conn: false ) end, "Without conn and with pool" => fn -> {:ok, %Tesla.Env{}} = - Pleroma.HTTP.get("https://httpbin.org/stream-bytes/1500", [], - adapter: [receive_conn: false] - ) + Pleroma.HTTP.get("https://httpbin.org/stream-bytes/1500", [], receive_conn: false) end, "With reused conn and without pool" => fn -> {:ok, %Tesla.Env{}} = - Pleroma.HTTP.get("https://httpbin.org/stream-bytes/1500", [], - adapter: [pool: :no_pool] - ) + Pleroma.HTTP.get("https://httpbin.org/stream-bytes/1500", [], pool: :no_pool) end, "With reused conn and with pool" => fn -> {:ok, %Tesla.Env{}} = Pleroma.HTTP.get("https://httpbin.org/stream-bytes/1500") diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index 904c5a74b..18f99318d 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -32,7 +32,8 @@ def run(["migrate_from_db" | options]) do @spec migrate_to_db(Path.t() | nil) :: any() def migrate_to_db(file_path \\ nil) do - if Pleroma.Config.get([:configurable_from_database]) do + with true <- Pleroma.Config.get([:configurable_from_database]), + :ok <- Pleroma.Config.DeprecationWarnings.warn() do config_file = if file_path do file_path @@ -46,7 +47,8 @@ def migrate_to_db(file_path \\ nil) do do_migrate_to_db(config_file) else - migration_error() + :error -> deprecation_error() + _ -> migration_error() end end @@ -120,6 +122,10 @@ defp migration_error do ) end + defp deprecation_error do + shell_error("Migration is not allowed until all deprecation warnings have been resolved.") + end + if Code.ensure_loaded?(Config.Reader) do defp config_header, do: "import Config\r\n\r\n" defp read_file(config_file), do: Config.Reader.read_imports!(config_file) diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex index 7d8f00b08..a01c36ece 100644 --- a/lib/mix/tasks/pleroma/database.ex +++ b/lib/mix/tasks/pleroma/database.ex @@ -99,7 +99,7 @@ def run(["fix_likes_collections"]) do where: fragment("(?)->>'likes' is not null", object.data), select: %{id: object.id, likes: fragment("(?)->>'likes'", object.data)} ) - |> Pleroma.RepoStreamer.chunk_stream(100) + |> Pleroma.Repo.chunk_stream(100, :batches) |> Stream.each(fn objects -> ids = objects @@ -133,8 +133,7 @@ def run(["ensure_expiration"]) do days = Pleroma.Config.get([:mrf_activity_expiration, :days], 365) Pleroma.Activity - |> join(:left, [a], u in assoc(a, :expiration)) - |> join(:inner, [a, _u], o in Object, + |> join(:inner, [a], o in Object, on: fragment( "(?->>'id') = COALESCE((?)->'object'->> 'id', (?)->>'object')", @@ -144,14 +143,20 @@ def run(["ensure_expiration"]) do ) ) |> where(local: true) - |> where([a, u], is_nil(u)) |> where([a], fragment("(? ->> 'type'::text) = 'Create'", a.data)) - |> where([_a, _u, o], fragment("?->>'type' = 'Note'", o.data)) - |> Pleroma.RepoStreamer.chunk_stream(100) + |> where([_a, o], fragment("?->>'type' = 'Note'", o.data)) + |> Pleroma.Repo.chunk_stream(100, :batches) |> Stream.each(fn activities -> Enum.each(activities, fn activity -> - expires_at = Timex.shift(activity.inserted_at, days: days) - Pleroma.ActivityExpiration.create(activity, expires_at, false) + expires_at = + activity.inserted_at + |> DateTime.from_naive!("Etc/UTC") + |> Timex.shift(days: days) + + Pleroma.Workers.PurgeExpiredActivity.enqueue(%{ + activity_id: activity.id, + expires_at: expires_at + }) end) end) |> Stream.run() diff --git a/lib/mix/tasks/pleroma/emoji.ex b/lib/mix/tasks/pleroma/emoji.ex index 8f52ee98d..1750373f9 100644 --- a/lib/mix/tasks/pleroma/emoji.ex +++ b/lib/mix/tasks/pleroma/emoji.ex @@ -183,7 +183,7 @@ def run(["gen-pack" | args]) do IO.puts("Downloading the pack and generating SHA256") - binary_archive = Tesla.get!(client(), src).body + {:ok, %{body: binary_archive}} = Pleroma.HTTP.get(src) archive_sha = :crypto.hash(:sha256, binary_archive) |> Base.encode16() IO.puts("SHA256 is #{archive_sha}") @@ -252,7 +252,7 @@ defp fetch_and_decode!(from) do end defp fetch("http" <> _ = from) do - with {:ok, %{body: body}} <- Tesla.get(client(), from) do + with {:ok, %{body: body}} <- Pleroma.HTTP.get(from) do {:ok, body} end end @@ -271,13 +271,5 @@ defp parse_global_opts(args) do ) end - defp client do - middleware = [ - {Tesla.Middleware.FollowRedirects, [max_redirects: 3]} - ] - - Tesla.client(middleware) - end - defp default_manifest, do: Pleroma.Config.get!([:emoji, :default_manifest]) end diff --git a/lib/mix/tasks/pleroma/frontend.ex b/lib/mix/tasks/pleroma/frontend.ex index 484af6da7..cbce81ab9 100644 --- a/lib/mix/tasks/pleroma/frontend.ex +++ b/lib/mix/tasks/pleroma/frontend.ex @@ -69,7 +69,7 @@ def run(["install", frontend | args]) do fe_label = "#{frontend} (#{ref})" - tmp_dir = Path.join(dest, "tmp") + tmp_dir = Path.join([instance_static_dir, "frontends", "tmp"]) with {_, :ok} <- {:download_or_unzip, download_or_unzip(frontend_info, tmp_dir, options[:file])}, @@ -124,9 +124,7 @@ defp download_build(frontend_info, dest) do url = String.replace(frontend_info["build_url"], "${ref}", frontend_info["ref"]) with {:ok, %{status: 200, body: zip_body}} <- - Pleroma.HTTP.get(url, [], - adapter: [pool: :media, timeout: 120_000, recv_timeout: 120_000] - ) do + Pleroma.HTTP.get(url, [], pool: :media, recv_timeout: 120_000) do unzip(zip_body, dest) else e -> {:error, e} @@ -135,6 +133,7 @@ defp download_build(frontend_info, dest) do defp install_frontend(frontend_info, source, dest) do from = frontend_info["build_dir"] || "dist" + File.rm_rf!(dest) File.mkdir_p!(dest) File.cp_r!(Path.join([source, from]), dest) :ok diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex index 01824aa18..b20c49d89 100644 --- a/lib/mix/tasks/pleroma/user.ex +++ b/lib/mix/tasks/pleroma/user.ex @@ -179,7 +179,7 @@ def run(["deactivate_all_from_instance", instance]) do start_pleroma() Pleroma.User.Query.build(%{nickname: "@#{instance}"}) - |> Pleroma.RepoStreamer.chunk_stream(500) + |> Pleroma.Repo.chunk_stream(500, :batches) |> Stream.each(fn users -> users |> Enum.each(fn user -> @@ -370,7 +370,7 @@ def run(["list"]) do start_pleroma() Pleroma.User.Query.build(%{local: true}) - |> Pleroma.RepoStreamer.chunk_stream(500) + |> Pleroma.Repo.chunk_stream(500, :batches) |> Stream.each(fn users -> users |> Enum.each(fn user -> diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index 97feebeaa..17af04257 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -7,7 +7,6 @@ defmodule Pleroma.Activity do alias Pleroma.Activity alias Pleroma.Activity.Queries - alias Pleroma.ActivityExpiration alias Pleroma.Bookmark alias Pleroma.Notification alias Pleroma.Object @@ -60,8 +59,6 @@ defmodule Pleroma.Activity do # typical case. has_one(:object, Object, on_delete: :nothing, foreign_key: :id) - has_one(:expiration, ActivityExpiration, on_delete: :delete_all) - timestamps() end @@ -304,14 +301,14 @@ def all_by_actor_and_id(actor, status_ids) do |> Repo.all() end - def follow_requests_for_actor(%Pleroma.User{ap_id: ap_id}) do + def follow_requests_for_actor(%User{ap_id: ap_id}) do ap_id |> Queries.by_object_id() |> Queries.by_type("Follow") |> where([a], fragment("? ->> 'state' = 'pending'", a.data)) end - def following_requests_for_actor(%Pleroma.User{ap_id: ap_id}) do + def following_requests_for_actor(%User{ap_id: ap_id}) do Queries.by_type("Follow") |> where([a], fragment("?->>'state' = 'pending'", a.data)) |> where([a], a.actor == ^ap_id) diff --git a/lib/pleroma/activity_expiration.ex b/lib/pleroma/activity_expiration.ex deleted file mode 100644 index 955f0578e..000000000 --- a/lib/pleroma/activity_expiration.ex +++ /dev/null @@ -1,74 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.ActivityExpiration do - use Ecto.Schema - - alias Pleroma.Activity - alias Pleroma.ActivityExpiration - alias Pleroma.Repo - - import Ecto.Changeset - import Ecto.Query - - @type t :: %__MODULE__{} - @min_activity_lifetime :timer.hours(1) - - schema "activity_expirations" do - belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType) - field(:scheduled_at, :naive_datetime) - end - - def changeset(%ActivityExpiration{} = expiration, attrs, validate_scheduled_at) do - expiration - |> cast(attrs, [:scheduled_at]) - |> validate_required([:scheduled_at]) - |> validate_scheduled_at(validate_scheduled_at) - end - - def get_by_activity_id(activity_id) do - ActivityExpiration - |> where([exp], exp.activity_id == ^activity_id) - |> Repo.one() - end - - def create(%Activity{} = activity, scheduled_at, validate_scheduled_at \\ true) do - %ActivityExpiration{activity_id: activity.id} - |> changeset(%{scheduled_at: scheduled_at}, validate_scheduled_at) - |> Repo.insert() - end - - def due_expirations(offset \\ 0) do - naive_datetime = - NaiveDateTime.utc_now() - |> NaiveDateTime.add(offset, :millisecond) - - ActivityExpiration - |> where([exp], exp.scheduled_at < ^naive_datetime) - |> limit(50) - |> preload(:activity) - |> Repo.all() - |> Enum.reject(fn %{activity: activity} -> - Activity.pinned_by_actor?(activity) - end) - end - - def validate_scheduled_at(changeset, false), do: changeset - - def validate_scheduled_at(changeset, true) do - validate_change(changeset, :scheduled_at, fn _, scheduled_at -> - if not expires_late_enough?(scheduled_at) do - [scheduled_at: "an ephemeral activity must live for at least one hour"] - else - [] - end - end) - end - - def expires_late_enough?(scheduled_at) do - now = NaiveDateTime.utc_now() - diff = NaiveDateTime.diff(scheduled_at, now, :millisecond) - diff > @min_activity_lifetime - end -end diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index c0b5db9f1..00ec79a2a 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -22,13 +22,18 @@ def named_version, do: @name <> " " <> @version def repository, do: @repository def user_agent do - case Config.get([:http, :user_agent], :default) do - :default -> - info = "#{Pleroma.Web.base_url()} <#{Config.get([:instance, :email], "")}>" - named_version() <> "; " <> info + if Process.whereis(Pleroma.Web.Endpoint) do + case Config.get([:http, :user_agent], :default) do + :default -> + info = "#{Pleroma.Web.base_url()} <#{Config.get([:instance, :email], "")}>" + named_version() <> "; " <> info - custom -> - custom + custom -> + custom + end + else + # fallback, if endpoint is not started yet + "Pleroma Data Loader" end end @@ -39,9 +44,13 @@ def start(_type, _args) do # every time the application is restarted, so we disable module # conflicts at runtime Code.compiler_options(ignore_module_conflict: true) + # Disable warnings_as_errors at runtime, it breaks Phoenix live reload + # due to protocol consolidation warnings + Code.compiler_options(warnings_as_errors: false) Pleroma.Telemetry.Logger.attach() Config.Holder.save_default() Pleroma.HTML.compile_scrubbers() + Pleroma.Config.Oban.warn() Config.DeprecationWarnings.warn() Pleroma.Plugs.HTTPSecurityPlug.warn_if_disabled() Pleroma.ApplicationRequirements.verify!() @@ -90,7 +99,7 @@ def start(_type, _args) do {Oban, Config.get(Oban)} ] ++ task_children(@env) ++ - streamer_child(@env) ++ + dont_run_in_test(@env) ++ chat_child(@env, chat_enabled?()) ++ [ Pleroma.Web.Endpoint, @@ -179,16 +188,17 @@ def build_cachex(type, opts), defp chat_enabled?, do: Config.get([:chat, :enabled]) - defp streamer_child(env) when env in [:test, :benchmark], do: [] + defp dont_run_in_test(env) when env in [:test, :benchmark], do: [] - defp streamer_child(_) do + defp dont_run_in_test(_) do [ {Registry, [ name: Pleroma.Web.Streamer.registry(), keys: :duplicate, partitions: System.schedulers_online() - ]} + ]}, + Pleroma.Web.FedSockets.Supervisor ] end diff --git a/lib/pleroma/chat.ex b/lib/pleroma/chat.ex index 24a86371e..84f8806a0 100644 --- a/lib/pleroma/chat.ex +++ b/lib/pleroma/chat.ex @@ -6,7 +6,9 @@ defmodule Pleroma.Chat do use Ecto.Schema import Ecto.Changeset + import Ecto.Query + alias Pleroma.Chat alias Pleroma.Repo alias Pleroma.User @@ -69,4 +71,12 @@ def bump_or_create(user_id, recipient) do conflict_target: [:user_id, :recipient] ) end + + @spec for_user_query(FlakeId.Ecto.CompatType.t()) :: Ecto.Query.t() + def for_user_query(user_id) do + from(c in Chat, + where: c.user_id == ^user_id, + order_by: [desc: c.updated_at] + ) + end end diff --git a/lib/pleroma/config/deprecation_warnings.ex b/lib/pleroma/config/deprecation_warnings.ex index 0f52eb210..98c4dc9c8 100644 --- a/lib/pleroma/config/deprecation_warnings.ex +++ b/lib/pleroma/config/deprecation_warnings.ex @@ -8,7 +8,7 @@ defmodule Pleroma.Config.DeprecationWarnings do require Logger alias Pleroma.Config - @type config_namespace() :: [atom()] + @type config_namespace() :: atom() | [atom()] @type config_map() :: {config_namespace(), config_namespace(), String.t()} @mrf_config_map [ @@ -26,6 +26,10 @@ def check_hellthread_threshold do !!!DEPRECATION WARNING!!! You are using the old configuration mechanism for the hellthread filter. Please check config.md. """) + + :error + else + :ok end end @@ -47,15 +51,26 @@ def mrf_user_allowlist do config :pleroma, :mrf_user_allowlist, #{inspect(rewritten, pretty: true)} """) + + :error + else + :ok end end def warn do - check_hellthread_threshold() - mrf_user_allowlist() - check_old_mrf_config() - check_media_proxy_whitelist_config() - check_welcome_message_config() + with :ok <- check_hellthread_threshold(), + :ok <- mrf_user_allowlist(), + :ok <- check_old_mrf_config(), + :ok <- check_media_proxy_whitelist_config(), + :ok <- check_welcome_message_config(), + :ok <- check_gun_pool_options(), + :ok <- check_activity_expiration_config() do + :ok + else + _ -> + :error + end end def check_welcome_message_config do @@ -72,6 +87,10 @@ def check_welcome_message_config do \n* `config :pleroma, :instance, welcome_user_nickname` is now `config :pleroma, :welcome, :direct_message, :sender_nickname` \n* `config :pleroma, :instance, welcome_message` is now `config :pleroma, :welcome, :direct_message, :message` """) + + :error + else + :ok end end @@ -99,8 +118,11 @@ def move_namespace_and_warn(config_map, warning_preface) do end end) - if warning != "" do + if warning == "" do + :ok + else Logger.warn(warning_preface <> warning) + :error end end @@ -113,6 +135,71 @@ def check_media_proxy_whitelist_config do !!!DEPRECATION WARNING!!! Your config is using old format (only domain) for MediaProxy whitelist option. Setting should work for now, but you are advised to change format to scheme with port to prevent possible issues later. """) + + :error + else + :ok end end + + def check_gun_pool_options do + pool_config = Config.get(:connections_pool) + + if timeout = pool_config[:await_up_timeout] do + Logger.warn(""" + !!!DEPRECATION WARNING!!! + Your config is using old setting name `await_up_timeout` instead of `connect_timeout`. Setting should work for now, but you are advised to change format to scheme with port to prevent possible issues later. + """) + + Config.put(:connections_pool, Keyword.put_new(pool_config, :connect_timeout, timeout)) + end + + pools_configs = Config.get(:pools) + + warning_preface = """ + !!!DEPRECATION WARNING!!! + Your config is using old setting name `timeout` instead of `recv_timeout` in pool settings. Setting should work for now, but you are advised to change format to scheme with port to prevent possible issues later. + """ + + updated_config = + Enum.reduce(pools_configs, [], fn {pool_name, config}, acc -> + if timeout = config[:timeout] do + Keyword.put(acc, pool_name, Keyword.put_new(config, :recv_timeout, timeout)) + else + acc + end + end) + + if updated_config != [] do + pool_warnings = + updated_config + |> Keyword.keys() + |> Enum.map(fn pool_name -> + "\n* `:timeout` options in #{pool_name} pool is now `:recv_timeout`" + end) + + Logger.warn(Enum.join([warning_preface | pool_warnings])) + + Config.put(:pools, updated_config) + :error + else + :ok + end + end + + @spec check_activity_expiration_config() :: :ok | nil + def check_activity_expiration_config do + warning_preface = """ + !!!DEPRECATION WARNING!!! + Your config is using old namespace for activity expiration configuration. Setting should work for now, but you are advised to change to new namespace to prevent possible issues later: + """ + + move_namespace_and_warn( + [ + {Pleroma.ActivityExpiration, Pleroma.Workers.PurgeExpiredActivity, + "\n* `config :pleroma, Pleroma.ActivityExpiration` is now `config :pleroma, Pleroma.Workers.PurgeExpiredActivity`"} + ], + warning_preface + ) + end end diff --git a/lib/pleroma/config/oban.ex b/lib/pleroma/config/oban.ex new file mode 100644 index 000000000..9f601b1a3 --- /dev/null +++ b/lib/pleroma/config/oban.ex @@ -0,0 +1,34 @@ +defmodule Pleroma.Config.Oban do + require Logger + + def warn do + oban_config = Pleroma.Config.get(Oban) + + crontab = + [ + Pleroma.Workers.Cron.StatsWorker, + Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker, + Pleroma.Workers.Cron.ClearOauthTokenWorker + ] + |> Enum.reduce(oban_config[:crontab], fn removed_worker, acc -> + with acc when is_list(acc) <- acc, + setting when is_tuple(setting) <- + Enum.find(acc, fn {_, worker} -> worker == removed_worker end) do + """ + !!!OBAN CONFIG WARNING!!! + You are using old workers in Oban crontab settings, which were removed. + Please, remove setting from crontab in your config file (prod.secret.exs): #{ + inspect(setting) + } + """ + |> Logger.warn() + + List.delete(acc, setting) + else + _ -> acc + end + end) + + Pleroma.Config.put(Oban, Keyword.put(oban_config, :crontab, crontab)) + end +end diff --git a/lib/pleroma/ecto_type/activity_pub/object_validators/emoji.ex b/lib/pleroma/ecto_type/activity_pub/object_validators/emoji.ex new file mode 100644 index 000000000..4aacc5c88 --- /dev/null +++ b/lib/pleroma/ecto_type/activity_pub/object_validators/emoji.ex @@ -0,0 +1,34 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.Emoji do + use Ecto.Type + + def type, do: :map + + def cast(data) when is_map(data) do + has_invalid_emoji? = + Enum.find(data, fn + {name, uri} when is_binary(name) and is_binary(uri) -> + # based on ObjectValidators.Uri.cast() + case URI.parse(uri) do + %URI{host: nil} -> true + %URI{host: ""} -> true + %URI{scheme: scheme} when scheme in ["https", "http"] -> false + _ -> true + end + + {_name, _uri} -> + true + end) + + if has_invalid_emoji?, do: :error, else: {:ok, data} + end + + def cast(_data), do: :error + + def dump(data), do: {:ok, data} + + def load(data), do: {:ok, data} +end diff --git a/lib/pleroma/gun/conn.ex b/lib/pleroma/gun/conn.ex index a3f75a4bb..477e19c6e 100644 --- a/lib/pleroma/gun/conn.ex +++ b/lib/pleroma/gun/conn.ex @@ -13,7 +13,7 @@ def open(%URI{} = uri, opts) do opts = opts |> Enum.into(%{}) - |> Map.put_new(:await_up_timeout, pool_opts[:await_up_timeout] || 5_000) + |> Map.put_new(:connect_timeout, pool_opts[:connect_timeout] || 5_000) |> Map.put_new(:supervise, false) |> maybe_add_tls_opts(uri) @@ -50,10 +50,10 @@ defp do_open(uri, %{proxy: {proxy_host, proxy_port}} = opts) do with open_opts <- Map.delete(opts, :tls_opts), {:ok, conn} <- Gun.open(proxy_host, proxy_port, open_opts), - {:ok, _} <- Gun.await_up(conn, opts[:await_up_timeout]), + {:ok, protocol} <- Gun.await_up(conn, opts[:connect_timeout]), stream <- Gun.connect(conn, connect_opts), {:response, :fin, 200, _} <- Gun.await(conn, stream) do - {:ok, conn} + {:ok, conn, protocol} else error -> Logger.warn( @@ -88,8 +88,8 @@ defp do_open(uri, %{proxy: {proxy_type, proxy_host, proxy_port}} = opts) do |> Map.put(:socks_opts, socks_opts) with {:ok, conn} <- Gun.open(proxy_host, proxy_port, opts), - {:ok, _} <- Gun.await_up(conn, opts[:await_up_timeout]) do - {:ok, conn} + {:ok, protocol} <- Gun.await_up(conn, opts[:connect_timeout]) do + {:ok, conn, protocol} else error -> Logger.warn( @@ -106,8 +106,8 @@ defp do_open(%URI{host: host, port: port} = uri, opts) do host = Pleroma.HTTP.AdapterHelper.parse_host(host) with {:ok, conn} <- Gun.open(host, port, opts), - {:ok, _} <- Gun.await_up(conn, opts[:await_up_timeout]) do - {:ok, conn} + {:ok, protocol} <- Gun.await_up(conn, opts[:connect_timeout]) do + {:ok, conn, protocol} else error -> Logger.warn( diff --git a/lib/pleroma/gun/connection_pool/worker.ex b/lib/pleroma/gun/connection_pool/worker.ex index c36332817..bf57e9e5f 100644 --- a/lib/pleroma/gun/connection_pool/worker.ex +++ b/lib/pleroma/gun/connection_pool/worker.ex @@ -15,7 +15,7 @@ def init([_key, _uri, _opts, _client_pid] = opts) do @impl true def handle_continue({:connect, [key, uri, opts, client_pid]}, _) do - with {:ok, conn_pid} <- Gun.Conn.open(uri, opts), + with {:ok, conn_pid, protocol} <- Gun.Conn.open(uri, opts), Process.link(conn_pid) do time = :erlang.monotonic_time(:millisecond) @@ -27,8 +27,12 @@ def handle_continue({:connect, [key, uri, opts, client_pid]}, _) do send(client_pid, {:conn_pid, conn_pid}) {:noreply, - %{key: key, timer: nil, client_monitors: %{client_pid => Process.monitor(client_pid)}}, - :hibernate} + %{ + key: key, + timer: nil, + client_monitors: %{client_pid => Process.monitor(client_pid)}, + protocol: protocol + }, :hibernate} else err -> {:stop, {:shutdown, err}, nil} @@ -53,14 +57,20 @@ def handle_cast({:remove_client, client_pid}, state) do end @impl true - def handle_call(:add_client, {client_pid, _}, %{key: key} = state) do + def handle_call(:add_client, {client_pid, _}, %{key: key, protocol: protocol} = state) do time = :erlang.monotonic_time(:millisecond) - {{conn_pid, _, _, _}, _} = + {{conn_pid, used_by, _, _}, _} = Registry.update_value(@registry, key, fn {conn_pid, used_by, crf, last_reference} -> {conn_pid, [client_pid | used_by], crf(time - last_reference, crf), time} end) + :telemetry.execute( + [:pleroma, :connection_pool, :client, :add], + %{client_pid: client_pid, clients: used_by}, + %{key: state.key, protocol: protocol} + ) + state = if state.timer != nil do Process.cancel_timer(state[:timer]) @@ -83,25 +93,18 @@ def handle_call(:remove_client, {client_pid, _}, %{key: key} = state) do end) {ref, state} = pop_in(state.client_monitors[client_pid]) - # DOWN message can receive right after `remove_client` call and cause worker to terminate - state = - if is_nil(ref) do - state + + Process.demonitor(ref, [:flush]) + + timer = + if used_by == [] do + max_idle = Pleroma.Config.get([:connections_pool, :max_idle_time], 30_000) + Process.send_after(self(), :idle_close, max_idle) else - Process.demonitor(ref) - - timer = - if used_by == [] do - max_idle = Pleroma.Config.get([:connections_pool, :max_idle_time], 30_000) - Process.send_after(self(), :idle_close, max_idle) - else - nil - end - - %{state | timer: timer} + nil end - {:reply, :ok, state, :hibernate} + {:reply, :ok, %{state | timer: timer}, :hibernate} end @impl true @@ -131,7 +134,7 @@ def handle_info({:gun_down, _pid, _protocol, _reason, _killed_streams}, state) d @impl true def handle_info({:DOWN, _ref, :process, pid, reason}, state) do :telemetry.execute( - [:pleroma, :connection_pool, :client_death], + [:pleroma, :connection_pool, :client, :dead], %{client_pid: pid, reason: reason}, %{key: state.key} ) diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex new file mode 100644 index 000000000..b6f35a24b --- /dev/null +++ b/lib/pleroma/helpers/media_helper.ex @@ -0,0 +1,150 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Helpers.MediaHelper do + @moduledoc """ + Handles common media-related operations. + """ + + alias Pleroma.HTTP + + def image_resize(url, options) do + with executable when is_binary(executable) <- System.find_executable("convert"), + {:ok, args} <- prepare_image_resize_args(options), + {:ok, env} <- HTTP.get(url, [], pool: :media), + {:ok, fifo_path} <- mkfifo() do + args = List.flatten([fifo_path, args]) + run_fifo(fifo_path, env, executable, args) + else + nil -> {:error, {:convert, :command_not_found}} + {:error, _} = error -> error + end + end + + defp prepare_image_resize_args( + %{max_width: max_width, max_height: max_height, format: "png"} = options + ) do + quality = options[:quality] || 85 + resize = Enum.join([max_width, "x", max_height, ">"]) + + args = [ + "-resize", + resize, + "-quality", + to_string(quality), + "png:-" + ] + + {:ok, args} + end + + defp prepare_image_resize_args(%{max_width: max_width, max_height: max_height} = options) do + quality = options[:quality] || 85 + resize = Enum.join([max_width, "x", max_height, ">"]) + + args = [ + "-interlace", + "Plane", + "-resize", + resize, + "-quality", + to_string(quality), + "jpg:-" + ] + + {:ok, args} + end + + defp prepare_image_resize_args(_), do: {:error, :missing_options} + + # Note: video thumbnail is intentionally not resized (always has original dimensions) + def video_framegrab(url) do + with executable when is_binary(executable) <- System.find_executable("ffmpeg"), + {:ok, env} <- HTTP.get(url, [], pool: :media), + {:ok, fifo_path} <- mkfifo(), + args = [ + "-y", + "-i", + fifo_path, + "-vframes", + "1", + "-f", + "mjpeg", + "-loglevel", + "error", + "-" + ] do + run_fifo(fifo_path, env, executable, args) + else + nil -> {:error, {:ffmpeg, :command_not_found}} + {:error, _} = error -> error + end + end + + defp run_fifo(fifo_path, env, executable, args) do + pid = + Port.open({:spawn_executable, executable}, [ + :use_stdio, + :stream, + :exit_status, + :binary, + args: args + ]) + + fifo = Port.open(to_charlist(fifo_path), [:eof, :binary, :stream, :out]) + fix = Pleroma.Helpers.QtFastStart.fix(env.body) + true = Port.command(fifo, fix) + :erlang.port_close(fifo) + loop_recv(pid) + after + File.rm(fifo_path) + end + + defp mkfifo do + path = Path.join(System.tmp_dir!(), "pleroma-media-preview-pipe-#{Ecto.UUID.generate()}") + + case System.cmd("mkfifo", [path]) do + {_, 0} -> + spawn(fifo_guard(path)) + {:ok, path} + + {_, err} -> + {:error, {:fifo_failed, err}} + end + end + + defp fifo_guard(path) do + pid = self() + + fn -> + ref = Process.monitor(pid) + + receive do + {:DOWN, ^ref, :process, ^pid, _} -> + File.rm(path) + end + end + end + + defp loop_recv(pid) do + loop_recv(pid, <<>>) + end + + defp loop_recv(pid, acc) do + receive do + {^pid, {:data, data}} -> + loop_recv(pid, acc <> data) + + {^pid, {:exit_status, 0}} -> + {:ok, acc} + + {^pid, {:exit_status, status}} -> + {:error, status} + after + 5000 -> + :erlang.port_close(pid) + {:error, :timeout} + end + end +end diff --git a/lib/pleroma/helpers/qt_fast_start.ex b/lib/pleroma/helpers/qt_fast_start.ex new file mode 100644 index 000000000..bb93224b5 --- /dev/null +++ b/lib/pleroma/helpers/qt_fast_start.ex @@ -0,0 +1,131 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Helpers.QtFastStart do + @moduledoc """ + (WIP) Converts a "slow start" (data before metadatas) mov/mp4 file to a "fast start" one (metadatas before data). + """ + + # TODO: Cleanup and optimizations + # Inspirations: https://www.ffmpeg.org/doxygen/3.4/qt-faststart_8c_source.html + # https://github.com/danielgtaylor/qtfaststart/blob/master/qtfaststart/processor.py + # ISO/IEC 14496-12:2015, ISO/IEC 15444-12:2015 + # Paracetamol + + def fix(<<0x00, 0x00, 0x00, _, 0x66, 0x74, 0x79, 0x70, _::bits>> = binary) do + index = fix(binary, 0, nil, nil, []) + + case index do + :abort -> binary + [{"ftyp", _, _, _, _}, {"mdat", _, _, _, _} | _] -> faststart(index) + [{"ftyp", _, _, _, _}, {"free", _, _, _, _}, {"mdat", _, _, _, _} | _] -> faststart(index) + _ -> binary + end + end + + def fix(binary) do + binary + end + + # MOOV have been seen before MDAT- abort + defp fix(<<_::bits>>, _, true, false, _) do + :abort + end + + defp fix( + <>, + pos, + got_moov, + got_mdat, + acc + ) do + full_size = (size - 8) * 8 + <> = rest + + acc = [ + {fourcc, pos, pos + size, size, + <>} + | acc + ] + + fix(rest, pos + size, got_moov || fourcc == "moov", got_mdat || fourcc == "mdat", acc) + end + + defp fix(<<>>, _pos, _, _, acc) do + :lists.reverse(acc) + end + + defp faststart(index) do + {{_ftyp, _, _, _, ftyp}, index} = List.keytake(index, "ftyp", 0) + + # Skip re-writing the free fourcc as it's kind of useless. + # Why stream useless bytes when you can do without? + {free_size, index} = + case List.keytake(index, "free", 0) do + {{_, _, _, size, _}, index} -> {size, index} + _ -> {0, index} + end + + {{_moov, _, _, moov_size, moov}, index} = List.keytake(index, "moov", 0) + offset = -free_size + moov_size + rest = for {_, _, _, _, data} <- index, do: data, into: [] + <> = moov + [ftyp, moov_head, fix_moov(moov_data, offset, []), rest] + end + + defp fix_moov( + <>, + offset, + acc + ) do + full_size = (size - 8) * 8 + <> = rest + + data = + cond do + fourcc in ["trak", "mdia", "minf", "stbl"] -> + # Theses contains sto or co64 part + [<>, fix_moov(data, offset, [])] + + fourcc in ["stco", "co64"] -> + # fix the damn thing + <> = data + + entry_size = + case fourcc do + "stco" -> 32 + "co64" -> 64 + end + + [ + <>, + rewrite_entries(entry_size, offset, rest, []) + ] + + true -> + [<>, data] + end + + acc = [acc | data] + fix_moov(rest, offset, acc) + end + + defp fix_moov(<<>>, _, acc), do: acc + + for size <- [32, 64] do + defp rewrite_entries( + unquote(size), + offset, + <>, + acc + ) do + rewrite_entries(unquote(size), offset, rest, [ + acc | <> + ]) + end + end + + defp rewrite_entries(_, _, <<>>, acc), do: acc +end diff --git a/lib/pleroma/helpers/uri_helper.ex b/lib/pleroma/helpers/uri_helper.ex index 6d205a636..f1301f055 100644 --- a/lib/pleroma/helpers/uri_helper.ex +++ b/lib/pleroma/helpers/uri_helper.ex @@ -3,18 +3,22 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Helpers.UriHelper do - def append_uri_params(uri, appended_params) do + def modify_uri_params(uri, overridden_params, deleted_params \\ []) do uri = URI.parse(uri) - appended_params = for {k, v} <- appended_params, into: %{}, do: {to_string(k), v} - existing_params = URI.query_decoder(uri.query || "") |> Enum.into(%{}) - updated_params_keys = Enum.uniq(Map.keys(existing_params) ++ Map.keys(appended_params)) + + existing_params = URI.query_decoder(uri.query || "") |> Map.new() + overridden_params = Map.new(overridden_params, fn {k, v} -> {to_string(k), v} end) + deleted_params = Enum.map(deleted_params, &to_string/1) updated_params = - for k <- updated_params_keys, do: {k, appended_params[k] || existing_params[k]} + existing_params + |> Map.merge(overridden_params) + |> Map.drop(deleted_params) uri |> Map.put(:query, URI.encode_query(updated_params)) |> URI.to_string() + |> String.replace_suffix("?", "") end def maybe_add_base("/" <> uri, base), do: Path.join([base, uri]) diff --git a/lib/pleroma/http/adapter_helper.ex b/lib/pleroma/http/adapter_helper.ex index 0728cbaa2..08b51578a 100644 --- a/lib/pleroma/http/adapter_helper.ex +++ b/lib/pleroma/http/adapter_helper.ex @@ -6,7 +6,7 @@ defmodule Pleroma.HTTP.AdapterHelper do @moduledoc """ Configure Tesla.Client with default and customized adapter options. """ - @defaults [pool: :federation] + @defaults [pool: :federation, connect_timeout: 5_000, recv_timeout: 5_000] @type proxy_type() :: :socks4 | :socks5 @type host() :: charlist() | :inet.ip_address() @@ -19,7 +19,6 @@ defmodule Pleroma.HTTP.AdapterHelper do | {Connection.proxy_type(), Connection.host(), pos_integer()} @callback options(keyword(), URI.t()) :: keyword() - @callback get_conn(URI.t(), keyword()) :: {:ok, term()} | {:error, term()} @spec format_proxy(String.t() | tuple() | nil) :: proxy() | nil def format_proxy(nil), do: nil @@ -47,9 +46,6 @@ def options(%URI{} = uri, opts \\ []) do |> adapter_helper().options(uri) end - @spec get_conn(URI.t(), keyword()) :: {:ok, keyword()} | {:error, atom()} - def get_conn(uri, opts), do: adapter_helper().get_conn(uri, opts) - defp adapter, do: Application.get_env(:tesla, :adapter) defp adapter_helper do diff --git a/lib/pleroma/http/adapter_helper/gun.ex b/lib/pleroma/http/adapter_helper/gun.ex index 02e20f2d1..1dbb71362 100644 --- a/lib/pleroma/http/adapter_helper/gun.ex +++ b/lib/pleroma/http/adapter_helper/gun.ex @@ -6,18 +6,13 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do @behaviour Pleroma.HTTP.AdapterHelper alias Pleroma.Config - alias Pleroma.Gun.ConnectionPool alias Pleroma.HTTP.AdapterHelper require Logger @defaults [ - connect_timeout: 5_000, - domain_lookup_timeout: 5_000, - tls_handshake_timeout: 5_000, retry: 1, - retry_timeout: 1000, - await_up_timeout: 5_000 + retry_timeout: 1_000 ] @type pool() :: :federation | :upload | :media | :default @@ -46,23 +41,17 @@ defp add_scheme_opts(opts, %{scheme: "https"}) do end defp put_timeout(opts) do + {recv_timeout, opts} = Keyword.pop(opts, :recv_timeout, pool_timeout(opts[:pool])) # this is the timeout to receive a message from Gun - Keyword.put_new(opts, :timeout, pool_timeout(opts[:pool])) + # `:timeout` key is used in Tesla + Keyword.put(opts, :timeout, recv_timeout) end @spec pool_timeout(pool()) :: non_neg_integer() def pool_timeout(pool) do - default = Config.get([:pools, :default, :timeout], 5_000) + default = Config.get([:pools, :default, :recv_timeout], 5_000) - Config.get([:pools, pool, :timeout], default) - end - - @spec get_conn(URI.t(), keyword()) :: {:ok, keyword()} | {:error, atom()} - def get_conn(uri, opts) do - case ConnectionPool.get_conn(uri, opts) do - {:ok, conn_pid} -> {:ok, Keyword.merge(opts, conn: conn_pid, close_conn: false)} - err -> err - end + Config.get([:pools, pool, :recv_timeout], default) end @prefix Pleroma.Gun.ConnectionPool diff --git a/lib/pleroma/http/adapter_helper/hackney.ex b/lib/pleroma/http/adapter_helper/hackney.ex index 62bd42485..ef84553c1 100644 --- a/lib/pleroma/http/adapter_helper/hackney.ex +++ b/lib/pleroma/http/adapter_helper/hackney.ex @@ -2,11 +2,8 @@ defmodule Pleroma.HTTP.AdapterHelper.Hackney do @behaviour Pleroma.HTTP.AdapterHelper @defaults [ - connect_timeout: 10_000, - recv_timeout: 20_000, follow_redirect: true, - force_redirect: true, - pool: :federation + force_redirect: true ] @spec options(keyword(), URI.t()) :: keyword() @@ -19,6 +16,7 @@ def options(connection_opts \\ [], %URI{} = uri) do |> Keyword.merge(config_opts) |> Keyword.merge(connection_opts) |> add_scheme_opts(uri) + |> maybe_add_with_body() |> Pleroma.HTTP.AdapterHelper.maybe_add_proxy(proxy) end @@ -28,6 +26,11 @@ defp add_scheme_opts(opts, %URI{scheme: "https"}) do defp add_scheme_opts(opts, _), do: opts - @spec get_conn(URI.t(), keyword()) :: {:ok, keyword()} - def get_conn(_uri, opts), do: {:ok, opts} + defp maybe_add_with_body(opts) do + if opts[:max_body] do + Keyword.put(opts, :with_body, true) + else + opts + end + end end diff --git a/lib/pleroma/http/ex_aws.ex b/lib/pleroma/http/ex_aws.ex index c3f335c73..5cac3532f 100644 --- a/lib/pleroma/http/ex_aws.ex +++ b/lib/pleroma/http/ex_aws.ex @@ -11,7 +11,7 @@ defmodule Pleroma.HTTP.ExAws do @impl true def request(method, url, body \\ "", headers \\ [], http_opts \\ []) do - http_opts = Keyword.put_new(http_opts, :adapter, pool: :upload) + http_opts = Keyword.put_new(http_opts, :pool, :upload) case HTTP.request(method, url, body, headers, http_opts) do {:ok, env} -> diff --git a/lib/pleroma/http/http.ex b/lib/pleroma/http/http.ex index b37b3fa89..052597191 100644 --- a/lib/pleroma/http/http.ex +++ b/lib/pleroma/http/http.ex @@ -60,30 +60,23 @@ def post(url, body, headers \\ [], options \\ []), {:ok, Env.t()} | {:error, any()} def request(method, url, body, headers, options) when is_binary(url) do uri = URI.parse(url) - adapter_opts = AdapterHelper.options(uri, options[:adapter] || []) + adapter_opts = AdapterHelper.options(uri, options || []) - case AdapterHelper.get_conn(uri, adapter_opts) do - {:ok, adapter_opts} -> - options = put_in(options[:adapter], adapter_opts) - params = options[:params] || [] - request = build_request(method, headers, options, url, body, params) + options = put_in(options[:adapter], adapter_opts) + params = options[:params] || [] + request = build_request(method, headers, options, url, body, params) - adapter = Application.get_env(:tesla, :adapter) + adapter = Application.get_env(:tesla, :adapter) - client = Tesla.client(adapter_middlewares(adapter), adapter) + client = Tesla.client(adapter_middlewares(adapter), adapter) - maybe_limit( - fn -> - request(client, request) - end, - adapter, - adapter_opts - ) - - # Connection release is handled in a custom FollowRedirects middleware - err -> - err - end + maybe_limit( + fn -> + request(client, request) + end, + adapter, + adapter_opts + ) end @spec request(Client.t(), keyword()) :: {:ok, Env.t()} | {:error, any()} @@ -110,7 +103,7 @@ defp maybe_limit(fun, _, _) do end defp adapter_middlewares(Tesla.Adapter.Gun) do - [Pleroma.HTTP.Middleware.FollowRedirects] + [Tesla.Middleware.FollowRedirects, Pleroma.Tesla.Middleware.ConnectionPool] end defp adapter_middlewares(_), do: [] diff --git a/lib/pleroma/http/tzdata.ex b/lib/pleroma/http/tzdata.ex index 4539ac359..09cfdadf7 100644 --- a/lib/pleroma/http/tzdata.ex +++ b/lib/pleroma/http/tzdata.ex @@ -11,7 +11,7 @@ defmodule Pleroma.HTTP.Tzdata do @impl true def get(url, headers, options) do - options = Keyword.put_new(options, :adapter, pool: :default) + options = Keyword.put_new(options, :pool, :default) with {:ok, %Tesla.Env{} = env} <- HTTP.get(url, headers, options) do {:ok, {env.status, env.headers, env.body}} @@ -20,7 +20,7 @@ def get(url, headers, options) do @impl true def head(url, headers, options) do - options = Keyword.put_new(options, :adapter, pool: :default) + options = Keyword.put_new(options, :pool, :default) with {:ok, %Tesla.Env{} = env} <- HTTP.head(url, headers, options) do {:ok, {env.status, env.headers}} diff --git a/lib/pleroma/instances/instance.ex b/lib/pleroma/instances/instance.ex index 8bf53c090..f0f601469 100644 --- a/lib/pleroma/instances/instance.ex +++ b/lib/pleroma/instances/instance.ex @@ -156,16 +156,12 @@ def get_or_update_favicon(%URI{host: host} = instance_uri) do defp scrape_favicon(%URI{} = instance_uri) do try do with {:ok, %Tesla.Env{body: html}} <- - Pleroma.HTTP.get(to_string(instance_uri), [{"accept", "text/html"}], - adapter: [pool: :media] - ), - favicon_rel <- - html - |> Floki.parse_document!() - |> Floki.attribute("link[rel=icon]", "href") - |> List.first(), - favicon <- URI.merge(instance_uri, favicon_rel) |> to_string(), - true <- is_binary(favicon) do + Pleroma.HTTP.get(to_string(instance_uri), [{"accept", "text/html"}], pool: :media), + {_, [favicon_rel | _]} when is_binary(favicon_rel) <- + {:parse, + html |> Floki.parse_document!() |> Floki.attribute("link[rel=icon]", "href")}, + {_, favicon} when is_binary(favicon) <- + {:merge, URI.merge(instance_uri, favicon_rel) |> to_string()} do favicon else _ -> nil diff --git a/lib/pleroma/mfa/token.ex b/lib/pleroma/mfa/token.ex index 0b2449971..69b64c0e8 100644 --- a/lib/pleroma/mfa/token.ex +++ b/lib/pleroma/mfa/token.ex @@ -10,10 +10,11 @@ defmodule Pleroma.MFA.Token do alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.OAuth.Authorization - alias Pleroma.Web.OAuth.Token, as: OAuthToken @expires 300 + @type t() :: %__MODULE__{} + schema "mfa_tokens" do field(:token, :string) field(:valid_until, :naive_datetime_usec) @@ -24,6 +25,7 @@ defmodule Pleroma.MFA.Token do timestamps() end + @spec get_by_token(String.t()) :: {:ok, t()} | {:error, :not_found} def get_by_token(token) do from( t in __MODULE__, @@ -33,33 +35,40 @@ def get_by_token(token) do |> Repo.find_resource() end - def validate(token) do - with {:fetch_token, {:ok, token}} <- {:fetch_token, get_by_token(token)}, - {:expired, false} <- {:expired, is_expired?(token)} do + @spec validate(String.t()) :: {:ok, t()} | {:error, :not_found} | {:error, :expired_token} + def validate(token_str) do + with {:ok, token} <- get_by_token(token_str), + false <- expired?(token) do {:ok, token} - else - {:expired, _} -> {:error, :expired_token} - {:fetch_token, _} -> {:error, :not_found} - error -> {:error, error} end end - def create_token(%User{} = user) do - %__MODULE__{} - |> change - |> assign_user(user) - |> put_token - |> put_valid_until - |> Repo.insert() + defp expired?(%__MODULE__{valid_until: valid_until}) do + with true <- NaiveDateTime.diff(NaiveDateTime.utc_now(), valid_until) > 0 do + {:error, :expired_token} + end end - def create_token(user, authorization) do + @spec create(User.t(), Authorization.t() | nil) :: {:ok, t()} | {:error, Ecto.Changeset.t()} + def create(user, authorization \\ nil) do + with {:ok, token} <- do_create(user, authorization) do + Pleroma.Workers.PurgeExpiredToken.enqueue(%{ + token_id: token.id, + valid_until: DateTime.from_naive!(token.valid_until, "Etc/UTC"), + mod: __MODULE__ + }) + + {:ok, token} + end + end + + defp do_create(user, authorization) do %__MODULE__{} - |> change + |> change() |> assign_user(user) - |> assign_authorization(authorization) - |> put_token - |> put_valid_until + |> maybe_assign_authorization(authorization) + |> put_token() + |> put_valid_until() |> Repo.insert() end @@ -69,15 +78,19 @@ defp assign_user(changeset, user) do |> validate_required([:user]) end - defp assign_authorization(changeset, authorization) do + defp maybe_assign_authorization(changeset, %Authorization{} = authorization) do changeset |> put_assoc(:authorization, authorization) |> validate_required([:authorization]) end + defp maybe_assign_authorization(changeset, _), do: changeset + defp put_token(changeset) do + token = Pleroma.Web.OAuth.Token.Utils.generate_token() + changeset - |> change(%{token: OAuthToken.Utils.generate_token()}) + |> change(%{token: token}) |> validate_required([:token]) |> unique_constraint(:token) end @@ -89,18 +102,4 @@ defp put_valid_until(changeset) do |> change(%{valid_until: expires_in}) |> validate_required([:valid_until]) end - - def is_expired?(%__MODULE__{valid_until: valid_until}) do - NaiveDateTime.diff(NaiveDateTime.utc_now(), valid_until) > 0 - end - - def is_expired?(_), do: false - - def delete_expired_tokens do - from( - q in __MODULE__, - where: fragment("?", q.valid_until) < ^Timex.now() - ) - |> Repo.delete_all() - end end diff --git a/lib/pleroma/migration_helper/notification_backfill.ex b/lib/pleroma/migration_helper/notification_backfill.ex index d260e62ca..24f4733fe 100644 --- a/lib/pleroma/migration_helper/notification_backfill.ex +++ b/lib/pleroma/migration_helper/notification_backfill.ex @@ -19,13 +19,13 @@ def fill_in_notification_types do query |> Repo.chunk_stream(100) |> Enum.each(fn notification -> - type = - notification.activity - |> type_from_activity() + if notification.activity do + type = type_from_activity(notification.activity) - notification - |> Ecto.Changeset.change(%{type: type}) - |> Repo.update() + notification + |> Ecto.Changeset.change(%{type: type}) + |> Repo.update() + end end) end @@ -72,8 +72,7 @@ defp type_from_activity(%{data: %{"type" => type}} = activity) do "pleroma:emoji_reaction" "Create" -> - activity - |> type_from_activity_object() + type_from_activity_object(activity) t -> raise "No notification type for activity type #{t}" diff --git a/lib/pleroma/moderation_log.ex b/lib/pleroma/moderation_log.ex index 31c9afe2a..47036a6f6 100644 --- a/lib/pleroma/moderation_log.ex +++ b/lib/pleroma/moderation_log.ex @@ -320,6 +320,19 @@ def insert_log(%{ |> insert_log_entry_with_message() end + @spec insert_log(%{actor: User, action: String.t(), subject_id: String.t()}) :: + {:ok, ModerationLog} | {:error, any} + def insert_log(%{actor: %User{} = actor, action: "chat_message_delete", subject_id: subject_id}) do + %ModerationLog{ + data: %{ + "actor" => %{"nickname" => actor.nickname}, + "action" => "chat_message_delete", + "subject_id" => subject_id + } + } + |> insert_log_entry_with_message() + end + @spec insert_log_entry_with_message(ModerationLog) :: {:ok, ModerationLog} | {:error, any} defp insert_log_entry_with_message(entry) do entry.data["message"] @@ -627,6 +640,17 @@ def get_log_entry_message(%ModerationLog{ "@#{actor_nickname} updated users: #{users_to_nicknames_string(subjects)}" end + @spec get_log_entry_message(ModerationLog) :: String.t() + def get_log_entry_message(%ModerationLog{ + data: %{ + "actor" => %{"nickname" => actor_nickname}, + "action" => "chat_message_delete", + "subject_id" => subject_id + } + }) do + "@#{actor_nickname} deleted chat message ##{subject_id}" + end + defp nicknames_to_string(nicknames) do nicknames |> Enum.map(&"@#{&1}") diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index c1825f810..8868a910e 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -648,4 +648,16 @@ def for_user_and_activity(user, activity) do ) |> Repo.one() end + + @spec mark_context_as_read(User.t(), String.t()) :: {integer(), nil | [term()]} + def mark_context_as_read(%User{id: id}, context) do + from( + n in Notification, + join: a in assoc(n, :activity), + where: n.user_id == ^id, + where: n.seen == false, + where: fragment("?->>'context'", a.data) == ^context + ) + |> Repo.update_all(set: [seen: true]) + end end diff --git a/lib/pleroma/object/containment.ex b/lib/pleroma/object/containment.ex index bc88e8a0c..29cb3d672 100644 --- a/lib/pleroma/object/containment.ex +++ b/lib/pleroma/object/containment.ex @@ -44,13 +44,6 @@ def get_object(_) do nil end - # TODO: We explicitly allow 'tag' URIs through, due to references to legacy OStatus - # objects being present in the test suite environment. Once these objects are - # removed, please also remove this. - if Mix.env() == :test do - defp compare_uris(_, %URI{scheme: "tag"}), do: :ok - end - defp compare_uris(%URI{host: host} = _id_uri, %URI{host: host} = _other_uri), do: :ok defp compare_uris(_id_uri, _other_uri), do: :error diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index 374d8704a..169298b34 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -12,6 +12,7 @@ defmodule Pleroma.Object.Fetcher do alias Pleroma.Web.ActivityPub.ObjectValidator alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.Federator + alias Pleroma.Web.FedSockets require Logger require Pleroma.Constants @@ -36,8 +37,7 @@ defp maybe_reinject_internal_fields(_, new_data), do: new_data defp reinject_object(%Object{data: %{"type" => "Question"}} = object, new_data) do Logger.debug("Reinjecting object #{new_data["id"]}") - with new_data <- Transmogrifier.fix_object(new_data), - data <- maybe_reinject_internal_fields(object, new_data), + with data <- maybe_reinject_internal_fields(object, new_data), {:ok, data, _} <- ObjectValidator.validate(data, %{}), changeset <- Object.change(object, %{data: data}), changeset <- touch_changeset(changeset), @@ -99,8 +99,8 @@ def fetch_object_from_id(id, options \\ []) do {:containment, _} -> {:error, "Object containment failed."} - {:transmogrifier, {:error, {:reject, nil}}} -> - {:reject, nil} + {:transmogrifier, {:error, {:reject, e}}} -> + {:reject, e} {:transmogrifier, _} = e -> {:error, e} @@ -183,27 +183,20 @@ defp maybe_date_fetch(headers, date) do end end - def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do + def fetch_and_contain_remote_object_from_id(prm, opts \\ []) + + def fetch_and_contain_remote_object_from_id(%{"id" => id}, opts), + do: fetch_and_contain_remote_object_from_id(id, opts) + + def fetch_and_contain_remote_object_from_id(id, opts) when is_binary(id) do Logger.debug("Fetching object #{id} via AP") - date = Pleroma.Signature.signed_date() - - headers = - [{"accept", "application/activity+json"}] - |> maybe_date_fetch(date) - |> sign_fetch(id, date) - - Logger.debug("Fetch headers: #{inspect(headers)}") - with {:scheme, true} <- {:scheme, String.starts_with?(id, "http")}, - {:ok, %{body: body, status: code}} when code in 200..299 <- HTTP.get(id, headers), - {:ok, data} <- Jason.decode(body), + {:ok, body} <- get_object(id, opts), + {:ok, data} <- safe_json_decode(body), :ok <- Containment.contain_origin_from_id(id, data) do {:ok, data} else - {:ok, %{status: code}} when code in [404, 410] -> - {:error, "Object has been deleted"} - {:scheme, _} -> {:error, "Unsupported URI scheme"} @@ -215,8 +208,44 @@ def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do end end - def fetch_and_contain_remote_object_from_id(%{"id" => id}), - do: fetch_and_contain_remote_object_from_id(id) + def fetch_and_contain_remote_object_from_id(_id, _opts), + do: {:error, "id must be a string"} - def fetch_and_contain_remote_object_from_id(_id), do: {:error, "id must be a string"} + defp get_object(id, opts) do + with false <- Keyword.get(opts, :force_http, false), + {:ok, fedsocket} <- FedSockets.get_or_create_fed_socket(id) do + Logger.debug("fetching via fedsocket - #{inspect(id)}") + FedSockets.fetch(fedsocket, id) + else + _other -> + Logger.debug("fetching via http - #{inspect(id)}") + get_object_http(id) + end + end + + defp get_object_http(id) do + date = Pleroma.Signature.signed_date() + + headers = + [{"accept", "application/activity+json"}] + |> maybe_date_fetch(date) + |> sign_fetch(id, date) + + case HTTP.get(id, headers) do + {:ok, %{body: body, status: code}} when code in 200..299 -> + {:ok, body} + + {:ok, %{status: code}} when code in [404, 410] -> + {:error, "Object has been deleted"} + + {:error, e} -> + {:error, e} + + e -> + {:error, e} + end + end + + defp safe_json_decode(nil), do: {:ok, nil} + defp safe_json_decode(json), do: Jason.decode(json) end diff --git a/lib/pleroma/plugs/oauth_scopes_plug.ex b/lib/pleroma/plugs/oauth_scopes_plug.ex index efc25b79f..b1a736d78 100644 --- a/lib/pleroma/plugs/oauth_scopes_plug.ex +++ b/lib/pleroma/plugs/oauth_scopes_plug.ex @@ -53,7 +53,7 @@ def drop_auth_info(conn) do |> assign(:token, nil) end - @doc "Filters descendants of supported scopes" + @doc "Keeps those of `scopes` which are descendants of `supported_scopes`" def filter_descendants(scopes, supported_scopes) do Enum.filter( scopes, diff --git a/lib/pleroma/repo.ex b/lib/pleroma/repo.ex index f317e4d58..4524bd5e2 100644 --- a/lib/pleroma/repo.ex +++ b/lib/pleroma/repo.ex @@ -49,7 +49,21 @@ def get_assoc(resource, association) do end end - def chunk_stream(query, chunk_size) do + @doc """ + Returns a lazy enumerable that emits all entries from the data store matching the given query. + + `returns_as` use to group records. use the `batches` option to fetch records in bulk. + + ## Examples + + # fetch records one-by-one + iex> Pleroma.Repo.chunk_stream(Pleroma.Activity.Queries.by_actor(ap_id), 500) + + # fetch records in bulk + iex> Pleroma.Repo.chunk_stream(Pleroma.Activity.Queries.by_actor(ap_id), 500, :batches) + """ + @spec chunk_stream(Ecto.Query.t(), integer(), atom()) :: Enumerable.t() + def chunk_stream(query, chunk_size, returns_as \\ :one) do # We don't actually need start and end funcitons of resource streaming, # but it seems to be the only way to not fetch records one-by-one and # have individual records be the elements of the stream, instead of @@ -69,7 +83,12 @@ def chunk_stream(query, chunk_size) do records -> last_id = List.last(records).id - {records, last_id} + + if returns_as == :one do + {records, last_id} + else + {[records], last_id} + end end end, fn _ -> :ok end diff --git a/lib/pleroma/repo_streamer.ex b/lib/pleroma/repo_streamer.ex deleted file mode 100644 index cb4d7bb7a..000000000 --- a/lib/pleroma/repo_streamer.ex +++ /dev/null @@ -1,34 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.RepoStreamer do - alias Pleroma.Repo - import Ecto.Query - - def chunk_stream(query, chunk_size) do - Stream.unfold(0, fn - :halt -> - {[], :halt} - - last_id -> - query - |> order_by(asc: :id) - |> where([r], r.id > ^last_id) - |> limit(^chunk_size) - |> Repo.all() - |> case do - [] -> - {[], :halt} - - records -> - last_id = List.last(records).id - {records, last_id} - end - end) - |> Stream.take_while(fn - [] -> false - _ -> true - end) - end -end diff --git a/lib/pleroma/reverse_proxy/client/tesla.ex b/lib/pleroma/reverse_proxy/client/tesla.ex index d5a339681..4b118eec2 100644 --- a/lib/pleroma/reverse_proxy/client/tesla.ex +++ b/lib/pleroma/reverse_proxy/client/tesla.ex @@ -28,7 +28,7 @@ def request(method, url, headers, body, opts \\ []) do url, body, headers, - Keyword.put(opts, :adapter, opts) + opts ) do if is_map(response.body) and method != :head do {:ok, response.status, response.headers, response.body} diff --git a/lib/pleroma/reverse_proxy/reverse_proxy.ex b/lib/pleroma/reverse_proxy/reverse_proxy.ex index 0de4e2309..8ae1157df 100644 --- a/lib/pleroma/reverse_proxy/reverse_proxy.ex +++ b/lib/pleroma/reverse_proxy/reverse_proxy.ex @@ -17,6 +17,9 @@ defmodule Pleroma.ReverseProxy do @failed_request_ttl :timer.seconds(60) @methods ~w(GET HEAD) + def max_read_duration_default, do: @max_read_duration + def default_cache_control_header, do: @default_cache_control_header + @moduledoc """ A reverse proxy. @@ -391,6 +394,8 @@ defp body_size_constraint(size, limit) when is_integer(limit) and limit > 0 and defp body_size_constraint(_, _), do: :ok + defp check_read_duration(nil = _duration, max), do: check_read_duration(@max_read_duration, max) + defp check_read_duration(duration, max) when is_integer(duration) and is_integer(max) and max > 0 do if duration > max do diff --git a/lib/pleroma/signature.ex b/lib/pleroma/signature.ex index 3aa6909d2..e388993b7 100644 --- a/lib/pleroma/signature.ex +++ b/lib/pleroma/signature.ex @@ -39,7 +39,7 @@ def key_id_to_actor_id(key_id) do def fetch_public_key(conn) do with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn), {:ok, actor_id} <- key_id_to_actor_id(kid), - {:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do + {:ok, public_key} <- User.get_public_key_for_ap_id(actor_id, force_http: true) do {:ok, public_key} else e -> @@ -50,8 +50,8 @@ def fetch_public_key(conn) do def refetch_public_key(conn) do with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn), {:ok, actor_id} <- key_id_to_actor_id(kid), - {:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id), - {:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do + {:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id, force_http: true), + {:ok, public_key} <- User.get_public_key_for_ap_id(actor_id, force_http: true) do {:ok, public_key} else e -> diff --git a/lib/pleroma/stats.ex b/lib/pleroma/stats.ex index 9a03f01db..e5c9c668b 100644 --- a/lib/pleroma/stats.ex +++ b/lib/pleroma/stats.ex @@ -3,12 +3,15 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Stats do + use GenServer + import Ecto.Query + alias Pleroma.CounterCache alias Pleroma.Repo alias Pleroma.User - use GenServer + @interval :timer.seconds(60) def start_link(_) do GenServer.start_link( @@ -18,6 +21,12 @@ def start_link(_) do ) end + @impl true + def init(_args) do + if Pleroma.Config.get(:env) == :test, do: :ok = Ecto.Adapters.SQL.Sandbox.checkout(Repo) + {:ok, nil, {:continue, :calculate_stats}} + end + @doc "Performs update stats" def force_update do GenServer.call(__MODULE__, :force_update) @@ -29,7 +38,11 @@ def do_collect do end @doc "Returns stats data" - @spec get_stats() :: %{domain_count: integer(), status_count: integer(), user_count: integer()} + @spec get_stats() :: %{ + domain_count: non_neg_integer(), + status_count: non_neg_integer(), + user_count: non_neg_integer() + } def get_stats do %{stats: stats} = GenServer.call(__MODULE__, :get_state) @@ -44,25 +57,14 @@ def get_peers do peers end - def init(_args) do - {:ok, calculate_stat_data()} - end - - def handle_call(:force_update, _from, _state) do - new_stats = calculate_stat_data() - {:reply, new_stats, new_stats} - end - - def handle_call(:get_state, _from, state) do - {:reply, state, state} - end - - def handle_cast(:run_update, _state) do - new_stats = calculate_stat_data() - - {:noreply, new_stats} - end - + @spec calculate_stat_data() :: %{ + peers: list(), + stats: %{ + domain_count: non_neg_integer(), + status_count: non_neg_integer(), + user_count: non_neg_integer() + } + } def calculate_stat_data do peers = from( @@ -97,6 +99,7 @@ def calculate_stat_data do } end + @spec get_status_visibility_count(String.t() | nil) :: map() def get_status_visibility_count(instance \\ nil) do if is_nil(instance) do CounterCache.get_sum() @@ -104,4 +107,36 @@ def get_status_visibility_count(instance \\ nil) do CounterCache.get_by_instance(instance) end end + + @impl true + def handle_continue(:calculate_stats, _) do + stats = calculate_stat_data() + Process.send_after(self(), :run_update, @interval) + {:noreply, stats} + end + + @impl true + def handle_call(:force_update, _from, _state) do + new_stats = calculate_stat_data() + {:reply, new_stats, new_stats} + end + + @impl true + def handle_call(:get_state, _from, state) do + {:reply, state, state} + end + + @impl true + def handle_cast(:run_update, _state) do + new_stats = calculate_stat_data() + + {:noreply, new_stats} + end + + @impl true + def handle_info(:run_update, _) do + new_stats = calculate_stat_data() + Process.send_after(self(), :run_update, @interval) + {:noreply, new_stats} + end end diff --git a/lib/pleroma/telemetry/logger.ex b/lib/pleroma/telemetry/logger.ex index 4cacae02f..197b1d091 100644 --- a/lib/pleroma/telemetry/logger.ex +++ b/lib/pleroma/telemetry/logger.ex @@ -7,7 +7,8 @@ defmodule Pleroma.Telemetry.Logger do [:pleroma, :connection_pool, :reclaim, :start], [:pleroma, :connection_pool, :reclaim, :stop], [:pleroma, :connection_pool, :provision_failure], - [:pleroma, :connection_pool, :client_death] + [:pleroma, :connection_pool, :client, :dead], + [:pleroma, :connection_pool, :client, :add] ] def attach do :telemetry.attach_many("pleroma-logger", @events, &handle_event/4, []) @@ -62,7 +63,7 @@ def handle_event( end def handle_event( - [:pleroma, :connection_pool, :client_death], + [:pleroma, :connection_pool, :client, :dead], %{client_pid: client_pid, reason: reason}, %{key: key}, _ @@ -73,4 +74,17 @@ def handle_event( }" end) end + + def handle_event( + [:pleroma, :connection_pool, :client, :add], + %{clients: [_, _ | _] = clients}, + %{key: key, protocol: :http}, + _ + ) do + Logger.info(fn -> + "Pool worker for #{key}: #{length(clients)} clients are using an HTTP1 connection at the same time, head-of-line blocking might occur." + end) + end + + def handle_event([:pleroma, :connection_pool, :client, :add], _, _, _), do: :ok end diff --git a/lib/pleroma/tesla/middleware/connection_pool.ex b/lib/pleroma/tesla/middleware/connection_pool.ex new file mode 100644 index 000000000..056e736ce --- /dev/null +++ b/lib/pleroma/tesla/middleware/connection_pool.ex @@ -0,0 +1,50 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Tesla.Middleware.ConnectionPool do + @moduledoc """ + Middleware to get/release connections from `Pleroma.Gun.ConnectionPool` + """ + + @behaviour Tesla.Middleware + + alias Pleroma.Gun.ConnectionPool + + @impl Tesla.Middleware + def call(%Tesla.Env{url: url, opts: opts} = env, next, _) do + uri = URI.parse(url) + + # Avoid leaking connections when the middleware is called twice + # with body_as: :chunks. We assume only the middleware can set + # opts[:adapter][:conn] + if opts[:adapter][:conn] do + ConnectionPool.release_conn(opts[:adapter][:conn]) + end + + case ConnectionPool.get_conn(uri, opts[:adapter]) do + {:ok, conn_pid} -> + adapter_opts = Keyword.merge(opts[:adapter], conn: conn_pid, close_conn: false) + opts = Keyword.put(opts, :adapter, adapter_opts) + env = %{env | opts: opts} + + case Tesla.run(env, next) do + {:ok, env} -> + unless opts[:adapter][:body_as] == :chunks do + ConnectionPool.release_conn(conn_pid) + {_, res} = pop_in(env.opts[:adapter][:conn]) + {:ok, res} + else + {:ok, env} + end + + err -> + ConnectionPool.release_conn(conn_pid) + err + end + + err -> + err + end + end +end diff --git a/lib/pleroma/tesla/middleware/follow_redirects.ex b/lib/pleroma/tesla/middleware/follow_redirects.ex deleted file mode 100644 index 5a7032215..000000000 --- a/lib/pleroma/tesla/middleware/follow_redirects.ex +++ /dev/null @@ -1,110 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2015-2020 Tymon Tobolski -# Copyright © 2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.HTTP.Middleware.FollowRedirects do - @moduledoc """ - Pool-aware version of https://github.com/teamon/tesla/blob/master/lib/tesla/middleware/follow_redirects.ex - - Follow 3xx redirects - ## Options - - `:max_redirects` - limit number of redirects (default: `5`) - """ - - alias Pleroma.Gun.ConnectionPool - - @behaviour Tesla.Middleware - - @max_redirects 5 - @redirect_statuses [301, 302, 303, 307, 308] - - @impl Tesla.Middleware - def call(env, next, opts \\ []) do - max = Keyword.get(opts, :max_redirects, @max_redirects) - - redirect(env, next, max) - end - - defp redirect(env, next, left) do - opts = env.opts[:adapter] - - case Tesla.run(env, next) do - {:ok, %{status: status} = res} when status in @redirect_statuses and left > 0 -> - release_conn(opts) - - case Tesla.get_header(res, "location") do - nil -> - {:ok, res} - - location -> - location = parse_location(location, res) - - case get_conn(location, opts) do - {:ok, opts} -> - %{env | opts: Keyword.put(env.opts, :adapter, opts)} - |> new_request(res.status, location) - |> redirect(next, left - 1) - - e -> - e - end - end - - {:ok, %{status: status}} when status in @redirect_statuses -> - release_conn(opts) - {:error, {__MODULE__, :too_many_redirects}} - - {:error, _} = e -> - release_conn(opts) - e - - other -> - unless opts[:body_as] == :chunks do - release_conn(opts) - end - - other - end - end - - defp get_conn(location, opts) do - uri = URI.parse(location) - - case ConnectionPool.get_conn(uri, opts) do - {:ok, conn} -> - {:ok, Keyword.merge(opts, conn: conn)} - - e -> - e - end - end - - defp release_conn(opts) do - ConnectionPool.release_conn(opts[:conn]) - end - - # The 303 (See Other) redirect was added in HTTP/1.1 to indicate that the originally - # requested resource is not available, however a related resource (or another redirect) - # available via GET is available at the specified location. - # https://tools.ietf.org/html/rfc7231#section-6.4.4 - defp new_request(env, 303, location), do: %{env | url: location, method: :get, query: []} - - # The 307 (Temporary Redirect) status code indicates that the target - # resource resides temporarily under a different URI and the user agent - # MUST NOT change the request method (...) - # https://tools.ietf.org/html/rfc7231#section-6.4.7 - defp new_request(env, 307, location), do: %{env | url: location} - - defp new_request(env, _, location), do: %{env | url: location, query: []} - - defp parse_location("https://" <> _rest = location, _env), do: location - defp parse_location("http://" <> _rest = location, _env), do: location - - defp parse_location(location, env) do - env.url - |> URI.parse() - |> URI.merge(location) - |> URI.to_string() - end -end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index ce5f4bc4a..410c9cbac 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -25,7 +25,6 @@ defmodule Pleroma.User do alias Pleroma.Object alias Pleroma.Registration alias Pleroma.Repo - alias Pleroma.RepoStreamer alias Pleroma.User alias Pleroma.UserRelationship alias Pleroma.Web @@ -83,7 +82,7 @@ defmodule Pleroma.User do ] schema "users" do - field(:bio, :string) + field(:bio, :string, default: "") field(:raw_bio, :string) field(:email, :string) field(:name, :string) @@ -276,9 +275,9 @@ def binary_id(%User{} = user), do: binary_id(user.id) @spec account_status(User.t()) :: account_status() def account_status(%User{deactivated: true}), do: :deactivated def account_status(%User{password_reset_pending: true}), do: :password_reset_pending - def account_status(%User{approval_pending: true}), do: :approval_pending + def account_status(%User{local: true, approval_pending: true}), do: :approval_pending - def account_status(%User{confirmation_pending: true}) do + def account_status(%User{local: true, confirmation_pending: true}) do if Config.get([:instance, :account_activation_required]) do :confirmation_pending else @@ -1587,7 +1586,7 @@ def purge_user_changeset(user) do # "Right to be forgotten" # https://gdpr.eu/right-to-be-forgotten/ change(user, %{ - bio: nil, + bio: "", raw_bio: nil, email: nil, name: nil, @@ -1686,42 +1685,6 @@ def perform(:delete, %User{} = user) do def perform(:deactivate_async, user, status), do: deactivate(user, status) - @spec perform(atom(), User.t(), list()) :: list() | {:error, any()} - def perform(:blocks_import, %User{} = blocker, blocked_identifiers) - when is_list(blocked_identifiers) do - Enum.map( - blocked_identifiers, - fn blocked_identifier -> - with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier), - {:ok, _block} <- CommonAPI.block(blocker, blocked) do - blocked - else - err -> - Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}") - err - end - end - ) - end - - def perform(:follow_import, %User{} = follower, followed_identifiers) - when is_list(followed_identifiers) do - Enum.map( - followed_identifiers, - fn followed_identifier -> - with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier), - {:ok, follower} <- maybe_direct_follow(follower, followed), - {:ok, _, _, _} <- CommonAPI.follow(follower, followed) do - followed - else - err -> - Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}") - err - end - end - ) - end - @spec external_users_query() :: Ecto.Query.t() def external_users_query do User.Query.build(%{ @@ -1750,21 +1713,6 @@ def external_users(opts \\ []) do Repo.all(query) end - def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do - BackgroundWorker.enqueue("blocks_import", %{ - "blocker_id" => blocker.id, - "blocked_identifiers" => blocked_identifiers - }) - end - - def follow_import(%User{} = follower, followed_identifiers) - when is_list(followed_identifiers) do - BackgroundWorker.enqueue("follow_import", %{ - "follower_id" => follower.id, - "followed_identifiers" => followed_identifiers - }) - end - def delete_notifications_from_user_activities(%User{ap_id: ap_id}) do Notification |> join(:inner, [n], activity in assoc(n, :activity)) @@ -1775,7 +1723,7 @@ def delete_notifications_from_user_activities(%User{ap_id: ap_id}) do def delete_user_activities(%User{ap_id: ap_id} = user) do ap_id |> Activity.Queries.by_actor() - |> RepoStreamer.chunk_stream(50) + |> Repo.chunk_stream(50, :batches) |> Stream.each(fn activities -> Enum.each(activities, fn activity -> delete_activity(activity, user) end) end) @@ -1821,12 +1769,12 @@ def html_filter_policy(%User{no_rich_text: true}) do def html_filter_policy(_), do: Config.get([:markup, :scrub_policy]) - def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id) + def fetch_by_ap_id(ap_id, opts \\ []), do: ActivityPub.make_user_from_ap_id(ap_id, opts) - def get_or_fetch_by_ap_id(ap_id) do + def get_or_fetch_by_ap_id(ap_id, opts \\ []) do cached_user = get_cached_by_ap_id(ap_id) - maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id) + maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id, opts) case {cached_user, maybe_fetched_user} do {_, {:ok, %User{} = user}} -> @@ -1899,8 +1847,8 @@ def public_key(%{public_key: public_key_pem}) when is_binary(public_key_pem) do def public_key(_), do: {:error, "key not found"} - def get_public_key_for_ap_id(ap_id) do - with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id), + def get_public_key_for_ap_id(ap_id, opts \\ []) do + with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id, opts), {:ok, public_key} <- public_key(user) do {:ok, public_key} else @@ -2315,6 +2263,11 @@ def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0) params = %{pinned_activities: user.pinned_activities ++ [id]} + # if pinned activity was scheduled for deletion, we remove job + if expiration = Pleroma.Workers.PurgeExpiredActivity.get_expiration(id) do + Oban.cancel_job(expiration.id) + end + user |> cast(params, [:pinned_activities]) |> validate_length(:pinned_activities, @@ -2327,9 +2280,19 @@ def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do |> update_and_set_cache() end - def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do + def remove_pinnned_activity(user, %Pleroma.Activity{id: id, data: data}) do params = %{pinned_activities: List.delete(user.pinned_activities, id)} + # if pinned activity was scheduled for deletion, we reschedule it for deletion + if data["expires_at"] do + {:ok, expires_at, _} = DateTime.from_iso8601(data["expires_at"]) + + Pleroma.Workers.PurgeExpiredActivity.enqueue(%{ + activity_id: id, + expires_at: expires_at + }) + end + user |> cast(params, [:pinned_activities]) |> update_and_set_cache() diff --git a/lib/pleroma/user/import.ex b/lib/pleroma/user/import.ex new file mode 100644 index 000000000..e458021c8 --- /dev/null +++ b/lib/pleroma/user/import.ex @@ -0,0 +1,85 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.User.Import do + use Ecto.Schema + + alias Pleroma.User + alias Pleroma.Web.CommonAPI + alias Pleroma.Workers.BackgroundWorker + + require Logger + + @spec perform(atom(), User.t(), list()) :: :ok | list() | {:error, any()} + def perform(:mutes_import, %User{} = user, [_ | _] = identifiers) do + Enum.map( + identifiers, + fn identifier -> + with {:ok, %User{} = muted_user} <- User.get_or_fetch(identifier), + {:ok, _} <- User.mute(user, muted_user) do + muted_user + else + error -> handle_error(:mutes_import, identifier, error) + end + end + ) + end + + def perform(:blocks_import, %User{} = blocker, [_ | _] = identifiers) do + Enum.map( + identifiers, + fn identifier -> + with {:ok, %User{} = blocked} <- User.get_or_fetch(identifier), + {:ok, _block} <- CommonAPI.block(blocker, blocked) do + blocked + else + error -> handle_error(:blocks_import, identifier, error) + end + end + ) + end + + def perform(:follow_import, %User{} = follower, [_ | _] = identifiers) do + Enum.map( + identifiers, + fn identifier -> + with {:ok, %User{} = followed} <- User.get_or_fetch(identifier), + {:ok, follower} <- User.maybe_direct_follow(follower, followed), + {:ok, _, _, _} <- CommonAPI.follow(follower, followed) do + followed + else + error -> handle_error(:follow_import, identifier, error) + end + end + ) + end + + def perform(_, _, _), do: :ok + + defp handle_error(op, user_id, error) do + Logger.debug("#{op} failed for #{user_id} with: #{inspect(error)}") + error + end + + def blocks_import(%User{} = blocker, [_ | _] = identifiers) do + BackgroundWorker.enqueue( + "blocks_import", + %{"user_id" => blocker.id, "identifiers" => identifiers} + ) + end + + def follow_import(%User{} = follower, [_ | _] = identifiers) do + BackgroundWorker.enqueue( + "follow_import", + %{"user_id" => follower.id, "identifiers" => identifiers} + ) + end + + def mutes_import(%User{} = user, [_ | _] = identifiers) do + BackgroundWorker.enqueue( + "mutes_import", + %{"user_id" => user.id, "identifiers" => identifiers} + ) + end +end diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex index 7babd47ea..b8c648672 100644 --- a/lib/pleroma/user/search.ex +++ b/lib/pleroma/user/search.ex @@ -52,6 +52,7 @@ defp search_query(query_string, for_user, following) do |> base_query(following) |> filter_blocked_user(for_user) |> filter_invisible_users() + |> filter_discoverable_users() |> filter_internal_users() |> filter_blocked_domains(for_user) |> fts_search(query_string) @@ -122,6 +123,10 @@ defp filter_invisible_users(query) do from(q in query, where: q.invisible == false) end + defp filter_discoverable_users(query) do + from(q in query, where: q.discoverable == true) + end + defp filter_internal_users(query) do from(q in query, where: q.actor_type != "Application") end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index e4eafc8ac..aacd58d03 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -5,7 +5,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do alias Pleroma.Activity alias Pleroma.Activity.Ir.Topics - alias Pleroma.ActivityExpiration alias Pleroma.Config alias Pleroma.Constants alias Pleroma.Conversation @@ -85,7 +84,7 @@ defp increase_replies_count_if_reply(%{ defp increase_replies_count_if_reply(_create_data), do: :noop - @object_types ~w[ChatMessage Question Answer Audio Event] + @object_types ~w[ChatMessage Question Answer Audio Video Event Article] @spec persist(map(), keyword()) :: {:ok, Activity.t() | Object.t()} def persist(%{"type" => type} = object, meta) when type in @object_types do with {:ok, object} <- Object.create(object) do @@ -102,7 +101,9 @@ def persist(object, meta) do local: local, recipients: recipients, actor: object["actor"] - }) do + }), + # TODO: add tests for expired activities, when Note type will be supported in new pipeline + {:ok, _} <- maybe_create_activity_expiration(activity) do {:ok, activity, meta} end end @@ -111,23 +112,14 @@ def persist(object, meta) do def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when is_map(map) do with nil <- Activity.normalize(map), map <- lazy_put_activity_defaults(map, fake), - true <- bypass_actor_check || check_actor_is_active(map["actor"]), - {_, true} <- {:remote_limit_error, check_remote_limit(map)}, + {_, true} <- {:actor_check, bypass_actor_check || check_actor_is_active(map["actor"])}, + {_, true} <- {:remote_limit_pass, check_remote_limit(map)}, {:ok, map} <- MRF.filter(map), {recipients, _, _} = get_recipients(map), {:fake, false, map, recipients} <- {:fake, fake, map, recipients}, {:containment, :ok} <- {:containment, Containment.contain_child(map)}, - {:ok, map, object} <- insert_full_object(map) do - {:ok, activity} = - %Activity{ - data: map, - local: local, - actor: map["actor"], - recipients: recipients - } - |> Repo.insert() - |> maybe_create_activity_expiration() - + {:ok, map, object} <- insert_full_object(map), + {:ok, activity} <- insert_activity_with_expiration(map, local, recipients) do # Splice in the child object if we have one. activity = Maps.put_if_present(activity, :object, object) @@ -138,6 +130,15 @@ def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when %Activity{} = activity -> {:ok, activity} + {:actor_check, _} -> + {:error, false} + + {:containment, _} = error -> + error + + {:error, _} = error -> + error + {:fake, true, map, recipients} -> activity = %Activity{ data: map, @@ -150,8 +151,24 @@ def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) {:ok, activity} - error -> - {:error, error} + {:remote_limit_pass, _} -> + {:error, :remote_limit} + + {:reject, _} = e -> + {:error, e} + end + end + + defp insert_activity_with_expiration(data, local, recipients) do + struct = %Activity{ + data: data, + local: local, + actor: data["actor"], + recipients: recipients + } + + with {:ok, activity} <- Repo.insert(struct) do + maybe_create_activity_expiration(activity) end end @@ -164,13 +181,19 @@ def notify_and_stream(activity) do stream_out_participations(participations) end - defp maybe_create_activity_expiration({:ok, %{data: %{"expires_at" => expires_at}} = activity}) do - with {:ok, _} <- ActivityExpiration.create(activity, expires_at) do + defp maybe_create_activity_expiration( + %{data: %{"expires_at" => %DateTime{} = expires_at}} = activity + ) do + with {:ok, _job} <- + Pleroma.Workers.PurgeExpiredActivity.enqueue(%{ + activity_id: activity.id, + expires_at: expires_at + }) do {:ok, activity} end end - defp maybe_create_activity_expiration(result), do: result + defp maybe_create_activity_expiration(activity), do: {:ok, activity} defp create_or_bump_conversation(activity, actor) do with {:ok, conversation} <- Conversation.create_or_bump_for(activity), @@ -818,7 +841,14 @@ defp restrict_blocked(query, %{blocking_user: %User{} = user} = opts) do from( [activity, object: o] in query, where: fragment("not (? = ANY(?))", activity.actor, ^blocked_ap_ids), - where: fragment("not (? && ?)", activity.recipients, ^blocked_ap_ids), + where: + fragment( + "((not (? && ?)) or ? = ?)", + activity.recipients, + ^blocked_ap_ids, + activity.actor, + ^user.ap_id + ), where: fragment( "recipients_contain_blocked_domains(?, ?) = false", @@ -1224,7 +1254,7 @@ defp object_to_user_data(data) do name: data["name"], follower_address: data["followers"], following_address: data["following"], - bio: data["summary"], + bio: data["summary"] || "", actor_type: actor_type, also_known_as: Map.get(data, "alsoKnownAs", []), public_key: public_key, @@ -1247,10 +1277,12 @@ defp object_to_user_data(data) do def fetch_follow_information_for_user(user) do with {:ok, following_data} <- - Fetcher.fetch_and_contain_remote_object_from_id(user.following_address), + Fetcher.fetch_and_contain_remote_object_from_id(user.following_address, + force_http: true + ), {:ok, hide_follows} <- collection_private(following_data), {:ok, followers_data} <- - Fetcher.fetch_and_contain_remote_object_from_id(user.follower_address), + Fetcher.fetch_and_contain_remote_object_from_id(user.follower_address, force_http: true), {:ok, hide_followers} <- collection_private(followers_data) do {:ok, %{ @@ -1324,8 +1356,8 @@ def user_data_from_user_object(data) do end end - def fetch_and_prepare_user_from_ap_id(ap_id) do - with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id), + def fetch_and_prepare_user_from_ap_id(ap_id, opts \\ []) do + with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id, opts), {:ok, data} <- user_data_from_user_object(data) do {:ok, maybe_update_follow_information(data)} else @@ -1367,13 +1399,13 @@ def maybe_handle_clashing_nickname(data) do end end - def make_user_from_ap_id(ap_id) do + def make_user_from_ap_id(ap_id, opts \\ []) do user = User.get_cached_by_ap_id(ap_id) if user && !User.ap_enabled?(user) do Transmogrifier.upgrade_user_from_ap_id(ap_id) else - with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id) do + with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id, opts) do if user do user |> User.remote_user_changeset(data) diff --git a/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex b/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex index 7b4c78e0f..bee47b4ed 100644 --- a/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex @@ -31,10 +31,10 @@ defp note?(activity) do defp maybe_add_expiration(activity) do days = Pleroma.Config.get([:mrf_activity_expiration, :days], 365) - expires_at = NaiveDateTime.utc_now() |> Timex.shift(days: days) + expires_at = DateTime.utc_now() |> Timex.shift(days: days) with %{"expires_at" => existing_expires_at} <- activity, - :lt <- NaiveDateTime.compare(existing_expires_at, expires_at) do + :lt <- DateTime.compare(existing_expires_at, expires_at) do activity else _ -> Map.put(activity, "expires_at", expires_at) diff --git a/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex b/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex new file mode 100644 index 000000000..ea9c3d3f5 --- /dev/null +++ b/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex @@ -0,0 +1,56 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy do + alias Pleroma.User + @behaviour Pleroma.Web.ActivityPub.MRF + @moduledoc "Remove bot posts from federated timeline" + + require Pleroma.Constants + + defp check_by_actor_type(user), do: user.actor_type in ["Application", "Service"] + defp check_by_nickname(user), do: Regex.match?(~r/bot@|ebooks@/i, user.nickname) + + defp check_if_bot(user), do: check_by_actor_type(user) or check_by_nickname(user) + + @impl true + def filter( + %{ + "type" => "Create", + "to" => to, + "cc" => cc, + "actor" => actor, + "object" => object + } = message + ) do + user = User.get_cached_by_ap_id(actor) + isbot = check_if_bot(user) + + if isbot and Enum.member?(to, Pleroma.Constants.as_public()) do + to = List.delete(to, Pleroma.Constants.as_public()) ++ [user.follower_address] + cc = List.delete(cc, user.follower_address) ++ [Pleroma.Constants.as_public()] + + object = + object + |> Map.put("to", to) + |> Map.put("cc", cc) + + message = + message + |> Map.put("to", to) + |> Map.put("cc", cc) + |> Map.put("object", object) + + {:ok, message} + else + {:ok, message} + end + end + + @impl true + def filter(message), do: {:ok, message} + + @impl true + def describe, do: {:ok, %{}} +end diff --git a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex index dfab105a3..0fb05d3c4 100644 --- a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex @@ -12,23 +12,21 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do require Logger - @options [ - pool: :media + @adapter_options [ + pool: :media, + recv_timeout: 10_000 ] def perform(:prefetch, url) do - Logger.debug("Prefetching #{inspect(url)}") + # Fetching only proxiable resources + if MediaProxy.enabled?() and MediaProxy.url_proxiable?(url) do + # If preview proxy is enabled, it'll also hit media proxy (so we're caching both requests) + prefetch_url = MediaProxy.preview_url(url) - opts = - if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do - Keyword.put(@options, :recv_timeout, 10_000) - else - @options - end + Logger.debug("Prefetching #{inspect(url)} as #{inspect(prefetch_url)}") - url - |> MediaProxy.url() - |> HTTP.get([], adapter: opts) + HTTP.get(prefetch_url, [], @adapter_options) + end end def perform(:preload, %{"object" => %{"attachment" => attachments}} = _message) do diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index b77c06395..bd0a2a8dc 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -12,11 +12,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do alias Pleroma.Activity alias Pleroma.EctoType.ActivityPub.ObjectValidators alias Pleroma.Object + alias Pleroma.Object.Containment alias Pleroma.User alias Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator alias Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator - alias Pleroma.Web.ActivityPub.ObjectValidators.AudioValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator @@ -149,10 +151,20 @@ def validate(%{"type" => "Question"} = object, meta) do end end - def validate(%{"type" => "Audio"} = object, meta) do + def validate(%{"type" => type} = object, meta) when type in ~w[Audio Video] do with {:ok, object} <- object - |> AudioValidator.cast_and_validate() + |> AudioVideoValidator.cast_and_validate() + |> Ecto.Changeset.apply_action(:insert) do + object = stringify_keys(object) + {:ok, object, meta} + end + end + + def validate(%{"type" => "Article"} = object, meta) do + with {:ok, object} <- + object + |> ArticleNoteValidator.cast_and_validate() |> Ecto.Changeset.apply_action(:insert) do object = stringify_keys(object) {:ok, object, meta} @@ -198,7 +210,7 @@ def validate( %{"type" => "Create", "object" => %{"type" => objtype} = object} = create_activity, meta ) - when objtype in ~w[Question Answer Audio Event] do + when objtype in ~w[Question Answer Audio Video Event Article] do with {:ok, object_data} <- cast_and_apply(object), meta = Keyword.put(meta, :object_data, object_data |> stringify_keys), {:ok, create_activity} <- @@ -232,14 +244,18 @@ def cast_and_apply(%{"type" => "Answer"} = object) do AnswerValidator.cast_and_apply(object) end - def cast_and_apply(%{"type" => "Audio"} = object) do - AudioValidator.cast_and_apply(object) + def cast_and_apply(%{"type" => type} = object) when type in ~w[Audio Video] do + AudioVideoValidator.cast_and_apply(object) end def cast_and_apply(%{"type" => "Event"} = object) do EventValidator.cast_and_apply(object) end + def cast_and_apply(%{"type" => "Article"} = object) do + ArticleNoteValidator.cast_and_apply(object) + end + def cast_and_apply(o), do: {:error, {:validator_not_set, o}} # is_struct/1 isn't present in Elixir 1.8.x @@ -262,7 +278,8 @@ def stringify_keys(object) when is_list(object) do def stringify_keys(object), do: object def fetch_actor(object) do - with {:ok, actor} <- ObjectValidators.ObjectID.cast(object["actor"]) do + with actor <- Containment.get_actor(object), + {:ok, actor} <- ObjectValidators.ObjectID.cast(actor) do User.get_or_fetch_by_ap_id(actor) end end diff --git a/lib/pleroma/web/activity_pub/object_validators/audio_validator.ex b/lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex similarity index 78% rename from lib/pleroma/web/activity_pub/object_validators/audio_validator.ex rename to lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex index d1869f188..5b7dad517 100644 --- a/lib/pleroma/web/activity_pub/object_validators/audio_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex @@ -2,13 +2,14 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioValidator do +defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator do use Ecto.Schema alias Pleroma.EctoType.ActivityPub.ObjectValidators alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations + alias Pleroma.Web.ActivityPub.Transmogrifier import Ecto.Changeset @@ -24,29 +25,31 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioValidator do # TODO: Write type field(:tag, {:array, :map}, default: []) field(:type, :string) + + field(:name, :string) + field(:summary, :string) field(:content, :string) + field(:context, :string) + # short identifier for PleromaFE to group statuses by context + field(:context_id, :integer) # TODO: Remove actor on objects field(:actor, ObjectValidators.ObjectID) field(:attributedTo, ObjectValidators.ObjectID) - field(:summary, :string) field(:published, ObjectValidators.DateTime) - # TODO: Write type - field(:emoji, :map, default: %{}) + field(:emoji, ObjectValidators.Emoji, default: %{}) field(:sensitive, :boolean, default: false) embeds_many(:attachment, AttachmentValidator) field(:replies_count, :integer, default: 0) field(:like_count, :integer, default: 0) field(:announcement_count, :integer, default: 0) - field(:inReplyTo, :string) + field(:inReplyTo, ObjectValidators.ObjectID) field(:url, ObjectValidators.Uri) - # short identifier for PleromaFE to group statuses by context - field(:context_id, :integer) - field(:likes, {:array, :string}, default: []) - field(:announcements, {:array, :string}, default: []) + field(:likes, {:array, ObjectValidators.ObjectID}, default: []) + field(:announcements, {:array, ObjectValidators.ObjectID}, default: []) end def cast_and_apply(data) do @@ -62,19 +65,14 @@ def cast_and_validate(data) do end def cast_data(data) do + data = fix(data) + %__MODULE__{} |> changeset(data) end - defp fix_url(%{"url" => url} = data) when is_list(url) do - attachment = - Enum.find(url, fn x -> is_map(x) and String.starts_with?(x["mimeType"], "audio/") end) - - link_element = Enum.find(url, fn x -> is_map(x) and x["mimeType"] == "text/html" end) - - data - |> Map.put("attachment", [attachment]) - |> Map.put("url", link_element["href"]) + defp fix_url(%{"url" => url} = data) when is_map(url) do + Map.put(data, "url", url["href"]) end defp fix_url(data), do: data @@ -83,7 +81,9 @@ defp fix(data) do data |> CommonFixes.fix_defaults() |> CommonFixes.fix_attribution() + |> CommonFixes.fix_actor() |> fix_url() + |> Transmogrifier.fix_emoji() end def changeset(struct, data) do @@ -96,8 +96,8 @@ def changeset(struct, data) do def validate_data(data_cng) do data_cng - |> validate_inclusion(:type, ["Audio"]) - |> validate_required([:id, :actor, :attributedTo, :type, :context, :attachment]) + |> validate_inclusion(:type, ["Article", "Note"]) + |> validate_required([:id, :actor, :attributedTo, :type, :context, :context_id]) |> CommonValidations.validate_any_presence([:cc, :to]) |> CommonValidations.validate_fields_match([:actor, :attributedTo]) |> CommonValidations.validate_actor_presence() diff --git a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex index c8b148280..df102a134 100644 --- a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do use Ecto.Schema + alias Pleroma.EctoType.ActivityPub.ObjectValidators alias Pleroma.Web.ActivityPub.ObjectValidators.UrlObjectValidator import Ecto.Changeset @@ -15,7 +16,11 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do field(:mediaType, :string, default: "application/octet-stream") field(:name, :string) - embeds_many(:url, UrlObjectValidator) + embeds_many :url, UrlObjectValidator, primary_key: false do + field(:type, :string) + field(:href, ObjectValidators.Uri) + field(:mediaType, :string, default: "application/octet-stream") + end end def cast_and_validate(data) do @@ -37,7 +42,18 @@ def changeset(struct, data) do struct |> cast(data, [:type, :mediaType, :name]) - |> cast_embed(:url, required: true) + |> cast_embed(:url, with: &url_changeset/2) + |> validate_inclusion(:type, ~w[Link Document Audio Image Video]) + |> validate_required([:type, :mediaType, :url]) + end + + def url_changeset(struct, data) do + data = fix_media_type(data) + + struct + |> cast(data, [:type, :href, :mediaType]) + |> validate_inclusion(:type, ["Link"]) + |> validate_required([:type, :href, :mediaType]) end def fix_media_type(data) do @@ -75,6 +91,7 @@ defp fix_url(data) do def validate_data(cng) do cng + |> validate_inclusion(:type, ~w[Document Audio Image Video]) |> validate_required([:mediaType, :url, :type]) end end diff --git a/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex b/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex new file mode 100644 index 000000000..16973e5db --- /dev/null +++ b/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex @@ -0,0 +1,134 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do + use Ecto.Schema + + alias Pleroma.EarmarkRenderer + alias Pleroma.EctoType.ActivityPub.ObjectValidators + alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes + alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations + alias Pleroma.Web.ActivityPub.Transmogrifier + + import Ecto.Changeset + + @primary_key false + @derive Jason.Encoder + + embedded_schema do + field(:id, ObjectValidators.ObjectID, primary_key: true) + field(:to, ObjectValidators.Recipients, default: []) + field(:cc, ObjectValidators.Recipients, default: []) + field(:bto, ObjectValidators.Recipients, default: []) + field(:bcc, ObjectValidators.Recipients, default: []) + # TODO: Write type + field(:tag, {:array, :map}, default: []) + field(:type, :string) + + field(:name, :string) + field(:summary, :string) + field(:content, :string) + + field(:context, :string) + # short identifier for PleromaFE to group statuses by context + field(:context_id, :integer) + + # TODO: Remove actor on objects + field(:actor, ObjectValidators.ObjectID) + + field(:attributedTo, ObjectValidators.ObjectID) + field(:published, ObjectValidators.DateTime) + field(:emoji, ObjectValidators.Emoji, default: %{}) + field(:sensitive, :boolean, default: false) + embeds_many(:attachment, AttachmentValidator) + field(:replies_count, :integer, default: 0) + field(:like_count, :integer, default: 0) + field(:announcement_count, :integer, default: 0) + field(:inReplyTo, ObjectValidators.ObjectID) + field(:url, ObjectValidators.Uri) + + field(:likes, {:array, ObjectValidators.ObjectID}, default: []) + field(:announcements, {:array, ObjectValidators.ObjectID}, default: []) + end + + def cast_and_apply(data) do + data + |> cast_data + |> apply_action(:insert) + end + + def cast_and_validate(data) do + data + |> cast_data() + |> validate_data() + end + + def cast_data(data) do + %__MODULE__{} + |> changeset(data) + end + + defp fix_url(%{"url" => url} = data) when is_list(url) do + attachment = + Enum.find(url, fn x -> + mime_type = x["mimeType"] || x["mediaType"] || "" + + is_map(x) and String.starts_with?(mime_type, ["video/", "audio/"]) + end) + + link_element = + Enum.find(url, fn x -> + mime_type = x["mimeType"] || x["mediaType"] || "" + + is_map(x) and mime_type == "text/html" + end) + + data + |> Map.put("attachment", [attachment]) + |> Map.put("url", link_element["href"]) + end + + defp fix_url(data), do: data + + defp fix_content(%{"mediaType" => "text/markdown", "content" => content} = data) + when is_binary(content) do + content = + content + |> Earmark.as_html!(%Earmark.Options{renderer: EarmarkRenderer}) + |> Pleroma.HTML.filter_tags() + + Map.put(data, "content", content) + end + + defp fix_content(data), do: data + + defp fix(data) do + data + |> CommonFixes.fix_defaults() + |> CommonFixes.fix_attribution() + |> CommonFixes.fix_actor() + |> Transmogrifier.fix_emoji() + |> fix_url() + |> fix_content() + end + + def changeset(struct, data) do + data = fix(data) + + struct + |> cast(data, __schema__(:fields) -- [:attachment]) + |> cast_embed(:attachment) + end + + def validate_data(data_cng) do + data_cng + |> validate_inclusion(:type, ["Audio", "Video"]) + |> validate_required([:id, :actor, :attributedTo, :type, :context, :attachment]) + |> CommonValidations.validate_any_presence([:cc, :to]) + |> CommonValidations.validate_fields_match([:actor, :attributedTo]) + |> CommonValidations.validate_actor_presence() + |> CommonValidations.validate_host_match() + end +end diff --git a/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex b/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex index 91b475393..6acd4a771 100644 --- a/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex @@ -22,7 +22,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator do field(:content, ObjectValidators.SafeText) field(:actor, ObjectValidators.ObjectID) field(:published, ObjectValidators.DateTime) - field(:emoji, :map, default: %{}) + field(:emoji, ObjectValidators.Emoji, default: %{}) embeds_one(:attachment, AttachmentValidator) end diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex index 721749de0..b3638cfc7 100644 --- a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex +++ b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex @@ -3,6 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do + alias Pleroma.Object.Containment alias Pleroma.Web.ActivityPub.Utils # based on Pleroma.Web.ActivityPub.Utils.lazy_put_objects_defaults @@ -11,12 +12,20 @@ def fix_defaults(data) do Utils.create_context(data["context"] || data["conversation"]) data - |> Map.put_new("context", context) - |> Map.put_new("context_id", context_id) + |> Map.put("context", context) + |> Map.put("context_id", context_id) end def fix_attribution(data) do data |> Map.put_new("actor", data["attributedTo"]) end + + def fix_actor(data) do + actor = Containment.get_actor(data) + + data + |> Map.put("actor", actor) + |> Map.put("attributedTo", actor) + end end diff --git a/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex b/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex index b3dbeea57..422ee07be 100644 --- a/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex @@ -10,9 +10,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do alias Pleroma.EctoType.ActivityPub.ObjectValidators alias Pleroma.Object + alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes + alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations import Ecto.Changeset - import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations @primary_key false @@ -75,14 +76,15 @@ defp fix(data, meta) do data |> fix_context(meta) |> fix_addressing(meta) + |> CommonFixes.fix_actor() end def validate_data(cng, meta \\ []) do cng |> validate_required([:actor, :type, :object]) |> validate_inclusion(:type, ["Create"]) - |> validate_actor_presence() - |> validate_any_presence([:to, :cc]) + |> CommonValidations.validate_actor_presence() + |> CommonValidations.validate_any_presence([:to, :cc]) |> validate_actors_match(meta) |> validate_context_match(meta) |> validate_object_nonexistence() diff --git a/lib/pleroma/web/activity_pub/object_validators/event_validator.ex b/lib/pleroma/web/activity_pub/object_validators/event_validator.ex index 07e4821a4..0b4c99dc0 100644 --- a/lib/pleroma/web/activity_pub/object_validators/event_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/event_validator.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations + alias Pleroma.Web.ActivityPub.Transmogrifier import Ecto.Changeset @@ -39,8 +40,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do field(:attributedTo, ObjectValidators.ObjectID) field(:published, ObjectValidators.DateTime) - # TODO: Write type - field(:emoji, :map, default: %{}) + field(:emoji, ObjectValidators.Emoji, default: %{}) field(:sensitive, :boolean, default: false) embeds_many(:attachment, AttachmentValidator) field(:replies_count, :integer, default: 0) @@ -74,6 +74,7 @@ defp fix(data) do data |> CommonFixes.fix_defaults() |> CommonFixes.fix_attribution() + |> Transmogrifier.fix_emoji() end def changeset(struct, data) do diff --git a/lib/pleroma/web/activity_pub/object_validators/note_validator.ex b/lib/pleroma/web/activity_pub/object_validators/note_validator.ex deleted file mode 100644 index 20e735619..000000000 --- a/lib/pleroma/web/activity_pub/object_validators/note_validator.ex +++ /dev/null @@ -1,66 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator do - use Ecto.Schema - - alias Pleroma.EctoType.ActivityPub.ObjectValidators - - import Ecto.Changeset - - @primary_key false - - embedded_schema do - field(:id, ObjectValidators.ObjectID, primary_key: true) - field(:to, ObjectValidators.Recipients, default: []) - field(:cc, ObjectValidators.Recipients, default: []) - field(:bto, ObjectValidators.Recipients, default: []) - field(:bcc, ObjectValidators.Recipients, default: []) - # TODO: Write type - field(:tag, {:array, :map}, default: []) - field(:type, :string) - - field(:name, :string) - field(:summary, :string) - field(:content, :string) - - field(:context, :string) - # short identifier for PleromaFE to group statuses by context - field(:context_id, :integer) - - field(:actor, ObjectValidators.ObjectID) - field(:attributedTo, ObjectValidators.ObjectID) - field(:published, ObjectValidators.DateTime) - # TODO: Write type - field(:emoji, :map, default: %{}) - field(:sensitive, :boolean, default: false) - # TODO: Write type - field(:attachment, {:array, :map}, default: []) - field(:replies_count, :integer, default: 0) - field(:like_count, :integer, default: 0) - field(:announcement_count, :integer, default: 0) - field(:inReplyTo, ObjectValidators.ObjectID) - field(:url, ObjectValidators.Uri) - - field(:likes, {:array, :string}, default: []) - field(:announcements, {:array, :string}, default: []) - end - - def cast_and_validate(data) do - data - |> cast_data() - |> validate_data() - end - - def cast_data(data) do - %__MODULE__{} - |> cast(data, __schema__(:fields)) - end - - def validate_data(data_cng) do - data_cng - |> validate_inclusion(:type, ["Note"]) - |> validate_required([:id, :actor, :to, :cc, :type, :content, :context]) - end -end diff --git a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex index 712047424..9310485dc 100644 --- a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex @@ -10,6 +10,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations alias Pleroma.Web.ActivityPub.ObjectValidators.QuestionOptionsValidator + alias Pleroma.Web.ActivityPub.Transmogrifier import Ecto.Changeset @@ -35,8 +36,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do field(:attributedTo, ObjectValidators.ObjectID) field(:summary, :string) field(:published, ObjectValidators.DateTime) - # TODO: Write type - field(:emoji, :map, default: %{}) + field(:emoji, ObjectValidators.Emoji, default: %{}) field(:sensitive, :boolean, default: false) embeds_many(:attachment, AttachmentValidator) field(:replies_count, :integer, default: 0) @@ -47,8 +47,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do # short identifier for PleromaFE to group statuses by context field(:context_id, :integer) - field(:likes, {:array, :string}, default: []) - field(:announcements, {:array, :string}, default: []) + field(:likes, {:array, ObjectValidators.ObjectID}, default: []) + field(:announcements, {:array, ObjectValidators.ObjectID}, default: []) field(:closed, ObjectValidators.DateTime) field(:voters, {:array, ObjectValidators.ObjectID}, default: []) @@ -85,6 +85,7 @@ defp fix(data) do data |> CommonFixes.fix_defaults() |> CommonFixes.fix_attribution() + |> Transmogrifier.fix_emoji() |> fix_closed() end diff --git a/lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex b/lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex deleted file mode 100644 index 881030f38..000000000 --- a/lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex +++ /dev/null @@ -1,24 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.ActivityPub.ObjectValidators.UrlObjectValidator do - use Ecto.Schema - - alias Pleroma.EctoType.ActivityPub.ObjectValidators - - import Ecto.Changeset - @primary_key false - - embedded_schema do - field(:type, :string) - field(:href, ObjectValidators.Uri) - field(:mediaType, :string, default: "application/octet-stream") - end - - def changeset(struct, data) do - struct - |> cast(data, __schema__(:fields)) - |> validate_required([:type, :href, :mediaType]) - end -end diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index d88f7f3ee..9c3956683 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -13,6 +13,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do alias Pleroma.User alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.ActivityPub.Transmogrifier + alias Pleroma.Web.FedSockets require Pleroma.Constants @@ -50,15 +51,35 @@ def is_representable?(%Activity{} = activity) do def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = params) do Logger.debug("Federating #{id} to #{inbox}") - uri = URI.parse(inbox) + case FedSockets.get_or_create_fed_socket(inbox) do + {:ok, fedsocket} -> + Logger.debug("publishing via fedsockets - #{inspect(inbox)}") + FedSockets.publish(fedsocket, json) + _ -> + Logger.debug("publishing via http - #{inspect(inbox)}") + http_publish(inbox, actor, json, params) + end + end + + def publish_one(%{actor_id: actor_id} = params) do + actor = User.get_cached_by_id(actor_id) + + params + |> Map.delete(:actor_id) + |> Map.put(:actor, actor) + |> publish_one() + end + + defp http_publish(inbox, actor, json, params) do + uri = %{path: path} = URI.parse(inbox) digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64()) date = Pleroma.Signature.signed_date() signature = Pleroma.Signature.sign(actor, %{ - "(request-target)": "post #{uri.path}", + "(request-target)": "post #{path}", host: signature_host(uri), "content-length": byte_size(json), digest: digest, @@ -89,15 +110,6 @@ def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = pa end end - def publish_one(%{actor_id: actor_id} = params) do - actor = User.get_cached_by_id(actor_id) - - params - |> Map.delete(:actor_id) - |> Map.put(:actor, actor) - |> publish_one() - end - defp signature_host(%URI{port: port, scheme: scheme, host: host}) do if port == URI.default_port(scheme) do host diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index a5e2323bd..b9a83a544 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -7,7 +7,6 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do """ alias Pleroma.Activity alias Pleroma.Activity.Ir.Topics - alias Pleroma.ActivityExpiration alias Pleroma.Chat alias Pleroma.Chat.MessageReference alias Pleroma.FollowingRelationship @@ -188,10 +187,6 @@ def handle(%{data: %{"type" => "Create"}} = activity, meta) do Object.increase_replies_count(in_reply_to) end - if expires_at = activity.data["expires_at"] do - ActivityExpiration.create(activity, expires_at) - end - BackgroundWorker.enqueue("fetch_data_for_activity", %{"activity_id" => activity.id}) meta = @@ -341,7 +336,7 @@ def handle_object_creation(%{"type" => "Answer"} = object_map, meta) do end def handle_object_creation(%{"type" => objtype} = object, meta) - when objtype in ~w[Audio Question Event] do + when objtype in ~w[Audio Video Question Event Article] do with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do {:ok, object, meta} end diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 63dd227c1..aa6a69463 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -7,7 +7,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do A module to handle coding from internal to wire ActivityPub and back. """ alias Pleroma.Activity - alias Pleroma.EarmarkRenderer alias Pleroma.EctoType.ActivityPub.ObjectValidators alias Pleroma.Maps alias Pleroma.Object @@ -45,7 +44,6 @@ def fix_object(object, options \\ []) do |> fix_addressing |> fix_summary |> fix_type(options) - |> fix_content end def fix_summary(%{"summary" => nil} = object) do @@ -168,7 +166,6 @@ def fix_in_reply_to(object, options \\ []) def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options) when not is_nil(in_reply_to) do in_reply_to_id = prepare_in_reply_to(in_reply_to) - object = Map.put(object, "inReplyToAtomUri", in_reply_to_id) depth = (options[:depth] || 0) + 1 if Federator.allowed_thread_distance?(depth) do @@ -176,9 +173,8 @@ def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options) %Activity{} <- Activity.get_create_by_object_ap_id(replied_object.data["id"]) do object |> Map.put("inReplyTo", replied_object.data["id"]) - |> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id) |> Map.put("context", replied_object.data["context"] || object["conversation"]) - |> Map.drop(["conversation"]) + |> Map.drop(["conversation", "inReplyToAtomUri"]) else e -> Logger.warn("Couldn't fetch #{inspect(in_reply_to_id)}, error: #{inspect(e)}") @@ -276,24 +272,7 @@ def fix_url(%{"url" => url} = object) when is_map(url) do Map.put(object, "url", url["href"]) end - def fix_url(%{"type" => "Video", "url" => url} = object) when is_list(url) do - attachment = - Enum.find(url, fn x -> - media_type = x["mediaType"] || x["mimeType"] || "" - - is_map(x) and String.starts_with?(media_type, "video/") - end) - - link_element = - Enum.find(url, fn x -> is_map(x) and (x["mediaType"] || x["mimeType"]) == "text/html" end) - - object - |> Map.put("attachment", [attachment]) - |> Map.put("url", link_element["href"]) - end - - def fix_url(%{"type" => object_type, "url" => url} = object) - when object_type != "Video" and is_list(url) do + def fix_url(%{"url" => url} = object) when is_list(url) do first_element = Enum.at(url, 0) url_string = @@ -318,9 +297,6 @@ def fix_emoji(%{"tag" => tags} = object) when is_list(tags) do Map.put(mapping, name, data["icon"]["url"]) end) - # we merge mastodon and pleroma emoji into a single mapping, to allow for both wire formats - emoji = Map.merge(object["emoji"] || %{}, emoji) - Map.put(object, "emoji", emoji) end @@ -376,18 +352,6 @@ def fix_type(%{"inReplyTo" => reply_id, "name" => _} = object, options) def fix_type(object, _), do: object - defp fix_content(%{"mediaType" => "text/markdown", "content" => content} = object) - when is_binary(content) do - html_content = - content - |> Earmark.as_html!(%Earmark.Options{renderer: EarmarkRenderer}) - |> Pleroma.HTML.filter_tags() - - Map.merge(object, %{"content" => html_content, "mediaType" => "text/html"}) - end - - defp fix_content(object), do: object - # Reduce the object list to find the reported user. defp get_reported(objects) do Enum.reduce_while(objects, nil, fn ap_id, _ -> @@ -460,7 +424,7 @@ def handle_incoming( %{"type" => "Create", "object" => %{"type" => objtype} = object} = data, options ) - when objtype in ~w{Article Note Video Page} do + when objtype in ~w{Note Page} do actor = Containment.get_actor(data) with nil <- Activity.get_create_by_object_ap_id(object["id"]), @@ -554,7 +518,9 @@ def handle_incoming( %{"type" => "Create", "object" => %{"type" => objtype}} = data, _options ) - when objtype in ~w{Question Answer ChatMessage Audio Event} do + when objtype in ~w{Question Answer ChatMessage Audio Video Event Article} do + data = Map.put(data, "object", strip_internal_fields(data["object"])) + with {:ok, %User{}} <- ObjectValidator.fetch_actor(data), {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do {:ok, activity} @@ -1034,7 +1000,7 @@ def perform(:user_upgrade, user) do def upgrade_user_from_ap_id(ap_id) do with %User{local: false} = user <- User.get_cached_by_ap_id(ap_id), - {:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id), + {:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id, force_http: true), {:ok, user} <- update_user(user, data) do TransmogrifierWorker.enqueue("user_upgrade", %{"user_id" => user.id}) {:ok, user} diff --git a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex index f5e4d49f9..d5713c3dd 100644 --- a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex @@ -23,8 +23,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do alias Pleroma.Web.Endpoint alias Pleroma.Web.Router - require Logger - @users_page_size 50 plug( @@ -68,6 +66,12 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do when action in [:list_user_statuses, :list_instance_statuses] ) + plug( + OAuthScopesPlug, + %{scopes: ["read:chats"], admin: true} + when action in [:list_user_chats] + ) + plug( OAuthScopesPlug, %{scopes: ["read"], admin: true} @@ -256,6 +260,20 @@ def list_user_statuses(%{assigns: %{user: admin}} = conn, %{"nickname" => nickna end end + def list_user_chats(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = _params) do + with %User{id: user_id} <- User.get_cached_by_nickname_or_id(nickname, for: admin) do + chats = + Pleroma.Chat.for_user_query(user_id) + |> Pleroma.Repo.all() + + conn + |> put_view(AdminAPI.ChatView) + |> render("index.json", chats: chats) + else + _ -> {:error, :not_found} + end + end + def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do user = User.get_cached_by_nickname(nickname) diff --git a/lib/pleroma/web/admin_api/controllers/chat_controller.ex b/lib/pleroma/web/admin_api/controllers/chat_controller.ex new file mode 100644 index 000000000..967600d69 --- /dev/null +++ b/lib/pleroma/web/admin_api/controllers/chat_controller.ex @@ -0,0 +1,85 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.ChatController do + use Pleroma.Web, :controller + + alias Pleroma.Activity + alias Pleroma.Chat + alias Pleroma.Chat.MessageReference + alias Pleroma.ModerationLog + alias Pleroma.Pagination + alias Pleroma.Plugs.OAuthScopesPlug + alias Pleroma.Web.AdminAPI + alias Pleroma.Web.CommonAPI + alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView + + require Logger + + plug(Pleroma.Web.ApiSpec.CastAndValidate) + + plug( + OAuthScopesPlug, + %{scopes: ["read:chats"], admin: true} when action in [:show, :messages] + ) + + plug( + OAuthScopesPlug, + %{scopes: ["write:chats"], admin: true} when action in [:delete_message] + ) + + action_fallback(Pleroma.Web.AdminAPI.FallbackController) + + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.ChatOperation + + def delete_message(%{assigns: %{user: user}} = conn, %{ + message_id: message_id, + id: chat_id + }) do + with %MessageReference{object: %{data: %{"id" => object_ap_id}}} = cm_ref <- + MessageReference.get_by_id(message_id), + ^chat_id <- to_string(cm_ref.chat_id), + %Activity{id: activity_id} <- Activity.get_create_by_object_ap_id(object_ap_id), + {:ok, _} <- CommonAPI.delete(activity_id, user) do + ModerationLog.insert_log(%{ + action: "chat_message_delete", + actor: user, + subject_id: message_id + }) + + conn + |> put_view(MessageReferenceView) + |> render("show.json", chat_message_reference: cm_ref) + else + _e -> + {:error, :could_not_delete} + end + end + + def messages(conn, %{id: id} = params) do + with %Chat{} = chat <- Chat.get_by_id(id) do + cm_refs = + chat + |> MessageReference.for_chat_query() + |> Pagination.fetch_paginated(params) + + conn + |> put_view(MessageReferenceView) + |> render("index.json", chat_message_references: cm_refs) + else + _ -> + conn + |> put_status(:not_found) + |> json(%{error: "not found"}) + end + end + + def show(conn, %{id: id}) do + with %Chat{} = chat <- Chat.get_by_id(id) do + conn + |> put_view(AdminAPI.ChatView) + |> render("show.json", chat: chat) + end + end +end diff --git a/lib/pleroma/web/admin_api/controllers/instance_document_controller.ex b/lib/pleroma/web/admin_api/controllers/instance_document_controller.ex new file mode 100644 index 000000000..504d9b517 --- /dev/null +++ b/lib/pleroma/web/admin_api/controllers/instance_document_controller.ex @@ -0,0 +1,41 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.InstanceDocumentController do + use Pleroma.Web, :controller + + alias Pleroma.Plugs.InstanceStatic + alias Pleroma.Plugs.OAuthScopesPlug + alias Pleroma.Web.InstanceDocument + + plug(Pleroma.Web.ApiSpec.CastAndValidate) + + action_fallback(Pleroma.Web.AdminAPI.FallbackController) + + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.InstanceDocumentOperation + + plug(OAuthScopesPlug, %{scopes: ["read"], admin: true} when action == :show) + plug(OAuthScopesPlug, %{scopes: ["write"], admin: true} when action in [:update, :delete]) + + def show(conn, %{name: document_name}) do + with {:ok, url} <- InstanceDocument.get(document_name), + {:ok, content} <- File.read(InstanceStatic.file_path(url)) do + conn + |> put_resp_content_type("text/html") + |> send_resp(200, content) + end + end + + def update(%{body_params: %{file: file}} = conn, %{name: document_name}) do + with {:ok, url} <- InstanceDocument.put(document_name, file.path) do + json(conn, %{"url" => url}) + end + end + + def delete(conn, %{name: document_name}) do + with :ok <- InstanceDocument.delete(document_name) do + json(conn, %{}) + end + end +end diff --git a/lib/pleroma/web/admin_api/views/chat_view.ex b/lib/pleroma/web/admin_api/views/chat_view.ex new file mode 100644 index 000000000..847df1423 --- /dev/null +++ b/lib/pleroma/web/admin_api/views/chat_view.ex @@ -0,0 +1,30 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.ChatView do + use Pleroma.Web, :view + + alias Pleroma.Chat + alias Pleroma.User + alias Pleroma.Web.MastodonAPI + alias Pleroma.Web.PleromaAPI + + def render("index.json", %{chats: chats} = opts) do + render_many(chats, __MODULE__, "show.json", Map.delete(opts, :chats)) + end + + def render("show.json", %{chat: %Chat{user_id: user_id}} = opts) do + user = User.get_by_id(user_id) + sender = MastodonAPI.AccountView.render("show.json", user: user, skip_visibility_check: true) + + serialized_chat = PleromaAPI.ChatView.render("show.json", opts) + + serialized_chat + |> Map.put(:sender, sender) + |> Map.put(:receiver, serialized_chat[:account]) + |> Map.delete(:account) + end + + def render(view, opts), do: PleromaAPI.ChatView.render(view, opts) +end diff --git a/lib/pleroma/web/admin_api/views/status_view.ex b/lib/pleroma/web/admin_api/views/status_view.ex index 500800be2..6042a22b6 100644 --- a/lib/pleroma/web/admin_api/views/status_view.ex +++ b/lib/pleroma/web/admin_api/views/status_view.ex @@ -8,6 +8,7 @@ defmodule Pleroma.Web.AdminAPI.StatusView do require Pleroma.Constants alias Pleroma.Web.AdminAPI + alias Pleroma.Web.CommonAPI alias Pleroma.Web.MastodonAPI defdelegate merge_account_views(user), to: AdminAPI.AccountView @@ -17,7 +18,7 @@ def render("index.json", opts) do end def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do - user = MastodonAPI.StatusView.get_user(activity.data["actor"]) + user = CommonAPI.get_user(activity.data["actor"]) MastodonAPI.StatusView.render("show.json", opts) |> Map.merge(%{account: merge_account_views(user)}) diff --git a/lib/pleroma/web/api_spec.ex b/lib/pleroma/web/api_spec.ex index 79fd5f871..93a5273e3 100644 --- a/lib/pleroma/web/api_spec.ex +++ b/lib/pleroma/web/api_spec.ex @@ -13,10 +13,15 @@ defmodule Pleroma.Web.ApiSpec do @impl OpenApi def spec do %OpenApi{ - servers: [ - # Populate the Server info from a phoenix endpoint - OpenApiSpex.Server.from_endpoint(Endpoint) - ], + servers: + if Phoenix.Endpoint.server?(:pleroma, Endpoint) do + [ + # Populate the Server info from a phoenix endpoint + OpenApiSpex.Server.from_endpoint(Endpoint) + ] + else + [] + end, info: %OpenApiSpex.Info{ title: "Pleroma", description: Application.spec(:pleroma, :description) |> to_string(), diff --git a/lib/pleroma/web/api_spec/helpers.ex b/lib/pleroma/web/api_spec/helpers.ex index 2a7f1a706..34de2ed57 100644 --- a/lib/pleroma/web/api_spec/helpers.ex +++ b/lib/pleroma/web/api_spec/helpers.ex @@ -72,7 +72,11 @@ def empty_object_response do end def empty_array_response do - Operation.response("Empty array", "application/json", %Schema{type: :array, example: []}) + Operation.response("Empty array", "application/json", %Schema{ + type: :array, + items: %Schema{type: :object, example: %{}}, + example: [] + }) end def no_content_response do diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index aaebc9b5c..d90ddb787 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -372,6 +372,10 @@ def identity_proofs_operation do tags: ["accounts"], summary: "Identity proofs", operationId: "AccountController.identity_proofs", + # Validators complains about unused path params otherwise + parameters: [ + %Reference{"$ref": "#/components/parameters/accountIdOrNickname"} + ], description: "Not implemented", responses: %{ 200 => empty_array_response() @@ -469,7 +473,6 @@ defp create_response do identifier: %Schema{type: :string}, message: %Schema{type: :string} }, - required: [], # Note: example of successful registration with failed login response: # example: %{ # "identifier" => "missing_confirmed_email", @@ -530,7 +533,7 @@ defp update_credentials_request do nullable: true, oneOf: [ %Schema{type: :array, items: attribute_field()}, - %Schema{type: :object, additionalProperties: %Schema{type: attribute_field()}} + %Schema{type: :object, additionalProperties: attribute_field()} ] }, # NOTE: `source` field is not supported diff --git a/lib/pleroma/web/api_spec/operations/admin/chat_operation.ex b/lib/pleroma/web/api_spec/operations/admin/chat_operation.ex new file mode 100644 index 000000000..d3e5dfc1c --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/admin/chat_operation.ex @@ -0,0 +1,96 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Admin.ChatOperation do + alias OpenApiSpex.Operation + alias Pleroma.Web.ApiSpec.Schemas.Chat + alias Pleroma.Web.ApiSpec.Schemas.ChatMessage + + import Pleroma.Web.ApiSpec.Helpers + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def delete_message_operation do + %Operation{ + tags: ["admin", "chat"], + summary: "Delete an individual chat message", + operationId: "AdminAPI.ChatController.delete_message", + parameters: [ + Operation.parameter(:id, :path, :string, "The ID of the Chat"), + Operation.parameter(:message_id, :path, :string, "The ID of the message") + ], + responses: %{ + 200 => + Operation.response( + "The deleted ChatMessage", + "application/json", + ChatMessage + ) + }, + security: [ + %{ + "oAuth" => ["write:chats"] + } + ] + } + end + + def messages_operation do + %Operation{ + tags: ["admin", "chat"], + summary: "Get the most recent messages of the chat", + operationId: "AdminAPI.ChatController.messages", + parameters: + [Operation.parameter(:id, :path, :string, "The ID of the Chat")] ++ + pagination_params(), + responses: %{ + 200 => + Operation.response( + "The messages in the chat", + "application/json", + Pleroma.Web.ApiSpec.ChatOperation.chat_messages_response() + ) + }, + security: [ + %{ + "oAuth" => ["read:chats"] + } + ] + } + end + + def show_operation do + %Operation{ + tags: ["chat"], + summary: "Create a chat", + operationId: "AdminAPI.ChatController.show", + parameters: [ + Operation.parameter( + :id, + :path, + :string, + "The id of the chat", + required: true, + example: "1234" + ) + ], + responses: %{ + 200 => + Operation.response( + "The existing chat", + "application/json", + Chat + ) + }, + security: [ + %{ + "oAuth" => ["read"] + } + ] + } + end +end diff --git a/lib/pleroma/web/api_spec/operations/admin/instance_document_operation.ex b/lib/pleroma/web/api_spec/operations/admin/instance_document_operation.ex new file mode 100644 index 000000000..a120ff4e8 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/admin/instance_document_operation.ex @@ -0,0 +1,115 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Admin.InstanceDocumentOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Helpers + alias Pleroma.Web.ApiSpec.Schemas.ApiError + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def show_operation do + %Operation{ + tags: ["Admin", "InstanceDocument"], + summary: "Get the instance document", + operationId: "AdminAPI.InstanceDocumentController.show", + security: [%{"oAuth" => ["read"]}], + parameters: [ + Operation.parameter(:name, :path, %Schema{type: :string}, "The document name", + required: true + ) + | Helpers.admin_api_params() + ], + responses: %{ + 200 => document_content(), + 400 => Operation.response("Bad Request", "application/json", ApiError), + 403 => Operation.response("Forbidden", "application/json", ApiError), + 404 => Operation.response("Not Found", "application/json", ApiError) + } + } + end + + def update_operation do + %Operation{ + tags: ["Admin", "InstanceDocument"], + summary: "Update the instance document", + operationId: "AdminAPI.InstanceDocumentController.update", + security: [%{"oAuth" => ["write"]}], + requestBody: Helpers.request_body("Parameters", update_request()), + parameters: [ + Operation.parameter(:name, :path, %Schema{type: :string}, "The document name", + required: true + ) + | Helpers.admin_api_params() + ], + responses: %{ + 200 => Operation.response("InstanceDocument", "application/json", instance_document()), + 400 => Operation.response("Bad Request", "application/json", ApiError), + 403 => Operation.response("Forbidden", "application/json", ApiError), + 404 => Operation.response("Not Found", "application/json", ApiError) + } + } + end + + defp update_request do + %Schema{ + title: "UpdateRequest", + description: "POST body for uploading the file", + type: :object, + required: [:file], + properties: %{ + file: %Schema{ + type: :string, + format: :binary, + description: "The file to be uploaded, using multipart form data." + } + } + } + end + + def delete_operation do + %Operation{ + tags: ["Admin", "InstanceDocument"], + summary: "Get the instance document", + operationId: "AdminAPI.InstanceDocumentController.delete", + security: [%{"oAuth" => ["write"]}], + parameters: [ + Operation.parameter(:name, :path, %Schema{type: :string}, "The document name", + required: true + ) + | Helpers.admin_api_params() + ], + responses: %{ + 200 => Operation.response("InstanceDocument", "application/json", instance_document()), + 400 => Operation.response("Bad Request", "application/json", ApiError), + 403 => Operation.response("Forbidden", "application/json", ApiError), + 404 => Operation.response("Not Found", "application/json", ApiError) + } + } + end + + defp instance_document do + %Schema{ + title: "InstanceDocument", + type: :object, + properties: %{ + url: %Schema{type: :string} + }, + example: %{ + "url" => "https://example.com/static/terms-of-service.html" + } + } + end + + defp document_content do + Operation.response("InstanceDocumentContent", "text/html", %Schema{ + type: :string, + example: "

Instance panel

" + }) + end +end diff --git a/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex b/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex index 2f812ac77..5ff263ceb 100644 --- a/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex +++ b/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex @@ -69,7 +69,7 @@ defp custom_emoji do type: :object, properties: %{ category: %Schema{type: :string}, - tags: %Schema{type: :array} + tags: %Schema{type: :array, items: %Schema{type: :string}} } } ], diff --git a/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex b/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex index 1a49fece0..745d41f88 100644 --- a/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex +++ b/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex @@ -23,7 +23,7 @@ def index_operation do parameters: [ Operation.parameter(:id, :path, FlakeID, "Status ID", required: true), Operation.parameter(:emoji, :path, :string, "Filter by a single unicode emoji", - required: false + required: nil ) ], security: [%{"oAuth" => ["read:statuses"]}], diff --git a/lib/pleroma/web/api_spec/operations/list_operation.ex b/lib/pleroma/web/api_spec/operations/list_operation.ex index c88ed5dd0..f6e73968a 100644 --- a/lib/pleroma/web/api_spec/operations/list_operation.ex +++ b/lib/pleroma/web/api_spec/operations/list_operation.ex @@ -114,7 +114,7 @@ def add_to_list_operation do description: "Add accounts to the given list.", operationId: "ListController.add_to_list", parameters: [id_param()], - requestBody: add_remove_accounts_request(), + requestBody: add_remove_accounts_request(true), security: [%{"oAuth" => ["write:lists"]}], responses: %{ 200 => Operation.response("Empty object", "application/json", %Schema{type: :object}) @@ -127,8 +127,16 @@ def remove_from_list_operation do tags: ["Lists"], summary: "Remove accounts from list", operationId: "ListController.remove_from_list", - parameters: [id_param()], - requestBody: add_remove_accounts_request(), + parameters: [ + id_param(), + Operation.parameter( + :account_ids, + :query, + %Schema{type: :array, items: %Schema{type: :string}}, + "Array of account IDs" + ) + ], + requestBody: add_remove_accounts_request(false), security: [%{"oAuth" => ["write:lists"]}], responses: %{ 200 => Operation.response("Empty object", "application/json", %Schema{type: :object}) @@ -171,7 +179,7 @@ defp create_update_request do ) end - defp add_remove_accounts_request do + defp add_remove_accounts_request(required) when is_boolean(required) do request_body( "Parameters", %Schema{ @@ -179,10 +187,9 @@ defp add_remove_accounts_request do type: :object, properties: %{ account_ids: %Schema{type: :array, description: "Array of account IDs", items: FlakeID} - }, - required: [:account_ids] + } }, - required: true + required: required ) end end diff --git a/lib/pleroma/web/api_spec/operations/user_import_operation.ex b/lib/pleroma/web/api_spec/operations/user_import_operation.ex new file mode 100644 index 000000000..a50314fb7 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/user_import_operation.ex @@ -0,0 +1,80 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.UserImportOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.ApiError + + import Pleroma.Web.ApiSpec.Helpers + + @spec open_api_operation(atom) :: Operation.t() + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def follow_operation do + %Operation{ + tags: ["follow_import"], + summary: "Imports your follows.", + operationId: "UserImportController.follow", + requestBody: request_body("Parameters", import_request(), required: true), + responses: %{ + 200 => ok_response(), + 500 => Operation.response("Error", "application/json", ApiError) + }, + security: [%{"oAuth" => ["write:follow"]}] + } + end + + def blocks_operation do + %Operation{ + tags: ["blocks_import"], + summary: "Imports your blocks.", + operationId: "UserImportController.blocks", + requestBody: request_body("Parameters", import_request(), required: true), + responses: %{ + 200 => ok_response(), + 500 => Operation.response("Error", "application/json", ApiError) + }, + security: [%{"oAuth" => ["write:blocks"]}] + } + end + + def mutes_operation do + %Operation{ + tags: ["mutes_import"], + summary: "Imports your mutes.", + operationId: "UserImportController.mutes", + requestBody: request_body("Parameters", import_request(), required: true), + responses: %{ + 200 => ok_response(), + 500 => Operation.response("Error", "application/json", ApiError) + }, + security: [%{"oAuth" => ["write:mutes"]}] + } + end + + defp import_request do + %Schema{ + type: :object, + required: [:list], + properties: %{ + list: %Schema{ + description: + "STRING or FILE containing a whitespace-separated list of accounts to import.", + anyOf: [ + %Schema{type: :string, format: :binary}, + %Schema{type: :string} + ] + } + } + } + end + + defp ok_response do + Operation.response("Ok", "application/json", %Schema{type: :string, example: "ok"}) + end +end diff --git a/lib/pleroma/web/api_spec/schemas/chat_message.ex b/lib/pleroma/web/api_spec/schemas/chat_message.ex index bbf2a4427..9d2799618 100644 --- a/lib/pleroma/web/api_spec/schemas/chat_message.ex +++ b/lib/pleroma/web/api_spec/schemas/chat_message.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.ChatMessage do alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.Emoji require OpenApiSpex @@ -18,7 +19,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.ChatMessage do chat_id: %Schema{type: :string}, content: %Schema{type: :string, nullable: true}, created_at: %Schema{type: :string, format: :"date-time"}, - emojis: %Schema{type: :array}, + emojis: %Schema{type: :array, items: Emoji}, attachment: %Schema{type: :object, nullable: true}, card: %Schema{ type: :object, diff --git a/lib/pleroma/web/api_spec/schemas/scheduled_status.ex b/lib/pleroma/web/api_spec/schemas/scheduled_status.ex index 0520d0848..addefa9d3 100644 --- a/lib/pleroma/web/api_spec/schemas/scheduled_status.ex +++ b/lib/pleroma/web/api_spec/schemas/scheduled_status.ex @@ -27,9 +27,9 @@ defmodule Pleroma.Web.ApiSpec.Schemas.ScheduledStatus do media_ids: %Schema{type: :array, nullable: true, items: %Schema{type: :string}}, sensitive: %Schema{type: :boolean, nullable: true}, spoiler_text: %Schema{type: :string, nullable: true}, - visibility: %Schema{type: VisibilityScope, nullable: true}, + visibility: %Schema{allOf: [VisibilityScope], nullable: true}, scheduled_at: %Schema{type: :string, format: :"date-time", nullable: true}, - poll: %Schema{type: Poll, nullable: true}, + poll: %Schema{allOf: [Poll], nullable: true}, in_reply_to_id: %Schema{type: :string, nullable: true} } } diff --git a/lib/pleroma/web/auth/pleroma_authenticator.ex b/lib/pleroma/web/auth/pleroma_authenticator.ex index 200ca03dc..c611b3e09 100644 --- a/lib/pleroma/web/auth/pleroma_authenticator.ex +++ b/lib/pleroma/web/auth/pleroma_authenticator.ex @@ -68,7 +68,7 @@ def create_from_registration( nickname = value([registration_attrs["nickname"], Registration.nickname(registration)]) email = value([registration_attrs["email"], Registration.email(registration)]) name = value([registration_attrs["name"], Registration.name(registration)]) || nickname - bio = value([registration_attrs["bio"], Registration.description(registration)]) + bio = value([registration_attrs["bio"], Registration.description(registration)]) || "" random_password = :crypto.strong_rand_bytes(64) |> Base.encode64() diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex index f849b2e01..548f76609 100644 --- a/lib/pleroma/web/common_api/activity_draft.ex +++ b/lib/pleroma/web/common_api/activity_draft.ex @@ -202,7 +202,7 @@ defp changes(draft) do additional = case draft.expires_at do - %NaiveDateTime{} = expires_at -> Map.put(additional, "expires_at", expires_at) + %DateTime{} = expires_at -> Map.put(additional, "expires_at", expires_at) _ -> additional end diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 3c7b9e794..60a50b027 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -4,7 +4,6 @@ defmodule Pleroma.Web.CommonAPI do alias Pleroma.Activity - alias Pleroma.ActivityExpiration alias Pleroma.Conversation.Participation alias Pleroma.Formatter alias Pleroma.Object @@ -384,9 +383,9 @@ def get_replied_to_visibility(activity) do def check_expiry_date({:ok, nil} = res), do: res def check_expiry_date({:ok, in_seconds}) do - expiry = NaiveDateTime.utc_now() |> NaiveDateTime.add(in_seconds) + expiry = DateTime.add(DateTime.utc_now(), in_seconds) - if ActivityExpiration.expires_late_enough?(expiry) do + if Pleroma.Workers.PurgeExpiredActivity.expires_late_enough?(expiry) do {:ok, expiry} else {:error, "Expiry date is too soon"} @@ -455,7 +454,8 @@ def unpin(id, user) do end def add_mute(user, activity) do - with {:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]) do + with {:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]), + _ <- Pleroma.Notification.mark_context_as_read(user, activity.data["context"]) do {:ok, activity} else {:error, _} -> {:error, dgettext("errors", "conversation is already muted")} @@ -553,4 +553,21 @@ def hide_reblogs(%User{} = user, %User{} = target) do def show_reblogs(%User{} = user, %User{} = target) do UserRelationship.delete_reblog_mute(user, target) end + + def get_user(ap_id, fake_record_fallback \\ true) do + cond do + user = User.get_cached_by_ap_id(ap_id) -> + user + + user = User.get_by_guessed_nickname(ap_id) -> + user + + fake_record_fallback -> + # TODO: refactor (fake records is never a good idea) + User.error_user(ap_id) + + true -> + nil + end + end end diff --git a/lib/pleroma/web/fed_sockets/fed_registry.ex b/lib/pleroma/web/fed_sockets/fed_registry.ex new file mode 100644 index 000000000..e00ea69c0 --- /dev/null +++ b/lib/pleroma/web/fed_sockets/fed_registry.ex @@ -0,0 +1,185 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.FedSockets.FedRegistry do + @moduledoc """ + The FedRegistry stores the active FedSockets for quick retrieval. + + The storage and retrieval portion of the FedRegistry is done in process through + elixir's `Registry` module for speed and its ability to monitor for terminated processes. + + Dropped connections will be caught by `Registry` and deleted. Since the next + message will initiate a new connection there is no reason to try and reconnect at that point. + + Normally outside modules should have no need to call or use the FedRegistry themselves. + """ + + alias Pleroma.Web.FedSockets.FedSocket + alias Pleroma.Web.FedSockets.SocketInfo + + require Logger + + @default_rejection_duration 15 * 60 * 1000 + @rejections :fed_socket_rejections + + @doc """ + Retrieves a FedSocket from the Registry given it's origin. + + The origin is expected to be a string identifying the endpoint "example.com" or "example2.com:8080" + + Will return: + * {:ok, fed_socket} for working FedSockets + * {:error, :rejected} for origins that have been tried and refused within the rejection duration interval + * {:error, some_reason} usually :missing for unknown origins + """ + def get_fed_socket(origin) do + case get_registry_data(origin) do + {:error, reason} -> + {:error, reason} + + {:ok, %{state: :connected} = socket_info} -> + {:ok, socket_info} + end + end + + @doc """ + Adds a connected FedSocket to the Registry. + + Always returns {:ok, fed_socket} + """ + def add_fed_socket(origin, pid \\ nil) do + origin + |> SocketInfo.build(pid) + |> SocketInfo.connect() + |> add_socket_info + end + + defp add_socket_info(%{origin: origin, state: :connected} = socket_info) do + case Registry.register(FedSockets.Registry, origin, socket_info) do + {:ok, _owner} -> + clear_prior_rejection(origin) + Logger.debug("fedsocket added: #{inspect(origin)}") + + {:ok, socket_info} + + {:error, {:already_registered, _pid}} -> + FedSocket.close(socket_info) + existing_socket_info = Registry.lookup(FedSockets.Registry, origin) + + {:ok, existing_socket_info} + + _ -> + {:error, :error_adding_socket} + end + end + + @doc """ + Mark this origin as having rejected a connection attempt. + This will keep it from getting additional connection attempts + for a period of time specified in the config. + + Always returns {:ok, new_reg_data} + """ + def set_host_rejected(uri) do + new_reg_data = + uri + |> SocketInfo.origin() + |> get_or_create_registry_data() + |> set_to_rejected() + |> save_registry_data() + + {:ok, new_reg_data} + end + + @doc """ + Retrieves the FedRegistryData from the Registry given it's origin. + + The origin is expected to be a string identifying the endpoint "example.com" or "example2.com:8080" + + Will return: + * {:ok, fed_registry_data} for known origins + * {:error, :missing} for uniknown origins + * {:error, :cache_error} indicating some low level runtime issues + """ + def get_registry_data(origin) do + case Registry.lookup(FedSockets.Registry, origin) do + [] -> + if is_rejected?(origin) do + Logger.debug("previously rejected fedsocket requested") + {:error, :rejected} + else + {:error, :missing} + end + + [{_pid, %{state: :connected} = socket_info}] -> + {:ok, socket_info} + + _ -> + {:error, :cache_error} + end + end + + @doc """ + Retrieves a map of all sockets from the Registry. The keys are the origins and the values are the corresponding SocketInfo + """ + def list_all do + (list_all_connected() ++ list_all_rejected()) + |> Enum.into(%{}) + end + + defp list_all_connected do + FedSockets.Registry + |> Registry.select([{{:"$1", :_, :"$3"}, [], [{{:"$1", :"$3"}}]}]) + end + + defp list_all_rejected do + {:ok, keys} = Cachex.keys(@rejections) + + {:ok, registry_data} = + Cachex.execute(@rejections, fn worker -> + Enum.map(keys, fn k -> {k, Cachex.get!(worker, k)} end) + end) + + registry_data + end + + defp clear_prior_rejection(origin), + do: Cachex.del(@rejections, origin) + + defp is_rejected?(origin) do + case Cachex.get(@rejections, origin) do + {:ok, nil} -> + false + + {:ok, _} -> + true + end + end + + defp get_or_create_registry_data(origin) do + case get_registry_data(origin) do + {:error, :missing} -> + %SocketInfo{origin: origin} + + {:ok, socket_info} -> + socket_info + end + end + + defp save_registry_data(%SocketInfo{origin: origin, state: :connected} = socket_info) do + {:ok, true} = Registry.update_value(FedSockets.Registry, origin, fn _ -> socket_info end) + socket_info + end + + defp save_registry_data(%SocketInfo{origin: origin, state: :rejected} = socket_info) do + rejection_expiration = + Pleroma.Config.get([:fed_sockets, :rejection_duration], @default_rejection_duration) + + {:ok, true} = Cachex.put(@rejections, origin, socket_info, ttl: rejection_expiration) + socket_info + end + + defp set_to_rejected(%SocketInfo{} = socket_info), + do: %SocketInfo{socket_info | state: :rejected} +end diff --git a/lib/pleroma/web/fed_sockets/fed_socket.ex b/lib/pleroma/web/fed_sockets/fed_socket.ex new file mode 100644 index 000000000..98d64e65a --- /dev/null +++ b/lib/pleroma/web/fed_sockets/fed_socket.ex @@ -0,0 +1,137 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.FedSockets.FedSocket do + @moduledoc """ + The FedSocket module abstracts the actions to be taken taken on connections regardless of + whether the connection started as inbound or outbound. + + + Normally outside modules will have no need to call the FedSocket module directly. + """ + + alias Pleroma.Object + alias Pleroma.Object.Containment + alias Pleroma.User + alias Pleroma.Web.ActivityPub.ObjectView + alias Pleroma.Web.ActivityPub.UserView + alias Pleroma.Web.ActivityPub.Visibility + alias Pleroma.Web.FedSockets.FetchRegistry + alias Pleroma.Web.FedSockets.IngesterWorker + alias Pleroma.Web.FedSockets.OutgoingHandler + alias Pleroma.Web.FedSockets.SocketInfo + + require Logger + + @shake "61dd18f7-f1e6-49a4-939a-a749fcdc1103" + + def connect_to_host(uri) do + case OutgoingHandler.start_link(uri) do + {:ok, pid} -> + {:ok, pid} + + error -> + {:error, error} + end + end + + def close(%SocketInfo{pid: socket_pid}), + do: Process.send(socket_pid, :close, []) + + def publish(%SocketInfo{pid: socket_pid}, json) do + %{action: :publish, data: json} + |> Jason.encode!() + |> send_packet(socket_pid) + end + + def fetch(%SocketInfo{pid: socket_pid}, id) do + fetch_uuid = FetchRegistry.register_fetch(id) + + %{action: :fetch, data: id, uuid: fetch_uuid} + |> Jason.encode!() + |> send_packet(socket_pid) + + wait_for_fetch_to_return(fetch_uuid, 0) + end + + def receive_package(%SocketInfo{} = fed_socket, json) do + json + |> Jason.decode!() + |> process_package(fed_socket) + end + + defp wait_for_fetch_to_return(uuid, cntr) do + case FetchRegistry.check_fetch(uuid) do + {:error, :waiting} -> + Process.sleep(:math.pow(cntr, 3) |> Kernel.trunc()) + wait_for_fetch_to_return(uuid, cntr + 1) + + {:error, :missing} -> + Logger.error("FedSocket fetch timed out - #{inspect(uuid)}") + {:error, :timeout} + + {:ok, _fr} -> + FetchRegistry.pop_fetch(uuid) + end + end + + defp process_package(%{"action" => "publish", "data" => data}, %{origin: origin} = _fed_socket) do + if Containment.contain_origin(origin, data) do + IngesterWorker.enqueue("ingest", %{"object" => data}) + end + + {:reply, %{"action" => "publish_reply", "status" => "processed"}} + end + + defp process_package(%{"action" => "fetch_reply", "uuid" => uuid, "data" => data}, _fed_socket) do + FetchRegistry.register_fetch_received(uuid, data) + {:noreply, nil} + end + + defp process_package(%{"action" => "fetch", "uuid" => uuid, "data" => ap_id}, _fed_socket) do + {:ok, data} = render_fetched_data(ap_id, uuid) + {:reply, data} + end + + defp process_package(%{"action" => "publish_reply"}, _fed_socket) do + {:noreply, nil} + end + + defp process_package(other, _fed_socket) do + Logger.warn("unknown json packages received #{inspect(other)}") + {:noreply, nil} + end + + defp render_fetched_data(ap_id, uuid) do + {:ok, + %{ + "action" => "fetch_reply", + "status" => "processed", + "uuid" => uuid, + "data" => represent_item(ap_id) + }} + end + + defp represent_item(ap_id) do + case User.get_by_ap_id(ap_id) do + nil -> + object = Object.get_cached_by_ap_id(ap_id) + + if Visibility.is_public?(object) do + Phoenix.View.render_to_string(ObjectView, "object.json", object: object) + else + nil + end + + user -> + Phoenix.View.render_to_string(UserView, "user.json", user: user) + end + end + + defp send_packet(data, socket_pid) do + Process.send(socket_pid, {:send, data}, []) + end + + def shake, do: @shake +end diff --git a/lib/pleroma/web/fed_sockets/fed_sockets.ex b/lib/pleroma/web/fed_sockets/fed_sockets.ex new file mode 100644 index 000000000..1fd5899c8 --- /dev/null +++ b/lib/pleroma/web/fed_sockets/fed_sockets.ex @@ -0,0 +1,185 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.FedSockets do + @moduledoc """ + This documents the FedSockets framework. A framework for federating + ActivityPub objects between servers via persistant WebSocket connections. + + FedSockets allow servers to authenticate on first contact and maintain that + connection, eliminating the need to authenticate every time data needs to be shared. + + ## Protocol + FedSockets currently support 2 types of data transfer: + * `publish` method which doesn't require a response + * `fetch` method requires a response be sent + + ### Publish + The publish operation sends a json encoded map of the shape: + %{action: :publish, data: json} + and accepts (but does not require) a reply of form: + %{"action" => "publish_reply"} + + The outgoing params represent + * data: ActivityPub object encoded into json + + + ### Fetch + The fetch operation sends a json encoded map of the shape: + %{action: :fetch, data: id, uuid: fetch_uuid} + and requires a reply of form: + %{"action" => "fetch_reply", "uuid" => uuid, "data" => data} + + The outgoing params represent + * id: an ActivityPub object URI + * uuid: a unique uuid generated by the sender + + The reply params represent + * data: an ActivityPub object encoded into json + * uuid: the uuid sent along with the fetch request + + ## Examples + Clients of FedSocket transfers shouldn't need to use any of the functions outside of this module. + + A typical publish operation can be performed through the following code, and a fetch operation in a similar manner. + + case FedSockets.get_or_create_fed_socket(inbox) do + {:ok, fedsocket} -> + FedSockets.publish(fedsocket, json) + + _ -> + alternative_publish(inbox, actor, json, params) + end + + ## Configuration + FedSockets have the following config settings + + config :pleroma, :fed_sockets, + enabled: true, + ping_interval: :timer.seconds(15), + connection_duration: :timer.hours(1), + rejection_duration: :timer.hours(1), + fed_socket_fetches: [ + default: 12_000, + interval: 3_000, + lazy: false + ] + * enabled - turn FedSockets on or off with this flag. Can be toggled at runtime. + * connection_duration - How long a FedSocket can sit idle before it's culled. + * rejection_duration - After failing to make a FedSocket connection a host will be excluded + from further connections for this amount of time + * fed_socket_fetches - Use these parameters to pass options to the Cachex queue backing the FetchRegistry + * fed_socket_rejections - Use these parameters to pass options to the Cachex queue backing the FedRegistry + + Cachex options are + * default: the minimum amount of time a fetch can wait before it times out. + * interval: the interval between checks for timed out entries. This plus the default represent the maximum time allowed + * lazy: leave at false for consistant and fast lookups, set to true for stricter timeout enforcement + + """ + require Logger + + alias Pleroma.Web.FedSockets.FedRegistry + alias Pleroma.Web.FedSockets.FedSocket + alias Pleroma.Web.FedSockets.SocketInfo + + @doc """ + returns a FedSocket for the given origin. Will reuse an existing one or create a new one. + + address is expected to be a fully formed URL such as: + "http://www.example.com" or "http://www.example.com:8080" + + It can and usually does include additional path parameters, + but these are ignored as the FedSockets are organized by host and port info alone. + """ + def get_or_create_fed_socket(address) do + with {:cache, {:error, :missing}} <- {:cache, get_fed_socket(address)}, + {:connect, {:ok, _pid}} <- {:connect, FedSocket.connect_to_host(address)}, + {:cache, {:ok, fed_socket}} <- {:cache, get_fed_socket(address)} do + Logger.debug("fedsocket created for - #{inspect(address)}") + {:ok, fed_socket} + else + {:cache, {:ok, socket}} -> + Logger.debug("fedsocket found in cache - #{inspect(address)}") + {:ok, socket} + + {:cache, {:error, :rejected} = e} -> + e + + {:connect, {:error, _host}} -> + Logger.debug("set host rejected for - #{inspect(address)}") + FedRegistry.set_host_rejected(address) + {:error, :rejected} + + {_, {:error, :disabled}} -> + {:error, :disabled} + + {_, {:error, reason}} -> + Logger.warn("get_or_create_fed_socket error - #{inspect(reason)}") + {:error, reason} + end + end + + @doc """ + returns a FedSocket for the given origin. Will not create a new FedSocket if one does not exist. + + address is expected to be a fully formed URL such as: + "http://www.example.com" or "http://www.example.com:8080" + """ + def get_fed_socket(address) do + origin = SocketInfo.origin(address) + + with {:config, true} <- {:config, Pleroma.Config.get([:fed_sockets, :enabled], false)}, + {:ok, socket} <- FedRegistry.get_fed_socket(origin) do + {:ok, socket} + else + {:config, _} -> + {:error, :disabled} + + {:error, :rejected} -> + Logger.debug("FedSocket previously rejected - #{inspect(origin)}") + {:error, :rejected} + + {:error, reason} -> + {:error, reason} + end + end + + @doc """ + Sends the supplied data via the publish protocol. + It will not block waiting for a reply. + Returns :ok but this is not an indication of a successful transfer. + + the data is expected to be JSON encoded binary data. + """ + def publish(%SocketInfo{} = fed_socket, json) do + FedSocket.publish(fed_socket, json) + end + + @doc """ + Sends the supplied data via the fetch protocol. + It will block waiting for a reply or timeout. + + Returns {:ok, object} where object is the requested object (or nil) + {:error, :timeout} in the event the message was not responded to + + the id is expected to be the URI of an ActivityPub object. + """ + def fetch(%SocketInfo{} = fed_socket, id) do + FedSocket.fetch(fed_socket, id) + end + + @doc """ + Disconnect all and restart FedSockets. + This is mainly used in development and testing but could be useful in production. + """ + def reset do + FedRegistry + |> Process.whereis() + |> Process.exit(:testing) + end + + def uri_for_origin(origin), + do: "ws://#{origin}/api/fedsocket/v1" +end diff --git a/lib/pleroma/web/fed_sockets/fetch_registry.ex b/lib/pleroma/web/fed_sockets/fetch_registry.ex new file mode 100644 index 000000000..7897f0fc6 --- /dev/null +++ b/lib/pleroma/web/fed_sockets/fetch_registry.ex @@ -0,0 +1,151 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.FedSockets.FetchRegistry do + @moduledoc """ + The FetchRegistry acts as a broker for fetch requests and return values. + This allows calling processes to block while waiting for a reply. + It doesn't impose it's own process instead using `Cachex` to handle fetches in process, allowing + multi threaded processes to avoid bottlenecking. + + Normally outside modules will have no need to call or use the FetchRegistry themselves. + + The `Cachex` parameters can be controlled from the config. Since exact timeout intervals + aren't necessary the following settings are used by default: + + config :pleroma, :fed_sockets, + fed_socket_fetches: [ + default: 12_000, + interval: 3_000, + lazy: false + ] + + """ + + defmodule FetchRegistryData do + defstruct uuid: nil, + sent_json: nil, + received_json: nil, + sent_at: nil, + received_at: nil + end + + alias Ecto.UUID + + require Logger + + @fetches :fed_socket_fetches + + @doc """ + Registers a json request wth the FetchRegistry and returns the identifying UUID. + """ + def register_fetch(json) do + %FetchRegistryData{uuid: uuid} = + json + |> new_registry_data + |> save_registry_data + + uuid + end + + @doc """ + Reports on the status of a Fetch given the identifying UUID. + + Will return + * {:ok, fetched_object} if a fetch has completed + * {:error, :waiting} if a fetch is still pending + * {:error, other_error} usually :missing to indicate a fetch that has timed out + """ + def check_fetch(uuid) do + case get_registry_data(uuid) do + {:ok, %FetchRegistryData{received_at: nil}} -> + {:error, :waiting} + + {:ok, %FetchRegistryData{} = reg_data} -> + {:ok, reg_data} + + e -> + e + end + end + + @doc """ + Retrieves the response to a fetch given the identifying UUID. + The completed fetch will be deleted from the FetchRegistry + + Will return + * {:ok, fetched_object} if a fetch has completed + * {:error, :waiting} if a fetch is still pending + * {:error, other_error} usually :missing to indicate a fetch that has timed out + """ + def pop_fetch(uuid) do + case check_fetch(uuid) do + {:ok, %FetchRegistryData{received_json: received_json}} -> + delete_registry_data(uuid) + {:ok, received_json} + + e -> + e + end + end + + @doc """ + This is called to register a fetch has returned. + It expects the result data along with the UUID that was sent in the request + + Will return the fetched object or :error + """ + def register_fetch_received(uuid, data) do + case get_registry_data(uuid) do + {:ok, %FetchRegistryData{received_at: nil} = reg_data} -> + reg_data + |> set_fetch_received(data) + |> save_registry_data() + + {:ok, %FetchRegistryData{} = reg_data} -> + Logger.warn("tried to add fetched data twice - #{uuid}") + reg_data + + {:error, _} -> + Logger.warn("Error adding fetch to registry - #{uuid}") + :error + end + end + + defp new_registry_data(json) do + %FetchRegistryData{ + uuid: UUID.generate(), + sent_json: json, + sent_at: :erlang.monotonic_time(:millisecond) + } + end + + defp get_registry_data(origin) do + case Cachex.get(@fetches, origin) do + {:ok, nil} -> + {:error, :missing} + + {:ok, reg_data} -> + {:ok, reg_data} + + _ -> + {:error, :cache_error} + end + end + + defp set_fetch_received(%FetchRegistryData{} = reg_data, data), + do: %FetchRegistryData{ + reg_data + | received_at: :erlang.monotonic_time(:millisecond), + received_json: data + } + + defp save_registry_data(%FetchRegistryData{uuid: uuid} = reg_data) do + {:ok, true} = Cachex.put(@fetches, uuid, reg_data) + reg_data + end + + defp delete_registry_data(origin), + do: {:ok, true} = Cachex.del(@fetches, origin) +end diff --git a/lib/pleroma/web/fed_sockets/incoming_handler.ex b/lib/pleroma/web/fed_sockets/incoming_handler.ex new file mode 100644 index 000000000..49d0d9d84 --- /dev/null +++ b/lib/pleroma/web/fed_sockets/incoming_handler.ex @@ -0,0 +1,88 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.FedSockets.IncomingHandler do + require Logger + + alias Pleroma.Web.FedSockets.FedRegistry + alias Pleroma.Web.FedSockets.FedSocket + alias Pleroma.Web.FedSockets.SocketInfo + + import HTTPSignatures, only: [validate_conn: 1, split_signature: 1] + + @behaviour :cowboy_websocket + + def init(req, state) do + shake = FedSocket.shake() + + with true <- Pleroma.Config.get([:fed_sockets, :enabled]), + sec_protocol <- :cowboy_req.header("sec-websocket-protocol", req, nil), + headers = %{"(request-target)" => ^shake} <- :cowboy_req.headers(req), + true <- validate_conn(%{req_headers: headers}), + %{"keyId" => origin} <- split_signature(headers["signature"]) do + req = + if is_nil(sec_protocol) do + req + else + :cowboy_req.set_resp_header("sec-websocket-protocol", sec_protocol, req) + end + + {:cowboy_websocket, req, %{origin: origin}, %{}} + else + _ -> + {:ok, req, state} + end + end + + def websocket_init(%{origin: origin}) do + case FedRegistry.add_fed_socket(origin) do + {:ok, socket_info} -> + {:ok, socket_info} + + e -> + Logger.error("FedSocket websocket_init failed - #{inspect(e)}") + {:error, inspect(e)} + end + end + + # Use the ping to check if the connection should be expired + def websocket_handle(:ping, socket_info) do + if SocketInfo.expired?(socket_info) do + {:stop, socket_info} + else + {:ok, socket_info, :hibernate} + end + end + + def websocket_handle({:text, data}, socket_info) do + socket_info = SocketInfo.touch(socket_info) + + case FedSocket.receive_package(socket_info, data) do + {:noreply, _} -> + {:ok, socket_info} + + {:reply, reply} -> + {:reply, {:text, Jason.encode!(reply)}, socket_info} + + {:error, reason} -> + Logger.error("incoming error - receive_package: #{inspect(reason)}") + {:ok, socket_info} + end + end + + def websocket_info({:send, message}, socket_info) do + socket_info = SocketInfo.touch(socket_info) + + {:reply, {:text, message}, socket_info} + end + + def websocket_info(:close, state) do + {:stop, state} + end + + def websocket_info(message, state) do + Logger.debug("#{__MODULE__} unknown message #{inspect(message)}") + {:ok, state} + end +end diff --git a/lib/pleroma/web/fed_sockets/ingester_worker.ex b/lib/pleroma/web/fed_sockets/ingester_worker.ex new file mode 100644 index 000000000..325f2a4ab --- /dev/null +++ b/lib/pleroma/web/fed_sockets/ingester_worker.ex @@ -0,0 +1,33 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.FedSockets.IngesterWorker do + use Pleroma.Workers.WorkerHelper, queue: "ingestion_queue" + require Logger + + alias Pleroma.Web.Federator + + @impl Oban.Worker + def perform(%Job{args: %{"op" => "ingest", "object" => ingestee}}) do + try do + ingestee + |> Jason.decode!() + |> do_ingestion() + rescue + e -> + Logger.error("IngesterWorker error - #{inspect(e)}") + e + end + end + + defp do_ingestion(params) do + case Federator.incoming_ap_doc(params) do + {:error, reason} -> + {:error, reason} + + {:ok, object} -> + {:ok, object} + end + end +end diff --git a/lib/pleroma/web/fed_sockets/outgoing_handler.ex b/lib/pleroma/web/fed_sockets/outgoing_handler.ex new file mode 100644 index 000000000..e235a7c43 --- /dev/null +++ b/lib/pleroma/web/fed_sockets/outgoing_handler.ex @@ -0,0 +1,151 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.FedSockets.OutgoingHandler do + use GenServer + + require Logger + + alias Pleroma.Application + alias Pleroma.Web.ActivityPub.InternalFetchActor + alias Pleroma.Web.FedSockets + alias Pleroma.Web.FedSockets.FedRegistry + alias Pleroma.Web.FedSockets.FedSocket + alias Pleroma.Web.FedSockets.SocketInfo + + def start_link(uri) do + GenServer.start_link(__MODULE__, %{uri: uri}) + end + + def init(%{uri: uri}) do + case initiate_connection(uri) do + {:ok, ws_origin, conn_pid} -> + FedRegistry.add_fed_socket(ws_origin, conn_pid) + + {:error, reason} -> + Logger.debug("Outgoing connection failed - #{inspect(reason)}") + :ignore + end + end + + def handle_info({:gun_ws, conn_pid, _ref, {:text, data}}, socket_info) do + socket_info = SocketInfo.touch(socket_info) + + case FedSocket.receive_package(socket_info, data) do + {:noreply, _} -> + {:noreply, socket_info} + + {:reply, reply} -> + :gun.ws_send(conn_pid, {:text, Jason.encode!(reply)}) + {:noreply, socket_info} + + {:error, reason} -> + Logger.error("incoming error - receive_package: #{inspect(reason)}") + {:noreply, socket_info} + end + end + + def handle_info(:close, state) do + Logger.debug("Sending close frame !!!!!!!") + {:close, state} + end + + def handle_info({:gun_down, _pid, _prot, :closed, _}, state) do + {:stop, :normal, state} + end + + def handle_info({:send, data}, %{conn_pid: conn_pid} = socket_info) do + socket_info = SocketInfo.touch(socket_info) + :gun.ws_send(conn_pid, {:text, data}) + {:noreply, socket_info} + end + + def handle_info({:gun_ws, _, _, :pong}, state) do + {:noreply, state, :hibernate} + end + + def handle_info(msg, state) do + Logger.debug("#{__MODULE__} unhandled event #{inspect(msg)}") + {:noreply, state} + end + + def terminate(reason, state) do + Logger.debug( + "#{__MODULE__} terminating outgoing connection for #{inspect(state)} for #{inspect(reason)}" + ) + + {:ok, state} + end + + def initiate_connection(uri) do + ws_uri = + uri + |> SocketInfo.origin() + |> FedSockets.uri_for_origin() + + %{host: host, port: port, path: path} = URI.parse(ws_uri) + + with {:ok, conn_pid} <- :gun.open(to_charlist(host), port, %{protocols: [:http]}), + {:ok, _} <- :gun.await_up(conn_pid), + reference <- + :gun.get(conn_pid, to_charlist(path), [ + {'user-agent', to_charlist(Application.user_agent())} + ]), + {:response, :fin, 204, _} <- :gun.await(conn_pid, reference), + headers <- build_headers(uri), + ref <- :gun.ws_upgrade(conn_pid, to_charlist(path), headers, %{silence_pings: false}) do + receive do + {:gun_upgrade, ^conn_pid, ^ref, [<<"websocket">>], _} -> + {:ok, ws_uri, conn_pid} + after + 15_000 -> + Logger.debug("Fedsocket timeout connecting to #{inspect(uri)}") + {:error, :timeout} + end + else + {:response, :nofin, 404, _} -> + {:error, :fedsockets_not_supported} + + e -> + Logger.debug("Fedsocket error connecting to #{inspect(uri)}") + {:error, e} + end + end + + defp build_headers(uri) do + host_for_sig = uri |> URI.parse() |> host_signature() + + shake = FedSocket.shake() + digest = "SHA-256=" <> (:crypto.hash(:sha256, shake) |> Base.encode64()) + date = Pleroma.Signature.signed_date() + shake_size = byte_size(shake) + + signature_opts = %{ + "(request-target)": shake, + "content-length": to_charlist("#{shake_size}"), + date: date, + digest: digest, + host: host_for_sig + } + + signature = Pleroma.Signature.sign(InternalFetchActor.get_actor(), signature_opts) + + [ + {'signature', to_charlist(signature)}, + {'date', date}, + {'digest', to_charlist(digest)}, + {'content-length', to_charlist("#{shake_size}")}, + {to_charlist("(request-target)"), to_charlist(shake)}, + {'user-agent', to_charlist(Application.user_agent())} + ] + end + + defp host_signature(%{host: host, scheme: scheme, port: port}) do + if port == URI.default_port(scheme) do + host + else + "#{host}:#{port}" + end + end +end diff --git a/lib/pleroma/web/fed_sockets/socket_info.ex b/lib/pleroma/web/fed_sockets/socket_info.ex new file mode 100644 index 000000000..d6fdffe1a --- /dev/null +++ b/lib/pleroma/web/fed_sockets/socket_info.ex @@ -0,0 +1,52 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.FedSockets.SocketInfo do + defstruct origin: nil, + pid: nil, + conn_pid: nil, + state: :default, + connected_until: nil + + alias Pleroma.Web.FedSockets.SocketInfo + @default_connection_duration 15 * 60 * 1000 + + def build(uri, conn_pid \\ nil) do + uri + |> build_origin() + |> build_pids(conn_pid) + |> touch() + end + + def touch(%SocketInfo{} = socket_info), + do: %{socket_info | connected_until: new_ttl()} + + def connect(%SocketInfo{} = socket_info), + do: %{socket_info | state: :connected} + + def expired?(%{connected_until: connected_until}), + do: connected_until < :erlang.monotonic_time(:millisecond) + + def origin(uri), + do: build_origin(uri).origin + + defp build_pids(socket_info, conn_pid), + do: struct(socket_info, pid: self(), conn_pid: conn_pid) + + defp build_origin(uri) when is_binary(uri), + do: uri |> URI.parse() |> build_origin + + defp build_origin(%{host: host, port: nil, scheme: scheme}), + do: build_origin(%{host: host, port: URI.default_port(scheme)}) + + defp build_origin(%{host: host, port: port}), + do: %SocketInfo{origin: "#{host}:#{port}"} + + defp new_ttl do + connection_duration = + Pleroma.Config.get([:fed_sockets, :connection_duration], @default_connection_duration) + + :erlang.monotonic_time(:millisecond) + connection_duration + end +end diff --git a/lib/pleroma/web/fed_sockets/supervisor.ex b/lib/pleroma/web/fed_sockets/supervisor.ex new file mode 100644 index 000000000..a5f4bebfb --- /dev/null +++ b/lib/pleroma/web/fed_sockets/supervisor.ex @@ -0,0 +1,59 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.FedSockets.Supervisor do + use Supervisor + import Cachex.Spec + + def start_link(opts) do + Supervisor.start_link(__MODULE__, opts, name: __MODULE__) + end + + def init(args) do + children = [ + build_cache(:fed_socket_fetches, args), + build_cache(:fed_socket_rejections, args), + {Registry, keys: :unique, name: FedSockets.Registry, meta: [rejected: %{}]} + ] + + opts = [strategy: :one_for_all, name: Pleroma.Web.Streamer.Supervisor] + Supervisor.init(children, opts) + end + + defp build_cache(name, args) do + opts = get_opts(name, args) + + %{ + id: String.to_atom("#{name}_cache"), + start: {Cachex, :start_link, [name, opts]}, + type: :worker + } + end + + defp get_opts(cache_name, args) + when cache_name in [:fed_socket_fetches, :fed_socket_rejections] do + default = get_opts_or_config(args, cache_name, :default, 15_000) + interval = get_opts_or_config(args, cache_name, :interval, 3_000) + lazy = get_opts_or_config(args, cache_name, :lazy, false) + + [expiration: expiration(default: default, interval: interval, lazy: lazy)] + end + + defp get_opts(name, args) do + Keyword.get(args, name, []) + end + + defp get_opts_or_config(args, name, key, default) do + args + |> Keyword.get(name, []) + |> Keyword.get(key) + |> case do + nil -> + Pleroma.Config.get([:fed_sockets, name, key], default) + + value -> + value + end + end +end diff --git a/lib/pleroma/web/federator/federator.ex b/lib/pleroma/web/federator/federator.ex index f5803578d..130654145 100644 --- a/lib/pleroma/web/federator/federator.ex +++ b/lib/pleroma/web/federator/federator.ex @@ -66,14 +66,17 @@ def perform(:publish, activity) do def perform(:incoming_ap_doc, params) do Logger.debug("Handling incoming AP activity") - params = Utils.normalize_params(params) + actor = + params + |> Map.get("actor") + |> Utils.get_ap_id() # NOTE: we use the actor ID to do the containment, this is fine because an # actor shouldn't be acting on objects outside their own AP server. - with {:ok, _user} <- ap_enabled_actor(params["actor"]), + with {_, {:ok, _user}} <- {:actor, ap_enabled_actor(actor)}, nil <- Activity.normalize(params["id"]), {_, :ok} <- - {:correct_origin?, Containment.contain_origin_from_id(params["actor"], params)}, + {:correct_origin?, Containment.contain_origin_from_id(actor, params)}, {:ok, activity} <- Transmogrifier.handle_incoming(params) do {:ok, activity} else @@ -85,10 +88,13 @@ def perform(:incoming_ap_doc, params) do Logger.debug("Already had #{params["id"]}") {:error, :already_present} + {:actor, e} -> + Logger.debug("Unhandled actor #{actor}, #{inspect(e)}") + {:error, e} + e -> # Just drop those for now - Logger.debug("Unhandled activity") - Logger.debug(Jason.encode!(params, pretty: true)) + Logger.debug(fn -> "Unhandled activity\n" <> Jason.encode!(params, pretty: true) end) {:error, e} end end diff --git a/lib/pleroma/web/instance_document.ex b/lib/pleroma/web/instance_document.ex new file mode 100644 index 000000000..df5caebf0 --- /dev/null +++ b/lib/pleroma/web/instance_document.ex @@ -0,0 +1,62 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.InstanceDocument do + alias Pleroma.Config + alias Pleroma.Web.Endpoint + + @instance_documents %{ + "terms-of-service" => "/static/terms-of-service.html", + "instance-panel" => "/instance/panel.html" + } + + @spec get(String.t()) :: {:ok, String.t()} | {:error, atom()} + def get(document_name) do + case Map.fetch(@instance_documents, document_name) do + {:ok, path} -> {:ok, path} + _ -> {:error, :not_found} + end + end + + @spec put(String.t(), String.t()) :: {:ok, String.t()} | {:error, atom()} + def put(document_name, origin_path) do + with {_, {:ok, destination_path}} <- + {:instance_document, Map.fetch(@instance_documents, document_name)}, + :ok <- put_file(origin_path, destination_path) do + {:ok, Path.join(Endpoint.url(), destination_path)} + else + {:instance_document, :error} -> {:error, :not_found} + error -> error + end + end + + @spec delete(String.t()) :: :ok | {:error, atom()} + def delete(document_name) do + with {_, {:ok, path}} <- {:instance_document, Map.fetch(@instance_documents, document_name)}, + instance_static_dir_path <- instance_static_dir(path), + :ok <- File.rm(instance_static_dir_path) do + :ok + else + {:instance_document, :error} -> {:error, :not_found} + {:error, :enoent} -> {:error, :not_found} + error -> error + end + end + + defp put_file(origin_path, destination_path) do + with destination <- instance_static_dir(destination_path), + {_, :ok} <- {:mkdir_p, File.mkdir_p(Path.dirname(destination))}, + {_, {:ok, _}} <- {:copy, File.copy(origin_path, destination)} do + :ok + else + {error, _} -> {:error, error} + end + end + + defp instance_static_dir(filename) do + [:instance, :static_dir] + |> Config.get!() + |> Path.join(filename) + end +end diff --git a/lib/pleroma/web/mastodon_api/controllers/list_controller.ex b/lib/pleroma/web/mastodon_api/controllers/list_controller.ex index acdc76fd2..5daeaa780 100644 --- a/lib/pleroma/web/mastodon_api/controllers/list_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/list_controller.ex @@ -74,7 +74,7 @@ def add_to_list(%{assigns: %{list: list}, body_params: %{account_ids: account_id # DELETE /api/v1/lists/:id/accounts def remove_from_list( - %{assigns: %{list: list}, body_params: %{account_ids: account_ids}} = conn, + %{assigns: %{list: list}, params: %{account_ids: account_ids}} = conn, _ ) do Enum.each(account_ids, fn account_id -> @@ -86,6 +86,10 @@ def remove_from_list( json(conn, %{}) end + def remove_from_list(%{body_params: params} = conn, _) do + remove_from_list(%{conn | params: params}, %{}) + end + defp list_by_id_and_user(%{assigns: %{user: user}, params: %{id: id}} = conn, _) do case Pleroma.List.get(id, user) do %Pleroma.List{} = list -> assign(conn, :list, list) diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 864c0417f..121ba1693 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -181,8 +181,10 @@ defp do_render("show.json", %{user: user} = opts) do user = User.sanitize_html(user, User.html_filter_policy(opts[:for])) display_name = user.name || user.nickname - image = User.avatar_url(user) |> MediaProxy.url() + avatar = User.avatar_url(user) |> MediaProxy.url() + avatar_static = User.avatar_url(user) |> MediaProxy.preview_url(static: true) header = User.banner_url(user) |> MediaProxy.url() + header_static = User.banner_url(user) |> MediaProxy.preview_url(static: true) following_count = if !user.hide_follows_count or !user.hide_follows or opts[:for] == user do @@ -245,12 +247,12 @@ defp do_render("show.json", %{user: user} = opts) do followers_count: followers_count, following_count: following_count, statuses_count: user.note_count, - note: user.bio || "", + note: user.bio, url: user.uri || user.ap_id, - avatar: image, - avatar_static: image, + avatar: avatar, + avatar_static: avatar_static, header: header, - header_static: header, + header_static: header_static, emojis: emojis, fields: user.fields, bot: bot, diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 3fe1967be..435bcde15 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -8,7 +8,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do require Pleroma.Constants alias Pleroma.Activity - alias Pleroma.ActivityExpiration alias Pleroma.HTML alias Pleroma.Object alias Pleroma.Repo @@ -56,23 +55,6 @@ defp get_replied_to_activities(activities) do end) end - def get_user(ap_id, fake_record_fallback \\ true) do - cond do - user = User.get_cached_by_ap_id(ap_id) -> - user - - user = User.get_by_guessed_nickname(ap_id) -> - user - - fake_record_fallback -> - # TODO: refactor (fake records is never a good idea) - User.error_user(ap_id) - - true -> - nil - end - end - defp get_context_id(%{data: %{"context_id" => context_id}}) when not is_nil(context_id), do: context_id @@ -120,7 +102,7 @@ def render("index.json", opts) do # Note: unresolved users are filtered out actors = (activities ++ parent_activities) - |> Enum.map(&get_user(&1.data["actor"], false)) + |> Enum.map(&CommonAPI.get_user(&1.data["actor"], false)) |> Enum.filter(& &1) UserRelationship.view_relationships_option(reading_user, actors, subset: :source_mutes) @@ -139,7 +121,7 @@ def render( "show.json", %{activity: %{data: %{"type" => "Announce", "object" => _object}} = activity} = opts ) do - user = get_user(activity.data["actor"]) + user = CommonAPI.get_user(activity.data["actor"]) created_at = Utils.to_masto_date(activity.data["published"]) activity_object = Object.normalize(activity) @@ -212,7 +194,7 @@ def render( def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do object = Object.normalize(activity) - user = get_user(activity.data["actor"]) + user = CommonAPI.get_user(activity.data["actor"]) user_follower_address = user.follower_address like_count = object.data["like_count"] || 0 @@ -245,8 +227,8 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} expires_at = with true <- client_posted_this_activity, - %ActivityExpiration{scheduled_at: scheduled_at} <- - ActivityExpiration.get_by_activity_id(activity.id) do + %Oban.Job{scheduled_at: scheduled_at} <- + Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id) do scheduled_at else _ -> nil @@ -266,7 +248,7 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} reply_to = get_reply_to(activity, opts) - reply_to_user = reply_to && get_user(reply_to.data["actor"]) + reply_to_user = reply_to && CommonAPI.get_user(reply_to.data["actor"]) content = object @@ -433,6 +415,7 @@ def render("attachment.json", %{attachment: attachment}) do [attachment_url | _] = attachment["url"] media_type = attachment_url["mediaType"] || attachment_url["mimeType"] || "image" href = attachment_url["href"] |> MediaProxy.url() + href_preview = attachment_url["href"] |> MediaProxy.preview_url() type = cond do @@ -448,7 +431,7 @@ def render("attachment.json", %{attachment: attachment}) do id: to_string(attachment["id"] || hash_id), url: href, remote_url: href, - preview_url: href, + preview_url: href_preview, text_url: href, type: type, description: attachment["name"], diff --git a/lib/pleroma/web/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex index cf923ded8..439cdd716 100644 --- a/lib/pleroma/web/mastodon_api/websocket_handler.ex +++ b/lib/pleroma/web/mastodon_api/websocket_handler.ex @@ -23,8 +23,8 @@ def init(%{qs: qs} = req, state) do with params <- Enum.into(:cow_qs.parse_qs(qs), %{}), sec_websocket <- :cowboy_req.header("sec-websocket-protocol", req, nil), access_token <- Map.get(params, "access_token"), - {:ok, user} <- authenticate_request(access_token, sec_websocket), - {:ok, topic} <- Streamer.get_topic(Map.get(params, "stream"), user, params) do + {:ok, user, oauth_token} <- authenticate_request(access_token, sec_websocket), + {:ok, topic} <- Streamer.get_topic(params["stream"], user, oauth_token, params) do req = if sec_websocket do :cowboy_req.set_resp_header("sec-websocket-protocol", sec_websocket, req) @@ -117,7 +117,7 @@ def terminate(reason, _req, state) do # Public streams without authentication. defp authenticate_request(nil, nil) do - {:ok, nil} + {:ok, nil, nil} end # Authenticated streams. @@ -125,9 +125,9 @@ defp authenticate_request(access_token, sec_websocket) do token = access_token || sec_websocket with true <- is_bitstring(token), - %Token{user_id: user_id} <- Repo.get_by(Token, token: token), + oauth_token = %Token{user_id: user_id} <- Repo.get_by(Token, token: token), user = %User{} <- User.get_cached_by_id(user_id) do - {:ok, user} + {:ok, user, oauth_token} else _ -> {:error, :unauthorized} end diff --git a/lib/pleroma/web/media_proxy/invalidation.ex b/lib/pleroma/web/media_proxy/invalidation.ex index 5808861e6..4f4340478 100644 --- a/lib/pleroma/web/media_proxy/invalidation.ex +++ b/lib/pleroma/web/media_proxy/invalidation.ex @@ -33,6 +33,8 @@ defp do_purge(urls) do def prepare_urls(urls) do urls |> List.wrap() - |> Enum.map(&MediaProxy.url/1) + |> Enum.map(fn url -> [MediaProxy.url(url), MediaProxy.preview_url(url)] end) + |> List.flatten() + |> Enum.uniq() end end diff --git a/lib/pleroma/web/media_proxy/media_proxy.ex b/lib/pleroma/web/media_proxy/media_proxy.ex index e18dd8224..8656b8cad 100644 --- a/lib/pleroma/web/media_proxy/media_proxy.ex +++ b/lib/pleroma/web/media_proxy/media_proxy.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.MediaProxy do alias Pleroma.Config + alias Pleroma.Helpers.UriHelper alias Pleroma.Upload alias Pleroma.Web alias Pleroma.Web.MediaProxy.Invalidation @@ -40,27 +41,35 @@ def url(url) when is_nil(url) or url == "", do: nil def url("/" <> _ = url), do: url def url(url) do - if disabled?() or not url_proxiable?(url) do - url - else + if enabled?() and url_proxiable?(url) do encode_url(url) + else + url end end @spec url_proxiable?(String.t()) :: boolean() def url_proxiable?(url) do - if local?(url) or whitelisted?(url) do - false + not local?(url) and not whitelisted?(url) + end + + def preview_url(url, preview_params \\ []) do + if preview_enabled?() do + encode_preview_url(url, preview_params) else - true + url(url) end end - defp disabled?, do: !Config.get([:media_proxy, :enabled], false) + def enabled?, do: Config.get([:media_proxy, :enabled], false) - defp local?(url), do: String.starts_with?(url, Pleroma.Web.base_url()) + # Note: media proxy must be enabled for media preview proxy in order to load all + # non-local non-whitelisted URLs through it and be sure that body size constraint is preserved. + def preview_enabled?, do: enabled?() and !!Config.get([:media_preview_proxy, :enabled]) - defp whitelisted?(url) do + def local?(url), do: String.starts_with?(url, Pleroma.Web.base_url()) + + def whitelisted?(url) do %{host: domain} = URI.parse(url) mediaproxy_whitelist_domains = @@ -85,17 +94,29 @@ defp maybe_get_domain_from_url("http" <> _ = url) do defp maybe_get_domain_from_url(domain), do: domain - def encode_url(url) do + defp base64_sig64(url) do base64 = Base.url_encode64(url, @base64_opts) sig64 = base64 - |> signed_url + |> signed_url() |> Base.url_encode64(@base64_opts) + {base64, sig64} + end + + def encode_url(url) do + {base64, sig64} = base64_sig64(url) + build_url(sig64, base64, filename(url)) end + def encode_preview_url(url, preview_params \\ []) do + {base64, sig64} = base64_sig64(url) + + build_preview_url(sig64, base64, filename(url), preview_params) + end + def decode_url(sig, url) do with {:ok, sig} <- Base.url_decode64(sig, @base64_opts), signature when signature == sig <- signed_url(url) do @@ -113,10 +134,14 @@ def filename(url_or_path) do if path = URI.parse(url_or_path).path, do: Path.basename(path) end - def build_url(sig_base64, url_base64, filename \\ nil) do + def base_url do + Config.get([:media_proxy, :base_url], Web.base_url()) + end + + defp proxy_url(path, sig_base64, url_base64, filename) do [ - Config.get([:media_proxy, :base_url], Web.base_url()), - "proxy", + base_url(), + path, sig_base64, url_base64, filename @@ -124,4 +149,38 @@ def build_url(sig_base64, url_base64, filename \\ nil) do |> Enum.filter(& &1) |> Path.join() end + + def build_url(sig_base64, url_base64, filename \\ nil) do + proxy_url("proxy", sig_base64, url_base64, filename) + end + + def build_preview_url(sig_base64, url_base64, filename \\ nil, preview_params \\ []) do + uri = proxy_url("proxy/preview", sig_base64, url_base64, filename) + + UriHelper.modify_uri_params(uri, preview_params) + end + + def verify_request_path_and_url( + %Plug.Conn{params: %{"filename" => _}, request_path: request_path}, + url + ) do + verify_request_path_and_url(request_path, url) + end + + def verify_request_path_and_url(request_path, url) when is_binary(request_path) do + filename = filename(url) + + if filename && not basename_matches?(request_path, filename) do + {:wrong_filename, filename} + else + :ok + end + end + + def verify_request_path_and_url(_, _), do: :ok + + defp basename_matches?(path, filename) do + basename = Path.basename(path) + basename == filename or URI.decode(basename) == filename or URI.encode(basename) == filename + end end diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index 9a64b0ef3..90651ed9b 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -5,44 +5,201 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do use Pleroma.Web, :controller + alias Pleroma.Config + alias Pleroma.Helpers.MediaHelper + alias Pleroma.Helpers.UriHelper alias Pleroma.ReverseProxy alias Pleroma.Web.MediaProxy + alias Plug.Conn - @default_proxy_opts [max_body_length: 25 * 1_048_576, http: [follow_redirect: true]] - - def remote(conn, %{"sig" => sig64, "url" => url64} = params) do - with config <- Pleroma.Config.get([:media_proxy], []), - true <- Keyword.get(config, :enabled, false), + def remote(conn, %{"sig" => sig64, "url" => url64}) do + with {_, true} <- {:enabled, MediaProxy.enabled?()}, {:ok, url} <- MediaProxy.decode_url(sig64, url64), {_, false} <- {:in_banned_urls, MediaProxy.in_banned_urls(url)}, - :ok <- filename_matches(params, conn.request_path, url) do - ReverseProxy.call(conn, url, Keyword.get(config, :proxy_opts, @default_proxy_opts)) + :ok <- MediaProxy.verify_request_path_and_url(conn, url) do + ReverseProxy.call(conn, url, media_proxy_opts()) else - error when error in [false, {:in_banned_urls, true}] -> - send_resp(conn, 404, Plug.Conn.Status.reason_phrase(404)) + {:enabled, false} -> + send_resp(conn, 404, Conn.Status.reason_phrase(404)) + + {:in_banned_urls, true} -> + send_resp(conn, 404, Conn.Status.reason_phrase(404)) {:error, :invalid_signature} -> - send_resp(conn, 403, Plug.Conn.Status.reason_phrase(403)) + send_resp(conn, 403, Conn.Status.reason_phrase(403)) {:wrong_filename, filename} -> redirect(conn, external: MediaProxy.build_url(sig64, url64, filename)) end end - def filename_matches(%{"filename" => _} = _, path, url) do - filename = MediaProxy.filename(url) - - if filename && does_not_match(path, filename) do - {:wrong_filename, filename} + def preview(%Conn{} = conn, %{"sig" => sig64, "url" => url64}) do + with {_, true} <- {:enabled, MediaProxy.preview_enabled?()}, + {:ok, url} <- MediaProxy.decode_url(sig64, url64), + :ok <- MediaProxy.verify_request_path_and_url(conn, url) do + handle_preview(conn, url) else - :ok + {:enabled, false} -> + send_resp(conn, 404, Conn.Status.reason_phrase(404)) + + {:error, :invalid_signature} -> + send_resp(conn, 403, Conn.Status.reason_phrase(403)) + + {:wrong_filename, filename} -> + redirect(conn, external: MediaProxy.build_preview_url(sig64, url64, filename)) end end - def filename_matches(_, _, _), do: :ok + defp handle_preview(conn, url) do + media_proxy_url = MediaProxy.url(url) - defp does_not_match(path, filename) do - basename = Path.basename(path) - basename != filename and URI.decode(basename) != filename and URI.encode(basename) != filename + with {:ok, %{status: status} = head_response} when status in 200..299 <- + Pleroma.HTTP.request("head", media_proxy_url, [], [], pool: :media) do + content_type = Tesla.get_header(head_response, "content-type") + content_length = Tesla.get_header(head_response, "content-length") + content_length = content_length && String.to_integer(content_length) + static = conn.params["static"] in ["true", true] + + cond do + static and content_type == "image/gif" -> + handle_jpeg_preview(conn, media_proxy_url) + + static -> + drop_static_param_and_redirect(conn) + + content_type == "image/gif" -> + redirect(conn, external: media_proxy_url) + + min_content_length_for_preview() > 0 and content_length > 0 and + content_length < min_content_length_for_preview() -> + redirect(conn, external: media_proxy_url) + + true -> + handle_preview(content_type, conn, media_proxy_url) + end + else + # If HEAD failed, redirecting to media proxy URI doesn't make much sense; returning an error + {_, %{status: status}} -> + send_resp(conn, :failed_dependency, "Can't fetch HTTP headers (HTTP #{status}).") + + {:error, :recv_response_timeout} -> + send_resp(conn, :failed_dependency, "HEAD request timeout.") + + _ -> + send_resp(conn, :failed_dependency, "Can't fetch HTTP headers.") + end + end + + defp handle_preview("image/png" <> _ = _content_type, conn, media_proxy_url) do + handle_png_preview(conn, media_proxy_url) + end + + defp handle_preview("image/" <> _ = _content_type, conn, media_proxy_url) do + handle_jpeg_preview(conn, media_proxy_url) + end + + defp handle_preview("video/" <> _ = _content_type, conn, media_proxy_url) do + handle_video_preview(conn, media_proxy_url) + end + + defp handle_preview(_unsupported_content_type, conn, media_proxy_url) do + fallback_on_preview_error(conn, media_proxy_url) + end + + defp handle_png_preview(conn, media_proxy_url) do + quality = Config.get!([:media_preview_proxy, :image_quality]) + {thumbnail_max_width, thumbnail_max_height} = thumbnail_max_dimensions() + + with {:ok, thumbnail_binary} <- + MediaHelper.image_resize( + media_proxy_url, + %{ + max_width: thumbnail_max_width, + max_height: thumbnail_max_height, + quality: quality, + format: "png" + } + ) do + conn + |> put_preview_response_headers(["image/png", "preview.png"]) + |> send_resp(200, thumbnail_binary) + else + _ -> + fallback_on_preview_error(conn, media_proxy_url) + end + end + + defp handle_jpeg_preview(conn, media_proxy_url) do + quality = Config.get!([:media_preview_proxy, :image_quality]) + {thumbnail_max_width, thumbnail_max_height} = thumbnail_max_dimensions() + + with {:ok, thumbnail_binary} <- + MediaHelper.image_resize( + media_proxy_url, + %{max_width: thumbnail_max_width, max_height: thumbnail_max_height, quality: quality} + ) do + conn + |> put_preview_response_headers() + |> send_resp(200, thumbnail_binary) + else + _ -> + fallback_on_preview_error(conn, media_proxy_url) + end + end + + defp handle_video_preview(conn, media_proxy_url) do + with {:ok, thumbnail_binary} <- + MediaHelper.video_framegrab(media_proxy_url) do + conn + |> put_preview_response_headers() + |> send_resp(200, thumbnail_binary) + else + _ -> + fallback_on_preview_error(conn, media_proxy_url) + end + end + + defp drop_static_param_and_redirect(conn) do + uri_without_static_param = + conn + |> current_url() + |> UriHelper.modify_uri_params(%{}, ["static"]) + + redirect(conn, external: uri_without_static_param) + end + + defp fallback_on_preview_error(conn, media_proxy_url) do + redirect(conn, external: media_proxy_url) + end + + defp put_preview_response_headers( + conn, + [content_type, filename] = _content_info \\ ["image/jpeg", "preview.jpg"] + ) do + conn + |> put_resp_header("content-type", content_type) + |> put_resp_header("content-disposition", "inline; filename=\"#{filename}\"") + |> put_resp_header("cache-control", ReverseProxy.default_cache_control_header()) + end + + defp thumbnail_max_dimensions do + config = media_preview_proxy_config() + + thumbnail_max_width = Keyword.fetch!(config, :thumbnail_max_width) + thumbnail_max_height = Keyword.fetch!(config, :thumbnail_max_height) + + {thumbnail_max_width, thumbnail_max_height} + end + + defp min_content_length_for_preview do + Keyword.get(media_preview_proxy_config(), :min_content_length, 0) + end + + defp media_preview_proxy_config do + Config.get!([:media_preview_proxy]) + end + + defp media_proxy_opts do + Config.get([:media_proxy, :proxy_opts], []) end end diff --git a/lib/pleroma/web/metadata/opengraph.ex b/lib/pleroma/web/metadata/opengraph.ex index 68c871e71..bb1b23208 100644 --- a/lib/pleroma/web/metadata/opengraph.ex +++ b/lib/pleroma/web/metadata/opengraph.ex @@ -61,7 +61,7 @@ def build_tags(%{ @impl Provider 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, [ diff --git a/lib/pleroma/web/metadata/restrict_indexing.ex b/lib/pleroma/web/metadata/restrict_indexing.ex index f15607896..a1dcb6e15 100644 --- a/lib/pleroma/web/metadata/restrict_indexing.ex +++ b/lib/pleroma/web/metadata/restrict_indexing.ex @@ -10,7 +10,9 @@ defmodule Pleroma.Web.Metadata.Providers.RestrictIndexing do """ @impl true - def build_tags(%{user: %{local: false}}) do + def build_tags(%{user: %{local: true, discoverable: true}}), do: [] + + def build_tags(_) do [ {:meta, [ @@ -19,7 +21,4 @@ def build_tags(%{user: %{local: false}}) do ], []} ] end - - @impl true - def build_tags(%{user: %{local: true}}), do: [] end diff --git a/lib/pleroma/web/metadata/twitter_card.ex b/lib/pleroma/web/metadata/twitter_card.ex index 5d08ce422..df34b033f 100644 --- a/lib/pleroma/web/metadata/twitter_card.ex +++ b/lib/pleroma/web/metadata/twitter_card.ex @@ -40,7 +40,7 @@ def build_tags(%{activity_id: id, object: object, user: user}) do @impl Provider 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 [ title_tag(user), {:meta, [property: "twitter:description", content: truncated_bio], []}, diff --git a/lib/pleroma/web/metadata/utils.ex b/lib/pleroma/web/metadata/utils.ex index 2f0dfb474..8a206e019 100644 --- a/lib/pleroma/web/metadata/utils.ex +++ b/lib/pleroma/web/metadata/utils.ex @@ -38,7 +38,7 @@ def scrub_html(content) when is_binary(content) do def scrub_html(content), do: content def attachment_url(url) do - MediaProxy.url(url) + MediaProxy.preview_url(url) end def user_name_string(user) do diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex index 06b116368..a4152e840 100644 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@ -119,7 +119,7 @@ defp handle_existing_authorization( redirect_uri = redirect_uri(conn, redirect_uri) url_params = %{access_token: token.token} url_params = Maps.put_if_present(url_params, :state, params["state"]) - url = UriHelper.append_uri_params(redirect_uri, url_params) + url = UriHelper.modify_uri_params(redirect_uri, url_params) redirect(conn, external: url) else conn @@ -161,7 +161,7 @@ def after_create_authorization(%Plug.Conn{} = conn, %Authorization{} = auth, %{ redirect_uri = redirect_uri(conn, redirect_uri) url_params = %{code: auth.token} url_params = Maps.put_if_present(url_params, :state, auth_attrs["state"]) - url = UriHelper.append_uri_params(redirect_uri, url_params) + url = UriHelper.modify_uri_params(redirect_uri, url_params) redirect(conn, external: url) else conn @@ -200,7 +200,7 @@ defp handle_create_authorization_error( {:mfa_required, user, auth, _}, params ) do - {:ok, token} = MFA.Token.create_token(user, auth) + {:ok, token} = MFA.Token.create(user, auth) data = %{ "mfa_token" => token.token, @@ -582,7 +582,7 @@ defp put_session_registration_id(%Plug.Conn{} = conn, registration_id), do: put_session(conn, :registration_id, registration_id) defp build_and_response_mfa_token(user, auth) do - with {:ok, token} <- MFA.Token.create_token(user, auth) do + with {:ok, token} <- MFA.Token.create(user, auth) do MFAView.render("mfa_response.json", %{token: token, user: user}) end end diff --git a/lib/pleroma/web/oauth/token.ex b/lib/pleroma/web/oauth/token.ex index 08bb7326d..de37998f2 100644 --- a/lib/pleroma/web/oauth/token.ex +++ b/lib/pleroma/web/oauth/token.ex @@ -50,7 +50,7 @@ def exchange_token(app, auth) do true <- auth.app_id == app.id do user = if auth.user_id, do: User.get_cached_by_id(auth.user_id), else: %User{} - create_token( + create( app, user, %{scopes: auth.scopes} @@ -83,8 +83,22 @@ defp put_valid_until(changeset, attrs) do |> validate_required([:valid_until]) end - @spec create_token(App.t(), User.t(), map()) :: {:ok, Token} | {:error, Changeset.t()} - def create_token(%App{} = app, %User{} = user, attrs \\ %{}) do + @spec create(App.t(), User.t(), map()) :: {:ok, Token} | {:error, Changeset.t()} + def create(%App{} = app, %User{} = user, attrs \\ %{}) do + with {:ok, token} <- do_create(app, user, attrs) do + if Pleroma.Config.get([:oauth2, :clean_expired_tokens]) do + Pleroma.Workers.PurgeExpiredToken.enqueue(%{ + token_id: token.id, + valid_until: DateTime.from_naive!(token.valid_until, "Etc/UTC"), + mod: __MODULE__ + }) + end + + {:ok, token} + end + end + + defp do_create(app, user, attrs) do %__MODULE__{user_id: user.id, app_id: app.id} |> cast(%{scopes: attrs[:scopes] || app.scopes}, [:scopes]) |> validate_required([:scopes, :app_id]) @@ -105,11 +119,6 @@ def delete_user_token(%User{id: user_id}, token_id) do |> Repo.delete_all() end - def delete_expired_tokens do - Query.get_expired_tokens() - |> Repo.delete_all() - end - def get_user_tokens(%User{id: user_id}) do Query.get_by_user(user_id) |> Query.preload([:app]) diff --git a/lib/pleroma/web/oauth/token/clean_worker.ex b/lib/pleroma/web/oauth/token/clean_worker.ex deleted file mode 100644 index e3aa4eb7e..000000000 --- a/lib/pleroma/web/oauth/token/clean_worker.ex +++ /dev/null @@ -1,38 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.OAuth.Token.CleanWorker do - @moduledoc """ - The module represents functions to clean an expired OAuth and MFA tokens. - """ - use GenServer - - @ten_seconds 10_000 - @one_day 86_400_000 - - alias Pleroma.MFA - alias Pleroma.Web.OAuth - alias Pleroma.Workers.BackgroundWorker - - def start_link(_), do: GenServer.start_link(__MODULE__, %{}) - - def init(_) do - Process.send_after(self(), :perform, @ten_seconds) - {:ok, nil} - end - - @doc false - def handle_info(:perform, state) do - BackgroundWorker.enqueue("clean_expired_tokens", %{}) - interval = Pleroma.Config.get([:oauth2, :clean_expired_tokens_interval], @one_day) - - Process.send_after(self(), :perform, interval) - {:noreply, state} - end - - def perform(:clean) do - OAuth.Token.delete_expired_tokens() - MFA.Token.delete_expired_tokens() - end -end diff --git a/lib/pleroma/web/oauth/token/query.ex b/lib/pleroma/web/oauth/token/query.ex index 93d6e26ed..fd6d9b112 100644 --- a/lib/pleroma/web/oauth/token/query.ex +++ b/lib/pleroma/web/oauth/token/query.ex @@ -33,12 +33,6 @@ def get_by_id(query \\ Token, id) do from(q in query, where: q.id == ^id) end - @spec get_expired_tokens(query, DateTime.t() | nil) :: query - def get_expired_tokens(query \\ Token, date \\ nil) do - expired_date = date || Timex.now() - from(q in query, where: fragment("?", q.valid_until) < ^expired_date) - end - @spec get_by_user(query, String.t()) :: query def get_by_user(query \\ Token, user_id) do from(q in query, where: q.user_id == ^user_id) diff --git a/lib/pleroma/web/oauth/token/strategy/refresh_token.ex b/lib/pleroma/web/oauth/token/strategy/refresh_token.ex index debc29b0b..625b0fde2 100644 --- a/lib/pleroma/web/oauth/token/strategy/refresh_token.ex +++ b/lib/pleroma/web/oauth/token/strategy/refresh_token.ex @@ -46,7 +46,7 @@ defp revoke_access_token(token) do defp create_access_token({:error, error}, _), do: {:error, error} defp create_access_token({:ok, token}, %{app: app, user: user} = token_params) do - Token.create_token(app, user, add_refresh_token(token_params, token.refresh_token)) + Token.create(app, user, add_refresh_token(token_params, token.refresh_token)) end defp add_refresh_token(params, token) do diff --git a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex index ea0921c77..867cff829 100644 --- a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex @@ -156,13 +156,8 @@ def index(%{assigns: %{user: %{id: user_id} = user}} = conn, _params) do blocked_ap_ids = User.blocked_users_ap_ids(user) chats = - from(c in Chat, - where: c.user_id == ^user_id, - where: c.recipient not in ^blocked_ap_ids, - order_by: [desc: c.updated_at], - inner_join: u in User, - on: u.ap_id == c.recipient - ) + Chat.for_user_query(user_id) + |> where([c], c.recipient not in ^blocked_ap_ids) |> Repo.all() conn diff --git a/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex b/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex new file mode 100644 index 000000000..f10c45750 --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex @@ -0,0 +1,61 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.UserImportController do + use Pleroma.Web, :controller + + require Logger + + alias Pleroma.Plugs.OAuthScopesPlug + alias Pleroma.User + alias Pleroma.Web.ApiSpec + + plug(OAuthScopesPlug, %{scopes: ["follow", "write:follows"]} when action == :follow) + plug(OAuthScopesPlug, %{scopes: ["follow", "write:blocks"]} when action == :blocks) + plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action == :mutes) + + plug(OpenApiSpex.Plug.CastAndValidate) + defdelegate open_api_operation(action), to: ApiSpec.UserImportOperation + + def follow(%{body_params: %{list: %Plug.Upload{path: path}}} = conn, _) do + follow(%Plug.Conn{conn | body_params: %{list: File.read!(path)}}, %{}) + end + + def follow(%{assigns: %{user: follower}, body_params: %{list: list}} = conn, _) do + identifiers = + list + |> String.split("\n") + |> Enum.map(&(&1 |> String.split(",") |> List.first())) + |> List.delete("Account address") + |> Enum.map(&(&1 |> String.trim() |> String.trim_leading("@"))) + |> Enum.reject(&(&1 == "")) + + User.Import.follow_import(follower, identifiers) + json(conn, "job started") + end + + def blocks(%{body_params: %{list: %Plug.Upload{path: path}}} = conn, _) do + blocks(%Plug.Conn{conn | body_params: %{list: File.read!(path)}}, %{}) + end + + def blocks(%{assigns: %{user: blocker}, body_params: %{list: list}} = conn, _) do + User.Import.blocks_import(blocker, prepare_user_identifiers(list)) + json(conn, "job started") + end + + def mutes(%{body_params: %{list: %Plug.Upload{path: path}}} = conn, _) do + mutes(%Plug.Conn{conn | body_params: %{list: File.read!(path)}}, %{}) + end + + def mutes(%{assigns: %{user: user}, body_params: %{list: list}} = conn, _) do + User.Import.mutes_import(user, prepare_user_identifiers(list)) + json(conn, "job started") + end + + defp prepare_user_identifiers(list) do + list + |> String.split() + |> Enum.map(&String.trim_leading(&1, "@")) + end +end diff --git a/lib/pleroma/web/pleroma_api/views/scrobble_view.ex b/lib/pleroma/web/pleroma_api/views/scrobble_view.ex index bbff93abe..95bd4c368 100644 --- a/lib/pleroma/web/pleroma_api/views/scrobble_view.ex +++ b/lib/pleroma/web/pleroma_api/views/scrobble_view.ex @@ -10,14 +10,14 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleView do alias Pleroma.Activity alias Pleroma.HTML alias Pleroma.Object + alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.MastodonAPI.AccountView - alias Pleroma.Web.MastodonAPI.StatusView def render("show.json", %{activity: %Activity{data: %{"type" => "Listen"}} = activity} = opts) do object = Object.normalize(activity) - user = StatusView.get_user(activity.data["actor"]) + user = CommonAPI.get_user(activity.data["actor"]) created_at = Utils.to_masto_date(activity.data["published"]) %{ diff --git a/lib/pleroma/web/rel_me.ex b/lib/pleroma/web/rel_me.ex index 8e2b51508..28f75b18d 100644 --- a/lib/pleroma/web/rel_me.ex +++ b/lib/pleroma/web/rel_me.ex @@ -5,7 +5,8 @@ defmodule Pleroma.Web.RelMe do @options [ pool: :media, - max_body: 2_000_000 + max_body: 2_000_000, + recv_timeout: 2_000 ] if Pleroma.Config.get(:env) == :test do @@ -23,18 +24,8 @@ def parse(url) when is_binary(url) do def parse(_), do: {:error, "No URL provided"} defp parse_url(url) do - opts = - if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do - Keyword.merge(@options, - recv_timeout: 2_000, - with_body: true - ) - else - @options - end - with {:ok, %Tesla.Env{body: html, status: status}} when status in 200..299 <- - Pleroma.HTTP.get(url, [], adapter: opts), + Pleroma.HTTP.get(url, [], @options), {:ok, html_tree} <- Floki.parse_document(html), data <- Floki.attribute(html_tree, "link[rel~=me]", "href") ++ diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex index b7852c6e3..d7a19df4a 100644 --- a/lib/pleroma/web/rich_media/helpers.ex +++ b/lib/pleroma/web/rich_media/helpers.ex @@ -9,14 +9,15 @@ defmodule Pleroma.Web.RichMedia.Helpers do alias Pleroma.Object alias Pleroma.Web.RichMedia.Parser - @rich_media_options [ + @options [ pool: :media, - max_body: 2_000_000 + max_body: 2_000_000, + recv_timeout: 2_000 ] @spec validate_page_url(URI.t() | binary()) :: :ok | :error defp validate_page_url(page_url) when is_binary(page_url) do - validate_tld = Pleroma.Config.get([Pleroma.Formatter, :validate_tld]) + validate_tld = Config.get([Pleroma.Formatter, :validate_tld]) page_url |> Linkify.Parser.url?(validate_tld: validate_tld) @@ -86,18 +87,8 @@ def perform(:fetch, %Activity{} = activity) do def rich_media_get(url) do headers = [{"user-agent", Pleroma.Application.user_agent() <> "; Bot"}] - options = - if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do - Keyword.merge(@rich_media_options, - recv_timeout: 2_000, - with_body: true - ) - else - @rich_media_options - end - head_check = - case Pleroma.HTTP.head(url, headers, adapter: options) do + case Pleroma.HTTP.head(url, headers, @options) do # If the HEAD request didn't reach the server for whatever reason, # we assume the GET that comes right after won't either {:error, _} = e -> @@ -112,7 +103,7 @@ def rich_media_get(url) do :ok end - with :ok <- head_check, do: Pleroma.HTTP.get(url, headers, adapter: options) + with :ok <- head_check, do: Pleroma.HTTP.get(url, headers, @options) end defp check_content_type(headers) do @@ -128,7 +119,7 @@ defp check_content_type(headers) do end end - @max_body @rich_media_options[:max_body] + @max_body @options[:max_body] defp check_content_length(headers) do case List.keyfind(headers, "content-length", 0) do {_, maybe_content_length} -> diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex index 569249f51..c70d2fdba 100644 --- a/lib/pleroma/web/rich_media/parser.ex +++ b/lib/pleroma/web/rich_media/parser.ex @@ -20,31 +20,61 @@ def parse(url) do with {:ok, data} <- get_cached_or_parse(url), {:ok, _} <- set_ttl_based_on_image(data, url) do {:ok, data} - else - error -> - Logger.error(fn -> "Rich media error: #{inspect(error)}" end) end end defp get_cached_or_parse(url) do - case Cachex.fetch!(:rich_media_cache, url, fn _ -> {:commit, parse_url(url)} end) do - {:ok, _data} = res -> - res + case Cachex.fetch(:rich_media_cache, url, fn -> + case parse_url(url) do + {:ok, _} = res -> + {:commit, res} - {:error, :body_too_large} = e -> - e + {:error, reason} = e -> + # Unfortunately we have to log errors here, instead of doing that + # along with ttl setting at the bottom. Otherwise we can get log spam + # if more than one process was waiting for the rich media card + # while it was generated. Ideally we would set ttl here as well, + # so we don't override it number_of_waiters_on_generation + # times, but one, obviously, can't set ttl for not-yet-created entry + # and Cachex doesn't support returning ttl from the fetch callback. + log_error(url, reason) + {:commit, e} + end + end) do + {action, res} when action in [:commit, :ok] -> + case res do + {:ok, _data} = res -> + res - {:error, {:content_type, _}} = e -> - e + {:error, reason} = e -> + if action == :commit, do: set_error_ttl(url, reason) + e + end - # The TTL is not set for the errors above, since they are unlikely to change - # with time - {:error, _} = e -> - ttl = Pleroma.Config.get([:rich_media, :failure_backoff], 60_000) - Cachex.expire(:rich_media_cache, url, ttl) - e + {:error, e} -> + {:error, {:cachex_error, e}} end end + + defp set_error_ttl(_url, :body_too_large), do: :ok + defp set_error_ttl(_url, {:content_type, _}), do: :ok + + # The TTL is not set for the errors above, since they are unlikely to change + # with time + + defp set_error_ttl(url, _reason) do + ttl = Pleroma.Config.get([:rich_media, :failure_backoff], 60_000) + Cachex.expire(:rich_media_cache, url, ttl) + :ok + end + + defp log_error(url, {:invalid_metadata, data}) do + Logger.debug(fn -> "Incomplete or invalid metadata for #{url}: #{inspect(data)}" end) + end + + defp log_error(url, reason) do + Logger.warn(fn -> "Rich media error for #{url}: #{inspect(reason)}" end) + end end @doc """ @@ -98,7 +128,7 @@ defp get_ttl_from_image(data, url) do end) end - defp parse_url(url) do + def parse_url(url) do with {:ok, %Tesla.Env{body: html}} <- Pleroma.Web.RichMedia.Helpers.rich_media_get(url), {:ok, html} <- Floki.parse_document(html) do html @@ -124,7 +154,7 @@ defp check_parsed_data(%{"title" => title} = data) end defp check_parsed_data(data) do - {:error, "Found metadata was invalid or incomplete: #{inspect(data)}"} + {:error, {:invalid_metadata, data}} end defp clean_parsed_data(data) do diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index c6433cc53..f924e1e91 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -178,9 +178,14 @@ defmodule Pleroma.Web.Router do get("/users", AdminAPIController, :list_users) get("/users/:nickname", AdminAPIController, :user_show) get("/users/:nickname/statuses", AdminAPIController, :list_user_statuses) + get("/users/:nickname/chats", AdminAPIController, :list_user_chats) get("/instances/:instance/statuses", AdminAPIController, :list_instance_statuses) + get("/instance_document/:name", InstanceDocumentController, :show) + patch("/instance_document/:name", InstanceDocumentController, :update) + delete("/instance_document/:name", InstanceDocumentController, :delete) + patch("/users/confirm_email", AdminAPIController, :confirm_email) patch("/users/resend_confirmation_email", AdminAPIController, :resend_confirmation_email) @@ -214,6 +219,10 @@ defmodule Pleroma.Web.Router do get("/media_proxy_caches", MediaProxyCacheController, :index) post("/media_proxy_caches/delete", MediaProxyCacheController, :delete) post("/media_proxy_caches/purge", MediaProxyCacheController, :purge) + + get("/chats/:id", ChatController, :show) + get("/chats/:id/messages", ChatController, :messages) + delete("/chats/:id/messages/:message_id", ChatController, :delete_message) end scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do @@ -260,14 +269,15 @@ defmodule Pleroma.Web.Router do post("/delete_account", UtilController, :delete_account) put("/notification_settings", UtilController, :update_notificaton_settings) post("/disable_account", UtilController, :disable_account) - - post("/blocks_import", UtilController, :blocks_import) - post("/follow_import", UtilController, :follow_import) end scope "/api/pleroma", Pleroma.Web.PleromaAPI do pipe_through(:authenticated_api) + post("/mutes_import", UserImportController, :mutes) + post("/blocks_import", UserImportController, :blocks) + post("/follow_import", UserImportController, :follow) + get("/accounts/mfa", TwoFactorAuthenticationController, :settings) get("/accounts/mfa/backup_codes", TwoFactorAuthenticationController, :backup_codes) get("/accounts/mfa/setup/:method", TwoFactorAuthenticationController, :setup) @@ -670,6 +680,8 @@ defmodule Pleroma.Web.Router do end scope "/proxy/", Pleroma.Web.MediaProxy do + get("/preview/:sig/:url", MediaProxyController, :preview) + get("/preview/:sig/:url/:filename", MediaProxyController, :preview) get("/:sig/:url", MediaProxyController, :remote) get("/:sig/:url/:filename", MediaProxyController, :remote) end diff --git a/lib/pleroma/web/streamer/streamer.ex b/lib/pleroma/web/streamer/streamer.ex index d1d70e556..5475f18a6 100644 --- a/lib/pleroma/web/streamer/streamer.ex +++ b/lib/pleroma/web/streamer/streamer.ex @@ -11,10 +11,12 @@ defmodule Pleroma.Web.Streamer do alias Pleroma.Conversation.Participation alias Pleroma.Notification alias Pleroma.Object + alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.CommonAPI + alias Pleroma.Web.OAuth.Token alias Pleroma.Web.StreamerView @mix_env Mix.env() @@ -26,53 +28,87 @@ def registry, do: @registry @user_streams ["user", "user:notification", "direct", "user:pleroma_chat"] @doc "Expands and authorizes a stream, and registers the process for streaming." - @spec get_topic_and_add_socket(stream :: String.t(), User.t() | nil, Map.t() | nil) :: + @spec get_topic_and_add_socket( + stream :: String.t(), + User.t() | nil, + Token.t() | nil, + Map.t() | nil + ) :: {:ok, topic :: String.t()} | {:error, :bad_topic} | {:error, :unauthorized} - def get_topic_and_add_socket(stream, user, params \\ %{}) do - case get_topic(stream, user, params) do + def get_topic_and_add_socket(stream, user, oauth_token, params \\ %{}) do + case get_topic(stream, user, oauth_token, params) do {:ok, topic} -> add_socket(topic, user) error -> error end end @doc "Expand and authorizes a stream" - @spec get_topic(stream :: String.t(), User.t() | nil, Map.t()) :: + @spec get_topic(stream :: String.t(), User.t() | nil, Token.t() | nil, Map.t()) :: {:ok, topic :: String.t()} | {:error, :bad_topic} - def get_topic(stream, user, params \\ %{}) + def get_topic(stream, user, oauth_token, params \\ %{}) # Allow all public steams. - def get_topic(stream, _, _) when stream in @public_streams do + def get_topic(stream, _user, _oauth_token, _params) when stream in @public_streams do {:ok, stream} end # Allow all hashtags streams. - def get_topic("hashtag", _, %{"tag" => tag}) do + def get_topic("hashtag", _user, _oauth_token, %{"tag" => tag} = _params) do {:ok, "hashtag:" <> tag} end # Expand user streams. - def get_topic(stream, %User{} = user, _) when stream in @user_streams do - {:ok, stream <> ":" <> to_string(user.id)} + def get_topic( + stream, + %User{id: user_id} = user, + %Token{user_id: token_user_id} = oauth_token, + _params + ) + when stream in @user_streams and user_id == token_user_id do + # Note: "read" works for all user streams (not mentioning it since it's an ancestor scope) + required_scopes = + if stream == "user:notification" do + ["read:notifications"] + else + ["read:statuses"] + end + + if OAuthScopesPlug.filter_descendants(required_scopes, oauth_token.scopes) == [] do + {:error, :unauthorized} + else + {:ok, stream <> ":" <> to_string(user.id)} + end end - def get_topic(stream, _, _) when stream in @user_streams do + def get_topic(stream, _user, _oauth_token, _params) when stream in @user_streams do {:error, :unauthorized} end # List streams. - def get_topic("list", %User{} = user, %{"list" => id}) do - if Pleroma.List.get(id, user) do - {:ok, "list:" <> to_string(id)} - else - {:error, :bad_topic} + def get_topic( + "list", + %User{id: user_id} = user, + %Token{user_id: token_user_id} = oauth_token, + %{"list" => id} + ) + when user_id == token_user_id do + cond do + OAuthScopesPlug.filter_descendants(["read", "read:lists"], oauth_token.scopes) == [] -> + {:error, :unauthorized} + + Pleroma.List.get(id, user) -> + {:ok, "list:" <> to_string(id)} + + true -> + {:error, :bad_topic} end end - def get_topic("list", _, _) do + def get_topic("list", _user, _oauth_token, _params) do {:error, :unauthorized} end - def get_topic(_, _, _) do + def get_topic(_stream, _user, _oauth_token, _params) do {:error, :bad_topic} end diff --git a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex index 521dc9322..072d889e2 100644 --- a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex @@ -135,7 +135,7 @@ defp handle_follow_error(conn, {:verify_mfa_code, followee, token, _} = _) do end defp handle_follow_error(conn, {:mfa_required, followee, user, _} = _) do - {:ok, %{token: token}} = MFA.Token.create_token(user) + {:ok, %{token: token}} = MFA.Token.create(user) render(conn, "follow_mfa.html", %{followee: followee, mfa_token: token, error: false}) end diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index f02c4075c..70b0fbd54 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -18,14 +18,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do plug(Pleroma.Web.FederatingPlug when action == :remote_subscribe) - plug( - OAuthScopesPlug, - %{scopes: ["follow", "write:follows"]} - when action == :follow_import - ) - - plug(OAuthScopesPlug, %{scopes: ["follow", "write:blocks"]} when action == :blocks_import) - plug( OAuthScopesPlug, %{scopes: ["write:accounts"]} @@ -104,33 +96,6 @@ def update_notificaton_settings(%{assigns: %{user: user}} = conn, params) do end end - def follow_import(conn, %{"list" => %Plug.Upload{} = listfile}) do - follow_import(conn, %{"list" => File.read!(listfile.path)}) - end - - def follow_import(%{assigns: %{user: follower}} = conn, %{"list" => list}) do - followed_identifiers = - list - |> String.split("\n") - |> Enum.map(&(&1 |> String.split(",") |> List.first())) - |> List.delete("Account address") - |> Enum.map(&(&1 |> String.trim() |> String.trim_leading("@"))) - |> Enum.reject(&(&1 == "")) - - User.follow_import(follower, followed_identifiers) - json(conn, "job started") - end - - def blocks_import(conn, %{"list" => %Plug.Upload{} = listfile}) do - blocks_import(conn, %{"list" => File.read!(listfile.path)}) - end - - def blocks_import(%{assigns: %{user: blocker}} = conn, %{"list" => list}) do - blocked_identifiers = list |> String.split() |> Enum.map(&String.trim_leading(&1, "@")) - User.blocks_import(blocker, blocked_identifiers) - json(conn, "job started") - end - def change_password(%{assigns: %{user: user}} = conn, params) do case CommonAPI.Utils.confirm_current_password(user, params["password"]) do {:ok, user} -> diff --git a/lib/pleroma/workers/background_worker.ex b/lib/pleroma/workers/background_worker.ex index cec5a7462..55b5a13d9 100644 --- a/lib/pleroma/workers/background_worker.ex +++ b/lib/pleroma/workers/background_worker.ex @@ -26,26 +26,10 @@ def perform(%Job{args: %{"op" => "force_password_reset", "user_id" => user_id}}) User.perform(:force_password_reset, user) end - def perform(%Job{ - args: %{ - "op" => "blocks_import", - "blocker_id" => blocker_id, - "blocked_identifiers" => blocked_identifiers - } - }) do - blocker = User.get_cached_by_id(blocker_id) - {:ok, User.perform(:blocks_import, blocker, blocked_identifiers)} - end - - def perform(%Job{ - args: %{ - "op" => "follow_import", - "follower_id" => follower_id, - "followed_identifiers" => followed_identifiers - } - }) do - follower = User.get_cached_by_id(follower_id) - {:ok, User.perform(:follow_import, follower, followed_identifiers)} + def perform(%Job{args: %{"op" => op, "user_id" => user_id, "identifiers" => identifiers}}) + when op in ["blocks_import", "follow_import", "mutes_import"] do + user = User.get_cached_by_id(user_id) + {:ok, User.Import.perform(String.to_atom(op), user, identifiers)} end def perform(%Job{args: %{"op" => "media_proxy_preload", "message" => message}}) do diff --git a/lib/pleroma/workers/cron/clear_oauth_token_worker.ex b/lib/pleroma/workers/cron/clear_oauth_token_worker.ex deleted file mode 100644 index 276f47efc..000000000 --- a/lib/pleroma/workers/cron/clear_oauth_token_worker.ex +++ /dev/null @@ -1,23 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Workers.Cron.ClearOauthTokenWorker do - @moduledoc """ - The worker to cleanup expired oAuth tokens. - """ - - use Oban.Worker, queue: "background" - - alias Pleroma.Config - alias Pleroma.Web.OAuth.Token - - @impl Oban.Worker - def perform(_job) do - if Config.get([:oauth2, :clean_expired_tokens], false) do - Token.delete_expired_tokens() - end - - :ok - end -end diff --git a/lib/pleroma/workers/cron/purge_expired_activities_worker.ex b/lib/pleroma/workers/cron/purge_expired_activities_worker.ex deleted file mode 100644 index 6549207fc..000000000 --- a/lib/pleroma/workers/cron/purge_expired_activities_worker.ex +++ /dev/null @@ -1,48 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker do - @moduledoc """ - The worker to purge expired activities. - """ - - use Oban.Worker, queue: "activity_expiration" - - alias Pleroma.Activity - alias Pleroma.ActivityExpiration - alias Pleroma.Config - alias Pleroma.User - alias Pleroma.Web.CommonAPI - - require Logger - - @interval :timer.minutes(1) - - @impl Oban.Worker - def perform(_job) do - if Config.get([ActivityExpiration, :enabled]) do - Enum.each(ActivityExpiration.due_expirations(@interval), &delete_activity/1) - end - after - :ok - end - - def delete_activity(%ActivityExpiration{activity_id: activity_id}) do - with {:activity, %Activity{} = activity} <- - {:activity, Activity.get_by_id_with_object(activity_id)}, - {:user, %User{} = user} <- {:user, User.get_by_ap_id(activity.object.data["actor"])} do - CommonAPI.delete(activity.id, user) - else - {:activity, _} -> - Logger.error( - "#{__MODULE__} Couldn't delete expired activity: not found activity ##{activity_id}" - ) - - {:user, _} -> - Logger.error( - "#{__MODULE__} Couldn't delete expired activity: not found actor of ##{activity_id}" - ) - end - end -end diff --git a/lib/pleroma/workers/cron/stats_worker.ex b/lib/pleroma/workers/cron/stats_worker.ex deleted file mode 100644 index 6a79540bc..000000000 --- a/lib/pleroma/workers/cron/stats_worker.ex +++ /dev/null @@ -1,17 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Workers.Cron.StatsWorker do - @moduledoc """ - The worker to update peers statistics. - """ - - use Oban.Worker, queue: "background" - - @impl Oban.Worker - def perform(_job) do - Pleroma.Stats.do_collect() - :ok - end -end diff --git a/lib/pleroma/workers/purge_expired_activity.ex b/lib/pleroma/workers/purge_expired_activity.ex new file mode 100644 index 000000000..c168890a2 --- /dev/null +++ b/lib/pleroma/workers/purge_expired_activity.ex @@ -0,0 +1,72 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.PurgeExpiredActivity do + @moduledoc """ + Worker which purges expired activity. + """ + + use Oban.Worker, queue: :activity_expiration, max_attempts: 1 + + import Ecto.Query + + alias Pleroma.Activity + + @spec enqueue(map()) :: + {:ok, Oban.Job.t()} + | {:error, :expired_activities_disabled} + | {:error, :expiration_too_close} + def enqueue(args) do + with true <- enabled?() do + {scheduled_at, args} = Map.pop(args, :expires_at) + + args + |> new(scheduled_at: scheduled_at) + |> Oban.insert() + end + end + + @impl true + def perform(%Oban.Job{args: %{"activity_id" => id}}) do + with %Activity{} = activity <- find_activity(id), + %Pleroma.User{} = user <- find_user(activity.object.data["actor"]) do + Pleroma.Web.CommonAPI.delete(activity.id, user) + end + end + + defp enabled? do + with false <- Pleroma.Config.get([__MODULE__, :enabled], false) do + {:error, :expired_activities_disabled} + end + end + + defp find_activity(id) do + with nil <- Activity.get_by_id_with_object(id) do + {:error, :activity_not_found} + end + end + + defp find_user(ap_id) do + with nil <- Pleroma.User.get_by_ap_id(ap_id) do + {:error, :user_not_found} + end + end + + def get_expiration(id) do + from(j in Oban.Job, + where: j.state == "scheduled", + where: j.queue == "activity_expiration", + where: fragment("?->>'activity_id' = ?", j.args, ^id) + ) + |> Pleroma.Repo.one() + end + + @spec expires_late_enough?(DateTime.t()) :: boolean() + def expires_late_enough?(scheduled_at) do + now = DateTime.utc_now() + diff = DateTime.diff(scheduled_at, now, :millisecond) + min_lifetime = Pleroma.Config.get([__MODULE__, :min_lifetime], 600) + diff > :timer.seconds(min_lifetime) + end +end diff --git a/lib/pleroma/workers/purge_expired_token.ex b/lib/pleroma/workers/purge_expired_token.ex new file mode 100644 index 000000000..a81e0cd28 --- /dev/null +++ b/lib/pleroma/workers/purge_expired_token.ex @@ -0,0 +1,29 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.PurgeExpiredToken do + @moduledoc """ + Worker which purges expired OAuth tokens + """ + + use Oban.Worker, queue: :token_expiration, max_attempts: 1 + + @spec enqueue(%{token_id: integer(), valid_until: DateTime.t(), mod: module()}) :: + {:ok, Oban.Job.t()} | {:error, Ecto.Changeset.t()} + def enqueue(args) do + {scheduled_at, args} = Map.pop(args, :valid_until) + + args + |> __MODULE__.new(scheduled_at: scheduled_at) + |> Oban.insert() + end + + @impl true + def perform(%Oban.Job{args: %{"token_id" => id, "mod" => module}}) do + module + |> String.to_existing_atom() + |> Pleroma.Repo.get(id) + |> Pleroma.Repo.delete() + end +end diff --git a/mix.exs b/mix.exs index c88bf392d..18f748672 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do def project do [ app: :pleroma, - version: version("2.1.2"), + version: version("2.1.50"), elixir: "~> 1.9", elixirc_paths: elixirc_paths(Mix.env()), compilers: [:phoenix, :gettext] ++ Mix.compilers(), @@ -134,7 +134,9 @@ defp deps do {:cachex, "~> 3.2"}, {:poison, "~> 3.0", override: true}, {:tesla, - github: "teamon/tesla", ref: "af3707078b10793f6a534938e56b963aff82fe3c", override: true}, + git: "https://github.com/teamon/tesla/", + ref: "9f7261ca49f9f901ceb73b60219ad6f8a9f6aa30", + override: true}, {:castore, "~> 0.1"}, {:cowlib, "~> 2.9", override: true}, {:gun, @@ -178,7 +180,7 @@ defp deps do {:flake_id, "~> 0.1.0"}, {:concurrent_limiter, git: "https://git.pleroma.social/pleroma/elixir-libraries/concurrent_limiter.git", - ref: "55e92f84b4ed531bd487952a71040a9c69dc2807"}, + ref: "d81be41024569330f296fc472e24198d7499ba78"}, {:remote_ip, git: "https://git.pleroma.social/pleroma/remote_ip.git", ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"}, diff --git a/mix.lock b/mix.lock index b97dd6342..adb3f024a 100644 --- a/mix.lock +++ b/mix.lock @@ -14,7 +14,7 @@ "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "805abd97539caf89ec6d4732c91e62ba9da0cda51ac462380bbd28ee697a8c42"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, "comeonin": {:hex, :comeonin, "5.3.1", "7fe612b739c78c9c1a75186ef2d322ce4d25032d119823269d0aa1e2f1e20025", [:mix], [], "hexpm", "d6222483060c17f0977fad1b7401ef0c5863c985a64352755f366aee3799c245"}, - "concurrent_limiter": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/concurrent_limiter.git", "55e92f84b4ed531bd487952a71040a9c69dc2807", [ref: "55e92f84b4ed531bd487952a71040a9c69dc2807"]}, + "concurrent_limiter": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/concurrent_limiter.git", "d81be41024569330f296fc472e24198d7499ba78", [ref: "d81be41024569330f296fc472e24198d7499ba78"]}, "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"}, "cors_plug": {:hex, :cors_plug, "2.0.2", "2b46083af45e4bc79632bd951550509395935d3e7973275b2b743bd63cc942ce", [:mix], [{:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "f0d0e13f71c51fd4ef8b2c7e051388e4dfb267522a83a22392c856de7e46465f"}, "cowboy": {:hex, :cowboy, "2.8.0", "f3dc62e35797ecd9ac1b50db74611193c29815401e53bac9a5c0577bd7bc667d", [:rebar3], [{:cowlib, "~> 2.9.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "4643e4fba74ac96d4d152c75803de6fad0b3fa5df354c71afdd6cbeeb15fac8a"}, @@ -31,6 +31,7 @@ "ecto": {:hex, :ecto, "3.4.5", "2bcd262f57b2c888b0bd7f7a28c8a48aa11dc1a2c6a858e45dd8f8426d504265", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8c6d1d4d524559e9b7a062f0498e2c206122552d63eacff0a6567ffe7a8e8691"}, "ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"}, "ecto_sql": {:hex, :ecto_sql, "3.4.5", "30161f81b167d561a9a2df4329c10ae05ff36eca7ccc84628f2c8b9fa1e43323", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "31990c6a3579b36a3c0841d34a94c275e727de8b84f58509da5f1b2032c98ac2"}, + "eimp": {:hex, :eimp, "1.0.14", "fc297f0c7e2700457a95a60c7010a5f1dcb768a083b6d53f49cd94ab95a28f22", [:rebar3], [{:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "501133f3112079b92d9e22da8b88bf4f0e13d4d67ae9c15c42c30bd25ceb83b6"}, "elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm", "d522695b93b7f0b4c0fcb2dfe73a6b905b1c301226a5a55cb42e5b14d509e050"}, "esshd": {:hex, :esshd, "0.1.1", "d4dd4c46698093a40a56afecce8a46e246eb35463c457c246dacba2e056f31b5", [:mix], [], "hexpm", "d73e341e3009d390aa36387dc8862860bf9f874c94d9fd92ade2926376f49981"}, "eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm", "b14f1dc204321429479c569cfbe8fb287541184ed040956c8862cb7a677b8406"}, @@ -80,6 +81,7 @@ "nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]}, "oban": {:hex, :oban, "2.0.0", "e6ce70d94dd46815ec0882a1ffb7356df9a9d5b8a40a64ce5c2536617a447379", [:mix], [{:ecto_sql, ">= 3.4.3", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cf574813bd048b98a698aa587c21367d2e06842d4e1b1993dcd6a696e9e633bd"}, "open_api_spex": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git", "f296ac0924ba3cf79c7a588c4c252889df4c2edd", [ref: "f296ac0924ba3cf79c7a588c4c252889df4c2edd"]}, + "p1_utils": {:hex, :p1_utils, "1.0.18", "3fe224de5b2e190d730a3c5da9d6e8540c96484cf4b4692921d1e28f0c32b01c", [:rebar3], [], "hexpm", "1fc8773a71a15553b179c986b22fbeead19b28fe486c332d4929700ffeb71f88"}, "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"}, "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "1.2.1", "9cbe354b58121075bd20eb83076900a3832324b7dd171a6895fab57b6bb2752c", [:mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}], "hexpm", "d3b40a4a4630f0b442f19eca891fcfeeee4c40871936fed2f68e1c4faa30481f"}, "phoenix": {:hex, :phoenix, "1.4.17", "1b1bd4cff7cfc87c94deaa7d60dd8c22e04368ab95499483c50640ef3bd838d8", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a8e5d7a3d76d452bb5fb86e8b7bd115f737e4f8efe202a463d4aeb4a5809611"}, @@ -110,7 +112,7 @@ "swoosh": {:hex, :swoosh, "1.0.0", "c547cfc83f30e12d5d1fdcb623d7de2c2e29a5becfc68bf8f42ba4d23d2c2756", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "b3b08e463f876cb6167f7168e9ad99a069a724e124bcee61847e0e1ed13f4a0d"}, "syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"}, "telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"}, - "tesla": {:git, "https://github.com/teamon/tesla.git", "af3707078b10793f6a534938e56b963aff82fe3c", [ref: "af3707078b10793f6a534938e56b963aff82fe3c"]}, + "tesla": {:git, "https://github.com/teamon/tesla/", "9f7261ca49f9f901ceb73b60219ad6f8a9f6aa30", [ref: "9f7261ca49f9f901ceb73b60219ad6f8a9f6aa30"]}, "timex": {:hex, :timex, "3.6.2", "845cdeb6119e2fef10751c0b247b6c59d86d78554c83f78db612e3290f819bc2", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "26030b46199d02a590be61c2394b37ea25a3664c02fafbeca0b24c972025d47a"}, "trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"}, "tzdata": {:hex, :tzdata, "1.0.3", "73470ad29dde46e350c60a66e6b360d3b99d2d18b74c4c349dbebbc27a09a3eb", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a6e1ee7003c4d04ecbd21dd3ec690d4c6662db5d3bbdd7262d53cdf5e7c746c1"}, @@ -118,5 +120,5 @@ "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm", "1d1848c40487cdb0b30e8ed975e34e025860c02e419cb615d255849f3427439d"}, "unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"}, "web_push_encryption": {:hex, :web_push_encryption, "0.3.0", "598b5135e696fd1404dc8d0d7c0fa2c027244a4e5d5e5a98ba267f14fdeaabc8", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "f10bdd1afe527ede694749fb77a2f22f146a51b054c7fa541c9fd920fba7c875"}, - "websocket_client": {:git, "https://github.com/jeremyong/websocket_client.git", "9a6f65d05ebf2725d62fb19262b21f1805a59fbf", []}, + "websocket_client": {:git, "https://github.com/jeremyong/websocket_client.git", "9a6f65d05ebf2725d62fb19262b21f1805a59fbf", []} } diff --git a/priv/gettext/es/LC_MESSAGES/errors.po b/priv/gettext/es/LC_MESSAGES/errors.po new file mode 100644 index 000000000..0a6fceaad --- /dev/null +++ b/priv/gettext/es/LC_MESSAGES/errors.po @@ -0,0 +1,586 @@ +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-09-09 09:49+0000\n" +"PO-Revision-Date: 2020-09-11 21:26+0000\n" +"Last-Translator: tarteka \n" +"Language-Team: Spanish \n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.0.4\n" + +## This file is a PO Template file. +## +## `msgid`s here are often extracted from source code. +## Add new translations manually only if they're dynamic +## translations that can't be statically extracted. +## +## Run `mix gettext.extract` to bring this file up to +## date. Leave `msgstr`s empty as changing them here as no +## effect: edit them in PO (`.po`) files instead. +## From Ecto.Changeset.cast/4 +msgid "can't be blank" +msgstr "no puede estar en blanco" + +## From Ecto.Changeset.unique_constraint/3 +msgid "has already been taken" +msgstr "ya está en uso" + +## From Ecto.Changeset.put_change/3 +msgid "is invalid" +msgstr "es inválido" + +## From Ecto.Changeset.validate_format/3 +msgid "has invalid format" +msgstr "el formato no es válido" + +## From Ecto.Changeset.validate_subset/3 +msgid "has an invalid entry" +msgstr "tiene una entrada inválida" + +## From Ecto.Changeset.validate_exclusion/3 +msgid "is reserved" +msgstr "está reservado" + +## From Ecto.Changeset.validate_confirmation/3 +msgid "does not match confirmation" +msgstr "la confirmación no coincide" + +## From Ecto.Changeset.no_assoc_constraint/3 +msgid "is still associated with this entry" +msgstr "todavía está asociado con esta entrada" + +msgid "are still associated with this entry" +msgstr "todavía están asociados con esta entrada" + +## From Ecto.Changeset.validate_length/3 +msgid "should be %{count} character(s)" +msgid_plural "should be %{count} character(s)" +msgstr[0] "debe tener %{count} carácter" +msgstr[1] "debe tener %{count} caracteres" + +msgid "should have %{count} item(s)" +msgid_plural "should have %{count} item(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should be at least %{count} character(s)" +msgid_plural "should be at least %{count} character(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should have at least %{count} item(s)" +msgid_plural "should have at least %{count} item(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should be at most %{count} character(s)" +msgid_plural "should be at most %{count} character(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should have at most %{count} item(s)" +msgid_plural "should have at most %{count} item(s)" +msgstr[0] "" +msgstr[1] "" + +## From Ecto.Changeset.validate_number/3 +msgid "must be less than %{number}" +msgstr "" + +msgid "must be greater than %{number}" +msgstr "debe ser mayor que %{number}" + +msgid "must be less than or equal to %{number}" +msgstr "debe ser menor o igual que %{number}" + +msgid "must be greater than or equal to %{number}" +msgstr "deber ser mayor o igual que %{number}" + +msgid "must be equal to %{number}" +msgstr "deber ser igual a %{number}" + +#: lib/pleroma/web/common_api/common_api.ex:505 +#, elixir-format +msgid "Account not found" +msgstr "Cuenta no encontrada" + +#: lib/pleroma/web/common_api/common_api.ex:339 +#, elixir-format +msgid "Already voted" +msgstr "Ya has votado" + +#: lib/pleroma/web/oauth/oauth_controller.ex:359 +#, elixir-format +msgid "Bad request" +msgstr "Solicitud incorrecta" + +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:426 +#, elixir-format +msgid "Can't delete object" +msgstr "No se puede eliminar el objeto" + +#: lib/pleroma/web/controller_helper.ex:105 +#: lib/pleroma/web/controller_helper.ex:111 +#, elixir-format +msgid "Can't display this activity" +msgstr "No se puede mostrar esta actividad" + +#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:285 +#, elixir-format +msgid "Can't find user" +msgstr "No se puede encontrar al usuario" + +#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:61 +#, elixir-format +msgid "Can't get favorites" +msgstr "No se puede obtener los favoritos" + +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:438 +#, elixir-format +msgid "Can't like object" +msgstr "" + +#: lib/pleroma/web/common_api/utils.ex:563 +#, elixir-format +msgid "Cannot post an empty status without attachments" +msgstr "No se puede publicar un estado vacío y sin archivos adjuntos" + +#: lib/pleroma/web/common_api/utils.ex:511 +#, elixir-format +msgid "Comment must be up to %{max_size} characters" +msgstr "" + +#: lib/pleroma/config/config_db.ex:191 +#, elixir-format +msgid "Config with params %{params} not found" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:181 +#: lib/pleroma/web/common_api/common_api.ex:185 +#, elixir-format +msgid "Could not delete" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:231 +#, elixir-format +msgid "Could not favorite" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:453 +#, elixir-format +msgid "Could not pin" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:278 +#, elixir-format +msgid "Could not unfavorite" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:463 +#, elixir-format +msgid "Could not unpin" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:216 +#, elixir-format +msgid "Could not unrepeat" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:512 +#: lib/pleroma/web/common_api/common_api.ex:521 +#, elixir-format +msgid "Could not update state" +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:207 +#, elixir-format +msgid "Error." +msgstr "" + +#: lib/pleroma/web/twitter_api/twitter_api.ex:106 +#, elixir-format +msgid "Invalid CAPTCHA" +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:116 +#: lib/pleroma/web/oauth/oauth_controller.ex:568 +#, elixir-format +msgid "Invalid credentials" +msgstr "" + +#: lib/pleroma/plugs/ensure_authenticated_plug.ex:38 +#, elixir-format +msgid "Invalid credentials." +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:355 +#, elixir-format +msgid "Invalid indices" +msgstr "" + +#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:29 +#, elixir-format +msgid "Invalid parameters" +msgstr "" + +#: lib/pleroma/web/common_api/utils.ex:414 +#, elixir-format +msgid "Invalid password." +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:220 +#, elixir-format +msgid "Invalid request" +msgstr "" + +#: lib/pleroma/web/twitter_api/twitter_api.ex:109 +#, elixir-format +msgid "Kocaptcha service unavailable" +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:112 +#, elixir-format +msgid "Missing parameters" +msgstr "" + +#: lib/pleroma/web/common_api/utils.ex:547 +#, elixir-format +msgid "No such conversation" +msgstr "" + +#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:388 +#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:414 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:456 +#, elixir-format +msgid "No such permission_group" +msgstr "" + +#: lib/pleroma/plugs/uploaded_media.ex:84 +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:486 lib/pleroma/web/admin_api/controllers/fallback_controller.ex:11 +#: lib/pleroma/web/feed/user_controller.ex:71 lib/pleroma/web/ostatus/ostatus_controller.ex:143 +#, elixir-format +msgid "Not found" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:331 +#, elixir-format +msgid "Poll's author can't vote" +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20 +#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:37 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:49 +#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:50 lib/pleroma/web/mastodon_api/controllers/status_controller.ex:306 +#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71 +#, elixir-format +msgid "Record not found" +msgstr "" + +#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:35 +#: lib/pleroma/web/feed/user_controller.ex:77 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:36 +#: lib/pleroma/web/ostatus/ostatus_controller.ex:149 +#, elixir-format +msgid "Something went wrong" +msgstr "" + +#: lib/pleroma/web/common_api/activity_draft.ex:107 +#, elixir-format +msgid "The message visibility must be direct" +msgstr "" + +#: lib/pleroma/web/common_api/utils.ex:573 +#, elixir-format +msgid "The status is over the character limit" +msgstr "" + +#: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:31 +#, elixir-format +msgid "This resource requires authentication." +msgstr "" + +#: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206 +#, elixir-format +msgid "Throttled" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:356 +#, elixir-format +msgid "Too many choices" +msgstr "" + +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:443 +#, elixir-format +msgid "Unhandled activity type" +msgstr "" + +#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:485 +#, elixir-format +msgid "You can't revoke your own admin status." +msgstr "" + +#: lib/pleroma/web/oauth/oauth_controller.ex:221 +#: lib/pleroma/web/oauth/oauth_controller.ex:308 +#, elixir-format +msgid "Your account is currently disabled" +msgstr "" + +#: lib/pleroma/web/oauth/oauth_controller.ex:183 +#: lib/pleroma/web/oauth/oauth_controller.ex:331 +#, elixir-format +msgid "Your login is missing a confirmed e-mail address" +msgstr "" + +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:390 +#, elixir-format +msgid "can't read inbox of %{nickname} as %{as_nickname}" +msgstr "" + +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:473 +#, elixir-format +msgid "can't update outbox of %{nickname} as %{as_nickname}" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:471 +#, elixir-format +msgid "conversation is already muted" +msgstr "" + +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:314 +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:492 +#, elixir-format +msgid "error" +msgstr "" + +#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:32 +#, elixir-format +msgid "mascots can only be images" +msgstr "" + +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:62 +#, elixir-format +msgid "not found" +msgstr "" + +#: lib/pleroma/web/oauth/oauth_controller.ex:394 +#, elixir-format +msgid "Bad OAuth request." +msgstr "" + +#: lib/pleroma/web/twitter_api/twitter_api.ex:115 +#, elixir-format +msgid "CAPTCHA already used" +msgstr "" + +#: lib/pleroma/web/twitter_api/twitter_api.ex:112 +#, elixir-format +msgid "CAPTCHA expired" +msgstr "" + +#: lib/pleroma/plugs/uploaded_media.ex:57 +#, elixir-format +msgid "Failed" +msgstr "" + +#: lib/pleroma/web/oauth/oauth_controller.ex:410 +#, elixir-format +msgid "Failed to authenticate: %{message}." +msgstr "" + +#: lib/pleroma/web/oauth/oauth_controller.ex:441 +#, elixir-format +msgid "Failed to set up user account." +msgstr "" + +#: lib/pleroma/plugs/oauth_scopes_plug.ex:38 +#, elixir-format +msgid "Insufficient permissions: %{permissions}." +msgstr "" + +#: lib/pleroma/plugs/uploaded_media.ex:104 +#, elixir-format +msgid "Internal Error" +msgstr "" + +#: lib/pleroma/web/oauth/fallback_controller.ex:22 +#: lib/pleroma/web/oauth/fallback_controller.ex:29 +#, elixir-format +msgid "Invalid Username/Password" +msgstr "" + +#: lib/pleroma/web/twitter_api/twitter_api.ex:118 +#, elixir-format +msgid "Invalid answer data" +msgstr "" + +#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:33 +#, elixir-format +msgid "Nodeinfo schema version not handled" +msgstr "" + +#: lib/pleroma/web/oauth/oauth_controller.ex:172 +#, elixir-format +msgid "This action is outside the authorized scopes" +msgstr "" + +#: lib/pleroma/web/oauth/fallback_controller.ex:14 +#, elixir-format +msgid "Unknown error, please check the details and try again." +msgstr "" + +#: lib/pleroma/web/oauth/oauth_controller.ex:119 +#: lib/pleroma/web/oauth/oauth_controller.ex:158 +#, elixir-format +msgid "Unlisted redirect_uri." +msgstr "" + +#: lib/pleroma/web/oauth/oauth_controller.ex:390 +#, elixir-format +msgid "Unsupported OAuth provider: %{provider}." +msgstr "" + +#: lib/pleroma/uploaders/uploader.ex:72 +#, elixir-format +msgid "Uploader callback timeout" +msgstr "" + +#: lib/pleroma/web/uploader_controller.ex:23 +#, elixir-format +msgid "bad request" +msgstr "" + +#: lib/pleroma/web/twitter_api/twitter_api.ex:103 +#, elixir-format +msgid "CAPTCHA Error" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:290 +#, elixir-format +msgid "Could not add reaction emoji" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:301 +#, elixir-format +msgid "Could not remove reaction emoji" +msgstr "" + +#: lib/pleroma/web/twitter_api/twitter_api.ex:129 +#, elixir-format +msgid "Invalid CAPTCHA (Missing parameter: %{name})" +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:92 +#, elixir-format +msgid "List not found" +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:123 +#, elixir-format +msgid "Missing parameter: %{name}" +msgstr "" + +#: lib/pleroma/web/oauth/oauth_controller.ex:210 +#: lib/pleroma/web/oauth/oauth_controller.ex:321 +#, elixir-format +msgid "Password reset is required" +msgstr "" + +#: lib/pleroma/tests/auth_test_controller.ex:9 +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:6 +#: lib/pleroma/web/admin_api/controllers/config_controller.ex:6 lib/pleroma/web/admin_api/controllers/fallback_controller.ex:6 +#: lib/pleroma/web/admin_api/controllers/invite_controller.ex:6 lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex:6 +#: lib/pleroma/web/admin_api/controllers/oauth_app_controller.ex:6 lib/pleroma/web/admin_api/controllers/relay_controller.ex:6 +#: lib/pleroma/web/admin_api/controllers/report_controller.ex:6 lib/pleroma/web/admin_api/controllers/status_controller.ex:6 +#: lib/pleroma/web/controller_helper.ex:6 lib/pleroma/web/embed_controller.ex:6 +#: lib/pleroma/web/fallback_redirect_controller.ex:6 lib/pleroma/web/feed/tag_controller.ex:6 +#: lib/pleroma/web/feed/user_controller.ex:6 lib/pleroma/web/mailer/subscription_controller.ex:2 +#: lib/pleroma/web/masto_fe_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/account_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/app_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/auth_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/filter_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/instance_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/list_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/marker_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex:14 +#: lib/pleroma/web/mastodon_api/controllers/media_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/notification_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/report_controller.ex:8 +#: lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/search_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/status_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:7 +#: lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:6 +#: lib/pleroma/web/media_proxy/media_proxy_controller.ex:6 lib/pleroma/web/mongooseim/mongoose_im_controller.ex:6 +#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:6 lib/pleroma/web/oauth/fallback_controller.ex:6 +#: lib/pleroma/web/oauth/mfa_controller.ex:10 lib/pleroma/web/oauth/oauth_controller.ex:6 +#: lib/pleroma/web/ostatus/ostatus_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/account_controller.ex:6 +#: lib/pleroma/web/pleroma_api/controllers/chat_controller.ex:5 lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex:6 +#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:2 lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex:6 +#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/notification_controller.ex:6 +#: lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex:6 +#: lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex:7 lib/pleroma/web/static_fe/static_fe_controller.ex:6 +#: lib/pleroma/web/twitter_api/controllers/password_controller.ex:10 lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex:6 +#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:6 lib/pleroma/web/twitter_api/twitter_api_controller.ex:6 +#: lib/pleroma/web/uploader_controller.ex:6 lib/pleroma/web/web_finger/web_finger_controller.ex:6 +#, elixir-format +msgid "Security violation: OAuth scopes check was neither handled nor explicitly skipped." +msgstr "" + +#: lib/pleroma/plugs/ensure_authenticated_plug.ex:28 +#, elixir-format +msgid "Two-factor authentication enabled, you must use a access token." +msgstr "" + +#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:210 +#, elixir-format +msgid "Unexpected error occurred while adding file to pack." +msgstr "" + +#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:138 +#, elixir-format +msgid "Unexpected error occurred while creating pack." +msgstr "" + +#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:278 +#, elixir-format +msgid "Unexpected error occurred while removing file from pack." +msgstr "" + +#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:250 +#, elixir-format +msgid "Unexpected error occurred while updating file in pack." +msgstr "" + +#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:179 +#, elixir-format +msgid "Unexpected error occurred while updating pack metadata." +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61 +#, elixir-format +msgid "Web push subscription is disabled on this Pleroma instance" +msgstr "" + +#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:451 +#, elixir-format +msgid "You can't revoke your own admin/moderator status." +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:126 +#, elixir-format +msgid "authorization required for timeline view" +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:24 +#, elixir-format +msgid "Access denied" +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:282 +#, elixir-format +msgid "This API requires an authenticated user" +msgstr "" + +#: lib/pleroma/plugs/user_is_admin_plug.ex:21 +#, elixir-format +msgid "User is not an admin." +msgstr "" diff --git a/priv/gettext/zh_Hans/LC_MESSAGES/errors.po b/priv/gettext/zh_Hans/LC_MESSAGES/errors.po new file mode 100644 index 000000000..4f029d558 --- /dev/null +++ b/priv/gettext/zh_Hans/LC_MESSAGES/errors.po @@ -0,0 +1,580 @@ +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-09-20 13:18+0000\n" +"PO-Revision-Date: 2020-09-20 14:48+0000\n" +"Last-Translator: Kana \n" +"Language-Team: Chinese (Simplified) \n" +"Language: zh_Hans\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Weblate 4.0.4\n" + +## This file is a PO Template file. +## +## `msgid`s here are often extracted from source code. +## Add new translations manually only if they're dynamic +## translations that can't be statically extracted. +## +## Run `mix gettext.extract` to bring this file up to +## date. Leave `msgstr`s empty as changing them here as no +## effect: edit them in PO (`.po`) files instead. +## From Ecto.Changeset.cast/4 +msgid "can't be blank" +msgstr "不能为空" + +## From Ecto.Changeset.unique_constraint/3 +msgid "has already been taken" +msgstr "已被占用" + +## From Ecto.Changeset.put_change/3 +msgid "is invalid" +msgstr "不合法" + +## From Ecto.Changeset.validate_format/3 +msgid "has invalid format" +msgstr "的格式不合法" + +## From Ecto.Changeset.validate_subset/3 +msgid "has an invalid entry" +msgstr "中存在不合法的项目" + +## From Ecto.Changeset.validate_exclusion/3 +msgid "is reserved" +msgstr "是被保留的" + +## From Ecto.Changeset.validate_confirmation/3 +msgid "does not match confirmation" +msgstr "" + +## From Ecto.Changeset.no_assoc_constraint/3 +msgid "is still associated with this entry" +msgstr "仍与该项目相关联" + +msgid "are still associated with this entry" +msgstr "仍与该项目相关联" + +## From Ecto.Changeset.validate_length/3 +msgid "should be %{count} character(s)" +msgid_plural "should be %{count} character(s)" +msgstr[0] "应为 %{count} 个字符" + +msgid "should have %{count} item(s)" +msgid_plural "should have %{count} item(s)" +msgstr[0] "应有 %{item} 项" + +msgid "should be at least %{count} character(s)" +msgid_plural "should be at least %{count} character(s)" +msgstr[0] "应至少有 %{count} 个字符" + +msgid "should have at least %{count} item(s)" +msgid_plural "should have at least %{count} item(s)" +msgstr[0] "应至少有 %{count} 项" + +msgid "should be at most %{count} character(s)" +msgid_plural "should be at most %{count} character(s)" +msgstr[0] "应至多有 %{count} 个字符" + +msgid "should have at most %{count} item(s)" +msgid_plural "should have at most %{count} item(s)" +msgstr[0] "应至多有 %{count} 项" + +## From Ecto.Changeset.validate_number/3 +msgid "must be less than %{number}" +msgstr "必须小于 %{number}" + +msgid "must be greater than %{number}" +msgstr "必须大于 %{number}" + +msgid "must be less than or equal to %{number}" +msgstr "必须小于等于 %{number}" + +msgid "must be greater than or equal to %{number}" +msgstr "必须大于等于 %{number}" + +msgid "must be equal to %{number}" +msgstr "必须等于 %{number}" + +#: lib/pleroma/web/common_api/common_api.ex:505 +#, elixir-format +msgid "Account not found" +msgstr "未找到账号" + +#: lib/pleroma/web/common_api/common_api.ex:339 +#, elixir-format +msgid "Already voted" +msgstr "已经进行了投票" + +#: lib/pleroma/web/oauth/oauth_controller.ex:359 +#, elixir-format +msgid "Bad request" +msgstr "不正确的请求" + +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:426 +#, elixir-format +msgid "Can't delete object" +msgstr "不能删除对象" + +#: lib/pleroma/web/controller_helper.ex:105 +#: lib/pleroma/web/controller_helper.ex:111 +#, elixir-format +msgid "Can't display this activity" +msgstr "不能显示该活动" + +#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:285 +#, elixir-format +msgid "Can't find user" +msgstr "找不到用户" + +#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:61 +#, elixir-format +msgid "Can't get favorites" +msgstr "不能获取收藏" + +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:438 +#, elixir-format +msgid "Can't like object" +msgstr "" + +#: lib/pleroma/web/common_api/utils.ex:563 +#, elixir-format +msgid "Cannot post an empty status without attachments" +msgstr "" + +#: lib/pleroma/web/common_api/utils.ex:511 +#, elixir-format +msgid "Comment must be up to %{max_size} characters" +msgstr "" + +#: lib/pleroma/config/config_db.ex:191 +#, elixir-format +msgid "Config with params %{params} not found" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:181 +#: lib/pleroma/web/common_api/common_api.ex:185 +#, elixir-format +msgid "Could not delete" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:231 +#, elixir-format +msgid "Could not favorite" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:453 +#, elixir-format +msgid "Could not pin" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:278 +#, elixir-format +msgid "Could not unfavorite" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:463 +#, elixir-format +msgid "Could not unpin" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:216 +#, elixir-format +msgid "Could not unrepeat" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:512 +#: lib/pleroma/web/common_api/common_api.ex:521 +#, elixir-format +msgid "Could not update state" +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:207 +#, elixir-format +msgid "Error." +msgstr "" + +#: lib/pleroma/web/twitter_api/twitter_api.ex:106 +#, elixir-format +msgid "Invalid CAPTCHA" +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:116 +#: lib/pleroma/web/oauth/oauth_controller.ex:568 +#, elixir-format +msgid "Invalid credentials" +msgstr "" + +#: lib/pleroma/plugs/ensure_authenticated_plug.ex:38 +#, elixir-format +msgid "Invalid credentials." +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:355 +#, elixir-format +msgid "Invalid indices" +msgstr "" + +#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:29 +#, elixir-format +msgid "Invalid parameters" +msgstr "" + +#: lib/pleroma/web/common_api/utils.ex:414 +#, elixir-format +msgid "Invalid password." +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:220 +#, elixir-format +msgid "Invalid request" +msgstr "" + +#: lib/pleroma/web/twitter_api/twitter_api.ex:109 +#, elixir-format +msgid "Kocaptcha service unavailable" +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:112 +#, elixir-format +msgid "Missing parameters" +msgstr "" + +#: lib/pleroma/web/common_api/utils.ex:547 +#, elixir-format +msgid "No such conversation" +msgstr "" + +#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:388 +#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:414 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:456 +#, elixir-format +msgid "No such permission_group" +msgstr "" + +#: lib/pleroma/plugs/uploaded_media.ex:84 +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:486 lib/pleroma/web/admin_api/controllers/fallback_controller.ex:11 +#: lib/pleroma/web/feed/user_controller.ex:71 lib/pleroma/web/ostatus/ostatus_controller.ex:143 +#, elixir-format +msgid "Not found" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:331 +#, elixir-format +msgid "Poll's author can't vote" +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20 +#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:37 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:49 +#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:50 lib/pleroma/web/mastodon_api/controllers/status_controller.ex:306 +#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71 +#, elixir-format +msgid "Record not found" +msgstr "" + +#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:35 +#: lib/pleroma/web/feed/user_controller.ex:77 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:36 +#: lib/pleroma/web/ostatus/ostatus_controller.ex:149 +#, elixir-format +msgid "Something went wrong" +msgstr "" + +#: lib/pleroma/web/common_api/activity_draft.ex:107 +#, elixir-format +msgid "The message visibility must be direct" +msgstr "" + +#: lib/pleroma/web/common_api/utils.ex:573 +#, elixir-format +msgid "The status is over the character limit" +msgstr "" + +#: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:31 +#, elixir-format +msgid "This resource requires authentication." +msgstr "" + +#: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206 +#, elixir-format +msgid "Throttled" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:356 +#, elixir-format +msgid "Too many choices" +msgstr "" + +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:443 +#, elixir-format +msgid "Unhandled activity type" +msgstr "" + +#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:485 +#, elixir-format +msgid "You can't revoke your own admin status." +msgstr "" + +#: lib/pleroma/web/oauth/oauth_controller.ex:221 +#: lib/pleroma/web/oauth/oauth_controller.ex:308 +#, elixir-format +msgid "Your account is currently disabled" +msgstr "" + +#: lib/pleroma/web/oauth/oauth_controller.ex:183 +#: lib/pleroma/web/oauth/oauth_controller.ex:331 +#, elixir-format +msgid "Your login is missing a confirmed e-mail address" +msgstr "" + +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:390 +#, elixir-format +msgid "can't read inbox of %{nickname} as %{as_nickname}" +msgstr "" + +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:473 +#, elixir-format +msgid "can't update outbox of %{nickname} as %{as_nickname}" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:471 +#, elixir-format +msgid "conversation is already muted" +msgstr "" + +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:314 +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:492 +#, elixir-format +msgid "error" +msgstr "" + +#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:32 +#, elixir-format +msgid "mascots can only be images" +msgstr "" + +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:62 +#, elixir-format +msgid "not found" +msgstr "" + +#: lib/pleroma/web/oauth/oauth_controller.ex:394 +#, elixir-format +msgid "Bad OAuth request." +msgstr "" + +#: lib/pleroma/web/twitter_api/twitter_api.ex:115 +#, elixir-format +msgid "CAPTCHA already used" +msgstr "" + +#: lib/pleroma/web/twitter_api/twitter_api.ex:112 +#, elixir-format +msgid "CAPTCHA expired" +msgstr "" + +#: lib/pleroma/plugs/uploaded_media.ex:57 +#, elixir-format +msgid "Failed" +msgstr "" + +#: lib/pleroma/web/oauth/oauth_controller.ex:410 +#, elixir-format +msgid "Failed to authenticate: %{message}." +msgstr "" + +#: lib/pleroma/web/oauth/oauth_controller.ex:441 +#, elixir-format +msgid "Failed to set up user account." +msgstr "" + +#: lib/pleroma/plugs/oauth_scopes_plug.ex:38 +#, elixir-format +msgid "Insufficient permissions: %{permissions}." +msgstr "" + +#: lib/pleroma/plugs/uploaded_media.ex:104 +#, elixir-format +msgid "Internal Error" +msgstr "" + +#: lib/pleroma/web/oauth/fallback_controller.ex:22 +#: lib/pleroma/web/oauth/fallback_controller.ex:29 +#, elixir-format +msgid "Invalid Username/Password" +msgstr "" + +#: lib/pleroma/web/twitter_api/twitter_api.ex:118 +#, elixir-format +msgid "Invalid answer data" +msgstr "" + +#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:33 +#, elixir-format +msgid "Nodeinfo schema version not handled" +msgstr "" + +#: lib/pleroma/web/oauth/oauth_controller.ex:172 +#, elixir-format +msgid "This action is outside the authorized scopes" +msgstr "" + +#: lib/pleroma/web/oauth/fallback_controller.ex:14 +#, elixir-format +msgid "Unknown error, please check the details and try again." +msgstr "" + +#: lib/pleroma/web/oauth/oauth_controller.ex:119 +#: lib/pleroma/web/oauth/oauth_controller.ex:158 +#, elixir-format +msgid "Unlisted redirect_uri." +msgstr "" + +#: lib/pleroma/web/oauth/oauth_controller.ex:390 +#, elixir-format +msgid "Unsupported OAuth provider: %{provider}." +msgstr "" + +#: lib/pleroma/uploaders/uploader.ex:72 +#, elixir-format +msgid "Uploader callback timeout" +msgstr "" + +#: lib/pleroma/web/uploader_controller.ex:23 +#, elixir-format +msgid "bad request" +msgstr "" + +#: lib/pleroma/web/twitter_api/twitter_api.ex:103 +#, elixir-format +msgid "CAPTCHA Error" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:290 +#, elixir-format +msgid "Could not add reaction emoji" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:301 +#, elixir-format +msgid "Could not remove reaction emoji" +msgstr "" + +#: lib/pleroma/web/twitter_api/twitter_api.ex:129 +#, elixir-format +msgid "Invalid CAPTCHA (Missing parameter: %{name})" +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:92 +#, elixir-format +msgid "List not found" +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:123 +#, elixir-format +msgid "Missing parameter: %{name}" +msgstr "" + +#: lib/pleroma/web/oauth/oauth_controller.ex:210 +#: lib/pleroma/web/oauth/oauth_controller.ex:321 +#, elixir-format +msgid "Password reset is required" +msgstr "" + +#: lib/pleroma/tests/auth_test_controller.ex:9 +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:6 +#: lib/pleroma/web/admin_api/controllers/config_controller.ex:6 lib/pleroma/web/admin_api/controllers/fallback_controller.ex:6 +#: lib/pleroma/web/admin_api/controllers/invite_controller.ex:6 lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex:6 +#: lib/pleroma/web/admin_api/controllers/oauth_app_controller.ex:6 lib/pleroma/web/admin_api/controllers/relay_controller.ex:6 +#: lib/pleroma/web/admin_api/controllers/report_controller.ex:6 lib/pleroma/web/admin_api/controllers/status_controller.ex:6 +#: lib/pleroma/web/controller_helper.ex:6 lib/pleroma/web/embed_controller.ex:6 +#: lib/pleroma/web/fallback_redirect_controller.ex:6 lib/pleroma/web/feed/tag_controller.ex:6 +#: lib/pleroma/web/feed/user_controller.ex:6 lib/pleroma/web/mailer/subscription_controller.ex:2 +#: lib/pleroma/web/masto_fe_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/account_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/app_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/auth_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/filter_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/instance_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/list_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/marker_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex:14 +#: lib/pleroma/web/mastodon_api/controllers/media_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/notification_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/report_controller.ex:8 +#: lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/search_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/status_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:7 +#: lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:6 +#: lib/pleroma/web/media_proxy/media_proxy_controller.ex:6 lib/pleroma/web/mongooseim/mongoose_im_controller.ex:6 +#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:6 lib/pleroma/web/oauth/fallback_controller.ex:6 +#: lib/pleroma/web/oauth/mfa_controller.ex:10 lib/pleroma/web/oauth/oauth_controller.ex:6 +#: lib/pleroma/web/ostatus/ostatus_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/account_controller.ex:6 +#: lib/pleroma/web/pleroma_api/controllers/chat_controller.ex:5 lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex:6 +#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:2 lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex:6 +#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/notification_controller.ex:6 +#: lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex:6 +#: lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex:7 lib/pleroma/web/static_fe/static_fe_controller.ex:6 +#: lib/pleroma/web/twitter_api/controllers/password_controller.ex:10 lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex:6 +#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:6 lib/pleroma/web/twitter_api/twitter_api_controller.ex:6 +#: lib/pleroma/web/uploader_controller.ex:6 lib/pleroma/web/web_finger/web_finger_controller.ex:6 +#, elixir-format +msgid "Security violation: OAuth scopes check was neither handled nor explicitly skipped." +msgstr "" + +#: lib/pleroma/plugs/ensure_authenticated_plug.ex:28 +#, elixir-format +msgid "Two-factor authentication enabled, you must use a access token." +msgstr "" + +#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:210 +#, elixir-format +msgid "Unexpected error occurred while adding file to pack." +msgstr "" + +#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:138 +#, elixir-format +msgid "Unexpected error occurred while creating pack." +msgstr "" + +#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:278 +#, elixir-format +msgid "Unexpected error occurred while removing file from pack." +msgstr "" + +#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:250 +#, elixir-format +msgid "Unexpected error occurred while updating file in pack." +msgstr "" + +#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:179 +#, elixir-format +msgid "Unexpected error occurred while updating pack metadata." +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61 +#, elixir-format +msgid "Web push subscription is disabled on this Pleroma instance" +msgstr "" + +#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:451 +#, elixir-format +msgid "You can't revoke your own admin/moderator status." +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:126 +#, elixir-format +msgid "authorization required for timeline view" +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:24 +#, elixir-format +msgid "Access denied" +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:282 +#, elixir-format +msgid "This API requires an authenticated user" +msgstr "" + +#: lib/pleroma/plugs/user_is_admin_plug.ex:21 +#, elixir-format +msgid "User is not an admin." +msgstr "" diff --git a/priv/repo/migrations/20200824115541_rename_activity_expiration_setting.exs b/priv/repo/migrations/20200824115541_rename_activity_expiration_setting.exs new file mode 100644 index 000000000..241882ef6 --- /dev/null +++ b/priv/repo/migrations/20200824115541_rename_activity_expiration_setting.exs @@ -0,0 +1,13 @@ +defmodule Pleroma.Repo.Migrations.RenameActivityExpirationSetting do + use Ecto.Migration + + def change do + config = Pleroma.ConfigDB.get_by_params(%{group: :pleroma, key: Pleroma.ActivityExpiration}) + + if config do + config + |> Ecto.Changeset.change(key: Pleroma.Workers.PurgeExpiredActivity) + |> Pleroma.Repo.update() + end + end +end diff --git a/priv/repo/migrations/20200825061316_move_activity_expirations_to_oban.exs b/priv/repo/migrations/20200825061316_move_activity_expirations_to_oban.exs new file mode 100644 index 000000000..a703af83f --- /dev/null +++ b/priv/repo/migrations/20200825061316_move_activity_expirations_to_oban.exs @@ -0,0 +1,28 @@ +defmodule Pleroma.Repo.Migrations.MoveActivityExpirationsToOban do + use Ecto.Migration + + import Ecto.Query, only: [from: 2] + + def change do + Pleroma.Config.Oban.warn() + + Supervisor.start_link([{Oban, Pleroma.Config.get(Oban)}], + strategy: :one_for_one, + name: Pleroma.Supervisor + ) + + from(e in "activity_expirations", + select: %{id: e.id, activity_id: e.activity_id, scheduled_at: e.scheduled_at} + ) + |> Pleroma.Repo.stream() + |> Stream.each(fn expiration -> + with {:ok, expires_at} <- DateTime.from_naive(expiration.scheduled_at, "Etc/UTC") do + Pleroma.Workers.PurgeExpiredActivity.enqueue(%{ + activity_id: FlakeId.to_string(expiration.activity_id), + expires_at: expires_at + }) + end + end) + |> Stream.run() + end +end diff --git a/priv/repo/migrations/20200825093037_drop_activity_expirations_table.exs b/priv/repo/migrations/20200825093037_drop_activity_expirations_table.exs new file mode 100644 index 000000000..11c461427 --- /dev/null +++ b/priv/repo/migrations/20200825093037_drop_activity_expirations_table.exs @@ -0,0 +1,7 @@ +defmodule Pleroma.Repo.Migrations.DropActivityExpirationsTable do + use Ecto.Migration + + def change do + drop(table("activity_expirations")) + end +end diff --git a/priv/repo/migrations/20200831142509_chat_constraints.exs b/priv/repo/migrations/20200831142509_chat_constraints.exs new file mode 100644 index 000000000..868a40a45 --- /dev/null +++ b/priv/repo/migrations/20200831142509_chat_constraints.exs @@ -0,0 +1,22 @@ +defmodule Pleroma.Repo.Migrations.ChatConstraints do + use Ecto.Migration + + def change do + remove_orphans = """ + delete from chats where not exists(select id from users where ap_id = chats.recipient); + """ + + execute(remove_orphans) + + drop(constraint(:chats, "chats_user_id_fkey")) + + alter table(:chats) do + modify(:user_id, references(:users, type: :uuid, on_delete: :delete_all)) + + modify( + :recipient, + references(:users, column: :ap_id, type: :string, on_delete: :delete_all) + ) + end + end +end diff --git a/priv/repo/migrations/20200901061256_ensure_bio_is_string.exs b/priv/repo/migrations/20200901061256_ensure_bio_is_string.exs new file mode 100644 index 000000000..0e3bb3c81 --- /dev/null +++ b/priv/repo/migrations/20200901061256_ensure_bio_is_string.exs @@ -0,0 +1,7 @@ +defmodule Pleroma.Repo.Migrations.EnsureBioIsString do + use Ecto.Migration + + def change do + execute("update users set bio = '' where bio is null", "") + end +end diff --git a/priv/repo/migrations/20200901061637_bio_set_not_null.exs b/priv/repo/migrations/20200901061637_bio_set_not_null.exs new file mode 100644 index 000000000..e3a67d4e7 --- /dev/null +++ b/priv/repo/migrations/20200901061637_bio_set_not_null.exs @@ -0,0 +1,10 @@ +defmodule Pleroma.Repo.Migrations.BioSetNotNull do + use Ecto.Migration + + def change do + execute( + "alter table users alter column bio set not null", + "alter table users alter column bio drop not null" + ) + end +end diff --git a/priv/repo/migrations/20200905082737_rename_await_up_timeout_in_connections_pool.exs b/priv/repo/migrations/20200905082737_rename_await_up_timeout_in_connections_pool.exs new file mode 100644 index 000000000..22c40663c --- /dev/null +++ b/priv/repo/migrations/20200905082737_rename_await_up_timeout_in_connections_pool.exs @@ -0,0 +1,13 @@ +defmodule Pleroma.Repo.Migrations.RenameAwaitUpTimeoutInConnectionsPool do + use Ecto.Migration + + def change do + with %Pleroma.ConfigDB{} = config <- + Pleroma.ConfigDB.get_by_params(%{group: :pleroma, key: :connections_pool}), + {timeout, value} when is_integer(timeout) <- Keyword.pop(config.value, :await_up_timeout) do + config + |> Ecto.Changeset.change(value: Keyword.put(value, :connect_timeout, timeout)) + |> Pleroma.Repo.update() + end + end +end diff --git a/priv/repo/migrations/20200905091427_rename_timeout_in_pools.exs b/priv/repo/migrations/20200905091427_rename_timeout_in_pools.exs new file mode 100644 index 000000000..bb2f50ecc --- /dev/null +++ b/priv/repo/migrations/20200905091427_rename_timeout_in_pools.exs @@ -0,0 +1,19 @@ +defmodule Pleroma.Repo.Migrations.RenameTimeoutInPools do + use Ecto.Migration + + def change do + with %Pleroma.ConfigDB{} = config <- + Pleroma.ConfigDB.get_by_params(%{group: :pleroma, key: :pools}) do + updated_value = + Enum.map(config.value, fn {pool, pool_value} -> + with {timeout, value} when is_integer(timeout) <- Keyword.pop(pool_value, :timeout) do + {pool, Keyword.put(value, :recv_timeout, timeout)} + end + end) + + config + |> Ecto.Changeset.change(value: updated_value) + |> Pleroma.Repo.update() + end + end +end diff --git a/priv/repo/migrations/20200906072147_remove_cron_stats_worker_from_oban_config.exs b/priv/repo/migrations/20200906072147_remove_cron_stats_worker_from_oban_config.exs new file mode 100644 index 000000000..022f21dc7 --- /dev/null +++ b/priv/repo/migrations/20200906072147_remove_cron_stats_worker_from_oban_config.exs @@ -0,0 +1,19 @@ +defmodule Pleroma.Repo.Migrations.RemoveCronStatsWorkerFromObanConfig do + use Ecto.Migration + + def change do + with %Pleroma.ConfigDB{} = config <- + Pleroma.ConfigDB.get_by_params(%{group: :pleroma, key: Oban}), + crontab when is_list(crontab) <- config.value[:crontab], + index when is_integer(index) <- + Enum.find_index(crontab, fn {_, worker} -> + worker == Pleroma.Workers.Cron.StatsWorker + end) do + updated_value = Keyword.put(config.value, :crontab, List.delete_at(crontab, index)) + + config + |> Ecto.Changeset.change(value: updated_value) + |> Pleroma.Repo.update() + end + end +end diff --git a/priv/repo/migrations/20200907084956_remove_cron_clear_oauth_token_worker_from_oban_config.exs b/priv/repo/migrations/20200907084956_remove_cron_clear_oauth_token_worker_from_oban_config.exs new file mode 100644 index 000000000..b5a0a0ff6 --- /dev/null +++ b/priv/repo/migrations/20200907084956_remove_cron_clear_oauth_token_worker_from_oban_config.exs @@ -0,0 +1,19 @@ +defmodule Pleroma.Repo.Migrations.RemoveCronClearOauthTokenWorkerFromObanConfig do + use Ecto.Migration + + def change do + with %Pleroma.ConfigDB{} = config <- + Pleroma.ConfigDB.get_by_params(%{group: :pleroma, key: Oban}), + crontab when is_list(crontab) <- config.value[:crontab], + index when is_integer(index) <- + Enum.find_index(crontab, fn {_, worker} -> + worker == Pleroma.Workers.Cron.ClearOauthTokenWorker + end) do + updated_value = Keyword.put(config.value, :crontab, List.delete_at(crontab, index)) + + config + |> Ecto.Changeset.change(value: updated_value) + |> Pleroma.Repo.update() + end + end +end diff --git a/priv/repo/migrations/20200907092050_move_tokens_expiration_into_oban.exs b/priv/repo/migrations/20200907092050_move_tokens_expiration_into_oban.exs new file mode 100644 index 000000000..9e49ddacb --- /dev/null +++ b/priv/repo/migrations/20200907092050_move_tokens_expiration_into_oban.exs @@ -0,0 +1,38 @@ +defmodule Pleroma.Repo.Migrations.MoveTokensExpirationIntoOban do + use Ecto.Migration + + import Ecto.Query, only: [from: 2] + + def change do + Pleroma.Config.Oban.warn() + + Supervisor.start_link([{Oban, Pleroma.Config.get(Oban)}], + strategy: :one_for_one, + name: Pleroma.Supervisor + ) + + if Pleroma.Config.get([:oauth2, :clean_expired_tokens]) do + from(t in Pleroma.Web.OAuth.Token, where: t.valid_until > ^NaiveDateTime.utc_now()) + |> Pleroma.Repo.stream() + |> Stream.each(fn token -> + Pleroma.Workers.PurgeExpiredToken.enqueue(%{ + token_id: token.id, + valid_until: DateTime.from_naive!(token.valid_until, "Etc/UTC"), + mod: Pleroma.Web.OAuth.Token + }) + end) + |> Stream.run() + end + + from(t in Pleroma.MFA.Token, where: t.valid_until > ^NaiveDateTime.utc_now()) + |> Pleroma.Repo.stream() + |> Stream.each(fn token -> + Pleroma.Workers.PurgeExpiredToken.enqueue(%{ + token_id: token.id, + valid_until: DateTime.from_naive!(token.valid_until, "Etc/UTC"), + mod: Pleroma.MFA.Token + }) + end) + |> Stream.run() + end +end diff --git a/priv/repo/migrations/20200910113106_remove_managed_config_from_db.exs b/priv/repo/migrations/20200910113106_remove_managed_config_from_db.exs new file mode 100644 index 000000000..e27a9ae48 --- /dev/null +++ b/priv/repo/migrations/20200910113106_remove_managed_config_from_db.exs @@ -0,0 +1,27 @@ +defmodule Pleroma.Repo.Migrations.RemoveManagedConfigFromDb do + use Ecto.Migration + import Ecto.Query + alias Pleroma.ConfigDB + alias Pleroma.Repo + + def up do + config_entry = + from(c in ConfigDB, + select: [:id, :value], + where: c.group == ^:pleroma and c.key == ^:instance + ) + |> Repo.one() + + if config_entry do + {_, value} = Keyword.pop(config_entry.value, :managed_config) + + config_entry + |> Ecto.Changeset.change(value: value) + |> Repo.update() + end + end + + def down do + :ok + end +end diff --git a/priv/repo/migrations/20200911055909_remove_cron_jobs.exs b/priv/repo/migrations/20200911055909_remove_cron_jobs.exs new file mode 100644 index 000000000..33897d128 --- /dev/null +++ b/priv/repo/migrations/20200911055909_remove_cron_jobs.exs @@ -0,0 +1,20 @@ +defmodule Pleroma.Repo.Migrations.RemoveCronJobs do + use Ecto.Migration + + import Ecto.Query, only: [from: 2] + + def up do + from(j in "oban_jobs", + where: + j.worker in ^[ + "Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker", + "Pleroma.Workers.Cron.StatsWorker", + "Pleroma.Workers.Cron.ClearOauthTokenWorker" + ], + select: [:id] + ) + |> Pleroma.Repo.delete_all() + end + + def down, do: :ok +end diff --git a/priv/repo/migrations/20200914105638_delete_notification_without_activity.exs b/priv/repo/migrations/20200914105638_delete_notification_without_activity.exs new file mode 100644 index 000000000..9333fc5a1 --- /dev/null +++ b/priv/repo/migrations/20200914105638_delete_notification_without_activity.exs @@ -0,0 +1,30 @@ +defmodule Pleroma.Repo.Migrations.DeleteNotificationWithoutActivity do + use Ecto.Migration + + import Ecto.Query + alias Pleroma.Repo + + def up do + from( + q in Pleroma.Notification, + left_join: c in assoc(q, :activity), + select: %{id: type(q.id, :integer)}, + where: is_nil(c.id) + ) + |> Repo.chunk_stream(1_000, :batches) + |> Stream.each(fn records -> + notification_ids = Enum.map(records, fn %{id: id} -> id end) + + Repo.delete_all( + from(n in "notifications", + where: n.id in ^notification_ids + ) + ) + end) + |> Stream.run() + end + + def down do + :ok + end +end diff --git a/priv/repo/migrations/20200914105800_add_notification_constraints.exs b/priv/repo/migrations/20200914105800_add_notification_constraints.exs new file mode 100644 index 000000000..a65c35fd0 --- /dev/null +++ b/priv/repo/migrations/20200914105800_add_notification_constraints.exs @@ -0,0 +1,23 @@ +defmodule Pleroma.Repo.Migrations.AddNotificationConstraints do + use Ecto.Migration + + def up do + drop(constraint(:notifications, "notifications_activity_id_fkey")) + + alter table(:notifications) do + modify(:activity_id, references(:activities, type: :uuid, on_delete: :delete_all), + null: false + ) + end + end + + def down do + drop(constraint(:notifications, "notifications_activity_id_fkey")) + + alter table(:notifications) do + modify(:activity_id, references(:activities, type: :uuid, on_delete: :delete_all), + null: true + ) + end + end +end diff --git a/test/activity_expiration_test.exs b/test/activity_expiration_test.exs deleted file mode 100644 index f86d79826..000000000 --- a/test/activity_expiration_test.exs +++ /dev/null @@ -1,55 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.ActivityExpirationTest do - use Pleroma.DataCase - alias Pleroma.ActivityExpiration - import Pleroma.Factory - - setup do: clear_config([ActivityExpiration, :enabled]) - - test "finds activities due to be deleted only" do - activity = insert(:note_activity) - - expiration_due = - insert(:expiration_in_the_past, %{activity_id: activity.id}) |> Repo.preload(:activity) - - activity2 = insert(:note_activity) - insert(:expiration_in_the_future, %{activity_id: activity2.id}) - - expirations = ActivityExpiration.due_expirations() - - assert length(expirations) == 1 - assert hd(expirations) == expiration_due - end - - test "denies expirations that don't live long enough" do - activity = insert(:note_activity) - now = NaiveDateTime.utc_now() - assert {:error, _} = ActivityExpiration.create(activity, now) - end - - test "deletes an expiration activity" do - Pleroma.Config.put([ActivityExpiration, :enabled], true) - activity = insert(:note_activity) - - naive_datetime = - NaiveDateTime.add( - NaiveDateTime.utc_now(), - -:timer.minutes(2), - :millisecond - ) - - expiration = - insert( - :expiration_in_the_past, - %{activity_id: activity.id, scheduled_at: naive_datetime} - ) - - Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker.perform(%Oban.Job{}) - - refute Pleroma.Repo.get(Pleroma.Activity, activity.id) - refute Pleroma.Repo.get(Pleroma.ActivityExpiration, expiration.id) - end -end diff --git a/test/activity_test.exs b/test/activity_test.exs index 2a92327d1..ee6a99cc3 100644 --- a/test/activity_test.exs +++ b/test/activity_test.exs @@ -185,15 +185,6 @@ test "find all statuses for unauthenticated users when `limit_to_local_content` end end - test "add an activity with an expiration" do - activity = insert(:note_activity) - insert(:expiration_in_the_future, %{activity_id: activity.id}) - - Pleroma.ActivityExpiration - |> where([a], a.activity_id == ^activity.id) - |> Repo.one!() - end - test "all_by_ids_with_object/1" do %{id: id1} = insert(:note_activity) %{id: id2} = insert(:note_activity) diff --git a/test/chat_test.exs b/test/chat_test.exs index 332f2180a..9e8a9ebf0 100644 --- a/test/chat_test.exs +++ b/test/chat_test.exs @@ -26,6 +26,28 @@ test "it creates a chat for a user and recipient" do assert chat.id end + test "deleting the user deletes the chat" do + user = insert(:user) + other_user = insert(:user) + + {:ok, chat} = Chat.bump_or_create(user.id, other_user.ap_id) + + Repo.delete(user) + + refute Chat.get_by_id(chat.id) + end + + test "deleting the recipient deletes the chat" do + user = insert(:user) + other_user = insert(:user) + + {:ok, chat} = Chat.bump_or_create(user.id, other_user.ap_id) + + Repo.delete(other_user) + + refute Chat.get_by_id(chat.id) + end + test "it returns and bumps a chat for a user and recipient if it already exists" do user = insert(:user) other_user = insert(:user) diff --git a/test/config/deprecation_warnings_test.exs b/test/config/deprecation_warnings_test.exs index 555661a71..e22052404 100644 --- a/test/config/deprecation_warnings_test.exs +++ b/test/config/deprecation_warnings_test.exs @@ -4,12 +4,15 @@ defmodule Pleroma.Config.DeprecationWarningsTest do import ExUnit.CaptureLog + alias Pleroma.Config + alias Pleroma.Config.DeprecationWarnings + test "check_old_mrf_config/0" do clear_config([:instance, :rewrite_policy], Pleroma.Web.ActivityPub.MRF.NoOpPolicy) clear_config([:instance, :mrf_transparency], true) clear_config([:instance, :mrf_transparency_exclusions], []) - assert capture_log(fn -> Pleroma.Config.DeprecationWarnings.check_old_mrf_config() end) =~ + assert capture_log(fn -> DeprecationWarnings.check_old_mrf_config() end) =~ """ !!!DEPRECATION WARNING!!! Your config is using old namespaces for MRF configuration. They should work for now, but you are advised to change to new namespaces to prevent possible issues later: @@ -44,22 +47,66 @@ test "move_namespace_and_warn/2" do ] assert capture_log(fn -> - Pleroma.Config.DeprecationWarnings.move_namespace_and_warn( + DeprecationWarnings.move_namespace_and_warn( config_map, "Warning preface" ) end) =~ "Warning preface\n error :key\n error :key2\n error :key3" - assert Pleroma.Config.get(new_group1) == 1 - assert Pleroma.Config.get(new_group2) == 2 - assert Pleroma.Config.get(new_group3) == 3 + assert Config.get(new_group1) == 1 + assert Config.get(new_group2) == 2 + assert Config.get(new_group3) == 3 end test "check_media_proxy_whitelist_config/0" do clear_config([:media_proxy, :whitelist], ["https://example.com", "example2.com"]) assert capture_log(fn -> - Pleroma.Config.DeprecationWarnings.check_media_proxy_whitelist_config() + DeprecationWarnings.check_media_proxy_whitelist_config() end) =~ "Your config is using old format (only domain) for MediaProxy whitelist option" end + + describe "check_gun_pool_options/0" do + test "await_up_timeout" do + config = Config.get(:connections_pool) + clear_config(:connections_pool, Keyword.put(config, :await_up_timeout, 5_000)) + + assert capture_log(fn -> + DeprecationWarnings.check_gun_pool_options() + end) =~ + "Your config is using old setting name `await_up_timeout` instead of `connect_timeout`" + end + + test "pool timeout" do + old_config = [ + federation: [ + size: 50, + max_waiting: 10, + timeout: 10_000 + ], + media: [ + size: 50, + max_waiting: 10, + timeout: 10_000 + ], + upload: [ + size: 25, + max_waiting: 5, + timeout: 15_000 + ], + default: [ + size: 10, + max_waiting: 2, + timeout: 5_000 + ] + ] + + clear_config(:pools, old_config) + + assert capture_log(fn -> + DeprecationWarnings.check_gun_pool_options() + end) =~ + "Your config is using old setting name `timeout` instead of `recv_timeout` in pool settings" + end + end end diff --git a/test/fixtures/23211.atom b/test/fixtures/23211.atom deleted file mode 100644 index d5d111baa..000000000 --- a/test/fixtures/23211.atom +++ /dev/null @@ -1,508 +0,0 @@ - - - GNU social - https://social.heldscal.la/api/statuses/user_timeline/23211.atom - lambadalambda timeline - Updates from lambadalambda on social.heldscal.la! - https://social.heldscal.la/avatar/23211-96-20170416114255.jpeg - 2017-05-02T14:59:30+00:00 - - http://activitystrea.ms/schema/1.0/person - https://social.heldscal.la/user/23211 - lambadalambda - Call me Deacon Blues. - - - - - - lambadalambda - Constance Variable - Call me Deacon Blues. - - Berlin - - - homepage - https://heldscal.la - true - - - - - - - - - - - - - - tag:social.heldscal.la,2017-05-02:fave:23211:comment:2015260:2017-05-02T14:45:47+00:00 - Favorite - lambadalambda favorited something by godemperorofdune: <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> It's because your instance decided to be trap! lol.</p> - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-02T14:45:47+00:00 - 2017-05-02T14:45:47+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:pawoo.net,2017-05-02:objectId=7397439:objectType=Status - New comment by godemperorofdune - <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> It's because your instance decided to be trap! lol.</p> - - - - - - - tag:social.heldscal.la,2017-05-02:objectType=thread:nonce=136e244b26cdf1e9 - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.heldscal.la,2017-05-02:noticeId=2015221:objectType=note - New note by lambadalambda - Some script thinks I'm a mastodon server.<br /> <br /> [info] GET /api/v1/timelines/public<br /> [debug] Processing with Fallback.RedirectController.redirector/2<br /> Parameters: %{&quot;limit&quot; =&gt; &quot;40&quot;, &quot;path&quot; =&gt; [&quot;api&quot;, &quot;v1&quot;, &quot;timelines&quot;, &quot;public&quot;]}<br /> Pipelines: [] - - - http://activitystrea.ms/schema/1.0/post - 2017-05-02T14:40:50+00:00 - 2017-05-02T14:40:50+00:00 - - tag:social.heldscal.la,2017-05-02:objectType=thread:nonce=136e244b26cdf1e9 - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:social.heldscal.la,2017-05-02:noticeId=2014759:objectType=comment - New comment by lambadalambda - @<a href="https://mstdn.io/users/mattskala" class="h-card u-url p-nickname mention" title="Matthew Skala">mattskala</a> You and @<a href="https://mastodon.social/users/kevinmarks" class="h-card u-url p-nickname mention" title="Kevin Marks">kevinmarks</a> are not wrong, but my comment was a suggestion to users and admins: Don't use big instances, don't run big instances. Also, it's a secondary advice to devs: Don't add features that encourage big instances. - - - http://activitystrea.ms/schema/1.0/post - 2017-05-02T14:11:54+00:00 - 2017-05-02T14:11:54+00:00 - - - - tag:social.heldscal.la,2017-05-02:objectType=thread:nonce=58e32e013ab6487d - - - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:social.heldscal.la,2017-05-02:noticeId=2014684:objectType=comment - New comment by lambadalambda - @<a href="https://mastodon.social/users/Ronkjeffries" class="h-card u-url p-nickname mention" title="Ron K Jeffries social">ronkjeffries</a> @<a href="https://xoxo.zone/users/KevinMarks" class="h-card u-url p-nickname mention" title="Kevin Marks ">kevinmarks</a> Usually people who run their own private instance just look at the timelines of other servers, follow a seed population and then go from there. This is of course hard on Mastodon, because it doesn't have a publicly visible timeline. - - - http://activitystrea.ms/schema/1.0/post - 2017-05-02T14:07:00+00:00 - 2017-05-02T14:07:00+00:00 - - - - tag:social.heldscal.la,2017-05-02:objectType=thread:nonce=58e32e013ab6487d - - - - - - - - - tag:social.heldscal.la,2017-05-02:fave:23211:comment:2014584:2017-05-02T14:05:32+00:00 - Favorite - lambadalambda favorited something by mattskala: <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> It's reasonable to expect that instance sizes will obey a power-law distribution because that's what such things in nature nearly always do. If so, there'll necessarily be a few instances much larger than the others; even if most are small, the network both socially and technically has to be able to deal with the existence of the few large ones.</p> - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-02T14:05:32+00:00 - 2017-05-02T14:05:32+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:mstdn.io,2017-05-02:objectId=1316931:objectType=Status - New comment by mattskala - <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> It's reasonable to expect that instance sizes will obey a power-law distribution because that's what such things in nature nearly always do. If so, there'll necessarily be a few instances much larger than the others; even if most are small, the network both socially and technically has to be able to deal with the existence of the few large ones.</p> - - - - - - - tag:social.heldscal.la,2017-05-02:objectType=thread:nonce=58e32e013ab6487d - - - - - - - tag:social.heldscal.la,2017-05-02:fave:23211:comment:2013568:2017-05-02T14:05:29+00:00 - Favorite - lambadalambda favorited something by kevinmarks: <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> except instance populations will be power law distributed, and the problems for the tummlers are worse at scale</p> - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-02T14:05:29+00:00 - 2017-05-02T14:05:29+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:xoxo.zone,2017-05-02:objectId=89478:objectType=Status - New comment by kevinmarks - <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> except instance populations will be power law distributed, and the problems for the tummlers are worse at scale</p> - - - - - - - tag:social.heldscal.la,2017-05-02:objectType=thread:nonce=58e32e013ab6487d - - - - - - - tag:social.heldscal.la,2017-05-02:fave:23211:comment:2014060:2017-05-02T13:34:32+00:00 - Favorite - lambadalambda favorited something by gcarregues: <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> Oh purée ! Ma vie en images !</p> - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-02T13:34:32+00:00 - 2017-05-02T13:34:32+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:mastodon.etalab.gouv.fr,2017-05-02:objectId=55287:objectType=Status - New comment by gcarregues - <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> Oh purée ! Ma vie en images !</p> - - - - - - - tag:social.heldscal.la,2017-05-02:objectType=thread:nonce=2c27c27df8ec4dcc - - - - - - - tag:social.heldscal.la,2017-05-02:fave:23211:note:2013573:2017-05-02T13:03:33+00:00 - Favorite - lambadalambda favorited something by phildobangnz: also @<a href="https://sealion.club/user/579" class="h-card mention" title="Sim Bot">sim</a> reminder you are awesome; don't even trip- u kewler than Tutankhamen's cucumber, fam. Okay, good night. - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-02T13:03:33+00:00 - 2017-05-02T13:03:33+00:00 - - http://activitystrea.ms/schema/1.0/note - tag:sealion.club,2017-05-02:noticeId=3060818:objectType=note - New note by phildobangnz - also @<a href="https://sealion.club/user/579" class="h-card mention" title="Sim Bot">sim</a> reminder you are awesome; don't even trip- u kewler than Tutankhamen's cucumber, fam. Okay, good night. - - - - - - - https://sealion.club/conversation/1633267 - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:social.heldscal.la,2017-05-02:noticeId=2013586:objectType=comment - New comment by lambadalambda - @<a href="https://xoxo.zone/users/KevinMarks" class="h-card u-url p-nickname mention" title="Kevin Marks ">kevinmarks</a> People can stay in their giant unmoderatable instances with meaningless public and federated timelines and experience constant federation drama if they want. I'll stay here with my 5 friends. - - - http://activitystrea.ms/schema/1.0/post - 2017-05-02T12:54:59+00:00 - 2017-05-02T12:54:59+00:00 - - - - tag:social.heldscal.la,2017-05-02:objectType=thread:nonce=58e32e013ab6487d - - - - - - - - tag:social.heldscal.la,2017-05-02:fave:23211:note:2013486:2017-05-02T12:46:48+00:00 - Favorite - lambadalambda favorited something by fortune: There once was a dentist named Stone<br /> Who saw all his patients alone.<br /> In a fit of depravity<br /> He filled the wrong cavity,<br /> And my, how his practice has grown! - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-02T12:46:48+00:00 - 2017-05-02T12:46:48+00:00 - - http://activitystrea.ms/schema/1.0/note - tag:gs.kawa-kun.com,2017-05-02:noticeId=1655658:objectType=note - New note by fortune - There once was a dentist named Stone<br /> Who saw all his patients alone.<br /> In a fit of depravity<br /> He filled the wrong cavity,<br /> And my, how his practice has grown! - - - - - - - https://gs.kawa-kun.com/conversation/714072 - - - - - - - tag:social.heldscal.la,2017-05-02:fave:23211:note:2013365:2017-05-02T12:37:55+00:00 - Favorite - lambadalambda favorited something by xj9: <p>&gt; rollerblading to work</p> - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-02T12:37:55+00:00 - 2017-05-02T12:37:55+00:00 - - http://activitystrea.ms/schema/1.0/note - tag:sunshinegardens.org,2017-05-02:objectId=61020:objectType=Status - New note by xj9 - <p>&gt; rollerblading to work</p> - - - - - - - tag:social.heldscal.la,2017-05-02:objectType=thread:nonce=5a0e98612f634218 - - - - - - - tag:social.heldscal.la,2017-05-02:fave:23211:comment:2013259:2017-05-02T12:29:03+00:00 - Favorite - lambadalambda favorited something by cereal: @<a href="https://gs.smuglo.li/user/28250" class="h-card mention" title="Bricky">thatbrickster</a> @<a href="https://social.heldscal.la/user/23211" class="h-card mention" title="Constance Variable">lambadalambda</a> But why? - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-02T12:29:03+00:00 - 2017-05-02T12:29:03+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:sealion.club,2017-05-02:noticeId=3059985:objectType=comment - New comment by cereal - @<a href="https://gs.smuglo.li/user/28250" class="h-card mention" title="Bricky">thatbrickster</a> @<a href="https://social.heldscal.la/user/23211" class="h-card mention" title="Constance Variable">lambadalambda</a> But why? - - - - - - - tag:social.heldscal.la,2017-05-02:objectType=thread:nonce=2c27c27df8ec4dcc - - - - - - - tag:social.heldscal.la,2017-05-02:fave:23211:comment:2013227:2017-05-02T12:24:27+00:00 - Favorite - lambadalambda favorited something by thatbrickster: @<a href="https://social.heldscal.la/user/23211" class="h-card u-url p-nickname mention" title="Constance Variable">lambadalambda</a> install gentoo - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-02T12:24:27+00:00 - 2017-05-02T12:24:27+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:gs.smuglo.li,2017-05-02:noticeId=2144296:objectType=comment - New comment by thatbrickster - @<a href="https://social.heldscal.la/user/23211" class="h-card u-url p-nickname mention" title="Constance Variable">lambadalambda</a> install gentoo - - - - - - - tag:social.heldscal.la,2017-05-02:objectType=thread:nonce=2c27c27df8ec4dcc - - - - - - - tag:social.heldscal.la,2017-05-02:fave:23211:comment:2013213:2017-05-02T12:22:53+00:00 - Favorite - lambadalambda favorited something by dwmatiz: @<a href="https://social.heldscal.la/user/23211" class="h-card mention">lambadalambda</a> *unzips dick* - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-02T12:22:53+00:00 - 2017-05-02T12:22:53+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:sealion.club,2017-05-02:noticeId=3059800:objectType=comment - New comment by dwmatiz - @<a href="https://social.heldscal.la/user/23211" class="h-card mention">lambadalambda</a> *unzips dick* - - - - - - - tag:social.heldscal.la,2017-05-02:objectType=thread:nonce=2c27c27df8ec4dcc - - - - - - - tag:social.heldscal.la,2017-05-02:fave:23211:comment:2013199:2017-05-02T12:22:03+00:00 - Favorite - lambadalambda favorited something by shpuld: @<a href="https://social.heldscal.la/user/23211" class="h-card mention" title="Constance Variable">lambadalambda</a> get #<span class="tag"><a href="https://shitposter.club/tag/cofe" rel="tag">cofe</a></span> - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-02T12:22:03+00:00 - 2017-05-02T12:22:03+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2017-05-02:noticeId=2783524:objectType=comment - New comment by shpuld - @<a href="https://social.heldscal.la/user/23211" class="h-card mention" title="Constance Variable">lambadalambda</a> get #<span class="tag"><a href="https://shitposter.club/tag/cofe" rel="tag">cofe</a></span> - - - - - - - tag:social.heldscal.la,2017-05-02:objectType=thread:nonce=2c27c27df8ec4dcc - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.heldscal.la,2017-05-02:noticeId=2013185:objectType=note - New note by lambadalambda - What now? <a href="https://social.heldscal.la/file/e4822d95de677757ff50d49672a4046c83218b76c04a0ad5e5f1f0a9a9eb1a74.gif" title="https://social.heldscal.la/file/e4822d95de677757ff50d49672a4046c83218b76c04a0ad5e5f1f0a9a9eb1a74.gif" rel="nofollow external noreferrer" class="attachment" id="attachment-422572">https://social.heldscal.la/attachment/422572</a> - - - http://activitystrea.ms/schema/1.0/post - 2017-05-02T12:21:04+00:00 - 2017-05-02T12:21:04+00:00 - - tag:social.heldscal.la,2017-05-02:objectType=thread:nonce=2c27c27df8ec4dcc - - - - - - - - tag:social.heldscal.la,2017-05-02:fave:23211:note:2012929:2017-05-02T12:01:25+00:00 - Favorite - lambadalambda favorited something by drkmttr: <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> I checked out No Agenda because I saw you mention it several time. Sadly, I wasn't impressed. I'm all about varying perspectives but Adam and John basically just sound like resentful curmudgeons. It seems like their shtick is basically playing devil's advocate to everything to arouse some discontent. Just my two cents. 😉</p> - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-02T12:01:25+00:00 - 2017-05-02T12:01:25+00:00 - - http://activitystrea.ms/schema/1.0/note - tag:mstdn.io,2017-05-02:objectId=1310093:objectType=Status - New note by drkmttr - <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> I checked out No Agenda because I saw you mention it several time. Sadly, I wasn't impressed. I'm all about varying perspectives but Adam and John basically just sound like resentful curmudgeons. It seems like their shtick is basically playing devil's advocate to everything to arouse some discontent. Just my two cents. 😉</p> - - - - - - - tag:social.heldscal.la,2017-05-02:objectType=thread:nonce=2f329b4eb20e83e2 - - - - - - - tag:social.heldscal.la,2017-05-02:fave:23211:comment:2012336:2017-05-02T11:06:42+00:00 - Favorite - lambadalambda favorited something by clacke: @<a href="https://mastodon.org.uk/users/dick_turpin" class="h-card u-url p-nickname mention" title="dick_turpin">dickturpin</a> @<a href="http://quitter.se/user/113503" class="h-card u-url p-nickname mention" title="Luke">luke</a> Oh no, I miss being irritated by you, it helps me understand myself and others. Also it builds character. :-)<br /> <br /> So if this is not federation because you can't follow all of online mankind, what should we call it? Proto-federated? Pre-federated?<br /> <br /> The term has been used decades ago for just one Microsoft Active Directory domain cross-certifying the root of another, by mutual agreement. I don't see how it's any less relevant to opportunistic federation between open servers on an open internet.<br /> <br /> I'm not saying we should be satisfied, I'm just saying that "federate" is a useful word and to build a big system we need to start with a small one. And focus on the things we *can* change, like helping the OStatus network grow and making the tools more useful.<br /> <br /> Saying that the network's ideals have failed because other networks aren't joining is doing neither of that. - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-02T11:06:42+00:00 - 2017-05-02T11:06:42+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:social.heldscal.la,2017-05-02:noticeId=2012336:objectType=comment - New comment by clacke - @<a href="https://mastodon.org.uk/users/dick_turpin" class="h-card u-url p-nickname mention" title="dick_turpin">dickturpin</a> @<a href="http://quitter.se/user/113503" class="h-card u-url p-nickname mention" title="Luke">luke</a> Oh no, I miss being irritated by you, it helps me understand myself and others. Also it builds character. :-)<br /> <br /> So if this is not federation because you can't follow all of online mankind, what should we call it? Proto-federated? Pre-federated?<br /> <br /> The term has been used decades ago for just one Microsoft Active Directory domain cross-certifying the root of another, by mutual agreement. I don't see how it's any less relevant to opportunistic federation between open servers on an open internet.<br /> <br /> I'm not saying we should be satisfied, I'm just saying that &quot;federate&quot; is a useful word and to build a big system we need to start with a small one. And focus on the things we *can* change, like helping the OStatus network grow and making the tools more useful.<br /> <br /> Saying that the network's ideals have failed because other networks aren't joining is doing neither of that. - - - - - - - https://s.wefamlee.be/conversation/16478 - - - - - - - tag:social.heldscal.la,2017-05-02:fave:23211:comment:2011332:2017-05-02T10:37:40+00:00 - Favorite - lambadalambda favorited something by moonman: @<a href="https://social.heldscal.la/user/23211" class="h-card mention" title="Constance Variable">lambadalambda</a> <a href="https://www.youtube.com/watch?v=mKLizztikRk" title="https://www.youtube.com/watch?v=mKLizztikRk" class="attachment" rel="nofollow">https://www.youtube.com/watch?v=mKLizztikRk</a> - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-02T10:37:40+00:00 - 2017-05-02T10:37:40+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2017-05-02:noticeId=2781833:objectType=comment - New comment by moonman - @<a href="https://social.heldscal.la/user/23211" class="h-card mention" title="Constance Variable">lambadalambda</a> <a href="https://www.youtube.com/watch?v=mKLizztikRk" title="https://www.youtube.com/watch?v=mKLizztikRk" class="attachment" rel="nofollow">https://www.youtube.com/watch?v=mKLizztikRk</a> - - - - - - - tag:social.heldscal.la,2017-05-02:objectType=thread:nonce=11d8b8c27d9513ec - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:social.heldscal.la,2017-05-02:noticeId=2012145:objectType=comment - New comment by lambadalambda - @<a href="https://sealion.club/user/186" class="h-card u-url p-nickname mention" title="I'M CEREAL U GUISE">cereal</a> ? No, you don't even need the identity servers for federation. - - - http://activitystrea.ms/schema/1.0/post - 2017-05-02T10:37:33+00:00 - 2017-05-02T10:37:33+00:00 - - - - https://sealion.club/conversation/1629037 - - - - - - - diff --git a/test/fixtures/custom_instance_panel.html b/test/fixtures/custom_instance_panel.html new file mode 100644 index 000000000..6371a1e43 --- /dev/null +++ b/test/fixtures/custom_instance_panel.html @@ -0,0 +1 @@ +

Custom instance panel

\ No newline at end of file diff --git a/test/fixtures/cw_retweet.xml b/test/fixtures/cw_retweet.xml deleted file mode 100644 index c99a569d7..000000000 --- a/test/fixtures/cw_retweet.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - https://mastodon.social/users/lambadalambda.atom - Critical Value - - 2017-04-16T21:47:25Z - https://files.mastodon.social/accounts/avatars/000/000/264/original/1429214160519.gif - - https://mastodon.social/users/lambadalambda - http://activitystrea.ms/schema/1.0/person - https://mastodon.social/users/lambadalambda - lambadalambda - lambadalambda@mastodon.social - - - - lambadalambda - Critical Value - public - - - - - - - tag:mastodon.social,2017-05-11:objectId=5647963:objectType=Status - 2017-05-11T10:23:15Z - 2017-05-11T10:23:15Z - lambadalambda shared a status by Skruyb@mamot.fr - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/share - - tag:mamot.fr,2017-05-10:objectId=1294943:objectType=Status - 2017-05-10T17:31:44Z - 2017-05-10T17:31:45Z - New status by Skruyb@mamot.fr - - https://mamot.fr/users/Skruyb - http://activitystrea.ms/schema/1.0/person - https://mamot.fr/users/Skruyb - Skruyb - Skruyb@mamot.fr - <p>Fr and En.<br>Posts will disappear on a regular basis.</p> - - - - Skruyb - The 7th Son - Fr and En.Posts will disappear on a regular basis. - public - - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - Hey. - <p><span class="h-card"><a href="https://mastodon.social/@lambadalambda">@<span>lambadalambda</span></a></span></p><p>Hey!!!</p> - - - public - - - - <p><span class="h-card"><a href="https://mastodon.social/@lambadalambda">@<span>lambadalambda</span></a></span></p><p>Hey!!!</p> - - public - - - - diff --git a/test/fixtures/delete.xml b/test/fixtures/delete.xml deleted file mode 100644 index 731e1c204..000000000 --- a/test/fixtures/delete.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - https://mastodon.sdf.org/users/snowdusk.atom - snowdusk - Amateur live performance DJ/radio DJ on SDF's underground Internet radio http://aNONradio.net (LIVE Sat Sun Mon Tue 23:00-24:00 UTC) - http://snowdusk.sdf.org - 2017-06-17T04:14:34Z - https://mastodon.sdf.org/system/accounts/avatars/000/000/002/original/405a7652d5f60449.jpg?1497672873 - - https://mastodon.sdf.org/users/snowdusk - http://activitystrea.ms/schema/1.0/person - https://mastodon.sdf.org/users/snowdusk - snowdusk - snowdusk@mastodon.sdf.org - <p>Amateur live performance DJ/radio DJ on SDF&apos;s underground Internet radio <a href="http://anonradio.net/" rel="nofollow noopener" target="_blank"><span class="invisible">http://</span><span class="">anonradio.net/</span><span class="invisible"></span></a> (LIVE Sat Sun Mon Tue 23:00-24:00 UTC) - <a href="http://snowdusk.sdf.org/" rel="nofollow noopener" target="_blank"><span class="invisible">http://</span><span class="">snowdusk.sdf.org/</span><span class="invisible"></span></a></p> - - - - snowdusk - snowdusk - Amateur live performance DJ/radio DJ on SDF's underground Internet radio http://aNONradio.net (LIVE Sat Sun Mon Tue 23:00-24:00 UTC) - http://snowdusk.sdf.org - public - - - - - - - - tag:mastodon.sdf.org,2017-06-10:objectId=310513:objectType=Status - 2017-06-10T22:02:31Z - 2017-06-10T22:02:31Z - snowdusk deleted status - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/delete - Deleted status - - - - diff --git a/test/fixtures/dm.xml b/test/fixtures/dm.xml deleted file mode 100644 index d0b8aa811..000000000 --- a/test/fixtures/dm.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - tag:mastodon.social,2017-06-30:objectId=11260427:objectType=Status - 2017-06-30T13:27:47Z - 2017-06-30T13:27:47Z - New status by lambadalambda - - https://mastodon.social/users/lambadalambda - http://activitystrea.ms/schema/1.0/person - https://mastodon.social/users/lambadalambda - lambadalambda - lambadalambda@mastodon.social - - - lambadalambda - Critical Value - public - - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://pleroma.soykaf.com/users/lain" class="u-url mention">@<span>lain</span></a></span> Hey.</p> - - direct - - - - diff --git a/test/fixtures/favorite.xml b/test/fixtures/favorite.xml deleted file mode 100644 index c32b4a403..000000000 --- a/test/fixtures/favorite.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - GNU social - https://social.heldscal.la/api/statuses/user_timeline/23211.atom - lambadalambda timeline - Updates from lambadalambda on social.heldscal.la! - https://social.heldscal.la/avatar/23211-96-20170416114255.jpeg - 2017-05-05T09:12:53+00:00 - - http://activitystrea.ms/schema/1.0/person - https://social.heldscal.la/user/23211 - lambadalambda - Call me Deacon Blues. - - - - - - lambadalambda - Constance Variable - Call me Deacon Blues. - - Berlin - - - homepage - https://heldscal.la - true - - - - - - - - - - - - - tag:social.heldscal.la,2017-05-05:fave:23211:comment:2061643:2017-05-05T09:12:50+00:00 - Favorite - lambadalambda favorited something by moonman: @<a href="https://shitposter.club/user/9655" class="h-card mention" title="Solidarity for Pigs">neimzr4luzerz</a> @<a href="https://gs.smuglo.li/user/2326" class="h-card mention" title="Dolus_McHonest">dolus</a> childhood poring over Strong's concordance and a koine Greek dictionary, fast forward to 2017 and some fuckstick who translates japanese jackoff material tells me you just need to make it sound right in English - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-05T09:12:50+00:00 - 2017-05-05T09:12:50+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment - New comment by moonman - @<a href="https://shitposter.club/user/9655" class="h-card mention" title="Solidarity for Pigs">neimzr4luzerz</a> @<a href="https://gs.smuglo.li/user/2326" class="h-card mention" title="Dolus_McHonest">dolus</a> childhood poring over Strong's concordance and a koine Greek dictionary, fast forward to 2017 and some fuckstick who translates japanese jackoff material tells me you just need to make it sound right in English - - - - - - - tag:social.heldscal.la,2017-05-05:objectType=thread:nonce=55ead90125cd4bd4 - - - - - - diff --git a/test/fixtures/favorite_with_local_note.xml b/test/fixtures/favorite_with_local_note.xml deleted file mode 100644 index 3c955607d..000000000 --- a/test/fixtures/favorite_with_local_note.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - GNU social - https://social.heldscal.la/api/statuses/user_timeline/23211.atom - lambadalambda timeline - Updates from lambadalambda on social.heldscal.la! - https://social.heldscal.la/avatar/23211-96-20170416114255.jpeg - 2017-05-05T09:12:53+00:00 - - http://activitystrea.ms/schema/1.0/person - https://social.heldscal.la/user/23211 - lambadalambda - Call me Deacon Blues. - - - - - - lambadalambda - Constance Variable - Call me Deacon Blues. - - Berlin - - - homepage - https://heldscal.la - true - - - - - - - - - - - - - tag:social.heldscal.la,2017-05-05:fave:23211:comment:2061643:2017-05-05T09:12:50+00:00 - Favorite - lambadalambda favorited something by moonman: @<a href="https://shitposter.club/user/9655" class="h-card mention" title="Solidarity for Pigs">neimzr4luzerz</a> @<a href="https://gs.smuglo.li/user/2326" class="h-card mention" title="Dolus_McHonest">dolus</a> childhood poring over Strong's concordance and a koine Greek dictionary, fast forward to 2017 and some fuckstick who translates japanese jackoff material tells me you just need to make it sound right in English - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-05T09:12:50+00:00 - 2017-05-05T09:12:50+00:00 - - http://activitystrea.ms/schema/1.0/comment - localid - New comment by moonman - @<a href="https://shitposter.club/user/9655" class="h-card mention" title="Solidarity for Pigs">neimzr4luzerz</a> @<a href="https://gs.smuglo.li/user/2326" class="h-card mention" title="Dolus_McHonest">dolus</a> childhood poring over Strong's concordance and a koine Greek dictionary, fast forward to 2017 and some fuckstick who translates japanese jackoff material tells me you just need to make it sound right in English - - - - - - tag:social.heldscal.la,2017-05-05:objectType=thread:nonce=55ead90125cd4bd4 - - - - - - diff --git a/test/fixtures/follow.xml b/test/fixtures/follow.xml deleted file mode 100644 index d4e89954b..000000000 --- a/test/fixtures/follow.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - GNU social - https://social.heldscal.la/api/statuses/user_timeline/23211.atom - lambadalambda timeline - Updates from lambadalambda on social.heldscal.la! - https://social.heldscal.la/avatar/23211-96-20170416114255.jpeg - 2017-05-07T09:54:49+00:00 - - http://activitystrea.ms/schema/1.0/person - https://social.heldscal.la/user/23211 - lambadalambda - Call me Deacon Blues. - - - - - - lambadalambda - Constance Variable - Call me Deacon Blues. - - Berlin - - - homepage - https://heldscal.la - true - - - - - - - - - - - - - tag:social.heldscal.la,2017-05-07:subscription:23211:person:44803:2017-05-07T09:54:48+00:00 - Constance Variable (lambadalambda@social.heldscal.la)'s status on Sunday, 07-May-2017 09:54:49 UTC - <a href="https://social.heldscal.la/lambadalambda">Constance Variable</a> started following <a href="https://pawoo.net/@pekorino">mono</a>. - - http://activitystrea.ms/schema/1.0/follow - 2017-05-07T09:54:49+00:00 - 2017-05-07T09:54:49+00:00 - - http://activitystrea.ms/schema/1.0/person - https://pawoo.net/users/pekorino - mono - http://shitposter.club/mono 孤独のグルメ - - - - - pekorino - mono - http://shitposter.club/mono 孤独のグルメ - - - tag:social.heldscal.la,2017-05-07:objectType=thread:nonce=6e80caf94e03029f - - - - - - diff --git a/test/fixtures/image.gif b/test/fixtures/image.gif new file mode 100755 index 000000000..9df64778b Binary files /dev/null and b/test/fixtures/image.gif differ diff --git a/test/fixtures/image.png b/test/fixtures/image.png new file mode 100755 index 000000000..e999e8800 Binary files /dev/null and b/test/fixtures/image.png differ diff --git a/test/fixtures/incoming_note_activity.xml b/test/fixtures/incoming_note_activity.xml deleted file mode 100644 index 21eda2d30..000000000 --- a/test/fixtures/incoming_note_activity.xml +++ /dev/null @@ -1,42 +0,0 @@ - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org:4040,2017-04-23:noticeId=29:objectType=note - New note by lambda - @<a href="http://pleroma.example.org:4000/users/lain3" class="h-card mention">lain3</a> - - - - - http://activitystrea.ms/schema/1.0/post - 2017-04-23T14:51:03+00:00 - 2017-04-23T14:51:03+00:00 - - http://activitystrea.ms/schema/1.0/person - http://gs.example.org:4040/index.php/user/1 - lambda - - - - - lambda - lambda - - - - - tag:gs.example.org:4040,2017-04-23:objectType=thread:nonce=f09e22f58abd5c7b - - - - http://gs.example.org:4040/index.php/api/statuses/user_timeline/1.atom - lambda - - - - http://gs.example.org:4040/theme/neo-gnu/default-avatar-profile.png - 2017-04-23T14:51:03+00:00 - - - - - diff --git a/test/fixtures/incoming_note_activity_answer.xml b/test/fixtures/incoming_note_activity_answer.xml deleted file mode 100644 index b1244faa6..000000000 --- a/test/fixtures/incoming_note_activity_answer.xml +++ /dev/null @@ -1,42 +0,0 @@ - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org:4040,2017-04-25:noticeId=55:objectType=note - New note by lambda - hey. - - - http://activitystrea.ms/schema/1.0/post - 2017-04-25T18:16:13+00:00 - 2017-04-25T18:16:13+00:00 - - http://activitystrea.ms/schema/1.0/person - http://gs.example.org:4040/index.php/user/1 - lambda - - - - - lambda - lambda - - - - - - - http://pleroma.example.org:4000/contexts/8f6f45d4-8e4d-4e1a-a2de-09f27367d2d0 - - - - http://gs.example.org:4040/index.php/api/statuses/user_timeline/1.atom - lambda - - - - http://gs.example.org:4040/theme/neo-gnu/default-avatar-profile.png - 2017-04-25T18:16:13+00:00 - - - - - diff --git a/test/fixtures/incoming_reply_mastodon.xml b/test/fixtures/incoming_reply_mastodon.xml deleted file mode 100644 index 8ee1186cc..000000000 --- a/test/fixtures/incoming_reply_mastodon.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - tag:mastodon.social,2017-05-02:objectId=4901603:objectType=Status - 2017-05-02T18:33:06Z - 2017-05-02T18:33:06Z - New status by lambadalambda - - https://mastodon.social/users/lambadalambda - http://activitystrea.ms/schema/1.0/person - https://mastodon.social/users/lambadalambda - lambadalambda - lambadalambda@mastodon.social - - - - lambadalambda - Critical Value - public - - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://pleroma.soykaf.com/users/lain" class="u-url mention">@<span>lain</span></a></span> hey</p> - - - public - - - - diff --git a/test/fixtures/incoming_websub_gnusocial_attachments.xml b/test/fixtures/incoming_websub_gnusocial_attachments.xml deleted file mode 100644 index 9d331ef32..000000000 --- a/test/fixtures/incoming_websub_gnusocial_attachments.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - GNU social - https://social.heldscal.la/api/statuses/user_timeline/23211.atom - lambadalambda timeline - Updates from lambadalambda on social.heldscal.la! - https://social.heldscal.la/avatar/23211-96-20170416114255.jpeg - 2017-05-02T20:29:35+00:00 - - http://activitystrea.ms/schema/1.0/person - https://social.heldscal.la/user/23211 - lambadalambda - Call me Deacon Blues. - - - - - - lambadalambda - Constance Variable - Call me Deacon Blues. - - Berlin - - - homepage - https://heldscal.la - true - - - - - - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.heldscal.la,2017-05-02:noticeId=2020923:objectType=note - New note by lambadalambda - Okay gonna stream some cool games!! <a href="https://social.heldscal.la/file/7ed5ee508e6376a6e3dd581e17e7ed0b7b638147c7e86784bf83abc2641ee3d4.gif" title="https://social.heldscal.la/file/7ed5ee508e6376a6e3dd581e17e7ed0b7b638147c7e86784bf83abc2641ee3d4.gif" rel="nofollow external noreferrer" class="attachment" id="attachment-423842">https://social.heldscal.la/attachment/423842</a> <a href="https://social.heldscal.la/file/4c209099cadfc5afd3e27a334aa0db96b3a7510dde1603305d68a2707e59a11f.png" title="https://social.heldscal.la/file/4c209099cadfc5afd3e27a334aa0db96b3a7510dde1603305d68a2707e59a11f.png" rel="nofollow external noreferrer" class="attachment" id="attachment-423843">https://social.heldscal.la/attachment/423843</a> - - - http://activitystrea.ms/schema/1.0/post - 2017-05-02T20:29:35+00:00 - 2017-05-02T20:29:35+00:00 - - tag:social.heldscal.la,2017-05-02:objectType=thread:nonce=26c7afdcbcf4ebd4 - - - - - - - - diff --git a/test/fixtures/lambadalambda.atom b/test/fixtures/lambadalambda.atom deleted file mode 100644 index 964a416f7..000000000 --- a/test/fixtures/lambadalambda.atom +++ /dev/null @@ -1,479 +0,0 @@ - - - https://mastodon.social/users/lambadalambda.atom - Critical Value - - 2017-04-16T21:47:25Z - https://files.mastodon.social/accounts/avatars/000/000/264/original/1429214160519.gif?1492379244 - - https://mastodon.social/users/lambadalambda - http://activitystrea.ms/schema/1.0/person - https://mastodon.social/users/lambadalambda - lambadalambda - lambadalambda@mastodon.social - a cool dude. - - - - lambadalambda - Critical Value - public - - - - - - - - tag:mastodon.social,2017-04-07:objectId=1874242:objectType=Status - 2017-04-07T11:02:56Z - 2017-04-07T11:02:56Z - lambadalambda shared a status by 0xroy@social.wxcafe.net - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/share - - tag:social.wxcafe.net,2017-04-07:objectId=72554:objectType=Status - 2017-04-07T11:01:59Z - 2017-04-07T11:02:00Z - New status by 0xroy@social.wxcafe.net - - https://social.wxcafe.net/users/0xroy - http://activitystrea.ms/schema/1.0/person - https://social.wxcafe.net/users/0xroy - 0xroy - 0xroy@social.wxcafe.net - ta caution weeb | discussions privées : <a href="https://💌.0xroy.me" rel="nofollow noopener" target="_blank"><span class="invisible">https://</span><span class="">💌.0xroy.me</span><span class="invisible"></span></a> - - - - 0xroy - 「R O Y 🍵 B O S」 - ta caution weeb | discussions privées : <a href="https://%F0%9F%92%8C.0xroy.me" rel="nofollow noopener"><span class="invisible">https://</span><span class="">💌.0xroy.me</span><span class="invisible"></span></a> - public - - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>someone pls eli5 matrix (protocol) and riot</p> - - public - - - <p>someone pls eli5 matrix (protocol) and riot</p> - - public - - - - - tag:mastodon.social,2017-04-06:objectId=1768247:objectType=Status - 2017-04-06T11:10:19Z - 2017-04-06T11:10:19Z - lambadalambda shared a status by areyoutoo@mastodon.xyz - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/share - - tag:mastodon.xyz,2017-04-05:objectId=133327:objectType=Status - 2017-04-05T17:36:41Z - 2017-04-05T18:12:14Z - New status by areyoutoo@mastodon.xyz - - https://mastodon.xyz/users/areyoutoo - http://activitystrea.ms/schema/1.0/person - https://mastodon.xyz/users/areyoutoo - areyoutoo - areyoutoo@mastodon.xyz - devops | retired gamedev | always boost puppy pics - - - - areyoutoo - Raw Butter - devops | retired gamedev | always boost puppy pics - public - - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>Some UX thoughts for <a href="https://mastodon.xyz/tags/mastodev" class="mention hashtag">#<span>mastodev</span></a>:</p><p>- Would be nice if I could work on multiple draft toots? Clicking to reply to someone seems to erase any draft I had been working on.</p><p>- Kinda risky to click on the Federated Timeline if it loads new toots and scrolls 10ms before I click on something.</p><p>I probably don't know enough web frontend to help, but it might be fun to try.</p> - - - public - - - <p>Some UX thoughts for <a href="https://mastodon.xyz/tags/mastodev" class="mention hashtag">#<span>mastodev</span></a>:</p><p>- Would be nice if I could work on multiple draft toots? Clicking to reply to someone seems to erase any draft I had been working on.</p><p>- Kinda risky to click on the Federated Timeline if it loads new toots and scrolls 10ms before I click on something.</p><p>I probably don't know enough web frontend to help, but it might be fun to try.</p> - - public - - - - - tag:mastodon.social,2017-04-06:objectId=1764509:objectType=Status - 2017-04-06T10:15:38Z - 2017-04-06T10:15:38Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - This is a test for cw federation - <p>This is a test for cw federation body text.</p> - - public - - - - - tag:mastodon.social,2017-04-05:objectId=1645208:objectType=Status - 2017-04-05T07:14:53Z - 2017-04-05T07:14:53Z - lambadalambda shared a status by lambadalambda@social.heldscal.la - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/share - - tag:social.heldscal.la,2017-04-05:noticeId=1502088:objectType=note - 2017-04-05T06:12:09Z - 2017-04-05T07:12:47Z - New status by lambadalambda@social.heldscal.la - - https://social.heldscal.la/user/23211 - http://activitystrea.ms/schema/1.0/person - https://social.heldscal.la/user/23211 - lambadalambda - lambadalambda@social.heldscal.la - Call me Deacon Blues. - - - - lambadalambda - Constance Variable - Call me Deacon Blues. - public - - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - Federation 101: <a href="https://www.youtube.com/watch?v=t1lYU5CA40o" rel="nofollow external noreferrer" class="attachment thumbnail">https://www.youtube.com/watch?v=t1lYU5CA40o</a> - - public - - - Federation 101: <a href="https://www.youtube.com/watch?v=t1lYU5CA40o" rel="nofollow external noreferrer" class="attachment thumbnail">https://www.youtube.com/watch?v=t1lYU5CA40o</a> - - public - - - - - tag:mastodon.social,2017-04-05:objectId=1641750:objectType=Status - 2017-04-05T05:44:48Z - 2017-04-05T05:44:48Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> just a test.</p> - - - public - - - - - tag:mastodon.social,2017-04-04:objectId=1540149:objectType=Status - 2017-04-04T06:31:09Z - 2017-04-04T06:31:09Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>Looks like you still can&apos;t delete your account here (PRIVACY!), but I won&apos;t be posting here anymore, my main account is <span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span></p> - - - public - - - - - tag:mastodon.social,2017-04-04:objectId=1539608:objectType=Status - 2017-04-04T06:18:16Z - 2017-04-04T06:18:16Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://mastodon.social/@ghostbar" class="u-url mention">@<span>ghostbar</span></a></span> Remember to rewrite it in Rust once you&apos;re done.</p> - - - public - - - - - - tag:mastodon.social,2017-04-03:objectId=1504813:objectType=Status - 2017-04-03T18:01:20Z - 2017-04-03T18:01:20Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://mastodon.xyz/@Azurolu" class="u-url mention">@<span>Azurolu</span></a></span> You mean gs.smuglo.li?</p> - - - public - - - - - - tag:mastodon.social,2017-04-03:objectId=1504805:objectType=Status - 2017-04-03T18:01:05Z - 2017-04-03T18:01:05Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>There&apos;s nothing wrong with having several alt accounts all across the fediverse. Try out another mastodon instance (<a href="https://icosahedron.website" rel="nofollow noopener" target="_blank"><span class="invisible">https://</span><span class="">icosahedron.website</span><span class="invisible"></span></a>) or a GNU Social instance (like <a href="https://shitposter.club" rel="nofollow noopener" target="_blank"><span class="invisible">https://</span><span class="">shitposter.club</span><span class="invisible"></span></a> or <a href="https://freezepeach.xyz" rel="nofollow noopener" target="_blank"><span class="invisible">https://</span><span class="">freezepeach.xyz</span><span class="invisible"></span></a>), or friendica. They are all on the same network, so you can still follow all your friends!</p> - - public - - - - - tag:mastodon.social,2017-04-03:objectId=1503965:objectType=Status - 2017-04-03T17:31:30Z - 2017-04-03T17:31:30Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://mastodon.social/@20Hz" class="u-url mention">@<span>20Hz</span></a></span> you could also try out a GS instance, which are on the same network :)</p> - - - public - - - - - - tag:mastodon.social,2017-04-03:objectId=1503955:objectType=Status - 2017-04-03T17:31:08Z - 2017-04-03T17:31:08Z - lambadalambda shared a status by shpuld@shitposter.club - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/share - - tag:shitposter.club,2017-04-03:noticeId=2251717:objectType=note - 2017-04-03T17:06:43Z - 2017-04-03T17:12:06Z - New status by shpuld@shitposter.club - - https://shitposter.club/user/5381 - http://activitystrea.ms/schema/1.0/person - https://shitposter.club/user/5381 - shpuld - shpuld@shitposter.club - - - - - shpuld - shp - public - - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - reposting the classic <a href="https://shitposter.club/file/89c5fe483526caf3a46cfc5cdd4ae68061054350e767397731af658d54786e31.jpg" class="attachment" rel="nofollow external">https://shitposter.club/attachment/219846</a> - - - public - - - reposting the classic <a href="https://shitposter.club/file/89c5fe483526caf3a46cfc5cdd4ae68061054350e767397731af658d54786e31.jpg" class="attachment" rel="nofollow external">https://shitposter.club/attachment/219846</a> - - public - - - - - tag:mastodon.social,2017-04-03:objectId=1503929:objectType=Status - 2017-04-03T17:30:43Z - 2017-04-03T17:30:43Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://mastodon.social/@ghostbar" class="u-url mention">@<span>ghostbar</span></a></span> Normally you shouldn&apos;t be running tens of thousands of users on one instance... That&apos;s one of the reasons for federation.</p> - - - public - - - - - - tag:mastodon.social,2017-04-03:objectId=1477255:objectType=Status - 2017-04-03T08:24:39Z - 2017-04-03T08:24:39Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://mastodon.social/@dot_tiff" class="u-url mention">@<span>dot_tiff</span></a></span> it&apos;s the vaporwave mode.</p> - - - public - - - - - - tag:mastodon.social,2017-04-03:objectId=1476210:objectType=Status - 2017-04-03T07:45:42Z - 2017-04-03T07:45:42Z - lambadalambda shared a status by lambadalambda@social.heldscal.la - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/share - - tag:social.heldscal.la,2017-04-03:noticeId=1475727:objectType=note - 2017-04-03T07:44:43Z - 2017-04-03T07:44:48Z - New status by lambadalambda@social.heldscal.la - - https://social.heldscal.la/user/23211 - http://activitystrea.ms/schema/1.0/person - https://social.heldscal.la/user/23211 - lambadalambda - lambadalambda@social.heldscal.la - Call me Deacon Blues. - - - - lambadalambda - Constance Variable - Call me Deacon Blues. - public - - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - Here's a song by the original anti-idol, Togawa Jun: <a href="https://www.youtube.com/watch?v=kNI_NK2YY-s" rel="nofollow external noreferrer" class="attachment">https://www.youtube.com/watch?v=kNI_NK2YY-s</a> - - public - - - Here's a song by the original anti-idol, Togawa Jun: <a href="https://www.youtube.com/watch?v=kNI_NK2YY-s" rel="nofollow external noreferrer" class="attachment">https://www.youtube.com/watch?v=kNI_NK2YY-s</a> - - public - - - - - tag:mastodon.social,2017-04-03:objectId=1476047:objectType=Status - 2017-04-03T07:39:14Z - 2017-04-03T07:39:14Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://mastodon.social/@amrrr" class="u-url mention">@<span>amrrr</span></a></span> tumblr/10, but pretty good!</p> - - - public - - - - - - tag:mastodon.social,2017-04-03:objectId=1475949:objectType=Status - 2017-04-03T07:35:45Z - 2017-04-03T07:35:45Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://mastodon.social/@Shookaite" class="u-url mention">@<span>Shookaite</span></a></span> Oh, you mean like userstyles?</p> - - - public - - - - - - tag:mastodon.social,2017-04-03:objectId=1475581:objectType=Status - 2017-04-03T07:20:03Z - 2017-04-03T07:20:03Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://mastodon.social/@Shookaite" class="u-url mention">@<span>Shookaite</span></a></span> Would be nice if someone helped port Pleroma to Mastodon, that has a theme switcher (click on the cog in the upper right): <a href="https://pleroma.heldscal.la/main/all" rel="nofollow noopener" target="_blank"><span class="invisible">https://</span><span class="">pleroma.heldscal.la/main/all</span><span class="invisible"></span></a></p> - - - public - - - - - - tag:mastodon.social,2017-04-02:objectId=1457325:objectType=Status - 2017-04-02T21:57:43Z - 2017-04-02T21:57:43Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://mastodon.social/@rhosyn" class="u-url mention">@<span>rhosyn</span></a></span> <span class="h-card"><a href="https://mastodon.social/@Meaningness" class="u-url mention">@<span>Meaningness</span></a></span> you could take a look at those listed at social.guhnoo.org</p> - - - - public - - - - - - tag:mastodon.social,2017-04-02:objectId=1447926:objectType=Status - 2017-04-02T18:31:52Z - 2017-04-02T18:31:52Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>My main account is <span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> , btw.</p> - - - public - - - - - tag:mastodon.social,2017-04-02:objectId=1447878:objectType=Status - 2017-04-02T18:30:37Z - 2017-04-02T18:30:37Z - lambadalambda shared a status by Firstaide@awoo.space - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/share - - tag:awoo.space,2017-04-02:objectId=135324:objectType=Status - 2017-04-02T18:29:32Z - 2017-04-02T18:29:32Z - New status by Firstaide@awoo.space - - https://awoo.space/users/Firstaide - http://activitystrea.ms/schema/1.0/person - https://awoo.space/users/Firstaide - Firstaide - Firstaide@awoo.space - A smol awoo account, for a smol autistic 💙 -They/them please! -NB/white/ace - - - - Firstaide - Miff🚑✨ - A smol awoo account, for a smol autistic 💙 -They/them please! -NB/white/ace - public - - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><a href="https://mastodon.social/users/lambadalambda" class="h-card u-url p-nickname mention">@<span>lambadalambda</span></a> yeah, I think that's p much the big issue here? <br>When I first heard of Masto, I thought it was just like twitter at first, I had no idea federation was even a thing?, and I actually joined p early on? :-o </p><p>idk I think more stuff needs to be done about federation promotion, but honestly its gotta come from the get go when people get here to make an account I feel :-o</p> - - - public - - - - <p><a href="https://mastodon.social/users/lambadalambda" class="h-card u-url p-nickname mention">@<span>lambadalambda</span></a> yeah, I think that's p much the big issue here? <br>When I first heard of Masto, I thought it was just like twitter at first, I had no idea federation was even a thing?, and I actually joined p early on? :-o </p><p>idk I think more stuff needs to be done about federation promotion, but honestly its gotta come from the get go when people get here to make an account I feel :-o</p> - - public - - - - diff --git a/test/fixtures/mastodon-note-cw.xml b/test/fixtures/mastodon-note-cw.xml deleted file mode 100644 index 02f49dd61..000000000 --- a/test/fixtures/mastodon-note-cw.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - https://mastodon.social/users/lambadalambda.atom - Critical Value - - 2017-04-16T21:47:25Z - https://files.mastodon.social/accounts/avatars/000/000/264/original/1429214160519.gif - - https://mastodon.social/users/lambadalambda - http://activitystrea.ms/schema/1.0/person - https://mastodon.social/users/lambadalambda - lambadalambda - lambadalambda@mastodon.social - - - - lambadalambda - Critical Value - public - - - - - - - tag:mastodon.social,2017-05-10:objectId=5551985:objectType=Status - 2017-05-10T12:21:36Z - 2017-05-10T12:21:36Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - technologic - <p>test</p> - - public - - - - diff --git a/test/fixtures/mastodon-note-unlisted.xml b/test/fixtures/mastodon-note-unlisted.xml deleted file mode 100644 index d21017b80..000000000 --- a/test/fixtures/mastodon-note-unlisted.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - https://mastodon.social/users/lambadalambda.atom - Critical Value - - 2017-04-16T21:47:25Z - https://files.mastodon.social/accounts/avatars/000/000/264/original/1429214160519.gif - - https://mastodon.social/users/lambadalambda - http://activitystrea.ms/schema/1.0/person - https://mastodon.social/users/lambadalambda - lambadalambda - lambadalambda@mastodon.social - - - - lambadalambda - Critical Value - public - - - - - - - tag:mastodon.social,2017-05-10:objectId=5551985:objectType=Status - 2017-05-10T12:21:36Z - 2017-05-10T12:21:36Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - technologic - <p>test</p> - unlisted - - - - diff --git a/test/fixtures/mastodon-problematic.xml b/test/fixtures/mastodon-problematic.xml deleted file mode 100644 index a39e72759..000000000 --- a/test/fixtures/mastodon-problematic.xml +++ /dev/null @@ -1,72 +0,0 @@ - - - https://icosahedron.website/users/shel.atom - shel🍖‼️ - Gay jackal dog, poet, future librarian. - -http://datapup.info -avatar: @puppytube@twitter.com - 2017-05-02T23:26:01Z - https://icosahedron.website/system/accounts/avatars/000/001/207/original/b1e07b09ae1cc787.png?1493767561 - - https://icosahedron.website/users/shel - http://activitystrea.ms/schema/1.0/person - https://icosahedron.website/users/shel - shel - shel@icosahedron.website - <p>Gay jackal dog, poet, future librarian. </p><p><a href="http://datapup.info/" rel="nofollow noopener" target="_blank"><span class="invisible">http://</span><span class="">datapup.info/</span><span class="invisible"></span></a><br />avatar: @puppytube@twitter.com</p> - - - - shel - shel🍖‼️ - Gay jackal dog, poet, future librarian. - -http://datapup.info -avatar: @puppytube@twitter.com - public - - - - - - - tag:icosahedron.website,2017-05-10:objectId=1414013:objectType=Status - 2017-05-10T17:16:24Z - 2017-05-10T17:16:24Z - shel shared a status by instance_names@cybre.space - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/share - - tag:cybre.space,2017-05-10:objectId=946671:objectType=Status - 2017-05-10T17:15:51Z - 2017-05-10T17:15:52Z - New status by instance_names@cybre.space - - https://cybre.space/users/instance_names - http://activitystrea.ms/schema/1.0/person - https://cybre.space/users/instance_names - instance_names - instance_names@cybre.space - <p>name ideas for your new mastodon instance. made by <span class="h-card"><a href="https://witches.town/@lycaon">@<span>lycaon</span></a></span> source available at <a href="https://github.com/LycaonIsAWolf/instance_names"><span class="invisible">https://</span><span class="ellipsis">github.com/LycaonIsAWolf/insta</span><span class="invisible">nce_names</span></a></p> - - - - instance_names - instance names - name ideas for your new mastodon instance. made by @lycaon source available at https://github.com/LycaonIsAWolf/instance_names - public - - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>dildo.codes</p> - unlisted - - - <p>dildo.codes</p> - - public - - - - diff --git a/test/fixtures/mastodon_conversation.xml b/test/fixtures/mastodon_conversation.xml deleted file mode 100644 index 8faab2304..000000000 --- a/test/fixtures/mastodon_conversation.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - tag:mastodon.social,2017-08-28:objectId=16402826:objectType=Status - 2017-08-28T17:58:55Z - 2017-08-28T17:58:55Z - New status by lambadalambda - - https://mastodon.social/users/lambadalambda - http://activitystrea.ms/schema/1.0/person - https://mastodon.social/users/lambadalambda - lambadalambda - lambadalambda@mastodon.social - - - - lambadalambda - Critical Value - public - - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - - <p>test. <a href="https://mastodon.social/media/XCp0OHGPON9kWZwhjaI" rel="nofollow noopener" target="_blank"><span class="invisible">https://</span><span class="ellipsis">mastodon.social/media/XCp0OHGP</span><span class="invisible">ON9kWZwhjaI</span></a></p> - - - public - - - - diff --git a/test/fixtures/nil_mention_entry.xml b/test/fixtures/nil_mention_entry.xml deleted file mode 100644 index e13024cb3..000000000 --- a/test/fixtures/nil_mention_entry.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - GNU social - https://social.stopwatchingus-heidelberg.de/api/statuses/user_timeline/18330.atom - atarifrosch timeline - Updates from atarifrosch on social.stopwatchingus-heidelberg.de! - https://social.stopwatchingus-heidelberg.de/avatar/18330-96-20150628163706.png - 2017-08-24T11:36:49+02:00 - - http://activitystrea.ms/schema/1.0/person - https://social.stopwatchingus-heidelberg.de/user/18330 - atarifrosch - Nerd, Pirat, Debian user, CAcert assurer, Geocacher, Freifunker. Autismus/Depression, agender. GnuPG Key-ID: 0xBCF81ADE - - - - - - atarifrosch - Atari-Frosch - Nerd, Pirat, Debian user, CAcert assurer, Geocacher, Freifunker. Autismus/Depression, agender. GnuPG Key-ID: 0xBCF81ADE - - Düsseldorf, NRW, Germany - - - homepage - https://www.atari-frosch.de/ - true - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.stopwatchingus-heidelberg.de,2017-08-22:noticeId=978072:objectType=note - New note by atarifrosch - 2017-08-22 Bundesverfassungsgericht: Erfolgreiche Verfassungsbeschwerde gegen die Versagung vorläufiger Leistungen für Kosten der Unterkunft und Heizung – <a href="https://www.bundesverfassungsgericht.de/SharedDocs/Pressemitteilungen/DE/2017/bvg17-072.html" title="https://www.bundesverfassungsgericht.de/SharedDocs/Pressemitteilungen/DE/2017/bvg17-072.html" class="attachment" id="attachment-450768" rel="nofollow external">https://www.bundesverfassungsgericht.de/SharedDocs/Pressemitteilungen/DE/2017/bvg17-072.html</a> !<a href="http://quitter.se/group/2184/id" class="h-card group" title="HartzIV (hartziv)">hartziv</a> - - - http://activitystrea.ms/schema/1.0/post - 2017-08-22T12:00:21+00:00 - 2017-08-22T12:00:21+00:00 - - tag:social.stopwatchingus-heidelberg.de,2017-08-22:noticeId=978072:objectType=thread:crc32=28a35f44 - - - - - - - - diff --git a/test/fixtures/ostatus_incoming_post.xml b/test/fixtures/ostatus_incoming_post.xml deleted file mode 100644 index 7967e1b32..000000000 --- a/test/fixtures/ostatus_incoming_post.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - GNU social - https://social.heldscal.la/api/statuses/user_timeline/23211.atom - lambadalambda timeline - Updates from lambadalambda on social.heldscal.la! - https://social.heldscal.la/avatar/23211-96-20170416114255.jpeg - 2017-04-29T18:25:38+00:00 - - http://activitystrea.ms/schema/1.0/person - https://social.heldscal.la/user/23211 - lambadalambda - Call me Deacon Blues. - - - - - - lambadalambda - Constance Variable - Call me Deacon Blues. - - Berlin - - - homepage - https://heldscal.la - true - - - - - - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.heldscal.la,2017-04-29:noticeId=1967725:objectType=note - New note by lambadalambda - Will it blend? - - - http://activitystrea.ms/schema/1.0/post - 2017-04-29T18:25:38+00:00 - 2017-04-29T18:25:38+00:00 - - tag:social.heldscal.la,2017-04-29:objectType=thread:nonce=3f3a9dd83acc4e35 - - - - - - diff --git a/test/fixtures/ostatus_incoming_post_tag.xml b/test/fixtures/ostatus_incoming_post_tag.xml deleted file mode 100644 index 0f99c4126..000000000 --- a/test/fixtures/ostatus_incoming_post_tag.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - GNU social - https://social.heldscal.la/api/statuses/user_timeline/23211.atom - lambadalambda timeline - Updates from lambadalambda on social.heldscal.la! - https://social.heldscal.la/avatar/23211-96-20170416114255.jpeg - 2017-04-29T18:25:38+00:00 - - http://activitystrea.ms/schema/1.0/person - https://social.heldscal.la/user/23211 - lambadalambda - Call me Deacon Blues. - - - - - - lambadalambda - Constance Variable - Call me Deacon Blues. - - Berlin - - - homepage - https://heldscal.la - true - - - - - - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.heldscal.la,2017-04-29:noticeId=1967725:objectType=note - New note by lambadalambda - Will it blend? - - - - - http://activitystrea.ms/schema/1.0/post - 2017-04-29T18:25:38+00:00 - 2017-04-29T18:25:38+00:00 - - tag:social.heldscal.la,2017-04-29:objectType=thread:nonce=3f3a9dd83acc4e35 - - - - - - diff --git a/test/fixtures/ostatus_incoming_reply.xml b/test/fixtures/ostatus_incoming_reply.xml deleted file mode 100644 index 83a427a68..000000000 --- a/test/fixtures/ostatus_incoming_reply.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - GNU social - https://social.heldscal.la/api/statuses/user_timeline/23211.atom - lambadalambda timeline - Updates from lambadalambda on social.heldscal.la! - https://social.heldscal.la/avatar/23211-96-20170416114255.jpeg - 2017-04-30T09:30:32+00:00 - - http://activitystrea.ms/schema/1.0/person - https://social.heldscal.la/user/23211 - lambadalambda - Call me Deacon Blues. - - - - - - lambadalambda - Constance Variable - Call me Deacon Blues. - - Berlin - - - homepage - https://heldscal.la - true - - - - - - - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:social.heldscal.la,2017-04-30:noticeId=1978790:objectType=comment - New comment by lambadalambda - @<a href="https://gs.archae.me/user/4687" class="h-card u-url p-nickname mention" title="shpbot">shpbot</a> why not indeed. - - - http://activitystrea.ms/schema/1.0/post - 2017-04-30T09:30:32+00:00 - 2017-04-30T09:30:32+00:00 - - - - https://gs.archae.me/conversation/327120 - - - - - - - diff --git a/test/fixtures/share-gs-local.xml b/test/fixtures/share-gs-local.xml deleted file mode 100644 index 9d52eab7b..000000000 --- a/test/fixtures/share-gs-local.xml +++ /dev/null @@ -1,99 +0,0 @@ - - - GNU social - https://social.heldscal.la/api/statuses/user_timeline/23211.atom - lambadalambda timeline - Updates from lambadalambda on social.heldscal.la! - https://social.heldscal.la/avatar/23211-96-20170416114255.jpeg - 2017-05-03T08:05:41+00:00 - - http://activitystrea.ms/schema/1.0/person - https://social.heldscal.la/user/23211 - lambadalambda - Call me Deacon Blues. - - - - - - lambadalambda - Constance Variable - Call me Deacon Blues. - - Berlin - - - homepage - https://heldscal.la - true - - - - - - - - - - - - - tag:social.heldscal.la,2017-05-03:noticeId=2028428:objectType=note - lambadalambda repeated a notice by lain - RT @<a href="https://pleroma.soykaf.com/users/lain" class="h-card u-url p-nickname mention" title="Lain Iwakura">lain</a> Added returning the entries as xml... let's see if the mastodon hammering stops now. - - http://activitystrea.ms/schema/1.0/share - 2017-05-03T08:05:41+00:00 - 2017-05-03T08:05:41+00:00 - - http://activitystrea.ms/schema/1.0/activity - LOCAL_ID - - Added returning the entries as xml... let's see if the mastodon hammering stops now. - - http://activitystrea.ms/schema/1.0/post - 2017-05-03T08:04:44+00:00 - 2017-05-03T08:04:44+00:00 - - http://activitystrea.ms/schema/1.0/person - LOCAL_USER - lain - Test account - - - - - - lain - Lain Iwakura - Test account - - - - http://activitystrea.ms/schema/1.0/note - https://pleroma.soykaf.com/objects/4c1bda26-902e-4525-9fcd-b9fd44925193 - New note by lain - Added returning the entries as xml... let's see if the mastodon hammering stops now. - - - - - https://pleroma.soykaf.com/contexts/ede39a2b-7cf3-4fa4-8ccd-cb97431bcc22 - - - https://pleroma.soykaf.com/users/lain/feed.atom - Lain Iwakura - - - https://social.heldscal.la/avatar/43188-96-20170429172422.jpeg - 2017-05-03T08:04:44+00:00 - - - - https://pleroma.soykaf.com/contexts/ede39a2b-7cf3-4fa4-8ccd-cb97431bcc22 - - - - - - diff --git a/test/fixtures/share-gs.xml b/test/fixtures/share-gs.xml deleted file mode 100644 index ab5e488bd..000000000 --- a/test/fixtures/share-gs.xml +++ /dev/null @@ -1,99 +0,0 @@ - - - GNU social - https://social.heldscal.la/api/statuses/user_timeline/23211.atom - lambadalambda timeline - Updates from lambadalambda on social.heldscal.la! - https://social.heldscal.la/avatar/23211-96-20170416114255.jpeg - 2017-05-03T08:05:41+00:00 - - http://activitystrea.ms/schema/1.0/person - https://social.heldscal.la/user/23211 - lambadalambda - Call me Deacon Blues. - - - - - - lambadalambda - Constance Variable - Call me Deacon Blues. - - Berlin - - - homepage - https://heldscal.la - true - - - - - - - - - - - - - tag:social.heldscal.la,2017-05-03:noticeId=2028428:objectType=note - lambadalambda repeated a notice by lain - RT @<a href="https://pleroma.soykaf.com/users/lain" class="h-card u-url p-nickname mention" title="Lain Iwakura">lain</a> Added returning the entries as xml... let's see if the mastodon hammering stops now. - - http://activitystrea.ms/schema/1.0/share - 2017-05-03T08:05:41+00:00 - 2017-05-03T08:05:41+00:00 - - http://activitystrea.ms/schema/1.0/activity - https://pleroma.soykaf.com/objects/4c1bda26-902e-4525-9fcd-b9fd44925193 - - Added returning the entries as xml... let's see if the mastodon hammering stops now. - - http://activitystrea.ms/schema/1.0/post - 2017-05-03T08:04:44+00:00 - 2017-05-03T08:04:44+00:00 - - http://activitystrea.ms/schema/1.0/person - https://pleroma.soykaf.com/users/lain - lain - Test account - - - - - - lain - Lain Iwakura - Test account - - - - http://activitystrea.ms/schema/1.0/note - https://pleroma.soykaf.com/objects/4c1bda26-902e-4525-9fcd-b9fd44925193 - New note by lain - Added returning the entries as xml... let's see if the mastodon hammering stops now. - - - - - https://pleroma.soykaf.com/contexts/ede39a2b-7cf3-4fa4-8ccd-cb97431bcc22 - - - https://pleroma.soykaf.com/users/lain/feed.atom - Lain Iwakura - - - https://social.heldscal.la/avatar/43188-96-20170429172422.jpeg - 2017-05-03T08:04:44+00:00 - - - - https://pleroma.soykaf.com/contexts/ede39a2b-7cf3-4fa4-8ccd-cb97431bcc22 - - - - - - diff --git a/test/fixtures/share.xml b/test/fixtures/share.xml deleted file mode 100644 index e07b88680..000000000 --- a/test/fixtures/share.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - tag:mastodon.social,2017-05-03:objectId=4934452:objectType=Status - 2017-05-03T08:21:09Z - 2017-05-03T08:21:09Z - lambadalambda shared a status by lain@pleroma.soykaf.com - - https://mastodon.social/users/lambadalambda - http://activitystrea.ms/schema/1.0/person - https://mastodon.social/users/lambadalambda - lambadalambda - lambadalambda@mastodon.social - - - - lambadalambda - Critical Value - public - - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/share - - https://pleroma.soykaf.com/objects/4c1bda26-902e-4525-9fcd-b9fd44925193 - 2017-05-03T08:04:44Z - 2017-05-03T08:05:52Z - New status by lain@pleroma.soykaf.com - - https://pleroma.soykaf.com/users/lain - http://activitystrea.ms/schema/1.0/person - https://pleroma.soykaf.com/users/lain - lain - lain@pleroma.soykaf.com - Test account - - - - lain - Lain Iwakura - Test account - public - - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - Added returning the entries as xml... let's see if the mastodon hammering stops now. - - public - - - Added returning the entries as xml... let's see if the mastodon hammering stops now. - - public - - - diff --git a/test/fixtures/tesla_mock/7369654.atom b/test/fixtures/tesla_mock/7369654.atom deleted file mode 100644 index 74fd9ce6b..000000000 --- a/test/fixtures/tesla_mock/7369654.atom +++ /dev/null @@ -1,44 +0,0 @@ - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2018-02-22:noticeId=7369654:objectType=comment - New comment by shpuld - @<a href="https://testing.pleroma.lol/users/lain" class="h-card mention" title="Rael Electric Razor">lain</a> me far right - - - http://activitystrea.ms/schema/1.0/post - 2018-02-22T09:20:12+00:00 - 2018-02-22T09:20:12+00:00 - - http://activitystrea.ms/schema/1.0/person - https://shitposter.club/user/5381 - shpuld - - - - - - shpuld - shp - - - - - - - tag:shitposter.club,2018-02-22:objectType=thread:nonce=e5a7c72d60a9c0e4 - - - - https://shitposter.club/api/statuses/user_timeline/5381.atom - shp - - - - https://shitposter.club/avatar/5381-96-20171230093854.png - 2018-02-23T13:30:15+00:00 - - - - - diff --git a/test/fixtures/tesla_mock/admin@mastdon.example.org.json b/test/fixtures/tesla_mock/admin@mastdon.example.org.json index a911b979a..f961ccb36 100644 --- a/test/fixtures/tesla_mock/admin@mastdon.example.org.json +++ b/test/fixtures/tesla_mock/admin@mastdon.example.org.json @@ -1,20 +1,24 @@ { - "@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1", { - "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", - "sensitive": "as:sensitive", - "movedTo": "as:movedTo", - "Hashtag": "as:Hashtag", - "ostatus": "http://ostatus.org#", - "atomUri": "ostatus:atomUri", - "inReplyToAtomUri": "ostatus:inReplyToAtomUri", - "conversation": "ostatus:conversation", - "toot": "http://joinmastodon.org/ns#", - "Emoji": "toot:Emoji", - "alsoKnownAs": { - "@id": "as:alsoKnownAs", - "@type": "@id" + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", + "sensitive": "as:sensitive", + "movedTo": "as:movedTo", + "Hashtag": "as:Hashtag", + "ostatus": "http://ostatus.org#", + "atomUri": "ostatus:atomUri", + "inReplyToAtomUri": "ostatus:inReplyToAtomUri", + "conversation": "ostatus:conversation", + "toot": "http://joinmastodon.org/ns#", + "Emoji": "toot:Emoji", + "alsoKnownAs": { + "@id": "as:alsoKnownAs", + "@type": "@id" + } } - }], + ], "id": "http://mastodon.example.org/users/admin", "type": "Person", "following": "http://mastodon.example.org/users/admin/following", @@ -23,6 +27,7 @@ "outbox": "http://mastodon.example.org/users/admin/outbox", "preferredUsername": "admin", "name": null, + "discoverable": "true", "summary": "\u003cp\u003e\u003c/p\u003e", "url": "http://mastodon.example.org/@admin", "manuallyApprovesFollowers": false, @@ -34,7 +39,8 @@ "owner": "http://mastodon.example.org/users/admin", "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtc4Tir+3ADhSNF6VKrtW\nOU32T01w7V0yshmQei38YyiVwVvFu8XOP6ACchkdxbJ+C9mZud8qWaRJKVbFTMUG\nNX4+6Q+FobyuKrwN7CEwhDALZtaN2IPbaPd6uG1B7QhWorrY+yFa8f2TBM3BxnUy\nI4T+bMIZIEYG7KtljCBoQXuTQmGtuffO0UwJksidg2ffCF5Q+K//JfQagJ3UzrR+\nZXbKMJdAw4bCVJYs4Z5EhHYBwQWiXCyMGTd7BGlmMkY6Av7ZqHKC/owp3/0EWDNz\nNqF09Wcpr3y3e8nA10X40MJqp/wR+1xtxp+YGbq/Cj5hZGBG7etFOmIpVBrDOhry\nBwIDAQAB\n-----END PUBLIC KEY-----\n" }, - "attachment": [{ + "attachment": [ + { "type": "PropertyValue", "name": "foo", "value": "bar" @@ -58,5 +64,7 @@ "mediaType": "image/png", "url": "https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png" }, - "alsoKnownAs": ["http://example.org/users/foo"] -} + "alsoKnownAs": [ + "http://example.org/users/foo" + ] +} \ No newline at end of file diff --git a/test/fixtures/tesla_mock/atarifrosch_feed.xml b/test/fixtures/tesla_mock/atarifrosch_feed.xml deleted file mode 100644 index e00df782e..000000000 --- a/test/fixtures/tesla_mock/atarifrosch_feed.xml +++ /dev/null @@ -1,473 +0,0 @@ - - - GNU social - https://social.stopwatchingus-heidelberg.de/api/statuses/user_timeline/18330.atom - atarifrosch-Zeitleiste - Aktualisierungen von atarifrosch auf social.stopwatchingus-heidelberg.de! - https://social.stopwatchingus-heidelberg.de/avatar/18330-96-20150628163706.png - 2017-08-24T12:06:55+02:00 - - http://activitystrea.ms/schema/1.0/person - https://social.stopwatchingus-heidelberg.de/user/18330 - atarifrosch - Nerd, Pirat, Debian user, CAcert assurer, Geocacher, Freifunker. Autismus/Depression, agender. GnuPG Key-ID: 0xBCF81ADE - - - - - - atarifrosch - Atari-Frosch - Nerd, Pirat, Debian user, CAcert assurer, Geocacher, Freifunker. Autismus/Depression, agender. GnuPG Key-ID: 0xBCF81ADE - - Düsseldorf, NRW, Germany - - - homepage - https://www.atari-frosch.de/ - true - - - - - - - - - - - - - - tag:social.stopwatchingus-heidelberg.de,2017-08-24:noticeId=978735:objectType=note - atarifrosch repeated a notice by hoergen - RT @<a href="https://social.hoergen.org/hoergen" class="h-card mention" title="hoergen">hoergen</a> Das falsche Bild der Tagesschau &quot;Auffallend &quot;erfolgreich&quot; - Andrea Nahles und Manuela Schwesig&quot; #<span class="tag"><a href="https://social.stopwatchingus-heidelberg.de/tag/geringverdiener" rel="tag">Geringverdiener</a></span> #<span class="tag"><a href="https://social.stopwatchingus-heidelberg.de/tag/mindestlohn" rel="tag">Mindestlohn</a></span> #<span class="tag"><a href="https://social.stopwatchingus-heidelberg.de/tag/mannxismus" rel="tag">mannxismus</a></span> #<span class="tag"><a href="https://social.stopwatchingus-heidelberg.de/tag/erwerbsminderungsrente" rel="tag">Erwerbsminderungsrente</a></span> #<span class="tag"><a href="https://social.stopwatchingus-heidelberg.de/tag/arbeitnehmerflexibilisierung" rel="tag">ArbeitnehmerFlexibilisierung</a></span> #<span class="tag"><a href="https://social.stopwatchingus-heidelberg.de/tag/altersarmut" rel="tag">AltersArmut</a></span> ..... <a href="http://www.tagesschau.de/inland/btw17/bilanz-schwesig-nahles-101.html" title="http://www.tagesschau.de/inland/btw17/bilanz-schwesig-nahles-101.html" class="attachment" id="attachment-450858" rel="nofollow external">http://www.tagesschau.de/inland/btw17/bilanz-schwesig-nahles-101.html</a> - https://social.stopwatchingus-heidelberg.de/notice/978735 - http://activitystrea.ms/schema/1.0/share - 2017-08-24T09:18:25+00:00 - 2017-08-24T09:18:25+00:00 - - http://activitystrea.ms/schema/1.0/activity - tag:social.hoergen.org,2017-08-24:noticeId=222320:objectType=note - - Das falsche Bild der Tagesschau <br /> &quot;Auffallend &quot;erfolgreich&quot; - Andrea Nahles und Manuela Schwesig&quot; #<span class="tag"><a href="https://social.hoergen.org/tag/geringverdiener" rel="tag">Geringverdiener</a></span> #<span class="tag"><a href="https://social.hoergen.org/tag/mindestlohn" rel="tag">Mindestlohn</a></span> #<span class="tag"><a href="https://social.hoergen.org/tag/mannxismus" rel="tag">mannxismus</a></span> #<span class="tag"><a href="https://social.hoergen.org/tag/erwerbsminderungsrente" rel="tag">Erwerbsminderungsrente</a></span> #<span class="tag"><a href="https://social.hoergen.org/tag/arbeitnehmerflexibilisierung" rel="tag">ArbeitnehmerFlexibilisierung</a></span> #<span class="tag"><a href="https://social.hoergen.org/tag/altersarmut" rel="tag">AltersArmut</a></span> ..... <br /> <br /> <a href="http://www.tagesschau.de/inland/btw17/bilanz-schwesig-nahles-101.html" title="http://www.tagesschau.de/inland/btw17/bilanz-schwesig-nahles-101.html" rel="nofollow external noreferrer" class="attachment">http://www.tagesschau.de/inland/btw17/bilanz-schwesig-nahles-101.html</a> - https://social.hoergen.org/notice/222320 - http://activitystrea.ms/schema/1.0/post - 2017-08-24T07:36:31+00:00 - 2017-08-24T07:36:31+00:00 - - http://activitystrea.ms/schema/1.0/person - https://social.hoergen.org/user/2 - hoergen - aka Andi Memyself #humanist #nerd Menschen liebhabender Misanthrop und auch sonst sehr vielseitig interessiert. - - - - - - hoergen - hoergen - aka Andi Memyself #humanist #nerd Menschen liebhabender Misanthrop und auch sonst sehr vielseitig interessiert. - - Berlin - - - homepage - https://hyperblog.de/hoergen/ - true - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.hoergen.org,2017-08-24:noticeId=222320:objectType=note - New note by hoergen - Das falsche Bild der Tagesschau <br /> &quot;Auffallend &quot;erfolgreich&quot; - Andrea Nahles und Manuela Schwesig&quot; #<span class="tag"><a href="https://social.hoergen.org/tag/geringverdiener" rel="tag">Geringverdiener</a></span> #<span class="tag"><a href="https://social.hoergen.org/tag/mindestlohn" rel="tag">Mindestlohn</a></span> #<span class="tag"><a href="https://social.hoergen.org/tag/mannxismus" rel="tag">mannxismus</a></span> #<span class="tag"><a href="https://social.hoergen.org/tag/erwerbsminderungsrente" rel="tag">Erwerbsminderungsrente</a></span> #<span class="tag"><a href="https://social.hoergen.org/tag/arbeitnehmerflexibilisierung" rel="tag">ArbeitnehmerFlexibilisierung</a></span> #<span class="tag"><a href="https://social.hoergen.org/tag/altersarmut" rel="tag">AltersArmut</a></span> ..... <br /> <br /> <a href="http://www.tagesschau.de/inland/btw17/bilanz-schwesig-nahles-101.html" title="http://www.tagesschau.de/inland/btw17/bilanz-schwesig-nahles-101.html" rel="nofollow external noreferrer" class="attachment">http://www.tagesschau.de/inland/btw17/bilanz-schwesig-nahles-101.html</a> - - - - - https://social.hoergen.org/conversation/98616 - - - - - - - - - https://social.hoergen.org/api/statuses/user_timeline/2.atom - hoergen - - - https://social.stopwatchingus-heidelberg.de/avatar/54316-original-20170824072526.jpeg - 2017-08-24T09:48:30+00:00 - - - - https://social.hoergen.org/conversation/98616 - - - - - - - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:social.stopwatchingus-heidelberg.de,2017-08-24:noticeId=978734:objectType=comment - New comment by atarifrosch - Jo, die Anzahl der Hartz-IV-Sanktionen nennt sie genausowenig wie die Anzahl der Menschen, die von den Repressionsbehörden in Obdachlosigkeit und Suizid getrieben wurden. Das würde die Erfolgszahlen dann doch ein wenig trüben, nech? - - - http://activitystrea.ms/schema/1.0/post - 2017-08-24T09:18:13+00:00 - 2017-08-24T09:18:13+00:00 - - - - https://social.hoergen.org/conversation/98616 - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.stopwatchingus-heidelberg.de,2017-08-24:noticeId=978732:objectType=note - New note by atarifrosch - Moin-quak. - - - http://activitystrea.ms/schema/1.0/post - 2017-08-24T09:09:39+00:00 - 2017-08-24T09:09:39+00:00 - - tag:social.stopwatchingus-heidelberg.de,2017-08-24:noticeId=978732:objectType=thread:crc32=2f92b7b6 - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.stopwatchingus-heidelberg.de,2017-08-23:noticeId=978594:objectType=note - New note by atarifrosch - n8-quak! - - - http://activitystrea.ms/schema/1.0/post - 2017-08-23T21:39:54+00:00 - 2017-08-23T21:39:54+00:00 - - tag:social.stopwatchingus-heidelberg.de,2017-08-23:noticeId=978594:objectType=thread:crc32=9bdb0ac9 - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.stopwatchingus-heidelberg.de,2017-08-23:noticeId=978503:objectType=note - New note by atarifrosch - 2017-08-16 Michal Špaček: Post a boarding pass on Facebook, get your account stolen – Post a boarding pass on Facebook, get your account stolen (gilt übrinx nicht nur für Facebook) - - - http://activitystrea.ms/schema/1.0/post - 2017-08-23T15:14:29+00:00 - 2017-08-23T15:14:29+00:00 - - tag:social.stopwatchingus-heidelberg.de,2017-08-23:noticeId=978503:objectType=thread:crc32=3de05c3a - - - - - - - tag:social.stopwatchingus-heidelberg.de,2017-08-23:fave:18330:activity:978458:2017-08-23T15:18:19+02:00 - Favorite - atarifrosch favorited something by einebiene: Haha, große Überraschung. <a href="http://www.sueddeutsche.de/wirtschaft/abgasaffaere-software-updates-fuer-dieselautos-helfen-kaum-1.3637636" title="http://www.sueddeutsche.de/wirtschaft/abgasaffaere-software-updates-fuer-dieselautos-helfen-kaum-1.3637636" rel="nofollow noreferrer" class="attachment">https://quitter.is/url/1122672</a><br /> Was ich an all diesen Artikeln schade finde, ist, daß immer nur auf den Umstieg von Auto zu anderem Auto gesprochen wird. Öffis werden nicht erwähnt, Carsharing nicht, radeln nicht, und in der Stadt wäre ne Vespa auch deutlich besser als ein SUV. - http://activitystrea.ms/schema/1.0/favorite - 2017-08-23T13:18:19+00:00 - 2017-08-23T13:18:19+00:00 - - http://activitystrea.ms/schema/1.0/note - tag:quitter.is,2017-08-23:noticeId=4032910:objectType=note - New note by einebiene - Haha, große Überraschung. <a href="http://www.sueddeutsche.de/wirtschaft/abgasaffaere-software-updates-fuer-dieselautos-helfen-kaum-1.3637636" title="http://www.sueddeutsche.de/wirtschaft/abgasaffaere-software-updates-fuer-dieselautos-helfen-kaum-1.3637636" rel="nofollow noreferrer" class="attachment">https://quitter.is/url/1122672</a><br /> Was ich an all diesen Artikeln schade finde, ist, daß immer nur auf den Umstieg von Auto zu anderem Auto gesprochen wird. Öffis werden nicht erwähnt, Carsharing nicht, radeln nicht, und in der Stadt wäre ne Vespa auch deutlich besser als ein SUV. - - - - - - - https://quitter.is/conversation/2535246 - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.stopwatchingus-heidelberg.de,2017-08-23:noticeId=978402:objectType=note - New note by atarifrosch - moin-quak - - - http://activitystrea.ms/schema/1.0/post - 2017-08-23T10:57:26+00:00 - 2017-08-23T10:57:26+00:00 - - tag:social.stopwatchingus-heidelberg.de,2017-08-23:noticeId=978402:objectType=thread:crc32=7050c397 - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.stopwatchingus-heidelberg.de,2017-08-22:noticeId=978164:objectType=note - New note by atarifrosch - n8-quak - - - http://activitystrea.ms/schema/1.0/post - 2017-08-22T19:54:30+00:00 - 2017-08-22T19:54:30+00:00 - - tag:social.stopwatchingus-heidelberg.de,2017-08-22:noticeId=978164:objectType=thread:crc32=b0a209c7 - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.stopwatchingus-heidelberg.de,2017-08-22:noticeId=978072:objectType=note - New note by atarifrosch - 2017-08-22 Bundesverfassungsgericht: Erfolgreiche Verfassungsbeschwerde gegen die Versagung vorläufiger Leistungen für Kosten der Unterkunft und Heizung – <a href="https://www.bundesverfassungsgericht.de/SharedDocs/Pressemitteilungen/DE/2017/bvg17-072.html" title="https://www.bundesverfassungsgericht.de/SharedDocs/Pressemitteilungen/DE/2017/bvg17-072.html" class="attachment" id="attachment-450768" rel="nofollow external">https://www.bundesverfassungsgericht.de/SharedDocs/Pressemitteilungen/DE/2017/bvg17-072.html</a> !<a href="http://quitter.se/group/2184/id" class="h-card group" title="HartzIV (hartziv)">hartziv</a> - - - http://activitystrea.ms/schema/1.0/post - 2017-08-22T12:00:21+00:00 - 2017-08-22T12:00:21+00:00 - - tag:social.stopwatchingus-heidelberg.de,2017-08-22:noticeId=978072:objectType=thread:crc32=28a35f44 - - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.stopwatchingus-heidelberg.de,2017-08-22:noticeId=978042:objectType=note - New note by atarifrosch - moin-quak! - - - http://activitystrea.ms/schema/1.0/post - 2017-08-22T07:55:27+00:00 - 2017-08-22T07:55:27+00:00 - - tag:social.stopwatchingus-heidelberg.de,2017-08-22:noticeId=978042:objectType=thread:crc32=f070a9f7 - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.stopwatchingus-heidelberg.de,2017-08-21:noticeId=977914:objectType=note - New note by atarifrosch - So, morgen geht's weiter. n8-quak! - - - http://activitystrea.ms/schema/1.0/post - 2017-08-21T22:09:53+00:00 - 2017-08-21T22:09:53+00:00 - - tag:social.stopwatchingus-heidelberg.de,2017-08-21:noticeId=977914:objectType=thread:crc32=c0a9f7fa - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.stopwatchingus-heidelberg.de,2017-08-21:noticeId=977710:objectType=note - New note by atarifrosch - moin-quak. - - - http://activitystrea.ms/schema/1.0/post - 2017-08-21T08:58:26+00:00 - 2017-08-21T08:58:26+00:00 - - tag:social.stopwatchingus-heidelberg.de,2017-08-21:noticeId=977710:objectType=thread:crc32=60cfb466 - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.stopwatchingus-heidelberg.de,2017-08-20:noticeId=977526:objectType=note - New note by atarifrosch - Meine Augen meinen, für heute sei es genug. Nun denn. n8-quak. - - - http://activitystrea.ms/schema/1.0/post - 2017-08-20T19:58:16+00:00 - 2017-08-20T19:58:16+00:00 - - tag:social.stopwatchingus-heidelberg.de,2017-08-20:noticeId=977526:objectType=thread:crc32=ce79634 - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.stopwatchingus-heidelberg.de,2017-08-20:noticeId=977369:objectType=note - New note by atarifrosch - [Blog] Im Netz aufgefischt #<span class="tag"><a href="https://social.stopwatchingus-heidelberg.de/tag/330" rel="tag">330</a></span> – <a href="https://blog.atari-frosch.de/2017/08/20/im-netz-aufgefischt-330/" title="https://blog.atari-frosch.de/2017/08/20/im-netz-aufgefischt-330/" class="attachment" id="attachment-450668" rel="nofollow external">https://blog.atari-frosch.de/2017/08/20/im-netz-aufgefischt-330/</a> (was ich diese Woche so gelesen habe) - - - http://activitystrea.ms/schema/1.0/post - 2017-08-20T09:14:07+00:00 - 2017-08-20T09:14:07+00:00 - - tag:social.stopwatchingus-heidelberg.de,2017-08-20:noticeId=977369:objectType=thread:crc32=2f800b86 - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.stopwatchingus-heidelberg.de,2017-08-19:noticeId=977268:objectType=note - New note by atarifrosch - Fast ständig husten müssen ist echt anstrengend … naja, n8-quak. - - - http://activitystrea.ms/schema/1.0/post - 2017-08-19T21:59:20+00:00 - 2017-08-19T21:59:20+00:00 - - tag:social.stopwatchingus-heidelberg.de,2017-08-19:noticeId=977268:objectType=thread:crc32=deda767a - - - - - - - tag:social.stopwatchingus-heidelberg.de,2017-08-19:fave:18330:activity:977146:2017-08-19T21:39:26+02:00 - Favorite - atarifrosch favorited something by einebienezwo: Ich mach gerade Kompetenztraining.<br /> Ich trainiere die Kompetenz, eine halb aufgegessene Gummibärchentüte nicht ganz aufzuessen. - http://activitystrea.ms/schema/1.0/favorite - 2017-08-19T19:39:26+00:00 - 2017-08-19T19:39:26+00:00 - - http://activitystrea.ms/schema/1.0/note - tag:gnusocial.de,2017-08-19:noticeId=11011264:objectType=note - New note by einebienezwo - Ich mach gerade Kompetenztraining.<br /> Ich trainiere die Kompetenz, eine halb aufgegessene Gummibärchentüte nicht ganz aufzuessen. - - - - - - - https://gnusocial.de/conversation/9363945 - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:social.stopwatchingus-heidelberg.de,2017-08-19:noticeId=977242:objectType=comment - New comment by atarifrosch - Wir hatten hier schon Ordnungsdienst auf'm Radweg. Fotografisch dokumentiert (nicht von mir, Bekannter hatte es gesehen). Da hatte grad 'ne Pizzeria neu eröffnet … - - - http://activitystrea.ms/schema/1.0/post - 2017-08-19T19:38:53+00:00 - 2017-08-19T19:38:53+00:00 - - - - https://gnusocial.de/conversation/9363813 - - - - - - - - tag:social.stopwatchingus-heidelberg.de,2017-08-19:fave:18330:activity:977180:2017-08-19T21:37:36+02:00 - Favorite - atarifrosch favorited something by jcaktiv: BTW Hallo zusammen &lt;3, wo ich schon mal wieder hier bin - http://activitystrea.ms/schema/1.0/favorite - 2017-08-19T19:37:36+00:00 - 2017-08-19T19:37:36+00:00 - - http://activitystrea.ms/schema/1.0/note - tag:quitter.se,2017-08-19:noticeId=17372467:objectType=note - New note by jcaktiv - BTW Hallo zusammen &lt;3, wo ich schon mal wieder hier bin - - - - - - - tag:quitter.se,2017-08-19:objectType=thread:nonce=46c1c433d88aaa9f - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:social.stopwatchingus-heidelberg.de,2017-08-19:noticeId=976985:objectType=comment - New comment by atarifrosch - Jo, oder einfach mal nachfragen, so als Realitätsabgleich. - - - http://activitystrea.ms/schema/1.0/post - 2017-08-19T10:34:50+00:00 - 2017-08-19T10:34:50+00:00 - - - - https://gnusocial.de/conversation/9362516 - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.stopwatchingus-heidelberg.de,2017-08-19:noticeId=976983:objectType=note - New note by atarifrosch - Schöne Alternative zu mit Werbung überladenen kommerziellen Anbietern: <a href="http://ifconfig.at/" title="http://ifconfig.at/" class="attachment" id="attachment-450636" rel="nofollow external">http://ifconfig.at/</a> – eigene IP, Hostname etc. abfragen, mit curl dann auch in Textform zur lokalen Weiterverarbeitung in Scripten etc. Leider (noch?) kein https. - - - http://activitystrea.ms/schema/1.0/post - 2017-08-19T10:33:04+00:00 - 2017-08-19T10:33:04+00:00 - - tag:social.stopwatchingus-heidelberg.de,2017-08-19:noticeId=976983:objectType=thread:crc32=4a3593c0 - - - - - - diff --git a/test/fixtures/tesla_mock/emelie.atom b/test/fixtures/tesla_mock/emelie.atom deleted file mode 100644 index ddaa1c6ca..000000000 --- a/test/fixtures/tesla_mock/emelie.atom +++ /dev/null @@ -1,306 +0,0 @@ - - - https://mastodon.social/users/emelie.atom - emelie 🎨 - 23 / #Sweden / #Artist / #Equestrian / #GameDev - -If I ain't spending time with my pets, I'm probably drawing. 🐴 🐱 🐰 - 2019-02-04T20:22:19Z - https://files.mastodon.social/accounts/avatars/000/015/657/original/e7163f98280da1a4.png - - https://mastodon.social/users/emelie - http://activitystrea.ms/schema/1.0/person - https://mastodon.social/users/emelie - emelie - emelie@mastodon.social - <p>23 / <a href="https://mastodon.social/tags/sweden" class="mention hashtag" rel="tag">#<span>Sweden</span></a> / <a href="https://mastodon.social/tags/artist" class="mention hashtag" rel="tag">#<span>Artist</span></a> / <a href="https://mastodon.social/tags/equestrian" class="mention hashtag" rel="tag">#<span>Equestrian</span></a> / <a href="https://mastodon.social/tags/gamedev" class="mention hashtag" rel="tag">#<span>GameDev</span></a></p><p>If I ain&apos;t spending time with my pets, I&apos;m probably drawing. 🐴 🐱 🐰</p> - - - - emelie - emelie 🎨 - 23 / #Sweden / #Artist / #Equestrian / #GameDev - -If I ain't spending time with my pets, I'm probably drawing. 🐴 🐱 🐰 - public - - - - - - - https://mastodon.social/users/emelie/statuses/101850331907006641 - 2019-04-01T09:58:50Z - 2019-04-01T09:58:50Z - New status by emelie - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - - <p>Me: I&apos;m going to make this vital change to my world building in the morning, no way I&apos;ll forget this, it&apos;s too big of a deal<br />Also me: forgets</p> - - public - - - - - - https://mastodon.social/users/emelie/statuses/101849626603073336 - 2019-04-01T06:59:28Z - 2019-04-01T06:59:28Z - New status by emelie - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - - <p><span class="h-card"><a href="https://mastodon.social/@Fergant" class="u-url mention">@<span>Fergant</span></a></span> Dom är i stort sett religiös skrift vid det här laget 👏👏</p><p>har dock bara läst svenska översättningen, kanske är dags att jag läser dom på engelska</p> - - - public - - - - - - - https://mastodon.social/users/emelie/statuses/101849580030237068 - 2019-04-01T06:47:37Z - 2019-04-01T06:47:37Z - New status by emelie - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - - <p>What&apos;s you people&apos;s favourite fantasy books? Give me some hot tips 🌞</p> - - public - - - - - - https://mastodon.social/users/emelie/statuses/101849550599949363 - 2019-04-01T06:40:08Z - 2019-04-01T06:40:08Z - New status by emelie - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - - <p>Stick them legs out 💃 <a href="https://mastodon.social/tags/mastocats" class="mention hashtag" rel="tag">#<span>mastocats</span></a></p> - - - - public - - - - - - https://mastodon.social/users/emelie/statuses/101849191533152720 - 2019-04-01T05:08:49Z - 2019-04-01T05:08:49Z - New status by emelie - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - - <p>long 🐱 <a href="https://mastodon.social/tags/mastocats" class="mention hashtag" rel="tag">#<span>mastocats</span></a></p> - - - - public - - - - - - https://mastodon.social/users/emelie/statuses/101849165031453009 - 2019-04-01T05:02:05Z - 2019-04-01T05:02:05Z - New status by emelie - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - - <p>You gotta take whatever bellyrubbing opportunity you can get before she changes her mind 🦁 <a href="https://mastodon.social/tags/mastocats" class="mention hashtag" rel="tag">#<span>mastocats</span></a></p> - - - - public - - - - - - https://mastodon.social/users/emelie/statuses/101846512530748693 - 2019-03-31T17:47:31Z - 2019-03-31T17:47:31Z - New status by emelie - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - - <p>Hello look at this boy having a decent haircut for once <a href="https://mastodon.social/tags/mastohorses" class="mention hashtag" rel="tag">#<span>mastohorses</span></a> <a href="https://mastodon.social/tags/equestrian" class="mention hashtag" rel="tag">#<span>equestrian</span></a></p> - - - - - public - - - - - - https://mastodon.social/users/emelie/statuses/101846181093805500 - 2019-03-31T16:23:14Z - 2019-03-31T16:23:14Z - New status by emelie - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - - <p>Sorry did I disturb the who-is-the-longest-cat competition ? <a href="https://mastodon.social/tags/mastocats" class="mention hashtag" rel="tag">#<span>mastocats</span></a></p> - - - - public - - - - - - https://mastodon.social/users/emelie/statuses/101845897513133849 - 2019-03-31T15:11:07Z - 2019-03-31T15:11:07Z - New status by emelie - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - - more earthsea ramblings - <p>I&apos;m re-watching Tales from Earthsea for the first time since I read the books, and that Therru doesn&apos;t squash Cob like a spider, as Orm Embar did is a wasted opportunity tbh</p> - - public - - - - - - https://mastodon.social/users/emelie/statuses/101841219051533307 - 2019-03-30T19:21:19Z - 2019-03-30T19:21:19Z - New status by emelie - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - - <p>I gave my cats some mackerel and they ate it all in 0.3 seconds, and now they won&apos;t stop meowing for more, and I&apos;m tired plz shut up</p> - - public - - - - - - https://mastodon.social/users/emelie/statuses/101839949762341381 - 2019-03-30T13:58:31Z - 2019-03-30T13:58:31Z - New status by emelie - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - - <p>yet I&apos;m confused about this american dude with a gun, like the heck r ya doin in mah ghibli</p> - - public - - - - - - - https://mastodon.social/users/emelie/statuses/101839928677863590 - 2019-03-30T13:53:09Z - 2019-03-30T13:53:09Z - New status by emelie - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - - <p>2 hours into Ni no Kuni 2 and I&apos;ve already sold my soul to this game</p> - - public - - - - - - https://mastodon.social/users/emelie/statuses/101836329521599438 - 2019-03-29T22:37:51Z - 2019-03-29T22:37:51Z - New status by emelie - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - - <p>Pippi Longstocking the original one-punch /man</p> - - public - - - - - - https://mastodon.social/users/emelie/statuses/101835905282948341 - 2019-03-29T20:49:57Z - 2019-03-29T20:49:57Z - New status by emelie - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - - <p>I&apos;ve had so much wine I thought I had a 3rd brother</p> - - public - - - - - - https://mastodon.social/users/emelie/statuses/101835878059204660 - 2019-03-29T20:43:02Z - 2019-03-29T20:43:02Z - New status by emelie - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - - <p>ååååhhh booi</p> - - public - - - - - - https://mastodon.social/users/emelie/statuses/101835848050598939 - 2019-03-29T20:35:24Z - 2019-03-29T20:35:24Z - New status by emelie - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - - <p><span class="h-card"><a href="https://thraeryn.net/@thraeryn" class="u-url mention">@<span>thraeryn</span></a></span> if I spent 1 hour and a half watching this monstrosity, I need to</p> - - - public - - - - - - - https://mastodon.social/users/emelie/statuses/101835823138262290 - 2019-03-29T20:29:04Z - 2019-03-29T20:29:04Z - New status by emelie - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - - medical, fluids mention - <p><span class="h-card"><a href="https://icosahedron.website/@Trev" class="u-url mention">@<span>Trev</span></a></span> *hugs* ✨</p> - - - public - - - - - - diff --git a/test/fixtures/tesla_mock/framatube.org-video.json b/test/fixtures/tesla_mock/framatube.org-video.json index 3d53f0c97..1fa529886 100644 --- a/test/fixtures/tesla_mock/framatube.org-video.json +++ b/test/fixtures/tesla_mock/framatube.org-video.json @@ -1 +1 @@ -{"type":"Video","id":"https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206","name":"Déframasoftisons Internet [Framasoft]","duration":"PT3622S","uuid":"6050732a-8a7a-43d4-a6cd-809525a1d206","tag":[{"type":"Hashtag","name":"déframasoftisons"},{"type":"Hashtag","name":"EPN23"},{"type":"Hashtag","name":"framaconf"},{"type":"Hashtag","name":"Framasoft"},{"type":"Hashtag","name":"pyg"}],"category":{"identifier":"15","name":"Science & Technology"},"views":122,"sensitive":false,"waitTranscoding":false,"state":1,"commentsEnabled":true,"downloadEnabled":true,"published":"2020-05-24T18:34:31.569Z","originallyPublishedAt":"2019-11-30T23:00:00.000Z","updated":"2020-07-05T09:01:01.720Z","mediaType":"text/markdown","content":"Après avoir mené avec un certain succès la campagne « Dégooglisons Internet » en 2014, l’association Framasoft annonce fin 2019 arrêter progressivement un certain nombre de ses services alternatifs aux GAFAM. Pourquoi ?\r\n\r\nTranscription par @april...","support":null,"subtitleLanguage":[],"icon":{"type":"Image","url":"https://framatube.org/static/thumbnails/6050732a-8a7a-43d4-a6cd-809525a1d206.jpg","mediaType":"image/jpeg","width":223,"height":122},"url":[{"type":"Link","mediaType":"text/html","href":"https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206"},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4","height":1080,"size":1157359410,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309939","height":1080,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-1080.torrent","height":1080},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-1080.torrent&xt=urn:btih:381c9429900552e23a4eb506318f1fa01e4d63a8&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fwebseed%2F6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4&ws=https%3A%2F%2Fpeertube.iselfhost.com%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4&ws=https%3A%2F%2Ftube.privacytools.io%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4&ws=https%3A%2F%2Fpeertube.live%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4","height":1080},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-480.mp4","height":480,"size":250095131,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309941","height":480,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-480.torrent","height":480},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-480.torrent&xt=urn:btih:a181dcbb5368ab5c31cc9ff07634becb72c344ee&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fwebseed%2F6050732a-8a7a-43d4-a6cd-809525a1d206-480.mp4&ws=https%3A%2F%2Fpeertube.iselfhost.com%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-480.mp4&ws=https%3A%2F%2Ftube.privacytools.io%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-480.mp4&ws=https%3A%2F%2Fpeertube.live%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-480.mp4","height":480},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-360.mp4","height":360,"size":171357733,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309942","height":360,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-360.torrent","height":360},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-360.torrent&xt=urn:btih:aedfa9479ea04a175eee0b0bd0bda64076308746&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fwebseed%2F6050732a-8a7a-43d4-a6cd-809525a1d206-360.mp4&ws=https%3A%2F%2Fpeertube.iselfhost.com%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-360.mp4&ws=https%3A%2F%2Ftube.privacytools.io%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-360.mp4&ws=https%3A%2F%2Fpeertube.live%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-360.mp4","height":360},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-720.mp4","height":720,"size":497100839,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309943","height":720,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-720.torrent","height":720},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-720.torrent&xt=urn:btih:71971668f82a3b24ac71bc3a982848dd8dc5a5f5&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fwebseed%2F6050732a-8a7a-43d4-a6cd-809525a1d206-720.mp4&ws=https%3A%2F%2Fpeertube.iselfhost.com%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-720.mp4&ws=https%3A%2F%2Ftube.privacytools.io%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-720.mp4&ws=https%3A%2F%2Fpeertube.live%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-720.mp4","height":720},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-240.mp4","height":240,"size":113038439,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309944","height":240,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-240.torrent","height":240},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-240.torrent&xt=urn:btih:c42aa6c95efb28d9f114ebd98537f7b00fa72246&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fwebseed%2F6050732a-8a7a-43d4-a6cd-809525a1d206-240.mp4&ws=https%3A%2F%2Fpeertube.iselfhost.com%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-240.mp4&ws=https%3A%2F%2Ftube.privacytools.io%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-240.mp4","height":240},{"type":"Link","mediaType":"application/x-mpegURL","href":"https://framatube.org/static/streaming-playlists/hls/6050732a-8a7a-43d4-a6cd-809525a1d206/master.m3u8","tag":[{"type":"Infohash","name":"f7428214539626e062f300f2ca4cf9154575144e"},{"type":"Infohash","name":"46e236dffb1ea6b9123a5396cbe88e97dd94cc6c"},{"type":"Infohash","name":"11f1045830b5d786c788f2594d19f128764e7d87"},{"type":"Infohash","name":"4327ad3e0d84de100130a27e9ab6fe40c4284f0e"},{"type":"Infohash","name":"41e2eee8e7b23a63c23a77c40a46de11492a4831"},{"type":"Link","name":"sha256","mediaType":"application/json","href":"https://framatube.org/static/streaming-playlists/hls/6050732a-8a7a-43d4-a6cd-809525a1d206/segments-sha256.json"},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/streaming-playlists/hls/6050732a-8a7a-43d4-a6cd-809525a1d206/6050732a-8a7a-43d4-a6cd-809525a1d206-1080-fragmented.mp4","height":1080,"size":1156777472,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309940","height":1080,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-1080-hls.torrent","height":1080},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-1080-hls.torrent&xt=urn:btih:0204d780ebfab0d5d9d3476a038e812ad792deeb&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fstreaming-playlists%2Fhls%2F6050732a-8a7a-43d4-a6cd-809525a1d206%2F6050732a-8a7a-43d4-a6cd-809525a1d206-1080-fragmented.mp4","height":1080},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/streaming-playlists/hls/6050732a-8a7a-43d4-a6cd-809525a1d206/6050732a-8a7a-43d4-a6cd-809525a1d206-480-fragmented.mp4","height":480,"size":249562889,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309945","height":480,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-480-hls.torrent","height":480},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-480-hls.torrent&xt=urn:btih:5d14f38ded29de629668fe1cfc61a75f4cce2628&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fstreaming-playlists%2Fhls%2F6050732a-8a7a-43d4-a6cd-809525a1d206%2F6050732a-8a7a-43d4-a6cd-809525a1d206-480-fragmented.mp4","height":480},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/streaming-playlists/hls/6050732a-8a7a-43d4-a6cd-809525a1d206/6050732a-8a7a-43d4-a6cd-809525a1d206-360-fragmented.mp4","height":360,"size":170836415,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309946","height":360,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-360-hls.torrent","height":360},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-360-hls.torrent&xt=urn:btih:30125488789080ad405ebcee6c214945f31b8f30&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fstreaming-playlists%2Fhls%2F6050732a-8a7a-43d4-a6cd-809525a1d206%2F6050732a-8a7a-43d4-a6cd-809525a1d206-360-fragmented.mp4","height":360},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/streaming-playlists/hls/6050732a-8a7a-43d4-a6cd-809525a1d206/6050732a-8a7a-43d4-a6cd-809525a1d206-720-fragmented.mp4","height":720,"size":496533741,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309947","height":720,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-720-hls.torrent","height":720},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-720-hls.torrent&xt=urn:btih:8ed1e8bccde709901c26e315fc8f53bfd26d1ba6&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fstreaming-playlists%2Fhls%2F6050732a-8a7a-43d4-a6cd-809525a1d206%2F6050732a-8a7a-43d4-a6cd-809525a1d206-720-fragmented.mp4","height":720},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/streaming-playlists/hls/6050732a-8a7a-43d4-a6cd-809525a1d206/6050732a-8a7a-43d4-a6cd-809525a1d206-240-fragmented.mp4","height":240,"size":112529249,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309948","height":240,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-240-hls.torrent","height":240},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-240-hls.torrent&xt=urn:btih:8b452bf4e70b9078d4e74ca8b5523cc9dc70d10a&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fstreaming-playlists%2Fhls%2F6050732a-8a7a-43d4-a6cd-809525a1d206%2F6050732a-8a7a-43d4-a6cd-809525a1d206-240-fragmented.mp4","height":240}]}],"likes":"https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206/likes","dislikes":"https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206/dislikes","shares":"https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206/announces","comments":"https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206/comments","attributedTo":[{"type":"Person","id":"https://framatube.org/accounts/framasoft"},{"type":"Group","id":"https://framatube.org/video-channels/bf54d359-cfad-4935-9d45-9d6be93f63e8"}],"to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://framatube.org/accounts/framasoft/followers"],"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"RsaSignature2017":"https://w3id.org/security#RsaSignature2017"},{"pt":"https://joinpeertube.org/ns#","sc":"http://schema.org#","Hashtag":"as:Hashtag","uuid":"sc:identifier","category":"sc:category","licence":"sc:license","subtitleLanguage":"sc:subtitleLanguage","sensitive":"as:sensitive","language":"sc:inLanguage","Infohash":"pt:Infohash","Playlist":"pt:Playlist","PlaylistElement":"pt:PlaylistElement","originallyPublishedAt":"sc:datePublished","views":{"@type":"sc:Number","@id":"pt:views"},"state":{"@type":"sc:Number","@id":"pt:state"},"size":{"@type":"sc:Number","@id":"pt:size"},"fps":{"@type":"sc:Number","@id":"pt:fps"},"startTimestamp":{"@type":"sc:Number","@id":"pt:startTimestamp"},"stopTimestamp":{"@type":"sc:Number","@id":"pt:stopTimestamp"},"position":{"@type":"sc:Number","@id":"pt:position"},"commentsEnabled":{"@type":"sc:Boolean","@id":"pt:commentsEnabled"},"downloadEnabled":{"@type":"sc:Boolean","@id":"pt:downloadEnabled"},"waitTranscoding":{"@type":"sc:Boolean","@id":"pt:waitTranscoding"},"support":{"@type":"sc:Text","@id":"pt:support"},"likes":{"@id":"as:likes","@type":"@id"},"dislikes":{"@id":"as:dislikes","@type":"@id"},"playlists":{"@id":"pt:playlists","@type":"@id"},"shares":{"@id":"as:shares","@type":"@id"},"comments":{"@id":"as:comments","@type":"@id"}}]} \ No newline at end of file +{"type":"Create","id":"https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206/activity","actor":"https://framatube.org/accounts/framasoft","object":{"type":"Video","id":"https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206","name":"Déframasoftisons Internet [Framasoft]","duration":"PT3622S","uuid":"6050732a-8a7a-43d4-a6cd-809525a1d206","tag":[{"type":"Hashtag","name":"déframasoftisons"},{"type":"Hashtag","name":"EPN23"},{"type":"Hashtag","name":"framaconf"},{"type":"Hashtag","name":"Framasoft"},{"type":"Hashtag","name":"pyg"}],"category":{"identifier":"15","name":"Science & Technology"},"views":154,"sensitive":false,"waitTranscoding":false,"state":1,"commentsEnabled":true,"downloadEnabled":true,"published":"2020-05-24T18:34:31.569Z","originallyPublishedAt":"2019-11-30T23:00:00.000Z","updated":"2020-08-17T11:01:02.994Z","mediaType":"text/markdown","content":"Après avoir mené avec un certain succès la campagne « Dégooglisons Internet » en 2014, l’association Framasoft annonce fin 2019 arrêter progressivement un certain nombre de ses services alternatifs aux GAFAM. Pourquoi ?\r\n\r\nTranscription par @aprilorg ici : https://www.april.org/deframasoftisons-internet-pierre-yves-gosset-framasoft","support":null,"subtitleLanguage":[],"icon":[{"type":"Image","url":"https://framatube.org/static/thumbnails/6050732a-8a7a-43d4-a6cd-809525a1d206.jpg","mediaType":"image/jpeg","width":223,"height":122},{"type":"Image","url":"https://framatube.org/static/previews/6050732a-8a7a-43d4-a6cd-809525a1d206.jpg","mediaType":"image/jpeg","width":850,"height":480}],"url":[{"type":"Link","mediaType":"text/html","href":"https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206"},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4","height":1080,"size":1157359410,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309939","height":1080,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-1080.torrent","height":1080},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-1080.torrent&xt=urn:btih:381c9429900552e23a4eb506318f1fa01e4d63a8&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fwebseed%2F6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4&ws=https%3A%2F%2Fpeertube.iselfhost.com%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4&ws=https%3A%2F%2Ftube.privacytools.io%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4&ws=https%3A%2F%2Fpeertube.live%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4","height":1080},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-720.mp4","height":720,"size":497100839,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309943","height":720,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-720.torrent","height":720},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-720.torrent&xt=urn:btih:71971668f82a3b24ac71bc3a982848dd8dc5a5f5&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fwebseed%2F6050732a-8a7a-43d4-a6cd-809525a1d206-720.mp4&ws=https%3A%2F%2Ftube.privacytools.io%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-720.mp4&ws=https%3A%2F%2Fpeertube.iselfhost.com%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-720.mp4&ws=https%3A%2F%2Fpeertube.live%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-720.mp4","height":720},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-480.mp4","height":480,"size":250095131,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309941","height":480,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-480.torrent","height":480},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-480.torrent&xt=urn:btih:a181dcbb5368ab5c31cc9ff07634becb72c344ee&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fwebseed%2F6050732a-8a7a-43d4-a6cd-809525a1d206-480.mp4&ws=https%3A%2F%2Fpeertube.iselfhost.com%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-480.mp4&ws=https%3A%2F%2Ftube.privacytools.io%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-480.mp4&ws=https%3A%2F%2Fpeertube.live%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-480.mp4","height":480},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-360.mp4","height":360,"size":171357733,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309942","height":360,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-360.torrent","height":360},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-360.torrent&xt=urn:btih:aedfa9479ea04a175eee0b0bd0bda64076308746&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fwebseed%2F6050732a-8a7a-43d4-a6cd-809525a1d206-360.mp4&ws=https%3A%2F%2Fpeertube.iselfhost.com%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-360.mp4&ws=https%3A%2F%2Ftube.privacytools.io%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-360.mp4&ws=https%3A%2F%2Fpeertube.live%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-360.mp4","height":360},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-240.mp4","height":240,"size":113038439,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309944","height":240,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-240.torrent","height":240},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-240.torrent&xt=urn:btih:c42aa6c95efb28d9f114ebd98537f7b00fa72246&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fwebseed%2F6050732a-8a7a-43d4-a6cd-809525a1d206-240.mp4&ws=https%3A%2F%2Fpeertube.iselfhost.com%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-240.mp4&ws=https%3A%2F%2Ftube.privacytools.io%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-240.mp4","height":240},{"type":"Link","mediaType":"application/x-mpegURL","href":"https://framatube.org/static/streaming-playlists/hls/6050732a-8a7a-43d4-a6cd-809525a1d206/master.m3u8","tag":[{"type":"Infohash","name":"f7428214539626e062f300f2ca4cf9154575144e"},{"type":"Infohash","name":"46e236dffb1ea6b9123a5396cbe88e97dd94cc6c"},{"type":"Infohash","name":"11f1045830b5d786c788f2594d19f128764e7d87"},{"type":"Infohash","name":"4327ad3e0d84de100130a27e9ab6fe40c4284f0e"},{"type":"Infohash","name":"41e2eee8e7b23a63c23a77c40a46de11492a4831"},{"type":"Link","name":"sha256","mediaType":"application/json","href":"https://framatube.org/static/streaming-playlists/hls/6050732a-8a7a-43d4-a6cd-809525a1d206/segments-sha256.json"},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/streaming-playlists/hls/6050732a-8a7a-43d4-a6cd-809525a1d206/6050732a-8a7a-43d4-a6cd-809525a1d206-1080-fragmented.mp4","height":1080,"size":1156777472,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309940","height":1080,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-1080-hls.torrent","height":1080},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-1080-hls.torrent&xt=urn:btih:0204d780ebfab0d5d9d3476a038e812ad792deeb&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fstreaming-playlists%2Fhls%2F6050732a-8a7a-43d4-a6cd-809525a1d206%2F6050732a-8a7a-43d4-a6cd-809525a1d206-1080-fragmented.mp4","height":1080},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/streaming-playlists/hls/6050732a-8a7a-43d4-a6cd-809525a1d206/6050732a-8a7a-43d4-a6cd-809525a1d206-720-fragmented.mp4","height":720,"size":496533741,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309947","height":720,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-720-hls.torrent","height":720},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-720-hls.torrent&xt=urn:btih:8ed1e8bccde709901c26e315fc8f53bfd26d1ba6&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fstreaming-playlists%2Fhls%2F6050732a-8a7a-43d4-a6cd-809525a1d206%2F6050732a-8a7a-43d4-a6cd-809525a1d206-720-fragmented.mp4","height":720},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/streaming-playlists/hls/6050732a-8a7a-43d4-a6cd-809525a1d206/6050732a-8a7a-43d4-a6cd-809525a1d206-480-fragmented.mp4","height":480,"size":249562889,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309945","height":480,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-480-hls.torrent","height":480},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-480-hls.torrent&xt=urn:btih:5d14f38ded29de629668fe1cfc61a75f4cce2628&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fstreaming-playlists%2Fhls%2F6050732a-8a7a-43d4-a6cd-809525a1d206%2F6050732a-8a7a-43d4-a6cd-809525a1d206-480-fragmented.mp4","height":480},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/streaming-playlists/hls/6050732a-8a7a-43d4-a6cd-809525a1d206/6050732a-8a7a-43d4-a6cd-809525a1d206-360-fragmented.mp4","height":360,"size":170836415,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309946","height":360,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-360-hls.torrent","height":360},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-360-hls.torrent&xt=urn:btih:30125488789080ad405ebcee6c214945f31b8f30&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fstreaming-playlists%2Fhls%2F6050732a-8a7a-43d4-a6cd-809525a1d206%2F6050732a-8a7a-43d4-a6cd-809525a1d206-360-fragmented.mp4","height":360},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/streaming-playlists/hls/6050732a-8a7a-43d4-a6cd-809525a1d206/6050732a-8a7a-43d4-a6cd-809525a1d206-240-fragmented.mp4","height":240,"size":112529249,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309948","height":240,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-240-hls.torrent","height":240},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-240-hls.torrent&xt=urn:btih:8b452bf4e70b9078d4e74ca8b5523cc9dc70d10a&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fstreaming-playlists%2Fhls%2F6050732a-8a7a-43d4-a6cd-809525a1d206%2F6050732a-8a7a-43d4-a6cd-809525a1d206-240-fragmented.mp4","height":240}]}],"likes":"https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206/likes","dislikes":"https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206/dislikes","shares":"https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206/announces","comments":"https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206/comments","attributedTo":[{"type":"Person","id":"https://framatube.org/accounts/framasoft"},{"type":"Group","id":"https://framatube.org/video-channels/bf54d359-cfad-4935-9d45-9d6be93f63e8"}],"to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://framatube.org/accounts/framasoft/followers"]},"to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://framatube.org/accounts/framasoft/followers"],"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"RsaSignature2017":"https://w3id.org/security#RsaSignature2017"},{"pt":"https://joinpeertube.org/ns#","sc":"http://schema.org#","Hashtag":"as:Hashtag","uuid":"sc:identifier","category":"sc:category","licence":"sc:license","subtitleLanguage":"sc:subtitleLanguage","sensitive":"as:sensitive","language":"sc:inLanguage","Infohash":"pt:Infohash","Playlist":"pt:Playlist","PlaylistElement":"pt:PlaylistElement","originallyPublishedAt":"sc:datePublished","views":{"@type":"sc:Number","@id":"pt:views"},"state":{"@type":"sc:Number","@id":"pt:state"},"size":{"@type":"sc:Number","@id":"pt:size"},"fps":{"@type":"sc:Number","@id":"pt:fps"},"startTimestamp":{"@type":"sc:Number","@id":"pt:startTimestamp"},"stopTimestamp":{"@type":"sc:Number","@id":"pt:stopTimestamp"},"position":{"@type":"sc:Number","@id":"pt:position"},"commentsEnabled":{"@type":"sc:Boolean","@id":"pt:commentsEnabled"},"downloadEnabled":{"@type":"sc:Boolean","@id":"pt:downloadEnabled"},"waitTranscoding":{"@type":"sc:Boolean","@id":"pt:waitTranscoding"},"support":{"@type":"sc:Text","@id":"pt:support"},"likes":{"@id":"as:likes","@type":"@id"},"dislikes":{"@id":"as:dislikes","@type":"@id"},"playlists":{"@id":"pt:playlists","@type":"@id"},"shares":{"@id":"as:shares","@type":"@id"},"comments":{"@id":"as:comments","@type":"@id"}}]} \ No newline at end of file diff --git a/test/fixtures/tesla_mock/http__gs.example.org_index.php_api_statuses_user_timeline_1.atom.xml b/test/fixtures/tesla_mock/http__gs.example.org_index.php_api_statuses_user_timeline_1.atom.xml deleted file mode 100644 index 490467708..000000000 --- a/test/fixtures/tesla_mock/http__gs.example.org_index.php_api_statuses_user_timeline_1.atom.xml +++ /dev/null @@ -1,460 +0,0 @@ - - - GNU social - http://gs.example.org/index.php/api/statuses/user_timeline/1.atom - lambda timeline - Updates from lambda on gs.example.org! - http://gs.example.org/theme/neo-gnu/default-avatar-profile.png - 2017-05-05T12:09:57+00:00 - - http://activitystrea.ms/schema/1.0/person - http://gs.example.org:4040/index.php/user/1 - lambda - - - - - lambda - lambda - - - - - - - - - - - - - tag:gs.example.org,2017-05-04:noticeId=84:objectType=note - lambda repeated a notice by lambda2 - RT @<a href="http://gs.example.org/index.php/user/7" class="h-card mention">lambda2</a> Hello! - - http://activitystrea.ms/schema/1.0/share - 2017-05-04T16:38:50+00:00 - 2017-05-04T16:38:50+00:00 - - http://activitystrea.ms/schema/1.0/activity - tag:gs.example.org,2017-05-01:noticeId=67:objectType=note - - Hello! - - http://activitystrea.ms/schema/1.0/post - 2017-05-01T08:41:04+00:00 - 2017-05-01T08:41:04+00:00 - - http://activitystrea.ms/schema/1.0/person - http://gs.example.org/index.php/user/7 - lambda2 - - - - - - lambda2 - lambda2 - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org,2017-05-01:noticeId=67:objectType=note - New note by lambda2 - Hello! - - - - - tag:gs.example.org,2017-05-01:objectType=thread:nonce=cffa792cb95fe417 - - - http://gs.example.org/index.php/api/statuses/user_timeline/7.atom - lambda2 - - - - http://gs.example.org/avatar/7-96-20170501084054.png - 2017-05-01T16:33:10+00:00 - - - - - - tag:gs.example.org,2017-05-01:objectType=thread:nonce=cffa792cb95fe417 - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org,2017-04-30:noticeId=63:objectType=note - New note by lambda - what now? - - - http://activitystrea.ms/schema/1.0/post - 2017-04-30T10:09:57+00:00 - 2017-04-30T10:09:57+00:00 - - - - tag:gs.example.org,2017-04-30:objectType=thread:nonce=1bbb60991ae9874b - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org,2017-04-30:noticeId=61:objectType=note - New note by lambda - @<a href="http://pleroma.example.org:4000/users/lain5" class="h-card mention">lain5</a> Hello! - - - http://activitystrea.ms/schema/1.0/post - 2017-04-30T10:07:26+00:00 - 2017-04-30T10:07:26+00:00 - - tag:gs.example.org,2017-04-30:objectType=thread:nonce=1bbb60991ae9874b - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org,2017-04-29:noticeId=59:objectType=note - New note by lambda - ey - - - http://activitystrea.ms/schema/1.0/post - 2017-04-29T17:04:59+00:00 - 2017-04-29T17:04:59+00:00 - - tag:gs.example.org,2017-04-29:objectType=thread:nonce=4cc42c2c61a0f4bd - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org,2017-04-29:noticeId=58:objectType=note - New note by lambda - Another one. - - - http://activitystrea.ms/schema/1.0/post - 2017-04-29T17:02:47+00:00 - 2017-04-29T17:02:47+00:00 - - tag:gs.example.org,2017-04-29:objectType=thread:nonce=53e9b8f1d6d38d13 - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org,2017-04-29:noticeId=57:objectType=note - New note by lambda - Let's see if this comes over. - - - http://activitystrea.ms/schema/1.0/post - 2017-04-29T17:01:39+00:00 - 2017-04-29T17:01:39+00:00 - - tag:gs.example.org,2017-04-29:objectType=thread:nonce=238a7bd3ffc7c9cc - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org,2017-04-29:noticeId=56:objectType=note - New note by lambda - @<a href="http://pleroma.example.org:4000/users/lain5" class="h-card mention">lain5</a> Hey! - - - http://activitystrea.ms/schema/1.0/post - 2017-04-29T16:38:13+00:00 - 2017-04-29T16:38:13+00:00 - - tag:gs.example.org,2017-04-29:objectType=thread:nonce=2629d3a398171b0f - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org:4040,2017-04-25:noticeId=55:objectType=note - New note by lambda - hey. - - - http://activitystrea.ms/schema/1.0/post - 2017-04-25T18:16:13+00:00 - 2017-04-25T18:16:13+00:00 - - - - http://pleroma.example.org:4000/contexts/8f6f45d4-8e4d-4e1a-a2de-09f27367d2d0 - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org:4040,2017-04-25:noticeId=53:objectType=note - New note by lambda - and this? - - - http://activitystrea.ms/schema/1.0/post - 2017-04-25T18:14:34+00:00 - 2017-04-25T18:14:34+00:00 - - - - http://pleroma.example.org:4000/contexts/24779b0e-91ad-487e-81bd-6cf5bb437b09 - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org:4040,2017-04-25:noticeId=52:objectType=note - New note by lambda - yeah it does :) - - - http://activitystrea.ms/schema/1.0/post - 2017-04-25T18:13:31+00:00 - 2017-04-25T18:13:31+00:00 - - - - tag:gs.example.org:4040,2017-04-25:objectType=thread:nonce=e0dc24b1a93ab6b3 - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org:4040,2017-04-25:noticeId=50:objectType=note - New note by lambda - @<a href="http://pleroma.example.org:4000/users/lain5" class="h-card mention">lain5</a> Let's try with one that originates here! - - - http://activitystrea.ms/schema/1.0/post - 2017-04-25T18:10:28+00:00 - 2017-04-25T18:10:28+00:00 - - tag:gs.example.org:4040,2017-04-25:objectType=thread:nonce=e0dc24b1a93ab6b3 - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org:4040,2017-04-25:noticeId=48:objectType=note - New note by lambda - works? - - - http://activitystrea.ms/schema/1.0/post - 2017-04-25T18:08:44+00:00 - 2017-04-25T18:08:44+00:00 - - - - http://pleroma.example.org:4000/contexts/24779b0e-91ad-487e-81bd-6cf5bb437b09 - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org:4040,2017-04-25:noticeId=46:objectType=note - New note by lambda - Let's send you an answer. - - - http://activitystrea.ms/schema/1.0/post - 2017-04-25T18:05:31+00:00 - 2017-04-25T18:05:31+00:00 - - - - tag:gs.example.org:4040,2017-04-25:objectType=thread:nonce=73c7bcf6658f7ce3 - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org:4040,2017-04-25:noticeId=44:objectType=note - New note by lambda - Hey. - - - http://activitystrea.ms/schema/1.0/post - 2017-04-25T18:01:09+00:00 - 2017-04-25T18:01:09+00:00 - - - - tag:gs.example.org:4040,2017-04-25:objectType=thread:nonce=6e7c8fc2823380b4 - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org:4040,2017-04-25:noticeId=43:objectType=note - New note by lambda - What's coming to you? - - - http://activitystrea.ms/schema/1.0/post - 2017-04-25T17:58:41+00:00 - 2017-04-25T17:58:41+00:00 - - - - tag:gs.example.org:4040,2017-04-25:objectType=thread:nonce=6e7c8fc2823380b4 - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org:4040,2017-04-25:noticeId=42:objectType=note - New note by lambda - Now this is podracing. - - - http://activitystrea.ms/schema/1.0/post - 2017-04-25T17:57:40+00:00 - 2017-04-25T17:57:40+00:00 - - - - tag:gs.example.org:4040,2017-04-25:objectType=thread:nonce=6e7c8fc2823380b4 - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org:4040,2017-04-25:noticeId=39:objectType=note - New note by lambda - Sure looks like it! - - - http://activitystrea.ms/schema/1.0/post - 2017-04-25T17:48:27+00:00 - 2017-04-25T17:48:27+00:00 - - - - tag:gs.example.org:4040,2017-04-25:objectType=thread:nonce=4c6114a75bb4cea5 - - - - - - - - tag:gs.example.org:4040,2017-04-25:subscription:1:person:6:2017-04-25T17:47:47+00:00 - lambda (lambda)'s status on Tuesday, 25-Apr-2017 17:47:47 UTC - <a href="http://gs.example.org:4040/index.php/lambda">lambda</a> started following <a href="http://pleroma.example.org:4000/users/lain5">l</a>. - - http://activitystrea.ms/schema/1.0/follow - 2017-04-25T17:47:47+00:00 - 2017-04-25T17:47:47+00:00 - - http://activitystrea.ms/schema/1.0/person - http://pleroma.example.org:4000/users/lain5 - l - lambadalambda - - - - - - lain5 - l - lambadalambda - - - tag:gs.example.org:4040,2017-04-25:objectType=thread:nonce=119acad17515314c - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org:4040,2017-04-25:noticeId=36:objectType=note - New note by lambda - @<a href="http://pleroma.example.org:4000/users/lain5" class="h-card mention">lain5</a> Hey, how are you? - - - http://activitystrea.ms/schema/1.0/post - 2017-04-25T17:46:22+00:00 - 2017-04-25T17:46:22+00:00 - - tag:gs.example.org:4040,2017-04-25:objectType=thread:nonce=9c5ec19a18191372 - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org:4040,2017-04-25:noticeId=35:objectType=note - New note by lambda - @lain5@pleroma.example.org does this not work? - - - http://activitystrea.ms/schema/1.0/post - 2017-04-25T17:42:31+00:00 - 2017-04-25T17:42:31+00:00 - - tag:gs.example.org:4040,2017-04-25:objectType=thread:nonce=fc841d7f52caa363 - - - - - - diff --git a/test/fixtures/tesla_mock/https___mamot.fr_users_Skruyb.atom b/test/fixtures/tesla_mock/https___mamot.fr_users_Skruyb.atom deleted file mode 100644 index b5f3d923b..000000000 --- a/test/fixtures/tesla_mock/https___mamot.fr_users_Skruyb.atom +++ /dev/null @@ -1,342 +0,0 @@ - - - https://mamot.fr/users/Skruyb.atom - The 7th Son - Fr and En. -Posts will disappear on a regular basis. - 2017-04-28T13:54:23Z - https://mamot.fr/system/accounts/avatars/000/026/213/original/d95dbcfc76f77f4c.jpg?1493230984 - - https://mamot.fr/users/Skruyb - http://activitystrea.ms/schema/1.0/person - https://mamot.fr/users/Skruyb - Skruyb - Skruyb@mamot.fr - <p>Fr and En.<br />Posts will disappear on a regular basis.</p> - - - - Skruyb - The 7th Son - Fr and En. -Posts will disappear on a regular basis. - public - - - - - - - - tag:mamot.fr,2017-05-10:objectId=1299665:objectType=Status - 2017-05-10T20:06:59Z - 2017-05-10T20:06:59Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://pouets.ovh/@noName" class="u-url mention">@<span>noName</span></a></span></p><p>Pour comparer faut avoir tester... Ô wait!!! 😁</p> - - - public - - - - - - tag:mamot.fr,2017-05-10:objectId=1299185:objectType=Status - 2017-05-10T19:52:14Z - 2017-05-10T19:52:14Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://witches.town/@Dhveszak" class="u-url mention">@<span>Dhveszak</span></a></span></p><p>Toi!! Tu vises le ministère de la propagande avoue!!!!!!!</p> - - - public - - - - - - tag:mamot.fr,2017-05-10:objectId=1299019:objectType=Status - 2017-05-10T19:47:19Z - 2017-05-10T19:47:19Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>Facebook s&apos;attaque aux sites internet &quot;trompeurs&quot;</p><p><a href="http://u.afp.com/4W4z" rel="nofollow noopener" target="_blank"><span class="invisible">http://</span><span class="">u.afp.com/4W4z</span><span class="invisible"></span></a></p><p>J&apos;attends de voir que Facebook s&apos;attaque à lui même... rien qu&apos;à lire leurs conditions générales d&apos;utilisation, le respect de la vie privée...</p><p>Charité bien ordonnée... Parfois l&apos;égoïsme aurait du bon.</p> - - public - - - - - tag:mamot.fr,2017-05-10:objectId=1298889:objectType=Status - 2017-05-10T19:43:18Z - 2017-05-10T19:43:18Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://octodon.social/@Balise" class="u-url mention">@<span>Balise</span></a></span></p><p>Fait comme moi, annonce que tu fais dans le flou artistique et que seuls des esprits éclairés pourront en percevoir la beauté et apprécier. Globalement après ça, tout le monde trouve les photos cool :-p</p> - - - public - - - - - - tag:mamot.fr,2017-05-10:objectId=1298728:objectType=Status - 2017-05-10T19:38:39Z - 2017-05-10T19:38:39Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://mastodon.social/@applecandy" class="u-url mention">@<span>applecandy</span></a></span></p><p>Lucky you!!!</p> - - - public - - - - - - tag:mamot.fr,2017-05-10:objectId=1298431:objectType=Status - 2017-05-10T19:26:32Z - 2017-05-10T19:26:32Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>Est-ce que je suis le seul qui lorsqu&apos;il commence à compter les arbres sur le bord de la route n&apos;arrive pas à s&apos;arrêter de compter?</p> - - public - - - - - tag:mamot.fr,2017-05-10:objectId=1298224:objectType=Status - 2017-05-10T19:18:17Z - 2017-05-10T19:18:17Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>Ca y est j&apos;ai une nouvelle passion. Mettre les bouchons qui trainent par terre dans le bons sens avec mon pied 🙌</p> - - public - - - - - tag:mamot.fr,2017-05-10:objectId=1297450:objectType=Status - 2017-05-10T18:53:37Z - 2017-05-10T18:53:37Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>Ok. On est capable d&apos;envoyer des mecs dans l&apos;espace, avoir des voitures autonomes, des trucs intelligents de partout mais pas tous les bâtiments accessibles aux personnes à mobilité réduite, les émissions sur le services publics avec une personne faisant la traduction pour les sourds et malentendants de manière systématique...</p><p>J&apos;ai du louper un truc dans l&apos;ordre des priorités Oo</p> - - public - - - - - tag:mamot.fr,2017-05-10:objectId=1297292:objectType=Status - 2017-05-10T18:48:17Z - 2017-05-10T18:48:17Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>J&apos;ai comme envie de faire un truc mais je ne sais pas quoi mais pourtant c&apos;est comme si je ressentais l&apos;idée dans ma tête mais c&apos;est pas clair...</p><p>Fuck!!! J&apos;vais aller draguer Josiane à la compta ça va me changer les idées!!!</p> - - public - - - - - tag:mamot.fr,2017-05-10:objectId=1296598:objectType=Status - 2017-05-10T18:25:11Z - 2017-05-10T18:25:11Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://mamot.fr/@Smeablog" class="u-url mention">@<span>Smeablog</span></a></span></p><p>Pas faux MDR!!!!</p> - - - public - - - - - - tag:mamot.fr,2017-05-10:objectId=1296571:objectType=Status - 2017-05-10T18:24:13Z - 2017-05-10T18:24:13Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://mamot.fr/@Smeablog" class="u-url mention">@<span>Smeablog</span></a></span></p><p>Ca ne change pas la finalité malheureusement, ça ne m&apos;ouvre pas ce à quoi je veux accéder 😭</p> - - - public - - - - - - tag:mamot.fr,2017-05-10:objectId=1296475:objectType=Status - 2017-05-10T18:20:50Z - 2017-05-10T18:20:50Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>Arrrgghhhhhhh!!!!</p><p>Quand t&apos;es sur le point de cliquer sur un lien dans le fil public global et que BOOM ça se met à jour... J&apos;ose même pas imaginer combien j&apos;ai ouvert de pages web sans le vouloir!!!</p> - - public - - - - - tag:mamot.fr,2017-05-10:objectId=1296426:objectType=Status - 2017-05-10T18:19:17Z - 2017-05-10T18:19:17Z - Skruyb shared a status by Isaluini@mastodon.social - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/share - - tag:mastodon.social,2017-05-10:objectId=5587049:objectType=Status - 2017-05-10T18:18:59Z - 2017-05-10T18:19:00Z - New status by Isaluini@mastodon.social - - https://mastodon.social/users/Isaluini - http://activitystrea.ms/schema/1.0/person - https://mastodon.social/users/Isaluini - Isaluini - Isaluini@mastodon.social - <p>Adicciones: Escribir, diseñar, cine, café, humor negro, música y dibujar. | Jedi. Bueno, no. Algún día (?) | Gratitude.</p> - - - - Isaluini - Isa - Adicciones: Escribir, diseñar, cine, café, humor negro, música y dibujar. | Jedi. Bueno, no. Algún día (?) | Gratitude. - public - - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>♫ <br><a href="https://www.youtube.com/watch?v=pT68FS3YbQ4"><span class="invisible">https://www.</span><span class="ellipsis">youtube.com/watch?v=pT68FS3YbQ</span><span class="invisible">4</span></a></p> - - public - - - <p>♫ <br><a href="https://www.youtube.com/watch?v=pT68FS3YbQ4"><span class="invisible">https://www.</span><span class="ellipsis">youtube.com/watch?v=pT68FS3YbQ</span><span class="invisible">4</span></a></p> - - public - - - - - tag:mamot.fr,2017-05-10:objectId=1295893:objectType=Status - 2017-05-10T18:01:51Z - 2017-05-10T18:01:51Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://mamot.fr/@Chat2Gouttieres" class="u-url mention">@<span>Chat2Gouttieres</span></a></span></p><p>Ah bah après faut savoir mettre à profit ce savoir ^^</p> - - - public - - - - - - tag:mamot.fr,2017-05-10:objectId=1295815:objectType=Status - 2017-05-10T18:00:02Z - 2017-05-10T18:00:02Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://mamot.fr/@Chat2Gouttieres" class="u-url mention">@<span>Chat2Gouttieres</span></a></span></p><p>Exactement. On a les jeux mais pas le pain encore.</p><p>Finalement on a rien inventé :-p</p> - - - public - - - - - - tag:mamot.fr,2017-05-10:objectId=1295778:objectType=Status - 2017-05-10T17:58:52Z - 2017-05-10T17:58:52Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://mamot.fr/@Chat2Gouttieres" class="u-url mention">@<span>Chat2Gouttieres</span></a></span></p><p>C&apos;est ça visiblement dans notre société dite moderne... &quot;Créer l&apos;illusion que&quot; Oo.</p> - - - public - - - - - - tag:mamot.fr,2017-05-10:objectId=1294943:objectType=Status - 2017-05-10T17:31:44Z - 2017-05-10T17:31:44Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - Hey. - <p><span class="h-card"><a href="https://mastodon.social/@lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span></p><p>Hey!!!</p> - - - public - - - - - - tag:mamot.fr,2017-05-10:objectId=1294914:objectType=Status - 2017-05-10T17:30:40Z - 2017-05-10T17:30:40Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://mamot.fr/@EloClemTiti" class="u-url mention">@<span>EloClemTiti</span></a></span></p><p>J&apos;ai souvent cette impression en effet 😂</p> - - - public - - - - - - tag:mamot.fr,2017-05-10:objectId=1294148:objectType=Status - 2017-05-10T17:02:01Z - 2017-05-10T17:02:01Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>Les gars, les boss veulent voir de l&apos;avancement!! Une idée?</p><p>On fait comme d&apos;habitude. On divise nos tâches en 25.000 tâches unitaires, on fout du vert au maximum et on crée l&apos;illusion que ça a bien avancé!</p><p>Deal!!</p><p>Bob, tu choisis quel vert on utilise<br />Alice, t&apos;es en charge de la typo<br />Moi, je m&apos;occupe qu&apos;on prend bien le dernier template ppt fournit par la comm interne.</p><p>Des winners qu&apos;on est!!!! Des WI-NNERS!!!</p> - - public - - - - - tag:mamot.fr,2017-05-10:objectId=1293995:objectType=Status - 2017-05-10T16:57:53Z - 2017-05-10T16:57:53Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://mastodon.social/@SauceHair" class="u-url mention">@<span>SauceHair</span></a></span></p><p>Cool!!</p><p>Bon courage.</p> - - - public - - - - - diff --git a/test/fixtures/tesla_mock/https___mastodon.social_users_lambadalambda.atom b/test/fixtures/tesla_mock/https___mastodon.social_users_lambadalambda.atom deleted file mode 100644 index 4d732b109..000000000 --- a/test/fixtures/tesla_mock/https___mastodon.social_users_lambadalambda.atom +++ /dev/null @@ -1,464 +0,0 @@ - - - https://mastodon.social/users/lambadalambda.atom - Critical Value - - 2017-04-16T21:47:25Z - https://files.mastodon.social/accounts/avatars/000/000/264/original/1429214160519.gif - - https://mastodon.social/users/lambadalambda - http://activitystrea.ms/schema/1.0/person - https://mastodon.social/users/lambadalambda - lambadalambda - lambadalambda@mastodon.social - - - - lambadalambda - Critical Value - public - - - - - - - - tag:mastodon.social,2017-05-04:objectId=4991300:objectType=Status - 2017-05-04T14:10:30Z - 2017-05-04T14:10:30Z - Delete - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/delete - - - - - tag:mastodon.social,2017-05-04:objectId=4980289:objectType=Status - 2017-05-04T07:43:23Z - 2017-05-04T07:43:23Z - Delete - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/delete - - - - - tag:mastodon.social,2017-05-03:objectId=4952899:objectType=Status - 2017-05-03T17:26:43Z - 2017-05-03T17:26:43Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://pleroma.soykaf.com/users/lain" class="u-url mention">@<span>lain</span></a></span> OK!!</p> - - - public - - - - - - tag:mastodon.social,2017-05-03:objectId=4952810:objectType=Status - 2017-05-03T17:24:34Z - 2017-05-03T17:24:34Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://pleroma.soykaf.com/users/lain" class="u-url mention">@<span>lain</span></a></span> yeah :)</p> - - - public - - - - - - tag:mastodon.social,2017-05-03:objectId=4950388:objectType=Status - 2017-05-03T16:22:00Z - 2017-05-03T16:22:00Z - lambadalambda shared a status by lambadalambda@social.heldscal.la - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/share - - tag:social.heldscal.la,2017-05-03:noticeId=2030733:objectType=note - 2017-05-03T12:29:20Z - 2017-05-03T12:29:31Z - New status by lambadalambda@social.heldscal.la - - https://social.heldscal.la/user/23211 - http://activitystrea.ms/schema/1.0/person - https://social.heldscal.la/user/23211 - lambadalambda - lambadalambda@social.heldscal.la - Call me Deacon Blues. - - - - lambadalambda - Constance Variable - Call me Deacon Blues. - public - - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - Time for work. <a href="https://social.heldscal.la/file/953c117a1e7e4c763755d2ac29cf1aae08e025599f4a4cc11ddff4082c07f969.jpg">https://social.heldscal.la/attachment/120552</a> - - - public - - - Time for work. <a href="https://social.heldscal.la/file/953c117a1e7e4c763755d2ac29cf1aae08e025599f4a4cc11ddff4082c07f969.jpg">https://social.heldscal.la/attachment/120552</a> - - public - - - - - tag:mastodon.social,2017-05-03:objectId=4934452:objectType=Status - 2017-05-03T08:21:09Z - 2017-05-03T08:21:09Z - lambadalambda shared a status by lain@pleroma.soykaf.com - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/share - - https://pleroma.soykaf.com/objects/4c1bda26-902e-4525-9fcd-b9fd44925193 - 2017-05-03T08:04:44Z - 2017-05-03T08:05:52Z - New status by lain@pleroma.soykaf.com - - https://pleroma.soykaf.com/users/lain - http://activitystrea.ms/schema/1.0/person - https://pleroma.soykaf.com/users/lain - lain - lain@pleroma.soykaf.com - Test account - - - - lain - Lain Iwakura - Test account - public - - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - Added returning the entries as xml... let's see if the mastodon hammering stops now. - - public - - - Added returning the entries as xml... let's see if the mastodon hammering stops now. - - public - - - - - tag:mastodon.social,2017-05-02:objectId=4905499:objectType=Status - 2017-05-02T19:34:21Z - 2017-05-02T19:34:21Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://pleroma.soykaf.com/users/lain" class="u-url mention">@<span>lain</span></a></span> yay!</p> - - - public - - - - - - tag:mastodon.social,2017-05-02:objectId=4905442:objectType=Status - 2017-05-02T19:33:33Z - 2017-05-02T19:33:33Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://pleroma.soykaf.com/users/lain" class="u-url mention">@<span>lain</span></a></span> so?</p> - - - public - - - - - - tag:mastodon.social,2017-05-02:objectId=4901603:objectType=Status - 2017-05-02T18:33:06Z - 2017-05-02T18:33:06Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://pleroma.soykaf.com/users/lain" class="u-url mention">@<span>lain</span></a></span> hey</p> - - - public - - - - - - tag:mastodon.social,2017-05-01:objectId=4836720:objectType=Status - 2017-05-01T18:52:16Z - 2017-05-01T18:52:16Z - lambadalambda shared a status by lain@pleroma.soykaf.com - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/share - - https://pleroma.soykaf.com/objects/7b41bb51-9aba-436a-82d9-dd3f5aca98c9 - 2017-05-01T18:50:54Z - 2017-05-01T18:50:57Z - New status by lain@pleroma.soykaf.com - - https://pleroma.soykaf.com/users/lain - http://activitystrea.ms/schema/1.0/person - https://pleroma.soykaf.com/users/lain - lain - lain@pleroma.soykaf.com - Test account - - - - lain - Lain Iwakura - Test account - public - - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <a href="https://mastodon.social/users/lambadalambda">@lambadalambda@mastodon.social</a> you're an all-star. - - - public - - - - <a href="https://mastodon.social/users/lambadalambda">@lambadalambda@mastodon.social</a> you're an all-star. - - public - - - - - tag:mastodon.social,2017-05-01:objectId=4836142:objectType=Status - 2017-05-01T18:38:47Z - 2017-05-01T18:38:47Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://pleroma.soykaf.com/users/lain" class="u-url mention">@<span>lain</span></a></span> Hey now!</p> - - - public - - - - - - tag:mastodon.social,2017-05-01:objectId=4836055:objectType=Status - 2017-05-01T18:37:04Z - 2017-05-01T18:37:04Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://pleroma.soykaf.com/users/lain" class="u-url mention">@<span>lain</span></a></span> hello</p> - - - public - - - - - - tag:mastodon.social,2017-05-01:objectId=4834850:objectType=Status - 2017-05-01T18:10:43Z - 2017-05-01T18:10:43Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://pleroma.soykaf.com/users/lain" class="u-url mention">@<span>lain</span></a></span> Hey!</p> - - - public - - - - - tag:mastodon.social,2017-04-29:objectId=4694455:objectType=Status - 2017-04-29T18:39:12Z - 2017-04-29T18:39:12Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>@lain@pleroma.soykaf.com What&apos;s up?</p> - - public - - - - - tag:mastodon.social,2017-04-29:objectId=4694384:objectType=Status - 2017-04-29T18:37:32Z - 2017-04-29T18:37:32Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://social.heldscal.la/lain" class="u-url mention">@<span>lain</span></a></span> Hey.</p> - - - public - - - - - tag:mastodon.social,2017-04-07:objectId=1874242:objectType=Status - 2017-04-07T11:02:56Z - 2017-04-07T11:02:56Z - lambadalambda shared a status by 0xroy@social.wxcafe.net - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/share - - tag:social.wxcafe.net,2017-04-07:objectId=72554:objectType=Status - 2017-04-07T11:01:59Z - 2017-04-07T11:02:00Z - New status by 0xroy@social.wxcafe.net - - https://social.wxcafe.net/users/0xroy - http://activitystrea.ms/schema/1.0/person - https://social.wxcafe.net/users/0xroy - 0xroy - 0xroy@social.wxcafe.net - ta caution weeb | discussions privées : <a href="https://%F0%9F%92%8C.0xroy.me"><span class="invisible">https://</span><span class="">💌.0xroy.me</span><span class="invisible"></span></a> - - - - 0xroy - 「R O Y 🍵 B O S」 - ta caution weeb | discussions privées : https://💌.0xroy.me - public - - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>someone pls eli5 matrix (protocol) and riot</p> - - public - - - <p>someone pls eli5 matrix (protocol) and riot</p> - - public - - - - - tag:mastodon.social,2017-04-06:objectId=1768247:objectType=Status - 2017-04-06T11:10:19Z - 2017-04-06T11:10:19Z - lambadalambda shared a status by areyoutoo@mastodon.xyz - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/share - - tag:mastodon.xyz,2017-04-05:objectId=133327:objectType=Status - 2017-04-05T17:36:41Z - 2017-04-05T18:12:14Z - New status by areyoutoo@mastodon.xyz - - https://mastodon.xyz/users/areyoutoo - http://activitystrea.ms/schema/1.0/person - https://mastodon.xyz/users/areyoutoo - areyoutoo - areyoutoo@mastodon.xyz - devops | retired gamedev | always boost puppy pics - - - - areyoutoo - Raw Butter - devops | retired gamedev | always boost puppy pics - public - - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>Some UX thoughts for <a href="https://mastodon.xyz/tags/mastodev">#<span>mastodev</span></a>:</p><p>- Would be nice if I could work on multiple draft toots? Clicking to reply to someone seems to erase any draft I had been working on.</p><p>- Kinda risky to click on the Federated Timeline if it loads new toots and scrolls 10ms before I click on something.</p><p>I probably don't know enough web frontend to help, but it might be fun to try.</p> - - - public - - - <p>Some UX thoughts for <a href="https://mastodon.xyz/tags/mastodev">#<span>mastodev</span></a>:</p><p>- Would be nice if I could work on multiple draft toots? Clicking to reply to someone seems to erase any draft I had been working on.</p><p>- Kinda risky to click on the Federated Timeline if it loads new toots and scrolls 10ms before I click on something.</p><p>I probably don't know enough web frontend to help, but it might be fun to try.</p> - - public - - - - - tag:mastodon.social,2017-04-06:objectId=1764509:objectType=Status - 2017-04-06T10:15:38Z - 2017-04-06T10:15:38Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - This is a test for cw federation - <p>This is a test for cw federation body text.</p> - - public - - - - - tag:mastodon.social,2017-04-05:objectId=1645208:objectType=Status - 2017-04-05T07:14:53Z - 2017-04-05T07:14:53Z - lambadalambda shared a status by lambadalambda@social.heldscal.la - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/share - - tag:social.heldscal.la,2017-04-05:noticeId=1502088:objectType=note - 2017-04-05T06:12:09Z - 2017-04-05T07:12:47Z - New status by lambadalambda@social.heldscal.la - - https://social.heldscal.la/user/23211 - http://activitystrea.ms/schema/1.0/person - https://social.heldscal.la/user/23211 - lambadalambda - lambadalambda@social.heldscal.la - Call me Deacon Blues. - - - - lambadalambda - Constance Variable - Call me Deacon Blues. - public - - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - Federation 101: <a href="https://www.youtube.com/watch?v=t1lYU5CA40o">https://www.youtube.com/watch?v=t1lYU5CA40o</a> - - public - - - Federation 101: <a href="https://www.youtube.com/watch?v=t1lYU5CA40o">https://www.youtube.com/watch?v=t1lYU5CA40o</a> - - public - - - - - tag:mastodon.social,2017-04-05:objectId=1641750:objectType=Status - 2017-04-05T05:44:48Z - 2017-04-05T05:44:48Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> just a test.</p> - - - public - - - - diff --git a/test/fixtures/tesla_mock/https___osada.macgirvin.com_channel_mike.json b/test/fixtures/tesla_mock/https___osada.macgirvin.com_channel_mike.json index c42f3a53c..ca76d6e17 100644 --- a/test/fixtures/tesla_mock/https___osada.macgirvin.com_channel_mike.json +++ b/test/fixtures/tesla_mock/https___osada.macgirvin.com_channel_mike.json @@ -8,6 +8,7 @@ "preferredUsername": "mike", "name": "Mike Macgirvin (Osada)", "updated": "2018-08-29T03:09:11Z", + "discoverable": "true", "icon": { "type": "Image", "mediaType": "image/jpeg", @@ -51,4 +52,4 @@ "created": "2018-10-17T07:16:28Z", "signatureValue": "WbfFVIPImkd3yNu6brz0CvZaeV242rwAbH0vy8DM4vfnXCxLr5Uv/Wj9gwP+tbooTxGaahAKBeqlGkQp8RLEo37LATrKMRLA/0V6DeeV+C5ORWR9B4WxyWiD3s/9Wf+KesFMtktNLAcMZ5PfnOS/xNYerhnpkp/gWPxtkglmLIWJv+w18A5zZ01JCxsO4QljHbhYaEUPHUfQ97abrkLECeam+FThVwdO6BFCtbjoNXHfzjpSZL/oKyBpi5/fpnqMqOLOQPs5WgBBZJvjEYYkQcoPTyxYI5NGpNbzIjGHPQNuACnOelH16A7L+q4swLWDIaEFeXQ2/5bmqVKZDZZ6usNP4QyTVszwd8jqo27qcDTNibXDUTsTdKpNQvM/3UncBuzuzmUV3FczhtGshIU1/pRVZiQycpVqPlGLvXhP/yZCe+1siyqDd+3uMaS2vkHTObSl5r+VYof+c+TcjrZXHSWnQTg8/X3zkoBWosrQ93VZcwjzMxQoARYv6rphbOoTz7RPmGAXYUt3/PDWkqDlmQDwCpLNNkJo1EidyefZBdD9HXQpCBO0ZU0NHb0JmPvg/+zU0krxlv70bm3RHA/maBETVjroIWzt7EwQEg5pL2hVnvSBG+1wF3BtRVe77etkPOHxLnYYIcAMLlVKCcgDd89DPIziQyruvkx1busHI08=" } -} +} \ No newline at end of file diff --git a/test/fixtures/tesla_mock/https___pawoo.net_users_pekorino.atom b/test/fixtures/tesla_mock/https___pawoo.net_users_pekorino.atom deleted file mode 100644 index 17d1956e8..000000000 --- a/test/fixtures/tesla_mock/https___pawoo.net_users_pekorino.atom +++ /dev/null @@ -1,231 +0,0 @@ - - - https://pawoo.net/users/pekorino.atom - モノエ - シアトル・米国 - -GNUsocial 英語版 -http://shitposter.club/mono - - - 2017-05-07T09:28:20Z - https://img.pawoo.net/accounts/avatars/000/128/378/original/e1fce04a36a1ad90.jpg - - https://pawoo.net/users/pekorino - http://activitystrea.ms/schema/1.0/person - https://pawoo.net/users/pekorino - pekorino - pekorino@pawoo.net - <p>シアトル・米国</p><p>GNUsocial 英語版<br /><a href="http://shitposter.club/mono" rel="nofollow noopener" target="_blank"><span class="invisible">http://</span><span class="">shitposter.club/mono</span><span class="invisible"></span></a> </p> - - - - pekorino - モノエ - シアトル・米国 - -GNUsocial 英語版 -http://shitposter.club/mono - - - public - - - - - - - tag:pawoo.net,2017-05-07:objectId=9319211:objectType=Status - 2017-05-07T09:56:35Z - 2017-05-07T09:56:35Z - New status by pekorino - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://shitposter.club/moonman" class="u-url mention">@<span>moonman</span></a></span> <span class="h-card"><a href="https://shitposter.club/rw" class="u-url mention">@<span>rw</span></a></span> <span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> <span class="h-card"><a href="https://shitposter.club/mono" class="u-url mention">@<span>mono</span></a></span> </p><p>i have to wait for someone to respond to this before i can follow because i dont think this software has a direct follow by url option</p> - - - - - - public - - - - - - tag:pawoo.net,2017-05-07:objectId=9318595:objectType=Status - 2017-05-07T09:54:39Z - 2017-05-07T09:54:39Z - New status by pekorino - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://shitposter.club/mono" class="u-url mention">@<span>mono</span></a></span> <span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> <span class="h-card"><a href="https://shitposter.club/rw" class="u-url mention">@<span>rw</span></a></span> <span class="h-card"><a href="https://shitposter.club/moonman" class="u-url mention">@<span>moonman</span></a></span> <br />please respond</p> - - - - - - public - - - - - - tag:pawoo.net,2017-05-07:objectId=9313978:objectType=Status - 2017-05-07T09:39:17Z - 2017-05-07T09:39:17Z - New status by pekorino - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://shitposter.club/moonman" class="u-url mention">@<span>moonman</span></a></span> <br />mastodon is so slow. browser crashed twice trying to set avatar</p> - - - public - - - - - tag:pawoo.net,2017-05-07:objectId=9312691:objectType=Status - 2017-05-07T09:34:38Z - 2017-05-07T09:34:38Z - New status by pekorino - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://shitposter.club/hardbass2k8" class="u-url mention">@<span>hardbass2k8</span></a></span> <a href="https://pawoo.net/media/mZJjLpbPU72GFEz2Svk" rel="nofollow noopener" target="_blank"><span class="invisible">https://</span><span class="ellipsis">pawoo.net/media/mZJjLpbPU72GFE</span><span class="invisible">z2Svk</span></a></p> - - - - public - - - - - - tag:pawoo.net,2017-05-07:objectId=9312379:objectType=Status - 2017-05-07T09:33:29Z - 2017-05-07T09:33:29Z - New status by pekorino - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://shitposter.club/hardbass2k8" class="u-url mention">@<span>hardbass2k8</span></a></span> <a href="https://pawoo.net/media/nt5JHBEHyTN2bqzdcGU" rel="nofollow noopener" target="_blank"><span class="invisible">https://</span><span class="ellipsis">pawoo.net/media/nt5JHBEHyTN2bq</span><span class="invisible">zdcGU</span></a></p> - - - - public - - - - - - tag:pawoo.net,2017-05-07:objectId=9311765:objectType=Status - 2017-05-07T09:31:26Z - 2017-05-07T09:31:26Z - New status by pekorino - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p><a href="https://pawoo.net/media/C4RV6ubsEtvS04DX6qs" rel="nofollow noopener" target="_blank"><span class="invisible">https://</span><span class="ellipsis">pawoo.net/media/C4RV6ubsEtvS04</span><span class="invisible">DX6qs</span></a></p> - - - public - - - - - tag:pawoo.net,2017-05-07:objectId=9311610:objectType=Status - 2017-05-07T09:30:59Z - 2017-05-07T09:30:59Z - New status by pekorino - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p><a href="https://pawoo.net/media/MBmkeEdrjs8pAtCHN6s" rel="nofollow noopener" target="_blank"><span class="invisible">https://</span><span class="ellipsis">pawoo.net/media/MBmkeEdrjs8pAt</span><span class="invisible">CHN6s</span></a></p> - - - public - - - - - tag:pawoo.net,2017-05-07:objectId=9307782:objectType=Status - 2017-05-07T09:16:47Z - 2017-05-07T09:16:47Z - New status by pekorino - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://shitposter.club/mono" class="u-url mention">@<span>mono</span></a></span></p><p>test</p> - - - public - - - - - tag:pawoo.net,2017-05-07:objectId=9307444:objectType=Status - 2017-05-07T09:15:42Z - 2017-05-07T09:15:42Z - New status by pekorino - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://shitposter.club/hardbass2k8" class="u-url mention">@<span>hardbass2k8</span></a></span> テスト</p> - - - public - - - - - - tag:pawoo.net,2017-05-07:objectId=9307239:objectType=Status - 2017-05-07T09:14:58Z - 2017-05-07T09:14:58Z - New status by pekorino - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>ててててててテスト</p> - - public - - - - - tag:pawoo.net,2017-04-20:objectId=2212164:objectType=Status - 2017-04-20T06:19:18Z - 2017-04-20T06:19:18Z - New status by pekorino - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://shitposter.club/mono" class="u-url mention">@<span>mono</span></a></span> <a href="https://pawoo.net/media/iMbjMBVPfZJX3lUC2Sc" rel="nofollow noopener" target="_blank"><span class="invisible">https://</span><span class="ellipsis">pawoo.net/media/iMbjMBVPfZJX3l</span><span class="invisible">UC2Sc</span></a></p> - - - - public - - - - - - tag:pawoo.net,2017-04-20:objectId=2206216:objectType=Status - 2017-04-20T05:57:59Z - 2017-04-20T05:57:59Z - New status by pekorino - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>テスト</p> - - public - - - - - tag:pawoo.net,2017-04-20:objectId=2204702:objectType=Status - 2017-04-20T05:52:09Z - 2017-04-20T05:52:09Z - New status by pekorino - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>HELLOWORLD</p> - - public - - - - diff --git a/test/fixtures/tesla_mock/https___pleroma.soykaf.com_users_lain_feed.atom.xml b/test/fixtures/tesla_mock/https___pleroma.soykaf.com_users_lain_feed.atom.xml deleted file mode 100644 index a2a2629a6..000000000 --- a/test/fixtures/tesla_mock/https___pleroma.soykaf.com_users_lain_feed.atom.xml +++ /dev/null @@ -1 +0,0 @@ -https://pleroma.soykaf.com/users/lain/feed.atomlain's timeline2017-05-05T08:38:03.385598https://pleroma.soykaf.com/users/lainhttp://activitystrea.ms/schema/1.0/personhttps://pleroma.soykaf.com/users/lainlainLain IwakuraTest accountlainhttp://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/sharehttps://pleroma.soykaf.com/activities/579e4224-b2ab-4ffa-8bbe-f7197a0a38d1lain repeated a noticeRT In just seven days, I can make you a man!<br> -- The Rocky Horror Picture Show2017-05-05T08:38:03.3855902017-05-05T08:38:03.385598https://pleroma.soykaf.com/contexts/e8673466-9642-4c9e-8781-f0f69d6b15aehttp://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/53dd40f4-3069-45a1-863b-94a9b317093dNew note by fortuneIn just seven days, I can make you a man!<br> -- The Rocky Horror Picture Show2017-05-05T02:10:02.9308022017-05-05T08:38:03.423539https://pleroma.soykaf.com/contexts/e8673466-9642-4c9e-8781-f0f69d6b15aehttps://pleroma.soykaf.com/users/fortunehttp://activitystrea.ms/schema/1.0/personhttps://pleroma.soykaf.com/users/fortunefortunefortuneThe trusty unix fortune filefortunehttp://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/2bc86888-a256-4771-bb53-903f375804f9New note by lainRTs federating into pleroma now.2017-05-04T18:18:50.2764702017-05-04T18:18:50.276476https://pleroma.soykaf.com/contexts/b7ae9350-f317-48aa-8058-2668091bb280http://activitystrea.ms/schema/1.0/favoritehttps://pleroma.soykaf.com/activities/902b1f50-f295-4189-8c15-9c880919e121New favorite by lainlain favorited something2017-05-04T08:03:01.3088902017-05-04T08:03:01.308927http://activitystrea.ms/schema/1.0/notetag:gs.smuglo.li,2017-05-03:noticeId=2164642:objectType=commenthttps://pleroma.soykaf.com/contexts/9419f742-aaba-4eb5-89a2-8b599e8bf43chttp://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/4e396e66-b063-454c-92c6-583506a9a2deNew note by lainClassic.<br><a href='https://pleroma.soykaf.com/media/adc36781-9765-4d9a-b57c-99b7a99108b2/mikodaemonstop.jpg'>https://pleroma.soykaf.com/media/adc36781-9765-4d9a-b57c-99b7a99108b2/mikodaemonstop.jpg</a>2017-05-04T07:59:45.1806192017-05-04T07:59:45.180628https://pleroma.soykaf.com/contexts/6afd9659-41e6-406d-ae97-43b880722861http://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/85d183e9-c935-4655-a1e6-8d69a4108235New note by lainん?<br><a href='https://pleroma.soykaf.com/media/ab144c6d-a38c-4d35-a60b-9a998becc094/n.gif'>https://pleroma.soykaf.com/media/ab144c6d-a38c-4d35-a60b-9a998becc094/n.gif</a>2017-05-04T07:58:08.8107162017-05-04T07:58:08.810726https://pleroma.soykaf.com/contexts/2e1aa616-86ce-4b50-9c81-63045a972156http://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/7c5c45bb-e4d9-4f72-b4c6-0314afbd3553New note by lainyeah.2017-05-04T07:55:17.3352902017-05-04T07:55:17.335299https://pleroma.soykaf.com/contexts/702c06cf-56ff-4a2f-bf5a-150bc00bb168http://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/f33f5f54-1c1d-4462-b9ed-229bb635dfd8New note by lainyeah.2017-05-04T07:49:24.9314842017-05-04T07:49:24.931492https://pleroma.soykaf.com/contexts/c4932e7a-00cb-431a-b4ec-7404cb9daf65http://activitystrea.ms/schema/1.0/favoritehttps://pleroma.soykaf.com/activities/0709bc79-7ac5-4983-b6d0-2205bf5ceba3New favorite by lainlain favorited something2017-05-03T20:08:11.2945792017-05-03T20:08:11.294587http://activitystrea.ms/schema/1.0/notetag:pawoo.net,2017-05-03:objectId=7967690:objectType=Statushttps://pleroma.soykaf.com/contexts/07a4b34d-6255-4bb2-8c73-c295a09ac952http://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/72c0288e-62d8-43d9-b3d8-1a9d78be8375New note by lain<a href='https://pawoo.net/users/God_Emperor_of_Dune'>@God_Emperor_of_Dune@pawoo.net</a> no man, just some fun domination play among buddies, nothing homo about it.2017-05-03T20:01:00.9983142017-05-03T20:01:00.998322https://pleroma.soykaf.com/contexts/07a4b34d-6255-4bb2-8c73-c295a09ac952http://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/d846409e-cf2a-4b68-a149-d5de34a91b0dNew note by lain<a href='https://social.heldscal.la/user/24974'>@dtluna@social.heldscal.la</a> btfo.<br><a href='https://pleroma.soykaf.com/media/fbe42e87-5574-4544-89ba-29ddf46227fa/pnc__picked_media_1889ce61-4961-4fea-8a14-04fe6783ebf6.jpg'>https://pleroma.soykaf.com/media/fbe42e87-5574-4544-89ba-29ddf46227fa/pnc__picked_media_1889ce61-4961-4fea-8a14-04fe6783ebf6.jpg</a>2017-05-03T20:00:15.8609952017-05-03T20:00:15.861002https://pleroma.soykaf.com/contexts/0e88f35e-1a38-4181-bef9-5cbb0d943c63http://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/9075265f-f3b2-40e8-809f-10714f05a1fdNew note by lain#nohomo <br><a href='https://pleroma.soykaf.com/media/5cc5ad91-d637-4c45-a691-5ea778dc1bb3/pnc__picked_media_f62dc9ae-ea23-4fe6-bf85-cb75a129ab34.jpg'>https://pleroma.soykaf.com/media/5cc5ad91-d637-4c45-a691-5ea778dc1bb3/pnc__picked_media_f62dc9ae-ea23-4fe6-bf85-cb75a129ab34.jpg</a>2017-05-03T19:50:38.5891062017-05-03T19:50:38.589113https://pleroma.soykaf.com/contexts/07a4b34d-6255-4bb2-8c73-c295a09ac952http://activitystrea.ms/schema/1.0/favoritehttps://pleroma.soykaf.com/activities/7924e992-0a95-40d9-8d17-7278c6c634c9New favorite by lainlain favorited something2017-05-03T18:32:59.2733752017-05-03T18:32:59.273382http://activitystrea.ms/schema/1.0/notetag:gs.smuglo.li,2017-05-03:noticeId=2164774:objectType=commenthttps://pleroma.soykaf.com/contexts/9419f742-aaba-4eb5-89a2-8b599e8bf43chttp://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/569571ba-f54c-41b0-bde4-0fede54599f0New note by lain<a href='https://gs.smuglo.li/user/2'>@nepfag@gs.smuglo.li</a>@gs.smuglo.li I'll do proper subfolders soon, for now it's one per attachment + thumbs etc.2017-05-03T18:27:01.4499492017-05-03T18:27:01.449956https://pleroma.soykaf.com/contexts/9419f742-aaba-4eb5-89a2-8b599e8bf43chttp://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/sharehttps://pleroma.soykaf.com/activities/b6cc5d7c-0785-4785-a689-f1b05dc9b24dlain repeated a noticeRT <p><span class="h-card"><a href="https://pleroma.soykaf.com/users/lain" class="u-url mention">@<span>lain</span></a></span> Hey now!</p>2017-05-03T18:13:48.8910612017-05-03T18:13:48.891069https://pleroma.soykaf.com/contexts/ec6fdd27-0ec1-4672-8408-5a8e5a9c094bhttp://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posttag:mastodon.social,2017-05-01:objectId=4836142:objectType=StatusNew note by lambadalambda@mastodon.social<p><span class="h-card"><a href="https://pleroma.soykaf.com/users/lain" class="u-url mention">@<span>lain</span></a></span> Hey now!</p>2017-05-01T18:38:49.3653912017-05-03T18:13:48.934745https://pleroma.soykaf.com/contexts/ec6fdd27-0ec1-4672-8408-5a8e5a9c094bhttps://mastodon.social/users/lambadalambdahttp://activitystrea.ms/schema/1.0/personhttps://mastodon.social/users/lambadalambdalambadalambda@mastodon.socialCritical Valuenillambadalambda@mastodon.socialhttp://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/sharehttps://pleroma.soykaf.com/activities/3c09eb31-4ba8-4ff5-b4fa-8f6f74d58bf0lain repeated a noticeRT Haha, salmons from mastodon didn't work because it's not implementing conversation id...2017-05-03T18:13:15.1480412017-05-03T18:13:15.148049tag:social.heldscal.la,2017-05-01:objectType=thread:nonce=86cda6c734401d80http://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posttag:social.heldscal.la,2017-05-01:noticeId=2000425:objectType=noteNew note by lambadalambda@social.heldscal.laHaha, salmons from mastodon didn't work because it's not implementing conversation id...2017-05-01T18:39:36.2163772017-05-03T18:13:15.171143tag:social.heldscal.la,2017-05-01:objectType=thread:nonce=86cda6c734401d80https://social.heldscal.la/user/23211http://activitystrea.ms/schema/1.0/personhttps://social.heldscal.la/user/23211lambadalambda@social.heldscal.laConstance Variablenillambadalambda@social.heldscal.lahttp://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/b8fc83d5-d7c0-4b5f-8976-0317b51935eaNew note by lain.<br><a href='https://pleroma.soykaf.com/media/563008a7-9a60-47ac-a263-22835729adf6/1492530528735.png'>https://pleroma.soykaf.com/media/563008a7-9a60-47ac-a263-22835729adf6/1492530528735.png</a>2017-05-03T18:12:50.7452412017-05-03T18:12:50.745249https://pleroma.soykaf.com/contexts/9419f742-aaba-4eb5-89a2-8b599e8bf43chttp://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/sharehttps://pleroma.soykaf.com/activities/ac93ecef-cde0-48e8-ae4b-19e3b94dbe30lain repeated a noticeRT Awright, which one of you hid my PENIS ENVY?2017-05-03T18:08:49.2310012017-05-03T18:08:49.235354https://pleroma.soykaf.com/contexts/a9132cf8-6afa-4dd8-8b29-7b6fcab623b8http://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/04e15c66-4936-4930-a134-32841f088bcfNew note by fortuneAwright, which one of you hid my PENIS ENVY?2017-05-01T19:40:03.1699962017-05-03T18:08:49.285347https://pleroma.soykaf.com/contexts/a9132cf8-6afa-4dd8-8b29-7b6fcab623b8https://pleroma.soykaf.com/users/fortunehttp://activitystrea.ms/schema/1.0/personhttps://pleroma.soykaf.com/users/fortunefortunefortuneThe trusty unix fortune filefortunehttp://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/sharehttps://pleroma.soykaf.com/activities/54b10fa9-d602-4a0f-b659-e6d3f7bc8c4clain repeated a noticeRT He is a man capable of turning any colour into grey.<br> -- John LeCarre2017-05-03T17:44:47.5789842017-05-03T17:44:47.578996https://pleroma.soykaf.com/contexts/8aebc8e5-5352-4047-8b74-4098a5830ccahttp://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/70ded299-184d-49cd-af17-23c0950536aaNew note by fortuneHe is a man capable of turning any colour into grey.<br> -- John LeCarre2017-05-02T08:40:03.4194652017-05-03T17:44:47.646192https://pleroma.soykaf.com/contexts/8aebc8e5-5352-4047-8b74-4098a5830ccahttps://pleroma.soykaf.com/users/fortunehttp://activitystrea.ms/schema/1.0/personhttps://pleroma.soykaf.com/users/fortunefortunefortuneThe trusty unix fortune filefortunehttp://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/sharehttps://pleroma.soykaf.com/activities/eff9fe49-8fc9-48e6-a1a0-921aa25c8118lain repeated a noticeRT The real trouble with women is that they have *all* the pussy.2017-05-03T17:30:22.5960372017-05-03T17:30:22.596048https://pleroma.soykaf.com/contexts/8c88c9df-4e40-4f54-b15f-c21848d1a8e2http://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/0b9b008d-49eb-48a9-a18d-172ce7d01ea2New note by fortuneThe real trouble with women is that they have *all* the pussy.2017-05-02T12:10:03.6030862017-05-03T17:30:22.683141https://pleroma.soykaf.com/contexts/8c88c9df-4e40-4f54-b15f-c21848d1a8e2https://pleroma.soykaf.com/users/fortunehttp://activitystrea.ms/schema/1.0/personhttps://pleroma.soykaf.com/users/fortunefortunefortuneThe trusty unix fortune filefortunehttp://activitystrea.ms/schema/1.0/favoritehttps://pleroma.soykaf.com/activities/5d90bb26-ce23-4a5b-8dbd-651011780007New favorite by lainlain favorited something2017-05-03T17:28:20.9679262017-05-03T17:28:20.967935http://activitystrea.ms/schema/1.0/notetag:mastodon.social,2017-05-03:objectId=4952899:objectType=Statushttps://pleroma.soykaf.com/contexts/42701ab4-964a-441a-a372-f51bd183e441 \ No newline at end of file diff --git a/test/fixtures/tesla_mock/https___shitposter.club_api_statuses_show_2827873.atom.xml b/test/fixtures/tesla_mock/https___shitposter.club_api_statuses_show_2827873.atom.xml deleted file mode 100644 index 26fdebb49..000000000 --- a/test/fixtures/tesla_mock/https___shitposter.club_api_statuses_show_2827873.atom.xml +++ /dev/null @@ -1,54 +0,0 @@ - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment - New comment by moonman - @<a href="https://shitposter.club/user/9655" class="h-card mention" title="Solidarity for Pigs">neimzr4luzerz</a> @<a href="https://gs.smuglo.li/user/2326" class="h-card mention" title="Dolus_McHonest">dolus</a> childhood poring over Strong's concordance and a koine Greek dictionary, fast forward to 2017 and some fuckstick who translates japanese jackoff material tells me you just need to make it sound right in English - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T08:51:48+00:00 - 2017-05-05T08:51:48+00:00 - - http://activitystrea.ms/schema/1.0/person - https://shitposter.club/user/1 - moonman - EMAIL:shitposterclub@gmail.com XMPP: moon@talk.shitposter.club Matrix Ed25519 fingerprint: 2HuDUTEz3iFN5N3xl6PYp9xZW/EWhgbbt78SrFy4w8o - - - - - - moonman - Generic Enemy - EMAIL:shitposterclub@gmail.com XMPP: moon@talk.shitposter.club Matrix Ed25519 fingerprint: 2HuDUTEz3iFN5N3xl6PYp9xZW/EWhgbbt78SrFy4w8o - - The Moon - - - homepage - https://shitposter.club/moonman - true - - - - - - - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26 - - - - - https://shitposter.club/api/statuses/user_timeline/1.atom - Generic Enemy - - - - https://shitposter.club/avatar/1-96-20170503024316.jpeg - 2017-05-05T11:43:58+00:00 - - - - - diff --git a/test/fixtures/tesla_mock/https___shitposter.club_api_statuses_user_timeline_1.atom.xml b/test/fixtures/tesla_mock/https___shitposter.club_api_statuses_user_timeline_1.atom.xml deleted file mode 100644 index 31df7c2a6..000000000 --- a/test/fixtures/tesla_mock/https___shitposter.club_api_statuses_user_timeline_1.atom.xml +++ /dev/null @@ -1,454 +0,0 @@ - - - GNU social - https://shitposter.club/api/statuses/user_timeline/1.atom - moonman timeline - Updates from moonman on Shitposter Club! - https://shitposter.club/avatar/1-96-20170503024316.jpeg - 2017-05-05T13:24:09+00:00 - - http://activitystrea.ms/schema/1.0/person - https://shitposter.club/user/1 - moonman - EMAIL:shitposterclub@gmail.com XMPP: moon@talk.shitposter.club Matrix Ed25519 fingerprint: 2HuDUTEz3iFN5N3xl6PYp9xZW/EWhgbbt78SrFy4w8o - - - - - - moonman - Generic Enemy - EMAIL:shitposterclub@gmail.com XMPP: moon@talk.shitposter.club Matrix Ed25519 fingerprint: 2HuDUTEz3iFN5N3xl6PYp9xZW/EWhgbbt78SrFy4w8o - - The Moon - - - homepage - https://shitposter.club/moonman - true - - - - - - - - - - - - - - tag:shitposter.club,2017-05-05:subscription:1:person:23190:2017-05-05T11:43:58+00:00 - Generic Enemy (moonman)'s status on Friday, 05-May-2017 11:43:58 UTC - <a href="https://shitposter.club/moonman">Generic Enemy</a> started following <a href="https://noagendasocial.com/@Ma5on">Mason</a>. - - http://activitystrea.ms/schema/1.0/follow - 2017-05-05T11:43:58+00:00 - 2017-05-05T11:43:58+00:00 - - http://activitystrea.ms/schema/1.0/person - https://noagendasocial.com/users/Ma5on - Mason - - - - - - ma5on - Mason - - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=abffa9c14a054d3b - - - - - - - tag:shitposter.club,2017-05-05:subscription:1:person:14357:2017-05-05T10:29:03+00:00 - Generic Enemy (moonman)'s status on Friday, 05-May-2017 10:29:03 UTC - <a href="https://shitposter.club/moonman">Generic Enemy</a> started following <a href="https://mastodon.cloud/@ohyran">Jens Reuterberg</a>. - - http://activitystrea.ms/schema/1.0/follow - 2017-05-05T10:29:03+00:00 - 2017-05-05T10:29:03+00:00 - - http://activitystrea.ms/schema/1.0/person - https://mastodon.cloud/users/ohyran - Jens Reuterberg - RPG-nerd, illustrator, Open Source enthusiast, KDE dude, designer and gay lefty. Might be a cliché - but we will soon find out! - - - - - - ohyran - Jens Reuterberg - RPG-nerd, illustrator, Open Source enthusiast, KDE dude, designer and gay lefty. Might be a cliché - but we will soon find out! - - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=937151d4825a85bf - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:shitposter.club,2017-05-05:noticeId=2828637:objectType=note - New note by moonman - basicall i would just rather have ppl say &quot;i like x and y&quot; than &quot;i'm a nerd&quot; the term can be retired. - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T10:24:54+00:00 - 2017-05-05T10:24:54+00:00 - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=65992b0b9b5e6931 - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2017-05-05:noticeId=2828579:objectType=comment - New comment by moonman - @<a href="https://gs.smuglo.li/user/35497" class="h-card mention" title="Bokuro Bokusawa">boco</a> to be honest i've turned right around and been cruel to other people, i said i'd never do it but it happens again eventually. - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T10:20:33+00:00 - 2017-05-05T10:20:33+00:00 - - - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=c997fc73d7f8a8f0 - - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2017-05-05:noticeId=2828554:objectType=comment - New comment by moonman - @<a href="https://mastodon.cloud/users/ohyran" class="h-card mention" title="Jens Reuterberg">ohyran</a> i won't ever get over bullying but i agree otherwise. i don't go to comic shops too often these days but i got dragged to one last year and the sheer diversity of people enjoying comics now compared to years ago was striking and it pleased me. and i noticed a couple years ago because of youtube i find things i truly enjoy watching, like in-depth videos about electronic parts, didn't exist 20 years ago. it's pretty great. - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T10:18:10+00:00 - 2017-05-05T10:18:10+00:00 - - - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=efae3a23b6e05767 - - - - - - - - tag:shitposter.club,2017-05-05:fave:1:comment:2828502:2017-05-05T10:12:52+00:00 - Favorite - moonman favorited something by ohyran: <p><span class="h-card"><a href="https://shitposter.club/moonman" class="u-url mention">@<span>moonman</span></a></span> fair enough - that distinction makes it clearer...</p><p>On the other hand - those of us who did "pay the price" of being nerdy little kids in the 80's and 90's should strive to get past it anyway (mental health wise not "just get over it") and see the "nerd culture" thing as a blessing of sorts. We are in the optimal spot to do it. (not saying that that is something easy btw just that NOW is the best of time to start talking about it)</p> - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-05T10:12:52+00:00 - 2017-05-05T10:12:52+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:mastodon.cloud,2017-05-05:objectId=6334570:objectType=Status - New comment by ohyran - <p><span class="h-card"><a href="https://shitposter.club/moonman" class="u-url mention">@<span>moonman</span></a></span> fair enough - that distinction makes it clearer...</p><p>On the other hand - those of us who did "pay the price" of being nerdy little kids in the 80's and 90's should strive to get past it anyway (mental health wise not "just get over it") and see the "nerd culture" thing as a blessing of sorts. We are in the optimal spot to do it. (not saying that that is something easy btw just that NOW is the best of time to start talking about it)</p> - - - - - - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=efae3a23b6e05767 - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:shitposter.club,2017-05-05:noticeId=2828496:objectType=note - New note by moonman - things are better now, a lot less kids in america get beaten up and called a fag. still too many. - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T10:11:31+00:00 - 2017-05-05T10:11:31+00:00 - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=c997fc73d7f8a8f0 - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2017-05-05:noticeId=2828457:objectType=comment - New comment by moonman - @<a href="https://shitposter.club/user/21787" class="h-card mention" title="Yukari">cutscenes</a> @<a href="https://gs.smuglo.li/user/28250" class="h-card mention" title="Bricky">thatbrickster</a> @<a href="https://gs.smuglo.li/user/35497" class="h-card mention" title="Bokuro Bokusawa">boco</a> i never understood this because nerds had pocket protectors, which was a draftsman engineer thing and therefore smart, while geeks were people in carnivals who bit heads off small animals. - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T10:07:57+00:00 - 2017-05-05T10:07:57+00:00 - - - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=efae3a23b6e05767 - - - - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2017-05-05:noticeId=2828435:objectType=comment - New comment by moonman - @<a href="https://mastodon.cloud/users/ohyran" class="h-card mention" title="Jens Reuterberg">ohyran</a> since i didn't specify i'm talking about people subjected to physical and psychological abuse and not people that are just mad that more people like comic books now. - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T10:05:07+00:00 - 2017-05-05T10:05:07+00:00 - - - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=efae3a23b6e05767 - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:shitposter.club,2017-05-05:noticeId=2828326:objectType=note - New note by moonman - if you were a &quot;nerd&quot; before, like, 2001 you have permanent excuse to hate this kind of shit.   <a href="https://shitposter.club/file/b79fa5644be0d6f22679136e67b7bf45c9c4a74a55c32dd2d0cf15de4ddd5be5.gif" title="https://shitposter.club/file/b79fa5644be0d6f22679136e67b7bf45c9c4a74a55c32dd2d0cf15de4ddd5be5.gif" class="attachment" id="attachment-662105" rel="nofollow external">https://shitposter.club/attachment/662105</a> - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T09:47:42+00:00 - 2017-05-05T09:47:42+00:00 - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=efae3a23b6e05767 - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:shitposter.club,2017-05-05:noticeId=2828250:objectType=note - New note by moonman - <a href="https://shitposter.club/file/1283e2d4dd8f96b8eeb5d9a16b318e210868aa11386cf0d593891e4c75c9126e.gif" title="https://shitposter.club/file/1283e2d4dd8f96b8eeb5d9a16b318e210868aa11386cf0d593891e4c75c9126e.gif" class="attachment" id="attachment-662098" rel="nofollow external">https://shitposter.club/attachment/662098</a> - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T09:39:06+00:00 - 2017-05-05T09:39:06+00:00 - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=ea8ffae90546f0ab - - - - - - - - tag:shitposter.club,2017-05-05:fave:1:comment:2828161:2017-05-05T09:28:19+00:00 - Favorite - moonman favorited something by kro: @<a href="https://shitposter.club/user/1" class="h-card u-url p-nickname mention" title="Generic Enemy">moonman</a> Till Brooklyn? - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-05T09:28:19+00:00 - 2017-05-05T09:28:19+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:gs.smuglo.li,2017-05-05:noticeId=2188587:objectType=comment - New comment by kro - @<a href="https://shitposter.club/user/1" class="h-card u-url p-nickname mention" title="Generic Enemy">moonman</a> Till Brooklyn? - - - - - - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=d7aa6b5b057ca555 - - - - - - - tag:shitposter.club,2017-05-05:fave:1:comment:2828125:2017-05-05T09:24:56+00:00 - Favorite - moonman favorited something by hardbass2k8: this has obviously interesting implications in various places, for example:<br /> the nationalism of the nazis might not have been real, who would have thought?<br /> socialism is usually promoted to implementation by real douchebags!<br /> your local social justice people might want diversity but they don't want you, m/19, white, why?<br /> amateur soccer club, they want to be the best in the amateur league but actually they just get drunk after training and are 50% overweight.<br /> This is because humans are not capable of telepathy, so if you join a group it doesn't magically align every little bit of your being with the declared group goals.<br /> <br /> Even though you see unmanned group beliefs flying around from time to time, generally groups are created from a bunch of people. they are not a container for people, they are the people inside them.<br /> <br /> so if you see a group that appears to be cool don't think of it as cool because its goals are cool but because its members are cool. if they aren't, tough cookies. don't be the retard and end up on the camp watchtower. - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-05T09:24:56+00:00 - 2017-05-05T09:24:56+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2017-05-05:noticeId=2828125:objectType=comment - New comment by hardbass2k8 - this has obviously interesting implications in various places, for example:<br /> the nationalism of the nazis might not have been real, who would have thought?<br /> socialism is usually promoted to implementation by real douchebags!<br /> your local social justice people might want diversity but they don't want you, m/19, white, why?<br /> amateur soccer club, they want to be the best in the amateur league but actually they just get drunk after training and are 50% overweight.<br /> This is because humans are not capable of telepathy, so if you join a group it doesn't magically align every little bit of your being with the declared group goals.<br /> <br /> Even though you see unmanned group beliefs flying around from time to time, generally groups are created from a bunch of people. they are not a container for people, they are the people inside them.<br /> <br /> so if you see a group that appears to be cool don't think of it as cool because its goals are cool but because its members are cool. if they aren't, tough cookies. don't be the retard and end up on the camp watchtower. - - - - - - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=51b227fe92f6babf - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:shitposter.club,2017-05-05:noticeId=2828128:objectType=note - New note by moonman - In a valid remake of They live, signs would say REBEL, and DON'T GET MARRIED AND HAVE KIDS - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T09:24:23+00:00 - 2017-05-05T09:24:23+00:00 - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=b74397fa766b82c9 - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:shitposter.club,2017-05-05:noticeId=2828104:objectType=note - New note by moonman - <a href="https://shitposter.club/file/4d34178bde99599f31a28928e1666fbd58448d8a22e94ed82222496e4a45cb07.gif" title="https://shitposter.club/file/4d34178bde99599f31a28928e1666fbd58448d8a22e94ed82222496e4a45cb07.gif" class="attachment" id="attachment-662049" rel="nofollow external">https://shitposter.club/attachment/662049</a> - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T09:21:01+00:00 - 2017-05-05T09:21:01+00:00 - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=d7aa6b5b057ca555 - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:shitposter.club,2017-05-05:noticeId=2828102:objectType=note - New note by moonman - when ppl find out i haven't always been serious  <a href="https://shitposter.club/file/5859fa95875342cc65dba0d852f726db158ce28198c326d5f13d9de7c0d2c449.gif" title="https://shitposter.club/file/5859fa95875342cc65dba0d852f726db158ce28198c326d5f13d9de7c0d2c449.gif" class="attachment" id="attachment-662053" rel="nofollow external">https://shitposter.club/attachment/662053</a> - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T09:20:45+00:00 - 2017-05-05T09:20:45+00:00 - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=0a025ac5a570b4ec - - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2017-05-05:noticeId=2828086:objectType=comment - New comment by moonman - @<a href="https://shitposter.club/user/9655" class="h-card mention" title="Solidarity for Pigs">neimzr4luzerz</a> @<a href="https://gs.smuglo.li/user/2326" class="h-card mention" title="Dolus_McHonest">dolus</a> @<a href="https://gs.smuglo.li/user/35497" class="h-card mention" title="Bokuro Bokusawa">boco</a> you are being too serious lol - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T09:17:19+00:00 - 2017-05-05T09:17:19+00:00 - - - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26 - - - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:shitposter.club,2017-05-05:noticeId=2828085:objectType=note - New note by moonman - shitposter dot club  <a href="https://shitposter.club/file/9b084c7210b16abbf4d28594b924a07ef4a2a06f89d901a4c42fb1e243291263.gif" title="https://shitposter.club/file/9b084c7210b16abbf4d28594b924a07ef4a2a06f89d901a4c42fb1e243291263.gif" class="attachment" id="attachment-662047" rel="nofollow external">https://shitposter.club/attachment/662047</a> - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T09:16:50+00:00 - 2017-05-05T09:16:50+00:00 - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=d1ae088a1b91e5e5 - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:shitposter.club,2017-05-05:noticeId=2828061:objectType=note - New note by moonman - even when i lie i tell the truth, is that so hard to understand? - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T09:15:07+00:00 - 2017-05-05T09:15:07+00:00 - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=a516e4b8506b8ef5 - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2017-05-05:noticeId=2828052:objectType=comment - New comment by moonman - @<a href="https://shitposter.club/user/9591" class="h-card mention" title="warum hei&#xDF;en deutschl&#xE4;nder deutschl&#xE4;nder">hardbass2k8</a> history, anthropology. - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T09:14:22+00:00 - 2017-05-05T09:14:22+00:00 - - - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=fe4d7f35b13403ba - - - - - - - diff --git a/test/fixtures/tesla_mock/https___shitposter.club_notice_2827873.json b/test/fixtures/tesla_mock/https___shitposter.club_notice_2827873.json deleted file mode 100644 index 4b7b4df44..000000000 --- a/test/fixtures/tesla_mock/https___shitposter.club_notice_2827873.json +++ /dev/null @@ -1 +0,0 @@ -{"@context":["https://www.w3.org/ns/activitystreams","https://shitposter.club/schemas/litepub-0.1.jsonld",{"@language":"und"}],"actor":"https://shitposter.club/users/moonman","attachment":[],"attributedTo":"https://shitposter.club/users/moonman","cc":["https://shitposter.club/users/moonman/followers"],"content":"@neimzr4luzerz @dolus childhood poring over Strong's concordance and a koine Greek dictionary, fast forward to 2017 and some fuckstick who translates japanese jackoff material tells me you just need to make it sound right in English","context":"tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26","conversation":"tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26","id":"tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment","inReplyTo":"tag:shitposter.club,2017-05-05:noticeId=2827849:objectType=comment","inReplyToStatusId":2827849,"published":"2017-05-05T08:51:48Z","sensitive":false,"summary":null,"tag":[],"to":["https://www.w3.org/ns/activitystreams#Public"],"type":"Note"} \ No newline at end of file diff --git a/test/fixtures/tesla_mock/https___social.heldscal.la_api_statuses_user_timeline_23211.atom.xml b/test/fixtures/tesla_mock/https___social.heldscal.la_api_statuses_user_timeline_23211.atom.xml deleted file mode 100644 index 6cba5c28f..000000000 --- a/test/fixtures/tesla_mock/https___social.heldscal.la_api_statuses_user_timeline_23211.atom.xml +++ /dev/null @@ -1,591 +0,0 @@ - - - GNU social - https://social.heldscal.la/api/statuses/user_timeline/23211.atom - lambadalambda timeline - Updates from lambadalambda on social.heldscal.la! - https://social.heldscal.la/avatar/23211-96-20170416114255.jpeg - 2017-05-05T12:01:21+00:00 - - http://activitystrea.ms/schema/1.0/person - https://social.heldscal.la/user/23211 - lambadalambda - Call me Deacon Blues. - - - - - - lambadalambda - Constance Variable - Call me Deacon Blues. - - Berlin - - - homepage - https://heldscal.la - true - - - - - - - - - - - - - - tag:social.heldscal.la,2017-05-05:fave:23211:comment:2063249:2017-05-05T11:40:21+00:00 - Favorite - lambadalambda favorited something by tatiana: <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> they will start complaining about this, but won't come up with any solutions)</p> - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-05T11:40:21+00:00 - 2017-05-05T11:40:21+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:social.weho.st,2017-05-05:objectId=172033:objectType=Status - New comment by tatiana - <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> they will start complaining about this, but won't come up with any solutions)</p> - - - - - - - tag:social.heldscal.la,2017-05-05:objectType=thread:nonce=e95b99adc050e198 - - - - - - - tag:social.heldscal.la,2017-05-05:fave:23211:comment:2063041:2017-05-05T11:27:28+00:00 - Favorite - lambadalambda favorited something by kat: @<a href="https://social.heldscal.la/lambadalambda" class="h-card mention" title="Constance Variable">lambadalambda</a> if the admin reading mine would delete a few it would be really useful in prioritising.  - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-05T11:27:28+00:00 - 2017-05-05T11:27:28+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:quitter.se,2017-05-05:noticeId=11807959:objectType=comment - New comment by kat - @<a href="https://social.heldscal.la/lambadalambda" class="h-card mention" title="Constance Variable">lambadalambda</a> if the admin reading mine would delete a few it would be really useful in prioritising.  - - - - - - - tag:social.heldscal.la,2017-05-05:objectType=thread:nonce=e95b99adc050e198 - - - - - - - tag:social.heldscal.la,2017-05-05:noticeId=2062924:objectType=note - lambadalambda repeated a notice by nielsk - RT @nielsk @<a href="https://social.heldscal.la/user/23211" class="h-card u-url p-nickname mention" title="Constance Variable">lambadalambda</a> but there are soooo many, where should I start to read? - - http://activitystrea.ms/schema/1.0/share - 2017-05-05T11:09:37+00:00 - 2017-05-05T11:09:37+00:00 - - http://activitystrea.ms/schema/1.0/activity - tag:mastodon.social,2017-05-05:objectId=5024471:objectType=Status - - <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> but there are soooo many, where should I start to read?</p> - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T11:05:18+00:00 - 2017-05-05T11:05:18+00:00 - - http://activitystrea.ms/schema/1.0/person - https://mastodon.social/users/nielsk - nielsk - Sysadmin by day and ehm… sysadmin by night. Besides that old video games, Japan, economics and some other stuff - - - - - - nielsk - nielsk - Sysadmin by day and ehm… sysadmin by night. Besides that old video games, Japan, economics and some other stuff - - - - http://activitystrea.ms/schema/1.0/comment - tag:mastodon.social,2017-05-05:objectId=5024471:objectType=Status - New comment by nielsk - <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> but there are soooo many, where should I start to read?</p> - - - - - - - tag:social.heldscal.la,2017-05-05:objectType=thread:nonce=e95b99adc050e198 - - - - https://mastodon.social/users/nielsk.atom - nielsk - - - https://social.heldscal.la/avatar/29849-96-20170428120041.jpeg - 2017-05-05T11:06:32+00:00 - - - - tag:social.heldscal.la,2017-05-05:objectType=thread:nonce=e95b99adc050e198 - - - - - - - tag:social.heldscal.la,2017-05-05:fave:23211:comment:2062875:2017-05-05T11:09:27+00:00 - Favorite - lambadalambda favorited something by nielsk: <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> but there are soooo many, where should I start to read?</p> - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-05T11:09:27+00:00 - 2017-05-05T11:09:27+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:mastodon.social,2017-05-05:objectId=5024471:objectType=Status - New comment by nielsk - <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> but there are soooo many, where should I start to read?</p> - - - - - - - tag:social.heldscal.la,2017-05-05:objectType=thread:nonce=e95b99adc050e198 - - - - - - - tag:social.heldscal.la,2017-05-05:fave:23211:comment:2062863:2017-05-05T11:09:11+00:00 - Favorite - lambadalambda favorited something by kasil: <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> surely, google is not that evil !</p> - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-05T11:09:11+00:00 - 2017-05-05T11:09:11+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:loutre.info,2017-05-05:objectId=23331:objectType=Status - New comment by kasil - <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> surely, google is not that evil !</p> - - - - - - - tag:social.heldscal.la,2017-05-05:objectType=thread:nonce=e95b99adc050e198 - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:social.heldscal.la,2017-05-05:noticeId=2062767:objectType=comment - New comment by lambadalambda - @<a href="https://sealion.club/user/4" class="h-card u-url p-nickname mention" title="dewoo &#x274E;">dwmatiz</a> dunno, probably. - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T10:55:17+00:00 - 2017-05-05T10:55:17+00:00 - - - - tag:social.heldscal.la,2017-05-05:objectType=thread:nonce=e95b99adc050e198 - - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:social.heldscal.la,2017-05-05:noticeId=2062705:objectType=comment - New comment by lambadalambda - @<a href="https://gs.smuglo.li/user/28250" class="h-card u-url p-nickname mention" title="Bricky">thatbrickster</a> I do it, too. - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T10:48:12+00:00 - 2017-05-05T10:48:12+00:00 - - - - tag:social.heldscal.la,2017-05-05:objectType=thread:nonce=e95b99adc050e198 - - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:social.heldscal.la,2017-05-05:noticeId=2062620:objectType=comment - New comment by lambadalambda - @<a href="https://social.tchncs.de/users/israuor" class="h-card u-url p-nickname mention" title="Israuor &#x2642;">israuor</a> @<a href="https://mastodon.gougere.fr/users/bortzmeyer" class="h-card u-url p-nickname mention" title="S. Bortzmeyer &#x2705;">bortzmeyer</a> so, 99%. 100% for 'normal' people. - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T10:38:52+00:00 - 2017-05-05T10:38:52+00:00 - - - - tag:social.heldscal.la,2017-05-05:objectType=thread:nonce=e95b99adc050e198 - - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.heldscal.la,2017-05-05:noticeId=2062583:objectType=note - New note by lambadalambda - I wonder what'll happen when people realize the admin at their mail hoster can read all their e-mails. - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T10:35:45+00:00 - 2017-05-05T10:35:45+00:00 - - tag:social.heldscal.la,2017-05-05:objectType=thread:nonce=e95b99adc050e198 - - - - - - - tag:social.heldscal.la,2017-05-05:subscription:23211:person:35708:2017-05-05T09:34:46+00:00 - Constance Variable (lambadalambda@social.heldscal.la)'s status on Friday, 05-May-2017 09:34:46 UTC - <a href="https://social.heldscal.la/lambadalambda">Constance Variable</a> started following <a href="https://mastodon.social/@milouse">milouse</a>. - - http://activitystrea.ms/schema/1.0/follow - 2017-05-05T09:34:46+00:00 - 2017-05-05T09:34:46+00:00 - - http://activitystrea.ms/schema/1.0/person - https://mastodon.social/users/milouse - milouse - #Scout leader #sgdf, interested in #openweb, #semanticweb, #privacy, #foss and #socialeconomy. 0xA714ECAC8C9CEE3D - - - - - - milouse - milouse - #Scout leader #sgdf, interested in #openweb, #semanticweb, #privacy, #foss and #socialeconomy. 0xA714ECAC8C9CEE3D - - - tag:social.heldscal.la,2017-05-05:objectType=thread:nonce=26ca19a355bb6135 - - - - - - - tag:social.heldscal.la,2017-05-05:noticeId=2061871:objectType=note - lambadalambda repeated a notice by safebot - RT @<a href="https://gs.smuglo.li/user/25857" class="h-card u-url p-nickname mention" title="safebot">safebot</a> #<span class="tag"><a href="https://social.heldscal.la/tag/cheers" rel="tag">cheers</a></span> <a href="https://gs.smuglo.li/attachment/456444" title="https://gs.smuglo.li/attachment/456444" rel="nofollow external noreferrer" class="attachment" id="attachment-432334">https://gs.smuglo.li/attachment/456444</a> - - http://activitystrea.ms/schema/1.0/share - 2017-05-05T09:16:17+00:00 - 2017-05-05T09:16:17+00:00 - - http://activitystrea.ms/schema/1.0/activity - tag:gs.smuglo.li,2017-05-05:noticeId=2188073:objectType=note - - #<span class="tag"><a href="https://gs.smuglo.li/tag/cheers" rel="tag">cheers</a></span> <a href="https://gs.smuglo.li/file/5099e73c83da778cd032a721e96880f99a868b712be2975d08238547a5ba06c7.jpg" title="https://gs.smuglo.li/file/5099e73c83da778cd032a721e96880f99a868b712be2975d08238547a5ba06c7.jpg" rel="nofollow noreferrer" class="attachment">https://gs.smuglo.li/attachment/456444</a> - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T08:36:53+00:00 - 2017-05-05T08:36:53+00:00 - - http://activitystrea.ms/schema/1.0/person - https://gs.smuglo.li/user/25857 - safebot - - - - - - safebot - safebot - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.smuglo.li,2017-05-05:noticeId=2188073:objectType=note - New note by safebot - #<span class="tag"><a href="https://gs.smuglo.li/tag/cheers" rel="tag">cheers</a></span> <a href="https://gs.smuglo.li/file/5099e73c83da778cd032a721e96880f99a868b712be2975d08238547a5ba06c7.jpg" title="https://gs.smuglo.li/file/5099e73c83da778cd032a721e96880f99a868b712be2975d08238547a5ba06c7.jpg" rel="nofollow noreferrer" class="attachment">https://gs.smuglo.li/attachment/456444</a> - - - - - https://gs.smuglo.li/conversation/1009429 - - - - https://gs.smuglo.li/api/statuses/user_timeline/25857.atom - safebot - - - https://social.heldscal.la/avatar/25719-original-20161215233234.jpeg - 2017-05-05T12:00:57+00:00 - - - - https://gs.smuglo.li/conversation/1009429 - - - - - - - tag:social.heldscal.la,2017-05-05:fave:23211:comment:2061643:2017-05-05T09:12:50+00:00 - Favorite - lambadalambda favorited something by moonman: @<a href="https://shitposter.club/user/9655" class="h-card mention" title="Solidarity for Pigs">neimzr4luzerz</a> @<a href="https://gs.smuglo.li/user/2326" class="h-card mention" title="Dolus_McHonest">dolus</a> childhood poring over Strong's concordance and a koine Greek dictionary, fast forward to 2017 and some fuckstick who translates japanese jackoff material tells me you just need to make it sound right in English - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-05T09:12:50+00:00 - 2017-05-05T09:12:50+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment - New comment by moonman - @<a href="https://shitposter.club/user/9655" class="h-card mention" title="Solidarity for Pigs">neimzr4luzerz</a> @<a href="https://gs.smuglo.li/user/2326" class="h-card mention" title="Dolus_McHonest">dolus</a> childhood poring over Strong's concordance and a koine Greek dictionary, fast forward to 2017 and some fuckstick who translates japanese jackoff material tells me you just need to make it sound right in English - - - - - - - tag:social.heldscal.la,2017-05-05:objectType=thread:nonce=55ead90125cd4bd4 - - - - - - - tag:social.heldscal.la,2017-05-05:fave:23211:comment:2061696:2017-05-05T09:06:10+00:00 - Favorite - lambadalambda favorited something by moonman: @<a href="https://shitposter.club/user/9655" class="h-card mention" title="Solidarity for Pigs">neimzr4luzerz</a> <br /> <span class="greentext">&gt; (((common era)))</span> - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-05T09:06:10+00:00 - 2017-05-05T09:06:10+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2017-05-05:noticeId=2827918:objectType=comment - New comment by moonman - @<a href="https://shitposter.club/user/9655" class="h-card mention" title="Solidarity for Pigs">neimzr4luzerz</a> <br /> <span class="greentext">&gt; (((common era)))</span> - - - - - - - tag:social.heldscal.la,2017-05-05:objectType=thread:nonce=55ead90125cd4bd4 - - - - - - - tag:social.heldscal.la,2017-05-05:fave:23211:note:2061673:2017-05-05T08:58:28+00:00 - Favorite - lambadalambda favorited something by moonman: discussion is one thing but any argument I've heard over and over again for the last three decades is going to go unanswered. - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-05T08:58:28+00:00 - 2017-05-05T08:58:28+00:00 - - http://activitystrea.ms/schema/1.0/note - tag:shitposter.club,2017-05-05:noticeId=2827895:objectType=note - New note by moonman - discussion is one thing but any argument I've heard over and over again for the last three decades is going to go unanswered. - - - - - - - https://shitposter.club/conversation/1390494 - - - - - - - tag:social.heldscal.la,2017-05-05:fave:23211:comment:2061280:2017-05-05T08:47:38+00:00 - Favorite - lambadalambda favorited something by moonman: @<a href="https://shitposter.club/user/9655" class="h-card mention" title="Solidarity for Pigs">neimzr4luzerz</a> sex is for procreation and as an expression of intimacy between commited couples, it is a sacramental act - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-05T08:47:38+00:00 - 2017-05-05T08:47:38+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2017-05-05:noticeId=2827561:objectType=comment - New comment by moonman - @<a href="https://shitposter.club/user/9655" class="h-card mention" title="Solidarity for Pigs">neimzr4luzerz</a> sex is for procreation and as an expression of intimacy between commited couples, it is a sacramental act - - - - - - - tag:social.heldscal.la,2017-05-05:objectType=thread:nonce=55ead90125cd4bd4 - - - - - - - tag:social.heldscal.la,2017-05-05:fave:23211:note:2061535:2017-05-05T08:40:55+00:00 - Favorite - lambadalambda favorited something by fortune: What did Mickey Mouse get for Christmas?<br /> <br /> A Dan Quayle watch.<br /> <br /> -- heard from a Mike Dukakis field worker - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-05T08:40:55+00:00 - 2017-05-05T08:40:55+00:00 - - http://activitystrea.ms/schema/1.0/note - tag:social.heldscal.la,2017-05-05:noticeId=2061535:objectType=note - New note by fortune - What did Mickey Mouse get for Christmas?<br /> <br /> A Dan Quayle watch.<br /> <br /> -- heard from a Mike Dukakis field worker - - - - - - - tag:social.heldscal.la,2017-05-05:objectType=thread:nonce=5185e5c145ee4762 - - - - - - - tag:social.heldscal.la,2017-05-05:fave:23211:comment:2061421:2017-05-05T08:36:27+00:00 - Favorite - lambadalambda favorited something by moonman: @<a href="https://maly.io/users/sonya" class="h-card mention" title="Sonya Mann ✅">sonya</a> banned from 4chan. you better watch ou. i'm trouble, y'hear? - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-05T08:36:27+00:00 - 2017-05-05T08:36:27+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2017-05-05:noticeId=2827689:objectType=comment - New comment by moonman - @<a href="https://maly.io/users/sonya" class="h-card mention" title="Sonya Mann ✅">sonya</a> banned from 4chan. you better watch ou. i'm trouble, y'hear? - - - - - - - https://shitposter.club/conversation/1389345 - - - - - - - tag:social.heldscal.la,2017-05-05:fave:23211:comment:2061351:2017-05-05T08:28:03+00:00 - Favorite - lambadalambda favorited something by moonman: @<a href="https://social.heldscal.la/user/29138" class="h-card mention" title="Claes Wallin (韋嘉誠)">clacke</a> is that the sequel to Time Crisis - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-05T08:28:03+00:00 - 2017-05-05T08:28:03+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2017-05-05:noticeId=2827630:objectType=comment - New comment by moonman - @<a href="https://social.heldscal.la/user/29138" class="h-card mention" title="Claes Wallin (韋嘉誠)">clacke</a> is that the sequel to Time Crisis - - - - - - - https://shitposter.club/conversation/1385528 - - - - - - - tag:social.heldscal.la,2017-05-05:fave:23211:comment:2061339:2017-05-05T08:21:05+00:00 - Favorite - lambadalambda favorited something by hardbass2k8: @<a href="https://social.heldscal.la/user/23211" class="h-card mention" title="Constance Variable">lambadalambda</a> pretty sure it's money laundering - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-05T08:21:05+00:00 - 2017-05-05T08:21:05+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2017-05-05:noticeId=2827617:objectType=comment - New comment by hardbass2k8 - @<a href="https://social.heldscal.la/user/23211" class="h-card mention" title="Constance Variable">lambadalambda</a> pretty sure it's money laundering - - - - - - - https://shitposter.club/conversation/1387523 - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.heldscal.la,2017-05-05:noticeId=2061303:objectType=note - New note by lambadalambda - It's got tattoos, it's got a pierced hood<br /> It's got generation X<br /> It's got lesbians, and vitriol<br /> And sadomasochistic latex sex<br /> It's got Mighty Morphin' power brokers<br /> And Tanya Harding nude<br /> Macrobiotic lacto-vegan non-confrontational free range food<br /> It's got the handshake, peace talk, non-aggression pact<br /> A multicultural integration of segregated historical facts<br /> <br /> #<span class="tag"><a href="https://social.heldscal.la/tag/nsfw" rel="tag">nsfw</a></span> <a href="https://social.heldscal.la/file/61c13b99c92f40ec4865e7a3830da340b187e3de70d94b8da38fd2138bbede3a.jpg" title="https://social.heldscal.la/file/61c13b99c92f40ec4865e7a3830da340b187e3de70d94b8da38fd2138bbede3a.jpg" rel="nofollow external noreferrer" class="attachment" id="attachment-432199">https://social.heldscal.la/attachment/432199</a> <a href="https://social.heldscal.la/file/a88bba1a324da68ee2cfdbcd1c4cde60bd9553298244d6f81731270b71aa80df.jpg" title="https://social.heldscal.la/file/a88bba1a324da68ee2cfdbcd1c4cde60bd9553298244d6f81731270b71aa80df.jpg" rel="nofollow external noreferrer" class="attachment" id="attachment-432200">https://social.heldscal.la/attachment/432200</a> <a href="https://social.heldscal.la/file/887329a303250e73dc2eea06b1f0512fcac4b9d1b534068f03c45f00d5b21c39.jpg" title="https://social.heldscal.la/file/887329a303250e73dc2eea06b1f0512fcac4b9d1b534068f03c45f00d5b21c39.jpg" rel="nofollow external noreferrer" class="attachment" id="attachment-432201">https://social.heldscal.la/attachment/432201</a> <a href="https://social.heldscal.la/file/6d7a1ec15c1368c4c68810434d24da528606fcbccdd1da97b25affafeeb6ffda.jpg" title="https://social.heldscal.la/file/6d7a1ec15c1368c4c68810434d24da528606fcbccdd1da97b25affafeeb6ffda.jpg" rel="nofollow external noreferrer" class="attachment" id="attachment-432202">https://social.heldscal.la/attachment/432202</a> <a href="https://social.heldscal.la/file/2f55f2bb028eb9be744cc82b35a6b86b496d8c3924c700aff55a872ff11df54c.jpg" title="https://social.heldscal.la/file/2f55f2bb028eb9be744cc82b35a6b86b496d8c3924c700aff55a872ff11df54c.jpg" rel="nofollow external noreferrer" class="attachment" id="attachment-432203">https://social.heldscal.la/attachment/432203</a> - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T08:17:08+00:00 - 2017-05-05T08:17:08+00:00 - - tag:social.heldscal.la,2017-05-05:objectType=thread:nonce=bb6f4343036970e8 - - - - - - - - - - - - diff --git a/test/fixtures/tesla_mock/https___social.heldscal.la_api_statuses_user_timeline_29191.atom.xml b/test/fixtures/tesla_mock/https___social.heldscal.la_api_statuses_user_timeline_29191.atom.xml deleted file mode 100644 index f70fbc695..000000000 --- a/test/fixtures/tesla_mock/https___social.heldscal.la_api_statuses_user_timeline_29191.atom.xml +++ /dev/null @@ -1,719 +0,0 @@ - - - GNU social - https://social.heldscal.la/api/statuses/user_timeline/29191.atom - shp timeline - Updates from shp on social.heldscal.la! - https://social.heldscal.la/avatar/29191-96-20170421154949.jpeg - 2017-05-05T11:57:06+00:00 - - http://activitystrea.ms/schema/1.0/person - https://social.heldscal.la/user/29191 - shp - cofe - - - - - - shp - shp - cofe - - cofe - - - - - - - - - - - - - - tag:social.heldscal.la,2017-04-29:noticeId=1967657:objectType=note - shp repeated a notice by lain - RT @<a href="https://social.heldscal.la/user/37181" class="h-card u-url p-nickname mention" title="Lain Iwakura">lain</a> @<a href="https://social.heldscal.la/user/29191" class="h-card u-url p-nickname mention" title="shp">shp</a> @<a href="https://social.heldscal.la/user/23211" class="h-card u-url p-nickname mention">lambadalambda</a> cofe. - - http://activitystrea.ms/schema/1.0/share - 2017-04-29T18:19:34+00:00 - 2017-04-29T18:19:34+00:00 - - http://activitystrea.ms/schema/1.0/activity - https://pleroma.soykaf.com/activities/43d12c05-db3f-4f3d-bee1-d676f264490c - - <a href="https://pleroma.soykaf.com/users/shp">@shp</a> <a href="https://social.heldscal.la/user/23211">@lambadalambda@social.heldscal.la</a> cofe. - - http://activitystrea.ms/schema/1.0/post - 2017-04-29T18:14:36+00:00 - 2017-04-29T18:14:36+00:00 - - http://activitystrea.ms/schema/1.0/person - https://pleroma.soykaf.com/users/lain - lain - Test account - - - - - - lain - Lain Iwakura - Test account - - - - http://activitystrea.ms/schema/1.0/note - https://pleroma.soykaf.com/activities/43d12c05-db3f-4f3d-bee1-d676f264490c - New note by lain - <a href="https://pleroma.soykaf.com/users/shp">@shp</a> <a href="https://social.heldscal.la/user/23211">@lambadalambda@social.heldscal.la</a> cofe. - - - - - tag:social.heldscal.la,2017-04-29:objectType=thread:nonce=e0b75431888efdab - - - https://pleroma.soykaf.com/users/lain/feed.atom - Lain Iwakura - - - https://social.heldscal.la/avatar/43188-96-20170429172422.jpeg - 2017-05-05T08:38:03+00:00 - - - - tag:social.heldscal.la,2017-04-29:objectType=thread:nonce=e0b75431888efdab - - - - - - - tag:social.heldscal.la,2017-04-27:subscription:29191:person:29558:2017-04-27T17:26:37+00:00 - shp (shp@social.heldscal.la)'s status on Thursday, 27-Apr-2017 17:26:37 UTC - <a href="https://social.heldscal.la/shp">shp</a> started following <a href="https://gs.smuglo.li/kfist">KFist</a>. - - http://activitystrea.ms/schema/1.0/follow - 2017-04-27T17:26:37+00:00 - 2017-04-27T17:26:37+00:00 - - http://activitystrea.ms/schema/1.0/person - https://gs.smuglo.li/user/28051 - KFist - I stream thanks to @nepfag. I also drink, shitpost, and fly planes. I visited Japan and it changed my life. Do you love your station? - - - - - - kfist - KFist - I stream thanks to @nepfag. I also drink, shitpost, and fly planes. I visited Japan and it changed my life. Do you love your station? - - homepage - http://smuglo.li:8000/stream.m3u - true - - - - tag:social.heldscal.la,2017-04-27:objectType=thread:nonce=f766240d13ed9c2e - - - - - - - tag:social.heldscal.la,2017-04-27:noticeId=1933030:objectType=note - shp repeated a notice by shpbot - RT @<a href="https://gs.archae.me/user/4687" class="h-card u-url p-nickname mention" title="shpbot">shpbot</a> &gt;QuakeC - - http://activitystrea.ms/schema/1.0/share - 2017-04-27T17:21:10+00:00 - 2017-04-27T17:21:10+00:00 - - http://activitystrea.ms/schema/1.0/activity - tag:gs.archae.me,2017-04-27:noticeId=760881:objectType=note - - <span class='greentext'>&gt;QuakeC</span> - - http://activitystrea.ms/schema/1.0/post - 2017-04-27T17:15:13+00:00 - 2017-04-27T17:15:13+00:00 - - http://activitystrea.ms/schema/1.0/person - https://gs.archae.me/user/4687 - shpbot - - - - - - shpbot - shpbot - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.archae.me,2017-04-27:noticeId=760881:objectType=note - New note by shpbot - <span class='greentext'>&gt;QuakeC</span> - - - - - https://gs.archae.me/conversation/318362 - - - https://gs.archae.me/api/statuses/user_timeline/4687.atom - shpbot - - - https://social.heldscal.la/avatar/31581-original-20170405170019.jpeg - 2017-05-05T11:45:08+00:00 - - - - https://gs.archae.me/conversation/318362 - - - - - - - tag:social.heldscal.la,2017-04-27:subscription:29191:person:23226:2017-04-27T17:20:48+00:00 - shp (shp@social.heldscal.la)'s status on Thursday, 27-Apr-2017 17:20:48 UTC - <a href="https://social.heldscal.la/shp">shp</a> started following <a href="http://quitter.se/taknamay">Internet Turtle Ⓐ 🏴 ✅</a>. - - http://activitystrea.ms/schema/1.0/follow - 2017-04-27T17:20:48+00:00 - 2017-04-27T17:20:48+00:00 - - http://activitystrea.ms/schema/1.0/person - http://quitter.se/user/115823 - Internet Turtle Ⓐ 🏴 ✅ - Scheme programmer, Novice esperantist, Spiritual naturalist - Will listen to your problems for free - XMPP: DarkDungeons94 at chatme.im - - - - - - taknamay - Internet Turtle Ⓐ 🏴 ✅ - Scheme programmer, Novice esperantist, Spiritual naturalist - Will listen to your problems for free - XMPP: DarkDungeons94 at chatme.im - - New Jersey, United States - - - homepage - https://quitter.se/taknamay - true - - - - tag:social.heldscal.la,2017-04-27:objectType=thread:nonce=a66b1fb22020c152 - - - - - - - tag:social.heldscal.la,2017-04-27:subscription:29191:person:29302:2017-04-27T17:20:33+00:00 - shp (shp@social.heldscal.la)'s status on Thursday, 27-Apr-2017 17:20:33 UTC - <a href="https://social.heldscal.la/shp">shp</a> started following <a href="https://icosahedron.website/@Trev">Chillidan Stormrave</a>. - - http://activitystrea.ms/schema/1.0/follow - 2017-04-27T17:20:33+00:00 - 2017-04-27T17:20:33+00:00 - - http://activitystrea.ms/schema/1.0/person - https://icosahedron.website/users/Trev - Trev Prime - web tech, music, ethics. radical individualist. kinda queer. love thy neighbor. always open for conversation. - - - - - - trev - Trev Prime - web tech, music, ethics. radical individualist. kinda queer. love thy neighbor. always open for conversation. - - - tag:social.heldscal.la,2017-04-27:objectType=thread:nonce=781c05bd64ad9520 - - - - - - - tag:social.heldscal.la,2017-04-27:subscription:29191:person:29367:2017-04-27T17:20:27+00:00 - shp (shp@social.heldscal.la)'s status on Thursday, 27-Apr-2017 17:20:27 UTC - <a href="https://social.heldscal.la/shp">shp</a> started following <a href="https://gs.kawa-kun.com/aya">射命丸 文</a>. - - http://activitystrea.ms/schema/1.0/follow - 2017-04-27T17:20:27+00:00 - 2017-04-27T17:20:27+00:00 - - http://activitystrea.ms/schema/1.0/person - https://gs.kawa-kun.com/user/4885 - 射命丸 文 - Traditional Reporter of Fantasy - - - - - - aya - 射命丸 文 - Traditional Reporter of Fantasy - - Gensōkyō - - - homepage - https://danbooru.donmai.us - true - - - - tag:social.heldscal.la,2017-04-27:objectType=thread:nonce=5921da7a934e47ca - - - - - - - tag:social.heldscal.la,2017-04-27:subscription:29191:person:27773:2017-04-27T17:20:18+00:00 - shp (shp@social.heldscal.la)'s status on Thursday, 27-Apr-2017 17:20:18 UTC - <a href="https://social.heldscal.la/shp">shp</a> started following <a href="https://gs.smuglo.li/japananon">JapanAnon</a>. - - http://activitystrea.ms/schema/1.0/follow - 2017-04-27T17:20:18+00:00 - 2017-04-27T17:20:18+00:00 - - http://activitystrea.ms/schema/1.0/person - https://gs.smuglo.li/user/27299 - JapanAnon - 匿名でしていてね! - - - - - - japananon - JapanAnon - 匿名でしていてね! - - ワイヤード - - - homepage - http://www.anonymous-japan.org - true - - - - tag:social.heldscal.la,2017-04-27:objectType=thread:nonce=ae3d819865886cba - - - - - - - tag:social.heldscal.la,2017-04-27:subscription:29191:person:36560:2017-04-27T17:19:30+00:00 - shp (shp@social.heldscal.la)'s status on Thursday, 27-Apr-2017 17:19:30 UTC - <a href="https://social.heldscal.la/shp">shp</a> started following <a href="https://shitposter.club/wareya">wareya</a>. - - http://activitystrea.ms/schema/1.0/follow - 2017-04-27T17:19:30+00:00 - 2017-04-27T17:19:30+00:00 - - http://activitystrea.ms/schema/1.0/person - https://shitposter.club/user/15439 - wareya - Who are you to defy such a perfect being that is the machine? 日本語難しいけど頑張るぜ github.com/wareya wareya.moe Short: reya or war, never "ware" - - - - - - wareya - wareya - Who are you to defy such a perfect being that is the machine? 日本語難しいけど頑張るぜ github.com/wareya wareya.moe Short: reya or war, never "ware" - - - tag:social.heldscal.la,2017-04-27:objectType=thread:nonce=bd88a3cd20b5a418 - - - - - - - tag:social.heldscal.la,2017-04-27:subscription:29191:person:41176:2017-04-27T17:19:21+00:00 - shp (shp@social.heldscal.la)'s status on Thursday, 27-Apr-2017 17:19:21 UTC - <a href="https://social.heldscal.la/shp">shp</a> started following <a href="https://hakui.club/takeshitakenji">竹下憲二 (白)</a>. - - http://activitystrea.ms/schema/1.0/follow - 2017-04-27T17:19:21+00:00 - 2017-04-27T17:19:21+00:00 - - http://activitystrea.ms/schema/1.0/person - https://hakui.club/user/6 - 竹下憲二 (白) - Oh boy. - - - - - - takeshitakenji - 竹下憲二 (白) - Oh boy. - - Seattle, WA - - - homepage - http://gs.kawa-kun.com - true - - - - tag:social.heldscal.la,2017-04-27:objectType=thread:nonce=b139a673deba6963 - - - - - - - tag:social.heldscal.la,2017-04-27:fave:29191:note:1932205:2017-04-27T17:17:46+00:00 - Favorite - shp favorited something by dolus: Looks like Merry is pussing out and caving to pressure. Sad. <a href="https://gs.smuglo.li/file/23e37de3c321248d3f322d8ec042372914568ab4c9431a94e568a61b8146587f.png" title="https://gs.smuglo.li/file/23e37de3c321248d3f322d8ec042372914568ab4c9431a94e568a61b8146587f.png" rel="nofollow noreferrer" class="attachment">https://gs.smuglo.li/attachment/432294</a> <a href="https://gs.smuglo.li/file/e5a9549a19986d59d51750090910f47c186787adf02b2b6ac58df37556887297.png" title="https://gs.smuglo.li/file/e5a9549a19986d59d51750090910f47c186787adf02b2b6ac58df37556887297.png" rel="nofollow noreferrer" class="attachment">https://gs.smuglo.li/attachment/432295</a> <a href="https://gs.smuglo.li/file/2fdfabbc8ab0b8dc135903a8c48c29b440d1f97446b98ced4ad14a54d3b5d41f.png" title="https://gs.smuglo.li/file/2fdfabbc8ab0b8dc135903a8c48c29b440d1f97446b98ced4ad14a54d3b5d41f.png" rel="nofollow noreferrer" class="attachment">https://gs.smuglo.li/attachment/432296</a> <a href="https://gs.smuglo.li/file/af605d7c6fe3a8c26c6d334c2a8e0005f7e86a266f14a5b3755e7d3ac4e226de.png" title="https://gs.smuglo.li/file/af605d7c6fe3a8c26c6d334c2a8e0005f7e86a266f14a5b3755e7d3ac4e226de.png" rel="nofollow noreferrer" class="attachment">https://gs.smuglo.li/attachment/432297</a> - - http://activitystrea.ms/schema/1.0/favorite - 2017-04-27T17:17:46+00:00 - 2017-04-27T17:17:46+00:00 - - http://activitystrea.ms/schema/1.0/note - tag:gs.smuglo.li,2017-04-27:noticeId=2065465:objectType=note - New note by dolus - Looks like Merry is pussing out and caving to pressure. Sad. <a href="https://gs.smuglo.li/file/23e37de3c321248d3f322d8ec042372914568ab4c9431a94e568a61b8146587f.png" title="https://gs.smuglo.li/file/23e37de3c321248d3f322d8ec042372914568ab4c9431a94e568a61b8146587f.png" rel="nofollow noreferrer" class="attachment">https://gs.smuglo.li/attachment/432294</a> <a href="https://gs.smuglo.li/file/e5a9549a19986d59d51750090910f47c186787adf02b2b6ac58df37556887297.png" title="https://gs.smuglo.li/file/e5a9549a19986d59d51750090910f47c186787adf02b2b6ac58df37556887297.png" rel="nofollow noreferrer" class="attachment">https://gs.smuglo.li/attachment/432295</a> <a href="https://gs.smuglo.li/file/2fdfabbc8ab0b8dc135903a8c48c29b440d1f97446b98ced4ad14a54d3b5d41f.png" title="https://gs.smuglo.li/file/2fdfabbc8ab0b8dc135903a8c48c29b440d1f97446b98ced4ad14a54d3b5d41f.png" rel="nofollow noreferrer" class="attachment">https://gs.smuglo.li/attachment/432296</a> <a href="https://gs.smuglo.li/file/af605d7c6fe3a8c26c6d334c2a8e0005f7e86a266f14a5b3755e7d3ac4e226de.png" title="https://gs.smuglo.li/file/af605d7c6fe3a8c26c6d334c2a8e0005f7e86a266f14a5b3755e7d3ac4e226de.png" rel="nofollow noreferrer" class="attachment">https://gs.smuglo.li/attachment/432297</a> - - - - - - - https://gs.smuglo.li/conversation/927473 - - - - - - - tag:social.heldscal.la,2017-04-27:fave:29191:note:1932492:2017-04-27T17:13:55+00:00 - Favorite - shp favorited something by zemichi: <a href="https://gs.smuglo.li/file/1d45ea4ffc95f15037f361b56ad6b89f8451b70ad1ff7a03b7bb0345b8e2227c.jpg" title="https://gs.smuglo.li/file/1d45ea4ffc95f15037f361b56ad6b89f8451b70ad1ff7a03b7bb0345b8e2227c.jpg" rel="nofollow noreferrer" class="attachment">https://gs.smuglo.li/attachment/432344</a><br /> that's a lot of loli - - http://activitystrea.ms/schema/1.0/favorite - 2017-04-27T17:13:55+00:00 - 2017-04-27T17:13:55+00:00 - - http://activitystrea.ms/schema/1.0/note - tag:gs.smuglo.li,2017-04-27:noticeId=2065713:objectType=note - New note by zemichi - <a href="https://gs.smuglo.li/file/1d45ea4ffc95f15037f361b56ad6b89f8451b70ad1ff7a03b7bb0345b8e2227c.jpg" title="https://gs.smuglo.li/file/1d45ea4ffc95f15037f361b56ad6b89f8451b70ad1ff7a03b7bb0345b8e2227c.jpg" rel="nofollow noreferrer" class="attachment">https://gs.smuglo.li/attachment/432344</a><br /> that's a lot of loli - - - - - - - https://gs.smuglo.li/conversation/927673 - - - - - - - tag:social.heldscal.la,2017-04-27:fave:29191:note:1932559:2017-04-27T17:12:46+00:00 - Favorite - shp favorited something by gsimg: <a href="https://gs.kawa-kun.com/file/3435c5cafda46f31cad5abb5837b3521b7b458198507073a496f4d10bad3633b.jpg" title="https://gs.kawa-kun.com/file/3435c5cafda46f31cad5abb5837b3521b7b458198507073a496f4d10bad3633b.jpg" rel="nofollow noreferrer" class="attachment">https://gs.kawa-kun.com/file/3435c5cafda46f31cad5abb5837b3521b7b458198507073a496f4d10bad3633b.jpg</a> #<span class="tag"><a href="https://gs.kawa-kun.com/tag/nsfw" rel="tag">nsfw</a></span> - - http://activitystrea.ms/schema/1.0/favorite - 2017-04-27T17:12:46+00:00 - 2017-04-27T17:12:46+00:00 - - http://activitystrea.ms/schema/1.0/note - tag:gs.kawa-kun.com,2017-04-27:noticeId=1608309:objectType=note - New note by gsimg - <a href="https://gs.kawa-kun.com/file/3435c5cafda46f31cad5abb5837b3521b7b458198507073a496f4d10bad3633b.jpg" title="https://gs.kawa-kun.com/file/3435c5cafda46f31cad5abb5837b3521b7b458198507073a496f4d10bad3633b.jpg" rel="nofollow noreferrer" class="attachment">https://gs.kawa-kun.com/file/3435c5cafda46f31cad5abb5837b3521b7b458198507073a496f4d10bad3633b.jpg</a> #<span class="tag"><a href="https://gs.kawa-kun.com/tag/nsfw" rel="tag">nsfw</a></span> - - - - - - - https://gs.kawa-kun.com/conversation/690817 - - - - - - - tag:social.heldscal.la,2017-04-27:fave:29191:note:1932601:2017-04-27T17:12:28+00:00 - Favorite - shp favorited something by zemichi: <a href="https://gs.smuglo.li/file/5d9114fafea7b9866c9d852bcfeaf66aade65ae26149758346bc5ade7e3fa8f0.jpg" title="https://gs.smuglo.li/file/5d9114fafea7b9866c9d852bcfeaf66aade65ae26149758346bc5ade7e3fa8f0.jpg" rel="nofollow noreferrer" class="attachment">https://gs.smuglo.li/attachment/432372</a> - - http://activitystrea.ms/schema/1.0/favorite - 2017-04-27T17:12:28+00:00 - 2017-04-27T17:12:28+00:00 - - http://activitystrea.ms/schema/1.0/note - tag:gs.smuglo.li,2017-04-27:noticeId=2065821:objectType=note - New note by zemichi - <a href="https://gs.smuglo.li/file/5d9114fafea7b9866c9d852bcfeaf66aade65ae26149758346bc5ade7e3fa8f0.jpg" title="https://gs.smuglo.li/file/5d9114fafea7b9866c9d852bcfeaf66aade65ae26149758346bc5ade7e3fa8f0.jpg" rel="nofollow noreferrer" class="attachment">https://gs.smuglo.li/attachment/432372</a> - - - - - - - https://gs.smuglo.li/conversation/927760 - - - - - - - tag:social.heldscal.la,2017-04-27:noticeId=1932867:objectType=note - shp repeated a notice by shpbot - RT @<a href="https://gs.archae.me/user/4687" class="h-card u-url p-nickname mention" title="shpbot">shpbot</a> <a href="https://shitposter.club/file/cbf7fbbee1127a9870e871305ca7de70f1eb1bbb79ffe5b3b0f33e37514d14d8.jpg" title="https://shitposter.club/file/cbf7fbbee1127a9870e871305ca7de70f1eb1bbb79ffe5b3b0f33e37514d14d8.jpg" rel="nofollow external noreferrer" class="attachment" id="attachment-237676">https://shitposter.club/file/cbf7fbbee1127a9870e871305ca7de70f1eb1bbb79ffe5b3b0f33e37514d14d8.jpg</a> #<span class="tag"><a href="https://social.heldscal.la/tag/2hu" rel="tag">2hu</a></span> #<span class="tag"><a href="https://social.heldscal.la/tag/ordinarymagician" rel="tag">ordinarymagician</a></span> :thinking: <a href="https://shitposter.club/file/abf3f82d9ce28d2293d858af26c75bb5d4fdd091c0d90ca7bc72ea7efba220e4.jpg" title="https://shitposter.club/file/abf3f82d9ce28d2293d858af26c75bb5d4fdd091c0d90ca7bc72ea7efba220e4.jpg" rel="nofollow external noreferrer" class="attachment" id="attachment-312306">https://shitposter.club/file/abf3f82d9ce28d2293d858af26c75bb5d4fdd091c0d90ca7bc72ea7efba220e4.jpg</a> - - http://activitystrea.ms/schema/1.0/share - 2017-04-27T17:11:35+00:00 - 2017-04-27T17:11:35+00:00 - - http://activitystrea.ms/schema/1.0/activity - tag:gs.archae.me,2017-04-27:noticeId=760830:objectType=note - - <a href="https://shitposter.club/file/cbf7fbbee1127a9870e871305ca7de70f1eb1bbb79ffe5b3b0f33e37514d14d8.jpg" title="https://shitposter.club/file/cbf7fbbee1127a9870e871305ca7de70f1eb1bbb79ffe5b3b0f33e37514d14d8.jpg" rel="nofollow noreferrer" class="attachment">https://shitposter.club/file/cbf7fbbee1127a9870e871305ca7de70f1eb1bbb79ffe5b3b0f33e37514d14d8.jpg</a> #<span class="tag"><a href="https://gs.archae.me/tag/2hu" rel="tag">2hu</a></span> #<span class="tag"><a href="https://gs.archae.me/tag/ordinarymagician" rel="tag">ordinarymagician</a></span> :thinking: <a href="https://shitposter.club/file/abf3f82d9ce28d2293d858af26c75bb5d4fdd091c0d90ca7bc72ea7efba220e4.jpg" title="https://shitposter.club/file/abf3f82d9ce28d2293d858af26c75bb5d4fdd091c0d90ca7bc72ea7efba220e4.jpg" rel="nofollow noreferrer" class="attachment">https://shitposter.club/file/abf3f82d9ce28d2293d858af26c75bb5d4fdd091c0d90ca7bc72ea7efba220e4.jpg</a> - - http://activitystrea.ms/schema/1.0/post - 2017-04-27T17:00:08+00:00 - 2017-04-27T17:00:08+00:00 - - http://activitystrea.ms/schema/1.0/person - https://gs.archae.me/user/4687 - shpbot - - - - - - shpbot - shpbot - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.archae.me,2017-04-27:noticeId=760830:objectType=note - New note by shpbot - <a href="https://shitposter.club/file/cbf7fbbee1127a9870e871305ca7de70f1eb1bbb79ffe5b3b0f33e37514d14d8.jpg" title="https://shitposter.club/file/cbf7fbbee1127a9870e871305ca7de70f1eb1bbb79ffe5b3b0f33e37514d14d8.jpg" rel="nofollow noreferrer" class="attachment">https://shitposter.club/file/cbf7fbbee1127a9870e871305ca7de70f1eb1bbb79ffe5b3b0f33e37514d14d8.jpg</a> #<span class="tag"><a href="https://gs.archae.me/tag/2hu" rel="tag">2hu</a></span> #<span class="tag"><a href="https://gs.archae.me/tag/ordinarymagician" rel="tag">ordinarymagician</a></span> :thinking: <a href="https://shitposter.club/file/abf3f82d9ce28d2293d858af26c75bb5d4fdd091c0d90ca7bc72ea7efba220e4.jpg" title="https://shitposter.club/file/abf3f82d9ce28d2293d858af26c75bb5d4fdd091c0d90ca7bc72ea7efba220e4.jpg" rel="nofollow noreferrer" class="attachment">https://shitposter.club/file/abf3f82d9ce28d2293d858af26c75bb5d4fdd091c0d90ca7bc72ea7efba220e4.jpg</a> - - - - - https://gs.archae.me/conversation/318317 - - - - - https://gs.archae.me/api/statuses/user_timeline/4687.atom - shpbot - - - https://social.heldscal.la/avatar/31581-original-20170405170019.jpeg - 2017-05-05T11:45:08+00:00 - - - - https://gs.archae.me/conversation/318317 - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.heldscal.la,2017-04-27:noticeId=1932815:objectType=note - New note by shp - federation issues with SPC atm it seems - - - http://activitystrea.ms/schema/1.0/post - 2017-04-27T17:08:55+00:00 - 2017-04-27T17:08:55+00:00 - - tag:social.heldscal.la,2017-04-27:objectType=thread:nonce=645a13c841f51769 - - - - - - - tag:social.heldscal.la,2017-04-26:fave:29191:note:1907285:2017-04-26T06:59:07+00:00 - Favorite - shp favorited something by lambadalambda: Is this the most offensive video on the net? <a href="https://social.heldscal.la/file/4c34bfb81a8155c265031bc48f7e69c29eb0d2941c57daf63f80e17b0e2e5f47.webm" title="https://social.heldscal.la/file/4c34bfb81a8155c265031bc48f7e69c29eb0d2941c57daf63f80e17b0e2e5f47.webm" rel="nofollow noreferrer" class="attachment">https://social.heldscal.la/attachment/402251</a> - - http://activitystrea.ms/schema/1.0/favorite - 2017-04-26T06:59:07+00:00 - 2017-04-26T06:59:07+00:00 - - http://activitystrea.ms/schema/1.0/note - tag:social.heldscal.la,2017-04-26:noticeId=1907285:objectType=note - New note by lambadalambda - Is this the most offensive video on the net? <a href="https://social.heldscal.la/file/4c34bfb81a8155c265031bc48f7e69c29eb0d2941c57daf63f80e17b0e2e5f47.webm" title="https://social.heldscal.la/file/4c34bfb81a8155c265031bc48f7e69c29eb0d2941c57daf63f80e17b0e2e5f47.webm" rel="nofollow external noreferrer" class="attachment" id="attachment-402251">https://social.heldscal.la/attachment/402251</a> - - - - - - - tag:social.heldscal.la,2017-04-26:objectType=thread:nonce=07b02e1328f456af - - - - - - - tag:social.heldscal.la,2017-04-26:noticeId=1907951:objectType=note - shp repeated a notice by shpbot - RT @<a href="https://gs.archae.me/user/4687" class="h-card u-url p-nickname mention" title="shpbot">shpbot</a> <a href="https://shitposter.club/file/718db06b564841331c72f9df767f8c9459e20c4dddbf0d4e61cd08ecbee7739d.jpg" title="https://shitposter.club/file/718db06b564841331c72f9df767f8c9459e20c4dddbf0d4e61cd08ecbee7739d.jpg" rel="nofollow external noreferrer" class="attachment" id="attachment-346198">https://shitposter.club/file/718db06b564841331c72f9df767f8c9459e20c4dddbf0d4e61cd08ecbee7739d.jpg</a> - - http://activitystrea.ms/schema/1.0/share - 2017-04-26T06:58:19+00:00 - 2017-04-26T06:58:19+00:00 - - http://activitystrea.ms/schema/1.0/activity - tag:gs.archae.me,2017-04-26:noticeId=752596:objectType=note - - <a href="https://shitposter.club/file/718db06b564841331c72f9df767f8c9459e20c4dddbf0d4e61cd08ecbee7739d.jpg" title="https://shitposter.club/file/718db06b564841331c72f9df767f8c9459e20c4dddbf0d4e61cd08ecbee7739d.jpg" rel="nofollow noreferrer" class="attachment">https://shitposter.club/file/718db06b564841331c72f9df767f8c9459e20c4dddbf0d4e61cd08ecbee7739d.jpg</a> - - http://activitystrea.ms/schema/1.0/post - 2017-04-26T06:15:07+00:00 - 2017-04-26T06:15:07+00:00 - - http://activitystrea.ms/schema/1.0/person - https://gs.archae.me/user/4687 - shpbot - - - - - - shpbot - shpbot - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.archae.me,2017-04-26:noticeId=752596:objectType=note - New note by shpbot - <a href="https://shitposter.club/file/718db06b564841331c72f9df767f8c9459e20c4dddbf0d4e61cd08ecbee7739d.jpg" title="https://shitposter.club/file/718db06b564841331c72f9df767f8c9459e20c4dddbf0d4e61cd08ecbee7739d.jpg" rel="nofollow noreferrer" class="attachment">https://shitposter.club/file/718db06b564841331c72f9df767f8c9459e20c4dddbf0d4e61cd08ecbee7739d.jpg</a> - - - - - https://gs.archae.me/conversation/314010 - - - https://gs.archae.me/api/statuses/user_timeline/4687.atom - shpbot - - - https://social.heldscal.la/avatar/31581-original-20170405170019.jpeg - 2017-05-05T11:45:08+00:00 - - - - https://gs.archae.me/conversation/314010 - - - - - - - tag:social.heldscal.la,2017-04-26:fave:29191:note:1907341:2017-04-26T06:58:16+00:00 - Favorite - shp favorited something by moonman: <a href="https://shitposter.club/file/1377b0894e983599c11e739e406243cabed9f8af7961a2550ecaf97e32de8e60.jpg" title="https://shitposter.club/file/1377b0894e983599c11e739e406243cabed9f8af7961a2550ecaf97e32de8e60.jpg" class="attachment" rel="nofollow">https://shitposter.club/attachment/630989</a> - - http://activitystrea.ms/schema/1.0/favorite - 2017-04-26T06:58:16+00:00 - 2017-04-26T06:58:16+00:00 - - http://activitystrea.ms/schema/1.0/note - tag:shitposter.club,2017-04-26:noticeId=2681941:objectType=note - New note by moonman - <a href="https://shitposter.club/file/1377b0894e983599c11e739e406243cabed9f8af7961a2550ecaf97e32de8e60.jpg" title="https://shitposter.club/file/1377b0894e983599c11e739e406243cabed9f8af7961a2550ecaf97e32de8e60.jpg" class="attachment" rel="nofollow">https://shitposter.club/attachment/630989</a> - - - - - - - https://shitposter.club/conversation/1300990 - - - - - - - tag:social.heldscal.la,2017-04-26:fave:29191:comment:1907412:2017-04-26T06:57:56+00:00 - Favorite - shp favorited something by lambadalambda: @<a href="https://gs.smuglo.li/user/2" class="h-card u-url p-nickname mention" title="nepfag">nepfag</a> <a href="https://cherubini.casa/why-i-shut-down-wizards-town-and-left-mastodon-6d4e631346b3?source=linkShare-89c2f851e979-1493184822&amp;gi=a6a47c5466a0" title="https://cherubini.casa/why-i-shut-down-wizards-town-and-left-mastodon-6d4e631346b3?source=linkShare-89c2f851e979-1493184822&amp;gi=a6a47c5466a0" rel="nofollow noreferrer" class="attachment">https://social.heldscal.la/url/402273</a> - - http://activitystrea.ms/schema/1.0/favorite - 2017-04-26T06:57:56+00:00 - 2017-04-26T06:57:56+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:social.heldscal.la,2017-04-26:noticeId=1907412:objectType=comment - New comment by lambadalambda - @<a href="https://gs.smuglo.li/user/2" class="h-card u-url p-nickname mention" title="nepfag">nepfag</a> <a href="https://cherubini.casa/why-i-shut-down-wizards-town-and-left-mastodon-6d4e631346b3?source=linkShare-89c2f851e979-1493184822&amp;gi=a6a47c5466a0" title="https://cherubini.casa/why-i-shut-down-wizards-town-and-left-mastodon-6d4e631346b3?source=linkShare-89c2f851e979-1493184822&amp;gi=a6a47c5466a0" rel="nofollow external noreferrer" class="attachment" id="attachment-402273">https://social.heldscal.la/url/402273</a> - - - - - - - tag:social.heldscal.la,2017-04-26:objectType=thread:nonce=85c21eda7aaa7259 - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.heldscal.la,2017-04-26:noticeId=1907942:objectType=note - New note by shp - #<span class="tag"><a href="https://social.heldscal.la/tag/cofe" rel="tag">cofe</a></span> time my friends <a href="https://social.heldscal.la/file/ec254b45b3a86ff74bc08bc7e065cb681d77cf7d4cedc9cdcf59e16adf311da3.png" title="https://social.heldscal.la/file/ec254b45b3a86ff74bc08bc7e065cb681d77cf7d4cedc9cdcf59e16adf311da3.png" rel="nofollow external noreferrer" class="attachment" id="attachment-402381">https://social.heldscal.la/attachment/402381</a> - - - http://activitystrea.ms/schema/1.0/post - 2017-04-26T06:57:18+00:00 - 2017-04-26T06:57:18+00:00 - - tag:social.heldscal.la,2017-04-26:objectType=thread:nonce=9c9d9373bccfaf70 - - - - - - - - diff --git a/test/fixtures/tesla_mock/sakamoto.atom b/test/fixtures/tesla_mock/sakamoto.atom deleted file mode 100644 index 648946795..000000000 --- a/test/fixtures/tesla_mock/sakamoto.atom +++ /dev/null @@ -1 +0,0 @@ -http://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://social.sakamoto.gq/objects/0ccc1a2c-66b0-4305-b23a-7f7f2b040056New note by eal<a href='https://shitposter.club/user/5381'>@shpuld</a> <a href='https://pleroma.hjkos.com/users/hj'>@hj</a> IM NOT GAY DAD2017-08-04T12:51:26.130592Z2017-08-04T12:51:26.130592Zhttps://pleroma.hjkos.com/contexts/53093c74-2100-4bf4-aac6-66d1973d03efhttps://social.sakamoto.gq/users/ealhttp://activitystrea.ms/schema/1.0/personhttps://social.sakamoto.gq/users/ealeal坂本(・ヮ・)eal \ No newline at end of file diff --git a/test/fixtures/tesla_mock/sakamoto_eal_feed.atom b/test/fixtures/tesla_mock/sakamoto_eal_feed.atom deleted file mode 100644 index 9340d9038..000000000 --- a/test/fixtures/tesla_mock/sakamoto_eal_feed.atom +++ /dev/null @@ -1 +0,0 @@ -https://social.sakamoto.gq/users/eal/feed.atomeal's timeline2017-08-04T14:19:12.683854https://social.sakamoto.gq/users/ealhttp://activitystrea.ms/schema/1.0/personhttps://social.sakamoto.gq/users/ealeal坂本(・ヮ・)ealhttp://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://social.sakamoto.gq/objects/b79a1721-23f3-45a5-9610-adb08c2afae5New note by ealHonestly, I like all smileys that are not emoji.2017-08-04T14:19:12.675999Z2017-08-04T14:19:12.675999Zhttps://social.sakamoto.gq/contexts/e05ede92-8db9-4963-8b8e-e71a5797d68fhttp://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://social.sakamoto.gq/objects/45475bf3-2dfc-4d9e-8eae-1f4f86f48982New note by ealThen again, I like all smileys/emoticons that are not emoji.<br>2017-08-04T14:19:10.113373Z2017-08-04T14:19:10.113373Zhttps://social.sakamoto.gq/contexts/852d1605-4dcb-4ba7-9ba4-dfc37ed62fbchttp://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://social.sakamoto.gq/objects/8f8fd6d6-cc63-40c6-a5d0-1c0e4f919368New note by ealI love the russian-style smiley.2017-08-04T14:18:30.478552Z2017-08-04T14:18:30.478552Zhttps://social.sakamoto.gq/contexts/852d1605-4dcb-4ba7-9ba4-dfc37ed62fbchttp://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/followhttps://social.sakamoto.gq/activities/6e69df95-f2ad-4b8e-af4a-e93ff93d64e1eal started following https://cybre.space/users/0x3Feal started following https://cybre.space/users/0x3F2017-08-04T14:17:24.942193Z2017-08-04T14:17:24.942193Zhttp://activitystrea.ms/schema/1.0/personhttps://cybre.space/users/0x3Fhttps://cybre.space/users/0x3Fhttp://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/followhttps://social.sakamoto.gq/activities/54c5e260-0185-4267-a2a6-f5dd9c76c2c9eal started following https://niu.moe/users/ryeeal started following https://niu.moe/users/rye2017-08-04T14:16:35.604739Z2017-08-04T14:16:35.604739Zhttp://activitystrea.ms/schema/1.0/personhttps://niu.moe/users/ryehttps://niu.moe/users/ryehttp://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/followhttps://social.sakamoto.gq/activities/092ca863-19a8-416c-85d7-d3f23b3c0203eal started following https://mastodon.xyz/users/rafudesueal started following https://mastodon.xyz/users/rafudesu2017-08-04T14:16:10.993429Z2017-08-04T14:16:10.993429Zhttp://activitystrea.ms/schema/1.0/personhttps://mastodon.xyz/users/rafudesuhttps://mastodon.xyz/users/rafudesuhttp://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/followhttps://social.sakamoto.gq/activities/be5cf702-b127-423b-a6be-5f78f01a4289eal started following https://gs.kawa-kun.com/user/2eal started following https://gs.kawa-kun.com/user/22017-08-04T14:15:41.804611Z2017-08-04T14:15:41.804611Zhttp://activitystrea.ms/schema/1.0/personhttps://gs.kawa-kun.com/user/2https://gs.kawa-kun.com/user/2http://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/followhttps://social.sakamoto.gq/activities/4951e2a1-9bae-4e87-8e98-e6d2f8a52338eal started following https://gs.kawa-kun.com/user/4885eal started following https://gs.kawa-kun.com/user/48852017-08-04T14:15:00.135352Z2017-08-04T14:15:00.135352Zhttp://activitystrea.ms/schema/1.0/personhttps://gs.kawa-kun.com/user/4885https://gs.kawa-kun.com/user/4885http://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/followhttps://social.sakamoto.gq/activities/cadf8745-b9ee-4f6c-af32-bfddb70e4607eal started following https://mastodon.social/users/Murassaeal started following https://mastodon.social/users/Murassa2017-08-04T14:14:36.339560Z2017-08-04T14:14:36.339560Zhttp://activitystrea.ms/schema/1.0/personhttps://mastodon.social/users/Murassahttps://mastodon.social/users/Murassahttp://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/followhttps://social.sakamoto.gq/activities/a52c9aab-f0e6-4ccb-8dd3-9f417e72a41ceal started following https://mastodon.social/users/rysiekeal started following https://mastodon.social/users/rysiek2017-08-04T14:13:04.061572Z2017-08-04T14:13:04.061572Zhttp://activitystrea.ms/schema/1.0/personhttps://mastodon.social/users/rysiekhttps://mastodon.social/users/rysiekhttp://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/followhttps://social.sakamoto.gq/activities/738bc887-4cca-4b36-8c86-2b54d4c54732eal started following https://mastodon.hasameli.com/users/munineal started following https://mastodon.hasameli.com/users/munin2017-08-04T14:12:10.514155Z2017-08-04T14:12:10.514155Zhttp://activitystrea.ms/schema/1.0/personhttps://mastodon.hasameli.com/users/muninhttps://mastodon.hasameli.com/users/muninhttp://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/followhttps://social.sakamoto.gq/activities/dc66ad5a-b776-4180-a8aa-e4c1bf7cb703eal started following https://cybre.space/users/nightpooleal started following https://cybre.space/users/nightpool2017-08-04T14:11:16.046148Z2017-08-04T14:11:16.046148Zhttp://activitystrea.ms/schema/1.0/personhttps://cybre.space/users/nightpoolhttps://cybre.space/users/nightpoolhttp://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://social.sakamoto.gq/objects/9c5c00d7-3ce4-4c11-b965-dc5c2bda86c5New note by eal<a href='https://mastodon.zombocloud.com/users/staticsafe'>@staticsafe</a> privet )))2017-08-04T14:10:08.812247Z2017-08-04T14:10:08.812247Zhttps://social.sakamoto.gq/contexts/12a33823-0327-4c1c-a591-850ea79331b5http://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/followhttps://social.sakamoto.gq/activities/49798053-1f40-4a71-ad33-106e90630863eal started following https://social.homunyan.com/users/animeirleal started following https://social.homunyan.com/users/animeirl2017-08-04T14:09:44.904792Z2017-08-04T14:09:44.904792Zhttp://activitystrea.ms/schema/1.0/personhttps://social.homunyan.com/users/animeirlhttps://social.homunyan.com/users/animeirlhttp://activitystrea.ms/schema/1.0/favoritehttps://social.sakamoto.gq/activities/2d83a1c5-70a6-45d3-9b84-59d6a70fbb17New favorite by ealeal favorited something2017-08-04T14:07:27.210044Z2017-08-04T14:07:27.210044Zhttp://activitystrea.ms/schema/1.0/notehttps://pleroma.soykaf.com/objects/b831e52f-4ed4-438e-95b4-888897f64f09https://pleroma.hjkos.com/contexts/3ed48205-1e72-4e19-a618-89a0d2ca811ehttp://activitystrea.ms/schema/1.0/favoritehttps://social.sakamoto.gq/activities/06d28bed-544a-496b-8414-1c6d439273b5New favorite by ealeal favorited something2017-08-04T14:05:37.280200Z2017-08-04T14:05:37.280200Zhttp://activitystrea.ms/schema/1.0/notetag:toot-lab.reclaim.technology,2017-08-04:objectId=1166030:objectType=Statustag:p2px.me,2017-08-04:objectType=thread:nonce=f8bfc4d13db6ce91http://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/followhttps://social.sakamoto.gq/activities/72bf19d4-9ad4-4b2f-9cd0-f0d70f4e931beal started following https://mstdn.jp/users/nullkaleal started following https://mstdn.jp/users/nullkal2017-08-04T14:05:04.148904Z2017-08-04T14:05:04.148904Zhttp://activitystrea.ms/schema/1.0/personhttps://mstdn.jp/users/nullkalhttps://mstdn.jp/users/nullkalhttp://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://social.sakamoto.gq/objects/b0e89515-7621-4e09-b23d-83e192324107New note by eal<a href='https://p2px.me/user/1'>@stitchxd</a> test also2017-08-04T14:04:38.699051Z2017-08-04T14:04:38.699051Ztag:p2px.me,2017-08-04:objectType=thread:nonce=f8bfc4d13db6ce91http://activitystrea.ms/schema/1.0/favoritehttps://social.sakamoto.gq/activities/d8d2006b-6b23-45d6-ba27-39d27587777dNew favorite by ealeal favorited something2017-08-04T14:04:32.106626Z2017-08-04T14:04:32.106626Zhttp://activitystrea.ms/schema/1.0/notetag:p2px.me,2017-08-04:noticeId=222109:objectType=notetag:p2px.me,2017-08-04:objectType=thread:nonce=f8bfc4d13db6ce91http://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/followhttps://social.sakamoto.gq/activities/cb9db95d-ec27-41fa-bebd-5375fc13acb9eal started following https://mastodon.social/users/Gargroneal started following https://mastodon.social/users/Gargron2017-08-04T14:04:04.325531Z2017-08-04T14:04:04.325531Zhttp://activitystrea.ms/schema/1.0/personhttps://mastodon.social/users/Gargronhttps://mastodon.social/users/Gargron \ No newline at end of file diff --git a/test/fixtures/tesla_mock/shp@pleroma.soykaf.com.feed b/test/fixtures/tesla_mock/shp@pleroma.soykaf.com.feed deleted file mode 100644 index b24ef7ab6..000000000 --- a/test/fixtures/tesla_mock/shp@pleroma.soykaf.com.feed +++ /dev/null @@ -1 +0,0 @@ -https://pleroma.soykaf.com/users/shp/feed.atomshp's timeline2017-09-14T08:31:48.911686https://pleroma.soykaf.com/users/shphttp://activitystrea.ms/schema/1.0/personhttps://pleroma.soykaf.com/users/shpshpshpcofeshphttp://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/followhttps://pleroma.soykaf.com/activities/0b5f5ef2-020a-4f9e-a92b-a2bf21224644shp started following https://pleroma.soykaf.com/users/goozshp started following https://pleroma.soykaf.com/users/gooz2017-09-14T08:31:48.911226Z2017-09-14T08:31:48.911226Zhttp://activitystrea.ms/schema/1.0/personhttps://pleroma.soykaf.com/users/goozhttps://pleroma.soykaf.com/users/goozhttp://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/followhttps://pleroma.soykaf.com/activities/d928b7f7-dc10-478c-859b-cd604770da60shp started following https://niu.moe/users/xiaoyongmaoshp started following https://niu.moe/users/xiaoyongmao2017-09-14T08:16:52.674253Z2017-09-14T08:16:52.674253Zhttp://activitystrea.ms/schema/1.0/personhttps://niu.moe/users/xiaoyongmaohttps://niu.moe/users/xiaoyongmaohttp://activitystrea.ms/schema/1.0/favoritehttps://pleroma.soykaf.com/activities/3f5089b3-f1e5-47b6-8bfe-a9c4a860e724New favorite by shpshp favorited something2017-09-14T08:12:18.213055Z2017-09-14T08:12:18.213055Zhttp://activitystrea.ms/schema/1.0/notehttps://mastodon.xyz/users/Azurolu/statuses/8346804tag:mastodon.xyz,2017-09-14:objectId=3669709:objectType=Conversationhttp://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/0def9b19-6b0f-44e0-96b3-543fa06a4010New note by shp<a href='https://niu.moe/users/Pasty'>@Pasty</a> I love the peach<br><a href="https://pleroma.soykaf.com/media/7e8bd209-dbd4-481a-a62c-d302d68df16d/__hinanawi_tenshi_touhou_drawn_by_e_o__8c6824f52dd494f6026607570179265f.jpg" class='attachment'>__hinanawi_tenshi_touhou_drawn_…</a>2017-09-14T08:12:04.367142Z2017-09-14T08:12:04.367142Ztag:niu.moe,2017-09-14:objectId=1660781:objectType=Conversationhttp://activitystrea.ms/schema/1.0/favoritehttps://pleroma.soykaf.com/activities/a4170edf-d273-4b82-931d-662aaf3872f3New favorite by shpshp favorited something2017-09-14T08:10:26.205104Z2017-09-14T08:10:26.205104Zhttp://activitystrea.ms/schema/1.0/notehttps://niu.moe/users/NekoiNemo/statuses/3210992tag:niu.moe,2017-09-14:objectId=1660761:objectType=Conversationhttp://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/c50c47a0-fac5-4781-a7e6-f20e7226d5fcNew note by shp<a href='https://freezepeach.xyz/user/3458'>@hakui</a> <a href='https://pleroma.soykaf.com/users/lain'>@lain</a> you guys are forgetting the pancakes jeez2017-09-14T08:09:30.088418Z2017-09-14T08:09:30.088418Zhttps://pleroma.soykaf.com/contexts/ac9c98ee-3eca-4b4b-9620-64b5e85e2623http://activitystrea.ms/schema/1.0/favoritehttps://pleroma.soykaf.com/activities/2af9f622-5986-483c-83a1-ac59a9035b50New favorite by shpshp favorited something2017-09-14T08:09:16.346235Z2017-09-14T08:09:16.346235Zhttp://activitystrea.ms/schema/1.0/notetag:freezepeach.xyz,2017-09-14:noticeId=3926191:objectType=commenthttps://pleroma.soykaf.com/contexts/ac9c98ee-3eca-4b4b-9620-64b5e85e2623http://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/f52aad69-5828-4e0e-bb7b-f2f0869d3ff0New note by shp<a href='https://gs.smuglo.li/user/253'>@kro</a> I'll probs try some of those 2hu mangos2017-09-14T08:09:13.262835Z2017-09-14T08:09:13.262835Ztag:gs.smuglo.li,2017-09-14:objectType=thread:nonce=c4ac2016e07c4123http://activitystrea.ms/schema/1.0/favoritehttps://pleroma.soykaf.com/activities/35743658-efee-46cf-9cdf-487b95709cd5New favorite by shpshp favorited something2017-09-14T08:09:00.517534Z2017-09-14T08:09:00.517534Zhttp://activitystrea.ms/schema/1.0/notetag:gs.smuglo.li,2017-09-14:noticeId=4113226:objectType=commenttag:gs.smuglo.li,2017-09-14:objectType=thread:nonce=c4ac2016e07c4123http://activitystrea.ms/schema/1.0/favoritehttps://pleroma.soykaf.com/activities/22258ba8-58dc-4e09-b476-fe28d3307377New favorite by shpshp favorited something2017-09-14T08:08:38.087136Z2017-09-14T08:08:38.087136Zhttp://activitystrea.ms/schema/1.0/notehttps://pleroma.soykaf.com/objects/13d7809e-5dca-4117-8738-887759392f2chttps://pleroma.soykaf.com/contexts/ac9c98ee-3eca-4b4b-9620-64b5e85e2623http://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/f56d640a-0dbd-48af-80b1-06d0dbd26774New note by shp<a href='https://social.sakamoto.gq/users/eal'>@eal</a> ...but neither does my phone<br><br>low brightness, very dark wallpaper (pic related, but even darker, couldn't find the actual version)<br><a href="https://pleroma.soykaf.com/media/6d1b8d57-80ae-41d6-bdea-58fea09ecdf4/phonewallpaper.png" class='attachment'>phonewallpaper.png</a>2017-09-14T08:07:23.081214Z2017-09-14T08:07:23.081214Zhttps://pleroma.soykaf.com/contexts/f4c5d56e-fc58-467b-a8a5-10515c012355http://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/d313df1d-121c-4ab8-abd1-e6aedcf55cbdNew note by shp<a href='https://niu.moe/users/Pasty'>@Pasty</a> y-you too2017-09-14T07:55:26.153486Z2017-09-14T07:55:26.153486Ztag:niu.moe,2017-09-14:objectId=1660616:objectType=Conversationhttp://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/7b642424-4edb-48cc-8711-1eafb4745269New note by shp<a href='https://social.sakamoto.gq/users/eal'>@eal</a> bothers me more when sleeping, wore one for nearly 2 years2017-09-14T07:54:53.449227Z2017-09-14T07:54:53.449227Zhttps://pleroma.soykaf.com/contexts/f4c5d56e-fc58-467b-a8a5-10515c012355http://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/5bc1bff1-88c3-489d-8efd-7e4755690a18New note by shpquick test2017-09-14T07:54:09.045525Z2017-09-14T07:54:09.045525Zhttps://pleroma.soykaf.com/contexts/cd770c2a-408e-4895-988c-60319298f219http://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/956f1fb5-6f2f-433e-ab71-7f732b76f4beNew note by shphad some trouble getting sleep last night. only used phone to check the time a few times (v essential to have a near-black wallpaper to not blind yourself when you do that). can't rember the last time I rolled in the bed for longer than an hour like that2017-09-14T07:51:23.557775Z2017-09-14T07:51:23.557775Zhttps://pleroma.soykaf.com/contexts/f4c5d56e-fc58-467b-a8a5-10515c012355http://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/d935d9f2-ebc7-4ff2-b65a-fbf418a60935New note by shp<a href='https://gs.smuglo.li/user/253'>@kro</a> doesn't sound like a bad idea at all2017-09-14T07:49:55.702555Z2017-09-14T07:49:55.702555Ztag:gs.smuglo.li,2017-09-14:objectType=thread:nonce=c4ac2016e07c4123http://activitystrea.ms/schema/1.0/favoritehttps://pleroma.soykaf.com/activities/342c8803-ee16-487d-9488-a39d763073f6New favorite by shpshp favorited something2017-09-14T07:49:41.875840Z2017-09-14T07:49:41.875840Zhttp://activitystrea.ms/schema/1.0/notetag:anticapitalist.party,2017-09-14:objectId=3322865:objectType=Statustag:anticapitalist.party,2017-09-14:objectId=1251751:objectType=Conversationhttp://activitystrea.ms/schema/1.0/favoritehttps://pleroma.soykaf.com/activities/5d98a19b-dd55-4077-9841-142937c613adNew favorite by shpshp favorited something2017-09-14T07:49:30.584265Z2017-09-14T07:49:30.584265Zhttp://activitystrea.ms/schema/1.0/notetag:gs.smuglo.li,2017-09-14:noticeId=4113170:objectType=commenttag:gs.smuglo.li,2017-09-14:objectType=thread:nonce=c4ac2016e07c4123http://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/fdf3626a-50ba-458b-9bf7-b5f2cfa505fcNew note by shp<a href='https://pleroma.hjkos.com/users/hj'>@hj</a> c time2017-09-14T07:48:52.805422Z2017-09-14T07:48:52.805422Zhttps://pleroma.hjkos.com/contexts/dc4a3a3e-d366-4c0c-8789-8a9bee3537d9http://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/c7c8eb17-b669-4827-9fbc-90f1fc54e4b1New note by shp<a href='https://sunshinegardens.org/users/tbny'>@tbny</a> err.. mediterranean from finnish*2017-09-14T07:46:52.764234Z2017-09-14T07:46:52.764234Zhttps://pleroma.soykaf.com/contexts/ac9c98ee-3eca-4b4b-9620-64b5e85e2623 \ No newline at end of file diff --git a/test/fixtures/tesla_mock/spc_5381.atom b/test/fixtures/tesla_mock/spc_5381.atom deleted file mode 100644 index c3288e97b..000000000 --- a/test/fixtures/tesla_mock/spc_5381.atom +++ /dev/null @@ -1,438 +0,0 @@ - - - GNU social - https://shitposter.club/api/statuses/user_timeline/5381.atom - shpuld timeline - Updates from shpuld on Shitposter Club! - https://shitposter.club/avatar/5381-96-20171230093854.png - 2018-02-23T13:42:22+00:00 - - http://activitystrea.ms/schema/1.0/person - https://shitposter.club/user/5381 - shpuld - - - - - - shpuld - shp - - - - - - - - - - - - - tag:shitposter.club,2018-02-23:fave:5381:comment:7387801:2018-02-23T13:39:40+00:00 - Favorite - shpuld favorited something by mayuutann: <p><span class="h-card"><a href="https://freezepeach.xyz/hakui" class="u-url mention">@<span>hakui</span></a></span> <span class="h-card"><a href="https://gs.smuglo.li/histoire" class="u-url mention">@<span>histoire</span></a></span> <span class="h-card"><a href="https://shitposter.club/shpuld" class="u-url mention">@<span>shpuld</span></a></span> <a href="https://mstdn.io/media/_Ee-x91XN0udpfZVO_U" rel="nofollow"><span class="invisible">https://</span><span class="ellipsis">mstdn.io/media/_Ee-x91XN0udpfZ</span><span class="invisible">VO_U</span></a></p> - - http://activitystrea.ms/schema/1.0/favorite - 2018-02-23T13:39:40+00:00 - 2018-02-23T13:39:40+00:00 - - http://activitystrea.ms/schema/1.0/comment - https://mstdn.io/users/mayuutann/statuses/99574950785668071 - New comment by mayuutann - <p><span class="h-card"><a href="https://freezepeach.xyz/hakui" class="u-url mention">@<span>hakui</span></a></span> <span class="h-card"><a href="https://gs.smuglo.li/histoire" class="u-url mention">@<span>histoire</span></a></span> <span class="h-card"><a href="https://shitposter.club/shpuld" class="u-url mention">@<span>shpuld</span></a></span> <a href="https://mstdn.io/media/_Ee-x91XN0udpfZVO_U" rel="nofollow"><span class="invisible">https://</span><span class="ellipsis">mstdn.io/media/_Ee-x91XN0udpfZ</span><span class="invisible">VO_U</span></a></p> - - - - - - - https://freezepeach.xyz/conversation/4182511 - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2018-02-23:noticeId=7387723:objectType=comment - New comment by shpuld - @<a href="https://freezepeach.xyz/user/3458" class="h-card mention" title="&#x5FA1;&#x5712;&#x306F;&#x304F;&#x3044;">hakui</a> @<a href="https://pleroma.soykaf.com/users/lain" class="h-card mention" title="&#x2468; lain &#x2468;">lain</a> how naive~ - - - http://activitystrea.ms/schema/1.0/post - 2018-02-23T13:30:15+00:00 - 2018-02-23T13:30:15+00:00 - - - - tag:shitposter.club,2018-02-23:objectType=thread:nonce=2f09acf104aebfe3 - - - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2018-02-23:noticeId=7387703:objectType=comment - New comment by shpuld - @<a href="https://freezepeach.xyz/user/3458" class="h-card mention" title="&#x5FA1;&#x5712;&#x306F;&#x304F;&#x3044;">hakui</a> @<a href="https://pleroma.soykaf.com/users/lain" class="h-card mention" title="&#x2468; lain &#x2468;">lain</a> you expect anyone to believe that?? - - - http://activitystrea.ms/schema/1.0/post - 2018-02-23T13:28:08+00:00 - 2018-02-23T13:28:08+00:00 - - - - tag:shitposter.club,2018-02-23:objectType=thread:nonce=2f09acf104aebfe3 - - - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2018-02-23:noticeId=7387639:objectType=comment - New comment by shpuld - @<a href="https://mstdn.io/users/mayuutann" class="h-card mention" title="Mayutan&#x2615;">mayuutann</a> @<a href="https://freezepeach.xyz/user/3458" class="h-card mention" title="&#x5FA1;&#x5712;&#x306F;&#x304F;&#x3044;">hakui</a> pacyuri!! <a href="https://shitposter.club/file/eea140be45df3f993c4533026bf9a78fe8facd296d2fa0c6d02b2e347c5dc30e.jpg" title="https://shitposter.club/file/eea140be45df3f993c4533026bf9a78fe8facd296d2fa0c6d02b2e347c5dc30e.jpg" class="attachment" id="attachment-1589462" rel="nofollow external">https://shitposter.club/attachment/1589462</a> - - - http://activitystrea.ms/schema/1.0/post - 2018-02-23T13:20:38+00:00 - 2018-02-23T13:20:38+00:00 - - - - https://freezepeach.xyz/conversation/4183220 - - - - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2018-02-23:noticeId=7387611:objectType=comment - New comment by shpuld - @<a href="https://freezepeach.xyz/user/3458" class="h-card mention" title="&#x5FA1;&#x5712;&#x306F;&#x304F;&#x3044;">hakui</a> why is pacyu eating a pizza so cute - - - http://activitystrea.ms/schema/1.0/post - 2018-02-23T13:18:07+00:00 - 2018-02-23T13:18:07+00:00 - - - - https://freezepeach.xyz/conversation/4183220 - - - - - - - - tag:shitposter.club,2018-02-23:fave:5381:comment:7387600:2018-02-23T13:17:52+00:00 - Favorite - shpuld favorited something by mayuutann: <p><span class="h-card"><a href="https://shitposter.club/shpuld" class="u-url mention">@<span>shpuld</span></a></span> <span class="h-card"><a href="https://gs.smuglo.li/histoire" class="u-url mention">@<span>histoire</span></a></span> <span class="h-card"><a href="https://freezepeach.xyz/hakui" class="u-url mention">@<span>hakui</span></a></span> pichu! <a href="https://mstdn.io/media/Crv5eubz1KO0dgBEulI" rel="nofollow"><span class="invisible">https://</span><span class="ellipsis">mstdn.io/media/Crv5eubz1KO0dgB</span><span class="invisible">EulI</span></a></p> - - http://activitystrea.ms/schema/1.0/favorite - 2018-02-23T13:17:52+00:00 - 2018-02-23T13:17:52+00:00 - - http://activitystrea.ms/schema/1.0/comment - https://mstdn.io/users/mayuutann/statuses/99574863865459283 - New comment by mayuutann - <p><span class="h-card"><a href="https://shitposter.club/shpuld" class="u-url mention">@<span>shpuld</span></a></span> <span class="h-card"><a href="https://gs.smuglo.li/histoire" class="u-url mention">@<span>histoire</span></a></span> <span class="h-card"><a href="https://freezepeach.xyz/hakui" class="u-url mention">@<span>hakui</span></a></span> pichu! <a href="https://mstdn.io/media/Crv5eubz1KO0dgBEulI" rel="nofollow"><span class="invisible">https://</span><span class="ellipsis">mstdn.io/media/Crv5eubz1KO0dgB</span><span class="invisible">EulI</span></a></p> - - - - - - - https://freezepeach.xyz/conversation/4182511 - - - - - - - tag:shitposter.club,2018-02-23:fave:5381:comment:7387544:2018-02-23T13:12:43+00:00 - Favorite - shpuld favorited something by mayuutann: <p><span class="h-card"><a href="https://shitposter.club/shpuld" class="u-url mention">@<span>shpuld</span></a></span> wa~~i!! :blobcheer:</p> - - http://activitystrea.ms/schema/1.0/favorite - 2018-02-23T13:12:43+00:00 - 2018-02-23T13:12:43+00:00 - - http://activitystrea.ms/schema/1.0/comment - https://mstdn.io/users/mayuutann/statuses/99574840290947233 - New comment by mayuutann - <p><span class="h-card"><a href="https://shitposter.club/shpuld" class="u-url mention">@<span>shpuld</span></a></span> wa~~i!! :blobcheer:</p> - - - - - - - tag:shitposter.club,2018-02-23:objectType=thread:nonce=d05e2b056274c5ab - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2018-02-23:noticeId=7387555:objectType=comment - New comment by shpuld - @<a href="https://freezepeach.xyz/user/3458" class="h-card mention" title="&#x5FA1;&#x5712;&#x306F;&#x304F;&#x3044;">hakui</a> more!! - - - http://activitystrea.ms/schema/1.0/post - 2018-02-23T13:12:23+00:00 - 2018-02-23T13:12:23+00:00 - - - - https://freezepeach.xyz/conversation/4183220 - - - - - - - - tag:shitposter.club,2018-02-23:fave:5381:note:7387537:2018-02-23T13:12:19+00:00 - Favorite - shpuld favorited something by hakui: you have pacyupacyu'd for: 45 minutes 03 seconds - - http://activitystrea.ms/schema/1.0/favorite - 2018-02-23T13:12:19+00:00 - 2018-02-23T13:12:19+00:00 - - http://activitystrea.ms/schema/1.0/note - tag:freezepeach.xyz,2018-02-23:noticeId=6451332:objectType=note - New note by hakui - you have pacyupacyu'd for: 45 minutes 03 seconds - - - - - - - https://freezepeach.xyz/conversation/4183220 - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2018-02-23:noticeId=7387539:objectType=comment - New comment by shpuld - @<a href="https://mstdn.io/users/mayuutann" class="h-card mention" title="Mayutan&#x2615;">mayuutann</a> ndndnd~ - - - http://activitystrea.ms/schema/1.0/post - 2018-02-23T13:11:04+00:00 - 2018-02-23T13:11:04+00:00 - - - - tag:shitposter.club,2018-02-23:objectType=thread:nonce=d05e2b056274c5ab - - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2018-02-23:noticeId=7387518:objectType=comment - New comment by shpuld - @<a href="https://mstdn.io/users/mayuutann" class="h-card mention" title="Mayutan&#x2615;">mayuutann</a> well done! mayumayu is so energetic - - - http://activitystrea.ms/schema/1.0/post - 2018-02-23T13:08:50+00:00 - 2018-02-23T13:08:50+00:00 - - - - tag:shitposter.club,2018-02-23:objectType=thread:nonce=d05e2b056274c5ab - - - - - - - - tag:shitposter.club,2018-02-23:fave:5381:note:7387503:2018-02-23T13:08:00+00:00 - Favorite - shpuld favorited something by mayuutann: <p>done with FIGURE MAT!!<br /> (Posted with IFTTT)</p> - - http://activitystrea.ms/schema/1.0/favorite - 2018-02-23T13:08:00+00:00 - 2018-02-23T13:08:00+00:00 - - http://activitystrea.ms/schema/1.0/note - https://mstdn.io/users/mayuutann/statuses/99574825526201897 - New note by mayuutann - <p>done with FIGURE MAT!!<br /> (Posted with IFTTT)</p> - - - - - - - tag:shitposter.club,2018-02-23:objectType=thread:nonce=c6aaa9b91e8d242f - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2018-02-23:noticeId=7387486:objectType=comment - New comment by shpuld - @<a href="https://freezepeach.xyz/user/3458" class="h-card mention" title="&#x5FA1;&#x5712;&#x306F;&#x304F;&#x3044;">hakui</a> @<a href="https://a.weirder.earth/users/mutstd" class="h-card mention" title="Mutant Standard">mutstd</a> @<a href="https://donphan.social/users/Siphonay" class="h-card mention" title="Siphonay">siphonay</a> jokes on you I'm oppressively shitposting myself - - - http://activitystrea.ms/schema/1.0/post - 2018-02-23T13:05:44+00:00 - 2018-02-23T13:05:44+00:00 - - - - tag:shitposter.club,2018-02-23:objectType=thread:nonce=5d306467336c9661 - - - - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2018-02-23:noticeId=7387466:objectType=comment - New comment by shpuld - @<a href="https://freezepeach.xyz/user/3458" class="h-card mention" title="&#x5FA1;&#x5712;&#x306F;&#x304F;&#x3044;">hakui</a> @<a href="https://a.weirder.earth/users/mutstd" class="h-card mention" title="Mutant Standard">mutstd</a> @<a href="https://donphan.social/users/Siphonay" class="h-card mention" title="Siphonay">siphonay</a> how does it feel being hostile - - - http://activitystrea.ms/schema/1.0/post - 2018-02-23T13:04:10+00:00 - 2018-02-23T13:04:10+00:00 - - - - tag:shitposter.club,2018-02-23:objectType=thread:nonce=5d306467336c9661 - - - - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2018-02-23:noticeId=7387459:objectType=comment - New comment by shpuld - @<a href="https://freezepeach.xyz/user/3458" class="h-card mention" title="&#x5FA1;&#x5712;&#x306F;&#x304F;&#x3044;">hakui</a> gorogoro - - - http://activitystrea.ms/schema/1.0/post - 2018-02-23T13:03:32+00:00 - 2018-02-23T13:03:32+00:00 - - - - https://freezepeach.xyz/conversation/4181784 - - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2018-02-23:noticeId=7387432:objectType=comment - New comment by shpuld - @<a href="https://freezepeach.xyz/user/3458" class="h-card mention" title="&#x5FA1;&#x5712;&#x306F;&#x304F;&#x3044;">hakui</a> ndnd - - - http://activitystrea.ms/schema/1.0/post - 2018-02-23T13:02:05+00:00 - 2018-02-23T13:02:05+00:00 - - - - https://freezepeach.xyz/conversation/4181784 - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:shitposter.club,2018-02-23:noticeId=7387367:objectType=note - New note by shpuld - dear diary: I'm trying to do work but I can only think of tenshi eating a corndog - - - http://activitystrea.ms/schema/1.0/post - 2018-02-23T12:56:03+00:00 - 2018-02-23T12:56:03+00:00 - - tag:shitposter.club,2018-02-23:objectType=thread:nonce=57f316da416743fc - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:shitposter.club,2018-02-23:noticeId=7387354:objectType=note - New note by shpuld - jesus christ it's such a fridey at work - - - http://activitystrea.ms/schema/1.0/post - 2018-02-23T12:53:50+00:00 - 2018-02-23T12:53:50+00:00 - - tag:shitposter.club,2018-02-23:objectType=thread:nonce=c05eb5e91bdcbdb7 - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2018-02-23:noticeId=7387343:objectType=comment - New comment by shpuld - @<a href="https://gs.smuglo.li/user/589" class="h-card mention" title="&#x16DE;&#x16A9;&#x16B3;&#x16C1;&#x16DE;&#x16A9;&#x16B3;&#x16C1;">dokidoki</a> give them free upgrades to krokodil - - - http://activitystrea.ms/schema/1.0/post - 2018-02-23T12:53:15+00:00 - 2018-02-23T12:53:15+00:00 - - - - https://gs.smuglo.li/conversation/3934774 - - - - - - - diff --git a/test/fixtures/tesla_mock/wedistribute-create-article.json b/test/fixtures/tesla_mock/wedistribute-create-article.json new file mode 100644 index 000000000..3cfef8b99 --- /dev/null +++ b/test/fixtures/tesla_mock/wedistribute-create-article.json @@ -0,0 +1 @@ +{"@context":["https:\/\/www.w3.org\/ns\/activitystreams"],"type":"Create","actor":"https:\/\/wedistribute.org\/wp-json\/pterotype\/v1\/actor\/-blog","object":{"@context":["https:\/\/www.w3.org\/ns\/activitystreams"],"type":"Article","name":"The end is near: Mastodon plans to drop OStatus support","content":"\n

The days of OStatus are numbered. The venerable protocol has served as a glue between many different types of servers since the early days of the Fediverse, connecting StatusNet (now GNU Social) to Friendica, Hubzilla, Mastodon, and Pleroma.<\/p>\n\n\n\n

Now that many fediverse platforms support ActivityPub as a successor protocol, Mastodon appears to be drawing a line in the sand. In a Patreon update<\/a>, Eugen Rochko writes:<\/p>\n\n\n\n

...OStatus...has overstayed its welcome in the code...and now that most of the network uses ActivityPub, it's time for it to go. <\/p>Eugen Rochko, Mastodon creator<\/cite><\/blockquote>\n\n\n\n

The pull request<\/a> to remove Pubsubhubbub and Salmon, two of the main components of OStatus, has already been merged into Mastodon's master branch.<\/p>\n\n\n\n

Some projects will be left in the dark as a side effect of this. GNU Social and PostActiv, for example, both only communicate using OStatus. While some discussion<\/a> exists regarding adopting ActivityPub for GNU Social, and a plugin is in development<\/a>, it hasn't been formally adopted yet. We just hope that the Free Software Foundation's instance<\/a> gets updated in time!<\/p>\n","summary":"One of the largest platforms in the federated social web is dropping the protocol that it started with.","attributedTo":"https:\/\/wedistribute.org\/wp-json\/pterotype\/v1\/actor\/-blog","url":"https:\/\/wedistribute.org\/2019\/07\/mastodon-drops-ostatus\/","to":["https:\/\/www.w3.org\/ns\/activitystreams#Public","https:\/\/wedistribute.org\/wp-json\/pterotype\/v1\/actor\/-blog\/followers"],"id":"https:\/\/wedistribute.org\/wp-json\/pterotype\/v1\/object\/85810","likes":"https:\/\/wedistribute.org\/wp-json\/pterotype\/v1\/object\/85810\/likes","shares":"https:\/\/wedistribute.org\/wp-json\/pterotype\/v1\/object\/85810\/shares"},"to":["https:\/\/www.w3.org\/ns\/activitystreams#Public","https:\/\/wedistribute.org\/wp-json\/pterotype\/v1\/actor\/-blog\/followers"],"id":"https:\/\/wedistribute.org\/wp-json\/pterotype\/v1\/object\/85809"} \ No newline at end of file diff --git a/test/fixtures/unfollow.xml b/test/fixtures/unfollow.xml deleted file mode 100644 index 7a8f8fd2e..000000000 --- a/test/fixtures/unfollow.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - GNU social - https://social.heldscal.la/api/statuses/user_timeline/23211.atom - lambadalambda timeline - Updates from lambadalambda on social.heldscal.la! - https://social.heldscal.la/avatar/23211-96-20170416114255.jpeg - 2017-05-07T09:54:49+00:00 - - http://activitystrea.ms/schema/1.0/person - https://social.heldscal.la/user/23211 - lambadalambda -

Call me Deacon Blues. - - - - - - lambadalambda - Constance Variable - Call me Deacon Blues. - - Berlin - - - homepage - https://heldscal.la - true - - - - - - - - - - - - - undo:tag:social.heldscal.la,2017-05-07:subscription:23211:person:44803:2017-05-07T09:54:48+00:00 - Constance Variable (lambadalambda@social.heldscal.la)'s status on Sunday, 07-May-2017 09:54:49 UTC - <a href="https://social.heldscal.la/lambadalambda">Constance Variable</a> stopped following <a href="https://pawoo.net/@pekorino">mono</a>. - - http://activitystrea.ms/schema/1.0/unfollow - 2017-05-07T09:54:49+00:00 - 2017-05-07T09:54:49+00:00 - - http://activitystrea.ms/schema/1.0/person - https://pawoo.net/users/pekorino - mono - http://shitposter.club/mono 孤独のグルメ - - - - - pekorino - mono - http://shitposter.club/mono 孤独のグルメ - - - tag:social.heldscal.la,2017-05-07:objectType=thread:nonce=6e80caf94e03029f - - - - - - diff --git a/test/integration/mastodon_websocket_test.exs b/test/integration/mastodon_websocket_test.exs index 76fbc8bda..0f2e6cc2b 100644 --- a/test/integration/mastodon_websocket_test.exs +++ b/test/integration/mastodon_websocket_test.exs @@ -78,7 +78,7 @@ test "receives well formatted events" do Pleroma.Repo.insert( OAuth.App.register_changeset(%OAuth.App{}, %{ client_name: "client", - scopes: ["scope"], + scopes: ["read"], redirect_uris: "url" }) ) diff --git a/test/marker_test.exs b/test/marker_test.exs index 5b6d0b4a4..7b3943c7b 100644 --- a/test/marker_test.exs +++ b/test/marker_test.exs @@ -33,8 +33,8 @@ test "return empty multi" do test "returns user markers" do user = insert(:user) marker = insert(:marker, user: user) - insert(:notification, user: user) - insert(:notification, user: user) + insert(:notification, user: user, activity: insert(:note_activity)) + insert(:notification, user: user, activity: insert(:note_activity)) insert(:marker, timeline: "home", user: user) assert Marker.get_markers( diff --git a/test/notification_test.exs b/test/notification_test.exs index a09b08675..f2e0f0b0d 100644 --- a/test/notification_test.exs +++ b/test/notification_test.exs @@ -179,17 +179,19 @@ test "does not create a notification for subscribed users if status is a reply" describe "create_notification" do @tag needs_streamer: true test "it creates a notification for user and send to the 'user' and the 'user:notification' stream" do - user = insert(:user) + %{user: user, token: oauth_token} = oauth_access(["read"]) task = Task.async(fn -> - Streamer.get_topic_and_add_socket("user", user) + {:ok, _topic} = Streamer.get_topic_and_add_socket("user", user, oauth_token) assert_receive {:render_with_user, _, _, _}, 4_000 end) task_user_notification = Task.async(fn -> - Streamer.get_topic_and_add_socket("user:notification", user) + {:ok, _topic} = + Streamer.get_topic_and_add_socket("user:notification", user, oauth_token) + assert_receive {:render_with_user, _, _, _}, 4_000 end) diff --git a/test/object/fetcher_test.exs b/test/object/fetcher_test.exs index 16cfa7f5c..14d2c645f 100644 --- a/test/object/fetcher_test.exs +++ b/test/object/fetcher_test.exs @@ -6,10 +6,12 @@ defmodule Pleroma.Object.FetcherTest do use Pleroma.DataCase alias Pleroma.Activity + alias Pleroma.Config alias Pleroma.Object alias Pleroma.Object.Fetcher - import Tesla.Mock + import Mock + import Tesla.Mock setup do mock(fn @@ -71,20 +73,20 @@ test "it works when fetching the OP actor errors out" do setup do: clear_config([:instance, :federation_incoming_replies_max_depth]) test "it returns thread depth exceeded error if thread depth is exceeded" do - Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0) + Config.put([:instance, :federation_incoming_replies_max_depth], 0) assert {:error, "Max thread distance exceeded."} = Fetcher.fetch_object_from_id(@ap_id, depth: 1) end test "it fetches object if max thread depth is restricted to 0 and depth is not specified" do - Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0) + Config.put([:instance, :federation_incoming_replies_max_depth], 0) assert {:ok, _} = Fetcher.fetch_object_from_id(@ap_id) end test "it fetches object if requested depth does not exceed max thread depth" do - Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 10) + Config.put([:instance, :federation_incoming_replies_max_depth], 10) assert {:ok, _} = Fetcher.fetch_object_from_id(@ap_id, depth: 10) end @@ -120,6 +122,16 @@ test "it fetches an object" do assert object == object_again end + + test "Return MRF reason when fetched status is rejected by one" do + clear_config([:mrf_keyword, :reject], ["yeah"]) + clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy]) + + assert {:reject, "[KeywordPolicy] Matches with rejected keyword"} == + Fetcher.fetch_object_from_id( + "http://mastodon.example.org/@admin/99541947525187367" + ) + end end describe "implementation quirks" do @@ -212,7 +224,7 @@ test "it can refetch pruned objects" do Pleroma.Signature, [:passthrough], [] do - Pleroma.Config.put([:activitypub, :sign_object_fetches], true) + Config.put([:activitypub, :sign_object_fetches], true) Fetcher.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367") @@ -223,7 +235,7 @@ test "it can refetch pruned objects" do Pleroma.Signature, [:passthrough], [] do - Pleroma.Config.put([:activitypub, :sign_object_fetches], false) + Config.put([:activitypub, :sign_object_fetches], false) Fetcher.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367") diff --git a/test/plugs/oauth_plug_test.exs b/test/plugs/oauth_plug_test.exs index f74c068cd..9d39d3153 100644 --- a/test/plugs/oauth_plug_test.exs +++ b/test/plugs/oauth_plug_test.exs @@ -16,7 +16,7 @@ defmodule Pleroma.Plugs.OAuthPlugTest do setup %{conn: conn} do user = insert(:user) - {:ok, %{token: token}} = Pleroma.Web.OAuth.Token.create_token(insert(:oauth_app), user) + {:ok, %{token: token}} = Pleroma.Web.OAuth.Token.create(insert(:oauth_app), user) %{user: user, token: token, conn: conn} end diff --git a/test/repo_test.exs b/test/repo_test.exs index 92e827c95..155791be2 100644 --- a/test/repo_test.exs +++ b/test/repo_test.exs @@ -37,7 +37,9 @@ test "get one-to-one assoc from repo" do test "get one-to-many assoc from repo" do user = insert(:user) - notification = refresh_record(insert(:notification, user: user)) + + notification = + refresh_record(insert(:notification, user: user, activity: insert(:note_activity))) assert Repo.get_assoc(user, :notifications) == {:ok, [notification]} end @@ -47,4 +49,32 @@ test "return error if has not assoc " do assert Repo.get_assoc(token, :user) == {:error, :not_found} end end + + describe "chunk_stream/3" do + test "fetch records one-by-one" do + users = insert_list(50, :user) + + {fetch_users, 50} = + from(t in User) + |> Repo.chunk_stream(5) + |> Enum.reduce({[], 0}, fn %User{} = user, {acc, count} -> + {acc ++ [user], count + 1} + end) + + assert users == fetch_users + end + + test "fetch records in bulk" do + users = insert_list(50, :user) + + {fetch_users, 10} = + from(t in User) + |> Repo.chunk_stream(5, :batches) + |> Enum.reduce({[], 0}, fn users, {acc, count} -> + {acc ++ users, count + 1} + end) + + assert users == fetch_users + end + end end diff --git a/test/stats_test.exs b/test/stats_test.exs index f09d8d31a..74bf785b0 100644 --- a/test/stats_test.exs +++ b/test/stats_test.exs @@ -4,7 +4,10 @@ defmodule Pleroma.StatsTest do use Pleroma.DataCase + import Pleroma.Factory + + alias Pleroma.Stats alias Pleroma.Web.CommonAPI describe "user count" do @@ -13,7 +16,7 @@ test "it ignores internal users" do _internal = insert(:user, local: true, nickname: nil) _internal = Pleroma.Web.ActivityPub.Relay.get_actor() - assert match?(%{stats: %{user_count: 1}}, Pleroma.Stats.calculate_stat_data()) + assert match?(%{stats: %{user_count: 1}}, Stats.calculate_stat_data()) end end @@ -47,23 +50,23 @@ test "on new status" do end) assert %{"direct" => 3, "private" => 4, "public" => 1, "unlisted" => 2} = - Pleroma.Stats.get_status_visibility_count() + Stats.get_status_visibility_count() end test "on status delete" do user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{visibility: "public", status: "hey"}) - assert %{"public" => 1} = Pleroma.Stats.get_status_visibility_count() + assert %{"public" => 1} = Stats.get_status_visibility_count() CommonAPI.delete(activity.id, user) - assert %{"public" => 0} = Pleroma.Stats.get_status_visibility_count() + assert %{"public" => 0} = Stats.get_status_visibility_count() end test "on status visibility update" do user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{visibility: "public", status: "hey"}) - assert %{"public" => 1, "private" => 0} = Pleroma.Stats.get_status_visibility_count() + assert %{"public" => 1, "private" => 0} = Stats.get_status_visibility_count() {:ok, _} = CommonAPI.update_activity_scope(activity.id, %{visibility: "private"}) - assert %{"public" => 0, "private" => 1} = Pleroma.Stats.get_status_visibility_count() + assert %{"public" => 0, "private" => 1} = Stats.get_status_visibility_count() end test "doesn't count unrelated activities" do @@ -75,7 +78,7 @@ test "doesn't count unrelated activities" do CommonAPI.repeat(activity.id, other_user) assert %{"direct" => 0, "private" => 0, "public" => 1, "unlisted" => 0} = - Pleroma.Stats.get_status_visibility_count() + Stats.get_status_visibility_count() end end @@ -110,10 +113,10 @@ test "single instance" do end) assert %{"direct" => 10, "private" => 0, "public" => 1, "unlisted" => 5} = - Pleroma.Stats.get_status_visibility_count(local_instance) + Stats.get_status_visibility_count(local_instance) assert %{"direct" => 0, "private" => 20, "public" => 0, "unlisted" => 0} = - Pleroma.Stats.get_status_visibility_count(instance2) + Stats.get_status_visibility_count(instance2) end end end diff --git a/test/support/data_case.ex b/test/support/data_case.ex index ba8848952..d5456521c 100644 --- a/test/support/data_case.ex +++ b/test/support/data_case.ex @@ -27,6 +27,21 @@ defmodule Pleroma.DataCase do import Ecto.Query import Pleroma.DataCase use Pleroma.Tests.Helpers + + # Sets up OAuth access with specified scopes + defp oauth_access(scopes, opts \\ []) do + user = + Keyword.get_lazy(opts, :user, fn -> + Pleroma.Factory.insert(:user) + end) + + token = + Keyword.get_lazy(opts, :oauth_token, fn -> + Pleroma.Factory.insert(:oauth_token, user: user, scopes: scopes) + end) + + %{user: user, token: token} + end end end diff --git a/test/support/factory.ex b/test/support/factory.ex index 486eda8da..fb82be0c4 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -31,6 +31,7 @@ def user_factory do nickname: sequence(:nickname, &"nick#{&1}"), password_hash: Pbkdf2.hash_pwd_salt("test"), bio: sequence(:bio, &"Tester Number #{&1}"), + discoverable: true, last_digest_emailed_at: NaiveDateTime.utc_now(), last_refreshed_at: NaiveDateTime.utc_now(), notification_settings: %Pleroma.User.NotificationSetting{}, @@ -200,25 +201,6 @@ def note_activity_factory(attrs \\ %{}) do |> Map.merge(attrs) end - defp expiration_offset_by_minutes(attrs, minutes) do - scheduled_at = - NaiveDateTime.utc_now() - |> NaiveDateTime.add(:timer.minutes(minutes), :millisecond) - |> NaiveDateTime.truncate(:second) - - %Pleroma.ActivityExpiration{} - |> Map.merge(attrs) - |> Map.put(:scheduled_at, scheduled_at) - end - - def expiration_in_the_past_factory(attrs \\ %{}) do - expiration_offset_by_minutes(attrs, -60) - end - - def expiration_in_the_future_factory(attrs \\ %{}) do - expiration_offset_by_minutes(attrs, 61) - end - def article_activity_factory do article = insert(:article) diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex index d9be248dc..cb022333f 100644 --- a/test/support/http_request_mock.ex +++ b/test/support/http_request_mock.ex @@ -103,14 +103,6 @@ def get( }} end - def get("https://mastodon.social/users/emelie.atom", _, _, _) do - {:ok, - %Tesla.Env{ - status: 200, - body: File.read!("test/fixtures/tesla_mock/emelie.atom") - }} - end - def get( "https://osada.macgirvin.com/.well-known/webfinger?resource=acct:mike@osada.macgirvin.com", _, @@ -137,14 +129,6 @@ def get( }} end - def get("https://pawoo.net/users/pekorino.atom", _, _, _) do - {:ok, - %Tesla.Env{ - status: 200, - body: File.read!("test/fixtures/tesla_mock/https___pawoo.net_users_pekorino.atom") - }} - end - def get( "https://pawoo.net/.well-known/webfinger?resource=acct:https://pawoo.net/users/pekorino", _, @@ -158,19 +142,6 @@ def get( }} end - def get( - "https://social.stopwatchingus-heidelberg.de/api/statuses/user_timeline/18330.atom", - _, - _, - _ - ) do - {:ok, - %Tesla.Env{ - status: 200, - body: File.read!("test/fixtures/tesla_mock/atarifrosch_feed.xml") - }} - end - def get( "https://social.stopwatchingus-heidelberg.de/.well-known/webfinger?resource=acct:https://social.stopwatchingus-heidelberg.de/user/18330", _, @@ -184,27 +155,6 @@ def get( }} end - def get("https://mamot.fr/users/Skruyb.atom", _, _, _) do - {:ok, - %Tesla.Env{ - status: 200, - body: File.read!("test/fixtures/tesla_mock/https___mamot.fr_users_Skruyb.atom") - }} - end - - def get( - "https://mamot.fr/.well-known/webfinger?resource=acct:https://mamot.fr/users/Skruyb", - _, - _, - [{"accept", "application/xrd+xml,application/jrd+json"}] - ) do - {:ok, - %Tesla.Env{ - status: 200, - body: File.read!("test/fixtures/tesla_mock/skruyb@mamot.fr.atom") - }} - end - def get( "https://social.heldscal.la/.well-known/webfinger?resource=nonexistant@social.heldscal.la", _, @@ -507,19 +457,6 @@ def get("https://mamot.fr/.well-known/host-meta", _, _, _) do }} end - def get( - "https://mamot.fr/.well-known/webfinger?resource=https://mamot.fr/users/Skruyb", - _, - _, - _ - ) do - {:ok, - %Tesla.Env{ - status: 200, - body: File.read!("test/fixtures/tesla_mock/skruyb@mamot.fr.atom") - }} - end - def get("http://pawoo.net/.well-known/host-meta", _, _, _) do {:ok, %Tesla.Env{ @@ -647,17 +584,6 @@ def get( }} end - def get("https://pleroma.soykaf.com/users/lain/feed.atom", _, _, _) do - {:ok, - %Tesla.Env{ - status: 200, - body: - File.read!( - "test/fixtures/tesla_mock/https___pleroma.soykaf.com_users_lain_feed.atom.xml" - ) - }} - end - def get(url, _, _, [{"accept", "application/xrd+xml,application/jrd+json"}]) when url in [ "https://pleroma.soykaf.com/.well-known/webfinger?resource=acct:https://pleroma.soykaf.com/users/lain", @@ -670,17 +596,6 @@ def get(url, _, _, [{"accept", "application/xrd+xml,application/jrd+json"}]) }} end - def get("https://shitposter.club/api/statuses/user_timeline/1.atom", _, _, _) do - {:ok, - %Tesla.Env{ - status: 200, - body: - File.read!( - "test/fixtures/tesla_mock/https___shitposter.club_api_statuses_user_timeline_1.atom.xml" - ) - }} - end - def get( "https://shitposter.club/.well-known/webfinger?resource=https://shitposter.club/user/1", _, @@ -694,37 +609,10 @@ def get( }} end - def get("https://shitposter.club/notice/2827873", _, _, _) do - {:ok, - %Tesla.Env{ - status: 200, - body: File.read!("test/fixtures/tesla_mock/https___shitposter.club_notice_2827873.json") - }} - end - - def get("https://shitposter.club/api/statuses/show/2827873.atom", _, _, _) do - {:ok, - %Tesla.Env{ - status: 200, - body: - File.read!( - "test/fixtures/tesla_mock/https___shitposter.club_api_statuses_show_2827873.atom.xml" - ) - }} - end - def get("https://testing.pleroma.lol/objects/b319022a-4946-44c5-9de9-34801f95507b", _, _, _) do {:ok, %Tesla.Env{status: 200}} end - def get("https://shitposter.club/api/statuses/user_timeline/5381.atom", _, _, _) do - {:ok, - %Tesla.Env{ - status: 200, - body: File.read!("test/fixtures/tesla_mock/spc_5381.atom") - }} - end - def get( "https://shitposter.club/.well-known/webfinger?resource=https://shitposter.club/user/5381", _, @@ -746,14 +634,6 @@ def get("http://shitposter.club/.well-known/host-meta", _, _, _) do }} end - def get("https://shitposter.club/api/statuses/show/7369654.atom", _, _, _) do - {:ok, - %Tesla.Env{ - status: 200, - body: File.read!("test/fixtures/tesla_mock/7369654.atom") - }} - end - def get("https://shitposter.club/notice/4027863", _, _, _) do {:ok, %Tesla.Env{ @@ -762,14 +642,6 @@ def get("https://shitposter.club/notice/4027863", _, _, _) do }} end - def get("https://social.sakamoto.gq/users/eal/feed.atom", _, _, _) do - {:ok, - %Tesla.Env{ - status: 200, - body: File.read!("test/fixtures/tesla_mock/sakamoto_eal_feed.atom") - }} - end - def get("http://social.sakamoto.gq/.well-known/host-meta", _, _, _) do {:ok, %Tesla.Env{ @@ -791,15 +663,6 @@ def get( }} end - def get( - "https://social.sakamoto.gq/objects/0ccc1a2c-66b0-4305-b23a-7f7f2b040056", - _, - _, - [{"accept", "application/atom+xml"}] - ) do - {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/sakamoto.atom")}} - end - def get("http://mastodon.social/.well-known/host-meta", _, _, _) do {:ok, %Tesla.Env{ @@ -853,28 +716,6 @@ def get( {:ok, %Tesla.Env{status: 406, body: ""}} end - def get("http://gs.example.org/index.php/api/statuses/user_timeline/1.atom", _, _, _) do - {:ok, - %Tesla.Env{ - status: 200, - body: - File.read!( - "test/fixtures/tesla_mock/http__gs.example.org_index.php_api_statuses_user_timeline_1.atom.xml" - ) - }} - end - - def get("https://social.heldscal.la/api/statuses/user_timeline/29191.atom", _, _, _) do - {:ok, - %Tesla.Env{ - status: 200, - body: - File.read!( - "test/fixtures/tesla_mock/https___social.heldscal.la_api_statuses_user_timeline_29191.atom.xml" - ) - }} - end - def get("http://squeet.me/.well-known/host-meta", _, _, _) do {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/squeet.me_host_meta")}} @@ -996,17 +837,6 @@ def get( }} end - def get("https://social.heldscal.la/api/statuses/user_timeline/23211.atom", _, _, _) do - {:ok, - %Tesla.Env{ - status: 200, - body: - File.read!( - "test/fixtures/tesla_mock/https___social.heldscal.la_api_statuses_user_timeline_23211.atom.xml" - ) - }} - end - def get( "https://social.heldscal.la/.well-known/webfinger?resource=https://social.heldscal.la/user/23211", _, @@ -1036,10 +866,6 @@ def get("https://social.heldscal.la/.well-known/host-meta", _, _, _) do }} end - def get("https://mastodon.social/users/lambadalambda.atom", _, _, _) do - {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/lambadalambda.atom")}} - end - def get("https://mastodon.social/users/lambadalambda", _, _, _) do {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/lambadalambda.json")}} end diff --git a/test/tasks/config_test.exs b/test/tasks/config_test.exs index fb12e7fb3..f36648829 100644 --- a/test/tasks/config_test.exs +++ b/test/tasks/config_test.exs @@ -40,6 +40,19 @@ test "error if file with custom settings doesn't exist" do on_exit(fn -> Application.put_env(:quack, :level, initial) end) end + @tag capture_log: true + test "config migration refused when deprecated settings are found" do + clear_config([:media_proxy, :whitelist], ["domain_without_scheme.com"]) + assert Repo.all(ConfigDB) == [] + + Mix.Tasks.Pleroma.Config.migrate_to_db("test/fixtures/config/temp.secret.exs") + + assert_received {:mix_shell, :error, [message]} + + assert message =~ + "Migration is not allowed until all deprecation warnings have been resolved." + end + test "filtered settings are migrated to db" do assert Repo.all(ConfigDB) == [] diff --git a/test/tasks/database_test.exs b/test/tasks/database_test.exs index 3a28aa133..292a5ef5f 100644 --- a/test/tasks/database_test.exs +++ b/test/tasks/database_test.exs @@ -3,14 +3,15 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Mix.Tasks.Pleroma.DatabaseTest do + use Pleroma.DataCase + use Oban.Testing, repo: Pleroma.Repo + alias Pleroma.Activity alias Pleroma.Object alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.CommonAPI - use Pleroma.DataCase - import Pleroma.Factory setup_all do @@ -130,40 +131,45 @@ test "it turns OrderedCollection likes into empty arrays" do describe "ensure_expiration" do test "it adds to expiration old statuses" do - %{id: activity_id1} = insert(:note_activity) + activity1 = insert(:note_activity) - %{id: activity_id2} = - insert(:note_activity, %{inserted_at: NaiveDateTime.from_iso8601!("2015-01-23 23:50:07")}) + {:ok, inserted_at, 0} = DateTime.from_iso8601("2015-01-23T23:50:07Z") + activity2 = insert(:note_activity, %{inserted_at: inserted_at}) - %{id: activity_id3} = activity3 = insert(:note_activity) + %{id: activity_id3} = insert(:note_activity) - expires_at = - NaiveDateTime.utc_now() - |> NaiveDateTime.add(60 * 61, :second) - |> NaiveDateTime.truncate(:second) + expires_at = DateTime.add(DateTime.utc_now(), 60 * 61) - Pleroma.ActivityExpiration.create(activity3, expires_at) + Pleroma.Workers.PurgeExpiredActivity.enqueue(%{ + activity_id: activity_id3, + expires_at: expires_at + }) Mix.Tasks.Pleroma.Database.run(["ensure_expiration"]) - expirations = - Pleroma.ActivityExpiration - |> order_by(:activity_id) - |> Repo.all() + assert_enqueued( + worker: Pleroma.Workers.PurgeExpiredActivity, + args: %{activity_id: activity1.id}, + scheduled_at: + activity1.inserted_at + |> DateTime.from_naive!("Etc/UTC") + |> Timex.shift(days: 365) + ) - assert [ - %Pleroma.ActivityExpiration{ - activity_id: ^activity_id1 - }, - %Pleroma.ActivityExpiration{ - activity_id: ^activity_id2, - scheduled_at: ~N[2016-01-23 23:50:07] - }, - %Pleroma.ActivityExpiration{ - activity_id: ^activity_id3, - scheduled_at: ^expires_at - } - ] = expirations + assert_enqueued( + worker: Pleroma.Workers.PurgeExpiredActivity, + args: %{activity_id: activity2.id}, + scheduled_at: + activity2.inserted_at + |> DateTime.from_naive!("Etc/UTC") + |> Timex.shift(days: 365) + ) + + assert_enqueued( + worker: Pleroma.Workers.PurgeExpiredActivity, + args: %{activity_id: activity_id3}, + scheduled_at: expires_at + ) end end end diff --git a/test/tasks/frontend_test.exs b/test/tasks/frontend_test.exs index 0ca2b9a28..022ae51be 100644 --- a/test/tasks/frontend_test.exs +++ b/test/tasks/frontend_test.exs @@ -48,11 +48,18 @@ test "it also works given a file" do } }) + folder = Path.join([@dir, "frontends", "pleroma", "fantasy"]) + previously_existing = Path.join([folder, "temp"]) + File.mkdir_p!(folder) + File.write!(previously_existing, "yey") + assert File.exists?(previously_existing) + capture_io(fn -> Frontend.run(["install", "pleroma", "--file", "test/fixtures/tesla_mock/frontend.zip"]) end) - assert File.exists?(Path.join([@dir, "frontends", "pleroma", "fantasy", "test.txt"])) + assert File.exists?(Path.join([folder, "test.txt"])) + refute File.exists?(previously_existing) end test "it downloads and unzips unknown frontends" do diff --git a/test/user/import_test.exs b/test/user/import_test.exs new file mode 100644 index 000000000..e404deeb5 --- /dev/null +++ b/test/user/import_test.exs @@ -0,0 +1,76 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.User.ImportTest do + alias Pleroma.Repo + alias Pleroma.Tests.ObanHelpers + alias Pleroma.User + + use Pleroma.DataCase + use Oban.Testing, repo: Pleroma.Repo + + import Pleroma.Factory + + setup_all do + Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) + :ok + end + + describe "follow_import" do + test "it imports user followings from list" do + [user1, user2, user3] = insert_list(3, :user) + + identifiers = [ + user2.ap_id, + user3.nickname + ] + + {:ok, job} = User.Import.follow_import(user1, identifiers) + + assert {:ok, result} = ObanHelpers.perform(job) + assert is_list(result) + assert result == [user2, user3] + assert User.following?(user1, user2) + assert User.following?(user1, user3) + end + end + + describe "blocks_import" do + test "it imports user blocks from list" do + [user1, user2, user3] = insert_list(3, :user) + + identifiers = [ + user2.ap_id, + user3.nickname + ] + + {:ok, job} = User.Import.blocks_import(user1, identifiers) + + assert {:ok, result} = ObanHelpers.perform(job) + assert is_list(result) + assert result == [user2, user3] + assert User.blocks?(user1, user2) + assert User.blocks?(user1, user3) + end + end + + describe "mutes_import" do + test "it imports user mutes from list" do + [user1, user2, user3] = insert_list(3, :user) + + identifiers = [ + user2.ap_id, + user3.nickname + ] + + {:ok, job} = User.Import.mutes_import(user1, identifiers) + + assert {:ok, result} = ObanHelpers.perform(job) + assert is_list(result) + assert result == [user2, user3] + assert User.mutes?(user1, user2) + assert User.mutes?(user1, user3) + end + end +end diff --git a/test/user_search_test.exs b/test/user_search_test.exs index 01976bf58..8529ce6db 100644 --- a/test/user_search_test.exs +++ b/test/user_search_test.exs @@ -25,6 +25,14 @@ test "excludes invisible users from results" do assert found_user.id == user.id end + test "excludes users when discoverable is false" do + insert(:user, %{nickname: "john 3000", discoverable: false}) + insert(:user, %{nickname: "john 3001"}) + + users = User.search("john") + assert Enum.count(users) == 1 + end + test "excludes service actors from results" do insert(:user, actor_type: "Application", nickname: "user1") service = insert(:user, actor_type: "Service", nickname: "user2") diff --git a/test/user_test.exs b/test/user_test.exs index 9cabb501c..d506f7047 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -976,23 +976,6 @@ test "it sets the follower_count property" do end end - describe "follow_import" do - test "it imports user followings from list" do - [user1, user2, user3] = insert_list(3, :user) - - identifiers = [ - user2.ap_id, - user3.nickname - ] - - {:ok, job} = User.follow_import(user1, identifiers) - - assert {:ok, result} = ObanHelpers.perform(job) - assert is_list(result) - assert result == [user2, user3] - end - end - describe "mutes" do test "it mutes people" do user = insert(:user) @@ -1199,23 +1182,6 @@ test "follows take precedence over domain blocks" do end end - describe "blocks_import" do - test "it imports user blocks from list" do - [user1, user2, user3] = insert_list(3, :user) - - identifiers = [ - user2.ap_id, - user3.nickname - ] - - {:ok, job} = User.blocks_import(user1, identifiers) - - assert {:ok, result} = ObanHelpers.perform(job) - assert is_list(result) - assert result == [user2, user3] - end - end - describe "get_recipients_from_activity" do test "works for announces" do actor = insert(:user) @@ -1510,7 +1476,7 @@ test "delete/1 purges a user when they wouldn't be fully deleted" do user = User.get_by_id(user.id) assert %User{ - bio: nil, + bio: "", raw_bio: nil, email: nil, name: nil, @@ -1681,7 +1647,7 @@ test "returns true when the account is itself" do assert User.visible_for(user, user) == :visible end - test "returns false when the account is unauthenticated and auth is required" do + test "returns false when the account is unconfirmed and confirmation is required" do Pleroma.Config.put([:instance, :account_activation_required], true) user = insert(:user, local: true, confirmation_pending: true) @@ -1690,14 +1656,23 @@ test "returns false when the account is unauthenticated and auth is required" do refute User.visible_for(user, other_user) == :visible end - test "returns true when the account is unauthenticated and auth is not required" do + test "returns true when the account is unconfirmed and confirmation is required but the account is remote" do + Pleroma.Config.put([:instance, :account_activation_required], true) + + user = insert(:user, local: false, confirmation_pending: true) + other_user = insert(:user, local: true) + + assert User.visible_for(user, other_user) == :visible + end + + test "returns true when the account is unconfirmed and confirmation is not required" do user = insert(:user, local: true, confirmation_pending: true) other_user = insert(:user, local: true) assert User.visible_for(user, other_user) == :visible end - test "returns true when the account is unauthenticated and being viewed by a privileged account (auth required)" do + test "returns true when the account is unconfirmed and being viewed by a privileged account (confirmation required)" do Pleroma.Config.put([:instance, :account_activation_required], true) user = insert(:user, local: true, confirmation_pending: true) diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index b579bb0bb..7bdad3810 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -239,7 +239,7 @@ test "drops activities beyond a certain limit" do } } - assert {:error, {:remote_limit_error, _}} = ActivityPub.insert(data) + assert {:error, :remote_limit} = ActivityPub.insert(data) end test "doesn't drop activities with content being null" do @@ -386,9 +386,11 @@ test "can be fetched into a timeline" do end describe "create activities" do - test "it reverts create" do - user = insert(:user) + setup do + [user: insert(:user)] + end + test "it reverts create", %{user: user} do with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do assert {:error, :reverted} = ActivityPub.create(%{ @@ -407,9 +409,47 @@ test "it reverts create" do assert Repo.aggregate(Object, :count, :id) == 0 end - test "removes doubled 'to' recipients" do - user = insert(:user) + test "creates activity if expiration is not configured and expires_at is not passed", %{ + user: user + } do + clear_config([Pleroma.Workers.PurgeExpiredActivity, :enabled], false) + assert {:ok, _} = + ActivityPub.create(%{ + to: ["user1", "user2"], + actor: user, + context: "", + object: %{ + "to" => ["user1", "user2"], + "type" => "Note", + "content" => "testing" + } + }) + end + + test "rejects activity if expires_at present but expiration is not configured", %{user: user} do + clear_config([Pleroma.Workers.PurgeExpiredActivity, :enabled], false) + + assert {:error, :expired_activities_disabled} = + ActivityPub.create(%{ + to: ["user1", "user2"], + actor: user, + context: "", + object: %{ + "to" => ["user1", "user2"], + "type" => "Note", + "content" => "testing" + }, + additional: %{ + "expires_at" => DateTime.utc_now() + } + }) + + assert Repo.aggregate(Activity, :count, :id) == 0 + assert Repo.aggregate(Object, :count, :id) == 0 + end + + test "removes doubled 'to' recipients", %{user: user} do {:ok, activity} = ActivityPub.create(%{ to: ["user1", "user1", "user2"], @@ -427,9 +467,7 @@ test "removes doubled 'to' recipients" do assert activity.recipients == ["user1", "user2", user.ap_id] end - test "increases user note count only for public activities" do - user = insert(:user) - + test "increases user note count only for public activities", %{user: user} do {:ok, _} = CommonAPI.post(User.get_cached_by_id(user.id), %{ status: "1", @@ -458,8 +496,7 @@ test "increases user note count only for public activities" do assert user.note_count == 2 end - test "increases replies count" do - user = insert(:user) + test "increases replies count", %{user: user} do user2 = insert(:user) {:ok, activity} = CommonAPI.post(user, %{status: "1", visibility: "public"}) @@ -2077,18 +2114,25 @@ test "it just returns the input if the user has no following/follower addresses" end describe "global activity expiration" do - setup do: clear_config([:mrf, :policies]) - test "creates an activity expiration for local Create activities" do - Pleroma.Config.put( - [:mrf, :policies], - Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy + clear_config([:mrf, :policies], Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy) + + {:ok, activity} = ActivityBuilder.insert(%{"type" => "Create", "context" => "3hu"}) + {:ok, follow} = ActivityBuilder.insert(%{"type" => "Follow", "context" => "3hu"}) + + assert_enqueued( + worker: Pleroma.Workers.PurgeExpiredActivity, + args: %{activity_id: activity.id}, + scheduled_at: + activity.inserted_at + |> DateTime.from_naive!("Etc/UTC") + |> Timex.shift(days: 365) ) - {:ok, %{id: id_create}} = ActivityBuilder.insert(%{"type" => "Create", "context" => "3hu"}) - {:ok, _follow} = ActivityBuilder.insert(%{"type" => "Follow", "context" => "3hu"}) - - assert [%{activity_id: ^id_create}] = Pleroma.ActivityExpiration |> Repo.all() + refute_enqueued( + worker: Pleroma.Workers.PurgeExpiredActivity, + args: %{activity_id: follow.id} + ) end end diff --git a/test/web/activity_pub/mrf/activity_expiration_policy_test.exs b/test/web/activity_pub/mrf/activity_expiration_policy_test.exs index f25cf8b12..e7370d4ef 100644 --- a/test/web/activity_pub/mrf/activity_expiration_policy_test.exs +++ b/test/web/activity_pub/mrf/activity_expiration_policy_test.exs @@ -18,11 +18,11 @@ test "adds `expires_at` property" do "object" => %{"type" => "Note"} }) - assert Timex.diff(expires_at, NaiveDateTime.utc_now(), :days) == 364 + assert Timex.diff(expires_at, DateTime.utc_now(), :days) == 364 end test "keeps existing `expires_at` if it less than the config setting" do - expires_at = NaiveDateTime.utc_now() |> Timex.shift(days: 1) + expires_at = DateTime.utc_now() |> Timex.shift(days: 1) assert {:ok, %{"type" => "Create", "expires_at" => ^expires_at}} = ActivityExpirationPolicy.filter(%{ @@ -35,7 +35,7 @@ test "keeps existing `expires_at` if it less than the config setting" do end test "overwrites existing `expires_at` if it greater than the config setting" do - too_distant_future = NaiveDateTime.utc_now() |> Timex.shift(years: 2) + too_distant_future = DateTime.utc_now() |> Timex.shift(years: 2) assert {:ok, %{"type" => "Create", "expires_at" => expires_at}} = ActivityExpirationPolicy.filter(%{ @@ -46,7 +46,7 @@ test "overwrites existing `expires_at` if it greater than the config setting" do "object" => %{"type" => "Note"} }) - assert Timex.diff(expires_at, NaiveDateTime.utc_now(), :days) == 364 + assert Timex.diff(expires_at, DateTime.utc_now(), :days) == 364 end test "ignores remote activities" do diff --git a/test/web/activity_pub/mrf/force_bot_unlisted_policy_test.exs b/test/web/activity_pub/mrf/force_bot_unlisted_policy_test.exs new file mode 100644 index 000000000..86dd9ddae --- /dev/null +++ b/test/web/activity_pub/mrf/force_bot_unlisted_policy_test.exs @@ -0,0 +1,60 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicyTest do + use Pleroma.DataCase + import Pleroma.Factory + + alias Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy + @public "https://www.w3.org/ns/activitystreams#Public" + + defp generate_messages(actor) do + {%{ + "actor" => actor.ap_id, + "type" => "Create", + "object" => %{}, + "to" => [@public, "f"], + "cc" => [actor.follower_address, "d"] + }, + %{ + "actor" => actor.ap_id, + "type" => "Create", + "object" => %{"to" => ["f", actor.follower_address], "cc" => ["d", @public]}, + "to" => ["f", actor.follower_address], + "cc" => ["d", @public] + }} + end + + test "removes from the federated timeline by nickname heuristics 1" do + actor = insert(:user, %{nickname: "annoying_ebooks@example.com"}) + + {message, except_message} = generate_messages(actor) + + assert ForceBotUnlistedPolicy.filter(message) == {:ok, except_message} + end + + test "removes from the federated timeline by nickname heuristics 2" do + actor = insert(:user, %{nickname: "cirnonewsnetworkbot@meow.cat"}) + + {message, except_message} = generate_messages(actor) + + assert ForceBotUnlistedPolicy.filter(message) == {:ok, except_message} + end + + test "removes from the federated timeline by actor type Application" do + actor = insert(:user, %{actor_type: "Application"}) + + {message, except_message} = generate_messages(actor) + + assert ForceBotUnlistedPolicy.filter(message) == {:ok, except_message} + end + + test "removes from the federated timeline by actor type Service" do + actor = insert(:user, %{actor_type: "Service"}) + + {message, except_message} = generate_messages(actor) + + assert ForceBotUnlistedPolicy.filter(message) == {:ok, except_message} + end +end diff --git a/test/web/activity_pub/mrf/mediaproxy_warming_policy_test.exs b/test/web/activity_pub/mrf/mediaproxy_warming_policy_test.exs index 313d59a66..1710c4d2a 100644 --- a/test/web/activity_pub/mrf/mediaproxy_warming_policy_test.exs +++ b/test/web/activity_pub/mrf/mediaproxy_warming_policy_test.exs @@ -22,6 +22,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicyTest do } } + setup do: clear_config([:media_proxy, :enabled], true) + test "it prefetches media proxy URIs" do with_mock HTTP, get: fn _, _, _ -> {:ok, []} end do MediaProxyWarmingPolicy.filter(@message) diff --git a/test/web/activity_pub/object_validators/note_validator_test.exs b/test/web/activity_pub/object_validators/article_note_validator_test.exs similarity index 76% rename from test/web/activity_pub/object_validators/note_validator_test.exs rename to test/web/activity_pub/object_validators/article_note_validator_test.exs index 30c481ffb..cc6dab872 100644 --- a/test/web/activity_pub/object_validators/note_validator_test.exs +++ b/test/web/activity_pub/object_validators/article_note_validator_test.exs @@ -2,10 +2,10 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidatorTest do +defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidatorTest do use Pleroma.DataCase - alias Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator alias Pleroma.Web.ActivityPub.Utils import Pleroma.Factory @@ -29,7 +29,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidatorTest do end test "a basic note validates", %{note: note} do - %{valid?: true} = NoteValidator.cast_and_validate(note) + %{valid?: true} = ArticleNoteValidator.cast_and_validate(note) end end end diff --git a/test/web/activity_pub/object_validators/chat_validation_test.exs b/test/web/activity_pub/object_validators/chat_validation_test.exs index 50bf03515..16e4808e5 100644 --- a/test/web/activity_pub/object_validators/chat_validation_test.exs +++ b/test/web/activity_pub/object_validators/chat_validation_test.exs @@ -69,6 +69,7 @@ test "validates for a basic object we build", %{valid_chat_message: valid_chat_m assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, []) assert Map.put(valid_chat_message, "attachment", nil) == object + assert match?(%{"firefox" => _}, object["emoji"]) end test "validates for a basic object with an attachment", %{ diff --git a/test/web/activity_pub/transmogrifier/article_handling_test.exs b/test/web/activity_pub/transmogrifier/article_handling_test.exs new file mode 100644 index 000000000..9b12a470a --- /dev/null +++ b/test/web/activity_pub/transmogrifier/article_handling_test.exs @@ -0,0 +1,75 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.Transmogrifier.ArticleHandlingTest do + use Oban.Testing, repo: Pleroma.Repo + use Pleroma.DataCase + + alias Pleroma.Activity + alias Pleroma.Object + alias Pleroma.Object.Fetcher + alias Pleroma.Web.ActivityPub.Transmogrifier + + test "Pterotype (Wordpress Plugin) Article" do + Tesla.Mock.mock(fn %{url: "https://wedistribute.org/wp-json/pterotype/v1/actor/-blog"} -> + %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/wedistribute-user.json")} + end) + + data = + File.read!("test/fixtures/tesla_mock/wedistribute-create-article.json") |> Jason.decode!() + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + + object = Object.normalize(data["object"]) + + assert object.data["name"] == "The end is near: Mastodon plans to drop OStatus support" + + assert object.data["summary"] == + "One of the largest platforms in the federated social web is dropping the protocol that it started with." + + assert object.data["url"] == "https://wedistribute.org/2019/07/mastodon-drops-ostatus/" + end + + test "Plume Article" do + Tesla.Mock.mock(fn + %{url: "https://baptiste.gelez.xyz/~/PlumeDevelopment/this-month-in-plume-june-2018/"} -> + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/tesla_mock/baptiste.gelex.xyz-article.json") + } + + %{url: "https://baptiste.gelez.xyz/@/BaptisteGelez"} -> + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/tesla_mock/baptiste.gelex.xyz-user.json") + } + end) + + {:ok, object} = + Fetcher.fetch_object_from_id( + "https://baptiste.gelez.xyz/~/PlumeDevelopment/this-month-in-plume-june-2018/" + ) + + assert object.data["name"] == "This Month in Plume: June 2018" + + assert object.data["url"] == + "https://baptiste.gelez.xyz/~/PlumeDevelopment/this-month-in-plume-june-2018/" + end + + test "Prismo Article" do + Tesla.Mock.mock(fn %{url: "https://prismo.news/@mxb"} -> + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/tesla_mock/https___prismo.news__mxb.json") + } + end) + + data = File.read!("test/fixtures/prismo-url-map.json") |> Jason.decode!() + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + object = Object.normalize(data["object"]) + + assert object.data["url"] == "https://prismo.news/posts/83" + end +end diff --git a/test/web/activity_pub/transmogrifier/question_handling_test.exs b/test/web/activity_pub/transmogrifier/question_handling_test.exs index c82361828..74ee79543 100644 --- a/test/web/activity_pub/transmogrifier/question_handling_test.exs +++ b/test/web/activity_pub/transmogrifier/question_handling_test.exs @@ -106,6 +106,57 @@ test "Mastodon Question activity with HTML tags in plaintext" do assert Enum.sort(object.data["oneOf"]) == Enum.sort(options) end + test "Mastodon Question activity with custom emojis" do + options = [ + %{ + "type" => "Note", + "name" => ":blobcat:", + "replies" => %{"totalItems" => 0, "type" => "Collection"} + }, + %{ + "type" => "Note", + "name" => ":blobfox:", + "replies" => %{"totalItems" => 0, "type" => "Collection"} + } + ] + + tag = [ + %{ + "icon" => %{ + "type" => "Image", + "url" => "https://blob.cat/emoji/custom/blobcats/blobcat.png" + }, + "id" => "https://blob.cat/emoji/custom/blobcats/blobcat.png", + "name" => ":blobcat:", + "type" => "Emoji", + "updated" => "1970-01-01T00:00:00Z" + }, + %{ + "icon" => %{"type" => "Image", "url" => "https://blob.cat/emoji/blobfox/blobfox.png"}, + "id" => "https://blob.cat/emoji/blobfox/blobfox.png", + "name" => ":blobfox:", + "type" => "Emoji", + "updated" => "1970-01-01T00:00:00Z" + } + ] + + data = + File.read!("test/fixtures/mastodon-question-activity.json") + |> Poison.decode!() + |> Kernel.put_in(["object", "oneOf"], options) + |> Kernel.put_in(["object", "tag"], tag) + + {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data) + object = Object.normalize(activity, false) + + assert object.data["oneOf"] == options + + assert object.data["emoji"] == %{ + "blobcat" => "https://blob.cat/emoji/custom/blobcats/blobcat.png", + "blobfox" => "https://blob.cat/emoji/blobfox/blobfox.png" + } + end + test "returns an error if received a second time" do data = File.read!("test/fixtures/mastodon-question-activity.json") |> Poison.decode!() diff --git a/test/web/activity_pub/transmogrifier/video_handling_test.exs b/test/web/activity_pub/transmogrifier/video_handling_test.exs new file mode 100644 index 000000000..69c953a2e --- /dev/null +++ b/test/web/activity_pub/transmogrifier/video_handling_test.exs @@ -0,0 +1,93 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.Transmogrifier.VideoHandlingTest do + use Oban.Testing, repo: Pleroma.Repo + use Pleroma.DataCase + + alias Pleroma.Activity + alias Pleroma.Object + alias Pleroma.Object.Fetcher + alias Pleroma.Web.ActivityPub.Transmogrifier + + setup_all do + Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) + :ok + end + + test "skip converting the content when it is nil" do + data = + File.read!("test/fixtures/tesla_mock/framatube.org-video.json") + |> Jason.decode!() + |> Kernel.put_in(["object", "content"], nil) + + {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data) + + assert object = Object.normalize(activity, false) + + assert object.data["content"] == nil + end + + test "it converts content of object to html" do + data = File.read!("test/fixtures/tesla_mock/framatube.org-video.json") |> Jason.decode!() + + {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data) + + assert object = Object.normalize(activity, false) + + assert object.data["content"] == + "

Après avoir mené avec un certain succès la campagne « Dégooglisons Internet » en 2014, l’association Framasoft annonce fin 2019 arrêter progressivement un certain nombre de ses services alternatifs aux GAFAM. Pourquoi ?

Transcription par @aprilorg ici : https://www.april.org/deframasoftisons-internet-pierre-yves-gosset-framasoft

" + end + + test "it remaps video URLs as attachments if necessary" do + {:ok, object} = + Fetcher.fetch_object_from_id( + "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3" + ) + + assert object.data["url"] == + "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3" + + assert object.data["attachment"] == [ + %{ + "type" => "Link", + "mediaType" => "video/mp4", + "name" => nil, + "url" => [ + %{ + "href" => + "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4", + "mediaType" => "video/mp4", + "type" => "Link" + } + ] + } + ] + + data = File.read!("test/fixtures/tesla_mock/framatube.org-video.json") |> Jason.decode!() + + {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data) + + assert object = Object.normalize(activity, false) + + assert object.data["attachment"] == [ + %{ + "type" => "Link", + "mediaType" => "video/mp4", + "name" => nil, + "url" => [ + %{ + "href" => + "https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4", + "mediaType" => "video/mp4", + "type" => "Link" + } + ] + } + ] + + assert object.data["url"] == + "https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206" + end +end diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index 3fa41b0c7..561674f01 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -8,7 +8,6 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do alias Pleroma.Activity alias Pleroma.Object - alias Pleroma.Object.Fetcher alias Pleroma.Tests.ObanHelpers alias Pleroma.User alias Pleroma.Web.ActivityPub.Transmogrifier @@ -45,15 +44,6 @@ test "it works for incoming notices with tag not being an array (kroeg)" do assert "test" in object.data["tag"] end - test "it works for incoming notices with url not being a string (prismo)" do - data = File.read!("test/fixtures/prismo-url-map.json") |> Poison.decode!() - - {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) - object = Object.normalize(data["object"]) - - assert object.data["url"] == "https://prismo.news/posts/83" - end - test "it cleans up incoming notices which are not really DMs" do user = insert(:user) other_user = insert(:user) @@ -105,7 +95,7 @@ test "it fetches reply-to activities if we don't have them" do object = data["object"] - |> Map.put("inReplyTo", "https://shitposter.club/notice/2827873") + |> Map.put("inReplyTo", "https://mstdn.io/users/mayuutann/statuses/99568293732299394") data = Map.put(data, "object", object) {:ok, returned_activity} = Transmogrifier.handle_incoming(data) @@ -113,10 +103,11 @@ test "it fetches reply-to activities if we don't have them" do assert activity = Activity.get_create_by_object_ap_id( - "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment" + "https://mstdn.io/users/mayuutann/statuses/99568293732299394" ) - assert returned_object.data["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873" + assert returned_object.data["inReplyTo"] == + "https://mstdn.io/users/mayuutann/statuses/99568293732299394" end test "it does not fetch reply-to activities beyond max replies depth limit" do @@ -140,8 +131,7 @@ test "it does not fetch reply-to activities beyond max replies depth limit" do "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment" ) - assert returned_object.data["inReplyToAtomUri"] == - "https://shitposter.club/notice/2827873" + assert returned_object.data["inReplyTo"] == "https://shitposter.club/notice/2827873" end end @@ -355,83 +345,6 @@ test "it works for incoming unfollows with an existing follow" do refute User.following?(User.get_cached_by_ap_id(data["actor"]), user) end - test "skip converting the content when it is nil" do - object_id = "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe" - - {:ok, object} = Fetcher.fetch_and_contain_remote_object_from_id(object_id) - - result = - Pleroma.Web.ActivityPub.Transmogrifier.fix_object(Map.merge(object, %{"content" => nil})) - - assert result["content"] == nil - end - - test "it converts content of object to html" do - object_id = "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe" - - {:ok, %{"content" => content_markdown}} = - Fetcher.fetch_and_contain_remote_object_from_id(object_id) - - {:ok, %Pleroma.Object{data: %{"content" => content}} = object} = - Fetcher.fetch_object_from_id(object_id) - - assert content_markdown == - "Support this and our other Michigan!/usr/group videos and meetings. Learn more at http://mug.org/membership\n\nTwenty Years in Jail: FreeBSD's Jails, Then and Now\n\nJails started as a limited virtualization system, but over the last two years they've..." - - assert content == - "

Support this and our other Michigan!/usr/group videos and meetings. Learn more at http://mug.org/membership

Twenty Years in Jail: FreeBSD’s Jails, Then and Now

Jails started as a limited virtualization system, but over the last two years they’ve…

" - - assert object.data["mediaType"] == "text/html" - end - - test "it remaps video URLs as attachments if necessary" do - {:ok, object} = - Fetcher.fetch_object_from_id( - "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3" - ) - - assert object.data["url"] == - "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3" - - assert object.data["attachment"] == [ - %{ - "type" => "Link", - "mediaType" => "video/mp4", - "url" => [ - %{ - "href" => - "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4", - "mediaType" => "video/mp4", - "type" => "Link" - } - ] - } - ] - - {:ok, object} = - Fetcher.fetch_object_from_id( - "https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206" - ) - - assert object.data["attachment"] == [ - %{ - "type" => "Link", - "mediaType" => "video/mp4", - "url" => [ - %{ - "href" => - "https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4", - "mediaType" => "video/mp4", - "type" => "Link" - } - ] - } - ] - - assert object.data["url"] == - "https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206" - end - test "it accepts Flag activities" do user = insert(:user) other_user = insert(:user) @@ -1072,7 +985,7 @@ test "returns not modified object when hasn't containts inReplyTo field", %{data assert Transmogrifier.fix_in_reply_to(data) == data end - test "returns object with inReplyToAtomUri when denied incoming reply", %{data: data} do + test "returns object with inReplyTo when denied incoming reply", %{data: data} do Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0) object_with_reply = @@ -1080,26 +993,22 @@ test "returns object with inReplyToAtomUri when denied incoming reply", %{data: modified_object = Transmogrifier.fix_in_reply_to(object_with_reply) assert modified_object["inReplyTo"] == "https://shitposter.club/notice/2827873" - assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873" object_with_reply = Map.put(data["object"], "inReplyTo", %{"id" => "https://shitposter.club/notice/2827873"}) modified_object = Transmogrifier.fix_in_reply_to(object_with_reply) assert modified_object["inReplyTo"] == %{"id" => "https://shitposter.club/notice/2827873"} - assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873" object_with_reply = Map.put(data["object"], "inReplyTo", ["https://shitposter.club/notice/2827873"]) modified_object = Transmogrifier.fix_in_reply_to(object_with_reply) assert modified_object["inReplyTo"] == ["https://shitposter.club/notice/2827873"] - assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873" object_with_reply = Map.put(data["object"], "inReplyTo", []) modified_object = Transmogrifier.fix_in_reply_to(object_with_reply) assert modified_object["inReplyTo"] == [] - assert modified_object["inReplyToAtomUri"] == "" end @tag capture_log: true @@ -1108,19 +1017,17 @@ test "returns modified object when allowed incoming reply", %{data: data} do Map.put( data["object"], "inReplyTo", - "https://shitposter.club/notice/2827873" + "https://mstdn.io/users/mayuutann/statuses/99568293732299394" ) Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 5) modified_object = Transmogrifier.fix_in_reply_to(object_with_reply) assert modified_object["inReplyTo"] == - "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment" - - assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873" + "https://mstdn.io/users/mayuutann/statuses/99568293732299394" assert modified_object["context"] == - "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26" + "tag:shitposter.club,2018-02-22:objectType=thread:nonce=e5a7c72d60a9c0e4" end end @@ -1139,75 +1046,7 @@ test "fixes data for object when url is map" do } end - test "fixes data for video object" do - object = %{ - "type" => "Video", - "url" => [ - %{ - "type" => "Link", - "mimeType" => "video/mp4", - "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4" - }, - %{ - "type" => "Link", - "mimeType" => "video/mp4", - "href" => "https://peertube46fb-ad81-2d4c2d1630e3-240.mp4" - }, - %{ - "type" => "Link", - "mimeType" => "text/html", - "href" => "https://peertube.-2d4c2d1630e3" - }, - %{ - "type" => "Link", - "mimeType" => "text/html", - "href" => "https://peertube.-2d4c2d16377-42" - } - ] - } - - assert Transmogrifier.fix_url(object) == %{ - "attachment" => [ - %{ - "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4", - "mimeType" => "video/mp4", - "type" => "Link" - } - ], - "type" => "Video", - "url" => "https://peertube.-2d4c2d1630e3" - } - end - - test "fixes url for not Video object" do - object = %{ - "type" => "Text", - "url" => [ - %{ - "type" => "Link", - "mimeType" => "text/html", - "href" => "https://peertube.-2d4c2d1630e3" - }, - %{ - "type" => "Link", - "mimeType" => "text/html", - "href" => "https://peertube.-2d4c2d16377-42" - } - ] - } - - assert Transmogrifier.fix_url(object) == %{ - "type" => "Text", - "url" => "https://peertube.-2d4c2d1630e3" - } - - assert Transmogrifier.fix_url(%{"type" => "Text", "url" => []}) == %{ - "type" => "Text", - "url" => "" - } - end - - test "retunrs not modified object" do + test "returns non-modified object" do assert Transmogrifier.fix_url(%{"type" => "Text"}) == %{"type" => "Text"} end end @@ -1222,7 +1061,9 @@ test "returns nil when cannot normalize object" do @tag capture_log: true test "returns {:ok, %Object{}} for success case" do assert {:ok, %Object{}} = - Transmogrifier.get_obj_helper("https://shitposter.club/notice/2827873") + Transmogrifier.get_obj_helper( + "https://mstdn.io/users/mayuutann/statuses/99568293732299394" + ) end end diff --git a/test/web/admin_api/controllers/admin_api_controller_test.exs b/test/web/admin_api/controllers/admin_api_controller_test.exs index f1e9b8938..cba6b43d3 100644 --- a/test/web/admin_api/controllers/admin_api_controller_test.exs +++ b/test/web/admin_api/controllers/admin_api_controller_test.exs @@ -203,7 +203,7 @@ test "single user", %{admin: admin, conn: conn} do assert user.note_count == 0 assert user.follower_count == 0 assert user.following_count == 0 - assert user.bio == nil + assert user.bio == "" assert user.name == nil assert called(Pleroma.Web.Federator.publish(:_)) @@ -1510,6 +1510,56 @@ test "excludes reblogs by default", %{conn: conn, user: user} do end end + describe "GET /api/pleroma/admin/users/:nickname/chats" do + setup do + user = insert(:user) + recipients = insert_list(3, :user) + + Enum.each(recipients, fn recipient -> + CommonAPI.post_chat_message(user, recipient, "yo") + end) + + %{user: user} + end + + test "renders user's chats", %{conn: conn, user: user} do + conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/chats") + + assert json_response(conn, 200) |> length() == 3 + end + end + + describe "GET /api/pleroma/admin/users/:nickname/chats unauthorized" do + setup do + user = insert(:user) + recipient = insert(:user) + CommonAPI.post_chat_message(user, recipient, "yo") + %{conn: conn} = oauth_access(["read:chats"]) + %{conn: conn, user: user} + end + + test "returns 403", %{conn: conn, user: user} do + conn + |> get("/api/pleroma/admin/users/#{user.nickname}/chats") + |> json_response(403) + end + end + + describe "GET /api/pleroma/admin/users/:nickname/chats unauthenticated" do + setup do + user = insert(:user) + recipient = insert(:user) + CommonAPI.post_chat_message(user, recipient, "yo") + %{conn: build_conn(), user: user} + end + + test "returns 403", %{conn: conn, user: user} do + conn + |> get("/api/pleroma/admin/users/#{user.nickname}/chats") + |> json_response(403) + end + end + describe "GET /api/pleroma/admin/moderation_log" do setup do moderator = insert(:user, is_moderator: true) diff --git a/test/web/admin_api/controllers/chat_controller_test.exs b/test/web/admin_api/controllers/chat_controller_test.exs new file mode 100644 index 000000000..bd4c9c9d1 --- /dev/null +++ b/test/web/admin_api/controllers/chat_controller_test.exs @@ -0,0 +1,219 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.ChatControllerTest do + use Pleroma.Web.ConnCase + + import Pleroma.Factory + + alias Pleroma.Chat + alias Pleroma.Chat.MessageReference + alias Pleroma.Config + alias Pleroma.ModerationLog + alias Pleroma.Object + alias Pleroma.Repo + alias Pleroma.Web.CommonAPI + + defp admin_setup do + admin = insert(:user, is_admin: true) + token = insert(:oauth_admin_token, user: admin) + + conn = + build_conn() + |> assign(:user, admin) + |> assign(:token, token) + + {:ok, %{admin: admin, token: token, conn: conn}} + end + + describe "DELETE /api/pleroma/admin/chats/:id/messages/:message_id" do + setup do: admin_setup() + + test "it deletes a message from the chat", %{conn: conn, admin: admin} do + user = insert(:user) + recipient = insert(:user) + + {:ok, message} = + CommonAPI.post_chat_message(user, recipient, "Hello darkness my old friend") + + object = Object.normalize(message, false) + + chat = Chat.get(user.id, recipient.ap_id) + recipient_chat = Chat.get(recipient.id, user.ap_id) + + cm_ref = MessageReference.for_chat_and_object(chat, object) + recipient_cm_ref = MessageReference.for_chat_and_object(recipient_chat, object) + + result = + conn + |> put_req_header("content-type", "application/json") + |> delete("/api/pleroma/admin/chats/#{chat.id}/messages/#{cm_ref.id}") + |> json_response_and_validate_schema(200) + + log_entry = Repo.one(ModerationLog) + + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} deleted chat message ##{cm_ref.id}" + + assert result["id"] == cm_ref.id + refute MessageReference.get_by_id(cm_ref.id) + refute MessageReference.get_by_id(recipient_cm_ref.id) + assert %{data: %{"type" => "Tombstone"}} = Object.get_by_id(object.id) + end + end + + describe "GET /api/pleroma/admin/chats/:id/messages" do + setup do: admin_setup() + + test "it paginates", %{conn: conn} do + user = insert(:user) + recipient = insert(:user) + + Enum.each(1..30, fn _ -> + {:ok, _} = CommonAPI.post_chat_message(user, recipient, "hey") + end) + + chat = Chat.get(user.id, recipient.ap_id) + + result = + conn + |> get("/api/pleroma/admin/chats/#{chat.id}/messages") + |> json_response_and_validate_schema(200) + + assert length(result) == 20 + + result = + conn + |> get("/api/pleroma/admin/chats/#{chat.id}/messages?max_id=#{List.last(result)["id"]}") + |> json_response_and_validate_schema(200) + + assert length(result) == 10 + end + + test "it returns the messages for a given chat", %{conn: conn} do + user = insert(:user) + other_user = insert(:user) + third_user = insert(:user) + + {:ok, _} = CommonAPI.post_chat_message(user, other_user, "hey") + {:ok, _} = CommonAPI.post_chat_message(user, third_user, "hey") + {:ok, _} = CommonAPI.post_chat_message(user, other_user, "how are you?") + {:ok, _} = CommonAPI.post_chat_message(other_user, user, "fine, how about you?") + + chat = Chat.get(user.id, other_user.ap_id) + + result = + conn + |> get("/api/pleroma/admin/chats/#{chat.id}/messages") + |> json_response_and_validate_schema(200) + + result + |> Enum.each(fn message -> + assert message["chat_id"] == chat.id |> to_string() + end) + + assert length(result) == 3 + end + end + + describe "GET /api/pleroma/admin/chats/:id" do + setup do: admin_setup() + + test "it returns a chat", %{conn: conn} do + user = insert(:user) + other_user = insert(:user) + + {:ok, chat} = Chat.get_or_create(user.id, other_user.ap_id) + + result = + conn + |> get("/api/pleroma/admin/chats/#{chat.id}") + |> json_response_and_validate_schema(200) + + assert result["id"] == to_string(chat.id) + assert %{} = result["sender"] + assert %{} = result["receiver"] + refute result["account"] + end + end + + describe "unauthorized chat moderation" do + setup do + user = insert(:user) + recipient = insert(:user) + + {:ok, message} = CommonAPI.post_chat_message(user, recipient, "Yo") + object = Object.normalize(message, false) + chat = Chat.get(user.id, recipient.ap_id) + cm_ref = MessageReference.for_chat_and_object(chat, object) + + %{conn: conn} = oauth_access(["read:chats", "write:chats"]) + %{conn: conn, chat: chat, cm_ref: cm_ref} + end + + test "DELETE /api/pleroma/admin/chats/:id/messages/:message_id", %{ + conn: conn, + chat: chat, + cm_ref: cm_ref + } do + conn + |> put_req_header("content-type", "application/json") + |> delete("/api/pleroma/admin/chats/#{chat.id}/messages/#{cm_ref.id}") + |> json_response(403) + + assert MessageReference.get_by_id(cm_ref.id) == cm_ref + end + + test "GET /api/pleroma/admin/chats/:id/messages", %{conn: conn, chat: chat} do + conn + |> get("/api/pleroma/admin/chats/#{chat.id}/messages") + |> json_response(403) + end + + test "GET /api/pleroma/admin/chats/:id", %{conn: conn, chat: chat} do + conn + |> get("/api/pleroma/admin/chats/#{chat.id}") + |> json_response(403) + end + end + + describe "unauthenticated chat moderation" do + setup do + user = insert(:user) + recipient = insert(:user) + + {:ok, message} = CommonAPI.post_chat_message(user, recipient, "Yo") + object = Object.normalize(message, false) + chat = Chat.get(user.id, recipient.ap_id) + cm_ref = MessageReference.for_chat_and_object(chat, object) + + %{conn: build_conn(), chat: chat, cm_ref: cm_ref} + end + + test "DELETE /api/pleroma/admin/chats/:id/messages/:message_id", %{ + conn: conn, + chat: chat, + cm_ref: cm_ref + } do + conn + |> put_req_header("content-type", "application/json") + |> delete("/api/pleroma/admin/chats/#{chat.id}/messages/#{cm_ref.id}") + |> json_response(403) + + assert MessageReference.get_by_id(cm_ref.id) == cm_ref + end + + test "GET /api/pleroma/admin/chats/:id/messages", %{conn: conn, chat: chat} do + conn + |> get("/api/pleroma/admin/chats/#{chat.id}/messages") + |> json_response(403) + end + + test "GET /api/pleroma/admin/chats/:id", %{conn: conn, chat: chat} do + conn + |> get("/api/pleroma/admin/chats/#{chat.id}") + |> json_response(403) + end + end +end diff --git a/test/web/admin_api/controllers/instance_document_controller_test.exs b/test/web/admin_api/controllers/instance_document_controller_test.exs new file mode 100644 index 000000000..5f7b042f6 --- /dev/null +++ b/test/web/admin_api/controllers/instance_document_controller_test.exs @@ -0,0 +1,106 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.InstanceDocumentControllerTest do + use Pleroma.Web.ConnCase, async: true + import Pleroma.Factory + alias Pleroma.Config + + @dir "test/tmp/instance_static" + @default_instance_panel ~s(

Welcome to Pleroma!

) + + setup do + File.mkdir_p!(@dir) + on_exit(fn -> File.rm_rf(@dir) end) + end + + setup do: clear_config([:instance, :static_dir], @dir) + + setup do + admin = insert(:user, is_admin: true) + token = insert(:oauth_admin_token, user: admin) + + conn = + build_conn() + |> assign(:user, admin) + |> assign(:token, token) + + {:ok, %{admin: admin, token: token, conn: conn}} + end + + describe "GET /api/pleroma/admin/instance_document/:name" do + test "return the instance document url", %{conn: conn} do + conn = get(conn, "/api/pleroma/admin/instance_document/instance-panel") + + assert content = html_response(conn, 200) + assert String.contains?(content, @default_instance_panel) + end + + test "it returns 403 if requested by a non-admin" do + non_admin_user = insert(:user) + token = insert(:oauth_token, user: non_admin_user) + + conn = + build_conn() + |> assign(:user, non_admin_user) + |> assign(:token, token) + |> get("/api/pleroma/admin/instance_document/instance-panel") + + assert json_response(conn, :forbidden) + end + + test "it returns 404 if the instance document with the given name doesn't exist", %{ + conn: conn + } do + conn = get(conn, "/api/pleroma/admin/instance_document/1234") + + assert json_response_and_validate_schema(conn, 404) + end + end + + describe "PATCH /api/pleroma/admin/instance_document/:name" do + test "uploads the instance document", %{conn: conn} do + image = %Plug.Upload{ + content_type: "text/html", + path: Path.absname("test/fixtures/custom_instance_panel.html"), + filename: "custom_instance_panel.html" + } + + conn = + conn + |> put_req_header("content-type", "multipart/form-data") + |> patch("/api/pleroma/admin/instance_document/instance-panel", %{ + "file" => image + }) + + assert %{"url" => url} = json_response_and_validate_schema(conn, 200) + index = get(build_conn(), url) + assert html_response(index, 200) == "

Custom instance panel

" + end + end + + describe "DELETE /api/pleroma/admin/instance_document/:name" do + test "deletes the instance document", %{conn: conn} do + File.mkdir!(@dir <> "/instance/") + File.write!(@dir <> "/instance/panel.html", "Custom instance panel") + + conn_resp = + conn + |> get("/api/pleroma/admin/instance_document/instance-panel") + + assert html_response(conn_resp, 200) == "Custom instance panel" + + conn + |> delete("/api/pleroma/admin/instance_document/instance-panel") + |> json_response_and_validate_schema(200) + + conn_resp = + conn + |> get("/api/pleroma/admin/instance_document/instance-panel") + + assert content = html_response(conn_resp, 200) + assert String.contains?(content, @default_instance_panel) + end + end +end diff --git a/test/web/admin_api/search_test.exs b/test/web/admin_api/search_test.exs index b974cedd5..d88867c52 100644 --- a/test/web/admin_api/search_test.exs +++ b/test/web/admin_api/search_test.exs @@ -177,5 +177,14 @@ test "it returns unapproved user" do assert total == 3 assert count == 1 end + + test "it returns non-discoverable users" do + insert(:user) + insert(:user, discoverable: false) + + {:ok, _results, total} = Search.user() + + assert total == 2 + end end end diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index 28bb6db30..2eab64e8b 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -4,11 +4,14 @@ defmodule Pleroma.Web.CommonAPITest do use Pleroma.DataCase + use Oban.Testing, repo: Pleroma.Repo + alias Pleroma.Activity alias Pleroma.Chat alias Pleroma.Conversation.Participation alias Pleroma.Notification alias Pleroma.Object + alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Transmogrifier @@ -18,6 +21,7 @@ defmodule Pleroma.Web.CommonAPITest do import Pleroma.Factory import Mock + import Ecto.Query, only: [from: 2] require Pleroma.Constants @@ -607,15 +611,15 @@ test "it validates character limits are correctly enforced" do test "it can handle activities that expire" do user = insert(:user) - expires_at = - NaiveDateTime.utc_now() - |> NaiveDateTime.truncate(:second) - |> NaiveDateTime.add(1_000_000, :second) + expires_at = DateTime.add(DateTime.utc_now(), 1_000_000) assert {:ok, activity} = CommonAPI.post(user, %{status: "chai", expires_in: 1_000_000}) - assert expiration = Pleroma.ActivityExpiration.get_by_activity_id(activity.id) - assert expiration.scheduled_at == expires_at + assert_enqueued( + worker: Pleroma.Workers.PurgeExpiredActivity, + args: %{activity_id: activity.id}, + scheduled_at: expires_at + ) end end @@ -819,6 +823,69 @@ test "should unpin when deleting a status", %{user: user, activity: activity} do [user: user, activity: activity] end + test "marks notifications as read after mute" do + author = insert(:user) + activity = insert(:note_activity, user: author) + + friend1 = insert(:user) + friend2 = insert(:user) + + {:ok, reply_activity} = + CommonAPI.post( + friend2, + %{ + status: "@#{author.nickname} @#{friend1.nickname} test reply", + in_reply_to_status_id: activity.id + } + ) + + {:ok, favorite_activity} = CommonAPI.favorite(friend2, activity.id) + {:ok, repeat_activity} = CommonAPI.repeat(activity.id, friend1) + + assert Repo.aggregate( + from(n in Notification, where: n.seen == false and n.user_id == ^friend1.id), + :count + ) == 1 + + unread_notifications = + Repo.all(from(n in Notification, where: n.seen == false, where: n.user_id == ^author.id)) + + assert Enum.any?(unread_notifications, fn n -> + n.type == "favourite" && n.activity_id == favorite_activity.id + end) + + assert Enum.any?(unread_notifications, fn n -> + n.type == "reblog" && n.activity_id == repeat_activity.id + end) + + assert Enum.any?(unread_notifications, fn n -> + n.type == "mention" && n.activity_id == reply_activity.id + end) + + {:ok, _} = CommonAPI.add_mute(author, activity) + assert CommonAPI.thread_muted?(author, activity) + + assert Repo.aggregate( + from(n in Notification, where: n.seen == false and n.user_id == ^friend1.id), + :count + ) == 1 + + read_notifications = + Repo.all(from(n in Notification, where: n.seen == true, where: n.user_id == ^author.id)) + + assert Enum.any?(read_notifications, fn n -> + n.type == "favourite" && n.activity_id == favorite_activity.id + end) + + assert Enum.any?(read_notifications, fn n -> + n.type == "reblog" && n.activity_id == repeat_activity.id + end) + + assert Enum.any?(read_notifications, fn n -> + n.type == "mention" && n.activity_id == reply_activity.id + end) + end + test "add mute", %{user: user, activity: activity} do {:ok, _} = CommonAPI.add_mute(user, activity) assert CommonAPI.thread_muted?(user, activity) @@ -1137,4 +1204,24 @@ test "respects visibility=private" do assert Visibility.get_visibility(activity) == "private" end end + + describe "get_user/1" do + test "gets user by ap_id" do + user = insert(:user) + assert CommonAPI.get_user(user.ap_id) == user + end + + test "gets user by guessed nickname" do + user = insert(:user, ap_id: "", nickname: "mario@mushroom.kingdom") + assert CommonAPI.get_user("https://mushroom.kingdom/users/mario") == user + end + + test "fallback" do + assert %User{ + name: "", + ap_id: "", + nickname: "erroruser@example.com" + } = CommonAPI.get_user("") + end + end end diff --git a/test/web/fed_sockets/fed_registry_test.exs b/test/web/fed_sockets/fed_registry_test.exs new file mode 100644 index 000000000..19ac874d6 --- /dev/null +++ b/test/web/fed_sockets/fed_registry_test.exs @@ -0,0 +1,124 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.FedSockets.FedRegistryTest do + use ExUnit.Case + + alias Pleroma.Web.FedSockets + alias Pleroma.Web.FedSockets.FedRegistry + alias Pleroma.Web.FedSockets.SocketInfo + + @good_domain "http://good.domain" + @good_domain_origin "good.domain:80" + + setup do + start_supervised({Pleroma.Web.FedSockets.Supervisor, []}) + build_test_socket(@good_domain) + Process.sleep(10) + + :ok + end + + describe "add_fed_socket/1 without conflicting sockets" do + test "can be added" do + Process.sleep(10) + assert {:ok, %SocketInfo{origin: origin}} = FedRegistry.get_fed_socket(@good_domain_origin) + assert origin == "good.domain:80" + end + + test "multiple origins can be added" do + build_test_socket("http://anothergood.domain") + Process.sleep(10) + + assert {:ok, %SocketInfo{origin: origin_1}} = + FedRegistry.get_fed_socket(@good_domain_origin) + + assert {:ok, %SocketInfo{origin: origin_2}} = + FedRegistry.get_fed_socket("anothergood.domain:80") + + assert origin_1 == "good.domain:80" + assert origin_2 == "anothergood.domain:80" + assert FedRegistry.list_all() |> Enum.count() == 2 + end + end + + describe "add_fed_socket/1 when duplicate sockets conflict" do + setup do + build_test_socket(@good_domain) + build_test_socket(@good_domain) + Process.sleep(10) + :ok + end + + test "will be ignored" do + assert {:ok, %SocketInfo{origin: origin, pid: pid_one}} = + FedRegistry.get_fed_socket(@good_domain_origin) + + assert origin == "good.domain:80" + + assert FedRegistry.list_all() |> Enum.count() == 1 + end + + test "the newer process will be closed" do + pid_two = build_test_socket(@good_domain) + + assert {:ok, %SocketInfo{origin: origin, pid: pid_one}} = + FedRegistry.get_fed_socket(@good_domain_origin) + + assert origin == "good.domain:80" + Process.sleep(10) + + refute Process.alive?(pid_two) + + assert FedRegistry.list_all() |> Enum.count() == 1 + end + end + + describe "get_fed_socket/1" do + test "returns missing for unknown hosts" do + assert {:error, :missing} = FedRegistry.get_fed_socket("not_a_dmoain") + end + + test "returns rejected for hosts previously rejected" do + "rejected.domain:80" + |> FedSockets.uri_for_origin() + |> FedRegistry.set_host_rejected() + + assert {:error, :rejected} = FedRegistry.get_fed_socket("rejected.domain:80") + end + + test "can retrieve a previously added SocketInfo" do + build_test_socket(@good_domain) + Process.sleep(10) + assert {:ok, %SocketInfo{origin: origin}} = FedRegistry.get_fed_socket(@good_domain_origin) + assert origin == "good.domain:80" + end + + test "removes references to SocketInfos when the process crashes" do + assert {:ok, %SocketInfo{origin: origin, pid: pid}} = + FedRegistry.get_fed_socket(@good_domain_origin) + + assert origin == "good.domain:80" + + Process.exit(pid, :testing) + Process.sleep(100) + assert {:error, :missing} = FedRegistry.get_fed_socket(@good_domain_origin) + end + end + + def build_test_socket(uri) do + Kernel.spawn(fn -> fed_socket_almost(uri) end) + end + + def fed_socket_almost(origin) do + FedRegistry.add_fed_socket(origin) + + receive do + :close -> + :ok + after + 5_000 -> :timeout + end + end +end diff --git a/test/web/fed_sockets/fetch_registry_test.exs b/test/web/fed_sockets/fetch_registry_test.exs new file mode 100644 index 000000000..7bd2d995a --- /dev/null +++ b/test/web/fed_sockets/fetch_registry_test.exs @@ -0,0 +1,67 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.FedSockets.FetchRegistryTest do + use ExUnit.Case + + alias Pleroma.Web.FedSockets.FetchRegistry + alias Pleroma.Web.FedSockets.FetchRegistry.FetchRegistryData + + @json_message "hello" + @json_reply "hello back" + + setup do + start_supervised( + {Pleroma.Web.FedSockets.Supervisor, + [ + ping_interval: 8, + connection_duration: 15, + rejection_duration: 5, + fed_socket_fetches: [default: 10, interval: 10] + ]} + ) + + :ok + end + + test "fetches can be stored" do + uuid = FetchRegistry.register_fetch(@json_message) + + assert {:error, :waiting} = FetchRegistry.check_fetch(uuid) + end + + test "fetches can return" do + uuid = FetchRegistry.register_fetch(@json_message) + task = Task.async(fn -> FetchRegistry.register_fetch_received(uuid, @json_reply) end) + + assert {:error, :waiting} = FetchRegistry.check_fetch(uuid) + Task.await(task) + + assert {:ok, %FetchRegistryData{received_json: received_json}} = + FetchRegistry.check_fetch(uuid) + + assert received_json == @json_reply + end + + test "fetches are deleted once popped from stack" do + uuid = FetchRegistry.register_fetch(@json_message) + task = Task.async(fn -> FetchRegistry.register_fetch_received(uuid, @json_reply) end) + Task.await(task) + + assert {:ok, %FetchRegistryData{received_json: received_json}} = + FetchRegistry.check_fetch(uuid) + + assert received_json == @json_reply + assert {:ok, @json_reply} = FetchRegistry.pop_fetch(uuid) + + assert {:error, :missing} = FetchRegistry.check_fetch(uuid) + end + + test "fetches can time out" do + uuid = FetchRegistry.register_fetch(@json_message) + assert {:error, :waiting} = FetchRegistry.check_fetch(uuid) + Process.sleep(500) + assert {:error, :missing} = FetchRegistry.check_fetch(uuid) + end +end diff --git a/test/web/fed_sockets/socket_info_test.exs b/test/web/fed_sockets/socket_info_test.exs new file mode 100644 index 000000000..db3d6edcd --- /dev/null +++ b/test/web/fed_sockets/socket_info_test.exs @@ -0,0 +1,118 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.FedSockets.SocketInfoTest do + use ExUnit.Case + + alias Pleroma.Web.FedSockets + alias Pleroma.Web.FedSockets.SocketInfo + + describe "uri_for_origin" do + test "provides the fed_socket URL given the origin information" do + endpoint = "example.com:4000" + assert FedSockets.uri_for_origin(endpoint) =~ "ws://" + assert FedSockets.uri_for_origin(endpoint) =~ endpoint + end + end + + describe "origin" do + test "will provide the origin field given a url" do + endpoint = "example.com:4000" + assert SocketInfo.origin("ws://#{endpoint}") == endpoint + assert SocketInfo.origin("http://#{endpoint}") == endpoint + assert SocketInfo.origin("https://#{endpoint}") == endpoint + end + + test "will proide the origin field given a uri" do + endpoint = "example.com:4000" + uri = URI.parse("http://#{endpoint}") + + assert SocketInfo.origin(uri) == endpoint + end + end + + describe "touch" do + test "will update the TTL" do + endpoint = "example.com:4000" + socket = SocketInfo.build("ws://#{endpoint}") + Process.sleep(2) + touched_socket = SocketInfo.touch(socket) + + assert socket.connected_until < touched_socket.connected_until + end + end + + describe "expired?" do + setup do + start_supervised( + {Pleroma.Web.FedSockets.Supervisor, + [ + ping_interval: 8, + connection_duration: 5, + rejection_duration: 5, + fed_socket_rejections: [lazy: true] + ]} + ) + + :ok + end + + test "tests if the TTL is exceeded" do + endpoint = "example.com:4000" + socket = SocketInfo.build("ws://#{endpoint}") + refute SocketInfo.expired?(socket) + Process.sleep(10) + + assert SocketInfo.expired?(socket) + end + end + + describe "creating outgoing connection records" do + test "can be passed a string" do + assert %{conn_pid: :pid, origin: _origin} = SocketInfo.build("example.com:4000", :pid) + end + + test "can be passed a URI" do + uri = URI.parse("http://example.com:4000") + assert %{conn_pid: :pid, origin: origin} = SocketInfo.build(uri, :pid) + assert origin =~ "example.com:4000" + end + + test "will include the port number" do + assert %{conn_pid: :pid, origin: origin} = SocketInfo.build("http://example.com:4000", :pid) + + assert origin =~ ":4000" + end + + test "will provide the port if missing" do + assert %{conn_pid: :pid, origin: "example.com:80"} = + SocketInfo.build("http://example.com", :pid) + + assert %{conn_pid: :pid, origin: "example.com:443"} = + SocketInfo.build("https://example.com", :pid) + end + end + + describe "creating incoming connection records" do + test "can be passed a string" do + assert %{pid: _, origin: _origin} = SocketInfo.build("example.com:4000") + end + + test "can be passed a URI" do + uri = URI.parse("example.com:4000") + assert %{pid: _, origin: _origin} = SocketInfo.build(uri) + end + + test "will include the port number" do + assert %{pid: _, origin: origin} = SocketInfo.build("http://example.com:4000") + + assert origin =~ ":4000" + end + + test "will provide the port if missing" do + assert %{pid: _, origin: "example.com:80"} = SocketInfo.build("http://example.com") + assert %{pid: _, origin: "example.com:443"} = SocketInfo.build("https://example.com") + end + end +end diff --git a/test/web/instances/instance_test.exs b/test/web/instances/instance_test.exs index 5d4efcebe..4f0805100 100644 --- a/test/web/instances/instance_test.exs +++ b/test/web/instances/instance_test.exs @@ -99,37 +99,54 @@ test "does NOT modify `unreachable_since` value of existing record in case it's end end - test "Scrapes favicon URLs" do - Tesla.Mock.mock(fn %{url: "https://favicon.example.org/"} -> - %Tesla.Env{ - status: 200, - body: ~s[] - } - end) + describe "get_or_update_favicon/1" do + test "Scrapes favicon URLs" do + Tesla.Mock.mock(fn %{url: "https://favicon.example.org/"} -> + %Tesla.Env{ + status: 200, + body: ~s[] + } + end) - assert "https://favicon.example.org/favicon.png" == - Instance.get_or_update_favicon(URI.parse("https://favicon.example.org/")) - end + assert "https://favicon.example.org/favicon.png" == + Instance.get_or_update_favicon(URI.parse("https://favicon.example.org/")) + end - test "Returns nil on too long favicon URLs" do - clear_config([:instances_favicons, :enabled], true) + test "Returns nil on too long favicon URLs" do + long_favicon_url = + "https://Lorem.ipsum.dolor.sit.amet/consecteturadipiscingelit/Praesentpharetrapurusutaliquamtempus/Mauriseulaoreetarcu/atfacilisisorci/Nullamporttitor/nequesedfeugiatmollis/dolormagnaefficiturlorem/nonpretiumsapienorcieurisus/Nullamveleratsem/Maecenassedaccumsanexnam/favicon.png" - long_favicon_url = - "https://Lorem.ipsum.dolor.sit.amet/consecteturadipiscingelit/Praesentpharetrapurusutaliquamtempus/Mauriseulaoreetarcu/atfacilisisorci/Nullamporttitor/nequesedfeugiatmollis/dolormagnaefficiturlorem/nonpretiumsapienorcieurisus/Nullamveleratsem/Maecenassedaccumsanexnam/favicon.png" + Tesla.Mock.mock(fn %{url: "https://long-favicon.example.org/"} -> + %Tesla.Env{ + status: 200, + body: + ~s[] + } + end) - Tesla.Mock.mock(fn %{url: "https://long-favicon.example.org/"} -> - %Tesla.Env{ - status: 200, - body: ~s[] - } - end) + assert capture_log(fn -> + assert nil == + Instance.get_or_update_favicon( + URI.parse("https://long-favicon.example.org/") + ) + end) =~ + "Instance.get_or_update_favicon(\"long-favicon.example.org\") error: %Postgrex.Error{" + end - assert capture_log(fn -> - assert nil == - Instance.get_or_update_favicon( - URI.parse("https://long-favicon.example.org/") - ) - end) =~ - "Instance.get_or_update_favicon(\"long-favicon.example.org\") error: %Postgrex.Error{" + test "Handles not getting a favicon URL properly" do + Tesla.Mock.mock(fn %{url: "https://no-favicon.example.org/"} -> + %Tesla.Env{ + status: 200, + body: ~s[

I wil look down and whisper "GNO.."

] + } + end) + + refute capture_log(fn -> + assert nil == + Instance.get_or_update_favicon( + URI.parse("https://no-favicon.example.org/") + ) + end) =~ "Instance.scrape_favicon(\"https://no-favicon.example.org/\") error: " + end end end diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs index 17a1e7d66..f7f1369e4 100644 --- a/test/web/mastodon_api/controllers/account_controller_test.exs +++ b/test/web/mastodon_api/controllers/account_controller_test.exs @@ -1442,7 +1442,10 @@ test "returns lists to which the account belongs" do describe "verify_credentials" do test "verify_credentials" do %{user: user, conn: conn} = oauth_access(["read:accounts"]) - [notification | _] = insert_list(7, :notification, user: user) + + [notification | _] = + insert_list(7, :notification, user: user, activity: insert(:note_activity)) + Pleroma.Notification.set_read_up_to(user, notification.id) conn = get(conn, "/api/v1/accounts/verify_credentials") diff --git a/test/web/mastodon_api/controllers/list_controller_test.exs b/test/web/mastodon_api/controllers/list_controller_test.exs index 57a9ef4a4..091ec006c 100644 --- a/test/web/mastodon_api/controllers/list_controller_test.exs +++ b/test/web/mastodon_api/controllers/list_controller_test.exs @@ -67,7 +67,7 @@ test "adding users to a list" do assert following == [other_user.follower_address] end - test "removing users from a list" do + test "removing users from a list, body params" do %{user: user, conn: conn} = oauth_access(["write:lists"]) other_user = insert(:user) third_user = insert(:user) @@ -85,6 +85,24 @@ test "removing users from a list" do assert following == [third_user.follower_address] end + test "removing users from a list, query params" do + %{user: user, conn: conn} = oauth_access(["write:lists"]) + other_user = insert(:user) + third_user = insert(:user) + {:ok, list} = Pleroma.List.create("name", user) + {:ok, list} = Pleroma.List.follow(list, other_user) + {:ok, list} = Pleroma.List.follow(list, third_user) + + assert %{} == + conn + |> put_req_header("content-type", "application/json") + |> delete("/api/v1/lists/#{list.id}/accounts?account_ids[]=#{other_user.id}") + |> json_response_and_validate_schema(:ok) + + %Pleroma.List{following: following} = Pleroma.List.get(list.id, user) + assert following == [third_user.follower_address] + end + test "listing users in a list" do %{user: user, conn: conn} = oauth_access(["read:lists"]) other_user = insert(:user) diff --git a/test/web/mastodon_api/controllers/marker_controller_test.exs b/test/web/mastodon_api/controllers/marker_controller_test.exs index 6dd40fb4a..9f0481120 100644 --- a/test/web/mastodon_api/controllers/marker_controller_test.exs +++ b/test/web/mastodon_api/controllers/marker_controller_test.exs @@ -11,7 +11,7 @@ defmodule Pleroma.Web.MastodonAPI.MarkerControllerTest do test "gets markers with correct scopes", %{conn: conn} do user = insert(:user) token = insert(:oauth_token, user: user, scopes: ["read:statuses"]) - insert_list(7, :notification, user: user) + insert_list(7, :notification, user: user, activity: insert(:note_activity)) {:ok, %{"notifications" => marker}} = Pleroma.Marker.upsert( diff --git a/test/web/mastodon_api/controllers/search_controller_test.exs b/test/web/mastodon_api/controllers/search_controller_test.exs index 24d1959f8..04dc6f445 100644 --- a/test/web/mastodon_api/controllers/search_controller_test.exs +++ b/test/web/mastodon_api/controllers/search_controller_test.exs @@ -282,18 +282,18 @@ test "search fetches remote statuses and prefers them over other results", %{con capture_log(fn -> {:ok, %{id: activity_id}} = CommonAPI.post(insert(:user), %{ - status: "check out https://shitposter.club/notice/2827873" + status: "check out http://mastodon.example.org/@admin/99541947525187367" }) results = conn - |> get("/api/v1/search?q=https://shitposter.club/notice/2827873") + |> get("/api/v1/search?q=http://mastodon.example.org/@admin/99541947525187367") |> json_response_and_validate_schema(200) - [status, %{"id" => ^activity_id}] = results["statuses"] - - assert status["uri"] == - "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment" + assert [ + %{"url" => "http://mastodon.example.org/@admin/99541947525187367"}, + %{"id" => ^activity_id} + ] = results["statuses"] end) end diff --git a/test/web/mastodon_api/controllers/status_controller_test.exs b/test/web/mastodon_api/controllers/status_controller_test.exs index f221884e7..633a25e50 100644 --- a/test/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/web/mastodon_api/controllers/status_controller_test.exs @@ -4,9 +4,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do use Pleroma.Web.ConnCase + use Oban.Testing, repo: Pleroma.Repo alias Pleroma.Activity - alias Pleroma.ActivityExpiration alias Pleroma.Config alias Pleroma.Conversation.Participation alias Pleroma.Object @@ -29,8 +29,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do setup do: oauth_access(["write:statuses"]) test "posting a status does not increment reblog_count when relaying", %{conn: conn} do - Pleroma.Config.put([:instance, :federating], true) - Pleroma.Config.get([:instance, :allow_relay], true) + Config.put([:instance, :federating], true) + Config.get([:instance, :allow_relay], true) response = conn @@ -103,7 +103,9 @@ test "posting a status", %{conn: conn} do # An activity that will expire: # 2 hours - expires_in = 120 * 60 + expires_in = 2 * 60 * 60 + + expires_at = DateTime.add(DateTime.utc_now(), expires_in) conn_four = conn @@ -113,29 +115,22 @@ test "posting a status", %{conn: conn} do "expires_in" => expires_in }) - assert fourth_response = - %{"id" => fourth_id} = json_response_and_validate_schema(conn_four, 200) + assert %{"id" => fourth_id} = json_response_and_validate_schema(conn_four, 200) - assert activity = Activity.get_by_id(fourth_id) - assert expiration = ActivityExpiration.get_by_activity_id(fourth_id) + assert Activity.get_by_id(fourth_id) - estimated_expires_at = - NaiveDateTime.utc_now() - |> NaiveDateTime.add(expires_in) - |> NaiveDateTime.truncate(:second) - - # This assert will fail if the test takes longer than a minute. I sure hope it never does: - assert abs(NaiveDateTime.diff(expiration.scheduled_at, estimated_expires_at, :second)) < 60 - - assert fourth_response["pleroma"]["expires_at"] == - NaiveDateTime.to_iso8601(expiration.scheduled_at) + assert_enqueued( + worker: Pleroma.Workers.PurgeExpiredActivity, + args: %{activity_id: fourth_id}, + scheduled_at: expires_at + ) end test "it fails to create a status if `expires_in` is less or equal than an hour", %{ conn: conn } do - # 1 hour - expires_in = 60 * 60 + # 1 minute + expires_in = 1 * 60 assert %{"error" => "Expiry date is too soon"} = conn @@ -146,8 +141,8 @@ test "it fails to create a status if `expires_in` is less or equal than an hour" }) |> json_response_and_validate_schema(422) - # 30 minutes - expires_in = 30 * 60 + # 5 minutes + expires_in = 5 * 60 assert %{"error" => "Expiry date is too soon"} = conn @@ -160,8 +155,8 @@ test "it fails to create a status if `expires_in` is less or equal than an hour" end test "Get MRF reason when posting a status is rejected by one", %{conn: conn} do - Pleroma.Config.put([:mrf_keyword, :reject], ["GNO"]) - Pleroma.Config.put([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy]) + Config.put([:mrf_keyword, :reject], ["GNO"]) + Config.put([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy]) assert %{"error" => "[KeywordPolicy] Matches with rejected keyword"} = conn @@ -1146,6 +1141,52 @@ test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do |> post("/api/v1/statuses/#{activity_two.id}/pin") |> json_response_and_validate_schema(400) end + + test "on pin removes deletion job, on unpin reschedule deletion" do + %{conn: conn} = oauth_access(["write:accounts", "write:statuses"]) + expires_in = 2 * 60 * 60 + + expires_at = DateTime.add(DateTime.utc_now(), expires_in) + + assert %{"id" => id} = + conn + |> put_req_header("content-type", "application/json") + |> post("api/v1/statuses", %{ + "status" => "oolong", + "expires_in" => expires_in + }) + |> json_response_and_validate_schema(200) + + assert_enqueued( + worker: Pleroma.Workers.PurgeExpiredActivity, + args: %{activity_id: id}, + scheduled_at: expires_at + ) + + assert %{"id" => ^id, "pinned" => true} = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/statuses/#{id}/pin") + |> json_response_and_validate_schema(200) + + refute_enqueued( + worker: Pleroma.Workers.PurgeExpiredActivity, + args: %{activity_id: id}, + scheduled_at: expires_at + ) + + assert %{"id" => ^id, "pinned" => false} = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/statuses/#{id}/unpin") + |> json_response_and_validate_schema(200) + + assert_enqueued( + worker: Pleroma.Workers.PurgeExpiredActivity, + args: %{activity_id: id}, + scheduled_at: expires_at + ) + end end describe "cards" do @@ -1681,19 +1722,17 @@ test "returns the favorites of a user" do test "expires_at is nil for another user" do %{conn: conn, user: user} = oauth_access(["read:statuses"]) + expires_at = DateTime.add(DateTime.utc_now(), 1_000_000) {:ok, activity} = CommonAPI.post(user, %{status: "foobar", expires_in: 1_000_000}) - expires_at = - activity.id - |> ActivityExpiration.get_by_activity_id() - |> Map.get(:scheduled_at) - |> NaiveDateTime.to_iso8601() - - assert %{"pleroma" => %{"expires_at" => ^expires_at}} = + assert %{"pleroma" => %{"expires_at" => a_expires_at}} = conn |> get("/api/v1/statuses/#{activity.id}") |> json_response_and_validate_schema(:ok) + {:ok, a_expires_at, 0} = DateTime.from_iso8601(a_expires_at) + assert DateTime.diff(expires_at, a_expires_at) == 0 + %{conn: conn} = oauth_access(["read:statuses"]) assert %{"pleroma" => %{"expires_at" => nil}} = diff --git a/test/web/mastodon_api/controllers/timeline_controller_test.exs b/test/web/mastodon_api/controllers/timeline_controller_test.exs index 517cabcff..c6e0268fd 100644 --- a/test/web/mastodon_api/controllers/timeline_controller_test.exs +++ b/test/web/mastodon_api/controllers/timeline_controller_test.exs @@ -114,8 +114,16 @@ test "doesn't return replies if follower is posting with blocked user" do {:ok, _reply_from_friend} = CommonAPI.post(friend, %{status: "status", in_reply_to_status_id: reply_from_blockee}) - res_conn = get(conn, "/api/v1/timelines/public") - [%{"id" => ^activity_id}] = json_response_and_validate_schema(res_conn, 200) + # Still shows replies from yourself + {:ok, %{id: reply_from_me}} = + CommonAPI.post(blocker, %{status: "status", in_reply_to_status_id: reply_from_blockee}) + + response = + get(conn, "/api/v1/timelines/public") + |> json_response_and_validate_schema(200) + + assert length(response) == 2 + [%{"id" => ^reply_from_me}, %{"id" => ^activity_id}] = response end test "doesn't return replies if follow is posting with users from blocked domain" do diff --git a/test/web/mastodon_api/views/account_view_test.exs b/test/web/mastodon_api/views/account_view_test.exs index 68a5d0091..a5f39b215 100644 --- a/test/web/mastodon_api/views/account_view_test.exs +++ b/test/web/mastodon_api/views/account_view_test.exs @@ -5,6 +5,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do use Pleroma.DataCase + alias Pleroma.Config alias Pleroma.User alias Pleroma.UserRelationship alias Pleroma.Web.CommonAPI @@ -68,7 +69,7 @@ test "Represent a user account" do sensitive: false, pleroma: %{ actor_type: "Person", - discoverable: false + discoverable: true }, fields: [] }, @@ -116,9 +117,6 @@ test "is nil when :instances_favicons is disabled", %{user: user} do end end - test "Favicon when :instance_favicons is enabled" do - end - test "Represent the user account for the account owner" do user = insert(:user) @@ -169,7 +167,7 @@ test "Represent a Service(bot) account" do sensitive: false, pleroma: %{ actor_type: "Service", - discoverable: false + discoverable: true }, fields: [] }, @@ -451,7 +449,7 @@ test "shows unread_conversation_count only to the account owner" do test "shows unread_count only to the account owner" do user = insert(:user) - insert_list(7, :notification, user: user) + insert_list(7, :notification, user: user, activity: insert(:note_activity)) other_user = insert(:user) user = User.get_cached_by_ap_id(user.ap_id) @@ -543,8 +541,9 @@ test "shows non-zero when historical unapproved requests are present" do end end - test "uses mediaproxy urls when it's enabled" do + test "uses mediaproxy urls when it's enabled (regardless of media preview proxy state)" do clear_config([:media_proxy, :enabled], true) + clear_config([:media_preview_proxy, :enabled]) user = insert(:user, @@ -553,20 +552,24 @@ test "uses mediaproxy urls when it's enabled" do emoji: %{"joker_smile" => "https://evil.website/society.png"} ) - AccountView.render("show.json", %{user: user, skip_visibility_check: true}) - |> Enum.all?(fn - {key, url} when key in [:avatar, :avatar_static, :header, :header_static] -> - String.starts_with?(url, Pleroma.Web.base_url()) + with media_preview_enabled <- [false, true] do + Config.put([:media_preview_proxy, :enabled], media_preview_enabled) - {:emojis, emojis} -> - Enum.all?(emojis, fn %{url: url, static_url: static_url} -> - String.starts_with?(url, Pleroma.Web.base_url()) && - String.starts_with?(static_url, Pleroma.Web.base_url()) - end) + AccountView.render("show.json", %{user: user, skip_visibility_check: true}) + |> Enum.all?(fn + {key, url} when key in [:avatar, :avatar_static, :header, :header_static] -> + String.starts_with?(url, Pleroma.Web.base_url()) - _ -> - true - end) - |> assert() + {:emojis, emojis} -> + Enum.all?(emojis, fn %{url: url, static_url: static_url} -> + String.starts_with?(url, Pleroma.Web.base_url()) && + String.starts_with?(static_url, Pleroma.Web.base_url()) + end) + + _ -> + true + end) + |> assert() + end end end diff --git a/test/web/media_proxy/media_proxy_controller_test.exs b/test/web/media_proxy/media_proxy_controller_test.exs index d4db44c63..33e6873f7 100644 --- a/test/web/media_proxy/media_proxy_controller_test.exs +++ b/test/web/media_proxy/media_proxy_controller_test.exs @@ -8,34 +8,34 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do import Mock alias Pleroma.Web.MediaProxy - alias Pleroma.Web.MediaProxy.MediaProxyController alias Plug.Conn setup do on_exit(fn -> Cachex.clear(:banned_urls_cache) end) end - test "it returns 404 when MediaProxy disabled", %{conn: conn} do - clear_config([:media_proxy, :enabled], false) - - assert %Conn{ - status: 404, - resp_body: "Not Found" - } = get(conn, "/proxy/hhgfh/eeeee") - - assert %Conn{ - status: 404, - resp_body: "Not Found" - } = get(conn, "/proxy/hhgfh/eeee/fff") - end - - describe "" do + describe "Media Proxy" do setup do clear_config([:media_proxy, :enabled], true) clear_config([Pleroma.Web.Endpoint, :secret_key_base], "00000000000") + [url: MediaProxy.encode_url("https://google.fn/test.png")] end + test "it returns 404 when disabled", %{conn: conn} do + clear_config([:media_proxy, :enabled], false) + + assert %Conn{ + status: 404, + resp_body: "Not Found" + } = get(conn, "/proxy/hhgfh/eeeee") + + assert %Conn{ + status: 404, + resp_body: "Not Found" + } = get(conn, "/proxy/hhgfh/eeee/fff") + end + test "it returns 403 for invalid signature", %{conn: conn, url: url} do Pleroma.Config.put([Pleroma.Web.Endpoint, :secret_key_base], "000") %{path: path} = URI.parse(url) @@ -56,7 +56,7 @@ test "it returns 403 for invalid signature", %{conn: conn, url: url} do } = get(conn, "/proxy/hhgfh/eeee/fff") end - test "redirects on valid url when filename is invalidated", %{conn: conn, url: url} do + test "redirects to valid url when filename is invalidated", %{conn: conn, url: url} do invalid_url = String.replace(url, "test.png", "test-file.png") response = get(conn, invalid_url) assert response.status == 302 @@ -80,42 +80,248 @@ test "it returns 404 when url is in banned_urls cache", %{conn: conn, url: url} end end - describe "filename_matches/3" do - test "preserves the encoded or decoded path" do - assert MediaProxyController.filename_matches( - %{"filename" => "/Hello world.jpg"}, - "/Hello world.jpg", - "http://pleroma.social/Hello world.jpg" - ) == :ok + describe "Media Preview Proxy" do + setup do + clear_config([:media_proxy, :enabled], true) + clear_config([:media_preview_proxy, :enabled], true) + clear_config([Pleroma.Web.Endpoint, :secret_key_base], "00000000000") - assert MediaProxyController.filename_matches( - %{"filename" => "/Hello%20world.jpg"}, - "/Hello%20world.jpg", - "http://pleroma.social/Hello%20world.jpg" - ) == :ok + original_url = "https://google.fn/test.png" - assert MediaProxyController.filename_matches( - %{"filename" => "/my%2Flong%2Furl%2F2019%2F07%2FS.jpg"}, - "/my%2Flong%2Furl%2F2019%2F07%2FS.jpg", - "http://pleroma.social/my%2Flong%2Furl%2F2019%2F07%2FS.jpg" - ) == :ok - - assert MediaProxyController.filename_matches( - %{"filename" => "/my%2Flong%2Furl%2F2019%2F07%2FS.jp"}, - "/my%2Flong%2Furl%2F2019%2F07%2FS.jp", - "http://pleroma.social/my%2Flong%2Furl%2F2019%2F07%2FS.jpg" - ) == {:wrong_filename, "my%2Flong%2Furl%2F2019%2F07%2FS.jpg"} + [ + url: MediaProxy.encode_preview_url(original_url), + media_proxy_url: MediaProxy.encode_url(original_url) + ] end - test "encoded url are tried to match for proxy as `conn.request_path` encodes the url" do - # conn.request_path will return encoded url - request_path = "/ANALYSE-DAI-_-LE-STABLECOIN-100-D%C3%89CENTRALIS%C3%89-BQ.jpg" + test "returns 404 when media proxy is disabled", %{conn: conn} do + clear_config([:media_proxy, :enabled], false) - assert MediaProxyController.filename_matches( - true, - request_path, - "https://mydomain.com/uploads/2019/07/ANALYSE-DAI-_-LE-STABLECOIN-100-DÉCENTRALISÉ-BQ.jpg" - ) == :ok + assert %Conn{ + status: 404, + resp_body: "Not Found" + } = get(conn, "/proxy/preview/hhgfh/eeeee") + + assert %Conn{ + status: 404, + resp_body: "Not Found" + } = get(conn, "/proxy/preview/hhgfh/fff") + end + + test "returns 404 when disabled", %{conn: conn} do + clear_config([:media_preview_proxy, :enabled], false) + + assert %Conn{ + status: 404, + resp_body: "Not Found" + } = get(conn, "/proxy/preview/hhgfh/eeeee") + + assert %Conn{ + status: 404, + resp_body: "Not Found" + } = get(conn, "/proxy/preview/hhgfh/fff") + end + + test "it returns 403 for invalid signature", %{conn: conn, url: url} do + Pleroma.Config.put([Pleroma.Web.Endpoint, :secret_key_base], "000") + %{path: path} = URI.parse(url) + + assert %Conn{ + status: 403, + resp_body: "Forbidden" + } = get(conn, path) + + assert %Conn{ + status: 403, + resp_body: "Forbidden" + } = get(conn, "/proxy/preview/hhgfh/eeee") + + assert %Conn{ + status: 403, + resp_body: "Forbidden" + } = get(conn, "/proxy/preview/hhgfh/eeee/fff") + end + + test "redirects to valid url when filename is invalidated", %{conn: conn, url: url} do + invalid_url = String.replace(url, "test.png", "test-file.png") + response = get(conn, invalid_url) + assert response.status == 302 + assert redirected_to(response) == url + end + + test "responds with 424 Failed Dependency if HEAD request to media proxy fails", %{ + conn: conn, + url: url, + media_proxy_url: media_proxy_url + } do + Tesla.Mock.mock(fn + %{method: "head", url: ^media_proxy_url} -> + %Tesla.Env{status: 500, body: ""} + end) + + response = get(conn, url) + assert response.status == 424 + assert response.resp_body == "Can't fetch HTTP headers (HTTP 500)." + end + + test "redirects to media proxy URI on unsupported content type", %{ + conn: conn, + url: url, + media_proxy_url: media_proxy_url + } do + Tesla.Mock.mock(fn + %{method: "head", url: ^media_proxy_url} -> + %Tesla.Env{status: 200, body: "", headers: [{"content-type", "application/pdf"}]} + end) + + response = get(conn, url) + assert response.status == 302 + assert redirected_to(response) == media_proxy_url + end + + test "with `static=true` and GIF image preview requested, responds with JPEG image", %{ + conn: conn, + url: url, + media_proxy_url: media_proxy_url + } do + # Setting a high :min_content_length to ensure this scenario is not affected by its logic + clear_config([:media_preview_proxy, :min_content_length], 1_000_000_000) + + Tesla.Mock.mock(fn + %{method: "head", url: ^media_proxy_url} -> + %Tesla.Env{ + status: 200, + body: "", + headers: [{"content-type", "image/gif"}, {"content-length", "1001718"}] + } + + %{method: :get, url: ^media_proxy_url} -> + %Tesla.Env{status: 200, body: File.read!("test/fixtures/image.gif")} + end) + + response = get(conn, url <> "?static=true") + + assert response.status == 200 + assert Conn.get_resp_header(response, "content-type") == ["image/jpeg"] + assert response.resp_body != "" + end + + test "with GIF image preview requested and no `static` param, redirects to media proxy URI", + %{ + conn: conn, + url: url, + media_proxy_url: media_proxy_url + } do + Tesla.Mock.mock(fn + %{method: "head", url: ^media_proxy_url} -> + %Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/gif"}]} + end) + + response = get(conn, url) + + assert response.status == 302 + assert redirected_to(response) == media_proxy_url + end + + test "with `static` param and non-GIF image preview requested, " <> + "redirects to media preview proxy URI without `static` param", + %{ + conn: conn, + url: url, + media_proxy_url: media_proxy_url + } do + Tesla.Mock.mock(fn + %{method: "head", url: ^media_proxy_url} -> + %Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/jpeg"}]} + end) + + response = get(conn, url <> "?static=true") + + assert response.status == 302 + assert redirected_to(response) == url + end + + test "with :min_content_length setting not matched by Content-Length header, " <> + "redirects to media proxy URI", + %{ + conn: conn, + url: url, + media_proxy_url: media_proxy_url + } do + clear_config([:media_preview_proxy, :min_content_length], 100_000) + + Tesla.Mock.mock(fn + %{method: "head", url: ^media_proxy_url} -> + %Tesla.Env{ + status: 200, + body: "", + headers: [{"content-type", "image/gif"}, {"content-length", "5000"}] + } + end) + + response = get(conn, url) + + assert response.status == 302 + assert redirected_to(response) == media_proxy_url + end + + test "thumbnails PNG images into PNG", %{ + conn: conn, + url: url, + media_proxy_url: media_proxy_url + } do + Tesla.Mock.mock(fn + %{method: "head", url: ^media_proxy_url} -> + %Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/png"}]} + + %{method: :get, url: ^media_proxy_url} -> + %Tesla.Env{status: 200, body: File.read!("test/fixtures/image.png")} + end) + + response = get(conn, url) + + assert response.status == 200 + assert Conn.get_resp_header(response, "content-type") == ["image/png"] + assert response.resp_body != "" + end + + test "thumbnails JPEG images into JPEG", %{ + conn: conn, + url: url, + media_proxy_url: media_proxy_url + } do + Tesla.Mock.mock(fn + %{method: "head", url: ^media_proxy_url} -> + %Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/jpeg"}]} + + %{method: :get, url: ^media_proxy_url} -> + %Tesla.Env{status: 200, body: File.read!("test/fixtures/image.jpg")} + end) + + response = get(conn, url) + + assert response.status == 200 + assert Conn.get_resp_header(response, "content-type") == ["image/jpeg"] + assert response.resp_body != "" + end + + test "redirects to media proxy URI in case of thumbnailing error", %{ + conn: conn, + url: url, + media_proxy_url: media_proxy_url + } do + Tesla.Mock.mock(fn + %{method: "head", url: ^media_proxy_url} -> + %Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/jpeg"}]} + + %{method: :get, url: ^media_proxy_url} -> + %Tesla.Env{status: 200, body: "error"} + end) + + response = get(conn, url) + + assert response.status == 302 + assert redirected_to(response) == media_proxy_url end end end diff --git a/test/web/media_proxy/media_proxy_test.exs b/test/web/media_proxy/media_proxy_test.exs index 72885cfdd..0e6df826c 100644 --- a/test/web/media_proxy/media_proxy_test.exs +++ b/test/web/media_proxy/media_proxy_test.exs @@ -6,9 +6,16 @@ defmodule Pleroma.Web.MediaProxyTest do use ExUnit.Case use Pleroma.Tests.Helpers + alias Pleroma.Config alias Pleroma.Web.Endpoint alias Pleroma.Web.MediaProxy + defp decode_result(encoded) do + [_, "proxy", sig, base64 | _] = URI.parse(encoded).path |> String.split("/") + {:ok, decoded} = MediaProxy.decode_url(sig, base64) + decoded + end + describe "when enabled" do setup do: clear_config([:media_proxy, :enabled], true) @@ -35,7 +42,7 @@ test "encodes and decodes URL" do assert String.starts_with?( encoded, - Pleroma.Config.get([:media_proxy, :base_url], Pleroma.Web.base_url()) + Config.get([:media_proxy, :base_url], Pleroma.Web.base_url()) ) assert String.ends_with?(encoded, "/logo.png") @@ -75,6 +82,64 @@ test "validates signature" do assert MediaProxy.decode_url(sig, base64) == {:error, :invalid_signature} end + def test_verify_request_path_and_url(request_path, url, expected_result) do + assert MediaProxy.verify_request_path_and_url(request_path, url) == expected_result + + assert MediaProxy.verify_request_path_and_url( + %Plug.Conn{ + params: %{"filename" => Path.basename(request_path)}, + request_path: request_path + }, + url + ) == expected_result + end + + test "if first arg of `verify_request_path_and_url/2` is a Plug.Conn without \"filename\" " <> + "parameter, `verify_request_path_and_url/2` returns :ok " do + assert MediaProxy.verify_request_path_and_url( + %Plug.Conn{params: %{}, request_path: "/some/path"}, + "https://instance.com/file.jpg" + ) == :ok + + assert MediaProxy.verify_request_path_and_url( + %Plug.Conn{params: %{}, request_path: "/path/to/file.jpg"}, + "https://instance.com/file.jpg" + ) == :ok + end + + test "`verify_request_path_and_url/2` preserves the encoded or decoded path" do + test_verify_request_path_and_url( + "/Hello world.jpg", + "http://pleroma.social/Hello world.jpg", + :ok + ) + + test_verify_request_path_and_url( + "/Hello%20world.jpg", + "http://pleroma.social/Hello%20world.jpg", + :ok + ) + + test_verify_request_path_and_url( + "/my%2Flong%2Furl%2F2019%2F07%2FS.jpg", + "http://pleroma.social/my%2Flong%2Furl%2F2019%2F07%2FS.jpg", + :ok + ) + + test_verify_request_path_and_url( + # Note: `conn.request_path` returns encoded url + "/ANALYSE-DAI-_-LE-STABLECOIN-100-D%C3%89CENTRALIS%C3%89-BQ.jpg", + "https://mydomain.com/uploads/2019/07/ANALYSE-DAI-_-LE-STABLECOIN-100-DÉCENTRALISÉ-BQ.jpg", + :ok + ) + + test_verify_request_path_and_url( + "/my%2Flong%2Furl%2F2019%2F07%2FS", + "http://pleroma.social/my%2Flong%2Furl%2F2019%2F07%2FS.jpg", + {:wrong_filename, "my%2Flong%2Furl%2F2019%2F07%2FS.jpg"} + ) + end + test "uses the configured base_url" do base_url = "https://cache.pleroma.social" clear_config([:media_proxy, :base_url], base_url) @@ -124,12 +189,6 @@ test "does not encode remote urls" do end end - defp decode_result(encoded) do - [_, "proxy", sig, base64 | _] = URI.parse(encoded).path |> String.split("/") - {:ok, decoded} = MediaProxy.decode_url(sig, base64) - decoded - end - describe "whitelist" do setup do: clear_config([:media_proxy, :enabled], true) diff --git a/test/web/metadata/metadata_test.exs b/test/web/metadata/metadata_test.exs index 9d3121b7b..ca6cbe67f 100644 --- a/test/web/metadata/metadata_test.exs +++ b/test/web/metadata/metadata_test.exs @@ -16,7 +16,14 @@ test "for remote user" do end test "for local user" do - user = insert(:user) + user = insert(:user, discoverable: false) + + assert Pleroma.Web.Metadata.build_tags(%{user: user}) =~ + "" + end + + test "for local user set to discoverable" do + user = insert(:user, discoverable: true) refute Pleroma.Web.Metadata.build_tags(%{user: user}) =~ "" @@ -24,11 +31,19 @@ test "for local user" do end describe "no metadata for private instances" do - test "for local user" do + test "for local user set to discoverable" do clear_config([:instance, :public], false) - user = insert(:user, bio: "This is my secret fedi account bio") + user = insert(:user, bio: "This is my secret fedi account bio", discoverable: true) assert "" = Pleroma.Web.Metadata.build_tags(%{user: user}) end + + test "search exclusion metadata is included" do + clear_config([:instance, :public], false) + user = insert(:user, bio: "This is my secret fedi account bio", discoverable: false) + + assert ~s() == + Pleroma.Web.Metadata.build_tags(%{user: user}) + end end end diff --git a/test/web/metadata/restrict_indexing_test.exs b/test/web/metadata/restrict_indexing_test.exs index aad0bac42..6b3a65372 100644 --- a/test/web/metadata/restrict_indexing_test.exs +++ b/test/web/metadata/restrict_indexing_test.exs @@ -14,8 +14,14 @@ test "for remote user" do test "for local user" do assert Pleroma.Web.Metadata.Providers.RestrictIndexing.build_tags(%{ - user: %Pleroma.User{local: true} + user: %Pleroma.User{local: true, discoverable: true} }) == [] end + + test "for local user when discoverable is false" do + assert Pleroma.Web.Metadata.Providers.RestrictIndexing.build_tags(%{ + user: %Pleroma.User{local: true, discoverable: false} + }) == [{:meta, [name: "robots", content: "noindex, noarchive"], []}] + end end end diff --git a/test/web/oauth/token_test.exs b/test/web/oauth/token_test.exs index 40d71eb59..c88b9cc98 100644 --- a/test/web/oauth/token_test.exs +++ b/test/web/oauth/token_test.exs @@ -69,17 +69,4 @@ test "deletes all tokens of a user" do assert tokens == 2 end - - test "deletes expired tokens" do - insert(:oauth_token, valid_until: Timex.shift(Timex.now(), days: -3)) - insert(:oauth_token, valid_until: Timex.shift(Timex.now(), days: -3)) - t3 = insert(:oauth_token) - t4 = insert(:oauth_token, valid_until: Timex.shift(Timex.now(), minutes: 10)) - {tokens, _} = Token.delete_expired_tokens() - assert tokens == 2 - available_tokens = Pleroma.Repo.all(Token) - - token_ids = available_tokens |> Enum.map(& &1.id) - assert token_ids == [t3.id, t4.id] - end end diff --git a/test/web/pleroma_api/controllers/user_import_controller_test.exs b/test/web/pleroma_api/controllers/user_import_controller_test.exs new file mode 100644 index 000000000..433c97e81 --- /dev/null +++ b/test/web/pleroma_api/controllers/user_import_controller_test.exs @@ -0,0 +1,235 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.UserImportControllerTest do + use Pleroma.Web.ConnCase + use Oban.Testing, repo: Pleroma.Repo + + alias Pleroma.Config + alias Pleroma.Tests.ObanHelpers + + import Pleroma.Factory + import Mock + + setup do + Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end) + :ok + end + + describe "POST /api/pleroma/follow_import" do + setup do: oauth_access(["follow"]) + + test "it returns HTTP 200", %{conn: conn} do + user2 = insert(:user) + + assert "job started" == + conn + |> put_req_header("content-type", "application/json") + |> post("/api/pleroma/follow_import", %{"list" => "#{user2.ap_id}"}) + |> json_response_and_validate_schema(200) + end + + test "it imports follow lists from file", %{conn: conn} do + user2 = insert(:user) + + with_mocks([ + {File, [], + read!: fn "follow_list.txt" -> + "Account address,Show boosts\n#{user2.ap_id},true" + end} + ]) do + assert "job started" == + conn + |> put_req_header("content-type", "application/json") + |> post("/api/pleroma/follow_import", %{ + "list" => %Plug.Upload{path: "follow_list.txt"} + }) + |> json_response_and_validate_schema(200) + + assert [{:ok, job_result}] = ObanHelpers.perform_all() + assert job_result == [user2] + end + end + + test "it imports new-style mastodon follow lists", %{conn: conn} do + user2 = insert(:user) + + response = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/pleroma/follow_import", %{ + "list" => "Account address,Show boosts\n#{user2.ap_id},true" + }) + |> json_response_and_validate_schema(200) + + assert response == "job started" + end + + test "requires 'follow' or 'write:follows' permissions" do + token1 = insert(:oauth_token, scopes: ["read", "write"]) + token2 = insert(:oauth_token, scopes: ["follow"]) + token3 = insert(:oauth_token, scopes: ["something"]) + another_user = insert(:user) + + for token <- [token1, token2, token3] do + conn = + build_conn() + |> put_req_header("authorization", "Bearer #{token.token}") + |> put_req_header("content-type", "application/json") + |> post("/api/pleroma/follow_import", %{"list" => "#{another_user.ap_id}"}) + + if token == token3 do + assert %{"error" => "Insufficient permissions: follow | write:follows."} == + json_response(conn, 403) + else + assert json_response(conn, 200) + end + end + end + + test "it imports follows with different nickname variations", %{conn: conn} do + users = [user2, user3, user4, user5, user6] = insert_list(5, :user) + + identifiers = + [ + user2.ap_id, + user3.nickname, + " ", + "@" <> user4.nickname, + user5.nickname <> "@localhost", + "@" <> user6.nickname <> "@localhost" + ] + |> Enum.join("\n") + + assert "job started" == + conn + |> put_req_header("content-type", "application/json") + |> post("/api/pleroma/follow_import", %{"list" => identifiers}) + |> json_response_and_validate_schema(200) + + assert [{:ok, job_result}] = ObanHelpers.perform_all() + assert job_result == users + end + end + + describe "POST /api/pleroma/blocks_import" do + # Note: "follow" or "write:blocks" permission is required + setup do: oauth_access(["write:blocks"]) + + test "it returns HTTP 200", %{conn: conn} do + user2 = insert(:user) + + assert "job started" == + conn + |> put_req_header("content-type", "application/json") + |> post("/api/pleroma/blocks_import", %{"list" => "#{user2.ap_id}"}) + |> json_response_and_validate_schema(200) + end + + test "it imports blocks users from file", %{conn: conn} do + users = [user2, user3] = insert_list(2, :user) + + with_mocks([ + {File, [], read!: fn "blocks_list.txt" -> "#{user2.ap_id} #{user3.ap_id}" end} + ]) do + assert "job started" == + conn + |> put_req_header("content-type", "application/json") + |> post("/api/pleroma/blocks_import", %{ + "list" => %Plug.Upload{path: "blocks_list.txt"} + }) + |> json_response_and_validate_schema(200) + + assert [{:ok, job_result}] = ObanHelpers.perform_all() + assert job_result == users + end + end + + test "it imports blocks with different nickname variations", %{conn: conn} do + users = [user2, user3, user4, user5, user6] = insert_list(5, :user) + + identifiers = + [ + user2.ap_id, + user3.nickname, + "@" <> user4.nickname, + user5.nickname <> "@localhost", + "@" <> user6.nickname <> "@localhost" + ] + |> Enum.join(" ") + + assert "job started" == + conn + |> put_req_header("content-type", "application/json") + |> post("/api/pleroma/blocks_import", %{"list" => identifiers}) + |> json_response_and_validate_schema(200) + + assert [{:ok, job_result}] = ObanHelpers.perform_all() + assert job_result == users + end + end + + describe "POST /api/pleroma/mutes_import" do + # Note: "follow" or "write:mutes" permission is required + setup do: oauth_access(["write:mutes"]) + + test "it returns HTTP 200", %{user: user, conn: conn} do + user2 = insert(:user) + + assert "job started" == + conn + |> put_req_header("content-type", "application/json") + |> post("/api/pleroma/mutes_import", %{"list" => "#{user2.ap_id}"}) + |> json_response_and_validate_schema(200) + + assert [{:ok, job_result}] = ObanHelpers.perform_all() + assert job_result == [user2] + assert Pleroma.User.mutes?(user, user2) + end + + test "it imports mutes users from file", %{user: user, conn: conn} do + users = [user2, user3] = insert_list(2, :user) + + with_mocks([ + {File, [], read!: fn "mutes_list.txt" -> "#{user2.ap_id} #{user3.ap_id}" end} + ]) do + assert "job started" == + conn + |> put_req_header("content-type", "application/json") + |> post("/api/pleroma/mutes_import", %{ + "list" => %Plug.Upload{path: "mutes_list.txt"} + }) + |> json_response_and_validate_schema(200) + + assert [{:ok, job_result}] = ObanHelpers.perform_all() + assert job_result == users + assert Enum.all?(users, &Pleroma.User.mutes?(user, &1)) + end + end + + test "it imports mutes with different nickname variations", %{user: user, conn: conn} do + users = [user2, user3, user4, user5, user6] = insert_list(5, :user) + + identifiers = + [ + user2.ap_id, + user3.nickname, + "@" <> user4.nickname, + user5.nickname <> "@localhost", + "@" <> user6.nickname <> "@localhost" + ] + |> Enum.join(" ") + + assert "job started" == + conn + |> put_req_header("content-type", "application/json") + |> post("/api/pleroma/mutes_import", %{"list" => identifiers}) + |> json_response_and_validate_schema(200) + + assert [{:ok, job_result}] = ObanHelpers.perform_all() + assert job_result == users + assert Enum.all?(users, &Pleroma.User.mutes?(user, &1)) + end + end +end diff --git a/test/web/push/impl_test.exs b/test/web/push/impl_test.exs index aeb5c1fbd..c7c17e156 100644 --- a/test/web/push/impl_test.exs +++ b/test/web/push/impl_test.exs @@ -12,7 +12,9 @@ defmodule Pleroma.Web.Push.ImplTest do alias Pleroma.Web.CommonAPI alias Pleroma.Web.Push.Impl alias Pleroma.Web.Push.Subscription + alias Pleroma.Web.WebPushHttpClientMock + import Mock import Pleroma.Factory setup do @@ -78,6 +80,22 @@ test "successful message sending" do assert Impl.push_message(@message, @sub, @api_key, %Subscription{}) == :ok end + test_with_mock "uses WebPushHttpClientMock as an HTTP client", WebPushHttpClientMock, + post: fn _, _, _ -> {:ok, %{status_code: 200}} end do + Impl.push_message(@message, @sub, @api_key, %Subscription{}) + assert_called(WebPushHttpClientMock.post("https://example.com/example/1234", :_, :_)) + end + + test_with_mock "uses Pleroma.HTTP as an HTTP client", Pleroma.HTTP, + post: fn _, _, _ -> {:ok, %{status_code: 200}} end do + client = Application.get_env(:web_push_encryption, :http_client) + on_exit(fn -> Application.put_env(:web_push_encryption, :http_client, client) end) + Application.put_env(:web_push_encryption, :http_client, Pleroma.HTTP) + + Impl.push_message(@message, @sub, @api_key, %Subscription{}) + assert_called(Pleroma.HTTP.post("https://example.com/example/1234", :_, :_)) + end + @tag capture_log: true test "fail message sending" do assert Impl.push_message( diff --git a/test/web/rich_media/parser_test.exs b/test/web/rich_media/parser_test.exs index b8ef2cccf..6d00c2af5 100644 --- a/test/web/rich_media/parser_test.exs +++ b/test/web/rich_media/parser_test.exs @@ -87,9 +87,7 @@ test "returns error when no metadata present" do end test "doesn't just add a title" do - assert Parser.parse("http://example.com/non-ogp") == - {:error, - "Found metadata was invalid or incomplete: %{\"url\" => \"http://example.com/non-ogp\"}"} + assert {:error, {:invalid_metadata, _}} = Parser.parse("http://example.com/non-ogp") end test "parses ogp" do diff --git a/test/web/streamer/streamer_test.exs b/test/web/streamer/streamer_test.exs index d56d74464..185724a9f 100644 --- a/test/web/streamer/streamer_test.exs +++ b/test/web/streamer/streamer_test.exs @@ -21,92 +21,148 @@ defmodule Pleroma.Web.StreamerTest do setup do: clear_config([:instance, :skip_thread_containment]) - describe "get_topic without an user" do + describe "get_topic/_ (unauthenticated)" do test "allows public" do - assert {:ok, "public"} = Streamer.get_topic("public", nil) - assert {:ok, "public:local"} = Streamer.get_topic("public:local", nil) - assert {:ok, "public:media"} = Streamer.get_topic("public:media", nil) - assert {:ok, "public:local:media"} = Streamer.get_topic("public:local:media", nil) + assert {:ok, "public"} = Streamer.get_topic("public", nil, nil) + assert {:ok, "public:local"} = Streamer.get_topic("public:local", nil, nil) + assert {:ok, "public:media"} = Streamer.get_topic("public:media", nil, nil) + assert {:ok, "public:local:media"} = Streamer.get_topic("public:local:media", nil, nil) end test "allows hashtag streams" do - assert {:ok, "hashtag:cofe"} = Streamer.get_topic("hashtag", nil, %{"tag" => "cofe"}) + assert {:ok, "hashtag:cofe"} = Streamer.get_topic("hashtag", nil, nil, %{"tag" => "cofe"}) end test "disallows user streams" do - assert {:error, _} = Streamer.get_topic("user", nil) - assert {:error, _} = Streamer.get_topic("user:notification", nil) - assert {:error, _} = Streamer.get_topic("direct", nil) + assert {:error, _} = Streamer.get_topic("user", nil, nil) + assert {:error, _} = Streamer.get_topic("user:notification", nil, nil) + assert {:error, _} = Streamer.get_topic("direct", nil, nil) end test "disallows list streams" do - assert {:error, _} = Streamer.get_topic("list", nil, %{"list" => 42}) + assert {:error, _} = Streamer.get_topic("list", nil, nil, %{"list" => 42}) end end - describe "get_topic with an user" do - setup do - user = insert(:user) - {:ok, %{user: user}} + describe "get_topic/_ (authenticated)" do + setup do: oauth_access(["read"]) + + test "allows public streams (regardless of OAuth token scopes)", %{ + user: user, + token: read_oauth_token + } do + with oauth_token <- [nil, read_oauth_token] do + assert {:ok, "public"} = Streamer.get_topic("public", user, oauth_token) + assert {:ok, "public:local"} = Streamer.get_topic("public:local", user, oauth_token) + assert {:ok, "public:media"} = Streamer.get_topic("public:media", user, oauth_token) + + assert {:ok, "public:local:media"} = + Streamer.get_topic("public:local:media", user, oauth_token) + end end - test "allows public streams", %{user: user} do - assert {:ok, "public"} = Streamer.get_topic("public", user) - assert {:ok, "public:local"} = Streamer.get_topic("public:local", user) - assert {:ok, "public:media"} = Streamer.get_topic("public:media", user) - assert {:ok, "public:local:media"} = Streamer.get_topic("public:local:media", user) - end + test "allows user streams (with proper OAuth token scopes)", %{ + user: user, + token: read_oauth_token + } do + %{token: read_notifications_token} = oauth_access(["read:notifications"], user: user) + %{token: read_statuses_token} = oauth_access(["read:statuses"], user: user) + %{token: badly_scoped_token} = oauth_access(["irrelevant:scope"], user: user) - test "allows user streams", %{user: user} do expected_user_topic = "user:#{user.id}" - expected_notif_topic = "user:notification:#{user.id}" + expected_notification_topic = "user:notification:#{user.id}" expected_direct_topic = "direct:#{user.id}" - assert {:ok, ^expected_user_topic} = Streamer.get_topic("user", user) - assert {:ok, ^expected_notif_topic} = Streamer.get_topic("user:notification", user) - assert {:ok, ^expected_direct_topic} = Streamer.get_topic("direct", user) + expected_pleroma_chat_topic = "user:pleroma_chat:#{user.id}" + + for valid_user_token <- [read_oauth_token, read_statuses_token] do + assert {:ok, ^expected_user_topic} = Streamer.get_topic("user", user, valid_user_token) + + assert {:ok, ^expected_direct_topic} = + Streamer.get_topic("direct", user, valid_user_token) + + assert {:ok, ^expected_pleroma_chat_topic} = + Streamer.get_topic("user:pleroma_chat", user, valid_user_token) + end + + for invalid_user_token <- [read_notifications_token, badly_scoped_token], + user_topic <- ["user", "direct", "user:pleroma_chat"] do + assert {:error, :unauthorized} = Streamer.get_topic(user_topic, user, invalid_user_token) + end + + for valid_notification_token <- [read_oauth_token, read_notifications_token] do + assert {:ok, ^expected_notification_topic} = + Streamer.get_topic("user:notification", user, valid_notification_token) + end + + for invalid_notification_token <- [read_statuses_token, badly_scoped_token] do + assert {:error, :unauthorized} = + Streamer.get_topic("user:notification", user, invalid_notification_token) + end end - test "allows hashtag streams", %{user: user} do - assert {:ok, "hashtag:cofe"} = Streamer.get_topic("hashtag", user, %{"tag" => "cofe"}) + test "allows hashtag streams (regardless of OAuth token scopes)", %{ + user: user, + token: read_oauth_token + } do + for oauth_token <- [nil, read_oauth_token] do + assert {:ok, "hashtag:cofe"} = + Streamer.get_topic("hashtag", user, oauth_token, %{"tag" => "cofe"}) + end end - test "disallows registering to an user stream", %{user: user} do + test "disallows registering to another user's stream", %{user: user, token: read_oauth_token} do another_user = insert(:user) - assert {:error, _} = Streamer.get_topic("user:#{another_user.id}", user) - assert {:error, _} = Streamer.get_topic("user:notification:#{another_user.id}", user) - assert {:error, _} = Streamer.get_topic("direct:#{another_user.id}", user) + assert {:error, _} = Streamer.get_topic("user:#{another_user.id}", user, read_oauth_token) + + assert {:error, _} = + Streamer.get_topic("user:notification:#{another_user.id}", user, read_oauth_token) + + assert {:error, _} = Streamer.get_topic("direct:#{another_user.id}", user, read_oauth_token) end - test "allows list stream that are owned by the user", %{user: user} do + test "allows list stream that are owned by the user (with `read` or `read:lists` scopes)", %{ + user: user, + token: read_oauth_token + } do + %{token: read_lists_token} = oauth_access(["read:lists"], user: user) + %{token: invalid_token} = oauth_access(["irrelevant:scope"], user: user) {:ok, list} = List.create("Test", user) - assert {:error, _} = Streamer.get_topic("list:#{list.id}", user) - assert {:ok, _} = Streamer.get_topic("list", user, %{"list" => list.id}) + + assert {:error, _} = Streamer.get_topic("list:#{list.id}", user, read_oauth_token) + + for valid_token <- [read_oauth_token, read_lists_token] do + assert {:ok, _} = Streamer.get_topic("list", user, valid_token, %{"list" => list.id}) + end + + assert {:error, _} = Streamer.get_topic("list", user, invalid_token, %{"list" => list.id}) end - test "disallows list stream that are not owned by the user", %{user: user} do + test "disallows list stream that are not owned by the user", %{user: user, token: oauth_token} do another_user = insert(:user) {:ok, list} = List.create("Test", another_user) - assert {:error, _} = Streamer.get_topic("list:#{list.id}", user) - assert {:error, _} = Streamer.get_topic("list", user, %{"list" => list.id}) + + assert {:error, _} = Streamer.get_topic("list:#{list.id}", user, oauth_token) + assert {:error, _} = Streamer.get_topic("list", user, oauth_token, %{"list" => list.id}) end end describe "user streams" do setup do - user = insert(:user) + %{user: user, token: token} = oauth_access(["read"]) notify = insert(:notification, user: user, activity: build(:note_activity)) - {:ok, %{user: user, notify: notify}} + {:ok, %{user: user, notify: notify, token: token}} end - test "it streams the user's post in the 'user' stream", %{user: user} do - Streamer.get_topic_and_add_socket("user", user) + test "it streams the user's post in the 'user' stream", %{user: user, token: oauth_token} do + Streamer.get_topic_and_add_socket("user", user, oauth_token) {:ok, activity} = CommonAPI.post(user, %{status: "hey"}) + assert_receive {:render_with_user, _, _, ^activity} refute Streamer.filtered_by_user?(user, activity) end - test "it streams boosts of the user in the 'user' stream", %{user: user} do - Streamer.get_topic_and_add_socket("user", user) + test "it streams boosts of the user in the 'user' stream", %{user: user, token: oauth_token} do + Streamer.get_topic_and_add_socket("user", user, oauth_token) other_user = insert(:user) {:ok, activity} = CommonAPI.post(other_user, %{status: "hey"}) @@ -117,9 +173,10 @@ test "it streams boosts of the user in the 'user' stream", %{user: user} do end test "it does not stream announces of the user's own posts in the 'user' stream", %{ - user: user + user: user, + token: oauth_token } do - Streamer.get_topic_and_add_socket("user", user) + Streamer.get_topic_and_add_socket("user", user, oauth_token) other_user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{status: "hey"}) @@ -129,9 +186,10 @@ test "it does not stream announces of the user's own posts in the 'user' stream" end test "it does stream notifications announces of the user's own posts in the 'user' stream", %{ - user: user + user: user, + token: oauth_token } do - Streamer.get_topic_and_add_socket("user", user) + Streamer.get_topic_and_add_socket("user", user, oauth_token) other_user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{status: "hey"}) @@ -145,8 +203,11 @@ test "it does stream notifications announces of the user's own posts in the 'use refute Streamer.filtered_by_user?(user, notification) end - test "it streams boosts of mastodon user in the 'user' stream", %{user: user} do - Streamer.get_topic_and_add_socket("user", user) + test "it streams boosts of mastodon user in the 'user' stream", %{ + user: user, + token: oauth_token + } do + Streamer.get_topic_and_add_socket("user", user, oauth_token) other_user = insert(:user) {:ok, activity} = CommonAPI.post(other_user, %{status: "hey"}) @@ -164,21 +225,34 @@ test "it streams boosts of mastodon user in the 'user' stream", %{user: user} do refute Streamer.filtered_by_user?(user, announce) end - test "it sends notify to in the 'user' stream", %{user: user, notify: notify} do - Streamer.get_topic_and_add_socket("user", user) + test "it sends notify to in the 'user' stream", %{ + user: user, + token: oauth_token, + notify: notify + } do + Streamer.get_topic_and_add_socket("user", user, oauth_token) Streamer.stream("user", notify) + assert_receive {:render_with_user, _, _, ^notify} refute Streamer.filtered_by_user?(user, notify) end - test "it sends notify to in the 'user:notification' stream", %{user: user, notify: notify} do - Streamer.get_topic_and_add_socket("user:notification", user) + test "it sends notify to in the 'user:notification' stream", %{ + user: user, + token: oauth_token, + notify: notify + } do + Streamer.get_topic_and_add_socket("user:notification", user, oauth_token) Streamer.stream("user:notification", notify) + assert_receive {:render_with_user, _, _, ^notify} refute Streamer.filtered_by_user?(user, notify) end - test "it sends chat messages to the 'user:pleroma_chat' stream", %{user: user} do + test "it sends chat messages to the 'user:pleroma_chat' stream", %{ + user: user, + token: oauth_token + } do other_user = insert(:user) {:ok, create_activity} = CommonAPI.post_chat_message(other_user, user, "hey cirno") @@ -187,7 +261,7 @@ test "it sends chat messages to the 'user:pleroma_chat' stream", %{user: user} d cm_ref = MessageReference.for_chat_and_object(chat, object) cm_ref = %{cm_ref | chat: chat, object: object} - Streamer.get_topic_and_add_socket("user:pleroma_chat", user) + Streamer.get_topic_and_add_socket("user:pleroma_chat", user, oauth_token) Streamer.stream("user:pleroma_chat", {user, cm_ref}) text = StreamerView.render("chat_update.json", %{chat_message_reference: cm_ref}) @@ -196,7 +270,7 @@ test "it sends chat messages to the 'user:pleroma_chat' stream", %{user: user} d assert_receive {:text, ^text} end - test "it sends chat messages to the 'user' stream", %{user: user} do + test "it sends chat messages to the 'user' stream", %{user: user, token: oauth_token} do other_user = insert(:user) {:ok, create_activity} = CommonAPI.post_chat_message(other_user, user, "hey cirno") @@ -205,7 +279,7 @@ test "it sends chat messages to the 'user' stream", %{user: user} do cm_ref = MessageReference.for_chat_and_object(chat, object) cm_ref = %{cm_ref | chat: chat, object: object} - Streamer.get_topic_and_add_socket("user", user) + Streamer.get_topic_and_add_socket("user", user, oauth_token) Streamer.stream("user", {user, cm_ref}) text = StreamerView.render("chat_update.json", %{chat_message_reference: cm_ref}) @@ -214,7 +288,10 @@ test "it sends chat messages to the 'user' stream", %{user: user} do assert_receive {:text, ^text} end - test "it sends chat message notifications to the 'user:notification' stream", %{user: user} do + test "it sends chat message notifications to the 'user:notification' stream", %{ + user: user, + token: oauth_token + } do other_user = insert(:user) {:ok, create_activity} = CommonAPI.post_chat_message(other_user, user, "hey") @@ -223,19 +300,21 @@ test "it sends chat message notifications to the 'user:notification' stream", %{ Repo.get_by(Pleroma.Notification, user_id: user.id, activity_id: create_activity.id) |> Repo.preload(:activity) - Streamer.get_topic_and_add_socket("user:notification", user) + Streamer.get_topic_and_add_socket("user:notification", user, oauth_token) Streamer.stream("user:notification", notify) + assert_receive {:render_with_user, _, _, ^notify} refute Streamer.filtered_by_user?(user, notify) end test "it doesn't send notify to the 'user:notification' stream when a user is blocked", %{ - user: user + user: user, + token: oauth_token } do blocked = insert(:user) {:ok, _user_relationship} = User.block(user, blocked) - Streamer.get_topic_and_add_socket("user:notification", user) + Streamer.get_topic_and_add_socket("user:notification", user, oauth_token) {:ok, activity} = CommonAPI.post(user, %{status: ":("}) {:ok, _} = CommonAPI.favorite(blocked, activity.id) @@ -244,14 +323,15 @@ test "it doesn't send notify to the 'user:notification' stream when a user is bl end test "it doesn't send notify to the 'user:notification' stream when a thread is muted", %{ - user: user + user: user, + token: oauth_token } do user2 = insert(:user) {:ok, activity} = CommonAPI.post(user, %{status: "super hot take"}) {:ok, _} = CommonAPI.add_mute(user, activity) - Streamer.get_topic_and_add_socket("user:notification", user) + Streamer.get_topic_and_add_socket("user:notification", user, oauth_token) {:ok, favorite_activity} = CommonAPI.favorite(user2, activity.id) @@ -260,12 +340,13 @@ test "it doesn't send notify to the 'user:notification' stream when a thread is end test "it sends favorite to 'user:notification' stream'", %{ - user: user + user: user, + token: oauth_token } do user2 = insert(:user, %{ap_id: "https://hecking-lewd-place.com/user/meanie"}) {:ok, activity} = CommonAPI.post(user, %{status: "super hot take"}) - Streamer.get_topic_and_add_socket("user:notification", user) + Streamer.get_topic_and_add_socket("user:notification", user, oauth_token) {:ok, favorite_activity} = CommonAPI.favorite(user2, activity.id) assert_receive {:render_with_user, _, "notification.json", notif} @@ -274,13 +355,14 @@ test "it sends favorite to 'user:notification' stream'", %{ end test "it doesn't send the 'user:notification' stream' when a domain is blocked", %{ - user: user + user: user, + token: oauth_token } do user2 = insert(:user, %{ap_id: "https://hecking-lewd-place.com/user/meanie"}) {:ok, user} = User.block_domain(user, "hecking-lewd-place.com") {:ok, activity} = CommonAPI.post(user, %{status: "super hot take"}) - Streamer.get_topic_and_add_socket("user:notification", user) + Streamer.get_topic_and_add_socket("user:notification", user, oauth_token) {:ok, favorite_activity} = CommonAPI.favorite(user2, activity.id) refute_receive _ @@ -288,7 +370,8 @@ test "it doesn't send the 'user:notification' stream' when a domain is blocked", end test "it sends follow activities to the 'user:notification' stream", %{ - user: user + user: user, + token: oauth_token } do user_url = user.ap_id user2 = insert(:user) @@ -303,7 +386,7 @@ test "it sends follow activities to the 'user:notification' stream", %{ %Tesla.Env{status: 200, body: body} end) - Streamer.get_topic_and_add_socket("user:notification", user) + Streamer.get_topic_and_add_socket("user:notification", user, oauth_token) {:ok, _follower, _followed, follow_activity} = CommonAPI.follow(user2, user) assert_receive {:render_with_user, _, "notification.json", notif} @@ -312,51 +395,53 @@ test "it sends follow activities to the 'user:notification' stream", %{ end end - test "it sends to public authenticated" do - user = insert(:user) - other_user = insert(:user) + describe "public streams" do + test "it sends to public (authenticated)" do + %{user: user, token: oauth_token} = oauth_access(["read"]) + other_user = insert(:user) - Streamer.get_topic_and_add_socket("public", other_user) + Streamer.get_topic_and_add_socket("public", user, oauth_token) - {:ok, activity} = CommonAPI.post(user, %{status: "Test"}) - assert_receive {:render_with_user, _, _, ^activity} - refute Streamer.filtered_by_user?(user, activity) + {:ok, activity} = CommonAPI.post(other_user, %{status: "Test"}) + assert_receive {:render_with_user, _, _, ^activity} + refute Streamer.filtered_by_user?(other_user, activity) + end + + test "it sends to public (unauthenticated)" do + user = insert(:user) + + Streamer.get_topic_and_add_socket("public", nil, nil) + + {:ok, activity} = CommonAPI.post(user, %{status: "Test"}) + activity_id = activity.id + assert_receive {:text, event} + assert %{"event" => "update", "payload" => payload} = Jason.decode!(event) + assert %{"id" => ^activity_id} = Jason.decode!(payload) + + {:ok, _} = CommonAPI.delete(activity.id, user) + assert_receive {:text, event} + assert %{"event" => "delete", "payload" => ^activity_id} = Jason.decode!(event) + end + + test "handles deletions" do + %{user: user, token: oauth_token} = oauth_access(["read"]) + other_user = insert(:user) + {:ok, activity} = CommonAPI.post(other_user, %{status: "Test"}) + + Streamer.get_topic_and_add_socket("public", user, oauth_token) + + {:ok, _} = CommonAPI.delete(activity.id, other_user) + activity_id = activity.id + assert_receive {:text, event} + assert %{"event" => "delete", "payload" => ^activity_id} = Jason.decode!(event) + end end - test "works for deletions" do - user = insert(:user) - other_user = insert(:user) - {:ok, activity} = CommonAPI.post(other_user, %{status: "Test"}) - - Streamer.get_topic_and_add_socket("public", user) - - {:ok, _} = CommonAPI.delete(activity.id, other_user) - activity_id = activity.id - assert_receive {:text, event} - assert %{"event" => "delete", "payload" => ^activity_id} = Jason.decode!(event) - end - - test "it sends to public unauthenticated" do - user = insert(:user) - - Streamer.get_topic_and_add_socket("public", nil) - - {:ok, activity} = CommonAPI.post(user, %{status: "Test"}) - activity_id = activity.id - assert_receive {:text, event} - assert %{"event" => "update", "payload" => payload} = Jason.decode!(event) - assert %{"id" => ^activity_id} = Jason.decode!(payload) - - {:ok, _} = CommonAPI.delete(activity.id, user) - assert_receive {:text, event} - assert %{"event" => "delete", "payload" => ^activity_id} = Jason.decode!(event) - end - - describe "thread_containment" do + describe "thread_containment/2" do test "it filters to user if recipients invalid and thread containment is enabled" do Pleroma.Config.put([:instance, :skip_thread_containment], false) author = insert(:user) - user = insert(:user) + %{user: user, token: oauth_token} = oauth_access(["read"]) User.follow(user, author, :follow_accept) activity = @@ -368,7 +453,7 @@ test "it filters to user if recipients invalid and thread containment is enabled ) ) - Streamer.get_topic_and_add_socket("public", user) + Streamer.get_topic_and_add_socket("public", user, oauth_token) Streamer.stream("public", activity) assert_receive {:render_with_user, _, _, ^activity} assert Streamer.filtered_by_user?(user, activity) @@ -377,7 +462,7 @@ test "it filters to user if recipients invalid and thread containment is enabled test "it sends message if recipients invalid and thread containment is disabled" do Pleroma.Config.put([:instance, :skip_thread_containment], true) author = insert(:user) - user = insert(:user) + %{user: user, token: oauth_token} = oauth_access(["read"]) User.follow(user, author, :follow_accept) activity = @@ -389,7 +474,7 @@ test "it sends message if recipients invalid and thread containment is disabled" ) ) - Streamer.get_topic_and_add_socket("public", user) + Streamer.get_topic_and_add_socket("public", user, oauth_token) Streamer.stream("public", activity) assert_receive {:render_with_user, _, _, ^activity} @@ -400,6 +485,7 @@ test "it sends message if recipients invalid and thread containment is enabled b Pleroma.Config.put([:instance, :skip_thread_containment], false) author = insert(:user) user = insert(:user, skip_thread_containment: true) + %{token: oauth_token} = oauth_access(["read"], user: user) User.follow(user, author, :follow_accept) activity = @@ -411,7 +497,7 @@ test "it sends message if recipients invalid and thread containment is enabled b ) ) - Streamer.get_topic_and_add_socket("public", user) + Streamer.get_topic_and_add_socket("public", user, oauth_token) Streamer.stream("public", activity) assert_receive {:render_with_user, _, _, ^activity} @@ -420,23 +506,26 @@ test "it sends message if recipients invalid and thread containment is enabled b end describe "blocks" do - test "it filters messages involving blocked users" do - user = insert(:user) + setup do: oauth_access(["read"]) + + test "it filters messages involving blocked users", %{user: user, token: oauth_token} do blocked_user = insert(:user) {:ok, _user_relationship} = User.block(user, blocked_user) - Streamer.get_topic_and_add_socket("public", user) + Streamer.get_topic_and_add_socket("public", user, oauth_token) {:ok, activity} = CommonAPI.post(blocked_user, %{status: "Test"}) assert_receive {:render_with_user, _, _, ^activity} assert Streamer.filtered_by_user?(user, activity) end - test "it filters messages transitively involving blocked users" do - blocker = insert(:user) + test "it filters messages transitively involving blocked users", %{ + user: blocker, + token: blocker_token + } do blockee = insert(:user) friend = insert(:user) - Streamer.get_topic_and_add_socket("public", blocker) + Streamer.get_topic_and_add_socket("public", blocker, blocker_token) {:ok, _user_relationship} = User.block(blocker, blockee) @@ -458,8 +547,9 @@ test "it filters messages transitively involving blocked users" do end describe "lists" do - test "it doesn't send unwanted DMs to list" do - user_a = insert(:user) + setup do: oauth_access(["read"]) + + test "it doesn't send unwanted DMs to list", %{user: user_a, token: user_a_token} do user_b = insert(:user) user_c = insert(:user) @@ -468,7 +558,7 @@ test "it doesn't send unwanted DMs to list" do {:ok, list} = List.create("Test", user_a) {:ok, list} = List.follow(list, user_b) - Streamer.get_topic_and_add_socket("list", user_a, %{"list" => list.id}) + Streamer.get_topic_and_add_socket("list", user_a, user_a_token, %{"list" => list.id}) {:ok, _activity} = CommonAPI.post(user_b, %{ @@ -479,14 +569,13 @@ test "it doesn't send unwanted DMs to list" do refute_receive _ end - test "it doesn't send unwanted private posts to list" do - user_a = insert(:user) + test "it doesn't send unwanted private posts to list", %{user: user_a, token: user_a_token} do user_b = insert(:user) {:ok, list} = List.create("Test", user_a) {:ok, list} = List.follow(list, user_b) - Streamer.get_topic_and_add_socket("list", user_a, %{"list" => list.id}) + Streamer.get_topic_and_add_socket("list", user_a, user_a_token, %{"list" => list.id}) {:ok, _activity} = CommonAPI.post(user_b, %{ @@ -497,8 +586,7 @@ test "it doesn't send unwanted private posts to list" do refute_receive _ end - test "it sends wanted private posts to list" do - user_a = insert(:user) + test "it sends wanted private posts to list", %{user: user_a, token: user_a_token} do user_b = insert(:user) {:ok, user_a} = User.follow(user_a, user_b) @@ -506,7 +594,7 @@ test "it sends wanted private posts to list" do {:ok, list} = List.create("Test", user_a) {:ok, list} = List.follow(list, user_b) - Streamer.get_topic_and_add_socket("list", user_a, %{"list" => list.id}) + Streamer.get_topic_and_add_socket("list", user_a, user_a_token, %{"list" => list.id}) {:ok, activity} = CommonAPI.post(user_b, %{ @@ -520,8 +608,9 @@ test "it sends wanted private posts to list" do end describe "muted reblogs" do - test "it filters muted reblogs" do - user1 = insert(:user) + setup do: oauth_access(["read"]) + + test "it filters muted reblogs", %{user: user1, token: user1_token} do user2 = insert(:user) user3 = insert(:user) CommonAPI.follow(user1, user2) @@ -529,34 +618,38 @@ test "it filters muted reblogs" do {:ok, create_activity} = CommonAPI.post(user3, %{status: "I'm kawen"}) - Streamer.get_topic_and_add_socket("user", user1) + Streamer.get_topic_and_add_socket("user", user1, user1_token) {:ok, announce_activity} = CommonAPI.repeat(create_activity.id, user2) assert_receive {:render_with_user, _, _, ^announce_activity} assert Streamer.filtered_by_user?(user1, announce_activity) end - test "it filters reblog notification for reblog-muted actors" do - user1 = insert(:user) + test "it filters reblog notification for reblog-muted actors", %{ + user: user1, + token: user1_token + } do user2 = insert(:user) CommonAPI.follow(user1, user2) CommonAPI.hide_reblogs(user1, user2) {:ok, create_activity} = CommonAPI.post(user1, %{status: "I'm kawen"}) - Streamer.get_topic_and_add_socket("user", user1) + Streamer.get_topic_and_add_socket("user", user1, user1_token) {:ok, _announce_activity} = CommonAPI.repeat(create_activity.id, user2) assert_receive {:render_with_user, _, "notification.json", notif} assert Streamer.filtered_by_user?(user1, notif) end - test "it send non-reblog notification for reblog-muted actors" do - user1 = insert(:user) + test "it send non-reblog notification for reblog-muted actors", %{ + user: user1, + token: user1_token + } do user2 = insert(:user) CommonAPI.follow(user1, user2) CommonAPI.hide_reblogs(user1, user2) {:ok, create_activity} = CommonAPI.post(user1, %{status: "I'm kawen"}) - Streamer.get_topic_and_add_socket("user", user1) + Streamer.get_topic_and_add_socket("user", user1, user1_token) {:ok, _favorite_activity} = CommonAPI.favorite(user2, create_activity.id) assert_receive {:render_with_user, _, "notification.json", notif} @@ -564,27 +657,28 @@ test "it send non-reblog notification for reblog-muted actors" do end end - test "it filters posts from muted threads" do - user = insert(:user) - user2 = insert(:user) - Streamer.get_topic_and_add_socket("user", user2) - {:ok, user2, user, _activity} = CommonAPI.follow(user2, user) - {:ok, activity} = CommonAPI.post(user, %{status: "super hot take"}) - {:ok, _} = CommonAPI.add_mute(user2, activity) - assert_receive {:render_with_user, _, _, ^activity} - assert Streamer.filtered_by_user?(user2, activity) + describe "muted threads" do + test "it filters posts from muted threads" do + user = insert(:user) + %{user: user2, token: user2_token} = oauth_access(["read"]) + Streamer.get_topic_and_add_socket("user", user2, user2_token) + + {:ok, user2, user, _activity} = CommonAPI.follow(user2, user) + {:ok, activity} = CommonAPI.post(user, %{status: "super hot take"}) + {:ok, _} = CommonAPI.add_mute(user2, activity) + + assert_receive {:render_with_user, _, _, ^activity} + assert Streamer.filtered_by_user?(user2, activity) + end end describe "direct streams" do - setup do - :ok - end + setup do: oauth_access(["read"]) - test "it sends conversation update to the 'direct' stream", %{} do - user = insert(:user) + test "it sends conversation update to the 'direct' stream", %{user: user, token: oauth_token} do another_user = insert(:user) - Streamer.get_topic_and_add_socket("direct", user) + Streamer.get_topic_and_add_socket("direct", user, oauth_token) {:ok, _create_activity} = CommonAPI.post(another_user, %{ @@ -602,11 +696,11 @@ test "it sends conversation update to the 'direct' stream", %{} do assert last_status["pleroma"]["direct_conversation_id"] == participation.id end - test "it doesn't send conversation update to the 'direct' stream when the last message in the conversation is deleted" do - user = insert(:user) + test "it doesn't send conversation update to the 'direct' stream when the last message in the conversation is deleted", + %{user: user, token: oauth_token} do another_user = insert(:user) - Streamer.get_topic_and_add_socket("direct", user) + Streamer.get_topic_and_add_socket("direct", user, oauth_token) {:ok, create_activity} = CommonAPI.post(another_user, %{ @@ -629,10 +723,12 @@ test "it doesn't send conversation update to the 'direct' stream when the last m refute_receive _ end - test "it sends conversation update to the 'direct' stream when a message is deleted" do - user = insert(:user) + test "it sends conversation update to the 'direct' stream when a message is deleted", %{ + user: user, + token: oauth_token + } do another_user = insert(:user) - Streamer.get_topic_and_add_socket("direct", user) + Streamer.get_topic_and_add_socket("direct", user, oauth_token) {:ok, create_activity} = CommonAPI.post(another_user, %{ diff --git a/test/web/twitter_api/password_controller_test.exs b/test/web/twitter_api/password_controller_test.exs index 231a46c67..a5e9e2178 100644 --- a/test/web/twitter_api/password_controller_test.exs +++ b/test/web/twitter_api/password_controller_test.exs @@ -37,7 +37,7 @@ test "it shows password reset form", %{conn: conn} do test "it returns HTTP 200", %{conn: conn} do user = insert(:user) {:ok, token} = PasswordResetToken.create_token(user) - {:ok, _access_token} = Token.create_token(insert(:oauth_app), user, %{}) + {:ok, _access_token} = Token.create(insert(:oauth_app), user, %{}) params = %{ "password" => "test", @@ -62,7 +62,7 @@ test "it sets password_reset_pending to false", %{conn: conn} do user = insert(:user, password_reset_pending: true) {:ok, token} = PasswordResetToken.create_token(user) - {:ok, _access_token} = Token.create_token(insert(:oauth_app), user, %{}) + {:ok, _access_token} = Token.create(insert(:oauth_app), user, %{}) params = %{ "password" => "test", diff --git a/test/web/twitter_api/remote_follow_controller_test.exs b/test/web/twitter_api/remote_follow_controller_test.exs index f7e54c26a..3852c7ce9 100644 --- a/test/web/twitter_api/remote_follow_controller_test.exs +++ b/test/web/twitter_api/remote_follow_controller_test.exs @@ -227,7 +227,7 @@ test "follows", %{conn: conn} do } ) - {:ok, %{token: token}} = MFA.Token.create_token(user) + {:ok, %{token: token}} = MFA.Token.create(user) user2 = insert(:user) otp_token = TOTP.generate_token(otp_secret) @@ -256,7 +256,7 @@ test "returns error when auth code is incorrect", %{conn: conn} do } ) - {:ok, %{token: token}} = MFA.Token.create_token(user) + {:ok, %{token: token}} = MFA.Token.create(user) user2 = insert(:user) otp_token = TOTP.generate_token(TOTP.generate_secret()) diff --git a/test/web/twitter_api/util_controller_test.exs b/test/web/twitter_api/util_controller_test.exs index 354d77b56..60f2fb052 100644 --- a/test/web/twitter_api/util_controller_test.exs +++ b/test/web/twitter_api/util_controller_test.exs @@ -21,170 +21,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do setup do: clear_config([:instance]) setup do: clear_config([:frontend_configurations, :pleroma_fe]) - describe "POST /api/pleroma/follow_import" do - setup do: oauth_access(["follow"]) - - test "it returns HTTP 200", %{conn: conn} do - user2 = insert(:user) - - response = - conn - |> post("/api/pleroma/follow_import", %{"list" => "#{user2.ap_id}"}) - |> json_response(:ok) - - assert response == "job started" - end - - test "it imports follow lists from file", %{user: user1, conn: conn} do - user2 = insert(:user) - - with_mocks([ - {File, [], - read!: fn "follow_list.txt" -> - "Account address,Show boosts\n#{user2.ap_id},true" - end} - ]) do - response = - conn - |> post("/api/pleroma/follow_import", %{"list" => %Plug.Upload{path: "follow_list.txt"}}) - |> json_response(:ok) - - assert response == "job started" - - assert ObanHelpers.member?( - %{ - "op" => "follow_import", - "follower_id" => user1.id, - "followed_identifiers" => [user2.ap_id] - }, - all_enqueued(worker: Pleroma.Workers.BackgroundWorker) - ) - end - end - - test "it imports new-style mastodon follow lists", %{conn: conn} do - user2 = insert(:user) - - response = - conn - |> post("/api/pleroma/follow_import", %{ - "list" => "Account address,Show boosts\n#{user2.ap_id},true" - }) - |> json_response(:ok) - - assert response == "job started" - end - - test "requires 'follow' or 'write:follows' permissions" do - token1 = insert(:oauth_token, scopes: ["read", "write"]) - token2 = insert(:oauth_token, scopes: ["follow"]) - token3 = insert(:oauth_token, scopes: ["something"]) - another_user = insert(:user) - - for token <- [token1, token2, token3] do - conn = - build_conn() - |> put_req_header("authorization", "Bearer #{token.token}") - |> post("/api/pleroma/follow_import", %{"list" => "#{another_user.ap_id}"}) - - if token == token3 do - assert %{"error" => "Insufficient permissions: follow | write:follows."} == - json_response(conn, 403) - else - assert json_response(conn, 200) - end - end - end - - test "it imports follows with different nickname variations", %{conn: conn} do - [user2, user3, user4, user5, user6] = insert_list(5, :user) - - identifiers = - [ - user2.ap_id, - user3.nickname, - " ", - "@" <> user4.nickname, - user5.nickname <> "@localhost", - "@" <> user6.nickname <> "@localhost" - ] - |> Enum.join("\n") - - response = - conn - |> post("/api/pleroma/follow_import", %{"list" => identifiers}) - |> json_response(:ok) - - assert response == "job started" - assert [{:ok, job_result}] = ObanHelpers.perform_all() - assert job_result == [user2, user3, user4, user5, user6] - end - end - - describe "POST /api/pleroma/blocks_import" do - # Note: "follow" or "write:blocks" permission is required - setup do: oauth_access(["write:blocks"]) - - test "it returns HTTP 200", %{conn: conn} do - user2 = insert(:user) - - response = - conn - |> post("/api/pleroma/blocks_import", %{"list" => "#{user2.ap_id}"}) - |> json_response(:ok) - - assert response == "job started" - end - - test "it imports blocks users from file", %{user: user1, conn: conn} do - user2 = insert(:user) - user3 = insert(:user) - - with_mocks([ - {File, [], read!: fn "blocks_list.txt" -> "#{user2.ap_id} #{user3.ap_id}" end} - ]) do - response = - conn - |> post("/api/pleroma/blocks_import", %{"list" => %Plug.Upload{path: "blocks_list.txt"}}) - |> json_response(:ok) - - assert response == "job started" - - assert ObanHelpers.member?( - %{ - "op" => "blocks_import", - "blocker_id" => user1.id, - "blocked_identifiers" => [user2.ap_id, user3.ap_id] - }, - all_enqueued(worker: Pleroma.Workers.BackgroundWorker) - ) - end - end - - test "it imports blocks with different nickname variations", %{conn: conn} do - [user2, user3, user4, user5, user6] = insert_list(5, :user) - - identifiers = - [ - user2.ap_id, - user3.nickname, - "@" <> user4.nickname, - user5.nickname <> "@localhost", - "@" <> user6.nickname <> "@localhost" - ] - |> Enum.join(" ") - - response = - conn - |> post("/api/pleroma/blocks_import", %{"list" => identifiers}) - |> json_response(:ok) - - assert response == "job started" - assert [{:ok, job_result}] = ObanHelpers.perform_all() - assert job_result == [user2, user3, user4, user5, user6] - end - end - describe "PUT /api/pleroma/notification_settings" do setup do: oauth_access(["write:accounts"]) @@ -594,7 +430,7 @@ test "with proper permissions and valid password", %{conn: conn, user: user} do user = User.get_by_id(user.id) assert user.deactivated == true assert user.name == nil - assert user.bio == nil + assert user.bio == "" assert user.password_hash == nil end end diff --git a/test/workers/cron/clear_oauth_token_worker_test.exs b/test/workers/cron/clear_oauth_token_worker_test.exs deleted file mode 100644 index 67836f34f..000000000 --- a/test/workers/cron/clear_oauth_token_worker_test.exs +++ /dev/null @@ -1,22 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Workers.Cron.ClearOauthTokenWorkerTest do - use Pleroma.DataCase - - import Pleroma.Factory - alias Pleroma.Workers.Cron.ClearOauthTokenWorker - - setup do: clear_config([:oauth2, :clean_expired_tokens]) - - test "deletes expired tokens" do - insert(:oauth_token, - valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), -60 * 10) - ) - - Pleroma.Config.put([:oauth2, :clean_expired_tokens], true) - ClearOauthTokenWorker.perform(%Oban.Job{}) - assert Pleroma.Repo.all(Pleroma.Web.OAuth.Token) == [] - end -end diff --git a/test/workers/cron/purge_expired_activities_worker_test.exs b/test/workers/cron/purge_expired_activities_worker_test.exs deleted file mode 100644 index d1acd9ae6..000000000 --- a/test/workers/cron/purge_expired_activities_worker_test.exs +++ /dev/null @@ -1,84 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Workers.Cron.PurgeExpiredActivitiesWorkerTest do - use Pleroma.DataCase - - alias Pleroma.ActivityExpiration - alias Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker - - import Pleroma.Factory - import ExUnit.CaptureLog - - setup do - clear_config([ActivityExpiration, :enabled]) - end - - test "deletes an expiration activity" do - Pleroma.Config.put([ActivityExpiration, :enabled], true) - activity = insert(:note_activity) - - naive_datetime = - NaiveDateTime.add( - NaiveDateTime.utc_now(), - -:timer.minutes(2), - :millisecond - ) - - expiration = - insert( - :expiration_in_the_past, - %{activity_id: activity.id, scheduled_at: naive_datetime} - ) - - Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker.perform(%Oban.Job{}) - - refute Pleroma.Repo.get(Pleroma.Activity, activity.id) - refute Pleroma.Repo.get(Pleroma.ActivityExpiration, expiration.id) - end - - test "works with ActivityExpirationPolicy" do - Pleroma.Config.put([ActivityExpiration, :enabled], true) - - clear_config([:mrf, :policies], Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy) - - user = insert(:user) - - days = Pleroma.Config.get([:mrf_activity_expiration, :days], 365) - - {:ok, %{id: id} = activity} = Pleroma.Web.CommonAPI.post(user, %{status: "cofe"}) - - past_date = - NaiveDateTime.utc_now() |> Timex.shift(days: -days) |> NaiveDateTime.truncate(:second) - - activity - |> Repo.preload(:expiration) - |> Map.get(:expiration) - |> Ecto.Changeset.change(%{scheduled_at: past_date}) - |> Repo.update!() - - Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker.perform(%Oban.Job{}) - - assert [%{data: %{"type" => "Delete", "deleted_activity_id" => ^id}}] = - Pleroma.Repo.all(Pleroma.Activity) - end - - describe "delete_activity/1" do - test "adds log message if activity isn't find" do - assert capture_log([level: :error], fn -> - PurgeExpiredActivitiesWorker.delete_activity(%ActivityExpiration{ - activity_id: "test-activity" - }) - end) =~ "Couldn't delete expired activity: not found activity" - end - - test "adds log message if actor isn't find" do - assert capture_log([level: :error], fn -> - PurgeExpiredActivitiesWorker.delete_activity(%ActivityExpiration{ - activity_id: "test-activity" - }) - end) =~ "Couldn't delete expired activity: not found activity" - end - end -end diff --git a/test/workers/purge_expired_activity_test.exs b/test/workers/purge_expired_activity_test.exs new file mode 100644 index 000000000..b5938776d --- /dev/null +++ b/test/workers/purge_expired_activity_test.exs @@ -0,0 +1,59 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.PurgeExpiredActivityTest do + use Pleroma.DataCase, async: true + use Oban.Testing, repo: Pleroma.Repo + + import Pleroma.Factory + + alias Pleroma.Workers.PurgeExpiredActivity + + test "enqueue job" do + activity = insert(:note_activity) + + assert {:ok, _} = + PurgeExpiredActivity.enqueue(%{ + activity_id: activity.id, + expires_at: DateTime.add(DateTime.utc_now(), 3601) + }) + + assert_enqueued( + worker: Pleroma.Workers.PurgeExpiredActivity, + args: %{activity_id: activity.id} + ) + + assert {:ok, _} = + perform_job(Pleroma.Workers.PurgeExpiredActivity, %{activity_id: activity.id}) + + assert %Oban.Job{} = Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id) + end + + test "error if user was not found" do + activity = insert(:note_activity) + + assert {:ok, _} = + PurgeExpiredActivity.enqueue(%{ + activity_id: activity.id, + expires_at: DateTime.add(DateTime.utc_now(), 3601) + }) + + user = Pleroma.User.get_by_ap_id(activity.actor) + Pleroma.Repo.delete(user) + + assert {:error, :user_not_found} = + perform_job(Pleroma.Workers.PurgeExpiredActivity, %{activity_id: activity.id}) + end + + test "error if actiivity was not found" do + assert {:ok, _} = + PurgeExpiredActivity.enqueue(%{ + activity_id: "some_id", + expires_at: DateTime.add(DateTime.utc_now(), 3601) + }) + + assert {:error, :activity_not_found} = + perform_job(Pleroma.Workers.PurgeExpiredActivity, %{activity_id: "some_if"}) + end +end diff --git a/test/workers/purge_expired_token_test.exs b/test/workers/purge_expired_token_test.exs new file mode 100644 index 000000000..fb7708c3f --- /dev/null +++ b/test/workers/purge_expired_token_test.exs @@ -0,0 +1,51 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.PurgeExpiredTokenTest do + use Pleroma.DataCase, async: true + use Oban.Testing, repo: Pleroma.Repo + + import Pleroma.Factory + + setup do: clear_config([:oauth2, :clean_expired_tokens], true) + + test "purges expired oauth token" do + user = insert(:user) + app = insert(:oauth_app) + + {:ok, %{id: id}} = Pleroma.Web.OAuth.Token.create(app, user) + + assert_enqueued( + worker: Pleroma.Workers.PurgeExpiredToken, + args: %{token_id: id, mod: Pleroma.Web.OAuth.Token} + ) + + assert {:ok, %{id: ^id}} = + perform_job(Pleroma.Workers.PurgeExpiredToken, %{ + token_id: id, + mod: Pleroma.Web.OAuth.Token + }) + + assert Repo.aggregate(Pleroma.Web.OAuth.Token, :count, :id) == 0 + end + + test "purges expired mfa token" do + authorization = insert(:oauth_authorization) + + {:ok, %{id: id}} = Pleroma.MFA.Token.create(authorization.user, authorization) + + assert_enqueued( + worker: Pleroma.Workers.PurgeExpiredToken, + args: %{token_id: id, mod: Pleroma.MFA.Token} + ) + + assert {:ok, %{id: ^id}} = + perform_job(Pleroma.Workers.PurgeExpiredToken, %{ + token_id: id, + mod: Pleroma.MFA.Token + }) + + assert Repo.aggregate(Pleroma.MFA.Token, :count, :id) == 0 + end +end