rettiwt-api 6.0.8 → 6.1.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/.github/ISSUE_TEMPLATE/bug-report.yml +57 -0
- package/.github/ISSUE_TEMPLATE/feature-request.yml +20 -0
- package/.github/ISSUE_TEMPLATE/question.yml +15 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +32 -0
- package/.github/workflows/ci.yml +33 -0
- package/.nvmrc +1 -0
- package/README.md +30 -6
- package/dist/Rettiwt.d.ts +3 -0
- package/dist/Rettiwt.js +4 -0
- package/dist/Rettiwt.js.map +1 -1
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/collections/Extractors.d.ts +24 -0
- package/dist/collections/Extractors.js +14 -0
- package/dist/collections/Extractors.js.map +1 -1
- package/dist/collections/Groups.js +11 -0
- package/dist/collections/Groups.js.map +1 -1
- package/dist/collections/Requests.js +12 -0
- package/dist/collections/Requests.js.map +1 -1
- package/dist/commands/DirectMessage.d.ts +10 -0
- package/dist/commands/DirectMessage.js +57 -0
- package/dist/commands/DirectMessage.js.map +1 -0
- package/dist/commands/List.js +44 -3
- package/dist/commands/List.js.map +1 -1
- package/dist/commands/Tweet.js +29 -1
- package/dist/commands/Tweet.js.map +1 -1
- package/dist/commands/User.js +39 -1
- package/dist/commands/User.js.map +1 -1
- package/dist/enums/Data.d.ts +3 -1
- package/dist/enums/Data.js +2 -0
- package/dist/enums/Data.js.map +1 -1
- package/dist/enums/Resource.d.ts +11 -0
- package/dist/enums/Resource.js +12 -0
- package/dist/enums/Resource.js.map +1 -1
- package/dist/enums/raw/Analytics.d.ts +6 -3
- package/dist/enums/raw/Analytics.js +5 -2
- package/dist/enums/raw/Analytics.js.map +1 -1
- package/dist/index.d.ts +15 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/models/args/FetchArgs.d.ts +9 -0
- package/dist/models/args/FetchArgs.js +16 -0
- package/dist/models/args/FetchArgs.js.map +1 -1
- package/dist/models/args/PostArgs.d.ts +1 -0
- package/dist/models/args/PostArgs.js +2 -0
- package/dist/models/args/PostArgs.js.map +1 -1
- package/dist/models/data/Analytics.d.ts +43 -0
- package/dist/models/data/Analytics.js +92 -0
- package/dist/models/data/Analytics.js.map +1 -0
- package/dist/models/data/Conversation.d.ts +93 -0
- package/dist/models/data/Conversation.js +293 -0
- package/dist/models/data/Conversation.js.map +1 -0
- package/dist/models/data/CursoredData.d.ts +2 -1
- package/dist/models/data/CursoredData.js +6 -1
- package/dist/models/data/CursoredData.js.map +1 -1
- package/dist/models/data/DirectMessage.d.ts +105 -0
- package/dist/models/data/DirectMessage.js +284 -0
- package/dist/models/data/DirectMessage.js.map +1 -0
- package/dist/models/data/Inbox.d.ts +44 -0
- package/dist/models/data/Inbox.js +106 -0
- package/dist/models/data/Inbox.js.map +1 -0
- package/dist/models/data/List.d.ts +20 -0
- package/dist/models/data/List.js +50 -1
- package/dist/models/data/List.js.map +1 -1
- package/dist/models/data/Tweet.d.ts +6 -6
- package/dist/models/data/Tweet.js +4 -2
- package/dist/models/data/Tweet.js.map +1 -1
- package/dist/models/data/User.d.ts +2 -0
- package/dist/models/data/User.js +6 -0
- package/dist/models/data/User.js.map +1 -1
- package/dist/requests/DirectMessage.d.ts +28 -0
- package/dist/requests/DirectMessage.js +149 -0
- package/dist/requests/DirectMessage.js.map +1 -0
- package/dist/requests/List.d.ts +10 -0
- package/dist/requests/List.js +52 -0
- package/dist/requests/List.js.map +1 -1
- package/dist/requests/Tweet.d.ts +8 -0
- package/dist/requests/Tweet.js +30 -0
- package/dist/requests/Tweet.js.map +1 -1
- package/dist/requests/User.d.ts +8 -1
- package/dist/requests/User.js +67 -8
- package/dist/requests/User.js.map +1 -1
- package/dist/services/public/DirectMessageService.d.ts +100 -0
- package/dist/services/public/DirectMessageService.js +143 -0
- package/dist/services/public/DirectMessageService.js.map +1 -0
- package/dist/services/public/FetcherService.d.ts +2 -2
- package/dist/services/public/FetcherService.js +4 -4
- package/dist/services/public/FetcherService.js.map +1 -1
- package/dist/services/public/ListService.d.ts +85 -0
- package/dist/services/public/ListService.js +111 -0
- package/dist/services/public/ListService.js.map +1 -1
- package/dist/services/public/TweetService.d.ts +56 -0
- package/dist/services/public/TweetService.js +72 -0
- package/dist/services/public/TweetService.js.map +1 -1
- package/dist/services/public/UserService.d.ts +61 -2
- package/dist/services/public/UserService.js +89 -0
- package/dist/services/public/UserService.js.map +1 -1
- package/dist/types/args/FetchArgs.d.ts +69 -12
- package/dist/types/args/PostArgs.d.ts +29 -11
- package/dist/types/data/Analytics.d.ts +42 -0
- package/dist/types/data/Analytics.js +3 -0
- package/dist/types/data/Analytics.js.map +1 -0
- package/dist/types/data/Conversation.d.ts +32 -0
- package/dist/types/data/Conversation.js +3 -0
- package/dist/types/data/Conversation.js.map +1 -0
- package/dist/types/data/CursoredData.d.ts +4 -1
- package/dist/types/data/DirectMessage.d.ts +25 -0
- package/dist/types/data/DirectMessage.js +3 -0
- package/dist/types/data/DirectMessage.js.map +1 -0
- package/dist/types/data/Inbox.d.ts +18 -0
- package/dist/types/data/Inbox.js +3 -0
- package/dist/types/data/Inbox.js.map +1 -0
- package/dist/types/data/List.d.ts +5 -1
- package/dist/types/data/Tweet.d.ts +6 -6
- package/dist/types/data/User.d.ts +4 -0
- package/dist/types/raw/base/Analytic.d.ts +6 -1
- package/dist/types/raw/base/Message.d.ts +16 -0
- package/dist/types/raw/base/Message.js +4 -0
- package/dist/types/raw/base/Message.js.map +1 -0
- package/dist/types/raw/base/Tweet.d.ts +6 -6
- package/dist/types/raw/base/User.d.ts +2 -1
- package/dist/types/raw/composite/TimelineList.d.ts +9 -0
- package/dist/types/raw/composite/TimelineList.js +3 -0
- package/dist/types/raw/composite/TimelineList.js.map +1 -0
- package/dist/types/raw/dm/Conversation.d.ts +55 -0
- package/dist/types/raw/dm/Conversation.js +4 -0
- package/dist/types/raw/dm/Conversation.js.map +1 -0
- package/dist/types/raw/dm/InboxInitial.d.ts +137 -0
- package/dist/types/raw/dm/InboxInitial.js +4 -0
- package/dist/types/raw/dm/InboxInitial.js.map +1 -0
- package/dist/types/raw/dm/InboxTimeline.d.ts +287 -0
- package/dist/types/raw/dm/InboxTimeline.js +4 -0
- package/dist/types/raw/dm/InboxTimeline.js.map +1 -0
- package/dist/types/raw/dm/UserUpdates.d.ts +41 -0
- package/dist/types/raw/dm/UserUpdates.js +4 -0
- package/dist/types/raw/dm/UserUpdates.js.map +1 -0
- package/dist/types/raw/list/AddMember.d.ts +151 -0
- package/dist/types/raw/list/AddMember.js +4 -0
- package/dist/types/raw/list/AddMember.js.map +1 -0
- package/dist/types/raw/list/Details.d.ts +44 -13
- package/dist/types/raw/list/RemoveMember.d.ts +150 -0
- package/dist/types/raw/list/RemoveMember.js +4 -0
- package/dist/types/raw/list/RemoveMember.js.map +1 -0
- package/dist/types/raw/tweet/Bookmark.d.ts +12 -0
- package/dist/types/raw/tweet/Bookmark.js +4 -0
- package/dist/types/raw/tweet/Bookmark.js.map +1 -0
- package/dist/types/raw/tweet/Unbookmark.d.ts +11 -0
- package/dist/types/raw/tweet/Unbookmark.js +4 -0
- package/dist/types/raw/tweet/Unbookmark.js.map +1 -0
- package/dist/types/raw/user/Analytics.d.ts +6 -18
- package/dist/types/raw/user/Analytics.js +0 -1
- package/dist/types/raw/user/Analytics.js.map +1 -1
- package/dist/types/raw/user/Lists.d.ts +319 -0
- package/dist/types/raw/user/Lists.js +4 -0
- package/dist/types/raw/user/Lists.js.map +1 -0
- package/eslint.config.mjs +1 -1
- package/package.json +11 -6
- package/playground/.env.example +1 -0
- package/playground/README.md +53 -0
- package/playground/index.js +15 -0
- package/playground/package.json +15 -0
- package/src/Rettiwt.ts +5 -0
- package/src/cli.ts +2 -0
- package/src/collections/Extractors.ts +29 -0
- package/src/collections/Groups.ts +11 -0
- package/src/collections/Requests.ts +20 -0
- package/src/commands/DirectMessage.ts +62 -0
- package/src/commands/List.ts +44 -3
- package/src/commands/Tweet.ts +29 -1
- package/src/commands/User.ts +65 -1
- package/src/enums/Data.ts +2 -0
- package/src/enums/Resource.ts +13 -0
- package/src/enums/raw/Analytics.ts +5 -2
- package/src/index.ts +15 -0
- package/src/models/args/FetchArgs.ts +17 -0
- package/src/models/args/PostArgs.ts +2 -0
- package/src/models/data/Analytics.ts +97 -0
- package/src/models/data/Conversation.ts +344 -0
- package/src/models/data/CursoredData.ts +7 -2
- package/src/models/data/DirectMessage.ts +335 -0
- package/src/models/data/Inbox.ts +124 -0
- package/src/models/data/List.ts +60 -1
- package/src/models/data/Tweet.ts +10 -8
- package/src/models/data/User.ts +6 -0
- package/src/requests/DirectMessage.ts +233 -0
- package/src/requests/List.ts +58 -0
- package/src/requests/Tweet.ts +32 -0
- package/src/requests/User.ts +70 -7
- package/src/services/public/DirectMessageService.ts +159 -0
- package/src/services/public/FetcherService.ts +3 -3
- package/src/services/public/ListService.ts +127 -0
- package/src/services/public/TweetService.ts +82 -0
- package/src/services/public/UserService.ts +110 -2
- package/src/types/args/FetchArgs.ts +77 -12
- package/src/types/args/PostArgs.ts +31 -11
- package/src/types/data/Analytics.ts +58 -0
- package/src/types/data/Conversation.ts +44 -0
- package/src/types/data/CursoredData.ts +4 -1
- package/src/types/data/DirectMessage.ts +33 -0
- package/src/types/data/Inbox.ts +23 -0
- package/src/types/data/List.ts +7 -1
- package/src/types/data/Tweet.ts +6 -6
- package/src/types/data/User.ts +6 -0
- package/src/types/raw/base/Analytic.ts +7 -1
- package/src/types/raw/base/Message.ts +22 -0
- package/src/types/raw/base/Tweet.ts +6 -6
- package/src/types/raw/base/User.ts +2 -1
- package/src/types/raw/composite/TimelineList.ts +10 -0
- package/src/types/raw/dm/Conversation.ts +59 -0
- package/src/types/raw/dm/InboxInitial.ts +155 -0
- package/src/types/raw/dm/InboxTimeline.ts +301 -0
- package/src/types/raw/dm/UserUpdates.ts +46 -0
- package/src/types/raw/list/AddMember.ts +175 -0
- package/src/types/raw/list/Details.ts +52 -13
- package/src/types/raw/list/RemoveMember.ts +174 -0
- package/src/types/raw/tweet/Bookmark.ts +14 -0
- package/src/types/raw/tweet/Unbookmark.ts +14 -0
- package/src/types/raw/user/Analytics.ts +6 -22
- package/src/types/raw/user/Lists.ts +378 -0
- package/tsconfig.json +1 -1
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import { IDirectMessage } from '../../types/data/DirectMessage';
|
|
2
|
+
import { IMessage as IRawMessage } from '../../types/raw/base/Message';
|
|
3
|
+
import { IConversationTimelineResponse } from '../../types/raw/dm/Conversation';
|
|
4
|
+
import { IInboxInitialResponse } from '../../types/raw/dm/InboxInitial';
|
|
5
|
+
import { IInboxTimelineResponse } from '../../types/raw/dm/InboxTimeline';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Type guard to check if the response is an IInboxInitialResponse
|
|
9
|
+
*/
|
|
10
|
+
function isInboxInitialResponse(
|
|
11
|
+
response: IInboxInitialResponse | IConversationTimelineResponse | IInboxTimelineResponse,
|
|
12
|
+
): response is IInboxInitialResponse {
|
|
13
|
+
return 'inbox_initial_state' in response;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Type guard to check if the response is an IConversationTimelineResponse
|
|
18
|
+
*/
|
|
19
|
+
function isConversationTimelineResponse(
|
|
20
|
+
response: IInboxInitialResponse | IConversationTimelineResponse | IInboxTimelineResponse,
|
|
21
|
+
): response is IConversationTimelineResponse {
|
|
22
|
+
return 'conversation_timeline' in response;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Type guard to check if the response is an IInboxTimelineResponse
|
|
27
|
+
*/
|
|
28
|
+
function isInboxTimelineResponse(
|
|
29
|
+
response: IInboxInitialResponse | IConversationTimelineResponse | IInboxTimelineResponse,
|
|
30
|
+
): response is IInboxTimelineResponse {
|
|
31
|
+
return 'inbox_timeline' in response;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The details of a single direct message.
|
|
36
|
+
*
|
|
37
|
+
* @public
|
|
38
|
+
*/
|
|
39
|
+
export class DirectMessage implements IDirectMessage {
|
|
40
|
+
/** The raw message details. */
|
|
41
|
+
private readonly _raw: IRawMessage;
|
|
42
|
+
|
|
43
|
+
public conversationId: string;
|
|
44
|
+
public createdAt: string;
|
|
45
|
+
public editCount?: number;
|
|
46
|
+
public id: string;
|
|
47
|
+
public mediaUrls?: string[];
|
|
48
|
+
public read?: boolean;
|
|
49
|
+
public recipientId?: string;
|
|
50
|
+
public senderId: string;
|
|
51
|
+
public text: string;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param message - The raw message details from the API response.
|
|
55
|
+
*/
|
|
56
|
+
public constructor(message: unknown) {
|
|
57
|
+
this._raw = message as IRawMessage;
|
|
58
|
+
|
|
59
|
+
const parsedData = this._parseMessageData(message);
|
|
60
|
+
|
|
61
|
+
this.id = parsedData.id;
|
|
62
|
+
this.conversationId = parsedData.conversationId;
|
|
63
|
+
this.senderId = parsedData.senderId;
|
|
64
|
+
this.recipientId = parsedData.recipientId;
|
|
65
|
+
this.text = parsedData.text;
|
|
66
|
+
this.createdAt = parsedData.createdAt;
|
|
67
|
+
this.editCount = parsedData.editCount ?? 0;
|
|
68
|
+
this.mediaUrls = this._extractMediaUrls(message);
|
|
69
|
+
this.read = true; // Default to true, can be enhanced later
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** The raw message details. */
|
|
73
|
+
public get raw(): IRawMessage {
|
|
74
|
+
return this._raw;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Extract messages from conversation timeline response
|
|
79
|
+
*/
|
|
80
|
+
private static _extractFromConversationTimeline(response: IConversationTimelineResponse): DirectMessage[] {
|
|
81
|
+
const messages: DirectMessage[] = [];
|
|
82
|
+
const entries = response.conversation_timeline?.entries ?? [];
|
|
83
|
+
|
|
84
|
+
for (const entry of entries) {
|
|
85
|
+
if ('message' in entry && entry.message) {
|
|
86
|
+
messages.push(new DirectMessage(entry.message));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return messages;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Extract messages from inbox initial response
|
|
95
|
+
*/
|
|
96
|
+
private static _extractFromInboxInitial(response: IInboxInitialResponse): DirectMessage[] {
|
|
97
|
+
const messages: DirectMessage[] = [];
|
|
98
|
+
const entries = response.inbox_initial_state?.entries ?? [];
|
|
99
|
+
|
|
100
|
+
for (const entry of entries) {
|
|
101
|
+
if ('message' in entry && entry.message) {
|
|
102
|
+
messages.push(new DirectMessage(entry.message));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return messages;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Extract messages from inbox timeline response
|
|
111
|
+
*/
|
|
112
|
+
private static _extractFromInboxTimeline(response: IInboxTimelineResponse): DirectMessage[] {
|
|
113
|
+
const messages: DirectMessage[] = [];
|
|
114
|
+
const entries = response.inbox_timeline?.entries ?? [];
|
|
115
|
+
|
|
116
|
+
for (const entry of entries) {
|
|
117
|
+
if ('message' in entry && entry.message) {
|
|
118
|
+
messages.push(new DirectMessage(entry.message));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return messages;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Extract media URLs from message attachment data with proper type safety.
|
|
127
|
+
*/
|
|
128
|
+
private _extractMediaUrls(message: unknown): string[] | undefined {
|
|
129
|
+
const urls: string[] = [];
|
|
130
|
+
const msg = message as Record<string, unknown>;
|
|
131
|
+
const messageData = msg.message_data as Record<string, unknown> | undefined;
|
|
132
|
+
|
|
133
|
+
// Check for card attachments with images
|
|
134
|
+
const attachment = messageData?.attachment as Record<string, unknown> | undefined;
|
|
135
|
+
const card = attachment?.card as Record<string, unknown> | undefined;
|
|
136
|
+
const bindingValues = card?.binding_values as Record<string, unknown> | undefined;
|
|
137
|
+
|
|
138
|
+
if (bindingValues) {
|
|
139
|
+
// Extract URLs from various image binding values
|
|
140
|
+
const imageBindings = [
|
|
141
|
+
'thumbnail_image',
|
|
142
|
+
'photo_image_full_size',
|
|
143
|
+
'summary_photo_image',
|
|
144
|
+
'thumbnail_image_original',
|
|
145
|
+
'summary_photo_image_original',
|
|
146
|
+
'photo_image_full_size_original',
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
for (const bindingKey of imageBindings) {
|
|
150
|
+
const imageBinding = bindingValues[bindingKey] as Record<string, unknown> | undefined;
|
|
151
|
+
const imageValue = imageBinding?.image_value as Record<string, unknown> | undefined;
|
|
152
|
+
|
|
153
|
+
if (imageValue?.url && typeof imageValue.url === 'string') {
|
|
154
|
+
urls.push(imageValue.url);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Check for tweet attachments
|
|
160
|
+
const tweet = attachment?.tweet as Record<string, unknown> | undefined;
|
|
161
|
+
if (tweet?.expanded_url && typeof tweet.expanded_url === 'string') {
|
|
162
|
+
urls.push(tweet.expanded_url);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return urls.length > 0 ? [...new Set(urls)] : undefined; // Remove duplicates
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Safely extract number value
|
|
170
|
+
*/
|
|
171
|
+
private _extractNumberValue(value: unknown): number | undefined {
|
|
172
|
+
if (typeof value === 'number') {
|
|
173
|
+
return value;
|
|
174
|
+
}
|
|
175
|
+
if (typeof value === 'string') {
|
|
176
|
+
const parsed = Number(value);
|
|
177
|
+
return isNaN(parsed) ? undefined : parsed;
|
|
178
|
+
}
|
|
179
|
+
return undefined;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Safely extract string value with fallback
|
|
184
|
+
*/
|
|
185
|
+
private _extractStringValue(...values: unknown[]): string | undefined {
|
|
186
|
+
for (const value of values) {
|
|
187
|
+
if (typeof value === 'string' && value.length > 0) {
|
|
188
|
+
return value;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return undefined;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Parse message data with proper type safety
|
|
196
|
+
*/
|
|
197
|
+
private _parseMessageData(message: unknown): IDirectMessage {
|
|
198
|
+
const msg = message as Record<string, unknown>;
|
|
199
|
+
const messageData = msg.message_data as Record<string, unknown> | undefined;
|
|
200
|
+
|
|
201
|
+
const id = this._extractStringValue(messageData?.id, msg.id) ?? '';
|
|
202
|
+
const conversationId = this._extractStringValue(msg.conversation_id, messageData?.conversation_id) ?? '';
|
|
203
|
+
const senderId = this._extractStringValue(messageData?.sender_id, msg.sender_id) ?? '';
|
|
204
|
+
const recipientId = this._extractStringValue(messageData?.recipient_id, msg.recipient_id);
|
|
205
|
+
const text = this._extractStringValue(messageData?.text, msg.text) ?? '';
|
|
206
|
+
const createdAt = this._parseTimestamp(this._extractStringValue(messageData?.time, msg.time) ?? '');
|
|
207
|
+
const editCount = this._extractNumberValue(messageData?.edit_count);
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
id,
|
|
211
|
+
conversationId,
|
|
212
|
+
senderId,
|
|
213
|
+
recipientId,
|
|
214
|
+
createdAt,
|
|
215
|
+
text,
|
|
216
|
+
editCount,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Parse timestamp with proper validation
|
|
222
|
+
*/
|
|
223
|
+
private _parseTimestamp(timestamp: string): string {
|
|
224
|
+
const numericTimestamp = Number(timestamp);
|
|
225
|
+
if (!isNaN(numericTimestamp)) {
|
|
226
|
+
const date = new Date(numericTimestamp);
|
|
227
|
+
if (!isNaN(date.getTime())) {
|
|
228
|
+
return date.toISOString();
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return new Date().toISOString();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Filter messages by conversation ID
|
|
236
|
+
*/
|
|
237
|
+
public static filterByConversation(messages: DirectMessage[], conversationId: string): DirectMessage[] {
|
|
238
|
+
return messages.filter((message) => message.conversationId === conversationId);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Filter messages by sender ID
|
|
243
|
+
*/
|
|
244
|
+
public static filterBySender(messages: DirectMessage[], senderId: string): DirectMessage[] {
|
|
245
|
+
return messages.filter((message) => message.isFromSender(senderId));
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Extracts and deserializes the list of direct messages from the given raw response data.
|
|
250
|
+
*
|
|
251
|
+
* @param response - The raw response data.
|
|
252
|
+
*
|
|
253
|
+
* @returns The deserialized list of direct messages.
|
|
254
|
+
*/
|
|
255
|
+
public static list(
|
|
256
|
+
response: IInboxInitialResponse | IConversationTimelineResponse | IInboxTimelineResponse,
|
|
257
|
+
): DirectMessage[] {
|
|
258
|
+
const messages: DirectMessage[] = [];
|
|
259
|
+
|
|
260
|
+
if (isInboxInitialResponse(response)) {
|
|
261
|
+
return DirectMessage._extractFromInboxInitial(response);
|
|
262
|
+
} else if (isConversationTimelineResponse(response)) {
|
|
263
|
+
return DirectMessage._extractFromConversationTimeline(response);
|
|
264
|
+
} else if (isInboxTimelineResponse(response)) {
|
|
265
|
+
return DirectMessage._extractFromInboxTimeline(response);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return messages;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Generic method to extract messages from any supported response type
|
|
273
|
+
*/
|
|
274
|
+
public static listFromResponse(
|
|
275
|
+
response: IInboxInitialResponse | IConversationTimelineResponse | IInboxTimelineResponse,
|
|
276
|
+
): DirectMessage[] {
|
|
277
|
+
return DirectMessage.list(response);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Sort messages by creation time (oldest to newest)
|
|
282
|
+
*/
|
|
283
|
+
public static sortByTime(messages: DirectMessage[], ascending = true): DirectMessage[] {
|
|
284
|
+
return [...messages].sort((a, b) => {
|
|
285
|
+
const timeA = new Date(a.createdAt).getTime();
|
|
286
|
+
const timeB = new Date(b.createdAt).getTime();
|
|
287
|
+
return ascending ? timeA - timeB : timeB - timeA;
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Get the age of this message in milliseconds
|
|
293
|
+
*/
|
|
294
|
+
public getAgeInMs(): number {
|
|
295
|
+
return Date.now() - new Date(this.createdAt).getTime();
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Check if this message has media attachments
|
|
300
|
+
*/
|
|
301
|
+
public hasMedia(): boolean {
|
|
302
|
+
return Boolean(this.mediaUrls && this.mediaUrls.length > 0);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Check if this message is from a specific sender
|
|
307
|
+
*/
|
|
308
|
+
public isFromSender(senderId: string): boolean {
|
|
309
|
+
return this.senderId === senderId;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* @returns A serializable JSON representation of `this` object.
|
|
314
|
+
*/
|
|
315
|
+
public toJSON(): IDirectMessage {
|
|
316
|
+
return {
|
|
317
|
+
conversationId: this.conversationId,
|
|
318
|
+
createdAt: this.createdAt,
|
|
319
|
+
editCount: this.editCount,
|
|
320
|
+
id: this.id,
|
|
321
|
+
mediaUrls: this.mediaUrls,
|
|
322
|
+
read: this.read,
|
|
323
|
+
recipientId: this.recipientId,
|
|
324
|
+
senderId: this.senderId,
|
|
325
|
+
text: this.text,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Check if this message was edited
|
|
331
|
+
*/
|
|
332
|
+
public wasEdited(): boolean {
|
|
333
|
+
return Boolean(this.editCount && this.editCount > 0);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { IInbox } from '../../types/data/Inbox';
|
|
2
|
+
import { IInboxInitialResponse } from '../../types/raw/dm/InboxInitial';
|
|
3
|
+
import { IInboxTimelineResponse } from '../../types/raw/dm/InboxTimeline';
|
|
4
|
+
|
|
5
|
+
import { Conversation } from './Conversation';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Type guard to check if the response is an IInboxInitialResponse
|
|
9
|
+
*/
|
|
10
|
+
function isInboxInitialResponse(
|
|
11
|
+
response: IInboxInitialResponse | IInboxTimelineResponse,
|
|
12
|
+
): response is IInboxInitialResponse {
|
|
13
|
+
return 'inbox_initial_state' in response;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Type guard to check if the response is an IInboxTimelineResponse
|
|
18
|
+
*/
|
|
19
|
+
function isInboxTimelineResponse(
|
|
20
|
+
response: IInboxInitialResponse | IInboxTimelineResponse,
|
|
21
|
+
): response is IInboxTimelineResponse {
|
|
22
|
+
return 'inbox_timeline' in response;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The details of a DM inbox containing conversations and metadata.
|
|
27
|
+
*
|
|
28
|
+
* @public
|
|
29
|
+
*/
|
|
30
|
+
export class Inbox implements IInbox {
|
|
31
|
+
/** The raw inbox details. */
|
|
32
|
+
private readonly _raw: IInboxInitialResponse | IInboxTimelineResponse;
|
|
33
|
+
|
|
34
|
+
public conversations: Conversation[];
|
|
35
|
+
public cursor: string;
|
|
36
|
+
public lastSeenEventId: string;
|
|
37
|
+
public trustedLastSeenEventId: string;
|
|
38
|
+
public untrustedLastSeenEventId: string;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param response - The raw inbox response from the API.
|
|
42
|
+
*/
|
|
43
|
+
public constructor(response: IInboxInitialResponse | IInboxTimelineResponse) {
|
|
44
|
+
this._raw = response;
|
|
45
|
+
|
|
46
|
+
// Handle inbox initial state response
|
|
47
|
+
if (isInboxInitialResponse(response)) {
|
|
48
|
+
const inboxState = response.inbox_initial_state;
|
|
49
|
+
|
|
50
|
+
this.cursor = inboxState.cursor ?? '';
|
|
51
|
+
this.lastSeenEventId = inboxState.last_seen_event_id ?? '';
|
|
52
|
+
this.trustedLastSeenEventId = inboxState.trusted_last_seen_event_id ?? '';
|
|
53
|
+
this.untrustedLastSeenEventId = inboxState.untrusted_last_seen_event_id ?? '';
|
|
54
|
+
|
|
55
|
+
// Parse conversations from inbox initial state
|
|
56
|
+
this.conversations = Conversation.listFromInboxInitial(response);
|
|
57
|
+
}
|
|
58
|
+
// Handle inbox timeline response
|
|
59
|
+
else if (isInboxTimelineResponse(response)) {
|
|
60
|
+
const inboxTimeline = response.inbox_timeline;
|
|
61
|
+
|
|
62
|
+
this.cursor = inboxTimeline.min_entry_id ?? '';
|
|
63
|
+
this.lastSeenEventId = '';
|
|
64
|
+
this.trustedLastSeenEventId = '';
|
|
65
|
+
this.untrustedLastSeenEventId = '';
|
|
66
|
+
|
|
67
|
+
// Parse conversations from inbox timeline
|
|
68
|
+
this.conversations = Conversation.listFromInboxTimeline(response);
|
|
69
|
+
} else {
|
|
70
|
+
// Fallback defaults (this should never happen with proper typing)
|
|
71
|
+
this.cursor = '';
|
|
72
|
+
this.lastSeenEventId = '';
|
|
73
|
+
this.trustedLastSeenEventId = '';
|
|
74
|
+
this.untrustedLastSeenEventId = '';
|
|
75
|
+
this.conversations = [];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** The raw inbox details. */
|
|
80
|
+
public get raw(): IInboxInitialResponse | IInboxTimelineResponse {
|
|
81
|
+
return this._raw;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Get the raw inbox initial state if this inbox was created from one
|
|
86
|
+
*/
|
|
87
|
+
public getInitialState(): IInboxInitialResponse | undefined {
|
|
88
|
+
return this.isInitialState() ? (this._raw as IInboxInitialResponse) : undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get the raw inbox timeline if this inbox was created from one
|
|
93
|
+
*/
|
|
94
|
+
public getTimeline(): IInboxTimelineResponse | undefined {
|
|
95
|
+
return this.isTimeline() ? (this._raw as IInboxTimelineResponse) : undefined;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Check if this inbox was created from an initial state response
|
|
100
|
+
*/
|
|
101
|
+
public isInitialState(): boolean {
|
|
102
|
+
return isInboxInitialResponse(this._raw);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Check if this inbox was created from a timeline response
|
|
107
|
+
*/
|
|
108
|
+
public isTimeline(): boolean {
|
|
109
|
+
return isInboxTimelineResponse(this._raw);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @returns A serializable JSON representation of `this` object.
|
|
114
|
+
*/
|
|
115
|
+
public toJSON(): IInbox {
|
|
116
|
+
return {
|
|
117
|
+
conversations: this.conversations.map((conv) => conv.toJSON()),
|
|
118
|
+
cursor: this.cursor,
|
|
119
|
+
lastSeenEventId: this.lastSeenEventId,
|
|
120
|
+
trustedLastSeenEventId: this.trustedLastSeenEventId,
|
|
121
|
+
untrustedLastSeenEventId: this.untrustedLastSeenEventId,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
package/src/models/data/List.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
import { LogActions } from '../../enums/Logging';
|
|
2
|
+
import { findByFilter } from '../../helper/JsonUtils';
|
|
3
|
+
import { LogService } from '../../services/internal/LogService';
|
|
1
4
|
import { IList } from '../../types/data/List';
|
|
2
5
|
import { IList as IRawList } from '../../types/raw/base/List';
|
|
6
|
+
import { ITimelineList } from '../../types/raw/composite/TimelineList';
|
|
7
|
+
import { IListDetailsResponse } from '../../types/raw/list/Details';
|
|
3
8
|
|
|
4
9
|
/**
|
|
5
10
|
* The details of a single Twitter List.
|
|
@@ -14,6 +19,8 @@ export class List implements IList {
|
|
|
14
19
|
public createdBy: string;
|
|
15
20
|
public description?: string;
|
|
16
21
|
public id: string;
|
|
22
|
+
public isFollowing: boolean;
|
|
23
|
+
public isMember: boolean;
|
|
17
24
|
public memberCount: number;
|
|
18
25
|
public name: string;
|
|
19
26
|
public subscriberCount: number;
|
|
@@ -27,9 +34,11 @@ export class List implements IList {
|
|
|
27
34
|
this.name = list.name;
|
|
28
35
|
this.createdAt = new Date(list.created_at).toISOString();
|
|
29
36
|
this.description = list.description.length ? list.description : undefined;
|
|
37
|
+
this.isFollowing = list.following;
|
|
38
|
+
this.isMember = list.is_member;
|
|
30
39
|
this.memberCount = list.member_count;
|
|
31
40
|
this.subscriberCount = list.subscriber_count;
|
|
32
|
-
this.createdBy = list.user_results.result.
|
|
41
|
+
this.createdBy = list.user_results.result.rest_id;
|
|
33
42
|
}
|
|
34
43
|
|
|
35
44
|
/** The raw list details. */
|
|
@@ -37,6 +46,54 @@ export class List implements IList {
|
|
|
37
46
|
return { ...this._raw };
|
|
38
47
|
}
|
|
39
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Extracts and deserializes a single target list from the given raw response data.
|
|
51
|
+
*
|
|
52
|
+
* @param response - The raw response data.
|
|
53
|
+
* @param id - The id of the target list.
|
|
54
|
+
*
|
|
55
|
+
* @returns The target deserialized list.
|
|
56
|
+
*/
|
|
57
|
+
public static single(response: IListDetailsResponse, id: string): List | undefined {
|
|
58
|
+
// If list found
|
|
59
|
+
if (response.data.list.id_str === id) {
|
|
60
|
+
return new List(response.data.list as unknown as IRawList);
|
|
61
|
+
}
|
|
62
|
+
// If not found
|
|
63
|
+
else {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Extracts and deserializes the timeline of lists followed from the given raw response data.
|
|
70
|
+
*
|
|
71
|
+
* @param response - The raw response data.
|
|
72
|
+
*
|
|
73
|
+
* @returns The deserialized timeline of lists.
|
|
74
|
+
*/
|
|
75
|
+
public static timeline(response: NonNullable<unknown>): List[] {
|
|
76
|
+
const lists: List[] = [];
|
|
77
|
+
|
|
78
|
+
// Extracting the matching data
|
|
79
|
+
const extract = findByFilter<ITimelineList>(response, '__typename', 'TimelineTwitterList').map(
|
|
80
|
+
(item) => item.list,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// Deserializing valid data
|
|
84
|
+
for (const item of extract) {
|
|
85
|
+
// If valid list
|
|
86
|
+
if (item !== undefined && item.id !== undefined && item.following === true) {
|
|
87
|
+
// Logging
|
|
88
|
+
LogService.log(LogActions.DESERIALIZE, { id: item.id });
|
|
89
|
+
|
|
90
|
+
lists.push(new List(item));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return lists;
|
|
95
|
+
}
|
|
96
|
+
|
|
40
97
|
/**
|
|
41
98
|
* @returns A serializable JSON representation of `this` object.
|
|
42
99
|
*/
|
|
@@ -46,6 +103,8 @@ export class List implements IList {
|
|
|
46
103
|
createdBy: this.createdBy,
|
|
47
104
|
description: this.description,
|
|
48
105
|
id: this.id,
|
|
106
|
+
isFollowing: this.isFollowing,
|
|
107
|
+
isMember: this.isMember,
|
|
49
108
|
memberCount: this.memberCount,
|
|
50
109
|
name: this.name,
|
|
51
110
|
subscriberCount: this.subscriberCount,
|
package/src/models/data/Tweet.ts
CHANGED
|
@@ -22,24 +22,24 @@ export class Tweet implements ITweet {
|
|
|
22
22
|
/** The raw tweet details. */
|
|
23
23
|
private readonly _raw: IRawTweet;
|
|
24
24
|
|
|
25
|
-
public bookmarkCount
|
|
25
|
+
public bookmarkCount?: number;
|
|
26
26
|
public conversationId: string;
|
|
27
27
|
public createdAt: string;
|
|
28
28
|
public entities: TweetEntities;
|
|
29
29
|
public fullText: string;
|
|
30
30
|
public id: string;
|
|
31
31
|
public lang: string;
|
|
32
|
-
public likeCount
|
|
32
|
+
public likeCount?: number;
|
|
33
33
|
public media?: TweetMedia[];
|
|
34
|
-
public quoteCount
|
|
34
|
+
public quoteCount?: number;
|
|
35
35
|
public quoted?: Tweet;
|
|
36
|
-
public replyCount
|
|
36
|
+
public replyCount?: number;
|
|
37
37
|
public replyTo?: string;
|
|
38
|
-
public retweetCount
|
|
38
|
+
public retweetCount?: number;
|
|
39
39
|
public retweetedTweet?: Tweet;
|
|
40
40
|
public tweetBy: User;
|
|
41
41
|
public url: string;
|
|
42
|
-
public viewCount
|
|
42
|
+
public viewCount?: number;
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
45
|
* @param tweet - The raw tweet details.
|
|
@@ -53,14 +53,16 @@ export class Tweet implements ITweet {
|
|
|
53
53
|
this.entities = new TweetEntities(tweet.legacy.entities);
|
|
54
54
|
this.media = tweet.legacy.extended_entities?.media?.map((media) => new TweetMedia(media));
|
|
55
55
|
this.quoted = this._getQuotedTweet(tweet);
|
|
56
|
-
this.fullText = tweet.note_tweet
|
|
56
|
+
this.fullText = tweet.note_tweet?.note_tweet_results?.result?.text
|
|
57
|
+
? tweet.note_tweet.note_tweet_results.result.text
|
|
58
|
+
: tweet.legacy.full_text;
|
|
57
59
|
this.replyTo = tweet.legacy.in_reply_to_status_id_str;
|
|
58
60
|
this.lang = tweet.legacy.lang;
|
|
59
61
|
this.quoteCount = tweet.legacy.quote_count;
|
|
60
62
|
this.replyCount = tweet.legacy.reply_count;
|
|
61
63
|
this.retweetCount = tweet.legacy.retweet_count;
|
|
62
64
|
this.likeCount = tweet.legacy.favorite_count;
|
|
63
|
-
this.viewCount = tweet.views
|
|
65
|
+
this.viewCount = tweet.views?.count ? parseInt(tweet.views.count) : undefined;
|
|
64
66
|
this.bookmarkCount = tweet.legacy.bookmark_count;
|
|
65
67
|
this.retweetedTweet = this._getRetweetedTweet(tweet);
|
|
66
68
|
this.url = `https://x.com/${this.tweetBy.userName}/status/${this.id}`;
|
package/src/models/data/User.ts
CHANGED
|
@@ -20,6 +20,8 @@ export class User implements IUser {
|
|
|
20
20
|
public followingsCount: number;
|
|
21
21
|
public fullName: string;
|
|
22
22
|
public id: string;
|
|
23
|
+
public isFollowed?: boolean;
|
|
24
|
+
public isFollowing?: boolean;
|
|
23
25
|
public isVerified: boolean;
|
|
24
26
|
public likeCount: number;
|
|
25
27
|
public location?: string;
|
|
@@ -39,6 +41,8 @@ export class User implements IUser {
|
|
|
39
41
|
this.fullName = user.legacy.name;
|
|
40
42
|
this.createdAt = new Date(user.legacy.created_at).toISOString();
|
|
41
43
|
this.description = user.legacy.description.length ? user.legacy.description : undefined;
|
|
44
|
+
this.isFollowed = user.legacy.following;
|
|
45
|
+
this.isFollowing = user.legacy.followed_by;
|
|
42
46
|
this.isVerified = user.is_blue_verified;
|
|
43
47
|
this.likeCount = user.legacy.favourites_count;
|
|
44
48
|
this.followersCount = user.legacy.followers_count;
|
|
@@ -168,6 +172,8 @@ export class User implements IUser {
|
|
|
168
172
|
followingsCount: this.followingsCount,
|
|
169
173
|
fullName: this.fullName,
|
|
170
174
|
id: this.id,
|
|
175
|
+
isFollowed: this.isFollowed,
|
|
176
|
+
isFollowing: this.isFollowing,
|
|
171
177
|
isVerified: this.isVerified,
|
|
172
178
|
likeCount: this.likeCount,
|
|
173
179
|
location: this.location,
|