Compare commits
34 Commits
Author | SHA1 | Date | |
---|---|---|---|
5186ae66c9 | |||
c35e1ef517 | |||
|
8f6891142b | ||
|
b52e6d4908 | ||
30c44760e7 | |||
a3ae3c320d | |||
ea262889e6 | |||
445542b653 | |||
b7718220ef | |||
34c62e8658 | |||
a9fcc93670 | |||
68d7ef258f | |||
fdea4f5ea5 | |||
ac3039e587 | |||
3829a1cf26 | |||
c3daa0b751 | |||
3a072beea3 | |||
8e6f2798a9 | |||
9cbde9a6b4 | |||
0870aa9fdb | |||
172650e644 | |||
52ff2e54a8 | |||
61d80a93cf | |||
7be3ee52e9 | |||
981eb0fd9f | |||
47f3044a6d | |||
6d03cc5f8d | |||
290c405f52 | |||
fcdbd32872 | |||
eb6c37cc53 | |||
d922842186 | |||
69323d6d60 | |||
46a0fb8c48 | |||
8316ed08a7 |
@ -22,7 +22,7 @@ jobs:
|
|||||||
# https://github.com/marketplace/actions/docker-setup-buildx
|
# https://github.com/marketplace/actions/docker-setup-buildx
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
id: buildx
|
id: buildx
|
||||||
uses: docker/setup-buildx-action@v3.6.1
|
uses: docker/setup-buildx-action@v3.7.1
|
||||||
|
|
||||||
# https://github.com/docker/login-action#docker-hub
|
# https://github.com/docker/login-action#docker-hub
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
@ -33,7 +33,7 @@ jobs:
|
|||||||
|
|
||||||
# https://github.com/docker/build-push-action#multi-platform-image
|
# https://github.com/docker/build-push-action#multi-platform-image
|
||||||
- name: Build and push API
|
- name: Build and push API
|
||||||
uses: docker/build-push-action@v6.7.0
|
uses: docker/build-push-action@v6.9.0
|
||||||
with:
|
with:
|
||||||
context: ./
|
context: ./
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
|
4
.github/workflows/docker-image-dev.yml
vendored
4
.github/workflows/docker-image-dev.yml
vendored
@ -22,7 +22,7 @@ jobs:
|
|||||||
# https://github.com/marketplace/actions/docker-setup-buildx
|
# https://github.com/marketplace/actions/docker-setup-buildx
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
id: buildx
|
id: buildx
|
||||||
uses: docker/setup-buildx-action@v3.6.1
|
uses: docker/setup-buildx-action@v3.7.1
|
||||||
|
|
||||||
# https://github.com/docker/login-action#docker-hub
|
# https://github.com/docker/login-action#docker-hub
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
@ -33,7 +33,7 @@ jobs:
|
|||||||
|
|
||||||
# https://github.com/docker/build-push-action#multi-platform-image
|
# https://github.com/docker/build-push-action#multi-platform-image
|
||||||
- name: Build and push API
|
- name: Build and push API
|
||||||
uses: docker/build-push-action@v6.7.0
|
uses: docker/build-push-action@v6.9.0
|
||||||
with:
|
with:
|
||||||
context: ./
|
context: ./
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
|
4
.github/workflows/docker-image-master.yml
vendored
4
.github/workflows/docker-image-master.yml
vendored
@ -22,7 +22,7 @@ jobs:
|
|||||||
# https://github.com/marketplace/actions/docker-setup-buildx
|
# https://github.com/marketplace/actions/docker-setup-buildx
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
id: buildx
|
id: buildx
|
||||||
uses: docker/setup-buildx-action@v3.6.1
|
uses: docker/setup-buildx-action@v3.7.1
|
||||||
|
|
||||||
# https://github.com/docker/login-action#docker-hub
|
# https://github.com/docker/login-action#docker-hub
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
@ -33,7 +33,7 @@ jobs:
|
|||||||
|
|
||||||
# https://github.com/docker/build-push-action#multi-platform-image
|
# https://github.com/docker/build-push-action#multi-platform-image
|
||||||
- name: Build and push API
|
- name: Build and push API
|
||||||
uses: docker/build-push-action@v6.7.0
|
uses: docker/build-push-action@v6.9.0
|
||||||
with:
|
with:
|
||||||
context: ./
|
context: ./
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
|
4
.github/workflows/docker-image-serverv2.yml
vendored
4
.github/workflows/docker-image-serverv2.yml
vendored
@ -22,7 +22,7 @@ jobs:
|
|||||||
# https://github.com/marketplace/actions/docker-setup-buildx
|
# https://github.com/marketplace/actions/docker-setup-buildx
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
id: buildx
|
id: buildx
|
||||||
uses: docker/setup-buildx-action@v3.6.1
|
uses: docker/setup-buildx-action@v3.7.1
|
||||||
|
|
||||||
# https://github.com/docker/login-action#docker-hub
|
# https://github.com/docker/login-action#docker-hub
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
@ -33,7 +33,7 @@ jobs:
|
|||||||
|
|
||||||
# https://github.com/docker/build-push-action#multi-platform-image
|
# https://github.com/docker/build-push-action#multi-platform-image
|
||||||
- name: Build and push API
|
- name: Build and push API
|
||||||
uses: docker/build-push-action@v6.7.0
|
uses: docker/build-push-action@v6.9.0
|
||||||
with:
|
with:
|
||||||
context: ./
|
context: ./
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
ARG DOTNET=8.0
|
ARG DOTNET=8.0
|
||||||
|
|
||||||
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/runtime:$DOTNET AS base
|
FROM --platform=$TARGETPLATFORM mcr.microsoft.com/dotnet/runtime:$DOTNET AS base
|
||||||
WORKDIR /publish
|
WORKDIR /publish
|
||||||
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
|
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
|
||||||
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
|
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
|
||||||
@ -22,7 +22,7 @@ RUN dotnet restore /src/Tranga.sln
|
|||||||
COPY . /src/
|
COPY . /src/
|
||||||
RUN dotnet publish -c Release --property:OutputPath=/publish -maxcpucount:1
|
RUN dotnet publish -c Release --property:OutputPath=/publish -maxcpucount:1
|
||||||
|
|
||||||
FROM --platform=$BUILDPLATFORM base AS runtime
|
FROM --platform=$TARGETPLATFORM base AS runtime
|
||||||
EXPOSE 6531
|
EXPOSE 6531
|
||||||
ARG UNAME=tranga
|
ARG UNAME=tranga
|
||||||
ARG UID=1000
|
ARG UID=1000
|
||||||
|
26
README.md
26
README.md
@ -51,11 +51,12 @@ Tranga can download Chapters and Metadata from "Scanlation" sites such as
|
|||||||
- [Bato.to](https://bato.to/v3x) (en)
|
- [Bato.to](https://bato.to/v3x) (en)
|
||||||
- [Manga4Life](https://manga4life.com) (en)
|
- [Manga4Life](https://manga4life.com) (en)
|
||||||
- [ManhuaPlus](https://manhuaplus.org/) (en)
|
- [ManhuaPlus](https://manhuaplus.org/) (en)
|
||||||
- [MangaHere](https://www.mangahere.cc/) (en) (Their covers suck)
|
- [MangaHere](https://www.mangahere.cc/) (en) (Their covers aren't scrapeable.)
|
||||||
- ❓ Open an [issue](https://github.com/C9Glax/tranga/issues)
|
- ❓ Open an [issue](https://github.com/C9Glax/tranga/issues/new?assignees=&labels=New+Connector&projects=&template=new_connector.yml&title=%5BNew+Connector%5D%3A+)
|
||||||
|
|
||||||
and trigger a library-scan with [Komga](https://komga.org/) and [Kavita](https://www.kavitareader.com/).
|
and trigger a library-scan with [Komga](https://komga.org/) and [Kavita](https://www.kavitareader.com/).
|
||||||
Notifications can be sent to your devices using [Gotify](https://gotify.net/) and [LunaSea](https://www.lunasea.app/).
|
Notifications can be sent to your devices using [Gotify](https://gotify.net/), [LunaSea](https://www.lunasea.app/) or [Ntfy](https://ntfy.sh/
|
||||||
|
).
|
||||||
|
|
||||||
### What this does and doesn't do
|
### What this does and doesn't do
|
||||||
|
|
||||||
@ -92,6 +93,16 @@ That is why I wanted to create my own project, in a language I understand, and t
|
|||||||
|
|
||||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||||
|
|
||||||
|
## Star History
|
||||||
|
|
||||||
|
<a href="https://star-history.com/#c9glax/tranga&Date">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=c9glax/tranga&type=Date&theme=dark" />
|
||||||
|
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=c9glax/tranga&type=Date" />
|
||||||
|
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=c9glax/tranga&type=Date" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
|
||||||
<!-- GETTING STARTED -->
|
<!-- GETTING STARTED -->
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
@ -107,14 +118,9 @@ access the folder.
|
|||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
#### To Build
|
#### To Build
|
||||||
[.NET-Core 7.0 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/7.0)
|
[.NET-Core 8.0 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)
|
||||||
#### To Run
|
#### To Run
|
||||||
[.NET-Core 7.0 Runtime](https://dotnet.microsoft.com/en-us/download/dotnet/7.0) scroll down a bit, should be on the right the second item.
|
[.NET-Core 8.0 Runtime](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) scroll down a bit, should be on the right the second item.
|
||||||
|
|
||||||
<!-- ROADMAP -->
|
|
||||||
## Roadmap
|
|
||||||
|
|
||||||
- [ ] ❓
|
|
||||||
|
|
||||||
See the [open issues](https://github.com/C9Glax/tranga/issues) for a full list of proposed features (and known issues).
|
See the [open issues](https://github.com/C9Glax/tranga/issues) for a full list of proposed features (and known issues).
|
||||||
|
|
||||||
|
@ -66,10 +66,10 @@ public abstract class GlobalBase
|
|||||||
Log(string.Format(fStr, replace));
|
Log(string.Format(fStr, replace));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void SendNotifications(string title, string text)
|
protected void SendNotifications(string title, string text, bool buffer = false)
|
||||||
{
|
{
|
||||||
foreach (NotificationConnector nc in notificationConnectors)
|
foreach (NotificationConnector nc in notificationConnectors)
|
||||||
nc.SendNotification(title, text);
|
nc.SendNotification(title, text, buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void AddNotificationConnector(NotificationConnector notificationConnector)
|
protected void AddNotificationConnector(NotificationConnector notificationConnector)
|
||||||
|
@ -37,7 +37,7 @@ public class DownloadChapter : Job
|
|||||||
if (success == HttpStatusCode.OK)
|
if (success == HttpStatusCode.OK)
|
||||||
{
|
{
|
||||||
UpdateLibraries();
|
UpdateLibraries();
|
||||||
SendNotifications("Chapter downloaded", $"{chapter.parentManga.sortName} - {chapter.chapterNumber}");
|
SendNotifications("Chapter downloaded", $"{chapter.parentManga.sortName} - {chapter.chapterNumber}", true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
downloadTask.Start();
|
downloadTask.Start();
|
||||||
|
@ -61,7 +61,7 @@ public class Kavita : LibraryConnector
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UpdateLibrary()
|
protected override void UpdateLibraryInternal()
|
||||||
{
|
{
|
||||||
Log("Updating libraries.");
|
Log("Updating libraries.");
|
||||||
foreach (KavitaLibrary lib in GetLibraries())
|
foreach (KavitaLibrary lib in GetLibraries())
|
||||||
|
@ -25,7 +25,7 @@ public class Komga : LibraryConnector
|
|||||||
return $"Komga {baseUrl}";
|
return $"Komga {baseUrl}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UpdateLibrary()
|
protected override void UpdateLibraryInternal()
|
||||||
{
|
{
|
||||||
Log("Updating libraries.");
|
Log("Updating libraries.");
|
||||||
foreach (KomgaLibrary lib in GetLibraries())
|
foreach (KomgaLibrary lib in GetLibraries())
|
||||||
|
@ -17,6 +17,9 @@ public abstract class LibraryConnector : GlobalBase
|
|||||||
public string baseUrl { get; }
|
public string baseUrl { get; }
|
||||||
// ReSharper disable once MemberCanBeProtected.Global
|
// ReSharper disable once MemberCanBeProtected.Global
|
||||||
public string auth { get; } //Base64 encoded, if you use your password everywhere, you have problems
|
public string auth { get; } //Base64 encoded, if you use your password everywhere, you have problems
|
||||||
|
private DateTime? _updateLibraryRequested = null;
|
||||||
|
private readonly Thread? _libraryBufferThread = null;
|
||||||
|
private const int NoChangeTimeout = 2, BiggestInterval = 20;
|
||||||
|
|
||||||
protected LibraryConnector(GlobalBase clone, string baseUrl, string auth, LibraryType libraryType) : base(clone)
|
protected LibraryConnector(GlobalBase clone, string baseUrl, string auth, LibraryType libraryType) : base(clone)
|
||||||
{
|
{
|
||||||
@ -28,8 +31,47 @@ public abstract class LibraryConnector : GlobalBase
|
|||||||
this.baseUrl = baseUrlRex.Match(baseUrl).Value;
|
this.baseUrl = baseUrlRex.Match(baseUrl).Value;
|
||||||
this.auth = auth;
|
this.auth = auth;
|
||||||
this.libraryType = libraryType;
|
this.libraryType = libraryType;
|
||||||
|
|
||||||
|
if (TrangaSettings.bufferLibraryUpdates)
|
||||||
|
{
|
||||||
|
_libraryBufferThread = new(CheckLibraryBuffer);
|
||||||
|
_libraryBufferThread.Start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public abstract void UpdateLibrary();
|
|
||||||
|
private void CheckLibraryBuffer()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (_updateLibraryRequested is not null && DateTime.Now.Subtract((DateTime)_updateLibraryRequested) > TimeSpan.FromMinutes(NoChangeTimeout)) //If no updates have been requested for NoChangeTimeout minutes, update library
|
||||||
|
{
|
||||||
|
UpdateLibraryInternal();
|
||||||
|
_updateLibraryRequested = null;
|
||||||
|
}
|
||||||
|
Thread.Sleep(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateLibrary()
|
||||||
|
{
|
||||||
|
_updateLibraryRequested ??= DateTime.Now;
|
||||||
|
if (!TrangaSettings.bufferLibraryUpdates)
|
||||||
|
{
|
||||||
|
UpdateLibraryInternal();
|
||||||
|
return;
|
||||||
|
}else if (_updateLibraryRequested is not null &&
|
||||||
|
DateTime.Now.Subtract((DateTime)_updateLibraryRequested) > TimeSpan.FromMinutes(BiggestInterval)) //If the last update has been more than BiggestInterval minutes ago, update library
|
||||||
|
{
|
||||||
|
UpdateLibraryInternal();
|
||||||
|
_updateLibraryRequested = null;
|
||||||
|
}
|
||||||
|
else if(_updateLibraryRequested is not null)
|
||||||
|
{
|
||||||
|
Log($"Buffering Library Updates (Updates in latest {((DateTime)_updateLibraryRequested).Add(TimeSpan.FromMinutes(BiggestInterval)).Subtract(DateTime.Now)} or {((DateTime)_updateLibraryRequested).Add(TimeSpan.FromMinutes(NoChangeTimeout)).Subtract(DateTime.Now)})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void UpdateLibraryInternal();
|
||||||
internal abstract bool Test();
|
internal abstract bool Test();
|
||||||
|
|
||||||
protected static class NetClient
|
protected static class NetClient
|
||||||
|
@ -15,7 +15,7 @@ public class MangaKatana : MangaConnector
|
|||||||
public override Manga[] GetManga(string publicationTitle = "")
|
public override Manga[] GetManga(string publicationTitle = "")
|
||||||
{
|
{
|
||||||
Log($"Searching Publications. Term=\"{publicationTitle}\"");
|
Log($"Searching Publications. Term=\"{publicationTitle}\"");
|
||||||
string sanitizedTitle = string.Join('_', Regex.Matches(publicationTitle, "[A-z]*").Where(m => m.Value.Length > 0)).ToLower();
|
string sanitizedTitle = string.Join("%20", Regex.Matches(publicationTitle, "[A-z]*").Where(m => m.Value.Length > 0)).ToLower();
|
||||||
string requestUrl = $"https://mangakatana.com/?search={sanitizedTitle}&search_by=book_name";
|
string requestUrl = $"https://mangakatana.com/?search={sanitizedTitle}&search_by=book_name";
|
||||||
RequestResult requestResult =
|
RequestResult requestResult =
|
||||||
downloadClient.MakeRequest(requestUrl, RequestType.Default);
|
downloadClient.MakeRequest(requestUrl, RequestType.Default);
|
||||||
|
@ -24,7 +24,7 @@ public class Gotify : NotificationConnector
|
|||||||
return $"Gotify {endpoint}";
|
return $"Gotify {endpoint}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void SendNotification(string title, string notificationText)
|
protected override void SendNotificationInternal(string title, string notificationText)
|
||||||
{
|
{
|
||||||
Log($"Sending notification: {title} - {notificationText}");
|
Log($"Sending notification: {title} - {notificationText}");
|
||||||
MessageData message = new(title, notificationText);
|
MessageData message = new(title, notificationText);
|
||||||
|
@ -20,7 +20,7 @@ public class LunaSea : NotificationConnector
|
|||||||
return $"LunaSea {id}";
|
return $"LunaSea {id}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void SendNotification(string title, string notificationText)
|
protected override void SendNotificationInternal(string title, string notificationText)
|
||||||
{
|
{
|
||||||
Log($"Sending notification: {title} - {notificationText}");
|
Log($"Sending notification: {title} - {notificationText}");
|
||||||
MessageData message = new(title, notificationText);
|
MessageData message = new(title, notificationText);
|
||||||
|
@ -3,14 +3,72 @@
|
|||||||
public abstract class NotificationConnector : GlobalBase
|
public abstract class NotificationConnector : GlobalBase
|
||||||
{
|
{
|
||||||
public readonly NotificationConnectorType notificationConnectorType;
|
public readonly NotificationConnectorType notificationConnectorType;
|
||||||
|
private DateTime? _notificationRequested = null;
|
||||||
|
private readonly Thread? _notificationBufferThread = null;
|
||||||
|
private const int NoChangeTimeout = 3, BiggestInterval = 30;
|
||||||
|
private List<KeyValuePair<string, string>> _notifications = new();
|
||||||
|
|
||||||
protected NotificationConnector(GlobalBase clone, NotificationConnectorType notificationConnectorType) : base(clone)
|
protected NotificationConnector(GlobalBase clone, NotificationConnectorType notificationConnectorType) : base(clone)
|
||||||
{
|
{
|
||||||
Log($"Creating notificationConnector {Enum.GetName(notificationConnectorType)}");
|
Log($"Creating notificationConnector {Enum.GetName(notificationConnectorType)}");
|
||||||
this.notificationConnectorType = notificationConnectorType;
|
this.notificationConnectorType = notificationConnectorType;
|
||||||
|
|
||||||
|
|
||||||
|
if (TrangaSettings.bufferLibraryUpdates)
|
||||||
|
{
|
||||||
|
_notificationBufferThread = new(CheckNotificationBuffer);
|
||||||
|
_notificationBufferThread.Start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckNotificationBuffer()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (_notificationRequested is not null && DateTime.Now.Subtract((DateTime)_notificationRequested) > TimeSpan.FromMinutes(NoChangeTimeout)) //If no updates have been requested for NoChangeTimeout minutes, update library
|
||||||
|
{
|
||||||
|
string[] uniqueTitles = _notifications.DistinctBy(n => n.Key).Select(n => n.Key).ToArray();
|
||||||
|
Log($"Notification Buffer sending! Notifications: {string.Join(", ", uniqueTitles)}");
|
||||||
|
foreach (string ut in uniqueTitles)
|
||||||
|
{
|
||||||
|
string[] texts = _notifications.Where(n => n.Key == ut).Select(n => n.Value).ToArray();
|
||||||
|
SendNotificationInternal($"{ut} ({texts.Length})", string.Join('\n', texts));
|
||||||
|
}
|
||||||
|
_notificationRequested = null;
|
||||||
|
_notifications.Clear();
|
||||||
|
}
|
||||||
|
Thread.Sleep(100);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum NotificationConnectorType : byte { Gotify = 0, LunaSea = 1, Ntfy = 2 }
|
public enum NotificationConnectorType : byte { Gotify = 0, LunaSea = 1, Ntfy = 2 }
|
||||||
|
|
||||||
public abstract void SendNotification(string title, string notificationText);
|
public void SendNotification(string title, string notificationText, bool buffer = false)
|
||||||
|
{
|
||||||
|
_notificationRequested ??= DateTime.Now;
|
||||||
|
if (!TrangaSettings.bufferNotifications || !buffer)
|
||||||
|
{
|
||||||
|
SendNotificationInternal(title, notificationText);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_notifications.Add(new(title, notificationText));
|
||||||
|
if (_notificationRequested is not null &&
|
||||||
|
DateTime.Now.Subtract((DateTime)_notificationRequested) > TimeSpan.FromMinutes(BiggestInterval)) //If the last update has been more than BiggestInterval minutes ago, update library
|
||||||
|
{
|
||||||
|
string[] uniqueTitles = _notifications.DistinctBy(n => n.Key).Select(n => n.Key).ToArray();
|
||||||
|
foreach (string ut in uniqueTitles)
|
||||||
|
{
|
||||||
|
string[] texts = _notifications.Where(n => n.Key == ut).Select(n => n.Value).ToArray();
|
||||||
|
SendNotificationInternal(ut, string.Join('\n', texts));
|
||||||
|
}
|
||||||
|
_notificationRequested = null;
|
||||||
|
_notifications.Clear();
|
||||||
|
}
|
||||||
|
else if(_notificationRequested is not null)
|
||||||
|
{
|
||||||
|
Log($"Buffering Notifications (Updates in latest {((DateTime)_notificationRequested).Add(TimeSpan.FromMinutes(BiggestInterval)).Subtract(DateTime.Now)} or {((DateTime)_notificationRequested).Add(TimeSpan.FromMinutes(NoChangeTimeout)).Subtract(DateTime.Now)})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void SendNotificationInternal(string title, string notificationText);
|
||||||
}
|
}
|
@ -54,7 +54,7 @@ public class Ntfy : NotificationConnector
|
|||||||
return $"Ntfy {endpoint} {topic}";
|
return $"Ntfy {endpoint} {topic}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void SendNotification(string title, string notificationText)
|
protected override void SendNotificationInternal(string title, string notificationText)
|
||||||
{
|
{
|
||||||
Log($"Sending notification: {title} - {notificationText}");
|
Log($"Sending notification: {title} - {notificationText}");
|
||||||
MessageData message = new(title, topic, notificationText);
|
MessageData message = new(title, topic, notificationText);
|
||||||
|
@ -34,6 +34,7 @@ public partial class Tranga : GlobalBase
|
|||||||
this._server = new Server(this);
|
this._server = new Server(this);
|
||||||
string[] emojis = { "(•‿•)", "(づ \u25d5‿\u25d5 )づ", "( \u02d8\u25bd\u02d8)っ\u2668", "=\uff3e\u25cf \u22cf \u25cf\uff3e=", "(ΦωΦ)", "(\u272a\u3268\u272a)", "( ノ・o・ )ノ", "(〜^\u2207^ )〜", "~(\u2267ω\u2266)~","૮ \u00b4• ﻌ \u00b4• ა", "(\u02c3ᆺ\u02c2)", "(=\ud83d\udf66 \u0f1d \ud83d\udf66=)"};
|
string[] emojis = { "(•‿•)", "(づ \u25d5‿\u25d5 )づ", "( \u02d8\u25bd\u02d8)っ\u2668", "=\uff3e\u25cf \u22cf \u25cf\uff3e=", "(ΦωΦ)", "(\u272a\u3268\u272a)", "( ノ・o・ )ノ", "(〜^\u2207^ )〜", "~(\u2267ω\u2266)~","૮ \u00b4• ﻌ \u00b4• ა", "(\u02c3ᆺ\u02c2)", "(=\ud83d\udf66 \u0f1d \ud83d\udf66=)"};
|
||||||
SendNotifications("Tranga Started", emojis[Random.Shared.Next(0,emojis.Length-1)]);
|
SendNotifications("Tranga Started", emojis[Random.Shared.Next(0,emojis.Length-1)]);
|
||||||
|
Log(TrangaSettings.AsJObject().ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public MangaConnector? GetConnector(string name)
|
public MangaConnector? GetConnector(string name)
|
||||||
|
@ -15,6 +15,8 @@ public static class TrangaSettings
|
|||||||
public static string workingDirectory { get; private set; } = Path.Join(RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "/usr/share" : Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "tranga-api");
|
public static string workingDirectory { get; private set; } = Path.Join(RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "/usr/share" : Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "tranga-api");
|
||||||
public static int apiPortNumber { get; private set; } = 6531;
|
public static int apiPortNumber { get; private set; } = 6531;
|
||||||
public static string userAgent { get; private set; } = DefaultUserAgent;
|
public static string userAgent { get; private set; } = DefaultUserAgent;
|
||||||
|
public static bool bufferLibraryUpdates { get; private set; } = false;
|
||||||
|
public static bool bufferNotifications { get; private set; } = false;
|
||||||
[JsonIgnore] public static string settingsFilePath => Path.Join(workingDirectory, "settings.json");
|
[JsonIgnore] public static string settingsFilePath => Path.Join(workingDirectory, "settings.json");
|
||||||
[JsonIgnore] public static string libraryConnectorsFilePath => Path.Join(workingDirectory, "libraryConnectors.json");
|
[JsonIgnore] public static string libraryConnectorsFilePath => Path.Join(workingDirectory, "libraryConnectors.json");
|
||||||
[JsonIgnore] public static string notificationConnectorsFilePath => Path.Join(workingDirectory, "notificationConnectors.json");
|
[JsonIgnore] public static string notificationConnectorsFilePath => Path.Join(workingDirectory, "notificationConnectors.json");
|
||||||
@ -46,15 +48,17 @@ public static class TrangaSettings
|
|||||||
ExportSettings();
|
ExportSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void CreateOrUpdate(string? downloadDirectory = null, string? pWorkingDirectory = null, int? pApiPortNumber = null, string? pUserAgent = null, bool? pAprilFoolsMode = null)
|
public static void CreateOrUpdate(string? downloadDirectory = null, string? pWorkingDirectory = null, int? pApiPortNumber = null, string? pUserAgent = null, bool? pAprilFoolsMode = null, bool? pBufferLibraryUpdates = null, bool? pBufferNotifications = null)
|
||||||
{
|
{
|
||||||
if(pWorkingDirectory is null && File.Exists(settingsFilePath))
|
if(pWorkingDirectory is null && File.Exists(settingsFilePath))
|
||||||
LoadFromWorkingDirectory(workingDirectory);
|
LoadFromWorkingDirectory(workingDirectory);
|
||||||
TrangaSettings.downloadLocation = downloadDirectory ?? TrangaSettings.downloadLocation;
|
downloadLocation = downloadDirectory ?? downloadLocation;
|
||||||
TrangaSettings.workingDirectory = pWorkingDirectory ?? TrangaSettings.workingDirectory;
|
workingDirectory = pWorkingDirectory ?? workingDirectory;
|
||||||
TrangaSettings.apiPortNumber = pApiPortNumber ?? TrangaSettings.apiPortNumber;
|
apiPortNumber = pApiPortNumber ?? apiPortNumber;
|
||||||
TrangaSettings.userAgent = pUserAgent ?? TrangaSettings.userAgent;
|
userAgent = pUserAgent ?? userAgent;
|
||||||
TrangaSettings.aprilFoolsMode = pAprilFoolsMode ?? TrangaSettings.aprilFoolsMode;
|
aprilFoolsMode = pAprilFoolsMode ?? aprilFoolsMode;
|
||||||
|
bufferLibraryUpdates = pBufferLibraryUpdates ?? bufferLibraryUpdates;
|
||||||
|
bufferNotifications = pBufferNotifications ?? bufferNotifications;
|
||||||
Directory.CreateDirectory(downloadLocation);
|
Directory.CreateDirectory(downloadLocation);
|
||||||
Directory.CreateDirectory(workingDirectory);
|
Directory.CreateDirectory(workingDirectory);
|
||||||
ExportSettings();
|
ExportSettings();
|
||||||
@ -90,7 +94,7 @@ public static class TrangaSettings
|
|||||||
|
|
||||||
public static void UpdateAprilFoolsMode(bool enabled)
|
public static void UpdateAprilFoolsMode(bool enabled)
|
||||||
{
|
{
|
||||||
TrangaSettings.aprilFoolsMode = enabled;
|
aprilFoolsMode = enabled;
|
||||||
ExportSettings();
|
ExportSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,10 +106,10 @@ public static class TrangaSettings
|
|||||||
else
|
else
|
||||||
Directory.CreateDirectory(newPath);
|
Directory.CreateDirectory(newPath);
|
||||||
|
|
||||||
if (moveFiles && Directory.Exists(TrangaSettings.downloadLocation))
|
if (moveFiles && Directory.Exists(downloadLocation))
|
||||||
Directory.Move(TrangaSettings.downloadLocation, newPath);
|
Directory.Move(downloadLocation, newPath);
|
||||||
|
|
||||||
TrangaSettings.downloadLocation = newPath;
|
downloadLocation = newPath;
|
||||||
ExportSettings();
|
ExportSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,26 +120,26 @@ public static class TrangaSettings
|
|||||||
GroupRead | GroupWrite | None | OtherRead | OtherWrite | UserRead | UserWrite);
|
GroupRead | GroupWrite | None | OtherRead | OtherWrite | UserRead | UserWrite);
|
||||||
else
|
else
|
||||||
Directory.CreateDirectory(newPath);
|
Directory.CreateDirectory(newPath);
|
||||||
Directory.Move(TrangaSettings.workingDirectory, newPath);
|
Directory.Move(workingDirectory, newPath);
|
||||||
TrangaSettings.workingDirectory = newPath;
|
workingDirectory = newPath;
|
||||||
ExportSettings();
|
ExportSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void UpdateUserAgent(string? customUserAgent)
|
public static void UpdateUserAgent(string? customUserAgent)
|
||||||
{
|
{
|
||||||
TrangaSettings.userAgent = customUserAgent ?? DefaultUserAgent;
|
userAgent = customUserAgent ?? DefaultUserAgent;
|
||||||
ExportSettings();
|
ExportSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void UpdateRateLimit(RequestType requestType, int newLimit)
|
public static void UpdateRateLimit(RequestType requestType, int newLimit)
|
||||||
{
|
{
|
||||||
TrangaSettings.requestLimits[requestType] = newLimit;
|
requestLimits[requestType] = newLimit;
|
||||||
ExportSettings();
|
ExportSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ResetRateLimits()
|
public static void ResetRateLimits()
|
||||||
{
|
{
|
||||||
TrangaSettings.requestLimits = DefaultRequestLimits;
|
requestLimits = DefaultRequestLimits;
|
||||||
ExportSettings();
|
ExportSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,13 +158,15 @@ public static class TrangaSettings
|
|||||||
public static JObject AsJObject()
|
public static JObject AsJObject()
|
||||||
{
|
{
|
||||||
JObject jobj = new JObject();
|
JObject jobj = new JObject();
|
||||||
jobj.Add("downloadLocation", JToken.FromObject(TrangaSettings.downloadLocation));
|
jobj.Add("downloadLocation", JToken.FromObject(downloadLocation));
|
||||||
jobj.Add("workingDirectory", JToken.FromObject(TrangaSettings.workingDirectory));
|
jobj.Add("workingDirectory", JToken.FromObject(workingDirectory));
|
||||||
jobj.Add("apiPortNumber", JToken.FromObject(TrangaSettings.apiPortNumber));
|
jobj.Add("apiPortNumber", JToken.FromObject(apiPortNumber));
|
||||||
jobj.Add("userAgent", JToken.FromObject(TrangaSettings.userAgent));
|
jobj.Add("userAgent", JToken.FromObject(userAgent));
|
||||||
jobj.Add("aprilFoolsMode", JToken.FromObject(TrangaSettings.aprilFoolsMode));
|
jobj.Add("aprilFoolsMode", JToken.FromObject(aprilFoolsMode));
|
||||||
jobj.Add("version", JToken.FromObject(TrangaSettings.version));
|
jobj.Add("version", JToken.FromObject(version));
|
||||||
jobj.Add("requestLimits", JToken.FromObject(TrangaSettings.requestLimits));
|
jobj.Add("requestLimits", JToken.FromObject(requestLimits));
|
||||||
|
jobj.Add("bufferLibraryUpdates", JToken.FromObject(bufferLibraryUpdates));
|
||||||
|
jobj.Add("bufferNotifications", JToken.FromObject(bufferNotifications));
|
||||||
return jobj;
|
return jobj;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,16 +176,20 @@ public static class TrangaSettings
|
|||||||
{
|
{
|
||||||
JObject jobj = JObject.Parse(serialized);
|
JObject jobj = JObject.Parse(serialized);
|
||||||
if (jobj.TryGetValue("downloadLocation", out JToken? dl))
|
if (jobj.TryGetValue("downloadLocation", out JToken? dl))
|
||||||
TrangaSettings.downloadLocation = dl.Value<string>()!;
|
downloadLocation = dl.Value<string>()!;
|
||||||
if (jobj.TryGetValue("workingDirectory", out JToken? wd))
|
if (jobj.TryGetValue("workingDirectory", out JToken? wd))
|
||||||
TrangaSettings.workingDirectory = wd.Value<string>()!;
|
workingDirectory = wd.Value<string>()!;
|
||||||
if (jobj.TryGetValue("apiPortNumber", out JToken? apn))
|
if (jobj.TryGetValue("apiPortNumber", out JToken? apn))
|
||||||
TrangaSettings.apiPortNumber = apn.Value<int>();
|
apiPortNumber = apn.Value<int>();
|
||||||
if (jobj.TryGetValue("userAgent", out JToken? ua))
|
if (jobj.TryGetValue("userAgent", out JToken? ua))
|
||||||
TrangaSettings.userAgent = ua.Value<string>()!;
|
userAgent = ua.Value<string>()!;
|
||||||
if (jobj.TryGetValue("aprilFoolsMode", out JToken? afm))
|
if (jobj.TryGetValue("aprilFoolsMode", out JToken? afm))
|
||||||
TrangaSettings.aprilFoolsMode = afm.Value<bool>()!;
|
aprilFoolsMode = afm.Value<bool>()!;
|
||||||
if (jobj.TryGetValue("requestLimits", out JToken? rl))
|
if (jobj.TryGetValue("requestLimits", out JToken? rl))
|
||||||
TrangaSettings.requestLimits = rl.ToObject<Dictionary<RequestType, int>>()!;
|
requestLimits = rl.ToObject<Dictionary<RequestType, int>>()!;
|
||||||
|
if (jobj.TryGetValue("bufferLibraryUpdates", out JToken? blu))
|
||||||
|
bufferLibraryUpdates = blu.Value<bool>()!;
|
||||||
|
if (jobj.TryGetValue("bufferNotifications", out JToken? bn))
|
||||||
|
bufferNotifications = bn.Value<bool>()!;
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user