Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c75ecd2707 | ||
|
|
c493ef3376 |
@@ -94,7 +94,7 @@ services:
|
|||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
env_file: stack.env
|
env_file: stack.env
|
||||||
hostname: knightcrawler-addon
|
hostname: knightcrawler-addon
|
||||||
image: gabisonfire/knightcrawler-addon:2.0.15
|
image: gabisonfire/knightcrawler-addon:2.0.17
|
||||||
labels:
|
labels:
|
||||||
logging: promtail
|
logging: promtail
|
||||||
networks:
|
networks:
|
||||||
@@ -117,7 +117,7 @@ services:
|
|||||||
redis:
|
redis:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
env_file: stack.env
|
env_file: stack.env
|
||||||
image: gabisonfire/knightcrawler-consumer:2.0.15
|
image: gabisonfire/knightcrawler-consumer:2.0.17
|
||||||
labels:
|
labels:
|
||||||
logging: promtail
|
logging: promtail
|
||||||
networks:
|
networks:
|
||||||
@@ -138,7 +138,7 @@ services:
|
|||||||
redis:
|
redis:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
env_file: stack.env
|
env_file: stack.env
|
||||||
image: gabisonfire/knightcrawler-debrid-collector:2.0.15
|
image: gabisonfire/knightcrawler-debrid-collector:2.0.17
|
||||||
labels:
|
labels:
|
||||||
logging: promtail
|
logging: promtail
|
||||||
networks:
|
networks:
|
||||||
@@ -152,7 +152,7 @@ services:
|
|||||||
migrator:
|
migrator:
|
||||||
condition: service_completed_successfully
|
condition: service_completed_successfully
|
||||||
env_file: stack.env
|
env_file: stack.env
|
||||||
image: gabisonfire/knightcrawler-metadata:2.0.15
|
image: gabisonfire/knightcrawler-metadata:2.0.17
|
||||||
networks:
|
networks:
|
||||||
- knightcrawler-network
|
- knightcrawler-network
|
||||||
restart: "no"
|
restart: "no"
|
||||||
@@ -163,7 +163,7 @@ services:
|
|||||||
postgres:
|
postgres:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
env_file: stack.env
|
env_file: stack.env
|
||||||
image: gabisonfire/knightcrawler-migrator:2.0.15
|
image: gabisonfire/knightcrawler-migrator:2.0.17
|
||||||
networks:
|
networks:
|
||||||
- knightcrawler-network
|
- knightcrawler-network
|
||||||
restart: "no"
|
restart: "no"
|
||||||
@@ -182,7 +182,7 @@ services:
|
|||||||
redis:
|
redis:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
env_file: stack.env
|
env_file: stack.env
|
||||||
image: gabisonfire/knightcrawler-producer:2.0.15
|
image: gabisonfire/knightcrawler-producer:2.0.17
|
||||||
labels:
|
labels:
|
||||||
logging: promtail
|
logging: promtail
|
||||||
networks:
|
networks:
|
||||||
@@ -207,7 +207,7 @@ services:
|
|||||||
deploy:
|
deploy:
|
||||||
replicas: ${QBIT_REPLICAS:-0}
|
replicas: ${QBIT_REPLICAS:-0}
|
||||||
env_file: stack.env
|
env_file: stack.env
|
||||||
image: gabisonfire/knightcrawler-qbit-collector:2.0.15
|
image: gabisonfire/knightcrawler-qbit-collector:2.0.17
|
||||||
labels:
|
labels:
|
||||||
logging: promtail
|
logging: promtail
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ x-depends: &knightcrawler-app-depends
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
metadata:
|
metadata:
|
||||||
image: gabisonfire/knightcrawler-metadata:2.0.15
|
image: gabisonfire/knightcrawler-metadata:2.0.17
|
||||||
env_file: ../../.env
|
env_file: ../../.env
|
||||||
networks:
|
networks:
|
||||||
- knightcrawler-network
|
- knightcrawler-network
|
||||||
@@ -30,7 +30,7 @@ services:
|
|||||||
condition: service_completed_successfully
|
condition: service_completed_successfully
|
||||||
|
|
||||||
migrator:
|
migrator:
|
||||||
image: gabisonfire/knightcrawler-migrator:2.0.15
|
image: gabisonfire/knightcrawler-migrator:2.0.17
|
||||||
env_file: ../../.env
|
env_file: ../../.env
|
||||||
networks:
|
networks:
|
||||||
- knightcrawler-network
|
- knightcrawler-network
|
||||||
@@ -40,7 +40,7 @@ services:
|
|||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|
||||||
addon:
|
addon:
|
||||||
image: gabisonfire/knightcrawler-addon:2.0.15
|
image: gabisonfire/knightcrawler-addon:2.0.17
|
||||||
<<: [*knightcrawler-app, *knightcrawler-app-depends]
|
<<: [*knightcrawler-app, *knightcrawler-app-depends]
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
hostname: knightcrawler-addon
|
hostname: knightcrawler-addon
|
||||||
@@ -48,22 +48,22 @@ services:
|
|||||||
- "7000:7000"
|
- "7000:7000"
|
||||||
|
|
||||||
consumer:
|
consumer:
|
||||||
image: gabisonfire/knightcrawler-consumer:2.0.15
|
image: gabisonfire/knightcrawler-consumer:2.0.17
|
||||||
<<: [*knightcrawler-app, *knightcrawler-app-depends]
|
<<: [*knightcrawler-app, *knightcrawler-app-depends]
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
debridcollector:
|
debridcollector:
|
||||||
image: gabisonfire/knightcrawler-debrid-collector:2.0.15
|
image: gabisonfire/knightcrawler-debrid-collector:2.0.17
|
||||||
<<: [*knightcrawler-app, *knightcrawler-app-depends]
|
<<: [*knightcrawler-app, *knightcrawler-app-depends]
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
producer:
|
producer:
|
||||||
image: gabisonfire/knightcrawler-producer:2.0.15
|
image: gabisonfire/knightcrawler-producer:2.0.17
|
||||||
<<: [*knightcrawler-app, *knightcrawler-app-depends]
|
<<: [*knightcrawler-app, *knightcrawler-app-depends]
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
qbitcollector:
|
qbitcollector:
|
||||||
image: gabisonfire/knightcrawler-qbit-collector:2.0.15
|
image: gabisonfire/knightcrawler-qbit-collector:2.0.17
|
||||||
<<: [*knightcrawler-app, *knightcrawler-app-depends]
|
<<: [*knightcrawler-app, *knightcrawler-app-depends]
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|||||||
@@ -108,14 +108,18 @@ public partial class DebridMediaManagerCrawler(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var parsedTorrent = rankTorrentName.Parse(torrentTitle.CleanTorrentTitleForImdb());
|
var parsedTorrent = rankTorrentName.Parse(torrentTitle);
|
||||||
|
|
||||||
if (!parsedTorrent.Success)
|
if (!parsedTorrent.Success)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var (cached, cachedResult) = await CheckIfInCacheAndReturn(parsedTorrent.Response.ParsedTitle);
|
var torrentType = parsedTorrent.Response.IsMovie ? "movie" : "tvSeries";
|
||||||
|
|
||||||
|
var cacheKey = GetCacheKey(torrentType, parsedTorrent.Response.ParsedTitle, parsedTorrent.Response.Year);
|
||||||
|
|
||||||
|
var (cached, cachedResult) = await CheckIfInCacheAndReturn(cacheKey);
|
||||||
|
|
||||||
if (cached)
|
if (cached)
|
||||||
{
|
{
|
||||||
@@ -124,14 +128,14 @@ public partial class DebridMediaManagerCrawler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
int? year = parsedTorrent.Response.Year != 0 ? parsedTorrent.Response.Year : null;
|
int? year = parsedTorrent.Response.Year != 0 ? parsedTorrent.Response.Year : null;
|
||||||
var imdbEntry = await Storage.FindImdbMetadata(parsedTorrent.Response.ParsedTitle, parsedTorrent.Response.IsMovie ? "movies" : "tv", year);
|
var imdbEntry = await Storage.FindImdbMetadata(parsedTorrent.Response.ParsedTitle, torrentType, year);
|
||||||
|
|
||||||
if (imdbEntry is null)
|
if (imdbEntry is null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
await AddToCache(parsedTorrent.Response.ParsedTitle.ToLowerInvariant(), imdbEntry);
|
await AddToCache(cacheKey, imdbEntry);
|
||||||
|
|
||||||
logger.LogInformation("[{ImdbId}] Found best match for {Title}: {BestMatch} with score {Score}", imdbEntry.ImdbId, parsedTorrent.Response.ParsedTitle, imdbEntry.Title, imdbEntry.Score);
|
logger.LogInformation("[{ImdbId}] Found best match for {Title}: {BestMatch} with score {Score}", imdbEntry.ImdbId, parsedTorrent.Response.ParsedTitle, imdbEntry.Title, imdbEntry.Score);
|
||||||
|
|
||||||
@@ -153,19 +157,19 @@ public partial class DebridMediaManagerCrawler(
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
private Task AddToCache(string lowerCaseTitle, ImdbEntry best)
|
private Task AddToCache(string cacheKey, ImdbEntry best)
|
||||||
{
|
{
|
||||||
var cacheOptions = new DistributedCacheEntryOptions
|
var cacheOptions = new DistributedCacheEntryOptions
|
||||||
{
|
{
|
||||||
AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(1),
|
AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(1),
|
||||||
};
|
};
|
||||||
|
|
||||||
return cache.SetStringAsync(lowerCaseTitle, JsonSerializer.Serialize(best), cacheOptions);
|
return cache.SetStringAsync(cacheKey, JsonSerializer.Serialize(best), cacheOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<(bool Success, ImdbEntry? Entry)> CheckIfInCacheAndReturn(string title)
|
private async Task<(bool Success, ImdbEntry? Entry)> CheckIfInCacheAndReturn(string cacheKey)
|
||||||
{
|
{
|
||||||
var cachedImdbId = await cache.GetStringAsync(title.ToLowerInvariant());
|
var cachedImdbId = await cache.GetStringAsync(cacheKey);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(cachedImdbId))
|
if (!string.IsNullOrEmpty(cachedImdbId))
|
||||||
{
|
{
|
||||||
@@ -207,16 +211,12 @@ public partial class DebridMediaManagerCrawler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static string AssignCategory(ImdbEntry entry) =>
|
private static string AssignCategory(ImdbEntry entry) =>
|
||||||
entry.Category switch
|
entry.Category.ToLower() switch
|
||||||
{
|
{
|
||||||
"movie" => "movies",
|
var category when string.Equals(category, "movie", StringComparison.OrdinalIgnoreCase) => "movies",
|
||||||
"tvMovie" => "movies",
|
var category when string.Equals(category, "tvSeries", StringComparison.OrdinalIgnoreCase) => "tv",
|
||||||
"tvSeries" => "tv",
|
|
||||||
"tvEpisode" => "tv",
|
|
||||||
"tvSpecial" => "tv",
|
|
||||||
"tvMiniSeries" => "tv",
|
|
||||||
"tv" => "tv",
|
|
||||||
"short" => "tv",
|
|
||||||
_ => "unknown",
|
_ => "unknown",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static string GetCacheKey(string category, string title, int year) => $"{category.ToLowerInvariant()}:{year}:{title.ToLowerInvariant()}";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
rank-torrent-name==0.1.9
|
rank-torrent-name==0.1.8
|
||||||
@@ -18,6 +18,7 @@ public static class ServiceCollectionExtensions
|
|||||||
services.AddHttpClient();
|
services.AddHttpClient();
|
||||||
services.AddSingleton<ITrackersService, TrackersService>();
|
services.AddSingleton<ITrackersService, TrackersService>();
|
||||||
services.AddHostedService<TrackersBackgroundService>();
|
services.AddHostedService<TrackersBackgroundService>();
|
||||||
|
services.AddHostedService<HousekeepingBackgroundService>();
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
namespace QBitCollector.Features.Qbit;
|
||||||
|
|
||||||
|
public class HousekeepingBackgroundService(IQBittorrentClient client, ILogger<HousekeepingBackgroundService> logger) : BackgroundService
|
||||||
|
{
|
||||||
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
|
logger.LogInformation("Service is Running.");
|
||||||
|
|
||||||
|
await DoWork();
|
||||||
|
|
||||||
|
using PeriodicTimer timer = new(TimeSpan.FromMinutes(2));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (await timer.WaitForNextTickAsync(stoppingToken))
|
||||||
|
{
|
||||||
|
await DoWork();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
logger.LogInformation("Service stopping.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DoWork()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
logger.LogInformation("Cleaning Stale Entries in Qbit...");
|
||||||
|
|
||||||
|
var torrents = await client.GetTorrentListAsync();
|
||||||
|
|
||||||
|
foreach (var torrentInfo in torrents)
|
||||||
|
{
|
||||||
|
if (!(torrentInfo.AddedOn < DateTimeOffset.UtcNow.AddMinutes(-1)))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.LogInformation("Torrent [{InfoHash}] Identified as stale because was added at {AddedOn}", torrentInfo.Hash, torrentInfo.AddedOn);
|
||||||
|
|
||||||
|
await client.DeleteAsync(new[] {torrentInfo.Hash}, deleteDownloadedData: true);
|
||||||
|
logger.LogInformation("Cleaned up stale torrent: [{InfoHash}]", torrentInfo.Hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
logger.LogError(e, "Error cleaning up stale torrents this interval.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -118,7 +118,7 @@ public class DapperDataStorage(PostgresConfiguration configuration, RabbitMqConf
|
|||||||
public async Task<ImdbEntry?> FindImdbMetadata(string? parsedTorrentTitle, string torrentType, int? year, CancellationToken cancellationToken = default) =>
|
public async Task<ImdbEntry?> FindImdbMetadata(string? parsedTorrentTitle, string torrentType, int? year, CancellationToken cancellationToken = default) =>
|
||||||
await ExecuteCommandAsync(async connection =>
|
await ExecuteCommandAsync(async connection =>
|
||||||
{
|
{
|
||||||
var query = $"select \"imdb_id\" as \"ImdbId\", \"title\" as \"Title\", \"year\" as \"Year\", \"score\" as Score from search_imdb_meta('{parsedTorrentTitle.Replace("'", "").Replace("\"", "")}', '{(torrentType.Equals("movie", StringComparison.OrdinalIgnoreCase) ? "movie" : "tvSeries")}'";
|
var query = $"select \"imdb_id\" as \"ImdbId\", \"title\" as \"Title\", \"year\" as \"Year\", \"score\" as Score, \"category\" as Category from search_imdb_meta('{parsedTorrentTitle.Replace("'", "").Replace("\"", "")}', '{torrentType}'";
|
||||||
query += year is not null ? $", {year}" : ", NULL";
|
query += year is not null ? $", {year}" : ", NULL";
|
||||||
query += ", 1)";
|
query += ", 1)";
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
using System.Text.RegularExpressions;
|
|
||||||
|
|
||||||
namespace SharedContracts.Extensions;
|
namespace SharedContracts.Extensions;
|
||||||
|
|
||||||
public static partial class StringExtensions
|
public static partial class StringExtensions
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
global using System.Text.Json;
|
global using System.Text.Json;
|
||||||
global using System.Text.Json.Serialization;
|
global using System.Text.Json.Serialization;
|
||||||
|
global using System.Text.RegularExpressions;
|
||||||
global using Dapper;
|
global using Dapper;
|
||||||
global using MassTransit;
|
global using MassTransit;
|
||||||
global using Microsoft.AspNetCore.Builder;
|
global using Microsoft.AspNetCore.Builder;
|
||||||
|
|||||||
@@ -20,8 +20,7 @@ public class RankTorrentName : IRankTorrentName
|
|||||||
var result = _rtn?.parse(title);
|
var result = _rtn?.parse(title);
|
||||||
return ParseResult(result);
|
return ParseResult(result);
|
||||||
}, new ParseTorrentTitleResponse(false, null), nameof(Parse), throwOnErrors: false, logErrors: false);
|
}, new ParseTorrentTitleResponse(false, null), nameof(Parse), throwOnErrors: false, logErrors: false);
|
||||||
|
|
||||||
|
|
||||||
private static ParseTorrentTitleResponse ParseResult(dynamic result)
|
private static ParseTorrentTitleResponse ParseResult(dynamic result)
|
||||||
{
|
{
|
||||||
if (result == null)
|
if (result == null)
|
||||||
|
|||||||
@@ -77,7 +77,22 @@ public class RtnResponse
|
|||||||
[JsonPropertyName("extended")]
|
[JsonPropertyName("extended")]
|
||||||
public bool Extended { get; set; }
|
public bool Extended { get; set; }
|
||||||
|
|
||||||
public bool IsMovie => (Season == null && Episode == null) || (Season?.Count == 0 && Episode?.Count == 0);
|
// [JsonPropertyName("is_show")]
|
||||||
|
// public bool IsTvShow { get; set; }
|
||||||
|
//
|
||||||
|
// [JsonPropertyName("is_movie")]
|
||||||
|
// public bool IsMovie { get; set; }
|
||||||
|
|
||||||
public string ToJson() => this.AsJson();
|
public string ToJson() => this.AsJson();
|
||||||
|
|
||||||
|
public bool IsMovie => !TvRegexes.Any(regex => regex.IsMatch(RawTitle)) && Season?.Count == 0 && Episode?.Count == 0;
|
||||||
|
|
||||||
|
private static List<Regex> TvRegexes { get; set; } =
|
||||||
|
[
|
||||||
|
new(@"[se]\d\d", RegexOptions.IgnoreCase),
|
||||||
|
new(@"\b(tv|complete)\b", RegexOptions.IgnoreCase),
|
||||||
|
new(@"\b(saisons?|stages?|seasons?).?\d", RegexOptions.IgnoreCase),
|
||||||
|
new(@"[a-z]\s?\-\s?\d{2,4}\b", RegexOptions.IgnoreCase),
|
||||||
|
new(@"\d{2,4}\s?\-\s?\d{2,4}\b", RegexOptions.IgnoreCase),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user