diff --git a/package.json b/package.json index 223024dd..27a780ff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rettiwt-api", - "version": "4.1.0-alpha.0", + "version": "4.1.0-alpha.1", "main": "dist/index.js", "types": "dist/index.d.ts", "description": "An API for fetching data from TwitterAPI, without any rate limits!", diff --git a/src/commands/User.ts b/src/commands/User.ts index fadc8183..833003b9 100644 --- a/src/commands/User.ts +++ b/src/commands/User.ts @@ -126,20 +126,6 @@ function createUserCommand(rettiwt: Rettiwt): Command { } }); - // Notifications - user.command('notifications') - .description('Fetch you list of notifications') - .argument('[count]', 'The number of notifications to fetch') - .argument('[cursor]', 'The cursor to the batch of notifications to fetch') - .action(async (count?: string, cursor?: string) => { - try { - const notifications = await rettiwt.user.notifications(count ? parseInt(count) : undefined, cursor); - output(notifications); - } catch (error) { - output(error); - } - }); - // Recommended user.command('recommended') .description('Fetch your recommended feed') diff --git a/src/models/data/CursoredData.ts b/src/models/data/CursoredData.ts index 97d8d32b..6399db7b 100644 --- a/src/models/data/CursoredData.ts +++ b/src/models/data/CursoredData.ts @@ -20,7 +20,7 @@ export class CursoredData { public list: T[] = []; /** The cursor to the next batch of data. */ - public next: Cursor; + public next: Cursor = new Cursor(''); /** * @param response - The raw response. @@ -29,13 +29,14 @@ export class CursoredData { public constructor(response: NonNullable, type: EBaseType) { if (type == EBaseType.TWEET) { this.list = Tweet.list(response) as T[]; + this.next = new Cursor(findByFilter(response, 'cursorType', 'Bottom')[0]?.value ?? ''); } else if (type == EBaseType.USER) { this.list = User.list(response) as T[]; + this.next = new Cursor(findByFilter(response, 'cursorType', 'Bottom')[0]?.value ?? ''); } else if (type == EBaseType.NOTIFICATION) { this.list = Notification.list(response) as T[]; + this.next = new Cursor(findByFilter(response, 'cursorType', 'Top')[0]?.value ?? ''); } - - this.next = new Cursor(findByFilter(response, 'cursorType', 'Bottom')[0]?.value ?? ''); } } diff --git a/src/services/public/TweetService.ts b/src/services/public/TweetService.ts index 564098fd..36efc119 100644 --- a/src/services/public/TweetService.ts +++ b/src/services/public/TweetService.ts @@ -454,9 +454,10 @@ export class TweetService extends FetcherService { * // Creating a new Rettiwt instance using the given 'API_KEY' * const rettiwt = new Rettiwt({ apiKey: API_KEY }); * - * // Streaming all upcoming tweets from user 'user1' - * (async () => { + * // Creating a function that streams all new tweets from the user 'user1' + * async function streamTweets() { * try { + * // Awaiting for the tweets returned by the AsyncGenerator returned by the method * for await (const tweet of rettiwt.tweet.stream({ fromUsers: ['user1'] }, 1000)) { * console.log(tweet.fullText); * } @@ -464,7 +465,10 @@ export class TweetService extends FetcherService { * catch (err) { * console.log(err); * } - * })(); + * } + * + * // Calling the function + * streamTweets(); * ``` */ public async *stream(filter: TweetFilter, pollingInterval: number = 60000): AsyncGenerator { diff --git a/src/services/public/UserService.ts b/src/services/public/UserService.ts index f49b6aaa..f86ff2fa 100644 --- a/src/services/public/UserService.ts +++ b/src/services/public/UserService.ts @@ -394,12 +394,11 @@ export class UserService extends FetcherService { } /** - * Get the list of notifications of the logged in user. + * Stream notifications of the logged in user in pseudo real-time. * - * @param count - The number of notifications to fetch, must be \<= 40. - * @param cursor - The cursor to the batch of notifications to fetch + * @param pollingInterval - The interval in milliseconds to poll for new tweets. Default interval is 60000 ms. * - * @returns The list of notifications of the target user. + * @returns An async generator that yields new notifications as they are received. * * @example * ``` @@ -408,32 +407,62 @@ export class UserService extends FetcherService { * // Creating a new Rettiwt instance using the given 'API_KEY' * const rettiwt = new Rettiwt({ apiKey: API_KEY }); * - * // Fetching the recent 40 Notifications of the logged in user - * rettiwt.user.notifications(40) - * .then(res => { - * console.log(res); - * }) - * .catch(err => { - * console.log(err); - * }); + * // Creating a function that streams all new notifications + * async function streamNotifications() { + * try { + * // Awaiting for the notifications returned by the AsyncGenerator returned by the method + * for await (const notification of rettiwt.user.notifications(1000)) { + * console.log(notification.message); + * } + * } + * catch (err) { + * console.log(err); + * } + * } + * + * // Calling the function + * streamNotifications(); * ``` */ - public async notifications(count?: number, cursor?: string): Promise> { + public async *notifications(pollingInterval: number = 60000): AsyncGenerator { const resource = EResourceType.USER_NOTIFICATIONS; - // Fetching raw list of notifications - const response = await this.request(resource, { - count: count, - cursor: cursor, - }); - - // Deserializing response - const data = extractors[resource](response); - - // Sorting the notifications by time, from recent to oldest - data.list.sort((a, b) => new Date(b.receivedAt).valueOf() - new Date(a.receivedAt).valueOf()); - - return data; + /** Whether it's the first batch of notifications or not. */ + let first: boolean = true; + + /** The cursor to the last notification received. */ + let cursor: string | undefined = undefined; + + while (true) { + // Pause execution for the specified polling interval before proceeding to the next iteration + await new Promise((resolve) => setTimeout(resolve, pollingInterval)); + + // Get the batch of notifications after the given cursor + const response = await this.request(resource, { + count: 40, + cursor: cursor, + }); + + // Deserializing response + const notifications = extractors[resource](response); + + // Sorting the notifications by time, from oldest to recent + notifications.list.sort((a, b) => new Date(a.receivedAt).valueOf() - new Date(b.receivedAt).valueOf()); + + // If not first batch, return new notifications + if (!first) { + // Yield the notifications + for (const notification of notifications.list) { + yield notification; + } + } + // Else do nothing, do nothing since first batch is notifications that have already been received + else { + first = false; + } + + cursor = notifications.next.value; + } } /**