diff --git a/README.md b/README.md
index ac5d2b3..0d21c4e 100644
--- a/README.md
+++ b/README.md
@@ -27,193 +27,15 @@ thread.js is a Node.js library that allows you to interact with the Threads API
```
npm install @threadsjs/threads.js
```
-## Usage
+## Example usage
```js
const { Client } = require('@threadsjs/threads.js');
(async () => {
- const client = new Client();
- // You can also specify a token: const client = new Client({ token: 'token' });
- await client.login('username', 'password');
+ const client = new Client({ token: 'token' });
- await client.users.fetch(25025320).then(user => {
+ await client.users.get().then(user => {
console.log(user);
});
})();
-```
-
-## Methods
-### client.users.fetch
-In the parameters, pass the user id (supported as string and number) of the user whose information you want to get.
-```js
-await client.users.fetch(1)
-```
-### client.users.search
-Pass the query as the first parameter, and the number of objects in the response as the second parameter (by default - 30). The minimum is 30.
-```js
-await client.users.search("zuck", 10)
-```
-
-
-
-### client.restrictions.restrict
-In the parameters, pass the user id (supported as string and number) of the user you want to restrict.
-```js
-await client.users.restrict(1)
-```
-### client.restrictions.unrestrict
-In the parameters, pass the user id (supported as string and number) of the user you want to unrestrict.
-```js
-await client.users.unrestrict(1)
-```
-
-
-### client.friendships.show
-In the parameters, pass the user id (supported as string and number) of the user whose friendship status information you want to get.
-```js
-await client.friendships.show(1)
-```
-### client.friendships.follow
-Pass the user id (supported as string and number) of the user you want to subscribe to in the parameters
-```js
-await client.friendships.follow(1)
-```
-### client.friendships.unfollow
-Pass the user id (supported as string and number) of the user you want to unsubscribe from in the parameters
-```js
-await client.friendships.unfollow(1)
-```
-### client.friendships.followers
-In the parameters, pass the user id (supported as string and number) of the user whose followers you want to get.
-```js
-await client.friendships.followers(1)
-```
-### client.friendships.following
-In the parameters, pass the user id (supported as string and number) of the user whose followings you want to get.
-```js
-await client.friendships.following(1)
-```
-### client.friendships.mute
-In the parameters, pass the user id (supported as string and number) of the user you want to mute.
-```js
-await client.friendships.mute(1)
-```
-### client.friendships.unmute
-In the parameters, pass the user id (supported as string and number) of the user you want to unmute.
-```js
-await client.friendships.unmute(1)
-```
-### client.friendships.block
-In the parameters, pass the user id (supported as string and number) of the user you want to block.
-```js
-await client.friendships.block(1)
-```
-### client.friendships.unblock
-In the parameters, pass the user id (supported as string and number) of the user you want to unblock.
-```js
-await client.friendships.unblock(1)
-```
-
-
-
-### client.feeds.fetch
-Gets the default feed. In the parameters, pass the optional max_id of the previous response's next_max_id.
-```js
-await client.feeds.fetch()
-await client.feeds.fetch("aAaAAAaaa")
-```
-### client.feeds.fetchThreads
-In the parameters, pass the user id (supported as string and number) of the user whose threads you want to get, and an optional max_id of the previous response's next_max_id.
-```js
-await client.feeds.fetchThreads(1),
-await client.feeds.fetchThreads(1, "aAaAAAaaa")
-```
-### client.feeds.fetchReplies
-In the parameters, pass the user id (supported as string and number) of the user whose replies you want to get, and an optional max_id of the previous response's next_max_id.
-```js
-await client.feeds.fetchReplies(1)
-await client.feeds.fetchReplies(1, "aAaAAAaaa")
-```
-### client.feeds.recommended
-Getting a list of recommendations. In the parameters, pass the optional paging_token of the previous response.
-```js
-await client.feeds.recommended()
-await client.feeds.recommended(15)
-```
-### client.feeds.notifications
-Getting a list of recommendations. In the parameters, pass an optional filter type and an optional pagination object with max_id and pagination_first_record_timestamp from the previous response.
-Valid filter types:
-- text_post_app_replies
-- text_post_app_mentions
-- verified
-```js
-let pagination = {
- max_id: "1688921943.766884",
- pagination_first_record_timestamp: "1689094189.845912"
-}
-
-await client.feeds.notifications()
-await client.feeds.notifications(null, pagination)
-
-await client.feeds.notifications("text_post_app_replies")
-await client.feeds.notifications("text_post_app_replies", pagination)
-```
-### client.feeds.notificationseen
-Clears all notifications. You might want to do this **after** client.feeds.notifications() and checking new_stories for what wasn't seen.
-```js
-await client.feeds.notificationseen()
-```
-
-
-
-### client.posts.fetch
-In the parameters pass the id of the post you want to get information about, and an optional pagination token from the previous request.
-```js
-await client.posts.fetch("aAaAAAaaa")
-await client.posts.fetch("aAaAAAaaa", "aAaAAAaaa")
-```
-### client.posts.likers
-In the parameters pass the id of the post whose likes you want to get
-```js
-await client.posts.likers("aAaAAAaaa")
-```
-### client.posts.create
-The method is used to create a thread. Pass the text of the thread as the first parameter, and the user id (supported as string and number) as the second
-```js
-await client.posts.create(1, { contents: "Hello World!" })
-```
-### client.posts.reply
-The method is used to create reply to a thread. Pass the text of the reply as the first parameter, the user id (supported as string and number) as the second, and post id as the third
-```js
-await client.posts.reply(1, { contents: "Hello World!", post: "aAaAAAaaa" })
-```
-### client.posts.quote
-The method is used to create a quote thread. Pass the text of the quote comment as the first parameter, the user id as the second, and post id as the third
-```js
-await client.posts.quote(1, { contents: "Hello World!", post: "aAaAAAaaa" })
-```
-### client.posts.delete
-The method is used to delete a thread. Pass the post id as the first parameter, and the user id (supported as string and number) as the second
-```js
-await client.posts.delete("aAaAAAaaa", 1)
-```
-### client.posts.like
-The method is used to like a thread. Pass the post id as the first parameter, and the user id (supported as string and number) as the second
-```js
-await client.posts.like("aAaAAAaaa", 1)
-```
-### client.posts.unlike
-The method is used to unlike a thread. Pass the post id as the first parameter, and the user id (supported as string and number) as the second
-```js
-await client.posts.unlike("aAaAAAaaa", 1)
-```
-### client.posts.repost
-The method is used to repost a thread. Pass the post id as the only parameter
-```js
-await client.posts.repost("aAaAAAaaa")
-```
-### client.posts.unrepost
-The method is used to un-repost a thread. Pass the post id as the only parameter
-```js
-await client.posts.unrepost("aAaAAAaaa")
-```
+```
\ No newline at end of file
diff --git a/src/index.js b/src/index.js
index 59d79d0..8bbe57c 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,209 +1,23 @@
const { fetch } = require("undici");
-const FeedManager = require("./managers/FeedManager");
-const FriendshipManager = require("./managers/FriendshipManager");
-const GraphQLManager = require("./managers/GraphQLManager");
-const PostManager = require("./managers/PostManager");
const RESTManager = require("./managers/RESTManager");
-const RestrictionManager = require("./managers/RestrictionManager");
const UserManager = require("./managers/UserManager");
-const { parseBloksResponse } = require("./util/Bloks.js");
-const crypto = require('crypto');
-const { v4: uuidv4 } = require('uuid');
-
-const androidId = (Math.random() * 1e24).toString(36);
+const PostManager = require("./managers/PostManager");
class Client {
constructor(options) {
this.options = {}
this.options.token = options ? options.token : null;
- this.options.userAgent = options ? options.userAgent : "Barcelona 289.0.0.77.109 Android";
- this.options.appId = options ? options.appId : "3419628305025917";
- this.options.androidId = options ? options.androidId : androidId;
- this.options.userId = null;
- this.options.base = options ? options.base : "https://i.instagram.com";
+ this.options.base = options ? options.base : "https://graph.instagram.com";
this.rest = new RESTManager(this);
this.users = new UserManager(this);
this.posts = new PostManager(this);
-
- this.feeds = new FeedManager(this);
-
- this.friendships = new FriendshipManager(this);
-
- this.restrictions = new RestrictionManager(this);
-
- this.graphql = new GraphQLManager(this);
}
- async _qeSync() {
- const uuid = uuidv4();
- const params = {
- id: uuid
- }
-
- return await fetch(this.options.base + '/api/v1/qe/sync/', {
- method: 'POST',
- headers: {
- "User-Agent": "Barcelona 289.0.0.77.109 Android",
- "Sec-Fetch-Site": "same-origin",
- "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
- 'X-DEVICE-ID': uuid
- },
- body: `params=${encodeURIComponent(params)}`
- }).then(res => {
- return res
- })
- }
-
- async _encryptPassword(password) {
- // https://github.com/dilame/instagram-private-api/blob/master/src/repositories/account.repository.ts#L79-L103
- const key = crypto.randomBytes(32);
- const iv = crypto.randomBytes(12);
- let keyId;
- let pubKey;
- await this._qeSync().then(async res => {
- const headers = res.headers;
- keyId = headers.get('ig-set-password-encryption-key-id');
- pubKey = headers.get('ig-set-password-encryption-pub-key');
- });
- const rsaEncrypted = crypto.publicEncrypt({
- key: Buffer.from(pubKey, 'base64').toString(),
- padding: crypto.constants.RSA_PKCS1_PADDING,
- }, key);
- const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
- const time = Math.floor(Date.now() / 1000).toString();
- cipher.setAAD(Buffer.from(time));
- const aesEncrypted = Buffer.concat([cipher.update(password, 'utf8'), cipher.final()]);
- const sizeBuffer = Buffer.alloc(2, 0);
- sizeBuffer.writeInt16LE(rsaEncrypted.byteLength, 0);
- const authTag = cipher.getAuthTag();
- return {
- time,
- password: Buffer.concat([
- Buffer.from([1, keyId]),
- iv,
- sizeBuffer,
- rsaEncrypted, authTag, aesEncrypted])
- .toString('base64'),
- };
- }
-
- async login(username, password) {
- const loginUrl = "/api/v1/bloks/apps/com.bloks.www.bloks.caa.login.async.send_login_request/";
- const encryptedPassword = await this._encryptPassword(password);
-
- const params = {
- client_input_params: {
- password: `#PWD_INSTAGRAM:4:${encryptedPassword.time}:${encryptedPassword.password}`,
- contact_point: username,
- device_id: `android-${androidId}`,
- },
- server_params: {
- credential_type: "password",
- device_id: `android-${androidId}`,
- },
- };
-
- const bkClientContext = {
- bloks_version:
- "5f56efad68e1edec7801f630b5c122704ec5378adbee6609a448f105f34a9c73",
- styles_id: "instagram",
- };
-
- const requestBody = {
- params: JSON.stringify(params),
- bk_client_context: JSON.stringify(bkClientContext),
- bloks_versioning_id:
- "5f56efad68e1edec7801f630b5c122704ec5378adbee6609a448f105f34a9c73",
- };
-
- const requestOptions = {
- method: "POST",
- headers: {
- "User-Agent": "Barcelona 289.0.0.77.109 Android",
- "Sec-Fetch-Site": "same-origin",
- "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
- },
- body: `params=${encodeURIComponent(
- requestBody.params,
- )}&bk_client_context=${encodeURIComponent(
- requestBody.bk_client_context,
- )}&bloks_versioning_id=${requestBody.bloks_versioning_id}`,
- };
-
- const response = await fetch(this.options.base + loginUrl, requestOptions);
- const text = await response.text();
- const bloks = parseBloksResponse(text);
-
- if ('error' in bloks && bloks.error.error_user_msg === "challenge_required") {
- throw new Error('Your Instagram account is facing a checkpoint.')
- }
-
- if (bloks.two_factor_required) {
- const {
- two_factor_identifier,
- trusted_notification_polling_nonce
- } = bloks.two_factor_info
-
- console.log('Please approve the login request on your Instagram')
-
- const self = this;
- const token = await new Promise((resolve) => {
- const statusUrl = "/api/v1/two_factor/check_trusted_notification_status/";
- const verifyUrl = "/api/v1/accounts/two_factor_login/";
-
- const interval = setInterval(async function () {
- requestOptions.body = new URLSearchParams({
- two_factor_identifier: two_factor_identifier,
- username,
- device_id: `android-${androidId}`,
- trusted_notification_polling_nonces:
- JSON.stringify([trusted_notification_polling_nonce]),
- }).toString();
-
- const response = await fetch(self.options.base + statusUrl, requestOptions);
- const json = await response.json();
-
- if (json.review_status === 1) {
- requestOptions.body = new URLSearchParams({
- signed_body: 'SIGNATURE.' + JSON.stringify({
- verification_code: '',
- two_factor_identifier,
- username,
- device_id: `android-${androidId}`,
- trusted_notification_polling_nonces:
- JSON.stringify([trusted_notification_polling_nonce]),
- verification_method: '4'
- })
- }).toString()
-
- const response = await fetch(self.options.base + verifyUrl, requestOptions);
- const json = await response.json();
- const header = response.headers.get('Ig-Set-Authorization');
-
- if (json.logged_in_user.pk_id) {
- self.options.userId = json.logged_in_user.pk_id;
- }
-
- clearInterval(interval);
- const token = header.replace("Bearer IGT:2:", "") || null
-
- resolve(token);
- }
- }, 2_500);
- });
-
- this.options.token = token;
- return;
- }
-
- if (bloks.login_response.logged_in_user.pk_id) {
- this.userId = bloks.login_response.logged_in_user.pk_id;
- }
-
- this.options.token = bloks.headers?.["IG-Set-Authorization"].replace("Bearer IGT:2:", "");
+ async login(token) {
+ // TODO: implement Instagram API login
}
}
diff --git a/src/managers/FeedManager.js b/src/managers/FeedManager.js
deleted file mode 100644
index 016d278..0000000
--- a/src/managers/FeedManager.js
+++ /dev/null
@@ -1,52 +0,0 @@
-const RESTManager = require('./RESTManager');
-
-class FeedManager extends RESTManager {
- async fetch(max_id) {
- return await this.request('/api/v1/feed/text_post_app_timeline/', {
- method: 'POST',
- body: 'pagination_source=text_post_feed_threads' + (max_id ? '&max_id=' + encodeURIComponent(max_id) : ''),
- })
- }
-
- async fetchThreads(user, max_id) {
- return await this.request(`/api/v1/text_feed/${String(user)}/profile/` + (max_id ? '?max_id=' + encodeURIComponent(max_id) : ''));
- }
-
- async fetchReplies(user, max_id) {
- return await this.request(`/api/v1/text_feed/${String(user)}/profile/replies/` + (max_id ? '?max_id=' + encodeURIComponent(max_id) : ''));
- }
-
- async recommended(paging_token) {
- return await this.request('/api/v1/text_feed/recommended_users/' + (paging_token ? '?paging_token=' + paging_token : ''));
- }
-
- async notifications(filter, pagination) {
- let params = {
- feed_type: 'all',
- mark_as_seen: false,
- timezone_offset: -25200,
- timezone_name: "America%2FLos_Angeles"
- }
-
- if (filter) {
- params.selected_filters = filter;
- }
-
- if (pagination) {
- params.max_id = pagination.max_id;
- params.pagination_first_record_timestamp = pagination.pagination_first_record_timestamp;
- }
-
- const queryString = Object.keys(params).map(key => key + '=' + params[key]).join('&');
-
- return await this.request('/api/v1/text_feed/text_app_notifications/?' + queryString);
- }
-
- async notificationseen() {
- return await this.request('/api/v1/text_feed/text_app_inbox_seen/', {
- method: 'POST',
- })
- }
-}
-
-module.exports = FeedManager;
\ No newline at end of file
diff --git a/src/managers/FriendshipManager.js b/src/managers/FriendshipManager.js
deleted file mode 100644
index e399e5b..0000000
--- a/src/managers/FriendshipManager.js
+++ /dev/null
@@ -1,74 +0,0 @@
-const RESTManager = require('./RESTManager');
-
-class UserManager extends RESTManager {
- async show(user) {
- return await this.request(`/api/v1/friendships/show/${String(user)}/`);
- }
-
- async follow(user) {
- return await this.request(`/api/v1/friendships/create/${String(user)}/`, {
- method: 'POST',
- });
- }
-
- async unfollow(user) {
- return await this.request(`/api/v1/friendships/destroy/${String(user)}/`, {
- method: 'POST',
- });
- }
-
- async followers(user) {
- return await this.request(`/api/v1/friendships/${String(user)}/followers/`);
- }
-
- async following(user) {
- return await this.request(`/api/v1/friendships/${String(user)}/following/`);
- }
-
- async mute(user) {
- const requestBody = {
- target_posts_author_id: user,
- container_module: "ig_text_feed_timeline"
- };
- return await this.request(`/api/v1/friendships/mute_posts_or_story_from_follow/`, {
- method: 'POST',
- body: `signed_body=SIGNATURE.${encodeURIComponent(JSON.stringify(requestBody))}`,
- });
- }
-
- async unmute(user) {
- const requestBody = {
- target_posts_author_id: user,
- container_module: "ig_text_feed_timeline"
- };
- return await this.request(`/api/v1/friendships/unmute_posts_or_story_from_follow/`, {
- method: 'POST',
- body: `signed_body=SIGNATURE.${encodeURIComponent(JSON.stringify(requestBody))}`,
- });
- }
-
- async block(user) {
- const requestBody = {
- surface: "ig_text_feed_timeline",
- is_auto_block_enabled: "true",
- user_id: user,
- };
- return await this.request(`/api/v1/friendships/block/${user}/`, {
- method: 'POST',
- body: `signed_body=SIGNATURE.${encodeURIComponent(JSON.stringify(requestBody))}`,
- });
- }
-
- async unblock(user) {
- const requestBody = {
- user_id: user,
- container_module: "ig_text_feed_profile"
- };
- return await this.request(`/api/v1/friendships/unblock/${user}/`, {
- method: 'POST',
- body: `signed_body=SIGNATURE.${encodeURIComponent(JSON.stringify(requestBody))}`,
- });
- }
-}
-
-module.exports = UserManager;
\ No newline at end of file
diff --git a/src/managers/GraphQLManager.js b/src/managers/GraphQLManager.js
deleted file mode 100644
index f63b8e4..0000000
--- a/src/managers/GraphQLManager.js
+++ /dev/null
@@ -1,60 +0,0 @@
-const { fetch } = require('undici');
-
-const docIDs = {
- BarcelonaProfileRootQuery: '23996318473300828',
- BarcelonaPostPageQuery: '6821609764538244',
- BarcelonaProfileThreadsTabQuery: '6549913525047487',
- BarcelonaProfileRepliesTabQuery: '6480022495409040',
-}
-
-class GraphQLManager {
- async getLsd() {
- return await fetch('https://www.threads.net/@instagram').then(async res => {
- const text = await res.text();
- const matches = text.match(/\["LSD",\[\],{"token":"([^"]*)"}/);
- return matches[1];
- })
- }
-
- async request(docId, variables) {
- if (this.lsd === undefined) {
- this.lsd = await this.getLsd();
- }
-
- let request = {};
- request.headers = {
- 'User-Agent': 'threads-client',
- 'Content-Type': 'application/x-www-form-urlencoded',
- 'X-IG-App-ID': '238260118697367',
- 'X-FB-LSD': this.lsd,
- 'Sec-Fetch-Site': 'same-origin',
- };
- request.body = `lsd=${this.lsd}&doc_id=${docId}&variables=${encodeURIComponent(JSON.stringify(variables))}`;
- request.credentials = 'omit';
- request.method = 'POST';
- const res = await fetch('https://www.threads.net/api/graphql', { ... request });
- const contentType = res.headers.get('content-type');
- if (contentType.includes('text/javascript')) {
- return res.json();
- }
- return res.text();
- }
-
- async getUser(userId) {
- return await this.request(docIDs.BarcelonaProfileRootQuery, {userID: String(userId)});
- }
-
- async getUserPosts(userId) {
- return await this.request(docIDs.BarcelonaProfileThreadsTabQuery, {userID: String(userId)});
- }
-
- async getUserReplies(userId) {
- return await this.request(docIDs.BarcelonaProfileRepliesTabQuery, {userID: String(userId)});
- }
-
- async getPost(postId) {
- return await this.request(docIDs.BarcelonaPostPageQuery, {postID: String(postId)});
- }
-}
-
-module.exports = GraphQLManager;
diff --git a/src/managers/PostManager.js b/src/managers/PostManager.js
index 2acd40e..c78e040 100644
--- a/src/managers/PostManager.js
+++ b/src/managers/PostManager.js
@@ -1,153 +1,35 @@
-const RESTManager = require("./RESTManager");
-
-class PostManager extends RESTManager {
- async fetch(post, paging_token) {
- return await this.request(`/api/v1/text_feed/${post}/replies/` + (paging_token ? `?paging_token=${encodeURIComponent(paging_token)}` : ""));
- }
-
- async likers(post, user) {
- return await this.request(`/api/v1/media/${post}_${user}/likers/`);
- }
-
- async create(
- user,
- options = {
- contents: "",
- data: null,
- }
- ) {
- const requestBody = {
- publish_mode: "text_post",
- text_post_app_info:
- '{"reply_control":0}' + options.data !== null ? options.data : "",
- timezone_offset: "-25200",
- source_type: "4",
- _uid: String(user),
- device_id: `android-${this.client.androidId}`,
- caption: options.contents,
- upload_id: new Date().getTime(),
- device: {
- manufacturer: "OnePlus",
- model: "ONEPLUS+A3010",
- android_version: 25,
- android_release: "7.1.1",
- },
- };
- return await this.request(`/api/v1/media/configure_text_only_post/`, {
- method: "POST",
- body: `signed_body=SIGNATURE.${encodeURIComponent(
- JSON.stringify(requestBody)
- )}`,
- });
- }
-
- async reply(
- user,
- options = {
- contents: "",
- post: "",
- }
- ) {
- let text_post_app_info = JSON.stringify({
- reply_id: options.post,
- reply_control: 0,
- });
- const requestBody = {
- publish_mode: "text_post",
- text_post_app_info,
- timezone_offset: "-25200",
- source_type: "4",
- _uid: String(user),
- device_id: `android-${this.client.androidId}`,
- caption: options.contents,
- upload_id: new Date().getTime(),
- device: {
- manufacturer: "OnePlus",
- model: "ONEPLUS+A3010",
- android_version: 25,
- android_release: "7.1.1",
- },
- };
-
- return await this.request(`/api/v1/media/configure_text_only_post/`, {
- method: "POST",
- body: `signed_body=SIGNATURE.${encodeURIComponent(
- JSON.stringify(requestBody)
- )}`,
- });
- }
-
- async quote(
- user,
- options = {
- contents: "",
- post: "",
- }
- ) {
- let text_post_app_info = JSON.stringify({"quoted_post_id": options.post, "reply_control": 0});
-
- const requestBody = {
- publish_mode: "text_post",
- text_post_app_info,
- timezone_offset: "-25200",
- source_type: "4",
- _uid: user,
- device_id: `android-${this.client.androidId}`,
- caption: options.contents,
- upload_id: new Date().getTime(),
- device: {
- manufacturer: "OnePlus",
- model: "ONEPLUS+A3010",
- android_version: 25,
- android_release: "7.1.1",
- },
- };
- return await this.request(`/api/v1/media/configure_text_only_post/`, {
- method: 'POST',
- body: `signed_body=SIGNATURE.${encodeURIComponent(JSON.stringify(requestBody))}`,
- });
- }
-
- async delete(post, user) {
- return await this.request(
- `/api/v1/media/${post}_${String(user)}/delete/?media_type=TEXT_POST`,
- {
- method: "POST",
- }
- );
- }
-
- async like(post, user) {
- return await this.request(`/api/v1/media/${post}_${String(user)}/like/`, {
- method: "POST",
- });
- }
-
- async unlike(post, user) {
- return await this.request(`/api/v1/media/${post}_${String(user)}/unlike/`, {
- method: "POST",
- });
- }
-
- async repost(post) {
- return await this.request(`/api/v1/repost/create_repost/`, {
- method: 'POST',
- body: 'media_id=' + post
- });
- }
-
- async unrepost(post) {
- return await this.request(`/api/v1/repost/delete_text_app_repost/`, {
- method: 'POST',
- body: 'original_media_id=' + post
- });
- }
-
- async embed(url) {
- return await this.request(
- `/api/v1/text_feed/link_preview/?url=${encodeURIComponent(url)}`
- );
- }
-}
-
-module.exports = PostManager;
+const RESTManager = require('./RESTManager');
+
+class PostManager extends RESTManager {
+ async post(options) {
+ let container;
+ if (typeof(options) === 'string') {
+ await this.request(`/v19.0/me/threads?media_type=IMAGE&text=${options}`, {
+ method: 'POST'
+ }).then(res => {
+ containerId = res.id;
+ });
+ } else {
+ // TODO: not this
+ let mediaType = options.mediaType === 'video' ? 'video' : 'image'
+ if (options.carouselItem) {
+ await this.request(`/v19.0/me/threads?media_type=${mediaType.toUpperCase()}&${mediaType}_url=${options.url}`, {
+ method: 'POST'
+ }).then(res => {
+ container = res.id;
+ });
+ } else {
+ await this.request(`/v19.0/me/threads?media_type=CAROUSEL&children=${options.url}&is_carousel_item=true`, {
+ method: 'POST'
+ }).then(res => {
+ container = res.id;
+ });
+ }
+ }
+ return await this.request(`/v19.0/me/threads_publish?creation_id=${container}`, {
+ method: 'POST'
+ })
+ }
+}
+
+module.exports = PostManager;
\ No newline at end of file
diff --git a/src/managers/RESTManager.js b/src/managers/RESTManager.js
index cc2f17d..96d92ea 100644
--- a/src/managers/RESTManager.js
+++ b/src/managers/RESTManager.js
@@ -10,18 +10,7 @@ class RESTManager {
options = {};
};
options.method = options?.method ?? 'GET';
- options.headers = {
- 'Authorization': `Bearer IGT:2:${this.client.options.token}`,
- 'User-Agent': this.client.options.userAgent,
- 'Sec-Fetch-Site': 'same-origin',
- };
- if (options.method === 'POST') {
- options.headers = {
- ...options.headers,
- 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
- };
- };
- const res = await fetch(this.client.options.base + url, { ... options });
+ const res = await fetch(`${this.client.options.base}${url}&access-token=${this.client.options.token}`, { ... options });
const contentType = res.headers.get('content-type');
if (contentType.includes('application/json')) {
return res.json();
diff --git a/src/managers/RestrictionManager.js b/src/managers/RestrictionManager.js
deleted file mode 100644
index 13d283e..0000000
--- a/src/managers/RestrictionManager.js
+++ /dev/null
@@ -1,23 +0,0 @@
-const RESTManager = require('./RESTManager');
-
-class RestrictionManager extends RESTManager {
- async restrict(user) {
- const requestBody = {
- user_ids: user,
- container_module: "ig_text_feed_timeline"
- };
- return await this.request(`/api/v1/restrict_action/restrict_many/`, {
- method: 'POST',
- body: `signed_body=SIGNATURE.${encodeURIComponent(JSON.stringify(requestBody))}`,
- });
- }
-
- async unrestrict(user) {
- return await this.request(`/api/v1/restrict_action/unrestrict/`, {
- method: 'POST',
- body: `target_user_id=${user}&container_module=ig_text_feed_timeline`,
- });
- }
-}
-
-module.exports = RestrictionManager;
\ No newline at end of file
diff --git a/src/managers/UserManager.js b/src/managers/UserManager.js
index ad29214..6db8aee 100644
--- a/src/managers/UserManager.js
+++ b/src/managers/UserManager.js
@@ -1,12 +1,12 @@
const RESTManager = require('./RESTManager');
class UserManager extends RESTManager {
- async fetch(user) {
- return await this.request(`/api/v1/users/${String(user)}/info`);
+ async get(id) {
+ return await this.request(`/${id ? id : 'me'}?fields=id,username,threads_profile_picture_url,threads_biography`);
}
- async search(query, count) {
- return await this.request(`/api/v1/users/search/?q=${query}&count=${count ?? 30}`);
+ async limit(id) {
+ return await this.request(`/${id ? id : 'me'}/threads_publishing_limit?fields=quota_usage,config`);
}
}
diff --git a/src/util/Bloks.js b/src/util/Bloks.js
deleted file mode 100644
index ce7f378..0000000
--- a/src/util/Bloks.js
+++ /dev/null
@@ -1,36 +0,0 @@
-function parseNestedJson(json) {
- var parsedJson = {};
-
- for (var key in json) {
- if (json.hasOwnProperty(key)) {
- var value = json[key];
-
- if (typeof value === 'string') {
- try {
- parsedJson[key] = JSON.parse(value);
- } catch (error) {
- // assign the original string value
- parsedJson[key] = value;
- }
- } else if (typeof value === 'object') {
- // recursively call for JSON within object value
- parsedJson[key] = parseNestedJson(value);
- } else {
- // assign non-string and non-object values
- parsedJson[key] = value;
- }
- }
- }
-
- return parsedJson;
-}
-
-function parseBloksResponse(text) {
- const { tree } = JSON.parse(text).layout.bloks_payload;
- const sanitized = JSON.parse(JSON.parse(tree['㐟']['#'].match(/\"\{.*\}\"/)[0]));
- const json = parseNestedJson(sanitized);
-
- return json;
-}
-
-module.exports = { parseBloksResponse }
diff --git a/typings/index.d.ts b/typings/index.d.ts
index fb06580..c39eb06 100644
--- a/typings/index.d.ts
+++ b/typings/index.d.ts
@@ -6,463 +6,3 @@
* floating point numbers, and as such cannot accurately represent integers greater than 2^53. This is
* a limitation of JavaScript, not threads.js.
*/
-
-export interface Base {
- status: 'ok' | 'fail';
-}
-
-export interface BiographyWithEntities {
- raw_text: string;
- entities: any[];
-}
-
-export interface BioLink {
- link_id: number;
- url: string;
- lynx_url: string;
- link_type: string;
- title: string;
- open_external_url_with_in_app_browser: string;
-}
-
-export interface Caption {
- pk: string;
- user_id: number;
- text: string;
- type: number;
- created_at: number;
- created_at_utc: number;
- content_type: string;
- status: string;
- bit_flags: number;
- did_report_as_spam: boolean;
- share_enabled: boolean;
- user: any;
- is_covered: boolean;
- is_ranked_comment: boolean;
- media_id: number;
- private_reply_status: number;
-}
-
-export class Client {
- public constructor(options: ClientOptions);
- private _qeSync(): Promise;
- private _encryptPassword(): Promise;
-
- public feeds: FeedManager;
- public friendships: FriendshipManager;
- public graphql: GraphQLManager;
- public login(username: string, password: string): Promise;
- public options: ClientOptions;
- public posts: PostManager;
- public rest: RESTManager;
- public restrictions: RestrictionManager;
- public users: UserManager;
-}
-
-export interface ClientOptions {
- token?: string | null;
- userAgent?: string;
- appId?: string;
- androidId?: string;
- userId?: string | null;
- base?: string;
-}
-
-export interface CreateOptions {
- contents: string;
- data?: null | Object;
-}
-
-export interface CreatorShoppingInfo {
- linked_merchant_accounts: any[];
-}
-
-export interface EncryptedPasswordResponse {
- time: string;
- password: string;
-}
-
-export interface FanClub {
- fan_club_id: number | null;
- fan_club_name: string | null;
- is_fan_club_referral_eligible: boolean | null;
- fan_consideration_page_revamp_eligiblity: boolean | null;
- is_fan_club_gifting_eligible: boolean | null;
- subscriber_count: number | null;
- connected_member_count: number | null;
- autosave_to_exclusive_highlight: boolean | null;
- has_enough_subscribers_for_ssc: boolean | null;
-}
-
-export class FeedManager extends RESTManager {
- fetch(max_id?: string): Promise;
- fetchThreads(user: string | number, max_id?: string): Promise;
- fetchReplies(user: string | number, max_id?: string): Promise;
- recommended(paging_token?: number): Promise;
- notifications(filter?: NotificationFilter, pagination?: NotificationPagination): Promise;
- notificationseen(): Promise;
-}
-
-export class FriendshipManager extends RESTManager {
- show(user: string | number): Promise;
- follow(user: string | number): Promise;
- unfollow(user: string | number): Promise;
- followers(user: string | number): Promise;
- following(user: string | number): Promise;
- mute(user: string | number): Promise;
- unmute(user: string | number): Promise;
- block(user: string | number): Promise;
- unblock(user: string | number): Promise;
-}
-
-export interface FriendshipStatus {
- following: boolean;
- followed_by: boolean;
- blocking: boolean;
- muting: boolean;
- is_private: boolean;
- incoming_request: boolean;
- outgoing_request: boolean;
- text_post_app_pre_following: boolean;
- is_bestie: boolean;
- is_restricted: boolean;
- is_feed_favorite: boolean;
- is_eligible_to_subscribe: boolean;
-}
-
-export class GraphQLManager {
- getLsd(): Promise;
- request(docId: string, variables: string): Promise;
- getUser(userId: string | number): Promise;
- getUserPosts(userId: string | number): Promise;
- getUserReplies(userId: string | number): Promise;
- getPost(postId: string): Promise;
-}
-
-export interface Interests {
- interests: any[];
-}
-
-export type NotificationFilter =
- 'text_post_app_replies' |
- 'text_post_app_mentions' |
- 'verified';
-
-export interface NotificationPagination {
- max_id: string;
- pagination_first_record_timestamp: string;
-}
-
-export interface PagingTokens {
- downwards: string;
-}
-
-export interface PinnedChannels {
- pinned_channel_list: any[];
- has_public_channels: boolean;
-}
-
-export interface Post {
- pk: number;
- id: string;
- taken_at: number;
- device_timestamp: number;
- client_cache_key: string;
- filter_type: number;
- like_and_view_counts_disabled: boolean;
- integrity_review_decision: string;
- text_post_app_info: TextPostAppInfo;
- caption: any;
- media_type: number;
- code: string;
- product_type: string;
- organic_tracking_token: string;
- image_versions2: any;
- original_width: number;
- original_height: number;
- is_dash_eligible?: number;
- video_dash_manifest?: string;
- video_codec?: string;
- has_audio?: boolean;
- video_duration?: number;
- video_versions: any[];
- like_count: number;
- has_liked: boolean;
- can_viewer_reshare: boolean;
- top_likers: any[];
- user: PostUser;
-}
-
-export class PostManager extends RESTManager {
- fetch(post: string, paging_token?: string) : Promise;
- likers(post: string, user: string | number) : Promise;
- create(contents: string, options: CreateOptions) : Promise;
- reply(user: string | number, options: ReplyOptions): Promise;
- quote(user: string | number, options: ReplyOptions): Promise;
- delete(post: string, user: string | number): Promise;
- like(post: string, user: string | number) : Promise;
- unlike(post: string, user: string | number) : Promise;
- repost(post: string) : Promise;
- unrepost(post: string) : Promise;
- embed(url: string) : Promise;
-}
-
-export interface PostUser {
- pk: number;
- pk_id: string;
- username: string;
- full_name: string;
- is_private: boolean;
- is_verified: boolean;
- profile_pic_id: string;
- profile_pic_url: string;
- friendship_status: FriendshipStatus;
- has_anonymous_profile_picture: boolean;
- has_onboarded_to_text_post_app: boolean;
- account_badges: any[];
-}
-
-export interface ProfilePicture {
- url: string;
- width: number;
- height: number;
-}
-
-export interface ReplyOptions {
- contents: string;
- post: string;
-}
-
-export class RESTManager {
- public constructor(client: Client);
- request(url: string, options?: object): Promise;
-}
-
-export class RestrictionManager extends RESTManager {
- restrict(user: string | number): Promise;
- unrestrict(user: string | number): Promise;
-}
-
-export interface TextPostAppInfo {
- is_post_unavailable: boolean;
- is_reply: boolean;
- reply_to_author: any | null;
- direct_reply_count: number;
- self_thread_count: number;
- reply_facepile_users: any[];
- link_preview_attachment: any | null;
- can_reply: boolean;
- reply_control: string;
- hush_info: any | null;
- share_info: any;
-}
-
-export interface Thread extends Base {
- containing_thread: ThreadObject;
- reply_threads: ThreadObject[];
- sibling_threads: any[];
- paging_tokens: PagingTokens;
- downwards_thread_will_continue: boolean;
- target_post_reply_placeholder: string;
-}
-
-export interface ThreadItem {
- post: Post;
- line_type: string;
- view_replies_cta_string: string;
- reply_facepile_users: any[];
- can_inline_expand_below: boolean;
-}
-
-export interface ThreadObject {
- thread_items: ThreadItem[];
- thread_type: string;
- show_create_reply_cta: boolean;
- id: number;
- posts: Post[];
-}
-
-export interface User extends Base {
- user: UserObject;
-}
-
-export class UserManager extends RESTManager {
- fetch(user: string | number): Promise;
- search(query: string, count?: number | string): Promise;
-}
-
-export interface UserObject {
- has_anonymous_profile_picture: boolean;
- is_supervision_features_enabled: boolean;
- is_new_to_instagram: boolean;
- follower_count: number;
- media_count: number;
- following_count: number;
- following_tag_count: number;
- can_use_affiliate_partnership_messaging_as_creator: boolean;
- can_use_affiliate_partnership_messaging_as_brand: boolean;
- has_collab_collections: boolean;
- has_private_collections: boolean;
- bio_interests: Interests;
- has_music_on_profile: boolean;
- is_potential_business: boolean;
- page_id: number;
- page_name: string;
- ads_page_id: number;
- ads_page_name: string;
- can_use_branded_content_discovery_as_creator: boolean;
- can_use_branded_content_discovery_as_brand: boolean;
- fan_club_info: FanClub;
- fbid_v2: string;
- pronouns: any[];
- is_eligible_for_diverse_owned_business_info: boolean;
- is_eligible_to_display_diverse_owned_business_info: boolean;
- is_whatsapp_linked: boolean;
- transparency_product_enabled: boolean;
- account_category: string;
- interop_messaging_user_fbid: number;
- bio_links: BioLink[];
- can_add_fb_group_link_on_profile: boolean;
- external_url: string;
- show_shoppable_feed: boolean;
- merchant_checkout_style: string;
- seller_shoppable_feed_type: string;
- creator_shopping_info: CreatorShoppingInfo;
- has_guides: boolean;
- has_highlight_reels: boolean;
- hd_profile_pic_url_info: ProfilePicture;
- hd_profile_pic_versions: ProfilePicture[];
- is_interest_account: boolean;
- is_favorite: boolean;
- is_favorite_for_stories: boolean;
- is_favorite_for_igtv: boolean;
- is_favorite_for_clips: boolean;
- is_favorite_for_highlights: boolean;
- live_subscription_status: string;
- is_bestie: boolean;
- usertags_count: number;
- total_ar_effects: number;
- total_clips_count: number;
- has_videos: boolean;
- total_igtv_videos: number;
- has_igtv_series: boolean;
- biography: string;
- include_direct_blacklist_status: boolean;
- biography_with_entities: BiographyWithEntities;
- show_fb_link_on_profile: boolean;
- primary_profile_link_type: number;
- can_hide_category: boolean;
- can_hide_public_contacts: boolean;
- should_show_category: boolean;
- category_id: number;
- is_category_tappable: boolean;
- should_show_public_contacts: boolean;
- is_eligible_for_smb_support_flow: boolean;
- is_eligible_for_lead_center: boolean;
- is_experienced_advertiser: boolean;
- lead_details_app_id: string;
- is_business: boolean;
- professional_conversion_suggested_account_type: number;
- account_type: number;
- direct_messaging: string;
- instagram_location_id: number;
- address_street: string;
- business_contact_method: string;
- city_id: number;
- city_name: string;
- contact_phone_number: string;
- is_profile_audio_call_enabled: boolean;
- latitude: number;
- longitude: number;
- public_email: string;
- public_phone_country_code: string;
- public_phone_number: string;
- zip: string;
- mutual_followers_count: number;
- has_onboarded_to_text_post_app: boolean;
- show_text_post_app_badge: boolean;
- show_ig_app_switcher_badge: boolean;
- show_text_post_app_switcher_badge: boolean;
- profile_context: string;
- profile_context_links_with_user_ids: any[];
- profile_context_facepile_users: any[];
- has_chaining: boolean;
- pk: string;
- pk_id: string;
- username: string;
- full_name: string;
- is_private: boolean;
- follow_friction_type: number;
- is_verified: boolean;
- profile_pic_id: string;
- profile_pic_url: string;
- current_catalog_id: any | null;
- mini_shop_seller_onboarding_status: any | null;
- shopping_post_onboard_nux_type: any | null;
- ads_incentive_expiration_date: any | null;
- displayed_action_button_partner: any | null;
- smb_delivery_partner: any | null;
- smb_support_delivery_partner: any | null;
- displayed_action_button_type: string;
- smb_support_partner: any | null;
- is_call_to_action_enabled: boolean;
- num_of_admined_pages: any | null;
- category: string;
- account_badges: any[];
- highlight_reshare_disabled: boolean;
- auto_expand_chaining: any | null;
- feed_post_reshare_disabled: boolean;
- robi_feedback_source: any | null;
- is_memorialized: boolean;
- open_external_url_with_in_app_browser: boolean;
- has_exclusive_feed_content: boolean;
- has_fan_club_subscriptions: boolean;
- pinned_channels_info: PinnedChannels;
- nametag: any | null;
- remove_message_entrypoint: boolean;
- show_account_transparency_details: boolean;
- existing_user_age_collection_enabled: boolean;
- show_post_insights_entry_point: boolean;
- has_public_tab_threads: boolean;
- third_party_downloads_enabled: number;
- is_regulated_c18: boolean;
- is_in_canada: boolean;
- profile_type: number;
- is_profile_broadcast_sharing_enabled: boolean;
-}
-
-export interface UserSearch extends Base {
- num_result: number;
- users: UserSearchObject[];
- has_more: boolean;
- rank_token: string;
-}
-
-export interface UserSearchObject {
- has_anonymous_profile_picture: boolean;
- follower_count: number;
- media_count: number;
- following_count: number;
- following_tag_count: number;
- fbid_v2: string;
- has_onboarded_to_text_post_app: boolean;
- show_text_post_app_badge: boolean;
- text_post_app_joiner_number: number;
- show_ig_app_switcher_badge: boolean;
- pk: string;
- pk_id: string;
- username: string;
- full_name: string;
- is_private: boolean;
- is_verified: boolean;
- profile_pic_id: string;
- profile_pic_url: string;
- has_opt_eligible_shop: boolean;
- account_badges: any[];
- third_party_downloads_enabled: number;
- unseen_count: number;
- friendship_status: FriendshipStatus;
- latest_reel_media: number;
- should_show_category: boolean;
-}
\ No newline at end of file