├── .drone.yml
├── .gitattributes
├── .gitignore
├── .idea
├── .gitignore
├── compiler.xml
├── encodings.xml
├── gradle.xml
├── jarRepositories.xml
├── misc.xml
├── modules.xml
├── tachiload.iml
├── tachiload_dev.iml
└── vcs.xml
├── COMPATIBILITY.md
├── Dockerfile
├── Dockerfile.dev
├── LICENSE
├── README.md
├── app
├── build.gradle.kts
└── src
│ └── main
│ ├── kotlin
│ └── tachiload
│ │ ├── app
│ │ ├── App.kt
│ │ ├── CLI.kt
│ │ ├── ConfigItem.kt
│ │ ├── Download.kt
│ │ ├── ExtensionsIndex.kt
│ │ ├── Helpers.kt
│ │ └── SBrowser.kt
│ │ └── tachiyomi
│ │ ├── Duktape.kt
│ │ ├── annotations
│ │ └── Nsfw.kt
│ │ ├── network
│ │ ├── CloudflareInterceptor.kt
│ │ ├── CustomCookieJar.kt
│ │ ├── NetworkHelper.kt
│ │ ├── OkHttpExtensions.kt
│ │ ├── ProgressListener.kt
│ │ ├── ProgressResponseBody.kt
│ │ ├── Requests.kt
│ │ └── UserAgentInterceptor.kt
│ │ ├── source
│ │ ├── CatalogueSource.kt
│ │ ├── ConfigurableSource.kt
│ │ ├── Source.kt
│ │ ├── SourceFactory.kt
│ │ ├── model
│ │ │ ├── ChapterInfo.kt
│ │ │ ├── Filter.kt
│ │ │ ├── FilterList.kt
│ │ │ ├── Listing.kt
│ │ │ ├── MangaInfo.kt
│ │ │ ├── MangasPage.kt
│ │ │ ├── MangasPageInfo.kt
│ │ │ ├── Page.kt
│ │ │ ├── PageListEmpty.kt
│ │ │ ├── SChapter.kt
│ │ │ ├── SChapterImpl.kt
│ │ │ ├── SManga.kt
│ │ │ └── SMangaImpl.kt
│ │ └── online
│ │ │ ├── HttpSource.kt
│ │ │ └── ParsedHttpSource.kt
│ │ └── util
│ │ └── JsoupExtensions.kt
│ └── resources
│ └── .gitkeep
├── buildSrc
├── build.gradle.kts
└── src
│ └── main
│ └── kotlin
│ ├── tachiload.kotlin-application-conventions.gradle.kts
│ ├── tachiload.kotlin-common-conventions.gradle.kts
│ └── tachiload.kotlin-library-conventions.gradle.kts
├── docker-compose.yml
├── entrypoint.sh
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── scripts
├── build.py
├── compat_build.py
├── compat_deps.py
├── compat_export.py
├── compat_init.py
├── configure.py
├── prepare.py
└── test.py
└── settings.gradle.kts
/.drone.yml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: pipeline
3 | type: docker
4 | name: tachiload
5 |
6 | clone:
7 | disable: true
8 |
9 | steps:
10 | - name: build
11 | image: openjdk:8-jdk-buster
12 | environment:
13 | github_token:
14 | from_secret: github_token
15 | commands:
16 | - apt-get update && apt-get install -y python3 git libwoff1 libopus0 libwebp6 libwebpdemux2 libenchant1c2a libgudev-1.0-0 libsecret-1-0 libhyphen0 libgdk-pixbuf2.0-0 libegl1 libnotify4 libxslt1.1 libevent-2.1-6 libgles2 libvpx5 libxcomposite1 libatk1.0-0 libatk-bridge2.0-0 libepoxy0 libgtk-3-0 libharfbuzz-icu0 libnss3 libxss1 libasound2 fonts-noto-color-emoji libxtst6 libdbus-glib-1-2 libxt6 xvfb
17 | - git clone https://drosocode:$github_token@github.com/drosoCode/tachiload /app && git clone --depth 1 https://github.com/tachiyomiorg/tachiyomi-extensions /tmp/tachiyomi-extensions
18 | - mkdir -p /app/app/src/main/kotlin/tachiload/extension && mv /tmp/tachiyomi-extensions/src/* /app/app/src/main/kotlin/tachiload/extension
19 | - chmod +x /app/gradlew
20 | - cd /app/scripts && python3 compat_init.py
21 | - cd /app/scripts && python3 prepare.py
22 | - cd /app/scripts && python3 compat_deps.py
23 | - cd /app/scripts && python3 build.py
24 | - cd /app/scripts && python3 compat_build.py
25 | - cd /app/scripts && python3 test.py
26 | - cd /app/scripts && python3 compat_export.py
27 | - cd /app && git add COMPATIBILITY.md && git commit -m "[CI] Update COMPATIBILITY.md" && git push -u origin main
28 | - mkdir /drone/src/release && cp /app/app/build/libs/app-all.jar /drone/src/release/tachiload.jar
29 |
30 | - name: publish
31 | image: debian:10-slim
32 | environment:
33 | GITHUB_TOKEN:
34 | from_secret: github_token
35 | commands:
36 | - apt-get update && apt-get install -y git wget
37 | - wget https://github.com/cli/cli/releases/download/v1.4.0/gh_1.4.0_linux_amd64.deb
38 | - dpkg -i gh_1.4.0_linux_amd64.deb
39 | - gh release create ${DRONE_TAG} /drone/src/release/tachiload.jar -R drosoCode/tachiload -n "Release of $(date +'%Y/%m/%d') version ${DRONE_TAG}" -t "Tachiload V. ${DRONE_TAG}"
40 |
41 | - name: discord_notification
42 | image: appleboy/drone-discord
43 | when:
44 | status:
45 | - success
46 | - failure
47 | settings:
48 | webhook_id:
49 | from_secret: webhook_id
50 | webhook_token:
51 | from_secret: webhook_token
52 | message: >
53 | {{#success build.status}}
54 | ✅ Build #{{build.number}} of `{{repo.name}}` succeeded.
55 | 📝 Commit by {{commit.author}} on `{{commit.branch}}`:
56 | ```
57 | {{commit.message}}
58 | ```
59 | {{else}}
60 | ❌ Build #{{build.number}} of `{{repo.name}}` failed.
61 | 📝 Commit by {{commit.author}} on `{{commit.branch}}`:
62 | ```
63 | {{commit.message}}
64 | ```
65 | {{/success}}
66 |
67 | trigger:
68 | event:
69 | - tag
70 | ---
71 | kind: signature
72 | hmac: ae15c0109791cb4ab64c75d393a6c792a13934fdec4b3eb83dfb25928cfa5737
73 |
74 | ...
75 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | #
2 | # https://help.github.com/articles/dealing-with-line-endings/
3 | #
4 | # These are explicitly windows files and should use crlf
5 | *.bat text eol=crlf
6 |
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled class file
2 | *.class
3 |
4 | # Log file
5 | *.log
6 |
7 | # BlueJ files
8 | *.ctxt
9 |
10 | # Mobile Tools for Java (J2ME)
11 | .mtj.tmp/
12 |
13 | # Package Files #
14 | *.jar
15 | *.war
16 | *.nar
17 | *.ear
18 | *.zip
19 | *.tar.gz
20 | *.rar
21 |
22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
23 | hs_err_pid*
24 |
25 | .gradle
26 | build
27 | __cache
28 | .cache
29 | app/src/main/kotlin/tachiload/extension
30 | !gradle-wrapper.jar
31 |
32 | extensions.json
33 | config.json
34 | downloads/
35 | docker-compose.yml
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
22 |
23 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/tachiload.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/tachiload_dev.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/COMPATIBILITY.md:
--------------------------------------------------------------------------------
1 | # Tachiload Compatibility List
2 | ## Tested with this version: [tachiyomi-extensions](https://github.com/tachiyomiorg/tachiyomi-extensions/tree/849e4456fd50f9619fad81778c08219442a1486f)
3 | |Language | Name | Depandancies | Compilation | Tests|
4 | |---------|------|--------------|-------------|------|
5 | | all | batoto | ✔️ | ✔️ | ✔️ |
6 | | all | dragonball_multiverse | ✔️ | ❌ | ❌ |
7 | | all | ehentai | ✔️ | ❌ | ❌ |
8 | | all | hentaihand | ✔️ | ❌ | ❌ |
9 | | all | hitomi | ✔️ | ❌ | ❌ |
10 | | all | imhentai | ✔️ | ✔️ | ✔️ |
11 | | all | komga | ✔️ | ❌ | ❌ |
12 | | all | lanraragi | ✔️ | ❌ | ❌ |
13 | | all | luscious | ✔️ | ❌ | ❌ |
14 | | all | mangadex | ✔️ | ❌ | ❌ |
15 | | all | mangaplus | ✔️ | ❌ | ❌ |
16 | | all | mangatoon | ✔️ | ✔️ | ✔️ |
17 | | all | mango | ✔️ | ❌ | ❌ |
18 | | all | mmrcms | ✔️ | ❌ | ❌ |
19 | | all | myreadingmanga | ✔️ | ❌ | ❌ |
20 | | all | nhentai | ✔️ | ❌ | ❌ |
21 | | all | ninehentai | ✔️ | ❌ | ❌ |
22 | | all | ninemanga | ✔️ | ✔️ | ✔️ |
23 | | all | noisemanga | ✔️ | ✔️ | ✔️ |
24 | | all | simplyhentai | ✔️ | ❌ | ❌ |
25 | | all | thelibraryofohara | ✔️ | ✔️ | ✔️ |
26 | | all | toomics | ✔️ | ✔️ | ✔️ |
27 | | all | webtoons | ✔️ | ✔️ | ✔️ |
28 | | all | zbulu | ✔️ | ❌ | ❌ |
29 | | ar | andromedascans | ✔️ | ✔️ | ✔️ |
30 | | ar | gmanga | ✔️ | ❌ | ❌ |
31 | | ar | mangaae | ✔️ | ❌ | ❌ |
32 | | ar | mangalink | ✔️ | ✔️ | ✔️ |
33 | | ar | mangazen | ✔️ | ✔️ | ❌ |
34 | | ar | shqqaa | ✔️ | ✔️ | ✔️ |
35 | | ca | fansubscat | ✔️ | ❌ | ❌ |
36 | | de | mangatube | ✔️ | ✔️ | ✔️ |
37 | | de | wiemanga | ✔️ | ✔️ | ✔️ |
38 | | en | boommanga | ❌ | ❌ | ❌ |
39 | | en | clonemanga | ✔️ | ✔️ | ✔️ |
40 | | en | comicastle | ✔️ | ✔️ | ✔️ |
41 | | en | comicextra | ✔️ | ✔️ | ✔️ |
42 | | en | comicpunch | ✔️ | ✔️ | ✔️ |
43 | | en | dilbert | ✔️ | ❌ | ❌ |
44 | | en | doujins | ✔️ | ❌ | ❌ |
45 | | en | dynasty | ✔️ | ❌ | ❌ |
46 | | en | earlymanga | ✔️ | ✔️ | ✔️ |
47 | | en | eggporncomics | ✔️ | ✔️ | ✔️ |
48 | | en | existentialcomics | ✔️ | ✔️ | ✔️ |
49 | | en | explosm | ✔️ | ✔️ | ✔️ |
50 | | en | gunnerkriggcourt | ✔️ | ✔️ | ✔️ |
51 | | en | guya | ✔️ | ❌ | ❌ |
52 | | en | hbrowse | ✔️ | ✔️ | ✔️ |
53 | | en | hentai2read | ✔️ | ❌ | ❌ |
54 | | en | hentaifox | ✔️ | ✔️ | ✔️ |
55 | | en | hiveworks | ✔️ | ❌ | ❌ |
56 | | en | honkaiimpact3 | ❌ | ❌ | ❌ |
57 | | en | keenspot | ✔️ | ✔️ | ❌ |
58 | | en | killsixbilliondemons | ✔️ | ✔️ | ❌ |
59 | | en | latisbooks | ✔️ | ✔️ | ✔️ |
60 | | en | lemonfont | ✔️ | ✔️ | ✔️ |
61 | | en | madokami | ✔️ | ❌ | ❌ |
62 | | en | manga1s | ✔️ | ✔️ | ✔️ |
63 | | en | mangadog | ✔️ | ❌ | ❌ |
64 | | en | mangadoom | ✔️ | ✔️ | ✔️ |
65 | | en | mangaeden | ✔️ | ✔️ | ✔️ |
66 | | en | mangafast | ✔️ | ✔️ | ✔️ |
67 | | en | mangafreak | ✔️ | ❌ | ❌ |
68 | | en | mangahasu | ✔️ | ❌ | ❌ |
69 | | en | mangahere | ✔️ | ✔️ | ✔️ |
70 | | en | mangahub | ✔️ | ✔️ | ✔️ |
71 | | en | mangajar | ✔️ | ✔️ | ✔️ |
72 | | en | mangakatana | ✔️ | ✔️ | ✔️ |
73 | | en | mangalife | ✔️ | ❌ | ❌ |
74 | | en | mangalinkz | ✔️ | ❌ | ❌ |
75 | | en | mangamainac | ✔️ | ✔️ | ❌ |
76 | | en | mangamutiny | ✔️ | ❌ | ❌ |
77 | | en | mangaowl | ✔️ | ✔️ | ✔️ |
78 | | en | mangapark | ✔️ | ❌ | ❌ |
79 | | en | mangapill | ✔️ | ✔️ | ✔️ |
80 | | en | mangareader | ✔️ | ✔️ | ❌ |
81 | | en | mangarockes | ✔️ | ✔️ | ✔️ |
82 | | en | mangasail | ✔️ | ✔️ | ✔️ |
83 | | en | mangasee | ✔️ | ❌ | ❌ |
84 | | en | mangatown | ✔️ | ✔️ | ✔️ |
85 | | en | manhwamanga | ✔️ | ✔️ | ✔️ |
86 | | en | manhwatime | ✔️ | ✔️ | ❌ |
87 | | en | manmanga | ✔️ | ✔️ | ✔️ |
88 | | en | merakiscans | ✔️ | ✔️ | ✔️ |
89 | | en | multporn | ✔️ | ❌ | ❌ |
90 | | en | myhentaicomics | ✔️ | ✔️ | ✔️ |
91 | | en | myhentaigallery | ✔️ | ❌ | ❌ |
92 | | en | naniscans | ✔️ | ❌ | ❌ |
93 | | en | nhentaicom | ❌ | ❌ | ❌ |
94 | | en | nineanime | ✔️ | ✔️ | ✔️ |
95 | | en | nuxscans | ✔️ | ✔️ | ✔️ |
96 | | en | nyahentai | ✔️ | ✔️ | ✔️ |
97 | | en | oglaf | ✔️ | ✔️ | ✔️ |
98 | | en | patchfriday | ✔️ | ✔️ | ✔️ |
99 | | en | perveden | ✔️ | ✔️ | ✔️ |
100 | | en | pururin | ✔️ | ✔️ | ✔️ |
101 | | en | questionablecontent | ✔️ | ❌ | ❌ |
102 | | en | rainofsnow | ✔️ | ✔️ | ✔️ |
103 | | en | readcomiconline | ✔️ | ❌ | ❌ |
104 | | en | readjump | ✔️ | ✔️ | ❌ |
105 | | en | readm | ✔️ | ✔️ | ✔️ |
106 | | en | readmangatoday | ✔️ | ✔️ | ✔️ |
107 | | en | readmanhwa | ✔️ | ❌ | ❌ |
108 | | en | schlockmercenary | ✔️ | ✔️ | ✔️ |
109 | | en | silentmangaaudition | ✔️ | ✔️ | ✔️ |
110 | | en | sleepypandascans | ✔️ | ✔️ | ❌ |
111 | | en | swordscomic | ✔️ | ✔️ | ✔️ |
112 | | en | tapastic | ✔️ | ❌ | ❌ |
113 | | en | tcbscans | ✔️ | ✔️ | ✔️ |
114 | | en | thepropertyofhate | ✔️ | ✔️ | ❌ |
115 | | en | timelessleaf | ✔️ | ✔️ | ✔️ |
116 | | en | tsumino | ✔️ | ❌ | ❌ |
117 | | en | vgperson | ✔️ | ❌ | ❌ |
118 | | en | vizshonenjump | ✔️ | ❌ | ❌ |
119 | | en | webcomics | ✔️ | ✔️ | ✔️ |
120 | | en | webnovel | ✔️ | ✔️ | ✔️ |
121 | | en | wutopia | ✔️ | ❌ | ❌ |
122 | | en | xkcd | ✔️ | ✔️ | ✔️ |
123 | | es | doujinyang | ✔️ | ❌ | ❌ |
124 | | es | heavenmanga | ✔️ | ✔️ | ✔️ |
125 | | es | ikuhentai | ✔️ | ✔️ | ✔️ |
126 | | es | inmanga | ✔️ | ✔️ | ✔️ |
127 | | es | kumanga | ✔️ | ❌ | ❌ |
128 | | es | lectormanga | ✔️ | ❌ | ❌ |
129 | | es | mangamx | ✔️ | ❌ | ❌ |
130 | | es | tmohentai | ✔️ | ❌ | ❌ |
131 | | es | tumangaonline | ✔️ | ❌ | ❌ |
132 | | es | vcpvmp | ✔️ | ✔️ | ✔️ |
133 | | fr | japscan | ✔️ | ❌ | ❌ |
134 | | fr | kangaryu | ✔️ | ✔️ | ✔️ |
135 | | fr | lirescan | ✔️ | ✔️ | ✔️ |
136 | | fr | mangakawaii | ✔️ | ❌ | ❌ |
137 | | fr | scantrad | ✔️ | ❌ | ❌ |
138 | | fr | scantradunion | ✔️ | ✔️ | ❌ |
139 | | id | KomikFan | ❌ | ❌ | ❌ |
140 | | id | bacakomik | ✔️ | ✔️ | ✔️ |
141 | | id | bacamanga | ✔️ | ✔️ | ✔️ |
142 | | id | comicfx | ✔️ | ✔️ | ✔️ |
143 | | id | komiku | ✔️ | ✔️ | ❌ |
144 | | id | maidmanga | ✔️ | ✔️ | ✔️ |
145 | | id | mangaindo | ✔️ | ✔️ | ✔️ |
146 | | id | mangaku | ✔️ | ❌ | ❌ |
147 | | id | mangayu | ✔️ | ❌ | ❌ |
148 | | id | manhuaid | ✔️ | ✔️ | ✔️ |
149 | | id | neumanga | ✔️ | ✔️ | ❌ |
150 | | id | nyanfm | ✔️ | ✔️ | ✔️ |
151 | | it | digitalteam | ✔️ | ❌ | ❌ |
152 | | it | hentaifantasy | ✔️ | ✔️ | ✔️ |
153 | | it | mangaeden | ✔️ | ✔️ | ✔️ |
154 | | it | mangaworld | ✔️ | ✔️ | ✔️ |
155 | | it | novelleleggere | ✔️ | ✔️ | ❌ |
156 | | it | perveden | ✔️ | ✔️ | ✔️ |
157 | | ja | mangaraw | ✔️ | ✔️ | ❌ |
158 | | ja | nikkangecchan | ✔️ | ✔️ | ✔️ |
159 | | ja | rawdevart | ✔️ | ✔️ | ✔️ |
160 | | ja | senmanga | ✔️ | ❌ | ❌ |
161 | | ja | shonenjumpplus | ✔️ | ❌ | ❌ |
162 | | ko | jmana | ✔️ | ❌ | ❌ |
163 | | ko | navercomic | ✔️ | ❌ | ❌ |
164 | | ko | newtoki | ✔️ | ❌ | ❌ |
165 | | ko | toonkor | ✔️ | ❌ | ❌ |
166 | | pt | bruttal | ✔️ | ❌ | ❌ |
167 | | pt | centraldemangas | ✔️ | ❌ | ❌ |
168 | | pt | goldenmangas | ✔️ | ✔️ | ❌ |
169 | | pt | hipercool | ✔️ | ❌ | ❌ |
170 | | pt | hqdragon | ✔️ | ✔️ | ✔️ |
171 | | pt | hqnow | ✔️ | ✔️ | ✔️ |
172 | | pt | mangahost | ✔️ | ❌ | ❌ |
173 | | pt | mangasproject | ✔️ | ❌ | ❌ |
174 | | pt | mangayabu | ✔️ | ✔️ | ✔️ |
175 | | pt | mundohentai | ✔️ | ✔️ | ❌ |
176 | | pt | mundomangakun | ✔️ | ✔️ | ✔️ |
177 | | pt | saikaiscan | ✔️ | ✔️ | ✔️ |
178 | | pt | socialcomics | ✔️ | ❌ | ❌ |
179 | | pt | supermangas | ✔️ | ❌ | ❌ |
180 | | pt | taosect | ✔️ | ✔️ | ✔️ |
181 | | pt | tsukimangas | ✔️ | ❌ | ❌ |
182 | | pt | unionmangas | ✔️ | ❌ | ❌ |
183 | | pt | yesmangas | ✔️ | ✔️ | ✔️ |
184 | | pt | zinnes | ✔️ | ✔️ | ✔️ |
185 | | ru | acomics | ✔️ | ✔️ | ✔️ |
186 | | ru | allhentai | ✔️ | ❌ | ❌ |
187 | | ru | comx | ✔️ | ❌ | ❌ |
188 | | ru | desu | ✔️ | ✔️ | ✔️ |
189 | | ru | henchan | ✔️ | ❌ | ❌ |
190 | | ru | libmanga | ✔️ | ❌ | ❌ |
191 | | ru | mangachan | ✔️ | ❌ | ❌ |
192 | | ru | mangaclub | ✔️ | ❌ | ❌ |
193 | | ru | mangahub | ✔️ | ❌ | ❌ |
194 | | ru | mangaonlinebiz | ✔️ | ✔️ | ✔️ |
195 | | ru | mintmanga | ✔️ | ❌ | ❌ |
196 | | ru | nudemoon | ✔️ | ✔️ | ✔️ |
197 | | ru | readmanga | ✔️ | ❌ | ❌ |
198 | | ru | remanga | ✔️ | ❌ | ❌ |
199 | | ru | risensteam | ✔️ | ✔️ | ✔️ |
200 | | ru | selfmanga | ✔️ | ✔️ | ✔️ |
201 | | ru | yaoichan | ✔️ | ❌ | ❌ |
202 | | th | nekopost | ✔️ | ✔️ | ❌ |
203 | | tr | MangaDenizi | ❌ | ❌ | ❌ |
204 | | tr | mangaship | ✔️ | ❌ | ❌ |
205 | | tr | serimanga | ✔️ | ✔️ | ✔️ |
206 | | vi | academyvn | ✔️ | ✔️ | ✔️ |
207 | | vi | blogtruyen | ✔️ | ✔️ | ✔️ |
208 | | vi | hentaivn | ✔️ | ✔️ | ❌ |
209 | | vi | iutruyentranh | ✔️ | ❌ | ❌ |
210 | | vi | medoctruyentranh | ✔️ | ✔️ | ✔️ |
211 | | vi | ngonphong | ✔️ | ✔️ | ✔️ |
212 | | vi | truyenqq | ✔️ | ❌ | ❌ |
213 | | vi | truyentranhlh | ✔️ | ✔️ | ✔️ |
214 | | zh | bainianmanga | ✔️ | ✔️ | ✔️ |
215 | | zh | bh3 | ✔️ | ✔️ | ❌ |
216 | | zh | comico | ✔️ | ✔️ | ✔️ |
217 | | zh | copymanga | ✔️ | ❌ | ❌ |
218 | | zh | dmzj | ✔️ | ❌ | ❌ |
219 | | zh | gufengmh | ✔️ | ❌ | ❌ |
220 | | zh | hanhankuman | ✔️ | ❌ | ❌ |
221 | | zh | jinmantiantang | ✔️ | ❌ | ❌ |
222 | | zh | kuaikanmanhua | ✔️ | ✔️ | ✔️ |
223 | | zh | mangabz | ✔️ | ❌ | ❌ |
224 | | zh | manhuadb | ✔️ | ✔️ | ✔️ |
225 | | zh | manhuadui | ✔️ | ✔️ | ✔️ |
226 | | zh | manhuagui | ✔️ | ❌ | ❌ |
227 | | zh | manhuaren | ✔️ | ❌ | ❌ |
228 | | zh | onemanhua | ✔️ | ❌ | ❌ |
229 | | zh | pufei | ✔️ | ❌ | ❌ |
230 | | zh | qimiaomh | ✔️ | ✔️ | ❌ |
231 | | zh | qiximh | ✔️ | ❌ | ❌ |
232 | | zh | tohomh123 | ✔️ | ✔️ | ❌ |
233 | | zh | wnacg | ✔️ | ✔️ | ✔️ |
234 | | zh | wuqimanga | ✔️ | ❌ | ❌ |
235 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM openjdk:8-jdk-buster AS builder
2 |
3 | WORKDIR /app
4 |
5 | RUN apt-get update && apt-get install -y python3 git
6 | ADD . /app
7 |
8 | RUN git clone --depth 1 https://github.com/tachiyomiorg/tachiyomi-extensions /tmp/tachiyomi-extensions \
9 | && mkdir -p /app/app/src/main/kotlin/tachiload/extension \
10 | && mv /tmp/tachiyomi-extensions/src/* /app/app/src/main/kotlin/tachiload/extension \
11 | && cd /app/scripts && python3 prepare.py \
12 | && python3 build.py
13 |
14 | FROM openjdk:8-jre-slim
15 |
16 | WORKDIR /app
17 |
18 | RUN apt-get update && apt-get install -y --no-install-recommends python3 python3-pip whiptail \
19 | libwoff1 libopus0 libwebp6 libwebpdemux2 libenchant1c2a libgudev-1.0-0 libsecret-1-0 libhyphen0 libgdk-pixbuf2.0-0 libegl1 libnotify4 libxslt1.1 libevent-2.1-6 libgles2 libvpx5 libxcomposite1 libatk1.0-0 libatk-bridge2.0-0 libepoxy0 libgtk-3-0 libharfbuzz-icu0 libnss3 libxss1 libasound2 fonts-noto-color-emoji libxtst6 libdbus-glib-1-2 libxt6 xvfb \
20 | && pip3 install whiptail-dialogs
21 | COPY --from=builder /app/entrypoint.sh /app/entrypoint.sh
22 | COPY --from=builder /app/scripts/configure.py /app/configure.py
23 | COPY --from=builder /app/app/build/libs/app-all.jar /app/app.jar
24 | ENTRYPOINT ["/app/entrypoint.sh"]
25 |
--------------------------------------------------------------------------------
/Dockerfile.dev:
--------------------------------------------------------------------------------
1 | FROM openjdk:8-jdk-buster AS builder
2 |
3 | WORKDIR /app
4 | RUN apt-get update && apt-get install -y python3 git \
5 | libwoff1 libopus0 libwebp6 libwebpdemux2 libenchant1c2a libgudev-1.0-0 libsecret-1-0 libhyphen0 libgdk-pixbuf2.0-0 libegl1 libnotify4 libxslt1.1 libevent-2.1-6 libgles2 libvpx5 libxcomposite1 libatk1.0-0 libatk-bridge2.0-0 libepoxy0 libgtk-3-0 libharfbuzz-icu0 libnss3 libxss1 libasound2 fonts-noto-color-emoji libxtst6 libdbus-glib-1-2 libxt6 xvfb
6 | RUN git clone --depth 1 https://github.com/tachiyomiorg/tachiyomi-extensions /tmp/tachiyomi-extensions
7 |
8 | ADD . /app
9 | RUN rm -rf /app/app/src/main/kotlin/tachiload/extension \
10 | && mkdir -p /app/app/src/main/kotlin/tachiload/extension \
11 | && mv /tmp/tachiyomi-extensions/src/* /app/app/src/main/kotlin/tachiload/extension \
12 | && cd /app/scripts \
13 | && python3 compat_init.py \
14 | && python3 prepare.py \
15 | && python3 compat_deps.py \
16 | && python3 build.py \
17 | && python3 compat_build.py \
18 | && python3 test.py \
19 | && python3 compat_export.py
20 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 drosoCode
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Tachiload
2 |
3 | ## Manga downloader based on [tachiyomi-extensions](https://github.com/tachiyomiorg/tachiyomi-extensions)
4 |
5 | This software is compatible with most of the extensions of [Tachiyomi](https://github.com/tachiyomiorg/tachiyomi), see the compatibility list [here](COMPATIBILITY.md)
6 |
7 | ## Usage
8 | - set your config file path and your download path in the `docker-compose.yml` file
9 | - (optionnal) edit the environment variables (TACHILOAD_CRON, TACHILOAD_WEBHOOK, TACHILOAD_CONFIG, TACHILOAD_DOWNLOAD) in the `docker-compose.yml` file (they correspond to the cli args)
10 | - run `docker-compose build` to build the container and `docker-compose up -d` to run it
11 | - to use the configuration utility, run `docker-compose exec tachiload /app/configure.py` and restart the container to apply changes
12 |
13 | ## CLI
14 | |Name | Short Name | Description | Example|
15 | |--------|------|------------|-------|
16 | | --cron [pattern] | -c | Cron pattern to run the download, if not specified, the download will run once and the program will exit | --cron "0 */6 * * *" |
17 | | --webhook [url] | -wh | Webhook url to receive notifications (discord) | --webhook "https://discord.com/api/webhooks/ID/TOKEN" |
18 | | --configPath [path] | -cfg | Path to the configuration file (default: ./config.json) | --configPath "/app/config.json" |
19 | | --downloadPath [path] | -dl | Path to the download directory (default: ./downloads) | --downloadPath "/app/downloads" |
20 | | --extensions | | (DEV) return a json object of all available extensions | --extensions |
21 | | --search [name] [ext1_lang] [ext1_name] [ext2_lang] [ext2_name] | | (DEV) return a json list of results for a search | --search "solo leveling" "en" "mangasee" |
22 | | --download [json dict] | | (DEV) download the specified manga | --download '{"extension": "mangasee", "language": "en", "data": {"url": "/manga/Akame-Ga-Kiru", "title": "Akame ga Kiru!", "status": 0, "thumbnail_url": "https://cover.nep.li/cover/Akame-Ga-Kiru.jpg", "initialized": false}}' |
23 |
24 | Thanks to [ClementD64](https://github.com/ClementD64) for his help on this project
25 |
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * This file was generated by the Gradle 'init' task.
3 | */
4 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
5 |
6 | plugins {
7 | id("org.jetbrains.kotlin.jvm")
8 | id("tachiload.kotlin-application-conventions")
9 | id("com.github.johnrengelman.shadow") version "6.1.0"
10 | }
11 |
12 | repositories {
13 | google()
14 | maven { url = uri("https://jitpack.io") }
15 | maven { url = uri("https://kotlin.bintray.com/kotlinx") }
16 | mavenCentral()
17 | jcenter()
18 | }
19 |
20 | dependencies {
21 | // Use the Kotlin JDK 8 standard library.
22 | implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.4.20"))
23 | implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.20")
24 |
25 | // This dependency is used by the application.
26 | implementation("com.google.guava:guava:29.0-jre")
27 | compileOnly("com.github.salomonbrys.kotson:kotson:2.5.0")
28 |
29 | // HTML parser
30 | implementation("org.jsoup:jsoup:1.13.1")
31 |
32 | // ReactiveX
33 | implementation("io.reactivex:rxandroid:1.2.1")
34 | implementation("io.reactivex:rxjava:1.3.8")
35 | implementation("com.jakewharton.rxrelay:rxrelay:1.2.0")
36 | implementation("com.github.pwittchen:reactivenetwork:0.13.0")
37 |
38 | // Network client
39 | val okhttpVersion = "4.10.0-RC1"
40 | //val okhttpVersion = "3.12.0"
41 | implementation("com.squareup.okhttp3:okhttp:$okhttpVersion")
42 | implementation("com.squareup.okhttp3:logging-interceptor:$okhttpVersion")
43 | implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:$okhttpVersion")
44 | implementation("com.squareup.okio:okio:2.9.0")
45 |
46 | // Dependency injection
47 | implementation("com.github.inorichi.injekt:injekt-core:65b0440")
48 |
49 | // JSON
50 | val kotlinSerializationVersion = "1.0.1"
51 | implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinSerializationVersion")
52 | implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:$kotlinSerializationVersion")
53 | implementation("com.google.code.gson:gson:2.8.6")
54 | implementation("com.github.salomonbrys.kotson:kotson:2.5.0")
55 | implementation("org.json:json:20201115")
56 |
57 | // Coroutines
58 | val coroutinesVersion = "1.4.2"
59 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
60 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion")
61 |
62 | // CRON
63 | implementation("it.sauronsoftware.cron4j:cron4j:2.2.5")
64 |
65 | // Args Parser
66 | implementation("org.jetbrains.kotlinx:kotlinx-cli:0.3")
67 |
68 | // Alternative to Webview for cloudflare interceptor
69 | implementation("com.microsoft.playwright:playwright:0.171.0")
70 | }
71 |
72 | application {
73 | // Define the main class for the application.
74 | mainClass.set("tachiload.app.AppKt")
75 | @Suppress("DEPRECATION")
76 | mainClassName = "tachiload.app.AppKt"
77 | }
78 |
79 |
80 | tasks {
81 | // See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers)
82 | withType {
83 | kotlinOptions.freeCompilerArgs += listOf(
84 | "-Xopt-in=kotlin.Experimental",
85 | "-Xopt-in=kotlin.RequiresOptIn",
86 | "-Xuse-experimental=kotlin.ExperimentalStdlibApi",
87 | "-Xuse-experimental=kotlinx.coroutines.FlowPreview",
88 | "-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi",
89 | "-Xuse-experimental=kotlinx.coroutines.InternalCoroutinesApi",
90 | "-Xuse-experimental=kotlinx.serialization.ExperimentalSerializationApi"
91 | )
92 | kotlinOptions {
93 | jvmTarget = JavaVersion.VERSION_1_8.toString()
94 | }
95 | }
96 | }
--------------------------------------------------------------------------------
/app/src/main/kotlin/tachiload/app/App.kt:
--------------------------------------------------------------------------------
1 | package tachiload.app
2 |
3 | import kotlin.system.exitProcess
4 | import it.sauronsoftware.cron4j.Scheduler
5 | import java.time.LocalDateTime
6 | import kotlinx.cli.*
7 |
8 |
9 | fun main(args: Array) {
10 |
11 | if(args.isNotEmpty() && (args[0] == "--extensions" || args[0] == "--search" || args[0] == "--download"))
12 | {
13 | CLI("./config.json", "./downloads", args)
14 | }
15 | else
16 | {
17 | val parser = ArgParser("Tachiload")
18 | val cron by parser.option(ArgType.String, shortName = "c", description = "Cron Expression")
19 | val webhook by parser.option(ArgType.String, shortName = "wh", description = "Webhook URL (discord) for notification")
20 | val configPath by parser.option(ArgType.String, shortName = "cfg", description = "Path to configuration file").default("./config.json")
21 | val downloadPath by parser.option(ArgType.String, shortName = "dl", description = "Path to download directory").default("./downloads")
22 | parser.parse(args)
23 |
24 | if(cron != null)
25 | {
26 | println("========================================== Tachiload [CRON] ==========================================")
27 | val s = Scheduler()
28 | s.schedule(cron, Runnable() {
29 | println("Running update at:"+LocalDateTime.now())
30 | Download(configPath, downloadPath, webhook).update()
31 | })
32 | s.start()
33 | try {
34 | Thread.sleep(Long.MAX_VALUE)
35 | } catch (e: InterruptedException) {
36 | }
37 | }
38 | else
39 | {
40 | println("========================================== Tachiload ==========================================")
41 | Download(configPath, downloadPath, webhook).update()
42 | }
43 | }
44 | exitProcess(0)
45 | }
46 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/tachiload/app/CLI.kt:
--------------------------------------------------------------------------------
1 | package tachiload.app
2 |
3 | import com.google.gson.Gson
4 | import com.google.gson.reflect.TypeToken
5 | import tachiload.tachiyomi.source.model.FilterList
6 | import tachiload.tachiyomi.source.model.SMangaImpl
7 |
8 | class CLI(private val configPath: String, private val downloadPath: String, private val args: Array) {
9 | init {
10 | if (this.args[0] == "--extensions" && this.args.size == 1) {
11 | print(this.extList())
12 | } else if(this.args[0] == "--search" && this.args.size >= 4) {
13 | print(this.search())
14 | } else if(this.args[0] == "--download" && this.args.size == 2) {
15 | print(this.download())
16 | } else {
17 | print("Error")
18 | }
19 | }
20 |
21 | private fun extList(): String {
22 | return this::class.java.classLoader.getResource("extensions.json").readText()
23 | }
24 |
25 | private fun search(): String {
26 | //args: --search [name] [lang_ext1] [name_ext1] [lang_ext2] [name_ext2]
27 | var lst = mutableListOf()
28 | for (i in 2 until args.size step 2)
29 | {
30 | val ext = Helpers.loadExtension(Helpers.loadIndex(), this.args[i], this.args[i+1]) ?: return "Error"
31 |
32 | var page = 1
33 | var nextPage = true
34 | while(nextPage) {
35 | val value = ext.fetchSearchManga(page, args[1], FilterList()).toBlocking().first()
36 | for (m in value.mangas) {
37 | lst.add(ConfigItem(this.args[i+1], this.args[i], m as SMangaImpl))
38 | }
39 | if(value.hasNextPage)
40 | page++
41 | else
42 | nextPage = false
43 | }
44 | }
45 | return Gson().toJson(lst)
46 | }
47 |
48 | private fun download(): String {
49 | val item: ConfigItem = Gson().fromJson(
50 | args[1],
51 | object: TypeToken() {}.type
52 | )
53 |
54 | val dl = Download(this.configPath, this.downloadPath)
55 | dl.downloadNewChapters(
56 | Helpers.loadExtension(Helpers.loadIndex(), item.language, item.extension),
57 | item.data,
58 | dl.getLatestChapter(item.data.title)
59 | )
60 | return ""
61 | }
62 |
63 | }
--------------------------------------------------------------------------------
/app/src/main/kotlin/tachiload/app/ConfigItem.kt:
--------------------------------------------------------------------------------
1 | package tachiload.app
2 |
3 | import tachiload.tachiyomi.source.model.SMangaImpl
4 |
5 | data class ConfigItem(
6 | val extension: String,
7 | val language: String,
8 | val data: SMangaImpl
9 | )
10 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/tachiload/app/Download.kt:
--------------------------------------------------------------------------------
1 | package tachiload.app
2 |
3 | import tachiload.tachiyomi.source.model.SChapter
4 | import tachiload.tachiyomi.source.model.SManga
5 | import tachiload.tachiyomi.source.online.HttpSource
6 | import java.io.File
7 | import java.io.FileOutputStream
8 | import okhttp3.OkHttpClient
9 | import okhttp3.MediaType.Companion.toMediaTypeOrNull
10 | import okhttp3.Request
11 | import okhttp3.RequestBody.Companion.toRequestBody
12 |
13 |
14 | class Download(private val configPath: String, private val downloadPath: String, private val webhook: String?=null) {
15 |
16 | private fun downloadChapter(extension: HttpSource, chapter: SChapter, title: String, number: Int) {
17 | println(" Downloading chapter $number")
18 | extension.fetchPageList(chapter).subscribe { pages ->
19 | for ((i, p) in pages.withIndex()) {
20 | extension.fetchImage(p).subscribe { value ->
21 | val p = this.downloadPath+"/$title/$number/"
22 | val directory = File(p)
23 | if (!directory.exists()) {
24 | directory.mkdirs()
25 | }
26 | val fos = FileOutputStream("$p$i.png")
27 | fos.write(value.body!!.bytes())
28 | fos.close()
29 | }
30 | }
31 | }
32 | }
33 |
34 | fun downloadNewChapters(extension: HttpSource?, manga: SManga, latestChapter: Int) {
35 | if (extension == null)
36 | return
37 | println(" Found $latestChapter chapters")
38 | extension.fetchChapterList(manga).subscribe { chapters ->
39 | if(latestChapter < 0)
40 | {
41 | for ((i, c) in chapters.withIndex()) {
42 | this.downloadChapter(extension, c, manga.title, chapters.size-i)
43 | }
44 | notify("Downloaded "+chapters.size+" new chapters for "+manga.title)
45 | }
46 | else if (chapters.size - latestChapter > 0)
47 | {
48 | for (i in 0 until chapters.size-latestChapter) {
49 | this.downloadChapter(extension, chapters[i], manga.title, chapters.size-i)
50 | }
51 | notify("Downloaded "+(chapters.size - latestChapter) +" new chapters ["+ (latestChapter+1) +"-"+ chapters.size +"] for "+manga.title)
52 | }
53 | }
54 | }
55 |
56 | fun getLatestChapter(title: String): Int
57 | {
58 | val dir = File(this.downloadPath+"/"+title)
59 | return if (!dir.exists()) {
60 | -1
61 | } else {
62 | val lst = dir.list()
63 | var i = -1
64 | for (a in lst) {
65 | if (a.toInt() > i)
66 | i = a.toInt()
67 | }
68 | i
69 | }
70 | }
71 |
72 | private fun notify(text: String)
73 | {
74 | if(this.webhook != null) {
75 | OkHttpClient().newCall(
76 | Request.Builder()
77 | .url(this.webhook)
78 | .post("{\"content\": \"$text\"}".toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull()))
79 | .build()
80 | ).execute()
81 | }
82 | }
83 |
84 | fun update()
85 | {
86 | val index = Helpers.loadIndex()
87 | val items = Helpers.loadItems(configPath)
88 |
89 | for (manga in items)
90 | {
91 | println("Updating " + manga.data.title + " ...")
92 | this.downloadNewChapters(
93 | Helpers.loadExtension(index, manga.language, manga.extension),
94 | manga.data,
95 | this.getLatestChapter(manga.data.title)
96 | )
97 | }
98 | }
99 | }
--------------------------------------------------------------------------------
/app/src/main/kotlin/tachiload/app/ExtensionsIndex.kt:
--------------------------------------------------------------------------------
1 | package tachiload.app
2 |
3 | data class ExtensionsIndex(
4 | val libVersion: String,
5 | val extVersionCode: String,
6 | val extClass: String,
7 | val name: String
8 | )
--------------------------------------------------------------------------------
/app/src/main/kotlin/tachiload/app/Helpers.kt:
--------------------------------------------------------------------------------
1 | package tachiload.app
2 |
3 | import com.google.gson.Gson
4 | import com.google.gson.reflect.TypeToken
5 | import tachiload.tachiyomi.source.SourceFactory
6 | import tachiload.tachiyomi.source.online.HttpSource
7 | import java.nio.file.Files
8 | import java.nio.file.Paths
9 |
10 | class Helpers {
11 | companion object {
12 | fun loadExtension(index: Map>, lang: String, ext: String): HttpSource? {
13 | val extClass = index[lang]!!.find { it.name == ext }?.extClass
14 | val instance = this.javaClass.classLoader.loadClass("tachiload.extension.$lang.$ext.src.eu.kanade.tachiyomi.extension.$lang.$ext$extClass")
15 | .getDeclaredConstructor()
16 | .newInstance()
17 |
18 | when (instance) {
19 | is HttpSource -> return instance
20 | is SourceFactory -> return instance.createSources()[0] as HttpSource
21 | }
22 | return null
23 | }
24 |
25 | fun loadIndex(): Map> {
26 | return Gson().fromJson(
27 | this::class.java.classLoader.getResource("extensions.json").readText(),
28 | object: TypeToken