rettiwt-api 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +2 -0
- package/.gitattributes +3 -0
- package/Dockerfile +9 -0
- package/README.md +38 -0
- package/dist/config/env.d.ts +5 -0
- package/dist/config/env.js +9 -0
- package/dist/config/env.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/models/graphql/Global.d.ts +4 -0
- package/dist/models/graphql/Global.js +13 -0
- package/dist/models/graphql/Global.js.map +1 -0
- package/dist/models/graphql/TweetTypes.d.ts +6 -0
- package/dist/models/graphql/TweetTypes.js +156 -0
- package/dist/models/graphql/TweetTypes.js.map +1 -0
- package/dist/models/graphql/UserTypes.d.ts +3 -0
- package/dist/models/graphql/UserTypes.js +139 -0
- package/dist/models/graphql/UserTypes.js.map +1 -0
- package/dist/queries/RootQuery.d.ts +4 -0
- package/dist/queries/RootQuery.js +59 -0
- package/dist/queries/RootQuery.js.map +1 -0
- package/dist/resolvers/ResolverBase.d.ts +5 -0
- package/dist/resolvers/ResolverBase.js +11 -0
- package/dist/resolvers/ResolverBase.js.map +1 -0
- package/dist/resolvers/TweetResolver.d.ts +54 -0
- package/dist/resolvers/TweetResolver.js +328 -0
- package/dist/resolvers/TweetResolver.js.map +1 -0
- package/dist/resolvers/UserResolver.d.ts +47 -0
- package/dist/resolvers/UserResolver.js +302 -0
- package/dist/resolvers/UserResolver.js.map +1 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +75 -0
- package/dist/server.js.map +1 -0
- package/dist/services/AuthService.d.ts +17 -0
- package/dist/services/AuthService.js +103 -0
- package/dist/services/AuthService.js.map +1 -0
- package/dist/services/CacheService.d.ts +25 -0
- package/dist/services/CacheService.js +141 -0
- package/dist/services/CacheService.js.map +1 -0
- package/dist/services/FetcherService.d.ts +30 -0
- package/dist/services/FetcherService.js +171 -0
- package/dist/services/FetcherService.js.map +1 -0
- package/dist/services/data/TweetService.d.ts +43 -0
- package/dist/services/data/TweetService.js +229 -0
- package/dist/services/data/TweetService.js.map +1 -0
- package/dist/services/data/UserAccountService.d.ts +49 -0
- package/dist/services/data/UserAccountService.js +250 -0
- package/dist/services/data/UserAccountService.js.map +1 -0
- package/dist/services/helper/Deserializers.d.ts +19 -0
- package/dist/services/helper/Deserializers.js +115 -0
- package/dist/services/helper/Deserializers.js.map +1 -0
- package/dist/services/helper/Extractors.d.ts +101 -0
- package/dist/services/helper/Extractors.js +409 -0
- package/dist/services/helper/Extractors.js.map +1 -0
- package/dist/services/helper/Headers.d.ts +9 -0
- package/dist/services/helper/Headers.js +47 -0
- package/dist/services/helper/Headers.js.map +1 -0
- package/dist/services/helper/Parser.d.ts +28 -0
- package/dist/services/helper/Parser.js +104 -0
- package/dist/services/helper/Parser.js.map +1 -0
- package/dist/services/helper/Urls.d.ts +74 -0
- package/dist/services/helper/Urls.js +114 -0
- package/dist/services/helper/Urls.js.map +1 -0
- package/dist/test/Test.js +2 -0
- package/dist/test/Test.js.map +1 -0
- package/dist/types/Authentication.d.ts +15 -0
- package/dist/types/Authentication.js +5 -0
- package/dist/types/Authentication.js.map +1 -0
- package/dist/types/HTTP.d.ts +22 -0
- package/dist/types/HTTP.js +30 -0
- package/dist/types/HTTP.js.map +1 -0
- package/dist/types/Service.d.ts +27 -0
- package/dist/types/Service.js +19 -0
- package/dist/types/Service.js.map +1 -0
- package/dist/types/Tweet.d.ts +40 -0
- package/dist/types/Tweet.js +5 -0
- package/dist/types/Tweet.js.map +1 -0
- package/dist/types/UserAccount.d.ts +19 -0
- package/dist/types/UserAccount.js +4 -0
- package/dist/types/UserAccount.js.map +1 -0
- package/dist/types/graphql/Errors.d.ts +15 -0
- package/dist/types/graphql/Errors.js +23 -0
- package/dist/types/graphql/Errors.js.map +1 -0
- package/dist/types/raw/auth/Cookie.d.ts +16 -0
- package/dist/types/raw/auth/Cookie.js +3 -0
- package/dist/types/raw/auth/Cookie.js.map +1 -0
- package/dist/types/raw/tweet/Favouriters.d.ts +164 -0
- package/dist/types/raw/tweet/Favouriters.js +3 -0
- package/dist/types/raw/tweet/Favouriters.js.map +1 -0
- package/dist/types/raw/tweet/Retweeters.d.ts +171 -0
- package/dist/types/raw/tweet/Retweeters.js +3 -0
- package/dist/types/raw/tweet/Retweeters.js.map +1 -0
- package/dist/types/raw/tweet/Tweet.d.ts +746 -0
- package/dist/types/raw/tweet/Tweet.js +3 -0
- package/dist/types/raw/tweet/Tweet.js.map +1 -0
- package/dist/types/raw/tweet/Tweets.d.ts +386 -0
- package/dist/types/raw/tweet/Tweets.js +3 -0
- package/dist/types/raw/tweet/Tweets.js.map +1 -0
- package/dist/types/raw/user/Followers.d.ts +176 -0
- package/dist/types/raw/user/Followers.js +3 -0
- package/dist/types/raw/user/Followers.js.map +1 -0
- package/dist/types/raw/user/Following.d.ts +176 -0
- package/dist/types/raw/user/Following.js +3 -0
- package/dist/types/raw/user/Following.js.map +1 -0
- package/dist/types/raw/user/Likes.d.ts +1059 -0
- package/dist/types/raw/user/Likes.js +3 -0
- package/dist/types/raw/user/Likes.js.map +1 -0
- package/dist/types/raw/user/Tweets.d.ts +2428 -0
- package/dist/types/raw/user/Tweets.js +3 -0
- package/dist/types/raw/user/Tweets.js.map +1 -0
- package/dist/types/raw/user/User.d.ts +117 -0
- package/dist/types/raw/user/User.js +3 -0
- package/dist/types/raw/user/User.js.map +1 -0
- package/dist/types/raw/user/Users.d.ts +120 -0
- package/dist/types/raw/user/Users.js +3 -0
- package/dist/types/raw/user/Users.js.map +1 -0
- package/environment.d.ts +11 -0
- package/package.json +40 -0
- package/src/config/env.ts +5 -0
- package/src/index.ts +19 -0
- package/src/models/graphql/Global.ts +10 -0
- package/src/models/graphql/TweetTypes.ts +154 -0
- package/src/models/graphql/UserTypes.ts +137 -0
- package/src/queries/RootQuery.ts +56 -0
- package/src/resolvers/ResolverBase.ts +12 -0
- package/src/resolvers/TweetResolver.ts +257 -0
- package/src/resolvers/UserResolver.ts +239 -0
- package/src/server.ts +36 -0
- package/src/services/AuthService.ts +58 -0
- package/src/services/CacheService.ts +70 -0
- package/src/services/FetcherService.ts +84 -0
- package/src/services/data/TweetService.ts +163 -0
- package/src/services/data/UserAccountService.ts +187 -0
- package/src/services/helper/Deserializers.ts +95 -0
- package/src/services/helper/Extractors.ts +455 -0
- package/src/services/helper/Headers.ts +45 -0
- package/src/services/helper/Parser.ts +108 -0
- package/src/services/helper/Urls.ts +109 -0
- package/src/types/Authentication.ts +16 -0
- package/src/types/HTTP.ts +23 -0
- package/src/types/Service.ts +36 -0
- package/src/types/Tweet.ts +44 -0
- package/src/types/UserAccount.ts +21 -0
- package/src/types/graphql/Errors.ts +16 -0
- package/src/types/raw/auth/Cookie.ts +16 -0
- package/src/types/raw/tweet/Favouriters.ts +193 -0
- package/src/types/raw/tweet/Retweeters.ts +201 -0
- package/src/types/raw/tweet/Tweet.ts +882 -0
- package/src/types/raw/tweet/Tweets.ts +444 -0
- package/src/types/raw/user/Followers.ts +208 -0
- package/src/types/raw/user/Following.ts +208 -0
- package/src/types/raw/user/Likes.ts +1247 -0
- package/src/types/raw/user/Tweets.ts +2847 -0
- package/src/types/raw/user/User.ts +135 -0
- package/tsconfig.json +95 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// PACKAGES
|
|
2
|
+
import NodeCache from 'node-cache';
|
|
3
|
+
|
|
4
|
+
// PARSERS
|
|
5
|
+
import * as Parsers from './helper/Parser';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @summary Handles reading and writing of data from and to cache.
|
|
9
|
+
*
|
|
10
|
+
* **Note**: To be able to CacheService, the data to be cached must have a unique "id" field.
|
|
11
|
+
*/
|
|
12
|
+
export class CacheService {
|
|
13
|
+
// MEMBER DATA
|
|
14
|
+
private static instance: CacheService; // To store the current instance of this service
|
|
15
|
+
private client: NodeCache; // To store the redis client instance
|
|
16
|
+
|
|
17
|
+
// MEMBER METHODS
|
|
18
|
+
private constructor() {
|
|
19
|
+
// Initializing new cache
|
|
20
|
+
this.client = new NodeCache();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @returns The current working instance of CacheService
|
|
25
|
+
*/
|
|
26
|
+
static async getInstance(): Promise<CacheService> {
|
|
27
|
+
// If an instance doesnt exists already
|
|
28
|
+
if (!this.instance) {
|
|
29
|
+
this.instance = new CacheService();
|
|
30
|
+
return this.instance;
|
|
31
|
+
}
|
|
32
|
+
// If an instance already exists, returning it
|
|
33
|
+
else {
|
|
34
|
+
return this.instance;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @summary Stores the input data into the cache.
|
|
40
|
+
* @returns Whether writing to cache was successful or not
|
|
41
|
+
* @param data The input data to store
|
|
42
|
+
*/
|
|
43
|
+
async write(data: any): Promise<void> {
|
|
44
|
+
// Converting the data to a list of data
|
|
45
|
+
data = Parsers.dataToList(data);
|
|
46
|
+
|
|
47
|
+
// Iterating over the list of data
|
|
48
|
+
for (let item of data) {
|
|
49
|
+
// Storing whether data is already cached or not
|
|
50
|
+
let cached = this.client.has(Parsers.findJSONKey(item, 'id'));
|
|
51
|
+
|
|
52
|
+
// If data does not already exist in cache
|
|
53
|
+
if(!cached) {
|
|
54
|
+
// Adding data to cache
|
|
55
|
+
this.client.set(Parsers.findJSONKey(item, 'id'), item);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @returns The data with the given id/rest id from cache
|
|
62
|
+
* @param id The id/rest id of the data to be fetched from cache
|
|
63
|
+
*/
|
|
64
|
+
async read(id: string): Promise<any> {
|
|
65
|
+
// Getting data from cache
|
|
66
|
+
let res = this.client.get(id);
|
|
67
|
+
|
|
68
|
+
return res;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// PACKAGES
|
|
2
|
+
import { curly, CurlyResult } from 'node-libcurl';
|
|
3
|
+
|
|
4
|
+
// SERVICES
|
|
5
|
+
import { AuthService } from './AuthService';
|
|
6
|
+
import { CacheService } from './CacheService';
|
|
7
|
+
|
|
8
|
+
// TYPES
|
|
9
|
+
import { HttpStatus } from "../types/HTTP";
|
|
10
|
+
import { Result as RawUser } from '../types/raw/user/User';
|
|
11
|
+
import { Result as RawTweet } from '../types/raw/tweet/Tweet';
|
|
12
|
+
|
|
13
|
+
// HELPERS
|
|
14
|
+
import * as Headers from './helper/Headers'
|
|
15
|
+
import * as Deserializers from './helper/Deserializers';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @service The base serivice from which all other data services derive their behaviour
|
|
19
|
+
*/
|
|
20
|
+
export class FetcherService {
|
|
21
|
+
// MEMBER DATA
|
|
22
|
+
private auth: AuthService; // To store the auth service instance to use for authentication
|
|
23
|
+
|
|
24
|
+
// MEMBER METHODS
|
|
25
|
+
constructor(auth: AuthService) {
|
|
26
|
+
this.auth = auth;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @summary Throws the appropriate http error after evaluation of the status code of reponse
|
|
31
|
+
* @param res The response object received from http communication
|
|
32
|
+
*/
|
|
33
|
+
private handleHTTPError(res: CurlyResult): CurlyResult {
|
|
34
|
+
if (res.statusCode != 200 && res.statusCode in HttpStatus) {
|
|
35
|
+
throw new Error(HttpStatus[res.statusCode])
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return res;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @returns The absolute raw json data from give url
|
|
43
|
+
* @param url The url to fetch data from
|
|
44
|
+
* @param authenticated Whether to authenticate requests or not
|
|
45
|
+
*/
|
|
46
|
+
protected async request<DataType>(url: string, authenticated: boolean = false): Promise<CurlyResult<DataType>> {
|
|
47
|
+
// Fetching the data
|
|
48
|
+
let res = await curly.get(url, {
|
|
49
|
+
httpHeader: authenticated ? Headers.authorizedHeader(await this.auth.getAuthCredentials()) : Headers.guestHeader(await this.auth.getGuestCredentials()),
|
|
50
|
+
sslVerifyPeer: false
|
|
51
|
+
}).then(res => this.handleHTTPError(res));
|
|
52
|
+
|
|
53
|
+
return res;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @summary Caches the extracted data
|
|
58
|
+
* @param data The extracted data to be cached
|
|
59
|
+
*/
|
|
60
|
+
protected async cacheData(data: any): Promise<void> {
|
|
61
|
+
// Creating an instance of cache
|
|
62
|
+
let cache = await CacheService.getInstance();
|
|
63
|
+
|
|
64
|
+
// Parsing the extracted data
|
|
65
|
+
let users = data.users.map((user: RawUser) => Deserializers.toUser(user));
|
|
66
|
+
let tweets = data.tweets.map((tweet: RawTweet) => Deserializers.toTweet(tweet));
|
|
67
|
+
|
|
68
|
+
// Caching the data
|
|
69
|
+
cache.write(users);
|
|
70
|
+
cache.write(tweets);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @returns The data with the given id (if it exists in cache)
|
|
75
|
+
* @param id The id of the data to be read from cache
|
|
76
|
+
*/
|
|
77
|
+
protected async readData(id: string): Promise<any> {
|
|
78
|
+
// Creating an instance of cache
|
|
79
|
+
let cache = await CacheService.getInstance();
|
|
80
|
+
|
|
81
|
+
// Reading data from cache
|
|
82
|
+
return cache.read(id);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// SERVICES
|
|
2
|
+
import { FetcherService } from "../FetcherService";
|
|
3
|
+
import { AuthService } from "../AuthService";
|
|
4
|
+
|
|
5
|
+
// TYPES
|
|
6
|
+
import { TweetFilter, Tweet } from "../../types/Tweet";
|
|
7
|
+
import { User } from "../../types/UserAccount";
|
|
8
|
+
import { CursoredData } from '../../types/Service';
|
|
9
|
+
import RawTweet from '../../types/raw/tweet/Tweet';
|
|
10
|
+
import RawTweets from '../../types/raw/tweet/Tweets';
|
|
11
|
+
import RawLikers from '../../types/raw/tweet/Favouriters';
|
|
12
|
+
import RawRetweeters from '../../types/raw/tweet/Retweeters';
|
|
13
|
+
|
|
14
|
+
// URLS
|
|
15
|
+
import * as Urls from '../helper/Urls';
|
|
16
|
+
|
|
17
|
+
// EXTRACTORS
|
|
18
|
+
import * as Extractors from "../helper/Extractors";
|
|
19
|
+
|
|
20
|
+
// DESERIALIZERS
|
|
21
|
+
import * as Deserializers from '../helper/Deserializers';
|
|
22
|
+
|
|
23
|
+
// PARSERS
|
|
24
|
+
import { toQueryString } from '../helper/Parser';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A service that deals with fetching of data related to tweets
|
|
28
|
+
*/
|
|
29
|
+
export class TweetService extends FetcherService {
|
|
30
|
+
// MEMBER METHODS
|
|
31
|
+
constructor(auth: AuthService) {
|
|
32
|
+
super(auth);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @returns The list of tweets that match the given filter
|
|
37
|
+
* @param filter The filter be used for searching the tweets
|
|
38
|
+
* @param count The number of tweets to fetch
|
|
39
|
+
* @param cursor The cursor to the next batch of tweets. If blank, first batch is fetched
|
|
40
|
+
*/
|
|
41
|
+
async getTweets(filter: TweetFilter, count: number, cursor: string): Promise<CursoredData<Tweet>> {
|
|
42
|
+
// Getting the raw data
|
|
43
|
+
let res = await this.request<RawTweets>(Urls.tweetsUrl(toQueryString(filter), count, cursor), false).then(res => res.data);
|
|
44
|
+
|
|
45
|
+
// Extracting data
|
|
46
|
+
let data = Extractors.extractTweets(res);
|
|
47
|
+
|
|
48
|
+
// Caching data
|
|
49
|
+
this.cacheData(data);
|
|
50
|
+
|
|
51
|
+
// Parsing data
|
|
52
|
+
let tweets = data.required.map(item => Deserializers.toTweet(item));
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
list: tweets,
|
|
56
|
+
next: { value: data.cursor }
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @returns The details of a single tweet with the given tweet id
|
|
62
|
+
* @param tweetId The rest id of the target tweet
|
|
63
|
+
*/
|
|
64
|
+
async getTweetById(tweetId: string): Promise<Tweet> {
|
|
65
|
+
// Getting data from cache
|
|
66
|
+
let cachedData = await this.readData(tweetId);
|
|
67
|
+
|
|
68
|
+
// If data exists in cache
|
|
69
|
+
if (cachedData) {
|
|
70
|
+
return cachedData;
|
|
71
|
+
}
|
|
72
|
+
// If data does not exist in cache
|
|
73
|
+
else {
|
|
74
|
+
// Fetching the raw data
|
|
75
|
+
let res = await this.request<RawTweet>(Urls.tweetDetailsUrl(tweetId)).then(res => res.data);
|
|
76
|
+
|
|
77
|
+
// Extracting data
|
|
78
|
+
let data = Extractors.extractTweet(res, tweetId);
|
|
79
|
+
|
|
80
|
+
// Caching data
|
|
81
|
+
this.cacheData(data);
|
|
82
|
+
|
|
83
|
+
// Parsing data
|
|
84
|
+
let tweet = Deserializers.toTweet(data.required[0]);
|
|
85
|
+
|
|
86
|
+
return tweet;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @returns The list of users who liked the given tweet
|
|
92
|
+
* @param tweetId The rest id of the target tweet
|
|
93
|
+
* @param count The batch size of the list
|
|
94
|
+
* @param cursor The cursor to the next batch of users. If blank, first batch is fetched
|
|
95
|
+
*/
|
|
96
|
+
async getTweetLikers(tweetId: string, count: number, cursor: string): Promise<CursoredData<User>> {
|
|
97
|
+
// Fetching the raw data
|
|
98
|
+
let res = await this.request<RawLikers>(Urls.tweetLikesUrl(tweetId, count, cursor)).then(res => res.data);
|
|
99
|
+
|
|
100
|
+
// Extracting data
|
|
101
|
+
let data = Extractors.extractTweetLikers(res);
|
|
102
|
+
|
|
103
|
+
// Caching data
|
|
104
|
+
this.cacheData(data);
|
|
105
|
+
|
|
106
|
+
// Parsing data
|
|
107
|
+
let users = data.required.map(item => Deserializers.toUser(item));
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
list: users,
|
|
111
|
+
next: { value: data.cursor }
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @returns The list of users who retweeted the given tweet
|
|
117
|
+
* @param tweetId The rest id of the target tweet
|
|
118
|
+
* @param count The batch size of the list
|
|
119
|
+
* @param cursor The cursor to the next batch of users. If blank, first batch is fetched
|
|
120
|
+
*/
|
|
121
|
+
async getTweetRetweeters(tweetId: string, count: number, cursor: string): Promise<CursoredData<User>> {
|
|
122
|
+
// Fetching the raw data
|
|
123
|
+
let res = await this.request<RawRetweeters>(Urls.tweetRetweetUrl(tweetId, count, cursor)).then(res => res.data);
|
|
124
|
+
|
|
125
|
+
// Extracting data
|
|
126
|
+
let data = Extractors.extractTweetRetweeters(res);
|
|
127
|
+
|
|
128
|
+
// Caching data
|
|
129
|
+
this.cacheData(data);
|
|
130
|
+
|
|
131
|
+
// Parsing data
|
|
132
|
+
let users = data.required.map(item => Deserializers.toUser(item));
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
list: users,
|
|
136
|
+
next: { value: data.cursor }
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* @returns The list of replies to the given tweet
|
|
142
|
+
* @param tweetId The rest id of the target tweet
|
|
143
|
+
* @param cursor The cursor to the next batch of replies. If blank, first batch is fetched
|
|
144
|
+
*/
|
|
145
|
+
async getTweetReplies(tweetId: string, cursor: string): Promise<CursoredData<Tweet>> {
|
|
146
|
+
// Fetching the raw data
|
|
147
|
+
let res = await this.request<RawTweet>(Urls.tweetRepliesUrl(tweetId, cursor)).then(res => res.data);
|
|
148
|
+
|
|
149
|
+
// Extracting data
|
|
150
|
+
let data = Extractors.extractTweetReplies(res, tweetId);
|
|
151
|
+
|
|
152
|
+
// Caching data
|
|
153
|
+
this.cacheData(data);
|
|
154
|
+
|
|
155
|
+
// Parsing data
|
|
156
|
+
let tweets = data.required.map(item => Deserializers.toTweet(item));
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
list: tweets,
|
|
160
|
+
next: { value: data.cursor }
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// SERVICES
|
|
2
|
+
import { FetcherService } from '../FetcherService';
|
|
3
|
+
import { AuthService } from '../AuthService';
|
|
4
|
+
|
|
5
|
+
// TYPES
|
|
6
|
+
import { User } from '../../types/UserAccount';
|
|
7
|
+
import { Tweet } from '../../types/Tweet';
|
|
8
|
+
import { CursoredData } from '../../types/Service';
|
|
9
|
+
import RawUser from '../../types/raw/user/User';
|
|
10
|
+
import RawUserTweets from '../../types/raw/user/Tweets';
|
|
11
|
+
import RawUserFollowers from '../../types/raw/user/Followers';
|
|
12
|
+
import RawUserFollowing from '../../types/raw/user/Following';
|
|
13
|
+
import RawUserLikes from '../../types/raw/user/Likes';
|
|
14
|
+
|
|
15
|
+
// URLS
|
|
16
|
+
import * as Urls from '../helper/Urls';
|
|
17
|
+
|
|
18
|
+
// EXTRACTORS
|
|
19
|
+
import * as Extractors from '../helper/Extractors';
|
|
20
|
+
|
|
21
|
+
// DESERIALIZERS
|
|
22
|
+
import * as Deserializers from '../helper/Deserializers';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A service that deals with fetching of data related to user account
|
|
26
|
+
*/
|
|
27
|
+
export class UserAccountService extends FetcherService {
|
|
28
|
+
// MEMBER METHODS
|
|
29
|
+
constructor(auth: AuthService) {
|
|
30
|
+
super(auth);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @returns The user account details of the given user
|
|
35
|
+
* @param screenName The screen name of the target user.
|
|
36
|
+
*/
|
|
37
|
+
async getUserAccountDetails(screenName: string): Promise<User> {
|
|
38
|
+
// Fetching the raw data
|
|
39
|
+
let res: RawUser = await this.request<RawUser>(Urls.userAccountUrl(screenName), false).then(res => res.data);
|
|
40
|
+
|
|
41
|
+
// Extracting data
|
|
42
|
+
let data = Extractors.extractUserAccountDetails(res);
|
|
43
|
+
|
|
44
|
+
// Caching data
|
|
45
|
+
this.cacheData(data);
|
|
46
|
+
|
|
47
|
+
// Parsing data
|
|
48
|
+
let user = Deserializers.toUser(data.required[0]);
|
|
49
|
+
|
|
50
|
+
return user;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @returns The user account details of the user with given rest id
|
|
55
|
+
* @param restId The screen name of the target user.
|
|
56
|
+
*/
|
|
57
|
+
async getUserAccountDetailsById(restId: string): Promise<User> {
|
|
58
|
+
// Getting data from cache
|
|
59
|
+
let cachedData = await this.readData(restId);
|
|
60
|
+
|
|
61
|
+
// If data exists in cache
|
|
62
|
+
if(cachedData) {
|
|
63
|
+
return cachedData;
|
|
64
|
+
}
|
|
65
|
+
// If data does not exist in cache
|
|
66
|
+
else {
|
|
67
|
+
// Fetchin the raw data
|
|
68
|
+
let res = await this.request<RawUser>(Urls.userAccountByIdUrl(restId), false).then(res => res.data);
|
|
69
|
+
|
|
70
|
+
// Extracting data
|
|
71
|
+
let data = Extractors.extractUserAccountDetails(res);
|
|
72
|
+
|
|
73
|
+
// Caching data
|
|
74
|
+
this.cacheData(data);
|
|
75
|
+
|
|
76
|
+
// Parsing data
|
|
77
|
+
let user = Deserializers.toUser(data.required[0]);
|
|
78
|
+
|
|
79
|
+
return user;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @returns The list of users followed by the target user
|
|
85
|
+
* @param userId The rest id of the target user
|
|
86
|
+
* @param count The batch size of the list
|
|
87
|
+
* @param cursor The cursor to next batch. If blank, first batch is fetched
|
|
88
|
+
*/
|
|
89
|
+
async getUserFollowing(userId: string, count: number, cursor: string): Promise<CursoredData<User>> {
|
|
90
|
+
// Fetchin the raw data
|
|
91
|
+
let res = await this.request<RawUserFollowing>(Urls.userFollowingUrl(userId, count, cursor)).then(res => res.data);
|
|
92
|
+
|
|
93
|
+
// Extracting data
|
|
94
|
+
let data = Extractors.extractUserFollow(res);
|
|
95
|
+
|
|
96
|
+
// Caching data
|
|
97
|
+
this.cacheData(data);
|
|
98
|
+
|
|
99
|
+
// Parsing data
|
|
100
|
+
let users = data.required.map(item => Deserializers.toUser(item));
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
list: users,
|
|
104
|
+
next: { value: data.cursor }
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* @returns The list of users following the target user
|
|
110
|
+
* @param userId The rest id of the target user
|
|
111
|
+
* @param count The batch size of the list
|
|
112
|
+
* @param cursor The cursor to next batch. If blank, first batch is fetched
|
|
113
|
+
*/
|
|
114
|
+
async getUserFollowers(userId: string, count: number, cursor: string): Promise<CursoredData<User>> {
|
|
115
|
+
/**
|
|
116
|
+
* When fetching list of followers, the official Twitter API seems to be fetching n + 20 followers,
|
|
117
|
+
* where n is the actual required number of followers.
|
|
118
|
+
* So changing count to count - 20, fixes fetching more than required number of follower
|
|
119
|
+
*/
|
|
120
|
+
// Fetching the raw data
|
|
121
|
+
let res = await this.request<RawUserFollowers>(Urls.userFollowersUrl(userId, (count > 20) ? (count - 20) : count, cursor)).then(res => res.data);
|
|
122
|
+
|
|
123
|
+
// Extracting data
|
|
124
|
+
let data = Extractors.extractUserFollow(res);
|
|
125
|
+
|
|
126
|
+
// Caching data
|
|
127
|
+
this.cacheData(data);
|
|
128
|
+
|
|
129
|
+
// Parsing data
|
|
130
|
+
let users = data.required.map(item => Deserializers.toUser(item));
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
list: users,
|
|
134
|
+
next: { value: data.cursor }
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* @returns The list of tweets liked by the target user
|
|
140
|
+
* @param userId The rest id of the target user
|
|
141
|
+
* @param count The batch size of the list
|
|
142
|
+
* @param cursor The cursor to next batch. If blank, first batch is fetched
|
|
143
|
+
*/
|
|
144
|
+
async getUserLikes(userId: string, count: number, cursor: string): Promise<CursoredData<Tweet>> {
|
|
145
|
+
// Fetching the raw data
|
|
146
|
+
let res = await this.request<RawUserLikes>(Urls.userLikesUrl(userId, count, cursor)).then(res => res.data);
|
|
147
|
+
|
|
148
|
+
// Extracting data
|
|
149
|
+
let data = Extractors.extractUserLikes(res);
|
|
150
|
+
|
|
151
|
+
// Caching data
|
|
152
|
+
this.cacheData(data);
|
|
153
|
+
|
|
154
|
+
// Parsing data
|
|
155
|
+
let tweets = data.required.map(item => Deserializers.toTweet(item));
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
list: tweets,
|
|
159
|
+
next: { value: data.cursor }
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* @returns The list of tweets made by the target user
|
|
165
|
+
* @param userId The rest id of the target user
|
|
166
|
+
* @param count The batch size of the list
|
|
167
|
+
* @param cursor The cursor to next batch. If blank, first batch is fetched
|
|
168
|
+
*/
|
|
169
|
+
async getUserTweets(userId: string, count: number, cursor: string): Promise<CursoredData<Tweet>> {
|
|
170
|
+
// Fetching the raw data
|
|
171
|
+
let res = await this.request<RawUserTweets>(Urls.userTweetsUrl(userId, count, cursor)).then(res => res.data);
|
|
172
|
+
|
|
173
|
+
// Extracting data
|
|
174
|
+
let data = Extractors.extractUserTweets(res);
|
|
175
|
+
|
|
176
|
+
// Caching data
|
|
177
|
+
this.cacheData(data);
|
|
178
|
+
|
|
179
|
+
// Parsing data
|
|
180
|
+
let tweets = data.required.map(item => Deserializers.toTweet(item));
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
list: tweets,
|
|
184
|
+
next: { value: data.cursor }
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// TYPES
|
|
2
|
+
import { User } from '../../types/UserAccount';
|
|
3
|
+
import { Tweet, TweetEntities } from '../../types/Tweet';
|
|
4
|
+
import { Result as RawUser } from '../../types/raw/user/User';
|
|
5
|
+
import { Result as RawTweet, Entities2 as RawTweetEntities } from '../../types/raw/tweet/Tweet';
|
|
6
|
+
|
|
7
|
+
// PARSERS
|
|
8
|
+
import * as Parsers from './Parser';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @returns A User object containing the user details
|
|
12
|
+
* @param data The raw user data from Twitter API
|
|
13
|
+
*/
|
|
14
|
+
export function toUser(data: RawUser): User {
|
|
15
|
+
return {
|
|
16
|
+
id: data.rest_id,
|
|
17
|
+
userName: data.legacy.screen_name,
|
|
18
|
+
fullName: data.legacy.name,
|
|
19
|
+
createdAt: data.legacy.created_at,
|
|
20
|
+
description: data.legacy.description,
|
|
21
|
+
isVerified: data.legacy.verified,
|
|
22
|
+
favouritesCount: data.legacy.favourites_count,
|
|
23
|
+
followersCount: data.legacy.followers_count,
|
|
24
|
+
followingsCount: data.legacy.friends_count,
|
|
25
|
+
statusesCount: data.legacy.statuses_count,
|
|
26
|
+
location: data.legacy.location,
|
|
27
|
+
pinnedTweet: data.legacy.pinned_tweet_ids_str[0],
|
|
28
|
+
profileBanner: data.legacy.profile_banner_url,
|
|
29
|
+
profileImage: data.legacy.profile_image_url_https
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @returns A TweetEntities object containing the various tweet entities
|
|
35
|
+
* @param data The raw tweet entities data from the response received from TwitterAPI
|
|
36
|
+
*/
|
|
37
|
+
export function toTweetEntities(data: RawTweetEntities): TweetEntities {
|
|
38
|
+
let entities: TweetEntities = {
|
|
39
|
+
mentionedUsers: [],
|
|
40
|
+
urls: [],
|
|
41
|
+
media: [],
|
|
42
|
+
hashtags: []
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// Extracting user mentions
|
|
46
|
+
if(data.user_mentions) {
|
|
47
|
+
for(let user of data.user_mentions) {
|
|
48
|
+
entities.mentionedUsers.push(user.id_str);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Extracting urls
|
|
53
|
+
if(data.urls) {
|
|
54
|
+
for(let url of data.urls) {
|
|
55
|
+
entities.urls.push(url.expanded_url);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Extracting hashtags
|
|
60
|
+
if(data.hashtags) {
|
|
61
|
+
for(let hashtag of data.hashtags) {
|
|
62
|
+
entities.hashtags.push(hashtag.text);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Extracting media urls (if any)
|
|
67
|
+
if(data.media) {
|
|
68
|
+
for(const media of data.media) {
|
|
69
|
+
entities.media.push(media.media_url_https);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return entities;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @returns A Tweet object containing the tweet data
|
|
78
|
+
* @param data The raw tweet data from the response received from TwitterAPI
|
|
79
|
+
*/
|
|
80
|
+
export function toTweet(data: RawTweet): Tweet {
|
|
81
|
+
return {
|
|
82
|
+
id: data.rest_id,
|
|
83
|
+
createdAt: data.legacy.created_at,
|
|
84
|
+
tweetBy: data.legacy.user_id_str,
|
|
85
|
+
entities: toTweetEntities(data.legacy.entities),
|
|
86
|
+
quoted: data.legacy.quoted_status_id_str,
|
|
87
|
+
fullText: Parsers.normalizeText(data.legacy.full_text),
|
|
88
|
+
replyTo: data.legacy.in_reply_to_status_id_str,
|
|
89
|
+
lang: data.legacy.lang,
|
|
90
|
+
quoteCount: data.legacy.quote_count,
|
|
91
|
+
replyCount: data.legacy.reply_count,
|
|
92
|
+
retweetCount: data.legacy.retweet_count,
|
|
93
|
+
likeCount: data.legacy.favorite_count
|
|
94
|
+
};
|
|
95
|
+
}
|