add cookie support
usage: - create cookies.json file somewhere, preferrably outside cobalt directory - in docker, you can bind mount it (`volumes` in composefile) - if you don't want cobalt to update the cookies, set it to `:ro` (cobalt will print a warning about this, ignore it) - set COOKIE_PATH to the absolute path of this file - enjoy? usage in services: probably the simplest api ever - import { getCookie, updateCookie } from '../../cookie/manager.js'; - const cookie = getCookie('<service_name>'); - add this to headers - `headers: { cookie }` - after fetch is done, save potential cookie updates: updateCookie(cookie, fetch.headers) - see instagram.js for example usage
This commit is contained in:
parent
91a60c1ec2
commit
a2216510b7
6 changed files with 140 additions and 1 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -17,3 +17,6 @@ docker-compose.yml
|
||||||
|
|
||||||
# vscode
|
# vscode
|
||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
|
# cookie file
|
||||||
|
cookies.json
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
"got": "^12.1.0",
|
"got": "^12.1.0",
|
||||||
"nanoid": "^4.0.2",
|
"nanoid": "^4.0.2",
|
||||||
"node-cache": "^5.1.2",
|
"node-cache": "^5.1.2",
|
||||||
|
"set-cookie-parser": "2.6.0",
|
||||||
"url-pattern": "1.0.3",
|
"url-pattern": "1.0.3",
|
||||||
"xml-js": "^1.6.11",
|
"xml-js": "^1.6.11",
|
||||||
"youtubei.js": "^5.4.0"
|
"youtubei.js": "^5.4.0"
|
||||||
|
|
43
src/modules/cookie/cookie.js
Normal file
43
src/modules/cookie/cookie.js
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import { strict as assert } from 'node:assert'
|
||||||
|
|
||||||
|
export default class Cookie {
|
||||||
|
constructor(input) {
|
||||||
|
assert(typeof input === 'object');
|
||||||
|
this._values = {}
|
||||||
|
this.set(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
set(values) {
|
||||||
|
Object.entries(values).forEach(
|
||||||
|
([ key, value ]) => this._values[key] = value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
unset(keys) {
|
||||||
|
for (const key of keys)
|
||||||
|
delete this._values[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromString(str) {
|
||||||
|
const obj = {}
|
||||||
|
|
||||||
|
str
|
||||||
|
.split('; ')
|
||||||
|
.forEach(cookie => {
|
||||||
|
const key = cookie.split('=')[0]
|
||||||
|
const value = cookie.split('=').splice(1).join('=')
|
||||||
|
obj[key] = decodeURIComponent(value)
|
||||||
|
})
|
||||||
|
|
||||||
|
return new Cookie(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return Object.entries(this._values)
|
||||||
|
.map(([ name, value ]) => `${name}=${encodeURIComponent(value)}`)
|
||||||
|
.join('; ');
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON() { return this.toString() }
|
||||||
|
values() { return Object.freeze({ ...this._values }) }
|
||||||
|
}
|
9
src/modules/cookie/cookies.json.example
Normal file
9
src/modules/cookie/cookies.json.example
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"instagram": [
|
||||||
|
"cookie=asd; bla=bla; fake=cookie"
|
||||||
|
],
|
||||||
|
"youtube": [
|
||||||
|
"epic=google_cookie",
|
||||||
|
"epic=another_epic; youtube=cookie"
|
||||||
|
]
|
||||||
|
}
|
78
src/modules/cookie/manager.js
Normal file
78
src/modules/cookie/manager.js
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
import path from 'path'
|
||||||
|
import Cookie from './cookie.js'
|
||||||
|
import { readFile, writeFile } from 'fs/promises'
|
||||||
|
import { parse as parseSetCookie, splitCookiesString } from 'set-cookie-parser'
|
||||||
|
|
||||||
|
const WRITE_INTERVAL = 60000,
|
||||||
|
COOKIE_PATH = process.env.COOKIE_PATH,
|
||||||
|
COUNTER = Symbol('counter');
|
||||||
|
|
||||||
|
let cookies = {}, dirty = false, intervalId;
|
||||||
|
|
||||||
|
const setup = async () => {
|
||||||
|
try {
|
||||||
|
if (!COOKIE_PATH)
|
||||||
|
return
|
||||||
|
|
||||||
|
cookies = await readFile(COOKIE_PATH, 'utf8')
|
||||||
|
cookies = JSON.parse(cookies)
|
||||||
|
intervalId = setInterval(writeChanges, WRITE_INTERVAL)
|
||||||
|
} catch { /* no cookies for you */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
setup()
|
||||||
|
|
||||||
|
function writeChanges() {
|
||||||
|
if (!dirty) return
|
||||||
|
dirty = false
|
||||||
|
|
||||||
|
writeFile(
|
||||||
|
COOKIE_PATH,
|
||||||
|
JSON.stringify(cookies, null, 4)
|
||||||
|
).catch(e => {
|
||||||
|
console.error('warn: cookies failed to save, stopping interval')
|
||||||
|
console.error('exception:', e)
|
||||||
|
clearInterval(intervalId)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCookie(service) {
|
||||||
|
if (!cookies[service] || !cookies[service].length)
|
||||||
|
return
|
||||||
|
|
||||||
|
let n
|
||||||
|
if (cookies[service][COUNTER] === undefined) {
|
||||||
|
n = cookies[service][COUNTER] = 0
|
||||||
|
} else {
|
||||||
|
++cookies[service][COUNTER]
|
||||||
|
n = (cookies[service][COUNTER] %= cookies[service].length)
|
||||||
|
}
|
||||||
|
|
||||||
|
const cookie = cookies[service][n]
|
||||||
|
if (typeof cookie === 'string')
|
||||||
|
cookies[service][n] = Cookie.fromString(cookie)
|
||||||
|
|
||||||
|
return cookies[service][n]
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: expiry checking? domain checking?
|
||||||
|
// might be pointless for the purposes of cobalt
|
||||||
|
export function updateCookie(cookie, headers) {
|
||||||
|
const parsed = parseSetCookie(
|
||||||
|
splitCookiesString(headers.get('set-cookie'))
|
||||||
|
), values = {}
|
||||||
|
|
||||||
|
cookie.unset(
|
||||||
|
parsed
|
||||||
|
.filter(c => c.expires < new Date())
|
||||||
|
.map(c => c.name)
|
||||||
|
)
|
||||||
|
|
||||||
|
parsed
|
||||||
|
.filter(c => c.expires > new Date())
|
||||||
|
.forEach(c => values[c.name] = c.value);
|
||||||
|
|
||||||
|
cookie.set(values)
|
||||||
|
if (Object.keys(values).length)
|
||||||
|
dirty = true
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
import { createStream } from "../../stream/manage.js";
|
import { createStream } from "../../stream/manage.js";
|
||||||
import { genericUserAgent } from "../../config.js";
|
import { genericUserAgent } from "../../config.js";
|
||||||
|
import { getCookie, updateCookie } from '../../cookie/manager.js';
|
||||||
|
|
||||||
export default async function(obj) {
|
export default async function(obj) {
|
||||||
let data;
|
let data;
|
||||||
|
@ -14,6 +15,8 @@ export default async function(obj) {
|
||||||
shortcode: obj.id
|
shortcode: obj.id
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
const cookie = getCookie('instagram');
|
||||||
|
|
||||||
data = await fetch(url, {
|
data = await fetch(url, {
|
||||||
headers: {
|
headers: {
|
||||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
|
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
|
||||||
|
@ -25,9 +28,11 @@ export default async function(obj) {
|
||||||
'Sec-Fetch-Site': 'same-origin',
|
'Sec-Fetch-Site': 'same-origin',
|
||||||
'upgrade-insecure-requests': '1',
|
'upgrade-insecure-requests': '1',
|
||||||
'accept-encoding': 'gzip, deflate, br',
|
'accept-encoding': 'gzip, deflate, br',
|
||||||
'accept-language': 'en-US,en;q=0.9,en;q=0.8'
|
'accept-language': 'en-US,en;q=0.9,en;q=0.8',
|
||||||
|
cookie
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
updateCookie(cookie, data.headers);
|
||||||
data = (await data.json()).data;
|
data = (await data.json()).data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
data = false;
|
data = false;
|
||||||
|
|
Loading…
Reference in a new issue