├── .gitignore ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── io │ │ └── github │ │ └── mzdluo123 │ │ └── txcaptchahelper │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── io │ │ │ └── github │ │ │ └── mzdluo123 │ │ │ └── txcaptchahelper │ │ │ ├── CaptchaActivity.kt │ │ │ └── MainActivity.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_captcha.xml │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values-night │ │ └── themes.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ └── test │ └── java │ └── io │ └── github │ └── mzdluo123 │ └── txcaptchahelper │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── readme.md └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'kotlin-android' 4 | id 'kotlin-android-extensions' 5 | } 6 | def onlineService = "https://txhelper.glitch.me/" 7 | 8 | android { 9 | compileSdkVersion 29 10 | 11 | defaultConfig { 12 | applicationId "io.github.mzdluo123.txcaptchahelper" 13 | minSdkVersion 21 14 | targetSdkVersion 29 15 | versionCode 3 16 | versionName "1.2" 17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 18 | buildConfigField("String", "OnlineService", "\"$onlineService\"") 19 | } 20 | 21 | buildTypes { 22 | release { 23 | minifyEnabled true 24 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 25 | } 26 | } 27 | compileOptions { 28 | sourceCompatibility JavaVersion.VERSION_1_8 29 | targetCompatibility JavaVersion.VERSION_1_8 30 | } 31 | kotlinOptions { 32 | jvmTarget = '1.8' 33 | } 34 | } 35 | 36 | dependencies { 37 | 38 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 39 | implementation 'androidx.core:core-ktx:1.3.2' 40 | implementation 'androidx.appcompat:appcompat:1.2.0' 41 | implementation 'com.google.android.material:material:1.2.1' 42 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4' 43 | // https://mvnrepository.com/artifact/com.google.code.gson/gson 44 | implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.6' 45 | implementation 'com.king.zxing:zxing-lite:2.0.2' 46 | implementation("com.squareup.okhttp3:okhttp:4.9.0") 47 | 48 | testImplementation 'junit:junit:4.+' 49 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 50 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 51 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/io/github/mzdluo123/txcaptchahelper/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.txcaptchahelper 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("io.github.mzdluo123.txcaptchahelper", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/txcaptchahelper/CaptchaActivity.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.txcaptchahelper 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Intent 5 | import android.net.Uri 6 | import android.os.Bundle 7 | import android.webkit.WebResourceRequest 8 | import android.webkit.WebView 9 | import android.webkit.WebViewClient 10 | import androidx.appcompat.app.AppCompatActivity 11 | import com.google.gson.JsonParser 12 | import kotlinx.android.synthetic.main.activity_captcha.* 13 | 14 | class CaptchaActivity : AppCompatActivity() { 15 | companion object { 16 | const val RESULT_OK = 0 17 | } 18 | 19 | override fun onCreate(savedInstanceState: Bundle?) { 20 | super.onCreate(savedInstanceState) 21 | setContentView(R.layout.activity_captcha) 22 | initWebView() 23 | webview.loadUrl(intent.getStringExtra("url")) 24 | } 25 | 26 | @SuppressLint("SetJavaScriptEnabled") 27 | private fun initWebView() { 28 | webview.webViewClient = object : WebViewClient() { 29 | override fun shouldOverrideUrlLoading( 30 | view: WebView?, 31 | request: WebResourceRequest? 32 | ): Boolean { 33 | return onJsBridgeInvoke(request!!.url) 34 | } 35 | 36 | override fun shouldOverrideUrlLoading( 37 | view: WebView?, 38 | url: String? 39 | ): Boolean { 40 | return onJsBridgeInvoke(Uri.parse(url)) 41 | } 42 | } 43 | WebView.setWebContentsDebuggingEnabled(true) 44 | webview.settings.apply { 45 | javaScriptEnabled = true 46 | domStorageEnabled = true 47 | } 48 | } 49 | 50 | private fun onJsBridgeInvoke(request: Uri): Boolean { 51 | if (request.path.equals("/onVerifyCAPTCHA")) { 52 | val p = request.getQueryParameter("p") 53 | val jsData = JsonParser.parseString(p).asJsonObject 54 | authFinish(jsData["ticket"].asString) 55 | } 56 | return false 57 | } 58 | 59 | private fun authFinish(ticket: String) { 60 | val intent = Intent().putExtra("ticket", ticket) 61 | setResult(RESULT_OK, intent) 62 | finish() 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/txcaptchahelper/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.txcaptchahelper 2 | 3 | import android.content.ClipData 4 | import android.content.ClipboardManager 5 | import android.content.Context 6 | import android.content.Intent 7 | import android.net.Uri 8 | import android.os.Bundle 9 | import android.widget.Toast 10 | import androidx.appcompat.app.AlertDialog 11 | import androidx.appcompat.app.AppCompatActivity 12 | import com.king.zxing.CameraScan 13 | import com.king.zxing.CaptureActivity 14 | import kotlinx.android.synthetic.main.activity_main.* 15 | import okhttp3.* 16 | import java.io.IOException 17 | 18 | 19 | class MainActivity : AppCompatActivity() { 20 | var code: Int = 0 21 | private val client = OkHttpClient() 22 | 23 | companion object { 24 | const val REQUEST_CODE_SLIDE = 1 25 | const val REQUEST_CODE_QRSCAN = 2 26 | const val REQUEST_CODE_PERMISSION = 3 27 | } 28 | 29 | override fun onCreate(savedInstanceState: Bundle?) { 30 | super.onCreate(savedInstanceState) 31 | setContentView(R.layout.activity_main) 32 | clean_btn.setOnClickListener { 33 | url_edit_text.text.clear() 34 | } 35 | next_btn.setOnClickListener { 36 | val url = url_edit_text.text.toString() 37 | if (url.isEmpty()) { 38 | Toast.makeText(this, "请填写URL或请求码", Toast.LENGTH_SHORT).show() 39 | return@setOnClickListener 40 | } 41 | kotlin.runCatching { 42 | code = url.toInt() 43 | processOnlineCode() 44 | }.onFailure { 45 | toCaptchaActivity(url) 46 | } 47 | 48 | } 49 | scan_btn.setOnClickListener { 50 | startActivityForResult( 51 | Intent(this, CaptureActivity::class.java), 52 | REQUEST_CODE_QRSCAN 53 | ) 54 | } 55 | proj_location.setOnClickListener { 56 | startActivity( 57 | Intent( 58 | Intent.ACTION_VIEW, 59 | Uri.parse("https://github.com/mzdluo123/TxCaptchaHelper") 60 | ) 61 | ) 62 | } 63 | } 64 | 65 | private fun processOnlineCode() { 66 | val dialog = 67 | AlertDialog.Builder(this) 68 | .setTitle("请稍后") 69 | .setMessage("正在获取信息") 70 | .setCancelable(false) 71 | .create() 72 | dialog.show() 73 | client.newCall( 74 | Request.Builder().url(BuildConfig.OnlineService + code).get().build() 75 | ).enqueue(object : Callback { 76 | override fun onFailure(call: Call, e: IOException) { 77 | runOnUiThread { 78 | dialog.dismiss() 79 | Toast.makeText(this@MainActivity, e.message, Toast.LENGTH_SHORT).show() 80 | } 81 | 82 | } 83 | 84 | override fun onResponse(call: Call, response: Response) { 85 | runOnUiThread { 86 | dialog.dismiss() 87 | if (response.code == 200) { 88 | val url = response.body!!.string() 89 | toCaptchaActivity(url) 90 | } else { 91 | Toast.makeText( 92 | this@MainActivity, 93 | "请求错误:" + response.code, 94 | Toast.LENGTH_SHORT 95 | ).show() 96 | } 97 | } 98 | } 99 | }) 100 | } 101 | 102 | private fun submitTicket(ticket: String) { 103 | val alert = 104 | AlertDialog.Builder(this).setTitle("请稍后").setMessage("正在提交").setCancelable(false) 105 | .create() 106 | alert.show() 107 | client.newCall( 108 | Request.Builder().url(BuildConfig.OnlineService + "finish/" + code).post( 109 | FormBody.Builder().add("ticket", ticket).build() 110 | ).build() 111 | ).enqueue(object : Callback { 112 | override fun onFailure(call: Call, e: IOException) { 113 | runOnUiThread { 114 | alert.dismiss() 115 | Toast.makeText(this@MainActivity, e.message, Toast.LENGTH_SHORT).show() 116 | } 117 | 118 | } 119 | 120 | override fun onResponse(call: Call, response: Response) { 121 | 122 | runOnUiThread { 123 | alert.dismiss() 124 | Toast.makeText( 125 | this@MainActivity, 126 | "提交成功,请在PC端再次发起请求得到ticket", 127 | Toast.LENGTH_SHORT 128 | ).show() 129 | } 130 | 131 | } 132 | }) 133 | 134 | } 135 | 136 | private fun toCaptchaActivity(url: String) { 137 | startActivityForResult( 138 | Intent(this, CaptchaActivity::class.java).putExtra("url", url), 139 | REQUEST_CODE_SLIDE 140 | ) 141 | 142 | } 143 | 144 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 145 | super.onActivityResult(requestCode, resultCode, data) 146 | if (data == null) { 147 | return 148 | } 149 | if (requestCode == REQUEST_CODE_SLIDE) { 150 | val ticket = data.getStringExtra("ticket") 151 | if (ticket == null) { 152 | Toast.makeText(this, "出现未知错误,请联系开发者", Toast.LENGTH_SHORT).show() 153 | return 154 | } 155 | if (code != 0) { 156 | submitTicket(ticket) 157 | return 158 | } 159 | AlertDialog.Builder(this).setTitle("Ticket").setMessage(ticket).setPositiveButton( 160 | "复制" 161 | ) { _, _ -> // dialog, which 162 | val clipboardManager = 163 | getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager 164 | val result = ClipData.newPlainText(null, ticket) 165 | clipboardManager.setPrimaryClip(result) 166 | Toast.makeText(this, "复制成功", Toast.LENGTH_SHORT).show() 167 | }.show() 168 | return 169 | } 170 | 171 | if (requestCode == REQUEST_CODE_QRSCAN) { 172 | val result = CameraScan.parseScanResult(data) 173 | toCaptchaActivity(result!!) 174 | } else if (requestCode == REQUEST_CODE_PERMISSION) { 175 | startActivityForResult(Intent(this, CaptureActivity::class.java), REQUEST_CODE_QRSCAN) 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_captcha.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 19 | 20 | 33 | 34 |