1
0
mirror of https://git.sr.ht/~cadence/bibliogram synced 2024-11-22 16:17:29 +00:00

Add preferRSS setting

This commit is contained in:
Cadence Fish 2020-02-03 03:53:37 +13:00
parent c5d8b5788b
commit 6e136dc77a
No known key found for this signature in database
GPG Key ID: 81015DF9AA8607E1
7 changed files with 84 additions and 26 deletions

View File

@ -85,6 +85,10 @@ class TtlCache {
} }
} }
/**
* @extends TtlCache<T>
* @template T
*/
class RequestCache extends TtlCache { class RequestCache extends TtlCache {
/** /**
* @param {number} ttl time to keep each resource in milliseconds * @param {number} ttl time to keep each resource in milliseconds
@ -97,7 +101,6 @@ class RequestCache extends TtlCache {
* @param {string} key * @param {string} key
* @param {() => Promise<T>} callback * @param {() => Promise<T>} callback
* @returns {Promise<T>} * @returns {Promise<T>}
* @template T
*/ */
getOrFetch(key, callback) { getOrFetch(key, callback) {
this.cleanKey(key) this.cleanKey(key)
@ -116,7 +119,6 @@ class RequestCache extends TtlCache {
* @param {string} key * @param {string} key
* @param {() => Promise<T>} callback * @param {() => Promise<T>} callback
* @returns {Promise<T>} * @returns {Promise<T>}
* @template T
*/ */
getOrFetchPromise(key, callback) { getOrFetchPromise(key, callback) {
return this.getOrFetch(key, callback).then(result => { return this.getOrFetch(key, callback).then(result => {
@ -126,5 +128,56 @@ class RequestCache extends TtlCache {
} }
} }
/**
* @template T
*/
class UserRequestCache extends TtlCache {
constructor(ttl) {
super(ttl)
/** @type {Map<string, {data: T, isReel: boolean, isFailedPromise: boolean, htmlFailed: boolean, time: number}>} */
this.cache
}
/**
* @param {string} key
* @param {boolean} isReel
* @param {any} [data]
*/
set(key, isReel, data) {
const existing = this.cache.get(key)
// Preserve html failure status if now requesting as reel
const htmlFailed = isReel && existing && existing.htmlFailed
this.cache.set(key, {data, isReel, isFailedPromise: false, htmlFailed, time: Date.now()})
}
/**
* @param {string} key
* @param {boolean} isHtmlPreferred
* @param {boolean} willFetchReel
* @param {() => Promise<T>} callback
* @returns {Promise<T>}
*/
getOrFetch(key, willFetchReel, isHtmlPreferred, callback) {
this.cleanKey(key)
if (this.cache.has(key)) {
const existing = this.cache.get(key)
if ((!existing.isReel || !isHtmlPreferred || existing.htmlFailed) && !existing.isFailedPromise) return Promise.resolve(existing.data)
}
const pending = callback().then(result => {
if (this.getWithoutClean(key) === pending) { // if nothing has replaced the current cache in the meantime
this.set(key, willFetchReel, result)
}
return result
}).catch(error => {
this.cache.get(key).htmlFailed = true
this.cache.get(key).isFailedPromise = true
throw error
})
this.set(key, willFetchReel, pending)
return pending
}
}
module.exports.TtlCache = TtlCache module.exports.TtlCache = TtlCache
module.exports.RequestCache = RequestCache module.exports.RequestCache = RequestCache
module.exports.UserRequestCache = UserRequestCache

View File

@ -2,20 +2,26 @@ const constants = require("./constants")
const {request} = require("./utils/request") const {request} = require("./utils/request")
const switcher = require("./utils/torswitcher") const switcher = require("./utils/torswitcher")
const {extractSharedData} = require("./utils/body") const {extractSharedData} = require("./utils/body")
const {TtlCache, RequestCache} = require("./cache") const {TtlCache, RequestCache, UserRequestCache} = require("./cache")
const RequestHistory = require("./structures/RequestHistory") const RequestHistory = require("./structures/RequestHistory")
const db = require("./db") const db = require("./db")
require("./testimports")(constants, request, extractSharedData, RequestCache, RequestHistory) require("./testimports")(constants, request, extractSharedData, UserRequestCache, RequestHistory)
const requestCache = new RequestCache(constants.caching.resource_cache_time) const requestCache = new RequestCache(constants.caching.resource_cache_time)
const userRequestCache = new UserRequestCache(constants.caching.resource_cache_time)
/** @type {import("./cache").TtlCache<import("./structures/TimelineEntry")>} */ /** @type {import("./cache").TtlCache<import("./structures/TimelineEntry")>} */
const timelineEntryCache = new TtlCache(constants.caching.resource_cache_time) const timelineEntryCache = new TtlCache(constants.caching.resource_cache_time)
const history = new RequestHistory(["user", "timeline", "post", "reel"]) const history = new RequestHistory(["user", "timeline", "post", "reel"])
async function fetchUser(username) { async function fetchUser(username, isRSS) {
if (constants.allow_user_from_reel === "never") { let mode = constants.allow_user_from_reel
if (mode === "preferForRSS") {
if (isRSS) mode = "prefer"
else mode = "fallback"
}
if (mode === "never") {
return fetchUserFromHTML(username) return fetchUserFromHTML(username)
} else if (constants.allow_user_from_reel === "prefer") { } else if (mode === "prefer") {
const userID = db.prepare("SELECT user_id FROM Users WHERE username = ?").pluck().get(username) const userID = db.prepare("SELECT user_id FROM Users WHERE username = ?").pluck().get(username)
if (userID) return fetchUserFromCombined(userID, username) if (userID) return fetchUserFromCombined(userID, username)
else return fetchUserFromHTML(username) else return fetchUserFromHTML(username)
@ -24,7 +30,6 @@ async function fetchUser(username) {
if (error === constants.symbols.INSTAGRAM_DEMANDS_LOGIN || error === constants.symbols.RATE_LIMITED) { if (error === constants.symbols.INSTAGRAM_DEMANDS_LOGIN || error === constants.symbols.RATE_LIMITED) {
const userID = db.prepare("SELECT user_id FROM Users WHERE username = ?").pluck().get(username) const userID = db.prepare("SELECT user_id FROM Users WHERE username = ?").pluck().get(username)
if (userID) { if (userID) {
requestCache.cache.delete("user/"+username)
return fetchUserFromCombined(userID, username) return fetchUserFromCombined(userID, username)
} }
} }
@ -34,7 +39,7 @@ async function fetchUser(username) {
} }
function fetchUserFromHTML(username) { function fetchUserFromHTML(username) {
return requestCache.getOrFetch("user/"+username, () => { return userRequestCache.getOrFetch("user/"+username, false, true, () => {
return switcher.request("user_html", `https://www.instagram.com/${username}/`, async res => { return switcher.request("user_html", `https://www.instagram.com/${username}/`, async res => {
if (res.status === 302) throw constants.symbols.INSTAGRAM_DEMANDS_LOGIN if (res.status === 302) throw constants.symbols.INSTAGRAM_DEMANDS_LOGIN
if (res.status === 429) throw constants.symbols.RATE_LIMITED if (res.status === 429) throw constants.symbols.RATE_LIMITED
@ -74,7 +79,7 @@ function fetchUserFromCombined(userID, username) {
user_id: userID, user_id: userID,
include_reel: true include_reel: true
})) }))
return requestCache.getOrFetch("user/"+username, () => { return userRequestCache.getOrFetch("user/"+username, true, false, () => {
return switcher.request("reel_graphql", `https://www.instagram.com/graphql/query/?${p.toString()}`, async res => { return switcher.request("reel_graphql", `https://www.instagram.com/graphql/query/?${p.toString()}`, async res => {
if (res.status === 429) throw constants.symbols.RATE_LIMITED if (res.status === 429) throw constants.symbols.RATE_LIMITED
return res return res
@ -192,8 +197,8 @@ function fetchShortcodeData(shortcode) {
.run({shortcode: data.shortcode, id: data.id, id_as_numeric: data.id, username: data.owner.username, json: JSON.stringify(data)}) .run({shortcode: data.shortcode, id: data.id, id_as_numeric: data.id, username: data.owner.username, json: JSON.stringify(data)})
} }
// if we have the owner but only a reelUser, update it. this code is gross. // if we have the owner but only a reelUser, update it. this code is gross.
if (requestCache.hasNotPromise("user/"+data.owner.username)) { if (userRequestCache.hasNotPromise("user/"+data.owner.username)) {
const user = requestCache.getWithoutClean("user/"+data.owner.username) const user = userRequestCache.getWithoutClean("user/"+data.owner.username)
if (user.fromReel) { if (user.fromReel) {
user.data.full_name = data.owner.full_name user.data.full_name = data.owner.full_name
user.data.is_verified = data.owner.is_verified user.data.is_verified = data.owner.is_verified
@ -214,7 +219,7 @@ module.exports.fetchUser = fetchUser
module.exports.fetchTimelinePage = fetchTimelinePage module.exports.fetchTimelinePage = fetchTimelinePage
module.exports.getOrCreateShortcode = getOrCreateShortcode module.exports.getOrCreateShortcode = getOrCreateShortcode
module.exports.fetchShortcodeData = fetchShortcodeData module.exports.fetchShortcodeData = fetchShortcodeData
module.exports.requestCache = requestCache module.exports.userRequestCache = userRequestCache
module.exports.timelineEntryCache = timelineEntryCache module.exports.timelineEntryCache = timelineEntryCache
module.exports.getOrFetchShortcode = getOrFetchShortcode module.exports.getOrFetchShortcode = getOrFetchShortcode
module.exports.history = history module.exports.history = history

View File

@ -20,7 +20,7 @@ let constants = {
} }
}, },
allow_user_from_reel: "fallback", // one of: "never", "fallback", "prefer". allow_user_from_reel: "preferForRSS", // one of: "never", "fallback", "prefer", "preferForRSS"
settings: { settings: {
rss_enabled: true rss_enabled: true

View File

@ -180,9 +180,9 @@ class TimelineEntry extends TimelineBaseMethods {
} }
// The owner may be in the user cache, so copy from that. // The owner may be in the user cache, so copy from that.
// This could be implemented better. // This could be implemented better.
else if (collectors.requestCache.hasNotPromise("user/"+this.data.owner.username)) { else if (collectors.userRequestCache.hasNotPromise("user/"+this.data.owner.username)) {
/** @type {import("./User")} */ /** @type {import("./User")} */
const user = collectors.requestCache.getWithoutClean("user/"+this.data.owner.username) const user = collectors.userRequestCache.getWithoutClean("user/"+this.data.owner.username)
if (user.data.full_name) { if (user.data.full_name) {
this.data.owner = { this.data.owner = {
id: user.data.id, id: user.data.id,

View File

@ -1,12 +1,12 @@
const constants = require("../../lib/constants") const constants = require("../../lib/constants")
const {fetchUser, requestCache} = require("../../lib/collectors") const {fetchUser, userRequestCache} = require("../../lib/collectors")
const {render} = require("pinski/plugins") const {render} = require("pinski/plugins")
const {pugCache} = require("../passthrough") const {pugCache} = require("../passthrough")
module.exports = [ module.exports = [
{route: `/u/(${constants.external.username_regex})/rss.xml`, methods: ["GET"], code: ({fill}) => { {route: `/u/(${constants.external.username_regex})/rss.xml`, methods: ["GET"], code: ({fill}) => {
if (constants.settings.rss_enabled) { if (constants.settings.rss_enabled) {
return fetchUser(fill[0]).then(async user => { return fetchUser(fill[0], true).then(async user => {
const content = await user.timeline.fetchFeed() const content = await user.timeline.fetchFeed()
const xml = content.xml() const xml = content.xml()
return { return {
@ -27,10 +27,10 @@ module.exports = [
statusCode: 503, statusCode: 503,
contentType: "text/html", contentType: "text/html",
headers: { headers: {
"Retry-After": requestCache.getTtl("user/"+fill[0], 1000) "Retry-After": userRequestCache.getTtl("user/"+fill[0], 1000)
}, },
content: pugCache.get("pug/blocked.pug").web({ content: pugCache.get("pug/blocked.pug").web({
expiresMinutes: requestCache.getTtl("user/"+fill[0], 1000*60) expiresMinutes: userRequestCache.getTtl("user/"+fill[0], 1000*60)
}) })
} }
} else { } else {

View File

@ -1,5 +1,5 @@
const constants = require("../../lib/constants") const constants = require("../../lib/constants")
const {fetchUser, getOrFetchShortcode, requestCache, history} = require("../../lib/collectors") const {fetchUser, getOrFetchShortcode, userRequestCache, history} = require("../../lib/collectors")
const {render, redirect} = require("pinski/plugins") const {render, redirect} = require("pinski/plugins")
const {pugCache} = require("../passthrough") const {pugCache} = require("../passthrough")
@ -34,7 +34,7 @@ module.exports = [
{ {
route: `/u/(${constants.external.username_regex})`, methods: ["GET"], code: ({url, fill}) => { route: `/u/(${constants.external.username_regex})`, methods: ["GET"], code: ({url, fill}) => {
const params = url.searchParams const params = url.searchParams
return fetchUser(fill[0]).then(async user => { return fetchUser(fill[0], false).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)
@ -53,10 +53,10 @@ module.exports = [
statusCode: 503, statusCode: 503,
contentType: "text/html", contentType: "text/html",
headers: { headers: {
"Retry-After": requestCache.getTtl("user/"+fill[0], 1000) "Retry-After": userRequestCache.getTtl("user/"+fill[0], 1000)
}, },
content: pugCache.get("pug/blocked.pug").web({ content: pugCache.get("pug/blocked.pug").web({
expiresMinutes: requestCache.getTtl("user/"+fill[0], 1000*60) expiresMinutes: userRequestCache.getTtl("user/"+fill[0], 1000*60)
}) })
} }
} else { } else {
@ -67,7 +67,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]).then(async user => { return fetchUser(fill[0], false).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)

View File

@ -1,5 +1,5 @@
const {instance, pugCache, wss} = require("./passthrough") const {instance, pugCache, wss} = require("./passthrough")
const {requestCache, timelineEntryCache, history} = require("../lib/collectors") const {userRequestCache, timelineEntryCache, history} = require("../lib/collectors")
const constants = require("../lib/constants") const constants = require("../lib/constants")
const util = require("util") const util = require("util")
const repl = require("repl") const repl = require("repl")