1
0
mirror of https://git.sr.ht/~cadence/bibliogram synced 2024-11-22 16:17:29 +00:00
This commit is contained in:
Cadence Ember 2020-06-25 02:58:01 +12:00
parent 865e3b0778
commit a023e09743
No known key found for this signature in database
GPG Key ID: 128B99B1B74A6412
12 changed files with 193 additions and 111 deletions

View File

@ -12,7 +12,7 @@ const requestCache = new RequestCache(constants.caching.resource_cache_time)
const userRequestCache = new UserRequestCache(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", "igtv", "post", "reel"])
const AssistantSwitcher = require("./structures/AssistantSwitcher") const AssistantSwitcher = require("./structures/AssistantSwitcher")
const assistantSwitcher = new AssistantSwitcher() const assistantSwitcher = new AssistantSwitcher()
@ -306,12 +306,12 @@ function fetchIGTVPage(userID, after) {
if (res.status === 429) throw constants.symbols.RATE_LIMITED if (res.status === 429) throw constants.symbols.RATE_LIMITED
}).then(g => g.json()).then(root => { }).then(g => g.json()).then(root => {
/** @type {import("./types").PagedEdges<import("./types").TimelineEntryN2>} */ /** @type {import("./types").PagedEdges<import("./types").TimelineEntryN2>} */
const timeline = root.data.user.edge_owner_to_timeline_media const timeline = root.data.user.edge_felix_video_timeline
history.report("timeline", true) history.report("igtv", true)
return timeline return timeline
}).catch(error => { }).catch(error => {
if (error === constants.symbols.RATE_LIMITED) { if (error === constants.symbols.RATE_LIMITED) {
history.report("timeline", false) history.report("igtv", false)
} }
throw error throw error
}) })
@ -422,6 +422,7 @@ function fetchShortcodeData(shortcode) {
module.exports.fetchUser = fetchUser module.exports.fetchUser = fetchUser
module.exports.fetchTimelinePage = fetchTimelinePage module.exports.fetchTimelinePage = fetchTimelinePage
module.exports.fetchIGTVPage = fetchIGTVPage
module.exports.getOrCreateShortcode = getOrCreateShortcode module.exports.getOrCreateShortcode = getOrCreateShortcode
module.exports.fetchShortcodeData = fetchShortcodeData module.exports.fetchShortcodeData = fetchShortcodeData
module.exports.userRequestCache = userRequestCache module.exports.userRequestCache = userRequestCache

View File

@ -12,8 +12,8 @@ class ReelUser extends BaseUser {
this.posts = 0 this.posts = 0
this.following = data.edge_follow ? data.edge_follow.count : 0 this.following = data.edge_follow ? data.edge_follow.count : 0
this.followedBy = data.edge_followed_by ? data.edge_followed_by.count : 0 this.followedBy = data.edge_followed_by ? data.edge_followed_by.count : 0
/** @type {import("./Timeline")} */ this.timeline = new Timeline(this, "timeline")
this.timeline = new Timeline(this) this.igtv = new Timeline(this, "igtv")
this.cachedAt = Date.now() this.cachedAt = Date.now()
this.computeProxyProfilePic() this.computeProxyProfilePic()
} }

View File

@ -21,9 +21,12 @@ function transformEdges(edges) {
class Timeline { class Timeline {
/** /**
* @param {import("./User")|import("./ReelUser")} user * @param {import("./User")|import("./ReelUser")} user
* @param {string} type
*/ */
constructor(user) { constructor(user, type) {
this.user = user this.user = user
/** one of: "timeline", "igtv" */
this.type = type
/** @type {import("./TimelineEntry")[][]} */ /** @type {import("./TimelineEntry")[][]} */
this.pages = [] this.pages = []
if (this.user.data.edge_owner_to_timeline_media) { if (this.user.data.edge_owner_to_timeline_media) {
@ -32,12 +35,17 @@ class Timeline {
} }
hasNextPage() { hasNextPage() {
return this.page_info.has_next_page return !this.page_info || this.page_info.has_next_page
} }
fetchNextPage() { fetchNextPage() {
if (!this.hasNextPage()) return constants.symbols.NO_MORE_PAGES if (!this.hasNextPage()) return constants.symbols.NO_MORE_PAGES
return collectors.fetchTimelinePage(this.user.data.id, this.page_info.end_cursor).then(page => { const method =
this.type === "timeline" ? collectors.fetchTimelinePage
: this.type === "igtv" ? collectors.fetchIGTVPage
: null
const after = this.page_info ? this.page_info.end_cursor : ""
return method(this.user.data.id, after).then(page => {
this.addPage(page) this.addPage(page)
return this.pages.slice(-1)[0] return this.pages.slice(-1)[0]
}) })

View File

@ -172,13 +172,23 @@ class TimelineEntry extends TimelineBaseMethods {
config_height: found.config_height, config_height: found.config_height,
src: proxyImage(found.src, found.config_width) // force resize to config rather than requested src: proxyImage(found.src, found.config_width) // force resize to config rather than requested
} }
} else if (this.data.thumbnail_src) {
return {
config_width: size, // probably?
config_height: size,
src: proxyImage(this.data.thumbnail_src, size) // force resize to requested
}
} else { } else {
return null return null
} }
} }
getThumbnailSizes() { getThumbnailSizes() {
if (this.data.thumbnail_resources) {
return `(max-width: 820px) 200px, 260px` // from css :( return `(max-width: 820px) 200px, 260px` // from css :(
} else {
return null
}
} }
async fetchChildren() { async fetchChildren() {

View File

@ -13,7 +13,8 @@ class User extends BaseUser {
this.following = data.edge_follow.count this.following = data.edge_follow.count
this.followedBy = data.edge_followed_by.count this.followedBy = data.edge_followed_by.count
this.posts = data.edge_owner_to_timeline_media.count this.posts = data.edge_owner_to_timeline_media.count
this.timeline = new Timeline(this) this.timeline = new Timeline(this, "timeline")
this.igtv = new Timeline(this, "igtv")
this.cachedAt = Date.now() this.cachedAt = Date.now()
this.computeProxyProfilePic() this.computeProxyProfilePic()
} }

View File

@ -65,22 +65,27 @@ module.exports = [
} }
}, },
{ {
route: `/u/(${constants.external.username_regex})`, methods: ["GET"], code: ({req, url, fill}) => { route: `/u/(${constants.external.username_regex})(/channel)?`, methods: ["GET"], code: ({req, url, fill}) => {
if (fill[0] !== fill[0].toLowerCase()) { // some capital letters const username = fill[0]
return Promise.resolve(redirect(`/u/${fill[0].toLowerCase()}`, 301)) const type = fill[1] ? "igtv" : "timeline"
if (username !== username.toLowerCase()) { // some capital letters
return Promise.resolve(redirect(`/u/${username.toLowerCase()}`, 301))
} }
const settings = getSettings(req) const settings = getSettings(req)
const params = url.searchParams const params = url.searchParams
return fetchUser(fill[0]).then(async user => { return fetchUser(username).then(async user => {
const page = +params.get("page") const selectedTimeline = user[type]
if (typeof page === "number" && !isNaN(page) && page >= 1) { let pageNumber = +params.get("page")
await user.timeline.fetchUpToPage(page - 1) if (isNaN(pageNumber) || pageNumber < 1) pageNumber = 1
} await selectedTimeline.fetchUpToPage(pageNumber - 1)
const followerCountsAvailable = !(user.constructor.name === "ReelUser" && user.following === 0 && user.followedBy === 0) const followerCountsAvailable = !(user.constructor.name === "ReelUser" && user.following === 0 && user.followedBy === 0)
return render(200, "pug/user.pug", { return render(200, "pug/user.pug", {
url, url,
user, user,
selectedTimeline,
type,
followerCountsAvailable, followerCountsAvailable,
constants, constants,
settings, settings,
@ -100,12 +105,12 @@ module.exports = [
statusCode: 503, statusCode: 503,
contentType: "text/html", contentType: "text/html",
headers: { headers: {
"Retry-After": userRequestCache.getTtl("user/"+fill[0], 1000) "Retry-After": userRequestCache.getTtl("user/"+username, 1000)
}, },
content: pugCache.get("pug/blocked.pug").web({ content: pugCache.get("pug/blocked.pug").web({
website_origin: constants.website_origin, website_origin: constants.website_origin,
username: fill[0], username,
expiresMinutes: userRequestCache.getTtl("user/"+fill[0], 1000*60), expiresMinutes: userRequestCache.getTtl("user/"+username, 1000*60),
getStaticURL, getStaticURL,
settings settings
}) })
@ -120,13 +125,25 @@ module.exports = [
}, },
{ {
route: `/fragment/user/(${constants.external.username_regex})/(\\d+)`, methods: ["GET"], code: async ({req, url, fill}) => { route: `/fragment/user/(${constants.external.username_regex})/(\\d+)`, methods: ["GET"], code: async ({req, url, fill}) => {
const username = fill[0]
let pageNumber = +fill[1]
if (isNaN(pageNumber) || pageNumber < 1) {
return {
statusCode: 400,
contentType: "text/html",
content: "Bad page number"
}
}
let type = url.searchParams.get("type")
if (!["timeline", "igtv"].includes(type)) type = "timeline"
const settings = getSettings(req) const settings = getSettings(req)
return fetchUser(fill[0]).then(async user => { return fetchUser(username).then(async user => {
const pageNumber = +fill[1]
const pageIndex = pageNumber - 1 const pageIndex = pageNumber - 1
await user.timeline.fetchUpToPage(pageIndex) const selectedTimeline = user[type]
if (user.timeline.pages[pageIndex]) { await selectedTimeline.fetchUpToPage(pageIndex)
return render(200, "pug/fragments/timeline_page.pug", {page: user.timeline.pages[pageIndex], pageIndex, user, url, settings}) if (selectedTimeline.pages[pageIndex]) {
return render(200, "pug/fragments/timeline_page.pug", {page: selectedTimeline.pages[pageIndex], selectedTimeline, type, pageIndex, user, url, settings})
} else { } else {
return { return {
statusCode: 400, statusCode: 400,

View File

@ -75,8 +75,9 @@ class NextPage extends FreezeWidth {
if (this.fetching) return if (this.fetching) return
this.fetching = true this.fetching = true
this.freeze("Loading...") this.freeze("Loading...")
const type = this.element.getAttribute("data-type")
return fetch(`/fragment/user/${this.element.getAttribute("data-username")}/${this.nextPageNumber}`).then(res => res.text()).then(text => { return fetch(`/fragment/user/${this.element.getAttribute("data-username")}/${this.nextPageNumber}?type=${type}`).then(res => res.text()).then(text => {
q("#next-page-container").remove() q("#next-page-container").remove()
this.observer.disconnect() this.observer.disconnect()
q("#timeline").insertAdjacentHTML("beforeend", text) q("#timeline").insertAdjacentHTML("beforeend", text)

View File

@ -1,6 +1,8 @@
//- Needs user, selectedTimeline, url, type
include ../includes/timeline_page.pug include ../includes/timeline_page.pug
include ../includes/next_page_button.pug include ../includes/next_page_button.pug
+timeline_page(page, pageIndex) +timeline_page(page, pageIndex)
+next_page_button(user, url) +next_page_button(user, selectedTimeline, url, type)

View File

@ -1,10 +1,10 @@
mixin next_page_button(user, url) mixin next_page_button(user, selectedTimeline, url, type)
if user.timeline.hasNextPage() if selectedTimeline.hasNextPage()
div.next-page-container#next-page-container div.next-page-container#next-page-container
- -
const nu = new URL(url) const nu = new URL(url)
nu.searchParams.set("page", user.timeline.pages.length+1) nu.searchParams.set("page", selectedTimeline.pages.length+1)
a(href=`${nu.search}#page-${user.timeline.pages.length+1}` data-page=(user.timeline.pages.length+1) data-username=(user.data.username))#next-page.next-page Next page a(href=`${nu.search}#page-${selectedTimeline.pages.length+1}` data-page=(selectedTimeline.pages.length+1) data-username=(user.data.username) data-type=type)#next-page.next-page Next page
else else
div.page-number.no-more-pages div.page-number.no-more-pages
span.number No more posts. span.number No more posts.

View File

@ -1,4 +1,4 @@
//- Needs user, followerCountsAvailable, url, constants, settings //- Needs user, selectedTimeline, type, followerCountsAvailable, url, constants, settings
include includes/timeline_page.pug include includes/timeline_page.pug
include includes/next_page_button.pug include includes/next_page_button.pug
@ -7,6 +7,9 @@ include includes/feed_link
- const numberFormat = new Intl.NumberFormat().format - const numberFormat = new Intl.NumberFormat().format
mixin selector-button(text, selectorType, urlSuffix)
a(href=(type !== selectorType && `/u/${user.data.username}${urlSuffix}`) class=(type === selectorType && "active")).selector= text
doctype html doctype html
html html
head head
@ -77,12 +80,18 @@ html
a(href="/") Home a(href="/") Home
a(href=settingsReferrer) Settings a(href=settingsReferrer) Settings
- const hasPosts = !user.data.is_private && user.timeline.pages.length && user.timeline.pages[0].length - const hasPosts = !user.data.is_private && selectedTimeline.pages.length && selectedTimeline.pages[0].length
.timeline-section
.selector-container
+selector-button("Timeline", "timeline", "")
if user.data.has_channel !== false
+selector-button("IGTV", "igtv", "/channel")
main(class=hasPosts ? "" : "no-posts")#timeline.timeline main(class=hasPosts ? "" : "no-posts")#timeline.timeline
if hasPosts if hasPosts
each page, pageIndex in user.timeline.pages each page, pageIndex in selectedTimeline.pages
+timeline_page(page, pageIndex) +timeline_page(page, pageIndex)
+next_page_button(user, url) +next_page_button(user, selectedTimeline, url, type)
else else
div div
div.page-number div.page-number

View File

@ -158,25 +158,41 @@ body
@media screen and (max-width: $layout-a-max) @media screen and (max-width: $layout-a-max)
display: none display: none
.timeline .timeline-section
--image-size: 260px
$image-size: var(--image-size)
@media screen and (max-width: $layout-a-max)
--image-size: 150px
flex: 1
@media screen and (max-width: $layout-c-max)
--image-size: calc(33vw - 10px)
background-color: map-get($theme, "background-primary") background-color: map-get($theme, "background-primary")
padding: 15px 15px 40px padding: 0px 15px 40px
&.no-posts .selector-container
padding: 15px
display: flex display: flex
flex-direction: column
justify-content: center justify-content: center
.selector
background-color: map-get($theme, "background-primary")
color: map-get($theme, "foreground-primary")
text-decoration: none
padding: 10px 10px 13px
line-height: 1
font-size: 22px
border: 1px solid transparent
border-bottom: 1px solid map-get($theme, "foreground-timeline-page")
margin: 0px 10px
box-shadow: map-get($theme, "shadow-down-only")
border-radius: 5px
&:hover, &:focus
border: 1px solid map-get($theme, "foreground-timeline-page")
&.active
background-color: map-get($theme, "background-power-primary")
color: map-get($theme, "foreground-power-primary")
cursor: default
border: 1px solid map-get($theme, "foreground-timeline-page")
&:hover, &:focus, &.active
padding-bottom: 10px
border-bottom: 4px solid map-get($theme, "foreground-primary")
.page-number .page-number
color: map-get($theme, "foreground-timeline-page") color: map-get($theme, "foreground-timeline-page")
line-height: 1 line-height: 1
@ -201,6 +217,22 @@ body
padding: 10px padding: 10px
background-color: map-get($theme, "background-primary") background-color: map-get($theme, "background-primary")
.timeline
--image-size: 260px
$image-size: var(--image-size)
@media screen and (max-width: $layout-a-max)
--image-size: 150px
flex: 1
@media screen and (max-width: $layout-c-max)
--image-size: calc(33vw - 10px)
&.no-posts
display: flex
flex-direction: column
justify-content: center
.next-page-container .next-page-container
margin: 20px 0px margin: 20px 0px
display: flex display: flex

View File

@ -54,4 +54,5 @@ $theme: (
"shadow-down": 0px -2px 4px 4px rgba(0, 0, 0, 0.4), "shadow-down": 0px -2px 4px 4px rgba(0, 0, 0, 0.4),
"shadow-right": -2px 0px 4px 4px rgba(0, 0, 0, 0.4), "shadow-right": -2px 0px 4px 4px rgba(0, 0, 0, 0.4),
"shadow-down-inset": 0px 6px 4px -4px rgba(0, 0, 0, 0.4) inset, "shadow-down-inset": 0px 6px 4px -4px rgba(0, 0, 0, 0.4) inset,
"shadow-down-only": 0px 2px 4px 1px rgba(0, 0, 0, 0.3)
); );