File indexing completed on 2024-12-08 12:55:17

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 "admin/adminaccountinfo.h"
0007 #include "admin/federationinfo.h"
0008 #include "identity.h"
0009 #include "preferences.h"
0010 #include "timeline/post.h"
0011 
0012 #include <QJsonDocument>
0013 #include <QJsonObject>
0014 #include <QObject>
0015 
0016 class Attachment;
0017 class Notification;
0018 class QNetworkReply;
0019 class QHttpMultiPart;
0020 class QFile;
0021 class Preferences;
0022 class AccountConfig;
0023 
0024 /// Represents an account, which could possibly be real or a mock for testing.
0025 /// Also handles most of the API work, and account actions.
0026 class AbstractAccount : public QObject
0027 {
0028     Q_OBJECT
0029 
0030     Q_PROPERTY(QString username READ username WRITE setUsername NOTIFY usernameChanged)
0031 
0032     Q_PROPERTY(QString instanceUri READ instanceUri CONSTANT)
0033     Q_PROPERTY(int maxPostLength READ maxPostLength NOTIFY fetchedInstanceMetadata)
0034     Q_PROPERTY(int maxPollOptions READ maxPollOptions 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 
0041 public:
0042     AbstractAccount(QObject *parent, const QString &instanceUri);
0043     AbstractAccount(QObject *parent);
0044 
0045     /// Register the application to the mastodon server
0046     void registerApplication(const QString &appName, const QString &website);
0047 
0048     /// Check if the application is registered
0049     /// \see registerApplication
0050     bool isRegistered() const;
0051 
0052     /// Get the oauth2 authorization url
0053     Q_INVOKABLE QUrl getAuthorizeUrl() const;
0054 
0055     /// Get the oauth2 token url
0056     QUrl getTokenUrl() const;
0057 
0058     /// Set the oauth2 token
0059     Q_INVOKABLE void setToken(const QString &authcode);
0060 
0061     /// Check if the account has a token set
0062     /// \see setToken
0063     bool haveToken() const;
0064 
0065     /// Check if the account has a username yet
0066     bool hasName() const;
0067 
0068     /// Check if the account has an instance uri set
0069     bool hasInstanceUrl() const;
0070 
0071     /// Verifies the token with the instance and if successful, loads identity information for the account
0072     virtual void validateToken() = 0;
0073 
0074     /// Returns the server-side preferences
0075     Preferences *preferences() const;
0076 
0077     /// Return the username of the account
0078     /// \see setUsername
0079     QString username() const;
0080 
0081     /// Sets the username for the account
0082     void setUsername(const QString &name);
0083 
0084     /// Fetches instance-specific metadata like max post length, allowed content types, etc
0085     void fetchInstanceMetadata();
0086 
0087     /// Returns the instance URI
0088     /// \see setInstanceUri
0089     QString instanceUri() const;
0090 
0091     /// Sets the instance URI for the account
0092     void setInstanceUri(const QString &instance_uri);
0093 
0094     /// Returns the max allowable length of posts in characters
0095     size_t maxPostLength() const;
0096 
0097     /// Returns the maximum number of poll options
0098     size_t maxPollOptions() const;
0099 
0100     /// Returns the amount of characters that URLs take
0101     /// Any URL that appears in a post will only be counted by this limit
0102     size_t charactersReservedPerUrl() const;
0103 
0104     /// Returns the title set by the instance
0105     QString instanceName() const;
0106 
0107     /// Returns the identity of the account
0108     Identity *identity();
0109 
0110     /// Looks up an identity specific to this account (like relationships) using an accountId
0111     /// and optionally a JSON document containing identity information.
0112     std::shared_ptr<Identity> identityLookup(const QString &accountId, const QJsonObject &doc);
0113 
0114     /// Checks if the accountId exists in the account's identity cache
0115     bool identityCached(const QString &accountId) const;
0116 
0117     /// Get identity of the admin::account
0118     std::shared_ptr<AdminAccountInfo> adminIdentityLookup(const QString &accountId, const QJsonObject &doc);
0119 
0120     /// Invalidates the account
0121     void invalidate();
0122 
0123     /// Favorite a post
0124     /// \see unfavorite
0125     void favorite(Post *p);
0126 
0127     /// Unfavorite a post
0128     /// \see favorite
0129     void unfavorite(Post *p);
0130 
0131     /// Boost (also known as reblog, or repeat) a post
0132     /// \see unrepeat
0133     void repeat(Post *p);
0134 
0135     /// Unboost a post
0136     /// \see repeat
0137     void unrepeat(Post *p);
0138 
0139     /// Bookmark a post
0140     /// \see unbookmark
0141     void bookmark(Post *p);
0142 
0143     /// Unbookmark a post
0144     /// \see bookmark
0145     void unbookmark(Post *p);
0146 
0147     /// Pin a post
0148     /// \see unpin
0149     void pin(Post *p);
0150 
0151     /// Unpin a post
0152     /// \see pin
0153     void unpin(Post *p);
0154 
0155     /// Returns a streaming url for \p stream
0156     QUrl streamingUrl(const QString &stream);
0157 
0158     /// Invalidates a post
0159     void invalidatePost(Post *p);
0160 
0161     /// Types of formatting that we may use is determined primarily by the server metadata, this is a simple enough
0162     /// way to determine what formats are accepted.
0163     enum AllowedContentType { PlainText = 1 << 0, Markdown = 1 << 1, Html = 1 << 2, BBCode = 1 << 3 };
0164 
0165     /// Return the allowed content types of the account's instance
0166     AllowedContentType allowedContentTypes() const
0167     {
0168         return m_allowedContentTypes;
0169     }
0170 
0171     /// Return a well-formed URL of an API path
0172     QUrl apiUrl(const QString &path) const;
0173 
0174     /// Make an HTTP GET request to the mastodon server
0175     /// \param url The url of the request
0176     /// \param authenticated Whether the request should be authentificated
0177     /// \param parent The parent object that calls get() or the callback belongs to
0178     /// \param callback The callback that should be executed if the request is successful
0179     /// \param errorCallback The callback that should be executed if the request is not successful
0180     virtual void get(const QUrl &url,
0181                      bool authenticated,
0182                      QObject *parent,
0183                      std::function<void(QNetworkReply *)> callback,
0184                      std::function<void(QNetworkReply *)> errorCallback = nullptr) = 0;
0185 
0186     /// Make an HTTP POST request to the mastodon server
0187     /// \param url The url of the request
0188     /// \param doc The request body as JSON
0189     /// \param parent The parent object that calls get() or the callback belongs to
0190     /// \param callback The callback that should be executed if the request is successful
0191     virtual void post(const QUrl &url,
0192                       const QJsonDocument &doc,
0193                       bool authenticated,
0194                       QObject *parent,
0195                       std::function<void(QNetworkReply *)> callback,
0196                       std::function<void(QNetworkReply *)> errorCallback = nullptr,
0197                       QHash<QByteArray, QByteArray> headers = {}) = 0;
0198 
0199     /// Make an HTTP POST request to the mastodon server
0200     /// \param url The url of the request
0201     /// \param doc The request body as form-data
0202     /// \param authenticated Whether the request should be authentificated
0203     /// \param parent The parent object that calls get() or the callback belongs to
0204     /// \param callback The callback that should be executed if the request is successful
0205     virtual void post(const QUrl &url, const QUrlQuery &formdata, bool authenticated, QObject *parent, std::function<void(QNetworkReply *)> callback) = 0;
0206 
0207     virtual QNetworkReply *post(const QUrl &url, QHttpMultiPart *message, bool authenticated, QObject *parent, std::function<void(QNetworkReply *)> callback) = 0;
0208     virtual void put(const QUrl &url, const QJsonDocument &doc, bool authenticated, QObject *parent, std::function<void(QNetworkReply *)> callback) = 0;
0209     virtual void patch(const QUrl &url, QHttpMultiPart *multiPart, bool authenticated, QObject *parent, std::function<void(QNetworkReply *)>) = 0;
0210     virtual void deleteResource(const QUrl &url, bool authenticated, QObject *parent, std::function<void(QNetworkReply *)> callback) = 0;
0211 
0212     /// Upload a file
0213     virtual QNetworkReply *upload(const QUrl &filename, std::function<void(QNetworkReply *)> callback) = 0;
0214 
0215     /// Write account to settings
0216     virtual void writeToSettings() = 0;
0217 
0218     /// Read account from settings
0219     virtual void buildFromSettings(const AccountConfig &settings) = 0;
0220 
0221     /// Check if the account has any follow requests
0222     virtual bool hasFollowRequests() const = 0;
0223 
0224     /// Check against the server for any new follow requests
0225     virtual void checkForFollowRequests() = 0;
0226 
0227     /// Follow the given account. Can also be used to update whether to show reblogs or enable notifications.
0228     /// @param Identity identity The account to follow
0229     /// @param bool reblogs Receive this account's reblogs in home timeline? Defaults to true.
0230     /// @param bool notify Receive notifications when this account posts a status? Defaults to false.
0231     Q_INVOKABLE void followAccount(Identity *identity, bool reblogs = true, bool notify = false);
0232 
0233     /// Unfollow the given account.
0234     /// @param Identity identity The account to unfollow
0235     Q_INVOKABLE void unfollowAccount(Identity *identity);
0236 
0237     /// Block the given account.
0238     /// @param Identity identity The account to block
0239     Q_INVOKABLE void blockAccount(Identity *identity);
0240 
0241     /// Unblock the given account.
0242     /// @param Identity identity The account to unblock
0243     Q_INVOKABLE void unblockAccount(Identity *identity);
0244 
0245     /// Mute the given account.
0246     /// @param Identity identity The account to mute
0247     /// @param bool notifications Whether notifications should also be muted, by default true
0248     /// @param int duration How long the mute should last, in seconds. Defaults to 0 (indefinite).
0249     Q_INVOKABLE void muteAccount(Identity *identity, bool notifications = true, int duration = 0);
0250 
0251     /// Unmute the given account.
0252     /// @param Identity identity The account to unmute
0253     Q_INVOKABLE void unmuteAccount(Identity *identity);
0254 
0255     /// Add the given account to the user's featured profiles.
0256     /// @param Identity identity The account to feature
0257     Q_INVOKABLE void featureAccount(Identity *identity);
0258 
0259     /// Remove the given account from the user's featured profiles.
0260     /// @param Identity identity The account to unfeature
0261     Q_INVOKABLE void unfeatureAccount(Identity *identity);
0262 
0263     /// Sets a private note on a user.
0264     /// @param Identity identity The account to annotate
0265     /// @param QString note The note to add to the account. Leave empty to remove the existing note.
0266     Q_INVOKABLE void addNote(Identity *identity, const QString &note);
0267 
0268     /// Returns the preferred settings group name for this Account which includes the username and the instance uri.
0269     QString settingsGroupName() const;
0270 
0271     /// Returns the preferred key name for the client secret
0272     QString clientSecretKey() const;
0273 
0274     /// Returns the preferred key name for the access token
0275     QString accessTokenKey() const;
0276 
0277     /// Type of account action
0278     enum AccountAction {
0279         Follow, ///< Follow the account
0280         Unfollow, ///< Unfollow the account
0281         Block, ///< Block the account
0282         Unblock, ///< Unlock the account
0283         Mute, ///< Mute the account
0284         Unmute, ///< Unmute the account
0285         Feature, ///< Feature the account
0286         Unfeature, ///< Unfeature the account
0287         Note, ///< Update the note for the account
0288     };
0289 
0290     /// Type of streaming event
0291     enum StreamingEventType {
0292         UpdateEvent, ///< A new Status has appeared.
0293         DeleteEvent, ///< A status has been deleted.
0294         NotificationEvent, ///< A new notification has appeared.
0295         FiltersChangedEvent, ///< Keyword filters have been changed.
0296         ConversationEvent, ///< A direct conversation has been updated.
0297         AnnouncementEvent, ///< An announcement has been published.
0298         AnnouncementRedactedEvent, ///< An announcement has received an emoji reaction.
0299         AnnouncementDeletedEvent, ///< An announcement has been deleted.
0300         StatusUpdatedEvent, ///< A Status has been edited.
0301         EncryptedMessageChangedEvent, ///< An encrypted message has been received.
0302     };
0303 
0304 Q_SIGNALS:
0305     /// Emitted when the account is authenticated
0306     /// \see validateToken
0307     void authenticated(bool successful);
0308 
0309     /// Emitted when the application is successfully registered to the server
0310     /// \see registerApplication
0311     void registered();
0312 
0313     /// Emitted when the account's own identity has been updated
0314     void identityChanged();
0315 
0316     /// Emitted when the requested timeline has been fetched
0317     void fetchedTimeline(const QString &timelineName, QList<Post *> posts);
0318 
0319     /// Emitted when th=e account has been invalidated
0320     /// \see invalidate
0321     void invalidated();
0322 
0323     /// Emitted when the account's username has been changed
0324     /// \see setUsername
0325     void usernameChanged();
0326 
0327     /// Emitted when the instance metadata has been fetched
0328     /// \see fetchInstanceMetadata
0329     void fetchedInstanceMetadata();
0330 
0331     /// Emitted when a post has been invalidated
0332     /// \see invalidatePost
0333     void invalidatedPost(Post *p);
0334 
0335     /// Emitted when a notification has been received
0336     void notification(std::shared_ptr<Notification> n);
0337 
0338     /// Emitted when an error occurred when performing an API request
0339     void errorOccured(const QString &errorMessage);
0340 
0341     /// Emitted when a streaming event has been received
0342     void streamingEvent(AbstractAccount::StreamingEventType eventType, const QByteArray &payload);
0343 
0344     /// Emitted when the account has follow requests
0345     void hasFollowRequestsChanged();
0346 
0347 protected:
0348     QString m_name;
0349     QString m_instance_uri;
0350     QString m_token;
0351     QString m_client_id;
0352     QString m_client_secret;
0353     size_t m_maxPostLength;
0354     size_t m_maxPollOptions;
0355     size_t m_charactersReservedPerUrl;
0356     QString m_instance_name;
0357     std::shared_ptr<Identity> m_identity;
0358     std::shared_ptr<AdminAccountInfo> m_adminIdentity;
0359     AdminAccountInfo *m_federationIdentity;
0360     AllowedContentType m_allowedContentTypes;
0361     Preferences *m_preferences = nullptr;
0362 
0363     // OAuth authorization
0364     QUrlQuery buildOAuthQuery() const;
0365 
0366     // updates and notifications
0367     void handleUpdate(const QJsonDocument &doc, const QString &target);
0368     void handleNotification(const QJsonDocument &doc);
0369 
0370     void mutatePost(Post *p, const QString &verb, bool deliver_home = false);
0371     QMap<QString, std::shared_ptr<Identity>> m_identityCache;
0372     QMap<QString, std::shared_ptr<AdminAccountInfo>> m_adminIdentityCache;
0373 
0374     void executeAction(Identity *i, AccountAction accountAction, const QJsonObject &extraArguments = {});
0375 };