diff options
| author | Andrew Branson <andrew.branson@jolla.com> | 2026-02-10 23:52:48 +0100 |
|---|---|---|
| committer | Andrew Branson <andrew.branson@jolla.com> | 2026-02-11 00:12:00 +0100 |
| commit | b42a78104f6d38ab3aa578e8d2201ac8c685a28c (patch) | |
| tree | 0a3cbf5483f0272f1a20678907a240ab8bfb0b11 | |
| parent | 69628390815254297bbd8c95436f6780fa846fae (diff) | |
Show account handle as description
| -rw-r--r-- | buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodonnotificationssyncadaptor.cpp | 12 | ||||
| -rw-r--r-- | settings/accounts/ui/MastodonSettingsDisplay.qml | 67 | ||||
| -rw-r--r-- | settings/accounts/ui/mastodon-settings.qml | 26 | ||||
| -rw-r--r-- | settings/accounts/ui/mastodon-update.qml | 125 | ||||
| -rw-r--r-- | settings/accounts/ui/mastodon.qml | 133 | ||||
| -rw-r--r-- | transferengine-plugins/mastodonshareservicestatus.cpp | 84 |
6 files changed, 364 insertions, 83 deletions
diff --git a/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodonnotificationssyncadaptor.cpp b/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodonnotificationssyncadaptor.cpp index 79b996c..e3eff52 100644 --- a/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodonnotificationssyncadaptor.cpp +++ b/buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodonnotificationssyncadaptor.cpp @@ -555,9 +555,19 @@ void MastodonNotificationsSyncAdaptor::publishSystemNotification(int accountId, const QString openUrl = notificationData.link.isEmpty() ? apiHost(accountId) + QStringLiteral("/notifications") : notificationData.link; + const QUrl parsedOpenUrl(openUrl); + const QString fallbackUrl = apiHost(accountId) + QStringLiteral("/notifications"); + const QString safeOpenUrl = parsedOpenUrl.isValid() + && !parsedOpenUrl.scheme().isEmpty() + && !parsedOpenUrl.host().isEmpty() + ? openUrl + : fallbackUrl; + QStringList openUrlArgs; + openUrlArgs << safeOpenUrl; + notification->setProperty(LastReadIdProperty, notificationData.notificationId); notification->setUrgency(Notification::Low); - notification->setRemoteAction(OPEN_BROWSER_ACTION(QStringList() << openUrl)); + notification->setRemoteAction(OPEN_BROWSER_ACTION(openUrlArgs)); notification->publish(); if (notification->replacesId() == 0) { qCWarning(lcSocialPlugin) << "failed to publish Mastodon notification" diff --git a/settings/accounts/ui/MastodonSettingsDisplay.qml b/settings/accounts/ui/MastodonSettingsDisplay.qml index 9a45e45..61e9f12 100644 --- a/settings/accounts/ui/MastodonSettingsDisplay.qml +++ b/settings/accounts/ui/MastodonSettingsDisplay.qml @@ -9,19 +9,60 @@ StandardAccountSettingsDisplay { settingsModified: true - function _displayNameFromApiHost(apiHost) { - var host = apiHost ? apiHost.toString().trim() : "" - host = host.replace(/^https?:\/\//i, "") - var pathSeparator = host.indexOf("/") - if (pathSeparator !== -1) { - host = host.substring(0, pathSeparator) + function refreshDescriptionEditor() { + var description = root.account.configurationValues("")["description"] + var descriptionValue = description ? description.toString().trim() : "" + var credentialsUserName = root.account.defaultCredentialsUserName + ? root.account.defaultCredentialsUserName.toString().trim() + : "" + if (descriptionValue.length > 0 && credentialsUserName !== descriptionValue) { + root.account.setConfigurationValue("", "default_credentials_username", descriptionValue) } - return host + + // Reuse the standard "Description" field as the account handle editor. + if (descriptionValue.length > 0) { + root.account.displayName = descriptionValue + } else if (credentialsUserName.length > 0) { + root.account.displayName = credentialsUserName + } else { + root.account.displayName = "" + } + } + + function _providerDisplayName() { + var providerDisplayName = root.accountProvider && root.accountProvider.displayName + ? root.accountProvider.displayName.toString().trim() + : "" + return providerDisplayName.length > 0 ? providerDisplayName : "Mastodon" } onAboutToSaveAccount: { settingsLoader.updateAllSyncProfiles() + var editedDescription = root.account.displayName + ? root.account.displayName.toString().trim() + : "" + var providerDisplayName = _providerDisplayName() + if (editedDescription === providerDisplayName) { + editedDescription = "" + } + + var storedDescriptionValue = root.account.configurationValues("")["description"] + var storedDescription = storedDescriptionValue ? storedDescriptionValue.toString().trim() : "" + if (storedDescription !== editedDescription) { + root.account.setConfigurationValue("", "description", editedDescription) + } + + var storedCredentialsUserName = root.account.defaultCredentialsUserName + ? root.account.defaultCredentialsUserName.toString().trim() + : "" + if (storedCredentialsUserName !== editedDescription) { + root.account.setConfigurationValue("", "default_credentials_username", editedDescription) + } + + // Keep account list title fixed to provider name. + root.account.displayName = providerDisplayName + if (eventsSyncSwitch.checked !== root.account.configurationValues("")["FeedViewAutoSync"]) { root.account.setConfigurationValue("", "FeedViewAutoSync", eventsSyncSwitch.checked) } @@ -38,17 +79,7 @@ StandardAccountSettingsDisplay { syncServicesRepeater.model = syncServices otherServicesDisplay.serviceModel = otherServices - var credentialsUserName = root.account.defaultCredentialsUserName - ? root.account.defaultCredentialsUserName.toString().trim() - : "" - if (credentialsUserName.length > 0 && root.account.displayName !== credentialsUserName) { - root.account.displayName = credentialsUserName - } else if ((!root.account.displayName || root.account.displayName.toString().trim().length === 0)) { - var fallback = _displayNameFromApiHost(root.account.configurationValues("")["api/Host"]) - if (fallback.length > 0) { - root.account.displayName = fallback - } - } + refreshDescriptionEditor() var autoSync = root.account.configurationValues("")["FeedViewAutoSync"] var isNewAccount = root.autoEnableAccount diff --git a/settings/accounts/ui/mastodon-settings.qml b/settings/accounts/ui/mastodon-settings.qml index 6b25bb3..bef1967 100644 --- a/settings/accounts/ui/mastodon-settings.qml +++ b/settings/accounts/ui/mastodon-settings.qml @@ -7,15 +7,10 @@ AccountSettingsAgent { id: root property string accountSubtitle: { - var credentialsUserName = account.defaultCredentialsUserName - ? account.defaultCredentialsUserName.toString().trim() - : "" - if (credentialsUserName.length > 0) { - return credentialsUserName - } - var displayName = account.displayName ? account.displayName.toString().trim() : "" - if (displayName.length > 0) { - return displayName + var description = account.configurationValues("")["description"] + var detail = description ? description.toString().trim() : "" + if (detail.length > 0) { + return detail } var apiHost = account.configurationValues("")["api/Host"] var host = apiHost ? apiHost.toString().trim() : "" @@ -24,6 +19,13 @@ AccountSettingsAgent { if (pathSeparator !== -1) { host = host.substring(0, pathSeparator) } + if (host.length > 0) { + return host + } + var displayName = account.displayName ? account.displayName.toString().trim() : "" + if (displayName.length > 0) { + return displayName + } return host } @@ -33,6 +35,12 @@ AccountSettingsAgent { } initialPage: Page { + onStatusChanged: { + if (status === PageStatus.Active && !credentialsUpdater.running) { + settingsDisplay.refreshDescriptionEditor() + } + } + onPageContainerChanged: { if (pageContainer == null && !credentialsUpdater.running) { root.delayDeletion = true diff --git a/settings/accounts/ui/mastodon-update.qml b/settings/accounts/ui/mastodon-update.qml index d577b05..fa99a82 100644 --- a/settings/accounts/ui/mastodon-update.qml +++ b/settings/accounts/ui/mastodon-update.qml @@ -34,6 +34,128 @@ AccountCredentialsAgent { return config && config[key] ? config[key].toString() : "" } + function _extractAccountName(responseData) { + if (!responseData) { + return "" + } + + var candidates = [ + "AccountUsername", + "UserName", + "user_name", + "acct", + "username", + "preferred_username", + "login", + "ScreenName" + ] + for (var i = 0; i < candidates.length; ++i) { + var value = responseData[candidates[i]] + if (value) { + var userName = value.toString().trim() + if (userName.length > 0) { + return userName + } + } + } + + return "" + } + + function _formatMastodonAccountId(accountName, apiHost) { + var value = accountName ? accountName.toString().trim() : "" + if (value.length === 0) { + return "" + } + + value = value.replace(/^@+/, "") + if (value.indexOf("@") !== -1) { + return "@" + value + } + + var host = apiHost.replace(/^https?:\/\//i, "") + if (host.length === 0) { + return "" + } + + return "@" + value + "@" + host + } + + function _extractAccessToken(responseData) { + if (!responseData) { + return "" + } + + var token = responseData["AccessToken"] + if (!token || token.toString().trim().length === 0) { + token = responseData["access_token"] + } + return token ? token.toString().trim() : "" + } + + function _isMastodonAccountId(value) { + var text = value ? value.toString().trim() : "" + return /^@[^@]+@[^@]+$/.test(text) + } + + function _completeUpdate() { + root.credentialsUpdated(root.accountId) + root.goToEndDestination() + } + + function _saveDescription(description) { + if (description.length > 0) { + account.setConfigurationValue("", "description", description) + if (_isMastodonAccountId(description)) { + account.setConfigurationValue("", "default_credentials_username", description) + } + } + account.sync() + _completeUpdate() + } + + function _updateDescription(responseData) { + var config = account.configurationValues("mastodon-microblog") + var apiHost = normalizeApiHost(_valueFromServiceConfig(config, "api/Host")) + var description = _formatMastodonAccountId(_extractAccountName(responseData), apiHost) + if (description.length > 0) { + _saveDescription(description) + return + } + + var accessToken = _extractAccessToken(responseData) + if (accessToken.length === 0) { + _completeUpdate() + return + } + + var xhr = new XMLHttpRequest() + xhr.onreadystatechange = function() { + if (xhr.readyState !== XMLHttpRequest.DONE) { + return + } + + var fetchedDescription = "" + if (xhr.status >= 200 && xhr.status < 300) { + try { + var response = JSON.parse(xhr.responseText) + fetchedDescription = _formatMastodonAccountId(_extractAccountName(response), apiHost) + } catch (err) { + } + } + + if (fetchedDescription.length > 0) { + _saveDescription(fetchedDescription) + } else { + _completeUpdate() + } + } + + xhr.open("GET", apiHost + "/api/v1/accounts/verify_credentials") + xhr.setRequestHeader("Authorization", "Bearer " + accessToken) + xhr.send() + } + function _startUpdate() { if (_started || initialPage.status !== PageStatus.Active || account.status !== Account.Initialized) { return @@ -83,8 +205,7 @@ AccountCredentialsAgent { } onAccountCredentialsUpdated: { - root.credentialsUpdated(root.accountId) - root.goToEndDestination() + root._updateDescription(responseData) } onAccountCredentialsUpdateError: { diff --git a/settings/accounts/ui/mastodon.qml b/settings/accounts/ui/mastodon.qml index 3359ce8..1ff94b5 100644 --- a/settings/accounts/ui/mastodon.qml +++ b/settings/accounts/ui/mastodon.qml @@ -126,13 +126,79 @@ AccountCreationAgent { xhr.send(postData.join("&")) } - function _handleAccountCreated(accountId, context) { + function _extractAccountName(responseData) { + if (!responseData) { + return "" + } + + var candidates = [ + "AccountUsername", + "UserName", + "user_name", + "acct", + "username", + "preferred_username", + "login", + "ScreenName" + ] + for (var i = 0; i < candidates.length; ++i) { + var value = responseData[candidates[i]] + if (value) { + var userName = value.toString().trim() + if (userName.length > 0) { + return userName + } + } + } + + return "" + } + + function _formatMastodonAccountId(accountName, apiHost) { + var value = accountName ? accountName.toString().trim() : "" + if (value.length === 0) { + return "" + } + + value = value.replace(/^@+/, "") + if (value.indexOf("@") !== -1) { + return "@" + value + } + + var host = oauthHost(apiHost) + if (host.length === 0) { + return "" + } + + return "@" + value + "@" + host + } + + function _isMastodonAccountId(value) { + var text = value ? value.toString().trim() : "" + return /^@[^@]+@[^@]+$/.test(text) + } + + function _extractAccessToken(responseData) { + if (!responseData) { + return "" + } + + var token = responseData["AccessToken"] + if (!token || token.toString().trim().length === 0) { + token = responseData["access_token"] + } + return token ? token.toString().trim() : "" + } + + function _handleAccountCreated(accountId, context, responseData) { var props = { "accountId": accountId, "apiHost": context.apiHost, "oauthHost": context.oauthHost, "clientId": context.clientId, - "clientSecret": context.clientSecret + "clientSecret": context.clientSecret, + "accessToken": _extractAccessToken(responseData), + "accountDescription": _formatMastodonAccountId(_extractAccountName(responseData), context.apiHost) } _accountSetup = accountSetupComponent.createObject(root, props) _accountSetup.done.connect(function() { @@ -251,7 +317,7 @@ AccountCreationAgent { } onAccountCreated: { - root._handleAccountCreated(accountId, context) + root._handleAccountCreated(accountId, context, responseData) } onAccountCreationError: { @@ -270,6 +336,8 @@ AccountCreationAgent { property string oauthHost property string clientId property string clientSecret + property string accessToken + property string accountDescription property bool hasConfigured signal done() @@ -295,20 +363,28 @@ AccountCreationAgent { hasConfigured = true var services = ["mastodon-microblog", "mastodon-sharing"] - var credentialsUserName = newAccount.defaultCredentialsUserName - ? newAccount.defaultCredentialsUserName.toString().trim() + var providerDisplayName = root.accountProvider && root.accountProvider.displayName + ? root.accountProvider.displayName.toString().trim() : "" - var providerDisplayName = root._displayName(apiHost) - var accountDescription = credentialsUserName.length > 0 - ? credentialsUserName - : root._fallbackDisplayName(apiHost) - if (accountDescription.length > 0) { - newAccount.displayName = accountDescription - newAccount.setConfigurationValue("", "description", accountDescription) + if (providerDisplayName.length === 0) { + providerDisplayName = "Mastodon" } + newAccount.displayName = providerDisplayName newAccount.setConfigurationValue("", "api/Host", apiHost) newAccount.setConfigurationValue("", "FeedViewAutoSync", true) + if (accountDescription.length > 0) { + newAccount.setConfigurationValue("", "description", accountDescription) + if (root._isMastodonAccountId(accountDescription)) { + newAccount.setConfigurationValue("", "default_credentials_username", accountDescription) + } + } else { + var hostDisplayName = root._fallbackDisplayName(apiHost) + if (hostDisplayName.length > 0) { + newAccount.setConfigurationValue("", "description", hostDisplayName) + } + } + for (var i = 0; i < services.length; ++i) { var service = services[i] newAccount.setConfigurationValue(service, "api/Host", apiHost) @@ -326,7 +402,38 @@ AccountCreationAgent { newAccount.enableWithService(services[j]) } - newAccount.sync() + if (accountDescription.length > 0 || accessToken.length === 0) { + newAccount.sync() + return + } + + var xhr = new XMLHttpRequest() + xhr.onreadystatechange = function() { + if (xhr.readyState !== XMLHttpRequest.DONE) { + return + } + + if (xhr.status >= 200 && xhr.status < 300) { + try { + var response = JSON.parse(xhr.responseText) + var fetchedDescription = root._formatMastodonAccountId(root._extractAccountName(response), apiHost) + if (fetchedDescription.length > 0) { + accountDescription = fetchedDescription + newAccount.setConfigurationValue("", "description", fetchedDescription) + if (root._isMastodonAccountId(fetchedDescription)) { + newAccount.setConfigurationValue("", "default_credentials_username", fetchedDescription) + } + } + } catch (err) { + } + } + + newAccount.sync() + } + + xhr.open("GET", apiHost + "/api/v1/accounts/verify_credentials") + xhr.setRequestHeader("Authorization", "Bearer " + accessToken) + xhr.send() } } } diff --git a/transferengine-plugins/mastodonshareservicestatus.cpp b/transferengine-plugins/mastodonshareservicestatus.cpp index 6ff4cb8..60173eb 100644 --- a/transferengine-plugins/mastodonshareservicestatus.cpp +++ b/transferengine-plugins/mastodonshareservicestatus.cpp @@ -201,53 +201,57 @@ void MastodonShareServiceStatus::queryStatus(QueryStatusMode mode) } } - if (acc->enabled() && service.isValid() && serviceFound) { - if (acc->value(QStringLiteral("CredentialsNeedUpdate")).toBool()) { - qWarning() << Q_FUNC_INFO << "Credentials need update for account id:" << id; - continue; - } - - acc->selectService(service); - if (acc->value(QStringLiteral("CredentialsNeedUpdate")).toBool()) { - qWarning() << Q_FUNC_INFO << "Credentials need update for account id:" << id; - acc->selectService(Accounts::Service()); - continue; - } + if (!service.isValid() || !serviceFound) { + continue; + } - if (!m_accountIdToDetailsIdx.contains(id)) { - AccountDetails details; - details.accountId = id; - details.apiHost = MastodonAuthUtils::normalizeApiHost(acc->value(QStringLiteral("api/Host")).toString()); - - QUrl apiUrl(details.apiHost); - details.providerName = apiUrl.host(); - if (details.providerName.isEmpty()) { - details.providerName = details.apiHost; - if (details.providerName.startsWith(QLatin1String("https://"))) { - details.providerName.remove(0, 8); - } else if (details.providerName.startsWith(QLatin1String("http://"))) { - details.providerName.remove(0, 7); - } - const int separator = details.providerName.indexOf(QLatin1Char('/')); - if (separator > 0) { - details.providerName.truncate(separator); - } - } + const bool accountEnabled = acc->enabled(); + acc->selectService(service); + const bool shareServiceEnabled = acc->enabled(); + if (!accountEnabled || !shareServiceEnabled) { + acc->selectService(Accounts::Service()); + continue; + } - details.displayName = acc->displayName(); + if (acc->value(QStringLiteral("CredentialsNeedUpdate")).toBool()) { + qWarning() << Q_FUNC_INFO << "Credentials need update for account id:" << id; + acc->selectService(Accounts::Service()); + continue; + } - m_accountIdToDetailsIdx.insert(id, m_accountDetails.size()); - m_accountDetails.append(details); + if (!m_accountIdToDetailsIdx.contains(id)) { + AccountDetails details; + details.accountId = id; + details.apiHost = MastodonAuthUtils::normalizeApiHost(acc->value(QStringLiteral("api/Host")).toString()); + + QUrl apiUrl(details.apiHost); + details.providerName = apiUrl.host(); + if (details.providerName.isEmpty()) { + details.providerName = details.apiHost; + if (details.providerName.startsWith(QLatin1String("https://"))) { + details.providerName.remove(0, 8); + } else if (details.providerName.startsWith(QLatin1String("http://"))) { + details.providerName.remove(0, 7); + } + const int separator = details.providerName.indexOf(QLatin1Char('/')); + if (separator > 0) { + details.providerName.truncate(separator); + } } - if (mode == SignInMode) { - signInActive = true; - m_accountDetailsState.insert(id, Waiting); - signIn(id); - } + details.displayName = acc->displayName(); - acc->selectService(Accounts::Service()); + m_accountIdToDetailsIdx.insert(id, m_accountDetails.size()); + m_accountDetails.append(details); + } + + if (mode == SignInMode) { + signInActive = true; + m_accountDetailsState.insert(id, Waiting); + signIn(id); } + + acc->selectService(Accounts::Service()); } if (!signInActive) { |
