summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Branson <andrew.branson@jolla.com>2026-02-10 23:52:48 +0100
committerAndrew Branson <andrew.branson@jolla.com>2026-02-11 00:12:00 +0100
commitb42a78104f6d38ab3aa578e8d2201ac8c685a28c (patch)
tree0a3cbf5483f0272f1a20678907a240ab8bfb0b11
parent69628390815254297bbd8c95436f6780fa846fae (diff)
Show account handle as description
-rw-r--r--buteo-plugins/buteo-sync-plugin-mastodon-notifications/mastodonnotificationssyncadaptor.cpp12
-rw-r--r--settings/accounts/ui/MastodonSettingsDisplay.qml67
-rw-r--r--settings/accounts/ui/mastodon-settings.qml26
-rw-r--r--settings/accounts/ui/mastodon-update.qml125
-rw-r--r--settings/accounts/ui/mastodon.qml133
-rw-r--r--transferengine-plugins/mastodonshareservicestatus.cpp84
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) {