add torrentio catalogs addon

This commit is contained in:
TheBeastLT
2022-04-01 13:39:18 +02:00
parent c466874324
commit b143449f77
12 changed files with 1886 additions and 0 deletions

48
catalogs/lib/cache.js Normal file
View File

@@ -0,0 +1,48 @@
const cacheManager = require('cache-manager');
const mangodbStore = require('cache-manager-mongodb');
const CATALOG_TTL = process.env.STREAM_TTL || 12 * 60 * 60; // 12 hours
const MONGO_URI = process.env.MONGODB_URI;
const remoteCache = initiateRemoteCache();
function initiateRemoteCache() {
if (MONGO_URI) {
return cacheManager.caching({
store: mangodbStore,
uri: MONGO_URI,
options: {
collection: 'torrentio_catalog_collection',
useNewUrlParser: true,
useUnifiedTopology: false,
ttl: CATALOG_TTL
},
ttl: CATALOG_TTL,
ignoreCacheErrors: true
});
} else {
return cacheManager.caching({
store: 'memory',
ttl: CATALOG_TTL
});
}
}
function cacheWrap(cache, key, method, options) {
if (!cache) {
return method();
}
return cache.wrap(key, method, options);
}
function cacheWrapCatalog(key, method) {
return cacheWrap(remoteCache, key, method, { ttl: CATALOG_TTL });
}
function cacheWrapIds(key, method) {
return cacheWrap(remoteCache, `ids|${key}`, method, { ttl: CATALOG_TTL });
}
module.exports = { cacheWrapCatalog, cacheWrapIds };

View File

@@ -0,0 +1,276 @@
const STYLESHEET = `
* {
box-sizing: border-box;
}
body,
html {
margin: 0;
padding: 0;
width: 100%;
height: 100%
}
html {
background-size: auto 100%;
background-size: cover;
background-position: center center;
background-repeat: repeat-y;
}
body {
display: flex;
background-color: transparent;
font-family: 'Open Sans', Arial, sans-serif;
color: white;
}
h1 {
font-size: 4.5vh;
font-weight: 700;
}
h2 {
font-size: 2.2vh;
font-weight: normal;
font-style: italic;
opacity: 0.8;
}
h3 {
font-size: 2.2vh;
}
h1,
h2,
h3,
p,
label {
margin: 0;
text-shadow: 0 0 1vh rgba(0, 0, 0, 0.15);
}
p {
font-size: 1.75vh;
}
ul {
font-size: 1.75vh;
margin: 0;
margin-top: 1vh;
padding-left: 3vh;
}
a {
color: green
}
a.install-link {
text-decoration: none
}
button {
border: 0;
outline: 0;
color: white;
background: #8A5AAB;
padding: 1.2vh 3.5vh;
margin: auto;
text-align: center;
font-family: 'Open Sans', Arial, sans-serif;
font-size: 2.2vh;
font-weight: 600;
cursor: pointer;
display: block;
box-shadow: 0 0.5vh 1vh rgba(0, 0, 0, 0.2);
transition: box-shadow 0.1s ease-in-out;
}
button:hover {
box-shadow: none;
}
button:active {
box-shadow: 0 0 0 0.5vh white inset;
}
#addon {
width: 90vh;
margin: auto;
padding-left: 10%;
padding-right: 10%;
background: rgba(0, 0, 0, 0.60);
}
.logo {
height: 14vh;
width: 14vh;
margin: auto;
margin-bottom: 3vh;
}
.logo img {
width: 100%;
}
.name, .version {
display: inline-block;
vertical-align: top;
}
.name {
line-height: 5vh;
}
.version {
position: absolute;
line-height: 5vh;
margin-left: 1vh;
opacity: 0.8;
}
.contact {
position: absolute;
left: 0;
bottom: 4vh;
width: 100%;
text-align: center;
}
.contact a {
font-size: 1.4vh;
font-style: italic;
}
.separator {
margin-bottom: 4vh;
}
.label {
font-size: 2.2vh;
font-weight: 600;
padding: 0;
line-height: inherit;
}
.btn-group, .multiselect-container {
width: 100%;
}
.btn {
text-align: left;
}
.multiselect-container {
border: 0;
border-radius: 0;
}
.input, .btn {
height: 3.8vh;
width: 100%;
margin: auto;
margin-bottom: 10px;
padding: 6px 12px;
border: 0;
border-radius: 0;
outline: 0;
color: #333;
background-color: rgb(255, 255, 255);
box-shadow: 0 0.5vh 1vh rgba(0, 0, 0, 0.2);
}
`;
const { Providers } = require('../../addon/lib/filter');
function landingTemplate(manifest, config = {}) {
const providers = config.providers || [];
const background = manifest.background || 'https://dl.strem.io/addon-background.jpg';
const logo = manifest.logo || 'https://dl.strem.io/addon-logo.png';
const contactHTML = manifest.contactEmail ?
`<div class="contact">
<p>Contact ${manifest.name} creator:</p>
<a href="mailto:${manifest.contactEmail}">${manifest.contactEmail}</a>
</div>` : '<div class="separator"></div>';
const providersHTML = Providers.options
.map(provider => `<option value="${provider.key}">${provider.foreign || ''}${provider.label}</option>`)
.join('\n');
const stylizedTypes = manifest.types
.map(t => t[0].toUpperCase() + t.slice(1) + (t !== 'series' ? 's' : ''));
return `
<!DOCTYPE html>
<html style="background-image: url(${background});">
<head>
<meta charset="utf-8">
<title>${manifest.name} - Stremio Addon</title>
<link rel="shortcut icon" href="${logo}" type="image/x-icon">
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,600,700&display=swap" rel="stylesheet">
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" >
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-multiselect/0.9.15/js/bootstrap-multiselect.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-multiselect/0.9.15/css/bootstrap-multiselect.css" rel="stylesheet"/>
<style>${STYLESHEET}</style>
</head>
<body>
<div id="addon">
<div class="logo">
<img src="${logo}">
</div>
<h1 class="name">${manifest.name}</h1>
<h2 class="version">${manifest.version || '0.0.0'}</h2>
<h2 class="description">${manifest.description || ''}</h2>
<div class="separator"></div>
<h3 class="gives">This addon has more :</h3>
<ul>
${stylizedTypes.map(t => `<li>${t}</li>`).join('')}
</ul>
<div class="separator"></div>
<label class="label" for="iProviders">Providers:</label>
<select id="iProviders" class="input" name="providers[]" multiple="multiple">
${providersHTML}
</select>
<div class="separator"></div>
<a id="installLink" class="install-link" href="#">
<button name="Install">INSTALL</button>
</a>
${contactHTML}
</div>
<script type="text/javascript">
$(document).ready(function() {
$('#iProviders').multiselect({
nonSelectedText: 'All providers',
onChange: () => generateInstallLink()
});
$('#iProviders').multiselect('select', [${providers.map(provider => '"' + provider + '"')}]);
generateInstallLink();
});
function generateInstallLink() {
const providersValue = $('#iProviders').val().join(',') || '';
const providers = providersValue.length && providersValue;
const configurationValue = [
['${Providers.key}', providers],
]
.filter(([_, value]) => value.length)
.map(([key, value]) => key + '=' + value).join('|');
const configuration = configurationValue && configurationValue.length ? '/' + configurationValue : '';
installLink.href = 'stremio://' + window.location.host + configuration + '/manifest.json';
}
</script>
</body>
</html>`
}
module.exports = landingTemplate;

54
catalogs/lib/manifest.js Normal file
View File

@@ -0,0 +1,54 @@
const { Type } = require('../../addon/lib/types');
const genres = [
'Today',
'Yesterday',
'Last Week',
'Last Month',
'All Time'
]
function createManifest() {
return {
id: 'com.stremio.torrentio.catalog.addon',
version: '1.0.0',
name: 'Torrentio Catalogs',
description: 'Provides catalogs for movies/series/anime based on top seeded torrents',
logo: `https://i.ibb.co/w4BnkC9/GwxAcDV.png`,
background: `https://i.ibb.co/VtSfFP9/t8wVwcg.jpg`,
types: [Type.MOVIE, Type.SERIES, Type.ANIME],
resources: ['catalog'],
catalogs: [
{
id: 'top-movies',
type: Type.MOVIE,
name: "Top seeded movies",
pageSize: 20,
extra: [{ name: 'genre', options: genres }, { name: 'skip' }],
genres: genres
},
{
id: 'top-series',
type: Type.SERIES,
name: "Top seeded series",
pageSize: 20,
extra: [{ name: 'genre', options: genres }, { name: 'skip' }],
genres: genres
},
{
id: 'top-anime',
type: Type.ANIME,
name: "Top seeded anime",
pageSize: 20,
extra: [{ name: 'genre', options: genres }, { name: 'skip' }],
genres: genres
}
],
behaviorHints: {
configurable: false, // @TODO might enable configuration to configure providers
configurationRequired: false
}
};
}
module.exports = { createManifest, genres };

35
catalogs/lib/metadata.js Normal file
View File

@@ -0,0 +1,35 @@
const axios = require('axios');
const { Type } = require('../../addon/lib/types');
const CINEMETA_URL = 'https://v3-cinemeta.strem.io';
const KITSU_URL = 'https://anime-kitsu.strem.fun';
const TIMEOUT = 30000;
const MAX_SIZE = 50;
function getMetas(ids, type) {
if (!ids.length || !type) {
return [];
}
return _requestMetadata(ids, type)
.catch((error) => {
throw new Error(`failed metadata ${type} query due: ${error.message}`);
});
}
function _requestMetadata(ids, type) {
const url = _getUrl(ids, type);
return axios.get(url, { timeout: TIMEOUT })
.then(response => response?.data?.metas || response?.data?.metasDetailed || [])
.then(metas => metas.filter(meta => meta));
}
function _getUrl(ids, type) {
const joinedIds = ids.slice(0, MAX_SIZE).join(',');
if (type === Type.ANIME) {
return `${KITSU_URL}/catalog/${type}/kitsu-anime-list/lastVideosIds=${joinedIds}.json`
}
return `${CINEMETA_URL}/catalog/${type}/last-videos/lastVideosIds=${joinedIds}.json`
}
module.exports = { getMetas };

View File

@@ -0,0 +1,29 @@
const { Sequelize, QueryTypes } = require('sequelize');
const { Type } = require('../../addon/lib/types');
const DATABASE_URI = process.env.DATABASE_URI;
const database = new Sequelize(DATABASE_URI, { logging: false });
async function getIds(type, startDate, endDate) {
const idName = type === Type.ANIME ? 'kitsuId' : 'imdbId';
const episodeCondition = type === Type.SERIES
? 'AND files."imdbSeason" IS NOT NULL AND files."imdbEpisode" IS NOT NULL'
: '';
const dateCondition = startDate && endDate
? `AND "uploadDate" BETWEEN '${startDate}' AND '${endDate}'`
: ''
const query = `SELECT files."${idName}"
FROM (SELECT torrents."infoHash", torrents.seeders FROM torrents
WHERE seeders > 0 AND type = '${type}' ${dateCondition}
) as torrents
JOIN files ON torrents."infoHash" = files."infoHash"
WHERE files."${idName}" IS NOT NULL ${episodeCondition}
GROUP BY files."${idName}"
ORDER BY max(torrents.seeders) DESC
LIMIT 5000`
const results = await database.query(query, { type: QueryTypes.SELECT });
return results.map(result => `${result.imdbId || result.kitsuId}`);
}
module.exports = { getIds };