Bilibili删除抽奖动态
// ==UserScript==
// @name Bilibili删除抽奖动态
// @namespace https://github.com/AliubYiero/TamperMonkeyScripts
// @version 1.0.2
// @description Bilibili删除抽奖动态并取消关注对应UP主
// @author Yiero
// @grant GM_xmlhttpRequest
// @grant GM_notification
// @grant GM_cookie
// @grant GM_openInTab
// @grant GM_getValue
// @grant GM_setValue
// @connect api.bilibili.com
// @crontab * * once * *
// ==/UserScript==
/* ==UserConfig==
配置项:
unFollow:
title: "取关默认分组中的抽奖用户"
description: "开启"
type: checkbox
default: true
==/UserConfig== */
/**
* 取关配置
*/
class UnFollowStorage {
static key = '配置项.unFollow';
static get() {
return GM_getValue(this.key, true);
}
static set(unFollowStat) {
GM_setValue(this.key, unFollowStat);
}
}
/*
* 通用API封装模块
* */
class CommonAPI {
constructor() { }
/**
* 获取 cookie 内容
* @param {string} domain 域名
* @param {string} key cookie 键名
* @returns {Promise<string>} cookie 值
*/
getCookie = async (domain, key) => {
return new Promise((resolve) => {
// @ts-ignore 忽略未被查找到的全局函数 GM_cookie
GM_cookie('list', {
domain,
}, (cookieList) => {
const userIdCookie = cookieList.find(cookie => cookie.name === key);
resolve(userIdCookie?.value || '');
});
});
};
/**
* 网络请求
* @param config
*/
fetch(config) {
let { url, param, method = 'GET', } = config;
/*
* 处理url参数
* */
const urlParam = new URLSearchParams(param).toString();
if (urlParam) {
url = `${url}?${urlParam}`;
}
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method,
url,
onload(response) {
try {
const res = JSON.parse(response.response);
// 每个请求延时500ms, 防止请求过于频繁被ban
setTimeout(() => {
resolve(res);
}, 600);
}
catch (e) {
reject(e);
}
},
});
});
}
/**
* 桌面通知
*/
notify(title, text, clickCallback) {
console.log('[notify] %s\n%s', title, text);
GM_notification({
title: title,
text: text,
onclick() {
clickCallback && clickCallback();
},
});
}
}
/*
* 获取用户信息模块
* */
class UserInfoGetter extends CommonAPI {
constructor() { super(); }
/**
* 获取csrf
* */
getCsrf() {
return this.getCookie('.bilibili.com', 'bili_jct');
}
/**
* 获取用户UID
*/
getUid() {
return this.getCookie('.bilibili.com', 'DedeUserID');
}
}
/**
* 获取动态列表模块
*/
class DynamicGetter extends CommonAPI {
dynamicList = [];
constructor() { super(); }
/**
* 获取全部动态
*/
async getAllDynamic(uid) {
/*
* 获取第一页动态
* */
let res;
try {
res = await this.api_GetDynamic(uid);
}
catch (e) {
console.error('获取动态失败: ', e);
return this.dynamicList;
}
/*
* 如果内存中存在动态列表, 更新第一页的动态就返回, 不继续请求
* */
if (this.dynamicList.length) {
// 获取最新的动态索引
const latestDynamicIndex = res.items.findIndex(item => item.id_str === this.dynamicList[0].id_str);
if (latestDynamicIndex > 0) {
this.dynamicList.splice(0, 0, ...res.items.slice(0, latestDynamicIndex));
}
return this.dynamicList;
}
/*
* 加载后续动态
* */
let offset;
this.dynamicList.push(...res.items);
while (res.has_more) {
offset = res.offset;
try {
res = await this.api_GetDynamic(uid, offset);
}
catch (e) {
console.error('获取动态失败: ', e);
break;
}
this.dynamicList.push(...res.items);
}
return this.dynamicList;
}
/**
* 获取全部转发动态
* */
async getAllForwardDynamic(uid) {
const dynamicList = await this.getAllDynamic(uid);
return dynamicList.filter(item => item.type === 'DYNAMIC_TYPE_FORWARD');
}
/**
* 获取全部抽奖动态
*/
async getAllLotteryDynamic(uid) {
const forwardDynamicList = await this.getAllForwardDynamic(uid);
return forwardDynamicList.filter(item => {
try {
const richTextNodes = item.orig?.modules.module_dynamic.desc.rich_text_nodes;
if (!richTextNodes) {
return false;
}
return richTextNodes.find(node => node.type === 'RICH_TEXT_NODE_TYPE_LOTTERY');
}
catch (e) {
return false;
}
});
}
/**
* 获取当前页动态
*/
async api_GetDynamic(uid, offset) {
/*
* 参数归一
* */
let urlParam = {
host_mid: uid,
};
urlParam = offset ? { offset, ...urlParam } : { ...urlParam };
/*
* 请求动态
* */
const res = await this.fetch({
url: 'https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/space',
method: 'GET',
param: urlParam,
});
if (res.code !== 0) {
throw new Error(`请求失败. \n[${res.code}] ${res.message}`);
}
return res.data;
}
}
/*
* 抽奖信息模块
* */
class LotteryInfoGetter extends CommonAPI {
constructor() {
super();
}
/*
* 根据传入的抽奖动态列表, 获取抽奖信息列表
* */
async getLotteryInfoList(dynamicList, csrf) {
const lotteryInfoList = [];
for (let dynamicInfo of dynamicList) {
const lotteryInfo = await this.getLotteryInfo(dynamicInfo, csrf);
if (!lotteryInfo) {
continue;
}
lotteryInfoList.push(lotteryInfo);
}
return lotteryInfoList;
}
/**
* @param dynamicId 动态id
* @param csrf csrf, 个人身份码
*/
async api_GetLotteryInfo(dynamicId, csrf) {
const res = await this.fetch({
url: 'https://api.vc.bilibili.com/lottery_svr/v1/lottery_svr/lottery_notice',
method: 'GET',
param: {
business_id: dynamicId,
csrf: csrf,
business_type: 1,
},
});
if (res && res.code !== 0) {
throw new Error(`获取抽奖信息失败: ${res.message}`);
}
return res.data;
}
/*
* 根据抽奖动态, 获取抽奖信息
* */
async getLotteryInfo(dynamicItem, csrf) {
if (dynamicItem.type !== 'DYNAMIC_TYPE_FORWARD') {
return null;
}
const originDynamicInfo = dynamicItem.orig;
if (!originDynamicInfo) {
return null;
}
const lotteryNode = originDynamicInfo.modules.module_dynamic.desc.rich_text_nodes
.find(node => node.type === 'RICH_TEXT_NODE_TYPE_LOTTERY');
if (!lotteryNode) {
return null;
}
return await this.api_GetLotteryInfo(originDynamicInfo.id_str, csrf);
}
}
/*
* 删除动态模块
* */
class DeleteController extends CommonAPI {
constructor() { super(); }
/**
* 删除动态
*/
async deleteDynamicList(lotteryDynamicList, csrf) {
const lotteryInfo = new LotteryInfoGetter();
const lotteryInfoList = await lotteryInfo.getLotteryInfoList(lotteryDynamicList, csrf);
// TODO 数据不统一处理
if (lotteryDynamicList.length !== lotteryInfoList.length) {
return;
}
while (lotteryDynamicList.length) {
const dynamicItem = lotteryDynamicList.shift();
const lotteryInfo = lotteryInfoList.shift();
if (!dynamicItem || !lotteryInfo) {
break;
}
/*
* 判断当前是否已经超时
* */
// 如果不超时, 则退出
if (Date.now() < lotteryInfo.lottery_time * 1000) {
continue;
}
// 删除动态
try {
await this.api_DeleteDynamic(dynamicItem.id_str, csrf);
}
catch (e) {
console.error(`删除动态失败: ${dynamicItem.id_str}`);
}
this.notify('已删除动态', `${dynamicItem.orig?.modules.module_author.name}: \n${dynamicItem.orig?.modules.module_dynamic.desc.text}`, () => {
GM_openInTab(`https://t.bilibili.com/${dynamicItem.orig?.id_str}`);
});
/*
* 查询配置项, 判断是否需要取消关注
* */
if (!UnFollowStorage.get()) {
continue;
}
/*
* 判断当前UP主是否在默认分组中
* */
// 查询用户分组
const userGroup = await this.api_GetUserInGroup(lotteryInfo.sender_uid);
// 如果不存在默认分组中 (默认分组: 已关注并且userGroup返回的对象没有返回值)
const userInDefaultGroup = dynamicItem.orig?.modules.module_author.following
&& Object.entries(userGroup).length === 0;
if (!userInDefaultGroup) {
continue;
}
/*
* 判断是否还存在该UP的抽奖动态
* */
const existLotteryWithSameUser = Boolean(lotteryInfoList.find(item => item.sender_uid === lotteryInfo.sender_uid));
if (existLotteryWithSameUser) {
continue;
}
// 不存在, 则取消关注
await this.api_UnfollowUser(lotteryInfo.sender_uid, csrf);
this.notify('取消关注', `已取消关注用户: ${dynamicItem.orig?.modules.module_author.name}`, () => {
GM_openInTab(`https://space.bilibili.com/${lotteryInfo.sender_uid}`);
});
}
}
/**
* 删除动态
*/
async api_DeleteDynamic(dynamicId, csrf) {
const res = await this.fetch({
url: 'https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/rm_dynamic',
method: 'POST',
param: {
dynamic_id: dynamicId,
csrf_token: csrf,
csrf,
},
});
if (res && res.code !== 0) {
throw new Error(`删除失败: ${res.message}`);
}
}
/**
* 取关用户
*/
async api_UnfollowUser(uid, csrf) {
const res = await this.fetch({
url: 'https://api.bilibili.com/x/relation/modify',
method: 'POST',
param: {
fid: uid,
act: 2,
re_src: 11,
csrf,
},
});
if (res && res.code !== 0) {
throw new Error(`取关失败: ${res.message}`);
}
}
/**
* 查询用户所在分组
*/
async api_GetUserInGroup(uid) {
const res = await this.fetch({
url: 'https://api.bilibili.com/x/relation/tag/user',
method: 'GET',
param: {
fid: uid,
},
});
if (res && res.code !== 0) {
throw new Error(`获取分组失败: ${res.message}`);
}
return res.data;
}
}
// @ts-ignore
// noinspection JSAnnotator
return new Promise(async (resolve, reject) => {
// 获取用户信息
const userInfoGetter = new UserInfoGetter();
const csrf = await userInfoGetter.getCsrf();
const uid = await userInfoGetter.getUid();
// 获取所有用户抽奖动态
const dynamicGetter = new DynamicGetter();
const lotteryDynamicList = await dynamicGetter.getAllLotteryDynamic(uid);
console.log(`抽奖动态总数: ${lotteryDynamicList.length}`, lotteryDynamicList);
// 删除动态
const deleteController = new DeleteController();
await deleteController.deleteDynamicList(lotteryDynamicList, csrf);
});