diff --git a/android/fastlane/metadata/android/en-GB/changelogs/33.txt b/android/fastlane/metadata/android/en-GB/changelogs/33.txt index 75aaca44..8b382340 100644 --- a/android/fastlane/metadata/android/en-GB/changelogs/33.txt +++ b/android/fastlane/metadata/android/en-GB/changelogs/33.txt @@ -1 +1,2 @@ * Fix issue #114 Chinese (Simplified) translated text is not displayed when selected. +* Guest accounts are not preemptively deleted after 25 days anymore. We'll have to wait until they expired by Twitter/X... diff --git a/fastlane/metadata/android/en-US/changelogs/33.txt b/fastlane/metadata/android/en-US/changelogs/33.txt index 75aaca44..8b382340 100644 --- a/fastlane/metadata/android/en-US/changelogs/33.txt +++ b/fastlane/metadata/android/en-US/changelogs/33.txt @@ -1 +1,2 @@ * Fix issue #114 Chinese (Simplified) translated text is not displayed when selected. +* Guest accounts are not preemptively deleted after 25 days anymore. We'll have to wait until they expired by Twitter/X... diff --git a/lib/client_account.dart b/lib/client_account.dart index d7d174aa..0d789fc5 100644 --- a/lib/client_account.dart +++ b/lib/client_account.dart @@ -22,6 +22,7 @@ class TwitterAccount { static Map? _guestAccountTokens; static final List> _guestAccountTokensLst = []; static final Map>> _rateLimits = {}; + static final List _guestAccountOauthTokenExpiredLst = []; // this must be executed only once at the start of the application static Future loadAllGuestAccountsAndRateLimits() async { @@ -143,6 +144,9 @@ class TwitterAccount { } } + // deleted expired guest accounts + await _deleteExpiredGuestAccounts(); + // now find the first guest account that is available or at least with the minimum waiting time Map? guestAccountInfo = getNextGuestAccount(uriPath, total); if (guestAccountInfo == null) { @@ -156,10 +160,13 @@ class TwitterAccount { else if (guestAccountInfo['guestAccount'] != null) { _guestAccountTokens = guestAccountInfo['guestAccount']; } - else { + else if (guestAccountInfo['minRateLimitReset'] != 0) { Map di = delayInfo(guestAccountInfo['minRateLimitReset']); throw RateLimitException('The request $uriPath has reached its limit. Please wait ${di['minutesStr']}.', longDelay: di['longDelay']); } + else { + throw RateLimitException('There is a problem getting a guest account.'); + } } static Map delayInfo(int targetDateTime) { @@ -380,6 +387,36 @@ class TwitterAccount { }; } + static void markGuestOauthTokenExpired() { + if (_guestAccountTokens == null) { + // should not happen + return; + } + String oauthToken = _guestAccountTokens!['oauthToken']; + if (!_guestAccountOauthTokenExpiredLst.contains(oauthToken)) { + _guestAccountOauthTokenExpiredLst.add(oauthToken); + } + } + + static Future _deleteExpiredGuestAccounts() async { + if (_guestAccountOauthTokenExpiredLst.isEmpty) { + return; + } + log.info('Deleting old guest accounts'); + + _guestAccountTokensLst.removeWhere((elm) => _guestAccountOauthTokenExpiredLst.contains(elm['oauthToken'])); + + _rateLimits.removeWhere((key, value) => _guestAccountOauthTokenExpiredLst.contains(key)); + + var database = await Repository.writable(); + + await database.delete(tableRateLimits, where: 'oauth_token IN (${List.filled(_guestAccountOauthTokenExpiredLst.length, '?').join(',')})', whereArgs: _guestAccountOauthTokenExpiredLst); + + await database.delete(tableGuestAccount, where: "oauth_token IN (${List.filled(_guestAccountOauthTokenExpiredLst.length, '?').join(',')}) ", whereArgs: _guestAccountOauthTokenExpiredLst); + + _guestAccountOauthTokenExpiredLst.clear(); + } + static String hmacSHA1(String key, String text) { var hmacSha1 = Hmac(sha1, utf8.encode(key)); var digest = hmacSha1.convert(utf8.encode(text)); @@ -529,7 +566,11 @@ class RateFetchContext { var headerRateLimitRemaining = response.headers['x-rate-limit-remaining']; var headerRateLimitReset = response.headers['x-rate-limit-reset']; TwitterAccount.log.info('*** headerRateLimitRemaining=$headerRateLimitRemaining, headerRateLimitReset=$headerRateLimitReset'); - if (headerRateLimitRemaining == null || headerRateLimitReset == null) { + if (response.statusCode == 401 && response.body.contains('Invalid or expired token')) { + remainingLst.add(-2); + resetLst.add(0); + } + else if (headerRateLimitRemaining == null || headerRateLimitReset == null) { TwitterAccount.log.info('The request $uriPath has no rate limits.'); remainingLst.add(null); resetLst.add(null); @@ -554,26 +595,37 @@ class RateFetchContext { return; } int minRemaining = double.maxFinite.round(); - int minReset = 0; + int minReset = -1; for (int i = 0; i < remainingLst.length; i++) { if (remainingLst[i] != null && remainingLst[i]! < minRemaining) { minRemaining = remainingLst[i]!; minReset = resetLst[i]!; } } - if (minReset == 0) { + if (minReset == -1) { return; } + if (minRemaining == -2) { + TwitterAccount.markGuestOauthTokenExpired(); + } await TwitterAccount.updateRateValues(uriPath, minRemaining, minReset); - if (minRemaining == -1) { + if (minRemaining <= -1) { // this should not happened but just in case, check if there is another guest account that is NOT with an embargo Map? guestAccountInfoTmp = TwitterAccount.getNextGuestAccount(uriPath, total); - if (guestAccountInfoTmp!['guestAccount'] != null) { - throw RateLimitException('The request $uriPath has reached its limit. Please wait 1 minute.'); + if (guestAccountInfoTmp == null) { + throw RateLimitException('There is a problem getting a guest account.'); } else { - Map di = TwitterAccount.delayInfo(guestAccountInfoTmp['minRateLimitReset']); - throw RateLimitException('The request $uriPath has reached its limit. Please wait ${di['minutesStr']}.', longDelay: di['longDelay']); + if (guestAccountInfoTmp!['guestAccount'] != null) { + throw RateLimitException('The request $uriPath has reached its limit. Please wait 1 minute.'); + } + else if (guestAccountInfoTmp['minRateLimitReset'] != 0) { + Map di = TwitterAccount.delayInfo(guestAccountInfoTmp['minRateLimitReset']); + throw RateLimitException('The request $uriPath has reached its limit. Please wait ${di['minutesStr']}.', longDelay: di['longDelay']); + } + else { + throw RateLimitException('There is a problem getting a guest account.'); + } } } } @@ -595,15 +647,4 @@ class GuestAccountsModel extends Store> { }); } - Future deleteOldAccounts() async { - log.info('Deleting old guest accounts'); - - await execute(() async { - var database = await Repository.writable(); - - await database.delete(tableGuestAccount, where: "created_at < date('now', '-25 day')"); - return (await database.query(tableGuestAccount)).map((e) => GuestAccount.fromMap(e)).toList(); - }); - } - } diff --git a/lib/database/repository.dart b/lib/database/repository.dart index cf0864c3..7f365c45 100644 --- a/lib/database/repository.dart +++ b/lib/database/repository.dart @@ -263,7 +263,6 @@ class Repository { var repository = await writable(); await repository.delete(tableFeedGroupChunk, where: "created_at <= date('now', '-7 day')"); - await repository.delete(tableGuestAccount, where: "created_at < date('now', '-25 day')"); int version = await repository.getVersion(); diff --git a/lib/settings/_data.dart b/lib/settings/_data.dart index 800e1b52..a313db21 100644 --- a/lib/settings/_data.dart +++ b/lib/settings/_data.dart @@ -126,7 +126,6 @@ class SettingsDataFragment extends StatelessWidget { await importModel.importData(dataToImport); if (guestAccounts != null) { - await context.read().deleteOldAccounts(); await TwitterAccount.loadAllGuestAccountsAndRateLimits(); } await groupModel.reloadGroups();