@dukebot/instagram-scraper-api 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dukebot
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # instagram-scraper-api
2
+
3
+ Reusable wrapper for extracting Instagram profile and post data using modern `import/export` syntax and the `instagram-scraper-api2` provider from RapidAPI.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @dukebot/instagram-scraper-api
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```js
14
+ import { InstagramScraperAPI } from "@dukebot/instagram-scraper-api";
15
+
16
+ const scraper = new InstagramScraperAPI({
17
+ apiHost: "instagram-scraper-api2.p.rapidapi.com",
18
+ apiKey: process.env.RAPIDAPI_KEY,
19
+ });
20
+
21
+ const user = await scraper.getUserInfo("instagram");
22
+ const posts = await scraper.getUserPosts("instagram", 5);
23
+
24
+ console.log(user.username);
25
+ console.log(posts.length);
26
+ ```
27
+
28
+ ## API
29
+
30
+ ### `new InstagramScraperAPI({ apiHost, apiKey, logInfoFn })`
31
+
32
+ - `apiHost`: provider host in RapidAPI.
33
+ - `apiKey`: RapidAPI API key.
34
+ - `logInfoFn`: optional function for logging traces.
35
+
36
+ ### `getUserInfo(usernameOrId)`
37
+
38
+ Returns a `User` instance.
39
+
40
+ ### `getUserPosts(usernameOrId, numPosts)`
41
+
42
+ Returns an array of `Post`. If `numPosts` is provided, the result is limited to that number.
43
+
44
+ ## Publish To npm
45
+
46
+ 1. Run `npm login`.
47
+ 2. Run `npm publish --access public`.
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@dukebot/instagram-scraper-api",
3
+ "version": "0.1.0",
4
+ "description": "Reusable Instagram data scraper wrapper for RapidAPI with modern ESM imports.",
5
+ "type": "module",
6
+ "main": "./src/index.js",
7
+ "exports": {
8
+ ".": "./src/index.js"
9
+ },
10
+ "files": [
11
+ "src",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "keywords": [
16
+ "instagram",
17
+ "scraper",
18
+ "rapidapi",
19
+ "esm",
20
+ "api"
21
+ ],
22
+ "author": "Dukebot",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/Dukebot/instagram-scraper-api.git"
26
+ },
27
+ "homepage": "https://github.com/Dukebot/instagram-scraper-api#readme",
28
+ "bugs": {
29
+ "url": "https://github.com/Dukebot/instagram-scraper-api/issues"
30
+ },
31
+ "license": "MIT",
32
+ "engines": {
33
+ "node": ">=18"
34
+ },
35
+ "scripts": {
36
+ "test": "node --test",
37
+ "user:info": "node ./scripts/get-user-info.js",
38
+ "user:posts": "node ./scripts/get-user-posts.js"
39
+ },
40
+ "dependencies": {
41
+ }
42
+ }
@@ -0,0 +1,3 @@
1
+ export { default as Location } from "./location.js";
2
+ export { default as Post } from "./post.js";
3
+ export { default as User } from "./user.js";
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Instagram location metadata attached to a post.
3
+ */
4
+ export default class Location {
5
+ /**
6
+ * @param {Object} [data={}] Raw location data.
7
+ * @param {string|number|null} [data.pk]
8
+ * @param {string|null} [data.shortName]
9
+ * @param {string|number|null} [data.facebookPlacesId]
10
+ * @param {string|null} [data.externalSource]
11
+ * @param {string|null} [data.name]
12
+ * @param {string|null} [data.address]
13
+ * @param {string|null} [data.city]
14
+ * @param {boolean|null} [data.hasViewerSaved]
15
+ * @param {number|null} [data.lng]
16
+ * @param {number|null} [data.lat]
17
+ * @param {boolean|null} [data.isEligibleForGuides]
18
+ */
19
+ constructor(data = {}) {
20
+ this.pk = data.pk ?? null;
21
+ this.shortName = data.shortName ?? null;
22
+ this.facebookPlacesId = data.facebookPlacesId ?? null;
23
+ this.externalSource = data.externalSource ?? null;
24
+ this.name = data.name ?? null;
25
+ this.address = data.address ?? null;
26
+ this.city = data.city ?? null;
27
+ this.hasViewerSaved = data.hasViewerSaved ?? null;
28
+ this.lng = data.lng ?? null;
29
+ this.lat = data.lat ?? null;
30
+ this.isEligibleForGuides = data.isEligibleForGuides ?? null;
31
+ }
32
+ }
@@ -0,0 +1,50 @@
1
+ import Location from "./location.js";
2
+
3
+ /**
4
+ * Normalized Instagram post entity.
5
+ */
6
+ export default class Post {
7
+ /**
8
+ * @param {Object} [data={}] Raw post data.
9
+ * @param {string|number|null} [data.pk]
10
+ * @param {string|number|null} [data.id]
11
+ * @param {string|number|null} [data.fbId]
12
+ * @param {string|number|null} [data.mediaId]
13
+ * @param {string|null} [data.code]
14
+ * @param {string|null} [data.title]
15
+ * @param {string|null} [data.text]
16
+ * @param {string|null} [data.externalUrl]
17
+ * @param {number|null} [data.takenAt]
18
+ * @param {string|null} [data.date]
19
+ * @param {string|null} [data.time]
20
+ * @param {Object|Location|null} [data.location]
21
+ * @param {string|null} [data.socialNetwork]
22
+ * @param {string|null} [data.accountUsername]
23
+ * @param {string|number|null} [data.accountId]
24
+ * @param {number|null} [data.likeCount]
25
+ * @param {number|null} [data.commentCount]
26
+ * @param {{url: string, caption: string|null}[]} [data.images]
27
+ * @param {{url: string, caption: string|null}[]} [data.videos]
28
+ */
29
+ constructor(data = {}) {
30
+ this.pk = data.pk ?? null;
31
+ this.id = data.id ?? null;
32
+ this.fbId = data.fbId ?? null;
33
+ this.mediaId = data.mediaId ?? null;
34
+ this.code = data.code ?? null;
35
+ this.title = data.title ?? null;
36
+ this.text = data.text ?? null;
37
+ this.externalUrl = data.externalUrl ?? null;
38
+ this.takenAt = data.takenAt ?? null;
39
+ this.date = data.date ?? null;
40
+ this.time = data.time ?? null;
41
+ this.location = data.location ? new Location(data.location) : null;
42
+ this.socialNetwork = data.socialNetwork ?? "Instagram";
43
+ this.accountUsername = data.accountUsername ?? null;
44
+ this.accountId = data.accountId ?? null;
45
+ this.likeCount = data.likeCount ?? null;
46
+ this.commentCount = data.commentCount ?? null;
47
+ this.images = data.images ?? [];
48
+ this.videos = data.videos ?? [];
49
+ }
50
+ }
@@ -0,0 +1,26 @@
1
+ import Post from "./post.js";
2
+
3
+ /**
4
+ * Normalized Instagram user entity.
5
+ */
6
+ export default class User {
7
+ /**
8
+ * @param {Object} [data={}] Raw user data.
9
+ * @param {string|number|null} [data.otherId]
10
+ * @param {string|null} [data.name]
11
+ * @param {string|null} [data.username]
12
+ * @param {string|null} [data.biography]
13
+ * @param {string|null} [data.externalUrl]
14
+ * @param {string|null} [data.socialNetwork]
15
+ * @param {Object[]|Post[]} [data.posts]
16
+ */
17
+ constructor(data = {}) {
18
+ this.otherId = data.otherId ?? null;
19
+ this.name = data.name ?? null;
20
+ this.username = data.username ?? null;
21
+ this.biography = data.biography ?? null;
22
+ this.externalUrl = data.externalUrl ?? null;
23
+ this.socialNetwork = data.socialNetwork ?? "Instagram";
24
+ this.posts = (data.posts ?? []).map((post) => new Post(post));
25
+ }
26
+ }
package/src/index.js ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Public entry point for the package.
3
+ */
4
+ export { default as InstagramScraperAPI } from "./instagram_scraper_api.js";
5
+ export * from "./entities/index.js";
@@ -0,0 +1,40 @@
1
+ import { InstagramScraperAPI2Service } from "./instagram_scraper_api2/index.js";
2
+
3
+ /**
4
+ * High-level API for retrieving Instagram profile data and posts.
5
+ */
6
+ export default class InstagramScraperAPI {
7
+ /**
8
+ * @param {Object} [data={}] Configuration object.
9
+ * @param {string} [data.apiHost] RapidAPI host.
10
+ * @param {string} [data.apiKey] RapidAPI key.
11
+ */
12
+ constructor({ apiHost, apiKey } = {}) {
13
+ this.service = new InstagramScraperAPI2Service({ apiHost, apiKey });
14
+ }
15
+
16
+ /**
17
+ * Fetches Instagram profile information for a user.
18
+ *
19
+ * @param {string} usernameOrId Instagram username or account id.
20
+ * @returns {Promise<import("./entities/user.js").default>}
21
+ */
22
+ async getUserInfo(usernameOrId) {
23
+ return this.service.getUserInfo(usernameOrId);
24
+ }
25
+
26
+ /**
27
+ * Fetches Instagram posts for a user.
28
+ *
29
+ * @param {string} usernameOrId Instagram username or account id.
30
+ * @param {number} [numPosts] Maximum number of posts to return.
31
+ * @returns {Promise<import("./entities/post.js").default[]>}
32
+ */
33
+ async getUserPosts(usernameOrId, numPosts) {
34
+ const posts = await this.service.getUserPosts(usernameOrId);
35
+ if (!numPosts || numPosts < 1) {
36
+ return posts;
37
+ }
38
+ return posts.slice(0, numPosts);
39
+ }
40
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Low-level client for interacting with the RapidAPI provider.
3
+ */
4
+ export default class InstagramScraperAPI2Client {
5
+ /**
6
+ * @param {Object} [data={}] Client configuration.
7
+ * @param {string} [data.apiHost] RapidAPI host.
8
+ * @param {string} [data.apiKey] RapidAPI key.
9
+ */
10
+ constructor({ apiHost, apiKey } = {}) {
11
+ if (!apiHost) throw new Error("apiHost is required");
12
+ if (!apiKey) throw new Error("apiKey is required");
13
+
14
+ this.baseURL = "https://instagram-scraper-api2.p.rapidapi.com/";
15
+ this.apiHost = apiHost;
16
+ this.apiKey = apiKey;
17
+ }
18
+
19
+ /**
20
+ * Performs a GET request against the provider.
21
+ *
22
+ * @param {string} url Relative API path.
23
+ * @param {{ params?: Record<string, string | number | boolean | null | undefined> }} [options]
24
+ * @returns {Promise<any>}
25
+ */
26
+ async get(url, options) {
27
+ try {
28
+ const requestUrl = new URL(url, this.baseURL);
29
+ const params = options?.params ?? {};
30
+
31
+ Object.entries(params).forEach(([key, value]) => {
32
+ if (value !== undefined && value !== null) {
33
+ requestUrl.searchParams.set(key, value);
34
+ }
35
+ });
36
+
37
+ const response = await fetch(requestUrl, {
38
+ method: "GET",
39
+ headers: {
40
+ "x-rapidapi-host": this.apiHost,
41
+ "x-rapidapi-key": this.apiKey,
42
+ },
43
+ });
44
+
45
+ const data = await response.json();
46
+
47
+ if (!response.ok) {
48
+ throw new Error(data?.message ?? `Request failed with status ${response.status}`);
49
+ }
50
+
51
+ if (data.status === "error") {
52
+ throw new Error(data.message);
53
+ }
54
+
55
+ return data;
56
+ } catch (error) {
57
+ throw new Error(error.message);
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Fetches profile information.
63
+ *
64
+ * @param {string} usernameOrIdOrUrl Instagram username, id, or profile URL.
65
+ * @returns {Promise<any>}
66
+ */
67
+ async getUserInfo(usernameOrIdOrUrl) {
68
+ if (!usernameOrIdOrUrl) {
69
+ throw new Error("usernameOrIdOrUrl is required");
70
+ }
71
+
72
+ return this.get("/v1/info", {
73
+ params: { username_or_id_or_url: usernameOrIdOrUrl },
74
+ });
75
+ }
76
+
77
+ /**
78
+ * Fetches posts for a profile.
79
+ *
80
+ * @param {string} usernameOrIdOrUrl Instagram username, id, or profile URL.
81
+ * @returns {Promise<any>}
82
+ */
83
+ async getUserPosts(usernameOrIdOrUrl) {
84
+ if (!usernameOrIdOrUrl) {
85
+ throw new Error("usernameOrIdOrUrl is required");
86
+ }
87
+
88
+ return this.get("/v1.2/posts", {
89
+ params: { username_or_id_or_url: usernameOrIdOrUrl },
90
+ });
91
+ }
92
+ }
@@ -0,0 +1 @@
1
+ export { default as InstagramScraperAPI2Service } from "./service.js";
@@ -0,0 +1,112 @@
1
+ import { Post, User } from "../entities/index.js";
2
+
3
+ /**
4
+ * Maps provider responses into the package's normalized entities.
5
+ */
6
+ export default class InstagramScraperAPI2ResponseProcessor {
7
+ /**
8
+ * @param {{ data: any }} response Provider response payload.
9
+ * @returns {User}
10
+ */
11
+ static processUserInfoResponse(response) {
12
+ return transformUser(response.data);
13
+ }
14
+
15
+ /**
16
+ * @param {{ data: { items: any[] } }} response Provider response payload.
17
+ * @returns {Post[]}
18
+ */
19
+ static processUserPostsResponse(response) {
20
+ return response.data.items.map(transformPost);
21
+ }
22
+ }
23
+
24
+ /**
25
+ * @param {any} user
26
+ * @returns {User}
27
+ */
28
+ function transformUser(user) {
29
+ return new User({
30
+ otherId: user.id,
31
+ name: user.full_name,
32
+ username: user.username,
33
+ biography: user.biography,
34
+ externalUrl: user.external_url,
35
+ });
36
+ }
37
+
38
+ /**
39
+ * @param {any} post
40
+ * @returns {Post}
41
+ */
42
+ function transformPost(post) {
43
+ const getImageUrlFromImageVersions = () => post.image_versions?.items?.[0]?.url ?? null;
44
+ const getVideoUrlFromVideoVersions = () => post.video_versions?.[0]?.url ?? null;
45
+ const urlToMediaObject = (url) => ({ url, caption: null });
46
+
47
+ function transformLocation() {
48
+ const location = post.location;
49
+ if (!location) {
50
+ return null;
51
+ }
52
+
53
+ return {
54
+ pk: location.id,
55
+ name: location.name,
56
+ lat: location.lat,
57
+ lng: location.lng,
58
+ };
59
+ }
60
+
61
+ function getDate() {
62
+ if (!post.taken_at) return null;
63
+ const dateTime = timestampToDateString(post.taken_at);
64
+ return dateTime.split(" ")[0];
65
+ }
66
+
67
+ function getTime() {
68
+ if (!post.taken_at) return null;
69
+ const dateTime = timestampToDateString(post.taken_at);
70
+ return dateTime.split(" ")[1];
71
+ }
72
+
73
+ return new Post({
74
+ pk: post.id,
75
+ id: post.id,
76
+ fbId: post.fbid,
77
+ code: post.code,
78
+ text: post.caption?.text ?? null,
79
+ takenAt: post.taken_at ? Number(post.taken_at) : null,
80
+ date: getDate(),
81
+ time: getTime(),
82
+ location: transformLocation(),
83
+ accountUsername: post.user?.username ?? null,
84
+ accountId: post.user?.id ?? null,
85
+ likeCount: post.like_count ?? null,
86
+ commentCount: post.comment_count ?? null,
87
+ images: [getImageUrlFromImageVersions()].filter(Boolean).map(urlToMediaObject),
88
+ videos: [getVideoUrlFromVideoVersions()].filter(Boolean).map(urlToMediaObject),
89
+ });
90
+ }
91
+
92
+ /**
93
+ * Formats a Unix timestamp using the same token pattern previously used by Moment.
94
+ *
95
+ * @param {number|string} timestamp Unix timestamp in seconds.
96
+ * @param {string} [format="YYYY-MM-DD HH:mm:ss"] Output pattern.
97
+ * @returns {string}
98
+ */
99
+ function timestampToDateString(timestamp, format = "YYYY-MM-DD HH:mm:ss") {
100
+ const date = new Date(Number(timestamp) * 1000);
101
+
102
+ const parts = {
103
+ YYYY: String(date.getFullYear()),
104
+ MM: String(date.getMonth() + 1).padStart(2, "0"),
105
+ DD: String(date.getDate()).padStart(2, "0"),
106
+ HH: String(date.getHours()).padStart(2, "0"),
107
+ mm: String(date.getMinutes()).padStart(2, "0"),
108
+ ss: String(date.getSeconds()).padStart(2, "0"),
109
+ };
110
+
111
+ return format.replace(/YYYY|MM|DD|HH|mm|ss/g, (token) => parts[token]);
112
+ }
@@ -0,0 +1,39 @@
1
+ import InstagramScraperAPI2Client from "./client.js";
2
+ import InstagramScraperAPI2ResponseProcessor from "./response-processor.js";
3
+
4
+ /**
5
+ * Internal service that coordinates the provider client and response mapping.
6
+ */
7
+ export default class InstagramScraperAPI2Service {
8
+ /**
9
+ * @param {Object} [data={}] Service configuration.
10
+ * @param {string} [data.apiHost] RapidAPI host.
11
+ * @param {string} [data.apiKey] RapidAPI key.
12
+ */
13
+ constructor({ apiHost, apiKey } = {}) {
14
+ this.client = new InstagramScraperAPI2Client({ apiHost, apiKey });
15
+ this.responseProcessor = InstagramScraperAPI2ResponseProcessor;
16
+ }
17
+
18
+ /**
19
+ * Fetches and maps profile data into a normalized user entity.
20
+ *
21
+ * @param {string} usernameOrIdOrUrl Instagram username, id, or profile URL.
22
+ * @returns {Promise<import("../entities/user.js").default>}
23
+ */
24
+ async getUserInfo(usernameOrIdOrUrl) {
25
+ const response = await this.client.getUserInfo(usernameOrIdOrUrl);
26
+ return this.responseProcessor.processUserInfoResponse(response);
27
+ }
28
+
29
+ /**
30
+ * Fetches and maps profile posts into normalized post entities.
31
+ *
32
+ * @param {string} usernameOrIdOrUrl Instagram username, id, or profile URL.
33
+ * @returns {Promise<import("../entities/post.js").default[]>}
34
+ */
35
+ async getUserPosts(usernameOrIdOrUrl) {
36
+ const response = await this.client.getUserPosts(usernameOrIdOrUrl);
37
+ return this.responseProcessor.processUserPostsResponse(response);
38
+ }
39
+ }