Merge branch 'main' into fix/comet-rewrite

This commit is contained in:
Viren070
2025-02-25 14:56:22 +00:00
23 changed files with 690 additions and 263 deletions
+218 -7
View File
@@ -329,6 +329,15 @@
"dev": true,
"license": "MIT OR Apache-2.0"
},
"node_modules/@colors/colors": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz",
"integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==",
"license": "MIT",
"engines": {
"node": ">=0.1.90"
}
},
"node_modules/@cspotcode/source-map-support": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
@@ -342,6 +351,17 @@
"node": ">=12"
}
},
"node_modules/@dabh/diagnostics": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz",
"integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==",
"license": "MIT",
"dependencies": {
"colorspace": "1.1.x",
"enabled": "2.0.x",
"kuler": "^2.0.0"
}
},
"node_modules/@emnapi/runtime": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz",
@@ -2242,6 +2262,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/triple-beam": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz",
"integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==",
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.18.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.2.tgz",
@@ -2903,6 +2929,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/async": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
"license": "MIT"
},
"node_modules/async-file": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/async-file/-/async-file-2.0.2.tgz",
@@ -3301,7 +3333,6 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"devOptional": true,
"license": "MIT"
},
"node_modules/color-string": {
@@ -3309,12 +3340,46 @@
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
"license": "MIT",
"optional": true,
"dependencies": {
"color-name": "^1.0.0",
"simple-swizzle": "^0.2.2"
}
},
"node_modules/colorspace": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz",
"integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==",
"license": "MIT",
"dependencies": {
"color": "^3.1.3",
"text-hex": "1.0.x"
}
},
"node_modules/colorspace/node_modules/color": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz",
"integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==",
"license": "MIT",
"dependencies": {
"color-convert": "^1.9.3",
"color-string": "^1.6.0"
}
},
"node_modules/colorspace/node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"license": "MIT",
"dependencies": {
"color-name": "1.1.3"
}
},
"node_modules/colorspace/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"license": "MIT"
},
"node_modules/commander": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-6.1.0.tgz",
@@ -3706,6 +3771,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/enabled": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz",
"integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==",
"license": "MIT"
},
"node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
@@ -4572,6 +4643,12 @@
"reusify": "^1.0.4"
}
},
"node_modules/fecha": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz",
"integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==",
"license": "MIT"
},
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -4675,6 +4752,12 @@
"dev": true,
"license": "ISC"
},
"node_modules/fn.name": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz",
"integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==",
"license": "MIT"
},
"node_modules/for-each": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
@@ -5158,8 +5241,7 @@
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
"license": "MIT",
"optional": true
"license": "MIT"
},
"node_modules/is-async-function": {
"version": "2.0.0",
@@ -5443,7 +5525,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -5691,6 +5772,12 @@
"json-buffer": "3.0.1"
}
},
"node_modules/kuler": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz",
"integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==",
"license": "MIT"
},
"node_modules/language-subtag-registry": {
"version": "0.3.23",
"resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz",
@@ -5754,6 +5841,23 @@
"dev": true,
"license": "MIT"
},
"node_modules/logform": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz",
"integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==",
"license": "MIT",
"dependencies": {
"@colors/colors": "1.6.0",
"@types/triple-beam": "^1.3.2",
"fecha": "^4.2.0",
"ms": "^2.1.1",
"safe-stable-stringify": "^2.3.1",
"triple-beam": "^1.3.0"
},
"engines": {
"node": ">= 12.0.0"
}
},
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -6299,6 +6403,15 @@
"wrappy": "1"
}
},
"node_modules/one-time": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz",
"integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==",
"license": "MIT",
"dependencies": {
"fn.name": "1.x.x"
}
},
"node_modules/optionator": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -6730,6 +6843,20 @@
"react-dom": ">=16.6.0"
}
},
"node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/readdirp": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz",
@@ -7034,6 +7161,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/safe-stable-stringify": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
"integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@@ -7363,7 +7499,6 @@
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
"license": "MIT",
"optional": true,
"dependencies": {
"is-arrayish": "^0.3.1"
}
@@ -7420,6 +7555,15 @@
"dev": true,
"license": "MIT"
},
"node_modules/stack-trace": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
"integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==",
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/stackback": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
@@ -7473,6 +7617,15 @@
"node": ">=10.0.0"
}
},
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.2.0"
}
},
"node_modules/string.prototype.includes": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz",
@@ -7673,6 +7826,12 @@
"node": ">=6"
}
},
"node_modules/text-hex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz",
"integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==",
"license": "MIT"
},
"node_modules/tinybench": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
@@ -7739,6 +7898,15 @@
"node": ">=0.6"
}
},
"node_modules/triple-beam": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz",
"integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==",
"license": "MIT",
"engines": {
"node": ">= 14.0.0"
}
},
"node_modules/ts-api-utils": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz",
@@ -8006,6 +8174,12 @@
}
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
@@ -8724,6 +8898,42 @@
"node": ">=8"
}
},
"node_modules/winston": {
"version": "3.17.0",
"resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz",
"integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==",
"license": "MIT",
"dependencies": {
"@colors/colors": "^1.6.0",
"@dabh/diagnostics": "^2.0.2",
"async": "^3.2.3",
"is-stream": "^2.0.0",
"logform": "^2.7.0",
"one-time": "^1.0.0",
"readable-stream": "^3.4.0",
"safe-stable-stringify": "^2.3.1",
"stack-trace": "0.0.x",
"triple-beam": "^1.3.0",
"winston-transport": "^4.9.0"
},
"engines": {
"node": ">= 12.0.0"
}
},
"node_modules/winston-transport": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz",
"integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==",
"license": "MIT",
"dependencies": {
"logform": "^2.7.0",
"readable-stream": "^3.6.2",
"triple-beam": "^1.3.0"
},
"engines": {
"node": ">= 12.0.0"
}
},
"node_modules/word-wrap": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
@@ -9389,7 +9599,8 @@
"name": "@aiostreams/utils",
"version": "1.13.8",
"dependencies": {
"jsoncrush": "^1.1.8"
"jsoncrush": "^1.1.8",
"winston": "^3.17.0"
}
},
"packages/wrappers": {
+142 -69
View File
@@ -33,9 +33,12 @@ import {
getMediaFlowPublicIp,
getTimeTakenSincePoint,
Settings,
createLogger,
} from '@aiostreams/utils';
import { errorStream } from './responses';
const logger = createLogger('addon');
export class AIOStreams {
private config: Config;
@@ -74,17 +77,18 @@ export class AIOStreams {
}
this.config.requestingIp = ip;
break;
} catch (error) {
console.error(
`|ERR| addon > getStreams: Failed to get requesting IP: ${error}, retrying ${ipRequestCount + 1}/3`
);
} catch (error: any) {
logger.error(error, {
func: 'getRequestingIp',
});
ipRequestCount++;
if (ipRequestCount < 3) {
logger.info(`Retrying ${ipRequestCount}/3...`);
}
}
}
if (ipRequestCount === 3) {
console.error(
'|ERR| addon > getStreams: Failed to get requesting IP after 3 attempts'
);
logger.error('Failed to get requesting IP after 3 attempts');
if (this.config.mediaFlowConfig?.mediaFlowEnabled) {
return [
errorStream('Aborted request after failing to get requesting IP'),
@@ -94,8 +98,24 @@ export class AIOStreams {
const { parsedStreams, errorStreams } =
await this.getParsedStreams(streamRequest);
console.log(
`|INF| addon > getStreams: Got ${parsedStreams.length} parsed streams and ${errorStreams.length} error streams in ${getTimeTakenSincePoint(startTime)}`
const skipReasons = {
excludeKeywords: 0,
requiredKeywords: 0,
excludeLanguages: 0,
excludeResolutions: 0,
excludeQualities: 0,
excludeEncodes: 0,
excludeAudioTags: 0,
excludeVisualTags: 0,
excludeStreamTypes: 0,
excludeCached: 0,
sizeFilters: 0,
duplicateStreams: 0,
streamLimiters: 0,
};
logger.info(
`Got ${parsedStreams.length} parsed streams and ${errorStreams.length} error streams in ${getTimeTakenSincePoint(startTime)}`
);
const filterStartTime = new Date().getTime();
@@ -120,8 +140,8 @@ export class AIOStreams {
: null;
excludeRegex || strictIncludeRegex
? console.log(
`|INF| addon > getStreams: Created regex filters: excludeRegex: ${excludeRegex}, strictIncludeRegex: ${strictIncludeRegex}`
? logger.debug(
`Using keyword regex filters: excludeRegex: ${excludeRegex}, strictIncludeRegex: ${strictIncludeRegex}`
)
: null;
@@ -129,17 +149,26 @@ export class AIOStreams {
const streamTypeFilter = this.config.streamTypes?.find(
(streamType) => streamType[parsedStream.type] === false
);
if (this.config.streamTypes && streamTypeFilter) return false;
if (this.config.streamTypes && streamTypeFilter) {
skipReasons.excludeStreamTypes++;
return false;
}
const resolutionFilter = this.config.resolutions?.find(
(resolution) => resolution[parsedStream.resolution] === false
);
if (resolutionFilter) return false;
if (resolutionFilter) {
skipReasons.excludeResolutions++;
return false;
}
const qualityFilter = this.config.qualities?.find(
(quality) => quality[parsedStream.quality] === false
);
if (this.config.qualities && qualityFilter) return false;
if (this.config.qualities && qualityFilter) {
skipReasons.excludeQualities++;
return false;
}
// Check for HDR and DV tags in the parsed stream
const hasHDR = parsedStream.visualTags.some((tag) =>
@@ -156,6 +185,7 @@ export class AIOStreams {
if (hasHDRAndDV) {
if (!HDRAndDVEnabled) {
skipReasons.excludeVisualTags++;
return false;
}
} else if (hasHDR) {
@@ -166,16 +196,21 @@ export class AIOStreams {
(tag) => isTagDisabled(tag) === true
);
if (disabledTags.length > 0) {
return false;
skipReasons.excludeVisualTags++;
return;
}
} else if (hasDV && isTagDisabled('DV')) {
skipReasons.excludeVisualTags++;
return false;
}
// Check other visual tags for explicit disabling
for (const tag of parsedStream.visualTags) {
if (tag.startsWith('HDR') || tag === 'DV') continue;
if (isTagDisabled(tag)) return false;
if (isTagDisabled(tag)) {
skipReasons.excludeVisualTags++;
return false;
}
}
// apply excludedLanguages filter
@@ -186,6 +221,7 @@ export class AIOStreams {
excludedLanguages.includes(lang)
)
) {
skipReasons.excludeLanguages++;
return false;
}
} else if (
@@ -193,74 +229,94 @@ export class AIOStreams {
excludedLanguages.includes('Unknown') &&
parsedStream.languages.length === 0
) {
skipReasons.excludeLanguages++;
return false;
}
const audioTagFilter = parsedStream.audioTags.find((tag) =>
this.config.audioTags.some((audioTag) => audioTag[tag] === false)
);
if (audioTagFilter) return false;
if (audioTagFilter) {
skipReasons.excludeAudioTags++;
return false;
}
if (
parsedStream.encode &&
this.config.encodes.some(
(encode) => encode[parsedStream.encode] === false
)
)
) {
skipReasons.excludeEncodes++;
return false;
}
if (
this.config.onlyShowCachedStreams &&
parsedStream.provider &&
!parsedStream.provider.cached
)
) {
skipReasons.excludeCached++;
return false;
}
if (
this.config.minSize &&
parsedStream.size &&
parsedStream.size < this.config.minSize
)
) {
skipReasons.sizeFilters++;
return false;
}
if (
this.config.maxSize &&
parsedStream.size &&
parsedStream.size > this.config.maxSize
)
) {
skipReasons.sizeFilters++;
return false;
}
if (
streamRequest.type === 'movie' &&
this.config.maxMovieSize &&
parsedStream.size &&
parsedStream.size > this.config.maxMovieSize
)
) {
skipReasons.sizeFilters++;
return false;
}
if (
streamRequest.type === 'movie' &&
this.config.minMovieSize &&
parsedStream.size &&
parsedStream.size < this.config.minMovieSize
)
) {
skipReasons.sizeFilters++;
return false;
}
if (
streamRequest.type === 'series' &&
this.config.maxEpisodeSize &&
parsedStream.size &&
parsedStream.size > this.config.maxEpisodeSize
)
) {
skipReasons.sizeFilters++;
return false;
}
if (
streamRequest.type === 'series' &&
this.config.minEpisodeSize &&
parsedStream.size &&
parsedStream.size < this.config.minEpisodeSize
)
) {
skipReasons.sizeFilters++;
return false;
}
// apply keyword filters
if (
@@ -269,9 +325,11 @@ export class AIOStreams {
excludeRegex
) {
if (parsedStream.filename && excludeRegex.test(parsedStream.filename)) {
skipReasons.excludeKeywords++;
return false;
}
if (parsedStream.indexers && excludeRegex.test(parsedStream.indexers)) {
skipReasons.excludeKeywords++;
return false;
}
}
@@ -285,14 +343,15 @@ export class AIOStreams {
parsedStream.filename &&
!strictIncludeRegex.test(parsedStream.filename)
) {
skipReasons.requiredKeywords++;
return false;
}
}
return true;
});
console.log(
`|INF| addon > getStreams: Initial filter to ${filteredResults.length} streams in ${getTimeTakenSincePoint(filterStartTime)}`
logger.info(
`Initial filter to ${filteredResults.length} streams in ${getTimeTakenSincePoint(filterStartTime)}`
);
if (this.config.cleanResults) {
@@ -332,16 +391,14 @@ export class AIOStreams {
const cleanResultsStartTime = new Date().getTime();
// Deduplication by normalised filename
const cleanResultsByFilenameStartTime = new Date().getTime();
console.log(
`|INF| addon > cleaner: Received ${initialStreams.length} streams to clean`
);
logger.info(`Received ${initialStreams.length} streams to clean`);
const streamsGroupedByFilename = groupStreamsByKey(
initialStreams,
(stream) => normaliseFilename(stream.filename)
);
console.log(
`|INF| addon > cleaner: Found ${Object.keys(streamsGroupedByFilename).length} unique filenames`
logger.info(
`Found ${Object.keys(streamsGroupedByFilename).length} unique filenames`
);
// Process grouped streams by filename
@@ -349,8 +406,8 @@ export class AIOStreams {
streamsGroupedByFilename
);
console.log(
`|INF| addon > cleaner: Deduplicated streams by filename to ${cleanedStreamsByFilename.length} streams in ${getTimeTakenSincePoint(cleanResultsByFilenameStartTime)}`
logger.info(
`Deduplicated streams by filename to ${cleanedStreamsByFilename.length} streams in ${getTimeTakenSincePoint(cleanResultsByFilenameStartTime)}`
);
// Deduplication by hash
@@ -360,22 +417,24 @@ export class AIOStreams {
cleanedStreamsByFilename,
(stream) => stream._infoHash
);
console.log(
`|INF| addon > cleaner: Found ${Object.keys(streamsGroupedByHash).length} unique hashes with ${cleanedStreamsByFilename.length - Object.values(streamsGroupedByHash).reduce((sum, group) => sum + group.length, 0)} streams not grouped`
logger.info(
`Found ${Object.keys(streamsGroupedByHash).length} unique hashes with ${cleanedStreamsByFilename.length - Object.values(streamsGroupedByHash).reduce((sum, group) => sum + group.length, 0)} streams not grouped`
);
// Process grouped streams by hash
const cleanedStreamsByHash =
await this.processGroupedStreams(streamsGroupedByHash);
console.log(
`|INF| addon > cleaner: Deduplicated streams by hash to ${cleanedStreamsByHash.length} streams in ${getTimeTakenSincePoint(cleanResultsByHashStartTime)}`
logger.info(
`Deduplicated streams by hash to ${cleanedStreamsByHash.length} streams in ${getTimeTakenSincePoint(cleanResultsByHashStartTime)}`
);
cleanedStreams.push(...cleanedStreamsByHash);
console.log(
`|INF| addon > cleaner: Deduplicated streams to ${cleanedStreams.length} streams in ${getTimeTakenSincePoint(cleanResultsStartTime)}`
logger.info(
`Deduplicated streams to ${cleanedStreams.length} streams in ${getTimeTakenSincePoint(cleanResultsStartTime)}`
);
skipReasons.duplicateStreams =
filteredResults.length - cleanedStreams.length;
filteredResults = cleanedStreams;
}
// Apply sorting
@@ -403,9 +462,7 @@ export class AIOStreams {
return 0;
});
console.log(
`|INF| addon > getStreams: Sorted results in ${getTimeTakenSincePoint(sortStartTime)}`
);
logger.info(`Sorted results in ${getTimeTakenSincePoint(sortStartTime)}`);
// apply config.maxResultsPerResolution
if (this.config.maxResultsPerResolution) {
@@ -423,14 +480,34 @@ export class AIOStreams {
return false;
});
skipReasons.streamLimiters =
filteredResults.length - limitedResults.length;
filteredResults = limitedResults;
console.log(
`|INF| addon > getStreams: Limited results to ${limitedResults.length} streams after applying maxResultsPerResolution in ${new Date().getTime() - startTime}ms`
logger.info(
`Limited results to ${limitedResults.length} streams after applying maxResultsPerResolution in ${new Date().getTime() - startTime}ms`
);
}
const totalSkipped = Object.values(skipReasons).reduce(
(acc, val) => acc + val,
0
);
const reportLines = [
'╔═══════════════════════╤════════════╗',
'║ Skip Reason │ Count ║',
'╟───────────────────────┼────────────╢',
...Object.entries(skipReasons).map(
([reason, count]) =>
`║ ${reason.padEnd(21)} │ ${String(count).padStart(10)} ║`
),
'╟───────────────────────┼────────────╢',
`║ Total Skipped │ ${String(totalSkipped).padStart(10)} ║`,
'╚═══════════════════════╧════════════╝',
];
logger.info('\n' + reportLines.join('\n'));
// Create stream objects
const streamsStartTime = new Date().getTime();
const streamObjects = await Promise.all(
@@ -443,11 +520,11 @@ export class AIOStreams {
...errorStreams.map((e) => errorStream(e.error, e.addon.name))
);
console.log(
`|INF| addon > getStreams: Created ${streams.length} stream objects in ${getTimeTakenSincePoint(streamsStartTime)}`
logger.info(
`Created ${streams.length} stream objects in ${getTimeTakenSincePoint(streamsStartTime)}`
);
console.log(
`|INF| addon > getStreams: Total time taken to serve streams: ${getTimeTakenSincePoint(startTime)}`
logger.info(
`Total time taken to get streams: ${getTimeTakenSincePoint(startTime)}`
);
return streams;
}
@@ -458,8 +535,9 @@ export class AIOStreams {
description: string
): Stream {
if (!parsedStream.url) {
console.error(
`|ERR| addon > createMediaFlowStream: Stream URL is missing, cannot proxy a stream without a URL`
logger.error(
`Stream URL is missing, cannot proxy a stream without a URL`,
{ func: 'createMediaFlowStream' }
);
throw new Error('Stream URL is missing');
}
@@ -506,8 +584,8 @@ export class AIOStreams {
if (!mediaFlowConfig.mediaFlowEnabled) return false;
if (!stream.url) return false;
// // now check if mediaFlowConfig.proxiedAddons or mediaFlowConfig.proxiedServices is not null
// console.log(this.config.mediaFlowConfig?.proxiedAddons);
// console.log(stream.addon.id);
// logger.info(this.config.mediaFlowConfig?.proxiedAddons);
// logger.info(stream.addon.id);
if (
mediaFlowConfig.proxiedAddons &&
mediaFlowConfig.proxiedAddons.length > 0 &&
@@ -603,9 +681,7 @@ export class AIOStreams {
}
return mediaFlowStream;
} catch (error) {
console.error(
`|ERR| addon > createStreamObject: Failed to create MediaFlow stream URL: ${error}`
);
logger.error(`Failed to create MediaFlow stream URL: ${error}`);
return null;
}
}
@@ -892,7 +968,6 @@ export class AIOStreams {
addon.options.overrideName ||
addonDetails.find((addonDetail) => addonDetail.id === addon.id)?.name ||
addon.id;
console.log(addon.options);
const addonId = `${addon.id}-${JSON.stringify(addon.options)}`;
try {
const startTime = new Date().getTime();
@@ -908,13 +983,11 @@ export class AIOStreams {
addon: { id: addonId, name: addonName },
}))
);
console.log(
`|INF| addon > getParsedStreams: Got ${addonStreams.length} streams ${addonErrors.length > 0 ? `and ${addonErrors.length} errors ` : ''}from addon ${addonName} in ${getTimeTakenSincePoint(startTime)}`
logger.info(
`Got ${addonStreams.length} streams ${addonErrors.length > 0 ? `and ${addonErrors.length} errors ` : ''}from addon ${addonName} in ${getTimeTakenSincePoint(startTime)}`
);
} catch (error: any) {
console.error(
`|ERR| addon > getParsedStreams: Failed to get streams from ${addonName}: ${error}`
);
logger.error(`Failed to get streams from ${addonName}: ${error}`);
errorStreams.push({
error: formatError(error.message ?? error ?? 'Unknown error'),
addon: {
@@ -1075,16 +1148,16 @@ export class AIOStreams {
return;
}
/*console.log(
/*logger.info(
`==================\nDetermining unique streams for ${groupedStreams[0].filename} from ${groupedStreams.length} total duplicates`
);
console.log(
logger.info(
groupedStreams.map(
(stream) =>
`Addon ID: ${stream.addon.id}, Provider ID: ${stream.provider?.id}, Provider Cached: ${stream.provider?.cached}, type: ${stream.torrent ? 'torrent' : 'usenet'}`
)
);
console.log('==================');*/
logger.info('==================');*/
// Separate streams into categories
const cachedStreams = groupedStreams.filter(
(stream) => stream.provider?.cached || (!stream.provider && stream.url)
@@ -1119,7 +1192,7 @@ export class AIOStreams {
return aIndex - bIndex;
})[0];
});
//selectedUncachedStreams.forEach(stream => console.log(`Selected uncached stream for provider ${stream.provider!.id}: Addon ID: ${stream.addon.id}`));
//selectedUncachedStreams.forEach(stream => logger.info(`Selected uncached stream for provider ${stream.provider!.id}: Addon ID: ${stream.addon.id}`));
// Select cached streams by provider and addon priority
const selectedCachedStream = cachedStreams.sort((a, b) => {
@@ -1175,11 +1248,11 @@ export class AIOStreams {
// Combine selected streams for this group
if (selectedNoProviderStream) {
//console.log(`Selected no provider stream: Addon ID: ${selectedNoProviderStream.addon.id}`);
//logger.info(`Selected no provider stream: Addon ID: ${selectedNoProviderStream.addon.id}`);
uniqueStreams.push(selectedNoProviderStream);
}
if (selectedCachedStream) {
//console.log(`Selected cached stream for provider ${selectedCachedStream.provider!.id} from Addon ID: ${selectedCachedStream.addon.id}`);
//logger.info(`Selected cached stream for provider ${selectedCachedStream.provider!.id} from Addon ID: ${selectedCachedStream.addon.id}`);
uniqueStreams.push(selectedCachedStream);
}
uniqueStreams.push(...selectedUncachedStreams);
+85 -78
View File
@@ -19,17 +19,21 @@ import {
decompressData,
decryptData,
uncrushJson,
loadSecretKey,
createLogger,
getTimeTakenSincePoint,
} from '@aiostreams/utils';
const logger = createLogger('server');
const app = express();
console.log(`|INF| server > init: Starting server and loading settings...`);
//logger.info(`Starting server and loading settings...`);
logger.info('Starting server and loading settings...', { func: 'init' });
Object.entries(Settings).forEach(([key, value]) => {
switch (key) {
case 'SECRET_KEY':
if (value) {
console.log(
`|INF| server > init: ${key} = ${value.replace(/./g, '*').slice(0, 32)}`
);
logger.info(`${key} = ${value.replace(/./g, '*').slice(0, 64)}`);
}
break;
@@ -39,30 +43,36 @@ Object.entries(Settings).forEach(([key, value]) => {
break;
default:
console.log(`|INF| server > init: ${key} = ${value}`);
logger.info(`${key} = ${value}`);
}
});
if (!Settings.SECRET_KEY) {
console.warn(
'|WRN| server > init: SECRET_KEY is not set, data encryption is disabled!'
);
}
let CUSTOM_CONFIGS: Record<string, string> = {};
if (Settings.CUSTOM_CONFIGS) {
try {
CUSTOM_CONFIGS = JSON.parse(Settings.CUSTOM_CONFIGS);
console.log(
`|INF| server > init: Loaded ${Object.keys(CUSTOM_CONFIGS).length} custom configs under aliases ${Object.keys(CUSTOM_CONFIGS).join(', ')}`
logger.info(
`Loaded ${Object.keys(CUSTOM_CONFIGS).length} custom configs under aliases ${Object.keys(CUSTOM_CONFIGS).join(', ')}`
);
} catch (error: any) {
console.error(
`|ERR| server > init: CUSTOM_CONFIGS is not valid JSON: ${error.message}`
);
logger.error(`CUSTOM_CONFIGS is not valid JSON: ${error.message}`);
}
}
// attempt to load the secret key
try {
if (Settings.SECRET_KEY) loadSecretKey();
} catch (error: any) {
// determine command to run based on system OS
const command =
process.platform === 'win32'
? '[System.Guid]::NewGuid().ToString("N").Substring(0, 32) + [System.Guid]::NewGuid().ToString("N").Substring(0, 32)'
: 'openssl rand -hex 32';
logger.error(
`The secret key is invalid. You will not be able to generate configurations. You can generate a new secret key by running the following command\n${command}`
);
}
const cache = new Cache(Settings.MAX_CACHE_SIZE);
// Built-in middleware for parsing JSON
@@ -72,16 +82,19 @@ app.use(express.urlencoded({ extended: true }));
// unhandled errors
app.use((err: any, req: Request, res: Response, next: any) => {
console.error(`|ERR| server > ${err.message}`);
logger.error(`${err.message}`);
res.status(500).send('Internal server error');
});
app.use((req, res, next) => {
res.append('Access-Control-Allow-Origin', '*');
res.append('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
console.log(
`|DBG| server > ${req.method} ${req.path.replace(/\/eyJ[\w\=]+/g, '/*******').replace(/\/E2?-[\w-\%]+/g, '/*******')}`
);
const start = Date.now();
res.on('finish', () => {
logger.info(
`${req.method} ${req.path.replace(/\/ey[JI][\w\=]+/g, '/*******').replace(/\/(E2?|B)?-[\w-\%]+/g, '/*******')} - ${res.statusCode} - ${getTimeTakenSincePoint(start)}`
);
});
next();
});
@@ -118,9 +131,7 @@ app.get('/:config/configure', (req, res) => {
try {
let configJson = extractJsonConfig(config);
if (config.startsWith('E-') || config.startsWith('E2-')) {
console.log(
`|DBG| server > Encrypted config detected, encrypting credentials`
);
logger.info(`Encrypted config detected, encrypting credentials`);
configJson = encryptInfoInConfig(configJson);
}
const base64Config = Buffer.from(JSON.stringify(configJson)).toString(
@@ -128,7 +139,7 @@ app.get('/:config/configure', (req, res) => {
);
res.redirect(`/${encodeURIComponent(base64Config)}/configure`);
} catch (error: any) {
console.error(`|ERR| server > Failed to extract config: ${error.message}`);
logger.error(`Failed to extract config: ${error.message}`);
res.status(400).send('Invalid config');
}
});
@@ -142,24 +153,22 @@ app.get('/:config/manifest.json', (req, res) => {
let configJson: Config;
try {
configJson = extractJsonConfig(config);
console.log(`|DBG| server > Extracted config for manifest request`);
logger.info(`Extracted config for manifest request`);
configJson = decryptEncryptedInfoFromConfig(configJson);
if (Settings.LOG_SENSITIVE_INFO) {
console.log(`|DBG| server > Final config: ${JSON.stringify(configJson)}`);
logger.info(`Final config: ${JSON.stringify(configJson)}`);
}
console.log(
`|DBG| server > Successfully removed or decrypted sensitive info`
);
logger.info(`Successfully removed or decrypted sensitive info`);
const { valid, errorMessage } = validateConfig(configJson);
if (!valid) {
console.error(
`|ERR| server > Received invalid config for manifest request: ${errorMessage}`
logger.error(
`Received invalid config for manifest request: ${errorMessage}`
);
res.status(400).json({ error: 'Invalid config', message: errorMessage });
return;
}
} catch (error: any) {
console.error(`|ERR| server > Failed to extract config: ${error.message}`);
logger.error(`Failed to extract config: ${error.message}`);
res.status(400).json({ error: 'Invalid config' });
return;
}
@@ -184,16 +193,14 @@ app.get('/:config/stream/:type/:id.json', (req, res: Response): void => {
let configJson: Config;
try {
configJson = extractJsonConfig(config);
console.log(`|DBG| server > Extracted config for stream request`);
logger.info(`Extracted config for stream request`);
configJson = decryptEncryptedInfoFromConfig(configJson);
if (Settings.LOG_SENSITIVE_INFO) {
console.log(`|DBG| server > Final config: ${JSON.stringify(configJson)}`);
logger.info(`Final config: ${JSON.stringify(configJson)}`);
}
console.log(
`|DBG| server > Successfully removed or decrypted sensitive info`
);
logger.info(`Successfully removed or decrypted sensitive info`);
} catch (error: any) {
console.error(`|ERR| server > Failed to extract config: ${error.message}`);
logger.error(`Failed to extract config: ${error.message}`);
res.json(
errorResponse(
`${error.message}, please check the logs or click this stream to create an issue on GitHub`,
@@ -205,10 +212,10 @@ app.get('/:config/stream/:type/:id.json', (req, res: Response): void => {
return;
}
console.log(`|DBG| server > Requesting streams for ${type} ${id}`);
logger.info(`Requesting streams for ${type} ${id}`);
if (type !== 'movie' && type !== 'series') {
console.error(`|ERR| server > Invalid type for stream request`);
logger.error(`Invalid type for stream request`);
res.json(
errorResponse(
'Invalid type for stream request, must be movie or series',
@@ -223,9 +230,7 @@ app.get('/:config/stream/:type/:id.json', (req, res: Response): void => {
try {
const { valid, errorCode, errorMessage } = validateConfig(configJson);
if (!valid) {
console.error(
`|ERR| server > Received invalid config: ${errorCode} - ${errorMessage}`
);
logger.error(`Received invalid config: ${errorCode} - ${errorMessage}`);
res.json(
errorResponse(errorMessage ?? 'Unknown', rootUrl(req), '/configure')
);
@@ -244,7 +249,7 @@ app.get('/:config/stream/:type/:id.json', (req, res: Response): void => {
res.json({ streams: streams });
})
.catch((error: any) => {
console.error(`|ERR| server > Internal addon error: ${error.message}`);
logger.error(`Internal addon error: ${error.message}`);
res.json(
errorResponse(
'An unexpected error occurred, please check the logs or create an issue on GitHub',
@@ -255,7 +260,7 @@ app.get('/:config/stream/:type/:id.json', (req, res: Response): void => {
);
});
} catch (error: any) {
console.error(`|ERR| server > Internal addon error: ${error.message}`);
logger.error(`Internal addon error: ${error.message}`);
res.json(
errorResponse(
'An unexpected error occurred, please check the logs or create an issue on GitHub',
@@ -271,7 +276,7 @@ app.post('/encrypt-user-data', (req, res) => {
const { data } = req.body;
let finalString: string = '';
if (!data) {
console.error('|ERR| server > /encrypt-user-data: No data provided');
logger.error('/encrypt-user-data: No data provided');
res.json({ success: false, message: 'No data provided' });
return;
}
@@ -280,16 +285,14 @@ app.post('/encrypt-user-data', (req, res) => {
const config = JSON.parse(data);
const { valid, errorCode, errorMessage } = validateConfig(config);
if (!valid) {
console.error(
`|ERR| server > generateConfig: Invalid config: ${errorCode} - ${errorMessage}`
logger.error(
`generateConfig: Invalid config: ${errorCode} - ${errorMessage}`
);
res.json({ success: false, message: errorMessage, error: errorMessage });
return;
}
} catch (error: any) {
console.error(
`|ERR| server > /encrypt-user-data: Invalid JSON: ${error.message}`
);
logger.error(`/encrypt-user-data: Invalid JSON: ${error.message}`);
res.json({ success: false, message: 'Malformed configuration' });
return;
}
@@ -306,28 +309,28 @@ app.post('/encrypt-user-data', (req, res) => {
finalString = `E2-${encodeURIComponent(iv)}-${encodeURIComponent(data)}`;
}
console.log(
logger.info(
`|INF| server > /encrypt-user-data: Encrypted user data, compression report:`
);
console.log(`+--------------------------------------------+`);
console.log(`| Original: ${data.length} bytes`);
console.log(`| URL Encoded: ${encodeURIComponent(data).length} bytes`);
console.log(`| Minified: ${JSON.stringify(minified).length} bytes`);
console.log(`| Crushed: ${crushed.length} bytes`);
console.log(`| Compressed: ${compressed.length} bytes`);
console.log(`| Final String: ${finalString.length} bytes`);
console.log(
logger.info(`+--------------------------------------------+`);
logger.info(`| Original: ${data.length} bytes`);
logger.info(`| URL Encoded: ${encodeURIComponent(data).length} bytes`);
logger.info(`| Minified: ${JSON.stringify(minified).length} bytes`);
logger.info(`| Crushed: ${crushed.length} bytes`);
logger.info(`| Compressed: ${compressed.length} bytes`);
logger.info(`| Final String: ${finalString.length} bytes`);
logger.info(
`| Ratio: ${((finalString.length / data.length) * 100).toFixed(2)}%`
);
console.log(
logger.info(
`| Reduction: ${data.length - finalString.length} bytes (${(((data.length - finalString.length) / data.length) * 100).toFixed(2)}%)`
);
console.log(`+--------------------------------------------+`);
logger.info(`+--------------------------------------------+`);
res.json({ success: true, data: finalString });
} catch (error: any) {
console.error(`|ERR| server > /encrypt-user-data: ${error.message}`);
console.error(error);
logger.error(`/encrypt-user-data: ${error.message}`);
logger.error(error);
res.json({ success: false, message: error.message });
}
});
@@ -351,7 +354,7 @@ app.use((req, res) => {
});
app.listen(Settings.PORT, () => {
console.log(`|INF| server > init: Listening on port ${Settings.PORT}`);
logger.info(`Listening on port ${Settings.PORT}`);
});
function extractJsonConfig(config: string): Config {
@@ -374,8 +377,8 @@ function extractJsonConfig(config: string): Config {
function extractCustomConfig(config: string): Config | undefined {
const customConfig = CUSTOM_CONFIGS?.[config];
if (!customConfig) return undefined;
console.log(
`|DBG| server > Found custom config for alias ${config}, attempting to extract config`
logger.info(
`Found custom config for alias ${config}, attempting to extract config`
);
return extractEncryptedOrEncodedConfig(
decodeURIComponent(customConfig),
@@ -391,7 +394,7 @@ function extractEncryptedOrEncodedConfig(
try {
if (config.startsWith('E-')) {
// compressed and encrypted (hex)
console.log(`|DBG| server > Extracting encrypted (v1) config`);
logger.info(`Extracting encrypted (v1) config`);
const parts = config.split('-');
if (parts.length !== 3) {
throw new Error('Invalid encrypted config format');
@@ -401,7 +404,7 @@ function extractEncryptedOrEncodedConfig(
decodedConfig = JSON.parse(decompressData(decryptData(data, iv)));
} else if (config.startsWith('E2-')) {
// minified, crushed, compressed and encrypted (base64)
console.log(`|DBG| server > Extracting encrypted (v2) config`);
logger.info(`Extracting encrypted (v2) config`);
const parts = config.split('-');
if (parts.length !== 3) {
throw new Error('Invalid encrypted config format');
@@ -414,9 +417,7 @@ function extractEncryptedOrEncodedConfig(
decodedConfig = unminifyConfig(JSON.parse(minifiedConfig));
} else if (config.startsWith('B-')) {
// minifed, crushed, compressed, base64 encoded
console.log(
`|DBG| server > Extracting base64 encoded and compressed config`
);
logger.info(`Extracting base64 encoded and compressed config`);
decodedConfig = unminifyConfig(
JSON.parse(
uncrushJson(decompressData(Buffer.from(config.slice(2), 'base64')))
@@ -424,15 +425,17 @@ function extractEncryptedOrEncodedConfig(
);
} else {
// plain base64 encoded
console.log(`|DBG| server > Extracting plain base64 encoded config`);
logger.info(`Extracting plain base64 encoded config`);
decodedConfig = JSON.parse(
Buffer.from(config, 'base64').toString('utf-8')
);
}
return decodedConfig;
} catch (error: any) {
console.error(`|ERR| Failed to parse ${label}: ${error.message}`);
console.error(error);
logger.error(`Failed to parse ${label}: ${error.message}`, {
func: 'extractJsonConfig',
});
logger.error(error, { func: 'extractJsonConfig' });
throw new Error(`Failed to parse ${label}`);
}
}
@@ -556,7 +559,7 @@ function processObjectValues(
Object.keys(obj).forEach((key) => {
const value = obj[key];
if (condition(key, value)) {
console.log(`|DBG| Processing ${labelPrefix} ${key}`);
logger.debug(`Processing ${labelPrefix} ${key}`);
obj[key] = decrypt
? decryptValue(value, `${labelPrefix} ${key}`)
: encryptValue(value, `${labelPrefix} ${key}`);
@@ -570,7 +573,8 @@ function encryptValue(value: any, label: string): any {
const { iv, data } = encryptData(compressData(value));
return `E2-${iv}-${data}`;
} catch (error: any) {
console.error(`Failed to encrypt ${label}`);
logger.error(`Failed to encrypt ${label}`, { func: 'encryptValue' });
logger.error(error, { func: 'encryptValue' });
return '';
}
}
@@ -584,7 +588,10 @@ function decryptValue(value: any, label: string): any {
if (decrypted === null) throw new Error('Decryption failed');
return decrypted;
} catch (error: any) {
console.error(`|ERR| Failed to decrypt ${label}: ${error.message}`);
logger.error(`Failed to decrypt ${label}: ${error.message}`, {
func: 'decryptValue',
});
logger.error(error, { func: 'decryptValue' });
throw new Error('Failed to decrypt config');
}
}
+1 -1
View File
@@ -17,4 +17,4 @@
"typescript": "^5.5.2",
"wrangler": "^3.99.0"
}
}
}
+8
View File
@@ -25,6 +25,14 @@ const nextConfig: NextConfig = {
env: {
NEXT_PUBLIC_BRANDING: branding,
},
webpack(config) {
config.resolve.fallback = {
...config.resolve.fallback, // if you miss it, all the other options in fallback, specified
// by next.js will be dropped. Doesn't make much sense, but how it is
fs: false, // the solution
};
return config;
},
};
export default nextConfig;
@@ -69,6 +69,7 @@ const defaultVisualTags: VisualTag[] = [
{ '3D': true },
{ IMAX: true },
{ AI: true },
{ SDR: true },
];
const defaultAudioTags: AudioTag[] = [
@@ -487,6 +488,7 @@ export default function Configure() {
function loadFromConfig(decodedConfig: Config) {
console.log('Loaded config', decodedConfig);
setOverrideName(decodedConfig.overrideName || '');
setStreamTypes(
loadValidValuesFromObject(decodedConfig.streamTypes, defaultStreamTypes)
);
+1
View File
@@ -45,6 +45,7 @@ export const PARSE_REGEX = {
'3D': createRegex('(bd)?(3|three)[ .\\-_]?(d(imension)?(al)?)'),
IMAX: createRegex('imax'),
AI: createRegex('ai[ .\\-_]?(upscale|enhanced|remaster)?'),
SDR: createRegex('sdr'),
},
audioTags: {
Atmos: createRegex('atmos'),
+2 -1
View File
@@ -8,6 +8,7 @@
},
"description": "Library to parse a torrent filename ",
"dependencies": {
"jsoncrush": "^1.1.8"
"jsoncrush": "^1.1.8",
"winston": "^3.17.0"
}
}
+33 -15
View File
@@ -7,6 +7,36 @@ import {
import { deflateSync, inflateSync } from 'zlib';
import { Settings } from './settings';
import JSONCrush from 'jsoncrush';
import { createLogger } from './logger';
const logger = createLogger('crypto');
export const loadSecretKey = (): Buffer | string => {
const secretKey = Settings.SECRET_KEY;
if (!secretKey) {
console.error('No secret key provided');
throw new Error('No secret key provided');
}
// must be 64 characters long and hex
if (secretKey.length === 32) {
// backwards compatibility
logger.warn(
'Secret key is 32 characters long, consider updating to a 64 character key and reconfiguring for better security'
);
return secretKey;
} else if (secretKey.length !== 64) {
logger.error('Secret key must be 64 characters long');
throw new Error('Secret key must be 64 characters long');
}
if (!/^[0-9a-fA-F]+$/.test(secretKey)) {
logger.error('Secret key must be a hex string (0-9, a-f)');
throw new Error('Secret key must be a hex string (0-9, a-f)');
}
return Buffer.from(secretKey, 'hex');
};
const pad = (data: Buffer, blockSize: number): Buffer => {
const padding = blockSize - (data.length % blockSize);
@@ -37,11 +67,7 @@ export const decompressData = (data: Buffer): string => {
};
export const encryptData = (data: Buffer): { iv: string; data: string } => {
const secretKey = Settings.SECRET_KEY;
if (!secretKey) {
console.error('|ERR| crypto > compressAndEncrypt > No secret key provided');
throw new Error('No secret key provided');
}
const secretKey = loadSecretKey();
// Then encrypt the compressed data
const iv = randomBytes(16);
@@ -61,13 +87,7 @@ export const encryptData = (data: Buffer): { iv: string; data: string } => {
};
export const decryptData = (encryptedData: Buffer, iv: Buffer): Buffer => {
const secretKey = Settings.SECRET_KEY;
if (!secretKey) {
console.error(
'|ERR| crypto > decryptAndDecompress > No secret key provided'
);
throw new Error('No secret key provided');
}
const secretKey = loadSecretKey();
const decipher = createDecipheriv('aes-256-cbc', secretKey, iv);
// Decrypt the data
@@ -100,9 +120,7 @@ export function parseAndDecryptString(data: string): string | null {
}
return data;
} catch (error: any) {
console.error(
`|ERR| crypto > parseAndDecryptString > Failed to decrypt data: ${error.message}`
);
logger.error(`Failed to decrypt data: ${error.message}`);
return null;
}
}
+1
View File
@@ -4,3 +4,4 @@ export * from './settings';
export * from './mediaflow';
export * from './cache';
export * from './general';
export * from './logger';
+80
View File
@@ -0,0 +1,80 @@
import winston from 'winston';
import { Settings } from './settings';
// Map log levels to short labels and emojis
const levelMap: { [key: string]: string } = {
error: 'ERR',
warn: 'WRN',
info: 'INF',
debug: 'DBG',
};
const moduleMap: { [key: string]: string } = {
server: '🌐 SERVER',
wrappers: '📦 WRAPPERS',
crypto: '🔒 CRYPTO',
addon: '🧩 ADDON',
parser: '🔍 PARSER',
mediaflow: '🌊 MEDIAFLOW',
};
// Define colors for each log level
const levelColors: { [key: string]: string } = {
ERR: 'red',
WRN: 'yellow',
INF: 'cyan',
DBG: 'magenta',
};
const emojiLevelMap: { [key: string]: string } = {
error: '🛑',
warn: '⚠️ ',
info: '🔵',
debug: '🐞',
};
// Apply colors to Winston
winston.addColors(levelColors);
export const createLogger = (module: string) => {
// cloudflare workers do not play nice with winston
// so we disable winston logs in cloudflare workers
try {
__dirname;
} catch (e) {
return console;
}
const isJsonFormat = Settings.LOG_FORMAT === 'json';
return winston.createLogger({
level: Settings.LOG_LEVEL || 'info', // Default to 'info' if not set
format: isJsonFormat
? winston.format.combine(
winston.format.timestamp(),
winston.format.json() // Use JSON format when LOG_FORMAT=json
)
: winston.format.combine(
winston.format.timestamp({
format: 'YYYY-MM-DD HH:mm:ss.SSS',
}),
winston.format.printf(({ timestamp, level, message, func }) => {
const emoji = emojiLevelMap[level] || '';
const formattedModule = moduleMap[module] || module;
level = levelMap[level] || level.toUpperCase();
// Apply color to the level
const coloredLevel = winston.format
.colorize()
.colorize(level, `${level}`);
const formatLine = (line: unknown) => {
return `${emoji} [${timestamp}] | ${coloredLevel} | ${formattedModule}${
func ? ' (' + func + ')' : ''
} > ${line}`;
};
if (typeof message === 'string') {
return message.split('\n').map(formatLine).join('\n');
}
return formatLine(message);
})
),
transports: [new winston.transports.Console()],
});
};
+14 -27
View File
@@ -3,6 +3,9 @@ import path from 'path';
import { Settings } from './settings';
import { getTextHash } from './crypto';
import { Cache } from './cache';
import { createLogger } from './logger';
const logger = createLogger('mediaflow');
const PRIVATE_CIDR = /^(10\.|127\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.)/;
@@ -15,21 +18,15 @@ export function createProxiedMediaFlowUrl(
}
) {
if (!url) {
console.error(
'|ERR| mediaflow > createProxiedMediaFlowUrl > streamUrl is missing, could not create proxied URL'
);
logger.error('streamUrl is missing, could not create proxied URL');
throw new Error('Stream URL is missing');
}
if (!mediaFlowConfig) {
console.error(
'|ERR| mediaflow > createProxiedMediaFlowUrl > mediaFlowConfig is missing'
);
logger.error('mediaFlowConfig is missing');
throw new Error('MediaFlow configuration is missing');
}
if (!mediaFlowConfig?.proxyUrl || !mediaFlowConfig?.apiPassword) {
console.error(
'|ERR| mediaflow > createProxiedMediaFlowUrl > mediaFlowUrl or API password is missing'
);
logger.error('mediaFlowUrl or API password is missing');
throw new Error('MediaFlow URL or API password is missing');
}
@@ -69,16 +66,12 @@ export async function getMediaFlowPublicIp(
) {
try {
if (!mediaFlowConfig) {
console.error(
'|ERR| mediaflow > getMediaFlowPublicIp > mediaFlowConfig is missing'
);
logger.error('mediaFlowConfig is missing');
throw new Error('MediaFlow configuration is missing');
}
if (!mediaFlowConfig?.proxyUrl) {
console.error(
'|ERR| mediaflow > getMediaFlowPublicIp > mediaFlowUrl is missing'
);
logger.error('mediaFlowUrl is missing');
throw new Error('MediaFlow URL is missing');
}
@@ -89,8 +82,8 @@ export async function getMediaFlowPublicIp(
const mediaFlowUrl = new URL(mediaFlowConfig.proxyUrl.replace(/\/$/, ''));
if (PRIVATE_CIDR.test(mediaFlowUrl.hostname)) {
// MediaFlow proxy URL is a private IP address
console.debug(
'|DBG| mediaflow > getMediaFlowPublicIp > MediaFlow proxy URL is a private IP address so returning null'
logger.debug(
'MediaFlow proxy URL is a private IP address so returning null'
);
return null;
}
@@ -100,9 +93,7 @@ export async function getMediaFlowPublicIp(
);
const cachedPublicIp = cache ? cache.get(cacheKey) : null;
if (cachedPublicIp) {
console.debug(
`|DBG| mediaflow > getMediaFlowPublicIp > Returning cached public IP`
);
logger.debug(`Returning cached public IP`);
return cachedPublicIp;
}
@@ -114,13 +105,9 @@ export async function getMediaFlowPublicIp(
}).toString();
if (Settings.LOG_SENSITIVE_INFO) {
console.debug(
`|DBG| mediaflow > getMediaFlowPublicIp > GET ${proxyIpUrl.toString()}`
);
logger.debug(`GET ${proxyIpUrl.toString()}`);
} else {
console.debug(
'|DBG| mediaflow > getMediaFlowPublicIp > GET /proxy/ip?api_password=***'
);
logger.debug('GET /proxy/ip?api_password=***');
}
const response = await fetch(proxyIpUrl.toString(), {
@@ -142,7 +129,7 @@ export async function getMediaFlowPublicIp(
}
return publicIp;
} catch (error: any) {
console.error(`|ERR| mediaflow > getMediaFlowPublicIp > ${error.message}`);
logger.error(`${error.message}`);
return null;
}
}
+7 -1
View File
@@ -4,7 +4,7 @@ import path from 'path';
try {
dotenv.config({ path: path.resolve(__dirname, '../../../.env') });
} catch (error) {
console.error('Error loading .env file:', error);
console.error('Error loading .env file', error);
}
export class Settings {
@@ -40,6 +40,12 @@ export class Settings {
public static readonly DISABLE_TORRENTIO_MESSAGE =
process.env.DISABLE_TORRENTIO_MESSAGE ||
'The Torrentio addon has been disabled, please remove it to use this addon.';
public static readonly LOG_LEVEL = process.env.LOG_LEVEL || 'info';
public static readonly LOG_FORMAT = process.env.LOG_FORMAT
? process.env.LOG_FORMAT === 'json'
? 'json'
: 'text'
: 'text';
// Cache settings
public static readonly CACHE_STREAM_RESULTS = process.env.CACHE_STREAM_RESULTS
+30 -18
View File
@@ -8,10 +8,17 @@ import {
ParseResult,
} from '@aiostreams/types';
import { parseFilename } from '@aiostreams/parser';
import { getTextHash, serviceDetails, Settings } from '@aiostreams/utils';
import { fetch as uFetch, ProxyAgent } from 'undici';
import {
getTextHash,
serviceDetails,
Settings,
createLogger,
} from '@aiostreams/utils';
// import { fetch as uFetch, ProxyAgent } from 'undici';
import { emojiToLanguage, codeToLanguage } from '@aiostreams/formatters';
const logger = createLogger('wrappers');
export class BaseWrapper {
private readonly streamPath: string = 'stream/{type}/{id}.json';
private indexerTimeout: number;
@@ -78,8 +85,10 @@ export class BaseWrapper {
let hostname: string;
try {
hostname = new URL(url).hostname;
} catch (e) {
console.error(`|ERR| utils > shouldProxyRequest Error parsing URL`);
} catch (e: any) {
logger.error(`Error parsing URL: ${this.getLoggableUrl(url)}`, {
func: 'shouldProxyRequest',
});
return false;
}
if (!Settings.ADDON_PROXY) {
@@ -90,8 +99,11 @@ export class BaseWrapper {
for (const rule of Settings.ADDON_PROXY_CONFIG.split(',')) {
const [ruleHost, enabled] = rule.split(':');
if (['true', 'false'].includes(enabled) === false) {
console.error(
`|ERR| utils > shouldProxyRequest > Invalid rule: ${rule}`
logger.error(
`Invalid rule: ${rule}. Rule must be in the format host:enabled`,
{
func: 'shouldProxyRequest',
}
);
continue;
}
@@ -111,7 +123,7 @@ export class BaseWrapper {
return useProxy;
}
private getLoggableUrl(url: string): string {
protected getLoggableUrl(url: string): string {
let urlObj = new URL(url);
return `${urlObj.protocol}//${urlObj.hostname}/${urlObj.pathname
.split('/')
@@ -132,8 +144,8 @@ export class BaseWrapper {
let sanitisedUrl = this.getLoggableUrl(url);
let useProxy = this.shouldProxyRequest(url);
console.log(
`|DBG| wrappers > base > ${this.addonName}: Making a ${useProxy ? 'proxied' : 'direct'} request to ${sanitisedUrl} with user IP ${
logger.info(
`Making a ${useProxy ? 'proxied' : 'direct'} request to ${this.addonName} (${sanitisedUrl}) with user IP ${
userIp
? Settings.LOG_SENSITIVE_INFO
? userIp
@@ -143,8 +155,8 @@ export class BaseWrapper {
);
let response = useProxy
? uFetch(url, {
dispatcher: new ProxyAgent(Settings.ADDON_PROXY),
? fetch(url, {
// dispatcher: new ProxyAgent(Settings.ADDON_PROXY),
method: 'GET',
headers: headers,
signal: AbortSignal.timeout(this.indexerTimeout),
@@ -163,8 +175,8 @@ export class BaseWrapper {
const requestCacheKey = getTextHash(url);
const cachedStreams = cache ? cache.get(requestCacheKey) : undefined;
if (cachedStreams) {
console.debug(
`|DBG| wrappers > base > ${this.addonName}: Returning cached streams for ${this.getLoggableUrl(url)}`
logger.info(
`Returning cached streams for ${this.addonName} (${this.getLoggableUrl(url)})`
);
return cachedStreams;
}
@@ -195,10 +207,10 @@ export class BaseWrapper {
let message = error.message;
if (error.name === 'TimeoutError') {
message = `The stream request to ${this.addonName} timed out after ${this.indexerTimeout}ms`;
return Promise.reject(new Error(message));
return Promise.reject(message);
}
console.error(`|ERR| wrappers > base > ${this.addonName}: ${message}`);
console.error(error);
logger.error(`Error during fetch for ${this.addonName}: ${message}`);
logger.error(error);
return Promise.reject(error.message);
}
}
@@ -274,8 +286,8 @@ export class BaseWrapper {
errorRegex.test(stream.title || '') ||
errorRegex.test(stream.description || '')
) {
console.log(
`|ERR| wrappers > base > ${this.addonName}: ${stream.title || stream.description} was detected as an error`
logger.debug(
`Result from ${this.addonName} (${(stream.title || stream.description).split('\n').join(' ')}) was detected as an error`
);
return {
type: 'error',
+4 -4
View File
@@ -1,9 +1,11 @@
import { AddonDetail, ParseResult, StreamRequest } from '@aiostreams/types';
import { ParsedStream, Stream, Config } from '@aiostreams/types';
import { BaseWrapper } from './base';
import { addonDetails } from '@aiostreams/utils';
import { addonDetails, createLogger } from '@aiostreams/utils';
import { Settings } from '@aiostreams/utils';
const logger = createLogger('wrappers');
export class Comet extends BaseWrapper {
constructor(
configString: string | null,
@@ -152,9 +154,7 @@ export async function getCometStreams(
}
const errorMessages: string[] = [];
const streamPromises = servicesToUse.map(async (service) => {
console.log(
`|INF| wrappers > comet: Getting Comet streams for service: ${service.id}`
);
logger.info(`Getting Comet streams for ${service.id}`, { func: 'comet' });
const cometConfig = getCometConfig(service.id, service.credentials.apiKey);
const configString = Buffer.from(JSON.stringify(cometConfig)).toString(
'base64'
+6 -4
View File
@@ -1,9 +1,11 @@
import { AddonDetail, ParseResult, StreamRequest } from '@aiostreams/types';
import { ParsedStream, Stream, Config } from '@aiostreams/types';
import { BaseWrapper } from './base';
import { addonDetails } from '@aiostreams/utils';
import { addonDetails, createLogger } from '@aiostreams/utils';
import { Settings } from '@aiostreams/utils';
const logger = createLogger('wrappers');
export class Debridio extends BaseWrapper {
constructor(
configString: string | null,
@@ -136,9 +138,9 @@ export async function getDebridioStreams(
const addonErrors: string[] = [];
const streamPromises = servicesToUse.map(async (service) => {
console.log(
`|INF| wrappers > debridio: Getting Debridio streams for ${service.name}`
);
logger.info(`Getting Debridio streams for ${service.name}`, {
func: 'debridio',
});
const debridioConfigString = getDebridioConfigString(
service.id,
service.credentials.apiKey
+6 -4
View File
@@ -1,9 +1,11 @@
import { AddonDetail, StreamRequest } from '@aiostreams/types';
import { ParsedStream, Config } from '@aiostreams/types';
import { BaseWrapper } from './base';
import { addonDetails } from '@aiostreams/utils';
import { addonDetails, createLogger } from '@aiostreams/utils';
import { Settings } from '@aiostreams/utils';
const logger = createLogger('wrappers');
// name, title, url
export class Jackettio extends BaseWrapper {
constructor(
@@ -156,9 +158,9 @@ export async function getJackettioStreams(
}
const streamPromises = servicesToUse.map(async (service) => {
console.log(
`|INF| wrappers > jackettio: Getting Jackettio streams for ${service.name}`
);
logger.info(`Getting Jackettio streams for ${service.name}`, {
func: 'jackettio',
});
const configString = getJackettioConfigString(
service.id,
service.credentials.apiKey
+15 -9
View File
@@ -6,8 +6,11 @@ import {
getTextHash,
getTimeTakenSincePoint,
Settings,
createLogger,
} from '@aiostreams/utils';
const logger = createLogger('wrappers');
export class MediaFusion extends BaseWrapper {
constructor(
configString: string | null,
@@ -74,16 +77,19 @@ export async function getMediafusionStreams(
const cacheKey = getTextHash(`mediafusionConfig:${JSON.stringify(data)}`);
const cachedConfig = cache ? cache.get(cacheKey) : null;
if (cachedConfig) {
console.log(
`|DBG| wrappers > mediafusion: Returning cached config string`
);
logger.info(`Returning cached config string`, {
func: 'mediafusion.getConfigString',
});
return cachedConfig;
}
try {
const encryptedStr = await _getConfigString(data);
console.log(
`|INF| wrappers > mediafusion: Config encryption took ${getTimeTakenSincePoint(startTime)}`
logger.info(
`Config encryption took ${getTimeTakenSincePoint(startTime)}`,
{
func: 'mediafusion.getConfigString',
}
);
cache?.set(cacheKey, encryptedStr, Settings.CACHE_MEDIAFUSION_CONFIG_TTL);
return encryptedStr;
@@ -198,9 +204,9 @@ export async function getMediafusionStreams(
service.id,
service.credentials
);
console.log(
`|INF| wrappers > mediafusion: Getting MediaFusion streams for service: ${service.id}`
);
logger.info(`Getting MediaFusion streams for ${service.id}`, {
func: 'mediafusion',
});
let encryptedStr: string = '';
try {
encryptedStr = await getConfigString(mediafusionConfig);
@@ -210,7 +216,7 @@ export async function getMediafusionStreams(
);
}
} catch (error: any) {
console.error(`|ERR| wrappers > mediafusion: ${error.message}`);
logger.error(`${error.message}`, { func: 'mediafusion' });
throw new Error(error.message);
}
const mediafusion = new MediaFusion(
+4 -4
View File
@@ -6,9 +6,11 @@ import {
} from '@aiostreams/types';
import { ParsedStream, Stream, Config } from '@aiostreams/types';
import { BaseWrapper } from './base';
import { addonDetails } from '@aiostreams/utils';
import { addonDetails, createLogger } from '@aiostreams/utils';
import { Settings } from '@aiostreams/utils';
const logger = createLogger('wrappers');
// name, title, url
export class OrionStremioAddon extends BaseWrapper {
constructor(
@@ -142,9 +144,7 @@ export async function getOrionStreams(
// otherwise, pass all the services to orion
const debridServices = usableServices.map((service) => service.id);
console.log(
`|DBG| wrappers > orion > using debrid services: ${debridServices}`
);
logger.info(`Using Orion with debrid services: ${debridServices}`);
const configString = getOrionConfigString(
orionApiKey,
orionOptions.linkLimit,
+9 -7
View File
@@ -1,9 +1,11 @@
import { AddonDetail, StreamRequest } from '@aiostreams/types';
import { ParsedStream, Config } from '@aiostreams/types';
import { BaseWrapper } from './base';
import { addonDetails } from '@aiostreams/utils';
import { addonDetails, createLogger } from '@aiostreams/utils';
import { Settings } from '@aiostreams/utils';
const logger = createLogger('wrappers');
export class Peerflix extends BaseWrapper {
constructor(
configString: string | null,
@@ -46,7 +48,6 @@ export async function getPeerflixStreams(
const indexerTimeout = peerflixOptions.indexerTimeout
? parseInt(peerflixOptions.indexerTimeout)
: undefined;
console.log(JSON.stringify(peerflixOptions));
// If overrideUrl is provided, use it to get streams and skip all other steps
if (peerflixOptions.overrideUrl) {
const peerflix = new Peerflix(
@@ -64,8 +65,9 @@ export async function getPeerflixStreams(
const usableServices = config.services.filter(
(service) => supportedServices.includes(service.id) && service.enabled
);
console.log(
`|DBG| wrappers > peerflix: Found ${usableServices.length} usable services: ${usableServices.map((service) => service.id).join(', ')}`
logger.debug(
`Found ${usableServices.length} usable services: ${usableServices.map((service) => service.id).join(', ')}`,
{ func: 'peerflix' }
);
// if no usable services found, use peerflix without any configuration
@@ -95,9 +97,9 @@ export async function getPeerflixStreams(
if (peerflixOptions.useMultipleInstances === 'true') {
let retrievedP2PStreams = false;
const promises = usableServices.map(async (service) => {
console.log(
`|DBG| wrappers > peerflix: Getting Peerflix streams for ${service.name}`
);
logger.info(`Getting Peerflix streams for ${service.name}`, {
func: 'peerflix',
});
let configPairs = [getServicePair(service.id, service.credentials)];
if (peerflixOptions.showP2PStreams === 'true' && !retrievedP2PStreams) {
configPairs.push(['debridoptions', 'torrentlinks']);
+10 -6
View File
@@ -6,9 +6,11 @@ import {
} from '@aiostreams/types';
import { ParsedStream, Config } from '@aiostreams/types';
import { BaseWrapper } from './base';
import { addonDetails } from '@aiostreams/utils';
import { addonDetails, createLogger } from '@aiostreams/utils';
import { Settings } from '@aiostreams/utils';
const logger = createLogger('wrappers');
// name, title, url
export class StremioJackett extends BaseWrapper {
constructor(
@@ -61,8 +63,10 @@ export class StremioJackett extends BaseWrapper {
url.pathname = components.join('/');
}
}
console.error(`
|ERR| wrappers > stremioJackett: Error parsing stream config for ${url}`);
logger.error(
`Error parsing stream config for playback URL: ${this.getLoggableUrl(url.toString())}`,
{ func: 'stremio-jackett' }
);
}
}
return parseResult;
@@ -201,9 +205,9 @@ export async function getStremioJackettStreams(
}
const streamPromises = servicesToUse.map(async (service) => {
console.log(
`|INF| wrappers > stremioJackett: Getting StremioJackett streams for ${service.name}`
);
logger.info(`Getting Stremio-Jackett streams for ${service.name}`, {
func: 'stremio-jackett',
});
const configString = getStremioJackettConfigString(
service.id,
service.credentials.apiKey,
+6 -4
View File
@@ -8,7 +8,9 @@ import {
StreamRequest,
} from '@aiostreams/types';
import { parseFilename } from '@aiostreams/parser';
import { Settings } from '@aiostreams/utils';
import { createLogger, Settings } from '@aiostreams/utils';
const logger = createLogger('wrappers');
interface TorboxStream extends Stream {
name: string;
@@ -49,9 +51,9 @@ export class Torbox extends BaseWrapper {
let type = stream.type;
let personal = false;
if (stream.name.includes('Your Media')) {
console.log(
`|INF| wrappers > torbox > detected personal stream in ${stream.name}`
);
logger.debug(`${stream.name} was detected as a personal stream.`, {
func: 'torbox',
});
personal = true;
}
const [dQuality, dFilename, dSize, dLanguage, dAgeOrSeeders] =
+6 -4
View File
@@ -1,9 +1,11 @@
import { AddonDetail, StreamRequest } from '@aiostreams/types';
import { ParsedStream, Config } from '@aiostreams/types';
import { BaseWrapper } from './base';
import { addonDetails } from '@aiostreams/utils';
import { addonDetails, createLogger } from '@aiostreams/utils';
import { Settings } from '@aiostreams/utils';
const logger = createLogger('wrappers');
export class Torrentio extends BaseWrapper {
constructor(
configString: string | null,
@@ -90,9 +92,9 @@ export async function getTorrentioStreams(
if (torrentioOptions.useMultipleInstances === 'true') {
const promises = usableServices.map(async (service) => {
console.log(
`|INF| wrappers > torrentio: Getting Torrentio streams for ${service.name}`
);
logger.info(`Getting Torrentio streams for ${service.name}`, {
func: 'torrentio',
});
let configString = getServicePair(service.id, service.credentials);
const torrentio = new Torrentio(
configString,