diff --git a/.github/workflows/docker-image-cuttingedge.yml b/.github/workflows/docker-image-cuttingedge.yml index 5f79ff8..32277fd 100644 --- a/.github/workflows/docker-image-cuttingedge.yml +++ b/.github/workflows/docker-image-cuttingedge.yml @@ -11,26 +11,7 @@ jobs: runs-on: ubuntu-latest - steps: - - name: Clear cache - uses: actions/github-script@v6 - with: - script: | - console.log("About to clear") - const caches = await github.rest.actions.getActionsCacheList({ - owner: context.repo.owner, - repo: context.repo.repo, - }) - for (const cache of caches.data.actions_caches) { - console.log(cache) - github.rest.actions.deleteActionsCacheById({ - owner: context.repo.owner, - repo: context.repo.repo, - cache_id: cache.id, - }) - } - console.log("Clear completed") - + steps: - name: Checkout uses: actions/checkout@v4 diff --git a/Dockerfile b/Dockerfile index 1ffcca0..55a5f6c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # syntax=docker/dockerfile:1 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 ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium @@ -22,7 +22,7 @@ RUN dotnet restore /src/Tranga.sln COPY . /src/ RUN dotnet publish -c Release --property:OutputPath=/publish -maxcpucount:1 -FROM --platform=$BUILDPLATFORM base AS runtime +FROM --platform=$TARGETPLATFORM base AS runtime EXPOSE 6531 ARG UNAME=tranga ARG UID=1000 diff --git a/README.md b/README.md index 05a0d79..2aca578 100644 --- a/README.md +++ b/README.md @@ -51,11 +51,12 @@ Tranga can download Chapters and Metadata from "Scanlation" sites such as - [Bato.to](https://bato.to/v3x) (en) - [Manga4Life](https://manga4life.com) (en) - [ManhuaPlus](https://manhuaplus.org/) (en) -- [MangaHere](https://www.mangahere.cc/) (en) (Their covers suck) -- ❓ Open an [issue](https://github.com/C9Glax/tranga/issues) +- [MangaHere](https://www.mangahere.cc/) (en) (Their covers aren't scrapeable.) +- ❓ 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/). -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 @@ -92,6 +93,16 @@ That is why I wanted to create my own project, in a language I understand, and t

(back to top)

+## Star History + + + + + + Star History Chart + + + ## Getting Started @@ -107,14 +118,9 @@ access the folder. ### Prerequisites #### 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 -[.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. - - -## Roadmap - -- [ ] ❓ +[.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. See the [open issues](https://github.com/C9Glax/tranga/issues) for a full list of proposed features (and known issues). diff --git a/Tranga/Chapter.cs b/Tranga/Chapter.cs index 2dbf346..6ae48a2 100644 --- a/Tranga/Chapter.cs +++ b/Tranga/Chapter.cs @@ -84,24 +84,26 @@ public readonly struct Chapter : IComparable /// true if chapter is present internal bool CheckChapterIsDownloaded() { - if (!Directory.Exists(Path.Join(TrangaSettings.downloadLocation, parentManga.folderName))) + string mangaDirectory = Path.Join(TrangaSettings.downloadLocation, parentManga.folderName); + if (!Directory.Exists(mangaDirectory)) return false; - FileInfo[] archives = new DirectoryInfo(Path.Join(TrangaSettings.downloadLocation, parentManga.folderName)).GetFiles().Where(file => file.Name.Split('.')[^1] == "cbz").ToArray(); + FileInfo[] archives = new DirectoryInfo(mangaDirectory).GetFiles("*.cbz"); Regex volChRex = new(@"(?:Vol(?:ume)?\.([0-9]+)\D*)?Ch(?:apter)?\.([0-9]+(?:\.[0-9]+)*)"); Chapter t = this; - string thisPath = GetArchiveFilePath(); + string correctPath = GetArchiveFilePath(); FileInfo? archive = archives.FirstOrDefault(archive => { Match m = volChRex.Match(archive.Name); - string archiveVolNum = m.Groups[1].Success ? m.Groups[1].Value : "0"; - string archiveChNum = m.Groups[2].Value; - return archiveVolNum == t.volumeNumber && archiveChNum == t.chapterNumber || - archiveVolNum == "0" && archiveChNum == t.chapterNumber; + /*Uncommenting this section will only allow *Version without Volume number* -> *Version with Volume number* but not the other way + if (m.Groups[1].Success) + return m.Groups[1].Value == t.volumeNumber && m.Groups[2].Value == t.chapterNumber; + else*/ + return m.Groups[2].Value == t.chapterNumber; }); - if(archive is not null && thisPath != archive.FullName) - archive.MoveTo(thisPath, true); - return archive is not null; + if(archive is not null && archive.FullName != correctPath) + archive.MoveTo(correctPath, true); + return (archive is not null); } /// /// Creates full file path of chapter-archive diff --git a/Tranga/GlobalBase.cs b/Tranga/GlobalBase.cs index 49a0d79..5466cd1 100644 --- a/Tranga/GlobalBase.cs +++ b/Tranga/GlobalBase.cs @@ -66,10 +66,10 @@ public abstract class GlobalBase 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) - nc.SendNotification(title, text); + nc.SendNotification(title, text, buffer); } protected void AddNotificationConnector(NotificationConnector notificationConnector) diff --git a/Tranga/Jobs/DownloadChapter.cs b/Tranga/Jobs/DownloadChapter.cs index 434736e..4e8456b 100644 --- a/Tranga/Jobs/DownloadChapter.cs +++ b/Tranga/Jobs/DownloadChapter.cs @@ -37,7 +37,7 @@ public class DownloadChapter : Job if (success == HttpStatusCode.OK) { UpdateLibraries(); - SendNotifications("Chapter downloaded", $"{chapter.parentManga.sortName} - {chapter.chapterNumber}"); + SendNotifications("Chapter downloaded", $"{chapter.parentManga.sortName} - {chapter.chapterNumber}", true); } }); downloadTask.Start(); diff --git a/Tranga/LibraryConnectors/Kavita.cs b/Tranga/LibraryConnectors/Kavita.cs index a9d2fa9..62150a0 100644 --- a/Tranga/LibraryConnectors/Kavita.cs +++ b/Tranga/LibraryConnectors/Kavita.cs @@ -61,7 +61,7 @@ public class Kavita : LibraryConnector return ""; } - public override void UpdateLibrary() + protected override void UpdateLibraryInternal() { Log("Updating libraries."); foreach (KavitaLibrary lib in GetLibraries()) diff --git a/Tranga/LibraryConnectors/Komga.cs b/Tranga/LibraryConnectors/Komga.cs index 557d934..2d6b2a1 100644 --- a/Tranga/LibraryConnectors/Komga.cs +++ b/Tranga/LibraryConnectors/Komga.cs @@ -25,7 +25,7 @@ public class Komga : LibraryConnector return $"Komga {baseUrl}"; } - public override void UpdateLibrary() + protected override void UpdateLibraryInternal() { Log("Updating libraries."); foreach (KomgaLibrary lib in GetLibraries()) diff --git a/Tranga/LibraryConnectors/LibraryConnector.cs b/Tranga/LibraryConnectors/LibraryConnector.cs index 80dd5b2..99320b0 100644 --- a/Tranga/LibraryConnectors/LibraryConnector.cs +++ b/Tranga/LibraryConnectors/LibraryConnector.cs @@ -17,6 +17,9 @@ public abstract class LibraryConnector : GlobalBase public string baseUrl { get; } // ReSharper disable once MemberCanBeProtected.Global 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) { @@ -28,8 +31,47 @@ public abstract class LibraryConnector : GlobalBase this.baseUrl = baseUrlRex.Match(baseUrl).Value; this.auth = auth; 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(); protected static class NetClient diff --git a/Tranga/MangaConnectors/MangaKatana.cs b/Tranga/MangaConnectors/MangaKatana.cs index 32bc78d..bc77637 100644 --- a/Tranga/MangaConnectors/MangaKatana.cs +++ b/Tranga/MangaConnectors/MangaKatana.cs @@ -15,7 +15,7 @@ public class MangaKatana : MangaConnector public override Manga[] GetManga(string 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"; RequestResult requestResult = downloadClient.MakeRequest(requestUrl, RequestType.Default); diff --git a/Tranga/NotificationConnectors/Gotify.cs b/Tranga/NotificationConnectors/Gotify.cs index cbe3d9c..3dc7f28 100644 --- a/Tranga/NotificationConnectors/Gotify.cs +++ b/Tranga/NotificationConnectors/Gotify.cs @@ -24,7 +24,7 @@ public class Gotify : NotificationConnector return $"Gotify {endpoint}"; } - public override void SendNotification(string title, string notificationText) + protected override void SendNotificationInternal(string title, string notificationText) { Log($"Sending notification: {title} - {notificationText}"); MessageData message = new(title, notificationText); diff --git a/Tranga/NotificationConnectors/LunaSea.cs b/Tranga/NotificationConnectors/LunaSea.cs index 41c220a..e124192 100644 --- a/Tranga/NotificationConnectors/LunaSea.cs +++ b/Tranga/NotificationConnectors/LunaSea.cs @@ -20,7 +20,7 @@ public class LunaSea : NotificationConnector return $"LunaSea {id}"; } - public override void SendNotification(string title, string notificationText) + protected override void SendNotificationInternal(string title, string notificationText) { Log($"Sending notification: {title} - {notificationText}"); MessageData message = new(title, notificationText); diff --git a/Tranga/NotificationConnectors/NotificationConnector.cs b/Tranga/NotificationConnectors/NotificationConnector.cs index 51b9469..f64da82 100644 --- a/Tranga/NotificationConnectors/NotificationConnector.cs +++ b/Tranga/NotificationConnectors/NotificationConnector.cs @@ -3,14 +3,72 @@ public abstract class NotificationConnector : GlobalBase { public readonly NotificationConnectorType notificationConnectorType; + private DateTime? _notificationRequested = null; + private readonly Thread? _notificationBufferThread = null; + private const int NoChangeTimeout = 3, BiggestInterval = 30; + private List> _notifications = new(); protected NotificationConnector(GlobalBase clone, NotificationConnectorType notificationConnectorType) : base(clone) { Log($"Creating notificationConnector {Enum.GetName(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 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)})"); + } + } - public abstract void SendNotification(string title, string notificationText); + protected abstract void SendNotificationInternal(string title, string notificationText); } \ No newline at end of file diff --git a/Tranga/NotificationConnectors/Ntfy.cs b/Tranga/NotificationConnectors/Ntfy.cs index f19fe34..afad9a6 100644 --- a/Tranga/NotificationConnectors/Ntfy.cs +++ b/Tranga/NotificationConnectors/Ntfy.cs @@ -54,7 +54,7 @@ public class Ntfy : NotificationConnector 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}"); MessageData message = new(title, topic, notificationText); diff --git a/Tranga/Tranga.cs b/Tranga/Tranga.cs index 3c9a209..2762a94 100644 --- a/Tranga/Tranga.cs +++ b/Tranga/Tranga.cs @@ -34,6 +34,7 @@ public partial class Tranga : GlobalBase 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=)"}; SendNotifications("Tranga Started", emojis[Random.Shared.Next(0,emojis.Length-1)]); + Log(TrangaSettings.AsJObject().ToString()); } public MangaConnector? GetConnector(string name) diff --git a/Tranga/TrangaSettings.cs b/Tranga/TrangaSettings.cs index b945fe5..ccfa997 100644 --- a/Tranga/TrangaSettings.cs +++ b/Tranga/TrangaSettings.cs @@ -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 int apiPortNumber { get; private set; } = 6531; 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 libraryConnectorsFilePath => Path.Join(workingDirectory, "libraryConnectors.json"); [JsonIgnore] public static string notificationConnectorsFilePath => Path.Join(workingDirectory, "notificationConnectors.json"); @@ -46,15 +48,17 @@ public static class TrangaSettings 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)) LoadFromWorkingDirectory(workingDirectory); - TrangaSettings.downloadLocation = downloadDirectory ?? TrangaSettings.downloadLocation; - TrangaSettings.workingDirectory = pWorkingDirectory ?? TrangaSettings.workingDirectory; - TrangaSettings.apiPortNumber = pApiPortNumber ?? TrangaSettings.apiPortNumber; - TrangaSettings.userAgent = pUserAgent ?? TrangaSettings.userAgent; - TrangaSettings.aprilFoolsMode = pAprilFoolsMode ?? TrangaSettings.aprilFoolsMode; + downloadLocation = downloadDirectory ?? downloadLocation; + workingDirectory = pWorkingDirectory ?? workingDirectory; + apiPortNumber = pApiPortNumber ?? apiPortNumber; + userAgent = pUserAgent ?? userAgent; + aprilFoolsMode = pAprilFoolsMode ?? aprilFoolsMode; + bufferLibraryUpdates = pBufferLibraryUpdates ?? bufferLibraryUpdates; + bufferNotifications = pBufferNotifications ?? bufferNotifications; Directory.CreateDirectory(downloadLocation); Directory.CreateDirectory(workingDirectory); ExportSettings(); @@ -90,7 +94,7 @@ public static class TrangaSettings public static void UpdateAprilFoolsMode(bool enabled) { - TrangaSettings.aprilFoolsMode = enabled; + aprilFoolsMode = enabled; ExportSettings(); } @@ -102,10 +106,10 @@ public static class TrangaSettings else Directory.CreateDirectory(newPath); - if (moveFiles && Directory.Exists(TrangaSettings.downloadLocation)) - Directory.Move(TrangaSettings.downloadLocation, newPath); + if (moveFiles && Directory.Exists(downloadLocation)) + Directory.Move(downloadLocation, newPath); - TrangaSettings.downloadLocation = newPath; + downloadLocation = newPath; ExportSettings(); } @@ -116,26 +120,26 @@ public static class TrangaSettings GroupRead | GroupWrite | None | OtherRead | OtherWrite | UserRead | UserWrite); else Directory.CreateDirectory(newPath); - Directory.Move(TrangaSettings.workingDirectory, newPath); - TrangaSettings.workingDirectory = newPath; + Directory.Move(workingDirectory, newPath); + workingDirectory = newPath; ExportSettings(); } public static void UpdateUserAgent(string? customUserAgent) { - TrangaSettings.userAgent = customUserAgent ?? DefaultUserAgent; + userAgent = customUserAgent ?? DefaultUserAgent; ExportSettings(); } public static void UpdateRateLimit(RequestType requestType, int newLimit) { - TrangaSettings.requestLimits[requestType] = newLimit; + requestLimits[requestType] = newLimit; ExportSettings(); } public static void ResetRateLimits() { - TrangaSettings.requestLimits = DefaultRequestLimits; + requestLimits = DefaultRequestLimits; ExportSettings(); } @@ -154,13 +158,15 @@ public static class TrangaSettings public static JObject AsJObject() { JObject jobj = new JObject(); - jobj.Add("downloadLocation", JToken.FromObject(TrangaSettings.downloadLocation)); - jobj.Add("workingDirectory", JToken.FromObject(TrangaSettings.workingDirectory)); - jobj.Add("apiPortNumber", JToken.FromObject(TrangaSettings.apiPortNumber)); - jobj.Add("userAgent", JToken.FromObject(TrangaSettings.userAgent)); - jobj.Add("aprilFoolsMode", JToken.FromObject(TrangaSettings.aprilFoolsMode)); - jobj.Add("version", JToken.FromObject(TrangaSettings.version)); - jobj.Add("requestLimits", JToken.FromObject(TrangaSettings.requestLimits)); + jobj.Add("downloadLocation", JToken.FromObject(downloadLocation)); + jobj.Add("workingDirectory", JToken.FromObject(workingDirectory)); + jobj.Add("apiPortNumber", JToken.FromObject(apiPortNumber)); + jobj.Add("userAgent", JToken.FromObject(userAgent)); + jobj.Add("aprilFoolsMode", JToken.FromObject(aprilFoolsMode)); + jobj.Add("version", JToken.FromObject(version)); + jobj.Add("requestLimits", JToken.FromObject(requestLimits)); + jobj.Add("bufferLibraryUpdates", JToken.FromObject(bufferLibraryUpdates)); + jobj.Add("bufferNotifications", JToken.FromObject(bufferNotifications)); return jobj; } @@ -170,16 +176,20 @@ public static class TrangaSettings { JObject jobj = JObject.Parse(serialized); if (jobj.TryGetValue("downloadLocation", out JToken? dl)) - TrangaSettings.downloadLocation = dl.Value()!; + downloadLocation = dl.Value()!; if (jobj.TryGetValue("workingDirectory", out JToken? wd)) - TrangaSettings.workingDirectory = wd.Value()!; + workingDirectory = wd.Value()!; if (jobj.TryGetValue("apiPortNumber", out JToken? apn)) - TrangaSettings.apiPortNumber = apn.Value(); + apiPortNumber = apn.Value(); if (jobj.TryGetValue("userAgent", out JToken? ua)) - TrangaSettings.userAgent = ua.Value()!; + userAgent = ua.Value()!; if (jobj.TryGetValue("aprilFoolsMode", out JToken? afm)) - TrangaSettings.aprilFoolsMode = afm.Value()!; + aprilFoolsMode = afm.Value()!; if (jobj.TryGetValue("requestLimits", out JToken? rl)) - TrangaSettings.requestLimits = rl.ToObject>()!; + requestLimits = rl.ToObject>()!; + if (jobj.TryGetValue("bufferLibraryUpdates", out JToken? blu)) + bufferLibraryUpdates = blu.Value()!; + if (jobj.TryGetValue("bufferNotifications", out JToken? bn)) + bufferNotifications = bn.Value()!; } } \ No newline at end of file