mirror of
https://git.sr.ht/~cadence/bibliogram
synced 2024-11-23 00:27:30 +00:00
Hopefully the final assistants changes
This commit is contained in:
parent
dc91575e1c
commit
41cbffa95a
@ -19,14 +19,26 @@ const assistantSwitcher = new AssistantSwitcher()
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} username
|
* @param {string} username
|
||||||
* @param {boolean} isRSS
|
* @param {symbol} [context]
|
||||||
*/
|
*/
|
||||||
async function fetchUser(username, isRSS) {
|
async function fetchUser(username, context) {
|
||||||
|
if (constants.external.reserved_paths.includes(username)) {
|
||||||
|
throw constants.symbols.ENDPOINT_OVERRIDDEN
|
||||||
|
}
|
||||||
|
|
||||||
let mode = constants.allow_user_from_reel
|
let mode = constants.allow_user_from_reel
|
||||||
if (mode === "preferForRSS") {
|
if (mode === "preferForRSS") {
|
||||||
if (isRSS) mode = "prefer"
|
if (context === constants.symbols.fetch_context.RSS) mode = "prefer"
|
||||||
else mode = "onlyPreferSaved"
|
else mode = "onlyPreferSaved"
|
||||||
}
|
}
|
||||||
|
if (context === constants.symbols.fetch_context.ASSISTANT) {
|
||||||
|
const saved = db.prepare("SELECT username, user_id, updated_version, biography, post_count, following_count, followed_by_count, external_url, full_name, is_private, is_verified, profile_pic_url FROM Users WHERE username = ?").get(username)
|
||||||
|
if (saved && saved.updated_version >= 2) {
|
||||||
|
return fetchUserFromSaved(saved)
|
||||||
|
} else {
|
||||||
|
return fetchUserFromHTML(username)
|
||||||
|
}
|
||||||
|
}
|
||||||
if (mode === "never") {
|
if (mode === "never") {
|
||||||
return fetchUserFromHTML(username)
|
return fetchUserFromHTML(username)
|
||||||
}
|
}
|
||||||
|
@ -40,15 +40,23 @@ let constants = {
|
|||||||
enable_updater_page: false
|
enable_updater_page: false
|
||||||
},
|
},
|
||||||
|
|
||||||
assistant: {
|
use_assistant: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
// List of assistant origin URLs, if you have any.
|
// Read the docs.
|
||||||
origins: [
|
assistants: [
|
||||||
],
|
],
|
||||||
offline_request_cooldown: 20*60*1000,
|
offline_request_cooldown: 20*60*1000,
|
||||||
blocked_request_cooldown: 2*60*60*1000,
|
blocked_request_cooldown: 2*60*60*1000,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
as_assistant: {
|
||||||
|
enabled: false, // You can still start just the assistant with npm run assistant.
|
||||||
|
require_key: false,
|
||||||
|
// List of keys that are allowed access. You can use any string.
|
||||||
|
// Try `crypto.randomBytes(20).toString("hex")` to get some randomness.
|
||||||
|
keys: []
|
||||||
|
},
|
||||||
|
|
||||||
caching: {
|
caching: {
|
||||||
image_cache_control: `public, max-age=${7*24*60*60}`,
|
image_cache_control: `public, max-age=${7*24*60*60}`,
|
||||||
resource_cache_time: 30*60*1000,
|
resource_cache_time: 30*60*1000,
|
||||||
@ -68,7 +76,15 @@ let constants = {
|
|||||||
timeline_fetch_first: 12,
|
timeline_fetch_first: 12,
|
||||||
username_regex: "[\\w.]*[\\w]",
|
username_regex: "[\\w.]*[\\w]",
|
||||||
shortcode_regex: "[\\w-]+",
|
shortcode_regex: "[\\w-]+",
|
||||||
hashtag_regex: "[^ \\n`~!@#\\$%^&*()\\-=+[\\]{};:\"',<.>/?\\\\]+"
|
hashtag_regex: "[^ \\n`~!@#\\$%^&*()\\-=+[\\]{};:\"',<.>/?\\\\]+",
|
||||||
|
reserved_paths: [ // https://github.com/cloudrac3r/bibliogram/wiki/Reserved-URLs
|
||||||
|
// Redirects
|
||||||
|
"about", "explore", "support", "press", "api", "privacy", "safety", "admin",
|
||||||
|
// Content
|
||||||
|
"embed.js",
|
||||||
|
// Not found, but likely reserved
|
||||||
|
"graphql", "accounts", "p", "help", "terms", "contact", "blog", "igtv"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
resources: {
|
resources: {
|
||||||
@ -93,7 +109,12 @@ let constants = {
|
|||||||
OFFLINE: Symbol("OFFLINE"),
|
OFFLINE: Symbol("OFFLINE"),
|
||||||
BLOCKED: Symbol("BLOCKED"),
|
BLOCKED: Symbol("BLOCKED"),
|
||||||
OK: Symbol("OK"),
|
OK: Symbol("OK"),
|
||||||
NONE: Symbol("NONE")
|
NONE: Symbol("NONE"),
|
||||||
|
NOT_AUTHENTICATED: Symbol("NOT_AUTHENTICATED")
|
||||||
|
},
|
||||||
|
fetch_context: {
|
||||||
|
RSS: Symbol("RSS"),
|
||||||
|
ASSISTANT: Symbol("ASSISTANT")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -2,17 +2,20 @@ const {request} = require("../utils/request")
|
|||||||
const constants = require("../constants")
|
const constants = require("../constants")
|
||||||
|
|
||||||
class Assistant {
|
class Assistant {
|
||||||
constructor(origin) {
|
constructor(origin, key) {
|
||||||
this.origin = origin
|
this.origin = origin
|
||||||
|
this.key = key
|
||||||
this.lastRequest = 0
|
this.lastRequest = 0
|
||||||
this.lastRequestStatus = constants.symbols.assistant_statuses.NONE
|
this.lastRequestStatus = constants.symbols.assistant_statuses.NONE
|
||||||
}
|
}
|
||||||
|
|
||||||
available() {
|
available() {
|
||||||
if (this.lastRequestStatus === constants.symbols.assistant_statuses.OFFLINE) {
|
if (this.lastRequestStatus === constants.symbols.assistant_statuses.OFFLINE) {
|
||||||
return Date.now() - this.lastRequest > constants.assistant.offline_request_cooldown
|
return Date.now() - this.lastRequest > constants.use_assistant.offline_request_cooldown
|
||||||
} else if (this.lastRequestStatus === constants.symbols.assistant_statuses.BLOCKED) {
|
} else if (this.lastRequestStatus === constants.symbols.assistant_statuses.BLOCKED) {
|
||||||
return Date.now() - this.lastRequest > constants.assistant.blocked_request_cooldown
|
return Date.now() - this.lastRequest > constants.use_assistant.blocked_request_cooldown
|
||||||
|
} else if (this.lastRequestStatus === constants.symbols.assistant_statuses.NOT_AUTHENTICATED) {
|
||||||
|
return false
|
||||||
} else {
|
} else {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -21,7 +24,9 @@ class Assistant {
|
|||||||
requestUser(username) {
|
requestUser(username) {
|
||||||
this.lastRequest = Date.now()
|
this.lastRequest = Date.now()
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
request(`${this.origin}/api/user/v1/${username}`).json().then(root => {
|
const url = new URL(`${this.origin}/api/user/v1/${username}`)
|
||||||
|
if (this.key !== null) url.searchParams.append("key", this.key)
|
||||||
|
request(url.toString()).json().then(root => {
|
||||||
// console.log(root)
|
// console.log(root)
|
||||||
if (root.status === "ok") {
|
if (root.status === "ok") {
|
||||||
this.lastRequestStatus = constants.symbols.assistant_statuses.OK
|
this.lastRequestStatus = constants.symbols.assistant_statuses.OK
|
||||||
@ -30,6 +35,9 @@ class Assistant {
|
|||||||
if (root.identifier === "NOT_FOUND") {
|
if (root.identifier === "NOT_FOUND") {
|
||||||
this.lastRequestStatus = constants.symbols.assistant_statuses.OK
|
this.lastRequestStatus = constants.symbols.assistant_statuses.OK
|
||||||
reject(constants.symbols.NOT_FOUND)
|
reject(constants.symbols.NOT_FOUND)
|
||||||
|
} else if (root.identifier === "NOT_AUTHENTICATED") {
|
||||||
|
this.lastRequestStatus = constants.symbols.assistant_statuses.NOT_AUTHENTICATED
|
||||||
|
reject(constants.symbols.assistant_statuses.NOT_AUTHENTICATED)
|
||||||
} else { // blocked
|
} else { // blocked
|
||||||
this.lastRequestStatus = constants.symbols.assistant_statuses.BLOCKED
|
this.lastRequestStatus = constants.symbols.assistant_statuses.BLOCKED
|
||||||
reject(constants.symbols.assistant_statuses.BLOCKED)
|
reject(constants.symbols.assistant_statuses.BLOCKED)
|
||||||
|
@ -5,11 +5,11 @@ const db = require("../db")
|
|||||||
|
|
||||||
class AssistantSwitcher {
|
class AssistantSwitcher {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.assistants = constants.assistant.origins.map(origin => new Assistant(origin))
|
this.assistants = constants.use_assistant.assistants.map(data => new Assistant(data.origin, data.key))
|
||||||
}
|
}
|
||||||
|
|
||||||
enabled() {
|
enabled() {
|
||||||
return constants.assistant.enabled && this.assistants.length
|
return constants.use_assistant.enabled && this.assistants.length
|
||||||
}
|
}
|
||||||
|
|
||||||
getAvailableAssistants() {
|
getAvailableAssistants() {
|
||||||
@ -30,6 +30,9 @@ class AssistantSwitcher {
|
|||||||
rejection.catch(() => {}) // otherwise we get a warning that the rejection was handled asynchronously
|
rejection.catch(() => {}) // otherwise we get a warning that the rejection was handled asynchronously
|
||||||
collectors.userRequestCache.set(`user/${username}`, false, rejection)
|
collectors.userRequestCache.set(`user/${username}`, false, rejection)
|
||||||
return reject(e)
|
return reject(e)
|
||||||
|
} else if (e === constants.symbols.assistant_statuses.NOT_AUTHENTICATED) {
|
||||||
|
// no further requests will be successful. the assistant has already marked itself as not available.
|
||||||
|
console.error(`Assistant ${assistant.origin} refused request, not authenticated`)
|
||||||
}
|
}
|
||||||
// that assistant broke. try the next one.
|
// that assistant broke. try the next one.
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ module.exports = [
|
|||||||
{route: `/u/(${constants.external.username_regex})/(rss|atom)\\.xml`, methods: ["GET"], code: ({fill}) => {
|
{route: `/u/(${constants.external.username_regex})/(rss|atom)\\.xml`, methods: ["GET"], code: ({fill}) => {
|
||||||
if (constants.settings.rss_enabled) {
|
if (constants.settings.rss_enabled) {
|
||||||
const kind = fill[1]
|
const kind = fill[1]
|
||||||
return fetchUser(fill[0], true).then(async user => {
|
return fetchUser(fill[0], constants.symbols.fetch_context.RSS).then(async user => {
|
||||||
const feed = await user.timeline.fetchFeed()
|
const feed = await user.timeline.fetchFeed()
|
||||||
if (kind === "rss") {
|
if (kind === "rss") {
|
||||||
var data = {
|
var data = {
|
||||||
|
@ -63,7 +63,7 @@ module.exports = [
|
|||||||
}
|
}
|
||||||
|
|
||||||
const params = url.searchParams
|
const params = url.searchParams
|
||||||
return fetchUser(fill[0], false).then(async user => {
|
return fetchUser(fill[0]).then(async user => {
|
||||||
const page = +params.get("page")
|
const page = +params.get("page")
|
||||||
if (typeof page === "number" && !isNaN(page) && page >= 1) {
|
if (typeof page === "number" && !isNaN(page) && page >= 1) {
|
||||||
await user.timeline.fetchUpToPage(page - 1)
|
await user.timeline.fetchUpToPage(page - 1)
|
||||||
@ -97,7 +97,7 @@ module.exports = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
route: `/fragment/user/(${constants.external.username_regex})/(\\d+)`, methods: ["GET"], code: async ({url, fill}) => {
|
route: `/fragment/user/(${constants.external.username_regex})/(\\d+)`, methods: ["GET"], code: async ({url, fill}) => {
|
||||||
return fetchUser(fill[0], false).then(async user => {
|
return fetchUser(fill[0]).then(async user => {
|
||||||
const pageNumber = +fill[1]
|
const pageNumber = +fill[1]
|
||||||
const pageIndex = pageNumber - 1
|
const pageIndex = pageNumber - 1
|
||||||
await user.timeline.fetchUpToPage(pageIndex)
|
await user.timeline.fetchUpToPage(pageIndex)
|
||||||
|
@ -29,11 +29,6 @@ const pinski = new Pinski({
|
|||||||
|
|
||||||
console.log("Assistant started")
|
console.log("Assistant started")
|
||||||
|
|
||||||
if (constants.allow_user_from_reel !== "never") {
|
|
||||||
constants.allow_user_from_reel = "never"
|
|
||||||
console.log(`[!] You are running the assistant, so \`constants.allow_user_from_reel\` has been set to "never" for this session.`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.stdin.isTTY || process.argv.includes("--enable-repl")) {
|
if (process.stdin.isTTY || process.argv.includes("--enable-repl")) {
|
||||||
require("./repl")
|
require("./repl")
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
const crypto = require("crypto")
|
||||||
const constants = require("../../lib/constants")
|
const constants = require("../../lib/constants")
|
||||||
const collectors = require("../../lib/collectors")
|
const collectors = require("../../lib/collectors")
|
||||||
const db = require("../../lib/db")
|
const db = require("../../lib/db")
|
||||||
@ -22,27 +23,49 @@ module.exports = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
route: `/api/user/v1/(${constants.external.username_regex})`, methods: ["GET"], code: async ({fill, url}) => {
|
route: `/api/user/v1/(${constants.external.username_regex})`, methods: ["GET"], code: async ({fill, url}) => {
|
||||||
function replyWithUserData(userData, type) {
|
function replyWithUserData(userData) {
|
||||||
return reply(200, {
|
return reply(200, {
|
||||||
status: "ok",
|
status: "ok",
|
||||||
version: "1.0",
|
version: "1.0",
|
||||||
generatedAt: Date.now(),
|
generatedAt: Date.now(),
|
||||||
data: {
|
data: {
|
||||||
type,
|
|
||||||
allow_user_from_reel: constants.allow_user_from_reel,
|
|
||||||
user: userData
|
user: userData
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (constants.as_assistant.require_key) {
|
||||||
|
if (url.searchParams.has("key")) {
|
||||||
|
const inputKey = url.searchParams.get("key")
|
||||||
|
if (!constants.as_assistant.keys.some(key => inputKey === key)) {
|
||||||
|
return reply(401, {
|
||||||
|
status: "fail",
|
||||||
|
version: "1.0",
|
||||||
|
generatedAt: Date.now(),
|
||||||
|
message: "The authentication key provided is not in the list of allowed keys.",
|
||||||
|
fields: ["q:key"],
|
||||||
|
identifier: "NOT_AUTHENTICATED"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return reply(401, {
|
||||||
|
status: "fail",
|
||||||
|
version: "1.0",
|
||||||
|
generatedAt: Date.now(),
|
||||||
|
message: "This endpoint requires authentication. If you have a key, specify it with the `key` query parameter.",
|
||||||
|
fields: ["q:key"],
|
||||||
|
identifier: "NOT_AUTHENTICATED"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const username = fill[0]
|
const username = fill[0]
|
||||||
const saved = db.prepare("SELECT username, user_id, updated_version, biography, post_count, following_count, followed_by_count, external_url, full_name, is_private, is_verified, profile_pic_url FROM Users WHERE username = ?").get(username)
|
const saved = db.prepare("SELECT username, user_id, updated_version, biography, post_count, following_count, followed_by_count, external_url, full_name, is_private, is_verified, profile_pic_url FROM Users WHERE username = ?").get(username)
|
||||||
if (saved && saved.updated_version >= 2) { // suitable data is already saved
|
if (saved && saved.updated_version >= 2) { // suitable data is already saved
|
||||||
delete saved.updated_version
|
delete saved.updated_version
|
||||||
return Promise.resolve(replyWithUserData(saved, "user"))
|
return Promise.resolve(replyWithUserData(saved))
|
||||||
} else {
|
} else {
|
||||||
return collectors.fetchUser(username, false).then(user => {
|
return collectors.fetchUser(username, constants.symbols.fetch_context.ASSISTANT).then(user => {
|
||||||
const type = user.constructor.name === "User" ? "user" : "reel"
|
|
||||||
return replyWithUserData({
|
return replyWithUserData({
|
||||||
username: user.data.username,
|
username: user.data.username,
|
||||||
user_id: user.data.id,
|
user_id: user.data.id,
|
||||||
@ -55,7 +78,7 @@ module.exports = [
|
|||||||
is_private: user.data.is_private,
|
is_private: user.data.is_private,
|
||||||
is_verified: user.data.is_verified,
|
is_verified: user.data.is_verified,
|
||||||
profile_pic_url: user.data.profile_pic_url
|
profile_pic_url: user.data.profile_pic_url
|
||||||
}, type)
|
})
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
if (error === constants.symbols.RATE_LIMITED || error === constants.symbols.INSTAGRAM_DEMANDS_LOGIN) {
|
if (error === constants.symbols.RATE_LIMITED || error === constants.symbols.INSTAGRAM_DEMANDS_LOGIN) {
|
||||||
return reply(503, {
|
return reply(503, {
|
||||||
|
@ -33,6 +33,12 @@ subdirs("pug", async (err, dirs) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pinski.addAPIDir("api")
|
pinski.addAPIDir("api")
|
||||||
|
|
||||||
|
if (constants.as_assistant.enabled) {
|
||||||
|
console.log("Assistant API enabled")
|
||||||
|
pinski.addAPIDir("assistant_api")
|
||||||
|
}
|
||||||
|
|
||||||
pinski.startServer()
|
pinski.startServer()
|
||||||
|
|
||||||
require("pinski/plugins").setInstance(pinski)
|
require("pinski/plugins").setInstance(pinski)
|
||||||
|
Loading…
Reference in New Issue
Block a user