File indexing completed on 2024-05-12 05:04:08

0001 // SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
0002 // SPDX-License-Identifier: GPL-3.0-or-later
0003 
0004 #pragma once
0005 
0006 #include "accountconfig.h"
0007 #include "adminaccountinfo.h"
0008 #include "customemoji.h"
0009 #include "identity.h"
0010 #include "preferences.h"
0011 #include "reportinfo.h"
0012 
0013 #include <QJsonDocument>
0014 #include <QJsonObject>
0015 
0016 class Attachment;
0017 class Notification;
0018 class QNetworkReply;
0019 class QHttpMultiPart;
0020 class QFile;
0021 class Preferences;
0022 
0023 /// Represents an account, which could possibly be real or a mock for testing.
0024 /// Also handles most of the API work, and account actions.
0025 class AbstractAccount : public QObject
0026 {
0027     Q_OBJECT
0028 
0029     Q_PROPERTY(QString username READ username WRITE setUsername NOTIFY usernameChanged)
0030 
0031     Q_PROPERTY(QString instanceUri READ instanceUri CONSTANT)
0032     Q_PROPERTY(int maxPostLength READ maxPostLength NOTIFY fetchedInstanceMetadata)
0033     Q_PROPERTY(int maxPollOptions READ maxPollOptions NOTIFY fetchedInstanceMetadata)
0034     Q_PROPERTY(bool supportsLocalVisibility READ supportsLocalVisibility NOTIFY fetchedInstanceMetadata)
0035     Q_PROPERTY(QString instanceName READ instanceName NOTIFY fetchedInstanceMetadata)
0036     Q_PROPERTY(QUrl authorizeUrl READ getAuthorizeUrl NOTIFY registered)
0037     Q_PROPERTY(Identity *identity READ identity NOTIFY identityChanged)
0038     Q_PROPERTY(Preferences *preferences READ preferences CONSTANT)
0039     Q_PROPERTY(bool hasFollowRequests READ hasFollowRequests NOTIFY hasFollowRequestsChanged)
0040     Q_PROPERTY(AccountConfig *config READ config NOTIFY fetchedInstanceMetadata)
0041     Q_PROPERTY(bool registrationsOpen READ registrationsOpen NOTIFY fetchedInstanceMetadata)
0042     Q_PROPERTY(QString registrationMessage READ registrationMessage NOTIFY fetchedInstanceMetadata)
0043 
0044 public:
0045     AbstractAccount(QObject *parent, const QString &instanceUri);
0046     AbstractAccount(QObject *parent);
0047 
0048     /// Register the application on the server
0049     /// \param appName The name of the application displayed to other clients
0050     /// \param website The application's website
0051     /// \param additionalScopes Any additional scopes to request
0052     void registerApplication(const QString &appName, const QString &website, const QString &additionalScopes = {});
0053 
0054     /// Register a new account on the server
0055     /// \param username The account's username
0056     /// \param email The account's email address
0057     /// \param password The account's password
0058     /// \param agreement Whether the user agrees the server's rules and terms
0059     /// \param locale The user's locale
0060     /// \param reason If the server requires approval, a reason given to register
0061     Q_INVOKABLE void
0062     registerAccount(const QString &username, const QString &email, const QString &password, bool agreement, const QString &locale, const QString &reason);
0063 
0064     /// Check if the application is registered
0065     /// \see registerApplication
0066     bool isRegistered() const;
0067 
0068     /// Check if this account's instance has registrations open.
0069     bool registrationsOpen() const;
0070 
0071     /// Check the reason why registrations are disabled,
0072     QString registrationMessage() const;
0073 
0074     /// Get the oauth2 authorization url
0075     Q_INVOKABLE QUrl getAuthorizeUrl() const;
0076 
0077     /// Sets the access token
0078     /// \param token The access token
0079     void setAccessToken(const QString &token);
0080 
0081     /// Get the oauth2 token url
0082     QUrl getTokenUrl() const;
0083 
0084     /// Set the oauth2 token
0085     /// \param authcode The oauth2 authentication code
0086     Q_INVOKABLE void setToken(const QString &authcode);
0087 
0088     /// Check if the account has a token set
0089     /// \see setToken
0090     bool haveToken() const;
0091 
0092     /// Check if the account has a username yet
0093     bool hasName() const;
0094 
0095     /// Check if the account has an instance uri set
0096     bool hasInstanceUrl() const;
0097 
0098     /// Verifies the token with the instance and if successful, loads identity information for the account
0099     Q_INVOKABLE virtual void validateToken(bool newAccount = false) = 0;
0100 
0101     /// Return local account preferences
0102     AccountConfig *config();
0103 
0104     /// Returns the server-side preferences
0105     Preferences *preferences() const;
0106 
0107     /// Return the username of the account
0108     /// \see setUsername
0109     QString username() const;
0110 
0111     /// Sets the username for the account
0112     /// \param name The new username
0113     void setUsername(const QString &name);
0114 
0115     /// Fetches instance-specific metadata like max post length, allowed content types, etc
0116     void fetchInstanceMetadata();
0117 
0118     /// Fetches instance-specific custom emojis
0119     void fetchCustomEmojis();
0120 
0121     /// Returns the custom emojis that's accessible for this account
0122     QList<CustomEmoji> customEmojis() const;
0123 
0124     /// Returns the instance URI
0125     /// \see setInstanceUri
0126     QString instanceUri() const;
0127 
0128     /// Sets the instance URI for the account
0129     /// \param instance_uri The new instance URI
0130     void setInstanceUri(const QString &instance_uri);
0131 
0132     /// Returns the max allowable length of posts in characters
0133     size_t maxPostLength() const;
0134 
0135     /// Returns the maximum number of poll options
0136     size_t maxPollOptions() const;
0137 
0138     /// Certain servers (like Pleroma/Akkoma) support an additional visibility type, called Local
0139     bool supportsLocalVisibility() const;
0140 
0141     /// Returns the amount of characters that URLs take
0142     /// Any URL that appears in a post will only be counted by this limit
0143     size_t charactersReservedPerUrl() const;
0144 
0145     /// Returns the title set by the instance
0146     QString instanceName() const;
0147 
0148     /// Returns the identity of the account
0149     Identity *identity();
0150 
0151     /// Looks up an identity specific to this account (like relationships) using an accountId
0152     /// and optionally a JSON document containing identity information.
0153     /// \param accountId The account ID
0154     /// \param doc doc Optionally provide an existing account JSON, if you were already given some in another request
0155     std::shared_ptr<Identity> identityLookup(const QString &accountId, const QJsonObject &doc);
0156 
0157     /// Checks if the accountId exists in the account's identity cache
0158     /// \param accountId The account ID to look up
0159     bool identityCached(const QString &accountId) const;
0160 
0161     /// Get identity of the admin::account
0162     /// \param accountId The account ID to look up
0163     /// \param doc doc Optionally provide an existing account JSON, if you were already given some in another request
0164     std::shared_ptr<AdminAccountInfo> adminIdentityLookup(const QString &accountId, const QJsonObject &doc);
0165 
0166     /// Vanilla pointer identity
0167     AdminAccountInfo *adminIdentityLookupWithVanillaPointer(const QString &accountId, const QJsonObject &doc);
0168 
0169     /// Populating with Admin::Report
0170     std::shared_ptr<ReportInfo> reportInfoLookup(const QString &reportId, const QJsonObject &doc);
0171 
0172     /// Invalidates the account
0173     void invalidate();
0174 
0175     /// Favorite a post
0176     /// \param p The post object to mutate
0177     /// \see unfavorite
0178     void favorite(Post *p);
0179 
0180     /// Unfavorite a post
0181     /// \param p The post object to mutate
0182     /// \see favorite
0183     void unfavorite(Post *p);
0184 
0185     /// Boost (also known as reblog, or repeat) a post
0186     /// \param p The post object to mutate
0187     /// \see unrepeat
0188     void repeat(Post *p);
0189 
0190     /// Unboost a post
0191     /// \param p The post object to mutate
0192     /// \see repeat
0193     void unrepeat(Post *p);
0194 
0195     /// Bookmark a post
0196     /// \param p The post object to mutate
0197     /// \see unbookmark
0198     void bookmark(Post *p);
0199 
0200     /// Unbookmark a post
0201     /// \param p The post object to mutate
0202     /// \see bookmark
0203     void unbookmark(Post *p);
0204 
0205     /// Pin a post
0206     /// \param p The post object to mutate
0207     /// \see unpin
0208     void pin(Post *p);
0209 
0210     /// Unpin a post
0211     /// \param p The post object to mutate
0212     /// \see pin
0213     void unpin(Post *p);
0214 
0215     /// Returns a streaming url for \p stream
0216     /// \param stream The requested stream (e.g. user)
0217     QUrl streamingUrl(const QString &stream);
0218 
0219     /// Invalidates a post
0220     /// \param p The post object to mutate
0221     void invalidatePost(Post *p);
0222 
0223     /// Types of formatting that we may use is determined primarily by the server metadata, this is a simple enough
0224     /// way to determine what formats are accepted.
0225     enum AllowedContentType { PlainText = 1 << 0, Markdown = 1 << 1, Html = 1 << 2, BBCode = 1 << 3 };
0226 
0227     /// Return the allowed content types of the account's instance
0228     AllowedContentType allowedContentTypes() const
0229     {
0230         return m_allowedContentTypes;
0231     }
0232 
0233     /// Return a well-formed URL of an API path
0234     /// \param path The base API path
0235     QUrl apiUrl(const QString &path) const;
0236 
0237     /// Make an HTTP GET request to the server
0238     /// \param url The url of the request
0239     /// \param authenticated Whether the request should be authentificated
0240     /// \param parent The parent object that calls get() or the callback belongs to
0241     /// \param callback The callback that should be executed if the request is successful
0242     /// \param errorCallback The callback that should be executed if the request is not successful
0243     virtual void get(const QUrl &url,
0244                      bool authenticated,
0245                      QObject *parent,
0246                      std::function<void(QNetworkReply *)> callback,
0247                      std::function<void(QNetworkReply *)> errorCallback = nullptr) = 0;
0248 
0249     /// Make an HTTP POST request to the server
0250     /// \param url The url of the request
0251     /// \param doc The request body as JSON
0252     /// \param parent The parent object that calls get() or the callback belongs to
0253     /// \param callback The callback that should be executed if the request is successful
0254     /// \param errorCallback The callback that should be executed if the request is not successful
0255     virtual void post(const QUrl &url,
0256                       const QJsonDocument &doc,
0257                       bool authenticated,
0258                       QObject *parent,
0259                       std::function<void(QNetworkReply *)> callback,
0260                       std::function<void(QNetworkReply *)> errorCallback = nullptr,
0261                       QHash<QByteArray, QByteArray> headers = {}) = 0;
0262 
0263     /// Make an HTTP POST request to the server
0264     /// \param url The url of the request
0265     /// \param doc The request body as form-data
0266     /// \param authenticated Whether the request should be authenticated
0267     /// \param parent The parent object that calls get() or the callback belongs to
0268     /// \param callback The callback that should be executed if the request is successful
0269     /// \param errorCallback The callback that should be executed if the request is not successful
0270     virtual void post(const QUrl &url,
0271                       const QUrlQuery &formdata,
0272                       bool authenticated,
0273                       QObject *parent,
0274                       std::function<void(QNetworkReply *)> callback,
0275                       std::function<void(QNetworkReply *)> errorCallback = nullptr) = 0;
0276 
0277     /// Make an HTTP POST request to the server
0278     /// \param url The url of the request
0279     /// \param message The request body as multi-part data
0280     /// \param authenticated Whether the request should be authenticated
0281     /// \param parent The parent object that calls get() or the callback belongs to
0282     /// \param callback The callback that should be executed if the request is successful
0283     virtual QNetworkReply *post(const QUrl &url, QHttpMultiPart *message, bool authenticated, QObject *parent, std::function<void(QNetworkReply *)> callback) = 0;
0284 
0285     /// Make an HTTP PUT request to the server
0286     /// \param url The url of the request
0287     /// \param doc The request body as JSON
0288     /// \param authenticated Whether the request should be authenticated
0289     /// \param parent The parent object that calls get() or the callback belongs to
0290     /// \param callback The callback that should be executed if the request is successful
0291     virtual void put(const QUrl &url, const QJsonDocument &doc, bool authenticated, QObject *parent, std::function<void(QNetworkReply *)> callback) = 0;
0292 
0293     /// Make an HTTP PUT request to the server
0294     /// \param url The url of the request
0295     /// \param doc The request body as form-data
0296     /// \param authenticated Whether the request should be authenticated
0297     /// \param parent The parent object that calls get() or the callback belongs to
0298     /// \param callback The callback that should be executed if the request is successful
0299     virtual void put(const QUrl &url, const QUrlQuery &doc, bool authenticated, QObject *parent, std::function<void(QNetworkReply *)> callback) = 0;
0300 
0301     /// Make an HTTP PATCH request to the server
0302     /// \param url The url of the request
0303     /// \param message The request body as multi-part data
0304     /// \param authenticated Whether the request should be authenticated
0305     /// \param parent The parent object that calls get() or the callback belongs to
0306     /// \param callback The callback that should be executed if the request is successful
0307     virtual void patch(const QUrl &url, QHttpMultiPart *multiPart, bool authenticated, QObject *parent, std::function<void(QNetworkReply *)>) = 0;
0308 
0309     /// Make an HTTP DELETE request to the server
0310     /// \param url The url of the request
0311     /// \param authenticated Whether the request should be authenticated
0312     /// \param parent The parent object that calls get() or the callback belongs to
0313     /// \param callback The callback that should be executed if the request is successful
0314     virtual void deleteResource(const QUrl &url, bool authenticated, QObject *parent, std::function<void(QNetworkReply *)> callback) = 0;
0315 
0316     /// Upload a file
0317     /// \param url The name of the file to upload
0318     /// \param callback The callback that should be executed if the request is successful
0319     virtual QNetworkReply *upload(const QUrl &filename, std::function<void(QNetworkReply *)> callback) = 0;
0320 
0321     /// Write account to settings to disk
0322     virtual void writeToSettings() = 0;
0323 
0324     /// Read account from settings to disk
0325     virtual void buildFromSettings() = 0;
0326 
0327     /// Check if the account has any follow requests
0328     virtual bool hasFollowRequests() const = 0;
0329 
0330     /// Check against the server for any new follow requests
0331     virtual void checkForFollowRequests() = 0;
0332 
0333     /// Update the push notification rules
0334     virtual void updatePushNotifications() = 0;
0335 
0336     /// Follow the given account. Can also be used to update whether to show reblogs or enable notifications.
0337     /// @param identity The account to follow
0338     /// @param reblogs Receive this account's reblogs in home timeline? Defaults to true.
0339     /// @param notify Receive notifications when this account posts a status? Defaults to false.
0340     Q_INVOKABLE void followAccount(Identity *identity, bool reblogs = true, bool notify = false);
0341 
0342     /// Unfollow the given account.
0343     /// @param identity The account to unfollow
0344     Q_INVOKABLE void unfollowAccount(Identity *identity);
0345 
0346     /// Block the given account.
0347     /// @param identity The account to block
0348     Q_INVOKABLE void blockAccount(Identity *identity);
0349 
0350     /// Unblock the given account.
0351     /// @param identity The account to unblock
0352     Q_INVOKABLE void unblockAccount(Identity *identity);
0353 
0354     /// Mute the given account.
0355     /// @param identity The account to mute
0356     /// @param notifications Whether notifications should also be muted, by default true
0357     /// @param duration How long the mute should last, in seconds. Defaults to 0 (indefinite).
0358     Q_INVOKABLE void muteAccount(Identity *identity, bool notifications = true, int duration = 0);
0359 
0360     /// Unmute the given account.
0361     /// @param identity The account to unmute
0362     Q_INVOKABLE void unmuteAccount(Identity *identity);
0363 
0364     /// Add the given account to the user's featured profiles.
0365     /// @param identity The account to feature
0366     Q_INVOKABLE void featureAccount(Identity *identity);
0367 
0368     /// Remove the given account from the user's featured profiles.
0369     /// @param identity The account to unfeature
0370     Q_INVOKABLE void unfeatureAccount(Identity *identity);
0371 
0372     /// Sets a private note on a user.
0373     /// @param identity The account to annotate
0374     /// @param note The note to add to the account. Leave empty to remove the existing note.
0375     Q_INVOKABLE void addNote(Identity *identity, const QString &note);
0376 
0377     Q_INVOKABLE void mutateRemotePost(const QString &url, const QString &verb);
0378 
0379     /// Fetches OEmbed data for a post.
0380     /// @param QString url the url of the post to fetch.
0381     Q_INVOKABLE void fetchOEmbed(const QString &id, Identity *identity);
0382 
0383     /// Returns the preferred settings group name for this Account which includes the username and the instance uri.
0384     QString settingsGroupName() const;
0385 
0386     /// Returns the preferred key name for the client secret
0387     QString clientSecretKey() const;
0388 
0389     /// Returns the preferred key name for the access token
0390     QString accessTokenKey() const;
0391 
0392     /// Type of account action
0393     enum AccountAction {
0394         Follow, ///< Follow the account
0395         Unfollow, ///< Unfollow the account
0396         Block, ///< Block the account
0397         Unblock, ///< Unlock the account
0398         Mute, ///< Mute the account
0399         Unmute, ///< Unmute the account
0400         Feature, ///< Feature the account
0401         Unfeature, ///< Unfeature the account
0402         Note, ///< Update the note for the account
0403     };
0404 
0405     /// Type of streaming event
0406     enum StreamingEventType {
0407         UpdateEvent, ///< A new Status has appeared.
0408         DeleteEvent, ///< A status has been deleted.
0409         NotificationEvent, ///< A new notification has appeared.
0410         FiltersChangedEvent, ///< Keyword filters have been changed.
0411         ConversationEvent, ///< A direct conversation has been updated.
0412         AnnouncementEvent, ///< An announcement has been published.
0413         AnnouncementRedactedEvent, ///< An announcement has received an emoji reaction.
0414         AnnouncementDeletedEvent, ///< An announcement has been deleted.
0415         StatusUpdatedEvent, ///< A Status has been edited.
0416         EncryptedMessageChangedEvent, ///< An encrypted message has been received.
0417     };
0418 
0419 Q_SIGNALS:
0420     /// Emitted when the account is authenticated
0421     /// \param Whether the authentication was successful
0422     /// \param errorMessage If not successful, a localized error message
0423     /// \see validateToken
0424     void authenticated(bool successful, const QString &errorMessage);
0425 
0426     /// Emitted when the application is successfully registered to the server
0427     /// \see registerApplication
0428     void registered();
0429 
0430     /// Emitted when the account's own identity has been updated
0431     void identityChanged();
0432 
0433     /// Emitted when the requested timeline has been fetched
0434     /// \param The name of the timeline that was fetched
0435     /// \param posts The list of posts fetched
0436     void fetchedTimeline(const QString &timelineName, QList<Post *> posts);
0437 
0438     /// Emitted when th=e account has been invalidated
0439     /// \see invalidate
0440     void invalidated();
0441 
0442     /// Emitted when the account's username has been changed
0443     /// \see setUsername
0444     void usernameChanged();
0445 
0446     /// Emitted when the instance metadata has been fetched
0447     /// \see fetchInstanceMetadata
0448     void fetchedInstanceMetadata();
0449 
0450     /// Emitted when the custom emojis has been fetched
0451     /// \see fetchCustomEmojis
0452     void fetchedCustomEmojis();
0453 
0454     /// Emitted when a post has been invalidated
0455     /// \param p The post that was invalidated
0456     /// \see invalidatePost
0457     void invalidatedPost(Post *p);
0458 
0459     /// Emitted when a notification has been received
0460     /// \param n A shared handle to the new notification
0461     void notification(std::shared_ptr<Notification> n);
0462 
0463     /// Emitted when an error occurred when performing an API request
0464     /// \param errorMessage A localized error message
0465     void errorOccured(const QString &errorMessage);
0466 
0467     /// Emitted when a streaming event has been received
0468     /// \param eventType The type of streaming event
0469     /// \param payload The payload for the streaming event
0470     void streamingEvent(AbstractAccount::StreamingEventType eventType, const QByteArray &payload);
0471 
0472     /// Emitted when the account has follow requests
0473     void hasFollowRequestsChanged();
0474 
0475     /// Emitted when a registration error has occurred.
0476     /// \param json The JSON body for further processing
0477     void registrationError(const QString &json);
0478 
0479     /// Emitted when the oembed data is successfully returned.
0480     /// \see fetchOEmbed
0481     void fetchedOEmbed(const QString &html);
0482 
0483 protected:
0484     QString m_name;
0485     QString m_instance_uri;
0486     QString m_token;
0487     QString m_client_id;
0488     QString m_client_secret;
0489     size_t m_maxPostLength;
0490     size_t m_maxPollOptions;
0491     bool m_supportsLocalVisibility;
0492     size_t m_charactersReservedPerUrl;
0493     QString m_instance_name;
0494     QJsonArray m_instance_rules;
0495     std::shared_ptr<Identity> m_identity;
0496     std::shared_ptr<AdminAccountInfo> m_adminIdentity;
0497     AdminAccountInfo *m_adminIdentityWithVanillaPointer;
0498     AdminAccountInfo *m_federationIdentity;
0499     std::shared_ptr<ReportInfo> m_reportInfo;
0500     AllowedContentType m_allowedContentTypes;
0501     Preferences *m_preferences = nullptr;
0502     QList<CustomEmoji> m_customEmojis;
0503     QString m_additionalScopes;
0504     AccountConfig *m_config = nullptr;
0505     bool m_registrationsOpen = false;
0506     QString m_registrationMessage;
0507     bool m_hasFollowRequests = false;
0508 
0509     // OAuth authorization
0510     QUrlQuery buildOAuthQuery() const;
0511 
0512     // updates and notifications
0513     void handleUpdate(const QJsonDocument &doc, const QString &target);
0514     void handleNotification(const QJsonDocument &doc);
0515 
0516     void mutatePost(const QString &id, const QString &verb, bool deliver_home = false);
0517     QMap<QString, std::shared_ptr<Identity>> m_identityCache;
0518     QMap<QString, std::shared_ptr<AdminAccountInfo>> m_adminIdentityCache;
0519     QMap<QString, AdminAccountInfo *> m_adminIdentityCacheWithVanillaPointer;
0520     QMap<QString, std::shared_ptr<ReportInfo>> m_reportInfoCache;
0521 
0522     void executeAction(Identity *i, AccountAction accountAction, const QJsonObject &extraArguments = {});
0523 };