├── .github ├── fake │ └── safe_JsExtensions.kt ├── legado │ ├── ic_launcher_my.xml │ ├── legado.jks │ ├── legado.sign │ ├── rss_yck.json │ └── speedup.gradle └── workflows │ ├── legado.yml │ └── legado_custom.yml ├── .lastcheck ├── README.md ├── action_app_build.sh ├── action_app_custom.sh ├── action_clone.sh ├── action_legado_myself.sh ├── action_schedule.sh ├── action_setenv.sh ├── action_util.sh └── diy ├── diy_10bits.sh └── diy_test.sh /.github/fake/safe_JsExtensions.kt: -------------------------------------------------------------------------------- 1 | package io.legado.app.help 2 | 3 | import android.net.Uri 4 | import android.util.Base64 5 | import androidx.annotation.Keep 6 | import io.legado.app.App 7 | import io.legado.app.constant.AppConst.dateFormat 8 | import io.legado.app.help.http.CookieStore 9 | import io.legado.app.help.http.SSLHelper 10 | import io.legado.app.model.Debug 11 | import io.legado.app.model.analyzeRule.AnalyzeUrl 12 | import io.legado.app.model.analyzeRule.QueryTTF 13 | import io.legado.app.utils.* 14 | import kotlinx.coroutines.runBlocking 15 | import org.jsoup.Connection 16 | import org.jsoup.Jsoup 17 | import rxhttp.wrapper.param.RxHttp 18 | import rxhttp.wrapper.param.toByteArray 19 | import java.io.* 20 | import java.net.URL 21 | import java.net.URLEncoder 22 | import java.util.* 23 | import java.util.zip.GZIPInputStream 24 | import java.util.zip.GZIPOutputStream 25 | 26 | @Keep 27 | @Suppress("unused") 28 | interface JsExtensions { 29 | 30 | /** 31 | * 访问网络,返回String 32 | */ 33 | fun ajax(urlStr: String): String? { 34 | return runBlocking { 35 | kotlin.runCatching { 36 | val analyzeUrl = AnalyzeUrl(urlStr) 37 | analyzeUrl.getStrResponse(urlStr).body 38 | }.onFailure { 39 | it.printStackTrace() 40 | }.getOrElse { 41 | it.msg 42 | } 43 | } 44 | } 45 | 46 | fun ajaxCacheGet(url: String, saveTime: Int = 0): String { 47 | return runBlocking { 48 | var x = CacheManager.get(url) 49 | if (x == null) { 50 | x = RxHttp.get(url).toString() 51 | x.let { 52 | CacheManager.put(url, x, saveTime) 53 | } 54 | } 55 | return@runBlocking x 56 | } 57 | } 58 | 59 | /** 60 | * 访问网络,返回Response 61 | */ 62 | fun connect(urlStr: String): Any { 63 | return runBlocking { 64 | kotlin.runCatching { 65 | val analyzeUrl = AnalyzeUrl(urlStr) 66 | analyzeUrl.getStrResponse(urlStr) 67 | }.onFailure { 68 | it.printStackTrace() 69 | }.getOrElse { 70 | it.msg 71 | } 72 | } 73 | } 74 | 75 | 76 | /** 77 | * js实现重定向拦截,网络访问get 78 | */ 79 | fun get(urlStr: String, headers: Map): Connection.Response { 80 | return Jsoup.connect(urlStr) 81 | .sslSocketFactory(SSLHelper.unsafeSSLSocketFactory) 82 | .ignoreContentType(true) 83 | .followRedirects(false) 84 | .headers(headers) 85 | .method(Connection.Method.GET) 86 | .execute() 87 | } 88 | 89 | /** 90 | * 网络访问post 91 | */ 92 | fun post(urlStr: String, body: String, headers: Map): Connection.Response { 93 | return Jsoup.connect(urlStr) 94 | .sslSocketFactory(SSLHelper.unsafeSSLSocketFactory) 95 | .ignoreContentType(true) 96 | .followRedirects(false) 97 | .requestBody(body) 98 | .headers(headers) 99 | .method(Connection.Method.POST) 100 | .execute() 101 | } 102 | 103 | /** 104 | *js实现读取cookie 105 | */ 106 | fun getCookie(tag: String, key: String? = null): String { 107 | val cookie = CookieStore.getCookie(tag) 108 | val cookieMap = CookieStore.cookieToMap(cookie) 109 | return if (key != null) { 110 | cookieMap[key] ?: "" 111 | } else { 112 | cookie 113 | } 114 | } 115 | 116 | /** 117 | * js实现解码,不能删 118 | */ 119 | fun base64Decode(str: String): String { 120 | return EncoderUtils.base64Decode(str, Base64.NO_WRAP) 121 | } 122 | 123 | fun base64Decode(str: String, flags: Int): String { 124 | return EncoderUtils.base64Decode(str, flags) 125 | } 126 | 127 | fun base64DecodeToByteArray(str: String?): ByteArray? { 128 | if (str.isNullOrBlank()) { 129 | return null 130 | } 131 | return Base64.decode(str, Base64.DEFAULT) 132 | } 133 | 134 | fun base64DecodeToByteArray(str: String?, flags: Int): ByteArray? { 135 | if (str.isNullOrBlank()) { 136 | return null 137 | } 138 | return Base64.decode(str, flags) 139 | } 140 | 141 | fun base64Encode(str: String): String? { 142 | return EncoderUtils.base64Encode(str, Base64.NO_WRAP) 143 | } 144 | 145 | fun base64Encode(str: String, flags: Int): String? { 146 | return EncoderUtils.base64Encode(str, flags) 147 | } 148 | 149 | fun md5Encode(str: String): String { 150 | return MD5Utils.md5Encode(str) 151 | } 152 | 153 | fun md5Encode16(str: String): String { 154 | return MD5Utils.md5Encode16(str) 155 | } 156 | 157 | /** 158 | * 时间格式化 159 | */ 160 | fun timeFormat(time: Long): String { 161 | return dateFormat.format(Date(time)) 162 | } 163 | 164 | /** 165 | * utf8编码转gbk编码 166 | */ 167 | fun utf8ToGbk(str: String): String { 168 | val utf8 = String(str.toByteArray(charset("UTF-8"))) 169 | val unicode = String(utf8.toByteArray(), charset("UTF-8")) 170 | return String(unicode.toByteArray(charset("GBK"))) 171 | } 172 | 173 | fun getAbsUrl(baseUrl: String, src: String): String { 174 | return if (src.startsWith("http://") || src.startsWith("https://")) { 175 | src 176 | } else { 177 | val aURL = URL(baseUrl) 178 | val url = when (aURL.port) { 179 | -1, 80 -> aURL.protocol + "://" + aURL.host 180 | else -> aURL.protocol + "://" + aURL.host + ":" + aURL.port 181 | } 182 | url + src 183 | } 184 | } 185 | 186 | fun getUniqList(list: List): List { 187 | return list.distinct() 188 | } 189 | 190 | fun compress(primStr: String?): String? { 191 | if (primStr == null || primStr.isEmpty()) { 192 | return primStr 193 | } 194 | val out = ByteArrayOutputStream() 195 | var gzip: GZIPOutputStream? = null 196 | try { 197 | gzip = GZIPOutputStream(out) 198 | gzip.write(primStr.toByteArray()) 199 | } catch (e: IOException) { 200 | e.printStackTrace() 201 | } finally { 202 | if (gzip != null) { 203 | try { 204 | gzip.close() 205 | } catch (e: IOException) { 206 | e.printStackTrace() 207 | } 208 | } 209 | } 210 | return Base64.encodeToString(out.toByteArray(), Base64.NO_WRAP) 211 | } 212 | 213 | fun uncompress(compressedStr: String?): String? { 214 | if (compressedStr == null) { 215 | return null 216 | } 217 | val out = ByteArrayOutputStream() 218 | var input: ByteArrayInputStream? = null 219 | var ginzip: GZIPInputStream? = null 220 | var compressed: ByteArray? = null 221 | var decompressed: String? = null 222 | try { 223 | compressed = Base64.decode(compressedStr, Base64.NO_WRAP) 224 | input = ByteArrayInputStream(compressed) 225 | ginzip = GZIPInputStream(input) 226 | val buffer = ByteArray(1024) 227 | var offset = -1 228 | while (ginzip.read(buffer).also { offset = it } != -1) { 229 | out.write(buffer, 0, offset) 230 | } 231 | decompressed = out.toString() 232 | } catch (e: IOException) { 233 | e.printStackTrace() 234 | } finally { 235 | if (ginzip != null) { 236 | try { 237 | ginzip.close() 238 | } catch (e: IOException) { 239 | } 240 | } 241 | if (input != null) { 242 | try { 243 | input.close() 244 | } catch (e: IOException) { 245 | } 246 | } 247 | try { 248 | out.close() 249 | } catch (e: IOException) { 250 | } 251 | } 252 | return decompressed 253 | } 254 | 255 | fun encodeURI(str: String): String { 256 | return try { 257 | URLEncoder.encode(str, "UTF-8") 258 | } catch (e: Exception) { 259 | "" 260 | } 261 | } 262 | 263 | fun encodeURI(str: String, enc: String): String { 264 | return try { 265 | URLEncoder.encode(str, enc) 266 | } catch (e: Exception) { 267 | "" 268 | } 269 | } 270 | 271 | fun htmlFormat(str: String): String { 272 | return str.htmlFormat() 273 | } 274 | 275 | 276 | /** 277 | * 解析字体,返回字体解析类 278 | */ 279 | fun queryBase64TTF(base64: String?): QueryTTF? { 280 | base64DecodeToByteArray(base64)?.let { 281 | return QueryTTF(it) 282 | } 283 | return null 284 | } 285 | 286 | fun queryTTF(str: String?): QueryTTF? { 287 | str ?: return null 288 | val key = md5Encode16(str) 289 | var qTTF = CacheManager.getQueryTTF(key) 290 | if (qTTF != null) return qTTF 291 | val font: ByteArray? = when { 292 | str.isAbsUrl() -> runBlocking { 293 | var x = CacheManager.getByteArray(key) 294 | if (x == null) { 295 | x = RxHttp.get(str).toByteArray().await() 296 | x.let { 297 | CacheManager.put(key, it) 298 | } 299 | } 300 | return@runBlocking x 301 | } 302 | str.isContentScheme() -> Uri.parse(str).readBytes(App.INSTANCE) 303 | str.startsWith("/storage") -> File(str).readBytes() 304 | else -> base64DecodeToByteArray(str) 305 | } 306 | font ?: return null 307 | qTTF = QueryTTF(font) 308 | CacheManager.put(key, qTTF) 309 | return qTTF 310 | } 311 | 312 | fun replaceFont( 313 | text: String, 314 | font1: QueryTTF?, 315 | font2: QueryTTF? 316 | ): String { 317 | if (font1 == null || font2 == null) return text 318 | val contentArray = text.toCharArray() 319 | contentArray.forEachIndexed { index, s -> 320 | val oldCode = s.toInt() 321 | if (font1.inLimit(s)) { 322 | val code = font2.getCodeByGlyf(font1.getGlyfByCode(oldCode)) 323 | if (code != 0) contentArray[index] = code.toChar() 324 | } 325 | } 326 | return contentArray.joinToString("") 327 | } 328 | 329 | /** 330 | * 输出调试日志 331 | */ 332 | fun log(msg: String) { 333 | Debug.log(msg) 334 | } 335 | } 336 | -------------------------------------------------------------------------------- /.github/legado/ic_launcher_my.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 9 | 13 | 17 | 21 | 25 | 29 | 33 | 36 | 40 | 43 | 47 | 48 | -------------------------------------------------------------------------------- /.github/legado/legado.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/10bits/gedoor-Build/eeb40c57bf86a30a28a3ed45b8d585409345198f/.github/legado/legado.jks -------------------------------------------------------------------------------- /.github/legado/legado.sign: -------------------------------------------------------------------------------- 1 | RELEASE_STORE_FILE=./legado.jks 2 | RELEASE_KEY_ALIAS=legado 3 | RELEASE_STORE_PASSWORD=gedoor_legado 4 | RELEASE_KEY_PASSWORD=gedoor_legado 5 | -------------------------------------------------------------------------------- /.github/legado/rss_yck.json: -------------------------------------------------------------------------------- 1 | { 2 | "customOrder": 5, 3 | "enableJs": true, 4 | "enabled": true, 5 | "singleUrl": true, 6 | "sourceGroup": "legado", 7 | "sourceIcon": "https://www.yckceo.com/favicon.ico", 8 | "sourceName": "源仓库", 9 | "sourceUrl": "https://www.yckceo.com" 10 | } 11 | -------------------------------------------------------------------------------- /.github/legado/speedup.gradle: -------------------------------------------------------------------------------- 1 | dexOptions { 2 | //使用增量模式构建 3 | //incremental true 4 | //最大堆内存 5 | javaMaxHeapSize "8g" 6 | //是否支持大工程模式 7 | jumboMode = true 8 | //预编译 9 | preDexLibraries = true 10 | maxProcessCount = 8 11 | //线程数 12 | threadCount = 8 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/legado.yml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | on: 4 | schedule: 5 | - cron: '0 */12 * * *' 6 | workflow_dispatch: 7 | 8 | jobs: 9 | check_release: 10 | if: github.repository_owner == github.actor 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: check latest tag 15 | shell: bash 16 | env: 17 | SECRETS_MINIFY: ${{ secrets.MINIFY }} 18 | SECRETS_RENAME: ${{ secrets.RENAME }} 19 | run: source $GITHUB_WORKSPACE/action_schedule.sh 20 | 21 | - name: check if release 22 | id: check_release 23 | shell: bash 24 | run: | 25 | function version_gt() { test "$(echo "$@" | tr " " "\n" | sort -V | head -n 1)" != "$1"; } 26 | if version_gt $APP_LATEST_TAG $APP_LAST_TAG; then 27 | git clone $APP_GIT_URL $APP_WORKSPACE 28 | cd $APP_WORKSPACE 29 | git checkout $LATEST_TAG 30 | echo "if_release=true" >>$GITHUB_OUTPUT 31 | fi 32 | 33 | - name: setup JDK 17 34 | if: steps.check_release.outputs.if_release == 'true' 35 | uses: actions/setup-java@v3 36 | with: 37 | distribution: 'temurin' 38 | java-version: 17 39 | 40 | - name: setup Gradle 41 | if: steps.check_release.outputs.if_release == 'true' 42 | uses: gradle/gradle-build-action@v2.4.2 43 | 44 | - name: build apk 45 | id: build_apk 46 | if: steps.check_release.outputs.if_release == 'true' 47 | shell: bash 48 | run: | 49 | source $GITHUB_WORKSPACE/action_app_custom.sh 50 | source $GITHUB_WORKSPACE/action_app_build.sh 51 | app_build 52 | 53 | - name: release apk 54 | if: steps.check_release.outputs.if_release == 'true' 55 | id: release_apk 56 | shell: bash 57 | run: | 58 | function set_env() { echo "$1=$2" >> $GITHUB_ENV; } 59 | if [ -f $APP_BUILD_APK ]; then 60 | echo "if_publish=true" >>$GITHUB_OUTPUT 61 | set_env APP_RELEASE_NAME $APP_UPLOAD_NAME.apk 62 | set_env APP_RELEASE $APP_BUILD_APK 63 | fi 64 | 65 | - name: create release 66 | id: create_release 67 | if: steps.release_apk.outputs.if_publish == 'true' 68 | uses: actions/create-release@v1 69 | env: 70 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 71 | with: 72 | tag_name: ${{ env.APP_UPLOAD_NAME }} 73 | release_name: ${{ env.APP_UPLOAD_NAME }} 74 | body_path: ${{ env.APP_LATEST_BODY }} 75 | draft: false 76 | prerelease: false 77 | 78 | - name: upload release asset 79 | id: upload-release-asset 80 | if: steps.release_apk.outputs.if_publish == 'true' 81 | uses: actions/upload-release-asset@v1 82 | env: 83 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 84 | with: 85 | upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps 86 | asset_path: ${{ env.APP_RELEASE }} 87 | asset_name: ${{ env.APP_RELEASE_NAME }} 88 | asset_content_type: application/vnd.android.package-archive 89 | 90 | - name: update info 91 | if: steps.upload-release-asset.outputs.browser_download_url != null 92 | shell: bash 93 | env: 94 | APP_DOWNLOAD: ${{ steps.upload-release-asset.outputs.browser_download_url }} 95 | run: | 96 | source $GITHUB_WORKSPACE/action_util.sh 97 | update_info 98 | 99 | -------------------------------------------------------------------------------- /.github/workflows/legado_custom.yml: -------------------------------------------------------------------------------- 1 | name: LegadoCustom 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | git_url: 7 | description: 'Your legado git url' 8 | required: true 9 | default: 'https://github.com/gedoor/legado.git' 10 | git_tag: 11 | description: 'Your legado git tag' 12 | required: true 13 | default: '3.23.110211' 14 | app_channel: 15 | description: 'Your legado build channel(App)' 16 | required: true 17 | default: 'App' 18 | app_custom: 19 | description: 'Your legado custom script' 20 | required: true 21 | default: 'diy_test.sh' 22 | app_name: 23 | description: 'Legado app name' 24 | required: true 25 | default: '阅读' 26 | app_suffix: 27 | description: 'Legado app suffix' 28 | required: true 29 | default: 'A' 30 | app_launch_name: 31 | description: 'Legado app launch name' 32 | required: true 33 | default: '阅读.A' 34 | webdav_upload: 35 | description: 'Webdav upload when build apk finish' 36 | required: true 37 | default: 'false' 38 | webdav_url: 39 | description: 'Webdav upload url' 40 | required: true 41 | default: 'https://dav.jianguoyun.com/dav/' 42 | webdav_auth: 43 | description: 'Webdav auth user and password' 44 | required: true 45 | default: 'test@test.com:test_12345' 46 | 47 | jobs: 48 | build_apk: 49 | if: github.repository_owner == github.actor 50 | runs-on: ubuntu-latest 51 | steps: 52 | - uses: actions/checkout@v2 53 | 54 | - name: setup JDK 17 55 | uses: actions/setup-java@v3 56 | with: 57 | distribution: 'temurin' 58 | java-version: 17 59 | 60 | - name: setup Gradle 61 | uses: gradle/gradle-build-action@v2.4.2 62 | 63 | - name: set env 64 | shell: bash 65 | env: 66 | SECRETS_APP_NAME: ${{ github.event.inputs.app_name }} 67 | SECRETS_GIT_URL: ${{ github.event.inputs.git_url }} 68 | SECRETS_CHANNEL: ${{ github.event.inputs.app_channel }} 69 | SECRETS_SUFFIX: ${{ github.event.inputs.app_suffix }} 70 | SECRETS_ENABLE: ${{ secrets.ENABLE }} 71 | SECRETS_MINIFY: ${{ secrets.MINIFY }} 72 | SECRETS_RENAME: ${{ secrets.RENAME }} 73 | REPO_OWNER: ${{ github.repository_owner }} 74 | REPO_ACTOR: ${{ github.actor }} 75 | SECRETS_TAG: ${{ github.event.inputs.git_tag }} 76 | SECRETS_LAUNCH_NAME: ${{ github.event.inputs.app_launch_name }} 77 | run: source $GITHUB_WORKSPACE/action_setenv.sh 78 | 79 | - name: clone code 80 | shell: bash 81 | run: source $GITHUB_WORKSPACE/action_clone.sh 82 | 83 | - name: custom your app 84 | shell: bash 85 | env: 86 | custom: ${{ github.event.inputs.app_custom }} 87 | run: | 88 | source $GITHUB_WORKSPACE/diy/$custom 89 | 90 | - name: build your app 91 | if: success() 92 | shell: bash 93 | run: | 94 | source $GITHUB_WORKSPACE/action_app_build.sh 95 | app_build 96 | 97 | - name: upload artifact 98 | uses: actions/upload-artifact@v4 99 | if: success() 100 | with: 101 | name: ${{ env.APP_UPLOAD_NAME }} 102 | path: ${{ env.APP_UPLOAD }} 103 | 104 | - name: upload to webdav 105 | if: success() 106 | shell: bash 107 | env: 108 | WEBDAV_URL: ${{ github.event.inputs.webdav_url }} 109 | WEBDAV_AUTH: ${{ github.event.inputs.webdav_auth }} 110 | WEBDAV_UPLOAD: ${{ github.event.inputs.webdav_upload }} 111 | run: | 112 | source $GITHUB_WORKSPACE/action_util.sh 113 | webdav_upload $APP_UPLOAD/*.apk 114 | 115 | -------------------------------------------------------------------------------- /.lastcheck: -------------------------------------------------------------------------------- 1 | 3.23.110211 2 | 3.23.110115 3 | 3.23.103114 4 | 3.23.051919 5 | 3.23.051822 6 | 3.23.051323 7 | 3.23.050309 8 | 3.23.040420 9 | 3.23.040118 10 | 3.23.032923 11 | 3.23.032820 12 | 3.23.032123 13 | 3.23.032112 14 | 3.23.032021 15 | 3.23.031520 16 | 3.23.031420 17 | 3.23.031314 18 | 3.23.031210 19 | 3.23.030400 20 | 3.23.022715 21 | 3.23.022621 22 | 3.23.022219 23 | 3.23.021621 24 | 3.23.020921 25 | 3.23.013123 26 | 3.23.012922 27 | 3.23.012018 28 | 3.23.011918 29 | 3.23.011022 30 | 3.23.010719 31 | 3.22.122419 32 | 3.22.121521 33 | 3.22.121018 34 | 3.22.120121 35 | 3.22.112222 36 | 3.22.111423 37 | 3.22.110823 38 | 3.22.102918 39 | 3.22.102223 40 | 3.22.102020 41 | 3.22.101712 42 | 3.22.101709 43 | 3.22.101619 44 | 3.22.101421 45 | 3.22.100921 46 | 3.22.100521 47 | 3.22.100210 48 | 3.22.100120 49 | 3.22.092618 50 | 3.22.092400 51 | 3.22.092220 52 | 3.22.092020 53 | 3.22.091920 54 | 3.22.091719 55 | 3.22.091712 56 | 3.22.091613 57 | 3.22.091520 58 | 3.22.083120 59 | 3.22.082409 60 | 3.22.081920 61 | 3.22.080322 62 | 3.22.072122 63 | 3.22.071618 64 | 3.22.071010 65 | 3.22.070921 66 | 3.22.070722 67 | 3.22.062320 68 | 3.22.061722 69 | 3.22.061022 70 | 3.22.060120 71 | 3.22.052723 72 | 3.22.052708 73 | 3.22.052621 74 | 3.22.052113 75 | 3.22.051719 76 | 3.22.051612 77 | 3.22.051215 78 | 3.22.051021 79 | 3.22.050215 80 | 3.22.050122 81 | 3.22.042323 82 | 3.22.042309 83 | 3.22.042218 84 | 3.22.041820 85 | 3.22.041421 86 | 3.22.041418 87 | 3.22.041209 88 | 3.22.040716 89 | 3.22.040517 90 | 3.22.033020 91 | 3.22.032521 92 | 3.22.032320 93 | 3.22.031722 94 | 3.22.031519 95 | 3.22.031209 96 | 3.22.030919 97 | 3.22.030711 98 | 3.22.030410 99 | 3.22.030322 100 | 3.22.022822 101 | 3.22.022814 102 | 3.22.022719 103 | 3.22.022623 104 | 3.22.022509 105 | 3.22.022219 106 | 3.22.021714 107 | 3.22.021120 108 | 3.22.021118 109 | 3.22.021109 110 | 3.22.020919 111 | 3.22.020311 112 | 3.22.012821 113 | 3.22.012619 114 | 3.22.012019 115 | 3.22.011116 116 | 3.22.011108 117 | 3.22.011020 118 | 3.22.010615 119 | 3.22.010310 120 | 3.22.010111 121 | 3.21.122920 122 | 3.21.121021 123 | 3.21.112719 124 | 3.21.112020 125 | 3.21.111418 126 | 3.21.110219 127 | 3.21.102414 128 | 3.21.102210 129 | 3.21.102122 130 | 3.21.101715 131 | 3.21.101610 132 | 3.21.101420 133 | 3.21.101221 134 | 3.21.101217 135 | 3.21.100821 136 | 3.21.100623 137 | 3.21.100223 138 | 3.21.100206 139 | 3.21.100121 140 | 3.21.092920 141 | 3.21.092817 142 | 3.21.092709 143 | 3.21.092208 144 | 3.21.092121 145 | 3.21.092013 146 | 3.21.091816 147 | 3.21.091611 148 | 3.21.091512 149 | 3.21.091421 150 | 3.21.090920 151 | 3.21.090919 152 | 3.21.090617 153 | 3.21.090117 154 | 3.21.082719 155 | 3.21.082519 156 | 3.21.082409 157 | 3.21.082311 158 | 3.21.082212 159 | 3.21.081308 160 | 3.21.081020 161 | 3.21.081008 162 | 3.21.080816 163 | 3.21.080316 164 | 3.21.080211 165 | 3.21.080116 166 | 3.21.080111 167 | 3.21.072722 168 | 3.21.072522 169 | 3.21.072501 170 | 3.21.072317 171 | 3.21.071723 172 | 3.21.071520 173 | 3.21.071314 174 | 3.21.071017 175 | 3.21.070914 176 | 3.21.070820 177 | 3.21.070815 178 | 3.21.062920 179 | 3.21.062909 180 | 3.21.062820 181 | 3.21.060618 182 | 3.21.052622 183 | 3.21.052422 184 | 3.21.052320 185 | 3.21.051618 186 | 3.21.051220 187 | 3.21.051120 188 | 3.21.051020 189 | 3.21.050918 190 | 3.21.050816 191 | 3.21.050808 192 | 3.21.050610 193 | 3.21.043022 194 | 3.21.041610 195 | 3.21.041415 196 | 3.21.040820 197 | 3.21.040323 198 | 3.21.033111 199 | 3.21.032520 200 | 3.21.031720 201 | 3.21.031511 202 | 3.21.030812 203 | 3.21.022720 204 | 3.21.022715 205 | 3.21.022121 206 | 3.21.021708 207 | 3.21.021012 208 | 3.21.020316 209 | 3.21.013116 210 | 3.21.012611 211 | 3.21.012120 212 | 3.21.011819 213 | 3.21.011221 214 | 3.21.010520 215 | 3.21.010510 216 | 3.21.010321 217 | 3.20.123022 218 | 3.20.122715 219 | 3.20.121923 220 | 3.20.121712 221 | 3.20.121618 222 | 3.20.121611 223 | 3.20.121519 224 | 3.20.121417 225 | 3.20.121311 226 | 3.20.121219 227 | 3.20.121112 228 | 3.20.121020 229 | 3.20.120609 230 | 3.20.120412 231 | 3.20.113015 232 | 3.20.112422 233 | 3.20.111514 234 | 3.20.111513 235 | 3.20.111113 236 | 3.20.110715 237 | 3.20.110116 238 | 3.20.103020 239 | 3.20.102815 240 | 3.20.102719 241 | 3.20.102509 242 | 3.20.102119 243 | 3.20.101908 244 | 3.20.101808 245 | 3.20.101722 246 | 3.20.101616 247 | 3.20.101421 248 | 3.20.101209 249 | 3.20.100714 250 | 3.20.100616 251 | 3.20.100216 252 | 3.20.093008 253 | 3.20.092409 254 | 3.20.092308 255 | 3.20.092300 256 | 3.20.092114 257 | 3.20.092019 258 | 3.20.091509 259 | 3.20.091317 260 | 3.20.090913 261 | 3.20.090813 262 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## legado阅读3.0自动构建[![Android CI](https://github.com/10bits/gedoor-Build/workflows/Android%20CI/badge.svg)](https://github.com/10bits/gedoor-Build/actions) 2 | 3 | 默认从最新发布的tag构建,每次构建会自动清空18PlusList.txt 4 | 5 | > 最新构建下载:[legado-3.23.110211.apk](https://github.com/10bits/gedoor-Build/releases/download/legado-3.23.110211/legado-3.23.110211.apk) 上次构建时间:2023-11-02 20:10:38 6 | 7 | > **2023/11/01** 8 | > 9 | > * 修复一些bug 10 | 11 | 12 | 使用app过程中遇到问题,请到这里解决[gedoor/legado](https://github.com/gedoor/legado/issues) 13 | 14 | -------------------------------------------------------------------------------- /action_app_build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | source $GITHUB_WORKSPACE/action_util.sh 3 | #开始构建 4 | function app_build() 5 | { 6 | debug "build with gradle" 7 | cd $APP_WORKSPACE 8 | cat gradle.properties 9 | chmod +x gradlew 10 | ./gradlew assemble${APP_CHANNEL}Release --build-cache --parallel --daemon --warning-mode all 11 | 12 | APP_BUILD_APK=$(find $APP_WORKSPACE/app/build/outputs -name "*.apk") 13 | debug "build apk $APP_BUILD_APK" 14 | if [ -f $APP_BUILD_APK ]; then 15 | set_env APP_BUILD_APK $APP_BUILD_APK 16 | set_env APP_UPLOAD ${APP_BUILD_APK%/*} 17 | debug "upload apk dir ${APP_BUILD_APK%/*}" 18 | fi 19 | 20 | } 21 | -------------------------------------------------------------------------------- /action_app_custom.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #本脚本用来个性化定制app,不会修改任何程序代码 3 | source $GITHUB_WORKSPACE/action_util.sh 4 | #去除河蟹,默认启用 5 | function app_clear_18plus() 6 | { 7 | if [[ "$APP_NAME" == "legado" ]]; then 8 | debug "清空18PlusList.txt" 9 | echo "">$APP_WORKSPACE/app/src/main/assets/18PlusList.txt 10 | fi 11 | } 12 | 13 | #修改桌面阅读名为阅读.A,安装多个阅读时候方便识别,默认启用 14 | function app_rename() 15 | { 16 | if [[ "$APP_NAME" == "legado" ]] && [[ "$SECRETS_RENAME" == "true" ]]; then 17 | debug "更改桌面启动名称" 18 | sed 's/"app_name">阅读/"app_name">'"$APP_LAUNCH_NAME"'/' \ 19 | $APP_WORKSPACE/app/src/main/res/values-zh/strings.xml -i 20 | debug "更改webdav备份目录legado为legado+后缀名" 21 | find $APP_WORKSPACE/app/src -regex '.*/storage/.*.kt' -exec \ 22 | sed "s/\${url}legado/&$APP_SUFFIX/" {} -i \; 23 | fi 24 | } 25 | 26 | #删除一些用不到的资源 27 | function app_resources_unuse() 28 | { 29 | if [[ "$APP_NAME" == "legado" ]]; then 30 | debug "删除一些用不到的资源" 31 | rm $APP_WORKSPACE/app/src/main/assets/bg -rf 32 | fi 33 | } 34 | 35 | #最小化生成apk体积 36 | function app_minify() 37 | { 38 | if [[ "$APP_NAME" == "legado" ]]; then 39 | debug "缩小apk体积" 40 | sed -e '/minifyEnabled/i\ shrinkResources true' \ 41 | -e 's/minifyEnabled false/minifyEnabled true/' \ 42 | $APP_WORKSPACE/app/build.gradle -i 43 | fi 44 | } 45 | 46 | #和已有阅读共存,默认启用 47 | function app_live_together() 48 | { 49 | if [[ "$APP_NAME" == "legado" ]]; then 50 | debug "解决安装程序共存问题" 51 | sed "s/'.release'/'.release$APP_SUFFIX'/" \ 52 | $APP_WORKSPACE/app/build.gradle -i 53 | sed "s/.release/.release$APP_SUFFIX/" \ 54 | $APP_WORKSPACE/app/google-services.json -i 55 | fi 56 | } 57 | 58 | #apk增加签名,默认启用 59 | function app_sign() 60 | { 61 | debug "给apk增加签名" 62 | cp $GITHUB_WORKSPACE/.github/legado/legado.jks \ 63 | $APP_WORKSPACE/app/legado.jks 64 | sed '$r '"$GITHUB_WORKSPACE/.github/legado/legado.sign"'' \ 65 | $APP_WORKSPACE/gradle.properties -i 66 | } 67 | 68 | #禁用一些库 69 | function app_not_apply_plugin() 70 | { 71 | if [[ "$APP_NAME" == "MyBookshelf" ]]; then 72 | debug "删除google services相关" 73 | sed -e '/io.fabric/d' \ 74 | -e '/com.google.firebase/d' \ 75 | -e '/com.google.gms/d' \ 76 | $APP_WORKSPACE/app/build.gradle -i 77 | fi 78 | } 79 | 80 | #签名 81 | app_sign; 82 | 83 | #是否启用资源压缩,默认不压缩 84 | [[ "$SECRETS_MINIFY" == "true" ]] && app_minify; 85 | 86 | #阅读3.0 87 | app_clear_18plus; 88 | app_rename; 89 | app_live_together; 90 | -------------------------------------------------------------------------------- /action_clone.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #本脚本用来clone远端仓库 3 | source $GITHUB_WORKSPACE/action_util.sh 4 | #建立工作目录 5 | function init_workspace() 6 | { 7 | git clone $APP_GIT_URL $APP_WORKSPACE 8 | cd $APP_WORKSPACE 9 | LatestTag=$(git describe --tags `git rev-list --tags --max-count=1`) 10 | if [[ -n "$SECRETS_TAG" ]] && [[ "$REPO_ACTOR" = "$REPO_OWNER" ]]; then 11 | [[ "$SECRETS_TAG" = "master" ]] && LatestTag="master" 12 | if [[ "$APP_NAME" = "legado" ]]; then 13 | if [[ -n "$(echo $SECRETS_TAG|grep -o '3\.[0-9]\{2\}\.[0-9]\{6\}')" ]]; then 14 | LatestTag=$SECRETS_TAG 15 | fi 16 | elif [[ "$APP_NAME" = "MyBookshelf" ]]; then 17 | if [[ -n "$(echo $SECRETS_TAG|grep -o '2\.[0-9]\{2\}\.[0-9]\{6\}')" ]]; then 18 | LatestTag=$SECRETS_TAG 19 | fi 20 | else 21 | [[ "$SECRETS_ENABLE" = "true" ]] && LatestTag=$SECRETS_TAG 22 | fi 23 | fi 24 | git checkout $LatestTag 25 | set_env APP_UPLOAD_NAME $APP_NAME-$LatestTag 26 | [[ "$APP_NAME" = "legado" ]] && \ 27 | set_env APP_TAG $(echo $LatestTag|grep -o '3\.[0-9]\{2\}\.[0-9]\{6\}') 28 | debug "$APP_NAME latest tag is $LatestTag" 29 | } 30 | init_workspace; 31 | 32 | -------------------------------------------------------------------------------- /action_legado_myself.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | source $GITHUB_WORKSPACE/action_util.sh 3 | #阅读3.0自用定制脚本 4 | function build_gradle_setting() 5 | { 6 | debug "maven中央仓库回归" 7 | sed "/google()/i\ mavenCentral()" $APP_WORKSPACE/build.gradle -i 8 | 9 | debug "Speed Up Gradle" 10 | sed -e '/android {/r '"$GITHUB_WORKSPACE/.github/legado/speedup.gradle"'' \ 11 | -e '/kapt {/a\ useBuildCache = true' \ 12 | -e '/minSdkVersion/c\ minSdkVersion 26' \ 13 | $APP_WORKSPACE/app/build.gradle -i 14 | } 15 | 16 | function bookshelfAdd_no_alert() 17 | { 18 | debug "关闭加入书架提示" 19 | find $APP_WORKSPACE/app/src -regex '.*/ReadBookActivity.kt' -exec \ 20 | sed -e '/fun finish()/,/fun onDestroy()/{s/alert/\/*&/;s/show()/&*\//}' \ 21 | -e '/!ReadBook.inBookshelf/a\viewModel.removeFromBookshelf{ super.finish() }' \ 22 | {} -i \; 23 | } 24 | 25 | function exploreShow_be_better() 26 | { 27 | debug "发现书籍界面优化" 28 | find $APP_WORKSPACE/app/src -regex '.*/ExploreShowActivity.kt' -exec \ 29 | sed -e "/loadMoreView.error(it)/i\isLoading = false" \ 30 | -e "/ExploreShowActivity/i\import io.legado.app.utils.longToastOnUi" \ 31 | -e '/loadMoreView.error(it)/i\longToastOnUi(it)' \ 32 | -e 's/loadMoreView.error(it)/loadMoreView.error("目标网站连接失败或超时")/' \ 33 | {} -i \; 34 | find $APP_WORKSPACE/app/src -regex '.*/ExploreShowViewModel.kt' -exec \ 35 | sed "s/30000L/8000L/" {} -i \; 36 | } 37 | 38 | function explore_can_search() 39 | { 40 | debug "发现界面支持搜索书籍" 41 | find $APP_WORKSPACE/app/src -regex '.*/ExploreFragment.kt' -exec \ 42 | sed -e 's/getString(R.string.screen_find)/"搜索书籍、书源"/' \ 43 | -e '/fun initSearchView()/i\override fun onResume(){super.onResume();searchView.clearFocus()}' \ 44 | -e '/ExploreFragment/i\import io.legado.app.ui.book.search.SearchActivity' \ 45 | -e '/onQueryTextSubmit/a\if(!query?.contains("group:")!!){startActivity { putExtra("key", query) }}' \ 46 | {} -i \; 47 | } 48 | 49 | function rhino_safe_js() 50 | { 51 | debug "safe JsExtensions.kt" 52 | if version_ge "$APP_TAG" "3.21.021012"; then 53 | sed -e '/^import io.legado.app.App$/c\import splitties.init.appCtx' \ 54 | -e 's/(App.INSTANCE)/(appCtx)/' \ 55 | $GITHUB_WORKSPACE/.github/fake/safe_JsExtensions.kt -i 56 | fi 57 | if version_ge "$APP_TAG" "3.21.031511"; then 58 | sed "s/str.htmlFormat()/HtmlFormatter.formatKeepImg(str)/" \ 59 | $GITHUB_WORKSPACE/.github/fake/safe_JsExtensions.kt -i 60 | fi 61 | find $APP_WORKSPACE/app/src -type d -regex '.*/app/help' -exec \ 62 | cp $GITHUB_WORKSPACE/.github/fake/safe_JsExtensions.kt {}/JsExtensions.kt \; 63 | 64 | debug "开启Rhino安全沙箱,移步https://github.com/10bits/rhino-android" 65 | sed "/gedoor:rhino-android/c\ implementation 'com.github.10bits:rhino-android:1.6'" \ 66 | $APP_WORKSPACE/app/build.gradle -i 67 | } 68 | 69 | function no_google_services() 70 | { 71 | debug "删除google services相关" 72 | sed -e "/com.google.firebase/d" \ 73 | -e "/com.google.gms/d" \ 74 | -e "/androidx.appcompat/a\ implementation 'androidx.documentfile:documentfile:1.0.1'" \ 75 | $APP_WORKSPACE/app/build.gradle -i 76 | } 77 | 78 | function my_launcher_icon(){ 79 | debug "替换图标" 80 | find $APP_WORKSPACE/app/src -type d -regex '.*/res/drawable' -exec \ 81 | cp $GITHUB_WORKSPACE/.github/legado/ic_launcher_my.xml {}/ic_launcher1.xml \; 82 | 83 | find $APP_WORKSPACE/app/src -regex '.*/res/.*/ic_launcher.xml' -exec \ 84 | sed "/background/d" {} -i \; 85 | } 86 | 87 | function quick_checkSource(){ 88 | debug "快速校验书源" 89 | find $APP_WORKSPACE/app/src -regex '.*/service/CheckSourceService.kt' -exec \ 90 | sed -e "/getBookInfoAwait/i\/*" \ 91 | -e "/timeout(/i\*/" \ 92 | -e '/exploreBookAwait/a\if(books.isEmpty()){throw Exception("发现书籍为空")}' \ 93 | {} -i \; 94 | } 95 | 96 | if [[ "$APP_NAME" == "legado" ]] && [[ "$REPO_ACTOR" == "10bits" ]]; then 97 | exploreShow_be_better; 98 | #bookshelfAdd_no_alert; 99 | build_gradle_setting; 100 | explore_can_search; 101 | no_google_services; 102 | #rhino_safe_js; 103 | my_launcher_icon; 104 | #quick_checkSource; 105 | fi 106 | -------------------------------------------------------------------------------- /action_schedule.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #本脚本会定时更新README.md里的最新tag显示 3 | 4 | function set_env() { echo "$1=$2" >> $GITHUB_ENV; } 5 | 6 | APP_NAME="legado" 7 | APP_GIT_URL="https://github.com/gedoor/legado.git" 8 | APP_SUFFIX="A" 9 | APP_WORKSPACE="/opt/$APP_NAME" 10 | APP_UPLOAD="$APP_WORKSPACE/app/build/outputs/apk/app/release" 11 | APP_LAUNCH_NAME="阅读.$APP_SUFFIX" 12 | 13 | GITHUB_API_LATEST="https://api.github.com/repos/gedoor/legado/releases/latest" 14 | 15 | set_env APP_NAME $APP_NAME 16 | set_env APP_GIT_URL $APP_GIT_URL 17 | set_env APP_LAUNCH_NAME $APP_LAUNCH_NAME 18 | set_env APP_WORKSPACE $APP_WORKSPACE 19 | set_env APP_SUFFIX $APP_SUFFIX 20 | set_env APP_UPLOAD $APP_UPLOAD 21 | set_env SECRETS_MINIFY $SECRETS_MINIFY 22 | set_env SECRETS_RENAME $SECRETS_RENAME 23 | 24 | LatestTag=$(curl -s $GITHUB_API_LATEST|jq .tag_name -r) 25 | LatestCheck=$(date -u -d"+8 hour" "+%Y-%m-%d %H:%M:%S") 26 | 27 | curl -s $GITHUB_API_LATEST|jq .body -r>/opt/latest.md 28 | 29 | set_env LATEST_TAG $LatestTag 30 | set_env APP_LATEST_TAG $(echo $LatestTag|grep -o '3\.[0-9]\{2\}\.[0-9]\{6\}') 31 | set_env APP_LATEST_BODY "/opt/latest.md" 32 | set_env APP_LATEST_CHECK "$LatestCheck" 33 | set_env APP_UPLOAD_NAME $APP_NAME-$LatestTag 34 | set_env APP_LAST_TAG $(cat $GITHUB_WORKSPACE/.lastcheck|sed -n 1p) 35 | -------------------------------------------------------------------------------- /action_setenv.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | function set_env() { echo "$1=$2" >> $GITHUB_ENV; } 4 | 5 | APP_NAME="legado" 6 | APP_GIT_URL="https://github.com/gedoor/legado.git" 7 | APP_SUFFIX="A" 8 | APP_CHANNEL="App" 9 | if [ "$SECRETS_ENABLE" == "true" ] && [ -n "$SECRETS_APP_NAME" ] && [ -n "$SECRETS_GIT_URL" ] && [ "$REPO_ACTOR" == "$REPO_OWNER" ]; then 10 | APP_NAME=$SECRETS_APP_NAME 11 | APP_GIT_URL=$SECRETS_GIT_URL 12 | fi 13 | 14 | if [ -n "$SECRETS_SUFFIX" ] && [ "$REPO_ACTOR" == "$REPO_OWNER" ]; then 15 | APP_SUFFIX=$SECRETS_SUFFIX 16 | fi 17 | 18 | APP_LAUNCH_NAME="阅读.$APP_SUFFIX" 19 | if [ -n "$SECRETS_LAUNCH_NAME" ] && [ "$REPO_ACTOR" = "$REPO_OWNER" ]; then 20 | APP_LAUNCH_NAME=$SECRETS_LAUNCH_NAME 21 | fi 22 | 23 | if [ -n "$SECRETS_CHANNEL" ]; then 24 | APP_CHANNEL=$SECRETS_CHANNEL 25 | fi 26 | 27 | APP_WORKSPACE="/opt/$APP_NAME" 28 | APP_LATEST_TAG='latest' 29 | APP_UPLOAD_NAME="$APP_NAME-$APP_LATEST_TAG" 30 | APP_UPLOAD="$APP_WORKSPACE/app/build/outputs/apk/app/release" 31 | 32 | set_env APP_NAME $APP_NAME 33 | set_env APP_CHANNEL $APP_CHANNEL 34 | set_env APP_LAUNCH_NAME $APP_LAUNCH_NAME 35 | set_env APP_WORKSPACE $APP_WORKSPACE 36 | set_env APP_SUFFIX $APP_SUFFIX 37 | set_env APP_GIT_URL $APP_GIT_URL 38 | set_env APP_UPLOAD_NAME $APP_UPLOAD_NAME 39 | set_env APP_UPLOAD $APP_UPLOAD 40 | set_env REPO_ACTOR $REPO_ACTOR 41 | set_env REPO_OWNER $REPO_OWNER 42 | set_env SECRETS_ENABLE $SECRETS_ENABLE 43 | set_env SECRETS_MINIFY $SECRETS_MINIFY 44 | set_env SECRETS_TAG $SECRETS_TAG 45 | set_env SECRETS_RENAME $SECRETS_RENAME 46 | -------------------------------------------------------------------------------- /action_util.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | function set_env() { echo "$1=$2" >> $GITHUB_ENV; } 3 | function debug() { echo "::debug::$1"; } 4 | function version_gt() { test "$(echo "$@" | tr " " "\n" | sort -V | head -n 1)" != "$1"; } 5 | function version_le() { test "$(echo "$@" | tr " " "\n" | sort -V | head -n 1)" == "$1"; } 6 | function version_lt() { test "$(echo "$@" | tr " " "\n" | sort -rV | head -n 1)" != "$1"; } 7 | function version_ge() { test "$(echo "$@" | tr " " "\n" | sort -rV | head -n 1)" == "$1"; } 8 | 9 | function update_info() 10 | { 11 | cd $GITHUB_WORKSPACE 12 | if version_gt $APP_LATEST_TAG $APP_LAST_TAG; then 13 | sed -e 's/^/> &/' \ 14 | -e '1i\' \ 15 | -e '$a\' \ 16 | -e 's/\r//' \ 17 | $APP_LATEST_BODY -i 18 | sed -e '//,//d' \ 19 | -e '5r '"$APP_LATEST_BODY"'' \ 20 | -e "5c > 最新构建下载:[$APP_RELEASE_NAME]($APP_DOWNLOAD) 上次构建时间:$APP_LATEST_CHECK" \ 21 | README.md -i 22 | sed "1i $APP_LATEST_TAG" .lastcheck -i 23 | 24 | git config user.name github-actions 25 | git config user.email github-actions@github.com 26 | git commit -a -m "$APP_NAME-$APP_LATEST_TAG release" 27 | git push 28 | fi 29 | } 30 | 31 | function webdav_upload() 32 | { 33 | if [[ "$WEBDAV_UPLOAD" == "true" ]] && [[ -n "$WEBDAV_AUTH" ]] && [[ -n "$WEBDAV_URL" ]] && [[ "$REPO_OWNER" == "$REPO_ACTOR" ]]; then 34 | debug "已开启webdav上传" 35 | curl -X MKCOL -u $WEBDAV_AUTH $WEBDAV_URL 36 | curl -u $WEBDAV_AUTH -T $1 $WEBDAV_URL 37 | fi 38 | } 39 | -------------------------------------------------------------------------------- /diy/diy_10bits.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | source $GITHUB_WORKSPACE/action_app_custom.sh 3 | function my_launcher_icon(){ 4 | debug "替换图标" 5 | find $APP_WORKSPACE/app/src -type d -regex '.*/res/drawable' -exec \ 6 | cp $GITHUB_WORKSPACE/.github/legado/ic_launcher_my.xml {}/ic_launcher1.xml \; 7 | 8 | find $APP_WORKSPACE/app/src -regex '.*/res/.*/ic_launcher.xml' -exec \ 9 | sed "/background/d" {} -i \; 10 | } 11 | my_launcher_icon; 12 | -------------------------------------------------------------------------------- /diy/diy_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | source $GITHUB_WORKSPACE/action_app_custom.sh 3 | echo "test" 4 | --------------------------------------------------------------------------------