前言
记一次使用篡改猴脚本获取 DNF 游戏活动奖励。
最近也是回来重新玩 DNF 了,但是回归什么都没有,没有黑钻,没有契约,太难受了,搜活动的时候发现有个看漫画拿星星的活动,即 《勇士的意志》第 2 季-地下城与勇士:创新世纪-DNF-官方网站-腾讯游戏 。里面有个点漫画得星星的部分,如下:
得到星星后就可以在下面奖励换黑钻了。
不过得一个个点,太麻烦了,所以本文会通过篡改猴脚本来自动获取星星。
正文
安装篡改猴
先进入篡改猴的官网:Tampermonkey 。
然后下载篡改猴的扩展,这里可以去扩展商店下载或者直接下载 crx 文件然后加载。
注意默认情况下是 Chrome 的版本的,如果使用 Edge 或者火狐的要在 tab 那里切换到对应的页面:
当然我还是推荐 Chrome 的。
安装脚本
脚本我已经放在 Github 上了。
Dedicatus546/dnf-comic-start-script
进入上面的仓库后点击 index.js
,即可进入脚本内容页面:
然后点击右上角的篡改猴点击新建脚本,点击图中红色框住的部分的按钮复制代码:
然后在新建脚本的页面粘贴拷贝的脚本:
然后 ctrl + s 保存,会出现下面这个页面:
页面操作
重新打开(或者刷新)活动页面,如果安装正确,左上角会出现脚本增加的额外的 UI :
等待页面加载完成后就可以点击开始获取星星,然后会有相关的信息显示出来:
在操作完成后页面会自动刷新,脚本操作的时候尽量不要操作页面,防止出现意外的情况。
每个话对应的星星只能获取一次,所以上图中的星星为已领取该奖励。
源码解析
接下来我们稍微解析一下脚本的实现原理,如果是单纯做活动的用户,后面的东西就不用看了。
首先在话数这里我们直接 F12 ,可以发现是一个 a
标签,除了 href
还有 onclick
点击函数:
这里 href
会跳转到腾讯动漫的站点,重点是这里的 onclick
绑定的 amsCommon.lotteryStar(1)
,由于是直接在 html 中编写调用代码,那么 amsCommon
应该是全局的一个对象,直接在控制台输出看下:
从这些暴露的函数来看,八九不离十应该是整个页面的活动逻辑。
文件的地址为 ams.js 。
直接把 lotteryStar
函数的源码贴上来。这个源码没混淆还加了一些注释,还是挺方便理解的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| var amsCommon = {
lotteryStar: function (index) { var sData = { index: index, }; var successCallback = function (res) { var starNum = $("#starNum").text(); starNum = starNum * 1 + 1; $("#starNum").text(starNum); amsCommon.showMsgDia("恭喜您,获得1个星星"); }; var failCallback = function (res) { if (res.iRet == 100001) { return; } amsCommon.handleFail(res); }; this.submitFlow("0a571a", false, sData, successCallback, failCallback); }, };
|
可以看到在成功回调 successCallback
中提示了获得星星,最后是调用了 submitFlow
,源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| var amsCommon = {
submitFlow: function ( token, showLoading, sData, successCallback, failCallback ) { var flow = { actId: "603984", token: token, loading: showLoading, sData: sData, success: function (res) { if (typeof successCallback == "function") { successCallback(res); } }, fail: function (res) { if (typeof failCallback == "function") { failCallback(res); } else { amsCommon.handleFail(res); } }, }; Milo.emit(flow); }, };
|
可以看到出现了 Milo ,这里的 Milo 应该是腾讯开源的一个库,不过好像只是内部使用的,Milo 。
这里可以不用管 Milo ,从上面两个源码就基本上可以看出, submitFlow
是一个基础的接口,然后 lotteryStar
传入了 0a571a
这个 token ,这个 token 就是对应点漫画获取星星的。
我们可以尝试直接在控制台输入 amsCommon.lotteryStar(1)
这段代码为领取第一话的星星,如果没领过的话会直接弹出一个方框提示获取星星成功,这是很重要的结果,这意味着这个结果不依赖腾讯动漫那边的数据,也就是说不用看漫画领取星星这个逻辑是可行的。
执行完后,可以在网络一栏看到发送的接口,在启动器可以看到调用流程:
调用的结果也是提示已领取星星了:
ok,有了上面的理论之后,实际的代码就很好写了,首先是获取当前已更新的漫画,这里有两种思路
- 分析 UI 结构,待更新的节点是没有 a 标签的。
- 分析
amsCommon
代码,里面有话数的全局数据。
待更新的节点没有 a
标签,如下:
由于这个特性,我们可以写出如下的代码获取所有已更新的话数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const indexList = [ Array.from(document.querySelector("#comicList_1").childNodes), Array.from(document.querySelector("#comicList_2").childNodes), Array.from(document.querySelector("#comicList_3").childNodes), Array.from(document.querySelector("#comicList_4").childNodes), ] .flat() .filter( (el) => el.childNodes.length === 1 && el.childNodes[0].tagName.toLowerCase() === "a" ) .map((el) => Number.parseInt(el.id.split("_")[1]));
|
放到控制台输出一下:
看起来没什么问题。
如果细看 amsCommon
的代码的话,会发现页面上的 li
也是它负责渲染的,源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| var amsCommon = {
initComicProgress: function () { if (pageName == "indexm") { var html_1 = "", html_2 = "", html_3 = "", html_4 = "", html_5 = ""; $.each(DNF_2023COMIC_DATA, function (i, item) { var html = ""; var index = i + 1; if (item.updateStatus > 0 && item.comicUrl != "") { html = '<li id="comicLinkBtn_' + index + '"><a href="javascript:;" onclick="amsCommon.lotteryStar(' + index + ");amsCommon.jumpComicPage('" + item.comicUrl + "')\"><span>第" + index + '话</span><span class="zt updated">已更新</span></a></li>'; } else { html = '<li id="comicLinkBtn_' + index + '"><span>第' + index + '话</span><span class="zt">待更新</span></li>'; } if (i < 20) { html_1 += html; } else if (i < 40) { html_2 += html; } else if (i < 60) { html_3 += html; } else if (i < 80) { html_4 += html; } else { html_5 += html; } }); $("#comicList_1").html(html_1); $("#comicList_2").html(html_2); $("#comicList_3").html(html_3); $("#comicList_4").html(html_4); $("#comicList_5").html(html_5); } else { var html_1 = "", html_2 = "", html_3 = "", html_4 = ""; $.each(DNF_2023COMIC_DATA, function (i, item) { var html = ""; var index = i + 1; if (item.updateStatus > 0 && item.comicUrl != "") { html = '<li id="comicLinkBtn_' + index + '"><a href="' + item.comicUrl + '" onclick="amsCommon.lotteryStar(' + index + ')" target="_blank"><span>第' + index + '话</span><span class="zt updated">已更新</span></a></li>'; } else { html = '<li id="comicLinkBtn_' + index + '"><span>第' + index + '话</span><span class="zt">待更新</span></li>'; } if (i < 30) { html_1 += html; } else if (i < 60) { html_2 += html; } else if (i < 90) { html_3 += html; } else { html_4 += html; } }); $("#comicList_1").html(html_1); $("#comicList_2").html(html_2); $("#comicList_3").html(html_3); $("#comicList_4").html(html_4); } }, };
|
可以看到这里引用了 DNF_2023COMIC_DATA
这个全局数据,在控制台打印下:
可以看到是漫画的数据,其中 updateStatus
为 1
时为已更新,为 0
时为未更新,那么代码就更简单了,如下:
1 2 3 4 5 6 7
| DNF_2023COMIC_DATA .filter((item) => item.updateStatus === "1") .map((item) => Number.parseInt(item.id));
|
核心的代码就是这样了,当然如果这样直接执行很有可能会报频繁请求,所以真实的脚本中加了一些 delay
来防止请求过于频繁,比如一个简单的 delay
实现:
1
| const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
这里再贴一个控制台版本的,直接 F12 拷贝到控制台执行即可,不用安装篡改猴:
1 2 3 4 5 6 7 8 9
| (async () => { const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); for (const id of DNF_2023COMIC_DATA.filter( (item) => item.updateStatus === "1" ).map((item) => Number.parseInt(item.id))) { amsCommon.lotteryStar(id); await delay(1500); } })();
|
后记
上次玩还是巴老师的版本,自定义玩不动,这阵子玩自定义几乎一直送了,不过还是带了一套神未知的工作服,愉快地单机游戏,马上也是要到 115 了,一想到修武器的地图要没了我就心痛,一次修武器 16 万金币…