Add experimental assistant feature
This commit is contained in:
@@ -14,6 +14,9 @@ const userRequestCache = new UserRequestCache(constants.caching.resource_cache_t
|
||||
const timelineEntryCache = new TtlCache(constants.caching.resource_cache_time)
|
||||
const history = new RequestHistory(["user", "timeline", "post", "reel"])
|
||||
|
||||
const AssistantSwitcher = require("./structures/AssistantSwitcher")
|
||||
const assistantSwitcher = new AssistantSwitcher()
|
||||
|
||||
/**
|
||||
* @param {string} username
|
||||
* @param {boolean} isRSS
|
||||
@@ -53,6 +56,11 @@ async function fetchUser(username, isRSS) {
|
||||
return fetchUserFromCombined(saved.user_id, username)
|
||||
} else if (saved && saved.updated_version >= 2) {
|
||||
return fetchUserFromSaved(saved)
|
||||
} else if (assistantSwitcher.enabled()) {
|
||||
return assistantSwitcher.requestUser(username).catch(error => {
|
||||
if (error === constants.symbols.NO_ASSISTANTS_AVAILABLE) throw constants.symbols.RATE_LIMITED
|
||||
else throw error
|
||||
})
|
||||
}
|
||||
}
|
||||
throw error
|
||||
@@ -332,3 +340,5 @@ module.exports.timelineEntryCache = timelineEntryCache
|
||||
module.exports.getOrFetchShortcode = getOrFetchShortcode
|
||||
module.exports.updateProfilePictureFromReel = updateProfilePictureFromReel
|
||||
module.exports.history = history
|
||||
module.exports.fetchUserFromSaved = fetchUserFromSaved
|
||||
module.exports.assistantSwitcher = assistantSwitcher
|
||||
|
||||
@@ -40,6 +40,15 @@ let constants = {
|
||||
enable_updater_page: false
|
||||
},
|
||||
|
||||
assistant: {
|
||||
enabled: false,
|
||||
// List of assistant origin URLs, if you have any.
|
||||
origins: [
|
||||
],
|
||||
offline_request_cooldown: 20*60*1000,
|
||||
blocked_request_cooldown: 2*60*60*1000,
|
||||
},
|
||||
|
||||
caching: {
|
||||
image_cache_control: `public, max-age=${7*24*60*60}`,
|
||||
resource_cache_time: 30*60*1000,
|
||||
@@ -78,7 +87,14 @@ let constants = {
|
||||
NO_SHARED_DATA: Symbol("NO_SHARED_DATA"),
|
||||
INSTAGRAM_DEMANDS_LOGIN: Symbol("INSTAGRAM_DEMANDS_LOGIN"),
|
||||
RATE_LIMITED: Symbol("RATE_LIMITED"),
|
||||
ENDPOINT_OVERRIDDEN: Symbol("ENDPOINT_OVERRIDDEN")
|
||||
ENDPOINT_OVERRIDDEN: Symbol("ENDPOINT_OVERRIDDEN"),
|
||||
NO_ASSISTANTS_AVAILABLE: Symbol("NO_ASSISTANTS_AVAILABLE"),
|
||||
assistant_statuses: {
|
||||
OFFLINE: Symbol("OFFLINE"),
|
||||
BLOCKED: Symbol("BLOCKED"),
|
||||
OK: Symbol("OK"),
|
||||
NONE: Symbol("NONE")
|
||||
}
|
||||
},
|
||||
|
||||
database_version: 2
|
||||
|
||||
42
src/lib/structures/Assistant.js
Normal file
42
src/lib/structures/Assistant.js
Normal file
@@ -0,0 +1,42 @@
|
||||
const {request} = require("../utils/request")
|
||||
const constants = require("../constants")
|
||||
|
||||
class Assistant {
|
||||
constructor(origin) {
|
||||
this.origin = origin
|
||||
this.lastRequest = 0
|
||||
this.lastRequestStatus = constants.symbols.assistant_statuses.NONE
|
||||
}
|
||||
|
||||
available() {
|
||||
if (this.lastRequestStatus === constants.symbols.assistant_statuses.OFFLINE) {
|
||||
return Date.now() - this.lastRequest > constants.assistant.offline_request_cooldown
|
||||
} else if (this.lastRequestStatus === constants.symbols.assistant_statuses.BLOCKED) {
|
||||
return Date.now() - this.lastRequest > constants.assistant.blocked_request_cooldown
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
requestUser(username) {
|
||||
this.lastRequest = Date.now()
|
||||
return new Promise((resolve, reject) => {
|
||||
request(`${this.origin}/api/user/v1/${username}`).json().then(root => {
|
||||
console.log(root)
|
||||
if (root.status === "ok") {
|
||||
this.lastRequestStatus = constants.symbols.assistant_statuses.OK
|
||||
resolve(root.data.user)
|
||||
} else {
|
||||
this.lastRequestStatus = constants.symbols.assistant_statuses.BLOCKED
|
||||
reject(constants.symbols.assistant_statuses.BLOCKED)
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error(error)
|
||||
this.lastRequestStatus = constants.symbols.assistant_statuses.OFFLINE
|
||||
reject(constants.symbols.assistant_statuses.OFFLINE)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Assistant
|
||||
49
src/lib/structures/AssistantSwitcher.js
Normal file
49
src/lib/structures/AssistantSwitcher.js
Normal file
@@ -0,0 +1,49 @@
|
||||
const constants = require("../constants")
|
||||
const collectors = require("../collectors")
|
||||
const Assistant = require("./Assistant")
|
||||
const db = require("../db")
|
||||
|
||||
class AssistantSwitcher {
|
||||
constructor() {
|
||||
this.assistants = constants.assistant.origins.map(origin => new Assistant(origin))
|
||||
}
|
||||
|
||||
enabled() {
|
||||
return constants.assistant.enabled && this.assistants.length
|
||||
}
|
||||
|
||||
getAvailableAssistants() {
|
||||
return this.assistants.filter(assistant => assistant.available()).sort((a, b) => (a.lastRequest - b.lastRequest))
|
||||
}
|
||||
|
||||
requestUser(username) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const assistants = this.getAvailableAssistants()
|
||||
while (assistants.length) {
|
||||
const assistant = assistants.shift()
|
||||
try {
|
||||
const user = await assistant.requestUser(username)
|
||||
return resolve(user)
|
||||
} catch (e) {
|
||||
// that assistant broke. try the next one.
|
||||
}
|
||||
}
|
||||
return reject(constants.symbols.NO_ASSISTANTS_AVAILABLE)
|
||||
}).then(user => {
|
||||
const bind = {...user}
|
||||
bind.created = Date.now()
|
||||
bind.updated = Date.now()
|
||||
bind.updated_version = constants.database_version
|
||||
bind.is_private = +user.is_private
|
||||
bind.is_verified = +user.is_verified
|
||||
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(bind)
|
||||
collectors.userRequestCache.cache.delete(`user/${username}`)
|
||||
return collectors.fetchUserFromSaved(user)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AssistantSwitcher
|
||||
Reference in New Issue
Block a user