diff --git a/.github/workflows/TVBoxOSC.jks b/.github/workflows/TVBoxOSC.jks new file mode 100644 index 0000000000..d9ed79a285 Binary files /dev/null and b/.github/workflows/TVBoxOSC.jks differ diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9a87f11bd0..acff9cadcb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,31 +1,159 @@ -name: Test Build +name: Test on: -# push: -# branches: -# - main -# pull_request: + schedule: + - cron: 0 13 1 * * workflow_dispatch: + inputs: + rebuild: + description: '忽略构建记录以重新构建' + required: false + type: boolean + donotpublish: + description: '构建后不提交发布新版' + required: false + type: boolean jobs: build: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - userName: q215613905 + repoName: TVBoxOS + branchName: main steps: - - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v4 with: fetch-depth: 0 + - name: Check New Commit + run: | + upStream=https://github.com/${{ matrix.userName }}/${{ matrix.repoName }} + echo "upStream=$upStream" >> $GITHUB_ENV + commit=$(curl -sL $upStream/commits/${{ matrix.branchName }} |grep -o "/${{ matrix.userName }}/${{ matrix.repoName }}/commit/[a-z0-9]\+" |head -1 | cut -d\/ -f5) + if [[ -z "${commit}" ]]; then + commit=$(curl -s "https://api.github.com/repos/${{ matrix.userName }}/${{ matrix.repoName }}/commits/${{ matrix.branchName }}?per_page=1" | jq -r '.sha' ) + fi + if ! grep -q "$commit" README.md || [ "${{ inputs.rebuild }}" == "true" ]; then + echo "commit=$commit" >> $GITHUB_ENV + echo "commitS=${commit:0:7}" >> $GITHUB_ENV + fi + echo "commit=$commit" + - name: Checkout Source Code + if: ${{ env.commit }} + run: | + git clone ${{ env.upStream }} TVBoxOSC + cd TVBoxOSC + git checkout ${{ env.commit }} + echo "tag=$(git log --date=format:'%Y%m%d-%H%M' --pretty=format:%cd ${{ env.commitS }} -1)" >> $GITHUB_ENV + - name: Extra Modify + if: ${{ env.commit }} + working-directory: TVBoxOSC + run: | + echo '修改VersionName' + if [ "${{ matrix.userName }}" == "takagen99" ]; then + # 进行 sed 修改 + sed -i 's/\.concat(buildTime())/ +\"${{ env.tag }}\"/g' app/build.gradle + # 目前关于页面没有合并我的分支 + sed -i "/android:text=/s#=\"#=\"${{ env.tag }}\\\\n\\\\n#" app/src/main/res/layout/dialog_about.xml + else + sed -i "/versionName/s#[0-9a-zA-Z_\.\'\"-]\+\$#\'${{ env.tag }}\'#" app/build.gradle + sed -i "/android:text=/s#=\"#=\"${{ env.tag }}\\\\n\\\\n#" app/src/main/res/layout/dialog_about.xml + fi + echo 'crosswalk源,防挂' + if grep -q 'crosswalk' build.gradle; then + sed -i "/crosswalk/a\ maven { url 'https://o0halflife0o.github.io/crosswalk/releases/crosswalk/android/maven2' }" build.gradle + else + sed -i "/jitpack.io/a\ maven { url 'https://o0halflife0o.github.io/crosswalk/releases/crosswalk/android/maven2' }" build.gradle + fi + - name: Compress Source Code + if: ${{ env.commit }} + working-directory: TVBoxOSC + run: | + #zip -q -x ".git/*" -x ".github/*" -r sourceCode-${{ env.commitS }}.zip . + tar -cJf sourceCode-${{ env.commitS }}.tar.xz --exclude=.git --exclude=.github * + - name: Release Apk Sign + if: ${{ env.commit }} + working-directory: TVBoxOSC + run: | + signingConfigs='ICAgIHNpZ25pbmdDb25maWdzIHtcCiAgICAgICAgaWYgKHByb2plY3QuaGFzUHJvcGVydHkoIlJFTEVBU0VfU1RPUkVfRklMRSIpKSB7XAogICAgICAgICAgICBteUNvbmZpZyB7XAogICAgICAgICAgICAgICAgc3RvcmVGaWxlIGZpbGUoUkVMRUFTRV9TVE9SRV9GSUxFKVwKICAgICAgICAgICAgICAgIHN0b3JlUGFzc3dvcmQgUkVMRUFTRV9TVE9SRV9QQVNTV09SRFwKICAgICAgICAgICAgICAgIGtleUFsaWFzIFJFTEVBU0VfS0VZX0FMSUFTXAogICAgICAgICAgICAgICAga2V5UGFzc3dvcmQgUkVMRUFTRV9LRVlfUEFTU1dPUkRcCiAgICAgICAgICAgICAgICB2MVNpZ25pbmdFbmFibGVkIHRydWVcCiAgICAgICAgICAgICAgICB2MlNpZ25pbmdFbmFibGVkIHRydWVcCiAgICAgICAgICAgICAgICBlbmFibGVWM1NpZ25pbmcgPSB0cnVlXAogICAgICAgICAgICAgICAgZW5hYmxlVjRTaWduaW5nID0gdHJ1ZVwKICAgICAgICAgICAgfVwKICAgICAgICB9XAogICAgfVwKXA==' + signingConfig='ICAgICAgICAgICAgaWYgKHByb2plY3QuaGFzUHJvcGVydHkoIlJFTEVBU0VfU1RPUkVfRklMRSIpKSB7XAogICAgICAgICAgICAgICAgc2lnbmluZ0NvbmZpZyBzaWduaW5nQ29uZmlncy5teUNvbmZpZ1wKICAgICAgICAgICAgfVwK' + signingConfigs="$(echo "$signingConfigs" |base64 -d )" + signingConfig="$(echo "$signingConfig" |base64 -d )" + sed -i -e "/defaultConfig {/i\\$signingConfigs " -e "/debug {/a\\$signingConfig " -e "/release {/a\\$signingConfig " app/build.gradle + cp -f ${{ github.workspace }}/.github/workflows/TVBoxOSC.jks app/TVBoxOSC.jks + sed -i '$a\RELEASE_STORE_FILE=./TVBoxOSC.jks' ./gradle.properties + sed -i '$a\RELEASE_KEY_ALIAS=TVBoxOSC' ./gradle.properties + sed -i '$a\RELEASE_STORE_PASSWORD=TVBoxOSC' ./gradle.properties + sed -i '$a\RELEASE_KEY_PASSWORD=TVBoxOSC' ./gradle.properties + sed -i 's/^#\(org.gradle.jvmargs=.*\)/\1/' ./gradle.properties + - uses: actions/setup-java@v4 + if: ${{ matrix.java_ver }} + with: + distribution: temurin + java-version: ${{ matrix.java_ver }} + - uses: gradle/actions/setup-gradle@v4 + if: ${{ env.commit }} - name: Build With Gradle + if: ${{ env.commit }} + working-directory: TVBoxOSC run: | chmod +x gradlew ./gradlew assemblerelease --build-cache --parallel --daemon --warning-mode all - name: Prepare App + if: ${{ env.commit }} + working-directory: TVBoxOSC run: | - mkdir -p ${{ github.workspace }}/apk/ - for file in `find ~ -name "*.apk" -print`; do - mv "$file" ${{ github.workspace }}/apk/ + rm -rf apk/ + mkdir -p apk/ + for file in `find ~ -name "*release*.apk" -print`; do + # 获取文件的基本名称 + base=$(basename "$file") + # 如果文件是 TVBox_ 开头 + if [[ "$base" == TVBox_* ]]; then + new_base=$(echo "$base" | sed "s/release/${{ matrix.userName }}_${{ env.tag }}/") + else + new_base="TVBox_${{ matrix.userName }}_${{ env.tag }}.apk" + fi + # 移动并重命名文件 + mv "$file" "apk/$new_base" done + mv sourceCode-${{ env.commitS }}.* apk/ + - name: Release Note + if: ${{ env.commit }} + working-directory: TVBoxOSC + run: | + lastCommit=$(grep "${{ matrix.userName }}\/${{ matrix.repoName }}" ${{ github.workspace }}/README.md |grep -o '[a-z0-9]\{40\}') + export LESSCHARSET=utf-8 + echo -e "Credit: [${{ matrix.userName }}](${{ env.upStream }})\nCommit: ${{ env.commit }}\nChangelog:\n\`\`\`" > apk/${{ matrix.userName }}-Release.log + if [ "${{ env.commit }}" == "${lastCommit}" ]; then + git log --pretty=format:%B ${{ env.commitS }} -1 |sed -e "s# \{2,\}#\n#g" -e "/^Merge \(pull\|branch\|remote\)/d" -e '/^$/d' |cat -n |sort -k2,2 -k1,1n |uniq -f1 |sort -k1,1n |cut -f2- >> apk/${{ matrix.userName }}-Release.log + else + git log --pretty=format:%B ${{ env.commitS }}...${lastCommit:0:7} |sed -e "s# \{2,\}#\n#g" -e "/^Merge \(pull\|branch\|remote\)/d" -e '/^$/d' |cat -n |sort -k2,2 -k1,1n |uniq -f1 |sort -k1,1n |cut -f2- >> apk/${{ matrix.userName }}-Release.log + fi + echo -e '\n```' >> apk/${{ matrix.userName }}-Release.log - name: Upload App To Artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 + if: ${{ env.commit }} + with: + name: ${{ matrix.userName }}-${{ matrix.repoName }} + path: | + TVBoxOSC/apk/* + - name: Whether Or Not to Publish + if: ${{ inputs.donotpublish && env.commit }} + run: | + echo "commit=" >> $GITHUB_ENV + clean: + needs: build + runs-on: ubuntu-latest + steps: + - name: Delete older workflow runs and artifacts + uses: Mattraks/delete-workflow-runs@main with: - name: com.github.tvbox.osc - path: ${{ github.workspace }}/apk/* + token: ${{ github.token }} + repository: ${{ github.repository }} + retain_days: 14 + keep_minimum_runs: 10 diff --git a/.gitignore b/.gitignore index 61e8c9b66a..0cf7dc8963 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,10 @@ +# windows thumbnail cache +Thumbs.db +# Mac DS_Store Files +.DS_Store # Built application files *.apk -*.aar +#*.aar *.ap_ *.aab @@ -46,6 +50,7 @@ captures/ .idea/assetWizardSettings.xml .idea/dictionaries .idea/libraries +.idea/shelf # Android Studio 3 in .gitignore file. .idea/caches .idea/modules.xml @@ -88,3 +93,9 @@ lint/tmp/ /LTDecodeSpace /adb.exe /LTDecodeNew3.exe + +.idea/ + +*.hprof +*.md +output-metadata.json diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index 25d48a7c96..0000000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -TVBox \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml deleted file mode 100644 index 681f41ae2a..0000000000 --- a/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - - -
- - - - xmlns:android - - ^$ - - - -
-
- - - - xmlns:.* - - ^$ - - - BY_NAME - -
-
- - - - .*:id - - http://schemas.android.com/apk/res/android - - - -
-
- - - - .*:name - - http://schemas.android.com/apk/res/android - - - -
-
- - - - name - - ^$ - - - -
-
- - - - style - - ^$ - - - -
-
- - - - .* - - ^$ - - - BY_NAME - -
-
- - - - .* - - http://schemas.android.com/apk/res/android - - - ANDROID_ATTRIBUTE_ORDER - -
-
- - - - .* - - .* - - - BY_NAME - -
-
-
-
-
-
\ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index fb7f4a8a46..0000000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml deleted file mode 100644 index 047baf8343..0000000000 --- a/.idea/jarRepositories.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index fbe5affca0..0000000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000000..54ffa0014c --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ +<<<<<<<<<<<<<>>>>>>>>>>>> +# TVBox + +=== Source Code - Editing the app default settings === + + //searchable:搜索开关 0:关闭 1:启用 + //filterable:首页可选 0:否 1:是 + //playerType:播放器类型 0:系统 1:IJK 2:EXO + //采集接口类型 0:xml 1:json 3:jar 4:remote + //parses解析类型 0:嗅探,自带播放器 1:解析,返回直链 + //直播参数说明 ua:用户自定义ua epg:节目网址 logo:台标网址 + + { + "spider": "./your.jar", + "wallpaper": "./api/img", + "sites": [], + "parses": [], + "hosts": [ + "cache.ott.ystenlive.itv.cmvideo.cn=base-v4-free-mghy.e.cdn.chinamobile.com", + "cache.ott.bestlive.itv.cmvideo.cn=ip" + ], + "lives": [], + "rules": [], + "doh": [ + { + "name": "騰訊", + "url": "https://doh.pub/dns-query" + }, + { + "name": "阿里", + "url": "https://dns.alidns.com/dns-query" + }, + { + "name": "360", + "url": "https://doh.360.cn/dns-query" + } + ] + } diff --git a/app/build.gradle b/app/build.gradle index c8eca69732..af5f91f4ed 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,12 +1,11 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 30 + compileSdkVersion 33 defaultConfig { - applicationId 'com.github.tvbox.osc' - minSdkVersion 16 - targetSdkVersion 26 + applicationId 'com.github.tvbox.osc.jun' + targetSdkVersion 28 versionCode 1 versionName '1.0.0' multiDexEnabled true @@ -20,24 +19,105 @@ android { packagingOptions { exclude 'META-INF/DEPENDENCIES' + exclude 'META-INF/beans.xml' } - buildTypes { - debug { + flavorDimensions "mode" + productFlavors { + java { + dimension "mode" + minSdkVersion 19 + ndk { + //noinspection ChromeOsAbiSupport + abiFilters 'armeabi-v7a', 'arm64-v8a' + } proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - minifyEnabled false - + } + java32 { + dimension "mode" + minSdkVersion 19 ndk { + //noinspection ChromeOsAbiSupport abiFilters 'armeabi-v7a' } + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } - release { + java64 { + dimension "mode" + minSdkVersion 21 + ndk { + abiFilters 'arm64-v8a' + } proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - minifyEnabled true - + } + python { + dimension "mode" + minSdkVersion 19 + ndk { + //noinspection ChromeOsAbiSupport + abiFilters 'armeabi-v7a', 'arm64-v8a' + } + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro', 'proguard-python.pro' + } + python32 { + dimension "mode" + minSdkVersion 19 ndk { + //noinspection ChromeOsAbiSupport abiFilters 'armeabi-v7a' } + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro', 'proguard-python.pro' + } + python64 { + dimension "mode" + minSdkVersion 21 + ndk { + abiFilters 'arm64-v8a' + } + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro', 'proguard-python.pro' + } + } + + sourceSets { + java { + java.srcDirs += 'src/normal/java' + } + java32 { + java.srcDirs += 'src/normal/java' + } + java64 { + java.srcDirs += 'src/normal/java' + } + python32 { + java.srcDirs += 'src/python/java' + } + python64 { + java.srcDirs += 'src/python/java' + } + } + + buildTypes { + debug { + minifyEnabled false + } + release { + minifyEnabled true + } + } + + // 禁用 ABI 分割 + splits { + abi { + enable false // 禁用 ABI 分割 + } + } + + applicationVariants.configureEach { variant -> + variant.outputs.configureEach { output -> + def flavorNames = variant.productFlavors.collect { it.name }.join('-') + // 构建自定义文件名 + def fileName = "TVBox_${variant.buildType.name}-${flavorNames}.apk" + outputFileName = fileName } } @@ -50,50 +130,75 @@ android { checkReleaseBuilds false abortOnError false } - dexOptions { - javaMaxHeapSize "4g" - additionalParameters += '--multi-dex' - additionalParameters += '--set-max-idx-number=48000' - additionalParameters += '--minimal-main-dex' - } +// dexOptions { +// javaMaxHeapSize "4g" +// additionalParameters += '--multi-dex' +// additionalParameters += '--set-max-idx-number=48000' +// additionalParameters += '--minimal-main-dex' +// } } -dependencies { - api fileTree(dir: "libs", include: ["*.jar"]) +repositories { +} +dependencies { + api fileTree(dir: "libs", include: ["*.jar","*.aar"]) implementation 'org.nanohttpd:nanohttpd:2.3.1' - implementation 'com.google.zxing:core:3.4.1' + implementation 'org.fourthline.cling:cling-core:2.1.2' + implementation 'org.fourthline.cling:cling-support:2.1.2' + compileOnly 'javax.enterprise:cdi-api:1.2' + compileOnly 'javax.inject:javax.inject:1' + compileOnly 'javax.annotation:javax.annotation-api:1.3.2' + compileOnly 'javax.servlet:javax.servlet-api:3.1.0' + implementation 'com.google.zxing:core:3.3.0' implementation 'androidx.appcompat:appcompat:1.3.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' - implementation 'androidx.recyclerview:recyclerview:1.1.0' - implementation files('libs\\thunder.jar') + implementation 'androidx.recyclerview:recyclerview:1.2.1' +// implementation files('libs/thunder.jar') +// implementation files('libs/commons-lang3-3.12.0.jar') + + implementation 'com.squareup.okhttp3:okhttp:3.12.11' annotationProcessor 'androidx.room:room-compiler:2.3.0' implementation 'androidx.room:room-runtime:2.3.0' implementation 'androidx.multidex:multidex:2.0.1' - implementation 'com.squareup.okhttp3:okhttp:3.12.1' - implementation 'com.squareup.okio:okio:2.6.0' + implementation 'com.squareup.okio:okio:2.8.0' implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.45-androidx' implementation 'com.kingja.loadsir:loadsir:1.3.8' - implementation 'com.google.code.gson:gson:2.8.7' + implementation 'com.google.code.gson:gson:2.10.1' implementation 'com.squareup.picasso:picasso:2.71828' + implementation 'com.github.bumptech.glide:glide:4.16.0' + annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0' implementation 'me.jessyan:autosize:1.2.1' implementation('com.thoughtworks.xstream:xstream:1.4.15') { exclude group: 'xmlpull', module: 'xmlpull' } implementation 'org.greenrobot:eventbus:3.2.0' implementation 'com.orhanobut:hawk:2.0.1' + implementation 'com.github.ctiao:DanmakuFlameMaster:0.9.25' implementation project(":player") + implementation project(":quickjs") + pythonImplementation project(":pyramid") + python32Implementation project(":pyramid") + python64Implementation project(":pyramid") + - implementation('org.xwalk:xwalk_shared_library:23.53.589.4') { - exclude group: 'com.android.support' - } implementation 'com.lzy.net:okgo:3.0.4' // implementation 'com.tencent.bugly:crashreport_upgrade:latest.release' implementation 'com.owen:tv-recyclerview:3.0.0' implementation 'com.github.getActivity:XXPermissions:13.6' -} \ No newline at end of file + implementation 'org.jsoup:jsoup:1.14.1' + implementation 'com.github.hedzr:android-file-chooser:v1.2.0-final' + implementation 'commons-io:commons-io:2.11.0' + implementation 'com.googlecode.juniversalchardet:juniversalchardet:1.0.3' + //支持http3 + implementation ('com.google.net.cronet:cronet-okhttp:0.1.0') +// implementation 'com.google.android.gms:play-services-cronet:18.0.1' + implementation ('org.chromium.net:cronet-embedded:101.4951.41') + implementation 'org.brotli:dec:0.1.2' +} + diff --git a/app/libs/commons-lang3-3.12.0.jar b/app/libs/commons-lang3-3.12.0.jar new file mode 100644 index 0000000000..4d434a2a45 Binary files /dev/null and b/app/libs/commons-lang3-3.12.0.jar differ diff --git a/app/libs/xwalk_shared_library-23.53.589.4.aar b/app/libs/xwalk_shared_library-23.53.589.4.aar new file mode 100644 index 0000000000..12ad550a39 Binary files /dev/null and b/app/libs/xwalk_shared_library-23.53.589.4.aar differ diff --git a/app/proguard-python.pro b/app/proguard-python.pro new file mode 100644 index 0000000000..45adcb7bb9 --- /dev/null +++ b/app/proguard-python.pro @@ -0,0 +1,5 @@ +# Python支持 +-keep public class com.undcover.freedom.pyramid.** { *; } +-dontwarn com.undcover.freedom.pyramid.** +-keep public class com.chaquo.python.** { *; } +-dontwarn com.chaquo.python.** diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 0b507db0e0..ce6c6ca8f8 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -35,14 +35,14 @@ # 保留我们使用的四大组件,自定义的Application等等这些类不被混淆 # 因为这些子类都有可能被外部调用 -keep public class * extends android.app.Activity --keep public class * extends android.app.Appliction +-keep public class * extends android.app.Application.** -keep public class * extends android.app.Service -keep public class * extends android.content.BroadcastReceiver -keep public class * extends android.content.ContentProvider -keep public class * extends android.app.backup.BackupAgentHelper -keep public class * extends android.preference.Preference -keep public class * extends android.view.View --keep public class com.android.vending.licensing.ILicensingService +-keep public class com.android.vending.licensing.ILicensingService.** # 保留support下的所有类及其内部类 -keep class android.support.** {*;} @@ -61,6 +61,9 @@ -keep class org.xmlpull.v1.** {*;} +-dontwarn org.fourthline.cling.** +-keep class org.fourthline.cling.** { *; } + # 保留R下面的资源 -keep class **.R$* {*;} @@ -198,6 +201,33 @@ ; } +-keep class com.github.catvod.Proxy { *; } -keep class com.github.catvod.crawler.*{*;} +-keep class com.github.catvod.net.** { *; } +-keep class com.github.tvbox.osc.bean.ProxyRule { *; } +-keep class com.github.tvbox.osc.util.net.** { *; } # 迅雷下载模块 --keep class com.xunlei.downloadlib.** {*;} \ No newline at end of file +-keep class com.xunlei.downloadlib.** {*;} +# quickjs引擎 +#-keep class com.github.tvbox.quickjs.** {*;} +-keep class com.whl.quickjs.** {*;} +# 支持影视的ali相关的jar +-keep class com.google.gson.**{*;} +# Zxing +-keep class com.google.zxing.**{*;} +-keepclassmembers enum * { + public static **[] values(); + public static ** valueOf(java.lang.String); +} +# Cronet支持http3 +-keep class com.google.net.cronet.**{*;} +-keep class org.chromium.net.**{*;} + +# Nano +-keep class fi.iki.elonen.** { *; } + +# Python支持 +#-keep public class com.undcover.freedom.pyramid.** { *; } +#-dontwarn com.undcover.freedom.pyramid.** +#-keep public class com.chaquo.python.** { *; } +#-dontwarn com.chaquo.python.** diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 93a0e81914..effeddc30d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,13 @@ + + + @@ -27,7 +34,8 @@ + android:exported="true" + android:screenOrientation="sensorLandscape"> @@ -38,35 +46,55 @@ + android:screenOrientation="sensorLandscape" /> - + android:configChanges="orientation|screenSize" + android:screenOrientation="sensorLandscape" /> + android:screenOrientation="sensorLandscape" /> + android:windowSoftInputMode="stateHidden|adjustResize" + android:screenOrientation="sensorLandscape" /> + + android:screenOrientation="sensorLandscape" /> + android:screenOrientation="sensorLandscape" /> + android:screenOrientation="sensorLandscape" /> + - + + + - + + + + + + @@ -97,4 +125,4 @@ android:value="disable" /> - \ No newline at end of file + diff --git a/app/src/main/assets/epg_data.json b/app/src/main/assets/epg_data.json new file mode 100644 index 0000000000..8e0b8c3b3f --- /dev/null +++ b/app/src/main/assets/epg_data.json @@ -0,0 +1,3742 @@ +{ + "epgs": [{ + "tvid": "1", + "epgid": "CCTV1", + "name": "CCTV-1综合,CCTV-1,CCTV1,CCTV1综合", + "status": "1", + "note": "CCTV-1综合", + "logo": "http://epg.51zmt.top:8000/tb1/CCTV/CCTV1.png" + }, { + "tvid": "2", + "epgid": "CCTV2", + "name": "CCTV-2财经,CCTV-2,CCTV2,CCTV2财经", + "status": "1", + "note": "CCTV-2财经", + "logo": "http://epg.51zmt.top:8000/tb1/CCTV/CCTV2.png" + }, { + "tvid": "3", + "epgid": "CCTV3", + "name": "CCTV-3综艺,CCTV-3,CCTV3,CCTV3综艺", + "status": "1", + "note": "CCTV-3综艺", + "logo": "http://epg.51zmt.top:8000/tb1/CCTV/CCTV3.png" + }, { + "tvid": "4", + "epgid": "CCTV4", + "name": "CCTV-4国际,CCTV-4,CCTV4,CCTV4国际,CCTV-4中文国际,CCTV4中文国际,CCTV-4国际亚洲", + "status": "1", + "note": "CCTV-4中文国际", + "logo": "http://epg.51zmt.top:8000/tb1/CCTV/CCTV4.png" + }, { + "tvid": "5", + "epgid": "CCTV5", + "name": "CCTV-5体育,CCTV-5,CCTV5,CCTV5体育,CCTV5plus", + "status": "1", + "note": "CCTV-5体育", + "logo": "http://epg.51zmt.top:8000/tb1/CCTV/CCTV5.png" + }, { + "tvid": "6", + "epgid": "CCTV5+", + "name": "CCTV5+,CCTV5⁺体育赛事,CCTV5⁺,CCTV-5⁺,CCTV-5⁺体育赛事,CCTV-体育赛事", + "status": "1", + "note": "CCTV-5⁺体育赛事", + "logo": "http://epg.51zmt.top:8000/tb1/CCTV/CCTV5+.png" + }, { + "tvid": "7", + "epgid": "CCTV6", + "name": "CCTV-6电影,CCTV-6,CCTV6,CCTV6电影", + "status": "1", + "note": "CCTV-6电影", + "logo": "http://epg.51zmt.top:8000/tb1/CCTV/CCTV6.png" + }, { + "tvid": "8", + "epgid": "CCTV7", + "name": "CCTV-7国防军事,CCTV-7,CCTV7,CCTV7国防军事,CCTV-7国防,CCTV7国防,CCTV7-国防军事", + "status": "1", + "note": "CCTV-7国防军事", + "logo": "http://epg.51zmt.top:8000/tb1/CCTV/CCTV7.png" + }, { + "tvid": "9", + "epgid": "CCTV8", + "name": "CCTV-8电视剧,CCTV-8,CCTV8,CCTV8电视剧,CCTV-8电视,CCTV8电视,CCTV8-电视剧", + "status": "1", + "note": "CCTV-8电视剧", + "logo": "http://epg.51zmt.top:8000/tb1/CCTV/CCTV8.png" + }, { + "tvid": "10", + "epgid": "CCTV9", + "name": "CCTV-9纪录,CCTV-9,CCTV9,CCTV9纪录,CCTV9-纪录", + "status": "1", + "note": "CCTV-9纪录", + "logo": "http://epg.51zmt.top:8000/tb1/CCTV/CCTV9.png" + }, { + "tvid": "11", + "epgid": "CCTV10", + "name": "CCTV-10科教,CCTV-10,CCTV10,CCTV10科教", + "status": "1", + "note": "CCTV-10科教", + "logo": "http://epg.51zmt.top:8000/tb1/CCTV/CCTV10.png" + }, { + "tvid": "12", + "epgid": "CCTV11", + "name": "CCTV-11戏曲,CCTV-11,CCTV11,CCTV11戏曲", + "status": "1", + "note": "CCTV-11戏曲", + "logo": "http://epg.51zmt.top:8000/tb1/CCTV/CCTV11.png" + }, { + "tvid": "13", + "epgid": "CCTV12", + "name": "CCTV-12法制,CCTV-12,CCTV12,CCTV12法制,CCTV-12社会与法,CCTV12社会与法", + "status": "1", + "note": "CCTV-12社会与法", + "logo": "http://epg.51zmt.top:8000/tb1/CCTV/CCTV12.png" + }, { + "tvid": "14", + "epgid": "CCTV13", + "name": "CCTV-13新闻,CCTV-13,CCTV13,CCTV13新闻", + "status": "1", + "note": "CCTV-13新闻", + "logo": "http://epg.51zmt.top:8000/tb1/CCTV/CCTV13.png" + }, { + "tvid": "15", + "epgid": "CCTV14", + "name": "CCTV-14少儿,CCTV-14,CCTV14,CCTV14少儿", + "status": "1", + "note": "CCTV-14少儿", + "logo": "http://epg.51zmt.top:8000/tb1/CCTV/CCTV14.png" + }, { + "tvid": "16", + "epgid": "CCTV15", + "name": "CCTV-15音乐,CCTV-15,CCTV15,CCTV15音乐", + "status": "1", + "note": "CCTV-15音乐", + "logo": "http://epg.51zmt.top:8000/tb1/CCTV/CCTV15.png" + }, { + "tvid": "17", + "epgid": "CCTV17", + "name": "CCTV-17农业,CCTV-17,CCTV17,CCTV17农业,CCTV-17农业农村,CCTV-17农村农业,CCTV17农业农村,CCTV17农村农业", + "status": "1", + "note": "CCTV-17农村农业", + "logo": "http://epg.51zmt.top:8000/tb1/CCTV/CCTV17.png" + }, { + "tvid": "18", + "epgid": "CGTN", + "name": "CGTN", + "status": "1", + "note": "CGTN", + "logo": "http://epg.51zmt.top:8000/tb1/CCTV/cgtn.png" + }, { + "tvid": "19", + "epgid": "CCTV4K", + "name": "CCTV4K", + "status": "1", + "note": "CCTV4K", + "logo": "http://epg.51zmt.top:8000/tb1/CCTV/CCTV4k.png" + }, { + "tvid": "20", + "epgid": "湖南卫视", + "name": "湖南卫视", + "status": "1", + "note": "湖南卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/hunan.png" + }, { + "tvid": "21", + "epgid": "浙江卫视", + "name": "浙江卫视", + "status": "1", + "note": "浙江卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/zhejiang.png" + }, { + "tvid": "22", + "epgid": "江苏卫视", + "name": "江苏卫视", + "status": "1", + "note": "江苏卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/jiangsu.png" + }, { + "tvid": "23", + "epgid": "北京卫视", + "name": "北京卫视", + "status": "1", + "note": "北京卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/beijing.png" + }, { + "tvid": "24", + "epgid": "东方卫视", + "name": "东方卫视", + "status": "1", + "note": "东方卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/dongfang.png" + }, { + "tvid": "25", + "epgid": "安徽卫视", + "name": "安徽卫视", + "status": "1", + "note": "安徽卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/anhui.png" + }, { + "tvid": "26", + "epgid": "广东卫视", + "name": "广东卫视", + "status": "1", + "note": "广东卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/guangdong.png" + }, { + "tvid": "27", + "epgid": "深圳卫视", + "name": "深圳卫视", + "status": "1", + "note": "深圳卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/shenzhen.png" + }, { + "tvid": "28", + "epgid": "辽宁卫视", + "name": "辽宁卫视", + "status": "1", + "note": "辽宁卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/liaoning.png" + }, { + "tvid": "29", + "epgid": "旅游卫视", + "name": "海南卫视", + "status": "1", + "note": "海南卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/lvyou.png" + }, { + "tvid": "30", + "epgid": "山东卫视", + "name": "山东卫视", + "status": "1", + "note": "山东卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/shandong.png" + }, { + "tvid": "31", + "epgid": "天津卫视", + "name": "天津卫视", + "status": "1", + "note": "天津卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/tianjin.png" + }, { + "tvid": "32", + "epgid": "重庆卫视", + "name": "重庆卫视", + "status": "1", + "note": "重庆卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/chongqing.png" + }, { + "tvid": "33", + "epgid": "东南卫视", + "name": "东南卫视", + "status": "1", + "note": "东南卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/dongnan.png" + }, { + "tvid": "34", + "epgid": "甘肃卫视", + "name": "甘肃卫视", + "status": "1", + "note": "甘肃卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/gansu.png" + }, { + "tvid": "35", + "epgid": "广西卫视", + "name": "广西卫视", + "status": "1", + "note": "广西卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/guangxi.png" + }, { + "tvid": "36", + "epgid": "贵州卫视", + "name": "贵州卫视", + "status": "1", + "note": "贵州卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/guizhou.png" + }, { + "tvid": "37", + "epgid": "河北卫视", + "name": "河北卫视", + "status": "1", + "note": "河北卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/hebei.png" + }, { + "tvid": "38", + "epgid": "黑龙江卫视", + "name": "黑龙江卫视", + "status": "1", + "note": "黑龙江卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/heilongjiang.png" + }, { + "tvid": "39", + "epgid": "河南卫视", + "name": "河南卫视", + "status": "1", + "note": "河南卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/henan.png" + }, { + "tvid": "40", + "epgid": "湖北卫视", + "name": "湖北卫视", + "status": "1", + "note": "湖北卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/hubei.png" + }, { + "tvid": "41", + "epgid": "江西卫视", + "name": "江西卫视", + "status": "1", + "note": "江西卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/jiangxi.png" + }, { + "tvid": "42", + "epgid": "吉林卫视", + "name": "吉林卫视", + "status": "1", + "note": "吉林卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/jilin.png" + }, { + "tvid": "43", + "epgid": "内蒙古卫视", + "name": "内蒙古卫视", + "status": "1", + "note": "内蒙古卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/neimeng.png" + }, { + "tvid": "44", + "epgid": "宁夏卫视", + "name": "宁夏卫视", + "status": "1", + "note": "宁夏卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/ningxia.png" + }, { + "tvid": "45", + "epgid": "山西卫视", + "name": "山西卫视", + "status": "1", + "note": "山西卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/shanxi_.png" + }, { + "tvid": "46", + "epgid": "陕西卫视", + "name": "陕西卫视", + "status": "1", + "note": "陕西卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/shanxi.png" + }, { + "tvid": "47", + "epgid": "四川卫视", + "name": "四川卫视", + "status": "1", + "note": "四川卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/sichuan.png" + }, { + "tvid": "48", + "epgid": "新疆卫视", + "name": "新疆卫视", + "status": "1", + "note": "新疆卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/xinjiang.png" + }, { + "tvid": "49", + "epgid": "云南卫视", + "name": "云南卫视", + "status": "1", + "note": "云南卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/yunnan.png" + }, { + "tvid": "50", + "epgid": "青海卫视", + "name": "青海卫视", + "status": "1", + "note": "青海卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/qinghai.png" + }, { + "tvid": "51", + "epgid": "南方卫视", + "name": "南方卫视,大湾区卫视", + "status": "1", + "note": "南方卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/nanfang.png" + }, { + "tvid": "52", + "epgid": "兵团卫视", + "name": "兵团卫视", + "status": "1", + "note": "兵团卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/bingtuan.png" + }, { + "tvid": "53", + "epgid": "延边卫视", + "name": "延边卫视", + "status": "1", + "note": "延边卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/YANBIAN1.png" + }, { + "tvid": "54", + "epgid": "黄河卫视", + "name": "黄河卫视", + "status": "1", + "note": "黄河卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/huanghe.png" + }, { + "tvid": "55", + "epgid": "厦门卫视", + "name": "厦门卫视", + "status": "1", + "note": "厦门卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/xiamen.png" + }, { + "tvid": "56", + "epgid": "金鹰卡通", + "name": "金鹰卡通", + "status": "1", + "note": "金鹰卡通", + "logo": "http://epg.51zmt.top:8000/tb1/qt/jinyingkatong.png" + }, { + "tvid": "57", + "epgid": "康巴卫视", + "name": "康巴卫视", + "status": "1", + "note": "康巴卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/kangba.png" + }, { + "tvid": "58", + "epgid": "西藏卫视", + "name": "西藏卫视", + "status": "1", + "note": "西藏卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/xizang.png" + }, { + "tvid": "59", + "epgid": "三沙卫视", + "name": "三沙卫视", + "status": "1", + "note": "三沙卫视", + "logo": "http://epg.51zmt.top:8000/tb1/ws/sansha.png" + }, { + "tvid": "60", + "epgid": "中国教育1台", + "name": "教育1台", + "status": "1", + "note": "教育1台", + "logo": "http://epg.51zmt.top:8000/tb1/qt/中国教育1台.png" + }, { + "tvid": "61", + "epgid": "中国教育2台", + "name": "教育2台", + "status": "1", + "note": "教育2台", + "logo": "http://epg.51zmt.top:8000/tb1/qt/中国教育2台.png" + }, { + "tvid": "62", + "epgid": "中国教育3台", + "name": "教育3台", + "status": "1", + "note": "教育3台", + "logo": "http://epg.51zmt.top:8000/tb1/qt/中国教育3台.png" + }, { + "tvid": "63", + "epgid": "3D电视试验频道", + "name": "中国3D电视试验频道", + "status": "1", + "note": "中国3D电视试验频道", + "logo": "http://epg.51zmt.top:8000/tb1/CCTV/CCTV3D.png" + }, { + "tvid": "64", + "epgid": "外汇理财", + "name": "外汇理财", + "status": "1", + "note": "外汇理财", + "logo": "http://epg.51zmt.top:8000/tb1/qt/IPTVEFEL.png" + }, { + "tvid": "65", + "epgid": "电竞天堂", + "name": "电竞天堂", + "status": "1", + "note": "电竞天堂", + "logo": "http://epg.51zmt.top:8000/tb1/qt/dianjingtiantang.png" + }, { + "tvid": "66", + "epgid": "IPTV5+", + "name": "IPTV5⁺", + "status": "1", + "note": "IPTV5⁺", + "logo": "http://epg.51zmt.top:8000/tb1/qt/IPTV5+.png" + }, { + "tvid": "67", + "epgid": "IPTV6+", + "name": "IPTV6⁺", + "status": "1", + "note": "IPTV6⁺", + "logo": "http://epg.51zmt.top:8000/tb1/qt/IPTV6+.png" + }, { + "tvid": "68", + "epgid": "IPTV经典电影", + "name": "IPTV经典电影", + "status": "1", + "note": "IPTV经典电影", + "logo": "http://epg.51zmt.top:8000/tb1/qt/IPTV经典电影.png" + }, { + "tvid": "69", + "epgid": "IPTV热播剧场", + "name": "IPTV热播剧场", + "status": "1", + "note": "IPTV热播剧场", + "logo": "http://epg.51zmt.top:8000/tb1/qt/IPTV热播剧场.png" + }, { + "tvid": "70", + "epgid": "IPTV少儿动画", + "name": "IPTV少儿动画", + "status": "1", + "note": "IPTV少儿动画", + "logo": "http://epg.51zmt.top:8000/tb1/qt/IPTV少儿动画.png" + }, { + "tvid": "71", + "epgid": "IPTV魅力时尚", + "name": "IPTV魅力时尚", + "status": "1", + "note": "IPTV魅力时尚", + "logo": "http://epg.51zmt.top:8000/tb1/qt/IPTV魅力时尚.png" + }, { + "tvid": "72", + "epgid": "DOX映画", + "name": "DOX映画", + "status": "1", + "note": "DOX映画", + "logo": null + }, { + "tvid": "73", + "epgid": "风云足球", + "name": "中数风云足球", + "status": "1", + "note": "中数风云足球", + "logo": "http://epg.51zmt.top:8000/tb1/CCTV/CCTVfengyunzuqiu.png" + }, { + "tvid": "74", + "epgid": "求索科学", + "name": "华数求索科学", + "status": "1", + "note": "华数求索科学", + "logo": "http://epg.51zmt.top:8000/tb1/qt/QSKX.png" + }, { + "tvid": "75", + "epgid": "CHC高清电影", + "name": "CHC高清电影", + "status": "1", + "note": "CHC高清电影", + "logo": "http://epg.51zmt.top:8000/tb1/qt/CHC3.jpg" + }, { + "tvid": "76", + "epgid": "求索动物", + "name": "华数求索动物", + "status": "1", + "note": "华数求索动物", + "logo": "http://epg.51zmt.top:8000/tb1/qt/QSDW.png" + }, { + "tvid": "77", + "epgid": "求索生活", + "name": "华数求索生活", + "status": "1", + "note": "华数求索生活", + "logo": null + }, { + "tvid": "78", + "epgid": "求索记录", + "name": "华数求索记录", + "status": "1", + "note": "华数求索记录", + "logo": "http://epg.51zmt.top:8000/tb1/qt/QSJL.png" + }, { + "tvid": "79", + "epgid": "CHC动作电影", + "name": "CHC动作电影", + "status": "1", + "note": "CHC动作电影", + "logo": "http://epg.51zmt.top:8000/tb1/qt/CHC1.jpg" + }, { + "tvid": "80", + "epgid": "CHC家庭电影", + "name": "CHC家庭电影", + "status": "1", + "note": "CHC家庭电影", + "logo": "http://epg.51zmt.top:8000/tb1/qt/CHC2.jpg" + }, { + "tvid": "81", + "epgid": "梨园", + "name": "中数梨园", + "status": "1", + "note": "中数梨园", + "logo": "http://epg.51zmt.top:8000/tb1/qt/CCTVPAYFEE20.jpg" + }, { + "tvid": "82", + "epgid": "风云音乐", + "name": "中数风云音乐", + "status": "1", + "note": "中数风云音乐", + "logo": "http://epg.51zmt.top:8000/tb1/CCTV/CCTVfengyunyinyue.png" + }, { + "tvid": "83", + "epgid": "第一剧场", + "name": "中数第一剧场", + "status": "1", + "note": "中数第一剧场", + "logo": "http://epg.51zmt.top:8000/tb1/CCTV/CCTVdiyijuchang.png" + }, { + "tvid": "84", + "epgid": "风云剧场", + "name": "中数风云剧场", + "status": "1", + "note": "中数风云剧场", + "logo": "http://epg.51zmt.top:8000/tb1/CCTV/CCTBfengyun.png" + }, { + "tvid": "85", + "epgid": "世界地理", + "name": "中数世界地理", + "status": "1", + "note": "中数世界地理", + "logo": "http://epg.51zmt.top:8000/tb1/CCTV/CCTVshijiedili.png" + }, { + "tvid": "86", + "epgid": "电视指南", + "name": "中数电视指南", + "status": "1", + "note": "中数电视指南", + "logo": null + }, { + "tvid": "87", + "epgid": "怀旧剧场", + "name": "中数怀旧剧场", + "status": "1", + "note": "中数怀旧剧场", + "logo": "http://epg.51zmt.top:8000/tb1/qt/CCTVPAYFEE7.jpg" + }, { + "tvid": "88", + "epgid": "兵器科技", + "name": "中数兵器科技", + "status": "1", + "note": "中数兵器科技", + "logo": "http://epg.51zmt.top:8000/tb1/qt/CCTVPAYFEE8.jpg" + }, { + "tvid": "89", + "epgid": "女性时尚", + "name": "中数女性时尚", + "status": "1", + "note": "中数女性时尚", + "logo": "http://epg.51zmt.top:8000/tb1/CCTV/CCTVnvxing.png" + }, { + "tvid": "90", + "epgid": "CCTV-娱乐", + "name": "中数CCTV-娱乐", + "status": "1", + "note": "中数CCTV-娱乐", + "logo": "http://epg.51zmt.top:8000/tb1/CCTV/CCTVyule.png" + }, { + "tvid": "91", + "epgid": "CCTV-戏曲", + "name": "中数CCTV-戏曲", + "status": "1", + "note": "中数CCTV-戏曲", + "logo": "http://epg.51zmt.top:8000/tb1/CCTV/CCTVxiqu.png" + }, { + "tvid": "92", + "epgid": "CCTV-电影", + "name": "中数CCTV-电影", + "status": "1", + "note": "中数CCTV-电影", + "logo": "http://epg.51zmt.top:8000/tb1/qt/CCTVPAYFEE12.jpg" + }, { + "tvid": "93", + "epgid": "高尔夫网球", + "name": "中数高尔夫网球", + "status": "1", + "note": "中数高尔夫网球", + "logo": "http://epg.51zmt.top:8000/tb1/qt/CCTVPAYFEE13.jpg" + }, { + "tvid": "94", + "epgid": "央视精品", + "name": "中数央视精品", + "status": "1", + "note": "中数央视精品", + "logo": "http://epg.51zmt.top:8000/tb1/CCTV/CCTVwenhuajingpin.png" + }, { + "tvid": "95", + "epgid": "彩民在线", + "name": "中数彩民在线", + "status": "1", + "note": "中数彩民在线", + "logo": "http://epg.51zmt.top:8000/tb1/qt/caiminzaixian.png" + }, { + "tvid": "96", + "epgid": "法律服务", + "name": "中数法律服务", + "status": "1", + "note": "中数法律服务", + "logo": "http://epg.51zmt.top:8000/tb1/qt/CCTVPAYFEE17.jpg" + }, { + "tvid": "97", + "epgid": "汽摩", + "name": "中数汽摩", + "status": "1", + "note": "中数汽摩", + "logo": "http://epg.51zmt.top:8000/tb1/qt/CCTVPAYFEE22.jpg" + }, { + "tvid": "98", + "epgid": "留学世界", + "name": "中数留学世界", + "status": "1", + "note": "中数留学世界", + "logo": "http://epg.51zmt.top:8000/tb1/qt/CCTVPAYFEE24.jpg" + }, { + "tvid": "99", + "epgid": "青年学苑", + "name": "中数青年学苑", + "status": "1", + "note": "中数青年学苑", + "logo": "http://epg.51zmt.top:8000/tb1/qt/CCTVPAYFEE25.jpg" + }, { + "tvid": "100", + "epgid": "摄影频道", + "name": "中数摄影频道", + "status": "1", + "note": "中数摄影频道", + "logo": "http://epg.51zmt.top:8000/tb1/qt/PHOTOGRAPHY-CHANNEL.jpg" + }, { + "tvid": "101", + "epgid": "天元围棋", + "name": "中数天元围棋", + "status": "1", + "note": "中数天元围棋", + "logo": "http://epg.51zmt.top:8000/tb1/qt/TIANYUANWEIQI.jpg" + }, { + "tvid": "102", + "epgid": "现代女性", + "name": "中数现代女性", + "status": "1", + "note": "中数现代女性", + "logo": "http://epg.51zmt.top:8000/tb1/qt/CCTVPAYFEE29.png" + }, { + "tvid": "103", + "epgid": "早期教育", + "name": "中数早期教育", + "status": "1", + "note": "中数早期教育", + "logo": "http://epg.51zmt.top:8000/tb1/qt/CCTVPAYFEE33.jpg" + }, { + "tvid": "104", + "epgid": "证券资讯", + "name": "中数CCTV证券资讯", + "status": "1", + "note": "中数CCTV证券资讯", + "logo": "http://epg.51zmt.top:8000/tb1/CCTV/CCTVzhengquan.png" + }, { + "tvid": "105", + "epgid": "中学生", + "name": "中数CCTV中学生频道", + "status": "1", + "note": "中数CCTV中学生频道", + "logo": null + }, { + "tvid": "106", + "epgid": "央视台球", + "name": "中数CCTV央视台球", + "status": "1", + "note": "中数CCTV央视台球", + "logo": "http://epg.51zmt.top:8000/tb1/CCTV/CCTVtaiqiu.png" + }, { + "tvid": "107", + "epgid": "茶频道", + "name": "中数茶频道", + "status": "1", + "note": "中数茶频道", + "logo": "http://epg.51zmt.top:8000/tb1/qt/DOCUMENTARY-CHANNEL.jpg" + }, { + "tvid": "108", + "epgid": "武术世界", + "name": "中数武术世界", + "status": "1", + "note": "中数武术世界", + "logo": "http://epg.51zmt.top:8000/tb1/qt/WUSHUSHIJIE.jpg" + }, { + "tvid": "109", + "epgid": "发现之旅", + "name": "中数发现之旅", + "status": "1", + "note": "中数发现之旅", + "logo": "http://epg.51zmt.top:8000/tb1/CCTV/CCTVfaxianzhilv.png" + }, { + "tvid": "110", + "epgid": "环球奇观", + "name": "中数环球奇观", + "status": "1", + "note": "中数环球奇观", + "logo": "http://epg.51zmt.top:8000/tb1/qt/HUANQIUQIGUAN.jpg" + }, { + "tvid": "111", + "epgid": "国学", + "name": "中数国学", + "status": "1", + "note": "中数国学", + "logo": "http://epg.51zmt.top:8000/tb1/qt/SHUOWENJIEZI.jpg" + }, { + "tvid": "112", + "epgid": "文物宝库", + "name": "中数文物宝库", + "status": "1", + "note": "中数文物宝库", + "logo": "http://epg.51zmt.top:8000/tb1/qt/WENWUBAOKU.jpg" + }, { + "tvid": "113", + "epgid": "新科动漫", + "name": "中数新科动漫", + "status": "1", + "note": "中数新科动漫", + "logo": "http://epg.51zmt.top:8000/tb1/CCTV/CCTVxinke.png" + }, { + "tvid": "114", + "epgid": "幼儿教育", + "name": "中数幼儿教育", + "status": "1", + "note": "中数幼儿教育", + "logo": "http://epg.51zmt.top:8000/tb1/qt/YOUERJIAOYU.jpg" + }, { + "tvid": "115", + "epgid": "老故事", + "name": "中数CCTV老故事", + "status": "1", + "note": "中数CCTV老故事", + "logo": "http://epg.51zmt.top:8000/tb1/CCTV/CCTVlaogushi.png" + }, { + "tvid": "116", + "epgid": "快乐垂钓", + "name": "中数快乐垂钓", + "status": "1", + "note": "中数快乐垂钓", + "logo": "http://epg.51zmt.top:8000/tb1/qt/KUAILECHUIDIAO.jpg" + }, { + "tvid": "117", + "epgid": "书画频道", + "name": "中数书画频道", + "status": "1", + "note": "中数书画频道", + "logo": "http://epg.51zmt.top:8000/tb1/qt/CCTVPAYFEE37.jpg" + }, { + "tvid": "118", + "epgid": "中华美食", + "name": "中数中华美食", + "status": "1", + "note": "中数中华美食", + "logo": null + }, { + "tvid": "119", + "epgid": "DOXTV", + "name": "华诚DOXTV", + "status": "1", + "note": "华诚DOXTV", + "logo": "http://epg.51zmt.top:8000/tb1/qt/DOXTV.png" + }, { + "tvid": "120", + "epgid": "先锋乒羽", + "name": "华诚先锋乒羽", + "status": "1", + "note": "华诚先锋乒羽", + "logo": "http://epg.51zmt.top:8000/tb1/qt/XFBY.png" + }, { + "tvid": "121", + "epgid": "车迷频道", + "name": "北广车迷频道", + "status": "1", + "note": "北广车迷频道", + "logo": "http://epg.51zmt.top:8000/tb1/qt/BAMC1.jpg" + }, { + "tvid": "122", + "epgid": "考试在线", + "name": "北广考试在线", + "status": "1", + "note": "北广考试在线", + "logo": null + }, { + "tvid": "123", + "epgid": "优优宝贝", + "name": "北广优优宝贝", + "status": "1", + "note": "北广优优宝贝", + "logo": "http://epg.51zmt.top:8000/tb1/qt/BAMC3.jpg" + }, { + "tvid": "124", + "epgid": "四海钓鱼", + "name": "北广四海钓鱼", + "status": "1", + "note": "北广四海钓鱼", + "logo": "http://epg.51zmt.top:8000/tb1/qt/BAMC4.jpg" + }, { + "tvid": "125", + "epgid": "动感音乐", + "name": "北广动感音乐", + "status": "1", + "note": "北广动感音乐", + "logo": "http://epg.51zmt.top:8000/tb1/qt/BAMC5.jpg" + }, { + "tvid": "126", + "epgid": "环球旅游", + "name": "北广环球旅游", + "status": "1", + "note": "北广环球旅游", + "logo": "http://epg.51zmt.top:8000/tb1/qt/BAMC6.jpg" + }, { + "tvid": "127", + "epgid": "新娱乐", + "name": "北广新娱乐", + "status": "1", + "note": "北广新娱乐", + "logo": "http://epg.51zmt.top:8000/tb1/qt/BAMC7.jpg" + }, { + "tvid": "128", + "epgid": "京视剧场", + "name": "北广京视剧场", + "status": "1", + "note": "北广京视剧场", + "logo": "http://epg.51zmt.top:8000/tb1/qt/BAMC8.jpg" + }, { + "tvid": "129", + "epgid": "弈坛春秋", + "name": "北广弈坛春秋", + "status": "1", + "note": "北广弈坛春秋", + "logo": "http://epg.51zmt.top:8000/tb1/qt/BAMC10.jpg" + }, { + "tvid": "130", + "epgid": "置业频道", + "name": "北广置业频道", + "status": "1", + "note": "北广置业频道", + "logo": null + }, { + "tvid": "131", + "epgid": "央广健康", + "name": "北广央广健康", + "status": "1", + "note": "北广央广健康", + "logo": "http://epg.51zmt.top:8000/tb1/qt/BAMC14.jpg" + }, { + "tvid": "132", + "epgid": "休闲指南", + "name": "北广休闲指南", + "status": "1", + "note": "北广休闲指南", + "logo": null + }, { + "tvid": "133", + "epgid": "时代家居", + "name": "北广时代家居", + "status": "1", + "note": "北广时代家居", + "logo": "http://epg.51zmt.top:8000/tb1/qt/BAMC17.jpg" + }, { + "tvid": "134", + "epgid": "时代出行", + "name": "北广时代出行", + "status": "1", + "note": "北广时代出行", + "logo": "http://epg.51zmt.top:8000/tb1/qt/BAMC18.jpg" + }, { + "tvid": "135", + "epgid": "时代风尚", + "name": "北广时代风尚", + "status": "1", + "note": "北广时代风尚", + "logo": "http://epg.51zmt.top:8000/tb1/qt/BAMC19.jpg" + }, { + "tvid": "136", + "epgid": "时代美食", + "name": "北广时代美食", + "status": "1", + "note": "北广时代美食", + "logo": null + }, { + "tvid": "137", + "epgid": "财富天下", + "name": "北广财富天下", + "status": "1", + "note": "北广财富天下", + "logo": "http://epg.51zmt.top:8000/tb1/qt/财富天下.png" + }, { + "tvid": "138", + "epgid": "人物频道", + "name": "北广人物频道", + "status": "1", + "note": "北广人物频道", + "logo": null + }, { + "tvid": "139", + "epgid": "家政频道", + "name": "北广家政频道", + "status": "1", + "note": "北广家政频道", + "logo": null + }, { + "tvid": "140", + "epgid": "百姓健康", + "name": "北广百姓健康", + "status": "1", + "note": "北广百姓健康", + "logo": "http://epg.51zmt.top:8000/tb1/qt/CHTV.jpg" + }, { + "tvid": "141", + "epgid": "亚洲影院", + "name": "华数亚洲影院", + "status": "1", + "note": "华数亚洲影院", + "logo": null + }, { + "tvid": "142", + "epgid": "精品剧场", + "name": "华数精品剧场", + "status": "1", + "note": "华数精品剧场", + "logo": "http://epg.51zmt.top:8000/tb1/qt/HSJPJC.jpg" + }, { + "tvid": "143", + "epgid": "少儿动漫", + "name": "华数少儿动漫", + "status": "1", + "note": "华数少儿动漫", + "logo": "http://epg.51zmt.top:8000/tb1/qt/HSSEDM.jpg" + }, { + "tvid": "144", + "epgid": "欧美影院", + "name": "华数欧美影院", + "status": "1", + "note": "华数欧美影院", + "logo": "http://epg.51zmt.top:8000/tb1/qt/OMYY.jpg" + }, { + "tvid": "145", + "epgid": "IPTV3+", + "name": "IPTV3⁺", + "status": "1", + "note": "IPTV3⁺", + "logo": null + }, { + "tvid": "146", + "epgid": "卡酷动画", + "name": "卡酷动画", + "status": "1", + "note": "卡酷动画", + "logo": "http://epg.51zmt.top:8000/tb1/qt/kaku.png" + }, { + "tvid": "147", + "epgid": "北京纪实", + "name": "北京冬奥纪实", + "status": "1", + "note": "北京冬奥纪实", + "logo": "http://epg.51zmt.top:8000/tb1/ws/beijingjishi.png" + }, { + "tvid": "148", + "epgid": "BTV文艺", + "name": "BTV文艺", + "status": "1", + "note": "BTV文艺", + "logo": "http://epg.51zmt.top:8000/tb1/sheng/BTV文艺.png" + }, { + "tvid": "149", + "epgid": "BTV科教", + "name": "BTV科教", + "status": "1", + "note": "BTV科教", + "logo": "http://epg.51zmt.top:8000/tb1/sheng/BTV科教.png" + }, { + "tvid": "150", + "epgid": "BTV影视", + "name": "BTV影视", + "status": "1", + "note": "BTV影视", + "logo": "http://epg.51zmt.top:8000/tb1/sheng/BTV影视.png" + }, { + "tvid": "151", + "epgid": "BTV财经", + "name": "BTV财经", + "status": "1", + "note": "BTV财经", + "logo": "http://epg.51zmt.top:8000/tb1/sheng/BTV财经.png" + }, { + "tvid": "152", + "epgid": "BTV生活", + "name": "BTV生活", + "status": "1", + "note": "BTV生活", + "logo": "http://epg.51zmt.top:8000/tb1/sheng/BTV生活.png" + }, { + "tvid": "153", + "epgid": "BTV青年", + "name": "BTV青年", + "status": "1", + "note": "BTV青年", + "logo": "http://epg.51zmt.top:8000/tb1/sheng/BTV青年.png" + }, { + "tvid": "154", + "epgid": "BTV新闻", + "name": "BTV新闻", + "status": "1", + "note": "BTV新闻", + "logo": "http://epg.51zmt.top:8000/tb1/sheng/BTV新闻.png" + }, { + "tvid": "155", + "epgid": "哈哈炫动", + "name": "哈哈炫动", + "status": "1", + "note": "哈哈炫动", + "logo": "http://epg.51zmt.top:8000/tb1/qt/xuandong.png" + }, { + "tvid": "156", + "epgid": "DOX雅趣", + "name": "DOX雅趣", + "status": "1", + "note": "DOX雅趣", + "logo": "http://epg.51zmt.top:8000/tb1/qt/DOX_YAQU.png" + }, { + "tvid": "157", + "epgid": "七彩戏剧", + "name": "七彩戏剧", + "status": "1", + "note": "七彩戏剧", + "logo": "http://epg.51zmt.top:8000/tb1/qt/qicaixiju.png" + }, { + "tvid": "158", + "epgid": "新视觉", + "name": "新视觉", + "status": "1", + "note": "新视觉", + "logo": "http://epg.51zmt.top:8000/tb1/qt/xinshijue.png" + }, { + "tvid": "159", + "epgid": "劲爆体育", + "name": "劲爆体育", + "status": "1", + "note": "劲爆体育", + "logo": "http://epg.51zmt.top:8000/tb1/qt/jinbaotiyu.jpg" + }, { + "tvid": "160", + "epgid": "DOX英伦", + "name": "DOX英伦", + "status": "1", + "note": "DOX英伦", + "logo": "http://epg.51zmt.top:8000/tb1/qt/dox_yinglun.png" + }, { + "tvid": "161", + "epgid": "DOX怡家", + "name": "DOX怡家", + "status": "1", + "note": "DOX怡家", + "logo": null + }, { + "tvid": "162", + "epgid": "DOX院线", + "name": "DOX院线", + "status": "1", + "note": "DOX院线", + "logo": null + }, { + "tvid": "163", + "epgid": "DOX新知", + "name": "DOX新知", + "status": "1", + "note": "DOX新知", + "logo": null + }, { + "tvid": "164", + "epgid": "DOX新艺", + "name": "DOX新艺", + "status": "1", + "note": "DOX新艺", + "logo": null + }, { + "tvid": "165", + "epgid": "DOX剧场", + "name": "DOX剧场", + "status": "1", + "note": "DOX剧场", + "logo": "http://epg.51zmt.top:8000/tb1/qt/dox_juchang.png" + }, { + "tvid": "166", + "epgid": "MAX极速汽车", + "name": "MAX极速汽车", + "status": "1", + "note": "MAX极速汽车", + "logo": "http://epg.51zmt.top:8000/tb1/qt/jisuqiche.png" + }, { + "tvid": "167", + "epgid": "全纪实", + "name": "全纪实", + "status": "1", + "note": "全纪实", + "logo": "http://epg.51zmt.top:8000/tb1/qt/quanjishi.png" + }, { + "tvid": "168", + "epgid": "欢笑剧场", + "name": "欢笑剧场", + "status": "1", + "note": "欢笑剧场", + "logo": "http://epg.51zmt.top:8000/tb1/qt/huanxiao.png" + }, { + "tvid": "169", + "epgid": "魅力音乐", + "name": "魅力音乐", + "status": "1", + "note": "魅力音乐", + "logo": null + }, { + "tvid": "170", + "epgid": "幸福彩", + "name": "幸福彩", + "status": "1", + "note": "幸福彩", + "logo": "http://epg.51zmt.top:8000/tb1/qt/xingfucai.png" + }, { + "tvid": "171", + "epgid": "生活时尚", + "name": "生活时尚", + "status": "1", + "note": "生活时尚", + "logo": "http://epg.51zmt.top:8000/tb1/qt/shenghuoshishang.png" + }, { + "tvid": "172", + "epgid": "游戏风云", + "name": "游戏风云", + "status": "1", + "note": "游戏风云", + "logo": "http://epg.51zmt.top:8000/tb1/qt/youxifengyun.png" + }, { + "tvid": "173", + "epgid": "上视新闻频道", + "name": "上视新闻频道", + "status": "1", + "note": "上视新闻频道", + "logo": "http://epg.51zmt.top:8000/tb1/qt/shangshixinwen.png" + }, { + "tvid": "174", + "epgid": "第一财经", + "name": "第一财经", + "status": "1", + "note": "第一财经", + "logo": "http://epg.51zmt.top:8000/tb1/qt/diyicaijing.png" + }, { + "tvid": "175", + "epgid": "东方影视", + "name": "东方影视", + "status": "1", + "note": "东方影视", + "logo": "http://epg.51zmt.top:8000/tb1/qt/shanghaidongfangyingshi.png" + }, { + "tvid": "176", + "epgid": "五星体育频道", + "name": "五星体育频道", + "status": "1", + "note": "五星体育频道", + "logo": "http://epg.51zmt.top:8000/tb1/qt/wuxingtiyu.png" + }, { + "tvid": "177", + "epgid": "上海纪实", + "name": "上海纪实人文", + "status": "1", + "note": "上海纪实人文", + "logo": "http://epg.51zmt.top:8000/tb1/ws/shanghaijishi.png" + }, { + "tvid": "178", + "epgid": "上海都市频道", + "name": "上海都市频道", + "status": "1", + "note": "上海都市频道", + "logo": "http://epg.51zmt.top:8000/tb1/qt/shanghaidushi.jpg" + }, { + "tvid": "179", + "epgid": "上视外语频道", + "name": "上视外语频道", + "status": "1", + "note": "上视外语频道", + "logo": "http://epg.51zmt.top:8000/tb1/qt/shanghaiwaiyu.png" + }, { + "tvid": "180", + "epgid": "中国交通频道", + "name": "中国交通频道", + "status": "1", + "note": "中国交通频道", + "logo": "http://epg.51zmt.top:8000/tb1/CCTV/zgjt.png" + }, { + "tvid": "181", + "epgid": "SCTV2", + "name": "四川文化旅游", + "status": "1", + "note": "四川文化旅游", + "logo": "http://epg.51zmt.top:8000/tb1/sheng/sctv2.png" + }, { + "tvid": "182", + "epgid": "SCTV3", + "name": "四川经济", + "status": "1", + "note": "四川经济", + "logo": "http://epg.51zmt.top:8000/tb1/sheng/sctv3.png" + }, { + "tvid": "183", + "epgid": "SCTV4", + "name": "四川新闻", + "status": "1", + "note": "四川新闻", + "logo": "http://epg.51zmt.top:8000/tb1/sheng/sctv4.png" + }, { + "tvid": "184", + "epgid": "SCTV5", + "name": "四川影视文艺频道", + "status": "1", + "note": "四川影视文艺频道", + "logo": "http://epg.51zmt.top:8000/tb1/sheng/sctv5.png" + }, { + "tvid": "185", + "epgid": "SCTV7", + "name": "四川妇女儿童频道", + "status": "1", + "note": "四川妇女儿童频道", + "logo": "http://epg.51zmt.top:8000/tb1/sheng/sctv7.png" + }, { + "tvid": "186", + "epgid": "峨嵋电影", + "name": "峨嵋电影频道", + "status": "1", + "note": "峨嵋电影频道", + "logo": "http://epg.51zmt.top:8000/tb1/qt/emeidianying.png" + }, { + "tvid": "187", + "epgid": "SCTV9", + "name": "四川公共频道", + "status": "1", + "note": "四川公共频道", + "logo": "http://epg.51zmt.top:8000/tb1/sheng/sctv9.png" + }, { + "tvid": "188", + "epgid": "SCTV8", + "name": "四川科教频道", + "status": "1", + "note": "四川科教频道", + "logo": "http://epg.51zmt.top:8000/tb1/sheng/sctv8.png" + }, { + "tvid": "189", + "epgid": "CDTV1", + "name": "成都新闻综合频道", + "status": "1", + "note": "成都新闻综合频道", + "logo": "http://epg.51zmt.top:8000/tb1/sheng/cdtv1.png" + }, { + "tvid": "190", + "epgid": "CDTV2", + "name": "成都经济资讯频道", + "status": "1", + "note": "成都经济资讯频道", + "logo": "http://epg.51zmt.top:8000/tb1/sheng/cdtv2.png" + }, { + "tvid": "191", + "epgid": "CDTV3", + "name": "成都都市生活频道", + "status": "1", + "note": "成都都市生活频道", + "logo": "http://epg.51zmt.top:8000/tb1/sheng/cdtv3.png" + }, { + "tvid": "192", + "epgid": "CDTV4", + "name": "成都影视文艺频道", + "status": "1", + "note": "成都影视文艺频道", + "logo": "http://epg.51zmt.top:8000/tb1/sheng/cdtv4.png" + }, { + "tvid": "193", + "epgid": "CDTV5", + "name": "成都公共频道", + "status": "1", + "note": "成都公共频道", + "logo": "http://epg.51zmt.top:8000/tb1/sheng/cdtv5.png" + }, { + "tvid": "194", + "epgid": "CDTV6", + "name": "成都少儿频道", + "status": "1", + "note": "成都少儿频道", + "logo": "http://epg.51zmt.top:8000/tb1/sheng/cdtv6.png" + }, { + "tvid": "195", + "epgid": "CDTV7", + "name": "成都美食天府", + "status": "1", + "note": "成都美食天府", + "logo": null + }, { + "tvid": "196", + "epgid": "山东齐鲁", + "name": "山东齐鲁", + "status": "1", + "note": "山东齐鲁", + "logo": "http://epg.51zmt.top:8000/tb1/qt/sd_qilu.png" + }, { + "tvid": "197", + "epgid": "山东体育", + "name": "山东体育", + "status": "1", + "note": "山东体育", + "logo": "http://epg.51zmt.top:8000/tb1/qt/sd_tiyu.png" + }, { + "tvid": "198", + "epgid": "山东农科", + "name": "山东农科", + "status": "1", + "note": "山东农科", + "logo": "http://epg.51zmt.top:8000/tb1/qt/sd_nongke.png" + }, { + "tvid": "199", + "epgid": "山东公共", + "name": "山东公共", + "status": "1", + "note": "山东公共", + "logo": "http://epg.51zmt.top:8000/tb1/qt/sd_gonggong.png" + }, { + "tvid": "200", + "epgid": "山东少儿", + "name": "山东少儿", + "status": "1", + "note": "山东少儿", + "logo": "http://epg.51zmt.top:8000/tb1/qt/sd_shaoer.png" + }, { + "tvid": "201", + "epgid": "山东影视", + "name": "山东影视", + "status": "1", + "note": "山东影视", + "logo": "http://epg.51zmt.top:8000/tb1/qt/sd_yingshi.png" + }, { + "tvid": "202", + "epgid": "山东综艺", + "name": "山东综艺", + "status": "1", + "note": "山东综艺", + "logo": "http://epg.51zmt.top:8000/tb1/qt/sd_zongyi.png" + }, { + "tvid": "203", + "epgid": "山东生活", + "name": "山东生活", + "status": "1", + "note": "山东生活", + "logo": "http://epg.51zmt.top:8000/tb1/qt/sd_shenghuo.png" + }, { + "tvid": "204", + "epgid": "环宇电影", + "name": "环宇电影", + "status": "1", + "note": "环宇电影", + "logo": "http://epg.51zmt.top:8000/tb1/qt/sd_huanyudianying.jpg" + }, { + "tvid": "205", + "epgid": "GTV游戏竞技", + "name": "GTV游戏竞技", + "status": "1", + "note": "GTV游戏竞技", + "logo": "http://epg.51zmt.top:8000/tb1/qt/gtv_youxi.png" + }, { + "tvid": "206", + "epgid": "网络棋牌", + "name": "GTV网络棋牌", + "status": "1", + "note": "GTV网络棋牌", + "logo": "http://epg.51zmt.top:8000/tb1/qt/wangluoqipai.png" + }, { + "tvid": "207", + "epgid": "新动漫", + "name": "新动漫", + "status": "1", + "note": "新动漫", + "logo": "http://epg.51zmt.top:8000/tb1/qt/xindongman.png" + }, { + "tvid": "208", + "epgid": "辽宁都市", + "name": "辽宁都市", + "status": "1", + "note": "辽宁都市", + "logo": "http://epg.51zmt.top:8000/tb1/qt/LNTV2.png" + }, { + "tvid": "209", + "epgid": "辽宁影视剧", + "name": "辽宁影视剧", + "status": "1", + "note": "辽宁影视剧", + "logo": "http://epg.51zmt.top:8000/tb1/qt/LNTV3.png" + }, { + "tvid": "210", + "epgid": "辽宁青少", + "name": "辽宁青少", + "status": "1", + "note": "辽宁青少", + "logo": "http://epg.51zmt.top:8000/tb1/qt/LNTV5.png" + }, { + "tvid": "211", + "epgid": "辽宁生活", + "name": "辽宁生活", + "status": "1", + "note": "辽宁生活", + "logo": "http://epg.51zmt.top:8000/tb1/qt/LNTV6.png" + }, { + "tvid": "212", + "epgid": "辽宁公共", + "name": "辽宁公共", + "status": "1", + "note": "辽宁公共", + "logo": "http://epg.51zmt.top:8000/tb1/qt/LNTV7.png" + }, { + "tvid": "213", + "epgid": "辽宁北方", + "name": "辽宁北方", + "status": "1", + "note": "辽宁北方", + "logo": "http://epg.51zmt.top:8000/tb1/qt/LNTV8.png" + }, { + "tvid": "214", + "epgid": "辽宁体育", + "name": "辽宁体育", + "status": "1", + "note": "辽宁体育", + "logo": "http://epg.51zmt.top:8000/tb1/qt/LNTV-SPORT.png" + }, { + "tvid": "215", + "epgid": "辽宁经济", + "name": "辽宁经济", + "status": "1", + "note": "辽宁经济", + "logo": "http://epg.51zmt.top:8000/tb1/qt/LNTV-FINANCE.png" + }, { + "tvid": "216", + "epgid": "沈阳新闻", + "name": "沈阳新闻", + "status": "1", + "note": "沈阳新闻", + "logo": "http://epg.51zmt.top:8000/tb1/qt/LNSY1.png" + }, { + "tvid": "217", + "epgid": "湖北综合频道", + "name": "湖北综合频道", + "status": "1", + "note": "湖北综合频道", + "logo": "http://epg.51zmt.top:8000/tb1/qt/HUBEI2.png" + }, { + "tvid": "218", + "epgid": "湖北影视频道", + "name": "湖北影视频道", + "status": "1", + "note": "湖北影视频道", + "logo": "http://epg.51zmt.top:8000/tb1/qt/HUBEI3.png" + }, { + "tvid": "219", + "epgid": "湖北教育频道", + "name": "湖北教育频道", + "status": "1", + "note": "湖北教育频道", + "logo": "http://epg.51zmt.top:8000/tb1/qt/HUBEI4.png" + }, { + "tvid": "220", + "epgid": "湖北生活频道", + "name": "湖北生活频道", + "status": "1", + "note": "湖北生活频道", + "logo": "http://epg.51zmt.top:8000/tb1/qt/HUBEI5.png" + }, { + "tvid": "221", + "epgid": "湖北公共·新闻", + "name": "湖北公共·新闻", + "status": "1", + "note": "湖北公共·新闻", + "logo": "http://epg.51zmt.top:8000/tb1/qt/HUBEI7.png" + }, { + "tvid": "222", + "epgid": "湖北经济频道", + "name": "湖北经济频道", + "status": "1", + "note": "湖北经济频道", + "logo": "http://epg.51zmt.top:8000/tb1/qt/HUBEI8.png" + }, { + "tvid": "223", + "epgid": "湖北垄上频道", + "name": "湖北垄上频道", + "status": "1", + "note": "湖北垄上频道", + "logo": "http://epg.51zmt.top:8000/tb1/qt/HBLS.png" + }, { + "tvid": "224", + "epgid": "武汉新闻综合频道", + "name": "武汉新闻综合频道", + "status": "1", + "note": "武汉新闻综合频道", + "logo": "http://epg.51zmt.top:8000/tb1/qt/WHTV1.png" + }, { + "tvid": "225", + "epgid": "武汉电视剧频道", + "name": "武汉电视剧频道", + "status": "1", + "note": "武汉电视剧频道", + "logo": "http://epg.51zmt.top:8000/tb1/qt/WHTV2.png" + }, { + "tvid": "226", + "epgid": "武汉科技生活频道", + "name": "武汉科技生活频道", + "status": "1", + "note": "武汉科技生活频道", + "logo": "http://epg.51zmt.top:8000/tb1/qt/WHTV3.png" + }, { + "tvid": "227", + "epgid": "武汉经济频道", + "name": "武汉经济频道", + "status": "1", + "note": "武汉经济频道", + "logo": "http://epg.51zmt.top:8000/tb1/qt/WHTV4.png" + }, { + "tvid": "228", + "epgid": "武汉文体频道", + "name": "武汉文体频道", + "status": "1", + "note": "武汉文体频道", + "logo": "http://epg.51zmt.top:8000/tb1/qt/WHTV5.png" + }, { + "tvid": "229", + "epgid": "武汉外语频道", + "name": "武汉外语频道", + "status": "1", + "note": "武汉外语频道", + "logo": "http://epg.51zmt.top:8000/tb1/qt/WHTV6.png" + }, { + "tvid": "230", + "epgid": "武汉少儿频道", + "name": "武汉少儿频道,武汉少儿", + "status": "1", + "note": "武汉少儿频道", + "logo": "http://epg.51zmt.top:8000/tb1/qt/WHTV7.png" + }, { + "tvid": "231", + "epgid": "武汉教育电视台", + "name": "武汉教育电视台", + "status": "1", + "note": "武汉教育电视台", + "logo": "http://epg.51zmt.top:8000/tb1/qt/WETV.png" + }, { + "tvid": "232", + "epgid": "靓妆频道", + "name": "江苏靓妆频道,靓妆频道", + "status": "1", + "note": "江苏靓妆频道", + "logo": null + }, { + "tvid": "233", + "epgid": "优漫卡通", + "name": "优漫卡通", + "status": "1", + "note": "优漫卡通", + "logo": "http://epg.51zmt.top:8000/tb1/qt/youman.png" + }, { + "tvid": "234", + "epgid": "DV生活频道", + "name": "DV生活频道", + "status": "1", + "note": "DV生活频道", + "logo": null + }, { + "tvid": "235", + "epgid": "高尔夫频道", + "name": "高尔夫频道", + "status": "1", + "note": "高尔夫频道", + "logo": null + }, { + "tvid": "236", + "epgid": "英语辅导频道", + "name": "英语辅导频道", + "status": "1", + "note": "英语辅导频道", + "logo": null + }, { + "tvid": "237", + "epgid": "嘉佳卡通", + "name": "嘉佳卡通", + "status": "1", + "note": "嘉佳卡通", + "logo": "http://epg.51zmt.top:8000/tb1/qt/jiajiakt.png" + }, { + "tvid": "238", + "epgid": "珠江频道", + "name": "珠江频道", + "status": "1", + "note": "珠江频道", + "logo": "http://epg.51zmt.top:8000/tb1/gt/zhujiang.png" + }, { + "tvid": "239", + "epgid": "金鹰纪实", + "name": "金鹰纪实", + "status": "1", + "note": "金鹰纪实", + "logo": "http://epg.51zmt.top:8000/tb1/ws/jinyingjishi.png" + }, { + "tvid": "240", + "epgid": "翡翠台", + "name": "翡翠台,翡翠,TVB", + "status": "1", + "note": "翡翠台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/TVB翡翠台.png" + }, { + "tvid": "241", + "epgid": "明珠台", + "name": "明珠台,明珠,Pearl,TVB Pearl", + "status": "1", + "note": "明珠台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/TVB明珠台.png" + }, { + "tvid": "242", + "epgid": "TVB经典台", + "name": "TVB经典台,TVB经典", + "status": "1", + "note": "TVB经典台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/TVBclassic.png" + }, { + "tvid": "243", + "epgid": "无线新闻", + "name": "无线新闻台,无线新闻", + "status": "1", + "note": "无线新闻台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/wxxw.png" + }, { + "tvid": "244", + "epgid": "无线财经", + "name": "无线财经资讯台,无线财经", + "status": "1", + "note": "无线财经资讯台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/tvbfinanceinformationchannel.png" + }, { + "tvid": "245", + "epgid": "凤凰中文", + "name": "凤凰卫视中文台,凤凰卫视,凤凰中文", + "status": "1", + "note": "凤凰卫视中文台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/fenghuangzhongwen.png" + }, { + "tvid": "246", + "epgid": "凤凰资讯", + "name": "凤凰卫视资讯台,凤凰资讯", + "status": "1", + "note": "凤凰卫视资讯台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/fenghuangzixun.png" + }, { + "tvid": "247", + "epgid": "凤凰香港", + "name": "凤凰卫视香港台,凤凰香港", + "status": "1", + "note": "凤凰卫视香港台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/fenghuangxianggang.png" + }, { + "tvid": "248", + "epgid": "阳光卫视", + "name": "阳光卫视", + "status": "1", + "note": "阳光卫视", + "logo": "http://epg.51zmt.top:8000/tb1/gt/yangguangweishi.png" + }, { + "tvid": "249", + "epgid": "美亚高清电影台", + "name": "美亚高清电影台(香港)", + "status": "1", + "note": "美亚高清电影台(香港)", + "logo": "http://epg.51zmt.top:8000/tb1/gt/meiyamovie.png" + }, { + "tvid": "250", + "epgid": "HMC", + "name": "香港有线Hollywood Movie Channel", + "status": "1", + "note": "香港有线Hollywood Movie Channel", + "logo": null + }, { + "tvid": "251", + "epgid": "国家地理悠人频道", + "name": "国家地理悠人频道", + "status": "1", + "note": "国家地理悠人频道", + "logo": null + }, { + "tvid": "252", + "epgid": "FOXLIFE", + "name": "FOX LIFE", + "status": "1", + "note": "FOX LIFE", + "logo": null + }, { + "tvid": "253", + "epgid": "ViuTV", + "name": "Viu TV", + "status": "1", + "note": "Viu TV", + "logo": "http://epg.51zmt.top:8000/tb1/gt/viutv.png" + }, { + "tvid": "254", + "epgid": "HKS", + "name": "香港卫视", + "status": "1", + "note": "香港卫视", + "logo": "http://epg.51zmt.top:8000/tb1/gt/hks.png" + }, { + "tvid": "255", + "epgid": "J2", + "name": "J2", + "status": "1", + "note": "J2", + "logo": "http://epg.51zmt.top:8000/tb1/gt/TVBJ2.png" + }, { + "tvid": "256", + "epgid": "香港国际财经台", + "name": "香港国际财经台,香港国际财经", + "status": "1", + "note": "香港国际财经台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/hkguojicaijing.png" + }, { + "tvid": "257", + "epgid": "香港开电视", + "name": "香港开电视", + "status": "1", + "note": "香港开电视", + "logo": "http://epg.51zmt.top:8000/tb1/gt/hongkongkai.png" + }, { + "tvid": "258", + "epgid": "有线财经资讯台", + "name": "有线财经资讯台,有线财经,有线财经资讯", + "status": "1", + "note": "有线财经资讯台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/youxiancaijingzixun.png" + }, { + "tvid": "259", + "epgid": "有线新闻台", + "name": "有线新闻台,有线新闻", + "status": "1", + "note": "有线新闻台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/youxianxinwen.png" + }, { + "tvid": "260", + "epgid": "香港603", + "name": "香港有线603", + "status": "1", + "note": "香港有线603", + "logo": "http://epg.51zmt.top:8000/tb1/gt/hongkong603.png" + }, { + "tvid": "261", + "epgid": "TVB星河频道", + "name": "TVB星河频道,TVB星河", + "status": "1", + "note": "TVB星河频道", + "logo": "http://epg.51zmt.top:8000/tb1/gt/TVB星河.png" + }, { + "tvid": "262", + "epgid": "星卫HD电影", + "name": "星卫HD电影台", + "status": "1", + "note": "星卫HD电影台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/starmov.png" + }, { + "tvid": "263", + "epgid": "卫视卡式台", + "name": "卫视卡式台", + "status": "1", + "note": "卫视卡式台", + "logo": null + }, { + "tvid": "264", + "epgid": "CHANNEL[V]", + "name": "CHANNEL [V]国际频道", + "status": "1", + "note": "CHANNEL [V]国际频道", + "logo": null + }, { + "tvid": "265", + "epgid": "东森亚洲新闻台", + "name": "东森亚洲新闻台,东森亚洲新闻", + "status": "1", + "note": "东森亚洲新闻台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/EbcAsiaNews.png" + }, { + "tvid": "266", + "epgid": "东森亚洲卫视", + "name": "东森亚洲卫视", + "status": "1", + "note": "东森亚洲卫视", + "logo": "http://epg.51zmt.top:8000/tb1/gt/EbcAsiaWeishi.png" + }, { + "tvid": "267", + "epgid": "公视2", + "name": "公视台语台(公视2)", + "status": "1", + "note": "公视台语台(公视2)", + "logo": "http://epg.51zmt.top:8000/tb1/gt/PublicTV2.png" + }, { + "tvid": "268", + "epgid": "GoodTV2", + "name": "好消息二台", + "status": "1", + "note": "好消息二台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/GoodTV2.png" + }, { + "tvid": "269", + "epgid": "创世电视", + "name": "创世电视", + "status": "1", + "note": "创世电视", + "logo": null + }, { + "tvid": "270", + "epgid": "壹电视综合台", + "name": "壹电视综合台,壹电视综合", + "status": "1", + "note": "壹电视综合台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/NEXTTVZonghe.png" + }, { + "tvid": "271", + "epgid": "壹电视电影台", + "name": "壹电视电影台,壹电视电影", + "status": "1", + "note": "壹电视电影台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/NEXTTVMovie.png" + }, { + "tvid": "272", + "epgid": "BabyTV", + "name": "Baby TV(台湾)", + "status": "1", + "note": "Baby TV(台湾)", + "logo": "http://epg.51zmt.top:8000/tb1/gt/BabyTV.png" + }, { + "tvid": "273", + "epgid": "公视三台", + "name": "公视三台", + "status": "1", + "note": "公视三台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/PublicTV3HD.png" + }, { + "tvid": "274", + "epgid": "民视无线台", + "name": "民视无线台,民视无线", + "status": "1", + "note": "民视无线台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/FTV.png" + }, { + "tvid": "275", + "epgid": "台视", + "name": "台视", + "status": "1", + "note": "台视", + "logo": "http://epg.51zmt.top:8000/tb1/gt/台视.png" + }, { + "tvid": "276", + "epgid": "大爱一台", + "name": "大爱电视台", + "status": "1", + "note": "大爱电视台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/daai.png" + }, { + "tvid": "277", + "epgid": "中视", + "name": "中视", + "status": "1", + "note": "中视", + "logo": "http://epg.51zmt.top:8000/tb1/gt/中视.png" + }, { + "tvid": "278", + "epgid": "人间卫视", + "name": "人间卫视", + "status": "1", + "note": "人间卫视", + "logo": "http://epg.51zmt.top:8000/tb1/gt/BeautifulLife.png" + }, { + "tvid": "279", + "epgid": "华视", + "name": "华视", + "status": "1", + "note": "华视", + "logo": "http://epg.51zmt.top:8000/tb1/gt/CTS.png" + }, { + "tvid": "280", + "epgid": "公视", + "name": "公视", + "status": "1", + "note": "公视", + "logo": "http://epg.51zmt.top:8000/tb1/gt/PublicTV.png" + }, { + "tvid": "281", + "epgid": "GoodTV", + "name": "好消息频道", + "status": "1", + "note": "好消息频道", + "logo": "http://epg.51zmt.top:8000/tb1/gt/GoodTV.png" + }, { + "tvid": "282", + "epgid": "原住民频道", + "name": "原住民频道", + "status": "1", + "note": "原住民频道", + "logo": "http://epg.51zmt.top:8000/tb1/gt/yuanzhumin.png" + }, { + "tvid": "283", + "epgid": "客家电视台", + "name": "客家电视台", + "status": "1", + "note": "客家电视台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/HakkaTV.png" + }, { + "tvid": "284", + "epgid": "MOMO亲子台", + "name": "MOMO亲子台", + "status": "1", + "note": "MOMO亲子台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/MOMOkids.png" + }, { + "tvid": "285", + "epgid": "东森幼幼台", + "name": "东森幼幼台,东森幼幼", + "status": "1", + "note": "东森幼幼台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/EBCYoYoTV.png" + }, { + "tvid": "286", + "epgid": "纬来综合台", + "name": "纬来综合台,纬来综合", + "status": "1", + "note": "纬来综合台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/VideolandOnTV.png" + }, { + "tvid": "287", + "epgid": "八大第一台", + "name": "八大第一台,八大第一", + "status": "1", + "note": "八大第一台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/GTVOneHD.png" + }, { + "tvid": "288", + "epgid": "八大综合台", + "name": "八大综合台,八大综合", + "status": "1", + "note": "八大综合台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/GTVVarietyHD.png" + }, { + "tvid": "289", + "epgid": "三立台湾台", + "name": "三立台湾台,三立台湾", + "status": "1", + "note": "三立台湾台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/SETTaiwan.png" + }, { + "tvid": "290", + "epgid": "三立都会台", + "name": "三立都会台,三立都会", + "status": "1", + "note": "三立都会台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/SETCity.png" + }, { + "tvid": "291", + "epgid": "卫视中文台", + "name": "卫视中文台,卫视中文", + "status": "1", + "note": "卫视中文台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/卫视中文.png" + }, { + "tvid": "292", + "epgid": "东森综合台", + "name": "东森综合台,东森综合", + "status": "1", + "note": "东森综合台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/EBCVariety.png" + }, { + "tvid": "293", + "epgid": "东森超视", + "name": "东森超视", + "status": "1", + "note": "东森超视", + "logo": "http://epg.51zmt.top:8000/tb1/gt/EBCSuper.png" + }, { + "tvid": "294", + "epgid": "中天综合台", + "name": "中天综合台,中天综合", + "status": "1", + "note": "中天综合台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/CtiVariety.png" + }, { + "tvid": "295", + "epgid": "东风卫视", + "name": "东风卫视", + "status": "1", + "note": "东风卫视", + "logo": "http://epg.51zmt.top:8000/tb1/gt/东风卫视.png" + }, { + "tvid": "296", + "epgid": "年代MUCH", + "name": "年代MUCH TV", + "status": "1", + "note": "年代MUCH TV", + "logo": "http://epg.51zmt.top:8000/tb1/gt/年代MUCH.png" + }, { + "tvid": "297", + "epgid": "中天娱乐台", + "name": "中天娱乐台", + "status": "1", + "note": "中天娱乐台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/CtiEntertainment.png" + }, { + "tvid": "298", + "epgid": "东森戏剧台", + "name": "东森戏剧台,东森戏剧", + "status": "1", + "note": "东森戏剧台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/EBCDrama.png" + }, { + "tvid": "299", + "epgid": "八大戏剧台", + "name": "八大戏剧台,八大戏剧", + "status": "1", + "note": "八大戏剧台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/GTVDramaHD.png" + }, { + "tvid": "300", + "epgid": "TVBS欢乐台", + "name": "TVBS欢乐台,TVBS欢乐", + "status": "1", + "note": "TVBS欢乐台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/TVBS欢乐.png" + }, { + "tvid": "301", + "epgid": "纬来戏剧台", + "name": "纬来戏剧台,纬来戏剧", + "status": "1", + "note": "纬来戏剧台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/VideolandDrama.png" + }, { + "tvid": "302", + "epgid": "高点综合台", + "name": "高点电视台", + "status": "1", + "note": "高点电视台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/TOPTVHD.png" + }, { + "tvid": "303", + "epgid": "JET综合台", + "name": "JET综合台", + "status": "1", + "note": "JET综合台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/JETVariety.png" + }, { + "tvid": "304", + "epgid": "壹电视新闻台", + "name": "壹电视新闻台", + "status": "1", + "note": "壹电视新闻台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/NextTVNews.png" + }, { + "tvid": "305", + "epgid": "年代新闻台", + "name": "年代新闻台,年代新闻", + "status": "1", + "note": "年代新闻台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/年代ERANewsHD.png" + }, { + "tvid": "306", + "epgid": "东森新闻台", + "name": "东森新闻台,东森新闻", + "status": "1", + "note": "东森新闻台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/EBCNews.png" + }, { + "tvid": "307", + "epgid": "中天新闻台", + "name": "中天新闻台,中天新闻", + "status": "1", + "note": "中天新闻台", + "logo": null + }, { + "tvid": "308", + "epgid": "民视新闻台", + "name": "民视新闻台,民视新闻", + "status": "1", + "note": "民视新闻台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/FTVNews.png" + }, { + "tvid": "309", + "epgid": "三立新闻台", + "name": "三立新闻台,三立新闻", + "status": "1", + "note": "三立新闻台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/SETNews.png" + }, { + "tvid": "310", + "epgid": "TVBS新闻台", + "name": "TVBS 新闻台,TVBS新闻台,TVBS新闻", + "status": "1", + "note": "TVBS 新闻台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/TVBS新闻.png" + }, { + "tvid": "311", + "epgid": "TVBS", + "name": "TVBS", + "status": "1", + "note": "TVBS", + "logo": "http://epg.51zmt.top:8000/tb1/gt/TVBS.png" + }, { + "tvid": "312", + "epgid": "东森财经新闻台", + "name": "东森财经新闻台,东森财经新闻", + "status": "1", + "note": "东森财经新闻台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/EBCFinancialNews.png" + }, { + "tvid": "313", + "epgid": "非凡新闻台", + "name": "非凡新闻台", + "status": "1", + "note": "非凡新闻台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/UniqueNews.png" + }, { + "tvid": "314", + "epgid": "卫视电影台", + "name": "卫视电影台", + "status": "1", + "note": "卫视电影台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/StarChineseMovies.png" + }, { + "tvid": "315", + "epgid": "东森电影台", + "name": "东森电影台,东森电影", + "status": "1", + "note": "东森电影台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/EBCMovies.png" + }, { + "tvid": "316", + "epgid": "纬来电影台", + "name": "纬来电影台,纬来电影", + "status": "1", + "note": "纬来电影台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/VideolandMovies.png" + }, { + "tvid": "317", + "epgid": "LSTime电影台", + "name": "LS Time龙祥時代电影台", + "status": "1", + "note": "LS Time龙祥時代电影台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/LSTIME.png" + }, { + "tvid": "318", + "epgid": "东森洋片台", + "name": "东森洋片台,东森洋片", + "status": "1", + "note": "东森洋片台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/EBCWesternMovies.png" + }, { + "tvid": "319", + "epgid": "好莱坞电影台", + "name": "好莱坞电影台,好莱坞电影", + "status": "1", + "note": "好莱坞电影台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/HMC.png" + }, { + "tvid": "320", + "epgid": "纬来育乐台", + "name": "纬来育乐台,纬来育乐", + "status": "1", + "note": "纬来育乐台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/VideolandMaxTV.png" + }, { + "tvid": "321", + "epgid": "纬来体育台", + "name": "纬来体育台,纬来体育", + "status": "1", + "note": "纬来体育台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/VideolandSportsHD.png" + }, { + "tvid": "322", + "epgid": "纬来日本台", + "name": "纬来日本台,纬来日本", + "status": "1", + "note": "纬来日本台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/VideolandJapanese.png" + }, { + "tvid": "323", + "epgid": "国兴卫视", + "name": "国兴卫视", + "status": "1", + "note": "国兴卫视", + "logo": "http://epg.51zmt.top:8000/tb1/gt/GoldSunTV.png" + }, { + "tvid": "324", + "epgid": "靖天综合台", + "name": "靖天综合台,靖天综合", + "status": "1", + "note": "靖天综合台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/靖天综合.png" + }, { + "tvid": "325", + "epgid": "靖天资讯台", + "name": "靖天资讯台,靖天资讯", + "status": "1", + "note": "靖天资讯台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/靖天资讯.png" + }, { + "tvid": "326", + "epgid": "信吉电视台", + "name": "信吉电视台", + "status": "1", + "note": "信吉电视台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/SinJiTVHD.png" + }, { + "tvid": "327", + "epgid": "靖洋戏剧台", + "name": "靖洋戏剧台", + "status": "1", + "note": "靖洋戏剧台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/靖洋戏剧.png" + }, { + "tvid": "328", + "epgid": "华艺台湾台", + "name": "华艺台湾台", + "status": "1", + "note": "华艺台湾台", + "logo": null + }, { + "tvid": "329", + "epgid": "凯亚综合台", + "name": "凯亚综合台", + "status": "1", + "note": "凯亚综合台", + "logo": null + }, { + "tvid": "330", + "epgid": "冠军电视台", + "name": "冠军电视台", + "status": "1", + "note": "冠军电视台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/ChampionTV1.png" + }, { + "tvid": "331", + "epgid": "台湾艺术台", + "name": "台湾艺术台,台湾艺术", + "status": "1", + "note": "台湾艺术台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/TACT.png" + }, { + "tvid": "332", + "epgid": "全大电视台", + "name": "全大电视台", + "status": "1", + "note": "全大电视台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/CostarTV.png" + }, { + "tvid": "333", + "epgid": "非凡商业台", + "name": "非凡商业台,非凡商业", + "status": "1", + "note": "非凡商业台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/UniqueUSTVHDUSTVHD.png" + }, { + "tvid": "334", + "epgid": "三立财经新闻台", + "name": "三立财经新闻台inews,三立财经新闻", + "status": "1", + "note": "三立财经新闻台inews", + "logo": "http://epg.51zmt.top:8000/tb1/gt/SETInews.png" + }, { + "tvid": "335", + "epgid": "中华财经", + "name": "中华财经", + "status": "1", + "note": "中华财经", + "logo": null + }, { + "tvid": "336", + "epgid": "运通财经", + "name": "运通财经", + "status": "1", + "note": "运通财经", + "logo": "http://epg.51zmt.top:8000/tb1/gt/EFTV.png" + }, { + "tvid": "337", + "epgid": "SBN全球财经台", + "name": "SBN全球财经台", + "status": "1", + "note": "SBN全球财经台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/SBN.png" + }, { + "tvid": "338", + "epgid": "诚心电视台", + "name": "诚心电视台", + "status": "1", + "note": "诚心电视台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/ChengSinTV.png" + }, { + "tvid": "339", + "epgid": "MTV", + "name": "MTV Live HD 音乐频道", + "status": "1", + "note": "MTV Live HD 音乐频道", + "logo": "http://epg.51zmt.top:8000/tb1/gt/mtvlivehd.png" + }, { + "tvid": "340", + "epgid": "靖天映画", + "name": "靖天映画", + "status": "1", + "note": "靖天映画", + "logo": "http://epg.51zmt.top:8000/tb1/gt/靖天映画.png" + }, { + "tvid": "341", + "epgid": "海豚综合台", + "name": "海豚综合台", + "status": "1", + "note": "海豚综合台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/海豚综合.png" + }, { + "tvid": "342", + "epgid": "霹雳台湾台", + "name": "霹雳台湾台", + "status": "1", + "note": "霹雳台湾台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/PiliPuppet.png" + }, { + "tvid": "343", + "epgid": "十方法界", + "name": "十方法界", + "status": "1", + "note": "十方法界", + "logo": "http://epg.51zmt.top:8000/tb1/gt/CosmosBuddhistMissionaryTV.png" + }, { + "tvid": "344", + "epgid": "信大频道", + "name": "信大频道", + "status": "1", + "note": "信大频道", + "logo": "http://epg.51zmt.top:8000/tb1/gt/SinDaTV.png" + }, { + "tvid": "345", + "epgid": "华藏卫视", + "name": "华藏卫视", + "status": "1", + "note": "华藏卫视", + "logo": "http://epg.51zmt.top:8000/tb1/gt/HwaZanTV.png" + }, { + "tvid": "346", + "epgid": "Z频道", + "name": "Z频道", + "status": "1", + "note": "Z频道", + "logo": "http://epg.51zmt.top:8000/tb1/gt/ZChannel.png" + }, { + "tvid": "347", + "epgid": "佛卫慈悲台", + "name": "佛卫慈悲台,佛卫慈悲", + "status": "1", + "note": "佛卫慈悲台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/BudddhaCompassionTV.png" + }, { + "tvid": "348", + "epgid": "生命频道", + "name": "生命频道", + "status": "1", + "note": "生命频道", + "logo": "http://epg.51zmt.top:8000/tb1/gt/shengming.png" + }, { + "tvid": "349", + "epgid": "天良综合台", + "name": "天良综合台", + "status": "1", + "note": "天良综合台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/TienLiangTVHD.png" + }, { + "tvid": "350", + "epgid": "正德电视台", + "name": "正德电视台", + "status": "1", + "note": "正德电视台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/ChengTeTV.png" + }, { + "tvid": "351", + "epgid": "高点育乐台", + "name": "高点育乐台", + "status": "1", + "note": "高点育乐台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/TOPTV2.png" + }, { + "tvid": "352", + "epgid": "冠军梦想台", + "name": "冠军梦想台", + "status": "1", + "note": "冠军梦想台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/ChampionTV2.png" + }, { + "tvid": "353", + "epgid": "八大娱乐台", + "name": "八大娱乐台,八大娱乐", + "status": "1", + "note": "八大娱乐台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/GTVEntertainment.png" + }, { + "tvid": "354", + "epgid": "大立电视台", + "name": "大立电视台,大立电视", + "status": "1", + "note": "大立电视台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/DaliTV.png" + }, { + "tvid": "355", + "epgid": "幸福空间居家台", + "name": "幸福空间居家台", + "status": "1", + "note": "幸福空间居家台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/gstv.png" + }, { + "tvid": "356", + "epgid": "大爱二台", + "name": "大爱二台", + "status": "1", + "note": "大爱二台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/daai2.png" + }, { + "tvid": "357", + "epgid": "台视新闻台", + "name": "台视新闻台,台视新闻", + "status": "1", + "note": "台视新闻台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/台视新闻.png" + }, { + "tvid": "358", + "epgid": "台视财经台", + "name": "台视财经台,台视财经", + "status": "1", + "note": "台视财经台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/台视财经.png" + }, { + "tvid": "359", + "epgid": "台视综合台", + "name": "台视综合台,台视综合", + "status": "1", + "note": "台视综合台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/台视综合.png" + }, { + "tvid": "360", + "epgid": "靖天欢乐台", + "name": "靖天欢乐台,靖天欢乐", + "status": "1", + "note": "靖天欢乐台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/靖天欢乐.png" + }, { + "tvid": "361", + "epgid": "靖天育乐台", + "name": "靖天育乐台,靖天育乐", + "status": "1", + "note": "靖天育乐台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/靖天育乐.png" + }, { + "tvid": "362", + "epgid": "靖天日本台", + "name": "靖天日本台,靖天日本", + "status": "1", + "note": "靖天日本台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/靖天日本.png" + }, { + "tvid": "363", + "epgid": "FoodNetwork美食台", + "name": "Food Network美食台", + "status": "1", + "note": "Food Network美食台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/FoodNetwork.png" + }, { + "tvid": "364", + "epgid": "HGTV居家乐活", + "name": "HGTV居家乐活", + "status": "1", + "note": "HGTV居家乐活", + "logo": "http://epg.51zmt.top:8000/tb1/gt/HGTV.png" + }, { + "tvid": "365", + "epgid": "TravelChannel", + "name": "Travel Channel", + "status": "1", + "note": "Travel Channel", + "logo": "http://epg.51zmt.top:8000/tb1/gt/travelchannel.png" + }, { + "tvid": "366", + "epgid": "亚洲美食频道", + "name": "亚洲美食频道", + "status": "1", + "note": "亚洲美食频道", + "logo": "http://epg.51zmt.top:8000/tb1/gt/afc.png" + }, { + "tvid": "367", + "epgid": "寰宇新闻", + "name": "寰宇新闻", + "status": "1", + "note": "寰宇新闻", + "logo": "http://epg.51zmt.top:8000/tb1/gt/GlobalNews.png" + }, { + "tvid": "368", + "epgid": "亚洲旅游台", + "name": "亚洲旅游台", + "status": "1", + "note": "亚洲旅游台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/asiatravel.png" + }, { + "tvid": "369", + "epgid": "博斯运动二台", + "name": "博斯运动二台", + "status": "1", + "note": "博斯运动二台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/sports_net_2.png" + }, { + "tvid": "370", + "epgid": "博斯网球台", + "name": "博斯网球台", + "status": "1", + "note": "博斯网球台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/sport_tennis.png" + }, { + "tvid": "371", + "epgid": "博斯无限台", + "name": "博斯无限台", + "status": "1", + "note": "博斯无限台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/sport_unlimited.png" + }, { + "tvid": "372", + "epgid": "博斯高球1台", + "name": "博斯高球1台", + "status": "1", + "note": "博斯高球1台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/sports-golfch.png" + }, { + "tvid": "373", + "epgid": "博斯高球2台", + "name": "博斯高球2台", + "status": "1", + "note": "博斯高球2台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/sport-golfplus.png" + }, { + "tvid": "374", + "epgid": "博斯魅力网", + "name": "博斯魅力网", + "status": "1", + "note": "博斯魅力网", + "logo": "http://epg.51zmt.top:8000/tb1/gt/sport-trendsport.png" + }, { + "tvid": "375", + "epgid": "博斯运动一台", + "name": "博斯运动一台", + "status": "1", + "note": "博斯运动一台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/sport-sports_net.png" + }, { + "tvid": "376", + "epgid": "博斯无限二台", + "name": "博斯无限二台", + "status": "1", + "note": "博斯无限二台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/sport_unlimited2.png" + }, { + "tvid": "377", + "epgid": "达文西频道", + "name": "达文西频道", + "status": "1", + "note": "达文西频道", + "logo": "http://epg.51zmt.top:8000/tb1/gt/davinci.png" + }, { + "tvid": "378", + "epgid": "MOMO追剧", + "name": "MOMO追剧台", + "status": "1", + "note": "MOMO追剧台", + "logo": null + }, { + "tvid": "379", + "epgid": "靖洋卡通台", + "name": "靖洋卡通台,靖洋卡通", + "status": "1", + "note": "靖洋卡通台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/靖洋卡通.png" + }, { + "tvid": "380", + "epgid": "靖天卡通台", + "name": "靖天卡通台,靖天卡通", + "status": "1", + "note": "靖天卡通台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/靖天卡通.png" + }, { + "tvid": "381", + "epgid": "三立综合台", + "name": "三立综合台,三立综合", + "status": "1", + "note": "三立综合台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/SETZonghe.png" + }, { + "tvid": "382", + "epgid": "龙华偶像", + "name": "龙华偶像", + "status": "1", + "note": "龙华偶像", + "logo": "http://epg.51zmt.top:8000/tb1/gt/龙华偶像.png" + }, { + "tvid": "383", + "epgid": "龙华戏剧", + "name": "龙华戏剧", + "status": "1", + "note": "龙华戏剧", + "logo": "http://epg.51zmt.top:8000/tb1/gt/龙华戏剧.png" + }, { + "tvid": "384", + "epgid": "龙华电影", + "name": "龙华电影", + "status": "1", + "note": "龙华电影", + "logo": "http://epg.51zmt.top:8000/tb1/gt/龙华电影.png" + }, { + "tvid": "385", + "epgid": "龙华卡通台", + "name": "龙华卡通台", + "status": "1", + "note": "龙华卡通台", + "logo": null + }, { + "tvid": "386", + "epgid": "龙华经典", + "name": "龙华经典", + "status": "1", + "note": "龙华经典", + "logo": "http://epg.51zmt.top:8000/tb1/gt/龙华经典.png" + }, { + "tvid": "387", + "epgid": "龙华影剧", + "name": "龙华影剧", + "status": "1", + "note": "龙华影剧", + "logo": "http://epg.51zmt.top:8000/tb1/gt/龙华影剧.png" + }, { + "tvid": "388", + "epgid": "龙华洋片", + "name": "龙华洋片", + "status": "1", + "note": "龙华洋片", + "logo": "http://epg.51zmt.top:8000/tb1/gt/龙华洋片.png" + }, { + "tvid": "389", + "epgid": "民视第一台", + "name": "民视第一台", + "status": "1", + "note": "民视第一台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/ftv-1.png" + }, { + "tvid": "390", + "epgid": "民视台湾台", + "name": "民视台湾台,民视台湾", + "status": "1", + "note": "民视台湾台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/ftv-taiwan.png" + }, { + "tvid": "391", + "epgid": "民视", + "name": "民视", + "status": "1", + "note": "民视", + "logo": "http://epg.51zmt.top:8000/tb1/gt/民视.png" + }, { + "tvid": "392", + "epgid": "中视菁采台", + "name": "中视菁采台", + "status": "1", + "note": "中视菁采台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/中视菁采.png" + }, { + "tvid": "393", + "epgid": "TVBS精采台", + "name": "TVBS精采台", + "status": "1", + "note": "TVBS精采台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/TVBS精采.png" + }, { + "tvid": "394", + "epgid": "BabyFirst", + "name": "Baby First宝宝世界", + "status": "1", + "note": "Baby First宝宝世界", + "logo": "http://epg.51zmt.top:8000/tb1/gt/BabyFirst.png" + }, { + "tvid": "395", + "epgid": "民视综艺台", + "name": "民视综艺台,民视综艺", + "status": "1", + "note": "民视综艺台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/ftv-zongyi.png" + }, { + "tvid": "396", + "epgid": "华艺MBC", + "name": "华艺MBC综合台", + "status": "1", + "note": "华艺MBC综合台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/huayimbc.png" + }, { + "tvid": "397", + "epgid": "TRACEUrban", + "name": "TRACE Urban", + "status": "1", + "note": "TRACE Urban", + "logo": "http://epg.51zmt.top:8000/tb1/gt/traceurban.png" + }, { + "tvid": "398", + "epgid": "FashionOne", + "name": "Fashion One", + "status": "1", + "note": "Fashion One", + "logo": "http://epg.51zmt.top:8000/tb1/gt/fashionone.png" + }, { + "tvid": "399", + "epgid": "龙华日韩台", + "name": "龙华日韩台,龙华日韩", + "status": "1", + "note": "龙华日韩台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/龙华日韩.png" + }, { + "tvid": "400", + "epgid": "靖天戏剧台", + "name": "靖天戏剧台,靖天戏剧", + "status": "1", + "note": "靖天戏剧台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/靖天戏剧.png" + }, { + "tvid": "401", + "epgid": "靖天电影台", + "name": "靖天电影台,靖天电影", + "status": "1", + "note": "靖天电影台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/靖天电影.png" + }, { + "tvid": "402", + "epgid": "wakuwaku", + "name": "WAKUWAKU JAPANE", + "status": "1", + "note": "WAKUWAKU JAPANE", + "logo": null + }, { + "tvid": "403", + "epgid": "中视经典", + "name": "中视经典台,中视经典", + "status": "1", + "note": "中视经典台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/中视经典.png" + }, { + "tvid": "404", + "epgid": "iFun1", + "name": "i-Fun动漫台", + "status": "1", + "note": "i-Fun动漫台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/ifun1.png" + }, { + "tvid": "405", + "epgid": "iFun2", + "name": "i-Fun动漫2台", + "status": "1", + "note": "i-Fun动漫2台", + "logo": null + }, { + "tvid": "406", + "epgid": "iFun3", + "name": "i-Fun动漫2台", + "status": "1", + "note": "i-Fun动漫2台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/ifun3.png" + }, { + "tvid": "407", + "epgid": "中视新闻", + "name": "中视新闻台,中视新闻", + "status": "1", + "note": "中视新闻台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/中视新闻.png" + }, { + "tvid": "408", + "epgid": "寰宇新闻2", + "name": "寰宇新闻二台,寰宇新闻2", + "status": "1", + "note": "寰宇新闻二台", + "logo": null + }, { + "tvid": "409", + "epgid": "CI", + "name": "CI 罪案侦查频道", + "status": "1", + "note": "CI 罪案侦查频道", + "logo": "http://epg.51zmt.top:8000/tb1/gt/CrimeInvestigation.png" + }, { + "tvid": "410", + "epgid": "cnex", + "name": "视纳华仁纪实频道", + "status": "1", + "note": "视纳华仁纪实频道", + "logo": "http://epg.51zmt.top:8000/tb1/gt/cnex.png" + }, { + "tvid": "411", + "epgid": "采昌影剧", + "name": "采昌影剧台", + "status": "1", + "note": "采昌影剧台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/采昌影剧.png" + }, { + "tvid": "412", + "epgid": "智林体育", + "name": "智林体育台", + "status": "1", + "note": "智林体育台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/智林体育台.png" + }, { + "tvid": "413", + "epgid": "影迷数位纪实", + "name": "影迷数位纪实台", + "status": "1", + "note": "影迷数位纪实台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/影迷纪实.png" + }, { + "tvid": "414", + "epgid": "影迷数位电影", + "name": "影迷数位电影台", + "status": "1", + "note": "影迷数位电影台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/影迷电影.png" + }, { + "tvid": "415", + "epgid": "ELTV", + "name": "ELTV生活英语台", + "status": "1", + "note": "ELTV生活英语台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/eltv.png" + }, { + "tvid": "416", + "epgid": "靖天国际", + "name": "KLT靖天国际,靖天国际", + "status": "1", + "note": "KLT靖天国际", + "logo": "http://epg.51zmt.top:8000/tb1/gt/靖天国际.png" + }, { + "tvid": "417", + "epgid": "龙华动画", + "name": "龙华动画", + "status": "1", + "note": "龙华动画", + "logo": "http://epg.51zmt.top:8000/tb1/gt/longhuadonghua.png" + }, { + "tvid": "418", + "epgid": "MTV综合", + "name": "MTV综合电视台", + "status": "1", + "note": "MTV综合电视台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/MTV.png" + }, { + "tvid": "419", + "epgid": "CMusic", + "name": "CMusic", + "status": "1", + "note": "CMusic", + "logo": null + }, { + "tvid": "420", + "epgid": "爱尔达体育2", + "name": "爱尔达体育2台", + "status": "1", + "note": "爱尔达体育2台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/elta_sports2.png" + }, { + "tvid": "421", + "epgid": "LUXETV", + "name": "LUXE TV Channel", + "status": "1", + "note": "LUXE TV Channel", + "logo": "http://epg.51zmt.top:8000/tb1/gt/luxetv.png" + }, { + "tvid": "422", + "epgid": "rollor", + "name": "滚动力 rollor", + "status": "1", + "note": "滚动力 rollor", + "logo": "http://epg.51zmt.top:8000/tb1/gt/roller.png" + }, { + "tvid": "423", + "epgid": "亚洲综合", + "name": "亚洲综合台,亚洲综合", + "status": "1", + "note": "亚洲综合台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/asiazonghe.png" + }, { + "tvid": "424", + "epgid": "寰宇HD综合", + "name": "寰宇HD综合台,寰宇,寰宇HD综合", + "status": "1", + "note": "寰宇HD综合台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/huanyuzonghe.png" + }, { + "tvid": "425", + "epgid": "纬来精采", + "name": "纬来精采台,纬来精采", + "status": "1", + "note": "纬来精采台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/weilaojingcai.png" + }, { + "tvid": "426", + "epgid": "Ettoday", + "name": "ETtoday综合台", + "status": "1", + "note": "ETtoday综合台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/etoday.png" + }, { + "tvid": "427", + "epgid": "八大优", + "name": "八大优频道,八大优", + "status": "1", + "note": "八大优频道", + "logo": "http://epg.51zmt.top:8000/tb1/gt/badayou.png" + }, { + "tvid": "428", + "epgid": "台湾戏剧", + "name": "台湾戏剧台", + "status": "1", + "note": "台湾戏剧台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/taiwanxiju.png" + }, { + "tvid": "429", + "epgid": "爱尔达影剧", + "name": "爱尔达影剧台", + "status": "1", + "note": "爱尔达影剧台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/elta_yingju.png" + }, { + "tvid": "430", + "epgid": "MY101综合", + "name": "MY101综合台", + "status": "1", + "note": "MY101综合台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/my101.png" + }, { + "tvid": "431", + "epgid": "星卫娱乐", + "name": "星卫娱乐台,星卫娱乐", + "status": "1", + "note": "星卫娱乐台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/starmovieyule.png" + }, { + "tvid": "432", + "epgid": "寰宇财经", + "name": "寰宇财经台,寰宇财经", + "status": "1", + "note": "寰宇财经台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/huanyucaijing.png" + }, { + "tvid": "433", + "epgid": "CatchPlay电影", + "name": "CatchPlay电影台", + "status": "1", + "note": "CatchPlay电影台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/catchplay.png" + }, { + "tvid": "434", + "epgid": "MyCinemaEurope", + "name": "我的欧洲电影", + "status": "1", + "note": "我的欧洲电影", + "logo": "http://epg.51zmt.top:8000/tb1/gt/MyCinemaEurope.png" + }, { + "tvid": "435", + "epgid": "TFC", + "name": "TFC(ABS-CBN)", + "status": "1", + "note": "TFC(ABS-CBN)", + "logo": "http://epg.51zmt.top:8000/tb1/gt/tfc.png" + }, { + "tvid": "436", + "epgid": "MY-KIDS", + "name": "MY-KIDS TV", + "status": "1", + "note": "MY-KIDS TV", + "logo": "http://epg.51zmt.top:8000/tb1/gt/mykids.png" + }, { + "tvid": "437", + "epgid": "爱尔达体育1", + "name": "爱尔达体育1台", + "status": "1", + "note": "爱尔达体育1台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/eata_sports1.png" + }, { + "tvid": "438", + "epgid": "爱尔达体育3", + "name": "爱尔达体育3台", + "status": "1", + "note": "爱尔达体育3台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/eata_sports3.png" + }, { + "tvid": "439", + "epgid": "狼谷竞技", + "name": "狼谷竞技台", + "status": "1", + "note": "狼谷竞技台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/wolvesvalley.png" + }, { + "tvid": "440", + "epgid": "美食星球", + "name": "美食星球频道", + "status": "1", + "note": "美食星球频道", + "logo": "http://epg.51zmt.top:8000/tb1/gt/fooldplanet.png" + }, { + "tvid": "441", + "epgid": "EYE旅游", + "name": "EYE TV旅游台", + "status": "1", + "note": "EYE TV旅游台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/eyelvyou.png" + }, { + "tvid": "442", + "epgid": "爱尔达综合", + "name": "爱尔达综合台", + "status": "1", + "note": "爱尔达综合台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/elta_zonghehd.png" + }, { + "tvid": "443", + "epgid": "天天电视", + "name": "天天电视台,天天电视", + "status": "1", + "note": "天天电视台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/tiantian.png" + }, { + "tvid": "444", + "epgid": "三立戏剧", + "name": "三立戏剧台,三立戏剧", + "status": "1", + "note": "三立戏剧台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/setxiju.png" + }, { + "tvid": "445", + "epgid": "EYE戏剧", + "name": "EYE TV戏剧台", + "status": "1", + "note": "EYE TV戏剧台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/eyexiju.png" + }, { + "tvid": "446", + "epgid": "曼迪日本", + "name": "曼迪日本台,曼迪日本", + "status": "1", + "note": "曼迪日本台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/mandi_japan.png" + }, { + "tvid": "447", + "epgid": "StarMoviesHD", + "name": "Star Movies HD(MOD)", + "status": "1", + "note": "Star Movies HD(MOD)", + "logo": "http://epg.51zmt.top:8000/tb1/gt/StarMoviesHD.png" + }, { + "tvid": "448", + "epgid": "华艺影剧", + "name": "华艺影剧台,华艺影剧", + "status": "1", + "note": "华艺影剧台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/huayi_yingju.png" + }, { + "tvid": "449", + "epgid": "唯心电视", + "name": "唯心电视台,唯心电视", + "status": "1", + "note": "唯心电视台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/weixin.png" + }, { + "tvid": "450", + "epgid": "卫视电影台", + "name": "卫视电影台(港),卫视电影", + "status": "1", + "note": "卫视电影台(港)", + "logo": "http://epg.51zmt.top:8000/tb1/gt/StarChineseMovies.png" + }, { + "tvid": "451", + "epgid": "澳亚卫视", + "name": "澳亚卫视", + "status": "1", + "note": "澳亚卫视", + "logo": null + }, { + "tvid": "452", + "epgid": "NHKWorld", + "name": "NHK World TV", + "status": "1", + "note": "NHK World TV", + "logo": "http://epg.51zmt.top:8000/tb1/gt/NHKWorld.png" + }, { + "tvid": "453", + "epgid": "FOXCRIME", + "name": "FOXCRIME", + "status": "1", + "note": "FOXCRIME", + "logo": null + }, { + "tvid": "454", + "epgid": "国家地理野生频道", + "name": "国家地理野生频道", + "status": "1", + "note": "国家地理野生频道", + "logo": "http://epg.51zmt.top:8000/tb1/gt/natgeowild.png" + }, { + "tvid": "455", + "epgid": "MEZZOLIVEHD", + "name": "MEZZO LIVE HD", + "status": "1", + "note": "MEZZO LIVE HD", + "logo": "http://epg.51zmt.top:8000/tb1/gt/mezzo.png" + }, { + "tvid": "456", + "epgid": "FoxSports3", + "name": "Fox Sports 3", + "status": "1", + "note": "Fox Sports 3", + "logo": null + }, { + "tvid": "457", + "epgid": "Lifetime", + "name": "Lifetime 娱乐频道", + "status": "1", + "note": "Lifetime 娱乐频道", + "logo": "http://epg.51zmt.top:8000/tb1/gt/Lifetime.png" + }, { + "tvid": "458", + "epgid": "BBCWorldNews", + "name": "BBC World News", + "status": "1", + "note": "BBC World News", + "logo": "http://epg.51zmt.top:8000/tb1/gt/BBCWORLDNEWSASIA.png" + }, { + "tvid": "459", + "epgid": "CN卡通频道", + "name": "CN卡通频道,CN卡通", + "status": "1", + "note": "CN卡通频道", + "logo": "http://epg.51zmt.top:8000/tb1/gt/CNCartoon.png" + }, { + "tvid": "460", + "epgid": "DisneyJunior", + "name": "Disney Junior", + "status": "1", + "note": "Disney Junior", + "logo": null + }, { + "tvid": "461", + "epgid": "CNBCHongKong", + "name": "CNBC亚太财经", + "status": "1", + "note": "CNBC亚太财经", + "logo": "http://epg.51zmt.top:8000/tb1/gt/CNBC.png" + }, { + "tvid": "462", + "epgid": "国家地理频道", + "name": "国家地理频道,国家地理", + "status": "1", + "note": "国家地理频道", + "logo": "http://epg.51zmt.top:8000/tb1/gt/natgeo.png" + }, { + "tvid": "463", + "epgid": "FashionTV", + "name": "法国时尚台", + "status": "1", + "note": "法国时尚台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/faguoshishang.png" + }, { + "tvid": "464", + "epgid": "FoxFamilyMovies", + "name": "Fox Family Movies", + "status": "1", + "note": "Fox Family Movies", + "logo": null + }, { + "tvid": "465", + "epgid": "FXHD", + "name": "FX HD", + "status": "1", + "note": "FX HD", + "logo": "http://epg.51zmt.top:8000/tb1/gt/FXHD.png" + }, { + "tvid": "466", + "epgid": "FoxNews", + "name": "Fox News Channel", + "status": "1", + "note": "Fox News Channel", + "logo": null + }, { + "tvid": "467", + "epgid": "DW", + "name": "DW德国之声", + "status": "1", + "note": "DW德国之声", + "logo": "http://epg.51zmt.top:8000/tb1/gt/DW.png" + }, { + "tvid": "468", + "epgid": "TV5Monde", + "name": "法国TV5Monde", + "status": "1", + "note": "法国TV5Monde", + "logo": "http://epg.51zmt.top:8000/tb1/gt/tv5monde.png" + }, { + "tvid": "469", + "epgid": "france24", + "name": "法国24台", + "status": "1", + "note": "法国24台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/france24.png" + }, { + "tvid": "470", + "epgid": "SkyNews", + "name": "Sky News", + "status": "1", + "note": "Sky News", + "logo": "http://epg.51zmt.top:8000/tb1/gt/SkyNews.png" + }, { + "tvid": "471", + "epgid": "天映经典", + "name": "天映经典频道,天映经典", + "status": "1", + "note": "天映经典频道", + "logo": null + }, { + "tvid": "472", + "epgid": "FOXActionMovies", + "name": "FOX Action Movies", + "status": "1", + "note": "FOX Action Movies", + "logo": null + }, { + "tvid": "473", + "epgid": "cinemaworld", + "name": "cinemaworld", + "status": "1", + "note": "cinemaworld", + "logo": "http://epg.51zmt.top:8000/tb1/gt/cinemaworld.png" + }, { + "tvid": "474", + "epgid": "ComedyCentralAsia", + "name": "Comedy Central Asia爆笑台", + "status": "1", + "note": "Comedy Central Asia爆笑台", + "logo": null + }, { + "tvid": "475", + "epgid": "CGTNDocumentary", + "name": "CGTN 纪录(英),CGTN纪录(英),CGTN 纪录,CGTN纪录", + "status": "1", + "note": "CGTN 纪录(英)", + "logo": "http://epg.51zmt.top:8000/tb1/CCTV/CGTNDoc.png" + }, { + "tvid": "476", + "epgid": "TraceSports", + "name": "Trace Sports", + "status": "1", + "note": "Trace Sports", + "logo": "http://epg.51zmt.top:8000/tb1/gt/TRACESportStars.png" + }, { + "tvid": "477", + "epgid": "Outdoor", + "name": "Outdoor Channel", + "status": "1", + "note": "Outdoor Channel", + "logo": "http://epg.51zmt.top:8000/tb1/gt/outdoor.png" + }, { + "tvid": "478", + "epgid": "HITS", + "name": "HITS", + "status": "1", + "note": "HITS", + "logo": "http://epg.51zmt.top:8000/tb1/gt/HITS.png" + }, { + "tvid": "479", + "epgid": "CNN", + "name": "CNN国际新闻频道", + "status": "1", + "note": "CNN国际新闻频道", + "logo": "http://epg.51zmt.top:8000/tb1/gt/CNNNews.png" + }, { + "tvid": "480", + "epgid": "国家地理频道(台湾)", + "name": "国家地理频道(台湾)", + "status": "1", + "note": "国家地理频道(台湾)", + "logo": "http://epg.51zmt.top:8000/tb1/gt/NationalGeographic_taiwan.png" + }, { + "tvid": "481", + "epgid": "Discovery", + "name": "探索发现频道,探索发现", + "status": "1", + "note": "探索发现频道", + "logo": "http://epg.51zmt.top:8000/tb1/gt/DiscoveryHD.png" + }, { + "tvid": "482", + "epgid": "TLC旅游生活频道", + "name": "TLC旅游生活频道", + "status": "1", + "note": "TLC旅游生活频道", + "logo": "http://epg.51zmt.top:8000/tb1/gt/tlc.png" + }, { + "tvid": "483", + "epgid": "动物星球", + "name": "动物星球", + "status": "1", + "note": "动物星球", + "logo": "http://epg.51zmt.top:8000/tb1/gt/AnimalPlanet.png" + }, { + "tvid": "484", + "epgid": "Disney", + "name": "迪士尼频道", + "status": "1", + "note": "迪士尼频道", + "logo": "http://epg.51zmt.top:8000/tb1/gt/Disney.png" + }, { + "tvid": "485", + "epgid": "HBO", + "name": "HBO", + "status": "1", + "note": "HBO", + "logo": "http://epg.51zmt.top:8000/tb1/gt/HBO.png" + }, { + "tvid": "486", + "epgid": "AXN", + "name": "AXN", + "status": "1", + "note": "AXN", + "logo": "http://epg.51zmt.top:8000/tb1/gt/AXN.png" + }, { + "tvid": "487", + "epgid": "FOXMOVIES", + "name": "FOX MOVIES", + "status": "1", + "note": "FOX MOVIES", + "logo": "http://epg.51zmt.top:8000/tb1/gt/FOXMOVIES.png" + }, { + "tvid": "488", + "epgid": "CINEMAX", + "name": "CINEMAX", + "status": "1", + "note": "CINEMAX", + "logo": "http://epg.51zmt.top:8000/tb1/gt/CinemaxHD.png" + }, { + "tvid": "489", + "epgid": "FOXSports", + "name": "FOX Sports", + "status": "1", + "note": "FOX Sports", + "logo": "http://epg.51zmt.top:8000/tb1/gt/FOXSports.png" + }, { + "tvid": "490", + "epgid": "FOXSports2", + "name": "FOX Sports 2", + "status": "1", + "note": "FOX Sports 2", + "logo": null + }, { + "tvid": "491", + "epgid": "ELEVEN体育一台", + "name": "ELEVEN体育一台", + "status": "1", + "note": "ELEVEN体育一台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/ELEVENsports1.png" + }, { + "tvid": "492", + "epgid": "FOX", + "name": "FOX", + "status": "1", + "note": "FOX", + "logo": "http://epg.51zmt.top:8000/tb1/gt/FOXHD.png" + }, { + "tvid": "493", + "epgid": "NHK", + "name": "NHK", + "status": "1", + "note": "NHK", + "logo": "http://epg.51zmt.top:8000/tb1/gt/NHK.png" + }, { + "tvid": "494", + "epgid": "AnimaxHD", + "name": "Animax HD", + "status": "1", + "note": "Animax HD", + "logo": null + }, { + "tvid": "495", + "epgid": "ChannelNewsAsia", + "name": "亚洲新闻台,亚洲新闻", + "status": "1", + "note": "亚洲新闻台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/channelnewsasia.png" + }, { + "tvid": "496", + "epgid": "BloombergTV", + "name": "彭博财经台,彭博财经", + "status": "1", + "note": "彭博财经台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/BloombergTV.png" + }, { + "tvid": "497", + "epgid": "ArirangTV", + "name": "韩国阿里郎", + "status": "1", + "note": "韩国阿里郎", + "logo": "http://epg.51zmt.top:8000/tb1/gt/ArirangTV.png" + }, { + "tvid": "498", + "epgid": "BBCLifestyle", + "name": "BBC Lifestyle", + "status": "1", + "note": "BBC Lifestyle", + "logo": "http://epg.51zmt.top:8000/tb1/gt/BBCLifestyle.png" + }, { + "tvid": "499", + "epgid": "DREAMWORKS", + "name": "梦工厂", + "status": "1", + "note": "梦工厂", + "logo": "http://epg.51zmt.top:8000/tb1/gt/dreamworks.png" + }, { + "tvid": "500", + "epgid": "WarnerTV", + "name": "Warner TV", + "status": "1", + "note": "Warner TV", + "logo": "http://epg.51zmt.top:8000/tb1/gt/WarnerTV.png" + }, { + "tvid": "501", + "epgid": "HBOHD", + "name": "HBO HD", + "status": "1", + "note": "HBO HD", + "logo": "http://epg.51zmt.top:8000/tb1/gt/HBOHD.png" + }, { + "tvid": "502", + "epgid": "HBOHits", + "name": "HBO 强档钜献", + "status": "1", + "note": "HBO 强档钜献", + "logo": "http://epg.51zmt.top:8000/tb1/gt/HBOHits.png" + }, { + "tvid": "503", + "epgid": "HBOSignature", + "name": "HBO 原创钜献", + "status": "1", + "note": "HBO 原创钜献", + "logo": "http://epg.51zmt.top:8000/tb1/gt/HBOSignature.png" + }, { + "tvid": "504", + "epgid": "HBOFamily", + "name": "HBO 温馨家庭", + "status": "1", + "note": "HBO 温馨家庭", + "logo": "http://epg.51zmt.top:8000/tb1/gt/HBOFamily.png" + }, { + "tvid": "505", + "epgid": "BlueAntEntertainment", + "name": "BlueAnt综艺娱乐", + "status": "1", + "note": "BlueAnt综艺娱乐", + "logo": "http://epg.51zmt.top:8000/tb1/gt/BlueAntEntertainment.png" + }, { + "tvid": "506", + "epgid": "tvN", + "name": "tvN", + "status": "1", + "note": "tvN", + "logo": "http://epg.51zmt.top:8000/tb1/gt/tvN.png" + }, { + "tvid": "507", + "epgid": "E-ENTERTAINMENT", + "name": "E!好莱坞娱乐频道", + "status": "1", + "note": "E!好莱坞娱乐频道", + "logo": null + }, { + "tvid": "508", + "epgid": "韩国娱乐台KMTV", + "name": "韩国娱乐台KMTV", + "status": "1", + "note": "韩国娱乐台KMTV", + "logo": "http://epg.51zmt.top:8000/tb1/gt/KMTV.png" + }, { + "tvid": "509", + "epgid": "DIVA", + "name": "DIVA", + "status": "1", + "note": "DIVA", + "logo": null + }, { + "tvid": "510", + "epgid": "EVE", + "name": "EVE", + "status": "1", + "note": "EVE", + "logo": "http://epg.51zmt.top:8000/tb1/gt/EVE.png" + }, { + "tvid": "511", + "epgid": "Discovery科学", + "name": "Discovery 科学频道", + "status": "1", + "note": "Discovery 科学频道", + "logo": "http://epg.51zmt.top:8000/tb1/gt/Discovery科学.png" + }, { + "tvid": "512", + "epgid": "DiscoveryAsia", + "name": "Discovery Asia", + "status": "1", + "note": "Discovery Asia", + "logo": "http://epg.51zmt.top:8000/tb1/gt/Discovery-Asia.png" + }, { + "tvid": "513", + "epgid": "DMAX", + "name": "DMAX", + "status": "1", + "note": "DMAX", + "logo": "http://epg.51zmt.top:8000/tb1/gt/DMAX.png" + }, { + "tvid": "514", + "epgid": "梅迪奇艺术", + "name": "梅迪奇艺术", + "status": "1", + "note": "梅迪奇艺术", + "logo": "http://epg.51zmt.top:8000/tb1/gt/medici-arts.png" + }, { + "tvid": "515", + "epgid": "BlueAntExtreme", + "name": "BlueAnt超极娱乐", + "status": "1", + "note": "BlueAnt超极娱乐", + "logo": "http://epg.51zmt.top:8000/tb1/gt/BLUEAntExtreme.png" + }, { + "tvid": "516", + "epgid": "Euronews", + "name": "欧洲新闻", + "status": "1", + "note": "欧洲新闻", + "logo": "http://epg.51zmt.top:8000/tb1/gt/euronews.png" + }, { + "tvid": "517", + "epgid": "Nickelodeon", + "name": "尼克儿童频道", + "status": "1", + "note": "尼克儿童频道", + "logo": "http://epg.51zmt.top:8000/tb1/gt/Nickelodeon.png" + }, { + "tvid": "518", + "epgid": "NickJr.", + "name": "小尼克频道", + "status": "1", + "note": "小尼克频道", + "logo": "http://epg.51zmt.top:8000/tb1/gt/NickJr.png" + }, { + "tvid": "519", + "epgid": "CBeebies", + "name": "CBeebies", + "status": "1", + "note": "CBeebies", + "logo": "http://epg.51zmt.top:8000/tb1/gt/CBeebies.png" + }, { + "tvid": "520", + "epgid": "Boomerang", + "name": "Boomerang", + "status": "1", + "note": "Boomerang", + "logo": "http://epg.51zmt.top:8000/tb1/gt/BOOMERANG.png" + }, { + "tvid": "521", + "epgid": "BBCEarth", + "name": "BBC Earth", + "status": "1", + "note": "BBC Earth", + "logo": "http://epg.51zmt.top:8000/tb1/gt/BBCEarth.png" + }, { + "tvid": "522", + "epgid": "ABCAustralia", + "name": "ABCAustralia", + "status": "1", + "note": "ABCAustralia", + "logo": "http://epg.51zmt.top:8000/tb1/gt/abcaus.png" + }, { + "tvid": "523", + "epgid": "CNNHeadlineNews", + "name": "CNN头条新闻台", + "status": "1", + "note": "CNN头条新闻台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/CNN.png" + }, { + "tvid": "524", + "epgid": "NHKWorldPremium", + "name": "NHKWorldPremium", + "status": "1", + "note": "NHKWorldPremium", + "logo": "http://epg.51zmt.top:8000/tb1/gt/nhkworldp.png" + }, { + "tvid": "525", + "epgid": "爱尔达娱乐台", + "name": "爱尔达娱乐台", + "status": "1", + "note": "爱尔达娱乐台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/ELTA_Ent.png" + }, { + "tvid": "526", + "epgid": "ELEVEN体育二台", + "name": "ELEVEN体育二台", + "status": "1", + "note": "ELEVEN体育二台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/elevensports2.png" + }, { + "tvid": "527", + "epgid": "History", + "name": "History 历史频道", + "status": "1", + "note": "History 历史频道", + "logo": "http://epg.51zmt.top:8000/tb1/gt/HistoryChannel.png" + }, { + "tvid": "528", + "epgid": "HISTORY2", + "name": "HISTORY 2 频道", + "status": "1", + "note": "HISTORY 2 频道", + "logo": null + }, { + "tvid": "529", + "epgid": "SMART知识频道", + "name": "SMART 知识频道", + "status": "1", + "note": "SMART 知识频道", + "logo": "http://epg.51zmt.top:8000/tb1/gt/Smart知识台.png" + }, { + "tvid": "530", + "epgid": "AMC", + "name": "AMC", + "status": "1", + "note": "AMC", + "logo": "http://epg.51zmt.top:8000/tb1/gt/AMC.png" + }, { + "tvid": "531", + "epgid": "ElevenSportPlus", + "name": "Eleven Sport Plus", + "status": "1", + "note": "Eleven Sport Plus", + "logo": null + }, { + "tvid": "532", + "epgid": "aljazeera", + "name": "半岛国际新闻台", + "status": "1", + "note": "半岛国际新闻台", + "logo": "http://epg.51zmt.top:8000/tb1/gt/aljazeera.png" + }, { + "tvid": "533", + "epgid": "EUROSPORT", + "name": "EUROSPORT", + "status": "1", + "note": "EUROSPORT", + "logo": "http://epg.51zmt.top:8000/tb1/gt/eurosport.png" + }, { + "tvid": "534", + "epgid": "Animax", + "name": "Animax", + "status": "1", + "note": "Animax", + "logo": "http://epg.51zmt.top:8000/tb1/gt/ANIMAX.png" + } + ] +} diff --git a/app/src/main/assets/js/lib/cat.js b/app/src/main/assets/js/lib/cat.js new file mode 100644 index 0000000000..63f5cf7445 --- /dev/null +++ b/app/src/main/assets/js/lib/cat.js @@ -0,0 +1 @@ +var e,pe,t=function(){return(t=Object.assign||function(e){for(var t,n=1,r=arguments.length;n{var t;return{name:e,value:this.attribs[e],namespace:null==(t=this["x-attribsNamespace"])?void 0:t[e],prefix:null==(t=this["x-attribsPrefix"])?void 0:t[e]}})}}function S(t){return t.type===e.Tag||t.type===e.Script||t.type===e.Style}function C(t){return t.type===e.CDATA}function N(t){return t.type===e.Text}function b(t){return t.type===e.Comment}function I(t){return t.type===e.Directive}function O(t){return t.type===e.Root}function k(e){return Object.prototype.hasOwnProperty.call(e,"children")}function L(e,t=!1){let n;if(N(e))n=new _(e.data);else if(b(e))n=new E(e.data);else if(S(e)){const r=t?D(e.children):[],i=new y(e.name,{...e.attribs},r);r.forEach(e=>e.parent=i),null!=e.namespace&&(i.namespace=e.namespace),e["x-attribsNamespace"]&&(i["x-attribsNamespace"]={...e["x-attribsNamespace"]}),e["x-attribsPrefix"]&&(i["x-attribsPrefix"]={...e["x-attribsPrefix"]}),n=i}else if(C(e)){const r=t?D(e.children):[],i=new g(r);r.forEach(e=>e.parent=i),n=i}else if(O(e)){const r=t?D(e.children):[],i=new v(r);r.forEach(e=>e.parent=i),e["x-mode"]&&(i["x-mode"]=e["x-mode"]),n=i}else{if(!I(e))throw new Error("Not implemented yet: "+e.type);{const t=new T(e.name,e.data);null!=e["x-name"]&&(t["x-name"]=e["x-name"],t["x-publicId"]=e["x-publicId"],t["x-systemId"]=e["x-systemId"]),n=t}}return n.startIndex=e.startIndex,n.endIndex=e.endIndex,null!=e.sourceCodeLocation&&(n.sourceCodeLocation=e.sourceCodeLocation),n}function D(e){var t=e.map(e=>L(e,!0));for(let e=1;e$\x80-\uFFFF]/g,M=new Map([[34,"""],[38,"&"],[39,"'"],[60,"<"],[62,">"]]),P=null!=String.prototype.codePointAt?(e,t)=>e.codePointAt(t):(e,t)=>55296==(64512&e.charCodeAt(t))?1024*(e.charCodeAt(t)-55296)+e.charCodeAt(t+1)-56320+65536:e.charCodeAt(t);function B(e){let t,n="",r=0;for(;null!==(t=x.exec(e));){var i=t.index,s=e.charCodeAt(i),a=M.get(s);r=void 0!==a?(n+=e.substring(r,i)+a,i+1):(n+=`${e.substring(r,i)}&#x${P(e,i).toString(16)};`,x.lastIndex+=Number(55296==(64512&s)))}return n+e.substr(r)}function F(e,t){return function(n){let r,i=0,s="";for(;r=e.exec(n);)i!==r.index&&(s+=n.substring(i,r.index)),s+=t.get(r[0].charCodeAt(0)),i=r.index+1;return s+n.substring(i)}}const U=F(/["&\u00A0]/g,new Map([[34,"""],[38,"&"],[160," "]])),H=F(/[&<>\u00A0]/g,new Map([[38,"&"],[60,"<"],[62,">"],[160," "]])),G=new Map(["altGlyph","altGlyphDef","altGlyphItem","animateColor","animateMotion","animateTransform","clipPath","feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feDropShadow","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feImage","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence","foreignObject","glyphRef","linearGradient","radialGradient","textPath"].map(e=>[e.toLowerCase(),e])),j=new Map(["definitionURL","attributeName","attributeType","baseFrequency","baseProfile","calcMode","clipPathUnits","diffuseConstant","edgeMode","filterUnits","glyphRef","gradientTransform","gradientUnits","kernelMatrix","kernelUnitLength","keyPoints","keySplines","keyTimes","lengthAdjust","limitingConeAngle","markerHeight","markerUnits","markerWidth","maskContentUnits","maskUnits","numOctaves","pathLength","patternContentUnits","patternTransform","patternUnits","pointsAtX","pointsAtY","pointsAtZ","preserveAlpha","preserveAspectRatio","primitiveUnits","refX","refY","repeatCount","repeatDur","requiredExtensions","requiredFeatures","specularConstant","specularExponent","spreadMethod","startOffset","stdDeviation","stitchTiles","surfaceScale","systemLanguage","tableValues","targetX","targetY","textLength","viewBox","viewTarget","xChannelSelector","yChannelSelector","zoomAndPan"].map(e=>[e.toLowerCase(),e])),q=new Set(["style","script","xmp","iframe","noembed","noframes","plaintext","noscript"]);function Y(e){return e.replace(/"/g,""")}const K=new Set(["area","base","basefont","br","col","command","embed","frame","hr","img","input","isindex","keygen","link","meta","param","source","track","wbr"]);function W(e,t={}){var n="length"in e?e:[e];let r="";for(let e=0;e`;case c:return``;case f:return``;case u:case l:case h:return function(e,t){!(t="foreign"===t.xmlMode&&(e.name=null!=(n=G.get(e.name))?n:e.name,e.parent)&&$.has(e.parent.name)?{...t,xmlMode:!1}:t).xmlMode&&Q.has(e.name)&&(t={...t,xmlMode:"foreign"});let r="<"+e.name;var n=function(e,t){var n;if(e){const r=!1===(null!=(n=t.encodeEntities)?n:t.decodeEntities)?Y:t.xmlMode||"utf8"!==t.encodeEntities?B:U;return Object.keys(e).map(n=>{var s,i=null!=(i=e[n])?i:"";return"foreign"===t.xmlMode&&(n=null!=(s=j.get(n))?s:n),t.emptyAttrs||t.xmlMode||""!==i?`${n}="${r(i)}"`:n}).join(" ")}}(e.attribs,t);return n&&(r+=" "+n),0===e.children.length&&(t.xmlMode?!1!==t.selfClosingTags:t.selfClosingTags&&K.has(e.name))?(t.xmlMode||(r+=" "),r+="/>"):(r+=">",0`)),r}(e,t);case a:return function(e,t){var n,r=e.data||"";return!1===(null!=(n=t.encodeEntities)?n:t.decodeEntities)||!t.xmlMode&&e.parent&&q.has(e.parent.name)?r:(t.xmlMode||"utf8"!==t.encodeEntities?B:H)(r)}(e,t)}}const $=new Set(["mi","mo","mn","ms","mtext","annotation-xml","foreignObject","desc","title"]),Q=new Set(["svg","math"]);function z(e,t){return W(e,t)}function X(e){return Array.isArray(e)?e.map(X).join(""):k(e)&&!b(e)?X(e.children):N(e)?e.data:""}function Z(t){return Array.isArray(t)?t.map(Z).join(""):k(t)&&(t.type===e.Tag||C(t))?Z(t.children):N(t)?t.data:""}function J(e){return k(e)?e.children:[]}function ee(e){return e.parent||null}function te(e){var t=ee(e);if(null!=t)return J(t);var n=[e];let{prev:r,next:i}=e;for(;null!=r;)n.unshift(r),{prev:r}=r;for(;null!=i;)n.push(i),{next:i}=i;return n}function ne(e){let t=e.next;for(;null!==t&&!S(t);)({next:t}=t);return t}function re(e){let t=e.prev;for(;null!==t&&!S(t);)({prev:t}=t);return t}function ie(e){var t,n;e.prev&&(e.prev.next=e.next),e.next&&(e.next.prev=e.prev),e.parent&&0<=(n=(t=e.parent.children).lastIndexOf(e))&&t.splice(n,1),e.next=null,e.prev=null,e.parent=null}function se(e,t,n=!0,r=1/0){return ae(e,Array.isArray(t)?t:[t],n,r)}function ae(e,t,n,r){for(var i=[],s=[t],a=[0];;)if(a[0]>=s[0].length){if(1===a.length)return i;s.shift(),a.shift()}else{const t=s[0][a[0]++];if(e(t)&&(i.push(t),--r<=0))return i;n&&k(t)&&0"function"==typeof e?t=>S(t)&&e(t.name):"*"===e?S:t=>S(t)&&t.name===e,tag_type:e=>"function"==typeof e?t=>e(t.type):t=>t.type===e,tag_contains:e=>"function"==typeof e?t=>N(t)&&e(t.data):t=>N(t)&&t.data===e};function ue(e,t){return"function"==typeof t?n=>S(n)&&t(n.attribs[e]):n=>S(n)&&n.attribs[e]===t}function le(e,t){return n=>e(n)||t(n)}function he(e){var t=Object.keys(e).map(t=>{var n=e[t];return Object.prototype.hasOwnProperty.call(ce,t)?ce[t](n):ue(t,n)});return 0===t.length?null:t.reduce(le)}function fe(e,t,n=!0,r=1/0){return se(ce.tag_name(e),t,n,r)}function de(e,t){var n=[],r=[];if(e===t)return 0;let i=k(e)?e:e.parent;for(;i;)n.unshift(i),i=i.parent;for(i=k(t)?t:t.parent;i;)r.unshift(i),i=i.parent;var o,c,u,l,s=Math.min(n.length,r.length);let a=0;for(;ac.indexOf(l)?o===t?pe.FOLLOWING|pe.CONTAINED_BY:pe.FOLLOWING:o===e?pe.PRECEDING|pe.CONTAINS:pe.PRECEDING)}function me(e){return(e=e.filter((e,t,n)=>!n.includes(e,t+1))).sort((e,t)=>(e=de(e,t))&pe.PRECEDING?-1:e&pe.FOLLOWING?1:0),e}!function(e){e[e.DISCONNECTED=1]="DISCONNECTED",e[e.PRECEDING=2]="PRECEDING",e[e.FOLLOWING=4]="FOLLOWING",e[e.CONTAINS=8]="CONTAINS",e[e.CONTAINED_BY=16]="CONTAINED_BY"}(pe=pe||{});const _e=["url","type","lang"],Ee=["fileSize","bitrate","framerate","samplingrate","channels","duration","height","width"];function Te(e){return fe("media:content",e).map(e=>{var t=e.attribs,n={medium:t.medium,isDefault:!!t.isDefault};for(const e of _e)t[e]&&(n[e]=t[e]);for(const e of Ee)t[e]&&(n[e]=parseInt(t[e],10));return t.expression&&(n.expression=t.expression),n})}function Ae(e,t){return fe(e,t,!0,1)[0]}function ge(e,t,n=!1){return X(fe(e,t,n,1)).trim()}function ve(e,t,n,r,i=!1){(n=ge(n,r,i))&&(e[t]=n)}function ye(e){return"rss"===e||"feed"===e||"rdf:RDF"===e}var Se=Object.freeze({__proto__:null,isTag:S,isCDATA:C,isText:N,isComment:b,isDocument:O,hasChildren:k,getOuterHTML:z,getInnerHTML:function(e,t){return k(e)?e.children.map(e=>z(e,t)).join(""):""},getText:function e(t){return Array.isArray(t)?t.map(e).join(""):S(t)?"br"===t.name?"\n":e(t.children):C(t)?e(t.children):N(t)?t.data:""},textContent:X,innerText:Z,getChildren:J,getParent:ee,getSiblings:te,getAttributeValue:function(e,t){return null==(e=e.attribs)?void 0:e[t]},hasAttrib:function(e,t){return null!=e.attribs&&Object.prototype.hasOwnProperty.call(e.attribs,t)&&null!=e.attribs[t]},getName:function(e){return e.name},nextElementSibling:ne,prevElementSibling:re,removeElement:ie,replaceElement:function(e,t){const n=t.prev=e.prev;n&&(n.next=t);var r=t.next=e.next;if(r&&(r.prev=t),r=t.parent=e.parent){const n=r.children;n[n.lastIndexOf(e)]=t,e.parent=null}},appendChild:function(e,t){ie(t),t.next=null,1<(t.parent=e).children.push(t)?((e=e.children[e.children.length-2]).next=t).prev=e:t.prev=null},append:function(e,t){ie(t);var n=e.parent,r=e.next;if(t.next=r,((t.prev=e).next=t).parent=n,r){if(r.prev=t,n){const e=n.children;e.splice(e.lastIndexOf(r),0,t)}}else n&&n.children.push(t)},prependChild:function(e,t){ie(t),t.parent=e,t.prev=null,1!==e.children.unshift(t)?((e=e.children[1]).prev=t).next=e:t.next=null},prepend:function(e,t){ie(t);var r,n=e.parent;n&&(r=n.children).splice(r.indexOf(e),0,t),e.prev&&(e.prev.next=t),t.parent=n,t.prev=e.prev,(t.next=e).prev=t},filter:se,find:ae,findOneChild:function(e,t){return t.find(e)},findOne:oe,existsOne:function e(t,n){return n.some(n=>S(n)&&(t(n)||e(t,n.children)))},findAll:function(e,t){for(var n=[],r=[t],i=[0];;)if(i[0]>=r[0].length){if(1===r.length)return n;r.shift(),i.shift()}else{const t=r[0][i[0]++];S(t)&&(e(t)&&n.push(t),0{var r={media:Te(e=e.children)},t=(ve(r,"id","id",e),ve(r,"title","title",e),null==(t=Ae("link",e))?void 0:t.attribs.href);return t&&(r.link=t),(t=ge("summary",e)||ge("content",e))&&(r.description=t),(t=ge("updated",e))&&(r.pubDate=new Date(t)),r})},t=(ve(r,"id","id",e),ve(r,"title","title",e),null==(t=Ae("link",e))?void 0:t.attribs.href);return t&&(r.link=t),ve(r,"description","subtitle",e),(t=ge("updated",e))&&(r.updated=new Date(t)),ve(r,"author","email",e,!0),r}:function(e){var t=null!=(t=null==(t=Ae("channel",e.children))?void 0:t.children)?t:[],e={type:e.name.substr(0,3),id:"",items:fe("item",e.children).map(e=>{var n={media:Te(e=e.children)};return ve(n,"id","guid",e),ve(n,"title","title",e),ve(n,"link","link",e),ve(n,"description","description",e),(e=ge("pubDate",e)||ge("dc:date",e))&&(n.pubDate=new Date(e)),n})},s=(ve(e,"title","title",t),ve(e,"link","link",t),ve(e,"description","description",t),ge("lastBuildDate",t));return s&&(e.updated=new Date(s)),ve(e,"author","managingEditor",t,!0),e})(e):null}}),Ce=function(){return(Ce=Object.assign||function(e){for(var t,n=1,r=arguments.length;ne.length-3)&&((n=e.charCodeAt(t+1))>=xe.LowerA&&n<=xe.LowerZ||n>=xe.UpperA&&n<=xe.UpperZ||n===xe.Exclamation)&&e.includes(">",t+2)}!function(e){e[e.LowerA=97]="LowerA",e[e.LowerZ=122]="LowerZ",e[e.UpperA=65]="UpperA",e[e.UpperZ=90]="UpperZ",e[e.Exclamation=33]="Exclamation"}(xe=xe||{});var He=Object.prototype.hasOwnProperty,Ge=/\s+/,je="data-",qe={null:null,true:!0,false:!1},Ye=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,Ke=/^{[^]*}$|^\[[^]*]$/;function We(e,t,n){if(e&&S(e))return null==e.attribs&&(e.attribs={}),t?He.call(e.attribs,t)?!n&&Ye.test(t)?t:e.attribs[t]:"option"===e.name&&"value"===t?Oe(e.children):"input"!==e.name||"radio"!==e.attribs.type&&"checkbox"!==e.attribs.type||"value"!==t?void 0:"on":e.attribs}function Ve(e,t,n){null===n?Ze(e,t):e.attribs[t]="".concat(n)}function $e(e,t,n){return t in e?e[t]:!n&&Ye.test(t)?void 0!==We(e,t,!1):We(e,t,n)}function Qe(e,t,n,r){t in e?e[t]=n:Ve(e,t,!r&&Ye.test(t)?n?"":null:"".concat(n))}function ze(e,t,n){null==e.data&&(e.data={}),"object"==typeof t?Object.assign(e.data,t):"string"==typeof t&&void 0!==n&&(e.data[t]=n)}function Xe(e,t){for(var n,i,r=null==t?(n=Object.keys(e.attribs).filter(function(e){return e.startsWith(je)})).map(function(e){return e.slice(je.length).replace(/[_.-](\w|$)/g,function(e,t){return t.toUpperCase()})}):(n=[je+t.replace(/[A-Z]/g,"-$&").toLowerCase()],[t]),a=0;a").parent().html();case"innerHTML":return this.html();default:return $e(i,e,this.options.xmlMode)}}if("object"==typeof e||void 0!==t){if("function"!=typeof t)return Be(this,function(n){S(n)&&("object"==typeof e?Object.keys(e).forEach(function(t){var i=e[t];Qe(n,t,i,r.options.xmlMode)}):Qe(n,e,t,r.options.xmlMode))});if("object"==typeof e)throw new Error("Bad combination of arguments.");return Be(this,function(n,i){S(n)&&Qe(n,e,t.call(n,i,$e(n,e,r.options.xmlMode)),r.options.xmlMode)})}},data:function(e,t){var r=this[0];if(r&&S(r))return null==r.data&&(r.data={}),e?"object"==typeof e||void 0!==t?(Be(this,function(n){S(n)&&("object"==typeof e?ze(n,e):ze(n,e,t))}),this):He.call(r.data,e)?r.data[e]:Xe(r,e):Xe(r)},val:function(e){var t=0===arguments.length,n=this[0];if(!n||!S(n))return t?void 0:this;switch(n.name){case"textarea":return this.text(e);case"select":var r=this.find("option:selected");if(t)return this.attr("multiple")?r.toArray().map(function(e){return Oe(e.children)}):r.attr("value");if(null!=this.attr("multiple")||"object"!=typeof e){this.find("option").removeAttr("selected");for(var i="object"!=typeof e?[e]:e,s=0;s>10|55296,1023&r|56320)}function lt(e){return e.replace(it,ut)}function ht(e){return 39===e||34===e}function ft(e){return 32===e||9===e||10===e||12===e||13===e}function pt(e){var t=[],n=dt(t,""+e,0);if(n>=1)):e.type===et.Pseudo&&(e.data?"has"===e.name||"contains"===e.name?r=0:Array.isArray(e.data)?(r=Math.min(...e.data.map(e=>Math.min(...e.map(vt)))))<0&&(r=0):r=2:r=3),r}const yt=/[-[\]{}()*+?.,\\^$|#\s]/g;function St(e){return e.replace(yt,"\\$&")}const Ct=new Set(["accept","accept-charset","align","alink","axis","bgcolor","charset","checked","clear","codetype","color","compact","declare","defer","dir","direction","disabled","enctype","face","frame","hreflang","http-equiv","lang","language","link","media","method","multiple","nohref","noresize","noshade","nowrap","readonly","rel","rev","rules","scope","scrolling","selected","shape","target","text","type","valign","valuetype","vlink"]);function Nt(e,t){return"boolean"==typeof e.ignoreCase?e.ignoreCase:"quirks"===e.ignoreCase?!!t.quirksMode:!t.xmlMode&&Ct.has(e.name)}const bt={equals(e,t,n){const r=n.adapter,i=t.name;let s=t.value;return Nt(t,n)?(s=s.toLowerCase(),t=>{var n=r.getAttributeValue(t,i);return null!=n&&n.length===s.length&&n.toLowerCase()===s&&e(t)}):t=>r.getAttributeValue(t,i)===s&&e(t)},hyphen(e,t,n){const r=n.adapter,i=t.name;let s=t.value;const a=s.length;return Nt(t,n)?(s=s.toLowerCase(),function(t){var n=r.getAttributeValue(t,i);return null!=n&&(n.length===a||"-"===n.charAt(a))&&n.substr(0,a).toLowerCase()===s&&e(t)}):function(t){var n=r.getAttributeValue(t,i);return null!=n&&(n.length===a||"-"===n.charAt(a))&&n.substr(0,a)===s&&e(t)}},element(e,t,n){const r=n.adapter,{name:i,value:s}=t;if(/\s/.test(s))return mt.falseFunc;const a=new RegExp(`(?:^|\\s)${St(s)}(?:$|\\s)`,Nt(t,n)?"i":"");return function(t){var n=r.getAttributeValue(t,i);return null!=n&&n.length>=s.length&&a.test(n)&&e(t)}},exists:(e,{name:t},{adapter:n})=>r=>n.hasAttrib(r,t)&&e(r),start(e,t,n){const r=n.adapter,i=t.name;let s=t.value;const a=s.length;return 0===a?mt.falseFunc:Nt(t,n)?(s=s.toLowerCase(),t=>{var n=r.getAttributeValue(t,i);return null!=n&&n.length>=a&&n.substr(0,a).toLowerCase()===s&&e(t)}):t=>{var n;return!(null==(n=r.getAttributeValue(t,i))||!n.startsWith(s))&&e(t)}},end(e,t,n){const r=n.adapter,i=t.name;let s=t.value;const a=-s.length;return 0==a?mt.falseFunc:Nt(t,n)?(s=s.toLowerCase(),t=>{var n;return(null==(n=r.getAttributeValue(t,i))?void 0:n.substr(a).toLowerCase())===s&&e(t)}):t=>{var n;return!(null==(n=r.getAttributeValue(t,i))||!n.endsWith(s))&&e(t)}},any(e,t,n){const r=n.adapter,{name:i,value:s}=t;if(""===s)return mt.falseFunc;if(Nt(t,n)){const t=new RegExp(St(s),"i");return function(n){var a=r.getAttributeValue(n,i);return null!=a&&a.length>=s.length&&t.test(a)&&e(n)}}return t=>{var n;return!(null==(n=r.getAttributeValue(t,i))||!n.includes(s))&&e(t)}},not(e,t,n){const r=n.adapter,i=t.name;let s=t.value;return""===s?t=>!!r.getAttributeValue(t,i)&&e(t):Nt(t,n)?(s=s.toLowerCase(),t=>{var n=r.getAttributeValue(t,i);return(null==n||n.length!==s.length||n.toLowerCase()!==s)&&e(t)}):t=>r.getAttributeValue(t,i)!==s&&e(t)}},It=new Set([9,10,12,13,32]),Ot="0".charCodeAt(0),kt="9".charCodeAt(0);function Lt(e){return function(e){const t=e[0],n=e[1]-1;if(n<0&&t<=0)return mt.falseFunc;if(-1===t)return e=>e<=n;if(0===t)return e=>e===n;if(1===t)return n<0?mt.trueFunc:e=>e>=n;const r=Math.abs(t),i=(n%r+r)%r;return 1e>=n&&e%r==i:e=>e<=n&&e%r==i}(function(e){if("even"===(e=e.trim().toLowerCase()))return[2,0];if("odd"===e)return[2,1];let t=0,n=0,r=s(),i=a();if(t=Ot&&e.charCodeAt(t)<=kt;)r=10*r+(e.charCodeAt(t)-Ot),t++;return t===n?null:r}function o(){for(;t{var r=t.getParent(n);return null!=r&&t.isTag(r)&&e(n)}}const Rt={contains:(e,t,{adapter:n})=>function(r){return e(r)&&n.getText(r).includes(t)},icontains(e,t,{adapter:n}){const r=t.toLowerCase();return function(t){return e(t)&&n.getText(t).toLowerCase().includes(r)}},"nth-child"(e,t,{adapter:n,equals:r}){const i=Lt(t);return i===mt.falseFunc?mt.falseFunc:i===mt.trueFunc?Dt(e,n):function(t){var s=n.getSiblings(t);let a=0;for(let e=0;et=>{var r=n.getParent(t);return(null==r||!n.isTag(r))&&e(t)},scope(e,t,n,r){const i=n.equals;return r&&0!==r.length?1===r.length?t=>i(r[0],t)&&e(t):t=>r.includes(t)&&e(t):Rt.root(e,t,n)},hover:wt("isHovered"),visited:wt("isVisited"),active:wt("isActive")};function wt(e){return function(t,n,{adapter:r}){const i=r[e];return"function"!=typeof i?mt.falseFunc:function(e){return i(e)&&t(e)}}}const xt={empty:(e,{adapter:t})=>!t.getChildren(e).some(e=>t.isTag(e)||""!==t.getText(e)),"first-child"(e,{adapter:t,equals:n}){var r;return t.prevElementSibling?null==t.prevElementSibling(e):null!=(r=t.getSiblings(e).find(e=>t.isTag(e)))&&n(e,r)},"last-child"(e,{adapter:t,equals:n}){var r=t.getSiblings(e);for(let i=r.length-1;0<=i;i--){if(n(e,r[i]))return!0;if(t.isTag(r[i]))break}return!1},"first-of-type"(e,{adapter:t,equals:n}){var r=t.getSiblings(e),i=t.getName(e);for(let s=0;sn(e,i)||!t.isTag(i)||t.getName(i)!==r)},"only-child":(e,{adapter:t,equals:n})=>t.getSiblings(e).every(r=>n(e,r)||!t.isTag(r))};function Mt(e,t,n,r){if(null===n){if(e.length>r)throw new Error(`Pseudo-class :${t} requires an argument`)}else if(e.length===r)throw new Error(`Pseudo-class :${t} doesn't have any arguments`)}const Pt={"any-link":":is(a, area, link)[href]",link:":any-link:not(:visited)",disabled:":is(\n :is(button, input, select, textarea, optgroup, option)[disabled],\n optgroup[disabled] > option,\n fieldset[disabled]:not(fieldset[disabled] legend:first-of-type *)\n )",enabled:":not(:disabled)",checked:":is(:is(input[type=radio], input[type=checkbox])[checked], option:selected)",required:":is(input, select, textarea)[required]",optional:":is(input, select, textarea):not([required])",selected:"option:is([selected], select:not([multiple]):not(:has(> option[selected])) > :first-of-type)",checkbox:"[type=checkbox]",file:"[type=file]",password:"[type=password]",radio:"[type=radio]",reset:"[type=reset]",image:"[type=image]",submit:"[type=submit]",parent:":not(:empty)",header:":is(h1, h2, h3, h4, h5, h6)",button:":is(button, input[type=button])",input:":is(input, textarea, select, button)",text:"input:is(:not([type!='']), [type=text])"},Bt={};function Ft(e,t){var n=t.getSiblings(e);return n.length<=1||(e=n.indexOf(e))<0||e===n.length-1?[]:n.slice(e+1).filter(t.isTag)}function Ut(e){return{xmlMode:!!e.xmlMode,lowerCaseAttributeNames:!!e.lowerCaseAttributeNames,lowerCaseTags:!!e.lowerCaseTags,quirksMode:!!e.quirksMode,cacheResults:!!e.cacheResults,pseudos:e.pseudos,adapter:e.adapter,equals:e.equals}}const Ht=(e,t,n,r,i)=>{const s=i(t,Ut(n),r);return s===mt.trueFunc?e:s===mt.falseFunc?mt.falseFunc:t=>s(t)&&e(t)},Gt={is:Ht,matches:Ht,where:Ht,not(e,t,n,r,i){const s=i(t,Ut(n),r);return s===mt.falseFunc?e:s===mt.trueFunc?mt.falseFunc:t=>!s(t)&&e(t)},has(e,t,n,r,i){const s=n.adapter,a=Ut(n),o=(a.relativeSelector=!0,t.some(e=>e.some(Tt))?[Bt]:void 0),c=i(t,a,o);if(c===mt.falseFunc)return mt.falseFunc;const u=function(e,t){return e===mt.falseFunc?mt.falseFunc:n=>t.isTag(n)&&e(n)}(c,s);if(o&&c!==mt.trueFunc){const{shouldTestNextSiblings:t=!1}=c;return n=>{if(!e(n))return!1;o[0]=n;var r=s.getChildren(n),n=t?[...r,...Ft(n,s)]:r;return s.existsOne(u,n)}}return t=>e(t)&&s.existsOne(u,s.getChildren(t))}};function jt(e,t){return(e=t.getParent(e))&&t.isTag(e)?e:null}function qt(e,t,n,r,i){const{adapter:s,equals:a}=n;switch(t.type){case et.PseudoElement:throw new Error("Pseudo-elements are not supported by css-select");case et.ColumnCombinator:throw new Error("Column combinators are not yet supported by css-select");case et.Attribute:if(null!=t.namespace)throw new Error("Namespaced attributes are not yet supported by css-select");return n.xmlMode&&!n.lowerCaseAttributeNames||(t.name=t.name.toLowerCase()),bt[t.action](e,t,n);case et.Pseudo:return function(e,t,n,r,i){const{name:a,data:o}=t;if(Array.isArray(o)){if(a in Gt)return Gt[a](e,o,n,r,i);throw new Error(`Unknown pseudo-class :${a}(${o})`)}const c=null==(t=n.pseudos)?void 0:t[a],u="string"==typeof c?c:Pt[a];if("string"==typeof u){if(null!=o)throw new Error(`Pseudo ${a} doesn't have any arguments`);const t=pt(u);return Gt.is(e,t,n,r,i)}if("function"==typeof c)return Mt(c,a,o,1),t=>c(t,o)&&e(t);if(a in Rt)return Rt[a](e,o,n,r);if(a in xt){const t=xt[a];return Mt(t,a,o,2),r=>t(r,n,o)&&e(r)}throw new Error("Unknown pseudo-class :"+a)}(e,t,n,r,i);case et.Tag:{if(null!=t.namespace)throw new Error("Namespaced tag names are not yet supported by css-select");let r=t.name;return n.xmlMode&&!n.lowerCaseTags||(r=r.toLowerCase()),function(t){return s.getName(t)===r&&e(t)}}case et.Descendant:{if(!1===n.cacheResults||"undefined"==typeof WeakSet)return function(t){let n=t;for(;n=jt(n,s);)if(e(n))return!0;return!1};const t=new WeakSet;return function(n){let r=n;for(;r=jt(r,s);)if(!t.has(r)){if(s.isTag(r)&&e(r))return!0;t.add(r)}return!1}}case"_flexibleDescendant":return function(t){let n=t;do{if(e(n))return!0}while(n=jt(n,s));return!1};case et.Parent:return function(t){return s.getChildren(t).some(t=>s.isTag(t)&&e(t))};case et.Child:return function(t){return null!=(t=s.getParent(t))&&s.isTag(t)&&e(t)};case et.Sibling:return function(t){var n=s.getSiblings(t);for(let r=0;re.some(Yt)))}const Kt={type:et.Descendant},Wt={type:"_flexibleDescendant"},Vt={type:et.Pseudo,name:"scope",data:null};function $t(e,t,n){e.forEach(gt),n=null!=(r=t.context)?r:n;const i=Array.isArray(n),s=n&&(Array.isArray(n)?n:[n]);if(!1!==t.relativeSelector)!function(e,{adapter:t},n){var r=!(null==n||!n.every(e=>{var n=t.isTag(e)&&t.getParent(e);return e===Bt||n&&t.isTag(n)}));for(const t of e){if(!(00{if(2<=e.length){const[t,n]=e;t.type===et.Pseudo&&"scope"===t.name&&(i&&n.type===et.Descendant?e[1]=Wt:n.type!==et.Adjacent&&n.type!==et.Sibling||(a=!0))}return function(e,t,n){return e.reduce((e,r)=>e===mt.falseFunc?mt.falseFunc:qt(e,r,t,n,$t),null!=(e=t.rootFunc)?e:mt.trueFunc)}(e,t,s)}).reduce(Qt,mt.falseFunc);return r.shouldTestNextSiblings=a,r}function Qt(e,t){return t===mt.falseFunc||e===mt.trueFunc?e:e===mt.falseFunc||t===mt.trueFunc?t:function(n){return e(n)||t(n)}}const zt=(e,t)=>e===t,Xt={adapter:Se,equals:zt},Zt=(Jt=$t,function(e,t,n){return t=function(e){var r;return null==(e=null!=e?e:Xt).adapter&&(e.adapter=Se),null==e.equals&&(e.equals=null!=(r=null==(r=e.adapter)?void 0:r.equals)?r:zt),e}(t),Jt(e,t,n)});function en(e,t,n=!1){return n&&(e=function(e,t){const n=Array.isArray(e)?e.slice(0):[e],r=n.length;for(let e=0;ee.some(nn)))}function rn(e){var t=[],n=[];for(const r of e)(r.some(nn)?t:n).push(r);return[n,t]}const sn={type:et.Universal,namespace:null},an={type:et.Pseudo,name:"scope",data:null};function on(e,t,n={}){return cn([e],t,n)}function cn(e,t,n={}){var i;return"function"==typeof t?e.some(t):([t,i]=rn(pt(t)),00S(e)&&!s.has(e)):t;if(0===a.length)break;var o=hn(r,t,n);if(o.length)if(s)o.forEach(e=>s.add(e));else{if(e===i.length-1)return o;s=new Set(o)}}return void 0!==s?s.size===t.length?t:t.filter(e=>s.has(e)):[]}function hn(e,t,n){var r,s;return e.some(ot)?(r=null!=(r=n.root)?r:function(e){for(;e.parent;)e=e.parent;return e}(t[0]),s={...n,context:t,relativeSelector:!1},e.push(an),fn(r,e,s,!0,t.length)):fn(t,e,n,!1,t.length)}function fn(e,t,n,r,i){var s=t.findIndex(nn),a=t.slice(0,s),o=t[s],c=t.length-1===s?i:1/0;if(0===(c=function(e,t,n){var r=null!=t?parseInt(t,10):NaN;switch(e){case"first":return 1;case"nth":case"eq":return isFinite(r)?0<=r?r+1:1/0:0;case"lt":return isFinite(r)?0<=r?Math.min(r,n):1/0:0;case"gt":return isFinite(r)?1/0:0;case"odd":return 2*n;case"even":return 2*n-1;case"last":case"not":return 1/0}}(o.name,o.data,c)))return[];r=(0!==a.length||Array.isArray(e)?0===a.length?(Array.isArray(e)?e:[e]).filter(S):r||a.some(ot)?pn(e,[a],n,c):mn(e,[a],n):J(e).filter(S)).slice(0,c);let h=function(e,t,n,r){var i="string"==typeof n?parseInt(n,10):NaN;switch(e){case"first":case"lt":return t;case"last":return 0t%2==0);case"odd":return t.filter((e,t)=>t%2==1);case"not":{const e=new Set(ln(n,t,r));return t.filter(t=>!e.has(t))}}}(o.name,r,o.data,n);if(0===h.length||t.length===s+1)return h;if(e=(a=t.slice(s+1)).some(ot)){if(ot(a[0])){const e=a[0].type;e!==et.Sibling&&e!==et.Adjacent||(h=en(h,Se,!0)),a.unshift(sn)}n={...n,relativeSelector:!1,rootFunc:e=>h.includes(e)}}else n.rootFunc&&n.rootFunc!==_t&&(n={...n,rootFunc:_t});return a.some(nn)?fn(h,a,n,!1,i):e?pn(h,[a],n,i):mn(h,[a],n)}function pn(e,t,n,r){return dn(e,Zt(t,n,e),r)}function dn(e,t,n=1/0){return ae(e=>S(e)&&t(e),en(e,Se,t.shouldTestNextSiblings),!0,n)}function mn(e,t,n){return 0===(e=(Array.isArray(e)?e:[e]).filter(S)).length||(t=Zt(t,n))===_t?e:e.filter(t)}var _n=function(e,t,n){if(n||2===arguments.length)for(var r,i=0,s=t.length;ifn(t,e,n,!0,1/0)),e.length&&s.push(pn(t,e,n,1/0)),0===s.length?[]:1===s.length?s[0]:me(s.reduce((e,t)=>[...e,...t])))}(e,r,t)))):this._make([])},parent:Sn,parents:Cn,parentsUntil:Nn,closest:function(e){var t,r,i,n=[];return e&&(r={xmlMode:this.options.xmlMode,root:null==(t=this._root)?void 0:t[0]},i="string"==typeof e?function(t){return on(t,e,r)}:xn(e),Be(this,function(e){for(;e&&S(e);){if(i(e,0)){n.includes(e)||n.push(e);break}e=e.parent}})),this._make(n)},next:bn,nextAll:In,nextUntil:On,prev:kn,prevAll:Ln,prevUntil:Dn,siblings:Rn,children:wn,contents:function(){var e=this.toArray().reduce(function(e,t){return k(t)?e.concat(t.children):e},[]);return this._make(e)},each:function(e){for(var t=0,n=this.length;t=e.length?null:e[t+n],l=0;lthis.bufferWaterline}dropParsedChunk(){this.willDropParsedChunk()&&(this.html=this.html.substring(this.pos),this.lineStartPos-=this.pos,this.droppedBufferSize+=this.pos,this.pos=0,this.lastGapPos=-2,this.gapStack.length=0)}write(e,t){0this.html.length)return this.endOfChunkHit=!this.lastChunkWritten,!1;if(t)return this.html.startsWith(e,this.pos);for(let t=0;t=this.html.length?(this.endOfChunkHit=!this.lastChunkWritten,ar.EOF):(e=this.html.charCodeAt(e))===ar.CARRIAGE_RETURN?ar.LINE_FEED:e}advance(){if(this.pos++,this.isEol&&(this.isEol=!1,this.line++,this.lineStartPos=this.pos),this.pos>=this.html.length)return this.endOfChunkHit=!this.lastChunkWritten,ar.EOF;let e=this.html.charCodeAt(this.pos);return e===ar.CARRIAGE_RETURN?(this.isEol=!0,this.skipNextNewLine=!0,ar.LINE_FEED):e===ar.LINE_FEED&&(this.isEol=!0,this.skipNextNewLine)?(this.line--,this.skipNextNewLine=!1,this._addGap(),this.advance()):(this.skipNextNewLine=!1,pr(e)&&(e=this._processSurrogate(e)),null===this.handler.onParseError||31䀾mmaĀ;d׷׸䎓;䏜reve;䄞ƀeiy؇،ؐdil;䄢rc;䄜;䐓ot;䄠r;쀀𝔊;拙pf;쀀𝔾eater̀EFGLSTصلَٖٛ٦qualĀ;Lؾؿ扥ess;招ullEqual;执reater;檢ess;扷lantEqual;橾ilde;扳cr;쀀𝒢;扫ЀAacfiosuڅڋږڛڞڪھۊRDcy;䐪Āctڐڔek;䋇;䁞irc;䄤r;愌lbertSpace;愋ǰگ\0ڲf;愍izontalLine;攀Āctۃۅòکrok;䄦mpńېۘownHumðįqual;扏܀EJOacdfgmnostuۺ۾܃܇܎ܚܞܡܨ݄ݸދޏޕcy;䐕lig;䄲cy;䐁cute耻Í䃍Āiyܓܘrc耻Î䃎;䐘ot;䄰r;愑rave耻Ì䃌ƀ;apܠܯܿĀcgܴܷr;䄪inaryI;慈lieóϝǴ݉\0ݢĀ;eݍݎ戬Āgrݓݘral;戫section;拂isibleĀCTݬݲomma;恣imes;恢ƀgptݿރވon;䄮f;쀀𝕀a;䎙cr;愐ilde;䄨ǫޚ\0ޞcy;䐆l耻Ï䃏ʀcfosuެ޷޼߂ߐĀiyޱ޵rc;䄴;䐙r;쀀𝔍pf;쀀𝕁ǣ߇\0ߌr;쀀𝒥rcy;䐈kcy;䐄΀HJacfosߤߨ߽߬߱ࠂࠈcy;䐥cy;䐌ppa;䎚Āey߶߻dil;䄶;䐚r;쀀𝔎pf;쀀𝕂cr;쀀𝒦րJTaceflmostࠥࠩࠬࡐࡣ঳সে্਷ੇcy;䐉耻<䀼ʀcmnpr࠷࠼ࡁࡄࡍute;䄹bda;䎛g;柪lacetrf;愒r;憞ƀaeyࡗ࡜ࡡron;䄽dil;䄻;䐛Āfsࡨ॰tԀACDFRTUVarࡾࢩࢱࣦ࣠ࣼयज़ΐ४Ānrࢃ࢏gleBracket;柨rowƀ;BR࢙࢚࢞憐ar;懤ightArrow;懆eiling;挈oǵࢷ\0ࣃbleBracket;柦nǔࣈ\0࣒eeVector;楡ectorĀ;Bࣛࣜ懃ar;楙loor;挊ightĀAV࣯ࣵrrow;憔ector;楎Āerँगeƀ;AVउऊऐ抣rrow;憤ector;楚iangleƀ;BEतथऩ抲ar;槏qual;抴pƀDTVषूौownVector;楑eeVector;楠ectorĀ;Bॖॗ憿ar;楘ectorĀ;B॥०憼ar;楒ightáΜs̀EFGLSTॾঋকঝঢভqualGreater;拚ullEqual;扦reater;扶ess;檡lantEqual;橽ilde;扲r;쀀𝔏Ā;eঽা拘ftarrow;懚idot;䄿ƀnpw৔ਖਛgȀLRlr৞৷ਂਐeftĀAR০৬rrow;柵ightArrow;柷ightArrow;柶eftĀarγਊightáοightáϊf;쀀𝕃erĀLRਢਬeftArrow;憙ightArrow;憘ƀchtਾੀੂòࡌ;憰rok;䅁;扪Ѐacefiosuਗ਼੝੠੷੼અઋ઎p;椅y;䐜Ādl੥੯iumSpace;恟lintrf;愳r;쀀𝔐nusPlus;戓pf;쀀𝕄cò੶;䎜ҀJacefostuણધભીଔଙඑ඗ඞcy;䐊cute;䅃ƀaey઴હાron;䅇dil;䅅;䐝ƀgswે૰଎ativeƀMTV૓૟૨ediumSpace;怋hiĀcn૦૘ë૙eryThiî૙tedĀGL૸ଆreaterGreateòٳessLesóੈLine;䀊r;쀀𝔑ȀBnptଢନଷ଺reak;恠BreakingSpace;䂠f;愕ڀ;CDEGHLNPRSTV୕ୖ୪୼஡௫ఄ౞಄ದ೘ൡඅ櫬Āou୛୤ngruent;扢pCap;扭oubleVerticalBar;戦ƀlqxஃஊ஛ement;戉ualĀ;Tஒஓ扠ilde;쀀≂̸ists;戄reater΀;EFGLSTஶஷ஽௉௓௘௥扯qual;扱ullEqual;쀀≧̸reater;쀀≫̸ess;批lantEqual;쀀⩾̸ilde;扵umpń௲௽ownHump;쀀≎̸qual;쀀≏̸eĀfsఊధtTriangleƀ;BEచఛడ拪ar;쀀⧏̸qual;括s̀;EGLSTవశ఼ౄోౘ扮qual;扰reater;扸ess;쀀≪̸lantEqual;쀀⩽̸ilde;扴estedĀGL౨౹reaterGreater;쀀⪢̸essLess;쀀⪡̸recedesƀ;ESಒಓಛ技qual;쀀⪯̸lantEqual;拠ĀeiಫಹverseElement;戌ghtTriangleƀ;BEೋೌ೒拫ar;쀀⧐̸qual;拭ĀquೝഌuareSuĀbp೨೹setĀ;E೰ೳ쀀⊏̸qual;拢ersetĀ;Eഃആ쀀⊐̸qual;拣ƀbcpഓതൎsetĀ;Eഛഞ쀀⊂⃒qual;抈ceedsȀ;ESTലള഻െ抁qual;쀀⪰̸lantEqual;拡ilde;쀀≿̸ersetĀ;E൘൛쀀⊃⃒qual;抉ildeȀ;EFT൮൯൵ൿ扁qual;扄ullEqual;扇ilde;扉erticalBar;戤cr;쀀𝒩ilde耻Ñ䃑;䎝܀Eacdfgmoprstuvලෂ෉෕ෛ෠෧෼ขภยา฿ไlig;䅒cute耻Ó䃓Āiy෎ීrc耻Ô䃔;䐞blac;䅐r;쀀𝔒rave耻Ò䃒ƀaei෮ෲ෶cr;䅌ga;䎩cron;䎟pf;쀀𝕆enCurlyĀDQฎบoubleQuote;怜uote;怘;橔Āclวฬr;쀀𝒪ash耻Ø䃘iŬื฼de耻Õ䃕es;樷ml耻Ö䃖erĀBP๋๠Āar๐๓r;怾acĀek๚๜;揞et;掴arenthesis;揜Ҁacfhilors๿ງຊຏຒດຝະ໼rtialD;戂y;䐟r;쀀𝔓i;䎦;䎠usMinus;䂱Āipຢອncareplanåڝf;愙Ȁ;eio຺ູ໠໤檻cedesȀ;EST່້໏໚扺qual;檯lantEqual;扼ilde;找me;怳Ādp໩໮uct;戏ortionĀ;aȥ໹l;戝Āci༁༆r;쀀𝒫;䎨ȀUfos༑༖༛༟OT耻"䀢r;쀀𝔔pf;愚cr;쀀𝒬؀BEacefhiorsu༾གྷཇའཱིྦྷྪྭ႖ႩႴႾarr;椐G耻®䂮ƀcnrཎནབute;䅔g;柫rĀ;tཛྷཝ憠l;椖ƀaeyཧཬཱron;䅘dil;䅖;䐠Ā;vླྀཹ愜erseĀEUྂྙĀlq྇ྎement;戋uilibrium;懋pEquilibrium;楯r»ཹo;䎡ghtЀACDFTUVa࿁࿫࿳ဢဨၛႇϘĀnr࿆࿒gleBracket;柩rowƀ;BL࿜࿝࿡憒ar;懥eftArrow;懄eiling;按oǵ࿹\0စbleBracket;柧nǔည\0နeeVector;楝ectorĀ;Bဝသ懂ar;楕loor;挋Āerိ၃eƀ;AVဵံြ抢rrow;憦ector;楛iangleƀ;BEၐၑၕ抳ar;槐qual;抵pƀDTVၣၮၸownVector;楏eeVector;楜ectorĀ;Bႂႃ憾ar;楔ectorĀ;B႑႒懀ar;楓Āpuႛ႞f;愝ndImplies;楰ightarrow;懛ĀchႹႼr;愛;憱leDelayed;槴ڀHOacfhimoqstuფჱჷჽᄙᄞᅑᅖᅡᅧᆵᆻᆿĀCcჩხHcy;䐩y;䐨FTcy;䐬cute;䅚ʀ;aeiyᄈᄉᄎᄓᄗ檼ron;䅠dil;䅞rc;䅜;䐡r;쀀𝔖ortȀDLRUᄪᄴᄾᅉownArrow»ОeftArrow»࢚ightArrow»࿝pArrow;憑gma;䎣allCircle;战pf;쀀𝕊ɲᅭ\0\0ᅰt;戚areȀ;ISUᅻᅼᆉᆯ斡ntersection;抓uĀbpᆏᆞsetĀ;Eᆗᆘ抏qual;抑ersetĀ;Eᆨᆩ抐qual;抒nion;抔cr;쀀𝒮ar;拆ȀbcmpᇈᇛሉላĀ;sᇍᇎ拐etĀ;Eᇍᇕqual;抆ĀchᇠህeedsȀ;ESTᇭᇮᇴᇿ扻qual;檰lantEqual;扽ilde;承Tháྌ;我ƀ;esሒሓሣ拑rsetĀ;Eሜም抃qual;抇et»ሓրHRSacfhiorsሾቄ቉ቕ቞ቱቶኟዂወዑORN耻Þ䃞ADE;愢ĀHc቎ቒcy;䐋y;䐦Ābuቚቜ;䀉;䎤ƀaeyብቪቯron;䅤dil;䅢;䐢r;쀀𝔗Āeiቻ኉Dzኀ\0ኇefore;戴a;䎘Ācn኎ኘkSpace;쀀  Space;怉ldeȀ;EFTካኬኲኼ戼qual;扃ullEqual;扅ilde;扈pf;쀀𝕋ipleDot;惛Āctዖዛr;쀀𝒯rok;䅦ૡዷጎጚጦ\0ጬጱ\0\0\0\0\0ጸጽ፷ᎅ\0᏿ᐄᐊᐐĀcrዻጁute耻Ú䃚rĀ;oጇገ憟cir;楉rǣጓ\0጖y;䐎ve;䅬Āiyጞጣrc耻Û䃛;䐣blac;䅰r;쀀𝔘rave耻Ù䃙acr;䅪Ādiፁ፩erĀBPፈ፝Āarፍፐr;䁟acĀekፗፙ;揟et;掵arenthesis;揝onĀ;P፰፱拃lus;抎Āgp፻፿on;䅲f;쀀𝕌ЀADETadps᎕ᎮᎸᏄϨᏒᏗᏳrrowƀ;BDᅐᎠᎤar;椒ownArrow;懅ownArrow;憕quilibrium;楮eeĀ;AᏋᏌ报rrow;憥ownáϳerĀLRᏞᏨeftArrow;憖ightArrow;憗iĀ;lᏹᏺ䏒on;䎥ing;䅮cr;쀀𝒰ilde;䅨ml耻Ü䃜ҀDbcdefosvᐧᐬᐰᐳᐾᒅᒊᒐᒖash;披ar;櫫y;䐒ashĀ;lᐻᐼ抩;櫦Āerᑃᑅ;拁ƀbtyᑌᑐᑺar;怖Ā;iᑏᑕcalȀBLSTᑡᑥᑪᑴar;戣ine;䁼eparator;杘ilde;所ThinSpace;怊r;쀀𝔙pf;쀀𝕍cr;쀀𝒱dash;抪ʀcefosᒧᒬᒱᒶᒼirc;䅴dge;拀r;쀀𝔚pf;쀀𝕎cr;쀀𝒲Ȁfiosᓋᓐᓒᓘr;쀀𝔛;䎞pf;쀀𝕏cr;쀀𝒳ҀAIUacfosuᓱᓵᓹᓽᔄᔏᔔᔚᔠcy;䐯cy;䐇cy;䐮cute耻Ý䃝Āiyᔉᔍrc;䅶;䐫r;쀀𝔜pf;쀀𝕐cr;쀀𝒴ml;䅸ЀHacdefosᔵᔹᔿᕋᕏᕝᕠᕤcy;䐖cute;䅹Āayᕄᕉron;䅽;䐗ot;䅻Dzᕔ\0ᕛoWidtè૙a;䎖r;愨pf;愤cr;쀀𝒵௡ᖃᖊᖐ\0ᖰᖶᖿ\0\0\0\0ᗆᗛᗫᙟ᙭\0ᚕ᚛ᚲᚹ\0ᚾcute耻á䃡reve;䄃̀;Ediuyᖜᖝᖡᖣᖨᖭ戾;쀀∾̳;房rc耻â䃢te肻´̆;䐰lig耻æ䃦Ā;r²ᖺ;쀀𝔞rave耻à䃠ĀepᗊᗖĀfpᗏᗔsym;愵èᗓha;䎱ĀapᗟcĀclᗤᗧr;䄁g;樿ɤᗰ\0\0ᘊʀ;adsvᗺᗻᗿᘁᘇ戧nd;橕;橜lope;橘;橚΀;elmrszᘘᘙᘛᘞᘿᙏᙙ戠;榤e»ᘙsdĀ;aᘥᘦ戡ѡᘰᘲᘴᘶᘸᘺᘼᘾ;榨;榩;榪;榫;榬;榭;榮;榯tĀ;vᙅᙆ戟bĀ;dᙌᙍ抾;榝Āptᙔᙗh;戢»¹arr;捼Āgpᙣᙧon;䄅f;쀀𝕒΀;Eaeiop዁ᙻᙽᚂᚄᚇᚊ;橰cir;橯;扊d;手s;䀧roxĀ;e዁ᚒñᚃing耻å䃥ƀctyᚡᚦᚨr;쀀𝒶;䀪mpĀ;e዁ᚯñʈilde耻ã䃣ml耻ä䃤Āciᛂᛈoninôɲnt;樑ࠀNabcdefiklnoprsu᛭ᛱᜰ᜼ᝃᝈ᝸᝽០៦ᠹᡐᜍ᤽᥈ᥰot;櫭Ācrᛶ᜞kȀcepsᜀᜅᜍᜓong;扌psilon;䏶rime;怵imĀ;e᜚᜛戽q;拍Ŷᜢᜦee;抽edĀ;gᜬᜭ挅e»ᜭrkĀ;t፜᜷brk;掶Āoyᜁᝁ;䐱quo;怞ʀcmprtᝓ᝛ᝡᝤᝨausĀ;eĊĉptyv;榰séᜌnoõēƀahwᝯ᝱ᝳ;䎲;愶een;扬r;쀀𝔟g΀costuvwឍឝឳេ៕៛៞ƀaiuបពរðݠrc;旯p»፱ƀdptឤឨឭot;樀lus;樁imes;樂ɱឹ\0\0ើcup;樆ar;昅riangleĀdu៍្own;施p;斳plus;樄eåᑄåᒭarow;植ƀako៭ᠦᠵĀcn៲ᠣkƀlst៺֫᠂ozenge;槫riangleȀ;dlr᠒᠓᠘᠝斴own;斾eft;旂ight;斸k;搣Ʊᠫ\0ᠳƲᠯ\0ᠱ;斒;斑4;斓ck;斈ĀeoᠾᡍĀ;qᡃᡆ쀀=⃥uiv;쀀≡⃥t;挐Ȁptwxᡙᡞᡧᡬf;쀀𝕓Ā;tᏋᡣom»Ꮜtie;拈؀DHUVbdhmptuvᢅᢖᢪᢻᣗᣛᣬ᣿ᤅᤊᤐᤡȀLRlrᢎᢐᢒᢔ;敗;敔;敖;敓ʀ;DUduᢡᢢᢤᢦᢨ敐;敦;敩;敤;敧ȀLRlrᢳᢵᢷᢹ;敝;敚;敜;教΀;HLRhlrᣊᣋᣍᣏᣑᣓᣕ救;敬;散;敠;敫;敢;敟ox;槉ȀLRlrᣤᣦᣨᣪ;敕;敒;攐;攌ʀ;DUduڽ᣷᣹᣻᣽;敥;敨;攬;攴inus;抟lus;択imes;抠ȀLRlrᤙᤛᤝ᤟;敛;敘;攘;攔΀;HLRhlrᤰᤱᤳᤵᤷ᤻᤹攂;敪;敡;敞;攼;攤;攜Āevģ᥂bar耻¦䂦Ȁceioᥑᥖᥚᥠr;쀀𝒷mi;恏mĀ;e᜚᜜lƀ;bhᥨᥩᥫ䁜;槅sub;柈Ŭᥴ᥾lĀ;e᥹᥺怢t»᥺pƀ;Eeįᦅᦇ;檮Ā;qۜۛೡᦧ\0᧨ᨑᨕᨲ\0ᨷᩐ\0\0᪴\0\0᫁\0\0ᬡᬮ᭍᭒\0᯽\0ᰌƀcpr᦭ᦲ᧝ute;䄇̀;abcdsᦿᧀᧄ᧊᧕᧙戩nd;橄rcup;橉Āau᧏᧒p;橋p;橇ot;橀;쀀∩︀Āeo᧢᧥t;恁îړȀaeiu᧰᧻ᨁᨅǰ᧵\0᧸s;橍on;䄍dil耻ç䃧rc;䄉psĀ;sᨌᨍ橌m;橐ot;䄋ƀdmnᨛᨠᨦil肻¸ƭptyv;榲t脀¢;eᨭᨮ䂢räƲr;쀀𝔠ƀceiᨽᩀᩍy;䑇ckĀ;mᩇᩈ朓ark»ᩈ;䏇r΀;Ecefms᩟᩠ᩢᩫ᪤᪪᪮旋;槃ƀ;elᩩᩪᩭ䋆q;扗eɡᩴ\0\0᪈rrowĀlr᩼᪁eft;憺ight;憻ʀRSacd᪒᪔᪖᪚᪟»ཇ;擈st;抛irc;抚ash;抝nint;樐id;櫯cir;槂ubsĀ;u᪻᪼晣it»᪼ˬ᫇᫔᫺\0ᬊonĀ;eᫍᫎ䀺Ā;qÇÆɭ᫙\0\0᫢aĀ;t᫞᫟䀬;䁀ƀ;fl᫨᫩᫫戁îᅠeĀmx᫱᫶ent»᫩eóɍǧ᫾\0ᬇĀ;dኻᬂot;橭nôɆƀfryᬐᬔᬗ;쀀𝕔oäɔ脀©;sŕᬝr;愗Āaoᬥᬩrr;憵ss;朗Ācuᬲᬷr;쀀𝒸Ābpᬼ᭄Ā;eᭁᭂ櫏;櫑Ā;eᭉᭊ櫐;櫒dot;拯΀delprvw᭠᭬᭷ᮂᮬᯔ᯹arrĀlr᭨᭪;椸;椵ɰ᭲\0\0᭵r;拞c;拟arrĀ;p᭿ᮀ憶;椽̀;bcdosᮏᮐᮖᮡᮥᮨ截rcap;橈Āauᮛᮞp;橆p;橊ot;抍r;橅;쀀∪︀Ȁalrv᮵ᮿᯞᯣrrĀ;mᮼᮽ憷;椼yƀevwᯇᯔᯘqɰᯎ\0\0ᯒreã᭳uã᭵ee;拎edge;拏en耻¤䂤earrowĀlrᯮ᯳eft»ᮀight»ᮽeäᯝĀciᰁᰇoninôǷnt;戱lcty;挭ঀAHabcdefhijlorstuwz᰸᰻᰿ᱝᱩᱵᲊᲞᲬᲷ᳻᳿ᴍᵻᶑᶫᶻ᷆᷍rò΁ar;楥Ȁglrs᱈ᱍ᱒᱔ger;怠eth;愸òᄳhĀ;vᱚᱛ怐»ऊūᱡᱧarow;椏aã̕Āayᱮᱳron;䄏;䐴ƀ;ao̲ᱼᲄĀgrʿᲁr;懊tseq;橷ƀglmᲑᲔᲘ耻°䂰ta;䎴ptyv;榱ĀirᲣᲨsht;楿;쀀𝔡arĀlrᲳᲵ»ࣜ»သʀaegsv᳂͸᳖᳜᳠mƀ;oș᳊᳔ndĀ;ș᳑uit;晦amma;䏝in;拲ƀ;io᳧᳨᳸䃷de脀÷;o᳧ᳰntimes;拇nø᳷cy;䑒cɯᴆ\0\0ᴊrn;挞op;挍ʀlptuwᴘᴝᴢᵉᵕlar;䀤f;쀀𝕕ʀ;emps̋ᴭᴷᴽᵂqĀ;d͒ᴳot;扑inus;戸lus;戔quare;抡blebarwedgåúnƀadhᄮᵝᵧownarrowóᲃarpoonĀlrᵲᵶefôᲴighôᲶŢᵿᶅkaro÷གɯᶊ\0\0ᶎrn;挟op;挌ƀcotᶘᶣᶦĀryᶝᶡ;쀀𝒹;䑕l;槶rok;䄑Ādrᶰᶴot;拱iĀ;fᶺ᠖斿Āah᷀᷃ròЩaòྦangle;榦Āci᷒ᷕy;䑟grarr;柿ऀDacdefglmnopqrstuxḁḉḙḸոḼṉṡṾấắẽỡἪἷὄ὎὚ĀDoḆᴴoôᲉĀcsḎḔute耻é䃩ter;橮ȀaioyḢḧḱḶron;䄛rĀ;cḭḮ扖耻ê䃪lon;払;䑍ot;䄗ĀDrṁṅot;扒;쀀𝔢ƀ;rsṐṑṗ檚ave耻è䃨Ā;dṜṝ檖ot;檘Ȁ;ilsṪṫṲṴ檙nters;揧;愓Ā;dṹṺ檕ot;檗ƀapsẅẉẗcr;䄓tyƀ;svẒẓẕ戅et»ẓpĀ1;ẝẤijạả;怄;怅怃ĀgsẪẬ;䅋p;怂ĀgpẴẸon;䄙f;쀀𝕖ƀalsỄỎỒrĀ;sỊị拕l;槣us;橱iƀ;lvỚớở䎵on»ớ;䏵ȀcsuvỪỳἋἣĀioữḱrc»Ḯɩỹ\0\0ỻíՈantĀglἂἆtr»ṝess»Ṻƀaeiἒ἖Ἒls;䀽st;扟vĀ;DȵἠD;橸parsl;槥ĀDaἯἳot;打rr;楱ƀcdiἾὁỸr;愯oô͒ĀahὉὋ;䎷耻ð䃰Āmrὓὗl耻ë䃫o;悬ƀcipὡὤὧl;䀡sôծĀeoὬὴctatioîՙnentialåչৡᾒ\0ᾞ\0ᾡᾧ\0\0ῆῌ\0ΐ\0ῦῪ \0 ⁚llingdotseñṄy;䑄male;晀ƀilrᾭᾳ῁lig;耀ffiɩᾹ\0\0᾽g;耀ffig;耀ffl;쀀𝔣lig;耀filig;쀀fjƀaltῙ῜ῡt;晭ig;耀flns;斱of;䆒ǰ΅\0ῳf;쀀𝕗ĀakֿῷĀ;vῼ´拔;櫙artint;樍Āao‌⁕Ācs‑⁒ႉ‸⁅⁈\0⁐β•‥‧‪‬\0‮耻½䂽;慓耻¼䂼;慕;慙;慛Ƴ‴\0‶;慔;慖ʴ‾⁁\0\0⁃耻¾䂾;慗;慜5;慘ƶ⁌\0⁎;慚;慝8;慞l;恄wn;挢cr;쀀𝒻ࢀEabcdefgijlnorstv₂₉₟₥₰₴⃰⃵⃺⃿℃ℒℸ̗ℾ⅒↞Ā;lٍ₇;檌ƀcmpₐₕ₝ute;䇵maĀ;dₜ᳚䎳;檆reve;䄟Āiy₪₮rc;䄝;䐳ot;䄡Ȁ;lqsؾق₽⃉ƀ;qsؾٌ⃄lanô٥Ȁ;cdl٥⃒⃥⃕c;檩otĀ;o⃜⃝檀Ā;l⃢⃣檂;檄Ā;e⃪⃭쀀⋛︀s;檔r;쀀𝔤Ā;gٳ؛mel;愷cy;䑓Ȁ;Eajٚℌℎℐ;檒;檥;檤ȀEaesℛℝ℩ℴ;扩pĀ;p℣ℤ檊rox»ℤĀ;q℮ℯ檈Ā;q℮ℛim;拧pf;쀀𝕘Āci⅃ⅆr;愊mƀ;el٫ⅎ⅐;檎;檐茀>;cdlqr׮ⅠⅪⅮⅳⅹĀciⅥⅧ;檧r;橺ot;拗Par;榕uest;橼ʀadelsↄⅪ←ٖ↛ǰ↉\0↎proø₞r;楸qĀlqؿ↖lesó₈ií٫Āen↣↭rtneqq;쀀≩︀Å↪ԀAabcefkosy⇄⇇⇱⇵⇺∘∝∯≨≽ròΠȀilmr⇐⇔⇗⇛rsðᒄf»․ilôکĀdr⇠⇤cy;䑊ƀ;cwࣴ⇫⇯ir;楈;憭ar;意irc;䄥ƀalr∁∎∓rtsĀ;u∉∊晥it»∊lip;怦con;抹r;쀀𝔥sĀew∣∩arow;椥arow;椦ʀamopr∺∾≃≞≣rr;懿tht;戻kĀlr≉≓eftarrow;憩ightarrow;憪f;쀀𝕙bar;怕ƀclt≯≴≸r;쀀𝒽asè⇴rok;䄧Ābp⊂⊇ull;恃hen»ᱛૡ⊣\0⊪\0⊸⋅⋎\0⋕⋳\0\0⋸⌢⍧⍢⍿\0⎆⎪⎴cute耻í䃭ƀ;iyݱ⊰⊵rc耻î䃮;䐸Ācx⊼⊿y;䐵cl耻¡䂡ĀfrΟ⋉;쀀𝔦rave耻ì䃬Ȁ;inoܾ⋝⋩⋮Āin⋢⋦nt;樌t;戭fin;槜ta;愩lig;䄳ƀaop⋾⌚⌝ƀcgt⌅⌈⌗r;䄫ƀelpܟ⌏⌓inåގarôܠh;䄱f;抷ed;䆵ʀ;cfotӴ⌬⌱⌽⍁are;愅inĀ;t⌸⌹戞ie;槝doô⌙ʀ;celpݗ⍌⍐⍛⍡al;抺Āgr⍕⍙eróᕣã⍍arhk;樗rod;樼Ȁcgpt⍯⍲⍶⍻y;䑑on;䄯f;쀀𝕚a;䎹uest耻¿䂿Āci⎊⎏r;쀀𝒾nʀ;EdsvӴ⎛⎝⎡ӳ;拹ot;拵Ā;v⎦⎧拴;拳Ā;iݷ⎮lde;䄩ǫ⎸\0⎼cy;䑖l耻ï䃯̀cfmosu⏌⏗⏜⏡⏧⏵Āiy⏑⏕rc;䄵;䐹r;쀀𝔧ath;䈷pf;쀀𝕛ǣ⏬\0⏱r;쀀𝒿rcy;䑘kcy;䑔Ѐacfghjos␋␖␢␧␭␱␵␻ppaĀ;v␓␔䎺;䏰Āey␛␠dil;䄷;䐺r;쀀𝔨reen;䄸cy;䑅cy;䑜pf;쀀𝕜cr;쀀𝓀஀ABEHabcdefghjlmnoprstuv⑰⒁⒆⒍⒑┎┽╚▀♎♞♥♹♽⚚⚲⛘❝❨➋⟀⠁⠒ƀart⑷⑺⑼rò৆òΕail;椛arr;椎Ā;gঔ⒋;檋ar;楢ॣ⒥\0⒪\0⒱\0\0\0\0\0⒵Ⓔ\0ⓆⓈⓍ\0⓹ute;䄺mptyv;榴raîࡌbda;䎻gƀ;dlࢎⓁⓃ;榑åࢎ;檅uo耻«䂫rЀ;bfhlpst࢙ⓞⓦⓩ⓫⓮⓱⓵Ā;f࢝ⓣs;椟s;椝ë≒p;憫l;椹im;楳l;憢ƀ;ae⓿─┄檫il;椙Ā;s┉┊檭;쀀⪭︀ƀabr┕┙┝rr;椌rk;杲Āak┢┬cĀek┨┪;䁻;䁛Āes┱┳;榋lĀdu┹┻;榏;榍Ȁaeuy╆╋╖╘ron;䄾Ādi═╔il;䄼ìࢰâ┩;䐻Ȁcqrs╣╦╭╽a;椶uoĀ;rนᝆĀdu╲╷har;楧shar;楋h;憲ʀ;fgqs▋▌উ◳◿扤tʀahlrt▘▤▷◂◨rrowĀ;t࢙□aé⓶arpoonĀdu▯▴own»њp»०eftarrows;懇ightƀahs◍◖◞rrowĀ;sࣴࢧarpoonó྘quigarro÷⇰hreetimes;拋ƀ;qs▋ও◺lanôবʀ;cdgsব☊☍☝☨c;檨otĀ;o☔☕橿Ā;r☚☛檁;檃Ā;e☢☥쀀⋚︀s;檓ʀadegs☳☹☽♉♋pproøⓆot;拖qĀgq♃♅ôউgtò⒌ôছiíলƀilr♕࣡♚sht;楼;쀀𝔩Ā;Eজ♣;檑š♩♶rĀdu▲♮Ā;l॥♳;楪lk;斄cy;䑙ʀ;achtੈ⚈⚋⚑⚖rò◁orneòᴈard;楫ri;旺Āio⚟⚤dot;䅀ustĀ;a⚬⚭掰che»⚭ȀEaes⚻⚽⛉⛔;扨pĀ;p⛃⛄檉rox»⛄Ā;q⛎⛏檇Ā;q⛎⚻im;拦Ѐabnoptwz⛩⛴⛷✚✯❁❇❐Ānr⛮⛱g;柬r;懽rëࣁgƀlmr⛿✍✔eftĀar০✇ightá৲apsto;柼ightá৽parrowĀlr✥✩efô⓭ight;憬ƀafl✶✹✽r;榅;쀀𝕝us;樭imes;樴š❋❏st;戗áፎƀ;ef❗❘᠀旊nge»❘arĀ;l❤❥䀨t;榓ʀachmt❳❶❼➅➇ròࢨorneòᶌarĀ;d྘➃;業;怎ri;抿̀achiqt➘➝ੀ➢➮➻quo;怹r;쀀𝓁mƀ;egল➪➬;檍;檏Ābu┪➳oĀ;rฟ➹;怚rok;䅂萀<;cdhilqrࠫ⟒☹⟜⟠⟥⟪⟰Āci⟗⟙;檦r;橹reå◲mes;拉arr;楶uest;橻ĀPi⟵⟹ar;榖ƀ;ef⠀भ᠛旃rĀdu⠇⠍shar;楊har;楦Āen⠗⠡rtneqq;쀀≨︀Å⠞܀Dacdefhilnopsu⡀⡅⢂⢎⢓⢠⢥⢨⣚⣢⣤ઃ⣳⤂Dot;戺Ȁclpr⡎⡒⡣⡽r耻¯䂯Āet⡗⡙;時Ā;e⡞⡟朠se»⡟Ā;sျ⡨toȀ;dluျ⡳⡷⡻owîҌefôएðᏑker;斮Āoy⢇⢌mma;権;䐼ash;怔asuredangle»ᘦr;쀀𝔪o;愧ƀcdn⢯⢴⣉ro耻µ䂵Ȁ;acdᑤ⢽⣀⣄sôᚧir;櫰ot肻·Ƶusƀ;bd⣒ᤃ⣓戒Ā;uᴼ⣘;横ţ⣞⣡p;櫛ò−ðઁĀdp⣩⣮els;抧f;쀀𝕞Āct⣸⣽r;쀀𝓂pos»ᖝƀ;lm⤉⤊⤍䎼timap;抸ఀGLRVabcdefghijlmoprstuvw⥂⥓⥾⦉⦘⧚⧩⨕⨚⩘⩝⪃⪕⪤⪨⬄⬇⭄⭿⮮ⰴⱧⱼ⳩Āgt⥇⥋;쀀⋙̸Ā;v⥐௏쀀≫⃒ƀelt⥚⥲⥶ftĀar⥡⥧rrow;懍ightarrow;懎;쀀⋘̸Ā;v⥻ే쀀≪⃒ightarrow;懏ĀDd⦎⦓ash;抯ash;抮ʀbcnpt⦣⦧⦬⦱⧌la»˞ute;䅄g;쀀∠⃒ʀ;Eiop඄⦼⧀⧅⧈;쀀⩰̸d;쀀≋̸s;䅉roø඄urĀ;a⧓⧔普lĀ;s⧓ସdz⧟\0⧣p肻 ଷmpĀ;e௹ఀʀaeouy⧴⧾⨃⨐⨓ǰ⧹\0⧻;橃on;䅈dil;䅆ngĀ;dൾ⨊ot;쀀⩭̸p;橂;䐽ash;怓΀;Aadqsxஒ⨩⨭⨻⩁⩅⩐rr;懗rĀhr⨳⨶k;椤Ā;oᏲᏰot;쀀≐̸uiöୣĀei⩊⩎ar;椨í஘istĀ;s஠டr;쀀𝔫ȀEest௅⩦⩹⩼ƀ;qs஼⩭௡ƀ;qs஼௅⩴lanô௢ií௪Ā;rஶ⪁»ஷƀAap⪊⪍⪑rò⥱rr;憮ar;櫲ƀ;svྍ⪜ྌĀ;d⪡⪢拼;拺cy;䑚΀AEadest⪷⪺⪾⫂⫅⫶⫹rò⥦;쀀≦̸rr;憚r;急Ȁ;fqs఻⫎⫣⫯tĀar⫔⫙rro÷⫁ightarro÷⪐ƀ;qs఻⪺⫪lanôౕĀ;sౕ⫴»శiíౝĀ;rవ⫾iĀ;eచథiäඐĀpt⬌⬑f;쀀𝕟膀¬;in⬙⬚⬶䂬nȀ;Edvஉ⬤⬨⬮;쀀⋹̸ot;쀀⋵̸ǡஉ⬳⬵;拷;拶iĀ;vಸ⬼ǡಸ⭁⭃;拾;拽ƀaor⭋⭣⭩rȀ;ast୻⭕⭚⭟lleì୻l;쀀⫽⃥;쀀∂̸lint;樔ƀ;ceಒ⭰⭳uåಥĀ;cಘ⭸Ā;eಒ⭽ñಘȀAait⮈⮋⮝⮧rò⦈rrƀ;cw⮔⮕⮙憛;쀀⤳̸;쀀↝̸ghtarrow»⮕riĀ;eೋೖ΀chimpqu⮽⯍⯙⬄୸⯤⯯Ȁ;cerല⯆ഷ⯉uå൅;쀀𝓃ortɭ⬅\0\0⯖ará⭖mĀ;e൮⯟Ā;q൴൳suĀbp⯫⯭å೸åഋƀbcp⯶ⰑⰙȀ;Ees⯿ⰀഢⰄ抄;쀀⫅̸etĀ;eഛⰋqĀ;qണⰀcĀ;eലⰗñസȀ;EesⰢⰣൟⰧ抅;쀀⫆̸etĀ;e൘ⰮqĀ;qൠⰣȀgilrⰽⰿⱅⱇìௗlde耻ñ䃱çృiangleĀlrⱒⱜeftĀ;eచⱚñదightĀ;eೋⱥñ೗Ā;mⱬⱭ䎽ƀ;esⱴⱵⱹ䀣ro;愖p;怇ҀDHadgilrsⲏⲔⲙⲞⲣⲰⲶⳓⳣash;抭arr;椄p;쀀≍⃒ash;抬ĀetⲨⲬ;쀀≥⃒;쀀>⃒nfin;槞ƀAetⲽⳁⳅrr;椂;쀀≤⃒Ā;rⳊⳍ쀀<⃒ie;쀀⊴⃒ĀAtⳘⳜrr;椃rie;쀀⊵⃒im;쀀∼⃒ƀAan⳰⳴ⴂrr;懖rĀhr⳺⳽k;椣Ā;oᏧᏥear;椧ቓ᪕\0\0\0\0\0\0\0\0\0\0\0\0\0ⴭ\0ⴸⵈⵠⵥ⵲ⶄᬇ\0\0ⶍⶫ\0ⷈⷎ\0ⷜ⸙⸫⸾⹃Ācsⴱ᪗ute耻ó䃳ĀiyⴼⵅrĀ;c᪞ⵂ耻ô䃴;䐾ʀabios᪠ⵒⵗLjⵚlac;䅑v;樸old;榼lig;䅓Ācr⵩⵭ir;榿;쀀𝔬ͯ⵹\0\0⵼\0ⶂn;䋛ave耻ò䃲;槁Ābmⶈ෴ar;榵Ȁacitⶕ⶘ⶥⶨrò᪀Āir⶝ⶠr;榾oss;榻nå๒;槀ƀaeiⶱⶵⶹcr;䅍ga;䏉ƀcdnⷀⷅǍron;䎿;榶pf;쀀𝕠ƀaelⷔ⷗ǒr;榷rp;榹΀;adiosvⷪⷫⷮ⸈⸍⸐⸖戨rò᪆Ȁ;efmⷷⷸ⸂⸅橝rĀ;oⷾⷿ愴f»ⷿ耻ª䂪耻º䂺gof;抶r;橖lope;橗;橛ƀclo⸟⸡⸧ò⸁ash耻ø䃸l;折iŬⸯ⸴de耻õ䃵esĀ;aǛ⸺s;樶ml耻ö䃶bar;挽ૡ⹞\0⹽\0⺀⺝\0⺢⺹\0\0⻋ຜ\0⼓\0\0⼫⾼\0⿈rȀ;astЃ⹧⹲຅脀¶;l⹭⹮䂶leìЃɩ⹸\0\0⹻m;櫳;櫽y;䐿rʀcimpt⺋⺏⺓ᡥ⺗nt;䀥od;䀮il;怰enk;怱r;쀀𝔭ƀimo⺨⺰⺴Ā;v⺭⺮䏆;䏕maô੶ne;明ƀ;tv⺿⻀⻈䏀chfork»´;䏖Āau⻏⻟nĀck⻕⻝kĀ;h⇴⻛;愎ö⇴sҀ;abcdemst⻳⻴ᤈ⻹⻽⼄⼆⼊⼎䀫cir;樣ir;樢Āouᵀ⼂;樥;橲n肻±ຝim;樦wo;樧ƀipu⼙⼠⼥ntint;樕f;쀀𝕡nd耻£䂣Ԁ;Eaceinosu່⼿⽁⽄⽇⾁⾉⾒⽾⾶;檳p;檷uå໙Ā;c໎⽌̀;acens່⽙⽟⽦⽨⽾pproø⽃urlyeñ໙ñ໎ƀaes⽯⽶⽺pprox;檹qq;檵im;拨iíໟmeĀ;s⾈ຮ怲ƀEas⽸⾐⽺ð⽵ƀdfp໬⾙⾯ƀals⾠⾥⾪lar;挮ine;挒urf;挓Ā;t໻⾴ï໻rel;抰Āci⿀⿅r;쀀𝓅;䏈ncsp;怈̀fiopsu⿚⋢⿟⿥⿫⿱r;쀀𝔮pf;쀀𝕢rime;恗cr;쀀𝓆ƀaeo⿸〉〓tĀei⿾々rnionóڰnt;樖stĀ;e【】䀿ñἙô༔઀ABHabcdefhilmnoprstux぀けさすムㄎㄫㅇㅢㅲㆎ㈆㈕㈤㈩㉘㉮㉲㊐㊰㊷ƀartぇおがròႳòϝail;検aròᱥar;楤΀cdenqrtとふへみわゔヌĀeuねぱ;쀀∽̱te;䅕iãᅮmptyv;榳gȀ;del࿑らるろ;榒;榥å࿑uo耻»䂻rր;abcfhlpstw࿜ガクシスゼゾダッデナp;極Ā;f࿠ゴs;椠;椳s;椞ë≝ð✮l;楅im;楴l;憣;憝Āaiパフil;椚oĀ;nホボ戶aló༞ƀabrョリヮrò៥rk;杳ĀakンヽcĀekヹ・;䁽;䁝Āes㄂㄄;榌lĀduㄊㄌ;榎;榐Ȁaeuyㄗㄜㄧㄩron;䅙Ādiㄡㄥil;䅗ì࿲âヺ;䑀Ȁclqsㄴㄷㄽㅄa;椷dhar;楩uoĀ;rȎȍh;憳ƀacgㅎㅟངlȀ;ipsླྀㅘㅛႜnåႻarôྩt;断ƀilrㅩဣㅮsht;楽;쀀𝔯ĀaoㅷㆆrĀduㅽㅿ»ѻĀ;l႑ㆄ;楬Ā;vㆋㆌ䏁;䏱ƀgns㆕ㇹㇼht̀ahlrstㆤㆰ㇂㇘㇤㇮rrowĀ;t࿜ㆭaéトarpoonĀduㆻㆿowîㅾp»႒eftĀah㇊㇐rrowó࿪arpoonóՑightarrows;應quigarro÷ニhreetimes;拌g;䋚ingdotseñἲƀahm㈍㈐㈓rò࿪aòՑ;怏oustĀ;a㈞㈟掱che»㈟mid;櫮Ȁabpt㈲㈽㉀㉒Ānr㈷㈺g;柭r;懾rëဃƀafl㉇㉊㉎r;榆;쀀𝕣us;樮imes;樵Āap㉝㉧rĀ;g㉣㉤䀩t;榔olint;樒arò㇣Ȁachq㉻㊀Ⴜ㊅quo;怺r;쀀𝓇Ābu・㊊oĀ;rȔȓƀhir㊗㊛㊠reåㇸmes;拊iȀ;efl㊪ၙᠡ㊫方tri;槎luhar;楨;愞ൡ㋕㋛㋟㌬㌸㍱\0㍺㎤\0\0㏬㏰\0㐨㑈㑚㒭㒱㓊㓱\0㘖\0\0㘳cute;䅛quï➺Ԁ;Eaceinpsyᇭ㋳㋵㋿㌂㌋㌏㌟㌦㌩;檴ǰ㋺\0㋼;檸on;䅡uåᇾĀ;dᇳ㌇il;䅟rc;䅝ƀEas㌖㌘㌛;檶p;檺im;择olint;樓iíሄ;䑁otƀ;be㌴ᵇ㌵担;橦΀Aacmstx㍆㍊㍗㍛㍞㍣㍭rr;懘rĀhr㍐㍒ë∨Ā;oਸ਼਴t耻§䂧i;䀻war;椩mĀin㍩ðnuóñt;朶rĀ;o㍶⁕쀀𝔰Ȁacoy㎂㎆㎑㎠rp;景Āhy㎋㎏cy;䑉;䑈rtɭ㎙\0\0㎜iäᑤaraì⹯耻­䂭Āgm㎨㎴maƀ;fv㎱㎲㎲䏃;䏂Ѐ;deglnprካ㏅㏉㏎㏖㏞㏡㏦ot;橪Ā;q኱ኰĀ;E㏓㏔檞;檠Ā;E㏛㏜檝;檟e;扆lus;樤arr;楲aròᄽȀaeit㏸㐈㐏㐗Āls㏽㐄lsetmé㍪hp;樳parsl;槤Ādlᑣ㐔e;挣Ā;e㐜㐝檪Ā;s㐢㐣檬;쀀⪬︀ƀflp㐮㐳㑂tcy;䑌Ā;b㐸㐹䀯Ā;a㐾㐿槄r;挿f;쀀𝕤aĀdr㑍ЂesĀ;u㑔㑕晠it»㑕ƀcsu㑠㑹㒟Āau㑥㑯pĀ;sᆈ㑫;쀀⊓︀pĀ;sᆴ㑵;쀀⊔︀uĀbp㑿㒏ƀ;esᆗᆜ㒆etĀ;eᆗ㒍ñᆝƀ;esᆨᆭ㒖etĀ;eᆨ㒝ñᆮƀ;afᅻ㒦ְrť㒫ֱ»ᅼaròᅈȀcemt㒹㒾㓂㓅r;쀀𝓈tmîñiì㐕aræᆾĀar㓎㓕rĀ;f㓔ឿ昆Āan㓚㓭ightĀep㓣㓪psiloîỠhé⺯s»⡒ʀbcmnp㓻㕞ሉ㖋㖎Ҁ;Edemnprs㔎㔏㔑㔕㔞㔣㔬㔱㔶抂;櫅ot;檽Ā;dᇚ㔚ot;櫃ult;櫁ĀEe㔨㔪;櫋;把lus;檿arr;楹ƀeiu㔽㕒㕕tƀ;en㔎㕅㕋qĀ;qᇚ㔏eqĀ;q㔫㔨m;櫇Ābp㕚㕜;櫕;櫓c̀;acensᇭ㕬㕲㕹㕻㌦pproø㋺urlyeñᇾñᇳƀaes㖂㖈㌛pproø㌚qñ㌗g;晪ڀ123;Edehlmnps㖩㖬㖯ሜ㖲㖴㗀㗉㗕㗚㗟㗨㗭耻¹䂹耻²䂲耻³䂳;櫆Āos㖹㖼t;檾ub;櫘Ā;dሢ㗅ot;櫄sĀou㗏㗒l;柉b;櫗arr;楻ult;櫂ĀEe㗤㗦;櫌;抋lus;櫀ƀeiu㗴㘉㘌tƀ;enሜ㗼㘂qĀ;qሢ㖲eqĀ;q㗧㗤m;櫈Ābp㘑㘓;櫔;櫖ƀAan㘜㘠㘭rr;懙rĀhr㘦㘨ë∮Ā;oਫ਩war;椪lig耻ß䃟௡㙑㙝㙠ዎ㙳㙹\0㙾㛂\0\0\0\0\0㛛㜃\0㜉㝬\0\0\0㞇ɲ㙖\0\0㙛get;挖;䏄rë๟ƀaey㙦㙫㙰ron;䅥dil;䅣;䑂lrec;挕r;쀀𝔱Ȁeiko㚆㚝㚵㚼Dz㚋\0㚑eĀ4fኄኁaƀ;sv㚘㚙㚛䎸ym;䏑Ācn㚢㚲kĀas㚨㚮pproø዁im»ኬsðኞĀas㚺㚮ð዁rn耻þ䃾Ǭ̟㛆⋧es膀×;bd㛏㛐㛘䃗Ā;aᤏ㛕r;樱;樰ƀeps㛡㛣㜀á⩍Ȁ;bcf҆㛬㛰㛴ot;挶ir;櫱Ā;o㛹㛼쀀𝕥rk;櫚á㍢rime;怴ƀaip㜏㜒㝤dåቈ΀adempst㜡㝍㝀㝑㝗㝜㝟ngleʀ;dlqr㜰㜱㜶㝀㝂斵own»ᶻeftĀ;e⠀㜾ñम;扜ightĀ;e㊪㝋ñၚot;旬inus;樺lus;樹b;槍ime;樻ezium;揢ƀcht㝲㝽㞁Āry㝷㝻;쀀𝓉;䑆cy;䑛rok;䅧Āio㞋㞎xô᝷headĀlr㞗㞠eftarro÷ࡏightarrow»ཝऀAHabcdfghlmoprstuw㟐㟓㟗㟤㟰㟼㠎㠜㠣㠴㡑㡝㡫㢩㣌㣒㣪㣶ròϭar;楣Ācr㟜㟢ute耻ú䃺òᅐrǣ㟪\0㟭y;䑞ve;䅭Āiy㟵㟺rc耻û䃻;䑃ƀabh㠃㠆㠋ròᎭlac;䅱aòᏃĀir㠓㠘sht;楾;쀀𝔲rave耻ù䃹š㠧㠱rĀlr㠬㠮»ॗ»ႃlk;斀Āct㠹㡍ɯ㠿\0\0㡊rnĀ;e㡅㡆挜r»㡆op;挏ri;旸Āal㡖㡚cr;䅫肻¨͉Āgp㡢㡦on;䅳f;쀀𝕦̀adhlsuᅋ㡸㡽፲㢑㢠ownáᎳarpoonĀlr㢈㢌efô㠭ighô㠯iƀ;hl㢙㢚㢜䏅»ᏺon»㢚parrows;懈ƀcit㢰㣄㣈ɯ㢶\0\0㣁rnĀ;e㢼㢽挝r»㢽op;挎ng;䅯ri;旹cr;쀀𝓊ƀdir㣙㣝㣢ot;拰lde;䅩iĀ;f㜰㣨»᠓Āam㣯㣲rò㢨l耻ü䃼angle;榧ހABDacdeflnoprsz㤜㤟㤩㤭㦵㦸㦽㧟㧤㧨㧳㧹㧽㨁㨠ròϷarĀ;v㤦㤧櫨;櫩asèϡĀnr㤲㤷grt;榜΀eknprst㓣㥆㥋㥒㥝㥤㦖appá␕othinçẖƀhir㓫⻈㥙opô⾵Ā;hᎷ㥢ïㆍĀiu㥩㥭gmá㎳Ābp㥲㦄setneqĀ;q㥽㦀쀀⊊︀;쀀⫋︀setneqĀ;q㦏㦒쀀⊋︀;쀀⫌︀Āhr㦛㦟etá㚜iangleĀlr㦪㦯eft»थight»ၑy;䐲ash»ံƀelr㧄㧒㧗ƀ;beⷪ㧋㧏ar;抻q;扚lip;拮Ābt㧜ᑨaòᑩr;쀀𝔳tré㦮suĀbp㧯㧱»ജ»൙pf;쀀𝕧roð໻tré㦴Ācu㨆㨋r;쀀𝓋Ābp㨐㨘nĀEe㦀㨖»㥾nĀEe㦒㨞»㦐igzag;榚΀cefoprs㨶㨻㩖㩛㩔㩡㩪irc;䅵Ādi㩀㩑Ābg㩅㩉ar;機eĀ;qᗺ㩏;扙erp;愘r;쀀𝔴pf;쀀𝕨Ā;eᑹ㩦atèᑹcr;쀀𝓌ૣណ㪇\0㪋\0㪐㪛\0\0㪝㪨㪫㪯\0\0㫃㫎\0㫘ៜ៟tré៑r;쀀𝔵ĀAa㪔㪗ròσrò৶;䎾ĀAa㪡㪤ròθrò৫að✓is;拻ƀdptឤ㪵㪾Āfl㪺ឩ;쀀𝕩imåឲĀAa㫇㫊ròώròਁĀcq㫒ីr;쀀𝓍Āpt៖㫜ré។Ѐacefiosu㫰㫽㬈㬌㬑㬕㬛㬡cĀuy㫶㫻te耻ý䃽;䑏Āiy㬂㬆rc;䅷;䑋n耻¥䂥r;쀀𝔶cy;䑗pf;쀀𝕪cr;쀀𝓎Ācm㬦㬩y;䑎l耻ÿ䃿Ԁacdefhiosw㭂㭈㭔㭘㭤㭩㭭㭴㭺㮀cute;䅺Āay㭍㭒ron;䅾;䐷ot;䅼Āet㭝㭡træᕟa;䎶r;쀀𝔷cy;䐶grarr;懝pf;쀀𝕫cr;쀀𝓏Ājn㮅㮇;怍j;怌'.split("").map(function(e){return e.charCodeAt(0)}))}),Nr=(yr(Cr),Sr(function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.default=new Uint16Array("Ȁaglq\tɭ\0\0p;䀦os;䀧t;䀾t;䀼uot;䀢".split("").map(function(e){return e.charCodeAt(0)}))})),br=(yr(Nr),Sr(function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.replaceCodePoint=t.fromCodePoint=void 0;var n,r=new Map([[0,65533],[128,8364],[130,8218],[131,402],[132,8222],[133,8230],[134,8224],[135,8225],[136,710],[137,8240],[138,352],[139,8249],[140,338],[142,381],[145,8216],[146,8217],[147,8220],[148,8221],[149,8226],[150,8211],[151,8212],[152,732],[153,8482],[154,353],[155,8250],[156,339],[158,382],[159,376]]);function i(e){var t;return 55296<=e&&e<=57343||1114111>>10&1023|55296),e=56320|1023&e),t+String.fromCharCode(e)},t.replaceCodePoint=i,t.default=function(e){return(0,t.fromCodePoint)(i(e))}})),Ir=(yr(br),br.replaceCodePoint,br.fromCodePoint,Cr),Or=Nr,kr=br,Lr=Sr(function(e,t){var n=gr&&gr.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n);var i=Object.getOwnPropertyDescriptor(t,n);i&&!("get"in i?!t.__esModule:i.writable||i.configurable)||(i={enumerable:!0,get:function(){return t[n]}}),Object.defineProperty(e,r,i)}:function(e,t,n,r){e[r=void 0===r?n:r]=t[n]}),r=gr&&gr.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),i=gr&&gr.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i in e)"default"!==i&&Object.prototype.hasOwnProperty.call(e,i)&&n(t,e,i);return r(t,e),t},s=gr&&gr.__importDefault||function(e){return e&&e.__esModule?e:{default:e}},a=(Object.defineProperty(t,"__esModule",{value:!0}),t.decodeXML=t.decodeHTMLStrict=t.decodeHTMLAttribute=t.decodeHTML=t.determineBranch=t.EntityDecoder=t.DecodingMode=t.BinTrieFlags=t.fromCodePoint=t.replaceCodePoint=t.decodeCodePoint=t.xmlDecodeTree=t.htmlDecodeTree=void 0,s(Ir)),s=(t.htmlDecodeTree=a.default,s(Or)),c=(t.xmlDecodeTree=s.default,i(kr));t.decodeCodePoint=c.default;var u,h,f,p,l=kr;function d(e){return e>=u.ZERO&&e<=u.NINE}Object.defineProperty(t,"replaceCodePoint",{enumerable:!0,get:function(){return l.replaceCodePoint}}),Object.defineProperty(t,"fromCodePoint",{enumerable:!0,get:function(){return l.fromCodePoint}}),function(e){e[e.NUM=35]="NUM",e[e.SEMI=59]="SEMI",e[e.EQUALS=61]="EQUALS",e[e.ZERO=48]="ZERO",e[e.NINE=57]="NINE",e[e.LOWER_A=97]="LOWER_A",e[e.LOWER_F=102]="LOWER_F",e[e.LOWER_X=120]="LOWER_X",e[e.LOWER_Z=122]="LOWER_Z",e[e.UPPER_A=65]="UPPER_A",e[e.UPPER_F=70]="UPPER_F",e[e.UPPER_Z=90]="UPPER_Z"}(u={}),function(e){e[e.VALUE_LENGTH=49152]="VALUE_LENGTH",e[e.BRANCH_LENGTH=16256]="BRANCH_LENGTH",e[e.JUMP_TABLE=127]="JUMP_TABLE"}(h=t.BinTrieFlags||(t.BinTrieFlags={})),function(e){e[e.EntityStart=0]="EntityStart",e[e.NumericStart=1]="NumericStart",e[e.NumericDecimal=2]="NumericDecimal",e[e.NumericHex=3]="NumericHex",e[e.NamedEntity=4]="NamedEntity"}(f={}),function(e){e[e.Legacy=0]="Legacy",e[e.Strict=1]="Strict",e[e.Attribute=2]="Attribute"}(p=t.DecodingMode||(t.DecodingMode={}));var _=function(){function e(e,t,n){this.decodeTree=e,this.emitCodePoint=t,this.errors=n,this.state=f.EntityStart,this.consumed=1,this.result=0,this.treeIndex=0,this.excess=1,this.decodeMode=p.Strict}return e.prototype.startEntity=function(e){this.decodeMode=e,this.state=f.EntityStart,this.result=0,this.treeIndex=0,this.excess=1,this.consumed=1},e.prototype.write=function(e,t){switch(this.state){case f.EntityStart:return e.charCodeAt(t)===u.NUM?(this.state=f.NumericStart,this.consumed+=1,this.stateNumericStart(e,t+1)):(this.state=f.NamedEntity,this.stateNamedEntity(e,t));case f.NumericStart:return this.stateNumericStart(e,t);case f.NumericDecimal:return this.stateNumericDecimal(e,t);case f.NumericHex:return this.stateNumericHex(e,t);case f.NamedEntity:return this.stateNamedEntity(e,t)}},e.prototype.stateNumericStart=function(e,t){return t>=e.length?-1:(32|e.charCodeAt(t))===u.LOWER_X?(this.state=f.NumericHex,this.consumed+=1,this.stateNumericHex(e,t+1)):(this.state=f.NumericDecimal,this.stateNumericDecimal(e,t))},e.prototype.addToNumericResult=function(e,t,n,r){t!==n&&(n-=t,this.result=this.result*Math.pow(r,n)+parseInt(e.substr(t,n),r),this.consumed+=n)},e.prototype.stateNumericHex=function(e,t){for(var r=t;t=u.UPPER_A&&i<=u.UPPER_F||u.LOWER_A<=i&&i<=u.LOWER_F))return this.addToNumericResult(e,r,t,16),this.emitNumericEntity(i,3);t+=1}return this.addToNumericResult(e,r,t,16),-1},e.prototype.stateNumericDecimal=function(e,t){for(var n=t;t>14;t>14)){if(s===u.SEMI)return this.emitNamedEntityData(this.treeIndex,i,this.consumed+this.excess);this.decodeMode!==p.Strict&&(this.result=this.treeIndex,this.consumed+=this.excess,this.excess=0)}}return-1},e.prototype.emitNotTerminatedNamedEntity=function(){var t=this.result,n=(this.decodeTree[t]&h.VALUE_LENGTH)>>14;return this.emitNamedEntityData(t,n,this.consumed),null!=(t=this.errors)&&t.missingSemicolonAfterCharacterReference(),this.consumed},e.prototype.emitNamedEntityData=function(e,t,n){var r=this.decodeTree;return this.emitCodePoint(1===t?r[e]&~h.VALUE_LENGTH:r[e+1],n),3===t&&this.emitCodePoint(r[e+2],n),n},e.prototype.end=function(){var e;switch(this.state){case f.NamedEntity:return 0===this.result||this.decodeMode===p.Attribute&&this.result!==this.treeIndex?0:this.emitNotTerminatedNamedEntity();case f.NumericDecimal:return this.emitNumericEntity(0,2);case f.NumericHex:return this.emitNumericEntity(0,3);case f.NumericStart:return null!=(e=this.errors)&&e.absenceOfDigitsInNumericCharacterReference(this.consumed),0;case f.EntityStart:return 0}},e}();function E(e){var t="",n=new _(e,function(e){return t+=(0,c.fromCodePoint)(e)});return function(e,r){for(var i=0,s=0;0<=(s=e.indexOf("&",s));){t+=e.slice(i,s),n.startEntity(r);var a=n.write(e,s+1);if(a<0){i=s+n.end();break}i=s+a,s=0===a?i+1:i}var o=t+e.slice(i);return t="",o}}function T(e,t,n,r){var i=(t&h.BRANCH_LENGTH)>>7,t=t&h.JUMP_TABLE;if(0==i)return 0!=t&&r===t?n:-1;if(t)return(t=r-t)<0||i<=t?-1:e[n+t]-1;for(var o=n,c=o+i-1;o<=c;){var u=o+c>>>1,l=e[u];if(l=ar.DIGIT_0&&e<=ar.DIGIT_9}function Zr(e){return e>=ar.LATIN_CAPITAL_A&&e<=ar.LATIN_CAPITAL_Z}function Jr(e){return function(e){return e>=ar.LATIN_SMALL_A&&e<=ar.LATIN_SMALL_Z}(e)||Zr(e)}function ei(e){return Jr(e)||Xr(e)}function ti(e){return e>=ar.LATIN_CAPITAL_A&&e<=ar.LATIN_CAPITAL_F}function ni(e){return e>=ar.LATIN_SMALL_A&&e<=ar.LATIN_SMALL_F}function ri(e){return e+32}function ii(e){return e===ar.SPACE||e===ar.LINE_FEED||e===ar.TABULATION||e===ar.FORM_FEED}function si(e){return ii(e)||e===ar.SOLIDUS||e===ar.GREATER_THAN_SIGN}class ai{constructor(e,t){this.options=e,this.handler=t,this.paused=!1,this.inLoop=!1,this.inForeignNode=!1,this.lastStartTagName="",this.active=!1,this.state=Qr.DATA,this.returnState=Qr.DATA,this.charRefCode=-1,this.consumedAfterSnapshot=-1,this.currentCharacterToken=null,this.currentToken=null,this.currentAttr={name:"",value:""},this.preprocessor=new Er(t),this.currentLocation=this.getCurrentLocation(-1)}_err(e){var t,n;null!=(n=(t=this.handler).onParseError)&&n.call(t,this.preprocessor.getError(e))}getCurrentLocation(e){return this.options.sourceCodeLocationInfo?{startLine:this.preprocessor.line,startCol:this.preprocessor.col-e,startOffset:this.preprocessor.offset-e,endLine:-1,endCol:-1,endOffset:-1}:null}_runParsingLoop(){if(!this.inLoop){for(this.inLoop=!0;this.active&&!this.paused;){this.consumedAfterSnapshot=0;var e=this._consume();this._ensureHibernation()||this._callState(e)}this.inLoop=!1}}pause(){this.paused=!0}resume(e){if(!this.paused)throw new Error("Parser was already resumed");this.paused=!1,this.inLoop||(this._runParsingLoop(),this.paused)||null==e||e()}write(e,t,n){this.active=!0,this.preprocessor.write(e,t),this._runParsingLoop(),this.paused||null==n||n()}insertHtmlAtCurrentPos(e){this.active=!0,this.preprocessor.insertHtmlAtCurrentPos(e),this._runParsingLoop()}_ensureHibernation(){return!(!this.preprocessor.endOfChunkHit||(this._unconsume(this.consumedAfterSnapshot),this.active=!1))}_consume(){return this.consumedAfterSnapshot++,this.preprocessor.advance()}_unconsume(e){this.consumedAfterSnapshot-=e,this.preprocessor.retreat(e)}_reconsumeInState(e,t){this.state=e,this._callState(t)}_advanceBy(e){this.consumedAfterSnapshot+=e;for(let t=0;t>14)-1;if(e!==ar.SEMICOLON&&this._isCharacterReferenceInAttribute()&&((o=this.preprocessor.peek(1))===ar.EQUALS_SIGN||ei(o))?(t=[ar.AMPERSAND],s+=a):(t=0==a?[Gr[s]&~Rr.VALUE_LENGTH]:1==a?[Gr[++s]]:[Gr[++s],Gr[++s]],n=0,r=e!==ar.SEMICOLON),0==a){this._consume();break}}}return this._unconsume(n),r&&!this.preprocessor.endOfChunkHit&&this._err(_r.missingSemicolonAfterCharacterReference),this._unconsume(1),t}_isCharacterReferenceInAttribute(){return this.returnState===Qr.ATTRIBUTE_VALUE_DOUBLE_QUOTED||this.returnState===Qr.ATTRIBUTE_VALUE_SINGLE_QUOTED||this.returnState===Qr.ATTRIBUTE_VALUE_UNQUOTED}_flushCodePointConsumedAsCharacterReference(e){this._isCharacterReferenceInAttribute()?this.currentAttr.value+=String.fromCodePoint(e):this._emitCodePoint(e)}_callState(e){switch(this.state){case Qr.DATA:this._stateData(e);break;case Qr.RCDATA:this._stateRcdata(e);break;case Qr.RAWTEXT:this._stateRawtext(e);break;case Qr.SCRIPT_DATA:this._stateScriptData(e);break;case Qr.PLAINTEXT:this._statePlaintext(e);break;case Qr.TAG_OPEN:this._stateTagOpen(e);break;case Qr.END_TAG_OPEN:this._stateEndTagOpen(e);break;case Qr.TAG_NAME:this._stateTagName(e);break;case Qr.RCDATA_LESS_THAN_SIGN:this._stateRcdataLessThanSign(e);break;case Qr.RCDATA_END_TAG_OPEN:this._stateRcdataEndTagOpen(e);break;case Qr.RCDATA_END_TAG_NAME:this._stateRcdataEndTagName(e);break;case Qr.RAWTEXT_LESS_THAN_SIGN:this._stateRawtextLessThanSign(e);break;case Qr.RAWTEXT_END_TAG_OPEN:this._stateRawtextEndTagOpen(e);break;case Qr.RAWTEXT_END_TAG_NAME:this._stateRawtextEndTagName(e);break;case Qr.SCRIPT_DATA_LESS_THAN_SIGN:this._stateScriptDataLessThanSign(e);break;case Qr.SCRIPT_DATA_END_TAG_OPEN:this._stateScriptDataEndTagOpen(e);break;case Qr.SCRIPT_DATA_END_TAG_NAME:this._stateScriptDataEndTagName(e);break;case Qr.SCRIPT_DATA_ESCAPE_START:this._stateScriptDataEscapeStart(e);break;case Qr.SCRIPT_DATA_ESCAPE_START_DASH:this._stateScriptDataEscapeStartDash(e);break;case Qr.SCRIPT_DATA_ESCAPED:this._stateScriptDataEscaped(e);break;case Qr.SCRIPT_DATA_ESCAPED_DASH:this._stateScriptDataEscapedDash(e);break;case Qr.SCRIPT_DATA_ESCAPED_DASH_DASH:this._stateScriptDataEscapedDashDash(e);break;case Qr.SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN:this._stateScriptDataEscapedLessThanSign(e);break;case Qr.SCRIPT_DATA_ESCAPED_END_TAG_OPEN:this._stateScriptDataEscapedEndTagOpen(e);break;case Qr.SCRIPT_DATA_ESCAPED_END_TAG_NAME:this._stateScriptDataEscapedEndTagName(e);break;case Qr.SCRIPT_DATA_DOUBLE_ESCAPE_START:this._stateScriptDataDoubleEscapeStart(e);break;case Qr.SCRIPT_DATA_DOUBLE_ESCAPED:this._stateScriptDataDoubleEscaped(e);break;case Qr.SCRIPT_DATA_DOUBLE_ESCAPED_DASH:this._stateScriptDataDoubleEscapedDash(e);break;case Qr.SCRIPT_DATA_DOUBLE_ESCAPED_DASH_DASH:this._stateScriptDataDoubleEscapedDashDash(e);break;case Qr.SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN:this._stateScriptDataDoubleEscapedLessThanSign(e);break;case Qr.SCRIPT_DATA_DOUBLE_ESCAPE_END:this._stateScriptDataDoubleEscapeEnd(e);break;case Qr.BEFORE_ATTRIBUTE_NAME:this._stateBeforeAttributeName(e);break;case Qr.ATTRIBUTE_NAME:this._stateAttributeName(e);break;case Qr.AFTER_ATTRIBUTE_NAME:this._stateAfterAttributeName(e);break;case Qr.BEFORE_ATTRIBUTE_VALUE:this._stateBeforeAttributeValue(e);break;case Qr.ATTRIBUTE_VALUE_DOUBLE_QUOTED:this._stateAttributeValueDoubleQuoted(e);break;case Qr.ATTRIBUTE_VALUE_SINGLE_QUOTED:this._stateAttributeValueSingleQuoted(e);break;case Qr.ATTRIBUTE_VALUE_UNQUOTED:this._stateAttributeValueUnquoted(e);break;case Qr.AFTER_ATTRIBUTE_VALUE_QUOTED:this._stateAfterAttributeValueQuoted(e);break;case Qr.SELF_CLOSING_START_TAG:this._stateSelfClosingStartTag(e);break;case Qr.BOGUS_COMMENT:this._stateBogusComment(e);break;case Qr.MARKUP_DECLARATION_OPEN:this._stateMarkupDeclarationOpen(e);break;case Qr.COMMENT_START:this._stateCommentStart(e);break;case Qr.COMMENT_START_DASH:this._stateCommentStartDash(e);break;case Qr.COMMENT:this._stateComment(e);break;case Qr.COMMENT_LESS_THAN_SIGN:this._stateCommentLessThanSign(e);break;case Qr.COMMENT_LESS_THAN_SIGN_BANG:this._stateCommentLessThanSignBang(e);break;case Qr.COMMENT_LESS_THAN_SIGN_BANG_DASH:this._stateCommentLessThanSignBangDash(e);break;case Qr.COMMENT_LESS_THAN_SIGN_BANG_DASH_DASH:this._stateCommentLessThanSignBangDashDash(e);break;case Qr.COMMENT_END_DASH:this._stateCommentEndDash(e);break;case Qr.COMMENT_END:this._stateCommentEnd(e);break;case Qr.COMMENT_END_BANG:this._stateCommentEndBang(e);break;case Qr.DOCTYPE:this._stateDoctype(e);break;case Qr.BEFORE_DOCTYPE_NAME:this._stateBeforeDoctypeName(e);break;case Qr.DOCTYPE_NAME:this._stateDoctypeName(e);break;case Qr.AFTER_DOCTYPE_NAME:this._stateAfterDoctypeName(e);break;case Qr.AFTER_DOCTYPE_PUBLIC_KEYWORD:this._stateAfterDoctypePublicKeyword(e);break;case Qr.BEFORE_DOCTYPE_PUBLIC_IDENTIFIER:this._stateBeforeDoctypePublicIdentifier(e);break;case Qr.DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED:this._stateDoctypePublicIdentifierDoubleQuoted(e);break;case Qr.DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED:this._stateDoctypePublicIdentifierSingleQuoted(e);break;case Qr.AFTER_DOCTYPE_PUBLIC_IDENTIFIER:this._stateAfterDoctypePublicIdentifier(e);break;case Qr.BETWEEN_DOCTYPE_PUBLIC_AND_SYSTEM_IDENTIFIERS:this._stateBetweenDoctypePublicAndSystemIdentifiers(e);break;case Qr.AFTER_DOCTYPE_SYSTEM_KEYWORD:this._stateAfterDoctypeSystemKeyword(e);break;case Qr.BEFORE_DOCTYPE_SYSTEM_IDENTIFIER:this._stateBeforeDoctypeSystemIdentifier(e);break;case Qr.DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED:this._stateDoctypeSystemIdentifierDoubleQuoted(e);break;case Qr.DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED:this._stateDoctypeSystemIdentifierSingleQuoted(e);break;case Qr.AFTER_DOCTYPE_SYSTEM_IDENTIFIER:this._stateAfterDoctypeSystemIdentifier(e);break;case Qr.BOGUS_DOCTYPE:this._stateBogusDoctype(e);break;case Qr.CDATA_SECTION:this._stateCdataSection(e);break;case Qr.CDATA_SECTION_BRACKET:this._stateCdataSectionBracket(e);break;case Qr.CDATA_SECTION_END:this._stateCdataSectionEnd(e);break;case Qr.CHARACTER_REFERENCE:this._stateCharacterReference(e);break;case Qr.NAMED_CHARACTER_REFERENCE:this._stateNamedCharacterReference(e);break;case Qr.AMBIGUOUS_AMPERSAND:this._stateAmbiguousAmpersand(e);break;case Qr.NUMERIC_CHARACTER_REFERENCE:this._stateNumericCharacterReference(e);break;case Qr.HEXADEMICAL_CHARACTER_REFERENCE_START:this._stateHexademicalCharacterReferenceStart(e);break;case Qr.HEXADEMICAL_CHARACTER_REFERENCE:this._stateHexademicalCharacterReference(e);break;case Qr.DECIMAL_CHARACTER_REFERENCE:this._stateDecimalCharacterReference(e);break;case Qr.NUMERIC_CHARACTER_REFERENCE_END:this._stateNumericCharacterReferenceEnd(e);break;default:throw new Error("Unknown state")}}_stateData(e){switch(e){case ar.LESS_THAN_SIGN:this.state=Qr.TAG_OPEN;break;case ar.AMPERSAND:this.returnState=Qr.DATA,this.state=Qr.CHARACTER_REFERENCE;break;case ar.NULL:this._err(_r.unexpectedNullCharacter),this._emitCodePoint(e);break;case ar.EOF:this._emitEOFToken();break;default:this._emitCodePoint(e)}}_stateRcdata(e){switch(e){case ar.AMPERSAND:this.returnState=Qr.RCDATA,this.state=Qr.CHARACTER_REFERENCE;break;case ar.LESS_THAN_SIGN:this.state=Qr.RCDATA_LESS_THAN_SIGN;break;case ar.NULL:this._err(_r.unexpectedNullCharacter),this._emitChars(sr);break;case ar.EOF:this._emitEOFToken();break;default:this._emitCodePoint(e)}}_stateRawtext(e){switch(e){case ar.LESS_THAN_SIGN:this.state=Qr.RAWTEXT_LESS_THAN_SIGN;break;case ar.NULL:this._err(_r.unexpectedNullCharacter),this._emitChars(sr);break;case ar.EOF:this._emitEOFToken();break;default:this._emitCodePoint(e)}}_stateScriptData(e){switch(e){case ar.LESS_THAN_SIGN:this.state=Qr.SCRIPT_DATA_LESS_THAN_SIGN;break;case ar.NULL:this._err(_r.unexpectedNullCharacter),this._emitChars(sr);break;case ar.EOF:this._emitEOFToken();break;default:this._emitCodePoint(e)}}_statePlaintext(e){switch(e){case ar.NULL:this._err(_r.unexpectedNullCharacter),this._emitChars(sr);break;case ar.EOF:this._emitEOFToken();break;default:this._emitCodePoint(e)}}_stateTagOpen(e){if(Jr(e))this._createStartTagToken(),this.state=Qr.TAG_NAME,this._stateTagName(e);else switch(e){case ar.EXCLAMATION_MARK:this.state=Qr.MARKUP_DECLARATION_OPEN;break;case ar.SOLIDUS:this.state=Qr.END_TAG_OPEN;break;case ar.QUESTION_MARK:this._err(_r.unexpectedQuestionMarkInsteadOfTagName),this._createCommentToken(1),this.state=Qr.BOGUS_COMMENT,this._stateBogusComment(e);break;case ar.EOF:this._err(_r.eofBeforeTagName),this._emitChars("<"),this._emitEOFToken();break;default:this._err(_r.invalidFirstCharacterOfTagName),this._emitChars("<"),this.state=Qr.DATA,this._stateData(e)}}_stateEndTagOpen(e){if(Jr(e))this._createEndTagToken(),this.state=Qr.TAG_NAME,this._stateTagName(e);else switch(e){case ar.GREATER_THAN_SIGN:this._err(_r.missingEndTagName),this.state=Qr.DATA;break;case ar.EOF:this._err(_r.eofBeforeTagName),this._emitChars("");break;case ar.NULL:this._err(_r.unexpectedNullCharacter),this.state=Qr.SCRIPT_DATA_ESCAPED,this._emitChars(sr);break;case ar.EOF:this._err(_r.eofInScriptHtmlCommentLikeText),this._emitEOFToken();break;default:this.state=Qr.SCRIPT_DATA_ESCAPED,this._emitCodePoint(e)}}_stateScriptDataEscapedLessThanSign(e){e===ar.SOLIDUS?this.state=Qr.SCRIPT_DATA_ESCAPED_END_TAG_OPEN:Jr(e)?(this._emitChars("<"),this.state=Qr.SCRIPT_DATA_DOUBLE_ESCAPE_START,this._stateScriptDataDoubleEscapeStart(e)):(this._emitChars("<"),this.state=Qr.SCRIPT_DATA_ESCAPED,this._stateScriptDataEscaped(e))}_stateScriptDataEscapedEndTagOpen(e){Jr(e)?(this.state=Qr.SCRIPT_DATA_ESCAPED_END_TAG_NAME,this._stateScriptDataEscapedEndTagName(e)):(this._emitChars("");break;case ar.NULL:this._err(_r.unexpectedNullCharacter),this.state=Qr.SCRIPT_DATA_DOUBLE_ESCAPED,this._emitChars(sr);break;case ar.EOF:this._err(_r.eofInScriptHtmlCommentLikeText),this._emitEOFToken();break;default:this.state=Qr.SCRIPT_DATA_DOUBLE_ESCAPED,this._emitCodePoint(e)}}_stateScriptDataDoubleEscapedLessThanSign(e){e===ar.SOLIDUS?(this.state=Qr.SCRIPT_DATA_DOUBLE_ESCAPE_END,this._emitChars("/")):(this.state=Qr.SCRIPT_DATA_DOUBLE_ESCAPED,this._stateScriptDataDoubleEscaped(e))}_stateScriptDataDoubleEscapeEnd(e){if(this.preprocessor.startsWith(lr,!1)&&si(this.preprocessor.peek(lr.length))){this._emitCodePoint(e);for(let e=0;e=e;){var t=this.current;0[e.name,e.value]));let i=0;for(let e=0;er.get(e.name)===e.value)&&3<=(i+=1)&&this.entries.splice(t.idx,1)}}}}insertMarker(){this.entries.unshift(Ei)}pushElement(e,t){this._ensureNoahArkCondition(e),this.entries.unshift({type:_i.Element,element:e,token:t})}insertElementAfterBookmark(e,t){var n=this.entries.indexOf(this.bookmark);this.entries.splice(n,0,{type:_i.Element,element:e,token:t})}removeEntry(e){0<=(e=this.entries.indexOf(e))&&this.entries.splice(e,1)}clearToLastMarker(){var e=this.entries.indexOf(Ei);0<=e?this.entries.splice(0,e+1):this.entries.length=0}getElementEntryInScopeWithTagName(e){var t=this.entries.find(t=>t.type===_i.Marker||this.treeAdapter.getTagName(t.element)===e);return t&&t.type===_i.Element?t:null}getElementEntry(e){return this.entries.find(t=>t.type===_i.Element&&t.element===e)}}function Ai(e){return{nodeName:"#text",value:e,parentNode:null}}const gi={createDocument:()=>({nodeName:"#document",mode:Br.NO_QUIRKS,childNodes:[]}),createDocumentFragment:()=>({nodeName:"#document-fragment",childNodes:[]}),createElement:(e,t,n)=>({nodeName:e,tagName:e,attrs:n,namespaceURI:t,childNodes:[],parentNode:null}),createCommentNode:e=>({nodeName:"#comment",data:e,parentNode:null}),appendChild(e,t){e.childNodes.push(t),t.parentNode=e},insertBefore(e,t,n){n=e.childNodes.indexOf(n),e.childNodes.splice(n,0,t),t.parentNode=e},setTemplateContent(e,t){e.content=t},getTemplateContent:e=>e.content,setDocumentType(e,t,n,r){const i=e.childNodes.find(e=>"#documentType"===e.nodeName);if(i)i.name=t,i.publicId=n,i.systemId=r;else{const i={nodeName:"#documentType",name:t,publicId:n,systemId:r,parentNode:null};gi.appendChild(e,i)}},setDocumentMode(e,t){e.mode=t},getDocumentMode:e=>e.mode,detachNode(e){var t;e.parentNode&&(t=e.parentNode.childNodes.indexOf(e),e.parentNode.childNodes.splice(t,1),e.parentNode=null)},insertText(e,t){if(0e.name));for(let r=0;re.childNodes[0],getChildNodes:e=>e.childNodes,getParentNode:e=>e.parentNode,getAttrList:e=>e.attrs,getTagName:e=>e.tagName,getNamespaceURI:e=>e.namespaceURI,getTextNodeContent:e=>e.value,getCommentNodeContent:e=>e.data,getDocumentTypeNodeName:e=>e.name,getDocumentTypeNodePublicId:e=>e.publicId,getDocumentTypeNodeSystemId:e=>e.systemId,isTextNode:e=>"#text"===e.nodeName,isCommentNode:e=>"#comment"===e.nodeName,isDocumentTypeNode:e=>"#documentType"===e.nodeName,isElementNode:e=>Object.prototype.hasOwnProperty.call(e,"tagName"),setNodeSourceCodeLocation(e,t){e.sourceCodeLocation=t},getNodeSourceCodeLocation:e=>e.sourceCodeLocation,updateNodeSourceCodeLocation(e,t){e.sourceCodeLocation={...e.sourceCodeLocation,...t}}},vi="html",yi="about:legacy-compat",Si="http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd",Ci=["+//silmaril//dtd html pro v0r11 19970101//","-//as//dtd html 3.0 aswedit + extensions//","-//advasoft ltd//dtd html 3.0 aswedit + extensions//","-//ietf//dtd html 2.0 level 1//","-//ietf//dtd html 2.0 level 2//","-//ietf//dtd html 2.0 strict level 1//","-//ietf//dtd html 2.0 strict level 2//","-//ietf//dtd html 2.0 strict//","-//ietf//dtd html 2.0//","-//ietf//dtd html 2.1e//","-//ietf//dtd html 3.0//","-//ietf//dtd html 3.2 final//","-//ietf//dtd html 3.2//","-//ietf//dtd html 3//","-//ietf//dtd html level 0//","-//ietf//dtd html level 1//","-//ietf//dtd html level 2//","-//ietf//dtd html level 3//","-//ietf//dtd html strict level 0//","-//ietf//dtd html strict level 1//","-//ietf//dtd html strict level 2//","-//ietf//dtd html strict level 3//","-//ietf//dtd html strict//","-//ietf//dtd html//","-//metrius//dtd metrius presentational//","-//microsoft//dtd internet explorer 2.0 html strict//","-//microsoft//dtd internet explorer 2.0 html//","-//microsoft//dtd internet explorer 2.0 tables//","-//microsoft//dtd internet explorer 3.0 html strict//","-//microsoft//dtd internet explorer 3.0 html//","-//microsoft//dtd internet explorer 3.0 tables//","-//netscape comm. corp.//dtd html//","-//netscape comm. corp.//dtd strict html//","-//o'reilly and associates//dtd html 2.0//","-//o'reilly and associates//dtd html extended 1.0//","-//o'reilly and associates//dtd html extended relaxed 1.0//","-//sq//dtd html 2.0 hotmetal + extensions//","-//softquad software//dtd hotmetal pro 6.0::19990601::extensions to html 4.0//","-//softquad//dtd hotmetal pro 4.0::19971010::extensions to html 4.0//","-//spyglass//dtd html 2.0 extended//","-//sun microsystems corp.//dtd hotjava html//","-//sun microsystems corp.//dtd hotjava strict html//","-//w3c//dtd html 3 1995-03-24//","-//w3c//dtd html 3.2 draft//","-//w3c//dtd html 3.2 final//","-//w3c//dtd html 3.2//","-//w3c//dtd html 3.2s draft//","-//w3c//dtd html 4.0 frameset//","-//w3c//dtd html 4.0 transitional//","-//w3c//dtd html experimental 19960712//","-//w3c//dtd html experimental 970421//","-//w3c//dtd w3 html//","-//w3o//dtd w3 html 3.0//","-//webtechs//dtd mozilla html 2.0//","-//webtechs//dtd mozilla html//"],Ni=[...Ci,"-//w3c//dtd html 4.01 frameset//","-//w3c//dtd html 4.01 transitional//"],bi=new Set(["-//w3o//dtd w3 html strict 3.0//en//","-/w3c/dtd html 4.0 transitional/en","html"]),Ii=["-//w3c//dtd xhtml 1.0 frameset//","-//w3c//dtd xhtml 1.0 transitional//"],Oi=[...Ii,"-//w3c//dtd html 4.01 frameset//","-//w3c//dtd html 4.01 transitional//"];function ki(e,t){return t.some(t=>e.startsWith(t))}const Li={TEXT_HTML:"text/html",APPLICATION_XML:"application/xhtml+xml"},Di="definitionurl",Ri="definitionURL",wi=new Map(["attributeName","attributeType","baseFrequency","baseProfile","calcMode","clipPathUnits","diffuseConstant","edgeMode","filterUnits","glyphRef","gradientTransform","gradientUnits","kernelMatrix","kernelUnitLength","keyPoints","keySplines","keyTimes","lengthAdjust","limitingConeAngle","markerHeight","markerUnits","markerWidth","maskContentUnits","maskUnits","numOctaves","pathLength","patternContentUnits","patternTransform","patternUnits","pointsAtX","pointsAtY","pointsAtZ","preserveAlpha","preserveAspectRatio","primitiveUnits","refX","refY","repeatCount","repeatDur","requiredExtensions","requiredFeatures","specularConstant","specularExponent","spreadMethod","startOffset","stdDeviation","stitchTiles","surfaceScale","systemLanguage","tableValues","targetX","targetY","textLength","viewBox","viewTarget","xChannelSelector","yChannelSelector","zoomAndPan"].map(e=>[e.toLowerCase(),e])),xi=new Map([["xlink:actuate",{prefix:"xlink",name:"actuate",namespace:Mr.XLINK}],["xlink:arcrole",{prefix:"xlink",name:"arcrole",namespace:Mr.XLINK}],["xlink:href",{prefix:"xlink",name:"href",namespace:Mr.XLINK}],["xlink:role",{prefix:"xlink",name:"role",namespace:Mr.XLINK}],["xlink:show",{prefix:"xlink",name:"show",namespace:Mr.XLINK}],["xlink:title",{prefix:"xlink",name:"title",namespace:Mr.XLINK}],["xlink:type",{prefix:"xlink",name:"type",namespace:Mr.XLINK}],["xml:base",{prefix:"xml",name:"base",namespace:Mr.XML}],["xml:lang",{prefix:"xml",name:"lang",namespace:Mr.XML}],["xml:space",{prefix:"xml",name:"space",namespace:Mr.XML}],["xmlns",{prefix:"",name:"xmlns",namespace:Mr.XMLNS}],["xmlns:xlink",{prefix:"xmlns",name:"xlink",namespace:Mr.XMLNS}]]),Mi=new Map(["altGlyph","altGlyphDef","altGlyphItem","animateColor","animateMotion","animateTransform","clipPath","feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feImage","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence","foreignObject","glyphRef","linearGradient","radialGradient","textPath"].map(e=>[e.toLowerCase(),e])),Pi=new Set([Ur.B,Ur.BIG,Ur.BLOCKQUOTE,Ur.BODY,Ur.BR,Ur.CENTER,Ur.CODE,Ur.DD,Ur.DIV,Ur.DL,Ur.DT,Ur.EM,Ur.EMBED,Ur.H1,Ur.H2,Ur.H3,Ur.H4,Ur.H5,Ur.H6,Ur.HEAD,Ur.HR,Ur.I,Ur.IMG,Ur.LI,Ur.LISTING,Ur.MENU,Ur.META,Ur.NOBR,Ur.OL,Ur.P,Ur.PRE,Ur.RUBY,Ur.S,Ur.SMALL,Ur.SPAN,Ur.STRONG,Ur.STRIKE,Ur.SUB,Ur.SUP,Ur.TABLE,Ur.TT,Ur.U,Ur.UL,Ur.VAR]);function Bi(e){for(let t=0;tthis.treeAdapter.isDocumentTypeNode(e));t&&this.treeAdapter.setNodeSourceCodeLocation(t,e.location)}}_attachElementToTree(e,t){if(this.options.sourceCodeLocationInfo&&(t=t&&{...t,startTag:t},this.treeAdapter.setNodeSourceCodeLocation(e,t)),this._shouldFosterParentOnInsertion())this._fosterParentElement(e);else{const t=this.openElements.currentTmplContentOrNode;this.treeAdapter.appendChild(t,e)}}_appendElement(e,t){t=this.treeAdapter.createElement(e.tagName,t,e.attrs),this._attachElementToTree(t,e.location)}_insertElement(e,t){t=this.treeAdapter.createElement(e.tagName,t,e.attrs),this._attachElementToTree(t,e.location),this.openElements.push(t,e.tagID)}_insertFakeElement(e,t){e=this.treeAdapter.createElement(e,Mr.HTML,[]),this._attachElementToTree(e,null),this.openElements.push(e,t)}_insertTemplate(e){var t=this.treeAdapter.createElement(e.tagName,Mr.HTML,e.attrs),n=this.treeAdapter.createDocumentFragment();this.treeAdapter.setTemplateContent(t,n),this._attachElementToTree(t,e.location),this.openElements.push(t,e.tagID),this.options.sourceCodeLocationInfo&&this.treeAdapter.setNodeSourceCodeLocation(n,null)}_insertFakeRootElement(){var e=this.treeAdapter.createElement(Fr.HTML,Mr.HTML,[]);this.options.sourceCodeLocationInfo&&this.treeAdapter.setNodeSourceCodeLocation(e,null),this.treeAdapter.appendChild(this.openElements.current,e),this.openElements.push(e,Ur.HTML)}_appendCommentNode(e,t){var n=this.treeAdapter.createCommentNode(e.data);this.treeAdapter.appendChild(t,n),this.options.sourceCodeLocationInfo&&this.treeAdapter.setNodeSourceCodeLocation(n,e.location)}_insertCharacters(e){let t,n;if(this._shouldFosterParentOnInsertion()?({parent:t,beforeElement:n}=this._findFosterParentingLocation(),n?this.treeAdapter.insertTextBefore(t,e.chars,n):this.treeAdapter.insertText(t,e.chars)):(t=this.openElements.currentTmplContentOrNode,this.treeAdapter.insertText(t,e.chars)),e.location){var r=this.treeAdapter.getChildNodes(t),i=n?r.lastIndexOf(n):r.length,i=r[i-1];if(this.treeAdapter.getNodeSourceCodeLocation(i)){const{endLine:t,endCol:n,endOffset:r}=e.location;this.treeAdapter.updateNodeSourceCodeLocation(i,{endLine:t,endCol:n,endOffset:r})}else this.options.sourceCodeLocationInfo&&this.treeAdapter.setNodeSourceCodeLocation(i,e.location)}}_adoptNodes(e,t){for(let n=this.treeAdapter.getFirstChild(e);n;n=this.treeAdapter.getFirstChild(e))this.treeAdapter.detachNode(n),this.treeAdapter.appendChild(t,n)}_setEndLocation(e,t){var n,r;this.treeAdapter.getNodeSourceCodeLocation(e)&&t.location&&(n=t.location,r=this.treeAdapter.getTagName(e),r=t.type===Tr.END_TAG&&r===t.tagName?{endTag:{...n},endLine:n.endLine,endCol:n.endCol,endOffset:n.endOffset}:{endLine:n.startLine,endCol:n.startCol,endOffset:n.startOffset},this.treeAdapter.updateNodeSourceCodeLocation(e,r))}shouldProcessStartTagTokenInForeignContent(e){if(!this.currentNotInHTML)return!1;let t,n;return 0===this.openElements.stackTop&&this.fragmentContext?(t=this.fragmentContext,n=this.fragmentContextID):{current:t,currentTagId:n}=this.openElements,(e.tagID!==Ur.SVG||this.treeAdapter.getTagName(t)!==Fr.ANNOTATION_XML||this.treeAdapter.getNamespaceURI(t)!==Mr.MATHML)&&(this.tokenizer.inForeignNode||(e.tagID===Ur.MGLYPH||e.tagID===Ur.MALIGNMARK)&&!this._isIntegrationPoint(n,t,Mr.HTML))}_processToken(e){switch(e.type){case Tr.CHARACTER:this.onCharacter(e);break;case Tr.NULL_CHARACTER:this.onNullCharacter(e);break;case Tr.COMMENT:this.onComment(e);break;case Tr.DOCTYPE:this.onDoctype(e);break;case Tr.START_TAG:this._processStartTag(e);break;case Tr.END_TAG:this.onEndTag(e);break;case Tr.EOF:this.onEof(e);break;case Tr.WHITESPACE_CHARACTER:this.onWhitespaceCharacter(e)}}_isIntegrationPoint(e,t,n){return Hi(e,this.treeAdapter.getNamespaceURI(t),this.treeAdapter.getAttrList(t),n)}_reconstructActiveFormattingElements(){const e=this.activeFormattingElements.entries.length;if(e){var t=this.activeFormattingElements.entries.findIndex(e=>e.type===_i.Marker||this.openElements.contains(e.element));for(let n=t<0?e-1:t-1;0<=n;n--){const e=this.activeFormattingElements.entries[n];this._insertElement(e.token,this.treeAdapter.getNamespaceURI(e.element)),e.element=this.openElements.current}}}_closeTableCell(){this.openElements.generateImpliedEndTags(),this.openElements.popUntilTableCellPopped(),this.activeFormattingElements.clearToLastMarker(),this.insertionMode=Yi.IN_ROW}_closePElement(){this.openElements.generateImpliedEndTagsWithExclusion(Ur.P),this.openElements.popUntilTagNamePopped(Ur.P)}_resetInsertionMode(){for(let e=this.openElements.stackTop;0<=e;e--)switch(0===e&&this.fragmentContext?this.fragmentContextID:this.openElements.tagIDs[e]){case Ur.TR:return void(this.insertionMode=Yi.IN_ROW);case Ur.TBODY:case Ur.THEAD:case Ur.TFOOT:return void(this.insertionMode=Yi.IN_TABLE_BODY);case Ur.CAPTION:return void(this.insertionMode=Yi.IN_CAPTION);case Ur.COLGROUP:return void(this.insertionMode=Yi.IN_COLUMN_GROUP);case Ur.TABLE:return void(this.insertionMode=Yi.IN_TABLE);case Ur.BODY:return void(this.insertionMode=Yi.IN_BODY);case Ur.FRAMESET:return void(this.insertionMode=Yi.IN_FRAMESET);case Ur.SELECT:return void this._resetInsertionModeForSelect(e);case Ur.TEMPLATE:return void(this.insertionMode=this.tmplInsertionModeStack[0]);case Ur.HTML:return void(this.insertionMode=this.headElement?Yi.AFTER_HEAD:Yi.BEFORE_HEAD);case Ur.TD:case Ur.TH:if(0e===Pr.COLOR||e===Pr.SIZE||e===Pr.FACE)||Pi.has(t)}(t)?(n=e._getAdjustedCurrentElement(),(n=e.treeAdapter.getNamespaceURI(n))===Mr.MATHML?Bi(t):n===Mr.SVG&&(function(e){var t=Mi.get(e.tagName);null!=t&&(e.tagName=t,e.tagID=qr(e.tagName))}(t),Fi(t)),Ui(t),t.selfClosing?e._appendElement(t,n):e._insertElement(t,n),t.ackSelfClosing=!0):(qs(e),e._startTagOutsideForeignContent(t))}(this,e):this._startTagOutsideForeignContent(e)}_startTagOutsideForeignContent(e){switch(this.insertionMode){case Yi.INITIAL:is(this,e);break;case Yi.BEFORE_HTML:!function(e,t){t.tagID===Ur.HTML?(e._insertElement(t,Mr.HTML),e.insertionMode=Yi.BEFORE_HEAD):ss(e,t)}(this,e);break;case Yi.BEFORE_HEAD:!function(e,t){switch(t.tagID){case Ur.HTML:As(e,t);break;case Ur.HEAD:e._insertElement(t,Mr.HTML),e.headElement=e.openElements.current,e.insertionMode=Yi.IN_HEAD;break;default:as(e,t)}}(this,e);break;case Yi.IN_HEAD:os(this,e);break;case Yi.IN_HEAD_NO_SCRIPT:!function(e,t){switch(t.tagID){case Ur.HTML:As(e,t);break;case Ur.BASEFONT:case Ur.BGSOUND:case Ur.HEAD:case Ur.LINK:case Ur.META:case Ur.NOFRAMES:case Ur.STYLE:os(e,t);break;case Ur.NOSCRIPT:e._err(t,_r.nestedNoscriptInHead);break;default:ls(e,t)}}(this,e);break;case Yi.AFTER_HEAD:!function(e,t){switch(t.tagID){case Ur.HTML:As(e,t);break;case Ur.BODY:e._insertElement(t,Mr.HTML),e.framesetOk=!1,e.insertionMode=Yi.IN_BODY;break;case Ur.FRAMESET:e._insertElement(t,Mr.HTML),e.insertionMode=Yi.IN_FRAMESET;break;case Ur.BASE:case Ur.BASEFONT:case Ur.BGSOUND:case Ur.LINK:case Ur.META:case Ur.NOFRAMES:case Ur.SCRIPT:case Ur.STYLE:case Ur.TEMPLATE:case Ur.TITLE:e._err(t,_r.abandonedHeadElementChild),e.openElements.push(e.headElement,Ur.HEAD),os(e,t),e.openElements.remove(e.headElement);break;case Ur.HEAD:e._err(t,_r.misplacedStartTagForHeadElement);break;default:hs(e,t)}}(this,e);break;case Yi.IN_BODY:As(this,e);break;case Yi.IN_TABLE:Cs(this,e);break;case Yi.IN_TABLE_TEXT:ks(this,e);break;case Yi.IN_CAPTION:!function(e,t){var n=t.tagID;Ls.has(n)?e.openElements.hasInTableScope(Ur.CAPTION)&&(e.openElements.generateImpliedEndTags(),e.openElements.popUntilTagNamePopped(Ur.CAPTION),e.activeFormattingElements.clearToLastMarker(),e.insertionMode=Yi.IN_TABLE,Cs(e,t)):As(e,t)}(this,e);break;case Yi.IN_COLUMN_GROUP:Ds(this,e);break;case Yi.IN_TABLE_BODY:ws(this,e);break;case Yi.IN_ROW:Ms(this,e);break;case Yi.IN_CELL:!function(e,t){var n=t.tagID;Ls.has(n)?(e.openElements.hasInTableScope(Ur.TD)||e.openElements.hasInTableScope(Ur.TH))&&(e._closeTableCell(),Ms(e,t)):As(e,t)}(this,e);break;case Yi.IN_SELECT:Bs(this,e);break;case Yi.IN_SELECT_IN_TABLE:!function(e,t){var n=t.tagID;n===Ur.CAPTION||n===Ur.TABLE||n===Ur.TBODY||n===Ur.TFOOT||n===Ur.THEAD||n===Ur.TR||n===Ur.TD||n===Ur.TH?(e.openElements.popUntilTagNamePopped(Ur.SELECT),e._resetInsertionMode(),e._processStartTag(t)):Bs(e,t)}(this,e);break;case Yi.IN_TEMPLATE:!function(e,t){switch(t.tagID){case Ur.BASE:case Ur.BASEFONT:case Ur.BGSOUND:case Ur.LINK:case Ur.META:case Ur.NOFRAMES:case Ur.SCRIPT:case Ur.STYLE:case Ur.TEMPLATE:case Ur.TITLE:os(e,t);break;case Ur.CAPTION:case Ur.COLGROUP:case Ur.TBODY:case Ur.TFOOT:case Ur.THEAD:e.tmplInsertionModeStack[0]=Yi.IN_TABLE,e.insertionMode=Yi.IN_TABLE,Cs(e,t);break;case Ur.COL:e.tmplInsertionModeStack[0]=Yi.IN_COLUMN_GROUP,e.insertionMode=Yi.IN_COLUMN_GROUP,Ds(e,t);break;case Ur.TR:e.tmplInsertionModeStack[0]=Yi.IN_TABLE_BODY,e.insertionMode=Yi.IN_TABLE_BODY,ws(e,t);break;case Ur.TD:case Ur.TH:e.tmplInsertionModeStack[0]=Yi.IN_ROW,e.insertionMode=Yi.IN_ROW,Ms(e,t);break;default:e.tmplInsertionModeStack[0]=Yi.IN_BODY,e.insertionMode=Yi.IN_BODY,As(e,t)}}(this,e);break;case Yi.AFTER_BODY:!function(e,t){(t.tagID===Ur.HTML?As:Gs)(e,t)}(this,e);break;case Yi.IN_FRAMESET:!function(e,t){switch(t.tagID){case Ur.HTML:As(e,t);break;case Ur.FRAMESET:e._insertElement(t,Mr.HTML);break;case Ur.FRAME:e._appendElement(t,Mr.HTML),t.ackSelfClosing=!0;break;case Ur.NOFRAMES:os(e,t)}}(this,e);break;case Yi.AFTER_FRAMESET:!function(e,t){switch(t.tagID){case Ur.HTML:As(e,t);break;case Ur.NOFRAMES:os(e,t)}}(this,e);break;case Yi.AFTER_AFTER_BODY:!function(e,t){(t.tagID===Ur.HTML?As:js)(e,t)}(this,e);break;case Yi.AFTER_AFTER_FRAMESET:!function(e,t){switch(t.tagID){case Ur.HTML:As(e,t);break;case Ur.NOFRAMES:os(e,t)}}(this,e)}}onEndTag(e){this.skipNextNewLine=!1,this.currentToken=e,this.currentNotInHTML?function(e,t){if(t.tagID===Ur.P||t.tagID===Ur.BR)return qs(e),e._endTagOutsideForeignContent(t);for(let n=e.openElements.stackTop;0=qi;!n||o?(o&&e.activeFormattingElements.removeEntry(n),e.openElements.remove(a)):(a=Zi(e,n),r===t&&(e.activeFormattingElements.bookmark=n),e.treeAdapter.detachNode(r),e.treeAdapter.appendChild(a,r),r=a)}return r}function Zi(e,t){var n=e.treeAdapter.getNamespaceURI(t.element),n=e.treeAdapter.createElement(t.token.tagName,n,t.token.attrs);return e.openElements.replace(t.element,n),t.element=n}function Ji(e,t,n){var i,r=qr(e.treeAdapter.getTagName(t));e._isElementCausesFosterParenting(r)?e._fosterParentElement(n):(i=e.treeAdapter.getNamespaceURI(t),r===Ur.TEMPLATE&&i===Mr.HTML&&(t=e.treeAdapter.getTemplateContent(t)),e.treeAdapter.appendChild(t,n))}function es(e,t,n){var r=e.treeAdapter.getNamespaceURI(n.element),i=n.token,r=e.treeAdapter.createElement(i.tagName,r,i.attrs);e._adoptNodes(t,r),e.treeAdapter.appendChild(t,r),e.activeFormattingElements.insertElementAfterBookmark(r,i),e.activeFormattingElements.removeEntry(n),e.openElements.remove(n.element),e.openElements.insertAfter(t,r,i.tagID)}function ts(e,t){for(let n=0;n=n;r--)e._setEndLocation(e.openElements.items[r],t);if(!e.fragmentContext&&0<=e.openElements.stackTop){const n=e.openElements.items[0],r=e.treeAdapter.getNodeSourceCodeLocation(n);if(r&&!r.endTag&&(e._setEndLocation(n,t),1<=e.openElements.stackTop)){const n=e.openElements.items[1],r=e.treeAdapter.getNodeSourceCodeLocation(n);r&&!r.endTag&&e._setEndLocation(n,t)}}}}function is(e,t){e._err(t,_r.missingDoctype,!0),e.treeAdapter.setDocumentMode(e.document,Br.QUIRKS),e.insertionMode=Yi.BEFORE_HTML,e._processToken(t)}function ss(e,t){e._insertFakeRootElement(),e.insertionMode=Yi.BEFORE_HEAD,e._processToken(t)}function as(e,t){e._insertFakeElement(Fr.HEAD,Ur.HEAD),e.headElement=e.openElements.current,e.insertionMode=Yi.IN_HEAD,e._processToken(t)}function os(e,t){switch(t.tagID){case Ur.HTML:As(e,t);break;case Ur.BASE:case Ur.BASEFONT:case Ur.BGSOUND:case Ur.LINK:case Ur.META:e._appendElement(t,Mr.HTML),t.ackSelfClosing=!0;break;case Ur.TITLE:e._switchToTextParsing(t,zr.RCDATA);break;case Ur.NOSCRIPT:e.options.scriptingEnabled?e._switchToTextParsing(t,zr.RAWTEXT):(e._insertElement(t,Mr.HTML),e.insertionMode=Yi.IN_HEAD_NO_SCRIPT);break;case Ur.NOFRAMES:case Ur.STYLE:e._switchToTextParsing(t,zr.RAWTEXT);break;case Ur.SCRIPT:e._switchToTextParsing(t,zr.SCRIPT_DATA);break;case Ur.TEMPLATE:e._insertTemplate(t),e.activeFormattingElements.insertMarker(),e.framesetOk=!1,e.insertionMode=Yi.IN_TEMPLATE,e.tmplInsertionModeStack.unshift(Yi.IN_TEMPLATE);break;case Ur.HEAD:e._err(t,_r.misplacedStartTagForHeadElement);break;default:us(e,t)}}function cs(e,t){0=t&&e.openElements.shortenToLength(t);break}if(e._isSpecialElement(i,s))break}}function vs(e,t){switch(t.tagID){case Ur.A:case Ur.B:case Ur.I:case Ur.S:case Ur.U:case Ur.EM:case Ur.TT:case Ur.BIG:case Ur.CODE:case Ur.FONT:case Ur.NOBR:case Ur.SMALL:case Ur.STRIKE:case Ur.STRONG:ts(e,t);break;case Ur.P:!function(e){e.openElements.hasInButtonScope(Ur.P)||e._insertFakeElement(Fr.P,Ur.P),e._closePElement()}(e);break;case Ur.DL:case Ur.UL:case Ur.OL:case Ur.DIR:case Ur.DIV:case Ur.NAV:case Ur.PRE:case Ur.MAIN:case Ur.MENU:case Ur.ASIDE:case Ur.BUTTON:case Ur.CENTER:case Ur.FIGURE:case Ur.FOOTER:case Ur.HEADER:case Ur.HGROUP:case Ur.DIALOG:case Ur.ADDRESS:case Ur.ARTICLE:case Ur.DETAILS:case Ur.SECTION:case Ur.SUMMARY:case Ur.LISTING:case Ur.FIELDSET:case Ur.BLOCKQUOTE:case Ur.FIGCAPTION:!function(e,t){t=t.tagID,e.openElements.hasInScope(t)&&(e.openElements.generateImpliedEndTags(),e.openElements.popUntilTagNamePopped(t))}(e,t);break;case Ur.LI:!function(e){e.openElements.hasInListItemScope(Ur.LI)&&(e.openElements.generateImpliedEndTagsWithExclusion(Ur.LI),e.openElements.popUntilTagNamePopped(Ur.LI))}(e);break;case Ur.DD:case Ur.DT:!function(e,t){t=t.tagID,e.openElements.hasInScope(t)&&(e.openElements.generateImpliedEndTagsWithExclusion(t),e.openElements.popUntilTagNamePopped(t))}(e,t);break;case Ur.H1:case Ur.H2:case Ur.H3:case Ur.H4:case Ur.H5:case Ur.H6:!function(e){e.openElements.hasNumberedHeaderInScope()&&(e.openElements.generateImpliedEndTags(),e.openElements.popUntilNumberedHeaderPopped())}(e);break;case Ur.BR:!function(e){e._reconstructActiveFormattingElements(),e._insertFakeElement(Fr.BR,Ur.BR),e.openElements.pop(),e.framesetOk=!1}(e);break;case Ur.BODY:!function(e,t){var n;e.openElements.hasInScope(Ur.BODY)&&(e.insertionMode=Yi.AFTER_BODY,e.options.sourceCodeLocationInfo)&&((n=e.openElements.tryPeekProperlyNestedBodyElement())&&e._setEndLocation(n,t))}(e,t);break;case Ur.HTML:!function(e,t){e.openElements.hasInScope(Ur.BODY)&&(e.insertionMode=Yi.AFTER_BODY,Hs(e,t))}(e,t);break;case Ur.FORM:!function(e){var t=0$\x80-\uFFFF]/g;var n=new Map([[34,"""],[38,"&"],[39,"'"],[60,"<"],[62,">"]]);function r(e){for(var i="",s=0;null!==(r=t.xmlReplacer.exec(e));)var r=r.index,o=e.charCodeAt(r),c=n.get(o),s=void 0!==c?(i+=e.substring(s,r)+c,r+1):(i+="".concat(e.substring(s,r),"&#x").concat((0,t.getCodePoint)(e,r).toString(16),";"),t.xmlReplacer.lastIndex+=Number(55296==(64512&o)));return i+e.substr(s)}function i(e,t){return function(n){for(var r,i=0,s="";r=e.exec(n);)i!==r.index&&(s+=n.substring(i,r.index)),s+=t.get(r[0].charCodeAt(0)),i=r.index+1;return s+n.substring(i)}}t.getCodePoint=null!=String.prototype.codePointAt?function(e,t){return e.codePointAt(t)}:function(e,t){return 55296==(64512&e.charCodeAt(t))?1024*(e.charCodeAt(t)-55296)+e.charCodeAt(t+1)-56320+65536:e.charCodeAt(t)},t.encodeXML=r,t.escape=r,t.escapeUTF8=i(/[&<>'"]/g,n),t.escapeAttribute=i(/["&\u00A0]/g,new Map([[34,"""],[38,"&"],[160," "]])),t.escapeText=i(/[&<>\u00A0]/g,new Map([[38,"&"],[60,"<"],[62,">"],[160," "]]))}),Ks=(yr(Ys),Ys.escapeText),Ws=Ys.escapeAttribute;Ys.escapeUTF8,Ys.escape,Ys.encodeXML,Ys.getCodePoint,Ys.xmlReplacer;const Vs=new Set([Fr.AREA,Fr.BASE,Fr.BASEFONT,Fr.BGSOUND,Fr.BR,Fr.COL,Fr.EMBED,Fr.FRAME,Fr.HR,Fr.IMG,Fr.INPUT,Fr.KEYGEN,Fr.LINK,Fr.META,Fr.PARAM,Fr.SOURCE,Fr.TRACK,Fr.WBR]),$s={treeAdapter:gi,scriptingEnabled:!0};function Qs(e,t){return zs(e,{...$s,...t})}function zs(e,t){return t.treeAdapter.isElementNode(e)?function(e,t){var n=t.treeAdapter.getTagName(e);return`<${n}${function(e,{treeAdapter:t}){let n="";for(const r of t.getAttrList(e)){if(n+=" ",r.namespace)switch(r.namespace){case Mr.XML:n+="xml:"+r.name;break;case Mr.XMLNS:"xmlns"!==r.name&&(n+="xmlns:"),n+=r.name;break;case Mr.XLINK:n+="xlink:"+r.name;break;default:n+=r.prefix+":"+r.name}else n+=r.name;n+=`="${Ws(r.value)}"`}return n}(e,t)}>`+(function(e,t){return t.treeAdapter.isElementNode(e)&&t.treeAdapter.getNamespaceURI(e)===Mr.HTML&&Vs.has(t.treeAdapter.getTagName(e))}(e,t)?"":function(e,t){let n="";var e=t.treeAdapter.isElementNode(e)&&t.treeAdapter.getTagName(e)===Fr.TEMPLATE&&t.treeAdapter.getNamespaceURI(e)===Mr.HTML?t.treeAdapter.getTemplateContent(e):e,i=t.treeAdapter.getChildNodes(e);if(i)for(const e of i)n+=zs(e,t);return n}(e,t)+``)}(e,t):t.treeAdapter.isTextNode(e)?function(e,t){var n=t.treeAdapter,r=n.getTextNodeContent(e),s=(e=n.getParentNode(e))&&n.isElementNode(e)&&n.getTagName(e);return s&&n.getNamespaceURI(e)===Mr.HTML&&(n=s,e=t.scriptingEnabled,Vr.has(n)||e&&n===Fr.NOSCRIPT)?r:Ks(r)}(e,t):t.treeAdapter.isCommentNode(e)?function(e,{treeAdapter:t}){return``}(e,t):t.treeAdapter.isDocumentTypeNode(e)?function(e,{treeAdapter:t}){return``}(e,t):""}function Xs(e){return new _(e)}function Zs(e){var t=e.includes('"')?"'":'"';return t+e+t}const Js={isCommentNode:b,isElementNode:S,isTextNode:N,createDocument(){var e=new v([]);return e["x-mode"]=Br.NO_QUIRKS,e},createDocumentFragment:()=>new v([]),createElement(e,t,n){var r=Object.create(null),i=Object.create(null),s=Object.create(null);for(let e=0;enew E(e),appendChild(e,t){var n=e.children[e.children.length-1];n&&((n.next=t).prev=n),e.children.push(t),t.parent=e},insertBefore(e,t,n){var r=e.children.indexOf(n),i=n.prev;i&&((i.next=t).prev=i),(n.prev=t).next=n,e.children.splice(r,0,t),t.parent=e},setTemplateContent(e,t){Js.appendChild(e,t)},getTemplateContent:e=>e.children[0],setDocumentType(e,t,n,r){var i=function(e,t,n){let r="!DOCTYPE ";return e&&(r+=e),t?r+=" PUBLIC "+Zs(t):n&&(r+=" SYSTEM"),n&&(r+=" "+Zs(n)),r}(t,n,r);let s=e.children.find(e=>I(e)&&"!doctype"===e.name);s?s.data=null!=i?i:null:(s=new T("!doctype",i),Js.appendChild(e,s)),s["x-name"]=null!=t?t:void 0,s["x-publicId"]=null!=n?n:void 0,s["x-systemId"]=null!=r?r:void 0},setDocumentMode(e,t){e["x-mode"]=t},getDocumentMode:e=>e["x-mode"],detachNode(e){var t,n,r;e.parent&&(t=e.parent.children.indexOf(e),{prev:n,next:r}=e,e.prev=null,e.next=null,n&&(n.next=r),r&&(r.prev=n),e.parent.children.splice(t,1),e.parent=null)},insertText(e,t){var n=e.children[e.children.length-1];n&&N(n)?n.data+=t:Js.appendChild(e,Xs(t))},insertTextBefore(e,t,n){var r=e.children[e.children.indexOf(n)-1];r&&N(r)?r.data+=t:Js.insertBefore(e,Xs(t),n)},adoptAttributes(e,t){for(let n=0;ne.children[0],getChildNodes:e=>e.children,getParentNode:e=>e.parent,getAttrList:e=>e.attributes,getTagName:e=>e.name,getNamespaceURI:e=>e.namespace,getTextNodeContent:e=>e.data,getCommentNodeContent:e=>e.data,getDocumentTypeNodeName(e){return null!=(e=e["x-name"])?e:""},getDocumentTypeNodePublicId(e){return null!=(e=e["x-publicId"])?e:""},getDocumentTypeNodeSystemId(e){return null!=(e=e["x-systemId"])?e:""},isDocumentTypeNode:e=>I(e)&&"!doctype"===e.name,setNodeSourceCodeLocation(e,t){t&&(e.startIndex=t.startOffset,e.endIndex=t.endOffset),e.sourceCodeLocation=t},getNodeSourceCodeLocation:e=>e.sourceCodeLocation,updateNodeSourceCodeLocation(e,t){null!=t.endOffset&&(e.endIndex=t.endOffset),e.sourceCodeLocation={...e.sourceCodeLocation,...t}}};var ea=function(e,t,n){if(n||2===arguments.length)for(var r,i=0,s=t.length;i=na.Zero&&e<=na.Nine}!function(e){e[e.Tab=9]="Tab",e[e.NewLine=10]="NewLine",e[e.FormFeed=12]="FormFeed",e[e.CarriageReturn=13]="CarriageReturn",e[e.Space=32]="Space",e[e.ExclamationMark=33]="ExclamationMark",e[e.Number=35]="Number",e[e.Amp=38]="Amp",e[e.SingleQuote=39]="SingleQuote",e[e.DoubleQuote=34]="DoubleQuote",e[e.Dash=45]="Dash",e[e.Slash=47]="Slash",e[e.Zero=48]="Zero",e[e.Nine=57]="Nine",e[e.Semi=59]="Semi",e[e.Lt=60]="Lt",e[e.Eq=61]="Eq",e[e.Gt=62]="Gt",e[e.Questionmark=63]="Questionmark",e[e.UpperA=65]="UpperA",e[e.LowerA=97]="LowerA",e[e.UpperF=70]="UpperF",e[e.LowerF=102]="LowerF",e[e.UpperZ=90]="UpperZ",e[e.LowerZ=122]="LowerZ",e[e.LowerX=120]="LowerX",e[e.OpeningSquareBracket=91]="OpeningSquareBracket"}(na=na||{}),function(e){e[e.Text=1]="Text",e[e.BeforeTagName=2]="BeforeTagName",e[e.InTagName=3]="InTagName",e[e.InSelfClosingTag=4]="InSelfClosingTag",e[e.BeforeClosingTagName=5]="BeforeClosingTagName",e[e.InClosingTagName=6]="InClosingTagName",e[e.AfterClosingTagName=7]="AfterClosingTagName",e[e.BeforeAttributeName=8]="BeforeAttributeName",e[e.InAttributeName=9]="InAttributeName",e[e.AfterAttributeName=10]="AfterAttributeName",e[e.BeforeAttributeValue=11]="BeforeAttributeValue",e[e.InAttributeValueDq=12]="InAttributeValueDq",e[e.InAttributeValueSq=13]="InAttributeValueSq",e[e.InAttributeValueNq=14]="InAttributeValueNq",e[e.BeforeDeclaration=15]="BeforeDeclaration",e[e.InDeclaration=16]="InDeclaration",e[e.InProcessingInstruction=17]="InProcessingInstruction",e[e.BeforeComment=18]="BeforeComment",e[e.CDATASequence=19]="CDATASequence",e[e.InSpecialComment=20]="InSpecialComment",e[e.InCommentLike=21]="InCommentLike",e[e.BeforeSpecialS=22]="BeforeSpecialS",e[e.SpecialStartSequence=23]="SpecialStartSequence",e[e.InSpecialTag=24]="InSpecialTag",e[e.BeforeEntity=25]="BeforeEntity",e[e.BeforeNumericEntity=26]="BeforeNumericEntity",e[e.InNamedEntity=27]="InNamedEntity",e[e.InNumericEntity=28]="InNumericEntity",e[e.InHexEntity=29]="InHexEntity"}(ra=ra||{}),function(e){e[e.NoValue=0]="NoValue",e[e.Unquoted=1]="Unquoted",e[e.Single=2]="Single",e[e.Double=3]="Double"}(ia=ia||{});const ua={Cdata:new Uint8Array([67,68,65,84,65,91]),CdataEnd:new Uint8Array([93,93,62]),CommentEnd:new Uint8Array([45,45,62]),ScriptEnd:new Uint8Array([60,47,115,99,114,105,112,116]),StyleEnd:new Uint8Array([60,47,115,116,121,108,101]),TitleEnd:new Uint8Array([60,47,116,105,116,108,101])};class la{constructor({xmlMode:e=!1,decodeEntities:t=!0},n){this.cbs=n,this.state=ra.Text,this.buffer="",this.sectionStart=0,this.index=0,this.baseState=ra.Text,this.isSpecial=!1,this.running=!0,this.offset=0,this.currentSequence=void 0,this.sequenceIndex=0,this.trieIndex=0,this.trieCurrent=0,this.entityResult=0,this.entityExcess=0,this.xmlMode=e,this.decodeEntities=t,this.entityTrie=e?Hr:Gr}reset(){this.state=ra.Text,this.buffer="",this.sectionStart=0,this.index=0,this.baseState=ra.Text,this.currentSequence=void 0,this.running=!0,this.offset=0}write(e){this.offset+=this.buffer.length,this.buffer=e,this.parse()}end(){this.running&&this.finish()}pause(){this.running=!1}resume(){this.running=!0,this.indexthis.sectionStart&&this.cbs.ontext(this.sectionStart,this.index),this.state=ra.BeforeTagName,this.sectionStart=this.index):this.decodeEntities&&e===na.Amp&&(this.state=ra.BeforeEntity)}stateSpecialStartSequence(e){var t=this.sequenceIndex===this.currentSequence.length;if(t?oa(e):(32|e)===this.currentSequence[this.sequenceIndex]){if(!t)return void this.sequenceIndex++}else this.isSpecial=!1;this.sequenceIndex=0,this.state=ra.InTagName,this.stateInTagName(e)}stateInSpecialTag(e){if(this.sequenceIndex===this.currentSequence.length){if(e===na.Gt||aa(e)){var t=this.index-this.currentSequence.length;if(this.sectionStart=na.LowerA&&e<=na.LowerZ||e>=na.UpperA&&e<=na.UpperZ}(e)}startSpecial(e,t){this.isSpecial=!0,this.currentSequence=e,this.sequenceIndex=t,this.state=ra.SpecialStartSequence}stateBeforeTagName(e){var t;e===na.ExclamationMark?(this.state=ra.BeforeDeclaration,this.sectionStart=this.index+1):e===na.Questionmark?(this.state=ra.InProcessingInstruction,this.sectionStart=this.index+1):this.isTagStartChar(e)?(t=32|e,this.sectionStart=this.index,this.xmlMode||t!==ua.TitleEnd[2]?this.state=this.xmlMode||t!==ua.ScriptEnd[2]?ra.InTagName:ra.BeforeSpecialS:this.startSpecial(ua.TitleEnd,3)):e===na.Slash?this.state=ra.BeforeClosingTagName:(this.state=ra.Text,this.stateText(e))}stateInTagName(e){oa(e)&&(this.cbs.onopentagname(this.sectionStart,this.index),this.sectionStart=-1,this.state=ra.BeforeAttributeName,this.stateBeforeAttributeName(e))}stateBeforeClosingTagName(e){aa(e)||(e===na.Gt?this.state=ra.Text:(this.state=this.isTagStartChar(e)?ra.InClosingTagName:ra.InSpecialComment,this.sectionStart=this.index))}stateInClosingTagName(e){e!==na.Gt&&!aa(e)||(this.cbs.onclosetag(this.sectionStart,this.index),this.sectionStart=-1,this.state=ra.AfterClosingTagName,this.stateAfterClosingTagName(e))}stateAfterClosingTagName(e){e!==na.Gt&&!this.fastForwardTo(na.Gt)||(this.state=ra.Text,this.baseState=ra.Text,this.sectionStart=this.index+1)}stateBeforeAttributeName(e){e===na.Gt?(this.cbs.onopentagend(this.index),this.isSpecial?(this.state=ra.InSpecialTag,this.sequenceIndex=0):this.state=ra.Text,this.baseState=this.state,this.sectionStart=this.index+1):e===na.Slash?this.state=ra.InSelfClosingTag:aa(e)||(this.state=ra.InAttributeName,this.sectionStart=this.index)}stateInSelfClosingTag(e){e===na.Gt?(this.cbs.onselfclosingtag(this.index),this.state=ra.Text,this.baseState=ra.Text,this.sectionStart=this.index+1,this.isSpecial=!1):aa(e)||(this.state=ra.BeforeAttributeName,this.stateBeforeAttributeName(e))}stateInAttributeName(e){e!==na.Eq&&!oa(e)||(this.cbs.onattribname(this.sectionStart,this.index),this.sectionStart=-1,this.state=ra.AfterAttributeName,this.stateAfterAttributeName(e))}stateAfterAttributeName(e){e===na.Eq?this.state=ra.BeforeAttributeValue:e===na.Slash||e===na.Gt?(this.cbs.onattribend(ia.NoValue,this.index),this.state=ra.BeforeAttributeName,this.stateBeforeAttributeName(e)):aa(e)||(this.cbs.onattribend(ia.NoValue,this.index),this.state=ra.InAttributeName,this.sectionStart=this.index)}stateBeforeAttributeValue(e){e===na.DoubleQuote?(this.state=ra.InAttributeValueDq,this.sectionStart=this.index+1):e===na.SingleQuote?(this.state=ra.InAttributeValueSq,this.sectionStart=this.index+1):aa(e)||(this.sectionStart=this.index,this.state=ra.InAttributeValueNq,this.stateInAttributeValueNoQuotes(e))}handleInAttributeValue(e,t){e===t||!this.decodeEntities&&this.fastForwardTo(t)?(this.cbs.onattribdata(this.sectionStart,this.index),this.sectionStart=-1,this.cbs.onattribend(t===na.DoubleQuote?ia.Double:ia.Single,this.index),this.state=ra.BeforeAttributeName):this.decodeEntities&&e===na.Amp&&(this.baseState=this.state,this.state=ra.BeforeEntity)}stateInAttributeValueDoubleQuotes(e){this.handleInAttributeValue(e,na.DoubleQuote)}stateInAttributeValueSingleQuotes(e){this.handleInAttributeValue(e,na.SingleQuote)}stateInAttributeValueNoQuotes(e){aa(e)||e===na.Gt?(this.cbs.onattribdata(this.sectionStart,this.index),this.sectionStart=-1,this.cbs.onattribend(ia.Unquoted,this.index),this.state=ra.BeforeAttributeName,this.stateBeforeAttributeName(e)):this.decodeEntities&&e===na.Amp&&(this.baseState=this.state,this.state=ra.BeforeEntity)}stateBeforeDeclaration(e){e===na.OpeningSquareBracket?(this.state=ra.CDATASequence,this.sequenceIndex=0):this.state=e===na.Dash?ra.BeforeComment:ra.InDeclaration}stateInDeclaration(e){e!==na.Gt&&!this.fastForwardTo(na.Gt)||(this.cbs.ondeclaration(this.sectionStart,this.index),this.state=ra.Text,this.sectionStart=this.index+1)}stateInProcessingInstruction(e){e!==na.Gt&&!this.fastForwardTo(na.Gt)||(this.cbs.onprocessinginstruction(this.sectionStart,this.index),this.state=ra.Text,this.sectionStart=this.index+1)}stateBeforeComment(e){e===na.Dash?(this.state=ra.InCommentLike,this.currentSequence=ua.CommentEnd,this.sequenceIndex=2,this.sectionStart=this.index+1):this.state=ra.InDeclaration}stateInSpecialComment(e){e!==na.Gt&&!this.fastForwardTo(na.Gt)||(this.cbs.oncomment(this.sectionStart,this.index,0),this.state=ra.Text,this.sectionStart=this.index+1)}stateBeforeSpecialS(e){var t=32|e;t===ua.ScriptEnd[3]?this.startSpecial(ua.ScriptEnd,4):t===ua.StyleEnd[3]?this.startSpecial(ua.StyleEnd,4):(this.state=ra.InTagName,this.stateInTagName(e))}stateBeforeEntity(e){this.entityExcess=1,this.entityResult=0,e===na.Number?this.state=ra.BeforeNumericEntity:e!==na.Amp&&(this.trieIndex=0,this.trieCurrent=this.entityTrie[0],this.state=ra.InNamedEntity,this.stateInNamedEntity(e))}stateInNamedEntity(e){if(this.entityExcess+=1,this.trieIndex=Dr(this.entityTrie,this.trieCurrent,this.trieIndex+1,e),this.trieIndex<0)this.emitNamedEntity(),this.index--;else{this.trieCurrent=this.entityTrie[this.trieIndex];var t=this.trieCurrent&Rr.VALUE_LENGTH;if(t)if(t=(t>>14)-1,this.allowLegacyEntity()||e===na.Semi){const e=this.index-this.entityExcess+1;e>this.sectionStart&&this.emitPartial(this.sectionStart,e),this.entityResult=this.trieIndex,this.trieIndex+=t,this.entityExcess=0,this.sectionStart=this.index+1,0==t&&this.emitNamedEntity()}else this.trieIndex+=t}}emitNamedEntity(){if(this.state=this.baseState,0!==this.entityResult)switch((this.entityTrie[this.entityResult]&Rr.VALUE_LENGTH)>>14){case 1:this.emitCodePoint(this.entityTrie[this.entityResult]&~Rr.VALUE_LENGTH);break;case 2:this.emitCodePoint(this.entityTrie[this.entityResult+1]);break;case 3:this.emitCodePoint(this.entityTrie[this.entityResult+1]),this.emitCodePoint(this.entityTrie[this.entityResult+2])}}stateBeforeNumericEntity(e){(32|e)===na.LowerX?(this.entityExcess++,this.state=ra.InHexEntity):(this.state=ra.InNumericEntity,this.stateInNumericEntity(e))}emitNumericEntity(e){var t=this.index-this.entityExcess-1;2+t+Number(this.state===ra.InHexEntity)!==this.index&&(t>this.sectionStart&&this.emitPartial(this.sectionStart,t),this.sectionStart=this.index+Number(e),this.emitCodePoint(xr(this.entityResult))),this.state=this.baseState}stateInNumericEntity(e){e===na.Semi?this.emitNumericEntity(!0):ca(e)?(this.entityResult=10*this.entityResult+(e-na.Zero),this.entityExcess++):(this.allowLegacyEntity()?this.emitNumericEntity(!1):this.state=this.baseState,this.index--)}stateInHexEntity(e){e===na.Semi?this.emitNumericEntity(!0):ca(e)?(this.entityResult=16*this.entityResult+(e-na.Zero),this.entityExcess++):function(e){return e>=na.UpperA&&e<=na.UpperF||e>=na.LowerA&&e<=na.LowerF}(e)?(this.entityResult=16*this.entityResult+((32|e)-na.LowerA+10),this.entityExcess++):(this.allowLegacyEntity()?this.emitNumericEntity(!1):this.state=this.baseState,this.index--)}allowLegacyEntity(){return!this.xmlMode&&(this.baseState===ra.Text||this.baseState===ra.InSpecialTag)}cleanup(){this.running&&this.sectionStart!==this.index&&(this.state===ra.Text||this.state===ra.InSpecialTag&&0===this.sequenceIndex?(this.cbs.ontext(this.sectionStart,this.index),this.sectionStart=this.index):this.state!==ra.InAttributeValueDq&&this.state!==ra.InAttributeValueSq&&this.state!==ra.InAttributeValueNq||(this.cbs.onattribdata(this.sectionStart,this.index),this.sectionStart=this.index))}shouldContinue(){return this.index=this.buffers[0].length;)this.shiftBuffer();let n=this.buffers[0].slice(e-this.bufferOffset,t-this.bufferOffset);for(;t-this.bufferOffset>this.buffers[0].length;)this.shiftBuffer(),n+=this.buffers[0].slice(0,t-this.bufferOffset);return n}shiftBuffer(){this.bufferOffset+=this.buffers[0].length,this.writeIndex--,this.buffers.shift()}write(e){var t,n;this.ended?null!=(n=(t=this.cbs).onerror)&&n.call(t,new Error(".write() after done!")):(this.buffers.push(e),this.tokenizer.running&&(this.tokenizer.write(e),this.writeIndex++))}end(e){var t,n;this.ended?null!=(n=(t=this.cbs).onerror)&&n.call(t,new Error(".end() after done!")):(e&&this.write(e),this.ended=!0,this.tokenizer.end())}pause(){this.tokenizer.pause()}resume(){for(this.tokenizer.resume();this.tokenizer.running&&this.writeIndex(t=this._getPreferredOutput(t),a&&Array.isArray(t)?e=e.concat(t):e.push(t),e),[]):this._getPreferredOutput(e[0]):o?[]:void 0},Fa.prototype._getPreferredOutput=function(e){const t=this.currResultType;switch(t){case"all":{const t=Array.isArray(e.path)?e.path:Fa.toPathArray(e.path);return e.pointer=Fa.toPointer(t),e.path="string"==typeof e.path?e.path:Fa.toPathString(e.path),e}case"value":case"parent":case"parentProperty":return e[t];case"path":return Fa.toPathString(e[t]);case"pointer":return Fa.toPointer(e.path);default:throw new TypeError("Unknown result type")}},Fa.prototype._handleCallback=function(e,t,n){var r;t&&(r=this._getPreferredOutput(e),e.path="string"==typeof e.path?e.path:Fa.toPathString(e.path),t(r,n,e))},Fa.prototype._trace=function(e,t,n,r,i,s,a,o){let c;if(!e.length)return c={path:n,value:t,parent:r,parentProperty:i,hasArrExpr:a},this._handleCallback(c,s,"value"),c;const u=e[0],l=e.slice(1),h=[];function f(e){Array.isArray(e)?e.forEach(e=>{h.push(e)}):h.push(e)}if(("string"!=typeof u||o)&&t&&xa.call(t,u))f(this._trace(l,t[u],Ma(n,u),t,u,s,a));else if("*"===u)this._walk(t,e=>{f(this._trace(l,t[e],Ma(n,e),t,e,s,!0,!0))});else if(".."===u)f(this._trace(l,t,n,r,i,s,a)),this._walk(t,r=>{"object"==typeof t[r]&&f(this._trace(e.slice(),t[r],Ma(n,r),t,r,s,!0))});else{if("^"===u)return this._hasParentSelector=!0,{path:n.slice(0,-1),expr:l,isParentSelector:!0};if("~"===u)return c={path:Ma(n,u),value:i,parent:r,parentProperty:null},this._handleCallback(c,s,"property"),c;if("$"===u)f(this._trace(l,t,n,null,null,s,a));else if(/^(-?\d*):(-?\d*):?(\d*)$/u.test(u))f(this._slice(u,l,t,n,r,i,s));else if(0===u.indexOf("?(")){if(this.currPreventEval)throw new Error("Eval [?(expr)] prevented in JSONPath expression.");const e=u.replace(/^\?\((.*?)\)$/u,"$1");this._walk(t,a=>{this._eval(e,t[a],a,n,r,i)&&f(this._trace(l,t[a],Ma(n,a),t,a,s,!0))})}else if("("===u[0]){if(this.currPreventEval)throw new Error("Eval [(expr)] prevented in JSONPath expression.");f(this._trace(Pa(this._eval(u,t,n[n.length-1],n.slice(0,-1),r,i),l),t,n,r,i,s,a))}else if("@"===u[0]){let e=!1;const a=u.slice(1,-2);switch(a){case"scalar":t&&["object","function"].includes(typeof t)||(e=!0);break;case"boolean":case"string":case"undefined":case"function":typeof t===a&&(e=!0);break;case"integer":!Number.isFinite(t)||t%1||(e=!0);break;case"number":Number.isFinite(t)&&(e=!0);break;case"nonFinite":"number"!=typeof t||Number.isFinite(t)||(e=!0);break;case"object":t&&typeof t===a&&(e=!0);break;case"array":Array.isArray(t)&&(e=!0);break;case"other":e=this.currOtherTypeCallback(t,n,r,i);break;case"null":null===t&&(e=!0);break;default:throw new TypeError("Unknown value type "+a)}if(e)return c={path:n,value:t,parent:r,parentProperty:i},this._handleCallback(c,s,"value"),c}else if("`"===u[0]&&t&&xa.call(t,u.slice(1))){const e=u.slice(1);f(this._trace(l,t[e],Ma(n,e),t,e,s,a,!0))}else if(u.includes(",")){const e=u.split(",");for(const a of e)f(this._trace(Pa(a,l),t,n,r,i,s,!0))}else!o&&t&&xa.call(t,u)&&f(this._trace(l,t[u],Ma(n,u),t,u,s,a,!0))}if(this._hasParentSelector)for(let e=0;e{t(e)})},Fa.prototype._slice=function(e,t,n,r,i,s,a){if(Array.isArray(n)){var o=n.length,u=(e=e.split(":"))[2]&&Number.parseInt(e[2])||1,l=e[0]&&Number.parseInt(e[0])||0,h=e[1]&&Number.parseInt(e[1])||o,l=l<0?Math.max(0,l+o):Math.min(o,l),h=h<0?Math.max(0,h+o):Math.min(o,h);const f=[];for(let e=l;e{f.push(e)});return f}},Fa.prototype._eval=function(e,t,n,r,i,s){if(this.currSandbox._$_parentProperty=s,this.currSandbox._$_parent=i,this.currSandbox._$_property=n,this.currSandbox._$_root=this.json,this.currSandbox._$_v=t,(s=e.includes("@path"))&&(this.currSandbox._$_path=Fa.toPathString(r.concat([n]))),!Fa.cache[i="script:"+e]){let t=e.replace(/@parentProperty/gu,"_$_parentProperty").replace(/@parent/gu,"_$_parent").replace(/@property/gu,"_$_property").replace(/@root/gu,"_$_root").replace(/@([.\s)[])/gu,"_$_v$1");s&&(t=t.replace(/@path/gu,"_$_path")),Fa.cache[i]=new this.vm.Script(t)}try{return Fa.cache[i].runInNewContext(this.currSandbox)}catch(t){throw new Error("jsonPath: "+t.message+": "+e)}},Fa.cache={},Fa.toPathString=function(e){var t=e,n=t.length;let r="$";for(let e=1;e":">"},i=/[&"'<>]/g,s=e.exports={};function a(e,t){return n.hasOwnProperty.call(e,t)}function o(e){return r[e]}function c(e,t,n){var r,i,a,s,o;return e instanceof Error&&(e=(i=e).name+": "+i.message),Object.setPrototypeOf?(r=new Error(e),Object.setPrototypeOf(r,c.prototype)):(r=this,Object.defineProperty(r,"message",{enumerable:!1,writable:!0,value:e})),Object.defineProperty(r,"name",{value:"Template render error"}),Error.captureStackTrace&&Error.captureStackTrace(r,this.constructor),s=i?(a=Object.getOwnPropertyDescriptor(i,"stack"))&&(a.get||function(){return a.value})||function(){return i.stack}:(o=new Error(e).stack,function(){return o}),Object.defineProperty(r,"stack",{get:function(){return s.call(r)}}),Object.defineProperty(r,"cause",{value:i}),r.lineno=t,r.colno=n,r.firstUpdate=!0,r.Update=function(e){return e="("+(e||"unknown path")+")",this.firstUpdate&&(this.lineno&&this.colno?e+=" [Line "+this.lineno+", Column "+this.colno+"]":this.lineno&&(e+=" [Line "+this.lineno+"]")),e+="\n ",this.firstUpdate&&(e+=" "),this.message=e+(this.message||""),this.firstUpdate=!1,this},r}function u(e){return"[object Function]"===n.toString.call(e)}function l(e){return"[object Array]"===n.toString.call(e)}function h(e){return"[object String]"===n.toString.call(e)}function f(e){return"[object Object]"===n.toString.call(e)}function p(e){var t=function(e){return e?"string"==typeof e?e.split("."):[e]:[]}(e);return function(e){for(var n=e,r=0;rr&&(a.warned=!0,(r=new Error("Possible EventEmitter memory leak detected. "+a.length+" "+t+" listeners added. Use emitter.setMaxListeners() to increase limit")).name="MaxListenersExceededWarning",r.emitter=e,r.type=t,r.count=a.length,function(e){"function"==typeof console.warn?console.warn(e):console.log(e)}(r))):(a=s[t]=n,++e._eventsCount),e}function mo(e,t,n){var r=!1;function i(){e.removeListener(t,i),r||(r=!0,n.apply(e,arguments))}return i.listener=n,i}function _o(e){var t=this._events;if(t){if("function"==typeof(t=t[e]))return 1;if(t)return t.length}return 0}function Eo(e,t){for(var n=new Array(t);t--;)n[t]=e[t];return n}function To(){var e;ho.call(this),this.__emitError=(e=this,function(t){e.emit("error",t)})}function Ao(){return new To}lo.prototype=Object.create(null),(ho.EventEmitter=ho).usingDomains=!1,ho.prototype.domain=void 0,ho.prototype._events=void 0,ho.prototype._maxListeners=void 0,ho.defaultMaxListeners=10,ho.init=function(){this.domain=null,ho.usingDomains&&(void 0).active,this._events&&this._events!==Object.getPrototypeOf(this)._events||(this._events=new lo,this._eventsCount=0),this._maxListeners=this._maxListeners||void 0},ho.prototype.setMaxListeners=function(e){if("number"!=typeof e||e<0||isNaN(e))throw new TypeError('"n" argument must be a positive number');return this._maxListeners=e,this},ho.prototype.getMaxListeners=function(){return fo(this)},ho.prototype.emit=function(e){var n,r,i,s,a,c="error"===e;if(a=this._events)c=c&&null==a.error;else if(!c)return!1;if(o=this.domain,c){if(c=arguments[1],o)return(c=c||new Error('Uncaught, unspecified "error" event')).domainEmitter=this,c.domain=o,c.domainThrown=!1,o.emit("error",c),!1;if(c instanceof Error)throw c;var o=new Error('Uncaught, unspecified "error" event. ('+c+")");throw o.context=c,o}if(!(n=a[e]))return!1;var l="function"==typeof n;switch(r=arguments.length){case 1:!function(e,n){if(l)e.call(n);else for(var r=e.length,i=Eo(e,r),s=0;s=","//","**"],u=r+this.current();switch(-1!==Ua.indexOf(c,u)&&(this.forward(),-1!==Ua.indexOf(c,(r=u)+this.current()))&&(r=u+this.current(),this.forward()),r){case"(":o=Ko;break;case")":o=Wo;break;case"[":o=Vo;break;case"]":o=$o;break;case"{":o=Qo;break;case"}":o=zo;break;case",":o=Zo;break;case":":o=Jo;break;case"~":o=ec;break;case"|":o=tc;break;default:o=Xo}return oc(o,r,t,n)}if((e=this._extractUntil(Mo+Po)).match(/^[-+]?[0-9]+$/))return"."===this.current()?(this.forward(),c=this._extract("0123456789"),oc(nc,e+"."+c,t,n)):oc("int",e,t,n);if(e.match(/^(true|false)$/))return oc(rc,e,t,n);if("none"===e)return oc(ic,e,t,n);if("null"===e)return oc(ic,e,t,n);if(e)return oc(sc,e,t,n);throw new Error("Unexpected value while parsing: "+e)}var h,f=this.tags.BLOCK_START.charAt(0)+this.tags.VARIABLE_START.charAt(0)+this.tags.COMMENT_START.charAt(0)+this.tags.COMMENT_END.charAt(0);if(this.isFinished())return null;if(e=(e=this._extractString(this.tags.BLOCK_START+"-"))||this._extractString(this.tags.BLOCK_START))return this.in_code=!0,oc(Ho,e,t,n);if(e=(e=this._extractString(this.tags.VARIABLE_START+"-"))||this._extractString(this.tags.VARIABLE_START))return this.in_code=!0,oc(jo,e,t,n);var e="",p=!1;for(this._matches(this.tags.COMMENT_START)&&(p=!0,e=this._extractString(this.tags.COMMENT_START));null!==(h=this._extractUntil(f));){if(e+=h,(this._matches(this.tags.BLOCK_START)||this._matches(this.tags.VARIABLE_START)||this._matches(this.tags.COMMENT_START))&&!p){if(this.lstripBlocks&&this._matches(this.tags.BLOCK_START)&&0this.len?null:this.str.slice(this.index,this.index+e.length)===e},t._extractString=function(e){return this._matches(e)?(this.forwardN(e.length),e):null},t._extractUntil=function(e){return this._extractMatching(!0,e||"")},t._extract=function(e){return this._extractMatching(!1,e)},t._extractMatching=function(e,t){if(this.isFinished())return null;var n=t.indexOf(this.current());if(e&&-1===n||!e&&-1!==n){var r=this.current();this.forward();for(var i=t.indexOf(this.current());(e&&-1===i||!e&&-1!==i)&&!this.isFinished();)r+=this.current(),this.forward(),i=t.indexOf(this.current());return r}return""},t._extractRegex=function(e){return(e=this.currentStr().match(e))?(this.forwardN(e[0].length),e):null},t.isFinished=function(){return this.index>=this.len},t.forwardN=function(e){for(var t=0;t",n+2),e(t,n+4)}))}};function Iu(e,t){return(Iu=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e})(e,t)}bu.Node,bu.Root,bu.NodeList,bu.Value,bu.Literal,bu.Group,bu.Pair,bu.Dict,bu.Output,bu.Capture,bu.TemplateData,bu.If,bu.IfAsync,bu.InlineIf,bu.For,bu.AsyncEach,bu.AsyncAll,bu.Macro,bu.Caller,bu.Import,bu.FromImport,bu.FunCall,bu.Filter,bu.FilterAsync,bu.KeywordArgs,bu.Block,bu.Super,bu.Extends,bu.Include,bu.Switch,bu.Case,bu.LookupVal,bu.BinOp,bu.In,bu.Is,bu.Or,bu.And,bu.Not,bu.Add,bu.Concat,bu.Sub,bu.Mul,bu.Div,bu.FloorDiv,bu.Mod,bu.Pow,bu.Neg,bu.Pos,bu.Compare,bu.CompareOperand,bu.CallExtension,bu.CallExtensionAsync,bu.printNodes;var Ou=function(e){var n;function r(){return e.apply(this,arguments)||this}n=e,(t=r).prototype=Object.create(n.prototype),Iu(t.prototype.constructor=t,n);var t=r.prototype;return t.init=function(e){this.tokens=e,this.peeked=null,this.breakOnBlocks=null,this.dropLeadingWhitespace=!1,this.extensions=[]},t.nextToken=function(e){var t;if(this.peeked){if(e||this.peeked.type!==uc.TOKEN_WHITESPACE)return t=this.peeked,this.peeked=null,t;this.peeked=null}if(t=this.tokens.nextToken(),!e)for(;t&&t.type===uc.TOKEN_WHITESPACE;)t=this.tokens.nextToken();return t},t.peekToken=function(){return this.peeked=this.peeked||this.nextToken(),this.peeked},t.pushToken=function(e){if(this.peeked)throw new Error("pushToken: can only push one token on between reads");this.peeked=e},t.error=function(e,t,n){var r;return void 0!==t&&void 0!==n||(t=(r=this.peekToken()||{}).lineno,n=r.colno),void 0!==t&&(t+=1),void 0!==n&&(n+=1),new Ua.TemplateError(e,t,n)},t.fail=function(e,t,n){throw this.error(e,t,n)},t.skip=function(e){var t=this.nextToken();return!(!t||t.type!==e)||(this.pushToken(t),!1)},t.expect=function(e){var t=this.nextToken();return t.type!==e&&this.fail("expected "+e+", got "+t.type,t.lineno,t.colno),t},t.skipValue=function(e,t){var n=this.nextToken();return!(!n||n.type!==e||n.value!==t)||(this.pushToken(n),!1)},t.skipSymbol=function(e){return this.skipValue(uc.TOKEN_SYMBOL,e)},t.advanceAfterBlockEnd=function(e){var t;return e||((t=this.peekToken())||this.fail("unexpected end of file"),t.type!==uc.TOKEN_SYMBOL&&this.fail("advanceAfterBlockEnd: expected symbol token or explicit name to be passed"),e=this.nextToken().value),(t=this.nextToken())&&t.type===uc.TOKEN_BLOCK_END?"-"===t.value.charAt(0)&&(this.dropLeadingWhitespace=!0):this.fail("expected block end in "+e+" statement"),t},t.advanceAfterVariableEnd=function(){var e=this.nextToken();e&&e.type===uc.TOKEN_VARIABLE_END?this.dropLeadingWhitespace="-"===e.value.charAt(e.value.length-this.tokens.tags.VARIABLE_END.length-1):(this.pushToken(e),this.fail("expected variable end"))},t.parseFor=function(){var e,t,n=this.peekToken();if(this.skipSymbol("for")?(e=new bu.For(n.lineno,n.colno),t="endfor"):this.skipSymbol("asyncEach")?(e=new bu.AsyncEach(n.lineno,n.colno),t="endeach"):this.skipSymbol("asyncAll")?(e=new bu.AsyncAll(n.lineno,n.colno),t="endall"):this.fail("parseFor: expected for{Async}",n.lineno,n.colno),e.name=this.parsePrimary(),e.name instanceof bu.Symbol||this.fail("parseFor: variable name expected for loop"),this.peekToken().type===uc.TOKEN_COMMA){var r=e.name;for(e.name=new bu.Array(r.lineno,r.colno),e.name.addChild(r);this.skip(uc.TOKEN_COMMA);){var i=this.parsePrimary();e.name.addChild(i)}}return this.skipSymbol("in")||this.fail('parseFor: expected "in" keyword for loop',n.lineno,n.colno),e.arr=this.parseExpression(),this.advanceAfterBlockEnd(n.value),e.body=this.parseUntilBlocks(t,"else"),this.skipSymbol("else")&&(this.advanceAfterBlockEnd("else"),e.else_=this.parseUntilBlocks(t)),this.advanceAfterBlockEnd(),e},t.parseMacro=function(){var e=this.peekToken(),t=(this.skipSymbol("macro")||this.fail("expected macro"),this.parsePrimary(!0)),n=this.parseSignature(),t=new bu.Macro(e.lineno,e.colno,t,n);return this.advanceAfterBlockEnd(e.value),t.body=this.parseUntilBlocks("endmacro"),this.advanceAfterBlockEnd(),t},t.parseCall=function(){var e=this.peekToken(),t=(this.skipSymbol("call")||this.fail("expected call"),this.parseSignature(!0)||new bu.NodeList),n=this.parsePrimary(),r=(this.advanceAfterBlockEnd(e.value),this.parseUntilBlocks("endcall")),i=(this.advanceAfterBlockEnd(),new bu.Symbol(e.lineno,e.colno,"caller")),t=new bu.Caller(e.lineno,e.colno,i,t,r);return(r=n.args.children)[r.length-1]instanceof bu.KeywordArgs||r.push(new bu.KeywordArgs),r[r.length-1].addChild(new bu.Pair(e.lineno,e.colno,i,t)),new bu.Output(e.lineno,e.colno,[n])},t.parseWithContext=function(){var e=this.peekToken(),t=null;return this.skipSymbol("with")?t=!0:this.skipSymbol("without")&&(t=!1),null===t||this.skipSymbol("context")||this.fail("parseFrom: expected context after with/without",e.lineno,e.colno),t},t.parseImport=function(){var e=this.peekToken(),t=(this.skipSymbol("import")||this.fail("parseImport: expected import",e.lineno,e.colno),this.parseExpression()),n=(this.skipSymbol("as")||this.fail('parseImport: expected "as" keyword',e.lineno,e.colno),this.parseExpression()),r=this.parseWithContext(),t=new bu.Import(e.lineno,e.colno,t,n,r);return this.advanceAfterBlockEnd(e.value),t},t.parseFrom=function(){var e=this.peekToken(),t=(this.skipSymbol("from")||this.fail("parseFrom: expected from"),this.parseExpression());this.skipSymbol("import")||this.fail("parseFrom: expected import",e.lineno,e.colno);for(var n,r=new bu.NodeList;;){var a,i=this.peekToken();if(i.type===uc.TOKEN_BLOCK_END){r.children.length||this.fail("parseFrom: Expected at least one import name",e.lineno,e.colno),"-"===i.value.charAt(0)&&(this.dropLeadingWhitespace=!0),this.nextToken();break}0","<=",">="],t=this.parseConcat(),n=[];;){var r=this.nextToken();if(!r)break;if(-1===e.indexOf(r.value)){this.pushToken(r);break}n.push(new bu.CompareOperand(r.lineno,r.colno,this.parseConcat(),r.value))}return n.length?new bu.Compare(n[0].lineno,n[0].colno,t,n):t},t.parseConcat=function(){for(var e=this.parseAdd();this.skipValue(uc.TOKEN_TILDE,"~");)var t=this.parseAdd(),e=new bu.Concat(e.lineno,e.colno,e,t);return e},t.parseAdd=function(){for(var e=this.parseSub();this.skipValue(uc.TOKEN_OPERATOR,"+");)var t=this.parseSub(),e=new bu.Add(e.lineno,e.colno,e,t);return e},t.parseSub=function(){for(var e=this.parseMul();this.skipValue(uc.TOKEN_OPERATOR,"-");)var t=this.parseMul(),e=new bu.Sub(e.lineno,e.colno,e,t);return e},t.parseMul=function(){for(var e=this.parseDiv();this.skipValue(uc.TOKEN_OPERATOR,"*");)var t=this.parseDiv(),e=new bu.Mul(e.lineno,e.colno,e,t);return e},t.parseDiv=function(){for(var e=this.parseFloorDiv();this.skipValue(uc.TOKEN_OPERATOR,"/");)var t=this.parseFloorDiv(),e=new bu.Div(e.lineno,e.colno,e,t);return e},t.parseFloorDiv=function(){for(var e=this.parseMod();this.skipValue(uc.TOKEN_OPERATOR,"//");)var t=this.parseMod(),e=new bu.FloorDiv(e.lineno,e.colno,e,t);return e},t.parseMod=function(){for(var e=this.parsePow();this.skipValue(uc.TOKEN_OPERATOR,"%");)var t=this.parsePow(),e=new bu.Mod(e.lineno,e.colno,e,t);return e},t.parsePow=function(){for(var e=this.parseUnary();this.skipValue(uc.TOKEN_OPERATOR,"**");)var t=this.parseUnary(),e=new bu.Pow(e.lineno,e.colno,e,t);return e},t.parseUnary=function(e){var n=this.peekToken(),n=this.skipValue(uc.TOKEN_OPERATOR,"-")?new bu.Neg(n.lineno,n.colno,this.parseUnary(!0)):this.skipValue(uc.TOKEN_OPERATOR,"+")?new bu.Pos(n.lineno,n.colno,this.parseUnary(!0)):this.parsePrimary();return e?n:this.parseFilter(n)},t.parsePrimary=function(e){var t,n=this.nextToken();if(n?n.type===uc.TOKEN_STRING?t=n.value:n.type===uc.TOKEN_INT?t=parseInt(n.value,10):n.type===uc.TOKEN_FLOAT?t=parseFloat(n.value):n.type===uc.TOKEN_BOOLEAN?"true"===n.value?t=!0:"false"===n.value?t=!1:this.fail("invalid boolean: "+n.value,n.lineno,n.colno):n.type===uc.TOKEN_NONE?t=null:n.type===uc.TOKEN_REGEX&&(t=new RegExp(n.value.body,n.value.flags)):this.fail("expected expression, got end of file"),t=void 0!==t?new bu.Literal(n.lineno,n.colno,t):n.type===uc.TOKEN_SYMBOL?new bu.Symbol(n.lineno,n.colno,n.value):(this.pushToken(n),this.parseAggregate()),t=e?t:this.parsePostfix(t))return t;throw this.error("unexpected token: "+n.value,n.lineno,n.colno)},t.parseFilterName=function(){for(var e=this.expect(uc.TOKEN_SYMBOL),t=e.value;this.skipValue(uc.TOKEN_OPERATOR,".");)t+="."+this.expect(uc.TOKEN_SYMBOL).value;return new bu.Symbol(e.lineno,e.colno,t)},t.parseFilterArgs=function(e){return this.peekToken().type===uc.TOKEN_LEFT_PAREN?this.parsePostfix(e).args.children:[]},t.parseFilter=function(e){for(;this.skip(uc.TOKEN_PIPE);){var t=this.parseFilterName();e=new bu.Filter(t.lineno,t.colno,t,new bu.NodeList(t.lineno,t.colno,[e].concat(this.parseFilterArgs(e))))}return e},t.parseFilterStatement=function(){var e=this.peekToken(),t=(this.skipSymbol("filter")||this.fail("parseFilterStatement: expected filter"),this.parseFilterName()),n=this.parseFilterArgs(t),e=(this.advanceAfterBlockEnd(e.value),new bu.Capture(t.lineno,t.colno,this.parseUntilBlocks("endfilter"))),e=(this.advanceAfterBlockEnd(),new bu.Filter(t.lineno,t.colno,t,new bu.NodeList(t.lineno,t.colno,[e].concat(n))));return new bu.Output(t.lineno,t.colno,[e])},t.parseAggregate=function(){var e,t=this.nextToken();switch(t.type){case uc.TOKEN_LEFT_PAREN:e=new bu.Group(t.lineno,t.colno);break;case uc.TOKEN_LEFT_BRACKET:e=new bu.Array(t.lineno,t.colno);break;case uc.TOKEN_LEFT_CURLY:e=new bu.Dict(t.lineno,t.colno);break;default:return null}for(;;){var i,n=this.peekToken().type;if(n===uc.TOKEN_RIGHT_PAREN||n===uc.TOKEN_RIGHT_BRACKET||n===uc.TOKEN_RIGHT_CURLY){this.nextToken();break}0e.length)a=i.slice(0,e.length),i.slice(a.length,o).forEach(function(e,n){n":">","<=":"<=",">=":">="},Qu=function(e){var n;function r(){return e.apply(this,arguments)||this}n=e,(t=r).prototype=Object.create(n.prototype),Ku(t.prototype.constructor=t,n);var t=r.prototype;return t.init=function(e,t){this.templateName=e,this.codebuf=[],this.lastId=0,this.buffer=null,this.bufferStack=[],this._scopeClosers="",this.inBlock=!1,this.throwOnUndefined=t},t.fail=function(e,t,n){throw void 0!==t&&(t+=1),void 0!==n&&(n+=1),new Wu(e,t,n)},t._pushBuffer=function(){var e=this._tmpid();return this.bufferStack.push(this.buffer),this.buffer=e,this._emit("var "+this.buffer+' = "";'),e},t._popBuffer=function(){this.buffer=this.bufferStack.pop()},t._emit=function(e){this.codebuf.push(e)},t._emitLine=function(e){this._emit(e+"\n")},t._emitLines=function(){for(var e=this,t=arguments.length,n=new Array(t),r=0;r=(t=t||80)?e:(t-=e.length,i=Ua.repeat(" ",t/2-t%2),t=Ua.repeat(" ",t/2),Yu.copySafeness(e,i+e+t))},e.default=function(e,t,n){return n?e||t:void 0!==e?e:t},e.dictsort=function(e,t,n){if(!Ua.isObject(e))throw new Ua.TemplateError("dictsort filter: val must be an object");var r,s,i=[];for(s in e)i.push([s,e[s]]);if(void 0===n||"key"===n)r=0;else{if("value"!==n)throw new Ua.TemplateError("dictsort filter: You can only sort by either key or value");r=1}return i.sort(function(e,n){return e=e[r],n=n[r],t||(Ua.isString(e)&&(e=e.toUpperCase()),Ua.isString(n)&&(n=n.toUpperCase())),n\n"))},e.random=function(e){return e[Math.floor(Math.random()*e.length)]},e.reject=a(!1),e.rejectattr=function(e,t){return e.filter(function(e){return!e[t]})},e.select=a(!0),e.selectattr=function(e,t){return e.filter(function(e){return!!e[t]})},e.replace=function(e,t,n,r){var i=e;if(t instanceof RegExp)return e.replace(t,n);void 0===r&&(r=-1);var s="";if("number"==typeof t)t=""+t;else if("string"!=typeof t)return e;if("string"!=typeof(e="number"==typeof e?""+e:e)&&!(e instanceof Yu.SafeString))return e;if(""===t)return s=n+e.split("").join(n)+n,Yu.copySafeness(e,s);var a=e.indexOf(t);if(0===r||-1===a)return e;for(var o=0,c=0;-1]*>|/gi,"")),t=t?r.replace(/^ +| +$/gm,"").replace(/ +/g," ").replace(/(\r\n)/g,"\n").replace(/\n\n\n+/g,"\n\n"):r.replace(/\s+/gi," ");return Yu.copySafeness(e,t)},e.title=function(e){var t=(e=n(e,"")).split(" ").map(i);return Yu.copySafeness(e,t.join(" "))},e.trim=o,e.truncate=function(e,t,r,i){var s=e;return(e=n(e,"")).length<=(t=t||255)?e:(e=r?e.substring(0,t):(-1===(r=e.lastIndexOf(" ",t))&&(r=t),e.substring(0,r)),Yu.copySafeness(s,e+=null!=i?i:"..."))},e.upper=function(e){return(e=n(e,"")).toUpperCase()},e.urlencode=function(e){var t=encodeURIComponent;return Ua.isString(e)?t(e):(Ua.isArray(e)?e:Ua._entries(e)).map(function(e){var n=e[0],e=e[1];return t(n)+"="+t(e)}).join("&")};var c=/^(?:\(|<|<)?(.*?)(?:\.|,|\)|\n|>)?$/,u=/^[\w.!#$%&'*+\-\/=?\^`{|}~]+@[a-z\d\-]+(\.[a-z\d\-]+)+$/i,l=/^https?:\/\/.*$/,h=/^www\./,f=/\.(?:org|net|com)(?:\:|\/|$)/,p=(e.urlize=function(e,t,n){r(t)&&(t=1/0);var i=!0===n?' rel="nofollow"':"";return e.split(/(\s+)/).filter(function(e){return e&&e.length}).map(function(e){var n=e.match(c),s=(n=n?n[1]:e).substr(0,t);return l.test(n)?'"+s+"":h.test(n)?'"+s+"":u.test(n)?''+n+"":f.test(n)?'"+s+"":e}).join("")},e.wordcount=function(e){return(e=(e=n(e,""))?e.match(/\w+/g):null)?e.length:null},e.float=function(e,t){return r(e=parseFloat(e))?t:e},Yu.makeMacro(["value","default","base"],[],function(e,t,n){return void 0===n&&(n=10),r(e=parseInt(e,n))?t:e}));e.int=p,e.d=e.default,e.e=e.escape}),Zu={};function Ju(e,t){for(var n=0,r=e.length-1;0<=r;r--){var i=e[r];"."===i?e.splice(r,1):".."===i?(e.splice(r,1),n++):n&&(e.splice(r,1),n--)}if(t)for(;n--;)e.unshift("..");return e}var el=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/,tl=function(e){return el.exec(e).slice(1)};function nl(){for(var e="",t=!1,n=arguments.length-1;-1<=n&&!t;n--){var r=0<=n?arguments[n]:"/";if("string"!=typeof r)throw new TypeError("Arguments to path.resolve must be strings");r&&(e=r+"/"+e,t="/"===r.charAt(0))}return(t?"/":"")+Ju(al(e.split("/"),function(e){return!!e}),!t).join("/")||"."}function rl(e){var t=il(e),n="/"===ol(e,-1);return(e=(e=Ju(al(e.split("/"),function(e){return!!e}),!t).join("/"))||t?e:".")&&n&&(e+="/"),(t?"/":"")+e}function il(e){return"/"===e.charAt(0)}var sl={extname:function(e){return tl(e)[3]},basename:function(e,t){return e=tl(e)[2],t&&e.substr(-1*t.length)===t?e.substr(0,e.length-t.length):e},dirname:function(e){var n=(e=tl(e))[0],e=e[1];return n||e?n+(e&&e.substr(0,e.length-1)):"."},sep:"/",delimiter:":",relative:function(e,t){function n(e){for(var t=0;t=e.length&&(t=0),this.current=e[t],this.current}};var e,t},joiner:function(e){e=e||",";var t=!0;return function(){var n=t?"":e;return t=!1,n}}}});function Al(e,t){e.prototype=Object.create(t.prototype),gl(e.prototype.constructor=e,t)}function gl(e,t){return(gl=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e})(e,t)}var vl=_l.FileSystemLoader,yl=_l.WebLoader,Sl=_l.PrecompiledLoader,Cl=Ec.Obj,Nl=Ec.EmitterObj,bl=Yu.handleError,Il=Yu.Frame;function Ol(e,t,n){Ro(function(){e(t,n)})}var kl={type:"code",obj:{root:function(e,t,n,r,i){try{i(null,"")}catch(e){i(bl(e,null,null))}}}},Ll=function(e){function t(){return e.apply(this,arguments)||this}Al(t,e);var n=t.prototype;return n.init=function(e,t){var n=this;t=this.opts=t||{},this.opts.dev=!!t.dev,this.opts.autoescape=null==t.autoescape||t.autoescape,this.opts.throwOnUndefined=!!t.throwOnUndefined,this.opts.trimBlocks=!!t.trimBlocks,this.opts.lstripBlocks=!!t.lstripBlocks,this.loaders=[],e?this.loaders=Ua.isArray(e)?e:[e]:vl?this.loaders=[new vl("views")]:yl&&(this.loaders=[new yl("/views")]),"undefined"!=typeof window&&window.nunjucksPrecompiled&&this.loaders.unshift(new Sl(window.nunjucksPrecompiled)),this._initLoaders(),this.globals=Tl(),this.filters={},this.tests={},this.asyncFilters=[],this.extensions={},this.extensionsList=[],Ua._entries(Xu).forEach(function(e){var t=e[0],e=e[1];return n.addFilter(t,e)}),Ua._entries(El).forEach(function(e){var t=e[0],e=e[1];return n.addTest(t,e)})},n._initLoaders=function(){var e=this;this.loaders.forEach(function(t){t.cache={},"function"==typeof t.on&&(t.on("update",function(n,r){t.cache[n]=null,e.emit("update",n,r,t)}),t.on("load",function(n,r){e.emit("load",n,r,t)}))})},n.invalidateCache=function(){this.loaders.forEach(function(e){e.cache={}})},n.addExtension=function(e,t){return t.__name=e,this.extensions[e]=t,this.extensionsList.push(t),this},n.removeExtension=function(e){var t=this.getExtension(e);t&&(this.extensionsList=Ua.without(this.extensionsList,t),delete this.extensions[e])},n.getExtension=function(e){return this.extensions[e]},n.hasExtension=function(e){return!!this.extensions[e]},n.addGlobal=function(e,t){return this.globals[e]=t,this},n.getGlobal=function(e){if(void 0===this.globals[e])throw new Error("global not found: "+e);return this.globals[e]},n.addFilter=function(e,t,n){return n&&this.asyncFilters.push(e),this.filters[e]=t,this},n.getFilter=function(e){if(this.filters[e])return this.filters[e];throw new Error("filter not found: "+e)},n.addTest=function(e,t){return this.tests[e]=t,this},n.getTest=function(e){if(this.tests[e])return this.tests[e];throw new Error("test not found: "+e)},n.resolveTemplate=function(e,t,n){return e.isRelative&&t&&e.isRelative(n)&&e.resolve?e.resolve(t,n):n},n.getTemplate=function(e,t,n,r,i){var s,a=this,o=this,c=null;if(e&&e.raw&&(e=e.raw),Ua.isFunction(n)&&(i=n,n=null,t=t||!1),Ua.isFunction(t)&&(i=t,t=!1),e instanceof Rl)c=e;else{if("string"!=typeof e)throw new Error("template names must be a string: "+e);for(var u=0;u{var t=["th","st","nd","rd"],n=e%100;return`[${e}${t[(n-20)%10]||t[n]||t[0]}]`}};const th=(e,t,n)=>{var r=String(e);return!r||r.length>=t?e:""+Array(t+1-r.length).join(n)+e},nh=(e,t)=>{var n,r,i;return e.date(){var e=-e.utcOffset(),n=Math.abs(e),r=Math.floor(n/60),n=n%60;return(e<=0?"+":"-")+th(r,2,"0")+":"+th(n,2,"0")},m:nh,a:e=>e<0?Math.ceil(e)||0:Math.floor(e),p:e=>({M:Vl,y:Ql,w:Wl,d:Kl,D:zl,h:Yl,m:ql,s:jl,ms:Gl,Q:$l})[e]||String(e||"").toLowerCase().replace(/s$/,""),u:e=>void 0===e};let ih="en";const sh={},ah=(sh[ih]=eh,e=>e instanceof lh),oh=(e,t,n)=>{let r;if(!e)return ih;if("string"==typeof e){const n=e.toLowerCase();if(sh[n]&&(r=n),t&&(sh[n]=t,r=n),t=e.split("-"),!r&&1ch(e,{locale:t.$L,utc:t.$u,x:t.$x,$offset:t.$offset});class lh{constructor(e){this.$L=oh(e.locale,null,!0),this.parse(e)}parse(e){this.$d=(e=>{const{date:t,utc:n}=e;if(null===t)return new Date(NaN);if(uh.u(t))return new Date;if(!(t instanceof Date||"string"!=typeof t||/Z$/i.test(t))){const e=t.match(Zl);if(e){const t=e[2]-1||0,r=(e[7]||"0").substring(0,3);return n?new Date(Date.UTC(e[1],t,e[3]||1,e[4]||0,e[5]||0,e[6]||0,r)):new Date(e[1],t,e[3]||1,e[4]||0,e[5]||0,e[6]||0,r)}}return new Date(t)})(e),this.$x=e.x||{},this.init()}init(){var e=this.$d;this.$y=e.getFullYear(),this.$M=e.getMonth(),this.$D=e.getDate(),this.$W=e.getDay(),this.$H=e.getHours(),this.$m=e.getMinutes(),this.$s=e.getSeconds(),this.$ms=e.getMilliseconds()}$utils(){return uh}isValid(){return!(this.$d.toString()===Xl)}isSame(e,t){return e=ch(e),this.startOf(t)<=e&&e<=this.endOf(t)}isAfter(e,t){return ch(e)(t=uh.w(this.$u?Date.UTC(this.$y,t,e):new Date(this.$y,t,e),this),n?t:t.endOf(Kl)),s=(e,t)=>uh.w(this.toDate()[e].apply(this.toDate("s"),(n?[0,0,0,0]:[23,59,59,999]).slice(t)),this),{$W:a,$M:o,$D:c}=this,u="set"+(this.$u?"UTC":"");switch(r){case Ql:return n?i(1,0):i(31,11);case Vl:return n?i(1,o):i(0,o+1);case Wl:{const e=this.$locale().weekStart||0,t=(a{var n=ch(this);return uh.w(n.date(n.date()+Math.round(t*e)),this)};return(t=uh.p(t))===Vl?this.set(Vl,this.$M+e):t===Ql?this.set(Ql,this.$y+e):t===Kl?r(1):t===Wl?r(7):(r={[ql]:Ul,[Yl]:Hl,[jl]:Fl}[t]||1,t=this.$d.getTime()+e*r,uh.w(t,this))}subtract(e,t){return this.add(-1*e,t)}format(e){var t=this.$locale();if(!this.isValid())return t.invalidDate||Xl;const n=e||"YYYY-MM-DDTHH:mm:ssZ",r=uh.z(this),{$H:i,$m:s,$M:a}=this,{weekdays:o,months:c,meridiem:u}=t,l=(e,t,r,i)=>e&&(e[t]||e(this,n))||r[t].slice(0,i),h=e=>uh.s(i%12||12,e,"0"),f=u||((e,t,n)=>(e=e<12?"AM":"PM",n?e.toLowerCase():e)),p={YY:String(this.$y).slice(-2),YYYY:this.$y,M:a+1,MM:uh.s(a+1,2,"0"),MMM:l(t.monthsShort,a,c,3),MMMM:l(c,a),D:this.$D,DD:uh.s(this.$D,2,"0"),d:String(this.$W),dd:l(t.weekdaysMin,this.$W,o,2),ddd:l(t.weekdaysShort,this.$W,o,3),dddd:o[this.$W],H:String(i),HH:uh.s(i,2,"0"),h:h(1),hh:h(2),a:f(i,s,!0),A:f(i,s,!1),m:String(s),mm:uh.s(s,2,"0"),s:String(this.$s),ss:uh.s(this.$s,2,"0"),SSS:uh.s(this.$ms,3,"0"),Z:r};return n.replace(Jl,(e,t)=>t||p[e]||r.replace(":",""))}utcOffset(){return 15*-Math.round(this.$d.getTimezoneOffset()/15)}diff(e,t,n){var t=uh.p(t),s=((e=ch(e)).utcOffset()-this.utcOffset())*Ul,a=this-e,e=uh.m(this,e),e={[Ql]:e/12,[Vl]:e,[$l]:e/3,[Wl]:(a-s)/6048e5,[Kl]:(a-s)/864e5,[Yl]:a/Hl,[ql]:a/Ul,[jl]:a/Fl}[t]||a;return n?e:uh.a(e)}daysInMonth(){return this.endOf(Vl).$D}$locale(){return sh[this.$L]}locale(e,t){var n;return e?(n=this.clone(),(e=oh(e,t,!0))&&(n.$L=e),n):this.$L}clone(){return uh.w(this.$d,this)}toDate(){return new Date(this.valueOf())}toJSON(){return this.isValid()?this.toISOString():null}toISOString(){return this.$d.toISOString()}toString(){return this.$d.toUTCString()}}const hh=lh.prototype;ch.prototype=hh,[["$ms",Gl],["$s",jl],["$m",ql],["$H",Yl],["$W",Kl],["$M",Vl],["$y",Ql],["$D",zl]].forEach(e=>{hh[e[1]]=function(t){return this.$g(t,e[0],e[1])}}),ch.extend=(e,t)=>(e.$i||(e(t,lh,ch),e.$i=!0),ch),ch.locale=oh,ch.isDayjs=ah,ch.unix=e=>ch(1e3*e),ch.en=sh[ih],ch.Ls=sh,ch.p={};var fh=Sr(function(e){function r(e){return e&&(e=e.toString().replace(n.pluses,"%20"),e=decodeURIComponent(e)),e}function i(e){var t,i,s,a,o,c,u,l=[];if(null!=e&&""!==e)for(u=(i=(e=(t=0)===e.indexOf("?")?e.substring(1):e).toString().split(n.query_separator)).length;t>>0;if("function"!=typeof e)throw new TypeError(e+" is not a function");for(1"']/g,$=RegExp(W.source),Q=RegExp(V.source),z=/<%-([\s\S]+?)%>/g,X=/<%([\s\S]+?)%>/g,Z=/<%=([\s\S]+?)%>/g,J=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,ee=/^\w*$/,te=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,ne=/[\\^$.*+?()[\]{}|]/g,re=RegExp(ne.source),ie=/^\s+/,se=/\s/,ae=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,oe=/\{\n\/\* \[wrapped with (.+)\] \*/,ce=/,? & /,ue=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g,le=/[()=,{}\[\]\/\s]/,he=/\\(\\)?/g,fe=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,pe=/\w*$/,de=/^[-+]0x[0-9a-f]+$/i,me=/^0b[01]+$/i,_e=/^\[object .+?Constructor\]$/,Ee=/^0o[0-7]+$/i,Te=/^(?:0|[1-9]\d*)$/,Ae=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,ge=/($^)/,ve=/['\n\r\u2028\u2029\\]/g,ye="\\ud800-\\udfff",Se="\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff",Ce="\\u2700-\\u27bf",be="A-Z\\xc0-\\xd6\\xd8-\\xde",Ie="\\ufe0e\\ufe0f",Le="["+ye+"]",De="["+(Oe="\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000")+"]",Re="["+Se+"]",xe="["+Ce+"]",Me="["+(Ne="a-z\\xdf-\\xf6\\xf8-\\xff")+"]",He="[\\ud800-\\udbff][\\udc00-\\udfff]",je="\\u200d",qe="(?:"+Me+"|"+(Oe="[^"+ye+Oe+"\\d+"+Ce+Ne+be+"]")+")",Oe="(?:"+(be="["+be+"]")+"|"+Oe+")",Ke="(?:['’](?:d|ll|m|re|s|t|ve))?",We="(?:['’](?:D|LL|M|RE|S|T|VE))?",$e=($e="["+Ie+"]?")+(Ve="(?:"+Re+"|"+(Ce="\\ud83c[\\udffb-\\udfff]")+")?")+"(?:"+je+"(?:"+[Ne="[^"+ye+"]",Ue="(?:\\ud83c[\\udde6-\\uddff]){2}",He].join("|")+")"+$e+Ve+")*",Ve="(?:"+[xe,Ue,He].join("|")+")"+$e,xe="(?:"+[Ne+Re+"?",Re,Ue,He,Le].join("|")+")",Ze=RegExp("['’]","g"),Je=RegExp(Re,"g"),et=RegExp(Ce+"(?="+Ce+")|"+xe+$e,"g"),tt=RegExp([be+"?"+Me+"+"+Ke+"(?="+[De,be,"$"].join("|")+")",Oe+"+"+We+"(?="+[De,be+qe,"$"].join("|")+")",be+"?"+qe+"+"+Ke,be+"+"+We,"\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])","\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])","\\d+",Ve].join("|"),"g"),nt=RegExp("["+je+ye+Se+Ie+"]"),rt=/[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/,it=["Array","Buffer","DataView","Date","Error","Float32Array","Float64Array","Function","Int8Array","Int16Array","Int32Array","Map","Math","Object","Promise","RegExp","Set","String","Symbol","TypeError","Uint8Array","Uint8ClampedArray","Uint16Array","Uint32Array","WeakMap","_","clearTimeout","isFinite","parseInt","setTimeout"],st=-1,at={},ot=(at[x]=at[M]=at[P]=at[B]=at[F]=at[U]=at[H]=at[G]=at[j]=!0,at[_]=at[E]=at[R]=at[T]=at[w]=at[A]=at[g]=at[v]=at[S]=at[C]=at[N]=at[I]=at[O]=at[k]=at[D]=!1,{}),ct=(ot[_]=ot[E]=ot[R]=ot[w]=ot[T]=ot[A]=ot[x]=ot[M]=ot[P]=ot[B]=ot[F]=ot[S]=ot[C]=ot[N]=ot[I]=ot[O]=ot[k]=ot[L]=ot[U]=ot[H]=ot[G]=ot[j]=!0,ot[g]=ot[v]=ot[D]=!1,{"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"}),ut=parseFloat,lt=parseInt,Ne="object"==typeof gr&&gr&&gr.Object===Object&&gr,Ue="object"==typeof self&&self&&self.Object===Object&&self,pt=Ne||Ue||Function("return this")(),mt=(He=t&&!t.nodeType&&t)&&e&&!e.nodeType&&e,_t=mt&&mt.exports===He,Et=_t&&Ne.process,At=(Le=function(){try{return mt&&mt.require&&mt.require("util").types||Et&&Et.binding&&Et.binding("util")}catch(e){}}())&&Le.isArrayBuffer,gt=Le&&Le.isDate,vt=Le&&Le.isMap,yt=Le&&Le.isRegExp,St=Le&&Le.isSet,Ct=Le&&Le.isTypedArray;function Nt(e,t,n){switch(n.length){case 0:return e.call(t);case 1:return e.call(t,n[0]);case 2:return e.call(t,n[0],n[1]);case 3:return e.call(t,n[0],n[1],n[2])}return e.apply(t,n)}function bt(e,t,n,r){for(var i=-1,s=null==e?0:e.length;++i":">",'"':""","'":"'"});function sn(e){return"\\"+ct[e]}function an(e){return nt.test(e)}function on(e){var t=-1,n=Array(e.size);return e.forEach(function(e,r){n[++t]=[r,e]}),n}function cn(e,t){return function(n){return e(t(n))}}function un(e,t){for(var n=-1,r=e.length,i=0,a=[];++n",""":'"',"'":"'"}),_n=function e(t){var ye=(t=null==t?pt:_n.defaults(pt.Object(),t,_n.pick(pt,it))).Array,Se=t.Date,Ce=t.Error,Ne=t.Function,be=t.Math,Ie=t.Object,Oe=t.RegExp,ke=t.String,Le=t.TypeError,De=ye.prototype,Re=Ne.prototype,we=Ie.prototype,xe=t["__core-js_shared__"],Me=Re.toString,Pe=we.hasOwnProperty,Be=0,Fe=(Re=/[^.]+$/.exec(xe&&xe.keys&&xe.keys.IE_PROTO||""))?"Symbol(src)_1."+Re:"",Ue=we.toString,He=Me.call(Ie),Ge=pt._,je=Oe("^"+Me.call(Pe).replace(ne,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),Re=_t?t.Buffer:n,Ye=t.Symbol,Ke=t.Uint8Array,We=Re?Re.allocUnsafe:n,Ve=cn(Ie.getPrototypeOf,Ie),$e=Ie.create,Qe=we.propertyIsEnumerable,ze=De.splice,Xe=Ye?Ye.isConcatSpreadable:n,et=Ye?Ye.iterator:n,nt=Ye?Ye.toStringTag:n,ct=function(){try{var e=fs(Ie,"defineProperty");return e({},"",{}),e}catch(e){}}(),ht=t.clearTimeout!==pt.clearTimeout&&t.clearTimeout,ft=Se&&Se.now!==pt.Date.now&&Se.now,dt=t.setTimeout!==pt.setTimeout&&t.setTimeout,mt=be.ceil,Et=be.floor,Tt=Ie.getOwnPropertySymbols,Re=Re?Re.isBuffer:n,Wt=t.isFinite,En=De.join,Tn=cn(Ie.keys,Ie),An=be.max,gn=be.min,vn=Se.now,yn=t.parseInt,Sn=be.random,Cn=De.reverse,Se=fs(t,"DataView"),bn=fs(t,"Map"),In=fs(t,"Promise"),On=fs(t,"Set"),t=fs(t,"WeakMap"),Ln=fs(Ie,"create"),Dn=t&&new t,Rn={},wn=Fs(Se),xn=Fs(bn),Mn=Fs(In),Pn=Fs(On),Bn=Fs(t),Un=(Ye=Ye?Ye.prototype:n)?Ye.valueOf:n,Hn=Ye?Ye.toString:n;function Gn(e){if(to(e)&&!Ya(e)&&!(e instanceof Kn)){if(e instanceof Yn)return e;if(Pe.call(e,"__wrapped__"))return Us(e)}return new Yn(e)}var jn=function(){function e(){}return function(t){return eo(t)?$e?$e(t):(e.prototype=t,t=new e,e.prototype=n,t):{}}}();function qn(){}function Yn(e,t){this.__wrapped__=e,this.__actions__=[],this.__chain__=!!t,this.__index__=0,this.__values__=n}function Kn(e){this.__wrapped__=e,this.__actions__=[],this.__dir__=1,this.__filtered__=!1,this.__iteratees__=[],this.__takeCount__=d,this.__views__=[]}function Wn(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t>>0,t>>>=0;for(var s=ye(i);++r>>1,a=e[s];null!==a&&!co(a)&&(n?a<=t:a>>0)?(e=Ao(e))&&("string"==typeof t||null!=t&&!so(t))&&!(t=li(t))&&an(e)?yi(pn(e),0,r):e.split(t,r):[]},Gn.spread=function(e,t){if("function"!=typeof e)throw new Le(r);return t=null==t?0:An(mo(t),0),Xr(function(n){var r=n[t],n=yi(n,0,t);return r&&xt(n,r),Nt(e,this,n)})},Gn.tail=function(e){var t=null==e?0:e.length;return t?ii(e,1,t):[]},Gn.take=function(e,t,r){return e&&e.length?ii(e,0,(t=r||t===n?1:mo(t))<0?0:t):[]},Gn.takeRight=function(e,t,r){var i=null==e?0:e.length;return i?ii(e,(t=i-(r||t===n?1:mo(t)))<0?0:t,i):[]},Gn.takeRightWhile=function(e,t){return e&&e.length?di(e,us(t,3),!1,!0):[]},Gn.takeWhile=function(e,t){return e&&e.length?di(e,us(t,3)):[]},Gn.tap=function(e,t){return t(e),e},Gn.throttle=function(e,t,n){var i=!0,s=!0;if("function"!=typeof e)throw new Le(r);return eo(n)&&(i="leading"in n?!!n.leading:i,s="trailing"in n?!!n.trailing:s),Da(e,t,{leading:i,maxWait:t,trailing:s})},Gn.thru=pa,Gn.toArray=fo,Gn.toPairs=Uo,Gn.toPairsIn=Ho,Gn.toPath=function(e){return Ya(e)?wt(e,Bs):co(e)?[e]:Li(Ps(Ao(e)))},Gn.toPlainObject=To,Gn.transform=function(e,t,n){var s,r=Ya(e),i=r||$a(e)||uo(e);return t=us(t,4),null==n&&(s=e&&e.constructor,n=i?r?new s:[]:eo(e)&&Xa(s)?jn(Ve(e)):{}),(i?It:vr)(e,function(e,r,i){return t(n,e,r,i)}),n},Gn.unary=function(e){return Ia(e,1)},Gn.union=ta,Gn.unionBy=na,Gn.unionWith=ra,Gn.uniq=function(e){return e&&e.length?hi(e):[]},Gn.uniqBy=function(e,t){return e&&e.length?hi(e,us(t,2)):[]},Gn.uniqWith=function(e,t){return t="function"==typeof t?t:n,e&&e.length?hi(e,n,t):[]},Gn.unset=function(e,t){return null==e||fi(e,t)},Gn.unzip=ia,Gn.unzipWith=sa,Gn.update=function(e,t,n){return null==e?e:pi(e,t,Ai(n))},Gn.updateWith=function(e,t,r,i){return i="function"==typeof i?i:n,null==e?e:pi(e,t,Ai(r),i)},Gn.values=Go,Gn.valuesIn=function(e){return null==e?[]:Zt(e,wo(e))},Gn.without=aa,Gn.words=Zo,Gn.wrap=function(e,t){return Ba(Ai(t),e)},Gn.xor=oa,Gn.xorBy=ca,Gn.xorWith=ua,Gn.zip=la,Gn.zipObject=function(e,t){return Ei(e||[],t||[],nr)},Gn.zipObjectDeep=function(e,t){return Ei(e||[],t||[],ei)},Gn.zipWith=ha,Gn.entries=Uo,Gn.entriesIn=Ho,Gn.extend=vo,Gn.extendWith=yo,cc(Gn,Gn),Gn.add=Tc,Gn.attempt=Jo,Gn.camelCase=jo,Gn.capitalize=qo,Gn.ceil=Ac,Gn.clamp=function(e,t,r){return r===n&&(r=t,t=n),r!==n&&(r=(r=Eo(r))==r?r:0),t!==n&&(t=(t=Eo(t))==t?t:0),cr(Eo(e),t,r)},Gn.clone=function(e){return ur(e,4)},Gn.cloneDeep=function(e){return ur(e,5)},Gn.cloneDeepWith=function(e,t){return ur(e,5,t="function"==typeof t?t:n)},Gn.cloneWith=function(e,t){return ur(e,4,t="function"==typeof t?t:n)},Gn.conformsTo=function(e,t){return null==t||lr(e,t,Ro(t))},Gn.deburr=Yo,Gn.defaultTo=function(e,t){return null==e||e!=e?t:e},Gn.divide=gc,Gn.endsWith=function(e,t,r){e=Ao(e),t=li(t);var i=e.length,i=r=r===n?i:cr(mo(r),0,i);return 0<=(r-=t.length)&&e.slice(r,i)==t},Gn.eq=Ha,Gn.escape=function(e){return(e=Ao(e))&&Q.test(e)?e.replace(V,rn):e},Gn.escapeRegExp=function(e){return(e=Ao(e))&&re.test(e)?e.replace(ne,"\\$&"):e},Gn.every=function(e,t,r){return(Ya(e)?kt:function(e,t){var n=!0;return pr(e,function(e,r,i){return n=!!t(e,r,i)}),n})(e,us(t=r&&gs(e,t,r)?n:t,3))},Gn.find=_a,Gn.findIndex=qs,Gn.findKey=function(e,t){return Ut(e,us(t,3),vr)},Gn.findLast=Ea,Gn.findLastIndex=Ys,Gn.findLastKey=function(e,t){return Ut(e,us(t,3),yr)},Gn.floor=vc,Gn.forEach=Ta,Gn.forEachRight=Aa,Gn.forIn=function(e,t){return null==e?e:Ar(e,us(t,3),wo)},Gn.forInRight=function(e,t){return null==e?e:gr(e,us(t,3),wo)},Gn.forOwn=function(e,t){return e&&vr(e,us(t,3))},Gn.forOwnRight=function(e,t){return e&&yr(e,us(t,3))},Gn.get=Io,Gn.gt=Ga,Gn.gte=ja,Gn.has=function(e,t){return null!=e&&_s(e,t,Or)},Gn.hasIn=Oo,Gn.head=Ws,Gn.identity=ic,Gn.includes=function(e,t,n,r){return e=Wa(e)?e:Go(e),n=n&&!r?mo(n):0,r=e.length,n<0&&(n=An(r+n,0)),oo(e)?n<=r&&-1=gn(t,n)&&e=this.__values__.length;return{done:e,value:e?n:this.__values__[this.__index__++]}},Gn.prototype.plant=function(e){for(var t,r=this;r instanceof qn;)var i=Us(r),s=(i.__index__=0,i.__values__=n,t?s.__wrapped__=i:t=i,i),r=r.__wrapped__;return s.__wrapped__=e,t},Gn.prototype.reverse=function(){var e=this.__wrapped__;return e instanceof Kn?((e=(e=this.__actions__.length?new Kn(this):e).reverse()).__actions__.push({func:pa,args:[ea],thisArg:n}),new Yn(e,this.__chain__)):this.thru(ea)},Gn.prototype.toJSON=Gn.prototype.valueOf=Gn.prototype.value=function(){return mi(this.__wrapped__,this.__actions__)},Gn.prototype.first=Gn.prototype.head,et&&(Gn.prototype[et]=function(){return this}),Gn}();mt?((mt.exports=_n)._=_n,He._=_n):pt._=_n}.call(gr)}),dh=Sr(function(e,t){e.exports=function(e){var n;if("undefined"!=typeof window&&window.crypto&&(n=window.crypto),"undefined"!=typeof self&&self.crypto&&(n=self.crypto),!(n=!(n=!(n="undefined"!=typeof globalThis&&globalThis.crypto?globalThis.crypto:n)&&"undefined"!=typeof window&&window.msCrypto?window.msCrypto:n)&&void 0!==gr&&gr.crypto?gr.crypto:n))try{n=Zu}catch(e){}function r(){if(n){if("function"==typeof n.getRandomValues)try{return n.getRandomValues(new Uint32Array(1))[0]}catch(e){}if("function"==typeof n.randomBytes)try{return n.randomBytes(4).readInt32LE()}catch(e){}}throw new Error("Native crypto module could not be used to get secure random number.")}var i=Object.create||function(){function e(){}return function(t){return e.prototype=t,t=new e,e.prototype=null,t}}(),s={},a=s.lib={},o=a.Base={extend:function(e){var t=i(this);return e&&t.mixIn(e),t.hasOwnProperty("init")&&this.init!==t.init||(t.init=function(){t.$super.init.apply(this,arguments)}),(t.init.prototype=t).$super=this,t},create:function(){var e=this.extend();return e.init.apply(e,arguments),e},init:function(){},mixIn:function(e){for(var t in e)e.hasOwnProperty(t)&&(this[t]=e[t]);e.hasOwnProperty("toString")&&(this.toString=e.toString)},clone:function(){return this.init.prototype.extend(this)}},c=a.WordArray=o.extend({init:function(e,n){e=this.words=e||[],this.sigBytes=null!=n?n:4*e.length},toString:function(e){return(e||l).stringify(this)},concat:function(e){var t=this.words,n=e.words,r=this.sigBytes,i=e.sigBytes;if(this.clamp(),r%4)for(var s=0;s>>2]>>>24-s%4*8&255;t[r+s>>>2]|=a<<24-(r+s)%4*8}else for(var o=0;o>>2]=n[o>>>2];return this.sigBytes+=i,this},clamp:function(){var t=this.words,n=this.sigBytes;t[n>>>2]&=4294967295<<32-n%4*8,t.length=e.ceil(n/4)},clone:function(){var e=o.clone.call(this);return e.words=this.words.slice(0),e},random:function(e){for(var t=[],n=0;n>>2]>>>24-i%4*8&255;r.push((s>>>4).toString(16)),r.push((15&s).toString(16))}return r.join("")},parse:function(e){for(var t=e.length,n=[],r=0;r>>3]|=parseInt(e.substr(r,2),16)<<24-r%8*4;return new c.init(n,t/2)}},h=u.Latin1={stringify:function(e){for(var t=e.words,n=e.sigBytes,r=[],i=0;i>>2]>>>24-i%4*8&255;r.push(String.fromCharCode(s))}return r.join("")},parse:function(e){for(var t=e.length,n=[],r=0;r>>2]|=(255&e.charCodeAt(r))<<24-r%4*8;return new c.init(n,t)}},f=u.Utf8={stringify:function(e){try{return decodeURIComponent(escape(h.stringify(e)))}catch(e){throw new Error("Malformed UTF-8 data")}},parse:function(e){return h.parse(unescape(encodeURIComponent(e)))}},p=a.BufferedBlockAlgorithm=o.extend({reset:function(){this._data=new c.init,this._nDataBytes=0},_append:function(e){"string"==typeof e&&(e=f.parse(e)),this._data.concat(e),this._nDataBytes+=e.sigBytes},_process:function(t){var n,r=this._data,i=r.words,s=r.sigBytes,a=this.blockSize,o=s/(4*a),u=(t?e.ceil(o):e.max((0|o)-this._minBufferSize,0))*a,t=e.min(4*u,s);if(u){for(var h=0;h>>2]|=e[i]<<24-i%4*8;t.call(this,r,n)}else t.apply(this,arguments)}).prototype=e)}(),n.lib.WordArray)}),Sr(function(e,t){var n;e.exports=(n=dh,function(){var t=n.lib.WordArray,r=n.enc;function i(e){return e<<8&4278255360|e>>>8&16711935}r.Utf16=r.Utf16BE={stringify:function(e){for(var t=e.words,n=e.sigBytes,r=[],i=0;i>>2]>>>16-i%4*8&65535;r.push(String.fromCharCode(s))}return r.join("")},parse:function(e){for(var n=e.length,r=[],i=0;i>>1]|=e.charCodeAt(i)<<16-i%2*16;return t.create(r,2*n)}},r.Utf16LE={stringify:function(e){for(var t=e.words,n=e.sigBytes,r=[],s=0;s>>2]>>>16-s%4*8&65535);r.push(String.fromCharCode(a))}return r.join("")},parse:function(e){for(var n=e.length,r=[],s=0;s>>1]|=i(e.charCodeAt(s)<<16-s%2*16);return t.create(r,2*n)}}}(),n.enc.Utf16)}),Sr(function(e,t){var n;e.exports=(n=dh,function(){var e=n,t=e.lib.WordArray;function r(e,n,r){for(var o,i=[],s=0,a=0;a>>6-a%4*2,i[s>>>2]|=o<<24-s%4*8,s++);return t.create(i,s)}e.enc.Base64={stringify:function(e){var t=e.words,n=e.sigBytes,r=this._map;e.clamp();for(var i=[],s=0;s>>2]>>>24-s%4*8&255)<<16|(t[s+1>>>2]>>>24-(s+1)%4*8&255)<<8|t[s+2>>>2]>>>24-(s+2)%4*8&255,o=0;o<4&&s+.75*o>>6*(3-o)&63));var c=r.charAt(64);if(c)for(;i.length%4;)i.push(c);return i.join("")},parse:function(e){var t=e.length,n=this._map;if(!(i=this._reverseMap))for(var i=this._reverseMap=[],s=0;s>>6-a%4*2,i[s>>>2]|=o<<24-s%4*8,s++);return t.create(i,s)}e.enc.Base64url={stringify:function(e,t=!0){var n=e.words,r=e.sigBytes,i=t?this._safe_map:this._map;e.clamp();for(var s=[],a=0;a>>2]>>>24-a%4*8&255)<<16|(n[a+1>>>2]>>>24-(a+1)%4*8&255)<<8|n[a+2>>>2]>>>24-(a+2)%4*8&255,c=0;c<4&&a+.75*c>>6*(3-c)&63));var u=i.charAt(64);if(u)for(;s.length%4;)s.push(u);return s.join("")},parse:function(e,t=!0){var n=e.length,i=t?this._safe_map:this._map;if(!(s=this._reverseMap))for(var s=this._reverseMap=[],a=0;a>>24)|4278255360&(i<<24|i>>>8)}var s=this._hash.words,a=e[t+0],c=e[t+1],p=e[t+2],d=e[t+3],m=e[t+4],_=e[t+5],E=e[t+6],T=e[t+7],A=e[t+8],g=e[t+9],v=e[t+10],y=e[t+11],S=e[t+12],C=e[t+13],N=e[t+14],b=e[t+15],I=u(s[0],O=s[1],k=s[2],L=s[3],a,7,o[0]),L=u(L,I,O,k,c,12,o[1]),k=u(k,L,I,O,p,17,o[2]),O=u(O,k,L,I,d,22,o[3]),I=u(I,O,k,L,m,7,o[4]),L=u(L,I,O,k,_,12,o[5]),k=u(k,L,I,O,E,17,o[6]),O=u(O,k,L,I,T,22,o[7]);I=u(I,O,k,L,A,7,o[8]),L=u(L,I,O,k,g,12,o[9]),k=u(k,L,I,O,v,17,o[10]),O=u(O,k,L,I,y,22,o[11]),I=u(I,O,k,L,S,7,o[12]),L=u(L,I,O,k,C,12,o[13]),k=u(k,L,I,O,N,17,o[14]),I=l(I,O=u(O,k,L,I,b,22,o[15]),k,L,c,5,o[16]),L=l(L,I,O,k,E,9,o[17]),k=l(k,L,I,O,y,14,o[18]),O=l(O,k,L,I,a,20,o[19]),I=l(I,O,k,L,_,5,o[20]),L=l(L,I,O,k,v,9,o[21]),k=l(k,L,I,O,b,14,o[22]),O=l(O,k,L,I,m,20,o[23]),I=l(I,O,k,L,g,5,o[24]),L=l(L,I,O,k,N,9,o[25]),k=l(k,L,I,O,d,14,o[26]),O=l(O,k,L,I,A,20,o[27]),I=l(I,O,k,L,C,5,o[28]),L=l(L,I,O,k,p,9,o[29]),k=l(k,L,I,O,T,14,o[30]),I=h(I,O=l(O,k,L,I,S,20,o[31]),k,L,_,4,o[32]),L=h(L,I,O,k,A,11,o[33]),k=h(k,L,I,O,y,16,o[34]),O=h(O,k,L,I,N,23,o[35]),I=h(I,O,k,L,c,4,o[36]),L=h(L,I,O,k,m,11,o[37]),k=h(k,L,I,O,T,16,o[38]),O=h(O,k,L,I,v,23,o[39]),I=h(I,O,k,L,C,4,o[40]),L=h(L,I,O,k,a,11,o[41]),k=h(k,L,I,O,d,16,o[42]),O=h(O,k,L,I,E,23,o[43]),I=h(I,O,k,L,g,4,o[44]),L=h(L,I,O,k,S,11,o[45]),k=h(k,L,I,O,b,16,o[46]),I=f(I,O=h(O,k,L,I,p,23,o[47]),k,L,a,6,o[48]),L=f(L,I,O,k,T,10,o[49]),k=f(k,L,I,O,N,15,o[50]),O=f(O,k,L,I,_,21,o[51]),I=f(I,O,k,L,S,6,o[52]),L=f(L,I,O,k,d,10,o[53]),k=f(k,L,I,O,v,15,o[54]),O=f(O,k,L,I,c,21,o[55]),I=f(I,O,k,L,A,6,o[56]),L=f(L,I,O,k,b,10,o[57]),k=f(k,L,I,O,E,15,o[58]),O=f(O,k,L,I,C,21,o[59]),I=f(I,O,k,L,m,6,o[60]),L=f(L,I,O,k,y,10,o[61]),k=f(k,L,I,O,p,15,o[62]),O=f(O,k,L,I,g,21,o[63]),s[0]=s[0]+I|0,s[1]=s[1]+O|0,s[2]=s[2]+k|0,s[3]=s[3]+L|0},_doFinalize:function(){var t=this._data,n=t.words,r=8*this._nDataBytes,i=8*t.sigBytes,s=(n[i>>>5]|=128<<24-i%32,e.floor(r/4294967296));n[15+(64+i>>>9<<4)]=16711935&(s<<8|s>>>24)|4278255360&(s<<24|s>>>8),n[14+(64+i>>>9<<4)]=16711935&(r<<8|r>>>24)|4278255360&(r<<24|r>>>8),t.sigBytes=4*(n.length+1),this._process();for(var c=(s=this._hash).words,u=0;u<4;u++){var l=c[u];c[u]=16711935&(l<<8|l>>>24)|4278255360&(l<<24|l>>>8)}return s},clone:function(){var e=s.clone.call(this);return e._hash=this._hash.clone(),e}}));function u(e,t,n,r,i,s,a){return((e=e+(t&n|~t&r)+i+a)<>>32-s)+t}function l(e,t,n,r,i,s,a){return((e=e+(t&r|n&~r)+i+a)<>>32-s)+t}function h(e,t,n,r,i,s,a){return((e=e+(t^n^r)+i+a)<>>32-s)+t}function f(e,t,n,r,i,s,a){return((e=e+(n^(t|~r))+i+a)<>>32-s)+t}t.MD5=s._createHelper(r),t.HmacMD5=s._createHmacHelper(r)}(Math),n.MD5)}),Sr(function(e,t){var n;e.exports=(n=dh,function(){var e=n,r=(t=e.lib).WordArray,i=t.Hasher,t=e.algo,a=[],t=t.SHA1=i.extend({_doReset:function(){this._hash=new r.init([1732584193,4023233417,2562383102,271733878,3285377520])},_doProcessBlock:function(e,t){for(var n=this._hash.words,r=n[0],i=n[1],s=n[2],o=n[3],c=n[4],u=0;u<80;u++){a[u]=u<16?0|e[t+u]:(l=a[u-3]^a[u-8]^a[u-14]^a[u-16])<<1|l>>>31;var l=(r<<5|r>>>27)+c+a[u];l+=u<20?1518500249+(i&s|~i&o):u<40?1859775393+(i^s^o):u<60?(i&s|i&o|s&o)-1894007588:(i^s^o)-899497514,c=o,o=s,s=i<<30|i>>>2,i=r,r=l}n[0]=n[0]+r|0,n[1]=n[1]+i|0,n[2]=n[2]+s|0,n[3]=n[3]+o|0,n[4]=n[4]+c|0},_doFinalize:function(){var e=this._data,t=e.words,n=8*this._nDataBytes,r=8*e.sigBytes;return t[r>>>5]|=128<<24-r%32,t[14+(64+r>>>9<<4)]=Math.floor(n/4294967296),t[15+(64+r>>>9<<4)]=n,e.sigBytes=4*t.length,this._process(),this._hash},clone:function(){var e=i.clone.call(this);return e._hash=this._hash.clone(),e}});e.SHA1=i._createHelper(t),e.HmacSHA1=i._createHmacHelper(t)}(),n.SHA1)}),Sr(function(e,t){var n;e.exports=(n=dh,function(e){var t=n,i=(r=t.lib).WordArray,s=r.Hasher,r=t.algo,o=[],c=[],u=(function(){function n(e){return 4294967296*(e-(0|e))|0}for(var r=2,i=0;i<64;)!function(t){for(var n=e.sqrt(t),r=2;r<=n;r++)if(!(t%r))return;return 1}(r)||(i<8&&(o[i]=n(e.pow(r,.5))),c[i]=n(e.pow(r,1/3)),i++),r++}(),[]),r=r.SHA256=s.extend({_doReset:function(){this._hash=new i.init(o.slice(0))},_doProcessBlock:function(e,t){for(var n=this._hash.words,r=n[0],i=n[1],s=n[2],a=n[3],o=n[4],l=n[5],h=n[6],f=n[7],p=0;p<64;p++){u[p]=p<16?0|e[t+p]:(((d=u[p-15])<<25|d>>>7)^(d<<14|d>>>18)^d>>>3)+u[p-7]+(((d=u[p-2])<<15|d>>>17)^(d<<13|d>>>19)^d>>>10)+u[p-16];var d=r&i^r&s^i&s,g=f+((o<<26|o>>>6)^(o<<21|o>>>11)^(o<<7|o>>>25))+(o&l^~o&h)+c[p]+u[p],f=h,h=l,l=o,o=a+g|0,a=s,s=i,i=r,r=g+(((r<<30|r>>>2)^(r<<19|r>>>13)^(r<<10|r>>>22))+d)|0}n[0]=n[0]+r|0,n[1]=n[1]+i|0,n[2]=n[2]+s|0,n[3]=n[3]+a|0,n[4]=n[4]+o|0,n[5]=n[5]+l|0,n[6]=n[6]+h|0,n[7]=n[7]+f|0},_doFinalize:function(){var t=this._data,n=t.words,r=8*this._nDataBytes,i=8*t.sigBytes;return n[i>>>5]|=128<<24-i%32,n[14+(64+i>>>9<<4)]=e.floor(r/4294967296),n[15+(64+i>>>9<<4)]=r,t.sigBytes=4*n.length,this._process(),this._hash},clone:function(){var e=s.clone.call(this);return e._hash=this._hash.clone(),e}});t.SHA256=s._createHelper(r),t.HmacSHA256=s._createHmacHelper(r)}(Math),n.SHA256)}),Sr(function(e,t){var n,r,s,i;e.exports=(r=(n=e=dh).lib.WordArray,i=n.algo,s=i.SHA256,i=i.SHA224=s.extend({_doReset:function(){this._hash=new r.init([3238371032,914150663,812702999,4144912697,4290775857,1750603025,1694076839,3204075428])},_doFinalize:function(){var e=s._doFinalize.call(this);return e.sigBytes-=4,e}}),n.SHA224=s._createHelper(i),n.HmacSHA224=s._createHmacHelper(i),e.SHA224)}),Sr(function(e,t){var n;e.exports=(n=dh,function(){var e=n,t=e.lib.Hasher,i=(r=e.x64).Word,s=r.WordArray,r=e.algo;function o(){return i.create.apply(i,arguments)}var c=[o(1116352408,3609767458),o(1899447441,602891725),o(3049323471,3964484399),o(3921009573,2173295548),o(961987163,4081628472),o(1508970993,3053834265),o(2453635748,2937671579),o(2870763221,3664609560),o(3624381080,2734883394),o(310598401,1164996542),o(607225278,1323610764),o(1426881987,3590304994),o(1925078388,4068182383),o(2162078206,991336113),o(2614888103,633803317),o(3248222580,3479774868),o(3835390401,2666613458),o(4022224774,944711139),o(264347078,2341262773),o(604807628,2007800933),o(770255983,1495990901),o(1249150122,1856431235),o(1555081692,3175218132),o(1996064986,2198950837),o(2554220882,3999719339),o(2821834349,766784016),o(2952996808,2566594879),o(3210313671,3203337956),o(3336571891,1034457026),o(3584528711,2466948901),o(113926993,3758326383),o(338241895,168717936),o(666307205,1188179964),o(773529912,1546045734),o(1294757372,1522805485),o(1396182291,2643833823),o(1695183700,2343527390),o(1986661051,1014477480),o(2177026350,1206759142),o(2456956037,344077627),o(2730485921,1290863460),o(2820302411,3158454273),o(3259730800,3505952657),o(3345764771,106217008),o(3516065817,3606008344),o(3600352804,1432725776),o(4094571909,1467031594),o(275423344,851169720),o(430227734,3100823752),o(506948616,1363258195),o(659060556,3750685593),o(883997877,3785050280),o(958139571,3318307427),o(1322822218,3812723403),o(1537002063,2003034995),o(1747873779,3602036899),o(1955562222,1575990012),o(2024104815,1125592928),o(2227730452,2716904306),o(2361852424,442776044),o(2428436474,593698344),o(2756734187,3733110249),o(3204031479,2999351573),o(3329325298,3815920427),o(3391569614,3928383900),o(3515267271,566280711),o(3940187606,3454069534),o(4118630271,4000239992),o(116418474,1914138554),o(174292421,2731055270),o(289380356,3203993006),o(460393269,320620315),o(685471733,587496836),o(852142971,1086792851),o(1017036298,365543100),o(1126000580,2618297676),o(1288033470,3409855158),o(1501505948,4234509866),o(1607167915,987167468),o(1816402316,1246189591)],u=[],r=(function(){for(var e=0;e<80;e++)u[e]=o()}(),r.SHA512=t.extend({_doReset:function(){this._hash=new s.init([new i.init(1779033703,4089235720),new i.init(3144134277,2227873595),new i.init(1013904242,4271175723),new i.init(2773480762,1595750129),new i.init(1359893119,2917565137),new i.init(2600822924,725511199),new i.init(528734635,4215389547),new i.init(1541459225,327033209)])},_doProcessBlock:function(e,t){for(var r=(n=this._hash.words)[0],i=n[1],s=n[2],a=n[3],o=n[4],l=n[5],h=n[6],n=n[7],p=r.high,d=r.low,m=i.high,_=i.low,E=s.high,T=s.low,A=a.high,g=a.low,v=o.high,y=o.low,S=l.high,C=l.low,N=h.high,b=h.low,I=n.high,O=n.low,k=p,L=d,D=m,R=_,w=E,x=T,M=A,P=g,B=v,F=y,U=S,H=C,G=N,j=b,q=I,Y=O,K=0;K<80;K++){var W,V,$=u[K];K<16?(V=$.high=0|e[t+2*K],W=$.low=0|e[t+2*K+1]):(z=(Q=u[K-15]).high,Q=Q.low,te=(ee=u[K-2]).high,ee=ee.low,V=(V=(V=((z>>>1|Q<<31)^(z>>>8|Q<<24)^z>>>7)+(se=u[K-7]).high+((W=(Q=(Q>>>1|z<<31)^(Q>>>8|z<<24)^(Q>>>7|z<<25))+se.low)>>>0>>0?1:0))+((te>>>19|ee<<13)^(te<<3|ee>>>29)^te>>>6)+((W+=z=(ee>>>19|te<<13)^(ee<<3|te>>>29)^(ee>>>6|te<<26))>>>0>>0?1:0))+(se=u[K-16]).high+((W+=Q=se.low)>>>0>>0?1:0),$.high=V,$.low=W);var he,ee=B&U^~B&G,te=F&H^~F&j,z=k&D^k&w^D&w,se=(L>>>28|k<<4)^(L<<30|k>>>2)^(L<<25|k>>>7),Q=c[K],$=Q.high,ye=Q.low,Se=q+((B>>>14|F<<18)^(B>>>18|F<<14)^(B<<23|F>>>9))+((he=Y+((F>>>14|B<<18)^(F>>>18|B<<14)^(F<<23|B>>>9)))>>>0>>0?1:0),Ce=se+(L&R^L&x^R&x),q=G,Y=j,G=U,j=H,U=B,H=F,B=M+(Se=Se+ee+((he+=te)>>>0>>0?1:0)+$+((he+=ye)>>>0>>0?1:0)+V+((he+=W)>>>0>>0?1:0))+((F=P+he|0)>>>0

>>0?1:0)|0,M=w,P=x,w=D,x=R,D=k,R=L,k=Se+(((k>>>28|L<<4)^(k<<30|L>>>2)^(k<<25|L>>>7))+z+(Ce>>>0>>0?1:0))+((L=he+Ce|0)>>>0>>0?1:0)|0}d=r.low=d+L,r.high=p+k+(d>>>0>>0?1:0),_=i.low=_+R,i.high=m+D+(_>>>0>>0?1:0),T=s.low=T+x,s.high=E+w+(T>>>0>>0?1:0),g=a.low=g+P,a.high=A+M+(g>>>0

>>0?1:0),y=o.low=y+F,o.high=v+B+(y>>>0>>0?1:0),C=l.low=C+H,l.high=S+U+(C>>>0>>0?1:0),b=h.low=b+j,h.high=N+G+(b>>>0>>0?1:0),O=n.low=O+Y,n.high=I+q+(O>>>0>>0?1:0)},_doFinalize:function(){var e=this._data,t=e.words,n=8*this._nDataBytes,r=8*e.sigBytes;return t[r>>>5]|=128<<24-r%32,t[30+(128+r>>>10<<5)]=Math.floor(n/4294967296),t[31+(128+r>>>10<<5)]=n,e.sigBytes=4*t.length,this._process(),this._hash.toX32()},clone:function(){var e=t.clone.call(this);return e._hash=this._hash.clone(),e},blockSize:32}));e.SHA512=t._createHelper(r),e.HmacSHA512=t._createHmacHelper(r)}(),n.SHA512)}),Sr(function(e,t){var n,i,s,o,r;e.exports=(r=(n=e=dh).x64,i=r.Word,s=r.WordArray,r=n.algo,o=r.SHA512,r=r.SHA384=o.extend({_doReset:function(){this._hash=new s.init([new i.init(3418070365,3238371032),new i.init(1654270250,914150663),new i.init(2438529370,812702999),new i.init(355462360,4144912697),new i.init(1731405415,4290775857),new i.init(2394180231,1750603025),new i.init(3675008525,1694076839),new i.init(1203062813,3204075428)])},_doFinalize:function(){var e=o._doFinalize.call(this);return e.sigBytes-=16,e}}),n.SHA384=o._createHelper(r),n.HmacSHA384=o._createHmacHelper(r),e.SHA384)}),Sr(function(e,t){var n;e.exports=(n=dh,function(e){var t=n,i=(r=t.lib).WordArray,s=r.Hasher,a=t.x64.Word,r=t.algo,c=[],u=[],l=[],h=(function(){for(var e=1,t=0,n=0;n<24;n++){c[e+5*t]=(n+1)*(n+2)/2%64;var r=(2*e+3*t)%5,e=t%5,t=r}for(e=0;e<5;e++)for(t=0;t<5;t++)u[e+5*t]=t+(2*e+3*t)%5*5;for(var i=1,s=0;s<24;s++){for(var p,o=0,h=0,f=0;f<7;f++)1&i&&((p=(1<>>24)|4278255360&(s<<24|s>>>8);(O=n[i]).high^=16711935&(a<<8|a>>>24)|4278255360&(a<<24|a>>>8),O.low^=s}for(var o=0;o<24;o++){for(var f=0;f<5;f++){for(var p=0,d=0,m=0;m<5;m++)p^=(O=n[f+5*m]).high,d^=O.low;var _=h[f];_.high=p,_.low=d}for(f=0;f<5;f++)for(var E=h[(f+4)%5],A=(T=h[(f+1)%5]).high,T=T.low,p=E.high^(A<<1|T>>>31),d=E.low^(T<<1|A>>>31),m=0;m<5;m++)(O=n[f+5*m]).high^=p,O.low^=d;for(var v=1;v<25;v++){var y=(O=n[v]).high,S=O.low,C=c[v];d=C<32?(p=y<>>32-C,S<>>32-C):(p=S<>>64-C,y<>>64-C),(y=h[u[v]]).high=p,y.low=d}var b=h[0],I=n[0];for(b.high=I.high,b.low=I.low,f=0;f<5;f++)for(m=0;m<5;m++){var O=n[v=f+5*m],k=h[v],L=h[(f+1)%5+5*m],D=h[(f+2)%5+5*m];O.high=k.high^~L.high&D.high,O.low=k.low^~L.low&D.low}(O=n[0]).high^=(b=l[o]).high,O.low^=b.low}},_doFinalize:function(){var t=this._data,n=t.words,r=(this._nDataBytes,8*t.sigBytes),s=32*this.blockSize;n[r>>>5]|=1<<24-r%32,n[(e.ceil((1+r)/s)*s>>>5)-1]|=128,t.sigBytes=4*n.length,this._process();for(var a=this._state,c=(r=this.cfg.outputLength/8)/8,u=[],l=0;l>>24)|4278255360&(f<<24|f>>>8);u.push(16711935&(h<<8|h>>>24)|4278255360&(h<<24|h>>>8)),u.push(f)}return new i.init(u,r)},clone:function(){for(var e=s.clone.call(this),t=e._state=this._state.slice(0),n=0;n<25;n++)t[n]=t[n].clone();return e}}));t.SHA3=s._createHelper(r),t.HmacSHA3=s._createHmacHelper(r)}(Math),n.SHA3)}),Sr(function(e,t){var n;e.exports=(n=dh,function(){var t=n,i=(r=t.lib).WordArray,s=r.Hasher,r=t.algo,o=i.create([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,7,4,13,1,10,6,15,3,12,0,9,5,2,14,11,8,3,10,14,4,9,15,8,1,2,7,0,6,13,11,5,12,1,9,11,10,0,8,12,4,13,3,7,15,14,5,6,2,4,0,5,9,7,12,2,10,14,1,3,8,11,6,15,13]),c=i.create([5,14,7,0,9,2,11,4,13,6,15,8,1,10,3,12,6,11,3,7,0,13,5,10,14,15,8,12,4,9,1,2,15,5,1,3,7,14,6,9,11,8,12,2,10,0,4,13,8,6,4,1,3,11,15,0,5,12,2,13,9,7,10,14,12,15,10,4,1,5,8,7,6,2,13,14,0,3,9,11]),u=i.create([11,14,15,12,5,8,7,9,11,13,14,15,6,7,9,8,7,6,8,13,11,9,7,15,7,12,15,9,11,7,13,12,11,13,6,7,14,9,13,15,14,8,13,6,5,12,7,5,11,12,14,15,14,15,9,8,9,14,5,6,8,6,5,12,9,15,5,11,6,8,13,12,5,12,13,14,11,8,5,6]),l=i.create([8,9,9,11,13,15,15,5,7,7,8,11,14,14,12,6,9,13,15,7,12,8,9,11,7,7,12,7,6,15,13,11,9,7,15,11,8,6,6,14,12,13,5,14,13,13,7,5,15,5,8,11,14,14,6,14,6,9,12,9,12,5,15,8,8,5,12,9,12,5,14,6,8,13,6,5,15,13,11,11]),h=i.create([0,1518500249,1859775393,2400959708,2840853838]),f=i.create([1352829926,1548603684,1836072691,2053994217,0]),r=r.RIPEMD160=s.extend({_doReset:function(){this._hash=i.create([1732584193,4023233417,2562383102,271733878,3285377520])},_doProcessBlock:function(e,t){for(var n=0;n<16;n++){var r=t+n,i=e[r];e[r]=16711935&(i<<8|i>>>24)|4278255360&(i<<24|i>>>8)}for(var s,a,p,g,v,I,O=this._hash.words,k=h.words,L=f.words,D=o.words,R=c.words,w=u.words,x=l.words,y=s=O[0],S=a=O[1],C=p=O[2],N=g=O[3],b=v=O[4],n=0;n<80;n+=1)I=(I=A(I=(I=s+e[t+D[n]]|0)+(n<16?(a^p^g)+k[0]:n<32?m(a,p,g)+k[1]:n<48?((a|~p)^g)+k[2]:n<64?E(a,p,g)+k[3]:(a^(p|~g))+k[4])|0,w[n]))+v|0,s=v,v=g,g=A(p,10),p=a,a=I,I=(I=A(I=(I=y+e[t+R[n]]|0)+(n<16?(S^(C|~N))+L[0]:n<32?E(S,C,N)+L[1]:n<48?((S|~C)^N)+L[2]:n<64?m(S,C,N)+L[3]:(S^C^N)+L[4])|0,x[n]))+b|0,y=b,b=N,N=A(C,10),C=S,S=I;I=O[1]+p+N|0,O[1]=O[2]+g+b|0,O[2]=O[3]+v+y|0,O[3]=O[4]+s+S|0,O[4]=O[0]+a+C|0,O[0]=I},_doFinalize:function(){var e=this._data,t=e.words,n=8*this._nDataBytes,r=8*e.sigBytes;t[r>>>5]|=128<<24-r%32,t[14+(64+r>>>9<<4)]=16711935&(n<<8|n>>>24)|4278255360&(n<<24|n>>>8),e.sigBytes=4*(t.length+1),this._process();for(var s=(r=this._hash).words,a=0;a<5;a++){var o=s[a];s[a]=16711935&(o<<8|o>>>24)|4278255360&(o<<24|o>>>8)}return r},clone:function(){var e=s.clone.call(this);return e._hash=this._hash.clone(),e}});function m(e,t,n){return e&t|~e&n}function E(e,t,n){return e&n|t&~n}function A(e,t){return e<>>32-t}t.RIPEMD160=s._createHelper(r),t.HmacRIPEMD160=s._createHmacHelper(r)}(),n.RIPEMD160)}),Sr(function(e,t){var i;e.exports=(e=dh.lib.Base,i=dh.enc.Utf8,void(dh.algo.HMAC=e.extend({init:function(e,t){e=this._hasher=new e.init,"string"==typeof t&&(t=i.parse(t));var n=e.blockSize,r=4*n;(t=t.sigBytes>r?e.finalize(t):t).clamp();for(var e=this._oKey=t.clone(),t=this._iKey=t.clone(),o=e.words,c=t.words,u=0;u>>2];e.sigBytes-=t}},m=(r.BlockCipher=l.extend({cfg:l.cfg.extend({mode:o,padding:d}),reset:function(){l.reset.call(this);var e,n=(t=this.cfg).iv,t=t.mode;this._xformMode==this._ENC_XFORM_MODE?e=t.createEncryptor:(e=t.createDecryptor,this._minBufferSize=1),this._mode&&this._mode.__creator==e?this._mode.init(this,n&&n.words):(this._mode=e.call(t,this,n&&n.words),this._mode.__creator=e)},_doProcessBlock:function(e,t){this._mode.processBlock(e,t)},_doFinalize:function(){var e,t=this.cfg.padding;return this._xformMode==this._ENC_XFORM_MODE?(t.pad(this._data,this.blockSize),e=this._process(!0)):(e=this._process(!0),t.unpad(e)),e},blockSize:4}),r.CipherParams=i.extend({init:function(e){this.mixIn(e)},toString:function(e){return(e||this.formatter).stringify(this)}})),o=(t.format={}).OpenSSL={stringify:function(e){var t=e.ciphertext;return((e=e.salt)?s.create([1398893684,1701076831]).concat(e).concat(t):t).toString(c)},parse:function(e){var t,r=(e=c.parse(e)).words;return 1398893684==r[0]&&1701076831==r[1]&&(t=s.create(r.slice(2,4)),r.splice(0,4),e.sigBytes-=16),m.create({ciphertext:e,salt:t})}},E=r.SerializableCipher=i.extend({cfg:i.extend({format:o}),encrypt:function(e,t,n,r){r=this.cfg.extend(r);var t=(i=e.createEncryptor(n,r)).finalize(t),i=i.cfg;return m.create({ciphertext:t,key:n,iv:i.iv,algorithm:e,mode:i.mode,padding:i.padding,blockSize:e.blockSize,formatter:r.format})},decrypt:function(e,t,n,r){return r=this.cfg.extend(r),t=this._parse(t,r.format),e.createDecryptor(n,r).finalize(t.ciphertext)},_parse:function(e,t){return"string"==typeof e?t.parse(e,this):e}}),d=(t.kdf={}).OpenSSL={execute:function(e,t,n,r){return r=r||s.random(8),e=u.create({keySize:t+n}).compute(e,r),n=s.create(e.words.slice(t),4*n),e.sigBytes=4*t,m.create({key:e,iv:n,salt:r})}},A=r.PasswordBasedCipher=E.extend({cfg:E.cfg.extend({kdf:d}),encrypt:function(e,t,n,r){return n=(r=this.cfg.extend(r)).kdf.execute(n,e.keySize,e.ivSize),r.iv=n.iv,(e=E.encrypt.call(this,e,t,n.key,r)).mixIn(n),e},decrypt:function(e,t,n,r){return r=this.cfg.extend(r),t=this._parse(t,r.format),n=r.kdf.execute(n,e.keySize,e.ivSize,t.salt),r.iv=n.iv,E.decrypt.call(this,e,t,n.key,r)}});function e(e){return"string"==typeof e?A:E}}())}),Sr(function(e,t){var n;e.exports=((n=dh).mode.CFB=function(){var e=n.lib.BlockCipherMode.extend();function t(e,t,n,r){var i,s=this._iv;s?(i=s.slice(0),this._iv=void 0):i=this._prevBlock,r.encryptBlock(i,0);for(var a=0;a>24&255)?(n=e>>8&255,r=255&e,255==(t=e>>16&255)?(t=0,255===n?(n=0,255===r?r=0:++r):++n):++t,e=0,e=(e+=t<<16)+(n<<8)+r):e+=1<<24,e}function r(e){0===(e[0]=t(e[0]))&&(e[1]=t(e[1]))}var i=e.Encryptor=e.extend({processBlock:function(e,t){var n=this._cipher,i=n.blockSize,s=this._iv,a=this._counter,o=(s&&(a=this._counter=s.slice(0),this._iv=void 0),r(a),a.slice(0));n.encryptBlock(o,0);for(var c=0;c>>2]|=t<<24-n%4*8,e.sigBytes+=t},unpad:function(e){var t=255&e.words[e.sigBytes-1>>>2];e.sigBytes-=t}},dh.pad.Ansix923)}),Sr(function(e,t){var n;e.exports=((n=dh).pad.Iso10126={pad:function(e,t){t=(t*=4)-e.sigBytes%t,e.concat(n.lib.WordArray.random(t-1)).concat(n.lib.WordArray.create([t<<24],1))},unpad:function(e){var t=255&e.words[e.sigBytes-1>>>2];e.sigBytes-=t}},n.pad.Iso10126)}),Sr(function(e,t){var n;e.exports=((n=dh).pad.Iso97971={pad:function(e,t){e.concat(n.lib.WordArray.create([2147483648],1)),n.pad.ZeroPadding.pad(e,t)},unpad:function(e){n.pad.ZeroPadding.unpad(e),e.sigBytes--}},n.pad.Iso97971)}),Sr(function(e,t){e.exports=(dh.pad.ZeroPadding={pad:function(e,t){t*=4,e.clamp(),e.sigBytes+=t-(e.sigBytes%t||t)},unpad:function(e){for(var t=e.words,n=e.sigBytes-1,n=e.sigBytes-1;0<=n;n--)if(t[n>>>2]>>>24-n%4*8&255){e.sigBytes=n+1;break}}},dh.pad.ZeroPadding)}),Sr(function(e,t){e.exports=(dh.pad.NoPadding={pad:function(){},unpad:function(){}},dh.pad.NoPadding)}),Sr(function(e,t){var r,i;e.exports=(r=(e=dh).lib.CipherParams,i=e.enc.Hex,e.format.Hex={stringify:function(e){return e.ciphertext.toString(i)},parse:function(e){return e=i.parse(e),r.create({ciphertext:e})}},e.format.Hex)}),Sr(function(e,t){var n;e.exports=(n=dh,function(){var e=n,t=e.lib.BlockCipher,r=e.algo,i=[],s=[],a=[],o=[],c=[],u=[],l=[],h=[],f=[],p=[],d=(function(){for(var e=[],t=0;t<256;t++)e[t]=t<128?t<<1:t<<1^283;for(var n=0,r=0,t=0;t<256;t++){var d=r^r<<1^r<<2^r<<3^r<<4,m=(i[n]=d=d>>>8^255&d^99,e[s[d]=n]),_=e[m],E=e[_],T=257*e[d]^16843008*d;a[n]=T<<24|T>>>8,o[n]=T<<16|T>>>16,c[n]=T<<8|T>>>24,u[n]=T,l[d]=(T=16843009*E^65537*_^257*m^16843008*n)<<24|T>>>8,h[d]=T<<16|T>>>16,f[d]=T<<8|T>>>24,p[d]=T,n?(n=m^e[e[e[E^m]]],r^=e[e[r]]):n=r=1}}(),[0,1,2,4,8,16,32,64,128,27,54]),r=r.AES=t.extend({_doReset:function(){if(!this._nRounds||this._keyPriorReset!==this._key){for(var e=this._keyPriorReset=this._key,t=e.words,n=e.sigBytes/4,r=4*(1+(this._nRounds=6+n)),s=this._keySchedule=[],a=0;a>>24]<<24|i[u>>>16&255]<<16|i[u>>>8&255]<<8|i[255&u]):(u=i[(u=u<<8|u>>>24)>>>24]<<24|i[u>>>16&255]<<16|i[u>>>8&255]<<8|i[255&u],u^=d[a/n|0]<<24),s[a]=s[a-n]^u);for(var o=this._invKeySchedule=[],c=0;c>>24]]^h[i[u>>>16&255]]^f[i[u>>>8&255]]^p[i[255&u]]}}},encryptBlock:function(e,t){this._doCryptBlock(e,t,this._keySchedule,a,o,c,u,i)},decryptBlock:function(e,t){var n=e[t+1];e[t+1]=e[t+3],e[t+3]=n,this._doCryptBlock(e,t,this._invKeySchedule,l,h,f,p,s),n=e[t+1],e[t+1]=e[t+3],e[t+3]=n},_doCryptBlock:function(e,t,n,r,i,s,a,o){for(var c=this._nRounds,u=e[t]^n[0],l=e[t+1]^n[1],h=e[t+2]^n[2],f=e[t+3]^n[3],p=4,d=1;d>>24]^i[l>>>16&255]^s[h>>>8&255]^a[255&f]^n[p++],_=r[l>>>24]^i[h>>>16&255]^s[f>>>8&255]^a[255&u]^n[p++],E=r[h>>>24]^i[f>>>16&255]^s[u>>>8&255]^a[255&l]^n[p++],T=r[f>>>24]^i[u>>>16&255]^s[l>>>8&255]^a[255&h]^n[p++],u=m,l=_,h=E,f=T;m=(o[u>>>24]<<24|o[l>>>16&255]<<16|o[h>>>8&255]<<8|o[255&f])^n[p++],_=(o[l>>>24]<<24|o[h>>>16&255]<<16|o[f>>>8&255]<<8|o[255&u])^n[p++],E=(o[h>>>24]<<24|o[f>>>16&255]<<16|o[u>>>8&255]<<8|o[255&l])^n[p++],T=(o[f>>>24]<<24|o[u>>>16&255]<<16|o[l>>>8&255]<<8|o[255&h])^n[p++],e[t]=m,e[t+1]=_,e[t+2]=E,e[t+3]=T},keySize:8});e.AES=t._createHelper(r)}(),n.AES)}),Sr(function(e,t){var n;e.exports=(n=dh,function(){var e=n,r=(t=e.lib).WordArray,t=t.BlockCipher,s=e.algo,a=[57,49,41,33,25,17,9,1,58,50,42,34,26,18,10,2,59,51,43,35,27,19,11,3,60,52,44,36,63,55,47,39,31,23,15,7,62,54,46,38,30,22,14,6,61,53,45,37,29,21,13,5,28,20,12,4],o=[14,17,11,24,1,5,3,28,15,6,21,10,23,19,12,4,26,8,16,7,27,20,13,2,41,52,31,37,47,55,30,40,51,45,33,48,44,49,39,56,34,53,46,42,50,36,29,32],c=[1,2,4,6,8,10,12,14,15,17,19,21,23,25,27,28],u=[{0:8421888,268435456:32768,536870912:8421378,805306368:2,1073741824:512,1342177280:8421890,1610612736:8389122,1879048192:8388608,2147483648:514,2415919104:8389120,2684354560:33280,2952790016:8421376,3221225472:32770,3489660928:8388610,3758096384:0,4026531840:33282,134217728:0,402653184:8421890,671088640:33282,939524096:32768,1207959552:8421888,1476395008:512,1744830464:8421378,2013265920:2,2281701376:8389120,2550136832:33280,2818572288:8421376,3087007744:8389122,3355443200:8388610,3623878656:32770,3892314112:514,4160749568:8388608,1:32768,268435457:2,536870913:8421888,805306369:8388608,1073741825:8421378,1342177281:33280,1610612737:512,1879048193:8389122,2147483649:8421890,2415919105:8421376,2684354561:8388610,2952790017:33282,3221225473:514,3489660929:8389120,3758096385:32770,4026531841:0,134217729:8421890,402653185:8421376,671088641:8388608,939524097:512,1207959553:32768,1476395009:8388610,1744830465:2,2013265921:33282,2281701377:32770,2550136833:8389122,2818572289:514,3087007745:8421888,3355443201:8389120,3623878657:0,3892314113:33280,4160749569:8421378},{0:1074282512,16777216:16384,33554432:524288,50331648:1074266128,67108864:1073741840,83886080:1074282496,100663296:1073758208,117440512:16,134217728:540672,150994944:1073758224,167772160:1073741824,184549376:540688,201326592:524304,218103808:0,234881024:16400,251658240:1074266112,8388608:1073758208,25165824:540688,41943040:16,58720256:1073758224,75497472:1074282512,92274688:1073741824,109051904:524288,125829120:1074266128,142606336:524304,159383552:0,176160768:16384,192937984:1074266112,209715200:1073741840,226492416:540672,243269632:1074282496,260046848:16400,268435456:0,285212672:1074266128,301989888:1073758224,318767104:1074282496,335544320:1074266112,352321536:16,369098752:540688,385875968:16384,402653184:16400,419430400:524288,436207616:524304,452984832:1073741840,469762048:540672,486539264:1073758208,503316480:1073741824,520093696:1074282512,276824064:540688,293601280:524288,310378496:1074266112,327155712:16384,343932928:1073758208,360710144:1074282512,377487360:16,394264576:1073741824,411041792:1074282496,427819008:1073741840,444596224:1073758224,461373440:524304,478150656:0,494927872:16400,511705088:1074266128,528482304:540672},{0:260,1048576:0,2097152:67109120,3145728:65796,4194304:65540,5242880:67108868,6291456:67174660,7340032:67174400,8388608:67108864,9437184:67174656,10485760:65792,11534336:67174404,12582912:67109124,13631488:65536,14680064:4,15728640:256,524288:67174656,1572864:67174404,2621440:0,3670016:67109120,4718592:67108868,5767168:65536,6815744:65540,7864320:260,8912896:4,9961472:256,11010048:67174400,12058624:65796,13107200:65792,14155776:67109124,15204352:67174660,16252928:67108864,16777216:67174656,17825792:65540,18874368:65536,19922944:67109120,20971520:256,22020096:67174660,23068672:67108868,24117248:0,25165824:67109124,26214400:67108864,27262976:4,28311552:65792,29360128:67174400,30408704:260,31457280:65796,32505856:67174404,17301504:67108864,18350080:260,19398656:67174656,20447232:0,21495808:65540,22544384:67109120,23592960:256,24641536:67174404,25690112:65536,26738688:67174660,27787264:65796,28835840:67108868,29884416:67109124,30932992:67174400,31981568:4,33030144:65792},{0:2151682048,65536:2147487808,131072:4198464,196608:2151677952,262144:0,327680:4198400,393216:2147483712,458752:4194368,524288:2147483648,589824:4194304,655360:64,720896:2147487744,786432:2151678016,851968:4160,917504:4096,983040:2151682112,32768:2147487808,98304:64,163840:2151678016,229376:2147487744,294912:4198400,360448:2151682112,425984:0,491520:2151677952,557056:4096,622592:2151682048,688128:4194304,753664:4160,819200:2147483648,884736:4194368,950272:4198464,1015808:2147483712,1048576:4194368,1114112:4198400,1179648:2147483712,1245184:0,1310720:4160,1376256:2151678016,1441792:2151682048,1507328:2147487808,1572864:2151682112,1638400:2147483648,1703936:2151677952,1769472:4198464,1835008:2147487744,1900544:4194304,1966080:64,2031616:4096,1081344:2151677952,1146880:2151682112,1212416:0,1277952:4198400,1343488:4194368,1409024:2147483648,1474560:2147487808,1540096:64,1605632:2147483712,1671168:4096,1736704:2147487744,1802240:2151678016,1867776:4160,1933312:2151682048,1998848:4194304,2064384:4198464},{0:128,4096:17039360,8192:262144,12288:536870912,16384:537133184,20480:16777344,24576:553648256,28672:262272,32768:16777216,36864:537133056,40960:536871040,45056:553910400,49152:553910272,53248:0,57344:17039488,61440:553648128,2048:17039488,6144:553648256,10240:128,14336:17039360,18432:262144,22528:537133184,26624:553910272,30720:536870912,34816:537133056,38912:0,43008:553910400,47104:16777344,51200:536871040,55296:553648128,59392:16777216,63488:262272,65536:262144,69632:128,73728:536870912,77824:553648256,81920:16777344,86016:553910272,90112:537133184,94208:16777216,98304:553910400,102400:553648128,106496:17039360,110592:537133056,114688:262272,118784:536871040,122880:0,126976:17039488,67584:553648256,71680:16777216,75776:17039360,79872:537133184,83968:536870912,88064:17039488,92160:128,96256:553910272,100352:262272,104448:553910400,108544:0,112640:553648128,116736:16777344,120832:262144,124928:537133056,129024:536871040},{0:268435464,256:8192,512:270532608,768:270540808,1024:268443648,1280:2097152,1536:2097160,1792:268435456,2048:0,2304:268443656,2560:2105344,2816:8,3072:270532616,3328:2105352,3584:8200,3840:270540800,128:270532608,384:270540808,640:8,896:2097152,1152:2105352,1408:268435464,1664:268443648,1920:8200,2176:2097160,2432:8192,2688:268443656,2944:270532616,3200:0,3456:270540800,3712:2105344,3968:268435456,4096:268443648,4352:270532616,4608:270540808,4864:8200,5120:2097152,5376:268435456,5632:268435464,5888:2105344,6144:2105352,6400:0,6656:8,6912:270532608,7168:8192,7424:268443656,7680:270540800,7936:2097160,4224:8,4480:2105344,4736:2097152,4992:268435464,5248:268443648,5504:8200,5760:270540808,6016:270532608,6272:270540800,6528:270532616,6784:8192,7040:2105352,7296:2097160,7552:0,7808:268435456,8064:268443656},{0:1048576,16:33555457,32:1024,48:1049601,64:34604033,80:0,96:1,112:34603009,128:33555456,144:1048577,160:33554433,176:34604032,192:34603008,208:1025,224:1049600,240:33554432,8:34603009,24:0,40:33555457,56:34604032,72:1048576,88:33554433,104:33554432,120:1025,136:1049601,152:33555456,168:34603008,184:1048577,200:1024,216:34604033,232:1,248:1049600,256:33554432,272:1048576,288:33555457,304:34603009,320:1048577,336:33555456,352:34604032,368:1049601,384:1025,400:34604033,416:1049600,432:1,448:0,464:34603008,480:33554433,496:1024,264:1049600,280:33555457,296:34603009,312:1,328:33554432,344:1048576,360:1025,376:34604032,392:33554433,408:34603008,424:0,440:34604033,456:1049601,472:1024,488:33555456,504:1048577},{0:134219808,1:131072,2:134217728,3:32,4:131104,5:134350880,6:134350848,7:2048,8:134348800,9:134219776,10:133120,11:134348832,12:2080,13:0,14:134217760,15:133152,2147483648:2048,2147483649:134350880,2147483650:134219808,2147483651:134217728,2147483652:134348800,2147483653:133120,2147483654:133152,2147483655:32,2147483656:134217760,2147483657:2080,2147483658:131104,2147483659:134350848,2147483660:0,2147483661:134348832,2147483662:134219776,2147483663:131072,16:133152,17:134350848,18:32,19:2048,20:134219776,21:134217760,22:134348832,23:131072,24:0,25:131104,26:134348800,27:134219808,28:134350880,29:133120,30:2080,31:134217728,2147483664:131072,2147483665:2048,2147483666:134348832,2147483667:133152,2147483668:32,2147483669:134348800,2147483670:134217728,2147483671:134219808,2147483672:134350880,2147483673:134217760,2147483674:134219776,2147483675:0,2147483676:133120,2147483677:2080,2147483678:131104,2147483679:134350848}],l=[4160749569,528482304,33030144,2064384,129024,8064,504,2147483679],h=s.DES=t.extend({_doReset:function(){for(var e=this._key.words,t=[],n=0;n<56;n++){var r=a[n]-1;t[n]=e[r>>>5]>>>31-r%32&1}for(var i=this._subKeys=[],s=0;s<16;s++){for(var u=i[s]=[],l=c[s],n=0;n<24;n++)u[n/6|0]|=t[(o[n]-1+l)%28]<<31-n%6,u[4+(n/6|0)]|=t[28+(o[n+24]-1+l)%28]<<31-n%6;for(u[0]=u[0]<<1|u[0]>>>31,n=1;n<7;n++)u[n]=u[n]>>>4*(n-1)+3;u[7]=u[7]<<5|u[7]>>>27}var h=this._invSubKeys=[];for(n=0;n<16;n++)h[n]=i[15-n]},encryptBlock:function(e,t){this._doCryptBlock(e,t,this._subKeys)},decryptBlock:function(e,t){this._doCryptBlock(e,t,this._invSubKeys)},_doCryptBlock:function(e,t,n){this._lBlock=e[t],this._rBlock=e[t+1],f.call(this,4,252645135),f.call(this,16,65535),p.call(this,2,858993459),p.call(this,8,16711935),f.call(this,1,1431655765);for(var r=0;r<16;r++){for(var i=n[r],s=this._lBlock,a=this._rBlock,o=0,c=0;c<8;c++)o|=u[c][((a^i[c])&l[c])>>>0];this._lBlock=a,this._rBlock=s^o}var h=this._lBlock;this._lBlock=this._rBlock,this._rBlock=h,f.call(this,1,1431655765),p.call(this,8,16711935),p.call(this,2,858993459),f.call(this,16,65535),f.call(this,4,252645135),e[t]=this._lBlock,e[t+1]=this._rBlock},keySize:2,ivSize:2,blockSize:2});function f(e,t){t=(this._lBlock>>>e^this._rBlock)&t,this._rBlock^=t,this._lBlock^=t<>>e^this._lBlock)&t,this._lBlock^=t,this._rBlock^=t<192.");var t=e.slice(0,2),n=e.length<4?e.slice(0,2):e.slice(2,4),e=e.length<6?e.slice(0,2):e.slice(4,6);this._des1=h.createEncryptor(r.create(t)),this._des2=h.createEncryptor(r.create(n)),this._des3=h.createEncryptor(r.create(e))},encryptBlock:function(e,t){this._des1.encryptBlock(e,t),this._des2.decryptBlock(e,t),this._des3.encryptBlock(e,t)},decryptBlock:function(e,t){this._des3.decryptBlock(e,t),this._des2.encryptBlock(e,t),this._des1.decryptBlock(e,t)},keySize:6,ivSize:2,blockSize:2}),e.TripleDES=t._createHelper(s)}(),n.TripleDES)}),Sr(function(e,t){var n;e.exports=(n=dh,function(){var e=n,t=e.lib.StreamCipher,r=e.algo,i=r.RC4=t.extend({_doReset:function(){for(var e=this._key,t=e.words,n=e.sigBytes,r=this._S=[],i=0;i<256;i++)r[i]=i;for(var i=0,s=0;i<256;i++){var a=t[(a=i%n)>>>2]>>>24-a%4*8&255,s=(s+r[i]+a)%256,a=r[i];r[i]=r[s],r[s]=a}this._i=this._j=0},_doProcessBlock:function(e,t){e[t]^=s.call(this)},keySize:8,ivSize:0});function s(){for(var e=this._S,t=this._i,n=this._j,r=0,i=0;i<4;i++){var n=(n+e[t=(t+1)%256])%256,s=e[t];e[t]=e[n],e[n]=s,r|=e[(e[t]+e[n])%256]<<24-8*i}return this._i=t,this._j=n,r}e.RC4=t._createHelper(i),r=r.RC4Drop=i.extend({cfg:i.cfg.extend({drop:192}),_doReset:function(){i._doReset.call(this);for(var e=this.cfg.drop;0>>24)|4278255360&(e[n]<<24|e[n]>>>8);for(var r=this._X=[e[0],e[3]<<16|e[2]>>>16,e[1],e[0]<<16|e[3]>>>16,e[2],e[1]<<16|e[0]>>>16,e[3],e[2]<<16|e[1]>>>16],i=this._C=[e[2]<<16|e[2]>>>16,4294901760&e[0]|65535&e[1],e[3]<<16|e[3]>>>16,4294901760&e[1]|65535&e[2],e[0]<<16|e[0]>>>16,4294901760&e[2]|65535&e[3],e[1]<<16|e[1]>>>16,4294901760&e[3]|65535&e[0]],n=this._b=0;n<4;n++)c.call(this);for(n=0;n<8;n++)i[n]^=r[n+4&7];if(t){var a=(t=t.words)[0],h=(a=16711935&(a<<8|a>>>24)|4278255360&(a<<24|a>>>8))>>>16|4294901760&(t=16711935&((t=t[1])<<8|t>>>24)|4278255360&(t<<24|t>>>8)),f=t<<16|65535&a;for(i[0]^=a,i[1]^=h,i[2]^=t,i[3]^=f,i[4]^=a,i[5]^=h,i[6]^=t,i[7]^=f,n=0;n<4;n++)c.call(this)}},_doProcessBlock:function(e,t){var n=this._X;c.call(this),i[0]=n[0]^n[5]>>>16^n[3]<<16,i[1]=n[2]^n[7]>>>16^n[5]<<16,i[2]=n[4]^n[1]>>>16^n[7]<<16,i[3]=n[6]^n[3]>>>16^n[1]<<16;for(var r=0;r<4;r++)i[r]=16711935&(i[r]<<8|i[r]>>>24)|4278255360&(i[r]<<24|i[r]>>>8),e[t+r]^=i[r]},blockSize:4,ivSize:2});function c(){for(var e=this._X,t=this._C,n=0;n<8;n++)s[n]=t[n];for(t[0]=t[0]+1295307597+this._b|0,t[1]=t[1]+3545052371+(t[0]>>>0>>0?1:0)|0,t[2]=t[2]+886263092+(t[1]>>>0>>0?1:0)|0,t[3]=t[3]+1295307597+(t[2]>>>0>>0?1:0)|0,t[4]=t[4]+3545052371+(t[3]>>>0>>0?1:0)|0,t[5]=t[5]+886263092+(t[4]>>>0>>0?1:0)|0,t[6]=t[6]+1295307597+(t[5]>>>0>>0?1:0)|0,t[7]=t[7]+3545052371+(t[6]>>>0>>0?1:0)|0,this._b=t[7]>>>0>>0?1:0,n=0;n<8;n++){var r=e[n]+t[n],i=65535&r,o=r>>>16;a[n]=((i*i>>>17)+i*o>>>15)+o*o^((4294901760&r)*r|0)+((65535&r)*r|0)}e[0]=a[0]+(a[7]<<16|a[7]>>>16)+(a[6]<<16|a[6]>>>16)|0,e[1]=a[1]+(a[0]<<8|a[0]>>>24)+a[7]|0,e[2]=a[2]+(a[1]<<16|a[1]>>>16)+(a[0]<<16|a[0]>>>16)|0,e[3]=a[3]+(a[2]<<8|a[2]>>>24)+a[1]|0,e[4]=a[4]+(a[3]<<16|a[3]>>>16)+(a[2]<<16|a[2]>>>16)|0,e[5]=a[5]+(a[4]<<8|a[4]>>>24)+a[3]|0,e[6]=a[6]+(a[5]<<16|a[5]>>>16)+(a[4]<<16|a[4]>>>16)|0,e[7]=a[7]+(a[6]<<8|a[6]>>>24)+a[5]|0}e.Rabbit=t._createHelper(r)}(),n.Rabbit)}),Sr(function(e,t){var n;e.exports=(n=dh,function(){var e=n,t=e.lib.StreamCipher,r=e.algo,i=[],s=[],a=[],r=r.RabbitLegacy=t.extend({_doReset:function(){for(var e=this._key.words,t=this.cfg.iv,n=this._X=[e[0],e[3]<<16|e[2]>>>16,e[1],e[0]<<16|e[3]>>>16,e[2],e[1]<<16|e[0]>>>16,e[3],e[2]<<16|e[1]>>>16],r=this._C=[e[2]<<16|e[2]>>>16,4294901760&e[0]|65535&e[1],e[3]<<16|e[3]>>>16,4294901760&e[1]|65535&e[2],e[0]<<16|e[0]>>>16,4294901760&e[2]|65535&e[3],e[1]<<16|e[1]>>>16,4294901760&e[3]|65535&e[0]],i=this._b=0;i<4;i++)c.call(this);for(i=0;i<8;i++)r[i]^=n[i+4&7];if(t){var h=(t=16711935&((t=(e=t.words)[0])<<8|t>>>24)|4278255360&(t<<24|t>>>8))>>>16|4294901760&(e=16711935&((e=e[1])<<8|e>>>24)|4278255360&(e<<24|e>>>8)),f=e<<16|65535&t;for(r[0]^=t,r[1]^=h,r[2]^=e,r[3]^=f,r[4]^=t,r[5]^=h,r[6]^=e,r[7]^=f,i=0;i<4;i++)c.call(this)}},_doProcessBlock:function(e,t){var n=this._X;c.call(this),i[0]=n[0]^n[5]>>>16^n[3]<<16,i[1]=n[2]^n[7]>>>16^n[5]<<16,i[2]=n[4]^n[1]>>>16^n[7]<<16,i[3]=n[6]^n[3]>>>16^n[1]<<16;for(var r=0;r<4;r++)i[r]=16711935&(i[r]<<8|i[r]>>>24)|4278255360&(i[r]<<24|i[r]>>>8),e[t+r]^=i[r]},blockSize:4,ivSize:2});function c(){for(var e=this._X,t=this._C,n=0;n<8;n++)s[n]=t[n];for(t[0]=t[0]+1295307597+this._b|0,t[1]=t[1]+3545052371+(t[0]>>>0>>0?1:0)|0,t[2]=t[2]+886263092+(t[1]>>>0>>0?1:0)|0,t[3]=t[3]+1295307597+(t[2]>>>0>>0?1:0)|0,t[4]=t[4]+3545052371+(t[3]>>>0>>0?1:0)|0,t[5]=t[5]+886263092+(t[4]>>>0>>0?1:0)|0,t[6]=t[6]+1295307597+(t[5]>>>0>>0?1:0)|0,t[7]=t[7]+3545052371+(t[6]>>>0>>0?1:0)|0,this._b=t[7]>>>0>>0?1:0,n=0;n<8;n++){var r=e[n]+t[n],i=65535&r,o=r>>>16;a[n]=((i*i>>>17)+i*o>>>15)+o*o^((4294901760&r)*r|0)+((65535&r)*r|0)}e[0]=a[0]+(a[7]<<16|a[7]>>>16)+(a[6]<<16|a[6]>>>16)|0,e[1]=a[1]+(a[0]<<8|a[0]>>>24)+a[7]|0,e[2]=a[2]+(a[1]<<16|a[1]>>>16)+(a[0]<<16|a[0]>>>16)|0,e[3]=a[3]+(a[2]<<8|a[2]>>>24)+a[1]|0,e[4]=a[4]+(a[3]<<16|a[3]>>>16)+(a[2]<<16|a[2]>>>16)|0,e[5]=a[5]+(a[4]<<8|a[4]>>>24)+a[3]|0,e[6]=a[6]+(a[5]<<16|a[5]>>>16)+(a[4]<<16|a[4]>>>16)|0,e[7]=a[7]+(a[6]<<8|a[6]>>>24)+a[5]|0}e.RabbitLegacy=t._createHelper(r)}(),n.RabbitLegacy)}),Sr(function(e,t){e.exports=dh})),_h=function(t,n,r,i){return"string"==typeof(t="undefined"!=typeof Buffer&&Buffer.isBuffer(t)?t.toString():t)?function(e,t,n,r){return t.xmlMode||t._useHtmlParser2?function(e,t){var n=new w(void 0,t);return new va(n,t).end(e),n.root}(e,t):ta(e,t,n,r)}(t,n,r,i):(n=t,!Array.isArray(n)&&O(n)?n:(Bn(n,r=new v([])),r))},Eh=function(e){return function r(s,a,o){if(void 0===o&&(o=!0),null==s)throw new Error("cheerio.load() expects a string");var c=rr(rr({},n),i(a)),u=e(s,c,o,null),l=function(n){function r(){return null!==n&&n.apply(this,arguments)||this}return nr(r,n),r.prototype._make=function(e,t){return(e=h(e,t)).prevObject=this,e},r.prototype._parse=function(t,n,r,i){return e(t,n,r,i)},r.prototype._render=function(e){return function(e,t){return t.xmlMode||t._useHtmlParser2?W(e,t):function(e){for(var t,n=("length"in e?e:[e]),r=0;r0?this.children[this.children.length-1]:null}get childNodes(){return this.children}set childNodes(e){this.children=e}}class g extends A{constructor(){super(...arguments),this.type=e.CDATA}get nodeType(){return 4}}class N extends A{constructor(){super(...arguments),this.type=e.Root}get nodeType(){return 9}}class C extends A{constructor(t,n,r=[],i=("script"===t?e.Script:"style"===t?e.Style:e.Tag)){super(r),this.name=t,this.attribs=n,this.type=i}get nodeType(){return 1}get tagName(){return this.name}set tagName(e){this.name=e}get attributes(){return Object.keys(this.attribs).map((e=>{var t,n;return{name:e,value:this.attribs[e],namespace:null===(t=this["x-attribsNamespace"])||void 0===t?void 0:t[e],prefix:null===(n=this["x-attribsPrefix"])||void 0===n?void 0:n[e]}}))}}function I(t){return(n=t).type===e.Tag||n.type===e.Script||n.type===e.Style;var n}function S(t){return t.type===e.CDATA}function b(t){return t.type===e.Text}function O(t){return t.type===e.Comment}function y(t){return t.type===e.Directive}function L(t){return t.type===e.Root}function k(e){return Object.prototype.hasOwnProperty.call(e,"children")}function v(e,t=!1){let n;if(b(e))n=new m(e.data);else if(O(e))n=new T(e.data);else if(I(e)){const r=t?D(e.children):[],i=new C(e.name,{...e.attribs},r);r.forEach((e=>e.parent=i)),null!=e.namespace&&(i.namespace=e.namespace),e["x-attribsNamespace"]&&(i["x-attribsNamespace"]={...e["x-attribsNamespace"]}),e["x-attribsPrefix"]&&(i["x-attribsPrefix"]={...e["x-attribsPrefix"]}),n=i}else if(S(e)){const r=t?D(e.children):[],i=new g(r);r.forEach((e=>e.parent=i)),n=i}else if(L(e)){const r=t?D(e.children):[],i=new N(r);r.forEach((e=>e.parent=i)),e["x-mode"]&&(i["x-mode"]=e["x-mode"]),n=i}else{if(!y(e))throw new Error(`Not implemented yet: ${e.type}`);{const t=new _(e.name,e.data);null!=e["x-name"]&&(t["x-name"]=e["x-name"],t["x-publicId"]=e["x-publicId"],t["x-systemId"]=e["x-systemId"]),n=t}}return n.startIndex=e.startIndex,n.endIndex=e.endIndex,null!=e.sourceCodeLocation&&(n.sourceCodeLocation=e.sourceCodeLocation),n}function D(e){const t=e.map((e=>v(e,!0)));for(let e=1;e$\x80-\uFFFF]/g,x=new Map([[34,"""],[38,"&"],[39,"'"],[60,"<"],[62,">"]]),w=null!=String.prototype.codePointAt?(e,t)=>e.codePointAt(t):(e,t)=>55296==(64512&e.charCodeAt(t))?1024*(e.charCodeAt(t)-55296)+e.charCodeAt(t+1)-56320+65536:e.charCodeAt(t);function B(e){let t,n="",r=0;for(;null!==(t=P.exec(e));){const i=t.index,s=e.charCodeAt(i),a=x.get(s);void 0!==a?(n+=e.substring(r,i)+a,r=i+1):(n+=`${e.substring(r,i)}&#x${w(e,i).toString(16)};`,r=P.lastIndex+=Number(55296==(64512&s)))}return n+e.substr(r)}function F(e,t){return function(n){let r,i=0,s="";for(;r=e.exec(n);)i!==r.index&&(s+=n.substring(i,r.index)),s+=t.get(r[0].charCodeAt(0)),i=r.index+1;return s+n.substring(i)}}const U=F(/["&\u00A0]/g,new Map([[34,"""],[38,"&"],[160," "]])),H=F(/[&<>\u00A0]/g,new Map([[38,"&"],[60,"<"],[62,">"],[160," "]])),G=new Map(["altGlyph","altGlyphDef","altGlyphItem","animateColor","animateMotion","animateTransform","clipPath","feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feDropShadow","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feImage","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence","foreignObject","glyphRef","linearGradient","radialGradient","textPath"].map((e=>[e.toLowerCase(),e]))),Y=new Map(["definitionURL","attributeName","attributeType","baseFrequency","baseProfile","calcMode","clipPathUnits","diffuseConstant","edgeMode","filterUnits","glyphRef","gradientTransform","gradientUnits","kernelMatrix","kernelUnitLength","keyPoints","keySplines","keyTimes","lengthAdjust","limitingConeAngle","markerHeight","markerUnits","markerWidth","maskContentUnits","maskUnits","numOctaves","pathLength","patternContentUnits","patternTransform","patternUnits","pointsAtX","pointsAtY","pointsAtZ","preserveAlpha","preserveAspectRatio","primitiveUnits","refX","refY","repeatCount","repeatDur","requiredExtensions","requiredFeatures","specularConstant","specularExponent","spreadMethod","startOffset","stdDeviation","stitchTiles","surfaceScale","systemLanguage","tableValues","targetX","targetY","textLength","viewBox","viewTarget","xChannelSelector","yChannelSelector","zoomAndPan"].map((e=>[e.toLowerCase(),e]))),q=new Set(["style","script","xmp","iframe","noembed","noframes","plaintext","noscript"]);function K(e){return e.replace(/"/g,""")}const j=new Set(["area","base","basefont","br","col","command","embed","frame","hr","img","input","isindex","keygen","link","meta","param","source","track","wbr"]);function V(e,t={}){const n="length"in e?e:[e];let r="";for(let e=0;e`;case c:return function(e){return`\x3c!--${e.data}--\x3e`}(e);case p:return function(e){return``}(e);case l:case h:case u:return function(e,t){var n;"foreign"===t.xmlMode&&(e.name=null!==(n=G.get(e.name))&&void 0!==n?n:e.name,e.parent&&Q.has(e.parent.name)&&(t={...t,xmlMode:!1}));!t.xmlMode&&X.has(e.name)&&(t={...t,xmlMode:"foreign"});let r=`<${e.name}`;const i=function(e,t){var n;if(!e)return;const r=!1===(null!==(n=t.encodeEntities)&&void 0!==n?n:t.decodeEntities)?K:t.xmlMode||"utf8"!==t.encodeEntities?B:U;return Object.keys(e).map((n=>{var i,s;const a=null!==(i=e[n])&&void 0!==i?i:"";return"foreign"===t.xmlMode&&(n=null!==(s=Y.get(n))&&void 0!==s?s:n),t.emptyAttrs||t.xmlMode||""!==a?`${n}="${r(a)}"`:n})).join(" ")}(e.attribs,t);i&&(r+=` ${i}`);0===e.children.length&&(t.xmlMode?!1!==t.selfClosingTags:t.selfClosingTags&&j.has(e.name))?(t.xmlMode||(r+=" "),r+="/>"):(r+=">",e.children.length>0&&(r+=V(e.children,t)),!t.xmlMode&&j.has(e.name)||(r+=``));return r}(e,t);case a:return function(e,t){var n;let r=e.data||"";!1===(null!==(n=t.encodeEntities)&&void 0!==n?n:t.decodeEntities)||!t.xmlMode&&e.parent&&q.has(e.parent.name)||(r=t.xmlMode||"utf8"!==t.encodeEntities?B(r):H(r));return r}(e,t)}}const Q=new Set(["mi","mo","mn","ms","mtext","annotation-xml","foreignObject","desc","title"]),X=new Set(["svg","math"]);function $(e,t){return V(e,t)}function z(e){return Array.isArray(e)?e.map(z).join(""):k(e)&&!O(e)?z(e.children):b(e)?e.data:""}function J(t){return Array.isArray(t)?t.map(J).join(""):k(t)&&(t.type===e.Tag||S(t))?J(t.children):b(t)?t.data:""}function Z(e){return k(e)?e.children:[]}function ee(e){return e.parent||null}function te(e){const t=ee(e);if(null!=t)return Z(t);const n=[e];let{prev:r,next:i}=e;for(;null!=r;)n.unshift(r),({prev:r}=r);for(;null!=i;)n.push(i),({next:i}=i);return n}function ne(e){let{next:t}=e;for(;null!==t&&!I(t);)({next:t}=t);return t}function re(e){let{prev:t}=e;for(;null!==t&&!I(t);)({prev:t}=t);return t}function ie(e){if(e.prev&&(e.prev.next=e.next),e.next&&(e.next.prev=e.prev),e.parent){const t=e.parent.children;t.splice(t.lastIndexOf(e),1)}}function se(e,t,n=!0,r=1/0){return Array.isArray(t)||(t=[t]),ae(e,t,n,r)}function ae(e,t,n,r){const i=[];for(const s of t){if(e(s)&&(i.push(s),--r<=0))break;if(n&&k(s)&&s.children.length>0){const t=ae(e,s.children,n,r);if(i.push(...t),r-=t.length,r<=0)break}}return i}function oe(e,t,n=!0){let r=null;for(let i=0;i0&&(r=oe(e,s.children,!0)))}return r}const ce={tag_name:e=>"function"==typeof e?t=>I(t)&&e(t.name):"*"===e?I:t=>I(t)&&t.name===e,tag_type:e=>"function"==typeof e?t=>e(t.type):t=>t.type===e,tag_contains:e=>"function"==typeof e?t=>b(t)&&e(t.data):t=>b(t)&&t.data===e};function le(e,t){return"function"==typeof t?n=>I(n)&&t(n.attribs[e]):n=>I(n)&&n.attribs[e]===t}function he(e,t){return n=>e(n)||t(n)}function ue(e){const t=Object.keys(e).map((t=>{const n=e[t];return Object.prototype.hasOwnProperty.call(ce,t)?ce[t](n):le(t,n)}));return 0===t.length?null:t.reduce(he)}function pe(e,t,n=!0,r=1/0){return se(ce.tag_name(e),t,n,r)}var fe;function de(e,t){const n=[],r=[];if(e===t)return 0;let i=k(e)?e:e.parent;for(;i;)n.unshift(i),i=i.parent;for(i=k(t)?t:t.parent;i;)r.unshift(i),i=i.parent;const s=Math.min(n.length,r.length);let a=0;for(;ac.indexOf(h)?o===t?fe.FOLLOWING|fe.CONTAINED_BY:fe.FOLLOWING:o===e?fe.PRECEDING|fe.CONTAINS:fe.PRECEDING}function Ee(e){return(e=e.filter(((e,t,n)=>!n.includes(e,t+1)))).sort(((e,t)=>{const n=de(e,t);return n&fe.PRECEDING?-1:n&fe.FOLLOWING?1:0})),e}!function(e){e[e.DISCONNECTED=1]="DISCONNECTED",e[e.PRECEDING=2]="PRECEDING",e[e.FOLLOWING=4]="FOLLOWING",e[e.CONTAINS=8]="CONTAINS",e[e.CONTAINED_BY=16]="CONTAINED_BY"}(fe||(fe={}));const me=["url","type","lang"],Te=["fileSize","bitrate","framerate","samplingrate","channels","duration","height","width"];function _e(e){return pe("media:content",e).map((e=>{const{attribs:t}=e,n={medium:t.medium,isDefault:!!t.isDefault};for(const e of me)t[e]&&(n[e]=t[e]);for(const e of Te)t[e]&&(n[e]=parseInt(t[e],10));return t.expression&&(n.expression=t.expression),n}))}function Ae(e,t){return pe(e,t,!0,1)[0]}function ge(e,t,n=!1){return z(pe(e,t,n,1)).trim()}function Ne(e,t,n,r,i=!1){const s=ge(n,r,i);s&&(e[t]=s)}function Ce(e){return"rss"===e||"feed"===e||"rdf:RDF"===e}var Ie,Se=Object.freeze({__proto__:null,isTag:I,isCDATA:S,isText:b,isComment:O,isDocument:L,hasChildren:k,getOuterHTML:$,getInnerHTML:function(e,t){return k(e)?e.children.map((e=>$(e,t))).join(""):""},getText:function e(t){return Array.isArray(t)?t.map(e).join(""):I(t)?"br"===t.name?"\n":e(t.children):S(t)?e(t.children):b(t)?t.data:""},textContent:z,innerText:J,getChildren:Z,getParent:ee,getSiblings:te,getAttributeValue:function(e,t){var n;return null===(n=e.attribs)||void 0===n?void 0:n[t]},hasAttrib:function(e,t){return null!=e.attribs&&Object.prototype.hasOwnProperty.call(e.attribs,t)&&null!=e.attribs[t]},getName:function(e){return e.name},nextElementSibling:ne,prevElementSibling:re,removeElement:ie,replaceElement:function(e,t){const n=t.prev=e.prev;n&&(n.next=t);const r=t.next=e.next;r&&(r.prev=t);const i=t.parent=e.parent;if(i){const n=i.children;n[n.lastIndexOf(e)]=t,e.parent=null}},appendChild:function(e,t){if(ie(t),t.next=null,t.parent=e,e.children.push(t)>1){const n=e.children[e.children.length-2];n.next=t,t.prev=n}else t.prev=null},append:function(e,t){ie(t);const{parent:n}=e,r=e.next;if(t.next=r,t.prev=e,e.next=t,t.parent=n,r){if(r.prev=t,n){const e=n.children;e.splice(e.lastIndexOf(r),0,t)}}else n&&n.children.push(t)},prependChild:function(e,t){if(ie(t),t.parent=e,t.prev=null,1!==e.children.unshift(t)){const n=e.children[1];n.prev=t,t.next=n}else t.next=null},prepend:function(e,t){ie(t);const{parent:n}=e;if(n){const r=n.children;r.splice(r.indexOf(e),0,t)}e.prev&&(e.prev.next=t),t.parent=n,t.prev=e.prev,t.next=e,e.prev=t},filter:se,find:ae,findOneChild:function(e,t){return t.find(e)},findOne:oe,existsOne:function e(t,n){return n.some((n=>I(n)&&(t(n)||n.children.length>0&&e(t,n.children))))},findAll:function(e,t){var n;const r=[],i=t.filter(I);let s;for(;s=i.shift();){const t=null===(n=s.children)||void 0===n?void 0:n.filter(I);t&&t.length>0&&i.unshift(...t),e(s)&&r.push(s)}return r},testElement:function(e,t){const n=ue(e);return!n||n(t)},getElements:function(e,t,n,r=1/0){const i=ue(e);return i?se(i,t,n,r):[]},getElementById:function(e,t,n=!0){return Array.isArray(t)||(t=[t]),oe(le("id",e),t,n)},getElementsByTagName:pe,getElementsByTagType:function(e,t,n=!0,r=1/0){return se(ce.tag_type(e),t,n,r)},removeSubsets:function(e){let t=e.length;for(;--t>=0;){const n=e[t];if(t>0&&e.lastIndexOf(n,t-1)>=0)e.splice(t,1);else for(let r=n.parent;r;r=r.parent)if(e.includes(r)){e.splice(t,1);break}}return e},get DocumentPosition(){return fe},compareDocumentPosition:de,uniqueSort:Ee,getFeed:function(e){const t=Ae(Ce,e);return t?"feed"===t.name?function(e){var t;const n=e.children,r={type:"atom",items:pe("entry",n).map((e=>{var t;const{children:n}=e,r={media:_e(n)};Ne(r,"id","id",n),Ne(r,"title","title",n);const i=null===(t=Ae("link",n))||void 0===t?void 0:t.attribs.href;i&&(r.link=i);const s=ge("summary",n)||ge("content",n);s&&(r.description=s);const a=ge("updated",n);return a&&(r.pubDate=new Date(a)),r}))};Ne(r,"id","id",n),Ne(r,"title","title",n);const i=null===(t=Ae("link",n))||void 0===t?void 0:t.attribs.href;i&&(r.link=i);Ne(r,"description","subtitle",n);const s=ge("updated",n);s&&(r.updated=new Date(s));return Ne(r,"author","email",n,!0),r}(t):function(e){var t,n;const r=null!==(n=null===(t=Ae("channel",e.children))||void 0===t?void 0:t.children)&&void 0!==n?n:[],i={type:e.name.substr(0,3),id:"",items:pe("item",e.children).map((e=>{const{children:t}=e,n={media:_e(t)};Ne(n,"id","guid",t),Ne(n,"title","title",t),Ne(n,"link","link",t),Ne(n,"description","description",t);const r=ge("pubDate",t);return r&&(n.pubDate=new Date(r)),n}))};Ne(i,"title","title",r),Ne(i,"link","link",r),Ne(i,"description","description",r);const s=ge("lastBuildDate",r);s&&(i.updated=new Date(s));return Ne(i,"author","managingEditor",r,!0),i}(t):null}}),be="undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{};function Oe(e){if(Object.keys)return Object.keys(e);var t=[];for(var n in e)t.push(n);return t}function ye(e,t){if(e.forEach)return e.forEach(t);for(var n=0;n1&&c.shift(),this._hasParentSelector=null;const l=this._trace(c,t,["$"],i,s,n).filter((function(e){return e&&!e.isParentSelector}));return l.length?o||1!==l.length||l[0].hasArrExpr?l.reduce(((e,t)=>{const n=this._getPreferredOutput(t);return a&&Array.isArray(n)?e=e.concat(n):e.push(n),e}),[]):this._getPreferredOutput(l[0]):o?[]:void 0},He.prototype._getPreferredOutput=function(e){const t=this.currResultType;switch(t){case"all":{const t=Array.isArray(e.path)?e.path:He.toPathArray(e.path);return e.pointer=He.toPointer(t),e.path="string"==typeof e.path?e.path:He.toPathString(e.path),e}case"value":case"parent":case"parentProperty":return e[t];case"path":return He.toPathString(e[t]);case"pointer":return He.toPointer(e.path);default:throw new TypeError("Unknown result type")}},He.prototype._handleCallback=function(e,t,n){if(t){const r=this._getPreferredOutput(e);e.path="string"==typeof e.path?e.path:He.toPathString(e.path),t(r,n,e)}},He.prototype._trace=function(e,t,n,r,i,s,a,o){let c;if(!e.length)return c={path:n,value:t,parent:r,parentProperty:i,hasArrExpr:a},this._handleCallback(c,s,"value"),c;const l=e[0],h=e.slice(1),u=[];function p(e){Array.isArray(e)?e.forEach((e=>{u.push(e)})):u.push(e)}if(("string"!=typeof l||o)&&t&&we.call(t,l))p(this._trace(h,t[l],Be(n,l),t,l,s,a));else if("*"===l)this._walk(t,(e=>{p(this._trace(h,t[e],Be(n,e),t,e,s,!0,!0))}));else if(".."===l)p(this._trace(h,t,n,r,i,s,a)),this._walk(t,(r=>{"object"==typeof t[r]&&p(this._trace(e.slice(),t[r],Be(n,r),t,r,s,!0))}));else{if("^"===l)return this._hasParentSelector=!0,{path:n.slice(0,-1),expr:h,isParentSelector:!0};if("~"===l)return c={path:Be(n,l),value:i,parent:r,parentProperty:null},this._handleCallback(c,s,"property"),c;if("$"===l)p(this._trace(h,t,n,null,null,s,a));else if(/^(-?\d*):(-?\d*):?(\d*)$/u.test(l))p(this._slice(l,h,t,n,r,i,s));else if(0===l.indexOf("?(")){if(this.currPreventEval)throw new Error("Eval [?(expr)] prevented in JSONPath expression.");const e=l.replace(/^\?\((.*?)\)$/u,"$1");this._walk(t,(a=>{this._eval(e,t[a],a,n,r,i)&&p(this._trace(h,t[a],Be(n,a),t,a,s,!0))}))}else if("("===l[0]){if(this.currPreventEval)throw new Error("Eval [(expr)] prevented in JSONPath expression.");p(this._trace(Fe(this._eval(l,t,n[n.length-1],n.slice(0,-1),r,i),h),t,n,r,i,s,a))}else if("@"===l[0]){let e=!1;const a=l.slice(1,-2);switch(a){case"scalar":t&&["object","function"].includes(typeof t)||(e=!0);break;case"boolean":case"string":case"undefined":case"function":typeof t===a&&(e=!0);break;case"integer":!Number.isFinite(t)||t%1||(e=!0);break;case"number":Number.isFinite(t)&&(e=!0);break;case"nonFinite":"number"!=typeof t||Number.isFinite(t)||(e=!0);break;case"object":t&&typeof t===a&&(e=!0);break;case"array":Array.isArray(t)&&(e=!0);break;case"other":e=this.currOtherTypeCallback(t,n,r,i);break;case"null":null===t&&(e=!0);break;default:throw new TypeError("Unknown value type "+a)}if(e)return c={path:n,value:t,parent:r,parentProperty:i},this._handleCallback(c,s,"value"),c}else if("`"===l[0]&&t&&we.call(t,l.slice(1))){const e=l.slice(1);p(this._trace(h,t[e],Be(n,e),t,e,s,a,!0))}else if(l.includes(",")){const e=l.split(",");for(const a of e)p(this._trace(Fe(a,h),t,n,r,i,s,!0))}else!o&&t&&we.call(t,l)&&p(this._trace(h,t[l],Be(n,l),t,l,s,a,!0))}if(this._hasParentSelector)for(let e=0;e{t(e)}))},He.prototype._slice=function(e,t,n,r,i,s,a){if(!Array.isArray(n))return;const o=n.length,c=e.split(":"),l=c[2]&&Number.parseInt(c[2])||1;let h=c[0]&&Number.parseInt(c[0])||0,u=c[1]&&Number.parseInt(c[1])||o;h=h<0?Math.max(0,h+o):Math.min(o,h),u=u<0?Math.max(0,u+o):Math.min(o,u);const p=[];for(let e=h;e{p.push(e)}))}return p},He.prototype._eval=function(e,t,n,r,i,s){this.currSandbox._$_parentProperty=s,this.currSandbox._$_parent=i,this.currSandbox._$_property=n,this.currSandbox._$_root=this.json,this.currSandbox._$_v=t;const a=e.includes("@path");a&&(this.currSandbox._$_path=He.toPathString(r.concat([n])));const o="script:"+e;if(!He.cache[o]){let t=e.replace(/@parentProperty/gu,"_$_parentProperty").replace(/@parent/gu,"_$_parent").replace(/@property/gu,"_$_property").replace(/@root/gu,"_$_root").replace(/@([.\s)[])/gu,"_$_v$1");a&&(t=t.replace(/@path/gu,"_$_path")),He.cache[o]=new this.vm.Script(t)}try{return He.cache[o].runInNewContext(this.currSandbox)}catch(t){throw new Error("jsonPath: "+t.message+": "+e)}},He.cache={},He.toPathString=function(e){const t=e,n=t.length;let r="$";for(let e=1;e":">"},i=/[&"'<>]/g,s=e.exports={};function a(e,t){return n.hasOwnProperty.call(e,t)}function o(e){return r[e]}function c(e,t,n){var r,i,s;if(e instanceof Error&&(e=(i=e).name+": "+i.message),Object.setPrototypeOf?(r=new Error(e),Object.setPrototypeOf(r,c.prototype)):(r=this,Object.defineProperty(r,"message",{enumerable:!1,writable:!0,value:e})),Object.defineProperty(r,"name",{value:"Template render error"}),Error.captureStackTrace&&Error.captureStackTrace(r,this.constructor),i){var a=Object.getOwnPropertyDescriptor(i,"stack");(s=a&&(a.get||function(){return a.value}))||(s=function(){return i.stack})}else{var o=new Error(e).stack;s=function(){return o}}return Object.defineProperty(r,"stack",{get:function(){return s.call(r)}}),Object.defineProperty(r,"cause",{value:i}),r.lineno=t,r.colno=n,r.firstUpdate=!0,r.Update=function(e){var t="("+(e||"unknown path")+")";return this.firstUpdate&&(this.lineno&&this.colno?t+=" [Line "+this.lineno+", Column "+this.colno+"]":this.lineno&&(t+=" [Line "+this.lineno+"]")),t+="\n ",this.firstUpdate&&(t+=" "),this.message=t+(this.message||""),this.firstUpdate=!1,this},r}function l(e){return"[object Function]"===n.toString.call(e)}function h(e){return"[object Array]"===n.toString.call(e)}function u(e){return"[object String]"===n.toString.call(e)}function p(e){return"[object Object]"===n.toString.call(e)}function f(e){var t=function(e){return e?"string"==typeof e?e.split("."):[e]:[]}(e);return function(e){for(var n=e,r=0;r1)for(var n=1;n0&&a.length>i){a.warned=!0;var o=new Error("Possible EventEmitter memory leak detected. "+a.length+" "+t+" listeners added. Use emitter.setMaxListeners() to increase limit");o.name="MaxListenersExceededWarning",o.emitter=e,o.type=t,o.count=a.length,function(e){"function"==typeof console.warn?console.warn(e):console.log(e)}(o)}}else a=s[t]=n,++e._eventsCount;return e}function bt(e,t,n){var r=!1;function i(){e.removeListener(t,i),r||(r=!0,n.apply(e,arguments))}return i.listener=n,i}function Ot(e){var t=this._events;if(t){var n=t[e];if("function"==typeof n)return 1;if(n)return n.length}return 0}function yt(e,t){for(var n=new Array(t);t--;)n[t]=e[t];return n}function Lt(){var e;Tt.call(this),this.__emitError=(e=this,function(t){e.emit("error",t)})}function kt(){return new Lt}mt.prototype=Object.create(null),Tt.EventEmitter=Tt,Tt.usingDomains=!1,Tt.prototype.domain=void 0,Tt.prototype._events=void 0,Tt.prototype._maxListeners=void 0,Tt.defaultMaxListeners=10,Tt.init=function(){this.domain=null,Tt.usingDomains&&undefined.active,this._events&&this._events!==Object.getPrototypeOf(this)._events||(this._events=new mt,this._eventsCount=0),this._maxListeners=this._maxListeners||void 0},Tt.prototype.setMaxListeners=function(e){if("number"!=typeof e||e<0||isNaN(e))throw new TypeError('"n" argument must be a positive number');return this._maxListeners=e,this},Tt.prototype.getMaxListeners=function(){return _t(this)},Tt.prototype.emit=function(e){var t,n,r,i,s,a,o,c="error"===e;if(a=this._events)c=c&&null==a.error;else if(!c)return!1;if(o=this.domain,c){if(t=arguments[1],!o){if(t instanceof Error)throw t;var l=new Error('Uncaught, unspecified "error" event. ('+t+")");throw l.context=t,l}return t||(t=new Error('Uncaught, unspecified "error" event')),t.domainEmitter=this,t.domain=o,t.domainThrown=!1,o.emit("error",t),!1}if(!(n=a[e]))return!1;var h="function"==typeof n;switch(r=arguments.length){case 1:At(n,h,this);break;case 2:gt(n,h,this,arguments[1]);break;case 3:Nt(n,h,this,arguments[1],arguments[2]);break;case 4:Ct(n,h,this,arguments[1],arguments[2],arguments[3]);break;default:for(i=new Array(r-1),s=1;s0;)if(n[s]===t||n[s].listener&&n[s].listener===t){a=n[s].listener,i=s;break}if(i<0)return this;if(1===n.length){if(n[0]=void 0,0==--this._eventsCount)return this._events=new mt,this;delete r[e]}else!function(e,t){for(var n=t,r=n+1,i=e.length;r0?Reflect.ownKeys(this._events):[]},("function"==typeof Object.create?function(e,t){e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})}:function(e,t){e.super_=t;var n=function(){};n.prototype=t.prototype,e.prototype=new n,e.prototype.constructor=e})(Lt,Tt),Lt.prototype.add=function(e){e.on("error",this.__emitError)},Lt.prototype.remove=function(e){e.removeListener("error",this.__emitError)},Lt.prototype.bind=function(e){var t=this.__emitError;return function(){var n=Array.prototype.slice.call(arguments);try{e.apply(null,n)}catch(e){t(e)}}},Lt.prototype.intercept=function(e){var t=this.__emitError;return function(n){if(n)t(n);else{var r=Array.prototype.slice.call(arguments,1);try{e.apply(null,r)}catch(n){t(n)}}}},Lt.prototype.run=function(e){var t=this.__emitError;try{e()}catch(e){t(e)}return this},Lt.prototype.dispose=function(){return this.removeAllListeners(),this},Lt.prototype.enter=Lt.prototype.exit=function(){return this};var vt,Dt={Domain:Lt,createDomain:kt,create:kt},Rt="function"==typeof setImmediate,Mt=Pt;function Pt(e){xt.length||(Ut(),wt=!0),xt[xt.length]=e}var xt=[],wt=!1,Bt=0;function Ft(){for(;Bt1024){for(var t=0,n=xt.length-Bt;t=!".indexOf(r)){this.forward();var o,c=["==","===","!=","!==","<=",">=","//","**"],l=r+this.current();switch(-1!==je.indexOf(c,l)&&(this.forward(),r=l,-1!==je.indexOf(c,l+this.current())&&(r=l+this.current(),this.forward())),r){case"(":o="left-paren";break;case")":o="right-paren";break;case"[":o="left-bracket";break;case"]":o="right-bracket";break;case"{":o="left-curly";break;case"}":o="right-curly";break;case",":o="comma";break;case":":o="colon";break;case"~":o="tilde";break;case"|":o="pipe";break;default:o="operator"}return Kt(o,r,t,n)}if((e=this._extractUntil(" \n\t\r ()[]{}%*-+~/#,:|.<>=!")).match(/^[-+]?[0-9]+$/))return"."===this.current()?(this.forward(),Kt("float",e+"."+this._extract("0123456789"),t,n)):Kt("int",e,t,n);if(e.match(/^(true|false)$/))return Kt("boolean",e,t,n);if("none"===e)return Kt("none",e,t,n);if("null"===e)return Kt("none",e,t,n);if(e)return Kt("symbol",e,t,n);throw new Error("Unexpected value while parsing: "+e)}var h,u=this.tags.BLOCK_START.charAt(0)+this.tags.VARIABLE_START.charAt(0)+this.tags.COMMENT_START.charAt(0)+this.tags.COMMENT_END.charAt(0);if(this.isFinished())return null;if((e=this._extractString(this.tags.BLOCK_START+"-"))||(e=this._extractString(this.tags.BLOCK_START)))return this.in_code=!0,Kt("block-start",e,t,n);if((e=this._extractString(this.tags.VARIABLE_START+"-"))||(e=this._extractString(this.tags.VARIABLE_START)))return this.in_code=!0,Kt("variable-start",e,t,n);e="";var p=!1;for(this._matches(this.tags.COMMENT_START)&&(p=!0,e=this._extractString(this.tags.COMMENT_START));null!==(h=this._extractUntil(u));){if(e+=h,(this._matches(this.tags.BLOCK_START)||this._matches(this.tags.VARIABLE_START)||this._matches(this.tags.COMMENT_START))&&!p){if(this.lstripBlocks&&this._matches(this.tags.BLOCK_START)&&this.colno>0&&this.colno<=e.length){var f=e.slice(-this.colno);if(/^\s+$/.test(f)&&!(e=e.slice(0,-this.colno)).length)return this.nextToken()}break}if(this._matches(this.tags.COMMENT_END)){if(!p)throw new Error("unexpected end of comment");e+=this._extractString(this.tags.COMMENT_END);break}e+=this.current(),this.forward()}if(null===h&&p)throw new Error("expected end of comment, got end of file");return Kt(p?"comment":"data",e,t,n)},t._parseString=function(e){this.forward();for(var t="";!this.isFinished()&&this.current()!==e;){var n=this.current();if("\\"===n){switch(this.forward(),this.current()){case"n":t+="\n";break;case"t":t+="\t";break;case"r":t+="\r";break;default:t+=this.current()}this.forward()}else t+=n,this.forward()}return this.forward(),t},t._matches=function(e){return this.index+e.length>this.len?null:this.str.slice(this.index,this.index+e.length)===e},t._extractString=function(e){return this._matches(e)?(this.forwardN(e.length),e):null},t._extractUntil=function(e){return this._extractMatching(!0,e||"")},t._extract=function(e){return this._extractMatching(!1,e)},t._extractMatching=function(e,t){if(this.isFinished())return null;var n=t.indexOf(this.current());if(e&&-1===n||!e&&-1!==n){var r=this.current();this.forward();for(var i=t.indexOf(this.current());(e&&-1===i||!e&&-1!==i)&&!this.isFinished();)r+=this.current(),this.forward(),i=t.indexOf(this.current());return r}return""},t._extractRegex=function(e){var t=this.currentStr().match(e);return t?(this.forwardN(t[0].length),t):null},t.isFinished=function(){return this.index>=this.len},t.forwardN=function(e){for(var t=0;t2?i-2:0),a=2;a0||!n)&&Et.stdout.write(" ".repeat(t));var s=i===r.length-1?"":"\n";Et.stdout.write(""+e+s)}))}var hr={Node:on,Root:hn,NodeList:ln,Value:cn,Literal:un,Symbol:pn,Group:fn,Array:dn,Pair:En,Dict:mn,Output:Un,Capture:Hn,TemplateData:Gn,If:_n,IfAsync:An,InlineIf:gn,For:Nn,AsyncEach:Cn,AsyncAll:In,Macro:Sn,Caller:bn,Import:On,FromImport:yn,FunCall:Ln,Filter:kn,FilterAsync:vn,KeywordArgs:Dn,Block:Rn,Super:Mn,Extends:Pn,Include:xn,Set:wn,Switch:Bn,Case:Fn,LookupVal:Tn,BinOp:qn,In:Kn,Is:jn,Or:Vn,And:Wn,Not:Qn,Add:Xn,Concat:$n,Sub:zn,Mul:Jn,Div:Zn,FloorDiv:er,Mod:tr,Pow:nr,Neg:rr,Pos:ir,Compare:sr,CompareOperand:ar,CallExtension:or,CallExtensionAsync:cr,printNodes:function e(t,n){if(n=n||0,lr(t.typename+": ",n),t instanceof ln)lr("\n"),t.children.forEach((function(t){e(t,n+2)}));else if(t instanceof or)lr(t.extName+"."+t.prop+"\n"),t.args&&e(t.args,n+2),t.contentArgs&&t.contentArgs.forEach((function(t){e(t,n+2)}));else{var r=[],i=null;t.iterFields((function(e,t){e instanceof on?r.push([t,e]):(i=i||{})[t]=e})),i?lr(JSON.stringify(i,null,2)+"\n",null,!0):lr("\n"),r.forEach((function(t){var r=t[0],i=t[1];lr("["+r+"] =>",n+2),e(i,n+4)}))}}};function ur(e,t){return ur=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e},ur(e,t)}hr.Node,hr.Root,hr.NodeList,hr.Value,hr.Literal,hr.Group,hr.Pair,hr.Dict,hr.Output,hr.Capture,hr.TemplateData,hr.If,hr.IfAsync,hr.InlineIf,hr.For,hr.AsyncEach,hr.AsyncAll,hr.Macro,hr.Caller,hr.Import,hr.FromImport,hr.FunCall,hr.Filter,hr.FilterAsync,hr.KeywordArgs,hr.Block,hr.Super,hr.Extends,hr.Include,hr.Switch,hr.Case,hr.LookupVal,hr.BinOp,hr.In,hr.Is,hr.Or,hr.And,hr.Not,hr.Add,hr.Concat,hr.Sub,hr.Mul,hr.Div,hr.FloorDiv,hr.Mod,hr.Pow,hr.Neg,hr.Pos,hr.Compare,hr.CompareOperand,hr.CallExtension,hr.CallExtensionAsync,hr.printNodes;var pr=function(e){var t,n;function r(){return e.apply(this,arguments)||this}n=e,(t=r).prototype=Object.create(n.prototype),t.prototype.constructor=t,ur(t,n);var i=r.prototype;return i.init=function(e){this.tokens=e,this.peeked=null,this.breakOnBlocks=null,this.dropLeadingWhitespace=!1,this.extensions=[]},i.nextToken=function(e){var t;if(this.peeked){if(e||this.peeked.type!==Vt.TOKEN_WHITESPACE)return t=this.peeked,this.peeked=null,t;this.peeked=null}if(t=this.tokens.nextToken(),!e)for(;t&&t.type===Vt.TOKEN_WHITESPACE;)t=this.tokens.nextToken();return t},i.peekToken=function(){return this.peeked=this.peeked||this.nextToken(),this.peeked},i.pushToken=function(e){if(this.peeked)throw new Error("pushToken: can only push one token on between reads");this.peeked=e},i.error=function(e,t,n){if(void 0===t||void 0===n){var r=this.peekToken()||{};t=r.lineno,n=r.colno}return void 0!==t&&(t+=1),void 0!==n&&(n+=1),new je.TemplateError(e,t,n)},i.fail=function(e,t,n){throw this.error(e,t,n)},i.skip=function(e){var t=this.nextToken();return!(!t||t.type!==e)||(this.pushToken(t),!1)},i.expect=function(e){var t=this.nextToken();return t.type!==e&&this.fail("expected "+e+", got "+t.type,t.lineno,t.colno),t},i.skipValue=function(e,t){var n=this.nextToken();return!(!n||n.type!==e||n.value!==t)||(this.pushToken(n),!1)},i.skipSymbol=function(e){return this.skipValue(Vt.TOKEN_SYMBOL,e)},i.advanceAfterBlockEnd=function(e){var t;return e||((t=this.peekToken())||this.fail("unexpected end of file"),t.type!==Vt.TOKEN_SYMBOL&&this.fail("advanceAfterBlockEnd: expected symbol token or explicit name to be passed"),e=this.nextToken().value),(t=this.nextToken())&&t.type===Vt.TOKEN_BLOCK_END?"-"===t.value.charAt(0)&&(this.dropLeadingWhitespace=!0):this.fail("expected block end in "+e+" statement"),t},i.advanceAfterVariableEnd=function(){var e=this.nextToken();e&&e.type===Vt.TOKEN_VARIABLE_END?this.dropLeadingWhitespace="-"===e.value.charAt(e.value.length-this.tokens.tags.VARIABLE_END.length-1):(this.pushToken(e),this.fail("expected variable end"))},i.parseFor=function(){var e,t,n=this.peekToken();if(this.skipSymbol("for")?(e=new hr.For(n.lineno,n.colno),t="endfor"):this.skipSymbol("asyncEach")?(e=new hr.AsyncEach(n.lineno,n.colno),t="endeach"):this.skipSymbol("asyncAll")?(e=new hr.AsyncAll(n.lineno,n.colno),t="endall"):this.fail("parseFor: expected for{Async}",n.lineno,n.colno),e.name=this.parsePrimary(),e.name instanceof hr.Symbol||this.fail("parseFor: variable name expected for loop"),this.peekToken().type===Vt.TOKEN_COMMA){var r=e.name;for(e.name=new hr.Array(r.lineno,r.colno),e.name.addChild(r);this.skip(Vt.TOKEN_COMMA);){var i=this.parsePrimary();e.name.addChild(i)}}return this.skipSymbol("in")||this.fail('parseFor: expected "in" keyword for loop',n.lineno,n.colno),e.arr=this.parseExpression(),this.advanceAfterBlockEnd(n.value),e.body=this.parseUntilBlocks(t,"else"),this.skipSymbol("else")&&(this.advanceAfterBlockEnd("else"),e.else_=this.parseUntilBlocks(t)),this.advanceAfterBlockEnd(),e},i.parseMacro=function(){var e=this.peekToken();this.skipSymbol("macro")||this.fail("expected macro");var t=this.parsePrimary(!0),n=this.parseSignature(),r=new hr.Macro(e.lineno,e.colno,t,n);return this.advanceAfterBlockEnd(e.value),r.body=this.parseUntilBlocks("endmacro"),this.advanceAfterBlockEnd(),r},i.parseCall=function(){var e=this.peekToken();this.skipSymbol("call")||this.fail("expected call");var t=this.parseSignature(!0)||new hr.NodeList,n=this.parsePrimary();this.advanceAfterBlockEnd(e.value);var r=this.parseUntilBlocks("endcall");this.advanceAfterBlockEnd();var i=new hr.Symbol(e.lineno,e.colno,"caller"),s=new hr.Caller(e.lineno,e.colno,i,t,r),a=n.args.children;return a[a.length-1]instanceof hr.KeywordArgs||a.push(new hr.KeywordArgs),a[a.length-1].addChild(new hr.Pair(e.lineno,e.colno,i,s)),new hr.Output(e.lineno,e.colno,[n])},i.parseWithContext=function(){var e=this.peekToken(),t=null;return this.skipSymbol("with")?t=!0:this.skipSymbol("without")&&(t=!1),null!==t&&(this.skipSymbol("context")||this.fail("parseFrom: expected context after with/without",e.lineno,e.colno)),t},i.parseImport=function(){var e=this.peekToken();this.skipSymbol("import")||this.fail("parseImport: expected import",e.lineno,e.colno);var t=this.parseExpression();this.skipSymbol("as")||this.fail('parseImport: expected "as" keyword',e.lineno,e.colno);var n=this.parseExpression(),r=this.parseWithContext(),i=new hr.Import(e.lineno,e.colno,t,n,r);return this.advanceAfterBlockEnd(e.value),i},i.parseFrom=function(){var e=this.peekToken();this.skipSymbol("from")||this.fail("parseFrom: expected from");var t=this.parseExpression();this.skipSymbol("import")||this.fail("parseFrom: expected import",e.lineno,e.colno);for(var n,r=new hr.NodeList;;){var i=this.peekToken();if(i.type===Vt.TOKEN_BLOCK_END){r.children.length||this.fail("parseFrom: Expected at least one import name",e.lineno,e.colno),"-"===i.value.charAt(0)&&(this.dropLeadingWhitespace=!0),this.nextToken();break}r.children.length>0&&!this.skip(Vt.TOKEN_COMMA)&&this.fail("parseFrom: expected comma",e.lineno,e.colno);var s=this.parsePrimary();if("_"===s.value.charAt(0)&&this.fail("parseFrom: names starting with an underscore cannot be imported",s.lineno,s.colno),this.skipSymbol("as")){var a=this.parsePrimary();r.addChild(new hr.Pair(s.lineno,s.colno,s,a))}else r.addChild(s);n=this.parseWithContext()}return new hr.FromImport(e.lineno,e.colno,t,r,n)},i.parseBlock=function(){var e=this.peekToken();this.skipSymbol("block")||this.fail("parseBlock: expected block",e.lineno,e.colno);var t=new hr.Block(e.lineno,e.colno);t.name=this.parsePrimary(),t.name instanceof hr.Symbol||this.fail("parseBlock: variable name expected",e.lineno,e.colno),this.advanceAfterBlockEnd(e.value),t.body=this.parseUntilBlocks("endblock"),this.skipSymbol("endblock"),this.skipSymbol(t.name.value);var n=this.peekToken();return n||this.fail("parseBlock: expected endblock, got end of file"),this.advanceAfterBlockEnd(n.value),t},i.parseExtends=function(){var e="extends",t=this.peekToken();this.skipSymbol(e)||this.fail("parseTemplateRef: expected extends");var n=new hr.Extends(t.lineno,t.colno);return n.template=this.parseExpression(),this.advanceAfterBlockEnd(t.value),n},i.parseInclude=function(){var e="include",t=this.peekToken();this.skipSymbol(e)||this.fail("parseInclude: expected include");var n=new hr.Include(t.lineno,t.colno);return n.template=this.parseExpression(),this.skipSymbol("ignore")&&this.skipSymbol("missing")&&(n.ignoreMissing=!0),this.advanceAfterBlockEnd(t.value),n},i.parseIf=function(){var e,t=this.peekToken();this.skipSymbol("if")||this.skipSymbol("elif")||this.skipSymbol("elseif")?e=new hr.If(t.lineno,t.colno):this.skipSymbol("ifAsync")?e=new hr.IfAsync(t.lineno,t.colno):this.fail("parseIf: expected if, elif, or elseif",t.lineno,t.colno),e.cond=this.parseExpression(),this.advanceAfterBlockEnd(t.value),e.body=this.parseUntilBlocks("elif","elseif","else","endif");var n=this.peekToken();switch(n&&n.value){case"elseif":case"elif":e.else_=this.parseIf();break;case"else":this.advanceAfterBlockEnd(),e.else_=this.parseUntilBlocks("endif"),this.advanceAfterBlockEnd();break;case"endif":e.else_=null,this.advanceAfterBlockEnd();break;default:this.fail("parseIf: expected elif, else, or endif, got end of file")}return e},i.parseSet=function(){var e=this.peekToken();this.skipSymbol("set")||this.fail("parseSet: expected set",e.lineno,e.colno);for(var t,n=new hr.Set(e.lineno,e.colno,[]);(t=this.parsePrimary())&&(n.targets.push(t),this.skip(Vt.TOKEN_COMMA)););return this.skipValue(Vt.TOKEN_OPERATOR,"=")?(n.value=this.parseExpression(),this.advanceAfterBlockEnd(e.value)):this.skip(Vt.TOKEN_BLOCK_END)?(n.body=new hr.Capture(e.lineno,e.colno,this.parseUntilBlocks("endset")),n.value=null,this.advanceAfterBlockEnd()):this.fail("parseSet: expected = or block end in set tag",e.lineno,e.colno),n},i.parseSwitch=function(){var e="switch",t="endswitch",n="case",r="default",i=this.peekToken();this.skipSymbol(e)||this.skipSymbol(n)||this.skipSymbol(r)||this.fail('parseSwitch: expected "switch," "case" or "default"',i.lineno,i.colno);var s=this.parseExpression();this.advanceAfterBlockEnd(e),this.parseUntilBlocks(n,r,t);var a,o=this.peekToken(),c=[];do{this.skipSymbol(n);var l=this.parseExpression();this.advanceAfterBlockEnd(e);var h=this.parseUntilBlocks(n,r,t);c.push(new hr.Case(o.line,o.col,l,h)),o=this.peekToken()}while(o&&o.value===n);switch(o.value){case r:this.advanceAfterBlockEnd(),a=this.parseUntilBlocks(t),this.advanceAfterBlockEnd();break;case t:this.advanceAfterBlockEnd();break;default:this.fail('parseSwitch: expected "case," "default" or "endswitch," got EOF.')}return new hr.Switch(i.lineno,i.colno,s,c,a)},i.parseStatement=function(){var e=this.peekToken();if(e.type!==Vt.TOKEN_SYMBOL&&this.fail("tag name expected",e.lineno,e.colno),this.breakOnBlocks&&-1!==je.indexOf(this.breakOnBlocks,e.value))return null;switch(e.value){case"raw":return this.parseRaw();case"verbatim":return this.parseRaw("verbatim");case"if":case"ifAsync":return this.parseIf();case"for":case"asyncEach":case"asyncAll":return this.parseFor();case"block":return this.parseBlock();case"extends":return this.parseExtends();case"include":return this.parseInclude();case"set":return this.parseSet();case"macro":return this.parseMacro();case"call":return this.parseCall();case"import":return this.parseImport();case"from":return this.parseFrom();case"filter":return this.parseFilterStatement();case"switch":return this.parseSwitch();default:if(this.extensions.length)for(var t=0;t0;){var o=s[0],c=s[1],l=s[2];l===e?r+=1:l===t&&(r-=1),0===r?(i+=c,this.tokens.backN(o.length-c.length)):i+=o}return new hr.Output(a.lineno,a.colno,[new hr.TemplateData(a.lineno,a.colno,i)])},i.parsePostfix=function(e){for(var t,n=this.peekToken();n;){if(n.type===Vt.TOKEN_LEFT_PAREN)e=new hr.FunCall(n.lineno,n.colno,e,this.parseSignature());else if(n.type===Vt.TOKEN_LEFT_BRACKET)(t=this.parseAggregate()).children.length>1&&this.fail("invalid index"),e=new hr.LookupVal(n.lineno,n.colno,e,t.children[0]);else{if(n.type!==Vt.TOKEN_OPERATOR||"."!==n.value)break;this.nextToken();var r=this.nextToken();r.type!==Vt.TOKEN_SYMBOL&&this.fail("expected name as lookup value, got "+r.value,r.lineno,r.colno),t=new hr.Literal(r.lineno,r.colno,r.value),e=new hr.LookupVal(n.lineno,n.colno,e,t)}n=this.peekToken()}return e},i.parseExpression=function(){return this.parseInlineIf()},i.parseInlineIf=function(){var e=this.parseOr();if(this.skipSymbol("if")){var t=this.parseOr(),n=e;(e=new hr.InlineIf(e.lineno,e.colno)).body=n,e.cond=t,this.skipSymbol("else")?e.else_=this.parseOr():e.else_=null}return e},i.parseOr=function(){for(var e=this.parseAnd();this.skipSymbol("or");){var t=this.parseAnd();e=new hr.Or(e.lineno,e.colno,e,t)}return e},i.parseAnd=function(){for(var e=this.parseNot();this.skipSymbol("and");){var t=this.parseNot();e=new hr.And(e.lineno,e.colno,e,t)}return e},i.parseNot=function(){var e=this.peekToken();return this.skipSymbol("not")?new hr.Not(e.lineno,e.colno,this.parseNot()):this.parseIn()},i.parseIn=function(){for(var e=this.parseIs();;){var t=this.nextToken();if(!t)break;var n=t.type===Vt.TOKEN_SYMBOL&&"not"===t.value;if(n||this.pushToken(t),!this.skipSymbol("in")){n&&this.pushToken(t);break}var r=this.parseIs();e=new hr.In(e.lineno,e.colno,e,r),n&&(e=new hr.Not(e.lineno,e.colno,e))}return e},i.parseIs=function(){var e=this.parseCompare();if(this.skipSymbol("is")){var t=this.skipSymbol("not"),n=this.parseCompare();e=new hr.Is(e.lineno,e.colno,e,n),t&&(e=new hr.Not(e.lineno,e.colno,e))}return e},i.parseCompare=function(){for(var e=["==","===","!=","!==","<",">","<=",">="],t=this.parseConcat(),n=[];;){var r=this.nextToken();if(!r)break;if(-1===e.indexOf(r.value)){this.pushToken(r);break}n.push(new hr.CompareOperand(r.lineno,r.colno,this.parseConcat(),r.value))}return n.length?new hr.Compare(n[0].lineno,n[0].colno,t,n):t},i.parseConcat=function(){for(var e=this.parseAdd();this.skipValue(Vt.TOKEN_TILDE,"~");){var t=this.parseAdd();e=new hr.Concat(e.lineno,e.colno,e,t)}return e},i.parseAdd=function(){for(var e=this.parseSub();this.skipValue(Vt.TOKEN_OPERATOR,"+");){var t=this.parseSub();e=new hr.Add(e.lineno,e.colno,e,t)}return e},i.parseSub=function(){for(var e=this.parseMul();this.skipValue(Vt.TOKEN_OPERATOR,"-");){var t=this.parseMul();e=new hr.Sub(e.lineno,e.colno,e,t)}return e},i.parseMul=function(){for(var e=this.parseDiv();this.skipValue(Vt.TOKEN_OPERATOR,"*");){var t=this.parseDiv();e=new hr.Mul(e.lineno,e.colno,e,t)}return e},i.parseDiv=function(){for(var e=this.parseFloorDiv();this.skipValue(Vt.TOKEN_OPERATOR,"/");){var t=this.parseFloorDiv();e=new hr.Div(e.lineno,e.colno,e,t)}return e},i.parseFloorDiv=function(){for(var e=this.parseMod();this.skipValue(Vt.TOKEN_OPERATOR,"//");){var t=this.parseMod();e=new hr.FloorDiv(e.lineno,e.colno,e,t)}return e},i.parseMod=function(){for(var e=this.parsePow();this.skipValue(Vt.TOKEN_OPERATOR,"%");){var t=this.parsePow();e=new hr.Mod(e.lineno,e.colno,e,t)}return e},i.parsePow=function(){for(var e=this.parseUnary();this.skipValue(Vt.TOKEN_OPERATOR,"**");){var t=this.parseUnary();e=new hr.Pow(e.lineno,e.colno,e,t)}return e},i.parseUnary=function(e){var t,n=this.peekToken();return t=this.skipValue(Vt.TOKEN_OPERATOR,"-")?new hr.Neg(n.lineno,n.colno,this.parseUnary(!0)):this.skipValue(Vt.TOKEN_OPERATOR,"+")?new hr.Pos(n.lineno,n.colno,this.parseUnary(!0)):this.parsePrimary(),e||(t=this.parseFilter(t)),t},i.parsePrimary=function(e){var t,n=this.nextToken(),r=null;if(n?n.type===Vt.TOKEN_STRING?t=n.value:n.type===Vt.TOKEN_INT?t=parseInt(n.value,10):n.type===Vt.TOKEN_FLOAT?t=parseFloat(n.value):n.type===Vt.TOKEN_BOOLEAN?"true"===n.value?t=!0:"false"===n.value?t=!1:this.fail("invalid boolean: "+n.value,n.lineno,n.colno):n.type===Vt.TOKEN_NONE?t=null:n.type===Vt.TOKEN_REGEX&&(t=new RegExp(n.value.body,n.value.flags)):this.fail("expected expression, got end of file"),void 0!==t?r=new hr.Literal(n.lineno,n.colno,t):n.type===Vt.TOKEN_SYMBOL?r=new hr.Symbol(n.lineno,n.colno,n.value):(this.pushToken(n),r=this.parseAggregate()),e||(r=this.parsePostfix(r)),r)return r;throw this.error("unexpected token: "+n.value,n.lineno,n.colno)},i.parseFilterName=function(){for(var e=this.expect(Vt.TOKEN_SYMBOL),t=e.value;this.skipValue(Vt.TOKEN_OPERATOR,".");)t+="."+this.expect(Vt.TOKEN_SYMBOL).value;return new hr.Symbol(e.lineno,e.colno,t)},i.parseFilterArgs=function(e){return this.peekToken().type===Vt.TOKEN_LEFT_PAREN?this.parsePostfix(e).args.children:[]},i.parseFilter=function(e){for(;this.skip(Vt.TOKEN_PIPE);){var t=this.parseFilterName();e=new hr.Filter(t.lineno,t.colno,t,new hr.NodeList(t.lineno,t.colno,[e].concat(this.parseFilterArgs(e))))}return e},i.parseFilterStatement=function(){var e=this.peekToken();this.skipSymbol("filter")||this.fail("parseFilterStatement: expected filter");var t=this.parseFilterName(),n=this.parseFilterArgs(t);this.advanceAfterBlockEnd(e.value);var r=new hr.Capture(t.lineno,t.colno,this.parseUntilBlocks("endfilter"));this.advanceAfterBlockEnd();var i=new hr.Filter(t.lineno,t.colno,t,new hr.NodeList(t.lineno,t.colno,[r].concat(n)));return new hr.Output(t.lineno,t.colno,[i])},i.parseAggregate=function(){var e,t=this.nextToken();switch(t.type){case Vt.TOKEN_LEFT_PAREN:e=new hr.Group(t.lineno,t.colno);break;case Vt.TOKEN_LEFT_BRACKET:e=new hr.Array(t.lineno,t.colno);break;case Vt.TOKEN_LEFT_CURLY:e=new hr.Dict(t.lineno,t.colno);break;default:return null}for(;;){var n=this.peekToken().type;if(n===Vt.TOKEN_RIGHT_PAREN||n===Vt.TOKEN_RIGHT_BRACKET||n===Vt.TOKEN_RIGHT_CURLY){this.nextToken();break}if(e.children.length>0&&(this.skip(Vt.TOKEN_COMMA)||this.fail("parseAggregate: expected comma after expression",t.lineno,t.colno)),e instanceof hr.Dict){var r=this.parsePrimary();this.skip(Vt.TOKEN_COLON)||this.fail("parseAggregate: expected colon after dict key",t.lineno,t.colno);var i=this.parseExpression();e.addChild(new hr.Pair(r.lineno,r.colno,r,i))}else{var s=this.parseExpression();e.addChild(s)}}return e},i.parseSignature=function(e,t){var n=this.peekToken();if(!t&&n.type!==Vt.TOKEN_LEFT_PAREN){if(e)return null;this.fail("expected arguments",n.lineno,n.colno)}n.type===Vt.TOKEN_LEFT_PAREN&&(n=this.nextToken());for(var r=new hr.NodeList(n.lineno,n.colno),i=new hr.KeywordArgs(n.lineno,n.colno),s=!1;;){if(n=this.peekToken(),!t&&n.type===Vt.TOKEN_RIGHT_PAREN){this.nextToken();break}if(t&&n.type===Vt.TOKEN_BLOCK_END)break;if(s&&!this.skip(Vt.TOKEN_COMMA))this.fail("parseSignature: expected comma after expression",n.lineno,n.colno);else{var a=this.parseExpression();this.skipValue(Vt.TOKEN_OPERATOR,"=")?i.addChild(new hr.Pair(a.lineno,a.colno,a,this.parseExpression())):r.addChild(a)}s=!0}return i.children.length&&r.addChild(i),r},i.parseUntilBlocks=function(){for(var e=this.breakOnBlocks,t=arguments.length,n=new Array(t),r=0;re.length)a=i.slice(0,e.length),i.slice(a.length,o).forEach((function(e,n){n":">","<=":"<=",">=":">="},Pr=function(e){var t,n;function r(){return e.apply(this,arguments)||this}n=e,(t=r).prototype=Object.create(n.prototype),t.prototype.constructor=t,vr(t,n);var i=r.prototype;return i.init=function(e,t){this.templateName=e,this.codebuf=[],this.lastId=0,this.buffer=null,this.bufferStack=[],this._scopeClosers="",this.inBlock=!1,this.throwOnUndefined=t},i.fail=function(e,t,n){throw void 0!==t&&(t+=1),void 0!==n&&(n+=1),new Dr(e,t,n)},i._pushBuffer=function(){var e=this._tmpid();return this.bufferStack.push(this.buffer),this.buffer=e,this._emit("var "+this.buffer+' = "";'),e},i._popBuffer=function(){this.buffer=this.bufferStack.pop()},i._emit=function(e){this.codebuf.push(e)},i._emitLine=function(e){this._emit(e+"\n")},i._emitLines=function(){for(var e=this,t=arguments.length,n=new Array(t),r=0;r0&&i._emit(","),i.compile(e,t)})),r&&this._emit(r)},i._compileExpression=function(e,t){this.assertType(e,hr.Literal,hr.Symbol,hr.Group,hr.Array,hr.Dict,hr.FunCall,hr.Caller,hr.Filter,hr.LookupVal,hr.Compare,hr.InlineIf,hr.In,hr.Is,hr.And,hr.Or,hr.Not,hr.Add,hr.Concat,hr.Sub,hr.Mul,hr.Div,hr.FloorDiv,hr.Mod,hr.Pow,hr.Neg,hr.Pos,hr.Compare,hr.NodeList),this.compile(e,t)},i.assertType=function(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),r=1;r0&&r._emit(","),e){r._emitLine("function(cb) {"),r._emitLine("if(!cb) { cb = function(err) { if(err) { throw err; }}}");var i=r._pushBuffer();r._withScopedSyntax((function(){r.compile(e,t),r._emitLine("cb(null, "+i+");")})),r._popBuffer(),r._emitLine("return "+i+";"),r._emitLine("}")}else r._emit("null")})),n){var o=this._tmpid();this._emitLine(", "+this._makeCallback(o)),this._emitLine(this.buffer+" += runtime.suppressValue("+o+", "+a+" && env.opts.autoescape);"),this._addScopeLevel()}else this._emit(")"),this._emit(", "+a+" && env.opts.autoescape);\n")},i.compileCallExtensionAsync=function(e,t){this.compileCallExtension(e,t,!0)},i.compileNodeList=function(e,t){this._compileChildren(e,t)},i.compileLiteral=function(e){if("string"==typeof e.value){var t=e.value.replace(/\\/g,"\\\\");t=(t=(t=(t=(t=t.replace(/"/g,'\\"')).replace(/\n/g,"\\n")).replace(/\r/g,"\\r")).replace(/\t/g,"\\t")).replace(/\u2028/g,"\\u2028"),this._emit('"'+t+'"')}else null===e.value?this._emit("null"):this._emit(e.value.toString())},i.compileSymbol=function(e,t){var n=e.value,r=t.lookup(n);r?this._emit(r):this._emit('runtime.contextOrFrameLookup(context, frame, "'+n+'")')},i.compileGroup=function(e,t){this._compileAggregate(e,t,"(",")")},i.compileArray=function(e,t){this._compileAggregate(e,t,"[","]")},i.compileDict=function(e,t){this._compileAggregate(e,t,"{","}")},i.compilePair=function(e,t){var n=e.key,r=e.value;n instanceof hr.Symbol?n=new hr.Literal(n.lineno,n.colno,n.value):n instanceof hr.Literal&&"string"==typeof n.value||this.fail("compilePair: Dict keys must be strings or names",n.lineno,n.colno),this.compile(n,t),this._emit(": "),this._compileExpression(r,t)},i.compileInlineIf=function(e,t){this._emit("("),this.compile(e.cond,t),this._emit("?"),this.compile(e.body,t),this._emit(":"),null!==e.else_?this.compile(e.else_,t):this._emit('""'),this._emit(")")},i.compileIn=function(e,t){this._emit("runtime.inOperator("),this.compile(e.left,t),this._emit(","),this.compile(e.right,t),this._emit(")")},i.compileIs=function(e,t){var n=e.right.name?e.right.name.value:e.right.value;this._emit('env.getTest("'+n+'").call(context, '),this.compile(e.left,t),e.right.args&&(this._emit(","),this.compile(e.right.args,t)),this._emit(") === true")},i._binOpEmitter=function(e,t,n){this.compile(e.left,t),this._emit(n),this.compile(e.right,t)},i.compileOr=function(e,t){return this._binOpEmitter(e,t," || ")},i.compileAnd=function(e,t){return this._binOpEmitter(e,t," && ")},i.compileAdd=function(e,t){return this._binOpEmitter(e,t," + ")},i.compileConcat=function(e,t){return this._binOpEmitter(e,t,' + "" + ')},i.compileSub=function(e,t){return this._binOpEmitter(e,t," - ")},i.compileMul=function(e,t){return this._binOpEmitter(e,t," * ")},i.compileDiv=function(e,t){return this._binOpEmitter(e,t," / ")},i.compileMod=function(e,t){return this._binOpEmitter(e,t," % ")},i.compileNot=function(e,t){this._emit("!"),this.compile(e.target,t)},i.compileFloorDiv=function(e,t){this._emit("Math.floor("),this.compile(e.left,t),this._emit(" / "),this.compile(e.right,t),this._emit(")")},i.compilePow=function(e,t){this._emit("Math.pow("),this.compile(e.left,t),this._emit(", "),this.compile(e.right,t),this._emit(")")},i.compileNeg=function(e,t){this._emit("-"),this.compile(e.target,t)},i.compilePos=function(e,t){this._emit("+"),this.compile(e.target,t)},i.compileCompare=function(e,t){var n=this;this.compile(e.expr,t),e.ops.forEach((function(e){n._emit(" "+Mr[e.type]+" "),n.compile(e.expr,t)}))},i.compileLookupVal=function(e,t){this._emit("runtime.memberLookup(("),this._compileExpression(e.target,t),this._emit("),"),this._compileExpression(e.val,t),this._emit(")")},i._getNodeName=function(e){switch(e.typename){case"Symbol":return e.value;case"FunCall":return"the return value of ("+this._getNodeName(e.name)+")";case"LookupVal":return this._getNodeName(e.target)+'["'+this._getNodeName(e.val)+'"]';case"Literal":return e.value.toString();default:return"--expression--"}},i.compileFunCall=function(e,t){this._emit("(lineno = "+e.lineno+", colno = "+e.colno+", "),this._emit("runtime.callWrap("),this._compileExpression(e.name,t),this._emit(', "'+this._getNodeName(e.name).replace(/"/g,'\\"')+'", context, '),this._compileAggregate(e.args,t,"[","])"),this._emit(")")},i.compileFilter=function(e,t){var n=e.name;this.assertType(n,hr.Symbol),this._emit('env.getFilter("'+n.value+'").call(context, '),this._compileAggregate(e.args,t),this._emit(")")},i.compileFilterAsync=function(e,t){var n=e.name,r=e.symbol.value;this.assertType(n,hr.Symbol),t.set(r,r),this._emit('env.getFilter("'+n.value+'").call(context, '),this._compileAggregate(e.args,t),this._emitLine(", "+this._makeCallback(r)),this._addScopeLevel()},i.compileKeywordArgs=function(e,t){this._emit("runtime.makeKeywordArgs("),this.compileDict(e,t),this._emit(")")},i.compileSet=function(e,t){var n=this,r=[];e.targets.forEach((function(e){var i=e.value,s=t.lookup(i);null==s&&(s=n._tmpid(),n._emitLine("var "+s+";")),r.push(s)})),e.value?(this._emit(r.join(" = ")+" = "),this._compileExpression(e.value,t),this._emitLine(";")):(this._emit(r.join(" = ")+" = "),this.compile(e.body,t),this._emitLine(";")),e.targets.forEach((function(e,t){var i=r[t],s=e.value;n._emitLine('frame.set("'+s+'", '+i+", true);"),n._emitLine("if(frame.topLevel) {"),n._emitLine('context.setVariable("'+s+'", '+i+");"),n._emitLine("}"),"_"!==s.charAt(0)&&(n._emitLine("if(frame.topLevel) {"),n._emitLine('context.addExport("'+s+'", '+i+");"),n._emitLine("}"))}))},i.compileSwitch=function(e,t){var n=this;this._emit("switch ("),this.compile(e.expr,t),this._emit(") {"),e.cases.forEach((function(e,r){n._emit("case "),n.compile(e.cond,t),n._emit(": "),n.compile(e.body,t),e.body.children.length&&n._emitLine("break;")})),e.default&&(this._emit("default:"),this.compile(e.default,t)),this._emit("}")},i.compileIf=function(e,t,n){var r=this;this._emit("if("),this._compileExpression(e.cond,t),this._emitLine(") {"),this._withScopedSyntax((function(){r.compile(e.body,t),n&&r._emit("cb()")})),e.else_?(this._emitLine("}\nelse {"),this._withScopedSyntax((function(){r.compile(e.else_,t),n&&r._emit("cb()")}))):n&&(this._emitLine("}\nelse {"),this._emit("cb()")),this._emitLine("}")},i.compileIfAsync=function(e,t){this._emit("(function(cb) {"),this.compileIf(e,t,!0),this._emit("})("+this._makeCallback()),this._addScopeLevel()},i._emitLoopBindings=function(e,t,n,r){var i=this;[{name:"index",val:n+" + 1"},{name:"index0",val:n},{name:"revindex",val:r+" - "+n},{name:"revindex0",val:r+" - "+n+" - 1"},{name:"first",val:n+" === 0"},{name:"last",val:n+" === "+r+" - 1"},{name:"length",val:r}].forEach((function(e){i._emitLine('frame.set("loop.'+e.name+'", '+e.val+");")}))},i.compileFor=function(e,t){var n=this,r=this._tmpid(),i=this._tmpid(),s=this._tmpid();if(t=t.push(),this._emitLine("frame = frame.push();"),this._emit("var "+s+" = "),this._compileExpression(e.arr,t),this._emitLine(";"),this._emit("if("+s+") {"),this._emitLine(s+" = runtime.fromIterator("+s+");"),e.name instanceof hr.Array){this._emitLine("var "+r+";"),this._emitLine("if(runtime.isArray("+s+")) {"),this._emitLine("var "+i+" = "+s+".length;"),this._emitLine("for("+r+"=0; "+r+" < "+s+".length; "+r+"++) {"),e.name.children.forEach((function(i,a){var o=n._tmpid();n._emitLine("var "+o+" = "+s+"["+r+"]["+a+"];"),n._emitLine('frame.set("'+i+'", '+s+"["+r+"]["+a+"]);"),t.set(e.name.children[a].value,o)})),this._emitLoopBindings(e,s,r,i),this._withScopedSyntax((function(){n.compile(e.body,t)})),this._emitLine("}"),this._emitLine("} else {");var a=e.name.children,o=a[0],c=a[1],l=this._tmpid(),h=this._tmpid();t.set(o.value,l),t.set(c.value,h),this._emitLine(r+" = -1;"),this._emitLine("var "+i+" = runtime.keys("+s+").length;"),this._emitLine("for(var "+l+" in "+s+") {"),this._emitLine(r+"++;"),this._emitLine("var "+h+" = "+s+"["+l+"];"),this._emitLine('frame.set("'+o.value+'", '+l+");"),this._emitLine('frame.set("'+c.value+'", '+h+");"),this._emitLoopBindings(e,s,r,i),this._withScopedSyntax((function(){n.compile(e.body,t)})),this._emitLine("}"),this._emitLine("}")}else{var u=this._tmpid();t.set(e.name.value,u),this._emitLine("var "+i+" = "+s+".length;"),this._emitLine("for(var "+r+"=0; "+r+" < "+s+".length; "+r+"++) {"),this._emitLine("var "+u+" = "+s+"["+r+"];"),this._emitLine('frame.set("'+e.name.value+'", '+u+");"),this._emitLoopBindings(e,s,r,i),this._withScopedSyntax((function(){n.compile(e.body,t)})),this._emitLine("}")}this._emitLine("}"),e.else_&&(this._emitLine("if (!"+i+") {"),this.compile(e.else_,t),this._emitLine("}")),this._emitLine("frame = frame.pop();")},i._compileAsyncLoop=function(e,t,n){var r=this,i=this._tmpid(),s=this._tmpid(),a=this._tmpid(),o=n?"asyncAll":"asyncEach";if(t=t.push(),this._emitLine("frame = frame.push();"),this._emit("var "+a+" = runtime.fromIterator("),this._compileExpression(e.arr,t),this._emitLine(");"),e.name instanceof hr.Array){var c=e.name.children.length;this._emit("runtime."+o+"("+a+", "+c+", function("),e.name.children.forEach((function(e){r._emit(e.value+",")})),this._emit(i+","+s+",next) {"),e.name.children.forEach((function(e){var n=e.value;t.set(n,n),r._emitLine('frame.set("'+n+'", '+n+");")}))}else{var l=e.name.value;this._emitLine("runtime."+o+"("+a+", 1, function("+l+", "+i+", "+s+",next) {"),this._emitLine('frame.set("'+l+'", '+l+");"),t.set(l,l)}this._emitLoopBindings(e,a,i,s),this._withScopedSyntax((function(){var s;n&&(s=r._pushBuffer()),r.compile(e.body,t),r._emitLine("next("+i+(s?","+s:"")+");"),n&&r._popBuffer()}));var h=this._tmpid();this._emitLine("}, "+this._makeCallback(h)),this._addScopeLevel(),n&&this._emitLine(this.buffer+" += "+h+";"),e.else_&&(this._emitLine("if (!"+a+".length) {"),this.compile(e.else_,t),this._emitLine("}")),this._emitLine("frame = frame.pop();")},i.compileAsyncEach=function(e,t){this._compileAsyncLoop(e,t)},i.compileAsyncAll=function(e,t){this._compileAsyncLoop(e,t,!0)},i._compileMacro=function(e,t){var n=this,r=[],i=null,s="macro_"+this._tmpid(),a=void 0!==t;e.args.children.forEach((function(t,s){s===e.args.children.length-1&&t instanceof hr.Dict?i=t:(n.assertType(t,hr.Symbol),r.push(t))}));var o,c=[].concat(r.map((function(e){return"l_"+e.value})),["kwargs"]),l=r.map((function(e){return'"'+e.value+'"'})),h=(i&&i.children||[]).map((function(e){return'"'+e.key.value+'"'}));o=a?t.push(!0):new Rr,this._emitLines("var "+s+" = runtime.makeMacro(","["+l.join(", ")+"], ","["+h.join(", ")+"], ","function ("+c.join(", ")+") {","var callerFrame = frame;","frame = "+(a?"frame.push(true);":"new runtime.Frame();"),"kwargs = kwargs || {};",'if (Object.prototype.hasOwnProperty.call(kwargs, "caller")) {','frame.set("caller", kwargs.caller); }'),r.forEach((function(e){n._emitLine('frame.set("'+e.value+'", l_'+e.value+");"),o.set(e.value,"l_"+e.value)})),i&&i.children.forEach((function(e){var t=e.key.value;n._emit('frame.set("'+t+'", '),n._emit('Object.prototype.hasOwnProperty.call(kwargs, "'+t+'")'),n._emit(' ? kwargs["'+t+'"] : '),n._compileExpression(e.value,o),n._emit(");")}));var u=this._pushBuffer();return this._withScopedSyntax((function(){n.compile(e.body,o)})),this._emitLine("frame = "+(a?"frame.pop();":"callerFrame;")),this._emitLine("return new runtime.SafeString("+u+");"),this._emitLine("});"),this._popBuffer(),s},i.compileMacro=function(e,t){var n=this._compileMacro(e),r=e.name.value;t.set(r,n),t.parent?this._emitLine('frame.set("'+r+'", '+n+");"):("_"!==e.name.value.charAt(0)&&this._emitLine('context.addExport("'+r+'");'),this._emitLine('context.setVariable("'+r+'", '+n+");"))},i.compileCaller=function(e,t){this._emit("(function (){");var n=this._compileMacro(e,t);this._emit("return "+n+";})()")},i._compileGetTemplate=function(e,t,n,r){var i=this._tmpid(),s=this._templateName(),a=this._makeCallback(i),o=n?"true":"false",c=r?"true":"false";return this._emit("env.getTemplate("),this._compileExpression(e.template,t),this._emitLine(", "+o+", "+s+", "+c+", "+a),i},i.compileImport=function(e,t){var n=e.target.value,r=this._compileGetTemplate(e,t,!1,!1);this._addScopeLevel(),this._emitLine(r+".getExported("+(e.withContext?"context.getVariables(), frame, ":"")+this._makeCallback(r)),this._addScopeLevel(),t.set(n,r),t.parent?this._emitLine('frame.set("'+n+'", '+r+");"):this._emitLine('context.setVariable("'+n+'", '+r+");")},i.compileFromImport=function(e,t){var n=this,r=this._compileGetTemplate(e,t,!1,!1);this._addScopeLevel(),this._emitLine(r+".getExported("+(e.withContext?"context.getVariables(), frame, ":"")+this._makeCallback(r)),this._addScopeLevel(),e.names.children.forEach((function(e){var i,s,a=n._tmpid();e instanceof hr.Pair?(i=e.key.value,s=e.value.value):s=i=e.value,n._emitLine("if(Object.prototype.hasOwnProperty.call("+r+', "'+i+'")) {'),n._emitLine("var "+a+" = "+r+"."+i+";"),n._emitLine("} else {"),n._emitLine("cb(new Error(\"cannot import '"+i+"'\")); return;"),n._emitLine("}"),t.set(s,a),t.parent?n._emitLine('frame.set("'+s+'", '+a+");"):n._emitLine('context.setVariable("'+s+'", '+a+");")}))},i.compileBlock=function(e){var t=this._tmpid();this.inBlock||this._emit('(parentTemplate ? function(e, c, f, r, cb) { cb(""); } : '),this._emit('context.getBlock("'+e.name.value+'")'),this.inBlock||this._emit(")"),this._emitLine("(env, context, frame, runtime, "+this._makeCallback(t)),this._emitLine(this.buffer+" += "+t+";"),this._addScopeLevel()},i.compileSuper=function(e,t){var n=e.blockName.value,r=e.symbol.value,i=this._makeCallback(r);this._emitLine('context.getSuper(env, "'+n+'", b_'+n+", frame, runtime, "+i),this._emitLine(r+" = runtime.markSafe("+r+");"),this._addScopeLevel(),t.set(r,r)},i.compileExtends=function(e,t){var n=this._tmpid(),r=this._compileGetTemplate(e,t,!0,!1);this._emitLine("parentTemplate = "+r),this._emitLine("for(var "+n+" in parentTemplate.blocks) {"),this._emitLine("context.addBlock("+n+", parentTemplate.blocks["+n+"]);"),this._emitLine("}"),this._addScopeLevel()},i.compileInclude=function(e,t){this._emitLine("var tasks = [];"),this._emitLine("tasks.push("),this._emitLine("function(callback) {");var n=this._compileGetTemplate(e,t,!1,e.ignoreMissing);this._emitLine("callback(null,"+n+");});"),this._emitLine("});");var r=this._tmpid();this._emitLine("tasks.push("),this._emitLine("function(template, callback){"),this._emitLine("template.render(context.getVariables(), frame, "+this._makeCallback(r)),this._emitLine("callback(null,"+r+");});"),this._emitLine("});"),this._emitLine("tasks.push("),this._emitLine("function(result, callback){"),this._emitLine(this.buffer+" += result;"),this._emitLine("callback(null);"),this._emitLine("});"),this._emitLine("env.waterfall(tasks, function(){"),this._addScopeLevel()},i.compileTemplateData=function(e,t){this.compileLiteral(e,t)},i.compileCapture=function(e,t){var n=this,r=this.buffer;this.buffer="output",this._emitLine("(function() {"),this._emitLine('var output = "";'),this._withScopedSyntax((function(){n.compile(e.body,t)})),this._emitLine("return output;"),this._emitLine("})()"),this.buffer=r},i.compileOutput=function(e,t){var n=this;e.children.forEach((function(r){r instanceof hr.TemplateData?r.value&&(n._emit(n.buffer+" += "),n.compileLiteral(r,t),n._emitLine(";")):(n._emit(n.buffer+" += runtime.suppressValue("),n.throwOnUndefined&&n._emit("runtime.ensureDefined("),n.compile(r,t),n.throwOnUndefined&&n._emit(","+e.lineno+","+e.colno+")"),n._emit(", env.opts.autoescape);\n"))}))},i.compileRoot=function(e,t){var n=this;t&&this.fail("compileRoot: root node can't have frame"),t=new Rr,this._emitFuncBegin(e,"root"),this._emitLine("var parentTemplate = null;"),this._compileChildren(e,t),this._emitLine("if(parentTemplate) {"),this._emitLine("parentTemplate.rootRenderFunc(env, context, frame, runtime, cb);"),this._emitLine("} else {"),this._emitLine("cb(null, "+this.buffer+");"),this._emitLine("}"),this._emitFuncEnd(!0),this.inBlock=!0;var r=[],i=e.findAll(hr.Block);i.forEach((function(e,t){var i=e.name.value;if(-1!==r.indexOf(i))throw new Error('Block "'+i+'" defined more than once.');r.push(i),n._emitFuncBegin(e,"b_"+i);var s=new Rr;n._emitLine("var frame = frame.push(true);"),n.compile(e.body,s),n._emitFuncEnd()})),this._emitLine("return {"),i.forEach((function(e,t){var r="b_"+e.name.value;n._emitLine(r+": "+r+",")})),this._emitLine("root: root\n};")},i.compile=function(e,t){var n=this["compile"+e.typename];n?n.call(this,e,t):this.fail("compile: Cannot compile node: "+e.typename,e.lineno,e.colno)},i.getCode=function(){return this.codebuf.join("")},r}(en.Obj),xr={compile:function(e,t,n,r,i){void 0===i&&(i={});var s=new Pr(r,i.throwOnUndefined),a=(n||[]).map((function(e){return e.preprocess})).filter((function(e){return!!e})).reduce((function(e,t){return t(e)}),e);return s.compile(Nr.transform(fr.parse(a,n,i),t,r)),s.getCode()},Compiler:Pr},wr=Ke((function(e){var t=e.exports={};function n(e,t){return null==e||!1===e?t:e}function r(e){return e!=e}function i(e){var t=(e=n(e,"")).toLowerCase();return kr.copySafeness(e,t.charAt(0).toUpperCase()+t.slice(1))}function s(e){if(je.isString(e))return e.split("");if(je.isObject(e))return je._entries(e||{}).map((function(e){return{key:e[0],value:e[1]}}));if(je.isArray(e))return e;throw new je.TemplateError("list filter: type not iterable")}function a(e){return function(t,n,r){void 0===n&&(n="truthy");var i=this,s=i.env.getTest(n);return je.toArray(t).filter((function(t){return s.call(i,t,r)===e}))}}function o(e){return kr.copySafeness(e,e.replace(/^\s*|\s*$/g,""))}t.abs=Math.abs,t.batch=function(e,t,n){var r,i=[],s=[];for(r=0;r=t)return e;var r=t-e.length,i=je.repeat(" ",r/2-r%2),s=je.repeat(" ",r/2);return kr.copySafeness(e,i+e+s)},t.default=function(e,t,n){return n?e||t:void 0!==e?e:t},t.dictsort=function(e,t,n){if(!je.isObject(e))throw new je.TemplateError("dictsort filter: val must be an object");var r,i=[];for(var s in e)i.push([s,e[s]]);if(void 0===n||"key"===n)r=0;else{if("value"!==n)throw new je.TemplateError("dictsort filter: You can only sort by either key or value");r=1}return i.sort((function(e,n){var i=e[r],s=n[r];return t||(je.isString(i)&&(i=i.toUpperCase()),je.isString(s)&&(s=s.toUpperCase())),i>s?1:i===s?0:-1})),i},t.dump=function(e,t){return JSON.stringify(e,null,t)},t.escape=function(e){return e instanceof kr.SafeString?e:(e=null==e?"":e,kr.markSafe(je.escape(e.toString())))},t.safe=function(e){return e instanceof kr.SafeString?e:(e=null==e?"":e,kr.markSafe(e.toString()))},t.first=function(e){return e[0]},t.forceescape=function(e){return e=null==e?"":e,kr.markSafe(je.escape(e.toString()))},t.groupby=function(e,t){return je.groupBy(e,t,this.env.opts.throwOnUndefined)},t.indent=function(e,t,r){if(""===(e=n(e,"")))return"";t=t||4;var i=e.split("\n"),s=je.repeat(" ",t),a=i.map((function(e,t){return 0!==t||r?""+s+e:e})).join("\n");return kr.copySafeness(e,a)},t.join=function(e,t,n){return t=t||"",n&&(e=je.map(e,(function(e){return e[n]}))),e.join(t)},t.last=function(e){return e[e.length-1]},t.length=function(e){var t=n(e,"");return void 0!==t?"function"==typeof Map&&t instanceof Map||"function"==typeof Set&&t instanceof Set?t.size:!je.isObject(t)||t instanceof kr.SafeString?t.length:je.keys(t).length:0},t.list=s,t.lower=function(e){return(e=n(e,"")).toLowerCase()},t.nl2br=function(e){return null==e?"":kr.copySafeness(e,e.replace(/\r\n|\n/g,"
\n"))},t.random=function(e){return e[Math.floor(Math.random()*e.length)]},t.reject=a(!1),t.rejectattr=function(e,t){return e.filter((function(e){return!e[t]}))},t.select=a(!0),t.selectattr=function(e,t){return e.filter((function(e){return!!e[t]}))},t.replace=function(e,t,n,r){var i=e;if(t instanceof RegExp)return e.replace(t,n);void 0===r&&(r=-1);var s="";if("number"==typeof t)t=""+t;else if("string"!=typeof t)return e;if("number"==typeof e&&(e=""+e),"string"!=typeof e&&!(e instanceof kr.SafeString))return e;if(""===t)return s=n+e.split("").join(n)+n,kr.copySafeness(e,s);var a=e.indexOf(t);if(0===r||-1===a)return e;for(var o=0,c=0;a>-1&&(-1===r||c=i&&h.push(n),s.push(h)}return s},t.sum=function(e,t,n){return void 0===n&&(n=0),t&&(e=je.map(e,(function(e){return e[t]}))),n+e.reduce((function(e,t){return e+t}),0)},t.sort=kr.makeMacro(["value","reverse","case_sensitive","attribute"],[],(function(e,t,n,r){var i=this,s=je.map(e,(function(e){return e})),a=je.getAttrGetter(r);return s.sort((function(e,s){var o=r?a(e):e,c=r?a(s):s;if(i.env.opts.throwOnUndefined&&r&&(void 0===o||void 0===c))throw new TypeError('sort: attribute "'+r+'" resolved to undefined');return!n&&je.isString(o)&&je.isString(c)&&(o=o.toLowerCase(),c=c.toLowerCase()),oc?t?-1:1:0})),s})),t.string=function(e){return kr.copySafeness(e,e)},t.striptags=function(e,t){var r=o((e=n(e,"")).replace(/<\/?([a-z][a-z0-9]*)\b[^>]*>|/gi,"")),i="";return i=t?r.replace(/^ +| +$/gm,"").replace(/ +/g," ").replace(/(\r\n)/g,"\n").replace(/\n\n\n+/g,"\n\n"):r.replace(/\s+/gi," "),kr.copySafeness(e,i)},t.title=function(e){var t=(e=n(e,"")).split(" ").map((function(e){return i(e)}));return kr.copySafeness(e,t.join(" "))},t.trim=o,t.truncate=function(e,t,r,i){var s=e;if(t=t||255,(e=n(e,"")).length<=t)return e;if(r)e=e.substring(0,t);else{var a=e.lastIndexOf(" ",t);-1===a&&(a=t),e=e.substring(0,a)}return e+=null!=i?i:"...",kr.copySafeness(s,e)},t.upper=function(e){return(e=n(e,"")).toUpperCase()},t.urlencode=function(e){var t=encodeURIComponent;return je.isString(e)?t(e):(je.isArray(e)?e:je._entries(e)).map((function(e){var n=e[0],r=e[1];return t(n)+"="+t(r)})).join("&")};var c=/^(?:\(|<|<)?(.*?)(?:\.|,|\)|\n|>)?$/,l=/^[\w.!#$%&'*+\-\/=?\^`{|}~]+@[a-z\d\-]+(\.[a-z\d\-]+)+$/i,h=/^https?:\/\/.*$/,u=/^www\./,p=/\.(?:org|net|com)(?:\:|\/|$)/;t.urlize=function(e,t,n){r(t)&&(t=1/0);var i=!0===n?' rel="nofollow"':"";return e.split(/(\s+)/).filter((function(e){return e&&e.length})).map((function(e){var n=e.match(c),r=n?n[1]:e,s=r.substr(0,t);return h.test(r)?'"+s+"":u.test(r)?'"+s+"":l.test(r)?''+r+"":p.test(r)?'"+s+"":e})).join("")},t.wordcount=function(e){var t=(e=n(e,""))?e.match(/\w+/g):null;return t?t.length:null},t.float=function(e,t){var n=parseFloat(e);return r(n)?t:n};var f=kr.makeMacro(["value","default","base"],[],(function(e,t,n){void 0===n&&(n=10);var i=parseInt(e,n);return r(i)?t:i}));t.int=f,t.d=t.default,t.e=t.escape})),Br={};function Fr(e,t){for(var n=0,r=e.length-1;r>=0;r--){var i=e[r];"."===i?e.splice(r,1):".."===i?(e.splice(r,1),n++):n&&(e.splice(r,1),n--)}if(t)for(;n--;n)e.unshift("..");return e}var Ur=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/,Hr=function(e){return Ur.exec(e).slice(1)};function Gr(){for(var e="",t=!1,n=arguments.length-1;n>=-1&&!t;n--){var r=n>=0?arguments[n]:"/";if("string"!=typeof r)throw new TypeError("Arguments to path.resolve must be strings");r&&(e=r+"/"+e,t="/"===r.charAt(0))}return(t?"/":"")+(e=Fr(jr(e.split("/"),(function(e){return!!e})),!t).join("/"))||"."}function Yr(e){var t=qr(e),n="/"===Vr(e,-1);return(e=Fr(jr(e.split("/"),(function(e){return!!e})),!t).join("/"))||t||(e="."),e&&n&&(e+="/"),(t?"/":"")+e}function qr(e){return"/"===e.charAt(0)}var Kr={extname:function(e){return Hr(e)[3]},basename:function(e,t){var n=Hr(e)[2];return t&&n.substr(-1*t.length)===t&&(n=n.substr(0,n.length-t.length)),n},dirname:function(e){var t=Hr(e),n=t[0],r=t[1];return n||r?(r&&(r=r.substr(0,r.length-1)),n+r):"."},sep:"/",delimiter:":",relative:function(e,t){function n(e){for(var t=0;t=0&&""===e[n];n--);return t>n?[]:e.slice(t,n-t+1)}e=Gr(e).substr(1),t=Gr(t).substr(1);for(var r=n(e.split("/")),i=n(t.split("/")),s=Math.min(r.length,i.length),a=s,o=0;o=t},t.greaterthan=function(e,t){return e>t},t.gt=t.greaterthan,t.le=function(e,t){return e<=t},t.lessthan=function(e,t){return e=e.length&&(t=0),this.current=e[t],this.current}}}ni.callable,ni.defined,ni.divisibleby,ni.escaped,ni.equalto,ni.eq,ni.sameas,ni.even,ni.falsy,ni.ge,ni.greaterthan,ni.gt,ni.le,ni.lessthan,ni.lt,ni.lower,ni.ne,ni.number,ni.odd,ni.string,ni.truthy,ni.undefined,ni.upper,ni.iterable,ni.mapping;var ii=function(){return{range:function(e,t,n){void 0===t?(t=e,e=0,n=1):n||(n=1);var r=[];if(n>0)for(var i=e;it;s+=n)r.push(s);return r},cycler:function(){return ri(Array.prototype.slice.call(arguments))},joiner:function(e){return function(e){e=e||",";var t=!0;return function(){var n=t?"":e;return t=!1,n}}(e)}}};function si(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,ai(e,t)}function ai(e,t){return ai=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e},ai(e,t)}var oi=ti.FileSystemLoader,ci=ti.WebLoader,li=ti.PrecompiledLoader,hi=en.Obj,ui=en.EmitterObj,pi=kr.handleError,fi=kr.Frame;function di(e,t,n){Gt((function(){e(t,n)}))}var Ei={type:"code",obj:{root:function(e,t,n,r,i){try{i(null,"")}catch(e){i(pi(e,null,null))}}}},mi=function(e){function t(){return e.apply(this,arguments)||this}si(t,e);var n=t.prototype;return n.init=function(e,t){var n=this;t=this.opts=t||{},this.opts.dev=!!t.dev,this.opts.autoescape=null==t.autoescape||t.autoescape,this.opts.throwOnUndefined=!!t.throwOnUndefined,this.opts.trimBlocks=!!t.trimBlocks,this.opts.lstripBlocks=!!t.lstripBlocks,this.loaders=[],e?this.loaders=je.isArray(e)?e:[e]:oi?this.loaders=[new oi("views")]:ci&&(this.loaders=[new ci("/views")]),"undefined"!=typeof window&&window.jinja2Precompiled&&this.loaders.unshift(new li(window.jinja2Precompiled)),this._initLoaders(),this.globals=ii(),this.filters={},this.tests={},this.asyncFilters=[],this.extensions={},this.extensionsList=[],je._entries(wr).forEach((function(e){var t=e[0],r=e[1];return n.addFilter(t,r)})),je._entries(ni).forEach((function(e){var t=e[0],r=e[1];return n.addTest(t,r)}))},n._initLoaders=function(){var e=this;this.loaders.forEach((function(t){t.cache={},"function"==typeof t.on&&(t.on("update",(function(n,r){t.cache[n]=null,e.emit("update",n,r,t)})),t.on("load",(function(n,r){e.emit("load",n,r,t)})))}))},n.invalidateCache=function(){this.loaders.forEach((function(e){e.cache={}}))},n.addExtension=function(e,t){return t.__name=e,this.extensions[e]=t,this.extensionsList.push(t),this},n.removeExtension=function(e){var t=this.getExtension(e);t&&(this.extensionsList=je.without(this.extensionsList,t),delete this.extensions[e])},n.getExtension=function(e){return this.extensions[e]},n.hasExtension=function(e){return!!this.extensions[e]},n.addGlobal=function(e,t){return this.globals[e]=t,this},n.getGlobal=function(e){if(void 0===this.globals[e])throw new Error("global not found: "+e);return this.globals[e]},n.addFilter=function(e,t,n){var r=t;return n&&this.asyncFilters.push(e),this.filters[e]=r,this},n.getFilter=function(e){if(!this.filters[e])throw new Error("filter not found: "+e);return this.filters[e]},n.addTest=function(e,t){return this.tests[e]=t,this},n.getTest=function(e){if(!this.tests[e])throw new Error("test not found: "+e);return this.tests[e]},n.resolveTemplate=function(e,t,n){return!(!e.isRelative||!t)&&e.isRelative(n)&&e.resolve?e.resolve(t,n):n},n.getTemplate=function(e,t,n,r,i){var s,a=this,o=this,c=null;if(e&&e.raw&&(e=e.raw),je.isFunction(n)&&(i=n,n=null,t=t||!1),je.isFunction(t)&&(i=t,t=!1),e instanceof _i)c=e;else{if("string"!=typeof e)throw new Error("template names must be a string: "+e);for(var l=0;le.length-3)return!1;var n=e.charCodeAt(t+1);return(n>=Pi.LowerA&&n<=Pi.LowerZ||n>=Pi.UpperA&&n<=Pi.UpperZ||n===Pi.Exclamation)&&e.includes(">",t+2)}!function(e){e[e.LowerA=97]="LowerA",e[e.LowerZ=122]="LowerZ",e[e.UpperA=65]="UpperA",e[e.UpperZ=90]="UpperZ",e[e.Exclamation=33]="Exclamation"}(Pi||(Pi={}));var Hi=Object.prototype.hasOwnProperty,Gi=/\s+/,Yi={null:null,true:!0,false:!1},qi=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,Ki=/^{[^]*}$|^\[[^]*]$/;function ji(e,t,n){var r;if(e&&I(e))return null!==(r=e.attribs)&&void 0!==r||(e.attribs={}),t?Hi.call(e.attribs,t)?!n&&qi.test(t)?t:e.attribs[t]:"option"===e.name&&"value"===t?Li(e.children):"input"!==e.name||"radio"!==e.attribs.type&&"checkbox"!==e.attribs.type||"value"!==t?void 0:"on":e.attribs}function Vi(e,t,n){null===n?zi(e,t):e.attribs[t]="".concat(n)}function Wi(e,t,n){return t in e?e[t]:!n&&qi.test(t)?void 0!==ji(e,t,!1):ji(e,t,n)}function Qi(e,t,n,r){t in e?e[t]=n:Vi(e,t,!r&&qi.test(t)?n?"":null:"".concat(n))}function Xi(e,t,n){var r,i=e;null!==(r=i.data)&&void 0!==r||(i.data={}),"object"==typeof t?Object.assign(i.data,t):"string"==typeof t&&void 0!==n&&(i.data[t]=n)}function $i(e,t){var n,r,i,s;null==t?r=(n=Object.keys(e.attribs).filter((function(e){return e.startsWith("data-")}))).map((function(e){return e.slice("data-".length).replace(/[_.-](\w|$)/g,(function(e,t){return t.toUpperCase()}))})):(n=["data-"+(s=t,s.replace(/[A-Z]/g,"-$&").toLowerCase())],r=[t]);for(var a=0;a1?this:ji(this[0],e,this.options.xmlMode)},prop:function(e,t){var n,r=this;if("string"==typeof e&&void 0===t){var i=this[0];if(!i||!I(i))return;switch(e){case"style":var s=this.css(),a=Object.keys(s);return a.forEach((function(e,t){s[t]=e})),s.length=a.length,s;case"tagName":case"nodeName":return i.name.toUpperCase();case"href":case"src":var o=null===(n=i.attribs)||void 0===n?void 0:n[e];return"undefined"==typeof URL||("href"!==e||"a"!==i.tagName&&"link"!==i.name)&&("src"!==e||"img"!==i.tagName&&"iframe"!==i.tagName&&"audio"!==i.tagName&&"video"!==i.tagName&&"source"!==i.tagName)||void 0===o||!this.options.baseURI?o:new URL(o,this.options.baseURI).href;case"innerText":return J(i);case"textContent":return z(i);case"outerHTML":return this.clone().wrap("").parent().html();case"innerHTML":return this.html();default:return Wi(i,e,this.options.xmlMode)}}if("object"==typeof e||void 0!==t){if("function"==typeof t){if("object"==typeof e)throw new Error("Bad combination of arguments.");return Bi(this,(function(n,i){I(n)&&Qi(n,e,t.call(n,i,Wi(n,e,r.options.xmlMode)),r.options.xmlMode)}))}return Bi(this,(function(n){I(n)&&("object"==typeof e?Object.keys(e).forEach((function(t){var i=e[t];Qi(n,t,i,r.options.xmlMode)})):Qi(n,e,t,r.options.xmlMode))}))}},data:function(e,t){var n,r=this[0];if(r&&I(r)){var i=r;return null!==(n=i.data)&&void 0!==n||(i.data={}),e?"object"==typeof e||void 0!==t?(Bi(this,(function(n){I(n)&&("object"==typeof e?Xi(n,e):Xi(n,e,t))})),this):Hi.call(i.data,e)?i.data[e]:$i(i,e):$i(i)}},val:function(e){var t=0===arguments.length,n=this[0];if(!n||!I(n))return t?void 0:this;switch(n.name){case"textarea":return this.text(e);case"select":var r=this.find("option:selected");if(!t){if(null==this.attr("multiple")&&"object"==typeof e)return this;this.find("option").removeAttr("selected");for(var i="object"!=typeof e?[e]:e,s=0;s-1;){var i=r+e.length;if((0===r||Gi.test(n[r-1]))&&(i===n.length||Gi.test(n[i])))return!0}return!1}))},addClass:function e(t){if("function"==typeof t)return Bi(this,(function(n,r){if(I(n)){var i=n.attribs.class||"";e.call([n],t.call(n,r,i))}}));if(!t||"string"!=typeof t)return this;for(var n=t.split(Gi),r=this.length,i=0;i=0&&(t.splice(o,1),s=!0,a--)}s&&(e.attribs.class=t.join(" "))}}))},toggleClass:function e(t,n){if("function"==typeof t)return Bi(this,(function(r,i){I(r)&&e.call([r],t.call(r,i,r.attribs.class||"",n),n)}));if(!t||"string"!=typeof t)return this;for(var r=t.split(Gi),i=r.length,s="boolean"==typeof n?n?1:-1:0,a=this.length,o=0;o=0&&u<0?l.push(r[h]):s<=0&&u>=0&&l.splice(u,1)}c.attribs.class=l.join(" ")}}return this}});!function(e){e.Attribute="attribute",e.Pseudo="pseudo",e.PseudoElement="pseudo-element",e.Tag="tag",e.Universal="universal",e.Adjacent="adjacent",e.Child="child",e.Descendant="descendant",e.Parent="parent",e.Sibling="sibling",e.ColumnCombinator="column-combinator"}(Zi||(Zi={})),function(e){e.Any="any",e.Element="element",e.End="end",e.Equals="equals",e.Exists="exists",e.Hyphen="hyphen",e.Not="not",e.Start="start"}(es||(es={}));const ns=/^[^\\#]?(?:\\(?:[\da-f]{1,6}\s?|.)|[\w\-\u00b0-\uFFFF])+/,rs=/\\([\da-f]{1,6}\s?|(\s)|.)/gi,is=new Map([[126,es.Element],[94,es.Start],[36,es.End],[42,es.Any],[33,es.Not],[124,es.Hyphen]]),ss=new Set(["has","not","matches","is","where","host","host-context"]);function as(e){switch(e.type){case Zi.Adjacent:case Zi.Child:case Zi.Descendant:case Zi.Parent:case Zi.Sibling:case Zi.ColumnCombinator:return!0;default:return!1}}const os=new Set(["contains","icontains"]);function cs(e,t,n){const r=parseInt(t,16)-65536;return r!=r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)}function ls(e){return e.replace(rs,cs)}function hs(e){return 39===e||34===e}function us(e){return 32===e||9===e||10===e||12===e||13===e}function ps(e){const t=[],n=fs(t,`${e}`,0);if(n0&&n0&&as(r[r.length-1]))throw new Error("Did not expect successive traversals.")}function l(e){r.length>0&&r[r.length-1].type===Zi.Descendant?r[r.length-1].type=e:(c(),r.push({type:e}))}function h(e,t){r.push({type:Zi.Attribute,name:e,action:t,value:i(1),namespace:null,ignoreCase:"quirks"})}function u(){if(r.length&&r[r.length-1].type===Zi.Descendant&&r.pop(),0===r.length)throw new Error("Empty sub-selector");e.push(r)}if(s(0),t.length===n)return n;e:for(;n=0&&r>=1)):e.type===Zi.Pseudo&&(e.data?"has"===e.name||"contains"===e.name?r=0:Array.isArray(e.data)?(r=Math.min(...e.data.map((e=>Math.min(...e.map(gs))))),r<0&&(r=0)):r=2:r=3),r}const Ns=/[-[\]{}()*+?.,\\^$|#\s]/g;function Cs(e){return e.replace(Ns,"\\$&")}const Is=new Set(["accept","accept-charset","align","alink","axis","bgcolor","charset","checked","clear","codetype","color","compact","declare","defer","dir","direction","disabled","enctype","face","frame","hreflang","http-equiv","lang","language","link","media","method","multiple","nohref","noresize","noshade","nowrap","readonly","rel","rev","rules","scope","scrolling","selected","shape","target","text","type","valign","valuetype","vlink"]);function Ss(e,t){return"boolean"==typeof e.ignoreCase?e.ignoreCase:"quirks"===e.ignoreCase?!!t.quirksMode:!t.xmlMode&&Is.has(e.name)}const bs={equals(e,t,n){const{adapter:r}=n,{name:i}=t;let{value:s}=t;return Ss(t,n)?(s=s.toLowerCase(),t=>{const n=r.getAttributeValue(t,i);return null!=n&&n.length===s.length&&n.toLowerCase()===s&&e(t)}):t=>r.getAttributeValue(t,i)===s&&e(t)},hyphen(e,t,n){const{adapter:r}=n,{name:i}=t;let{value:s}=t;const a=s.length;return Ss(t,n)?(s=s.toLowerCase(),function(t){const n=r.getAttributeValue(t,i);return null!=n&&(n.length===a||"-"===n.charAt(a))&&n.substr(0,a).toLowerCase()===s&&e(t)}):function(t){const n=r.getAttributeValue(t,i);return null!=n&&(n.length===a||"-"===n.charAt(a))&&n.substr(0,a)===s&&e(t)}},element(e,t,n){const{adapter:r}=n,{name:i,value:s}=t;if(/\s/.test(s))return ds.falseFunc;const a=new RegExp(`(?:^|\\s)${Cs(s)}(?:$|\\s)`,Ss(t,n)?"i":"");return function(t){const n=r.getAttributeValue(t,i);return null!=n&&n.length>=s.length&&a.test(n)&&e(t)}},exists:(e,{name:t},{adapter:n})=>r=>n.hasAttrib(r,t)&&e(r),start(e,t,n){const{adapter:r}=n,{name:i}=t;let{value:s}=t;const a=s.length;return 0===a?ds.falseFunc:Ss(t,n)?(s=s.toLowerCase(),t=>{const n=r.getAttributeValue(t,i);return null!=n&&n.length>=a&&n.substr(0,a).toLowerCase()===s&&e(t)}):t=>{var n;return!!(null===(n=r.getAttributeValue(t,i))||void 0===n?void 0:n.startsWith(s))&&e(t)}},end(e,t,n){const{adapter:r}=n,{name:i}=t;let{value:s}=t;const a=-s.length;return 0===a?ds.falseFunc:Ss(t,n)?(s=s.toLowerCase(),t=>{var n;return(null===(n=r.getAttributeValue(t,i))||void 0===n?void 0:n.substr(a).toLowerCase())===s&&e(t)}):t=>{var n;return!!(null===(n=r.getAttributeValue(t,i))||void 0===n?void 0:n.endsWith(s))&&e(t)}},any(e,t,n){const{adapter:r}=n,{name:i,value:s}=t;if(""===s)return ds.falseFunc;if(Ss(t,n)){const t=new RegExp(Cs(s),"i");return function(n){const a=r.getAttributeValue(n,i);return null!=a&&a.length>=s.length&&t.test(a)&&e(n)}}return t=>{var n;return!!(null===(n=r.getAttributeValue(t,i))||void 0===n?void 0:n.includes(s))&&e(t)}},not(e,t,n){const{adapter:r}=n,{name:i}=t;let{value:s}=t;return""===s?t=>!!r.getAttributeValue(t,i)&&e(t):Ss(t,n)?(s=s.toLowerCase(),t=>{const n=r.getAttributeValue(t,i);return(null==n||n.length!==s.length||n.toLowerCase()!==s)&&e(t)}):t=>r.getAttributeValue(t,i)!==s&&e(t)}};var Os=Ke((function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.parse=void 0;var n=new Set([9,10,12,13,32]),r="0".charCodeAt(0),i="9".charCodeAt(0);t.parse=function(e){if("even"===(e=e.trim().toLowerCase()))return[2,0];if("odd"===e)return[2,1];var t=0,s=0,a=c(),o=l();if(t=r&&e.charCodeAt(t)<=i;)s=10*s+(e.charCodeAt(t)-r),t++;return t===n?null:s}function h(){for(;t=n};var r=Math.abs(t),i=(n%r+r)%r;return t>1?function(e){return e>=n&&e%r===i}:function(e){return e<=n&&e%r===i}}}));qe(ys),ys.compile;var Ls=Os,ks=ys,vs=Ke((function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.compile=t.parse=void 0,Object.defineProperty(t,"parse",{enumerable:!0,get:function(){return Ls.parse}}),Object.defineProperty(t,"compile",{enumerable:!0,get:function(){return ks.compile}}),t.default=function(e){return(0,ks.compile)((0,Ls.parse)(e))}})),Ds=qe(vs);function Rs(e,t){return n=>{const r=t.getParent(n);return null!=r&&t.isTag(r)&&e(n)}}vs.compile,vs.parse;const Ms={contains:(e,t,{adapter:n})=>function(r){return e(r)&&n.getText(r).includes(t)},icontains(e,t,{adapter:n}){const r=t.toLowerCase();return function(t){return e(t)&&n.getText(t).toLowerCase().includes(r)}},"nth-child"(e,t,{adapter:n,equals:r}){const i=Ds(t);return i===ds.falseFunc?ds.falseFunc:i===ds.trueFunc?Rs(e,n):function(t){const s=n.getSiblings(t);let a=0;for(let e=0;e=0&&!r(t,s[e]);e--)n.isTag(s[e])&&a++;return i(a)&&e(t)}},"nth-of-type"(e,t,{adapter:n,equals:r}){const i=Ds(t);return i===ds.falseFunc?ds.falseFunc:i===ds.trueFunc?Rs(e,n):function(t){const s=n.getSiblings(t);let a=0;for(let e=0;e=0;e--){const i=s[e];if(r(t,i))break;n.isTag(i)&&n.getName(i)===n.getName(t)&&a++}return i(a)&&e(t)}},root:(e,t,{adapter:n})=>t=>{const r=n.getParent(t);return(null==r||!n.isTag(r))&&e(t)},scope(e,t,n,r){const{equals:i}=n;return r&&0!==r.length?1===r.length?t=>i(r[0],t)&&e(t):t=>r.includes(t)&&e(t):Ms.root(e,t,n)},hover:Ps("isHovered"),visited:Ps("isVisited"),active:Ps("isActive")};function Ps(e){return function(t,n,{adapter:r}){const i=r[e];return"function"!=typeof i?ds.falseFunc:function(e){return i(e)&&t(e)}}}const xs={empty:(e,{adapter:t})=>!t.getChildren(e).some((e=>t.isTag(e)||""!==t.getText(e))),"first-child"(e,{adapter:t,equals:n}){if(t.prevElementSibling)return null==t.prevElementSibling(e);const r=t.getSiblings(e).find((e=>t.isTag(e)));return null!=r&&n(e,r)},"last-child"(e,{adapter:t,equals:n}){const r=t.getSiblings(e);for(let i=r.length-1;i>=0;i--){if(n(e,r[i]))return!0;if(t.isTag(r[i]))break}return!1},"first-of-type"(e,{adapter:t,equals:n}){const r=t.getSiblings(e),i=t.getName(e);for(let s=0;s=0;s--){const a=r[s];if(n(e,a))return!0;if(t.isTag(a)&&t.getName(a)===i)break}return!1},"only-of-type"(e,{adapter:t,equals:n}){const r=t.getName(e);return t.getSiblings(e).every((i=>n(e,i)||!t.isTag(i)||t.getName(i)!==r))},"only-child":(e,{adapter:t,equals:n})=>t.getSiblings(e).every((r=>n(e,r)||!t.isTag(r)))};function ws(e,t,n,r){if(null===n){if(e.length>r)throw new Error(`Pseudo-class :${t} requires an argument`)}else if(e.length===r)throw new Error(`Pseudo-class :${t} doesn't have any arguments`)}const Bs={"any-link":":is(a, area, link)[href]",link:":any-link:not(:visited)",disabled:":is(\n :is(button, input, select, textarea, optgroup, option)[disabled],\n optgroup[disabled] > option,\n fieldset[disabled]:not(fieldset[disabled] legend:first-of-type *)\n )",enabled:":not(:disabled)",checked:":is(:is(input[type=radio], input[type=checkbox])[checked], option:selected)",required:":is(input, select, textarea)[required]",optional:":is(input, select, textarea):not([required])",selected:"option:is([selected], select:not([multiple]):not(:has(> option[selected])) > :first-of-type)",checkbox:"[type=checkbox]",file:"[type=file]",password:"[type=password]",radio:"[type=radio]",reset:"[type=reset]",image:"[type=image]",submit:"[type=submit]",parent:":not(:empty)",header:":is(h1, h2, h3, h4, h5, h6)",button:":is(button, input[type=button])",input:":is(input, textarea, select, button)",text:"input:is(:not([type!='']), [type=text])"},Fs={};function Us(e,t){const n=t.getSiblings(e);if(n.length<=1)return[];const r=n.indexOf(e);return r<0||r===n.length-1?[]:n.slice(r+1).filter(t.isTag)}function Hs(e){return{xmlMode:!!e.xmlMode,lowerCaseAttributeNames:!!e.lowerCaseAttributeNames,lowerCaseTags:!!e.lowerCaseTags,quirksMode:!!e.quirksMode,cacheResults:!!e.cacheResults,pseudos:e.pseudos,adapter:e.adapter,equals:e.equals}}const Gs=(e,t,n,r,i)=>{const s=i(t,Hs(n),r);return s===ds.trueFunc?e:s===ds.falseFunc?ds.falseFunc:t=>s(t)&&e(t)},Ys={is:Gs,matches:Gs,where:Gs,not(e,t,n,r,i){const s=i(t,Hs(n),r);return s===ds.falseFunc?e:s===ds.trueFunc?ds.falseFunc:t=>!s(t)&&e(t)},has(e,t,n,r,i){const{adapter:s}=n,a=Hs(n);a.relativeSelector=!0;const o=t.some((e=>e.some(Ts)))?[Fs]:void 0,c=i(t,a,o);if(c===ds.falseFunc)return ds.falseFunc;const l=function(e,t){return e===ds.falseFunc?ds.falseFunc:n=>t.isTag(n)&&e(n)}(c,s);if(o&&c!==ds.trueFunc){const{shouldTestNextSiblings:t=!1}=c;return n=>{if(!e(n))return!1;o[0]=n;const r=s.getChildren(n),i=t?[...r,...Us(n,s)]:r;return s.existsOne(l,i)}}return t=>e(t)&&s.existsOne(l,s.getChildren(t))}};function qs(e,t){const n=t.getParent(e);return n&&t.isTag(n)?n:null}function Ks(e,t,n,r,i){const{adapter:s,equals:a}=n;switch(t.type){case Zi.PseudoElement:throw new Error("Pseudo-elements are not supported by css-select");case Zi.ColumnCombinator:throw new Error("Column combinators are not yet supported by css-select");case Zi.Attribute:if(null!=t.namespace)throw new Error("Namespaced attributes are not yet supported by css-select");return n.xmlMode&&!n.lowerCaseAttributeNames||(t.name=t.name.toLowerCase()),bs[t.action](e,t,n);case Zi.Pseudo:return function(e,t,n,r,i){var s;const{name:a,data:o}=t;if(Array.isArray(o)){if(!(a in Ys))throw new Error(`Unknown pseudo-class :${a}(${o})`);return Ys[a](e,o,n,r,i)}const c=null===(s=n.pseudos)||void 0===s?void 0:s[a],l="string"==typeof c?c:Bs[a];if("string"==typeof l){if(null!=o)throw new Error(`Pseudo ${a} doesn't have any arguments`);const t=ps(l);return Ys.is(e,t,n,r,i)}if("function"==typeof c)return ws(c,a,o,1),t=>c(t,o)&&e(t);if(a in Ms)return Ms[a](e,o,n,r);if(a in xs){const t=xs[a];return ws(t,a,o,2),r=>t(r,n,o)&&e(r)}throw new Error(`Unknown pseudo-class :${a}`)}(e,t,n,r,i);case Zi.Tag:{if(null!=t.namespace)throw new Error("Namespaced tag names are not yet supported by css-select");let{name:r}=t;return n.xmlMode&&!n.lowerCaseTags||(r=r.toLowerCase()),function(t){return s.getName(t)===r&&e(t)}}case Zi.Descendant:{if(!1===n.cacheResults||"undefined"==typeof WeakSet)return function(t){let n=t;for(;n=qs(n,s);)if(e(n))return!0;return!1};const t=new WeakSet;return function(n){let r=n;for(;r=qs(r,s);)if(!t.has(r)){if(s.isTag(r)&&e(r))return!0;t.add(r)}return!1}}case"_flexibleDescendant":return function(t){let n=t;do{if(e(n))return!0}while(n=qs(n,s));return!1};case Zi.Parent:return function(t){return s.getChildren(t).some((t=>s.isTag(t)&&e(t)))};case Zi.Child:return function(t){const n=s.getParent(t);return null!=n&&s.isTag(n)&&e(n)};case Zi.Sibling:return function(t){const n=s.getSiblings(t);for(let r=0;re.some(js))))}const Vs={type:Zi.Descendant},Ws={type:"_flexibleDescendant"},Qs={type:Zi.Pseudo,name:"scope",data:null};function Xs(e,t,n){var r;e.forEach(As),n=null!==(r=t.context)&&void 0!==r?r:n;const i=Array.isArray(n),s=n&&(Array.isArray(n)?n:[n]);if(!1!==t.relativeSelector)!function(e,{adapter:t},n){const r=!!(null==n?void 0:n.every((e=>{const n=t.isTag(e)&&t.getParent(e);return e===Fs||n&&t.isTag(n)})));for(const t of e){if(t.length>0&&Ts(t[0])&&t[0].type!==Zi.Descendant);else{if(!r||t.some(js))continue;t.unshift(Vs)}t.unshift(Qs)}}(e,t,s);else if(e.some((e=>e.length>0&&Ts(e[0]))))throw new Error("Relative selectors are not allowed when the `relativeSelector` option is disabled");let a=!1;const o=e.map((e=>{if(e.length>=2){const[t,n]=e;t.type!==Zi.Pseudo||"scope"!==t.name||(i&&n.type===Zi.Descendant?e[1]=Ws:n.type!==Zi.Adjacent&&n.type!==Zi.Sibling||(a=!0))}return function(e,t,n){var r;return e.reduce(((e,r)=>e===ds.falseFunc?ds.falseFunc:Ks(e,r,t,n,Xs)),null!==(r=t.rootFunc)&&void 0!==r?r:ds.trueFunc)}(e,t,s)})).reduce($s,ds.falseFunc);return o.shouldTestNextSiblings=a,o}function $s(e,t){return t===ds.falseFunc||e===ds.trueFunc?e:e===ds.falseFunc||t===ds.trueFunc?t:function(n){return e(n)||t(n)}}const zs=(e,t)=>e===t,Js={adapter:Se,equals:zs};const Zs=(ea=Xs,function(e,t,n){const r=function(e){var t,n,r,i;const s=null!=e?e:Js;return null!==(t=s.adapter)&&void 0!==t||(s.adapter=Se),null!==(n=s.equals)&&void 0!==n||(s.equals=null!==(i=null===(r=s.adapter)||void 0===r?void 0:r.equals)&&void 0!==i?i:zs),s}(t);return ea(e,r,n)});var ea;function ta(e,t,n=!1){return n&&(e=function(e,t){const n=Array.isArray(e)?e.slice(0):[e],r=n.length;for(let e=0;ee.some(ra))))}function ia(e){const t=[],n=[];for(const r of e)r.some(ra)?t.push(r):n.push(r);return[n,t]}const sa={type:Zi.Universal,namespace:null},aa={type:Zi.Pseudo,name:"scope",data:null};function oa(e,t,n={}){return ca([e],t,n)}function ca(e,t,n={}){if("function"==typeof t)return e.some(t);const[r,i]=ia(ps(t));return r.length>0&&e.some(Zs(r,n))||i.some((t=>ua(t,e,n).length>0))}function la(e,t,n={}){return ha(ps(e),t,n)}function ha(e,t,n){if(0===t.length)return[];const[r,i]=ia(e);let s;if(r.length){const e=Ea(t,r,n);if(0===i.length)return e;e.length&&(s=new Set(e))}for(let e=0;eI(e)&&!s.has(e))):t;if(0===a.length)break;const o=ua(r,t,n);if(o.length)if(s)o.forEach((e=>s.add(e)));else{if(e===i.length-1)return o;s=new Set(o)}}return void 0!==s?s.size===t.length?t:t.filter((e=>s.has(e))):[]}function ua(e,t,n){var r;if(e.some(as)){const i=null!==(r=n.root)&&void 0!==r?r:function(e){for(;e.parent;)e=e.parent;return e}(t[0]),s={...n,context:t,relativeSelector:!1};return e.push(aa),pa(i,e,s,!0,t.length)}return pa(t,e,n,!1,t.length)}function pa(e,t,n,r,i){const s=t.findIndex(ra),a=t.slice(0,s),o=t[s],c=t.length-1===s?i:1/0,l=function(e,t,n){const r=null!=t?parseInt(t,10):NaN;switch(e){case"first":return 1;case"nth":case"eq":return isFinite(r)?r>=0?r+1:1/0:0;case"lt":return isFinite(r)?r>=0?Math.min(r,n):1/0:0;case"gt":return isFinite(r)?1/0:0;case"odd":return 2*n;case"even":return 2*n-1;case"last":case"not":return 1/0}}(o.name,o.data,c);if(0===l)return[];const h=(0!==a.length||Array.isArray(e)?0===a.length?(Array.isArray(e)?e:[e]).filter(I):r||a.some(as)?fa(e,[a],n,l):Ea(e,[a],n):Z(e).filter(I)).slice(0,l);let u=function(e,t,n,r){const i="string"==typeof n?parseInt(n,10):NaN;switch(e){case"first":case"lt":return t;case"last":return t.length>0?[t[t.length-1]]:t;case"nth":case"eq":return isFinite(i)&&Math.abs(i)t%2==0));case"odd":return t.filter(((e,t)=>t%2==1));case"not":{const e=new Set(ha(n,t,r));return t.filter((t=>!e.has(t)))}}}(o.name,h,o.data,n);if(0===u.length||t.length===s+1)return u;const p=t.slice(s+1),f=p.some(as);if(f){if(as(p[0])){const{type:e}=p[0];e!==Zi.Sibling&&e!==Zi.Adjacent||(u=ta(u,Se,!0)),p.unshift(sa)}n={...n,relativeSelector:!1,rootFunc:e=>u.includes(e)}}else n.rootFunc&&n.rootFunc!==Es&&(n={...n,rootFunc:Es});return p.some(ra)?pa(u,p,n,!1,i):f?fa(u,[p],n,i):Ea(u,[p],n)}function fa(e,t,n,r){return da(e,Zs(t,n,e),r)}function da(e,t,n=1/0){return ae((e=>I(e)&&t(e)),ta(e,Se,t.shouldTestNextSiblings),!0,n)}function Ea(e,t,n){const r=(Array.isArray(e)?e:[e]).filter(I);if(0===r.length)return r;const i=Zs(t,n);return i===Es?r:r.filter(i)}var ma=function(e,t,n){if(n||2===arguments.length)for(var r,i=0,s=t.length;i1&&s.length>1?n.reduce((function(e,t){return t(e)}),s):s)}}}var Aa=_a((function(e,t){for(var n,r=[],i=0;ipa(t,e,n,!0,r)));return i.length&&a.push(fa(t,i,n,r)),0===a.length?[]:1===a.length?a[0]:Ee(a.reduce(((e,t)=>[...e,...t])))}(e,i,s))},parent:Ia,parents:Sa,parentsUntil:ba,closest:function(e){var t,n=[];if(!e)return this._make(n);var r={xmlMode:this.options.xmlMode,root:null===(t=this._root)||void 0===t?void 0:t[0]},i="string"==typeof e?function(t){return oa(t,e,r)}:Pa(e);return Bi(this,(function(e){for(;e&&I(e);){if(i(e,0)){n.includes(e)||n.push(e);break}e=e.parent}})),this._make(n)},next:Oa,nextAll:ya,nextUntil:La,prev:ka,prevAll:va,prevUntil:Da,siblings:Ra,children:Ma,contents:function(){var e=this.toArray().reduce((function(e,t){return k(t)?e.concat(t.children):e}),[]);return this._make(e)},each:function(e){for(var t=0,n=this.length;t0})},first:function(){return this.length>1?this._make(this[0]):this},last:function(){return this.length>0?this._make(this[this.length-1]):this},eq:function(e){var t;return 0===(e=+e)&&this.length<=1?this:(e<0&&(e=this.length+e),this._make(null!==(t=this[e])&&void 0!==t?t:[]))},get:function(e){return null==e?this.toArray():this[e<0?this.length+e:e]},toArray:function(){return Array.prototype.slice.call(this)},index:function(e){var t,n;return null==e?(t=this.parent().children(),n=this[0]):"string"==typeof e?(t=this._make(e),n=this[0]):(t=this,n=wi(e)?e[0]:e),Array.prototype.indexOf.call(t,n)},slice:function(e,t){return this._make(Array.prototype.slice.call(this,e,t))},end:function(){var e;return null!==(e=this.prevObject)&&void 0!==e?e:this._make([])},add:function(e,t){var n=this._make(e,t),r=Ee(ma(ma([],this.get(),!0),n.get(),!0));return this._make(r)},addBack:function(e){return this.prevObject?this.add(e?this.prevObject.filter(e):this.prevObject):this}});function Ba(e,t){var n=Array.isArray(e)?e:[e];t?t.children=n:t=null;for(var r=0;r=e.length?null:e[t+n],h=0;h-1&&(p.children.splice(f,1),i===p&&t>f&&o[0]--)}u.parent=i,u.prev&&(u.prev.next=null!==(s=u.next)&&void 0!==s?s:null),u.next&&(u.next.prev=null!==(a=u.prev)&&void 0!==a?a:null),u.prev=0===h?c:r[h-1],u.next=h===r.length-1?l:r[h+1]}return c&&(c.next=r[0]),l&&(l.prev=r[r.length-1]),e.splice.apply(e,o)}var Ga=Ua((function(e,t,n){Ha(t,t.length,0,e,n)})),Ya=Ua((function(e,t,n){Ha(t,0,0,e,n)}));function qa(e){return function(t){for(var n=this.length-1,r=this.parents().last(),i=0;i0&&void 0!==t&&(n[t]+=";".concat(o))}else n[t=s.slice(0,a).trim()]=s.slice(a+1).trim()}return n}(e.attribs.style);if("string"==typeof t)return n[t];if(Array.isArray(t)){var r={};return t.forEach((function(e){null!=n[e]&&(r[e]=n[e])})),r}return n}}var Xa=Object.freeze({__proto__:null,css:function(e,t){return null!=e&&null!=t||"object"==typeof e&&!Array.isArray(e)?Bi(this,(function(n,r){I(n)&&Wa(n,e,t,r)})):0!==this.length?Qa(this[0],e):void 0}}),$a=/%20/g,za=/\r?\n/g;var Ja=Object.freeze({__proto__:null,serialize:function(){var e=this.serializeArray().map((function(e){return"".concat(encodeURIComponent(e.name),"=").concat(encodeURIComponent(e.value))}));return e.join("&").replace($a,"+")},serializeArray:function(){var e=this;return this.map((function(t,n){var r=e._make(n);return I(n)&&"form"===n.name?r.find("input,select,textarea,keygen").toArray():r.filter("input,select,textarea,keygen").toArray()})).filter('[name!=""]:enabled:not(:submit, :button, :image, :reset, :file):matches([checked], :not(:checkbox, :radio))').map((function(t,n){var r,i=e._make(n),s=i.attr("name"),a=null!==(r=i.val())&&void 0!==r?r:"";return Array.isArray(a)?a.map((function(e){return{name:s,value:e.replace(za,"\r\n")}})):{name:s,value:a.replace(za,"\r\n")}})).toArray()}}),Za=function(e,t,n){if(this.length=0,this.options=n,this._root=t,e){for(var r=0;r=55296&&e<=57343}function po(e){return 32!==e&&10!==e&&13!==e&&9!==e&&12!==e&&e>=1&&e<=31||e>=127&&e<=159}function fo(e){return e>=64976&&e<=65007||ro.has(e)}var Eo;!function(e){e.controlCharacterInInputStream="control-character-in-input-stream",e.noncharacterInInputStream="noncharacter-in-input-stream",e.surrogateInInputStream="surrogate-in-input-stream",e.nonVoidHtmlElementStartTagWithTrailingSolidus="non-void-html-element-start-tag-with-trailing-solidus",e.endTagWithAttributes="end-tag-with-attributes",e.endTagWithTrailingSolidus="end-tag-with-trailing-solidus",e.unexpectedSolidusInTag="unexpected-solidus-in-tag",e.unexpectedNullCharacter="unexpected-null-character",e.unexpectedQuestionMarkInsteadOfTagName="unexpected-question-mark-instead-of-tag-name",e.invalidFirstCharacterOfTagName="invalid-first-character-of-tag-name",e.unexpectedEqualsSignBeforeAttributeName="unexpected-equals-sign-before-attribute-name",e.missingEndTagName="missing-end-tag-name",e.unexpectedCharacterInAttributeName="unexpected-character-in-attribute-name",e.unknownNamedCharacterReference="unknown-named-character-reference",e.missingSemicolonAfterCharacterReference="missing-semicolon-after-character-reference",e.unexpectedCharacterAfterDoctypeSystemIdentifier="unexpected-character-after-doctype-system-identifier",e.unexpectedCharacterInUnquotedAttributeValue="unexpected-character-in-unquoted-attribute-value",e.eofBeforeTagName="eof-before-tag-name",e.eofInTag="eof-in-tag",e.missingAttributeValue="missing-attribute-value",e.missingWhitespaceBetweenAttributes="missing-whitespace-between-attributes",e.missingWhitespaceAfterDoctypePublicKeyword="missing-whitespace-after-doctype-public-keyword",e.missingWhitespaceBetweenDoctypePublicAndSystemIdentifiers="missing-whitespace-between-doctype-public-and-system-identifiers",e.missingWhitespaceAfterDoctypeSystemKeyword="missing-whitespace-after-doctype-system-keyword",e.missingQuoteBeforeDoctypePublicIdentifier="missing-quote-before-doctype-public-identifier",e.missingQuoteBeforeDoctypeSystemIdentifier="missing-quote-before-doctype-system-identifier",e.missingDoctypePublicIdentifier="missing-doctype-public-identifier",e.missingDoctypeSystemIdentifier="missing-doctype-system-identifier",e.abruptDoctypePublicIdentifier="abrupt-doctype-public-identifier",e.abruptDoctypeSystemIdentifier="abrupt-doctype-system-identifier",e.cdataInHtmlContent="cdata-in-html-content",e.incorrectlyOpenedComment="incorrectly-opened-comment",e.eofInScriptHtmlCommentLikeText="eof-in-script-html-comment-like-text",e.eofInDoctype="eof-in-doctype",e.nestedComment="nested-comment",e.abruptClosingOfEmptyComment="abrupt-closing-of-empty-comment",e.eofInComment="eof-in-comment",e.incorrectlyClosedComment="incorrectly-closed-comment",e.eofInCdata="eof-in-cdata",e.absenceOfDigitsInNumericCharacterReference="absence-of-digits-in-numeric-character-reference",e.nullCharacterReference="null-character-reference",e.surrogateCharacterReference="surrogate-character-reference",e.characterReferenceOutsideUnicodeRange="character-reference-outside-unicode-range",e.controlCharacterReference="control-character-reference",e.noncharacterCharacterReference="noncharacter-character-reference",e.missingWhitespaceBeforeDoctypeName="missing-whitespace-before-doctype-name",e.missingDoctypeName="missing-doctype-name",e.invalidCharacterSequenceAfterDoctypeName="invalid-character-sequence-after-doctype-name",e.duplicateAttribute="duplicate-attribute",e.nonConformingDoctype="non-conforming-doctype",e.missingDoctype="missing-doctype",e.misplacedDoctype="misplaced-doctype",e.endTagWithoutMatchingOpenElement="end-tag-without-matching-open-element",e.closingOfElementWithOpenChildElements="closing-of-element-with-open-child-elements",e.disallowedContentInNoscriptInHead="disallowed-content-in-noscript-in-head",e.openElementsLeftAfterEof="open-elements-left-after-eof",e.abandonedHeadElementChild="abandoned-head-element-child",e.misplacedStartTagForHeadElement="misplaced-start-tag-for-head-element",e.nestedNoscriptInHead="nested-noscript-in-head",e.eofInElementThatCanContainOnlyText="eof-in-element-that-can-contain-only-text"}(Eo=Eo||(Eo={}));class mo{constructor(e){this.handler=e,this.html="",this.pos=-1,this.lastGapPos=-2,this.gapStack=[],this.skipNextNewLine=!1,this.lastChunkWritten=!1,this.endOfChunkHit=!1,this.bufferWaterline=65536,this.isEol=!1,this.lineStartPos=0,this.droppedBufferSize=0,this.line=1,this.lastErrOffset=-1}get col(){return this.pos-this.lineStartPos+Number(this.lastGapPos!==this.pos)}get offset(){return this.droppedBufferSize+this.pos}getError(e){const{line:t,col:n,offset:r}=this;return{code:e,startLine:t,endLine:t,startCol:n,endCol:n,startOffset:r,endOffset:r}}_err(e){this.handler.onParseError&&this.lastErrOffset!==this.offset&&(this.lastErrOffset=this.offset,this.handler.onParseError(this.getError(e)))}_addGap(){this.gapStack.push(this.lastGapPos),this.lastGapPos=this.pos}_processSurrogate(e){if(this.pos!==this.html.length-1){const t=this.html.charCodeAt(this.pos+1);if(function(e){return e>=56320&&e<=57343}(t))return this.pos++,this._addGap(),1024*(e-55296)+9216+t}else if(!this.lastChunkWritten)return this.endOfChunkHit=!0,io.EOF;return this._err(Eo.surrogateInInputStream),e}willDropParsedChunk(){return this.pos>this.bufferWaterline}dropParsedChunk(){this.willDropParsedChunk()&&(this.html=this.html.substring(this.pos),this.lineStartPos-=this.pos,this.droppedBufferSize+=this.pos,this.pos=0,this.lastGapPos=-2,this.gapStack.length=0)}write(e,t){this.html.length>0?this.html+=e:this.html=e,this.endOfChunkHit=!1,this.lastChunkWritten=t}insertHtmlAtCurrentPos(e){this.html=this.html.substring(0,this.pos+1)+e+this.html.substring(this.pos+1),this.endOfChunkHit=!1}startsWith(e,t){if(this.pos+e.length>this.html.length)return this.endOfChunkHit=!this.lastChunkWritten,!1;if(t)return this.html.startsWith(e,this.pos);for(let t=0;t=this.html.length?(this.endOfChunkHit=!this.lastChunkWritten,io.EOF):this.html.charCodeAt(t)}advance(){if(this.pos++,this.isEol&&(this.isEol=!1,this.line++,this.lineStartPos=this.pos),this.pos>=this.html.length)return this.endOfChunkHit=!this.lastChunkWritten,io.EOF;let e=this.html.charCodeAt(this.pos);if(e===io.CARRIAGE_RETURN)return this.isEol=!0,this.skipNextNewLine=!0,io.LINE_FEED;if(e===io.LINE_FEED&&(this.isEol=!0,this.skipNextNewLine))return this.line--,this.skipNextNewLine=!1,this._addGap(),this.advance();this.skipNextNewLine=!1,uo(e)&&(e=this._processSurrogate(e));return null===this.handler.onParseError||e>31&&e<127||e===io.LINE_FEED||e===io.CARRIAGE_RETURN||e>159&&e<64976||this._checkForProblematicCharacters(e),e}_checkForProblematicCharacters(e){po(e)?this._err(Eo.controlCharacterInInputStream):fo(e)&&this._err(Eo.noncharacterInInputStream)}retreat(e){for(this.pos-=e;this.pos=0;n--)if(e.attrs[n].name===t)return e.attrs[n].value;return null}!function(e){e[e.CHARACTER=0]="CHARACTER",e[e.NULL_CHARACTER=1]="NULL_CHARACTER",e[e.WHITESPACE_CHARACTER=2]="WHITESPACE_CHARACTER",e[e.START_TAG=3]="START_TAG",e[e.END_TAG=4]="END_TAG",e[e.COMMENT=5]="COMMENT",e[e.DOCTYPE=6]="DOCTYPE",e[e.EOF=7]="EOF",e[e.HIBERNATION=8]="HIBERNATION"}(To=To||(To={}));var Ao=Ke((function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.default=new Uint16Array('ᵁ<Õıʊҝջאٵ۞ޢߖࠏ੊ઑඡ๭༉༦჊ረዡᐕᒝᓃᓟᔥ\0\0\0\0\0\0ᕫᛍᦍᰒᷝ὾⁠↰⊍⏀⏻⑂⠤⤒ⴈ⹈⿎〖㊺㘹㞬㣾㨨㩱㫠㬮ࠀEMabcfglmnoprstu\\bfms„‹•˜¦³¹ÈÏlig耻Æ䃆P耻&䀦cute耻Á䃁reve;䄂Āiyx}rc耻Â䃂;䐐r;쀀𝔄rave耻À䃀pha;䎑acr;䄀d;橓Āgp¡on;䄄f;쀀𝔸plyFunction;恡ing耻Å䃅Ācs¾Ãr;쀀𝒜ign;扔ilde耻Ã䃃ml耻Ä䃄ЀaceforsuåûþėĜĢħĪĀcrêòkslash;或Ŷöø;櫧ed;挆y;䐑ƀcrtąċĔause;戵noullis;愬a;䎒r;쀀𝔅pf;쀀𝔹eve;䋘còēmpeq;扎܀HOacdefhilorsuōőŖƀƞƢƵƷƺǜȕɳɸɾcy;䐧PY耻©䂩ƀcpyŝŢźute;䄆Ā;iŧŨ拒talDifferentialD;慅leys;愭ȀaeioƉƎƔƘron;䄌dil耻Ç䃇rc;䄈nint;戰ot;䄊ĀdnƧƭilla;䂸terDot;䂷òſi;䎧rcleȀDMPTLJNjǑǖot;抙inus;抖lus;投imes;抗oĀcsǢǸkwiseContourIntegral;戲eCurlyĀDQȃȏoubleQuote;思uote;怙ȀlnpuȞȨɇɕonĀ;eȥȦ户;橴ƀgitȯȶȺruent;扡nt;戯ourIntegral;戮ĀfrɌɎ;愂oduct;成nterClockwiseContourIntegral;戳oss;樯cr;쀀𝒞pĀ;Cʄʅ拓ap;才րDJSZacefiosʠʬʰʴʸˋ˗ˡ˦̳ҍĀ;oŹʥtrahd;椑cy;䐂cy;䐅cy;䐏ƀgrsʿ˄ˇger;怡r;憡hv;櫤Āayː˕ron;䄎;䐔lĀ;t˝˞戇a;䎔r;쀀𝔇Āaf˫̧Ācm˰̢riticalȀADGT̖̜̀̆cute;䂴oŴ̋̍;䋙bleAcute;䋝rave;䁠ilde;䋜ond;拄ferentialD;慆Ѱ̽\0\0\0͔͂\0Ѕf;쀀𝔻ƀ;DE͈͉͍䂨ot;惜qual;扐blèCDLRUVͣͲ΂ϏϢϸontourIntegraìȹoɴ͹\0\0ͻ»͉nArrow;懓Āeo·ΤftƀARTΐΖΡrrow;懐ightArrow;懔eåˊngĀLRΫτeftĀARγιrrow;柸ightArrow;柺ightArrow;柹ightĀATϘϞrrow;懒ee;抨pɁϩ\0\0ϯrrow;懑ownArrow;懕erticalBar;戥ǹABLRTaВЪаўѿͼrrowƀ;BUНОТ憓ar;椓pArrow;懵reve;䌑eft˒к\0ц\0ѐightVector;楐eeVector;楞ectorĀ;Bљњ憽ar;楖ightǔѧ\0ѱeeVector;楟ectorĀ;BѺѻ懁ar;楗eeĀ;A҆҇护rrow;憧ĀctҒҗr;쀀𝒟rok;䄐ࠀNTacdfglmopqstuxҽӀӄӋӞӢӧӮӵԡԯԶՒ՝ՠեG;䅊H耻Ð䃐cute耻É䃉ƀaiyӒӗӜron;䄚rc耻Ê䃊;䐭ot;䄖r;쀀𝔈rave耻È䃈ement;戈ĀapӺӾcr;䄒tyɓԆ\0\0ԒmallSquare;旻erySmallSquare;斫ĀgpԦԪon;䄘f;쀀𝔼silon;䎕uĀaiԼՉlĀ;TՂՃ橵ilde;扂librium;懌Āci՗՚r;愰m;橳a;䎗ml耻Ë䃋Āipժկsts;戃onentialE;慇ʀcfiosօֈ֍ֲ׌y;䐤r;쀀𝔉lledɓ֗\0\0֣mallSquare;旼erySmallSquare;斪Ͱֺ\0ֿ\0\0ׄf;쀀𝔽All;戀riertrf;愱cò׋؀JTabcdfgorstר׬ׯ׺؀ؒؖ؛؝أ٬ٲcy;䐃耻>䀾mmaĀ;d׷׸䎓;䏜reve;䄞ƀeiy؇،ؐdil;䄢rc;䄜;䐓ot;䄠r;쀀𝔊;拙pf;쀀𝔾eater̀EFGLSTصلَٖٛ٦qualĀ;Lؾؿ扥ess;招ullEqual;执reater;檢ess;扷lantEqual;橾ilde;扳cr;쀀𝒢;扫ЀAacfiosuڅڋږڛڞڪھۊRDcy;䐪Āctڐڔek;䋇;䁞irc;䄤r;愌lbertSpace;愋ǰگ\0ڲf;愍izontalLine;攀Āctۃۅòکrok;䄦mpńېۘownHumðįqual;扏܀EJOacdfgmnostuۺ۾܃܇܎ܚܞܡܨ݄ݸދޏޕcy;䐕lig;䄲cy;䐁cute耻Í䃍Āiyܓܘrc耻Î䃎;䐘ot;䄰r;愑rave耻Ì䃌ƀ;apܠܯܿĀcgܴܷr;䄪inaryI;慈lieóϝǴ݉\0ݢĀ;eݍݎ戬Āgrݓݘral;戫section;拂isibleĀCTݬݲomma;恣imes;恢ƀgptݿރވon;䄮f;쀀𝕀a;䎙cr;愐ilde;䄨ǫޚ\0ޞcy;䐆l耻Ï䃏ʀcfosuެ޷޼߂ߐĀiyޱ޵rc;䄴;䐙r;쀀𝔍pf;쀀𝕁ǣ߇\0ߌr;쀀𝒥rcy;䐈kcy;䐄΀HJacfosߤߨ߽߬߱ࠂࠈcy;䐥cy;䐌ppa;䎚Āey߶߻dil;䄶;䐚r;쀀𝔎pf;쀀𝕂cr;쀀𝒦րJTaceflmostࠥࠩࠬࡐࡣ঳সে্਷ੇcy;䐉耻<䀼ʀcmnpr࠷࠼ࡁࡄࡍute;䄹bda;䎛g;柪lacetrf;愒r;憞ƀaeyࡗ࡜ࡡron;䄽dil;䄻;䐛Āfsࡨ॰tԀACDFRTUVarࡾࢩࢱࣦ࣠ࣼयज़ΐ४Ānrࢃ࢏gleBracket;柨rowƀ;BR࢙࢚࢞憐ar;懤ightArrow;懆eiling;挈oǵࢷ\0ࣃbleBracket;柦nǔࣈ\0࣒eeVector;楡ectorĀ;Bࣛࣜ懃ar;楙loor;挊ightĀAV࣯ࣵrrow;憔ector;楎Āerँगeƀ;AVउऊऐ抣rrow;憤ector;楚iangleƀ;BEतथऩ抲ar;槏qual;抴pƀDTVषूौownVector;楑eeVector;楠ectorĀ;Bॖॗ憿ar;楘ectorĀ;B॥०憼ar;楒ightáΜs̀EFGLSTॾঋকঝঢভqualGreater;拚ullEqual;扦reater;扶ess;檡lantEqual;橽ilde;扲r;쀀𝔏Ā;eঽা拘ftarrow;懚idot;䄿ƀnpw৔ਖਛgȀLRlr৞৷ਂਐeftĀAR০৬rrow;柵ightArrow;柷ightArrow;柶eftĀarγਊightáοightáϊf;쀀𝕃erĀLRਢਬeftArrow;憙ightArrow;憘ƀchtਾੀੂòࡌ;憰rok;䅁;扪Ѐacefiosuਗ਼੝੠੷੼અઋ઎p;椅y;䐜Ādl੥੯iumSpace;恟lintrf;愳r;쀀𝔐nusPlus;戓pf;쀀𝕄cò੶;䎜ҀJacefostuણધભીଔଙඑ඗ඞcy;䐊cute;䅃ƀaey઴હાron;䅇dil;䅅;䐝ƀgswે૰଎ativeƀMTV૓૟૨ediumSpace;怋hiĀcn૦૘ë૙eryThiî૙tedĀGL૸ଆreaterGreateòٳessLesóੈLine;䀊r;쀀𝔑ȀBnptଢନଷ଺reak;恠BreakingSpace;䂠f;愕ڀ;CDEGHLNPRSTV୕ୖ୪୼஡௫ఄ౞಄ದ೘ൡඅ櫬Āou୛୤ngruent;扢pCap;扭oubleVerticalBar;戦ƀlqxஃஊ஛ement;戉ualĀ;Tஒஓ扠ilde;쀀≂̸ists;戄reater΀;EFGLSTஶஷ஽௉௓௘௥扯qual;扱ullEqual;쀀≧̸reater;쀀≫̸ess;批lantEqual;쀀⩾̸ilde;扵umpń௲௽ownHump;쀀≎̸qual;쀀≏̸eĀfsఊధtTriangleƀ;BEచఛడ拪ar;쀀⧏̸qual;括s̀;EGLSTవశ఼ౄోౘ扮qual;扰reater;扸ess;쀀≪̸lantEqual;쀀⩽̸ilde;扴estedĀGL౨౹reaterGreater;쀀⪢̸essLess;쀀⪡̸recedesƀ;ESಒಓಛ技qual;쀀⪯̸lantEqual;拠ĀeiಫಹverseElement;戌ghtTriangleƀ;BEೋೌ೒拫ar;쀀⧐̸qual;拭ĀquೝഌuareSuĀbp೨೹setĀ;E೰ೳ쀀⊏̸qual;拢ersetĀ;Eഃആ쀀⊐̸qual;拣ƀbcpഓതൎsetĀ;Eഛഞ쀀⊂⃒qual;抈ceedsȀ;ESTലള഻െ抁qual;쀀⪰̸lantEqual;拡ilde;쀀≿̸ersetĀ;E൘൛쀀⊃⃒qual;抉ildeȀ;EFT൮൯൵ൿ扁qual;扄ullEqual;扇ilde;扉erticalBar;戤cr;쀀𝒩ilde耻Ñ䃑;䎝܀Eacdfgmoprstuvලෂ෉෕ෛ෠෧෼ขภยา฿ไlig;䅒cute耻Ó䃓Āiy෎ීrc耻Ô䃔;䐞blac;䅐r;쀀𝔒rave耻Ò䃒ƀaei෮ෲ෶cr;䅌ga;䎩cron;䎟pf;쀀𝕆enCurlyĀDQฎบoubleQuote;怜uote;怘;橔Āclวฬr;쀀𝒪ash耻Ø䃘iŬื฼de耻Õ䃕es;樷ml耻Ö䃖erĀBP๋๠Āar๐๓r;怾acĀek๚๜;揞et;掴arenthesis;揜Ҁacfhilors๿ງຊຏຒດຝະ໼rtialD;戂y;䐟r;쀀𝔓i;䎦;䎠usMinus;䂱Āipຢອncareplanåڝf;愙Ȁ;eio຺ູ໠໤檻cedesȀ;EST່້໏໚扺qual;檯lantEqual;扼ilde;找me;怳Ādp໩໮uct;戏ortionĀ;aȥ໹l;戝Āci༁༆r;쀀𝒫;䎨ȀUfos༑༖༛༟OT耻"䀢r;쀀𝔔pf;愚cr;쀀𝒬؀BEacefhiorsu༾གྷཇའཱིྦྷྪྭ႖ႩႴႾarr;椐G耻®䂮ƀcnrཎནབute;䅔g;柫rĀ;tཛྷཝ憠l;椖ƀaeyཧཬཱron;䅘dil;䅖;䐠Ā;vླྀཹ愜erseĀEUྂྙĀlq྇ྎement;戋uilibrium;懋pEquilibrium;楯r»ཹo;䎡ghtЀACDFTUVa࿁࿫࿳ဢဨၛႇϘĀnr࿆࿒gleBracket;柩rowƀ;BL࿜࿝࿡憒ar;懥eftArrow;懄eiling;按oǵ࿹\0စbleBracket;柧nǔည\0နeeVector;楝ectorĀ;Bဝသ懂ar;楕loor;挋Āerိ၃eƀ;AVဵံြ抢rrow;憦ector;楛iangleƀ;BEၐၑၕ抳ar;槐qual;抵pƀDTVၣၮၸownVector;楏eeVector;楜ectorĀ;Bႂႃ憾ar;楔ectorĀ;B႑႒懀ar;楓Āpuႛ႞f;愝ndImplies;楰ightarrow;懛ĀchႹႼr;愛;憱leDelayed;槴ڀHOacfhimoqstuფჱჷჽᄙᄞᅑᅖᅡᅧᆵᆻᆿĀCcჩხHcy;䐩y;䐨FTcy;䐬cute;䅚ʀ;aeiyᄈᄉᄎᄓᄗ檼ron;䅠dil;䅞rc;䅜;䐡r;쀀𝔖ortȀDLRUᄪᄴᄾᅉownArrow»ОeftArrow»࢚ightArrow»࿝pArrow;憑gma;䎣allCircle;战pf;쀀𝕊ɲᅭ\0\0ᅰt;戚areȀ;ISUᅻᅼᆉᆯ斡ntersection;抓uĀbpᆏᆞsetĀ;Eᆗᆘ抏qual;抑ersetĀ;Eᆨᆩ抐qual;抒nion;抔cr;쀀𝒮ar;拆ȀbcmpᇈᇛሉላĀ;sᇍᇎ拐etĀ;Eᇍᇕqual;抆ĀchᇠህeedsȀ;ESTᇭᇮᇴᇿ扻qual;檰lantEqual;扽ilde;承Tháྌ;我ƀ;esሒሓሣ拑rsetĀ;Eሜም抃qual;抇et»ሓրHRSacfhiorsሾቄ቉ቕ቞ቱቶኟዂወዑORN耻Þ䃞ADE;愢ĀHc቎ቒcy;䐋y;䐦Ābuቚቜ;䀉;䎤ƀaeyብቪቯron;䅤dil;䅢;䐢r;쀀𝔗Āeiቻ኉Dzኀ\0ኇefore;戴a;䎘Ācn኎ኘkSpace;쀀  Space;怉ldeȀ;EFTካኬኲኼ戼qual;扃ullEqual;扅ilde;扈pf;쀀𝕋ipleDot;惛Āctዖዛr;쀀𝒯rok;䅦ૡዷጎጚጦ\0ጬጱ\0\0\0\0\0ጸጽ፷ᎅ\0᏿ᐄᐊᐐĀcrዻጁute耻Ú䃚rĀ;oጇገ憟cir;楉rǣጓ\0጖y;䐎ve;䅬Āiyጞጣrc耻Û䃛;䐣blac;䅰r;쀀𝔘rave耻Ù䃙acr;䅪Ādiፁ፩erĀBPፈ፝Āarፍፐr;䁟acĀekፗፙ;揟et;掵arenthesis;揝onĀ;P፰፱拃lus;抎Āgp፻፿on;䅲f;쀀𝕌ЀADETadps᎕ᎮᎸᏄϨᏒᏗᏳrrowƀ;BDᅐᎠᎤar;椒ownArrow;懅ownArrow;憕quilibrium;楮eeĀ;AᏋᏌ报rrow;憥ownáϳerĀLRᏞᏨeftArrow;憖ightArrow;憗iĀ;lᏹᏺ䏒on;䎥ing;䅮cr;쀀𝒰ilde;䅨ml耻Ü䃜ҀDbcdefosvᐧᐬᐰᐳᐾᒅᒊᒐᒖash;披ar;櫫y;䐒ashĀ;lᐻᐼ抩;櫦Āerᑃᑅ;拁ƀbtyᑌᑐᑺar;怖Ā;iᑏᑕcalȀBLSTᑡᑥᑪᑴar;戣ine;䁼eparator;杘ilde;所ThinSpace;怊r;쀀𝔙pf;쀀𝕍cr;쀀𝒱dash;抪ʀcefosᒧᒬᒱᒶᒼirc;䅴dge;拀r;쀀𝔚pf;쀀𝕎cr;쀀𝒲Ȁfiosᓋᓐᓒᓘr;쀀𝔛;䎞pf;쀀𝕏cr;쀀𝒳ҀAIUacfosuᓱᓵᓹᓽᔄᔏᔔᔚᔠcy;䐯cy;䐇cy;䐮cute耻Ý䃝Āiyᔉᔍrc;䅶;䐫r;쀀𝔜pf;쀀𝕐cr;쀀𝒴ml;䅸ЀHacdefosᔵᔹᔿᕋᕏᕝᕠᕤcy;䐖cute;䅹Āayᕄᕉron;䅽;䐗ot;䅻Dzᕔ\0ᕛoWidtè૙a;䎖r;愨pf;愤cr;쀀𝒵௡ᖃᖊᖐ\0ᖰᖶᖿ\0\0\0\0ᗆᗛᗫᙟ᙭\0ᚕ᚛ᚲᚹ\0ᚾcute耻á䃡reve;䄃̀;Ediuyᖜᖝᖡᖣᖨᖭ戾;쀀∾̳;房rc耻â䃢te肻´̆;䐰lig耻æ䃦Ā;r²ᖺ;쀀𝔞rave耻à䃠ĀepᗊᗖĀfpᗏᗔsym;愵èᗓha;䎱ĀapᗟcĀclᗤᗧr;䄁g;樿ɤᗰ\0\0ᘊʀ;adsvᗺᗻᗿᘁᘇ戧nd;橕;橜lope;橘;橚΀;elmrszᘘᘙᘛᘞᘿᙏᙙ戠;榤e»ᘙsdĀ;aᘥᘦ戡ѡᘰᘲᘴᘶᘸᘺᘼᘾ;榨;榩;榪;榫;榬;榭;榮;榯tĀ;vᙅᙆ戟bĀ;dᙌᙍ抾;榝Āptᙔᙗh;戢»¹arr;捼Āgpᙣᙧon;䄅f;쀀𝕒΀;Eaeiop዁ᙻᙽᚂᚄᚇᚊ;橰cir;橯;扊d;手s;䀧roxĀ;e዁ᚒñᚃing耻å䃥ƀctyᚡᚦᚨr;쀀𝒶;䀪mpĀ;e዁ᚯñʈilde耻ã䃣ml耻ä䃤Āciᛂᛈoninôɲnt;樑ࠀNabcdefiklnoprsu᛭ᛱᜰ᜼ᝃᝈ᝸᝽០៦ᠹᡐᜍ᤽᥈ᥰot;櫭Ācrᛶ᜞kȀcepsᜀᜅᜍᜓong;扌psilon;䏶rime;怵imĀ;e᜚᜛戽q;拍Ŷᜢᜦee;抽edĀ;gᜬᜭ挅e»ᜭrkĀ;t፜᜷brk;掶Āoyᜁᝁ;䐱quo;怞ʀcmprtᝓ᝛ᝡᝤᝨausĀ;eĊĉptyv;榰séᜌnoõēƀahwᝯ᝱ᝳ;䎲;愶een;扬r;쀀𝔟g΀costuvwឍឝឳេ៕៛៞ƀaiuបពរðݠrc;旯p»፱ƀdptឤឨឭot;樀lus;樁imes;樂ɱឹ\0\0ើcup;樆ar;昅riangleĀdu៍្own;施p;斳plus;樄eåᑄåᒭarow;植ƀako៭ᠦᠵĀcn៲ᠣkƀlst៺֫᠂ozenge;槫riangleȀ;dlr᠒᠓᠘᠝斴own;斾eft;旂ight;斸k;搣Ʊᠫ\0ᠳƲᠯ\0ᠱ;斒;斑4;斓ck;斈ĀeoᠾᡍĀ;qᡃᡆ쀀=⃥uiv;쀀≡⃥t;挐Ȁptwxᡙᡞᡧᡬf;쀀𝕓Ā;tᏋᡣom»Ꮜtie;拈؀DHUVbdhmptuvᢅᢖᢪᢻᣗᣛᣬ᣿ᤅᤊᤐᤡȀLRlrᢎᢐᢒᢔ;敗;敔;敖;敓ʀ;DUduᢡᢢᢤᢦᢨ敐;敦;敩;敤;敧ȀLRlrᢳᢵᢷᢹ;敝;敚;敜;教΀;HLRhlrᣊᣋᣍᣏᣑᣓᣕ救;敬;散;敠;敫;敢;敟ox;槉ȀLRlrᣤᣦᣨᣪ;敕;敒;攐;攌ʀ;DUduڽ᣷᣹᣻᣽;敥;敨;攬;攴inus;抟lus;択imes;抠ȀLRlrᤙᤛᤝ᤟;敛;敘;攘;攔΀;HLRhlrᤰᤱᤳᤵᤷ᤻᤹攂;敪;敡;敞;攼;攤;攜Āevģ᥂bar耻¦䂦Ȁceioᥑᥖᥚᥠr;쀀𝒷mi;恏mĀ;e᜚᜜lƀ;bhᥨᥩᥫ䁜;槅sub;柈Ŭᥴ᥾lĀ;e᥹᥺怢t»᥺pƀ;Eeįᦅᦇ;檮Ā;qۜۛೡᦧ\0᧨ᨑᨕᨲ\0ᨷᩐ\0\0᪴\0\0᫁\0\0ᬡᬮ᭍᭒\0᯽\0ᰌƀcpr᦭ᦲ᧝ute;䄇̀;abcdsᦿᧀᧄ᧊᧕᧙戩nd;橄rcup;橉Āau᧏᧒p;橋p;橇ot;橀;쀀∩︀Āeo᧢᧥t;恁îړȀaeiu᧰᧻ᨁᨅǰ᧵\0᧸s;橍on;䄍dil耻ç䃧rc;䄉psĀ;sᨌᨍ橌m;橐ot;䄋ƀdmnᨛᨠᨦil肻¸ƭptyv;榲t脀¢;eᨭᨮ䂢räƲr;쀀𝔠ƀceiᨽᩀᩍy;䑇ckĀ;mᩇᩈ朓ark»ᩈ;䏇r΀;Ecefms᩟᩠ᩢᩫ᪤᪪᪮旋;槃ƀ;elᩩᩪᩭ䋆q;扗eɡᩴ\0\0᪈rrowĀlr᩼᪁eft;憺ight;憻ʀRSacd᪒᪔᪖᪚᪟»ཇ;擈st;抛irc;抚ash;抝nint;樐id;櫯cir;槂ubsĀ;u᪻᪼晣it»᪼ˬ᫇᫔᫺\0ᬊonĀ;eᫍᫎ䀺Ā;qÇÆɭ᫙\0\0᫢aĀ;t᫞᫟䀬;䁀ƀ;fl᫨᫩᫫戁îᅠeĀmx᫱᫶ent»᫩eóɍǧ᫾\0ᬇĀ;dኻᬂot;橭nôɆƀfryᬐᬔᬗ;쀀𝕔oäɔ脀©;sŕᬝr;愗Āaoᬥᬩrr;憵ss;朗Ācuᬲᬷr;쀀𝒸Ābpᬼ᭄Ā;eᭁᭂ櫏;櫑Ā;eᭉᭊ櫐;櫒dot;拯΀delprvw᭠᭬᭷ᮂᮬᯔ᯹arrĀlr᭨᭪;椸;椵ɰ᭲\0\0᭵r;拞c;拟arrĀ;p᭿ᮀ憶;椽̀;bcdosᮏᮐᮖᮡᮥᮨ截rcap;橈Āauᮛᮞp;橆p;橊ot;抍r;橅;쀀∪︀Ȁalrv᮵ᮿᯞᯣrrĀ;mᮼᮽ憷;椼yƀevwᯇᯔᯘqɰᯎ\0\0ᯒreã᭳uã᭵ee;拎edge;拏en耻¤䂤earrowĀlrᯮ᯳eft»ᮀight»ᮽeäᯝĀciᰁᰇoninôǷnt;戱lcty;挭ঀAHabcdefhijlorstuwz᰸᰻᰿ᱝᱩᱵᲊᲞᲬᲷ᳻᳿ᴍᵻᶑᶫᶻ᷆᷍rò΁ar;楥Ȁglrs᱈ᱍ᱒᱔ger;怠eth;愸òᄳhĀ;vᱚᱛ怐»ऊūᱡᱧarow;椏aã̕Āayᱮᱳron;䄏;䐴ƀ;ao̲ᱼᲄĀgrʿᲁr;懊tseq;橷ƀglmᲑᲔᲘ耻°䂰ta;䎴ptyv;榱ĀirᲣᲨsht;楿;쀀𝔡arĀlrᲳᲵ»ࣜ»သʀaegsv᳂͸᳖᳜᳠mƀ;oș᳊᳔ndĀ;ș᳑uit;晦amma;䏝in;拲ƀ;io᳧᳨᳸䃷de脀÷;o᳧ᳰntimes;拇nø᳷cy;䑒cɯᴆ\0\0ᴊrn;挞op;挍ʀlptuwᴘᴝᴢᵉᵕlar;䀤f;쀀𝕕ʀ;emps̋ᴭᴷᴽᵂqĀ;d͒ᴳot;扑inus;戸lus;戔quare;抡blebarwedgåúnƀadhᄮᵝᵧownarrowóᲃarpoonĀlrᵲᵶefôᲴighôᲶŢᵿᶅkaro÷གɯᶊ\0\0ᶎrn;挟op;挌ƀcotᶘᶣᶦĀryᶝᶡ;쀀𝒹;䑕l;槶rok;䄑Ādrᶰᶴot;拱iĀ;fᶺ᠖斿Āah᷀᷃ròЩaòྦangle;榦Āci᷒ᷕy;䑟grarr;柿ऀDacdefglmnopqrstuxḁḉḙḸոḼṉṡṾấắẽỡἪἷὄ὎὚ĀDoḆᴴoôᲉĀcsḎḔute耻é䃩ter;橮ȀaioyḢḧḱḶron;䄛rĀ;cḭḮ扖耻ê䃪lon;払;䑍ot;䄗ĀDrṁṅot;扒;쀀𝔢ƀ;rsṐṑṗ檚ave耻è䃨Ā;dṜṝ檖ot;檘Ȁ;ilsṪṫṲṴ檙nters;揧;愓Ā;dṹṺ檕ot;檗ƀapsẅẉẗcr;䄓tyƀ;svẒẓẕ戅et»ẓpĀ1;ẝẤijạả;怄;怅怃ĀgsẪẬ;䅋p;怂ĀgpẴẸon;䄙f;쀀𝕖ƀalsỄỎỒrĀ;sỊị拕l;槣us;橱iƀ;lvỚớở䎵on»ớ;䏵ȀcsuvỪỳἋἣĀioữḱrc»Ḯɩỹ\0\0ỻíՈantĀglἂἆtr»ṝess»Ṻƀaeiἒ἖Ἒls;䀽st;扟vĀ;DȵἠD;橸parsl;槥ĀDaἯἳot;打rr;楱ƀcdiἾὁỸr;愯oô͒ĀahὉὋ;䎷耻ð䃰Āmrὓὗl耻ë䃫o;悬ƀcipὡὤὧl;䀡sôծĀeoὬὴctatioîՙnentialåչৡᾒ\0ᾞ\0ᾡᾧ\0\0ῆῌ\0ΐ\0ῦῪ \0 ⁚llingdotseñṄy;䑄male;晀ƀilrᾭᾳ῁lig;耀ffiɩᾹ\0\0᾽g;耀ffig;耀ffl;쀀𝔣lig;耀filig;쀀fjƀaltῙ῜ῡt;晭ig;耀flns;斱of;䆒ǰ΅\0ῳf;쀀𝕗ĀakֿῷĀ;vῼ´拔;櫙artint;樍Āao‌⁕Ācs‑⁒ႉ‸⁅⁈\0⁐β•‥‧‪‬\0‮耻½䂽;慓耻¼䂼;慕;慙;慛Ƴ‴\0‶;慔;慖ʴ‾⁁\0\0⁃耻¾䂾;慗;慜5;慘ƶ⁌\0⁎;慚;慝8;慞l;恄wn;挢cr;쀀𝒻ࢀEabcdefgijlnorstv₂₉₟₥₰₴⃰⃵⃺⃿℃ℒℸ̗ℾ⅒↞Ā;lٍ₇;檌ƀcmpₐₕ₝ute;䇵maĀ;dₜ᳚䎳;檆reve;䄟Āiy₪₮rc;䄝;䐳ot;䄡Ȁ;lqsؾق₽⃉ƀ;qsؾٌ⃄lanô٥Ȁ;cdl٥⃒⃥⃕c;檩otĀ;o⃜⃝檀Ā;l⃢⃣檂;檄Ā;e⃪⃭쀀⋛︀s;檔r;쀀𝔤Ā;gٳ؛mel;愷cy;䑓Ȁ;Eajٚℌℎℐ;檒;檥;檤ȀEaesℛℝ℩ℴ;扩pĀ;p℣ℤ檊rox»ℤĀ;q℮ℯ檈Ā;q℮ℛim;拧pf;쀀𝕘Āci⅃ⅆr;愊mƀ;el٫ⅎ⅐;檎;檐茀>;cdlqr׮ⅠⅪⅮⅳⅹĀciⅥⅧ;檧r;橺ot;拗Par;榕uest;橼ʀadelsↄⅪ←ٖ↛ǰ↉\0↎proø₞r;楸qĀlqؿ↖lesó₈ií٫Āen↣↭rtneqq;쀀≩︀Å↪ԀAabcefkosy⇄⇇⇱⇵⇺∘∝∯≨≽ròΠȀilmr⇐⇔⇗⇛rsðᒄf»․ilôکĀdr⇠⇤cy;䑊ƀ;cwࣴ⇫⇯ir;楈;憭ar;意irc;䄥ƀalr∁∎∓rtsĀ;u∉∊晥it»∊lip;怦con;抹r;쀀𝔥sĀew∣∩arow;椥arow;椦ʀamopr∺∾≃≞≣rr;懿tht;戻kĀlr≉≓eftarrow;憩ightarrow;憪f;쀀𝕙bar;怕ƀclt≯≴≸r;쀀𝒽asè⇴rok;䄧Ābp⊂⊇ull;恃hen»ᱛૡ⊣\0⊪\0⊸⋅⋎\0⋕⋳\0\0⋸⌢⍧⍢⍿\0⎆⎪⎴cute耻í䃭ƀ;iyݱ⊰⊵rc耻î䃮;䐸Ācx⊼⊿y;䐵cl耻¡䂡ĀfrΟ⋉;쀀𝔦rave耻ì䃬Ȁ;inoܾ⋝⋩⋮Āin⋢⋦nt;樌t;戭fin;槜ta;愩lig;䄳ƀaop⋾⌚⌝ƀcgt⌅⌈⌗r;䄫ƀelpܟ⌏⌓inåގarôܠh;䄱f;抷ed;䆵ʀ;cfotӴ⌬⌱⌽⍁are;愅inĀ;t⌸⌹戞ie;槝doô⌙ʀ;celpݗ⍌⍐⍛⍡al;抺Āgr⍕⍙eróᕣã⍍arhk;樗rod;樼Ȁcgpt⍯⍲⍶⍻y;䑑on;䄯f;쀀𝕚a;䎹uest耻¿䂿Āci⎊⎏r;쀀𝒾nʀ;EdsvӴ⎛⎝⎡ӳ;拹ot;拵Ā;v⎦⎧拴;拳Ā;iݷ⎮lde;䄩ǫ⎸\0⎼cy;䑖l耻ï䃯̀cfmosu⏌⏗⏜⏡⏧⏵Āiy⏑⏕rc;䄵;䐹r;쀀𝔧ath;䈷pf;쀀𝕛ǣ⏬\0⏱r;쀀𝒿rcy;䑘kcy;䑔Ѐacfghjos␋␖␢␧␭␱␵␻ppaĀ;v␓␔䎺;䏰Āey␛␠dil;䄷;䐺r;쀀𝔨reen;䄸cy;䑅cy;䑜pf;쀀𝕜cr;쀀𝓀஀ABEHabcdefghjlmnoprstuv⑰⒁⒆⒍⒑┎┽╚▀♎♞♥♹♽⚚⚲⛘❝❨➋⟀⠁⠒ƀart⑷⑺⑼rò৆òΕail;椛arr;椎Ā;gঔ⒋;檋ar;楢ॣ⒥\0⒪\0⒱\0\0\0\0\0⒵Ⓔ\0ⓆⓈⓍ\0⓹ute;䄺mptyv;榴raîࡌbda;䎻gƀ;dlࢎⓁⓃ;榑åࢎ;檅uo耻«䂫rЀ;bfhlpst࢙ⓞⓦⓩ⓫⓮⓱⓵Ā;f࢝ⓣs;椟s;椝ë≒p;憫l;椹im;楳l;憢ƀ;ae⓿─┄檫il;椙Ā;s┉┊檭;쀀⪭︀ƀabr┕┙┝rr;椌rk;杲Āak┢┬cĀek┨┪;䁻;䁛Āes┱┳;榋lĀdu┹┻;榏;榍Ȁaeuy╆╋╖╘ron;䄾Ādi═╔il;䄼ìࢰâ┩;䐻Ȁcqrs╣╦╭╽a;椶uoĀ;rนᝆĀdu╲╷har;楧shar;楋h;憲ʀ;fgqs▋▌উ◳◿扤tʀahlrt▘▤▷◂◨rrowĀ;t࢙□aé⓶arpoonĀdu▯▴own»њp»०eftarrows;懇ightƀahs◍◖◞rrowĀ;sࣴࢧarpoonó྘quigarro÷⇰hreetimes;拋ƀ;qs▋ও◺lanôবʀ;cdgsব☊☍☝☨c;檨otĀ;o☔☕橿Ā;r☚☛檁;檃Ā;e☢☥쀀⋚︀s;檓ʀadegs☳☹☽♉♋pproøⓆot;拖qĀgq♃♅ôউgtò⒌ôছiíলƀilr♕࣡♚sht;楼;쀀𝔩Ā;Eজ♣;檑š♩♶rĀdu▲♮Ā;l॥♳;楪lk;斄cy;䑙ʀ;achtੈ⚈⚋⚑⚖rò◁orneòᴈard;楫ri;旺Āio⚟⚤dot;䅀ustĀ;a⚬⚭掰che»⚭ȀEaes⚻⚽⛉⛔;扨pĀ;p⛃⛄檉rox»⛄Ā;q⛎⛏檇Ā;q⛎⚻im;拦Ѐabnoptwz⛩⛴⛷✚✯❁❇❐Ānr⛮⛱g;柬r;懽rëࣁgƀlmr⛿✍✔eftĀar০✇ightá৲apsto;柼ightá৽parrowĀlr✥✩efô⓭ight;憬ƀafl✶✹✽r;榅;쀀𝕝us;樭imes;樴š❋❏st;戗áፎƀ;ef❗❘᠀旊nge»❘arĀ;l❤❥䀨t;榓ʀachmt❳❶❼➅➇ròࢨorneòᶌarĀ;d྘➃;業;怎ri;抿̀achiqt➘➝ੀ➢➮➻quo;怹r;쀀𝓁mƀ;egল➪➬;檍;檏Ābu┪➳oĀ;rฟ➹;怚rok;䅂萀<;cdhilqrࠫ⟒☹⟜⟠⟥⟪⟰Āci⟗⟙;檦r;橹reå◲mes;拉arr;楶uest;橻ĀPi⟵⟹ar;榖ƀ;ef⠀भ᠛旃rĀdu⠇⠍shar;楊har;楦Āen⠗⠡rtneqq;쀀≨︀Å⠞܀Dacdefhilnopsu⡀⡅⢂⢎⢓⢠⢥⢨⣚⣢⣤ઃ⣳⤂Dot;戺Ȁclpr⡎⡒⡣⡽r耻¯䂯Āet⡗⡙;時Ā;e⡞⡟朠se»⡟Ā;sျ⡨toȀ;dluျ⡳⡷⡻owîҌefôएðᏑker;斮Āoy⢇⢌mma;権;䐼ash;怔asuredangle»ᘦr;쀀𝔪o;愧ƀcdn⢯⢴⣉ro耻µ䂵Ȁ;acdᑤ⢽⣀⣄sôᚧir;櫰ot肻·Ƶusƀ;bd⣒ᤃ⣓戒Ā;uᴼ⣘;横ţ⣞⣡p;櫛ò−ðઁĀdp⣩⣮els;抧f;쀀𝕞Āct⣸⣽r;쀀𝓂pos»ᖝƀ;lm⤉⤊⤍䎼timap;抸ఀGLRVabcdefghijlmoprstuvw⥂⥓⥾⦉⦘⧚⧩⨕⨚⩘⩝⪃⪕⪤⪨⬄⬇⭄⭿⮮ⰴⱧⱼ⳩Āgt⥇⥋;쀀⋙̸Ā;v⥐௏쀀≫⃒ƀelt⥚⥲⥶ftĀar⥡⥧rrow;懍ightarrow;懎;쀀⋘̸Ā;v⥻ే쀀≪⃒ightarrow;懏ĀDd⦎⦓ash;抯ash;抮ʀbcnpt⦣⦧⦬⦱⧌la»˞ute;䅄g;쀀∠⃒ʀ;Eiop඄⦼⧀⧅⧈;쀀⩰̸d;쀀≋̸s;䅉roø඄urĀ;a⧓⧔普lĀ;s⧓ସdz⧟\0⧣p肻 ଷmpĀ;e௹ఀʀaeouy⧴⧾⨃⨐⨓ǰ⧹\0⧻;橃on;䅈dil;䅆ngĀ;dൾ⨊ot;쀀⩭̸p;橂;䐽ash;怓΀;Aadqsxஒ⨩⨭⨻⩁⩅⩐rr;懗rĀhr⨳⨶k;椤Ā;oᏲᏰot;쀀≐̸uiöୣĀei⩊⩎ar;椨í஘istĀ;s஠டr;쀀𝔫ȀEest௅⩦⩹⩼ƀ;qs஼⩭௡ƀ;qs஼௅⩴lanô௢ií௪Ā;rஶ⪁»ஷƀAap⪊⪍⪑rò⥱rr;憮ar;櫲ƀ;svྍ⪜ྌĀ;d⪡⪢拼;拺cy;䑚΀AEadest⪷⪺⪾⫂⫅⫶⫹rò⥦;쀀≦̸rr;憚r;急Ȁ;fqs఻⫎⫣⫯tĀar⫔⫙rro÷⫁ightarro÷⪐ƀ;qs఻⪺⫪lanôౕĀ;sౕ⫴»శiíౝĀ;rవ⫾iĀ;eచథiäඐĀpt⬌⬑f;쀀𝕟膀¬;in⬙⬚⬶䂬nȀ;Edvஉ⬤⬨⬮;쀀⋹̸ot;쀀⋵̸ǡஉ⬳⬵;拷;拶iĀ;vಸ⬼ǡಸ⭁⭃;拾;拽ƀaor⭋⭣⭩rȀ;ast୻⭕⭚⭟lleì୻l;쀀⫽⃥;쀀∂̸lint;樔ƀ;ceಒ⭰⭳uåಥĀ;cಘ⭸Ā;eಒ⭽ñಘȀAait⮈⮋⮝⮧rò⦈rrƀ;cw⮔⮕⮙憛;쀀⤳̸;쀀↝̸ghtarrow»⮕riĀ;eೋೖ΀chimpqu⮽⯍⯙⬄୸⯤⯯Ȁ;cerല⯆ഷ⯉uå൅;쀀𝓃ortɭ⬅\0\0⯖ará⭖mĀ;e൮⯟Ā;q൴൳suĀbp⯫⯭å೸åഋƀbcp⯶ⰑⰙȀ;Ees⯿ⰀഢⰄ抄;쀀⫅̸etĀ;eഛⰋqĀ;qണⰀcĀ;eലⰗñസȀ;EesⰢⰣൟⰧ抅;쀀⫆̸etĀ;e൘ⰮqĀ;qൠⰣȀgilrⰽⰿⱅⱇìௗlde耻ñ䃱çృiangleĀlrⱒⱜeftĀ;eచⱚñదightĀ;eೋⱥñ೗Ā;mⱬⱭ䎽ƀ;esⱴⱵⱹ䀣ro;愖p;怇ҀDHadgilrsⲏⲔⲙⲞⲣⲰⲶⳓⳣash;抭arr;椄p;쀀≍⃒ash;抬ĀetⲨⲬ;쀀≥⃒;쀀>⃒nfin;槞ƀAetⲽⳁⳅrr;椂;쀀≤⃒Ā;rⳊⳍ쀀<⃒ie;쀀⊴⃒ĀAtⳘⳜrr;椃rie;쀀⊵⃒im;쀀∼⃒ƀAan⳰⳴ⴂrr;懖rĀhr⳺⳽k;椣Ā;oᏧᏥear;椧ቓ᪕\0\0\0\0\0\0\0\0\0\0\0\0\0ⴭ\0ⴸⵈⵠⵥ⵲ⶄᬇ\0\0ⶍⶫ\0ⷈⷎ\0ⷜ⸙⸫⸾⹃Ācsⴱ᪗ute耻ó䃳ĀiyⴼⵅrĀ;c᪞ⵂ耻ô䃴;䐾ʀabios᪠ⵒⵗLjⵚlac;䅑v;樸old;榼lig;䅓Ācr⵩⵭ir;榿;쀀𝔬ͯ⵹\0\0⵼\0ⶂn;䋛ave耻ò䃲;槁Ābmⶈ෴ar;榵Ȁacitⶕ⶘ⶥⶨrò᪀Āir⶝ⶠr;榾oss;榻nå๒;槀ƀaeiⶱⶵⶹcr;䅍ga;䏉ƀcdnⷀⷅǍron;䎿;榶pf;쀀𝕠ƀaelⷔ⷗ǒr;榷rp;榹΀;adiosvⷪⷫⷮ⸈⸍⸐⸖戨rò᪆Ȁ;efmⷷⷸ⸂⸅橝rĀ;oⷾⷿ愴f»ⷿ耻ª䂪耻º䂺gof;抶r;橖lope;橗;橛ƀclo⸟⸡⸧ò⸁ash耻ø䃸l;折iŬⸯ⸴de耻õ䃵esĀ;aǛ⸺s;樶ml耻ö䃶bar;挽ૡ⹞\0⹽\0⺀⺝\0⺢⺹\0\0⻋ຜ\0⼓\0\0⼫⾼\0⿈rȀ;astЃ⹧⹲຅脀¶;l⹭⹮䂶leìЃɩ⹸\0\0⹻m;櫳;櫽y;䐿rʀcimpt⺋⺏⺓ᡥ⺗nt;䀥od;䀮il;怰enk;怱r;쀀𝔭ƀimo⺨⺰⺴Ā;v⺭⺮䏆;䏕maô੶ne;明ƀ;tv⺿⻀⻈䏀chfork»´;䏖Āau⻏⻟nĀck⻕⻝kĀ;h⇴⻛;愎ö⇴sҀ;abcdemst⻳⻴ᤈ⻹⻽⼄⼆⼊⼎䀫cir;樣ir;樢Āouᵀ⼂;樥;橲n肻±ຝim;樦wo;樧ƀipu⼙⼠⼥ntint;樕f;쀀𝕡nd耻£䂣Ԁ;Eaceinosu່⼿⽁⽄⽇⾁⾉⾒⽾⾶;檳p;檷uå໙Ā;c໎⽌̀;acens່⽙⽟⽦⽨⽾pproø⽃urlyeñ໙ñ໎ƀaes⽯⽶⽺pprox;檹qq;檵im;拨iíໟmeĀ;s⾈ຮ怲ƀEas⽸⾐⽺ð⽵ƀdfp໬⾙⾯ƀals⾠⾥⾪lar;挮ine;挒urf;挓Ā;t໻⾴ï໻rel;抰Āci⿀⿅r;쀀𝓅;䏈ncsp;怈̀fiopsu⿚⋢⿟⿥⿫⿱r;쀀𝔮pf;쀀𝕢rime;恗cr;쀀𝓆ƀaeo⿸〉〓tĀei⿾々rnionóڰnt;樖stĀ;e【】䀿ñἙô༔઀ABHabcdefhilmnoprstux぀けさすムㄎㄫㅇㅢㅲㆎ㈆㈕㈤㈩㉘㉮㉲㊐㊰㊷ƀartぇおがròႳòϝail;検aròᱥar;楤΀cdenqrtとふへみわゔヌĀeuねぱ;쀀∽̱te;䅕iãᅮmptyv;榳gȀ;del࿑らるろ;榒;榥å࿑uo耻»䂻rր;abcfhlpstw࿜ガクシスゼゾダッデナp;極Ā;f࿠ゴs;椠;椳s;椞ë≝ð✮l;楅im;楴l;憣;憝Āaiパフil;椚oĀ;nホボ戶aló༞ƀabrョリヮrò៥rk;杳ĀakンヽcĀekヹ・;䁽;䁝Āes㄂㄄;榌lĀduㄊㄌ;榎;榐Ȁaeuyㄗㄜㄧㄩron;䅙Ādiㄡㄥil;䅗ì࿲âヺ;䑀Ȁclqsㄴㄷㄽㅄa;椷dhar;楩uoĀ;rȎȍh;憳ƀacgㅎㅟངlȀ;ipsླྀㅘㅛႜnåႻarôྩt;断ƀilrㅩဣㅮsht;楽;쀀𝔯ĀaoㅷㆆrĀduㅽㅿ»ѻĀ;l႑ㆄ;楬Ā;vㆋㆌ䏁;䏱ƀgns㆕ㇹㇼht̀ahlrstㆤㆰ㇂㇘㇤㇮rrowĀ;t࿜ㆭaéトarpoonĀduㆻㆿowîㅾp»႒eftĀah㇊㇐rrowó࿪arpoonóՑightarrows;應quigarro÷ニhreetimes;拌g;䋚ingdotseñἲƀahm㈍㈐㈓rò࿪aòՑ;怏oustĀ;a㈞㈟掱che»㈟mid;櫮Ȁabpt㈲㈽㉀㉒Ānr㈷㈺g;柭r;懾rëဃƀafl㉇㉊㉎r;榆;쀀𝕣us;樮imes;樵Āap㉝㉧rĀ;g㉣㉤䀩t;榔olint;樒arò㇣Ȁachq㉻㊀Ⴜ㊅quo;怺r;쀀𝓇Ābu・㊊oĀ;rȔȓƀhir㊗㊛㊠reåㇸmes;拊iȀ;efl㊪ၙᠡ㊫方tri;槎luhar;楨;愞ൡ㋕㋛㋟㌬㌸㍱\0㍺㎤\0\0㏬㏰\0㐨㑈㑚㒭㒱㓊㓱\0㘖\0\0㘳cute;䅛quï➺Ԁ;Eaceinpsyᇭ㋳㋵㋿㌂㌋㌏㌟㌦㌩;檴ǰ㋺\0㋼;檸on;䅡uåᇾĀ;dᇳ㌇il;䅟rc;䅝ƀEas㌖㌘㌛;檶p;檺im;择olint;樓iíሄ;䑁otƀ;be㌴ᵇ㌵担;橦΀Aacmstx㍆㍊㍗㍛㍞㍣㍭rr;懘rĀhr㍐㍒ë∨Ā;oਸ਼਴t耻§䂧i;䀻war;椩mĀin㍩ðnuóñt;朶rĀ;o㍶⁕쀀𝔰Ȁacoy㎂㎆㎑㎠rp;景Āhy㎋㎏cy;䑉;䑈rtɭ㎙\0\0㎜iäᑤaraì⹯耻­䂭Āgm㎨㎴maƀ;fv㎱㎲㎲䏃;䏂Ѐ;deglnprካ㏅㏉㏎㏖㏞㏡㏦ot;橪Ā;q኱ኰĀ;E㏓㏔檞;檠Ā;E㏛㏜檝;檟e;扆lus;樤arr;楲aròᄽȀaeit㏸㐈㐏㐗Āls㏽㐄lsetmé㍪hp;樳parsl;槤Ādlᑣ㐔e;挣Ā;e㐜㐝檪Ā;s㐢㐣檬;쀀⪬︀ƀflp㐮㐳㑂tcy;䑌Ā;b㐸㐹䀯Ā;a㐾㐿槄r;挿f;쀀𝕤aĀdr㑍ЂesĀ;u㑔㑕晠it»㑕ƀcsu㑠㑹㒟Āau㑥㑯pĀ;sᆈ㑫;쀀⊓︀pĀ;sᆴ㑵;쀀⊔︀uĀbp㑿㒏ƀ;esᆗᆜ㒆etĀ;eᆗ㒍ñᆝƀ;esᆨᆭ㒖etĀ;eᆨ㒝ñᆮƀ;afᅻ㒦ְrť㒫ֱ»ᅼaròᅈȀcemt㒹㒾㓂㓅r;쀀𝓈tmîñiì㐕aræᆾĀar㓎㓕rĀ;f㓔ឿ昆Āan㓚㓭ightĀep㓣㓪psiloîỠhé⺯s»⡒ʀbcmnp㓻㕞ሉ㖋㖎Ҁ;Edemnprs㔎㔏㔑㔕㔞㔣㔬㔱㔶抂;櫅ot;檽Ā;dᇚ㔚ot;櫃ult;櫁ĀEe㔨㔪;櫋;把lus;檿arr;楹ƀeiu㔽㕒㕕tƀ;en㔎㕅㕋qĀ;qᇚ㔏eqĀ;q㔫㔨m;櫇Ābp㕚㕜;櫕;櫓c̀;acensᇭ㕬㕲㕹㕻㌦pproø㋺urlyeñᇾñᇳƀaes㖂㖈㌛pproø㌚qñ㌗g;晪ڀ123;Edehlmnps㖩㖬㖯ሜ㖲㖴㗀㗉㗕㗚㗟㗨㗭耻¹䂹耻²䂲耻³䂳;櫆Āos㖹㖼t;檾ub;櫘Ā;dሢ㗅ot;櫄sĀou㗏㗒l;柉b;櫗arr;楻ult;櫂ĀEe㗤㗦;櫌;抋lus;櫀ƀeiu㗴㘉㘌tƀ;enሜ㗼㘂qĀ;qሢ㖲eqĀ;q㗧㗤m;櫈Ābp㘑㘓;櫔;櫖ƀAan㘜㘠㘭rr;懙rĀhr㘦㘨ë∮Ā;oਫ਩war;椪lig耻ß䃟௡㙑㙝㙠ዎ㙳㙹\0㙾㛂\0\0\0\0\0㛛㜃\0㜉㝬\0\0\0㞇ɲ㙖\0\0㙛get;挖;䏄rë๟ƀaey㙦㙫㙰ron;䅥dil;䅣;䑂lrec;挕r;쀀𝔱Ȁeiko㚆㚝㚵㚼Dz㚋\0㚑eĀ4fኄኁaƀ;sv㚘㚙㚛䎸ym;䏑Ācn㚢㚲kĀas㚨㚮pproø዁im»ኬsðኞĀas㚺㚮ð዁rn耻þ䃾Ǭ̟㛆⋧es膀×;bd㛏㛐㛘䃗Ā;aᤏ㛕r;樱;樰ƀeps㛡㛣㜀á⩍Ȁ;bcf҆㛬㛰㛴ot;挶ir;櫱Ā;o㛹㛼쀀𝕥rk;櫚á㍢rime;怴ƀaip㜏㜒㝤dåቈ΀adempst㜡㝍㝀㝑㝗㝜㝟ngleʀ;dlqr㜰㜱㜶㝀㝂斵own»ᶻeftĀ;e⠀㜾ñम;扜ightĀ;e㊪㝋ñၚot;旬inus;樺lus;樹b;槍ime;樻ezium;揢ƀcht㝲㝽㞁Āry㝷㝻;쀀𝓉;䑆cy;䑛rok;䅧Āio㞋㞎xô᝷headĀlr㞗㞠eftarro÷ࡏightarrow»ཝऀAHabcdfghlmoprstuw㟐㟓㟗㟤㟰㟼㠎㠜㠣㠴㡑㡝㡫㢩㣌㣒㣪㣶ròϭar;楣Ācr㟜㟢ute耻ú䃺òᅐrǣ㟪\0㟭y;䑞ve;䅭Āiy㟵㟺rc耻û䃻;䑃ƀabh㠃㠆㠋ròᎭlac;䅱aòᏃĀir㠓㠘sht;楾;쀀𝔲rave耻ù䃹š㠧㠱rĀlr㠬㠮»ॗ»ႃlk;斀Āct㠹㡍ɯ㠿\0\0㡊rnĀ;e㡅㡆挜r»㡆op;挏ri;旸Āal㡖㡚cr;䅫肻¨͉Āgp㡢㡦on;䅳f;쀀𝕦̀adhlsuᅋ㡸㡽፲㢑㢠ownáᎳarpoonĀlr㢈㢌efô㠭ighô㠯iƀ;hl㢙㢚㢜䏅»ᏺon»㢚parrows;懈ƀcit㢰㣄㣈ɯ㢶\0\0㣁rnĀ;e㢼㢽挝r»㢽op;挎ng;䅯ri;旹cr;쀀𝓊ƀdir㣙㣝㣢ot;拰lde;䅩iĀ;f㜰㣨»᠓Āam㣯㣲rò㢨l耻ü䃼angle;榧ހABDacdeflnoprsz㤜㤟㤩㤭㦵㦸㦽㧟㧤㧨㧳㧹㧽㨁㨠ròϷarĀ;v㤦㤧櫨;櫩asèϡĀnr㤲㤷grt;榜΀eknprst㓣㥆㥋㥒㥝㥤㦖appá␕othinçẖƀhir㓫⻈㥙opô⾵Ā;hᎷ㥢ïㆍĀiu㥩㥭gmá㎳Ābp㥲㦄setneqĀ;q㥽㦀쀀⊊︀;쀀⫋︀setneqĀ;q㦏㦒쀀⊋︀;쀀⫌︀Āhr㦛㦟etá㚜iangleĀlr㦪㦯eft»थight»ၑy;䐲ash»ံƀelr㧄㧒㧗ƀ;beⷪ㧋㧏ar;抻q;扚lip;拮Ābt㧜ᑨaòᑩr;쀀𝔳tré㦮suĀbp㧯㧱»ജ»൙pf;쀀𝕧roð໻tré㦴Ācu㨆㨋r;쀀𝓋Ābp㨐㨘nĀEe㦀㨖»㥾nĀEe㦒㨞»㦐igzag;榚΀cefoprs㨶㨻㩖㩛㩔㩡㩪irc;䅵Ādi㩀㩑Ābg㩅㩉ar;機eĀ;qᗺ㩏;扙erp;愘r;쀀𝔴pf;쀀𝕨Ā;eᑹ㩦atèᑹcr;쀀𝓌ૣណ㪇\0㪋\0㪐㪛\0\0㪝㪨㪫㪯\0\0㫃㫎\0㫘ៜ៟tré៑r;쀀𝔵ĀAa㪔㪗ròσrò৶;䎾ĀAa㪡㪤ròθrò৫að✓is;拻ƀdptឤ㪵㪾Āfl㪺ឩ;쀀𝕩imåឲĀAa㫇㫊ròώròਁĀcq㫒ីr;쀀𝓍Āpt៖㫜ré។Ѐacefiosu㫰㫽㬈㬌㬑㬕㬛㬡cĀuy㫶㫻te耻ý䃽;䑏Āiy㬂㬆rc;䅷;䑋n耻¥䂥r;쀀𝔶cy;䑗pf;쀀𝕪cr;쀀𝓎Ācm㬦㬩y;䑎l耻ÿ䃿Ԁacdefhiosw㭂㭈㭔㭘㭤㭩㭭㭴㭺㮀cute;䅺Āay㭍㭒ron;䅾;䐷ot;䅼Āet㭝㭡træᕟa;䎶r;쀀𝔷cy;䐶grarr;懝pf;쀀𝕫cr;쀀𝓏Ājn㮅㮇;怍j;怌'.split("").map((function(e){return e.charCodeAt(0)})))}));qe(Ao);var go=Ke((function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.default=new Uint16Array("Ȁaglq\tɭ\0\0p;䀦os;䀧t;䀾t;䀼uot;䀢".split("").map((function(e){return e.charCodeAt(0)})))}));qe(go);var No=Ke((function(e,t){var n;Object.defineProperty(t,"__esModule",{value:!0}),t.replaceCodePoint=t.fromCodePoint=void 0;var r=new Map([[0,65533],[128,8364],[130,8218],[131,402],[132,8222],[133,8230],[134,8224],[135,8225],[136,710],[137,8240],[138,352],[139,8249],[140,338],[142,381],[145,8216],[146,8217],[147,8220],[148,8221],[149,8226],[150,8211],[151,8212],[152,732],[153,8482],[154,353],[155,8250],[156,339],[158,382],[159,376]]);function i(e){var t;return e>=55296&&e<=57343||e>1114111?65533:null!==(t=r.get(e))&&void 0!==t?t:e}t.fromCodePoint=null!==(n=String.fromCodePoint)&&void 0!==n?n:function(e){var t="";return e>65535&&(e-=65536,t+=String.fromCharCode(e>>>10&1023|55296),e=56320|1023&e),t+=String.fromCharCode(e)},t.replaceCodePoint=i,t.default=function(e){return(0,t.fromCodePoint)(i(e))}}));qe(No),No.replaceCodePoint,No.fromCodePoint;var Co=Ao,Io=go,So=No,bo=Ke((function(e,t){var n=Ge&&Ge.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.decodeXML=t.decodeHTMLStrict=t.decodeHTML=t.determineBranch=t.BinTrieFlags=t.fromCodePoint=t.replaceCodePoint=t.decodeCodePoint=t.xmlDecodeTree=t.htmlDecodeTree=void 0;var r=n(Co);t.htmlDecodeTree=r.default;var i=n(Io);t.xmlDecodeTree=i.default;var s=n(So);t.decodeCodePoint=s.default;var a,o,c=So;function l(e){return function(t,n){for(var r="",i=0,c=0;(c=t.indexOf("&",c))>=0;)if(r+=t.slice(i,c),i=c,c+=1,t.charCodeAt(c)!==a.NUM){for(var l=0,u=1,p=0,f=e[p];c>14)-1))break;p+=E}}if(0!==l)r+=1===(E=(e[l]&o.VALUE_LENGTH)>>14)?String.fromCharCode(e[l]&~o.VALUE_LENGTH):2===E?String.fromCharCode(e[l+1]):String.fromCharCode(e[l+1],e[l+2]),i=c-u+1}else{var m=c+1,T=10,_=t.charCodeAt(m);(_|a.To_LOWER_BIT)===a.LOWER_X&&(T=16,c+=1,m+=1);do{_=t.charCodeAt(++c)}while(_>=a.ZERO&&_<=a.NINE||16===T&&(_|a.To_LOWER_BIT)>=a.LOWER_A&&(_|a.To_LOWER_BIT)<=a.LOWER_F);if(m!==c){var A=t.substring(m,c),g=parseInt(A,T);if(t.charCodeAt(c)===a.SEMI)c+=1;else if(n)continue;r+=(0,s.default)(g),i=c}}return r+t.slice(i)}}function h(e,t,n,r){var i=(t&o.BRANCH_LENGTH)>>7,s=t&o.JUMP_TABLE;if(0===i)return 0!==s&&r===s?n:-1;if(s){var a=r-s;return a<0||a>=i?-1:e[n+a]-1}for(var c=n,l=c+i-1;c<=l;){var h=c+l>>>1,u=e[h];if(ur))return e[h+i];l=h-1}}return-1}Object.defineProperty(t,"replaceCodePoint",{enumerable:!0,get:function(){return c.replaceCodePoint}}),Object.defineProperty(t,"fromCodePoint",{enumerable:!0,get:function(){return c.fromCodePoint}}),function(e){e[e.NUM=35]="NUM",e[e.SEMI=59]="SEMI",e[e.ZERO=48]="ZERO",e[e.NINE=57]="NINE",e[e.LOWER_A=97]="LOWER_A",e[e.LOWER_F=102]="LOWER_F",e[e.LOWER_X=120]="LOWER_X",e[e.To_LOWER_BIT=32]="To_LOWER_BIT"}(a||(a={})),function(e){e[e.VALUE_LENGTH=49152]="VALUE_LENGTH",e[e.BRANCH_LENGTH=16256]="BRANCH_LENGTH",e[e.JUMP_TABLE=127]="JUMP_TABLE"}(o=t.BinTrieFlags||(t.BinTrieFlags={})),t.determineBranch=h;var u=l(r.default),p=l(i.default);t.decodeHTML=function(e){return u(e,!1)},t.decodeHTMLStrict=function(e){return u(e,!0)},t.decodeXML=function(e){return p(e,!0)}}));qe(bo),bo.decodeXML,bo.decodeHTMLStrict,bo.decodeHTML;var Oo=bo.determineBranch,yo=bo.BinTrieFlags,Lo=bo.fromCodePoint,ko=bo.replaceCodePoint;bo.decodeCodePoint;var vo,Do,Ro,Mo,Po,xo=bo.xmlDecodeTree,wo=bo.htmlDecodeTree;!function(e){e.HTML="http://www.w3.org/1999/xhtml",e.MATHML="http://www.w3.org/1998/Math/MathML",e.SVG="http://www.w3.org/2000/svg",e.XLINK="http://www.w3.org/1999/xlink",e.XML="http://www.w3.org/XML/1998/namespace",e.XMLNS="http://www.w3.org/2000/xmlns/"}(vo=vo||(vo={})),function(e){e.TYPE="type",e.ACTION="action",e.ENCODING="encoding",e.PROMPT="prompt",e.NAME="name",e.COLOR="color",e.FACE="face",e.SIZE="size"}(Do=Do||(Do={})),function(e){e.NO_QUIRKS="no-quirks",e.QUIRKS="quirks",e.LIMITED_QUIRKS="limited-quirks"}(Ro=Ro||(Ro={})),function(e){e.A="a",e.ADDRESS="address",e.ANNOTATION_XML="annotation-xml",e.APPLET="applet",e.AREA="area",e.ARTICLE="article",e.ASIDE="aside",e.B="b",e.BASE="base",e.BASEFONT="basefont",e.BGSOUND="bgsound",e.BIG="big",e.BLOCKQUOTE="blockquote",e.BODY="body",e.BR="br",e.BUTTON="button",e.CAPTION="caption",e.CENTER="center",e.CODE="code",e.COL="col",e.COLGROUP="colgroup",e.DD="dd",e.DESC="desc",e.DETAILS="details",e.DIALOG="dialog",e.DIR="dir",e.DIV="div",e.DL="dl",e.DT="dt",e.EM="em",e.EMBED="embed",e.FIELDSET="fieldset",e.FIGCAPTION="figcaption",e.FIGURE="figure",e.FONT="font",e.FOOTER="footer",e.FOREIGN_OBJECT="foreignObject",e.FORM="form",e.FRAME="frame",e.FRAMESET="frameset",e.H1="h1",e.H2="h2",e.H3="h3",e.H4="h4",e.H5="h5",e.H6="h6",e.HEAD="head",e.HEADER="header",e.HGROUP="hgroup",e.HR="hr",e.HTML="html",e.I="i",e.IMG="img",e.IMAGE="image",e.INPUT="input",e.IFRAME="iframe",e.KEYGEN="keygen",e.LABEL="label",e.LI="li",e.LINK="link",e.LISTING="listing",e.MAIN="main",e.MALIGNMARK="malignmark",e.MARQUEE="marquee",e.MATH="math",e.MENU="menu",e.META="meta",e.MGLYPH="mglyph",e.MI="mi",e.MO="mo",e.MN="mn",e.MS="ms",e.MTEXT="mtext",e.NAV="nav",e.NOBR="nobr",e.NOFRAMES="noframes",e.NOEMBED="noembed",e.NOSCRIPT="noscript",e.OBJECT="object",e.OL="ol",e.OPTGROUP="optgroup",e.OPTION="option",e.P="p",e.PARAM="param",e.PLAINTEXT="plaintext",e.PRE="pre",e.RB="rb",e.RP="rp",e.RT="rt",e.RTC="rtc",e.RUBY="ruby",e.S="s",e.SCRIPT="script",e.SECTION="section",e.SELECT="select",e.SOURCE="source",e.SMALL="small",e.SPAN="span",e.STRIKE="strike",e.STRONG="strong",e.STYLE="style",e.SUB="sub",e.SUMMARY="summary",e.SUP="sup",e.TABLE="table",e.TBODY="tbody",e.TEMPLATE="template",e.TEXTAREA="textarea",e.TFOOT="tfoot",e.TD="td",e.TH="th",e.THEAD="thead",e.TITLE="title",e.TR="tr",e.TRACK="track",e.TT="tt",e.U="u",e.UL="ul",e.SVG="svg",e.VAR="var",e.WBR="wbr",e.XMP="xmp"}(Mo=Mo||(Mo={})),function(e){e[e.UNKNOWN=0]="UNKNOWN",e[e.A=1]="A",e[e.ADDRESS=2]="ADDRESS",e[e.ANNOTATION_XML=3]="ANNOTATION_XML",e[e.APPLET=4]="APPLET",e[e.AREA=5]="AREA",e[e.ARTICLE=6]="ARTICLE",e[e.ASIDE=7]="ASIDE",e[e.B=8]="B",e[e.BASE=9]="BASE",e[e.BASEFONT=10]="BASEFONT",e[e.BGSOUND=11]="BGSOUND",e[e.BIG=12]="BIG",e[e.BLOCKQUOTE=13]="BLOCKQUOTE",e[e.BODY=14]="BODY",e[e.BR=15]="BR",e[e.BUTTON=16]="BUTTON",e[e.CAPTION=17]="CAPTION",e[e.CENTER=18]="CENTER",e[e.CODE=19]="CODE",e[e.COL=20]="COL",e[e.COLGROUP=21]="COLGROUP",e[e.DD=22]="DD",e[e.DESC=23]="DESC",e[e.DETAILS=24]="DETAILS",e[e.DIALOG=25]="DIALOG",e[e.DIR=26]="DIR",e[e.DIV=27]="DIV",e[e.DL=28]="DL",e[e.DT=29]="DT",e[e.EM=30]="EM",e[e.EMBED=31]="EMBED",e[e.FIELDSET=32]="FIELDSET",e[e.FIGCAPTION=33]="FIGCAPTION",e[e.FIGURE=34]="FIGURE",e[e.FONT=35]="FONT",e[e.FOOTER=36]="FOOTER",e[e.FOREIGN_OBJECT=37]="FOREIGN_OBJECT",e[e.FORM=38]="FORM",e[e.FRAME=39]="FRAME",e[e.FRAMESET=40]="FRAMESET",e[e.H1=41]="H1",e[e.H2=42]="H2",e[e.H3=43]="H3",e[e.H4=44]="H4",e[e.H5=45]="H5",e[e.H6=46]="H6",e[e.HEAD=47]="HEAD",e[e.HEADER=48]="HEADER",e[e.HGROUP=49]="HGROUP",e[e.HR=50]="HR",e[e.HTML=51]="HTML",e[e.I=52]="I",e[e.IMG=53]="IMG",e[e.IMAGE=54]="IMAGE",e[e.INPUT=55]="INPUT",e[e.IFRAME=56]="IFRAME",e[e.KEYGEN=57]="KEYGEN",e[e.LABEL=58]="LABEL",e[e.LI=59]="LI",e[e.LINK=60]="LINK",e[e.LISTING=61]="LISTING",e[e.MAIN=62]="MAIN",e[e.MALIGNMARK=63]="MALIGNMARK",e[e.MARQUEE=64]="MARQUEE",e[e.MATH=65]="MATH",e[e.MENU=66]="MENU",e[e.META=67]="META",e[e.MGLYPH=68]="MGLYPH",e[e.MI=69]="MI",e[e.MO=70]="MO",e[e.MN=71]="MN",e[e.MS=72]="MS",e[e.MTEXT=73]="MTEXT",e[e.NAV=74]="NAV",e[e.NOBR=75]="NOBR",e[e.NOFRAMES=76]="NOFRAMES",e[e.NOEMBED=77]="NOEMBED",e[e.NOSCRIPT=78]="NOSCRIPT",e[e.OBJECT=79]="OBJECT",e[e.OL=80]="OL",e[e.OPTGROUP=81]="OPTGROUP",e[e.OPTION=82]="OPTION",e[e.P=83]="P",e[e.PARAM=84]="PARAM",e[e.PLAINTEXT=85]="PLAINTEXT",e[e.PRE=86]="PRE",e[e.RB=87]="RB",e[e.RP=88]="RP",e[e.RT=89]="RT",e[e.RTC=90]="RTC",e[e.RUBY=91]="RUBY",e[e.S=92]="S",e[e.SCRIPT=93]="SCRIPT",e[e.SECTION=94]="SECTION",e[e.SELECT=95]="SELECT",e[e.SOURCE=96]="SOURCE",e[e.SMALL=97]="SMALL",e[e.SPAN=98]="SPAN",e[e.STRIKE=99]="STRIKE",e[e.STRONG=100]="STRONG",e[e.STYLE=101]="STYLE",e[e.SUB=102]="SUB",e[e.SUMMARY=103]="SUMMARY",e[e.SUP=104]="SUP",e[e.TABLE=105]="TABLE",e[e.TBODY=106]="TBODY",e[e.TEMPLATE=107]="TEMPLATE",e[e.TEXTAREA=108]="TEXTAREA",e[e.TFOOT=109]="TFOOT",e[e.TD=110]="TD",e[e.TH=111]="TH",e[e.THEAD=112]="THEAD",e[e.TITLE=113]="TITLE",e[e.TR=114]="TR",e[e.TRACK=115]="TRACK",e[e.TT=116]="TT",e[e.U=117]="U",e[e.UL=118]="UL",e[e.SVG=119]="SVG",e[e.VAR=120]="VAR",e[e.WBR=121]="WBR",e[e.XMP=122]="XMP"}(Po=Po||(Po={}));const Bo=new Map([[Mo.A,Po.A],[Mo.ADDRESS,Po.ADDRESS],[Mo.ANNOTATION_XML,Po.ANNOTATION_XML],[Mo.APPLET,Po.APPLET],[Mo.AREA,Po.AREA],[Mo.ARTICLE,Po.ARTICLE],[Mo.ASIDE,Po.ASIDE],[Mo.B,Po.B],[Mo.BASE,Po.BASE],[Mo.BASEFONT,Po.BASEFONT],[Mo.BGSOUND,Po.BGSOUND],[Mo.BIG,Po.BIG],[Mo.BLOCKQUOTE,Po.BLOCKQUOTE],[Mo.BODY,Po.BODY],[Mo.BR,Po.BR],[Mo.BUTTON,Po.BUTTON],[Mo.CAPTION,Po.CAPTION],[Mo.CENTER,Po.CENTER],[Mo.CODE,Po.CODE],[Mo.COL,Po.COL],[Mo.COLGROUP,Po.COLGROUP],[Mo.DD,Po.DD],[Mo.DESC,Po.DESC],[Mo.DETAILS,Po.DETAILS],[Mo.DIALOG,Po.DIALOG],[Mo.DIR,Po.DIR],[Mo.DIV,Po.DIV],[Mo.DL,Po.DL],[Mo.DT,Po.DT],[Mo.EM,Po.EM],[Mo.EMBED,Po.EMBED],[Mo.FIELDSET,Po.FIELDSET],[Mo.FIGCAPTION,Po.FIGCAPTION],[Mo.FIGURE,Po.FIGURE],[Mo.FONT,Po.FONT],[Mo.FOOTER,Po.FOOTER],[Mo.FOREIGN_OBJECT,Po.FOREIGN_OBJECT],[Mo.FORM,Po.FORM],[Mo.FRAME,Po.FRAME],[Mo.FRAMESET,Po.FRAMESET],[Mo.H1,Po.H1],[Mo.H2,Po.H2],[Mo.H3,Po.H3],[Mo.H4,Po.H4],[Mo.H5,Po.H5],[Mo.H6,Po.H6],[Mo.HEAD,Po.HEAD],[Mo.HEADER,Po.HEADER],[Mo.HGROUP,Po.HGROUP],[Mo.HR,Po.HR],[Mo.HTML,Po.HTML],[Mo.I,Po.I],[Mo.IMG,Po.IMG],[Mo.IMAGE,Po.IMAGE],[Mo.INPUT,Po.INPUT],[Mo.IFRAME,Po.IFRAME],[Mo.KEYGEN,Po.KEYGEN],[Mo.LABEL,Po.LABEL],[Mo.LI,Po.LI],[Mo.LINK,Po.LINK],[Mo.LISTING,Po.LISTING],[Mo.MAIN,Po.MAIN],[Mo.MALIGNMARK,Po.MALIGNMARK],[Mo.MARQUEE,Po.MARQUEE],[Mo.MATH,Po.MATH],[Mo.MENU,Po.MENU],[Mo.META,Po.META],[Mo.MGLYPH,Po.MGLYPH],[Mo.MI,Po.MI],[Mo.MO,Po.MO],[Mo.MN,Po.MN],[Mo.MS,Po.MS],[Mo.MTEXT,Po.MTEXT],[Mo.NAV,Po.NAV],[Mo.NOBR,Po.NOBR],[Mo.NOFRAMES,Po.NOFRAMES],[Mo.NOEMBED,Po.NOEMBED],[Mo.NOSCRIPT,Po.NOSCRIPT],[Mo.OBJECT,Po.OBJECT],[Mo.OL,Po.OL],[Mo.OPTGROUP,Po.OPTGROUP],[Mo.OPTION,Po.OPTION],[Mo.P,Po.P],[Mo.PARAM,Po.PARAM],[Mo.PLAINTEXT,Po.PLAINTEXT],[Mo.PRE,Po.PRE],[Mo.RB,Po.RB],[Mo.RP,Po.RP],[Mo.RT,Po.RT],[Mo.RTC,Po.RTC],[Mo.RUBY,Po.RUBY],[Mo.S,Po.S],[Mo.SCRIPT,Po.SCRIPT],[Mo.SECTION,Po.SECTION],[Mo.SELECT,Po.SELECT],[Mo.SOURCE,Po.SOURCE],[Mo.SMALL,Po.SMALL],[Mo.SPAN,Po.SPAN],[Mo.STRIKE,Po.STRIKE],[Mo.STRONG,Po.STRONG],[Mo.STYLE,Po.STYLE],[Mo.SUB,Po.SUB],[Mo.SUMMARY,Po.SUMMARY],[Mo.SUP,Po.SUP],[Mo.TABLE,Po.TABLE],[Mo.TBODY,Po.TBODY],[Mo.TEMPLATE,Po.TEMPLATE],[Mo.TEXTAREA,Po.TEXTAREA],[Mo.TFOOT,Po.TFOOT],[Mo.TD,Po.TD],[Mo.TH,Po.TH],[Mo.THEAD,Po.THEAD],[Mo.TITLE,Po.TITLE],[Mo.TR,Po.TR],[Mo.TRACK,Po.TRACK],[Mo.TT,Po.TT],[Mo.U,Po.U],[Mo.UL,Po.UL],[Mo.SVG,Po.SVG],[Mo.VAR,Po.VAR],[Mo.WBR,Po.WBR],[Mo.XMP,Po.XMP]]);function Fo(e){var t;return null!==(t=Bo.get(e))&&void 0!==t?t:Po.UNKNOWN}const Uo=Po,Ho={[vo.HTML]:new Set([Uo.ADDRESS,Uo.APPLET,Uo.AREA,Uo.ARTICLE,Uo.ASIDE,Uo.BASE,Uo.BASEFONT,Uo.BGSOUND,Uo.BLOCKQUOTE,Uo.BODY,Uo.BR,Uo.BUTTON,Uo.CAPTION,Uo.CENTER,Uo.COL,Uo.COLGROUP,Uo.DD,Uo.DETAILS,Uo.DIR,Uo.DIV,Uo.DL,Uo.DT,Uo.EMBED,Uo.FIELDSET,Uo.FIGCAPTION,Uo.FIGURE,Uo.FOOTER,Uo.FORM,Uo.FRAME,Uo.FRAMESET,Uo.H1,Uo.H2,Uo.H3,Uo.H4,Uo.H5,Uo.H6,Uo.HEAD,Uo.HEADER,Uo.HGROUP,Uo.HR,Uo.HTML,Uo.IFRAME,Uo.IMG,Uo.INPUT,Uo.LI,Uo.LINK,Uo.LISTING,Uo.MAIN,Uo.MARQUEE,Uo.MENU,Uo.META,Uo.NAV,Uo.NOEMBED,Uo.NOFRAMES,Uo.NOSCRIPT,Uo.OBJECT,Uo.OL,Uo.P,Uo.PARAM,Uo.PLAINTEXT,Uo.PRE,Uo.SCRIPT,Uo.SECTION,Uo.SELECT,Uo.SOURCE,Uo.STYLE,Uo.SUMMARY,Uo.TABLE,Uo.TBODY,Uo.TD,Uo.TEMPLATE,Uo.TEXTAREA,Uo.TFOOT,Uo.TH,Uo.THEAD,Uo.TITLE,Uo.TR,Uo.TRACK,Uo.UL,Uo.WBR,Uo.XMP]),[vo.MATHML]:new Set([Uo.MI,Uo.MO,Uo.MN,Uo.MS,Uo.MTEXT,Uo.ANNOTATION_XML]),[vo.SVG]:new Set([Uo.TITLE,Uo.FOREIGN_OBJECT,Uo.DESC]),[vo.XLINK]:new Set,[vo.XML]:new Set,[vo.XMLNS]:new Set};function Go(e){return e===Uo.H1||e===Uo.H2||e===Uo.H3||e===Uo.H4||e===Uo.H5||e===Uo.H6}const Yo=new Set([Mo.STYLE,Mo.SCRIPT,Mo.XMP,Mo.IFRAME,Mo.NOEMBED,Mo.NOFRAMES,Mo.PLAINTEXT]);const qo=new Map([[128,8364],[130,8218],[131,402],[132,8222],[133,8230],[134,8224],[135,8225],[136,710],[137,8240],[138,352],[139,8249],[140,338],[142,381],[145,8216],[146,8217],[147,8220],[148,8221],[149,8226],[150,8211],[151,8212],[152,732],[153,8482],[154,353],[155,8250],[156,339],[158,382],[159,376]]);var Ko;!function(e){e[e.DATA=0]="DATA",e[e.RCDATA=1]="RCDATA",e[e.RAWTEXT=2]="RAWTEXT",e[e.SCRIPT_DATA=3]="SCRIPT_DATA",e[e.PLAINTEXT=4]="PLAINTEXT",e[e.TAG_OPEN=5]="TAG_OPEN",e[e.END_TAG_OPEN=6]="END_TAG_OPEN",e[e.TAG_NAME=7]="TAG_NAME",e[e.RCDATA_LESS_THAN_SIGN=8]="RCDATA_LESS_THAN_SIGN",e[e.RCDATA_END_TAG_OPEN=9]="RCDATA_END_TAG_OPEN",e[e.RCDATA_END_TAG_NAME=10]="RCDATA_END_TAG_NAME",e[e.RAWTEXT_LESS_THAN_SIGN=11]="RAWTEXT_LESS_THAN_SIGN",e[e.RAWTEXT_END_TAG_OPEN=12]="RAWTEXT_END_TAG_OPEN",e[e.RAWTEXT_END_TAG_NAME=13]="RAWTEXT_END_TAG_NAME",e[e.SCRIPT_DATA_LESS_THAN_SIGN=14]="SCRIPT_DATA_LESS_THAN_SIGN",e[e.SCRIPT_DATA_END_TAG_OPEN=15]="SCRIPT_DATA_END_TAG_OPEN",e[e.SCRIPT_DATA_END_TAG_NAME=16]="SCRIPT_DATA_END_TAG_NAME",e[e.SCRIPT_DATA_ESCAPE_START=17]="SCRIPT_DATA_ESCAPE_START",e[e.SCRIPT_DATA_ESCAPE_START_DASH=18]="SCRIPT_DATA_ESCAPE_START_DASH",e[e.SCRIPT_DATA_ESCAPED=19]="SCRIPT_DATA_ESCAPED",e[e.SCRIPT_DATA_ESCAPED_DASH=20]="SCRIPT_DATA_ESCAPED_DASH",e[e.SCRIPT_DATA_ESCAPED_DASH_DASH=21]="SCRIPT_DATA_ESCAPED_DASH_DASH",e[e.SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN=22]="SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN",e[e.SCRIPT_DATA_ESCAPED_END_TAG_OPEN=23]="SCRIPT_DATA_ESCAPED_END_TAG_OPEN",e[e.SCRIPT_DATA_ESCAPED_END_TAG_NAME=24]="SCRIPT_DATA_ESCAPED_END_TAG_NAME",e[e.SCRIPT_DATA_DOUBLE_ESCAPE_START=25]="SCRIPT_DATA_DOUBLE_ESCAPE_START",e[e.SCRIPT_DATA_DOUBLE_ESCAPED=26]="SCRIPT_DATA_DOUBLE_ESCAPED",e[e.SCRIPT_DATA_DOUBLE_ESCAPED_DASH=27]="SCRIPT_DATA_DOUBLE_ESCAPED_DASH",e[e.SCRIPT_DATA_DOUBLE_ESCAPED_DASH_DASH=28]="SCRIPT_DATA_DOUBLE_ESCAPED_DASH_DASH",e[e.SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN=29]="SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN",e[e.SCRIPT_DATA_DOUBLE_ESCAPE_END=30]="SCRIPT_DATA_DOUBLE_ESCAPE_END",e[e.BEFORE_ATTRIBUTE_NAME=31]="BEFORE_ATTRIBUTE_NAME",e[e.ATTRIBUTE_NAME=32]="ATTRIBUTE_NAME",e[e.AFTER_ATTRIBUTE_NAME=33]="AFTER_ATTRIBUTE_NAME",e[e.BEFORE_ATTRIBUTE_VALUE=34]="BEFORE_ATTRIBUTE_VALUE",e[e.ATTRIBUTE_VALUE_DOUBLE_QUOTED=35]="ATTRIBUTE_VALUE_DOUBLE_QUOTED",e[e.ATTRIBUTE_VALUE_SINGLE_QUOTED=36]="ATTRIBUTE_VALUE_SINGLE_QUOTED",e[e.ATTRIBUTE_VALUE_UNQUOTED=37]="ATTRIBUTE_VALUE_UNQUOTED",e[e.AFTER_ATTRIBUTE_VALUE_QUOTED=38]="AFTER_ATTRIBUTE_VALUE_QUOTED",e[e.SELF_CLOSING_START_TAG=39]="SELF_CLOSING_START_TAG",e[e.BOGUS_COMMENT=40]="BOGUS_COMMENT",e[e.MARKUP_DECLARATION_OPEN=41]="MARKUP_DECLARATION_OPEN",e[e.COMMENT_START=42]="COMMENT_START",e[e.COMMENT_START_DASH=43]="COMMENT_START_DASH",e[e.COMMENT=44]="COMMENT",e[e.COMMENT_LESS_THAN_SIGN=45]="COMMENT_LESS_THAN_SIGN",e[e.COMMENT_LESS_THAN_SIGN_BANG=46]="COMMENT_LESS_THAN_SIGN_BANG",e[e.COMMENT_LESS_THAN_SIGN_BANG_DASH=47]="COMMENT_LESS_THAN_SIGN_BANG_DASH",e[e.COMMENT_LESS_THAN_SIGN_BANG_DASH_DASH=48]="COMMENT_LESS_THAN_SIGN_BANG_DASH_DASH",e[e.COMMENT_END_DASH=49]="COMMENT_END_DASH",e[e.COMMENT_END=50]="COMMENT_END",e[e.COMMENT_END_BANG=51]="COMMENT_END_BANG",e[e.DOCTYPE=52]="DOCTYPE",e[e.BEFORE_DOCTYPE_NAME=53]="BEFORE_DOCTYPE_NAME",e[e.DOCTYPE_NAME=54]="DOCTYPE_NAME",e[e.AFTER_DOCTYPE_NAME=55]="AFTER_DOCTYPE_NAME",e[e.AFTER_DOCTYPE_PUBLIC_KEYWORD=56]="AFTER_DOCTYPE_PUBLIC_KEYWORD",e[e.BEFORE_DOCTYPE_PUBLIC_IDENTIFIER=57]="BEFORE_DOCTYPE_PUBLIC_IDENTIFIER",e[e.DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED=58]="DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED",e[e.DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED=59]="DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED",e[e.AFTER_DOCTYPE_PUBLIC_IDENTIFIER=60]="AFTER_DOCTYPE_PUBLIC_IDENTIFIER",e[e.BETWEEN_DOCTYPE_PUBLIC_AND_SYSTEM_IDENTIFIERS=61]="BETWEEN_DOCTYPE_PUBLIC_AND_SYSTEM_IDENTIFIERS",e[e.AFTER_DOCTYPE_SYSTEM_KEYWORD=62]="AFTER_DOCTYPE_SYSTEM_KEYWORD",e[e.BEFORE_DOCTYPE_SYSTEM_IDENTIFIER=63]="BEFORE_DOCTYPE_SYSTEM_IDENTIFIER",e[e.DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED=64]="DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED",e[e.DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED=65]="DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED",e[e.AFTER_DOCTYPE_SYSTEM_IDENTIFIER=66]="AFTER_DOCTYPE_SYSTEM_IDENTIFIER",e[e.BOGUS_DOCTYPE=67]="BOGUS_DOCTYPE",e[e.CDATA_SECTION=68]="CDATA_SECTION",e[e.CDATA_SECTION_BRACKET=69]="CDATA_SECTION_BRACKET",e[e.CDATA_SECTION_END=70]="CDATA_SECTION_END",e[e.CHARACTER_REFERENCE=71]="CHARACTER_REFERENCE",e[e.NAMED_CHARACTER_REFERENCE=72]="NAMED_CHARACTER_REFERENCE",e[e.AMBIGUOUS_AMPERSAND=73]="AMBIGUOUS_AMPERSAND",e[e.NUMERIC_CHARACTER_REFERENCE=74]="NUMERIC_CHARACTER_REFERENCE",e[e.HEXADEMICAL_CHARACTER_REFERENCE_START=75]="HEXADEMICAL_CHARACTER_REFERENCE_START",e[e.HEXADEMICAL_CHARACTER_REFERENCE=76]="HEXADEMICAL_CHARACTER_REFERENCE",e[e.DECIMAL_CHARACTER_REFERENCE=77]="DECIMAL_CHARACTER_REFERENCE",e[e.NUMERIC_CHARACTER_REFERENCE_END=78]="NUMERIC_CHARACTER_REFERENCE_END"}(Ko||(Ko={}));const jo={DATA:Ko.DATA,RCDATA:Ko.RCDATA,RAWTEXT:Ko.RAWTEXT,SCRIPT_DATA:Ko.SCRIPT_DATA,PLAINTEXT:Ko.PLAINTEXT,CDATA_SECTION:Ko.CDATA_SECTION};function Vo(e){return e>=io.DIGIT_0&&e<=io.DIGIT_9}function Wo(e){return e>=io.LATIN_CAPITAL_A&&e<=io.LATIN_CAPITAL_Z}function Qo(e){return function(e){return e>=io.LATIN_SMALL_A&&e<=io.LATIN_SMALL_Z}(e)||Wo(e)}function Xo(e){return Qo(e)||Vo(e)}function $o(e){return e>=io.LATIN_CAPITAL_A&&e<=io.LATIN_CAPITAL_F}function zo(e){return e>=io.LATIN_SMALL_A&&e<=io.LATIN_SMALL_F}function Jo(e){return e+32}function Zo(e){return e===io.SPACE||e===io.LINE_FEED||e===io.TABULATION||e===io.FORM_FEED}function ec(e){return Zo(e)||e===io.SOLIDUS||e===io.GREATER_THAN_SIGN}class tc{constructor(e,t){this.options=e,this.handler=t,this.paused=!1,this.inLoop=!1,this.inForeignNode=!1,this.lastStartTagName="",this.active=!1,this.state=Ko.DATA,this.returnState=Ko.DATA,this.charRefCode=-1,this.consumedAfterSnapshot=-1,this.currentCharacterToken=null,this.currentToken=null,this.currentAttr={name:"",value:""},this.preprocessor=new mo(t),this.currentLocation=this.getCurrentLocation(-1)}_err(e){var t,n;null===(n=(t=this.handler).onParseError)||void 0===n||n.call(t,this.preprocessor.getError(e))}getCurrentLocation(e){return this.options.sourceCodeLocationInfo?{startLine:this.preprocessor.line,startCol:this.preprocessor.col-e,startOffset:this.preprocessor.offset-e,endLine:-1,endCol:-1,endOffset:-1}:null}_runParsingLoop(){if(!this.inLoop){for(this.inLoop=!0;this.active&&!this.paused;){this.consumedAfterSnapshot=0;const e=this._consume();this._ensureHibernation()||this._callState(e)}this.inLoop=!1}}pause(){this.paused=!0}resume(e){if(!this.paused)throw new Error("Parser was already resumed");this.paused=!1,this.inLoop||(this._runParsingLoop(),this.paused||null==e||e())}write(e,t,n){this.active=!0,this.preprocessor.write(e,t),this._runParsingLoop(),this.paused||null==n||n()}insertHtmlAtCurrentPos(e){this.active=!0,this.preprocessor.insertHtmlAtCurrentPos(e),this._runParsingLoop()}_ensureHibernation(){return!!this.preprocessor.endOfChunkHit&&(this._unconsume(this.consumedAfterSnapshot),this.active=!1,!0)}_consume(){return this.consumedAfterSnapshot++,this.preprocessor.advance()}_unconsume(e){this.consumedAfterSnapshot-=e,this.preprocessor.retreat(e)}_reconsumeInState(e,t){this.state=e,this._callState(t)}_advanceBy(e){this.consumedAfterSnapshot+=e;for(let t=0;t0&&this._err(Eo.endTagWithAttributes),e.selfClosing&&this._err(Eo.endTagWithTrailingSolidus),this.handler.onEndTag(e)),this.preprocessor.dropParsedChunk()}emitCurrentComment(e){this.prepareToken(e),this.handler.onComment(e),this.preprocessor.dropParsedChunk()}emitCurrentDoctype(e){this.prepareToken(e),this.handler.onDoctype(e),this.preprocessor.dropParsedChunk()}_emitCurrentCharacterToken(e){if(this.currentCharacterToken){switch(e&&this.currentCharacterToken.location&&(this.currentCharacterToken.location.endLine=e.startLine,this.currentCharacterToken.location.endCol=e.startCol,this.currentCharacterToken.location.endOffset=e.startOffset),this.currentCharacterToken.type){case To.CHARACTER:this.handler.onCharacter(this.currentCharacterToken);break;case To.NULL_CHARACTER:this.handler.onNullCharacter(this.currentCharacterToken);break;case To.WHITESPACE_CHARACTER:this.handler.onWhitespaceCharacter(this.currentCharacterToken)}this.currentCharacterToken=null}}_emitEOFToken(){const e=this.getCurrentLocation(0);e&&(e.endLine=e.startLine,e.endCol=e.startCol,e.endOffset=e.startOffset),this._emitCurrentCharacterToken(e),this.handler.onEof({type:To.EOF,location:e}),this.active=!1}_appendCharToCurrentCharacterToken(e,t){if(this.currentCharacterToken){if(this.currentCharacterToken.type===e)return void(this.currentCharacterToken.chars+=t);this.currentLocation=this.getCurrentLocation(0),this._emitCurrentCharacterToken(this.currentLocation),this.preprocessor.dropParsedChunk()}this._createCharacterToken(e,t)}_emitCodePoint(e){const t=Zo(e)?To.WHITESPACE_CHARACTER:e===io.NULL?To.NULL_CHARACTER:To.CHARACTER;this._appendCharToCurrentCharacterToken(t,String.fromCodePoint(e))}_emitChars(e){this._appendCharToCurrentCharacterToken(To.CHARACTER,e)}_matchNamedCharacterReference(e){let t=null,n=0,r=!1;for(let s=0,a=wo[0];s>=0&&(s=Oo(wo,a,s+1,e),!(s<0));e=this._consume()){n+=1,a=wo[s];const o=a&yo.VALUE_LENGTH;if(o){const a=(o>>14)-1;if(e!==io.SEMICOLON&&this._isCharacterReferenceInAttribute()&&((i=this.preprocessor.peek(1))===io.EQUALS_SIGN||Xo(i))?(t=[io.AMPERSAND],s+=a):(t=0===a?[wo[s]&~yo.VALUE_LENGTH]:1===a?[wo[++s]]:[wo[++s],wo[++s]],n=0,r=e!==io.SEMICOLON),0===a){this._consume();break}}}var i;return this._unconsume(n),r&&!this.preprocessor.endOfChunkHit&&this._err(Eo.missingSemicolonAfterCharacterReference),this._unconsume(1),t}_isCharacterReferenceInAttribute(){return this.returnState===Ko.ATTRIBUTE_VALUE_DOUBLE_QUOTED||this.returnState===Ko.ATTRIBUTE_VALUE_SINGLE_QUOTED||this.returnState===Ko.ATTRIBUTE_VALUE_UNQUOTED}_flushCodePointConsumedAsCharacterReference(e){this._isCharacterReferenceInAttribute()?this.currentAttr.value+=String.fromCodePoint(e):this._emitCodePoint(e)}_callState(e){switch(this.state){case Ko.DATA:this._stateData(e);break;case Ko.RCDATA:this._stateRcdata(e);break;case Ko.RAWTEXT:this._stateRawtext(e);break;case Ko.SCRIPT_DATA:this._stateScriptData(e);break;case Ko.PLAINTEXT:this._statePlaintext(e);break;case Ko.TAG_OPEN:this._stateTagOpen(e);break;case Ko.END_TAG_OPEN:this._stateEndTagOpen(e);break;case Ko.TAG_NAME:this._stateTagName(e);break;case Ko.RCDATA_LESS_THAN_SIGN:this._stateRcdataLessThanSign(e);break;case Ko.RCDATA_END_TAG_OPEN:this._stateRcdataEndTagOpen(e);break;case Ko.RCDATA_END_TAG_NAME:this._stateRcdataEndTagName(e);break;case Ko.RAWTEXT_LESS_THAN_SIGN:this._stateRawtextLessThanSign(e);break;case Ko.RAWTEXT_END_TAG_OPEN:this._stateRawtextEndTagOpen(e);break;case Ko.RAWTEXT_END_TAG_NAME:this._stateRawtextEndTagName(e);break;case Ko.SCRIPT_DATA_LESS_THAN_SIGN:this._stateScriptDataLessThanSign(e);break;case Ko.SCRIPT_DATA_END_TAG_OPEN:this._stateScriptDataEndTagOpen(e);break;case Ko.SCRIPT_DATA_END_TAG_NAME:this._stateScriptDataEndTagName(e);break;case Ko.SCRIPT_DATA_ESCAPE_START:this._stateScriptDataEscapeStart(e);break;case Ko.SCRIPT_DATA_ESCAPE_START_DASH:this._stateScriptDataEscapeStartDash(e);break;case Ko.SCRIPT_DATA_ESCAPED:this._stateScriptDataEscaped(e);break;case Ko.SCRIPT_DATA_ESCAPED_DASH:this._stateScriptDataEscapedDash(e);break;case Ko.SCRIPT_DATA_ESCAPED_DASH_DASH:this._stateScriptDataEscapedDashDash(e);break;case Ko.SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN:this._stateScriptDataEscapedLessThanSign(e);break;case Ko.SCRIPT_DATA_ESCAPED_END_TAG_OPEN:this._stateScriptDataEscapedEndTagOpen(e);break;case Ko.SCRIPT_DATA_ESCAPED_END_TAG_NAME:this._stateScriptDataEscapedEndTagName(e);break;case Ko.SCRIPT_DATA_DOUBLE_ESCAPE_START:this._stateScriptDataDoubleEscapeStart(e);break;case Ko.SCRIPT_DATA_DOUBLE_ESCAPED:this._stateScriptDataDoubleEscaped(e);break;case Ko.SCRIPT_DATA_DOUBLE_ESCAPED_DASH:this._stateScriptDataDoubleEscapedDash(e);break;case Ko.SCRIPT_DATA_DOUBLE_ESCAPED_DASH_DASH:this._stateScriptDataDoubleEscapedDashDash(e);break;case Ko.SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN:this._stateScriptDataDoubleEscapedLessThanSign(e);break;case Ko.SCRIPT_DATA_DOUBLE_ESCAPE_END:this._stateScriptDataDoubleEscapeEnd(e);break;case Ko.BEFORE_ATTRIBUTE_NAME:this._stateBeforeAttributeName(e);break;case Ko.ATTRIBUTE_NAME:this._stateAttributeName(e);break;case Ko.AFTER_ATTRIBUTE_NAME:this._stateAfterAttributeName(e);break;case Ko.BEFORE_ATTRIBUTE_VALUE:this._stateBeforeAttributeValue(e);break;case Ko.ATTRIBUTE_VALUE_DOUBLE_QUOTED:this._stateAttributeValueDoubleQuoted(e);break;case Ko.ATTRIBUTE_VALUE_SINGLE_QUOTED:this._stateAttributeValueSingleQuoted(e);break;case Ko.ATTRIBUTE_VALUE_UNQUOTED:this._stateAttributeValueUnquoted(e);break;case Ko.AFTER_ATTRIBUTE_VALUE_QUOTED:this._stateAfterAttributeValueQuoted(e);break;case Ko.SELF_CLOSING_START_TAG:this._stateSelfClosingStartTag(e);break;case Ko.BOGUS_COMMENT:this._stateBogusComment(e);break;case Ko.MARKUP_DECLARATION_OPEN:this._stateMarkupDeclarationOpen(e);break;case Ko.COMMENT_START:this._stateCommentStart(e);break;case Ko.COMMENT_START_DASH:this._stateCommentStartDash(e);break;case Ko.COMMENT:this._stateComment(e);break;case Ko.COMMENT_LESS_THAN_SIGN:this._stateCommentLessThanSign(e);break;case Ko.COMMENT_LESS_THAN_SIGN_BANG:this._stateCommentLessThanSignBang(e);break;case Ko.COMMENT_LESS_THAN_SIGN_BANG_DASH:this._stateCommentLessThanSignBangDash(e);break;case Ko.COMMENT_LESS_THAN_SIGN_BANG_DASH_DASH:this._stateCommentLessThanSignBangDashDash(e);break;case Ko.COMMENT_END_DASH:this._stateCommentEndDash(e);break;case Ko.COMMENT_END:this._stateCommentEnd(e);break;case Ko.COMMENT_END_BANG:this._stateCommentEndBang(e);break;case Ko.DOCTYPE:this._stateDoctype(e);break;case Ko.BEFORE_DOCTYPE_NAME:this._stateBeforeDoctypeName(e);break;case Ko.DOCTYPE_NAME:this._stateDoctypeName(e);break;case Ko.AFTER_DOCTYPE_NAME:this._stateAfterDoctypeName(e);break;case Ko.AFTER_DOCTYPE_PUBLIC_KEYWORD:this._stateAfterDoctypePublicKeyword(e);break;case Ko.BEFORE_DOCTYPE_PUBLIC_IDENTIFIER:this._stateBeforeDoctypePublicIdentifier(e);break;case Ko.DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED:this._stateDoctypePublicIdentifierDoubleQuoted(e);break;case Ko.DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED:this._stateDoctypePublicIdentifierSingleQuoted(e);break;case Ko.AFTER_DOCTYPE_PUBLIC_IDENTIFIER:this._stateAfterDoctypePublicIdentifier(e);break;case Ko.BETWEEN_DOCTYPE_PUBLIC_AND_SYSTEM_IDENTIFIERS:this._stateBetweenDoctypePublicAndSystemIdentifiers(e);break;case Ko.AFTER_DOCTYPE_SYSTEM_KEYWORD:this._stateAfterDoctypeSystemKeyword(e);break;case Ko.BEFORE_DOCTYPE_SYSTEM_IDENTIFIER:this._stateBeforeDoctypeSystemIdentifier(e);break;case Ko.DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED:this._stateDoctypeSystemIdentifierDoubleQuoted(e);break;case Ko.DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED:this._stateDoctypeSystemIdentifierSingleQuoted(e);break;case Ko.AFTER_DOCTYPE_SYSTEM_IDENTIFIER:this._stateAfterDoctypeSystemIdentifier(e);break;case Ko.BOGUS_DOCTYPE:this._stateBogusDoctype(e);break;case Ko.CDATA_SECTION:this._stateCdataSection(e);break;case Ko.CDATA_SECTION_BRACKET:this._stateCdataSectionBracket(e);break;case Ko.CDATA_SECTION_END:this._stateCdataSectionEnd(e);break;case Ko.CHARACTER_REFERENCE:this._stateCharacterReference(e);break;case Ko.NAMED_CHARACTER_REFERENCE:this._stateNamedCharacterReference(e);break;case Ko.AMBIGUOUS_AMPERSAND:this._stateAmbiguousAmpersand(e);break;case Ko.NUMERIC_CHARACTER_REFERENCE:this._stateNumericCharacterReference(e);break;case Ko.HEXADEMICAL_CHARACTER_REFERENCE_START:this._stateHexademicalCharacterReferenceStart(e);break;case Ko.HEXADEMICAL_CHARACTER_REFERENCE:this._stateHexademicalCharacterReference(e);break;case Ko.DECIMAL_CHARACTER_REFERENCE:this._stateDecimalCharacterReference(e);break;case Ko.NUMERIC_CHARACTER_REFERENCE_END:this._stateNumericCharacterReferenceEnd(e);break;default:throw new Error("Unknown state")}}_stateData(e){switch(e){case io.LESS_THAN_SIGN:this.state=Ko.TAG_OPEN;break;case io.AMPERSAND:this.returnState=Ko.DATA,this.state=Ko.CHARACTER_REFERENCE;break;case io.NULL:this._err(Eo.unexpectedNullCharacter),this._emitCodePoint(e);break;case io.EOF:this._emitEOFToken();break;default:this._emitCodePoint(e)}}_stateRcdata(e){switch(e){case io.AMPERSAND:this.returnState=Ko.RCDATA,this.state=Ko.CHARACTER_REFERENCE;break;case io.LESS_THAN_SIGN:this.state=Ko.RCDATA_LESS_THAN_SIGN;break;case io.NULL:this._err(Eo.unexpectedNullCharacter),this._emitChars("�");break;case io.EOF:this._emitEOFToken();break;default:this._emitCodePoint(e)}}_stateRawtext(e){switch(e){case io.LESS_THAN_SIGN:this.state=Ko.RAWTEXT_LESS_THAN_SIGN;break;case io.NULL:this._err(Eo.unexpectedNullCharacter),this._emitChars("�");break;case io.EOF:this._emitEOFToken();break;default:this._emitCodePoint(e)}}_stateScriptData(e){switch(e){case io.LESS_THAN_SIGN:this.state=Ko.SCRIPT_DATA_LESS_THAN_SIGN;break;case io.NULL:this._err(Eo.unexpectedNullCharacter),this._emitChars("�");break;case io.EOF:this._emitEOFToken();break;default:this._emitCodePoint(e)}}_statePlaintext(e){switch(e){case io.NULL:this._err(Eo.unexpectedNullCharacter),this._emitChars("�");break;case io.EOF:this._emitEOFToken();break;default:this._emitCodePoint(e)}}_stateTagOpen(e){if(Qo(e))this._createStartTagToken(),this.state=Ko.TAG_NAME,this._stateTagName(e);else switch(e){case io.EXCLAMATION_MARK:this.state=Ko.MARKUP_DECLARATION_OPEN;break;case io.SOLIDUS:this.state=Ko.END_TAG_OPEN;break;case io.QUESTION_MARK:this._err(Eo.unexpectedQuestionMarkInsteadOfTagName),this._createCommentToken(1),this.state=Ko.BOGUS_COMMENT,this._stateBogusComment(e);break;case io.EOF:this._err(Eo.eofBeforeTagName),this._emitChars("<"),this._emitEOFToken();break;default:this._err(Eo.invalidFirstCharacterOfTagName),this._emitChars("<"),this.state=Ko.DATA,this._stateData(e)}}_stateEndTagOpen(e){if(Qo(e))this._createEndTagToken(),this.state=Ko.TAG_NAME,this._stateTagName(e);else switch(e){case io.GREATER_THAN_SIGN:this._err(Eo.missingEndTagName),this.state=Ko.DATA;break;case io.EOF:this._err(Eo.eofBeforeTagName),this._emitChars("");break;case io.NULL:this._err(Eo.unexpectedNullCharacter),this.state=Ko.SCRIPT_DATA_ESCAPED,this._emitChars("�");break;case io.EOF:this._err(Eo.eofInScriptHtmlCommentLikeText),this._emitEOFToken();break;default:this.state=Ko.SCRIPT_DATA_ESCAPED,this._emitCodePoint(e)}}_stateScriptDataEscapedLessThanSign(e){e===io.SOLIDUS?this.state=Ko.SCRIPT_DATA_ESCAPED_END_TAG_OPEN:Qo(e)?(this._emitChars("<"),this.state=Ko.SCRIPT_DATA_DOUBLE_ESCAPE_START,this._stateScriptDataDoubleEscapeStart(e)):(this._emitChars("<"),this.state=Ko.SCRIPT_DATA_ESCAPED,this._stateScriptDataEscaped(e))}_stateScriptDataEscapedEndTagOpen(e){Qo(e)?(this.state=Ko.SCRIPT_DATA_ESCAPED_END_TAG_NAME,this._stateScriptDataEscapedEndTagName(e)):(this._emitChars("");break;case io.NULL:this._err(Eo.unexpectedNullCharacter),this.state=Ko.SCRIPT_DATA_DOUBLE_ESCAPED,this._emitChars("�");break;case io.EOF:this._err(Eo.eofInScriptHtmlCommentLikeText),this._emitEOFToken();break;default:this.state=Ko.SCRIPT_DATA_DOUBLE_ESCAPED,this._emitCodePoint(e)}}_stateScriptDataDoubleEscapedLessThanSign(e){e===io.SOLIDUS?(this.state=Ko.SCRIPT_DATA_DOUBLE_ESCAPE_END,this._emitChars("/")):(this.state=Ko.SCRIPT_DATA_DOUBLE_ESCAPED,this._stateScriptDataDoubleEscaped(e))}_stateScriptDataDoubleEscapeEnd(e){if(this.preprocessor.startsWith(co,!1)&&ec(this.preprocessor.peek(co.length))){this._emitCodePoint(e);for(let e=0;e1114111)this._err(Eo.characterReferenceOutsideUnicodeRange),this.charRefCode=io.REPLACEMENT_CHARACTER;else if(uo(this.charRefCode))this._err(Eo.surrogateCharacterReference),this.charRefCode=io.REPLACEMENT_CHARACTER;else if(fo(this.charRefCode))this._err(Eo.noncharacterCharacterReference);else if(po(this.charRefCode)||this.charRefCode===io.CARRIAGE_RETURN){this._err(Eo.controlCharacterReference);const e=qo.get(this.charRefCode);void 0!==e&&(this.charRefCode=e)}this._flushCodePointConsumedAsCharacterReference(this.charRefCode),this._reconsumeInState(this.returnState,e)}}const nc=new Set([Po.DD,Po.DT,Po.LI,Po.OPTGROUP,Po.OPTION,Po.P,Po.RB,Po.RP,Po.RT,Po.RTC]),rc=new Set([...nc,Po.CAPTION,Po.COLGROUP,Po.TBODY,Po.TD,Po.TFOOT,Po.TH,Po.THEAD,Po.TR]),ic=new Map([[Po.APPLET,vo.HTML],[Po.CAPTION,vo.HTML],[Po.HTML,vo.HTML],[Po.MARQUEE,vo.HTML],[Po.OBJECT,vo.HTML],[Po.TABLE,vo.HTML],[Po.TD,vo.HTML],[Po.TEMPLATE,vo.HTML],[Po.TH,vo.HTML],[Po.ANNOTATION_XML,vo.MATHML],[Po.MI,vo.MATHML],[Po.MN,vo.MATHML],[Po.MO,vo.MATHML],[Po.MS,vo.MATHML],[Po.MTEXT,vo.MATHML],[Po.DESC,vo.SVG],[Po.FOREIGN_OBJECT,vo.SVG],[Po.TITLE,vo.SVG]]),sc=[Po.H1,Po.H2,Po.H3,Po.H4,Po.H5,Po.H6],ac=[Po.TR,Po.TEMPLATE,Po.HTML],oc=[Po.TBODY,Po.TFOOT,Po.THEAD,Po.TEMPLATE,Po.HTML],cc=[Po.TABLE,Po.TEMPLATE,Po.HTML],lc=[Po.TD,Po.TH];class hc{constructor(e,t,n){this.treeAdapter=t,this.handler=n,this.items=[],this.tagIDs=[],this.stackTop=-1,this.tmplCount=0,this.currentTagId=Po.UNKNOWN,this.current=e}get currentTmplContentOrNode(){return this._isInTemplate()?this.treeAdapter.getTemplateContent(this.current):this.current}_indexOf(e){return this.items.lastIndexOf(e,this.stackTop)}_isInTemplate(){return this.currentTagId===Po.TEMPLATE&&this.treeAdapter.getNamespaceURI(this.current)===vo.HTML}_updateCurrentElement(){this.current=this.items[this.stackTop],this.currentTagId=this.tagIDs[this.stackTop]}push(e,t){this.stackTop++,this.items[this.stackTop]=e,this.current=e,this.tagIDs[this.stackTop]=t,this.currentTagId=t,this._isInTemplate()&&this.tmplCount++,this.handler.onItemPush(e,t,!0)}pop(){const e=this.current;this.tmplCount>0&&this._isInTemplate()&&this.tmplCount--,this.stackTop--,this._updateCurrentElement(),this.handler.onItemPop(e,!0)}replace(e,t){const n=this._indexOf(e);this.items[n]=t,n===this.stackTop&&(this.current=t)}insertAfter(e,t,n){const r=this._indexOf(e)+1;this.items.splice(r,0,t),this.tagIDs.splice(r,0,n),this.stackTop++,r===this.stackTop&&this._updateCurrentElement(),this.handler.onItemPush(this.current,this.currentTagId,r===this.stackTop)}popUntilTagNamePopped(e){let t=this.stackTop+1;do{t=this.tagIDs.lastIndexOf(e,t-1)}while(t>0&&this.treeAdapter.getNamespaceURI(this.items[t])!==vo.HTML);this.shortenToLength(t<0?0:t)}shortenToLength(e){for(;this.stackTop>=e;){const t=this.current;this.tmplCount>0&&this._isInTemplate()&&(this.tmplCount-=1),this.stackTop--,this._updateCurrentElement(),this.handler.onItemPop(t,this.stackTop=0;n--)if(e.includes(this.tagIDs[n])&&this.treeAdapter.getNamespaceURI(this.items[n])===t)return n;return-1}clearBackTo(e,t){const n=this._indexOfTagNames(e,t);this.shortenToLength(n+1)}clearBackToTableContext(){this.clearBackTo(cc,vo.HTML)}clearBackToTableBodyContext(){this.clearBackTo(oc,vo.HTML)}clearBackToTableRowContext(){this.clearBackTo(ac,vo.HTML)}remove(e){const t=this._indexOf(e);t>=0&&(t===this.stackTop?this.pop():(this.items.splice(t,1),this.tagIDs.splice(t,1),this.stackTop--,this._updateCurrentElement(),this.handler.onItemPop(e,!1)))}tryPeekProperlyNestedBodyElement(){return this.stackTop>=1&&this.tagIDs[1]===Po.BODY?this.items[1]:null}contains(e){return this._indexOf(e)>-1}getCommonAncestor(e){const t=this._indexOf(e)-1;return t>=0?this.items[t]:null}isRootHtmlElementCurrent(){return 0===this.stackTop&&this.tagIDs[0]===Po.HTML}hasInScope(e){for(let t=this.stackTop;t>=0;t--){const n=this.tagIDs[t],r=this.treeAdapter.getNamespaceURI(this.items[t]);if(n===e&&r===vo.HTML)return!0;if(ic.get(n)===r)return!1}return!0}hasNumberedHeaderInScope(){for(let e=this.stackTop;e>=0;e--){const t=this.tagIDs[e],n=this.treeAdapter.getNamespaceURI(this.items[e]);if(Go(t)&&n===vo.HTML)return!0;if(ic.get(t)===n)return!1}return!0}hasInListItemScope(e){for(let t=this.stackTop;t>=0;t--){const n=this.tagIDs[t],r=this.treeAdapter.getNamespaceURI(this.items[t]);if(n===e&&r===vo.HTML)return!0;if((n===Po.UL||n===Po.OL)&&r===vo.HTML||ic.get(n)===r)return!1}return!0}hasInButtonScope(e){for(let t=this.stackTop;t>=0;t--){const n=this.tagIDs[t],r=this.treeAdapter.getNamespaceURI(this.items[t]);if(n===e&&r===vo.HTML)return!0;if(n===Po.BUTTON&&r===vo.HTML||ic.get(n)===r)return!1}return!0}hasInTableScope(e){for(let t=this.stackTop;t>=0;t--){const n=this.tagIDs[t];if(this.treeAdapter.getNamespaceURI(this.items[t])===vo.HTML){if(n===e)return!0;if(n===Po.TABLE||n===Po.TEMPLATE||n===Po.HTML)return!1}}return!0}hasTableBodyContextInTableScope(){for(let e=this.stackTop;e>=0;e--){const t=this.tagIDs[e];if(this.treeAdapter.getNamespaceURI(this.items[e])===vo.HTML){if(t===Po.TBODY||t===Po.THEAD||t===Po.TFOOT)return!0;if(t===Po.TABLE||t===Po.HTML)return!1}}return!0}hasInSelectScope(e){for(let t=this.stackTop;t>=0;t--){const n=this.tagIDs[t];if(this.treeAdapter.getNamespaceURI(this.items[t])===vo.HTML){if(n===e)return!0;if(n!==Po.OPTION&&n!==Po.OPTGROUP)return!1}}return!0}generateImpliedEndTags(){for(;nc.has(this.currentTagId);)this.pop()}generateImpliedEndTagsThoroughly(){for(;rc.has(this.currentTagId);)this.pop()}generateImpliedEndTagsWithExclusion(e){for(;this.currentTagId!==e&&rc.has(this.currentTagId);)this.pop()}}var uc;!function(e){e[e.Marker=0]="Marker",e[e.Element=1]="Element"}(uc=uc||(uc={}));const pc={type:uc.Marker};class fc{constructor(e){this.treeAdapter=e,this.entries=[],this.bookmark=null}_getNoahArkConditionCandidates(e,t){const n=[],r=t.length,i=this.treeAdapter.getTagName(e),s=this.treeAdapter.getNamespaceURI(e);for(let e=0;e[e.name,e.value])));let i=0;for(let e=0;er.get(e.name)===e.value))&&(i+=1,i>=3&&this.entries.splice(t.idx,1))}}insertMarker(){this.entries.unshift(pc)}pushElement(e,t){this._ensureNoahArkCondition(e),this.entries.unshift({type:uc.Element,element:e,token:t})}insertElementAfterBookmark(e,t){const n=this.entries.indexOf(this.bookmark);this.entries.splice(n,0,{type:uc.Element,element:e,token:t})}removeEntry(e){const t=this.entries.indexOf(e);t>=0&&this.entries.splice(t,1)}clearToLastMarker(){const e=this.entries.indexOf(pc);e>=0?this.entries.splice(0,e+1):this.entries.length=0}getElementEntryInScopeWithTagName(e){const t=this.entries.find((t=>t.type===uc.Marker||this.treeAdapter.getTagName(t.element)===e));return t&&t.type===uc.Element?t:null}getElementEntry(e){return this.entries.find((t=>t.type===uc.Element&&t.element===e))}}function dc(e){return{nodeName:"#text",value:e,parentNode:null}}const Ec={createDocument:()=>({nodeName:"#document",mode:Ro.NO_QUIRKS,childNodes:[]}),createDocumentFragment:()=>({nodeName:"#document-fragment",childNodes:[]}),createElement:(e,t,n)=>({nodeName:e,tagName:e,attrs:n,namespaceURI:t,childNodes:[],parentNode:null}),createCommentNode:e=>({nodeName:"#comment",data:e,parentNode:null}),appendChild(e,t){e.childNodes.push(t),t.parentNode=e},insertBefore(e,t,n){const r=e.childNodes.indexOf(n);e.childNodes.splice(r,0,t),t.parentNode=e},setTemplateContent(e,t){e.content=t},getTemplateContent:e=>e.content,setDocumentType(e,t,n,r){const i=e.childNodes.find((e=>"#documentType"===e.nodeName));if(i)i.name=t,i.publicId=n,i.systemId=r;else{const i={nodeName:"#documentType",name:t,publicId:n,systemId:r,parentNode:null};Ec.appendChild(e,i)}},setDocumentMode(e,t){e.mode=t},getDocumentMode:e=>e.mode,detachNode(e){if(e.parentNode){const t=e.parentNode.childNodes.indexOf(e);e.parentNode.childNodes.splice(t,1),e.parentNode=null}},insertText(e,t){if(e.childNodes.length>0){const n=e.childNodes[e.childNodes.length-1];if(Ec.isTextNode(n))return void(n.value+=t)}Ec.appendChild(e,dc(t))},insertTextBefore(e,t,n){const r=e.childNodes[e.childNodes.indexOf(n)-1];r&&Ec.isTextNode(r)?r.value+=t:Ec.insertBefore(e,dc(t),n)},adoptAttributes(e,t){const n=new Set(e.attrs.map((e=>e.name)));for(let r=0;re.childNodes[0],getChildNodes:e=>e.childNodes,getParentNode:e=>e.parentNode,getAttrList:e=>e.attrs,getTagName:e=>e.tagName,getNamespaceURI:e=>e.namespaceURI,getTextNodeContent:e=>e.value,getCommentNodeContent:e=>e.data,getDocumentTypeNodeName:e=>e.name,getDocumentTypeNodePublicId:e=>e.publicId,getDocumentTypeNodeSystemId:e=>e.systemId,isTextNode:e=>"#text"===e.nodeName,isCommentNode:e=>"#comment"===e.nodeName,isDocumentTypeNode:e=>"#documentType"===e.nodeName,isElementNode:e=>Object.prototype.hasOwnProperty.call(e,"tagName"),setNodeSourceCodeLocation(e,t){e.sourceCodeLocation=t},getNodeSourceCodeLocation:e=>e.sourceCodeLocation,updateNodeSourceCodeLocation(e,t){e.sourceCodeLocation={...e.sourceCodeLocation,...t}}},mc=["+//silmaril//dtd html pro v0r11 19970101//","-//as//dtd html 3.0 aswedit + extensions//","-//advasoft ltd//dtd html 3.0 aswedit + extensions//","-//ietf//dtd html 2.0 level 1//","-//ietf//dtd html 2.0 level 2//","-//ietf//dtd html 2.0 strict level 1//","-//ietf//dtd html 2.0 strict level 2//","-//ietf//dtd html 2.0 strict//","-//ietf//dtd html 2.0//","-//ietf//dtd html 2.1e//","-//ietf//dtd html 3.0//","-//ietf//dtd html 3.2 final//","-//ietf//dtd html 3.2//","-//ietf//dtd html 3//","-//ietf//dtd html level 0//","-//ietf//dtd html level 1//","-//ietf//dtd html level 2//","-//ietf//dtd html level 3//","-//ietf//dtd html strict level 0//","-//ietf//dtd html strict level 1//","-//ietf//dtd html strict level 2//","-//ietf//dtd html strict level 3//","-//ietf//dtd html strict//","-//ietf//dtd html//","-//metrius//dtd metrius presentational//","-//microsoft//dtd internet explorer 2.0 html strict//","-//microsoft//dtd internet explorer 2.0 html//","-//microsoft//dtd internet explorer 2.0 tables//","-//microsoft//dtd internet explorer 3.0 html strict//","-//microsoft//dtd internet explorer 3.0 html//","-//microsoft//dtd internet explorer 3.0 tables//","-//netscape comm. corp.//dtd html//","-//netscape comm. corp.//dtd strict html//","-//o'reilly and associates//dtd html 2.0//","-//o'reilly and associates//dtd html extended 1.0//","-//o'reilly and associates//dtd html extended relaxed 1.0//","-//sq//dtd html 2.0 hotmetal + extensions//","-//softquad software//dtd hotmetal pro 6.0::19990601::extensions to html 4.0//","-//softquad//dtd hotmetal pro 4.0::19971010::extensions to html 4.0//","-//spyglass//dtd html 2.0 extended//","-//sun microsystems corp.//dtd hotjava html//","-//sun microsystems corp.//dtd hotjava strict html//","-//w3c//dtd html 3 1995-03-24//","-//w3c//dtd html 3.2 draft//","-//w3c//dtd html 3.2 final//","-//w3c//dtd html 3.2//","-//w3c//dtd html 3.2s draft//","-//w3c//dtd html 4.0 frameset//","-//w3c//dtd html 4.0 transitional//","-//w3c//dtd html experimental 19960712//","-//w3c//dtd html experimental 970421//","-//w3c//dtd w3 html//","-//w3o//dtd w3 html 3.0//","-//webtechs//dtd mozilla html 2.0//","-//webtechs//dtd mozilla html//"],Tc=[...mc,"-//w3c//dtd html 4.01 frameset//","-//w3c//dtd html 4.01 transitional//"],_c=new Set(["-//w3o//dtd w3 html strict 3.0//en//","-/w3c/dtd html 4.0 transitional/en","html"]),Ac=["-//w3c//dtd xhtml 1.0 frameset//","-//w3c//dtd xhtml 1.0 transitional//"],gc=[...Ac,"-//w3c//dtd html 4.01 frameset//","-//w3c//dtd html 4.01 transitional//"];function Nc(e,t){return t.some((t=>e.startsWith(t)))}const Cc="text/html",Ic="application/xhtml+xml",Sc=new Map(["attributeName","attributeType","baseFrequency","baseProfile","calcMode","clipPathUnits","diffuseConstant","edgeMode","filterUnits","glyphRef","gradientTransform","gradientUnits","kernelMatrix","kernelUnitLength","keyPoints","keySplines","keyTimes","lengthAdjust","limitingConeAngle","markerHeight","markerUnits","markerWidth","maskContentUnits","maskUnits","numOctaves","pathLength","patternContentUnits","patternTransform","patternUnits","pointsAtX","pointsAtY","pointsAtZ","preserveAlpha","preserveAspectRatio","primitiveUnits","refX","refY","repeatCount","repeatDur","requiredExtensions","requiredFeatures","specularConstant","specularExponent","spreadMethod","startOffset","stdDeviation","stitchTiles","surfaceScale","systemLanguage","tableValues","targetX","targetY","textLength","viewBox","viewTarget","xChannelSelector","yChannelSelector","zoomAndPan"].map((e=>[e.toLowerCase(),e]))),bc=new Map([["xlink:actuate",{prefix:"xlink",name:"actuate",namespace:vo.XLINK}],["xlink:arcrole",{prefix:"xlink",name:"arcrole",namespace:vo.XLINK}],["xlink:href",{prefix:"xlink",name:"href",namespace:vo.XLINK}],["xlink:role",{prefix:"xlink",name:"role",namespace:vo.XLINK}],["xlink:show",{prefix:"xlink",name:"show",namespace:vo.XLINK}],["xlink:title",{prefix:"xlink",name:"title",namespace:vo.XLINK}],["xlink:type",{prefix:"xlink",name:"type",namespace:vo.XLINK}],["xml:base",{prefix:"xml",name:"base",namespace:vo.XML}],["xml:lang",{prefix:"xml",name:"lang",namespace:vo.XML}],["xml:space",{prefix:"xml",name:"space",namespace:vo.XML}],["xmlns",{prefix:"",name:"xmlns",namespace:vo.XMLNS}],["xmlns:xlink",{prefix:"xmlns",name:"xlink",namespace:vo.XMLNS}]]),Oc=new Map(["altGlyph","altGlyphDef","altGlyphItem","animateColor","animateMotion","animateTransform","clipPath","feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feImage","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence","foreignObject","glyphRef","linearGradient","radialGradient","textPath"].map((e=>[e.toLowerCase(),e]))),yc=new Set([Po.B,Po.BIG,Po.BLOCKQUOTE,Po.BODY,Po.BR,Po.CENTER,Po.CODE,Po.DD,Po.DIV,Po.DL,Po.DT,Po.EM,Po.EMBED,Po.H1,Po.H2,Po.H3,Po.H4,Po.H5,Po.H6,Po.HEAD,Po.HR,Po.I,Po.IMG,Po.LI,Po.LISTING,Po.MENU,Po.META,Po.NOBR,Po.OL,Po.P,Po.PRE,Po.RUBY,Po.S,Po.SMALL,Po.SPAN,Po.STRONG,Po.STRIKE,Po.SUB,Po.SUP,Po.TABLE,Po.TT,Po.U,Po.UL,Po.VAR]);function Lc(e){for(let t=0;t0&&this._setContextModes(e,t)}onItemPop(e,t){var n,r;if(this.options.sourceCodeLocationInfo&&this._setEndLocation(e,this.currentToken),null===(r=(n=this.treeAdapter).onItemPop)||void 0===r||r.call(n,e,this.openElements.current),t){let e,t;0===this.openElements.stackTop&&this.fragmentContext?(e=this.fragmentContext,t=this.fragmentContextID):({current:e,currentTagId:t}=this.openElements),this._setContextModes(e,t)}}_setContextModes(e,t){const n=e===this.document||this.treeAdapter.getNamespaceURI(e)===vo.HTML;this.currentNotInHTML=!n,this.tokenizer.inForeignNode=!n&&!this._isIntegrationPoint(t,e)}_switchToTextParsing(e,t){this._insertElement(e,vo.HTML),this.tokenizer.state=t,this.originalInsertionMode=this.insertionMode,this.insertionMode=Rc.TEXT}switchToPlaintextParsing(){this.insertionMode=Rc.TEXT,this.originalInsertionMode=Rc.IN_BODY,this.tokenizer.state=jo.PLAINTEXT}_getAdjustedCurrentElement(){return 0===this.openElements.stackTop&&this.fragmentContext?this.fragmentContext:this.openElements.current}_findFormInFragmentContext(){let e=this.fragmentContext;for(;e;){if(this.treeAdapter.getTagName(e)===Mo.FORM){this.formElement=e;break}e=this.treeAdapter.getParentNode(e)}}_initTokenizerForFragmentParsing(){if(this.fragmentContext&&this.treeAdapter.getNamespaceURI(this.fragmentContext)===vo.HTML)switch(this.fragmentContextID){case Po.TITLE:case Po.TEXTAREA:this.tokenizer.state=jo.RCDATA;break;case Po.STYLE:case Po.XMP:case Po.IFRAME:case Po.NOEMBED:case Po.NOFRAMES:case Po.NOSCRIPT:this.tokenizer.state=jo.RAWTEXT;break;case Po.SCRIPT:this.tokenizer.state=jo.SCRIPT_DATA;break;case Po.PLAINTEXT:this.tokenizer.state=jo.PLAINTEXT}}_setDocumentType(e){const t=e.name||"",n=e.publicId||"",r=e.systemId||"";if(this.treeAdapter.setDocumentType(this.document,t,n,r),e.location){const t=this.treeAdapter.getChildNodes(this.document).find((e=>this.treeAdapter.isDocumentTypeNode(e)));t&&this.treeAdapter.setNodeSourceCodeLocation(t,e.location)}}_attachElementToTree(e,t){if(this.options.sourceCodeLocationInfo){const n=t&&{...t,startTag:t};this.treeAdapter.setNodeSourceCodeLocation(e,n)}if(this._shouldFosterParentOnInsertion())this._fosterParentElement(e);else{const t=this.openElements.currentTmplContentOrNode;this.treeAdapter.appendChild(t,e)}}_appendElement(e,t){const n=this.treeAdapter.createElement(e.tagName,t,e.attrs);this._attachElementToTree(n,e.location)}_insertElement(e,t){const n=this.treeAdapter.createElement(e.tagName,t,e.attrs);this._attachElementToTree(n,e.location),this.openElements.push(n,e.tagID)}_insertFakeElement(e,t){const n=this.treeAdapter.createElement(e,vo.HTML,[]);this._attachElementToTree(n,null),this.openElements.push(n,t)}_insertTemplate(e){const t=this.treeAdapter.createElement(e.tagName,vo.HTML,e.attrs),n=this.treeAdapter.createDocumentFragment();this.treeAdapter.setTemplateContent(t,n),this._attachElementToTree(t,e.location),this.openElements.push(t,e.tagID),this.options.sourceCodeLocationInfo&&this.treeAdapter.setNodeSourceCodeLocation(n,null)}_insertFakeRootElement(){const e=this.treeAdapter.createElement(Mo.HTML,vo.HTML,[]);this.options.sourceCodeLocationInfo&&this.treeAdapter.setNodeSourceCodeLocation(e,null),this.treeAdapter.appendChild(this.openElements.current,e),this.openElements.push(e,Po.HTML)}_appendCommentNode(e,t){const n=this.treeAdapter.createCommentNode(e.data);this.treeAdapter.appendChild(t,n),this.options.sourceCodeLocationInfo&&this.treeAdapter.setNodeSourceCodeLocation(n,e.location)}_insertCharacters(e){let t,n;if(this._shouldFosterParentOnInsertion()?(({parent:t,beforeElement:n}=this._findFosterParentingLocation()),n?this.treeAdapter.insertTextBefore(t,e.chars,n):this.treeAdapter.insertText(t,e.chars)):(t=this.openElements.currentTmplContentOrNode,this.treeAdapter.insertText(t,e.chars)),!e.location)return;const r=this.treeAdapter.getChildNodes(t),i=n?r.lastIndexOf(n):r.length,s=r[i-1];if(this.treeAdapter.getNodeSourceCodeLocation(s)){const{endLine:t,endCol:n,endOffset:r}=e.location;this.treeAdapter.updateNodeSourceCodeLocation(s,{endLine:t,endCol:n,endOffset:r})}else this.options.sourceCodeLocationInfo&&this.treeAdapter.setNodeSourceCodeLocation(s,e.location)}_adoptNodes(e,t){for(let n=this.treeAdapter.getFirstChild(e);n;n=this.treeAdapter.getFirstChild(e))this.treeAdapter.detachNode(n),this.treeAdapter.appendChild(t,n)}_setEndLocation(e,t){if(this.treeAdapter.getNodeSourceCodeLocation(e)&&t.location){const n=t.location,r=this.treeAdapter.getTagName(e),i=t.type===To.END_TAG&&r===t.tagName?{endTag:{...n},endLine:n.endLine,endCol:n.endCol,endOffset:n.endOffset}:{endLine:n.startLine,endCol:n.startCol,endOffset:n.startOffset};this.treeAdapter.updateNodeSourceCodeLocation(e,i)}}shouldProcessStartTagTokenInForeignContent(e){if(!this.currentNotInHTML)return!1;let t,n;return 0===this.openElements.stackTop&&this.fragmentContext?(t=this.fragmentContext,n=this.fragmentContextID):({current:t,currentTagId:n}=this.openElements),(e.tagID!==Po.SVG||this.treeAdapter.getTagName(t)!==Mo.ANNOTATION_XML||this.treeAdapter.getNamespaceURI(t)!==vo.MATHML)&&(this.tokenizer.inForeignNode||(e.tagID===Po.MGLYPH||e.tagID===Po.MALIGNMARK)&&!this._isIntegrationPoint(n,t,vo.HTML))}_processToken(e){switch(e.type){case To.CHARACTER:this.onCharacter(e);break;case To.NULL_CHARACTER:this.onNullCharacter(e);break;case To.COMMENT:this.onComment(e);break;case To.DOCTYPE:this.onDoctype(e);break;case To.START_TAG:this._processStartTag(e);break;case To.END_TAG:this.onEndTag(e);break;case To.EOF:this.onEof(e);break;case To.WHITESPACE_CHARACTER:this.onWhitespaceCharacter(e)}}_isIntegrationPoint(e,t,n){return Dc(e,this.treeAdapter.getNamespaceURI(t),this.treeAdapter.getAttrList(t),n)}_reconstructActiveFormattingElements(){const e=this.activeFormattingElements.entries.length;if(e){const t=this.activeFormattingElements.entries.findIndex((e=>e.type===uc.Marker||this.openElements.contains(e.element)));for(let n=t<0?e-1:t-1;n>=0;n--){const e=this.activeFormattingElements.entries[n];this._insertElement(e.token,this.treeAdapter.getNamespaceURI(e.element)),e.element=this.openElements.current}}}_closeTableCell(){this.openElements.generateImpliedEndTags(),this.openElements.popUntilTableCellPopped(),this.activeFormattingElements.clearToLastMarker(),this.insertionMode=Rc.IN_ROW}_closePElement(){this.openElements.generateImpliedEndTagsWithExclusion(Po.P),this.openElements.popUntilTagNamePopped(Po.P)}_resetInsertionMode(){for(let e=this.openElements.stackTop;e>=0;e--)switch(0===e&&this.fragmentContext?this.fragmentContextID:this.openElements.tagIDs[e]){case Po.TR:return void(this.insertionMode=Rc.IN_ROW);case Po.TBODY:case Po.THEAD:case Po.TFOOT:return void(this.insertionMode=Rc.IN_TABLE_BODY);case Po.CAPTION:return void(this.insertionMode=Rc.IN_CAPTION);case Po.COLGROUP:return void(this.insertionMode=Rc.IN_COLUMN_GROUP);case Po.TABLE:return void(this.insertionMode=Rc.IN_TABLE);case Po.BODY:return void(this.insertionMode=Rc.IN_BODY);case Po.FRAMESET:return void(this.insertionMode=Rc.IN_FRAMESET);case Po.SELECT:return void this._resetInsertionModeForSelect(e);case Po.TEMPLATE:return void(this.insertionMode=this.tmplInsertionModeStack[0]);case Po.HTML:return void(this.insertionMode=this.headElement?Rc.AFTER_HEAD:Rc.BEFORE_HEAD);case Po.TD:case Po.TH:if(e>0)return void(this.insertionMode=Rc.IN_CELL);break;case Po.HEAD:if(e>0)return void(this.insertionMode=Rc.IN_HEAD)}this.insertionMode=Rc.IN_BODY}_resetInsertionModeForSelect(e){if(e>0)for(let t=e-1;t>0;t--){const e=this.openElements.tagIDs[t];if(e===Po.TEMPLATE)break;if(e===Po.TABLE)return void(this.insertionMode=Rc.IN_SELECT_IN_TABLE)}this.insertionMode=Rc.IN_SELECT}_isElementCausesFosterParenting(e){return Pc.has(e)}_shouldFosterParentOnInsertion(){return this.fosterParentingEnabled&&this._isElementCausesFosterParenting(this.openElements.currentTagId)}_findFosterParentingLocation(){for(let e=this.openElements.stackTop;e>=0;e--){const t=this.openElements.items[e];switch(this.openElements.tagIDs[e]){case Po.TEMPLATE:if(this.treeAdapter.getNamespaceURI(t)===vo.HTML)return{parent:this.treeAdapter.getTemplateContent(t),beforeElement:null};break;case Po.TABLE:{const n=this.treeAdapter.getParentNode(t);return n?{parent:n,beforeElement:t}:{parent:this.openElements.items[e-1],beforeElement:null}}}}return{parent:this.openElements.items[0],beforeElement:null}}_fosterParentElement(e){const t=this._findFosterParentingLocation();t.beforeElement?this.treeAdapter.insertBefore(t.parent,e,t.beforeElement):this.treeAdapter.appendChild(t.parent,e)}_isSpecialElement(e,t){const n=this.treeAdapter.getNamespaceURI(e);return Ho[n].has(t)}onCharacter(e){if(this.skipNextNewLine=!1,this.tokenizer.inForeignNode)!function(e,t){e._insertCharacters(t),e.framesetOk=!1}(this,e);else switch(this.insertionMode){case Rc.INITIAL:Vc(this,e);break;case Rc.BEFORE_HTML:Wc(this,e);break;case Rc.BEFORE_HEAD:Qc(this,e);break;case Rc.IN_HEAD:zc(this,e);break;case Rc.IN_HEAD_NO_SCRIPT:Jc(this,e);break;case Rc.AFTER_HEAD:Zc(this,e);break;case Rc.IN_BODY:case Rc.IN_CAPTION:case Rc.IN_CELL:case Rc.IN_TEMPLATE:nl(this,e);break;case Rc.TEXT:case Rc.IN_SELECT:case Rc.IN_SELECT_IN_TABLE:this._insertCharacters(e);break;case Rc.IN_TABLE:case Rc.IN_TABLE_BODY:case Rc.IN_ROW:ul(this,e);break;case Rc.IN_TABLE_TEXT:ml(this,e);break;case Rc.IN_COLUMN_GROUP:gl(this,e);break;case Rc.AFTER_BODY:kl(this,e);break;case Rc.AFTER_AFTER_BODY:vl(this,e)}}onNullCharacter(e){if(this.skipNextNewLine=!1,this.tokenizer.inForeignNode)!function(e,t){t.chars="�",e._insertCharacters(t)}(this,e);else switch(this.insertionMode){case Rc.INITIAL:Vc(this,e);break;case Rc.BEFORE_HTML:Wc(this,e);break;case Rc.BEFORE_HEAD:Qc(this,e);break;case Rc.IN_HEAD:zc(this,e);break;case Rc.IN_HEAD_NO_SCRIPT:Jc(this,e);break;case Rc.AFTER_HEAD:Zc(this,e);break;case Rc.TEXT:this._insertCharacters(e);break;case Rc.IN_TABLE:case Rc.IN_TABLE_BODY:case Rc.IN_ROW:ul(this,e);break;case Rc.IN_COLUMN_GROUP:gl(this,e);break;case Rc.AFTER_BODY:kl(this,e);break;case Rc.AFTER_AFTER_BODY:vl(this,e)}}onComment(e){if(this.skipNextNewLine=!1,this.currentNotInHTML)Kc(this,e);else switch(this.insertionMode){case Rc.INITIAL:case Rc.BEFORE_HTML:case Rc.BEFORE_HEAD:case Rc.IN_HEAD:case Rc.IN_HEAD_NO_SCRIPT:case Rc.AFTER_HEAD:case Rc.IN_BODY:case Rc.IN_TABLE:case Rc.IN_CAPTION:case Rc.IN_COLUMN_GROUP:case Rc.IN_TABLE_BODY:case Rc.IN_ROW:case Rc.IN_CELL:case Rc.IN_SELECT:case Rc.IN_SELECT_IN_TABLE:case Rc.IN_TEMPLATE:case Rc.IN_FRAMESET:case Rc.AFTER_FRAMESET:Kc(this,e);break;case Rc.IN_TABLE_TEXT:Tl(this,e);break;case Rc.AFTER_BODY:!function(e,t){e._appendCommentNode(t,e.openElements.items[0])}(this,e);break;case Rc.AFTER_AFTER_BODY:case Rc.AFTER_AFTER_FRAMESET:!function(e,t){e._appendCommentNode(t,e.document)}(this,e)}}onDoctype(e){switch(this.skipNextNewLine=!1,this.insertionMode){case Rc.INITIAL:!function(e,t){e._setDocumentType(t);const n=t.forceQuirks?Ro.QUIRKS:function(e){if("html"!==e.name)return Ro.QUIRKS;const{systemId:t}=e;if(t&&"http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd"===t.toLowerCase())return Ro.QUIRKS;let{publicId:n}=e;if(null!==n){if(n=n.toLowerCase(),_c.has(n))return Ro.QUIRKS;let e=null===t?Tc:mc;if(Nc(n,e))return Ro.QUIRKS;if(e=null===t?Ac:gc,Nc(n,e))return Ro.LIMITED_QUIRKS}return Ro.NO_QUIRKS}(t);(function(e){return"html"===e.name&&null===e.publicId&&(null===e.systemId||"about:legacy-compat"===e.systemId)})(t)||e._err(t,Eo.nonConformingDoctype);e.treeAdapter.setDocumentMode(e.document,n),e.insertionMode=Rc.BEFORE_HTML}(this,e);break;case Rc.BEFORE_HEAD:case Rc.IN_HEAD:case Rc.IN_HEAD_NO_SCRIPT:case Rc.AFTER_HEAD:this._err(e,Eo.misplacedDoctype);break;case Rc.IN_TABLE_TEXT:Tl(this,e)}}onStartTag(e){this.skipNextNewLine=!1,this.currentToken=e,this._processStartTag(e),e.selfClosing&&!e.ackSelfClosing&&this._err(e,Eo.nonVoidHtmlElementStartTagWithTrailingSolidus)}_processStartTag(e){this.shouldProcessStartTagTokenInForeignContent(e)?function(e,t){if(function(e){const t=e.tagID;return t===Po.FONT&&e.attrs.some((({name:e})=>e===Do.COLOR||e===Do.SIZE||e===Do.FACE))||yc.has(t)}(t))Dl(e),e._startTagOutsideForeignContent(t);else{const n=e._getAdjustedCurrentElement(),r=e.treeAdapter.getNamespaceURI(n);r===vo.MATHML?Lc(t):r===vo.SVG&&(!function(e){const t=Oc.get(e.tagName);null!=t&&(e.tagName=t,e.tagID=Fo(e.tagName))}(t),kc(t)),vc(t),t.selfClosing?e._appendElement(t,r):e._insertElement(t,r),t.ackSelfClosing=!0}}(this,e):this._startTagOutsideForeignContent(e)}_startTagOutsideForeignContent(e){switch(this.insertionMode){case Rc.INITIAL:Vc(this,e);break;case Rc.BEFORE_HTML:!function(e,t){t.tagID===Po.HTML?(e._insertElement(t,vo.HTML),e.insertionMode=Rc.BEFORE_HEAD):Wc(e,t)}(this,e);break;case Rc.BEFORE_HEAD:!function(e,t){switch(t.tagID){case Po.HTML:ol(e,t);break;case Po.HEAD:e._insertElement(t,vo.HTML),e.headElement=e.openElements.current,e.insertionMode=Rc.IN_HEAD;break;default:Qc(e,t)}}(this,e);break;case Rc.IN_HEAD:Xc(this,e);break;case Rc.IN_HEAD_NO_SCRIPT:!function(e,t){switch(t.tagID){case Po.HTML:ol(e,t);break;case Po.BASEFONT:case Po.BGSOUND:case Po.HEAD:case Po.LINK:case Po.META:case Po.NOFRAMES:case Po.STYLE:Xc(e,t);break;case Po.NOSCRIPT:e._err(t,Eo.nestedNoscriptInHead);break;default:Jc(e,t)}}(this,e);break;case Rc.AFTER_HEAD:!function(e,t){switch(t.tagID){case Po.HTML:ol(e,t);break;case Po.BODY:e._insertElement(t,vo.HTML),e.framesetOk=!1,e.insertionMode=Rc.IN_BODY;break;case Po.FRAMESET:e._insertElement(t,vo.HTML),e.insertionMode=Rc.IN_FRAMESET;break;case Po.BASE:case Po.BASEFONT:case Po.BGSOUND:case Po.LINK:case Po.META:case Po.NOFRAMES:case Po.SCRIPT:case Po.STYLE:case Po.TEMPLATE:case Po.TITLE:e._err(t,Eo.abandonedHeadElementChild),e.openElements.push(e.headElement,Po.HEAD),Xc(e,t),e.openElements.remove(e.headElement);break;case Po.HEAD:e._err(t,Eo.misplacedStartTagForHeadElement);break;default:Zc(e,t)}}(this,e);break;case Rc.IN_BODY:ol(this,e);break;case Rc.IN_TABLE:pl(this,e);break;case Rc.IN_TABLE_TEXT:Tl(this,e);break;case Rc.IN_CAPTION:!function(e,t){const n=t.tagID;_l.has(n)?e.openElements.hasInTableScope(Po.CAPTION)&&(e.openElements.generateImpliedEndTags(),e.openElements.popUntilTagNamePopped(Po.CAPTION),e.activeFormattingElements.clearToLastMarker(),e.insertionMode=Rc.IN_TABLE,pl(e,t)):ol(e,t)}(this,e);break;case Rc.IN_COLUMN_GROUP:Al(this,e);break;case Rc.IN_TABLE_BODY:Nl(this,e);break;case Rc.IN_ROW:Il(this,e);break;case Rc.IN_CELL:!function(e,t){const n=t.tagID;_l.has(n)?(e.openElements.hasInTableScope(Po.TD)||e.openElements.hasInTableScope(Po.TH))&&(e._closeTableCell(),Il(e,t)):ol(e,t)}(this,e);break;case Rc.IN_SELECT:bl(this,e);break;case Rc.IN_SELECT_IN_TABLE:!function(e,t){const n=t.tagID;n===Po.CAPTION||n===Po.TABLE||n===Po.TBODY||n===Po.TFOOT||n===Po.THEAD||n===Po.TR||n===Po.TD||n===Po.TH?(e.openElements.popUntilTagNamePopped(Po.SELECT),e._resetInsertionMode(),e._processStartTag(t)):bl(e,t)}(this,e);break;case Rc.IN_TEMPLATE:!function(e,t){switch(t.tagID){case Po.BASE:case Po.BASEFONT:case Po.BGSOUND:case Po.LINK:case Po.META:case Po.NOFRAMES:case Po.SCRIPT:case Po.STYLE:case Po.TEMPLATE:case Po.TITLE:Xc(e,t);break;case Po.CAPTION:case Po.COLGROUP:case Po.TBODY:case Po.TFOOT:case Po.THEAD:e.tmplInsertionModeStack[0]=Rc.IN_TABLE,e.insertionMode=Rc.IN_TABLE,pl(e,t);break;case Po.COL:e.tmplInsertionModeStack[0]=Rc.IN_COLUMN_GROUP,e.insertionMode=Rc.IN_COLUMN_GROUP,Al(e,t);break;case Po.TR:e.tmplInsertionModeStack[0]=Rc.IN_TABLE_BODY,e.insertionMode=Rc.IN_TABLE_BODY,Nl(e,t);break;case Po.TD:case Po.TH:e.tmplInsertionModeStack[0]=Rc.IN_ROW,e.insertionMode=Rc.IN_ROW,Il(e,t);break;default:e.tmplInsertionModeStack[0]=Rc.IN_BODY,e.insertionMode=Rc.IN_BODY,ol(e,t)}}(this,e);break;case Rc.AFTER_BODY:!function(e,t){t.tagID===Po.HTML?ol(e,t):kl(e,t)}(this,e);break;case Rc.IN_FRAMESET:!function(e,t){switch(t.tagID){case Po.HTML:ol(e,t);break;case Po.FRAMESET:e._insertElement(t,vo.HTML);break;case Po.FRAME:e._appendElement(t,vo.HTML),t.ackSelfClosing=!0;break;case Po.NOFRAMES:Xc(e,t)}}(this,e);break;case Rc.AFTER_FRAMESET:!function(e,t){switch(t.tagID){case Po.HTML:ol(e,t);break;case Po.NOFRAMES:Xc(e,t)}}(this,e);break;case Rc.AFTER_AFTER_BODY:!function(e,t){t.tagID===Po.HTML?ol(e,t):vl(e,t)}(this,e);break;case Rc.AFTER_AFTER_FRAMESET:!function(e,t){switch(t.tagID){case Po.HTML:ol(e,t);break;case Po.NOFRAMES:Xc(e,t)}}(this,e)}}onEndTag(e){this.skipNextNewLine=!1,this.currentToken=e,this.currentNotInHTML?function(e,t){if(t.tagID===Po.P||t.tagID===Po.BR)return Dl(e),void e._endTagOutsideForeignContent(t);for(let n=e.openElements.stackTop;n>0;n--){const r=e.openElements.items[n];if(e.treeAdapter.getNamespaceURI(r)===vo.HTML){e._endTagOutsideForeignContent(t);break}const i=e.treeAdapter.getTagName(r);if(i.toLowerCase()===t.tagName){t.tagName=i,e.openElements.shortenToLength(n);break}}}(this,e):this._endTagOutsideForeignContent(e)}_endTagOutsideForeignContent(e){switch(this.insertionMode){case Rc.INITIAL:Vc(this,e);break;case Rc.BEFORE_HTML:!function(e,t){const n=t.tagID;n!==Po.HTML&&n!==Po.HEAD&&n!==Po.BODY&&n!==Po.BR||Wc(e,t)}(this,e);break;case Rc.BEFORE_HEAD:!function(e,t){const n=t.tagID;n===Po.HEAD||n===Po.BODY||n===Po.HTML||n===Po.BR?Qc(e,t):e._err(t,Eo.endTagWithoutMatchingOpenElement)}(this,e);break;case Rc.IN_HEAD:!function(e,t){switch(t.tagID){case Po.HEAD:e.openElements.pop(),e.insertionMode=Rc.AFTER_HEAD;break;case Po.BODY:case Po.BR:case Po.HTML:zc(e,t);break;case Po.TEMPLATE:$c(e,t);break;default:e._err(t,Eo.endTagWithoutMatchingOpenElement)}}(this,e);break;case Rc.IN_HEAD_NO_SCRIPT:!function(e,t){switch(t.tagID){case Po.NOSCRIPT:e.openElements.pop(),e.insertionMode=Rc.IN_HEAD;break;case Po.BR:Jc(e,t);break;default:e._err(t,Eo.endTagWithoutMatchingOpenElement)}}(this,e);break;case Rc.AFTER_HEAD:!function(e,t){switch(t.tagID){case Po.BODY:case Po.HTML:case Po.BR:Zc(e,t);break;case Po.TEMPLATE:$c(e,t);break;default:e._err(t,Eo.endTagWithoutMatchingOpenElement)}}(this,e);break;case Rc.IN_BODY:ll(this,e);break;case Rc.TEXT:!function(e,t){var n;t.tagID===Po.SCRIPT&&(null===(n=e.scriptHandler)||void 0===n||n.call(e,e.openElements.current));e.openElements.pop(),e.insertionMode=e.originalInsertionMode}(this,e);break;case Rc.IN_TABLE:fl(this,e);break;case Rc.IN_TABLE_TEXT:Tl(this,e);break;case Rc.IN_CAPTION:!function(e,t){const n=t.tagID;switch(n){case Po.CAPTION:case Po.TABLE:e.openElements.hasInTableScope(Po.CAPTION)&&(e.openElements.generateImpliedEndTags(),e.openElements.popUntilTagNamePopped(Po.CAPTION),e.activeFormattingElements.clearToLastMarker(),e.insertionMode=Rc.IN_TABLE,n===Po.TABLE&&fl(e,t));break;case Po.BODY:case Po.COL:case Po.COLGROUP:case Po.HTML:case Po.TBODY:case Po.TD:case Po.TFOOT:case Po.TH:case Po.THEAD:case Po.TR:break;default:ll(e,t)}}(this,e);break;case Rc.IN_COLUMN_GROUP:!function(e,t){switch(t.tagID){case Po.COLGROUP:e.openElements.currentTagId===Po.COLGROUP&&(e.openElements.pop(),e.insertionMode=Rc.IN_TABLE);break;case Po.TEMPLATE:$c(e,t);break;case Po.COL:break;default:gl(e,t)}}(this,e);break;case Rc.IN_TABLE_BODY:Cl(this,e);break;case Rc.IN_ROW:Sl(this,e);break;case Rc.IN_CELL:!function(e,t){const n=t.tagID;switch(n){case Po.TD:case Po.TH:e.openElements.hasInTableScope(n)&&(e.openElements.generateImpliedEndTags(),e.openElements.popUntilTagNamePopped(n),e.activeFormattingElements.clearToLastMarker(),e.insertionMode=Rc.IN_ROW);break;case Po.TABLE:case Po.TBODY:case Po.TFOOT:case Po.THEAD:case Po.TR:e.openElements.hasInTableScope(n)&&(e._closeTableCell(),Sl(e,t));break;case Po.BODY:case Po.CAPTION:case Po.COL:case Po.COLGROUP:case Po.HTML:break;default:ll(e,t)}}(this,e);break;case Rc.IN_SELECT:Ol(this,e);break;case Rc.IN_SELECT_IN_TABLE:!function(e,t){const n=t.tagID;n===Po.CAPTION||n===Po.TABLE||n===Po.TBODY||n===Po.TFOOT||n===Po.THEAD||n===Po.TR||n===Po.TD||n===Po.TH?e.openElements.hasInTableScope(n)&&(e.openElements.popUntilTagNamePopped(Po.SELECT),e._resetInsertionMode(),e.onEndTag(t)):Ol(e,t)}(this,e);break;case Rc.IN_TEMPLATE:!function(e,t){t.tagID===Po.TEMPLATE&&$c(e,t)}(this,e);break;case Rc.AFTER_BODY:Ll(this,e);break;case Rc.IN_FRAMESET:!function(e,t){t.tagID!==Po.FRAMESET||e.openElements.isRootHtmlElementCurrent()||(e.openElements.pop(),e.fragmentContext||e.openElements.currentTagId===Po.FRAMESET||(e.insertionMode=Rc.AFTER_FRAMESET))}(this,e);break;case Rc.AFTER_FRAMESET:!function(e,t){t.tagID===Po.HTML&&(e.insertionMode=Rc.AFTER_AFTER_FRAMESET)}(this,e);break;case Rc.AFTER_AFTER_BODY:vl(this,e)}}onEof(e){switch(this.insertionMode){case Rc.INITIAL:Vc(this,e);break;case Rc.BEFORE_HTML:Wc(this,e);break;case Rc.BEFORE_HEAD:Qc(this,e);break;case Rc.IN_HEAD:zc(this,e);break;case Rc.IN_HEAD_NO_SCRIPT:Jc(this,e);break;case Rc.AFTER_HEAD:Zc(this,e);break;case Rc.IN_BODY:case Rc.IN_TABLE:case Rc.IN_CAPTION:case Rc.IN_COLUMN_GROUP:case Rc.IN_TABLE_BODY:case Rc.IN_ROW:case Rc.IN_CELL:case Rc.IN_SELECT:case Rc.IN_SELECT_IN_TABLE:hl(this,e);break;case Rc.TEXT:!function(e,t){e._err(t,Eo.eofInElementThatCanContainOnlyText),e.openElements.pop(),e.insertionMode=e.originalInsertionMode,e.onEof(t)}(this,e);break;case Rc.IN_TABLE_TEXT:Tl(this,e);break;case Rc.IN_TEMPLATE:yl(this,e);break;case Rc.AFTER_BODY:case Rc.IN_FRAMESET:case Rc.AFTER_FRAMESET:case Rc.AFTER_AFTER_BODY:case Rc.AFTER_AFTER_FRAMESET:jc(this,e)}}onWhitespaceCharacter(e){if(this.skipNextNewLine&&(this.skipNextNewLine=!1,e.chars.charCodeAt(0)===io.LINE_FEED)){if(1===e.chars.length)return;e.chars=e.chars.substr(1)}if(this.tokenizer.inForeignNode)this._insertCharacters(e);else switch(this.insertionMode){case Rc.IN_HEAD:case Rc.IN_HEAD_NO_SCRIPT:case Rc.AFTER_HEAD:case Rc.TEXT:case Rc.IN_COLUMN_GROUP:case Rc.IN_SELECT:case Rc.IN_SELECT_IN_TABLE:case Rc.IN_FRAMESET:case Rc.AFTER_FRAMESET:this._insertCharacters(e);break;case Rc.IN_BODY:case Rc.IN_CAPTION:case Rc.IN_CELL:case Rc.IN_TEMPLATE:case Rc.AFTER_BODY:case Rc.AFTER_AFTER_BODY:case Rc.AFTER_AFTER_FRAMESET:tl(this,e);break;case Rc.IN_TABLE:case Rc.IN_TABLE_BODY:case Rc.IN_ROW:ul(this,e);break;case Rc.IN_TABLE_TEXT:El(this,e)}}}function Bc(e,t){let n=e.activeFormattingElements.getElementEntryInScopeWithTagName(t.tagName);return n?e.openElements.contains(n.element)?e.openElements.hasInScope(t.tagID)||(n=null):(e.activeFormattingElements.removeEntry(n),n=null):cl(e,t),n}function Fc(e,t){let n=null,r=e.openElements.stackTop;for(;r>=0;r--){const i=e.openElements.items[r];if(i===t.element)break;e._isSpecialElement(i,e.openElements.tagIDs[r])&&(n=i)}return n||(e.openElements.shortenToLength(r<0?0:r),e.activeFormattingElements.removeEntry(t)),n}function Uc(e,t,n){let r=t,i=e.openElements.getCommonAncestor(t);for(let s=0,a=i;a!==n;s++,a=i){i=e.openElements.getCommonAncestor(a);const n=e.activeFormattingElements.getElementEntry(a),o=n&&s>=3;!n||o?(o&&e.activeFormattingElements.removeEntry(n),e.openElements.remove(a)):(a=Hc(e,n),r===t&&(e.activeFormattingElements.bookmark=n),e.treeAdapter.detachNode(r),e.treeAdapter.appendChild(a,r),r=a)}return r}function Hc(e,t){const n=e.treeAdapter.getNamespaceURI(t.element),r=e.treeAdapter.createElement(t.token.tagName,n,t.token.attrs);return e.openElements.replace(t.element,r),t.element=r,r}function Gc(e,t,n){const r=Fo(e.treeAdapter.getTagName(t));if(e._isElementCausesFosterParenting(r))e._fosterParentElement(n);else{const i=e.treeAdapter.getNamespaceURI(t);r===Po.TEMPLATE&&i===vo.HTML&&(t=e.treeAdapter.getTemplateContent(t)),e.treeAdapter.appendChild(t,n)}}function Yc(e,t,n){const r=e.treeAdapter.getNamespaceURI(n.element),{token:i}=n,s=e.treeAdapter.createElement(i.tagName,r,i.attrs);e._adoptNodes(t,s),e.treeAdapter.appendChild(t,s),e.activeFormattingElements.insertElementAfterBookmark(s,i),e.activeFormattingElements.removeEntry(n),e.openElements.remove(n.element),e.openElements.insertAfter(t,s,i.tagID)}function qc(e,t){for(let n=0;n<8;n++){const n=Bc(e,t);if(!n)break;const r=Fc(e,n);if(!r)break;e.activeFormattingElements.bookmark=n;const i=Uc(e,r,n.element),s=e.openElements.getCommonAncestor(n.element);e.treeAdapter.detachNode(i),s&&Gc(e,s,i),Yc(e,r,n)}}function Kc(e,t){e._appendCommentNode(t,e.openElements.currentTmplContentOrNode)}function jc(e,t){if(e.stopped=!0,t.location){const n=e.fragmentContext?0:2;for(let r=e.openElements.stackTop;r>=n;r--)e._setEndLocation(e.openElements.items[r],t);if(!e.fragmentContext&&e.openElements.stackTop>=0){const n=e.openElements.items[0],r=e.treeAdapter.getNodeSourceCodeLocation(n);if(r&&!r.endTag&&(e._setEndLocation(n,t),e.openElements.stackTop>=1)){const n=e.openElements.items[1],r=e.treeAdapter.getNodeSourceCodeLocation(n);r&&!r.endTag&&e._setEndLocation(n,t)}}}}function Vc(e,t){e._err(t,Eo.missingDoctype,!0),e.treeAdapter.setDocumentMode(e.document,Ro.QUIRKS),e.insertionMode=Rc.BEFORE_HTML,e._processToken(t)}function Wc(e,t){e._insertFakeRootElement(),e.insertionMode=Rc.BEFORE_HEAD,e._processToken(t)}function Qc(e,t){e._insertFakeElement(Mo.HEAD,Po.HEAD),e.headElement=e.openElements.current,e.insertionMode=Rc.IN_HEAD,e._processToken(t)}function Xc(e,t){switch(t.tagID){case Po.HTML:ol(e,t);break;case Po.BASE:case Po.BASEFONT:case Po.BGSOUND:case Po.LINK:case Po.META:e._appendElement(t,vo.HTML),t.ackSelfClosing=!0;break;case Po.TITLE:e._switchToTextParsing(t,jo.RCDATA);break;case Po.NOSCRIPT:e.options.scriptingEnabled?e._switchToTextParsing(t,jo.RAWTEXT):(e._insertElement(t,vo.HTML),e.insertionMode=Rc.IN_HEAD_NO_SCRIPT);break;case Po.NOFRAMES:case Po.STYLE:e._switchToTextParsing(t,jo.RAWTEXT);break;case Po.SCRIPT:e._switchToTextParsing(t,jo.SCRIPT_DATA);break;case Po.TEMPLATE:e._insertTemplate(t),e.activeFormattingElements.insertMarker(),e.framesetOk=!1,e.insertionMode=Rc.IN_TEMPLATE,e.tmplInsertionModeStack.unshift(Rc.IN_TEMPLATE);break;case Po.HEAD:e._err(t,Eo.misplacedStartTagForHeadElement);break;default:zc(e,t)}}function $c(e,t){e.openElements.tmplCount>0?(e.openElements.generateImpliedEndTagsThoroughly(),e.openElements.currentTagId!==Po.TEMPLATE&&e._err(t,Eo.closingOfElementWithOpenChildElements),e.openElements.popUntilTagNamePopped(Po.TEMPLATE),e.activeFormattingElements.clearToLastMarker(),e.tmplInsertionModeStack.shift(),e._resetInsertionMode()):e._err(t,Eo.endTagWithoutMatchingOpenElement)}function zc(e,t){e.openElements.pop(),e.insertionMode=Rc.AFTER_HEAD,e._processToken(t)}function Jc(e,t){const n=t.type===To.EOF?Eo.openElementsLeftAfterEof:Eo.disallowedContentInNoscriptInHead;e._err(t,n),e.openElements.pop(),e.insertionMode=Rc.IN_HEAD,e._processToken(t)}function Zc(e,t){e._insertFakeElement(Mo.BODY,Po.BODY),e.insertionMode=Rc.IN_BODY,el(e,t)}function el(e,t){switch(t.type){case To.CHARACTER:nl(e,t);break;case To.WHITESPACE_CHARACTER:tl(e,t);break;case To.COMMENT:Kc(e,t);break;case To.START_TAG:ol(e,t);break;case To.END_TAG:ll(e,t);break;case To.EOF:hl(e,t)}}function tl(e,t){e._reconstructActiveFormattingElements(),e._insertCharacters(t)}function nl(e,t){e._reconstructActiveFormattingElements(),e._insertCharacters(t),e.framesetOk=!1}function rl(e,t){e._reconstructActiveFormattingElements(),e._appendElement(t,vo.HTML),e.framesetOk=!1,t.ackSelfClosing=!0}function il(e){const t=_o(e,Do.TYPE);return null!=t&&"hidden"===t.toLowerCase()}function sl(e,t){e._switchToTextParsing(t,jo.RAWTEXT)}function al(e,t){e._reconstructActiveFormattingElements(),e._insertElement(t,vo.HTML)}function ol(e,t){switch(t.tagID){case Po.I:case Po.S:case Po.B:case Po.U:case Po.EM:case Po.TT:case Po.BIG:case Po.CODE:case Po.FONT:case Po.SMALL:case Po.STRIKE:case Po.STRONG:!function(e,t){e._reconstructActiveFormattingElements(),e._insertElement(t,vo.HTML),e.activeFormattingElements.pushElement(e.openElements.current,t)}(e,t);break;case Po.A:!function(e,t){const n=e.activeFormattingElements.getElementEntryInScopeWithTagName(Mo.A);n&&(qc(e,t),e.openElements.remove(n.element),e.activeFormattingElements.removeEntry(n)),e._reconstructActiveFormattingElements(),e._insertElement(t,vo.HTML),e.activeFormattingElements.pushElement(e.openElements.current,t)}(e,t);break;case Po.H1:case Po.H2:case Po.H3:case Po.H4:case Po.H5:case Po.H6:!function(e,t){e.openElements.hasInButtonScope(Po.P)&&e._closePElement(),Go(e.openElements.currentTagId)&&e.openElements.pop(),e._insertElement(t,vo.HTML)}(e,t);break;case Po.P:case Po.DL:case Po.OL:case Po.UL:case Po.DIV:case Po.DIR:case Po.NAV:case Po.MAIN:case Po.MENU:case Po.ASIDE:case Po.CENTER:case Po.FIGURE:case Po.FOOTER:case Po.HEADER:case Po.HGROUP:case Po.DIALOG:case Po.DETAILS:case Po.ADDRESS:case Po.ARTICLE:case Po.SECTION:case Po.SUMMARY:case Po.FIELDSET:case Po.BLOCKQUOTE:case Po.FIGCAPTION:!function(e,t){e.openElements.hasInButtonScope(Po.P)&&e._closePElement(),e._insertElement(t,vo.HTML)}(e,t);break;case Po.LI:case Po.DD:case Po.DT:!function(e,t){e.framesetOk=!1;const n=t.tagID;for(let t=e.openElements.stackTop;t>=0;t--){const r=e.openElements.tagIDs[t];if(n===Po.LI&&r===Po.LI||(n===Po.DD||n===Po.DT)&&(r===Po.DD||r===Po.DT)){e.openElements.generateImpliedEndTagsWithExclusion(r),e.openElements.popUntilTagNamePopped(r);break}if(r!==Po.ADDRESS&&r!==Po.DIV&&r!==Po.P&&e._isSpecialElement(e.openElements.items[t],r))break}e.openElements.hasInButtonScope(Po.P)&&e._closePElement(),e._insertElement(t,vo.HTML)}(e,t);break;case Po.BR:case Po.IMG:case Po.WBR:case Po.AREA:case Po.EMBED:case Po.KEYGEN:rl(e,t);break;case Po.HR:!function(e,t){e.openElements.hasInButtonScope(Po.P)&&e._closePElement(),e._appendElement(t,vo.HTML),e.framesetOk=!1,t.ackSelfClosing=!0}(e,t);break;case Po.RB:case Po.RTC:!function(e,t){e.openElements.hasInScope(Po.RUBY)&&e.openElements.generateImpliedEndTags(),e._insertElement(t,vo.HTML)}(e,t);break;case Po.RT:case Po.RP:!function(e,t){e.openElements.hasInScope(Po.RUBY)&&e.openElements.generateImpliedEndTagsWithExclusion(Po.RTC),e._insertElement(t,vo.HTML)}(e,t);break;case Po.PRE:case Po.LISTING:!function(e,t){e.openElements.hasInButtonScope(Po.P)&&e._closePElement(),e._insertElement(t,vo.HTML),e.skipNextNewLine=!0,e.framesetOk=!1}(e,t);break;case Po.XMP:!function(e,t){e.openElements.hasInButtonScope(Po.P)&&e._closePElement(),e._reconstructActiveFormattingElements(),e.framesetOk=!1,e._switchToTextParsing(t,jo.RAWTEXT)}(e,t);break;case Po.SVG:!function(e,t){e._reconstructActiveFormattingElements(),kc(t),vc(t),t.selfClosing?e._appendElement(t,vo.SVG):e._insertElement(t,vo.SVG),t.ackSelfClosing=!0}(e,t);break;case Po.HTML:!function(e,t){0===e.openElements.tmplCount&&e.treeAdapter.adoptAttributes(e.openElements.items[0],t.attrs)}(e,t);break;case Po.BASE:case Po.LINK:case Po.META:case Po.STYLE:case Po.TITLE:case Po.SCRIPT:case Po.BGSOUND:case Po.BASEFONT:case Po.TEMPLATE:Xc(e,t);break;case Po.BODY:!function(e,t){const n=e.openElements.tryPeekProperlyNestedBodyElement();n&&0===e.openElements.tmplCount&&(e.framesetOk=!1,e.treeAdapter.adoptAttributes(n,t.attrs))}(e,t);break;case Po.FORM:!function(e,t){const n=e.openElements.tmplCount>0;e.formElement&&!n||(e.openElements.hasInButtonScope(Po.P)&&e._closePElement(),e._insertElement(t,vo.HTML),n||(e.formElement=e.openElements.current))}(e,t);break;case Po.NOBR:!function(e,t){e._reconstructActiveFormattingElements(),e.openElements.hasInScope(Po.NOBR)&&(qc(e,t),e._reconstructActiveFormattingElements()),e._insertElement(t,vo.HTML),e.activeFormattingElements.pushElement(e.openElements.current,t)}(e,t);break;case Po.MATH:!function(e,t){e._reconstructActiveFormattingElements(),Lc(t),vc(t),t.selfClosing?e._appendElement(t,vo.MATHML):e._insertElement(t,vo.MATHML),t.ackSelfClosing=!0}(e,t);break;case Po.TABLE:!function(e,t){e.treeAdapter.getDocumentMode(e.document)!==Ro.QUIRKS&&e.openElements.hasInButtonScope(Po.P)&&e._closePElement(),e._insertElement(t,vo.HTML),e.framesetOk=!1,e.insertionMode=Rc.IN_TABLE}(e,t);break;case Po.INPUT:!function(e,t){e._reconstructActiveFormattingElements(),e._appendElement(t,vo.HTML),il(t)||(e.framesetOk=!1),t.ackSelfClosing=!0}(e,t);break;case Po.PARAM:case Po.TRACK:case Po.SOURCE:!function(e,t){e._appendElement(t,vo.HTML),t.ackSelfClosing=!0}(e,t);break;case Po.IMAGE:!function(e,t){t.tagName=Mo.IMG,t.tagID=Po.IMG,rl(e,t)}(e,t);break;case Po.BUTTON:!function(e,t){e.openElements.hasInScope(Po.BUTTON)&&(e.openElements.generateImpliedEndTags(),e.openElements.popUntilTagNamePopped(Po.BUTTON)),e._reconstructActiveFormattingElements(),e._insertElement(t,vo.HTML),e.framesetOk=!1}(e,t);break;case Po.APPLET:case Po.OBJECT:case Po.MARQUEE:!function(e,t){e._reconstructActiveFormattingElements(),e._insertElement(t,vo.HTML),e.activeFormattingElements.insertMarker(),e.framesetOk=!1}(e,t);break;case Po.IFRAME:!function(e,t){e.framesetOk=!1,e._switchToTextParsing(t,jo.RAWTEXT)}(e,t);break;case Po.SELECT:!function(e,t){e._reconstructActiveFormattingElements(),e._insertElement(t,vo.HTML),e.framesetOk=!1,e.insertionMode=e.insertionMode===Rc.IN_TABLE||e.insertionMode===Rc.IN_CAPTION||e.insertionMode===Rc.IN_TABLE_BODY||e.insertionMode===Rc.IN_ROW||e.insertionMode===Rc.IN_CELL?Rc.IN_SELECT_IN_TABLE:Rc.IN_SELECT}(e,t);break;case Po.OPTION:case Po.OPTGROUP:!function(e,t){e.openElements.currentTagId===Po.OPTION&&e.openElements.pop(),e._reconstructActiveFormattingElements(),e._insertElement(t,vo.HTML)}(e,t);break;case Po.NOEMBED:sl(e,t);break;case Po.FRAMESET:!function(e,t){const n=e.openElements.tryPeekProperlyNestedBodyElement();e.framesetOk&&n&&(e.treeAdapter.detachNode(n),e.openElements.popAllUpToHtmlElement(),e._insertElement(t,vo.HTML),e.insertionMode=Rc.IN_FRAMESET)}(e,t);break;case Po.TEXTAREA:!function(e,t){e._insertElement(t,vo.HTML),e.skipNextNewLine=!0,e.tokenizer.state=jo.RCDATA,e.originalInsertionMode=e.insertionMode,e.framesetOk=!1,e.insertionMode=Rc.TEXT}(e,t);break;case Po.NOSCRIPT:e.options.scriptingEnabled?sl(e,t):al(e,t);break;case Po.PLAINTEXT:!function(e,t){e.openElements.hasInButtonScope(Po.P)&&e._closePElement(),e._insertElement(t,vo.HTML),e.tokenizer.state=jo.PLAINTEXT}(e,t);break;case Po.COL:case Po.TH:case Po.TD:case Po.TR:case Po.HEAD:case Po.FRAME:case Po.TBODY:case Po.TFOOT:case Po.THEAD:case Po.CAPTION:case Po.COLGROUP:break;default:al(e,t)}}function cl(e,t){const n=t.tagName,r=t.tagID;for(let t=e.openElements.stackTop;t>0;t--){const i=e.openElements.items[t],s=e.openElements.tagIDs[t];if(r===s&&(r!==Po.UNKNOWN||e.treeAdapter.getTagName(i)===n)){e.openElements.generateImpliedEndTagsWithExclusion(r),e.openElements.stackTop>=t&&e.openElements.shortenToLength(t);break}if(e._isSpecialElement(i,s))break}}function ll(e,t){switch(t.tagID){case Po.A:case Po.B:case Po.I:case Po.S:case Po.U:case Po.EM:case Po.TT:case Po.BIG:case Po.CODE:case Po.FONT:case Po.NOBR:case Po.SMALL:case Po.STRIKE:case Po.STRONG:qc(e,t);break;case Po.P:!function(e){e.openElements.hasInButtonScope(Po.P)||e._insertFakeElement(Mo.P,Po.P),e._closePElement()}(e);break;case Po.DL:case Po.UL:case Po.OL:case Po.DIR:case Po.DIV:case Po.NAV:case Po.PRE:case Po.MAIN:case Po.MENU:case Po.ASIDE:case Po.BUTTON:case Po.CENTER:case Po.FIGURE:case Po.FOOTER:case Po.HEADER:case Po.HGROUP:case Po.DIALOG:case Po.ADDRESS:case Po.ARTICLE:case Po.DETAILS:case Po.SECTION:case Po.SUMMARY:case Po.LISTING:case Po.FIELDSET:case Po.BLOCKQUOTE:case Po.FIGCAPTION:!function(e,t){const n=t.tagID;e.openElements.hasInScope(n)&&(e.openElements.generateImpliedEndTags(),e.openElements.popUntilTagNamePopped(n))}(e,t);break;case Po.LI:!function(e){e.openElements.hasInListItemScope(Po.LI)&&(e.openElements.generateImpliedEndTagsWithExclusion(Po.LI),e.openElements.popUntilTagNamePopped(Po.LI))}(e);break;case Po.DD:case Po.DT:!function(e,t){const n=t.tagID;e.openElements.hasInScope(n)&&(e.openElements.generateImpliedEndTagsWithExclusion(n),e.openElements.popUntilTagNamePopped(n))}(e,t);break;case Po.H1:case Po.H2:case Po.H3:case Po.H4:case Po.H5:case Po.H6:!function(e){e.openElements.hasNumberedHeaderInScope()&&(e.openElements.generateImpliedEndTags(),e.openElements.popUntilNumberedHeaderPopped())}(e);break;case Po.BR:!function(e){e._reconstructActiveFormattingElements(),e._insertFakeElement(Mo.BR,Po.BR),e.openElements.pop(),e.framesetOk=!1}(e);break;case Po.BODY:!function(e,t){if(e.openElements.hasInScope(Po.BODY)&&(e.insertionMode=Rc.AFTER_BODY,e.options.sourceCodeLocationInfo)){const n=e.openElements.tryPeekProperlyNestedBodyElement();n&&e._setEndLocation(n,t)}}(e,t);break;case Po.HTML:!function(e,t){e.openElements.hasInScope(Po.BODY)&&(e.insertionMode=Rc.AFTER_BODY,Ll(e,t))}(e,t);break;case Po.FORM:!function(e){const t=e.openElements.tmplCount>0,{formElement:n}=e;t||(e.formElement=null),(n||t)&&e.openElements.hasInScope(Po.FORM)&&(e.openElements.generateImpliedEndTags(),t?e.openElements.popUntilTagNamePopped(Po.FORM):n&&e.openElements.remove(n))}(e);break;case Po.APPLET:case Po.OBJECT:case Po.MARQUEE:!function(e,t){const n=t.tagID;e.openElements.hasInScope(n)&&(e.openElements.generateImpliedEndTags(),e.openElements.popUntilTagNamePopped(n),e.activeFormattingElements.clearToLastMarker())}(e,t);break;case Po.TEMPLATE:$c(e,t);break;default:cl(e,t)}}function hl(e,t){e.tmplInsertionModeStack.length>0?yl(e,t):jc(e,t)}function ul(e,t){if(Pc.has(e.openElements.currentTagId))switch(e.pendingCharacterTokens.length=0,e.hasNonWhitespacePendingCharacterToken=!1,e.originalInsertionMode=e.insertionMode,e.insertionMode=Rc.IN_TABLE_TEXT,t.type){case To.CHARACTER:ml(e,t);break;case To.WHITESPACE_CHARACTER:El(e,t)}else dl(e,t)}function pl(e,t){switch(t.tagID){case Po.TD:case Po.TH:case Po.TR:!function(e,t){e.openElements.clearBackToTableContext(),e._insertFakeElement(Mo.TBODY,Po.TBODY),e.insertionMode=Rc.IN_TABLE_BODY,Nl(e,t)}(e,t);break;case Po.STYLE:case Po.SCRIPT:case Po.TEMPLATE:Xc(e,t);break;case Po.COL:!function(e,t){e.openElements.clearBackToTableContext(),e._insertFakeElement(Mo.COLGROUP,Po.COLGROUP),e.insertionMode=Rc.IN_COLUMN_GROUP,Al(e,t)}(e,t);break;case Po.FORM:!function(e,t){e.formElement||0!==e.openElements.tmplCount||(e._insertElement(t,vo.HTML),e.formElement=e.openElements.current,e.openElements.pop())}(e,t);break;case Po.TABLE:!function(e,t){e.openElements.hasInTableScope(Po.TABLE)&&(e.openElements.popUntilTagNamePopped(Po.TABLE),e._resetInsertionMode(),e._processStartTag(t))}(e,t);break;case Po.TBODY:case Po.TFOOT:case Po.THEAD:!function(e,t){e.openElements.clearBackToTableContext(),e._insertElement(t,vo.HTML),e.insertionMode=Rc.IN_TABLE_BODY}(e,t);break;case Po.INPUT:!function(e,t){il(t)?e._appendElement(t,vo.HTML):dl(e,t),t.ackSelfClosing=!0}(e,t);break;case Po.CAPTION:!function(e,t){e.openElements.clearBackToTableContext(),e.activeFormattingElements.insertMarker(),e._insertElement(t,vo.HTML),e.insertionMode=Rc.IN_CAPTION}(e,t);break;case Po.COLGROUP:!function(e,t){e.openElements.clearBackToTableContext(),e._insertElement(t,vo.HTML),e.insertionMode=Rc.IN_COLUMN_GROUP}(e,t);break;default:dl(e,t)}}function fl(e,t){switch(t.tagID){case Po.TABLE:e.openElements.hasInTableScope(Po.TABLE)&&(e.openElements.popUntilTagNamePopped(Po.TABLE),e._resetInsertionMode());break;case Po.TEMPLATE:$c(e,t);break;case Po.BODY:case Po.CAPTION:case Po.COL:case Po.COLGROUP:case Po.HTML:case Po.TBODY:case Po.TD:case Po.TFOOT:case Po.TH:case Po.THEAD:case Po.TR:break;default:dl(e,t)}}function dl(e,t){const n=e.fosterParentingEnabled;e.fosterParentingEnabled=!0,el(e,t),e.fosterParentingEnabled=n}function El(e,t){e.pendingCharacterTokens.push(t)}function ml(e,t){e.pendingCharacterTokens.push(t),e.hasNonWhitespacePendingCharacterToken=!0}function Tl(e,t){let n=0;if(e.hasNonWhitespacePendingCharacterToken)for(;n0&&e.openElements.currentTagId===Po.OPTION&&e.openElements.tagIDs[e.openElements.stackTop-1]===Po.OPTGROUP&&e.openElements.pop(),e.openElements.currentTagId===Po.OPTGROUP&&e.openElements.pop();break;case Po.OPTION:e.openElements.currentTagId===Po.OPTION&&e.openElements.pop();break;case Po.SELECT:e.openElements.hasInSelectScope(Po.SELECT)&&(e.openElements.popUntilTagNamePopped(Po.SELECT),e._resetInsertionMode());break;case Po.TEMPLATE:$c(e,t)}}function yl(e,t){e.openElements.tmplCount>0?(e.openElements.popUntilTagNamePopped(Po.TEMPLATE),e.activeFormattingElements.clearToLastMarker(),e.tmplInsertionModeStack.shift(),e._resetInsertionMode(),e.onEof(t)):jc(e,t)}function Ll(e,t){var n;if(t.tagID===Po.HTML){if(e.fragmentContext||(e.insertionMode=Rc.AFTER_AFTER_BODY),e.options.sourceCodeLocationInfo&&e.openElements.tagIDs[0]===Po.HTML){e._setEndLocation(e.openElements.items[0],t);const r=e.openElements.items[1];r&&!(null===(n=e.treeAdapter.getNodeSourceCodeLocation(r))||void 0===n?void 0:n.endTag)&&e._setEndLocation(r,t)}}else kl(e,t)}function kl(e,t){e.insertionMode=Rc.IN_BODY,el(e,t)}function vl(e,t){e.insertionMode=Rc.IN_BODY,el(e,t)}function Dl(e){for(;e.treeAdapter.getNamespaceURI(e.openElements.current)!==vo.HTML&&!e._isIntegrationPoint(e.openElements.currentTagId,e.openElements.current);)e.openElements.pop()}var Rl=Ke((function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.escapeText=t.escapeAttribute=t.escapeUTF8=t.escape=t.encodeXML=t.getCodePoint=t.xmlReplacer=void 0,t.xmlReplacer=/["&'<>$\x80-\uFFFF]/g;var n=new Map([[34,"""],[38,"&"],[39,"'"],[60,"<"],[62,">"]]);function r(e){for(var r,i="",s=0;null!==(r=t.xmlReplacer.exec(e));){var a=r.index,o=e.charCodeAt(a),c=n.get(o);void 0!==c?(i+=e.substring(s,a)+c,s=a+1):(i+="".concat(e.substring(s,a),"&#x").concat((0,t.getCodePoint)(e,a).toString(16),";"),s=t.xmlReplacer.lastIndex+=Number(55296==(64512&o)))}return i+e.substr(s)}function i(e,t){return function(n){for(var r,i=0,s="";r=e.exec(n);)i!==r.index&&(s+=n.substring(i,r.index)),s+=t.get(r[0].charCodeAt(0)),i=r.index+1;return s+n.substring(i)}}t.getCodePoint=null!=String.prototype.codePointAt?function(e,t){return e.codePointAt(t)}:function(e,t){return 55296==(64512&e.charCodeAt(t))?1024*(e.charCodeAt(t)-55296)+e.charCodeAt(t+1)-56320+65536:e.charCodeAt(t)},t.encodeXML=r,t.escape=r,t.escapeUTF8=i(/[&<>'"]/g,n),t.escapeAttribute=i(/["&\u00A0]/g,new Map([[34,"""],[38,"&"],[160," "]])),t.escapeText=i(/[&<>\u00A0]/g,new Map([[38,"&"],[60,"<"],[62,">"],[160," "]]))}));qe(Rl);var Ml=Rl.escapeText,Pl=Rl.escapeAttribute;Rl.escapeUTF8,Rl.escape,Rl.encodeXML,Rl.getCodePoint,Rl.xmlReplacer;const xl=new Set([Mo.AREA,Mo.BASE,Mo.BASEFONT,Mo.BGSOUND,Mo.BR,Mo.COL,Mo.EMBED,Mo.FRAME,Mo.HR,Mo.IMG,Mo.INPUT,Mo.KEYGEN,Mo.LINK,Mo.META,Mo.PARAM,Mo.SOURCE,Mo.TRACK,Mo.WBR]);const wl={treeAdapter:Ec,scriptingEnabled:!0};function Bl(e,t){return Fl(e,{...wl,...t})}function Fl(e,t){return t.treeAdapter.isElementNode(e)?function(e,t){const n=t.treeAdapter.getTagName(e);return`<${n}${function(e,{treeAdapter:t}){let n="";for(const r of t.getAttrList(e)){if(n+=" ",r.namespace)switch(r.namespace){case vo.XML:n+=`xml:${r.name}`;break;case vo.XMLNS:"xmlns"!==r.name&&(n+="xmlns:"),n+=r.name;break;case vo.XLINK:n+=`xlink:${r.name}`;break;default:n+=`${r.prefix}:${r.name}`}else n+=r.name;n+=`="${Pl(r.value)}"`}return n}(e,t)}>${function(e,t){return t.treeAdapter.isElementNode(e)&&t.treeAdapter.getNamespaceURI(e)===vo.HTML&&xl.has(t.treeAdapter.getTagName(e))}(e,t)?"":`${function(e,t){let n="";const r=t.treeAdapter.isElementNode(e)&&t.treeAdapter.getTagName(e)===Mo.TEMPLATE&&t.treeAdapter.getNamespaceURI(e)===vo.HTML?t.treeAdapter.getTemplateContent(e):e,i=t.treeAdapter.getChildNodes(r);if(i)for(const e of i)n+=Fl(e,t);return n}(e,t)}`}`}(e,t):t.treeAdapter.isTextNode(e)?function(e,t){const{treeAdapter:n}=t,r=n.getTextNodeContent(e),i=n.getParentNode(e),s=i&&n.isElementNode(i)&&n.getTagName(i);return s&&n.getNamespaceURI(i)===vo.HTML&&(a=s,o=t.scriptingEnabled,Yo.has(a)||o&&a===Mo.NOSCRIPT)?r:Ml(r);var a,o}(e,t):t.treeAdapter.isCommentNode(e)?function(e,{treeAdapter:t}){return`\x3c!--${t.getCommentNodeContent(e)}--\x3e`}(e,t):t.treeAdapter.isDocumentTypeNode(e)?function(e,{treeAdapter:t}){return``}(e,t):""}function Ul(e){return new m(e)}function Hl(e){const t=e.includes('"')?"'":'"';return t+e+t}const Gl={isCommentNode:O,isElementNode:I,isTextNode:b,createDocument(){const e=new N([]);return e["x-mode"]=Ro.NO_QUIRKS,e},createDocumentFragment:()=>new N([]),createElement(e,t,n){const r=Object.create(null),i=Object.create(null),s=Object.create(null);for(let e=0;enew T(e),appendChild(e,t){const n=e.children[e.children.length-1];n&&(n.next=t,t.prev=n),e.children.push(t),t.parent=e},insertBefore(e,t,n){const r=e.children.indexOf(n),{prev:i}=n;i&&(i.next=t,t.prev=i),n.prev=t,t.next=n,e.children.splice(r,0,t),t.parent=e},setTemplateContent(e,t){Gl.appendChild(e,t)},getTemplateContent:e=>e.children[0],setDocumentType(e,t,n,r){const i=function(e,t,n){let r="!DOCTYPE ";return e&&(r+=e),t?r+=` PUBLIC ${Hl(t)}`:n&&(r+=" SYSTEM"),n&&(r+=` ${Hl(n)}`),r}(t,n,r);let s=e.children.find((e=>y(e)&&"!doctype"===e.name));s?s.data=null!=i?i:null:(s=new _("!doctype",i),Gl.appendChild(e,s)),s["x-name"]=null!=t?t:void 0,s["x-publicId"]=null!=n?n:void 0,s["x-systemId"]=null!=r?r:void 0},setDocumentMode(e,t){e["x-mode"]=t},getDocumentMode:e=>e["x-mode"],detachNode(e){if(e.parent){const t=e.parent.children.indexOf(e),{prev:n,next:r}=e;e.prev=null,e.next=null,n&&(n.next=r),r&&(r.prev=n),e.parent.children.splice(t,1),e.parent=null}},insertText(e,t){const n=e.children[e.children.length-1];n&&b(n)?n.data+=t:Gl.appendChild(e,Ul(t))},insertTextBefore(e,t,n){const r=e.children[e.children.indexOf(n)-1];r&&b(r)?r.data+=t:Gl.insertBefore(e,Ul(t),n)},adoptAttributes(e,t){for(let n=0;ne.children[0],getChildNodes:e=>e.children,getParentNode:e=>e.parent,getAttrList:e=>e.attributes,getTagName:e=>e.name,getNamespaceURI:e=>e.namespace,getTextNodeContent:e=>e.data,getCommentNodeContent:e=>e.data,getDocumentTypeNodeName(e){var t;return null!==(t=e["x-name"])&&void 0!==t?t:""},getDocumentTypeNodePublicId(e){var t;return null!==(t=e["x-publicId"])&&void 0!==t?t:""},getDocumentTypeNodeSystemId(e){var t;return null!==(t=e["x-systemId"])&&void 0!==t?t:""},isDocumentTypeNode:e=>y(e)&&"!doctype"===e.name,setNodeSourceCodeLocation(e,t){t&&(e.startIndex=t.startOffset,e.endIndex=t.endOffset),e.sourceCodeLocation=t},getNodeSourceCodeLocation:e=>e.sourceCodeLocation,updateNodeSourceCodeLocation(e,t){null!=t.endOffset&&(e.endIndex=t.endOffset),e.sourceCodeLocation={...e.sourceCodeLocation,...t}}};var Yl=function(e,t,n){if(n||2===arguments.length)for(var r,i=0,s=t.length;i=Kl.Zero&&e<=Kl.Nine}!function(e){e[e.Tab=9]="Tab",e[e.NewLine=10]="NewLine",e[e.FormFeed=12]="FormFeed",e[e.CarriageReturn=13]="CarriageReturn",e[e.Space=32]="Space",e[e.ExclamationMark=33]="ExclamationMark",e[e.Num=35]="Num",e[e.Amp=38]="Amp",e[e.SingleQuote=39]="SingleQuote",e[e.DoubleQuote=34]="DoubleQuote",e[e.Dash=45]="Dash",e[e.Slash=47]="Slash",e[e.Zero=48]="Zero",e[e.Nine=57]="Nine",e[e.Semi=59]="Semi",e[e.Lt=60]="Lt",e[e.Eq=61]="Eq",e[e.Gt=62]="Gt",e[e.Questionmark=63]="Questionmark",e[e.UpperA=65]="UpperA",e[e.LowerA=97]="LowerA",e[e.UpperF=70]="UpperF",e[e.LowerF=102]="LowerF",e[e.UpperZ=90]="UpperZ",e[e.LowerZ=122]="LowerZ",e[e.LowerX=120]="LowerX",e[e.OpeningSquareBracket=91]="OpeningSquareBracket"}(Kl||(Kl={})),function(e){e[e.Text=1]="Text",e[e.BeforeTagName=2]="BeforeTagName",e[e.InTagName=3]="InTagName",e[e.InSelfClosingTag=4]="InSelfClosingTag",e[e.BeforeClosingTagName=5]="BeforeClosingTagName",e[e.InClosingTagName=6]="InClosingTagName",e[e.AfterClosingTagName=7]="AfterClosingTagName",e[e.BeforeAttributeName=8]="BeforeAttributeName",e[e.InAttributeName=9]="InAttributeName",e[e.AfterAttributeName=10]="AfterAttributeName",e[e.BeforeAttributeValue=11]="BeforeAttributeValue",e[e.InAttributeValueDq=12]="InAttributeValueDq",e[e.InAttributeValueSq=13]="InAttributeValueSq",e[e.InAttributeValueNq=14]="InAttributeValueNq",e[e.BeforeDeclaration=15]="BeforeDeclaration",e[e.InDeclaration=16]="InDeclaration",e[e.InProcessingInstruction=17]="InProcessingInstruction",e[e.BeforeComment=18]="BeforeComment",e[e.CDATASequence=19]="CDATASequence",e[e.InSpecialComment=20]="InSpecialComment",e[e.InCommentLike=21]="InCommentLike",e[e.BeforeSpecialS=22]="BeforeSpecialS",e[e.SpecialStartSequence=23]="SpecialStartSequence",e[e.InSpecialTag=24]="InSpecialTag",e[e.BeforeEntity=25]="BeforeEntity",e[e.BeforeNumericEntity=26]="BeforeNumericEntity",e[e.InNamedEntity=27]="InNamedEntity",e[e.InNumericEntity=28]="InNumericEntity",e[e.InHexEntity=29]="InHexEntity"}(jl||(jl={})),function(e){e[e.NoValue=0]="NoValue",e[e.Unquoted=1]="Unquoted",e[e.Single=2]="Single",e[e.Double=3]="Double"}(Vl||(Vl={}));const zl={Cdata:new Uint8Array([67,68,65,84,65,91]),CdataEnd:new Uint8Array([93,93,62]),CommentEnd:new Uint8Array([45,45,62]),ScriptEnd:new Uint8Array([60,47,115,99,114,105,112,116]),StyleEnd:new Uint8Array([60,47,115,116,121,108,101]),TitleEnd:new Uint8Array([60,47,116,105,116,108,101])};class Jl{constructor({xmlMode:e=!1,decodeEntities:t=!0},n){this.cbs=n,this.state=jl.Text,this.buffer="",this.sectionStart=0,this.index=0,this.baseState=jl.Text,this.isSpecial=!1,this.running=!0,this.offset=0,this.sequenceIndex=0,this.trieIndex=0,this.trieCurrent=0,this.entityResult=0,this.entityExcess=0,this.xmlMode=e,this.decodeEntities=t,this.entityTrie=e?xo:wo}reset(){this.state=jl.Text,this.buffer="",this.sectionStart=0,this.index=0,this.baseState=jl.Text,this.currentSequence=void 0,this.running=!0,this.offset=0}write(e){this.offset+=this.buffer.length,this.buffer=e,this.parse()}end(){this.running&&this.finish()}pause(){this.running=!1}resume(){this.running=!0,this.indexthis.sectionStart&&this.cbs.ontext(this.sectionStart,this.index),this.state=jl.BeforeTagName,this.sectionStart=this.index):this.decodeEntities&&e===Kl.Amp&&(this.state=jl.BeforeEntity)}stateSpecialStartSequence(e){const t=this.sequenceIndex===this.currentSequence.length;if(t?Xl(e):(32|e)===this.currentSequence[this.sequenceIndex]){if(!t)return void this.sequenceIndex++}else this.isSpecial=!1;this.sequenceIndex=0,this.state=jl.InTagName,this.stateInTagName(e)}stateInSpecialTag(e){if(this.sequenceIndex===this.currentSequence.length){if(e===Kl.Gt||Ql(e)){const t=this.index-this.currentSequence.length;if(this.sectionStart=Kl.LowerA&&e<=Kl.LowerZ||e>=Kl.UpperA&&e<=Kl.UpperZ}(e)}startSpecial(e,t){this.isSpecial=!0,this.currentSequence=e,this.sequenceIndex=t,this.state=jl.SpecialStartSequence}stateBeforeTagName(e){if(e===Kl.ExclamationMark)this.state=jl.BeforeDeclaration,this.sectionStart=this.index+1;else if(e===Kl.Questionmark)this.state=jl.InProcessingInstruction,this.sectionStart=this.index+1;else if(this.isTagStartChar(e)){const t=32|e;this.sectionStart=this.index,this.xmlMode||t!==zl.TitleEnd[2]?this.state=this.xmlMode||t!==zl.ScriptEnd[2]?jl.InTagName:jl.BeforeSpecialS:this.startSpecial(zl.TitleEnd,3)}else e===Kl.Slash?this.state=jl.BeforeClosingTagName:(this.state=jl.Text,this.stateText(e))}stateInTagName(e){Xl(e)&&(this.cbs.onopentagname(this.sectionStart,this.index),this.sectionStart=-1,this.state=jl.BeforeAttributeName,this.stateBeforeAttributeName(e))}stateBeforeClosingTagName(e){Ql(e)||(e===Kl.Gt?this.state=jl.Text:(this.state=this.isTagStartChar(e)?jl.InClosingTagName:jl.InSpecialComment,this.sectionStart=this.index))}stateInClosingTagName(e){(e===Kl.Gt||Ql(e))&&(this.cbs.onclosetag(this.sectionStart,this.index),this.sectionStart=-1,this.state=jl.AfterClosingTagName,this.stateAfterClosingTagName(e))}stateAfterClosingTagName(e){(e===Kl.Gt||this.fastForwardTo(Kl.Gt))&&(this.state=jl.Text,this.sectionStart=this.index+1)}stateBeforeAttributeName(e){e===Kl.Gt?(this.cbs.onopentagend(this.index),this.isSpecial?(this.state=jl.InSpecialTag,this.sequenceIndex=0):this.state=jl.Text,this.baseState=this.state,this.sectionStart=this.index+1):e===Kl.Slash?this.state=jl.InSelfClosingTag:Ql(e)||(this.state=jl.InAttributeName,this.sectionStart=this.index)}stateInSelfClosingTag(e){e===Kl.Gt?(this.cbs.onselfclosingtag(this.index),this.state=jl.Text,this.baseState=jl.Text,this.sectionStart=this.index+1,this.isSpecial=!1):Ql(e)||(this.state=jl.BeforeAttributeName,this.stateBeforeAttributeName(e))}stateInAttributeName(e){(e===Kl.Eq||Xl(e))&&(this.cbs.onattribname(this.sectionStart,this.index),this.sectionStart=-1,this.state=jl.AfterAttributeName,this.stateAfterAttributeName(e))}stateAfterAttributeName(e){e===Kl.Eq?this.state=jl.BeforeAttributeValue:e===Kl.Slash||e===Kl.Gt?(this.cbs.onattribend(Vl.NoValue,this.index),this.state=jl.BeforeAttributeName,this.stateBeforeAttributeName(e)):Ql(e)||(this.cbs.onattribend(Vl.NoValue,this.index),this.state=jl.InAttributeName,this.sectionStart=this.index)}stateBeforeAttributeValue(e){e===Kl.DoubleQuote?(this.state=jl.InAttributeValueDq,this.sectionStart=this.index+1):e===Kl.SingleQuote?(this.state=jl.InAttributeValueSq,this.sectionStart=this.index+1):Ql(e)||(this.sectionStart=this.index,this.state=jl.InAttributeValueNq,this.stateInAttributeValueNoQuotes(e))}handleInAttributeValue(e,t){e===t||!this.decodeEntities&&this.fastForwardTo(t)?(this.cbs.onattribdata(this.sectionStart,this.index),this.sectionStart=-1,this.cbs.onattribend(t===Kl.DoubleQuote?Vl.Double:Vl.Single,this.index),this.state=jl.BeforeAttributeName):this.decodeEntities&&e===Kl.Amp&&(this.baseState=this.state,this.state=jl.BeforeEntity)}stateInAttributeValueDoubleQuotes(e){this.handleInAttributeValue(e,Kl.DoubleQuote)}stateInAttributeValueSingleQuotes(e){this.handleInAttributeValue(e,Kl.SingleQuote)}stateInAttributeValueNoQuotes(e){Ql(e)||e===Kl.Gt?(this.cbs.onattribdata(this.sectionStart,this.index),this.sectionStart=-1,this.cbs.onattribend(Vl.Unquoted,this.index),this.state=jl.BeforeAttributeName,this.stateBeforeAttributeName(e)):this.decodeEntities&&e===Kl.Amp&&(this.baseState=this.state,this.state=jl.BeforeEntity)}stateBeforeDeclaration(e){e===Kl.OpeningSquareBracket?(this.state=jl.CDATASequence,this.sequenceIndex=0):this.state=e===Kl.Dash?jl.BeforeComment:jl.InDeclaration}stateInDeclaration(e){(e===Kl.Gt||this.fastForwardTo(Kl.Gt))&&(this.cbs.ondeclaration(this.sectionStart,this.index),this.state=jl.Text,this.sectionStart=this.index+1)}stateInProcessingInstruction(e){(e===Kl.Gt||this.fastForwardTo(Kl.Gt))&&(this.cbs.onprocessinginstruction(this.sectionStart,this.index),this.state=jl.Text,this.sectionStart=this.index+1)}stateBeforeComment(e){e===Kl.Dash?(this.state=jl.InCommentLike,this.currentSequence=zl.CommentEnd,this.sequenceIndex=2,this.sectionStart=this.index+1):this.state=jl.InDeclaration}stateInSpecialComment(e){(e===Kl.Gt||this.fastForwardTo(Kl.Gt))&&(this.cbs.oncomment(this.sectionStart,this.index,0),this.state=jl.Text,this.sectionStart=this.index+1)}stateBeforeSpecialS(e){const t=32|e;t===zl.ScriptEnd[3]?this.startSpecial(zl.ScriptEnd,4):t===zl.StyleEnd[3]?this.startSpecial(zl.StyleEnd,4):(this.state=jl.InTagName,this.stateInTagName(e))}stateBeforeEntity(e){this.entityExcess=1,this.entityResult=0,e===Kl.Num?this.state=jl.BeforeNumericEntity:e===Kl.Amp||(this.trieIndex=0,this.trieCurrent=this.entityTrie[0],this.state=jl.InNamedEntity,this.stateInNamedEntity(e))}stateInNamedEntity(e){if(this.entityExcess+=1,this.trieIndex=Oo(this.entityTrie,this.trieCurrent,this.trieIndex+1,e),this.trieIndex<0)return this.emitNamedEntity(),void this.index--;this.trieCurrent=this.entityTrie[this.trieIndex];const t=this.trieCurrent&yo.VALUE_LENGTH;if(t){const n=(t>>14)-1;if(this.allowLegacyEntity()||e===Kl.Semi){const e=this.index-this.entityExcess+1;e>this.sectionStart&&this.emitPartial(this.sectionStart,e),this.entityResult=this.trieIndex,this.trieIndex+=n,this.entityExcess=0,this.sectionStart=this.index+1,0===n&&this.emitNamedEntity()}else this.trieIndex+=n}}emitNamedEntity(){if(this.state=this.baseState,0===this.entityResult)return;switch((this.entityTrie[this.entityResult]&yo.VALUE_LENGTH)>>14){case 1:this.emitCodePoint(this.entityTrie[this.entityResult]&~yo.VALUE_LENGTH);break;case 2:this.emitCodePoint(this.entityTrie[this.entityResult+1]);break;case 3:this.emitCodePoint(this.entityTrie[this.entityResult+1]),this.emitCodePoint(this.entityTrie[this.entityResult+2])}}stateBeforeNumericEntity(e){(32|e)===Kl.LowerX?(this.entityExcess++,this.state=jl.InHexEntity):(this.state=jl.InNumericEntity,this.stateInNumericEntity(e))}emitNumericEntity(e){const t=this.index-this.entityExcess-1;t+2+Number(this.state===jl.InHexEntity)!==this.index&&(t>this.sectionStart&&this.emitPartial(this.sectionStart,t),this.sectionStart=this.index+Number(e),this.emitCodePoint(ko(this.entityResult))),this.state=this.baseState}stateInNumericEntity(e){e===Kl.Semi?this.emitNumericEntity(!0):$l(e)?(this.entityResult=10*this.entityResult+(e-Kl.Zero),this.entityExcess++):(this.allowLegacyEntity()?this.emitNumericEntity(!1):this.state=this.baseState,this.index--)}stateInHexEntity(e){e===Kl.Semi?this.emitNumericEntity(!0):$l(e)?(this.entityResult=16*this.entityResult+(e-Kl.Zero),this.entityExcess++):!function(e){return e>=Kl.UpperA&&e<=Kl.UpperF||e>=Kl.LowerA&&e<=Kl.LowerF}(e)?(this.allowLegacyEntity()?this.emitNumericEntity(!1):this.state=this.baseState,this.index--):(this.entityResult=16*this.entityResult+((32|e)-Kl.LowerA+10),this.entityExcess++)}allowLegacyEntity(){return!this.xmlMode&&(this.baseState===jl.Text||this.baseState===jl.InSpecialTag)}cleanup(){this.running&&this.sectionStart!==this.index&&(this.state===jl.Text||this.state===jl.InSpecialTag&&0===this.sequenceIndex?(this.cbs.ontext(this.sectionStart,this.index),this.sectionStart=this.index):this.state!==jl.InAttributeValueDq&&this.state!==jl.InAttributeValueSq&&this.state!==jl.InAttributeValueNq||(this.cbs.onattribdata(this.sectionStart,this.index),this.sectionStart=this.index))}shouldContinue(){return this.index0&&s.has(this.stack[this.stack.length-1]);){const e=this.stack.pop();null===(n=(t=this.cbs).onclosetag)||void 0===n||n.call(t,e,!0)}this.isVoidElement(e)||(this.stack.push(e),ah.has(e)?this.foreignContext.push(!0):oh.has(e)&&this.foreignContext.push(!1)),null===(i=(r=this.cbs).onopentagname)||void 0===i||i.call(r,e),this.cbs.onopentag&&(this.attribs={})}endOpenTag(e){var t,n;this.startIndex=this.openTagStart,this.attribs&&(null===(n=(t=this.cbs).onopentag)||void 0===n||n.call(t,this.tagname,this.attribs,e),this.attribs=null),this.cbs.onclosetag&&this.isVoidElement(this.tagname)&&this.cbs.onclosetag(this.tagname,!0),this.tagname=""}onopentagend(e){this.endIndex=e,this.endOpenTag(!1),this.startIndex=e+1}onclosetag(e,t){var n,r,i,s,a,o;this.endIndex=t;let c=this.getSlice(e,t);if(this.lowerCaseTagNames&&(c=c.toLowerCase()),(ah.has(c)||oh.has(c))&&this.foreignContext.pop(),this.isVoidElement(c))this.options.xmlMode||"br"!==c||(null===(r=(n=this.cbs).onopentagname)||void 0===r||r.call(n,"br"),null===(s=(i=this.cbs).onopentag)||void 0===s||s.call(i,"br",{},!0),null===(o=(a=this.cbs).onclosetag)||void 0===o||o.call(a,"br",!1));else{const e=this.stack.lastIndexOf(c);if(-1!==e)if(this.cbs.onclosetag){let t=this.stack.length-e;for(;t--;)this.cbs.onclosetag(this.stack.pop(),0!==t)}else this.stack.length=e;else this.options.xmlMode||"p"!==c||(this.emitOpenTag("p"),this.closeCurrentTag(!0))}this.startIndex=t+1}onselfclosingtag(e){this.endIndex=e,this.options.xmlMode||this.options.recognizeSelfClosing||this.foreignContext[this.foreignContext.length-1]?(this.closeCurrentTag(!1),this.startIndex=e+1):this.onopentagend(e)}closeCurrentTag(e){var t,n;const r=this.tagname;this.endOpenTag(e),this.stack[this.stack.length-1]===r&&(null===(n=(t=this.cbs).onclosetag)||void 0===n||n.call(t,r,!e),this.stack.pop())}onattribname(e,t){this.startIndex=e;const n=this.getSlice(e,t);this.attribname=this.lowerCaseAttributeNames?n.toLowerCase():n}onattribdata(e,t){this.attribvalue+=this.getSlice(e,t)}onattribentity(e){this.attribvalue+=Lo(e)}onattribend(e,t){var n,r;this.endIndex=t,null===(r=(n=this.cbs).onattribute)||void 0===r||r.call(n,this.attribname,this.attribvalue,e===Vl.Double?'"':e===Vl.Single?"'":e===Vl.NoValue?void 0:null),this.attribs&&!Object.prototype.hasOwnProperty.call(this.attribs,this.attribname)&&(this.attribs[this.attribname]=this.attribvalue),this.attribvalue=""}getInstructionName(e){const t=e.search(ch);let n=t<0?e:e.substr(0,t);return this.lowerCaseTagNames&&(n=n.toLowerCase()),n}ondeclaration(e,t){this.endIndex=t;const n=this.getSlice(e,t);if(this.cbs.onprocessinginstruction){const e=this.getInstructionName(n);this.cbs.onprocessinginstruction(`!${e}`,`!${n}`)}this.startIndex=t+1}onprocessinginstruction(e,t){this.endIndex=t;const n=this.getSlice(e,t);if(this.cbs.onprocessinginstruction){const e=this.getInstructionName(n);this.cbs.onprocessinginstruction(`?${e}`,`?${n}`)}this.startIndex=t+1}oncomment(e,t,n){var r,i,s,a;this.endIndex=t,null===(i=(r=this.cbs).oncomment)||void 0===i||i.call(r,this.getSlice(e,t-n)),null===(a=(s=this.cbs).oncommentend)||void 0===a||a.call(s),this.startIndex=t+1}oncdata(e,t,n){var r,i,s,a,o,c,l,h,u,p;this.endIndex=t;const f=this.getSlice(e,t-n);this.options.xmlMode||this.options.recognizeCDATA?(null===(i=(r=this.cbs).oncdatastart)||void 0===i||i.call(r),null===(a=(s=this.cbs).ontext)||void 0===a||a.call(s,f),null===(c=(o=this.cbs).oncdataend)||void 0===c||c.call(o)):(null===(h=(l=this.cbs).oncomment)||void 0===h||h.call(l,`[CDATA[${f}]]`),null===(p=(u=this.cbs).oncommentend)||void 0===p||p.call(u)),this.startIndex=t+1}onend(){var e,t;if(this.cbs.onclosetag){this.endIndex=this.startIndex;for(let e=this.stack.length;e>0;this.cbs.onclosetag(this.stack[--e],!0));}null===(t=(e=this.cbs).onend)||void 0===t||t.call(e)}reset(){var e,t,n,r;null===(t=(e=this.cbs).onreset)||void 0===t||t.call(e),this.tokenizer.reset(),this.tagname="",this.attribname="",this.attribs=null,this.stack.length=0,this.startIndex=0,this.endIndex=0,null===(r=(n=this.cbs).onparserinit)||void 0===r||r.call(n,this),this.buffers.length=0,this.bufferOffset=0,this.writeIndex=0,this.ended=!1}parseComplete(e){this.reset(),this.end(e)}getSlice(e,t){for(;e-this.bufferOffset>=this.buffers[0].length;)this.shiftBuffer();let n=this.buffers[0].slice(e-this.bufferOffset,t-this.bufferOffset);for(;t-this.bufferOffset>this.buffers[0].length;)this.shiftBuffer(),n+=this.buffers[0].slice(0,t-this.bufferOffset);return n}shiftBuffer(){this.bufferOffset+=this.buffers[0].length,this.writeIndex--,this.buffers.shift()}write(e){var t,n;this.ended?null===(n=(t=this.cbs).onerror)||void 0===n||n.call(t,new Error(".write() after done!")):(this.buffers.push(e),this.tokenizer.running&&(this.tokenizer.write(e),this.writeIndex++))}end(e){var t,n;this.ended?null===(n=(t=this.cbs).onerror)||void 0===n||n.call(t,Error(".end() after done!")):(e&&this.write(e),this.ended=!0,this.tokenizer.end())}pause(){this.tokenizer.pause()}resume(){for(this.tokenizer.resume();this.tokenizer.running&&this.writeIndex>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + thisWords[(thisSigBytes + i) >>> 2] |= thatByte << (24 - ((thisSigBytes + i) % 4) * 8); + } + } else { + // Copy one word at a time + for (var j = 0; j < thatSigBytes; j += 4) { + thisWords[(thisSigBytes + j) >>> 2] = thatWords[j >>> 2]; + } + } + this.sigBytes += thatSigBytes; + + // Chainable + return this; + }, + + /** + * Removes insignificant bits. + * + * @example + * + * wordArray.clamp(); + */ + clamp: function () { + // Shortcuts + var words = this.words; + var sigBytes = this.sigBytes; + + // Clamp + words[sigBytes >>> 2] &= 0xffffffff << (32 - (sigBytes % 4) * 8); + words.length = Math.ceil(sigBytes / 4); + }, + + /** + * Creates a copy of this word array. + * + * @return {WordArray} The clone. + * + * @example + * + * var clone = wordArray.clone(); + */ + clone: function () { + var clone = Base.clone.call(this); + clone.words = this.words.slice(0); + + return clone; + }, + + /** + * Creates a word array filled with random bytes. + * + * @param {number} nBytes The number of random bytes to generate. + * + * @return {WordArray} The random word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.lib.WordArray.random(16); + */ + random: function (nBytes) { + var words = []; + + for (var i = 0; i < nBytes; i += 4) { + words.push(cryptoSecureRandomInt()); + } + + return new WordArray.init(words, nBytes); + } + }); + + /** + * Encoder namespace. + */ + var C_enc = C.enc = {}; + + /** + * Hex encoding strategy. + */ + var Hex = C_enc.Hex = { + /** + * Converts a word array to a hex string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The hex string. + * + * @static + * + * @example + * + * var hexString = CryptoJS.enc.Hex.stringify(wordArray); + */ + stringify: function (wordArray) { + // Shortcuts + var words = wordArray.words; + var sigBytes = wordArray.sigBytes; + + // Convert + var hexChars = []; + for (var i = 0; i < sigBytes; i++) { + var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + hexChars.push((bite >>> 4).toString(16)); + hexChars.push((bite & 0x0f).toString(16)); + } + + return hexChars.join(''); + }, + + /** + * Converts a hex string to a word array. + * + * @param {string} hexStr The hex string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Hex.parse(hexString); + */ + parse: function (hexStr) { + // Shortcut + var hexStrLength = hexStr.length; + + // Convert + var words = []; + for (var i = 0; i < hexStrLength; i += 2) { + words[i >>> 3] |= parseInt(hexStr.substr(i, 2), 16) << (24 - (i % 8) * 4); + } + + return new WordArray.init(words, hexStrLength / 2); + } + }; + + /** + * Latin1 encoding strategy. + */ + var Latin1 = C_enc.Latin1 = { + /** + * Converts a word array to a Latin1 string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The Latin1 string. + * + * @static + * + * @example + * + * var latin1String = CryptoJS.enc.Latin1.stringify(wordArray); + */ + stringify: function (wordArray) { + // Shortcuts + var words = wordArray.words; + var sigBytes = wordArray.sigBytes; + + // Convert + var latin1Chars = []; + for (var i = 0; i < sigBytes; i++) { + var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + latin1Chars.push(String.fromCharCode(bite)); + } + + return latin1Chars.join(''); + }, + + /** + * Converts a Latin1 string to a word array. + * + * @param {string} latin1Str The Latin1 string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Latin1.parse(latin1String); + */ + parse: function (latin1Str) { + // Shortcut + var latin1StrLength = latin1Str.length; + + // Convert + var words = []; + for (var i = 0; i < latin1StrLength; i++) { + words[i >>> 2] |= (latin1Str.charCodeAt(i) & 0xff) << (24 - (i % 4) * 8); + } + + return new WordArray.init(words, latin1StrLength); + } + }; + + /** + * UTF-8 encoding strategy. + */ + var Utf8 = C_enc.Utf8 = { + /** + * Converts a word array to a UTF-8 string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The UTF-8 string. + * + * @static + * + * @example + * + * var utf8String = CryptoJS.enc.Utf8.stringify(wordArray); + */ + stringify: function (wordArray) { + try { + return decodeURIComponent(escape(Latin1.stringify(wordArray))); + } catch (e) { + throw new Error('Malformed UTF-8 data'); + } + }, + + /** + * Converts a UTF-8 string to a word array. + * + * @param {string} utf8Str The UTF-8 string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Utf8.parse(utf8String); + */ + parse: function (utf8Str) { + return Latin1.parse(unescape(encodeURIComponent(utf8Str))); + } + }; + + /** + * Abstract buffered block algorithm template. + * + * The property blockSize must be implemented in a concrete subtype. + * + * @property {number} _minBufferSize The number of blocks that should be kept unprocessed in the buffer. Default: 0 + */ + var BufferedBlockAlgorithm = C_lib.BufferedBlockAlgorithm = Base.extend({ + /** + * Resets this block algorithm's data buffer to its initial state. + * + * @example + * + * bufferedBlockAlgorithm.reset(); + */ + reset: function () { + // Initial values + this._data = new WordArray.init(); + this._nDataBytes = 0; + }, + + /** + * Adds new data to this block algorithm's buffer. + * + * @param {WordArray|string} data The data to append. Strings are converted to a WordArray using UTF-8. + * + * @example + * + * bufferedBlockAlgorithm._append('data'); + * bufferedBlockAlgorithm._append(wordArray); + */ + _append: function (data) { + // Convert string to WordArray, else assume WordArray already + if (typeof data == 'string') { + data = Utf8.parse(data); + } + + // Append + this._data.concat(data); + this._nDataBytes += data.sigBytes; + }, + + /** + * Processes available data blocks. + * + * This method invokes _doProcessBlock(offset), which must be implemented by a concrete subtype. + * + * @param {boolean} doFlush Whether all blocks and partial blocks should be processed. + * + * @return {WordArray} The processed data. + * + * @example + * + * var processedData = bufferedBlockAlgorithm._process(); + * var processedData = bufferedBlockAlgorithm._process(!!'flush'); + */ + _process: function (doFlush) { + var processedWords; + + // Shortcuts + var data = this._data; + var dataWords = data.words; + var dataSigBytes = data.sigBytes; + var blockSize = this.blockSize; + var blockSizeBytes = blockSize * 4; + + // Count blocks ready + var nBlocksReady = dataSigBytes / blockSizeBytes; + if (doFlush) { + // Round up to include partial blocks + nBlocksReady = Math.ceil(nBlocksReady); + } else { + // Round down to include only full blocks, + // less the number of blocks that must remain in the buffer + nBlocksReady = Math.max((nBlocksReady | 0) - this._minBufferSize, 0); + } + + // Count words ready + var nWordsReady = nBlocksReady * blockSize; + + // Count bytes ready + var nBytesReady = Math.min(nWordsReady * 4, dataSigBytes); + + // Process blocks + if (nWordsReady) { + for (var offset = 0; offset < nWordsReady; offset += blockSize) { + // Perform concrete-algorithm logic + this._doProcessBlock(dataWords, offset); + } + + // Remove processed words + processedWords = dataWords.splice(0, nWordsReady); + data.sigBytes -= nBytesReady; + } + + // Return processed words + return new WordArray.init(processedWords, nBytesReady); + }, + + /** + * Creates a copy of this object. + * + * @return {Object} The clone. + * + * @example + * + * var clone = bufferedBlockAlgorithm.clone(); + */ + clone: function () { + var clone = Base.clone.call(this); + clone._data = this._data.clone(); + + return clone; + }, + + _minBufferSize: 0 + }); + + /** + * Abstract hasher template. + * + * @property {number} blockSize The number of 32-bit words this hasher operates on. Default: 16 (512 bits) + */ + var Hasher = C_lib.Hasher = BufferedBlockAlgorithm.extend({ + /** + * Configuration options. + */ + cfg: Base.extend(), + + /** + * Initializes a newly created hasher. + * + * @param {Object} cfg (Optional) The configuration options to use for this hash computation. + * + * @example + * + * var hasher = CryptoJS.algo.SHA256.create(); + */ + init: function (cfg) { + // Apply config defaults + this.cfg = this.cfg.extend(cfg); + + // Set initial values + this.reset(); + }, + + /** + * Resets this hasher to its initial state. + * + * @example + * + * hasher.reset(); + */ + reset: function () { + // Reset data buffer + BufferedBlockAlgorithm.reset.call(this); + + // Perform concrete-hasher logic + this._doReset(); + }, + + /** + * Updates this hasher with a message. + * + * @param {WordArray|string} messageUpdate The message to append. + * + * @return {Hasher} This hasher. + * + * @example + * + * hasher.update('message'); + * hasher.update(wordArray); + */ + update: function (messageUpdate) { + // Append + this._append(messageUpdate); + + // Update the hash + this._process(); + + // Chainable + return this; + }, + + /** + * Finalizes the hash computation. + * Note that the finalize operation is effectively a destructive, read-once operation. + * + * @param {WordArray|string} messageUpdate (Optional) A final message update. + * + * @return {WordArray} The hash. + * + * @example + * + * var hash = hasher.finalize(); + * var hash = hasher.finalize('message'); + * var hash = hasher.finalize(wordArray); + */ + finalize: function (messageUpdate) { + // Final message update + if (messageUpdate) { + this._append(messageUpdate); + } + + // Perform concrete-hasher logic + var hash = this._doFinalize(); + + return hash; + }, + + blockSize: 512/32, + + /** + * Creates a shortcut function to a hasher's object interface. + * + * @param {Hasher} hasher The hasher to create a helper for. + * + * @return {Function} The shortcut function. + * + * @static + * + * @example + * + * var SHA256 = CryptoJS.lib.Hasher._createHelper(CryptoJS.algo.SHA256); + */ + _createHelper: function (hasher) { + return function (message, cfg) { + return new hasher.init(cfg).finalize(message); + }; + }, + + /** + * Creates a shortcut function to the HMAC's object interface. + * + * @param {Hasher} hasher The hasher to use in this HMAC helper. + * + * @return {Function} The shortcut function. + * + * @static + * + * @example + * + * var HmacSHA256 = CryptoJS.lib.Hasher._createHmacHelper(CryptoJS.algo.SHA256); + */ + _createHmacHelper: function (hasher) { + return function (message, key) { + return new C_algo.HMAC.init(hasher, key).finalize(message); + }; + } + }); + + /** + * Algorithm namespace. + */ + var C_algo = C.algo = {}; + + return C; + }(Math)); + + + (function (undefined) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Base = C_lib.Base; + var X32WordArray = C_lib.WordArray; + + /** + * x64 namespace. + */ + var C_x64 = C.x64 = {}; + + /** + * A 64-bit word. + */ + var X64Word = C_x64.Word = Base.extend({ + /** + * Initializes a newly created 64-bit word. + * + * @param {number} high The high 32 bits. + * @param {number} low The low 32 bits. + * + * @example + * + * var x64Word = CryptoJS.x64.Word.create(0x00010203, 0x04050607); + */ + init: function (high, low) { + this.high = high; + this.low = low; + } + + /** + * Bitwise NOTs this word. + * + * @return {X64Word} A new x64-Word object after negating. + * + * @example + * + * var negated = x64Word.not(); + */ + // not: function () { + // var high = ~this.high; + // var low = ~this.low; + + // return X64Word.create(high, low); + // }, + + /** + * Bitwise ANDs this word with the passed word. + * + * @param {X64Word} word The x64-Word to AND with this word. + * + * @return {X64Word} A new x64-Word object after ANDing. + * + * @example + * + * var anded = x64Word.and(anotherX64Word); + */ + // and: function (word) { + // var high = this.high & word.high; + // var low = this.low & word.low; + + // return X64Word.create(high, low); + // }, + + /** + * Bitwise ORs this word with the passed word. + * + * @param {X64Word} word The x64-Word to OR with this word. + * + * @return {X64Word} A new x64-Word object after ORing. + * + * @example + * + * var ored = x64Word.or(anotherX64Word); + */ + // or: function (word) { + // var high = this.high | word.high; + // var low = this.low | word.low; + + // return X64Word.create(high, low); + // }, + + /** + * Bitwise XORs this word with the passed word. + * + * @param {X64Word} word The x64-Word to XOR with this word. + * + * @return {X64Word} A new x64-Word object after XORing. + * + * @example + * + * var xored = x64Word.xor(anotherX64Word); + */ + // xor: function (word) { + // var high = this.high ^ word.high; + // var low = this.low ^ word.low; + + // return X64Word.create(high, low); + // }, + + /** + * Shifts this word n bits to the left. + * + * @param {number} n The number of bits to shift. + * + * @return {X64Word} A new x64-Word object after shifting. + * + * @example + * + * var shifted = x64Word.shiftL(25); + */ + // shiftL: function (n) { + // if (n < 32) { + // var high = (this.high << n) | (this.low >>> (32 - n)); + // var low = this.low << n; + // } else { + // var high = this.low << (n - 32); + // var low = 0; + // } + + // return X64Word.create(high, low); + // }, + + /** + * Shifts this word n bits to the right. + * + * @param {number} n The number of bits to shift. + * + * @return {X64Word} A new x64-Word object after shifting. + * + * @example + * + * var shifted = x64Word.shiftR(7); + */ + // shiftR: function (n) { + // if (n < 32) { + // var low = (this.low >>> n) | (this.high << (32 - n)); + // var high = this.high >>> n; + // } else { + // var low = this.high >>> (n - 32); + // var high = 0; + // } + + // return X64Word.create(high, low); + // }, + + /** + * Rotates this word n bits to the left. + * + * @param {number} n The number of bits to rotate. + * + * @return {X64Word} A new x64-Word object after rotating. + * + * @example + * + * var rotated = x64Word.rotL(25); + */ + // rotL: function (n) { + // return this.shiftL(n).or(this.shiftR(64 - n)); + // }, + + /** + * Rotates this word n bits to the right. + * + * @param {number} n The number of bits to rotate. + * + * @return {X64Word} A new x64-Word object after rotating. + * + * @example + * + * var rotated = x64Word.rotR(7); + */ + // rotR: function (n) { + // return this.shiftR(n).or(this.shiftL(64 - n)); + // }, + + /** + * Adds this word with the passed word. + * + * @param {X64Word} word The x64-Word to add with this word. + * + * @return {X64Word} A new x64-Word object after adding. + * + * @example + * + * var added = x64Word.add(anotherX64Word); + */ + // add: function (word) { + // var low = (this.low + word.low) | 0; + // var carry = (low >>> 0) < (this.low >>> 0) ? 1 : 0; + // var high = (this.high + word.high + carry) | 0; + + // return X64Word.create(high, low); + // } + }); + + /** + * An array of 64-bit words. + * + * @property {Array} words The array of CryptoJS.x64.Word objects. + * @property {number} sigBytes The number of significant bytes in this word array. + */ + var X64WordArray = C_x64.WordArray = Base.extend({ + /** + * Initializes a newly created word array. + * + * @param {Array} words (Optional) An array of CryptoJS.x64.Word objects. + * @param {number} sigBytes (Optional) The number of significant bytes in the words. + * + * @example + * + * var wordArray = CryptoJS.x64.WordArray.create(); + * + * var wordArray = CryptoJS.x64.WordArray.create([ + * CryptoJS.x64.Word.create(0x00010203, 0x04050607), + * CryptoJS.x64.Word.create(0x18191a1b, 0x1c1d1e1f) + * ]); + * + * var wordArray = CryptoJS.x64.WordArray.create([ + * CryptoJS.x64.Word.create(0x00010203, 0x04050607), + * CryptoJS.x64.Word.create(0x18191a1b, 0x1c1d1e1f) + * ], 10); + */ + init: function (words, sigBytes) { + words = this.words = words || []; + + if (sigBytes != undefined) { + this.sigBytes = sigBytes; + } else { + this.sigBytes = words.length * 8; + } + }, + + /** + * Converts this 64-bit word array to a 32-bit word array. + * + * @return {CryptoJS.lib.WordArray} This word array's data as a 32-bit word array. + * + * @example + * + * var x32WordArray = x64WordArray.toX32(); + */ + toX32: function () { + // Shortcuts + var x64Words = this.words; + var x64WordsLength = x64Words.length; + + // Convert + var x32Words = []; + for (var i = 0; i < x64WordsLength; i++) { + var x64Word = x64Words[i]; + x32Words.push(x64Word.high); + x32Words.push(x64Word.low); + } + + return X32WordArray.create(x32Words, this.sigBytes); + }, + + /** + * Creates a copy of this word array. + * + * @return {X64WordArray} The clone. + * + * @example + * + * var clone = x64WordArray.clone(); + */ + clone: function () { + var clone = Base.clone.call(this); + + // Clone "words" array + var words = clone.words = this.words.slice(0); + + // Clone each X64Word object + var wordsLength = words.length; + for (var i = 0; i < wordsLength; i++) { + words[i] = words[i].clone(); + } + + return clone; + } + }); + }()); + + + (function () { + // Check if typed arrays are supported + if (typeof ArrayBuffer != 'function') { + return; + } + + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + + // Reference original init + var superInit = WordArray.init; + + // Augment WordArray.init to handle typed arrays + var subInit = WordArray.init = function (typedArray) { + // Convert buffers to uint8 + if (typedArray instanceof ArrayBuffer) { + typedArray = new Uint8Array(typedArray); + } + + // Convert other array views to uint8 + if ( + typedArray instanceof Int8Array || + (typeof Uint8ClampedArray !== "undefined" && typedArray instanceof Uint8ClampedArray) || + typedArray instanceof Int16Array || + typedArray instanceof Uint16Array || + typedArray instanceof Int32Array || + typedArray instanceof Uint32Array || + typedArray instanceof Float32Array || + typedArray instanceof Float64Array + ) { + typedArray = new Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength); + } + + // Handle Uint8Array + if (typedArray instanceof Uint8Array) { + // Shortcut + var typedArrayByteLength = typedArray.byteLength; + + // Extract bytes + var words = []; + for (var i = 0; i < typedArrayByteLength; i++) { + words[i >>> 2] |= typedArray[i] << (24 - (i % 4) * 8); + } + + // Initialize this word array + superInit.call(this, words, typedArrayByteLength); + } else { + // Else call normal init + superInit.apply(this, arguments); + } + }; + + subInit.prototype = WordArray; + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var C_enc = C.enc; + + /** + * UTF-16 BE encoding strategy. + */ + var Utf16BE = C_enc.Utf16 = C_enc.Utf16BE = { + /** + * Converts a word array to a UTF-16 BE string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The UTF-16 BE string. + * + * @static + * + * @example + * + * var utf16String = CryptoJS.enc.Utf16.stringify(wordArray); + */ + stringify: function (wordArray) { + // Shortcuts + var words = wordArray.words; + var sigBytes = wordArray.sigBytes; + + // Convert + var utf16Chars = []; + for (var i = 0; i < sigBytes; i += 2) { + var codePoint = (words[i >>> 2] >>> (16 - (i % 4) * 8)) & 0xffff; + utf16Chars.push(String.fromCharCode(codePoint)); + } + + return utf16Chars.join(''); + }, + + /** + * Converts a UTF-16 BE string to a word array. + * + * @param {string} utf16Str The UTF-16 BE string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Utf16.parse(utf16String); + */ + parse: function (utf16Str) { + // Shortcut + var utf16StrLength = utf16Str.length; + + // Convert + var words = []; + for (var i = 0; i < utf16StrLength; i++) { + words[i >>> 1] |= utf16Str.charCodeAt(i) << (16 - (i % 2) * 16); + } + + return WordArray.create(words, utf16StrLength * 2); + } + }; + + /** + * UTF-16 LE encoding strategy. + */ + C_enc.Utf16LE = { + /** + * Converts a word array to a UTF-16 LE string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The UTF-16 LE string. + * + * @static + * + * @example + * + * var utf16Str = CryptoJS.enc.Utf16LE.stringify(wordArray); + */ + stringify: function (wordArray) { + // Shortcuts + var words = wordArray.words; + var sigBytes = wordArray.sigBytes; + + // Convert + var utf16Chars = []; + for (var i = 0; i < sigBytes; i += 2) { + var codePoint = swapEndian((words[i >>> 2] >>> (16 - (i % 4) * 8)) & 0xffff); + utf16Chars.push(String.fromCharCode(codePoint)); + } + + return utf16Chars.join(''); + }, + + /** + * Converts a UTF-16 LE string to a word array. + * + * @param {string} utf16Str The UTF-16 LE string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Utf16LE.parse(utf16Str); + */ + parse: function (utf16Str) { + // Shortcut + var utf16StrLength = utf16Str.length; + + // Convert + var words = []; + for (var i = 0; i < utf16StrLength; i++) { + words[i >>> 1] |= swapEndian(utf16Str.charCodeAt(i) << (16 - (i % 2) * 16)); + } + + return WordArray.create(words, utf16StrLength * 2); + } + }; + + function swapEndian(word) { + return ((word << 8) & 0xff00ff00) | ((word >>> 8) & 0x00ff00ff); + } + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var C_enc = C.enc; + + /** + * Base64 encoding strategy. + */ + var Base64 = C_enc.Base64 = { + /** + * Converts a word array to a Base64 string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The Base64 string. + * + * @static + * + * @example + * + * var base64String = CryptoJS.enc.Base64.stringify(wordArray); + */ + stringify: function (wordArray) { + // Shortcuts + var words = wordArray.words; + var sigBytes = wordArray.sigBytes; + var map = this._map; + + // Clamp excess bits + wordArray.clamp(); + + // Convert + var base64Chars = []; + for (var i = 0; i < sigBytes; i += 3) { + var byte1 = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + var byte2 = (words[(i + 1) >>> 2] >>> (24 - ((i + 1) % 4) * 8)) & 0xff; + var byte3 = (words[(i + 2) >>> 2] >>> (24 - ((i + 2) % 4) * 8)) & 0xff; + + var triplet = (byte1 << 16) | (byte2 << 8) | byte3; + + for (var j = 0; (j < 4) && (i + j * 0.75 < sigBytes); j++) { + base64Chars.push(map.charAt((triplet >>> (6 * (3 - j))) & 0x3f)); + } + } + + // Add padding + var paddingChar = map.charAt(64); + if (paddingChar) { + while (base64Chars.length % 4) { + base64Chars.push(paddingChar); + } + } + + return base64Chars.join(''); + }, + + /** + * Converts a Base64 string to a word array. + * + * @param {string} base64Str The Base64 string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Base64.parse(base64String); + */ + parse: function (base64Str) { + // Shortcuts + var base64StrLength = base64Str.length; + var map = this._map; + var reverseMap = this._reverseMap; + + if (!reverseMap) { + reverseMap = this._reverseMap = []; + for (var j = 0; j < map.length; j++) { + reverseMap[map.charCodeAt(j)] = j; + } + } + + // Ignore padding + var paddingChar = map.charAt(64); + if (paddingChar) { + var paddingIndex = base64Str.indexOf(paddingChar); + if (paddingIndex !== -1) { + base64StrLength = paddingIndex; + } + } + + // Convert + return parseLoop(base64Str, base64StrLength, reverseMap); + + }, + + _map: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' + }; + + function parseLoop(base64Str, base64StrLength, reverseMap) { + var words = []; + var nBytes = 0; + for (var i = 0; i < base64StrLength; i++) { + if (i % 4) { + var bits1 = reverseMap[base64Str.charCodeAt(i - 1)] << ((i % 4) * 2); + var bits2 = reverseMap[base64Str.charCodeAt(i)] >>> (6 - (i % 4) * 2); + var bitsCombined = bits1 | bits2; + words[nBytes >>> 2] |= bitsCombined << (24 - (nBytes % 4) * 8); + nBytes++; + } + } + return WordArray.create(words, nBytes); + } + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var C_enc = C.enc; + + /** + * Base64url encoding strategy. + */ + var Base64url = C_enc.Base64url = { + /** + * Converts a word array to a Base64url string. + * + * @param {WordArray} wordArray The word array. + * + * @param {boolean} urlSafe Whether to use url safe + * + * @return {string} The Base64url string. + * + * @static + * + * @example + * + * var base64String = CryptoJS.enc.Base64url.stringify(wordArray); + */ + stringify: function (wordArray, urlSafe=true) { + // Shortcuts + var words = wordArray.words; + var sigBytes = wordArray.sigBytes; + var map = urlSafe ? this._safe_map : this._map; + + // Clamp excess bits + wordArray.clamp(); + + // Convert + var base64Chars = []; + for (var i = 0; i < sigBytes; i += 3) { + var byte1 = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + var byte2 = (words[(i + 1) >>> 2] >>> (24 - ((i + 1) % 4) * 8)) & 0xff; + var byte3 = (words[(i + 2) >>> 2] >>> (24 - ((i + 2) % 4) * 8)) & 0xff; + + var triplet = (byte1 << 16) | (byte2 << 8) | byte3; + + for (var j = 0; (j < 4) && (i + j * 0.75 < sigBytes); j++) { + base64Chars.push(map.charAt((triplet >>> (6 * (3 - j))) & 0x3f)); + } + } + + // Add padding + var paddingChar = map.charAt(64); + if (paddingChar) { + while (base64Chars.length % 4) { + base64Chars.push(paddingChar); + } + } + + return base64Chars.join(''); + }, + + /** + * Converts a Base64url string to a word array. + * + * @param {string} base64Str The Base64url string. + * + * @param {boolean} urlSafe Whether to use url safe + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Base64url.parse(base64String); + */ + parse: function (base64Str, urlSafe=true) { + // Shortcuts + var base64StrLength = base64Str.length; + var map = urlSafe ? this._safe_map : this._map; + var reverseMap = this._reverseMap; + + if (!reverseMap) { + reverseMap = this._reverseMap = []; + for (var j = 0; j < map.length; j++) { + reverseMap[map.charCodeAt(j)] = j; + } + } + + // Ignore padding + var paddingChar = map.charAt(64); + if (paddingChar) { + var paddingIndex = base64Str.indexOf(paddingChar); + if (paddingIndex !== -1) { + base64StrLength = paddingIndex; + } + } + + // Convert + return parseLoop(base64Str, base64StrLength, reverseMap); + + }, + + _map: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=', + _safe_map: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_', + }; + + function parseLoop(base64Str, base64StrLength, reverseMap) { + var words = []; + var nBytes = 0; + for (var i = 0; i < base64StrLength; i++) { + if (i % 4) { + var bits1 = reverseMap[base64Str.charCodeAt(i - 1)] << ((i % 4) * 2); + var bits2 = reverseMap[base64Str.charCodeAt(i)] >>> (6 - (i % 4) * 2); + var bitsCombined = bits1 | bits2; + words[nBytes >>> 2] |= bitsCombined << (24 - (nBytes % 4) * 8); + nBytes++; + } + } + return WordArray.create(words, nBytes); + } + }()); + + (function (Math) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var Hasher = C_lib.Hasher; + var C_algo = C.algo; + + // Constants table + var T = []; + + // Compute constants + (function () { + for (var i = 0; i < 64; i++) { + T[i] = (Math.abs(Math.sin(i + 1)) * 0x100000000) | 0; + } + }()); + + /** + * MD5 hash algorithm. + */ + var MD5 = C_algo.MD5 = Hasher.extend({ + _doReset: function () { + this._hash = new WordArray.init([ + 0x67452301, 0xefcdab89, + 0x98badcfe, 0x10325476 + ]); + }, + + _doProcessBlock: function (M, offset) { + // Swap endian + for (var i = 0; i < 16; i++) { + // Shortcuts + var offset_i = offset + i; + var M_offset_i = M[offset_i]; + + M[offset_i] = ( + (((M_offset_i << 8) | (M_offset_i >>> 24)) & 0x00ff00ff) | + (((M_offset_i << 24) | (M_offset_i >>> 8)) & 0xff00ff00) + ); + } + + // Shortcuts + var H = this._hash.words; + + var M_offset_0 = M[offset + 0]; + var M_offset_1 = M[offset + 1]; + var M_offset_2 = M[offset + 2]; + var M_offset_3 = M[offset + 3]; + var M_offset_4 = M[offset + 4]; + var M_offset_5 = M[offset + 5]; + var M_offset_6 = M[offset + 6]; + var M_offset_7 = M[offset + 7]; + var M_offset_8 = M[offset + 8]; + var M_offset_9 = M[offset + 9]; + var M_offset_10 = M[offset + 10]; + var M_offset_11 = M[offset + 11]; + var M_offset_12 = M[offset + 12]; + var M_offset_13 = M[offset + 13]; + var M_offset_14 = M[offset + 14]; + var M_offset_15 = M[offset + 15]; + + // Working varialbes + var a = H[0]; + var b = H[1]; + var c = H[2]; + var d = H[3]; + + // Computation + a = FF(a, b, c, d, M_offset_0, 7, T[0]); + d = FF(d, a, b, c, M_offset_1, 12, T[1]); + c = FF(c, d, a, b, M_offset_2, 17, T[2]); + b = FF(b, c, d, a, M_offset_3, 22, T[3]); + a = FF(a, b, c, d, M_offset_4, 7, T[4]); + d = FF(d, a, b, c, M_offset_5, 12, T[5]); + c = FF(c, d, a, b, M_offset_6, 17, T[6]); + b = FF(b, c, d, a, M_offset_7, 22, T[7]); + a = FF(a, b, c, d, M_offset_8, 7, T[8]); + d = FF(d, a, b, c, M_offset_9, 12, T[9]); + c = FF(c, d, a, b, M_offset_10, 17, T[10]); + b = FF(b, c, d, a, M_offset_11, 22, T[11]); + a = FF(a, b, c, d, M_offset_12, 7, T[12]); + d = FF(d, a, b, c, M_offset_13, 12, T[13]); + c = FF(c, d, a, b, M_offset_14, 17, T[14]); + b = FF(b, c, d, a, M_offset_15, 22, T[15]); + + a = GG(a, b, c, d, M_offset_1, 5, T[16]); + d = GG(d, a, b, c, M_offset_6, 9, T[17]); + c = GG(c, d, a, b, M_offset_11, 14, T[18]); + b = GG(b, c, d, a, M_offset_0, 20, T[19]); + a = GG(a, b, c, d, M_offset_5, 5, T[20]); + d = GG(d, a, b, c, M_offset_10, 9, T[21]); + c = GG(c, d, a, b, M_offset_15, 14, T[22]); + b = GG(b, c, d, a, M_offset_4, 20, T[23]); + a = GG(a, b, c, d, M_offset_9, 5, T[24]); + d = GG(d, a, b, c, M_offset_14, 9, T[25]); + c = GG(c, d, a, b, M_offset_3, 14, T[26]); + b = GG(b, c, d, a, M_offset_8, 20, T[27]); + a = GG(a, b, c, d, M_offset_13, 5, T[28]); + d = GG(d, a, b, c, M_offset_2, 9, T[29]); + c = GG(c, d, a, b, M_offset_7, 14, T[30]); + b = GG(b, c, d, a, M_offset_12, 20, T[31]); + + a = HH(a, b, c, d, M_offset_5, 4, T[32]); + d = HH(d, a, b, c, M_offset_8, 11, T[33]); + c = HH(c, d, a, b, M_offset_11, 16, T[34]); + b = HH(b, c, d, a, M_offset_14, 23, T[35]); + a = HH(a, b, c, d, M_offset_1, 4, T[36]); + d = HH(d, a, b, c, M_offset_4, 11, T[37]); + c = HH(c, d, a, b, M_offset_7, 16, T[38]); + b = HH(b, c, d, a, M_offset_10, 23, T[39]); + a = HH(a, b, c, d, M_offset_13, 4, T[40]); + d = HH(d, a, b, c, M_offset_0, 11, T[41]); + c = HH(c, d, a, b, M_offset_3, 16, T[42]); + b = HH(b, c, d, a, M_offset_6, 23, T[43]); + a = HH(a, b, c, d, M_offset_9, 4, T[44]); + d = HH(d, a, b, c, M_offset_12, 11, T[45]); + c = HH(c, d, a, b, M_offset_15, 16, T[46]); + b = HH(b, c, d, a, M_offset_2, 23, T[47]); + + a = II(a, b, c, d, M_offset_0, 6, T[48]); + d = II(d, a, b, c, M_offset_7, 10, T[49]); + c = II(c, d, a, b, M_offset_14, 15, T[50]); + b = II(b, c, d, a, M_offset_5, 21, T[51]); + a = II(a, b, c, d, M_offset_12, 6, T[52]); + d = II(d, a, b, c, M_offset_3, 10, T[53]); + c = II(c, d, a, b, M_offset_10, 15, T[54]); + b = II(b, c, d, a, M_offset_1, 21, T[55]); + a = II(a, b, c, d, M_offset_8, 6, T[56]); + d = II(d, a, b, c, M_offset_15, 10, T[57]); + c = II(c, d, a, b, M_offset_6, 15, T[58]); + b = II(b, c, d, a, M_offset_13, 21, T[59]); + a = II(a, b, c, d, M_offset_4, 6, T[60]); + d = II(d, a, b, c, M_offset_11, 10, T[61]); + c = II(c, d, a, b, M_offset_2, 15, T[62]); + b = II(b, c, d, a, M_offset_9, 21, T[63]); + + // Intermediate hash value + H[0] = (H[0] + a) | 0; + H[1] = (H[1] + b) | 0; + H[2] = (H[2] + c) | 0; + H[3] = (H[3] + d) | 0; + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); + + var nBitsTotalH = Math.floor(nBitsTotal / 0x100000000); + var nBitsTotalL = nBitsTotal; + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = ( + (((nBitsTotalH << 8) | (nBitsTotalH >>> 24)) & 0x00ff00ff) | + (((nBitsTotalH << 24) | (nBitsTotalH >>> 8)) & 0xff00ff00) + ); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = ( + (((nBitsTotalL << 8) | (nBitsTotalL >>> 24)) & 0x00ff00ff) | + (((nBitsTotalL << 24) | (nBitsTotalL >>> 8)) & 0xff00ff00) + ); + + data.sigBytes = (dataWords.length + 1) * 4; + + // Hash final blocks + this._process(); + + // Shortcuts + var hash = this._hash; + var H = hash.words; + + // Swap endian + for (var i = 0; i < 4; i++) { + // Shortcut + var H_i = H[i]; + + H[i] = (((H_i << 8) | (H_i >>> 24)) & 0x00ff00ff) | + (((H_i << 24) | (H_i >>> 8)) & 0xff00ff00); + } + + // Return final computed hash + return hash; + }, + + clone: function () { + var clone = Hasher.clone.call(this); + clone._hash = this._hash.clone(); + + return clone; + } + }); + + function FF(a, b, c, d, x, s, t) { + var n = a + ((b & c) | (~b & d)) + x + t; + return ((n << s) | (n >>> (32 - s))) + b; + } + + function GG(a, b, c, d, x, s, t) { + var n = a + ((b & d) | (c & ~d)) + x + t; + return ((n << s) | (n >>> (32 - s))) + b; + } + + function HH(a, b, c, d, x, s, t) { + var n = a + (b ^ c ^ d) + x + t; + return ((n << s) | (n >>> (32 - s))) + b; + } + + function II(a, b, c, d, x, s, t) { + var n = a + (c ^ (b | ~d)) + x + t; + return ((n << s) | (n >>> (32 - s))) + b; + } + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.MD5('message'); + * var hash = CryptoJS.MD5(wordArray); + */ + C.MD5 = Hasher._createHelper(MD5); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacMD5(message, key); + */ + C.HmacMD5 = Hasher._createHmacHelper(MD5); + }(Math)); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var Hasher = C_lib.Hasher; + var C_algo = C.algo; + + // Reusable object + var W = []; + + /** + * SHA-1 hash algorithm. + */ + var SHA1 = C_algo.SHA1 = Hasher.extend({ + _doReset: function () { + this._hash = new WordArray.init([ + 0x67452301, 0xefcdab89, + 0x98badcfe, 0x10325476, + 0xc3d2e1f0 + ]); + }, + + _doProcessBlock: function (M, offset) { + // Shortcut + var H = this._hash.words; + + // Working variables + var a = H[0]; + var b = H[1]; + var c = H[2]; + var d = H[3]; + var e = H[4]; + + // Computation + for (var i = 0; i < 80; i++) { + if (i < 16) { + W[i] = M[offset + i] | 0; + } else { + var n = W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16]; + W[i] = (n << 1) | (n >>> 31); + } + + var t = ((a << 5) | (a >>> 27)) + e + W[i]; + if (i < 20) { + t += ((b & c) | (~b & d)) + 0x5a827999; + } else if (i < 40) { + t += (b ^ c ^ d) + 0x6ed9eba1; + } else if (i < 60) { + t += ((b & c) | (b & d) | (c & d)) - 0x70e44324; + } else /* if (i < 80) */ { + t += (b ^ c ^ d) - 0x359d3e2a; + } + + e = d; + d = c; + c = (b << 30) | (b >>> 2); + b = a; + a = t; + } + + // Intermediate hash value + H[0] = (H[0] + a) | 0; + H[1] = (H[1] + b) | 0; + H[2] = (H[2] + c) | 0; + H[3] = (H[3] + d) | 0; + H[4] = (H[4] + e) | 0; + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = Math.floor(nBitsTotal / 0x100000000); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = nBitsTotal; + data.sigBytes = dataWords.length * 4; + + // Hash final blocks + this._process(); + + // Return final computed hash + return this._hash; + }, + + clone: function () { + var clone = Hasher.clone.call(this); + clone._hash = this._hash.clone(); + + return clone; + } + }); + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA1('message'); + * var hash = CryptoJS.SHA1(wordArray); + */ + C.SHA1 = Hasher._createHelper(SHA1); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA1(message, key); + */ + C.HmacSHA1 = Hasher._createHmacHelper(SHA1); + }()); + + + (function (Math) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var Hasher = C_lib.Hasher; + var C_algo = C.algo; + + // Initialization and round constants tables + var H = []; + var K = []; + + // Compute constants + (function () { + function isPrime(n) { + var sqrtN = Math.sqrt(n); + for (var factor = 2; factor <= sqrtN; factor++) { + if (!(n % factor)) { + return false; + } + } + + return true; + } + + function getFractionalBits(n) { + return ((n - (n | 0)) * 0x100000000) | 0; + } + + var n = 2; + var nPrime = 0; + while (nPrime < 64) { + if (isPrime(n)) { + if (nPrime < 8) { + H[nPrime] = getFractionalBits(Math.pow(n, 1 / 2)); + } + K[nPrime] = getFractionalBits(Math.pow(n, 1 / 3)); + + nPrime++; + } + + n++; + } + }()); + + // Reusable object + var W = []; + + /** + * SHA-256 hash algorithm. + */ + var SHA256 = C_algo.SHA256 = Hasher.extend({ + _doReset: function () { + this._hash = new WordArray.init(H.slice(0)); + }, + + _doProcessBlock: function (M, offset) { + // Shortcut + var H = this._hash.words; + + // Working variables + var a = H[0]; + var b = H[1]; + var c = H[2]; + var d = H[3]; + var e = H[4]; + var f = H[5]; + var g = H[6]; + var h = H[7]; + + // Computation + for (var i = 0; i < 64; i++) { + if (i < 16) { + W[i] = M[offset + i] | 0; + } else { + var gamma0x = W[i - 15]; + var gamma0 = ((gamma0x << 25) | (gamma0x >>> 7)) ^ + ((gamma0x << 14) | (gamma0x >>> 18)) ^ + (gamma0x >>> 3); + + var gamma1x = W[i - 2]; + var gamma1 = ((gamma1x << 15) | (gamma1x >>> 17)) ^ + ((gamma1x << 13) | (gamma1x >>> 19)) ^ + (gamma1x >>> 10); + + W[i] = gamma0 + W[i - 7] + gamma1 + W[i - 16]; + } + + var ch = (e & f) ^ (~e & g); + var maj = (a & b) ^ (a & c) ^ (b & c); + + var sigma0 = ((a << 30) | (a >>> 2)) ^ ((a << 19) | (a >>> 13)) ^ ((a << 10) | (a >>> 22)); + var sigma1 = ((e << 26) | (e >>> 6)) ^ ((e << 21) | (e >>> 11)) ^ ((e << 7) | (e >>> 25)); + + var t1 = h + sigma1 + ch + K[i] + W[i]; + var t2 = sigma0 + maj; + + h = g; + g = f; + f = e; + e = (d + t1) | 0; + d = c; + c = b; + b = a; + a = (t1 + t2) | 0; + } + + // Intermediate hash value + H[0] = (H[0] + a) | 0; + H[1] = (H[1] + b) | 0; + H[2] = (H[2] + c) | 0; + H[3] = (H[3] + d) | 0; + H[4] = (H[4] + e) | 0; + H[5] = (H[5] + f) | 0; + H[6] = (H[6] + g) | 0; + H[7] = (H[7] + h) | 0; + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = Math.floor(nBitsTotal / 0x100000000); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = nBitsTotal; + data.sigBytes = dataWords.length * 4; + + // Hash final blocks + this._process(); + + // Return final computed hash + return this._hash; + }, + + clone: function () { + var clone = Hasher.clone.call(this); + clone._hash = this._hash.clone(); + + return clone; + } + }); + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA256('message'); + * var hash = CryptoJS.SHA256(wordArray); + */ + C.SHA256 = Hasher._createHelper(SHA256); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA256(message, key); + */ + C.HmacSHA256 = Hasher._createHmacHelper(SHA256); + }(Math)); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var C_algo = C.algo; + var SHA256 = C_algo.SHA256; + + /** + * SHA-224 hash algorithm. + */ + var SHA224 = C_algo.SHA224 = SHA256.extend({ + _doReset: function () { + this._hash = new WordArray.init([ + 0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, + 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4 + ]); + }, + + _doFinalize: function () { + var hash = SHA256._doFinalize.call(this); + + hash.sigBytes -= 4; + + return hash; + } + }); + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA224('message'); + * var hash = CryptoJS.SHA224(wordArray); + */ + C.SHA224 = SHA256._createHelper(SHA224); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA224(message, key); + */ + C.HmacSHA224 = SHA256._createHmacHelper(SHA224); + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Hasher = C_lib.Hasher; + var C_x64 = C.x64; + var X64Word = C_x64.Word; + var X64WordArray = C_x64.WordArray; + var C_algo = C.algo; + + function X64Word_create() { + return X64Word.create.apply(X64Word, arguments); + } + + // Constants + var K = [ + X64Word_create(0x428a2f98, 0xd728ae22), X64Word_create(0x71374491, 0x23ef65cd), + X64Word_create(0xb5c0fbcf, 0xec4d3b2f), X64Word_create(0xe9b5dba5, 0x8189dbbc), + X64Word_create(0x3956c25b, 0xf348b538), X64Word_create(0x59f111f1, 0xb605d019), + X64Word_create(0x923f82a4, 0xaf194f9b), X64Word_create(0xab1c5ed5, 0xda6d8118), + X64Word_create(0xd807aa98, 0xa3030242), X64Word_create(0x12835b01, 0x45706fbe), + X64Word_create(0x243185be, 0x4ee4b28c), X64Word_create(0x550c7dc3, 0xd5ffb4e2), + X64Word_create(0x72be5d74, 0xf27b896f), X64Word_create(0x80deb1fe, 0x3b1696b1), + X64Word_create(0x9bdc06a7, 0x25c71235), X64Word_create(0xc19bf174, 0xcf692694), + X64Word_create(0xe49b69c1, 0x9ef14ad2), X64Word_create(0xefbe4786, 0x384f25e3), + X64Word_create(0x0fc19dc6, 0x8b8cd5b5), X64Word_create(0x240ca1cc, 0x77ac9c65), + X64Word_create(0x2de92c6f, 0x592b0275), X64Word_create(0x4a7484aa, 0x6ea6e483), + X64Word_create(0x5cb0a9dc, 0xbd41fbd4), X64Word_create(0x76f988da, 0x831153b5), + X64Word_create(0x983e5152, 0xee66dfab), X64Word_create(0xa831c66d, 0x2db43210), + X64Word_create(0xb00327c8, 0x98fb213f), X64Word_create(0xbf597fc7, 0xbeef0ee4), + X64Word_create(0xc6e00bf3, 0x3da88fc2), X64Word_create(0xd5a79147, 0x930aa725), + X64Word_create(0x06ca6351, 0xe003826f), X64Word_create(0x14292967, 0x0a0e6e70), + X64Word_create(0x27b70a85, 0x46d22ffc), X64Word_create(0x2e1b2138, 0x5c26c926), + X64Word_create(0x4d2c6dfc, 0x5ac42aed), X64Word_create(0x53380d13, 0x9d95b3df), + X64Word_create(0x650a7354, 0x8baf63de), X64Word_create(0x766a0abb, 0x3c77b2a8), + X64Word_create(0x81c2c92e, 0x47edaee6), X64Word_create(0x92722c85, 0x1482353b), + X64Word_create(0xa2bfe8a1, 0x4cf10364), X64Word_create(0xa81a664b, 0xbc423001), + X64Word_create(0xc24b8b70, 0xd0f89791), X64Word_create(0xc76c51a3, 0x0654be30), + X64Word_create(0xd192e819, 0xd6ef5218), X64Word_create(0xd6990624, 0x5565a910), + X64Word_create(0xf40e3585, 0x5771202a), X64Word_create(0x106aa070, 0x32bbd1b8), + X64Word_create(0x19a4c116, 0xb8d2d0c8), X64Word_create(0x1e376c08, 0x5141ab53), + X64Word_create(0x2748774c, 0xdf8eeb99), X64Word_create(0x34b0bcb5, 0xe19b48a8), + X64Word_create(0x391c0cb3, 0xc5c95a63), X64Word_create(0x4ed8aa4a, 0xe3418acb), + X64Word_create(0x5b9cca4f, 0x7763e373), X64Word_create(0x682e6ff3, 0xd6b2b8a3), + X64Word_create(0x748f82ee, 0x5defb2fc), X64Word_create(0x78a5636f, 0x43172f60), + X64Word_create(0x84c87814, 0xa1f0ab72), X64Word_create(0x8cc70208, 0x1a6439ec), + X64Word_create(0x90befffa, 0x23631e28), X64Word_create(0xa4506ceb, 0xde82bde9), + X64Word_create(0xbef9a3f7, 0xb2c67915), X64Word_create(0xc67178f2, 0xe372532b), + X64Word_create(0xca273ece, 0xea26619c), X64Word_create(0xd186b8c7, 0x21c0c207), + X64Word_create(0xeada7dd6, 0xcde0eb1e), X64Word_create(0xf57d4f7f, 0xee6ed178), + X64Word_create(0x06f067aa, 0x72176fba), X64Word_create(0x0a637dc5, 0xa2c898a6), + X64Word_create(0x113f9804, 0xbef90dae), X64Word_create(0x1b710b35, 0x131c471b), + X64Word_create(0x28db77f5, 0x23047d84), X64Word_create(0x32caab7b, 0x40c72493), + X64Word_create(0x3c9ebe0a, 0x15c9bebc), X64Word_create(0x431d67c4, 0x9c100d4c), + X64Word_create(0x4cc5d4be, 0xcb3e42b6), X64Word_create(0x597f299c, 0xfc657e2a), + X64Word_create(0x5fcb6fab, 0x3ad6faec), X64Word_create(0x6c44198c, 0x4a475817) + ]; + + // Reusable objects + var W = []; + (function () { + for (var i = 0; i < 80; i++) { + W[i] = X64Word_create(); + } + }()); + + /** + * SHA-512 hash algorithm. + */ + var SHA512 = C_algo.SHA512 = Hasher.extend({ + _doReset: function () { + this._hash = new X64WordArray.init([ + new X64Word.init(0x6a09e667, 0xf3bcc908), new X64Word.init(0xbb67ae85, 0x84caa73b), + new X64Word.init(0x3c6ef372, 0xfe94f82b), new X64Word.init(0xa54ff53a, 0x5f1d36f1), + new X64Word.init(0x510e527f, 0xade682d1), new X64Word.init(0x9b05688c, 0x2b3e6c1f), + new X64Word.init(0x1f83d9ab, 0xfb41bd6b), new X64Word.init(0x5be0cd19, 0x137e2179) + ]); + }, + + _doProcessBlock: function (M, offset) { + // Shortcuts + var H = this._hash.words; + + var H0 = H[0]; + var H1 = H[1]; + var H2 = H[2]; + var H3 = H[3]; + var H4 = H[4]; + var H5 = H[5]; + var H6 = H[6]; + var H7 = H[7]; + + var H0h = H0.high; + var H0l = H0.low; + var H1h = H1.high; + var H1l = H1.low; + var H2h = H2.high; + var H2l = H2.low; + var H3h = H3.high; + var H3l = H3.low; + var H4h = H4.high; + var H4l = H4.low; + var H5h = H5.high; + var H5l = H5.low; + var H6h = H6.high; + var H6l = H6.low; + var H7h = H7.high; + var H7l = H7.low; + + // Working variables + var ah = H0h; + var al = H0l; + var bh = H1h; + var bl = H1l; + var ch = H2h; + var cl = H2l; + var dh = H3h; + var dl = H3l; + var eh = H4h; + var el = H4l; + var fh = H5h; + var fl = H5l; + var gh = H6h; + var gl = H6l; + var hh = H7h; + var hl = H7l; + + // Rounds + for (var i = 0; i < 80; i++) { + var Wil; + var Wih; + + // Shortcut + var Wi = W[i]; + + // Extend message + if (i < 16) { + Wih = Wi.high = M[offset + i * 2] | 0; + Wil = Wi.low = M[offset + i * 2 + 1] | 0; + } else { + // Gamma0 + var gamma0x = W[i - 15]; + var gamma0xh = gamma0x.high; + var gamma0xl = gamma0x.low; + var gamma0h = ((gamma0xh >>> 1) | (gamma0xl << 31)) ^ ((gamma0xh >>> 8) | (gamma0xl << 24)) ^ (gamma0xh >>> 7); + var gamma0l = ((gamma0xl >>> 1) | (gamma0xh << 31)) ^ ((gamma0xl >>> 8) | (gamma0xh << 24)) ^ ((gamma0xl >>> 7) | (gamma0xh << 25)); + + // Gamma1 + var gamma1x = W[i - 2]; + var gamma1xh = gamma1x.high; + var gamma1xl = gamma1x.low; + var gamma1h = ((gamma1xh >>> 19) | (gamma1xl << 13)) ^ ((gamma1xh << 3) | (gamma1xl >>> 29)) ^ (gamma1xh >>> 6); + var gamma1l = ((gamma1xl >>> 19) | (gamma1xh << 13)) ^ ((gamma1xl << 3) | (gamma1xh >>> 29)) ^ ((gamma1xl >>> 6) | (gamma1xh << 26)); + + // W[i] = gamma0 + W[i - 7] + gamma1 + W[i - 16] + var Wi7 = W[i - 7]; + var Wi7h = Wi7.high; + var Wi7l = Wi7.low; + + var Wi16 = W[i - 16]; + var Wi16h = Wi16.high; + var Wi16l = Wi16.low; + + Wil = gamma0l + Wi7l; + Wih = gamma0h + Wi7h + ((Wil >>> 0) < (gamma0l >>> 0) ? 1 : 0); + Wil = Wil + gamma1l; + Wih = Wih + gamma1h + ((Wil >>> 0) < (gamma1l >>> 0) ? 1 : 0); + Wil = Wil + Wi16l; + Wih = Wih + Wi16h + ((Wil >>> 0) < (Wi16l >>> 0) ? 1 : 0); + + Wi.high = Wih; + Wi.low = Wil; + } + + var chh = (eh & fh) ^ (~eh & gh); + var chl = (el & fl) ^ (~el & gl); + var majh = (ah & bh) ^ (ah & ch) ^ (bh & ch); + var majl = (al & bl) ^ (al & cl) ^ (bl & cl); + + var sigma0h = ((ah >>> 28) | (al << 4)) ^ ((ah << 30) | (al >>> 2)) ^ ((ah << 25) | (al >>> 7)); + var sigma0l = ((al >>> 28) | (ah << 4)) ^ ((al << 30) | (ah >>> 2)) ^ ((al << 25) | (ah >>> 7)); + var sigma1h = ((eh >>> 14) | (el << 18)) ^ ((eh >>> 18) | (el << 14)) ^ ((eh << 23) | (el >>> 9)); + var sigma1l = ((el >>> 14) | (eh << 18)) ^ ((el >>> 18) | (eh << 14)) ^ ((el << 23) | (eh >>> 9)); + + // t1 = h + sigma1 + ch + K[i] + W[i] + var Ki = K[i]; + var Kih = Ki.high; + var Kil = Ki.low; + + var t1l = hl + sigma1l; + var t1h = hh + sigma1h + ((t1l >>> 0) < (hl >>> 0) ? 1 : 0); + var t1l = t1l + chl; + var t1h = t1h + chh + ((t1l >>> 0) < (chl >>> 0) ? 1 : 0); + var t1l = t1l + Kil; + var t1h = t1h + Kih + ((t1l >>> 0) < (Kil >>> 0) ? 1 : 0); + var t1l = t1l + Wil; + var t1h = t1h + Wih + ((t1l >>> 0) < (Wil >>> 0) ? 1 : 0); + + // t2 = sigma0 + maj + var t2l = sigma0l + majl; + var t2h = sigma0h + majh + ((t2l >>> 0) < (sigma0l >>> 0) ? 1 : 0); + + // Update working variables + hh = gh; + hl = gl; + gh = fh; + gl = fl; + fh = eh; + fl = el; + el = (dl + t1l) | 0; + eh = (dh + t1h + ((el >>> 0) < (dl >>> 0) ? 1 : 0)) | 0; + dh = ch; + dl = cl; + ch = bh; + cl = bl; + bh = ah; + bl = al; + al = (t1l + t2l) | 0; + ah = (t1h + t2h + ((al >>> 0) < (t1l >>> 0) ? 1 : 0)) | 0; + } + + // Intermediate hash value + H0l = H0.low = (H0l + al); + H0.high = (H0h + ah + ((H0l >>> 0) < (al >>> 0) ? 1 : 0)); + H1l = H1.low = (H1l + bl); + H1.high = (H1h + bh + ((H1l >>> 0) < (bl >>> 0) ? 1 : 0)); + H2l = H2.low = (H2l + cl); + H2.high = (H2h + ch + ((H2l >>> 0) < (cl >>> 0) ? 1 : 0)); + H3l = H3.low = (H3l + dl); + H3.high = (H3h + dh + ((H3l >>> 0) < (dl >>> 0) ? 1 : 0)); + H4l = H4.low = (H4l + el); + H4.high = (H4h + eh + ((H4l >>> 0) < (el >>> 0) ? 1 : 0)); + H5l = H5.low = (H5l + fl); + H5.high = (H5h + fh + ((H5l >>> 0) < (fl >>> 0) ? 1 : 0)); + H6l = H6.low = (H6l + gl); + H6.high = (H6h + gh + ((H6l >>> 0) < (gl >>> 0) ? 1 : 0)); + H7l = H7.low = (H7l + hl); + H7.high = (H7h + hh + ((H7l >>> 0) < (hl >>> 0) ? 1 : 0)); + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); + dataWords[(((nBitsLeft + 128) >>> 10) << 5) + 30] = Math.floor(nBitsTotal / 0x100000000); + dataWords[(((nBitsLeft + 128) >>> 10) << 5) + 31] = nBitsTotal; + data.sigBytes = dataWords.length * 4; + + // Hash final blocks + this._process(); + + // Convert hash to 32-bit word array before returning + var hash = this._hash.toX32(); + + // Return final computed hash + return hash; + }, + + clone: function () { + var clone = Hasher.clone.call(this); + clone._hash = this._hash.clone(); + + return clone; + }, + + blockSize: 1024/32 + }); + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA512('message'); + * var hash = CryptoJS.SHA512(wordArray); + */ + C.SHA512 = Hasher._createHelper(SHA512); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA512(message, key); + */ + C.HmacSHA512 = Hasher._createHmacHelper(SHA512); + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_x64 = C.x64; + var X64Word = C_x64.Word; + var X64WordArray = C_x64.WordArray; + var C_algo = C.algo; + var SHA512 = C_algo.SHA512; + + /** + * SHA-384 hash algorithm. + */ + var SHA384 = C_algo.SHA384 = SHA512.extend({ + _doReset: function () { + this._hash = new X64WordArray.init([ + new X64Word.init(0xcbbb9d5d, 0xc1059ed8), new X64Word.init(0x629a292a, 0x367cd507), + new X64Word.init(0x9159015a, 0x3070dd17), new X64Word.init(0x152fecd8, 0xf70e5939), + new X64Word.init(0x67332667, 0xffc00b31), new X64Word.init(0x8eb44a87, 0x68581511), + new X64Word.init(0xdb0c2e0d, 0x64f98fa7), new X64Word.init(0x47b5481d, 0xbefa4fa4) + ]); + }, + + _doFinalize: function () { + var hash = SHA512._doFinalize.call(this); + + hash.sigBytes -= 16; + + return hash; + } + }); + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA384('message'); + * var hash = CryptoJS.SHA384(wordArray); + */ + C.SHA384 = SHA512._createHelper(SHA384); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA384(message, key); + */ + C.HmacSHA384 = SHA512._createHmacHelper(SHA384); + }()); + + + (function (Math) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var Hasher = C_lib.Hasher; + var C_x64 = C.x64; + var X64Word = C_x64.Word; + var C_algo = C.algo; + + // Constants tables + var RHO_OFFSETS = []; + var PI_INDEXES = []; + var ROUND_CONSTANTS = []; + + // Compute Constants + (function () { + // Compute rho offset constants + var x = 1, y = 0; + for (var t = 0; t < 24; t++) { + RHO_OFFSETS[x + 5 * y] = ((t + 1) * (t + 2) / 2) % 64; + + var newX = y % 5; + var newY = (2 * x + 3 * y) % 5; + x = newX; + y = newY; + } + + // Compute pi index constants + for (var x = 0; x < 5; x++) { + for (var y = 0; y < 5; y++) { + PI_INDEXES[x + 5 * y] = y + ((2 * x + 3 * y) % 5) * 5; + } + } + + // Compute round constants + var LFSR = 0x01; + for (var i = 0; i < 24; i++) { + var roundConstantMsw = 0; + var roundConstantLsw = 0; + + for (var j = 0; j < 7; j++) { + if (LFSR & 0x01) { + var bitPosition = (1 << j) - 1; + if (bitPosition < 32) { + roundConstantLsw ^= 1 << bitPosition; + } else /* if (bitPosition >= 32) */ { + roundConstantMsw ^= 1 << (bitPosition - 32); + } + } + + // Compute next LFSR + if (LFSR & 0x80) { + // Primitive polynomial over GF(2): x^8 + x^6 + x^5 + x^4 + 1 + LFSR = (LFSR << 1) ^ 0x71; + } else { + LFSR <<= 1; + } + } + + ROUND_CONSTANTS[i] = X64Word.create(roundConstantMsw, roundConstantLsw); + } + }()); + + // Reusable objects for temporary values + var T = []; + (function () { + for (var i = 0; i < 25; i++) { + T[i] = X64Word.create(); + } + }()); + + /** + * SHA-3 hash algorithm. + */ + var SHA3 = C_algo.SHA3 = Hasher.extend({ + /** + * Configuration options. + * + * @property {number} outputLength + * The desired number of bits in the output hash. + * Only values permitted are: 224, 256, 384, 512. + * Default: 512 + */ + cfg: Hasher.cfg.extend({ + outputLength: 512 + }), + + _doReset: function () { + var state = this._state = [] + for (var i = 0; i < 25; i++) { + state[i] = new X64Word.init(); + } + + this.blockSize = (1600 - 2 * this.cfg.outputLength) / 32; + }, + + _doProcessBlock: function (M, offset) { + // Shortcuts + var state = this._state; + var nBlockSizeLanes = this.blockSize / 2; + + // Absorb + for (var i = 0; i < nBlockSizeLanes; i++) { + // Shortcuts + var M2i = M[offset + 2 * i]; + var M2i1 = M[offset + 2 * i + 1]; + + // Swap endian + M2i = ( + (((M2i << 8) | (M2i >>> 24)) & 0x00ff00ff) | + (((M2i << 24) | (M2i >>> 8)) & 0xff00ff00) + ); + M2i1 = ( + (((M2i1 << 8) | (M2i1 >>> 24)) & 0x00ff00ff) | + (((M2i1 << 24) | (M2i1 >>> 8)) & 0xff00ff00) + ); + + // Absorb message into state + var lane = state[i]; + lane.high ^= M2i1; + lane.low ^= M2i; + } + + // Rounds + for (var round = 0; round < 24; round++) { + // Theta + for (var x = 0; x < 5; x++) { + // Mix column lanes + var tMsw = 0, tLsw = 0; + for (var y = 0; y < 5; y++) { + var lane = state[x + 5 * y]; + tMsw ^= lane.high; + tLsw ^= lane.low; + } + + // Temporary values + var Tx = T[x]; + Tx.high = tMsw; + Tx.low = tLsw; + } + for (var x = 0; x < 5; x++) { + // Shortcuts + var Tx4 = T[(x + 4) % 5]; + var Tx1 = T[(x + 1) % 5]; + var Tx1Msw = Tx1.high; + var Tx1Lsw = Tx1.low; + + // Mix surrounding columns + var tMsw = Tx4.high ^ ((Tx1Msw << 1) | (Tx1Lsw >>> 31)); + var tLsw = Tx4.low ^ ((Tx1Lsw << 1) | (Tx1Msw >>> 31)); + for (var y = 0; y < 5; y++) { + var lane = state[x + 5 * y]; + lane.high ^= tMsw; + lane.low ^= tLsw; + } + } + + // Rho Pi + for (var laneIndex = 1; laneIndex < 25; laneIndex++) { + var tMsw; + var tLsw; + + // Shortcuts + var lane = state[laneIndex]; + var laneMsw = lane.high; + var laneLsw = lane.low; + var rhoOffset = RHO_OFFSETS[laneIndex]; + + // Rotate lanes + if (rhoOffset < 32) { + tMsw = (laneMsw << rhoOffset) | (laneLsw >>> (32 - rhoOffset)); + tLsw = (laneLsw << rhoOffset) | (laneMsw >>> (32 - rhoOffset)); + } else /* if (rhoOffset >= 32) */ { + tMsw = (laneLsw << (rhoOffset - 32)) | (laneMsw >>> (64 - rhoOffset)); + tLsw = (laneMsw << (rhoOffset - 32)) | (laneLsw >>> (64 - rhoOffset)); + } + + // Transpose lanes + var TPiLane = T[PI_INDEXES[laneIndex]]; + TPiLane.high = tMsw; + TPiLane.low = tLsw; + } + + // Rho pi at x = y = 0 + var T0 = T[0]; + var state0 = state[0]; + T0.high = state0.high; + T0.low = state0.low; + + // Chi + for (var x = 0; x < 5; x++) { + for (var y = 0; y < 5; y++) { + // Shortcuts + var laneIndex = x + 5 * y; + var lane = state[laneIndex]; + var TLane = T[laneIndex]; + var Tx1Lane = T[((x + 1) % 5) + 5 * y]; + var Tx2Lane = T[((x + 2) % 5) + 5 * y]; + + // Mix rows + lane.high = TLane.high ^ (~Tx1Lane.high & Tx2Lane.high); + lane.low = TLane.low ^ (~Tx1Lane.low & Tx2Lane.low); + } + } + + // Iota + var lane = state[0]; + var roundConstant = ROUND_CONSTANTS[round]; + lane.high ^= roundConstant.high; + lane.low ^= roundConstant.low; + } + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + var blockSizeBits = this.blockSize * 32; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x1 << (24 - nBitsLeft % 32); + dataWords[((Math.ceil((nBitsLeft + 1) / blockSizeBits) * blockSizeBits) >>> 5) - 1] |= 0x80; + data.sigBytes = dataWords.length * 4; + + // Hash final blocks + this._process(); + + // Shortcuts + var state = this._state; + var outputLengthBytes = this.cfg.outputLength / 8; + var outputLengthLanes = outputLengthBytes / 8; + + // Squeeze + var hashWords = []; + for (var i = 0; i < outputLengthLanes; i++) { + // Shortcuts + var lane = state[i]; + var laneMsw = lane.high; + var laneLsw = lane.low; + + // Swap endian + laneMsw = ( + (((laneMsw << 8) | (laneMsw >>> 24)) & 0x00ff00ff) | + (((laneMsw << 24) | (laneMsw >>> 8)) & 0xff00ff00) + ); + laneLsw = ( + (((laneLsw << 8) | (laneLsw >>> 24)) & 0x00ff00ff) | + (((laneLsw << 24) | (laneLsw >>> 8)) & 0xff00ff00) + ); + + // Squeeze state to retrieve hash + hashWords.push(laneLsw); + hashWords.push(laneMsw); + } + + // Return final computed hash + return new WordArray.init(hashWords, outputLengthBytes); + }, + + clone: function () { + var clone = Hasher.clone.call(this); + + var state = clone._state = this._state.slice(0); + for (var i = 0; i < 25; i++) { + state[i] = state[i].clone(); + } + + return clone; + } + }); + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA3('message'); + * var hash = CryptoJS.SHA3(wordArray); + */ + C.SHA3 = Hasher._createHelper(SHA3); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA3(message, key); + */ + C.HmacSHA3 = Hasher._createHmacHelper(SHA3); + }(Math)); + + + /** @preserve + (c) 2012 by Cédric Mesnil. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + (function (Math) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var Hasher = C_lib.Hasher; + var C_algo = C.algo; + + // Constants table + var _zl = WordArray.create([ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, + 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, + 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, + 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13]); + var _zr = WordArray.create([ + 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, + 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, + 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, + 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, + 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11]); + var _sl = WordArray.create([ + 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, + 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, + 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, + 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, + 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6 ]); + var _sr = WordArray.create([ + 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, + 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, + 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, + 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, + 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11 ]); + + var _hl = WordArray.create([ 0x00000000, 0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xA953FD4E]); + var _hr = WordArray.create([ 0x50A28BE6, 0x5C4DD124, 0x6D703EF3, 0x7A6D76E9, 0x00000000]); + + /** + * RIPEMD160 hash algorithm. + */ + var RIPEMD160 = C_algo.RIPEMD160 = Hasher.extend({ + _doReset: function () { + this._hash = WordArray.create([0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0]); + }, + + _doProcessBlock: function (M, offset) { + + // Swap endian + for (var i = 0; i < 16; i++) { + // Shortcuts + var offset_i = offset + i; + var M_offset_i = M[offset_i]; + + // Swap + M[offset_i] = ( + (((M_offset_i << 8) | (M_offset_i >>> 24)) & 0x00ff00ff) | + (((M_offset_i << 24) | (M_offset_i >>> 8)) & 0xff00ff00) + ); + } + // Shortcut + var H = this._hash.words; + var hl = _hl.words; + var hr = _hr.words; + var zl = _zl.words; + var zr = _zr.words; + var sl = _sl.words; + var sr = _sr.words; + + // Working variables + var al, bl, cl, dl, el; + var ar, br, cr, dr, er; + + ar = al = H[0]; + br = bl = H[1]; + cr = cl = H[2]; + dr = dl = H[3]; + er = el = H[4]; + // Computation + var t; + for (var i = 0; i < 80; i += 1) { + t = (al + M[offset+zl[i]])|0; + if (i<16){ + t += f1(bl,cl,dl) + hl[0]; + } else if (i<32) { + t += f2(bl,cl,dl) + hl[1]; + } else if (i<48) { + t += f3(bl,cl,dl) + hl[2]; + } else if (i<64) { + t += f4(bl,cl,dl) + hl[3]; + } else {// if (i<80) { + t += f5(bl,cl,dl) + hl[4]; + } + t = t|0; + t = rotl(t,sl[i]); + t = (t+el)|0; + al = el; + el = dl; + dl = rotl(cl, 10); + cl = bl; + bl = t; + + t = (ar + M[offset+zr[i]])|0; + if (i<16){ + t += f5(br,cr,dr) + hr[0]; + } else if (i<32) { + t += f4(br,cr,dr) + hr[1]; + } else if (i<48) { + t += f3(br,cr,dr) + hr[2]; + } else if (i<64) { + t += f2(br,cr,dr) + hr[3]; + } else {// if (i<80) { + t += f1(br,cr,dr) + hr[4]; + } + t = t|0; + t = rotl(t,sr[i]) ; + t = (t+er)|0; + ar = er; + er = dr; + dr = rotl(cr, 10); + cr = br; + br = t; + } + // Intermediate hash value + t = (H[1] + cl + dr)|0; + H[1] = (H[2] + dl + er)|0; + H[2] = (H[3] + el + ar)|0; + H[3] = (H[4] + al + br)|0; + H[4] = (H[0] + bl + cr)|0; + H[0] = t; + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = ( + (((nBitsTotal << 8) | (nBitsTotal >>> 24)) & 0x00ff00ff) | + (((nBitsTotal << 24) | (nBitsTotal >>> 8)) & 0xff00ff00) + ); + data.sigBytes = (dataWords.length + 1) * 4; + + // Hash final blocks + this._process(); + + // Shortcuts + var hash = this._hash; + var H = hash.words; + + // Swap endian + for (var i = 0; i < 5; i++) { + // Shortcut + var H_i = H[i]; + + // Swap + H[i] = (((H_i << 8) | (H_i >>> 24)) & 0x00ff00ff) | + (((H_i << 24) | (H_i >>> 8)) & 0xff00ff00); + } + + // Return final computed hash + return hash; + }, + + clone: function () { + var clone = Hasher.clone.call(this); + clone._hash = this._hash.clone(); + + return clone; + } + }); + + + function f1(x, y, z) { + return ((x) ^ (y) ^ (z)); + + } + + function f2(x, y, z) { + return (((x)&(y)) | ((~x)&(z))); + } + + function f3(x, y, z) { + return (((x) | (~(y))) ^ (z)); + } + + function f4(x, y, z) { + return (((x) & (z)) | ((y)&(~(z)))); + } + + function f5(x, y, z) { + return ((x) ^ ((y) |(~(z)))); + + } + + function rotl(x,n) { + return (x<>>(32-n)); + } + + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.RIPEMD160('message'); + * var hash = CryptoJS.RIPEMD160(wordArray); + */ + C.RIPEMD160 = Hasher._createHelper(RIPEMD160); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacRIPEMD160(message, key); + */ + C.HmacRIPEMD160 = Hasher._createHmacHelper(RIPEMD160); + }(Math)); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Base = C_lib.Base; + var C_enc = C.enc; + var Utf8 = C_enc.Utf8; + var C_algo = C.algo; + + /** + * HMAC algorithm. + */ + var HMAC = C_algo.HMAC = Base.extend({ + /** + * Initializes a newly created HMAC. + * + * @param {Hasher} hasher The hash algorithm to use. + * @param {WordArray|string} key The secret key. + * + * @example + * + * var hmacHasher = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, key); + */ + init: function (hasher, key) { + // Init hasher + hasher = this._hasher = new hasher.init(); + + // Convert string to WordArray, else assume WordArray already + if (typeof key == 'string') { + key = Utf8.parse(key); + } + + // Shortcuts + var hasherBlockSize = hasher.blockSize; + var hasherBlockSizeBytes = hasherBlockSize * 4; + + // Allow arbitrary length keys + if (key.sigBytes > hasherBlockSizeBytes) { + key = hasher.finalize(key); + } + + // Clamp excess bits + key.clamp(); + + // Clone key for inner and outer pads + var oKey = this._oKey = key.clone(); + var iKey = this._iKey = key.clone(); + + // Shortcuts + var oKeyWords = oKey.words; + var iKeyWords = iKey.words; + + // XOR keys with pad constants + for (var i = 0; i < hasherBlockSize; i++) { + oKeyWords[i] ^= 0x5c5c5c5c; + iKeyWords[i] ^= 0x36363636; + } + oKey.sigBytes = iKey.sigBytes = hasherBlockSizeBytes; + + // Set initial values + this.reset(); + }, + + /** + * Resets this HMAC to its initial state. + * + * @example + * + * hmacHasher.reset(); + */ + reset: function () { + // Shortcut + var hasher = this._hasher; + + // Reset + hasher.reset(); + hasher.update(this._iKey); + }, + + /** + * Updates this HMAC with a message. + * + * @param {WordArray|string} messageUpdate The message to append. + * + * @return {HMAC} This HMAC instance. + * + * @example + * + * hmacHasher.update('message'); + * hmacHasher.update(wordArray); + */ + update: function (messageUpdate) { + this._hasher.update(messageUpdate); + + // Chainable + return this; + }, + + /** + * Finalizes the HMAC computation. + * Note that the finalize operation is effectively a destructive, read-once operation. + * + * @param {WordArray|string} messageUpdate (Optional) A final message update. + * + * @return {WordArray} The HMAC. + * + * @example + * + * var hmac = hmacHasher.finalize(); + * var hmac = hmacHasher.finalize('message'); + * var hmac = hmacHasher.finalize(wordArray); + */ + finalize: function (messageUpdate) { + // Shortcut + var hasher = this._hasher; + + // Compute HMAC + var innerHash = hasher.finalize(messageUpdate); + hasher.reset(); + var hmac = hasher.finalize(this._oKey.clone().concat(innerHash)); + + return hmac; + } + }); + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Base = C_lib.Base; + var WordArray = C_lib.WordArray; + var C_algo = C.algo; + var SHA1 = C_algo.SHA1; + var HMAC = C_algo.HMAC; + + /** + * Password-Based Key Derivation Function 2 algorithm. + */ + var PBKDF2 = C_algo.PBKDF2 = Base.extend({ + /** + * Configuration options. + * + * @property {number} keySize The key size in words to generate. Default: 4 (128 bits) + * @property {Hasher} hasher The hasher to use. Default: SHA1 + * @property {number} iterations The number of iterations to perform. Default: 1 + */ + cfg: Base.extend({ + keySize: 128/32, + hasher: SHA1, + iterations: 1 + }), + + /** + * Initializes a newly created key derivation function. + * + * @param {Object} cfg (Optional) The configuration options to use for the derivation. + * + * @example + * + * var kdf = CryptoJS.algo.PBKDF2.create(); + * var kdf = CryptoJS.algo.PBKDF2.create({ keySize: 8 }); + * var kdf = CryptoJS.algo.PBKDF2.create({ keySize: 8, iterations: 1000 }); + */ + init: function (cfg) { + this.cfg = this.cfg.extend(cfg); + }, + + /** + * Computes the Password-Based Key Derivation Function 2. + * + * @param {WordArray|string} password The password. + * @param {WordArray|string} salt A salt. + * + * @return {WordArray} The derived key. + * + * @example + * + * var key = kdf.compute(password, salt); + */ + compute: function (password, salt) { + // Shortcut + var cfg = this.cfg; + + // Init HMAC + var hmac = HMAC.create(cfg.hasher, password); + + // Initial values + var derivedKey = WordArray.create(); + var blockIndex = WordArray.create([0x00000001]); + + // Shortcuts + var derivedKeyWords = derivedKey.words; + var blockIndexWords = blockIndex.words; + var keySize = cfg.keySize; + var iterations = cfg.iterations; + + // Generate key + while (derivedKeyWords.length < keySize) { + var block = hmac.update(salt).finalize(blockIndex); + hmac.reset(); + + // Shortcuts + var blockWords = block.words; + var blockWordsLength = blockWords.length; + + // Iterations + var intermediate = block; + for (var i = 1; i < iterations; i++) { + intermediate = hmac.finalize(intermediate); + hmac.reset(); + + // Shortcut + var intermediateWords = intermediate.words; + + // XOR intermediate with block + for (var j = 0; j < blockWordsLength; j++) { + blockWords[j] ^= intermediateWords[j]; + } + } + + derivedKey.concat(block); + blockIndexWords[0]++; + } + derivedKey.sigBytes = keySize * 4; + + return derivedKey; + } + }); + + /** + * Computes the Password-Based Key Derivation Function 2. + * + * @param {WordArray|string} password The password. + * @param {WordArray|string} salt A salt. + * @param {Object} cfg (Optional) The configuration options to use for this computation. + * + * @return {WordArray} The derived key. + * + * @static + * + * @example + * + * var key = CryptoJS.PBKDF2(password, salt); + * var key = CryptoJS.PBKDF2(password, salt, { keySize: 8 }); + * var key = CryptoJS.PBKDF2(password, salt, { keySize: 8, iterations: 1000 }); + */ + C.PBKDF2 = function (password, salt, cfg) { + return PBKDF2.create(cfg).compute(password, salt); + }; + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Base = C_lib.Base; + var WordArray = C_lib.WordArray; + var C_algo = C.algo; + var MD5 = C_algo.MD5; + + /** + * This key derivation function is meant to conform with EVP_BytesToKey. + * www.openssl.org/docs/crypto/EVP_BytesToKey.html + */ + var EvpKDF = C_algo.EvpKDF = Base.extend({ + /** + * Configuration options. + * + * @property {number} keySize The key size in words to generate. Default: 4 (128 bits) + * @property {Hasher} hasher The hash algorithm to use. Default: MD5 + * @property {number} iterations The number of iterations to perform. Default: 1 + */ + cfg: Base.extend({ + keySize: 128/32, + hasher: MD5, + iterations: 1 + }), + + /** + * Initializes a newly created key derivation function. + * + * @param {Object} cfg (Optional) The configuration options to use for the derivation. + * + * @example + * + * var kdf = CryptoJS.algo.EvpKDF.create(); + * var kdf = CryptoJS.algo.EvpKDF.create({ keySize: 8 }); + * var kdf = CryptoJS.algo.EvpKDF.create({ keySize: 8, iterations: 1000 }); + */ + init: function (cfg) { + this.cfg = this.cfg.extend(cfg); + }, + + /** + * Derives a key from a password. + * + * @param {WordArray|string} password The password. + * @param {WordArray|string} salt A salt. + * + * @return {WordArray} The derived key. + * + * @example + * + * var key = kdf.compute(password, salt); + */ + compute: function (password, salt) { + var block; + + // Shortcut + var cfg = this.cfg; + + // Init hasher + var hasher = cfg.hasher.create(); + + // Initial values + var derivedKey = WordArray.create(); + + // Shortcuts + var derivedKeyWords = derivedKey.words; + var keySize = cfg.keySize; + var iterations = cfg.iterations; + + // Generate key + while (derivedKeyWords.length < keySize) { + if (block) { + hasher.update(block); + } + block = hasher.update(password).finalize(salt); + hasher.reset(); + + // Iterations + for (var i = 1; i < iterations; i++) { + block = hasher.finalize(block); + hasher.reset(); + } + + derivedKey.concat(block); + } + derivedKey.sigBytes = keySize * 4; + + return derivedKey; + } + }); + + /** + * Derives a key from a password. + * + * @param {WordArray|string} password The password. + * @param {WordArray|string} salt A salt. + * @param {Object} cfg (Optional) The configuration options to use for this computation. + * + * @return {WordArray} The derived key. + * + * @static + * + * @example + * + * var key = CryptoJS.EvpKDF(password, salt); + * var key = CryptoJS.EvpKDF(password, salt, { keySize: 8 }); + * var key = CryptoJS.EvpKDF(password, salt, { keySize: 8, iterations: 1000 }); + */ + C.EvpKDF = function (password, salt, cfg) { + return EvpKDF.create(cfg).compute(password, salt); + }; + }()); + + + /** + * Cipher core components. + */ + CryptoJS.lib.Cipher || (function (undefined) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Base = C_lib.Base; + var WordArray = C_lib.WordArray; + var BufferedBlockAlgorithm = C_lib.BufferedBlockAlgorithm; + var C_enc = C.enc; + var Utf8 = C_enc.Utf8; + var Base64 = C_enc.Base64; + var C_algo = C.algo; + var EvpKDF = C_algo.EvpKDF; + + /** + * Abstract base cipher template. + * + * @property {number} keySize This cipher's key size. Default: 4 (128 bits) + * @property {number} ivSize This cipher's IV size. Default: 4 (128 bits) + * @property {number} _ENC_XFORM_MODE A constant representing encryption mode. + * @property {number} _DEC_XFORM_MODE A constant representing decryption mode. + */ + var Cipher = C_lib.Cipher = BufferedBlockAlgorithm.extend({ + /** + * Configuration options. + * + * @property {WordArray} iv The IV to use for this operation. + */ + cfg: Base.extend(), + + /** + * Creates this cipher in encryption mode. + * + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {Cipher} A cipher instance. + * + * @static + * + * @example + * + * var cipher = CryptoJS.algo.AES.createEncryptor(keyWordArray, { iv: ivWordArray }); + */ + createEncryptor: function (key, cfg) { + return this.create(this._ENC_XFORM_MODE, key, cfg); + }, + + /** + * Creates this cipher in decryption mode. + * + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {Cipher} A cipher instance. + * + * @static + * + * @example + * + * var cipher = CryptoJS.algo.AES.createDecryptor(keyWordArray, { iv: ivWordArray }); + */ + createDecryptor: function (key, cfg) { + return this.create(this._DEC_XFORM_MODE, key, cfg); + }, + + /** + * Initializes a newly created cipher. + * + * @param {number} xformMode Either the encryption or decryption transormation mode constant. + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @example + * + * var cipher = CryptoJS.algo.AES.create(CryptoJS.algo.AES._ENC_XFORM_MODE, keyWordArray, { iv: ivWordArray }); + */ + init: function (xformMode, key, cfg) { + // Apply config defaults + this.cfg = this.cfg.extend(cfg); + + // Store transform mode and key + this._xformMode = xformMode; + this._key = key; + + // Set initial values + this.reset(); + }, + + /** + * Resets this cipher to its initial state. + * + * @example + * + * cipher.reset(); + */ + reset: function () { + // Reset data buffer + BufferedBlockAlgorithm.reset.call(this); + + // Perform concrete-cipher logic + this._doReset(); + }, + + /** + * Adds data to be encrypted or decrypted. + * + * @param {WordArray|string} dataUpdate The data to encrypt or decrypt. + * + * @return {WordArray} The data after processing. + * + * @example + * + * var encrypted = cipher.process('data'); + * var encrypted = cipher.process(wordArray); + */ + process: function (dataUpdate) { + // Append + this._append(dataUpdate); + + // Process available blocks + return this._process(); + }, + + /** + * Finalizes the encryption or decryption process. + * Note that the finalize operation is effectively a destructive, read-once operation. + * + * @param {WordArray|string} dataUpdate The final data to encrypt or decrypt. + * + * @return {WordArray} The data after final processing. + * + * @example + * + * var encrypted = cipher.finalize(); + * var encrypted = cipher.finalize('data'); + * var encrypted = cipher.finalize(wordArray); + */ + finalize: function (dataUpdate) { + // Final data update + if (dataUpdate) { + this._append(dataUpdate); + } + + // Perform concrete-cipher logic + var finalProcessedData = this._doFinalize(); + + return finalProcessedData; + }, + + keySize: 128/32, + + ivSize: 128/32, + + _ENC_XFORM_MODE: 1, + + _DEC_XFORM_MODE: 2, + + /** + * Creates shortcut functions to a cipher's object interface. + * + * @param {Cipher} cipher The cipher to create a helper for. + * + * @return {Object} An object with encrypt and decrypt shortcut functions. + * + * @static + * + * @example + * + * var AES = CryptoJS.lib.Cipher._createHelper(CryptoJS.algo.AES); + */ + _createHelper: (function () { + function selectCipherStrategy(key) { + if (typeof key == 'string') { + return PasswordBasedCipher; + } else { + return SerializableCipher; + } + } + + return function (cipher) { + return { + encrypt: function (message, key, cfg) { + return selectCipherStrategy(key).encrypt(cipher, message, key, cfg); + }, + + decrypt: function (ciphertext, key, cfg) { + return selectCipherStrategy(key).decrypt(cipher, ciphertext, key, cfg); + } + }; + }; + }()) + }); + + /** + * Abstract base stream cipher template. + * + * @property {number} blockSize The number of 32-bit words this cipher operates on. Default: 1 (32 bits) + */ + var StreamCipher = C_lib.StreamCipher = Cipher.extend({ + _doFinalize: function () { + // Process partial blocks + var finalProcessedBlocks = this._process(!!'flush'); + + return finalProcessedBlocks; + }, + + blockSize: 1 + }); + + /** + * Mode namespace. + */ + var C_mode = C.mode = {}; + + /** + * Abstract base block cipher mode template. + */ + var BlockCipherMode = C_lib.BlockCipherMode = Base.extend({ + /** + * Creates this mode for encryption. + * + * @param {Cipher} cipher A block cipher instance. + * @param {Array} iv The IV words. + * + * @static + * + * @example + * + * var mode = CryptoJS.mode.CBC.createEncryptor(cipher, iv.words); + */ + createEncryptor: function (cipher, iv) { + return this.Encryptor.create(cipher, iv); + }, + + /** + * Creates this mode for decryption. + * + * @param {Cipher} cipher A block cipher instance. + * @param {Array} iv The IV words. + * + * @static + * + * @example + * + * var mode = CryptoJS.mode.CBC.createDecryptor(cipher, iv.words); + */ + createDecryptor: function (cipher, iv) { + return this.Decryptor.create(cipher, iv); + }, + + /** + * Initializes a newly created mode. + * + * @param {Cipher} cipher A block cipher instance. + * @param {Array} iv The IV words. + * + * @example + * + * var mode = CryptoJS.mode.CBC.Encryptor.create(cipher, iv.words); + */ + init: function (cipher, iv) { + this._cipher = cipher; + this._iv = iv; + } + }); + + /** + * Cipher Block Chaining mode. + */ + var CBC = C_mode.CBC = (function () { + /** + * Abstract base CBC mode. + */ + var CBC = BlockCipherMode.extend(); + + /** + * CBC encryptor. + */ + CBC.Encryptor = CBC.extend({ + /** + * Processes the data block at offset. + * + * @param {Array} words The data words to operate on. + * @param {number} offset The offset where the block starts. + * + * @example + * + * mode.processBlock(data.words, offset); + */ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher; + var blockSize = cipher.blockSize; + + // XOR and encrypt + xorBlock.call(this, words, offset, blockSize); + cipher.encryptBlock(words, offset); + + // Remember this block to use with next block + this._prevBlock = words.slice(offset, offset + blockSize); + } + }); + + /** + * CBC decryptor. + */ + CBC.Decryptor = CBC.extend({ + /** + * Processes the data block at offset. + * + * @param {Array} words The data words to operate on. + * @param {number} offset The offset where the block starts. + * + * @example + * + * mode.processBlock(data.words, offset); + */ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher; + var blockSize = cipher.blockSize; + + // Remember this block to use with next block + var thisBlock = words.slice(offset, offset + blockSize); + + // Decrypt and XOR + cipher.decryptBlock(words, offset); + xorBlock.call(this, words, offset, blockSize); + + // This block becomes the previous block + this._prevBlock = thisBlock; + } + }); + + function xorBlock(words, offset, blockSize) { + var block; + + // Shortcut + var iv = this._iv; + + // Choose mixing block + if (iv) { + block = iv; + + // Remove IV for subsequent blocks + this._iv = undefined; + } else { + block = this._prevBlock; + } + + // XOR blocks + for (var i = 0; i < blockSize; i++) { + words[offset + i] ^= block[i]; + } + } + + return CBC; + }()); + + /** + * Padding namespace. + */ + var C_pad = C.pad = {}; + + /** + * PKCS #5/7 padding strategy. + */ + var Pkcs7 = C_pad.Pkcs7 = { + /** + * Pads data using the algorithm defined in PKCS #5/7. + * + * @param {WordArray} data The data to pad. + * @param {number} blockSize The multiple that the data should be padded to. + * + * @static + * + * @example + * + * CryptoJS.pad.Pkcs7.pad(wordArray, 4); + */ + pad: function (data, blockSize) { + // Shortcut + var blockSizeBytes = blockSize * 4; + + // Count padding bytes + var nPaddingBytes = blockSizeBytes - data.sigBytes % blockSizeBytes; + + // Create padding word + var paddingWord = (nPaddingBytes << 24) | (nPaddingBytes << 16) | (nPaddingBytes << 8) | nPaddingBytes; + + // Create padding + var paddingWords = []; + for (var i = 0; i < nPaddingBytes; i += 4) { + paddingWords.push(paddingWord); + } + var padding = WordArray.create(paddingWords, nPaddingBytes); + + // Add padding + data.concat(padding); + }, + + /** + * Unpads data that had been padded using the algorithm defined in PKCS #5/7. + * + * @param {WordArray} data The data to unpad. + * + * @static + * + * @example + * + * CryptoJS.pad.Pkcs7.unpad(wordArray); + */ + unpad: function (data) { + // Get number of padding bytes from last byte + var nPaddingBytes = data.words[(data.sigBytes - 1) >>> 2] & 0xff; + + // Remove padding + data.sigBytes -= nPaddingBytes; + } + }; + + /** + * Abstract base block cipher template. + * + * @property {number} blockSize The number of 32-bit words this cipher operates on. Default: 4 (128 bits) + */ + var BlockCipher = C_lib.BlockCipher = Cipher.extend({ + /** + * Configuration options. + * + * @property {Mode} mode The block mode to use. Default: CBC + * @property {Padding} padding The padding strategy to use. Default: Pkcs7 + */ + cfg: Cipher.cfg.extend({ + mode: CBC, + padding: Pkcs7 + }), + + reset: function () { + var modeCreator; + + // Reset cipher + Cipher.reset.call(this); + + // Shortcuts + var cfg = this.cfg; + var iv = cfg.iv; + var mode = cfg.mode; + + // Reset block mode + if (this._xformMode == this._ENC_XFORM_MODE) { + modeCreator = mode.createEncryptor; + } else /* if (this._xformMode == this._DEC_XFORM_MODE) */ { + modeCreator = mode.createDecryptor; + // Keep at least one block in the buffer for unpadding + this._minBufferSize = 1; + } + + if (this._mode && this._mode.__creator == modeCreator) { + this._mode.init(this, iv && iv.words); + } else { + this._mode = modeCreator.call(mode, this, iv && iv.words); + this._mode.__creator = modeCreator; + } + }, + + _doProcessBlock: function (words, offset) { + this._mode.processBlock(words, offset); + }, + + _doFinalize: function () { + var finalProcessedBlocks; + + // Shortcut + var padding = this.cfg.padding; + + // Finalize + if (this._xformMode == this._ENC_XFORM_MODE) { + // Pad data + padding.pad(this._data, this.blockSize); + + // Process final blocks + finalProcessedBlocks = this._process(!!'flush'); + } else /* if (this._xformMode == this._DEC_XFORM_MODE) */ { + // Process final blocks + finalProcessedBlocks = this._process(!!'flush'); + + // Unpad data + padding.unpad(finalProcessedBlocks); + } + + return finalProcessedBlocks; + }, + + blockSize: 128/32 + }); + + /** + * A collection of cipher parameters. + * + * @property {WordArray} ciphertext The raw ciphertext. + * @property {WordArray} key The key to this ciphertext. + * @property {WordArray} iv The IV used in the ciphering operation. + * @property {WordArray} salt The salt used with a key derivation function. + * @property {Cipher} algorithm The cipher algorithm. + * @property {Mode} mode The block mode used in the ciphering operation. + * @property {Padding} padding The padding scheme used in the ciphering operation. + * @property {number} blockSize The block size of the cipher. + * @property {Format} formatter The default formatting strategy to convert this cipher params object to a string. + */ + var CipherParams = C_lib.CipherParams = Base.extend({ + /** + * Initializes a newly created cipher params object. + * + * @param {Object} cipherParams An object with any of the possible cipher parameters. + * + * @example + * + * var cipherParams = CryptoJS.lib.CipherParams.create({ + * ciphertext: ciphertextWordArray, + * key: keyWordArray, + * iv: ivWordArray, + * salt: saltWordArray, + * algorithm: CryptoJS.algo.AES, + * mode: CryptoJS.mode.CBC, + * padding: CryptoJS.pad.PKCS7, + * blockSize: 4, + * formatter: CryptoJS.format.OpenSSL + * }); + */ + init: function (cipherParams) { + this.mixIn(cipherParams); + }, + + /** + * Converts this cipher params object to a string. + * + * @param {Format} formatter (Optional) The formatting strategy to use. + * + * @return {string} The stringified cipher params. + * + * @throws Error If neither the formatter nor the default formatter is set. + * + * @example + * + * var string = cipherParams + ''; + * var string = cipherParams.toString(); + * var string = cipherParams.toString(CryptoJS.format.OpenSSL); + */ + toString: function (formatter) { + return (formatter || this.formatter).stringify(this); + } + }); + + /** + * Format namespace. + */ + var C_format = C.format = {}; + + /** + * OpenSSL formatting strategy. + */ + var OpenSSLFormatter = C_format.OpenSSL = { + /** + * Converts a cipher params object to an OpenSSL-compatible string. + * + * @param {CipherParams} cipherParams The cipher params object. + * + * @return {string} The OpenSSL-compatible string. + * + * @static + * + * @example + * + * var openSSLString = CryptoJS.format.OpenSSL.stringify(cipherParams); + */ + stringify: function (cipherParams) { + var wordArray; + + // Shortcuts + var ciphertext = cipherParams.ciphertext; + var salt = cipherParams.salt; + + // Format + if (salt) { + wordArray = WordArray.create([0x53616c74, 0x65645f5f]).concat(salt).concat(ciphertext); + } else { + wordArray = ciphertext; + } + + return wordArray.toString(Base64); + }, + + /** + * Converts an OpenSSL-compatible string to a cipher params object. + * + * @param {string} openSSLStr The OpenSSL-compatible string. + * + * @return {CipherParams} The cipher params object. + * + * @static + * + * @example + * + * var cipherParams = CryptoJS.format.OpenSSL.parse(openSSLString); + */ + parse: function (openSSLStr) { + var salt; + + // Parse base64 + var ciphertext = Base64.parse(openSSLStr); + + // Shortcut + var ciphertextWords = ciphertext.words; + + // Test for salt + if (ciphertextWords[0] == 0x53616c74 && ciphertextWords[1] == 0x65645f5f) { + // Extract salt + salt = WordArray.create(ciphertextWords.slice(2, 4)); + + // Remove salt from ciphertext + ciphertextWords.splice(0, 4); + ciphertext.sigBytes -= 16; + } + + return CipherParams.create({ ciphertext: ciphertext, salt: salt }); + } + }; + + /** + * A cipher wrapper that returns ciphertext as a serializable cipher params object. + */ + var SerializableCipher = C_lib.SerializableCipher = Base.extend({ + /** + * Configuration options. + * + * @property {Formatter} format The formatting strategy to convert cipher param objects to and from a string. Default: OpenSSL + */ + cfg: Base.extend({ + format: OpenSSLFormatter + }), + + /** + * Encrypts a message. + * + * @param {Cipher} cipher The cipher algorithm to use. + * @param {WordArray|string} message The message to encrypt. + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {CipherParams} A cipher params object. + * + * @static + * + * @example + * + * var ciphertextParams = CryptoJS.lib.SerializableCipher.encrypt(CryptoJS.algo.AES, message, key); + * var ciphertextParams = CryptoJS.lib.SerializableCipher.encrypt(CryptoJS.algo.AES, message, key, { iv: iv }); + * var ciphertextParams = CryptoJS.lib.SerializableCipher.encrypt(CryptoJS.algo.AES, message, key, { iv: iv, format: CryptoJS.format.OpenSSL }); + */ + encrypt: function (cipher, message, key, cfg) { + // Apply config defaults + cfg = this.cfg.extend(cfg); + + // Encrypt + var encryptor = cipher.createEncryptor(key, cfg); + var ciphertext = encryptor.finalize(message); + + // Shortcut + var cipherCfg = encryptor.cfg; + + // Create and return serializable cipher params + return CipherParams.create({ + ciphertext: ciphertext, + key: key, + iv: cipherCfg.iv, + algorithm: cipher, + mode: cipherCfg.mode, + padding: cipherCfg.padding, + blockSize: cipher.blockSize, + formatter: cfg.format + }); + }, + + /** + * Decrypts serialized ciphertext. + * + * @param {Cipher} cipher The cipher algorithm to use. + * @param {CipherParams|string} ciphertext The ciphertext to decrypt. + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {WordArray} The plaintext. + * + * @static + * + * @example + * + * var plaintext = CryptoJS.lib.SerializableCipher.decrypt(CryptoJS.algo.AES, formattedCiphertext, key, { iv: iv, format: CryptoJS.format.OpenSSL }); + * var plaintext = CryptoJS.lib.SerializableCipher.decrypt(CryptoJS.algo.AES, ciphertextParams, key, { iv: iv, format: CryptoJS.format.OpenSSL }); + */ + decrypt: function (cipher, ciphertext, key, cfg) { + // Apply config defaults + cfg = this.cfg.extend(cfg); + + // Convert string to CipherParams + ciphertext = this._parse(ciphertext, cfg.format); + + // Decrypt + var plaintext = cipher.createDecryptor(key, cfg).finalize(ciphertext.ciphertext); + + return plaintext; + }, + + /** + * Converts serialized ciphertext to CipherParams, + * else assumed CipherParams already and returns ciphertext unchanged. + * + * @param {CipherParams|string} ciphertext The ciphertext. + * @param {Formatter} format The formatting strategy to use to parse serialized ciphertext. + * + * @return {CipherParams} The unserialized ciphertext. + * + * @static + * + * @example + * + * var ciphertextParams = CryptoJS.lib.SerializableCipher._parse(ciphertextStringOrParams, format); + */ + _parse: function (ciphertext, format) { + if (typeof ciphertext == 'string') { + return format.parse(ciphertext, this); + } else { + return ciphertext; + } + } + }); + + /** + * Key derivation function namespace. + */ + var C_kdf = C.kdf = {}; + + /** + * OpenSSL key derivation function. + */ + var OpenSSLKdf = C_kdf.OpenSSL = { + /** + * Derives a key and IV from a password. + * + * @param {string} password The password to derive from. + * @param {number} keySize The size in words of the key to generate. + * @param {number} ivSize The size in words of the IV to generate. + * @param {WordArray|string} salt (Optional) A 64-bit salt to use. If omitted, a salt will be generated randomly. + * + * @return {CipherParams} A cipher params object with the key, IV, and salt. + * + * @static + * + * @example + * + * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32); + * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32, 'saltsalt'); + */ + execute: function (password, keySize, ivSize, salt) { + // Generate random salt + if (!salt) { + salt = WordArray.random(64/8); + } + + // Derive key and IV + var key = EvpKDF.create({ keySize: keySize + ivSize }).compute(password, salt); + + // Separate key and IV + var iv = WordArray.create(key.words.slice(keySize), ivSize * 4); + key.sigBytes = keySize * 4; + + // Return params + return CipherParams.create({ key: key, iv: iv, salt: salt }); + } + }; + + /** + * A serializable cipher wrapper that derives the key from a password, + * and returns ciphertext as a serializable cipher params object. + */ + var PasswordBasedCipher = C_lib.PasswordBasedCipher = SerializableCipher.extend({ + /** + * Configuration options. + * + * @property {KDF} kdf The key derivation function to use to generate a key and IV from a password. Default: OpenSSL + */ + cfg: SerializableCipher.cfg.extend({ + kdf: OpenSSLKdf + }), + + /** + * Encrypts a message using a password. + * + * @param {Cipher} cipher The cipher algorithm to use. + * @param {WordArray|string} message The message to encrypt. + * @param {string} password The password. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {CipherParams} A cipher params object. + * + * @static + * + * @example + * + * var ciphertextParams = CryptoJS.lib.PasswordBasedCipher.encrypt(CryptoJS.algo.AES, message, 'password'); + * var ciphertextParams = CryptoJS.lib.PasswordBasedCipher.encrypt(CryptoJS.algo.AES, message, 'password', { format: CryptoJS.format.OpenSSL }); + */ + encrypt: function (cipher, message, password, cfg) { + // Apply config defaults + cfg = this.cfg.extend(cfg); + + // Derive key and other params + var derivedParams = cfg.kdf.execute(password, cipher.keySize, cipher.ivSize); + + // Add IV to config + cfg.iv = derivedParams.iv; + + // Encrypt + var ciphertext = SerializableCipher.encrypt.call(this, cipher, message, derivedParams.key, cfg); + + // Mix in derived params + ciphertext.mixIn(derivedParams); + + return ciphertext; + }, + + /** + * Decrypts serialized ciphertext using a password. + * + * @param {Cipher} cipher The cipher algorithm to use. + * @param {CipherParams|string} ciphertext The ciphertext to decrypt. + * @param {string} password The password. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {WordArray} The plaintext. + * + * @static + * + * @example + * + * var plaintext = CryptoJS.lib.PasswordBasedCipher.decrypt(CryptoJS.algo.AES, formattedCiphertext, 'password', { format: CryptoJS.format.OpenSSL }); + * var plaintext = CryptoJS.lib.PasswordBasedCipher.decrypt(CryptoJS.algo.AES, ciphertextParams, 'password', { format: CryptoJS.format.OpenSSL }); + */ + decrypt: function (cipher, ciphertext, password, cfg) { + // Apply config defaults + cfg = this.cfg.extend(cfg); + + // Convert string to CipherParams + ciphertext = this._parse(ciphertext, cfg.format); + + // Derive key and other params + var derivedParams = cfg.kdf.execute(password, cipher.keySize, cipher.ivSize, ciphertext.salt); + + // Add IV to config + cfg.iv = derivedParams.iv; + + // Decrypt + var plaintext = SerializableCipher.decrypt.call(this, cipher, ciphertext, derivedParams.key, cfg); + + return plaintext; + } + }); + }()); + + + /** + * Cipher Feedback block mode. + */ + CryptoJS.mode.CFB = (function () { + var CFB = CryptoJS.lib.BlockCipherMode.extend(); + + CFB.Encryptor = CFB.extend({ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher; + var blockSize = cipher.blockSize; + + generateKeystreamAndEncrypt.call(this, words, offset, blockSize, cipher); + + // Remember this block to use with next block + this._prevBlock = words.slice(offset, offset + blockSize); + } + }); + + CFB.Decryptor = CFB.extend({ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher; + var blockSize = cipher.blockSize; + + // Remember this block to use with next block + var thisBlock = words.slice(offset, offset + blockSize); + + generateKeystreamAndEncrypt.call(this, words, offset, blockSize, cipher); + + // This block becomes the previous block + this._prevBlock = thisBlock; + } + }); + + function generateKeystreamAndEncrypt(words, offset, blockSize, cipher) { + var keystream; + + // Shortcut + var iv = this._iv; + + // Generate keystream + if (iv) { + keystream = iv.slice(0); + + // Remove IV for subsequent blocks + this._iv = undefined; + } else { + keystream = this._prevBlock; + } + cipher.encryptBlock(keystream, 0); + + // Encrypt + for (var i = 0; i < blockSize; i++) { + words[offset + i] ^= keystream[i]; + } + } + + return CFB; + }()); + + + /** + * Counter block mode. + */ + CryptoJS.mode.CTR = (function () { + var CTR = CryptoJS.lib.BlockCipherMode.extend(); + + var Encryptor = CTR.Encryptor = CTR.extend({ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher + var blockSize = cipher.blockSize; + var iv = this._iv; + var counter = this._counter; + + // Generate keystream + if (iv) { + counter = this._counter = iv.slice(0); + + // Remove IV for subsequent blocks + this._iv = undefined; + } + var keystream = counter.slice(0); + cipher.encryptBlock(keystream, 0); + + // Increment counter + counter[blockSize - 1] = (counter[blockSize - 1] + 1) | 0 + + // Encrypt + for (var i = 0; i < blockSize; i++) { + words[offset + i] ^= keystream[i]; + } + } + }); + + CTR.Decryptor = Encryptor; + + return CTR; + }()); + + + /** @preserve + * Counter block mode compatible with Dr Brian Gladman fileenc.c + * derived from CryptoJS.mode.CTR + * Jan Hruby jhruby.web@gmail.com + */ + CryptoJS.mode.CTRGladman = (function () { + var CTRGladman = CryptoJS.lib.BlockCipherMode.extend(); + + function incWord(word) + { + if (((word >> 24) & 0xff) === 0xff) { //overflow + var b1 = (word >> 16)&0xff; + var b2 = (word >> 8)&0xff; + var b3 = word & 0xff; + + if (b1 === 0xff) // overflow b1 + { + b1 = 0; + if (b2 === 0xff) + { + b2 = 0; + if (b3 === 0xff) + { + b3 = 0; + } + else + { + ++b3; + } + } + else + { + ++b2; + } + } + else + { + ++b1; + } + + word = 0; + word += (b1 << 16); + word += (b2 << 8); + word += b3; + } + else + { + word += (0x01 << 24); + } + return word; + } + + function incCounter(counter) + { + if ((counter[0] = incWord(counter[0])) === 0) + { + // encr_data in fileenc.c from Dr Brian Gladman's counts only with DWORD j < 8 + counter[1] = incWord(counter[1]); + } + return counter; + } + + var Encryptor = CTRGladman.Encryptor = CTRGladman.extend({ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher + var blockSize = cipher.blockSize; + var iv = this._iv; + var counter = this._counter; + + // Generate keystream + if (iv) { + counter = this._counter = iv.slice(0); + + // Remove IV for subsequent blocks + this._iv = undefined; + } + + incCounter(counter); + + var keystream = counter.slice(0); + cipher.encryptBlock(keystream, 0); + + // Encrypt + for (var i = 0; i < blockSize; i++) { + words[offset + i] ^= keystream[i]; + } + } + }); + + CTRGladman.Decryptor = Encryptor; + + return CTRGladman; + }()); + + + + + /** + * Output Feedback block mode. + */ + CryptoJS.mode.OFB = (function () { + var OFB = CryptoJS.lib.BlockCipherMode.extend(); + + var Encryptor = OFB.Encryptor = OFB.extend({ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher + var blockSize = cipher.blockSize; + var iv = this._iv; + var keystream = this._keystream; + + // Generate keystream + if (iv) { + keystream = this._keystream = iv.slice(0); + + // Remove IV for subsequent blocks + this._iv = undefined; + } + cipher.encryptBlock(keystream, 0); + + // Encrypt + for (var i = 0; i < blockSize; i++) { + words[offset + i] ^= keystream[i]; + } + } + }); + + OFB.Decryptor = Encryptor; + + return OFB; + }()); + + + /** + * Electronic Codebook block mode. + */ + CryptoJS.mode.ECB = (function () { + var ECB = CryptoJS.lib.BlockCipherMode.extend(); + + ECB.Encryptor = ECB.extend({ + processBlock: function (words, offset) { + this._cipher.encryptBlock(words, offset); + } + }); + + ECB.Decryptor = ECB.extend({ + processBlock: function (words, offset) { + this._cipher.decryptBlock(words, offset); + } + }); + + return ECB; + }()); + + + /** + * ANSI X.923 padding strategy. + */ + CryptoJS.pad.AnsiX923 = { + pad: function (data, blockSize) { + // Shortcuts + var dataSigBytes = data.sigBytes; + var blockSizeBytes = blockSize * 4; + + // Count padding bytes + var nPaddingBytes = blockSizeBytes - dataSigBytes % blockSizeBytes; + + // Compute last byte position + var lastBytePos = dataSigBytes + nPaddingBytes - 1; + + // Pad + data.clamp(); + data.words[lastBytePos >>> 2] |= nPaddingBytes << (24 - (lastBytePos % 4) * 8); + data.sigBytes += nPaddingBytes; + }, + + unpad: function (data) { + // Get number of padding bytes from last byte + var nPaddingBytes = data.words[(data.sigBytes - 1) >>> 2] & 0xff; + + // Remove padding + data.sigBytes -= nPaddingBytes; + } + }; + + + /** + * ISO 10126 padding strategy. + */ + CryptoJS.pad.Iso10126 = { + pad: function (data, blockSize) { + // Shortcut + var blockSizeBytes = blockSize * 4; + + // Count padding bytes + var nPaddingBytes = blockSizeBytes - data.sigBytes % blockSizeBytes; + + // Pad + data.concat(CryptoJS.lib.WordArray.random(nPaddingBytes - 1)). + concat(CryptoJS.lib.WordArray.create([nPaddingBytes << 24], 1)); + }, + + unpad: function (data) { + // Get number of padding bytes from last byte + var nPaddingBytes = data.words[(data.sigBytes - 1) >>> 2] & 0xff; + + // Remove padding + data.sigBytes -= nPaddingBytes; + } + }; + + + /** + * ISO/IEC 9797-1 Padding Method 2. + */ + CryptoJS.pad.Iso97971 = { + pad: function (data, blockSize) { + // Add 0x80 byte + data.concat(CryptoJS.lib.WordArray.create([0x80000000], 1)); + + // Zero pad the rest + CryptoJS.pad.ZeroPadding.pad(data, blockSize); + }, + + unpad: function (data) { + // Remove zero padding + CryptoJS.pad.ZeroPadding.unpad(data); + + // Remove one more byte -- the 0x80 byte + data.sigBytes--; + } + }; + + + /** + * Zero padding strategy. + */ + CryptoJS.pad.ZeroPadding = { + pad: function (data, blockSize) { + // Shortcut + var blockSizeBytes = blockSize * 4; + + // Pad + data.clamp(); + data.sigBytes += blockSizeBytes - ((data.sigBytes % blockSizeBytes) || blockSizeBytes); + }, + + unpad: function (data) { + // Shortcut + var dataWords = data.words; + + // Unpad + var i = data.sigBytes - 1; + for (var i = data.sigBytes - 1; i >= 0; i--) { + if (((dataWords[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff)) { + data.sigBytes = i + 1; + break; + } + } + } + }; + + + /** + * A noop padding strategy. + */ + CryptoJS.pad.NoPadding = { + pad: function () { + }, + + unpad: function () { + } + }; + + + (function (undefined) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var CipherParams = C_lib.CipherParams; + var C_enc = C.enc; + var Hex = C_enc.Hex; + var C_format = C.format; + + var HexFormatter = C_format.Hex = { + /** + * Converts the ciphertext of a cipher params object to a hexadecimally encoded string. + * + * @param {CipherParams} cipherParams The cipher params object. + * + * @return {string} The hexadecimally encoded string. + * + * @static + * + * @example + * + * var hexString = CryptoJS.format.Hex.stringify(cipherParams); + */ + stringify: function (cipherParams) { + return cipherParams.ciphertext.toString(Hex); + }, + + /** + * Converts a hexadecimally encoded ciphertext string to a cipher params object. + * + * @param {string} input The hexadecimally encoded string. + * + * @return {CipherParams} The cipher params object. + * + * @static + * + * @example + * + * var cipherParams = CryptoJS.format.Hex.parse(hexString); + */ + parse: function (input) { + var ciphertext = Hex.parse(input); + return CipherParams.create({ ciphertext: ciphertext }); + } + }; + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var BlockCipher = C_lib.BlockCipher; + var C_algo = C.algo; + + // Lookup tables + var SBOX = []; + var INV_SBOX = []; + var SUB_MIX_0 = []; + var SUB_MIX_1 = []; + var SUB_MIX_2 = []; + var SUB_MIX_3 = []; + var INV_SUB_MIX_0 = []; + var INV_SUB_MIX_1 = []; + var INV_SUB_MIX_2 = []; + var INV_SUB_MIX_3 = []; + + // Compute lookup tables + (function () { + // Compute double table + var d = []; + for (var i = 0; i < 256; i++) { + if (i < 128) { + d[i] = i << 1; + } else { + d[i] = (i << 1) ^ 0x11b; + } + } + + // Walk GF(2^8) + var x = 0; + var xi = 0; + for (var i = 0; i < 256; i++) { + // Compute sbox + var sx = xi ^ (xi << 1) ^ (xi << 2) ^ (xi << 3) ^ (xi << 4); + sx = (sx >>> 8) ^ (sx & 0xff) ^ 0x63; + SBOX[x] = sx; + INV_SBOX[sx] = x; + + // Compute multiplication + var x2 = d[x]; + var x4 = d[x2]; + var x8 = d[x4]; + + // Compute sub bytes, mix columns tables + var t = (d[sx] * 0x101) ^ (sx * 0x1010100); + SUB_MIX_0[x] = (t << 24) | (t >>> 8); + SUB_MIX_1[x] = (t << 16) | (t >>> 16); + SUB_MIX_2[x] = (t << 8) | (t >>> 24); + SUB_MIX_3[x] = t; + + // Compute inv sub bytes, inv mix columns tables + var t = (x8 * 0x1010101) ^ (x4 * 0x10001) ^ (x2 * 0x101) ^ (x * 0x1010100); + INV_SUB_MIX_0[sx] = (t << 24) | (t >>> 8); + INV_SUB_MIX_1[sx] = (t << 16) | (t >>> 16); + INV_SUB_MIX_2[sx] = (t << 8) | (t >>> 24); + INV_SUB_MIX_3[sx] = t; + + // Compute next counter + if (!x) { + x = xi = 1; + } else { + x = x2 ^ d[d[d[x8 ^ x2]]]; + xi ^= d[d[xi]]; + } + } + }()); + + // Precomputed Rcon lookup + var RCON = [0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36]; + + /** + * AES block cipher algorithm. + */ + var AES = C_algo.AES = BlockCipher.extend({ + _doReset: function () { + var t; + + // Skip reset of nRounds has been set before and key did not change + if (this._nRounds && this._keyPriorReset === this._key) { + return; + } + + // Shortcuts + var key = this._keyPriorReset = this._key; + var keyWords = key.words; + var keySize = key.sigBytes / 4; + + // Compute number of rounds + var nRounds = this._nRounds = keySize + 6; + + // Compute number of key schedule rows + var ksRows = (nRounds + 1) * 4; + + // Compute key schedule + var keySchedule = this._keySchedule = []; + for (var ksRow = 0; ksRow < ksRows; ksRow++) { + if (ksRow < keySize) { + keySchedule[ksRow] = keyWords[ksRow]; + } else { + t = keySchedule[ksRow - 1]; + + if (!(ksRow % keySize)) { + // Rot word + t = (t << 8) | (t >>> 24); + + // Sub word + t = (SBOX[t >>> 24] << 24) | (SBOX[(t >>> 16) & 0xff] << 16) | (SBOX[(t >>> 8) & 0xff] << 8) | SBOX[t & 0xff]; + + // Mix Rcon + t ^= RCON[(ksRow / keySize) | 0] << 24; + } else if (keySize > 6 && ksRow % keySize == 4) { + // Sub word + t = (SBOX[t >>> 24] << 24) | (SBOX[(t >>> 16) & 0xff] << 16) | (SBOX[(t >>> 8) & 0xff] << 8) | SBOX[t & 0xff]; + } + + keySchedule[ksRow] = keySchedule[ksRow - keySize] ^ t; + } + } + + // Compute inv key schedule + var invKeySchedule = this._invKeySchedule = []; + for (var invKsRow = 0; invKsRow < ksRows; invKsRow++) { + var ksRow = ksRows - invKsRow; + + if (invKsRow % 4) { + var t = keySchedule[ksRow]; + } else { + var t = keySchedule[ksRow - 4]; + } + + if (invKsRow < 4 || ksRow <= 4) { + invKeySchedule[invKsRow] = t; + } else { + invKeySchedule[invKsRow] = INV_SUB_MIX_0[SBOX[t >>> 24]] ^ INV_SUB_MIX_1[SBOX[(t >>> 16) & 0xff]] ^ + INV_SUB_MIX_2[SBOX[(t >>> 8) & 0xff]] ^ INV_SUB_MIX_3[SBOX[t & 0xff]]; + } + } + }, + + encryptBlock: function (M, offset) { + this._doCryptBlock(M, offset, this._keySchedule, SUB_MIX_0, SUB_MIX_1, SUB_MIX_2, SUB_MIX_3, SBOX); + }, + + decryptBlock: function (M, offset) { + // Swap 2nd and 4th rows + var t = M[offset + 1]; + M[offset + 1] = M[offset + 3]; + M[offset + 3] = t; + + this._doCryptBlock(M, offset, this._invKeySchedule, INV_SUB_MIX_0, INV_SUB_MIX_1, INV_SUB_MIX_2, INV_SUB_MIX_3, INV_SBOX); + + // Inv swap 2nd and 4th rows + var t = M[offset + 1]; + M[offset + 1] = M[offset + 3]; + M[offset + 3] = t; + }, + + _doCryptBlock: function (M, offset, keySchedule, SUB_MIX_0, SUB_MIX_1, SUB_MIX_2, SUB_MIX_3, SBOX) { + // Shortcut + var nRounds = this._nRounds; + + // Get input, add round key + var s0 = M[offset] ^ keySchedule[0]; + var s1 = M[offset + 1] ^ keySchedule[1]; + var s2 = M[offset + 2] ^ keySchedule[2]; + var s3 = M[offset + 3] ^ keySchedule[3]; + + // Key schedule row counter + var ksRow = 4; + + // Rounds + for (var round = 1; round < nRounds; round++) { + // Shift rows, sub bytes, mix columns, add round key + var t0 = SUB_MIX_0[s0 >>> 24] ^ SUB_MIX_1[(s1 >>> 16) & 0xff] ^ SUB_MIX_2[(s2 >>> 8) & 0xff] ^ SUB_MIX_3[s3 & 0xff] ^ keySchedule[ksRow++]; + var t1 = SUB_MIX_0[s1 >>> 24] ^ SUB_MIX_1[(s2 >>> 16) & 0xff] ^ SUB_MIX_2[(s3 >>> 8) & 0xff] ^ SUB_MIX_3[s0 & 0xff] ^ keySchedule[ksRow++]; + var t2 = SUB_MIX_0[s2 >>> 24] ^ SUB_MIX_1[(s3 >>> 16) & 0xff] ^ SUB_MIX_2[(s0 >>> 8) & 0xff] ^ SUB_MIX_3[s1 & 0xff] ^ keySchedule[ksRow++]; + var t3 = SUB_MIX_0[s3 >>> 24] ^ SUB_MIX_1[(s0 >>> 16) & 0xff] ^ SUB_MIX_2[(s1 >>> 8) & 0xff] ^ SUB_MIX_3[s2 & 0xff] ^ keySchedule[ksRow++]; + + // Update state + s0 = t0; + s1 = t1; + s2 = t2; + s3 = t3; + } + + // Shift rows, sub bytes, add round key + var t0 = ((SBOX[s0 >>> 24] << 24) | (SBOX[(s1 >>> 16) & 0xff] << 16) | (SBOX[(s2 >>> 8) & 0xff] << 8) | SBOX[s3 & 0xff]) ^ keySchedule[ksRow++]; + var t1 = ((SBOX[s1 >>> 24] << 24) | (SBOX[(s2 >>> 16) & 0xff] << 16) | (SBOX[(s3 >>> 8) & 0xff] << 8) | SBOX[s0 & 0xff]) ^ keySchedule[ksRow++]; + var t2 = ((SBOX[s2 >>> 24] << 24) | (SBOX[(s3 >>> 16) & 0xff] << 16) | (SBOX[(s0 >>> 8) & 0xff] << 8) | SBOX[s1 & 0xff]) ^ keySchedule[ksRow++]; + var t3 = ((SBOX[s3 >>> 24] << 24) | (SBOX[(s0 >>> 16) & 0xff] << 16) | (SBOX[(s1 >>> 8) & 0xff] << 8) | SBOX[s2 & 0xff]) ^ keySchedule[ksRow++]; + + // Set output + M[offset] = t0; + M[offset + 1] = t1; + M[offset + 2] = t2; + M[offset + 3] = t3; + }, + + keySize: 256/32 + }); + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.AES.encrypt(message, key, cfg); + * var plaintext = CryptoJS.AES.decrypt(ciphertext, key, cfg); + */ + C.AES = BlockCipher._createHelper(AES); + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var BlockCipher = C_lib.BlockCipher; + var C_algo = C.algo; + + // Permuted Choice 1 constants + var PC1 = [ + 57, 49, 41, 33, 25, 17, 9, 1, + 58, 50, 42, 34, 26, 18, 10, 2, + 59, 51, 43, 35, 27, 19, 11, 3, + 60, 52, 44, 36, 63, 55, 47, 39, + 31, 23, 15, 7, 62, 54, 46, 38, + 30, 22, 14, 6, 61, 53, 45, 37, + 29, 21, 13, 5, 28, 20, 12, 4 + ]; + + // Permuted Choice 2 constants + var PC2 = [ + 14, 17, 11, 24, 1, 5, + 3, 28, 15, 6, 21, 10, + 23, 19, 12, 4, 26, 8, + 16, 7, 27, 20, 13, 2, + 41, 52, 31, 37, 47, 55, + 30, 40, 51, 45, 33, 48, + 44, 49, 39, 56, 34, 53, + 46, 42, 50, 36, 29, 32 + ]; + + // Cumulative bit shift constants + var BIT_SHIFTS = [1, 2, 4, 6, 8, 10, 12, 14, 15, 17, 19, 21, 23, 25, 27, 28]; + + // SBOXes and round permutation constants + var SBOX_P = [ + { + 0x0: 0x808200, + 0x10000000: 0x8000, + 0x20000000: 0x808002, + 0x30000000: 0x2, + 0x40000000: 0x200, + 0x50000000: 0x808202, + 0x60000000: 0x800202, + 0x70000000: 0x800000, + 0x80000000: 0x202, + 0x90000000: 0x800200, + 0xa0000000: 0x8200, + 0xb0000000: 0x808000, + 0xc0000000: 0x8002, + 0xd0000000: 0x800002, + 0xe0000000: 0x0, + 0xf0000000: 0x8202, + 0x8000000: 0x0, + 0x18000000: 0x808202, + 0x28000000: 0x8202, + 0x38000000: 0x8000, + 0x48000000: 0x808200, + 0x58000000: 0x200, + 0x68000000: 0x808002, + 0x78000000: 0x2, + 0x88000000: 0x800200, + 0x98000000: 0x8200, + 0xa8000000: 0x808000, + 0xb8000000: 0x800202, + 0xc8000000: 0x800002, + 0xd8000000: 0x8002, + 0xe8000000: 0x202, + 0xf8000000: 0x800000, + 0x1: 0x8000, + 0x10000001: 0x2, + 0x20000001: 0x808200, + 0x30000001: 0x800000, + 0x40000001: 0x808002, + 0x50000001: 0x8200, + 0x60000001: 0x200, + 0x70000001: 0x800202, + 0x80000001: 0x808202, + 0x90000001: 0x808000, + 0xa0000001: 0x800002, + 0xb0000001: 0x8202, + 0xc0000001: 0x202, + 0xd0000001: 0x800200, + 0xe0000001: 0x8002, + 0xf0000001: 0x0, + 0x8000001: 0x808202, + 0x18000001: 0x808000, + 0x28000001: 0x800000, + 0x38000001: 0x200, + 0x48000001: 0x8000, + 0x58000001: 0x800002, + 0x68000001: 0x2, + 0x78000001: 0x8202, + 0x88000001: 0x8002, + 0x98000001: 0x800202, + 0xa8000001: 0x202, + 0xb8000001: 0x808200, + 0xc8000001: 0x800200, + 0xd8000001: 0x0, + 0xe8000001: 0x8200, + 0xf8000001: 0x808002 + }, + { + 0x0: 0x40084010, + 0x1000000: 0x4000, + 0x2000000: 0x80000, + 0x3000000: 0x40080010, + 0x4000000: 0x40000010, + 0x5000000: 0x40084000, + 0x6000000: 0x40004000, + 0x7000000: 0x10, + 0x8000000: 0x84000, + 0x9000000: 0x40004010, + 0xa000000: 0x40000000, + 0xb000000: 0x84010, + 0xc000000: 0x80010, + 0xd000000: 0x0, + 0xe000000: 0x4010, + 0xf000000: 0x40080000, + 0x800000: 0x40004000, + 0x1800000: 0x84010, + 0x2800000: 0x10, + 0x3800000: 0x40004010, + 0x4800000: 0x40084010, + 0x5800000: 0x40000000, + 0x6800000: 0x80000, + 0x7800000: 0x40080010, + 0x8800000: 0x80010, + 0x9800000: 0x0, + 0xa800000: 0x4000, + 0xb800000: 0x40080000, + 0xc800000: 0x40000010, + 0xd800000: 0x84000, + 0xe800000: 0x40084000, + 0xf800000: 0x4010, + 0x10000000: 0x0, + 0x11000000: 0x40080010, + 0x12000000: 0x40004010, + 0x13000000: 0x40084000, + 0x14000000: 0x40080000, + 0x15000000: 0x10, + 0x16000000: 0x84010, + 0x17000000: 0x4000, + 0x18000000: 0x4010, + 0x19000000: 0x80000, + 0x1a000000: 0x80010, + 0x1b000000: 0x40000010, + 0x1c000000: 0x84000, + 0x1d000000: 0x40004000, + 0x1e000000: 0x40000000, + 0x1f000000: 0x40084010, + 0x10800000: 0x84010, + 0x11800000: 0x80000, + 0x12800000: 0x40080000, + 0x13800000: 0x4000, + 0x14800000: 0x40004000, + 0x15800000: 0x40084010, + 0x16800000: 0x10, + 0x17800000: 0x40000000, + 0x18800000: 0x40084000, + 0x19800000: 0x40000010, + 0x1a800000: 0x40004010, + 0x1b800000: 0x80010, + 0x1c800000: 0x0, + 0x1d800000: 0x4010, + 0x1e800000: 0x40080010, + 0x1f800000: 0x84000 + }, + { + 0x0: 0x104, + 0x100000: 0x0, + 0x200000: 0x4000100, + 0x300000: 0x10104, + 0x400000: 0x10004, + 0x500000: 0x4000004, + 0x600000: 0x4010104, + 0x700000: 0x4010000, + 0x800000: 0x4000000, + 0x900000: 0x4010100, + 0xa00000: 0x10100, + 0xb00000: 0x4010004, + 0xc00000: 0x4000104, + 0xd00000: 0x10000, + 0xe00000: 0x4, + 0xf00000: 0x100, + 0x80000: 0x4010100, + 0x180000: 0x4010004, + 0x280000: 0x0, + 0x380000: 0x4000100, + 0x480000: 0x4000004, + 0x580000: 0x10000, + 0x680000: 0x10004, + 0x780000: 0x104, + 0x880000: 0x4, + 0x980000: 0x100, + 0xa80000: 0x4010000, + 0xb80000: 0x10104, + 0xc80000: 0x10100, + 0xd80000: 0x4000104, + 0xe80000: 0x4010104, + 0xf80000: 0x4000000, + 0x1000000: 0x4010100, + 0x1100000: 0x10004, + 0x1200000: 0x10000, + 0x1300000: 0x4000100, + 0x1400000: 0x100, + 0x1500000: 0x4010104, + 0x1600000: 0x4000004, + 0x1700000: 0x0, + 0x1800000: 0x4000104, + 0x1900000: 0x4000000, + 0x1a00000: 0x4, + 0x1b00000: 0x10100, + 0x1c00000: 0x4010000, + 0x1d00000: 0x104, + 0x1e00000: 0x10104, + 0x1f00000: 0x4010004, + 0x1080000: 0x4000000, + 0x1180000: 0x104, + 0x1280000: 0x4010100, + 0x1380000: 0x0, + 0x1480000: 0x10004, + 0x1580000: 0x4000100, + 0x1680000: 0x100, + 0x1780000: 0x4010004, + 0x1880000: 0x10000, + 0x1980000: 0x4010104, + 0x1a80000: 0x10104, + 0x1b80000: 0x4000004, + 0x1c80000: 0x4000104, + 0x1d80000: 0x4010000, + 0x1e80000: 0x4, + 0x1f80000: 0x10100 + }, + { + 0x0: 0x80401000, + 0x10000: 0x80001040, + 0x20000: 0x401040, + 0x30000: 0x80400000, + 0x40000: 0x0, + 0x50000: 0x401000, + 0x60000: 0x80000040, + 0x70000: 0x400040, + 0x80000: 0x80000000, + 0x90000: 0x400000, + 0xa0000: 0x40, + 0xb0000: 0x80001000, + 0xc0000: 0x80400040, + 0xd0000: 0x1040, + 0xe0000: 0x1000, + 0xf0000: 0x80401040, + 0x8000: 0x80001040, + 0x18000: 0x40, + 0x28000: 0x80400040, + 0x38000: 0x80001000, + 0x48000: 0x401000, + 0x58000: 0x80401040, + 0x68000: 0x0, + 0x78000: 0x80400000, + 0x88000: 0x1000, + 0x98000: 0x80401000, + 0xa8000: 0x400000, + 0xb8000: 0x1040, + 0xc8000: 0x80000000, + 0xd8000: 0x400040, + 0xe8000: 0x401040, + 0xf8000: 0x80000040, + 0x100000: 0x400040, + 0x110000: 0x401000, + 0x120000: 0x80000040, + 0x130000: 0x0, + 0x140000: 0x1040, + 0x150000: 0x80400040, + 0x160000: 0x80401000, + 0x170000: 0x80001040, + 0x180000: 0x80401040, + 0x190000: 0x80000000, + 0x1a0000: 0x80400000, + 0x1b0000: 0x401040, + 0x1c0000: 0x80001000, + 0x1d0000: 0x400000, + 0x1e0000: 0x40, + 0x1f0000: 0x1000, + 0x108000: 0x80400000, + 0x118000: 0x80401040, + 0x128000: 0x0, + 0x138000: 0x401000, + 0x148000: 0x400040, + 0x158000: 0x80000000, + 0x168000: 0x80001040, + 0x178000: 0x40, + 0x188000: 0x80000040, + 0x198000: 0x1000, + 0x1a8000: 0x80001000, + 0x1b8000: 0x80400040, + 0x1c8000: 0x1040, + 0x1d8000: 0x80401000, + 0x1e8000: 0x400000, + 0x1f8000: 0x401040 + }, + { + 0x0: 0x80, + 0x1000: 0x1040000, + 0x2000: 0x40000, + 0x3000: 0x20000000, + 0x4000: 0x20040080, + 0x5000: 0x1000080, + 0x6000: 0x21000080, + 0x7000: 0x40080, + 0x8000: 0x1000000, + 0x9000: 0x20040000, + 0xa000: 0x20000080, + 0xb000: 0x21040080, + 0xc000: 0x21040000, + 0xd000: 0x0, + 0xe000: 0x1040080, + 0xf000: 0x21000000, + 0x800: 0x1040080, + 0x1800: 0x21000080, + 0x2800: 0x80, + 0x3800: 0x1040000, + 0x4800: 0x40000, + 0x5800: 0x20040080, + 0x6800: 0x21040000, + 0x7800: 0x20000000, + 0x8800: 0x20040000, + 0x9800: 0x0, + 0xa800: 0x21040080, + 0xb800: 0x1000080, + 0xc800: 0x20000080, + 0xd800: 0x21000000, + 0xe800: 0x1000000, + 0xf800: 0x40080, + 0x10000: 0x40000, + 0x11000: 0x80, + 0x12000: 0x20000000, + 0x13000: 0x21000080, + 0x14000: 0x1000080, + 0x15000: 0x21040000, + 0x16000: 0x20040080, + 0x17000: 0x1000000, + 0x18000: 0x21040080, + 0x19000: 0x21000000, + 0x1a000: 0x1040000, + 0x1b000: 0x20040000, + 0x1c000: 0x40080, + 0x1d000: 0x20000080, + 0x1e000: 0x0, + 0x1f000: 0x1040080, + 0x10800: 0x21000080, + 0x11800: 0x1000000, + 0x12800: 0x1040000, + 0x13800: 0x20040080, + 0x14800: 0x20000000, + 0x15800: 0x1040080, + 0x16800: 0x80, + 0x17800: 0x21040000, + 0x18800: 0x40080, + 0x19800: 0x21040080, + 0x1a800: 0x0, + 0x1b800: 0x21000000, + 0x1c800: 0x1000080, + 0x1d800: 0x40000, + 0x1e800: 0x20040000, + 0x1f800: 0x20000080 + }, + { + 0x0: 0x10000008, + 0x100: 0x2000, + 0x200: 0x10200000, + 0x300: 0x10202008, + 0x400: 0x10002000, + 0x500: 0x200000, + 0x600: 0x200008, + 0x700: 0x10000000, + 0x800: 0x0, + 0x900: 0x10002008, + 0xa00: 0x202000, + 0xb00: 0x8, + 0xc00: 0x10200008, + 0xd00: 0x202008, + 0xe00: 0x2008, + 0xf00: 0x10202000, + 0x80: 0x10200000, + 0x180: 0x10202008, + 0x280: 0x8, + 0x380: 0x200000, + 0x480: 0x202008, + 0x580: 0x10000008, + 0x680: 0x10002000, + 0x780: 0x2008, + 0x880: 0x200008, + 0x980: 0x2000, + 0xa80: 0x10002008, + 0xb80: 0x10200008, + 0xc80: 0x0, + 0xd80: 0x10202000, + 0xe80: 0x202000, + 0xf80: 0x10000000, + 0x1000: 0x10002000, + 0x1100: 0x10200008, + 0x1200: 0x10202008, + 0x1300: 0x2008, + 0x1400: 0x200000, + 0x1500: 0x10000000, + 0x1600: 0x10000008, + 0x1700: 0x202000, + 0x1800: 0x202008, + 0x1900: 0x0, + 0x1a00: 0x8, + 0x1b00: 0x10200000, + 0x1c00: 0x2000, + 0x1d00: 0x10002008, + 0x1e00: 0x10202000, + 0x1f00: 0x200008, + 0x1080: 0x8, + 0x1180: 0x202000, + 0x1280: 0x200000, + 0x1380: 0x10000008, + 0x1480: 0x10002000, + 0x1580: 0x2008, + 0x1680: 0x10202008, + 0x1780: 0x10200000, + 0x1880: 0x10202000, + 0x1980: 0x10200008, + 0x1a80: 0x2000, + 0x1b80: 0x202008, + 0x1c80: 0x200008, + 0x1d80: 0x0, + 0x1e80: 0x10000000, + 0x1f80: 0x10002008 + }, + { + 0x0: 0x100000, + 0x10: 0x2000401, + 0x20: 0x400, + 0x30: 0x100401, + 0x40: 0x2100401, + 0x50: 0x0, + 0x60: 0x1, + 0x70: 0x2100001, + 0x80: 0x2000400, + 0x90: 0x100001, + 0xa0: 0x2000001, + 0xb0: 0x2100400, + 0xc0: 0x2100000, + 0xd0: 0x401, + 0xe0: 0x100400, + 0xf0: 0x2000000, + 0x8: 0x2100001, + 0x18: 0x0, + 0x28: 0x2000401, + 0x38: 0x2100400, + 0x48: 0x100000, + 0x58: 0x2000001, + 0x68: 0x2000000, + 0x78: 0x401, + 0x88: 0x100401, + 0x98: 0x2000400, + 0xa8: 0x2100000, + 0xb8: 0x100001, + 0xc8: 0x400, + 0xd8: 0x2100401, + 0xe8: 0x1, + 0xf8: 0x100400, + 0x100: 0x2000000, + 0x110: 0x100000, + 0x120: 0x2000401, + 0x130: 0x2100001, + 0x140: 0x100001, + 0x150: 0x2000400, + 0x160: 0x2100400, + 0x170: 0x100401, + 0x180: 0x401, + 0x190: 0x2100401, + 0x1a0: 0x100400, + 0x1b0: 0x1, + 0x1c0: 0x0, + 0x1d0: 0x2100000, + 0x1e0: 0x2000001, + 0x1f0: 0x400, + 0x108: 0x100400, + 0x118: 0x2000401, + 0x128: 0x2100001, + 0x138: 0x1, + 0x148: 0x2000000, + 0x158: 0x100000, + 0x168: 0x401, + 0x178: 0x2100400, + 0x188: 0x2000001, + 0x198: 0x2100000, + 0x1a8: 0x0, + 0x1b8: 0x2100401, + 0x1c8: 0x100401, + 0x1d8: 0x400, + 0x1e8: 0x2000400, + 0x1f8: 0x100001 + }, + { + 0x0: 0x8000820, + 0x1: 0x20000, + 0x2: 0x8000000, + 0x3: 0x20, + 0x4: 0x20020, + 0x5: 0x8020820, + 0x6: 0x8020800, + 0x7: 0x800, + 0x8: 0x8020000, + 0x9: 0x8000800, + 0xa: 0x20800, + 0xb: 0x8020020, + 0xc: 0x820, + 0xd: 0x0, + 0xe: 0x8000020, + 0xf: 0x20820, + 0x80000000: 0x800, + 0x80000001: 0x8020820, + 0x80000002: 0x8000820, + 0x80000003: 0x8000000, + 0x80000004: 0x8020000, + 0x80000005: 0x20800, + 0x80000006: 0x20820, + 0x80000007: 0x20, + 0x80000008: 0x8000020, + 0x80000009: 0x820, + 0x8000000a: 0x20020, + 0x8000000b: 0x8020800, + 0x8000000c: 0x0, + 0x8000000d: 0x8020020, + 0x8000000e: 0x8000800, + 0x8000000f: 0x20000, + 0x10: 0x20820, + 0x11: 0x8020800, + 0x12: 0x20, + 0x13: 0x800, + 0x14: 0x8000800, + 0x15: 0x8000020, + 0x16: 0x8020020, + 0x17: 0x20000, + 0x18: 0x0, + 0x19: 0x20020, + 0x1a: 0x8020000, + 0x1b: 0x8000820, + 0x1c: 0x8020820, + 0x1d: 0x20800, + 0x1e: 0x820, + 0x1f: 0x8000000, + 0x80000010: 0x20000, + 0x80000011: 0x800, + 0x80000012: 0x8020020, + 0x80000013: 0x20820, + 0x80000014: 0x20, + 0x80000015: 0x8020000, + 0x80000016: 0x8000000, + 0x80000017: 0x8000820, + 0x80000018: 0x8020820, + 0x80000019: 0x8000020, + 0x8000001a: 0x8000800, + 0x8000001b: 0x0, + 0x8000001c: 0x20800, + 0x8000001d: 0x820, + 0x8000001e: 0x20020, + 0x8000001f: 0x8020800 + } + ]; + + // Masks that select the SBOX input + var SBOX_MASK = [ + 0xf8000001, 0x1f800000, 0x01f80000, 0x001f8000, + 0x0001f800, 0x00001f80, 0x000001f8, 0x8000001f + ]; + + /** + * DES block cipher algorithm. + */ + var DES = C_algo.DES = BlockCipher.extend({ + _doReset: function () { + // Shortcuts + var key = this._key; + var keyWords = key.words; + + // Select 56 bits according to PC1 + var keyBits = []; + for (var i = 0; i < 56; i++) { + var keyBitPos = PC1[i] - 1; + keyBits[i] = (keyWords[keyBitPos >>> 5] >>> (31 - keyBitPos % 32)) & 1; + } + + // Assemble 16 subkeys + var subKeys = this._subKeys = []; + for (var nSubKey = 0; nSubKey < 16; nSubKey++) { + // Create subkey + var subKey = subKeys[nSubKey] = []; + + // Shortcut + var bitShift = BIT_SHIFTS[nSubKey]; + + // Select 48 bits according to PC2 + for (var i = 0; i < 24; i++) { + // Select from the left 28 key bits + subKey[(i / 6) | 0] |= keyBits[((PC2[i] - 1) + bitShift) % 28] << (31 - i % 6); + + // Select from the right 28 key bits + subKey[4 + ((i / 6) | 0)] |= keyBits[28 + (((PC2[i + 24] - 1) + bitShift) % 28)] << (31 - i % 6); + } + + // Since each subkey is applied to an expanded 32-bit input, + // the subkey can be broken into 8 values scaled to 32-bits, + // which allows the key to be used without expansion + subKey[0] = (subKey[0] << 1) | (subKey[0] >>> 31); + for (var i = 1; i < 7; i++) { + subKey[i] = subKey[i] >>> ((i - 1) * 4 + 3); + } + subKey[7] = (subKey[7] << 5) | (subKey[7] >>> 27); + } + + // Compute inverse subkeys + var invSubKeys = this._invSubKeys = []; + for (var i = 0; i < 16; i++) { + invSubKeys[i] = subKeys[15 - i]; + } + }, + + encryptBlock: function (M, offset) { + this._doCryptBlock(M, offset, this._subKeys); + }, + + decryptBlock: function (M, offset) { + this._doCryptBlock(M, offset, this._invSubKeys); + }, + + _doCryptBlock: function (M, offset, subKeys) { + // Get input + this._lBlock = M[offset]; + this._rBlock = M[offset + 1]; + + // Initial permutation + exchangeLR.call(this, 4, 0x0f0f0f0f); + exchangeLR.call(this, 16, 0x0000ffff); + exchangeRL.call(this, 2, 0x33333333); + exchangeRL.call(this, 8, 0x00ff00ff); + exchangeLR.call(this, 1, 0x55555555); + + // Rounds + for (var round = 0; round < 16; round++) { + // Shortcuts + var subKey = subKeys[round]; + var lBlock = this._lBlock; + var rBlock = this._rBlock; + + // Feistel function + var f = 0; + for (var i = 0; i < 8; i++) { + f |= SBOX_P[i][((rBlock ^ subKey[i]) & SBOX_MASK[i]) >>> 0]; + } + this._lBlock = rBlock; + this._rBlock = lBlock ^ f; + } + + // Undo swap from last round + var t = this._lBlock; + this._lBlock = this._rBlock; + this._rBlock = t; + + // Final permutation + exchangeLR.call(this, 1, 0x55555555); + exchangeRL.call(this, 8, 0x00ff00ff); + exchangeRL.call(this, 2, 0x33333333); + exchangeLR.call(this, 16, 0x0000ffff); + exchangeLR.call(this, 4, 0x0f0f0f0f); + + // Set output + M[offset] = this._lBlock; + M[offset + 1] = this._rBlock; + }, + + keySize: 64/32, + + ivSize: 64/32, + + blockSize: 64/32 + }); + + // Swap bits across the left and right words + function exchangeLR(offset, mask) { + var t = ((this._lBlock >>> offset) ^ this._rBlock) & mask; + this._rBlock ^= t; + this._lBlock ^= t << offset; + } + + function exchangeRL(offset, mask) { + var t = ((this._rBlock >>> offset) ^ this._lBlock) & mask; + this._lBlock ^= t; + this._rBlock ^= t << offset; + } + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.DES.encrypt(message, key, cfg); + * var plaintext = CryptoJS.DES.decrypt(ciphertext, key, cfg); + */ + C.DES = BlockCipher._createHelper(DES); + + /** + * Triple-DES block cipher algorithm. + */ + var TripleDES = C_algo.TripleDES = BlockCipher.extend({ + _doReset: function () { + // Shortcuts + var key = this._key; + var keyWords = key.words; + // Make sure the key length is valid (64, 128 or >= 192 bit) + if (keyWords.length !== 2 && keyWords.length !== 4 && keyWords.length < 6) { + throw new Error('Invalid key length - 3DES requires the key length to be 64, 128, 192 or >192.'); + } + + // Extend the key according to the keying options defined in 3DES standard + var key1 = keyWords.slice(0, 2); + var key2 = keyWords.length < 4 ? keyWords.slice(0, 2) : keyWords.slice(2, 4); + var key3 = keyWords.length < 6 ? keyWords.slice(0, 2) : keyWords.slice(4, 6); + + // Create DES instances + this._des1 = DES.createEncryptor(WordArray.create(key1)); + this._des2 = DES.createEncryptor(WordArray.create(key2)); + this._des3 = DES.createEncryptor(WordArray.create(key3)); + }, + + encryptBlock: function (M, offset) { + this._des1.encryptBlock(M, offset); + this._des2.decryptBlock(M, offset); + this._des3.encryptBlock(M, offset); + }, + + decryptBlock: function (M, offset) { + this._des3.decryptBlock(M, offset); + this._des2.encryptBlock(M, offset); + this._des1.decryptBlock(M, offset); + }, + + keySize: 192/32, + + ivSize: 64/32, + + blockSize: 64/32 + }); + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.TripleDES.encrypt(message, key, cfg); + * var plaintext = CryptoJS.TripleDES.decrypt(ciphertext, key, cfg); + */ + C.TripleDES = BlockCipher._createHelper(TripleDES); + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var StreamCipher = C_lib.StreamCipher; + var C_algo = C.algo; + + /** + * RC4 stream cipher algorithm. + */ + var RC4 = C_algo.RC4 = StreamCipher.extend({ + _doReset: function () { + // Shortcuts + var key = this._key; + var keyWords = key.words; + var keySigBytes = key.sigBytes; + + // Init sbox + var S = this._S = []; + for (var i = 0; i < 256; i++) { + S[i] = i; + } + + // Key setup + for (var i = 0, j = 0; i < 256; i++) { + var keyByteIndex = i % keySigBytes; + var keyByte = (keyWords[keyByteIndex >>> 2] >>> (24 - (keyByteIndex % 4) * 8)) & 0xff; + + j = (j + S[i] + keyByte) % 256; + + // Swap + var t = S[i]; + S[i] = S[j]; + S[j] = t; + } + + // Counters + this._i = this._j = 0; + }, + + _doProcessBlock: function (M, offset) { + M[offset] ^= generateKeystreamWord.call(this); + }, + + keySize: 256/32, + + ivSize: 0 + }); + + function generateKeystreamWord() { + // Shortcuts + var S = this._S; + var i = this._i; + var j = this._j; + + // Generate keystream word + var keystreamWord = 0; + for (var n = 0; n < 4; n++) { + i = (i + 1) % 256; + j = (j + S[i]) % 256; + + // Swap + var t = S[i]; + S[i] = S[j]; + S[j] = t; + + keystreamWord |= S[(S[i] + S[j]) % 256] << (24 - n * 8); + } + + // Update counters + this._i = i; + this._j = j; + + return keystreamWord; + } + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.RC4.encrypt(message, key, cfg); + * var plaintext = CryptoJS.RC4.decrypt(ciphertext, key, cfg); + */ + C.RC4 = StreamCipher._createHelper(RC4); + + /** + * Modified RC4 stream cipher algorithm. + */ + var RC4Drop = C_algo.RC4Drop = RC4.extend({ + /** + * Configuration options. + * + * @property {number} drop The number of keystream words to drop. Default 192 + */ + cfg: RC4.cfg.extend({ + drop: 192 + }), + + _doReset: function () { + RC4._doReset.call(this); + + // Drop + for (var i = this.cfg.drop; i > 0; i--) { + generateKeystreamWord.call(this); + } + } + }); + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.RC4Drop.encrypt(message, key, cfg); + * var plaintext = CryptoJS.RC4Drop.decrypt(ciphertext, key, cfg); + */ + C.RC4Drop = StreamCipher._createHelper(RC4Drop); + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var StreamCipher = C_lib.StreamCipher; + var C_algo = C.algo; + + // Reusable objects + var S = []; + var C_ = []; + var G = []; + + /** + * Rabbit stream cipher algorithm + */ + var Rabbit = C_algo.Rabbit = StreamCipher.extend({ + _doReset: function () { + // Shortcuts + var K = this._key.words; + var iv = this.cfg.iv; + + // Swap endian + for (var i = 0; i < 4; i++) { + K[i] = (((K[i] << 8) | (K[i] >>> 24)) & 0x00ff00ff) | + (((K[i] << 24) | (K[i] >>> 8)) & 0xff00ff00); + } + + // Generate initial state values + var X = this._X = [ + K[0], (K[3] << 16) | (K[2] >>> 16), + K[1], (K[0] << 16) | (K[3] >>> 16), + K[2], (K[1] << 16) | (K[0] >>> 16), + K[3], (K[2] << 16) | (K[1] >>> 16) + ]; + + // Generate initial counter values + var C = this._C = [ + (K[2] << 16) | (K[2] >>> 16), (K[0] & 0xffff0000) | (K[1] & 0x0000ffff), + (K[3] << 16) | (K[3] >>> 16), (K[1] & 0xffff0000) | (K[2] & 0x0000ffff), + (K[0] << 16) | (K[0] >>> 16), (K[2] & 0xffff0000) | (K[3] & 0x0000ffff), + (K[1] << 16) | (K[1] >>> 16), (K[3] & 0xffff0000) | (K[0] & 0x0000ffff) + ]; + + // Carry bit + this._b = 0; + + // Iterate the system four times + for (var i = 0; i < 4; i++) { + nextState.call(this); + } + + // Modify the counters + for (var i = 0; i < 8; i++) { + C[i] ^= X[(i + 4) & 7]; + } + + // IV setup + if (iv) { + // Shortcuts + var IV = iv.words; + var IV_0 = IV[0]; + var IV_1 = IV[1]; + + // Generate four subvectors + var i0 = (((IV_0 << 8) | (IV_0 >>> 24)) & 0x00ff00ff) | (((IV_0 << 24) | (IV_0 >>> 8)) & 0xff00ff00); + var i2 = (((IV_1 << 8) | (IV_1 >>> 24)) & 0x00ff00ff) | (((IV_1 << 24) | (IV_1 >>> 8)) & 0xff00ff00); + var i1 = (i0 >>> 16) | (i2 & 0xffff0000); + var i3 = (i2 << 16) | (i0 & 0x0000ffff); + + // Modify counter values + C[0] ^= i0; + C[1] ^= i1; + C[2] ^= i2; + C[3] ^= i3; + C[4] ^= i0; + C[5] ^= i1; + C[6] ^= i2; + C[7] ^= i3; + + // Iterate the system four times + for (var i = 0; i < 4; i++) { + nextState.call(this); + } + } + }, + + _doProcessBlock: function (M, offset) { + // Shortcut + var X = this._X; + + // Iterate the system + nextState.call(this); + + // Generate four keystream words + S[0] = X[0] ^ (X[5] >>> 16) ^ (X[3] << 16); + S[1] = X[2] ^ (X[7] >>> 16) ^ (X[5] << 16); + S[2] = X[4] ^ (X[1] >>> 16) ^ (X[7] << 16); + S[3] = X[6] ^ (X[3] >>> 16) ^ (X[1] << 16); + + for (var i = 0; i < 4; i++) { + // Swap endian + S[i] = (((S[i] << 8) | (S[i] >>> 24)) & 0x00ff00ff) | + (((S[i] << 24) | (S[i] >>> 8)) & 0xff00ff00); + + // Encrypt + M[offset + i] ^= S[i]; + } + }, + + blockSize: 128/32, + + ivSize: 64/32 + }); + + function nextState() { + // Shortcuts + var X = this._X; + var C = this._C; + + // Save old counter values + for (var i = 0; i < 8; i++) { + C_[i] = C[i]; + } + + // Calculate new counter values + C[0] = (C[0] + 0x4d34d34d + this._b) | 0; + C[1] = (C[1] + 0xd34d34d3 + ((C[0] >>> 0) < (C_[0] >>> 0) ? 1 : 0)) | 0; + C[2] = (C[2] + 0x34d34d34 + ((C[1] >>> 0) < (C_[1] >>> 0) ? 1 : 0)) | 0; + C[3] = (C[3] + 0x4d34d34d + ((C[2] >>> 0) < (C_[2] >>> 0) ? 1 : 0)) | 0; + C[4] = (C[4] + 0xd34d34d3 + ((C[3] >>> 0) < (C_[3] >>> 0) ? 1 : 0)) | 0; + C[5] = (C[5] + 0x34d34d34 + ((C[4] >>> 0) < (C_[4] >>> 0) ? 1 : 0)) | 0; + C[6] = (C[6] + 0x4d34d34d + ((C[5] >>> 0) < (C_[5] >>> 0) ? 1 : 0)) | 0; + C[7] = (C[7] + 0xd34d34d3 + ((C[6] >>> 0) < (C_[6] >>> 0) ? 1 : 0)) | 0; + this._b = (C[7] >>> 0) < (C_[7] >>> 0) ? 1 : 0; + + // Calculate the g-values + for (var i = 0; i < 8; i++) { + var gx = X[i] + C[i]; + + // Construct high and low argument for squaring + var ga = gx & 0xffff; + var gb = gx >>> 16; + + // Calculate high and low result of squaring + var gh = ((((ga * ga) >>> 17) + ga * gb) >>> 15) + gb * gb; + var gl = (((gx & 0xffff0000) * gx) | 0) + (((gx & 0x0000ffff) * gx) | 0); + + // High XOR low + G[i] = gh ^ gl; + } + + // Calculate new state values + X[0] = (G[0] + ((G[7] << 16) | (G[7] >>> 16)) + ((G[6] << 16) | (G[6] >>> 16))) | 0; + X[1] = (G[1] + ((G[0] << 8) | (G[0] >>> 24)) + G[7]) | 0; + X[2] = (G[2] + ((G[1] << 16) | (G[1] >>> 16)) + ((G[0] << 16) | (G[0] >>> 16))) | 0; + X[3] = (G[3] + ((G[2] << 8) | (G[2] >>> 24)) + G[1]) | 0; + X[4] = (G[4] + ((G[3] << 16) | (G[3] >>> 16)) + ((G[2] << 16) | (G[2] >>> 16))) | 0; + X[5] = (G[5] + ((G[4] << 8) | (G[4] >>> 24)) + G[3]) | 0; + X[6] = (G[6] + ((G[5] << 16) | (G[5] >>> 16)) + ((G[4] << 16) | (G[4] >>> 16))) | 0; + X[7] = (G[7] + ((G[6] << 8) | (G[6] >>> 24)) + G[5]) | 0; + } + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.Rabbit.encrypt(message, key, cfg); + * var plaintext = CryptoJS.Rabbit.decrypt(ciphertext, key, cfg); + */ + C.Rabbit = StreamCipher._createHelper(Rabbit); + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var StreamCipher = C_lib.StreamCipher; + var C_algo = C.algo; + + // Reusable objects + var S = []; + var C_ = []; + var G = []; + + /** + * Rabbit stream cipher algorithm. + * + * This is a legacy version that neglected to convert the key to little-endian. + * This error doesn't affect the cipher's security, + * but it does affect its compatibility with other implementations. + */ + var RabbitLegacy = C_algo.RabbitLegacy = StreamCipher.extend({ + _doReset: function () { + // Shortcuts + var K = this._key.words; + var iv = this.cfg.iv; + + // Generate initial state values + var X = this._X = [ + K[0], (K[3] << 16) | (K[2] >>> 16), + K[1], (K[0] << 16) | (K[3] >>> 16), + K[2], (K[1] << 16) | (K[0] >>> 16), + K[3], (K[2] << 16) | (K[1] >>> 16) + ]; + + // Generate initial counter values + var C = this._C = [ + (K[2] << 16) | (K[2] >>> 16), (K[0] & 0xffff0000) | (K[1] & 0x0000ffff), + (K[3] << 16) | (K[3] >>> 16), (K[1] & 0xffff0000) | (K[2] & 0x0000ffff), + (K[0] << 16) | (K[0] >>> 16), (K[2] & 0xffff0000) | (K[3] & 0x0000ffff), + (K[1] << 16) | (K[1] >>> 16), (K[3] & 0xffff0000) | (K[0] & 0x0000ffff) + ]; + + // Carry bit + this._b = 0; + + // Iterate the system four times + for (var i = 0; i < 4; i++) { + nextState.call(this); + } + + // Modify the counters + for (var i = 0; i < 8; i++) { + C[i] ^= X[(i + 4) & 7]; + } + + // IV setup + if (iv) { + // Shortcuts + var IV = iv.words; + var IV_0 = IV[0]; + var IV_1 = IV[1]; + + // Generate four subvectors + var i0 = (((IV_0 << 8) | (IV_0 >>> 24)) & 0x00ff00ff) | (((IV_0 << 24) | (IV_0 >>> 8)) & 0xff00ff00); + var i2 = (((IV_1 << 8) | (IV_1 >>> 24)) & 0x00ff00ff) | (((IV_1 << 24) | (IV_1 >>> 8)) & 0xff00ff00); + var i1 = (i0 >>> 16) | (i2 & 0xffff0000); + var i3 = (i2 << 16) | (i0 & 0x0000ffff); + + // Modify counter values + C[0] ^= i0; + C[1] ^= i1; + C[2] ^= i2; + C[3] ^= i3; + C[4] ^= i0; + C[5] ^= i1; + C[6] ^= i2; + C[7] ^= i3; + + // Iterate the system four times + for (var i = 0; i < 4; i++) { + nextState.call(this); + } + } + }, + + _doProcessBlock: function (M, offset) { + // Shortcut + var X = this._X; + + // Iterate the system + nextState.call(this); + + // Generate four keystream words + S[0] = X[0] ^ (X[5] >>> 16) ^ (X[3] << 16); + S[1] = X[2] ^ (X[7] >>> 16) ^ (X[5] << 16); + S[2] = X[4] ^ (X[1] >>> 16) ^ (X[7] << 16); + S[3] = X[6] ^ (X[3] >>> 16) ^ (X[1] << 16); + + for (var i = 0; i < 4; i++) { + // Swap endian + S[i] = (((S[i] << 8) | (S[i] >>> 24)) & 0x00ff00ff) | + (((S[i] << 24) | (S[i] >>> 8)) & 0xff00ff00); + + // Encrypt + M[offset + i] ^= S[i]; + } + }, + + blockSize: 128/32, + + ivSize: 64/32 + }); + + function nextState() { + // Shortcuts + var X = this._X; + var C = this._C; + + // Save old counter values + for (var i = 0; i < 8; i++) { + C_[i] = C[i]; + } + + // Calculate new counter values + C[0] = (C[0] + 0x4d34d34d + this._b) | 0; + C[1] = (C[1] + 0xd34d34d3 + ((C[0] >>> 0) < (C_[0] >>> 0) ? 1 : 0)) | 0; + C[2] = (C[2] + 0x34d34d34 + ((C[1] >>> 0) < (C_[1] >>> 0) ? 1 : 0)) | 0; + C[3] = (C[3] + 0x4d34d34d + ((C[2] >>> 0) < (C_[2] >>> 0) ? 1 : 0)) | 0; + C[4] = (C[4] + 0xd34d34d3 + ((C[3] >>> 0) < (C_[3] >>> 0) ? 1 : 0)) | 0; + C[5] = (C[5] + 0x34d34d34 + ((C[4] >>> 0) < (C_[4] >>> 0) ? 1 : 0)) | 0; + C[6] = (C[6] + 0x4d34d34d + ((C[5] >>> 0) < (C_[5] >>> 0) ? 1 : 0)) | 0; + C[7] = (C[7] + 0xd34d34d3 + ((C[6] >>> 0) < (C_[6] >>> 0) ? 1 : 0)) | 0; + this._b = (C[7] >>> 0) < (C_[7] >>> 0) ? 1 : 0; + + // Calculate the g-values + for (var i = 0; i < 8; i++) { + var gx = X[i] + C[i]; + + // Construct high and low argument for squaring + var ga = gx & 0xffff; + var gb = gx >>> 16; + + // Calculate high and low result of squaring + var gh = ((((ga * ga) >>> 17) + ga * gb) >>> 15) + gb * gb; + var gl = (((gx & 0xffff0000) * gx) | 0) + (((gx & 0x0000ffff) * gx) | 0); + + // High XOR low + G[i] = gh ^ gl; + } + + // Calculate new state values + X[0] = (G[0] + ((G[7] << 16) | (G[7] >>> 16)) + ((G[6] << 16) | (G[6] >>> 16))) | 0; + X[1] = (G[1] + ((G[0] << 8) | (G[0] >>> 24)) + G[7]) | 0; + X[2] = (G[2] + ((G[1] << 16) | (G[1] >>> 16)) + ((G[0] << 16) | (G[0] >>> 16))) | 0; + X[3] = (G[3] + ((G[2] << 8) | (G[2] >>> 24)) + G[1]) | 0; + X[4] = (G[4] + ((G[3] << 16) | (G[3] >>> 16)) + ((G[2] << 16) | (G[2] >>> 16))) | 0; + X[5] = (G[5] + ((G[4] << 8) | (G[4] >>> 24)) + G[3]) | 0; + X[6] = (G[6] + ((G[5] << 16) | (G[5] >>> 16)) + ((G[4] << 16) | (G[4] >>> 16))) | 0; + X[7] = (G[7] + ((G[6] << 8) | (G[6] >>> 24)) + G[5]) | 0; + } + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.RabbitLegacy.encrypt(message, key, cfg); + * var plaintext = CryptoJS.RabbitLegacy.decrypt(ciphertext, key, cfg); + */ + C.RabbitLegacy = StreamCipher._createHelper(RabbitLegacy); + }()); + + + return CryptoJS; + +})); \ No newline at end of file diff --git a/app/src/main/assets/js/lib/gbk.js b/app/src/main/assets/js/lib/gbk.js new file mode 100644 index 0000000000..40d43c5317 --- /dev/null +++ b/app/src/main/assets/js/lib/gbk.js @@ -0,0 +1,65 @@ +export function gbkTool() { + var data = function(zipData) { + var re = zipData + .replace(/#(\d+)\$/g, function(a, b) { + return Array(+b + 3).join('#'); + }) + .replace(/#/g, '####') + .replace(/(\w\w):([\w#]+)(?:,|$)/g, function(a, hd, dt) { + return dt.replace(/../g, function(a) { + if (a != '##') { + return hd + a; + } else { + return a; + } + }); + }); + return re; + }('4e:020405060f12171f20212326292e2f313335373c40414244464a5155575a5b6263646567686a6b6c6d6e6f727475767778797a7b7c7d7f808182838485878a#909697999c9d9ea3aaafb0b1b4b6b7b8b9bcbdbec8cccfd0d2dadbdce0e2e6e7e9edeeeff1f4f8f9fafcfe,4f:00020304050607080b0c12131415161c1d212328292c2d2e31333537393b3e3f40414244454748494a4b4c525456616266686a6b6d6e7172757778797a7d8081828586878a8c8e909293959698999a9c9e9fa1a2a4abadb0b1b2b3b4b6b7b8b9babbbcbdbec0c1c2c6c7c8c9cbcccdd2d3d4d5d6d9dbe0e2e4e5e7ebecf0f2f4f5f6f7f9fbfcfdff,50:000102030405060708090a#0b0e1011131516171b1d1e20222324272b2f303132333435363738393b3d3f404142444546494a4b4d5051525354565758595b5d5e5f6061626364666768696a6b6d6e6f70717273747578797a7c7d818283848687898a8b8c8e8f909192939495969798999a9b9c9d9e9fa0a1a2a4a6aaabadaeafb0b1b3b4b5b6b7b8b9bcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdced0d1d2d3d4d5d7d8d9dbdcdddedfe0e1e2e3e4e5e8e9eaebeff0f1f2f4f6f7f8f9fafcfdfeff,51:00010203040508#090a0c0d0e0f1011131415161718191a1b1c1d1e1f2022232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e42474a4c4e4f5052535758595b5d5e5f606163646667696a6f727a7e7f838486878a8b8e8f90919394989a9d9e9fa1a3a6a7a8a9aaadaeb4b8b9babebfc1c2c3c5c8cacdced0d2d3d4d5d6d7d8d9dadcdedfe2e3e5e6e7e8e9eaeceef1f2f4f7fe,52:0405090b0c0f101314151c1e1f2122232526272a2c2f313234353c3e4445464748494b4e4f5253555758#595a5b5d5f6062636466686b6c6d6e7071737475767778797a7b7c7e808384858687898a8b8c8d8e8f91929495969798999a9ca4a5a6a7aeafb0b4b5b6b7b8b9babbbcbdc0c1c2c4c5c6c8cacccdcecfd1d3d4d5d7d9dadbdcdddee0e1e2e3e5e6e7e8e9eaebecedeeeff1f2f3f4f5f6f7f8fbfcfd,53:0102030407090a0b0c0e11121314181b1c1e1f2224252728292b2c2d2f3031323334353637383c3d404244464b4c4d505458595b5d65686a6c6d7276797b7c7d7e80818387888a8e8f#90919293949697999b9c9ea0a1a4a7aaabacadafb0b1b2b3b4b5b7b8b9babcbdbec0c3c4c5c6c7cecfd0d2d3d5dadcdddee1e2e7f4fafeff,54:000205070b1418191a1c2224252a303336373a3d3f4142444547494c4d4e4f515a5d5e5f6061636567696a6b6c6d6e6f7074797a7e7f8183858788898a8d919397989c9e9fa0a1a2a5aeb0b2b5b6b7b9babcbec3c5cacbd6d8dbe0e1e2e3e4ebeceff0f1f4f5f6f7f8f9fbfe,55:0002030405080a0b0c0d0e121315161718191a1c1d1e1f212526#28292b2d3234353638393a3b3d40424547484b4c4d4e4f515253545758595a5b5d5e5f60626368696b6f7071727374797a7d7f85868c8d8e9092939596979a9b9ea0a1a2a3a4a5a6a8a9aaabacadaeafb0b2b4b6b8babcbfc0c1c2c3c6c7c8cacbcecfd0d5d7d8d9dadbdee0e2e7e9edeef0f1f4f6f8f9fafbfcff,56:0203040506070a0b0d1011121314151617191a1c1d202122252628292a2b2e2f30333537383a3c3d3e404142434445464748494a4b4f5051525355565a5b5d5e5f6061#636566676d6e6f70727374757778797a7d7e7f80818283848788898a8b8c8d9091929495969798999a9b9c9d9e9fa0a1a2a4a5a6a7a8a9aaabacadaeb0b1b2b3b4b5b6b8b9babbbdbebfc0c1c2c3c4c5c6c7c8c9cbcccdcecfd0d1d2d3d5d6d8d9dce3e5e6e7e8e9eaeceeeff2f3f6f7f8fbfc,57:00010205070b0c0d0e0f101112131415161718191a1b1d1e202122242526272b313234353637383c3d3f414344454648494b52535455565859626365676c6e707172747578797a7d7e7f80#818788898a8d8e8f90919495969798999a9c9d9e9fa5a8aaacafb0b1b3b5b6b7b9babbbcbdbebfc0c1c4c5c6c7c8c9cacccdd0d1d3d6d7dbdcdee1e2e3e5e6e7e8e9eaebeceef0f1f2f3f5f6f7fbfcfeff,58:0103040508090a0c0e0f101213141617181a1b1c1d1f222325262728292b2c2d2e2f31323334363738393a3b3c3d3e3f4041424345464748494a4b4e4f505253555657595a5b5c5d5f6061626364666768696a6d6e6f707172737475767778797a7b7c7d7f82848687888a8b8c#8d8e8f909194959697989b9c9da0a1a2a3a4a5a6a7aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbdbebfc0c2c3c4c6c7c8c9cacbcccdcecfd0d2d3d4d6d7d8d9dadbdcdddedfe0e1e2e3e5e6e7e8e9eaedeff1f2f4f5f7f8fafbfcfdfeff,59:000103050608090a0b0c0e1011121317181b1d1e2021222326282c30323335363b3d3e3f404345464a4c4d505253595b5c5d5e5f616364666768696a6b6c6d6e6f70717275777a7b7c7e7f8085898b8c8e8f90919495989a9b9c9d9fa0a1a2a6#a7acadb0b1b3b4b5b6b7b8babcbdbfc0c1c2c3c4c5c7c8c9cccdcecfd5d6d9dbdedfe0e1e2e4e6e7e9eaebedeeeff0f1f2f3f4f5f6f7f8fafcfdfe,5a:00020a0b0d0e0f101214151617191a1b1d1e2122242627282a2b2c2d2e2f3033353738393a3b3d3e3f414243444547484b4c4d4e4f5051525354565758595b5c5d5e5f60616364656668696b6c6d6e6f7071727378797b7c7d7e808182838485868788898a8b8c8d8e8f9091939495969798999c9d9e9fa0a1a2a3a4a5a6a7a8a9abac#adaeafb0b1b4b6b7b9babbbcbdbfc0c3c4c5c6c7c8cacbcdcecfd0d1d3d5d7d9dadbdddedfe2e4e5e7e8eaecedeeeff0f2f3f4f5f6f7f8f9fafbfcfdfeff,5b:0001020304050607080a0b0c0d0e0f10111213141518191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303133353638393a3b3c3d3e3f4142434445464748494a4b4c4d4e4f52565e606167686b6d6e6f7274767778797b7c7e7f82868a8d8e90919294969fa7a8a9acadaeafb1b2b7babbbcc0c1c3c8c9cacbcdcecf#d1d4d5d6d7d8d9dadbdce0e2e3e6e7e9eaebecedeff1f2f3f4f5f6f7fdfe,5c:0002030507080b0c0d0e10121317191b1e1f2021232628292a2b2d2e2f303233353637434446474c4d5253545657585a5b5c5d5f62646768696a6b6c6d70727374757677787b7c7d7e808384858687898a8b8e8f9293959d9e9fa0a1a4a5a6a7a8aaaeafb0b2b4b6b9babbbcbec0c2c3c5c6c7c8c9cacccdcecfd0d1d3d4d5d6d7d8dadbdcdddedfe0e2e3e7e9ebeceeeff1f2f3f4f5f6f7f8f9fafcfdfeff,5d:00#01040508090a0b0c0d0f10111213151718191a1c1d1f2021222325282a2b2c2f3031323335363738393a3b3c3f4041424344454648494d4e4f5051525354555657595a5c5e5f6061626364656667686a6d6e7071727375767778797a7b7c7d7e7f8081838485868788898a8b8c8d8e8f9091929394959697989a9b9c9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b8b9babbbcbdbebfc0c1c2c3c4c6c7c8c9cacbcccecfd0d1d2d3d4d5d6d7d8d9dadcdfe0e3e4eaeced#f0f5f6f8f9fafbfcff,5e:000407090a0b0d0e1213171e1f20212223242528292a2b2c2f303233343536393a3e3f404143464748494a4b4d4e4f50515253565758595a5c5d5f60636465666768696a6b6c6d6e6f70717577797e8182838588898c8d8e92989b9da1a2a3a4a8a9aaabacaeafb0b1b2b4babbbcbdbfc0c1c2c3c4c5c6c7c8cbcccdcecfd0d4d5d7d8d9dadcdddedfe0e1e2e3e4e5e6e7e9ebecedeeeff0f1f2f3f5f8f9fbfcfd,5f:050607090c0d0e10121416191a1c1d1e21222324#282b2c2e30323334353637383b3d3e3f4142434445464748494a4b4c4d4e4f5154595a5b5c5e5f60636567686b6e6f72747576787a7d7e7f83868d8e8f919394969a9b9d9e9fa0a2a3a4a5a6a7a9abacafb0b1b2b3b4b6b8b9babbbebfc0c1c2c7c8cacbced3d4d5dadbdcdedfe2e3e5e6e8e9eceff0f2f3f4f6f7f9fafc,60:0708090b0c10111317181a1e1f2223242c2d2e3031323334363738393a3d3e404445464748494a4c4e4f5153545657585b5c5e5f606165666e71727475777e80#8182858687888a8b8e8f909193959798999c9ea1a2a4a5a7a9aaaeb0b3b5b6b7b9babdbebfc0c1c2c3c4c7c8c9cccdcecfd0d2d3d4d6d7d9dbdee1e2e3e4e5eaf1f2f5f7f8fbfcfdfeff,61:02030405070a0b0c1011121314161718191b1c1d1e21222528292a2c2d2e2f303132333435363738393a3b3c3d3e4041424344454647494b4d4f50525354565758595a5b5c5e5f606163646566696a6b6c6d6e6f717273747678797a7b7c7d7e7f808182838485868788898a8c8d8f9091929395#969798999a9b9c9e9fa0a1a2a3a4a5a6aaabadaeafb0b1b2b3b4b5b6b8b9babbbcbdbfc0c1c3c4c5c6c7c9cccdcecfd0d3d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e7e8e9eaebecedeeeff0f1f2f3f4f6f7f8f9fafbfcfdfe,62:00010203040507091314191c1d1e2023262728292b2d2f303132353638393a3b3c424445464a4f50555657595a5c5d5e5f6061626465687172747577787a7b7d818283858687888b8c8d8e8f9094999c9d9ea3a6a7a9aaadaeafb0b2b3b4b6b7b8babec0c1#c3cbcfd1d5dddee0e1e4eaebf0f2f5f8f9fafb,63:00030405060a0b0c0d0f10121314151718191c2627292c2d2e30313334353637383b3c3e3f40414447484a51525354565758595a5b5c5d60646566686a6b6c6f707273747578797c7d7e7f81838485868b8d9193949597999a9b9c9d9e9fa1a4a6abafb1b2b5b6b9bbbdbfc0c1c2c3c5c7c8cacbccd1d3d4d5d7d8d9dadbdcdddfe2e4e5e6e7e8ebeceeeff0f1f3f5f7f9fafbfcfe,64:0304060708090a0d0e111215161718191a1d1f222324#252728292b2e2f3031323335363738393b3c3e404243494b4c4d4e4f505153555657595a5b5c5d5f60616263646566686a6b6c6e6f70717273747576777b7c7d7e7f8081838688898a8b8c8d8e8f90939497989a9b9c9d9fa0a1a2a3a5a6a7a8aaabafb1b2b3b4b6b9bbbdbebfc1c3c4c6c7c8c9cacbcccfd1d3d4d5d6d9dadbdcdddfe0e1e3e5e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff,65:01020304050607080a0b0c0d0e0f10111314151617191a1b1c1d1e1f2021#222324262728292a2c2d30313233373a3c3d404142434446474a4b4d4e5052535457585a5c5f606164656768696a6d6e6f7173757678797a7b7c7d7e7f8081828384858688898a8d8e8f92949596989a9d9ea0a2a3a6a8aaacaeb1b2b3b4b5b6b7b8babbbebfc0c2c7c8c9cacdd0d1d3d4d5d8d9dadbdcdddedfe1e3e4eaebf2f3f4f5f8f9fbfcfdfeff,66:0104050708090b0d1011121617181a1b1c1e2122232426292a2b2c2e3032333738393a3b3d3f40424445464748494a4d4e505158#595b5c5d5e6062636567696a6b6c6d7172737578797b7c7d7f808183858688898a8b8d8e8f909293949598999a9b9c9e9fa0a1a2a3a4a5a6a9aaabacadafb0b1b2b3b5b6b7b8babbbcbdbfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8dadedfe0e1e2e3e4e5e7e8eaebecedeeeff1f5f6f8fafbfd,67:010203040506070c0e0f1112131618191a1c1e20212223242527292e303233363738393b3c3e3f414445474a4b4d5254555758595a5b5d62636466676b6c6e717476#78797a7b7d8082838586888a8c8d8e8f9192939496999b9fa0a1a4a6a9acaeb1b2b4b9babbbcbdbebfc0c2c5c6c7c8c9cacbcccdced5d6d7dbdfe1e3e4e6e7e8eaebedeef2f5f6f7f8f9fafbfcfe,68:01020304060d1012141518191a1b1c1e1f20222324252627282b2c2d2e2f30313435363a3b3f474b4d4f52565758595a5b5c5d5e5f6a6c6d6e6f707172737578797a7b7c7d7e7f8082848788898a8b8c8d8e90919294959698999a9b9c9d9e9fa0a1a3a4a5a9aaabacaeb1b2b4b6b7b8#b9babbbcbdbebfc1c3c4c5c6c7c8cacccecfd0d1d3d4d6d7d9dbdcdddedfe1e2e4e5e6e7e8e9eaebecedeff2f3f4f6f7f8fbfdfeff,69:00020304060708090a0c0f11131415161718191a1b1c1d1e21222325262728292a2b2c2e2f313233353637383a3b3c3e4041434445464748494a4b4c4d4e4f50515253555658595b5c5f616264656768696a6c6d6f7072737475767a7b7d7e7f8183858a8b8c8e8f909192939697999a9d9e9fa0a1a2a3a4a5a6a9aaacaeafb0b2b3b5b6b8b9babcbd#bebfc0c2c3c4c5c6c7c8c9cbcdcfd1d2d3d5d6d7d8d9dadcdddee1e2e3e4e5e6e7e8e9eaebeceeeff0f1f3f4f5f6f7f8f9fafbfcfe,6a:000102030405060708090b0c0d0e0f10111213141516191a1b1c1d1e20222324252627292b2c2d2e30323334363738393a3b3c3f40414243454648494a4b4c4d4e4f515253545556575a5c5d5e5f60626364666768696a6b6c6d6e6f70727374757677787a7b7d7e7f81828385868788898a8b8c8d8f929394959698999a9b9c9d9e9fa1a2a3a4a5a6#a7a8aaadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff,6b:000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f252628292a2b2c2d2e2f303133343536383b3c3d3f4041424445484a4b4d4e4f5051525354555657585a5b5c5d5e5f606168696b6c6d6e6f7071727374757677787a7d7e7f808588#8c8e8f909194959798999c9d9e9fa0a2a3a4a5a6a7a8a9abacadaeafb0b1b2b6b8b9babbbcbdbec0c3c4c6c7c8c9caccced0d1d8dadcdddedfe0e2e3e4e5e6e7e8e9ecedeef0f1f2f4f6f7f8fafbfcfeff,6c:000102030408090a0b0c0e12171c1d1e2023252b2c2d31333637393a3b3c3e3f434445484b4c4d4e4f5152535658595a62636566676b6c6d6e6f71737577787a7b7c7f8084878a8b8d8e9192959697989a9c9d9ea0a2a8acafb0b4b5b6b7bac0c1c2c3c6c7c8cbcdcecfd1d2d8#d9dadcdddfe4e6e7e9ecedf2f4f9ff,6d:000203050608090a0d0f101113141516181c1d1f20212223242628292c2d2f30343637383a3f404244494c50555657585b5d5f6162646567686b6c6d707172737576797a7b7d7e7f8081838486878a8b8d8f9092969798999a9ca2a5acadb0b1b3b4b6b7b9babbbcbdbec1c2c3c8c9cacdcecfd0d2d3d4d5d7dadbdcdfe2e3e5e7e8e9eaedeff0f2f4f5f6f8fafdfeff,6e:0001020304060708090b0f12131518191b1c1e1f222627282a2c2e30313335#3637393b3c3d3e3f40414245464748494a4b4c4f5051525557595a5c5d5e606162636465666768696a6c6d6f707172737475767778797a7b7c7d8081828487888a8b8c8d8e91929394959697999a9b9d9ea0a1a3a4a6a8a9abacadaeb0b3b5b8b9bcbebfc0c3c4c5c6c8c9cacccdced0d2d6d8d9dbdcdde3e7eaebecedeeeff0f1f2f3f5f6f7f8fafbfcfdfeff,6f:000103040507080a0b0c0d0e101112161718191a1b1c1d1e1f212223252627282c2e303234353738393a3b3c3d3f404142#43444548494a4c4e4f5051525354555657595a5b5d5f60616364656768696a6b6c6f707173757677797b7d7e7f808182838586878a8b8f909192939495969798999a9b9d9e9fa0a2a3a4a5a6a8a9aaabacadaeafb0b1b2b4b5b7b8babbbcbdbebfc1c3c4c5c6c7c8cacbcccdcecfd0d3d4d5d6d7d8d9dadbdcdddfe2e3e4e5e6e7e8e9eaebecedf0f1f2f3f4f5f6f7f8f9fafbfcfdfeff,70:000102030405060708090a0b0c0d0e0f1012131415161718191c1d1e1f2021222425262728292a#2b2c2d2e2f30313233343637383a3b3c3d3e3f404142434445464748494a4b4d4e505152535455565758595a5b5c5d5f606162636465666768696a6e7172737477797a7b7d818283848687888b8c8d8f90919397989a9b9e9fa0a1a2a3a4a5a6a7a8a9aab0b2b4b5b6babebfc4c5c6c7c9cbcccdcecfd0d1d2d3d4d5d6d7dadcdddee0e1e2e3e5eaeef0f1f2f3f4f5f6f8fafbfcfeff,71:0001020304050607080b0c0d0e0f111214171b1c1d1e1f2021222324252728292a2b2c2d2e323334#353738393a3b3c3d3e3f4041424344464748494b4d4f505152535455565758595a5b5d5f6061626365696a6b6c6d6f707174757677797b7c7e7f8081828385868788898b8c8d8e909192939596979a9b9c9d9ea1a2a3a4a5a6a7a9aaabadaeafb0b1b2b4b6b7b8babbbcbdbebfc0c1c2c4c5c6c7c8c9cacbcccdcfd0d1d2d3d6d7d8d9dadbdcdddedfe1e2e3e4e6e8e9eaebecedeff0f1f2f3f4f5f6f7f8fafbfcfdfeff,72:0001020304050708090a0b0c0d0e0f101112131415161718191a#1b1c1e1f2021222324252627292b2d2e2f3233343a3c3e40414243444546494a4b4e4f505153545557585a5c5e60636465686a6b6c6d707173747677787b7c7d828385868788898c8e9091939495969798999a9b9c9d9ea0a1a2a3a4a5a6a7a8a9aaabaeb1b2b3b5babbbcbdbebfc0c5c6c7c9cacbcccfd1d3d4d5d6d8dadb#95$,30:000102,00b702:c9c7,00a830:0305,2014ff5e20:162618191c1d,30:141508090a0b0c0d0e0f16171011,00:b1d7f7,22:362728110f2a2908371aa52520,231222:992b2e614c483d1d606e6f64651e3534,26:4240,00b020:3233,2103ff0400a4ff:e0e1,203000a7211626:0605,25:cbcfcec7c6a1a0b3b2,203b21:92909193,30:13#95$,21:70717273747576777879#4$,24:88898a8b8c8d8e8f909192939495969798999a9b7475767778797a7b7c7d7e7f808182838485868760616263646566676869##,32:20212223242526272829##,21:606162636465666768696a6b#97$,ff:010203e505060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5de3#95$,30:4142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f90919293#106$a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6#103$,03:9192939495969798999a9b9c9d9e9fa0a1a3a4a5a6a7a8a9#6$b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c3c4c5c6c7c8c9#5$,fe:3536393a3f403d3e41424344##3b3c373831#3334#104$,04:10111213141501161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f#13$30313233343551363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f#11$,02:cacbd9,20:13152535,21:050996979899,22:151f23526667bf,25:505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f7071727381828384858687#88898a8b8c8d8e8f939495bcbde2e3e4e5,2609229530:121d1e#9$,010100e101ce00e0011300e9011b00e8012b00ed01d000ec014d00f301d200f2016b00fa01d400f901:d6d8dadc,00:fcea,0251e7c701:4448,e7c802:61#2$,31:05060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20212223242526272829#19$,30:212223242526272829,32a333:8e8f9c9d9ea1c4ced1d2d5,fe30ff:e2e4#,212132:31#,20:10#1$,30:fc9b9cfdfe069d9e,fe:494a4b4c4d4e4f50515254555657595a5b5c5d5e5f6061#626364656668696a6b,e7:e7e8e9eaebecedeeeff0f1f2f3,30:07#11$,25:000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b#13$,72:dcdddfe2e3e4e5e6e7eaebf5f6f9fdfeff,73:00020405060708090b0c0d0f1011121418191a1f2023242627282d2f30323335363a3b3c3d404142434445464748#494a4b4c4e4f515354555658595a5b5c5d5e5f6162636465666768696a6b6e7071#92$72737475767778797a7b7c7d7f808182838586888a8c8d8f90929394959798999a9c9d9ea0a1a3a4a5a6a7a8aaacadb1b4b5b6b8b9bcbdbebfc1c3c4c5c6c7#cbccced2d3d4d5d6d7d8dadbdcdddfe1e2e3e4e6e8eaebeceeeff0f1f3f4f5f6f7#92$f8f9fafbfcfdfeff,74:0001020407080b0c0d0e1112131415161718191c1d1e1f2021232427292b2d2f31323738393a3b3d3e3f4042434445464748494a4b4c4d#4e4f505152535456585d606162636465666768696a6b6c6e6f717273747578797a#92$7b7c7d7f8284858688898a8c8d8f9192939495969798999a9b9d9fa0a1a2a3a4a5a6aaabacadaeafb0b1b2b3b4b5b6b7b8b9bbbcbdbebfc0c1c2c3c4c5c6c7#c8c9cacbcccdcecfd0d1d3d4d5d6d7d8d9dadbdddfe1e5e7e8e9eaebecedf0f1f2#92$f3f5f8f9fafbfcfdfe,75:0001020305060708090a0b0c0e1012141516171b1d1e202122232426272a2e3436393c3d3f414243444647494a4d5051525355565758#5d5e5f60616263646768696b6c6d6e6f7071737576777a7b7c7d7e808182848587#92$88898a8c8d8e909395989b9c9ea2a6a7a8a9aaadb6b7babbbfc0c1c6cbcccecfd0d1d3d7d9dadcdddfe0e1e5e9ecedeeeff2f3f5f6f7f8fafbfdfe,76:02040607#08090b0d0e0f11121314161a1c1d1e212327282c2e2f31323637393a3b3d414244#92$45464748494a4b4e4f50515253555758595a5b5d5f6061626465666768696a6c6d6e7071727374757677797a7c7f80818385898a8c8d8f9092949597989a9b#9c9d9e9fa0a1a2a3a5a6a7a8a9aaabacadafb0b3b5b6b7b8b9babbbcbdbec0c1c3,554a963f57c3632854ce550954c076:914c,853c77ee827e788d72319698978d6c285b894ffa630966975cb880fa684880ae660276ce51f9655671ac7ff1888450b2596561ca6fb382ad634c625253ed54277b06516b75a45df462d48dcb9776628a8019575d97387f627238767d67cf767e64464f708d2562dc7a17659173ed642c6273822c9881677f724862:6ecc,4f3474e3534a529e7eca90a65e2e6886699c81807ed168d278c5868c9551508d8c2482de80de53058912526576:c4c7c9cbccd3d5d9dadcdddee0e1e2e3e4e6e7e8e9eaebecedf0f3f5f6f7fafbfdff,77:00020305060a0c0e0f1011121314151617181b1c1d1e21232425272a2b#2c2e3031323334393b3d3e3f4244454648494a4b4c4d4e4f52535455565758595c,858496f94fdd582199715b9d62:b1a5,66b48c799c8d7206676f789160b253:5117,8f8880cc8d1d94a1500d72c8590760eb711988ab595482ef672c7b285d297ef7752d6cf58e668ff8903c9f3b6bd491197b145f7c78a784d6853d6b:d5d9d6,5e:0187,75f995ed655d5f:0ac5,8f9f58c181c2907f965b97ad8fb97f168d2c62414fbf53:d85e,8f:a8a9ab,904d68075f6a819888689cd6618b522b762a5f6c658c6fd26ee85bbe644851:75b0,67c44e1979c9997c70b377:5d5e5f606467696a6d6e6f7071727374757677787a7b7c818283868788898a8b8f90939495969798999a9b9c9d9ea1a3a4a6a8abadaeafb1b2b4b6b7b8b9ba#bcbec0c1c2c3c4c5c6c7c8c9cacbcccecfd0d1d2d3d4d5d6d8d9dadddedfe0e1e4,75c55e7673bb83e064ad62e894b56ce2535a52c3640f94c27b944f2f5e1b823681:168a,6e246cca9a736355535c54fa886557e04e0d5e036b657c3f90e8601664e6731c88c16750624d8d22776c8e2991c75f6983dc8521991053c286956b8b60:ede8,707f82:cd31,4ed36ca785cf64cd7cd969fd66f9834953957b564fa7518c6d4b5c428e6d63d253c983:2c36,67e578b4643d5bdf5c945dee8be762c667f48c7a640063ba8749998b8c177f2094f24ea7961098a4660c731677:e6e8eaeff0f1f2f4f5f7f9fafbfc,78:0304050607080a0b0e0f101315191b1e20212224282a2b2e2f31323335363d3f414243444648494a4b4d4f51535458595a#5b5c5e5f606162636465666768696f7071727374757678797a7b7d7e7f80818283,573a5c1d5e38957f507f80a05382655e7545553150218d856284949e671d56326f6e5de2543570928f66626f64a463a35f7b6f8890f481e38fb05c1866685ff16c8996488d81886c649179f057ce6a59621054484e587a0b60e96f848bda627f901e9a8b79e4540375f4630153196c608fdf5f1b9a70803b9f7f4f885c3a8d647fc565a570bd51:45b2,866b5d075ba062bd916c75748e0c7a2061017b794ec77ef877854e1181ed521d51fa6a7153a88e87950496cf6ec19664695a78:848586888a8b8f9092949596999d9ea0a2a4a6a8a9aaabacadaeafb5b6b7b8babbbcbdbfc0c2c3c4c6c7c8cccdcecfd1d2d3d6d7d8dadbdcdddedfe0e1e2e3#e4e5e6e7e9eaebedeeeff0f1f3f5f6f8f9fbfcfdfeff,79:00020304060708090a0b0c,784050a877d7641089e6590463e35ddd7a7f693d4f20823955984e3275ae7a975e:628a,95ef521b5439708a6376952457826625693f918755076df37eaf882262337ef075b5832878c196cc8f9e614874f78bcd6b64523a8d506b21806a847156f153064e:ce1b,51d17c97918b7c074fc38e7f7be17a9c64675d1450ac810676017cb96dec7fe067515b:58f8,78cb64:ae13,63:aa2b,9519642d8fbe7b5476296253592754466b7950a362345e266b864ee38d37888b5f85902e79:0d0e0f1011121415161718191a1b1c1d1f2021222325262728292a2b2c2d2e2f3031323335363738393d3f42434445474a4b4c4d4e4f505152545558596163#6466696a6b6c6e70717273747576797b7c7d7e7f8283868788898b8c8d8e909192,6020803d62c54e39535590f863b880c665e66c2e4f4660ee6de18bde5f3986cb5f536321515a83616863520063638e4850125c9b79775bfc52307a3b60bc905376d75f:b797,76848e6c706f767b7b4977aa51f3909358244f4e6ef48fea654c7b1b72c46da47fdf5ae162b55e95573084827b2c5e1d5f1f90127f1498a063826ec7789870b95178975b57ab75354f4375385e9760e659606dc06bbf788953fc96d551cb52016389540a94938c038dcc7239789f87768fed8c0d53e079:939495969798999b9c9d9e9fa0a1a2a3a4a5a6a8a9aaabacadaeafb0b1b2b4b5b6b7b8bcbfc2c4c5c7c8cacccecfd0d3d4d6d7d9dadbdcdddee0e1e2e5e8ea#eceef1f2f3f4f5f6f7f9fafcfeff,7a:0104050708090a0c0f10111213151618191b1c,4e0176ef53ee948998769f0e952d5b9a8ba24e:221c,51ac846361c252a8680b4f97606b51bb6d1e515c6296659796618c46901775d890fd77636bd272:8aec,8bfb583577798d4c675c9540809a5ea66e2159927aef77ed953b6bb565ad7f0e58065151961f5bf958a954288e726566987f56e4949d76fe9041638754c659:1a3a,579b8eb267358dfa8235524160f0581586fe5ce89e454fc4989d8bb95a2560765384627c904f9102997f6069800c513f80335c1499756d314e8c7a:1d1f21222425262728292a2b2c2d2e2f303132343536383a3e4041424344454748494a4b4c4d4e4f50525354555658595a5b5c5d5e5f606162636465666768#696a6b6c6d6e6f717273757b7c7d7e828587898a8b8c8e8f909394999a9b9ea1a2,8d3053d17f5a7b4f4f104e4f96006cd573d085e95e06756a7ffb6a0a77fe94927e4151e170e653cd8fd483038d2972af996d6cdb574a82b365b980aa623f963259a84eff8bbf7eba653e83f2975e556198de80a5532a8bfd542080ba5e9f6cb88d3982ac915a54296c1b52067eb7575f711a6c7e7c89594b4efd5fff61247caa4e305c0167ab87025cf0950b98ce75af70fd902251af7f1d8bbd594951e44f5b5426592b657780a45b7562:76c2,8f905e456c1f7b264f:0fd8,670d7a:a3a4a7a9aaabaeafb0b1b2b4b5b6b7b8b9babbbcbdbec0c1c2c3c4c5c6c7c8c9cacccdcecfd0d1d2d3d4d5d7d8dadbdcdde1e2e4e7e8e9eaebeceef0f1f2f3#f4f5f6f7f8fbfcfe,7b:0001020507090c0d0e1012131617181a1c1d1f21222327292d,6d:6eaa,798f88b15f17752b629a8f854fef91dc65a781:2f51,5e9c81508d74526f89868d4b590d50854ed8961c723681798d1f5bcc8ba3964459877f1a549056:760e,8be565396982949976d66e895e72751867:46d1,7aff809d8d76611f79c665628d635188521a94a27f38809b7eb25c976e2f67607bd9768b9ad8818f7f947cd5641e95507a3f54:4ae5,6b4c640162089e3d80f3759952729769845b683c86e496:0194,94ec4e2a54047ed968398ddf801566f45e9a7fb97b:2f303234353637393b3d3f404142434446484a4d4e535557595c5e5f61636465666768696a6b6c6d6f70737476787a7c7d7f81828384868788898a8b8c8e8f#9192939698999a9b9e9fa0a3a4a5aeafb0b2b3b5b6b7b9babbbcbdbebfc0c2c3c4,57c2803f68975de5653b529f606d9f9a4f9b8eac516c5bab5f135de96c5e62f18d21517194a952fe6c9f82df72d757a267848d2d591f8f9c83c754957b8d4f306cbd5b6459d19f1353e486ca9aa88c3780a16545987e56fa96c7522e74dc52505be1630289024e5662d0602a68fa51735b9851a089c27ba199867f5060ef704c8d2f51495e7f901b747089c4572d78455f529f9f95fa8f689b3c8be17678684267dc8d:ea35,523d8f8a6eda68cd950590ed56fd679c88f98fc754c87b:c5c8c9cacbcdcecfd0d2d4d5d6d7d8dbdcdedfe0e2e3e4e7e8e9ebecedeff0f2f3f4f5f6f8f9fafbfdff,7c:0001020304050608090a0d0e101112131415171819#1a1b1c1d1e20212223242528292b2c2d2e2f3031323334353637393a3b3c3d3e42,9ab85b696d776c264ea55bb39a87916361a890af97e9542b6db55bd251fd558a7f:55f0,64bc634d65f161be608d710a6c:5749,592f676d822a58d5568e8c6a6beb90dd597d801753f76d695475559d83:77cf,683879be548c4f55540876d28c8996026cb36db88d6b89109e648d3a563f9ed175d55f8872e0606854fc4ea86a2a886160528f7054c470d886799e3f6d2a5b8f5f187ea255894faf7334543c539a501954:0e7c,4e4e5ffd745a58f6846b80e1877472d07cca6e567c:434445464748494a4b4c4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f70717275767778797a7e7f8081828384858687#888a8b8c8d8e8f90939496999a9ba0a1a3a6a7a8a9abacadafb0b4b5b6b7b8babb,5f27864e552c62a44e926caa623782b154d7534e733e6ed1753b521253168bdd69d05f8a60006dee574f6b2273af68538fd87f13636260a3552475ea8c6271156da35ba65e7b8352614c9ec478fa87577c27768751f060f6714c66435e4c604d8c0e707063258f895fbd606286d456de6bc160946167534960e066668d3f79fd4f1a70e96c478b:b3f2,7ed88364660f5a5a9b426d:51f7,8c416d3b4f19706b83b7621660d1970d8d27797851fb57:3efa,673a75787a3d79ef7b957c:bfc0c2c3c4c6c9cbcecfd0d1d2d3d4d8dadbdddee1e2e3e4e5e6e7e9eaebecedeef0f1f2f3f4f5f6f7f9fafcfdfeff,7d:000102030405060708090b0c0d0e0f10#1112131415161718191a1b1c1d1e1f212324252628292a2c2d2e30313233343536,808c99658ff96fc08ba59e2159ec7ee97f095409678168d88f917c4d96c653ca602575be6c7253735ac97ea7632451e0810a5df184df628051805b634f0e796d524260b86d4e5b:c4c2,8b:a1b0,65e25fcc964559937e:e7aa,560967b759394f735bb652a0835a988a8d3e753294be50477a3c4ef767b69a7e5ac16b7c76d1575a5c167b3a95f4714e517c80a9827059787f04832768c067ec78:b177,62e363617b804fed526a51cf835069db92748d:f531,89c1952e7bad4ef67d:3738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6f70717273747576#78797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798,506582305251996f6e:1085,6da75efa50f559dc5c066d466c5f7586848b686859568bb253209171964d854969127901712680f64ea490ca6d479a845a0756bc640594f077eb4fa5811a72e189d2997a7f347ede527f655991758f:7f83,53eb7a9663:eda5,768679f888579636622a52ab8282685467706377776b7aed6d017ed389e359d0621285c982a5754c501f4ecb75a58beb5c4a5dfe7b4b65a491d14eca6d25895f7d2795264ec58c288fdb9773664b79818fd170ec6d787d:999a9b9c9d9e9fa0a1a2a3a4a5a7a8a9aaabacadafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9#dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fa,5c3d52b283465162830e775b66769cb84eac60ca7c:beb3,7ecf4e958b66666f988897595883656c955c5f8475c997567a:dfde,51c070af7a9863ea7a767ea0739697ed4e4570784e5d915253a965:51e7,81fc8205548e5c31759a97a062d872d975bd5c459a7983ca5c40548077e94e3e6cae805a62d2636e5de851778ddd8e1e952f4ff153e560e770ac526763509e435a1f5026773753777ee26485652b628963985014723589c951b38bc07edd574783cc94a7519b541b5cfb7d:fbfcfdfeff,7e:000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233343536373839#3a3c3d3e3f40424344454648494a4b4c4d4e4f505152535455565758595a5b5c5d,4fca7ae36d5a90e19a8f55805496536154af5f0063e9697751ef6168520a582a52d8574e780d770b5eb761777ce062:5b97,4ea27095800362f770e49760577782db67ef68f578d5989779d158f354b353ef6e34514b523b5ba28bfe80af554357a660735751542d7a7a60505b5463a762a053e362635bc767af54ed7a9f82e691775e9388e4593857ae630e8de880ef57577b774fa95feb5bbd6b3e53217b5072c2684677:ff36,65f751b54e8f76d45cbf7aa58475594e9b4150807e:5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f8081838485868788898a8b8c8d8e8f909192939495969798999a9c9d9e#aeb4bbbcd6e4ecf9,7f:0a101e37393b3c3d3e3f404143464748494a4b4c4d4e4f5253,998861276e8357646606634656f062:ec69,5ed39614578362c955878721814a8fa3556683b167658d5684dd5a6a680f62e67bee961151706f9c8c3063fd89c861d27f0670c26ee57405699472fc5eca90ce67176d6a635e52b3726280014f6c59e5916a70d96d9d52d24e5096f7956d857e78ca7d2f5121579264c2808b7c7b6cea68f1695e51b7539868a872819ece7bf172f879bb6f137406674e91cc9ca4793c83:8954,540f68174e3d538952b1783e5386522950884f:8bd0,7f:56595b5c5d5e6063646566676b6c6d6f7073757677787a7b7c7d7f8082838485868788898b8d8f9091929395969798999b9ca0a2a3a5a6a8a9aaabacadaeb1#b3b4b5b6b7babbbec0c2c3c4c6c7c8c9cbcdcfd0d1d2d3d6d7d9dadbdcdddee2e3,75e27acb7c926ca596b6529b748354e94fe9805483b28fde95705ec9601c6d9f5e18655b813894fe604b70bc7ec37cae51c968817cb1826f4e248f8691cf667e4eae8c0564a9804a50da759771ce5be58fbd6f664e86648295635ed66599521788c270c852a3730e7433679778f797164e3490bb9cde6dcb51db8d41541d62ce73b283f196f69f8494c34f367f9a51cc707596755cad988653e64ee46e9c740969b4786b998f7559521876246d4167f3516d9f99804b54997b3c7abf7f:e4e7e8eaebecedeff2f4f5f6f7f8f9fafdfeff,80:020708090a0e0f11131a1b1d1e1f2123242b2c2d2e2f303234393a3c3e404144454748494e4f505153555657#595b5c5d5e5f6061626364656667686b6c6d6e6f7072737475767778797a7b7c7d,9686578462e29647697c5a0464027bd36f0f964b82a6536298855e90708963b35364864f9c819e93788c97328d:ef42,9e7f6f5e79845f559646622e9a74541594dd4fa365c55c:6561,7f1586516c2f5f8b73876ee47eff5ce6631b5b6a6ee653754e7163a0756562a18f6e4f264ed16ca67eb68bba841d87ba7f57903b95237ba99aa188f8843d6d1b9a867edc59889ebb739b780186829a:6c82,561b541757cb4e709ea653568fc881097792999286ee6ee1851366fc61626f2b80:7e818285888a8d8e8f909192949597999ea3a6a7a8acb0b3b5b6b8b9bbc5c7c8c9cacbcfd0d1d2d3d4d5d8dfe0e2e3e6eef5f7f9fbfeff,81:000103040507080b#0c1517191b1c1d1f202122232425262728292a2b2d2e3033343537393a3b3c3d3f,8c298292832b76f26c135fd983bd732b8305951a6bdb77db94c6536f830251925e3d8c8c8d384e4873ab679a68859176970971646ca177095a9295416bcf7f8e66275bd059b95a9a95:e8f7,4eec84:0c99,6aac76df9530731b68a65b5f772f919a97617cdc8ff78c1c5f257c7379d889c56ccc871c5bc65e4268c977207ef551:954d,52c95a297f05976282d763cf778485d079d26e3a5e9959998511706d6c1162bf76bf654f60af95fd660e879f9e2394ed54:0d7d,8c2c647881:40414243444547494d4e4f525657585b5c5d5e5f6162636466686a6b6c6f727375767778818384858687898b8c8d8e90929394959697999a9e9fa0a1a2a4a5#a7a9abacadaeafb0b1b2b4b5b6b7b8b9bcbdbebfc4c5c7c8c9cbcdcecfd0d1d2d3,647986116a21819c78e864699b5462b9672b83ab58a89ed86cab6f205bde964c8c0b725f67d062c772614ea959c66bcd589366ae5e5552df6155672876ee776672677a4662ff54:ea50,94a090a35a1c7eb36c164e435976801059485357753796be56ca63208111607c95f96dd65462998151855ae980fd59ae9713502a6ce55c3c62df4f60533f817b90066eba852b62c85e7478be64b5637b5ff55a18917f9e1f5c3f634f80425b7d556e95:4a4d,6d8560a867e072de51dd5b8181:d4d5d6d7d8d9dadbdcdddedfe0e1e2e4e5e6e8e9ebeeeff0f1f2f5f6f7f8f9fafdff,82:030708090a0b0e0f111315161718191a1d2024252627292e323a3c3d3f#404142434546484a4c4d4e5051525354555657595b5c5d5e606162636465666769,62e76cde725b626d94ae7ebd81136d53519c5f04597452aa6012597366968650759f632a61e67cef8bfa54e66b279e256bb485d5545550766ca4556a8db4722c5e156015743662cd6392724c5f986e436d3e65006f5876d878d076fc7554522453db4e535e9e65c180:2ad6,629b5486522870ae888d8dd16ce1547880da57f988f48d54966a914d4f696c9b55b776c6783062a870f96f8e5f6d84ec68da787c7bf781a8670b9e4f636778b0576f7812973962:79ab,528874356bd782:6a6b6c6d71757677787b7c808183858687898c90939495969a9b9ea0a2a3a7b2b5b6babbbcbfc0c2c3c5c6c9d0d6d9dadde2e7e8e9eaecedeef0f2f3f5f6f8#fafcfdfeff,83:000a0b0d1012131618191d1e1f20212223242526292a2e3032373b3d,5564813e75b276ae533975de50fb5c418b6c7bc7504f72479a9798d86f0274e27968648777a562fc98918d2b54c180584e52576a82f9840d5e7351ed74f68bc45c4f57616cfc98875a4678349b448feb7c955256625194fa4ec68386846183e984b257d467345703666e6d668c3166dd7011671f6b3a6816621a59bb4e0351c46f0667d26c8f517668cb59476b6775665d0e81109f5065d779:4841,9a918d775c824e5e4f01542f5951780c56686c148fc45f036c:7de3,8bab639083:3e3f41424445484a4b4c4d4e5355565758595d6270717273747576797a7e7f808182838487888a8b8c8d8f909194959697999a9d9fa1a2a3a4a5a6a7acadae#afb5bbbebfc2c3c4c6c8c9cbcdced0d1d2d3d5d7d9dadbdee2e3e4e6e7e8ebeced,60706d3d7275626694:8ec5,53438fc17b7e4edf8c264e7e9ed494:b1b3,524d6f5c90636d458c3458115d4c6b:2049,67aa545b81547f8c589985375f3a62a26a47953965726084686577a74e544fa85de7979864ac7fd85ced4fcf7a8d520783044e14602f7a8394a64fb54eb279e6743452e482b964d279bd5bdd6c8197528f7b6c22503e537f6e0564ce66746c3060c598778bf75e86743c7a7779cb4e1890b174036c4256da914b6cc58d8b533a86c666f28eaf5c489a716e2083:eeeff3f4f5f6f7fafbfcfeff,84:0002050708090a10121314151617191a1b1e1f20212223292a2b2c2d2e2f30323334353637393a3b3e3f404142434445474849#4a4b4c4d4e4f505253545556585d5e5f606264656667686a6e6f70727477797b7c,53d65a369f8b8da353bb570898a76743919b6cc9516875ca62f372ac52:389d,7f3a7094763853749e4a69b7786e96c088d97fa471:36c3,518967d374e458e4651856b78ba9997662707ed560f970ed58ec4e:c1ba,5fcd97e74efb8ba45203598a7eab62544ecd65e5620e833884c98363878d71946eb65bb97ed2519763c967d480898339881551125b7a59828fb14e736c5d516589258f6f962e854a745e95:10f0,6da682e55f3164926d128428816e9cc3585e8d5b4e0953c184:7d7e7f8081838485868a8d8f90919293949596989a9b9d9e9fa0a2a3a4a5a6a7a8a9aaabacadaeb0b1b3b5b6b7bbbcbec0c2c3c5c6c7c8cbcccecfd2d4d5d7#d8d9dadbdcdee1e2e4e7e8e9eaebedeeeff1f2f3f4f5f6f7f8f9fafbfdfe,85:000102,4f1e6563685155d34e2764149a9a626b5ac2745f82726da968ee50e7838e7802674052396c997eb150bb5565715e7b5b665273ca82eb67495c715220717d886b95ea965564c58d6181b355846c5562477f2e58924f2455468d4f664c4e0a5c1a88f368a2634e7a0d70e7828d52fa97f65c1154e890b57ecd59628d4a86c782:0c0d,8d6664445c0461516d89793e8bbe78377533547b4f388eab6df15a207ec5795e6c885ba15a76751a80be614e6e1758f075:1f25,727253477ef385:030405060708090a0b0d0e0f101214151618191b1c1d1e2022232425262728292a2d2e2f303132333435363e3f404142444546474b4c4d4e4f505152535455#57585a5b5c5d5f60616263656667696a6b6c6d6e6f707173757677787c7d7f8081,770176db526980dc57235e08593172ee65bd6e7f8bd75c388671534177f362fe65f64ec098df86805b9e8bc653f277e24f7f5c4e9a7659cb5f0f793a58eb4e1667ff4e8b62ed8a93901d52bf662f55dc566c90024ed54f8d91ca99706c0f5e0260435ba489c68bd56536624b99965b:88ff,6388552e53d77626517d852c67a268b36b8a62928f9353d482126dd1758f4e668d4e5b70719f85af66:91d9,7f7287009ecd9f205c5e672f8ff06811675f620d7ad658855eb665706f3185:82838688898a8b8c8d8e909192939495969798999a9d9e9fa0a1a2a3a5a6a7a9abacadb1b2b3b4b5b6b8babbbcbdbebfc0c2c3c4c5c6c7c8cacbcccdced1d2#d4d6d7d8d9dadbdddedfe0e1e2e3e5e6e7e8eaebecedeeeff0f1f2f3f4f5f6f7f8,60555237800d6454887075295e05681362f4971c53cc723d8c016c3477617a0e542e77ac987a821c8bf47855671470c165af64955636601d79c153f84e1d6b7b80865bfa55e356db4f:3a3c,99725df3677e80386002988290015b8b8b:bcf5,641c825864de55fd82cf91654fd77d20901f7c9f50f358516eaf5bbf8bc980839178849c7b97867d96:8b8f,7ee59ad3788e5c817a57904296a7795f5b59635f7b0b84d168ad55067f2974107d2295016240584c4ed65b835979585485:f9fafcfdfe,86:0001020304060708090a0b0c0d0e0f10121314151718191a1b1c1d1e1f20212223242526282a2b2c2d2e2f3031323334353637393a3b3d3e3f40#4142434445464748494a4b4c525355565758595b5c5d5f6061636465666768696a,736d631e8e:4b0f,80ce82d462ac53f06cf0915e592a60016c70574d644a8d2a762b6ee9575b6a8075f06f6d8c:2d08,57666bef889278b363a253f970ad6c645858642a580268e0819b55107cd650188eba6dcc8d9f70eb638f6d9b6ed47ee68404684390036dd896768ba85957727985e4817e75bc8a8a68af52548e22951163d098988e44557c4f5366ff568f60d56d9552435c4959296dfb586b75:301c,606c82148146631167618fe2773a8d:f334,94c15e165385542c70c386:6d6f7072737475767778838485868788898e8f90919294969798999a9b9e9fa0a1a2a5a6abadaeb2b3b7b8b9bbbcbdbebfc1c2c3c5c8cccdd2d3d5d6d7dadc#dde0e1e2e3e5e6e7e8eaebeceff5f6f7fafbfcfdff,87:010405060b0c0e0f10111416,6c405ef7505c4ead5ead633a8247901a6850916e77b3540c94dc5f647ae5687663457b527edf75db507762955934900f51f879c37a8156fe5f9290146d825c60571f541051546e4d56e263a89893817f8715892a9000541e5c6f81c062:d658,81319e3596409a:6e7c,692d59a562d3553e631654c786d96d3c5a0374e6889c6b6a59168c4c5f2f6e7e73a9987d4e3870f75b8c7897633d665a769660cb5b9b5a494e0781556c6a738b4ea167897f515f8065fa671b5fd859845a0187:191b1d1f20242627282a2b2c2d2f303233353638393a3c3d404142434445464a4b4d4f505152545556585a5b5c5d5e5f6162666768696a6b6c6d6f71727375#7778797a7f8081848687898a8c8e8f90919294959698999a9b9c9d9ea0a1a2a3a4,5dcd5fae537197e68fdd684556f4552f60df4e3a6f4d7ef482c7840e59d44f:1f2a,5c3e7eac672a851a5473754f80c355829b4f4f4d6e2d8c135c096170536b761f6e29868a658795fb7eb9543b7a337d0a95ee55e17fc174ee631d87176da17a9d621165a1536763e16c835deb545c94a84e4c6c618bec5c4b65e0829c68a754:3e34,6b:cb66,4e9463425348821e4f:0dae,575e620a96fe6664726952:ffa1,609f8bef661471996790897f785277fd6670563b54389521727a87:a5a6a7a9aaaeb0b1b2b4b6b7b8b9bbbcbebfc1c2c3c4c5c7c8c9cccdcecfd0d4d5d6d7d8d9dadcdddedfe1e2e3e4e6e7e8e9ebecedeff0f1f2f3f4f5f6f7f8#fafbfcfdff,88:0001020405060708090b0c0d0e0f101112141718191a1c1d1e1f2023,7a00606f5e0c6089819d591560dc718470ef6eaa6c5072806a8488ad5e2d4e605ab3559c94e36d177cfb9699620f7ec6778e867e5323971e8f9666875ce14fa072ed4e0b53a6590f54136380952851484ed99c9c7ea454b88d248854823795f26d8e5f265acc663e966973:b02e,53bf817a99857fa15baa96:7750,7ebf76f853a2957699997bb189446e584e617fd479658be660f354cd4eab98795df76a6150cf54118c618427785d9704524a54ee56a395006d885bb56dc6665388:2425262728292a2b2c2d2e2f30313334353637383a3b3d3e3f414243464748494a4b4e4f505152535556585a5b5c5d5e5f6066676a6d6f717374757678797a#7b7c80838687898a8c8e8f90919394959798999a9b9d9e9fa0a1a3a5a6a7a8a9aa,5c0f5b5d6821809655787b11654869544e9b6b47874e978b534f631f643a90aa659c80c18c10519968b0537887f961c86c:c4fb,8c225c5185aa82af950c6b238f9b65b05f:fbc3,4fe18845661f8165732960fa51745211578b5f6290a2884c91925e78674f602759d351:44f6,80f853086c7996c4718a4f:11ee,7f9e673d55c5950879c088967ee3589f620c9700865a5618987b5f908bb884c4915753d965ed5e8f755c60647d6e5a7f7e:eaed,8f6955a75ba360ac65cb738488:acaeafb0b2b3b4b5b6b8b9babbbdbebfc0c3c4c7c8cacbcccdcfd0d1d3d6d7dadbdcdddee0e1e6e7e9eaebecedeeeff2f5f6f7fafbfdff,89:0001030405060708#090b0c0d0e0f1114151617181c1d1e1f20222324262728292c2d2e2f3132333537,9009766377297eda9774859b5b667a7496ea884052cb718f5faa65ec8be25bfb9a6f5de16b896c5b8b:adaf,900a8fc5538b62bc9e:262d,54404e2b82bd7259869c5d1688596daf96c554d14e9a8bb6710954bd960970df6df976d04e25781487125ca95ef68a00989c960e708e6cbf594463a9773c884d6f148273583071d5538c781a96c155015f6671305bb48c1a9a8c6b83592e9e2f79e76768626c4f6f75a17f8a6d0b96336c274ef075d2517b68376f3e908081705996747689:38393a3b3c3d3e3f40424345464748494a4b4c4d4e4f505152535455565758595a5b5c5d6061626364656768696a6b6c6d6e6f707172737475767778797a7c#7d7e808284858788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1,64475c2790657a918c2359da54ac8200836f898180006930564e8036723791ce51b64e5f987563964e1a53f666f3814b591c6db24e0058f9533b63d694f14f:9d0a,886398905937905779fb4eea80f075916c825b9c59e85f5d69058681501a5df24e5977e34ee5827a6291661390915c794ebf5f7981c69038808475ab4ea688d4610f6bc55fc64e4976ca6ea28b:e3ae,8c0a8bd15f027f:fccc,7ece83:356b,56e06bb797f3963459fb541f94f66deb5bc5996e5c395f15969089:a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c3cdd3d4d5d7d8d9dbdddfe0e1e2e4e7e8e9eaecedeef0f1f2f4f5f6f7f8f9fa#fbfcfdfeff,8a:01020304050608090a0b0c0d0e0f101112131415161718191a1b1c1d,537082f16a315a749e705e947f2883b984:2425,836787478fce8d6276c85f719896786c662054df62e54f6381c375c85eb896cd8e0a86f9548f6cf36d8c6c38607f52c775285e7d4f1860a05fe75c24753190ae94c072b96cb96e389149670953:cbf3,4f5191c98bf153c85e7c8fc26de44e8e76c26986865e611a82064f:59de,903e9c7c61096e:1d14,96854e885a3196e84e0e5c7f79b95b878bed7fbd738957df828b90c15401904755bb5cea5fa161086b3272f180b28a:891e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3f4041424344454647494a4b4c4d4e4f505152535455565758595a5b5c5d5e#5f606162636465666768696a6b6c6d6e6f7071727374757677787a7b7c7d7e7f80,6d745bd388d598848c6b9a6d9e336e0a51:a443,57a38881539f63f48f9556ed54585706733f6e907f188fdc82d1613f6028966266f07ea68d:8ac3,94a55cb37ca4670860a6960580184e9190e75300966851418fd08574915d665597f55b55531d78386742683d54c9707e5bb08f7d518d572854b1651266828d:5e43,810f846c906d7cdf51ff85fb67a365e96fa186a48e81566a90207682707671e58d2362e952196cfd8d3c600e589e618e66fe8d60624e55b36e23672d8f678a:81828384858687888b8c8d8e8f9091929495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2#c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3,94e195f87728680569a8548b4e4d70b88bc86458658b5b857a84503a5be877bb6be18a797c986cbe76cf65a98f975d2d5c5586386808536062187ad96e5b7efd6a1f7ae05f706f335f20638c6da867564e085e108d264ed780c07634969c62db662d627e6cbc8d7571677f695146808753ec906e629854f286f08f998005951785178fd96d5973cd659f771f7504782781fb8d1e94884fa6679575b98bca9707632f9547963584b8632377415f8172f04e896014657462ef6b63653f8a:e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff,8b:0001020304050608090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20212223#24252728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445,5e2775c790d18bc1829d679d652f5431871877e580a281026c414e4b7ec7804c76f4690d6b966267503c4f84574063076b628dbe53ea65e87eb85fd763:1ab7,81:f3f4,7f6e5e1c5cd95236667a79e97a1a8d28709975d46ede6cbb7a924e2d76c55fe0949f88777ec879cd80bf91cd4ef24f17821f54685dde6d328bcc7ca58f7480985e1a549276b15b99663c9aa473e0682a86db6731732a8b:f8db,90107af970db716e62c477a956314e3b845767f152a986c08d2e94f87b518b:464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f6061626364656768696a6b6d6e6f707172737475767778797a7b7c7d7e7f80818283848586#8788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9facb1bbc7d0ea,8c:091e,4f4f6ce8795d9a7b6293722a62fd4e1378168f6c64b08d5a7bc668695e8488c55986649e58ee72b6690e95258ffd8d5857607f008c0651c6634962d95353684c74228301914c55447740707c6d4a517954a88d4459ff6ecb6dc45b5c7d2b4ed47c7d6ed35b5081ea6e0d5b579b0368d58e2a5b977efc603b7eb590b98d70594f63cd79df8db3535265cf79568bc5963b7ec494bb7e825634918967007f6a5c0a907566285de64f5067de505a4f5c57505e:a7#3$,8c:38393a3b3c3d3e3f4042434445484a4b4d4e4f5051525354565758595b5c5d5e5f60636465666768696c6d6e6f707172747576777b7c7d7e7f808183848687#888b8d8e8f90919293959697999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacad,4e:8d0c,51404e105eff53454e:15981e,9b325b6c56694e2879ba4e3f53154e47592d723b536e6c1056df80e499976bd3777e9f174e:369f,9f104e:5c6993,82885b5b556c560f4ec453:8d9da3a5ae,97658d5d53:1af5262e3e,8d5c53:6663,52:02080e2d333f404c5e615c,84af52:7d82819093,51827f544e:bbc3c9c2e8e1ebde,4f1b4ef34f:2264,4ef54f:2527092b5e67,65384f:5a5d,8c:aeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebec#edeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff,8d:000102030405060708090a0b0c0d,4f:5f57323d76749189838f7e7baa7cac94e6e8eac5dae3dcd1dff8,50:294c,4ff350:2c0f2e2d,4ffe50:1c0c25287e4355484e6c7ba5a7a9bad6,510650:edece6ee,51:070b,4edd6c3d4f:5865ce,9fa06c467c74516e5dfd9ec999985181591452f9530d8a07531051eb591951554ea051564eb388:6ea4,4eb5811488d279805b3488037fb851:abb1bdbc,8d:0e0f101112131415161718191a1b1c205152575f6568696a6c6e6f717278797a7b7c7d7e7f808283868788898c8d8e8f90929395969798999a9b9c9d9ea0a1#a2a4a5a6a7a8a9aaabacadaeafb0b2b6b7b9bbbdc0c1c2c5c7c8c9cacdd0d2d3d4,51:c796a2a5,8b:a0a6a7aab4b5b7c2c3cbcfced2d3d4d6d8d9dcdfe0e4e8e9eef0f3f6f9fcff,8c:000204070c0f1112141516191b181d1f202125272a2b2e2f32333536,53:697a,96:1d2221312a3d3c4249545f676c7274888d97b0,90:979b9d99aca1b4b3b6ba,8d:d5d8d9dce0e1e2e5e6e7e9edeef0f1f2f4f6fcfeff,8e:00010203040607080b0d0e1011121315161718191a1b1c202124252627282b2d303233343637383b3c3e#3f4345464c4d4e4f505354555657585a5b5c5d5e5f60616263646567686a6b6e71,90:b8b0cfc5bed0c4c7d3e6e2dcd7dbebeffe,91:04221e23312f394346,520d594252:a2acadbe,54ff52:d0d6f0,53df71ee77cd5ef451:f5fc,9b2f53b65f01755a5def57:4ca9a1,58:7ebcc5d1,57:292c2a33392e2f5c3b4269856b867c7b686d7673ada48cb2cfa7b493a0d5d8dad9d2b8f4eff8e4dd,8e:73757778797a7b7d7e808283848688898a8b8c8d8e91929395969798999a9b9d9fa0a1a2a3a4a5a6a7a8a9aaadaeb0b1b3b4b5b6b7b8b9bbbcbdbebfc0c1c2#c3c4c5c6c7c8c9cacbcccdcfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4,58:0b0d,57:fded,58:001e194420656c81899a80,99a89f1961ff82:797d7f8f8aa8848e919799abb8beb0c8cae398b7aecbccc1a9b4a1aa9fc4cea4e1,830982:f7e4,83:0f07,82:dcf4d2d8,830c82:fbd3,83:111a061415,82:e0d5,83:1c515b5c08923c34319b5e2f4f47435f4017602d3a336665,8e:e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff,8f:000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20212223#2425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f4041424344,83:681b696c6a6d6eb078b3b4a0aa939c857cb6a97db87b989ea8babcc1,840183:e5d8,580784:180b,83:ddfdd6,84:1c381106,83:d4df,84:0f03,83:f8f9eac5c0,842683:f0e1,84:5c515a597387887a89783c4669768c8e316dc1cdd0e6bdd3cabfbae0a1b9b497e5e3,850c750d853884f085:391f3a,8f:45464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f6061626364656a808c929da0a1a2a4a5a6a7aaacadaeafb2b3b4b5b7b8babbbcbfc0c3c6#c9cacbcccdcfd2d6d7dae0e1e3e7eceff1f2f4f5f6fafbfcfeff,90:07080c0e131518,85:563b,84:fffc,85:594868645e7a,77a285:43727ba4a8878f79ae9c85b9b7b0d3c1dcff,86:270529163c,5efe5f0859:3c41,803759:555a58,530f5c:22252c34,62:4c6a9fbbcadad7ee,632262f663:394b43adf6717a8eb46dac8a69aebcf2f8e0ffc4dece,645263:c6be,64:45410b1b200c26215e846d96,90:191c2324252728292a2b2c303132333437393a3d3f4043454648494a4b4c4e545556595a5c5d5e5f6061646667696a6b6c6f70717273767778797a7b7c7e81#84858687898a8c8d8e8f90929496989a9c9e9fa0a4a5a7a8a9abadb2b7bcbdbfc0,64:7ab7b899bac0d0d7e4e2,65:09252e,5f:0bd2,75195f1153:5ff1fde9e8fb,54:1216064b5253545643215759233282947771649a9b8476669dd0adc2b4d2a7a6d3d472a3d5bbbfccd9dadca9aaa4ddcfde,551b54e7552054fd551454f355:22230f11272a678fb5496d41553f503c,90:c2c3c6c8c9cbcccdd2d4d5d6d8d9dadedfe0e3e4e5e9eaeceef0f1f2f3f5f6f7f9fafbfcff,91:00010305060708090a0b0c0d0e0f1011121314151617181a1b1c#1d1f20212425262728292a2b2c2d2e30323334353637383a3b3c3d3e3f40414244,55:375675767733305c8bd283b1b988819f7ed6917bdfbdbe9499eaf7c9,561f55:d1ebecd4e6ddc4efe5f2f3cccde8f5e4,8f9456:1e080c012423,55fe56:00272d5839572c4d62595c4c548664716b7b7c8593afd4d7dde1f5ebf9ff,57:040a091c,5e:0f191411313b3c,91:454748515354555658595b5c5f606667686b6d737a7b7c808182838486888a8e8f939495969798999c9d9e9fa0a1a4a5a6a7a8a9abacb0b1b2b3b6b7b8b9bb#bcbdbebfc0c1c2c3c4c5c6c8cbd0d2d3d4d5d6d7d8d9dadbdddedfe0e1e2e3e4e5,5e:3744545b5e61,5c:8c7a8d9096889899919a9cb5a2bdacabb1a3c1b7c4d2e4cbe5,5d:020327262e241e061b583e343d6c5b6f5d6b4b4a697482999d,8c735d:b7c5,5f:73778287898c95999ca8adb5bc,88625f6172:adb0b4b7b8c3c1cecdd2e8efe9f2f4f7,730172f3730372fa91:e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff,92:000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021222324#25262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445,72fb73:1713210a1e1d152239252c3831504d57606c6f7e,821b592598e759:2402,99:636768696a6b6c74777d8084878a8d9091939495,5e:80918b96a5a0b9b5beb3,8d535e:d2d1dbe8ea,81ba5f:c4c9d6cf,60035fee60045f:e1e4fe,60:0506,5f:eaedf8,60:1935261b0f0d292b0a3f2178797b7a42,92:464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f7071727375767778797a7b7c7d7e7f808182838485#868788898a8b8c8d8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7,60:6a7d969aad9d83928c9becbbb1ddd8c6dab4,61:20261523,60f461:000e2b4a75ac94a7b7d4f5,5fdd96b395:e9ebf1f3f5f6fcfe,96:030406080a0b0c0d0f12151617191a,4e2c723f62156c:35545c4aa38590948c6869747686a9d0d4adf7f8f1d7b2e0d6faebeeb1d3effe,92:a8a9aaabacadafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8#e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff,93:00010203040506070809,6d:39270c43480704190e2b4d2e351a4f525433916f9ea05e93945c607c63,6e1a6d:c7c5de,6e0e6d:bfe0,6e116d:e6ddd9,6e166dab6e0c6dae6e:2b6e4e6bb25f865354322544dfb198e0,6f2d6e:e2a5a7bdbbb7d7b4cf8fc29f,6f:6246472415,6ef96f:2f364b742a0929898d8c78727c7ad1,93:0a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3f40414243444546474849#4a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696b,6f:c9a7b9b6c2e1eedee0ef,70:1a231b39354f5e,5b:80849593a5b8,752f9a9e64345b:e4ee,89305bf08e478b078f:b6d3d5e5eee4e9e6f3e8,90:05040b26110d162135362d2f445152506858625b,66b990:747d8288838b,5f:50575658,5c3b54ab5c:5059,5b715c:6366,7fbc5f:2a292d,82745f3c9b3b5c6e59:81838da9aaa3,93:6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaab#acadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cbcccd,59:97caab9ea4d2b2afd7be,5a:0506,59dd5a0859:e3d8f9,5a:0c09323411231340674a553c6275,80ec5a:aa9b777abeebb2d2d4b8e0e3f1d6e6d8dc,5b:091716323740,5c:151c,5b:5a6573515362,9a:7577787a7f7d808185888a90929396989b9c9d9fa0a2a3a5a7,7e:9fa1a3a5a8a9,93:cecfd0d1d2d3d4d5d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff,94:000102030405060708090a0b0c0d#0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e,7e:adb0bec0c1c2c9cbccd0d4d7dbe0e1e8ebeeeff1f2,7f0d7e:f6fafbfe,7f:01020307080b0c0f111217191c1b1f212223242526272a2b2c2d2f3031323335,5e7a757f5ddb753e909573:8e91aea29fcfc2d1b7b3c0c9c8e5d9,987c740a73:e9e7debaf2,74:0f2a5b262528302e2c,94:2f303132333435363738393a3b3c3d3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6c6d6e6f#707172737475767778797a7b7c7d7e7f8081828384919698c7cfd3d4dae6fb,95:1c20,74:1b1a415c575559776d7e9c8e8081878b9ea8a990a7d2ba,97:eaebec,67:4c535e4869a5876a7398a775a89ead8b777cf0,680967d8680a67:e9b0,680c67:d9b5dab3dd,680067:c3b8e2,680e67:c1fd,68:323360614e624464831d55664167403e4a4929b58f7477936bc2,696e68fc69:1f20,68f995:27333d43484b555a606e74757778797a7b7c7d7e808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aa#abacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacb,692468f069:0b0157,68e369:10713960425d846b80987834cc8788ce896663799ba7bbabadd4b1c1cadf95e08dff,6a2f69ed6a:171865,69f26a:443ea0505b358e793d28587c9190a997ab,73:3752,6b:8182878492938d9a9ba1aa,8f:6b6d71727375767877797a7c7e818284878b,95:cccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7ecff,96:0713181b1e20232425262728292b2c2d2f303738393a3e41434a4e4f5152535657#58595a5c5d5e606365666b6d6e6f70717378797a7b7c7d7e7f808182838487898a,8f:8d8e8f989a,8ece62:0b171b1f222125242c,81e774:eff4ff,75:0f1113,65:34eeeff0,66:0a19,677266:031500,708566:f71d34313635,800666:5f54414f56615777848ca79dbedbdce6e9,8d:3233363b3d4045464849474d5559,89:c7cacbcccecfd0d1,72:6e9f5d666f7e7f848b8d8f92,63:0832b0,96:8c8e91929395969a9b9d9e9fa0a1a2a3a4a5a6a8a9aaabacadaeafb1b2b4b5b7b8babbbfc2c3c8cacbd0d1d3d4d6d7d8d9dadbdcdddedfe1e2e3e4e5e6e7eb#ecedeef0f1f2f4f5f8fafbfcfdff,97:0203050a0b0c10111214151718191a1b1d1f20,64:3fd8,80046b:eaf3fdf5f9,6c:0507060d1518191a2129242a32,65:35556b,72:4d525630,8662521680:9f9c93bc,670a80:bdb1abadb4b7e7e8e9eadbc2c4d9cdd7,671080:ddebf1f4ed,81:0d0e,80:f2fc,671581128c5a81:361e2c1832484c5374595a7160697c7d6d67,584d5ab581:888291,6ed581:a3aacc,672681:cabb,97:2122232425262728292b2c2e2f3133343536373a3b3c3d3f404142434445464748494a4b4c4d4e4f5051545557585a5c5d5f63646667686a6b6c6d6e6f7071#72757778797a7b7d7e7f8081828384868788898a8c8e8f9093959697999a9b9c9d,81:c1a6,6b:243739434659,98:d1d2d3d5d9da,6bb35f406bc289f365909f5165:93bcc6c4c3ccced2d6,70:809c969dbbc0b7abb1e8ca,71:1013162f31735c6845724a787a98b3b5a8a0e0d4e7f9,72:1d28,706c71:1866b9,62:3e3d434849,79:3b4046495b5c535a6257606f677a858a9aa7b3,5f:d1d0,97:9e9fa1a2a4a5a6a7a8a9aaacaeb0b1b3b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3#e4e5e8eeeff0f1f2f4f7f8f9fafbfcfdfeff,98:000102030405060708090a0b0c0d0e,60:3c5d5a67415963ab,61:060d5da99dcbd1,620680:807f,6c:93f6,6dfc77:f6f8,78:0009171811,65ab78:2d1c1d393a3b1f3c252c23294e6d56572650474c6a9b939a879ca1a3b2b9a5d4d9c9ecf2,790578f479:13241e34,9f9b9e:f9fbfc,76f177:040d,76f977:07081a22192d263538505147435a68,98:0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d#4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e,77:62657f8d7d808c919fa0b0b5bd,75:3a404e4b485b727983,7f:58615f,8a487f:68747179817e,76:cde5,883294:8586878b8a8c8d8f909497959a9b9ca3a4abaaadacafb0b2b4b6b7b8b9babcbdbfc4c8c9cacbcccdced0d1d2d5d6d7d9d8dbdedfe0e2e4e5e7e8ea,98:6f70717273748b8e929599a3a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcfd0d4d6d7dbdcdde0e1e2e3e4#e5e6e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff,99:0001020304050607,94:e9ebeeeff3f4f5f7f9fcfdff,95:03020607090a0d0e0f1213141516181b1d1e1f222a2b292c3132343637383c3e3f4235444546494c4e4f525354565758595b5e5f5d61626465666768696a6b6c6f7172733a,77:e7ec,96c979:d5ede3eb,7a065d477a:03021e14,99:08090a0b0c0e0f1112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2f303132333435363738393a3b3c3d3e3f40414243444546474849#4a4b4c4d4e4f50515253565758595a5b5c5d5e5f60616264667378797b7e828389,7a:393751,9ecf99a57a7076:888e9399a4,74:dee0,752c9e:202228292a2b2c3231363837393a3e414244464748494b4c4e5155575a5b5c5e63666768696a6b6c716d73,75:929496a09daca3b3b4b8c4b1b0c3c2d6cde3e8e6e4ebe7,760375:f1fcff,76:1000050c170a25181519,99:8c8e9a9b9c9d9e9fa0a1a2a3a4a6a7a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8#d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9,76:1b3c2220402d303f35433e334d5e545c566b6f,7fca7a:e6787980868895a6a0aca8adb3,88:6469727d7f82a2c6b7bcc9e2cee3e5f1,891a88:fce8fef0,89:2119131b0a342b3641667b,758b80e576:b2b4,77dc80:1214161c20222526272928310b3543464d526971,898398:788083,99:fafbfcfdfeff,9a:000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738#393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f50515253545556575859,98:898c8d8f949a9b9e9fa1a2a5a6,86:4d546c6e7f7a7c7ba88d8bac9da7a3aa93a9b6c4b5ceb0bab1afc9cfb4e9f1f2edf3d0,871386:def4dfd8d1,87:0307,86f887:080a0d09233b1e252e1a3e48343129373f82227d7e7b60704c6e8b53637c64596593afa8d2,9a:5a5b5c5d5e5f606162636465666768696a6b7283898d8e949599a6a9aaabacadaeafb2b3b4b5b9bbbdbebfc3c4c6c7c8c9cacdcecfd0d2d4d5d6d7d9dadbdc#dddee0e2e3e4e5e7e8e9eaeceef0f1f2f3f4f5f6f7f8fafcfdfeff,9b:000102040506,87:c68885ad9783abe5acb5b3cbd3bdd1c0cadbeae0ee,88:1613,87fe88:0a1b21393c,7f:36424445,82107a:fafd,7b:080304150a2b0f47382a192e31202524333e1e585a45754c5d606e7b62727190a6a7b8ac9da885aa9ca2abb4d1c1ccdddae5e6ea,7c0c7b:fefc,7c:0f160b,9b:07090a0b0c0d0e1011121415161718191a1b1c1d1e2021222425262728292a2b2c2d2e3031333435363738393a3d3e3f40464a4b4c4e50525355565758595a#5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b,7c:1f2a26384140,81fe82:010204,81ec884482:2122232d2f282b383b33343e44494b4f5a5f68,88:7e8588d8df,895e7f:9d9fa7afb0b2,7c7c65497c:919d9c9ea2b2bcbdc1c7cccdc8c5d7e8,826e66a87f:bfced5e5e1e6e9eef3,7cf87d:77a6ae,7e:479b,9e:b8b4,8d:73849491b1676d,8c:4749,91:4a504e4f64,9b:7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9ba#bbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadb,91:626170696f7d7e7274798c85908d91a2a3aaadaeafb5b4ba,8c559e7e8d:b8eb,8e:055969,8d:b5bfbcbac4d6d7dadececfdbc6ecf7f8e3f9fbe4,8e098dfd8e:141d1f2c2e232f3a4039353d3149414251524a70767c6f74858f94909c9e,8c:78828a859894,659b89:d6dedadc,9b:dcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff,9c:000102030405060708090a0b0c0d0e0f101112131415161718191a#1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b,89:e5ebef,8a3e8b26975396:e9f3ef,97:0601080f0e2a2d303e,9f:808385868788898a8c,9efe9f:0b0d,96:b9bcbdced2,77bf96e092:8eaec8,93:3e6aca8f,94:3e6b,9c:7f8285868788,7a239c:8b8e90919294959a9b9e9fa0a1a2a3a5a6a7a8a9abadaeb0b1b2b3b4b5b6b7babbbcbdc4c5c6c7cacb3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a#7b7d7e808384898a8c8f93969798999daaacafb9bebfc0c1c2c8c9d1d2dadbe0e1cccdcecfd0d3d4d5d7d8d9dcdddfe2,97:7c85919294afaba3b2b4,9a:b1b0b7,9e589a:b6babcc1c0c5c2cbccd1,9b:45434749484d51,98e899:0d2e5554,9a:dfe1e6efebfbedf9,9b:080f131f23,9e:bdbe,7e3b9e:8287888b92,93d69e:9d9fdbdcdde0dfe2e9e7e5eaef,9f:222c2f39373d3e44,9c:e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff,9d:000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021#22232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142#92$434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f8081#82838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2#92$a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1#e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff,9e:000102#92$030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e24272e30343b3c404d5052535456595d5f606162656e6f727475767778797a7b7c7d80#8183848586898a8c8d8e8f90919495969798999a9b9c9ea0a1a2a3a4a5a7a8a9aa#92$abacadaeafb0b1b2b3b5b6b7b9babcbfc0c1c2c3c5c6c7c8cacbccd0d2d3d5d6d7d9dadee1e3e4e6e8ebecedeef0f1f2f3f4f5f6f7f8fafdff,9f:000102030405#060708090a0c0f1112141516181a1b1c1d1e1f21232425262728292a2b2d2e3031#92$3233343536383a3c3f4041424345464748494a4b4c4d4e4f52535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778#797a7b7c7d7e81828d8e8f9091929394959697989c9d9ea1a2a3a4a5,f9:2c7995e7f1#92$,fa:0c0d0e0f111314181f20212324272829,e8:15161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40414243#4445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f6061626364'), + U2Ghash = {}, + G2Uhash = {}; + ! function(data) { + var k = 0; + data = data.match(/..../g); + for (var i = 0x81; i <= 0xfe; i++) { + for (var j = 0x40; j <= 0xFE; j++) { + U2Ghash[data[k++]] = ('%' + i.toString(16) + '%' + j.toString(16)) + .toUpperCase(); + } + } + for (var key in U2Ghash) { + G2Uhash[U2Ghash[key]] = key; + } + }(data); + + function isAscii(unicode) { + return ((unicode == 0x20AC) || (unicode <= 0x007F && unicode >= 0x0000)); + } + + return { + encode: function(str) { + return str.replace(/./g, function(a) { + var code = a.charCodeAt(0); + if (isAscii(code)) { + return encodeURIComponent(a); + } else { + var key = code.toString(16); + if (key.length != 4) key = ('000' + key).match(/....$/)[0]; + return U2Ghash[key] || a; + } + }); + }, + decode: function(str) { + return str.replace(/%[0-9A-F]{2}%[0-9A-F]{2}/g, function(a) { + if (a in G2Uhash) { + return String.fromCharCode('0x' + G2Uhash[a]); + } else { + return a; + } + }).replace(/%[\w]{2}/g, function(a) { + return decodeURIComponent(a); + + }); + } + }; +} \ No newline at end of file diff --git a/app/src/main/assets/js/lib/net.js b/app/src/main/assets/js/lib/net.js new file mode 100644 index 0000000000..adf63a4dc3 --- /dev/null +++ b/app/src/main/assets/js/lib/net.js @@ -0,0 +1,17 @@ +let req = (url, options) => http(url, Object.assign({ + async: false +}, options)); + +function http(url, options = {}) { + if (options?.async === false) return _http(url, options) + return new Promise(resolve => _http(url, Object.assign({ + complete: res => resolve(res) + }, options))).catch(err => { + console.error(err.name, err.message, err.stack) + return { + ok: false, + status: 500, + url + } + }) +}; \ No newline at end of file diff --git a/app/src/main/assets/js/lib/similarity.js b/app/src/main/assets/js/lib/similarity.js new file mode 100644 index 0000000000..3dfc497d5c --- /dev/null +++ b/app/src/main/assets/js/lib/similarity.js @@ -0,0 +1 @@ +function compareTwoStrings(first,second){if((first=first.replace(/\s+/g,""))===(second=second.replace(/\s+/g,"")))return 1;if(first.length<2||second.length<2)return 0;var firstBigrams=new Map;for(let i=0;iratings[bestMatchIndex].rating&&(bestMatchIndex=i)}return{ratings:ratings,bestMatch:ratings[bestMatchIndex],bestMatchIndex:bestMatchIndex}}function lcs(str1,str2){if(!str1||!str2)return{length:0,sequence:"",offset:0};for(var sequence="",str1Length=str1.length,str2Length=str2.length,num=new Array(str1Length),maxlen=0,lastSubsBegin=0,i=0;imaxlen&&(maxlen=num[i][j],lastSubsBegin===(thisSubsBegin=i-num[i][j]+1)?sequence+=str1[i]:(lastSubsBegin=thisSubsBegin,sequence="",sequence+=str1.substr(lastSubsBegin,i+1-lastSubsBegin))));return{length:maxlen,sequence:sequence,offset:thisSubsBegin}}function findBestLCS(mainString,targetStrings){var results=[];let bestMatchIndex=0;for(let i=0;iresults[bestMatchIndex].lcs.length&&(bestMatchIndex=i)}return{allLCS:results,bestMatch:results[bestMatchIndex],bestMatchIndex:bestMatchIndex}}export{compareTwoStrings,findBestMatch,findBestLCS}; \ No newline at end of file diff --git a/app/src/main/assets/js/lib/utils.js b/app/src/main/assets/js/lib/utils.js new file mode 100644 index 0000000000..eb3f49213b --- /dev/null +++ b/app/src/main/assets/js/lib/utils.js @@ -0,0 +1 @@ +//bbQ0FUT1A0GGxpYi91dGlscy5qcwppc1N1Yg5nZXRTaXplEnJlbW92ZUV4dAZsb2caaXNWaWRlb0Zvcm1hdBJqc29uUGFyc2UKZGVidWcGZXh0BnNydAZhc3MGc3NhCHNpemUOdG9GaXhlZARUQgRHQgRNQgRLQgh0ZXh0DmluZGV4T2YCLhJzdWJzdHJpbmcWbGFzdEluZGV4T2YGc3RyDmNvbnNvbGUGdXJsCFJVTEUQdXJsPWh0dHAGLmpzCC5jc3MKLmh0bWwIdGVzdAhqc29uGGpzb25QbGF5RGF0YQ5oZWFkZXJzBHVhDnJlZmVyZXIUdGFza1Jlc3VsdApwYXJzZRRzdGFydHNXaXRoBC8vDGh0dHBzOghodHRwFHVzZXItYWdlbnQIdHJpbRRVc2VyLUFnZW50AiAOUmVmZXJlcgxoZWFkZXIGb2JqAmECPQ+kAwAHAACmAwABqAMAAqoDAAOsAwAErgMABbADAAayAwAADgAGAaABAAAAAQcHIACmAwABqAMBAaoDAgGsAwMBrgMEAbADBQGyAwYBCOgdvgDfvgHgvgLhvgPivgRfBAC+BV8FAL4GXwYAKSmkAwEFAQAfoAEOQwYBpgMBAAECAAAeAbQDAAEAzwTbAAAAqRHpFA7PBNwAAACpEekJDs8E3QAAAKkopAMDAQMOQwYBqAMBAAEDAACkAQG8AwABAM+zpOgDvyjPvAAEvAAEmrwABJq8AASapegjz7wABLwABJq8AASavAAEmpvXQt8AAAC1JAEABOAAAACdKM+8AAS8AASavAAEmqXoH8+8AAS8AASavAAEmpvXQt8AAAC1JAEABOEAAACdKM+8AAS8AASapegbz7wABLwABJqb10LfAAAAtSQBAATiAAAAnSjPvAAEm9dC3wAAALUkAQAE4wAAAJ0opAMHDQMmYl1OU0lOPzVOCCEOQwYBqgMBAAEGAAAtAcgDAAEAz0LlAAAABOYAAAAkAQCypegaz0LnAAAAs89C6AAAAATmAAAAJAEAJAIAKM8opAMYAQMOYgYBrAMBAAEDAAARAdIDAAEAOOoAAABC2QAAAM8kAQAOBi6kAxwCA04OQwYBrgMBAQEDAAJgAtYDAAEA2AMAAAC9AL0BM8fPQuUAAAAE7QAAACQBALKlEek6Ds9C5QAAAATuAAAAJAEAsqUR6SYOz0LlAAAABO8AAAAkAQCypRHpEg7PQuUAAAAE8AAAACQBALKl6AMJKMNC8QAAAM8lAQCkAyAHAyEATgIICAfCAmh0dHAoKD8haHR0cCkuKXsxMix9P1wuKG0zdTh8bXA0fGZsdnxhdml8bWt2fHJtfHdtdnxtcGd8bTRhfG1wMylcPy4qfGh0dHAoKD8haHR0cCkuKXsxMix9XC4obTN1OHxtcDR8Zmx2fGF2aXxta3Z8cm18d212fG1wZ3xtNGF8bXAzKXxodHRwKCg/IWh0dHApLikqP3ZpZGVvXC90b3MqB+YKAAYBrAIAAAgGAAAABAf1////CwAJOgIAAAklAQAAAWgAAXQAAXQAAXAADwwAAAALARgNAAAAAWgAAXQAAXQAAXAACgMMAQ7k////EAgcAAAACwEYDQAAAAFoAAF0AAF0AAFwAAoDDAEH3////wEuAAsCCaYAAAAJkwAAAAmAAAAACW0AAAAJXQAAAAlKAAAACTcAAAAJJAAAAAkRAAAAAW0AATMAAXUAATgABwkAAAABbQABcAABNAAHCQAAAAFmAAFsAAF2AAcJAAAAAWEAAXYAAWkABwkAAAABbQABawABdgAHBgAAAAFyAAFtAAcJAAAAAXcAAW0AAXYABwkAAAABbQABcAABZwAHCQAAAAFtAAE0AAFhAAcJAAAAAW0AAXAAATMADAIBPwAcAgAAAAAAAAD///9/AQAAAAMKBwsBAAABaAABdAABdAABcAAPDAAAAAsDGA0AAAABaAABdAABdAABcAAKAwwDDuT///8QCR0AAAAZCwMYDQAAAAFoAAF0AAF0AAFwAAoDDAMa3v///wEuAAsECaYAAAAJkwAAAAmAAAAACW0AAAAJXQAAAAlKAAAACTcAAAAJJAAAAAkRAAAAAW0AATMAAXUAATgABwkAAAABbQABcAABNAAHCQAAAAFmAAFsAAF2AAcJAAAAAWEAAXYAAWkABwkAAAABbQABawABdgAHBgAAAAFyAAFtAAcJAAAAAXcAAW0AAXYABwkAAAABbQABcAABZwAHCQAAAAFtAAE0AAFhAAcJAAAAAW0AAXAAATMADAQHXQAAAAFoAAF0AAF0AAFwAA0FBQgcAAAACwUYDQAAAAFoAAF0AAF0AAFwAAoDDAUH3////wF2AAFpAAFkAAFlAAFvAAEvAAF0AAFvABwEAAAAAAAAAP///38BAAAAAXMACgwACg5DBgGwAwIGAgQBALEBCLABAAEA5AMAAQDmAwAAANYDAAEA6AMAAgDqAwADAOwDAAQA7gMABQCuAwQAOJgAAABC+AAAANAkAQDLQesAAADMQvkAAAAE+gAAACQBAOgJBPsAAADEncjEQvkAAAAE/AAAACQBAJboAwcoxM+r6AnbxO2W6AMHKAvJwwT9AAAARxHpAw6/zkL+AAAAJAAA57Ol6BDFBP8AAABxBAABAADGnUnDQfYAAAAR6QMOv8IEQv4AAAAkAADns6XoEcUEAQEAAHEEAAEAAMAEnUkLxUwCAQAAxEzrAAAAwgUopAMoFgNJIVMsWAgIHCEICQ0/SU86TlQIISIOQwYBsgMBAQEGAQA3AoYEAAEAiAQAAACyAwYAz3zqL8fPw0eXBEkAAACp6Anbz8NH7Q7qGjjqAAAAQtkAAADDBAUBAACdz8NHnSQBAA5/6NAOKaQDRgcDHD8hDYEX \ No newline at end of file diff --git "a/app/src/main/assets/js/lib/\346\250\241\346\235\277.js" "b/app/src/main/assets/js/lib/\346\250\241\346\235\277.js" new file mode 100644 index 0000000000..82b037b3a6 --- /dev/null +++ "b/app/src/main/assets/js/lib/\346\250\241\346\235\277.js" @@ -0,0 +1,304 @@ +if (typeof Object.assign != 'function') { + Object.assign = function () { + var target = arguments[0]; + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i]; + for (var key in source) { + if (Object.prototype.hasOwnProperty.call(source, key)) { + target[key] = source[key]; + } + } + } + return target; +}; +} +function getMubans() { + var mubanDict = { // 模板字典 + mxpro: { + title: '', + host: '', + // homeUrl:'/', + url: '/vodshow/fyclass--------fypage---.html', + searchUrl: '/vodsearch/**----------fypage---.html', + searchable: 2,//是否启用全局搜索, + quickSearch: 0,//是否启用快速搜索, + filterable: 0,//是否启用分类筛选, + headers: {//网站的请求头,完整支持所有的,常带ua和cookies + 'User-Agent': 'MOBILE_UA', + // "Cookie": "searchneed=ok" + }, + class_parse: '.navbar-items li:gt(2):lt(8);a&&Text;a&&href;/(\\d+).html', + play_parse: true, + lazy: '', + limit: 6, + 推荐: '.tab-list.active;a.module-poster-item.module-item;.module-poster-item-title&&Text;.lazyload&&data-original;.module-item-note&&Text;a&&href', + double: true, // 推荐内容是否双层定位 + 一级: 'body a.module-poster-item.module-item;a&&title;.lazyload&&data-original;.module-item-note&&Text;a&&href', + 二级: { + "title": "h1&&Text;.module-info-tag&&Text", + "img": ".lazyload&&data-original", + "desc": ".module-info-item:eq(1)&&Text;.module-info-item:eq(2)&&Text;.module-info-item:eq(3)&&Text", + "content": ".module-info-introduction&&Text", + "tabs": ".module-tab-item", + "lists": ".module-play-list:eq(#id) a" + }, + 搜索: 'body .module-item;.module-card-item-title&&Text;.lazyload&&data-original;.module-item-note&&Text;a&&href;.module-info-item-content&&Text', + }, + mxone5: { + title: '', + host: '', + url: '/show/fyclass--------fypage---.html', + searchUrl: '/search/**----------fypage---.html', + searchable: 2,//是否启用全局搜索, + quickSearch: 0,//是否启用快速搜索, + filterable: 0,//是否启用分类筛选, + class_parse: '.nav-menu-items&&li;a&&Text;a&&href;.*/(.*?).html', + play_parse: true, + lazy: '', + limit: 6, + 推荐: '.module-list;.module-items&&.module-item;a&&title;img&&data-src;.module-item-text&&Text;a&&href', + double: true, // 推荐内容是否双层定位 + 一级: '.module-items .module-item;a&&title;img&&data-src;.module-item-text&&Text;a&&href', + 二级: { + "title": "h1&&Text;.tag-link&&Text", + "img": ".module-item-pic&&img&&data-src", + "desc": ".video-info-items:eq(0)&&Text;.video-info-items:eq(1)&&Text;.video-info-items:eq(2)&&Text;.video-info-items:eq(3)&&Text", + "content": ".vod_content&&Text", + "tabs": ".module-tab-item", + "lists": ".module-player-list:eq(#id)&&.scroll-content&&a" + }, + 搜索: '.module-items .module-search-item;a&&title;img&&data-src;.video-serial&&Text;a&&href', + }, + 首图: { + title: '', + host: '', + url: '/vodshow/fyclass--------fypage---/', + searchUrl: '/vodsearch/**----------fypage---.html', + searchable: 2,//是否启用全局搜索, + quickSearch: 0,//是否启用快速搜索, + filterable: 0,//是否启用分类筛选, + headers: {//网站的请求头,完整支持所有的,常带ua和cookies + 'User-Agent': 'MOBILE_UA', + // "Cookie": "searchneed=ok" + }, + class_parse: '.myui-header__menu li.hidden-sm:gt(0):lt(5);a&&Text;a&&href;/(\\d+).html', + play_parse: true, + lazy: '', + limit: 6, + 推荐: 'ul.myui-vodlist.clearfix;li;a&&title;a&&data-original;.pic-text&&Text;a&&href', + double: true, // 推荐内容是否双层定位 + 一级: '.myui-vodlist li;a&&title;a&&data-original;.pic-text&&Text;a&&href', + 二级: { + "title": ".myui-content__detail .title&&Text;.myui-content__detail p:eq(-2)&&Text", + "img": ".myui-content__thumb .lazyload&&data-original", + "desc": ".myui-content__detail p:eq(0)&&Text;.myui-content__detail p:eq(1)&&Text;.myui-content__detail p:eq(2)&&Text", + "content": ".content&&Text", + "tabs": ".nav-tabs:eq(0) li", + "lists": ".myui-content__list:eq(#id) li" + }, + 搜索: '#searchList li;a&&title;.lazyload&&data-original;.text-muted&&Text;a&&href;.text-muted:eq(-1)&&Text', + }, + 首图2: { + title: '', + host: '', + url: '/list/fyclass-fypage.html', + searchUrl: '/vodsearch/**----------fypage---.html', + searchable: 2,//是否启用全局搜索, + quickSearch: 0,//是否启用快速搜索, + filterable: 0,//是否启用分类筛选, + headers: { + 'User-Agent': 'UC_UA', + // "Cookie": "" + }, + // class_parse:'.stui-header__menu li:gt(0):lt(7);a&&Text;a&&href;/(\\d+).html', + class_parse: '.stui-header__menu li:gt(0):lt(7);a&&Text;a&&href;.*/(.*?).html', + play_parse: true, + lazy: '', + limit: 6, + 推荐: 'ul.stui-vodlist.clearfix;li;a&&title;.lazyload&&data-original;.pic-text&&Text;a&&href', + double: true, // 推荐内容是否双层定位 + 一级: '.stui-vodlist li;a&&title;a&&data-original;.pic-text&&Text;a&&href', + 二级: { + "title": ".stui-content__detail .title&&Text;.stui-content__detail p:eq(-2)&&Text", + "img": ".stui-content__thumb .lazyload&&data-original", + "desc": ".stui-content__detail p:eq(0)&&Text;.stui-content__detail p:eq(1)&&Text;.stui-content__detail p:eq(2)&&Text", + "content": ".detail&&Text", + "tabs": ".stui-vodlist__head h3", + "lists": ".stui-content__playlist:eq(#id) li" + }, + 搜索: 'ul.stui-vodlist__media:eq(0) li,ul.stui-vodlist:eq(0) li,#searchList li;a&&title;.lazyload&&data-original;.text-muted&&Text;a&&href;.text-muted:eq(-1)&&Text', + 搜索1: 'ul.stui-vodlist&&li;a&&title;.lazyload&&data-original;.text-muted&&Text;a&&href;.text-muted:eq(-1)&&Text', + 搜索2: 'ul.stui-vodlist__media&&li;a&&title;.lazyload&&data-original;.text-muted&&Text;a&&href;.text-muted:eq(-1)&&Text', + }, + 默认: { + title: '', + host: '', + url: '/vodshow/fyclass--------fypage---.html', + searchUrl: '/vodsearch/-------------.html?wd=**', + searchable: 2,//是否启用全局搜索, + quickSearch: 0,//是否启用快速搜索, + filterable: 0,//是否启用分类筛选, + headers: { + 'User-Agent': 'MOBILE_UA', + }, + play_parse: true, + lazy: '', + limit: 6, + double: true, // 推荐内容是否双层定位 + }, + vfed: { + title: '', + host: '', + url: '/index.php/vod/show/id/fyclass/page/fypage.html', + searchUrl: '/index.php/vod/search/page/fypage/wd/**.html', + searchable: 2,//是否启用全局搜索, + quickSearch: 0,//是否启用快速搜索, + filterable: 0,//是否启用分类筛选, + headers: { + 'User-Agent': 'UC_UA', + }, + // class_parse:'.fed-pops-navbar&&ul.fed-part-rows&&a.fed-part-eone:gt(0):lt(5);a&&Text;a&&href;.*/(.*?).html', + class_parse: '.fed-pops-navbar&&ul.fed-part-rows&&a;a&&Text;a&&href;.*/(.*?).html', + play_parse: true, + lazy: '', + limit: 6, + 推荐: 'ul.fed-list-info.fed-part-rows;li;a.fed-list-title&&Text;a&&data-original;.fed-list-remarks&&Text;a&&href', + double: true, // 推荐内容是否双层定位 + 一级: '.fed-list-info&&li;a.fed-list-title&&Text;a&&data-original;.fed-list-remarks&&Text;a&&href', + 二级: { + "title": "h1.fed-part-eone&&Text;.fed-deta-content&&.fed-part-rows&&li&&Text", + "img": ".fed-list-info&&a&&data-original", + "desc": ".fed-deta-content&&.fed-part-rows&&li:eq(1)&&Text;.fed-deta-content&&.fed-part-rows&&li:eq(2)&&Text;.fed-deta-content&&.fed-part-rows&&li:eq(3)&&Text", + "content": ".fed-part-esan&&Text", + "tabs": ".fed-drop-boxs&&.fed-part-rows&&li", + "lists": ".fed-play-item:eq(#id)&&ul:eq(1)&&li" + }, + 搜索: '.fed-deta-info;h1&&Text;.lazyload&&data-original;.fed-list-remarks&&Text;a&&href;.fed-deta-content&&Text', + }, + 海螺3: { + title: '', + host: '', + searchUrl: '/v_search/**----------fypage---.html', + url: '/vod_____show/fyclass--------fypage---.html', + headers: { + 'User-Agent': 'MOBILE_UA' + }, + timeout: 5000, + class_parse: 'body&&.hl-nav li:gt(0);a&&Text;a&&href;.*/(.*?).html', + cate_exclude: '明星|专题|最新|排行', + limit: 40, + play_parse: true, + lazy: '', + 推荐: '.hl-vod-list;li;a&&title;a&&data-original;.remarks&&Text;a&&href', + double: true, + 一级: '.hl-vod-list&&.hl-list-item;a&&title;a&&data-original;.remarks&&Text;a&&href', + 二级: { + "title": ".hl-infos-title&&Text;.hl-text-conch&&Text", + "img": ".hl-lazy&&data-original", + "desc": ".hl-infos-content&&.hl-text-conch&&Text", + "content": ".hl-content-text&&Text", + "tabs": ".hl-tabs&&a", + "lists": ".hl-plays-list:eq(#id)&&li" + }, + 搜索: '.hl-list-item;a&&title;a&&data-original;.remarks&&Text;a&&href', + searchable: 2,//是否启用全局搜索, + quickSearch: 0,//是否启用快速搜索, + filterable: 0,//是否启用分类筛选, + }, + 海螺2: { + title: '', + host: '', + searchUrl: '/index.php/vod/search/page/fypage/wd/**/', + url: '/index.php/vod/show/id/fyclass/page/fypage/', + headers: { + 'User-Agent': 'MOBILE_UA' + }, + timeout: 5000, + class_parse: '#nav-bar li;a&&Text;a&&href;id/(.*?)/', + limit: 40, + play_parse: true, + lazy: '', + 推荐: '.list-a.size;li;a&&title;.lazy&&data-original;.bt&&Text;a&&href', + double: true, + 一级: '.list-a&&li;a&&title;.lazy&&data-original;.list-remarks&&Text;a&&href', + 二级: { + "title": "h2&&Text;.deployment&&Text", + "img": ".lazy&&data-original", + "desc": ".deployment&&Text", + "content": ".ec-show&&Text", + "tabs": "#tag&&a", + "lists": ".play_list_box:eq(#id)&&li" + }, + 搜索: '.search-list;a&&title;.lazy&&data-original;.deployment&&Text;a&&href', + searchable: 2,//是否启用全局搜索, + quickSearch: 0,//是否启用快速搜索, + filterable: 0,//是否启用分类筛选, + }, + 短视: { + title: '', + host: '', + // homeUrl:'/', + url: '/channel/fyclass-fypage.html', + searchUrl: '/search.html?wd=**', + searchable: 2,//是否启用全局搜索, + quickSearch: 0,//是否启用快速搜索, + filterable: 0,//是否启用分类筛选, + headers: {//网站的请求头,完整支持所有的,常带ua和cookies + 'User-Agent': 'MOBILE_UA', + // "Cookie": "searchneed=ok" + }, + class_parse: '.menu_bottom ul li;a&&Text;a&&href;.*/(.*?).html', + cate_exclude: '解析|动态', + play_parse: true, + lazy: '', + limit: 6, + 推荐: '.indexShowBox;ul&&li;a&&title;img&&data-src;.s1&&Text;a&&href', + double: true, // 推荐内容是否双层定位 + 一级: '.pic-list&&li;a&&title;img&&data-src;.s1&&Text;a&&href', + 二级: { + "title": "h1&&Text;.content-rt&&p:eq(0)&&Text", + "img": ".img&&img&&data-src", + "desc": ".content-rt&&p:eq(1)&&Text;.content-rt&&p:eq(2)&&Text;.content-rt&&p:eq(3)&&Text;.content-rt&&p:eq(4)&&Text;.content-rt&&p:eq(5)&&Text", + "content": ".zkjj_a&&Text", + "tabs": ".py-tabs&&option", + "lists": ".player:eq(#id) li" + }, + 搜索: '.sr_lists&&ul&&li;h3&&Text;img&&data-src;.int&&p:eq(0)&&Text;a&&href', + }, + 短视2:{ + title: '', + host: '', + class_name:'电影&电视剧&综艺&动漫', + class_url:'1&2&3&4', + searchUrl: '/index.php/ajax/suggest?mid=1&wd=**&limit=50', + searchable: 2, + quickSearch: 0, + headers:{'User-Agent':'MOBILE_UA'}, + url: '/index.php/api/vod#type=fyclass&page=fypage', + filterable:0,//是否启用分类筛选, + filter_url:'', + filter: {}, + filter_def:{}, + detailUrl:'/index.php/vod/detail/id/fyid.html', + play_parse: true, + lazy: '', + limit: 6, + 推荐:'.list-vod.flex .public-list-box;a&&title;.lazy&&data-original;.public-list-prb&&Text;a&&href', + 一级:'js:let body=input.split("#")[1];let t=Math.round(new Date/1e3).toString();let key=md5("DS"+t+"DCC147D11943AF75");let url=input.split("#")[0];body=body+"&time="+t+"&key="+key;print(body);fetch_params.body=body;let html=post(url,fetch_params);let data=JSON.parse(html);VODS=data.list.map(function(it){it.vod_pic=urljoin2(input.split("/i")[0],it.vod_pic);return it});', + 二级:{ + "title":".slide-info-title&&Text;.slide-info:eq(3)--strong&&Text", + "img":".detail-pic&&data-original", + "desc":".fraction&&Text;.slide-info-remarks:eq(1)&&Text;.slide-info-remarks:eq(2)&&Text;.slide-info:eq(2)--strong&&Text;.slide-info:eq(1)--strong&&Text", + "content":"#height_limit&&Text", + "tabs":".anthology.wow.fadeInUp.animated&&.swiper-wrapper&&a", + "tab_text":".swiper-slide&&Text", + "lists":".anthology-list-box:eq(#id) li" + }, + 搜索:'json:list;name;pic;;id', + } + }; + return JSON.parse(JSON.stringify(mubanDict)); +} +var mubanDict = getMubans(); +var muban = getMubans(); +export default {muban,getMubans}; \ No newline at end of file diff --git a/app/src/main/java/com/github/catvod/Proxy.java b/app/src/main/java/com/github/catvod/Proxy.java new file mode 100644 index 0000000000..b7aab1731d --- /dev/null +++ b/app/src/main/java/com/github/catvod/Proxy.java @@ -0,0 +1,28 @@ +package com.github.catvod; + +import com.github.tvbox.osc.server.RemoteServer; + +public class Proxy { + + private static int port = RemoteServer.serverPort; + + public static void set(int port) { + Proxy.port = port; + } + + public static int getPort() { + return port > 0 ? port : RemoteServer.serverPort; + } + + public static String getUrl(boolean local) { + return "http://" + (local ? "127.0.0.1" : getIp()) + ":" + getPort() + "/proxy"; + } + + private static String getIp() { + try { + return RemoteServer.getLocalIPAddress(com.github.tvbox.osc.base.App.getInstance()); + } catch (Throwable th) { + return "127.0.0.1"; + } + } +} diff --git a/app/src/main/java/com/github/catvod/crawler/JarLoader.java b/app/src/main/java/com/github/catvod/crawler/JarLoader.java index 097dbcee51..895372f21a 100644 --- a/app/src/main/java/com/github/catvod/crawler/JarLoader.java +++ b/app/src/main/java/com/github/catvod/crawler/JarLoader.java @@ -1,12 +1,23 @@ package com.github.catvod.crawler; import android.content.Context; +import android.os.Environment; +import android.text.TextUtils; +import android.util.Log; +import com.github.catvod.net.OkHttp; import com.github.tvbox.osc.base.App; +import com.github.tvbox.osc.server.ControlManager; +import com.github.tvbox.osc.server.RemoteServer; +import com.github.tvbox.osc.util.FileUtils; +import com.github.tvbox.osc.util.MD5; +import com.lzy.okgo.OkGo; import org.json.JSONObject; import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; import java.lang.reflect.Method; import java.util.HashMap; import java.util.LinkedHashMap; @@ -14,108 +25,426 @@ import java.util.concurrent.ConcurrentHashMap; import dalvik.system.DexClassLoader; +import okhttp3.Response; public class JarLoader { - private DexClassLoader classLoader = null; - private ConcurrentHashMap spiders = new ConcurrentHashMap<>(); - private Method proxyFun = null; - - /** - * 不要在主线程调用我 - * - * @param cache - */ + + private static final String TAG = "JarLoader"; + private static final String MAIN_KEY = "main"; + + private final ConcurrentHashMap loaders = new ConcurrentHashMap<>(); + private final ConcurrentHashMap proxyMethods = new ConcurrentHashMap<>(); + private final ConcurrentHashMap danmuClickMethods = new ConcurrentHashMap<>(); + private final ConcurrentHashMap danmuLongClickMethods = new ConcurrentHashMap<>(); + private final ConcurrentHashMap spiders = new ConcurrentHashMap<>(); + private final ConcurrentHashMap locks = new ConcurrentHashMap<>(); + private final ConcurrentHashMap siteJarKeys = new ConcurrentHashMap<>(); + private final ConcurrentHashMap aliases = new ConcurrentHashMap<>(); + private final ProtectedInitJar protectedInitJar = new ProtectedInitJar(); + private volatile String recent = MAIN_KEY; + public boolean load(String cache) { + boolean success = load(MAIN_KEY, new File(cache)); + if (success) recent = MAIN_KEY; + return success; + } + + public void setRecentJarKey(String key) { + if (TextUtils.isEmpty(key)) return; + recent = realKey(key); + injectProxyPort(loaders.get(recent)); + } + + public void loadLiveJar(String jar) { + String key = jarKey(jar); + parseJar(key, jar); + setRecentJarKey(key); + } + + public void clear() { + for (Spider spider : spiders.values()) { + try { + spider.destroy(); + } catch (Throwable ignored) { + } + } + loaders.clear(); + proxyMethods.clear(); + danmuClickMethods.clear(); + danmuLongClickMethods.clear(); spiders.clear(); - proxyFun = null; - boolean success = false; + locks.clear(); + siteJarKeys.clear(); + aliases.clear(); + recent = MAIN_KEY; + } + + private boolean load(String key, File file) { + if (Thread.interrupted()) return false; + if (!exists(file)) return false; + if (loaders.containsKey(key)) return true; try { - File cacheDir = new File(App.getInstance().getCacheDir().getAbsolutePath() + "/catvod_csp"); - if (!cacheDir.exists()) - cacheDir.mkdirs(); - classLoader = new DexClassLoader(cache, cacheDir.getAbsolutePath(), null, App.getInstance().getClassLoader()); - // make force wait here, some device async dex load - int count = 0; - do { - try { - Class classInit = classLoader.loadClass("com.github.catvod.spider.Init"); - if (classInit != null) { - Method method = classInit.getMethod("init", Context.class); - method.invoke(null, App.getInstance()); - System.out.println("自定义爬虫代码加载成功!"); - success = true; - try { - Class proxy = classLoader.loadClass("com.github.catvod.spider.Proxy"); - Method mth = proxy.getMethod("proxy", Map.class); - proxyFun = mth; - } catch (Throwable th) { - - } - break; - } - Thread.sleep(200); - } catch (Throwable th) { - th.printStackTrace(); - } - count++; - } while (count < 5); - } catch (Throwable th) { - th.printStackTrace(); + file.setReadOnly(); + String cachePath = jarDir().getAbsolutePath(); + DexClassLoader loader = new DexClassLoader(file.getAbsolutePath(), cachePath, cachePath, App.getInstance().getClassLoader()); + invokeInit(loader, file.getAbsolutePath()); + invokeProxy(key, loader); + invokeDanmaku(key, loader); + injectProxyPort(loader); + loaders.put(key, loader); + Log.i(TAG, "load success key=" + key + ", file=" + file.getAbsolutePath()); + return true; + } catch (Throwable e) { + Log.i(TAG, "load error key=" + key + ", msg=" + e.getMessage()); + e.printStackTrace(); + return false; } - return success; } - public Spider getSpider(String key, String cls, String ext) { - String clsKey = cls.replace("csp_", ""); - if (spiders.containsKey(key)) - return spiders.get(key); - if (classLoader == null) + private void invokeInit(DexClassLoader loader, String jar) { + try { + Class clz = loader.loadClass("com.github.catvod.spider.Init"); + Method method = clz.getMethod("init", Context.class); + if (protectedInitJar.check(jar)) { + Log.i(TAG, "echo-load initProtectedJar file=" + jar); + protectedInitJar.init(clz); + } else { + method.invoke(null, App.getInstance()); + } + } catch (Throwable e) { + e.printStackTrace(); + } + } + + private void invokeProxy(String key, DexClassLoader loader) { + try { + Class clz = loader.loadClass("com.github.catvod.spider.Proxy"); + Method method = clz.getMethod("proxy", Map.class); + proxyMethods.put(key, method); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + private void invokeDanmaku(String key, DexClassLoader loader) { + try { + Class clz = loader.loadClass("com.github.catvod.spider.Danmaku"); + try { + danmuClickMethods.put(key, clz.getMethod("onClick", String.class, String.class)); + } catch (Throwable ignored) { + } + try { + danmuLongClickMethods.put(key, clz.getMethod("onLongClick", String.class, String.class)); + } catch (Throwable ignored) { + } + } catch (Throwable ignored) { + } + } + + public void parseJar(String key, String jar) { + if (TextUtils.isEmpty(key) || TextUtils.isEmpty(jar)) return; + if (loaders.containsKey(key)) return; + Object lock = lock(key); + synchronized (lock) { + if (loaders.containsKey(key)) return; + String source = jar; + String md5 = ""; + String[] texts = jar.split(";md5;"); + if (texts.length > 1) { + source = texts[0]; + md5 = texts[1].trim(); + } + aliases.put(jarKey(source), key); + if (md5.startsWith("http")) { + String value = OkHttp.string(md5, null); + md5 = value == null ? "" : value.trim(); + } + File file = fileForJar(source); + if (!TextUtils.isEmpty(md5) && exists(file) && MD5.getFileMd5(file).equalsIgnoreCase(md5)) { + load(key, file); + } else if (TextUtils.isEmpty(md5) && exists(file) && !FileUtils.isWeekAgo(file)) { + load(key, file); + } else if (source.startsWith("http")) { + load(key, download(source, file)); + } else if (source.startsWith("assets")) { + load(key, copyAsset(source, file)); + } else if (source.startsWith("file")) { + load(key, local(source)); + } else if (source.startsWith("clan://")) { + load(key, download(clanToAddress(source), file)); + } + } + } + + public DexClassLoader dex(String jar) { + try { + String key = jarKey(jar); + parseJar(key, jar); + return loaders.get(key); + } catch (Throwable e) { + e.printStackTrace(); + return null; + } + } + + public Spider getSpider(String key, String api, String ext, String jar) { + key = key == null ? "" : key; + api = api == null ? "" : api; + ext = ext == null ? "" : ext; + jar = jar == null ? "" : jar; + if (TextUtils.isEmpty(api)) return new SpiderNull(); + + String jaKey = TextUtils.isEmpty(jar) ? MAIN_KEY : jarKey(jar); + String spKey = jaKey + key; + recent = jaKey; + siteJarKeys.put(key, jaKey); + injectProxyPort(loaders.get(jaKey)); + + Spider cached = spiders.get(spKey); + if (cached != null) { + Log.i(TAG, "getSpider cached key=" + spKey); + return cached; + } + + try { + if (!MAIN_KEY.equals(jaKey)) parseJar(jaKey, jar); + DexClassLoader loader = loaders.get(jaKey); + if (loader == null) return new SpiderNull(); + Spider spider = (Spider) loader.loadClass("com.github.catvod.spider." + className(api)).newInstance(); + spider.siteKey = key; + spider.initApi(new SpiderApi()); + spider.init(App.getInstance(), ext); + spiders.put(spKey, spider); + Log.i(TAG, "getSpider success key=" + spKey); + return spider; + } catch (Throwable e) { + Log.i(TAG, "getSpider error key=" + spKey + ", msg=" + e.getMessage()); + e.printStackTrace(); return new SpiderNull(); + } + } + + public void searchDanmuUi(String name, String episode, boolean longClick) { try { - Spider sp = (Spider) classLoader.loadClass("com.github.catvod.spider." + clsKey).newInstance(); - sp.init(App.getInstance(), ext); - spiders.put(key, sp); - return sp; - } catch (Throwable th) { - th.printStackTrace(); + ConcurrentHashMap methods = longClick ? danmuLongClickMethods : danmuClickMethods; + Method method = methods.get(recent); + if (method == null) method = methods.get("main"); + if (method == null) return; + method.invoke(null, name, episode); + } catch (Throwable e) { + e.printStackTrace(); } - return new SpiderNull(); + } + + public boolean hasDanmuSearchUi() { + return danmuClickMethods.containsKey(recent) || danmuLongClickMethods.containsKey(recent); } public JSONObject jsonExt(String key, LinkedHashMap jxs, String url) { try { - String clsKey = "Json" + key; - String hotClass = "com.github.catvod.parser." + clsKey; - Class jsonParserCls = classLoader.loadClass(hotClass); - Method mth = jsonParserCls.getMethod("parse", LinkedHashMap.class, String.class); - return (JSONObject) mth.invoke(null, jxs, url); - } catch (Throwable th) { - th.printStackTrace(); + Class clz = loadParserClass("com.github.catvod.parser.Json" + key); + Method method = clz.getMethod("parse", LinkedHashMap.class, String.class); + return (JSONObject) method.invoke(null, jxs, url); + } catch (Throwable e) { + e.printStackTrace(); + return null; } - return null; } public JSONObject jsonExtMix(String flag, String key, String name, LinkedHashMap> jxs, String url) { try { - String clsKey = "Mix" + key; - String hotClass = "com.github.catvod.parser." + clsKey; - Class jsonParserCls = classLoader.loadClass(hotClass); - Method mth = jsonParserCls.getMethod("parse", LinkedHashMap.class, String.class, String.class, String.class); - return (JSONObject) mth.invoke(null, jxs, name, flag, url); - } catch (Throwable th) { - th.printStackTrace(); + Class clz = loadParserClass("com.github.catvod.parser.Mix" + key); + Method method = clz.getMethod("parse", LinkedHashMap.class, String.class, String.class, String.class); + return (JSONObject) method.invoke(null, jxs, name, flag, url); + } catch (Throwable e) { + e.printStackTrace(); + return null; + } + } + + public Object[] proxyInvoke(Map params) { + String siteKey = params == null ? null : params.get("siteKey"); + if (!TextUtils.isEmpty(siteKey)) { + Object[] result = proxyInvoke(proxyMethods.get(siteJarKeys.get(siteKey)), params); + if (result != null) return result; + } + Object[] result = proxyInvoke(proxyMethods.get(recent), params); + if (result != null) return result; + for (Map.Entry entry : proxyMethods.entrySet()) { + if (entry.getKey().equals(recent)) continue; + result = proxyInvoke(entry.getValue(), params); + if (result != null) return result; } return null; } - public Object[] proxyInvoke(Map params) { + private Object[] proxyInvoke(Method method, Map params) { try { - if (proxyFun != null) { - return (Object[]) proxyFun.invoke(null, params); + return method == null ? null : (Object[]) method.invoke(null, params); + } catch (Throwable e) { + e.printStackTrace(); + return null; + } + } + + private DexClassLoader requireRecentLoader() { + DexClassLoader loader = loaders.get(recent); + if (loader == null) loader = loaders.get(MAIN_KEY); + if (loader == null) throw new IllegalStateException("No jar loaded for recent key: " + recent); + return loader; + } + + private Class loadParserClass(String name) throws ClassNotFoundException { + DexClassLoader loader = loaders.get(recent); + if (loader != null) { + try { + return loader.loadClass(name); + } catch (ClassNotFoundException ignored) { } - } catch (Throwable th) { + } + loader = loaders.get(MAIN_KEY); + if (loader != null) return loader.loadClass(name); + throw new ClassNotFoundException(name); + } + private File download(String url, File file) { + InputStream is = null; + FileOutputStream os = null; + try { + Response response = OkGo.get(url).execute(); + if (response.body() == null) return file; + is = response.body().byteStream(); + os = new FileOutputStream(create(file)); + byte[] buffer = new byte[16384]; + int length; + while ((length = is.read(buffer)) != -1) { + if (Thread.interrupted()) return file; + os.write(buffer, 0, length); + } + os.flush(); + } catch (Throwable e) { + e.printStackTrace(); + } finally { + close(is); + close(os); + } + return file; + } + + private File copyAsset(String url, File file) { + InputStream is = null; + FileOutputStream os = null; + try { + String path = url.replace("assets://", "").replace("assets/", ""); + is = App.getInstance().getAssets().open(path); + os = new FileOutputStream(create(file)); + byte[] buffer = new byte[16384]; + int length; + while ((length = is.read(buffer)) != -1) { + os.write(buffer, 0, length); + } + os.flush(); + } catch (Throwable e) { + e.printStackTrace(); + } finally { + close(is); + close(os); + } + return file; + } + + private File local(String path) { + path = path.replace("file:/", ""); + File file = new File(Environment.getExternalStorageDirectory(), path); + return file.exists() ? file : new File(path); + } + + private File fileForJar(String jar) { + return new File(jarDir(), jarKey(jar) + ".jar"); + } + + private File jarDir() { + File dir = new File(App.getInstance().getCacheDir(), "jar"); + if (!dir.exists()) dir.mkdirs(); + return dir; + } + + private File create(File file) throws Exception { + File parent = file.getParentFile(); + if (parent != null && !parent.exists()) parent.mkdirs(); + if (file.exists()) file.delete(); + file.createNewFile(); + file.setReadable(true); + file.setWritable(true); + file.setExecutable(true); + return file; + } + + private boolean exists(File file) { + return file != null && file.exists() && file.length() > 0; + } + + private Object lock(String key) { + Object lock = locks.get(key); + if (lock != null) return lock; + Object created = new Object(); + Object old = locks.putIfAbsent(key, created); + return old == null ? created : old; + } + + private String jarKey(String jar) { + String key = MD5.string2MD5(jar == null ? "" : jar); + return TextUtils.isEmpty(key) ? MAIN_KEY : key; + } + + private String realKey(String key) { + String alias = aliases.get(key); + return TextUtils.isEmpty(alias) ? key : alias; + } + + private String className(String api) { + return api.contains("csp_") ? api.split("csp_")[1] : api; + } + + private String clanToAddress(String url) { + if (url.startsWith("clan://localhost/")) { + return url.replace("clan://localhost/", ControlManager.get().getAddress(true) + "file/"); + } + if (url.startsWith("clan://")) { + String text = url.substring(7); + int index = text.indexOf('/'); + if (index > 0) return "http://" + text.substring(0, index) + "/file/" + text.substring(index + 1); + } + return url; + } + + private void injectProxyPort(DexClassLoader loader) { + com.github.catvod.Proxy.set(getServerPort()); + if (loader == null) return; + try { + Class proxy = loader.loadClass("com.github.catvod.Proxy"); + Method set = proxy.getMethod("set", int.class); + set.invoke(null, getServerPort()); + } catch (Throwable ignored) { + } + } + + private int getServerPort() { + try { + String address = ControlManager.get().getAddress(true); + if (address != null && address.startsWith("http://127.0.0.1:")) { + String baseUrl = address.endsWith("/") ? address.substring(0, address.length() - 1) : address; + return Integer.parseInt(baseUrl.substring(baseUrl.lastIndexOf(":") + 1)); + } + } catch (Throwable ignored) { + } + return RemoteServer.serverPort; + } + + private void close(java.io.Closeable closeable) { + try { + if (closeable != null) closeable.close(); + } catch (Throwable ignored) { } - return null; } } diff --git a/app/src/main/java/com/github/catvod/crawler/JsLoader.java b/app/src/main/java/com/github/catvod/crawler/JsLoader.java new file mode 100644 index 0000000000..cadaaa3a28 --- /dev/null +++ b/app/src/main/java/com/github/catvod/crawler/JsLoader.java @@ -0,0 +1,177 @@ +package com.github.catvod.crawler; + + +import android.util.Log; + +import com.github.tvbox.osc.base.App; + +import com.github.tvbox.osc.util.FileUtils; +import com.github.tvbox.osc.util.LOG; +import com.github.tvbox.osc.util.MD5; + +import com.github.catvod.crawler.js.JsSpider; +import com.lzy.okgo.OkGo; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import dalvik.system.DexClassLoader; +import okhttp3.Response; + +public class JsLoader { + private static final ConcurrentHashMap spiders = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap> classes = new ConcurrentHashMap<>(); + //当前的Js爬虫key + private volatile String recentKey = ""; + + public static void destroy() { + for (Spider spider : spiders.values()){ + spider.cancelByTag(); + spider.destroy(); + } + } + + public synchronized void clear() { + for (Spider spider : spiders.values()) { + spider.cancelByTag(); + spider.destroy(); + } + spiders.clear(); + classes.clear(); + recentKey = ""; + } + + public static void stopAll() { + for (Spider spider : spiders.values()){ + spider.cancelByTag(); + } + } + + private boolean loadClassLoader(String jar, String key) { + boolean success = false; + Class classInit = null; + try { + File cacheDir = new File(App.getInstance().getCacheDir().getAbsolutePath() + "/catvod_jsapi"); + if (!cacheDir.exists()) + cacheDir.mkdirs(); + DexClassLoader classLoader = new DexClassLoader(jar, cacheDir.getAbsolutePath(), null, App.getInstance().getClassLoader()); + int count = 0; + do { + try { + try { + classInit = classLoader.loadClass("com.github.catvod.js.Function"); + classInit.getDeclaredConstructor(com.whl.quickjs.wrapper.QuickJSContext.class); + Log.i("JSLoader", "echo-load_com.github.catvod.js.Function"); + } catch (Throwable ignored) { + classInit = classLoader.loadClass("com.github.catvod.js.Method"); + classInit.getDeclaredConstructor(com.whl.quickjs.wrapper.QuickJSContext.class); + Log.i("JSLoader", "echo-load_com.github.catvod.js.Method"); + } + if (classInit != null) { + Log.i("JSLoader", "echo-自定义jsapi代码加载成功!"); + success = true; + break; + } + Thread.sleep(200); + } catch (Throwable th) { + th.printStackTrace(); + } + count++; + } while (count < 5); + + if (success) { + classes.put(key, classInit); + } + } catch (Throwable th) { + th.printStackTrace(); + } + return success; + } + + private Class loadJarInternal(String jar, String md5, String key) { + if (classes.containsKey(key)){ + Log.i("JSLoader", "echo-loadJarInternal cached"); + return classes.get(key); + } + File cache = new File(App.getInstance().getFilesDir().getAbsolutePath() + "/csp/" + key + ".jar"); + if (!md5.isEmpty()) { + if (cache.exists() && MD5.getFileMd5(cache).equalsIgnoreCase(md5)) { + loadClassLoader(cache.getAbsolutePath(), key); + return classes.get(key); + } + }else { + if (cache.exists() && !FileUtils.isWeekAgo(cache)) { + if(loadClassLoader(cache.getAbsolutePath(), key)){ + return classes.get(key); + } + } + } + try { + Response response = OkGo.get(jar).execute(); + InputStream is = response.body().byteStream(); + OutputStream os = new FileOutputStream(cache); + try { + byte[] buffer = new byte[2048]; + int length; + while ((length = is.read(buffer)) > 0) { + os.write(buffer, 0, length); + } + } finally { + try { + is.close(); + os.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + loadClassLoader(cache.getAbsolutePath(), key); + return classes.get(key); + } catch (Throwable e) { + e.printStackTrace(); + } + return null; + } + + public synchronized Spider getSpider(String key, String api, String ext, String jar) { + recentKey = key; + if (spiders.containsKey(key)){ + Log.i("JSLoader", "echo-getSpider cached "+key); + return spiders.get(key); + } + Class classLoader = null; + if (!jar.isEmpty()) { + String[] urls = jar.split(";md5;"); + String jarUrl = urls[0]; + String jarKey = MD5.string2MD5(jarUrl); + String jarMd5 = urls.length > 1 ? urls[1].trim() : ""; + classLoader = loadJarInternal(jarUrl, jarMd5, jarKey); + } + try { + Log.i("JSLoader", "echo-getSpider load"); + Spider sp = new JsSpider(key, api, classLoader); + sp.siteKey = key; + sp.init(App.getInstance(), ext); + spiders.put(key, sp); + return sp; + } catch (Throwable th) { + LOG.i("echo-getSpider-error "+th.getMessage()); + } + return new SpiderNull(); + } + + public Object[] proxyInvoke(Map params) { + try { + Spider proxyFun = spiders.get(recentKey); + if (proxyFun != null) { + return proxyFun.proxyLocal(params); + } + } catch (Throwable th) { + } + return null; + } +} diff --git a/app/src/main/java/com/github/catvod/crawler/ProtectedInitJar.java b/app/src/main/java/com/github/catvod/crawler/ProtectedInitJar.java new file mode 100644 index 0000000000..8ddd90fe2e --- /dev/null +++ b/app/src/main/java/com/github/catvod/crawler/ProtectedInitJar.java @@ -0,0 +1,400 @@ +package com.github.catvod.crawler; + +import android.content.Context; +import android.text.TextUtils; +import android.util.Base64; + +import com.github.tvbox.osc.base.App; + +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.concurrent.ConcurrentHashMap; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +class ProtectedInitJar { + + private static final byte[] DEX_MAGIC = new byte[]{'d', 'e', 'x'}; + private static final int BUFFER_SIZE = 8192; + + private final ConcurrentHashMap jars = new ConcurrentHashMap<>(); + + void clear() { + jars.clear(); + } + + boolean check(String jar) { + Boolean cached = jars.get(jar); + if (cached != null) return cached; + boolean result = scan(jar, App.getInstance().getPackageName()); + jars.put(jar, result); + return result; + } + + void init(Class clz) { + try { + Method get = clz.getMethod("get"); + Object init = get.invoke(null); + Field context = clz.getDeclaredField("c"); + context.setAccessible(true); + context.set(init, App.getInstance()); + } catch (Throwable ignored) { + } + invokeNoArg(clz, "replaceCloudDiskNames"); + invokeStartGoProxy(clz); + } + + private void invokeNoArg(Class clz, String methodName) { + try { + Method method = clz.getMethod(methodName); + method.invoke(null); + } catch (Throwable ignored) { + } + } + + private void invokeStartGoProxy(Class clz) { + try { + Method method = clz.getMethod("startGoProxy", Context.class); + method.invoke(null, App.getInstance()); + } catch (Throwable ignored) { + } + } + + private static boolean scan(String jar, String packageName) { + try { + File file = new File(jar); + if (!file.exists()) return false; + if (isDexFile(file)) { + InputStream is = null; + try { + is = new FileInputStream(file); + return checkDex(readBytes(is), packageName); + } finally { + close(is); + } + } + ZipFile zip = null; + try { + zip = new ZipFile(file); + java.util.Enumeration entries = zip.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + if (!entry.getName().endsWith(".dex")) continue; + InputStream is = null; + try { + is = zip.getInputStream(entry); + if (checkDex(readBytes(is), packageName)) return true; + } finally { + close(is); + } + } + } finally { + close(zip); + } + } catch (Throwable ignored) { + } + return false; + } + + private static boolean checkDex(byte[] data, String packageName) { + if (data == null || data.length < 112 || !startsWith(data, DEX_MAGIC)) return false; + try { + return new Dex(data, packageName).isProtected(); + } catch (Throwable ignored) { + return false; + } + } + + private static boolean isDexFile(File file) { + InputStream is = null; + try { + is = new FileInputStream(file); + byte[] magic = new byte[DEX_MAGIC.length]; + int len = is.read(magic); + return len == DEX_MAGIC.length && startsWith(magic, DEX_MAGIC); + } catch (Throwable ignored) { + return false; + } finally { + close(is); + } + } + + private static byte[] readBytes(InputStream is) throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[BUFFER_SIZE]; + int len; + while ((len = is.read(buffer)) != -1) baos.write(buffer, 0, len); + close(is); + return baos.toByteArray(); + } + + private static boolean startsWith(byte[] data, byte[] pattern) { + if (data == null || pattern == null || data.length < pattern.length) return false; + for (int i = 0; i < pattern.length; i++) { + if (data[i] != pattern[i]) return false; + } + return true; + } + + private static void close(Closeable closeable) { + try { + if (closeable != null) closeable.close(); + } catch (Throwable ignored) { + } + } + + private static class Dex { + + private final byte[] data; + private final int stringIdsSize; + private final int stringIdsOff; + private final int typeIdsSize; + private final int typeIdsOff; + private final int methodIdsSize; + private final int methodIdsOff; + private final int classDefsSize; + private final int classDefsOff; + private final Whitelist whitelist; + + Dex(byte[] data, String packageName) { + this.data = data; + this.stringIdsSize = uint(0x38); + this.stringIdsOff = uint(0x3c); + this.typeIdsSize = uint(0x40); + this.typeIdsOff = uint(0x44); + this.methodIdsSize = uint(0x58); + this.methodIdsOff = uint(0x5c); + this.classDefsSize = uint(0x60); + this.classDefsOff = uint(0x64); + this.whitelist = new Whitelist(packageName); + } + + boolean isProtected() { + boolean hasInit = false; + boolean hasKillProcess = false; + boolean checkedWhitelist = false; + for (int i = 0; i < classDefsSize; i++) { + int off = classDefsOff + i * 32; + String desc = typeDesc(uint(off)); + if (!desc.startsWith("Lcom/github/catvod/spider/Init")) continue; + hasInit = hasInit || "Lcom/github/catvod/spider/Init;".equals(desc); + int classDataOff = uint(off + 24); + if (classDataOff <= 0) continue; + if ("Lcom/github/catvod/spider/Init;".equals(desc)) { + checkedWhitelist = true; + if (scanInitWhitelist(classDataOff)) return false; + } + hasKillProcess = hasKillProcess || scanKillProcess(classDataOff); + if (hasInit && hasKillProcess && checkedWhitelist) return true; + } + return hasInit && hasKillProcess; + } + + private boolean scanInitWhitelist(int off) { + int[] cursor = new int[]{off}; + int staticFields = uleb(cursor); + int instanceFields = uleb(cursor); + int directMethods = uleb(cursor); + int virtualMethods = uleb(cursor); + skipFields(cursor, staticFields + instanceFields); + return scanInitMethods(cursor, directMethods) || scanInitMethods(cursor, virtualMethods); + } + + private boolean scanInitMethods(int[] cursor, int count) { + int methodIdx = 0; + for (int i = 0; i < count; i++) { + methodIdx += uleb(cursor); + uleb(cursor); + int codeOff = uleb(cursor); + if (codeOff <= 0 || methodIdx >= methodIdsSize) continue; + if ("init".equals(methodName(methodIdx)) && scanWhitelist(codeOff)) return true; + } + return false; + } + + private boolean scanKillProcess(int off) { + int[] cursor = new int[]{off}; + int staticFields = uleb(cursor); + int instanceFields = uleb(cursor); + int directMethods = uleb(cursor); + int virtualMethods = uleb(cursor); + skipFields(cursor, staticFields + instanceFields); + return scanKillMethods(cursor, directMethods) || scanKillMethods(cursor, virtualMethods); + } + + private boolean scanKillMethods(int[] cursor, int count) { + int methodIdx = 0; + for (int i = 0; i < count; i++) { + methodIdx += uleb(cursor); + uleb(cursor); + int codeOff = uleb(cursor); + if (codeOff > 0 && scanKillProcessCode(codeOff)) return true; + } + return false; + } + + private boolean scanWhitelist(int codeOff) { + int insnsSize = uint(codeOff + 12); + int insnsOff = codeOff + 16; + int end = insnsOff + insnsSize * 2; + for (int off = insnsOff; off + 1 < end && off + 1 < data.length; off += 2) { + int op = data[off] & 0xff; + if (op == 0x1a && off + 3 < end) { + if (whitelist.contains(string(ushort(off + 2)))) return true; + } else if (op == 0x1b && off + 5 < end) { + if (whitelist.contains(string(uint(off + 2)))) return true; + } + } + return false; + } + + private boolean scanKillProcessCode(int codeOff) { + int insnsSize = uint(codeOff + 12); + int insnsOff = codeOff + 16; + int end = insnsOff + insnsSize * 2; + for (int off = insnsOff; off + 3 < end && off + 3 < data.length; off += 2) { + int op = data[off] & 0xff; + if (op >= 0x6e && op <= 0x72 && isKillProcess(ushort(off + 2))) return true; + } + return false; + } + + private void skipFields(int[] cursor, int count) { + for (int i = 0; i < count; i++) { + uleb(cursor); + uleb(cursor); + } + } + + private boolean isKillProcess(int methodIdx) { + if (methodIdx < 0 || methodIdx >= methodIdsSize) return false; + return "Landroid/os/Process;".equals(methodClass(methodIdx)) && "killProcess".equals(methodName(methodIdx)); + } + + private String methodClass(int methodIdx) { + return typeDesc(ushort(methodIdsOff + methodIdx * 8)); + } + + private String methodName(int methodIdx) { + return string(uint(methodIdsOff + methodIdx * 8 + 4)); + } + + private String typeDesc(int typeIdx) { + if (typeIdx < 0 || typeIdx >= typeIdsSize) return ""; + return string(uint(typeIdsOff + typeIdx * 4)); + } + + private String string(int stringIdx) { + if (stringIdx < 0 || stringIdx >= stringIdsSize) return ""; + int[] cursor = new int[]{uint(stringIdsOff + stringIdx * 4)}; + uleb(cursor); + int start = cursor[0]; + int end = start; + while (end < data.length && data[end] != 0) end++; + try { + return new String(data, start, end - start, "UTF-8"); + } catch (Throwable ignored) { + return ""; + } + } + + private int uleb(int[] cursor) { + int result = 0; + int shift = 0; + while (cursor[0] < data.length) { + int b = data[cursor[0]++] & 0xff; + result |= (b & 0x7f) << shift; + if ((b & 0x80) == 0) break; + shift += 7; + } + return result; + } + + private int ushort(int off) { + if (off < 0 || off + 1 >= data.length) return 0; + return (data[off] & 0xff) | ((data[off + 1] & 0xff) << 8); + } + + private int uint(int off) { + if (off < 0 || off + 3 >= data.length) return 0; + return (data[off] & 0xff) + | ((data[off + 1] & 0xff) << 8) + | ((data[off + 2] & 0xff) << 16) + | ((data[off + 3] & 0xff) << 24); + } + } + + private static class Whitelist { + + private static final String AES_KEY = "1234123412341234"; + private final String packageName; + + Whitelist(String packageName) { + this.packageName = packageName; + } + + boolean contains(String value) { + if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(value)) return false; + if (packageName.equals(value) || containsItem(value)) return true; + return containsItem(decrypt(value)); + } + + private boolean containsItem(String text) { + if (TextUtils.isEmpty(text)) return false; + int start = 0; + while (start <= text.length()) { + int end = text.indexOf(',', start); + if (end < 0) end = text.length(); + if (packageName.equals(text.substring(start, end).trim())) return true; + start = end + 1; + } + return false; + } + + private String decrypt(String value) { + if (!isCipherText(value)) return ""; + try { + byte[] key = AES_KEY.getBytes("UTF-8"); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(key)); + return new String(cipher.doFinal(Base64.decode(value, Base64.DEFAULT)), "UTF-8"); + } catch (Throwable ignored) { + return ""; + } + } + + private boolean isCipherText(String value) { + int len = value.length(); + if (len < 24 || len % 4 != 0) return false; + int padding = 0; + for (int i = 0; i < len; i++) { + char c = value.charAt(i); + if (c == '=') { + padding++; + if (i < len - 2) return false; + } else if (!isBase64(c)) { + return false; + } + } + int rawLen = len * 3 / 4 - padding; + return rawLen > 0 && rawLen % 16 == 0; + } + + private boolean isBase64(char c) { + return (c >= 'A' && c <= 'Z') + || (c >= 'a' && c <= 'z') + || (c >= '0' && c <= '9') + || c == '+' + || c == '/'; + } + } +} diff --git a/app/src/main/java/com/github/catvod/crawler/Spider.java b/app/src/main/java/com/github/catvod/crawler/Spider.java index e67abb6baf..7183d37070 100644 --- a/app/src/main/java/com/github/catvod/crawler/Spider.java +++ b/app/src/main/java/com/github/catvod/crawler/Spider.java @@ -2,26 +2,35 @@ import android.content.Context; -import com.github.tvbox.osc.util.OkGoHelper; +import com.github.catvod.net.OkHttp; import org.json.JSONObject; import java.util.HashMap; import java.util.List; +import java.util.Map; import okhttp3.Dns; +import okhttp3.OkHttpClient; -public abstract class Spider { +public class Spider { public static JSONObject empty = new JSONObject(); + public String siteKey; + + protected static Context mContext; public void init(Context context) { + mContext = context; } public void init(Context context, String extend) { init(context); } + public void initApi(SpiderApi api) { + } + /** * 首页数据内容 * @@ -75,6 +84,10 @@ public String searchContent(String key, boolean quick) { return ""; } + public String searchContent(String key, boolean quick, String pg) { + return searchContent(key, quick); + } + /** * 播放信息 * @@ -105,7 +118,48 @@ public boolean manualVideoCheck() { return false; } + /** + * 直播list + * @return + */ + public String liveContent(String url) { + return ""; + } + public static Dns safeDns() { - return OkGoHelper.dnsOverHttps; + return OkHttp.dns(); + } + + public static OkHttpClient client() { + return OkHttp.client(); + } + + /** + * 取消请求tag + */ + public void cancelByTag() { + + } + + /** + * 销毁 + */ + public void destroy() {} + + /** + * 爬虫代理 + * @param params + * @return + */ + public Object[] proxyLocal(Map< String, String > params) { + return null; + } + + public Object[] proxy(Map params) { + return proxyLocal(params); + } + + public String action(String action) { + return null; } } diff --git a/app/src/main/java/com/github/catvod/crawler/SpiderApi.java b/app/src/main/java/com/github/catvod/crawler/SpiderApi.java new file mode 100644 index 0000000000..d0a87ef287 --- /dev/null +++ b/app/src/main/java/com/github/catvod/crawler/SpiderApi.java @@ -0,0 +1,170 @@ +package com.github.catvod.crawler; + +import android.app.Activity; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; +import android.util.Base64; +import android.view.Surface; +import android.view.WindowManager; + +import com.github.tvbox.osc.server.ControlManager; +import com.github.tvbox.osc.base.App; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonPrimitive; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import okhttp3.FormBody; +import okhttp3.Headers; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +public class SpiderApi { + + public String getAddress(boolean local) { + try { + return ControlManager.get().getAddress(local); + } catch (Throwable th) { + return ""; + } + } + + public String getPort() { + try { + String address = ControlManager.get().getAddress(true); + int idx = address.lastIndexOf(":"); + return idx >= 0 ? address.substring(idx + 1).replace("/", "") : ""; + } catch (Throwable th) { + return ""; + } + } + + public void log(String msg) { + try { + SpiderDebug.log(msg); + } catch (Throwable ignored) { + } + } + + public int getScreenOrientation() { + try { + Activity activity = App.getInstance().getCurrentActivity(); + Context context = activity == null ? App.getInstance() : activity; + int orientation = context.getResources().getConfiguration().orientation; + int rotation = Surface.ROTATION_0; + WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + if (windowManager != null && windowManager.getDefaultDisplay() != null) { + rotation = windowManager.getDefaultDisplay().getRotation(); + } + if (orientation == Configuration.ORIENTATION_PORTRAIT) { + return ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; + } + if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + return rotation == Surface.ROTATION_90 ? ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE : ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; + } + return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; + } catch (Throwable th) { + return ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE; + } + } + + public String multiReq(JsonArray array) { + try { + if (array == null || array.size() == 0) return ""; + ExecutorService executor = Executors.newFixedThreadPool(Math.min(array.size(), 6)); + java.util.ArrayList> futures = new java.util.ArrayList<>(); + for (JsonElement element : array) { + if (!element.isJsonObject()) continue; + JsonObject obj = element.getAsJsonObject(); + futures.add(executor.submit(() -> request(obj))); + } + JsonArray result = new JsonArray(); + for (Future future : futures) result.add(toResult(future.get())); + executor.shutdown(); + return result.toString(); + } catch (Throwable th) { + return ""; + } + } + + public String webParse(String url, String flag) { + try { + if (url == null || url.isEmpty()) return ""; + String encoded = Base64.encodeToString(url.getBytes("UTF-8"), Base64.DEFAULT | Base64.URL_SAFE | Base64.NO_WRAP); + return "proxy://go=SuperParse&flag=" + (flag == null ? "" : flag) + "&url=" + encoded; + } catch (Throwable th) { + return ""; + } + } + + private static String request(JsonObject obj) { + try { + String url = string(obj, "url"); + if (url.isEmpty()) return ""; + String method = string(obj, "method"); + Headers headers = headers(obj.get("headers")); + Request.Builder builder = new Request.Builder().url(url).headers(headers); + if ("POST".equalsIgnoreCase(method)) builder.post(body(obj)); + OkHttpClient client = com.github.catvod.net.OkHttp.client(); + try (Response response = client.newCall(builder.build()).execute()) { + return response.body() != null ? response.body().string() : ""; + } + } catch (Throwable th) { + return ""; + } + } + + private static JsonElement toResult(String text) { + if (text == null) return new JsonPrimitive(""); + try { + String trim = text.trim(); + if (trim.startsWith("{") || trim.startsWith("[")) { + return JsonParser.parseString(trim); + } + } catch (Throwable ignored) { + } + return new JsonPrimitive(text); + } + + private static RequestBody body(JsonObject obj) { + JsonElement data = obj.get("data"); + if (data == null || data.isJsonNull()) return RequestBody.create(null, ""); + String postType = string(obj, "postType"); + if ("form".equalsIgnoreCase(postType) && data.isJsonObject()) { + FormBody.Builder builder = new FormBody.Builder(); + for (Map.Entry entry : data.getAsJsonObject().entrySet()) { + builder.add(entry.getKey(), entry.getValue().getAsString()); + } + return builder.build(); + } + return RequestBody.create(null, data.isJsonPrimitive() ? data.getAsString() : data.toString()); + } + + private static Headers headers(JsonElement element) { + try { + if (element == null || element.isJsonNull() || !element.isJsonObject()) return new Headers.Builder().build(); + HashMap map = new HashMap<>(); + for (Map.Entry entry : element.getAsJsonObject().entrySet()) { + map.put(entry.getKey(), entry.getValue().getAsString()); + } + return Headers.of(map); + } catch (Throwable th) { + return new Headers.Builder().build(); + } + } + + private static String string(JsonObject obj, String key) { + JsonElement element = obj.get(key); + return element == null || element.isJsonNull() ? "" : element.getAsString(); + } +} diff --git a/app/src/main/java/com/github/catvod/crawler/js/Async.java b/app/src/main/java/com/github/catvod/crawler/js/Async.java new file mode 100644 index 0000000000..865d455b37 --- /dev/null +++ b/app/src/main/java/com/github/catvod/crawler/js/Async.java @@ -0,0 +1,101 @@ +package com.github.catvod.crawler.js; + +import com.whl.quickjs.wrapper.JSCallFunction; +import com.whl.quickjs.wrapper.JSFunction; +import com.whl.quickjs.wrapper.JSObject; + +import java.util.concurrent.CountDownLatch; + +public class Async { + + private final Result future; + + private final JSCallFunction success = new JSCallFunction() { + @Override + public Object call(Object... args) { + future.complete(args != null && args.length > 0 ? args[0] : null); + return null; + } + }; + + private final JSCallFunction error = new JSCallFunction() { + @Override + public Object call(Object... args) { + String msg = args != null && args.length > 0 && args[0] != null ? args[0].toString() : ""; + future.completeExceptionally(new Exception(msg)); + return null; + } + }; + + public static Result run(JSObject object, String name, Object[] args) { + return new Async().call(object, name, args); + } + + private Async() { + this.future = new Result(); + } + + private Result call(JSObject object, String name, Object[] args) { + JSFunction function = object.getJSFunction(name); + if (function == null) return empty(); + try { + Object result = function.call(args); + if (result instanceof JSObject) then((JSObject) result); + else future.complete(result); + } catch (Throwable t) { + future.completeExceptionally(t); + } finally { + function.release(); + } + return future; + } + + private Result empty() { + future.complete(null); + return future; + } + + private void then(JSObject promise) { + JSFunction then = promise.getJSFunction("then"); + if (then == null) { + future.complete(promise); + } else { + consume(then, success); + consume(promise.getJSFunction("catch"), error); + } + } + + private void consume(JSFunction function, JSCallFunction callback) { + if (function == null) return; + try { + function.call(callback); + } finally { + function.release(); + } + } + + public static class Result { + private final CountDownLatch latch = new CountDownLatch(1); + private Object value; + private Throwable error; + + void complete(Object value) { + this.value = value; + latch.countDown(); + } + + void completeExceptionally(Throwable error) { + this.error = error; + latch.countDown(); + } + + public Object get() throws Exception { + latch.await(); + if (error != null) { + if (error instanceof Exception) throw (Exception) error; + throw new Exception(error); + } + return value; + } + } +} diff --git a/app/src/main/java/com/github/catvod/crawler/js/Connect.java b/app/src/main/java/com/github/catvod/crawler/js/Connect.java new file mode 100644 index 0000000000..a8ad12e6c9 --- /dev/null +++ b/app/src/main/java/com/github/catvod/crawler/js/Connect.java @@ -0,0 +1,141 @@ +package com.github.catvod.crawler.js; + +import android.util.Base64; + +import com.github.tvbox.osc.util.LOG; +import com.github.tvbox.osc.util.OkGoHelper; +import com.google.common.net.HttpHeaders; +import com.lzy.okgo.OkGo; +import com.whl.quickjs.wrapper.JSArray; +import com.whl.quickjs.wrapper.JSObject; +import com.whl.quickjs.wrapper.JSUtils; +import com.whl.quickjs.wrapper.QuickJSContext; + +import java.util.List; +import java.util.Map; +import java.util.Random; + +import okhttp3.Call; +import okhttp3.FormBody; +import okhttp3.Headers; +import okhttp3.MediaType; +import okhttp3.MultipartBody; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +public class Connect { + static OkHttpClient client; + + public static Call to(String url, Req req) { + client = OkGoHelper.getDefaultClient(); + return client.newCall(getRequest(url, req, Headers.of(req.getHeader()))); + } + + public static JSObject success(QuickJSContext ctx, Req req, Response res) { + try { + JSObject jsObject = ctx.createNewJSObject(); + JSObject jsHeader = ctx.createNewJSObject(); + setHeader(ctx, res, jsHeader); + ctx.setProperty(jsObject, "headers", jsHeader); + if (req.getBuffer() == 0) ctx.setProperty(jsObject, "content", new String(res.body().bytes(), req.getCharset())); + if (req.getBuffer() == 1) { + JSArray array = ctx.createNewJSArray(); + byte[] bytes = res.body().bytes(); + for (int i = 0; i < bytes.length; i++) array.set((int) bytes[i], i); + ctx.setProperty(jsObject, "content", array); + } + if (req.getBuffer() == 2) ctx.setProperty(jsObject, "content", Base64.encodeToString(res.body().bytes(), Base64.DEFAULT | Base64.NO_WRAP)); + return jsObject; + } catch (Exception e) { + return error(ctx); + } + } + + public static JSObject error(QuickJSContext ctx) { + JSObject jsObject = ctx.createNewJSObject(); + JSObject jsHeader = ctx.createNewJSObject(); + ctx.setProperty(jsObject, "headers", jsHeader); + ctx.setProperty(jsObject, "content", ""); + return jsObject; + } + + private static Request getRequest(String url, Req req, Headers headers) { + if (req.getMethod().equalsIgnoreCase("post")) { + return new Request.Builder().url(url).tag("js_okhttp_tag").headers(headers).post(getPostBody(req, headers.get(HttpHeaders.CONTENT_TYPE))).build(); + } else if (req.getMethod().equalsIgnoreCase("header")) { + return new Request.Builder().url(url).tag("js_okhttp_tag").headers(headers).head().build(); + } else { + return new Request.Builder().url(url).tag("js_okhttp_tag").headers(headers).get().build(); + } + } + + private static RequestBody getPostBody(Req req, String contentType) { + if (req.getData() != null && req.getPostType().equals("json")) return getJsonBody(req); + if (req.getData() != null && req.getPostType().equals("form")) return getFormBody(req); + if (req.getData() != null && req.getPostType().equals("form-data")) return getFormDataBody(req); + if (req.getBody() != null && contentType != null) return RequestBody.create(MediaType.get(contentType), req.getBody()); + return RequestBody.create(null, ""); + } + + private static RequestBody getJsonBody(Req req) { + return RequestBody.create(MediaType.get("application/json"), req.getData().toString()); + } + + private static RequestBody getFormBody(Req req) { + FormBody.Builder formBody = new FormBody.Builder(); + Map params = Json.toMap(req.getData()); + for (String key : params.keySet()) formBody.add(key, params.get(key)); + return formBody.build(); + } + + private static RequestBody getFormDataBody(Req req) { + String boundary = "--dio-boundary-" + new Random().nextInt(42949) + "" + new Random().nextInt(67296); + MultipartBody.Builder builder = new MultipartBody.Builder(boundary).setType(MultipartBody.FORM); + Map params = Json.toMap(req.getData()); + for (String key : params.keySet()) builder.addFormDataPart(key, params.get(key)); + return builder.build(); + } + + private static void setHeader(QuickJSContext ctx, Response res, JSObject object) { + for (Map.Entry> entry : res.headers().toMultimap().entrySet()) { + if (entry.getValue().size() == 1) ctx.setProperty(object, entry.getKey(), entry.getValue().get(0)); + if (entry.getValue().size() >= 2) ctx.setProperty(object, entry.getKey(), new JSUtils().toArray(ctx, entry.getValue())); + } + } + public static void cancelByTag(Object tag) { + try { + if (client != null) { + for (Call call : client.dispatcher().queuedCalls()) { + if (tag.equals(call.request().tag())) { + call.cancel(); + } + } + for (Call call : client.dispatcher().runningCalls()) { + if (tag.equals(call.request().tag())) { + call.cancel(); + } + } + } + OkGo.getInstance().cancelTag(tag); + cancelDefaultClient(tag); + } catch (Exception e) { + } + } + + private static void cancelDefaultClient(Object tag) { + OkHttpClient defaultClient = OkGoHelper.getDefaultClient(); + if (defaultClient == null || tag == null) return; + for (Call call : defaultClient.dispatcher().queuedCalls()) { + if (tag.equals(call.request().tag())) { + call.cancel(); + } + } + for (Call call : defaultClient.dispatcher().runningCalls()) { + if (tag.equals(call.request().tag())) { + call.cancel(); + } + } + } +} diff --git a/app/src/main/java/com/github/catvod/crawler/js/Crypto.java b/app/src/main/java/com/github/catvod/crawler/js/Crypto.java new file mode 100644 index 0000000000..93d704fc0f --- /dev/null +++ b/app/src/main/java/com/github/catvod/crawler/js/Crypto.java @@ -0,0 +1,82 @@ +package com.github.catvod.crawler.js; + +import android.util.Base64; + +import java.security.Key; +import java.security.KeyFactory; +import java.security.PublicKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Arrays; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +public class Crypto { + + public static String aes(String mode, boolean encrypt, String input, boolean inBase64, String key, String iv, boolean outBase64) { + try { + byte[] keyBuf = key.getBytes(); + if (keyBuf.length < 16) keyBuf = Arrays.copyOf(keyBuf, 16); + byte[] ivBuf = iv == null ? new byte[0] : iv.getBytes(); + if (ivBuf.length < 16) ivBuf = Arrays.copyOf(ivBuf, 16); + Cipher cipher = Cipher.getInstance(mode + "Padding"); + SecretKeySpec keySpec = new SecretKeySpec(keyBuf, "AES"); + if (iv == null) cipher.init(encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, keySpec); + else cipher.init(encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(ivBuf)); + byte[] inBuf = inBase64 ? Base64.decode(input.replaceAll("_", "/").replaceAll("-", "+"), Base64.DEFAULT) : input.getBytes("UTF-8"); + return outBase64 ? Base64.encodeToString(cipher.doFinal(inBuf), Base64.NO_WRAP) : new String(cipher.doFinal(inBuf), "UTF-8"); + } catch (Exception e) { + e.printStackTrace(); + return ""; + } + } + + public static String rsa(boolean pub, boolean encrypt, String input, boolean inBase64, String key, boolean outBase64) { + try { + Key rsaKey = generateKey(pub, key); + int len = getModulusLength(rsaKey); + byte[] outBytes = new byte[0]; + byte[] inBytes = inBase64 ? Base64.decode(input.replaceAll("_", "/").replaceAll("-", "+"), Base64.DEFAULT) : input.getBytes("UTF-8"); + Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + cipher.init(encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, rsaKey); + int blockLen = encrypt ? len / 8 - 11 : len / 8; + int bufIdx = 0; + while (bufIdx < inBytes.length) { + int bufEndIdx = Math.min(bufIdx + blockLen, inBytes.length); + byte[] tmpInBytes = new byte[bufEndIdx - bufIdx]; + System.arraycopy(inBytes, bufIdx, tmpInBytes, 0, tmpInBytes.length); + byte[] tmpBytes = cipher.doFinal(tmpInBytes); + bufIdx = bufEndIdx; + outBytes = concatArrays(outBytes, tmpBytes); + } + return outBase64 ? Base64.encodeToString(outBytes, Base64.NO_WRAP) : new String(outBytes, "UTF-8"); + } catch (Exception e) { + e.printStackTrace(); + return ""; + } + } + + private static Key generateKey(boolean pub, String key) throws Exception { + if (pub) key = key.replaceAll("\r\n", "").replaceAll("\n", "").replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", ""); + else key = key.replaceAll("\r\n", "").replaceAll("\n", "").replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", ""); + return pub ? KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(Base64.decode(key, Base64.DEFAULT))) : KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(Base64.decode(key, Base64.DEFAULT))); + } + + private static int getModulusLength(Key key) { + if (key instanceof PublicKey) return ((RSAPublicKey) key).getModulus().bitLength(); + else return ((RSAPrivateKey) key).getModulus().bitLength(); + } + + private static byte[] concatArrays(byte[] a, byte[] b) { + int aLen = a.length; + int bLen = b.length; + byte[] result = new byte[aLen + bLen]; + System.arraycopy(a, 0, result, 0, aLen); + System.arraycopy(b, 0, result, aLen, bLen); + return result; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/catvod/crawler/js/FunCall.java b/app/src/main/java/com/github/catvod/crawler/js/FunCall.java new file mode 100644 index 0000000000..352cfd4e43 --- /dev/null +++ b/app/src/main/java/com/github/catvod/crawler/js/FunCall.java @@ -0,0 +1,42 @@ +package com.github.catvod.crawler.js; + +import com.whl.quickjs.wrapper.JSCallFunction; +import com.whl.quickjs.wrapper.JSFunction; +import com.whl.quickjs.wrapper.JSObject; + +import java.util.concurrent.Callable; + +public class FunCall implements Callable { + + private final JSObject jsObject; + private final Object[] args; + private final String name; + private Object result; + + public static FunCall call(JSObject jsObject, String name, Object... args) { + return new FunCall(jsObject, name, args); + } + + private FunCall(JSObject jsObject, String name, Object... args) { + this.jsObject = jsObject; + this.name = name; + this.args = args; + } + + @Override + public Object call() throws Exception { + result = jsObject.getJSFunction(name).call(args); + if (!(result instanceof JSObject)) return result; + JSObject promise = (JSObject) result; + JSFunction then = promise.getJSFunction("then"); + if (then != null) then.call(jsCallFunction); + return result; + } + + private final JSCallFunction jsCallFunction = new JSCallFunction() { + @Override + public Object call(Object... args) { + return result = args[0]; + } + }; +} \ No newline at end of file diff --git a/app/src/main/java/com/github/catvod/crawler/js/Global.java b/app/src/main/java/com/github/catvod/crawler/js/Global.java new file mode 100644 index 0000000000..3a11a4b6e7 --- /dev/null +++ b/app/src/main/java/com/github/catvod/crawler/js/Global.java @@ -0,0 +1,331 @@ +package com.github.catvod.crawler.js; + +import androidx.annotation.Keep; +import androidx.annotation.NonNull; + +import com.github.catvod.Proxy; +import com.github.catvod.crawler.js.rsa.RSAEncrypt; +import com.whl.quickjs.wrapper.ContextSetter; +import com.whl.quickjs.wrapper.Function; +import com.whl.quickjs.wrapper.JSArray; +import com.whl.quickjs.wrapper.JSFunction; +import com.whl.quickjs.wrapper.JSObject; +import com.whl.quickjs.wrapper.JSUtils; +import com.whl.quickjs.wrapper.QuickJSContext; + +import org.json.JSONObject; + +import java.io.IOException; +import java.net.URLEncoder; + +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.ExecutorService; + +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.Response; + +public class Global { + private QuickJSContext runtime; + public ExecutorService executor; + private final Timer timer; + + public Global(ExecutorService executor) { + this.executor = executor; + this.timer = new Timer(); + } + + @Keep + @Function + public String getProxy(boolean local) { + return Proxy.getUrl(local) + "?do=js"; + } + + @Keep + @Function + public String js2Proxy(Boolean dynamic, Integer siteType, String siteKey, String url, JSObject headers) { + boolean local = dynamic == null || !dynamic; + return getProxy(local) + "&from=catvod" + "&siteType=" + siteType + "&siteKey=" + siteKey + "&header=" + URLEncoder.encode(headers.stringify()) + "&url=" + URLEncoder.encode(url); + } + + @Keep + @Function + public String joinUrl(String parent, String child) { + return HtmlParser.joinUrl(parent, child); + } + + @Keep + @Function + public String pd(String html, String rule, String add_url) { + return HtmlParser.parseDomForUrl(html, rule, add_url); + } + + @Keep + @Function + public String pdfh(String html, String rule) { + return HtmlParser.parseDomForUrl(html, rule, ""); + } + + @Keep + @Function + public JSArray pdfa(String html, String rule) { + + return new JSUtils().toArray(runtime, HtmlParser.parseDomForArray(html, rule)); + } + + @Keep + @Function + public JSArray pdfla(String html, String p1, String list_text, String list_url, String add_url) { + return new JSUtils().toArray(runtime, HtmlParser.parseDomForList(html, p1, list_text, list_url, add_url)); + } + + @Keep + @Function + public String s2t(String text) { + try { + return Trans.s2t(false, text); + } catch (Exception e) { + return ""; + } + } + + @Keep + @Function + public String t2s(String text) { + try { + return Trans.t2s(false, text); + } catch (Exception e) { + return ""; + } + } + + @Keep + @Function + public String aesX(String mode, boolean encrypt, String input, boolean inBase64, String key, String iv, boolean outBase64) { + String result = Crypto.aes(mode, encrypt, input, inBase64, key, iv, outBase64); + //LOG.e("aesX",String.format("mode:%s\nencrypt:%s\ninBase64:%s\noutBase64:%s\nkey:%s\niv:%s\ninput:\n%s\nresult:\n%s", mode, encrypt, inBase64, outBase64, key, iv, input, result)); + return result; + } + + @Keep + @Function + public String rsaX(String mode, boolean pub, boolean encrypt, String input, boolean inBase64, String key, boolean outBase64) { + String result = Crypto.rsa(pub, encrypt, input, inBase64, key, outBase64); + //LOG.e("aesX",String.format("mode:%s\npub:%s\nencrypt:%s\ninBase64:%s\noutBase64:%s\nkey:\n%s\ninput:\n%s\nresult:\n%s", mode, pub, encrypt, inBase64, outBase64, key, input, result)); + return result; + } + + @Keep + @Function + public String rsaEncrypt(String data, String key) { + return rsaEncrypt(data, key, null); + } + /** + * RSA 加密 + * + * @param data 要加密的数据 + * @param key 密钥,type 为 1 则公钥,type 为 2 则私钥 + * @param options 加密的选项,包含加密配置和类型:{ config: "RSA/ECB/PKCS1Padding", type: 1, long: 1 } + * config 加密的配置,默认 RSA/ECB/PKCS1Padding (可选) + * type 加密类型,1 公钥加密 私钥解密,2 私钥加密 公钥解密(可选,默认 1) + * long 加密方式,1 普通,2 分段(可选,默认 1) + * block 分段长度,false 固定117,true 自动(可选,默认 true ) + * @return 返回加密结果 + */ + + @Keep + @Function + public String rsaEncrypt(String data, String key, JSObject options) { + int mLong = 1; + int mType = 1; + boolean mBlock = true; + String mConfig = null; + if (options != null) { + JSONObject op = JSUtils.toJsonObject(options); + if (op.has("config")) { + try { + mConfig = (String) op.get("config"); + } catch (Exception e) { + e.printStackTrace(); + } + } + if (op.has("type")) { + try { + mType = ((Double) op.get("type")).intValue(); + } catch (Exception e) { + e.printStackTrace(); + } + } + if (op.has("long")) { + try { + mLong = ((Double) op.get("long")).intValue(); + } catch (Exception e) { + e.printStackTrace(); + } + } + if (op.has("block")) { + try { + mBlock = (Boolean) op.get("block"); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + try { + switch (mType) { + case 1: + if (mConfig != null) { + return RSAEncrypt.encryptByPublicKey(data, key, mConfig, mLong, mBlock); + } else { + return RSAEncrypt.encryptByPublicKey(data, key, mLong, mBlock); + } + case 2: + if (mConfig != null) { + return RSAEncrypt.encryptByPrivateKey(data, key, mConfig, mLong, mBlock); + } else { + return RSAEncrypt.encryptByPrivateKey(data, key, mLong, mBlock); + } + default: + return ""; + } + } catch (Exception e) { + return ""; + } + } + + @Keep + @Function + public String rsaDecrypt(String encryptBase64Data, String key) { + return rsaDecrypt(encryptBase64Data, key, null); + } + + /** + * RSA 解密 + * + * @param encryptBase64Data 加密后的 Base64 字符串 + * @param key 密钥,type 为 1 则私钥,type 为 2 则公钥 + * @param options 解密的选项,包含解密配置和类型:{ config: "RSA/ECB/PKCS1Padding", type: 1, long: 1 } + * config 解密的配置,默认 RSA/ECB/PKCS1Padding (可选) + * type 解密类型,1 公钥加密 私钥解密,2 私钥加密 公钥解密(可选,默认 1) + * long 解密方式,1 普通,2 分段(可选,默认 1) + * block 分段长度,false 固定128,true 自动(可选,默认 true ) + * @return 返回解密结果 + */ + @Keep + @Function + public String rsaDecrypt(String encryptBase64Data, String key, JSObject options) { + int mLong = 1; + int mType = 1; + boolean mBlock = true; + String mConfig = null; + if (options != null) { + JSONObject op = JSUtils.toJsonObject(options); + if (op.has("config")) { + try { + mConfig = (String) op.get("config"); + } catch (Exception e) { + e.printStackTrace(); + } + } + if (op.has("type")) { + try { + mType = ((Double) op.get("type")).intValue(); + } catch (Exception e) { + e.printStackTrace(); + } + } + if (op.has("long")) { + try { + mLong = ((Double) op.get("long")).intValue(); + } catch (Exception e) { + e.printStackTrace(); + } + } + if (op.has("block")) { + try { + mBlock = (Boolean) op.get("block"); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + try { + switch (mType) { + case 1: + if (mConfig != null) { + return RSAEncrypt.decryptByPrivateKey(encryptBase64Data, key, mConfig, mLong, mBlock); + } else { + return RSAEncrypt.decryptByPrivateKey(encryptBase64Data, key, mLong, mBlock); + } + case 2: + if (mConfig != null) { + return RSAEncrypt.decryptByPublicKey(encryptBase64Data, key, mConfig, mLong, mBlock); + } else { + return RSAEncrypt.decryptByPublicKey(encryptBase64Data, key, mLong, mBlock); + } + default: + return ""; + } + } catch (Exception e) { + return ""; + } + } + + private JSObject req(String url, JSObject options) { + try { + Req req = Req.objectFrom(JSUtils.toJsonObject(options).toString()); + Response res = Connect.to(url, req).execute(); + return Connect.success(runtime, req, res); + } catch (Exception e) { + return Connect.error(runtime); + } + } + + @Keep + @Function + public JSObject _http(String url, JSObject options) { + JSFunction complete = options.getJSFunction("complete"); + if (complete == null) return req(url, options); + Req req = Req.objectFrom(JSUtils.toJsonObject(options).toString()); + Connect.to(url, req).enqueue(getCallback(complete, req)); + return null; + } + + @Keep + @Function + public void setTimeout(JSFunction func, Integer delay) { + func.hold(); + timer.schedule(new TimerTask() { + @Override + public void run() { + if (!executor.isShutdown()) executor.submit(() -> {func.call();}); + } + }, delay); + } + + private Callback getCallback(JSFunction complete, Req req) { + return new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response res) { + executor.submit(() -> { + complete.call(Connect.success(runtime, req, res)); + }); + } + + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + executor.submit(() -> { + complete.call(Connect.error(runtime)); + }); + } + }; + } + @Keep + // 声明用于依赖注入的 QuickJSContext + @ContextSetter + public void setJSContext(QuickJSContext runtime) { + this.runtime = runtime; + } + +} diff --git a/app/src/main/java/com/github/catvod/crawler/js/HtmlParser.java b/app/src/main/java/com/github/catvod/crawler/js/HtmlParser.java new file mode 100644 index 0000000000..f53ef46498 --- /dev/null +++ b/app/src/main/java/com/github/catvod/crawler/js/HtmlParser.java @@ -0,0 +1,300 @@ +package com.github.catvod.crawler.js; + +import android.text.TextUtils; +import com.github.tvbox.osc.util.StringUtils; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class HtmlParser { + private static String pdfh_html = ""; + private static String pdfa_html = ""; + private static final Pattern p = Pattern.compile("url\\((.*?)\\)", Pattern.MULTILINE | Pattern.DOTALL); + private static final Pattern NOADD_INDEX = Pattern.compile(":eq|:lt|:gt|:first|:last|^body$|^#"); // 不自动加eq下标索引 + private static final Pattern URLJOIN_ATTR = Pattern.compile("(url|src|href|-original|-src|-play|-url|style)$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); // 需要自动urljoin的属性 + private static final Pattern SPECIAL_URL = Pattern.compile("^(ftp|magnet|thunder|ws):", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); // 过滤特殊链接,不走urlJoin + private static Document pdfh_doc = null; + private static Document pdfa_doc = null; + + public static String joinUrl(String parent, String child) { + if (StringUtils.isEmpty(parent)) { + return child; + } + + URL url; + String q = parent; + try { + url = new URL(new URL(parent), child); + q = url.toExternalForm(); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + // if (q.contains("#")) { + // q = q.replaceAll("^(.+?)#.*?$", "$1"); + // } + return q; + } + + public static class Painfo { + public String nparse_rule; + public int nparse_index; + public List excludes; + } + + private static Painfo getParseInfo(String nparse) { + /* + 根据传入的单规则获取 parse规则,索引位置,排除列表 -- 可以用于剔除元素,支持多个,按标签剔除,按id剔除等操作 + :param nparse: + :return:*/ + Painfo painfo = new Painfo(); + //List excludes = new ArrayList<>(); //定义排除列表默认值为空 + //int nparse_index; //定义位置索引默认值为0 + painfo.nparse_rule = nparse; //定义规则默认值为本身 + if (nparse.contains(":eq")) { + painfo.nparse_rule = nparse.split(":")[0]; + String nparse_pos = nparse.split(":")[1]; + + if (painfo.nparse_rule.contains("--")) { + String[] rules = painfo.nparse_rule.split("--"); + painfo.excludes = new ArrayList<>(Arrays.asList(rules)); + painfo.excludes.remove(0); + painfo.nparse_rule = rules[0]; + } else if (nparse_pos.contains("--")) { + String[] rules = nparse_pos.split("--"); + painfo.excludes = new ArrayList<>(Arrays.asList(rules)); + painfo.excludes.remove(0); + nparse_pos = rules[0]; + } + + try { + painfo.nparse_index = Integer.parseInt(nparse_pos.replace("eq(", "").replace(")", "")); + } catch (Exception e1) { + painfo.nparse_index = 0; + } + } else { + if (nparse.contains("--")) { + String[] rules = painfo.nparse_rule.split("--"); + painfo.excludes = new ArrayList<>(Arrays.asList(rules)); + painfo.excludes.remove(0); + painfo.nparse_rule = rules[0]; + } + } + return painfo; + } + + public static boolean isIndex(String str) { + if (StringUtils.isEmpty(str)) { + return false; + } + for (String str2 : new String[]{":eq", ":lt", ":gt", ":first", ":last", "body", "#"}) { + if (str.contains(str2)) { + if (str2.equals("body") || str2.equals("#")) { + return str.startsWith(str2); + } + return true; + } + } + return false; + } + + public static boolean isUrl(String str) { + if (StringUtils.isEmpty(str)) { + return false; + } + for (String str2 : new String[]{"url", "src", "href", "-original", "-play"}) { + if (str.contains(str2)) { + return true; + } + } + return false; + } + + private static String parseHikerToJq(String parse, boolean first) { + /* + 海阔解析表达式转原生表达式,自动补eq,如果传了first就最后一个也取eq(0) + :param parse: + :param first: + :return: + */ + // 不自动加eq下标索引 + if (parse.contains("&&")) { + String[] parses = parse.split("&&"); //带&&的重新拼接 + List < String > new_parses = new ArrayList < > (); //构造新的解析表达式列表 + for (int i = 0; i < parses.length; i++) { + String[] pss = parses[i].split(" "); + String ps = pss[pss.length - 1]; //如果分割&&后带空格就取最后一个元素 + Matcher m = NOADD_INDEX.matcher(ps); + //if (!isIndex(ps)) { + if (!m.find()) { + if (!first && i >= parses.length - 1) { //不传first且遇到最后一个,不用补eq(0) + new_parses.add(parses[i]); + } else { + new_parses.add(parses[i] + ":eq(0)"); + } + } else { + new_parses.add(parses[i]); + } + } + parse = TextUtils.join(" ", new_parses); + } else { + String[] pss = parse.split(" "); + String ps = pss[pss.length - 1]; //如果分割&&后带空格就取最后一个元素 + Matcher m = NOADD_INDEX.matcher(ps); + //if (!isIndex(ps) && first) { + if (!m.find() && first) { + parse = parse + ":eq(0)"; + } + } + return parse; + } + + public static String parseDomForUrl(String html, String rule, String add_url) { + if (!pdfh_html.equals(html)) { + pdfh_html = html; + pdfh_doc = Jsoup.parse(html); + } + Document doc = pdfh_doc; + if (rule.equals("body&&Text") || rule.equals("Text")) { + return doc.text(); + } else if (rule.equals("body&&Html") || rule.equals("Html")) { + return doc.html(); + } + String option = ""; + if (rule.contains("&&")) { + String[] rs = rule.split("&&"); + option = rs[rs.length - 1]; + List excludes = new ArrayList<>(Arrays.asList(rs)); + excludes.remove(rs.length - 1); + rule = TextUtils.join("&&", excludes); + } + rule = parseHikerToJq(rule, true); + String[] parses = rule.split(" "); + Elements ret = new Elements(); + for (String nparse : parses) { + ret = parseOneRule(doc, nparse, ret); + if (ret.isEmpty()) { + return ""; + } + } + String result; + if (StringUtils.isNotEmpty(option)) { + if (option.equals("Text")) { + result = ret.text(); + } else if (option.equals("Html")) { + result = ret.html(); + } else { + result = ret.attr(option); + if (option.toLowerCase() + .contains("style") && result.contains("url(")) { + Matcher m = p.matcher(result); + if (m.find()) { + result = m.group(1); + } + if (StringUtils.isNotEmpty(result)) { + result = result.replaceAll("^['|\"](.*)['|\"]$", "$1"); + } + } + if (StringUtils.isNotEmpty(result) && StringUtils.isNotEmpty(add_url)) { + // 需要自动urljoin的属性 + Matcher m = URLJOIN_ATTR.matcher(option); + Matcher n = SPECIAL_URL.matcher(result); + //if (isUrl(option)) { + if (m.find() && !n.find()) { + if (result.contains("http")) { + result = result.substring(result.indexOf("http")); + } else { + result = joinUrl(add_url, result); + } + } + } + } + } else { + result = ret.outerHtml(); + } + return result; + + } + + public static List < String > parseDomForArray(String html, String rule) { + if (!pdfa_html.equals(html)) { + pdfa_html = html; + pdfa_doc = Jsoup.parse(html); + } + Document doc = pdfa_doc; + rule = parseHikerToJq(rule, false); + String[] parses = rule.split(" "); + Elements ret = new Elements(); + for (String pars: parses) { + ret = parseOneRule(doc, pars, ret); + if (ret.isEmpty()) { + return new ArrayList < > (); + } + } + + List < String > eleHtml = new ArrayList < > (); + for (int i = 0; i < ret.size(); i++) { + Element element1 = ret.get(i); + eleHtml.add(element1.outerHtml()); + } + return eleHtml; + } + + private static Elements parseOneRule(Document doc, String nparse, Elements ret) { + Painfo painfo = getParseInfo(nparse); + if (ret.isEmpty()) { + ret = doc.select(painfo.nparse_rule); + } else { + ret = ret.select(painfo.nparse_rule); + } + + if (nparse.contains(":eq")) { + if (painfo.nparse_index < 0) { + ret = ret.eq(ret.size() + painfo.nparse_index); + } else { + ret = ret.eq(painfo.nparse_index); + } + } + + if (painfo.excludes != null && !ret.isEmpty()) { + ret = ret.clone(); //克隆一个, 免得直接remove会影响doc的缓存 + for (int i = 0; i < painfo.excludes.size(); i++) { + ret.select(painfo.excludes.get(i)) + .remove(); + } + } + return ret; + } + + public static List < String > parseDomForList(String html, String p1, String list_text, String list_url, String add_url) { + if (!pdfa_html.equals(html)) { + pdfa_html = html; + pdfa_doc = Jsoup.parse(html); + } + Document doc = pdfa_doc; + p1 = parseHikerToJq(p1, false); + String[] parses = p1.split(" "); + Elements ret = new Elements(); + for (String pars: parses) { + ret = parseOneRule(doc, pars, ret); + if (ret.isEmpty()) { + return new ArrayList < > (); + } + } + List < String > new_vod_list = new ArrayList < > (); + for (int i = 0; i < ret.size(); i++) { + String it = ret.get(i) + .outerHtml(); + new_vod_list.add(parseDomForUrl(it, list_text, "") + .trim() + '$' + parseDomForUrl(it, list_url, add_url)); + } + return new_vod_list; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/catvod/crawler/js/JsSpider.java b/app/src/main/java/com/github/catvod/crawler/js/JsSpider.java new file mode 100644 index 0000000000..905600d2c2 --- /dev/null +++ b/app/src/main/java/com/github/catvod/crawler/js/JsSpider.java @@ -0,0 +1,619 @@ +package com.github.catvod.crawler.js; + +import android.content.Context; +import android.text.TextUtils; +import android.util.Base64; +import com.github.catvod.crawler.Spider; +import com.github.tvbox.osc.util.FileUtils; +import com.github.tvbox.osc.util.LOG; +import com.github.tvbox.osc.util.MD5; + +import com.whl.quickjs.wrapper.ContextSetter; +import com.whl.quickjs.wrapper.Function; +import com.whl.quickjs.wrapper.JSArray; + +import com.whl.quickjs.wrapper.JSCallFunction; +import com.whl.quickjs.wrapper.JSMethod; +import com.whl.quickjs.wrapper.JSObject; +import com.whl.quickjs.wrapper.JSUtils; +import com.whl.quickjs.wrapper.QuickJSContext; +import com.whl.quickjs.wrapper.UriUtil; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.ByteArrayInputStream; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicBoolean; + + +public class JsSpider extends Spider { + + private static final byte BYTECODE_VERSION = 67; + private static final String EMPTY_MODULE_CODE = + "const empty = null;\n" + + "export default empty;\n" + + "export const JSEncrypt = empty;\n" + + "export const NodeRSA = empty;\n" + + "export const pako = empty;\n" + + "export const JSON5 = empty;\n" + + "export const mb = empty;\n" + + "export const parse = empty;\n" + + "export const stringify = empty;\n" + + "export const inflate = empty;\n" + + "export const deflate = empty;\n" + + "export const gzip = empty;\n" + + "export const ungzip = empty;\n" + + "export const encrypt = empty;\n" + + "export const decrypt = empty;"; + + private final ExecutorService executor; + private final Class dex; + private QuickJSContext ctx; + private JSObject jsObject; + private final String key; + private final String api; + private boolean cat; + private byte[] emptyModuleBytecode; + private final AtomicBoolean destroyed = new AtomicBoolean(false); + + public JsSpider(String key, String api, Class cls) throws Exception { + this.key = "J" + MD5.encode(key); + this.executor = Executors.newSingleThreadExecutor(); + this.api = api; + this.dex = cls; + initializeJS(); + } + public void cancelByTag() { + Connect.cancelByTag("js_okhttp_tag"); + } + + private JSObject createObject() { + return ctx.createNewJSObject(); + } + + private JSArray createArray() { + return ctx.createNewJSArray(); + } + + private void set(JSObject object, String name, Object value) { + ctx.setProperty(object, name, value); + } + + private Object get(JSObject object, String name) { + return ctx.getProperty(object, name); + } + + private JSONArray toJsonArray(JSArray array) { + return JSUtils.toJsonArray(array); + } + + private void bind(JSObject target, Object receiver) { + for (Method method : receiver.getClass().getMethods()) { + if (method.isAnnotationPresent(ContextSetter.class)) { + try { + method.invoke(receiver, ctx); + } catch (Throwable ignored) { + } + } + } + for (Method method : receiver.getClass().getMethods()) { + if (!isQuickJsMethod(method)) continue; + String name = methodName(method); + set(target, name, new JSCallFunction() { + @Override + public Object call(Object... args) { + try { + return method.invoke(receiver, args); + } catch (Throwable ignored) { + return null; + } + } + }); + } + } + + private boolean isQuickJsMethod(Method method) { + return method.isAnnotationPresent(Function.class) || method.isAnnotationPresent(JSMethod.class); + } + + private String methodName(Method method) { + Function function = method.getAnnotation(Function.class); + if (function != null && !TextUtils.isEmpty(function.name())) return function.name(); + return method.getName(); + } + + private void submit(Runnable runnable) { + if (!destroyed.get()) executor.submit(runnable); + } + + private Future submit(Callable callable) { + return executor.submit(callable); + } + + private Object call(String func, Object... args) { +// return executor.submit((FunCall.call(jsObject, func, args))).get(); + if (destroyed.get() || jsObject == null) return null; + try { + return submit(() -> Async.run(jsObject, func, args).get()).get(); // 等待 executor 线程完成 JS 调用 + } catch (InterruptedException | ExecutionException e) { + LOG.i("Executor 提交或等待失败"+ e); + return null; + } + } + + private JSObject cfg(String ext) { + JSObject cfg = createObject(); + set(cfg, "stype", 3); + set(cfg, "skey", TextUtils.isEmpty(siteKey) ? key : siteKey); + if (Json.invalid(ext)) set(cfg, "ext", ext); + else set(cfg, "ext", (JSObject) ctx.parse(ext)); + return cfg; + } + + @Override + public void init(Context context, String extend) { + try { + if (cat) call("init", submit(() -> cfg(extend)).get()); + else call("init", Json.valid(extend) ? ctx.parse(extend) : extend); + }catch (Exception e){ + + } + } + + @Override + public String homeContent(boolean filter) { + try { + return (String) call("home", filter); + }catch (Exception e){ + return null; + } + } + + @Override + public String homeVideoContent() { + try { + return (String) call("homeVod"); + }catch (Exception e){ + return null; + } + } + + @Override + public String categoryContent(String tid, String pg, boolean filter, HashMap extend) { + try { + JSObject obj = submit(() -> new JSUtils().toObj(ctx, extend)).get(); + return (String) call("category", tid, pg, filter, obj); + }catch (Exception e){ + return null; + } + } + + @Override + public String detailContent(List ids) { + try { + return (String) call("detail", ids.get(0)); + }catch (Exception e){ + return null; + } + } + + @Override + public String searchContent(String key, boolean quick) { + try { + return (String) call("search", key, quick); + }catch (Exception e){ + return null; + } + } + + + @Override + public String playerContent(String flag, String id, List vipFlags) { + try { + JSArray array = submit(() -> new JSUtils().toArray(ctx, vipFlags)).get(); + return (String) call("play", flag, id, array); + }catch (Exception e){ + return null; + } + } + + @Override + public String liveContent(String url) { + try { + return (String) call("live", url); + } catch (Exception e) { + return null; + } + } + + @Override + public boolean manualVideoCheck() { + try { + return (Boolean) call("sniffer"); + }catch (Exception e){ + return false; + } + } + + @Override + public boolean isVideoFormat(String url) { + try { + return (Boolean) call("isVideo", url); + }catch (Exception e){ + return false; + } + } + + @Override + public Object[] proxyLocal(Map params) { + try { + if ("catvod".equals(params.get("from"))) return proxy2(params); + else return submit(() -> proxy1(params)).get(); + + }catch (Exception E){ + return new Object[0]; + } + } + + @Override + public String action(String action) { + try { + return (String) call("action", action); + } catch (Exception e) { + return null; + } + } + + @Override + public void destroy() { + if (!destroyed.compareAndSet(false, true)) return; + try { + executor.submit(() -> { + try { + jsObject = null; + if (ctx != null) ctx.destroy(); + } catch (Throwable th) { + LOG.i("echo-js-destroy-error " + th.getMessage()); + } finally { + executor.shutdown(); + } + }); + } catch (Throwable th) { + executor.shutdownNow(); + } + } + + private static final String SPIDER_STRING_CODE = "import * as spider from '%s'\n\n" + + "if (!globalThis.__JS_SPIDER__) {\n" + + " if (spider.__jsEvalReturn) {\n" + + " globalThis.req = http\n" + + " globalThis.__JS_SPIDER__ = spider.__jsEvalReturn()\n" + + " globalThis.__JS_SPIDER__.is_cat = true\n" + + " } else if (spider.default) {\n" + + " globalThis.__JS_SPIDER__ = typeof spider.default === 'function' ? spider.default() : spider.default\n" + + " }\n" + + "}\n"; + private void initializeJS() throws Exception { + submit(() -> { + if (ctx == null) createCtx(); + if (dex != null) createDex(); + + String content = FileUtils.loadModule(api); + if (isInvalidModuleContent(content)) {return null;} + + if(content.startsWith("//bb")){ + cat = true; + byte[] b = Base64.decode(content.replace("//bb",""), 0); + try { + ctx.execute(byteFF(b)); + ctx.evaluateModule(String.format(SPIDER_STRING_CODE, key + ".js") + "globalThis." + key + " = globalThis.__JS_SPIDER__;", "tv_box_root.js"); + } catch (Throwable th) { + LOG.i("echo-bytecode-execute-error " + api + ", msg=" + th.getMessage()); + return null; + } + //ctx.execute(byteFF(b), key + ".js","__jsEvalReturn"); + //ctx.evaluate("globalThis." + key + " = __JS_SPIDER__;"); + } else { + if (content.contains("__JS_SPIDER__")) { + content = content.replaceAll("__JS_SPIDER__\\s*=", "export default "); + } + String moduleExtName = "default"; + if (content.contains("__jsEvalReturn") && !content.contains("export default")) { + moduleExtName = "__jsEvalReturn"; + cat = true; + } + try { + ctx.evaluateModule(content, api); + ctx.evaluateModule(String.format(SPIDER_STRING_CODE, api) + "globalThis." + key + " = globalThis.__JS_SPIDER__;", "tv_box_root.js"); + } catch (Throwable th) { + LOG.i("echo-evaluateModule-error " + api + ", msg=" + th.getMessage()); + return null; + } + //ctx.evaluateModule(content, api, moduleExtName); + //ctx.evaluate("globalThis." + key + " = __JS_SPIDER__;"); + } + jsObject = (JSObject) get(ctx.getGlobalObject(), key); + if (jsObject != null) jsObject.hold(); + return null; + }).get(); + } + + public static byte[] byteFF(byte[] bytes) { + byte[] newBt = new byte[bytes.length - 4]; + newBt[0] = BYTECODE_VERSION; + System.arraycopy(bytes, 5, newBt, 1, bytes.length - 5); + return newBt; + } + + private void createCtx() { + ctx = QuickJSContext.create(); + emptyModuleBytecode = ctx.compileModule(EMPTY_MODULE_CODE, "empty.js"); + ctx.setModuleLoader(new QuickJSContext.BytecodeModuleLoader() { + @Override + public byte[] getModuleBytecode(String moduleName) { + String ss = FileUtils.loadModule(moduleName); + if (isInvalidModuleContent(ss)) { + return compileEmptyModule(moduleName); + } + if(ss.startsWith("//DRPY")){ + try { + byte[] bytes = bytecode(Base64.decode(ss.replace("//DRPY",""), Base64.URL_SAFE)); + return bytes == null ? compileEmptyModule(moduleName) : bytes; + } catch (Throwable th) { + LOG.i("echo-bytecode-module-error " + moduleName + ", msg=" + th.getMessage()); + return compileEmptyModule(moduleName); + } + } else if(ss.startsWith("//bb")){ + try { + byte[] b = Base64.decode(ss.replace("//bb",""), 0); + return byteFF(b); + } catch (Throwable th) { + LOG.i("echo-bytecode-module-error " + moduleName + ", msg=" + th.getMessage()); + return compileEmptyModule(moduleName); + } + } else { + return compileModule(moduleName, ss); + } + } + + @Override + public String moduleNormalizeName(String moduleBaseName, String moduleName) { + return UriUtil.resolve(moduleBaseName, moduleName); + } + }); + ctx.setConsole(new QuickJSContext.Console() { + @Override + public void log(String s) { + LOG.i("echo-QuJs " + s); + } + @Override + public void info(String s) { + LOG.i("echo-QuJs " + s); + } + @Override + public void warn(String s) { + LOG.i("echo-QuJs " + s); + } + @Override + public void error(String s) { + LOG.i("echo-QuJs " + s); + } + }); + + bind(ctx.getGlobalObject(), new Global(executor)); + + JSObject local = createObject(); + set(ctx.getGlobalObject(), "local", local); + bind(local, new local()); + + String net = FileUtils.loadModule("net.js"); + if (!isInvalidModuleContent(net)) ctx.getGlobalObject().getContext().evaluate(net); + preloadTemplate(); + } + + private byte[] compileEmptyModule(String moduleName) { + LOG.i("echo-getModuleBytecode empty :" + moduleName); + return emptyModuleBytecode; + } + + private static byte[] bytecode(byte[] bytes) { + if (bytes == null || bytes.length == 0) return null; + if (bytes[0] == BYTECODE_VERSION) return bytes; + LOG.i("echo-bytecode-version-mismatch actual=" + bytes[0] + ", expected=" + BYTECODE_VERSION); + return null; + } + + private byte[] compileModule(String moduleName, String content) { + try { + if (moduleName != null && moduleName.contains("cheerio.min.js")) { + byte[] bytecode = ctx.compileModule(content, "cheerio.min.js"); + FileUtils.setCacheByte("cheerio.min", bytecode); + return bytecode; + } else if (moduleName != null && moduleName.contains("crypto-js.js")) { + byte[] bytecode = ctx.compileModule(content, "crypto-js.js"); + FileUtils.setCacheByte("crypto-js", bytecode); + return bytecode; + } + return ctx.compileModule(content, moduleName); + } catch (Throwable th) { + LOG.i("echo-compileModule-error " + moduleName + ", msg=" + th.getMessage()); + return compileEmptyModule(moduleName); + } + } + + private boolean isInvalidModuleContent(String content) { + if (TextUtils.isEmpty(content)) return true; + String trim = content.trim(); + if (trim.startsWith("\uFEFF")) trim = trim.substring(1).trim(); + String lower = trim.toLowerCase(); + return lower.startsWith("<") + || lower.startsWith("{\"code\":404") + || lower.startsWith("404") + || lower.startsWith("not found"); + } + + private void preloadTemplate() { + try { + String template = "import tpl from '模板.js';\n" + + "globalThis.muban = tpl.muban;\n" + + "globalThis.getMubans = tpl.getMubans;"; + ctx.evaluateModule(template, "tv_box_template.js"); + } catch (Throwable th) { + LOG.i("echo-preloadTemplate-error " + th.getMessage()); + } + } + + private void createDex() { + try { + JSObject obj = createObject(); + Class clz = dex; + Class[] classes = clz.getDeclaredClasses(); + set(ctx.getGlobalObject(), "jsapi", obj); + if (classes.length == 0) invokeSingle(clz, obj); + if (classes.length >= 1) invokeMultiple(clz, obj); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + private void invokeSingle(Class clz, JSObject jsObj) throws Throwable { + invoke(clz, jsObj, clz.getDeclaredConstructor(QuickJSContext.class).newInstance(ctx)); + } + + private void invokeMultiple(Class clz, JSObject jsObj) throws Throwable { + for (Class subClz : clz.getDeclaredClasses()) { + Object javaObj = subClz.getDeclaredConstructor(clz).newInstance(clz.getDeclaredConstructor(QuickJSContext.class).newInstance(ctx)); + JSObject subObj = createObject(); + invoke(subClz, subObj, javaObj); + set(jsObj, subClz.getSimpleName(), subObj); + } + } + + private void invoke(Class clz, JSObject jsObj, Object javaObj) { + for (Method method : clz.getMethods()) { + if (!isQuickJsMethod(method)) continue; + invoke(jsObj, method, javaObj); + } + } + + private void invoke(JSObject jsObj, Method method, Object javaObj) { + set(jsObj, methodName(method), new JSCallFunction() { + @Override + public Object call(Object... objects) { + try { + return method.invoke(javaObj, objects); + } catch (Throwable e) { + return null; + } + } + }); + } + + private String getContent() { + String global = "globalThis." + key; + String content = FileUtils.loadModule(api); + if (isInvalidModuleContent(content)) {return null;} + if (content.contains("__jsEvalReturn")) { + ctx.evaluate("req = http"); + return content.concat(global).concat(" = __jsEvalReturn()"); + } else if (content.contains("__JS_SPIDER__")) { + return content.replace("__JS_SPIDER__", global); + } else { + return content.replaceAll("export default.*?[{]", global + " = {"); + } + } + + private Object[] proxy1(Map params) { + JSObject object = new JSUtils().toObj(ctx, params); + JSONArray array = toJsonArray((JSArray) jsObject.getJSFunction("proxy").call(object)); + boolean headerAvailable = array.length() > 3 && array.opt(3) != null; + Object[] result = new Object[4]; + result[0] = array.opt(0); + result[1] = array.opt(1); + result[2] = getStream(array.opt(2)); + result[3] = headerAvailable ? getHeader(array.opt(3)) : null; + if (array.length() > 4) { + try { + if ( array.optInt(4) == 1) { + String content = array.optString(2); + if (content.contains("base64,")) content = content.substring(content.indexOf("base64,") + 7); + result[2] = new ByteArrayInputStream(Base64.decode(content, Base64.DEFAULT)); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return result; + } + + private Map getHeader(Object headerRaw) { + Map headers = new HashMap<>(); + if (headerRaw instanceof JSONObject) { + JSONObject json = (JSONObject) headerRaw; + Iterator keys = json.keys(); + while (keys.hasNext()) { + String key = keys.next(); + headers.put(key, json.optString(key)); + } + } else if (headerRaw instanceof String) { + try { + JSONObject json = new JSONObject((String) headerRaw); + Iterator keys = json.keys(); + while (keys.hasNext()) { + String key = keys.next(); + headers.put(key, json.optString(key)); + } + } catch (JSONException e) { + LOG.i("getHeader: 无法解析 String 为 JSON"+ e); + } + } else if (headerRaw instanceof Map) { + //noinspection unchecked + for (Map.Entry entry : ((Map) headerRaw).entrySet()) { + headers.put(String.valueOf(entry.getKey()), String.valueOf(entry.getValue())); + } + } + return headers; + } + + private Object[] proxy2(Map params) throws Exception { + String url = params.get("url"); + String header = params.get("header"); + JSArray array = submit(() -> new JSUtils().toArray(ctx, Arrays.asList(url.split("/")))).get(); + Object object = submit(() -> ctx.parse(header)).get(); + String json = (String) call("proxy", array, object); + Res res = Res.objectFrom(json); + String contentType = res.getContentType(); + if (TextUtils.isEmpty(contentType)) contentType = "application/octet-stream"; + Object[] result = new Object[3]; + result[0] = 200; + result[1] = contentType; + if (res.getBuffer() == 2) { + result[2] = new ByteArrayInputStream(Base64.decode(res.getContent(), Base64.DEFAULT)); + } else { + result[2] = new ByteArrayInputStream(res.getContent().getBytes()); + } + return result; + } + private ByteArrayInputStream getStream(Object o) { + if (o instanceof JSONArray) { + JSONArray a = (JSONArray) o; + byte[] bytes = new byte[a.length()]; + for (int i = 0; i < a.length(); i++) bytes[i] = (byte) a.optInt(i); + return new ByteArrayInputStream(bytes); + } else { + return new ByteArrayInputStream(o.toString().getBytes()); + } + } +} diff --git a/app/src/main/java/com/github/catvod/crawler/js/Json.java b/app/src/main/java/com/github/catvod/crawler/js/Json.java new file mode 100644 index 0000000000..09df169380 --- /dev/null +++ b/app/src/main/java/com/github/catvod/crawler/js/Json.java @@ -0,0 +1,68 @@ +package com.github.catvod.crawler.js; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class Json { + + public static boolean valid(String text) { + try { + new JSONObject(text); + return true; + } catch (Exception e) { + return false; + } + } + + public static boolean invalid(String text) { + return !valid(text); + } + + public static String safeString(JsonObject obj, String key) { + try { + return obj.getAsJsonPrimitive(key).getAsString().trim(); + } catch (Exception e) { + return ""; + } + } + + public static List safeListString(JsonObject obj, String key) { + List result = new ArrayList<>(); + if (!obj.has(key)) return result; + if (obj.get(key).isJsonObject()) result.add(safeString(obj, key)); + else for (JsonElement opt : obj.getAsJsonArray(key)) result.add(opt.getAsString()); + return result; + } + + public static List safeListElement(JsonObject obj, String key) { + List result = new ArrayList<>(); + if (!obj.has(key)) return result; + if (obj.get(key).isJsonObject()) result.add(obj.get(key).getAsJsonObject()); + for (JsonElement opt : obj.getAsJsonArray(key)) result.add(opt.getAsJsonObject()); + return result; + } + + public static JsonObject safeObject(JsonElement element) { + try { + if (element.isJsonPrimitive()) element = JsonParser.parseString(element.getAsJsonPrimitive().getAsString()); + return element.getAsJsonObject(); + } catch (Exception e) { + return new JsonObject(); + } + } + + public static Map toMap(JsonElement element) { + Map map = new HashMap<>(); + JsonObject object = safeObject(element); + for (String key : object.keySet()) map.put(key, safeString(object, key)); + return map; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/catvod/crawler/js/Req.java b/app/src/main/java/com/github/catvod/crawler/js/Req.java new file mode 100644 index 0000000000..674e379165 --- /dev/null +++ b/app/src/main/java/com/github/catvod/crawler/js/Req.java @@ -0,0 +1,88 @@ +package com.github.catvod.crawler.js; + +import android.text.TextUtils; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.annotations.SerializedName; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public class Req { + + @SerializedName("buffer") + private Integer buffer; + @SerializedName("redirect") + private Integer redirect; + @SerializedName("timeout") + private Integer timeout; + @SerializedName("postType") + private String postType; + @SerializedName("method") + private String method; + @SerializedName("body") + private String body; + @SerializedName("data") + private JsonElement data; + @SerializedName("headers") + private JsonElement headers; + + public static Req objectFrom(String json) { + return new Gson().fromJson(json, Req.class); + } + + public int getBuffer() { + return buffer == null ? 0 : buffer; + } + + public Integer getRedirect() { + return redirect == null ? 1 : redirect; + } + + public Integer getTimeout() { + return timeout == null ? 10000 : timeout; + } + + public String getPostType() { + return TextUtils.isEmpty(postType) ? "json" : postType; + } + + public String getMethod() { + return TextUtils.isEmpty(method) ? "get" : method; + } + + public String getBody() { + return body; + } + + public JsonElement getData() { + return data; + } + + private JsonElement getHeaders() { + return headers; + } + + public boolean isRedirect() { + return getRedirect() == 1; + } + + public Map getHeader() { + return Json.toMap(getHeaders()); + } + + public String getCharset() { + Map header = getHeader(); + List keys = Arrays.asList("Content-Type", "content-type"); + for (String key : keys) if (header.containsKey(key)) return getCharset(Objects.requireNonNull(header.get(key))); + return "UTF-8"; + } + + private String getCharset(String value) { + for (String text : value.split(";")) if (text.contains("charset=")) return text.split("=")[1]; + return "UTF-8"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/catvod/crawler/js/Res.java b/app/src/main/java/com/github/catvod/crawler/js/Res.java new file mode 100644 index 0000000000..48510b8439 --- /dev/null +++ b/app/src/main/java/com/github/catvod/crawler/js/Res.java @@ -0,0 +1,62 @@ +package com.github.catvod.crawler.js; + +import android.text.TextUtils; +import android.util.Base64; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.annotations.SerializedName; + +import java.io.ByteArrayInputStream; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public class Res { + + @SerializedName("code") + private Integer code; + @SerializedName("buffer") + private Integer buffer; + @SerializedName("content") + private String content; + @SerializedName("headers") + private JsonElement headers; + + public static Res objectFrom(String json) { + return new Gson().fromJson(json, Res.class); + } + + public int getCode() { + return code == null ? 200 : code; + } + + public int getBuffer() { + return buffer == null ? 0 : buffer; + } + + public String getContent() { + return TextUtils.isEmpty(content) ? "" : content; + } + + private JsonElement getHeaders() { + return headers; + } + + public Map getHeader() { + return Json.toMap(getHeaders()); + } + + public String getContentType() { + Map header = getHeader(); + List keys = Arrays.asList("Content-Type", "content-type"); + for (String key : keys) if (header.containsKey(key)) return Objects.requireNonNull(header.get(key)); + return "application/octet-stream"; + } + + public ByteArrayInputStream getStream() { + if (getBuffer() == 2) return new ByteArrayInputStream(Base64.decode(getContent(), Base64.DEFAULT)); + return new ByteArrayInputStream(getContent().getBytes()); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/catvod/crawler/js/Trans.java b/app/src/main/java/com/github/catvod/crawler/js/Trans.java new file mode 100644 index 0000000000..a7c394e190 --- /dev/null +++ b/app/src/main/java/com/github/catvod/crawler/js/Trans.java @@ -0,0 +1,68 @@ +package com.github.catvod.crawler.js; + +import android.text.TextUtils; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +public class Trans { + + private final Map s2t; + private final Map t2s; + private final boolean trans; + + private static class Loader { + static volatile Trans INSTANCE = new Trans(); + } + + private static Trans get() { + return Loader.INSTANCE; + } + + private Trans() { + s2t = new HashMap<>(); + t2s = new HashMap<>(); + trans = Locale.getDefault().getCountry().equals("TW"); + if (trans) init(); + } + + private void init() { + char[] UTF8T = "萬與醜專業叢東絲丟兩嚴喪個爿豐臨為麗舉麼義烏樂喬習鄉書買亂爭於虧雲亙亞產畝親褻嚲億僅從侖倉儀們價眾優夥會傴傘偉傳傷倀倫傖偽佇體餘傭僉俠侶僥偵側僑儈儕儂俁儔儼倆儷儉債傾傯僂僨償儻儐儲儺兒兌兗黨蘭關興茲養獸囅內岡冊寫軍農塚馮衝決況凍淨淒涼淩減湊凜幾鳳鳧憑凱擊氹鑿芻劃劉則剛創刪別剗剄劊劌剴劑剮劍剝劇勸辦務勱動勵勁勞勢勳猛勩勻匭匱區醫華協單賣盧鹵臥衛卻巹廠廳歷厲壓厭厙廁廂厴廈廚廄廝縣參靉靆雙發變敘疊葉號歎嘰籲後嚇呂嗎唚噸聽啟吳嘸囈嘔嚦唄員咼嗆嗚詠哢嚨嚀噝吒噅鹹呱響啞噠嘵嗶噦嘩噲嚌噥喲嘜嗊嘮啢嗩唕喚呼嘖嗇囀齧囉嘽嘯噴嘍嚳囁嗬噯噓嚶囑嚕劈囂謔團園囪圍圇國圖圓聖壙場阪壞塊堅壇壢壩塢墳墜壟壟壚壘墾坰堊墊埡墶壋塏堖塒塤堝墊垵塹墮壪牆壯聲殼壺壼處備復夠頭誇夾奪奩奐奮獎奧妝婦媽嫵嫗媯姍婁婭嬈嬌孌娛媧嫻嫿嬰嬋嬸媼嬡嬪嬙嬤孫學孿寧寶實寵審憲宮寬賓寢對尋導壽將爾塵堯尷屍盡層屭屜屆屬屢屨嶼歲豈嶇崗峴嶴嵐島嶺崠巋嶨嶧峽嶢嶠崢巒嶗崍嶮嶄嶸嶔崳嶁脊巔鞏巰幣帥師幃帳簾幟帶幀幫幬幘幗冪襆幹並廣莊慶廬廡庫應廟龐廢廎廩開異棄張彌弳彎彈強歸當錄彠彥徹徑徠禦憶懺憂愾懷態慫憮慪悵愴憐總懟懌戀懇惡慟懨愷惻惱惲悅愨懸慳憫驚懼慘懲憊愜慚憚慣湣慍憤憒願懾憖怵懣懶懍戇戔戲戧戰戩戶紮撲扡執擴捫掃揚擾撫拋摶摳掄搶護報擔擬攏揀擁攔擰撥擇掛摯攣掗撾撻挾撓擋撟掙擠揮撏撈損撿換搗據撚擄摑擲撣摻摜摣攬撳攙擱摟攪攜攝攄擺搖擯攤攖撐攆擷擼攛擻攢敵斂數齋斕斗斬斷無舊時曠暘曇晝曨顯晉曬曉曄暈暉暫曖劄術樸機殺雜權條來楊榪傑極構樅樞棗櫪梘棖槍楓梟櫃檸檉梔柵標棧櫛櫳棟櫨櫟欄樹棲樣欒棬椏橈楨檔榿橋樺檜槳樁夢檮棶檢欞槨櫝槧欏橢樓欖櫬櫚櫸檟檻檳櫧橫檣櫻櫫櫥櫓櫞簷檁歡歟歐殲歿殤殘殞殮殫殯毆毀轂畢斃氈毿氌氣氫氬氳彙漢汙湯洶遝溝沒灃漚瀝淪滄渢溈滬濔濘淚澩瀧瀘濼瀉潑澤涇潔灑窪浹淺漿澆湞溮濁測澮濟瀏滻渾滸濃潯濜塗湧濤澇淶漣潿渦溳渙滌潤澗漲澀澱淵淥漬瀆漸澠漁瀋滲溫遊灣濕潰濺漵漊潷滾滯灩灄滿瀅濾濫灤濱灘澦瀠瀟瀲濰潛瀦瀾瀨瀕灝滅燈靈災燦煬爐燉煒熗點煉熾爍爛烴燭煙煩燒燁燴燙燼熱煥燜燾煆糊溜愛爺牘犛牽犧犢強狀獷獁猶狽麅獮獰獨狹獅獪猙獄猻獫獵獼玀豬貓蝟獻獺璣璵瑒瑪瑋環現瑲璽瑉玨琺瓏璫琿璡璉瑣瓊瑤璦璿瓔瓚甕甌電畫暢佘疇癤療瘧癘瘍鬁瘡瘋皰屙癰痙癢瘂癆瘓癇癡癉瘮瘞瘺癟癱癮癭癩癬癲臒皚皺皸盞鹽監蓋盜盤瞘眥矓著睜睞瞼瞞矚矯磯礬礦碭碼磚硨硯碸礪礱礫礎硜矽碩硤磽磑礄確鹼礙磧磣堿镟滾禮禕禰禎禱禍稟祿禪離禿稈種積稱穢穠穭稅穌穩穡窮竊竅窯竄窩窺竇窶豎競篤筍筆筧箋籠籩築篳篩簹箏籌簽簡籙簀篋籜籮簞簫簣簍籃籬籪籟糴類秈糶糲粵糞糧糝餱緊縶糸糾紆紅紂纖紇約級紈纊紀紉緯紜紘純紕紗綱納紝縱綸紛紙紋紡紵紖紐紓線紺絏紱練組紳細織終縐絆紼絀紹繹經紿綁絨結絝繞絰絎繪給絢絳絡絕絞統綆綃絹繡綌綏絛繼綈績緒綾緓續綺緋綽緔緄繩維綿綬繃綢綯綹綣綜綻綰綠綴緇緙緗緘緬纜緹緲緝縕繢緦綞緞緶線緱縋緩締縷編緡緣縉縛縟縝縫縗縞纏縭縊縑繽縹縵縲纓縮繆繅纈繚繕繒韁繾繰繯繳纘罌網羅罰罷羆羈羥羨翹翽翬耮耬聳恥聶聾職聹聯聵聰肅腸膚膁腎腫脹脅膽勝朧腖臚脛膠脈膾髒臍腦膿臠腳脫腡臉臘醃膕齶膩靦膃騰臏臢輿艤艦艙艫艱豔艸藝節羋薌蕪蘆蓯葦藶莧萇蒼苧蘇檾蘋莖蘢蔦塋煢繭荊薦薘莢蕘蓽蕎薈薺蕩榮葷滎犖熒蕁藎蓀蔭蕒葒葤藥蒞蓧萊蓮蒔萵薟獲蕕瑩鶯蓴蘀蘿螢營縈蕭薩蔥蕆蕢蔣蔞藍薊蘺蕷鎣驀薔蘞藺藹蘄蘊藪槁蘚虜慮虛蟲虯蟣雖蝦蠆蝕蟻螞蠶蠔蜆蠱蠣蟶蠻蟄蛺蟯螄蠐蛻蝸蠟蠅蟈蟬蠍螻蠑螿蟎蠨釁銜補襯袞襖嫋褘襪襲襏裝襠褌褳襝褲襇褸襤繈襴見觀覎規覓視覘覽覺覬覡覿覥覦覯覲覷觴觸觶讋譽謄訁計訂訃認譏訐訌討讓訕訖訓議訊記訒講諱謳詎訝訥許訛論訩訟諷設訪訣證詁訶評詛識詗詐訴診詆謅詞詘詔詖譯詒誆誄試詿詩詰詼誠誅詵話誕詬詮詭詢詣諍該詳詫諢詡譸誡誣語誚誤誥誘誨誑說誦誒請諸諏諾讀諑誹課諉諛誰諗調諂諒諄誶談誼謀諶諜謊諫諧謔謁謂諤諭諼讒諮諳諺諦謎諞諝謨讜謖謝謠謗諡謙謐謹謾謫譾謬譚譖譙讕譜譎讞譴譫讖穀豶貝貞負貟貢財責賢敗賬貨質販貪貧貶購貯貫貳賤賁貰貼貴貺貸貿費賀貽賊贄賈賄貲賃賂贓資賅贐賕賑賚賒賦賭齎贖賞賜贔賙賡賠賧賴賵贅賻賺賽賾贗讚贇贈贍贏贛赬趙趕趨趲躉躍蹌蹠躒踐躂蹺蹕躚躋踴躊蹤躓躑躡蹣躕躥躪躦軀車軋軌軒軑軔轉軛輪軟轟軲軻轤軸軹軼軤軫轢軺輕軾載輊轎輈輇輅較輒輔輛輦輩輝輥輞輬輟輜輳輻輯轀輸轡轅轄輾轆轍轔辭辯辮邊遼達遷過邁運還這進遠違連遲邇逕跡適選遜遞邐邏遺遙鄧鄺鄔郵鄒鄴鄰鬱郤郟鄶鄭鄆酈鄖鄲醞醱醬釅釃釀釋里钜鑒鑾鏨釓釔針釘釗釙釕釷釺釧釤鈒釩釣鍆釹鍚釵鈃鈣鈈鈦鈍鈔鍾鈉鋇鋼鈑鈐鑰欽鈞鎢鉤鈧鈁鈥鈄鈕鈀鈺錢鉦鉗鈷缽鈳鉕鈽鈸鉞鑽鉬鉭鉀鈿鈾鐵鉑鈴鑠鉛鉚鈰鉉鉈鉍鈹鐸鉶銬銠鉺銪鋏鋣鐃銍鐺銅鋁銱銦鎧鍘銖銑鋌銩銛鏵銓鉿銚鉻銘錚銫鉸銥鏟銃鐋銨銀銣鑄鐒鋪鋙錸鋱鏈鏗銷鎖鋰鋥鋤鍋鋯鋨鏽銼鋝鋒鋅鋶鐦鐧銳銻鋃鋟鋦錒錆鍺錯錨錡錁錕錩錫錮鑼錘錐錦鍁錈錇錟錠鍵鋸錳錙鍥鍈鍇鏘鍶鍔鍤鍬鍾鍛鎪鍠鍰鎄鍍鎂鏤鎡鏌鎮鎛鎘鑷鐫鎳鎿鎦鎬鎊鎰鎔鏢鏜鏍鏰鏞鏡鏑鏃鏇鏐鐔钁鐐鏷鑥鐓鑭鐠鑹鏹鐙鑊鐳鐶鐲鐮鐿鑔鑣鑞鑲長門閂閃閆閈閉問闖閏闈閑閎間閔閌悶閘鬧閨聞闥閩閭闓閥閣閡閫鬮閱閬闍閾閹閶鬩閿閽閻閼闡闌闃闠闊闋闔闐闒闕闞闤隊陽陰陣階際陸隴陳陘陝隉隕險隨隱隸雋難雛讎靂霧霽黴靄靚靜靨韃鞽韉韝韋韌韍韓韙韞韜韻頁頂頃頇項順須頊頑顧頓頎頒頌頏預顱領頗頸頡頰頲頜潁熲頦頤頻頮頹頷頴穎顆題顒顎顓顏額顳顢顛顙顥纇顫顬顰顴風颺颭颮颯颶颸颼颻飀飄飆飆飛饗饜飣饑飥餳飩餼飪飫飭飯飲餞飾飽飼飿飴餌饒餉餄餎餃餏餅餑餖餓餘餒餕餜餛餡館餷饋餶餿饞饁饃餺餾饈饉饅饊饌饢馬馭馱馴馳驅馹駁驢駔駛駟駙駒騶駐駝駑駕驛駘驍罵駰驕驊駱駭駢驫驪騁驗騂駸駿騏騎騍騅騌驌驂騙騭騤騷騖驁騮騫騸驃騾驄驏驟驥驦驤髏髖髕鬢魘魎魚魛魢魷魨魯魴魺鮁鮃鯰鱸鮋鮓鮒鮊鮑鱟鮍鮐鮭鮚鮳鮪鮞鮦鰂鮜鱠鱭鮫鮮鮺鯗鱘鯁鱺鰱鰹鯉鰣鰷鯀鯊鯇鮶鯽鯒鯖鯪鯕鯫鯡鯤鯧鯝鯢鯰鯛鯨鯵鯴鯔鱝鰈鰏鱨鯷鰮鰃鰓鱷鰍鰒鰉鰁鱂鯿鰠鼇鰭鰨鰥鰩鰟鰜鰳鰾鱈鱉鰻鰵鱅鰼鱖鱔鱗鱒鱯鱤鱧鱣鳥鳩雞鳶鳴鳲鷗鴉鶬鴇鴆鴣鶇鸕鴨鴞鴦鴒鴟鴝鴛鴬鴕鷥鷙鴯鴰鵂鴴鵃鴿鸞鴻鵐鵓鸝鵑鵠鵝鵒鷳鵜鵡鵲鶓鵪鶤鵯鵬鵮鶉鶊鵷鷫鶘鶡鶚鶻鶿鶥鶩鷊鷂鶲鶹鶺鷁鶼鶴鷖鸚鷓鷚鷯鷦鷲鷸鷺鸇鷹鸌鸏鸛鸘鹺麥麩黃黌黶黷黲黽黿鼂鼉鞀鼴齇齊齏齒齔齕齗齟齡齙齠齜齦齬齪齲齷龍龔龕龜誌制谘範鬆冇嚐嘗鬨準鐘彆閒乾儘臟拚作".toCharArray(); + char[] UTF8S = "万与丑专业丛东丝丢两严丧个丬丰临为丽举么义乌乐乔习乡书买乱争于亏云亘亚产亩亲亵亸亿仅从仑仓仪们价众优伙会伛伞伟传伤伥伦伧伪伫体余佣佥侠侣侥侦侧侨侩侪侬俣俦俨俩俪俭债倾偬偻偾偿傥傧储傩儿兑兖党兰关兴兹养兽冁内冈册写军农冢冯冲决况冻净凄凉凌减凑凛几凤凫凭凯击凼凿刍划刘则刚创删别刬刭刽刿剀剂剐剑剥剧劝办务劢动励劲劳势勋勐勚匀匦匮区医华协单卖卢卤卧卫却卺厂厅历厉压厌厍厕厢厣厦厨厩厮县参叆叇双发变叙叠叶号叹叽吁后吓吕吗吣吨听启吴呒呓呕呖呗员呙呛呜咏咔咙咛咝咤咴咸哌响哑哒哓哔哕哗哙哜哝哟唛唝唠唡唢唣唤唿啧啬啭啮啰啴啸喷喽喾嗫呵嗳嘘嘤嘱噜噼嚣嚯团园囱围囵国图圆圣圹场坂坏块坚坛坜坝坞坟坠垄垅垆垒垦垧垩垫垭垯垱垲垴埘埙埚埝埯堑堕塆墙壮声壳壶壸处备复够头夸夹夺奁奂奋奖奥妆妇妈妩妪妫姗娄娅娆娇娈娱娲娴婳婴婵婶媪嫒嫔嫱嬷孙学孪宁宝实宠审宪宫宽宾寝对寻导寿将尔尘尧尴尸尽层屃屉届属屡屦屿岁岂岖岗岘岙岚岛岭岽岿峃峄峡峣峤峥峦崂崃崄崭嵘嵚嵛嵝嵴巅巩巯币帅师帏帐帘帜带帧帮帱帻帼幂幞干并广庄庆庐庑库应庙庞废庼廪开异弃张弥弪弯弹强归当录彟彦彻径徕御忆忏忧忾怀态怂怃怄怅怆怜总怼怿恋恳恶恸恹恺恻恼恽悦悫悬悭悯惊惧惨惩惫惬惭惮惯愍愠愤愦愿慑慭憷懑懒懔戆戋戏戗战戬户扎扑扦执扩扪扫扬扰抚抛抟抠抡抢护报担拟拢拣拥拦拧拨择挂挚挛挜挝挞挟挠挡挢挣挤挥挦捞损捡换捣据捻掳掴掷掸掺掼揸揽揿搀搁搂搅携摄摅摆摇摈摊撄撑撵撷撸撺擞攒敌敛数斋斓斗斩断无旧时旷旸昙昼昽显晋晒晓晔晕晖暂暧札术朴机杀杂权条来杨杩杰极构枞枢枣枥枧枨枪枫枭柜柠柽栀栅标栈栉栊栋栌栎栏树栖样栾桊桠桡桢档桤桥桦桧桨桩梦梼梾检棂椁椟椠椤椭楼榄榇榈榉槚槛槟槠横樯樱橥橱橹橼檐檩欢欤欧歼殁殇残殒殓殚殡殴毁毂毕毙毡毵氇气氢氩氲汇汉污汤汹沓沟没沣沤沥沦沧沨沩沪沵泞泪泶泷泸泺泻泼泽泾洁洒洼浃浅浆浇浈浉浊测浍济浏浐浑浒浓浔浕涂涌涛涝涞涟涠涡涢涣涤润涧涨涩淀渊渌渍渎渐渑渔渖渗温游湾湿溃溅溆溇滗滚滞滟滠满滢滤滥滦滨滩滪潆潇潋潍潜潴澜濑濒灏灭灯灵灾灿炀炉炖炜炝点炼炽烁烂烃烛烟烦烧烨烩烫烬热焕焖焘煅煳熘爱爷牍牦牵牺犊强状犷犸犹狈狍狝狞独狭狮狯狰狱狲猃猎猕猡猪猫猬献獭玑玙玚玛玮环现玱玺珉珏珐珑珰珲琎琏琐琼瑶瑷璇璎瓒瓮瓯电画畅畲畴疖疗疟疠疡疬疮疯疱疴痈痉痒痖痨痪痫痴瘅瘆瘗瘘瘪瘫瘾瘿癞癣癫癯皑皱皲盏盐监盖盗盘眍眦眬着睁睐睑瞒瞩矫矶矾矿砀码砖砗砚砜砺砻砾础硁硅硕硖硗硙硚确硷碍碛碜碱碹磙礼祎祢祯祷祸禀禄禅离秃秆种积称秽秾稆税稣稳穑穷窃窍窑窜窝窥窦窭竖竞笃笋笔笕笺笼笾筑筚筛筜筝筹签简箓箦箧箨箩箪箫篑篓篮篱簖籁籴类籼粜粝粤粪粮糁糇紧絷纟纠纡红纣纤纥约级纨纩纪纫纬纭纮纯纰纱纲纳纴纵纶纷纸纹纺纻纼纽纾线绀绁绂练组绅细织终绉绊绋绌绍绎经绐绑绒结绔绕绖绗绘给绚绛络绝绞统绠绡绢绣绤绥绦继绨绩绪绫绬续绮绯绰绱绲绳维绵绶绷绸绹绺绻综绽绾绿缀缁缂缃缄缅缆缇缈缉缊缋缌缍缎缏缐缑缒缓缔缕编缗缘缙缚缛缜缝缞缟缠缡缢缣缤缥缦缧缨缩缪缫缬缭缮缯缰缱缲缳缴缵罂网罗罚罢罴羁羟羡翘翙翚耢耧耸耻聂聋职聍联聩聪肃肠肤肷肾肿胀胁胆胜胧胨胪胫胶脉脍脏脐脑脓脔脚脱脶脸腊腌腘腭腻腼腽腾膑臜舆舣舰舱舻艰艳艹艺节芈芗芜芦苁苇苈苋苌苍苎苏苘苹茎茏茑茔茕茧荆荐荙荚荛荜荞荟荠荡荣荤荥荦荧荨荩荪荫荬荭荮药莅莜莱莲莳莴莶获莸莹莺莼萚萝萤营萦萧萨葱蒇蒉蒋蒌蓝蓟蓠蓣蓥蓦蔷蔹蔺蔼蕲蕴薮藁藓虏虑虚虫虬虮虽虾虿蚀蚁蚂蚕蚝蚬蛊蛎蛏蛮蛰蛱蛲蛳蛴蜕蜗蜡蝇蝈蝉蝎蝼蝾螀螨蟏衅衔补衬衮袄袅袆袜袭袯装裆裈裢裣裤裥褛褴襁襕见观觃规觅视觇览觉觊觋觌觍觎觏觐觑觞触觯詟誉誊讠计订讣认讥讦讧讨让讪讫训议讯记讱讲讳讴讵讶讷许讹论讻讼讽设访诀证诂诃评诅识诇诈诉诊诋诌词诎诏诐译诒诓诔试诖诗诘诙诚诛诜话诞诟诠诡询诣诤该详诧诨诩诪诫诬语诮误诰诱诲诳说诵诶请诸诹诺读诼诽课诿谀谁谂调谄谅谆谇谈谊谋谌谍谎谏谐谑谒谓谔谕谖谗谘谙谚谛谜谝谞谟谠谡谢谣谤谥谦谧谨谩谪谫谬谭谮谯谰谱谲谳谴谵谶谷豮贝贞负贠贡财责贤败账货质贩贪贫贬购贮贯贰贱贲贳贴贵贶贷贸费贺贻贼贽贾贿赀赁赂赃资赅赆赇赈赉赊赋赌赍赎赏赐赑赒赓赔赕赖赗赘赙赚赛赜赝赞赟赠赡赢赣赪赵赶趋趱趸跃跄跖跞践跶跷跸跹跻踊踌踪踬踯蹑蹒蹰蹿躏躜躯车轧轨轩轪轫转轭轮软轰轱轲轳轴轵轶轷轸轹轺轻轼载轾轿辀辁辂较辄辅辆辇辈辉辊辋辌辍辎辏辐辑辒输辔辕辖辗辘辙辚辞辩辫边辽达迁过迈运还这进远违连迟迩迳迹适选逊递逦逻遗遥邓邝邬邮邹邺邻郁郄郏郐郑郓郦郧郸酝酦酱酽酾酿释里鉅鉴銮錾钆钇针钉钊钋钌钍钎钏钐钑钒钓钔钕钖钗钘钙钚钛钝钞钟钠钡钢钣钤钥钦钧钨钩钪钫钬钭钮钯钰钱钲钳钴钵钶钷钸钹钺钻钼钽钾钿铀铁铂铃铄铅铆铈铉铊铋铍铎铏铐铑铒铕铗铘铙铚铛铜铝铞铟铠铡铢铣铤铥铦铧铨铪铫铬铭铮铯铰铱铲铳铴铵银铷铸铹铺铻铼铽链铿销锁锂锃锄锅锆锇锈锉锊锋锌锍锎锏锐锑锒锓锔锕锖锗错锚锜锞锟锠锡锢锣锤锥锦锨锩锫锬锭键锯锰锱锲锳锴锵锶锷锸锹锺锻锼锽锾锿镀镁镂镃镆镇镈镉镊镌镍镎镏镐镑镒镕镖镗镙镚镛镜镝镞镟镠镡镢镣镤镥镦镧镨镩镪镫镬镭镮镯镰镱镲镳镴镶长门闩闪闫闬闭问闯闰闱闲闳间闵闶闷闸闹闺闻闼闽闾闿阀阁阂阃阄阅阆阇阈阉阊阋阌阍阎阏阐阑阒阓阔阕阖阗阘阙阚阛队阳阴阵阶际陆陇陈陉陕陧陨险随隐隶隽难雏雠雳雾霁霉霭靓静靥鞑鞒鞯鞴韦韧韨韩韪韫韬韵页顶顷顸项顺须顼顽顾顿颀颁颂颃预颅领颇颈颉颊颋颌颍颎颏颐频颒颓颔颕颖颗题颙颚颛颜额颞颟颠颡颢颣颤颥颦颧风飏飐飑飒飓飔飕飖飗飘飙飚飞飨餍饤饥饦饧饨饩饪饫饬饭饮饯饰饱饲饳饴饵饶饷饸饹饺饻饼饽饾饿馀馁馂馃馄馅馆馇馈馉馊馋馌馍馎馏馐馑馒馓馔馕马驭驮驯驰驱驲驳驴驵驶驷驸驹驺驻驼驽驾驿骀骁骂骃骄骅骆骇骈骉骊骋验骍骎骏骐骑骒骓骔骕骖骗骘骙骚骛骜骝骞骟骠骡骢骣骤骥骦骧髅髋髌鬓魇魉鱼鱽鱾鱿鲀鲁鲂鲄鲅鲆鲇鲈鲉鲊鲋鲌鲍鲎鲏鲐鲑鲒鲓鲔鲕鲖鲗鲘鲙鲚鲛鲜鲝鲞鲟鲠鲡鲢鲣鲤鲥鲦鲧鲨鲩鲪鲫鲬鲭鲮鲯鲰鲱鲲鲳鲴鲵鲶鲷鲸鲹鲺鲻鲼鲽鲾鲿鳀鳁鳂鳃鳄鳅鳆鳇鳈鳉鳊鳋鳌鳍鳎鳏鳐鳑鳒鳓鳔鳕鳖鳗鳘鳙鳛鳜鳝鳞鳟鳠鳡鳢鳣鸟鸠鸡鸢鸣鸤鸥鸦鸧鸨鸩鸪鸫鸬鸭鸮鸯鸰鸱鸲鸳鸴鸵鸶鸷鸸鸹鸺鸻鸼鸽鸾鸿鹀鹁鹂鹃鹄鹅鹆鹇鹈鹉鹊鹋鹌鹍鹎鹏鹐鹑鹒鹓鹔鹕鹖鹗鹘鹚鹛鹜鹝鹞鹟鹠鹡鹢鹣鹤鹥鹦鹧鹨鹩鹪鹫鹬鹭鹯鹰鹱鹲鹳鹴鹾麦麸黄黉黡黩黪黾鼋鼌鼍鼗鼹齄齐齑齿龀龁龂龃龄龅龆龇龈龉龊龋龌龙龚龛龟志制咨范松冇尝尝闹准钟彆闲干尽脏拼作".toCharArray(); + for (int i = 0, n = UTF8T.length; i < n; ++i) { + s2t.put(UTF8S[i], UTF8T[i]); + t2s.put(UTF8T[i], UTF8S[i]); + } + } + + private String get(String text, Map map) { + if (TextUtils.isEmpty(text)) return text; + char[] chars = text.toCharArray(); + for (int i = 0; i < chars.length; ++i) { + Character found = map.get(chars[i]); + if (found != null) chars[i] = found; + } + return String.valueOf(chars); + } + + public static boolean pass() { + return !get().trans; + } + + public static String s2t(String text) { + return s2t(pass(), text); + } + + public static String t2s(String text) { + return t2s(pass(), text); + } + + public static String s2t(boolean pass, String text) { + return pass ? text : get().get(text, get().s2t); + } + + public static String t2s(boolean pass, String text) { + return pass ? text : get().get(text, get().t2s); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/catvod/crawler/js/local.java b/app/src/main/java/com/github/catvod/crawler/js/local.java new file mode 100644 index 0000000000..ac6a83ae02 --- /dev/null +++ b/app/src/main/java/com/github/catvod/crawler/js/local.java @@ -0,0 +1,30 @@ +package com.github.catvod.crawler.js; + +import androidx.annotation.Keep; +import com.orhanobut.hawk.Hawk; +import com.whl.quickjs.wrapper.Function; + +public class local {@Keep@Function + public void delete(String str, String str2) { + try { + Hawk.delete("jsRuntime_" + str + "_" + str2); + } catch (Exception e) { + e.printStackTrace(); + } + }@Keep@Function + public String get(String str, String str2) { + try { + return Hawk.get("jsRuntime_" + str + "_" + str2, ""); + } catch (Exception e) { + Hawk.delete(str); + return str2; + } + }@Keep@Function + public void set(String str, String str2, String str3) { + try { + Hawk.put("jsRuntime_" + str + "_" + str2, str3); + } catch (Exception e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/catvod/crawler/js/rsa/DataUtils.java b/app/src/main/java/com/github/catvod/crawler/js/rsa/DataUtils.java new file mode 100644 index 0000000000..73cdd4bdd1 --- /dev/null +++ b/app/src/main/java/com/github/catvod/crawler/js/rsa/DataUtils.java @@ -0,0 +1,43 @@ +package com.github.catvod.crawler.js.rsa; + +import android.util.Base64; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; + +/** + * 数据工具类 + */ +public class DataUtils { + + /** + * 将 Base64 字符串 解码成 字节数组 + */ + public static byte[] base64Decode(String data) { + return Base64.decode(data.getBytes(), Base64.NO_WRAP); + } + + /** + * 将 字节数组 转换成 Base64 编码 + */ + public static String base64Encode(byte[] data) { + return Base64.encodeToString(data, Base64.NO_WRAP); + } + + /** + * 将字节数组转换成 int 类型 + */ + public static int byte2Int(byte[] bytes) { + ByteBuffer buffer = ByteBuffer.wrap(bytes); + return buffer.getInt(); + } + + /** + * 将 int 转换成 byte 数组 + */ + public static byte[] int2byte(int data) { + ByteBuffer buffer = ByteBuffer.allocate(4); + buffer.putInt(data); + return buffer.array(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/catvod/crawler/js/rsa/RSAEncrypt.java b/app/src/main/java/com/github/catvod/crawler/js/rsa/RSAEncrypt.java new file mode 100644 index 0000000000..3df0d82430 --- /dev/null +++ b/app/src/main/java/com/github/catvod/crawler/js/rsa/RSAEncrypt.java @@ -0,0 +1,341 @@ +package com.github.catvod.crawler.js.rsa; + +import android.util.Log; + +import java.io.ByteArrayOutputStream; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; + +import javax.crypto.Cipher; + +/** + * RSA 非对称加密算法,加解密工具类, + * 加密长度 不能超过 128 个字节。 + */ +public class RSAEncrypt { + + public static final String TAG = RSAEncrypt.class.getSimpleName() + " --> "; + + /** + * 标准 jdk 加密填充方式,加解密算法/工作模式/填充方式 + */ + public static final String ECB_PKCS1_PADDING = "RSA/ECB/PKCS1Padding"; + + /** + * RSA 加密算法 + */ + public static final String KEY_ALGORITHM = "RSA"; + + /** + * RSA 最大加密明文大小 + */ + private static final int MAX_ENCRYPT_BLOCK = 117; + /** + * RSA最大解密密文大小 + */ + private static final int MAX_DECRYPT_BLOCK = 128; + + /** + * 随机生成 RSA 密钥对 + * + * @param keyLength 密钥长度,范围:512~2048,一般:1024 + */ + public static KeyPair getKeyPair(int keyLength) { + try { + KeyPairGenerator generator = KeyPairGenerator.getInstance(KEY_ALGORITHM); + generator.initialize(keyLength); + return generator.genKeyPair(); + } catch (Exception e) { + handleException(e); + } + return null; + } + + /** + * 获取公钥 Base64 编码 + * + * @param publicKey 公钥 + */ + public static String getPublicKeyBase64(PublicKey publicKey) { + return DataUtils.base64Encode(publicKey.getEncoded()); + } + + /** + * 获取私钥 Base64 编码 + * + * @param privateKey 公钥 + */ + public static String getPrivateKeyBase64(PrivateKey privateKey) { + return DataUtils.base64Encode(privateKey.getEncoded()); + } + + /** + * 获取 PublicKey 对象 + * + * @param pubKey 公钥,X509 格式de + */ + public static PublicKey getPublicKey(String pubKey) { + try { + // 将公钥进行 Base64 解码 创建 PublicKey 对象并返回 + return KeyFactory.getInstance(KEY_ALGORITHM).generatePublic(new X509EncodedKeySpec(DataUtils.base64Decode(pubKey))); + } catch (NoSuchAlgorithmException unused) { + handleException(new Exception("无此算法")); + } catch (InvalidKeySpecException unused2) { + handleException(new Exception("公钥非法")); + } catch (NullPointerException unused3) { + handleException(new Exception("公钥数据为空")); + } + return null; + } + + /** + * 获取 PrivateKey 对象 + * + * @param prvKey 私钥,PKCS8 格式 + */ + public static PrivateKey getPrivateKey(String prvKey) { + try { + // 将私钥进行 Base64 解码 创建 PrivateKey 对象并返回 + return KeyFactory.getInstance(KEY_ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(DataUtils.base64Decode(prvKey))); + } catch (NoSuchAlgorithmException unused) { + handleException(new Exception("无此算法")); + } catch (InvalidKeySpecException unused2) { + handleException(new Exception("私钥非法")); + } catch (NullPointerException unused3) { + handleException(new Exception("私钥数据为空")); + } + return null; + } + + // --------------------- 1 公钥加密,私钥解密 --------------------- + + /** + * 使用公钥将数据进行分段加密 + * + * @param data 要加密的数据 + * @param pubKey 公钥 Base64 字符串,X509 格式 + * @return 加密后的 Base64 编码数据,加密失败返回 null + */ + public static String encryptByPublicKey(String data, String pubKey, int mlong, boolean block) { + return encryptByPublicKey(data, pubKey, ECB_PKCS1_PADDING, mlong, block); + } + + public static String encryptByPublicKey(String data, String pubKey, String config, int mlong, boolean block) { + try { + byte[] bytes = data.getBytes("UTF-8"); + // 创建 Cipher 对象 + Cipher cipher = Cipher.getInstance(config); + // 初始化 Cipher 对象,加密模式 + RSAPublicKey rSAPublicKey = (RSAPublicKey) getPublicKey(pubKey); + cipher.init(Cipher.ENCRYPT_MODE, rSAPublicKey); + if(mlong == 1){ + return DataUtils.base64Encode(cipher.doFinal(bytes)); + } + int bitLength = MAX_ENCRYPT_BLOCK; + if(block){ + bitLength = rSAPublicKey.getModulus().bitLength() / 8 - 11; + } + int inputLen = bytes.length; + // 保存加密的数据 + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int offSet = 0, i = 0; + byte[] cache; + // 使用 RSA 对数据分段加密 + while (inputLen - offSet > 0) { + if (inputLen - offSet > bitLength) { + cache = cipher.doFinal(bytes, offSet, bitLength); + } else { + cache = cipher.doFinal(bytes, offSet, inputLen - offSet); + } + // 将加密以后的数据保存到内存 + out.write(cache, 0, cache.length); + i++; + offSet = i * bitLength; + } + byte[] encryptedData = out.toByteArray(); + out.close(); + // 将加密后的数据转换成 Base64 字符串 + return DataUtils.base64Encode(encryptedData); + } catch (Exception e) { + handleException(e); + } + return null; + } + + /** + * 使用私钥将加密后的 Base64 字符串进行分段解密 + * + * + * @param encryptBase64Data 加密后的 Base64 字符串 + * @param prvKey 私钥 Base64 字符串,PKCS8 格式 + * @return 解密后的明文,解密失败返回 null + */ + public static String decryptByPrivateKey(String encryptBase64Data, String prvKey, int mlong, boolean block) { + return decryptByPrivateKey(encryptBase64Data, prvKey, ECB_PKCS1_PADDING, mlong, block); + } + + public static String decryptByPrivateKey(String encryptBase64Data, String prvKey, String config, int mlong, boolean block) { + try { + // 将要解密的数据,进行 Base64 解码 + byte[] encryptedData = DataUtils.base64Decode(encryptBase64Data); + // 创建 Cipher 对象,用来解密 + Cipher cipher = Cipher.getInstance(config); + // 初始化 Cipher 对象,解密模式 + RSAPrivateKey rSAPrivateKey = (RSAPrivateKey) getPrivateKey(prvKey); + cipher.init(Cipher.DECRYPT_MODE, rSAPrivateKey); + if(mlong == 1){ + return new String(cipher.doFinal(encryptedData)); + } + int bitLength = MAX_DECRYPT_BLOCK; + if(block){ + bitLength = rSAPrivateKey.getModulus().bitLength() / 8; + } + int inputLen = encryptedData.length; + // 保存解密的数据 + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int offSet = 0, i = 0; + byte[] cache; + // 对数据分段解密 + while (inputLen - offSet > 0) { + if (inputLen - offSet > bitLength) { + cache = cipher.doFinal(encryptedData, offSet, bitLength); + } else { + cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet); + } + // 将解密后的数据保存到内存 + out.write(cache, 0, cache.length); + i++; + offSet = i * bitLength; + } + out.close(); + return out.toString("UTF-8"); + } catch (Exception e) { + handleException(e); + } + return null; + } + + // --------------------- 2 私钥加密,公钥解密 --------------------- + + /** + * 使用 私钥 将数据进行分段加密 + * + * @param data 要加密的数据 + * @param prvKey 私钥 Base64 字符串,PKCS8 格式 + * @return 加密后的 Base64 编码数据,加密失败返回 null + */ + public static String encryptByPrivateKey(String data, String prvKey, int mlong, boolean block) { + return encryptByPrivateKey(data, prvKey, ECB_PKCS1_PADDING, mlong, block); + } + + public static String encryptByPrivateKey(String data, String prvKey, String config, int mlong, boolean block) { + try { + byte[] bytes = data.getBytes("UTF-8"); + // 创建 Cipher 对象 + Cipher cipher = Cipher.getInstance(config); + // 初始化 Cipher 对象,加密模式 + RSAPrivateKey rSAPrivateKey = (RSAPrivateKey) getPrivateKey(prvKey); + cipher.init(Cipher.ENCRYPT_MODE, rSAPrivateKey); + if(mlong == 1){ + return DataUtils.base64Encode(cipher.doFinal(bytes)); + } + int bitLength = MAX_ENCRYPT_BLOCK; + if(block){ + bitLength = rSAPrivateKey.getModulus().bitLength() / 8 - 11; + } + int inputLen = bytes.length; + // 保存加密的数据 + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int offSet = 0, i = 0; + byte[] cache; + // 使用 RSA 对数据分段加密 + while (inputLen - offSet > 0) { + if (inputLen - offSet > bitLength) { + cache = cipher.doFinal(bytes, offSet, bitLength); + } else { + cache = cipher.doFinal(bytes, offSet, inputLen - offSet); + } + // 将加密以后的数据保存到内存 + out.write(cache, 0, cache.length); + i++; + offSet = i * bitLength; + } + byte[] encryptedData = out.toByteArray(); + out.close(); + // 将加密后的数据转换成 Base64 字符串 + return DataUtils.base64Encode(encryptedData); + } catch (Exception e) { + handleException(e); + } + return null; + } + + /** + * 使用 公钥 将加密后的 Base64 字符串进行分段解密 + * + * @param encryptBase64Data 加密后的 Base64 字符串 + * @param pubKey 公钥 Base64 字符串,X509 格式 + * @return 解密后的明文,解密失败返回 null + */ + + public static String decryptByPublicKey(String encryptBase64Data, String pubKey, int mlong, boolean block) { + return decryptByPublicKey(encryptBase64Data, pubKey, ECB_PKCS1_PADDING, mlong, block); + } + public static String decryptByPublicKey(String encryptBase64Data, String pubKey, String config, int mlong, boolean block) { + try { + // 将要解密的数据,进行 Base64 解码 + byte[] encryptedData = DataUtils.base64Decode(encryptBase64Data); + // 创建 Cipher 对象,用来解密 + Cipher cipher = Cipher.getInstance(config); + // 初始化 Cipher 对象,解密模式 + RSAPublicKey rSAPublicKey = (RSAPublicKey) getPublicKey(pubKey); + cipher.init(Cipher.DECRYPT_MODE, rSAPublicKey); + if(mlong == 1){ + return new String(cipher.doFinal(encryptedData)); + } + int bitLength = MAX_DECRYPT_BLOCK; + if(block){ + bitLength = rSAPublicKey.getModulus().bitLength() / 8; + } + int inputLen = encryptedData.length; + // 保存解密的数据 + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int offSet = 0, i = 0; + byte[] cache; + // 对数据分段解密 + while (inputLen - offSet > 0) { + if (inputLen - offSet > bitLength) { + cache = cipher.doFinal(encryptedData, offSet, bitLength); + } else { + cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet); + } + // 将解密后的数据保存到内存 + out.write(cache, 0, cache.length); + i++; + offSet = i * bitLength; + } + out.close(); + return out.toString("UTF-8"); + } catch (Exception e) { + handleException(e); + } + return null; + } + + /** + * 处理异常 + */ + private static void handleException(Exception e) { + e.printStackTrace(); + Log.e(TAG, TAG + e); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/catvod/crawler/python/IPyLoader.java b/app/src/main/java/com/github/catvod/crawler/python/IPyLoader.java new file mode 100644 index 0000000000..9f54b9a497 --- /dev/null +++ b/app/src/main/java/com/github/catvod/crawler/python/IPyLoader.java @@ -0,0 +1,14 @@ +package com.github.catvod.crawler.python; + +import com.github.catvod.crawler.Spider; + +import java.util.Map; + +public interface IPyLoader { + void clear(); + void setConfig(String jsonStr); + void setRecentPyKey(String key); + Spider getSpider(String key, String cls, String ext); + Object[] proxyInvoke(Map params); + Object[] proxyInvoke(Map params, String key); +} diff --git a/app/src/main/java/com/github/catvod/net/OkDns.java b/app/src/main/java/com/github/catvod/net/OkDns.java new file mode 100644 index 0000000000..ff4b05d89b --- /dev/null +++ b/app/src/main/java/com/github/catvod/net/OkDns.java @@ -0,0 +1,47 @@ +package com.github.catvod.net; + +import androidx.annotation.NonNull; + +import com.github.tvbox.osc.util.OkGoHelper; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import okhttp3.Dns; + +public class OkDns implements Dns { + + private final ConcurrentHashMap hosts = new ConcurrentHashMap<>(); + + public void addAll(List hosts) { + if (hosts == null) return; + for (String host : hosts) { + if (host == null) continue; + String[] splits = host.split("=", 2); + if (splits.length == 2) this.hosts.put(splits[0].trim(), splits[1].trim()); + } + } + + public void clear() { + hosts.clear(); + } + + private String get(String hostname) { + String target = hosts.get(hostname); + if (target != null) return target; + for (Map.Entry entry : hosts.entrySet()) { + if (hostname.contains(entry.getKey())) return entry.getValue(); + } + return hostname; + } + + @NonNull + @Override + public List lookup(@NonNull String hostname) throws UnknownHostException { + Dns dns = OkGoHelper.dnsOverHttps != null ? OkGoHelper.dnsOverHttps : Dns.SYSTEM; + return dns.lookup(get(hostname)); + } +} diff --git a/app/src/main/java/com/github/catvod/net/OkHttp.java b/app/src/main/java/com/github/catvod/net/OkHttp.java new file mode 100644 index 0000000000..6ff1010827 --- /dev/null +++ b/app/src/main/java/com/github/catvod/net/OkHttp.java @@ -0,0 +1,197 @@ +package com.github.catvod.net; + +import androidx.collection.ArrayMap; + +import com.github.tvbox.osc.util.SSL.SSLSocketFactoryCompat; +import com.github.tvbox.osc.util.OkGoHelper; + +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.X509TrustManager; + +import okhttp3.Call; +import okhttp3.FormBody; +import okhttp3.Headers; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +public class OkHttp { + + private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(30); + private static OkDns dns; + private static OkHttpClient client; + + public static synchronized OkDns dns() { + if (dns == null) dns = new OkDns(); + return dns; + } + + public static synchronized OkHttpClient client() { + if (client != null) return client; + OkHttpClient base = OkGoHelper.getDefaultClient(); + if (base != null) return client = base.newBuilder().dns(dns()).build(); + OkHttpClient.Builder builder = new OkHttpClient.Builder().dns(dns()).proxySelector(OkGoHelper.proxySelector()).proxyAuthenticator(OkGoHelper.proxyAuthenticator()).connectTimeout(TIMEOUT, TimeUnit.MILLISECONDS).readTimeout(TIMEOUT, TimeUnit.MILLISECONDS).writeTimeout(TIMEOUT, TimeUnit.MILLISECONDS); + setOkHttpSsl(builder); + return client = builder.build(); + } + + public static OkHttpClient player() { + return client(); + } + + public static OkHttpClient client(long timeout) { + return client().newBuilder().connectTimeout(timeout, TimeUnit.MILLISECONDS).readTimeout(timeout, TimeUnit.MILLISECONDS).writeTimeout(timeout, TimeUnit.MILLISECONDS).build(); + } + + public static OkHttpClient noRedirect() { + return noRedirect(TIMEOUT); + } + + public static OkHttpClient noRedirect(long timeout) { + OkHttpClient base = OkGoHelper.getNoRedirectClient(); + if (base == null) base = client(); + return base.newBuilder().dns(dns()).connectTimeout(timeout, TimeUnit.MILLISECONDS).readTimeout(timeout, TimeUnit.MILLISECONDS).writeTimeout(timeout, TimeUnit.MILLISECONDS).followRedirects(false).followSslRedirects(false).build(); + } + + public static synchronized void reset() { + client = null; + dns = null; + } + + public static synchronized void resetClient() { + client = null; + } + + public static OkHttpClient client(boolean redirect, long timeout) { + return redirect ? client(timeout) : noRedirect(timeout); + } + + public static String string(String url) { + if (url == null || !url.startsWith("http")) return ""; + try (Response res = newCall(url).execute()) { + return res.body() != null ? res.body().string() : ""; + } catch (Exception e) { + e.printStackTrace(); + return ""; + } + } + + public static String string(String url, long timeout) { + if (url == null || !url.startsWith("http")) return ""; + try (Response res = newCall(client(timeout), url).execute()) { + return res.body() != null ? res.body().string() : ""; + } catch (Exception e) { + e.printStackTrace(); + return ""; + } + } + + public static String string(String url, Map headers) { + if (url == null || !url.startsWith("http")) return ""; + try (Response res = newCall(url, headers).execute()) { + return res.body() != null ? res.body().string() : ""; + } catch (Exception e) { + e.printStackTrace(); + return ""; + } + } + + public static Call newCall(String url) { + return client().newCall(new Request.Builder().url(url).build()); + } + + public static Call newCall(String url, String tag) { + return client().newCall(new Request.Builder().url(url).tag(tag).build()); + } + + public static Call newCall(OkHttpClient client, String url) { + return client.newCall(new Request.Builder().url(url).build()); + } + + public static Call newCall(OkHttpClient client, String url, String tag) { + return client.newCall(new Request.Builder().url(url).tag(tag).build()); + } + + public static Call newCall(String url, Map headers) { + return client().newCall(new Request.Builder().url(url).headers(headers(headers)).build()); + } + + public static Call newCall(String url, Map headers, ArrayMap params) { + return client().newCall(new Request.Builder().url(buildUrl(url, params)).headers(headers(headers)).build()); + } + + public static Call newCall(String url, Map headers, RequestBody body) { + return client().newCall(new Request.Builder().url(url).headers(headers(headers)).post(body).build()); + } + + public static Call newCall(String url, RequestBody body, String tag) { + return client().newCall(new Request.Builder().url(url).post(body).tag(tag).build()); + } + + public static Call newCall(OkHttpClient client, String url, RequestBody body) { + return client.newCall(new Request.Builder().url(url).post(body).build()); + } + + public static void cancel(String tag) { + cancel(client(), tag); + } + + public static void cancel(OkHttpClient client, String tag) { + if (client == null || tag == null) return; + for (Call call : client.dispatcher().queuedCalls()) if (tag.equals(call.request().tag())) call.cancel(); + for (Call call : client.dispatcher().runningCalls()) if (tag.equals(call.request().tag())) call.cancel(); + } + + public static void cancelAll() { + cancelAll(client()); + } + + public static void cancelAll(OkHttpClient client) { + if (client != null) client.dispatcher().cancelAll(); + } + + public static FormBody toBody(ArrayMap params) { + FormBody.Builder body = new FormBody.Builder(); + if (params != null) for (Map.Entry entry : params.entrySet()) body.add(entry.getKey(), entry.getValue()); + return body.build(); + } + + private static Headers headers(Map headers) { + return headers == null ? new Headers.Builder().build() : Headers.of(headers); + } + + private static void setOkHttpSsl(OkHttpClient.Builder builder) { + try { + SSLSocketFactory sslSocketFactory = new SSLSocketFactoryCompat(TRUST_ALL_CERT); + builder.sslSocketFactory(sslSocketFactory, TRUST_ALL_CERT); + builder.hostnameVerifier((hostname, session) -> true); + } catch (Throwable ignored) { + } + } + + private static final X509TrustManager TRUST_ALL_CERT = new X509TrustManager() { + @Override + public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) { + } + + @Override + public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) { + } + + @Override + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return new java.security.cert.X509Certificate[]{}; + } + }; + + private static HttpUrl buildUrl(String url, ArrayMap params) { + HttpUrl.Builder builder = HttpUrl.parse(url).newBuilder(); + if (params != null) for (Map.Entry entry : params.entrySet()) builder.addQueryParameter(entry.getKey(), entry.getValue()); + return builder.build(); + } +} diff --git a/app/src/main/java/com/github/tvbox/osc/api/ApiConfig.java b/app/src/main/java/com/github/tvbox/osc/api/ApiConfig.java index 8034c7bf19..2bd720e5c2 100644 --- a/app/src/main/java/com/github/tvbox/osc/api/ApiConfig.java +++ b/app/src/main/java/com/github/tvbox/osc/api/ApiConfig.java @@ -1,30 +1,46 @@ package com.github.tvbox.osc.api; +import static com.github.tvbox.osc.util.RegexUtils.getPattern; + import android.app.Activity; import android.net.Uri; +import android.os.Handler; +import android.os.Looper; import android.text.TextUtils; import android.util.Base64; import com.github.catvod.crawler.JarLoader; +import com.github.catvod.crawler.JsLoader; +import com.github.catvod.crawler.pyLoader; import com.github.catvod.crawler.Spider; +import com.github.catvod.crawler.python.IPyLoader; import com.github.tvbox.osc.base.App; import com.github.tvbox.osc.bean.LiveChannelGroup; import com.github.tvbox.osc.bean.IJKCode; import com.github.tvbox.osc.bean.LiveChannelItem; +import com.github.tvbox.osc.bean.LiveSettingGroup; +import com.github.tvbox.osc.bean.LiveSettingItem; import com.github.tvbox.osc.bean.ParseBean; +import com.github.tvbox.osc.bean.ProxyRule; import com.github.tvbox.osc.bean.SourceBean; import com.github.tvbox.osc.server.ControlManager; +import com.github.tvbox.osc.util.AES; import com.github.tvbox.osc.util.AdBlocker; import com.github.tvbox.osc.util.DefaultConfig; +import com.github.tvbox.osc.util.FileUtils; import com.github.tvbox.osc.util.HawkConfig; +import com.github.tvbox.osc.util.HistoryHelper; +import com.github.tvbox.osc.util.LOG; +import com.github.tvbox.osc.util.M3u8; import com.github.tvbox.osc.util.MD5; +import com.github.tvbox.osc.util.OkGoHelper; +import com.github.tvbox.osc.util.Proxy; +import com.github.tvbox.osc.util.VideoParseRuler; +import com.github.tvbox.osc.util.live.TxtSubscribe; import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import com.lzy.okgo.OkGo; -import com.lzy.okgo.callback.AbsCallback; -import com.lzy.okgo.model.Response; import com.orhanobut.hawk.Hawk; import org.json.JSONObject; @@ -33,12 +49,22 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; +import java.net.URLDecoder; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * @author pj567 @@ -47,25 +73,45 @@ */ public class ApiConfig { private static ApiConfig instance; - private LinkedHashMap sourceBeanList; + private final LinkedHashMap sourceBeanList; private SourceBean mHomeSource; private ParseBean mDefaultParse; - private List liveChannelGroupList; - private List parseBeanList; + private final List liveChannelGroupList; + private final List parseBeanList; private List vipParseFlags; + private Map myHosts; private List ijkCodes; private String spider = null; + private String currentPyKey = ""; + private String currentLivePyKey = ""; + private String currentPlaySourceKey = ""; + private String loadedLiveConfigUrl = ""; public String wallpaper = ""; + private String danmaku = ""; - private SourceBean emptyHome = new SourceBean(); + private final SourceBean emptyHome = new SourceBean(); - private JarLoader jarLoader = new JarLoader(); + private final JarLoader jarLoader = new JarLoader(); + private final JsLoader jsLoader = new JsLoader(); + private final IPyLoader pyLoader = new pyLoader(); + private final Gson gson; + private final Handler mainHandler = new Handler(Looper.getMainLooper()); + private final ExecutorService configLoadExecutor = Executors.newSingleThreadExecutor(); + private final ExecutorService jarLoadExecutor = Executors.newSingleThreadExecutor(); + private final ExecutorService danmuSearchExecutor = Executors.newSingleThreadExecutor(); + private final Set warmedSearchSpiderKeys = new HashSet<>(); + private final String userAgent = "okhttp/3.15"; private ApiConfig() { + clearLoader(); sourceBeanList = new LinkedHashMap<>(); liveChannelGroupList = new ArrayList<>(); parseBeanList = new ArrayList<>(); + searchSourceBeanList = new ArrayList<>(); + gson = new Gson(); + Hawk.put(HawkConfig.LIVE_GROUP_LIST,new JsonArray()); + loadDefaultConfig(); } public static ApiConfig get() { @@ -79,6 +125,70 @@ public static ApiConfig get() { return instance; } + public static String FindResult(String json, String configKey) { + String content = json; + try { + if (AES.isJson(content)) return content; + Pattern pattern = getPattern("[A-Za-z0-9]{8}\\*\\*"); + Matcher matcher = pattern.matcher(content); + if(matcher.find()){ + content=content.substring(content.indexOf(matcher.group()) + 10); + content = new String(Base64.decode(content, Base64.DEFAULT)); + } + content = content.trim(); + if (content.startsWith("2423")) { + content = content.replaceAll("\\s+", ""); + String data = content.substring(content.indexOf("2324") + 4, content.length() - 26); + content = new String(AES.toBytes(content)).toLowerCase(); + String key = AES.rightPadding(content.substring(content.indexOf("$#") + 2, content.indexOf("#$")), "0", 16); + String iv = AES.rightPadding(content.substring(content.length() - 13), "0", 16); + json = AES.CBC(data, key, iv); + }else if (configKey !=null && !AES.isJson(content)) { + json = AES.ECB(content, configKey); + } + else{ + json = content; + } + } catch (Exception e) { + e.printStackTrace(); + } + return json; + } + + private static byte[] getImgJar(String body){ + Pattern pattern = getPattern("[A-Za-z0-9]{8}\\*\\*"); + Matcher matcher = pattern.matcher(body); + if(matcher.find()){ + body = body.substring(body.indexOf(matcher.group()) + 10); + return Base64.decode(body, Base64.DEFAULT); + } + return "".getBytes(); + } + + private String TempKey = null; + private String configUrl(String apiUrl){ + TempKey = null; + String configUrl = "", pk = ";pk;"; + apiUrl=apiUrl.replace("file://", "clan://localhost/"); + if (apiUrl.contains(pk)) { + String[] a = apiUrl.split(pk); + TempKey = a[1]; + if (apiUrl.startsWith("clan")){ + configUrl = clanToAddress(a[0]); + }else if (apiUrl.startsWith("http")){ + configUrl = a[0]; + }else { + configUrl = "http://" + a[0]; + } + } else if (apiUrl.startsWith("clan")) { + configUrl = clanToAddress(apiUrl); + } else if (!apiUrl.startsWith("http")) { + configUrl = "http://" + apiUrl; + } else { + configUrl = apiUrl; + } + return configUrl; + } public void loadConfig(boolean useCache, LoadConfigCallback callback, Activity activity) { String apiUrl = Hawk.get(HawkConfig.API_URL, ""); if (apiUrl.isEmpty()) { @@ -88,131 +198,444 @@ public void loadConfig(boolean useCache, LoadConfigCallback callback, Activity a File cache = new File(App.getInstance().getFilesDir().getAbsolutePath() + "/" + MD5.encode(apiUrl)); if (useCache && cache.exists()) { try { - parseJson(apiUrl, cache); + String json = readConfigFile(cache); + if (switchApiCollectionIfNeeded(apiUrl, json)) { + loadConfig(false, callback, activity); + return; + } + clearApiLinesIfUnmatched(apiUrl); + parseJson(apiUrl, json); callback.success(); return; } catch (Throwable th) { th.printStackTrace(); } } - String apiFix = apiUrl; - if (apiUrl.startsWith("clan://")) { - apiFix = clanToAddress(apiUrl); + String configUrl=configUrl(apiUrl); + + final String configKey = TempKey; + + fetchConfigAsync(apiUrl, configUrl, configKey, new ConfigFetchCallback() { + @Override + public void success(String json) { + try { +// LOG.longI("echo-ConfigJson", json); + if (switchApiCollectionIfNeeded(apiUrl, json)) { + FileUtils.saveCache(cache,json); + loadConfig(false, callback, activity); + return; + } + clearApiLinesIfUnmatched(apiUrl); + parseJson(apiUrl, json); + FileUtils.saveCache(cache,json); + callback.success(); + } catch (Throwable th) { + th.printStackTrace(); + callback.error("配置解析失败"); + } + } + + @Override + public void error(String error) { + if (cache.exists()) { + try { + String json = readConfigFile(cache); + if (switchApiCollectionIfNeeded(apiUrl, json)) { + loadConfig(false, callback, activity); + return; + } + clearApiLinesIfUnmatched(apiUrl); + parseJson(apiUrl, json); + callback.success(); + return; + } catch (Throwable th) { + th.printStackTrace(); + } + } + callback.error("拉取配置失败\n" + error); + } + }); + } + + public void loadLiveConfig(boolean useCache, LoadConfigCallback callback) { + String apiUrl = Hawk.get(HawkConfig.LIVE_API_URL, ""); + if (apiUrl.isEmpty()) { + apiUrl = Hawk.get(HawkConfig.API_URL, ""); } - OkGo.get(apiFix) - .execute(new AbsCallback() { - @Override - public void onSuccess(Response response) { - try { - String json = response.body(); - parseJson(apiUrl, response.body()); - try { - File cacheDir = cache.getParentFile(); - if (!cacheDir.exists()) - cacheDir.mkdirs(); - if (cache.exists()) - cache.delete(); - FileOutputStream fos = new FileOutputStream(cache); - fos.write(json.getBytes("UTF-8")); - fos.flush(); - fos.close(); - } catch (Throwable th) { - th.printStackTrace(); - } + if (apiUrl.isEmpty()) { + callback.error("-1"); + return; + } + final String liveApiUrl = apiUrl; + String liveApiConfigUrl = configUrl(liveApiUrl); + final String liveConfigKey = TempKey; + File live_cache = new File(App.getInstance().getFilesDir().getAbsolutePath() + "/" + MD5.encode(liveApiUrl)); + LOG.i("echo-load live config "+liveApiUrl); + if (useCache && live_cache.exists()) { + try { + parseLiveConfigContent(liveApiUrl, live_cache); + if (hasLiveConfigResult()) { + loadedLiveConfigUrl = liveApiUrl; + callback.success(); + return; + } + } catch (Throwable th) { + th.printStackTrace(); + } + } + fetchConfigAsync(liveApiUrl, liveApiConfigUrl, liveConfigKey, new ConfigFetchCallback() { + @Override + public void success(String json) { + try { + parseLiveConfigContent(liveApiUrl, json); + if (!hasLiveConfigResult()) { + callback.error("直播配置解析失败"); + return; + } + loadedLiveConfigUrl = liveApiUrl; + FileUtils.saveCache(live_cache, json); + callback.success(); + } catch (Throwable th) { + th.printStackTrace(); + callback.error("直播配置解析失败"); + } + } + + @Override + public void error(String error) { + if (live_cache.exists()) { + try { + parseLiveConfigContent(liveApiUrl, live_cache); + if (hasLiveConfigResult()) { + loadedLiveConfigUrl = liveApiUrl; callback.success(); - } catch (Throwable th) { - th.printStackTrace(); - callback.error("解析配置失败"); + return; } + } catch (Throwable th) { + th.printStackTrace(); } + } + callback.error("直播配置拉取失败"); + } + }); + } + + private boolean hasLiveConfigResult() { + return liveChannelGroupList != null && !liveChannelGroupList.isEmpty(); + } + + public boolean shouldReloadLiveConfig() { + String apiUrl = Hawk.get(HawkConfig.LIVE_API_URL, ""); + if (apiUrl.isEmpty()) apiUrl = Hawk.get(HawkConfig.API_URL, ""); + return liveChannelGroupList == null || liveChannelGroupList.isEmpty() || !apiUrl.equals(loadedLiveConfigUrl); + } + + public static String getLiveGroupIndexKey() { + String liveApiUrl = Hawk.get(HawkConfig.LIVE_API_URL, ""); + if (liveApiUrl == null || liveApiUrl.length() == 0) { + return HawkConfig.LIVE_GROUP_INDEX; + } + return HawkConfig.LIVE_GROUP_INDEX + "_" + liveApiUrl; + } + + public static int getLiveGroupIndex() { + return Hawk.get(getLiveGroupIndexKey(), 0); + } + + public static void setLiveGroupIndex(int index) { + Hawk.put(getLiveGroupIndexKey(), index); + } + + private static final int LOAD_JAR_MAX_RETRY = 1; + + public void loadJar(boolean useCache, String spider, LoadConfigCallback callback) { + loadJar(useCache, spider, callback, 0); + } + + private interface JarLoadCallback { + void complete(boolean success); + } + + private interface JarDownloadCallback { + void complete(File file, String error); + } + + private interface ConfigFetchCallback { + void success(String body); + void error(String error); + } + + private void fetchConfigAsync(final String apiUrl, final String requestUrl, final String configKey, final ConfigFetchCallback callback) { + configLoadExecutor.execute(new Runnable() { + @Override + public void run() { + String result = ""; + String error = ""; + okhttp3.Response response = null; + try { + okhttp3.Request request = new okhttp3.Request.Builder() + .url(requestUrl) + .build(); + okhttp3.OkHttpClient client = OkGoHelper.getDefaultClient(); + if (client == null) client = com.github.catvod.net.OkHttp.client(); + response = client.newCall(request).execute(); + if (!response.isSuccessful()) { + error = "HTTP " + response.code(); + } else if (response.body() == null) { + error = "empty body"; + } else { + result = FindResult(response.body().string(), configKey); + if (apiUrl.startsWith("clan")) { + result = clanContentFix(clanToAddress(apiUrl), result); + } + result = fixContentPath(apiUrl, result); + } + } catch (Throwable th) { + error = th.getMessage(); + if (TextUtils.isEmpty(error)) error = th.toString(); + } finally { + if (response != null) closeQuietly(response.body()); + } + final String finalResult = result; + final String finalError = error; + mainHandler.post(new Runnable() { @Override - public void onError(Response response) { - super.onError(response); - if (cache.exists()) { - try { - parseJson(apiUrl, cache); - callback.success(); - return; - } catch (Throwable th) { - th.printStackTrace(); - } + public void run() { + if (TextUtils.isEmpty(finalError)) { + callback.success(finalResult); + } else { + callback.error(finalError); } - callback.error("拉取配置失败\n" + (response.getException() != null ? response.getException().getMessage() : "")); } + }); + } + }); + } + + private void loadJarAsync(File file, JarLoadCallback callback) { + jarLoadExecutor.execute(new Runnable() { + @Override + public void run() { + boolean success = false; + try { + success = file != null && file.exists() && jarLoader.load(file.getAbsolutePath()); + } catch (Throwable th) { + LOG.e("echo---jar Loader threw exception: " + th.getMessage()); + } + final boolean result = success; + mainHandler.post(new Runnable() { + @Override + public void run() { + callback.complete(result); + } + }); + } + }); + } - public String convertResponse(okhttp3.Response response) throws Throwable { - String result = ""; - if (response.body() == null) { - result = ""; + private void downloadJarAsync(String url, boolean isJarInImg, File cache, JarDownloadCallback callback) { + jarLoadExecutor.execute(new Runnable() { + @Override + public void run() { + File result = null; + String error = ""; + okhttp3.Response response = null; + InputStream inputStream = null; + FileOutputStream outputStream = null; + File temp = new File(cache.getAbsolutePath() + ".tmp"); + try { + File cacheDir = cache.getParentFile(); + if (cacheDir != null && !cacheDir.exists()) cacheDir.mkdirs(); + if (temp.exists()) temp.delete(); + okhttp3.Request request = new okhttp3.Request.Builder() + .url(url) + .header("User-Agent", userAgent) + .build(); + okhttp3.OkHttpClient client = OkGoHelper.getDefaultClient(); + if (client == null) client = com.github.catvod.net.OkHttp.client(); + response = client.newCall(request).execute(); + if (!response.isSuccessful()) { + error = "HTTP " + response.code(); + } else if (response.body() == null) { + error = "empty body"; + } else if (isJarInImg) { + String respData = response.body().string(); + LOG.i("echo---jar Response: " + respData); + byte[] imgJar = getImgJar(respData); + if (imgJar == null || imgJar.length == 0) { + error = "empty img jar"; } else { - result = response.body().string(); + outputStream = new FileOutputStream(temp); + outputStream.write(imgJar); + outputStream.flush(); + closeQuietly(outputStream); + outputStream = null; + result = replaceCache(temp, cache); } - if (apiUrl.startsWith("clan")) { - result = clanContentFix(clanToAddress(apiUrl), result); + } else { + inputStream = response.body().byteStream(); + outputStream = new FileOutputStream(temp); + byte[] buffer = new byte[16384]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); } - return result; + outputStream.flush(); + closeQuietly(outputStream); + outputStream = null; + result = replaceCache(temp, cache); + } + } catch (Throwable th) { + error = th.getMessage(); + } finally { + closeQuietly(inputStream); + closeQuietly(outputStream); + if (response != null) closeQuietly(response.body()); + if (result == null && temp.exists()) temp.delete(); + } + final File finalResult = result; + final String finalError = error; + mainHandler.post(new Runnable() { + @Override + public void run() { + callback.complete(finalResult, finalError); } }); + } + }); } + private File replaceCache(File temp, File cache) throws IOException { + if (cache.exists() && !cache.delete()) { + LOG.i("echo---delete old jar cache failed:" + cache.getAbsolutePath()); + } + if (!temp.renameTo(cache)) { + FileUtils.copyFile(temp, cache); + temp.delete(); + } + return cache; + } - public void loadJar(boolean useCache, String spider, LoadConfigCallback callback) { + private void closeQuietly(java.io.Closeable closeable) { + try { + if (closeable != null) closeable.close(); + } catch (Throwable ignored) { + } + } + + private void loadJar(boolean useCache, String spider, LoadConfigCallback callback, int retryCount) { String[] urls = spider.split(";md5;"); String jarUrl = urls[0]; String md5 = urls.length > 1 ? urls[1].trim() : ""; - File cache = new File(App.getInstance().getFilesDir().getAbsolutePath() + "/csp.jar"); + File cache = new File(App.getInstance().getFilesDir().getAbsolutePath() + "/csp/"+MD5.string2MD5(jarUrl)+".jar"); if (!md5.isEmpty() || useCache) { if (cache.exists() && (useCache || MD5.getFileMd5(cache).equalsIgnoreCase(md5))) { + if (cache.exists()) { + loadJarAsync(cache, new JarLoadCallback() { + @Override + public void complete(boolean success) { + if (success) { + callback.success(); + } else { + callback.error("md5缓存失效"); + } + } + }); + return; + } if (jarLoader.load(cache.getAbsolutePath())) { callback.success(); } else { - callback.error(""); + callback.error("md5缓存失效"); } return; } - } - - OkGo.get(jarUrl).execute(new AbsCallback() { - - @Override - public File convertResponse(okhttp3.Response response) throws Throwable { - File cacheDir = cache.getParentFile(); - if (!cacheDir.exists()) - cacheDir.mkdirs(); - if (cache.exists()) - cache.delete(); - FileOutputStream fos = new FileOutputStream(cache); - fos.write(response.body().bytes()); - fos.flush(); - fos.close(); - return cache; + }else { + if (Boolean.parseBoolean(jarCache) && cache.exists() && !FileUtils.isWeekAgo(cache)) { + LOG.i("echo-load jar jarCache:"+jarUrl); + if (cache.exists()) { + loadJarAsync(cache, new JarLoadCallback() { + @Override + public void complete(boolean success) { + if (success) { + callback.success(); + } else { + loadJar(false, spider, callback, retryCount); + } + } + }); + return; + } + if (jarLoader.load(cache.getAbsolutePath())) { + callback.success(); + return; + } } + } - @Override - public void onSuccess(Response response) { - if (response.body().exists()) { - if (jarLoader.load(response.body().getAbsolutePath())) { - callback.success(); - } else { - callback.error(""); - } - } else { - callback.error(""); + boolean isJarInImg = jarUrl.startsWith("img+"); + jarUrl = jarUrl.replace("img+", ""); + LOG.i("echo-load jar start:"+jarUrl); + final String requestUrl = jarUrl; + downloadJarAsync(requestUrl, isJarInImg, cache, new JarDownloadCallback() { + private boolean retryLoad(String reason) { + if (retryCount >= LOAD_JAR_MAX_RETRY) return false; + if (cache.exists() && !cache.delete()) { + LOG.i("echo---delete bad jar cache failed:" + cache.getAbsolutePath()); } + LOG.i("echo---retry load jar reason:" + reason + " url:" + requestUrl + " retry:" + (retryCount + 1)); + loadJar(false, spider, callback, retryCount+1); + return true; } @Override - public void onError(Response response) { - super.onError(response); - callback.error(""); + public void complete(File file, String error) { + if (file != null && file.exists()) { + loadJarAsync(file, new JarLoadCallback() { + @Override + public void complete(boolean success) { + if (success) { + LOG.i("echo---load-jar-success"); + callback.success(); + } else { + LOG.e("echo---jar Loader returned false"); + if (retryLoad("loader_false")) return; + callback.error("JAR加载失败"); + } + } + }); + return; + } + if (!TextUtils.isEmpty(error)) { + LOG.i("echo---jar Request failed: " + error); + } + if (cache.exists()) { + loadJarAsync(cache, new JarLoadCallback() { + @Override + public void complete(boolean success) { + if (success) { + callback.success(); + } else { + if (retryLoad("request_error")) return; + callback.error("网络错误"); + } + } + }); + return; + } + if (retryLoad("request_error")) return; + callback.error("网络错误"); } }); } private void parseJson(String apiUrl, File f) throws Throwable { - System.out.println("从本地缓存加载" + f.getAbsolutePath()); + parseJson(apiUrl, readConfigFile(f)); + } + + private String readConfigFile(File f) throws Throwable { BufferedReader bReader = new BufferedReader(new InputStreamReader(new FileInputStream(f), "UTF-8")); StringBuilder sb = new StringBuilder(); String s = ""; @@ -220,13 +643,108 @@ private void parseJson(String apiUrl, File f) throws Throwable { sb.append(s + "\n"); } bReader.close(); - parseJson(apiUrl, sb.toString()); + return sb.toString(); + } + + private boolean switchApiCollectionIfNeeded(String apiUrl, String jsonStr) { + ArrayList apiLines = parseApiCollection(jsonStr); + if (apiLines.isEmpty()) { + return false; + } + String firstApi = HistoryHelper.getApiLineUrl(apiLines.get(0)); + if (TextUtils.isEmpty(firstApi) || firstApi.equals(apiUrl)) { + return false; + } + Hawk.put(HawkConfig.API_LINE_LIST, apiLines); + Hawk.put(HawkConfig.API_LINE_SOURCE, apiUrl); + Hawk.put(HawkConfig.API_URL, firstApi); + HistoryHelper.setApiHistory(apiUrl); + String liveApiUrl = Hawk.get(HawkConfig.LIVE_API_URL, ""); + if (TextUtils.isEmpty(liveApiUrl) || liveApiUrl.equals(apiUrl)) { + Hawk.put(HawkConfig.LIVE_API_URL, firstApi); + HistoryHelper.setLiveApiHistory(firstApi); + } + return true; + } + + private ArrayList parseApiCollection(String jsonStr) { + ArrayList apiLines = new ArrayList<>(); + try { + String json = trimJsonObject(jsonStr); + if (TextUtils.isEmpty(json)) { + return apiLines; + } + JsonObject infoJson = gson.fromJson(json, JsonObject.class); + if (infoJson == null || infoJson.has("sites") || !infoJson.has("urls") || !infoJson.get("urls").isJsonArray()) { + return apiLines; + } + JsonArray urls = infoJson.get("urls").getAsJsonArray(); + for (JsonElement element : urls) { + String name = ""; + String url = ""; + if (element.isJsonObject()) { + JsonObject item = element.getAsJsonObject(); + name = DefaultConfig.safeJsonString(item, "name", ""); + url = DefaultConfig.safeJsonString(item, "url", ""); + if (TextUtils.isEmpty(url)) { + url = DefaultConfig.safeJsonString(item, "api", ""); + } + } else if (element.isJsonPrimitive()) { + url = element.getAsString(); + } + if (!TextUtils.isEmpty(url)) { + apiLines.add(HistoryHelper.buildApiLine(name, url)); + } + } + } catch (Throwable ignored) { + } + return apiLines; + } + + private String trimJsonObject(String content) { + if (content == null) { + return ""; + } + String trimContent = content.trim(); + int start = trimContent.indexOf("{"); + int end = trimContent.lastIndexOf("}"); + if (start >= 0 && end > start) { + return trimContent.substring(start, end + 1); + } + return trimContent; + } + + private void resetConfigData() { + clearSpiderCache(); + currentPlaySourceKey = ""; + sourceBeanList.clear(); + liveChannelGroupList.clear(); + parseBeanList.clear(); + searchSourceBeanList = new ArrayList<>(); + Hawk.put(HawkConfig.LIVE_GROUP_LIST,new JsonArray()); + } + + private void clearApiLinesIfUnmatched(String apiUrl) { + ArrayList apiLines = Hawk.get(HawkConfig.API_LINE_LIST, new ArrayList()); + if (apiLines.isEmpty()) { + return; + } + for (String apiLine : apiLines) { + if (apiUrl.equals(HistoryHelper.getApiLineUrl(apiLine))) { + return; + } + } + HistoryHelper.clearApiLineList(); } + private static String jarCache ="true"; private void parseJson(String apiUrl, String jsonStr) { - JsonObject infoJson = new Gson().fromJson(jsonStr, JsonObject.class); + resetConfigData(); + JsonObject infoJson = gson.fromJson(jsonStr, JsonObject.class); // spider spider = DefaultConfig.safeJsonString(infoJson, "spider", ""); + jarCache = DefaultConfig.safeJsonString(infoJson, "jarCache", "true"); + danmaku = DefaultConfig.safeJsonString(infoJson, "danmaku", ""); // wallpaper wallpaper = DefaultConfig.safeJsonString(infoJson, "wallpaper", ""); // 远端站点源 @@ -236,39 +754,54 @@ private void parseJson(String apiUrl, String jsonStr) { SourceBean sb = new SourceBean(); String siteKey = obj.get("key").getAsString().trim(); sb.setKey(siteKey); - sb.setName(obj.get("name").getAsString().trim()); + sb.setName(obj.has("name")?obj.get("name").getAsString().trim():siteKey); sb.setType(obj.get("type").getAsInt()); sb.setApi(obj.get("api").getAsString().trim()); sb.setSearchable(DefaultConfig.safeJsonInt(obj, "searchable", 1)); sb.setQuickSearch(DefaultConfig.safeJsonInt(obj, "quickSearch", 1)); - sb.setFilterable(DefaultConfig.safeJsonInt(obj, "filterable", 1)); + if(siteKey.startsWith("py_")){ + sb.setFilterable(1); + }else { + sb.setFilterable(DefaultConfig.safeJsonInt(obj, "filterable", 1)); + } sb.setPlayerUrl(DefaultConfig.safeJsonString(obj, "playUrl", "")); sb.setExt(DefaultConfig.safeJsonString(obj, "ext", "")); + sb.setJar(DefaultConfig.safeJsonString(obj, "jar", "")); + sb.setPlayerType(DefaultConfig.safeJsonInt(obj, "playerType", -1)); sb.setCategories(DefaultConfig.safeJsonStringList(obj, "categories")); - if (firstSite == null) - firstSite = sb; + sb.setTimeout(DefaultConfig.safeJsonInt(obj, "timeout", 0)); + sb.setClickSelector(DefaultConfig.safeJsonString(obj, "click", "")); + sb.setStyle(DefaultConfig.safeJsonString(obj, "style", "")); + if (firstSite == null) firstSite = sb; sourceBeanList.put(siteKey, sb); } if (sourceBeanList != null && sourceBeanList.size() > 0) { String home = Hawk.get(HawkConfig.HOME_API, ""); SourceBean sh = getSource(home); - if (sh == null) + if (sh == null) { + assert firstSite != null; setSourceBean(firstSite); + } else setSourceBean(sh); } // 需要使用vip解析的flag vipParseFlags = DefaultConfig.safeJsonStringList(infoJson, "flags"); // 解析地址 - for (JsonElement opt : infoJson.get("parses").getAsJsonArray()) { - JsonObject obj = (JsonObject) opt; - ParseBean pb = new ParseBean(); - pb.setName(obj.get("name").getAsString().trim()); - pb.setUrl(obj.get("url").getAsString().trim()); - String ext = obj.has("ext") ? obj.get("ext").getAsJsonObject().toString() : ""; - pb.setExt(ext); - pb.setType(DefaultConfig.safeJsonInt(obj, "type", 0)); - parseBeanList.add(pb); + parseBeanList.clear(); + if(infoJson.has("parses")){ + JsonArray parses = infoJson.get("parses").getAsJsonArray(); + for (JsonElement opt : parses) { + JsonObject obj = (JsonObject) opt; + ParseBean pb = new ParseBean(); + pb.setName(obj.get("name").getAsString().trim()); + pb.setUrl(obj.get("url").getAsString().trim()); + String ext = obj.has("ext") ? obj.get("ext").getAsJsonObject().toString() : ""; + pb.setExt(ext); + pb.setType(DefaultConfig.safeJsonInt(obj, "type", 0)); + parseBeanList.add(pb); + } + if(!parseBeanList.isEmpty())addSuperParse(); } // 获取默认解析 if (parseBeanList != null && parseBeanList.size() > 0) { @@ -281,68 +814,363 @@ private void parseJson(String apiUrl, String jsonStr) { if (mDefaultParse == null) setDefaultParse(parseBeanList.get(0)); } + // 直播源 - liveChannelGroupList.clear(); //修复从后台切换重复加载频道列表 - try { - String lives = infoJson.get("lives").getAsJsonArray().toString(); - int index = lives.indexOf("proxy://"); - if (index != -1) { - int endIndex = lives.lastIndexOf("\""); - String url = lives.substring(index, endIndex); - url = DefaultConfig.checkReplaceProxy(url); + String live_api_url=Hawk.get(HawkConfig.LIVE_API_URL,""); + if(live_api_url.isEmpty() || apiUrl.equals(live_api_url)){ + LOG.i("echo-load-config_live"); + initLiveSettings(); + if(infoJson.has("lives")){ + JsonArray lives_groups=infoJson.get("lives").getAsJsonArray(); + int live_group_index=getLiveGroupIndex(); + if(live_group_index>lives_groups.size()-1)live_group_index=0; + Hawk.put(HawkConfig.LIVE_GROUP_LIST,lives_groups); + //加载多源配置 + try { + ArrayList liveSettingItemList = new ArrayList<>(); + for (int i=0; i< lives_groups.size();i++) { + JsonObject jsonObject = lives_groups.get(i).getAsJsonObject(); + String name = jsonObject.has("name")?jsonObject.get("name").getAsString():"线路"+(i+1); + LiveSettingItem liveSettingItem = new LiveSettingItem(); + liveSettingItem.setItemIndex(i); + liveSettingItem.setItemName(name); + liveSettingItemList.add(liveSettingItem); + } + liveSettingGroupList.get(5).setLiveSettingItems(liveSettingItemList); + } catch (Exception e) { + // 捕获任何可能发生的异常 + e.printStackTrace(); + } - //clan - String extUrl = Uri.parse(url).getQueryParameter("ext"); - if (extUrl != null && !extUrl.isEmpty()) { - String extUrlFix = new String(Base64.decode(extUrl, Base64.DEFAULT | Base64.URL_SAFE | Base64.NO_WRAP), "UTF-8"); - if (extUrlFix.startsWith("clan://")) { - extUrlFix = clanContentFix(clanToAddress(apiUrl), extUrlFix); - extUrlFix = Base64.encodeToString(extUrlFix.getBytes("UTF-8"), Base64.DEFAULT | Base64.URL_SAFE | Base64.NO_WRAP); - url = url.replace(extUrl, extUrlFix); + JsonObject livesOBJ = lives_groups.get(live_group_index).getAsJsonObject(); + loadLiveApi(livesOBJ); + } + } + + myHosts = new HashMap<>(); + if (infoJson.has("hosts")) { + JsonArray hostsArray = infoJson.getAsJsonArray("hosts"); + for (int i = 0; i < hostsArray.size(); i++) { + String entry = hostsArray.get(i).getAsString(); + String[] parts = entry.split("=", 2); // 只分割一次,防止 value 里有 = + if (parts.length == 2) { + myHosts.put(parts[0], parts[1]); + } + } + } + + loadProxyRules(infoJson); + + //video parse rule for host + if (infoJson.has("rules")) { + VideoParseRuler.clearRule(); + for(JsonElement oneHostRule : infoJson.getAsJsonArray("rules")) { + JsonObject obj = (JsonObject) oneHostRule; + //嗅探过滤规则 + if (obj.has("host")) { + String host = obj.get("host").getAsString(); + if (obj.has("rule")) { + JsonArray ruleJsonArr = obj.getAsJsonArray("rule"); + ArrayList rule = new ArrayList<>(); + for (JsonElement one : ruleJsonArr) { + String oneRule = one.getAsString(); + rule.add(oneRule); + } + if (rule.size() > 0) { + VideoParseRuler.addHostRule(host, rule); + } + } + if (obj.has("filter")) { + JsonArray filterJsonArr = obj.getAsJsonArray("filter"); + ArrayList filter = new ArrayList<>(); + for (JsonElement one : filterJsonArr) { + String oneFilter = one.getAsString(); + filter.add(oneFilter); + } + if (filter.size() > 0) { + VideoParseRuler.addHostFilter(host, filter); + } + } + } + //广告过滤规则 + if (obj.has("hosts") && obj.has("regex")) { + ArrayList rule = new ArrayList<>(); + ArrayList ads = new ArrayList<>(); + JsonArray regexArray = obj.getAsJsonArray("regex"); + for (JsonElement one : regexArray) { + String regex = one.getAsString(); + if (M3u8.isAd(regex)) ads.add(regex); + else rule.add(regex); + } + JsonArray array = obj.getAsJsonArray("hosts"); + for (JsonElement one : array) { + String host = one.getAsString(); + VideoParseRuler.addHostRule(host, rule); + VideoParseRuler.addHostRegex(host, ads); + } + } + //嗅探脚本规则 如 click + if (obj.has("hosts") && obj.has("script")) { + ArrayList scripts = new ArrayList<>(); + JsonArray scriptArray = obj.getAsJsonArray("script"); + for (JsonElement one : scriptArray) { + String script = one.getAsString(); + scripts.add(script); + } + JsonArray array = obj.getAsJsonArray("hosts"); + for (JsonElement one : array) { + String host = one.getAsString(); + VideoParseRuler.addHostScript(host, scripts); } } - LiveChannelGroup liveChannelGroup = new LiveChannelGroup(); - liveChannelGroup.setGroupName(url); - liveChannelGroupList.add(liveChannelGroup); - } else { - loadLives(infoJson.get("lives").getAsJsonArray()); } - } catch (Throwable th) { - th.printStackTrace(); } + + if (infoJson.has("doh")) { + String doh_json = infoJson.getAsJsonArray("doh").toString(); + if(!Hawk.get(HawkConfig.DOH_JSON, "").equals(doh_json)){ + Hawk.put(HawkConfig.DOH_URL, 0); + Hawk.put(HawkConfig.DOH_JSON,doh_json); + } + }else { + Hawk.put(HawkConfig.DOH_JSON,""); + } + OkGoHelper.setDnsList(); + LOG.i("echo-api-config-----------load"); + //追加的广告拦截 + if(infoJson.has("ads")){ + for (JsonElement host : infoJson.getAsJsonArray("ads")) { + if(!AdBlocker.hasHost(host.getAsString())){ + AdBlocker.addAdHost(host.getAsString()); + } + } + } + } + + private void loadDefaultConfig() { + String defaultIJKADS="{\"ijk\":[{\"options\":[{\"name\":\"opensles\",\"category\":4,\"value\":\"0\"},{\"name\":\"framedrop\",\"category\":4,\"value\":\"1\"},{\"name\":\"soundtouch\",\"category\":4,\"value\":\"1\"},{\"name\":\"start-on-prepared\",\"category\":4,\"value\":\"1\"},{\"name\":\"http-detect-rangeupport\",\"category\":1,\"value\":\"0\"},{\"name\":\"fflags\",\"category\":1,\"value\":\"fastseek\"},{\"name\":\"skip_loop_filter\",\"category\":2,\"value\":\"48\"},{\"name\":\"reconnect\",\"category\":4,\"value\":\"1\"},{\"name\":\"enable-accurate-seek\",\"category\":4,\"value\":\"0\"},{\"name\":\"mediacodec\",\"category\":4,\"value\":\"0\"},{\"name\":\"mediacodec-all-videos\",\"category\":4,\"value\":\"0\"},{\"name\":\"mediacodec-auto-rotate\",\"category\":4,\"value\":\"0\"},{\"name\":\"mediacodec-handle-resolution-change\",\"category\":4,\"value\":\"0\"},{\"name\":\"mediacodec-hevc\",\"category\":4,\"value\":\"0\"},{\"name\":\"max-buffer-size\",\"category\":4,\"value\":\"15728640\"}],\"group\":\"软解码\"},{\"options\":[{\"name\":\"opensles\",\"category\":4,\"value\":\"0\"},{\"name\":\"framedrop\",\"category\":4,\"value\":\"1\"},{\"name\":\"soundtouch\",\"category\":4,\"value\":\"1\"},{\"name\":\"start-on-prepared\",\"category\":4,\"value\":\"1\"},{\"name\":\"http-detect-rangeupport\",\"category\":1,\"value\":\"0\"},{\"name\":\"fflags\",\"category\":1,\"value\":\"fastseek\"},{\"name\":\"skip_loop_filter\",\"category\":2,\"value\":\"48\"},{\"name\":\"reconnect\",\"category\":4,\"value\":\"1\"},{\"name\":\"enable-accurate-seek\",\"category\":4,\"value\":\"0\"},{\"name\":\"mediacodec\",\"category\":4,\"value\":\"1\"},{\"name\":\"mediacodec-all-videos\",\"category\":4,\"value\":\"1\"},{\"name\":\"mediacodec-auto-rotate\",\"category\":4,\"value\":\"1\"},{\"name\":\"mediacodec-handle-resolution-change\",\"category\":4,\"value\":\"1\"},{\"name\":\"mediacodec-hevc\",\"category\":4,\"value\":\"1\"},{\"name\":\"max-buffer-size\",\"category\":4,\"value\":\"15728640\"}],\"group\":\"硬解码\"}],\"ads\":[\"mimg.0c1q0l.cn\",\"www.googletagmanager.com\",\"www.google-analytics.com\",\"mc.usihnbcq.cn\",\"mg.g1mm3d.cn\",\"mscs.svaeuzh.cn\",\"cnzz.hhttm.top\",\"tp.vinuxhome.com\",\"cnzz.mmstat.com\",\"www.baihuillq.com\",\"s23.cnzz.com\",\"z3.cnzz.com\",\"c.cnzz.com\",\"stj.v1vo.top\",\"z12.cnzz.com\",\"img.mosflower.cn\",\"tips.gamevvip.com\",\"ehwe.yhdtns.com\",\"xdn.cqqc3.com\",\"www.jixunkyy.cn\",\"sp.chemacid.cn\",\"hm.baidu.com\",\"s9.cnzz.com\",\"z6.cnzz.com\",\"um.cavuc.com\",\"mav.mavuz.com\",\"wofwk.aoidf3.com\",\"z5.cnzz.com\",\"xc.hubeijieshikj.cn\",\"tj.tianwenhu.com\",\"xg.gars57.cn\",\"k.jinxiuzhilv.com\",\"cdn.bootcss.com\",\"ppl.xunzhuo123.com\",\"xomk.jiangjunmh.top\",\"img.xunzhuo123.com\",\"z1.cnzz.com\",\"s13.cnzz.com\",\"xg.huataisangao.cn\",\"z7.cnzz.com\",\"xg.huataisangao.cn\",\"z2.cnzz.com\",\"s96.cnzz.com\",\"q11.cnzz.com\",\"thy.dacedsfa.cn\",\"xg.whsbpw.cn\",\"s19.cnzz.com\",\"z8.cnzz.com\",\"s4.cnzz.com\",\"f5w.as12df.top\",\"ae01.alicdn.com\",\"www.92424.cn\",\"k.wudejia.com\",\"vivovip.mmszxc.top\",\"qiu.xixiqiu.com\",\"cdnjs.hnfenxun.com\",\"cms.qdwght.com\"]}"; + JsonObject defaultJson=gson.fromJson(defaultIJKADS, JsonObject.class); // 广告地址 - for (JsonElement host : infoJson.getAsJsonArray("ads")) { - AdBlocker.addAdHost(host.getAsString()); + if(AdBlocker.isEmpty()){ + //默认广告拦截 + for (JsonElement host : defaultJson.getAsJsonArray("ads")) { + AdBlocker.addAdHost(host.getAsString()); + } } // IJK解码配置 - boolean foundOldSelect = false; - String ijkCodec = Hawk.get(HawkConfig.IJK_CODEC, ""); - ijkCodes = new ArrayList<>(); - for (JsonElement opt : infoJson.get("ijk").getAsJsonArray()) { - JsonObject obj = (JsonObject) opt; - String name = obj.get("group").getAsString(); - LinkedHashMap baseOpt = new LinkedHashMap<>(); - for (JsonElement cfg : obj.get("options").getAsJsonArray()) { - JsonObject cObj = (JsonObject) cfg; - String key = cObj.get("category").getAsString() + "|" + cObj.get("name").getAsString(); - String val = cObj.get("value").getAsString(); - baseOpt.put(key, val); - } - IJKCode codec = new IJKCode(); - codec.setName(name); - codec.setOption(baseOpt); - if (name.equals(ijkCodec) || TextUtils.isEmpty(ijkCodec)) { - codec.selected(true); - ijkCodec = name; - foundOldSelect = true; - } else { - codec.selected(false); + if(ijkCodes==null){ + ijkCodes = new ArrayList<>(); + boolean foundOldSelect = false; + String ijkCodec = Hawk.get(HawkConfig.IJK_CODEC, "硬解码"); + JsonArray ijkJsonArray = defaultJson.get("ijk").getAsJsonArray(); + for (JsonElement opt : ijkJsonArray) { + JsonObject obj = (JsonObject) opt; + String name = obj.get("group").getAsString(); + LinkedHashMap baseOpt = new LinkedHashMap<>(); + for (JsonElement cfg : obj.get("options").getAsJsonArray()) { + JsonObject cObj = (JsonObject) cfg; + String key = cObj.get("category").getAsString() + "|" + cObj.get("name").getAsString(); + String val = cObj.get("value").getAsString(); + baseOpt.put(key, val); + } + IJKCode codec = new IJKCode(); + codec.setName(name); + codec.setOption(baseOpt); + if (name.equals(ijkCodec) || TextUtils.isEmpty(ijkCodec)) { + codec.selected(true); + ijkCodec = name; + foundOldSelect = true; + } else { + codec.selected(false); + } + ijkCodes.add(codec); + } + if (!foundOldSelect && ijkCodes.size() > 0) { + ijkCodes.get(0).selected(true); + } + } + LOG.i("echo-default-config-----------load"); + } + private void parseLiveConfigContent(String apiUrl, File f) throws Throwable { + BufferedReader bReader = new BufferedReader(new InputStreamReader(new FileInputStream(f), "UTF-8")); + StringBuilder sb = new StringBuilder(); + String s = ""; + while ((s = bReader.readLine()) != null) { + sb.append(s + "\n"); + } + bReader.close(); + parseLiveConfigContent(apiUrl, sb.toString()); + } + + private void parseLiveConfigContent(String apiUrl, String content) { + String jsonContent = trimJsonObject(content); + if (!TextUtils.isEmpty(jsonContent)) { + try { + JsonObject infoJson = gson.fromJson(jsonContent, JsonObject.class); + if (infoJson != null && infoJson.has("lives")) { + parseLiveJson(apiUrl, jsonContent); + return; + } + } catch (Throwable ignored) { + } + } + if (isLiveJsonContent(content)) { + parseLiveJson(apiUrl, jsonContent); + } else { + parseLiveText(apiUrl, content); + } + } + + private boolean isLiveJsonContent(String content) { + if (content == null) return false; + String text = content.trim(); + if (text.startsWith("\ufeff")) text = text.substring(1).trim(); + return text.startsWith("{"); + } + + private void parseLiveText(String apiUrl, String content) { + liveChannelGroupList.clear(); + liveSpider = ""; + currentLiveSpider = ""; + currentLivePyKey = ""; + initLiveSettings(); + Hawk.put(HawkConfig.LIVE_GROUP_LIST, new JsonArray()); + Hawk.put(HawkConfig.EPG_URL, extractLiveTextEpg(content)); + Hawk.put(HawkConfig.LIVE_PLAY_TYPE, Hawk.get(HawkConfig.PLAY_TYPE, 0)); + Hawk.put(HawkConfig.LIVE_WEB_HEADER, null); + JsonArray livesArray = TxtSubscribe.parseToJsonArray(content); + loadLives(livesArray); + LOG.i("echo-live-text-config-----------load:" + apiUrl); + } + + private String extractLiveTextEpg(String content) { + if (content == null) return ""; + String text = content.replace("\r\n", "\n").replace('\r', '\n'); + String[] lines = text.split("\n"); + for (String line : lines) { + line = line.trim(); + if (line.startsWith("\ufeff")) line = line.substring(1).trim(); + if (!line.startsWith("#EXTM3U")) continue; + String epg = extractQuotedAttr(line, "x-tvg-url"); + if (epg.isEmpty()) epg = extractQuotedAttr(line, "tvg-url"); + if (epg.isEmpty()) epg = extractQuotedAttr(line, "url-tvg"); + return epg; + } + return ""; + } + + private String extractQuotedAttr(String line, String key) { + String token = key + "=\""; + int start = line.indexOf(token); + if (start < 0) return ""; + start += token.length(); + int end = line.indexOf("\"", start); + if (end < 0) return ""; + return line.substring(start, end).trim(); + } + + private String liveSpider=""; + private void parseLiveJson(String apiUrl, String jsonStr) { + liveChannelGroupList.clear(); + JsonObject infoJson = gson.fromJson(jsonStr, JsonObject.class); + // spider + liveSpider = DefaultConfig.safeJsonString(infoJson, "spider", ""); + // 直播源 + initLiveSettings(); + if(infoJson.has("lives")){ + JsonArray lives_groups=infoJson.get("lives").getAsJsonArray(); + + int live_group_index=getLiveGroupIndex(); + if(live_group_index>lives_groups.size()-1)live_group_index=0; + Hawk.put(HawkConfig.LIVE_GROUP_LIST,lives_groups); + //加载多源配置 + try { + ArrayList liveSettingItemList = new ArrayList<>(); + for (int i=0; i< lives_groups.size();i++) { + JsonObject jsonObject = lives_groups.get(i).getAsJsonObject(); + String name = jsonObject.has("name")?jsonObject.get("name").getAsString():"线路"+(i+1); + LiveSettingItem liveSettingItem = new LiveSettingItem(); + liveSettingItem.setItemIndex(i); + liveSettingItem.setItemName(name); + liveSettingItemList.add(liveSettingItem); + } + liveSettingGroupList.get(5).setLiveSettingItems(liveSettingItemList); + } catch (Exception e) { + // 捕获任何可能发生的异常 + e.printStackTrace(); + } + + JsonObject livesOBJ = lives_groups.get(live_group_index).getAsJsonObject(); + loadLiveApi(livesOBJ); + } + + myHosts = new HashMap<>(); + if (infoJson.has("hosts")) { + JsonArray hostsArray = infoJson.getAsJsonArray("hosts"); + for (int i = 0; i < hostsArray.size(); i++) { + String entry = hostsArray.get(i).getAsString(); + String[] parts = entry.split("=", 2); // 只分割一次,防止 value 里有 = + if (parts.length == 2) { + myHosts.put(parts[0], parts[1]); + } + } + } + LOG.i("echo-api-live-config-----------load"); + } + + private final List liveSettingGroupList = new ArrayList<>(); + private void initLiveSettings() { + ArrayList groupNames = new ArrayList<>(Arrays.asList("线路选择", "画面比例", "播放解码", "超时换源", "偏好设置", "多源切换", "配置切换")); + ArrayList> itemsArrayList = new ArrayList<>(); + ArrayList sourceItems = new ArrayList<>(); + ArrayList scaleItems = new ArrayList<>(Arrays.asList("默认", "16:9", "4:3", "填充", "原始", "裁剪")); + ArrayList playerDecoderItems = new ArrayList<>(Arrays.asList("系统", "ijk硬解", "ijk软解", "exo")); + ArrayList timeoutItems = new ArrayList<>(Arrays.asList("5s", "10s", "15s", "20s", "25s", "30s")); + ArrayList personalSettingItems = new ArrayList<>(Arrays.asList("显示时间", "显示网速", "显分辨率", "换台反转", "跨选分类")); + ArrayList yumItems = new ArrayList<>(); + ArrayList liveApiHistoryItems = new ArrayList<>(); + + itemsArrayList.add(sourceItems); + itemsArrayList.add(scaleItems); + itemsArrayList.add(playerDecoderItems); + itemsArrayList.add(timeoutItems); + itemsArrayList.add(personalSettingItems); + itemsArrayList.add(yumItems); + itemsArrayList.add(liveApiHistoryItems); + + liveSettingGroupList.clear(); + for (int i = 0; i < groupNames.size(); i++) { + LiveSettingGroup liveSettingGroup = new LiveSettingGroup(); + ArrayList liveSettingItemList = new ArrayList<>(); + liveSettingGroup.setGroupIndex(i); + liveSettingGroup.setGroupName(groupNames.get(i)); + for (int j = 0; j < itemsArrayList.get(i).size(); j++) { + LiveSettingItem liveSettingItem = new LiveSettingItem(); + liveSettingItem.setItemIndex(j); + liveSettingItem.setItemName(itemsArrayList.get(i).get(j)); + liveSettingItemList.add(liveSettingItem); } - ijkCodes.add(codec); + liveSettingGroup.setLiveSettingItems(liveSettingItemList); + liveSettingGroupList.add(liveSettingGroup); } - if (!foundOldSelect && ijkCodes.size() > 0) { - ijkCodes.get(0).selected(true); + refreshLiveApiHistoryItems(); + } + + public List getLiveSettingGroupList() { + return liveSettingGroupList; + } + + public void refreshLiveApiHistoryItems() { + if (liveSettingGroupList.size() < 7) return; + ArrayList liveSettingItemList = new ArrayList<>(); + ArrayList history = Hawk.get(HawkConfig.LIVE_API_HISTORY, new ArrayList()); + for (int i = 0; i < history.size(); i++) { + LiveSettingItem liveSettingItem = new LiveSettingItem(); + liveSettingItem.setItemIndex(i); + liveSettingItem.setItemName(history.get(i)); + liveSettingItemList.add(liveSettingItem); } + liveSettingGroupList.get(6).setLiveSettingItems(liveSettingItemList); } public void loadLives(JsonArray livesArray) { @@ -366,8 +1194,40 @@ public void loadLives(JsonArray livesArray) { JsonObject obj = (JsonObject) channelElement; LiveChannelItem liveChannelItem = new LiveChannelItem(); liveChannelItem.setChannelName(obj.get("name").getAsString().trim()); - liveChannelItem.setChannelIndex(channelIndex++); - liveChannelItem.setChannelNum(++channelNum); + liveChannelItem.setChannelLogo(DefaultConfig.safeJsonString(obj, "logo", "")); + liveChannelItem.setChannelEpg(DefaultConfig.safeJsonString(obj, "epg", "")); + liveChannelItem.setChannelUa(DefaultConfig.safeJsonString(obj, "ua", "")); + liveChannelItem.setChannelClick(DefaultConfig.safeJsonString(obj, "click", "")); + liveChannelItem.setChannelFormat(DefaultConfig.safeJsonString(obj, "format", "")); + liveChannelItem.setChannelOrigin(DefaultConfig.safeJsonString(obj, "origin", "")); + liveChannelItem.setChannelReferer(DefaultConfig.safeJsonString(obj, "referer", "")); + liveChannelItem.setChannelTvgId(DefaultConfig.safeJsonString(obj, "tvg-id", "")); + liveChannelItem.setChannelTvgName(DefaultConfig.safeJsonString(obj, "tvg-name", "")); + if (obj.has("parse")) { + try { + liveChannelItem.setChannelParse(obj.get("parse").getAsInt()); + } catch (Throwable ignored) { + } + } + if (obj.has("catchup")) { + JsonObject catchupObj = new JsonObject(); + if (obj.get("catchup").isJsonObject()) { + catchupObj = obj.getAsJsonObject("catchup"); + } else { + catchupObj.addProperty("type", obj.get("catchup").getAsString()); + if (obj.has("catchup-source")) catchupObj.addProperty("source", obj.get("catchup-source").getAsString()); + if (obj.has("catchup-replace")) catchupObj.addProperty("replace", obj.get("catchup-replace").getAsString()); + } + liveChannelItem.setChannelCatchup(catchupObj); + } + if (obj.has("header") && obj.get("header").isJsonObject()) { + JsonObject headerObj = obj.getAsJsonObject("header"); + HashMap channelHeader = new HashMap<>(); + for (Map.Entry entry : headerObj.entrySet()) { + channelHeader.put(entry.getKey(), entry.getValue().getAsString()); + } + liveChannelItem.setChannelHeader(channelHeader); + } ArrayList urls = DefaultConfig.safeJsonStringList(obj, "urls"); ArrayList sourceNames = new ArrayList<>(); ArrayList sourceUrls = new ArrayList<>(); @@ -383,24 +1243,417 @@ public void loadLives(JsonArray livesArray) { } liveChannelItem.setChannelSourceNames(sourceNames); liveChannelItem.setChannelUrls(sourceUrls); - liveChannelGroup.getLiveChannels().add(liveChannelItem); + if (mergeLiveChannel(liveChannelGroup.getLiveChannels(), liveChannelItem)) { + liveChannelItem.setChannelIndex(channelIndex++); + liveChannelItem.setChannelNum(++channelNum); + } + } + liveChannelGroupList.add(liveChannelGroup); + } + } + + private boolean mergeLiveChannel(ArrayList channelItems, LiveChannelItem newItem) { + LiveChannelItem oldItem = findLiveChannel(channelItems, newItem.getChannelName()); + if (oldItem == null) { + channelItems.add(newItem); + return true; + } + mergeLiveChannelUrls(oldItem, newItem); + return false; + } + + private LiveChannelItem findLiveChannel(ArrayList channelItems, String channelName) { + for (LiveChannelItem item : channelItems) { + if (channelName != null && channelName.equals(item.getChannelName())) return item; + } + return null; + } + + private void mergeLiveChannelUrls(LiveChannelItem oldItem, LiveChannelItem newItem) { + ArrayList oldUrls = oldItem.getChannelUrls(); + ArrayList oldSourceNames = oldItem.getChannelSourceNames(); + if (oldUrls == null) { + oldUrls = new ArrayList<>(); + oldItem.setChannelUrls(oldUrls); + } + if (oldSourceNames == null) { + oldSourceNames = new ArrayList<>(); + oldItem.setChannelSourceNames(oldSourceNames); + } + while (oldSourceNames.size() < oldUrls.size()) { + oldSourceNames.add("源" + Integer.toString(oldSourceNames.size() + 1)); + } + ArrayList newUrls = newItem.getChannelUrls(); + ArrayList newSourceNames = newItem.getChannelSourceNames(); + if (newUrls == null) return; + for (int i = 0; i < newUrls.size(); i++) { + String url = newUrls.get(i); + if (oldUrls.contains(url)) continue; + oldUrls.add(url); + if (newSourceNames != null && i < newSourceNames.size()) { + oldSourceNames.add(newSourceNames.get(i)); + } else { + oldSourceNames.add("源" + Integer.toString(oldSourceNames.size() + 1)); + } + } + oldItem.setChannelUrls(oldUrls); + oldItem.setChannelSourceNames(oldSourceNames); + } + + public void loadLiveApi(JsonObject livesOBJ) { + try { + LOG.i("echo-loadLiveApi"); + liveChannelGroupList.clear(); + currentLiveSpider = ""; + currentLivePyKey = ""; + String lives = livesOBJ.toString(); + int index = lives.indexOf("proxy://"); + String url; + if (index != -1) { + int endIndex = lives.lastIndexOf("\""); + url = lives.substring(index, endIndex); + url = DefaultConfig.checkReplaceProxy(url); + String extUrl = Uri.parse(url).getQueryParameter("ext"); + if (extUrl != null && !extUrl.isEmpty()) { + String extUrlFix; + if(extUrl.startsWith("http") || extUrl.startsWith("clan://")){ + extUrlFix = extUrl; + }else { + extUrlFix = new String(Base64.decode(extUrl, Base64.DEFAULT | Base64.URL_SAFE | Base64.NO_WRAP), "UTF-8"); + } + extUrlFix = Base64.encodeToString(extUrlFix.getBytes("UTF-8"), Base64.DEFAULT | Base64.URL_SAFE | Base64.NO_WRAP); + url = url.replace(extUrl, extUrlFix); + } + } else { + String api = livesOBJ.has("api") ? livesOBJ.get("api").getAsString().trim() : ""; + String type = livesOBJ.has("type") ? livesOBJ.get("type").getAsString() : (isLiveSpiderApi(api) ? "3" : "0"); + if(type.equals("0") || type.equals("3")){ + url = livesOBJ.has("url")?livesOBJ.get("url").getAsString():""; + if(url.isEmpty())url=api; + LOG.i("echo-liveurl"+url); + if(!url.startsWith("http://127.0.0.1")){ + if(url.startsWith("http")){ + url = Base64.encodeToString(url.getBytes("UTF-8"), Base64.DEFAULT | Base64.URL_SAFE | Base64.NO_WRAP); + } + url ="http://127.0.0.1:9978/proxy?do=live&type=txt&ext="+url; + } + if(type.equals("3")){ + String jarUrl = livesOBJ.has("jar")?livesOBJ.get("jar").getAsString().trim():""; + LOG.i("echo-liveApi1"+api); + if(api.contains(".py")){ + LOG.i("echo-pyLoader.getSpider"); + String ext=""; + if(livesOBJ.has("ext") && (livesOBJ.get("ext").isJsonObject() || livesOBJ.get("ext").isJsonArray())){ + ext=livesOBJ.get("ext").toString(); + }else { + ext=DefaultConfig.safeJsonString(livesOBJ, "ext", ""); + } + + currentLivePyKey = MD5.string2MD5(api); + currentLiveSpider = api; + pyLoader.getSpider(currentLivePyKey,api,ext); + } else if (api.contains(".js")) { + LOG.i("echo-jsLoader.getSpider"); + String ext=""; + if(livesOBJ.has("ext") && (livesOBJ.get("ext").isJsonObject() || livesOBJ.get("ext").isJsonArray())){ + ext=livesOBJ.get("ext").toString(); + }else { + ext=DefaultConfig.safeJsonString(livesOBJ, "ext", ""); + } + currentLiveSpider = api; + jsLoader.getSpider(MD5.string2MD5(api), api, ext, jarUrl); + } + if(!jarUrl.isEmpty() && !isLiveSpiderApi(api)){ + jarLoader.loadLiveJar(jarUrl); + if (TextUtils.isEmpty(currentLiveSpider)) { + currentLiveSpider = jarUrl; + } + }else if(!liveSpider.isEmpty() && !isLiveSpiderApi(api)){ + jarLoader.loadLiveJar(liveSpider); + if (TextUtils.isEmpty(currentLiveSpider)) { + currentLiveSpider = liveSpider; + } + } + } + }else { + liveChannelGroupList.clear(); + return; + } + } + //设置epg + if(livesOBJ.has("epg")){ + String epg =livesOBJ.get("epg").getAsString(); + Hawk.put(HawkConfig.EPG_URL,epg); + }else { + Hawk.put(HawkConfig.EPG_URL,""); + } + //直播播放器类型 + if(livesOBJ.has("playerType")){ + String livePlayType =livesOBJ.get("playerType").getAsString(); + Hawk.put(HawkConfig.LIVE_PLAY_TYPE,livePlayType); + }else { + Hawk.put(HawkConfig.LIVE_PLAY_TYPE,Hawk.get(HawkConfig.PLAY_TYPE, 0)); + } + //设置UA + if(livesOBJ.has("timeout")){ + int timeout = Math.max(5, Math.min(30, livesOBJ.get("timeout").getAsInt())); + Hawk.put(HawkConfig.LIVE_CONNECT_TIMEOUT, (timeout + 4) / 5 - 1); } + if(livesOBJ.has("header")) { + JsonObject headerObj = livesOBJ.getAsJsonObject("header"); + HashMap liveHeader = new HashMap<>(); + for (Map.Entry entry : headerObj.entrySet()) { + liveHeader.put(entry.getKey(), entry.getValue().getAsString()); + } + Hawk.put(HawkConfig.LIVE_WEB_HEADER, liveHeader); + } else if(livesOBJ.has("ua")) { + String ua = livesOBJ.get("ua").getAsString(); + HashMap liveHeader = new HashMap<>(); + liveHeader.put("User-Agent", ua); + Hawk.put(HawkConfig.LIVE_WEB_HEADER, liveHeader); + }else { + Hawk.put(HawkConfig.LIVE_WEB_HEADER,null); + } + LiveChannelGroup liveChannelGroup = new LiveChannelGroup(); + liveChannelGroup.setGroupName(url); + liveChannelGroupList.clear(); liveChannelGroupList.add(liveChannelGroup); + } catch (Throwable th) { + th.printStackTrace(); } } + private String currentLiveSpider; + public void setLiveJar(String liveJar) + { + if(liveJar.contains(".py")){ + currentLivePyKey = MD5.string2MD5(liveJar); + pyLoader.getSpider(currentLivePyKey, liveJar, ""); + pyLoader.setRecentPyKey(currentLivePyKey); + }else if(liveJar.contains(".js")){ + jsLoader.getSpider(MD5.string2MD5(liveJar), liveJar, "", ""); + }else { + String jarUrl=!liveJar.isEmpty()?liveJar:liveSpider; + jarLoader.setRecentJarKey(MD5.string2MD5(jarUrl)); + } + currentLiveSpider=liveJar; + } + public String getSpider() { return spider; } + public String getDanmaku() { + return danmaku == null ? "" : danmaku; + } + public Spider getCSP(SourceBean sourceBean) { - return jarLoader.getSpider(sourceBean.getKey(), sourceBean.getApi(), sourceBean.getExt()); + if (sourceBean.getApi().endsWith(".js") || sourceBean.getApi().contains(".js?")){ + currentPyKey = ""; + return jsLoader.getSpider(sourceBean.getKey(), sourceBean.getApi(), sourceBean.getExt(), sourceBean.getJar()); + } + else if (sourceBean.getApi().contains(".py")) { + currentPyKey = sourceBean.getKey(); + pyLoader.setRecentPyKey(currentPyKey); + return pyLoader.getSpider(sourceBean.getKey(), sourceBean.getApi(), sourceBean.getExt()); + } + else { + currentPyKey = ""; + return jarLoader.getSpider(sourceBean.getKey(), sourceBean.getApi(), sourceBean.getExt(), sourceBean.getJar()); + } + } + + public void warmSearchSpiders() { + final ArrayList sources = new ArrayList<>(sourceBeanList.values()); + final SourceBean home = getHomeSourceBean(); + final Set sharedSpiderApis = new HashSet<>(); + Set spiderApis = new HashSet<>(); + for (SourceBean source : sources) { + if (source == null || source.getType() != 3) continue; + String spiderApiKey = source.getJar() + "|" + source.getApi(); + if (!spiderApis.add(spiderApiKey)) sharedSpiderApis.add(spiderApiKey); + } + configLoadExecutor.execute(new Runnable() { + @Override + public void run() { + LOG.i("echo-warm-spider start"); + int eligibleCount = 0; + for (SourceBean source : sources) { + if (source == null || source.getType() != 3 || !source.isSearchable()) continue; + if (home != null && TextUtils.equals(home.getKey(), source.getKey())) continue; + // 同类 Spider 可能通过静态状态保存 ext,不能在后台预热时交替初始化。 + if (sharedSpiderApis.contains(source.getJar() + "|" + source.getApi())) continue; + if (eligibleCount >= 10) break; + eligibleCount++; + String warmKey = source.getKey() + "|" + source.getApi() + "|" + source.getJar() + "|" + source.getExt(); + synchronized (warmedSearchSpiderKeys) { + if (warmedSearchSpiderKeys.contains(warmKey)) continue; + warmedSearchSpiderKeys.add(warmKey); + } + try { + LOG.i("echo-warm-spider load:" + warmKey); + getCSP(source); + } catch (Throwable th) { + LOG.e("echo-warm-search-spider-error " + source.getKey() + ":" + th.getMessage()); + } + } + } + }); + } + + public Spider getPyCSP(String url) { + currentLivePyKey = MD5.string2MD5(url); + currentLiveSpider = url; + return pyLoader.getSpider(currentLivePyKey, url, ""); + } + + public Spider getJsCSP(String url) { + currentLiveSpider = url; + return jsLoader.getSpider(MD5.string2MD5(url), url, "", ""); + } + + public Spider getLiveCSP(String url) { + return url.contains(".js") ? getJsCSP(url) : getPyCSP(url); + } + + public void searchDanmuUi(String name, String episode, boolean longClick) { + danmuSearchExecutor.execute(() -> { + try { + jarLoader.searchDanmuUi(name, episode, longClick); + } catch (Throwable th) { + LOG.e("ApiConfig searchDanmuUi error: " + th.getMessage()); + th.printStackTrace(); + } + }); + } + + public boolean hasDanmuSearchUi() { + return jarLoader.hasDanmuSearchUi(); + } + + public int getLiveConnectTimeoutSeconds() { + return (Hawk.get(HawkConfig.LIVE_CONNECT_TIMEOUT, 1) + 1) * 5; + } + + private boolean isLiveSpiderApi(String api) { + return api.contains(".py") || api.contains(".js"); } - public Object[] proxyLocal(Map param) { + public Object[] proxyLocal(Map param) { + SourceBean source = getCurrentProxySource(param); + String api = source.getApi(); + + String siteKey = param.get("siteKey"); + String action = param.get("do"); + + boolean isJs = "js".equals(action); + boolean isPy = "py".equals(action); + boolean isLive = Hawk.get(HawkConfig.PLAYER_IS_LIVE, false); + boolean isApiJs = api.contains(".js"); + boolean isApiPy = api.contains(".py"); + + boolean canUseType3 = !TextUtils.isEmpty(siteKey) + && source.getType() == 3 + && !isJs + && !isPy + && !isLive + && !isApiJs + && !isApiPy; + + if (canUseType3) { + try { + Spider spider = getCSP(source); + + Object[] result = spider.proxy(param); + if (result != null) return result; + + result = jarLoader.proxyInvoke(param); + if (result != null) return result; + + result = proxyDirect(param); + if (result != null) return result; + + return null; + } catch (Throwable th) { + LOG.e("echo-proxy siteKey error: " + th.getMessage()); + return null; + } + } + + if (isJs) { + return jsLoader.proxyInvoke(param); + } + + if (isLive) { + String liveApi = currentLiveSpider != null ? currentLiveSpider : ""; + + if (liveApi.contains(".py")) { + return pyLoader.proxyInvoke(param, currentLivePyKey); + } + if (liveApi.contains(".js")) { + return jsLoader.proxyInvoke(param); + } + return jarLoader.proxyInvoke(param); + } + + if (isPy) { + return pyLoader.proxyInvoke(param, getCurrentPyKey()); + } + + if (isApiPy) { + return pyLoader.proxyInvoke(param, getCurrentPyKey()); + } + return jarLoader.proxyInvoke(param); } + private Object[] proxyDirect(Map param) { + try { + String url = param.get("url"); + if (TextUtils.isEmpty(url)) return null; + url = URLDecoder.decode(url, "UTF-8"); + if (!url.startsWith("http://") && !url.startsWith("https://")) return null; + if (!DefaultConfig.isVideoFormat(url)) return null; + if (url.contains(".m3u8")) { + param.put("url", url); + param.put("go", "live"); + param.put("type", "m3u8"); + return Proxy.itv(param); + } + return null; + } catch (Throwable th) { + LOG.e("echo-proxy direct fallback error: " + th.getMessage()); + return null; + } + } + + private SourceBean getCurrentProxySource(Map param) { + String siteKey = param.get("siteKey"); + if (TextUtils.isEmpty(siteKey)) { + siteKey = currentPlaySourceKey; + if (!TextUtils.isEmpty(siteKey)) param.put("siteKey", siteKey); + } + SourceBean sourceBean = TextUtils.isEmpty(siteKey) ? null : getSource(siteKey); + return sourceBean == null ? ApiConfig.get().getHomeSourceBean() : sourceBean; + } + + public void setCurrentPlaySourceKey(String sourceKey) { + currentPlaySourceKey = sourceKey == null ? "" : sourceKey; + } + + private String getCurrentPyKey() { + SourceBean sourceBean = getCurrentProxySource(new HashMap()); + if (sourceBean.getApi().contains(".py")) { + if (!sourceBean.getKey().equals(currentPyKey)) { + currentPyKey = sourceBean.getKey(); + pyLoader.getSpider(currentPyKey, sourceBean.getApi(), sourceBean.getExt()); + pyLoader.setRecentPyKey(currentPyKey); + } + return currentPyKey; + } + return currentPyKey; + } + public JSONObject jsonExt(String key, LinkedHashMap jxs, String url) { return jarLoader.jsonExt(key, jxs, url); } @@ -412,9 +1665,8 @@ public JSONObject jsonExtMix(String flag, String key, String name, LinkedHashMap public interface LoadConfigCallback { void success(); - void retry(); - void error(String msg); + void notice(String msg); } public interface FastParseCallback { @@ -424,8 +1676,16 @@ public interface FastParseCallback { } public SourceBean getSource(String key) { - if (!sourceBeanList.containsKey(key)) + if (!sourceBeanList.containsKey(key)) { + if ("push_agent".equals(key)) { + SourceBean sourceBean = new SourceBean(); + sourceBean.setKey("push_agent"); + sourceBean.setName("推送"); + sourceBean.setType(-1); + return sourceBean; + } return null; + } return sourceBeanList.get(key); } @@ -449,6 +1709,27 @@ public ParseBean getDefaultParse() { public List getSourceBeanList() { return new ArrayList<>(sourceBeanList.values()); } + public List getSwitchSourceBeanList() { + List filteredList = new ArrayList<>(); + for (SourceBean bean : sourceBeanList.values()) { + filteredList.add(bean); + } + return filteredList; + } + + private List searchSourceBeanList; + public List getSearchSourceBeanList() { + if(searchSourceBeanList.isEmpty()){ + LOG.i("echo-第一次getSearchSourceBeanList"); + searchSourceBeanList = new ArrayList<>(); + for (SourceBean bean : sourceBeanList.values()) { + if (bean.isSearchable()) { + searchSourceBeanList.add(bean); + } + } + } + return searchSourceBeanList; + } public List getParseBeanList() { return parseBeanList; @@ -471,7 +1752,7 @@ public List getIjkCodes() { } public IJKCode getCurrentIJKCode() { - String codeName = Hawk.get(HawkConfig.IJK_CODEC, ""); + String codeName = Hawk.get(HawkConfig.IJK_CODEC, "硬解码"); return getIJKCodec(codeName); } @@ -495,6 +1776,71 @@ String clanToAddress(String lanLink) { String clanContentFix(String lanLink, String content) { String fix = lanLink.substring(0, lanLink.indexOf("/file/") + 6); - return content.replace("clan://", fix); + return content.replace("clan://localhost/", fix).replace("file://", fix); + } + + String fixContentPath(String url, String content) { + if (content.contains("\"./") || content.contains("\"../")) { + url=url.replace("file://","clan://localhost/"); + if(!url.startsWith("http") && !url.startsWith("clan://")){ + url = "http://" + url; + } + if(url.startsWith("clan://"))url=clanToAddress(url); + String base = url.substring(0,url.lastIndexOf("/") + 1); + String parent = base.endsWith("/") ? base.substring(0, base.length() - 1) : base; + int parentEnd = parent.lastIndexOf("/"); + if (parentEnd >= 0) parent = parent.substring(0, parentEnd + 1); + content = content.replace("../", parent); + content = content.replace("./", base); + } + return content; + } + + public Map getMyHost() { + return myHosts; + } + + private void loadProxyRules(JsonObject infoJson) { + if (!infoJson.has("proxy")) { + OkGoHelper.setProxyList(null); + return; + } + try { + OkGoHelper.setProxyList(ProxyRule.arrayFrom(infoJson.get("proxy"))); + } catch (Throwable th) { + th.printStackTrace(); + OkGoHelper.setProxyList(null); + } + } + + public void clearJarLoader() + { + jarLoader.clear(); + } + + private void addSuperParse() + { + ParseBean superPb = new ParseBean(); + superPb.setName("超级解析"); + superPb.setUrl("SuperParse"); + superPb.setExt(""); + superPb.setType(4); + parseBeanList.add(0, superPb); + } + + public void clearLoader(){ + jarLoader.clear(); + pyLoader.clear(); + jsLoader.clear(); + synchronized (warmedSearchSpiderKeys) { + warmedSearchSpiderKeys.clear(); + } + } + + public void clearSpiderCache() { + currentPyKey = ""; + currentLivePyKey = ""; + currentLiveSpider = ""; + clearLoader(); } } diff --git a/app/src/main/java/com/github/tvbox/osc/api/DanmakuApi.java b/app/src/main/java/com/github/tvbox/osc/api/DanmakuApi.java new file mode 100644 index 0000000000..f2e182906c --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/api/DanmakuApi.java @@ -0,0 +1,681 @@ +package com.github.tvbox.osc.api; + +import android.os.Handler; +import android.os.Looper; +import android.text.TextUtils; + +import androidx.annotation.NonNull; +import androidx.collection.ArrayMap; + +import com.github.catvod.net.OkHttp; +import com.github.tvbox.osc.util.DanmuHelper; +import com.github.tvbox.osc.util.HawkConfig; +import com.github.tvbox.osc.util.LOG; +import com.github.catvod.crawler.js.Trans; +import com.orhanobut.hawk.Hawk; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.IOException; +import java.net.URLEncoder; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.Response; + +public class DanmakuApi { + private static final String TAG = DanmakuApi.class.getSimpleName(); +// private static final String BUILTIN_API = "https://saas-oa.shyeguang.cn"; + private static final String BUILTIN_API = "https://logvardanmu.konfan.cn/87654321"; + private static final String USE_DEFAULT_KEY = "danmu_api_use_default"; + private static final long BUILTIN_TIMEOUT = TimeUnit.SECONDS.toMillis(20); + private static final int BUILTIN_MAX_RETRY = 2; + private static final int EPISODE_QUERY_NUMBER = 0; + private static final int EPISODE_QUERY_EMPTY = 1; + private static final Handler handler = new Handler(Looper.getMainLooper()); + private static final AtomicInteger searchSeq = new AtomicInteger(); + + public interface SearchCallback { + void onFound(String url); + + default void onNotFound() { + } + } + + public static boolean canSearch() { + return DanmuHelper.isOpen() && !TextUtils.isEmpty(getApiUrl()); + } + + public static boolean hasCustomApi() { + String custom = Hawk.get(HawkConfig.DANMU_API, ""); + return !isUseDefault() && !TextUtils.isEmpty(custom) && !TextUtils.isEmpty(custom.trim()); + } + + public static String getDisplayApiUrl() { + if (isUseDefault()) return ""; + String custom = Hawk.get(HawkConfig.DANMU_API, ""); + if (!TextUtils.isEmpty(custom)) return custom.trim(); + String config = ApiConfig.get().getDanmaku().trim(); + return TextUtils.isEmpty(config) ? "" : config; + } + + public static boolean isUseDefault() { + return Hawk.get(USE_DEFAULT_KEY, false); + } + + public static void setUseDefault(boolean useDefault) { + Hawk.put(USE_DEFAULT_KEY, useDefault); + if (useDefault) Hawk.put(HawkConfig.DANMU_API, ""); + } + + public static void setCustomApi(String api) { + Hawk.put(USE_DEFAULT_KEY, false); + Hawk.put(HawkConfig.DANMU_API, api); + } + + public static void search(String name, String episode, SearchCallback callback) { + String apiUrl = getApiUrl(); +// LOG.i("echo-danmaku search apiUrl: " + apiUrl); + if (TextUtils.isEmpty(apiUrl) || callback == null) return; + try { + OkHttp.cancel(TAG); + int seq = searchSeq.incrementAndGet(); +// LOG.i("echo-danmaku search title: " + safeLog(name) + ", episode: " + safeLog(episode)); + if (!hasPlaceholder(apiUrl) && !isDanmakuSearchApi(apiUrl)) { + searchBuiltin(apiUrl, name, episode, callback, 0, seq); + return; + } + newCall(apiUrl, name, episode).enqueue(new Callback() { + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + if (!isCurrentSearch(seq)) return; + LOG.e("echo-danmaku search error: " + e.getMessage()); + notifyNotFound(callback, seq); + } + + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + if (!isCurrentSearch(seq)) return; + try { + String body = response.body() == null ? "" : response.body().string(); + String url = parseUrl(body); + if (!TextUtils.isEmpty(url) && isCurrentSearch(seq)) { + handler.post(new Runnable() { + @Override + public void run() { + if (isCurrentSearch(seq)) callback.onFound(url); + } + }); + } else { + notifyNotFound(callback, seq); + } + } catch (Throwable th) { + LOG.e("echo-danmaku search parse error: " + th.getMessage()); + notifyNotFound(callback, seq); + } + } + }); + } catch (Throwable th) { + LOG.e("echo-danmaku search start error: " + th.getMessage()); + notifyNotFound(callback, searchSeq.get()); + } + } + + public static void cancel() { + searchSeq.incrementAndGet(); + OkHttp.cancel(TAG); + } + + private static void searchBuiltin(String apiUrl, String name, String episode, SearchCallback callback, int retry, int seq) { + searchBuiltin(apiUrl, name, episode, callback, retry, seq, EPISODE_QUERY_NUMBER); + } + + private static void searchBuiltin(String apiUrl, String name, String episode, SearchCallback callback, int retry, int seq, int queryMode) { + final String baseUrl = normalizeBaseUrl(apiUrl); + final String simpleName = Trans.t2s(name == null ? "" : name); + final String simpleEpisode = Trans.t2s(episode == null ? "" : episode); + final String episodeQuery = getEpisodeQuery(simpleEpisode, queryMode); + final String searchUrl = baseUrl + "/api/v2/search/episodes?anime=" + encode(simpleName) + + (TextUtils.isEmpty(episodeQuery) ? "" : "&episode=" + encode(episodeQuery)); +// LOG.i("echo-danmaku builtin search episodes: " + searchUrl + ", retry=" + retry + ", mode=" + queryMode); + OkHttp.newCall(OkHttp.client(BUILTIN_TIMEOUT), searchUrl, TAG).enqueue(new Callback() { + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + if (!isCurrentSearch(seq)) return; + if (retry < BUILTIN_MAX_RETRY) { + LOG.e("echo-danmaku builtin search error: " + e.getMessage() + ", retry later"); + handler.postDelayed(new Runnable() { + @Override + public void run() { + if (isCurrentSearch(seq)) searchBuiltin(apiUrl, name, episode, callback, retry + 1, seq, queryMode); + } + }, 1500L * (retry + 1)); + } else { + LOG.e("echo-danmaku builtin search error: " + e.getMessage()); + searchBuiltinAnime(baseUrl, simpleName, simpleEpisode, callback, seq, true); + } + } + + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + if (!isCurrentSearch(seq)) return; + try { + String body = response.body() == null ? "" : response.body().string(); + EpisodeMatch episodeMatch = findEpisodeFromSearchEpisodes(body, simpleEpisode); + if (episodeMatch != null && !TextUtils.isEmpty(episodeMatch.id)) { + loadBuiltinComment(baseUrl, simpleName, simpleEpisode, episodeMatch, callback, seq); + return; + } + if (isSearchEpisodesMovieResult(body)) { + LOG.i("echo-danmaku builtin movie result not matched, skip anime fallback"); + notifyNotFound(callback, seq); + return; + } + if (tryNextEpisodeQuery(apiUrl, name, episode, callback, seq, queryMode)) return; + LOG.i("echo-danmaku builtin episode not matched, title: " + safeLog(simpleName) + ", episode: " + safeLog(simpleEpisode)); + searchBuiltinAnime(baseUrl, simpleName, simpleEpisode, callback, seq, true); + } catch (Throwable th) { + LOG.e("echo-danmaku builtin episode parse error: " + th.getMessage()); + if (tryNextEpisodeQuery(apiUrl, name, episode, callback, seq, queryMode)) return; + searchBuiltinAnime(baseUrl, simpleName, simpleEpisode, callback, seq, true); + } + } + }); + } + + private static boolean tryNextEpisodeQuery(String apiUrl, String name, String episode, SearchCallback callback, int seq, int queryMode) { + int nextMode = getNextEpisodeQueryMode(Trans.t2s(episode == null ? "" : episode), queryMode); + if (nextMode < 0) return false; +// LOG.i("echo-danmaku builtin retry episodes query mode: " + queryMode + " -> " + nextMode); + searchBuiltin(apiUrl, name, episode, callback, 0, seq, nextMode); + return true; + } + + private static void searchBuiltinAnime(String baseUrl, String name, String episode, SearchCallback callback, int seq, boolean notifyOnEmpty) { + final String searchUrl = baseUrl + "/api/v2/search/anime?keyword=" + encode(name); + LOG.i("echo-danmaku builtin search anime: " + searchUrl); + OkHttp.newCall(OkHttp.client(BUILTIN_TIMEOUT), searchUrl, TAG).enqueue(new Callback() { + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + if (!isCurrentSearch(seq)) return; + LOG.e("echo-danmaku builtin anime error: " + e.getMessage()); + if (notifyOnEmpty) notifyNotFound(callback, seq); + } + + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + if (!isCurrentSearch(seq)) return; + try { + String body = response.body() == null ? "" : response.body().string(); + String animeId = findAnimeId(body); + if (TextUtils.isEmpty(animeId)) { + if (notifyOnEmpty) notifyNotFound(callback, seq); + return; + } + loadBuiltinBangumi(baseUrl, animeId, episode, callback, seq); + } catch (Throwable th) { + LOG.e("echo-danmaku builtin anime parse error: " + th.getMessage()); + if (notifyOnEmpty) notifyNotFound(callback, seq); + } + } + }); + } + + private static void loadBuiltinBangumi(String baseUrl, String animeId, String episode, SearchCallback callback, int seq) { + final String bangumiUrl = baseUrl + "/api/v2/bangumi/" + animeId; +// LOG.i("echo-danmaku builtin bangumi: " + bangumiUrl); + OkHttp.newCall(OkHttp.client(BUILTIN_TIMEOUT), bangumiUrl, TAG).enqueue(new Callback() { + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + if (!isCurrentSearch(seq)) return; + LOG.e("echo-danmaku builtin bangumi error: " + e.getMessage()); + notifyNotFound(callback, seq); + } + + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + if (!isCurrentSearch(seq)) return; + try { + String body = response.body() == null ? "" : response.body().string(); + EpisodeMatch episodeMatch = findEpisode(body, episode); + if (episodeMatch != null && !TextUtils.isEmpty(episodeMatch.id)) { + loadBuiltinComment(baseUrl, "", episode, episodeMatch, callback, seq); + } else { +// LOG.i("echo-danmaku builtin bangumi episode not matched, episode: " + safeLog(episode)); + notifyNotFound(callback, seq); + } + } catch (Throwable th) { + LOG.e("echo-danmaku builtin bangumi parse error: " + th.getMessage()); + notifyNotFound(callback, seq); + } + } + }); + } + + private static void loadBuiltinComment(String baseUrl, String title, String episode, EpisodeMatch episodeMatch, SearchCallback callback, int seq) { + final String commentUrl = baseUrl + "/api/v2/comment/" + episodeMatch.id + "?format=json"; + LOG.i("echo-danmaku builtin load title: " + safeLog(title) + + ", request episode: " + safeLog(episode) + + ", matched episode: " + safeLog(episodeMatch.title) + + ", matched number: " + episodeMatch.number + + ", episodeId: " + episodeMatch.id); + LOG.i("echo-danmaku builtin comment: " + commentUrl); + OkHttp.newCall(OkHttp.client(BUILTIN_TIMEOUT), commentUrl, TAG).enqueue(new Callback() { + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + if (!isCurrentSearch(seq)) return; + LOG.e("echo-danmaku builtin comment error: " + e.getMessage()); + notifyNotFound(callback, seq); + } + + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + if (!isCurrentSearch(seq)) return; + try { + String body = response.body() == null ? "" : response.body().string(); + final String xml = commentJsonToXml(body); + if (TextUtils.isEmpty(xml) || !isCurrentSearch(seq)) { + notifyNotFound(callback, seq); + return; + } + handler.post(new Runnable() { + @Override + public void run() { + if (isCurrentSearch(seq)) callback.onFound(xml); + } + }); + } catch (Throwable th) { + LOG.e("echo-danmaku builtin comment parse error: " + th.getMessage()); + notifyNotFound(callback, seq); + } + } + }); + } + + private static void notifyNotFound(SearchCallback callback, int seq) { + if (callback == null || !isCurrentSearch(seq)) return; + handler.post(new Runnable() { + @Override + public void run() { + if (isCurrentSearch(seq)) callback.onNotFound(); + } + }); + } + + private static boolean isCurrentSearch(int seq) { + return seq == searchSeq.get(); + } + + private static Call newCall(String apiUrl, String name, String episode) { + name = Trans.t2s(name == null ? "" : name); + episode = Trans.t2s(episode == null ? "" : episode); + if (hasPlaceholder(apiUrl)) { + return OkHttp.newCall(apiUrl.replace("{name}", name).replace("{episode}", episode), TAG); + } + ArrayMap params = new ArrayMap<>(); + params.put("name", name); + params.put("episode", episode); + return OkHttp.newCall(apiUrl, OkHttp.toBody(params), TAG); + } + + private static String getApiUrl() { + if (isUseDefault()) return BUILTIN_API; + String custom = Hawk.get(HawkConfig.DANMU_API, ""); + if (!TextUtils.isEmpty(custom)) return custom.trim(); + String config = ApiConfig.get().getDanmaku().trim(); + if (!TextUtils.isEmpty(config)) return config; + return BUILTIN_API; + } + + private static boolean hasPlaceholder(String apiUrl) { + return !TextUtils.isEmpty(apiUrl) && (apiUrl.contains("{name}") || apiUrl.contains("{episode}")); + } + + private static boolean isDanmakuSearchApi(String apiUrl) { + String url = apiUrl == null ? "" : apiUrl.trim(); + while (url.endsWith("/")) url = url.substring(0, url.length() - 1); + return url.endsWith("/danmaku"); + } + + private static String normalizeBaseUrl(String apiUrl) { + String url = apiUrl == null ? "" : apiUrl.trim(); + if (url.endsWith("/")) url = url.substring(0, url.length() - 1); + if (url.endsWith("/87654321")) url = url.substring(0, url.length() - "/87654321".length()); + return url; + } + + private static String encode(String text) { + try { + return URLEncoder.encode(text == null ? "" : text, "UTF-8"); + } catch (Throwable th) { + return text == null ? "" : text; + } + } + + private static String getEpisodeQuery(String episode, int queryMode) { + if (queryMode == EPISODE_QUERY_EMPTY) return ""; + int number = extractNumber(episode); + return number > 0 ? String.valueOf(number) : ""; + } + + private static int getNextEpisodeQueryMode(String episode, int queryMode) { + if (queryMode == EPISODE_QUERY_NUMBER && !TextUtils.isEmpty(getEpisodeQuery(episode, EPISODE_QUERY_NUMBER))) { + return EPISODE_QUERY_EMPTY; + } + return -1; + } + + private static String findAnimeId(String body) throws Exception { + JSONObject object = new JSONObject(body); + JSONArray array = object.optJSONArray("animes"); + if (array == null) array = object.optJSONArray("anime"); + if (array == null) array = object.optJSONArray("data"); + if (array == null || array.length() <= 0) return ""; + JSONObject item = array.optJSONObject(0); + if (item == null) return ""; + String id = item.optString("animeId", ""); + if (TextUtils.isEmpty(id)) id = item.optString("id", ""); + return id; + } + + private static EpisodeMatch findEpisode(String body, String episode) throws Exception { + return findEpisode(body, episode, true); + } + + private static EpisodeMatch findEpisode(String body, String episode, boolean allowMovieFallback) throws Exception { + JSONObject object = new JSONObject(body); + EpisodeList episodeList = findEpisodeList(object); + JSONArray episodes = episodeList == null ? null : episodeList.episodes; + if (episodes == null || episodes.length() <= 0) return null; + int targetNumber = extractNumber(episode); + EpisodeMatch first = null; + EpisodeMatch firstMandarin = null; + for (int i = 0; i < episodes.length(); i++) { + JSONObject item = episodes.optJSONObject(i); + if (item == null) continue; + String id = firstString(item, "episodeId", "id"); + if (TextUtils.isEmpty(id)) continue; + String title = firstString(item, "episodeTitle", "title", "name"); + int number = parseEpisodeNumber(firstString(item, "episodeNumber", "number", "sort")); + EpisodeMatch match = new EpisodeMatch(id, title, number); + if (first == null) first = match; + if (firstMandarin == null && isMandarinTitle(title)) firstMandarin = match; + if (!TextUtils.isEmpty(episode) && !TextUtils.isEmpty(title) && title.contains(episode)) { + return match; + } + if (targetNumber > 0 && number == targetNumber) return match; + if (targetNumber > 0 && extractNumber(title) == targetNumber) return match; + } + if (TextUtils.isEmpty(episode)) return first; + if (allowMovieFallback && episodeList.isMovie) return firstMandarin == null ? first : firstMandarin; + return null; + } + + private static EpisodeMatch findEpisodeFromSearchEpisodes(String body, String episode) throws Exception { + EpisodeMatch match = findEpisode(body, episode, false); + if (match != null) return match; + EpisodeMatch movieFallback = findMovieFallback(new JSONObject(body), episode); + if (movieFallback != null) { + LOG.i("echo-danmaku episodes movie fallback episode: " + safeLog(movieFallback.title) + ", episodeId: " + movieFallback.id); + } + return movieFallback; + } + + private static JSONArray findEpisodes(JSONObject object) { + EpisodeList episodeList = findEpisodeList(object); + return episodeList == null ? null : episodeList.episodes; + } + + private static EpisodeList findEpisodeList(JSONObject object) { + JSONArray array = object.optJSONArray("episodes"); + if (array != null) return new EpisodeList(array, isMovieType(object)); + JSONObject bangumi = object.optJSONObject("bangumi"); + if (bangumi != null) { + array = bangumi.optJSONArray("episodes"); + if (array != null) return new EpisodeList(array, isMovieType(bangumi)); + } + JSONArray animes = object.optJSONArray("animes"); + if (animes == null) animes = object.optJSONArray("anime"); + if (animes == null) animes = object.optJSONArray("data"); + if (animes != null) { + for (int i = 0; i < animes.length(); i++) { + JSONObject item = animes.optJSONObject(i); + if (item == null) continue; + array = item.optJSONArray("episodes"); + if (array != null && array.length() > 0) return new EpisodeList(array, isMovieType(item)); + } + } + return null; + } + + private static boolean isMovieType(JSONObject object) { + if (object == null) return false; + if (isMovieTypeText(object.optString("type", ""))) return true; + if (isMovieTypeText(object.optString("typeDescription", ""))) return true; + JSONObject bangumi = object.optJSONObject("bangumi"); + if (bangumi != null && isMovieTypeText(bangumi.optString("type", ""))) return true; + JSONArray animes = object.optJSONArray("animes"); + if (animes == null) animes = object.optJSONArray("anime"); + if (animes == null) animes = object.optJSONArray("data"); + if (animes == null) return false; + for (int i = 0; i < animes.length(); i++) { + JSONObject item = animes.optJSONObject(i); + if (item != null && isMovieTypeText(item.optString("type", ""))) return true; + } + return false; + } + + private static boolean isMovieTypeText(String type) { + return "\u7535\u5f71".equals(type); + } + + private static boolean isMandarinTitle(String title) { + return !TextUtils.isEmpty(title) && (title.contains("\u56fd\u8bed") || title.contains("\u666e\u901a\u8bdd")); + } + + private static boolean isCantoneseTitle(String title) { + return !TextUtils.isEmpty(title) && title.contains("\u7ca4\u8bed"); + } + + private static boolean prefersCantonese(String episode) { + return isCantoneseTitle(episode); + } + + private static boolean isPreferredLanguageTitle(String title, boolean preferCantonese) { + return preferCantonese ? isCantoneseTitle(title) : isMandarinTitle(title); + } + + private static boolean isSearchEpisodesMovieResult(String body) { + try { + JSONObject object = new JSONObject(body); + JSONArray animes = object.optJSONArray("animes"); + if (animes == null) animes = object.optJSONArray("anime"); + if (animes == null) animes = object.optJSONArray("data"); + if (animes == null) return false; + for (int i = 0; i < animes.length(); i++) { + JSONObject anime = animes.optJSONObject(i); + if (anime != null && isMovieType(anime) && anime.optJSONArray("episodes") != null) return true; + } + } catch (Throwable ignored) { + } + return false; + } + + private static EpisodeMatch findMovieFallback(JSONObject object, String episode) { + JSONArray animes = object.optJSONArray("animes"); + if (animes == null) animes = object.optJSONArray("anime"); + if (animes == null) animes = object.optJSONArray("data"); + if (animes == null) return null; + boolean preferCantonese = prefersCantonese(episode); + EpisodeMatch firstMovie = null; + for (int i = 0; i < animes.length(); i++) { + JSONObject anime = animes.optJSONObject(i); + if (anime == null || !isMovieType(anime)) continue; + JSONArray episodes = anime.optJSONArray("episodes"); + if (episodes == null) continue; + for (int j = 0; j < episodes.length(); j++) { + JSONObject item = episodes.optJSONObject(j); + if (item == null) continue; + String id = firstString(item, "episodeId", "id"); + if (TextUtils.isEmpty(id)) continue; + String title = firstString(item, "episodeTitle", "title", "name"); + EpisodeMatch match = new EpisodeMatch(id, title, parseEpisodeNumber(firstString(item, "episodeNumber", "number", "sort"))); + if (firstMovie == null) firstMovie = match; + if (isPreferredLanguageTitle(title, preferCantonese)) return match; + } + } + return firstMovie; + } + + private static int parseEpisodeNumber(String value) { + try { + if (TextUtils.isEmpty(value)) return -1; + return (int) Float.parseFloat(value); + } catch (Throwable th) { + return extractNumber(value); + } + } + + private static int extractNumber(String text) { + if (TextUtils.isEmpty(text)) return -1; + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < text.length(); i++) { + char ch = text.charAt(i); + if (Character.isDigit(ch)) builder.append(ch); + } + if (builder.length() == 0) return -1; + try { + return Integer.parseInt(builder.toString()); + } catch (Throwable th) { + return -1; + } + } + + private static String commentJsonToXml(String body) throws Exception { + JSONObject object = new JSONObject(body); + JSONArray comments = object.optJSONArray("comments"); + if (comments == null) comments = object.optJSONArray("data"); + if (comments == null || comments.length() <= 0) return ""; + StringBuilder builder = new StringBuilder(); + builder.append(""); + for (int i = 0; i < comments.length(); i++) { + JSONObject item = comments.optJSONObject(i); + if (item == null) continue; + String param = item.optString("p", ""); + String text = item.optString("m", ""); + if (TextUtils.isEmpty(text)) text = item.optString("text", ""); + if (TextUtils.isEmpty(param) || TextUtils.isEmpty(text)) continue; + builder.append("") + .append(escapeXml(text)).append(""); + } + builder.append(""); + String xml = builder.toString(); + LOG.i("echo-danmaku builtin xml length: " + xml.length()); + return xml; + } + + private static String normalizeDanmakuParam(String param) { + String[] values = param.split(","); + if (values.length < 4) return param; + String time = values[0]; + String type = values[1]; + String size = values[2]; + String color = values[3]; + if (isColorValue(size)) { + color = size; + size = "25"; + } else if (!isColorValue(color)) { + color = "16777215"; + } + return time + "," + type + "," + size + "," + normalizeColor(color); + } + + private static boolean isColorValue(String value) { + try { + if (TextUtils.isEmpty(value)) return false; + String text = value.trim(); + if (text.startsWith("#")) return true; + if (text.startsWith("0x") || text.startsWith("0X")) return true; + long color = Long.parseLong(text); + return color >= 0 && color <= 0x00ffffffL; + } catch (Throwable th) { + return false; + } + } + + private static String normalizeColor(String color) { + if (TextUtils.isEmpty(color)) return "16777215"; + String text = color.trim(); + try { + if (text.startsWith("#")) return String.valueOf(Long.parseLong(text.substring(1), 16)); + if (text.startsWith("0x") || text.startsWith("0X")) return String.valueOf(Long.parseLong(text.substring(2), 16)); + } catch (Throwable ignored) { + } + return text; + } + + private static String firstString(JSONObject object, String... keys) { + for (String key : keys) { + String value = object.optString(key, ""); + if (!TextUtils.isEmpty(value)) return value; + } + return ""; + } + + private static String safeLog(String text) { + return TextUtils.isEmpty(text) ? "" : text; + } + + private static class EpisodeList { + final JSONArray episodes; + final boolean isMovie; + + EpisodeList(JSONArray episodes, boolean isMovie) { + this.episodes = episodes; + this.isMovie = isMovie; + } + } + + private static class EpisodeMatch { + final String id; + final String title; + final int number; + + EpisodeMatch(String id, String title, int number) { + this.id = id; + this.title = title; + this.number = number; + } + } + + private static String escapeXml(String text) { + if (TextUtils.isEmpty(text)) return ""; + return text.replace("&", "&") + .replace("\"", """) + .replace("'", "'") + .replace(">", ">") + .replace("<", "<"); + } + + private static String parseUrl(String body) throws Exception { + if (TextUtils.isEmpty(body)) return ""; + String text = body.trim(); + if (text.startsWith("[")) { + JSONArray array = new JSONArray(text); + for (int i = 0; i < array.length(); i++) { + JSONObject object = array.optJSONObject(i); + if (object == null) continue; + String url = object.optString("url", "").trim(); + if (!TextUtils.isEmpty(url)) return url; + } + } else if (text.startsWith("{")) { + return new JSONObject(text).optString("url", "").trim(); + } else if (text.startsWith("http") || text.startsWith("file")) { + return text; + } + return ""; + } +} diff --git a/app/src/main/java/com/github/tvbox/osc/base/App.java b/app/src/main/java/com/github/tvbox/osc/base/App.java index 6cbded39ea..56eccca9d0 100644 --- a/app/src/main/java/com/github/tvbox/osc/base/App.java +++ b/app/src/main/java/com/github/tvbox/osc/base/App.java @@ -1,18 +1,26 @@ package com.github.tvbox.osc.base; +import android.app.Activity; import androidx.multidex.MultiDexApplication; +import com.github.tvbox.osc.bean.VodInfo; import com.github.tvbox.osc.callback.EmptyCallback; import com.github.tvbox.osc.callback.LoadingCallback; import com.github.tvbox.osc.data.AppDataManager; import com.github.tvbox.osc.server.ControlManager; +import com.github.tvbox.osc.util.AppManager; +import com.github.tvbox.osc.util.EpgUtil; +import com.github.tvbox.osc.util.FileUtils; import com.github.tvbox.osc.util.HawkConfig; +import com.github.tvbox.osc.util.LOG; import com.github.tvbox.osc.util.OkGoHelper; import com.github.tvbox.osc.util.PlayerHelper; import com.kingja.loadsir.core.LoadSir; import com.orhanobut.hawk.Hawk; +import com.p2p.P2PClass; +import com.whl.quickjs.android.QuickJSLoader; +import com.github.catvod.crawler.JsLoader; -import me.jessyan.autosize.AutoSize; import me.jessyan.autosize.AutoSizeConfig; import me.jessyan.autosize.unit.Subunits; @@ -24,13 +32,18 @@ public class App extends MultiDexApplication { private static App instance; + private static P2PClass p; + public static String burl; + private static String dashData; + @Override public void onCreate() { super.onCreate(); instance = this; initParams(); // OKGo - OkGoHelper.init(); + OkGoHelper.init(); //台标获取 + EpgUtil.init(); // 初始化Web服务器 ControlManager.init(this); //初始化数据库 @@ -44,6 +57,8 @@ public void onCreate() { .setSupportSP(false) .setSupportSubunits(Subunits.MM); PlayerHelper.init(); + QuickJSLoader.init(); + FileUtils.cleanPlayerCache(); } private void initParams() { @@ -58,4 +73,42 @@ private void initParams() { public static App getInstance() { return instance; } + + @Override + public void onTerminate() { + super.onTerminate(); + JsLoader.destroy(); + } + + + private VodInfo vodInfo; + public void setVodInfo(VodInfo vodinfo){ + this.vodInfo = vodinfo; + } + public VodInfo getVodInfo(){ + return this.vodInfo; + } + + public static P2PClass getp2p() { + try { + if (p == null) { + p = new P2PClass(FileUtils.getExternalCachePath()); + } + return p; + } catch (Exception e) { + LOG.e(e.toString()); + return null; + } + } + + public Activity getCurrentActivity() { + return AppManager.getInstance().currentActivity(); + } + + public void setDashData(String data) { + dashData = data; + } + public String getDashData() { + return dashData; + } } \ No newline at end of file diff --git a/app/src/main/java/com/github/tvbox/osc/base/BaseActivity.java b/app/src/main/java/com/github/tvbox/osc/base/BaseActivity.java index 4b48f2e5d1..b500058cfe 100644 --- a/app/src/main/java/com/github/tvbox/osc/base/BaseActivity.java +++ b/app/src/main/java/com/github/tvbox/osc/base/BaseActivity.java @@ -29,8 +29,10 @@ import java.io.IOException; import java.io.InputStreamReader; +import me.jessyan.autosize.AutoSizeConfig; import me.jessyan.autosize.AutoSizeCompat; import me.jessyan.autosize.internal.CustomAdapt; +import xyz.doikki.videoplayer.util.CutoutUtil; /** * @author pj567 @@ -42,6 +44,18 @@ public abstract class BaseActivity extends AppCompatActivity implements CustomAd private LoadService mLoadService; private static float screenRatio = -100.0f; + private final Runnable refreshAutoSizeRunnable = new Runnable() { + @Override + public void run() { + refreshAutoSize(); + } + }; + private final Runnable hideSysBarRunnable = new Runnable() { + @Override + public void run() { + hideSysBar(); + } + }; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { @@ -49,9 +63,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { if (screenRatio < 0) { DisplayMetrics dm = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(dm); - int screenWidth = dm.widthPixels; - int screenHeight = dm.heightPixels; - screenRatio = (float) Math.max(screenWidth, screenHeight) / (float) Math.min(screenWidth, screenHeight); + updateScreenRatio(dm); } } catch (Throwable th) { th.printStackTrace(); @@ -59,6 +71,8 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(getLayoutResID()); mContext = this; + initSystemUiListener(); + CutoutUtil.adaptCutoutAboveAndroidP(mContext, true);//设置刘海 AppManager.getInstance().addActivity(this); init(); } @@ -68,6 +82,8 @@ protected void onResume() { super.onResume(); hideSysBar(); changeWallpaper(false); + refreshAutoSize(); + scheduleRefreshAutoSize(); } public void hideSysBar() { @@ -83,6 +99,70 @@ public void hideSysBar() { } } + private void initSystemUiListener() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + final View decorView = getWindow().getDecorView(); + decorView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() { + @Override + public void onSystemUiVisibilityChange(int visibility) { + if ((visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) { + decorView.removeCallbacks(hideSysBarRunnable); + decorView.postDelayed(hideSysBarRunnable, 300); + } + } + }); + } + } + + @Override + protected void onPause() { + super.onPause(); + getWindow().getDecorView().removeCallbacks(refreshAutoSizeRunnable); + getWindow().getDecorView().removeCallbacks(hideSysBarRunnable); + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + if (hasFocus) { + hideSysBar(); + scheduleRefreshAutoSize(); + } + } + + private void scheduleRefreshAutoSize() { + View decorView = getWindow().getDecorView(); + decorView.removeCallbacks(refreshAutoSizeRunnable); + decorView.postDelayed(refreshAutoSizeRunnable, 300); + } + + private void refreshAutoSize() { + try { + DisplayMetrics dm = new DisplayMetrics(); + getWindowManager().getDefaultDisplay().getMetrics(dm); + if (dm.widthPixels <= 0 || dm.heightPixels <= 0) { + return; + } + updateScreenRatio(dm); + AutoSizeConfig.getInstance() + .setScreenWidth(dm.widthPixels) + .setScreenHeight(dm.heightPixels); + AutoSizeCompat.autoConvertDensityOfCustomAdapt(super.getResources(), this); + getWindow().getDecorView().requestLayout(); + } catch (Throwable th) { + th.printStackTrace(); + } + } + + private void updateScreenRatio(DisplayMetrics dm) { + int screenWidth = dm.widthPixels; + int screenHeight = dm.heightPixels; + int min = Math.min(screenWidth, screenHeight); + if (min > 0) { + screenRatio = (float) Math.max(screenWidth, screenHeight) / (float) min; + } + } + @Override public Resources getResources() { if (Looper.myLooper() == Looper.getMainLooper()) { @@ -121,6 +201,13 @@ protected void showLoading() { } } + protected boolean isLoading() { + if (mLoadService != null && mLoadService.getCurrentCallback() != null) { + return mLoadService.getCurrentCallback().equals(LoadingCallback.class); + } + return false; + } + protected void showEmpty() { if (null != mLoadService) { mLoadService.showCallback(EmptyCallback.class); @@ -205,6 +292,8 @@ public void changeWallpaper(boolean force) { // 采样率 opts.inSampleSize = scale; globalWp = new BitmapDrawable(BitmapFactory.decodeFile(wp.getAbsolutePath(), opts)); + } else { + globalWp = null; } } catch (Throwable throwable) { throwable.printStackTrace(); @@ -215,4 +304,4 @@ public void changeWallpaper(boolean force) { else getWindow().setBackgroundDrawableResource(R.drawable.app_bg); } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/github/tvbox/osc/base/BaseLazyFragment.java b/app/src/main/java/com/github/tvbox/osc/base/BaseLazyFragment.java index 36752f2685..a17a44412d 100644 --- a/app/src/main/java/com/github/tvbox/osc/base/BaseLazyFragment.java +++ b/app/src/main/java/com/github/tvbox/osc/base/BaseLazyFragment.java @@ -255,6 +255,14 @@ public void onReload(View v) { } } + protected void setLoadSir2(View view) { + mLoadService = LoadSir.getDefault().register(view, new Callback.OnReloadListener() { + @Override + public void onReload(View v) { + } + }); + } + protected void showLoading() { if (mLoadService != null) { mLoadService.showCallback(LoadingCallback.class); diff --git a/app/src/main/java/com/github/tvbox/osc/bean/AbsJson.java b/app/src/main/java/com/github/tvbox/osc/bean/AbsJson.java index 961face383..fd5db3c1b0 100644 --- a/app/src/main/java/com/github/tvbox/osc/bean/AbsJson.java +++ b/app/src/main/java/com/github/tvbox/osc/bean/AbsJson.java @@ -14,7 +14,7 @@ public class AbsJson implements Serializable { public int code; // : 1 public String limit; // : "20" public ArrayList list; // : [{vod_id: 71930, type_id: 22, type_id_1: 20, group_id: 0, vod_name: "意式情歌",…},…] - public String msg; // : "数据列表" + public String msg; // : "提示信息" public int page; // : "2" public int pagecount; // : 209 public int total; // : 4166 @@ -104,9 +104,12 @@ public class AbsJsonVod implements Serializable { public String vod_weekday; //: "" public String vod_writer; //: "周炎青,刘恒,支雅雪,孙露军,李璐,王梦璇" public String vod_year; //: "2021" + public String action; public Movie.Video toXmlVideo() { Movie.Video video = new Movie.Video(); + video.tag = vod_tag; + video.action = action; video.last = vod_time; video.id = vod_id; video.tid = type_id; @@ -130,13 +133,12 @@ public Movie.Video toXmlVideo() { String[] playFlags = vod_play_from.split("\\$\\$\\$"); String[] playUrls = vod_play_url.split("\\$\\$\\$"); List infoList = new ArrayList<>(); - for (int i = 0; i < playFlags.length; i++) { + for (int i = 0; i < playFlags.length && i < playUrls.length; i++) { + if (playFlags[i].trim().isEmpty() || playUrls[i].trim().isEmpty()) + continue; Movie.Video.UrlBean.UrlInfo urlInfo = new Movie.Video.UrlBean.UrlInfo(); - urlInfo.flag = playFlags[i]; - if (i < playUrls.length) - urlInfo.urls = playUrls[i]; - else - urlInfo.urls = ""; + urlInfo.flag = playFlags[i].trim(); + urlInfo.urls = playUrls[i]; infoList.add(urlInfo); } urlBean.infoList = infoList; @@ -168,6 +170,7 @@ public AbsXml toAbsXml() { } movie.videoList = videoList; xml.movie = movie; + xml.msg = msg; return xml; } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/github/tvbox/osc/bean/AbsSortJson.java b/app/src/main/java/com/github/tvbox/osc/bean/AbsSortJson.java index c51e1d636d..e4f2188d7b 100644 --- a/app/src/main/java/com/github/tvbox/osc/bean/AbsSortJson.java +++ b/app/src/main/java/com/github/tvbox/osc/bean/AbsSortJson.java @@ -17,10 +17,17 @@ public AbsSortXml toAbsSortXml() { AbsSortXml absSortXml = new AbsSortXml(); MovieSort movieSort = new MovieSort(); movieSort.sortList = new ArrayList<>(); + if (classes == null) { + classes = new ArrayList<>(); + } for (AbsJsonClass cls : classes) { + if (cls == null || cls.type_id == null || cls.type_name == null) { + continue; + } MovieSort.SortData sortData = new MovieSort.SortData(); sortData.id = cls.type_id; sortData.name = cls.type_name; + sortData.flag = cls.type_flag; movieSort.sortList.add(sortData); } if (list != null && !list.isEmpty()) { @@ -39,8 +46,11 @@ public AbsSortXml toAbsSortXml() { } public class AbsJsonClass implements Serializable { + @SerializedName(value = "type_id", alternate = "id") public String type_id; + @SerializedName(value = "type_name", alternate = "name") public String type_name; + public String type_flag; } } diff --git a/app/src/main/java/com/github/tvbox/osc/bean/AbsSortXml.java b/app/src/main/java/com/github/tvbox/osc/bean/AbsSortXml.java index 2a318a24d9..39b4741263 100644 --- a/app/src/main/java/com/github/tvbox/osc/bean/AbsSortXml.java +++ b/app/src/main/java/com/github/tvbox/osc/bean/AbsSortXml.java @@ -12,6 +12,8 @@ */ @XStreamAlias("rss") public class AbsSortXml implements Serializable { + public String sourceKey; + @XStreamAlias("class") public MovieSort classes; diff --git a/app/src/main/java/com/github/tvbox/osc/bean/AbsXml.java b/app/src/main/java/com/github/tvbox/osc/bean/AbsXml.java index ebcbbeae00..fc4504ab6c 100644 --- a/app/src/main/java/com/github/tvbox/osc/bean/AbsXml.java +++ b/app/src/main/java/com/github/tvbox/osc/bean/AbsXml.java @@ -11,6 +11,12 @@ */ @XStreamAlias("rss") public class AbsXml implements Serializable { + public String sourceKey; + public String searchToken; + @XStreamAlias("list") public Movie movie; -} \ No newline at end of file + + @XStreamAlias("msg") + public String msg; +} diff --git a/app/src/main/java/com/github/tvbox/osc/bean/Danmu.java b/app/src/main/java/com/github/tvbox/osc/bean/Danmu.java new file mode 100644 index 0000000000..cd8865cd3c --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/bean/Danmu.java @@ -0,0 +1,143 @@ +package com.github.tvbox.osc.bean; + +import android.text.TextUtils; +import android.util.Xml; + +import org.xmlpull.v1.XmlPullParser; + +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Danmu { + private static final Pattern D_TAG_PATTERN = Pattern.compile( + "]*\\bp\\s*=\\s*(['\"])(.*?)\\1[^>]*>(.*?)", + Pattern.CASE_INSENSITIVE | Pattern.DOTALL); + + private final List data = new ArrayList<>(); + + public static Danmu fromXml(String xml) { + Danmu danmu = new Danmu(); + if (TextUtils.isEmpty(xml)) return danmu; + String fixedXml = escapeIllegalEntities(xml); + if (parseByXmlPull(danmu, fixedXml)) return danmu; + danmu.data.clear(); + parseByTag(danmu, xml); + return danmu; + } + + private static boolean parseByXmlPull(Danmu danmu, String xml) { + try { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(new StringReader(xml)); + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG && "d".equals(parser.getName())) { + String param = parser.getAttributeValue(null, "p"); + String text = parser.nextText(); + if (!TextUtils.isEmpty(param) && !TextUtils.isEmpty(text)) { + danmu.data.add(new Data(param, text)); + } + } + eventType = parser.next(); + } + return !danmu.data.isEmpty(); + } catch (Throwable th) { + return false; + } + } + + private static void parseByTag(Danmu danmu, String xml) { + Matcher matcher = D_TAG_PATTERN.matcher(xml); + while (matcher.find()) { + String param = decodeXmlString(matcher.group(2)); + String text = matcher.group(3); + if (!TextUtils.isEmpty(param) && !TextUtils.isEmpty(text)) { + danmu.data.add(new Data(param, text)); + } + } + } + + private static String escapeIllegalEntities(String xml) { + StringBuilder builder = new StringBuilder(xml.length()); + int length = xml.length(); + for (int i = 0; i < length; i++) { + char ch = xml.charAt(i); + if (ch == '&' && !isLegalEntity(xml, i + 1)) { + builder.append("&"); + } else { + builder.append(ch); + } + } + return builder.toString(); + } + + private static boolean isLegalEntity(String text, int start) { + int end = text.indexOf(';', start); + if (end < 0 || end - start > 10) return false; + String entity = text.substring(start, end); + if ("amp".equals(entity) || "lt".equals(entity) || "gt".equals(entity) + || "quot".equals(entity) || "apos".equals(entity)) { + return true; + } + if (entity.startsWith("#x") || entity.startsWith("#X")) { + return isHexNumber(entity, 2); + } + return entity.startsWith("#") && isDecimalNumber(entity, 1); + } + + private static boolean isDecimalNumber(String text, int start) { + if (text.length() <= start) return false; + for (int i = start; i < text.length(); i++) { + if (!Character.isDigit(text.charAt(i))) return false; + } + return true; + } + + private static boolean isHexNumber(String text, int start) { + if (text.length() <= start) return false; + for (int i = start; i < text.length(); i++) { + char ch = text.charAt(i); + if (!Character.isDigit(ch) + && (ch < 'a' || ch > 'f') + && (ch < 'A' || ch > 'F')) { + return false; + } + } + return true; + } + + private static String decodeXmlString(String text) { + if (TextUtils.isEmpty(text)) return ""; + return text.replace("&", "&") + .replace(""", "\"") + .replace("'", "'") + .replace(">", ">") + .replace("<", "<"); + } + + public List getData() { + return data.isEmpty() ? Collections.emptyList() : data; + } + + public static class Data { + private final String param; + private final String text; + + Data(String param, String text) { + this.param = param; + this.text = text; + } + + public String getParam() { + return TextUtils.isEmpty(param) ? "" : param; + } + + public String getText() { + return TextUtils.isEmpty(text) ? "" : text; + } + } +} diff --git a/app/src/main/java/com/github/tvbox/osc/bean/Epginfo.java b/app/src/main/java/com/github/tvbox/osc/bean/Epginfo.java new file mode 100644 index 0000000000..2aa263d0c5 --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/bean/Epginfo.java @@ -0,0 +1,54 @@ +package com.github.tvbox.osc.bean; + +import com.github.tvbox.osc.util.HawkConfig; +import com.orhanobut.hawk.Hawk; + +import java.text.ParsePosition; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.ArrayList; +import java.util.Date; +import java.util.TimeZone; + +public class Epginfo { + + public Date startdateTime; + public Date enddateTime; + public int datestart; + public int dateend; + public String title; + public String originStart; + public String originEnd; + public String start; + public String end; + public int index; + public Date epgDate; + public String currentEpgDate = null; + SimpleDateFormat timeFormat = new SimpleDateFormat("yyyy-MM-dd"); + + public Epginfo(Date Date,String str, Date date, String str1, String str2,int pos) { + epgDate = Date; + currentEpgDate = timeFormat.format(epgDate); + title = str; + originStart = str1; + originEnd = str2; + index = pos; + SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); + simpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT+8:00")); + SimpleDateFormat userSimpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z"); + userSimpleDateFormat.setTimeZone(TimeZone.getDefault()); + startdateTime = userSimpleDateFormat.parse(simpleDateFormat.format(date) + " " + str1 + ":00 GMT+8:00", new ParsePosition(0)); + enddateTime = userSimpleDateFormat.parse(simpleDateFormat.format(date) + " " + str2 + ":00 GMT+8:00", new ParsePosition(0)); + if (startdateTime != null && enddateTime != null && !enddateTime.after(startdateTime)) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(enddateTime); + calendar.add(Calendar.DAY_OF_MONTH, 1); + enddateTime = calendar.getTime(); + } + SimpleDateFormat zoneFormat = new SimpleDateFormat("HH:mm"); + start = zoneFormat.format(startdateTime); + end = zoneFormat.format(enddateTime); + datestart = Integer.parseInt(start.replace(":", "")); + dateend = Integer.parseInt(end.replace(":", "")); + } +} diff --git a/app/src/main/java/com/github/tvbox/osc/bean/IpScanningVo.java b/app/src/main/java/com/github/tvbox/osc/bean/IpScanningVo.java new file mode 100644 index 0000000000..c1cd746002 --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/bean/IpScanningVo.java @@ -0,0 +1,28 @@ +package com.github.tvbox.osc.bean; + +public class IpScanningVo { + + private String hostName; + private String ip; + + public IpScanningVo(String hostName, String ip) { + this.hostName = hostName; + this.ip = ip; + } + + public String getHostName() { + return hostName; + } + + public void setHostName(String hostName) { + this.hostName = hostName; + } + + public String getIp() { + return ip; + } + + public void setIp(String ip) { + this.ip = ip; + } +} diff --git a/app/src/main/java/com/github/tvbox/osc/bean/LiveChannelItem.java b/app/src/main/java/com/github/tvbox/osc/bean/LiveChannelItem.java index bb567305e5..aad4f08326 100644 --- a/app/src/main/java/com/github/tvbox/osc/bean/LiveChannelItem.java +++ b/app/src/main/java/com/github/tvbox/osc/bean/LiveChannelItem.java @@ -1,6 +1,11 @@ package com.github.tvbox.osc.bean; +import com.google.gson.JsonObject; + import java.util.ArrayList; +import java.util.HashMap; +import java.util.Objects; +import java.util.Map; /** * @author pj567 @@ -19,10 +24,31 @@ public class LiveChannelItem { private int channelIndex; private int channelNum; private String channelName; + private String channelLogo; + private String channelEpg; + private String channelUa; + private String channelClick; + private String channelFormat; + private String channelOrigin; + private String channelReferer; + private String channelTvgId; + private String channelTvgName; + private JsonObject channelCatchup; + private Map channelHeader; + private Integer channelParse; private ArrayList channelSourceNames; private ArrayList channelUrls; public int sourceIndex = 0; public int sourceNum = 0; + public boolean include_back = false; + + public void setinclude_back(boolean include_back) { + this.include_back = include_back; + } + + public boolean getinclude_back() { + return include_back; + } public void setChannelIndex(int channelIndex) { this.channelIndex = channelIndex; @@ -48,6 +74,114 @@ public String getChannelName() { return channelName; } + public void setChannelLogo(String channelLogo) { + this.channelLogo = channelLogo; + } + + public String getChannelLogo() { + return channelLogo == null ? "" : channelLogo; + } + + public void setChannelEpg(String channelEpg) { + this.channelEpg = channelEpg; + } + + public String getChannelEpg() { + return channelEpg == null ? "" : channelEpg; + } + + public void setChannelUa(String channelUa) { + this.channelUa = channelUa; + } + + public String getChannelUa() { + return channelUa == null ? "" : channelUa; + } + + public void setChannelClick(String channelClick) { + this.channelClick = channelClick; + } + + public String getChannelClick() { + return channelClick == null ? "" : channelClick; + } + + public void setChannelFormat(String channelFormat) { + this.channelFormat = channelFormat; + } + + public String getChannelFormat() { + return channelFormat == null ? "" : channelFormat; + } + + public void setChannelOrigin(String channelOrigin) { + this.channelOrigin = channelOrigin; + } + + public String getChannelOrigin() { + return channelOrigin == null ? "" : channelOrigin; + } + + public void setChannelReferer(String channelReferer) { + this.channelReferer = channelReferer; + } + + public String getChannelReferer() { + return channelReferer == null ? "" : channelReferer; + } + + public void setChannelTvgId(String channelTvgId) { + this.channelTvgId = channelTvgId; + } + + public String getChannelTvgId() { + return channelTvgId == null ? "" : channelTvgId; + } + + public void setChannelTvgName(String channelTvgName) { + this.channelTvgName = channelTvgName; + } + + public String getChannelTvgName() { + return channelTvgName == null ? "" : channelTvgName; + } + + public void setChannelCatchup(JsonObject channelCatchup) { + this.channelCatchup = channelCatchup; + } + + public JsonObject getChannelCatchup() { + return channelCatchup == null ? new JsonObject() : channelCatchup; + } + + public boolean hasCatchup() { + return channelCatchup != null && channelCatchup.entrySet().size() > 0; + } + + public void setChannelHeader(Map channelHeader) { + this.channelHeader = channelHeader; + } + + public Map getChannelHeader() { + return channelHeader == null ? new HashMap() : channelHeader; + } + + public void setChannelParse(Integer channelParse) { + this.channelParse = channelParse; + } + + public int getChannelParse() { + return channelParse == null ? 0 : channelParse.intValue(); + } + + public Map getHeaders() { + Map headers = new HashMap<>(getChannelHeader()); + if (!getChannelUa().isEmpty()) headers.put("User-Agent", getChannelUa()); + if (!getChannelOrigin().isEmpty()) headers.put("Origin", getChannelOrigin()); + if (!getChannelReferer().isEmpty()) headers.put("Referer", getChannelReferer()); + return headers; + } + public ArrayList getChannelUrls() { return channelUrls; } @@ -92,4 +226,22 @@ public void setChannelSourceNames(ArrayList channelSourceNames) { public String getSourceName() { return channelSourceNames.get(sourceIndex); } + + public boolean isEmptyCatchup() { + return channelCatchup == null || channelCatchup.entrySet().size() == 0; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + LiveChannelItem that = (LiveChannelItem) o; + return Objects.equals(channelName, that.channelName) + && Objects.equals(channelUrls.get(sourceIndex), that.getUrl()); + } + + @Override + public int hashCode() { + return Objects.hash(channelName, channelUrls.get(sourceIndex)); + } } \ No newline at end of file diff --git a/app/src/main/java/com/github/tvbox/osc/bean/LiveDayListGroup.java b/app/src/main/java/com/github/tvbox/osc/bean/LiveDayListGroup.java new file mode 100644 index 0000000000..abe652731a --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/bean/LiveDayListGroup.java @@ -0,0 +1,24 @@ +package com.github.tvbox.osc.bean; + +public class LiveDayListGroup { + private int groupIndex; + private String groupName; + + + public int getGroupIndex() { + return groupIndex; + } + + public void setGroupIndex(int groupIndex) { + this.groupIndex = groupIndex; + } + + public String getGroupName() { + return groupName; + } + + public void setGroupName(String groupName) { + this.groupName = groupName; + } + +} diff --git a/app/src/main/java/com/github/tvbox/osc/bean/LiveEpgDate.java b/app/src/main/java/com/github/tvbox/osc/bean/LiveEpgDate.java new file mode 100644 index 0000000000..cbf63c3348 --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/bean/LiveEpgDate.java @@ -0,0 +1,34 @@ +package com.github.tvbox.osc.bean; +import java.util.ArrayList; +import java.util.Date; + +public class LiveEpgDate { + + private int index; + private String datePresented; + private Date dateParamVal; + + public int getIndex() { + return index; + } + + public void setIndex(int index) { + this.index = index; + } + + public String getDatePresented() { + return datePresented; + } + + public void setDatePresented(String datePresented) { + this.datePresented = datePresented; + } + + public Date getDateParamVal() { + return dateParamVal; + } + + public void setDateParamVal(Date dateParamVal) { + this.dateParamVal = dateParamVal; + } +} diff --git a/app/src/main/java/com/github/tvbox/osc/bean/LivePlayerManager.java b/app/src/main/java/com/github/tvbox/osc/bean/LivePlayerManager.java index 1c4b23e44a..3d0d9e8f0f 100644 --- a/app/src/main/java/com/github/tvbox/osc/bean/LivePlayerManager.java +++ b/app/src/main/java/com/github/tvbox/osc/bean/LivePlayerManager.java @@ -1,31 +1,29 @@ package com.github.tvbox.osc.bean; import androidx.annotation.NonNull; -import androidx.exifinterface.media.ExifInterface; -import com.github.tvbox.osc.api.ApiConfig; import com.github.tvbox.osc.util.HawkConfig; +import com.github.tvbox.osc.util.LOG; import com.github.tvbox.osc.util.PlayerHelper; import com.orhanobut.hawk.Hawk; import org.json.JSONException; import org.json.JSONObject; -import java.util.HashMap; -import java.util.Objects; - import xyz.doikki.videoplayer.player.VideoView; public class LivePlayerManager { JSONObject defaultPlayerConfig = new JSONObject(); JSONObject currentPlayerConfig; + private String currentApi=""; public void init(VideoView videoView) { try { - defaultPlayerConfig.put("pl", Hawk.get(HawkConfig.PLAY_TYPE, 0)); - defaultPlayerConfig.put("ijk", Hawk.get(HawkConfig.IJK_CODEC, "软解码")); + currentApi=Hawk.get(HawkConfig.LIVE_API_URL,""); + defaultPlayerConfig.put("pl", Hawk.get(HawkConfig.LIVE_PLAY_TYPE, Hawk.get(HawkConfig.PLAY_TYPE, 0))); + defaultPlayerConfig.put("ijk", Hawk.get(HawkConfig.IJK_CODEC, "硬解码")); defaultPlayerConfig.put("pr", Hawk.get(HawkConfig.PLAY_RENDER, 0)); - defaultPlayerConfig.put("sc", Hawk.get(HawkConfig.PLAY_SCALE, 0)); + defaultPlayerConfig.put("sc", Hawk.get(HawkConfig.LIVE_PLAY_SCALE, 0)); } catch (JSONException e) { e.printStackTrace(); } @@ -42,12 +40,25 @@ public void getDefaultLiveChannelPlayer(VideoView videoView) { } public void getLiveChannelPlayer(VideoView videoView, String channelName) { + channelName=currentCfgKey(channelName); JSONObject playerConfig = Hawk.get(channelName, null); if (playerConfig == null) { + try { + defaultPlayerConfig.put("sc", Hawk.get(HawkConfig.LIVE_PLAY_SCALE, 0)); + } catch (JSONException e) { + e.printStackTrace(); + } if (!currentPlayerConfig.toString().equals(defaultPlayerConfig.toString())) getDefaultLiveChannelPlayer(videoView); + else + videoView.setScreenScaleType(Hawk.get(HawkConfig.LIVE_PLAY_SCALE, 0)); return; } + try { + playerConfig.put("sc", Hawk.get(HawkConfig.LIVE_PLAY_SCALE, 0)); + } catch (JSONException e) { + e.printStackTrace(); + } if (playerConfig.toString().equals(currentPlayerConfig.toString())) return; @@ -101,6 +112,7 @@ public int getLivePlayerScale() { } public void changeLivePlayerType(VideoView videoView, int playerType, String channelName) { + channelName=currentCfgKey(channelName); JSONObject playerConfig = currentPlayerConfig; try { switch (playerType) { @@ -134,20 +146,54 @@ public void changeLivePlayerType(VideoView videoView, int playerType, String cha currentPlayerConfig = playerConfig; } + public boolean switchLivePlayer(VideoView videoView, String channelName) { + channelName = currentCfgKey(channelName); + JSONObject playerConfig = currentPlayerConfig; + if (playerConfig == null) { + LOG.i("echo-liveSwitchPlayer: skip empty player config"); + return false; + } + try { + int playerType = playerConfig.getInt("pl"); + int switchPlayerType = (playerType == 1) ? 2 : (playerType == 2) ? 1 : playerType; + if (switchPlayerType == playerType) { + LOG.i("echo-liveSwitchPlayer: skip unsupported playerType=" + playerType); + return false; + } + LOG.i("echo-liveSwitchPlayer: " + playerType + " -> " + switchPlayerType); + playerConfig.put("pl", switchPlayerType); + } catch (JSONException e) { + LOG.i("echo-liveSwitchPlayer error: " + e.getMessage()); + return false; + } + PlayerHelper.updateCfg(videoView, playerConfig); + + if (playerConfig.toString().equals(defaultPlayerConfig.toString())) + Hawk.delete(channelName); + else + Hawk.put(channelName, playerConfig); + + currentPlayerConfig = playerConfig; + return true; + } + public void changeLivePlayerScale(@NonNull VideoView videoView, int playerScale, String channelName){ videoView.setScreenScaleType(playerScale); + Hawk.put(HawkConfig.LIVE_PLAY_SCALE, playerScale); JSONObject playerConfig = currentPlayerConfig; try { playerConfig.put("sc", playerScale); + defaultPlayerConfig.put("sc", playerScale); } catch (JSONException e) { e.printStackTrace(); } - if (playerConfig.toString().equals(defaultPlayerConfig.toString())) - Hawk.delete(channelName); - else - Hawk.put(channelName, playerConfig); currentPlayerConfig = playerConfig; } -} \ No newline at end of file + + private String currentCfgKey(String channelName) + { + return currentApi+"_"+channelName; + } +} diff --git a/app/src/main/java/com/github/tvbox/osc/bean/Movie.java b/app/src/main/java/com/github/tvbox/osc/bean/Movie.java index 6decd100af..208770a010 100644 --- a/app/src/main/java/com/github/tvbox/osc/bean/Movie.java +++ b/app/src/main/java/com/github/tvbox/osc/bean/Movie.java @@ -62,6 +62,10 @@ public static class Video implements Serializable { @XStreamAlias("des") public String des;// { public boolean select = false; public ArrayList filters = new ArrayList<>(); public HashMap filterSelect = new HashMap<>(); + public String flag; // 类型 public SortData() { } @@ -41,17 +42,51 @@ public SortData(String id, String name) { this.name = name; } + public int filterSelectCount() { + if (filterSelect == null) { + return 0; + } + int count = 0; + for (String filter : filterSelect.values()) { + if (filter != null && !filter.isEmpty()) { + count++; + } + } + return count; + } @Override public int compareTo(SortData o) { return this.sort - o.sort; } + + @Override + public String toString() { + return "SortData{" + + "id='" + id + '\'' + + ", name='" + name + '\'' + + ", sort=" + sort + + ", select=" + select + + ", filters=" + filters + + ", filterSelect=" + filterSelect + + ", flag='" + flag + '\'' + + '}'; + } } public static class SortFilter { public String key; public String name; public LinkedHashMap values; + + @Override + public String toString() { + return "SortFilter{" + + "key='" + key + '\'' + + ", name='" + name + '\'' + + ", values=" + values + + '}'; + } } } \ No newline at end of file diff --git a/app/src/main/java/com/github/tvbox/osc/bean/ProxyRule.java b/app/src/main/java/com/github/tvbox/osc/bean/ProxyRule.java new file mode 100644 index 0000000000..dbb3eebbcf --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/bean/ProxyRule.java @@ -0,0 +1,108 @@ +package com.github.tvbox.osc.bean; + +import android.net.Uri; +import android.text.TextUtils; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.annotations.SerializedName; +import com.google.gson.reflect.TypeToken; + +import java.lang.reflect.Type; +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class ProxyRule implements Comparable { + + @SerializedName("name") + private String name; + @SerializedName("hosts") + private List hosts; + @SerializedName("urls") + private List urls; + + private List proxies; + private List uris; + private boolean wildcard; + + public static List arrayFrom(JsonElement element) { + try { + Type listType = new TypeToken>() {}.getType(); + List items = new Gson().fromJson(element, listType); + return items == null ? Collections.emptyList() : items; + } catch (Exception e) { + return Collections.emptyList(); + } + } + + public void init() { + wildcard = false; + for (String host : getHosts()) { + if (host != null && host.indexOf('*') >= 0) { + wildcard = true; + break; + } + } + uris = new ArrayList<>(); + for (String url : getUrls()) { + if (TextUtils.isEmpty(url)) continue; + Uri uri = Uri.parse(url); + if (isValid(uri)) uris.add(uri); + } + proxies = new ArrayList<>(); + for (Uri uri : uris) { + java.net.Proxy proxy = create(uri); + if (proxy != null) proxies.add(proxy); + } + } + + public String getName() { + return TextUtils.isEmpty(name) ? "" : name; + } + + public List getHosts() { + return hosts == null ? Collections.emptyList() : hosts; + } + + public List getUrls() { + return urls == null ? Collections.emptyList() : urls; + } + + public List getProxies() { + return proxies == null ? Collections.emptyList() : proxies; + } + + public String getUserInfo(String host) { + if (uris == null || host == null) return null; + for (Uri uri : uris) { + if (uri == null || uri.getHost() == null) continue; + if (host.equalsIgnoreCase(uri.getHost())) { + String userInfo = uri.getUserInfo(); + if (!TextUtils.isEmpty(userInfo)) return userInfo; + } + } + return null; + } + + private boolean isValid(Uri uri) { + return uri != null && uri.getScheme() != null && uri.getHost() != null && uri.getPort() > 0; + } + + private java.net.Proxy create(Uri uri) { + InetSocketAddress address = InetSocketAddress.createUnresolved(uri.getHost(), uri.getPort()); + if (isScheme(uri, "http")) return new java.net.Proxy(java.net.Proxy.Type.HTTP, address); + if (isScheme(uri, "socks")) return new java.net.Proxy(java.net.Proxy.Type.SOCKS, address); + return null; + } + + private boolean isScheme(Uri uri, String scheme) { + return uri.getScheme() != null && uri.getScheme().startsWith(scheme); + } + + @Override + public int compareTo(ProxyRule other) { + return Boolean.compare(this.wildcard, other.wildcard); + } +} diff --git a/app/src/main/java/com/github/tvbox/osc/bean/SourceBean.java b/app/src/main/java/com/github/tvbox/osc/bean/SourceBean.java index 8d9a8f5a22..36da9c0fb5 100644 --- a/app/src/main/java/com/github/tvbox/osc/bean/SourceBean.java +++ b/app/src/main/java/com/github/tvbox/osc/bean/SourceBean.java @@ -9,41 +9,50 @@ public class SourceBean { private int type; // 0 xml 1 json 3 Spider private int searchable; // 是否可搜索 private int quickSearch; // 是否可以快速搜索 - private int filterable; // 可筛选? + private int filterable; // 是否可以站点选择 private String playerUrl; // 站点解析Url private String ext; // 扩展数据 + private String jar; // 自定义jar private ArrayList categories = null; // 分类&排序 + private int playerType; // 0 system 1 ikj 2 exo 10 mxplayer -1 以参数设置页面的为准 + private int timeout; // 站点播放信息获取超时,单位秒 + private String clickSelector; // 需要点击播放的嗅探站点selector ddrk.me;#id + private String style; // 展示风格 + + private String safeString(String value) { + return value == null ? "" : value; + } public String getKey() { - return key; + return safeString(key); } public void setKey(String key) { - this.key = key; + this.key = safeString(key); } public String getName() { - return name; + return safeString(name); } public void setName(String name) { - this.name = name; + this.name = safeString(name); } public String getApi() { - return api; + return safeString(api); } public void setApi(String api) { - this.api = api; + this.api = safeString(api); } public void setPlayerUrl(String playerUrl) { - this.playerUrl = playerUrl; + this.playerUrl = safeString(playerUrl); } public String getPlayerUrl() { - return playerUrl; + return safeString(playerUrl); } public int getType() { @@ -79,11 +88,11 @@ public void setFilterable(int filterable) { } public String getExt() { - return ext; + return safeString(ext); } public void setExt(String ext) { - this.ext = ext; + this.ext = safeString(ext); } public ArrayList getCategories() { @@ -93,4 +102,33 @@ public ArrayList getCategories() { public void setCategories(ArrayList categories) { this.categories = categories; } -} \ No newline at end of file + + public String getJar() { + return safeString(jar); + } + + public void setJar(String jar) { + this.jar = safeString(jar); + } + + public int getPlayerType() { return playerType; } + + public void setPlayerType(int playerType) { this.playerType = playerType; } + + public int getTimeout() { return timeout; } + + public void setTimeout(int timeout) { this.timeout = timeout; } + + public int getPlayTimeoutSeconds() { + return timeout > 0 ? Math.max(5, Math.min(60, timeout)) : 15; + } + + public String getClickSelector() { return safeString(clickSelector); } + + public void setClickSelector(String clickSelector) { this.clickSelector = safeString(clickSelector); } + + + public String getStyle() { return safeString(style); } + + public void setStyle(String style) { this.style = safeString(style); } +} diff --git a/app/src/main/java/com/github/tvbox/osc/bean/Subtitle.java b/app/src/main/java/com/github/tvbox/osc/bean/Subtitle.java new file mode 100644 index 0000000000..7f611cfa2c --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/bean/Subtitle.java @@ -0,0 +1,43 @@ +package com.github.tvbox.osc.bean; + +public class Subtitle { + + private String name; + + private String url; + + private boolean isZip; + + public boolean getIsZip() { + return isZip; + } + + public String getName() { + return name; + } + + public String getUrl() { + return url; + } + + public void setName(String name) { + this.name = name; + } + + public void setUrl(String url) { + this.url = url; + } + + public void setIsZip(boolean zip) { + isZip = zip; + } + + @Override + public String toString() { + return "Subtitle{" + + "name='" + name + '\'' + + ", url='" + url + '\'' + + ", isZip=" + isZip + + '}'; + } +} diff --git a/app/src/main/java/com/github/tvbox/osc/bean/SubtitleData.java b/app/src/main/java/com/github/tvbox/osc/bean/SubtitleData.java new file mode 100644 index 0000000000..1a919d7165 --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/bean/SubtitleData.java @@ -0,0 +1,43 @@ +package com.github.tvbox.osc.bean; + +import java.util.List; + +public class SubtitleData { + + private Boolean isNew; + + private List subtitleList; + + private Boolean isZip; + + public Boolean getIsNew() { + return isNew; + } + + public List getSubtitleList() { + return subtitleList; + } + + public Boolean getIsZip() { + return isZip; + } + + public void setIsNew(Boolean isNew) { + this.isNew = isNew; + } + + public void setSubtitleList(List subtitle) { + this.subtitleList = subtitle; + } + + public void setIsZip(Boolean zip) { + isZip = zip; + } + + @Override + public String toString() { + return "SubtitleData{" + + "isNew='" + isNew + '\'' + + '}'; + } +} diff --git a/app/src/main/java/com/github/tvbox/osc/bean/VodInfo.java b/app/src/main/java/com/github/tvbox/osc/bean/VodInfo.java index e60a5fb5b9..a5fe3e873b 100644 --- a/app/src/main/java/com/github/tvbox/osc/bean/VodInfo.java +++ b/app/src/main/java/com/github/tvbox/osc/bean/VodInfo.java @@ -1,11 +1,10 @@ package com.github.tvbox.osc.bean; -import com.github.tvbox.osc.api.ApiConfig; +import static com.github.tvbox.osc.util.RegexUtils.getPattern; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import java.util.Set; @@ -81,27 +80,42 @@ public void setVideo(Movie.Video video) { seriesFlags.add(new VodSeriesFlag(urlInfo.flag)); } } - SourceBean sb = ApiConfig.get().getSource(video.sourceKey); - if (sb != null) { // ssp 不排序 - // 优先展示m3u8 - Collections.sort(seriesFlags, new Comparator() { - final String PREFIX = "m3u8"; - - @Override - public int compare(VodSeriesFlag a, VodSeriesFlag b) { - if (a.name.contains(PREFIX) && b.name.contains(PREFIX)) - return a.name.compareTo(b.name); - if (a.name.contains(PREFIX) && !b.name.contains(PREFIX)) return -1; - if (!a.name.contains(PREFIX) && b.name.contains(PREFIX)) return 1; - return 0; - } - }); - } + seriesMap = new LinkedHashMap<>(); for (VodSeriesFlag flag : seriesFlags) { - seriesMap.put(flag.name, tempSeriesMap.get(flag.name)); + List list = tempSeriesMap.get(flag.name); + assert list != null; + if(seriesFlags.size()<=5){ + if(isReverse(list))Collections.reverse(list); + } + seriesMap.put(flag.name, list); + } + } + } + + private int extractNumber(String name) { + java.util.regex.Matcher matcher = getPattern("\\d+").matcher(name); + if (matcher.find()) { + return Integer.parseInt(matcher.group()); + } + return 0; + } + private boolean isReverse(List list) { + int ascCount = 0, descCount = 0; + // 比较最多前 6 个相邻元素对 + int limit = Math.min(list.size() - 1, 6); + for (int i = 0; i < limit; i++) { + int current = extractNumber(list.get(i).name); + int next = extractNumber(list.get(i + 1).name); + if (current < next) { + ascCount++; + if (ascCount == 2) return false; + } else if (current > next) { + descCount++; + if (descCount == 2) return true; } } + return false; } public void reverse() { diff --git a/app/src/main/java/com/github/tvbox/osc/cache/RoomDataManger.java b/app/src/main/java/com/github/tvbox/osc/cache/RoomDataManger.java index ae6743e2f5..a05689cd32 100644 --- a/app/src/main/java/com/github/tvbox/osc/cache/RoomDataManger.java +++ b/app/src/main/java/com/github/tvbox/osc/cache/RoomDataManger.java @@ -7,11 +7,14 @@ import com.github.tvbox.osc.bean.VodInfo; import com.github.tvbox.osc.data.AppDataManager; import com.google.gson.ExclusionStrategy; +import com.github.tvbox.osc.util.HawkConfig; +import com.github.tvbox.osc.util.HistoryHelper; import com.google.gson.FieldAttributes; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; +import com.orhanobut.hawk.Hawk; import java.util.ArrayList; import java.util.List; @@ -79,6 +82,12 @@ public static void deleteVodRecord(String sourceKey, VodInfo vodInfo) { } public static List getAllVodRecord(int limit) { + int count = AppDataManager.get().getVodRecordDao().getCount(); + Integer index = Hawk.get(HawkConfig.HISTORY_NUM, 0); + Integer hisNum = HistoryHelper.getHisNum(index); + if ( count > hisNum ) { + AppDataManager.get().getVodRecordDao().reserver(hisNum); + } List recordList = AppDataManager.get().getVodRecordDao().getAll(limit); List vodInfoList = new ArrayList<>(); if (recordList != null) { @@ -89,8 +98,8 @@ public static List getAllVodRecord(int limit) { info = getVodInfoGson().fromJson(record.dataJson, new TypeToken() { }.getType()); info.sourceKey = record.sourceKey; - SourceBean sourceBean = ApiConfig.get().getSource(info.sourceKey); - if (sourceBean == null || info.name == null) +// SourceBean sourceBean = ApiConfig.get().getSource(info.sourceKey); + if (info.name == null) info = null; } } catch (Exception e) { @@ -121,6 +130,26 @@ public static void deleteVodCollect(int id) { AppDataManager.get().getVodCollectDao().delete(id); } + public static void deleteVodCollect(String sourceKey, VodInfo vodInfo) { + VodCollect record = AppDataManager.get().getVodCollectDao().getVodCollect(sourceKey, vodInfo.id); + if (record != null) { + AppDataManager.get().getVodCollectDao().delete(record); + } + } + + public static void deleteVodCollectAll() { + AppDataManager.get().getVodCollectDao().deleteAll(); + } + + public static void deleteVodRecordAll() { + AppDataManager.get().getVodRecordDao().deleteAll(); + } + + public static boolean isVodCollect(String sourceKey, String vodId) { + VodCollect record = AppDataManager.get().getVodCollectDao().getVodCollect(sourceKey, vodId); + return record != null; + } + public static List getAllVodCollect() { return AppDataManager.get().getVodCollectDao().getAll(); } diff --git a/app/src/main/java/com/github/tvbox/osc/cache/VodCollectDao.java b/app/src/main/java/com/github/tvbox/osc/cache/VodCollectDao.java index 86b699a83d..4c50d9f1c1 100644 --- a/app/src/main/java/com/github/tvbox/osc/cache/VodCollectDao.java +++ b/app/src/main/java/com/github/tvbox/osc/cache/VodCollectDao.java @@ -32,4 +32,8 @@ public interface VodCollectDao { @Delete int delete(VodCollect record); + + @Query("DELETE FROM vodCollect") + void deleteAll(); + } \ No newline at end of file diff --git a/app/src/main/java/com/github/tvbox/osc/cache/VodRecordDao.java b/app/src/main/java/com/github/tvbox/osc/cache/VodRecordDao.java index 9484b6ae47..68465a4251 100644 --- a/app/src/main/java/com/github/tvbox/osc/cache/VodRecordDao.java +++ b/app/src/main/java/com/github/tvbox/osc/cache/VodRecordDao.java @@ -26,4 +26,18 @@ public interface VodRecordDao { @Delete int delete(VodRecord record); -} \ No newline at end of file + + @Query("select count(*) from vodRecord") + int getCount(); + + @Query("DELETE FROM vodRecord") + void deleteAll(); + + /** + * 保留最新指定条数, 其他删除. + * @param size 保留条数 + * @return + */ + @Query("DELETE FROM vodRecord where id NOT IN (SELECT id FROM vodRecord ORDER BY updateTime desc LIMIT :size)") + int reserver(int size); +} diff --git a/app/src/main/java/com/github/tvbox/osc/data/AppDataManager.java b/app/src/main/java/com/github/tvbox/osc/data/AppDataManager.java index a5a85486f2..8990a4f454 100644 --- a/app/src/main/java/com/github/tvbox/osc/data/AppDataManager.java +++ b/app/src/main/java/com/github/tvbox/osc/data/AppDataManager.java @@ -1,5 +1,6 @@ package com.github.tvbox.osc.data; +import android.annotation.SuppressLint; import android.database.Cursor; import android.database.sqlite.SQLiteException; @@ -53,6 +54,7 @@ public void migrate(SupportSQLiteDatabase database) { }; static final Migration MIGRATION_2_3 = new Migration(2, 3) { + @SuppressLint("Range") @Override public void migrate(SupportSQLiteDatabase database) { database.execSQL("CREATE TABLE IF NOT EXISTS `vodRecordTmp` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `vodId` TEXT, `updateTime` INTEGER NOT NULL, `sourceKey` TEXT, `data` BLOB, `dataJson` TEXT, `testMigration` INTEGER NOT NULL)"); diff --git a/app/src/main/java/com/github/tvbox/osc/dlna/CastDevice.java b/app/src/main/java/com/github/tvbox/osc/dlna/CastDevice.java new file mode 100644 index 0000000000..ea1bc9796b --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/dlna/CastDevice.java @@ -0,0 +1,59 @@ +package com.github.tvbox.osc.dlna; + +import android.text.TextUtils; + +import org.fourthline.cling.model.meta.RemoteDevice; + +public class CastDevice { + public static final int TYPE_TVBOX = 1; + public static final int TYPE_DLNA = 2; + + private final int type; + private final String id; + private final String name; + + public static CastDevice tvbox(String host) { + return new CastDevice(TYPE_TVBOX, host, "TVBox " + host); + } + + public static CastDevice dlna(RemoteDevice device) { + String name = device.getDetails() == null ? "DLNA" : device.getDetails().getFriendlyName(); + String uuid = device.getIdentity().getUdn().getIdentifierString(); + return new CastDevice(TYPE_DLNA, uuid, TextUtils.isEmpty(name) ? "DLNA" : name); + } + + public CastDevice(int type, String id, String name) { + this.type = type; + this.id = id; + this.name = name; + } + + public int getType() { + return type; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public String getDisplayName() { + return (type == TYPE_DLNA ? "DLNA " : "TVBox ") + name; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof CastDevice)) return false; + CastDevice other = (CastDevice) obj; + return type == other.type && TextUtils.equals(id, other.id); + } + + @Override + public int hashCode() { + return (type * 31) + (id == null ? 0 : id.hashCode()); + } +} diff --git a/app/src/main/java/com/github/tvbox/osc/dlna/CastVideo.java b/app/src/main/java/com/github/tvbox/osc/dlna/CastVideo.java new file mode 100644 index 0000000000..7937b86faa --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/dlna/CastVideo.java @@ -0,0 +1,33 @@ +package com.github.tvbox.osc.dlna; + +import java.util.HashMap; + +public class CastVideo { + private final String url; + private final String name; + private final HashMap headers; + private final long position; + + public CastVideo(String url, String name, HashMap headers, long position) { + this.url = url; + this.name = name; + this.headers = headers == null ? new HashMap() : headers; + this.position = position; + } + + public String getUrl() { + return url; + } + + public String getName() { + return name; + } + + public HashMap getHeaders() { + return headers; + } + + public long getPosition() { + return position; + } +} diff --git a/app/src/main/java/com/github/tvbox/osc/dlna/DLNACastManager.java b/app/src/main/java/com/github/tvbox/osc/dlna/DLNACastManager.java new file mode 100644 index 0000000000..ee6f2cdbf3 --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/dlna/DLNACastManager.java @@ -0,0 +1,300 @@ +package com.github.tvbox.osc.dlna; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.net.wifi.WifiManager; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; + +import com.google.gson.Gson; +import com.github.tvbox.osc.util.LOG; + +import org.fourthline.cling.android.AndroidUpnpService; +import org.fourthline.cling.controlpoint.ControlPoint; +import org.fourthline.cling.model.action.ActionInvocation; +import org.fourthline.cling.model.message.UpnpResponse; +import org.fourthline.cling.model.message.header.STAllHeader; +import org.fourthline.cling.model.meta.RemoteDevice; +import org.fourthline.cling.model.meta.RemoteService; +import org.fourthline.cling.model.types.UDADeviceType; +import org.fourthline.cling.model.types.UDAServiceType; +import org.fourthline.cling.registry.DefaultRegistryListener; +import org.fourthline.cling.registry.Registry; +import org.fourthline.cling.support.avtransport.callback.Play; +import org.fourthline.cling.support.avtransport.callback.Seek; +import org.fourthline.cling.support.avtransport.callback.SetAVTransportURI; +import org.fourthline.cling.support.model.SeekMode; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +public class DLNACastManager extends DefaultRegistryListener implements ServiceConnection { + private static final UDADeviceType RENDERER_TYPE = new UDADeviceType("MediaRenderer", 1); + private static final UDAServiceType AVT_TYPE = new UDAServiceType("AVTransport", 1); + private static final DLNACastManager INSTANCE = new DLNACastManager(); + + private final Handler mainHandler = new Handler(Looper.getMainLooper()); + private final Map devices = new LinkedHashMap<>(); + private AndroidUpnpService upnpService; + private DeviceListener deviceListener; + private WifiManager.MulticastLock multicastLock; + private boolean binding; + + public static DLNACastManager get() { + return INSTANCE; + } + + public void init(Context context) { + if (upnpService != null || binding) { + search(); + return; + } + Context appContext = context.getApplicationContext(); + acquireMulticastLock(appContext); + binding = appContext.bindService(new Intent(context, DLNACastService.class), this, Context.BIND_AUTO_CREATE); + if (!binding) releaseMulticastLock(); + } + + public void release(Context context) { + try { + if (upnpService != null) upnpService.getRegistry().removeListener(this); + context.getApplicationContext().unbindService(this); + } catch (Exception ignored) { + } + upnpService = null; + binding = false; + devices.clear(); + releaseMulticastLock(); + } + + private void acquireMulticastLock(Context context) { + try { + if (multicastLock != null && multicastLock.isHeld()) return; + WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + if (wifiManager == null) return; + multicastLock = wifiManager.createMulticastLock("tvbox_dlna_cast"); + multicastLock.setReferenceCounted(false); + multicastLock.acquire(); + } catch (Exception ignored) { + } + } + + private void releaseMulticastLock() { + try { + if (multicastLock != null && multicastLock.isHeld()) multicastLock.release(); + } catch (Exception ignored) { + } + multicastLock = null; + } + + public void setDeviceListener(DeviceListener listener) { + this.deviceListener = listener; + } + + public void search() { + if (upnpService != null) { + upnpService.getControlPoint().search(new STAllHeader()); + loadRegisteredDevices(); + } + } + + public List getDevices() { + return new ArrayList<>(devices.values()); + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + binding = false; + upnpService = (AndroidUpnpService) service; + upnpService.getRegistry().addListener(this); + search(); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + upnpService = null; + binding = false; + } + + @Override + public void remoteDeviceAdded(Registry registry, RemoteDevice device) { + if (device.getType().implementsVersion(RENDERER_TYPE)) addDevice(CastDevice.dlna(device)); + } + + @Override + public void remoteDeviceRemoved(Registry registry, RemoteDevice device) { + if (device.getType().implementsVersion(RENDERER_TYPE)) removeDevice(CastDevice.dlna(device)); + } + + private void loadRegisteredDevices() { + if (upnpService == null) return; + for (org.fourthline.cling.model.meta.Device device : upnpService.getRegistry().getDevices(RENDERER_TYPE)) { + if (device instanceof RemoteDevice) addDevice(CastDevice.dlna((RemoteDevice) device)); + } + } + + private void addDevice(final CastDevice device) { + devices.put(device.getId(), device); + mainHandler.post(new Runnable() { + @Override + public void run() { + if (deviceListener != null) deviceListener.onDeviceChanged(); + } + }); + } + + private void removeDevice(final CastDevice device) { + devices.remove(device.getId()); + mainHandler.post(new Runnable() { + @Override + public void run() { + if (deviceListener != null) deviceListener.onDeviceChanged(); + } + }); + } + + public void cast(final CastDevice device, final CastVideo video, final CastCallback callback) { + ControlPoint control = upnpService == null ? null : upnpService.getControlPoint(); + RemoteService service = findAVTransport(device); + if (control == null || service == null) { + postFail(callback, "设备离线"); + return; + } + LOG.i("dlna-cast start device=" + device.getName() + ", id=" + device.getId() + ", url=" + video.getUrl()); + control.execute(uriAction(control, service, video, callback)); + } + + private RemoteService findAVTransport(CastDevice device) { + if (upnpService == null || device == null) return null; + for (org.fourthline.cling.model.meta.Device item : upnpService.getRegistry().getDevices(RENDERER_TYPE)) { + if (!(item instanceof RemoteDevice)) continue; + RemoteDevice remote = (RemoteDevice) item; + if (remote.getIdentity().getUdn().getIdentifierString().equals(device.getId())) { + return remote.findService(AVT_TYPE); + } + } + return null; + } + + private SetAVTransportURI uriAction(final ControlPoint control, final RemoteService service, final CastVideo video, final CastCallback callback) { + String metaData = buildMetaData(video); + return new SetAVTransportURI(service, video.getUrl(), metaData) { + @Override + public void success(ActionInvocation invocation) { + control.execute(playAction(control, service, video, callback)); + } + + @Override + public void failure(ActionInvocation invocation, UpnpResponse operation, String defaultMsg) { + LOG.e("dlna-cast SetAVTransportURI failure: " + formatResponse(operation, defaultMsg)); + postFail(callback, defaultMsg); + } + }; + } + + private Play playAction(final ControlPoint control, final RemoteService service, final CastVideo video, final CastCallback callback) { + return new Play(service) { + @Override + public void success(ActionInvocation invocation) { + if (video.getPosition() > 0) control.execute(seekAction(service, video.getPosition())); + postSuccess(callback); + } + + @Override + public void failure(ActionInvocation invocation, UpnpResponse operation, String defaultMsg) { + LOG.e("dlna-cast Play failure: " + formatResponse(operation, defaultMsg)); + postFail(callback, defaultMsg); + } + }; + } + + private Seek seekAction(RemoteService service, long position) { + return new Seek(service, SeekMode.REL_TIME, formatMs(position)) { + @Override + public void success(ActionInvocation invocation) { + } + + @Override + public void failure(ActionInvocation invocation, UpnpResponse operation, String defaultMsg) { + LOG.i("dlna-cast Seek ignored: " + formatResponse(operation, defaultMsg)); + } + }; + } + + private String buildMetaData(CastVideo video) { + try { + StringBuilder sb = new StringBuilder(); + sb.append(""); + sb.append(""); + sb.append("").append(escapeXml(video.getName())).append(""); + sb.append(""); + sb.append("object.item.videoItem"); + HashMap headers = video.getHeaders(); + if (headers != null && !headers.isEmpty()) { + sb.append("").append(escapeXml(new Gson().toJson(headers))).append(""); + } + sb.append("").append(escapeXml(video.getUrl())).append(""); + sb.append(""); + sb.append(""); + return sb.toString(); + } catch (Exception e) { + LOG.e("dlna-cast metadata failure: " + e.getMessage()); + return ""; + } + } + + private String escapeXml(String value) { + if (value == null) return ""; + return value.replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace("'", "'") + .replace("\"", """); + } + + private String formatMs(long ms) { + if (ms <= 0) return "00:00:00"; + long s = ms / 1000; + return String.format(Locale.US, "%02d:%02d:%02d", s / 3600, (s % 3600) / 60, s % 60); + } + + private String formatResponse(UpnpResponse operation, String defaultMsg) { + if (operation == null) return defaultMsg == null ? "" : defaultMsg; + return operation.getStatusCode() + " " + operation.getStatusMessage() + " " + (defaultMsg == null ? "" : defaultMsg); + } + + private void postSuccess(final CastCallback callback) { + mainHandler.post(new Runnable() { + @Override + public void run() { + if (callback != null) callback.onResult(true, ""); + } + }); + } + + private void postFail(final CastCallback callback, final String msg) { + mainHandler.post(new Runnable() { + @Override + public void run() { + if (callback != null) callback.onResult(false, msg == null ? "投屏失败" : msg); + } + }); + } + + public interface DeviceListener { + void onDeviceChanged(); + } + + public interface CastCallback { + void onResult(boolean success, String msg); + } +} diff --git a/app/src/main/java/com/github/tvbox/osc/dlna/DLNACastService.java b/app/src/main/java/com/github/tvbox/osc/dlna/DLNACastService.java new file mode 100644 index 0000000000..5e4087bf34 --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/dlna/DLNACastService.java @@ -0,0 +1,18 @@ +package com.github.tvbox.osc.dlna; + +import org.fourthline.cling.UpnpServiceConfiguration; +import org.fourthline.cling.android.AndroidUpnpServiceImpl; +import org.fourthline.cling.model.types.ServiceType; +import org.fourthline.cling.model.types.UDAServiceType; + +public class DLNACastService extends AndroidUpnpServiceImpl { + @Override + protected UpnpServiceConfiguration createConfiguration() { + return new DLNAServiceConfiguration() { + @Override + public ServiceType[] getExclusiveServiceTypes() { + return new ServiceType[]{new UDAServiceType("AVTransport", 1)}; + } + }; + } +} diff --git a/app/src/main/java/com/github/tvbox/osc/dlna/DLNAServiceConfiguration.java b/app/src/main/java/com/github/tvbox/osc/dlna/DLNAServiceConfiguration.java new file mode 100644 index 0000000000..9835f32ea4 --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/dlna/DLNAServiceConfiguration.java @@ -0,0 +1,38 @@ +package com.github.tvbox.osc.dlna; + +import android.os.Build; + +import org.fourthline.cling.binding.xml.ServiceDescriptorBinder; +import org.fourthline.cling.binding.xml.UDA10ServiceDescriptorBinderImpl; +import org.fourthline.cling.android.AndroidUpnpServiceConfiguration; +import org.fourthline.cling.model.ServerClientTokens; +import org.fourthline.cling.transport.spi.NetworkAddressFactory; +import org.fourthline.cling.transport.spi.StreamClient; +import org.fourthline.cling.transport.spi.StreamServer; + +public class DLNAServiceConfiguration extends AndroidUpnpServiceConfiguration { + @Override + @SuppressWarnings("rawtypes") + public StreamClient createStreamClient() { + return new OkHttpStreamClient(new OkHttpStreamClient.Configuration(getSyncProtocolExecutorService()) { + @Override + public String getUserAgentValue(int majorVersion, int minorVersion) { + ServerClientTokens tokens = new ServerClientTokens(majorVersion, minorVersion); + tokens.setOsName("Android"); + tokens.setOsVersion(Build.VERSION.RELEASE); + return tokens.toString(); + } + }); + } + + @Override + @SuppressWarnings("rawtypes") + public StreamServer createStreamServer(NetworkAddressFactory networkAddressFactory) { + return new SocketHttpStreamServer(new SocketHttpStreamServer.Configuration(networkAddressFactory.getStreamListenPort())); + } + + @Override + protected ServiceDescriptorBinder createServiceDescriptorBinderUDA10() { + return new UDA10ServiceDescriptorBinderImpl(); + } +} diff --git a/app/src/main/java/com/github/tvbox/osc/dlna/OkHttpStreamClient.java b/app/src/main/java/com/github/tvbox/osc/dlna/OkHttpStreamClient.java new file mode 100644 index 0000000000..03360931f4 --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/dlna/OkHttpStreamClient.java @@ -0,0 +1,121 @@ +package com.github.tvbox.osc.dlna; + +import org.fourthline.cling.model.message.StreamRequestMessage; +import org.fourthline.cling.model.message.StreamResponseMessage; +import org.fourthline.cling.model.message.UpnpHeaders; +import org.fourthline.cling.model.message.UpnpResponse; +import org.fourthline.cling.transport.spi.AbstractStreamClient; +import org.fourthline.cling.transport.spi.AbstractStreamClientConfiguration; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; + +import okhttp3.Call; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +public class OkHttpStreamClient extends AbstractStreamClient { + private final Configuration configuration; + private final OkHttpClient httpClient; + + public OkHttpStreamClient(Configuration configuration) { + this.configuration = configuration; + int timeout = configuration.getTimeoutSeconds() + 5; + this.httpClient = new OkHttpClient.Builder() + .connectTimeout(timeout, TimeUnit.SECONDS) + .readTimeout(timeout, TimeUnit.SECONDS) + .build(); + } + + @Override + public Configuration getConfiguration() { + return configuration; + } + + @Override + protected Call createRequest(StreamRequestMessage requestMessage) { + String method = requestMessage.getOperation().getHttpMethodName(); + Request.Builder builder = new Request.Builder() + .url(requestMessage.getOperation().getURI().toString()) + .method(method, buildRequestBody(requestMessage, method)); + for (Map.Entry> entry : requestMessage.getHeaders().entrySet()) { + if (entry.getKey() == null || entry.getValue() == null) continue; + for (String value : entry.getValue()) { + if (value != null) builder.addHeader(entry.getKey(), value); + } + } + if (requestMessage.getHeaders().get("user-agent") == null) { + builder.header("User-Agent", configuration.getUserAgentValue(requestMessage.getUdaMajorVersion(), requestMessage.getUdaMinorVersion())); + } + return httpClient.newCall(builder.build()); + } + + private RequestBody buildRequestBody(StreamRequestMessage requestMessage, String method) { + if (requestMessage.hasBody()) { + byte[] bytes = requestMessage.getBodyBytes(); + if (bytes != null && bytes.length > 0) { + List contentTypes = requestMessage.getHeaders().get("content-type"); + MediaType mediaType = contentTypes != null && !contentTypes.isEmpty() ? MediaType.parse(contentTypes.get(0)) : null; + return RequestBody.create(mediaType, bytes); + } + } + return requiresBody(method) ? RequestBody.create(null, new byte[0]) : null; + } + + private boolean requiresBody(String method) { + return "POST".equals(method) || "NOTIFY".equals(method) || "PUT".equals(method); + } + + @Override + protected Callable createCallable(StreamRequestMessage requestMessage, final Call call) { + return new Callable() { + @Override + public StreamResponseMessage call() throws Exception { + Response response = call.execute(); + try { + StreamResponseMessage responseMessage = new StreamResponseMessage(new UpnpResponse(response.code(), response.message())); + UpnpHeaders upnpHeaders = new UpnpHeaders(); + for (String name : response.headers().names()) { + for (String value : response.headers(name)) { + upnpHeaders.add(name, value); + } + } + responseMessage.setHeaders(upnpHeaders); + byte[] bytes = response.body() == null ? new byte[0] : response.body().bytes(); + if (bytes.length > 0) responseMessage.setBodyCharacters(bytes); + return responseMessage; + } finally { + response.close(); + } + } + }; + } + + @Override + protected void abort(Call call) { + call.cancel(); + } + + @Override + protected boolean logExecutionException(Throwable t) { + return false; + } + + @Override + public void stop() { + httpClient.dispatcher().executorService().shutdown(); + httpClient.connectionPool().evictAll(); + } + + public static class Configuration extends AbstractStreamClientConfiguration { + public Configuration(ExecutorService executorService) { + super(executorService); + } + } +} diff --git a/app/src/main/java/com/github/tvbox/osc/dlna/SocketHttpStreamServer.java b/app/src/main/java/com/github/tvbox/osc/dlna/SocketHttpStreamServer.java new file mode 100644 index 0000000000..2537c13e80 --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/dlna/SocketHttpStreamServer.java @@ -0,0 +1,246 @@ +package com.github.tvbox.osc.dlna; + +import org.fourthline.cling.model.message.Connection; +import org.fourthline.cling.model.message.StreamRequestMessage; +import org.fourthline.cling.model.message.StreamResponseMessage; +import org.fourthline.cling.model.message.UpnpHeaders; +import org.fourthline.cling.model.message.UpnpMessage; +import org.fourthline.cling.model.message.UpnpRequest; +import org.fourthline.cling.protocol.ProtocolFactory; +import org.fourthline.cling.transport.Router; +import org.fourthline.cling.transport.spi.InitializationException; +import org.fourthline.cling.transport.spi.StreamServer; +import org.fourthline.cling.transport.spi.StreamServerConfiguration; +import org.fourthline.cling.transport.spi.UpnpStream; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.net.URI; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class SocketHttpStreamServer implements StreamServer { + private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); + private final Configuration configuration; + private ServerSocket serverSocket; + private volatile boolean stopped; + private Router router; + + public SocketHttpStreamServer(Configuration configuration) { + this.configuration = configuration; + } + + @Override + public Configuration getConfiguration() { + return configuration; + } + + @Override + public void init(InetAddress bindAddress, Router router) throws InitializationException { + this.router = router; + try { + serverSocket = new ServerSocket(); + serverSocket.setReuseAddress(true); + serverSocket.bind(new InetSocketAddress(bindAddress, configuration.getListenPort()), 50); + } catch (IOException e) { + throw new InitializationException("Could not bind HTTP server socket on " + bindAddress, e); + } + } + + @Override + public int getPort() { + return serverSocket != null ? serverSocket.getLocalPort() : -1; + } + + @Override + public void stop() { + stopped = true; + try { + if (serverSocket != null) serverSocket.close(); + } catch (IOException ignored) { + } + } + + @Override + public void run() { + while (!stopped) { + try { + Socket socket = serverSocket.accept(); + socket.setSoTimeout(30000); + router.received(new SocketUpnpStream(router.getProtocolFactory(), socket)); + } catch (SocketException e) { + break; + } catch (IOException ignored) { + } + } + } + + private static class SocketUpnpStream extends UpnpStream { + private final Socket socket; + + SocketUpnpStream(ProtocolFactory protocolFactory, Socket socket) { + super(protocolFactory); + this.socket = socket; + } + + @Override + public void run() { + try { + InputStream inputStream = socket.getInputStream(); + String requestLine = readLine(inputStream); + String[] parts = requestLine.split(" ", 3); + if (requestLine.length() == 0 || parts.length < 2) { + socket.close(); + return; + } + Map> headers = readHeaders(inputStream); + StreamRequestMessage requestMessage = buildRequestMessage(parts[0], parts[1], headers); + readBodyInto(inputStream, requestMessage, headers); + StreamResponseMessage responseMessage = process(requestMessage); + OutputStream outputStream = socket.getOutputStream(); + writeResponse(outputStream, responseMessage); + outputStream.flush(); + responseSent(responseMessage); + } catch (Exception e) { + responseException(e); + } finally { + try { + socket.close(); + } catch (IOException ignored) { + } + } + } + + private String readLine(InputStream inputStream) throws IOException { + StringBuilder sb = new StringBuilder(); + int previous = -1; + int value; + while ((value = inputStream.read()) != -1) { + if (previous == '\r' && value == '\n') { + sb.deleteCharAt(sb.length() - 1); + return sb.toString(); + } + sb.append((char) value); + previous = value; + } + return sb.toString(); + } + + private Map> readHeaders(InputStream inputStream) throws IOException { + Map> headers = new HashMap<>(); + String line; + while (!(line = readLine(inputStream)).isEmpty()) { + int colon = line.indexOf(':'); + if (colon < 0) continue; + String key = line.substring(0, colon).trim().toLowerCase(); + String value = line.substring(colon + 1).trim(); + List values = headers.get(key); + if (values == null) { + values = new ArrayList<>(); + headers.put(key, values); + } + values.add(value); + } + return headers; + } + + private StreamRequestMessage buildRequestMessage(String method, String rawUri, Map> headers) { + StreamRequestMessage message = new StreamRequestMessage(UpnpRequest.Method.getByHttpName(method), URI.create(rawUri)); + message.setConnection(new SocketConnection(socket)); + message.setHeaders(new UpnpHeaders(headers)); + return message; + } + + private void readBodyInto(InputStream inputStream, StreamRequestMessage message, Map> headers) throws IOException { + List lengthHeaders = headers.get("content-length"); + if (lengthHeaders == null || lengthHeaders.isEmpty()) return; + int length = Integer.parseInt(lengthHeaders.get(0).trim()); + if (length <= 0) return; + byte[] body = new byte[length]; + int offset = 0; + int read; + while (offset < length && (read = inputStream.read(body, offset, length - offset)) != -1) { + offset += read; + } + if (message.isContentTypeMissingOrText()) message.setBodyCharacters(body); + else message.setBody(UpnpMessage.BodyType.BYTES, body); + } + + private void writeResponse(OutputStream outputStream, StreamResponseMessage message) throws IOException { + if (message == null) { + writeStatusLine(outputStream, 404, "Not Found"); + writeHeader(outputStream, "Content-Length", "0"); + writeEndHeaders(outputStream); + return; + } + writeStatusLine(outputStream, message.getOperation().getStatusCode(), message.getOperation().getStatusMessage()); + for (Map.Entry> entry : message.getHeaders().entrySet()) { + if (entry.getKey() == null || entry.getValue() == null) continue; + for (String value : entry.getValue()) { + writeHeader(outputStream, entry.getKey(), value); + } + } + byte[] body = message.hasBody() ? message.getBodyBytes() : null; + writeHeader(outputStream, "Content-Length", String.valueOf(body != null ? body.length : 0)); + writeEndHeaders(outputStream); + if (body != null && body.length > 0) outputStream.write(body); + } + + private void writeStatusLine(OutputStream outputStream, int code, String reason) throws IOException { + outputStream.write(("HTTP/1.1 " + code + " " + (reason == null ? "" : reason) + "\r\n").getBytes(ISO_8859_1)); + } + + private void writeHeader(OutputStream outputStream, String name, String value) throws IOException { + outputStream.write((name + ": " + value + "\r\n").getBytes(ISO_8859_1)); + } + + private void writeEndHeaders(OutputStream outputStream) throws IOException { + outputStream.write("\r\n".getBytes(ISO_8859_1)); + } + } + + private static class SocketConnection implements Connection { + private final Socket socket; + + SocketConnection(Socket socket) { + this.socket = socket; + } + + @Override + public boolean isOpen() { + return !socket.isClosed(); + } + + @Override + public InetAddress getRemoteAddress() { + return socket.getInetAddress(); + } + + @Override + public InetAddress getLocalAddress() { + return socket.getLocalAddress(); + } + } + + public static class Configuration implements StreamServerConfiguration { + private final int listenPort; + + public Configuration(int listenPort) { + this.listenPort = listenPort; + } + + @Override + public int getListenPort() { + return listenPort; + } + } +} diff --git a/app/src/main/java/com/github/tvbox/osc/event/RefreshEvent.java b/app/src/main/java/com/github/tvbox/osc/event/RefreshEvent.java index aa9be925e7..e157bd46c4 100644 --- a/app/src/main/java/com/github/tvbox/osc/event/RefreshEvent.java +++ b/app/src/main/java/com/github/tvbox/osc/event/RefreshEvent.java @@ -16,6 +16,15 @@ public class RefreshEvent { public static final int TYPE_QUICK_SEARCH_RESULT = 7; public static final int TYPE_API_URL_CHANGE = 8; public static final int TYPE_PUSH_URL = 9; + public static final int TYPE_EPG_URL_CHANGE = 10; + public static final int TYPE_SETTING_SEARCH_TV = 11; + public static final int TYPE_SUBTITLE_SIZE_CHANGE = 12; + public static final int TYPE_FILTER_CHANGE = 13; + public static final int TYPE_LIVE_API_URL_CHANGE = 14; + public static final int TYPE_HOME_SOURCE_CHANGE = 15; + public static final int TYPE_SET_DANMU_SETTINGS = 18; + public static final int TYPE_DANMU_REFRESH = 19; + public static final int TYPE_PLAY_QUALITY = 20; public int type; public Object obj; @@ -27,4 +36,4 @@ public RefreshEvent(int type, Object obj) { this.type = type; this.obj = obj; } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/github/tvbox/osc/glide/OkHttpAppGlideModule.java b/app/src/main/java/com/github/tvbox/osc/glide/OkHttpAppGlideModule.java new file mode 100644 index 0000000000..34b6af423b --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/glide/OkHttpAppGlideModule.java @@ -0,0 +1,26 @@ +package com.github.tvbox.osc.glide; + +import android.content.Context; + +import androidx.annotation.NonNull; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.Registry; +import com.bumptech.glide.annotation.GlideModule; +import com.bumptech.glide.load.model.GlideUrl; +import com.bumptech.glide.module.AppGlideModule; + +import java.io.InputStream; + +@GlideModule +public class OkHttpAppGlideModule extends AppGlideModule { + @Override + public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) { + registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory(com.github.catvod.net.OkHttp.client())); + } + + @Override + public boolean isManifestParsingEnabled() { + return false; + } +} diff --git a/app/src/main/java/com/github/tvbox/osc/glide/OkHttpStreamFetcher.java b/app/src/main/java/com/github/tvbox/osc/glide/OkHttpStreamFetcher.java new file mode 100644 index 0000000000..01155ab880 --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/glide/OkHttpStreamFetcher.java @@ -0,0 +1,101 @@ +package com.github.tvbox.osc.glide; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.bumptech.glide.Priority; +import com.bumptech.glide.load.DataSource; +import com.bumptech.glide.load.HttpException; +import com.bumptech.glide.load.data.DataFetcher; +import com.bumptech.glide.load.data.DataFetcher.DataCallback; +import com.bumptech.glide.load.model.GlideUrl; +import com.bumptech.glide.util.ContentLengthInputStream; +import com.bumptech.glide.util.Preconditions; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; + +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; + +public class OkHttpStreamFetcher implements DataFetcher, Callback { + private final Call.Factory client; + private final GlideUrl url; + private InputStream stream; + private ResponseBody responseBody; + private DataCallback callback; + + @Nullable + private volatile Call call; + + public OkHttpStreamFetcher(Call.Factory client, GlideUrl url) { + this.client = client; + this.url = url; + } + + @Override + public void loadData(@NonNull Priority priority, @NonNull DataCallback callback) { + Request.Builder requestBuilder = new Request.Builder().url(url.toStringUrl()); + for (Map.Entry headerEntry : url.getHeaders().entrySet()) { + String key = headerEntry.getKey(); + String value = headerEntry.getValue(); + if (key != null && value != null) requestBuilder.addHeader(key, value); + } + Request request = requestBuilder.build(); + this.callback = callback; + call = client.newCall(request); + call.enqueue(this); + } + + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + DataCallback local = callback; + if (local != null) local.onLoadFailed(e); + } + + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + responseBody = response.body(); + DataCallback local = callback; + if (local == null) return; + if (response.isSuccessful()) { + long contentLength = Preconditions.checkNotNull(responseBody).contentLength(); + stream = ContentLengthInputStream.obtain(responseBody.byteStream(), contentLength); + local.onDataReady(stream); + } else { + local.onLoadFailed(new HttpException(response.message(), response.code())); + } + } + + @Override + public void cleanup() { + try { + if (stream != null) stream.close(); + } catch (IOException ignored) { + } + if (responseBody != null) responseBody.close(); + callback = null; + } + + @Override + public void cancel() { + Call local = call; + if (local != null) local.cancel(); + } + + @NonNull + @Override + public Class getDataClass() { + return InputStream.class; + } + + @NonNull + @Override + public DataSource getDataSource() { + return DataSource.REMOTE; + } +} diff --git a/app/src/main/java/com/github/tvbox/osc/glide/OkHttpUrlLoader.java b/app/src/main/java/com/github/tvbox/osc/glide/OkHttpUrlLoader.java new file mode 100644 index 0000000000..969e2b114a --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/glide/OkHttpUrlLoader.java @@ -0,0 +1,52 @@ +package com.github.tvbox.osc.glide; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.bumptech.glide.load.Options; +import com.bumptech.glide.load.model.GlideUrl; +import com.bumptech.glide.load.model.ModelLoader.LoadData; +import com.bumptech.glide.load.model.ModelLoader; +import com.bumptech.glide.load.model.ModelLoaderFactory; +import com.bumptech.glide.load.model.MultiModelLoaderFactory; + +import java.io.InputStream; + +import okhttp3.Call; + +public class OkHttpUrlLoader implements ModelLoader { + private final Call.Factory client; + + public OkHttpUrlLoader(Call.Factory client) { + this.client = client; + } + + @Override + public boolean handles(@NonNull GlideUrl url) { + return true; + } + + @Nullable + @Override + public LoadData buildLoadData(@NonNull GlideUrl model, int width, int height, @NonNull Options options) { + return new LoadData<>(model, new OkHttpStreamFetcher(client, model)); + } + + public static class Factory implements ModelLoaderFactory { + private final Call.Factory client; + + public Factory(Call.Factory client) { + this.client = client; + } + + @NonNull + @Override + public ModelLoader build(@NonNull MultiModelLoaderFactory multiFactory) { + return new OkHttpUrlLoader(client); + } + + @Override + public void teardown() { + } + } +} diff --git a/app/src/main/java/com/github/tvbox/osc/picasso/MyOkhttpDownLoader.java b/app/src/main/java/com/github/tvbox/osc/picasso/MyOkhttpDownLoader.java new file mode 100644 index 0000000000..53d63c34d4 --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/picasso/MyOkhttpDownLoader.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2013 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tvbox.osc.picasso; + +import android.text.TextUtils; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.squareup.picasso.Downloader; + +import java.io.IOException; +import java.net.URLDecoder; + +import okhttp3.Cache; +import okhttp3.Call; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +/** + * A {@link Downloader} which uses OkHttp to download images. + */ +public final class MyOkhttpDownLoader implements Downloader { + private static final String DEFAULT_USER_AGENT = "Dalvik/2.1.0 (Linux; U; Android 13; M2102J2SC Build/TKQ1.220829.002)"; + @VisibleForTesting + final Call.Factory client; + private final Cache cache; + private boolean sharedClient = true; + + /** + * Create a new downloader that uses the specified OkHttp instance. A response cache will not be + * automatically configured. + */ + public MyOkhttpDownLoader(OkHttpClient client) { + this.client = client; + this.cache = client.cache(); + } + + /** + * Create a new downloader that uses the specified {@link Call.Factory} instance. + */ + public MyOkhttpDownLoader(Call.Factory client) { + this.client = client; + this.cache = null; + } + + @NonNull + @Override + public Response load(@NonNull Request request) throws IOException { + String url = request.url().toString(); + String header = null; + String cookie = null; + String ua = null; + String referer = null; + + //检查链接里面是否有自定义header + if (url.contains("@Headers=")){ + header =url.split("@Headers=")[1].split("@")[0]; + header =URLDecoder.decode(header,"UTF-8"); + } + if (url.contains("@Cookie=")) cookie= url.split("@Cookie=")[1].split("@")[0]; + if (url.contains("@User-Agent=")) ua =url.split("@User-Agent=")[1].split("@")[0]; + if (url.contains("@Referer=")) referer= url.split("@Referer=")[1].split("@")[0]; + + url = url.split("@")[0]; + Request.Builder mRequestBuilder = request.newBuilder().url(url); + if(!TextUtils.isEmpty(header)) { + JsonObject jsonInfo = new Gson().fromJson(header, JsonObject.class); + for (String key : jsonInfo.keySet()) { + String val = jsonInfo.get(key).getAsString(); + if (!TextUtils.isEmpty(key) && !TextUtils.isEmpty(val)) { + mRequestBuilder.header(key, val.trim()); + } + } + } + if(!TextUtils.isEmpty(cookie)) { + mRequestBuilder.header("Cookie", cookie); + } + if(!TextUtils.isEmpty(ua)){ + mRequestBuilder.header("User-Agent", ua); + } else if (TextUtils.isEmpty(mRequestBuilder.build().header("User-Agent"))) { + mRequestBuilder.header("User-Agent", DEFAULT_USER_AGENT); + } + if(!TextUtils.isEmpty(referer)){ + mRequestBuilder.header("Referer", referer); + } + return client.newCall(mRequestBuilder.build()).execute(); + } + + @Override + public void shutdown() { + if (!sharedClient && cache != null) { + try { + cache.close(); + } catch (IOException ignored) { + } + } + } +} diff --git a/app/src/main/java/com/github/tvbox/osc/picasso/RoundTransformation.java b/app/src/main/java/com/github/tvbox/osc/picasso/RoundTransformation.java index 6293ed7545..871cf5daa5 100644 --- a/app/src/main/java/com/github/tvbox/osc/picasso/RoundTransformation.java +++ b/app/src/main/java/com/github/tvbox/osc/picasso/RoundTransformation.java @@ -5,7 +5,6 @@ import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; -import android.graphics.PaintFlagsDrawFilter; import android.graphics.Path; import android.graphics.RectF; import android.graphics.Shader; @@ -63,37 +62,44 @@ public RoundTransformation roundRadius(int radius, @RoundType int mRoundType) { @Override public Bitmap transform(Bitmap source) { - int width = source.getWidth(); - int height = source.getHeight(); + final int sourceWidth = source.getWidth(); + final int sourceHeight = source.getHeight(); if (viewWidth == 0 || viewHeight == 0) { - viewWidth = width; - viewHeight = height; + viewWidth = sourceWidth; + viewHeight = sourceHeight; } - Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); - BitmapShader mBitmapShader = new BitmapShader(source, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); - if (viewWidth != width || viewHeight != height) { - //是否以宽计算 - float scale; - if (width * 1f / viewWidth > height * 1f / viewHeight) { - scale = viewHeight * 1f / height; - width = (int) (width * scale); - height = viewHeight; + final float scale; + final int targetWidth; + final int targetHeight; + if (sourceWidth != viewWidth || sourceHeight != viewHeight) { + if (sourceWidth * 1f / viewWidth > sourceHeight * 1f / viewHeight) { + scale = (float) viewHeight / sourceHeight; + targetWidth = (int) (sourceWidth * scale); + targetHeight = viewHeight; } else { - scale = viewWidth * 1f / width; - height = (int) (height * scale); - width = viewWidth; + scale = (float) viewWidth / sourceWidth; + targetWidth = viewWidth; + targetHeight = (int) (sourceHeight * scale); } + } else { + scale = 1f; + targetWidth = sourceWidth; + targetHeight = sourceHeight; + } + BitmapShader shader = new BitmapShader(source, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); + if (scale != 1f) { Matrix matrix = new Matrix(); - matrix.postScale(scale, scale); - mBitmapShader.setLocalMatrix(matrix); + matrix.setScale(scale, scale); + shader.setLocalMatrix(matrix); } - Bitmap bitmap = Bitmap.createBitmap(viewWidth, viewHeight, Bitmap.Config.ARGB_8888); + Bitmap bitmap = Bitmap.createBitmap(targetWidth, targetHeight, Bitmap.Config.ARGB_8888); bitmap.setHasAlpha(true); - Canvas mCanvas = new Canvas(bitmap); - mPaint.setShader(mBitmapShader); - // mPaint.setAntiAlias(true); - mCanvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG)); - drawRoundRect(mCanvas, mPaint, width, height); + Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + paint.setShader(shader); + Canvas canvas = new Canvas(bitmap); + RectF rect = new RectF(0, 0, targetWidth, targetHeight); + canvas.drawRoundRect(rect, radius, radius, paint); + source.recycle(); return bitmap; } diff --git a/app/src/main/java/com/github/tvbox/osc/player/ExoMediaPlayerFactory.java b/app/src/main/java/com/github/tvbox/osc/player/ExoMediaPlayerFactory.java new file mode 100644 index 0000000000..a98ec8d7df --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/player/ExoMediaPlayerFactory.java @@ -0,0 +1,17 @@ +package com.github.tvbox.osc.player; + +import android.content.Context; + +import xyz.doikki.videoplayer.player.PlayerFactory; + +public class ExoMediaPlayerFactory extends PlayerFactory { + + public static ExoMediaPlayerFactory create() { + return new ExoMediaPlayerFactory(); + } + + @Override + public ExoPlayer createPlayer(Context context) { + return new ExoPlayer(context); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/tvbox/osc/player/ExoPlayer.java b/app/src/main/java/com/github/tvbox/osc/player/ExoPlayer.java new file mode 100644 index 0000000000..7b9db72833 --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/player/ExoPlayer.java @@ -0,0 +1,300 @@ +package com.github.tvbox.osc.player; + +import android.content.Context; +import android.util.Pair; + +import com.github.tvbox.osc.util.AudioTrackMemory; +import com.github.tvbox.osc.util.LOG; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.DefaultLoadControl; +import com.google.android.exoplayer2.DefaultRenderersFactory; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.RenderersFactory; +import com.google.android.exoplayer2.Tracks; +import com.google.android.exoplayer2.source.TrackGroup; +import com.google.android.exoplayer2.source.TrackGroupArray; +import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; +import com.google.android.exoplayer2.trackselection.MappingTrackSelector; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import xyz.doikki.videoplayer.exo.ExoMediaPlayer; + +public class ExoPlayer extends ExoMediaPlayer { + + private static AudioTrackMemory memory; + + public ExoPlayer(Context context) { + super(context); + setLoadControl(new DefaultLoadControl.Builder() + .setBufferDurationsMs( + DefaultLoadControl.DEFAULT_MIN_BUFFER_MS, + DefaultLoadControl.DEFAULT_MAX_BUFFER_MS, + DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS, + DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS) + .build()); + setRenderersFactory(buildRenderersFactory(context)); + LOG.i("echo-exo-low-memory-load-control"); + memory = AudioTrackMemory.getInstance(context); + } + + private RenderersFactory buildRenderersFactory(Context context) { + DefaultRenderersFactory factory = new DefaultRenderersFactory(context) + .setEnableDecoderFallback(true) + .setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER); + try { + Method method = DefaultRenderersFactory.class.getMethod("forceDisableMediaCodecAsynchronousQueueing"); + method.invoke(factory); + LOG.i("echo-exo-disable-async-codec-queue"); + } catch (Throwable th) { + LOG.i("echo-exo-disable-async-codec-queue-skip:" + th.getClass().getSimpleName()); + } + return factory; + } + + public TrackInfo getTrackInfo() { + TrackInfo data = new TrackInfo(); + MappingTrackSelector.MappedTrackInfo mappedInfo = trackSelector.getCurrentMappedTrackInfo(); + if (mappedInfo == null) return data; + + for (int rendererIndex = 0; rendererIndex < mappedInfo.getRendererCount(); rendererIndex++) { + int type = mappedInfo.getRendererType(rendererIndex); + if (type != C.TRACK_TYPE_AUDIO && type != C.TRACK_TYPE_TEXT) continue; + + TrackGroupArray groups = mappedInfo.getTrackGroups(rendererIndex); + for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) { + TrackGroup group = groups.get(groupIndex); + for (int trackIndex = 0; trackIndex < group.length; trackIndex++) { + Format fmt = group.getFormat(trackIndex); + String language = getLanguage(fmt); + String detail = getName(fmt); + TrackInfoBean bean = new TrackInfoBean(); + bean.language = language; + bean.name = buildDisplayName(type == C.TRACK_TYPE_AUDIO ? "\u97f3\u8f68" : "\u5b57\u5e55", + type == C.TRACK_TYPE_AUDIO ? data.getAudio().size() + 1 : data.getSubtitle().size() + 1, + language, detail); + bean.renderId = rendererIndex; + bean.trackGroupId = groupIndex; + bean.trackId = trackIndex; + bean.groupIndex = groupIndex; + bean.index = trackIndex; + bean.selected = isCurrentTrackSelected(fmt, type); + + if (type == C.TRACK_TYPE_AUDIO) { + data.addAudio(bean); + } else { + data.addSubtitle(bean); + } + } + } + } + return data; + } + + public void setTrack(int groupIndex, int trackIndex, String playKey) { + MappingTrackSelector.MappedTrackInfo mappedInfo = trackSelector.getCurrentMappedTrackInfo(); + setTrack(findAudioRendererIndex(mappedInfo), groupIndex, trackIndex, playKey); + } + + public void setTrack(TrackInfoBean track, String playKey) { + if (track == null) return; + setTrack(track.renderId, track.trackGroupId, track.trackId, playKey); + } + + private void setTrack(int rendererIndex, int groupIndex, int trackIndex, String playKey) { + try { + MappingTrackSelector.MappedTrackInfo mappedInfo = trackSelector.getCurrentMappedTrackInfo(); + if (mappedInfo == null) { + LOG.i("echo-setTrack: MappedTrackInfo is null"); + return; + } + if (rendererIndex == C.INDEX_UNSET || rendererIndex < 0 || rendererIndex >= mappedInfo.getRendererCount()) { + LOG.i("echo-setTrack: No renderer found"); + return; + } + + TrackGroupArray groups = mappedInfo.getTrackGroups(rendererIndex); + if (!isTrackIndexValid(groups, groupIndex, trackIndex)) { + LOG.i("echo-setTrack: Invalid track index - group:" + groupIndex + ", track:" + trackIndex); + return; + } + + DefaultTrackSelector.SelectionOverride override = + new DefaultTrackSelector.SelectionOverride(groupIndex, trackIndex); + DefaultTrackSelector.Parameters.Builder builder = trackSelector.buildUponParameters(); + builder.setRendererDisabled(rendererIndex, false); + builder.clearSelectionOverrides(rendererIndex); + builder.setSelectionOverride(rendererIndex, groups, override); + trackSelector.setParameters(builder.build()); + + if (!playKey.isEmpty()) { + memory.save(playKey, groupIndex, trackIndex); + } + } catch (Exception e) { + LOG.i("echo-setTrack error: " + e.getMessage()); + } + } + + public void loadDefaultTrack(String playKey) { + Pair pair = memory.exoLoad(playKey); + if (pair == null) return; + + MappingTrackSelector.MappedTrackInfo mappedInfo = trackSelector.getCurrentMappedTrackInfo(); + if (mappedInfo == null) return; + + int audioRendererIndex = findAudioRendererIndex(mappedInfo); + if (audioRendererIndex == C.INDEX_UNSET) return; + + setTrack(audioRendererIndex, pair.first, pair.second, ""); + } + + private int findAudioRendererIndex(MappingTrackSelector.MappedTrackInfo mappedInfo) { + if (mappedInfo == null) return C.INDEX_UNSET; + for (int i = 0; i < mappedInfo.getRendererCount(); i++) { + if (mappedInfo.getRendererType(i) == C.TRACK_TYPE_AUDIO) { + return i; + } + } + return C.INDEX_UNSET; + } + + private boolean isTrackIndexValid(TrackGroupArray groups, int groupIndex, int trackIndex) { + if (groupIndex < 0 || groupIndex >= groups.length) return false; + TrackGroup group = groups.get(groupIndex); + return trackIndex >= 0 && trackIndex < group.length; + } + + private boolean isCurrentTrackSelected(Format format, int trackType) { + if (mInternalPlayer == null) return false; + Tracks tracks = mInternalPlayer.getCurrentTracks(); + for (Tracks.Group group : tracks.getGroups()) { + if (group.getType() != trackType || !group.isSelected()) continue; + for (int i = 0; i < group.length; i++) { + if (group.isTrackSelected(i) && isSameFormat(format, group.getTrackFormat(i))) { + return true; + } + } + } + return false; + } + + private boolean isSameFormat(Format a, Format b) { + if (a == b) return true; + if (a == null || b == null) return false; + if (a.id != null && b.id != null && a.id.equals(b.id)) return true; + return a.equals(b); + } + + private static final Map LANG_MAP = new HashMap<>(); + + static { + LANG_MAP.put("zh", "\u56fd\u8bed"); + LANG_MAP.put("zh-cn", "\u56fd\u8bed"); + LANG_MAP.put("cmn", "\u56fd\u8bed"); + LANG_MAP.put("chi", "\u56fd\u8bed"); + LANG_MAP.put("zho", "\u56fd\u8bed"); + LANG_MAP.put("chs", "\u56fd\u8bed"); + LANG_MAP.put("yue", "\u7ca4\u8bed"); + LANG_MAP.put("zh-hk", "\u7ca4\u8bed"); + LANG_MAP.put("zh-yue", "\u7ca4\u8bed"); + LANG_MAP.put("en", "\u82f1\u8bed"); + LANG_MAP.put("en-us", "\u82f1\u8bed"); + LANG_MAP.put("eng", "\u82f1\u8bed"); + LANG_MAP.put("ja", "\u65e5\u8bed"); + LANG_MAP.put("jpn", "\u65e5\u8bed"); + LANG_MAP.put("ko", "\u97e9\u8bed"); + LANG_MAP.put("kor", "\u97e9\u8bed"); + LANG_MAP.put("th", "\u6cf0\u8bed"); + LANG_MAP.put("tha", "\u6cf0\u8bed"); + } + + private String getLanguage(Format fmt) { + String language = matchLanguage(fmt.language); + if (!language.isEmpty()) { + return language; + } + return matchLanguage((fmt.label == null ? "" : fmt.label) + " " + + (fmt.id == null ? "" : fmt.id) + " " + + (fmt.codecs == null ? "" : fmt.codecs)); + } + + private String matchLanguage(String text) { + if (text == null) return ""; + String value = text.toLowerCase(); + String mapped = LANG_MAP.get(value); + if (mapped != null) return mapped; + if (value.contains("yue") || value.contains("cantonese") || value.contains("\u7ca4") || value.contains("\u5e7f\u4e1c")) { + return "\u7ca4\u8bed"; + } + if (value.contains("zh") || value.contains("chi") || value.contains("zho") || value.contains("chs") + || value.contains("cht") || value.contains("cmn") || value.contains("\u4e2d") + || value.contains("\u56fd\u8bed") || value.contains("\u666e\u901a\u8bdd")) { + return "\u56fd\u8bed"; + } + if (value.contains("en") || value.contains("eng") || value.contains("english") || value.contains("\u82f1")) { + return "\u82f1\u8bed"; + } + if (value.contains("ja") || value.contains("jpn") || value.contains("japanese") || value.contains("\u65e5")) { + return "\u65e5\u8bed"; + } + if (value.contains("ko") || value.contains("kor") || value.contains("korean") || value.contains("\u97e9")) { + return "\u97e9\u8bed"; + } + if (value.contains("tha") || value.contains("thai") || value.contains("th")) { + return "\u6cf0\u8bed"; + } + return ""; + } + + private String getName(Format fmt) { + String channelLabel; + if (fmt.channelCount <= 0) { + channelLabel = ""; + } else if (fmt.channelCount == 1) { + channelLabel = "\u5355\u58f0\u9053"; + } else if (fmt.channelCount == 2) { + channelLabel = "\u7acb\u4f53\u58f0"; + } else { + channelLabel = fmt.channelCount + " \u58f0\u9053"; + } + + String codec = ""; + if (fmt.codecs != null && !fmt.codecs.isEmpty()) { + codec = fmt.codecs.toUpperCase(); + } + if (fmt.sampleMimeType != null && fmt.sampleMimeType.contains("/")) { + String mime = fmt.sampleMimeType.substring(fmt.sampleMimeType.indexOf('/') + 1); + if (codec.isEmpty()) { + codec = mime.toUpperCase(); + } + } + StringBuilder builder = new StringBuilder(); + appendPart(builder, fmt.label); + appendPart(builder, codec); + appendPart(builder, channelLabel); + return builder.toString(); + } + + private String buildDisplayName(String prefix, int number, String language, String detail) { + StringBuilder builder = new StringBuilder(prefix).append(" ").append(number); + if (language != null && !language.isEmpty()) { + builder.append(" - ").append(language); + } + if (detail != null && !detail.isEmpty()) { + builder.append(" ").append(detail); + } + return builder.toString(); + } + + private void appendPart(StringBuilder builder, String value) { + if (value == null) return; + String part = value.trim(); + if (part.isEmpty() || "und".equalsIgnoreCase(part) || "\u672a\u77e5".equals(part)) return; + if (builder.length() > 0) { + builder.append(" / "); + } + builder.append(part); + } +} diff --git a/app/src/main/java/com/github/tvbox/osc/player/IjkMediaPlayer.java b/app/src/main/java/com/github/tvbox/osc/player/IjkMediaPlayer.java index 6d72390cba..968442fceb 100644 --- a/app/src/main/java/com/github/tvbox/osc/player/IjkMediaPlayer.java +++ b/app/src/main/java/com/github/tvbox/osc/player/IjkMediaPlayer.java @@ -5,19 +5,39 @@ import com.github.tvbox.osc.api.ApiConfig; import com.github.tvbox.osc.bean.IJKCode; +import com.github.tvbox.osc.server.ControlManager; +import com.github.tvbox.osc.util.AudioTrackMemory; +import com.github.tvbox.osc.util.FileUtils; +import com.github.tvbox.osc.util.HawkConfig; +import com.github.tvbox.osc.util.LOG; +import com.github.tvbox.osc.util.MD5; +import com.orhanobut.hawk.Hawk; +import java.io.File; +import java.net.URI; +import java.net.URLEncoder; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; +import tv.danmaku.ijk.media.player.IMediaPlayer; +import tv.danmaku.ijk.media.player.misc.ITrackInfo; +import tv.danmaku.ijk.media.player.misc.IjkTrackInfo; +import xyz.doikki.videoplayer.exo.ExoMediaSourceHelper; import xyz.doikki.videoplayer.ijk.IjkPlayer; public class IjkMediaPlayer extends IjkPlayer { private IJKCode codec = null; + protected String currentPlayPath; + private static AudioTrackMemory memory; + private static final String DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"; + private static final String DEFAULT_ACCEPT = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/json;q=0.9"; public IjkMediaPlayer(Context context, IJKCode codec) { super(context); this.codec = codec; + memory = AudioTrackMemory.getInstance(context); } @Override @@ -32,6 +52,7 @@ public void setOptions() { int category = Integer.parseInt(opt[0].trim()); String name = opt[1].trim(); try { + assert value != null; long valLong = Long.parseLong(value); mMediaPlayer.setOption(category, name, valLong); } catch (Exception e) { @@ -39,19 +60,272 @@ public void setOptions() { } } } + mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max-fps", 30); + + // 设置视频流格式 +// mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_PLAYER, "overlay-format", tv.danmaku.ijk.media.player.IjkMediaPlayer.SDL_FCC_RV32); + + //开启内置字幕 + mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_PLAYER, "subtitle", 1); + mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT, "dns_cache_clear", 1); + mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT, "dns_cache_timeout", -1); + mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT,"safe",0); + + if(Hawk.get(HawkConfig.PLAYER_IS_LIVE, false)){ + LOG.i("echo-type-直播"); + mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max_cached_duration", 300); + mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT, "flush_packets", 1); + mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_PLAYER, "min-frames", 1); + mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_CODEC, "threads", "1"); + }else{ + LOG.i("echo-type-点播"); + // 降低延迟 + mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max_cached_duration", 3000); + mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT, "infbuf", 0); + mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_CODEC, "threads", "2"); + } +// mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_PLAYER, "sync-av-start", 1);//强制音画同步 } + private static final String ITV_TARGET_DOMAIN = "gslbserv.itv.cmvideo.cn"; @Override public void setDataSource(String path, Map headers) { try { - if (path != null && !TextUtils.isEmpty(path) && path.startsWith("rtsp")) { - mMediaPlayer.setOption(1, "infbuf", 1); - mMediaPlayer.setOption(1, "rtsp_transport", "tcp"); - mMediaPlayer.setOption(1, "rtsp_flags", "prefer_tcp"); + switch (getStreamType(path)) { + case RTSP_UDP_RTP: + mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT, "infbuf", 1); + mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT, "rtsp_transport", "tcp"); + mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT, "rtsp_flags", "prefer_tcp"); + mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT, "probesize", 512 * 1000); + mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT, "analyzeduration", 2 * 1000 * 1000); + break; + + case CACHE_VIDEO: + if (Hawk.get(HawkConfig.IJK_CACHE_PLAY, false)) { + String cachePath = FileUtils.getCachePath() + "/ijkcaches/"; + File cacheFile = new File(cachePath); + if (!cacheFile.exists()) cacheFile.mkdirs(); + String tmpMd5 = MD5.string2MD5(path); + String cacheFilePath = cachePath + tmpMd5 + ".file"; + String cacheMapPath = cachePath + tmpMd5 + ".map"; + + mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT, "cache_file_path", cacheFilePath); + mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT, "cache_map_path", cacheMapPath); + mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT, "parse_cache_map", 1); + mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT, "auto_save_map", 1); + mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT, "cache_max_capacity", 60 * 1024 * 1024); + path = "ijkio:cache:ffio:" + path; + } + break; + + case M3U8: + // 直播且是ijk的时候自动自动走代理解决DNS + if (Hawk.get(HawkConfig.PLAYER_IS_LIVE, false) ) { + URI uri = new URI(path); + String host = uri.getHost(); + if(ITV_TARGET_DOMAIN.equalsIgnoreCase(host))path = ControlManager.get().getAddress(true) + "proxy?go=live&type=m3u8&url="+ URLEncoder.encode(path,"UTF-8"); + } + break; + + default: + break; } } catch (Exception e) { e.printStackTrace(); } - super.setDataSource(path, headers); + setDataSourceHeader(headers); + mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT, "protocol_whitelist", "ijkio,ffio,async,cache,crypto,file,dash,http,https,ijkhttphook,ijkinject,ijklivehook,ijklongurl,ijksegment,ijktcphook,pipe,rtp,tcp,tls,udp,ijkurlhook,data"); + currentPlayPath = path; + super.setDataSource(path, null); + } + + /** + * 解析 URL + */ + private static final int RTSP_UDP_RTP = 1; + private static final int CACHE_VIDEO = 2; + private static final int M3U8 = 3; + private static final int OTHER = 0; + + private int getStreamType(String path) { + if (TextUtils.isEmpty(path)) { + return OTHER; + } + // 低成本检查 RTSP/UDP/RTP 类型 + String lowerPath = path.toLowerCase(); + if (lowerPath.startsWith("rtsp://") || lowerPath.startsWith("udp://") || lowerPath.startsWith("rtp://")) { + return RTSP_UDP_RTP; + } + String cleanUrl = path.split("\\?")[0]; + if (cleanUrl.endsWith(".m3u8")) { + return M3U8; + } + if (cleanUrl.endsWith(".mp4") || cleanUrl.endsWith(".mkv") || cleanUrl.endsWith(".avi")) { + return CACHE_VIDEO; + } + return OTHER; + } + + private void setDataSourceHeader(Map headers) { + LinkedHashMap playHeaders = new LinkedHashMap<>(); + String userAgent = null; + boolean hasAccept = false; + if (headers != null && !headers.isEmpty()) { + for (Map.Entry entry : headers.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + if (TextUtils.isEmpty(key) || TextUtils.isEmpty(value)) { + continue; + } + if (ExoMediaSourceHelper.HEADER_FORMAT.equalsIgnoreCase(key)) { + continue; + } + if ("User-Agent".equalsIgnoreCase(key)) { + userAgent = value.trim(); + } else { + if ("Accept".equalsIgnoreCase(key)) { + hasAccept = true; + } + playHeaders.put(key, value.trim()); + } + } + } + if (TextUtils.isEmpty(userAgent)) { + userAgent = DEFAULT_USER_AGENT; + } + if (!hasAccept) { + playHeaders.put("Accept", DEFAULT_ACCEPT); + } + if (!TextUtils.isEmpty(userAgent)) { + mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT, "user_agent", userAgent); + } + if (playHeaders.size() > 0) { + StringBuilder sb = new StringBuilder(); + for (Map.Entry entry : playHeaders.entrySet()) { + sb.append(entry.getKey()); + sb.append(": "); + sb.append(entry.getValue()); + sb.append("\r\n"); + } + mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT, "headers", sb.toString()); + } + } + + public TrackInfo getTrackInfo() { + IjkTrackInfo[] trackInfo = mMediaPlayer.getTrackInfo(); + if (trackInfo == null) return null; + TrackInfo data = new TrackInfo(); + int subtitleSelected = mMediaPlayer.getSelectedTrack(ITrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT); + int audioSelected = mMediaPlayer.getSelectedTrack(ITrackInfo.MEDIA_TRACK_TYPE_AUDIO); + int index = 0; + for (IjkTrackInfo info : trackInfo) { + if (info.getTrackType() == ITrackInfo.MEDIA_TRACK_TYPE_AUDIO) {//音轨信息 + TrackInfoBean a = new TrackInfoBean(); + String name = processAudioName(info.getInfoInline()); + a.language = info.getLanguage(); + if(name.startsWith("aac"))a.language="中文"; + a.name = name; + String language = getFriendlyLanguage(a.language, info.getInfoInline()); + a.language = language; + a.name = buildDisplayName("\u97f3\u8f68", data.getAudio().size() + 1, language, name); + a.trackId = index; + a.index = index; + a.selected = index == audioSelected; + // 如果需要,还可以检查轨道的描述或标题以获取更多信息 + data.addAudio(a); + } + else if (info.getTrackType() == ITrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT) {//内置字幕 + TrackInfoBean t = new TrackInfoBean(); + t.name = info.getInfoInline(); + t.language = info.getLanguage(); + String language = getFriendlyLanguage(t.language, t.name); + t.language = language; + t.name = buildDisplayName("\u5b57\u5e55", data.getSubtitle().size() + 1, language, ""); + t.trackId = index; + t.index = index; + t.selected = index == subtitleSelected; + data.addSubtitle(t); + } + index++; + } + return data; + } + // 处理音轨名称格式 + private String processAudioName(String rawName) { + if (rawName == null) return ""; + return rawName.replace("AUDIO,", "") + .replace("N/A,", "") + .replace(" ", "") + .replaceAll("^,+|,+$", "") + .replace(",", " / "); + } + + private String getFriendlyLanguage(String language, String rawInfo) { + String text = ((language == null ? "" : language) + " " + (rawInfo == null ? "" : rawInfo)).toLowerCase(); + if (text.contains("yue") || text.contains("cantonese") || text.contains("\u7ca4") || text.contains("\u5e7f\u4e1c")) { + return "\u7ca4\u8bed"; + } + if (text.contains("zh") || text.contains("chi") || text.contains("zho") || text.contains("chs") + || text.contains("cht") || text.contains("cmn") || text.contains("\u4e2d") + || text.contains("\u56fd\u8bed") || text.contains("\u666e\u901a\u8bdd")) { + return "\u56fd\u8bed"; + } + if (text.contains("en") || text.contains("eng") || text.contains("english") || text.contains("\u82f1")) { + return "\u82f1\u8bed"; + } + if (text.contains("ja") || text.contains("jpn") || text.contains("japanese") || text.contains("\u65e5")) { + return "\u65e5\u8bed"; + } + if (text.contains("ko") || text.contains("kor") || text.contains("korean") || text.contains("\u97e9")) { + return "\u97e9\u8bed"; + } + if (text.contains("tha") || text.contains("thai") || text.contains("th")) { + return "\u6cf0\u8bed"; + } + return ""; + } + + private String buildDisplayName(String prefix, int number, String language, String detail) { + StringBuilder builder = new StringBuilder(prefix).append(" ").append(number); + if (language != null && !language.isEmpty()) { + builder.append(" - ").append(language); + } + if (detail != null && !detail.isEmpty()) { + builder.append(" ").append(detail); + } + return builder.toString(); + } + + public void setTrack(int trackIndex) { + int audioSelected = mMediaPlayer.getSelectedTrack(ITrackInfo.MEDIA_TRACK_TYPE_AUDIO); + int subtitleSelected = mMediaPlayer.getSelectedTrack(ITrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT); + if (trackIndex!=audioSelected && trackIndex!=subtitleSelected){ + mMediaPlayer.selectTrack(trackIndex); + } + } + public void setTrack(int trackIndex,String playKey) { + int audioSelected = mMediaPlayer.getSelectedTrack(ITrackInfo.MEDIA_TRACK_TYPE_AUDIO); + if (trackIndex!=audioSelected){ + if (!playKey.isEmpty()) { + memory.save(playKey, trackIndex); + } + mMediaPlayer.selectTrack(trackIndex); + } + } + + public void setOnTimedTextListener(IMediaPlayer.OnTimedTextListener listener) { + mMediaPlayer.setOnTimedTextListener(listener); + } + + public void loadDefaultTrack(TrackInfo trackInfo,String playKey) { + if(trackInfo!=null && trackInfo.getAudio().size()>1){ + Integer trackIndex = memory.ijkLoad(playKey); + if (trackIndex == -1) { + int firsIndex=trackInfo.getAudio().get(0).index; + setTrack(firsIndex); + return; + }; + setTrack(trackIndex); + } } } diff --git a/app/src/main/java/com/github/tvbox/osc/player/MyVideoView.java b/app/src/main/java/com/github/tvbox/osc/player/MyVideoView.java new file mode 100644 index 0000000000..5b9e0685b1 --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/player/MyVideoView.java @@ -0,0 +1,103 @@ +package com.github.tvbox.osc.player; + +import android.content.Context; +import android.util.AttributeSet; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import master.flame.danmaku.controller.DrawHandler; +import master.flame.danmaku.danmaku.model.BaseDanmaku; +import master.flame.danmaku.danmaku.model.DanmakuTimer; +import master.flame.danmaku.ui.widget.DanmakuView; +import xyz.doikki.videoplayer.player.AbstractPlayer; +import xyz.doikki.videoplayer.player.VideoView; + +public class MyVideoView extends VideoView implements DrawHandler.Callback { + private DanmakuView danmuView; + + public MyVideoView(@NonNull Context context) { + super(context, null); + } + + public MyVideoView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs, 0); + } + + public MyVideoView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public AbstractPlayer getMediaPlayer() { + return mMediaPlayer; + } + + public int[] getVideoSize() { + return mVideoSize; + } + + @Override + public void seekTo(long pos) { + super.seekTo(pos); + if (haveDanmu()) danmuView.seekTo(pos); + } + + @Override + public void resume() { + super.resume(); + if (haveDanmu()) danmuView.resume(); + } + + @Override + public void start() { + super.start(); + if (haveDanmu()) danmuView.resume(); + } + + @Override + public void pause() { + super.pause(); + if (haveDanmu()) danmuView.pause(); + } + + @Override + public void release() { + super.release(); + if (haveDanmu()) danmuView.release(); + } + + private boolean haveDanmu() { + return danmuView != null && danmuView.isPrepared(); + } + + public void setDanmuView(DanmakuView view) { + danmuView = view; + if (danmuView != null) danmuView.setCallback(this); + } + + public DanmakuView getDanmuView() { + return danmuView; + } + + @Override + public void prepared() { + post(() -> { + if (danmuView == null) return; + if (isPlaying() && danmuView.isPrepared()) { + danmuView.start(getCurrentPosition()); + } + }); + } + + @Override + public void updateTimer(DanmakuTimer timer) { + } + + @Override + public void danmakuShown(BaseDanmaku danmaku) { + } + + @Override + public void drawingFinished() { + } +} diff --git a/app/src/main/java/com/github/tvbox/osc/player/TrackInfo.java b/app/src/main/java/com/github/tvbox/osc/player/TrackInfo.java new file mode 100644 index 0000000000..57c904151f --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/player/TrackInfo.java @@ -0,0 +1,47 @@ +package com.github.tvbox.osc.player; + +import java.util.ArrayList; +import java.util.List; + +public class TrackInfo { + private List audio; + private List subtitle; + + public TrackInfo() { + audio = new ArrayList<>(); + subtitle = new ArrayList<>(); + } + + public List getAudio() { + return audio; + } + + public int getAudioSelected(boolean track) { + return getSelected(audio, track); + } + + public int getSubtitleSelected(boolean track) { + return getSelected(subtitle, track); + } + + public int getSelected(List list, boolean track) { + int i = 0; + for (TrackInfoBean trackInfoBean : list) { + if (trackInfoBean.selected) return track ? trackInfoBean.trackId : i; + i++; + } + return 99999; + } + + public void addAudio(TrackInfoBean audio) { + this.audio.add(audio); + } + + public List getSubtitle() { + return subtitle; + } + + public void addSubtitle(TrackInfoBean subtitle) { + this.subtitle.add(subtitle); + } +} diff --git a/app/src/main/java/com/github/tvbox/osc/player/TrackInfoBean.java b/app/src/main/java/com/github/tvbox/osc/player/TrackInfoBean.java new file mode 100644 index 0000000000..47ba106fa8 --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/player/TrackInfoBean.java @@ -0,0 +1,12 @@ +package com.github.tvbox.osc.player; + +public class TrackInfoBean { + public int trackId; + public int renderId; + public int trackGroupId; + public String name; + public String language; + public int groupIndex; + public int index; + public boolean selected; +} diff --git a/app/src/main/java/com/github/tvbox/osc/player/controller/BaseController.java b/app/src/main/java/com/github/tvbox/osc/player/controller/BaseController.java index 342fea80ad..e1fd816624 100644 --- a/app/src/main/java/com/github/tvbox/osc/player/controller/BaseController.java +++ b/app/src/main/java/com/github/tvbox/osc/player/controller/BaseController.java @@ -118,6 +118,7 @@ protected void onPlayStateChanged(int playState) { super.onPlayStateChanged(playState); switch (playState) { case VideoView.STATE_IDLE: + mPauseRoot.setVisibility(GONE); mLoading.setVisibility(GONE); break; case VideoView.STATE_PLAYING: @@ -131,10 +132,12 @@ protected void onPlayStateChanged(int playState) { case VideoView.STATE_PREPARED: case VideoView.STATE_ERROR: case VideoView.STATE_BUFFERED: + mPauseRoot.setVisibility(GONE); mLoading.setVisibility(GONE); break; case VideoView.STATE_PREPARING: case VideoView.STATE_BUFFERING: + mPauseRoot.setVisibility(GONE); mLoading.setVisibility(VISIBLE); break; case VideoView.STATE_PLAYBACK_COMPLETED: @@ -172,6 +175,12 @@ public void setDoubleTapTogglePlayEnabled(boolean enabled) { mIsDoubleTapTogglePlayEnabled = enabled; } + public void hidePauseRoot() { + if (mPauseRoot != null) { + mPauseRoot.setVisibility(GONE); + } + } + @Override public void setPlayerState(int playerState) { super.setPlayerState(playerState); @@ -198,6 +207,14 @@ protected boolean isInPlaybackState() { && mCurPlayState != VideoView.STATE_PLAYBACK_COMPLETED; } + protected boolean canHandleGesture(MotionEvent event) { + return isInPlaybackState() + && mIsGestureEnabled + && mCanSlide + && !isLocked() + && !PlayerUtils.isEdge(getContext(), event); + } + @Override public boolean onTouch(View v, MotionEvent event) { return mGestureDetector.onTouchEvent(event); @@ -251,12 +268,7 @@ public boolean onDoubleTap(MotionEvent e) { */ @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { - if (!isInPlaybackState() //不处于播放状态 - || !mIsGestureEnabled //关闭了手势 - || !mCanSlide //关闭了滑动手势 - || isLocked() //锁住了屏幕 - || PlayerUtils.isEdge(getContext(), e1)) //处于屏幕边沿 - return true; + if (!canHandleGesture(e1)) return true; float deltaX = e1.getX() - e2.getX(); float deltaY = e1.getY() - e2.getY(); if (mFirstTouch) { @@ -299,8 +311,8 @@ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float d protected void slideToChangePosition(float deltaX) { deltaX = -deltaX; int width = getMeasuredWidth(); - int duration = (int) mControlWrapper.getDuration(); - int currentPosition = (int) mControlWrapper.getCurrentPosition(); + int duration = PlayerUtils.safeTimeMs(mControlWrapper.getDuration()); + int currentPosition = PlayerUtils.safeTimeMs(mControlWrapper.getCurrentPosition()); int position = (int) (deltaX / width * 120000 + currentPosition); if (position > duration) position = duration; if (position < 0) position = 0; diff --git a/app/src/main/java/com/github/tvbox/osc/player/controller/VodController.java b/app/src/main/java/com/github/tvbox/osc/player/controller/VodController.java index fe22096db4..8622032eda 100644 --- a/app/src/main/java/com/github/tvbox/osc/player/controller/VodController.java +++ b/app/src/main/java/com/github/tvbox/osc/player/controller/VodController.java @@ -1,43 +1,99 @@ package com.github.tvbox.osc.player.controller; +import android.app.Activity; +import android.annotation.SuppressLint; import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.Handler; import android.os.Message; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; +import android.webkit.WebView; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.SeekBar; import android.widget.TextView; +import android.widget.Toast; import androidx.annotation.NonNull; +import androidx.recyclerview.widget.DiffUtil; import com.chad.library.adapter.base.BaseQuickAdapter; import com.github.tvbox.osc.R; import com.github.tvbox.osc.api.ApiConfig; +import com.github.tvbox.osc.base.App; import com.github.tvbox.osc.bean.IJKCode; import com.github.tvbox.osc.bean.ParseBean; -import com.github.tvbox.osc.player.thirdparty.MXPlayer; -import com.github.tvbox.osc.player.thirdparty.ReexPlayer; +import com.github.tvbox.osc.bean.SourceBean; +import com.github.tvbox.osc.server.ControlManager; +import com.github.tvbox.osc.server.RemoteServer; +import com.github.tvbox.osc.subtitle.widget.SimpleSubtitleView; import com.github.tvbox.osc.ui.adapter.ParseAdapter; +import com.github.tvbox.osc.ui.adapter.SelectDialogAdapter; +import com.github.tvbox.osc.ui.dialog.SelectDialog; +import com.github.tvbox.osc.util.FastClickCheckUtil; import com.github.tvbox.osc.util.HawkConfig; +import com.github.tvbox.osc.util.LOG; +import com.github.tvbox.osc.util.M3u8; import com.github.tvbox.osc.util.PlayerHelper; +import com.github.tvbox.osc.util.ScreenUtils; +import com.github.tvbox.osc.util.SubtitleHelper; +import com.github.tvbox.osc.util.VideoParseRuler; +import com.github.tvbox.osc.util.thunder.Jianpian; +import com.github.tvbox.osc.util.thunder.Thunder; +import com.lzy.okgo.OkGo; +import com.lzy.okgo.callback.AbsCallback; +import com.lzy.okgo.model.HttpHeaders; +import com.lzy.okgo.model.Response; import com.orhanobut.hawk.Hawk; import com.owen.tvrecyclerview.widget.TvRecyclerView; import com.owen.tvrecyclerview.widget.V7LinearLayoutManager; import org.jetbrains.annotations.NotNull; +import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import org.xwalk.core.XWalkView; +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Date; +import java.util.Map; + import xyz.doikki.videoplayer.player.VideoView; -import xyz.doikki.videoplayer.util.PlayerUtils; import static xyz.doikki.videoplayer.util.PlayerUtils.stringForTime; +import static xyz.doikki.videoplayer.util.PlayerUtils.seconds2Time; +import static xyz.doikki.videoplayer.util.PlayerUtils.safeTimeMs; public class VodController extends BaseController { + private static final float PORTRAIT_EPISODE_SWIPE_DP = 80f; + private static final long PORTRAIT_EPISODE_TITLE_SHOW_MS = 3000L; + private boolean portraitEpisodeSwipeTriggered; + private final Runnable portraitEpisodeTitleRunnable = new Runnable() { + @Override + public void run() { + if (!isBottomVisible()) mTopRoot1.setVisibility(GONE); + } + }; + public VodController(@NonNull @NotNull Context context) { super(context); mHandlerCallback = new HandlerCallback() { @@ -53,11 +109,36 @@ public void callback(Message msg) { break; } case 1002: { // 显示底部菜单 + updateDanmuSearchUiBtn(); mBottomRoot.setVisibility(VISIBLE); + mTopRoot1.setVisibility(VISIBLE); + mTopRoot2.setVisibility(VISIBLE); + mPlayLoadNetSpeedRightTop.setVisibility(VISIBLE); + if(Hawk.get(HawkConfig.SCREEN_DISPLAY,GONE)==GONE){ + mPlayPauseTime.setVisibility(VISIBLE); + }else { + net_play_speed.setVisibility(GONE); + } + mPlayTitle.setVisibility(GONE); + boolean isPortrait = getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; + backBtn.setVisibility(ScreenUtils.isTv(context) || isPortrait ? INVISIBLE : VISIBLE); + showLockView(); break; } case 1003: { // 隐藏底部菜单 mBottomRoot.setVisibility(GONE); + mTopRoot1.setVisibility(GONE); + mPlayLoadNetSpeedRightTop.setVisibility(GONE); + if(Hawk.get(HawkConfig.SCREEN_DISPLAY,GONE)==GONE){ + mPlayPauseTime.setVisibility(GONE); + }else { + net_play_speed.setVisibility(VISIBLE); + } + backBtn.setVisibility(INVISIBLE); + mHandler.removeCallbacks(lockRunnable); + if (mLockView != null) { + mLockView.setVisibility(INVISIBLE); + } break; } case 1004: { // 设置速度 @@ -77,27 +158,149 @@ public void callback(Message msg) { }; } + @Override + public boolean onDown(MotionEvent e) { + portraitEpisodeSwipeTriggered = false; + return super.onDown(e); + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + if (isPortraitEpisodeSwipe(e1, e2)) { + if (!portraitEpisodeSwipeTriggered && Math.abs(e2.getY() - e1.getY()) >= portraitEpisodeSwipeThreshold()) { + portraitEpisodeSwipeTriggered = true; + if (listener != null) { + if (e2.getY() < e1.getY()) listener.playNext(false); + else listener.playPre(); + showPortraitEpisodeTitle(); + } + } + return true; + } + return super.onScroll(e1, e2, distanceX, distanceY); + } + + private boolean isPortraitEpisodeSwipe(MotionEvent e1, MotionEvent e2) { + if (e1 == null || e2 == null || !canHandleGesture(e1)) return false; + if (getResources().getConfiguration().orientation != Configuration.ORIENTATION_PORTRAIT) return false; + return Math.abs(e2.getY() - e1.getY()) > Math.abs(e2.getX() - e1.getX()); + } + + private float portraitEpisodeSwipeThreshold() { + return getResources().getDisplayMetrics().density * PORTRAIT_EPISODE_SWIPE_DP; + } + + private void showPortraitEpisodeTitle() { + if (isBottomVisible()) return; + mHandler.removeCallbacks(portraitEpisodeTitleRunnable); + mTopRoot1.setVisibility(VISIBLE); + mHandler.postDelayed(portraitEpisodeTitleRunnable, PORTRAIT_EPISODE_TITLE_SHOW_MS); + } + SeekBar mSeekBar; TextView mCurrentTime; TextView mTotalTime; boolean mIsDragging; + private boolean mSeekBarKeyTracking; LinearLayout mProgressRoot; TextView mProgressText; ImageView mProgressIcon; + ImageView mLockView; LinearLayout mBottomRoot; + LinearLayout mPlayBtnGroup; + LinearLayout mTopRoot1; + LinearLayout mTopRoot2; LinearLayout mParseRoot; - TvRecyclerView mGridView; + TvRecyclerView mGridParseView; TextView mPlayTitle; + TextView mPlayTitle1; + TextView mPlayLabel; + TextView mPlayLoadNetSpeedRightTop; TextView mNextBtn; TextView mPreBtn; TextView mPlayerScaleBtn; - TextView mPlayerSpeedBtn; + public TextView mPlayerSpeedBtn; TextView mPlayerBtn; TextView mPlayerIJKBtn; TextView mPlayerRetry; - TextView mPlayerTimeStartBtn; - TextView mPlayerTimeSkipBtn; - TextView mPlayerTimeStepBtn; + TextView mPlayrefresh; + public TextView mPlayerTimeStartEndText; + public TextView mPlayerTimeStartBtn; + public TextView mPlayerTimeSkipBtn; + public TextView mPlayerTimeResetBtn; + TextView mPlayPauseTime; + TextView mPlayLoadNetSpeed; + TextView mVideoSize; + public SimpleSubtitleView mSubtitleView; + TextView mZimuBtn; + TextView mAudioTrackBtn; + TextView mDanmuSettingBtn; + TextView mDanmuSearchUiBtn; + public TextView mLandscapePortraitBtn; + private View backBtn;//返回键 + private boolean isClickBackBtn; + TextView seekTime; //右上角进度时间显示 + TextView mScreenDisplay; //增加屏显开关 + LinearLayout tv_screen_display; //增加屏显布局 + TextView mCastBtn; + TextView net_play_speed; + private boolean hasDanmu = false; + private boolean showParse; + + LockRunnable lockRunnable = new LockRunnable(); + private boolean isLock = false; + private boolean previewMode = false; + Handler myHandle; + Runnable myRunnable; + int myHandleSeconds = 10000;//闲置多少毫秒秒关闭底栏 默认6秒 + + int videoPlayState = 0; + + private final Runnable myRunnable2 = new Runnable() { + @SuppressLint("SetTextI18n") + @Override + public void run() { + Date date = new Date(); + @SuppressLint("SimpleDateFormat") SimpleDateFormat timeFormat = new SimpleDateFormat("hh:mm a"); + mPlayPauseTime.setText(timeFormat.format(date)); + long mSpeed = mControlWrapper.getTcpSpeed(); + String speed = PlayerHelper.getDisplaySpeed(mSpeed,false); + String speedBps = PlayerHelper.getDisplaySpeedBps(mSpeed,true); + mPlayLoadNetSpeedRightTop.setText(speedBps); + mPlayLoadNetSpeed.setText(speed); + net_play_speed.setText(speedBps); + int[] mVideoSizes = mControlWrapper.getVideoSize(); + String width = Integer.toString(mVideoSizes[0]); + String height = Integer.toString(mVideoSizes[1]); + mVideoSize.setText("[ " + width + " X " + height +" ]"); + + mHandler.postDelayed(this, 1000); + } + }; + + private void showLockView() { + if (previewMode || getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { + if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) isLock = false; + mHandler.removeCallbacks(lockRunnable); + if (mLockView != null) { + mLockView.setVisibility(INVISIBLE); + } + return; + } + mLockView.setVisibility(ScreenUtils.isTv(getContext()) ? INVISIBLE : VISIBLE); + mHandler.removeCallbacks(lockRunnable); + if (isLock) { + mHandler.postDelayed(lockRunnable, 3000); + } + } + + public void setPreviewMode(boolean previewMode) { + this.previewMode = previewMode; + mHandler.removeCallbacks(lockRunnable); + if (mLockView != null) { + mLockView.setVisibility(INVISIBLE); + } + } @Override protected void initView() { @@ -105,25 +308,112 @@ protected void initView() { mCurrentTime = findViewById(R.id.curr_time); mTotalTime = findViewById(R.id.total_time); mPlayTitle = findViewById(R.id.tv_info_name); + mPlayTitle1 = findViewById(R.id.tv_info_name1); + mPlayLabel = findViewById(R.id.play_label); + mPlayLoadNetSpeedRightTop = findViewById(R.id.tv_play_load_net_speed_right_top); mSeekBar = findViewById(R.id.seekBar); + CircleThumbDrawable seekThumb = new CircleThumbDrawable(getContext()); + mSeekBar.setThumb(seekThumb); + mSeekBar.setThumbOffset(seekThumb.getIntrinsicWidth() / 2); mProgressRoot = findViewById(R.id.tv_progress_container); mProgressIcon = findViewById(R.id.tv_progress_icon); mProgressText = findViewById(R.id.tv_progress_text); mBottomRoot = findViewById(R.id.bottom_container); + mTopRoot1 = findViewById(R.id.tv_top_l_container); + mTopRoot2 = findViewById(R.id.tv_top_r_container); + mPlayBtnGroup = findViewById(R.id.play_btn_group); + tv_screen_display = findViewById(R.id.tv_screen_display); + net_play_speed = findViewById(R.id.net_play_speed); mParseRoot = findViewById(R.id.parse_root); - mGridView = findViewById(R.id.mGridView); + mGridParseView = findViewById(R.id.mGridParseView); mPlayerRetry = findViewById(R.id.play_retry); + mPlayrefresh = findViewById(R.id.play_refresh); mNextBtn = findViewById(R.id.play_next); mPreBtn = findViewById(R.id.play_pre); mPlayerScaleBtn = findViewById(R.id.play_scale); mPlayerSpeedBtn = findViewById(R.id.play_speed); mPlayerBtn = findViewById(R.id.play_player); mPlayerIJKBtn = findViewById(R.id.play_ijk); + mPlayerTimeStartEndText = findViewById(R.id.play_time_start_end_text); mPlayerTimeStartBtn = findViewById(R.id.play_time_start); mPlayerTimeSkipBtn = findViewById(R.id.play_time_end); - mPlayerTimeStepBtn = findViewById(R.id.play_time_step); + mPlayerTimeResetBtn = findViewById(R.id.play_time_reset); + mPlayPauseTime = findViewById(R.id.tv_sys_time); + mPlayLoadNetSpeed = findViewById(R.id.tv_play_load_net_speed); + mVideoSize = findViewById(R.id.tv_videosize); + mSubtitleView = findViewById(R.id.subtitle_view); + mZimuBtn = findViewById(R.id.zimu_select); + mAudioTrackBtn = findViewById(R.id.audio_track_select); + mDanmuSettingBtn = findViewById(R.id.danmu_setting); + mDanmuSearchUiBtn = findViewById(R.id.danmu_search_ui); + mLandscapePortraitBtn = findViewById(R.id.landscape_portrait); + backBtn = findViewById(R.id.tv_back); + seekTime = findViewById(R.id.tv_seek_time); + mScreenDisplay = findViewById(R.id.screen_display); + mCastBtn = findViewById(R.id.play_cast); + updateDanmuSearchUiBtn(); + backBtn.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { + setLandscapePortrait(); + hideBottom(); + return; + } + if (getContext() instanceof Activity) { + isClickBackBtn = true; + ((Activity) getContext()).onBackPressed(); + } + } + }); + mLockView = findViewById(R.id.tv_lock); + mLockView.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + isLock = !isLock; + mLockView.setImageResource(isLock ? R.drawable.icon_lock : R.drawable.icon_unlock); + if (isLock) { + Message obtain = Message.obtain(); + obtain.what = 1003;//隐藏底部菜单 + mHandler.sendMessage(obtain); + } + showLockView(); + } + }); + View rootView = findViewById(R.id.rootView); + rootView.setOnTouchListener(new OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + if (previewMode) { + return false; + } + if (isLock) { + if (event.getAction() == MotionEvent.ACTION_UP) { + showLockView(); + } + } + return isLock; + } + }); + + initSubtitleInfo(); + + myHandle = new Handler(); + myRunnable = new Runnable() { + @Override + public void run() { + hideBottom(); + } + }; + + mPlayPauseTime.post(new Runnable() { + @Override + public void run() { + mHandler.post(myRunnable2); + } + }); - mGridView.setLayoutManager(new V7LinearLayoutManager(getContext(), 0, false)); + mGridParseView.setLayoutManager(new V7LinearLayoutManager(getContext(), 0, false)); ParseAdapter parseAdapter = new ParseAdapter(); parseAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() { @Override @@ -138,11 +428,9 @@ public void onItemClick(BaseQuickAdapter adapter, View view, int position) { hideBottom(); } }); - mGridView.setAdapter(parseAdapter); + mGridParseView.setAdapter(parseAdapter); parseAdapter.setNewData(ApiConfig.get().getParseBeanList()); - mParseRoot.setVisibility(VISIBLE); - mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { @@ -150,33 +438,124 @@ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { return; } - long duration = mControlWrapper.getDuration(); - long newPosition = (duration * progress) / seekBar.getMax(); - if (mCurrentTime != null) - mCurrentTime.setText(stringForTime((int) newPosition)); + updateSeekBarTime(progress); } @Override public void onStartTrackingTouch(SeekBar seekBar) { - mIsDragging = true; - mControlWrapper.stopProgress(); - mControlWrapper.stopFadeOut(); + beginSeekBarTracking(); } @Override public void onStopTrackingTouch(SeekBar seekBar) { - long duration = mControlWrapper.getDuration(); - long newPosition = (duration * seekBar.getProgress()) / seekBar.getMax(); - mControlWrapper.seekTo((int) newPosition); - mIsDragging = false; - mControlWrapper.startProgress(); - mControlWrapper.startFadeOut(); + finishSeekBarTracking(); + } + }); + mSeekBar.setOnHoverListener(new OnHoverListener() { + @Override + public boolean onHover(View v, MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_HOVER_ENTER: + keepSeekBarActive(); + v.setSelected(true); + v.refreshDrawableState(); + v.jumpDrawablesToCurrentState(); + v.invalidate(); + break; + case MotionEvent.ACTION_HOVER_MOVE: + keepSeekBarActive(); + break; + case MotionEvent.ACTION_HOVER_EXIT: + v.setSelected(false); + v.refreshDrawableState(); + v.jumpDrawablesToCurrentState(); + v.invalidate(); + break; + } + return false; + } + }); + mSeekBar.setOnFocusChangeListener(new OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (hasFocus) { + boolean hasParse = mParseRoot.getVisibility() == VISIBLE && mGridParseView.getAdapter() != null && mGridParseView.getAdapter().getItemCount() > 0; + mSeekBar.setNextFocusUpId(hasParse ? R.id.mGridParseView : R.id.play_next); + keepSeekBarActive(); + v.setSelected(true); + v.refreshDrawableState(); + v.jumpDrawablesToCurrentState(); + v.invalidate(); + } else { + v.setSelected(false); + v.refreshDrawableState(); + v.jumpDrawablesToCurrentState(); + v.invalidate(); + if (mSeekBarKeyTracking) { + finishSeekBarTracking(); + } + } + } + }); + mSeekBar.setOnKeyListener(new OnKeyListener() { + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (keyCode != KeyEvent.KEYCODE_DPAD_LEFT && keyCode != KeyEvent.KEYCODE_DPAD_RIGHT) { + return false; + } + if (!isInPlaybackState()) { + return true; + } + if (event.getAction() == KeyEvent.ACTION_DOWN) { + beginSeekBarTracking(); + mSeekBarKeyTracking = true; + v.setSelected(true); + v.jumpDrawablesToCurrentState(); + v.invalidate(); + moveSeekBarByKey(keyCode == KeyEvent.KEYCODE_DPAD_RIGHT ? 1 : -1); + return true; + } else if (event.getAction() == KeyEvent.ACTION_UP) { + v.setSelected(v.hasFocus()); + v.jumpDrawablesToCurrentState(); + v.invalidate(); + if (mSeekBarKeyTracking) { + finishSeekBarTracking(); + } + return true; + } + return true; + } + }); + mSeekBar.setOnGenericMotionListener(new OnGenericMotionListener() { + @Override + public boolean onGenericMotion(View v, MotionEvent event) { + if (event.getAction() != MotionEvent.ACTION_SCROLL || !isInPlaybackState()) { + return false; + } + float scroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL); + if (scroll == 0) { + scroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL); + } + if (scroll == 0) { + return false; + } + beginSeekBarTracking(); + moveSeekBarByKey(scroll > 0 ? 1 : -1); + finishSeekBarTracking(); + return true; } }); mPlayerRetry.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - listener.replay(); + listener.replay(true); + hideBottom(); + } + }); + mPlayrefresh.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + listener.replay(false); hideBottom(); } }); @@ -197,6 +576,8 @@ public void onClick(View view) { mPlayerScaleBtn.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { + myHandle.removeCallbacks(myRunnable); + myHandle.postDelayed(myRunnable, myHandleSeconds); try { int scaleType = mPlayerConfig.getInt("sc"); scaleType++; @@ -214,6 +595,8 @@ public void onClick(View view) { mPlayerSpeedBtn.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { + myHandle.removeCallbacks(myRunnable); + myHandle.postDelayed(myRunnable, myHandleSeconds); try { float speed = (float) mPlayerConfig.getDouble("sp"); speed += 0.25f; @@ -222,44 +605,127 @@ public void onClick(View view) { mPlayerConfig.put("sp", speed); updatePlayerCfgView(); listener.updatePlayerCfg(); + speed_old = speed; mControlWrapper.setSpeed(speed); } catch (JSONException e) { e.printStackTrace(); } } }); + + mPlayerSpeedBtn.setOnLongClickListener(new OnLongClickListener() { + @Override + public boolean onLongClick(View view) { + try { + mPlayerConfig.put("sp", 1.0f); + updatePlayerCfgView(); + listener.updatePlayerCfg(); + speed_old = 1.0f; + mControlWrapper.setSpeed(1.0f); + } catch (JSONException e) { + e.printStackTrace(); + } + return true; + } + }); mPlayerBtn.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { + myHandle.removeCallbacks(myRunnable); + myHandle.postDelayed(myRunnable, myHandleSeconds); try { int playerType = mPlayerConfig.getInt("pl"); - boolean playerVail = false; - do { - playerType++; - if (playerType <= 2) { - playerVail = true; - } else if (playerType == 10) { - playerVail = mxPlayerExist; - } else if (playerType == 11) { - playerVail = reexPlayerExist; - } else if (playerType > 11) { - playerType = 0; - playerVail = true; + ArrayList exsitPlayerTypes = PlayerHelper.getExistPlayerTypes(); + int playerTypeIdx = 0; + int playerTypeSize = exsitPlayerTypes.size(); + for(int i = 0; i players = PlayerHelper.getExistPlayerTypes(); + ArrayList renders = new ArrayList<>(); + for(int p = 0; p dialog = new SelectDialog<>(mActivity); + dialog.setTip("请选择播放器"); + dialog.setAdapter(new SelectDialogAdapter.SelectDialogInterface() { + @Override + public void click(Integer value, int pos) { + try { + dialog.cancel(); + int thisPlayType = players.get(pos); + if (thisPlayType != playerType) { + mPlayerConfig.put("pl", thisPlayType); + updatePlayerCfgView(); + listener.updatePlayerCfg(); + listener.replay(false); + listener.setAllowSwitchPlayer(false); + hideBottom(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public String getDisplay(Integer val) { + Integer playerType = players.get(val); + return PlayerHelper.getPlayerName(playerType); + } + }, new DiffUtil.ItemCallback() { + @Override + public boolean areItemsTheSame(@NonNull @NotNull Integer oldItem, @NonNull @NotNull Integer newItem) { + return oldItem.intValue() == newItem.intValue(); + } + + @Override + public boolean areContentsTheSame(@NonNull @NotNull Integer oldItem, @NonNull @NotNull Integer newItem) { + return oldItem.intValue() == newItem.intValue(); + } + }, renders, defaultPos); + dialog.show(); + } catch (JSONException e) { + e.printStackTrace(); + } + return true; + } + }); mPlayerIJKBtn.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { + myHandle.removeCallbacks(myRunnable); + myHandle.postDelayed(myRunnable, myHandleSeconds); try { String ijk = mPlayerConfig.getString("ijk"); List codecs = ApiConfig.get().getIjkCodes(); @@ -276,40 +742,69 @@ public void onClick(View view) { mPlayerConfig.put("ijk", ijk); updatePlayerCfgView(); listener.updatePlayerCfg(); - listener.replay(); + listener.replay(false); hideBottom(); } catch (JSONException e) { e.printStackTrace(); } } }); +// 增加播放页面片头片尾时间重置 + mPlayerTimeResetBtn.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + myHandle.removeCallbacks(myRunnable); + myHandle.postDelayed(myRunnable, myHandleSeconds); + try { + mPlayerConfig.put("et", 0); + mPlayerConfig.put("st", 0); + updatePlayerCfgView(); + listener.updatePlayerCfg(); + } catch (JSONException e) { + e.printStackTrace(); + } + } + }); mPlayerTimeStartBtn.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { + myHandle.removeCallbacks(myRunnable); + myHandle.postDelayed(myRunnable, myHandleSeconds); + try { + int current = safeTimeMs(mControlWrapper.getCurrentPosition()); + int duration = safeTimeMs(mControlWrapper.getDuration()); + if (current > duration / 2) return; + mPlayerConfig.put("st",current/1000); + updatePlayerCfgView(); + listener.updatePlayerCfg(); + } catch (JSONException e) { + e.printStackTrace(); + } + } + }); + mPlayerTimeStartBtn.setOnLongClickListener(new OnLongClickListener() { + @Override + public boolean onLongClick(View view) { try { - int step = Hawk.get(HawkConfig.PLAY_TIME_STEP, 5); - int st = mPlayerConfig.getInt("st"); - st += step; - if (st > 60 * 10) - st = 0; - mPlayerConfig.put("st", st); + mPlayerConfig.put("st", 0); updatePlayerCfgView(); listener.updatePlayerCfg(); } catch (JSONException e) { e.printStackTrace(); } + return true; } }); mPlayerTimeSkipBtn.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { + myHandle.removeCallbacks(myRunnable); + myHandle.postDelayed(myRunnable, myHandleSeconds); try { - int step = Hawk.get(HawkConfig.PLAY_TIME_STEP, 5); - int et = mPlayerConfig.getInt("et"); - et += step; - if (et > 60 * 10) - et = 0; - mPlayerConfig.put("et", et); + int current = safeTimeMs(mControlWrapper.getCurrentPosition()); + int duration = safeTimeMs(mControlWrapper.getDuration()); + if (current < duration / 2) return; + mPlayerConfig.put("et", (duration - current)/1000); updatePlayerCfgView(); listener.updatePlayerCfg(); } catch (JSONException e) { @@ -317,18 +812,205 @@ public void onClick(View view) { } } }); - mPlayerTimeStepBtn.setOnClickListener(new OnClickListener() { + mPlayerTimeSkipBtn.setOnLongClickListener(new OnLongClickListener() { @Override - public void onClick(View view) { - int step = Hawk.get(HawkConfig.PLAY_TIME_STEP, 5); - step += 5; - if (step > 30) { - step = 5; + public boolean onLongClick(View view) { + try { + mPlayerConfig.put("et", 0); + updatePlayerCfgView(); + listener.updatePlayerCfg(); + } catch (JSONException e) { + e.printStackTrace(); } - Hawk.put(HawkConfig.PLAY_TIME_STEP, step); - updatePlayerCfgView(); + return true; } }); + mZimuBtn.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + FastClickCheckUtil.check(view); + listener.selectSubtitle(); + hideBottom(); + } + }); + mZimuBtn.setOnLongClickListener(new OnLongClickListener() { + @Override + public boolean onLongClick(View view) { + mSubtitleView.setVisibility(View.GONE); + mSubtitleView.destroy(); + mSubtitleView.clearSubtitleCache(); + mSubtitleView.isInternal = false; + hideBottom(); + Toast.makeText(getContext(), "字幕已关闭", Toast.LENGTH_SHORT).show(); + return true; + } + }); + mAudioTrackBtn.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + FastClickCheckUtil.check(view); + listener.selectAudioTrack(); + hideBottom(); + } + }); + mDanmuSettingBtn.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + FastClickCheckUtil.check(view); + listener.showDanmuSetting(); + } + }); + mDanmuSearchUiBtn.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + listener.searchDanmuUi(false); + hideBottom(); + } + }); + mDanmuSearchUiBtn.setOnLongClickListener(new OnLongClickListener() { + @Override + public boolean onLongClick(View view) { + listener.searchDanmuUi(true); + hideBottom(); + return true; + } + }); + mLandscapePortraitBtn.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + FastClickCheckUtil.check(view); + setLandscapePortrait(); + hideBottom(); + } + }); + //屏显 + int disPlay = Hawk.get(HawkConfig.SCREEN_DISPLAY, GONE); + mTopRoot2.setVisibility(disPlay); + seekTime.setVisibility(disPlay); + net_play_speed.setVisibility(disPlay); + mPlayPauseTime.setVisibility(disPlay); + mScreenDisplay.setTextColor(disPlay==VISIBLE?getResources().getColor(R.color.color_02F8E1): Color.WHITE); + mScreenDisplay.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + int disPlay =(Hawk.get(HawkConfig.SCREEN_DISPLAY, GONE) == VISIBLE) ? GONE : VISIBLE; + seekTime.setVisibility(disPlay); + net_play_speed.setVisibility(disPlay); + if(disPlay==VISIBLE)mPlayPauseTime.setVisibility(disPlay); + Hawk.put(HawkConfig.SCREEN_DISPLAY, disPlay); + mScreenDisplay.setTextColor(disPlay==VISIBLE?getResources().getColor(R.color.color_02F8E1): Color.WHITE); + hideBottom(); + } + }); + mCastBtn.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + if (listener != null) listener.clickCast(); + } + }); + if (Build.VERSION.SDK_INT < 30) { + mCastBtn.setVisibility(GONE); + } else { + mCastBtn.setVisibility(VISIBLE); + } + mScreenDisplay.setNextFocusRightId(R.id.play_next); + mNextBtn.setNextFocusLeftId(R.id.screen_display); + } + + private void hideLiveAboutBtn() { + if (mControlWrapper != null && mControlWrapper.getDuration() == 0) { + mPlayerSpeedBtn.setVisibility(GONE); + mPlayerTimeStartEndText.setVisibility(GONE); + mPlayerTimeStartBtn.setVisibility(GONE); + mPlayerTimeSkipBtn.setVisibility(GONE); + mPlayerTimeResetBtn.setVisibility(GONE); + } else { + mPlayerSpeedBtn.setVisibility(View.VISIBLE); + mPlayerTimeStartEndText.setVisibility(View.VISIBLE); + mPlayerTimeStartBtn.setVisibility(View.VISIBLE); + mPlayerTimeSkipBtn.setVisibility(View.VISIBLE); + mPlayerTimeResetBtn.setVisibility(View.VISIBLE); + } + } + + public void initLandscapePortraitBtnInfo() { + boolean isPortrait = getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; + if (mCurrentTime != null) mCurrentTime.setVisibility(isPortrait ? GONE : VISIBLE); + if (mTotalTime != null) mTotalTime.setVisibility(isPortrait ? GONE : VISIBLE); + if (isPortrait) { + if (backBtn != null) backBtn.setVisibility(INVISIBLE); + if (mLockView != null) mLockView.setVisibility(INVISIBLE); + } + updatePortraitMenu(isPortrait); + + if (mLandscapePortraitBtn == null) return; + boolean showButton = false; + if (mControlWrapper != null && mActivity != null && !ScreenUtils.isTv(mActivity)) { + int[] videoSize = mControlWrapper.getVideoSize(); + int width = videoSize[0]; + int height = videoSize[1]; + showButton = width > 0 && height > 0 && width <= height && ScreenUtils.getSqrt(mActivity) < 10.0; + } + mLandscapePortraitBtn.setVisibility(showButton ? VISIBLE : GONE); + if (showButton) mLandscapePortraitBtn.setText(isPortrait ? "横屏" : "竖屏"); + } + + private void updatePortraitMenu(boolean isPortrait) { + if (isPortrait) { + mParseRoot.setVisibility(GONE); + mPlayLabel.setVisibility(GONE); + mPlayrefresh.setVisibility(GONE); + mPlayerScaleBtn.setVisibility(GONE); + mPlayerIJKBtn.setVisibility(GONE); + mPlayerTimeStartEndText.setVisibility(GONE); + mPlayerTimeStartBtn.setVisibility(GONE); + mPlayerTimeSkipBtn.setVisibility(GONE); + mPlayerTimeResetBtn.setVisibility(GONE); + mCastBtn.setVisibility(GONE); + mZimuBtn.setVisibility(GONE); + mAudioTrackBtn.setVisibility(GONE); + mDanmuSettingBtn.setVisibility(GONE); + mDanmuSearchUiBtn.setVisibility(GONE); + mScreenDisplay.setVisibility(GONE); + return; + } + + mParseRoot.setVisibility(showParse ? VISIBLE : GONE); + mPlayrefresh.setVisibility(VISIBLE); + mPlayerScaleBtn.setVisibility(VISIBLE); + mPlayerTimeStartEndText.setVisibility(VISIBLE); + mPlayerTimeStartBtn.setVisibility(VISIBLE); + mPlayerTimeSkipBtn.setVisibility(VISIBLE); + mPlayerTimeResetBtn.setVisibility(VISIBLE); + mZimuBtn.setVisibility(VISIBLE); + mScreenDisplay.setVisibility(VISIBLE); + mCastBtn.setVisibility(Build.VERSION.SDK_INT < 30 ? GONE : VISIBLE); + if (mPlayerConfig != null) updatePlayerCfgView(); + hideLiveAboutBtn(); + updateDanmuBtn(); + updateDanmuSearchUiBtn(); + } + + void setLandscapePortrait() { + if (mActivity == null) return; + if (mActivity.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { + mLandscapePortraitBtn.setText("竖屏"); + mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE); + } else { + mLandscapePortraitBtn.setText("横屏"); + mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT); + } + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + initLandscapePortraitBtnInfo(); + } + + void initSubtitleInfo() { + int subtitleTextSize = SubtitleHelper.getTextSize(mActivity); + mSubtitleView.setTextSize(subtitleTextSize); } @Override @@ -337,40 +1019,57 @@ protected int getLayoutId() { } public void showParse(boolean userJxList) { - mParseRoot.setVisibility(userJxList ? VISIBLE : GONE); + showParse = userJxList; + boolean isPortrait = getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; + mParseRoot.setVisibility(showParse && !isPortrait ? VISIBLE : GONE); } private JSONObject mPlayerConfig = null; - private boolean mxPlayerExist = false; - private boolean reexPlayerExist = false; - public void setPlayerConfig(JSONObject playerCfg) { this.mPlayerConfig = playerCfg; updatePlayerCfgView(); - mxPlayerExist = MXPlayer.getPackageInfo() != null; - reexPlayerExist = ReexPlayer.getPackageInfo() != null; } void updatePlayerCfgView() { try { int playerType = mPlayerConfig.getInt("pl"); - mPlayerBtn.setText(PlayerHelper.getPlayerName(playerType)); + mPlayerBtn.setText(getPlayerShortName(playerType)); mPlayerScaleBtn.setText(PlayerHelper.getScaleName(mPlayerConfig.getInt("sc"))); - mPlayerIJKBtn.setText(mPlayerConfig.getString("ijk")); + mPlayerIJKBtn.setText(getCodecShortName(mPlayerConfig.getString("ijk"))); mPlayerIJKBtn.setVisibility(playerType == 1 ? VISIBLE : GONE); mPlayerScaleBtn.setText(PlayerHelper.getScaleName(mPlayerConfig.getInt("sc"))); mPlayerSpeedBtn.setText("x" + mPlayerConfig.getDouble("sp")); - mPlayerTimeStartBtn.setText(PlayerUtils.stringForTime(mPlayerConfig.getInt("st") * 1000)); - mPlayerTimeSkipBtn.setText(PlayerUtils.stringForTime(mPlayerConfig.getInt("et") * 1000)); - mPlayerTimeStepBtn.setText(Hawk.get(HawkConfig.PLAY_TIME_STEP, 5) + "s"); + mPlayerTimeStartBtn.setText(stringForTime(mPlayerConfig.getInt("st") * 1000)); + mPlayerTimeSkipBtn.setText(stringForTime(mPlayerConfig.getInt("et") * 1000)); + mAudioTrackBtn.setVisibility((playerType == 1 || playerType == 2) ? VISIBLE : GONE); } catch (JSONException e) { e.printStackTrace(); } } + private String getPlayerShortName(int playerType) { + String playerName = PlayerHelper.getPlayerName(playerType); + return playerName; + } + + private String getCodecShortName(String codecName) { + if ("硬解码".equals(codecName)) { + return "硬解"; + } + if ("软解码".equals(codecName)) { + return "软解"; + } + return codecName; + } + public void setTitle(String playTitleInfo) { mPlayTitle.setText(playTitleInfo); + mPlayTitle1.setText(playTitleInfo); + } + + public void setUrlTitle(String playTitleInfo) { + mPlayTitle.setText(playTitleInfo); } public void resetSpeed() { @@ -379,18 +1078,71 @@ public void resetSpeed() { mHandler.sendEmptyMessageDelayed(1004, 100); } + public void setHasDanmu(boolean hasDanmu) { + this.hasDanmu = hasDanmu; + updateDanmuBtn(); + } + + public void updateDanmuBtn() { + if (mDanmuSettingBtn == null) return; + mDanmuSettingBtn.setVisibility(hasDanmu ? VISIBLE : GONE); + } + + public void updateDanmuSearchUiBtn() { + if (mDanmuSearchUiBtn == null) return; + boolean hasDanmuSearchUi = ApiConfig.get().hasDanmuSearchUi(); + mDanmuSearchUiBtn.setVisibility(hasDanmuSearchUi ? VISIBLE : GONE); + updatePlayLabelVisibility(); + } + + private void updatePlayLabelVisibility() { + if (mPlayLabel == null || mPlayBtnGroup == null) return; + boolean hidePlayLabel = false; + String prevText = null; + for (int i = 0; i < mPlayBtnGroup.getChildCount(); i++) { + View child = mPlayBtnGroup.getChildAt(i); + if (child == mPlayLabel || child.getVisibility() != VISIBLE || !(child instanceof TextView)) continue; + CharSequence text = ((TextView) child).getText(); + String currentText = text == null ? "" : text.toString().trim(); + if ("弹幕".equals(prevText) && ("搜弹幕".equals(currentText) || "弹幕搜索".equals(currentText))) { + hidePlayLabel = true; + break; + } + prevText = currentText; + } + mPlayLabel.setVisibility(hidePlayLabel ? GONE : VISIBLE); + } + public interface VodControlListener { void playNext(boolean rmProgress); void playPre(); + void prepared(); + void changeParse(ParseBean pb); void updatePlayerCfg(); - void replay(); + void replay(boolean replay); void errReplay(); + + void selectSubtitle(); + + void selectAudioTrack(); + + void showDanmuSetting(); + + void searchDanmuUi(boolean longClick); + + void startPlayUrl(String url, HashMap headers); + + void onM3u8ProxyUrl(String proxyUrl, String sourceUrl); + + void clickCast(); + + void setAllowSwitchPlayer(boolean isAllow); } public void setListener(VodControlListener listener) { @@ -401,8 +1153,90 @@ public void setListener(VodControlListener listener) { private boolean skipEnd = true; + private void keepSeekBarActive() { + if (myHandle != null && myRunnable != null) { + myHandle.removeCallbacks(myRunnable); + myHandle.postDelayed(myRunnable, myHandleSeconds); + } + mHandler.removeMessages(1002); + mHandler.removeMessages(1003); + if (mBottomRoot != null && mBottomRoot.getVisibility() != VISIBLE) { + mHandler.sendEmptyMessage(1002); + } + } + + private void beginSeekBarTracking() { + keepSeekBarActive(); + if (mIsDragging) { + return; + } + mIsDragging = true; + mControlWrapper.stopProgress(); + mControlWrapper.stopFadeOut(); + } + + private void finishSeekBarTracking() { + keepSeekBarActive(); + long newPosition = getSeekBarPosition(mSeekBar.getProgress()); + mControlWrapper.seekTo(newPosition); + mIsDragging = false; + mSeekBarKeyTracking = false; + mControlWrapper.startProgress(); + mControlWrapper.startFadeOut(); + } + + private long getSeekBarPosition(int progress) { + long duration = safeTimeMs(mControlWrapper.getDuration()); + int max = mSeekBar == null ? 0 : mSeekBar.getMax(); + if (duration <= 0 || max <= 0) { + return 0; + } + return (duration * progress) / max; + } + + private void updateSeekBarTime(int progress) { + long newPosition = getSeekBarPosition(progress); + if (mCurrentTime != null) { + mCurrentTime.setText(stringForTime(safeTimeMs(newPosition))); + } + } + + private void moveSeekBarByKey(int dir) { + int duration = safeTimeMs(mControlWrapper.getDuration()); + if (duration <= 0 || mSeekBar.getMax() <= 0) { + return; + } + int progress = mSeekBar.getProgress() + getSeekBarKeyProgress(duration) * dir; + if (progress < 0) { + progress = 0; + } else if (progress > mSeekBar.getMax()) { + progress = mSeekBar.getMax(); + } + mSeekBar.setProgress(progress); + updateSeekBarTime(progress); + updateSeekUI(safeTimeMs(mControlWrapper.getCurrentPosition()), safeTimeMs(getSeekBarPosition(progress)), duration); + } + + private int getSeekBarKeyProgress(int duration) { + long increment; + if (duration > 3 * 60 * 60 * 1000) { + increment = 5 * 60 * 1000; + } else if (duration > 30 * 60 * 1000) { + increment = 60 * 1000; + } else if (duration > 15 * 60 * 1000) { + increment = 30 * 1000; + } else if (duration > 10 * 60 * 1000) { + increment = 15 * 1000; + } else { + increment = 10 * 1000; + } + return Math.max(1, (int) (increment * mSeekBar.getMax() / duration)); + } + + @SuppressLint("SetTextI18n") @Override protected void setProgress(int duration, int position) { + if (mIsDragging) { return; } @@ -419,8 +1253,9 @@ protected void setProgress(int duration, int position) { listener.playNext(true); } } - mCurrentTime.setText(PlayerUtils.stringForTime(position)); - mTotalTime.setText(PlayerUtils.stringForTime(duration)); + mCurrentTime.setText(stringForTime(position)); + mTotalTime.setText(stringForTime(duration)); + seekTime.setText((seconds2Time(position)) + " | " + (seconds2Time(duration))); //右上角进度条时间显示 if (duration > 0) { mSeekBar.setEnabled(true); int pos = (int) (position * 1.0 / duration * mSeekBar.getMax()); @@ -439,6 +1274,7 @@ protected void setProgress(int duration, int position) { private boolean simSlideStart = false; private int simSeekPosition = 0; private long simSlideOffset = 0; + private long lastSlideTime = 0; public void tvSlideStop() { if (!simSlideStart) @@ -450,18 +1286,29 @@ public void tvSlideStop() { simSeekPosition = 0; simSlideOffset = 0; } - public void tvSlideStart(int dir) { - int duration = (int) mControlWrapper.getDuration(); + int duration = safeTimeMs(mControlWrapper.getDuration()); if (duration <= 0) return; + + long currentTime = System.currentTimeMillis(); + final int baseSkip = 10000; // 基础跳转10秒 + final float accelerationFactor = 2.0f; // 连续操作时的加速因子 + final long threshold = 800; // 操作间隔阈值500ms + if (!simSlideStart) { simSlideStart = true; + simSlideOffset = (long) baseSkip * dir; + } else { + if (currentTime - lastSlideTime <= threshold) { + simSlideOffset += (baseSkip * accelerationFactor * dir); + } else { + simSlideOffset = (long) baseSkip * dir; + } } - // 每次10秒 - simSlideOffset += (10000.0f * dir); - int currentPosition = (int) mControlWrapper.getCurrentPosition(); - int position = (int) (simSlideOffset + currentPosition); + lastSlideTime = currentTime; + int currentPosition = safeTimeMs(mControlWrapper.getCurrentPosition()); + int position = (int) (currentPosition + simSlideOffset); if (position > duration) position = duration; if (position < 0) position = 0; updateSeekUI(currentPosition, position, duration); @@ -476,7 +1323,7 @@ protected void updateSeekUI(int curr, int seekTo, int duration) { } else { mProgressIcon.setImageResource(R.drawable.icon_back); } - mProgressText.setText(PlayerUtils.stringForTime(seekTo) + " / " + PlayerUtils.stringForTime(duration)); + mProgressText.setText(stringForTime(seekTo) + " / " + stringForTime(duration)); mHandler.sendEmptyMessage(1000); mHandler.removeMessages(1001); mHandler.sendEmptyMessageDelayed(1001, 1000); @@ -485,22 +1332,34 @@ protected void updateSeekUI(int curr, int seekTo, int duration) { @Override protected void onPlayStateChanged(int playState) { super.onPlayStateChanged(playState); + videoPlayState = playState; switch (playState) { case VideoView.STATE_IDLE: break; case VideoView.STATE_PLAYING: + initLandscapePortraitBtnInfo(); startProgress(); break; case VideoView.STATE_PAUSED: + mTopRoot1.setVisibility(GONE); +// mTopRoot2.setVisibility(GONE); + mPlayLoadNetSpeedRightTop.setVisibility(GONE); + mPlayTitle.setVisibility(VISIBLE); break; case VideoView.STATE_ERROR: listener.errReplay(); break; case VideoView.STATE_PREPARED: + mPlayLoadNetSpeed.setVisibility(GONE); + hideLiveAboutBtn(); + listener.prepared(); + break; case VideoView.STATE_BUFFERED: + mPlayLoadNetSpeed.setVisibility(GONE); break; case VideoView.STATE_PREPARING: case VideoView.STATE_BUFFERING: + if(mProgressRoot.getVisibility()==GONE)mPlayLoadNetSpeed.setVisibility(VISIBLE); break; case VideoView.STATE_PLAYBACK_COMPLETED: listener.playNext(true); @@ -515,6 +1374,13 @@ boolean isBottomVisible() { void showBottom() { mHandler.removeMessages(1003); mHandler.sendEmptyMessage(1002); + mNextBtn.requestFocus(); + } + + void showUpBottom() { + mHandler.removeMessages(1003); + mHandler.sendEmptyMessage(1002); + mPlayerTimeStartBtn.requestFocus(); } void hideBottom() { @@ -524,15 +1390,19 @@ void hideBottom() { @Override public boolean onKeyEvent(KeyEvent event) { + myHandle.removeCallbacks(myRunnable); if (super.onKeyEvent(event)) { return true; } + int keyCode = event.getKeyCode(); + int action = event.getAction(); if (isBottomVisible()) { + mHandler.removeMessages(1002); + mHandler.removeMessages(1003); + myHandle.postDelayed(myRunnable, myHandleSeconds); return super.dispatchKeyEvent(event); } boolean isInPlayback = isInPlaybackState(); - int keyCode = event.getKeyCode(); - int action = event.getAction(); if (action == KeyEvent.ACTION_DOWN) { if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT || keyCode == KeyEvent.KEYCODE_DPAD_LEFT) { if (isInPlayback) { @@ -544,10 +1414,11 @@ public boolean onKeyEvent(KeyEvent event) { togglePlay(); return true; } - } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) { - } else if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) { + } else if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN || keyCode== KeyEvent.KEYCODE_MENU) { if (!isBottomVisible()) { showBottom(); + myHandle.postDelayed(myRunnable, myHandleSeconds); + return true; } } } else if (action == KeyEvent.ACTION_UP) { @@ -561,18 +1432,169 @@ public boolean onKeyEvent(KeyEvent event) { return super.dispatchKeyEvent(event); } + + private boolean fromLongPress; + private float speed_old = 1.0f; + + private void speedPlayStart(){ + fromLongPress = true; + try { + speed_old = (float) mPlayerConfig.getDouble("sp"); + float speed = 3.0f; + mPlayerConfig.put("sp", speed); + updatePlayerCfgView(); + listener.updatePlayerCfg(); + mControlWrapper.setSpeed(speed); + findViewById(R.id.play_speed_3_container).setVisibility(View.VISIBLE); + } catch (JSONException f) { + f.printStackTrace(); + } + } + private void speedPlayEnd(){ + if (fromLongPress) { + fromLongPress =false; + try { + float speed = speed_old; + mPlayerConfig.put("sp", speed); + updatePlayerCfgView(); + listener.updatePlayerCfg(); + mControlWrapper.setSpeed(speed); + } catch (JSONException f) { + f.printStackTrace(); + } + findViewById(R.id.play_speed_3_container).setVisibility(View.GONE); + } + } + @Override + public void onLongPress(MotionEvent e) { + if (videoPlayState!=VideoView.STATE_PAUSED) { + speedPlayStart(); + } + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouchEvent(MotionEvent e) { + if (e.getAction() == MotionEvent.ACTION_UP) { + speedPlayEnd(); + } + return super.onTouchEvent(e); + } + + + private final Handler mmHandler = new Handler(); + private Runnable mLongPressRunnable; + private static final long LONG_PRESS_DELAY = 800; + private boolean isLongPressTriggered = false; + + private boolean setMinPlayTimeChange(String typeEt,boolean increase){ + myHandle.removeCallbacks(myRunnable); + myHandle.postDelayed(myRunnable, myHandleSeconds); + try { + int currentValue = mPlayerConfig.optInt(typeEt, 0); + if(currentValue!=0){ + int newValue = increase ? currentValue + 1 : currentValue - 1; + if(newValue < 0) { + newValue = 0; + } + mPlayerConfig.put(typeEt,newValue); + updatePlayerCfgView(); + listener.updatePlayerCfg(); + return true; + } + } catch (JSONException e) { + e.printStackTrace(); + } + return false; + } + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (isBottomVisible()) { + if (keyCode == KeyEvent.KEYCODE_DPAD_UP ) { + if(mPlayerTimeStartBtn.hasFocus()){ + if(setMinPlayTimeChange("st",true)){ + return true; + } + } + View focusedView = mPlayBtnGroup.findFocus(); + if (focusedView instanceof TextView) { + return true; + } + } + if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN ) { + if(mPlayerTimeStartBtn.hasFocus()){ + if(setMinPlayTimeChange("st",false))return true; + } + } + return super.onKeyDown(keyCode, event); + } + if (keyCode == KeyEvent.KEYCODE_DPAD_UP && event.getRepeatCount() == 0) { + isLongPressTriggered = false; + mLongPressRunnable = new Runnable() { + @Override + public void run() { + speedPlayStart(); + isLongPressTriggered = true; + } + }; + mmHandler.postDelayed(mLongPressRunnable, LONG_PRESS_DELAY); + return true; + } + return super.onKeyDown(keyCode, event); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_DPAD_UP) { + // 移除长按回调 + if (mLongPressRunnable != null) { + mmHandler.removeCallbacks(mLongPressRunnable); + mLongPressRunnable = null; + } + if (isLongPressTriggered) { + speedPlayEnd(); + } else { + if (!isBottomVisible()) { + showUpBottom(); + myHandle.postDelayed(myRunnable, myHandleSeconds); + }else { + return super.onKeyUp(keyCode, event); + } + } + return true; + } + return super.onKeyUp(keyCode, event); + } + @Override public boolean onSingleTapConfirmed(MotionEvent e) { + myHandle.removeCallbacks(myRunnable); if (!isBottomVisible()) { showBottom(); + // 闲置计时关闭 + myHandle.postDelayed(myRunnable, myHandleSeconds); } else { hideBottom(); } return true; } - + + private class LockRunnable implements Runnable { + @Override + public void run() { + mLockView.setVisibility(INVISIBLE); + } + } + @Override public boolean onBackPressed() { + if (isClickBackBtn) { + isClickBackBtn = false; + if (isBottomVisible()) { + hideBottom(); + } + return false; + } if (super.onBackPressed()) { return true; } @@ -582,4 +1604,326 @@ public boolean onBackPressed() { } return false; } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mHandler.removeCallbacks(myRunnable2); + } + + + //尝试去bom + public String getWebPlayUrlIfNeeded(String webPlayUrl) { + if (webPlayUrl != null && !webPlayUrl.contains("127.0.0.1:9978") && webPlayUrl.contains(".m3u8")) { + try { + String urlEncode = URLEncoder.encode(webPlayUrl, "UTF-8"); + LOG.i("echo-BOM-------"); + return ControlManager.get().getAddress(true) + "proxy?go=bom&url=" + urlEncode; + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } + return webPlayUrl; + } + + public String encodeUrl(String url) { + try { + return URLEncoder.encode(url, "UTF-8"); + } catch (Exception e) { + return url; + } + } + + public boolean switchPlayer(){ + try { + int playerType= mPlayerConfig.getInt("pl"); + int p_type = (playerType == 1) ? playerType + 1 : (playerType == 2) ? playerType - 1 : playerType; + if (p_type != playerType) { + LOG.i("echo-switchPlayer: " + playerType + " -> " + p_type); +// Toast.makeText(getContext(), "切换到"+(p_type==1?"IJK":"EXO"), Toast.LENGTH_SHORT).show(); + mPlayerConfig.put("pl", p_type); + updatePlayerCfgView(); + listener.updatePlayerCfg(); + }else { + LOG.i("echo-switchPlayer: skip unsupported playerType=" + playerType); + return true; + } + }catch (Exception e){ + LOG.i("echo-switchPlayer error: " + e.getMessage()); + return true; + } + return false; + } + + public void playM3u8(final String url, final HashMap headers) { + if(url.contains("url=")){ + listener.startPlayUrl(url, headers); + return; + } + OkGo.getInstance().cancelTag("m3u8-1"); + OkGo.getInstance().cancelTag("m3u8-2"); + final HttpHeaders okGoHeaders = new HttpHeaders(); + if (headers != null) { + for (Map.Entry entry : headers.entrySet()) { + okGoHeaders.put(entry.getKey(), entry.getValue()); + } + } + OkGo.get(url) + .tag("m3u8-1") + .headers(okGoHeaders) + .execute(new AbsCallback() { + @Override + public void onSuccess(Response response) { + String content = response.body(); + if (!content.startsWith("#EXTM3U")) { + listener.startPlayUrl(url, headers); + return; + } + String forwardUrl = extractForwardUrl(url, content); + if (forwardUrl.isEmpty()) { + LOG.i("echo-m3u81-to-play"); + processM3u8Content(url, content, headers); + } else { + fetchAndProcessForwardUrl(forwardUrl, headers, okGoHeaders, url); + } + } + + @Override + public String convertResponse(okhttp3.Response response) throws Throwable { + return response.body().string(); + } + + @Override + public void onError(Response response) { + super.onError(response); + LOG.e("echo-m3u8请求错误1: " + response.getException()); + listener.startPlayUrl(url, headers); + } + }); + } + + private String extractForwardUrl(String baseUrl, String content) { + String[] lines = content.split("\\r?\\n",50); + for (int i = 0; i < lines.length; i++) { + String line = lines[i].trim(); + if (line.startsWith("#EXT-X-STREAM-INF")) { + // 只需要找接下来的几行 + for (int j = i + 1; j < lines.length; j++) { + String targetLine = lines[j].trim(); + if (targetLine.isEmpty()) continue; + if (isValidM3u8Line(targetLine)) { + return resolveForwardUrl(baseUrl, targetLine); + } + } + } + } + return ""; + } + + private boolean isValidM3u8Line(String line) { + return !line.startsWith("#") && (line.endsWith(".m3u8") || line.contains(".m3u8?")); + } + + private void processM3u8Content(String url, String content, HashMap headers) { + String basePath = getBasePath(url); + RemoteServer.m3u8Content = M3u8.purify(basePath, content); + if (RemoteServer.m3u8Content == null || M3u8.currentAdCount==0) { + LOG.i("echo-m3u8内容解析:未检测到广告"); + listener.startPlayUrl(url, headers); + } else { + String proxyUrl = ControlManager.get().getAddress(true) + "proxyM3u8"; + listener.onM3u8ProxyUrl(proxyUrl, url); + listener.startPlayUrl(proxyUrl, headers); + Toast.makeText(getContext(), "已移除视频广告 "+M3u8.currentAdCount+" 条", Toast.LENGTH_SHORT).show(); + } + } + + private void fetchAndProcessForwardUrl(final String forwardUrl, final HashMap headers, + HttpHeaders okGoHeaders, final String fallbackUrl) { + OkGo.get(forwardUrl) + .tag("m3u8-2") + .headers(okGoHeaders) + .execute(new AbsCallback() { + @Override + public void onSuccess(Response response) { + String content = response.body(); + LOG.i("echo-m3u82-to-play"); + processM3u8Content(forwardUrl, content, headers); + } + @Override + public String convertResponse(okhttp3.Response response) throws Throwable { + return response.body().string(); + } + @Override + public void onError(Response response) { + super.onError(response); + LOG.e("echo-重定向 m3u8 请求错误: " + response.getException()); + listener.startPlayUrl(fallbackUrl, headers); + } + }); + } + + private String getBasePath(String url) { + int ilast = url.lastIndexOf('/'); + return url.substring(0, ilast + 1); + } + + private String resolveForwardUrl(String baseUrl, String line) { + try { + // 使用 URL 构造器自动解析相对路径 + URL base = new URL(baseUrl); + URL resolved = new URL(base, line); + return resolved.toString(); + } catch (MalformedURLException e) { + // 出现异常时可以记录日志,并返回原始 line + LOG.e("echo-resolveForwardUrl异常: " + e.getMessage()); + return line; + } + } + + public String firstUrlByArray(String url) + { + try { + JSONArray urlArray = new JSONArray(url); + for (int i = 0; i < urlArray.length(); i++) { + String item = urlArray.getString(i); + if (item.contains("http")) { + url = item; + break; // 找到第一个立即终止循环 + } + } + } catch (JSONException e) { + } + return url; + } + + public void evaluateScript(SourceBean sourceBean,String url, WebView web_view, XWalkView xWalk_view){ + String clickSelector = sourceBean.getClickSelector().trim(); + clickSelector=clickSelector.isEmpty()?VideoParseRuler.getHostScript(url):clickSelector; + if (!clickSelector.isEmpty()) { + String selector; + if (clickSelector.contains(";") && !clickSelector.endsWith(";")) { + String[] parts = clickSelector.split(";", 2); + if (!url.contains(parts[0])) { + return; + } + selector = parts[1].trim(); + } else { + selector = clickSelector.trim(); + } + // 构造点击的 JS 代码 + String js = selector; +// if(!selector.contains("click()"))js+=".click();"; + LOG.i("echo-javascript:" + js); + if(web_view!=null){ + //4.4以上才支持这种写法 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + web_view.evaluateJavascript(js, null); + } else { + web_view.loadUrl("javascript:" + js); + } + } + if(xWalk_view!=null){ + //4.0+开始全部支持这种写法 + xWalk_view.evaluateJavascript(js, null); + } + } + } + + public void stopOther() + { + Thunder.stop(false);//停止磁力下载 + Jianpian.finish();//停止p2p下载 + App.getInstance().setDashData(null); + } + + private static class CircleThumbDrawable extends Drawable { + private final Paint fillPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Paint strokePaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private final int normalSize; + private final int activeSize; + private final float strokeWidth; + private boolean active; + + CircleThumbDrawable(Context context) { + normalSize = dp(context, 12); + activeSize = dp(context, 16); + strokeWidth = dp(context, 2); + fillPaint.setStyle(Paint.Style.FILL); + fillPaint.setColor(Color.WHITE); + strokePaint.setStyle(Paint.Style.STROKE); + strokePaint.setStrokeWidth(strokeWidth); + strokePaint.setColor(Color.parseColor("#FF4081")); + } + + @Override + public void draw(@NonNull Canvas canvas) { + Rect bounds = getBounds(); + float cx = bounds.exactCenterX(); + float cy = bounds.exactCenterY(); + int size = active ? activeSize : normalSize; + float radius = Math.max(0, size / 2f - strokeWidth / 2f); + canvas.drawCircle(cx, cy, radius, fillPaint); + canvas.drawCircle(cx, cy, radius, strokePaint); + } + + @Override + public boolean isStateful() { + return true; + } + + @Override + protected boolean onStateChange(int[] stateSet) { + boolean newActive = false; + if (stateSet != null) { + for (int state : stateSet) { + if (state == android.R.attr.state_pressed + || state == android.R.attr.state_focused + || state == android.R.attr.state_selected) { + newActive = true; + break; + } + } + } + if (active == newActive) { + return false; + } + active = newActive; + invalidateSelf(); + return true; + } + + @Override + public void setAlpha(int alpha) { + fillPaint.setAlpha(alpha); + strokePaint.setAlpha(alpha); + invalidateSelf(); + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + fillPaint.setColorFilter(colorFilter); + strokePaint.setColorFilter(colorFilter); + invalidateSelf(); + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + + @Override + public int getIntrinsicWidth() { + return activeSize; + } + + @Override + public int getIntrinsicHeight() { + return activeSize; + } + + private static int dp(Context context, float value) { + return (int) (value * context.getResources().getDisplayMetrics().density + 0.5f); + } + } } diff --git a/app/src/main/java/com/github/tvbox/osc/player/danmu/DanmuLoadController.java b/app/src/main/java/com/github/tvbox/osc/player/danmu/DanmuLoadController.java new file mode 100644 index 0000000000..7de9add9c9 --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/player/danmu/DanmuLoadController.java @@ -0,0 +1,230 @@ +package com.github.tvbox.osc.player.danmu; + +import android.text.TextUtils; +import android.view.View; + +import com.github.tvbox.osc.api.DanmakuApi; +import com.github.tvbox.osc.player.MyVideoView; +import com.github.tvbox.osc.player.controller.VodController; +import com.github.tvbox.osc.util.DanmuHelper; +import com.github.tvbox.osc.util.LOG; + +import java.util.HashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; + +import master.flame.danmaku.danmaku.model.BaseDanmaku; +import master.flame.danmaku.danmaku.model.IDisplayer; +import master.flame.danmaku.danmaku.model.android.DanmakuContext; +import master.flame.danmaku.ui.widget.DanmakuView; +import xyz.doikki.videoplayer.player.VideoView; + +public class DanmuLoadController { + public interface LoadCallback { + void onFailed(); + } + + private final MyVideoView videoView; + private final VodController controller; + private final DanmakuView danmuView; + private final DanmakuContext danmakuContext; + private final AtomicInteger loadSeq = new AtomicInteger(); + private ExecutorService executor; + private String danmuText = ""; + private String danmuTitle = ""; + private String danmuEpisode = ""; + private int startedSeq = -1; + private boolean pendingPrepare; + private LoadCallback loadCallback; + + public DanmuLoadController(MyVideoView videoView, VodController controller, DanmakuView danmuView) { + this.videoView = videoView; + this.controller = controller; + this.danmuView = danmuView; + this.danmakuContext = DanmakuContext.create(); + if (this.videoView != null) { + this.videoView.setDanmuView(this.danmuView); + } + applySettings(false); + } + + public void applySettings(boolean reload) { + if (danmuView == null || danmakuContext == null) return; + if (!DanmuHelper.isOpen()) { + releaseView(); + if (controller != null) controller.setHasDanmu(!TextUtils.isEmpty(danmuText)); + return; + } + HashMap maxLines = new HashMap<>(); + int maxLine = DanmuHelper.getMaxLine(); + maxLines.put(BaseDanmaku.TYPE_FIX_TOP, maxLine); + maxLines.put(BaseDanmaku.TYPE_SCROLL_RL, maxLine); + maxLines.put(BaseDanmaku.TYPE_SCROLL_LR, maxLine); + maxLines.put(BaseDanmaku.TYPE_FIX_BOTTOM, maxLine); + danmakuContext.setMaximumLines(maxLines) + .setScrollSpeedFactor(DanmuHelper.getSpeed()) + .setDanmakuTransparency(DanmuHelper.getAlpha()) + .setScaleTextSize(DanmuHelper.getSizeScale()); + danmakuContext.setDanmakuStyle(IDisplayer.DANMAKU_STYLE_STROKEN, 3) + .setDanmakuMargin(8); + if (reload && !TextUtils.isEmpty(danmuText) && DanmuHelper.isOpen()) { + prepare(danmuText); + } + } + + public void check(String danmu) { + check(danmu, "", ""); + } + + public void check(String danmu, String title, String episode) { + check(danmu, title, episode, null); + } + + public void check(String danmu, String title, String episode, LoadCallback callback) { + loadCallback = callback; + danmuText = TextUtils.isEmpty(danmu) ? "" : danmu.trim(); + danmuTitle = TextUtils.isEmpty(title) ? "" : title; + danmuEpisode = TextUtils.isEmpty(episode) ? "" : episode; + releaseView(); + boolean hasDanmu = !TextUtils.isEmpty(danmuText); + if (controller != null) controller.setHasDanmu(hasDanmu); + if (!hasDanmu || !DanmuHelper.isOpen()) { + if (danmuView != null) danmuView.setVisibility(View.GONE); + return; + } + if (danmuView != null) danmuView.setVisibility(View.VISIBLE); + if (!isVideoReady()) { + pendingPrepare = true; + return; + } + prepare(danmuText); + } + + public void startIfReady() { + if (pendingPrepare && !TextUtils.isEmpty(danmuText) && DanmuHelper.isOpen() && isVideoReady()) { + pendingPrepare = false; + prepare(danmuText); + return; + } + startIfReady(loadSeq.get()); + } + + public void reset() { + DanmakuApi.cancel(); + danmuText = ""; + danmuTitle = ""; + danmuEpisode = ""; + pendingPrepare = false; + loadCallback = null; + loadSeq.incrementAndGet(); + startedSeq = -1; + if (controller != null) controller.setHasDanmu(false); + releaseView(); + } + + public void destroy() { + reset(); + if (executor != null) { + executor.shutdownNow(); + executor = null; + } + } + + private void prepare(String danmu) { + if (TextUtils.isEmpty(danmu)) return; + pendingPrepare = false; + int seq = loadSeq.incrementAndGet(); + startedSeq = -1; + LOG.i("echo-danmu load title: " + safeLog(danmuTitle) + ", episode: " + safeLog(danmuEpisode) + ", source: " + getSourceSummary(danmu)); + if (executor == null || executor.isShutdown()) { + executor = Executors.newSingleThreadExecutor(); + } + executor.execute(() -> { + Parser parser = new Parser(danmu, () -> seq != loadSeq.get()); + if (seq != loadSeq.get()) return; + int danmuCount = parser.getDanmuCount(); + LOG.i("echo-danmu parsed count: " + danmuCount); + if (danmuView == null) return; + danmuView.post(() -> { + if (seq != loadSeq.get() || danmakuContext == null) return; + try { + danmuView.release(); + if (videoView != null) videoView.setDanmuView(danmuView); + if (danmuCount <= 0) { + LOG.e("echo-danmu empty after parse"); + danmuView.setVisibility(View.GONE); + notifyLoadFailed(seq); + return; + } + danmuView.prepare(parser, danmakuContext); + clearLoadCallback(seq); + danmuView.setVisibility(DanmuHelper.isOpen() ? View.VISIBLE : View.GONE); + startIfReady(seq); + danmuView.postDelayed(() -> startIfReady(seq), 300); + danmuView.postDelayed(() -> startIfReady(seq), 1000); + } catch (Throwable th) { + LOG.e("echo-danmu prepare error: " + th.getMessage()); + danmuView.setVisibility(View.GONE); + notifyLoadFailed(seq); + } + }); + }); + } + + private void clearLoadCallback(int seq) { + if (seq == loadSeq.get()) loadCallback = null; + } + + private void notifyLoadFailed(int seq) { + if (seq != loadSeq.get() || loadCallback == null) return; + LoadCallback callback = loadCallback; + loadCallback = null; + callback.onFailed(); + } + + private void startIfReady(int seq) { + if (seq != loadSeq.get() + || seq == startedSeq + || videoView == null + || !videoView.isPlaying() + || danmuView == null + || !danmuView.isPrepared() + || !DanmuHelper.isOpen()) { + return; + } + long position = videoView.getCurrentPosition(); + danmuView.setVisibility(View.VISIBLE); + danmuView.seekTo(position); + danmuView.start(position); + startedSeq = seq; + LOG.i("echo-danmu start at: " + position); + } + + private boolean isVideoReady() { + if (videoView == null) return false; + int state = videoView.getCurrentPlayState(); + return state == VideoView.STATE_PREPARED + || state == VideoView.STATE_BUFFERED + || state == VideoView.STATE_PLAYING; + } + + private void releaseView() { + if (danmuView == null) return; + try { + danmuView.release(); + } catch (Throwable ignored) { + } + danmuView.setVisibility(View.GONE); + } + + private String getSourceSummary(String danmu) { + if (TextUtils.isEmpty(danmu)) return ""; + if (danmu.startsWith("http") || danmu.startsWith("file")) return danmu; + return "inline xml length=" + danmu.length(); + } + + private String safeLog(String text) { + return TextUtils.isEmpty(text) ? "" : text; + } +} diff --git a/app/src/main/java/com/github/tvbox/osc/player/danmu/Parser.java b/app/src/main/java/com/github/tvbox/osc/player/danmu/Parser.java new file mode 100644 index 0000000000..6214805a15 --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/player/danmu/Parser.java @@ -0,0 +1,400 @@ +package com.github.tvbox.osc.player.danmu; + +import android.graphics.Color; +import android.text.TextUtils; + +import com.github.tvbox.osc.bean.Danmu; +import com.github.tvbox.osc.util.DanmuHelper; +import com.github.tvbox.osc.util.FileUtils; +import com.github.tvbox.osc.util.LOG; +import com.github.tvbox.osc.util.OkGoHelper; +import com.github.tvbox.osc.util.SSL.SSLSocketFactoryCompat; + +import org.json.JSONArray; +import org.json.JSONException; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.SocketTimeoutException; +import java.security.cert.CertificateException; +import java.util.concurrent.TimeUnit; +import java.util.zip.GZIPInputStream; +import java.util.zip.Inflater; +import java.util.zip.InflaterInputStream; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.X509TrustManager; + +import master.flame.danmaku.danmaku.model.AlphaValue; +import master.flame.danmaku.danmaku.model.BaseDanmaku; +import master.flame.danmaku.danmaku.model.Duration; +import master.flame.danmaku.danmaku.model.IDanmakus; +import master.flame.danmaku.danmaku.model.IDisplayer; +import master.flame.danmaku.danmaku.model.SpecialDanmaku; +import master.flame.danmaku.danmaku.model.android.DanmakuFactory; +import master.flame.danmaku.danmaku.model.android.Danmakus; +import master.flame.danmaku.danmaku.parser.BaseDanmakuParser; +import master.flame.danmaku.danmaku.util.DanmakuUtils; +import okhttp3.OkHttpClient; +import okhttp3.Request; + +public class Parser extends BaseDanmakuParser { + public interface CancelChecker { + boolean isCancelled(); + } + + private static final long HTTP_TIMEOUT_MS = 20 * 1000L; + private static final X509TrustManager TRUST_ALL_CERT = new X509TrustManager() { + @Override + public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException { + } + + @Override + public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException { + } + + @Override + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return new java.security.cert.X509Certificate[]{}; + } + }; + private static final HostnameVerifier TRUST_ALL_HOSTNAME = new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + }; + private static volatile OkHttpClient HTTP_CLIENT = buildHttpClient(false); + private static volatile OkHttpClient UNSAFE_HTTP_CLIENT = buildHttpClient(true); + private final Danmu danmu; + private final CancelChecker cancelChecker; + private float scaleX; + private float scaleY; + private int index; + + public Parser(String input) { + this(input, null); + } + + public Parser(String input, CancelChecker cancelChecker) { + this.cancelChecker = cancelChecker; + this.danmu = Danmu.fromXml(resolveContent(input)); + } + + public int getDanmuCount() { + return danmu.getData().size(); + } + + private String resolveContent(String input) { + if (isCancelled()) return ""; + if (TextUtils.isEmpty(input)) return ""; + String source = input.trim(); + if (source.startsWith("file")) return FileUtils.read(source); + if (source.startsWith("http")) { + try { + if (isCancelled()) return ""; + okhttp3.Response response = executeHttp(source); + if (isCancelled()) { + response.close(); + return ""; + } + String content = readBody(response); + LOG.i("echo-danmu http code: " + response.code() + + ", encoding: " + response.header("Content-Encoding", "") + + ", length: " + content.length()); + return content; + } catch (SocketTimeoutException e) { + if (isLocalProxy(source)) { + try { + LOG.e("echo-danmu load timeout, retry local proxy"); + if (isCancelled()) return ""; + okhttp3.Response response = executeHttp(source); + if (isCancelled()) { + response.close(); + return ""; + } + String content = readBody(response); + LOG.i("echo-danmu retry http code: " + response.code() + + ", encoding: " + response.header("Content-Encoding", "") + + ", length: " + content.length()); + return content; + } catch (Throwable retryError) { + LOG.e("echo-danmu retry error: " + retryError.getMessage()); + } + } + LOG.e("echo-danmu load error: timeout"); + return ""; + } catch (Throwable th) { + if (source.startsWith("https") && isSslError(th)) { + try { + LOG.i("echo-danmu ssl verify failed, retry unsafe ssl"); + if (isCancelled()) return ""; + okhttp3.Response response = executeUnsafeHttp(source); + if (isCancelled()) { + response.close(); + return ""; + } + String content = readBody(response); + LOG.i("echo-danmu unsafe ssl http code: " + response.code() + + ", encoding: " + response.header("Content-Encoding", "") + + ", length: " + content.length()); + return content; + } catch (Throwable retryError) { + LOG.e("echo-danmu unsafe ssl retry error: " + retryError.getMessage()); + } + } + LOG.e("echo-danmu load error: " + th.getMessage()); + return ""; + } + } + return source; + } + + private boolean isCancelled() { + return cancelChecker != null && cancelChecker.isCancelled(); + } + + private static OkHttpClient buildHttpClient(boolean unsafeSsl) { + OkHttpClient base = OkGoHelper.getDefaultClient(); + OkHttpClient.Builder builder = base != null ? base.newBuilder() : new OkHttpClient.Builder().proxySelector(OkGoHelper.proxySelector()).proxyAuthenticator(OkGoHelper.proxyAuthenticator()); + builder.readTimeout(HTTP_TIMEOUT_MS, TimeUnit.MILLISECONDS); + builder.writeTimeout(HTTP_TIMEOUT_MS, TimeUnit.MILLISECONDS); + builder.connectTimeout(HTTP_TIMEOUT_MS, TimeUnit.MILLISECONDS); + builder.retryOnConnectionFailure(true); + if (unsafeSsl) { + SSLSocketFactory sslSocketFactory = new SSLSocketFactoryCompat(TRUST_ALL_CERT); + builder.sslSocketFactory(sslSocketFactory, TRUST_ALL_CERT); + builder.hostnameVerifier(TRUST_ALL_HOSTNAME); + } + return builder.build(); + } + + public static synchronized void resetHttpClient() { + HTTP_CLIENT = buildHttpClient(false); + UNSAFE_HTTP_CLIENT = buildHttpClient(true); + } + + private okhttp3.Response executeHttp(String source) throws IOException { + Request request = new Request.Builder().url(source).get().build(); + return HTTP_CLIENT.newCall(request).execute(); + } + + private okhttp3.Response executeUnsafeHttp(String source) throws IOException { + Request request = new Request.Builder().url(source).get().build(); + return UNSAFE_HTTP_CLIENT.newCall(request).execute(); + } + + private boolean isSslError(Throwable th) { + while (th != null) { + if (th instanceof SSLException) return true; + th = th.getCause(); + } + return false; + } + + private boolean isLocalProxy(String source) { + return source.startsWith("http://127.0.0.1:") + || source.startsWith("http://localhost:"); + } + + private String readBody(okhttp3.Response response) throws IOException { + if (response.body() == null) return ""; + byte[] bytes = response.body().bytes(); + if (looksXml(bytes)) return new String(bytes, "UTF-8"); + String encoding = response.header("Content-Encoding", ""); + if ("gzip".equalsIgnoreCase(encoding)) { + bytes = readAll(new GZIPInputStream(new ByteArrayInputStream(bytes))); + } else if ("deflate".equalsIgnoreCase(encoding)) { + bytes = inflate(bytes); + } + return new String(bytes, "UTF-8"); + } + + private boolean looksXml(byte[] bytes) { + if (bytes == null) return false; + for (byte value : bytes) { + char ch = (char) (value & 0xff); + if (Character.isWhitespace(ch)) continue; + return ch == '<'; + } + return false; + } + + private byte[] inflate(byte[] bytes) throws IOException { + try { + return readAll(new InflaterInputStream(new ByteArrayInputStream(bytes))); + } catch (IOException e) { + Inflater inflater = new Inflater(true); + try { + return readAll(new InflaterInputStream(new ByteArrayInputStream(bytes), inflater)); + } finally { + inflater.end(); + } + } + } + + private byte[] readAll(InputStream input) throws IOException { + try { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + byte[] buffer = new byte[4096]; + int read; + while ((read = input.read(buffer)) != -1) { + output.write(buffer, 0, read); + } + return output.toByteArray(); + } finally { + input.close(); + } + } + + @Override + protected Danmakus parse() { + Danmakus result = new Danmakus(IDanmakus.ST_BY_TIME); + int renderedCount = 0; + for (Danmu.Data data : danmu.getData()) { + BaseDanmaku danmaku = createDanmaku(data); + if (danmaku == null) continue; + synchronized (result.obtainSynchronizer()) { + result.addItem(danmaku); + } + renderedCount++; + } + LOG.i("echo-danmu rendered count: " + renderedCount); + return result; + } + + @Override + public BaseDanmakuParser setDisplayer(IDisplayer display) { + super.setDisplayer(display); + scaleX = mDispWidth / DanmakuFactory.BILI_PLAYER_WIDTH; + scaleY = mDispHeight / DanmakuFactory.BILI_PLAYER_HEIGHT; + return this; + } + + private BaseDanmaku createDanmaku(Danmu.Data data) { + try { + String[] values = data.getParam().split(","); + if (values.length < 4) return null; + int type = Integer.parseInt(values[1]); + BaseDanmaku item = mContext.mDanmakuFactory.createDanmaku(type, mContext); + if (item == null) return null; + long time = (long) (Float.parseFloat(values[0]) * 1000); + float size = Float.parseFloat(values[2]) * Math.max(1.0f, mDispDensity - 0.6f); + int color = DanmuHelper.useRandomColor() + ? DanmuHelper.randomColor() + : (int) ((0x00000000ff000000L | Long.parseLong(values[3])) & 0x00000000ffffffffL); + item.setTime(time); + item.setTimer(mTimer); + item.textSize = size; + item.textColor = color; + item.textShadowColor = color <= Color.BLACK ? Color.WHITE : Color.BLACK; + item.flags = mContext.mGlobalFlagValues; + item.index = index++; + DanmakuUtils.fillText(item, decodeXmlString(data.getText())); + if (item.getType() == BaseDanmaku.TYPE_SPECIAL + && data.getText().startsWith("[") + && data.getText().endsWith("]") + && !setSpecial(item)) { + return null; + } + return item; + } catch (Throwable ignored) { + return null; + } + } + + private boolean setSpecial(BaseDanmaku item) { + String[] textArr; + try { + JSONArray jsonArray = new JSONArray(item.text); + textArr = new String[jsonArray.length()]; + for (int i = 0; i < textArr.length; i++) { + textArr[i] = jsonArray.getString(i); + } + } catch (JSONException e) { + return false; + } + if (textArr.length < 5 || TextUtils.isEmpty(textArr[4])) return false; + try { + DanmakuUtils.fillText(item, textArr[4]); + float beginX = Float.parseFloat(textArr[0]); + float beginY = Float.parseFloat(textArr[1]); + float endX = beginX; + float endY = beginY; + String[] alphaArr = textArr[2].split("-"); + int beginAlpha = (int) (AlphaValue.MAX * Float.parseFloat(alphaArr[0])); + int endAlpha = alphaArr.length > 1 + ? (int) (AlphaValue.MAX * Float.parseFloat(alphaArr[1])) + : beginAlpha; + long alphaDuration = (long) (Float.parseFloat(textArr[3]) * 1000); + long translationDuration = alphaDuration; + long translationStartDelay = 0; + float rotateY = 0; + float rotateZ = 0; + if (textArr.length >= 7) { + rotateZ = Float.parseFloat(textArr[5]); + rotateY = Float.parseFloat(textArr[6]); + } + if (textArr.length >= 11) { + endX = Float.parseFloat(textArr[7]); + endY = Float.parseFloat(textArr[8]); + if (!TextUtils.isEmpty(textArr[9])) translationDuration = Long.parseLong(textArr[9]); + if (!TextUtils.isEmpty(textArr[10])) translationStartDelay = (long) Float.parseFloat(textArr[10]); + } + if (isPercentageNumber(textArr[0])) beginX *= DanmakuFactory.BILI_PLAYER_WIDTH; + if (isPercentageNumber(textArr[1])) beginY *= DanmakuFactory.BILI_PLAYER_HEIGHT; + if (textArr.length >= 8 && isPercentageNumber(textArr[7])) endX *= DanmakuFactory.BILI_PLAYER_WIDTH; + if (textArr.length >= 9 && isPercentageNumber(textArr[8])) endY *= DanmakuFactory.BILI_PLAYER_HEIGHT; + item.duration = new Duration(alphaDuration); + item.rotationZ = rotateZ; + item.rotationY = rotateY; + mContext.mDanmakuFactory.fillTranslationData(item, beginX, beginY, endX, endY, + translationDuration, translationStartDelay, scaleX, scaleY); + mContext.mDanmakuFactory.fillAlphaData(item, beginAlpha, endAlpha, alphaDuration); + if (textArr.length >= 12 && "true".equalsIgnoreCase(textArr[11])) { + item.textShadowColor = Color.TRANSPARENT; + } + if (textArr.length >= 14) { + ((SpecialDanmaku) item).isQuadraticEaseOut = "0".equals(textArr[13]); + } + if (textArr.length >= 15 && !TextUtils.isEmpty(textArr[14])) { + fillLinePath(item, textArr[14]); + } + return true; + } catch (Throwable ignored) { + return false; + } + } + + private void fillLinePath(BaseDanmaku item, String motionPathString) { + String motionPath = motionPathString.substring(1); + if (TextUtils.isEmpty(motionPath)) return; + String[] pointStrArray = motionPath.split("L"); + float[][] points = new float[pointStrArray.length][2]; + for (int i = 0; i < pointStrArray.length; i++) { + String[] pointArray = pointStrArray[i].split(","); + if (pointArray.length < 2) return; + points[i][0] = Float.parseFloat(pointArray[0]); + points[i][1] = Float.parseFloat(pointArray[1]); + } + DanmakuFactory.fillLinePathData(item, points, scaleX, scaleY); + } + + private boolean isPercentageNumber(String number) { + return number != null && number.contains("."); + } + + private String decodeXmlString(String text) { + if (TextUtils.isEmpty(text)) return ""; + return text.replace("&", "&") + .replace(""", "\"") + .replace("'", "'") + .replace(">", ">") + .replace("<", "<"); + } +} diff --git a/app/src/main/java/com/github/tvbox/osc/player/render/SurfaceRenderView.java b/app/src/main/java/com/github/tvbox/osc/player/render/SurfaceRenderView.java index 684b28de3f..6738acb7b3 100644 --- a/app/src/main/java/com/github/tvbox/osc/player/render/SurfaceRenderView.java +++ b/app/src/main/java/com/github/tvbox/osc/player/render/SurfaceRenderView.java @@ -100,4 +100,4 @@ public void surfaceChanged(SurfaceHolder holder, int format, int width, int heig public void surfaceDestroyed(SurfaceHolder holder) { } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/github/tvbox/osc/player/thirdparty/Kodi.java b/app/src/main/java/com/github/tvbox/osc/player/thirdparty/Kodi.java new file mode 100644 index 0000000000..8d65ad2f99 --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/player/thirdparty/Kodi.java @@ -0,0 +1,108 @@ +package com.github.tvbox.osc.player.thirdparty; + +import android.app.Activity; +import android.content.ActivityNotFoundException; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.util.Log; + +import com.github.tvbox.osc.base.App; + +import java.net.URLEncoder; +import java.util.HashMap; + +public class Kodi { + public static final String TAG = "ThirdParty.Kodi"; + + private static final String PACKAGE_NAME = "org.xbmc.kodi"; + private static final String PLAYBACK_ACTIVITY = "org.xbmc.kodi.Splash"; + + private static class KodiPackageInfo { + final String packageName; + final String activityName; + + KodiPackageInfo(String packageName, String activityName) { + this.packageName = packageName; + this.activityName = activityName; + } + } + + private static final KodiPackageInfo[] PACKAGES = { + new KodiPackageInfo(PACKAGE_NAME, PLAYBACK_ACTIVITY), + }; + + /** + * @return null if any Kodi packages not exist. + */ + public static KodiPackageInfo getPackageInfo() { + for (KodiPackageInfo pkg : PACKAGES) { + try { + ApplicationInfo info = App.getInstance().getPackageManager().getApplicationInfo(pkg.packageName, 0); + if (info.enabled) + return pkg; + else + Log.v(TAG, "Kodi package `" + pkg.packageName + "` is disabled."); + } catch (PackageManager.NameNotFoundException ex) { + Log.v(TAG, "Kodi package `" + pkg.packageName + "` does not exist."); + } + } + return null; + } + + private static class Subtitle { + final Uri uri; + String name; + String filename; + + Subtitle(Uri uri) { + if (uri.getScheme() == null) + throw new IllegalStateException("Scheme is missed for subtitle URI " + uri); + + this.uri = uri; + } + + Subtitle(String uriStr) { + this(Uri.parse(uriStr)); + } + } + + + public static boolean run(Activity activity, String url, String title, String subtitle, HashMap headers) { + KodiPackageInfo packageInfo = getPackageInfo(); + if (packageInfo == null) + return false; + + + + try { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setPackage(packageInfo.packageName); + intent.setClassName(packageInfo.packageName, packageInfo.activityName); + if (headers != null && headers.size() > 0) { + url = url + "|"; + int idx = 0; + for (String hk : headers.keySet()) { + url += hk + "=" + URLEncoder.encode(headers.get(hk), "UTF-8"); + if (idx < headers.keySet().size() -1) { + url += "&"; + } + idx ++; + } + } + intent.setData(Uri.parse(url)); + intent.putExtra("title", title); + intent.putExtra("name", title); + + if (subtitle != null && !subtitle.isEmpty()) { + intent.putExtra("subs", subtitle); + } + activity.startActivity(intent); + return true; + } catch (Exception ex) { + Log.e(TAG, "Can't run Kodi", ex); + return false; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/tvbox/osc/player/thirdparty/MXPlayer.java b/app/src/main/java/com/github/tvbox/osc/player/thirdparty/MXPlayer.java index 0132f08329..6b620bc529 100644 --- a/app/src/main/java/com/github/tvbox/osc/player/thirdparty/MXPlayer.java +++ b/app/src/main/java/com/github/tvbox/osc/player/thirdparty/MXPlayer.java @@ -11,6 +11,7 @@ import com.github.tvbox.osc.base.App; +import java.net.URLEncoder; import java.util.HashMap; public class MXPlayer { @@ -77,34 +78,33 @@ public static boolean run(Activity activity, String url, String title, String su if (packageInfo == null) return false; - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setPackage(packageInfo.packageName); - intent.setClassName(packageInfo.packageName, packageInfo.activityName); - - intent.setData(Uri.parse(url)); - intent.putExtra("title", title); - if (headers != null && headers.size() > 0) { - String[] hds = new String[headers.size() * 2]; - int idx = 0; - for (String hk : headers.keySet()) { - hds[idx] = hk; - hds[idx + 1] = headers.get(hk).trim(); - idx += 2; - } - intent.putExtra("headers", headers); - } - - if (subtitle != null && !subtitle.isEmpty()) { - Parcelable[] parcels = new Parcelable[1]; - parcels[0] = Uri.parse(subtitle); - intent.putExtra("subs", parcels); - intent.putExtra("subs.enable", parcels); - } - try { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setPackage(packageInfo.packageName); + intent.setClassName(packageInfo.packageName, packageInfo.activityName); + if (headers != null && headers.size() > 0) { + url = url + "|"; + int idx = 0; + for (String hk : headers.keySet()) { + url += hk + "=" + URLEncoder.encode(headers.get(hk), "UTF-8"); + if (idx < headers.keySet().size() -1) { + url += "&"; + } + idx ++; + } + } + intent.setData(Uri.parse(url)); + intent.putExtra("title", title); + + if (subtitle != null && !subtitle.isEmpty()) { + Parcelable[] parcels = new Parcelable[1]; + parcels[0] = Uri.parse(subtitle); + intent.putExtra("subs", parcels); + intent.putExtra("subs.enable", parcels); + } activity.startActivity(intent); return true; - } catch (ActivityNotFoundException ex) { + } catch (Exception ex) { Log.e(TAG, "Can't run MX Player(Pro)", ex); return false; } diff --git a/app/src/main/java/com/github/tvbox/osc/player/thirdparty/RemoteTVBox.java b/app/src/main/java/com/github/tvbox/osc/player/thirdparty/RemoteTVBox.java new file mode 100644 index 0000000000..311c88f713 --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/player/thirdparty/RemoteTVBox.java @@ -0,0 +1,160 @@ +package com.github.tvbox.osc.player.thirdparty; + +import android.app.Activity; +import android.text.TextUtils; + +import com.github.tvbox.osc.base.App; +import com.github.tvbox.osc.server.RemoteServer; +import com.github.tvbox.osc.util.HawkConfig; +import com.github.tvbox.osc.util.OkGoHelper; +import com.orhanobut.hawk.Hawk; + +import java.io.IOException; +import java.net.URLEncoder; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import okhttp3.Call; +import okhttp3.FormBody; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +public class RemoteTVBox { + + public static boolean run(Activity activity, String url, String title, String subtitle, HashMap headers) { + String actionUrl = getAvalibleActionUrl(); + if (TextUtils.isEmpty(actionUrl)) { + return false; + } + try { + if (headers != null && headers.size() > 0) { + url = url + "|"; + int idx = 0; + for (String hk : headers.keySet()) { + url += URLEncoder.encode(hk, "UTF-8") + "=" + URLEncoder.encode(headers.get(hk), "UTF-8"); + if (idx < headers.keySet().size() -1) { + url += "&"; + } + idx ++; + } + } + Map params = new HashMap<>(); + params.put("do", "push"); + params.put("url", url); + post(actionUrl, params, new okhttp3.Callback() { + @Override + public void onFailure(Call call, IOException e) { + e.printStackTrace(); + } + + @Override + public void onResponse(Call call, Response response) throws IOException { + String pushResult = response.body().string(); + if (pushResult.equals("ok")) { + + } + } + }); + } catch (Exception e) { + e.printStackTrace(); + } + + return true; + } + + public static void searchAvalible(Callback callback) { + final String localIp = RemoteServer.getLocalIPAddress(App.getInstance()); + int divisionIp = TextUtils.isEmpty(localIp) ? -1 : localIp.lastIndexOf("."); + if (divisionIp <= 0) { + callback.fail(true, true); + return; + } + final String prefix = localIp.substring(0, divisionIp + 1); + final int port = 9978; + final AtomicInteger finishedNum = new AtomicInteger(0); + final AtomicInteger foundNum = new AtomicInteger(0); + final int total = 254; + for (int i = 1; i <= 255; i++) { + String ip = prefix + i; + if (ip.equals(localIp)) { + continue; + } + final String actionUrl = "http://" + ip + ":" + port + "/action"; + final String viewHost = ip + ":" + port; + try { + post(actionUrl, null, new okhttp3.Callback() { + @Override + public void onFailure(Call call, IOException e) { + notifySearchFail(callback, foundNum, finishedNum, total); + } + + @Override + public void onResponse(Call call, Response response) throws IOException { + try { + String result = response.body() == null ? "" : response.body().string(); + boolean end = finishedNum.incrementAndGet() == total; + if ("ok".equalsIgnoreCase(result)) { + foundNum.incrementAndGet(); + callback.found(viewHost, end); + } else { + callback.fail(foundNum.get() == 0 && end, end); + } + } finally { + response.close(); + } + } + }); + } catch (Exception e) { + notifySearchFail(callback, foundNum, finishedNum, total); + } + } + } + + private static void notifySearchFail(Callback callback, AtomicInteger foundNum, AtomicInteger finishedNum, int total) { + boolean end = finishedNum.incrementAndGet() == total; + callback.fail(foundNum.get() == 0 && end, end); + } + + public static String getAvalible() { + return Hawk.get(HawkConfig.REMOTE_TVBOX, null); + } + + public static String getAvalibleActionUrl() { + if (getAvalible() == null) { + return ""; + } + return "http://" + getAvalible() + "/action"; + } + + public static void setAvalible(String viewHost) { + Hawk.put(HawkConfig.REMOTE_TVBOX, viewHost); + } + + public static void post(String url, Map params, okhttp3.Callback callback) { + OkHttpClient base = OkGoHelper.getDefaultClient(); + OkHttpClient.Builder builder = base != null ? base.newBuilder() : new OkHttpClient.Builder().proxySelector(OkGoHelper.proxySelector()).proxyAuthenticator(OkGoHelper.proxyAuthenticator()); + builder.readTimeout(1000, TimeUnit.MILLISECONDS); + builder.writeTimeout(1000, TimeUnit.MILLISECONDS); + builder.connectTimeout(1000, TimeUnit.MILLISECONDS); + OkHttpClient client = builder.build(); + FormBody.Builder formBodyBuilder = new FormBody.Builder(); + if (params != null && params.size() > 0) { + for(Map.Entry entry : params.entrySet()) { + formBodyBuilder.add(entry.getKey(), entry.getValue()); + } + } + FormBody formBody = formBodyBuilder.build(); + client.newCall(new Request.Builder().url(url).post(formBody).build()).enqueue(callback); + } + + public abstract class Callback { + public abstract void found(String viewHost, boolean end); + public abstract void fail(boolean all, boolean end); + } + +} + + diff --git a/app/src/main/java/com/github/tvbox/osc/player/thirdparty/VlcPlayer.java b/app/src/main/java/com/github/tvbox/osc/player/thirdparty/VlcPlayer.java new file mode 100644 index 0000000000..7182bda886 --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/player/thirdparty/VlcPlayer.java @@ -0,0 +1,76 @@ +package com.github.tvbox.osc.player.thirdparty; + +import android.app.Activity; +import android.content.ActivityNotFoundException; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.util.Log; + +import com.github.tvbox.osc.base.App; + +public class VlcPlayer { + public static final String TAG = "ThirdParty.VLC"; + + private static final String PACKAGE_NAME = "org.videolan.vlc"; + private static final String PLAYBACK_ACTIVITY = "org.videolan.vlc.gui.video.VideoPlayerActivity"; + + private static class VlcPackageInfo { + final String packageName; + final String activityName; + + VlcPackageInfo(String packageName, String activityName) { + this.packageName = packageName; + this.activityName = activityName; + } + } + + private static final VlcPackageInfo[] PACKAGES = { + new VlcPackageInfo(PACKAGE_NAME, PLAYBACK_ACTIVITY), + }; + + public static VlcPackageInfo getPackageInfo() { + for (VlcPackageInfo pkg : PACKAGES) { + try { + ApplicationInfo info = App.getInstance().getPackageManager().getApplicationInfo(pkg.packageName, 0); + if (info.enabled) + return pkg; + else + Log.v(TAG, "VLC Player package `" + pkg.packageName + "` is disabled."); + } catch (PackageManager.NameNotFoundException ex) { + Log.v(TAG, "VLC Player package `" + pkg.packageName + "` does not exist."); + } + } + return null; + } + + public static boolean run(Activity activity, String url, String title, String subtitle, long progress) { + VlcPackageInfo packageInfo = getPackageInfo(); + if (packageInfo == null) + return false; + + // https://wiki.videolan.org/Android_Player_Intents/ + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setPackage(packageInfo.packageName); + intent.setDataAndTypeAndNormalize(Uri.parse(url), "video/*"); + intent.putExtra("title", title); + + if (subtitle != null && !subtitle.isEmpty()) { + intent.putExtra("subtitles_location", subtitle); + } + + if (progress > 0) { + intent.putExtra("from_start", false); + intent.putExtra("position", progress); + } + + try { + activity.startActivity(intent); + return true; + } catch (ActivityNotFoundException ex) { + Log.e(TAG, "Can't run VLC Player(Pro)", ex); + return false; + } + } +} diff --git a/app/src/main/java/com/github/tvbox/osc/receiver/PushReceiver.java b/app/src/main/java/com/github/tvbox/osc/receiver/PushReceiver.java new file mode 100644 index 0000000000..00f2b52392 --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/receiver/PushReceiver.java @@ -0,0 +1,36 @@ +package com.github.tvbox.osc.receiver; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.text.TextUtils; + +import com.github.tvbox.osc.api.ApiConfig; +import com.github.tvbox.osc.ui.activity.DetailActivity; + +public class PushReceiver extends BroadcastReceiver { + public static String action = "android.content.movie.push.Action"; + + public static void send(Context context, String url) { + if (context == null || TextUtils.isEmpty(url)) return; + Intent intent = new Intent(); + intent.setAction(action); + intent.setPackage(context.getPackageName()); + intent.setComponent(new ComponentName(context, PushReceiver.class)); + intent.putExtra("url", url); + context.sendBroadcast(intent); + } + + @Override + public void onReceive(Context context, Intent intent) { + if (!action.equals(intent.getAction()) || intent.getExtras() == null) return; + String url = intent.getExtras().getString("url"); + if (TextUtils.isEmpty(url) || ApiConfig.get().getSource("push_agent") == null) return; + Intent newIntent = new Intent(context, DetailActivity.class); + newIntent.putExtra("id", url); + newIntent.putExtra("sourceKey", "push_agent"); + newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + context.startActivity(newIntent); + } +} diff --git a/app/src/main/java/com/github/tvbox/osc/server/CacheRequestProcess.java b/app/src/main/java/com/github/tvbox/osc/server/CacheRequestProcess.java new file mode 100644 index 0000000000..88c671ce3d --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/server/CacheRequestProcess.java @@ -0,0 +1,38 @@ +package com.github.tvbox.osc.server; + +import android.text.TextUtils; + +import com.orhanobut.hawk.Hawk; + +import java.util.Map; + +import fi.iki.elonen.NanoHTTPD; + +public class CacheRequestProcess implements RequestProcess { + + @Override + public boolean isRequest(NanoHTTPD.IHTTPSession session, String fileName) { + return fileName != null && fileName.startsWith("/cache"); + } + + @Override + public NanoHTTPD.Response doResponse(NanoHTTPD.IHTTPSession session, String fileName, Map params, Map files) { + if (params == null) params = session.getParms(); + if (files != null && !files.isEmpty()) params.putAll(files); + String action = params.get("do"); + String key = params.get("key"); + if (TextUtils.isEmpty(key)) return RemoteServer.createPlainTextResponse(NanoHTTPD.Response.Status.OK, ""); + String cacheKey = getKey(params.get("rule"), key); + if ("get".equals(action)) return RemoteServer.createPlainTextResponse(NanoHTTPD.Response.Status.OK, Hawk.get(cacheKey, "")); + if ("set".equals(action)) { + String value = params.get("value"); + Hawk.put(cacheKey, value == null ? "" : value); + } + if ("del".equals(action)) Hawk.delete(cacheKey); + return RemoteServer.createPlainTextResponse(NanoHTTPD.Response.Status.OK, "OK"); + } + + private String getKey(String rule, String key) { + return "cache_" + (TextUtils.isEmpty(rule) ? "" : rule + "_") + key; + } +} diff --git a/app/src/main/java/com/github/tvbox/osc/server/ControlManager.java b/app/src/main/java/com/github/tvbox/osc/server/ControlManager.java index 5337055cf1..6639d52da9 100644 --- a/app/src/main/java/com/github/tvbox/osc/server/ControlManager.java +++ b/app/src/main/java/com/github/tvbox/osc/server/ControlManager.java @@ -7,13 +7,17 @@ import android.text.TextUtils; import com.github.tvbox.osc.event.RefreshEvent; +import com.github.tvbox.osc.receiver.PushReceiver; import com.github.tvbox.osc.receiver.SearchReceiver; import com.github.tvbox.osc.util.HawkConfig; +import com.github.tvbox.osc.util.HistoryHelper; import com.orhanobut.hawk.Hawk; import org.greenrobot.eventbus.EventBus; import java.io.IOException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import tv.danmaku.ijk.media.player.IjkMediaPlayer; @@ -47,11 +51,17 @@ public static void init(Context context) { } public String getAddress(boolean local) { + if (mServer == null || !mServer.isStarting()) { + startServer(); + } + if (mServer == null || !mServer.isStarting()) { + return ""; + } return local ? mServer.getLoadAddress() : mServer.getServerAddress(); } public void startServer() { - if (mServer != null) { + if (mServer != null && mServer.isStarting()) { return; } do { @@ -76,13 +86,29 @@ public void onApiReceived(String url) { EventBus.getDefault().post(new RefreshEvent(RefreshEvent.TYPE_API_URL_CHANGE, url)); } + @Override + public void onLiveApiReceived(String url) { + if (!TextUtils.isEmpty(url)) { + Hawk.put(HawkConfig.LIVE_API_URL, url); + HistoryHelper.setLiveApiHistory(url); + } + EventBus.getDefault().post(new RefreshEvent(RefreshEvent.TYPE_LIVE_API_URL_CHANGE, url)); + } + + @Override + public void onDanmuApiReceived(String url) { + Hawk.put(HawkConfig.DANMU_API, TextUtils.isEmpty(url) ? "" : url); + EventBus.getDefault().post(new RefreshEvent(RefreshEvent.TYPE_SET_DANMU_SETTINGS, false)); + } + @Override public void onPushReceived(String url) { - EventBus.getDefault().post(new RefreshEvent(RefreshEvent.TYPE_PUSH_URL, url)); + PushReceiver.send(mContext, url); } }); try { mServer.start(); + com.github.catvod.Proxy.set(RemoteServer.serverPort); IjkMediaPlayer.setDotPort(Hawk.get(HawkConfig.DOH_URL, 0) > 0, RemoteServer.serverPort); break; } catch (IOException ex) { @@ -96,5 +122,6 @@ public void stopServer() { if (mServer != null && mServer.isStarting()) { mServer.stop(); } + mServer = null; } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/github/tvbox/osc/server/DataReceiver.java b/app/src/main/java/com/github/tvbox/osc/server/DataReceiver.java index 8e15dba9d0..7394f67fc4 100644 --- a/app/src/main/java/com/github/tvbox/osc/server/DataReceiver.java +++ b/app/src/main/java/com/github/tvbox/osc/server/DataReceiver.java @@ -15,5 +15,9 @@ public interface DataReceiver { void onApiReceived(String url); + void onLiveApiReceived(String url); + + void onDanmuApiReceived(String url); + void onPushReceived(String url); -} \ No newline at end of file +} diff --git a/app/src/main/java/com/github/tvbox/osc/server/InputRequestProcess.java b/app/src/main/java/com/github/tvbox/osc/server/InputRequestProcess.java index 82d444d818..4349cf900d 100644 --- a/app/src/main/java/com/github/tvbox/osc/server/InputRequestProcess.java +++ b/app/src/main/java/com/github/tvbox/osc/server/InputRequestProcess.java @@ -45,9 +45,19 @@ public NanoHTTPD.Response doResponse(NanoHTTPD.IHTTPSession session, String file mDataReceiver.onApiReceived(params.get("url").trim()); break; } + case "liveApi": { + mDataReceiver.onLiveApiReceived(params.get("url").trim()); + break; + } + case "danmuApi": { + mDataReceiver.onDanmuApiReceived(params.get("url").trim()); + break; + } case "push": { - // 暂未实现 - mDataReceiver.onPushReceived(params.get("url").trim()); + String url = params.get("url"); + if (url != null && url.trim().length() > 0) { + mDataReceiver.onPushReceived(url.trim()); + } break; } } diff --git a/app/src/main/java/com/github/tvbox/osc/server/RemoteServer.java b/app/src/main/java/com/github/tvbox/osc/server/RemoteServer.java index 63065c08c9..781d81e876 100644 --- a/app/src/main/java/com/github/tvbox/osc/server/RemoteServer.java +++ b/app/src/main/java/com/github/tvbox/osc/server/RemoteServer.java @@ -1,16 +1,27 @@ package com.github.tvbox.osc.server; +import static com.github.tvbox.osc.util.RegexUtils.getPattern; + import android.annotation.SuppressLint; import android.content.Context; import android.net.wifi.WifiManager; import android.os.Environment; +import android.text.TextUtils; +import android.util.Base64; import com.github.tvbox.osc.R; import com.github.tvbox.osc.api.ApiConfig; +import com.github.tvbox.osc.base.App; +import com.github.tvbox.osc.bean.VodInfo; +import com.github.tvbox.osc.event.RefreshEvent; import com.github.tvbox.osc.event.ServerEvent; +import com.github.tvbox.osc.receiver.PushReceiver; import com.github.tvbox.osc.util.FileUtils; +import com.github.tvbox.osc.util.LOG; import com.github.tvbox.osc.util.OkGoHelper; +import com.github.tvbox.osc.util.Proxy; import com.google.gson.JsonArray; +import com.google.gson.Gson; import com.google.gson.JsonObject; import org.greenrobot.eventbus.EventBus; @@ -22,10 +33,13 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.UnsupportedEncodingException; import java.net.Inet4Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; +import java.net.URLDecoder; +import java.net.URLEncoder; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; @@ -52,6 +66,7 @@ public class RemoteServer extends NanoHTTPD { public static int serverPort = 9978; private boolean isStarted = false; private DataReceiver mDataReceiver; + public static String m3u8Content; private ArrayList getRequestList = new ArrayList<>(); private ArrayList postRequestList = new ArrayList<>(); @@ -70,10 +85,12 @@ private void addGetRequestProcess() { getRequestList.add(new RawRequestProcess(this.mContext, "/jquery.js", R.raw.jquery, "application/x-javascript")); getRequestList.add(new RawRequestProcess(this.mContext, "/script.js", R.raw.script, "application/x-javascript")); getRequestList.add(new RawRequestProcess(this.mContext, "/favicon.ico", R.drawable.app_icon, "image/x-icon")); + getRequestList.add(new CacheRequestProcess()); } private void addPostRequestProcess() { postRequestList.add(new InputRequestProcess(this)); + postRequestList.add(new CacheRequestProcess()); } @Override @@ -89,6 +106,38 @@ public void stop() { isStarted = false; } + private Response getProxy(Object[] rs){ + try { + if (rs == null || rs.length < 3) { + LOG.e("echo-proxy error: empty proxy result"); + return NanoHTTPD.newFixedLengthResponse(Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "500"); + } + if (rs[0] instanceof NanoHTTPD.Response) return (NanoHTTPD.Response) rs[0]; + int code = (int) rs[0]; + String mime = (String) rs[1]; + InputStream stream = rs[2] != null ? (InputStream) rs[2] : null; + Response response = NanoHTTPD.newChunkedResponse( + Response.Status.lookup(code), + mime, + stream + ); + // 添加头部信息 + if (rs.length >= 4 && rs[3] instanceof Map) { + @SuppressWarnings("unchecked") + Map mapHeader = (Map) rs[3]; + if(!mapHeader.isEmpty()){ + for (String key : mapHeader.keySet()) { + response.addHeader(key, mapHeader.get(key)); + } + } + } + return response; + } catch (Throwable th) { + LOG.e("echo-proxy error: " + th.getMessage()); + return NanoHTTPD.newFixedLengthResponse(Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "500"); + } + } + @Override public Response serve(IHTTPSession session) { EventBus.getDefault().post(new ServerEvent(ServerEvent.SERVER_CONNECTION)); @@ -98,30 +147,15 @@ public Response serve(IHTTPSession session) { fileName = fileName.substring(0, fileName.indexOf('?')); } if (session.getMethod() == Method.GET) { + if (isProxyRequest(fileName, session.getParms())) { + return handleProxy(session); + } for (RequestProcess process : getRequestList) { if (process.isRequest(session, fileName)) { return process.doResponse(session, fileName, session.getParms(), null); } } - if (fileName.equals("/proxy")) { - Map params = session.getParms(); - if (params.containsKey("do")) { - Object[] rs = ApiConfig.get().proxyLocal(params); - try { - int code = (int) rs[0]; - String mime = (String) rs[1]; - InputStream stream = rs[2] != null ? (InputStream) rs[2] : null; - Response response = NanoHTTPD.newChunkedResponse( - NanoHTTPD.Response.Status.lookup(code), - mime, - stream - ); - return response; - } catch (Throwable th) { - return NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "500"); - } - } - } else if (fileName.startsWith("/file/")) { + if (fileName.startsWith("/file/")) { try { String f = fileName.substring(6); String root = Environment.getExternalStorageDirectory().getAbsolutePath(); @@ -148,6 +182,37 @@ public Response serve(IHTTPSession session) { rs = new byte[0]; } return NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.OK, "application/dns-message", new ByteArrayInputStream(rs), rs.length); + } else if (fileName.startsWith("/push/")) { + String url = fileName.substring(6); + if (url.startsWith("b64:")) { + try { + url = new String(Base64.decode(url.substring(4), Base64.DEFAULT | Base64.URL_SAFE | Base64.NO_WRAP), "UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } else { + url = URLDecoder.decode(url); + } + PushReceiver.send(mContext, url); + return NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.OK, NanoHTTPD.MIME_PLAINTEXT, "ok"); + } else if (fileName.equals("/action")) { + return handleAction(session.getParms()); + } else if (fileName.startsWith("/proxyM3u8")) { +// com.github.tvbox.osc.util.LOG.i("echo-proxyM3u8 length:" + (m3u8Content == null ? 0 : m3u8Content.length())); + return NanoHTTPD.newFixedLengthResponse(Response.Status.OK, "application/vnd.apple.mpegurl", m3u8Content == null ? "" : m3u8Content); + } + else if (fileName.startsWith("/dash/")) { + String dashData = App.getInstance().getDashData(); + try { + String data = new String(Base64.decode(dashData, Base64.DEFAULT | Base64.NO_WRAP), "UTF-8"); + return NanoHTTPD.newFixedLengthResponse( + Response.Status.OK, + "application/dash+xml", + data + ); + } catch (Throwable th) { + return NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, dashData); + } } } else if (session.getMethod() == Method.POST) { Map files = new HashMap(); @@ -157,7 +222,7 @@ public Response serve(IHTTPSession session) { if (hd != null) { // cuke: 修正中文乱码问题 if (hd.toLowerCase().contains("multipart/form-data") && !hd.toLowerCase().contains("charset=")) { - Matcher matcher = Pattern.compile("[ |\t]*(boundary[ |\t]*=[ |\t]*['|\"]?[^\"^'^;^,]*['|\"]?)", Pattern.CASE_INSENSITIVE).matcher(hd); + Matcher matcher = getPattern("[ |\t]*(boundary[ |\t]*=[ |\t]*['|\"]?[^\"^'^;^,]*['|\"]?)", Pattern.CASE_INSENSITIVE).matcher(hd); String boundary = matcher.find() ? matcher.group(1) : null; if (boundary != null) { session.getHeaders().put("content-type", "multipart/form-data; charset=utf-8; " + boundary); @@ -239,6 +304,84 @@ public Response serve(IHTTPSession session) { return getRequestList.get(0).doResponse(session, "", null, null); } + private boolean isProxyRequest(String fileName, Map params) { + if (params == null) return false; + if (!params.containsKey("do") && !params.containsKey("go")) return false; + return fileName.equals("/proxy") || fileName.equals("/"); + } + + private Response handleProxy(IHTTPSession session) { + Map params = session.getParms(); + params.putAll(session.getHeaders()); + if (params.containsKey("do")) { + boolean isDanmuProxy = "danmu".equals(params.get("do")); + if (isDanmuProxy) normalizeDanmuParams(params); + if (isDanmuProxy) LOG.i("echo-proxy-danmu params: " + params.toString()); + Object[] rs = ApiConfig.get().proxyLocal(params); + return getProxy(rs); + } + if (params.containsKey("go")) { + Object[] rs = Proxy.proxy(params); + return getProxy(rs); + } + return getProxy(null); + } + + private Response handleAction(Map params) { + if (params == null) return createPlainTextResponse(Response.Status.OK, "ok"); + String action = params.get("do"); + if ("refresh".equals(action)) { + handleRefreshAction(params); + } + return createPlainTextResponse(Response.Status.OK, "ok"); + } + + private void handleRefreshAction(Map params) { + String type = params.get("type"); + if ("danmaku".equals(type)) { + String path = params.get("path"); + EventBus.getDefault().post(new RefreshEvent(RefreshEvent.TYPE_DANMU_REFRESH, path == null ? "" : path)); + } + } + + private void normalizeDanmuParams(Map params) { + try { + VodInfo vodInfo = App.getInstance().getVodInfo(); + if (vodInfo == null) return; + if (!TextUtils.isEmpty(vodInfo.name)) params.put("vodName", vodInfo.name); + if (!isNumeric(params.get("vodIndex"))) { + String episode = getCurrentEpisodeIndex(vodInfo); + if (!TextUtils.isEmpty(episode)) params.put("vodIndex", episode); + } + } catch (Throwable th) { + LOG.e("echo-proxy-danmu normalize error: " + th.getMessage()); + } + } + + private String getCurrentEpisodeIndex(VodInfo vodInfo) { + if (vodInfo.seriesMap != null && !TextUtils.isEmpty(vodInfo.playFlag)) { + java.util.List series = vodInfo.seriesMap.get(vodInfo.playFlag); + if (series != null && vodInfo.playIndex >= 0 && vodInfo.playIndex < series.size()) { + VodInfo.VodSeries current = series.get(vodInfo.playIndex); + if (current != null && !TextUtils.isEmpty(current.name)) { + String number = extractNumber(current.name); + return TextUtils.isEmpty(number) ? current.name : number; + } + } + } + return String.valueOf(Math.max(0, vodInfo.playIndex) + 1); + } + + private boolean isNumeric(String text) { + return !TextUtils.isEmpty(text) && text.matches("\\d+"); + } + + private String extractNumber(String text) { + if (TextUtils.isEmpty(text)) return ""; + Matcher matcher = getPattern("\\d+").matcher(text); + return matcher.find() ? matcher.group() : ""; + } + public void setDataReceiver(DataReceiver receiver) { mDataReceiver = receiver; } @@ -384,4 +527,4 @@ void extractFile(InputStream inputStream, String destFilePath) throws Throwable bos.close(); } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/github/tvbox/osc/subtitle/DefaultSubtitleEngine.java b/app/src/main/java/com/github/tvbox/osc/subtitle/DefaultSubtitleEngine.java new file mode 100644 index 0000000000..c665bb2bf1 --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/subtitle/DefaultSubtitleEngine.java @@ -0,0 +1,283 @@ +/* + * Copyright (C) of Avery + * + * _ooOoo_ + * o8888888o + * 88" . "88 + * (| -_- |) + * O\ = /O + * ____/`- -'\____ + * .' \\| |// `. + * / \\||| : |||// \ + * / _||||| -:- |||||- \ + * | | \\\ - /// | | + * | \_| ''\- -/'' | | + * \ .-\__ `-` ___/-. / + * ___`. .' /- -.- -\ `. . __ + * ."" '< `.___\_<|>_/___.' >'"". + * | | : `- \`.;`\ _ /`;.`/ - ` : | | + * \ \ `-. \_ __\ /__ _/ .-` / / + * ======`-.____`-.___\_____/___.-`____.-'====== + * `=- -=' + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + * Buddha bless, there will never be bug!!! + */ + +package com.github.tvbox.osc.subtitle; + +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Message; +import androidx.annotation.Nullable; +import android.text.TextUtils; +import android.util.Log; + +import com.github.tvbox.osc.base.App; +import com.github.tvbox.osc.cache.CacheManager; +import com.github.tvbox.osc.subtitle.model.Subtitle; +import com.github.tvbox.osc.subtitle.model.Time; +import com.github.tvbox.osc.util.FileUtils; +import com.github.tvbox.osc.util.MD5; +import com.github.tvbox.osc.util.SubtitleHelper; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.TreeMap; + +import xyz.doikki.videoplayer.player.AbstractPlayer; + +/** + * @author AveryZhong. + */ + +public class DefaultSubtitleEngine implements SubtitleEngine { + private static final String TAG = DefaultSubtitleEngine.class.getSimpleName(); + private static final int MSG_REFRESH = 0x888; + private static final int REFRESH_INTERVAL = 100; + + @Nullable + private HandlerThread mHandlerThread; + @Nullable + private Handler mWorkHandler; + @Nullable + private List mSubtitles; + private UIRenderTask mUIRenderTask; + private AbstractPlayer mMediaPlayer; + private OnSubtitlePreparedListener mOnSubtitlePreparedListener; + private OnSubtitleChangeListener mOnSubtitleChangeListener; + + public DefaultSubtitleEngine() { + + } + + @Override + public void bindToMediaPlayer(AbstractPlayer mediaPlayer) { + mMediaPlayer = mediaPlayer; + } + + @Override + public void setSubtitlePath(final String path) { + initWorkThread(); + reset(); + if (TextUtils.isEmpty(path)) { + Log.w(TAG, "loadSubtitleFromRemote: path is null."); + return; + } + + SubtitleLoader.loadSubtitle(path, new SubtitleLoader.Callback() { + @Override + public void onSuccess(final SubtitleLoadSuccessResult subtitleLoadSuccessResult) { + if (subtitleLoadSuccessResult == null) { + Log.d(TAG, "onSuccess: subtitleLoadSuccessResult is null."); + return; + } + if (subtitleLoadSuccessResult.timedTextObject == null) { + Log.d(TAG, "onSuccess: timedTextObject is null."); + return; + } + final TreeMap captions = subtitleLoadSuccessResult.timedTextObject.captions; + if (captions == null) { + Log.d(TAG, "onSuccess: captions is null."); + return; + } + mSubtitles = new ArrayList<>(captions.values()); + setSubtitleDelay(SubtitleHelper.getTimeDelay()); + notifyPrepared(); + + String subtitlePath = subtitleLoadSuccessResult.subtitlePath; + if (subtitlePath.startsWith("http://") || subtitlePath.startsWith("https://")) { + String subtitleFileCacheDir = App.getInstance().getCacheDir().getAbsolutePath() + "/zimu/"; + File cacheDir = new File(subtitleFileCacheDir); + if (!cacheDir.exists()) { + cacheDir.mkdirs(); + } + String subtitleFile = subtitleFileCacheDir + subtitleLoadSuccessResult.fileName; + File cacheSubtitleFile = new File(subtitleFile); + boolean writeResult = FileUtils.writeSimple(subtitleLoadSuccessResult.content.getBytes(), cacheSubtitleFile); + if (writeResult && playSubtitleCacheKey != null) { + CacheManager.save(MD5.string2MD5(getPlaySubtitleCacheKey()), subtitleFile); + } + } else { + CacheManager.save(MD5.string2MD5(getPlaySubtitleCacheKey()), path); + } + } + + @Override + public void onError(final Exception exception) { + Log.e(TAG, "onError: " + exception.getMessage()); + } + }); + } + + @Override + public void setSubtitleDelay(Integer milliseconds) { + if (milliseconds == 0) { + return; + } + if (mSubtitles == null || mSubtitles.size() == 0) { + return; + } + List thisSubtitles = mSubtitles; + mSubtitles = null; + for (int i = 0; i < thisSubtitles.size(); i++) { + Subtitle subtitle = thisSubtitles.get(i); + Time start = subtitle.start; + Time end = subtitle.end; + start.mseconds += milliseconds; + end.mseconds += milliseconds; + if (start.mseconds <= 0) { + start.mseconds = 0; + } + if (end.mseconds <= 0) { + end.mseconds = 0; + } + subtitle.start = start; + subtitle.end = end; + } + mSubtitles = thisSubtitles; + } + + private static String playSubtitleCacheKey; + public void setPlaySubtitleCacheKey(String cacheKey) { + playSubtitleCacheKey = cacheKey; + } + + public String getPlaySubtitleCacheKey() { + return playSubtitleCacheKey; + } + + @Override + public void reset() { + stop(); + mSubtitles = null; + mUIRenderTask = null; + } + + @Override + public void start() { + Log.d(TAG, "start: "); + if (mMediaPlayer == null) { + Log.w(TAG, "MediaPlayer is not bind, You must bind MediaPlayer to " + + SubtitleEngine.class.getSimpleName() + + " before start() method be called," + + " you can do this by call " + + "bindToMediaPlayer(MediaPlayer mediaPlayer) method."); + return; + } + stop(); + if (mWorkHandler != null) { + mWorkHandler.sendEmptyMessageDelayed(MSG_REFRESH, REFRESH_INTERVAL); + } + + } + + @Override + public void pause() { + stop(); + } + + @Override + public void resume() { + start(); + } + + @Override + public void stop() { + if (mWorkHandler != null) { + mWorkHandler.removeMessages(MSG_REFRESH); + } + } + + @Override + public void destroy() { + Log.d(TAG, "destroy: "); + stopWorkThread(); + reset(); + + } + + private void initWorkThread() { + stopWorkThread(); + mHandlerThread = new HandlerThread("SubtitleFindThread"); + mHandlerThread.start(); + mWorkHandler = new Handler(mHandlerThread.getLooper(), new Handler.Callback() { + @Override + public boolean handleMessage(final Message msg) { + try { + long delay = REFRESH_INTERVAL; + if (mMediaPlayer != null && mMediaPlayer.isPlaying()) { + long position = mMediaPlayer.getCurrentPosition(); + Subtitle subtitle = SubtitleFinder.find(position, mSubtitles); + notifyRefreshUI(subtitle); + if (subtitle != null) { + delay = subtitle.end.mseconds - position; + } + + } + if (mWorkHandler != null) { + mWorkHandler.sendEmptyMessageDelayed(MSG_REFRESH, delay); + } + } catch (Exception e) { + // ignored + } + return true; + } + }); + } + + private void stopWorkThread() { + if (mHandlerThread != null) { + mHandlerThread.quit(); + mHandlerThread = null; + } + if (mWorkHandler != null) { + mWorkHandler.removeCallbacksAndMessages(null); + mWorkHandler = null; + } + } + + private void notifyRefreshUI(final Subtitle subtitle) { + if (mUIRenderTask == null) { + mUIRenderTask = new UIRenderTask(mOnSubtitleChangeListener); + } + mUIRenderTask.execute(subtitle); + } + + private void notifyPrepared() { + if (mOnSubtitlePreparedListener != null) { + mOnSubtitlePreparedListener.onSubtitlePrepared(mSubtitles); + } + } + + @Override + public void setOnSubtitlePreparedListener(final OnSubtitlePreparedListener listener) { + mOnSubtitlePreparedListener = listener; + } + + @Override + public void setOnSubtitleChangeListener(final OnSubtitleChangeListener listener) { + mOnSubtitleChangeListener = listener; + } + +} diff --git a/app/src/main/java/com/github/tvbox/osc/subtitle/SubtitleEngine.java b/app/src/main/java/com/github/tvbox/osc/subtitle/SubtitleEngine.java new file mode 100644 index 0000000000..3ad3a2d392 --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/subtitle/SubtitleEngine.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) of Avery + * + * _ooOoo_ + * o8888888o + * 88" . "88 + * (| -_- |) + * O\ = /O + * ____/`- -'\____ + * .' \\| |// `. + * / \\||| : |||// \ + * / _||||| -:- |||||- \ + * | | \\\ - /// | | + * | \_| ''\- -/'' | | + * \ .-\__ `-` ___/-. / + * ___`. .' /- -.- -\ `. . __ + * ."" '< `.___\_<|>_/___.' >'"". + * | | : `- \`.;`\ _ /`;.`/ - ` : | | + * \ \ `-. \_ __\ /__ _/ .-` / / + * ======`-.____`-.___\_____/___.-`____.-'====== + * `=- -=' + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + * Buddha bless, there will never be bug!!! + */ + +package com.github.tvbox.osc.subtitle; + +import androidx.annotation.Nullable; + +import com.github.tvbox.osc.subtitle.model.Subtitle; + +import java.util.List; + +import xyz.doikki.videoplayer.player.AbstractPlayer; + +/** + * @author AveryZhong. + */ + +public interface SubtitleEngine { + + /** + * 设置字幕路径,加载字幕 + * + * @param path 字幕路径(本地路径或者是远程路径) + */ + void setSubtitlePath(String path); + + /** + * 字幕延时 + * @param milliseconds + */ + void setSubtitleDelay(Integer milliseconds); + + void setPlaySubtitleCacheKey(String cacheKey); + + String getPlaySubtitleCacheKey(); + + /** + * 开启字幕刷新任务 + */ + void start(); + + /** + * 暂停 + */ + void pause(); + + /** + * 恢复 + */ + void resume(); + + /** + * 停止字幕刷新任务 + */ + void stop(); + + /** + * 重置 + */ + void reset(); + + /** + * 销毁字幕 + */ + void destroy(); + + /** + * 绑定AbstractPlayer + * + * @param mediaPlayer mediaPlayer + */ + void bindToMediaPlayer(AbstractPlayer mediaPlayer); + + /** + * 设置字幕准备完成监接口 + * + * @param listener OnSubtitlePreparedListener + */ + void setOnSubtitlePreparedListener(OnSubtitlePreparedListener listener); + + /** + * 设置字幕改变监听接口 + * + * @param listener OnSubtitleChangeListener + */ + void setOnSubtitleChangeListener(OnSubtitleChangeListener listener); + + /** + * 幕准备完成监接口 + */ + interface OnSubtitlePreparedListener { + void onSubtitlePrepared(@Nullable List subtitles); + } + + /** + * 字幕改变监听接口 + */ + interface OnSubtitleChangeListener { + void onSubtitleChanged(@Nullable Subtitle subtitle); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/github/tvbox/osc/subtitle/SubtitleFinder.java b/app/src/main/java/com/github/tvbox/osc/subtitle/SubtitleFinder.java new file mode 100644 index 0000000000..13e6c1f45f --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/subtitle/SubtitleFinder.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) of Avery + * + * _ooOoo_ + * o8888888o + * 88" . "88 + * (| -_- |) + * O\ = /O + * ____/`- -'\____ + * .' \\| |// `. + * / \\||| : |||// \ + * / _||||| -:- |||||- \ + * | | \\\ - /// | | + * | \_| ''\- -/'' | | + * \ .-\__ `-` ___/-. / + * ___`. .' /- -.- -\ `. . __ + * ."" '< `.___\_<|>_/___.' >'"". + * | | : `- \`.;`\ _ /`;.`/ - ` : | | + * \ \ `-. \_ __\ /__ _/ .-` / / + * ======`-.____`-.___\_____/___.-`____.-'====== + * `=- -=' + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + * Buddha bless, there will never be bug!!! + */ + +package com.github.tvbox.osc.subtitle; + +import androidx.annotation.Nullable; + +import com.github.tvbox.osc.subtitle.model.Subtitle; + +import java.util.List; + +/** + * @author AveryZhong. + */ + +public class SubtitleFinder { + private SubtitleFinder() { + throw new AssertionError("No instance for you"); + } + + @Nullable + public static Subtitle find(long position, List subtitles) { + if (subtitles == null || subtitles.isEmpty()) { + return null; + } + int start = 0; + int end = subtitles.size() - 1; + while (start <= end) { + int middle = (start + end) / 2; + Subtitle middleSubtitle = subtitles.get(middle); + if (position < middleSubtitle.start.mseconds) { + if (position > middleSubtitle.end.mseconds) { + return middleSubtitle; + } + end = middle - 1; + } else if (position > middleSubtitle.end.mseconds) { + if (position < middleSubtitle.start.mseconds) { + return middleSubtitle; + } + start = middle + 1; + } else if (position >= middleSubtitle.start.mseconds + && position <= middleSubtitle.end.mseconds) { + return middleSubtitle; + } + } + return null; + } +} diff --git a/app/src/main/java/com/github/tvbox/osc/subtitle/SubtitleLoadSuccessResult.java b/app/src/main/java/com/github/tvbox/osc/subtitle/SubtitleLoadSuccessResult.java new file mode 100644 index 0000000000..9d40ad8beb --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/subtitle/SubtitleLoadSuccessResult.java @@ -0,0 +1,10 @@ +package com.github.tvbox.osc.subtitle; + +import com.github.tvbox.osc.subtitle.model.TimedTextObject; + +public class SubtitleLoadSuccessResult { + public String fileName; + public String content; + public TimedTextObject timedTextObject; + public String subtitlePath; +} diff --git a/app/src/main/java/com/github/tvbox/osc/subtitle/SubtitleLoader.java b/app/src/main/java/com/github/tvbox/osc/subtitle/SubtitleLoader.java new file mode 100644 index 0000000000..3da7e5ff90 --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/subtitle/SubtitleLoader.java @@ -0,0 +1,249 @@ +package com.github.tvbox.osc.subtitle; + +import android.net.Uri; +import android.text.TextUtils; +import android.util.Log; + +import com.github.tvbox.osc.subtitle.exception.FatalParsingException; +import com.github.tvbox.osc.subtitle.format.FormatASS; +import com.github.tvbox.osc.subtitle.format.FormatSRT; +import com.github.tvbox.osc.subtitle.format.FormatSTL; +import com.github.tvbox.osc.subtitle.format.TimedTextFileFormat; +import com.github.tvbox.osc.subtitle.model.TimedTextObject; +import com.github.tvbox.osc.subtitle.runtime.AppTaskExecutor; +import com.github.tvbox.osc.util.FileUtils; +import com.github.tvbox.osc.util.UnicodeReader; +import com.lzy.okgo.OkGo; + +import org.apache.commons.io.input.ReaderInputStream; +import org.mozilla.universalchardet.UniversalDetector; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.net.URLDecoder; +import java.nio.charset.Charset; + +import okhttp3.Response; + +/** + * @author AveryZhong. + */ + +public class SubtitleLoader { + private static final String TAG = SubtitleLoader.class.getSimpleName(); + + private SubtitleLoader() { + throw new AssertionError("No instance for you."); + } + + public static void loadSubtitle(final String path, final Callback callback) { + if (TextUtils.isEmpty(path)) { + return; + } + if (path.startsWith("http://") + || path.startsWith("https://")) { + loadFromRemoteAsync(path, callback); + } else { + loadFromLocalAsync(path, callback); + } + } + + private static void loadFromRemoteAsync(final String remoteSubtitlePath, + final Callback callback) { + AppTaskExecutor.deskIO().execute(new Runnable() { + @Override + public void run() { + try { + final SubtitleLoadSuccessResult subtitleLoadSuccessResult = loadFromRemote(remoteSubtitlePath); + if (callback != null) { + AppTaskExecutor.mainThread().execute(new Runnable() { + @Override + public void run() { + callback.onSuccess(subtitleLoadSuccessResult); + } + }); + } + + } catch (final Exception e) { + e.printStackTrace(); + if (callback != null) { + AppTaskExecutor.mainThread().execute(new Runnable() { + @Override + public void run() { + callback.onError(e); + } + }); + } + + } + } + }); + } + + private static void loadFromLocalAsync(final String localSubtitlePath, + final Callback callback) { + AppTaskExecutor.deskIO().execute(new Runnable() { + @Override + public void run() { + try { + final SubtitleLoadSuccessResult subtitleLoadSuccessResult = loadFromLocal(localSubtitlePath); + if (callback != null) { + AppTaskExecutor.mainThread().execute(new Runnable() { + @Override + public void run() { + callback.onSuccess(subtitleLoadSuccessResult); + } + }); + } + + } catch (final Exception e) { + e.printStackTrace(); + if (callback != null) { + AppTaskExecutor.mainThread().execute(new Runnable() { + @Override + public void run() { + callback.onError(e); + } + }); + } + + } + } + }); + } + + public SubtitleLoadSuccessResult loadSubtitle(String path) { + if (TextUtils.isEmpty(path)) { + return null; + } + try { + if (path.startsWith("http://") + || path.startsWith("https://")) { + return loadFromRemote(path); + } else { + return loadFromLocal(path); + } + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + private static SubtitleLoadSuccessResult loadFromRemote(final String remoteSubtitlePath) + throws IOException, FatalParsingException, Exception { + Log.d(TAG, "parseRemote: remoteSubtitlePath = " + remoteSubtitlePath); + String referer = ""; + if (remoteSubtitlePath.contains("alicloud") || remoteSubtitlePath.contains("aliyundrive")) { + referer = "https://www.aliyundrive.com/"; + } else if (remoteSubtitlePath.contains("assrt.net")) { + referer = "https://secure.assrt.net/"; + } + String ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.54 Safari/537.36"; + Response response = OkGo.get(remoteSubtitlePath.split("#")[0]) + .headers("Referer", referer) + .headers("User-Agent", ua) + .execute(); + byte[] bytes = response.body().bytes(); + UniversalDetector detector = new UniversalDetector(null); + detector.handleData(bytes, 0, bytes.length); + detector.dataEnd(); + String encoding = detector.getDetectedCharset(); + if (TextUtils.isEmpty(encoding)) encoding = "UTF-8"; + String content = new String(bytes, encoding); + InputStream is = new ByteArrayInputStream(content.getBytes()); + String filename = ""; + String contentDispostion = response.header("content-disposition", ""); + String[] cd = contentDispostion.split(";"); + if (cd.length > 1) { + String filenameInfo = cd[1]; + filenameInfo = filenameInfo.trim(); + if (filenameInfo.startsWith("filename=")) { + filename = filenameInfo.replace("filename=", ""); + filename = filename.replace("\"", ""); + } else if (filenameInfo.startsWith("filename*=")) { + filename = filenameInfo.substring(filenameInfo.lastIndexOf("''")+2); + } + filename = filename.trim(); + filename = URLDecoder.decode(filename); + } + String filePath = filename; + if (filename == null || filename.length() < 1) { + Uri uri = Uri.parse(remoteSubtitlePath); + filePath = uri.getPath(); + } + if (!filePath.contains(".") && remoteSubtitlePath.contains("#")) { + filePath = remoteSubtitlePath.split("#")[1]; + filePath = URLDecoder.decode(filePath); + } + SubtitleLoadSuccessResult subtitleLoadSuccessResult = new SubtitleLoadSuccessResult(); + subtitleLoadSuccessResult.timedTextObject = loadAndParse(is, filePath); + subtitleLoadSuccessResult.fileName = filePath; + subtitleLoadSuccessResult.content = content; + subtitleLoadSuccessResult.subtitlePath = remoteSubtitlePath; + return subtitleLoadSuccessResult; + } + + private static SubtitleLoadSuccessResult loadFromLocal(final String localSubtitlePath) + throws IOException, FatalParsingException { + Log.d(TAG, "parseLocal: localSubtitlePath = " + localSubtitlePath); + File file = new File(localSubtitlePath); + if (!file.exists()) { + Log.d(TAG, "parseLocal: localSubtitlePath = " + localSubtitlePath + " file not exsits"); + return null; + } + byte[] bytes = FileUtils.readSimple(file); + UniversalDetector detector = new UniversalDetector(null); + detector.handleData(bytes, 0, bytes.length); + detector.dataEnd(); + String encoding = detector.getDetectedCharset(); + String content = new String(bytes, encoding); + InputStream is = new ByteArrayInputStream(content.getBytes()); + String filePath = file.getPath(); + SubtitleLoadSuccessResult subtitleLoadSuccessResult = new SubtitleLoadSuccessResult(); + subtitleLoadSuccessResult.timedTextObject = loadAndParse(is, filePath); + String fileName = filePath.substring(filePath.lastIndexOf("/") + 1); + subtitleLoadSuccessResult.fileName = fileName; + subtitleLoadSuccessResult.subtitlePath = localSubtitlePath; + return subtitleLoadSuccessResult; + } + + private static TimedTextObject loadAndParse(final InputStream is, final String filePath) + throws IOException, FatalParsingException { + String fileName = filePath.substring(filePath.lastIndexOf("/") + 1); + String ext = ""; + if (fileName.lastIndexOf(".") > 0) { + ext = fileName.substring(fileName.lastIndexOf(".")); + } + Log.d(TAG, "parse: name = " + fileName + ", ext = " + ext); + Reader reader = new UnicodeReader(is); //处理有BOM头的utf8 + InputStream newInputStream = new ReaderInputStream(reader, Charset.defaultCharset()); + if (".srt".equalsIgnoreCase(ext)) { + return new FormatSRT().parseFile(fileName, newInputStream); + } else if (".ass".equalsIgnoreCase(ext)) { + return new FormatASS().parseFile(fileName, newInputStream); + } else if (".stl".equalsIgnoreCase(ext)) { + return new FormatSTL().parseFile(fileName, newInputStream); + } else if (".ttml".equalsIgnoreCase(ext)) { + return new FormatSTL().parseFile(fileName, newInputStream); + } + TimedTextFileFormat[] arr = {new FormatSRT(), new FormatASS(), new FormatSTL(), new FormatSTL()}; + for(TimedTextFileFormat oneFormat : arr) { + try { + TimedTextObject obj = oneFormat.parseFile(fileName, newInputStream); + return obj; + } catch (Exception e) { + continue; + } + } + return null; + } + + public interface Callback { + void onSuccess(SubtitleLoadSuccessResult SubtitleLoadSuccessResult); + + void onError(Exception exception); + } +} diff --git a/app/src/main/java/com/github/tvbox/osc/subtitle/UIRenderTask.java b/app/src/main/java/com/github/tvbox/osc/subtitle/UIRenderTask.java new file mode 100644 index 0000000000..f221e422d1 --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/subtitle/UIRenderTask.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) of Avery + * + * _ooOoo_ + * o8888888o + * 88" . "88 + * (| -_- |) + * O\ = /O + * ____/`- -'\____ + * .' \\| |// `. + * / \\||| : |||// \ + * / _||||| -:- |||||- \ + * | | \\\ - /// | | + * | \_| ''\- -/'' | | + * \ .-\__ `-` ___/-. / + * ___`. .' /- -.- -\ `. . __ + * ."" '< `.___\_<|>_/___.' >'"". + * | | : `- \`.;`\ _ /`;.`/ - ` : | | + * \ \ `-. \_ __\ /__ _/ .-` / / + * ======`-.____`-.___\_____/___.-`____.-'====== + * `=- -=' + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + * Buddha bless, there will never be bug!!! + */ + +package com.github.tvbox.osc.subtitle; + +import com.github.tvbox.osc.subtitle.model.Subtitle; +import com.github.tvbox.osc.subtitle.runtime.AppTaskExecutor; + +/** + * @author AveryZhong. + */ + +public class UIRenderTask implements Runnable { + + private Subtitle mSubtitle; + private SubtitleEngine.OnSubtitleChangeListener mOnSubtitleChangeListener; + + public UIRenderTask(final SubtitleEngine.OnSubtitleChangeListener l) { + mOnSubtitleChangeListener = l; + } + + @Override + public void run() { + if (mOnSubtitleChangeListener != null) { + mOnSubtitleChangeListener.onSubtitleChanged(mSubtitle); + } + } + + public void execute(final Subtitle subtitle) { + mSubtitle = subtitle; + AppTaskExecutor.mainThread().execute(this); + } +} diff --git a/app/src/main/java/com/github/tvbox/osc/subtitle/exception/FatalParsingException.java b/app/src/main/java/com/github/tvbox/osc/subtitle/exception/FatalParsingException.java new file mode 100755 index 0000000000..4b96f6abc6 --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/subtitle/exception/FatalParsingException.java @@ -0,0 +1,52 @@ +/** + * Class that represents the .ASS and .SSA subtitle file format + * + *

+ * Copyright (c) 2012 J. David Requejo
+ * j[dot]david[dot]requejo[at] Gmail + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software + * is furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in all copies + * or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE + * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * @author J. David REQUEJO + * + */ + +package com.github.tvbox.osc.subtitle.exception; + +/** + * This class represents problems that may arise during the parsing of a subttile file. + * + * @author J. David + * + */ +public class FatalParsingException extends Exception { + + private static final long serialVersionUID = 6798827566637277804L; + + private String parsingError; + + public FatalParsingException(String parsingError){ + super(parsingError); + this.parsingError = parsingError; + } + + @Override + public String getLocalizedMessage(){ + return parsingError; + } + +} diff --git a/app/src/main/java/com/github/tvbox/osc/subtitle/format/FormatASS.java b/app/src/main/java/com/github/tvbox/osc/subtitle/format/FormatASS.java new file mode 100755 index 0000000000..c956766680 --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/subtitle/format/FormatASS.java @@ -0,0 +1,608 @@ +/** + * Class that represents the .ASS and .SSA subtitle file format + * + *

+ * Copyright (c) 2012 J. David Requejo
+ * j[dot]david[dot]requejo[at] Gmail + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software + * is furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in all copies + * or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE + * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * @author J. David REQUEJO + * + */ + +package com.github.tvbox.osc.subtitle.format; + + +import com.github.tvbox.osc.subtitle.model.Style; +import com.github.tvbox.osc.subtitle.model.Subtitle; +import com.github.tvbox.osc.subtitle.model.Time; +import com.github.tvbox.osc.subtitle.model.TimedTextObject; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Iterator; + +public class FormatASS implements TimedTextFileFormat { + + public TimedTextObject parseFile(String fileName, InputStream is) throws IOException { + + TimedTextObject tto = new TimedTextObject(); + tto.fileName = fileName; + + Subtitle caption = new Subtitle(); + Style style; + + //for the clock timer + float timer = 100; + + //if the file is .SSA or .ASS + boolean isASS = false; + + //variables to store the formats + String[] styleFormat; + String[] dialogueFormat; + + //first lets load the file + InputStreamReader in= new InputStreamReader(is); + BufferedReader br = new BufferedReader(in); + + String line; + int lineCounter = 0; + try { + //we scour the file + line=br.readLine(); + lineCounter++; + while (line!=null){ + line = line.trim(); + //we skip any line until we find a section [section name] + if(line.startsWith("[")){ + //now we must identify the section + if(line.equalsIgnoreCase("[Script info]")){ + //its the script info section section + lineCounter++; + line=br.readLine().trim(); + //Each line is scanned for useful info until a new section is detected + while (!line.startsWith("[")){ + if(line.startsWith("Title:")) { //标题信息非必要 + String[] titleArr = line.split(":"); + //We have found the title + tto.title = titleArr.length > 1 ? titleArr[1].trim() : ""; + } else if (line.startsWith("Original Script:")) { //作者信息非必要 + String[] authorArr = line.split(":"); + //We have found the author + tto.author = authorArr.length > 1 ? authorArr[1].trim() : ""; + } else if (line.startsWith("Script Type:")){ + //we have found the version + if(line.split(":")[1].trim().equalsIgnoreCase("v4.00+"))isASS = true; + //we check the type to set isASS or to warn if it comes from an older version than the studied specs + else if(!line.split(":")[1].trim().equalsIgnoreCase("v4.00")) + tto.warnings+="Script version is older than 4.00, it may produce parsing errors."; + } else if (line.startsWith("Timer:")) + //We have found the timer + timer = Float.parseFloat(line.split(":")[1].trim().replace(',','.')); + //we go to the next line + lineCounter++; + line=br.readLine().trim(); + } + + } else if (line.equalsIgnoreCase("[v4 Styles]") + || line.equalsIgnoreCase("[v4 Styles+]") + || line.equalsIgnoreCase("[v4+ Styles]")){ + //its the Styles description section + if(line.contains("+")&&isASS==false){ + //its ASS and it had not been noted + isASS=true; + tto.warnings+="ScriptType should be set to v4:00+ in the [Script Info] section.\n\n"; + } + lineCounter++; + line=br.readLine(); + //the first line should define the format + if(!line.startsWith("Format:")){ + //if not, we scan for the format. + tto.warnings+="Format: (format definition) expected at line "+line+" for the styles section\n\n"; + while (!line.startsWith("Format:")){ + lineCounter++; + line=br.readLine(); + } + } + // we recover the format's fields + styleFormat = line.split(":")[1].trim().split(","); + lineCounter++; + line=br.readLine(); + // we parse each style until we reach a new section + while (!line.startsWith("Style:")){ + tto.warnings+="Style: (format definition) expected at line "+line+" for the styles section\n\n"; + //next line + lineCounter++; + line=br.readLine(); + } + //we parse the style + style = parseStyleForASS(line.split(":")[1].trim().split(","),styleFormat,lineCounter,isASS,tto.warnings); + //and save the style + tto.styling.put(style.iD, style); + + } else if (line.trim().equalsIgnoreCase("[Events]")){ + //its the events specification section + lineCounter++; + line=br.readLine(); + tto.warnings+="Only dialogue events are considered, all other events are ignored.\n\n"; + //the first line should define the format of the dialogues + if(!line.startsWith("Format:")){ + //if not, we scan for the format. + tto.warnings+="Format: (format definition) expected at line "+line+" for the events section\n\n"; + while (!line.startsWith("Format:")){ + lineCounter++; + line=br.readLine(); + } + } + // we recover the format's fields + dialogueFormat = line.split(":")[1].trim().split(","); + //next line + lineCounter++; + line=br.readLine(); + // we parse each style until we reach a new section + while (!line.startsWith("[")){ + //we check it is a dialogue + //WARNING: all other events are ignored. + if (line.startsWith("Dialogue:")){ + //we parse the dialogue + caption = parseDialogueForASS(line.split(":",2)[1].trim().split(",",10),dialogueFormat,timer, tto); + //and save the caption + int key = caption.start.mseconds; + //in case the key is already there, we increase it by a millisecond, since no duplicates are allowed + while (tto.captions.containsKey(key)) key++; + tto.captions.put(key, caption); + } + //next line + lineCounter++; + line=br.readLine(); + } + + } else if (line.trim().equalsIgnoreCase("[Fonts]") || line.trim().equalsIgnoreCase("[Graphics]")){ + //its the custom fonts or embedded graphics section + //these are not supported + tto.warnings+= "The section "+line.trim()+" is not supported for conversion, all information there will be lost.\n\n"; + } else { + tto.warnings+= "Unrecognized section: "+line.trim()+" all information there is ignored."; + } + } + line = br.readLine(); + lineCounter++; + } + // parsed styles that are not used should be eliminated + tto.cleanUnusedStyles(); + + } catch (NullPointerException e){ + tto.warnings+= "unexpected end of file, maybe last caption is not complete.\n\n"; + } finally{ + //we close the reader + is.close(); + } + + tto.built = true; + return tto; + } + + + public String[] toFile(TimedTextObject tto) { + + //first we check if the TimedTextObject had been built, otherwise... + if(!tto.built) + return null; + + //we will write the lines in an ArrayList + int index = 0; + //the minimum size of the file is the number of captions and styles + lines for sections and formats and the script info, so we'll take some extra space. + ArrayList file = new ArrayList(30+tto.styling.size()+tto.captions.size()); + + //header is placed + file.add(index++,"[Script Info]"); + //title next + String title = "Title: "; + if (tto.title == null || tto.title.isEmpty()) + title += tto.fileName; + else title += tto.title; + file.add(index++,title); + //author next + String author = "Original Script: "; + if (tto.author == null || tto.author.isEmpty()) + author += "Unknown"; + else author += tto.author; + file.add(index++,author); + //additional info + if (tto.copyrigth != null && !tto.copyrigth.isEmpty()) + file.add(index++,"; "+tto.copyrigth); + if (tto.description != null && !tto.description.isEmpty()) + file.add(index++,"; "+tto.description); + file.add(index++,"; Converted by the Online Subtitle Converter developed by J. David Requejo"); + //mandatory info + if (tto.useASSInsteadOfSSA) + file.add(index++,"Script Type: V4.00+"); + else file.add(index++,"Script Type: V4.00"); + file.add(index++,"Collisions: Normal"); + file.add(index++,"Timer: 100,0000"); + if (tto.useASSInsteadOfSSA) + file.add(index++,"WrapStyle: 1"); + //an empty line is added + file.add(index++,""); + + //Styles section + if (tto.useASSInsteadOfSSA) + file.add(index++,"[V4+ Styles]"); + else file.add(index++,"[V4 Styles]"); + //define the format + if (tto.useASSInsteadOfSSA) + file.add(index++,"Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding"); + else file.add(index++,"Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding"); + //Next we iterate over the styles + Iterator - + + diff --git a/app/src/normal/java/com/github/catvod/crawler/pyLoader.java b/app/src/normal/java/com/github/catvod/crawler/pyLoader.java new file mode 100644 index 0000000000..799afe1b24 --- /dev/null +++ b/app/src/normal/java/com/github/catvod/crawler/pyLoader.java @@ -0,0 +1,43 @@ +package com.github.catvod.crawler; + +import android.util.Log; + +import com.github.catvod.crawler.python.IPyLoader; + +import java.util.Map; + +public class pyLoader implements IPyLoader { + + @Override + public void clear() { + Log.i("PyLoader", "java flavor: clear() called, Python is not supported."); + } + + @Override + public void setConfig(String jsonStr) { + Log.i("PyLoader", "java flavor: setConfig() called, Python is not supported."); + } + + @Override + public void setRecentPyKey(String key) { + Log.i("PyLoader", "java flavor: setRecentPyKey() called, Python is not supported."); + } + + @Override + public Spider getSpider(String key, String cls, String ext) { + Log.i("PyLoader", "java flavor: getSpider() called, Python is not supported."); + return new SpiderNull(); + } + + @Override + public Object[] proxyInvoke(Map params) { + Log.i("PyLoader", "java flavor: proxyInvoke(params) called, Python is not supported."); + return null; + } + + @Override + public Object[] proxyInvoke(Map params, String key) { + Log.i("PyLoader", "java flavor: proxyInvoke(params, key) called, Python is not supported."); + return null; + } +} diff --git a/app/src/python/java/com/github/catvod/crawler/pyLoader.java b/app/src/python/java/com/github/catvod/crawler/pyLoader.java new file mode 100644 index 0000000000..25dcb52db0 --- /dev/null +++ b/app/src/python/java/com/github/catvod/crawler/pyLoader.java @@ -0,0 +1,111 @@ +package com.github.catvod.crawler; + +import android.os.Build; +import android.util.Log; + +import com.github.catvod.crawler.python.IPyLoader; +import com.github.tvbox.osc.base.App; +import com.github.tvbox.osc.util.LOG; +import com.undcover.freedom.pyramid.PythonLoader; +import com.undcover.freedom.pyramid.PythonSpider; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class pyLoader implements IPyLoader { + private PythonLoader pythonLoader; + private final ConcurrentHashMap spiders; + private String lastConfig = null; // 记录上次的配置 + + public pyLoader() { + spiders = new ConcurrentHashMap<>(); + } + + @Override + public synchronized void clear() { + spiders.clear(); + if (pythonLoader != null) { + pythonLoader.clear(); + } + lastConfig = null; + recentPyKey = null; + } + + @Override + public void setConfig(String jsonStr) { + if (!isPythonSupported()) { + Log.w("PyLoader", "python32 is disabled on Android 16+ 32-bit process."); + return; + } + if (jsonStr != null && !jsonStr.equals(lastConfig)) { + Log.i("PyLoader", "echo-setConfig 初始化json "); + getPythonLoader().setConfig(jsonStr); + lastConfig = jsonStr; + } + } + + private String recentPyKey; + @Override + public void setRecentPyKey(String key) { + recentPyKey = key; + } + + @Override + public synchronized Spider getSpider(String key, String cls, String ext) { + if (!isPythonSupported()) { + Log.w("PyLoader", "python32 is disabled on Android 16+ 32-bit process."); + return new SpiderNull(); + } + if (spiders.containsKey(key)) { + Log.i("PyLoader", "echo-getSpider spider缓存: " + key); + return spiders.get(key); + } + try { + Log.i("PyLoader", "echo-getSpider url: " + cls); + Spider sp = getPythonLoader().getSpider(key, cls, ext); + if (sp == null) return new SpiderNull(); + if (sp instanceof SpiderNull) return sp; +// Log.i("PyLoader", "echo-getSpider homeContent: " + sp.homeContent(true)); + spiders.put(key, sp); + Log.i("PyLoader", "echo-getSpider 加载spider: " + key); + return sp; + } catch (Throwable th) { + th.printStackTrace(); + } + return new SpiderNull(); + } + + @Override + public Object[] proxyInvoke(Map params){ + return proxyInvoke(params, recentPyKey); + } + + @Override + public Object[] proxyInvoke(Map params, String key){ + if (!isPythonSupported()) return null; + if(key==null || key.isEmpty())return null; + LOG.i("echo-recentPyKey" + key); + try { + Spider spider = spiders.get(key); + if (!(spider instanceof PythonSpider)) return null; + PythonSpider originalSpider = (PythonSpider) spider; + return originalSpider.proxyLocal(params); + } catch (Throwable th) { + LOG.i("echo-proxyInvoke_Throwable:---" + th.getMessage()); + th.printStackTrace(); + } + return null; + } + + private PythonLoader getPythonLoader() { + if (pythonLoader == null) { + pythonLoader = PythonLoader.getInstance().setApplication(App.getInstance()); + } + return pythonLoader; + } + + private boolean isPythonSupported() { + if (Build.VERSION.SDK_INT < 36) return true; + if (Build.VERSION.SDK_INT < 23) return true; + return android.os.Process.is64Bit(); + } +} diff --git a/app/src/python/java/com/undcover/freedom/pyramid/PyLog.java b/app/src/python/java/com/undcover/freedom/pyramid/PyLog.java new file mode 100644 index 0000000000..af81e2912b --- /dev/null +++ b/app/src/python/java/com/undcover/freedom/pyramid/PyLog.java @@ -0,0 +1,446 @@ +package com.undcover.freedom.pyramid; +import android.util.Log; + +/** + * Created by UndCover on 16/12/15. + */ + +public class PyLog { + public final static int LEVEL_V = 5; + public final static int LEVEL_D = 4; + public final static int LEVEL_I = 3; + public final static int LEVEL_W = 2; + public final static int LEVEL_E = 1; + public final static int LEVEL_RELEASE = 0; + private static int logLevel = LEVEL_RELEASE; + private static PyLog mLog; + + public synchronized static PyLog getInstance() { + mLog = new PyLog(); + return mLog; + } + + public PyLog setLogLevel(int logLevel) { + try + { + checkInit(); + } + catch (Exception e){ + return mLog; + } + this.logLevel = logLevel; + return mLog; + } + + /** + * 用于生命周期 + */ + public final static int FILTER_LC = 0x01; + /** + * 用于网络请求 默认为 LEVEL_I + */ + public final static int FILTER_NW = 0x02; + /** + * ActivityManager内置Log + */ + public final static int FILTER_AM = 0x04; + /** + * 用于FrameWork内置log + */ + public final static int FILTER_FW = 0x08; + + private static boolean isLifeCycleEnable = false; + private static boolean isNetWorkEnable = false; + private static boolean isFrameWorkEnable = false; + private static boolean isAtyManagerEnable = false; + + /** + * 设置内设Log的过滤,添加需要输出的Log
+ * FILTER_LC 生命周期
+ * FILTER_NW 网络请求
+ * FILTER_AM AtyManager
+ * FILTER_FW 框架
+ * + * @param filter + * @return + */ + public PyLog setFilter(int filter) { + try + { + checkInit(); + } + catch (Exception e){ + return mLog; + } + isLifeCycleEnable = (filter & FILTER_LC) / FILTER_LC == 1 ? true : false; + isNetWorkEnable = (filter & FILTER_NW) / FILTER_NW == 1 ? true : false; + isFrameWorkEnable = (filter & FILTER_FW) / FILTER_FW == 1 ? true : false; + isAtyManagerEnable = (filter & FILTER_AM) / FILTER_AM == 1 ? true : false; + return mLog; + } + + public static void V(String tag, String msg) { + if (logLevel < LEVEL_V) + return; + Log.v(tag, msg); + } + + public static void D(String tag, String msg) { + if (logLevel < LEVEL_D) + return; + Log.d(tag, msg); + } + + public static void I(String tag, String msg) { + if (logLevel < LEVEL_I) + return; + Log.i(tag, msg); + } + + public static void W(String tag, String msg) { + if (logLevel < LEVEL_W) + return; + Log.w(tag, msg); + } + + public static void E(String tag, String msg) { + if (logLevel < LEVEL_E) + return; + Log.e(tag, msg); + } + + private static int segmentSize = 3 * 1024; + + private static void longV(String tag, String msg) { + if (logLevel < LEVEL_V) + return; + while (msg.length() > segmentSize) {// 循环分段打印日志 + String logContent = msg.substring(0, segmentSize); + msg = msg.replace(logContent, "\t\t"); + Log.v(tag, logContent); + } + Log.v(tag, msg);// 打印剩余日志 + } + + private static void longD(String tag, String msg) { + if (logLevel < LEVEL_D) + return; + while (msg.length() > segmentSize) {// 循环分段打印日志 + String logContent = msg.substring(0, segmentSize); + msg = msg.replace(logContent, "\t\t"); + Log.d(tag, logContent); + } + Log.d(tag, msg);// 打印剩余日志 + } + + private static void longI(String tag, String msg) { + if (logLevel < LEVEL_I) + return; + while (msg.length() > segmentSize) {// 循环分段打印日志 + String logContent = msg.substring(0, segmentSize); + msg = msg.replace(logContent, "\t\t"); + Log.i(tag, logContent); + } + Log.i(tag, msg);// 打印剩余日志 + } + + private static void longW(String tag, String msg) { + if (logLevel < LEVEL_W) + return; + + while (msg.length() > segmentSize) {// 循环分段打印日志 + String logContent = msg.substring(0, segmentSize); + msg = msg.replace(logContent, "\t\t"); + Log.w(tag, logContent); + } + Log.w(tag, msg);// 打印剩余日志 + } + + private static void longE(String tag, String msg) { + if (logLevel < LEVEL_E) + return; + + while (msg.length() > segmentSize) {// 循环分段打印日志 + String logContent = msg.substring(0, segmentSize); + msg = msg.replace(logContent, "\t\t"); + Log.e(tag, logContent); + } + Log.e(tag, msg);// 打印剩余日志 + +// if (tag == null || tag.length() == 0 +// || msg == null || msg.length() == 0) +// return; + +// int segmentSize = 3 * 1024; +// long length = msg.length(); +// if (length <= segmentSize) {// 长度小于等于限制直接打印 +// Log.e(tag, msg); +// } else { +// while (msg.length() > segmentSize) {// 循环分段打印日志 +// String logContent = msg.substring(0, segmentSize); +// msg = msg.replace(logContent, ""); +// Log.e(tag, logContent); +// } +// Log.e(tag, msg);// 打印剩余日志 +// } + } + + /** + * 默认Tag + * + * @param msg + */ + public static void v(String msg) { + v(TagConstant.TAG_DEF, msg); + } + + /** + * 默认Tag + * + * @param msg + */ + public static void d(String msg) { + d(TagConstant.TAG_DEF, msg); + } + + /** + * 默认Tag + * + * @param msg + */ + public static void i(String msg) { + i(TagConstant.TAG_DEF, msg); + } + + /** + * 默认Tag + * + * @param msg + */ + public static void w(String msg) { + w(TagConstant.TAG_DEF, msg); + } + + /** + * 默认Tag + * + * @param msg + */ + public static void e(String msg) { + e(TagConstant.TAG_DEF, msg); + } + + /** + * 添加AppTag + * + * @param tag + * @param msg + */ + public static void v(String tag, String msg) { + String msgStr = tag + " " + msg; + if (msgStr.length() > segmentSize) { + longV(TagConstant.TAG_APP, msgStr); + } else { + V(TagConstant.TAG_APP, msgStr); + } + } + + /** + * 添加AppTag + * + * @param tag + * @param msg + */ + public static void d(String tag, String msg) { + String msgStr = tag + " " + msg; + if (msgStr.length() > segmentSize) { + longD(TagConstant.TAG_APP, msgStr); + } else { + D(TagConstant.TAG_APP, msgStr); + } + } + + /** + * 添加AppTag + * + * @param tag + * @param msg + */ + public static void i(String tag, String msg) { + String msgStr = tag + " " + msg; + if (msgStr.length() > segmentSize) { + longI(TagConstant.TAG_APP, msgStr); + } else { + I(TagConstant.TAG_APP, msgStr); + } + } + + /** + * 添加AppTag + * + * @param tag + * @param msg + */ + public static void w(String tag, String msg) { + String msgStr = tag + " " + msg; + if (msgStr.length() > segmentSize) { + longW(TagConstant.TAG_APP, msgStr); + } else { + W(TagConstant.TAG_APP, msgStr); + } + } + + /** + * 添加AppTag + * + * @param tag + * @param msg + */ + public static void e(String tag, String msg) { + String msgStr = tag + " " + msg; + if (msgStr.length() > segmentSize) { + longE(TagConstant.TAG_APP, msgStr); + } else { + E(TagConstant.TAG_APP, msgStr); + } + } + + /** + * 多参数,使用默认Tag + * + * @param args + */ + public static void v(String... args) { + String msg = getArgsStr(args); + v(TagConstant.TAG_DEF, msg); + } + + /** + * 多参数,使用默认Tag + * + * @param args + */ + public static void d(String... args) { + String msg = getArgsStr(args); + d(msg); + } + + /** + * 多参数,使用默认Tag + * + * @param args + */ + public static void i(String... args) { + String msg = getArgsStr(args); + i(msg); + } + + /** + * 多参数,使用默认Tag + * + * @param args + */ + public static void w(String... args) { + String msg = getArgsStr(args); + w(msg); + } + + /** + * 多参数,使用默认Tag + * + * @param args + */ + public static void e(String... args) { + String msg = getArgsStr(args); + e(msg); + } + + /** + * 打印生命周期 + * + * @param tag + * @param msg + */ + public static void lc(String tag, String msg) { + if (isLifeCycleEnable) { + d(tag, TagConstant.TAG_LC, msg); + } + } + + /** + * 打印ActivityManager管理 + * + * @param tag + * @param msg + */ + public static void am(String tag, String msg) { + if (isAtyManagerEnable) { + d(tag, TagConstant.TAG_AM, msg); + } + } + + /** + * 打印框架信息 + * + * @param tag + * @param msg + */ + public static void fw(String tag, String msg) { + if (isFrameWorkEnable) { + d(tag, TagConstant.TAG_FW, msg); + } + } + + /** + * 打印网络请求 + * + * @param tag + * @param msg + */ + public static void nw(String tag, String msg) { + if (isNetWorkEnable) { + nw(tag, msg, false); + } + } + + public static void nw(String tag, String msg, boolean isError) { + if (isNetWorkEnable) { + if (isError) { + e(tag, TagConstant.TAG_NW, msg); + } else { + i(tag, TagConstant.TAG_NW, msg); + } + } + } + + public static String getStackTraceString(Throwable tr) { + return Log.getStackTraceString(tr); + } + + private static String getArgsStr(String... args) { + String ret = ""; + if (args != null && args.length > 0) { + for (String str : args) { + ret += str + " "; + } + } + return ret; + } + + private static void checkInit() throws Exception { + if (mLog == null) { + throw new Exception("SDK未初始化"); + } + } + + public static class TagConstant { + public static String TAG_APP = "SmartSdk"; + public static String TAG_LC = "-----LifeCycle-----"; + public static String TAG_AM = "-----AtyManager-----"; + public static String TAG_NW = "-----NetWork-----"; + public static String TAG_FW = "-----FrameWork-----"; + public static String TAG_DEF = ""; + public static String TAG_REQ = "Request\n"; + public static String TAG_RSP = "Response\n"; + } +} \ No newline at end of file diff --git a/app/src/python/java/com/undcover/freedom/pyramid/PyToast.java b/app/src/python/java/com/undcover/freedom/pyramid/PyToast.java new file mode 100644 index 0000000000..950a6a3d85 --- /dev/null +++ b/app/src/python/java/com/undcover/freedom/pyramid/PyToast.java @@ -0,0 +1,58 @@ +package com.undcover.freedom.pyramid; + +import android.content.Context; +import android.widget.Toast; + + +/** + * Created by UndCover on 16/9/7. + */ +public class PyToast { + private static Toast innerToast; + private static Context mContext; + private static PyToast sInstance; + + + public static void init(Context context) { + mContext = context; + } + + /** + * 在Application中 用于初始化 + * + * @return + */ + public static PyToast getInstance() { + if (sInstance == null) { + synchronized (PyToast.class) { + if (sInstance == null) { + sInstance = new PyToast(); + } + } + } + return sInstance; + } + + public static void showCancelableToast(String msg) { + showCancelableToast(msg, Toast.LENGTH_SHORT); + } + + /** + * 快速显示Toast,无需排队等待 + * + * @param msg + * @param duration + */ + public static void showCancelableToast(String msg, int duration) { + if (innerToast != null) { + innerToast.cancel(); + } + + innerToast = Toast.makeText(mContext, msg, duration); + innerToast.show(); + } + + public static void showMessage(String msg, int duration) { + Toast.makeText(mContext, msg, duration).show(); + } +} \ No newline at end of file diff --git a/app/src/python/java/com/undcover/freedom/pyramid/PythonLoader.java b/app/src/python/java/com/undcover/freedom/pyramid/PythonLoader.java new file mode 100644 index 0000000000..703ad91383 --- /dev/null +++ b/app/src/python/java/com/undcover/freedom/pyramid/PythonLoader.java @@ -0,0 +1,288 @@ +package com.undcover.freedom.pyramid; + +import android.app.Application; +import android.content.Context; +import android.util.Base64; + +import com.chaquo.python.PyObject; +import com.chaquo.python.Python; +import com.chaquo.python.android.AndroidPlatform; +import com.github.catvod.Proxy; +import com.github.catvod.crawler.Spider; +import com.github.catvod.crawler.SpiderNull; + +import com.github.catvod.net.OkHttp; +import com.github.tvbox.osc.util.OkGoHelper; + + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import okhttp3.HttpUrl; +import okhttp3.Request; +import okhttp3.Response; + +public class PythonLoader { + private final ConcurrentHashMap spiders = new ConcurrentHashMap<>(); + private static PythonLoader sInstance; + private Application app; + private final HashMap siteMap; + Python pyInstance; + PyObject pyApp; + Python.Platform androidPlatform; + + public PythonLoader() { + siteMap = new HashMap<>(); + } + + public void clear() { + for (Spider spider : spiders.values()) { + spider.destroy(); + } + spiders.clear(); + siteMap.clear(); + } + + public static PythonLoader getInstance() { + if (sInstance == null) { + synchronized (PyToast.class) { + if (sInstance == null) { + sInstance = new PythonLoader(); + } + } + } + return sInstance; + } + + private void setSdk(Context context) { + int logLevel = PyLog.LEVEL_V; + PyLog.getInstance().setLogLevel(logLevel).setFilter(PyLog.FILTER_NW | PyLog.FILTER_LC); + PyLog.TagConstant.TAG_APP = "PythonLoader"; + + PyToast.init(context); + } + + public void setConfig(String config) { + try { + siteMap.clear(); + JSONObject configJo = new JSONObject(config); + JSONArray siteList = configJo.getJSONArray("sites"); + for (int i = 0; i < siteList.length(); i++) { + JSONObject jo = siteList.getJSONObject(i); + String key = jo.optString("api"); + siteMap.put(key, jo); + } + } catch (JSONException e) { + e.printStackTrace(); + } + } + + public PythonLoader setApplication(Application app) { + this.app = app; + setSdk(this.app); + if (pyInstance == null) { + try { + if (!Python.isStarted()) { + androidPlatform = new AndroidPlatform(app); + Python.start(androidPlatform); + } + pyInstance = Python.getInstance(); + pyApp = pyInstance.getModule("app"); + } catch (Throwable th) { + throw new RuntimeException(th); + } + } + File pyCache = new File(app.getCacheDir(), "py"); + if (!pyCache.exists()) pyCache.mkdirs(); + setPluginConfig(pyCache.getAbsolutePath()); + return this; + } + + String cache = ""; + + public PythonLoader setPluginConfig(String config) { + if (config == null || config.isEmpty()) { + this.cache = ""; + } else if (config.endsWith(File.separator)) { + this.cache = config; + } else { + this.cache = config + File.separator; + } + return this; + } + + String getCachePath() { + return cache; + } + + public String getUrlByApi(String api) { + String key = ""; + String url = ""; + if (siteMap.containsKey(api)) { + JSONObject jo = siteMap.get(api); + key = jo.optString("key"); + url = jo.optString("ext"); + } + if (!key.isEmpty() && !url.isEmpty()) { + if (spiders.containsKey(key)) { + return ""; + } else { + return url; + } + } + return ""; + } + + public Spider getSpider(String key, String url) throws Exception { + return getSpider(key, url, ""); + } + + public Spider getSpider(String key, String url, String ext) throws Exception { + if (app == null) throw new Exception("set application first"); + if (spiders.containsKey(key)) { + PyLog.d(key + " :缓存加载成功!"); + return spiders.get(key); + } + + // 使用ExecutorService来管理线程 + ExecutorService executor = Executors.newSingleThreadExecutor(); + Future future = null; + try { + PythonSpider sp = new PythonSpider(key, cache); + + // 提交初始化任务 + future = executor.submit(() -> { + try { + sp.init(app, url, ext); + } catch (Exception e) { + e.printStackTrace(); + } + }); + + // 等待线程完成,最多30秒 + future.get(30, TimeUnit.SECONDS); + + // 任务成功,缓存并返回 + if (!sp.isLoadSuccess()) return new SpiderNull(); + spiders.put(key, sp); + return sp; + } catch (TimeoutException e) { + PyLog.e("echo-init方法执行超时"); + // 超时了,不做中断,返回空的Spider + } catch (ExecutionException | InterruptedException e) { + PyLog.e("echo-init:ExecutionException|InterruptedException"); + } finally { + // 关闭线程池 + if (future != null && !future.isDone()) { + future.cancel(true); // 取消任务 + } + executor.shutdown(); // 关闭线程池 + } + return new SpiderNull(); + } + + public String localProxyUrl() { + return Proxy.getUrl(true); + } + + public Map str2map(String header) { + Map map = new HashMap<>(); + if (header == null || header.isEmpty()) + return map; + try { + JSONObject jo = new JSONObject(header); + for (Iterator it = jo.keys(); it.hasNext(); ) { + String key = it.next(); + String value = jo.optString(key); + map.put(key, value); + } + } catch (JSONException e) { + e.printStackTrace(); + } + return map; + } + + public InputStream getFileStream(String url, String param, String header) { + if (streamCallback != null) { + return streamCallback.get(url, str2map(param), str2map(header)); + } else { + try { + okhttp3.OkHttpClient client = OkGoHelper.getDefaultClient(); + if (client == null) client = OkHttp.client(); + Response response = client.newCall(getRequest(url, str2map(param), str2map(header))).execute(); + if (response.body() != null) return response.body().byteStream(); + response.close(); + return new ByteArrayInputStream(new byte[0]); + } catch (Exception e) { + return new ByteArrayInputStream(new byte[0]); + } + } + } + + public String getFileString(String url, String header) { + if (stringCallback != null) { + return stringCallback.get(url, str2map(header)); + } else { + return OkHttp.string(url, str2map(header)); + } + } + + private Request getRequest(String url, Map paramsMap, Map headerMap) { + HttpUrl httpUrl = HttpUrl.parse(url); + if (httpUrl != null && paramsMap != null && !paramsMap.isEmpty()) { + HttpUrl.Builder builder = httpUrl.newBuilder(); + for (Map.Entry entry : paramsMap.entrySet()) { + builder.addQueryParameter(entry.getKey(), entry.getValue()); + } + httpUrl = builder.build(); + } + Request.Builder builder = new Request.Builder(); + if (httpUrl != null) { + builder.url(httpUrl); + } else { + builder.url(url); + } + if (headerMap != null) { + for (Map.Entry entry : headerMap.entrySet()) { + builder.addHeader(entry.getKey(), entry.getValue()); + } + } + return builder.build(); + } + + FileStreamCallback streamCallback; + FileStringCallback stringCallback; + + public PythonLoader setFileStreamCallback(FileStreamCallback callback) { + streamCallback = callback; + return this; + } + + public PythonLoader setFileStringCallback(FileStringCallback callback) { + stringCallback = callback; + return this; + } + + public interface FileStreamCallback { + InputStream get(String url, Map paramsMap, Map headerMap); + } + + public interface FileStringCallback { + String get(String url, Map headerMap); + } +} diff --git a/app/src/python/java/com/undcover/freedom/pyramid/PythonSpider.java b/app/src/python/java/com/undcover/freedom/pyramid/PythonSpider.java new file mode 100644 index 0000000000..4f09024724 --- /dev/null +++ b/app/src/python/java/com/undcover/freedom/pyramid/PythonSpider.java @@ -0,0 +1,342 @@ +package com.undcover.freedom.pyramid; + +import android.content.Context; +import android.net.Uri; +import android.util.Base64; +import android.util.Log; + +import com.chaquo.python.PyObject; +import com.github.catvod.crawler.Spider; +import com.google.android.exoplayer2.util.UriUtil; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public class PythonSpider extends Spider { + PyObject app; + PyObject pySpider; + boolean loadSuccess = false; + private String cachePath; + private String name; + + public PythonSpider() { + this(PythonLoader.getInstance().getCachePath()); + } + + public PythonSpider(String cache) { + this("", cache); + } + + public PythonSpider(String name, String cache) { + this.cachePath = cache; + this.name = name; + } + + @Override + public void init(Context context) { + app.callAttr("init", pySpider); + } + + public void init(Context context, String url) { + init(context, url, ""); + } + + public void init(Context context, String url, String extend) { + app = PythonLoader.getInstance().pyApp; + PyObject retValue = app.callAttr("downloadPlugin", cachePath, url); + Uri uri = Uri.parse(url); + String extInfo = extend; + if (extInfo == null || extInfo.length() == 0) extInfo = uri.getQueryParameter("extend"); + if (null == extInfo) extInfo = ""; + String path = retValue.toString(); + Log.i("PyLoader", "echo-init path: " +path); + File file = new File(path); + if (file.exists()) { + pySpider = app.callAttr("loadFromDisk", path); + try { + pySpider.put("siteKey", name); + } catch (Exception ignored) { + } + + List poList = app.callAttr("getDependence", pySpider).asList(); + for (PyObject po : poList) { + String api = po.toString(); + Log.i("PyLoader", "echo-init api: " +api); + String depUrl = PythonLoader.getInstance().getUrlByApi(api); + if (depUrl.isEmpty()) depUrl = resolveDependenceUrl(url, api); + if (!depUrl.isEmpty()) { + Log.i("PyLoader", "echo-init depUrl: " +depUrl); + String tmpPath = app.callAttr("downloadPlugin", cachePath, depUrl).toString(); + if (!new File(tmpPath).exists()) { + PyToast.showCancelableToast(api + "加载失败!"); + return; + } else { + app.callAttr("registerPluginAlias", api, tmpPath); + PyLog.d(api + ": 加载插件依赖成功!"); + } + } + } + app.callAttr("init", pySpider, extInfo); + loadSuccess = true; + PyLog.d(name + ": 下載插件成功!"); + } else { + PyToast.showCancelableToast(name + "下载插件失败"); + } + } + + private String resolveDependenceUrl(String baseUrl, String api) { + if (api == null || api.isEmpty()) return ""; + String dep = api.endsWith(".py") ? api : api + ".py"; + return UriUtil.resolve(baseUrl, dep); + } + + public String getName() { + if (name.isEmpty()) { + PyObject po = app.callAttr("getName", pySpider); + return po.toString(); + } else { + return name; + } + } + + public boolean isLoadSuccess() { + return loadSuccess && pySpider != null; + } + + public JSONObject map2json(HashMap extend) { + JSONObject jo = new JSONObject(); + try { + if (extend != null) { + for (String key : extend.keySet()) { + jo.put(key, extend.get(key)); + } + } + } catch (JSONException e) { + e.printStackTrace(); + } + return jo; + } + + public JSONObject map2json(Map extend) { + JSONObject jo = new JSONObject(); + try { + if (extend != null) { + for (Object key : extend.keySet()) { + jo.put(key.toString(), extend.get(key)); + } + } + } catch (JSONException e) { + e.printStackTrace(); + } + return jo; + } + + public JSONArray list2json(List array) { + JSONArray ja = new JSONArray(); + if (array != null) { + for (String str : array) { + ja.put(str); + } + } + return ja; + } + + public String paramLog(Object... obj) { + StringBuilder sb = new StringBuilder(); + sb.append("request params:["); + for (Object o : obj) { + sb.append(o).append("-"); + } + sb.append("]"); + return sb.toString(); + } + + public Object[] proxyLocal(Map params) { +// Log.i("PyLoader","echo-proxyLocal:param"+params.toString()); + PyObject proxyResult = app.callAttr("localProxy", pySpider, map2json(params).toString()); + if (proxyResult == null) return null; + List list = proxyResult.asList(); + if (list == null || list.size() < 3) return null; + boolean base64 = list.size() > 4 && list.get(4).toInt() == 1; + boolean headerAvailable = list.size() > 3 && list.get(3) != null; + Object[] result = new Object[4]; + result[0] = list.get(0).toInt(); + result[1] = list.get(1).toString(); + result[2] = getStream(list.get(2), base64); + result[3] = headerAvailable ? getHeader(list.get(3)) : null; +// result[3] = null; + return result; + } + + + private Map getHeader(PyObject headerObj) { + if (headerObj == null) { + return null; + } + // 处理 headerObj + Map headerMap = new HashMap<>(); + for (PyObject key : headerObj.asMap().keySet()) { + headerMap.put(key.toString(), Objects.requireNonNull(headerObj.asMap().get(key)).toString()); + } + return headerMap; + } + + private ByteArrayInputStream getStream(PyObject o, boolean base64) { + if (o == null) return new ByteArrayInputStream(new byte[0]); + String typeStr = o.type().toString(); + if (typeStr.contains("bytes")) return new ByteArrayInputStream(o.toJava(byte[].class)); + String content = o.toString(); + if (base64 && content.contains("base64,")) { + content = content.split("base64,")[1]; + } + return new ByteArrayInputStream(base64 ? decode(content) : content.getBytes()); + } + + public String replaceLocalUrl(String content) { + return content.replace("http://127.0.0.1:UndCover/proxy", PythonLoader.getInstance().localProxyUrl()); + } + + /** + * 首页数据内容 + * + * @param filter 是否开启筛选 + * @return + */ + public String homeContent(boolean filter) { + if (pySpider == null) return "{}"; + PyLog.nw("homeContent" + "-" + name, paramLog(filter)); + PyObject po = app.callAttr("homeContent", pySpider, filter); + String rsp = po.toString(); + PyLog.nw("homeContent" + "-" + name, rsp); + return rsp; + } + + /** + * 首页最近更新数据 如果上面的homeContent中不包含首页最近更新视频的数据 可以使用这个接口返回 + * + * @return + */ + public String homeVideoContent() { + PyLog.nw("homeVideoContent" + "-" + name, ""); + PyObject po = app.callAttr("homeVideoContent", pySpider); + String rsp = po.toString(); + PyLog.nw("homeVideoContent" + "-" + name, rsp); + return rsp; + } + + /** + * 分类数据 + * + * @param tid + * @param pg + * @param filter + * @param extend + * @return + */ + public String categoryContent(String tid, String pg, boolean filter, HashMap extend) { + PyLog.nw("categoryContent" + "-" + name, paramLog(tid, pg, filter, map2json(extend).toString())); + PyObject po = app.callAttr("categoryContent", pySpider, tid, pg, filter, map2json(extend).toString()); + String rsp = po.toString(); + PyLog.nw("categoryContent" + "-" + name, rsp); + return rsp; + } + + /** + * 详情数据 + * + * @param ids + * @return + */ + public String detailContent(List ids) { + PyLog.nw("detailContent" + "-" + name, paramLog(list2json(ids).toString())); + PyObject po = app.callAttr("detailContent", pySpider, list2json(ids).toString()); + String rsp = po.toString(); + PyLog.nw("detailContent" + "-" + name, rsp); + return rsp; + } + + /** + * 搜索数据内容 + * + * @param key + * @param quick + * @return + */ + public String searchContent(String key, boolean quick) { + PyLog.nw("searchContent" + "-" + name, paramLog(key, quick)); + PyObject po = app.callAttr("searchContent", pySpider, key, quick); + String rsp = po.toString(); + PyLog.nw("searchContent" + "-" + name, rsp); + return rsp; + } + + /** + * 播放信息 + * + * @param flag + * @param id + * @return + */ + public String playerContent(String flag, String id, List vipFlags) { + PyLog.nw("playerContent" + "-" + name, paramLog(flag, id, list2json(vipFlags).toString())); + PyObject po = app.callAttr("playerContent", pySpider, flag, id, list2json(vipFlags).toString()); + String rsp = replaceLocalUrl(po.toString()); + PyLog.nw("playerContent" + "-" + name, rsp); + return rsp; + } + + /** + * 直播列表数据 + * @return + */ + public String liveContent(String url) { + PyLog.nw("liveContent" + "-" + name, ""); + PyObject po = app.callAttr("liveContent", pySpider,url); + String rsp = po.toString(); + PyLog.nw("liveContent" + "-" + name, rsp); + return rsp; + } + + /** + * webview解析时使用 可自定义判断当前加载的 url 是否是视频 + * + * @param url + * @return + */ + public boolean isVideoFormat(String url) { + return false; + } + + /** + * 是否手动检测webview中加载的url + * + * @return + */ + public boolean manualVideoCheck() { + return false; + } + + @Override + public void destroy() { + try { + if (app != null && pySpider != null) app.callAttr("destroy", pySpider); + } catch (Exception ignored) { + } + } + + public static byte[] decode(String s) { + return decode(s, Base64.DEFAULT | Base64.NO_WRAP); + } + + public static byte[] decode(String s, int flags) { + return Base64.decode(s, flags); + } +} diff --git a/build.gradle b/build.gradle index b3e1572c12..4c6a903513 100644 --- a/build.gradle +++ b/build.gradle @@ -5,26 +5,39 @@ buildscript { google() mavenCentral() maven { url "https://jitpack.io" } - maven { url 'https://download.01.org/crosswalk/releases/crosswalk/android/maven2' } + maven { url "https://maven.aliyun.com/repository/public" } + maven { url "https://maven.aliyun.com/repository/releases" } + maven { url "https://storage.googleapis.com/r8-releases/raw" } + maven { url "https://chaquo.com/maven" } + maven { + url "http://4thline.org/m2" + allowInsecureProtocol = true + } } dependencies { - classpath "com.android.tools.build:gradle:4.2.1" + classpath 'com.android.tools.build:gradle:7.2.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files + classpath "com.chaquo.python:gradle:12.0.1" } } allprojects { repositories { - gradlePluginPortal() google() mavenCentral() maven { url "https://jitpack.io" } - maven { url 'https://download.01.org/crosswalk/releases/crosswalk/android/maven2' } + maven { url "https://maven.aliyun.com/repository/public" } + maven { url "https://maven.aliyun.com/repository/releases" } + gradlePluginPortal() + maven { + url "http://4thline.org/m2" + allowInsecureProtocol = true + } } } task clean(type: Delete) { delete rootProject.buildDir -} \ No newline at end of file +} diff --git a/gradle.properties b/gradle.properties index 19dc3e601b..23a72b3804 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,6 @@ # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx2048m # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects @@ -17,4 +16,7 @@ org.gradle.jvmargs=-Xmx2048m android.useAndroidX=true # Automatically convert third-party libraries to use AndroidX android.enableJetifier=true -IsDebug=true \ No newline at end of file +#android.injected.build.abi=armeabi-v7a +IsDebug=true +#build on off +org.gradle.jvmargs=-Xmx2048m diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 070478bcbc..a295be6964 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Sep 21 09:53:05 CST 2021 +#Fri Jun 19 23:54:31 CST 2026 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/player/build.gradle b/player/build.gradle index 002af46ea0..23611d5d1f 100644 --- a/player/build.gradle +++ b/player/build.gradle @@ -3,28 +3,33 @@ plugins { } android { - compileSdkVersion 30 + compileSdkVersion 33 defaultConfig { - minSdkVersion 16 - targetSdkVersion 26 - versionCode 1 - versionName "1.0" + minSdkVersion 19 + targetSdkVersion 28 } buildTypes { - all { + debug { ndk { - abiFilters 'armeabi-v7a' + abiFilters 'armeabi-v7a', 'arm64-v8a' } } release { minifyEnabled false + ndk { + //noinspection ChromeOsAbiSupport + abiFilters 'armeabi-v7a', 'arm64-v8a' + } proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } releaseSimon.initWith(release) releaseSimon { + ndk { + abiFilters 'armeabi-v7a' + } } } compileOptions { @@ -35,11 +40,13 @@ android { dependencies { implementation "androidx.annotation:annotation:1.3.0" - api "com.google.android.exoplayer:exoplayer-core:2.14.2" - api "com.google.android.exoplayer:exoplayer-dash:2.14.2" - api "com.google.android.exoplayer:exoplayer-hls:2.14.2" - api "com.google.android.exoplayer:exoplayer-rtsp:2.14.2" - api "com.google.android.exoplayer:extension-rtmp:2.14.2" - api "com.google.android.exoplayer:extension-okhttp:2.14.2" + implementation "com.squareup.okhttp3:okhttp:3.12.11" + api "com.google.android.exoplayer:exoplayer-core:2.19.1" + api "com.google.android.exoplayer:exoplayer-ui:2.19.1" + api "com.google.android.exoplayer:exoplayer-dash:2.19.1" + api "com.google.android.exoplayer:exoplayer-hls:2.19.1" + api "com.google.android.exoplayer:exoplayer-rtsp:2.19.1" + api "com.google.android.exoplayer:exoplayer-datasource:2.19.1" + api "com.google.android.exoplayer:extension-rtmp:2.19.1" api 'xyz.doikki.android.dkplayer:dkplayer-ui:3.3.5' -} \ No newline at end of file +} diff --git a/player/src/main/java/tv/danmaku/ijk/media/player/AndroidMediaPlayer.java b/player/src/main/java/tv/danmaku/ijk/media/player/AndroidMediaPlayer.java index a99cfb41a7..ebd864a914 100644 --- a/player/src/main/java/tv/danmaku/ijk/media/player/AndroidMediaPlayer.java +++ b/player/src/main/java/tv/danmaku/ijk/media/player/AndroidMediaPlayer.java @@ -223,7 +223,15 @@ public boolean isPlaying() { @Override public void seekTo(long msec) throws IllegalStateException { - mInternalMediaPlayer.seekTo((int) msec); + int seekMs; + if (msec < 0) { + seekMs = 0; + } else if (msec > Integer.MAX_VALUE) { + seekMs = Integer.MAX_VALUE; + } else { + seekMs = (int) msec; + } + mInternalMediaPlayer.seekTo(seekMs); } @Override diff --git a/player/src/main/java/tv/danmaku/ijk/media/player/IjkMediaPlayer.java b/player/src/main/java/tv/danmaku/ijk/media/player/IjkMediaPlayer.java index 94feba2310..38f054184d 100644 --- a/player/src/main/java/tv/danmaku/ijk/media/player/IjkMediaPlayer.java +++ b/player/src/main/java/tv/danmaku/ijk/media/player/IjkMediaPlayer.java @@ -187,8 +187,21 @@ public static void loadLibrariesOnce(IjkLibLoader libLoader) { if (!mIsLibLoaded) { if (libLoader == null) libLoader = sLocalLibLoader; + try { + //ff4 + libLoader.loadLibrary("ijkffmpeg"); + libLoader.loadLibrary("ijksdl"); + libLoader.loadLibrary("player"); + + //ff5 +// libLoader.loadLibrary("ffmpeg"); +// libLoader.loadLibrary("exoffmpeg"); +// libLoader.loadLibrary("ijksdl"); +// libLoader.loadLibrary("ijkplayer"); + } catch (Throwable ignored) { + + } - libLoader.loadLibrary("player"); mIsLibLoaded = true; } } @@ -424,7 +437,7 @@ public void setDataSource(String path, Map headers) sb.append(entry.getValue()); sb.append("\r\n"); setOption(OPT_CATEGORY_FORMAT, "headers", sb.toString()); - setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "protocol_whitelist", "async,cache,crypto,file,http,https,ijkhttphook,ijkinject,ijklivehook,ijklongurl,ijksegment,ijktcphook,pipe,rtp,tcp,tls,udp,ijkurlhook,data"); + setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "protocol_whitelist", "async,cache,crypto,file,dash,http,https,ijkhttphook,ijkinject,ijklivehook,ijklongurl,ijksegment,ijktcphook,pipe,rtp,tcp,tls,udp,ijkurlhook,data"); } } setDataSource(path); @@ -1036,8 +1049,26 @@ public void handleMessage(Message msg) { if (msg.obj == null) { player.notifyOnTimedText(null); } else { - IjkTimedText text = new IjkTimedText(new Rect(0, 0, 1, 1), (String) msg.obj); - player.notifyOnTimedText(text); + String msg_text=(String) msg.obj; + if(msg_text.contains("{\\fad")){ + player.notifyOnTimedText(null); + return; + } + if (msg.arg1 == 0) {// normal + IjkTimedText text = new IjkTimedText(new Rect(0, 0, 1, 1), msg_text); + player.notifyOnTimedText(text); + } else if (msg.arg1 == 1) { // ass + IjkTimedText text = new IjkTimedText(new Rect(0, 0, 1, 1), msg_text); + player.notifyOnTimedText(text); + } else if (msg.arg1 == 2) { // bitmap + IjkTimedText text; + if (msg.arg2 > 0 && ((int[]) msg.obj).length == msg.arg2) { + text = new IjkTimedText((int[]) msg.obj); + } else { + text = new IjkTimedText(null, ""); + } + player.notifyOnTimedText(text); + } } return; case MEDIA_NOP: // interface test message - ignore diff --git a/player/src/main/java/tv/danmaku/ijk/media/player/IjkTimedText.java b/player/src/main/java/tv/danmaku/ijk/media/player/IjkTimedText.java index 0d11ae4bca..2cd550bf2d 100644 --- a/player/src/main/java/tv/danmaku/ijk/media/player/IjkTimedText.java +++ b/player/src/main/java/tv/danmaku/ijk/media/player/IjkTimedText.java @@ -17,22 +17,43 @@ package tv.danmaku.ijk.media.player; import android.graphics.Rect; + import java.lang.String; public final class IjkTimedText { private Rect mTextBounds = null; private String mTextChars = null; + /** + * int planeWidth = bitmapData[0]; + * int planeHeight = bitmapData[1]; + * int bitmapX = bitmapData[2]; + * int bitmapY = bitmapData[3]; + * int bitmapWidth = bitmapData[4]; + * int bitmapHeight = bitmapData[5]; + * Bitmap bitmap = Bitmap.createBitmap(bitmapData, 6, bitmapWidth, bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888); + */ + private int[] mBitmapData = null; public IjkTimedText(Rect bounds, String text) { mTextBounds = bounds; mTextChars = text; } + + public IjkTimedText(int[] bitmap) { + this.mBitmapData = bitmap; + } + public Rect getBounds() { return mTextBounds; } + + public int[] getBitmapData() { + return mBitmapData; + } + public String getText() { return mTextChars; } diff --git a/player/src/main/java/xyz/doikki/videoplayer/controller/BaseVideoController.java b/player/src/main/java/xyz/doikki/videoplayer/controller/BaseVideoController.java index b3970c4a9e..1d41860a5f 100644 --- a/player/src/main/java/xyz/doikki/videoplayer/controller/BaseVideoController.java +++ b/player/src/main/java/xyz/doikki/videoplayer/controller/BaseVideoController.java @@ -324,8 +324,8 @@ public void run() { }; private int setProgress() { - int position = (int) mControlWrapper.getCurrentPosition(); - int duration = (int) mControlWrapper.getDuration(); + int position = PlayerUtils.safeTimeMs(mControlWrapper.getCurrentPosition()); + int duration = PlayerUtils.safeTimeMs(mControlWrapper.getDuration()); handleSetProgress(duration, position); return position; } @@ -616,7 +616,7 @@ protected void onPlayerStateChanged(int playerState) { mOrientationHelper.disable(); } if (hasCutout()) { - CutoutUtil.adaptCutoutAboveAndroidP(getContext(), false); + CutoutUtil.adaptCutoutAboveAndroidP(getContext(), true); } break; case VideoView.PLAYER_FULL_SCREEN: diff --git a/player/src/main/java/xyz/doikki/videoplayer/controller/ControlWrapper.java b/player/src/main/java/xyz/doikki/videoplayer/controller/ControlWrapper.java index e2b88c30d6..3b3ca692c2 100644 --- a/player/src/main/java/xyz/doikki/videoplayer/controller/ControlWrapper.java +++ b/player/src/main/java/xyz/doikki/videoplayer/controller/ControlWrapper.java @@ -11,15 +11,15 @@ * 并对部分api做了封装,方便使用 */ public class ControlWrapper implements MediaPlayerControl, IVideoController { - + private MediaPlayerControl mPlayerControl; private IVideoController mController; - + public ControlWrapper(@NonNull MediaPlayerControl playerControl, @NonNull IVideoController controller) { mPlayerControl = playerControl; mController = controller; } - + @Override public void start() { mPlayerControl.start(); @@ -189,7 +189,7 @@ public void toggleFullScreenByVideoSize(Activity activity) { if (isFullScreen()) { stopFullScreen(); if (width > height) { - activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); } } else { startFullScreen(); diff --git a/player/src/main/java/xyz/doikki/videoplayer/controller/GestureVideoController.java b/player/src/main/java/xyz/doikki/videoplayer/controller/GestureVideoController.java index f47f182731..ed172aaa6a 100644 --- a/player/src/main/java/xyz/doikki/videoplayer/controller/GestureVideoController.java +++ b/player/src/main/java/xyz/doikki/videoplayer/controller/GestureVideoController.java @@ -225,8 +225,8 @@ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float d protected void slideToChangePosition(float deltaX) { deltaX = -deltaX; int width = getMeasuredWidth(); - int duration = (int) mControlWrapper.getDuration(); - int currentPosition = (int) mControlWrapper.getCurrentPosition(); + int duration = PlayerUtils.safeTimeMs(mControlWrapper.getDuration()); + int currentPosition = PlayerUtils.safeTimeMs(mControlWrapper.getCurrentPosition()); int position = (int) (deltaX / width * 120000 + currentPosition); if (position > duration) position = duration; if (position < 0) position = 0; diff --git a/player/src/main/java/xyz/doikki/videoplayer/exo/ExoMediaPlayer.java b/player/src/main/java/xyz/doikki/videoplayer/exo/ExoMediaPlayer.java index f40a68812a..cd8275dbfe 100644 --- a/player/src/main/java/xyz/doikki/videoplayer/exo/ExoMediaPlayer.java +++ b/player/src/main/java/xyz/doikki/videoplayer/exo/ExoMediaPlayer.java @@ -2,18 +2,21 @@ import android.content.Context; import android.content.res.AssetFileDescriptor; +import android.util.Log; import android.view.Surface; import android.view.SurfaceHolder; +import androidx.annotation.NonNull; + import com.google.android.exoplayer2.DefaultLoadControl; import com.google.android.exoplayer2.DefaultRenderersFactory; -import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.LoadControl; +import com.google.android.exoplayer2.PlaybackException; import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.RenderersFactory; import com.google.android.exoplayer2.SimpleExoPlayer; -import com.google.android.exoplayer2.analytics.AnalyticsCollector; +import com.google.android.exoplayer2.analytics.DefaultAnalyticsCollector; import com.google.android.exoplayer2.source.DefaultMediaSourceFactory; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; @@ -24,11 +27,12 @@ import com.google.android.exoplayer2.util.EventLogger; import com.google.android.exoplayer2.video.VideoSize; +import java.util.HashMap; import java.util.Map; import xyz.doikki.videoplayer.player.AbstractPlayer; import xyz.doikki.videoplayer.player.VideoViewManager; - +import xyz.doikki.videoplayer.util.PlayerUtils; public class ExoMediaPlayer extends AbstractPlayer implements Player.Listener { @@ -43,7 +47,13 @@ public class ExoMediaPlayer extends AbstractPlayer implements Player.Listener { private LoadControl mLoadControl; private RenderersFactory mRenderersFactory; - private TrackSelector mTrackSelector; + protected TrackSelector mTrackSelector; + + protected DefaultTrackSelector trackSelector; + + protected String currentPlayPath; + protected Map currentHeaders; + private boolean mRetriedAsHls; public ExoMediaPlayer(Context context) { mAppContext = context.getApplicationContext(); @@ -52,22 +62,31 @@ public ExoMediaPlayer(Context context) { @Override public void initPlayer() { + if (mLoadControl == null) { + mLoadControl = new DefaultLoadControl.Builder() + .setBufferDurationsMs(15_000, 30_000, 1_000, 3_000) + .setTargetBufferBytes(32 * 1024 * 1024) + .setPrioritizeTimeOverSizeThresholds(false) + .setBackBuffer(0, false) + .build(); + } mInternalPlayer = new SimpleExoPlayer.Builder( mAppContext, - mRenderersFactory == null ? mRenderersFactory = new DefaultRenderersFactory(mAppContext) : mRenderersFactory, + mRenderersFactory == null ? mRenderersFactory = new DefaultRenderersFactory(mAppContext).setEnableDecoderFallback(true) // 启用解码器回退,避免硬件加速问题 + .setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER) : mRenderersFactory, mTrackSelector == null ? mTrackSelector = new DefaultTrackSelector(mAppContext) : mTrackSelector, new DefaultMediaSourceFactory(mAppContext), - mLoadControl == null ? mLoadControl = new DefaultLoadControl() : mLoadControl, + mLoadControl, DefaultBandwidthMeter.getSingletonInstance(mAppContext), - new AnalyticsCollector(Clock.DEFAULT)) + new DefaultAnalyticsCollector(Clock.DEFAULT)) .build(); setOptions(); - //播放器日志 + // 播放器日志(当开启日志且 mTrackSelector 为 MappingTrackSelector 时) if (VideoViewManager.getConfig().mIsEnableLog && mTrackSelector instanceof MappingTrackSelector) { mInternalPlayer.addAnalyticsListener(new EventLogger((MappingTrackSelector) mTrackSelector, "ExoPlayer")); } - + if(trackSelector == null)trackSelector=(DefaultTrackSelector)mTrackSelector; mInternalPlayer.addListener(this); } @@ -85,12 +104,16 @@ public void setLoadControl(LoadControl loadControl) { @Override public void setDataSource(String path, Map headers) { - mMediaSource = mMediaSourceHelper.getMediaSource(path, headers); + Log.i("Tvbox-runtime","echo-setDataSource:"+path); + currentPlayPath = path; + currentHeaders = copyHeaders(headers); + mRetriedAsHls = false; + mMediaSource = mMediaSourceHelper.getMediaSource(path, copyHeaders(currentHeaders)); } @Override public void setDataSource(AssetFileDescriptor fd) { - //no support + // 不支持AssetFileDescriptor方式 } @Override @@ -134,6 +157,7 @@ public void reset() { mInternalPlayer.clearMediaItems(); mInternalPlayer.setVideoSurface(null); mIsPreparing = false; + mRetriedAsHls = false; } } @@ -167,7 +191,6 @@ public void release() { mInternalPlayer.release(); mInternalPlayer = null; } - mIsPreparing = false; mSpeedPlaybackParameters = null; } @@ -243,8 +266,7 @@ public float getSpeed() { @Override public long getTcpSpeed() { - // no support - return 0; + return PlayerUtils.getNetSpeed(mAppContext); } @Override @@ -272,12 +294,46 @@ public void onPlaybackStateChanged(int playbackState) { } @Override - public void onPlayerError(ExoPlaybackException error) { + public void onPlayerError(PlaybackException error) { + Log.e("Tvbox-runtime", "echo-Exo player error: " + currentPlayPath, error); + if (retryAsHls(error)) { + return; + } if (mPlayerEventListener != null) { mPlayerEventListener.onError(); } } + private boolean retryAsHls(PlaybackException error) { + if (mRetriedAsHls || mInternalPlayer == null || currentPlayPath == null) { + return false; + } + if (!isParsingError(error)) { + return false; + } + mRetriedAsHls = true; + Log.i("Tvbox-runtime", "echo-Exo retry as HLS: " + currentPlayPath); + mMediaSource = mMediaSourceHelper.getHlsMediaSource(currentPlayPath, copyHeaders(currentHeaders)); + mIsPreparing = true; + mInternalPlayer.setMediaSource(mMediaSource); + mInternalPlayer.prepare(); + mInternalPlayer.setPlayWhenReady(true); + return true; + } + + private boolean isParsingError(PlaybackException error) { + int errorCode = error.errorCode; + return errorCode == PlaybackException.ERROR_CODE_IO_UNSPECIFIED + || errorCode == PlaybackException.ERROR_CODE_PARSING_CONTAINER_MALFORMED + || errorCode == PlaybackException.ERROR_CODE_PARSING_CONTAINER_UNSUPPORTED + || errorCode == PlaybackException.ERROR_CODE_PARSING_MANIFEST_MALFORMED + || errorCode == PlaybackException.ERROR_CODE_PARSING_MANIFEST_UNSUPPORTED; + } + + private Map copyHeaders(Map headers) { + return headers == null ? null : new HashMap<>(headers); + } + @Override public void onVideoSizeChanged(VideoSize videoSize) { if (mPlayerEventListener != null) { diff --git a/player/src/main/java/xyz/doikki/videoplayer/exo/ExoMediaSourceHelper.java b/player/src/main/java/xyz/doikki/videoplayer/exo/ExoMediaSourceHelper.java index a068c663ea..e0d31d5fe0 100644 --- a/player/src/main/java/xyz/doikki/videoplayer/exo/ExoMediaSourceHelper.java +++ b/player/src/main/java/xyz/doikki/videoplayer/exo/ExoMediaSourceHelper.java @@ -7,7 +7,6 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.database.ExoDatabaseProvider; -import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSource; import com.google.android.exoplayer2.ext.rtmp.RtmpDataSourceFactory; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.ProgressiveMediaSource; @@ -15,33 +14,29 @@ import com.google.android.exoplayer2.source.hls.HlsMediaSource; import com.google.android.exoplayer2.source.rtsp.RtspMediaSource; import com.google.android.exoplayer2.upstream.DataSource; -import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.upstream.cache.Cache; import com.google.android.exoplayer2.upstream.cache.CacheDataSource; import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor; import com.google.android.exoplayer2.upstream.cache.SimpleCache; -import com.google.android.exoplayer2.util.Util; import java.io.File; -import java.lang.reflect.Field; -import java.util.Iterator; +import java.util.HashMap; import java.util.Map; import okhttp3.OkHttpClient; public final class ExoMediaSourceHelper { + public static final String HEADER_FORMAT = "TVBox-Format"; private static ExoMediaSourceHelper sInstance; - private final String mUserAgent; private final Context mAppContext; private OkHttpDataSource.Factory mHttpDataSourceFactory; - private OkHttpClient mOkClient = null; private Cache mCache; + private OkHttpClient mClient; private ExoMediaSourceHelper(Context context) { mAppContext = context.getApplicationContext(); - mUserAgent = Util.getUserAgent(mAppContext, mAppContext.getApplicationInfo().name); } public static ExoMediaSourceHelper getInstance(Context context) { @@ -56,7 +51,8 @@ public static ExoMediaSourceHelper getInstance(Context context) { } public void setOkClient(OkHttpClient client) { - mOkClient = client; + mClient = client; + mHttpDataSourceFactory = null; } public MediaSource getMediaSource(String uri) { @@ -72,6 +68,14 @@ public MediaSource getMediaSource(String uri, boolean isCache) { } public MediaSource getMediaSource(String uri, Map headers, boolean isCache) { + return getMediaSource(uri, headers, isCache, inferContentType(uri, headers)); + } + + public MediaSource getHlsMediaSource(String uri, Map headers) { + return getMediaSource(uri, headers, false, C.TYPE_HLS); + } + + private MediaSource getMediaSource(String uri, Map headers, boolean isCache, int contentType) { Uri contentUri = Uri.parse(uri); if ("rtmp".equals(contentUri.getScheme())) { return new ProgressiveMediaSource.Factory(new RtmpDataSourceFactory(null)) @@ -79,38 +83,72 @@ public MediaSource getMediaSource(String uri, Map headers, boole } else if ("rtsp".equals(contentUri.getScheme())) { return new RtspMediaSource.Factory().createMediaSource(MediaItem.fromUri(contentUri)); } - int contentType = inferContentType(uri); DataSource.Factory factory; if (isCache) { factory = getCacheDataSourceFactory(); } else { factory = getDataSourceFactory(); } - if (mHttpDataSourceFactory != null) { - setHeaders(headers); - } + applyHeaders(headers); switch (contentType) { case C.TYPE_DASH: return new DashMediaSource.Factory(factory).createMediaSource(MediaItem.fromUri(contentUri)); case C.TYPE_HLS: - return new HlsMediaSource.Factory(factory).createMediaSource(MediaItem.fromUri(contentUri)); + return new HlsMediaSource.Factory(factory) + .setLoadErrorHandlingPolicy(new HlsErrorHandlingPolicy()) // 设置自定义错误处理策略,跳过坏的切片 + .createMediaSource(MediaItem.fromUri(contentUri)); default: case C.TYPE_OTHER: return new ProgressiveMediaSource.Factory(factory).createMediaSource(MediaItem.fromUri(contentUri)); } } - private int inferContentType(String fileName) { + private int inferContentType(String fileName, Map headers) { + int formatType = inferFormatContentType(headers); + if (formatType != C.TYPE_OTHER) { + return formatType; + } fileName = fileName.toLowerCase(); - if (fileName.contains(".mpd")) { + if (fileName.contains(".mpd") || fileName.contains("type=mpd") || fileName.contains("type=dash") || fileName.contains("format=mpd") || fileName.contains("format=dash")) { return C.TYPE_DASH; - } else if (fileName.contains(".m3u8")) { + } else if (isHlsUri(fileName)) { return C.TYPE_HLS; } else { return C.TYPE_OTHER; } } + private int inferFormatContentType(Map headers) { + if (headers == null || !headers.containsKey(HEADER_FORMAT)) { + return C.TYPE_OTHER; + } + String format = headers.get(HEADER_FORMAT); + if (format == null) { + return C.TYPE_OTHER; + } + format = format.trim().toLowerCase(); + if (format.equals("hls") || format.contains("mpegurl") || format.contains("m3u8")) { + return C.TYPE_HLS; + } + if (format.equals("dash") || format.equals("mpd") || format.contains("dash+xml")) { + return C.TYPE_DASH; + } + return C.TYPE_OTHER; + } + + private boolean isHlsUri(String uri) { + if (uri.contains("m3u8") || uri.contains("type=hls") || uri.contains("format=hls")) { + return true; + } + Uri parsedUri = Uri.parse(uri); + String path = parsedUri.getPath(); + if (path == null) { + return false; + } + path = path.toLowerCase(); + return path.endsWith("/live.php") || path.contains("/live/"); + } + private DataSource.Factory getCacheDataSourceFactory() { if (mCache == null) { mCache = newCache(); @@ -123,18 +161,26 @@ private DataSource.Factory getCacheDataSourceFactory() { private Cache newCache() { return new SimpleCache( - new File(mAppContext.getExternalCacheDir(), "exo-video-cache"),//缓存目录 + new File(externalCacheDir(), "exo-video-cache"),//缓存目录 new LeastRecentlyUsedCacheEvictor(512 * 1024 * 1024),//缓存大小,默认512M,使用LRU算法实现 new ExoDatabaseProvider(mAppContext)); } + private File externalCacheDir() + { + File externalCacheDir = mAppContext.getExternalCacheDir(); + if (externalCacheDir == null){ + externalCacheDir = mAppContext.getCacheDir(); + } + return externalCacheDir; + } /** * Returns a new DataSource factory. * * @return A new DataSource factory. */ private DataSource.Factory getDataSourceFactory() { - return new DefaultDataSourceFactory(mAppContext, getHttpDataSourceFactory()); + return new com.google.android.exoplayer2.upstream.DefaultDataSourceFactory(mAppContext, getHttpDataSourceFactory()); } /** @@ -142,39 +188,36 @@ private DataSource.Factory getDataSourceFactory() { * * @return A new HttpDataSource factory. */ - private DataSource.Factory getHttpDataSourceFactory() { + private OkHttpDataSource.Factory getHttpDataSourceFactory() { if (mHttpDataSourceFactory == null) { - mHttpDataSourceFactory = new OkHttpDataSource.Factory(mOkClient) - .setUserAgent(mUserAgent)/* - .setAllowCrossProtocolRedirects(true)*/; + OkHttpClient client = mClient != null ? mClient : new OkHttpClient.Builder().build(); + mHttpDataSourceFactory = new OkHttpDataSource.Factory(client); } return mHttpDataSourceFactory; } - private void setHeaders(Map headers) { + private void applyHeaders(Map headers) { + Map requestHeaders = new HashMap<>(); + String userAgent = null; if (headers != null && headers.size() > 0) { - //如果发现用户通过header传递了UA,则强行将HttpDataSourceFactory里面的userAgent字段替换成用户的 - if (headers.containsKey("User-Agent")) { - String value = headers.remove("User-Agent"); - if (!TextUtils.isEmpty(value)) { - try { - Field userAgentField = mHttpDataSourceFactory.getClass().getDeclaredField("userAgent"); - userAgentField.setAccessible(true); - userAgentField.set(mHttpDataSourceFactory, value.trim()); - } catch (Exception e) { - //ignore - } + for (Map.Entry entry : headers.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + if (TextUtils.isEmpty(key) || TextUtils.isEmpty(value)) { + continue; + } + if (HEADER_FORMAT.equalsIgnoreCase(key)) { + continue; + } + if ("User-Agent".equalsIgnoreCase(key)) { + userAgent = value.trim(); + } else { + requestHeaders.put(key, value.trim()); } } - Iterator iter = headers.keySet().iterator(); - while (iter.hasNext()) { - String k = iter.next(); - String v = headers.get(k); - if (v != null) - headers.put(k, v.trim()); - } - mHttpDataSourceFactory.setDefaultRequestProperties(headers); } + mHttpDataSourceFactory.setUserAgent(userAgent); + mHttpDataSourceFactory.setDefaultRequestProperties(requestHeaders); } public void setCache(Cache cache) { diff --git a/player/src/main/java/xyz/doikki/videoplayer/exo/HlsErrorHandlingPolicy.java b/player/src/main/java/xyz/doikki/videoplayer/exo/HlsErrorHandlingPolicy.java new file mode 100644 index 0000000000..9218dd9aaa --- /dev/null +++ b/player/src/main/java/xyz/doikki/videoplayer/exo/HlsErrorHandlingPolicy.java @@ -0,0 +1,63 @@ +package xyz.doikki.videoplayer.exo; + +import androidx.annotation.NonNull; + +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy; +import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; + +import java.io.IOException; + +/** + * 自定义HLS错误处理策略,跳过坏的切片继续播放 + */ +public class HlsErrorHandlingPolicy extends DefaultLoadErrorHandlingPolicy { + + private static final int MAX_RETRIES = 3; // 最多重试3次 + private static final long RETRY_DELAY_MS = 500; // 重试延迟500毫秒 + + public HlsErrorHandlingPolicy() { + super(); + } + + @Override + public long getRetryDelayMsFor(@NonNull LoadErrorHandlingPolicy.LoadErrorInfo loadErrorInfo) { + // 如果是HLS切片错误,使用较短的重试延迟 + if (isChunkError(loadErrorInfo)) { + return RETRY_DELAY_MS; + } + return super.getRetryDelayMsFor(loadErrorInfo); + } + + @Override + public int getMinimumLoadableRetryCount(int dataType) { + // 对于HLS切片,限制重试次数,避免无限重试 + if (dataType == C.DATA_TYPE_MEDIA) { + return MAX_RETRIES; + } + return super.getMinimumLoadableRetryCount(dataType); + } + + @Override + public LoadErrorHandlingPolicy.FallbackSelection getFallbackSelectionFor( + @NonNull FallbackOptions fallbackOptions, + @NonNull LoadErrorInfo loadErrorInfo) { + + // 对于HLS切片加载错误,不使用fallback,而是直接跳过 + if (isChunkError(loadErrorInfo)) { + // 返回null表示不使用fallback,ExoPlayer会跳过这个切片继续播放 + return null; + } + + return super.getFallbackSelectionFor(fallbackOptions, loadErrorInfo); + } + + /** + * 判断是否是切片错误 + */ + private boolean isChunkError(@NonNull LoadErrorInfo loadErrorInfo) { + // 检查是否是IO异常(网络错误、404等) + Throwable error = loadErrorInfo.exception; + return error instanceof IOException; + } +} diff --git a/player/src/main/java/xyz/doikki/videoplayer/exo/OkHttpDataSource.java b/player/src/main/java/xyz/doikki/videoplayer/exo/OkHttpDataSource.java new file mode 100644 index 0000000000..c2e988d821 --- /dev/null +++ b/player/src/main/java/xyz/doikki/videoplayer/exo/OkHttpDataSource.java @@ -0,0 +1,406 @@ +package xyz.doikki.videoplayer.exo; + +import static com.google.android.exoplayer2.upstream.HttpUtil.buildRangeRequestHeader; +import static java.lang.Math.min; + +import android.net.Uri; + +import androidx.annotation.Nullable; + +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.PlaybackException; +import com.google.android.exoplayer2.upstream.BaseDataSource; +import com.google.android.exoplayer2.upstream.DataSource; +import com.google.android.exoplayer2.upstream.DataSourceException; +import com.google.android.exoplayer2.upstream.DataSpec; +import com.google.android.exoplayer2.upstream.HttpDataSource; +import com.google.android.exoplayer2.upstream.HttpDataSource.HttpDataSourceException; +import com.google.android.exoplayer2.upstream.HttpDataSource.InvalidContentTypeException; +import com.google.android.exoplayer2.upstream.HttpDataSource.InvalidResponseCodeException; +import com.google.android.exoplayer2.upstream.HttpUtil; +import com.google.android.exoplayer2.upstream.TransferListener; +import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.Util; +import com.google.common.base.Predicate; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InterruptedIOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; + +import okhttp3.CacheControl; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.HttpUrl; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.ResponseBody; + +/** + * ExoPlayer HTTP data source backed by okhttp3. + */ +@Deprecated +public final class OkHttpDataSource extends BaseDataSource implements HttpDataSource { + + public static final class Factory extends HttpDataSource.BaseFactory { + + private final Call.Factory callFactory; + @Nullable + private String userAgent; + @Nullable + private TransferListener transferListener; + @Nullable + private CacheControl cacheControl; + @Nullable + private Predicate contentTypePredicate; + + public Factory(Call.Factory callFactory) { + this.callFactory = callFactory; + } + + public Factory setUserAgent(@Nullable String userAgent) { + this.userAgent = userAgent; + return this; + } + + public Factory setCacheControl(@Nullable CacheControl cacheControl) { + this.cacheControl = cacheControl; + return this; + } + + public Factory setContentTypePredicate(@Nullable Predicate contentTypePredicate) { + this.contentTypePredicate = contentTypePredicate; + return this; + } + + public Factory setTransferListener(@Nullable TransferListener transferListener) { + this.transferListener = transferListener; + return this; + } + + @Override + protected HttpDataSource createDataSourceInternal(RequestProperties defaultRequestProperties) { + OkHttpDataSource dataSource = new OkHttpDataSource(callFactory, userAgent, cacheControl, defaultRequestProperties, contentTypePredicate); + if (transferListener != null) { + dataSource.addTransferListener(transferListener); + } + return dataSource; + } + } + + private final Call.Factory callFactory; + private final RequestProperties requestProperties; + @Nullable + private final String userAgent; + @Nullable + private final CacheControl cacheControl; + @Nullable + private final RequestProperties defaultRequestProperties; + @Nullable + private Predicate contentTypePredicate; + @Nullable + private DataSpec dataSpec; + @Nullable + private Response response; + @Nullable + private InputStream responseByteStream; + private boolean opened; + private long bytesToRead; + private long bytesRead; + + public OkHttpDataSource(Call.Factory callFactory) { + this(callFactory, null); + } + + public OkHttpDataSource(Call.Factory callFactory, @Nullable String userAgent) { + this(callFactory, userAgent, null, null, null); + } + + public OkHttpDataSource(Call.Factory callFactory, @Nullable String userAgent, @Nullable CacheControl cacheControl, @Nullable HttpDataSource.RequestProperties defaultRequestProperties) { + this(callFactory, userAgent, cacheControl, defaultRequestProperties, null); + } + + private OkHttpDataSource(Call.Factory callFactory, @Nullable String userAgent, @Nullable CacheControl cacheControl, @Nullable HttpDataSource.RequestProperties defaultRequestProperties, @Nullable Predicate contentTypePredicate) { + super(true); + this.callFactory = Assertions.checkNotNull(callFactory); + this.userAgent = userAgent; + this.cacheControl = cacheControl; + this.defaultRequestProperties = defaultRequestProperties; + this.contentTypePredicate = contentTypePredicate; + this.requestProperties = new RequestProperties(); + } + + @Deprecated + public void setContentTypePredicate(@Nullable Predicate contentTypePredicate) { + this.contentTypePredicate = contentTypePredicate; + } + + @Override + public @Nullable Uri getUri() { + return response == null ? null : Uri.parse(response.request().url().toString()); + } + + @Override + public int getResponseCode() { + return response == null ? -1 : response.code(); + } + + @Override + public Map> getResponseHeaders() { + return response == null ? Collections.>emptyMap() : response.headers().toMultimap(); + } + + @Override + public void setRequestProperty(String name, String value) { + Assertions.checkNotNull(name); + Assertions.checkNotNull(value); + requestProperties.set(name, value); + } + + @Override + public void clearRequestProperty(String name) { + Assertions.checkNotNull(name); + requestProperties.remove(name); + } + + @Override + public void clearAllRequestProperties() { + requestProperties.clear(); + } + + @Override + public long open(DataSpec dataSpec) throws HttpDataSourceException { + this.dataSpec = dataSpec; + bytesRead = 0; + bytesToRead = 0; + transferInitializing(dataSpec); + + Request request = makeRequest(dataSpec); + Response response; + ResponseBody responseBody; + Call call = callFactory.newCall(request); + try { + response = executeCall(call); + this.response = response; + responseBody = Assertions.checkNotNull(response.body()); + responseByteStream = responseBody.byteStream(); + } catch (IOException e) { + throw HttpDataSourceException.createForIOException(e, dataSpec, HttpDataSourceException.TYPE_OPEN); + } + + int responseCode = response.code(); + if (!response.isSuccessful()) { + if (responseCode == 416) { + long documentSize = HttpUtil.getDocumentSize(response.headers().get("Content-Range")); + if (dataSpec.position == documentSize) { + opened = true; + transferStarted(dataSpec); + return dataSpec.length != C.LENGTH_UNSET ? dataSpec.length : 0; + } + } + + byte[] errorResponseBody; + try { + errorResponseBody = Util.toByteArray(Assertions.checkNotNull(responseByteStream)); + } catch (IOException e) { + errorResponseBody = Util.EMPTY_BYTE_ARRAY; + } + Map> headers = response.headers().toMultimap(); + closeConnectionQuietly(); + IOException cause = responseCode == 416 ? new DataSourceException(PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE) : null; + throw new InvalidResponseCodeException(responseCode, response.message(), cause, headers, dataSpec, errorResponseBody); + } + + MediaType mediaType = responseBody.contentType(); + String contentType = mediaType != null ? mediaType.toString() : ""; + if (contentTypePredicate != null && !contentTypePredicate.apply(contentType)) { + closeConnectionQuietly(); + throw new InvalidContentTypeException(contentType, dataSpec); + } + + long bytesToSkip = responseCode == 200 && dataSpec.position != 0 ? dataSpec.position : 0; + if (dataSpec.length != C.LENGTH_UNSET) { + bytesToRead = dataSpec.length; + } else { + long contentLength = responseBody.contentLength(); + bytesToRead = contentLength != -1 ? (contentLength - bytesToSkip) : C.LENGTH_UNSET; + } + + opened = true; + transferStarted(dataSpec); + + try { + skipFully(bytesToSkip, dataSpec); + } catch (HttpDataSourceException e) { + closeConnectionQuietly(); + throw e; + } + return bytesToRead; + } + + @Override + public int read(byte[] buffer, int offset, int length) throws HttpDataSourceException { + try { + return readInternal(buffer, offset, length); + } catch (IOException e) { + throw HttpDataSourceException.createForIOException(e, Assertions.checkNotNull(dataSpec), HttpDataSourceException.TYPE_READ); + } + } + + @Override + public void close() throws HttpDataSourceException { + if (opened) { + opened = false; + transferEnded(); + closeConnectionQuietly(); + } + } + + private Request makeRequest(DataSpec dataSpec) throws HttpDataSourceException { + long position = dataSpec.position; + long length = dataSpec.length; + + HttpUrl url = HttpUrl.parse(dataSpec.uri.toString()); + if (url == null) { + throw new HttpDataSourceException("Malformed URL", dataSpec, PlaybackException.ERROR_CODE_FAILED_RUNTIME_CHECK, HttpDataSourceException.TYPE_OPEN); + } + + Request.Builder builder = new Request.Builder().url(url); + if (cacheControl != null) { + builder.cacheControl(cacheControl); + } + + Map headers = new HashMap<>(); + if (defaultRequestProperties != null) { + headers.putAll(defaultRequestProperties.getSnapshot()); + } + headers.putAll(requestProperties.getSnapshot()); + headers.putAll(dataSpec.httpRequestHeaders); + for (Map.Entry header : headers.entrySet()) { + builder.header(header.getKey(), header.getValue()); + } + + String rangeHeader = buildRangeRequestHeader(position, length); + if (rangeHeader != null) { + builder.addHeader("Range", rangeHeader); + } + if (userAgent != null) { + builder.addHeader("User-Agent", userAgent); + } + if (!dataSpec.isFlagSet(DataSpec.FLAG_ALLOW_GZIP)) { + builder.addHeader("Accept-Encoding", "identity"); + } + + RequestBody requestBody = null; + if (dataSpec.httpBody != null) { + requestBody = RequestBody.create(null, dataSpec.httpBody); + } else if (dataSpec.httpMethod == DataSpec.HTTP_METHOD_POST) { + requestBody = RequestBody.create(null, Util.EMPTY_BYTE_ARRAY); + } + builder.method(dataSpec.getHttpMethodString(), requestBody); + return builder.build(); + } + + private Response executeCall(Call call) throws IOException { + final Response[] responseHolder = new Response[1]; + final IOException[] errorHolder = new IOException[1]; + final CountDownLatch latch = new CountDownLatch(1); + call.enqueue(new Callback() { + @Override + public void onFailure(Call call, IOException e) { + errorHolder[0] = e; + latch.countDown(); + } + + @Override + public void onResponse(Call call, Response response) { + responseHolder[0] = response; + latch.countDown(); + } + }); + + boolean interrupted = false; + while (true) { + try { + latch.await(); + break; + } catch (InterruptedException e) { + interrupted = true; + call.cancel(); + } + } + if (interrupted) { + throw new InterruptedIOException(); + } + if (errorHolder[0] != null) { + throw errorHolder[0]; + } + return Assertions.checkNotNull(responseHolder[0]); + } + + private void skipFully(long bytesToSkip, DataSpec dataSpec) throws HttpDataSourceException { + if (bytesToSkip == 0) { + return; + } + byte[] skipBuffer = new byte[4096]; + try { + while (bytesToSkip > 0) { + int readLength = (int) min(bytesToSkip, skipBuffer.length); + int read = Assertions.checkNotNull(responseByteStream).read(skipBuffer, 0, readLength); + if (Thread.currentThread().isInterrupted()) { + throw new InterruptedIOException(); + } + if (read == -1) { + throw new HttpDataSourceException(dataSpec, PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE, HttpDataSourceException.TYPE_OPEN); + } + bytesToSkip -= read; + bytesTransferred(read); + } + } catch (IOException e) { + if (e instanceof HttpDataSourceException) { + throw (HttpDataSourceException) e; + } + throw new HttpDataSourceException(dataSpec, PlaybackException.ERROR_CODE_IO_UNSPECIFIED, HttpDataSourceException.TYPE_OPEN); + } + } + + private int readInternal(byte[] buffer, int offset, int readLength) throws IOException { + if (readLength == 0) { + return 0; + } + if (bytesToRead != C.LENGTH_UNSET) { + long bytesRemaining = bytesToRead - bytesRead; + if (bytesRemaining == 0) { + return C.RESULT_END_OF_INPUT; + } + readLength = (int) min(readLength, bytesRemaining); + } + + int read = Assertions.checkNotNull(responseByteStream).read(buffer, offset, readLength); + if (read == -1) { + return C.RESULT_END_OF_INPUT; + } + + bytesRead += read; + bytesTransferred(read); + return read; + } + + private void closeConnectionQuietly() { + if (response != null) { + ResponseBody body = response.body(); + if (body != null) { + body.close(); + } + response = null; + } + responseByteStream = null; + } +} diff --git a/player/src/main/java/xyz/doikki/videoplayer/ijk/IjkPlayer.java b/player/src/main/java/xyz/doikki/videoplayer/ijk/IjkPlayer.java index 2dbeddf5d2..44cbe63b17 100644 --- a/player/src/main/java/xyz/doikki/videoplayer/ijk/IjkPlayer.java +++ b/player/src/main/java/xyz/doikki/videoplayer/ijk/IjkPlayer.java @@ -15,8 +15,10 @@ import tv.danmaku.ijk.media.player.IjkMediaPlayer; import tv.danmaku.ijk.media.player.misc.ITrackInfo; import tv.danmaku.ijk.media.player.misc.IjkTrackInfo; +import xyz.doikki.videoplayer.exo.ExoMediaSourceHelper; import xyz.doikki.videoplayer.player.AbstractPlayer; import xyz.doikki.videoplayer.player.VideoViewManager; +import xyz.doikki.videoplayer.util.PlayerUtils; public class IjkPlayer extends AbstractPlayer implements IMediaPlayer.OnErrorListener, IMediaPlayer.OnCompletionListener, IMediaPlayer.OnInfoListener, @@ -61,6 +63,7 @@ public void setDataSource(String path, Map headers) { } else { //处理UA问题 if (headers != null) { + headers.remove(ExoMediaSourceHelper.HEADER_FORMAT); String userAgent = headers.get("User-Agent"); if (!TextUtils.isEmpty(userAgent)) { mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "user_agent", userAgent); @@ -135,7 +138,7 @@ public boolean isPlaying() { @Override public void seekTo(long time) { try { - mMediaPlayer.seekTo((int) time); + mMediaPlayer.seekTo(PlayerUtils.safeTimeMs(time)); } catch (IllegalStateException e) { mPlayerEventListener.onError(); } diff --git a/player/src/main/java/xyz/doikki/videoplayer/player/AndroidMediaPlayer.java b/player/src/main/java/xyz/doikki/videoplayer/player/AndroidMediaPlayer.java index 87a08536c7..210e586cd3 100644 --- a/player/src/main/java/xyz/doikki/videoplayer/player/AndroidMediaPlayer.java +++ b/player/src/main/java/xyz/doikki/videoplayer/player/AndroidMediaPlayer.java @@ -11,6 +11,9 @@ import java.util.Map; +import xyz.doikki.videoplayer.exo.ExoMediaSourceHelper; +import xyz.doikki.videoplayer.util.PlayerUtils; + /** * 封装系统的MediaPlayer,不推荐,系统的MediaPlayer兼容性较差,建议使用IjkPlayer或者ExoPlayer */ @@ -44,6 +47,7 @@ public void initPlayer() { @Override public void setDataSource(String path, Map headers) { try { + if (headers != null) headers.remove(ExoMediaSourceHelper.HEADER_FORMAT); mMediaPlayer.setDataSource(mAppContext, Uri.parse(path), headers); } catch (Exception e) { mPlayerEventListener.onError(); @@ -113,7 +117,7 @@ public boolean isPlaying() { @Override public void seekTo(long time) { try { - mMediaPlayer.seekTo((int) time); + mMediaPlayer.seekTo(PlayerUtils.safeTimeMs(time)); } catch (IllegalStateException e) { mPlayerEventListener.onError(); } @@ -216,8 +220,7 @@ public float getSpeed() { @Override public long getTcpSpeed() { - // no support - return 0; + return PlayerUtils.getNetSpeed(mAppContext); } @Override diff --git a/player/src/main/java/xyz/doikki/videoplayer/player/VideoView.java b/player/src/main/java/xyz/doikki/videoplayer/player/VideoView.java index 9f709c68f7..bdf8237c50 100644 --- a/player/src/main/java/xyz/doikki/videoplayer/player/VideoView.java +++ b/player/src/main/java/xyz/doikki/videoplayer/player/VideoView.java @@ -13,6 +13,9 @@ import android.text.TextUtils; import android.util.AttributeSet; import android.view.Gravity; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.TextureView; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; @@ -346,17 +349,62 @@ public void pause() { * 继续播放 */ public void resume() { - if (isInPlaybackState() - && !mMediaPlayer.isPlaying()) { - mMediaPlayer.start(); - setPlayState(STATE_PLAYING); - if (mAudioFocusHelper != null && !isMute()) { - mAudioFocusHelper.requestFocus(); + if (isInPlaybackState() && !mMediaPlayer.isPlaying()) { + assert mRenderView != null; + View renderView = mRenderView.getView(); + if (renderView instanceof SurfaceView) { + final SurfaceView surfaceView = (SurfaceView) renderView; + final SurfaceHolder holder = surfaceView.getHolder(); + if (holder.getSurface() != null && holder.getSurface().isValid()) { + mMediaPlayer.setDisplay(holder); + resumePlay(); + } else { + holder.addCallback(new SurfaceHolder.Callback() { + @Override + public void surfaceCreated(SurfaceHolder holder) { + addDisplay(); + if (mRenderView != null) { + mRenderView.setScaleType(mCurrentScreenScaleType); + mRenderView.setVideoSize(mVideoSize[0], mVideoSize[1]); + } + mMediaPlayer.setDisplay(holder); + resumePlay(); + // 移除回调,避免重复调用 + holder.removeCallback(this); + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + } + }); + } + } else { + resumePlay(); + } + if (mRenderView != null) { + // 强制请求布局(解决部分设备渲染问题) + mRenderView.getView().requestLayout(); + mRenderView.getView().invalidate(); + } + if (mRenderView != null && mRenderView.getView() != null) { + // 统一设置视图可见性 + mRenderView.getView().setVisibility(View.VISIBLE); } - mPlayerContainer.setKeepScreenOn(true); } } + private void resumePlay(){ + mMediaPlayer.start(); + setPlayState(STATE_PLAYING); + if (mAudioFocusHelper != null && !isMute()) { + mAudioFocusHelper.requestFocus(); + } + mPlayerContainer.setKeepScreenOn(true); + } /** * 释放播放器 */ @@ -395,6 +443,8 @@ public void release() { //切换转态 setPlayState(STATE_IDLE); } + mVideoSize[0] = 0; + mVideoSize[1] = 0; } /** @@ -632,6 +682,8 @@ public void setUrl(String url, Map headers) { mAssetFileDescriptor = null; mUrl = url; mHeaders = headers; + mVideoSize[0] = 0; + mVideoSize[1] = 0; } /** @@ -740,9 +792,6 @@ private void hideSysBar(ViewGroup decorView) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { uiOptions |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - uiOptions |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; - } decorView.setSystemUiVisibility(uiOptions); getActivity().getWindow().setFlags( WindowManager.LayoutParams.FLAG_FULLSCREEN, @@ -787,9 +836,6 @@ private void showSysBar(ViewGroup decorView) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { uiOptions &= ~View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - uiOptions &= ~View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; - } decorView.setSystemUiVisibility(uiOptions); getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); } diff --git a/player/src/main/java/xyz/doikki/videoplayer/util/CutoutUtil.java b/player/src/main/java/xyz/doikki/videoplayer/util/CutoutUtil.java index fc8735be6d..5ea7fdd716 100644 --- a/player/src/main/java/xyz/doikki/videoplayer/util/CutoutUtil.java +++ b/player/src/main/java/xyz/doikki/videoplayer/util/CutoutUtil.java @@ -2,6 +2,7 @@ import android.annotation.SuppressLint; import android.app.Activity; +import android.app.Dialog; import android.content.Context; import android.graphics.Rect; import android.os.Build; @@ -143,4 +144,19 @@ public static void adaptCutoutAboveAndroidP(Context context, boolean isAdapt) { } } + + /** + * 适配刘海屏,针对Android P以上系统 + */ + public static void adaptCutoutAboveAndroidP(Dialog dialog, boolean isAdapt) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + WindowManager.LayoutParams lp = dialog.getWindow().getAttributes(); + if (isAdapt) { + lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + } else { + lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT; + } + dialog.getWindow().setAttributes(lp); + } + } } diff --git a/player/src/main/java/xyz/doikki/videoplayer/util/PlayerUtils.java b/player/src/main/java/xyz/doikki/videoplayer/util/PlayerUtils.java index c7f3cd51a6..7306bb6cbe 100644 --- a/player/src/main/java/xyz/doikki/videoplayer/util/PlayerUtils.java +++ b/player/src/main/java/xyz/doikki/videoplayer/util/PlayerUtils.java @@ -9,6 +9,7 @@ import android.graphics.Point; import android.net.ConnectivityManager; import android.net.NetworkInfo; +import android.net.TrafficStats; import android.os.Build; import android.telephony.TelephonyManager; import android.util.TypedValue; @@ -34,7 +35,8 @@ */ public final class PlayerUtils { - + private static long lastTotalRxBytes; + private static long lastTimeStamp; private PlayerUtils() { } @@ -260,6 +262,9 @@ public static String getCurrentSystemTime() { * 格式化时间 */ public static String stringForTime(int timeMs) { + if (timeMs < 0) { + timeMs = 0; + } int totalSeconds = timeMs / 1000; int seconds = totalSeconds % 60; @@ -273,6 +278,30 @@ public static String stringForTime(int timeMs) { } } + /** + * 格式化时间2 + */ + public static String seconds2Time(int timeMs) { + if (timeMs < 0) { + timeMs = 0; + } + int totalSeconds = timeMs / 1000; + + int seconds = totalSeconds % 60; + int minutes = (totalSeconds / 60); + return String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds); + } + + public static int safeTimeMs(long timeMs) { + if (timeMs <= 0) { + return 0; + } + if (timeMs > Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } + return (int) timeMs; + } + /** * 获取集合的快照 */ @@ -286,4 +315,30 @@ public static List getSnapshot(@NonNull Collection other) { } return result; } + + public static long getNetSpeed(Context context) { + //这里的context是获取getApplicationContext的 + if (context == null) { + return 0; + } + //先使用getUidRxBytes方法获取该进程总接收量,如果没获取到就把当前接收数据总量设置为0,否则就获取接收的总流量并转为kb + long nowTotalRxBytes = TrafficStats.getUidRxBytes(context.getApplicationInfo().uid) == TrafficStats.UNSUPPORTED ? 0 : (TrafficStats.getTotalRxBytes());//转为KB + //记录当前的时间 + long nowTimeStamp = System.currentTimeMillis(); + //上一次记录的时间-当前记录时间算出两次记录的时间差 + long calculationTime = (nowTimeStamp - lastTimeStamp); + //如果时间差不变,直接返回0 + if (calculationTime == 0) { + return calculationTime; + } + + //两次的数据接收量的差除以两次数据接收的时间,就计算网速了。这边的时间差是毫秒,咱们需要转换成秒。 + long speed = ((nowTotalRxBytes - lastTotalRxBytes) * 1000 / calculationTime); + //当前时间存到上次时间这个变量,供下次计算用 + lastTimeStamp = nowTimeStamp; + //当前总接收量存到上次接收总量这个变量,供下次计算用 + lastTotalRxBytes = nowTotalRxBytes; + return speed; + } + } diff --git a/player/src/main/jniLibs/arm64-v8a/libijkffmpeg.so b/player/src/main/jniLibs/arm64-v8a/libijkffmpeg.so new file mode 100644 index 0000000000..72f4e995a5 Binary files /dev/null and b/player/src/main/jniLibs/arm64-v8a/libijkffmpeg.so differ diff --git a/player/src/main/jniLibs/arm64-v8a/libijksdl.so b/player/src/main/jniLibs/arm64-v8a/libijksdl.so new file mode 100644 index 0000000000..fb39659f2a Binary files /dev/null and b/player/src/main/jniLibs/arm64-v8a/libijksdl.so differ diff --git a/player/src/main/jniLibs/arm64-v8a/libp2p.so b/player/src/main/jniLibs/arm64-v8a/libp2p.so new file mode 100644 index 0000000000..2d75eed49a Binary files /dev/null and b/player/src/main/jniLibs/arm64-v8a/libp2p.so differ diff --git a/player/src/main/jniLibs/arm64-v8a/libplayer.so b/player/src/main/jniLibs/arm64-v8a/libplayer.so new file mode 100644 index 0000000000..0849e1474f Binary files /dev/null and b/player/src/main/jniLibs/arm64-v8a/libplayer.so differ diff --git a/player/src/main/jniLibs/arm64-v8a/libxl_stat.so b/player/src/main/jniLibs/arm64-v8a/libxl_stat.so new file mode 100644 index 0000000000..f7824603fb Binary files /dev/null and b/player/src/main/jniLibs/arm64-v8a/libxl_stat.so differ diff --git a/player/src/main/jniLibs/arm64-v8a/libxl_thunder_sdk.so b/player/src/main/jniLibs/arm64-v8a/libxl_thunder_sdk.so new file mode 100644 index 0000000000..2e3ec930b0 Binary files /dev/null and b/player/src/main/jniLibs/arm64-v8a/libxl_thunder_sdk.so differ diff --git a/player/src/main/jniLibs/armeabi-v7a/libijkffmpeg.so b/player/src/main/jniLibs/armeabi-v7a/libijkffmpeg.so new file mode 100644 index 0000000000..7bf406433c Binary files /dev/null and b/player/src/main/jniLibs/armeabi-v7a/libijkffmpeg.so differ diff --git a/player/src/main/jniLibs/armeabi-v7a/libijksdl.so b/player/src/main/jniLibs/armeabi-v7a/libijksdl.so new file mode 100644 index 0000000000..63b11aa968 Binary files /dev/null and b/player/src/main/jniLibs/armeabi-v7a/libijksdl.so differ diff --git a/player/src/main/jniLibs/armeabi-v7a/libp2p.so b/player/src/main/jniLibs/armeabi-v7a/libp2p.so new file mode 100644 index 0000000000..170246640c Binary files /dev/null and b/player/src/main/jniLibs/armeabi-v7a/libp2p.so differ diff --git a/player/src/main/jniLibs/armeabi-v7a/libplayer.so b/player/src/main/jniLibs/armeabi-v7a/libplayer.so index 42c95e8974..ffef24aa6a 100644 Binary files a/player/src/main/jniLibs/armeabi-v7a/libplayer.so and b/player/src/main/jniLibs/armeabi-v7a/libplayer.so differ diff --git a/pyramid/build.gradle b/pyramid/build.gradle new file mode 100644 index 0000000000..8c9ba13752 --- /dev/null +++ b/pyramid/build.gradle @@ -0,0 +1,75 @@ +plugins { + id 'com.android.library' + id 'com.chaquo.python' +} + +android { + compileSdk 33 + + defaultConfig { + minSdkVersion 19 + targetSdkVersion 28 + python { + buildPython("D:/Programs/Python/Python38/python.exe") + pip { + install "lxml" + install "ujson" + install "pyquery" + install "requests" + install "jsonpath" + install "cachetools" + install 'pycryptodome' + install 'beautifulsoup4' + } + } + } + + flavorDimensions "mode" + productFlavors { + python { + dimension "mode" + ndk { + abiFilters "armeabi-v7a", "arm64-v8a" + } + } + python32 { + dimension "mode" + ndk { + abiFilters "armeabi-v7a" + } + } + python64 { + dimension "mode" + ndk { + abiFilters "arm64-v8a" + } + } + } + + buildTypes { + debug { + } + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + sourceSets { + main{ + // Android代码模块 + setRoot "src/main" + // Python代码模块,也就是你的Python源码所在项目中的文件夹 + python.srcDirs = ["src/python"] + } + } + buildToolsVersion '30.0.3' +} + +dependencies { + +} diff --git a/pyramid/consumer-rules.pro b/pyramid/consumer-rules.pro new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/pyramid/consumer-rules.pro @@ -0,0 +1 @@ + diff --git a/pyramid/proguard-rules.pro b/pyramid/proguard-rules.pro new file mode 100644 index 0000000000..f1b424510d --- /dev/null +++ b/pyramid/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/pyramid/src/main/AndroidManifest.xml b/pyramid/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..b267ccd556 --- /dev/null +++ b/pyramid/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/pyramid/src/python/app.py b/pyramid/src/python/app.py new file mode 100644 index 0000000000..8683ee1583 --- /dev/null +++ b/pyramid/src/python/app.py @@ -0,0 +1,160 @@ +#coding=utf-8 +#!/usr/bin/python +import os +import requests +from importlib.machinery import SourceFileLoader ### 导入这个模块 +from urllib import parse +import json +import sys +sys.dont_write_bytecode = True + +PLUGIN_DOWNLOAD_TIMEOUT = 20 + +def createFile(file_path): + if os.path.exists(file_path) is False: + os.makedirs(file_path) + +def redirectResponse(tUrl): + rsp = requests.get(tUrl, allow_redirects=False, verify=False, timeout=PLUGIN_DOWNLOAD_TIMEOUT) + if 'Location' in rsp.headers: + return redirectResponse(rsp.headers['Location']) + else: + return rsp + +def downloadFile(name,url): + try: + rsp = redirectResponse(url) + with open(name,'wb') as f: + f.write(rsp.content) + print(url) + except: + print(name + ' =======================================> error') + print(url) + +def downloadPlugin(basePath,url): + createFile(basePath) + name = url.split('/')[-1].split('.')[0] + pyName = '' + if url.startswith('file://'): + pyName = url.replace('file://','') + else: + pyName = basePath + name+'.py' + downloadFile(pyName,url) + sPath = gParam['SpiderPath'] + sPath[name] = pyName + sParam = gParam['SpiderParam'] + paramList = parse.parse_qs(parse.urlparse(url).query).get('extend') + if paramList == None: + paramList = [''] + sParam[name] = paramList[0] + return pyName + +def registerPluginAlias(alias,fileName): + if alias == None or alias == '': + return + name = fileName.split('/')[-1].split('.')[0] + sPath = gParam['SpiderPath'] + sPath[alias] = fileName + sParam = gParam['SpiderParam'] + sParam[alias] = sParam[name] if name in sParam.keys() else '' + +def loadFromDisk(fileName): + name = fileName.split('/')[-1].split('.')[0] + spList = gParam['SpiderList'] + sp = SourceFileLoader(name, fileName).load_module().Spider() + spList[name] = sp + return spList[name] + +def str2json(content): + return json.loads(content) + +def getDependenceList(ru): + get_dependence = getattr(ru, 'getDependence', None) + if callable(get_dependence): + result = get_dependence() + return result if result is not None else [] + return [] + +def setExtendInfo(ru, extend): + setter = getattr(ru, 'setExtendInfo', None) + if callable(setter): + setter(extend) + else: + setattr(ru, 'extend', extend) + +gParam = { + "SpiderList":{}, + "SpiderPath":{}, + "SpiderParam":{} +} + +def getDependence(ru): + return getDependenceList(ru) + +def getName(ru): + result = ru.getName() + return result + +def init(ru,extend): + spoList = [] + spList = gParam['SpiderList'] + sPath = gParam['SpiderPath'] + sParam = gParam['SpiderParam'] + for key in getDependenceList(ru): + sp = None + if key in spList.keys(): + sp = spList[key] + elif key in sPath.keys(): + sp = loadFromDisk(sPath[key]) + if sp != None: + setExtendInfo(sp, sParam[key]) + spoList.append(sp) + setExtendInfo(ru, extend) + ru.init(spoList if len(spoList) > 0 else extend) + +def homeContent(ru,filter): + result = ru.homeContent(filter) + formatJo = json.dumps(result,ensure_ascii=False) + return formatJo + +def homeVideoContent(ru): + result = ru.homeVideoContent() + formatJo = json.dumps(result,ensure_ascii=False) + return formatJo + +def categoryContent(ru,tid, pg, filter, extend): + result = ru.categoryContent(tid, pg, filter, str2json(extend)) + formatJo = json.dumps(result,ensure_ascii=False) + return formatJo + +def detailContent(ru,array): + result = ru.detailContent(str2json(array)) + formatJo = json.dumps(result,ensure_ascii=False) + return formatJo + +def playerContent(ru,flag,id,vipFlags): + result = ru.playerContent(flag,id,str2json(vipFlags)) + formatJo = json.dumps(result,ensure_ascii=False) + return formatJo + +def liveContent(ru,url): + result = ru.liveContent(url) + return result + +def searchContent(ru,key,quick): + result = ru.searchContent(key,quick) + formatJo = json.dumps(result,ensure_ascii=False) + return formatJo + +def localProxy(ru,param): + result = ru.localProxy(str2json(param)) + return result + +def destroy(ru): + ru.destroy() + +def run(): + pass + +if __name__ == '__main__': + run() diff --git a/pyramid/src/python/base/htmlParser.py b/pyramid/src/python/base/htmlParser.py new file mode 100644 index 0000000000..4010124d50 --- /dev/null +++ b/pyramid/src/python/base/htmlParser.py @@ -0,0 +1,273 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# File : htmlParser.py +# Author: DaShenHan&道长-----先苦后甜,任凭晚风拂柳颜------ +# Date : 2022/8/25 +# upDate : 2022/11/17 支持 -- 剔除元素 多个剔除 + +import ujson +from pyquery import PyQuery as pq +from urllib.parse import urljoin +import re +from jsonpath import jsonpath + +PARSE_CACHE = True # 解析缓存 +NOADD_INDEX = ':eq|:lt|:gt|:first|:last|^body$|^#' # 不自动加eq下标索引 +URLJOIN_ATTR = '(url|src|href|-original|-src|-play|-url)$' # 需要自动urljoin的属性 + + +class jsoup: + def __init__(self, MY_URL=''): + self.MY_URL = MY_URL + self.pdfh_html = '' + self.pdfa_html = '' + + self.pdfh_doc = None + self.pdfa_doc = None + + def test(self, text: str, string: str): + """ + 正则判断字符串包含,模仿js的 //.test() + :param text: + :param string: + :return: + """ + searchObj = re.search(rf'{text}', string, re.M | re.I) + test_ret = True if searchObj else False + return test_ret + + def contains(self, text: str, match: str): + # return match in text + return text.find(match) > -1 + + def parseHikerToJq(self, parse, first=False): + """ + 海阔解析表达式转原生表达式,自动补eq,如果传了first就最后一个也取eq(0) + :param parse: + :param first: + :return: + """ + if self.contains(parse, '&&'): + parse = parse.split('&&') # 带&&的重新拼接 + new_parses = [] # 构造新的解析表达式列表 + for i in range(len(parse)): + ps = parse[i].split(' ')[-1] # 如果分割&&后带空格就取最后一个元素 + if not self.test(NOADD_INDEX, ps): + if not first and i >= len(parse) - 1: # 不传first且遇到最后一个,不用补eq(0) + new_parses.append(parse[i]) + else: + new_parses.append(f'{parse[i]}:eq(0)') + else: + new_parses.append(parse[i]) + parse = ' '.join(new_parses) + else: + ps = parse.split(' ')[-1] # 如果带空格就取最后一个元素 + if not self.test(NOADD_INDEX, ps) and first: + parse = f'{parse}:eq(0)' + + return parse + + def getParseInfo(self, nparse): + """ + 根据传入的单规则获取 parse规则,索引位置,排除列表 -- 可以用于剔除元素,支持多个,按标签剔除,按id剔除等操作 + :param nparse: + :return: + """ + excludes = [] # 定义排除列表默认值为空 + nparse_index = 0 # 定义位置索引默认值为0 + nparse_rule = nparse # 定义规则默认值为本身 + if self.contains(nparse, ':eq'): + nparse_rule = nparse.split(':eq')[0] + nparse_pos = nparse.split(':eq')[1] + # print(nparse_rule) + if self.contains(nparse_rule, '--'): + excludes = nparse_rule.split('--')[1:] + nparse_rule = nparse_rule.split('--')[0] + elif self.contains(nparse_pos, '--'): + excludes = nparse_pos.split('--')[1:] + nparse_pos = nparse_pos.split('--')[0] + try: + nparse_index = int(nparse_pos.split('(')[1].split(')')[0]) + except: + pass + + elif self.contains(nparse, '--'): + nparse_rule = nparse.split('--')[0] + excludes = nparse.split('--')[1:] + + # if nparse_index > 0: + # print(f'nparse_rule:{nparse_rule},nparse_index:{nparse_index},excludes:{excludes}') + return nparse_rule, nparse_index, excludes + + def parseOneRule(self, doc, nparse, ret=None): + """ + 解析空格分割后的原生表达式中的一条记录,正确处理eq的索引,返回处理后的ret + :param doc: pq(html) load 后的pq对象 + :param nparse: 当前单个解析表达式 + :param ret: pd对象结果 + :return: + """ + nparse_rule, nparse_index, excludes = self.getParseInfo(nparse) + + if not ret: + ret = doc(nparse_rule) + else: + ret = ret(nparse_rule) + # print(f'nparse_rule:{nparse_rule},nparse_index:{nparse_index},excludes:{excludes},ret:{ret}') + if self.contains(nparse, ':eq'): + ret = ret.eq(nparse_index) + # if nparse_index > 4: + # print('nparse_index',ret,not ret) + + if excludes and ret: + # print(excludes) + ret = ret.clone() # 克隆一个,免得直接remove会影响doc的缓存 + for exclude in excludes: + # ret.remove(exclude) + ret(exclude).remove() + return ret + + def pdfa(self, html, parse: str): + # 看官方文档才能解决这个问题!!! + # https://pyquery.readthedocs.io/en/latest/api.html + if not all([html, parse]): + return [] + parse = self.parseHikerToJq(parse) + print(f'pdfa:{parse}') + if PARSE_CACHE: + if self.pdfa_html != html: + self.pdfa_html = html + self.pdfa_doc = pq(html) + doc = self.pdfa_doc + else: + doc = pq(html) + + parses = parse.split(' ') + # print(parses) + ret = None + for nparse in parses: + ret = self.parseOneRule(doc, nparse, ret) + if not ret: # 可能循环取值后ret 对应eq取完无值了,pdfa直接返回空列表 + return [] + res = [item.outerHtml() for item in ret.items()] + return res + + def pdfh(self, html, parse: str, base_url: str = ''): + if not all([html, parse]): + return '' + if PARSE_CACHE: + if self.pdfh_html != html: + self.pdfh_html = html + self.pdfh_doc = pq(html) + doc = self.pdfh_doc + else: + doc = pq(html) + if parse == 'body&&Text' or parse == 'Text': + text = doc.text() + return text + elif parse == 'body&&Html' or parse == 'Html': + return doc.html() + + option = None + if self.contains(parse, '&&'): + option = parse.split('&&')[-1] + parse = '&&'.join(parse.split('&&')[:-1]) + parse = self.parseHikerToJq(parse, True) + # print(f'pdfh:{parse},option:{option}') + parses = parse.split(' ') + # print(parses) + ret = None + for nparse in parses: + ret = self.parseOneRule(doc, nparse, ret) + # print(nparse,ret) + if not ret: # 可能循环取值后ret 对应eq取完无值了,pdfh直接返回空字符串 + return '' + + if option: + if option == 'Text': + ret = ret.text() + elif option == 'Html': + ret = ret.html() + else: + ret = ret.attr(option) or '' + if self.contains(option.lower(), 'style') and self.contains(ret, 'url('): + try: + ret = re.search('url\((.*?)\)', ret, re.M | re.S).groups()[0] + except: + pass + + if ret and base_url: + # need_add = re.search(URLJOIN_ATTR, option, re.M | re.I) + need_add = self.test(URLJOIN_ATTR, option) + if need_add: + if 'http' in ret: + ret = ret[ret.find('http'):] + else: + ret = urljoin(base_url, ret) + else: + ret = ret.outerHtml() + return ret + + def pd(self, html, parse: str, base_url: str = ''): + if not base_url: + base_url = self.MY_URL + return self.pdfh(html, parse, base_url) + + def pq(self, html: str): + return pq(html) + + def pjfh(self, html, parse: str, add_url=False): + if not all([html, parse]): + return '' + if isinstance(html, str): + # print(html) + try: + html = ujson.loads(html) + # html = eval(html) + except: + print('字符串转json失败') + return '' + if not parse.startswith('$.'): + parse = f'$.{parse}' + ret = '' + for ps in parse.split('||'): + ret = jsonpath(html, ps) + if isinstance(ret, list): + ret = str(ret[0]) if ret[0] else '' + else: + ret = str(ret) if ret else '' + if add_url and ret: + ret = urljoin(self.MY_URL, ret) + if ret: + break + # print(ret) + return ret + + def pj(self, html, parse: str): + return self.pjfh(html, parse, True) + + def pjfa(self, html, parse: str): + if not all([html, parse]): + return [] + if isinstance(html, str): + try: + html = ujson.loads(html) + except: + return [] + if not parse.startswith('$.'): + parse = f'$.{parse}' + # print(html) + # print(parse) + ret = jsonpath(html, parse) + # print(ret) + # print(type(ret)) + # print(type(ret[0])) + # print(len(ret)) + if isinstance(ret, list) and isinstance(ret[0], list) and len(ret) == 1: + # print('自动解包') + ret = ret[0] # 自动解包 + return ret or [] + + +if __name__ == '__main__': + pass diff --git a/pyramid/src/python/base/localProxy.py b/pyramid/src/python/base/localProxy.py new file mode 100644 index 0000000000..02b89c9213 --- /dev/null +++ b/pyramid/src/python/base/localProxy.py @@ -0,0 +1,8 @@ +from com.github.catvod import Proxy as CatVodProxy + +class Proxy: + def getUrl(self, local): + return CatVodProxy.getUrl(local).replace('/proxy', '') + + def getPort(self): + return CatVodProxy.getPort() diff --git a/pyramid/src/python/base/spider.py b/pyramid/src/python/base/spider.py new file mode 100644 index 0000000000..a4f84e5dfe --- /dev/null +++ b/pyramid/src/python/base/spider.py @@ -0,0 +1,182 @@ +import re +import os +import json +import time +import requests +from lxml import etree +from abc import abstractmethod, ABCMeta +from importlib.machinery import SourceFileLoader +from base.localProxy import Proxy + +class Spider(metaclass=ABCMeta): + _instance = None + + def __init__(self): + self.extend = '' + + def __new__(cls, *args, **kwargs): + if cls._instance: + return cls._instance + else: + cls._instance = super().__new__(cls) + return cls._instance + + @abstractmethod + def init(self, extend=""): + pass + + def homeContent(self, filter): + pass + + def homeVideoContent(self): + pass + + def categoryContent(self, tid, pg, filter, extend): + pass + + def detailContent(self, ids): + pass + + def searchContent(self, key, quick, pg="1"): + pass + + def playerContent(self, flag, id, vipFlags): + pass + + def liveContent(self, url): + pass + + def localProxy(self, param): + pass + + def isVideoFormat(self, url): + pass + + def manualVideoCheck(self): + pass + + def action(self, action): + pass + + def destroy(self): + pass + + def getName(self): + pass + + def getDependence(self): + return [] + + def setExtendInfo(self, extend): + self.extend = extend + + def loadSpider(self, name): + return self.loadModule(name).Spider() + + def loadModule(self, name, fileName=None): + path = fileName if fileName else os.path.join(os.path.join("../plugin"), f'{name}.py') + return SourceFileLoader(name, path).load_module() + + def regStr(self, src, reg=None, group=1): + if reg is None: + return '' + match = None + try: + match = re.search(reg, src) + except Exception: + pass + if not match: + try: + match = re.search(src, reg) + except Exception: + pass + return match.group(group) if match else '' + + def removeHtmlTags(self, src): + clean = re.compile('<.*?>') + return re.sub(clean, '', src) + + def cleanText(self, src): + clean = re.sub('[\U0001F600-\U0001F64F\U0001F300-\U0001F5FF\U0001F680-\U0001F6FF\U0001F1E0-\U0001F1FF]', '', + src) + return clean + + def fetch(self, url, headers=None, cookies=None, params=None, timeout=15, verify=True, stream=False, + allow_redirects=True): + rsp = requests.get(url, params=params, cookies=cookies, headers=headers, timeout=timeout, verify=verify, + stream=stream, allow_redirects=allow_redirects) + rsp.encoding = 'utf-8' + return rsp + + def _clean_header_value(self, value): + cleaned = value.strip() + if cleaned.startswith(""): + cleaned = "DefaultValue" # 根据需求替换为合适的默认值 + return cleaned + + def post(self, url, data=None, headers=None, cookies=None, params=None, json=None, timeout=15, verify=True, stream=False, allow_redirects=True): + # 如果 headers 不为 None,则对其进行预处理 + if headers: + headers = {k: self._clean_header_value(v) for k, v in headers.items()} + + rsp = requests.post(url, params=params, data=data, json=json, cookies=cookies, headers=headers, timeout=timeout, verify=verify, stream=stream, allow_redirects=allow_redirects) + rsp.encoding = 'utf-8' + return rsp + + def postJson(self, url, json, headers=None, cookies=None): + rsp = requests.post(url, json=json, headers=headers, cookies=cookies) + rsp.encoding = 'utf-8' + return rsp + + def html(self, content): + return etree.HTML(content) + + def xpText(self, root, expr): + ele = root.xpath(expr) + if len(ele) == 0: + return '' + else: + return ele[0] + + def str2json(self, content): + return json.loads(content) + + def json2str(self, content): + return json.dumps(content, ensure_ascii=False) + + def getProxyUrl(self, local=True): + return f'{Proxy.getUrl(self,local)}?do=py' + + def log(self, msg): + if isinstance(msg, dict) or isinstance(msg, list): + print(json.dumps(msg, ensure_ascii=False)) + else: + print(f'{msg}') + + def getCache(self, key): + value = self.fetch(f'http://127.0.0.1:{Proxy.getPort(self)}/cache?do=get&key={key}', timeout=5).text + if len(value) > 0: + if value.startswith('{') and value.endswith('}') or value.startswith('[') and value.endswith(']'): + value = json.loads(value) + if type(value) == dict: + if not 'expiresAt' in value or value['expiresAt'] >= int(time.time()): + return value + else: + self.delCache(key) + return None + return value + else: + return None + + def setCache(self, key, value): + if type(value) in [int, float]: + value = str(value) + if len(value) > 0: + if type(value) == dict or type(value) == list: + value = json.dumps(value, ensure_ascii=False) + r = self.post(f'http://127.0.0.1:{Proxy.getPort(self)}/cache?do=set&key={key}', data={"value": value}, timeout=5) + return 'succeed' if r.status_code == 200 else 'failed' + + def delCache(self, key): + r = self.fetch(f'http://127.0.0.1:{Proxy.getPort(self)}/cache?do=del&key={key}', timeout=5) + return 'succeed' if r.status_code == 200 else 'failed' diff --git a/pyramid/src/python/runner.py b/pyramid/src/python/runner.py new file mode 100644 index 0000000000..0ce3482a7d --- /dev/null +++ b/pyramid/src/python/runner.py @@ -0,0 +1,31 @@ +#coding=utf-8 +#!/usr/bin/python +class Runner(): # 元类 默认的元类 type + def __init__(self,spider): + self.spider = spider + def getDependence(self): + return self.spider.getDependence() + def getName(self): + return self.spider.getName() + def init(self,extend=""): + self.spider.init(extend) + def homeContent(self,filter): + return self.spider.homeContent(filter) + def homeVideoContent(self): + return self.spider.homeVideoContent() + def categoryContent(self,tid,pg,filter,extend): + return self.spider.categoryContent(tid,pg,filter,extend) + def detailContent(self,ids): + return self.spider.detailContent(ids) + def searchContent(self,key,quick): + return self.spider.searchContent(key,quick) + def playerContent(self,flag,id,vipFlags): + return self.spider.playerContent(flag,id,vipFlags) + def liveContent(self,url): + return self.spider.liveContent(url) + def localProxy(self,param): + return self.spider.localProxy(param) + def isVideoFormat(self,url): + return self.spider.isVideoFormat(url) + def manualVideoCheck(self): + return self.spider.manualVideoCheck() \ No newline at end of file diff --git a/pyramid/src/python/trigger.py b/pyramid/src/python/trigger.py new file mode 100644 index 0000000000..2c40ab1bf0 --- /dev/null +++ b/pyramid/src/python/trigger.py @@ -0,0 +1,33 @@ +#coding=utf-8 +#!/usr/bin/python +class Trigger(): # 元类 默认的元类 type + @staticmethod + def init(sp_obj,extend=""): + sp_obj.init(extend) + @staticmethod + def homeContent(sp_obj,filter): + return sp_obj.homeContent(filter) + @staticmethod + def homeVideoContent(sp_obj): + return sp_obj.homeVideoContent() + @staticmethod + def categoryContent(sp_obj,tid,pg,filter,extend): + return sp_obj.categoryContent(tid,pg,filter,extend) + @staticmethod + def detailContent(sp_obj,ids): + return sp_obj.detailContent(ids) + @staticmethod + def searchContent(sp_obj,key,quick): + return sp_obj.searchContent(key,quick) + @staticmethod + def playerContent(sp_obj,flag,id,vipFlags): + return sp_obj.playerContent(flag,id,vipFlags) + @staticmethod + def liveContent(sp_obj,url): + return sp_obj.liveContent(url) + @staticmethod + def isVideoFormat(sp_obj,url): + return sp_obj.isVideoFormat(url) + @staticmethod + def manualVideoCheck(sp_obj): + return sp_obj.manualVideoCheck() \ No newline at end of file diff --git a/quickjs/build.gradle b/quickjs/build.gradle new file mode 100644 index 0000000000..a7fffeedf9 --- /dev/null +++ b/quickjs/build.gradle @@ -0,0 +1,34 @@ +plugins { + id 'com.android.library' +} + +android { + compileSdkVersion 33 + + buildFeatures { + buildConfig false + } + + defaultConfig { + minSdkVersion 19 + targetSdkVersion 28 + } + + buildTypes { + debug { + } + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + api 'wang.harlon.quickjs:wrapper-android:2.1.0' + api 'wang.harlon.quickjs:wrapper-java:2.1.0' +} diff --git a/quickjs/src/main/AndroidManifest.xml b/quickjs/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..3005262392 --- /dev/null +++ b/quickjs/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/quickjs/src/main/java/com/whl/quickjs/wrapper/ContextSetter.java b/quickjs/src/main/java/com/whl/quickjs/wrapper/ContextSetter.java new file mode 100644 index 0000000000..7510bf5b8b --- /dev/null +++ b/quickjs/src/main/java/com/whl/quickjs/wrapper/ContextSetter.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2021-2023. caoccao.com Sam Cao + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.whl.quickjs.wrapper; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The interface QuickJSContext setter. + * + * @since 0.8.1 + */ +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface ContextSetter { +} diff --git a/quickjs/src/main/java/com/whl/quickjs/wrapper/Function.java b/quickjs/src/main/java/com/whl/quickjs/wrapper/Function.java new file mode 100644 index 0000000000..37a03e9e33 --- /dev/null +++ b/quickjs/src/main/java/com/whl/quickjs/wrapper/Function.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021-2023. caoccao.com Sam Cao + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.whl.quickjs.wrapper; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The annotation QUJS function is for auto-registering JS function interception. + * + * @since 0.8.1 + */ +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Function { + /** + * JS function name. + * + * @return the name of the JS function to be injected + * @since 0.8.1 + */ + String name() default ""; + +} diff --git a/quickjs/src/main/java/com/whl/quickjs/wrapper/JSUtils.java b/quickjs/src/main/java/com/whl/quickjs/wrapper/JSUtils.java new file mode 100644 index 0000000000..f17b2c036f --- /dev/null +++ b/quickjs/src/main/java/com/whl/quickjs/wrapper/JSUtils.java @@ -0,0 +1,99 @@ +package com.whl.quickjs.wrapper; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.lang.reflect.Array; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +public class JSUtils { + + public static boolean isEmpty(Object obj) { + if (obj == null) return true; + else if (obj instanceof CharSequence) return ((CharSequence) obj).length() == 0; + else if (obj instanceof Collection) return ((Collection) obj).isEmpty(); + else if (obj instanceof Map) return ((Map) obj).isEmpty(); + else if (obj.getClass().isArray()) return Array.getLength(obj) == 0; + + return false; + } + + public static boolean isNotEmpty(CharSequence str) { + return !isEmpty(str); + } + + public static boolean isNotEmpty(Object obj) { + return !isEmpty(obj); + } + + public JSArray toArray(QuickJSContext ctx, List items) { + JSArray array = ctx.createNewJSArray(); + if (items == null || items.isEmpty()) return array; + for (int i = 0; i < items.size(); i++) array.set(toJSValue(ctx, items.get(i)), i); + return array; + } + + public JSArray toArray(QuickJSContext ctx, byte[] bytes) { + JSArray array = ctx.createNewJSArray(); + if (bytes == null || bytes.length == 0) return array; + for (int i = 0; i < bytes.length; i++) array.set((int) bytes[i], i); + return array; + } + + public JSArray toArray(QuickJSContext ctx, T[] arrays) { + JSArray array = ctx.createNewJSArray(); + if (arrays == null || arrays.length == 0) return array; + for (int i = 0; i < arrays.length; i++) array.set(toJSValue(ctx, arrays[i]), i); + return array; + } + + public JSObject toObj(QuickJSContext ctx, Map map) { + JSObject obj = ctx.createNewJSObject(); + if (map == null || map.isEmpty()) return obj; + for (Map.Entry entry : map.entrySet()) { + Object key = entry.getKey(); + if (key == null) continue; + ctx.setProperty(obj, String.valueOf(key), toJSValue(ctx, entry.getValue())); + } + return obj; + } + + public Object toJSValue(QuickJSContext ctx, Object value) { + if (value == null) return null; + if (value instanceof JSObject || value instanceof JSCallFunction) return value; + if (value instanceof Map) return toObj(ctx, (Map) value); + if (value instanceof List) return toArray(ctx, (List) value); + if (value instanceof byte[]) return toArray(ctx, (byte[]) value); + Class valueClass = value.getClass(); + if (valueClass.isArray()) { + JSArray array = ctx.createNewJSArray(); + int length = Array.getLength(value); + for (int i = 0; i < length; i++) { + array.set(toJSValue(ctx, Array.get(value, i)), i); + } + return array; + } + return value; + } + + public static JSONObject toJsonObject(JSObject object) { + if (object == null) return new JSONObject(); + try { + return new JSONObject(object.stringify()); + } catch (JSONException e) { + return new JSONObject(); + } + } + + public static JSONArray toJsonArray(JSArray array) { + if (array == null) return new JSONArray(); + try { + return new JSONArray(array.stringify()); + } catch (JSONException e) { + return new JSONArray(); + } + } +} diff --git a/quickjs/src/main/java/com/whl/quickjs/wrapper/MapCreator.java b/quickjs/src/main/java/com/whl/quickjs/wrapper/MapCreator.java new file mode 100644 index 0000000000..3854273f85 --- /dev/null +++ b/quickjs/src/main/java/com/whl/quickjs/wrapper/MapCreator.java @@ -0,0 +1,80 @@ +package com.whl.quickjs.wrapper; + +import java.lang.reflect.Array; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public interface MapCreator { + + default Map createMap() { + return new HashMap(); + } + + default Map create() { + return createMap(); + } + + default Map toMap() { + return createMap(); + } + + static JSObject create(QuickJSContext ctx, Map map) { + return toObj(ctx, map); + } + + static JSObject createJSObject(QuickJSContext ctx, Map map) { + return toObj(ctx, map); + } + + static JSObject toObj(QuickJSContext ctx, Map map) { + JSObject obj = ctx.createNewJSObject(); + if (map == null || map.isEmpty()) return obj; + for (Map.Entry entry : map.entrySet()) { + Object key = entry.getKey(); + if (key == null) continue; + ctx.setProperty(obj, String.valueOf(key), toJSValue(ctx, entry.getValue())); + } + return obj; + } + + static JSObject toObject(QuickJSContext ctx, Map map) { + return toObj(ctx, map); + } + + static Object toJSValue(QuickJSContext ctx, Object value) { + if (value == null) return null; + if (value instanceof JSObject || value instanceof JSCallFunction) return value; + if (value instanceof Map) return toObj(ctx, (Map) value); + if (value instanceof List) return toArray(ctx, (List) value); + if (value instanceof byte[]) return toArray(ctx, (byte[]) value); + Class valueClass = value.getClass(); + if (valueClass.isArray()) { + JSArray array = ctx.createNewJSArray(); + int length = Array.getLength(value); + for (int i = 0; i < length; i++) { + array.set(toJSValue(ctx, Array.get(value, i)), i); + } + return array; + } + return value; + } + + static JSArray toArray(QuickJSContext ctx, List list) { + JSArray array = ctx.createNewJSArray(); + if (list == null || list.isEmpty()) return array; + for (int i = 0; i < list.size(); i++) { + array.set(toJSValue(ctx, list.get(i)), i); + } + return array; + } + + static JSArray toArray(QuickJSContext ctx, byte[] bytes) { + JSArray array = ctx.createNewJSArray(); + if (bytes == null || bytes.length == 0) return array; + for (int i = 0; i < bytes.length; i++) { + array.set((int) bytes[i], i); + } + return array; + } +} diff --git a/quickjs/src/main/java/com/whl/quickjs/wrapper/UriUtil.java b/quickjs/src/main/java/com/whl/quickjs/wrapper/UriUtil.java new file mode 100644 index 0000000000..dd56109633 --- /dev/null +++ b/quickjs/src/main/java/com/whl/quickjs/wrapper/UriUtil.java @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.whl.quickjs.wrapper; + +import android.net.Uri; +import android.text.TextUtils; + +/** + * Utility methods for manipulating URIs. + */ +public final class UriUtil { + + /** + * The length of arrays returned by {@link #getUriIndices(String)}. + */ + private static final int INDEX_COUNT = 4; + /** + * An index into an array returned by {@link #getUriIndices(String)}. + * + *

The value at this position in the array is the index of the ':' after the scheme. Equals -1 + * if the URI is a relative reference (no scheme). The hier-part starts at (schemeColon + 1), + * including when the URI has no scheme. + */ + private static final int SCHEME_COLON = 0; + /** + * An index into an array returned by {@link #getUriIndices(String)}. + * + *

The value at this position in the array is the index of the path part. Equals (schemeColon + + * 1) if no authority part, (schemeColon + 3) if the authority part consists of just "//", and + * (query) if no path part. The characters starting at this index can be "//" only if the + * authority part is non-empty (in this case the double-slash means the first segment is empty). + */ + private static final int PATH = 1; + /** + * An index into an array returned by {@link #getUriIndices(String)}. + * + *

The value at this position in the array is the index of the query part, including the '?' + * before the query. Equals fragment if no query part, and (fragment - 1) if the query part is a + * single '?' with no data. + */ + private static final int QUERY = 2; + /** + * An index into an array returned by {@link #getUriIndices(String)}. + * + *

The value at this position in the array is the index of the fragment part, including the '#' + * before the fragment. Equal to the length of the URI if no fragment part, and (length - 1) if + * the fragment part is a single '#' with no data. + */ + private static final int FRAGMENT = 3; + + private UriUtil() { + } + + /** + * Like {@link #resolve(String, String)}, but returns a {@link Uri} instead of a {@link String}. + * + * @param baseUri The base URI. + * @param referenceUri The reference URI to resolve. + */ + public static Uri resolveToUri(String baseUri, String referenceUri) { + return Uri.parse(resolve(baseUri, referenceUri)); + } + + /** + * Performs relative resolution of a {@code referenceUri} with respect to a {@code baseUri}. + * + *

The resolution is performed as specified by RFC-3986. + * + * @param baseUri The base URI. + * @param referenceUri The reference URI to resolve. + */ + public static String resolve(String baseUri, String referenceUri) { + StringBuilder uri = new StringBuilder(); + + // Map null onto empty string, to make the following logic simpler. + baseUri = baseUri == null ? "" : baseUri; + referenceUri = referenceUri == null ? "" : referenceUri; + + int[] refIndices = getUriIndices(referenceUri); + if (refIndices[SCHEME_COLON] != -1) { + // The reference is absolute. The target Uri is the reference. + uri.append(referenceUri); + removeDotSegments(uri, refIndices[PATH], refIndices[QUERY]); + return uri.toString(); + } + + int[] baseIndices = getUriIndices(baseUri); + if (refIndices[FRAGMENT] == 0) { + // The reference is empty or contains just the fragment part, then the target Uri is the + // concatenation of the base Uri without its fragment, and the reference. + return uri.append(baseUri, 0, baseIndices[FRAGMENT]).append(referenceUri).toString(); + } + + if (refIndices[QUERY] == 0) { + // The reference starts with the query part. The target is the base up to (but excluding) the + // query, plus the reference. + return uri.append(baseUri, 0, baseIndices[QUERY]).append(referenceUri).toString(); + } + + if (refIndices[PATH] != 0) { + // The reference has authority. The target is the base scheme plus the reference. + int baseLimit = baseIndices[SCHEME_COLON] + 1; + uri.append(baseUri, 0, baseLimit).append(referenceUri); + return removeDotSegments(uri, baseLimit + refIndices[PATH], baseLimit + refIndices[QUERY]); + } + + if (referenceUri.charAt(refIndices[PATH]) == '/') { + // The reference path is rooted. The target is the base scheme and authority (if any), plus + // the reference. + uri.append(baseUri, 0, baseIndices[PATH]).append(referenceUri); + return removeDotSegments(uri, baseIndices[PATH], baseIndices[PATH] + refIndices[QUERY]); + } + + // The target Uri is the concatenation of the base Uri up to (but excluding) the last segment, + // and the reference. This can be split into 2 cases: + if (baseIndices[SCHEME_COLON] + 2 < baseIndices[PATH] + && baseIndices[PATH] == baseIndices[QUERY]) { + // Case 1: The base hier-part is just the authority, with an empty path. An additional '/' is + // needed after the authority, before appending the reference. + uri.append(baseUri, 0, baseIndices[PATH]).append('/').append(referenceUri); + return removeDotSegments(uri, baseIndices[PATH], baseIndices[PATH] + refIndices[QUERY] + 1); + } else { + // Case 2: Otherwise, find the last '/' in the base hier-part and append the reference after + // it. If base hier-part has no '/', it could only mean that it is completely empty or + // contains only one segment, in which case the whole hier-part is excluded and the reference + // is appended right after the base scheme colon without an added '/'. + int lastSlashIndex = baseUri.lastIndexOf('/', baseIndices[QUERY] - 1); + int baseLimit = lastSlashIndex == -1 ? baseIndices[PATH] : lastSlashIndex + 1; + uri.append(baseUri, 0, baseLimit).append(referenceUri); + return removeDotSegments(uri, baseIndices[PATH], baseLimit + refIndices[QUERY]); + } + } + + /** + * Returns true if the URI is starting with a scheme component, false otherwise. + */ + public static boolean isAbsolute(String uri) { + return uri != null && getUriIndices(uri)[SCHEME_COLON] != -1; + } + + /** + * Removes query parameter from a URI, if present. + * + * @param uri The URI. + * @param queryParameterName The name of the query parameter. + * @return The URI without the query parameter. + */ + public static Uri removeQueryParameter(Uri uri, String queryParameterName) { + Uri.Builder builder = uri.buildUpon(); + builder.clearQuery(); + for (String key : uri.getQueryParameterNames()) { + if (!key.equals(queryParameterName)) { + for (String value : uri.getQueryParameters(key)) { + builder.appendQueryParameter(key, value); + } + } + } + return builder.build(); + } + + /** + * Removes dot segments from the path of a URI. + * + * @param uri A {@link StringBuilder} containing the URI. + * @param offset The index of the start of the path in {@code uri}. + * @param limit The limit (exclusive) of the path in {@code uri}. + */ + private static String removeDotSegments(StringBuilder uri, int offset, int limit) { + if (offset >= limit) { + // Nothing to do. + return uri.toString(); + } + if (uri.charAt(offset) == '/') { + // If the path starts with a /, always retain it. + offset++; + } + // The first character of the current path segment. + int segmentStart = offset; + int i = offset; + while (i <= limit) { + int nextSegmentStart; + if (i == limit) { + nextSegmentStart = i; + } else if (uri.charAt(i) == '/') { + nextSegmentStart = i + 1; + } else { + i++; + continue; + } + // We've encountered the end of a segment or the end of the path. If the final segment was + // "." or "..", remove the appropriate segments of the path. + if (i == segmentStart + 1 && uri.charAt(segmentStart) == '.') { + // Given "abc/def/./ghi", remove "./" to get "abc/def/ghi". + uri.delete(segmentStart, nextSegmentStart); + limit -= nextSegmentStart - segmentStart; + i = segmentStart; + } else if (i == segmentStart + 2 + && uri.charAt(segmentStart) == '.' + && uri.charAt(segmentStart + 1) == '.') { + // Given "abc/def/../ghi", remove "def/../" to get "abc/ghi". + int prevSegmentStart = uri.lastIndexOf("/", segmentStart - 2) + 1; + int removeFrom = prevSegmentStart > offset ? prevSegmentStart : offset; + uri.delete(removeFrom, nextSegmentStart); + limit -= nextSegmentStart - removeFrom; + segmentStart = prevSegmentStart; + i = prevSegmentStart; + } else { + i++; + segmentStart = i; + } + } + return uri.toString(); + } + + /** + * Calculates indices of the constituent components of a URI. + * + * @param uriString The URI as a string. + * @return The corresponding indices. + */ + private static int[] getUriIndices(String uriString) { + int[] indices = new int[INDEX_COUNT]; + if (TextUtils.isEmpty(uriString)) { + indices[SCHEME_COLON] = -1; + return indices; + } + + // Determine outer structure from right to left. + // Uri = scheme ":" hier-part [ "?" query ] [ "#" fragment ] + int length = uriString.length(); + int fragmentIndex = uriString.indexOf('#'); + if (fragmentIndex == -1) { + fragmentIndex = length; + } + int queryIndex = uriString.indexOf('?'); + if (queryIndex == -1 || queryIndex > fragmentIndex) { + // '#' before '?': '?' is within the fragment. + queryIndex = fragmentIndex; + } + // Slashes are allowed only in hier-part so any colon after the first slash is part of the + // hier-part, not the scheme colon separator. + int schemeIndexLimit = uriString.indexOf('/'); + if (schemeIndexLimit == -1 || schemeIndexLimit > queryIndex) { + schemeIndexLimit = queryIndex; + } + int schemeIndex = uriString.indexOf(':'); + if (schemeIndex > schemeIndexLimit) { + // '/' before ':' + schemeIndex = -1; + } + + // Determine hier-part structure: hier-part = "//" authority path / path + // This block can also cope with schemeIndex == -1. + boolean hasAuthority = + schemeIndex + 2 < queryIndex + && uriString.charAt(schemeIndex + 1) == '/' + && uriString.charAt(schemeIndex + 2) == '/'; + int pathIndex; + if (hasAuthority) { + pathIndex = uriString.indexOf('/', schemeIndex + 3); // find first '/' after "://" + if (pathIndex == -1 || pathIndex > queryIndex) { + pathIndex = queryIndex; + } + } else { + pathIndex = schemeIndex + 1; + } + + indices[SCHEME_COLON] = schemeIndex; + indices[PATH] = pathIndex; + indices[QUERY] = queryIndex; + indices[FRAGMENT] = fragmentIndex; + return indices; + } +} diff --git a/settings.gradle b/settings.gradle index 197eb2a2cc..63bed6c56f 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,5 @@ rootProject.name = "TVBox" include ':app' -include ':player' \ No newline at end of file +include ':player' +include ':quickjs' +include ':pyramid' \ No newline at end of file