mirror of https://git.sr.ht/~cadence/bibliogram
205 lines
5.6 KiB
JavaScript
205 lines
5.6 KiB
JavaScript
|
const {Readable} = require("stream")
|
||
|
|
||
|
const db = require("../../lib/db")
|
||
|
const collectors = require("../../lib/collectors")
|
||
|
const constants = require("../../lib/constants")
|
||
|
|
||
|
/** @type {Set<Waiter>} */
|
||
|
const waiters = new Set()
|
||
|
|
||
|
setInterval((new (function() {
|
||
|
const payload = `:keepalive ${Date.now()}\n\n`
|
||
|
for (const waiter of waiters.values()) {
|
||
|
waiter.stream.push(payload)
|
||
|
}
|
||
|
})).constructor, 50000)
|
||
|
|
||
|
class Waiter {
|
||
|
constructor(username) {
|
||
|
const _this = this
|
||
|
this.username = username
|
||
|
this.stream = new Readable({
|
||
|
autoDestroy: true,
|
||
|
// @ts-ignore
|
||
|
emitClose: true,
|
||
|
read: function() {},
|
||
|
destroy: function() {
|
||
|
waiters.delete(_this)
|
||
|
}
|
||
|
})
|
||
|
this.stream.push(":connected\n\n")
|
||
|
this.stream.on("end", () => {
|
||
|
waiters.delete(this)
|
||
|
})
|
||
|
waiters.add(this)
|
||
|
}
|
||
|
|
||
|
complete() {
|
||
|
this.stream.push("event: profile_available\ndata: profile_available\n\n")
|
||
|
this.stream.push(null)
|
||
|
waiters.delete(this)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
module.exports = [
|
||
|
{
|
||
|
route: "/api/suggest_user/v1", methods: ["POST"], upload: true, code: async ({url, body}) => {
|
||
|
body = body.toString()
|
||
|
const respondAsPlaintext = url.searchParams.has("plaintext")
|
||
|
const params = new URLSearchParams(body)
|
||
|
const missingParams = []
|
||
|
if (!params.has("username")) missingParams.push("username")
|
||
|
if (!params.has("user_id")) missingParams.push("user_id")
|
||
|
if (missingParams.length) {
|
||
|
return {
|
||
|
statusCode: 400,
|
||
|
contentType: "application/json",
|
||
|
content: {
|
||
|
status: "fail",
|
||
|
version: "1.0",
|
||
|
generatedAt: Date.now(),
|
||
|
message: "These required POST body parameters were missing: " + missingParams.join(", "),
|
||
|
fields: missingParams.map(p => `bp:${p}`),
|
||
|
identifier: "MISSING_REQUIRED_PARAMETERS"
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
const username = params.get("username")
|
||
|
const userID = (params.get("user_id").match(/\d+/) || [])[0]
|
||
|
if (!userID) {
|
||
|
return {
|
||
|
statusCode: 400,
|
||
|
contentType: "application/json",
|
||
|
content: {
|
||
|
status: "fail",
|
||
|
version: "1.0",
|
||
|
generatedAt: Date.now(),
|
||
|
message: "The user_id parameter must be a number.",
|
||
|
fields: ["bp:user_id"],
|
||
|
identifier: "MALFORMED_USER_ID"
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
const existing = db.prepare("SELECT * FROM Users WHERE user_id = ?").get(userID)
|
||
|
if (existing) {
|
||
|
if (respondAsPlaintext) {
|
||
|
return {
|
||
|
statusCode: 403,
|
||
|
contentType: "text/plain",
|
||
|
content: "The user is already known. Nothing has changed.\n"
|
||
|
}
|
||
|
} else {
|
||
|
return {
|
||
|
statusCode: 403,
|
||
|
contentType: "application/json",
|
||
|
content: {
|
||
|
status: "fail",
|
||
|
version: "1.0",
|
||
|
generatedAt: Date.now(),
|
||
|
message: "The user is already known. Nothing has changed.",
|
||
|
identifier: "USER_ALREADY_KNOWN"
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return collectors.verifyUserPair(userID, username).then(valid => {
|
||
|
if (!valid) {
|
||
|
return {
|
||
|
statusCode: 400,
|
||
|
contentType: "application/json",
|
||
|
content: {
|
||
|
status: "fail",
|
||
|
version: "1.0",
|
||
|
generatedAt: Date.now(),
|
||
|
message: "The user_id and username do not refer to the same user.",
|
||
|
identifier: "IDENTIFIERS_DO_NOT_MATCH"
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
db.prepare(
|
||
|
"INSERT INTO Users (user_id, username, created, updated, updated_version, post_count, following_count, followed_by_count, is_private, is_verified, profile_pic_url)"
|
||
|
+" VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
||
|
).run(
|
||
|
userID, username, Date.now(), Date.now(), 1, 0, 0, 0, 0, 0, ""
|
||
|
)
|
||
|
collectors.userRequestCache.cache.delete("user/"+username)
|
||
|
for (const waiter of waiters) {
|
||
|
if (waiter.username === username) waiter.complete()
|
||
|
}
|
||
|
if (respondAsPlaintext) {
|
||
|
return {
|
||
|
statusCode: 201,
|
||
|
contentType: "text/plain",
|
||
|
content: "User added! Go back to your web browser.\n"
|
||
|
}
|
||
|
} else {
|
||
|
return {
|
||
|
statusCode: 201,
|
||
|
contentType: "application/json",
|
||
|
content: {
|
||
|
status: "ok",
|
||
|
version: "1.0",
|
||
|
generatedAt: Date.now()
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}).catch(error => {
|
||
|
if (error === constants.symbols.NOT_FOUND) {
|
||
|
return {
|
||
|
statusCode: 400,
|
||
|
contentType: "application/json",
|
||
|
content: {
|
||
|
status: "fail",
|
||
|
version: "1.0",
|
||
|
generatedAt: Date.now(),
|
||
|
message: "That user does not exist.",
|
||
|
identifier: "USER_DOES_NOT_EXIST"
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
return {
|
||
|
statusCode: 500,
|
||
|
contentType: "application/json",
|
||
|
content: {
|
||
|
status: "fail",
|
||
|
version: "1.0",
|
||
|
generatedAt: Date.now(),
|
||
|
message: "An unknown server error occurred.",
|
||
|
identifier: "UNKNOWN_ERROR"
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
},
|
||
|
{
|
||
|
route: `/api/user_available_stream/v1/(${constants.external.username_regex})`, methods: ["GET"], code: async ({fill}) => {
|
||
|
const username = fill[0]
|
||
|
const waiter = new Waiter(username)
|
||
|
return {
|
||
|
statusCode: 200,
|
||
|
contentType: "text/event-stream",
|
||
|
headers: {
|
||
|
"X-Accel-Buffering": "no"
|
||
|
},
|
||
|
stream: waiter.stream
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
{
|
||
|
route: `/u/(${constants.external.username_regex})/unblock.sh`, methods: ["GET"], code: async ({fill}) => {
|
||
|
const username = fill[0]
|
||
|
return {
|
||
|
statusCode: 200,
|
||
|
contentType: "text/plain",
|
||
|
content:
|
||
|
`# Good on you for looking at shell scripts before blindly running them.`
|
||
|
+`\n# This script contacts Instagram to get the profile's user ID, then sends the ID to Bibliogram. Bibliogram can take over from there.`
|
||
|
+`\ncurl 'https://www.instagram.com/${username}/' -Ss | grep -oE '"id":"[0-9]+"'`
|
||
|
+` | head -n 1 | grep -oE '[0-9]+' | curl --data-urlencode 'username=${username}' --data-urlencode 'user_id@-'`
|
||
|
+` '${constants.website_origin}/api/suggest_user/v1?plaintext=1'`
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
]
|