@codybrom/denim 1.3.5 → 2.0.0
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/workflows/publish.yml +17 -7
- package/.vscode/settings.json +34 -9
- package/CHANGELOG.md +128 -0
- package/deno.json +22 -8
- package/deno.lock +17 -59
- package/examples/edge-function.ts +171 -177
- package/mod.ts +138 -650
- package/mod_test.ts +1287 -380
- package/package.json +22 -22
- package/readme.md +155 -191
- package/src/api/createCarouselItem.ts +86 -0
- package/src/api/createThreadsContainer.ts +122 -0
- package/src/api/debugToken.ts +35 -0
- package/src/api/deleteThread.ts +36 -0
- package/src/api/exchangeCodeForToken.ts +50 -0
- package/src/api/exchangeToken.ts +36 -0
- package/src/api/getAppAccessToken.ts +35 -0
- package/src/api/getConversation.ts +51 -0
- package/src/api/getGhostPosts.ts +50 -0
- package/src/api/getLocation.ts +38 -0
- package/src/api/getMediaInsights.ts +39 -0
- package/src/api/getMentions.ts +57 -0
- package/src/api/getOEmbed.ts +41 -0
- package/src/api/getProfile.ts +46 -0
- package/src/api/getProfilePosts.ts +53 -0
- package/src/api/getPublishingLimit.ts +59 -0
- package/src/api/getReplies.ts +51 -0
- package/src/api/getSingleThread.ts +37 -0
- package/src/api/getThreadsList.ts +49 -0
- package/src/api/getUserInsights.ts +54 -0
- package/src/api/getUserReplies.ts +54 -0
- package/src/api/lookupProfile.ts +53 -0
- package/src/api/manageReply.ts +41 -0
- package/src/api/publishThreadsContainer.ts +107 -0
- package/src/api/refreshToken.ts +33 -0
- package/src/api/repost.ts +38 -0
- package/src/api/searchKeyword.ts +86 -0
- package/src/api/searchLocations.ts +46 -0
- package/src/constants.ts +80 -0
- package/src/types.ts +925 -0
- package/src/utils/checkContainerStatus.ts +39 -0
- package/src/utils/getAPI.ts +13 -0
- package/src/utils/mock_threads_api.ts +582 -0
- package/src/utils/validateRequest.ts +166 -0
- package/mock_threads_api.ts +0 -174
- package/types.ts +0 -235
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import type { ThreadsPostRequest } from "../types.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Validates the ThreadsPostRequest object to ensure correct usage of media-specific properties.
|
|
4
|
+
*
|
|
5
|
+
* @param request - The ThreadsPostRequest object to validate
|
|
6
|
+
* @throws Will throw an error if the request contains invalid combinations of media type and properties
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* const request: ThreadsPostRequest = {
|
|
11
|
+
* userId: "123456",
|
|
12
|
+
* accessToken: "your_access_token",
|
|
13
|
+
* mediaType: "IMAGE",
|
|
14
|
+
* imageUrl: "https://example.com/image.jpg"
|
|
15
|
+
* };
|
|
16
|
+
* await validateRequest(request); // This will not throw an error
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export async function validateRequest(
|
|
20
|
+
request: ThreadsPostRequest,
|
|
21
|
+
): Promise<void> {
|
|
22
|
+
// Check for invalid combinations first
|
|
23
|
+
if (request.mediaType !== "IMAGE" && request.imageUrl) {
|
|
24
|
+
throw new Error("imageUrl can only be used with IMAGE media type");
|
|
25
|
+
}
|
|
26
|
+
if (request.mediaType !== "VIDEO" && request.videoUrl) {
|
|
27
|
+
throw new Error("videoUrl can only be used with VIDEO media type");
|
|
28
|
+
}
|
|
29
|
+
if (request.mediaType !== "TEXT" && request.linkAttachment) {
|
|
30
|
+
throw new Error("linkAttachment can only be used with TEXT media type");
|
|
31
|
+
}
|
|
32
|
+
if (request.mediaType !== "CAROUSEL" && request.children) {
|
|
33
|
+
throw new Error("children can only be used with CAROUSEL media type");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Poll attachment can only be used with TEXT posts
|
|
37
|
+
if (request.pollAttachment && request.mediaType !== "TEXT") {
|
|
38
|
+
throw new Error("pollAttachment can only be used with TEXT media type");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// GIF attachment can only be used with TEXT posts
|
|
42
|
+
if (request.gifAttachment && request.mediaType !== "TEXT") {
|
|
43
|
+
throw new Error("gifAttachment can only be used with TEXT media type");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Ghost posts can only be TEXT and cannot be replies
|
|
47
|
+
if (request.isGhostPost) {
|
|
48
|
+
if (request.mediaType !== "TEXT") {
|
|
49
|
+
throw new Error("isGhostPost can only be used with TEXT media type");
|
|
50
|
+
}
|
|
51
|
+
if (request.replyToId) {
|
|
52
|
+
throw new Error("isGhostPost cannot be used together with replyToId");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Text attachment can only be used with TEXT posts and not with polls
|
|
57
|
+
if (request.textAttachment) {
|
|
58
|
+
if (request.mediaType !== "TEXT") {
|
|
59
|
+
throw new Error("textAttachment can only be used with TEXT media type");
|
|
60
|
+
}
|
|
61
|
+
if (request.pollAttachment) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
"textAttachment cannot be used together with pollAttachment",
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Text entities limited to 10
|
|
69
|
+
if (request.textEntities && request.textEntities.length > 10) {
|
|
70
|
+
throw new Error("textEntities cannot have more than 10 entries");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// If combinations are valid, do media-specific validations
|
|
74
|
+
if (request.mediaType === "IMAGE" && request.imageUrl) {
|
|
75
|
+
await validateImageSpecs(request.imageUrl);
|
|
76
|
+
}
|
|
77
|
+
if (request.mediaType === "VIDEO" && request.videoUrl) {
|
|
78
|
+
await validateVideoSpecs(request.videoUrl);
|
|
79
|
+
}
|
|
80
|
+
if (request.mediaType === "TEXT" && request.linkAttachment) {
|
|
81
|
+
validateLinkUrl(request.linkAttachment);
|
|
82
|
+
}
|
|
83
|
+
if (
|
|
84
|
+
request.mediaType === "CAROUSEL" &&
|
|
85
|
+
(!request.children || request.children.length < 2)
|
|
86
|
+
) {
|
|
87
|
+
throw new Error("CAROUSEL media type requires at least 2 children");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function validateImageSpecs(imageUrl: string): Promise<void> {
|
|
92
|
+
let response: Response;
|
|
93
|
+
try {
|
|
94
|
+
const controller = new AbortController();
|
|
95
|
+
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
|
96
|
+
response = await fetch(imageUrl, {
|
|
97
|
+
method: "HEAD",
|
|
98
|
+
signal: controller.signal,
|
|
99
|
+
});
|
|
100
|
+
clearTimeout(timeoutId);
|
|
101
|
+
} catch (_error) {
|
|
102
|
+
// HEAD not supported or network error — skip validation
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (response.status === 405) return;
|
|
107
|
+
|
|
108
|
+
if (!response.ok) {
|
|
109
|
+
throw new Error(`Failed to fetch image: ${response.statusText}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const contentType = response.headers.get("content-type")?.split(";")[0]
|
|
113
|
+
.trim();
|
|
114
|
+
if (!contentType || !["image/jpeg", "image/png"].includes(contentType)) {
|
|
115
|
+
throw new Error("Image format must be JPEG or PNG");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const contentLength = response.headers.get("content-length");
|
|
119
|
+
if (contentLength && parseInt(contentLength, 10) > 8 * 1024 * 1024) {
|
|
120
|
+
throw new Error("Image file size must not exceed 8 MB");
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function validateVideoSpecs(videoUrl: string): Promise<void> {
|
|
125
|
+
let response: Response;
|
|
126
|
+
try {
|
|
127
|
+
const controller = new AbortController();
|
|
128
|
+
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
|
129
|
+
response = await fetch(videoUrl, {
|
|
130
|
+
method: "HEAD",
|
|
131
|
+
signal: controller.signal,
|
|
132
|
+
});
|
|
133
|
+
clearTimeout(timeoutId);
|
|
134
|
+
} catch (_error) {
|
|
135
|
+
// HEAD not supported or network error — skip validation
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (response.status === 405) return;
|
|
140
|
+
|
|
141
|
+
if (!response.ok) {
|
|
142
|
+
throw new Error(`Failed to fetch video: ${response.statusText}`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const contentType = response.headers.get("content-type")?.split(";")[0]
|
|
146
|
+
.trim();
|
|
147
|
+
if (!contentType || !["video/quicktime", "video/mp4"].includes(contentType)) {
|
|
148
|
+
throw new Error("Video format must be MOV or MP4");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const contentLength = response.headers.get("content-length");
|
|
152
|
+
if (contentLength && parseInt(contentLength, 10) > 1024 * 1024 * 1024) {
|
|
153
|
+
throw new Error("Video file size must not exceed 1 GB");
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function validateLinkUrl(url: string): void {
|
|
158
|
+
// Pattern to ensure URL starts with http:// or https:// and has a valid domain
|
|
159
|
+
const urlPattern = /^https?:\/\/[\w.-]+\.[a-zA-Z]{2,}/;
|
|
160
|
+
|
|
161
|
+
if (!urlPattern.test(url)) {
|
|
162
|
+
throw new Error(
|
|
163
|
+
"Invalid URL format for linkAttachment. URL must start with http:// or https:// and contain a valid domain",
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
}
|
package/mock_threads_api.ts
DELETED
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
// mock_threads_api.ts
|
|
2
|
-
|
|
3
|
-
import type {
|
|
4
|
-
ThreadsContainer,
|
|
5
|
-
ThreadsPost,
|
|
6
|
-
ThreadsProfile,
|
|
7
|
-
PublishingLimit,
|
|
8
|
-
ThreadsPostRequest,
|
|
9
|
-
ThreadsListResponse,
|
|
10
|
-
} from "./types.ts";
|
|
11
|
-
|
|
12
|
-
export class MockThreadsAPI implements MockThreadsAPI {
|
|
13
|
-
private containers: Map<string, ThreadsContainer> = new Map();
|
|
14
|
-
private posts: Map<string, ThreadsPost> = new Map();
|
|
15
|
-
private users: Map<string, ThreadsProfile> = new Map();
|
|
16
|
-
private publishingLimits: Map<string, PublishingLimit> = new Map();
|
|
17
|
-
private errorMode = false;
|
|
18
|
-
|
|
19
|
-
constructor() {
|
|
20
|
-
// Initialize with some sample data
|
|
21
|
-
this.users.set("12345", {
|
|
22
|
-
id: "12345",
|
|
23
|
-
username: "testuser",
|
|
24
|
-
name: "Test User",
|
|
25
|
-
threadsProfilePictureUrl: "https://example.com/profile.jpg",
|
|
26
|
-
threadsBiography: "This is a test user",
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
this.publishingLimits.set("12345", {
|
|
30
|
-
quota_usage: 10,
|
|
31
|
-
config: {
|
|
32
|
-
quota_total: 250,
|
|
33
|
-
quota_duration: 86400,
|
|
34
|
-
},
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
setErrorMode(mode: boolean) {
|
|
39
|
-
this.errorMode = mode;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
createThreadsContainer(
|
|
43
|
-
request: ThreadsPostRequest
|
|
44
|
-
): Promise<string | { id: string; permalink: string }> {
|
|
45
|
-
if (this.errorMode) {
|
|
46
|
-
return Promise.reject(new Error("Failed to create Threads container"));
|
|
47
|
-
}
|
|
48
|
-
const containerId = `container_${Math.random().toString(36).substring(7)}`;
|
|
49
|
-
const permalink = `https://www.threads.net/@${request.userId}/post/${containerId}`;
|
|
50
|
-
const container: ThreadsContainer = {
|
|
51
|
-
id: containerId,
|
|
52
|
-
permalink,
|
|
53
|
-
status: "FINISHED",
|
|
54
|
-
};
|
|
55
|
-
this.containers.set(containerId, container);
|
|
56
|
-
|
|
57
|
-
// Create a post immediately when creating a container
|
|
58
|
-
const postId = `post_${Math.random().toString(36).substring(7)}`;
|
|
59
|
-
const post: ThreadsPost = {
|
|
60
|
-
id: postId,
|
|
61
|
-
media_product_type: "THREADS",
|
|
62
|
-
media_type: request.mediaType,
|
|
63
|
-
permalink,
|
|
64
|
-
owner: { id: request.userId },
|
|
65
|
-
username: "testuser",
|
|
66
|
-
text: request.text || "",
|
|
67
|
-
timestamp: new Date().toISOString(),
|
|
68
|
-
shortcode: postId,
|
|
69
|
-
is_quote_post: false,
|
|
70
|
-
hasReplies: false,
|
|
71
|
-
isReply: false,
|
|
72
|
-
isReplyOwnedByMe: false,
|
|
73
|
-
};
|
|
74
|
-
this.posts.set(postId, post);
|
|
75
|
-
|
|
76
|
-
// Always return an object with both id and permalink
|
|
77
|
-
return Promise.resolve({ id: containerId, permalink });
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
publishThreadsContainer(
|
|
81
|
-
_userId: string,
|
|
82
|
-
_accessToken: string,
|
|
83
|
-
containerId: string,
|
|
84
|
-
getPermalink: boolean = false
|
|
85
|
-
): Promise<string | { id: string; permalink: string }> {
|
|
86
|
-
if (this.errorMode) {
|
|
87
|
-
return Promise.reject(new Error("Failed to publish Threads container"));
|
|
88
|
-
}
|
|
89
|
-
const container = this.containers.get(containerId);
|
|
90
|
-
if (!container) {
|
|
91
|
-
return Promise.reject(new Error("Container not found"));
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Find the post associated with this container
|
|
95
|
-
const existingPost = Array.from(this.posts.values()).find(
|
|
96
|
-
(post) => post.permalink === container.permalink
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
if (!existingPost) {
|
|
100
|
-
return Promise.reject(
|
|
101
|
-
new Error("Post not found for the given container")
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return Promise.resolve(
|
|
106
|
-
getPermalink
|
|
107
|
-
? { id: existingPost.id, permalink: existingPost.permalink || "" }
|
|
108
|
-
: existingPost.id
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
createCarouselItem(
|
|
113
|
-
request: Omit<ThreadsPostRequest, "mediaType"> & {
|
|
114
|
-
mediaType: "IMAGE" | "VIDEO";
|
|
115
|
-
}
|
|
116
|
-
): Promise<string | { id: string }> {
|
|
117
|
-
const itemId = `item_${Math.random().toString(36).substring(7)}`;
|
|
118
|
-
const container: ThreadsContainer = {
|
|
119
|
-
id: itemId,
|
|
120
|
-
permalink: `https://www.threads.net/@${request.userId}/post/${itemId}`,
|
|
121
|
-
status: "FINISHED",
|
|
122
|
-
};
|
|
123
|
-
this.containers.set(itemId, container);
|
|
124
|
-
return Promise.resolve({ id: itemId });
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
getPublishingLimit(
|
|
128
|
-
userId: string,
|
|
129
|
-
_accessToken: string
|
|
130
|
-
): Promise<PublishingLimit> {
|
|
131
|
-
if (this.errorMode) {
|
|
132
|
-
return Promise.reject(new Error("Failed to get publishing limit"));
|
|
133
|
-
}
|
|
134
|
-
const limit = this.publishingLimits.get(userId);
|
|
135
|
-
if (!limit) {
|
|
136
|
-
return Promise.reject(new Error("Publishing limit not found"));
|
|
137
|
-
}
|
|
138
|
-
return Promise.resolve(limit);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
getThreadsList(
|
|
142
|
-
userId: string,
|
|
143
|
-
_accessToken: string,
|
|
144
|
-
options?: {
|
|
145
|
-
since?: string;
|
|
146
|
-
until?: string;
|
|
147
|
-
limit?: number;
|
|
148
|
-
after?: string;
|
|
149
|
-
before?: string;
|
|
150
|
-
}
|
|
151
|
-
): Promise<ThreadsListResponse> {
|
|
152
|
-
const threads = Array.from(this.posts.values())
|
|
153
|
-
.filter((post) => post.owner.id === userId)
|
|
154
|
-
.slice(0, options?.limit || 25);
|
|
155
|
-
|
|
156
|
-
return Promise.resolve({
|
|
157
|
-
data: threads as ThreadsPost[],
|
|
158
|
-
paging: {
|
|
159
|
-
cursors: {
|
|
160
|
-
before: "BEFORE_CURSOR",
|
|
161
|
-
after: "AFTER_CURSOR",
|
|
162
|
-
},
|
|
163
|
-
},
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
getSingleThread(mediaId: string, _accessToken: string): Promise<ThreadsPost> {
|
|
168
|
-
const post = this.posts.get(mediaId);
|
|
169
|
-
if (!post) {
|
|
170
|
-
return Promise.reject(new Error("Thread not found"));
|
|
171
|
-
}
|
|
172
|
-
return Promise.resolve(post as ThreadsPost);
|
|
173
|
-
}
|
|
174
|
-
}
|
package/types.ts
DELETED
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
// types.ts
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Represents the types of media that can be posted on Threads.
|
|
5
|
-
*/
|
|
6
|
-
export type MediaType = "TEXT" | "IMAGE" | "VIDEO" | "CAROUSEL";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Represents the options for controlling who can reply to a post.
|
|
10
|
-
*/
|
|
11
|
-
export type ReplyControl =
|
|
12
|
-
| "everyone"
|
|
13
|
-
| "accounts_you_follow"
|
|
14
|
-
| "mentioned_only";
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Represents a request to post content on Threads.
|
|
18
|
-
*/
|
|
19
|
-
export interface ThreadsPostRequest {
|
|
20
|
-
/** The user ID of the Threads account */
|
|
21
|
-
userId: string;
|
|
22
|
-
/** The access token for authentication */
|
|
23
|
-
accessToken: string;
|
|
24
|
-
/** The type of media being posted */
|
|
25
|
-
mediaType: MediaType;
|
|
26
|
-
/** The text content of the post (optional) */
|
|
27
|
-
text?: string;
|
|
28
|
-
/** The URL of the image to be posted (optional, for IMAGE type) */
|
|
29
|
-
imageUrl?: string;
|
|
30
|
-
/** The URL of the video to be posted (optional, for VIDEO type) */
|
|
31
|
-
videoUrl?: string;
|
|
32
|
-
/** The accessibility text for the image or video (optional) */
|
|
33
|
-
altText?: string;
|
|
34
|
-
/** The URL to be attached as a link to the post (optional, for text posts only) */
|
|
35
|
-
linkAttachment?: string;
|
|
36
|
-
/** List of country codes where the post should be visible (optional - requires special API access) */
|
|
37
|
-
allowlistedCountryCodes?: string[];
|
|
38
|
-
/** Controls who can reply to the post (optional) */
|
|
39
|
-
replyControl?: ReplyControl;
|
|
40
|
-
/** Array of carousel item IDs (required for CAROUSEL type, not applicable for other types) */
|
|
41
|
-
children?: string[];
|
|
42
|
-
/** Whether to return the permalink of the post (optional, default: false) */
|
|
43
|
-
getPermalink?: boolean;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Represents a single Threads media object.
|
|
48
|
-
*/
|
|
49
|
-
export interface ThreadsPost {
|
|
50
|
-
/** Unique identifier for the media object */
|
|
51
|
-
id: string;
|
|
52
|
-
/** Type of product where the media is published (e.g., "THREADS") */
|
|
53
|
-
media_product_type: string;
|
|
54
|
-
/** Type of media (e.g., "TEXT", "IMAGE", "VIDEO", "CAROUSEL") */
|
|
55
|
-
media_type: MediaType;
|
|
56
|
-
/** URL of the media content (if applicable) */
|
|
57
|
-
media_url?: string;
|
|
58
|
-
/** Permanent link to the post */
|
|
59
|
-
permalink?: string;
|
|
60
|
-
/** Information about the owner of the post */
|
|
61
|
-
owner: { id: string };
|
|
62
|
-
/** Username of the account that created the post */
|
|
63
|
-
username: string;
|
|
64
|
-
/** Text content of the post */
|
|
65
|
-
text?: string;
|
|
66
|
-
/** Timestamp of when the post was created (ISO 8601 format) */
|
|
67
|
-
timestamp: string;
|
|
68
|
-
/** Short code identifier for the media */
|
|
69
|
-
shortcode: string;
|
|
70
|
-
/** URL of the thumbnail image (for video posts) */
|
|
71
|
-
thumbnail_url?: string;
|
|
72
|
-
/** List of child posts (for carousel posts) */
|
|
73
|
-
children?: ThreadsPost[];
|
|
74
|
-
/** Indicates if the post is a quote of another post */
|
|
75
|
-
is_quote_post: boolean;
|
|
76
|
-
/** Accessibility text for the image or video */
|
|
77
|
-
altText?: string;
|
|
78
|
-
/** URL of the attached link */
|
|
79
|
-
linkAttachmentUrl?: string;
|
|
80
|
-
/** Indicates if the post has replies */
|
|
81
|
-
hasReplies: boolean;
|
|
82
|
-
/** Indicates if the post is a reply to another post */
|
|
83
|
-
isReply: boolean;
|
|
84
|
-
/** Indicates if the reply is owned by the current user */
|
|
85
|
-
isReplyOwnedByMe: boolean;
|
|
86
|
-
/** Information about the root post (for replies) */
|
|
87
|
-
rootPost?: { id: string };
|
|
88
|
-
/** Information about the post being replied to */
|
|
89
|
-
repliedTo?: { id: string };
|
|
90
|
-
/** Visibility status of the post */
|
|
91
|
-
hideStatus?: "VISIBLE" | "HIDDEN";
|
|
92
|
-
/** Controls who can reply to the post */
|
|
93
|
-
replyAudience?: ReplyControl;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Represents the response structure when retrieving a list of Threads.
|
|
98
|
-
*/
|
|
99
|
-
export interface ThreadsListResponse {
|
|
100
|
-
/** Array of ThreadsPost representing the retrieved posts */
|
|
101
|
-
data: ThreadsPost[];
|
|
102
|
-
/** Pagination information */
|
|
103
|
-
paging?: {
|
|
104
|
-
/** Cursors for navigating through pages of results */
|
|
105
|
-
cursors: {
|
|
106
|
-
/** Cursor for the previous page */
|
|
107
|
-
before: string;
|
|
108
|
-
/** Cursor for the next page */
|
|
109
|
-
after: string;
|
|
110
|
-
};
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Represents the publishing limit information for a user.
|
|
116
|
-
*/
|
|
117
|
-
export interface PublishingLimit {
|
|
118
|
-
/** Current usage count towards the quota */
|
|
119
|
-
quota_usage: number;
|
|
120
|
-
/** Configuration for the publishing limit */
|
|
121
|
-
config: {
|
|
122
|
-
/** Total allowed quota */
|
|
123
|
-
quota_total: number;
|
|
124
|
-
/** Duration of the quota period in seconds */
|
|
125
|
-
quota_duration: number;
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Represents a Threads media container.
|
|
131
|
-
*/
|
|
132
|
-
export interface ThreadsContainer {
|
|
133
|
-
/** Unique identifier for the container */
|
|
134
|
-
id: string;
|
|
135
|
-
/** Permanent link to the container */
|
|
136
|
-
permalink: string;
|
|
137
|
-
/** Status of the container */
|
|
138
|
-
status: "FINISHED" | "FAILED";
|
|
139
|
-
/** Error message if the container failed */
|
|
140
|
-
errorMessage?: string;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Represents a Threads user profile.
|
|
145
|
-
*/
|
|
146
|
-
export interface ThreadsProfile {
|
|
147
|
-
/** Unique identifier for the user */
|
|
148
|
-
id: string;
|
|
149
|
-
/** Username of the account */
|
|
150
|
-
username: string;
|
|
151
|
-
/** Display name of the user */
|
|
152
|
-
name: string;
|
|
153
|
-
/** URL of the user's profile picture */
|
|
154
|
-
threadsProfilePictureUrl: string;
|
|
155
|
-
/** Biography text of the user */
|
|
156
|
-
threadsBiography: string;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Represents the mock API for Threads operations.
|
|
161
|
-
*/
|
|
162
|
-
export interface MockThreadsAPI {
|
|
163
|
-
/**
|
|
164
|
-
* Creates a Threads media container.
|
|
165
|
-
* @param request The request object containing post details
|
|
166
|
-
* @returns A promise that resolves to either a string ID or an object with ID and permalink
|
|
167
|
-
*/
|
|
168
|
-
createThreadsContainer(
|
|
169
|
-
request: ThreadsPostRequest
|
|
170
|
-
): Promise<string | { id: string; permalink: string }>;
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Publishes a Threads media container.
|
|
174
|
-
* @param userId The user ID
|
|
175
|
-
* @param accessToken The access token
|
|
176
|
-
* @param containerId The ID of the container to publish
|
|
177
|
-
* @param getPermalink Whether to return the permalink
|
|
178
|
-
* @returns A promise that resolves to either a string ID or an object with ID and permalink
|
|
179
|
-
*/
|
|
180
|
-
publishThreadsContainer(
|
|
181
|
-
userId: string,
|
|
182
|
-
accessToken: string,
|
|
183
|
-
containerId: string,
|
|
184
|
-
getPermalink?: boolean
|
|
185
|
-
): Promise<string | { id: string; permalink: string }>;
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Creates a carousel item for a Threads post.
|
|
189
|
-
* @param request The request object containing carousel item details
|
|
190
|
-
* @returns A promise that resolves to either a string ID or an object with ID
|
|
191
|
-
*/
|
|
192
|
-
createCarouselItem(
|
|
193
|
-
request: Omit<ThreadsPostRequest, "mediaType"> & {
|
|
194
|
-
mediaType: "IMAGE" | "VIDEO";
|
|
195
|
-
}
|
|
196
|
-
): Promise<string | { id: string }>;
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Retrieves the publishing limit for a user.
|
|
200
|
-
* @param userId The user ID
|
|
201
|
-
* @param accessToken The access token
|
|
202
|
-
* @returns A promise that resolves to the publishing limit information
|
|
203
|
-
*/
|
|
204
|
-
getPublishingLimit(
|
|
205
|
-
userId: string,
|
|
206
|
-
accessToken: string
|
|
207
|
-
): Promise<PublishingLimit>;
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Retrieves a list of Threads posts for a user.
|
|
211
|
-
* @param userId The user ID
|
|
212
|
-
* @param accessToken The access token
|
|
213
|
-
* @param options Optional parameters for pagination and date range
|
|
214
|
-
* @returns A promise that resolves to the Threads list response
|
|
215
|
-
*/
|
|
216
|
-
getThreadsList(
|
|
217
|
-
userId: string,
|
|
218
|
-
accessToken: string,
|
|
219
|
-
options?: {
|
|
220
|
-
since?: string;
|
|
221
|
-
until?: string;
|
|
222
|
-
limit?: number;
|
|
223
|
-
after?: string;
|
|
224
|
-
before?: string;
|
|
225
|
-
}
|
|
226
|
-
): Promise<ThreadsListResponse>;
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Retrieves a single Thread post.
|
|
230
|
-
* @param mediaId The ID of the media to retrieve
|
|
231
|
-
* @param accessToken The access token
|
|
232
|
-
* @returns A promise that resolves to the Thread post
|
|
233
|
-
*/
|
|
234
|
-
getSingleThread(mediaId: string, accessToken: string): Promise<ThreadsPost>;
|
|
235
|
-
}
|