Run pre-commit

This commit is contained in:
purple_emily
2024-03-08 14:34:53 +00:00
parent 31e16df720
commit 79409915cf
221 changed files with 525 additions and 526 deletions

View File

@@ -16,16 +16,16 @@
}
},
"WriteTo": [
{
{
"Name": "Console",
"Args": {
"outputTemplate": "{Timestamp:HH:mm:ss} [{Level}] [{SourceContext}] {Message}{NewLine}{Exception}"
}
}
}
],
"Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ],
"Properties": {
"Application": "Metadata"
}
}
}
}

View File

@@ -4,41 +4,41 @@ public static class ConfigurationExtensions
{
private const string ConfigurationFolder = "Configuration";
private const string LoggingConfig = "logging.json";
public static IConfigurationBuilder AddServiceConfiguration(this IConfigurationBuilder configuration)
{
configuration.SetBasePath(Path.Combine(AppContext.BaseDirectory, ConfigurationFolder));
configuration.AddJsonFile(LoggingConfig, false, true);
configuration.AddEnvironmentVariables();
configuration.AddUserSecrets<Program>();
return configuration;
}
public static TConfiguration LoadConfigurationFromConfig<TConfiguration>(this IServiceCollection services, IConfiguration configuration, string sectionName)
where TConfiguration : class
{
var instance = configuration.GetSection(sectionName).Get<TConfiguration>();
ArgumentNullException.ThrowIfNull(instance, nameof(instance));
services.TryAddSingleton(instance);
return instance;
}
public static TConfiguration LoadConfigurationFromEnv<TConfiguration>(this IServiceCollection services)
where TConfiguration : class
{
var instance = Activator.CreateInstance<TConfiguration>();
ArgumentNullException.ThrowIfNull(instance, nameof(instance));
services.TryAddSingleton(instance);
return instance;
}
}
}

View File

@@ -5,9 +5,9 @@ public static class EnvironmentExtensions
public static bool GetEnvironmentVariableAsBool(this string prefix, string varName, bool fallback = false)
{
var fullVarName = GetFullVariableName(prefix, varName);
var str = Environment.GetEnvironmentVariable(fullVarName);
if (string.IsNullOrEmpty(str))
{
return fallback;
@@ -21,11 +21,11 @@ public static class EnvironmentExtensions
_ => false,
};
}
public static int GetEnvironmentVariableAsInt(this string prefix, string varName, int fallback = 0)
{
var fullVarName = GetFullVariableName(prefix, varName);
var str = Environment.GetEnvironmentVariable(fullVarName);
if (string.IsNullOrEmpty(str))
@@ -35,11 +35,11 @@ public static class EnvironmentExtensions
return int.TryParse(str, out var result) ? result : fallback;
}
public static string GetRequiredEnvironmentVariableAsString(this string prefix, string varName)
{
var fullVarName = GetFullVariableName(prefix, varName);
var str = Environment.GetEnvironmentVariable(fullVarName);
if (string.IsNullOrEmpty(str))
@@ -49,11 +49,11 @@ public static class EnvironmentExtensions
return str;
}
public static string GetOptionalEnvironmentVariableAsString(this string prefix, string varName, string? fallback = null)
{
var fullVarName = GetFullVariableName(prefix, varName);
var str = Environment.GetEnvironmentVariable(fullVarName);
if (string.IsNullOrEmpty(str))
@@ -63,6 +63,6 @@ public static class EnvironmentExtensions
return str;
}
private static string GetFullVariableName(string prefix, string varName) => $"{prefix}_{varName}";
}
}

View File

@@ -3,4 +3,4 @@ namespace Metadata.Extensions;
public static class JsonSerializerExtensions
{
public static string ToJson<T>(this T value) => JsonSerializer.Serialize(value);
}
}

View File

@@ -8,15 +8,15 @@ public static class ServiceCollectionExtensions
return services;
}
internal static IServiceCollection AddMongoDb(this IServiceCollection services)
{
services.LoadConfigurationFromEnv<MongoConfiguration>();
services.AddTransient<ImdbMongoDbService>();
return services;
}
internal static IServiceCollection AddJobSupport(this IServiceCollection services)
{
services.LoadConfigurationFromEnv<JobConfiguration>();
@@ -24,7 +24,7 @@ public static class ServiceCollectionExtensions
services.AddScheduler()
.AddTransient<DownloadImdbDataJob>()
.AddHostedService<JobScheduler>();
return services;
}
}
}

View File

@@ -13,7 +13,7 @@ internal static class WebApplicationBuilderExtensions
{
options.DefaultExecutionTimeout = 6.Hours();
});
return builder;
}
}
}

View File

@@ -6,8 +6,8 @@ public class JobConfiguration
private const string DownloadImdbDataVariable = "DOWNLOAD_IMDB_DATA_SCHEDULE";
private const string DownloadImdbDataOnceVariable = "DOWNLOAD_IMDB_DATA_ONCE";
private const string InsertBatchSizeVariable = "INSERT_BATCH_SIZE";
public int InsertBatchSize { get; init; } = Prefix.GetEnvironmentVariableAsInt(InsertBatchSizeVariable, 25_000);
public string DownloadImdbCronSchedule { get; init; } = Prefix.GetOptionalEnvironmentVariableAsString(DownloadImdbDataVariable, CronExpressions.EveryHour);
public bool DownloadImdbOnce { get; init; } = Prefix.GetEnvironmentVariableAsBool(DownloadImdbDataOnceVariable);
}
}

View File

@@ -8,13 +8,13 @@ public class MongoConfiguration
private const string DbVariable = "DB";
private const string UsernameVariable = "USER";
private const string PasswordVariable = "PASSWORD";
private string Host { get; init; } = Prefix.GetRequiredEnvironmentVariableAsString(HostVariable);
private int Port { get; init; } = Prefix.GetEnvironmentVariableAsInt(PortVariable, 27017);
private string Username { get; init; } = Prefix.GetRequiredEnvironmentVariableAsString(UsernameVariable);
private string Password { get; init; } = Prefix.GetRequiredEnvironmentVariableAsString(PasswordVariable);
public string DbName { get; init; } = Prefix.GetRequiredEnvironmentVariableAsString(DbVariable);
public string ConnectionString => $"mongodb://{Username}:{Password}@{Host}:{Port}/{DbName}?tls=false&directConnection=true&authSource=admin";
}
}

View File

@@ -1,3 +1,3 @@
namespace Metadata.Features.DeleteDownloadedImdbData;
public record DeleteDownloadedImdbDataRequest(string FilePath);
public record DeleteDownloadedImdbDataRequest(string FilePath);

View File

@@ -5,9 +5,9 @@ public class DeleteDownloadedImdbDataRequestHandler(ILogger<DeleteDownloadedImdb
public Task Handle(DeleteDownloadedImdbDataRequest request, CancellationToken _)
{
logger.LogInformation("Deleting file {FilePath}", request.FilePath);
File.Delete(request.FilePath);
logger.LogInformation("File Deleted");
if (configuration.DownloadImdbOnce)
@@ -18,4 +18,4 @@ public class DeleteDownloadedImdbDataRequestHandler(ILogger<DeleteDownloadedImdb
return Task.CompletedTask;
}
}
}

View File

@@ -5,4 +5,4 @@ public class DownloadImdbDataJob(IMessageBus messageBus, JobConfiguration config
public override bool IsScheduelable => !configuration.DownloadImdbOnce && !string.IsNullOrEmpty(configuration.DownloadImdbCronSchedule);
public override string JobName => nameof(DownloadImdbDataJob);
public override async Task Invoke() => await messageBus.SendAsync(new GetImdbDataRequest());
}
}

View File

@@ -1,3 +1,3 @@
namespace Metadata.Features.DownloadImdbData;
public record GetImdbDataRequest;
public record GetImdbDataRequest;

View File

@@ -3,28 +3,28 @@ namespace Metadata.Features.DownloadImdbData;
public class GetImdbDataRequestHandler(IHttpClientFactory clientFactory, ILogger<GetImdbDataRequestHandler> logger)
{
private const string TitleBasicsFileName = "title.basics.tsv";
public async Task<ImportImdbDataRequest> Handle(GetImdbDataRequest _, CancellationToken cancellationToken)
{
logger.LogInformation("Downloading IMDB data");
var client = clientFactory.CreateClient("imdb-data");
var response = await client.GetAsync($"{TitleBasicsFileName}.gz", cancellationToken);
var tempFile = Path.Combine(Path.GetTempPath(), TitleBasicsFileName);
response.EnsureSuccessStatusCode();
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken);
await using var gzipStream = new GZipStream(stream, CompressionMode.Decompress);
await using var fileStream = File.Create(tempFile);
await gzipStream.CopyToAsync(fileStream, cancellationToken);
logger.LogInformation("Downloaded IMDB data to {TempFile}", tempFile);
fileStream.Close();
return new(tempFile);
}
}
}

View File

@@ -12,4 +12,4 @@ public class ImdbEntry
public string? EndYear { get; set; }
public string? RuntimeMinutes { get; set; }
public string? Genres { get; set; }
}
}

View File

@@ -8,7 +8,7 @@ public class ImdbMongoDbService
public ImdbMongoDbService(MongoConfiguration configuration, ILogger<ImdbMongoDbService> logger)
{
_logger = logger;
var client = new MongoClient(configuration.ConnectionString);
var database = client.GetDatabase(configuration.DbName);
@@ -37,7 +37,7 @@ public class ImdbMongoDbService
await _imdbCollection.BulkWriteAsync(operations);
}
public bool IsDatabaseInitialized()
{
try
@@ -61,4 +61,4 @@ public class ImdbMongoDbService
return false;
}
}
}
}

View File

@@ -1,3 +1,3 @@
namespace Metadata.Features.ImportImdbData;
public record ImportImdbDataRequest(string FilePath);
public record ImportImdbDataRequest(string FilePath);

View File

@@ -21,16 +21,16 @@ public class ImportImdbDataRequestHandler(ILogger<ImportImdbDataRequestHandler>
FullMode = BoundedChannelFullMode.Wait,
});
// Skip the header row
await csv.ReadAsync();
var batchInsertTask = CreateBatchOfEntries(channel, cancellationToken);
await ReadEntries(csv, channel, cancellationToken);
channel.Writer.Complete();
await batchInsertTask;
return new(request.FilePath);
@@ -45,7 +45,7 @@ public class ImportImdbDataRequestHandler(ILogger<ImportImdbDataRequestHandler>
{
return;
}
var batch = new List<ImdbEntry>
{
movieData,
@@ -63,7 +63,7 @@ public class ImportImdbDataRequestHandler(ILogger<ImportImdbDataRequestHandler>
}
}
}, cancellationToken);
private static async Task ReadEntries(CsvReader csv, Channel<ImdbEntry, ImdbEntry> channel, CancellationToken cancellationToken)
{
while (await csv.ReadAsync())
@@ -80,13 +80,13 @@ public class ImportImdbDataRequestHandler(ILogger<ImportImdbDataRequestHandler>
RuntimeMinutes = csv.GetField(7),
Genres = csv.GetField(8),
};
if (cancellationToken.IsCancellationRequested)
{
return;
}
await channel.Writer.WriteAsync(movieData, cancellationToken);
}
}
}
}

View File

@@ -5,6 +5,6 @@ public abstract class BaseJob : IMetadataJob
public abstract bool IsScheduelable { get; }
public abstract string JobName { get; }
public abstract Task Invoke();
}
}

View File

@@ -4,4 +4,4 @@ public interface IMetadataJob : IInvocable
{
bool IsScheduelable { get; }
string JobName { get; }
}
}

View File

@@ -5,14 +5,14 @@ public class JobScheduler(IServiceProvider serviceProvider) : IHostedService
public Task StartAsync(CancellationToken cancellationToken)
{
using var scope = serviceProvider.CreateAsyncScope();
var mongoDbService = scope.ServiceProvider.GetRequiredService<ImdbMongoDbService>();
if (!mongoDbService.IsDatabaseInitialized())
{
throw new InvalidOperationException("MongoDb is not initialized");
}
var jobConfigurations = scope.ServiceProvider.GetRequiredService<JobConfiguration>();
var downloadJob = scope.ServiceProvider.GetRequiredService<DownloadImdbDataJob>();
@@ -20,15 +20,15 @@ public class JobScheduler(IServiceProvider serviceProvider) : IHostedService
{
return downloadJob.Invoke();
}
var scheduler = scope.ServiceProvider.GetRequiredService<IScheduler>();
scheduler.Schedule<DownloadImdbDataJob>()
.Cron(jobConfigurations.DownloadImdbCronSchedule)
.PreventOverlapping(nameof(downloadJob.JobName));
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
}

View File

@@ -6,4 +6,4 @@ public static class CronExpressions
public const string EveryDay = "0 0 0 * *";
public const string EveryWeek = "0 0 * * 0";
public const string EveryMonth = "0 0 0 * *";
}
}

View File

@@ -4,4 +4,4 @@ public static class HttpClients
{
public const string ImdbDataClientName = "imdb-data";
public const string ImdbClientBaseAddress = "https://datasets.imdbws.com/";
}
}

View File

@@ -10,4 +10,4 @@ builder.Services
var host = builder.Build();
await host.RunAsync();
await host.RunAsync();

View File

@@ -1 +1 @@
*.ts
*.ts

View File

@@ -36,4 +36,4 @@ module.exports = {
"one-var": ["error", { uninitialized: "consecutive" }],
"prefer-destructuring": "warn",
},
};
};

View File

@@ -1,4 +1,4 @@
#!/bin/bash
docker build -t ippexdeploymentscr.azurecr.io/dave/stremio-addon-jackett:latest . --platform linux/amd64
docker push ippexdeploymentscr.azurecr.io/dave/stremio-addon-jackett:latest
docker push ippexdeploymentscr.azurecr.io/dave/stremio-addon-jackett:latest

View File

@@ -18,4 +18,4 @@
"typeRoots": ["node_modules/@types", "src/@types"]
},
"exclude": ["node_modules"]
}
}

View File

@@ -22,12 +22,12 @@ builder.defineStreamHandler((args) => {
if (!args.id.match(/tt\d+/i) && !args.id.match(/kitsu:\d+/i)) {
return Promise.resolve({ streams: [] });
}
if (processConfig.DEBUG) {
console.log(`Incoming stream ${args.id} request`)
console.log('args', args);
}
return cacheWrapStream(args.id, () => limiter.schedule(() =>
streamHandler(args)
.then(records => records.map(record => toStreamInfo(record, args.type))))

View File

@@ -111,7 +111,7 @@ export const transformData = async (data, query) => {
console.log("Transforming data for query " + data);
let results = [];
const parsedData = await parseString(data);
if (!parsedData.rss.channel[0]?.item) {
@@ -126,7 +126,7 @@ export const transformData = async (data, query) => {
[torznabDataItem.$.name]: torznabDataItem.$.value,
})
);
if (torznabData.infohash) {
const [title, pubDate, category, size] = [rssItem.title[0], rssItem.pubDate[0], rssItem.category[0], rssItem.size[0]];
@@ -148,4 +148,4 @@ export const transformData = async (data, query) => {
return results;
};
};

View File

@@ -38,4 +38,4 @@ export const searchJackett = async (query) => {
}
return sortedResults;
};
};

View File

@@ -18,7 +18,7 @@ const getMovieSearchQueries = (cleanName, year) => {
const getSeriesSearchQueries = (cleanName, year, season, episode) => {
return {
seriesByEpisode: seriesByEpisode(cleanName, season, episode),
};
};
}
export const jackettSearchQueries = (cleanName, type, year, season, episode) => {
@@ -27,8 +27,8 @@ export const jackettSearchQueries = (cleanName, type, year, season, episode) =>
return getMovieSearchQueries(cleanName, year);
case Type.SERIES:
return getSeriesSearchQueries(cleanName, year, season, episode);
default:
return { };
}
};
};

View File

@@ -8,7 +8,7 @@ const cinemetaUri = cinemetaConfig.URI;
export const getMetaData = (args) => {
const [imdbId] = args.id.split(':');
const {type} = args;
return cacheWrapImdbMetaData(args.id, () => getInfoForImdbId(imdbId, type));
}
@@ -20,7 +20,7 @@ const getInfoForImdbId = async (imdbId, type) => {
console.log(`Getting info for ${imdbId} of type ${type}`);
console.log(`Request URI: ${requestUri}`);
}
try {
const { data: response } = await axios.get(requestUri, options);
return response.meta;
@@ -28,4 +28,4 @@ const getInfoForImdbId = async (imdbId, type) => {
console.log(error);
return {};
}
};
};

View File

@@ -22,4 +22,4 @@ export function parseConfiguration(configuration) {
.map(value => keysToUppercase.includes(key) ? value.toUpperCase() : value.toLowerCase()))
return configValues;
}
}

View File

@@ -180,7 +180,7 @@ a.install-link {
}
.input:focus, .btn:focus {
outline: none;
outline: none;
box-shadow: 0 0 0 2pt rgb(30, 144, 255, 0.7);
}
`;
@@ -210,7 +210,7 @@ export default function landingTemplate(manifest, config = {}) {
const debridOptionsHTML = Object.values(DebridOptions.options)
.map(option => `<option value="${option.key}">${option.description}</option>`)
.join('\n');
return `
<!DOCTYPE html>
<html style="background-image: url(${background});">
@@ -239,54 +239,54 @@ export default function landingTemplate(manifest, config = {}) {
<h2 class="description">${manifest.description || ''}</h2>
<div class="separator"></div>
<label class="label" id="iLimitLabel" for="iLimit">Max results per quality:</label>
<input type="text" inputmode="numeric" pattern="[0-9]*" id="iLimit" onchange="generateInstallLink()" class="input" placeholder="All results">
<label class="label" for="iDebridProviders">Debrid provider:</label>
<select id="iDebridProviders" class="input" onchange="debridProvidersChange()">
<option value="none" selected>None</option>
${debridProvidersHTML}
</select>
<div id="dRealDebrid">
<label class="label" for="iRealDebrid">RealDebrid API Key (Find it <a href='https://real-debrid.com/apitoken' target="_blank">here</a>):</label>
<input type="text" id="iRealDebrid" onchange="generateInstallLink()" class="input">
</div>
<div id="dAllDebrid">
<label class="label" for="iAllDebrid">AllDebrid API Key (Create it <a href='https://alldebrid.com/apikeys' target="_blank">here</a>):</label>
<input type="text" id="iAllDebrid" onchange="generateInstallLink()" class="input">
</div>
<div id="dPremiumize">
<label class="label" for="iPremiumize">Premiumize API Key (Find it <a href='https://www.premiumize.me/account' target="_blank">here</a>):</label>
<input type="text" id="iPremiumize" onchange="generateInstallLink()" class="input">
</div>
<div id="dDebridLink">
<label class="label" for="iDebridLink">DebridLink API Key (Find it <a href='https://debrid-link.fr/webapp/apikey' target="_blank">here</a>):</label>
<input type="text" id="iDebridLink" onchange="generateInstallLink()" class="input">
</div>
<div id="dOffcloud">
<label class="label" for="iOffcloud">Offcloud API Key (Find it <a href='https://offcloud.com/#/account' target="_blank">here</a>):</label>
<input type="text" id="iOffcloud" onchange="generateInstallLink()" class="input">
</div>
<div id="dPutio">
<label class="label" for="iPutio">Put.io ClientId and Token (Create new OAuth App <a href='https://app.put.io/oauth' target="_blank">here</a>):</label>
<input type="text" id="iPutioClientId" placeholder="ClientId" onchange="generateInstallLink()" class="input">
<input type="text" id="iPutioToken" placeholder="Token" onchange="generateInstallLink()" class="input">
</div>
<div id="dDebridOptions">
<label class="label" for="iDebridOptions">Debrid options:</label>
<select id="iDebridOptions" class="input" onchange="generateInstallLink()" name="debridOptions[]" multiple="multiple">
${debridOptionsHTML}
</select>
</div>
<div class="separator"></div>
<a id="installLink" class="install-link" href="#">
@@ -295,7 +295,7 @@ export default function landingTemplate(manifest, config = {}) {
<div class="contact">
<p>Or paste into Stremio search bar after clicking install</p>
</div>
<div class="separator"></div>
</div>
<script type="text/javascript">
@@ -304,7 +304,7 @@ export default function landingTemplate(manifest, config = {}) {
const isTvAgent = /\\b(?:tv|wv)\\b/i.test(navigator.userAgent)
const isDesktopMedia = window.matchMedia("(pointer:fine)").matches;
if (isDesktopMedia && !isTvMedia && !isTvAgent) {
$('#iDebridOptions').multiselect({
$('#iDebridOptions').multiselect({
nonSelectedText: 'None',
buttonTextAlignment: 'left',
onChange: () => generateInstallLink()
@@ -325,7 +325,7 @@ export default function landingTemplate(manifest, config = {}) {
generateInstallLink();
debridProvidersChange();
});
function debridProvidersChange() {
const provider = $('#iDebridProviders').val()
$('#dDebridOptions').toggle(provider !== 'none');
@@ -336,10 +336,10 @@ export default function landingTemplate(manifest, config = {}) {
$('#dOffcloud').toggle(provider === '${MochOptions.offcloud.key}');
$('#dPutio').toggle(provider === '${MochOptions.putio.key}');
}
function generateInstallLink() {
const limitValue = $('#iLimit').val() || '';
const debridOptionsValue = $('#iDebridOptions').val().join(',') || '';
const realDebridValue = $('#iRealDebrid').val() || '';
const allDebridValue = $('#iAllDebrid').val() || '';
@@ -348,9 +348,9 @@ export default function landingTemplate(manifest, config = {}) {
const offcloudValue = $('#iOffcloud').val() || ''
const putioClientIdValue = $('#iPutioClientId').val() || '';
const putioTokenValue = $('#iPutioToken').val() || '';
const limit = /^[1-9][0-9]{0,2}$/.test(limitValue) && limitValue;
const debridOptions = debridOptionsValue.length && debridOptionsValue.trim();
const realDebrid = realDebridValue.length && realDebridValue.trim();
const premiumize = premiumizeValue.length && premiumizeValue.trim();
@@ -361,7 +361,7 @@ export default function landingTemplate(manifest, config = {}) {
let configurationValue = [
['limit', limit],
['${DebridOptions.key}', debridOptions],
['${DebridOptions.key}', debridOptions],
['${MochOptions.realdebrid.key}', realDebrid],
['${MochOptions.premiumize.key}', premiumize],
['${MochOptions.alldebrid.key}', allDebrid],

View File

@@ -56,4 +56,4 @@ export function getSources(magnetInfo) {
}
const trackers = Array.isArray(magnetInfo.announce) ? magnetInfo.announce : magnetInfo.announce.split(',');
return trackers.map(tracker => `tracker:${tracker}`).concat(`dht:${magnetInfo.infohash}`);
}
}

View File

@@ -39,4 +39,4 @@ export const cacheConfig = {
STALE_REVALIDATE_AGE: parseInt(process.env.STALE_REVALIDATE_AGE) || 4 * 60 * 60, // 4 hours
STALE_ERROR_AGE: parseInt(process.env.STALE_ERROR_AGE) || 7 * 24 * 60 * 60, // 7 days
GLOBAL_KEY_PREFIX: process.env.GLOBAL_KEY_PREFIX || 'jackettio-addon',
}
}

View File

@@ -31,7 +31,7 @@ export function toStreamInfo(record, type) {
const behaviorHints = bingeGroup ? { bingeGroup } : undefined;
const magnetInfo = decode(record.magneturl)
return cleanOutputObject({
name: name,
title: title,
@@ -61,7 +61,7 @@ function formatSize(size) {
function getBingeGroupParts(record, sameInfo, quality, torrentInfo, fileInfo, type) {
if (type === Type.MOVIE) {
return [quality];
} else if (sameInfo) {
return [quality];
}

View File

@@ -3,4 +3,4 @@ export const Type = {
SERIES: 'series',
ANIME: 'anime',
OTHER: 'other'
};
};

View File

@@ -13,4 +13,4 @@ export function isStaticUrl(url) {
return Object.values(staticVideoUrls).some(videoUrl => url?.endsWith(videoUrl));
}
export default staticVideoUrls
export default staticVideoUrls

View File

@@ -1 +1 @@
*.ts
*.ts

View File

@@ -36,4 +36,4 @@ module.exports = {
"one-var": ["error", { uninitialized: "consecutive" }],
"prefer-destructuring": "warn",
},
};
};

View File

@@ -11,4 +11,4 @@ module.exports = {
},
},
],
};
};

View File

@@ -65,4 +65,4 @@ try {
} catch (e) {
console.log(e);
process.exit(1);
}
}

View File

@@ -18,4 +18,4 @@
"typeRoots": ["node_modules/@types", "src/@types"]
},
"exclude": ["node_modules"]
}
}

View File

@@ -26,7 +26,7 @@ builder.defineStreamHandler((args) => {
if (!args.id.match(/tt\d+/i) && !args.id.match(/kitsu:\d+/i)) {
return Promise.resolve({ streams: [] });
}
return cacheWrapStream(args.id, () => limiter.schedule(() =>
streamHandler(args)
.then(records => records

View File

@@ -25,4 +25,4 @@ export function parseConfiguration(configuration) {
.map(value => keysToUppercase.includes(key) ? value.toUpperCase() : value.toLowerCase()))
return configValues;
}
}

View File

@@ -180,7 +180,7 @@ a.install-link {
}
.input:focus, .btn:focus {
outline: none;
outline: none;
box-shadow: 0 0 0 2pt rgb(30, 144, 255, 0.7);
}
`;
@@ -228,7 +228,7 @@ export default function landingTemplate(manifest, config = {}) {
.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});">
@@ -264,73 +264,73 @@ export default function landingTemplate(manifest, config = {}) {
</ul>
<div class="separator"></div>
<label class="label" for="iSort">Sorting:</label>
<select id="iSort" class="input" onchange="sortModeChange()">
${sortOptionsHTML}
</select>
<label class="label" for="iLanguages">Priority foreign language:</label>
<select id="iLanguages" class="input" onchange="generateInstallLink()" name="languages[]" multiple="multiple" title="Streams with the selected dubs/subs language will be shown on the top">
${languagesOptionsHTML}
</select>
<label class="label" for="iQualityFilter">Exclude qualities/resolutions:</label>
<select id="iQualityFilter" class="input" onchange="generateInstallLink()" name="qualityFilters[]" multiple="multiple">
${qualityFiltersHTML}
</select>
<label class="label" id="iLimitLabel" for="iLimit">Max results per quality:</label>
<input type="text" inputmode="numeric" pattern="[0-9]*" id="iLimit" onchange="generateInstallLink()" class="input" placeholder="All results">
<label class="label" id="iSizeFilterLabel" for="iSizeFilter">Video size limit:</label>
<input type="text" pattern="([0-9.]*(?:MB|GB),?)+" id="iSizeFilter" onchange="generateInstallLink()" class="input" placeholder="No limit" title="Returned videos cannot exceed this size, use comma to have different size for movies and series. Examples: 5GB ; 800MB ; 10GB,2GB">
<label class="label" for="iDebridProviders">Debrid provider:</label>
<select id="iDebridProviders" class="input" onchange="debridProvidersChange()">
<option value="none" selected>None</option>
${debridProvidersHTML}
</select>
<div id="dRealDebrid">
<label class="label" for="iRealDebrid">RealDebrid API Key (Find it <a href='https://real-debrid.com/apitoken' target="_blank">here</a>):</label>
<input type="text" id="iRealDebrid" onchange="generateInstallLink()" class="input">
</div>
<div id="dAllDebrid">
<label class="label" for="iAllDebrid">AllDebrid API Key (Create it <a href='https://alldebrid.com/apikeys' target="_blank">here</a>):</label>
<input type="text" id="iAllDebrid" onchange="generateInstallLink()" class="input">
</div>
<div id="dPremiumize">
<label class="label" for="iPremiumize">Premiumize API Key (Find it <a href='https://www.premiumize.me/account' target="_blank">here</a>):</label>
<input type="text" id="iPremiumize" onchange="generateInstallLink()" class="input">
</div>
<div id="dDebridLink">
<label class="label" for="iDebridLink">DebridLink API Key (Find it <a href='https://debrid-link.fr/webapp/apikey' target="_blank">here</a>):</label>
<input type="text" id="iDebridLink" onchange="generateInstallLink()" class="input">
</div>
<div id="dOffcloud">
<label class="label" for="iOffcloud">Offcloud API Key (Find it <a href='https://offcloud.com/#/account' target="_blank">here</a>):</label>
<input type="text" id="iOffcloud" onchange="generateInstallLink()" class="input">
</div>
<div id="dPutio">
<label class="label" for="iPutio">Put.io ClientId and Token (Create new OAuth App <a href='https://app.put.io/oauth' target="_blank">here</a>):</label>
<input type="text" id="iPutioClientId" placeholder="ClientId" onchange="generateInstallLink()" class="input">
<input type="text" id="iPutioToken" placeholder="Token" onchange="generateInstallLink()" class="input">
</div>
<div id="dDebridOptions">
<label class="label" for="iDebridOptions">Debrid options:</label>
<select id="iDebridOptions" class="input" onchange="generateInstallLink()" name="debridOptions[]" multiple="multiple">
${debridOptionsHTML}
</select>
</div>
<div class="separator"></div>
<a id="installLink" class="install-link" href="#">
@@ -339,7 +339,7 @@ export default function landingTemplate(manifest, config = {}) {
<div class="contact">
<p>Or paste into Stremio search bar after clicking install</p>
</div>
<div class="separator"></div>
</div>
<script type="text/javascript">
@@ -348,19 +348,19 @@ export default function landingTemplate(manifest, config = {}) {
const isTvAgent = /\\b(?:tv|wv)\\b/i.test(navigator.userAgent)
const isDesktopMedia = window.matchMedia("(pointer:fine)").matches;
if (isDesktopMedia && !isTvMedia && !isTvAgent) {
$('#iLanguages').multiselect({
$('#iLanguages').multiselect({
nonSelectedText: 'None',
buttonTextAlignment: 'left',
onChange: () => generateInstallLink()
});
$('#iLanguages').multiselect('select', [${languages.map(language => '"' + language + '"')}]);
$('#iQualityFilter').multiselect({
$('#iQualityFilter').multiselect({
nonSelectedText: 'None',
buttonTextAlignment: 'left',
onChange: () => generateInstallLink()
});
$('#iQualityFilter').multiselect('select', [${qualityFilters.map(filter => '"' + filter + '"')}]);
$('#iDebridOptions').multiselect({
$('#iDebridOptions').multiselect({
nonSelectedText: 'None',
buttonTextAlignment: 'left',
onChange: () => generateInstallLink()
@@ -385,7 +385,7 @@ export default function landingTemplate(manifest, config = {}) {
generateInstallLink();
debridProvidersChange();
});
function sortModeChange() {
if (['${SortOptions.options.seeders.key}', '${SortOptions.options.size.key}'].includes($('#iSort').val())) {
$("#iLimitLabel").text("Max results:");
@@ -394,7 +394,7 @@ export default function landingTemplate(manifest, config = {}) {
}
generateInstallLink();
}
function debridProvidersChange() {
const provider = $('#iDebridProviders').val()
$('#dDebridOptions').toggle(provider !== 'none');
@@ -405,14 +405,14 @@ export default function landingTemplate(manifest, config = {}) {
$('#dOffcloud').toggle(provider === '${MochOptions.offcloud.key}');
$('#dPutio').toggle(provider === '${MochOptions.putio.key}');
}
function generateInstallLink() {
const qualityFilterValue = $('#iQualityFilter').val().join(',') || '';
const sortValue = $('#iSort').val() || '';
const languagesValue = $('#iLanguages').val().join(',') || [];
const limitValue = $('#iLimit').val() || '';
const sizeFilterValue = $('#iSizeFilter').val() || '';
const debridOptionsValue = $('#iDebridOptions').val().join(',') || '';
const realDebridValue = $('#iRealDebrid').val() || '';
const allDebridValue = $('#iAllDebrid').val() || '';
@@ -421,13 +421,13 @@ export default function landingTemplate(manifest, config = {}) {
const offcloudValue = $('#iOffcloud').val() || ''
const putioClientIdValue = $('#iPutioClientId').val() || '';
const putioTokenValue = $('#iPutioToken').val() || '';
const qualityFilters = qualityFilterValue.length && qualityFilterValue;
const sort = sortValue !== '${SortOptions.options.qualitySeeders.key}' && sortValue;
const languages = languagesValue.length && languagesValue;
const limit = /^[1-9][0-9]{0,2}$/.test(limitValue) && limitValue;
const sizeFilter = sizeFilterValue.length && sizeFilterValue;
const debridOptions = debridOptionsValue.length && debridOptionsValue.trim();
const realDebrid = realDebridValue.length && realDebridValue.trim();
const premiumize = premiumizeValue.length && premiumizeValue.trim();
@@ -442,7 +442,7 @@ export default function landingTemplate(manifest, config = {}) {
['${QualityFilter.key}', qualityFilters],
['limit', limit],
['${SizeFilter.key}', sizeFilter],
['${DebridOptions.key}', debridOptions],
['${DebridOptions.key}', debridOptions],
['${MochOptions.realdebrid.key}', realDebrid],
['${MochOptions.premiumize.key}', premiumize],
['${MochOptions.alldebrid.key}', allDebrid],

View File

@@ -85,4 +85,4 @@ export function getSources(trackersInput, infoHash) {
function unique(array) {
return Array.from(new Set(array));
}
}

View File

@@ -36,7 +36,7 @@ export default function sortStreams(streams, config) {
function _sortStreams(streams, config) {
const limit = /^[1-9][0-9]*$/.test(config.limit) && parseInt(config.limit) || undefined;
return sortBySize(streams, limit);
}

View File

@@ -3,4 +3,4 @@ export const Type = {
SERIES: 'series',
ANIME: 'anime',
OTHER: 'other'
};
};

View File

@@ -13,4 +13,4 @@ export function isStaticUrl(url) {
return Object.values(staticVideoUrls).some(videoUrl => url?.endsWith(videoUrl));
}
export default staticVideoUrls
export default staticVideoUrls

View File

@@ -1,3 +1,3 @@
dist/
esbuild.ts
jest.config.ts
jest.config.ts

View File

@@ -81,4 +81,4 @@
}
}
]
}
}

View File

@@ -1 +1 @@
v20.10.0
v20.10.0

View File

@@ -44,4 +44,4 @@ try {
} catch (e) {
console.log(e);
process.exit(1);
}
}

View File

@@ -11,4 +11,4 @@ export default {
transform: {
'^.+\\.tsx?$': 'ts-jest',
},
};
};

View File

@@ -1,4 +1,4 @@
export enum CacheType {
Memory = 'memory',
MongoDb = 'mongodb'
}
}

View File

@@ -2,4 +2,4 @@ export enum TorrentType {
Series = 'Series',
Movie = 'Movie',
Anime = 'anime',
}
}

View File

@@ -15,4 +15,4 @@ export const BooleanHelpers = {
throw new Error(`Invalid boolean value: '${value}'. Allowed values are 'true', 'false', 'yes', 'no', '1', or '0'.`);
}
}
}
}

View File

@@ -63,4 +63,4 @@ export const ExtensionHelpers = {
const extensionMatch = filename.match(/\.(\w{2,4})$/);
return extensionMatch !== null && extensions.includes(extensionMatch[1].toLowerCase());
}
}
}

View File

@@ -34,4 +34,4 @@ export const PromiseHelpers = {
return array.sort((a, b) => array.filter(v => v === a).length - array.filter(v => v === b).length).pop();
}
};
/* eslint-enable @typescript-eslint/no-explicit-any */
/* eslint-enable @typescript-eslint/no-explicit-any */

View File

@@ -1,3 +1,3 @@
export interface ICacheOptions {
ttl: number;
}
}

View File

@@ -8,4 +8,4 @@ export interface ICacheService {
cacheTrackers: (method: CacheMethod) => Promise<any>;
}
/* eslint-enable @typescript-eslint/no-explicit-any */
/* eslint-enable @typescript-eslint/no-explicit-any */

View File

@@ -81,4 +81,4 @@ export interface ICinemetaLink {
export interface ICinemetaBehaviorHints {
defaultVideoId?: null;
hasScheduledVideos?: boolean;
}
}

View File

@@ -6,4 +6,4 @@ export interface ICommonVideoMetadata {
name?: string;
id?: string;
imdb_id?: string;
}
}

View File

@@ -12,4 +12,4 @@ export interface IIngestedRabbitTorrent {
export interface IIngestedRabbitMessage {
message: IIngestedRabbitTorrent;
}
}

View File

@@ -20,4 +20,4 @@ export interface IKitsuCatalogMetaData {
background: string;
trailers: IKitsuTrailer[];
links: IKitsuLink[];
}
}

View File

@@ -46,4 +46,4 @@ export interface IKitsuLink {
name?: string;
category?: string;
url?: string;
}
}

View File

@@ -9,4 +9,4 @@ export interface ILoggingService {
warn(message: string, ...args: any[]): void;
}
/* eslint-enable @typescript-eslint/no-explicit-any */
/* eslint-enable @typescript-eslint/no-explicit-any */

View File

@@ -6,4 +6,4 @@ export interface IMetaDataQuery {
season?: number
episode?: number
id?: string | number
}
}

View File

@@ -12,4 +12,4 @@ export interface IMetadataResponse {
videos?: ICommonVideoMetadata[];
episodeCount?: number[];
totalCount?: number;
}
}

View File

@@ -11,4 +11,4 @@ export interface IMetadataService {
isEpisodeImdbId(imdbId: string | undefined): Promise<boolean>;
escapeTitle(title: string): string;
}
}

View File

@@ -33,4 +33,4 @@ export interface IParseTorrentTitleResult {
videoFile?: IFileAttributes;
folderName?: string;
fileName?: string;
}
}

View File

@@ -15,4 +15,4 @@ export interface IParsedTorrent extends IParseTorrentTitleResult {
seeders?: number;
torrentId?: string;
fileCollection?: ITorrentFileCollection;
}
}

View File

@@ -1,3 +1,3 @@
export interface IProcessTorrentsJob {
listenToQueue: () => Promise<void>;
}
}

View File

@@ -3,4 +3,4 @@ import {ITorrentFileCollection} from "@interfaces/torrent_file_collection";
export interface ITorrentDownloadService {
getTorrentFiles(torrent: IParsedTorrent, timeout: number): Promise<ITorrentFileCollection>;
}
}

View File

@@ -15,4 +15,4 @@ export interface ITorrentEntriesService {
createTorrentContents(torrent: Torrent): Promise<void>;
updateTorrentSeeders(torrent: ITorrentAttributes): Promise<[number] | undefined>;
}
}

View File

@@ -6,4 +6,4 @@ export interface ITorrentFileCollection {
contents?: IContentAttributes[];
videos?: IFileAttributes[];
subtitles?: ISubtitleAttributes[];
}
}

View File

@@ -5,4 +5,4 @@ export interface ITorrentFileService {
parseTorrentFiles(torrent: IParsedTorrent): Promise<ITorrentFileCollection>;
isPackTorrent(torrent: IParsedTorrent): boolean;
}
}

View File

@@ -2,4 +2,4 @@ import {IIngestedTorrentAttributes} from "@repository/interfaces/ingested_torren
export interface ITorrentProcessingService {
processTorrentRecord(torrent: IIngestedTorrentAttributes): Promise<void>;
}
}

View File

@@ -2,4 +2,4 @@ import {ITorrentFileCollection} from "@interfaces/torrent_file_collection";
export interface ITorrentSubtitleService {
assignSubtitles(fileCollection: ITorrentFileCollection): ITorrentFileCollection;
}
}

View File

@@ -1,3 +1,3 @@
export interface ITrackerService {
getTrackers(): Promise<string[]>;
}
}

View File

@@ -60,4 +60,4 @@ export class ProcessTorrentsJob implements IProcessTorrentsJob {
this.logger.error('Failed to setup channel', error);
}
};
}
}

View File

@@ -12,4 +12,4 @@ export const cacheConfig = {
get MONGO_URI(): string {
return `mongodb://${this.MONGODB_USER}:${this.MONGODB_PASSWORD}@${this.MONGODB_HOST}:${this.MONGODB_PORT}/${this.MONGODB_DB}?authSource=admin`;
}
};
};

View File

@@ -11,4 +11,4 @@ export const databaseConfig = {
get POSTGRES_URI(): string {
return `postgres://${this.POSTGRES_USER}:${this.POSTGRES_PASSWORD}@${this.POSTGRES_HOST}:${this.POSTGRES_PORT}/${this.POSTGRES_DB}`;
}
};
};

View File

@@ -3,4 +3,4 @@ import {BooleanHelpers} from "@helpers/boolean_helpers";
export const jobConfig = {
JOB_CONCURRENCY: parseInt(process.env.JOB_CONCURRENCY || "1", 10),
JOBS_ENABLED: BooleanHelpers.parseBool(process.env.JOBS_ENABLED, true)
};
};

View File

@@ -2,4 +2,4 @@ export const metadataConfig = {
IMDB_CONCURRENT: parseInt(process.env.IMDB_CONCURRENT || "1", 10),
IMDB_INTERVAL_MS: parseInt(process.env.IMDB_INTERVAL_MS || "1000", 10),
TITLE_MATCH_THRESHOLD: Number(process.env.TITLE_MATCH_THRESHOLD || 0.25),
};
};

View File

@@ -6,8 +6,8 @@ export const rabbitConfig = {
PASSWORD: process.env.RABBITMQ_PASSWORD || 'guest',
QUEUE_NAME: process.env.RABBITMQ_QUEUE_NAME || 'ingested',
DURABLE: BooleanHelpers.parseBool(process.env.RABBITMQ_DURABLE, true),
get RABBIT_URI(): string {
return `amqp://${this.USER}:${this.PASSWORD}@${this.HOST}?heartbeat=30`;
}
};
};

View File

@@ -1,4 +1,4 @@
export const torrentConfig = {
MAX_CONNECTIONS_PER_TORRENT: parseInt(process.env.MAX_CONNECTIONS_PER_TORRENT || "20", 10),
TIMEOUT: parseInt(process.env.TORRENT_TIMEOUT || "30000", 10)
};
};

View File

@@ -3,4 +3,4 @@ import {BooleanHelpers} from "@helpers/boolean_helpers";
export const trackerConfig = {
TRACKERS_URL: process.env.TRACKERS_URL || 'https://ngosang.github.io/trackerslist/trackers_all.txt',
UDP_ENABLED: BooleanHelpers.parseBool(process.env.UDP_TRACKERS_ENABLED, false)
};
};

View File

@@ -10,4 +10,4 @@ export interface IImdbEntry extends Document {
RuntimeMinutes: string;
StartYear: string;
TitleType: string;
}
}

View File

@@ -2,4 +2,4 @@ export interface IMongoMetadataQuery {
$text: { $search: string },
TitleType: string,
StartYear?: string;
}
}

View File

@@ -1,4 +1,4 @@
export interface IMongoRepository {
connect(): Promise<void>;
getImdbId(title: string, category: string, year?: string | number): Promise<string | null>;
}
}

View File

@@ -15,4 +15,4 @@ const ImdbEntriesSchema: Schema = new Schema({
ImdbEntriesSchema.index({ PrimaryTitle: 'text', TitleType: 1, StartYear: 1 }, { background: true });
export const ImdbEntryModel = mongoose.model<IImdbEntry>('ImdbEntry', ImdbEntriesSchema, 'imdb-entries');
export const ImdbEntryModel = mongoose.model<IImdbEntry>('ImdbEntry', ImdbEntriesSchema, 'imdb-entries');

View File

@@ -20,7 +20,7 @@ const fuseOptions : IFuseOptions<IImdbEntry> = {
export class MongoRepository implements IMongoRepository {
@inject(IocTypes.ILoggingService) private logger: ILoggingService;
private db: typeof mongoose = mongoose;
async connect() : Promise<void> {
try {
await this.db.connect(configurationService.cacheConfig.MONGO_URI, {directConnection: true});
@@ -60,4 +60,4 @@ export class MongoRepository implements IMongoRepository {
return null;
}
}
}
}

View File

@@ -257,4 +257,4 @@ export class DatabaseRepository implements IDatabaseRepository {
return newDatabase;
};
}
}

View File

@@ -8,4 +8,4 @@ export interface IContentAttributes {
}
export interface IContentCreationAttributes extends Optional<IContentAttributes, 'fileIndex' | 'size'> {
}
}

View File

@@ -61,4 +61,4 @@ export interface IDatabaseRepository {
getSkipTorrent(infoHash: string): Promise<SkipTorrent>;
createSkipTorrent(torrent: ITorrentCreationAttributes): Promise<[SkipTorrent, boolean | null]>;
}
}

View File

@@ -19,4 +19,4 @@ export interface IFileAttributes extends IParseTorrentTitleResult {
}
export interface IFileCreationAttributes extends Optional<IFileAttributes, 'fileIndex' | 'size' | 'imdbId' | 'imdbSeason' | 'imdbEpisode' | 'kitsuId' | 'kitsuEpisode'> {
}
}

View File

@@ -3,4 +3,4 @@ export interface IIngestedPageAttributes {
}
export interface IIngestedPageCreationAttributes extends IIngestedPageAttributes {
}
}

Some files were not shown because too many files have changed in this diff Show More