Skip to content

Commit

Permalink
Merge pull request #59 from ccbikai/main
Browse files Browse the repository at this point in the history
  • Loading branch information
spencerwooo authored Jan 3, 2024
2 parents 9b2f4f7 + db56f72 commit e9599ed
Show file tree
Hide file tree
Showing 3 changed files with 5 additions and 87 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ Yes, `substats` is now version `v2.0-beta`! Most of the updates are under-the-ho
- [x] Updated to CloudFlare's module workers.
- [x] Worker is built with `esbuild` instead of `webpack`, extra fast!
- [x] Support for Newsblur has been deprecated ~~(seems nobody uses it)~~.
- [x] KV storages are now supported, some routes including `instagram` and `inoreader` depends on this for storing cookies (wip).
- [x] KV storages are now supported, some routes including `instagram` depends on this for storing cookies (wip).
- [x] Caching is ported to module workers in 2.0 and supported as always.
- [x] New documentation and query builder.

Expand Down
3 changes: 0 additions & 3 deletions worker/bindings.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ interface Env {
STEAM_API_KEY: string
TG_BOT_TOKEN: string
UNSPLASH_ACCESS_TOKEN: string
INOREADER_EMAIL: string
INOREADER_PASSWORD: string
INOREADER_COOKIE: string
INSTAGRAM_COOKIE: string

// Worker KV namespaces
Expand Down
87 changes: 4 additions & 83 deletions worker/src/providers/inoreader.ts
Original file line number Diff line number Diff line change
@@ -1,99 +1,20 @@
import type { SubstatsResponse } from '@/types'
import { providerErrorHandler } from '.'

// https%3A%2F%2Fnnw.ranchero.com%2Ffeed.xml
type InoreaderRawResponseOnSuccess = { xjxobj: Array<Record<string, string>> }

async function getInoreaderCookie(env: Env): Promise<string> {
const { INOREADER_EMAIL, INOREADER_PASSWORD } = env
const { KV_COOKIES } = env

const cookie = await KV_COOKIES.get('inoreader')
if (cookie) {
return cookie
}

// If Inoreader's cookie is not set, we need to login and set it with an
// expiration inside the worker's KV storage.
const body = new URLSearchParams({
warp_action: 'login',
username: INOREADER_EMAIL,
password: INOREADER_PASSWORD,
remember_me: 'on',
})

// TODO: this is not working yet, fetch here does not return the required
// 'Set-Cookie' header, it somehow stores the cookie somewhere and returns a
// header without any one of the required cookies. (We at least require the
// ssid)
const resp = await fetch('https://www.inoreader.com/', {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: body,
method: 'POST',
})

const freshCookie = resp.headers.get('set-cookie')
console.log(freshCookie)

if (freshCookie) {
// const expires = freshCookie.match(/expires=([^;]+);/)?.[1] ?? ''
await KV_COOKIES.put('inoreader', freshCookie)
return freshCookie
}
return ''
}

export default async function inoreaderProvider(
key: string,
env: Env,
): Promise<SubstatsResponse> {
// This route uses Inoreader's search API, which is a POST request with an
// x-www-form-urlencoded body containing the search query. It returns a JSON
// object with an 'xjxobj' key, which contains raw HTML. We can extract the
// feed's follower count from this raw HTML.
const requestBody = new URLSearchParams()
requestBody.append('xjxfun', 'build_searcher_content')
requestBody.append('xjxr', '1645356116453')
requestBody.append(
'xjxargs[]',
`{"tab":"feeds","term":"${decodeURIComponent(key)}","offset":0}`,
)

// Attempt to get the Inoreader cookie from either env or KV storage.
const inoreaderCookie =
env.INOREADER_COOKIE ?? (await getInoreaderCookie(env))

try {
const resp = await fetch('https://www.inoreader.com/', {
const resp = await fetch(`https://www.innoreader.com/feed/${encodeURIComponent(key)}`, {
headers: {
'content-type': 'application/x-www-form-urlencoded',
cookie: inoreaderCookie ?? '',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36'
},
body: requestBody,
method: 'POST',
cf: { cacheEverything: true },
})

const data = await resp.json<InoreaderRawResponseOnSuccess>()

// We are looking for the returned element with the 'search_content' prop as
// 'id' and 'innerHTML' prop as 'prop', this object should contain 'data' as
// an HTML string for us to parse. A sample response:
// {
// xjxobj: [
// { cmd: 'js', data: '...'},
// { cmd: 'jc', func: 'xxx', data: '...'},
// { cmd: 'as', id: 'search_content', prop: 'innerHTML', data: '...' },
// { /* ... */ },
// ]
// }
const predicate = (x: Record<string, string>) =>
'id' in x && x?.id === 'search_content' && 'data' in x
const followerHTML = data.xjxobj.find(predicate)?.data
const followerHTML = await resp.text()

if (!followerHTML) {
if (!followerHTML || /Page not found/.test(followerHTML)) {
throw new Error('Feed not found on Inoreader')
}

Expand Down

1 comment on commit e9599ed

@vercel
Copy link

@vercel vercel bot commented on e9599ed Jan 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.