2020-01-12 12:50:21 +00:00
const constants = require ( "./constants" )
const { request } = require ( "./utils/request" )
2020-02-02 11:43:56 +00:00
const switcher = require ( "./utils/torswitcher" )
2022-07-28 11:31:05 +00:00
const { extractPreloader } = require ( "./utils/body" )
2020-02-02 14:53:37 +00:00
const { TtlCache , RequestCache , UserRequestCache } = require ( "./cache" )
2020-01-30 12:51:59 +00:00
const RequestHistory = require ( "./structures/RequestHistory" )
2021-11-05 04:01:46 +00:00
const fhp = require ( "fast-html-parser" )
2020-02-01 04:44:40 +00:00
const db = require ( "./db" )
2022-07-28 11:31:05 +00:00
require ( "./testimports" ) ( constants , request , extractPreloader , UserRequestCache , RequestHistory , db )
2020-01-12 12:50:21 +00:00
2020-01-28 03:14:21 +00:00
const requestCache = new RequestCache ( constants . caching . resource _cache _time )
2020-04-04 14:57:31 +00:00
/** @type {import("./cache").UserRequestCache<import("./structures/User")|import("./structures/ReelUser")>} */
2020-02-02 14:53:37 +00:00
const userRequestCache = new UserRequestCache ( constants . caching . resource _cache _time )
2020-01-26 14:56:59 +00:00
/** @type {import("./cache").TtlCache<import("./structures/TimelineEntry")>} */
2020-01-28 03:14:21 +00:00
const timelineEntryCache = new TtlCache ( constants . caching . resource _cache _time )
2020-06-24 14:58:01 +00:00
const history = new RequestHistory ( [ "user" , "timeline" , "igtv" , "post" , "reel" ] )
2020-01-12 12:50:21 +00:00
2020-04-07 06:30:00 +00:00
const AssistantSwitcher = require ( "./structures/AssistantSwitcher" )
const assistantSwitcher = new AssistantSwitcher ( )
2020-02-18 00:39:20 +00:00
/ * *
* @ param { string } username
2020-04-12 14:52:04 +00:00
* @ param { symbol } [ context ]
2020-02-18 00:39:20 +00:00
* /
2020-04-12 14:52:04 +00:00
async function fetchUser ( username , context ) {
if ( constants . external . reserved _paths . includes ( username ) ) {
throw constants . symbols . ENDPOINT _OVERRIDDEN
}
2020-02-02 14:53:37 +00:00
let mode = constants . allow _user _from _reel
2022-07-24 13:48:44 +00:00
if ( mode === "iweb" ) {
return fetchUserFromIWeb ( username )
2022-07-28 11:31:05 +00:00
} else if ( mode === "html" ) {
return fetchUserFromHTML ( username )
2020-02-02 14:53:37 +00:00
}
2022-07-24 13:48:44 +00:00
throw new Error ( ` Your instance admin selected fetch mode ${ mode } , which is now unsupported. Please use "iweb" instead (the default). ` )
2020-02-02 13:24:14 +00:00
}
2022-07-28 11:31:05 +00:00
/ * *
* @ param { string } username
* @ returns { Promise < { user : import ( "./structures/User" ) , quotaUsed : number } > }
* /
function fetchUserFromHTML ( username ) {
const blockedCacheConfig = constants . caching . self _blocked _status . user _html
if ( blockedCacheConfig ) {
if ( history . store . has ( "user" ) ) {
const entry = history . store . get ( "user" )
if ( ! entry . lastRequestSuccessful && Date . now ( ) < entry . lastRequestAt + blockedCacheConfig . time ) {
return Promise . reject ( entry . kind || constants . symbols . RATE _LIMITED )
}
}
}
let quotaUsed = 0
return userRequestCache . getOrFetch ( "user/" + username , false , true , ( ) => {
quotaUsed ++
return switcher . request ( "user_html" , ` https://www.instagram.com/ ${ username } /feed/ ` , async res => {
if ( res . status === 301 ) throw constants . symbols . ENDPOINT _OVERRIDDEN
if ( res . status === 302 ) throw constants . symbols . INSTAGRAM _DEMANDS _LOGIN
if ( res . status === 429 ) throw constants . symbols . RATE _LIMITED
return res
} ) . then ( async g => {
const res = await g . response ( )
if ( res . status === 404 ) {
throw constants . symbols . NOT _FOUND
} else {
const text = await g . text ( )
// require down here or have to deal with require loop. require cache will take care of it anyway.
// User -> Timeline -> TimelineEntry -> collectors -/> User
const User = require ( "./structures/User" )
const preloader = extractPreloader ( text )
const profileInfoResponse = preloader . find ( x => x . request . url === "/api/v1/users/web_profile_info/" )
if ( ! profileInfoResponse ) {
throw new Error ( "No profile info in the preloader." )
}
const user = new User ( JSON . parse ( profileInfoResponse . result . response ) . data . user )
history . report ( "user" , true )
if ( constants . caching . db _user _id ) {
const existing = db . prepare ( "SELECT created, updated_version FROM Users WHERE username = ?" ) . get ( user . data . username )
db . prepare (
"REPLACE INTO Users (username, user_id, created, updated, updated_version, biography, post_count, following_count, followed_by_count, external_url, full_name, is_private, is_verified, profile_pic_url) VALUES "
+ "(@username, @user_id, @created, @updated, @updated_version, @biography, @post_count, @following_count, @followed_by_count, @external_url, @full_name, @is_private, @is_verified, @profile_pic_url)"
) . run ( {
username : user . data . username ,
user _id : user . data . id ,
created : existing && existing . updated _version === constants . database _version ? existing . created : Date . now ( ) ,
updated : Date . now ( ) ,
updated _version : constants . database _version ,
biography : user . data . biography || null ,
post _count : user . posts || 0 ,
following _count : user . following || 0 ,
followed _by _count : user . followedBy || 0 ,
external _url : user . data . external _url || null ,
full _name : user . data . full _name || null ,
is _private : + user . data . is _private ,
is _verified : + user . data . is _verified ,
profile _pic _url : user . data . profile _pic _url
} )
}
return user
}
} ) . catch ( error => {
if ( error === constants . symbols . INSTAGRAM _DEMANDS _LOGIN || error === constants . symbols . RATE _LIMITED ) {
history . report ( "user" , false , error )
}
throw error
} )
} ) . then ( user => ( { user , quotaUsed } ) )
}
2020-02-18 00:39:20 +00:00
/ * *
* @ param { string } username
2020-07-22 12:58:21 +00:00
* @ returns { Promise < { user : import ( "./structures/User" ) , quotaUsed : number } > }
2020-02-18 00:39:20 +00:00
* /
2022-07-24 13:48:44 +00:00
function fetchUserFromIWeb ( username ) {
2021-01-30 06:29:24 +00:00
const blockedCacheConfig = constants . caching . self _blocked _status . user _html
if ( blockedCacheConfig ) {
2020-04-19 13:57:21 +00:00
if ( history . store . has ( "user" ) ) {
const entry = history . store . get ( "user" )
2021-01-30 06:29:24 +00:00
if ( ! entry . lastRequestSuccessful && Date . now ( ) < entry . lastRequestAt + blockedCacheConfig . time ) {
2021-01-30 06:40:20 +00:00
return Promise . reject ( entry . kind || constants . symbols . RATE _LIMITED )
2020-04-16 13:40:20 +00:00
}
}
2020-04-19 13:57:21 +00:00
}
2022-05-06 04:05:37 +00:00
let quotaUsed = 0
2020-04-19 13:57:21 +00:00
return userRequestCache . getOrFetch ( "user/" + username , false , true , ( ) => {
2022-05-06 04:05:37 +00:00
quotaUsed ++
2022-07-24 13:48:44 +00:00
const params = new URLSearchParams ( { username } )
return switcher . request ( "user_html" , ` https://i.instagram.com/api/v1/users/web_profile_info/? ${ params } ` , async res => {
2020-02-05 12:32:51 +00:00
if ( res . status === 301 ) throw constants . symbols . ENDPOINT _OVERRIDDEN
2020-02-02 13:24:14 +00:00
if ( res . status === 302 ) throw constants . symbols . INSTAGRAM _DEMANDS _LOGIN
if ( res . status === 429 ) throw constants . symbols . RATE _LIMITED
return res
2020-03-15 06:50:29 +00:00
} ) . then ( async g => {
const res = await g . response ( )
2022-07-24 13:48:44 +00:00
const json = await g . json ( )
// require down here or have to deal with require loop. require cache will take care of it anyway.
// User -> Timeline -> TimelineEntry -> collectors -/> User
const User = require ( "./structures/User" )
const user = new User ( json . data . user )
history . report ( "user" , true )
// sure, cache the user info. why not.
if ( constants . caching . db _user _id ) {
const existing = db . prepare ( "SELECT created, updated_version FROM Users WHERE username = ?" ) . get ( user . data . username )
db . prepare (
"REPLACE INTO Users (username, user_id, created, updated, updated_version, biography, post_count, following_count, followed_by_count, external_url, full_name, is_private, is_verified, profile_pic_url) VALUES "
+ "(@username, @user_id, @created, @updated, @updated_version, @biography, @post_count, @following_count, @followed_by_count, @external_url, @full_name, @is_private, @is_verified, @profile_pic_url)"
) . run ( {
username : user . data . username ,
user _id : user . data . id ,
created : existing && existing . updated _version === constants . database _version ? existing . created : Date . now ( ) ,
updated : Date . now ( ) ,
updated _version : constants . database _version ,
biography : user . data . biography || null ,
post _count : user . posts || 0 ,
following _count : user . following || 0 ,
followed _by _count : user . followedBy || 0 ,
external _url : user . data . external _url || null ,
full _name : user . data . full _name || null ,
is _private : + user . data . is _private ,
is _verified : + user . data . is _verified ,
profile _pic _url : user . data . profile _pic _url
} )
2020-02-02 13:24:14 +00:00
}
2022-07-24 13:48:44 +00:00
return user
2020-02-02 13:24:14 +00:00
} ) . catch ( error => {
if ( error === constants . symbols . INSTAGRAM _DEMANDS _LOGIN || error === constants . symbols . RATE _LIMITED ) {
2021-01-30 06:40:20 +00:00
history . report ( "user" , false , error )
2020-02-02 13:24:14 +00:00
}
throw error
2020-01-12 12:50:21 +00:00
} )
2022-05-06 04:05:37 +00:00
} ) . then ( user => ( { user , quotaUsed } ) )
2020-01-12 12:50:21 +00:00
}
2020-04-04 14:57:31 +00:00
/ * *
* @ param { string } userID
* /
function updateProfilePictureFromReel ( userID ) {
const p = new URLSearchParams ( )
p . set ( "query_hash" , constants . external . reel _query _hash )
p . set ( "variables" , JSON . stringify ( {
user _id : userID ,
include _reel : true
} ) )
return switcher . request ( "reel_graphql" , ` https://www.instagram.com/graphql/query/? ${ p . toString ( ) } ` , async res => {
if ( res . status === 429 ) throw constants . symbols . RATE _LIMITED
return res
} ) . then ( res => res . json ( ) ) . then ( root => {
const result = root . data . user
if ( ! result ) throw constants . symbols . NOT _FOUND
const profilePicURL = result . reel . user . profile _pic _url
if ( ! profilePicURL ) throw constants . symbols . NOT _FOUND
db . prepare ( "UPDATE Users SET profile_pic_url = ? WHERE user_id = ?" ) . run ( profilePicURL , userID )
2020-04-20 07:12:01 +00:00
for ( const entry of userRequestCache . cache . values ( ) ) {
2020-04-04 14:57:31 +00:00
// yes, data.data is correct.
2020-04-20 07:12:01 +00:00
if ( entry . data && entry . data . data && entry . data . data . id === userID ) {
entry . data . data . profile _pic _url = profilePicURL
entry . data . computeProxyProfilePic ( )
2020-04-04 14:57:31 +00:00
break // stop checking entries from the cache since we won't find any more
}
}
return profilePicURL
} ) . catch ( error => {
throw error
} )
}
2020-02-18 00:39:20 +00:00
/ * *
* @ param { string } userID
* @ param { string } username
2020-07-22 12:58:21 +00:00
* @ returns { Promise < { user : import ( "./structures/ReelUser" ) | import ( "./structures/User" ) , quotaUsed : number } > }
2020-02-18 00:39:20 +00:00
* /
2020-02-02 13:24:14 +00:00
function fetchUserFromCombined ( userID , username ) {
// Fetch basic user information
const p = new URLSearchParams ( )
p . set ( "query_hash" , constants . external . reel _query _hash )
p . set ( "variables" , JSON . stringify ( {
user _id : userID ,
include _reel : true
} ) )
2020-02-02 14:53:37 +00:00
return userRequestCache . getOrFetch ( "user/" + username , true , false , ( ) => {
2020-02-02 13:24:14 +00:00
return switcher . request ( "reel_graphql" , ` https://www.instagram.com/graphql/query/? ${ p . toString ( ) } ` , async res => {
if ( res . status === 429 ) throw constants . symbols . RATE _LIMITED
return res
} ) . then ( res => res . json ( ) ) . then ( root => {
const result = root . data . user
2020-07-07 10:08:19 +00:00
if ( ! result ) {
// user ID doesn't exist.
db . prepare ( "DELETE FROM Users WHERE user_id = ?" ) . run ( userID ) // deleting the entry makes sense to me; the username might be claimed by somebody else later
throw constants . symbols . NOT _FOUND // this should cascade down and show the user not found page
}
2020-02-02 13:24:14 +00:00
// require down here or have to deal with require loop. require cache will take care of it anyway.
// ReelUser -> Timeline -> TimelineEntry -> collectors -/> User
const ReelUser = require ( "./structures/ReelUser" )
const user = new ReelUser ( result . reel . user )
2020-02-18 04:06:11 +00:00
history . report ( "reel" , true )
2020-02-02 13:24:14 +00:00
return user
} )
} ) . then ( async user => {
// Add first timeline page
2020-07-22 12:58:21 +00:00
let quotaUsed = 0
2020-02-02 13:24:14 +00:00
if ( ! user . timeline . pages [ 0 ] ) {
2020-07-22 12:58:21 +00:00
const fetched = await fetchTimelinePage ( userID , "" )
if ( ! fetched . fromCache ) quotaUsed ++
user . timeline . addPage ( fetched . result )
2020-02-02 13:24:14 +00:00
}
2020-07-22 12:58:21 +00:00
return { user , quotaUsed }
2020-02-02 13:44:52 +00:00
} ) . catch ( error => {
if ( error === constants . symbols . RATE _LIMITED ) {
2021-01-30 06:40:20 +00:00
history . report ( "reel" , false , error )
2020-02-02 13:44:52 +00:00
}
throw error
2020-02-02 13:24:14 +00:00
} )
}
2020-01-12 12:50:21 +00:00
/ * *
* @ param { string } userID
* @ param { string } after
2020-07-22 12:58:21 +00:00
* @ returns { Promise < { result : import ( "./types" ) . PagedEdges < import ( "./types" ) . TimelineEntryN2 > , fromCache : boolean } > }
2020-01-12 12:50:21 +00:00
* /
function fetchTimelinePage ( userID , after ) {
2021-01-30 06:29:24 +00:00
const blockedCacheConfig = constants . caching . self _blocked _status . timeline _graphql
if ( blockedCacheConfig ) {
if ( history . store . has ( "timeline" ) ) {
const entry = history . store . get ( "timeline" )
if ( ! entry . lastRequestSuccessful && Date . now ( ) < entry . lastRequestAt + blockedCacheConfig . time ) {
2021-01-30 06:40:20 +00:00
return Promise . reject ( entry . kind || constants . symbols . RATE _LIMITED )
2021-01-30 06:29:24 +00:00
}
}
}
2020-01-12 12:50:21 +00:00
const p = new URLSearchParams ( )
p . set ( "query_hash" , constants . external . timeline _query _hash )
p . set ( "variables" , JSON . stringify ( {
id : userID ,
first : constants . external . timeline _fetch _first ,
after : after
} ) )
2020-02-02 13:24:14 +00:00
return requestCache . getOrFetchPromise ( ` page/ ${ userID } / ${ after } ` , ( ) => {
return switcher . request ( "timeline_graphql" , ` https://www.instagram.com/graphql/query/? ${ p . toString ( ) } ` , async res => {
2021-01-18 12:45:22 +00:00
if ( res . status === 302 ) throw constants . symbols . INSTAGRAM _BLOCK _TYPE _DECEMBER
2020-02-02 11:43:56 +00:00
if ( res . status === 429 ) throw constants . symbols . RATE _LIMITED
2020-03-15 06:50:29 +00:00
} ) . then ( g => g . json ( ) ) . then ( root => {
2020-07-07 10:08:19 +00:00
if ( root . data . user === null ) {
// user ID doesn't exist.
db . prepare ( "DELETE FROM Users WHERE user_id = ?" ) . run ( userID ) // deleting the entry makes sense to me; the username might be claimed by somebody else later
requestCache
throw constants . symbols . NOT _FOUND // this should cascade down and show the user not found page
}
2020-02-02 11:43:56 +00:00
/** @type {import("./types").PagedEdges<import("./types").TimelineEntryN2>} */
const timeline = root . data . user . edge _owner _to _timeline _media
history . report ( "timeline" , true )
return timeline
} ) . catch ( error => {
2021-01-30 06:29:24 +00:00
if ( error === constants . symbols . RATE _LIMITED || error === constants . symbols . INSTAGRAM _BLOCK _TYPE _DECEMBER ) {
2021-01-30 06:40:20 +00:00
history . report ( "timeline" , false , error )
2020-01-30 12:51:59 +00:00
}
2020-02-02 11:43:56 +00:00
throw error
2020-01-12 12:50:21 +00:00
} )
} )
}
2020-06-11 16:09:28 +00:00
/ * *
* @ param { string } userID
* @ param { string } after
2020-07-22 12:58:21 +00:00
* @ returns { Promise < { result : import ( "./types" ) . PagedEdges < import ( "./types" ) . TimelineEntryN2 > , fromCache : boolean } > }
2020-06-11 16:09:28 +00:00
* /
function fetchIGTVPage ( userID , after ) {
const p = new URLSearchParams ( )
p . set ( "query_hash" , constants . external . igtv _query _hash )
p . set ( "variables" , JSON . stringify ( {
id : userID ,
first : constants . external . igtv _fetch _first ,
after : after
} ) )
return requestCache . getOrFetchPromise ( ` igtv/ ${ userID } / ${ after } ` , ( ) => {
// assuming this uses the same bucket as timeline, which may not be the case
return switcher . request ( "timeline_graphql" , ` https://www.instagram.com/graphql/query/? ${ p . toString ( ) } ` , async res => {
2021-01-18 12:45:22 +00:00
if ( res . status === 302 ) throw constants . symbols . INSTAGRAM _BLOCK _TYPE _DECEMBER
2020-06-11 16:09:28 +00:00
if ( res . status === 429 ) throw constants . symbols . RATE _LIMITED
} ) . then ( g => g . json ( ) ) . then ( root => {
/** @type {import("./types").PagedEdges<import("./types").TimelineEntryN2>} */
2020-06-24 14:58:01 +00:00
const timeline = root . data . user . edge _felix _video _timeline
history . report ( "igtv" , true )
2020-06-11 16:09:28 +00:00
return timeline
} ) . catch ( error => {
2021-01-30 06:29:24 +00:00
if ( error === constants . symbols . RATE _LIMITED || error === constants . symbols . INSTAGRAM _BLOCK _TYPE _DECEMBER ) {
2021-01-30 06:40:20 +00:00
history . report ( "igtv" , false , error )
2020-06-11 16:09:28 +00:00
}
throw error
} )
} )
}
/ * *
* @ param { string } userID
* @ param { string } username
2020-07-22 12:58:21 +00:00
* @ returns { Promise < { result : boolean , fromCache : boolean } > }
2020-06-11 16:09:28 +00:00
* /
function verifyUserPair ( userID , username ) {
// Fetch basic user information
const p = new URLSearchParams ( )
p . set ( "query_hash" , constants . external . reel _query _hash )
p . set ( "variables" , JSON . stringify ( {
user _id : userID ,
include _reel : true
} ) )
return requestCache . getOrFetchPromise ( "userID/" + userID , ( ) => {
return switcher . request ( "reel_graphql" , ` https://www.instagram.com/graphql/query/? ${ p . toString ( ) } ` , async res => {
2021-01-18 12:45:22 +00:00
if ( res . status === 302 ) throw constants . symbols . INSTAGRAM _BLOCK _TYPE _DECEMBER
2020-06-11 16:09:28 +00:00
if ( res . status === 429 ) throw constants . symbols . RATE _LIMITED
return res
} ) . then ( res => res . json ( ) ) . then ( root => {
let user = root . data . user
if ( ! user ) throw constants . symbols . NOT _FOUND
user = user . reel . user
history . report ( "reel" , true )
return user . id === userID && user . username === username
} ) . catch ( error => {
throw error
} )
} )
}
2020-01-18 15:38:14 +00:00
/ * *
* @ param { string } shortcode
2020-01-26 14:56:59 +00:00
* @ returns { import ( "./structures/TimelineEntry" ) }
2020-01-18 15:38:14 +00:00
* /
2020-01-26 14:56:59 +00:00
function getOrCreateShortcode ( shortcode ) {
if ( timelineEntryCache . has ( shortcode ) ) {
return timelineEntryCache . get ( shortcode )
} else {
// require down here or have to deal with require loop. require cache will take care of it anyway.
2020-02-02 13:24:14 +00:00
// TimelineEntry -> collectors -/> TimelineEntry
2020-01-26 14:56:59 +00:00
const TimelineEntry = require ( "./structures/TimelineEntry" )
const result = new TimelineEntry ( )
timelineEntryCache . set ( shortcode , result )
return result
}
}
2020-01-18 15:38:14 +00:00
2020-01-26 14:56:59 +00:00
async function getOrFetchShortcode ( shortcode ) {
if ( timelineEntryCache . has ( shortcode ) ) {
2020-07-29 09:51:41 +00:00
return { post : timelineEntryCache . get ( shortcode ) , fromCache : true }
2020-01-26 14:56:59 +00:00
} else {
2020-07-29 09:51:41 +00:00
const { result , fromCache } = await fetchShortcodeData ( shortcode )
2020-01-26 14:56:59 +00:00
const entry = getOrCreateShortcode ( shortcode )
2020-07-29 09:51:41 +00:00
entry . applyN3 ( result )
2021-11-05 04:01:46 +00:00
entry . fullyUpdated = true // we already called fetchShortcodeData, which fetches the greatest amount of data possible. it's no use trying to fetch that again with .update().
2020-07-29 09:51:41 +00:00
return { post : entry , fromCache }
2020-01-26 14:56:59 +00:00
}
}
/ * *
* @ param { string } shortcode
2020-07-22 12:58:21 +00:00
* @ returns { Promise < { result : import ( "./types" ) . TimelineEntryN3 , fromCache : boolean } > }
2020-01-26 14:56:59 +00:00
* /
function fetchShortcodeData ( shortcode ) {
2021-11-01 12:58:03 +00:00
// embed endpoint unfortunately only returns a single image, or a single video thumbnail
2020-01-18 15:38:14 +00:00
return requestCache . getOrFetchPromise ( "shortcode/" + shortcode , ( ) => {
2021-11-01 12:58:03 +00:00
return switcher . request ( "post_graphql" , ` https://www.instagram.com/p/ ${ shortcode } /embed/captioned/ ` , async res => {
2020-02-02 11:43:56 +00:00
if ( res . status === 429 ) throw constants . symbols . RATE _LIMITED
2021-11-01 12:58:03 +00:00
} ) . then ( res => res . text ( ) ) . then ( text => {
2021-11-05 04:01:46 +00:00
let data = null
const match = text . match ( /window\.__additionalDataLoaded\('extra',(.*)\);<\/script>/ )
if ( match ) {
const textData = match [ 1 ]
data = JSON . parse ( textData )
}
2020-02-02 11:43:56 +00:00
if ( data == null ) {
2021-11-05 04:01:46 +00:00
// we have to actually parse the HTML to get the data
const root = fhp . parse ( text )
// Check if post really exists
if ( root . querySelector ( ".EmbedIsBroken" ) ) {
throw constants . symbols . NOT _FOUND
}
// find embed
const e _embed = root . querySelector ( ".Embed" )
2022-02-21 10:37:26 +00:00
// rate limited? if so, the request to instagram took 5-10 seconds, and returned no other content after <body style="background: white">
// so in that case there will be no useful elements, and no .Embed element
if ( ! e _embed ) {
throw constants . symbols . RATE _LIMITED
}
2021-11-05 04:01:46 +00:00
// find avatar
const e _avatar = root . querySelector ( ".Avatar" )
const e _avatarImage = e _avatar . querySelector ( "img" )
// find username
const e _usernameText = root . querySelector ( ".UsernameText" )
const e _viewProfile = root . querySelector ( ".ViewProfileButton" )
// find verified
const e _verified = root . querySelector ( ".VerifiedSprite" )
// find media
const e _media = root . querySelector ( ".EmbeddedMediaImage" )
// find caption
const e _caption = root . querySelector ( ".Caption" )
// extract owner
const owner = {
id : e _embed . attributes [ "data-owner-id" ] ,
is _verified : ! ! e _verified ,
profile _pic _url : e _avatarImage . attributes . src ,
username : e _viewProfile . attributes . href . replace ( new RegExp ( ` ^https: \/ \/ www \. instagram \. com \/ ( ${ constants . external . username _regex } ).* $ ` , "s" ) , "$1" )
}
// extract media type
let mediaType = e _embed . attributes [ "data-media-type" ]
const videoData = { }
if ( mediaType === "GraphVideo" ) {
Object . assign ( videoData , {
video _url : null ,
video _view _count : null
} )
} else {
mediaType = "GraphImage"
}
// extract display resources
const display _resources = e _media . attributes . srcset . split ( "," ) . map ( source => {
source = source . trim ( )
const [ url , widthString ] = source . split ( " " )
const width = + widthString . match ( /\d+/ ) [ 0 ]
return {
src : url ,
config _width : width ,
config _height : width // best guess!
}
} )
// extract caption text
2022-02-21 10:37:26 +00:00
let captionText = ""
if ( e _caption ) {
captionText = e _caption . childNodes . slice ( 4 , - 3 ) . map ( node => { // slice removes unneeded starting and ending whitespace and user handles
if ( node . tagName === "br" ) {
return "\n"
} else {
return node . text
}
} ) . join ( "" )
}
2021-11-05 04:01:46 +00:00
return {
_ _typename : mediaType ,
id : e _embed . attributes [ "data-media-id" ] ,
display _url : e _media . attributes . src ,
display _resources ,
is _video : mediaType === "GraphVideo" ,
shortcode ,
accessibility _caption : e _media . attributes . alt ,
... videoData ,
owner ,
edge _media _to _caption : {
edges : [
{
node : {
text : captionText
}
}
]
}
}
2020-01-27 06:03:28 +00:00
} else {
2021-11-01 12:58:03 +00:00
data = data . shortcode _media
2020-02-02 11:43:56 +00:00
history . report ( "post" , true )
if ( constants . caching . db _post _n3 ) {
db . prepare ( "REPLACE INTO Posts (shortcode, id, id_as_numeric, username, json) VALUES (@shortcode, @id, @id_as_numeric, @username, @json)" )
. run ( { shortcode : data . shortcode , id : data . id , id _as _numeric : data . id , username : data . owner . username , json : JSON . stringify ( data ) } )
2020-01-30 12:51:59 +00:00
}
2020-02-02 13:24:14 +00:00
// if we have the owner but only a reelUser, update it. this code is gross.
2020-02-02 14:53:37 +00:00
if ( userRequestCache . hasNotPromise ( "user/" + data . owner . username ) ) {
const user = userRequestCache . getWithoutClean ( "user/" + data . owner . username )
2020-02-02 13:24:14 +00:00
if ( user . fromReel ) {
user . data . full _name = data . owner . full _name
user . data . is _verified = data . owner . is _verified
}
}
2020-02-02 11:43:56 +00:00
return data
}
} ) . catch ( error => {
2021-11-01 12:58:03 +00:00
if ( error === constants . symbols . RATE _LIMITED ) {
2021-01-30 06:40:20 +00:00
history . report ( "post" , false , error )
2020-01-27 06:03:28 +00:00
}
2020-02-02 11:43:56 +00:00
throw error
2020-01-18 15:38:14 +00:00
} )
} )
}
2020-01-12 12:50:21 +00:00
module . exports . fetchUser = fetchUser
module . exports . fetchTimelinePage = fetchTimelinePage
2020-06-24 14:58:01 +00:00
module . exports . fetchIGTVPage = fetchIGTVPage
2020-01-26 14:56:59 +00:00
module . exports . getOrCreateShortcode = getOrCreateShortcode
module . exports . fetchShortcodeData = fetchShortcodeData
2020-07-07 10:08:19 +00:00
module . exports . requestCache = requestCache
2020-02-02 14:53:37 +00:00
module . exports . userRequestCache = userRequestCache
2020-01-26 14:56:59 +00:00
module . exports . timelineEntryCache = timelineEntryCache
module . exports . getOrFetchShortcode = getOrFetchShortcode
2020-04-04 14:57:31 +00:00
module . exports . updateProfilePictureFromReel = updateProfilePictureFromReel
2020-01-30 12:51:59 +00:00
module . exports . history = history
2020-04-07 06:30:00 +00:00
module . exports . assistantSwitcher = assistantSwitcher
2020-06-11 16:09:28 +00:00
module . exports . verifyUserPair = verifyUserPair