现在可以领取到满40-20、满30-14的优惠券,还有些隐藏优惠券之类的
下载地址(百度网盘)
链接: https://pan.baidu.com/s/1SvSXJJH06Uahj7CTsw7bxQ 提取码: gbik
使用教程
没有任何教程,打开后并登录就可以直接用了
以下是写程序的过程:
脚本是纯 JS,依赖几个 npm 包(crypto、https 之类,都是 Node 内置的),没有原生模块。所以打包方案其实比较明确:
- Cordova / Capacitor:套壳 WebView,但脚本是 Node 环境,不是浏览器环境,跑不了
- 服务端中转:把脚本部署到服务器,App 只做前端,但这样得多养一台服务器,成本不划算
- nodejs-mobile:把整个 Node.js 运行时编译成 Android 的 so 库,嵌进 App 里,JS 原样跑
nodejs-mobile 的思路是:Android 原生层通过 JNI 调用 C++ 桥接层,C++ 层加载libnode.so(Node 运行时),然后在里面执行 JS 文件。JS 还是那个 JS,几乎不用改。
我用的是 nodejs-mobile 的 18.20.4 版本,对应 Node 18。GitHub 上搜nodejs-mobile就有,官方也维护了 Android 的 Gradle 模板。
架构设计:本地 HTTP 通信
JS 和 Java 怎么通信,是第一个要想清楚的问题。我一开始想用 JNI 直接互相调用,后来发现太麻烦——JS 是异步的,Java 是同步思维,两边混在一起调试起来很痛苦。
最后选了最土但最稳的方案:本地 HTTP 服务。
Node 这边起一个 HTTP server,监听127.0.0.1:39000,对外暴露一个/cmd接口。Java 那边要用什么功能,就往这个接口 POST 一个 JSON 命令:
Node 执行完返回 JSON:
好处是两边彻底解耦:JS 怎么改,Java 不用动;Java 怎么调,JS 不用关心。调试的时候甚至可以用电脑上的 curl 直接打这个接口,把逻辑验一遍再上真机。
授权流程:PKCE + 轮询
领券的前提是登录,而登录走的是美团 passport 的 OAuth 授权。脚本里用的是 PKCE 流程:
- 生成
code_verifier(32 字节随机数,base64url 编码) - 对 verifier 做 SHA-256,得到
code_challenge - 请求授权链接:
passport.meituan.com/api/account/userauth/code?client_id=xxx&code_challenge=xxx - 用户登录美团
- App 轮询
userauth/check接口,等authStatus变成 4(成功),拿到 token
这里有个细节:美团返回的授权链接字段名不是文档里写的auth_link,而是shortLink。第一次对接时按文档写的取auth_link,但是取不到,排查了半天,最后把返回的 JSON 整个打出来才看到是shortLink。这种字段名对不上的问题,后面还遇到了好几次,后面细说。
踩坑实录
坑 1:打开就死机,其实是 ANR
第一版 APK 装到手机上,一点开就卡死,过一会系统弹"美团自动领券未响应"。看日志才知道是 ANR——主线程被阻塞。
原因很简单:我在onCreate里直接同步做了两件事:把 assets 里的 Node 工程复制到应用私有目录,然后启动 Node 进程。这两件事都放在主线程,Node 冷启动要一两秒,加上文件复制,主线程一卡,系统就判 ANR。
解决:全部丢到子线程。复制文件、启动 Node、等引擎就绪,都在后台线程做。UI 先显示"引擎启动中…",等就绪了再更新按钮状态。
坑 2:模拟器上点击没反应,缺 ABI
用 MuMu 模拟器测试,发现 App 能启动,但一点按钮就崩或者没反应。查了 logcat,发现是libnode.so没加载成功——找不到x86_64的 so。
nodejs-mobile 默认只编译了arm64-v8a,而 MuMu 模拟器是 x86_64 架构。我一度把x86_64也加进了abiFilters,模拟器能跑了,但 APK 体积直接翻倍,而且真机根本用不到。
解决:最后只留arm64-v8a,模拟器的问题用真机代替。反正这 App 最终只在真机上用,没必要为模拟器多塞几十 MB。
坑 3:真机上请求全部失败,cleartext 拦截
代码逻辑没问题,但在真机上所有 HTTP 请求都失败。logcat 里有一条很关键的错误:
Android 9 之后默认禁止明文 HTTP 流量。本地服务是http://127.0.0.1:39000,不是 HTTPS,直接被系统拦了。
解决:在AndroidManifest.xml的<application>上加android:usesCleartextTraffic="true"。
坑 4:绑定后还显示"获取授权链接失败"
授权链接接口返回正常了,但前端还是报"获取授权链接失败"。原因是前面说的:美团返回的字段叫shortLink,我代码里取的是auth_link。
解决:改成兼容多个字段名:
这种"响应字段名和文档对不上"的坑,对接非官方接口时太常见了,最好的习惯就是:拿到响应先整个打印出来,别猜字段名。
坑 5:授权成功但 App 一直收不到 token
这个坑最隐蔽。授权流程走完了,美团 App 里也提示"授权成功"了,但 App 这边一直显示"等待授权确认…",最后超时。
问题出在轮询接口的响应结构上。美团userauth/check的真实响应是:
而我的代码是:
resp.data已经是整个响应对象了,它的结构是{data: {...}},authStatus在内层data里。我直接读d.authStatus,永远读不到,轮询就一直空转到超时。
解决:兼容两层结构:
坑 6:轮询时报ECONNABORTED,然后就崩了
改完层级问题,授权能收到 token 了,但偶尔会报:
这是 Node 的底层网络错误,意思是 TCP 连接被中断了。原因可能是手机网络抖动、Wi-Fi 切换,也可能是美团服务端主动断开。
问题在于我的轮询代码没有 try/catch:一次请求失败,整个轮询直接异常退出,前端就显示"未等到授权"。
解决:单次请求失败不中断轮询,退避重试:
另外还把 DNS 强制成 IPv4——Android 上 IPv6 解析不稳定,也是 ECONNABORTED 的一个常见来源。
坑 7:Gradle 构建各种幺蛾子
这部分纯粹是环境问题,但也浪费了不少时间。
先是gradlew.bat莫名丢失,构建直接报"不是内部或外部命令"。后来发现是项目目录被复制时漏了 wrapper 文件,手动补一个指向本地缓存的 Gradle 就行。
然后是经典的 Gradle daemon 锁问题:
这是上次构建的 daemon 进程没退干净,锁住了缓存目录。解决:杀掉残留的 Java 进程,删掉整个缓存目录,或者干脆换个新的缓存目录名重新构建。
安全提示
这种 App 有几个绕不开的问题:
- 代码是明文的。
assets/目录下的 JS 在 APK 里就是明文,解压就能看到。client_id、接口地址、签名逻辑全暴露。想防君子不防小人,最多做一层 JS 混淆,或者把核心签名逻辑下沉到 C++ so 里。 - 有风控风险。这种自动化领券本质是打擦边球,注意下美团的风控。实测中遇到过
authStatus=3(风控拒绝),连续请求过快也可能触发限流。账号有被限制的风险,自己评估。