├── .github
└── workflows
│ └── release.yml
├── .gitignore
├── LICENSE
├── README.md
├── build.gradle.kts
├── extension-app
├── .gitignore
├── build.gradle.kts
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ ├── com
│ │ └── heyanle
│ │ │ └── easybangumi_extension
│ │ │ ├── anfun
│ │ │ ├── AnfunDetailedComponent.kt
│ │ │ ├── AnfunListComponent.kt
│ │ │ ├── AnfunPageComponent.kt
│ │ │ ├── AnfunSearchComponent.kt
│ │ │ └── AnfunSource.kt
│ │ │ ├── anim
│ │ │ ├── AnimOneInfo.kt
│ │ │ ├── AnimOneSource.kt
│ │ │ ├── AnimPageComponent.kt
│ │ │ └── DataSource.kt
│ │ │ └── ggl
│ │ │ ├── GGLComponent.kt
│ │ │ ├── GGLDetailedComponent.kt
│ │ │ ├── GGLListComponent.kt
│ │ │ ├── GGLPlayComponent.kt
│ │ │ └── GGLSource.kt
│ ├── io
│ │ └── github
│ │ │ ├── easybangumiorg
│ │ │ └── source
│ │ │ │ └── aio
│ │ │ │ ├── AIOHtpHelper.kt
│ │ │ │ ├── OkHttp.kt
│ │ │ │ ├── SourceResult.kt
│ │ │ │ ├── String.kt
│ │ │ │ ├── auete
│ │ │ │ ├── AueteDetail.kt
│ │ │ │ ├── AuetePage.kt
│ │ │ │ ├── AuetePlay.kt
│ │ │ │ ├── AueteSearch.kt
│ │ │ │ ├── AueteSource.kt
│ │ │ │ └── Common.kt
│ │ │ │ ├── changzhang
│ │ │ │ ├── ChangZhangDetailPage.kt
│ │ │ │ ├── ChangZhangPage.kt
│ │ │ │ ├── ChangZhangPlayPage.kt
│ │ │ │ ├── ChangZhangSearchPage.kt
│ │ │ │ └── ChangZhangSource.kt
│ │ │ │ ├── fengche
│ │ │ │ ├── Common.kt
│ │ │ │ ├── FengCheDetail.kt
│ │ │ │ ├── FengCheHostUrlHelper.kt
│ │ │ │ ├── FengChePage.kt
│ │ │ │ ├── FengChePlay.kt
│ │ │ │ ├── FengChePrefer.kt
│ │ │ │ ├── FengCheSearch.kt
│ │ │ │ └── FengCheSource.kt
│ │ │ │ ├── libvio
│ │ │ │ ├── LibVioDetail.kt
│ │ │ │ ├── LibVioPage.kt
│ │ │ │ ├── LibVioPlay.kt
│ │ │ │ ├── LibVioSearch.kt
│ │ │ │ └── LibVioSource.kt
│ │ │ │ └── xigua
│ │ │ │ ├── Common.kt
│ │ │ │ ├── XiGuaPage.kt
│ │ │ │ ├── XiguaDetail.kt
│ │ │ │ ├── XiguaPlay.kt
│ │ │ │ ├── XiguaSearch.kt
│ │ │ │ └── XiguaSource.kt
│ │ │ └── peacefulprogram
│ │ │ ├── easybangumi_mikudm
│ │ │ ├── MikudmApiSource.kt
│ │ │ ├── MikudmDetailComponent.kt
│ │ │ ├── MikudmPageComponent.kt
│ │ │ ├── MikudmPlayComponent.kt
│ │ │ ├── MikudmPreferenceComponent.kt
│ │ │ ├── MikudmSearchComponent.kt
│ │ │ └── MikudmUtil.kt
│ │ │ ├── easybangumi_mxdm
│ │ │ ├── MxdmApiSource.kt
│ │ │ ├── MxdmDetailComponent.kt
│ │ │ ├── MxdmPageComponent.kt
│ │ │ ├── MxdmPlayComponent.kt
│ │ │ ├── MxdmPreferenceComponent.kt
│ │ │ ├── MxdmSearchComponent.kt
│ │ │ └── MxdmUtil.kt
│ │ │ └── easybangumi_nivod
│ │ │ ├── NivodApiSource.kt
│ │ │ ├── NivodConstants.kt
│ │ │ ├── NivodDetailComponent.kt
│ │ │ ├── NivodPageComponent.kt
│ │ │ ├── NivodPlayComponent.kt
│ │ │ ├── NivodSearchComponent.kt
│ │ │ ├── Util.kt
│ │ │ └── dto
│ │ │ ├── ChannelRecommendResponse.kt
│ │ │ ├── Common.kt
│ │ │ ├── SearchVideoResponse.kt
│ │ │ ├── VideoDetailResponse.kt
│ │ │ └── VideoStreamUrlResponse.kt
│ └── org
│ │ └── easybangumi
│ │ └── extension
│ │ └── EasySourceFactory.kt
│ └── res
│ ├── drawable
│ ├── anfun.png
│ ├── anim1.png
│ ├── auete.png
│ ├── changzhang.png
│ ├── cycplus.png
│ ├── fengche.png
│ ├── ggl.png
│ ├── libvio.png
│ ├── mikudm.png
│ ├── mxdm.png
│ ├── nivod.png
│ ├── xigua.png
│ └── yhdm.png
│ ├── mipmap-xxhdpi
│ ├── app_logo.png
│ └── logo_new.png
│ ├── values
│ └── strings.xml
│ └── xml
│ ├── backup_rules.xml
│ └── data_extraction_rules.xml
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── headline.png
└── settings.gradle.kts
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: release
2 | on:
3 | push:
4 | tags:
5 | - '**'
6 | workflow_dispatch:
7 | jobs:
8 | build-and-release:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Checkout
12 | uses: actions/checkout@v3.5.2
13 | - name: Setup Java JDK
14 | uses: actions/setup-java@v1.4.4
15 | with:
16 | java-version: 17
17 | - name: Build
18 | run: |
19 | chmod +x ./gradlew
20 | ./gradlew :extension-app:assemble
21 | - name: Setup build tool version variable
22 | shell: bash
23 | run: |
24 | BUILD_TOOL_VERSION=$(ls /usr/local/lib/android/sdk/build-tools/ | tail -n 1)
25 | echo "BUILD_TOOL_VERSION=$BUILD_TOOL_VERSION" >> $GITHUB_ENV
26 | echo Last build tool version is: $BUILD_TOOL_VERSION
27 | - name: sign-apk
28 | uses: r0adkll/sign-android-release@v1
29 | with:
30 | releaseDirectory: extension-app/build/outputs/apk/release
31 | signingKeyBase64: ${{ secrets.SIGNING_KEY }}
32 | alias: ${{ secrets.KEY_ALIAS }}
33 | keyStorePassword: ${{ secrets.KEY_STORE_PWD }}
34 | keyPassword: ${{ secrets.KEY_PWD }}
35 | env:
36 | # override default build-tools version (29.0.3) -- optional
37 | BUILD_TOOLS_VERSION: ${{ env.BUILD_TOOL_VERSION }}
38 | - name: rename-apk
39 | run: |
40 | mv extension-app/build/outputs/apk/release/extension-app-release-unsigned-signed.apk extension-app-${{ github.ref_name }}.apk
41 | - name: Release
42 | run: |
43 | gh release create -d ${{ github.ref_name }} extension-app-${{ github.ref_name }}.apk
44 | env:
45 | GITHUB_TOKEN: ${{ secrets.TOKEN }}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | .idea
4 | /local.properties
5 | /.idea/caches
6 | /.idea/libraries
7 | /.idea/modules.xml
8 | /.idea/workspace.xml
9 | /.idea/navEditor.xml
10 | /.idea/assetWizardSettings.xml
11 | .DS_Store
12 | /build
13 | /captures
14 | /publishing
15 | .externalNativeBuild
16 | .cxx
17 | local.properties
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 |
4 |
5 |
6 |
7 |
8 | 本仓库内容均来自互联网投稿,为了代码安全性只收录提交到纯纯看番官方的代码,并由官方编译。
9 |
10 | 如果使用其他的源,用户需要自己甄别插件安全性,纯纯看番官方无法保证这些插件是否安全。
11 |
12 | 如果本仓库内容您认为侵犯了你的权益,请提交issue联系我们删除。
13 |
14 | 纯纯看番本体:[https://github.com/easybangumiorg/EasyBangumi](https://github.com/easybangumiorg/EasyBangumi)
15 |
16 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | plugins {
3 | id("com.android.application") version "8.1.2" apply false
4 | id("org.jetbrains.kotlin.android") version "1.9.0" apply false
5 | }
6 |
7 | tasks.create("clean") {
8 | delete {
9 | rootProject.buildDir
10 | }
11 | }
12 |
13 | subprojects {
14 | // 定义检查依赖变化的时间间隔,!!配置为0实时刷新
15 | configurations.all {
16 | // check for updates every build
17 | resolutionStrategy.cacheChangingModulesFor(0, java.util.concurrent.TimeUnit.SECONDS)
18 | }
19 | }
--------------------------------------------------------------------------------
/extension-app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/extension-app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id ("com.android.application")
3 | id ("org.jetbrains.kotlin.android")
4 | }
5 |
6 | // 包名
7 | val packageName = "org.easybangumi.extension"
8 |
9 | // 库版本,目前 5.0.3 支持的库版本为 3 到 5
10 | val extensionLibVersion = 7
11 |
12 | android {
13 | namespace = packageName
14 | compileSdk = 34
15 |
16 | defaultConfig {
17 | applicationId = packageName
18 | minSdk = 21
19 | targetSdk = 34
20 | versionCode = 7
21 | versionName = "1.6"
22 |
23 | manifestPlaceholders.put("extensionLibVersion", extensionLibVersion)
24 |
25 | }
26 |
27 | buildTypes {
28 | release {
29 | isMinifyEnabled = false
30 | }
31 | }
32 | compileOptions {
33 | sourceCompatibility = JavaVersion.VERSION_1_8
34 | targetCompatibility = JavaVersion.VERSION_1_8
35 | }
36 | kotlinOptions {
37 | jvmTarget = "1.8"
38 | }
39 |
40 | dependenciesInfo{
41 | includeInApk = false
42 | includeInBundle = false
43 | }
44 | }
45 |
46 |
47 | dependencies {
48 | implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
49 | compileOnly("io.github.easybangumiorg:extension-api:1.${extensionLibVersion}-SNAPSHOT")
50 | }
51 |
--------------------------------------------------------------------------------
/extension-app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
23 | -keep class com.heyanle.**{*;}
24 | -keep interface com.heyanle.**{*;}
25 |
26 | # 协程
27 |
28 | -keep class kotlin.** { *; }
29 | -keep class kotlin.Metadata { *; }
30 | -dontwarn kotlin.**
31 | -keepclassmembers class **$WhenMappings {
32 | ;
33 | }
34 | -keepclassmembers class kotlin.Metadata {
35 | public ;
36 | }
37 | -assumenosideeffects class kotlin.jvm.internal.Intrinsics {
38 | static void checkParameterIsNotNull(java.lang.Object, java.lang.String);
39 | }
40 | -keep class kotlinx.coroutines.android.** {*;}
41 | # ServiceLoader support
42 | -keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
43 | -keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
44 | -keepnames class kotlinx.coroutines.android.AndroidExceptionPreHandler {}
45 | -keepnames class kotlinx.coroutines.android.AndroidDispatcherFactory {}
46 |
47 | # Most of volatile fields are updated with AFU and should not be mangled
48 | -keepclassmembernames class kotlinx.** {
49 | volatile ;
50 | }
51 |
52 |
--------------------------------------------------------------------------------
/extension-app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
12 |
13 |
14 |
15 |
18 |
19 |
20 |
23 |
24 |
25 |
27 |
28 |
29 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/extension-app/src/main/java/com/heyanle/easybangumi_extension/anfun/AnfunListComponent.kt:
--------------------------------------------------------------------------------
1 | package com.heyanle.easybangumi_extension.anfun
2 |
3 | import com.heyanle.easybangumi4.source_api.component.ComponentWrapper
4 | import com.heyanle.easybangumi4.source_api.entity.CartoonCover
5 | import com.heyanle.easybangumi4.source_api.entity.CartoonCoverImpl
6 | import com.heyanle.easybangumi4.source_api.utils.api.OkhttpHelper
7 | import com.heyanle.easybangumi4.source_api.utils.core.SourceUtils
8 | import org.jsoup.select.Elements
9 |
10 | /**
11 | * Created by heyanle on 2024/1/28.
12 | * https://github.com/heyanLE
13 | */
14 | class AnfunListComponent(
15 | private val okhttpHelper: OkhttpHelper,
16 | ) : ComponentWrapper() {
17 |
18 | companion object {
19 | const val ROOT_URL = "https://www.anfuns.cc"
20 | }
21 |
22 | suspend fun listPage(
23 | element: Elements,
24 | ): Pair> {
25 | val r = arrayListOf()
26 | for (video in element) {
27 | video.apply {
28 | val name = select("a").attr("title")
29 | val videoUrl = select("a").attr("href")
30 | val coverUrl = select("a").attr("data-original")
31 | val episode = select(".remarks").text()
32 | val id = videoUrl.subSequence(7, videoUrl.length - 5).toString()
33 | if (!name.isNullOrBlank() && !videoUrl.isNullOrBlank() && !coverUrl.isNullOrBlank()) {
34 | val b = CartoonCoverImpl(
35 | id = id,
36 | source = source.key,
37 | url = videoUrl,
38 | title = name,
39 | intro = episode ?: "",
40 | coverUrl = SourceUtils.urlParser(ROOT_URL, coverUrl)
41 | )
42 | r.add(b)
43 | }
44 | }
45 | }
46 | return Pair(null, r)
47 | }
48 |
49 | }
--------------------------------------------------------------------------------
/extension-app/src/main/java/com/heyanle/easybangumi_extension/anfun/AnfunPageComponent.kt:
--------------------------------------------------------------------------------
1 | package com.heyanle.easybangumi_extension.anfun
2 |
3 | import com.heyanle.easybangumi4.source_api.component.ComponentWrapper
4 | import com.heyanle.easybangumi4.source_api.component.page.PageComponent
5 | import com.heyanle.easybangumi4.source_api.component.page.SourcePage
6 | import com.heyanle.easybangumi4.source_api.entity.CartoonCover
7 | import com.heyanle.easybangumi4.source_api.entity.CartoonCoverImpl
8 | import com.heyanle.easybangumi4.source_api.utils.api.OkhttpHelper
9 | import com.heyanle.easybangumi4.source_api.utils.core.SourceUtils
10 | import com.heyanle.easybangumi4.source_api.utils.core.network.GET
11 | import com.heyanle.easybangumi4.source_api.withResult
12 | import kotlinx.coroutines.Dispatchers
13 | import org.jsoup.Jsoup
14 |
15 | /**
16 | * Created by heyanle on 2024/1/28.
17 | * https://github.com/heyanLE
18 | */
19 | class AnfunPageComponent(
20 | private val anfunListComponent: AnfunListComponent,
21 | private val okhttpHelper: OkhttpHelper,
22 | ): ComponentWrapper(), PageComponent {
23 |
24 | override fun getPages(): List {
25 | return listOf(
26 | // 首页
27 | SourcePage.Group(
28 | "首页",
29 | false,
30 | ) {
31 | withResult(Dispatchers.IO) {
32 | homeListPages()
33 | }
34 | },
35 |
36 | // 新番时刻表
37 | SourcePage.Group(
38 | "每日更新列表",
39 | false,
40 | ) {
41 | withResult(Dispatchers.IO) {
42 | homeTimelinePages()
43 | }
44 | },
45 | )
46 | }
47 |
48 | // 获取主页所有 ListPage
49 | private suspend fun homeListPages(): List {
50 | val res = arrayListOf()
51 | val doc = Jsoup.parse(
52 | okhttpHelper.cloudflareWebViewClient.newCall(GET(AnfunSource.ROOT_URL))
53 | .execute().body?.string()!!
54 | )
55 |
56 | val modules = doc.select("#conch-content").select("div[class='container']")
57 | for (em in modules){
58 | val moduleHeading = em.select(".hl-rb-head").first()
59 | val type = moduleHeading?.select(".hl-rb-title")
60 | val label = type?.text()?:continue
61 | if (label == "每周更新" || label == "网络资讯" || label == "动漫专题") continue
62 | val lis = em.select(".row").select("ul").select("li")
63 | val page = SourcePage.SingleCartoonPage.WithCover(
64 | label = label,
65 | firstKey = { 0 },
66 | ) {
67 | withResult(Dispatchers.IO) {
68 | anfunListComponent.listPage(
69 | lis
70 | )
71 | }
72 | }
73 | res.add(page)
74 | }
75 | return res
76 | }
77 |
78 | // 获取新番时刻表 ListPage
79 | private suspend fun homeTimelinePages(): List {
80 | val res = arrayListOf()
81 | val docmuent = Jsoup.parse(
82 | okhttpHelper.cloudflareWebViewClient.newCall(GET(AnfunSource.ROOT_URL))
83 | .execute().body?.string()!!
84 | )
85 | val doc = docmuent.select("#conch-content").select("div[class='container']")[2]
86 | val days = mutableListOf()
87 | doc.select(".hl-rb-head").select("span").select("a").forEach {
88 | // Log.e("星期", it.text())
89 | days.add(it.text())
90 | }
91 | val updateList = doc.select(".row").select(".hl-list-wrap")
92 | for ((index,ems) in updateList.withIndex()){
93 | val r = arrayListOf()
94 | val target = ems.select("ul").select("li")
95 | for (em in target) {
96 | val titleEm = em.select("a")
97 | val cover = titleEm.attr("data-original")
98 | val title = titleEm.attr("title")
99 | val episode = titleEm.select(".remarks").text()
100 | val url = titleEm.attr("href")
101 | val id = url.subSequence(7, url.length - 5).toString()
102 | val desc = em.select(".hl-item-sub").text()
103 | if (!title.isNullOrBlank() && !episode.isNullOrBlank() && !url.isNullOrBlank()) {
104 | val car = CartoonCoverImpl(
105 | id = id,
106 | source = source.key,
107 | url = url,
108 | title = title,
109 | intro = episode,
110 | coverUrl = null,
111 | )
112 | r.add(car)
113 | }
114 | }
115 | res.add(
116 | SourcePage.SingleCartoonPage.WithoutCover(
117 | days[index],
118 | firstKey = { 0 },
119 | load = {
120 | withResult {
121 | null to r
122 | }
123 | }
124 | ))
125 | if (index == 6) break
126 | }
127 | return res
128 | }
129 | }
--------------------------------------------------------------------------------
/extension-app/src/main/java/com/heyanle/easybangumi_extension/anfun/AnfunSearchComponent.kt:
--------------------------------------------------------------------------------
1 | package com.heyanle.easybangumi_extension.anfun
2 |
3 | import android.util.Log
4 | import com.heyanle.easybangumi4.source_api.SourceResult
5 | import com.heyanle.easybangumi4.source_api.component.ComponentWrapper
6 | import com.heyanle.easybangumi4.source_api.component.search.SearchComponent
7 | import com.heyanle.easybangumi4.source_api.entity.CartoonCover
8 | import com.heyanle.easybangumi4.source_api.entity.CartoonCoverImpl
9 | import com.heyanle.easybangumi4.source_api.utils.api.OkhttpHelper
10 | import com.heyanle.easybangumi4.source_api.utils.core.SourceUtils
11 | import com.heyanle.easybangumi4.source_api.utils.core.network.GET
12 | import com.heyanle.easybangumi4.source_api.withResult
13 | import kotlinx.coroutines.Dispatchers
14 | import org.jsoup.Jsoup
15 | import org.jsoup.select.Elements
16 |
17 | /**
18 | * Created by heyanle on 2024/1/29.
19 | * https://github.com/heyanLE
20 | */
21 | class AnfunSearchComponent(
22 | private val okhttpHelper: OkhttpHelper
23 | ) : ComponentWrapper(), SearchComponent {
24 | override fun getFirstSearchKey(keyword: String): Int {
25 | return 0
26 | }
27 |
28 | override suspend fun search(
29 | pageKey: Int,
30 | keyword: String
31 | ): SourceResult>> {
32 | return withResult(Dispatchers.IO) {
33 | val url = SourceUtils.urlParser(
34 | AnfunSource.ROOT_URL,
35 | "/search/page/${pageKey + 1}/wd/${keyword}.html"
36 | )
37 | Log.e("TAG", "--->${url}")
38 | val d = okhttpHelper.cloudflareWebViewClient.newCall(
39 | GET(
40 | SourceUtils.urlParser(
41 | AnfunSource.ROOT_URL,
42 | url
43 | )
44 | )
45 | ).execute().body?.string()!!
46 | val doc = Jsoup.parse(d)
47 | val r = arrayListOf()
48 | val lpic = doc.select("#conch-content").select(".row").select("ul")[0]
49 | val results: Elements = lpic.select("li")
50 | if (results.size == 0) {
51 | Log.e("TAG", "已经加载完毕~")
52 | return@withResult Pair(null, r)
53 | }
54 | for (i in results.indices) {
55 | var cover = results[i].select("a").attr("data-original")
56 | if (cover.startsWith("//")) {
57 | cover = "https:${cover}"
58 | }
59 | val title = results[i].select("a").attr("title")
60 | val itemUrl = results[i].select("a").attr("href")
61 | val id = itemUrl.subSequence(7, itemUrl.length - 5).toString()
62 | val episode = results[i].select(".hl-pic-text").select("span").text()
63 | val describe =
64 | results[i].select("p[class='hl-item-sub hl-text-muted hl-lc-2']").text()
65 |
66 | val b = CartoonCoverImpl(
67 | id = id,
68 | title = title,
69 | url = itemUrl,
70 | intro = episode,
71 | coverUrl = SourceUtils.urlParser(AnfunSource.ROOT_URL, cover),
72 | source = source.key,
73 | )
74 | r.add(b)
75 | }
76 | val pages = doc.select(".hl-list-wrap").select(".hl-page-wrap").select("li")
77 | return@withResult if (pages.isEmpty()) {
78 | Pair(null, r)
79 | } else {
80 | var hasNext = false
81 | for (p in pages) {
82 | if (p.text() == (pageKey + 2).toString() || p.text() == "下一页") {
83 | hasNext = true
84 | break
85 | }
86 | }
87 | if (!hasNext) {
88 | Pair(null, r)
89 | } else {
90 | Pair(pageKey + 1, r)
91 | }
92 | }
93 | }
94 | }
95 | }
--------------------------------------------------------------------------------
/extension-app/src/main/java/com/heyanle/easybangumi_extension/anfun/AnfunSource.kt:
--------------------------------------------------------------------------------
1 | package com.heyanle.easybangumi_extension.anfun
2 |
3 | import com.heyanle.extension_api.ExtensionIconSource
4 | import com.heyanle.extension_api.ExtensionSource
5 | import org.easybangumi.extension.R
6 | import kotlin.reflect.KClass
7 |
8 | /**
9 | * Created by heyanle on 2024/1/28.
10 | * https://github.com/heyanLE
11 | */
12 | class AnfunSource : ExtensionSource(), ExtensionIconSource {
13 |
14 | companion object {
15 | const val ROOT_URL = "https://www.anfuns.cc"
16 | }
17 |
18 | override fun getIconResourcesId(): Int? {
19 | return R.drawable.anfun
20 | }
21 |
22 | override val sourceKey: String
23 | get() = "heyanle_Anfun"
24 | override val describe: String?
25 | get() = null
26 | override val label: String
27 | get() = "AnFuns动漫"
28 | override val version: String
29 | get() = "1.2"
30 | override val versionCode: Int
31 | get() = 2
32 |
33 | override fun register(): List> {
34 | return listOf(
35 | AnfunDetailedComponent::class,
36 | AnfunListComponent::class,
37 | AnfunPageComponent::class,
38 | AnfunSearchComponent::class
39 | )
40 | }
41 | }
--------------------------------------------------------------------------------
/extension-app/src/main/java/com/heyanle/easybangumi_extension/anim/AnimOneInfo.kt:
--------------------------------------------------------------------------------
1 | package com.heyanle.easybangumi_extension.anim
2 |
3 | import com.heyanle.easybangumi4.source_api.Source
4 | import com.heyanle.easybangumi4.source_api.entity.CartoonCoverImpl
5 | import com.heyanle.easybangumi4.source_api.entity.CartoonImpl
6 |
7 |
8 | /**
9 | * Created by HeYanLe on 2023/7/16 16:56.
10 | * https://github.com/heyanLE
11 | */
12 | data class AnimOneInfo(
13 | val id: Long,
14 | val name: String,
15 | val intro: String,
16 | val year: String,
17 | val season: String,
18 | val translator: String,
19 | ){
20 |
21 | fun toCartoon(source: Source) = CartoonImpl(
22 | id = id.toString(),
23 | source = source.key,
24 | url = source.describe?:"",
25 | title = name,
26 | genre = "${year}, ${season}, $translator",
27 | intro = intro,
28 | )
29 |
30 | fun toCartoonCover(source: Source) = CartoonCoverImpl(
31 | id = id.toString(),
32 | source = source.key,
33 | url = source.describe?:"",
34 | title = name
35 | )
36 | }
--------------------------------------------------------------------------------
/extension-app/src/main/java/com/heyanle/easybangumi_extension/anim/AnimOneSource.kt:
--------------------------------------------------------------------------------
1 | package com.heyanle.easybangumi_extension.anim
2 |
3 |
4 | import com.heyanle.easybangumi4.source_api.Source
5 | import com.heyanle.extension_api.ExtensionIconSource
6 | import com.heyanle.extension_api.ExtensionSource
7 | import org.easybangumi.extension.R
8 | import kotlin.reflect.KClass
9 |
10 | /**
11 | * Created by HeYanLe on 2023/6/6 18:25.
12 | * https://github.com/heyanLE
13 | */
14 | class AnimOneSource : Source, ExtensionIconSource {
15 |
16 | override fun getIconResourcesId(): Int? {
17 | return R.drawable.anim1
18 | }
19 |
20 | override val key: String
21 | get() = "com.heyanle.easybangumi_extension.animone-anim_one"
22 | override val describe: String?
23 | get() = "https://anime1.me/"
24 | override val label: String
25 | get() = "Anim1"
26 | override val version: String
27 | get() = "2.0"
28 | override val versionCode: Int
29 | get() = 2
30 |
31 | override fun register(): List> {
32 | return listOf(
33 | AnimPageComponent::class
34 | )
35 | }
36 | }
--------------------------------------------------------------------------------
/extension-app/src/main/java/com/heyanle/easybangumi_extension/anim/DataSource.kt:
--------------------------------------------------------------------------------
1 | package com.heyanle.easybangumi_extension.anim
2 |
3 | import com.google.gson.JsonParser
4 | import com.heyanle.easybangumi4.source_api.utils.api.NetworkHelper
5 | import com.heyanle.easybangumi4.source_api.utils.api.OkhttpHelper
6 | import com.heyanle.easybangumi4.source_api.utils.core.network.GET
7 | import kotlinx.coroutines.Dispatchers
8 | import kotlinx.coroutines.withContext
9 | import org.jsoup.Jsoup
10 |
11 |
12 | /**
13 | * Created by HeYanLe on 2023/7/16 16:56.
14 | * https://github.com/heyanLE
15 | */
16 | object DataSource {
17 |
18 | private var allData: List? = null
19 |
20 | suspend fun getData(
21 | refresh: Boolean,
22 | okhttpHelper: OkhttpHelper,
23 | ):List {
24 | return withContext(Dispatchers.IO){
25 | if(allData == null || refresh){
26 | val rs = arrayListOf()
27 | runCatching {
28 | val url = "https://d1zquzjgwo9yb.cloudfront.net/?_="
29 | val res = okhttpHelper.client.newCall(GET(url)).execute().body?.string()?:""
30 | val jsonElement = JsonParser.parseString(res).asJsonArray
31 | rs.addAll(jsonElement.map {
32 | val d = it.asJsonArray
33 | AnimOneInfo(
34 | id = d.get(0).asLong,
35 | name = Jsoup.parse(d.get(1).asString).text(),
36 | intro = d.get(2).asString,
37 | year = d.get(3).asString,
38 | season = d.get(4).asString,
39 | translator = d.get(5).asString,
40 | )
41 | })
42 | }.onFailure {
43 | it.printStackTrace()
44 | }
45 | allData = rs
46 | rs
47 | }else{
48 | allData?: emptyList()
49 | }
50 | }
51 | }
52 |
53 | }
--------------------------------------------------------------------------------
/extension-app/src/main/java/com/heyanle/easybangumi_extension/ggl/GGLComponent.kt:
--------------------------------------------------------------------------------
1 | package com.heyanle.easybangumi_extension.ggl
2 |
3 | import android.util.Log
4 | import com.heyanle.easybangumi4.source_api.SourceResult
5 | import com.heyanle.easybangumi4.source_api.component.ComponentWrapper
6 | import com.heyanle.easybangumi4.source_api.component.page.PageComponent
7 | import com.heyanle.easybangumi4.source_api.component.page.SourcePage
8 | import com.heyanle.easybangumi4.source_api.component.search.SearchComponent
9 | import com.heyanle.easybangumi4.source_api.entity.CartoonCover
10 | import com.heyanle.easybangumi4.source_api.entity.CartoonCoverImpl
11 | import com.heyanle.easybangumi4.source_api.utils.core.SourceUtils
12 | import com.heyanle.easybangumi4.source_api.withResult
13 | import kotlinx.coroutines.Dispatchers
14 | import java.net.URLEncoder
15 |
16 | /**
17 | * Created by heyanle on 2024/1/28.
18 | * https://github.com/heyanLE
19 | */
20 | class GGLComponent(
21 | private val gglListComponent: GGLListComponent,
22 | ) : ComponentWrapper(), PageComponent, SearchComponent {
23 |
24 |
25 | override fun getPages(): List {
26 | return listOf(
27 | SourcePage.SingleCartoonPage.WithCover(
28 | label = "日番",
29 | firstKey = { 1 },
30 | ) {
31 | withResult(Dispatchers.IO) {
32 | gglListComponent.listHomePage(
33 | "https://anime.girigirilove.com/show/2-----------/",
34 | it
35 | )
36 | }
37 | },
38 | SourcePage.SingleCartoonPage.WithCover(
39 | label = "美番",
40 | firstKey = { 1 },
41 | ) {
42 | withResult(Dispatchers.IO) {
43 | gglListComponent.listHomePage(
44 | "https://anime.girigirilove.com/show/3-----------/",
45 | it
46 | )
47 | }
48 |
49 | },
50 | SourcePage.SingleCartoonPage.WithCover(
51 | label = "剧场版",
52 | firstKey = { 1 },
53 | ) {
54 | withResult(Dispatchers.IO) {
55 | gglListComponent.listHomePage(
56 | "https://anime.girigirilove.com/show/21-----------/",
57 | it
58 | )
59 | }
60 |
61 | },
62 | )
63 | }
64 |
65 | override fun getFirstSearchKey(keyword: String): Int {
66 | return 1
67 | }
68 |
69 | override suspend fun search(
70 | pageKey: Int,
71 | keyword: String
72 | ): SourceResult>> {
73 | return withResult(Dispatchers.IO) {
74 | val url = SourceUtils.urlParser(
75 | GGLListComponent.ROOT_URL,
76 | "/search/${URLEncoder.encode(keyword, "utf-8")}----------${pageKey}---/"
77 | )
78 | val doc = gglListComponent.getDoc(url).getOrThrow()
79 | val list = arrayListOf()
80 |
81 | doc.select("div div.public-list-box.search-box").forEach {
82 | val uu = it.child(1).child(0).attr("href")
83 | val id = uu.subSequence(1, uu.length - 1).toString()
84 |
85 | val coverStyle = it.select("div.cover")[0].attr("style")
86 | val coverPattern = Regex("""(?<=url\().*(?=\))""")
87 | var cover = coverPattern.find(coverStyle)?.value ?: ""
88 | if (cover.startsWith("//")) {
89 | cover = "http:${cover}"
90 | }
91 | Log.d("GGLSearchComponent", coverStyle)
92 |
93 | val title = it.select("div.thumb-content div.thumb-txt").first()?.text() ?: ""
94 | val b = CartoonCoverImpl(
95 | id = id,
96 | title = title,
97 | url = SourceUtils.urlParser(GGLListComponent.ROOT_URL, uu),
98 | intro = "",
99 | coverUrl = SourceUtils.urlParser(GGLListComponent.ROOT_URL, cover),
100 | source = source.key,
101 | )
102 | list.add(b)
103 | }
104 |
105 | (if (list.isEmpty()) null else pageKey + 1) to list
106 |
107 |
108 | }
109 | }
110 | }
--------------------------------------------------------------------------------
/extension-app/src/main/java/com/heyanle/easybangumi_extension/ggl/GGLDetailedComponent.kt:
--------------------------------------------------------------------------------
1 | package com.heyanle.easybangumi_extension.ggl
2 |
3 | import com.heyanle.easybangumi4.source_api.SourceResult
4 | import com.heyanle.easybangumi4.source_api.component.ComponentWrapper
5 | import com.heyanle.easybangumi4.source_api.component.detailed.DetailedComponent
6 | import com.heyanle.easybangumi4.source_api.component.page.PageComponent
7 | import com.heyanle.easybangumi4.source_api.component.search.SearchComponent
8 | import com.heyanle.easybangumi4.source_api.component.update.UpdateComponent
9 | import com.heyanle.easybangumi4.source_api.entity.Cartoon
10 | import com.heyanle.easybangumi4.source_api.entity.CartoonImpl
11 | import com.heyanle.easybangumi4.source_api.entity.CartoonSummary
12 | import com.heyanle.easybangumi4.source_api.entity.Episode
13 | import com.heyanle.easybangumi4.source_api.entity.PlayLine
14 | import com.heyanle.easybangumi4.source_api.utils.api.OkhttpHelper
15 | import com.heyanle.easybangumi4.source_api.utils.core.SourceUtils
16 | import com.heyanle.easybangumi4.source_api.utils.core.network.GET
17 | import com.heyanle.easybangumi4.source_api.withResult
18 | import kotlinx.coroutines.Dispatchers
19 | import org.jsoup.Jsoup
20 | import org.jsoup.nodes.Document
21 |
22 | /**
23 | * Created by heyanle on 2024/1/28.
24 | * https://github.com/heyanLE
25 | */
26 | class GGLDetailedComponent(
27 | private val okhttpHelper: OkhttpHelper,
28 | ) : ComponentWrapper(), DetailedComponent, UpdateComponent {
29 |
30 | override suspend fun getAll(summary: CartoonSummary): SourceResult>> {
31 | return withResult(Dispatchers.IO) {
32 | val doc = getDoc(summary)
33 | detailed(doc, summary) to playLine(doc, summary)
34 | }
35 |
36 | }
37 |
38 | override suspend fun getDetailed(summary: CartoonSummary): SourceResult {
39 | return withResult(Dispatchers.IO) {
40 | val doc = getDoc(summary)
41 | detailed(doc, summary)
42 | }
43 | }
44 |
45 | override suspend fun getPlayLine(summary: CartoonSummary): SourceResult> {
46 | return withResult(Dispatchers.IO) {
47 | val doc = getDoc(summary)
48 | playLine(doc, summary)
49 | }
50 | }
51 |
52 | private fun getDoc(summary: CartoonSummary): Document {
53 | val d = okhttpHelper.cloudflareWebViewClient.newCall(
54 | GET(
55 | SourceUtils.urlParser(
56 | GGLListComponent.ROOT_URL,
57 | summary.id
58 | )
59 | )
60 | )
61 | .execute().body?.string() ?: throw NullPointerException()
62 | return Jsoup.parse(d)
63 | }
64 |
65 | private fun detailed(doc: Document, summary: CartoonSummary): Cartoon {
66 | val title = doc.select("div.detail-info h3.slide-info-title").text()
67 | // val genre = doc.select("div.detail-info div.slide-info span").map { it.text() }.joinToString { ", " }
68 | val cover = doc.select("div.wow div.detail-pic img").first()?.attr("data-src") ?: ""
69 | val desc = doc.select("div.switch-box div.check div.text").first()?.text() ?: ""
70 | return CartoonImpl(
71 | id = summary.id,
72 | url = SourceUtils.urlParser(GGLListComponent.ROOT_URL, summary.id),
73 | source = summary.source,
74 | title = title,
75 | coverUrl = SourceUtils.urlParser(GGLListComponent.ROOT_URL, cover),
76 | intro = "",
77 | description = desc,
78 | genre = null,
79 | status = Cartoon.STATUS_UNKNOWN,
80 | updateStrategy = Cartoon.UPDATE_STRATEGY_ALWAYS,
81 | )
82 | }
83 |
84 | private fun playLine(doc: Document, summary: CartoonSummary): List {
85 | val tabs =
86 | doc.select("div.anthology.wow div.anthology-tab div.swiper-wrapper a.swiper-slide")
87 | .iterator()
88 | val epRoot = doc.select("div.anthology-list-box div ul.anthology-list-play").iterator()
89 | val playLines = arrayListOf()
90 | var ii = 1
91 | while (tabs.hasNext() && epRoot.hasNext()) {
92 | val tab = tabs.next()
93 | val ul = epRoot.next()
94 |
95 | val es = arrayListOf()
96 | ul.children().forEachIndexed { index, element ->
97 | es.add(
98 | Episode(
99 | id = (index + 1).toString(),
100 | label = element?.text() ?: "",
101 | order = index
102 | )
103 | )
104 | }
105 |
106 | playLines.add(
107 | PlayLine(
108 | id = ii.toString(),
109 | label = tab.text(),
110 | episode = es
111 | )
112 | )
113 | ii++
114 | }
115 | return playLines
116 | }
117 |
118 | override suspend fun update(
119 | cartoon: Cartoon,
120 | oldPlayLine: List
121 | ): SourceResult {
122 | return withResult(Dispatchers.IO) {
123 | when (val n = getAll(CartoonSummary(cartoon.id, cartoon.source))) {
124 | is SourceResult.Complete -> {
125 | n.data.first.apply {
126 |
127 | val newPlayLine = n.data.second
128 |
129 | if (oldPlayLine.size != newPlayLine.size) {
130 | isUpdate = true
131 | } else {
132 | isUpdate = false
133 | for (i in oldPlayLine.indices) {
134 | if (oldPlayLine[i].episode.size != newPlayLine[i].episode.size) {
135 | isUpdate = true
136 | break
137 | }
138 | }
139 | }
140 | }
141 | }
142 |
143 | is SourceResult.Error -> {
144 | throw n.throwable
145 | }
146 | }
147 | }
148 | }
149 | }
--------------------------------------------------------------------------------
/extension-app/src/main/java/com/heyanle/easybangumi_extension/ggl/GGLListComponent.kt:
--------------------------------------------------------------------------------
1 | package com.heyanle.easybangumi_extension.ggl
2 |
3 | import com.heyanle.easybangumi4.source_api.component.ComponentWrapper
4 | import com.heyanle.easybangumi4.source_api.entity.CartoonCover
5 | import com.heyanle.easybangumi4.source_api.entity.CartoonCoverImpl
6 | import com.heyanle.easybangumi4.source_api.utils.api.OkhttpHelper
7 | import com.heyanle.easybangumi4.source_api.utils.core.SourceUtils
8 | import com.heyanle.easybangumi4.source_api.utils.core.network.GET
9 |
10 | import org.jsoup.Jsoup
11 | import org.jsoup.nodes.Document
12 |
13 | /**
14 | * Created by HeYanLe on 2023/5/21 21:18.
15 | * https://github.com/heyanLE
16 | */
17 | class GGLListComponent(
18 | private val okhttpHelper: OkhttpHelper,
19 | ): ComponentWrapper() {
20 |
21 | companion object {
22 | val ROOT_URL = "https://anime.girigirilove.com"
23 | }
24 | suspend fun listHomePage(
25 | url: String,
26 | page: Int,
27 | ): Pair>{
28 | val d = if(!url.endsWith("/")) "${url}/" else url
29 | val u = d.replace("---/", "${page}---/")
30 | val list = arrayListOf()
31 | val doc = getDoc(u).getOrThrow()
32 | doc.select("div.border-box div.public-list-box").forEach {
33 | val uu = it.child(0).child(0).attr("href")
34 | val id = uu.subSequence(1, uu.length-1).toString()
35 | list.add(
36 | CartoonCoverImpl(
37 | id = id,
38 | source = source.key,
39 | url = SourceUtils.urlParser(ROOT_URL, uu),
40 | title = it.child(1).child(0).text(),
41 | intro = it.select("span .public-list-prb").first()?.text(),
42 | coverUrl = SourceUtils.urlParser(ROOT_URL,it.select("img").first()?.attr("data-src")?:""),
43 | )
44 | )
45 | }
46 |
47 |
48 | return (if(list.isEmpty()) null else page+1) to list
49 | }
50 |
51 | fun getDoc(target: String): Result {
52 | return runCatching {
53 | val req = okhttpHelper.cloudflareWebViewClient.newCall(
54 | GET(
55 | SourceUtils.urlParser(ROOT_URL, target),
56 | )
57 | ).execute()
58 | val body = req.body!!.string()
59 | Jsoup.parse(body)
60 | }
61 | }
62 | }
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/extension-app/src/main/java/com/heyanle/easybangumi_extension/ggl/GGLPlayComponent.kt:
--------------------------------------------------------------------------------
1 | package com.heyanle.easybangumi_extension.ggl
2 |
3 | import android.util.Log
4 | import com.heyanle.easybangumi4.source_api.ParserException
5 | import com.heyanle.easybangumi4.source_api.SourceResult
6 | import com.heyanle.easybangumi4.source_api.component.ComponentWrapper
7 | import com.heyanle.easybangumi4.source_api.component.play.PlayComponent
8 | import com.heyanle.easybangumi4.source_api.entity.CartoonSummary
9 | import com.heyanle.easybangumi4.source_api.entity.Episode
10 | import com.heyanle.easybangumi4.source_api.entity.PlayLine
11 | import com.heyanle.easybangumi4.source_api.entity.PlayerInfo
12 | import com.heyanle.easybangumi4.source_api.utils.api.NetworkHelper
13 | import com.heyanle.easybangumi4.source_api.utils.api.OkhttpHelper
14 | import com.heyanle.easybangumi4.source_api.utils.api.WebViewHelper
15 | import com.heyanle.easybangumi4.source_api.utils.core.SourceUtils
16 | import com.heyanle.easybangumi4.source_api.withResult
17 | import kotlinx.coroutines.Dispatchers
18 | import org.jsoup.Jsoup
19 |
20 | /**
21 | * Created by heyanle on 2024/1/28.
22 | * https://github.com/heyanLE
23 | */
24 | class GGLPlayComponent(
25 | private val webViewHelper: WebViewHelper,
26 | private val networkHelper: NetworkHelper,
27 | ): ComponentWrapper(), PlayComponent {
28 |
29 | override suspend fun getPlayInfo(
30 | summary: CartoonSummary,
31 | playLine: PlayLine,
32 | episode: Episode
33 | ): SourceResult {
34 | return withResult(Dispatchers.IO) {
35 | val urlPath = "${if(summary.id.startsWith("GV"))summary.id else "GV${summary.id}"}-${playLine.id}-${episode.id}"
36 | val url = SourceUtils.urlParser(GGLListComponent.ROOT_URL, "/play${urlPath}/")
37 | val doc = webViewHelper.getRenderedHtmlCode(url = url, callBackRegex = "https://anime.girigirilove.com/addons/dp/player/index.php?.*", "utf-8", networkHelper.defaultLinuxUA, null, null, 20000L)
38 | Log.i("GGLPlayComponent", doc)
39 | val jsoup = Jsoup.parse(doc)
40 | val src = jsoup.select("tbody td iframe").first()?.attr("src")?:""
41 | val u = src.split("?").last().split("&").find {
42 | it.startsWith("url=")
43 | }?.let {
44 | it.subSequence(4, it.length)
45 | }?.toString() ?:""
46 | if(u.isEmpty()){
47 | throw ParserException("url 解析失败")
48 | }
49 | PlayerInfo(
50 | decodeType = if(u.endsWith("m3u8")) PlayerInfo.DECODE_TYPE_HLS else PlayerInfo.DECODE_TYPE_OTHER,
51 | uri = u
52 | )
53 | }
54 | }
55 |
56 | }
--------------------------------------------------------------------------------
/extension-app/src/main/java/com/heyanle/easybangumi_extension/ggl/GGLSource.kt:
--------------------------------------------------------------------------------
1 | package com.heyanle.easybangumi_extension.ggl
2 |
3 | import com.heyanle.easybangumi4.source_api.Source
4 | import com.heyanle.extension_api.ExtensionIconSource
5 | import com.heyanle.extension_api.ExtensionSource
6 | import org.easybangumi.extension.R
7 | import kotlin.reflect.KClass
8 |
9 | /**
10 | * Created by heyanle on 2024/1/28.
11 | * https://github.com/heyanLE
12 | */
13 | class GGLSource : ExtensionSource(), ExtensionIconSource {
14 |
15 | override val describe: String?
16 | get() = "girigirilove"
17 | override val label: String
18 | get() = "girigirilove"
19 | override val version: String
20 | get() = "3.0"
21 | override val versionCode: Int
22 | get() = 3
23 |
24 | override fun register(): List> {
25 | return listOf(
26 | GGLComponent::class,
27 | GGLListComponent::class,
28 | GGLPlayComponent::class,
29 | GGLDetailedComponent::class
30 | )
31 | }
32 |
33 | override fun getIconResourcesId(): Int? {
34 | return R.drawable.ggl
35 | }
36 |
37 | override val sourceKey: String
38 | get() = "heyanle_girigirilove"
39 | }
--------------------------------------------------------------------------------
/extension-app/src/main/java/io/github/easybangumiorg/source/aio/AIOHtpHelper.kt:
--------------------------------------------------------------------------------
1 | package io.github.easybangumiorg.source.aio
2 |
3 | /**
4 | * Created by heyanle on 2024/1/28.
5 | * https://github.com/heyanLE
6 | */
7 | class AIOHtpHelper {
8 | }
--------------------------------------------------------------------------------
/extension-app/src/main/java/io/github/easybangumiorg/source/aio/OkHttp.kt:
--------------------------------------------------------------------------------
1 | package io.github.easybangumiorg.source.aio
2 |
3 | import android.util.Log
4 | import kotlinx.serialization.json.Json
5 | import okhttp3.OkHttpClient
6 | import okhttp3.Request
7 | import okhttp3.Response
8 | import org.jsoup.Jsoup
9 | import org.jsoup.nodes.Document
10 | import java.nio.charset.Charset
11 |
12 | val json = Json {
13 | ignoreUnknownKeys = true
14 | }
15 |
16 |
17 | val commonHttpClient = OkHttpClient.Builder()
18 | .addInterceptor {
19 | val req = it.request()
20 | Log.d("CommonOkHttpClient", req.url.toString())
21 | val builder = req.newBuilder()
22 | if (req.header("user-agent")?.isNotEmpty() != true) {
23 | builder.header(
24 | "user-agent",
25 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
26 | )
27 | }
28 | it.proceed(builder.build())
29 | }
30 | .build()
31 |
32 | fun OkHttpClient.newRequest(block: Request.Builder.() -> Unit): Response {
33 | val req = Request.Builder()
34 | .apply(block)
35 | .build()
36 | return newCall(req).execute()
37 | }
38 |
39 | fun OkHttpClient.newGetRequest(block: Request.Builder.() -> Unit): Response {
40 | return newRequest {
41 | block()
42 | get()
43 | }
44 | }
45 |
46 | fun Response.asDocument(): Document {
47 | return Jsoup.parse(this.bodyString()).apply {
48 | setBaseUri(request.url.toString())
49 | }
50 | }
51 |
52 | fun Response.bodyString(charset: Charset = Charsets.UTF_8): String {
53 | if (code != 200) {
54 | throw RuntimeException("请求${request.url}失败,code: $code")
55 | }
56 | return this.use {
57 | val body = it.body ?: throw RuntimeException("请求${request.url}失败,响应体为空")
58 | body.string()
59 | }
60 | }
61 |
62 | inline fun Response.readJson(): T {
63 | return json.decodeFromString(this.bodyString())
64 | }
65 |
66 |
--------------------------------------------------------------------------------
/extension-app/src/main/java/io/github/easybangumiorg/source/aio/SourceResult.kt:
--------------------------------------------------------------------------------
1 | package io.github.easybangumiorg.source.aio
2 |
3 |
4 |
5 | import com.heyanle.easybangumi4.source_api.SourceResult
6 | import com.heyanle.easybangumi4.source_api.withResult
7 | import kotlinx.coroutines.Dispatchers
8 |
9 | fun SourceResult.map(convert: (T) -> R): SourceResult {
10 | return when (this) {
11 | is SourceResult.Complete -> SourceResult.Complete(convert(data))
12 | is SourceResult.Error -> SourceResult.Error(throwable, isParserError)
13 | }
14 | }
15 |
16 | suspend fun withIoResult(block: suspend () -> T) = withResult(Dispatchers.IO, block)
--------------------------------------------------------------------------------
/extension-app/src/main/java/io/github/easybangumiorg/source/aio/String.kt:
--------------------------------------------------------------------------------
1 | package io.github.easybangumiorg.source.aio
2 |
3 | import java.net.URLDecoder
4 | import java.net.URLEncoder
5 |
6 | fun String.encodeUri(): String = URLEncoder.encode(this, "UTF-8")
7 |
8 | fun String.decodeUri(): String = URLDecoder.decode(this, "UTF-8")
9 |
--------------------------------------------------------------------------------
/extension-app/src/main/java/io/github/easybangumiorg/source/aio/auete/AueteDetail.kt:
--------------------------------------------------------------------------------
1 | package io.github.easybangumiorg.source.aio.auete
2 |
3 | import com.heyanle.easybangumi4.source_api.SourceResult
4 | import com.heyanle.easybangumi4.source_api.component.ComponentWrapper
5 | import com.heyanle.easybangumi4.source_api.component.detailed.DetailedComponent
6 | import com.heyanle.easybangumi4.source_api.entity.Cartoon
7 | import com.heyanle.easybangumi4.source_api.entity.CartoonImpl
8 | import com.heyanle.easybangumi4.source_api.entity.CartoonSummary
9 | import com.heyanle.easybangumi4.source_api.entity.Episode
10 | import com.heyanle.easybangumi4.source_api.entity.PlayLine
11 | import io.github.easybangumiorg.source.aio.asDocument
12 | import io.github.easybangumiorg.source.aio.commonHttpClient
13 | import io.github.easybangumiorg.source.aio.map
14 | import io.github.easybangumiorg.source.aio.newGetRequest
15 | import io.github.easybangumiorg.source.aio.withIoResult
16 |
17 | class AueteDetail : ComponentWrapper(), DetailedComponent {
18 | override suspend fun getAll(summary: CartoonSummary): SourceResult>> =
19 | withIoResult {
20 | val doc = commonHttpClient.newGetRequest {
21 | url("$AueteBaseUrl/${summary.id}/")
22 | }.asDocument()
23 | val container = doc.selectFirst(".main .card-thread > .card-body")!!
24 | val img = container.selectFirst(".cover img")?.absUrl("src") ?: ""
25 | val title =
26 | container.selectFirst(".media > .media-body > .title")!!.text().trim().let { str ->
27 | val idx = str.indexOf('《')
28 | if (idx == -1) {
29 | str
30 | } else {
31 | var end = str.length
32 | for (i in (idx + 1) until str.length) {
33 | if (str[i] == '》') {
34 | end = i
35 | break
36 | }
37 | }
38 | str.substring(idx + 1, end)
39 | }
40 | }
41 | val infoList = mutableListOf()
42 | val infoEls = container.select(".message > p")
43 | var desc = ""
44 | for ((index, p) in infoEls.withIndex()) {
45 | val text = p.text().trim()
46 | if (text.contains("简介")) {
47 | if (index + 1 < infoEls.size) {
48 | desc = infoEls[index + 1].text().trim()
49 | }
50 | break
51 | }
52 | infoList.add(text)
53 |
54 | }
55 |
56 | val playlists = container.select("[id=player_list]").map { playlistContainer ->
57 | var name = playlistContainer.selectFirst(".title")!!.text().trim()
58 | name.indexOf('』').let {
59 | if (it >= 0) {
60 | name = name.substring(it + 1)
61 | }
62 | }
63 | name.indexOf(':').let {
64 | if (it >= 0) {
65 | name = name.substring(0, it)
66 | }
67 | }
68 | val episodes = playlistContainer.select("ul > li > a").map { epEl ->
69 | val id = epEl.attr("href").let {
70 | it.substring(it.lastIndexOf('/') + 1, it.lastIndexOf('.'))
71 | }
72 | Episode(label = epEl.text().trim(), id = id, order = 0)
73 | }
74 | PlayLine(id = name, label = name, episode = arrayListOf(*episodes.toTypedArray()))
75 | }
76 | val cartoon = CartoonImpl(
77 | id = summary.id,
78 | source = source.key,
79 | url = "$AueteBaseUrl/${summary.id}/",
80 | title = title,
81 | coverUrl = img,
82 | description = desc,
83 | intro = infoList.joinToString(separator = "\n")
84 | )
85 | cartoon to playlists
86 | }
87 |
88 | override suspend fun getDetailed(summary: CartoonSummary): SourceResult =
89 | getAll(summary).map { it.first }
90 |
91 | override suspend fun getPlayLine(summary: CartoonSummary): SourceResult> =
92 | getAll(summary).map { it.second }
93 | }
--------------------------------------------------------------------------------
/extension-app/src/main/java/io/github/easybangumiorg/source/aio/auete/AuetePage.kt:
--------------------------------------------------------------------------------
1 | package io.github.easybangumiorg.source.aio.auete
2 |
3 | import com.heyanle.easybangumi4.source_api.component.ComponentWrapper
4 | import com.heyanle.easybangumi4.source_api.component.page.PageComponent
5 | import com.heyanle.easybangumi4.source_api.component.page.SourcePage
6 | import com.heyanle.easybangumi4.source_api.entity.CartoonCover
7 | import com.heyanle.easybangumi4.source_api.entity.CartoonCoverImpl
8 | import com.heyanle.easybangumi4.source_api.withResult
9 | import io.github.easybangumiorg.source.aio.asDocument
10 | import io.github.easybangumiorg.source.aio.commonHttpClient
11 | import io.github.easybangumiorg.source.aio.newGetRequest
12 | import io.github.easybangumiorg.source.aio.withIoResult
13 |
14 | class AuetePage : ComponentWrapper(), PageComponent {
15 | override fun getPages(): List {
16 | val home = SourcePage.Group(label = "首页", newScreen = false) {
17 | withIoResult {
18 | val doc = commonHttpClient.newGetRequest {
19 | url(AueteBaseUrl)
20 | }.asDocument()
21 | val areas: MutableList =
22 | doc.select(".container > .row > .main .card").map { area ->
23 | val areaName = area.firstElementChild()!!.selectFirst("a")!!.text().trim()
24 | val videos =
25 | area.select(".threadlist > li").map { it.parseAueteVideo(source.key) }
26 | SourcePage.SingleCartoonPage.WithCover(label = areaName, firstKey = { 0 }) {
27 | withResult {
28 | null to videos
29 | }
30 | }
31 | }.toMutableList()
32 | doc.selectFirst(".card-site-info")
33 | ?.parent()
34 | ?.children()
35 | ?.asSequence()
36 | ?.forEach {
37 | val title = it.selectFirst(".card-header")?.text()?.trim() ?: return@forEach
38 | val videos = it.select(".card-body > .list-ul > li > a").map { videoEl ->
39 | CartoonCoverImpl(
40 | id = videoEl.attr("href").trim('/'),
41 | source = source.key,
42 | url = videoEl.absUrl("href"),
43 | title = videoEl.text().trim()
44 | )
45 | }
46 | if (videos.isEmpty()) {
47 | return@forEach
48 | }
49 | SourcePage.SingleCartoonPage.WithoutCover(label = title, firstKey = { 0 }) {
50 | withResult {
51 | null to videos
52 | }
53 | }.let { page -> areas.add(page) }
54 | }
55 |
56 | areas
57 | }
58 | }
59 | val others = getVideoCategories().map { (pageLabel, pageKey, subTypes) ->
60 | SourcePage.Group(label = pageLabel, newScreen = false) {
61 | withResult {
62 | subTypes.map { (typeName, typeKey) ->
63 | SourcePage.SingleCartoonPage.WithCover(
64 | label = typeName,
65 | firstKey = { 1 }) { page ->
66 | withIoResult {
67 | getVideoOfCategoryAndType(
68 | category = pageKey,
69 | type = typeKey,
70 | page = page
71 | )
72 | }
73 | }
74 | }
75 | }
76 | }
77 | }
78 | return listOf(home) + others
79 | }
80 |
81 | private fun getVideoOfCategoryAndType(
82 | category: String,
83 | type: String,
84 | page: Int
85 | ): Pair> {
86 | val urlBuilder = StringBuilder(AueteBaseUrl)
87 | .append('/')
88 | .append(category)
89 | if (type.isNotEmpty()) {
90 | urlBuilder.append('/')
91 | .append(type)
92 | }
93 | urlBuilder.append("/index")
94 | if (page > 1) {
95 | urlBuilder.append(page)
96 | }
97 | urlBuilder.append(".html")
98 | val doc = commonHttpClient.newGetRequest {
99 | url(urlBuilder.toString())
100 | }.asDocument()
101 | val videos = doc.select(".threadlist > li").map { it.parseAueteVideo(source = source.key) }
102 | val nextPage = if (doc.aueteHaveNextPage()) page + 1 else null
103 | return nextPage to videos
104 |
105 | }
106 |
107 | private fun getVideoCategories() = listOf(
108 | Triple(
109 | "电影",
110 | "Movie",
111 | listOf(
112 | "全部" to "",
113 | "喜剧片" to "xjp",
114 | "动作片" to "dzp",
115 | "爱情片" to "aqp",
116 | "科幻片" to "khp",
117 | "恐怖片" to "kbp",
118 | "惊悚片" to "jsp",
119 | "战争片" to "zzp",
120 | "剧情片" to "jqp"
121 | )
122 | ),
123 | Triple(
124 | "电视剧",
125 | "Tv",
126 | listOf(
127 | "全部" to "",
128 | "美剧" to "oumei",
129 | "韩剧" to "hanju",
130 | "日剧" to "riju",
131 | "泰剧" to "yataiju",
132 | "网剧" to "wangju",
133 | "台剧" to "taiju",
134 | "国产" to "neidi",
135 | "港剧" to "tvbgj",
136 | "英剧" to "yingju",
137 | "外剧" to "waiju"
138 | )
139 | ),
140 | Triple(
141 | "综艺",
142 | "Zy",
143 | listOf(
144 | "全部" to "",
145 | "国综" to "guozong",
146 | "韩综" to "hanzong",
147 | "美综" to "meizong"
148 | )
149 | ),
150 | Triple(
151 | "动漫",
152 | "Dm",
153 | listOf(
154 | "全部" to "",
155 | "动画" to "donghua",
156 | "日漫" to "riman",
157 | "国漫" to "guoman",
158 | "美漫" to "meiman"
159 | )
160 | ),
161 | Triple(
162 | "其他",
163 | "qita",
164 | listOf(
165 | "全部" to "",
166 | "记录片" to "Jlp",
167 | "经典片" to "Jdp",
168 | "经典剧" to "Jdj",
169 | "网大电影" to "wlp",
170 | "国产老电影" to "laodianying"
171 | )
172 | )
173 | )
174 | }
--------------------------------------------------------------------------------
/extension-app/src/main/java/io/github/easybangumiorg/source/aio/auete/AuetePlay.kt:
--------------------------------------------------------------------------------
1 | package io.github.easybangumiorg.source.aio.auete
2 |
3 | import com.heyanle.easybangumi4.source_api.ParserException
4 | import com.heyanle.easybangumi4.source_api.SourceResult
5 | import com.heyanle.easybangumi4.source_api.component.ComponentWrapper
6 | import com.heyanle.easybangumi4.source_api.component.play.PlayComponent
7 | import com.heyanle.easybangumi4.source_api.entity.CartoonSummary
8 | import com.heyanle.easybangumi4.source_api.entity.Episode
9 | import com.heyanle.easybangumi4.source_api.entity.PlayLine
10 | import com.heyanle.easybangumi4.source_api.entity.PlayerInfo
11 | import io.github.easybangumiorg.source.aio.bodyString
12 | import io.github.easybangumiorg.source.aio.commonHttpClient
13 | import io.github.easybangumiorg.source.aio.newGetRequest
14 | import io.github.easybangumiorg.source.aio.withIoResult
15 | import kotlin.io.encoding.ExperimentalEncodingApi
16 |
17 | class AuetePlay : ComponentWrapper(), PlayComponent {
18 | @OptIn(ExperimentalEncodingApi::class)
19 | override suspend fun getPlayInfo(
20 | summary: CartoonSummary,
21 | playLine: PlayLine,
22 | episode: Episode
23 | ): SourceResult = withIoResult {
24 | val html = commonHttpClient.newGetRequest {
25 | url("$AueteBaseUrl/${summary.id}/${episode.id}.html")
26 | }.bodyString()
27 | val idx = html.indexOf(" now")
28 | var start = -1
29 | if (idx > 0) {
30 | for (i in (idx + 4) until html.length) {
31 | val c = html[i]
32 | if (c == '"') {
33 | if (start == -1) {
34 | start = i
35 | } else {
36 | val videoUrl = if (html.substring(idx, start).contains("base64")) {
37 | kotlin.io.encoding.Base64.decode(html.substring(start + 1, i))
38 | .toString(Charsets.UTF_8)
39 | } else {
40 | html.substring(start + 1, i)
41 | }
42 | return@withIoResult PlayerInfo(
43 | uri = videoUrl,
44 | decodeType = PlayerInfo.DECODE_TYPE_HLS
45 | )
46 | }
47 | }
48 | }
49 | }
50 | throw ParserException("未获取到视频链接")
51 | }
52 | }
--------------------------------------------------------------------------------
/extension-app/src/main/java/io/github/easybangumiorg/source/aio/auete/AueteSearch.kt:
--------------------------------------------------------------------------------
1 | package io.github.easybangumiorg.source.aio.auete
2 |
3 | //import com.heyanle.easybangumi4.source_api.SourceResult
4 | import com.heyanle.easybangumi4.source_api.SourceResult
5 | import com.heyanle.easybangumi4.source_api.component.ComponentWrapper
6 | import com.heyanle.easybangumi4.source_api.component.search.SearchComponent
7 | import com.heyanle.easybangumi4.source_api.entity.CartoonCover
8 | import com.heyanle.easybangumi4.source_api.entity.CartoonCoverImpl
9 | import io.github.easybangumiorg.source.aio.asDocument
10 | import io.github.easybangumiorg.source.aio.commonHttpClient
11 | import io.github.easybangumiorg.source.aio.encodeUri
12 | import io.github.easybangumiorg.source.aio.newGetRequest
13 | import io.github.easybangumiorg.source.aio.withIoResult
14 |
15 | class AueteSearch : ComponentWrapper(), SearchComponent {
16 | override fun getFirstSearchKey(keyword: String): Int = 1
17 |
18 | override suspend fun search(
19 | pageKey: Int,
20 | keyword: String
21 | ): SourceResult>> = withIoResult {
22 | val doc = commonHttpClient.newGetRequest {
23 | url("$AueteBaseUrl/auete3so.php?searchword=${keyword.encodeUri()}")
24 | }.asDocument()
25 | val videos = doc.getElementsByClass("threadlist").map { el ->
26 | val linkEl = el.selectFirst("a")!!
27 | CartoonCoverImpl(
28 | id = linkEl.attr("href").trim('/'),
29 | url = linkEl.absUrl("href"),
30 | title = linkEl.text().trim(),
31 | source = source.key,
32 | )
33 | }
34 | null to videos
35 | }
36 | }
--------------------------------------------------------------------------------
/extension-app/src/main/java/io/github/easybangumiorg/source/aio/auete/AueteSource.kt:
--------------------------------------------------------------------------------
1 | package io.github.easybangumiorg.source.aio.auete
2 |
3 | import com.heyanle.easybangumi4.source_api.Source
4 | import com.heyanle.extension_api.ExtensionIconSource
5 | import org.easybangumi.extension.R
6 | import kotlin.reflect.KClass
7 |
8 | class AueteSource : Source, ExtensionIconSource {
9 | override val describe: String
10 | get() = label
11 | override val key: String
12 | get() = "auete"
13 | override val label: String
14 | get() = "Auete影视"
15 | override val version: String
16 | get() = "2.0"
17 | override val versionCode: Int
18 | get() = 4
19 |
20 | override fun getIconResourcesId(): Int = R.drawable.auete
21 |
22 | override fun register(): List> = listOf(
23 | AuetePage::class,
24 | AueteDetail::class,
25 | AueteSearch::class,
26 | AuetePlay::class
27 | )
28 | }
--------------------------------------------------------------------------------
/extension-app/src/main/java/io/github/easybangumiorg/source/aio/auete/Common.kt:
--------------------------------------------------------------------------------
1 | package io.github.easybangumiorg.source.aio.auete
2 |
3 | import com.heyanle.easybangumi4.source_api.entity.CartoonCover
4 | import com.heyanle.easybangumi4.source_api.entity.CartoonCoverImpl
5 | import org.jsoup.nodes.Document
6 | import org.jsoup.nodes.Element
7 |
8 | const val AueteBaseUrl = "https://auete.pro"
9 |
10 | fun Element.parseAueteVideo(source: String): CartoonCover {
11 | val linkEl = selectFirst("a")!!
12 | val id = linkEl.attr("href").trim('/')
13 | val image = selectFirst("img")?.absUrl("src") ?: ""
14 | val episode = selectFirst(".hdtag")?.text()?.trim()
15 | val title = selectFirst(".title")!!.text().trim()
16 | return CartoonCoverImpl(
17 | id = id,
18 | source = source,
19 | url = linkEl.absUrl("href"),
20 | title = title,
21 | coverUrl = image,
22 | intro = episode
23 | )
24 | }
25 |
26 | fun Document.aueteHaveNextPage(): Boolean {
27 | val pageItems = select(".pagination > li")
28 | if (pageItems.isEmpty()) {
29 | return false
30 | }
31 | return pageItems.indexOfLast { it.hasClass("active") } < pageItems.size - 3
32 | }
--------------------------------------------------------------------------------
/extension-app/src/main/java/io/github/easybangumiorg/source/aio/changzhang/ChangZhangDetailPage.kt:
--------------------------------------------------------------------------------
1 | package io.github.easybangumiorg.source.aio.changzhang
2 |
3 | import com.heyanle.easybangumi4.source_api.SourceResult
4 | import com.heyanle.easybangumi4.source_api.component.ComponentWrapper
5 | import com.heyanle.easybangumi4.source_api.component.detailed.DetailedComponent
6 | import com.heyanle.easybangumi4.source_api.entity.Cartoon
7 | import com.heyanle.easybangumi4.source_api.entity.CartoonImpl
8 | import com.heyanle.easybangumi4.source_api.entity.CartoonSummary
9 | import com.heyanle.easybangumi4.source_api.entity.Episode
10 | import com.heyanle.easybangumi4.source_api.entity.PlayLine
11 | import com.heyanle.easybangumi4.source_api.utils.api.OkhttpHelper
12 | import io.github.easybangumiorg.source.aio.asDocument
13 | import io.github.easybangumiorg.source.aio.map
14 | import io.github.easybangumiorg.source.aio.newGetRequest
15 | import io.github.easybangumiorg.source.aio.withIoResult
16 |
17 | class ChangZhangDetailPage(private val okhttpHelper: OkhttpHelper):DetailedComponent,ComponentWrapper() {
18 | override suspend fun getAll(summary: CartoonSummary): SourceResult>> =
19 | withIoResult{
20 | val detailPageUrl = "${ChangZhangSource.BASE_URL}/movie/${summary.id}.html"
21 | val doc = okhttpHelper.cloudflareClient.newGetRequest {
22 | url(detailPageUrl)
23 | }.asDocument()
24 | val episodes = doc.select(".paly_list_btn > a").map { epEl ->
25 | val url = epEl.attr("href")
26 | Episode(
27 | id = url.substring(url.lastIndexOf('/') + 1, url.lastIndexOf('.')),
28 | label = epEl.text().trim(),
29 | order = 0
30 | )
31 | }
32 | val container = doc.selectFirst(".dyxingq")!!
33 | val image = container.selectFirst(".dyimg > img")!!.let {
34 | it.dataset()["original"] ?: it.attr("src")
35 | }
36 | val title = container.selectFirst(".dytext > .moviedteail_tt > h1")!!.text()
37 | val infoList = container.select(".dytext > .moviedteail_list > li").map { it.text().trim() }
38 | val desc = doc.selectFirst(".yp_context")?.text()?.trim()
39 | CartoonImpl(
40 | id = summary.id,
41 | source = source.key,
42 | url = detailPageUrl,
43 | title = title,
44 | intro = infoList.joinToString(separator = "\n"),
45 | coverUrl = image,
46 | description = desc
47 | ) to listOf(PlayLine(id = "1", label = "播放列表", episode = arrayListOf(*episodes.toTypedArray())))
48 | }
49 |
50 | override suspend fun getDetailed(summary: CartoonSummary): SourceResult = getAll(summary).map { it.first }
51 |
52 | override suspend fun getPlayLine(summary: CartoonSummary): SourceResult> = getAll(summary).map { it.second }
53 | }
--------------------------------------------------------------------------------
/extension-app/src/main/java/io/github/easybangumiorg/source/aio/changzhang/ChangZhangPage.kt:
--------------------------------------------------------------------------------
1 | package io.github.easybangumiorg.source.aio.changzhang
2 |
3 | import com.heyanle.easybangumi4.source_api.SourceResult
4 | import com.heyanle.easybangumi4.source_api.component.ComponentWrapper
5 | import com.heyanle.easybangumi4.source_api.component.page.PageComponent
6 | import com.heyanle.easybangumi4.source_api.component.page.SourcePage
7 | import com.heyanle.easybangumi4.source_api.entity.CartoonCover
8 | import com.heyanle.easybangumi4.source_api.utils.api.OkhttpHelper
9 | import io.github.easybangumiorg.source.aio.asDocument
10 | import io.github.easybangumiorg.source.aio.changzhang.ChangZhangSource.Companion.hasNextPage
11 | import io.github.easybangumiorg.source.aio.changzhang.ChangZhangSource.Companion.parseAnime
12 | import io.github.easybangumiorg.source.aio.newGetRequest
13 | import io.github.easybangumiorg.source.aio.withIoResult
14 |
15 | class ChangZhangPage(private val okhttpHelper: OkhttpHelper):PageComponent,ComponentWrapper() {
16 | override fun getPages(): List {
17 | val homePage = SourcePage.Group("首页",newScreen = false){
18 | withIoResult {
19 | val doc = okhttpHelper.cloudflareClient.newGetRequest { url(ChangZhangSource.BASE_URL) }.asDocument()
20 | doc.getElementsByClass("mi_btcon").asSequence().map { groupEl ->
21 | val name = groupEl.selectFirst(".bt_tit a")?.text()?.trim()?:"推荐"
22 | val videos = groupEl.select(".bt_img > ul > li").map { it.parseAnime(source = source.key) }
23 | name to videos
24 | }
25 | .filter { it.second.isNotEmpty() }
26 | .map { (label,videos)->
27 | SourcePage.SingleCartoonPage.WithCover(label = label, firstKey = {0}){
28 | SourceResult.Complete(null to videos)
29 | }
30 | }
31 | .toList()
32 | }
33 | }
34 |
35 | val movie = SourcePage.Group("电影",newScreen = false){
36 | withIoResult {
37 | val doc = okhttpHelper.cloudflareClient.newGetRequest { url("${ChangZhangSource.BASE_URL}/movie_bt") }.asDocument()
38 | doc.select("#beautiful-taxonomy-filters-tax-movie_bt_tags > a").asSequence()
39 | .map {
40 | it.text() to (it.attr("cat-url").takeIf { it.isNotEmpty() }?: it.attr("href"))
41 | }
42 | .map { (label,url)->
43 | SourcePage.SingleCartoonPage.WithCover(label = label, firstKey = {1}){page->
44 | withIoResult { fetchVideoCategoryPage(url,page) }
45 | }
46 | }
47 | .toList()
48 | }
49 | }
50 | val otherPages = listOf(
51 | "美剧" to "meijutt",
52 | "日剧" to "riju",
53 | "韩剧" to "hanjutv",
54 | "番剧" to "fanju",
55 | "电视剧" to "dsj",
56 | "国产剧" to "gcj",
57 | "剧场版" to "dongmanjuchangban",
58 | "海外剧" to "haiwaijuqita",
59 | "热映中" to "benyueremen"
60 | ).map { (label,urlSuffix)->
61 | SourcePage.SingleCartoonPage.WithCover(label = label, firstKey = {1}){page->
62 | withIoResult {
63 | fetchVideoCategoryPage("${ChangZhangSource.BASE_URL}/$urlSuffix",page)
64 | }
65 | }
66 | }
67 | return listOf(homePage,movie)+otherPages
68 | }
69 |
70 | private fun fetchVideoCategoryPage(url:String,page:Int): Pair> {
71 | val actualUrl = if (page>1) "$url/page/$page" else url
72 | val doc = okhttpHelper.cloudflareClient.newGetRequest { url(actualUrl) }.asDocument()
73 | val videos = doc.select(".bt_img > ul > li").map { it.parseAnime(source = source.key) }
74 | val next = if (doc.hasNextPage()) page +1 else null
75 | return next to videos
76 | }
77 | }
--------------------------------------------------------------------------------
/extension-app/src/main/java/io/github/easybangumiorg/source/aio/changzhang/ChangZhangSearchPage.kt:
--------------------------------------------------------------------------------
1 | package io.github.easybangumiorg.source.aio.changzhang
2 |
3 | import com.heyanle.easybangumi4.source_api.SourceResult
4 | import com.heyanle.easybangumi4.source_api.component.ComponentWrapper
5 | import com.heyanle.easybangumi4.source_api.component.search.SearchComponent
6 | import com.heyanle.easybangumi4.source_api.entity.CartoonCover
7 | import com.heyanle.easybangumi4.source_api.utils.api.OkhttpHelper
8 | import io.github.easybangumiorg.source.aio.asDocument
9 | import io.github.easybangumiorg.source.aio.changzhang.ChangZhangSource.Companion.hasNextPage
10 | import io.github.easybangumiorg.source.aio.changzhang.ChangZhangSource.Companion.parseAnime
11 | import io.github.easybangumiorg.source.aio.encodeUri
12 | import io.github.easybangumiorg.source.aio.newGetRequest
13 | import io.github.easybangumiorg.source.aio.newRequest
14 | import io.github.easybangumiorg.source.aio.withIoResult
15 | import okhttp3.Cookie
16 | import okhttp3.FormBody
17 |
18 | class ChangZhangSearchPage(private val okhttpHelper: OkhttpHelper):SearchComponent,ComponentWrapper() {
19 |
20 | var searchUrl:String? = null
21 |
22 | override fun getFirstSearchKey(keyword: String): Int {
23 | return 1
24 | }
25 |
26 | override suspend fun search(
27 | pageKey: Int,
28 | keyword: String
29 | ): SourceResult>> = withIoResult{
30 | if (searchUrl == null) {
31 | searchUrl = okhttpHelper.client.newGetRequest {
32 | url(ChangZhangSource.BASE_URL)
33 | }
34 | .asDocument()
35 | .selectFirst(".w-search-form")
36 | ?.attr("action")
37 | ?: throw RuntimeException("未获取到搜索视频链接")
38 | }
39 | val doc = okhttpHelper.cloudflareClient.newGetRequest {
40 | url("$searchUrl?q=${keyword.encodeUri()}&f=_all&p=$pageKey")
41 | }.use { resp ->
42 | val respDoc = resp.asDocument()
43 | if (respDoc.title().contains("人机验证")) {
44 | val value = Cookie.parseAll(resp.request.url, resp.headers)
45 | .lastOrNull { it.name == "result" }?.value
46 | ?: throw RuntimeException("Cookie中无验证码")
47 | okhttpHelper.cloudflareClient.newRequest {
48 | url(resp.request.url)
49 | post(FormBody.Builder().add("result", value).build())
50 | }
51 | okhttpHelper.cloudflareClient.newGetRequest { url(resp.request.url) }.asDocument()
52 | } else {
53 | respDoc
54 | }
55 | }
56 | val videos = doc.select(".search_list > ul > li").map { it.parseAnime(source = source.key) }
57 | val nextPage = if (doc.hasNextPage()) pageKey + 1 else null
58 | nextPage to videos
59 | }
60 | }
--------------------------------------------------------------------------------
/extension-app/src/main/java/io/github/easybangumiorg/source/aio/changzhang/ChangZhangSource.kt:
--------------------------------------------------------------------------------
1 | package io.github.easybangumiorg.source.aio.changzhang
2 |
3 | import com.heyanle.easybangumi4.source_api.entity.CartoonCover
4 | import com.heyanle.easybangumi4.source_api.entity.CartoonCoverImpl
5 | import com.heyanle.extension_api.ExtensionIconSource
6 | import com.heyanle.extension_api.ExtensionSource
7 | import org.easybangumi.extension.R
8 | import org.jsoup.nodes.Document
9 | import org.jsoup.nodes.Element
10 | import kotlin.reflect.KClass
11 |
12 | class ChangZhangSource:ExtensionSource(),ExtensionIconSource {
13 | override val describe: String
14 | get() = "厂长资源"
15 | override val label: String
16 | get() = describe
17 | override val sourceKey: String
18 | get() = "changzhang"
19 | override val version: String
20 | get() = "1.0"
21 | override val versionCode: Int
22 | get() = 1
23 |
24 | override fun getIconResourcesId(): Int= R.drawable.changzhang
25 |
26 | override fun register(): List> = listOf(
27 | ChangZhangPage::class,
28 | ChangZhangDetailPage::class,
29 | ChangZhangSearchPage::class,
30 | ChangZhangPlayPage::class
31 | )
32 |
33 | companion object {
34 | const val BASE_URL = "https://www.czzy88.com"
35 |
36 | fun Element.parseAnime(source:String): CartoonCover {
37 | val url = selectFirst("a")!!.absUrl("href")
38 | val image = selectFirst("img")!!.let {
39 | it.dataset()["original"] ?: it.attr("src")
40 | }
41 | val ep = selectFirst(".jidi")?.text()?.trim()
42 | val title = selectFirst(".dytit")!!.text().trim()
43 | return CartoonCoverImpl(
44 | id = url.substring(url.lastIndexOf('/') + 1, url.lastIndexOf('.')),
45 | url = url,
46 | title = title,
47 | source = source,
48 | intro = ep,
49 | coverUrl = image
50 | )
51 | }
52 |
53 | fun Document.hasNextPage(): Boolean {
54 | val pageItems = select(".pagenavi_txt > a")
55 | if (pageItems.isEmpty()) {
56 | return false
57 | }
58 | return !pageItems.last()!!.hasClass("current")
59 | }
60 | }
61 | }
--------------------------------------------------------------------------------
/extension-app/src/main/java/io/github/easybangumiorg/source/aio/fengche/Common.kt:
--------------------------------------------------------------------------------
1 | package io.github.easybangumiorg.source.aio.fengche
2 |
3 | import android.util.Log
4 | import com.heyanle.easybangumi4.source_api.entity.CartoonCover
5 | import com.heyanle.easybangumi4.source_api.entity.CartoonCoverImpl
6 | import io.github.easybangumiorg.source.aio.asDocument
7 | import io.github.easybangumiorg.source.aio.commonHttpClient
8 | import io.github.easybangumiorg.source.aio.newGetRequest
9 | import org.jsoup.nodes.Document
10 | import org.jsoup.nodes.Element
11 | import java.util.concurrent.locks.ReentrantLock
12 | import kotlin.concurrent.withLock
13 |
14 |
15 | //private val urlPageUrl = "https://wedm.cc/"
16 | //
17 | //private val baseUrlFallback = "https://www.886dm.tv"
18 | //
19 | //@Volatile
20 | //private var _fengCheBaseUrl: String? = null
21 | //
22 | //val FengCheBaseUrl: String
23 | // get() = requireFengCheBaseUrl()
24 | //
25 | //private val baseUrlLock = ReentrantLock()
26 | //
27 | //private fun requireFengCheBaseUrl(): String {
28 | // if (_fengCheBaseUrl == null) {
29 | // baseUrlLock.withLock {
30 | // if (_fengCheBaseUrl == null) {
31 | // _fengCheBaseUrl = runCatching {
32 | // requestFengCheBaseUrl()
33 | // }.onFailure {
34 | // Log.e("FengCheSourceCommon", "requireFengCheBaseUrl:${it.message}", it)
35 | // }
36 | // .getOrNull() ?: baseUrlFallback
37 | // }
38 | // }
39 | // }
40 | // return _fengCheBaseUrl!!
41 | //}
42 | //
43 | //private fun requestFengCheBaseUrl(): String {
44 | // val doc = commonHttpClient.newGetRequest {
45 | // url(urlPageUrl)
46 | // }.asDocument()
47 | //
48 | // val website = doc.selectFirst(".main .speedlist li a i")?.text()?.trim()?.let { text ->
49 | // text
50 | // }
51 | // return if (website?.isNotBlank() == true) {
52 | // "https://$website"
53 | // } else {
54 | // baseUrlFallback
55 | // }
56 | //}
57 |
58 |
59 | fun String.extractFengCheIdFromUrl() =
60 | this.substring(this.lastIndexOf('/') + 1, this.lastIndexOf('.'))
61 |
62 |
63 | fun Document.hasNextPage(): Boolean =
64 | this.getElementById("aapages")
65 | ?.children()
66 | ?.findLast { it.hasClass("pagenow") }
67 | ?.nextElementSibling()
68 | ?.tagName() === "a"
69 |
70 | fun Element.parseFengCheAnime(sourceKey: String): CartoonCover {
71 | val linkEl = selectFirst("a")!!
72 | val url = linkEl.absUrl("href")
73 | val imageUrl = selectFirst(".img_wrapper")?.dataset()?.get("original")
74 | val episode = linkEl.children().last()?.text()?.trim() ?: ""
75 | val title = linkEl.attr("title").takeIf { it.isNotEmpty() }?: linkEl.text().trim()
76 | return CartoonCoverImpl(
77 | id = url.extractFengCheIdFromUrl(),
78 | url = url,
79 | title = title,
80 | source = sourceKey,
81 | coverUrl = imageUrl,
82 | intro = episode
83 | )
84 | }
--------------------------------------------------------------------------------
/extension-app/src/main/java/io/github/easybangumiorg/source/aio/fengche/FengCheDetail.kt:
--------------------------------------------------------------------------------
1 | package io.github.easybangumiorg.source.aio.fengche
2 |
3 | import com.heyanle.easybangumi4.source_api.SourceResult
4 | import com.heyanle.easybangumi4.source_api.component.ComponentWrapper
5 | import com.heyanle.easybangumi4.source_api.component.detailed.DetailedComponent
6 | import com.heyanle.easybangumi4.source_api.entity.Cartoon
7 | import com.heyanle.easybangumi4.source_api.entity.CartoonImpl
8 | import com.heyanle.easybangumi4.source_api.entity.CartoonSummary
9 | import com.heyanle.easybangumi4.source_api.entity.Episode
10 | import com.heyanle.easybangumi4.source_api.entity.PlayLine
11 | import com.heyanle.easybangumi4.source_api.withResult
12 | //import com.heyanle.easybangumi4.source_api.withResult
13 | import io.github.easybangumiorg.source.aio.asDocument
14 | import io.github.easybangumiorg.source.aio.commonHttpClient
15 | import io.github.easybangumiorg.source.aio.map
16 | import io.github.easybangumiorg.source.aio.newGetRequest
17 | import kotlinx.coroutines.Dispatchers
18 |
19 | class FengCheDetail (
20 | private val hostUrlHelper: FengCheHostUrlHelper
21 | ): ComponentWrapper(), DetailedComponent {
22 | override suspend fun getAll(summary: CartoonSummary): SourceResult>> =
23 | withResult(Dispatchers.IO) {
24 | val document = commonHttpClient.newGetRequest {
25 | url("${hostUrlHelper.fengcheBaseUrl}/video/${summary.id}.html")
26 | }.asDocument()
27 | val imageUrl =
28 | document.selectFirst(".con_c1 .img_wrapper")!!.dataset()["original"]!!
29 | val detailContainer = document.selectFirst(".con_xinxi")!!
30 | val title = detailContainer.selectFirst("a")!!.text().trim()
31 | val intro = detailContainer.selectFirst(".yplx_c1")!!.children()
32 | .find { it.text().contains("类型:") }
33 | ?.children()
34 | ?.lastOrNull()
35 | ?.text()
36 | ?.trim()
37 | ?: ""
38 | val desc = detailContainer.selectFirst(".yplx_c3")?.children()
39 | ?.lastOrNull()
40 | ?.text()
41 | ?.trim()
42 | val playlistNames = document.select(".playlist-tab a").map { it.text().trim() }
43 | val episodesGroup =
44 | document.select(".con_juji_bg > .tab-content > .con_c2_list").asSequence()
45 | .map { epContainer ->
46 | epContainer.select("a").map {
47 | Episode(
48 | id = it.attr("href").extractFengCheIdFromUrl(),
49 | label = it.text().trim(),
50 | order = 1
51 | )
52 | }
53 | }
54 | .toList()
55 | val playlists = List(playlistNames.size.coerceAtMost(episodesGroup.size)) {
56 | PlayLine(
57 | id = it.toString(),
58 | label = playlistNames[it],
59 | episode = arrayListOf(*episodesGroup[it].toTypedArray())
60 | )
61 | }
62 | val cartoon = CartoonImpl(
63 | id = summary.id,
64 | source = source.key,
65 | url = "${hostUrlHelper.fengcheBaseUrl}/video/${summary.id}.html",
66 | title = title,
67 | coverUrl = imageUrl,
68 | description = desc,
69 | intro = intro
70 | )
71 | cartoon to playlists
72 | }
73 |
74 | override suspend fun getDetailed(summary: CartoonSummary): SourceResult {
75 | return getAll(summary).map { it.first }
76 | }
77 |
78 | override suspend fun getPlayLine(summary: CartoonSummary): SourceResult> {
79 | return getAll(summary).map { it.second }
80 | }
81 |
82 | }
--------------------------------------------------------------------------------
/extension-app/src/main/java/io/github/easybangumiorg/source/aio/fengche/FengCheHostUrlHelper.kt:
--------------------------------------------------------------------------------
1 | package io.github.easybangumiorg.source.aio.fengche
2 |
3 | import com.heyanle.easybangumi4.source_api.utils.api.PreferenceHelper
4 | import io.github.easybangumiorg.source.aio.asDocument
5 | import io.github.easybangumiorg.source.aio.commonHttpClient
6 | import io.github.easybangumiorg.source.aio.newGetRequest
7 |
8 | /**
9 | * Created by heyanlin on 2024/5/23.
10 | */
11 | class FengCheHostUrlHelper(
12 | private val preferenceHelper: PreferenceHelper
13 | ) {
14 |
15 | private val autoHostUrl: Boolean
16 | get() = preferenceHelper.get("auto_host_url", "false") == "true"
17 |
18 | private val diyUrl: String
19 | get() = preferenceHelper.get("BaseUrl", "http://www.fcdm9.com/")
20 |
21 | private val urlPageUrl = "https://wedm.cc/"
22 |
23 | private val fengcheBaseUrlAutoUrl: String by lazy {
24 | val doc = commonHttpClient.newGetRequest {
25 | url(urlPageUrl)
26 | }.asDocument()
27 |
28 | val website = doc.selectFirst(".main .speedlist li a i")?.text()?.trim()?.let { text ->
29 | text
30 | }
31 | return@lazy if (website?.isNotBlank() == true) {
32 | "https://$website"
33 | } else {
34 | diyUrl
35 | }
36 | }
37 |
38 | val fengcheBaseUrl: String
39 | get() = (if(autoHostUrl) fengcheBaseUrlAutoUrl else diyUrl).let {
40 | if (it.endsWith("/")){
41 | it.substring(0, it.length - 1)
42 | }else{
43 | it
44 | }
45 | }
46 |
47 |
48 |
49 |
50 | }
--------------------------------------------------------------------------------
/extension-app/src/main/java/io/github/easybangumiorg/source/aio/fengche/FengChePage.kt:
--------------------------------------------------------------------------------
1 | package io.github.easybangumiorg.source.aio.fengche
2 |
3 | import com.heyanle.easybangumi4.source_api.component.ComponentWrapper
4 | import com.heyanle.easybangumi4.source_api.component.page.PageComponent
5 | import com.heyanle.easybangumi4.source_api.component.page.SourcePage
6 | import com.heyanle.easybangumi4.source_api.entity.CartoonCover
7 | import com.heyanle.easybangumi4.source_api.entity.CartoonCoverImpl
8 | import com.heyanle.easybangumi4.source_api.utils.api.OkhttpHelper
9 | import com.heyanle.easybangumi4.source_api.withResult
10 | import io.github.easybangumiorg.source.aio.asDocument
11 | import io.github.easybangumiorg.source.aio.commonHttpClient
12 | import io.github.easybangumiorg.source.aio.encodeUri
13 | import io.github.easybangumiorg.source.aio.newGetRequest
14 | import kotlinx.coroutines.Dispatchers
15 | import kotlinx.coroutines.sync.Mutex
16 | import kotlinx.coroutines.sync.withLock
17 |
18 | class FengChePage(
19 | val okhttpHelper: OkhttpHelper,
20 | private val hostUrlHelper: FengCheHostUrlHelper
21 | ) : ComponentWrapper(), PageComponent {
22 |
23 | private val categoryLock = Mutex()
24 |
25 | private var categories: List? = null
26 |
27 | override fun getPages(): List {
28 | val home = SourcePage.Group("首页", false) {
29 | withResult(Dispatchers.IO) {
30 | val document = okhttpHelper.client.newGetRequest {
31 | url(hostUrlHelper.fengcheBaseUrl)
32 | }.asDocument()
33 | val pages = mutableListOf()
34 | document.select("body > div.wrapper").forEach { wrapperEl ->
35 | val videoEls = wrapperEl.select(".picList > li")
36 | .takeIf { it.isNotEmpty() }
37 | ?: wrapperEl.select(".c2_list > li")
38 | val title = wrapperEl.selectFirst(".cont_title")
39 | ?: wrapperEl.selectFirst(".title > .t_head")
40 | title ?: return@forEach
41 | val videos = videoEls.map { it.parseFengCheAnime(source.key)}
42 | if (videos.isNotEmpty()) {
43 | val page =
44 | SourcePage.SingleCartoonPage.WithCover(title.text().trim(), { 0 }) {
45 | withResult {
46 | null to videos
47 | }
48 | }
49 | pages.add(page)
50 | }
51 | }
52 | pages
53 | }
54 | }
55 | val categoryPages = listOf(
56 | "ribendongman" to "日本动漫",
57 | "guochandongman" to "国产动漫",
58 | "dongmandianying" to "动漫电影",
59 | "oumeidongman" to "欧美动漫",
60 | ).map { (typeKey, label) ->
61 | SourcePage.Group(label = label, false) {
62 | withResult(Dispatchers.IO) {
63 | val categories = getVideoCategories()
64 | categories.map { category ->
65 | SourcePage.SingleCartoonPage.WithCover(
66 | category.label,
67 | firstKey = { 1 }) { page ->
68 | withResult(Dispatchers.IO) {
69 | requestVideoOfCategory(typeKey, category.value, page)
70 | }
71 | }
72 | }
73 | }
74 | }
75 | }
76 | return listOf(home) + categoryPages
77 | }
78 |
79 | private fun requestVideoOfCategory(
80 | typeKey: String,
81 | category: String,
82 | page: Int
83 | ): Pair> {
84 | val pageUrl = "${hostUrlHelper.fengcheBaseUrl}/show/$typeKey---$category-----$page---.html"
85 | val document = okhttpHelper.client.newGetRequest {
86 | url(pageUrl)
87 | }.asDocument()
88 | val videos = document.select(".wrapper > .culum_con > .c2_list > li")
89 | .map { it.parseFengCheAnime(source.key) }
90 | val nextPage = if (document.hasNextPage()) page + 1 else null
91 | return nextPage to videos
92 | }
93 |
94 |
95 | private suspend fun getVideoCategories(): List {
96 | if (categories == null) {
97 | categoryLock.withLock {
98 | if (categories == null) {
99 | categories = requestVideoCategories()
100 | }
101 | }
102 | }
103 | return categories!!
104 | }
105 |
106 | private fun requestVideoCategories(): List {
107 | val document = okhttpHelper.client.newGetRequest {
108 | url("${hostUrlHelper.fengcheBaseUrl}/type/ribendongman.html")
109 | }.asDocument()
110 | val row =
111 | document.select("body > div.wrapper.culum_list > div.conx.star_bot > div > div")[1]
112 | val ignoreKey = "ribendongman"
113 | val linkList = row.getElementsByTag("a")
114 | val valueIndex = linkList.last()!!.attr("href").run {
115 | substring(lastIndexOf('/') + 1, lastIndexOf('.'))
116 | }
117 | .split('-')
118 | .indexOfFirst { it.isNotEmpty() && it != ignoreKey }
119 | // 忽略第一个
120 | val categories = linkList.map {el->
121 | val value = el.attr("href").run {
122 | substring(lastIndexOf('/') + 1, lastIndexOf('.'))
123 | }
124 | .split('-')[valueIndex]
125 | VideoCategory(label = el.text().trim(), value = value)
126 | }
127 | return categories
128 | }
129 |
130 | private data class VideoCategory(
131 | val label: String,
132 | val value: String
133 | )
134 | }
--------------------------------------------------------------------------------
/extension-app/src/main/java/io/github/easybangumiorg/source/aio/fengche/FengChePlay.kt:
--------------------------------------------------------------------------------
1 | package io.github.easybangumiorg.source.aio.fengche
2 |
3 | import android.util.Base64
4 | import com.google.gson.Gson
5 | import com.heyanle.easybangumi4.source_api.SourceResult
6 | import com.heyanle.easybangumi4.source_api.component.ComponentWrapper
7 | import com.heyanle.easybangumi4.source_api.component.play.PlayComponent
8 | import com.heyanle.easybangumi4.source_api.entity.CartoonSummary
9 | import com.heyanle.easybangumi4.source_api.entity.Episode
10 | import com.heyanle.easybangumi4.source_api.entity.PlayLine
11 | import com.heyanle.easybangumi4.source_api.entity.PlayerInfo
12 | import com.heyanle.easybangumi4.source_api.withResult
13 | import io.github.easybangumiorg.source.aio.bodyString
14 | import io.github.easybangumiorg.source.aio.commonHttpClient
15 | import io.github.easybangumiorg.source.aio.newGetRequest
16 | import kotlinx.coroutines.Dispatchers
17 | import javax.crypto.Cipher
18 | import javax.crypto.spec.IvParameterSpec
19 | import javax.crypto.spec.SecretKeySpec
20 |
21 | class FengChePlay(
22 | private val hostUrlHelper: FengCheHostUrlHelper
23 | ) : ComponentWrapper(), PlayComponent {
24 | override suspend fun getPlayInfo(
25 | summary: CartoonSummary,
26 | playLine: PlayLine,
27 | episode: Episode
28 | ): SourceResult = withResult(Dispatchers.IO) {
29 | val newHtml = commonHttpClient.newGetRequest {
30 | url("https://danmu.yhdmjx.com/m3u8.php?url=" + extractPlayerParam(episode.id))
31 | }.bodyString()
32 | val btToken = extractBtToken(newHtml)
33 | val encryptedUrl = extractEncryptedUrl(newHtml)
34 |
35 | val plainUrl = Cipher.getInstance("AES/CBC/PKCS5Padding").run {
36 | init(
37 | Cipher.DECRYPT_MODE,
38 | SecretKeySpec("57A891D97E332A9D".toByteArray(), "AES"),
39 | IvParameterSpec(btToken.toByteArray(Charsets.UTF_8))
40 | )
41 | doFinal(Base64.decode(encryptedUrl, Base64.DEFAULT))
42 | }.toString(Charsets.UTF_8)
43 | PlayerInfo(
44 | uri = plainUrl
45 | )
46 | }
47 |
48 | private fun extractPlayerParam(episodeId: String): String {
49 | val html = commonHttpClient.newGetRequest {
50 | url("${hostUrlHelper.fengcheBaseUrl}/play/$episodeId.html")
51 | }.bodyString()
52 | val startIndex = html.indexOf("player_aaaa")
53 | val startBracketIndex = html.indexOf('{', startIndex + 1)
54 | var endIndex = -1
55 | var bracketCount = 0
56 | for (i in (startBracketIndex + 1)..>(
72 | html.substring(startBracketIndex, endIndex + 1),
73 | Map::class.java
74 | )["url"] ?: throw RuntimeException("未获取到播放信息")
75 | }
76 |
77 | private fun extractEncryptedUrl(html: String): String {
78 | val key = "getVideoInfo("
79 | val i1 = html.indexOf(key)
80 | val i2 = html.indexOf('"', i1 + key.length)
81 | val i3 = html.indexOf('"', i2 + 1)
82 | return html.substring(i2 + 1, i3)
83 | }
84 |
85 | private fun extractBtToken(html: String): String {
86 | val i1 = html.indexOf("bt_token")
87 | val i2 = html.indexOf('"', i1)
88 | val i3 = html.indexOf('"', i2 + 1)
89 | return html.substring(i2 + 1, i3)
90 | }
91 |
92 | }
--------------------------------------------------------------------------------
/extension-app/src/main/java/io/github/easybangumiorg/source/aio/fengche/FengChePrefer.kt:
--------------------------------------------------------------------------------
1 | package io.github.easybangumiorg.source.aio.fengche
2 |
3 | import com.heyanle.easybangumi4.source_api.component.ComponentWrapper
4 | import com.heyanle.easybangumi4.source_api.component.preference.PreferenceComponent
5 | import com.heyanle.easybangumi4.source_api.component.preference.SourcePreference
6 | import com.heyanle.easybangumi4.source_api.component.search.SearchComponent
7 |
8 | /**
9 | * Created by heyanlin on 2024/5/23.
10 | */
11 | class FengChePrefer : ComponentWrapper(), PreferenceComponent {
12 |
13 | override fun register(): List = listOf(
14 | SourcePreference.Switch("自动从 wedm.cc 获取网址", "auto_host_url", true),
15 | SourcePreference.Edit("自定义网址(当自动关闭时才有效)", "BaseUrl", "http://www.fcdm9.com/"),
16 | )
17 | }
--------------------------------------------------------------------------------
/extension-app/src/main/java/io/github/easybangumiorg/source/aio/fengche/FengCheSearch.kt:
--------------------------------------------------------------------------------
1 | package io.github.easybangumiorg.source.aio.fengche
2 |
3 | import com.heyanle.easybangumi4.source_api.SourceResult
4 | import com.heyanle.easybangumi4.source_api.component.ComponentWrapper
5 | import com.heyanle.easybangumi4.source_api.component.search.SearchComponent
6 | import com.heyanle.easybangumi4.source_api.entity.CartoonCover
7 | import com.heyanle.easybangumi4.source_api.entity.CartoonCoverImpl
8 | import com.heyanle.easybangumi4.source_api.withResult
9 | import io.github.easybangumiorg.source.aio.asDocument
10 | import io.github.easybangumiorg.source.aio.commonHttpClient
11 | import io.github.easybangumiorg.source.aio.encodeUri
12 | import io.github.easybangumiorg.source.aio.newGetRequest
13 | import kotlinx.coroutines.Dispatchers
14 |
15 | class FengCheSearch(
16 | private val hostUrlHelper: FengCheHostUrlHelper
17 | ) : ComponentWrapper(), SearchComponent {
18 |
19 | override fun getFirstSearchKey(keyword: String): Int = 1
20 |
21 | override suspend fun search(
22 | pageKey: Int,
23 | keyword: String
24 | ): SourceResult>> = withResult(Dispatchers.IO) {
25 | val document = commonHttpClient.newGetRequest {
26 | url("${hostUrlHelper.fengcheBaseUrl}/search/${keyword.encodeUri()}----------$pageKey---.html")
27 | }.asDocument()
28 | val videos =
29 | document.select(".sear_con > .reusltbox").map { box->
30 | val link = box.selectFirst("a")!!.absUrl("href")
31 | val img = box.selectFirst(".img_wrapper")?.dataset()?.get("original")
32 | val title = box.selectFirst(".result_title > a")!!.text().trim()
33 | CartoonCoverImpl(
34 | id = link.extractFengCheIdFromUrl(),
35 | source = source.key,
36 | url = link,
37 | title = title,
38 | coverUrl = img
39 | )
40 | }
41 | val nextPage = if (document.hasNextPage() && videos.isNotEmpty()) pageKey + 1 else null
42 | nextPage to videos
43 | }
44 |
45 | }
--------------------------------------------------------------------------------
/extension-app/src/main/java/io/github/easybangumiorg/source/aio/fengche/FengCheSource.kt:
--------------------------------------------------------------------------------
1 | package io.github.easybangumiorg.source.aio.fengche
2 |
3 | import com.heyanle.easybangumi4.source_api.Source
4 | import com.heyanle.extension_api.ExtensionIconSource
5 | import org.easybangumi.extension.R
6 | import kotlin.reflect.KClass
7 |
8 | class FengCheSource : Source, ExtensionIconSource {
9 | override val describe: String
10 | get() = "风车动漫"
11 | override val key: String
12 | get() = "wedm.cc"
13 | override val label: String
14 | get() = "风车动漫"
15 | override val version: String
16 | get() = "2.1"
17 | override val versionCode: Int
18 | get() = 5
19 |
20 | override fun getIconResourcesId(): Int = R.drawable.fengche
21 |
22 | override fun register(): List> = listOf(
23 | FengChePage::class,
24 | FengCheDetail::class,
25 | FengChePlay::class,
26 | FengCheSearch::class,
27 | FengChePrefer::class,
28 | FengCheHostUrlHelper::class
29 | )
30 | }
--------------------------------------------------------------------------------
/extension-app/src/main/java/io/github/easybangumiorg/source/aio/libvio/LibVioDetail.kt:
--------------------------------------------------------------------------------
1 | package io.github.easybangumiorg.source.aio.libvio
2 |
3 | import com.heyanle.easybangumi4.source_api.SourceResult
4 | import com.heyanle.easybangumi4.source_api.component.ComponentWrapper
5 | import com.heyanle.easybangumi4.source_api.component.detailed.DetailedComponent
6 | import com.heyanle.easybangumi4.source_api.entity.Cartoon
7 | import com.heyanle.easybangumi4.source_api.entity.CartoonImpl
8 | import com.heyanle.easybangumi4.source_api.entity.CartoonSummary
9 | import com.heyanle.easybangumi4.source_api.entity.Episode
10 | import com.heyanle.easybangumi4.source_api.entity.PlayLine
11 | import io.github.easybangumiorg.source.aio.asDocument
12 | import io.github.easybangumiorg.source.aio.map
13 | import io.github.easybangumiorg.source.aio.withIoResult
14 |
15 | class LibVioDetail(private val libVioSource: LibVioSource) : ComponentWrapper(), DetailedComponent {
16 | override suspend fun getAll(summary: CartoonSummary): SourceResult>> =
17 | withIoResult {
18 | val doc = libVioSource.requestWithFunCDNInterceptor {
19 | url("${LibVioSource.BASE_URL}/detail/${summary.id}.html")
20 | get()
21 | }.asDocument()
22 | val playlists = mutableListOf()
23 | for (playlistEl in doc.select(".stui-vodlist__head > .stui-content__playlist")) {
24 | val name =
25 | playlistEl.previousElementSibling()?.takeIf { it.hasClass("stui-pannel__head") }
26 | ?.text()?.trim() ?: continue
27 | if (name.contains("下载") || name.contains("网盘") || name.contains("云盘")) {
28 | continue
29 | }
30 | val episodes = playlistEl.select("a").map { el ->
31 | Episode(
32 | label = el.text(),
33 | order = 0,
34 | id = el.attr("href")
35 | .let { it.substring(it.lastIndexOf('/') + 1, it.lastIndexOf('.')) }
36 | )
37 | }
38 | if (episodes.isEmpty()) {
39 | continue
40 | }
41 | playlists.add(
42 | PlayLine(
43 | id = name,
44 | label = name,
45 | episode = arrayListOf(*episodes.toTypedArray())
46 | )
47 | )
48 | }
49 |
50 | val detailContainer =
51 | doc.selectFirst(".stui-content") ?: throw RuntimeException("未找到视频详情")
52 | val img = detailContainer.selectFirst("img")?.dataset()?.get("original") ?: ""
53 | val title = detailContainer.selectFirst(".stui-content__detail > .title")!!.text()
54 | val infos = detailContainer.select(".stui-content__detail > .data").map { it.text() }
55 | val desc =
56 | detailContainer.selectFirst(".stui-content__detail > .desc > .detail-content")
57 | ?.text()
58 | ?: ""
59 | val cartoon = CartoonImpl(
60 | id = summary.id,
61 | source = libVioSource.key,
62 | title = title,
63 | coverUrl = img,
64 | intro = infos.joinToString(separator = "\n"),
65 | description = desc,
66 | url = "${LibVioSource.BASE_URL}/detail/${summary.id}.html"
67 | )
68 | cartoon to playlists
69 | }
70 |
71 | override suspend fun getDetailed(summary: CartoonSummary): SourceResult =
72 | getAll(summary).map { it.first }
73 |
74 | override suspend fun getPlayLine(summary: CartoonSummary): SourceResult> =
75 | getAll(summary).map { it.second }
76 |
77 | }
--------------------------------------------------------------------------------
/extension-app/src/main/java/io/github/easybangumiorg/source/aio/libvio/LibVioPage.kt:
--------------------------------------------------------------------------------
1 | package io.github.easybangumiorg.source.aio.libvio
2 |
3 | import android.content.Context
4 | import com.heyanle.easybangumi4.source_api.component.ComponentWrapper
5 | import com.heyanle.easybangumi4.source_api.component.page.PageComponent
6 | import com.heyanle.easybangumi4.source_api.component.page.SourcePage
7 | import com.heyanle.easybangumi4.source_api.entity.CartoonCover
8 | import com.heyanle.easybangumi4.source_api.utils.api.OkhttpHelper
9 | import com.heyanle.easybangumi4.source_api.utils.api.StringHelper
10 | import com.heyanle.easybangumi4.source_api.withResult
11 | import io.github.easybangumiorg.source.aio.asDocument
12 | import io.github.easybangumiorg.source.aio.withIoResult
13 |
14 | class LibVioPage(
15 | private val libVioSource: LibVioSource,
16 | context: Context,
17 | okhttpHelper: OkhttpHelper,
18 | stringHelper: StringHelper
19 | ) : ComponentWrapper(), PageComponent {
20 |
21 | init {
22 | libVioSource.init(context, okhttpHelper, stringHelper)
23 | }
24 |
25 | override fun getPages(): List {
26 | val pages =
27 | listOf("电影" to "1", "剧集" to "2", "动漫" to "4").map { (groupName, groupKey) ->
28 | SourcePage.Group(label = groupName, newScreen = false) {
29 | withIoResult {
30 | val doc = libVioSource.requestWithFunCDNInterceptor {
31 | url("${LibVioSource.BASE_URL}/type/$groupKey.html")
32 | get()
33 | }.asDocument()
34 | val categories =
35 | listOf("全部" to "") + doc.getElementById("screenbox")!!.child(0)
36 | .children()
37 | .let {
38 | it.subList(1, it.size)
39 | }.map { el ->
40 | val link = el.getElementsByTag("a")[0]
41 | val url = link.attr("href")
42 | val key =
43 | url.substring(
44 | url.lastIndexOf('/') + 1,
45 | url.lastIndexOf('.')
46 | )
47 | .split('-')
48 | .asSequence()
49 | .filter { it.isNotEmpty() }
50 | .last()
51 | link.text().trim() to key
52 | }.distinctBy { it.second }
53 |
54 | categories.map { (categoryName, categoryKey) ->
55 | SourcePage.SingleCartoonPage.WithCover(
56 | label = categoryName,
57 | firstKey = { 1 }) { page ->
58 | withIoResult {
59 | loadVideoOfCategory(
60 | groupKey = groupKey,
61 | categoryKey = categoryKey,
62 | page = page
63 | )
64 | }
65 | }
66 | }
67 | }
68 | }
69 | }
70 | return listOf(SourcePage.Group(label = "首页", newScreen = false) {
71 | withIoResult {
72 | val doc = libVioSource.requestWithFunCDNInterceptor {
73 | url(LibVioSource.BASE_URL)
74 | get()
75 | }.asDocument()
76 | val list = mutableListOf()
77 |
78 | val containers = doc.getElementsByClass("stui-vodlist")
79 | for (container in containers) {
80 | val videoEls = container.select("li > .stui-vodlist__box")
81 | if (videoEls.isEmpty()) {
82 | break
83 | }
84 |
85 | val groupName =
86 | container.previousElementSibling()
87 | ?.takeIf { it.hasClass("stui-vodlist__head") }
88 | ?.selectFirst("h3")?.text()?.trim() ?: "推荐视频"
89 | val videos = videoEls.map { libVioSource.parseLibVioVideo(it) }
90 | val page = SourcePage.SingleCartoonPage.WithCover(
91 | label = groupName,
92 | firstKey = { 0 }) {
93 | withResult {
94 | null to videos
95 | }
96 | }
97 | list.add(page)
98 | }
99 | list
100 | }
101 | }) + pages
102 | }
103 |
104 | private suspend fun loadVideoOfCategory(
105 | groupKey: String,
106 | categoryKey: String,
107 | page: Int
108 | ): Pair> {
109 | val doc = libVioSource.requestWithFunCDNInterceptor {
110 | url("${LibVioSource.BASE_URL}/show/$groupKey--time-$categoryKey-----$page---.html")
111 | get()
112 | }.asDocument()
113 | return libVioSource.parseVideoPage(doc, page)
114 | }
115 |
116 |
117 | }
--------------------------------------------------------------------------------
/extension-app/src/main/java/io/github/easybangumiorg/source/aio/libvio/LibVioPlay.kt:
--------------------------------------------------------------------------------
1 | package io.github.easybangumiorg.source.aio.libvio
2 |
3 | import android.util.Log
4 | import com.google.gson.Gson
5 | import com.heyanle.easybangumi4.source_api.SourceResult
6 | import com.heyanle.easybangumi4.source_api.component.ComponentWrapper
7 | import com.heyanle.easybangumi4.source_api.component.play.PlayComponent
8 | import com.heyanle.easybangumi4.source_api.entity.CartoonSummary
9 | import com.heyanle.easybangumi4.source_api.entity.Episode
10 | import com.heyanle.easybangumi4.source_api.entity.PlayLine
11 | import com.heyanle.easybangumi4.source_api.entity.PlayerInfo
12 | import io.github.easybangumiorg.source.aio.bodyString
13 | import io.github.easybangumiorg.source.aio.decodeUri
14 | import io.github.easybangumiorg.source.aio.encodeUri
15 | import io.github.easybangumiorg.source.aio.newGetRequest
16 | import io.github.easybangumiorg.source.aio.withIoResult
17 | import java.net.URI
18 | import java.util.concurrent.ConcurrentHashMap
19 | import kotlin.io.encoding.Base64
20 | import kotlin.io.encoding.ExperimentalEncodingApi
21 |
22 | class LibVioPlay(private val libVioSource: LibVioSource) : ComponentWrapper(), PlayComponent {
23 | private val playerServerCache = ConcurrentHashMap()
24 |
25 | @OptIn(ExperimentalEncodingApi::class)
26 | override suspend fun getPlayInfo(
27 | summary: CartoonSummary,
28 | playLine: PlayLine,
29 | episode: Episode
30 | ): SourceResult = withIoResult {
31 | val html = libVioSource.requestWithFunCDNInterceptor {
32 | url("${LibVioSource.BASE_URL}/play/${episode.id}.html")
33 | get()
34 | }.bodyString()
35 | val keyword = "player_aaaa"
36 | val startIndex = html.indexOf(keyword)
37 | if (startIndex == -1) {
38 | throw RuntimeException("未获取到视频播放信息")
39 | }
40 | val configStartIndex = html.indexOf('{', startIndex = startIndex + keyword.length)
41 | val configJson =
42 | html.substring(
43 | configStartIndex,
44 | html.indexOf('}', startIndex = configStartIndex + 1) + 1
45 | )
46 | val cfg = Gson().fromJson