|
|
|
@ -6,6 +6,7 @@ using System.Text;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using FluentValidation.Results;
|
|
|
|
|
using MonoTorrent;
|
|
|
|
|
using Newtonsoft.Json;
|
|
|
|
|
using NLog;
|
|
|
|
|
using Nostr.Client.Client;
|
|
|
|
@ -27,7 +28,13 @@ public class Nostr : IndexerBase<NostrSettings>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override string Name => "nostr";
|
|
|
|
|
public override IndexerCapabilities Capabilities { get; protected set; }
|
|
|
|
|
|
|
|
|
|
public override IndexerCapabilities Capabilities
|
|
|
|
|
{
|
|
|
|
|
get => GetCapabilities();
|
|
|
|
|
protected set { }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override string[] IndexerUrls => new[] { "wss://nos.lol", "wss://relay.nostr.band", "wss://relay.damus.io" };
|
|
|
|
|
public override string[] LegacyUrls => Array.Empty<string>();
|
|
|
|
|
public override string Description => "nostr torrent index";
|
|
|
|
@ -54,17 +61,50 @@ public class Nostr : IndexerBase<NostrSettings>
|
|
|
|
|
|
|
|
|
|
public override Task<IndexerPageableQueryResult> Fetch(MusicSearchCriteria searchCriteria)
|
|
|
|
|
{
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
var filter = new NostrTorrentFilter()
|
|
|
|
|
{
|
|
|
|
|
Kinds = new[] { (NostrKind)2003 },
|
|
|
|
|
HashTags = new[] { "music" },
|
|
|
|
|
Search = searchCriteria.SearchTerm
|
|
|
|
|
};
|
|
|
|
|
if (searchCriteria.Limit.HasValue)
|
|
|
|
|
{
|
|
|
|
|
filter.Limit = searchCriteria.Limit.Value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return FetchFilter(filter);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override Task<IndexerPageableQueryResult> Fetch(TvSearchCriteria searchCriteria)
|
|
|
|
|
{
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
var filter = new NostrTorrentFilter()
|
|
|
|
|
{
|
|
|
|
|
Kinds = new[] { (NostrKind)2003 },
|
|
|
|
|
HashTags = new[] { "tv" },
|
|
|
|
|
Search = searchCriteria.SearchTerm
|
|
|
|
|
};
|
|
|
|
|
if (searchCriteria.Limit.HasValue)
|
|
|
|
|
{
|
|
|
|
|
filter.Limit = searchCriteria.Limit.Value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return FetchFilter(filter);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override Task<IndexerPageableQueryResult> Fetch(BookSearchCriteria searchCriteria)
|
|
|
|
|
{
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
var filter = new NostrTorrentFilter()
|
|
|
|
|
{
|
|
|
|
|
Kinds = new[] { (NostrKind)2003 },
|
|
|
|
|
HashTags = new[] { "book" },
|
|
|
|
|
Search = searchCriteria.SearchTerm
|
|
|
|
|
};
|
|
|
|
|
if (searchCriteria.Limit.HasValue)
|
|
|
|
|
{
|
|
|
|
|
filter.Limit = searchCriteria.Limit.Value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return FetchFilter(filter);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override Task<IndexerPageableQueryResult> Fetch(BasicSearchCriteria searchCriteria)
|
|
|
|
@ -84,12 +124,62 @@ public class Nostr : IndexerBase<NostrSettings>
|
|
|
|
|
|
|
|
|
|
public override Task<byte[]> Download(Uri link)
|
|
|
|
|
{
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
if (link.Scheme == "magnet")
|
|
|
|
|
{
|
|
|
|
|
return Task.FromResult(Encoding.GetBytes(link.AbsoluteUri));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
throw new Exception("Link format not supported");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override IndexerCapabilities GetCapabilities()
|
|
|
|
|
{
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
var caps = new IndexerCapabilities
|
|
|
|
|
{
|
|
|
|
|
TvSearchParams = new List<TvSearchParam>
|
|
|
|
|
{
|
|
|
|
|
TvSearchParam.Q, TvSearchParam.ImdbId
|
|
|
|
|
},
|
|
|
|
|
MovieSearchParams = new List<MovieSearchParam>
|
|
|
|
|
{
|
|
|
|
|
MovieSearchParam.Q, MovieSearchParam.ImdbId
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
caps.Categories.AddCategoryMapping("video,movie", NewznabStandardCategory.Movies, "Movies");
|
|
|
|
|
caps.Categories.AddCategoryMapping("video,movie,dvdr", NewznabStandardCategory.MoviesDVD, "DVDR Movies");
|
|
|
|
|
caps.Categories.AddCategoryMapping("video,movie,4k", NewznabStandardCategory.MoviesUHD, "4K Movies");
|
|
|
|
|
caps.Categories.AddCategoryMapping("video,movie,hd", NewznabStandardCategory.MoviesHD, "HD Movies");
|
|
|
|
|
|
|
|
|
|
caps.Categories.AddCategoryMapping("video,tv", NewznabStandardCategory.TV, "TV");
|
|
|
|
|
caps.Categories.AddCategoryMapping("video,tv,4k", NewznabStandardCategory.TVUHD, "4K TV");
|
|
|
|
|
caps.Categories.AddCategoryMapping("video,tv,hd", NewznabStandardCategory.TVHD, "HD TV");
|
|
|
|
|
|
|
|
|
|
caps.Categories.AddCategoryMapping("audio", NewznabStandardCategory.Audio, "Audio");
|
|
|
|
|
caps.Categories.AddCategoryMapping("audio,music,flac", NewznabStandardCategory.AudioLossless, "FLAC Audio");
|
|
|
|
|
caps.Categories.AddCategoryMapping("audio,audio-book", NewznabStandardCategory.AudioAudiobook, "Audio Book");
|
|
|
|
|
|
|
|
|
|
caps.Categories.AddCategoryMapping("game,pc", NewznabStandardCategory.PCGames, "Games");
|
|
|
|
|
caps.Categories.AddCategoryMapping("game,mac", NewznabStandardCategory.PCMac, "Mac Games");
|
|
|
|
|
caps.Categories.AddCategoryMapping("game,unix", NewznabStandardCategory.PCGames, "Unix Games");
|
|
|
|
|
caps.Categories.AddCategoryMapping("game,ios", NewznabStandardCategory.PCMobileiOS, "iOS Games");
|
|
|
|
|
caps.Categories.AddCategoryMapping("game,android", NewznabStandardCategory.PCMobileiOS, "Android Games");
|
|
|
|
|
|
|
|
|
|
caps.Categories.AddCategoryMapping("game,psx", NewznabStandardCategory.Console, "PSx Games");
|
|
|
|
|
caps.Categories.AddCategoryMapping("game,xbox", NewznabStandardCategory.ConsoleXBox, "XBOX Games");
|
|
|
|
|
caps.Categories.AddCategoryMapping("game,wii", NewznabStandardCategory.ConsoleWii, "Wii Games");
|
|
|
|
|
|
|
|
|
|
caps.Categories.AddCategoryMapping("porn", NewznabStandardCategory.XXX, "Porn");
|
|
|
|
|
caps.Categories.AddCategoryMapping("porn,movie,dvdr", NewznabStandardCategory.XXXDVD, "DVDR Porn");
|
|
|
|
|
caps.Categories.AddCategoryMapping("porn,movie,hd", NewznabStandardCategory.XXXx264, "HD Porn");
|
|
|
|
|
caps.Categories.AddCategoryMapping("porn,movie,4k", NewznabStandardCategory.XXXUHD, "4K Porn");
|
|
|
|
|
caps.Categories.AddCategoryMapping("porn,picture", NewznabStandardCategory.XXXImageSet, "Porn Images");
|
|
|
|
|
|
|
|
|
|
caps.Categories.AddCategoryMapping("other", NewznabStandardCategory.Other, "Other");
|
|
|
|
|
caps.Categories.AddCategoryMapping("other,comic", NewznabStandardCategory.BooksComics, "Comics");
|
|
|
|
|
caps.Categories.AddCategoryMapping("other,e-book", NewznabStandardCategory.BooksEBook, "E-Books");
|
|
|
|
|
|
|
|
|
|
return caps;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override Task Test(List<ValidationFailure> failures)
|
|
|
|
@ -100,7 +190,7 @@ public class Nostr : IndexerBase<NostrSettings>
|
|
|
|
|
|
|
|
|
|
public override bool SupportsRss => true;
|
|
|
|
|
public override bool SupportsSearch => true;
|
|
|
|
|
public override bool SupportsRedirect => false;
|
|
|
|
|
public override bool SupportsRedirect => true;
|
|
|
|
|
public override bool SupportsPagination => true;
|
|
|
|
|
public override bool FollowRedirect => false;
|
|
|
|
|
|
|
|
|
@ -126,29 +216,64 @@ public class Nostr : IndexerBase<NostrSettings>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var ev = subEvent.Event;
|
|
|
|
|
var files = ev.Tags?.Where(a => a.TagIdentifier == "file").ToArray() ?? Array.Empty<NostrEventTag>();
|
|
|
|
|
results.Releases.Add(new TorrentInfo()
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
Guid = $"nostr-{ev.Id}",
|
|
|
|
|
Title = ev.Tags?.FindFirstTagValue("title"),
|
|
|
|
|
Description = ev.Content,
|
|
|
|
|
Files = files.Length,
|
|
|
|
|
Size = files.Sum(a => long.TryParse(a.AdditionalData[1], out var s) ? s : 0),
|
|
|
|
|
InfoHash = ev.Tags?.FindFirstTagValue("btih"),
|
|
|
|
|
ImdbId = int.TryParse(ev.Tags?.FindFirstTagValue("imdb"), out var x) ? x : default,
|
|
|
|
|
PublishDate = ev.CreatedAt!.Value,
|
|
|
|
|
DownloadProtocol = DownloadProtocol.Torrent
|
|
|
|
|
});
|
|
|
|
|
var btih = ev.Tags?.FindFirstTagValue("btih");
|
|
|
|
|
var title = ev.Tags?.FindFirstTagValue("title");
|
|
|
|
|
if (string.IsNullOrEmpty(btih) || string.IsNullOrEmpty(title) || btih.Length != 40)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var files = ev.Tags?.Where(a => a.TagIdentifier == "file").ToArray() ?? Array.Empty<NostrEventTag>();
|
|
|
|
|
var totalSize = files.Sum(a => long.TryParse(a.AdditionalData[1], out var s) ? s : 0);
|
|
|
|
|
var catTags =
|
|
|
|
|
ev.Tags?.Where(a => a.TagIdentifier == "t").Select(a => a.AdditionalData[0].ToLowerInvariant())
|
|
|
|
|
.ToArray() ??
|
|
|
|
|
Array.Empty<string>();
|
|
|
|
|
var cat = Capabilities.Categories.MapTrackerCatToNewznab(string.Join(",", catTags));
|
|
|
|
|
if (cat.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
cat = Capabilities.Categories.MapTrackerCatToNewznab(string.Join(",", catTags.SkipLast(1)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (cat.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
cat = Capabilities.Categories.MapTrackerCatToNewznab(string.Join(",", catTags.SkipLast(2)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (cat.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
cat = Capabilities.Categories.MapTrackerCatToNewznab("other");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
results.Releases.Add(new TorrentInfo()
|
|
|
|
|
{
|
|
|
|
|
Guid = $"nostr-{ev.Id}",
|
|
|
|
|
Title = title,
|
|
|
|
|
Description = ev.Content,
|
|
|
|
|
Files = files.Length,
|
|
|
|
|
IndexerId = 1,
|
|
|
|
|
Categories = cat,
|
|
|
|
|
Size = totalSize,
|
|
|
|
|
MagnetUrl = new MagnetLink(InfoHash.FromHex(btih), title, null, null, totalSize).ToV1String(),
|
|
|
|
|
InfoHash = btih,
|
|
|
|
|
ImdbId = int.TryParse(ev.Tags?.FindFirstTagValue("imdb"), out var x) ? x : default,
|
|
|
|
|
PublishDate = ev.CreatedAt!.Value,
|
|
|
|
|
DownloadProtocol = DownloadProtocol.Torrent
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
_logger.Warn("Failed to parse event {}", ex.Message);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await client.Communicator.Start();
|
|
|
|
|
client.Send(req);
|
|
|
|
|
|
|
|
|
|
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
|
|
|
|
cts.Token.Register(() =>
|
|
|
|
|
{
|
|
|
|
|
tcs.TrySetException(new TimeoutException());
|
|
|
|
|
});
|
|
|
|
|
cts.Token.Register(() => { tcs.TrySetException(new TimeoutException()); });
|
|
|
|
|
await tcs.Task;
|
|
|
|
|
eoseSub.Dispose();
|
|
|
|
|
eventsSub.Dispose();
|
|
|
|
|