api: restructuring

fresh start :)
- removed all web stuff
- removed localization from api
- removed error.js
- cleaned up module names
- environment variables are now capitalized
- copied favicon for api to assets folder
This commit is contained in:
wukko 2024-02-13 13:41:16 +06:00 committed by dumbmoron
parent b1a54fd1ff
commit 39ae930a0c
No known key found for this signature in database
27 changed files with 77 additions and 192 deletions

2
api/README.md Normal file
View File

@ -0,0 +1,2 @@
# cobalt api
(TODO)

View File

@ -2,90 +2,6 @@
"streamLifespan": 90000,
"maxVideoDuration": 10800000,
"genericUserAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36",
"authorInfo": {
"name": "wukko",
"link": "https://wukko.me/",
"contact": "https://wukko.me/contacts",
"support": {
"default": {
"email": {
"emoji": "📧",
"url": "mailto:support@cobalt.tools",
"name": "support@cobalt.tools"
},
"twitter": {
"emoji": "🐦",
"url": "https://twitter.com/justusecobalt",
"name": "@justusecobalt"
},
"discord": {
"emoji": "👾",
"url": "https://discord.gg/pQPt8HBUPu",
"name": "cobalt discord server"
}
},
"ru": {
"telegram": {
"emoji": "📬",
"url": "https://t.me/justusecobalt_ru",
"name": "канал в telegram"
},
"email": {
"emoji": "📧",
"url": "mailto:support@cobalt.tools",
"name": "support@cobalt.tools"
}
}
}
},
"donations": {
"crypto": {
"monero": "4B1SNB6s8Pq1hxjNeKPEe8Qa8EP3zdL16Sqsa7QDoJcUecKQzEj9BMxWnEnTGu12doKLJBKRDUqnn6V9qfSdXpXi3Nw5Uod",
"litecoin": "ltc1qvp0xhrk2m7pa6p6z844qcslfyxv4p3vf95rhna",
"ethereum": "0x4B4cF23051c78c7A7E0eA09d39099621c46bc302",
"usdt-erc20": "0x4B4cF23051c78c7A7E0eA09d39099621c46bc302",
"usdt-trc20": "TVbx7YT3rBfu931Gxko6pRfXtedYqbgnBB",
"bitcoin": "bc1qlvcnlnyzfsgnuxyxsv3k0p0q0yln0azjpadyx4",
"bitcoin-alt": "18PKf6N2cHrmSzz9ZzTSvDd2jAkqGC7SxA",
"ton": "UQA3SO-hHZq1oCCT--u6or6ollB8fd2o52aD8mXiLk9iDZd3"
},
"links": {
"boosty": "https://boosty.to/wukko/donate"
}
},
"links": {
"saveToGalleryShortcut": "https://www.icloud.com/shortcuts/14e9aebf04b24156acc34ceccf7e6fcd",
"saveToFilesShortcut": "https://www.icloud.com/shortcuts/2134cd9d4d6b41448b2201f933542b2e",
"statusPage": "https://status.cobalt.tools/",
"troubleshootingGuide": "https://github.com/wukko/cobalt/blob/current/docs/troubleshooting.md"
},
"celebrations": {
"01-01": "🎄",
"02-17": "😺",
"02-22": "😺",
"03-01": "😺",
"03-08": "💪",
"05-26": "🎂",
"08-08": "😺",
"08-26": "🐶",
"10-29": "😺",
"10-30": "🎃",
"10-31": "🎃",
"11-01": "🕯️",
"11-02": "🕯️",
"12-20": "🎄",
"12-21": "🎄",
"12-22": "🎄",
"12-23": "🎄",
"12-24": "🎄",
"12-25": "🎄",
"12-26": "🎄",
"12-27": "🎄",
"12-28": "🎄",
"12-29": "🎄",
"12-30": "🎄",
"12-31": "🎄"
},
"supportedAudio": ["mp3", "ogg", "wav", "opus"],
"ffmpegArgs": {
"webm": ["-c:v", "copy", "-c:a", "copy"],
@ -94,15 +10,5 @@
"audio": ["-ar", "48000", "-ac", "2", "-b:a", "320k"],
"m4a": ["-movflags", "frag_keyframe+empty_moov"],
"gif": ["-vf", "scale=-1:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse", "-loop", "0"]
},
"sponsors": [{
"name": "royale",
"fullName": "RoyaleHosting",
"url": "https://royalehosting.net/",
"logo": {
"width": 605,
"height": 136,
"scale": 5
}
}]
}
}

View File

@ -1,9 +1,9 @@
import UrlPattern from "url-pattern";
import { loadJSON } from "../modules/util/loadFromFs.js";
const config = loadJSON("./src/config.json");
const packageJson = loadJSON("./package.json");
const servicesConfigJson = loadJSON("./src/modules/processing/servicesConfig.json");
const config = loadJSON("config.json");
const packageJson = loadJSON("package.json");
const servicesConfigJson = loadJSON("modules/processing/servicesConfig.json");
Object.values(servicesConfigJson.config).forEach(service => {
service.patterns = service.patterns.map(
@ -20,11 +20,5 @@ export const
streamLifespan = config.streamLifespan,
maxVideoDuration = config.maxVideoDuration,
genericUserAgent = config.genericUserAgent,
repo = packageJson["bugs"]["url"].replace('/issues', ''),
authorInfo = config.authorInfo,
donations = config.donations,
ffmpegArgs = config.ffmpegArgs,
supportedAudio = config.supportedAudio,
celebrations = config.celebrations,
links = config.links,
sponsors = config.sponsors
supportedAudio = config.supportedAudio

View File

@ -9,8 +9,7 @@ import { getJSON } from "../modules/util/preApi.js";
import { apiJSON, checkJSONPost, getIP, languageCode } from "../modules/util/misc.js";
import { Bright, Cyan } from "../modules/util/consoleText.js";
import stream from "../modules/stream/stream.js";
import loc from "../localization/manager.js";
import { generateHmac } from "../modules/sub/crypto.js";
import { generateHmac } from "../modules/util/crypto.js";
import { verifyStream } from "../modules/stream/manage.js";
export function runAPI(express, app, gitCommit, gitBranch, __dirname) {
@ -28,7 +27,7 @@ export function runAPI(express, app, gitCommit, gitBranch, __dirname) {
handler: (req, res, next, opt) => {
return res.status(429).json({
"status": "rate-limit",
"text": loc(languageCode(req), 'ErrorRateLimit')
"text": 'ErrorRateLimit'
});
}
});
@ -41,7 +40,7 @@ export function runAPI(express, app, gitCommit, gitBranch, __dirname) {
handler: (req, res, next, opt) => {
return res.status(429).json({
"status": "rate-limit",
"text": loc(languageCode(req), 'ErrorRateLimit')
"text": 'ErrorRateLimit'
});
}
});
@ -109,11 +108,11 @@ export function runAPI(express, app, gitCommit, gitBranch, __dirname) {
j = await getJSON(chck.url, lang, chck);
} else {
j = apiJSON(0, {
t: !contentCon ? "invalid content type header" : loc(lang, 'ErrorNoLink')
t: !contentCon ? "invalid content type header" : 'ErrorNoLink'
});
}
} catch (e) {
j = apiJSON(0, { t: loc(lang, 'ErrorCantProcess') });
j = apiJSON(0, { t: 'ErrorCantProcess' });
}
return res.status(j.status).json(j.body);
} catch (e) {
@ -166,7 +165,7 @@ export function runAPI(express, app, gitCommit, gitBranch, __dirname) {
} catch (e) {
return res.status(500).json({
status: "error",
text: loc(languageCode(req), 'ErrorCantProcess')
text: 'ErrorCantProcess'
});
}
});
@ -176,7 +175,7 @@ export function runAPI(express, app, gitCommit, gitBranch, __dirname) {
});
app.get('/favicon.ico', (req, res) => {
res.sendFile(`${__dirname}/src/front/icons/favicon.ico`)
res.sendFile(`${__dirname}/api/assets/favicon.ico`)
});
app.get('/*', (req, res) => {

View File

@ -1,9 +1,6 @@
import { strict as assert } from "node:assert";
import { apiJSON } from "../sub/utils.js";
import { errorUnsupported, genericError, brokenLink } from "../sub/errors.js";
import loc from "../../localization/manager.js";
import { apiJSON } from "../util/misc.js";
import { testers } from "./servicesPatternTesters.js";
import matchActionDecider from "./matchActionDecider.js";
@ -32,8 +29,8 @@ export default async function(host, patternMatch, url, lang, obj) {
try {
let r, isAudioOnly = !!obj.isAudioOnly, disableMetadata = !!obj.disableMetadata;
if (!testers[host]) return apiJSON(0, { t: errorUnsupported(lang) });
if (!(testers[host](patternMatch))) return apiJSON(0, { t: brokenLink(lang, host) });
if (!testers[host]) return apiJSON(0, { t: 'ErrorUnsupported' });
if (!(testers[host](patternMatch))) return apiJSON(0, { t: 'ErrorBrokenLink' });
switch (host) {
case "twitter":
@ -159,20 +156,18 @@ export default async function(host, patternMatch, url, lang, obj) {
r = await dailymotion(patternMatch);
break;
default:
return apiJSON(0, { t: errorUnsupported(lang) });
return apiJSON(0, { t: 'ErrorUnsupported' });
}
if (r.isAudioOnly) isAudioOnly = true;
let isAudioMuted = isAudioOnly ? false : obj.isAudioMuted;
if (r.error && r.critical)
return apiJSON(6, { t: loc(lang, r.error) })
return apiJSON(6, { t: r.error })
if (r.error)
return apiJSON(0, {
t: Array.isArray(r.error)
? loc(lang, r.error[0], r.error[1])
: loc(lang, r.error)
t: r.error
})
return matchActionDecider(
@ -181,6 +176,6 @@ export default async function(host, patternMatch, url, lang, obj) {
obj.filenamePattern, obj.twitterGif
)
} catch (e) {
return apiJSON(0, { t: genericError(lang, host) })
return apiJSON(0, { t: 'ErrorBadFetch' })
}
}

View File

@ -1,6 +1,5 @@
import { audioIgnore, services, supportedAudio } from "../config.js";
import { apiJSON } from "../sub/utils.js";
import loc from "../../localization/manager.js";
import { audioIgnore, services, supportedAudio } from "../../core/config.js";
import { apiJSON } from "../util/misc.js";
import createFilename from "./createFilename.js";
export default function(r, host, userFormat, isAudioOnly, lang, isAudioMuted, disableMetadata, filenamePattern, toGif) {
@ -35,7 +34,7 @@ export default function(r, host, userFormat, isAudioOnly, lang, isAudioMuted, di
switch (action) {
default:
return apiJSON(0, { t: loc(lang, 'ErrorEmptyDownload') });
return apiJSON(0, { t: 'ErrorEmptyDownload' });
case "photo":
responseType = 1;
@ -127,7 +126,7 @@ export default function(r, host, userFormat, isAudioOnly, lang, isAudioMuted, di
case "audio":
if ((host === "reddit" && r.typeId === 1) || audioIgnore.includes(host)) {
return apiJSON(0, { t: loc(lang, 'ErrorEmptyDownload') })
return apiJSON(0, { t: 'ErrorEmptyDownload' })
}
let processType = "render",

View File

@ -1,4 +1,4 @@
import { genericUserAgent, maxVideoDuration } from "../../config.js";
import { genericUserAgent, maxVideoDuration } from "../../../core/config.js";
// TO-DO: higher quality downloads (currently requires an account)

View File

@ -1,5 +1,5 @@
import { createStream } from "../../stream/manage.js";
import { genericUserAgent } from "../../config.js";
import { genericUserAgent } from "../../../core/config.js";
import { getCookie, updateCookie } from "../cookie/manager.js";
const commonInstagramHeaders = {

View File

@ -1,5 +1,5 @@
import { genericUserAgent, maxVideoDuration } from "../../config.js";
import { cleanString } from "../../sub/utils.js";
import { genericUserAgent, maxVideoDuration } from "../../../core/config.js";
import { cleanString } from "../../util/misc.js";
const resolutions = {
"ultra": "2160",

View File

@ -1,4 +1,4 @@
import { genericUserAgent } from "../../config.js";
import { genericUserAgent } from "../../../core/config.js";
const videoLinkBase = {
"regular": "https://v1.pinimg.com/videos/mc/720p/",

View File

@ -1,4 +1,4 @@
import { genericUserAgent, maxVideoDuration } from "../../config.js";
import { genericUserAgent, maxVideoDuration } from "../../../core/config.js";
import { getCookie, updateCookieValues } from "../cookie/manager.js";
async function getAccessToken() {

View File

@ -1,6 +1,6 @@
import HLS from 'hls-parser';
import { maxVideoDuration } from "../../config.js";
import { cleanString } from '../../sub/utils.js';
import { maxVideoDuration } from "../../../core/config.js";
import { cleanString } from '../../util/misc.js';
export default async function(obj) {
let quality = obj.quality === "max" ? "9000" : obj.quality;

View File

@ -1,5 +1,5 @@
import { maxVideoDuration } from "../../config.js";
import { cleanString } from "../../sub/utils.js";
import { maxVideoDuration } from "../../../core/config.js";
import { cleanString } from "../../util/misc.js";
const cachedID = {
version: '',

View File

@ -1,4 +1,4 @@
import { genericUserAgent } from "../../config.js";
import { genericUserAgent } from "../../../core/config.js";
const shortDomain = "https://vt.tiktok.com/";
const apiPath = "https://api22-normal-c-alisg.tiktokv.com/aweme/v1/feed/?region=US&carrier_region=US";

View File

@ -1,5 +1,5 @@
import psl from "psl";
import { genericUserAgent } from "../../config.js";
import { genericUserAgent } from "../../../core/config.js";
const API_KEY = 'jrsCWX1XDuVxAFO4GkK147syAoN8BJZ5voz8tS80bPcj26Vc5Z';
const API_BASE = 'https://api-http2.tumblr.com';

View File

@ -1,5 +1,5 @@
import { maxVideoDuration } from "../../config.js";
import { cleanString } from '../../sub/utils.js';
import { maxVideoDuration } from "../../../core/config.js";
import { cleanString } from '../../util/misc.js';
const gqlURL = "https://gql.twitch.tv/gql";
const clientIdHead = { "client-id": "kimne78kx3ncx6brgo4mv6wki5h1ko" };

View File

@ -1,4 +1,4 @@
import { genericUserAgent } from "../../config.js";
import { genericUserAgent } from "../../../core/config.js";
import { createStream } from "../../stream/manage.js";
const graphqlURL = 'https://twitter.com/i/api/graphql/5GOHgZe-8U2j5sVHQzEm9A/TweetResultByRestId';

View File

@ -1,5 +1,5 @@
import { maxVideoDuration } from "../../config.js";
import { cleanString } from '../../sub/utils.js';
import { maxVideoDuration } from "../../../core/config.js";
import { cleanString } from '../../util/misc.js';
const resolutionMatch = {
"3840": "2160",

View File

@ -1,5 +1,5 @@
import { genericUserAgent, maxVideoDuration } from "../../config.js";
import { cleanString } from "../../sub/utils.js";
import { genericUserAgent, maxVideoDuration } from "../../../core/config.js";
import { cleanString } from "../../util/misc.js";
const resolutions = ["2160", "1440", "1080", "720", "480", "360", "240"];

View File

@ -1,6 +1,6 @@
import { Innertube } from 'youtubei.js';
import { maxVideoDuration } from '../../config.js';
import { cleanString } from '../../sub/utils.js';
import { maxVideoDuration } from '../../../core/config.js';
import { cleanString } from '../../util/misc.js';
const yt = await Innertube.create();

View File

@ -1,4 +1,4 @@
import { services } from "../config.js";
import { services } from "../../core/config.js";
import { strict as assert } from "node:assert";
import psl from "psl";

View File

@ -2,8 +2,8 @@ import NodeCache from "node-cache";
import { randomBytes } from "crypto";
import { nanoid } from 'nanoid';
import { decryptStream, encryptStream, generateHmac } from "../sub/crypto.js";
import { streamLifespan } from "../config.js";
import { decryptStream, encryptStream, generateHmac } from "../util/crypto.js";
import { streamLifespan } from "../../core/config.js";
const streamNoAccess = {
error: "i couldn't verify if you have access to this stream. go back and try again!",

View File

@ -1,7 +1,7 @@
import { spawn } from "child_process";
import ffmpeg from "ffmpeg-static";
import { ffmpegArgs, genericUserAgent } from "../config.js";
import { metadataManager } from "../sub/utils.js";
import { ffmpegArgs, genericUserAgent } from "../../core/config.js";
import { metadataManager } from "../util/misc.js";
import { request } from "undici";
import { create as contentDisposition } from "content-disposition-header";

View File

@ -1,8 +1,15 @@
import * as fs from "fs";
import path from 'path';
import { fileURLToPath } from 'url';
const splitDir = path.dirname(fileURLToPath(import.meta.url)).split('/');
splitDir.splice(-2);
const dir = splitDir.join('/');
export function loadJSON(path) {
try {
return JSON.parse(fs.readFileSync(path, 'utf-8'))
return JSON.parse(fs.readFileSync(`${dir}/${path}`, 'utf-8'))
} catch(e) {
return false
}

View File

@ -1,17 +1,15 @@
import { services } from "./config.js";
import { services } from "../../core/config.js";
import { apiJSON } from "./sub/utils.js";
import { errorUnsupported } from "./sub/errors.js";
import loc from "../localization/manager.js";
import match from "./processing/match.js";
import { getHostIfValid } from "./processing/url.js";
import { apiJSON } from "./misc.js";
import match from "../processing/match.js";
import { getHostIfValid } from "../processing/url.js";
export async function getJSON(url, lang, obj) {
try {
const host = getHostIfValid(url);
if (!host || !services[host].enabled) {
return apiJSON(0, { t: errorUnsupported(lang) });
return apiJSON(0, { t: 'ErrorUnsupported' });
}
let patternMatch;
@ -23,11 +21,11 @@ export async function getJSON(url, lang, obj) {
}
if (!patternMatch) {
return apiJSON(0, { t: errorUnsupported(lang) });
return apiJSON(0, { t: 'ErrorUnsupported' });
}
return await match(host, patternMatch, url, lang, obj)
} catch (e) {
return apiJSON(0, { t: loc(lang, 'ErrorSomethingWentWrong') })
return apiJSON(0, { t: 'ErrorSomethingWentWrong' })
}
}

View File

@ -1,19 +1,15 @@
{
"name": "cobalt",
"name": "cobalt_api",
"description": "save what you love",
"version": "7.12.6",
"version": "8.0",
"author": "wukko",
"exports": "./src/cobalt.js",
"exports": "./server.js",
"type": "module",
"engines": {
"node": ">=18"
},
"scripts": {
"start": "node src/cobalt",
"setup": "node src/modules/setup",
"test": "node src/test/test",
"build": "node src/modules/buildStatic",
"testFilenames": "node src/test/testFilenamePresets"
"start": "node server.js"
},
"repository": {
"type": "git",

View File

@ -1,14 +1,14 @@
import "dotenv/config";
import "./modules/sub/alias-envs.js";
import "./modules/util/alias-envs.js";
import express from "express";
import { Bright, Green, Red } from "./modules/sub/consoleText.js";
import { getCurrentBranch, shortCommit } from "./modules/sub/currentCommit.js";
import { loadLoc } from "./localization/manager.js";
import path from 'path';
import { fileURLToPath } from 'url';
import { runAPI } from "./core/endpoints.js";
import { Red } from "./modules/util/consoleText.js";
import { getCurrentBranch, shortCommit } from "./modules/util/currentCommit.js";
const app = express();
@ -20,21 +20,10 @@ const __dirname = path.dirname(__filename).slice(0, -4);
app.disable('x-powered-by');
await loadLoc();
const apiMode = process.env.API_URL && !process.env.WEB_URL;
const webMode = process.env.WEB_URL && process.env.API_URL;
if (apiMode) {
const { runAPI } = await import('./core/api.js');
if (process.env.API_URL) {
runAPI(express, app, gitCommit, gitBranch, __dirname)
} else if (webMode) {
const { runWeb } = await import('./core/web.js');
await runWeb(express, app, gitCommit, gitBranch, __dirname)
} else {
console.log(
Red(`cobalt wasn't configured yet or configuration is invalid.\n`)
+ Bright(`please run the setup script to fix this: `)
+ Green(`npm run setup`)
Red(`cobalt wasn't configured yet or configuration is invalid. check if API_URL is present in env\n`)
)
}