之前写过一个微博超话批量关注、签到、发帖、捞贴的脚本,还录了视频,代码基于魂签浏览器扩展。因为扩展作者近来没有更新过代码,现有的构建程序在新版的浏览器下执行出错,所以我fork了一份做了修改并维护。

批量操作原理,就是先使用Fiddler或Charles等工具,抓取用户真实点击时HTTP请求的URI和上下行数据,脚本同样请求即可。浏览器扩展权限很大,从脚本里发起的操作基本等同于用户的直接操作。只要用户已经登录过微博,脚本操作可以直接使用其cookie,不存在登录状态问题

以 批量发帖 为例,发帖 HTTP请求代码:

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
const rsp = await axios({
url: url,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-Requested-With': 'XMLHttpRequest',
'Origin': 'https://weibo.com',
'Referer': `https://weibo.com/p/${hid}/super_index`
},
data: {
'id': hid,
'domain': '100808',
'module': 'share_topic',
'title': '%E5%8F%91%E5%B8%96',
'content': '',
'api_url': 'http://i.huati.weibo.com/pcpage/super/publisher',
'spr': '',
'extraurl': '',
'is_stock': '',
'check_url': `http%3A%2F%2Fi.huati.weibo.com%2Faj%2Fsuperpublishauth%26pageid%3D${hid}%26uid%3D1764819037`,
'location': 'page_100808_super_index',
'text': text,
'appkey': '',
'style_type': 1,
'pic_id': picture,
'tid': '',
'pdetail': hid,
'mid': '',
'isReEdit': 'false',
'sync_wb': 0,
'pub_source': 'page_2',
'api': `http://i.huati.weibo.com/pcpage/operation/publisher/sendcontent?sign=super&page_id=${hid}`,
'longtext': 1,
'topic_id': `1022:${hid}`,
'pub_type': 'dialog',
'_t': 0
},
transformRequest: [function(data) {
return objectToUrlEncodedParams(data);
}]
});

数据直接配置在脚本里:

1
2
3
4
5
6
7
8
9
10
11
12
13
let chaohuas = [{
"hid": "100808db06c78d1e24cf708a14ce81c9b617ec",
"hname": "测试超话",
"text": "#测试[超话]# 这是要发布的内容\r\n单图",
"picture": "0072KUQXly1ghh1d0daf7j30jg163t9r"
},
{
"hid": "1008084b97c8f5ab54d661a331566ab64bf9d6",
"hname": "趣味测试超话",
"text": "#测试[超话]# 这是要发布的内容\r\n多图",
"picture": "0072KUQXly1ghh1d0daf7j30jg163t9r|0072KUQXly1ghh1d0wgjuj30j60n9ab0"
}
];

这里配置了两条带图微博,会分别发在两个超话,详细说明可参考开头的视频。

视频虽然浏览量不高,但还是有一些同学使用并提出了问题和建议,其中就有希望能在 油猴 扩展下使用。考虑到 魂签 扩展确实小众,且没有上架官方商店,只能以开发者模式使用,而油猴受众更广,资料更多,对新人更友好,于是决定做一版油猴脚本。

为了维护方便,最大可能保持两个扩展所用脚本代码的一致性,只对不兼容的地方做了调整。

一个不同是,魂签扩展界面上,可以直接点击执行脚本,也可以设置定时运行。油猴扩展的机制是进入匹配网站时,自动运行脚本。批量签到 等操作,还是希望用户主动点击执行,所以油猴版采用的方案是在网页之上添加一层操作界面,包括 关闭、批量关注 等按钮,供用户操作。

以 批量关注 脚本为例,界面相关代码如下:

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
// 容纳按钮的容器,同时做互斥,可能多个脚本共用
let containerId = 'kf-container';
let container = document.getElementById(containerId);
if (!container) {
// 引入Bootstrap CSS
var bootstrapCss = document.createElement('link');
bootstrapCss.rel = 'stylesheet';
bootstrapCss.href = 'https://cdn.jsdelivr.net/npm/bootstrap@5/dist/css/bootstrap.min.css';
document.head.appendChild(bootstrapCss);

// 创建容器
container = document.createElement('div');
container.id = containerId;
container.className = 'btn-group-vertical btn-group-sm position-fixed';
container.style.left = '5px';
container.style.top = '5px';
container.style['z-index'] = "999999";
document.body.appendChild(container);

// 关闭按钮
let closeBtn = document.createElement("button");
closeBtn.className = 'btn-close';
closeBtn.innerText = '';
container.appendChild(closeBtn);

// 点击关闭容器
closeBtn.addEventListener('click', function() {
document.body.removeChild(container);
});
}

// 批量关注按钮
let followBtn = document.createElement("button");
followBtn.className = "kf-button btn btn-info";
followBtn.innerText = "批量关注";
container.appendChild(followBtn);

// 改变所有关联按钮可点击状态
function changeRelatedButtonState(disabled) {
let buttons = document.getElementsByClassName('kf-button');
for (let button of buttons) {
button.disabled = disabled;
}
}

// 点击开始执行
followBtn.addEventListener('click', function() {
changeRelatedButtonState(true);
run()
.then(result => alert(`微博超话批量关注成功!(^∀^)\n${result}`))
.catch(err => alert(`微博超话批量关注出错!(>﹏<)\n${err}`))
.finally(() => changeRelatedButtonState(false));
}, false);

关注、签到、发帖和捞贴分成了4个扩展脚本,可能单个使用也可能同时使用,对应按钮都放在同一个容器,多个脚本也只会创建一个容器,同时也保证只执行一次公共逻辑。

注意一下 CSS 文件的引入方式,使用 @resource 没有效果,只能以这种添加页面元素的方式引入。

实际显示:

weibo_ui

另外一个不同是,魂签可以直接跨域,而油猴需要授权使用 GM_xmlhttpRequest 来跨域。一般的 GET/PUT请求都直接使用 axios,只有跨域的地方,才封装使用这个特定API,并确保跟 axios 的参数和返回值基本一致。封装代码如下:

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
function objectToUrlEncodedParams(obj) {
return Object.entries(obj)
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
.join('&');
}

function xmlHttpRequest(url, method, headers = {}, data = {}) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
url: url,
method: method,
headers: headers,
data: data,
onload: function(rsp) {
resolve({
status: rsp.status,
data: rsp.response,
});
},
onerror: function(rsp) {
reject({
status: rsp.status,
data: rsp.response,
});
},
})
});
}

function xmlHttpGet(req) {
let url = req.url || '';
let method = 'GET';
let headers = req.headers || {};
let params = req.params || {};
return xmlHttpRequest(`${url}?${objectToUrlEncodedParams(params)}`, method, headers);
}

代码放在了GitHub