Skip to content

Commit

Permalink
Validate usernames before download
Browse files Browse the repository at this point in the history
  • Loading branch information
Alphka committed Apr 14, 2024
1 parent 1a3e16e commit 36dd1cb
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 50 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"author": "Kayo Souza",
"name": "insta-downloader",
"version": "3.1.1",
"version": "3.2.0",
"description": "An application to download content from Instagram",
"main": "src/index.js",
"scripts": {
Expand Down
87 changes: 48 additions & 39 deletions src/Downloader.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import Question from "./helpers/Question.js"
import Queue from "./Queue.js"
import axios, { AxiosError } from "axios"
import dotenv from "dotenv"
import chalk from "chalk"
import sharp from "sharp"
import mime from "mime"
import Log from "./helpers/Log.js"

const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
Expand Down Expand Up @@ -40,10 +40,11 @@ export default class Downloader {
Accept: "*/*",
Origin: BASE_URL
}
/** @type {import("./typings/index.js").Config} */ config

/** @type {string} */ output
/** @type {string[]} */ usernames
/** @type {number | undefined} */ limit
/** @type {string} */ output
/** @type {import("./typings/index.js").Config} */ config
/** @type {Queue<ReturnType<typeof this.Download>>} */ queue

/**
Expand All @@ -52,10 +53,33 @@ export default class Downloader {
* @param {number} [limit]
*/
constructor(usernames, queue, limit){
this.usernames = Array.isArray(usernames) ? Array.from(new Set(usernames)) : [usernames]
if(Array.isArray(usernames)){
this.usernames = Array.from(new Set(usernames))

for(let index = this.usernames.length - 1; index >= 0; index--){
const username = this.usernames[index]

try{
this.ValidateUsername(username)
}catch(error){
Log(new Error(/** @type {string} */ (error)))
this.usernames.splice(index, 1)
}
}
}else{
this.ValidateUsername(usernames)
this.usernames = [usernames]
}

this.limit = limit
this.queue = new Queue(queue)
}
/** @param {string} username */
ValidateUsername(username){
if(!/^([A-Za-z0-9_](?:(?:[A-Za-z0-9_]|(?:\.(?!\.))){0,28}(?:[A-Za-z0-9_]))?)$/.test(username)){
throw `Invalid username: ${username}`
}
}
GetConfig(){
if(!existsSync(configPath)){
/** @type {import("./typings/index.js").Config} */
Expand Down Expand Up @@ -124,18 +148,21 @@ export default class Downloader {
* hcover?: boolean
* }} data */
async Init({ output, timeline, highlights, hcover, stories }){
this.Log("Initializing")
Log("Initializing")

if(!this.usernames.length) throw "There are no valid usernames"

this.GetConfig()
this.UpdateHeaders()

do{
try{
await this.CheckServerConfig()
await this.CheckLogin()
this.Log("Logged in")
Log("Logged in")
break
}catch{
this.Log(new Error("You are not logged in. Type your data for authentication."))
Log(new Error("You are not logged in. Type your data for authentication."))

const id = (await Question("User id: ")).trim()
const token = (await Question("CSRF Token: ")).trim()
Expand All @@ -160,18 +187,18 @@ export default class Downloader {
const { id, followed_by_viewer, is_private } = await this.GetUser(username)

if(is_private && !followed_by_viewer){
this.Log(new Error(`You don't have access to a private account: ${username}`))
Log(new Error(`You don't have access to a private account: ${username}`))
continue
}

user_id = id
}catch(error){
if(error instanceof Error) error.message = `User not found: ${username}`
this.Log(error)
Log(error)
continue
}

this.Log(`Downloading from user: ${username}, id: ${user_id}`)
Log(`Downloading from user: ${username}, id: ${user_id}`)

const folder = join(output, username)

Expand All @@ -185,7 +212,7 @@ export default class Downloader {
if(result.status === "rejected"){
const { reason } = result
if(reason instanceof Error) reason.stack = `Failed to download user's content: ${username}`
this.Log(reason)
Log(reason)
}
}
}
Expand Down Expand Up @@ -336,7 +363,7 @@ export default class Downloader {
for(const { id, items } of highlightsContents){
if(count > limit) throw new Error("Unexpected error")

this.Log(`Downloading highlight: ${id.substring(id.indexOf(":") + 1)}`)
Log(`Downloading highlight: ${id.substring(id.indexOf(":") + 1)}`)

for(const item of items){
const { url } = GetCorrectContent(item)[0]
Expand Down Expand Up @@ -367,7 +394,7 @@ export default class Downloader {
try{
await this.Download(coverUrl, folder, new Date)
}catch(error){
this.Log(error)
Log(error)
}
}
}
Expand All @@ -377,8 +404,8 @@ export default class Downloader {
}

if(hasHighlights){
if(count === 0) this.Log("No content found in the highlights")
}else this.Log("No highlights found")
if(count === 0) Log("No content found in the highlights")
}else Log("No highlights found")
}
/**
* @param {string} user_id
Expand All @@ -389,13 +416,13 @@ export default class Downloader {
async DownloadStories(user_id, folder, limit = Infinity, username){
const results = await this.GetStories(/** @type {`${number}`} */ (user_id), username)

if(!results) return this.Log("No stories found")
if(!results) return Log("No stories found")

const { items: stories } = results

if(stories.length){
if(!existsSync(folder)) await mkdir(folder, { recursive: true })
this.Log("Downloading stories")
Log("Downloading stories")
}

let count = 0
Expand Down Expand Up @@ -438,7 +465,7 @@ export default class Downloader {
if(num_results === 0) break

if(first){
this.Log("Downloading timeline")
Log("Downloading timeline")
first = false
}

Expand All @@ -452,10 +479,10 @@ export default class Downloader {
hasMore = more_available
lastId = next_max_id
count = data.count
}else this.Log(new Error("Failed to get timeline, lastId: " + (lastId || null)))
}else Log(new Error("Failed to get timeline, lastId: " + (lastId || null)))
}

if(count === 0) this.Log("No content found in timeline")
if(count === 0) Log("No content found in timeline")
}
/**
* @param {(import("./typings/api.js").FeedItem | import("./typings/api.js").GraphHighlightsMedia | import("./typings/api.js").GraphReelsMedia)[]} items
Expand Down Expand Up @@ -519,7 +546,7 @@ export default class Downloader {
headers: { Referer: username ? `${BASE_URL}/${username}/` : BASE_URL + "/" }
})
}catch(error){
this.Log(error instanceof Error ? error : new Error(String(error)))
Log(error instanceof Error ? error : new Error(String(error)))
urls.delete(url)
}
}))
Expand Down Expand Up @@ -654,22 +681,4 @@ export default class Downloader {
config.app_id = response.data.match(/"X-IG-App-ID":"(\d+)"/)?.[1]
}
}
Log(...args){
if(isTesting) return

const date = new Date().toLocaleString("pt-BR").split(", ")[1]

if(args.length === 1){
const arg = args[0]

if(arg instanceof Error){
const message = arg.cause ? `${arg.message} (${arg.cause})` : arg.message
return console.error(chalk.redBright(`[${date}] ${message}`))
}

if(typeof arg === "string") return console.log(`${chalk.blackBright(`[${date}]`)} ${arg}`)
}

console.log(chalk.blackBright(`[${date}]`), ...args)
}
}
22 changes: 22 additions & 0 deletions src/helpers/Log.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import chalk from "chalk"

const isTesting = process.env.npm_command === "test" || process.env.npm_lifecycle_event === "test"

export default function Log(...args){
if(isTesting) return

const date = new Date().toLocaleString("pt-BR").split(", ")[1]

if(args.length === 1){
const arg = args[0]

if(arg instanceof Error){
const message = arg.cause ? `${arg.message} (${arg.cause})` : arg.message
return console.error(chalk.redBright(`[${date}] ${message}`))
}

if(typeof arg === "string") return console.log(`${chalk.blackBright(`[${date}]`)} ${arg}`)
}

console.log(chalk.blackBright(`[${date}]`), ...args)
}
31 changes: 21 additions & 10 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import packageConfig from "../package.json" assert { type: "json" }
import Downloader from "./Downloader.js"
import isNumber from "./helpers/isNumber.js"
import config from "./config.js"
import Log from "./helpers/Log.js"

const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
Expand Down Expand Up @@ -54,16 +55,26 @@ const command = program
* @param {import("./typings/index.js").Options} options
* @param {import("commander").Command} command
*/
(_arg, options, command) => {
if(!command.args.length) throw "No usernames provided"
if(!options.highlights) options.hcover = false

const output = GetOutputDirectory(options.output, options.force)

new Downloader(command.args, isNumber(options.queue) ? Number(options.queue) : 12, isNumber(options.limit) ? Number(options.limit) : undefined).Init({
output,
...options
})
async (_arg, options, command) => {
try{
if(!command.args.length) throw "No usernames provided"
if(!options.highlights) options.hcover = false

const output = GetOutputDirectory(options.output, options.force)

const downloader = new Downloader(
command.args,
isNumber(options.queue) ? Number(options.queue) : 12,
isNumber(options.limit) ? Number(options.limit) : undefined
)

await downloader.Init({
output,
...options
})
}catch(error){
Log(error instanceof Error ? error : new Error(String(error)))
}
})

config.options.forEach(({ option, alternative, description, defaultValue, syntax }) => {
Expand Down

0 comments on commit 36dd1cb

Please sign in to comment.