├── .eslintrc.json
├── .github
└── workflows
│ └── node.js.yml
├── .gitignore
├── .idea
├── .gitignore
├── jsLibraryMappings.xml
├── modules.xml
├── user-instagram-reborn.iml
└── vcs.xml
├── README.md
├── app
├── data.json
└── services
│ ├── AspectRatioCalculator.js
│ ├── BasicRequestHandler.js
│ ├── CacheStorage.js
│ ├── CookieReader.js
│ └── InstagramHelper.js
├── babel.config.json
├── index.js
├── jest.config.json
├── package-lock.json
├── package.json
├── src
└── Instagram
│ ├── Application
│ ├── AuthenticationHandler.js
│ ├── GetPostDataHandler.js
│ ├── GetUserDataHandler.js
│ └── Query
│ │ ├── AuthenticationQuery.js
│ │ ├── GetPostDataQuery.js
│ │ └── GetUserDataQuery.js
│ ├── Domain
│ ├── ChildMedia.js
│ ├── Comment.js
│ ├── CommentOwner.js
│ ├── Dimensions.js
│ ├── Location.js
│ ├── Media.js
│ ├── MediaType.js
│ ├── MediaVariant.js
│ ├── Owner.js
│ ├── Post.js
│ ├── Session.js
│ ├── TaggedUser.js
│ ├── User.js
│ └── errors
│ │ ├── InstagramAuthenticationError.js
│ │ ├── InstagramGenericError.js
│ │ ├── NoSessionError.js
│ │ ├── PostNotFoundError.js
│ │ ├── RateLimitError.js
│ │ ├── TooManyLoginsError.js
│ │ └── UserNotFoundError.js
│ └── Infrastructure
│ └── InstagramRepository.js
└── tests
└── app
└── services
├── aspectRatioCalculator.spec.js
└── cookieReader.spec.js
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | "parser": "@babel/eslint-parser",
4 | "parserOptions": {
5 | "babelOptions": {
6 | "configFile": "./babel.config.json"
7 | }
8 | },
9 | "rules": {
10 | "lines-between-class-members": "off",
11 | "comma-dangle": "off",
12 | "max-len": "off",
13 | "no-trailing-spaces": "off",
14 | "dot-notation": "off",
15 | "indent": "off",
16 | "quote-props": "off",
17 | "no-shadow": "off",
18 | "prefer-destructuring": "off",
19 | "no-prototype-builtins": "off",
20 | "object-shorthand": "off",
21 | "consistent-return": "off"
22 | },
23 | "env": {
24 | "jest": true
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: Node.js CI
5 |
6 | on:
7 | push:
8 | branches: [ master, '2.2' ]
9 | pull_request:
10 | branches: [ master ]
11 |
12 | jobs:
13 | build:
14 | runs-on: ubuntu-latest
15 | strategy:
16 | matrix:
17 | node-version: [14.16.1]
18 | steps:
19 | - uses: actions/checkout@v2
20 | - name: Use Node.js ${{ matrix.node-version }}
21 | uses: actions/setup-node@v2
22 | with:
23 | node-version: ${{ matrix.node-version }}
24 | - name: Install dependencies
25 | run: npm install
26 | - name: Running ESLint
27 | run: npm run eslint
28 | - name: Run unit tests
29 | run: npm run test
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Datasource local storage ignored files
5 | /dataSources/
6 | /dataSources.local.xml
7 | # Editor-based HTTP Client requests
8 | /httpRequests/
9 |
--------------------------------------------------------------------------------
/.idea/jsLibraryMappings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/user-instagram-reborn.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # User Instagram - DEPRECATED
2 |
3 | [](https://img.shields.io/david/EdouardCourty/user-instagram)
4 | [](https://github.com/EdouardCourty/user-instagram/actions/workflows/node.js.yml)
5 |
6 | ## The end
7 |
8 | Thursday 07 2022.
9 | Instagram's API & Authentication changed a lot and I don't have the time to update and reverse-engineer everything again.
10 | I have no choice but to deprecate this module.
11 | If you want to become the official maintainer, feel free to open an issue and we'll get in touch!
12 |
13 | Thanks to the thousands of people that used this module through the years!
14 | Big love from Paris!
15 |
16 | ## Usage
17 |
18 | Use this module in your projet by installing it with `npm install user-instagram`.
19 |
20 | Here is a quick example or usage:
21 | ```javascript
22 | const instagram = require('user-instagram');
23 |
24 | await instagram.authenticate('my_instagram_username', 'my_instagram_password');
25 |
26 | // Fetching a user
27 | instagram.getUserData('edouard_courty').then(userData => {
28 | // Do whatever you need to to with this data
29 | console.log(`My username is ${userData.getUsername()}.`);
30 | })
31 |
32 | // Fetching a post
33 | instagram.getPostData('CUc7tBPFXvP').then(postData => {
34 | // Do whatever you need to to with this data
35 | console.log(`The caption of this post is ${postData.getCaption()}.`);
36 | })
37 | ```
38 |
39 | ## Documentation
40 |
41 | In the previous versions of `user-instagram`, only a small amount of requests could be sent every day without getting rate-mimited.
42 | Couple of issues were submitted about this problem and it's the main reason why I decided to refactor this module, and add an authentication method to it.
43 |
44 | ### Authentication
45 |
46 | The `authenticate` method takes two mandatory parameters: `username` and `password`.
47 | A good way to keep these strings safe is storing them in an uncommited file in your repo like some `instagram_config.json` file structured like the following:
48 | ```json
49 | {
50 | "username": "your username here",
51 | "password": "your password here"
52 | }
53 | ```
54 | Then use it like this:
55 | ```javascript
56 | const instagram = require('user-instagram');
57 | const {username, password} = require('instagram_config.json')
58 |
59 | await instagram.authenticate(username, password);
60 | ```
61 |
62 | ### Getting a user's data
63 |
64 | When logged in, you can request the data of any public user you want, plus the private users that your account follows.
65 | The `getUserData` method takes only one parameter: the username of the user to be fetched.
66 |
67 | This method will return a promise holding a `User` class, containing getters for all the interesting properties of this class.
68 |
69 | #### Available user properties
70 |
71 | All the boolean values are accessed with the following methods: (the function names should be self explanatory of their return value)
72 | - `isVerified()`
73 | - `isPrivate()`
74 | - `isBusinessAccount()`
75 | - `isProfessionalAccount()`
76 | - `hasClips()`
77 | - `hasArEffect()`
78 | - `hasChannel()`
79 | - `hasGuides()`
80 | - `isHidingLikesAndViewsCount()`
81 | - `hasJoinedRecently()`
82 |
83 | All the non-boolean values are accessed with the following methods:
84 | - `getUsername()`
85 | - `getBiography()`
86 | - `getPublicationsCount()`
87 | - `getFollowersCount()`
88 | - `getExternalUrl()`
89 | - `getFollowingCount()`
90 | - `getFullName()`
91 | - `getHighlightsReelsCount()`
92 | - `getId()`
93 | - `getBusinessAddressJson()`
94 | - `getBusinessContactMethod()`
95 | - `getBusinessEmail()`
96 | - `getBusinessPhoneNumber()`
97 | - `getBusinessCategoryName()`
98 | - `getOverallCategoryName()`
99 | - `getCategoryEnum()`
100 | - `getProfilePicture()`
101 | - `getHdProfilePicture()`
102 | - `getPronouns()`
103 | - `getMedias()`
104 |
105 | ### Getting a post's data
106 |
107 | When logged in, you can request the data of any public post you want, plus the posts of the private accounts that your account follows.
108 | The `getPostData` method takes only one parameter: the shortcode of the post to be fetched.
109 |
110 | This method will return a promise holding a `Post` class, containing getters for all the interesting properties of this class.
111 |
112 | #### Available post properties
113 |
114 | All the boolean values are accessed with the following methods: (the function names should be self explanatory of their return value)
115 | - `isVideo()`
116 | - `areCommentsDisabled()`
117 | - `areLikesAndViewsCountDisabled()`
118 | - `isPaidPartnership()`
119 | - `isAd()`
120 | - `hasAudio()`
121 |
122 | All the non-boolean values are accessed with the following methods:
123 | - `getId()`
124 | - `getType()`
125 | - `getShortcode()`
126 | - `getDimensions()`
127 | - `getDisplayUrl()`
128 | - `getVariants()`
129 | - `getAccessibilityCaption()`
130 | - `getTaggedUsers()`
131 | - `getCaption()`
132 | - `getCommentsCount()`
133 | - `getComments()`
134 | - `getDate()`
135 | - `getLikesCount()`
136 | - `getLocation()`
137 | - `getOwner()`
138 | - `getChildren()`
139 | - `getVideoViewsCount()`
140 | - `getVideoPlaysCount()`
141 |
142 | #### Generic properties shared across the module
143 |
144 | Each Media from the `getMedias()` in the `User` class method is a `Media` class that has these getters:
145 | - `getType()`
146 | - `getId()`
147 | - `getShortcode()`
148 | - `getCaption()`
149 | - `getDimensions()`
150 | - `getDisplayUrl()`
151 | - `getTaggedUsers()`
152 | - `isVideo()`
153 | - `getAccessibilityCaption()`
154 | - `areCommentsDisabled()`
155 | - `getCommentsCount()`
156 | - `getLikesCount()`
157 | - `getTimestamp()`
158 | - `getLocation()`
159 | - `getChildren()`
160 | - `hasAudio()`
161 | - `getViewsCount()`
162 | - `getVideoUrl()`
163 |
164 | Every `TaggerUser` from `getTaggedUsers()` in a `Post` or a `User.getMedias()` hold the following getters:
165 | - `getTagXPosition()`
166 | - `getTagYPosition()`
167 | - `getFullName()`
168 | - `getId()`
169 | - `isVerified()`
170 | - `getProfilePictureUrl()`
171 | - `getUsername()`
172 |
173 | Every dimension value from `getDimensions()` from a `Media` or a `Post` is a `Dimension` class with a bult-in aspect-ratio calculator:
174 | - `getHeight()`
175 | - `getWidth()`
176 | - `getAspectRatio()`
177 |
178 | © Edouard Courty - 2021
179 |
--------------------------------------------------------------------------------
/app/data.json:
--------------------------------------------------------------------------------
1 | {
2 | "usernameUrlTemplate": "https://instagram.com/%USERNAME%/?__a=1",
3 | "postUrlTemplate": "https://instagram.com/p/%SHORTCODE%/?__a=1",
4 | "headers": {
5 | "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36",
6 | "sec-fetch-site": "none",
7 | "sec-fetch-dest": "document",
8 | "sec-fetch-mode": "navigate",
9 | "sec-fetch-user": "?1",
10 | "sec-ch-ua-mobile": "?0",
11 | "sec-ch-ua-platform": "\"Linux\"",
12 | "sec-ch-ua": "\"Google Chrome\";v=\"93\", \" Not;A Brand\";v=\"99\", \"Chromium\";v=\"93\"",
13 | "accept-encoding": "gzip, deflate, br"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/app/services/AspectRatioCalculator.js:
--------------------------------------------------------------------------------
1 | class AspectRatioCalculator {
2 | /**
3 | * @param {number} a
4 | * @param {number} b
5 | * @returns {number}
6 | */
7 | static #gcd = (a, b) => (b === 0 ? a : this.#gcd(b, a % b));
8 | /**
9 | * @param {number} a
10 | * @param {number} b
11 | * @returns {number[]}
12 | */
13 | static #highestFirst = (a, b) => (a < b ? [b, a] : [a, b]);
14 | /**
15 | * @param {number} h
16 | * @param {number} w
17 | * @param {number} divisor
18 | * @param {string|null} separator
19 | * @returns {string}
20 | */
21 | static #format = (h, w, divisor, separator) => `${h / divisor}${separator}${w / divisor}`;
22 |
23 | /**
24 | * @param {number} height
25 | * @param {number} width
26 | * @param {string} separator
27 | * @returns {string}
28 | */
29 | static getAspectRatio(height, width, separator = ':') {
30 | const [h, w] = this.#highestFirst(height, width);
31 | const divisor = this.#gcd(h, w);
32 | return this.#format(h, w, divisor, separator);
33 | }
34 | }
35 |
36 | module.exports = AspectRatioCalculator;
37 |
--------------------------------------------------------------------------------
/app/services/BasicRequestHandler.js:
--------------------------------------------------------------------------------
1 | const axios = require('axios');
2 |
3 | const applicationData = require('../data.json');
4 |
5 | const CacheStorage = require('./CacheStorage');
6 | const NoSessionError = require('../../src/Instagram/Domain/errors/NoSessionError');
7 | const InstagramRepository = require('../../src/Instagram/Infrastructure/InstagramRepository');
8 |
9 | class BasicRequestHandler {
10 | static async handle(queryUrl) {
11 | if (!CacheStorage.get().has('session')) {
12 | throw new NoSessionError();
13 | }
14 |
15 | /** @type Session */
16 | let session = CacheStorage.get().get('session');
17 |
18 | if (session.getExpiryDate() < new Date()) {
19 | await InstagramRepository.authenticate();
20 | session = CacheStorage.get().get('session');
21 | }
22 |
23 | return new Promise((resolve, reject) => {
24 | const headers = {
25 | ...applicationData.headers,
26 | cookie: `sessionid=${session.getId()}`
27 | };
28 | axios.get(queryUrl, {
29 | headers: headers
30 | }).then(resolve).catch(reject);
31 | });
32 | }
33 | }
34 |
35 | module.exports = BasicRequestHandler;
36 |
--------------------------------------------------------------------------------
/app/services/CacheStorage.js:
--------------------------------------------------------------------------------
1 | class CacheStorage {
2 | static #pool = new Map();
3 |
4 | static get = () => this.#pool;
5 | }
6 |
7 | module.exports = CacheStorage;
8 |
--------------------------------------------------------------------------------
/app/services/CookieReader.js:
--------------------------------------------------------------------------------
1 | class CookieReader {
2 | /**
3 | * @param {string} cookie
4 | * @param {string} cookieKey
5 | * @returns Promise
6 | */
7 | static read(cookie, cookieKey) {
8 | const key = cookieKey.trim();
9 | return new Promise((resolve, reject) => {
10 | cookie
11 | .split('=')
12 | .map((splitted) => splitted.split(';'))
13 | .flat(1)
14 | .forEach((arrayValue, index, array) => {
15 | const value = arrayValue.trim();
16 | if (index > 0 && array[index - 1].trim() === key) {
17 | return resolve(value);
18 | }
19 | });
20 | return reject();
21 | });
22 | }
23 | }
24 |
25 | module.exports = CookieReader;
26 |
--------------------------------------------------------------------------------
/app/services/InstagramHelper.js:
--------------------------------------------------------------------------------
1 | class InstagramHelper {
2 | static BASE_URL = 'https://www.instagram.com/';
3 | static AUTH_URL = 'https://www.instagram.com/accounts/login/ajax/';
4 | static DEFAULT_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36';
5 |
6 | static #ENCRYPTED_PASSWORD_TEMPLATE = '#PWD_INSTAGRAM_BROWSER:0:%TIME%:%PASSWORD%'
7 |
8 | static storage = new Map();
9 |
10 | /**
11 | * @param {string} password
12 | * @returns {string}
13 | */
14 | static getEncryptedPasswordFromPassword(password) {
15 | const currentDate = new Date();
16 | const unixTimestamp = Math.round(currentDate.getTime() / 1000);
17 | return this.#ENCRYPTED_PASSWORD_TEMPLATE
18 | .replace('%TIME%', unixTimestamp.toString())
19 | .replace('%PASSWORD%', password);
20 | }
21 | }
22 |
23 | module.exports = InstagramHelper;
24 |
--------------------------------------------------------------------------------
/babel.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | "@babel/plugin-proposal-class-properties",
4 | "@babel/plugin-proposal-private-methods"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const GetUserDataHandler = require('./src/Instagram/Application/GetUserDataHandler');
2 | const GetPostDataHandler = require('./src/Instagram/Application/GetPostDataHandler');
3 | const AuthenticationHandler = require('./src/Instagram/Application/AuthenticationHandler');
4 |
5 | const GetUserDataQuery = require('./src/Instagram/Application/Query/GetUserDataQuery');
6 | const GetPostDataQuery = require('./src/Instagram/Application/Query/GetPostDataQuery');
7 | const AuthenticationQuery = require('./src/Instagram/Application/Query/AuthenticationQuery');
8 |
9 | module.exports = {
10 | /**
11 | * @param {string} username
12 | * @returns Promise
13 | */
14 | getUserData: (username) => GetUserDataHandler.handle(new GetUserDataQuery(username)),
15 | /**
16 | * @param {string} shortCode
17 | * @returns {Promise}
18 | */
19 | getPostData: (shortCode) => GetPostDataHandler.handle(new GetPostDataQuery(shortCode)),
20 | /**
21 | * @param {string} username
22 | * @param {string} password
23 | * @returns {Promise}
24 | */
25 | authenticate: (username, password) => AuthenticationHandler.handle(new AuthenticationQuery(username, password))
26 | };
27 |
--------------------------------------------------------------------------------
/jest.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "verbose": true,
3 | "transform": {}
4 | }
5 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "user-instagram",
3 | "version": "3.0.2",
4 | "description": "Get user & post data without authentication",
5 | "main": "index.js",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/EdouardCourty/user-instagram.git"
9 | },
10 | "type": "commonjs",
11 | "keywords": [
12 | "Instagram",
13 | "Instagram API",
14 | "Instagram user",
15 | "Scrapping"
16 | ],
17 | "author": "Edouard Courty",
18 | "license": "MIT",
19 | "bugs": {
20 | "url": "https://github.com/EdouardCourty/user-instagram/issues"
21 | },
22 | "homepage": "https://github.com/EdouardCourty/user-instagram#readme",
23 | "dependencies": {
24 | "axios": "^0.21.1",
25 | "cookie-parser": "^1.4.5"
26 | },
27 | "scripts": {
28 | "eslint": "eslint .",
29 | "test": "node --experimental-vm-modules node_modules/.bin/jest tests"
30 | },
31 | "devDependencies": {
32 | "@babel/eslint-parser": "^7.14.5",
33 | "@babel/plugin-proposal-class-properties": "^7.14.5",
34 | "@babel/plugin-proposal-private-methods": "^7.14.5",
35 | "eslint": "^7.32.0",
36 | "eslint-config-airbnb": "^18.2.1",
37 | "eslint-plugin-import": "^2.25.2",
38 | "eslint-plugin-jsx-a11y": "^6.4.1",
39 | "eslint-plugin-react": "^7.25.1",
40 | "jest": "^27.1.0"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Instagram/Application/AuthenticationHandler.js:
--------------------------------------------------------------------------------
1 | const InstagramRepository = require('../Infrastructure/InstagramRepository');
2 |
3 | const CacheStorage = require('../../../app/services/CacheStorage');
4 |
5 | class AuthenticationHandler {
6 | /**
7 | * @param {AuthenticationQuery} authenticationQuery
8 | */
9 | static handle(authenticationQuery) {
10 | CacheStorage.get().set('username', authenticationQuery.getUsername());
11 | CacheStorage.get().set('password', authenticationQuery.getPassword());
12 | return InstagramRepository.authenticate();
13 | }
14 | }
15 |
16 | module.exports = AuthenticationHandler;
17 |
--------------------------------------------------------------------------------
/src/Instagram/Application/GetPostDataHandler.js:
--------------------------------------------------------------------------------
1 | const InstagramRepository = require('../Infrastructure/InstagramRepository');
2 |
3 | class GetPostDataHandler {
4 | /**
5 | * @param {GetPostDataQuery} getPostDataQuery
6 | * @returns Promise
7 | */
8 | static handle(getPostDataQuery) {
9 | return InstagramRepository.getPost(getPostDataQuery.getShortCode());
10 | }
11 | }
12 |
13 | module.exports = GetPostDataHandler;
14 |
--------------------------------------------------------------------------------
/src/Instagram/Application/GetUserDataHandler.js:
--------------------------------------------------------------------------------
1 | const InstagramRepository = require('../Infrastructure/InstagramRepository');
2 |
3 | class GetUserDataHandler {
4 | /**
5 | * @param {GetUserDataQuery} getUserDataQuery
6 | * @returns Promise
7 | */
8 | static handle(getUserDataQuery) {
9 | return InstagramRepository.getUser(getUserDataQuery.getUserName());
10 | }
11 | }
12 |
13 | module.exports = GetUserDataHandler;
14 |
--------------------------------------------------------------------------------
/src/Instagram/Application/Query/AuthenticationQuery.js:
--------------------------------------------------------------------------------
1 | class AuthenticationQuery {
2 | /** @type string */
3 | #username;
4 | /** @type string */
5 | #password;
6 |
7 | /**
8 | * @param {string} username
9 | * @param {string} password
10 | */
11 | constructor(username, password) {
12 | if (typeof username !== 'string') {
13 | throw new TypeError('The username has to be a string.');
14 | } else if (typeof password !== 'string') {
15 | throw new TypeError('The password has to be a string.');
16 | }
17 | this.#username = username;
18 | this.#password = password;
19 | }
20 |
21 | /** @returns string */
22 | getUsername = () => this.#username;
23 | /** @returns string */
24 | getPassword = () => this.#password;
25 | }
26 |
27 | module.exports = AuthenticationQuery;
28 |
--------------------------------------------------------------------------------
/src/Instagram/Application/Query/GetPostDataQuery.js:
--------------------------------------------------------------------------------
1 | class GetPostDataQuery {
2 | /** @type string */
3 | #shortCode;
4 |
5 | /**
6 | * @param {string} shortCode
7 | */
8 | constructor(shortCode) {
9 | if (typeof shortCode !== 'string') {
10 | throw new TypeError('The sortcode has to be a string.');
11 | }
12 | this.#shortCode = shortCode;
13 | }
14 |
15 | /** @returns string */
16 | getShortCode = () => this.#shortCode;
17 | }
18 |
19 | module.exports = GetPostDataQuery;
20 |
--------------------------------------------------------------------------------
/src/Instagram/Application/Query/GetUserDataQuery.js:
--------------------------------------------------------------------------------
1 | class GetUserDataQuery {
2 | /** @type string */
3 | #username;
4 |
5 | /**
6 | * @param {string} username
7 | */
8 | constructor(username) {
9 | if (typeof username !== 'string') {
10 | throw TypeError('The username has to be a string.');
11 | }
12 | this.#username = username;
13 | }
14 |
15 | /** @returns string */
16 | getUserName = () => this.#username;
17 | }
18 |
19 | module.exports = GetUserDataQuery;
20 |
--------------------------------------------------------------------------------
/src/Instagram/Domain/ChildMedia.js:
--------------------------------------------------------------------------------
1 | class ChildMedia {
2 | /** @type MediaType */
3 | #type;
4 | /** @type string */
5 | #id;
6 | /** @type string */
7 | #shortcode;
8 | /** @type Dimensions */
9 | #dimensions;
10 | /** @type string */
11 | #displayUrl;
12 | /** @type TaggedUser[] */
13 | #taggedUsers;
14 | /** @type string */
15 | #accessibilityCaption;
16 |
17 | constructor(type, id, shortcode, dimensions, displayUrl, taggedUsers, accessibilityCaption) {
18 | this.#type = type;
19 | this.#id = id;
20 | this.#shortcode = shortcode;
21 | this.#dimensions = dimensions;
22 | this.#displayUrl = displayUrl;
23 | this.#taggedUsers = taggedUsers;
24 | this.#accessibilityCaption = accessibilityCaption;
25 | }
26 |
27 | getType = () => this.#type.getValue();
28 | getId = () => this.#id;
29 | getShortcode = () => this.#shortcode;
30 | getDimensions = () => this.#dimensions;
31 | getDisplayUrl = () => this.#displayUrl;
32 | getTaggedUsers = () => this.#taggedUsers;
33 | getAccessibilityCaption = () => this.#accessibilityCaption;
34 | }
35 |
36 | module.exports = ChildMedia;
37 |
--------------------------------------------------------------------------------
/src/Instagram/Domain/Comment.js:
--------------------------------------------------------------------------------
1 | class Comment {
2 | /** @type string */
3 | #id;
4 | /** @type string */
5 | #text;
6 | /** @type Date */
7 | #date;
8 | /** @type CommentOwner */
9 | #owner;
10 | /** @type number */
11 | #likesCount;
12 | /** @type boolean */
13 | #hasResponses;
14 | /** @type Comment[] */
15 | #responses;
16 |
17 | constructor(id, text, date, owner, likesCount, hasResponses, responses) {
18 | this.#id = id;
19 | this.#text = text;
20 | this.#date = date;
21 | this.#owner = owner;
22 | this.#likesCount = likesCount;
23 | this.#hasResponses = hasResponses;
24 | this.#responses = responses;
25 | }
26 |
27 | getId = () => this.#id;
28 | getText = () => this.#text;
29 | getDate = () => this.#date;
30 | getOwner = () => this.#owner;
31 | getLikesCount = () => this.#likesCount;
32 | hasResponses = () => this.#hasResponses;
33 | getResponses = () => this.#responses;
34 | }
35 |
36 | module.exports = Comment;
37 |
--------------------------------------------------------------------------------
/src/Instagram/Domain/CommentOwner.js:
--------------------------------------------------------------------------------
1 | class CommentOwner {
2 | /** @type string */
3 | #id;
4 | /** @type boolean */
5 | #isVerified;
6 | /** @type string */
7 | #profilePicUrl;
8 | /** @type string */
9 | #username;
10 |
11 | constructor(id, isVerified, profilePicUrl, username) {
12 | this.#id = id;
13 | this.#isVerified = isVerified;
14 | this.#profilePicUrl = profilePicUrl;
15 | this.#username = username;
16 | }
17 |
18 | getId = () => this.#id;
19 | isVerified = () => this.#isVerified;
20 | getProfilePicUrl = () => this.#profilePicUrl;
21 | getUsername = () => this.#username;
22 | }
23 |
24 | module.exports = CommentOwner;
25 |
--------------------------------------------------------------------------------
/src/Instagram/Domain/Dimensions.js:
--------------------------------------------------------------------------------
1 | const AspectRatioCalculator = require('../../../app/services/AspectRatioCalculator');
2 |
3 | class Dimensions {
4 | /** @type number */
5 | #height;
6 | /** @type number */
7 | #width;
8 |
9 | constructor(height, width) {
10 | this.#height = height;
11 | this.#width = width;
12 | }
13 |
14 | getHeight = () => this.#height;
15 | getWidth = () => this.#width;
16 | /**
17 | * @param {string|null} separator
18 | * @returns {string}
19 | */
20 | getAspectRatio = (separator = ':') => AspectRatioCalculator.getAspectRatio(this.#height, this.#width, separator);
21 | }
22 |
23 | module.exports = Dimensions;
24 |
--------------------------------------------------------------------------------
/src/Instagram/Domain/Location.js:
--------------------------------------------------------------------------------
1 | class Location {
2 | /** @type string */
3 | #id;
4 | /** @type boolean */
5 | #hasPublicPage;
6 | /** @type string */
7 | #name;
8 | /** @type string */
9 | #slug;
10 |
11 | constructor(id, hasPublicPage, name, slug) {
12 | this.#id = id;
13 | this.#hasPublicPage = hasPublicPage;
14 | this.#name = name;
15 | this.#slug = slug;
16 | }
17 |
18 | getId = () => this.#id;
19 | hasPublicPage = () => this.#hasPublicPage;
20 | getName = () => this.#name;
21 | getSlug = () => this.#slug;
22 | }
23 |
24 | module.exports = Location;
25 |
--------------------------------------------------------------------------------
/src/Instagram/Domain/Media.js:
--------------------------------------------------------------------------------
1 | class Media {
2 | /** @type MediaType */
3 | #type;
4 | /** @type string */
5 | #id;
6 | /** @type string */
7 | #shortcode;
8 | /** @type string */
9 | #caption;
10 | /** @type Dimensions */
11 | #dimensions;
12 | /** @type string */
13 | #displayUrl;
14 | /** @type TaggedUser[] */
15 | #taggedUsers;
16 | /** @type boolean */
17 | #isVideo;
18 | /** @type string */
19 | #accessibilityCaption;
20 | /** @type boolean */
21 | #commentsDisabled;
22 | /** @type number */
23 | #commentsCount;
24 | /** @type number */
25 | #likesCount;
26 | /** @type number */
27 | #timestamp;
28 | /** @type Location */
29 | #location;
30 | /** @type ChildPicture[] */
31 | #children;
32 | /** @type boolean */
33 | #hasAudio;
34 | /** @type number|null */
35 | #viewsCount;
36 | /** @type string|null */
37 | #videoUrl;
38 |
39 | constructor(type, id, shortcode, caption, dimensions, displayUrl, taggedUsers, isVideo, accessibilityCaption, commentsDisabled, commentsCount, likesCount, timestamp, location, children, hasAudio = false, viewsCount = 0, videoUrl = null) {
40 | this.#type = type;
41 | this.#id = id;
42 | this.#shortcode = shortcode;
43 | this.#caption = caption;
44 | this.#dimensions = dimensions;
45 | this.#displayUrl = displayUrl;
46 | this.#taggedUsers = taggedUsers;
47 | this.#isVideo = isVideo;
48 | this.#accessibilityCaption = accessibilityCaption;
49 | this.#commentsDisabled = commentsDisabled;
50 | this.#commentsCount = commentsCount;
51 | this.#likesCount = likesCount;
52 | this.#timestamp = timestamp;
53 | this.#location = location;
54 | this.#children = children;
55 | this.#hasAudio = hasAudio;
56 | this.#viewsCount = viewsCount;
57 | this.#videoUrl = videoUrl;
58 | }
59 |
60 | getType = () => this.#type.getValue();
61 | getId = () => this.#id;
62 | getShortcode = () => this.#shortcode;
63 | getCaption = () => this.#caption;
64 | getDimensions = () => this.#dimensions;
65 | getDisplayUrl = () => this.#displayUrl;
66 | getTaggedUsers = () => this.#taggedUsers;
67 | isVideo = () => this.#isVideo;
68 | getAccessibilityCaption = () => this.#accessibilityCaption;
69 | areCommentsDisabled = () => this.#commentsDisabled;
70 | getCommentsCount = () => this.#commentsCount;
71 | getLikesCount = () => this.#likesCount;
72 | getTimestamp = () => this.#timestamp;
73 | getLocation = () => this.#location;
74 | getChildren = () => this.#children;
75 | hasAudio = () => this.#hasAudio;
76 | getViewsCount = () => this.#viewsCount;
77 | getVideoUrl = () => this.#videoUrl;
78 | }
79 |
80 | module.exports = Media;
81 |
--------------------------------------------------------------------------------
/src/Instagram/Domain/MediaType.js:
--------------------------------------------------------------------------------
1 | class MediaType {
2 | static TYPE_PICTURE = 'picture';
3 | static TYPE_VIDEO = 'video';
4 | static TYPE_MULTIPLE_PICTURE = 'multiple_picture';
5 |
6 | static INSTAGRAM_TYPE_PICTURE = 'GraphImage';
7 | static INSTAGRAM_TYPE_VIDEO = 'GraphVideo';
8 | static INASTAGRAM_TYPE_MULTIPLE_PICTURE = 'GraphSidecar';
9 |
10 | #value;
11 |
12 | constructor(type) {
13 | const matching = {};
14 | matching[MediaType.INSTAGRAM_TYPE_PICTURE] = MediaType.TYPE_PICTURE;
15 | matching[MediaType.INSTAGRAM_TYPE_VIDEO] = MediaType.TYPE_VIDEO;
16 | matching[MediaType.INASTAGRAM_TYPE_MULTIPLE_PICTURE] = MediaType.TYPE_MULTIPLE_PICTURE;
17 | if (!Object.keys(matching).includes(type)) {
18 | throw new Error(`${type} is not a valid Media type.`);
19 | }
20 | this.#value = matching[type];
21 | }
22 |
23 | getValue = () => this.#value;
24 | }
25 |
26 | module.exports = MediaType;
27 |
--------------------------------------------------------------------------------
/src/Instagram/Domain/MediaVariant.js:
--------------------------------------------------------------------------------
1 | class MediaVariant {
2 | /** @type string */
3 | #displayUrl;
4 | /** @type Dimensions */
5 | #dimensions;
6 |
7 | constructor(displayUrl, dimensions) {
8 | this.#displayUrl = displayUrl;
9 | this.#dimensions = dimensions;
10 | }
11 |
12 | getDisplayUrl = () => this.#displayUrl;
13 | getDimensions = () => this.#dimensions;
14 | }
15 |
16 | module.exports = MediaVariant;
17 |
--------------------------------------------------------------------------------
/src/Instagram/Domain/Owner.js:
--------------------------------------------------------------------------------
1 | class Owner {
2 | /** @type string */
3 | #id;
4 | /** @type boolean */
5 | #isVerified;
6 | /** @type string */
7 | #profilePicUrl;
8 | /** @type string */
9 | #username;
10 | /** @type string */
11 | #fullName;
12 | /** @type boolean */
13 | #isPrivate;
14 | /** @type number */
15 | #postsCount;
16 | /** @type number */
17 | #followersCount;
18 |
19 | constructor(
20 | id,
21 | isVerified,
22 | profilePicUrl,
23 | username,
24 | fullName,
25 | isPrivate,
26 | postsCount,
27 | followersCount
28 | ) {
29 | this.#id = id;
30 | this.#isVerified = isVerified;
31 | this.#profilePicUrl = profilePicUrl;
32 | this.#username = username;
33 | this.#fullName = fullName;
34 | this.#isPrivate = isPrivate;
35 | this.#postsCount = postsCount;
36 | this.#followersCount = followersCount;
37 | }
38 |
39 | getId = () => this.#id;
40 | isVerified = () => this.#isVerified;
41 | getProfilePictureUrl = () => this.#profilePicUrl;
42 | getUsername = () => this.#username;
43 | getFullName = () => this.#fullName;
44 | isPrivate = () => this.#isPrivate;
45 | getPostsCount = () => this.#postsCount;
46 | getFollowersCount = () => this.#followersCount;
47 | }
48 |
49 | module.exports = Owner;
50 |
--------------------------------------------------------------------------------
/src/Instagram/Domain/Post.js:
--------------------------------------------------------------------------------
1 | class Post {
2 | /** @type string */
3 | #id;
4 | /** @type MediaType */
5 | #type;
6 | /** @type string */
7 | #shortcode;
8 | /** @type Dimensions */
9 | #dimensions;
10 | /** @type string */
11 | #displayUrl;
12 | /** @type string[] */
13 | #variants;
14 | /** @type string */
15 | #accessibilityCaption;
16 | /** @type boolean */
17 | #isVideo;
18 | /** @type TaggedUser[] */
19 | #taggedUsers;
20 | /** @type string */
21 | #caption;
22 | /** @type boolean */
23 | #likeAndViewDisabled;
24 | /** @type number */
25 | #commentsCount;
26 | /** @type Comment[] */
27 | #comments;
28 | /** @type boolean */
29 | #commentsDisabled;
30 | /** @type Date */
31 | #takenAt;
32 | /** @type number */
33 | #likesCount;
34 | /** @type boolean */
35 | #isPaidPartnership;
36 | /** @type Location */
37 | #location;
38 | /** @type Owner */
39 | #owner;
40 | /** @type boolean */
41 | #isAd;
42 | /** @type ChildMedia[] */
43 | #children;
44 | /** @type boolean */
45 | #hasAudio;
46 | /** @type number */
47 | #videoViewsCount;
48 | /** @type number */
49 | #videoPlaysCount;
50 |
51 | constructor(
52 | id,
53 | type,
54 | shortcode,
55 | dimensions,
56 | displayUrl,
57 | variants,
58 | accessibilityCaption,
59 | isVideo,
60 | taggedUsers,
61 | caption,
62 | likeAndViewDisabled,
63 | commentsCount,
64 | comments,
65 | commentsDisabled,
66 | takenAt,
67 | likesCount,
68 | isPaidPartnership,
69 | location,
70 | owner,
71 | isAd,
72 | children,
73 | hasAudio,
74 | videoViewsCount,
75 | videoPlaysCount
76 | ) {
77 | this.#id = id;
78 | this.#type = type;
79 | this.#shortcode = shortcode;
80 | this.#dimensions = dimensions;
81 | this.#displayUrl = displayUrl;
82 | this.#variants = variants;
83 | this.#accessibilityCaption = accessibilityCaption;
84 | this.#isVideo = isVideo;
85 | this.#taggedUsers = taggedUsers;
86 | this.#caption = caption;
87 | this.#likeAndViewDisabled = likeAndViewDisabled;
88 | this.#commentsCount = commentsCount;
89 | this.#comments = comments;
90 | this.#commentsDisabled = commentsDisabled;
91 | this.#takenAt = takenAt;
92 | this.#likesCount = likesCount;
93 | this.#isPaidPartnership = isPaidPartnership;
94 | this.#location = location;
95 | this.#owner = owner;
96 | this.#isAd = isAd;
97 | this.#children = children;
98 | this.#hasAudio = hasAudio;
99 | this.#videoViewsCount = videoViewsCount;
100 | this.#videoPlaysCount = videoPlaysCount;
101 | }
102 |
103 | getId = () => this.#id;
104 | getType = () => this.#type;
105 | getShortcode = () => this.#shortcode;
106 | getDimensions = () => this.#dimensions;
107 | getDisplayUrl = () => this.#displayUrl;
108 | getVariants = () => this.#variants;
109 | getAccessibilityCaption = () => this.#accessibilityCaption;
110 | isVideo = () => this.#isVideo;
111 | getTaggedUsers = () => this.#taggedUsers;
112 | getCaption = () => this.#caption;
113 | areLikesAndViewsCountDisabled = () => this.#likeAndViewDisabled;
114 | getCommentsCount = () => this.#commentsCount;
115 | getComments = () => this.#comments;
116 | areCommentsDisabled = () => this.#commentsDisabled;
117 | getDate = () => this.#takenAt;
118 | getLikesCount = () => this.#likesCount;
119 | isPaidPartnership = () => this.#isPaidPartnership;
120 | getLocation = () => this.#location;
121 | getOwner = () => this.#owner;
122 | isAd = () => this.#isAd;
123 | getChildren = () => this.#children;
124 | hasAudio = () => this.#hasAudio;
125 | getVideoViewsCount = () => this.#videoViewsCount;
126 | getVideoPlaysCount = () => this.#videoPlaysCount;
127 | }
128 |
129 | module.exports = Post;
130 |
--------------------------------------------------------------------------------
/src/Instagram/Domain/Session.js:
--------------------------------------------------------------------------------
1 | class Session {
2 | #id;
3 | #expiryDate;
4 |
5 | /**
6 | * @param {string} id
7 | * @param {Date} expiryDate
8 | */
9 | constructor(id, expiryDate) {
10 | this.#id = id;
11 | this.#expiryDate = expiryDate;
12 | }
13 |
14 | /** @return string */
15 | getId = () => this.#id;
16 | /** @returns Date */
17 | getExpiryDate = () => this.#expiryDate;
18 | }
19 |
20 | module.exports = Session;
21 |
--------------------------------------------------------------------------------
/src/Instagram/Domain/TaggedUser.js:
--------------------------------------------------------------------------------
1 | class TaggedUser {
2 | /** @type number */
3 | #tagXPosition;
4 | /** @type number */
5 | #tagYPosition;
6 | /** @type string */
7 | #fullName;
8 | /** @type string */
9 | #id;
10 | /** @type boolean */
11 | #isVerified;
12 | /** @type string */
13 | #profilePictureUrl;
14 | /** @type string */
15 | #username;
16 |
17 | constructor(tagXPosition, taxYPosition, fullName, id, isVerified, profilePictureUrl, username) {
18 | this.#tagXPosition = tagXPosition;
19 | this.#tagYPosition = taxYPosition;
20 | this.#fullName = fullName;
21 | this.#id = id;
22 | this.#isVerified = isVerified;
23 | this.#profilePictureUrl = profilePictureUrl;
24 | this.#username = username;
25 | }
26 |
27 | getTagXPosition = () => this.#tagXPosition;
28 | getTagYPosition = () => this.#tagYPosition;
29 | getFullName = () => this.#fullName;
30 | getId = () => this.#id;
31 | isVerified = () => this.#isVerified;
32 | getProfilePictureUrl = () => this.#profilePictureUrl;
33 | getUsername = () => this.#username;
34 | }
35 |
36 | module.exports = TaggedUser;
37 |
--------------------------------------------------------------------------------
/src/Instagram/Domain/User.js:
--------------------------------------------------------------------------------
1 | class User {
2 | /** @type string */
3 | #username;
4 | /** @type string */
5 | #biography;
6 | /** @type number */
7 | #publicationsCount;
8 | /** @type number */
9 | #followersCount;
10 | /** @type string */
11 | #externalUrl;
12 | /** @type number */
13 | #followingCount;
14 | /** @type string */
15 | #fullName;
16 | /** @type boolean */
17 | #hasArEffects;
18 | /** @type boolean */
19 | #hasClips;
20 | /** @type boolean */
21 | #hasGuides;
22 | /** @type boolean */
23 | #hasChannel;
24 | /** @type number */
25 | #highlightReelCount;
26 | /** @type number */
27 | #isHidingLikesAndViewsCount;
28 | /** @type string */
29 | #id;
30 | /** @type boolean */
31 | #isBusinessAccount;
32 | /** @type boolean */
33 | #isProfessionalAccount;
34 | /** @type boolean */
35 | #hasJoinedRecently;
36 | /** @type string */
37 | #businessAddressJson;
38 | /** @type string */
39 | #businessContactMethod;
40 | /** @type string */
41 | #businessEmail;
42 | /** @type string */
43 | #businessPhoneNumber;
44 | /** @type string */
45 | #businessCategoryName;
46 | /** @type string */
47 | #overallCategoryName;
48 | /** @type string */
49 | #categoryEnum;
50 | /** @type string */
51 | #categoryName;
52 | /** @type boolean */
53 | #isPrivate;
54 | /** @type boolean */
55 | #isVerified;
56 | /** @type string */
57 | #profilePictureUrl;
58 | /** @type string */
59 | #profilePictureUrlHd;
60 | /** @type string[] */
61 | #pronouns;
62 | /** @type Media[] */
63 | #medias;
64 |
65 | constructor(
66 | username,
67 | biography,
68 | publicationsCount,
69 | followersCount,
70 | followingCount,
71 | externalUrl,
72 | fullName,
73 | hasArEffects,
74 | hasClips,
75 | hasGuides,
76 | hasChannel,
77 | highlightReelsCount,
78 | isHidingLikesAndViewsCount,
79 | id,
80 | isBusinessAccount,
81 | isProfessionalAccount,
82 | hasJoinedRecently,
83 | businessAddressJson,
84 | businessContactMethod,
85 | businessEmail,
86 | businessPhoneNumber,
87 | businessCategoryName,
88 | overallCetagoryName,
89 | categoryEnum,
90 | categoryName,
91 | isPrivate,
92 | isVerified,
93 | profilePictureUrl,
94 | profilePictureUrlHd,
95 | pronouns,
96 | medias,
97 | ) {
98 | this.#username = username;
99 | this.#biography = biography;
100 | this.#publicationsCount = publicationsCount;
101 | this.#followersCount = followersCount;
102 | this.#followingCount = followingCount;
103 | this.#externalUrl = externalUrl;
104 | this.#fullName = fullName;
105 | this.#hasArEffects = hasArEffects;
106 | this.#hasClips = hasClips;
107 | this.#hasGuides = hasGuides;
108 | this.#hasChannel = hasChannel;
109 | this.#highlightReelCount = highlightReelsCount;
110 | this.#isHidingLikesAndViewsCount = isHidingLikesAndViewsCount;
111 | this.#id = id;
112 | this.#isBusinessAccount = isBusinessAccount;
113 | this.#isProfessionalAccount = isProfessionalAccount;
114 | this.#hasJoinedRecently = hasJoinedRecently;
115 | this.#businessAddressJson = businessAddressJson;
116 | this.#businessContactMethod = businessContactMethod;
117 | this.#businessEmail = businessEmail;
118 | this.#businessPhoneNumber = businessPhoneNumber;
119 | this.#businessCategoryName = businessCategoryName;
120 | this.#overallCategoryName = overallCetagoryName;
121 | this.#categoryEnum = categoryEnum;
122 | this.#categoryName = categoryName;
123 | this.#isPrivate = isPrivate;
124 | this.#isVerified = isVerified;
125 | this.#profilePictureUrl = profilePictureUrl;
126 | this.#profilePictureUrlHd = profilePictureUrlHd;
127 | this.#pronouns = pronouns;
128 | this.#medias = medias;
129 | }
130 |
131 | getUsername = () => this.#username;
132 | getBiography = () => this.#biography;
133 | getPublicationsCount = () => this.#publicationsCount;
134 | getFollowersCount = () => this.#followersCount;
135 | getExternalUrl = () => this.#externalUrl;
136 | getFollowingCount = () => this.#followingCount;
137 | getFullName = () => this.#fullName;
138 | hasArEffect = () => this.#hasArEffects;
139 | hasClips = () => this.#hasClips;
140 | hasGuides = () => this.#hasGuides;
141 | hasChannel = () => this.#hasChannel;
142 | getHighlightsReelsCount = () => this.#highlightReelCount;
143 | isHidingLikesAndViewsCount = () => this.#isHidingLikesAndViewsCount;
144 | getId = () => this.#id;
145 | isBusinessAccount = () => this.#isBusinessAccount;
146 | isProfessionalAccount = () => this.#isProfessionalAccount;
147 | hasJoinedRecently = () => this.#hasJoinedRecently;
148 | getBusinessAddressJson = () => this.#businessAddressJson;
149 | getBusinessContactMethod = () => this.#businessContactMethod;
150 | getBusinessEmail = () => this.#businessEmail;
151 | getBusinessPhoneNumber = () => this.#businessPhoneNumber;
152 | getBusinessCategoryName = () => this.#businessCategoryName;
153 | getOverallCategoryName = () => this.#overallCategoryName;
154 | getCategoryEnum = () => this.#categoryEnum;
155 | getCategoryName = () => this.#categoryName;
156 | isPrivate = () => this.#isPrivate;
157 | isVerified = () => this.#isVerified;
158 | getProfilePicture = () => this.#profilePictureUrl;
159 | getHdProfilePicture = () => this.#profilePictureUrlHd;
160 | getPronouns = () => this.#pronouns;
161 | getMedias = () => this.#medias;
162 | }
163 |
164 | module.exports = User;
165 |
--------------------------------------------------------------------------------
/src/Instagram/Domain/errors/InstagramAuthenticationError.js:
--------------------------------------------------------------------------------
1 | class InstagramAuthenticationError extends Error {
2 | }
3 |
4 | module.exports = InstagramAuthenticationError;
5 |
--------------------------------------------------------------------------------
/src/Instagram/Domain/errors/InstagramGenericError.js:
--------------------------------------------------------------------------------
1 | class InstagramGenericError extends Error {
2 | }
3 |
4 | module.exports = InstagramGenericError;
5 |
--------------------------------------------------------------------------------
/src/Instagram/Domain/errors/NoSessionError.js:
--------------------------------------------------------------------------------
1 | class NoSessionError extends Error {
2 | message = 'No session found. Did you authenticate ?';
3 | }
4 |
5 | module.exports = NoSessionError;
6 |
--------------------------------------------------------------------------------
/src/Instagram/Domain/errors/PostNotFoundError.js:
--------------------------------------------------------------------------------
1 | class PostNotFoundError extends Error {
2 | /** @param {string} shortcode */
3 | static fromShortcode = (shortcode) => new this(`No post with ${shortcode} as shortcode could be found.`);
4 | }
5 |
6 | module.exports = PostNotFoundError;
7 |
--------------------------------------------------------------------------------
/src/Instagram/Domain/errors/RateLimitError.js:
--------------------------------------------------------------------------------
1 | class RateLimitError extends Error {
2 | constructor() {
3 | super('The server has sent an unsuccessful response. The rate limit has been hit, retry later.');
4 | }
5 | }
6 |
7 | module.exports = RateLimitError;
8 |
--------------------------------------------------------------------------------
/src/Instagram/Domain/errors/TooManyLoginsError.js:
--------------------------------------------------------------------------------
1 | class TooManyLoginsError extends Error {
2 | }
3 |
4 | module.exports = TooManyLoginsError;
5 |
--------------------------------------------------------------------------------
/src/Instagram/Domain/errors/UserNotFoundError.js:
--------------------------------------------------------------------------------
1 | class UserNotFoundError extends Error {
2 | /** @param {string} username */
3 | static fromUsername = (username) => new this(`No profile with ${username} as username could be found.`);
4 | }
5 |
6 | module.exports = UserNotFoundError;
7 |
--------------------------------------------------------------------------------
/src/Instagram/Infrastructure/InstagramRepository.js:
--------------------------------------------------------------------------------
1 | const url = require('url');
2 | const axios = require('axios');
3 | const applicationData = require('../../../app/data.json');
4 |
5 | const CacheStorage = require('../../../app/services/CacheStorage');
6 | const CookieReader = require('../../../app/services/CookieReader');
7 | const InstagramHelper = require('../../../app/services/InstagramHelper');
8 | const BasicRequestHandler = require('../../../app/services/BasicRequestHandler');
9 |
10 | const Post = require('../Domain/Post');
11 | const User = require('../Domain/User');
12 |
13 | const Media = require('../Domain/Media');
14 | const Session = require('../Domain/Session');
15 | const Location = require('../Domain/Location');
16 | const MediaType = require('../Domain/MediaType');
17 | const Dimensions = require('../Domain/Dimensions');
18 | const TaggedUser = require('../Domain/TaggedUser');
19 | const ChildMedia = require('../Domain/ChildMedia');
20 | const Comment = require('../Domain/Comment');
21 |
22 | const RateLimitError = require('../Domain/errors/RateLimitError');
23 | const NoSessionError = require('../Domain/errors/NoSessionError');
24 | const UserNotFoundError = require('../Domain/errors/UserNotFoundError');
25 | const TooManyLoginsError = require('../Domain/errors/TooManyLoginsError');
26 | const InstagramGenericError = require('../Domain/errors/InstagramGenericError');
27 | const InstagramAuthenticationError = require('../Domain/errors/InstagramAuthenticationError');
28 | const PostNotFoundError = require('../Domain/errors/PostNotFoundError');
29 | const MediaVariant = require('../Domain/MediaVariant');
30 | const CommentOwner = require('../Domain/CommentOwner');
31 | const Owner = require('../Domain/Owner');
32 |
33 | class InstagramRepository {
34 | /**
35 | * @param {string} shortcode
36 | */
37 | static async getPost(shortcode) {
38 | const queryUrl = applicationData.postUrlTemplate.replace('%SHORTCODE%', shortcode);
39 |
40 | return new Promise((resolve, reject) => {
41 | BasicRequestHandler.handle(queryUrl).then((request) => {
42 | if (typeof request.data === 'string' && request.data.includes('')) {
43 | throw new RateLimitError();
44 | }
45 |
46 | if (!request.data.hasOwnProperty('graphql')) {
47 | throw PostNotFoundError.fromShortcode(shortcode);
48 | }
49 |
50 | const rawPostData = request.data['graphql']['shortcode_media'];
51 |
52 | const hasComments = rawPostData['edge_media_to_parent_comment']['count'] !== 0;
53 | const hasCaption = rawPostData['edge_media_to_caption']['edges'].length > 0;
54 | const hasTaggedUsers = rawPostData['edge_media_to_tagged_user']['edges'].length > 0;
55 | const hasChildren = rawPostData.hasOwnProperty('edge_sidecar_to_children');
56 | const isVideo = rawPostData['is_video'];
57 |
58 | resolve(new Post(
59 | rawPostData['id'],
60 | new MediaType(rawPostData['__typename']),
61 | rawPostData['shortcode'],
62 | new Dimensions(rawPostData['height'], rawPostData['width']),
63 | rawPostData['display_url'],
64 | rawPostData['display_resources'].map((resource) => new MediaVariant(
65 | resource['src'],
66 | new Dimensions(resource['config_height'], resource['config_width'])
67 | )),
68 | rawPostData['accessibility_caption'],
69 | isVideo,
70 | hasTaggedUsers
71 | ? rawPostData['edge_media_to_tagged_user']['edges'].map((edge) => {
72 | const node = edge.node;
73 | return new TaggedUser(
74 | node['x'],
75 | node['y'],
76 | node['user']['full_name'],
77 | node['user']['id'],
78 | node['user']['is_verified'],
79 | node['user']['profile_pic_url'],
80 | node['user']['username']
81 | );
82 | }) : [],
83 | hasCaption ? rawPostData['edge_media_to_caption']['edges'][0]['node']['text'] : null,
84 | rawPostData['like_and_view_counts_disabled'],
85 | rawPostData['edge_media_to_parent_comment']['count'],
86 | hasComments ? rawPostData['edge_media_to_parent_comment']['edges'].map((objectEdge) => {
87 | const edge = objectEdge.node;
88 | const ownerData = edge['owner'];
89 | return new Comment(
90 | edge['id'],
91 | edge['text'],
92 | new Date(edge['created_at'] * 1000),
93 | new CommentOwner(
94 | ownerData['id'],
95 | ownerData['is_verified'],
96 | ownerData['profile_pic_url'],
97 | ownerData['username']
98 | ),
99 | edge['edge_liked_by']['count'],
100 | edge['edge_threaded_comments']['count'] !== 0,
101 | edge['edge_threaded_comments']['edges'].map((objectEdge) => {
102 | const edge = objectEdge.node;
103 | return new Comment(
104 | edge['id'],
105 | edge['text'],
106 | new Date(edge['created_at'] * 1000),
107 | new CommentOwner(
108 | ownerData['id'],
109 | ownerData['is_verified'],
110 | ownerData['profile_pic_url'],
111 | ownerData['username']
112 | ),
113 | edge['edge_liked_by']['count'],
114 | false,
115 | []
116 | );
117 | })
118 | );
119 | }) : [],
120 | rawPostData['comments_disabled'],
121 | new Date(rawPostData['taken_at_timestamp'] * 1000),
122 | rawPostData['edge_media_preview_like']['count'],
123 | rawPostData['is_paid_partnership'],
124 | rawPostData['location']
125 | ? new Location(
126 | rawPostData['location']['id'],
127 | rawPostData['location']['has_public_page'],
128 | rawPostData['location']['name'],
129 | rawPostData['location']['slug']
130 | ) : null,
131 | new Owner(
132 | rawPostData['owner']['id'],
133 | rawPostData['owner']['is_verified'],
134 | rawPostData['owner']['profile_pic_url'],
135 | rawPostData['owner']['username'],
136 | rawPostData['owner']['full_name'],
137 | rawPostData['owner']['is_private'],
138 | rawPostData['owner']['edge_owner_to_timeline_media']['count'],
139 | rawPostData['owner']['edge_followed_by']['count'],
140 | ),
141 | rawPostData['is_ad'],
142 | hasChildren ? rawPostData['edge_sidecar_to_children']['edges'].map((edge) => {
143 | const node = edge.node;
144 | const hasTaggedUsers = node['edge_media_to_tagged_user']['edges'].length > 0;
145 | return new ChildMedia(
146 | new MediaType(node['__typename']),
147 | node['id'],
148 | node['shortcode'],
149 | new Dimensions(node['dimensions']['height'], node['dimensions']['width']),
150 | node['display_url'],
151 | hasTaggedUsers ? node['edge_media_to_tagged_user']['edges'].map((edge) => {
152 | const node = edge.node;
153 | return new TaggedUser(
154 | node['x'],
155 | node['y'],
156 | node['user']['full_name'],
157 | node['user']['id'],
158 | node['user']['is_verified'],
159 | node['user']['profile_pic_url'],
160 | node['user']['username']
161 | );
162 | }) : [],
163 | node['accessibility_caption']
164 | );
165 | }) : [],
166 | rawPostData['is_video'] ? rawPostData['has_audio'] : false,
167 | rawPostData['is_video'] ? rawPostData['video_view_count'] : null,
168 | rawPostData['is_video'] ? rawPostData['video_play_count'] : null
169 | ));
170 | }).catch((error) => {
171 | if (error instanceof NoSessionError) {
172 | return reject(error);
173 | }
174 | if (error.hasOwnProperty('response') && error.response.status === 404) {
175 | return reject(PostNotFoundError.fromShortcode(shortcode));
176 | }
177 | return reject(new InstagramGenericError(error.message));
178 | });
179 | });
180 | }
181 |
182 | /**
183 | * @param {string} username
184 | * @returns Promise
185 | */
186 | static async getUser(username) {
187 | const queryUrl = applicationData.usernameUrlTemplate.replace('%USERNAME%', username);
188 |
189 | return new Promise((resolve, reject) => {
190 | BasicRequestHandler.handle(queryUrl).then((request) => {
191 | if (typeof request.data === 'string' && request.data.includes('')) {
192 | throw new RateLimitError();
193 | }
194 |
195 | if (!request.data.hasOwnProperty('graphql')) {
196 | throw UserNotFoundError.fromUsername(username);
197 | }
198 |
199 | const rawUserData = request.data['graphql']['user'];
200 |
201 | const hasPosts = rawUserData['edge_owner_to_timeline_media']['edges'].length > 0;
202 |
203 | resolve(new User(
204 | rawUserData.username,
205 | rawUserData['biography'],
206 | rawUserData['edge_owner_to_timeline_media']['count'],
207 | rawUserData['edge_followed_by']['count'],
208 | rawUserData['edge_follow']['count'],
209 | rawUserData['external_url'],
210 | rawUserData['full_name'],
211 | rawUserData['has_ar_effects'],
212 | rawUserData['has_clips'],
213 | rawUserData['has_guides'],
214 | rawUserData['has_channel'],
215 | rawUserData['highlight_reel_count'],
216 | rawUserData['hide_like_and_view_counts'],
217 | rawUserData['id'],
218 | rawUserData['is_business_account'],
219 | rawUserData['is_professional_account'],
220 | rawUserData['is_joined_recently'],
221 | rawUserData['business_address_json'],
222 | rawUserData['business_contact_method'],
223 | rawUserData['business_email'],
224 | rawUserData['business_phone_number'],
225 | rawUserData['business_category_name'],
226 | rawUserData['overall_category_name'],
227 | rawUserData['category_enum'],
228 | rawUserData['category_name'],
229 | rawUserData['is_private'],
230 | rawUserData['is_verified'],
231 | rawUserData['profile_pic_url'],
232 | rawUserData['profile_pic_url_hd'],
233 | rawUserData['pronouns'],
234 | hasPosts ? rawUserData['edge_owner_to_timeline_media']['edges'].map((edge) => {
235 | const node = edge.node;
236 | const hasChildren = node.hasOwnProperty('edge_sidecar_to_children');
237 | const hasTaggedUsers = node['edge_media_to_tagged_user']['edges'].length > 0;
238 | const hasCaption = node['edge_media_to_caption']['edges'].length > 0;
239 | return new Media(
240 | new MediaType(node['__typename']),
241 | node['id'],
242 | node['shortcode'],
243 | hasCaption ? node['edge_media_to_caption']['edges'][0]['node']['text'] : null,
244 | new Dimensions(node['dimensions']['height'], node['dimensions']['width']),
245 | node['display_url'],
246 | hasTaggedUsers
247 | ? node['edge_media_to_tagged_user']['edges'].map((edge) => {
248 | const node = edge.node;
249 | return new TaggedUser(
250 | node['x'],
251 | node['y'],
252 | node['user']['full_name'],
253 | node['user']['id'],
254 | node['user']['is_verified'],
255 | node['user']['profile_pic_url'],
256 | node['user']['username']
257 | );
258 | }) : [],
259 | node['is_video'],
260 | node['accessibility_caption'],
261 | node['comments_disabled'],
262 | node['edge_media_to_comment']['count'],
263 | node['edge_liked_by']['count'],
264 | node['taken_at_timestamp'],
265 | node['location'] ? new Location(node['location']['id'], node['location']['has_public_page'], node['location']['name'], node['location']['slug']) : null,
266 | hasChildren
267 | ? node['edge_sidecar_to_children']['edges'].map((edge) => {
268 | const node = edge.node;
269 | const hasTaggedUsers = node['edge_media_to_tagged_user']['edges'].length > 0;
270 | return new ChildMedia(
271 | new MediaType(node['__typename']),
272 | node['id'],
273 | node['shortcode'],
274 | new Dimensions(node['dimensions']['height'], node['dimensions']['width']),
275 | node['display_url'],
276 | hasTaggedUsers
277 | ? node['edge_media_to_tagged_user']['edges'].map((edge) => {
278 | const node = edge.node;
279 | return new TaggedUser(
280 | node['x'],
281 | node['y'],
282 | node['user']['full_name'],
283 | node['user']['id'],
284 | node['user']['is_verified'],
285 | node['user']['profile_pic_url'],
286 | node['user']['username']
287 | );
288 | }) : [],
289 | node['accessibility_caption']
290 | );
291 | }) : [],
292 | node['has_audio'],
293 | node['video_view_count'],
294 | node['video_url']
295 | );
296 | }) : []
297 | ));
298 | }).catch((error) => {
299 | if (error instanceof NoSessionError) {
300 | return reject(error);
301 | }
302 | if (error.hasOwnProperty('response') && error.response.status === 404) {
303 | return reject(UserNotFoundError.fromUsername(username));
304 | }
305 | return reject(new InstagramGenericError(error.message));
306 | });
307 | });
308 | }
309 |
310 | static async authenticate() {
311 | const request = await axios.get(InstagramHelper.BASE_URL, {
312 | headers: {
313 | 'user-agent': InstagramHelper.DEFAULT_USER_AGENT
314 | }
315 | });
316 |
317 | const matches = request.data.match(/