add twitter api

Serhii committed Jan 24, 2023 at 20:24 UTC 83c1907e31460d7fc5f4679249e52f6dfe1ed1e4
6 files changed +110 -7
lib/core/address_validator.dart
+18
@@ -198,4 +198,22 @@ class AddressValidator extends TextValidator {
198 return [];
199 }
200 }
201 +
202 + static String? getAddressFromStringPattern(CryptoCurrency type) {
203 + switch (type) {
204 + case CryptoCurrency.xmr:
205 + return '([^0-9a-zA-Z]|^)4[0-9a-zA-Z]{94}([^0-9a-zA-Z]|\$)'
206 + '|([^0-9a-zA-Z]|^)8[0-9a-zA-Z]{94}([^0-9a-zA-Z]|\$)'
207 + '|([^0-9a-zA-Z]|^)[0-9a-zA-Z]{106}([^0-9a-zA-Z]|\$)';
208 + case CryptoCurrency.btc:
209 + return '([^0-9a-zA-Z]|^)1[0-9a-zA-Z]{32}([^0-9a-zA-Z]|\$)'
210 + '|([^0-9a-zA-Z]|^)1[0-9a-zA-Z]{33}([^0-9a-zA-Z]|\$)'
211 + '|([^0-9a-zA-Z]|^)3[0-9a-zA-Z]{32}([^0-9a-zA-Z]|\$)'
212 + '|([^0-9a-zA-Z]|^)3[0-9a-zA-Z]{33}([^0-9a-zA-Z]|\$)'
213 + '|([^0-9a-zA-Z]|^)bc1[0-9a-zA-Z]{39}([^0-9a-zA-Z]|\$)'
214 + '|([^0-9a-zA-Z]|^)bc1[0-9a-zA-Z]{59}([^0-9a-zA-Z]|\$)';
215 + default:
216 + return null;
217 + }
218 + }
219 }
lib/entities/parse_address_from_domain.dart
+25 -5
@@ -1,18 +1,19 @@
1 +import 'package:cake_wallet/core/address_validator.dart';
2 import 'package:cake_wallet/core/yat_service.dart';
3 import 'package:cake_wallet/entities/openalias_record.dart';
4 import 'package:cake_wallet/entities/parsed_address.dart';
5 import 'package:cake_wallet/entities/unstoppable_domain_address.dart';
6 import 'package:cake_wallet/entities/emoji_string_extension.dart';
7 +import 'package:cake_wallet/twitter/twitter_api.dart';
8 +import 'package:cw_core/crypto_currency.dart';
9 import 'package:cw_core/wallet_type.dart';
10 import 'package:cake_wallet/entities/fio_address_provider.dart';
11
12 class AddressResolver {
10 -
13 AddressResolver({required this.yatService, required this.walletType});
12 -
14 final YatService yatService;
15 final WalletType walletType;
15 -
16 +
17 static const unstoppableDomains = [
18 'crypto',
19 'zil',
@@ -26,9 +27,28 @@ class AddressResolver {
27 'blockchain'
28 ];
29
30 + static String? extractAddressByType({required String raw, required CryptoCurrency type}) {
31 + final addressPattern = AddressValidator.getAddressFromStringPattern(type);
32 +
33 + if (addressPattern == null) {
34 + throw 'Unexpected token: $type for getAddressFromStringPattern';
35 + }
36 +
37 + final match = RegExp(addressPattern).firstMatch(raw);
38 + return match?.group(0)?.replaceAll(RegExp('[^0-9a-zA-Z]'), '');
39 + }
40 +
41 Future<ParsedAddress> resolve(String text, String ticker) async {
42 try {
31 - if (text.contains('@') && !text.contains('.')) {
43 + if (text.startsWith('@') && !text.substring(1).contains('@')) {
44 + final formattedName = text.substring(1);
45 + final twitterUser = await TwitterApi.lookupUserByName(userName: formattedName);
46 + final address = extractAddressByType(raw: twitterUser.description ?? '', type: CryptoCurrency.fromString(ticker));
47 + if (address != null) {
48 + return ParsedAddress.fetchTwitterAddress(address: address, name: text);
49 + }
50 + }
51 + if (!text.startsWith('@') && text.contains('@') && !text.contains('.')) {
52 final bool isFioRegistered = await FioAddressProvider.checkAvail(text);
53 if (isFioRegistered) {
54 final address = await FioAddressProvider.getPubAddress(text, ticker);
@@ -58,7 +78,7 @@ class AddressResolver {
78 final record = await OpenaliasRecord.fetchAddressAndName(
79 formattedName: formattedName, ticker: ticker);
80 return ParsedAddress.fetchOpenAliasAddress(record: record, name: text);
61 -
81 +
82 } catch (e) {
83 print(e.toString());
84 }
lib/entities/parsed_address.dart
+9 -2
@@ -1,8 +1,7 @@
1 import 'package:cake_wallet/entities/openalias_record.dart';
2 import 'package:cake_wallet/entities/yat_record.dart';
3 -import 'package:flutter/material.dart';
3
5 -enum ParseFrom { unstoppableDomains, openAlias, yatRecord, fio, notParsed }
4 +enum ParseFrom { unstoppableDomains, openAlias, yatRecord, fio, notParsed, twitter }
5
6 class ParsedAddress {
7 ParsedAddress({
@@ -62,6 +61,14 @@ class ParsedAddress {
61 );
62 }
63
64 + factory ParsedAddress.fetchTwitterAddress({required String address, required String name}){
65 + return ParsedAddress(
66 + addresses: [address],
67 + name: name,
68 + parseFrom: ParseFrom.twitter,
69 + );
70 + }
71 +
72 final List<String> addresses;
73 final String name;
74 final String description;
lib/src/screens/send/widgets/extract_address_from_parsed.dart
+5
@@ -28,6 +28,11 @@ Future<String> extractAddressFromParsed(
28 content = S.of(context).openalias_alert_content(parsedAddress.name);
29 address = parsedAddress.addresses.first;
30 break;
31 + case ParseFrom.twitter:
32 + title = S.of(context).address_detected;
33 + content = S.of(context).openalias_alert_content(parsedAddress.name);
34 + address = parsedAddress.addresses.first;
35 + break;
36 case ParseFrom.yatRecord:
37 if (parsedAddress.name.isEmpty) {
38 title = S.of(context).yat_error;
lib/twitter/twitter_api.dart new
+37
@@ -0,0 +1,37 @@
1 +import 'dart:convert';
2 +import 'package:cake_wallet/twitter/twitter_user.dart';
3 +import 'package:http/http.dart' as http;
4 +import 'package:cake_wallet/.secrets.g.dart' as secrets;
5 +
6 +class TwitterApi {
7 + static const twitterBearerToken = secrets.twitterBearerToken;
8 + static const httpsScheme = 'https';
9 + static const apiHost = 'api.twitter.com';
10 + static const userPath = '/2/users/by/username/';
11 +
12 + static Future<TwitterUser> lookupUserByName({required String userName}) async {
13 + final queryParams = {'user.fields': 'description'};
14 +
15 + final headers = {'authorization': 'Bearer ${secrets.twitterBearerToken}'};
16 +
17 + final uri = Uri(
18 + scheme: httpsScheme,
19 + host: apiHost,
20 + path: userPath + userName,
21 + queryParameters: queryParams,
22 + );
23 +
24 + var response = await http.get(uri, headers: headers);
25 +
26 + if (response.statusCode != 200) {
27 + throw Exception('Unexpected http status: ${response.statusCode}');
28 + }
29 + final responseJSON = json.decode(response.body) as Map<String, dynamic>;
30 +
31 + if (responseJSON['errors'] != null) {
32 + throw Exception(responseJSON['errors'][0]['detail']);
33 + }
34 +
35 + return TwitterUser.fromJson(responseJSON['data'] as Map<String, dynamic>);
36 + }
37 +}
lib/twitter/twitter_user.dart new
+16
@@ -0,0 +1,16 @@
1 +class TwitterUser {
2 + TwitterUser({required this.id, required this.username, required this.name, this.description});
3 +
4 + final String id;
5 + final String username;
6 + final String name;
7 + final String? description;
8 +
9 + factory TwitterUser.fromJson(Map<String, dynamic> json) {
10 + return TwitterUser(
11 + id: json['id'] as String,
12 + username: json['username'] as String,
13 + name: json['name'] as String,
14 + description: json['description'] as String?);
15 + }
16 +}