2
0

Compare commits

..

100 Commits

Author SHA1 Message Date
ba029b71f5 Merge branch 'refs/heads/manhuaplus' into cuttingedge-merge-ServerV2 2024-08-08 19:00:20 +02:00
082802ddbe Merge branch 'refs/heads/master' into cuttingedge-merge-ServerV2 2024-08-08 19:00:09 +02:00
d5f1df0400
Merge pull request #216 from C9Glax/dependabot/github_actions/docker/build-push-action-6.6.1
Bump docker/build-push-action from 6.5.0 to 6.6.1
2024-08-08 18:59:46 +02:00
d00881e611 Add Connector ManhuaPlus
https://github.com/C9Glax/tranga/issues/213
2024-08-08 18:58:40 +02:00
dependabot[bot]
72bc7ec07b
Bump docker/build-push-action from 6.5.0 to 6.6.1
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.5.0 to 6.6.1.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.5.0...v6.6.1)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-08 05:08:32 +00:00
926c0d5833 fix #214 foldernames 2024-07-31 19:24:59 +02:00
3b6417eff2 Fix #214 HTML encoded Characters 2024-07-31 17:48:15 +02:00
1991862a42 Merge remote-tracking branch 'refs/remotes/github/master' into cuttingedge-merge-ServerV2 2024-07-31 17:44:22 +02:00
40e4d5c203
Merge pull request #215 from C9Glax/dependabot/github_actions/docker/setup-buildx-action-3.6.1
Bump docker/setup-buildx-action from 3.4.0 to 3.6.1
2024-07-31 17:44:05 +02:00
49e9731184
Merge pull request #212 from C9Glax/dependabot/github_actions/docker/setup-qemu-action-3.2.0
Bump docker/setup-qemu-action from 3.1.0 to 3.2.0
2024-07-31 17:43:57 +02:00
a4e85f254f
Merge pull request #210 from C9Glax/dependabot/github_actions/docker/build-push-action-6.5.0
Bump docker/build-push-action from 6.3.0 to 6.5.0
2024-07-31 17:43:48 +02:00
dependabot[bot]
4f47aeadcf
Bump docker/setup-buildx-action from 3.4.0 to 3.6.1
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3.4.0 to 3.6.1.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v3.4.0...v3.6.1)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-30 05:45:04 +00:00
dependabot[bot]
e0c1356fea
Bump docker/setup-qemu-action from 3.1.0 to 3.2.0
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 3.1.0 to 3.2.0.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v3.1.0...v3.2.0)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-23 06:02:31 +00:00
dependabot[bot]
0d9b3d2499
Bump docker/build-push-action from 6.3.0 to 6.5.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.3.0 to 6.5.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.3.0...v6.5.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-23 06:02:27 +00:00
b8c28e6d21
Merge pull request #207 from C9Glax/master
Update active dev branch with changes to master
2024-07-11 15:45:33 +02:00
9ea5e436fe
Merge pull request #204 from C9Glax/dependabot/github_actions/docker/setup-buildx-action-3.4.0
Bump docker/setup-buildx-action from 3.3.0 to 3.4.0
2024-07-11 15:44:39 +02:00
b4c310638a
Merge pull request #205 from C9Glax/dependabot/github_actions/docker/build-push-action-6.3.0
Bump docker/build-push-action from 6.1.0 to 6.3.0
2024-07-11 15:44:17 +02:00
159341ff3c
Merge pull request #206 from C9Glax/dependabot/github_actions/docker/setup-qemu-action-3.1.0
Bump docker/setup-qemu-action from 2.2.0 to 3.1.0
2024-07-11 15:43:58 +02:00
dependabot[bot]
29338b9b17
Bump docker/setup-qemu-action from 2.2.0 to 3.1.0
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 2.2.0 to 3.1.0.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v2.2.0...v3.1.0)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-10 05:46:20 +00:00
dependabot[bot]
0eda8913b0
Bump docker/build-push-action from 6.1.0 to 6.3.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.1.0 to 6.3.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.1.0...v6.3.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-10 05:46:17 +00:00
dependabot[bot]
5ca50630e4
Bump docker/setup-buildx-action from 3.3.0 to 3.4.0
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3.3.0 to 3.4.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v3.3.0...v3.4.0)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-10 05:46:15 +00:00
d0bfb262bf Merge remote-tracking branch 'refs/remotes/github/master' into cuttingedge-merge-ServerV2 2024-07-09 11:22:05 +02:00
4f14f15ade
Merge pull request #200 from C9Glax/dependabot/github_actions/docker/setup-qemu-action-3.1.0
Bump docker/setup-qemu-action from 2.2.0 to 3.1.0
2024-07-09 11:20:29 +02:00
d89a24fd11
Merge pull request #201 from C9Glax/dependabot/github_actions/docker/build-push-action-6.3.0
Bump docker/build-push-action from 6.1.0 to 6.3.0
2024-07-09 11:20:14 +02:00
a5859e3c82
Merge pull request #203 from C9Glax/dependabot/github_actions/docker/setup-buildx-action-3.4.0
Bump docker/setup-buildx-action from 3.3.0 to 3.4.0
2024-07-09 11:19:55 +02:00
33e5d65785 fix Kavita GetLibraries 2024-07-09 11:17:50 +02:00
dependabot[bot]
d60ed77dbe
Bump docker/setup-buildx-action from 3.3.0 to 3.4.0
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3.3.0 to 3.4.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v3.3.0...v3.4.0)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-05 05:11:09 +00:00
dependabot[bot]
e15c6816b5
Bump docker/build-push-action from 6.1.0 to 6.3.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.1.0 to 6.3.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.1.0...v6.3.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-04 05:56:47 +00:00
dependabot[bot]
4a4fe4b40d
Bump docker/setup-qemu-action from 2.2.0 to 3.1.0
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 2.2.0 to 3.1.0.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v2.2.0...v3.1.0)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-04 05:56:42 +00:00
4881789970 Merge branch 'refs/heads/cuttingedge' 2024-06-29 22:50:07 +02:00
be1e6fe988 Merge branch 'refs/heads/cuttingedge-merge-ServerV2' into cuttingedge 2024-06-29 22:49:56 +02:00
f61e51e506 Fix crash when moving files, now overwrites. 2024-06-29 22:49:39 +02:00
eba511749b
Merge pull request #199 from C9Glax/cuttingedge
Merge cuttingedge to latest.
2024-06-29 19:49:06 +02:00
de4c57a0cd Merge branch 'refs/heads/cuttingedge-merge-ServerV2' into cuttingedge 2024-06-29 19:37:09 +02:00
e368c3c98a Fix https://github.com/C9Glax/tranga/issues/193
Mangaworld Volume and Chapter number Parsing.
2024-06-29 19:37:02 +02:00
d17ca1d97a
Merge pull request #197 from C9Glax/master
Merge Github Actions
2024-06-29 19:22:59 +02:00
e9376e3782
Merge pull request #196 from C9Glax/master
Merge Github Actions
2024-06-29 19:21:41 +02:00
7c217a7e33 Merge branch 'refs/heads/cuttingedge-merge-ServerV2' into cuttingedge 2024-06-29 19:20:16 +02:00
a437fcbca1 Possible fix https://github.com/C9Glax/tranga/issues/185
Mangaworld publication id had invalid path characters.
2024-06-29 19:20:04 +02:00
1dcfecd66f Create CoverImageCache when saving coverimages. 2024-06-29 19:14:37 +02:00
6db4646336 Move/rename archives if volume number gets updated. 2024-06-29 19:11:18 +02:00
8a6298e3fd
Merge pull request #157 from C9Glax/dependabot/github_actions/docker/setup-buildx-action-3.3.0
Bump docker/setup-buildx-action from 3.1.0 to 3.3.0
2024-06-27 00:08:31 +02:00
194705c124
Merge pull request #194 from C9Glax/dependabot/github_actions/docker/build-push-action-6.1.0
Bump docker/build-push-action from 5.3.0 to 6.1.0
2024-06-27 00:06:28 +02:00
dependabot[bot]
f4d5969003
Bump docker/build-push-action from 5.3.0 to 6.1.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 5.3.0 to 6.1.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v5.3.0...v6.1.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-24 05:58:28 +00:00
9d92069a4b #187 NTFY JsonConverter 2024-06-15 21:39:53 +02:00
5614729eab #187 Server v1 NTFY username password 2024-06-15 21:33:42 +02:00
d52ec8d36f NTFY username and password usage instead of auth. 2024-06-15 21:24:28 +02:00
37dfb4df02 Merge branch 'refs/heads/cuttingedge-merge-ServerV2' into cuttingedge 2024-06-02 01:05:20 +02:00
42feea3ad5 Fix covers returning wrong fileLocation if cover already exists. 2024-06-02 01:05:08 +02:00
bbc750d731 Merge branch 'refs/heads/cuttingedge-merge-ServerV2' into cuttingedge 2024-06-02 00:23:23 +02:00
08dd01942f #183 Fix NTFY not exporting topic to notificationConnectors.json 2024-06-02 00:23:16 +02:00
351144e763 Merge branch 'refs/heads/cuttingedge-merge-ServerV2' into cuttingedge 2024-06-02 00:09:18 +02:00
aea4c0c61b Add GlaxArguments to fetch Runtime-Args 2024-06-02 00:09:03 +02:00
7b9e935db7 Commented optional second level only domains for cover-image-names 2024-06-01 22:10:09 +02:00
048b165d76 Merge branch 'refs/heads/cuttingedge-merge-ServerV2' into cuttingedge 2024-06-01 22:09:18 +02:00
ebe3012c69 NTFY check endpoint URI and add optional custom topic #183 2024-06-01 22:09:08 +02:00
a5dbed9525 Merge branch 'refs/heads/cuttingedge-merge-ServerV2' into cuttingedge 2024-05-26 23:04:27 +02:00
811ddd903f fix missing minus-sign from domain namers in coverimages 2024-05-26 23:04:16 +02:00
f948809bcd Merge branch 'refs/heads/cuttingedge-merge-ServerV2' into cuttingedge 2024-05-26 22:51:59 +02:00
7ceb9cd4cb #182 Changed filename to instead of remote filename have the format server-internalId.fileFormat 2024-05-26 22:51:46 +02:00
57f1e037ef Corrected check for if cover exists 2024-05-26 22:45:39 +02:00
6ca8d58e43 Merge branch 'refs/heads/cuttingedge-merge-ServerV2' into cuttingedge 2024-05-26 18:46:58 +02:00
e3211b95e2 #182 Remove covers that have no asssociated Manga 2024-05-26 18:46:40 +02:00
b5e9e03f64 Merge branch 'refs/heads/cuttingedge-merge-ServerV2' into cuttingedge 2024-05-26 18:34:57 +02:00
98bd8a983b Possible Fix #182 2024-05-26 18:34:45 +02:00
f4996659ef Fix loading file results in "null"-job and crashes. 2024-05-26 18:23:16 +02:00
e05684d5d1 Fix loading file results in "null"-job and crashes. 2024-05-26 18:22:51 +02:00
4a7d23c0d9 Merge branch 'refs/heads/cuttingedge-merge-ServerV2' into cuttingedge 2024-05-26 18:10:45 +02:00
1d44b6d9c6 Log added Jobs during Startup 2024-05-26 18:10:29 +02:00
811a183af2 Merge branch 'refs/heads/cuttingedge-merge-ServerV2' into cuttingedge 2024-04-27 19:09:22 +02:00
fb0755eb89 Use NeedlemanWunsch for string comparison on Mangasee.cs
Resolves #132
#167
2024-04-27 19:09:12 +02:00
2e8b896f3b Fix #178 wrong check on parsing variable aprilfoolsmode 2024-04-27 17:53:08 +02:00
4692cc297a Fix MangaDex linksNode is null 2024-04-26 00:48:55 +02:00
3d855020eb Export job files indented. 2024-04-25 21:32:48 +02:00
c6d0168d2f Fix #174 auth not being written to file for ntfy. 2024-04-25 21:29:05 +02:00
d52213002e Delete old jobfiles. 2024-04-25 21:24:29 +02:00
ec9290f41f Merge branch 'refs/heads/cuttingedge-merge-ServerV2' into cuttingedge
# Conflicts:
#	Tranga/Jobs/UpdateMetadata.cs
2024-04-25 21:10:42 +02:00
6b91796e5a Update manga in DownloadNewChapters Jobs 2024-04-25 21:10:26 +02:00
9f9ea569d5 fix bug Manga.WithMetadata coverfilenameincache not being replaced. 2024-04-25 21:03:57 +02:00
4bd1150a0e fix bug Manga.WithMetadata coverfilenameincache not being replaced. 2024-04-25 21:03:44 +02:00
8b62e2c467 Possible fix #175 Export jobs when Manga-Metadata is updated. 2024-04-25 20:57:59 +02:00
7ec262a2e4 Possible fix #175 Export jobs when Manga-Metadata is updated. 2024-04-25 20:57:46 +02:00
d32d5976ee Merge branch 'refs/heads/cuttingedge-merge-ServerV2' into cuttingedge 2024-04-25 20:46:32 +02:00
58cff6513a Possible fix #175 2024-04-25 20:46:26 +02:00
783f229a6a Add LibraryConnector.Test to see if requests can be made to endpoint. 2024-04-23 00:58:33 +02:00
aaf06da8e1 Merge branch 'refs/heads/cuttingedge-merge-ServerV2' into cuttingedge 2024-04-23 00:20:50 +02:00
51a26a3cba Fix https://github.com/C9Glax/tranga/issues/143
ImageCache could never find files, because they were not in the expected location.
2024-04-23 00:20:34 +02:00
762da4c859 Make cachedPublications private with getter-setter 2024-04-22 22:43:42 +02:00
daba940b45 Make cachePublications a dictionary with internalId as key. 2024-04-22 22:38:23 +02:00
79e61a62c7 Export Jobfiles after execution, update metadata in jobfiles 2024-04-22 22:29:22 +02:00
06fe98323a Fix crashing when comparing old Manga (missing websiteUrl) 2024-04-22 22:09:43 +02:00
5f820c53f5 Update websiteUrl on metadata-refresh https://github.com/C9Glax/tranga-website/issues/60 2024-04-22 22:03:09 +02:00
c69f1f6569 Addresses #170 Manganato authors and genres include "\r\n" 2024-04-22 04:45:49 +02:00
5bdbd9e2e4 Hack to resolve #60 Website-URL.
Field will have same name, just acquisition will be better.
2024-04-22 02:25:39 +02:00
f729c44f88 Merge branch 'refs/heads/master' into cuttingedge 2024-04-20 18:49:19 +02:00
df2fc4a036 Remove README CLI reference 2024-04-20 18:39:49 +02:00
0ab2ae03ce unionby isntead of concat 2024-04-19 03:07:46 +02:00
95236daf41 Check if tags and authors are the same on Manga equals.
UpdateManga performs union/concat operation on alttitles, tags and authors
2024-04-19 03:00:31 +02:00
294ce01bc3 Set Manga.releaseStatus to new releaseStatus.
Fix #119
2024-04-19 02:37:17 +02:00
dependabot[bot]
af4229920d
Bump docker/setup-buildx-action from 3.1.0 to 3.3.0
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3.1.0 to 3.3.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v3.1.0...v3.3.0)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-09 05:32:25 +00:00
30 changed files with 526 additions and 316 deletions

View File

@ -15,12 +15,12 @@ jobs:
# https://github.com/docker/setup-qemu-action#usage # https://github.com/docker/setup-qemu-action#usage
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2.2.0 uses: docker/setup-qemu-action@v3.2.0
# 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.3.0 uses: docker/setup-buildx-action@v3.6.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
@ -31,7 +31,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 base - name: Build and push base
uses: docker/build-push-action@v5.3.0 uses: docker/build-push-action@v6.6.1
with: with:
context: ./ context: ./
file: ./Dockerfile-base file: ./Dockerfile-base

View File

@ -17,12 +17,12 @@ jobs:
# https://github.com/docker/setup-qemu-action#usage # https://github.com/docker/setup-qemu-action#usage
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2.2.0 uses: docker/setup-qemu-action@v3.2.0
# 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.3.0 uses: docker/setup-buildx-action@v3.6.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@v5.3.0 uses: docker/build-push-action@v6.6.1
with: with:
context: ./ context: ./
file: ./Dockerfile file: ./Dockerfile

View File

@ -17,12 +17,12 @@ jobs:
# https://github.com/docker/setup-qemu-action#usage # https://github.com/docker/setup-qemu-action#usage
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2.2.0 uses: docker/setup-qemu-action@v3.2.0
# 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.3.0 uses: docker/setup-buildx-action@v3.6.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@v5.3.0 uses: docker/build-push-action@v6.6.1
with: with:
context: ./ context: ./
file: ./Dockerfile file: ./Dockerfile

View File

@ -17,12 +17,12 @@ jobs:
# https://github.com/docker/setup-qemu-action#usage # https://github.com/docker/setup-qemu-action#usage
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2.2.0 uses: docker/setup-qemu-action@v3.2.0
# 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.3.0 uses: docker/setup-buildx-action@v3.6.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@v5.3.0 uses: docker/build-push-action@v6.6.1
with: with:
context: ./ context: ./
file: ./Dockerfile file: ./Dockerfile

View File

@ -17,12 +17,12 @@ jobs:
# https://github.com/docker/setup-qemu-action#usage # https://github.com/docker/setup-qemu-action#usage
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2.2.0 uses: docker/setup-qemu-action@v3.2.0
# 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.3.0 uses: docker/setup-buildx-action@v3.6.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@v5.3.0 uses: docker/build-push-action@v6.6.1
with: with:
context: ./ context: ./
file: ./Dockerfile file: ./Dockerfile

View File

@ -59,7 +59,6 @@ Notifications can be sent to your devices using [Gotify](https://gotify.net/) an
Tranga (this git-repo) will open a port (standard 6531) and listen for requests to add Jobs to Monitor and/or download specific Manga. Tranga (this git-repo) will open a port (standard 6531) and listen for requests to add Jobs to Monitor and/or download specific Manga.
The configuration is all done through HTTP-Requests. The configuration is all done through HTTP-Requests.
The frontend in this repo is **CLI**-based.
_**For a web-frontend use [tranga-website](https://github.com/C9Glax/tranga-website).**_ _**For a web-frontend use [tranga-website](https://github.com/C9Glax/tranga-website).**_
This project downloads the images for a Manga from the specified Scanlation-Website and packages them with some metadata - from that same website - in a .cbz-archive (per chapter). This project downloads the images for a Manga from the specified Scanlation-Website and packages them with some metadata - from that same website - in a .cbz-archive (per chapter).
@ -86,6 +85,7 @@ That is why I wanted to create my own project, in a language I understand, and t
- Newtonsoft.JSON - Newtonsoft.JSON
- [PuppeteerSharp](https://www.puppeteersharp.com/) - [PuppeteerSharp](https://www.puppeteersharp.com/)
- [Html Agility Pack (HAP)](https://html-agility-pack.net/) - [Html Agility Pack (HAP)](https://html-agility-pack.net/)
- [Soenneker.Utils.String.NeedlemanWunsch](https://github.com/soenneker/soenneker.utils.string.needlemanwunsch)
- 💙 Blåhaj 🦈 - 💙 Blåhaj 🦈
<p align="right">(<a href="#readme-top">back to top</a>)</p> <p align="right">(<a href="#readme-top">back to top</a>)</p>

View File

@ -91,18 +91,22 @@ public readonly struct Chapter : IComparable
{ {
if (!Directory.Exists(Path.Join(downloadLocation, parentManga.folderName))) if (!Directory.Exists(Path.Join(downloadLocation, parentManga.folderName)))
return false; return false;
FileInfo[] archives = new DirectoryInfo(Path.Join(downloadLocation, parentManga.folderName)).GetFiles(); FileInfo[] archives = new DirectoryInfo(Path.Join(downloadLocation, parentManga.folderName)).GetFiles().Where(file => file.Name.Split('.')[^1] == "cbz").ToArray();
Regex volChRex = new(@"(?:Vol(?:ume)?\.([0-9]+)\D*)?Ch(?:apter)?\.([0-9]+(?:\.[0-9]+)*)"); Regex volChRex = new(@"(?:Vol(?:ume)?\.([0-9]+)\D*)?Ch(?:apter)?\.([0-9]+(?:\.[0-9]+)*)");
Chapter t = this; Chapter t = this;
return archives.Select(archive => archive.Name).Any(archiveFileName => string thisPath = GetArchiveFilePath(downloadLocation);
FileInfo? archive = archives.FirstOrDefault(archive =>
{ {
Match m = volChRex.Match(archiveFileName); Match m = volChRex.Match(archive.Name);
string archiveVolNum = m.Groups[1].Success ? m.Groups[1].Value : "0"; string archiveVolNum = m.Groups[1].Success ? m.Groups[1].Value : "0";
string archiveChNum = m.Groups[2].Value; string archiveChNum = m.Groups[2].Value;
return archiveVolNum == t.volumeNumber && return archiveVolNum == t.volumeNumber && archiveChNum == t.chapterNumber ||
archiveChNum == t.chapterNumber; archiveVolNum == "0" && archiveChNum == t.chapterNumber;
}); });
if(archive is not null && thisPath != archive.FullName)
archive.MoveTo(thisPath, true);
return archive is not null;
} }
/// <summary> /// <summary>
/// Creates full file path of chapter-archive /// Creates full file path of chapter-archive

View File

@ -14,7 +14,7 @@ public abstract class GlobalBase
protected TrangaSettings settings { get; init; } protected TrangaSettings settings { get; init; }
protected HashSet<NotificationConnector> notificationConnectors { get; init; } protected HashSet<NotificationConnector> notificationConnectors { get; init; }
protected HashSet<LibraryConnector> libraryConnectors { get; init; } protected HashSet<LibraryConnector> libraryConnectors { get; init; }
protected List<Manga> cachedPublications { get; init; } private Dictionary<string, Manga> cachedPublications { get; init; }
public static readonly NumberFormatInfo numberFormatDecimalPoint = new (){ NumberDecimalSeparator = "." }; public static readonly NumberFormatInfo numberFormatDecimalPoint = new (){ NumberDecimalSeparator = "." };
protected static readonly Regex baseUrlRex = new(@"https?:\/\/[0-9A-z\.-]+(:[0-9]+)?"); protected static readonly Regex baseUrlRex = new(@"https?:\/\/[0-9A-z\.-]+(:[0-9]+)?");
@ -36,6 +36,29 @@ public abstract class GlobalBase
this.cachedPublications = new(); this.cachedPublications = new();
} }
protected void AddMangaToCache(Manga manga)
{
if (!this.cachedPublications.TryAdd(manga.internalId, manga))
{
Log($"Overwriting Manga {manga.internalId}");
this.cachedPublications[manga.internalId] = manga;
}
}
protected Manga? GetCachedManga(string internalId)
{
return cachedPublications.TryGetValue(internalId, out Manga manga) switch
{
true => manga,
_ => null
};
}
protected IEnumerable<Manga> GetAllCachedManga()
{
return cachedPublications.Values;
}
protected void Log(string message) protected void Log(string message)
{ {
logger?.WriteLine(this.GetType().Name, message); logger?.WriteLine(this.GetType().Name, message);

View File

@ -150,39 +150,53 @@ public class JobBoss : GlobalBase
//Load json-job-files //Load json-job-files
foreach (FileInfo file in new DirectoryInfo(settings.jobsFolderPath).EnumerateFiles().Where(fileInfo => idRex.IsMatch(fileInfo.Name))) foreach (FileInfo file in new DirectoryInfo(settings.jobsFolderPath).EnumerateFiles().Where(fileInfo => idRex.IsMatch(fileInfo.Name)))
{ {
Job job = JsonConvert.DeserializeObject<Job>(File.ReadAllText(file.FullName), Log($"Adding {file.Name}");
new JobJsonConverter(this, new MangaConnectorJsonConverter(this, connectors)))!; Job? job = JsonConvert.DeserializeObject<Job>(File.ReadAllText(file.FullName),
new JobJsonConverter(this, new MangaConnectorJsonConverter(this, connectors)));
if (job is null)
{
string newName = file.FullName + ".failed";
Log($"Failed loading file {file.Name}.\nMoving to {newName}");
File.Move(file.FullName, newName);
}
else
{
Log($"Adding Job {job}");
this.jobs.Add(job); this.jobs.Add(job);
} }
}
//Connect jobs to parent-jobs and add Publications to cache //Connect jobs to parent-jobs and add Publications to cache
foreach (Job job in this.jobs) foreach (Job job in this.jobs)
{ {
this.jobs.FirstOrDefault(jjob => jjob.id == job.parentJobId)?.AddSubJob(job); Log($"Loading Job {job}");
Job? parentJob = this.jobs.FirstOrDefault(jjob => jjob.id == job.parentJobId);
if (parentJob is not null)
{
parentJob.AddSubJob(job);
Log($"Parent Job {parentJob}");
}
if (job is DownloadNewChapters dncJob) if (job is DownloadNewChapters dncJob)
cachedPublications.Add(dncJob.manga); AddMangaToCache(dncJob.manga);
} }
HashSet<string> coverFileNames = cachedPublications.Select(manga => manga.coverFileNameInCache!).ToHashSet(); string[] coverFiles = Directory.GetFiles(settings.coverImageCache);
foreach (string fileName in Directory.GetFiles(settings.coverImageCache)) //Cleanup Unused Covers foreach(string fileName in coverFiles.Where(fileName => !GetAllCachedManga().Any(manga => manga.coverFileNameInCache == fileName)))
{
if(!coverFileNames.Any(existingManga => fileName.Contains(existingManga)))
File.Delete(fileName); File.Delete(fileName);
} }
}
private void UpdateJobFile(Job job) internal void UpdateJobFile(Job job, string? oldFile = null)
{ {
string jobFilePath = Path.Join(settings.jobsFolderPath, $"{job.id}.json"); string newJobFilePath = Path.Join(settings.jobsFolderPath, $"{job.id}.json");
if (!this.jobs.Any(jjob => jjob.id == job.id)) if (!this.jobs.Any(jjob => jjob.id == job.id))
{ {
try try
{ {
Log($"Deleting Job-file {jobFilePath}"); Log($"Deleting Job-file {newJobFilePath}");
while(IsFileInUse(jobFilePath)) while(IsFileInUse(newJobFilePath))
Thread.Sleep(10); Thread.Sleep(10);
File.Delete(jobFilePath); File.Delete(newJobFilePath);
} }
catch (Exception e) catch (Exception e)
{ {
@ -191,11 +205,24 @@ public class JobBoss : GlobalBase
} }
else else
{ {
Log($"Exporting Job {jobFilePath}"); Log($"Exporting Job {newJobFilePath}");
string jobStr = JsonConvert.SerializeObject(job); string jobStr = JsonConvert.SerializeObject(job, Formatting.Indented);
while(IsFileInUse(jobFilePath)) while(IsFileInUse(newJobFilePath))
Thread.Sleep(10); Thread.Sleep(10);
File.WriteAllText(jobFilePath, jobStr); File.WriteAllText(newJobFilePath, jobStr);
}
if(oldFile is not null)
try
{
Log($"Deleting old Job-file {oldFile}");
while(IsFileInUse(oldFile))
Thread.Sleep(10);
File.Delete(oldFile);
}
catch (Exception e)
{
Log(e.ToString());
} }
} }
@ -245,7 +272,9 @@ public class JobBoss : GlobalBase
Log($"Next job in {jobs.MinBy(job => job.nextExecution)?.nextExecution.Subtract(DateTime.Now)} {jobs.MinBy(job => job.nextExecution)?.id}"); Log($"Next job in {jobs.MinBy(job => job.nextExecution)?.nextExecution.Subtract(DateTime.Now)} {jobs.MinBy(job => job.nextExecution)?.id}");
}else if (queueHead.progressToken.state is ProgressToken.State.Standby) }else if (queueHead.progressToken.state is ProgressToken.State.Standby)
{ {
Job[] subJobs = jobQueue.Peek().ExecuteReturnSubTasks(this).ToArray(); Job eJob = jobQueue.Peek();
Job[] subJobs = eJob.ExecuteReturnSubTasks(this).ToArray();
UpdateJobFile(eJob);
AddJobs(subJobs); AddJobs(subJobs);
AddJobsToQueue(subJobs); AddJobsToQueue(subJobs);
}else if (queueHead.progressToken.state is ProgressToken.State.Running && DateTime.Now.Subtract(queueHead.progressToken.lastUpdate) > TimeSpan.FromMinutes(5)) }else if (queueHead.progressToken.state is ProgressToken.State.Running && DateTime.Now.Subtract(queueHead.progressToken.lastUpdate) > TimeSpan.FromMinutes(5))

View File

@ -33,8 +33,26 @@ public class UpdateMetadata : Job
return Array.Empty<Job>(); return Array.Empty<Job>();
} }
this.manga.UpdateMetadata(updatedManga); this.manga = manga.WithMetadata(updatedManga);
this.manga.SaveSeriesInfoJson(settings.downloadLocation, true); this.manga.SaveSeriesInfoJson(settings.downloadLocation, true);
this.mangaConnector.CopyCoverFromCacheToDownloadLocation(manga);
foreach (Job job in jobBoss.GetJobsLike(publication: this.manga))
{
string oldFile;
if (job is DownloadNewChapters dc)
{
oldFile = dc.id;
dc.manga = this.manga;
}
else if (job is UpdateMetadata um)
{
oldFile = um.id;
um.manga = this.manga;
}
else
continue;
jobBoss.UpdateJobFile(job, oldFile);
}
this.progressToken.Complete(); this.progressToken.Complete();
} }
else else

View File

@ -68,6 +68,14 @@ public class Kavita : LibraryConnector
NetClient.MakePost($"{baseUrl}/api/Library/scan?libraryId={lib.id}", "Bearer", auth, logger); NetClient.MakePost($"{baseUrl}/api/Library/scan?libraryId={lib.id}", "Bearer", auth, logger);
} }
internal override bool Test()
{
foreach (KavitaLibrary lib in GetLibraries())
if (NetClient.MakePost($"{baseUrl}/api/Library/scan?libraryId={lib.id}", "Bearer", auth, logger))
return true;
return false;
}
/// <summary> /// <summary>
/// Fetches all libraries available to the user /// Fetches all libraries available to the user
/// </summary> /// </summary>
@ -75,7 +83,7 @@ public class Kavita : LibraryConnector
private IEnumerable<KavitaLibrary> GetLibraries() private IEnumerable<KavitaLibrary> GetLibraries()
{ {
Log("Getting libraries."); Log("Getting libraries.");
Stream data = NetClient.MakeRequest($"{baseUrl}/api/Library", "Bearer", auth, logger); Stream data = NetClient.MakeRequest($"{baseUrl}/api/Library/libraries", "Bearer", auth, logger);
if (data == Stream.Null) if (data == Stream.Null)
{ {
Log("No libraries returned"); Log("No libraries returned");
@ -88,11 +96,13 @@ public class Kavita : LibraryConnector
return Array.Empty<KavitaLibrary>(); return Array.Empty<KavitaLibrary>();
} }
HashSet<KavitaLibrary> ret = new(); List<KavitaLibrary> ret = new();
foreach (JsonNode? jsonNode in result) foreach (JsonNode? jsonNode in result)
{ {
var jObject = (JsonObject?)jsonNode; JsonObject? jObject = (JsonObject?)jsonNode;
if(jObject is null)
continue;
int libraryId = jObject!["id"]!.GetValue<int>(); int libraryId = jObject!["id"]!.GetValue<int>();
string libraryName = jObject["name"]!.GetValue<string>(); string libraryName = jObject["name"]!.GetValue<string>();
ret.Add(new KavitaLibrary(libraryId, libraryName)); ret.Add(new KavitaLibrary(libraryId, libraryName));

View File

@ -32,6 +32,14 @@ public class Komga : LibraryConnector
NetClient.MakePost($"{baseUrl}/api/v1/libraries/{lib.id}/scan", "Basic", auth, logger); NetClient.MakePost($"{baseUrl}/api/v1/libraries/{lib.id}/scan", "Basic", auth, logger);
} }
internal override bool Test()
{
foreach (KomgaLibrary lib in GetLibraries())
if (NetClient.MakePost($"{baseUrl}/api/v1/libraries/{lib.id}/scan", "Basic", auth, logger))
return true;
return false;
}
/// <summary> /// <summary>
/// Fetches all libraries available to the user /// Fetches all libraries available to the user
/// </summary> /// </summary>

View File

@ -30,6 +30,7 @@ public abstract class LibraryConnector : GlobalBase
this.libraryType = libraryType; this.libraryType = libraryType;
} }
public abstract void UpdateLibrary(); public abstract void UpdateLibrary();
internal abstract bool Test();
protected static class NetClient protected static class NetClient
{ {

View File

@ -1,6 +1,7 @@
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Web;
using Newtonsoft.Json; using Newtonsoft.Json;
using static System.IO.UnixFileMode; using static System.IO.UnixFileMode;
@ -12,15 +13,15 @@ namespace Tranga;
public struct Manga public struct Manga
{ {
public string sortName { get; private set; } public string sortName { get; private set; }
public List<string> authors { get; } public List<string> authors { get; private set; }
// ReSharper disable once UnusedAutoPropertyAccessor.Global // ReSharper disable once UnusedAutoPropertyAccessor.Global
public Dictionary<string,string> altTitles { get; } public Dictionary<string,string> altTitles { get; private set; }
// ReSharper disable once MemberCanBePrivate.Global // ReSharper disable once MemberCanBePrivate.Global
public string? description { get; private set; } public string? description { get; private set; }
public string[] tags { get; } public string[] tags { get; private set; }
// ReSharper disable once UnusedAutoPropertyAccessor.Global // ReSharper disable once UnusedAutoPropertyAccessor.Global
public string? coverUrl { get; } public string? coverUrl { get; private set; }
public string? coverFileNameInCache { get; } public string? coverFileNameInCache { get; private set; }
// ReSharper disable once UnusedAutoPropertyAccessor.Global // ReSharper disable once UnusedAutoPropertyAccessor.Global
public Dictionary<string,string> links { get; } public Dictionary<string,string> links { get; }
// ReSharper disable once MemberCanBePrivate.Global // ReSharper disable once MemberCanBePrivate.Global
@ -28,7 +29,7 @@ public struct Manga
public string? originalLanguage { get; } public string? originalLanguage { get; }
// ReSharper disable twice MemberCanBePrivate.Global // ReSharper disable twice MemberCanBePrivate.Global
public string status { get; private set; } public string status { get; private set; }
public ReleaseStatusByte releaseStatus { get; } public ReleaseStatusByte releaseStatus { get; private set; }
public enum ReleaseStatusByte : byte public enum ReleaseStatusByte : byte
{ {
Continuing = 0, Continuing = 0,
@ -44,24 +45,25 @@ public struct Manga
public float latestChapterDownloaded { get; set; } public float latestChapterDownloaded { get; set; }
public float latestChapterAvailable { get; set; } public float latestChapterAvailable { get; set; }
public string? websiteUrl { get; private set; }
private static readonly Regex LegalCharacters = new (@"[A-Za-zÀ-ÖØ-öø-ÿ0-9 \.\-,'\'\)\(~!\+]*"); private static readonly Regex LegalCharacters = new (@"[A-Za-zÀ-ÖØ-öø-ÿ0-9 \.\-,'\'\)\(~!\+]*");
[JsonConstructor] [JsonConstructor]
public Manga(string sortName, List<string> authors, string? description, Dictionary<string,string> altTitles, string[] tags, string? coverUrl, string? coverFileNameInCache, Dictionary<string,string>? links, int? year, string? originalLanguage, string status, string publicationId, ReleaseStatusByte releaseStatus = 0, string? websiteUrl = null, string? folderName = null, float? ignoreChaptersBelow = 0) public Manga(string sortName, List<string> authors, string? description, Dictionary<string,string> altTitles, string[] tags, string? coverUrl, string? coverFileNameInCache, Dictionary<string,string>? links, int? year, string? originalLanguage, string publicationId, ReleaseStatusByte releaseStatus, string? websiteUrl = null, string? folderName = null, float? ignoreChaptersBelow = 0)
{ {
this.sortName = sortName; this.sortName = HttpUtility.HtmlDecode(sortName);
this.authors = authors; this.authors = authors.Select(HttpUtility.HtmlDecode).ToList()!;
this.description = description; this.description = HttpUtility.HtmlDecode(description);
this.altTitles = altTitles; this.altTitles = altTitles.ToDictionary(a => HttpUtility.HtmlDecode(a.Key), a => HttpUtility.HtmlDecode(a.Value));
this.tags = tags; this.tags = tags.Select(HttpUtility.HtmlDecode).ToArray()!;
this.coverFileNameInCache = coverFileNameInCache; this.coverFileNameInCache = coverFileNameInCache;
this.coverUrl = coverUrl; this.coverUrl = coverUrl;
this.links = links ?? new Dictionary<string, string>(); this.links = links ?? new Dictionary<string, string>();
this.year = year; this.year = year;
this.originalLanguage = originalLanguage; this.originalLanguage = originalLanguage;
this.status = status;
this.publicationId = publicationId; this.publicationId = publicationId;
this.folderName = folderName ?? string.Concat(LegalCharacters.Matches(sortName)); this.folderName = folderName ?? string.Concat(LegalCharacters.Matches(HttpUtility.HtmlDecode(sortName)));
while (this.folderName.EndsWith('.')) while (this.folderName.EndsWith('.'))
this.folderName = this.folderName.Substring(0, this.folderName.Length - 1); this.folderName = this.folderName.Substring(0, this.folderName.Length - 1);
string onlyLowerLetters = string.Concat(this.sortName.ToLower().Where(Char.IsLetter)); string onlyLowerLetters = string.Concat(this.sortName.ToLower().Where(Char.IsLetter));
@ -70,17 +72,26 @@ public struct Manga
this.latestChapterDownloaded = 0; this.latestChapterDownloaded = 0;
this.latestChapterAvailable = 0; this.latestChapterAvailable = 0;
this.releaseStatus = releaseStatus; this.releaseStatus = releaseStatus;
this.status = Enum.GetName(releaseStatus) ?? "";
this.websiteUrl = websiteUrl;
} }
public void UpdateMetadata(Manga newManga) public Manga WithMetadata(Manga newManga)
{ {
this.sortName = newManga.sortName; return this with
this.description = newManga.description; {
foreach (string author in newManga.authors) sortName = newManga.sortName,
if(!this.authors.Contains(author)) description = newManga.description,
this.authors.Add(author); coverUrl = newManga.coverUrl,
this.status = newManga.status; authors = authors.Union(newManga.authors).ToList(),
this.year = newManga.year; altTitles = altTitles.UnionBy(newManga.altTitles, kv => kv.Key).ToDictionary(x => x.Key, x => x.Value),
tags = tags.Union(newManga.tags).ToArray(),
status = newManga.status,
releaseStatus = newManga.releaseStatus,
websiteUrl = newManga.websiteUrl,
year = newManga.year,
coverFileNameInCache = newManga.coverFileNameInCache
};
} }
public override bool Equals(object? obj) public override bool Equals(object? obj)
@ -93,7 +104,10 @@ public struct Manga
this.releaseStatus == compareManga.releaseStatus && this.releaseStatus == compareManga.releaseStatus &&
this.sortName == compareManga.sortName && this.sortName == compareManga.sortName &&
this.latestChapterAvailable.Equals(compareManga.latestChapterAvailable) && this.latestChapterAvailable.Equals(compareManga.latestChapterAvailable) &&
this.tags.SequenceEqual(compareManga.tags); this.authors.All(a => compareManga.authors.Contains(a)) &&
(this.coverFileNameInCache??"").Equals(compareManga.coverFileNameInCache) &&
(this.websiteUrl??"").Equals(compareManga.websiteUrl) &&
this.tags.All(t => compareManga.tags.Contains(t));
} }
public override string ToString() public override string ToString()
@ -168,38 +182,22 @@ public struct Manga
[JsonRequired]public string year { get; } [JsonRequired]public string year { get; }
[JsonRequired]public string status { get; } [JsonRequired]public string status { get; }
[JsonRequired]public string description_text { get; } [JsonRequired]public string description_text { get; }
[JsonIgnore] public static string[] continuing = new[]
{
"ongoing",
"hiatus",
"in corso",
"in pausa"
};
[JsonIgnore] public static string[] ended = new[]
{
"completed",
"cancelled",
"discontinued",
"finito",
"cancellato",
"droppato"
};
public Metadata(Manga manga) : this(manga.sortName, manga.year.ToString() ?? string.Empty, manga.status, manga.description ?? "") public Metadata(Manga manga) : this(manga.sortName, manga.year.ToString() ?? string.Empty, manga.releaseStatus, manga.description ?? "")
{ {
} }
public Metadata(string name, string year, string status, string description_text) public Metadata(string name, string year, ReleaseStatusByte status, string description_text)
{ {
this.name = name; this.name = name;
this.year = year; this.year = year;
if(continuing.Contains(status.ToLower())) this.status = status switch
this.status = "Continuing"; {
else if(ended.Contains(status.ToLower())) ReleaseStatusByte.Continuing => "Continuing",
this.status = "Ended"; ReleaseStatusByte.Completed => "Ended",
else _ => Enum.GetName(status) ?? "Ended"
this.status = status; };
this.description_text = description_text; this.description_text = description_text;
//kill it with fire, but otherwise Komga will not parse //kill it with fire, but otherwise Komga will not parse

View File

@ -49,7 +49,7 @@ public class Bato : MangaConnector
Log($"Failed to retrieve site"); Log($"Failed to retrieve site");
return null; return null;
} }
return ParseSinglePublicationFromHtml(requestResult.htmlDocument, url.Split('/')[^1]); return ParseSinglePublicationFromHtml(requestResult.htmlDocument, url.Split('/')[^1], url);
} }
private Manga[] ParsePublicationsFromHtml(HtmlDocument document) private Manga[] ParsePublicationsFromHtml(HtmlDocument document)
@ -72,7 +72,7 @@ public class Bato : MangaConnector
return ret.ToArray(); return ret.ToArray();
} }
private Manga ParseSinglePublicationFromHtml(HtmlDocument document, string publicationId) private Manga ParseSinglePublicationFromHtml(HtmlDocument document, string publicationId, string websiteUrl)
{ {
HtmlNode infoNode = document.DocumentNode.SelectSingleNode("/html/body/div/main/div[1]/div[2]"); HtmlNode infoNode = document.DocumentNode.SelectSingleNode("/html/body/div/main/div[1]/div[2]");
@ -86,7 +86,7 @@ public class Bato : MangaConnector
string posterUrl = document.DocumentNode.SelectNodes("//img") string posterUrl = document.DocumentNode.SelectNodes("//img")
.First(child => child.GetAttributeValue("data-hk", "") == "0-1-0").GetAttributeValue("src", "").Replace("&amp;", "&"); .First(child => child.GetAttributeValue("data-hk", "") == "0-1-0").GetAttributeValue("src", "").Replace("&amp;", "&");
string coverFileNameInCache = SaveCoverImageToCache(posterUrl, RequestType.MangaCover); string coverFileNameInCache = SaveCoverImageToCache(posterUrl, publicationId, RequestType.MangaCover);
List<HtmlNode> genreNodes = document.DocumentNode.SelectSingleNode("//b[text()='Genres:']/..").SelectNodes("span").ToList(); List<HtmlNode> genreNodes = document.DocumentNode.SelectSingleNode("//b[text()='Genres:']/..").SelectNodes("span").ToList();
string[] tags = genreNodes.Select(node => node.FirstChild.InnerText).ToArray(); string[] tags = genreNodes.Select(node => node.FirstChild.InnerText).ToArray();
@ -115,8 +115,8 @@ public class Bato : MangaConnector
} }
Manga manga = new (sortName, authors, description, altTitles, tags, posterUrl, coverFileNameInCache, new Dictionary<string, string>(), Manga manga = new (sortName, authors, description, altTitles, tags, posterUrl, coverFileNameInCache, new Dictionary<string, string>(),
year, originalLanguage, status, publicationId, releaseStatus); year, originalLanguage, publicationId, releaseStatus, websiteUrl: websiteUrl);
cachedPublications.Add(manga); AddMangaToCache(manga);
return manga; return manga;
} }

View File

@ -175,14 +175,14 @@ public abstract class MangaConnector : GlobalBase
return; return;
} }
string fileInCache = Path.Join(settings.coverImageCache, manga.coverFileNameInCache); string? fileInCache = manga.coverFileNameInCache;
if (!File.Exists(fileInCache)) if (fileInCache is null || !File.Exists(fileInCache))
{ {
Log($"Cloning cover failed: File missing {fileInCache}."); Log($"Cloning cover failed: File missing {fileInCache}.");
if (retries > 0 && manga.coverUrl is not null) if (retries > 0 && manga.coverUrl is not null)
{ {
Log($"Trying {retries} more times"); Log($"Trying {retries} more times");
SaveCoverImageToCache(manga.coverUrl, 0); SaveCoverImageToCache(manga.coverUrl, manga.internalId, 0);
CopyCoverFromCacheToDownloadLocation(manga, --retries); CopyCoverFromCacheToDownloadLocation(manga, --retries);
} }
@ -285,20 +285,23 @@ public abstract class MangaConnector : GlobalBase
return HttpStatusCode.OK; return HttpStatusCode.OK;
} }
protected string SaveCoverImageToCache(string url, RequestType requestType) protected string SaveCoverImageToCache(string url, string mangaInternalId, RequestType requestType)
{ {
string filetype = url.Split('/')[^1].Split('?')[0].Split('.')[^1]; Regex urlRex = new (@"https?:\/\/((?:[a-zA-Z0-9-]+\.)+[a-zA-Z0-9]+)\/(?:.+\/)*(.+\.([a-zA-Z]+))");
string filename = $"{DateTime.Now.Ticks.ToString()}.{filetype}"; //https?:\/\/[a-zA-Z0-9-]+\.([a-zA-Z0-9-]+\.[a-zA-Z0-9]+)\/(?:.+\/)*(.+\.([a-zA-Z]+)) for only second level domains
Match match = urlRex.Match(url);
string filename = $"{match.Groups[1].Value}-{mangaInternalId}.{match.Groups[3].Value}";
string saveImagePath = Path.Join(settings.coverImageCache, filename); string saveImagePath = Path.Join(settings.coverImageCache, filename);
if (File.Exists(saveImagePath)) if (File.Exists(saveImagePath))
return filename; return saveImagePath;
RequestResult coverResult = downloadClient.MakeRequest(url, requestType); RequestResult coverResult = downloadClient.MakeRequest(url, requestType);
using MemoryStream ms = new(); using MemoryStream ms = new();
coverResult.result.CopyTo(ms); coverResult.result.CopyTo(ms);
Directory.CreateDirectory(settings.coverImageCache);
File.WriteAllBytes(saveImagePath, ms.ToArray()); File.WriteAllBytes(saveImagePath, ms.ToArray());
Log($"Saving cover to {saveImagePath}"); Log($"Saving cover to {saveImagePath}");
return filename; return saveImagePath;
} }
} }

View File

@ -38,6 +38,8 @@ public class MangaConnectorJsonConverter : JsonConverter
return this._connectors.First(c => c is Bato); return this._connectors.First(c => c is Bato);
case "Manga4Life": case "Manga4Life":
return this._connectors.First(c => c is MangaLife); return this._connectors.First(c => c is MangaLife);
case "ManhuaPlus":
return this._connectors.First(c => c is ManhuaPlus);
} }
throw new Exception(); throw new Exception();

View File

@ -115,8 +115,8 @@ public class MangaDex : MangaConnector
}; };
Dictionary<string, string> linksDict = new(); Dictionary<string, string> linksDict = new();
if (attributes.TryGetPropertyValue("links", out JsonNode? linksNode)) if (attributes.TryGetPropertyValue("links", out JsonNode? linksNode) && linksNode is not null)
foreach (KeyValuePair<string, JsonNode> linkKv in linksNode!.AsObject()) foreach (KeyValuePair<string, JsonNode?> linkKv in linksNode!.AsObject())
linksDict.TryAdd(linkKv.Key, linkKv.Value.GetValue<string>()); linksDict.TryAdd(linkKv.Key, linkKv.Value.GetValue<string>());
string? originalLanguage = string? originalLanguage =
@ -160,7 +160,7 @@ public class MangaDex : MangaConnector
return null; return null;
string fileName = coverNode["attributes"]!["fileName"]!.GetValue<string>(); string fileName = coverNode["attributes"]!["fileName"]!.GetValue<string>();
string coverUrl = $"https://uploads.mangadex.org/covers/{publicationId}/{fileName}"; string coverUrl = $"https://uploads.mangadex.org/covers/{publicationId}/{fileName}";
string coverCacheName = SaveCoverImageToCache(coverUrl, RequestType.MangaCover); string coverCacheName = SaveCoverImageToCache(coverUrl, publicationId, RequestType.MangaCover);
List<string> authors = new(); List<string> authors = new();
JsonNode?[] authorNodes = relationshipsNode.AsArray() JsonNode?[] authorNodes = relationshipsNode.AsArray()
@ -183,11 +183,11 @@ public class MangaDex : MangaConnector
linksDict, linksDict,
year, year,
originalLanguage, originalLanguage,
Enum.GetName(status) ?? "",
publicationId, publicationId,
status status,
websiteUrl: $"https://mangadex.org/title/{publicationId}"
); );
cachedPublications.Add(pub); AddMangaToCache(pub);
return pub; return pub;
} }

View File

@ -28,7 +28,7 @@ public class MangaKatana : MangaConnector
&& requestResult.redirectedToUrl is not null && requestResult.redirectedToUrl is not null
&& requestResult.redirectedToUrl.Contains("mangakatana.com/manga")) && requestResult.redirectedToUrl.Contains("mangakatana.com/manga"))
{ {
return new [] { ParseSinglePublicationFromHtml(requestResult.result, requestResult.redirectedToUrl.Split('/')[^1]) }; return new [] { ParseSinglePublicationFromHtml(requestResult.result, requestResult.redirectedToUrl.Split('/')[^1], requestResult.redirectedToUrl) };
} }
Manga[] publications = ParsePublicationsFromHtml(requestResult.result); Manga[] publications = ParsePublicationsFromHtml(requestResult.result);
@ -47,7 +47,7 @@ public class MangaKatana : MangaConnector
downloadClient.MakeRequest(url, RequestType.MangaInfo); downloadClient.MakeRequest(url, RequestType.MangaInfo);
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300) if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
return null; return null;
return ParseSinglePublicationFromHtml(requestResult.result, url.Split('/')[^1]); return ParseSinglePublicationFromHtml(requestResult.result, url.Split('/')[^1], url);
} }
private Manga[] ParsePublicationsFromHtml(Stream html) private Manga[] ParsePublicationsFromHtml(Stream html)
@ -77,13 +77,12 @@ public class MangaKatana : MangaConnector
return ret.ToArray(); return ret.ToArray();
} }
private Manga ParseSinglePublicationFromHtml(Stream html, string publicationId) private Manga ParseSinglePublicationFromHtml(Stream html, string publicationId, string websiteUrl)
{ {
StreamReader reader = new(html); StreamReader reader = new(html);
string htmlString = reader.ReadToEnd(); string htmlString = reader.ReadToEnd();
HtmlDocument document = new(); HtmlDocument document = new();
document.LoadHtml(htmlString); document.LoadHtml(htmlString);
string status = "";
Dictionary<string, string> altTitles = new(); Dictionary<string, string> altTitles = new();
Dictionary<string, string>? links = null; Dictionary<string, string>? links = null;
HashSet<string> tags = new(); HashSet<string> tags = new();
@ -112,8 +111,7 @@ public class MangaKatana : MangaConnector
authors = value.Split(','); authors = value.Split(',');
break; break;
case "status": case "status":
status = value; switch (value.ToLower())
switch (status.ToLower())
{ {
case "ongoing": releaseStatus = Manga.ReleaseStatusByte.Continuing; break; case "ongoing": releaseStatus = Manga.ReleaseStatusByte.Continuing; break;
case "completed": releaseStatus = Manga.ReleaseStatusByte.Completed; break; case "completed": releaseStatus = Manga.ReleaseStatusByte.Completed; break;
@ -128,7 +126,7 @@ public class MangaKatana : MangaConnector
string posterUrl = document.DocumentNode.SelectSingleNode("//*[@id='single_book']/div[1]/div").Descendants("img").First() string posterUrl = document.DocumentNode.SelectSingleNode("//*[@id='single_book']/div[1]/div").Descendants("img").First()
.GetAttributes().First(a => a.Name == "src").Value; .GetAttributes().First(a => a.Name == "src").Value;
string coverFileNameInCache = SaveCoverImageToCache(posterUrl, RequestType.MangaCover); string coverFileNameInCache = SaveCoverImageToCache(posterUrl, publicationId, RequestType.MangaCover);
string description = document.DocumentNode.SelectSingleNode("//*[@id='single_book']/div[3]/p").InnerText; string description = document.DocumentNode.SelectSingleNode("//*[@id='single_book']/div[3]/p").InnerText;
while (description.StartsWith('\n')) while (description.StartsWith('\n'))
@ -144,8 +142,8 @@ public class MangaKatana : MangaConnector
} }
Manga manga = new (sortName, authors.ToList(), description, altTitles, tags.ToArray(), posterUrl, coverFileNameInCache, links, Manga manga = new (sortName, authors.ToList(), description, altTitles, tags.ToArray(), posterUrl, coverFileNameInCache, links,
year, originalLanguage, status, publicationId, releaseStatus); year, originalLanguage, publicationId, releaseStatus, websiteUrl: websiteUrl);
cachedPublications.Add(manga); AddMangaToCache(manga);
return manga; return manga;
} }

View File

@ -41,7 +41,7 @@ public class MangaLife : MangaConnector
RequestResult requestResult = this.downloadClient.MakeRequest(url, RequestType.MangaInfo); RequestResult requestResult = this.downloadClient.MakeRequest(url, RequestType.MangaInfo);
if(requestResult.htmlDocument is not null) if(requestResult.htmlDocument is not null)
return ParseSinglePublicationFromHtml(requestResult.htmlDocument, publicationId); return ParseSinglePublicationFromHtml(requestResult.htmlDocument, publicationId, url);
return null; return null;
} }
@ -69,7 +69,7 @@ public class MangaLife : MangaConnector
} }
private Manga ParseSinglePublicationFromHtml(HtmlDocument document, string publicationId) private Manga ParseSinglePublicationFromHtml(HtmlDocument document, string publicationId, string websiteUrl)
{ {
string originalLanguage = "", status = ""; string originalLanguage = "", status = "";
Dictionary<string, string> altTitles = new(), links = new(); Dictionary<string, string> altTitles = new(), links = new();
@ -78,7 +78,7 @@ public class MangaLife : MangaConnector
HtmlNode posterNode = document.DocumentNode.SelectSingleNode("//div[@class='BoxBody']//div[@class='row']//img"); HtmlNode posterNode = document.DocumentNode.SelectSingleNode("//div[@class='BoxBody']//div[@class='row']//img");
string posterUrl = posterNode.GetAttributeValue("src", ""); string posterUrl = posterNode.GetAttributeValue("src", "");
string coverFileNameInCache = SaveCoverImageToCache(posterUrl, RequestType.MangaCover); string coverFileNameInCache = SaveCoverImageToCache(posterUrl, publicationId, RequestType.MangaCover);
HtmlNode titleNode = document.DocumentNode.SelectSingleNode("//div[@class='BoxBody']//div[@class='row']//h1"); HtmlNode titleNode = document.DocumentNode.SelectSingleNode("//div[@class='BoxBody']//div[@class='row']//h1");
string sortName = titleNode.InnerText; string sortName = titleNode.InnerText;
@ -122,8 +122,8 @@ public class MangaLife : MangaConnector
string description = descriptionNode.InnerText; string description = descriptionNode.InnerText;
Manga manga = new(sortName, authors.ToList(), description, altTitles, tags.ToArray(), posterUrl, Manga manga = new(sortName, authors.ToList(), description, altTitles, tags.ToArray(), posterUrl,
coverFileNameInCache, links, year, originalLanguage, status, publicationId, releaseStatus); coverFileNameInCache, links, year, originalLanguage, publicationId, releaseStatus, websiteUrl: websiteUrl);
cachedPublications.Add(manga); AddMangaToCache(manga);
return manga; return manga;
} }

View File

@ -65,12 +65,11 @@ public class Manganato : MangaConnector
if (requestResult.htmlDocument is null) if (requestResult.htmlDocument is null)
return null; return null;
return ParseSinglePublicationFromHtml(requestResult.htmlDocument, url.Split('/')[^1]); return ParseSinglePublicationFromHtml(requestResult.htmlDocument, url.Split('/')[^1], url);
} }
private Manga ParseSinglePublicationFromHtml(HtmlDocument document, string publicationId) private Manga ParseSinglePublicationFromHtml(HtmlDocument document, string publicationId, string websiteUrl)
{ {
string status = "";
Dictionary<string, string> altTitles = new(); Dictionary<string, string> altTitles = new();
Dictionary<string, string>? links = null; Dictionary<string, string>? links = null;
HashSet<string> tags = new(); HashSet<string> tags = new();
@ -99,10 +98,11 @@ public class Manganato : MangaConnector
break; break;
case "authors": case "authors":
authors = value.Split('-'); authors = value.Split('-');
for (int i = 0; i < authors.Length; i++)
authors[i] = authors[i].Replace("\r\n", "");
break; break;
case "status": case "status":
status = value; switch (value.ToLower())
switch (status.ToLower())
{ {
case "ongoing": releaseStatus = Manga.ReleaseStatusByte.Continuing; break; case "ongoing": releaseStatus = Manga.ReleaseStatusByte.Continuing; break;
case "completed": releaseStatus = Manga.ReleaseStatusByte.Completed; break; case "completed": releaseStatus = Manga.ReleaseStatusByte.Completed; break;
@ -110,6 +110,8 @@ public class Manganato : MangaConnector
break; break;
case "genres": case "genres":
string[] genres = value.Split(" - "); string[] genres = value.Split(" - ");
for (int i = 0; i < genres.Length; i++)
genres[i] = genres[i].Replace("\r\n", "");
tags = genres.ToHashSet(); tags = genres.ToHashSet();
break; break;
} }
@ -118,7 +120,7 @@ public class Manganato : MangaConnector
string posterUrl = document.DocumentNode.Descendants("span").First(s => s.HasClass("info-image")).Descendants("img").First() string posterUrl = document.DocumentNode.Descendants("span").First(s => s.HasClass("info-image")).Descendants("img").First()
.GetAttributes().First(a => a.Name == "src").Value; .GetAttributes().First(a => a.Name == "src").Value;
string coverFileNameInCache = SaveCoverImageToCache(posterUrl, RequestType.MangaCover); string coverFileNameInCache = SaveCoverImageToCache(posterUrl, publicationId, RequestType.MangaCover);
string description = document.DocumentNode.Descendants("div").First(d => d.HasClass("panel-story-info-description")) string description = document.DocumentNode.Descendants("div").First(d => d.HasClass("panel-story-info-description"))
.InnerText.Replace("Description :", ""); .InnerText.Replace("Description :", "");
@ -130,8 +132,8 @@ public class Manganato : MangaConnector
int year = Convert.ToInt32(yearString.Split(',')[^1]) + 2000; int year = Convert.ToInt32(yearString.Split(',')[^1]) + 2000;
Manga manga = new (sortName, authors.ToList(), description, altTitles, tags.ToArray(), posterUrl, coverFileNameInCache, links, Manga manga = new (sortName, authors.ToList(), description, altTitles, tags.ToArray(), posterUrl, coverFileNameInCache, links,
year, originalLanguage, status, publicationId, releaseStatus); year, originalLanguage, publicationId, releaseStatus, websiteUrl: websiteUrl);
cachedPublications.Add(manga); AddMangaToCache(manga);
return manga; return manga;
} }

View File

@ -4,6 +4,7 @@ using System.Text.RegularExpressions;
using System.Xml.Linq; using System.Xml.Linq;
using HtmlAgilityPack; using HtmlAgilityPack;
using Newtonsoft.Json; using Newtonsoft.Json;
using Soenneker.Utils.String.NeedlemanWunsch;
using Tranga.Jobs; using Tranga.Jobs;
namespace Tranga.MangaConnectors; namespace Tranga.MangaConnectors;
@ -41,14 +42,6 @@ public class Mangasee : MangaConnector
SearchResult[] filteredResults = FilteredResults(publicationTitle, searchResults); SearchResult[] filteredResults = FilteredResults(publicationTitle, searchResults);
Log($"Total available manga: {searchResults.Length} Filtered down to: {filteredResults.Length}"); Log($"Total available manga: {searchResults.Length} Filtered down to: {filteredResults.Length}");
/*
Dictionary<SearchResult, int> levenshteinRelation = filteredResults.ToDictionary(result => result,
result =>
{
Log($"Levenshtein {result.s}");
return LevenshteinDistance(publicationTitle.Replace(" ", "").ToLower(), result.s.Replace(" ", "").ToLower());
});
Log($"After levenshtein: {levenshteinRelation.Count}");*/
string[] urls = filteredResults.Select(result => $"https://mangasee123.com/manga/{result.i}").ToArray(); string[] urls = filteredResults.Select(result => $"https://mangasee123.com/manga/{result.i}").ToArray();
List<Manga> searchResultManga = new(); List<Manga> searchResultManga = new();
@ -70,42 +63,19 @@ public class Mangasee : MangaConnector
private SearchResult[] FilteredResults(string publicationTitle, SearchResult[] unfilteredSearchResults) private SearchResult[] FilteredResults(string publicationTitle, SearchResult[] unfilteredSearchResults)
{ {
string[] bannedStrings = {"a", "the", "of", "as", "to", "no", "for", "on", "with", "be", "and", "in", "wa", "at"}; Dictionary<SearchResult, int> similarity = new();
string[] cleanSplitPublicationTitle = publicationTitle.Split(' ') foreach (SearchResult sr in unfilteredSearchResults)
.Where(part => part.Length > 0 && !bannedStrings.Contains(part.ToLower())).ToArray();
return unfilteredSearchResults.Where(usr =>
{ {
string cleanSearchResultString = string.Join(' ', usr.s.Split(' ').Where(part => part.Length > 0 && !bannedStrings.Contains(part.ToLower()))); List<int> scores = new();
foreach(string splitPublicationTitlePart in cleanSplitPublicationTitle) foreach (string se in sr.a)
if (cleanSearchResultString.Contains(splitPublicationTitlePart, StringComparison.InvariantCultureIgnoreCase) || scores.Add(NeedlemanWunschStringUtil.CalculateSimilarity(se.ToLower(), publicationTitle.ToLower()));
cleanSearchResultString.Contains(splitPublicationTitlePart, StringComparison.InvariantCultureIgnoreCase)) scores.Add(NeedlemanWunschStringUtil.CalculateSimilarity(sr.s.ToLower(), publicationTitle.ToLower()));
return true; similarity.Add(sr, scores.Sum() / scores.Count);
return false;
}).ToArray();
} }
private int LevenshteinDistance(string a, string b) SearchResult[] similarity90 = similarity.Where(s => s.Value < 10).Select(s => s.Key).ToArray();
{
if (b.Length == 0)
return a.Length;
if (a.Length == 0)
return b.Length;
if (a[0] == b[0])
return LevenshteinDistance(a[1..], b[1..]);
int case1 = LevenshteinDistance(a, b[1..]); return similarity90;
int case2 = LevenshteinDistance(a[1..], b[1..]);
int case3 = LevenshteinDistance(a[1..], b);
if (case1 < case2)
{
return 1 + (case1 < case3 ? case1 : case3);
}
else
{
return 1 + (case2 < case3 ? case2 : case3);
}
} }
public override Manga? GetMangaFromId(string publicationId) public override Manga? GetMangaFromId(string publicationId)
@ -120,11 +90,11 @@ public class Mangasee : MangaConnector
RequestResult requestResult = this.downloadClient.MakeRequest(url, RequestType.MangaInfo); RequestResult requestResult = this.downloadClient.MakeRequest(url, RequestType.MangaInfo);
if((int)requestResult.statusCode < 300 && (int)requestResult.statusCode >= 200 && requestResult.htmlDocument is not null) if((int)requestResult.statusCode < 300 && (int)requestResult.statusCode >= 200 && requestResult.htmlDocument is not null)
return ParseSinglePublicationFromHtml(requestResult.htmlDocument, publicationId); return ParseSinglePublicationFromHtml(requestResult.htmlDocument, publicationId, url);
return null; return null;
} }
private Manga ParseSinglePublicationFromHtml(HtmlDocument document, string publicationId) private Manga ParseSinglePublicationFromHtml(HtmlDocument document, string publicationId, string websiteUrl)
{ {
string originalLanguage = "", status = ""; string originalLanguage = "", status = "";
Dictionary<string, string> altTitles = new(), links = new(); Dictionary<string, string> altTitles = new(), links = new();
@ -133,7 +103,7 @@ public class Mangasee : MangaConnector
HtmlNode posterNode = document.DocumentNode.SelectSingleNode("//div[@class='BoxBody']//div[@class='row']//img"); HtmlNode posterNode = document.DocumentNode.SelectSingleNode("//div[@class='BoxBody']//div[@class='row']//img");
string posterUrl = posterNode.GetAttributeValue("src", ""); string posterUrl = posterNode.GetAttributeValue("src", "");
string coverFileNameInCache = SaveCoverImageToCache(posterUrl, RequestType.MangaCover); string coverFileNameInCache = SaveCoverImageToCache(posterUrl, publicationId, RequestType.MangaCover);
HtmlNode titleNode = document.DocumentNode.SelectSingleNode("//div[@class='BoxBody']//div[@class='row']//h1"); HtmlNode titleNode = document.DocumentNode.SelectSingleNode("//div[@class='BoxBody']//div[@class='row']//h1");
string sortName = titleNode.InnerText; string sortName = titleNode.InnerText;
@ -178,8 +148,8 @@ public class Mangasee : MangaConnector
Manga manga = new(sortName, authors.ToList(), description, altTitles, tags.ToArray(), posterUrl, Manga manga = new(sortName, authors.ToList(), description, altTitles, tags.ToArray(), posterUrl,
coverFileNameInCache, links, coverFileNameInCache, links,
year, originalLanguage, status, publicationId, releaseStatus); year, originalLanguage, publicationId, releaseStatus, websiteUrl: websiteUrl);
cachedPublications.Add(manga); AddMangaToCache(manga);
return manga; return manga;
} }

View File

@ -68,10 +68,10 @@ public class Mangaworld: MangaConnector
Regex idRex = new (@"https:\/\/www\.mangaworld\.[a-z]{0,63}\/manga\/([0-9]+\/[0-9A-z\-]+).*"); Regex idRex = new (@"https:\/\/www\.mangaworld\.[a-z]{0,63}\/manga\/([0-9]+\/[0-9A-z\-]+).*");
string id = idRex.Match(url).Groups[1].Value; string id = idRex.Match(url).Groups[1].Value;
return ParseSinglePublicationFromHtml(requestResult.htmlDocument, id); return ParseSinglePublicationFromHtml(requestResult.htmlDocument, id, url);
} }
private Manga ParseSinglePublicationFromHtml(HtmlDocument document, string publicationId) private Manga ParseSinglePublicationFromHtml(HtmlDocument document, string publicationId, string websiteUrl)
{ {
Dictionary<string, string> altTitles = new(); Dictionary<string, string> altTitles = new();
Dictionary<string, string>? links = null; Dictionary<string, string>? links = null;
@ -111,7 +111,7 @@ public class Mangaworld: MangaConnector
string posterUrl = document.DocumentNode.SelectSingleNode("//img[@class='rounded']").GetAttributeValue("src", ""); string posterUrl = document.DocumentNode.SelectSingleNode("//img[@class='rounded']").GetAttributeValue("src", "");
string coverFileNameInCache = SaveCoverImageToCache(posterUrl, RequestType.MangaCover); string coverFileNameInCache = SaveCoverImageToCache(posterUrl, publicationId.Replace('/', '-'), RequestType.MangaCover);
string description = document.DocumentNode.SelectSingleNode("//div[@id='noidungm']").InnerText; string description = document.DocumentNode.SelectSingleNode("//div[@id='noidungm']").InnerText;
@ -119,8 +119,8 @@ public class Mangaworld: MangaConnector
int year = Convert.ToInt32(yearString); int year = Convert.ToInt32(yearString);
Manga manga = new (sortName, authors.ToList(), description, altTitles, tags.ToArray(), posterUrl, coverFileNameInCache, links, Manga manga = new (sortName, authors.ToList(), description, altTitles, tags.ToArray(), posterUrl, coverFileNameInCache, links,
year, originalLanguage, status, publicationId, releaseStatus); year, originalLanguage, publicationId, releaseStatus, websiteUrl: websiteUrl);
cachedPublications.Add(manga); AddMangaToCache(manga);
return manga; return manga;
} }
@ -153,10 +153,13 @@ public class Mangaworld: MangaConnector
{ {
foreach (HtmlNode volNode in document.DocumentNode.SelectNodes("//div[contains(concat(' ',normalize-space(@class),' '),'volume-element')]")) foreach (HtmlNode volNode in document.DocumentNode.SelectNodes("//div[contains(concat(' ',normalize-space(@class),' '),'volume-element')]"))
{ {
string volume = volNode.SelectNodes("div").First(node => node.HasClass("volume")).SelectSingleNode("p").InnerText.Split(' ')[^1]; string volume = Regex.Match(volNode.SelectNodes("div").First(node => node.HasClass("volume")).SelectSingleNode("p").InnerText,
@"[Vv]olume ([0-9]+).*").Groups[1].Value;
foreach (HtmlNode chNode in volNode.SelectNodes("div").First(node => node.HasClass("volume-chapters")).SelectNodes("div")) foreach (HtmlNode chNode in volNode.SelectNodes("div").First(node => node.HasClass("volume-chapters")).SelectNodes("div"))
{ {
string number = chNode.SelectSingleNode("a").SelectSingleNode("span").InnerText.Split(" ")[^1];
string number = Regex.Match(chNode.SelectSingleNode("a").SelectSingleNode("span").InnerText,
@"[Cc]apitolo ([0-9]+).*").Groups[1].Value;
string url = chNode.SelectSingleNode("a").GetAttributeValue("href", ""); string url = chNode.SelectSingleNode("a").GetAttributeValue("href", "");
ret.Add(new Chapter(manga, null, volume, number, url)); ret.Add(new Chapter(manga, null, volume, number, url));
} }

View File

@ -0,0 +1,184 @@
using System.Net;
using System.Text.RegularExpressions;
using HtmlAgilityPack;
using Tranga.Jobs;
namespace Tranga.MangaConnectors;
public class ManhuaPlus : MangaConnector
{
public ManhuaPlus(GlobalBase clone) : base(clone, "ManhuaPlus")
{
this.downloadClient = new ChromiumDownloadClient(clone);
}
public override Manga[] GetManga(string publicationTitle = "")
{
Log($"Searching Publications. Term=\"{publicationTitle}\"");
string sanitizedTitle = string.Join(' ', Regex.Matches(publicationTitle, "[A-z]*").Where(str => str.Length > 0)).ToLower();
string requestUrl = $"https://manhuaplus.org/search?keyword={sanitizedTitle}";
RequestResult requestResult =
downloadClient.MakeRequest(requestUrl, RequestType.Default);
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
return Array.Empty<Manga>();
if (requestResult.htmlDocument is null)
return Array.Empty<Manga>();
Manga[] publications = ParsePublicationsFromHtml(requestResult.htmlDocument);
Log($"Retrieved {publications.Length} publications. Term=\"{publicationTitle}\"");
return publications;
}
private Manga[] ParsePublicationsFromHtml(HtmlDocument document)
{
if (document.DocumentNode.SelectSingleNode("//h1/../..").ChildNodes//I already want to not.
.Any(node => node.InnerText.Contains("No manga found")))
return Array.Empty<Manga>();
List<string> urls = document.DocumentNode
.SelectNodes("//h1/../..//a[contains(@href, 'https://manhuaplus.org/manga/') and contains(concat(' ',normalize-space(@class),' '),' clamp ') and not(contains(@href, '/chapter'))]")
.Select(mangaNode => mangaNode.GetAttributeValue("href", "")).ToList();
logger?.WriteLine($"Got {urls.Count} urls.");
HashSet<Manga> ret = new();
foreach (string url in urls)
{
Manga? manga = GetMangaFromUrl(url);
if (manga is not null)
ret.Add((Manga)manga);
}
return ret.ToArray();
}
public override Manga? GetMangaFromId(string publicationId)
{
return GetMangaFromUrl($"https://manhuaplus.org/manga/{publicationId}");
}
public override Manga? GetMangaFromUrl(string url)
{
Regex publicationIdRex = new(@"https:\/\/manhuaplus.org\/manga\/(.*)(\/.*)*");
string publicationId = publicationIdRex.Match(url).Groups[1].Value;
RequestResult requestResult = this.downloadClient.MakeRequest(url, RequestType.MangaInfo);
if((int)requestResult.statusCode < 300 && (int)requestResult.statusCode >= 200 && requestResult.htmlDocument is not null && requestResult.redirectedToUrl != "https://manhuaplus.org/home") //When manga doesnt exists it redirects to home
return ParseSinglePublicationFromHtml(requestResult.htmlDocument, publicationId, url);
return null;
}
private Manga ParseSinglePublicationFromHtml(HtmlDocument document, string publicationId, string websiteUrl)
{
string originalLanguage = "", status = "";
Dictionary<string, string> altTitles = new(), links = new();
HashSet<string> tags = new();
Manga.ReleaseStatusByte releaseStatus = Manga.ReleaseStatusByte.Unreleased;
HtmlNode posterNode = document.DocumentNode.SelectSingleNode("/html/body/main/div/div/div[2]/div[1]/figure/a/img");//BRUH
Regex posterRex = new(@".*(\/uploads/covers/[a-zA-Z0-9\-\._\~\!\$\&\'\(\)\*\+\,\;\=\:\@]+).*");
string posterUrl = $"https://manhuaplus.org/{posterRex.Match(posterNode.GetAttributeValue("src", "")).Groups[1].Value}";
string coverFileNameInCache = SaveCoverImageToCache(posterUrl, publicationId, RequestType.MangaCover);
HtmlNode titleNode = document.DocumentNode.SelectSingleNode("//h1");
string sortName = titleNode.InnerText.Replace("\n", "");
HtmlNode[] authorsNodes = document.DocumentNode
.SelectNodes("//a[contains(@href, 'https://manhuaplus.org/authors/')]")
.ToArray();
List<string> authors = new();
foreach (HtmlNode authorNode in authorsNodes)
authors.Add(authorNode.InnerText);
HtmlNode[] genreNodes = document.DocumentNode
.SelectNodes("//a[contains(@href, 'https://manhuaplus.org/genres/')]").ToArray();
foreach (HtmlNode genreNode in genreNodes)
tags.Add(genreNode.InnerText.Replace("\n", ""));
string yearNodeStr = document.DocumentNode
.SelectSingleNode("//aside//i[contains(concat(' ',normalize-space(@class),' '),' fa-clock ')]/../span").InnerText.Replace("\n", "");
int year = int.Parse(yearNodeStr.Split(' ')[0].Split('/')[^1]);
status = document.DocumentNode.SelectSingleNode("//aside//i[contains(concat(' ',normalize-space(@class),' '),' fa-rss ')]/../span").InnerText.Replace("\n", "");
switch (status.ToLower())
{
case "cancelled": releaseStatus = Manga.ReleaseStatusByte.Cancelled; break;
case "hiatus": releaseStatus = Manga.ReleaseStatusByte.OnHiatus; break;
case "discontinued": releaseStatus = Manga.ReleaseStatusByte.Cancelled; break;
case "complete": releaseStatus = Manga.ReleaseStatusByte.Completed; break;
case "ongoing": releaseStatus = Manga.ReleaseStatusByte.Continuing; break;
}
HtmlNode descriptionNode = document.DocumentNode
.SelectSingleNode("//div[@id='syn-target']");
string description = descriptionNode.InnerText;
Manga manga = new(sortName, authors.ToList(), description, altTitles, tags.ToArray(), posterUrl,
coverFileNameInCache, links,
year, originalLanguage, publicationId, releaseStatus, websiteUrl: websiteUrl);
AddMangaToCache(manga);
return manga;
}
public override Chapter[] GetChapters(Manga manga, string language="en")
{
Log($"Getting chapters {manga}");
RequestResult result = downloadClient.MakeRequest($"https://manhuaplus.org/manga/{manga.publicationId}", RequestType.Default);
if ((int)result.statusCode < 200 || (int)result.statusCode >= 300 || result.htmlDocument is null)
{
return Array.Empty<Chapter>();
}
HtmlNodeCollection chapterNodes = result.htmlDocument.DocumentNode.SelectNodes("//li[contains(concat(' ',normalize-space(@class),' '),' chapter ')]//a");
string[] urls = chapterNodes.Select(node => node.GetAttributeValue("href", "")).ToArray();
Regex urlRex = new (@".*\/chapter-([0-9\-]+).*");
List<Chapter> chapters = new();
foreach (string url in urls)
{
Match rexMatch = urlRex.Match(url);
string volumeNumber = "1";
string chapterNumber = rexMatch.Groups[1].Value;
string fullUrl = url;
chapters.Add(new Chapter(manga, "", volumeNumber, chapterNumber, fullUrl));
}
//Return Chapters ordered by Chapter-Number
Log($"Got {chapters.Count} chapters. {manga}");
return chapters.Order().ToArray();
}
public override HttpStatusCode DownloadChapter(Chapter chapter, ProgressToken? progressToken = null)
{
if (progressToken?.cancellationRequested ?? false)
{
progressToken.Cancel();
return HttpStatusCode.RequestTimeout;
}
Manga chapterParentManga = chapter.parentManga;
if (progressToken?.cancellationRequested ?? false)
{
progressToken.Cancel();
return HttpStatusCode.RequestTimeout;
}
Log($"Retrieving chapter-info {chapter} {chapterParentManga}");
RequestResult requestResult = this.downloadClient.MakeRequest(chapter.url, RequestType.Default);
if (requestResult.htmlDocument is null)
{
progressToken?.Cancel();
return HttpStatusCode.RequestTimeout;
}
HtmlDocument document = requestResult.htmlDocument;
HtmlNode[] images = document.DocumentNode.SelectNodes("//a[contains(concat(' ',normalize-space(@class),' '),' readImg ')]/img").ToArray();
List<string> urls = images.Select(node => node.GetAttributeValue("src", "")).ToList();
string comicInfoPath = Path.GetTempFileName();
File.WriteAllText(comicInfoPath, chapter.GetComicInfoXmlString());
return DownloadChapterImages(urls.ToArray(), chapter.GetArchiveFilePath(settings.downloadLocation), RequestType.MangaImage, comicInfoPath, progressToken:progressToken);
}
}

View File

@ -28,7 +28,7 @@ public class NotificationManagerJsonConverter : JsonConverter
case (byte)NotificationConnector.NotificationConnectorType.LunaSea: case (byte)NotificationConnector.NotificationConnectorType.LunaSea:
return new LunaSea(this._clone, jo.GetValue("id")!.Value<string>()!); return new LunaSea(this._clone, jo.GetValue("id")!.Value<string>()!);
case (byte)NotificationConnector.NotificationConnectorType.Ntfy: case (byte)NotificationConnector.NotificationConnectorType.Ntfy:
return new Ntfy(this._clone, jo.GetValue("endpoint")!.Value<string>()!, jo.GetValue("auth")!.Value<string>()!); return new Ntfy(this._clone, jo.GetValue("endpoint")!.Value<string>()!, jo.GetValue("topic")!.Value<string>()!, jo.GetValue("auth")!.Value<string>()!);
} }
throw new Exception(); throw new Exception();

View File

@ -1,34 +1,63 @@
using System.Text; using System.Text;
using System.Text.RegularExpressions;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace Tranga.NotificationConnectors; namespace Tranga.NotificationConnectors;
public class Ntfy : NotificationConnector public class Ntfy : NotificationConnector
{ {
// ReSharper disable once MemberCanBePrivate.Global // ReSharper disable twice MemberCanBePrivate.Global
public string endpoint { get; init; } public string endpoint { get; init; }
private string auth { get; init; } public string auth { get; init; }
private const string Topic = "tranga"; public string topic { get; init; }
private readonly HttpClient _client = new(); private readonly HttpClient _client = new();
[JsonConstructor] [JsonConstructor]
public Ntfy(GlobalBase clone, string endpoint, string auth) : base(clone, NotificationConnectorType.Ntfy) public Ntfy(GlobalBase clone, string endpoint, string topic, string auth) : base(clone, NotificationConnectorType.Ntfy)
{ {
if (!baseUrlRex.IsMatch(endpoint))
throw new ArgumentException("endpoint does not match pattern");
this.endpoint = endpoint; this.endpoint = endpoint;
this.topic = topic;
this.auth = auth; this.auth = auth;
} }
public Ntfy(GlobalBase clone, string endpoint, string username, string password, string? topic = null) :
this(clone, EndpointAndTopicFromUrl(endpoint)[0], topic??EndpointAndTopicFromUrl(endpoint)[1], AuthFromUsernamePassword(username, password))
{
}
private static string AuthFromUsernamePassword(string username, string password)
{
string authHeader = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}"));
string authParam = Convert.ToBase64String(Encoding.UTF8.GetBytes(authHeader)).Replace("=","");
return authParam;
}
private static string[] EndpointAndTopicFromUrl(string url)
{
string[] ret = new string[2];
if (!baseUrlRex.IsMatch(url))
throw new ArgumentException("url does not match pattern");
Regex rootUriRex = new(@"(https?:\/\/[a-zA-Z0-9-\.]+\.[a-zA-Z0-9]+)(?:\/([a-zA-Z0-9-\.]+))?.*");
Match match = rootUriRex.Match(url);
if(!match.Success)
throw new ArgumentException($"Error getting URI from provided endpoint-URI: {url}");
ret[0] = match.Groups[1].Value;
ret[1] = match.Groups[2].Success && match.Groups[2].Value.Length > 0 ? match.Groups[2].Value : "tranga";
return ret;
}
public override string ToString() public override string ToString()
{ {
return $"Ntfy {endpoint} {Topic}"; return $"Ntfy {endpoint} {topic}";
} }
public override void SendNotification(string title, string notificationText) public override void SendNotification(string title, string notificationText)
{ {
Log($"Sending notification: {title} - {notificationText}"); Log($"Sending notification: {title} - {notificationText}");
MessageData message = new(title, notificationText); MessageData message = new(title, topic, notificationText);
HttpRequestMessage request = new(HttpMethod.Post, $"{this.endpoint}?auth={this.auth}"); HttpRequestMessage request = new(HttpMethod.Post, $"{this.endpoint}?auth={this.auth}");
request.Content = new StringContent(JsonConvert.SerializeObject(message, Formatting.None), Encoding.UTF8, "application/json"); request.Content = new StringContent(JsonConvert.SerializeObject(message, Formatting.None), Encoding.UTF8, "application/json");
HttpResponseMessage response = _client.Send(request); HttpResponseMessage response = _client.Send(request);
@ -47,9 +76,9 @@ public class Ntfy : NotificationConnector
public string message { get; } public string message { get; }
public int priority { get; } public int priority { get; }
public MessageData(string title, string message) public MessageData(string title, string topic, string message)
{ {
this.topic = Topic; this.topic = topic;
this.title = title; this.title = title;
this.message = message; this.message = message;
this.priority = 3; this.priority = 3;

View File

@ -122,7 +122,7 @@ public class Server : GlobalBase
break; break;
} }
string filePath = settings.GetFullCoverPath((Manga)manga!); string filePath = manga?.coverFileNameInCache ?? "";
if (File.Exists(filePath)) if (File.Exists(filePath))
{ {
FileStream coverStream = new(filePath, FileMode.Open); FileStream coverStream = new(filePath, FileMode.Open);
@ -410,7 +410,7 @@ public class Server : GlobalBase
break; break;
case "Settings/AprilFoolsMode": case "Settings/AprilFoolsMode":
if (!requestVariables.TryGetValue("enabled", out string? aprilFoolsModeEnabledStr) || if (!requestVariables.TryGetValue("enabled", out string? aprilFoolsModeEnabledStr) ||
bool.TryParse(aprilFoolsModeEnabledStr, out bool aprilFoolsModeEnabled)) !bool.TryParse(aprilFoolsModeEnabledStr, out bool aprilFoolsModeEnabled))
{ {
SendResponse(HttpStatusCode.BadRequest, response); SendResponse(HttpStatusCode.BadRequest, response);
break; break;
@ -492,12 +492,13 @@ public class Server : GlobalBase
}else if (notificationConnectorType is NotificationConnector.NotificationConnectorType.Ntfy) }else if (notificationConnectorType is NotificationConnector.NotificationConnectorType.Ntfy)
{ {
if (!requestVariables.TryGetValue("ntfyUrl", out string? ntfyUrl) || if (!requestVariables.TryGetValue("ntfyUrl", out string? ntfyUrl) ||
!requestVariables.TryGetValue("ntfyAuth", out string? ntfyAuth)) !requestVariables.TryGetValue("ntfyUser", out string? ntfyUser)||
!requestVariables.TryGetValue("ntfyPass", out string? ntfyPass))
{ {
SendResponse(HttpStatusCode.BadRequest, response); SendResponse(HttpStatusCode.BadRequest, response);
break; break;
} }
AddNotificationConnector(new Ntfy(this, ntfyUrl, ntfyAuth)); AddNotificationConnector(new Ntfy(this, ntfyUrl, ntfyUser, ntfyPass, null));
SendResponse(HttpStatusCode.Accepted, response); SendResponse(HttpStatusCode.Accepted, response);
} }
else else
@ -534,12 +535,13 @@ public class Server : GlobalBase
}else if (notificationConnectorType is NotificationConnector.NotificationConnectorType.Ntfy) }else if (notificationConnectorType is NotificationConnector.NotificationConnectorType.Ntfy)
{ {
if (!requestVariables.TryGetValue("ntfyUrl", out string? ntfyUrl) || if (!requestVariables.TryGetValue("ntfyUrl", out string? ntfyUrl) ||
!requestVariables.TryGetValue("ntfyAuth", out string? ntfyAuth)) !requestVariables.TryGetValue("ntfyUser", out string? ntfyUser)||
!requestVariables.TryGetValue("ntfyPass", out string? ntfyPass))
{ {
SendResponse(HttpStatusCode.BadRequest, response); SendResponse(HttpStatusCode.BadRequest, response);
break; break;
} }
notificationConnector = new Ntfy(this, ntfyUrl, ntfyAuth); notificationConnector = new Ntfy(this, ntfyUrl, ntfyUser, ntfyPass, null);
} }
else else
{ {

View File

@ -24,7 +24,8 @@ public partial class Tranga : GlobalBase
new MangaKatana(this), new MangaKatana(this),
new Mangaworld(this), new Mangaworld(this),
new Bato(this), new Bato(this),
new MangaLife(this) new MangaLife(this),
new ManhuaPlus(this)
}; };
foreach(DirectoryInfo dir in new DirectoryInfo(Path.GetTempPath()).GetDirectories("trangatemp"))//Cleanup old temp folders foreach(DirectoryInfo dir in new DirectoryInfo(Path.GetTempPath()).GetDirectories("trangatemp"))//Cleanup old temp folders
dir.Delete(); dir.Delete();
@ -54,12 +55,7 @@ public partial class Tranga : GlobalBase
return _connectors; return _connectors;
} }
public Manga? GetPublicationById(string internalId) public Manga? GetPublicationById(string internalId) => GetCachedManga(internalId);
{
if (cachedPublications.Exists(publication => publication.internalId == internalId))
return cachedPublications.First(publication => publication.internalId == internalId);
return null;
}
public bool TryGetPublicationById(string internalId, out Manga? manga) public bool TryGetPublicationById(string internalId, out Manga? manga)
{ {

View File

@ -8,9 +8,11 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="GlaxArguments" Version="1.1.0" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.46" /> <PackageReference Include="HtmlAgilityPack" Version="1.11.46" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="PuppeteerSharp" Version="10.0.0" /> <PackageReference Include="PuppeteerSharp" Version="10.0.0" />
<PackageReference Include="Soenneker.Utils.String.NeedlemanWunsch" Version="2.1.301" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,4 +1,5 @@
using Logging; using Logging;
using GlaxArguments;
namespace Tranga; namespace Tranga;
@ -7,46 +8,53 @@ public partial class Tranga : GlobalBase
public static void Main(string[] args) public static void Main(string[] args)
{ {
Console.WriteLine(string.Join(' ', args)); Argument downloadLocation = new (new[] { "-d", "--downloadLocation" }, 1, "Directory to which downloaded Manga are saved");
string[]? help = GetArg(args, ArgEnum.Help); Argument workingDirectory = new (new[] { "-w", "--workingDirectory" }, 1, "Directory in which application-data is saved");
if (help is not null) Argument consoleLogger = new (new []{"-c", "--consoleLogger"}, 0, "Enables the consoleLogger");
{ Argument fileLogger = new (new []{"-f", "--fileLogger"}, 0, "Enables the fileLogger");
PrintHelp(); Argument fPath = new (new []{"-l", "--fPath"}, 1, "Log Folder Path");
return;
}
string[]? consoleLogger = GetArg(args, ArgEnum.ConsoleLogger); Argument[] arguments = new[]
string[]? fileLogger = GetArg(args, ArgEnum.FileLogger); {
string? directoryPath = GetArg(args, ArgEnum.FileLoggerPath)?[0]; downloadLocation,
workingDirectory,
consoleLogger,
fileLogger,
fPath
};
ArgumentFetcher fetcher = new (arguments);
Dictionary<Argument, string[]> fetched = fetcher.Fetch(args);
string? directoryPath = fetched.TryGetValue(fPath, out string[]? path) ? path[0] : null;
if (directoryPath is not null && !Directory.Exists(directoryPath)) if (directoryPath is not null && !Directory.Exists(directoryPath))
Directory.CreateDirectory(directoryPath); Directory.CreateDirectory(directoryPath);
List<Logger.LoggerType> enabledLoggers = new(); List<Logger.LoggerType> enabledLoggers = new();
if(consoleLogger is not null) if(fetched.ContainsKey(consoleLogger))
enabledLoggers.Add(Logger.LoggerType.ConsoleLogger); enabledLoggers.Add(Logger.LoggerType.ConsoleLogger);
if (fileLogger is not null) if (fetched.ContainsKey(fileLogger))
enabledLoggers.Add(Logger.LoggerType.FileLogger); enabledLoggers.Add(Logger.LoggerType.FileLogger);
Logger logger = new(enabledLoggers.ToArray(), Console.Out, Console.OutputEncoding, directoryPath); Logger logger = new(enabledLoggers.ToArray(), Console.Out, Console.OutputEncoding, directoryPath);
TrangaSettings? settings = null; TrangaSettings? settings = null;
string[]? downloadLocationPath = GetArg(args, ArgEnum.DownloadLocation); bool dlp = fetched.TryGetValue(downloadLocation, out string[]? downloadLocationPath);
string[]? workingDirectory = GetArg(args, ArgEnum.WorkingDirectory); bool wdp = fetched.TryGetValue(downloadLocation, out string[]? workingDirectoryPath);
if (downloadLocationPath is not null && workingDirectory is not null) if (dlp && wdp)
{ {
settings = new TrangaSettings(downloadLocationPath[0], workingDirectory[0]); settings = new TrangaSettings(downloadLocationPath![0], workingDirectoryPath![0]);
}else if (downloadLocationPath is not null) }else if (dlp)
{ {
if (settings is null) if (settings is null)
settings = new TrangaSettings(downloadLocation: downloadLocationPath[0]); settings = new TrangaSettings(downloadLocation: downloadLocationPath![0]);
else else
settings = new TrangaSettings(downloadLocation: downloadLocationPath[0], settings.workingDirectory); settings = new TrangaSettings(downloadLocation: downloadLocationPath![0], settings.workingDirectory);
}else if (workingDirectory is not null) }else if (wdp)
{ {
if (settings is null) if (settings is null)
settings = new TrangaSettings(downloadLocation: workingDirectory[0]); settings = new TrangaSettings(downloadLocation: workingDirectoryPath![0]);
else else
settings = new TrangaSettings(settings.downloadLocation, workingDirectory[0]); settings = new TrangaSettings(settings.downloadLocation, workingDirectoryPath![0]);
} }
else else
{ {
@ -58,84 +66,4 @@ public partial class Tranga : GlobalBase
Tranga _ = new (logger, settings); Tranga _ = new (logger, settings);
} }
private static void PrintHelp()
{
Console.WriteLine("Tranga-Help:");
foreach (Argument argument in Arguments.Values)
{
foreach(string name in argument.names)
Console.Write("{0} ", name);
if(argument.parameterCount > 0)
Console.Write($"<{argument.parameterCount}>");
Console.Write("\r\n {0}\r\n", argument.helpText);
}
}
/// <summary>
/// Returns an array containing the parameters for the argument.
/// </summary>
/// <param name="args">List of argument-strings</param>
/// <param name="arg">Requested parameter</param>
/// <returns>
/// If there are no parameters for an argument, returns an empty array.
/// If the argument is not found returns null.
/// </returns>
private static string[]? GetArg(string[] args, ArgEnum arg)
{
List<string> argsList = args.ToList();
List<string> ret = new();
foreach (string name in Arguments[arg].names)
{
int argIndex = argsList.IndexOf(name);
if (argIndex != -1)
{
if (Arguments[arg].parameterCount == 0)
return ret.ToArray();
for (int parameterIndex = 1; parameterIndex <= Arguments[arg].parameterCount; parameterIndex++)
{
if(argIndex + parameterIndex >= argsList.Count || args[argIndex + parameterIndex].Contains('-'))//End of arguments, or no parameter provided, when one is required
Console.WriteLine($"No parameter provided for argument {name}. -h for help.");
ret.Add(args[argIndex + parameterIndex]);
}
}
}
return ret.Any() ? ret.ToArray() : null;
}
private static readonly Dictionary<ArgEnum, Argument> Arguments = new()
{
{ ArgEnum.DownloadLocation, new(new []{"-d", "--downloadLocation"}, 1, "Directory to which downloaded Manga are saved") },
{ ArgEnum.WorkingDirectory, new(new []{"-w", "--workingDirectory"}, 1, "Directory in which application-data is saved") },
{ ArgEnum.ConsoleLogger, new(new []{"-c", "--consoleLogger"}, 0, "Enables the consoleLogger") },
{ ArgEnum.FileLogger, new(new []{"-f", "--fileLogger"}, 0, "Enables the fileLogger") },
{ ArgEnum.FileLoggerPath, new (new []{"-l", "--fPath"}, 1, "Log Folder Path" ) },
{ ArgEnum.Help, new(new []{"-h", "--help"}, 0, "Print this") }
//{ ArgEnum., new(new []{""}, 1, "") }
};
internal enum ArgEnum
{
TrangaSettings,
DownloadLocation,
WorkingDirectory,
ConsoleLogger,
FileLogger,
FileLoggerPath,
Help
}
private struct Argument
{
public string[] names { get; }
public byte parameterCount { get; }
public string helpText { get; }
public Argument(string[] names, byte parameterCount, string helpText)
{
this.names = names;
this.parameterCount = parameterCount;
this.helpText = helpText;
}
}
} }