| 1 | import 'dart:convert'; |
| 2 | |
| 3 | import 'package:cake_wallet/.secrets.g.dart' as secrets; |
| 4 | import 'package:cake_wallet/core/address_resolver/twitter/twitter_user.dart'; |
| 5 | import 'package:cw_core/utils/proxy_wrapper.dart'; |
| 6 | |
| 7 | class TwitterApi { |
| 8 | static const twitterBearerToken = secrets.twitterBearerToken; |
| 9 | static const httpsScheme = 'https'; |
| 10 | static const apiHost = 'api.twitter.com'; |
| 11 | static const userPath = '/2/users/by/username/'; |
| 12 | |
| 13 | static Future<TwitterUser> lookupUserByName({required String userName}) async { |
| 14 | final queryParams = { |
| 15 | 'user.fields': 'description,profile_image_url,location', |
| 16 | 'expansions': 'pinned_tweet_id', |
| 17 | 'tweet.fields': 'note_tweet' |
| 18 | }; |
| 19 | final headers = {'authorization': 'Bearer $twitterBearerToken'}; |
| 20 | final uri = Uri( |
| 21 | scheme: httpsScheme, |
| 22 | host: apiHost, |
| 23 | path: userPath + userName, |
| 24 | queryParameters: queryParams); |
| 25 | |
| 26 | final response = |
| 27 | await ProxyWrapper().get(clearnetUri: uri, headers: headers).catchError((error) { |
| 28 | throw Exception('HTTP request failed: $error'); |
| 29 | }); |
| 30 | |
| 31 | if (response.statusCode != 200) { |
| 32 | throw Exception('Unexpected http status: ${response.statusCode}'); |
| 33 | } |
| 34 | |
| 35 | final Map<String, dynamic> responseJSON = jsonDecode(response.body) as Map<String, dynamic>; |
| 36 | if (responseJSON['errors'] != null && |
| 37 | !responseJSON['errors'][0]['detail'] |
| 38 | .toString() |
| 39 | .contains("Could not find tweet with pinned_tweet_id")) { |
| 40 | throw Exception(responseJSON['errors'][0]['detail']); |
| 41 | } |
| 42 | |
| 43 | return TwitterUser.fromJson(responseJSON, _getPinnedTweet(responseJSON)); |
| 44 | } |
| 45 | |
| 46 | static Tweet? _getPinnedTweet(Map<String, dynamic> responseJSON) { |
| 47 | try { |
| 48 | final tweetId = responseJSON['data']['pinned_tweet_id'] as String?; |
| 49 | if (tweetId == null || responseJSON['includes'] == null) return null; |
| 50 | |
| 51 | final tweetIncludes = List.from(responseJSON['includes']['tweets'] as List); |
| 52 | final pinnedTweetData = tweetIncludes.firstWhere( |
| 53 | (tweet) => tweet['id'] == tweetId, |
| 54 | orElse: () => null, |
| 55 | ) as Map<String, dynamic>?; |
| 56 | |
| 57 | if (pinnedTweetData == null) return null; |
| 58 | |
| 59 | final pinnedTweetText = |
| 60 | (pinnedTweetData['note_tweet']?['text'] ?? pinnedTweetData['text']) as String; |
| 61 | |
| 62 | return Tweet(id: tweetId, text: pinnedTweetText); |
| 63 | } catch (e) { |
| 64 | return null; |
| 65 | } |
| 66 | } |
| 67 | } |