├── cookies └── .gitkeep ├── downloads └── .gitkeep ├── src ├── scripts │ ├── index.ts │ ├── InstagramVerifier.ts │ └── RedditInstagramPoster.ts ├── models │ ├── instagram-upload-type.ts │ ├── downloaded-image.ts │ ├── instagram-upload.ts │ ├── index.ts │ └── reddit-post-response.ts ├── env.config.ts ├── util │ ├── config.ts │ ├── reddit.ts │ ├── downloader.ts │ ├── instagram.ts │ └── data.ts └── app.ts ├── cron.sh ├── .env.example ├── tslint.json ├── tsconfig.json ├── package.json ├── .github └── workflows │ └── node.js.yml ├── README.md └── .gitignore /cookies/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /downloads/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/scripts/index.ts: -------------------------------------------------------------------------------- 1 | export * from './RedditInstagramPoster'; 2 | -------------------------------------------------------------------------------- /cron.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd /home/ubuntu/projects/auto-poster 3 | npm run-script start -------------------------------------------------------------------------------- /src/models/instagram-upload-type.ts: -------------------------------------------------------------------------------- 1 | export enum InstagramUploadType { 2 | Feed = 'feed', 3 | Story = 'story' 4 | } 5 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | DATABASE_NAME=database.db 2 | UPLOADS_PER_SCRIPT_RUN=1 3 | TIMOUT_IN_BETWEEN_UPLOADS_IN_SECONDS=30 4 | USE_COOKIE_STORE=true -------------------------------------------------------------------------------- /src/models/downloaded-image.ts: -------------------------------------------------------------------------------- 1 | export interface DownloadedImage { 2 | name: string; 3 | fileName: string; 4 | filePath: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/models/instagram-upload.ts: -------------------------------------------------------------------------------- 1 | export interface InstagramUpload { 2 | id: number; 3 | unique_id: string; 4 | title: string; 5 | file_path: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/models/index.ts: -------------------------------------------------------------------------------- 1 | export * from './instagram-upload'; 2 | export * from './downloaded-image'; 3 | export * from './reddit-post-response'; 4 | export * from './instagram-upload-type'; 5 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "jsRules": {}, 7 | "rules": { 8 | "no-console": false 9 | }, 10 | "rulesDirectory": [] 11 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "esModuleInterop": true, 5 | "target": "es2017", 6 | "moduleResolution": "node", 7 | "sourceMap": true, 8 | "outDir": "dist", 9 | }, 10 | "lib": [ 11 | "es2015" 12 | ], 13 | "include": [ 14 | "src/**/*" 15 | ] 16 | } -------------------------------------------------------------------------------- /src/env.config.ts: -------------------------------------------------------------------------------- 1 | import * as dotenv from 'dotenv'; 2 | dotenv.config(); 3 | 4 | export default { 5 | database: process.env.DATABASE_NAME, 6 | uploadsPerScriptRun: Number(process.env.UPLOADS_PER_SCRIPT_RUN), 7 | timeoutInBetweenUploadsInSeconds: Number(process.env.TIMOUT_IN_BETWEEN_UPLOADS_IN_SECONDS), 8 | useCookieStore: Boolean(JSON.parse(process.env.USE_COOKIE_STORE)) 9 | }; 10 | -------------------------------------------------------------------------------- /src/util/config.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | 3 | 4 | export interface Settings { 5 | accounts: Account[] 6 | } 7 | 8 | export interface Account { 9 | instagramUsername: string; 10 | instagramPassword: string; 11 | subreddit: string; 12 | tags: string; 13 | postOnStory: boolean; 14 | credits: boolean; 15 | } 16 | 17 | export class Config { 18 | settings: Settings; 19 | 20 | constructor() { 21 | this.settings = JSON.parse(fs.readFileSync('settings.json', 'utf8')); 22 | } 23 | } -------------------------------------------------------------------------------- /src/models/reddit-post-response.ts: -------------------------------------------------------------------------------- 1 | export interface RedditPostResponse { 2 | kind: string; 3 | data: RedditPost 4 | } 5 | 6 | interface RedditPost { 7 | title: string; 8 | name: string; 9 | permalink: string; 10 | author: string; 11 | is_self: boolean; 12 | is_video: boolean; 13 | url: string; 14 | score: number; 15 | gallery_data: RedditGalleryData; 16 | } 17 | 18 | interface RedditGalleryData { 19 | items: RedditGalleryDataItem[]; 20 | } 21 | 22 | interface RedditGalleryDataItem { 23 | media_id: string; 24 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "instagram-bot", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "tsc && node dist/app.js", 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "build": "tsc", 10 | "verifier": "tsc && node dist/scripts/InstagramVerifier.js" 11 | }, 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "dotenv": "^8.2.0", 16 | "instagram-web-api": "^2.2.2", 17 | "node-fetch": "^2.6.1", 18 | "sharp": "^0.26.3", 19 | "sqlite3": "^5.0.2", 20 | "tough-cookie-filestore2": "^1.0.0" 21 | }, 22 | "devDependencies": { 23 | "@types/sharp": "^0.26.1", 24 | "@types/sqlite3": "^3.1.7", 25 | "@types/node-fetch": "^2.5.8", 26 | "tslint": "^6.1.3", 27 | "typescript": "^4.1.5" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.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: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [10.x, 12.x, 14.x] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v1 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | - run: npm ci 28 | - run: npm run build --if-present 29 | # - run: npm test 30 | -------------------------------------------------------------------------------- /src/scripts/InstagramVerifier.ts: -------------------------------------------------------------------------------- 1 | import { Config } from "../util/config"; 2 | import { InstagramWrapper } from "../util/instagram"; 3 | 4 | 5 | class InstagramVerifier { 6 | private config: Config; 7 | private instagram: InstagramWrapper; 8 | 9 | constructor() { 10 | this.config = new Config(); 11 | 12 | const username = process.argv[2]; 13 | const account = this.config.settings.accounts.find(x => x.instagramUsername === username); 14 | 15 | if (!account) { 16 | console.log(`Account '${username}' not found in settings.json.`) 17 | process.exit(); 18 | } 19 | 20 | this.instagram = new InstagramWrapper(account.instagramUsername, account.instagramPassword); 21 | } 22 | 23 | start() { 24 | this.instagram.client.login() 25 | .then(() => console.log('no verification needed.')) 26 | .catch((response: any) => this.instagram.onInstagramLoginError(response.error)); 27 | } 28 | } 29 | 30 | const verifier = new InstagramVerifier(); 31 | verifier.start(); -------------------------------------------------------------------------------- /src/app.ts: -------------------------------------------------------------------------------- 1 | import env from './env.config'; 2 | import { RedditInstagramPoster } from './scripts'; 3 | import { Config } from './util/config'; 4 | import { Data } from './util/data'; 5 | 6 | 7 | class AutoPoster { 8 | private config: Config; 9 | private database: Data; 10 | 11 | constructor() { 12 | this.config = new Config(); 13 | this.database = new Data(); 14 | } 15 | 16 | public run() { 17 | const limit = (12 * 7) * this.config.settings.accounts.length; 18 | this.database.all(limit).then(posts => { 19 | this.config.settings.accounts.forEach((account, index) => { 20 | setTimeout(async () => { 21 | const redditInstagramPoster = new RedditInstagramPoster(this.database, account); 22 | await redditInstagramPoster.run(posts); 23 | }, ((env.timeoutInBetweenUploadsInSeconds * env.uploadsPerScriptRun) * 1000) * ((index - 1) + 1)) 24 | }); 25 | }).catch(err => console.error(err)); 26 | } 27 | } 28 | 29 | const bot = new AutoPoster(); 30 | bot.run(); 31 | -------------------------------------------------------------------------------- /src/util/reddit.ts: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | import { RedditPostResponse } from '../models'; 3 | 4 | 5 | export class Reddit { 6 | public REDDIT_URL = 'https://www.reddit.com'; 7 | private subreddit; 8 | 9 | constructor(subreddit: string) { 10 | this.subreddit = subreddit; 11 | } 12 | 13 | getPosts(limit = 100): Promise { 14 | return new Promise(async (resolve, reject) => { 15 | fetch(`${this.REDDIT_URL}/r/${this.subreddit}/top/.json?t=week&limit=${limit}`, { method: 'GET' }) 16 | .then(res => res.json()) 17 | .then(json => resolve(json.data.children.filter( 18 | (post: RedditPostResponse) => !post.data.is_self 19 | && !post.data.is_video 20 | && this.hasValidUrl(post.data.url) 21 | ))) 22 | .catch(reject); 23 | }) 24 | } 25 | 26 | private hasValidUrl(url: string): boolean { 27 | if (/redgifs|gfycat|gif/.test(url)) { 28 | return false; 29 | } 30 | 31 | return true; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # auto-poster 2 | 3 | Automatically post images from a subreddit to an instagram account. 4 | 5 | ### Setup 6 | 7 | 1. Create a copy of the `.env.example` file to `.env` and enter your environment variables. 8 | 2. Create a `settings.json` file in the root with your account credentials. Example: 9 | 10 | ``` 11 | { 12 | "accounts": [ 13 | { 14 | "instagramUsername": "", 15 | "instagramPassword": "", 16 | "subreddit": "", 17 | "tags": "", 18 | "postOnStory": false, 19 | "credits": true 20 | } 21 | ] 22 | } 23 | ``` 24 | 25 | 3. Run the following commands: 26 | 27 | ``` 28 | npm install 29 | npm start 30 | ``` 31 | 32 | ### Setup via cron (linux) 33 | 34 | 1. Make the cron scrip executable. 35 | 36 | ``` 37 | chmod +x /home/ubuntu/projects/auto-poster/cron.sh 38 | ``` 39 | 40 | 2. Add this to your crontab using `crontab -e`. This will run the script every 12 hours. 41 | 42 | ``` 43 | 0 */12 * * * sh /home/ubuntu/projects/auto-poster/cron.sh 44 | ``` 45 | 46 | ### What to do when receiving challenges 47 | 48 | When supsicious behaviour is found on your account, Instagram will provide a challenge for you to complete. This can be a verification via SMS or email (Whichever is linked to your account). The bot will not be able to post when this is happening. 49 | 50 | To fix this: 51 | 52 | - Sign into the account with your phone and verify via SMS. 53 | - Delete the `{account}_cookies.json` for this account. 54 | - Run the verifier script `npm run verifier `. This will send a verification request to your email with a code that you have to enter in the command prompt. (This will also create a new cookies.json file for this account) 55 | - Run `npm start` again. 56 | -------------------------------------------------------------------------------- /src/util/downloader.ts: -------------------------------------------------------------------------------- 1 | import request from 'request'; 2 | import sharp from 'sharp'; 3 | import { DownloadedImage } from '../models'; 4 | 5 | 6 | export abstract class Downloader { 7 | public static downloadImage(name: string, extension: string, url: string): Promise { 8 | return new Promise((resolve, reject) => { 9 | const fileName = `${name}.${extension}`; 10 | const filePath = `downloads/${fileName}`; 11 | 12 | request({ url, encoding: null }, (error, response: any, body: any) => { 13 | const image = { name, fileName, filePath } as DownloadedImage; 14 | 15 | if (error) { 16 | reject(); 17 | } 18 | 19 | // sharp(body).toFile(`downloads/${name}_original.jpg`); 20 | 21 | const action = sharp(body); 22 | action.metadata().then(metadata => { 23 | if (metadata.width < 1080) { 24 | action.resize(1080, 1080, { fit: sharp.fit.contain }); 25 | } else if (metadata.width <= 1080 && metadata.height > 1350) { 26 | action.resize(1080, 1080, { fit: sharp.fit.contain }); 27 | } else if (metadata.width > 1080 && metadata.height > 1080) { 28 | action.resize({ width: 1080, height: 1350, fit: sharp.fit.contain }); 29 | } else if (metadata.width > 1080) { 30 | action.resize(1080, 1080, { fit: sharp.fit.contain }); 31 | } else { 32 | action.resize({ width: 1080, fit: sharp.fit.cover }); 33 | } 34 | 35 | action.toFile(filePath); 36 | }); 37 | 38 | resolve(image); 39 | }) 40 | }); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/util/instagram.ts: -------------------------------------------------------------------------------- 1 | import Instagram from 'instagram-web-api'; 2 | import readline from 'readline'; 3 | import FileCookieStore from 'tough-cookie-filestore2'; 4 | import envConfig from '../env.config'; 5 | import { InstagramUploadType } from '../models'; 6 | 7 | export class InstagramWrapper { 8 | public client: Instagram; 9 | 10 | constructor(username: string, password: string) { 11 | if (envConfig.useCookieStore) { 12 | const cookieStore = new FileCookieStore(`./cookies/${username}_cookies.json`) 13 | this.client = new Instagram({ username, password, cookieStore }); 14 | } else { 15 | this.client = new Instagram({ username, password }); 16 | } 17 | } 18 | 19 | public async upload(photo: string, caption: string, type: InstagramUploadType) { 20 | return this.client.uploadPhoto({ photo, caption, post: type }); 21 | } 22 | 23 | public onInstagramLoginError(error) { 24 | console.log(error); 25 | console.log('Requesting verification code via email.'); 26 | 27 | if (!error) { 28 | return; 29 | } 30 | 31 | const rl = readline.createInterface({ 32 | input: process.stdin, 33 | output: process.stdout 34 | }); 35 | 36 | this.client.updateChallenge({ challengeUrl: error.checkpoint_url, choice: 1 }).then(res => { 37 | rl.question('Enter the verification code from the instagram verification email:', async securityCode => { 38 | if (securityCode) { 39 | console.log(`Sending ${securityCode} as verification request.`) 40 | this.client.updateChallenge({ challengeUrl: error.checkpoint_url, securityCode }).then(async result => { 41 | rl.close(); 42 | this.client.logout().then(res => console.log(res)).catch(err => console.log(err)); 43 | }).catch(err => console.log(err)); 44 | } 45 | 46 | rl.close(); 47 | }); 48 | }); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | # Downloads 107 | downloads/* 108 | 109 | # Database 110 | database.db 111 | 112 | # Settings 113 | settings.json 114 | 115 | cookies/ -------------------------------------------------------------------------------- /src/util/data.ts: -------------------------------------------------------------------------------- 1 | import { Database } from 'sqlite3'; 2 | import env from '../env.config'; 3 | import { InstagramUpload } from '../models'; 4 | 5 | 6 | export class Data { 7 | private INSTAGRAM_UPLOADS_TABLE = 'instagram_uploads'; 8 | 9 | constructor() { 10 | const db = this.connect(); 11 | db.serialize(() => { 12 | db.get(`SELECT * FROM sqlite_sequence WHERE name='${this.INSTAGRAM_UPLOADS_TABLE}';`, (error, result) => { 13 | if (error) { 14 | db.run(`CREATE TABLE "instagram_uploads" ( 15 | "id" INTEGER NOT NULL UNIQUE, 16 | "unique_id" TEXT NOT NULL UNIQUE, 17 | "title" TEXT NOT NULL, 18 | "file_path" TEXT NOT NULL UNIQUE, 19 | PRIMARY KEY("id" AUTOINCREMENT) 20 | )`); 21 | 22 | console.log('Created database tables'); 23 | } 24 | 25 | this.close(db); 26 | }) 27 | }); 28 | } 29 | 30 | connect(): Database { 31 | return new Database(env.database, (err) => { 32 | if (err) { 33 | console.error(err.message); 34 | } 35 | console.log(`Connected to ${env.database}.`); 36 | }); 37 | } 38 | 39 | close(db: Database): void { 40 | db.close((err) => { 41 | if (err) { 42 | console.error(err.message); 43 | } 44 | console.log('Close the database connection.'); 45 | }); 46 | } 47 | 48 | all(limit: number = 100): Promise { 49 | return new Promise((resolve, reject) => { 50 | const db = this.connect(); 51 | 52 | db.serialize(() => { 53 | db.all(`select * from instagram_uploads order by id desc limit ${limit}`, (error, results: InstagramUpload[]) => { 54 | this.close(db); 55 | 56 | if (error) { 57 | reject(error); 58 | } 59 | 60 | resolve(results); 61 | }); 62 | }); 63 | }); 64 | } 65 | 66 | insert(uniqueId: string, title: string, filePath: string): Promise { 67 | return new Promise((resolve, reject) => { 68 | const db = new Database(env.database, (err) => { 69 | if (err) { 70 | console.error(err.message); 71 | } 72 | console.log(`Connected to ${env.database}.`); 73 | }); 74 | 75 | db.serialize(() => { 76 | db.run(`insert into ${this.INSTAGRAM_UPLOADS_TABLE} (unique_id, title, file_path) values (?,?, ?)`, [uniqueId, title, filePath], (error, result) => { 77 | if (error) { 78 | reject(error); 79 | } 80 | 81 | resolve(result); 82 | }); 83 | }); 84 | 85 | db.close((err) => { 86 | if (err) { 87 | console.error(err.message); 88 | } 89 | console.log('Close the database connection.'); 90 | }); 91 | }); 92 | } 93 | 94 | // get(uniqueId: string): Promise { 95 | // const sql = `select * from instagram_uploads where unique_id = ?`; 96 | 97 | // return new Promise((resolve, reject) => { 98 | // this.db.get(sql, [uniqueId], (error, result: InstagramUpload) => { 99 | // if (error) { 100 | // reject(error); 101 | // } 102 | 103 | // resolve(result); 104 | // }); 105 | // }); 106 | // } 107 | } -------------------------------------------------------------------------------- /src/scripts/RedditInstagramPoster.ts: -------------------------------------------------------------------------------- 1 | import env from '../env.config'; 2 | import { DownloadedImage, InstagramUpload, InstagramUploadType, RedditPostResponse } from "../models"; 3 | import { Account } from '../util/config'; 4 | import { Data } from '../util/data'; 5 | import { Downloader } from '../util/downloader'; 6 | import { InstagramWrapper } from "../util/instagram"; 7 | import { Reddit } from "../util/reddit"; 8 | 9 | export class RedditInstagramPoster { 10 | private database: Data; 11 | private account: Account; 12 | private reddit: Reddit; 13 | private instagram: InstagramWrapper; 14 | 15 | constructor(database: Data, account: Account) { 16 | this.database = database; 17 | this.account = account; 18 | this.reddit = new Reddit(account.subreddit); 19 | this.instagram = new InstagramWrapper(account.instagramUsername, account.instagramPassword); 20 | } 21 | 22 | public async run(databaseUploads?: InstagramUpload[]) { 23 | console.log(`Starting RedditInstagramPoster => @${this.account.instagramUsername}`) 24 | const posts = await this.reddit.getPosts(); 25 | 26 | if (posts.length === 0) { 27 | console.error('no reddit posts found.'); 28 | return; 29 | } 30 | 31 | let uploads = []; 32 | if (databaseUploads) { 33 | uploads = databaseUploads; 34 | } else { 35 | uploads = await this.database.all(1000); 36 | } 37 | 38 | const filteredPosts = posts 39 | .filter(post => !uploads.map(x => x.unique_id).includes(post.data.name)) 40 | .slice(0, env.uploadsPerScriptRun); 41 | 42 | if (filteredPosts.length === 0) { 43 | return; 44 | } 45 | 46 | await this.uploadRedditPostsToInstagram(filteredPosts); 47 | } 48 | 49 | private async uploadRedditPostsToInstagram(posts: RedditPostResponse[]) { 50 | const images$: Promise[] = posts.map(post => Downloader.downloadImage(post.data.name, 'jpg', this.getImageUrl(post))); 51 | const images = await Promise.all(images$); 52 | 53 | if (images.length === 0) { 54 | return; 55 | } 56 | 57 | console.log(`fetched ${images.length} images, starting upload...`); 58 | 59 | await this.instagram.client.login() 60 | .then(() => this.onInstagramLoginSuccess(posts, images)) 61 | .catch((response: any) => this.instagram.onInstagramLoginError(response.error)); 62 | } 63 | 64 | private getImageUrl(redditPost: RedditPostResponse) { 65 | let imageUrl = redditPost.data.url; 66 | 67 | if (redditPost.data.gallery_data) { 68 | imageUrl = `https://i.redd.it/${redditPost.data.gallery_data.items[0].media_id}.jpg`; 69 | } 70 | 71 | return imageUrl; 72 | } 73 | 74 | private async onInstagramLoginSuccess(posts, images) { 75 | images.forEach(async (image, index) => { 76 | await setTimeout(async () => { 77 | const post = posts.find(post => post.data.name === image.name); 78 | let caption = post.data.title; 79 | 80 | console.log(caption); 81 | 82 | if (this.account.credits) { 83 | caption = `${caption} 84 | 85 | For more follow @${this.account.instagramUsername} 86 | 87 | source: https://reddit.com${post.data.permalink} 88 | 89 | ${this.account.tags}`; 90 | } else { 91 | caption = `${caption} 92 | - 93 | ${this.account.tags}`; 94 | } 95 | 96 | console.log(`uploading ${image.fileName} to @${this.account.instagramUsername}...`); 97 | 98 | const { media } = await this.instagram.upload(image.filePath, caption, InstagramUploadType.Feed); 99 | console.log(`uploaded ${image.filePath} to @${this.account.instagramUsername} (${media.code})`); 100 | 101 | if (this.account.postOnStory) { 102 | await this.instagram.upload(image.filePath, caption, InstagramUploadType.Story); 103 | } 104 | 105 | await this.database.insert(image.name, caption, image.fileName); 106 | console.log(`inserted ${image.fileName} into database`); 107 | 108 | console.log(`waiting ${env.timeoutInBetweenUploadsInSeconds} seconds before starting next upload.`); 109 | }, (env.timeoutInBetweenUploadsInSeconds * 1000) * ((index - 1) + 1)); 110 | }); 111 | } 112 | } --------------------------------------------------------------------------------