Created
August 20, 2023 11:37
-
-
Save ouyen/da54a0bb25624b5dc158030af3055945 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint-disable no-unused-vars */ | |
/* eslint-disable no-extra-semi */ | |
/* eslint-disable no-console */ | |
// ==UserScript== | |
// @name 柠檬精僵尸清道夫一键僵尸粉清除助手v2023.06.30 | |
// @namespace https://blog.csdn.net/wangwei490202517/category_10507403.html?spm=1001.2014.3001.5482 | |
// @version 1.0.9 | |
// @description 一键批量删除僵尸粉:兴趣推荐、互相关注、不活跃用户、垃圾昵称、等塞粉微博粉丝记录 | |
// @author echo_wx:WZMSLR | |
// @match https://weibo.com/* | |
// @icon https://tva2.sinaimg.cn/crop.2.18.304.304.180/ad573135jw8f33qt1c74sj208k08z75q.jpg?KID=imgbed,tva&Expires=1671381776&ssig=du6gqkc8OF | |
// @license MIT | |
// @grant none | |
// ==/UserScript== | |
let removeTargetFans = false; /*是否删除符合条件的粉丝,默认关闭*/ | |
let deepSearchMode =false; /*是否开启深度清理模式*/ | |
let ignoreLikes = true; /*是否无视用户最近点赞*/ | |
let minDayCount = 180; /*最小不活跃天数*/ | |
let fansMinCount = 2; /*最小粉丝数*/ | |
let weiboMinCount = 3; /*最小微博数*/ | |
let totalCount = 0; | |
let checkIsFromRecommended = true; /*是否检查通过推荐关注*/ | |
let checkIsInactive = true; /*是否检查不活跃用户*/ | |
let checkFansOrWeibo = false; /*是否只判断粉丝数或微博数*/ | |
let checkIsFollowing =true; /*是否检查相互关注*/ | |
let checkIsDefaultIcon = false; /*是否检查默认头像*/ | |
let checkIsNormalFollowed = false; /*是否检查关注来源*/ | |
let isError = false; | |
let isRunning = false; | |
let userID = 0; | |
let zeroFanPageCount = 0; | |
function clearLog() | |
{ | |
let logger = document.querySelector("p.logger"); | |
logger.innerHTML = ""; | |
} | |
function logError(msg){ | |
let logger = document.querySelector("p.logger"); | |
let parent = logger.parentElement; | |
logger.innerHTML +="<span style='color: Crimson; font-weight:bold;'>" + msg + "</span><br>"; | |
parent.scrollTop = parent.scrollHeight; | |
} | |
function logSuccessMsg(msg){ | |
let logger = document.querySelector("p.logger"); | |
let parent = logger.parentElement; | |
logger.innerHTML +="<pre style='tab-size: 15em; color: forestgreen;'>" + msg + "</pre>"; | |
parent.scrollTop = parent.scrollHeight; | |
} | |
function logImportantMsg(msg) | |
{ | |
let logger = document.querySelector("p.logger"); | |
let parent = logger.parentElement; | |
logger.innerHTML +="<pre style='font-weight:bold'>" + msg + "</pre>"; | |
parent.scrollTop = parent.scrollHeight; | |
} | |
function logMsg(msg){ | |
let logger = document.querySelector("p.logger"); | |
let parent = logger.parentElement; | |
logger.innerHTML +="<pre style='tab-size: 15em;'>" + msg + "</pre>"; | |
parent.scrollTop = parent.scrollHeight; | |
} | |
async function removeFanMainFunc() { | |
const delay = ms => new Promise(res => setTimeout(res, ms)); | |
const minute = 1000 * 60; | |
const hour = minute * 60; | |
const day = hour * 24; | |
clearTimeout(); | |
isError = false; | |
let startIndex = 0; | |
let pageIndex = 1; | |
while(true) { | |
if(!isRunning) | |
{ | |
if (removeTargetFans) | |
logError("【WX:WZMSLR】用户退出程序。总计清理了 " + totalCount + " 个疑似僵尸粉"); | |
else | |
logError("【WX:WZMSLR】用户退出程序。总计发现了 " + totalCount + " 个疑似僵尸粉"); | |
break; | |
} | |
await delay(300); | |
let fanListJson = await fetch( | |
'https://weibo.com/ajax/friendships/friends?relate=fans&count=20&type=fans&fansSortType=followTime' + | |
'&page=' + pageIndex + | |
'&uid=' + userID, | |
{ | |
method: "GET", | |
}).then(response => response.json()) | |
.catch((e) => { | |
}); | |
if (fanListJson == null || fanListJson.ok != 1 || fanListJson['users'].length === 0) { | |
fanListJson = await fetch( | |
'https://weibo.com/ajax/friendships/friends?relate=fans&count=20&type=fans&fansSortType=followTime' + | |
'&page=' + pageIndex + | |
'&uid=' + userID, | |
{ | |
method: "GET", | |
}).then(response => response.json()) | |
.catch((e) => { | |
}); | |
if (fanListJson == null || fanListJson.ok != 1) { | |
if (removeTargetFans) | |
logError("【WX:WZMSLR】请求异常,退出程序。总计清理了 " + totalCount + " 个疑似僵尸粉"); | |
else | |
logError("【WX:WZMSLR】请求异常,退出程序。总计发现了 " + totalCount + " 个疑似僵尸粉"); | |
break; | |
} | |
} | |
let fans = fanListJson['users']; | |
let count = 0; | |
for (let i = startIndex; i < fans.length; i++) { | |
if(!isRunning) | |
{ | |
if (removeTargetFans) | |
logError("【柠檬精】提醒:用户退出程序。总计清理了 " + totalCount + " 个疑似僵尸粉"); | |
else | |
logError("【柠檬精】提醒:用户退出程序。总计发现了 " + totalCount + " 个疑似僵尸粉"); | |
return; | |
} | |
let isFromRecommended = false; | |
let isInactive = false; | |
let isFollowing = false; | |
let isDefaultIcon = false; | |
let isNormalFollowed = false; | |
let isIrrelevant = false; | |
let fansCount, weiboCount; | |
/*关注来源*/ | |
let originSrc = fans[i]['origin_source_info']; | |
isNormalFollowed = originSrc != null && originSrc['text'] !== "兴趣推荐" && originSrc['text'] !== "微博推荐"; | |
/*是否为铁粉*/ | |
if(fans[i].fansIcon != null) | |
{ | |
if(fans[i].fansIcon == "loyal_fans") | |
continue; | |
} | |
/*是否为非推荐的会员*/ | |
if (isNormalFollowed && (fans[i]['mbtype'] === 12)) | |
continue; | |
/*关注来源是否为兴趣推荐*/ | |
if (checkIsFromRecommended) { | |
isFromRecommended = originSrc != null && originSrc['text'] === "兴趣推荐"; | |
} | |
let fanID = fans[i]['id']; | |
let fanName = fans[i]['name']; | |
/*粉丝数与微博数*/ | |
if (checkIsInactive) { | |
fansCount = fans[i]['followers_count']; | |
weiboCount = fans[i]['statuses_count']; | |
if (checkFansOrWeibo) | |
isInactive = fansCount < fansMinCount || weiboCount < weiboMinCount; | |
else | |
isInactive = fansCount < fansMinCount && weiboCount < weiboMinCount || weiboCount === 0 || fansCount === 0; | |
} | |
/*是否为默认头像*/ | |
if (checkIsDefaultIcon) { | |
let icon = fans[i]['avatar_hd']; | |
isDefaultIcon = icon != null && icon.includes("/default/images/default_avatar_"); | |
} | |
/*是否互相关注*/ | |
if (checkIsFollowing) { | |
isFollowing = fans[i]['following']; | |
} | |
let isZombie = false; | |
if (!isFollowing) | |
if (!checkIsNormalFollowed || (checkIsNormalFollowed && !isNormalFollowed)) | |
if (isDefaultIcon || isFromRecommended || isInactive || isDefaultIcon || (deepSearchMode && weiboCount === 0)) | |
isZombie = true; | |
/*深度检查*/ | |
if (deepSearchMode && !isFollowing && !isZombie) { | |
await delay(600); | |
let weiboJson = await fetch('https://weibo.com/ajax/statuses/mymblog?page=1&feature=0&uid=' + fanID, { | |
method: "GET", | |
}).then(response => response.json()) | |
.catch((e) => { | |
}); | |
if (weiboJson == null || weiboJson.ok != 1) { | |
await delay(800); | |
weiboJson = await fetch('https://weibo.com/ajax/statuses/mymblog?page=1&feature=0&uid=' + fanID, { | |
method: "GET", | |
}).then(response => response.json()) | |
.catch((e) => { | |
}); | |
if (weiboJson == null || weiboJson.ok != 1) { | |
logError(fanName + "主页请求失败,跳过检查"); | |
continue; | |
} | |
} | |
let weiboList = weiboJson.data['list']; | |
let invalidWeiboCount = 0; | |
let validWeiboCount = 0; | |
let currDate = Date.now(); | |
for (let index = 0; index < weiboList.length; index++) { | |
let currWeibo = weiboList[index]; | |
let weiboSrc = currWeibo['source']; | |
if(weiboSrc != null && weiboSrc.includes("生日动态")) | |
continue; | |
if (weiboSrc != null && (weiboSrc.includes("渔场") || | |
weiboSrc.includes("活动") || weiboSrc.includes("森林") || weiboSrc.includes("微博积分") || | |
weiboSrc.includes("支付宝") || weiboSrc.includes("淘宝") || weiboSrc.includes("芭芭农场") || | |
weiboSrc.includes("任务") || weiboSrc.includes("签到") || weiboSrc.includes("新浪游戏") || | |
weiboSrc.includes("微博游戏") || weiboSrc.includes("红包") || weiboSrc.includes("微博会员") || | |
weiboSrc.includes("微博运动") || weiboSrc.includes("熊猫守护者") || weiboSrc.includes("点淘"))) { | |
invalidWeiboCount++; | |
continue; | |
} | |
if (ignoreLikes && currWeibo['title'] != null && currWeibo['title']['text'].includes("赞")) | |
continue; | |
let createTime = currWeibo['created_at']; | |
if (currWeibo['retweeted_status'] != null) { | |
createTime = currWeibo['retweeted_status']['created_at']; | |
} | |
if (Math.round((currDate - new Date(createTime)) / day) <= minDayCount) { | |
validWeiboCount++; | |
} | |
invalidWeiboCount--; | |
} | |
if (invalidWeiboCount > 3) { | |
isIrrelevant = true; | |
isZombie = true; | |
} | |
if (validWeiboCount < 3) { | |
isInactive = true; | |
isZombie = true; | |
} | |
} | |
await delay(300); | |
if (isZombie) { | |
let reason = "原因: "; | |
if (isFromRecommended) | |
reason += " 兴趣推荐"; | |
if (isInactive) | |
reason += " 不活跃用户"; | |
if (isIrrelevant) | |
reason += " 不相关用户"; | |
if (isDefaultIcon) | |
reason += " 默认头像"; | |
if (removeTargetFans) { | |
let fData = new FormData(); | |
fData.append("uid", fanID); | |
fData.append("_t", "0"); | |
const json = await fetch('/aj/f/remove?ajwvr=6&__rnd=' + Math.round(new Date().getTime()), { | |
method: "POST", | |
body: fData | |
}).then(response => response.json()) | |
.catch((e) => { | |
}); | |
if (json != null && json.code == 100000) { | |
logSuccessMsg("【WX:WZMSLR】提示:"+ fanName + "\t删除成功 " + reason); | |
count++; | |
totalCount++; | |
} else | |
logError("【WX:WZMSLR】提示:" + fanName + "\t删除失败 "); | |
} else { | |
logMsg("【WX:WZMSLR】提示:"+ fanName + "→疑似僵尸粉 " + reason); | |
count++; | |
totalCount++; | |
} | |
} | |
} | |
if (fans.length > 0) { | |
startIndex = 0; | |
zeroFanPageCount = 0; | |
if (removeTargetFans) { | |
logImportantMsg("【WX:WZMSLR】提醒:第 " + pageIndex + " 页删除了 " + count + " 个粉丝"); | |
if (count > 0) { | |
startIndex = fans.length - count; | |
if (startIndex < 0) startIndex = 0; | |
} else { | |
pageIndex++; | |
} | |
} else { | |
logImportantMsg("【WX:WZMSLR】提醒:第 " + pageIndex + " 页发现了 " + count + " 个疑似僵尸粉"); | |
pageIndex++; | |
} | |
} else { | |
if(zeroFanPageCount < 5) | |
{ | |
zeroFanPageCount++; | |
pageIndex++; | |
}else { | |
if (removeTargetFans) | |
logError("【柠檬精】已到达粉丝列表上限,退出程序。总计清理了 " + totalCount + " 个疑似僵尸粉"); | |
else | |
logError("【柠檬精】已到达粉丝列表上限,退出程序。总计发现了 " + totalCount + " 个疑似僵尸粉"); | |
break; | |
} | |
} | |
} | |
StopFunc(); | |
} | |
function RemoveFans(startIndex){ | |
let p = new Promise(function(){removeFanMainFunc(startIndex)}); | |
} | |
let InitParameters = ()=> | |
{ | |
checkIsFromRecommended = document.body.querySelector("input#checkIsFromRecommended").checked; | |
checkIsFollowing = document.body.querySelector("input#checkIsFollowing").checked; | |
checkIsNormalFollowed = document.body.querySelector("input#checkIsNormalFollowed").checked; | |
checkIsDefaultIcon= document.body.querySelector("input#checkIsDefaultIcon").checked; | |
checkIsInactive = document.body.querySelector("input#checkIsInactive").checked; | |
checkFansOrWeibo = document.body.querySelector("input#checkFansOrWeibo").checked; | |
fansMinCount = parseInt(document.body.querySelector("input#fansMinCount").value); | |
weiboMinCount = parseInt(document.body.querySelector("input#weiboMinCount").value); | |
deepSearchMode = document.body.querySelector("input#deepSearchMode").checked; | |
ignoreLikes = document.body.querySelector("input#ignoreLikes").checked; | |
minDayCount = parseInt(document.body.querySelector("input#minDayCount").value); | |
}; | |
let StartCheckFans = ()=> | |
{ | |
SetMode("running"); | |
InitParameters(); | |
clearLog(); | |
totalCount = 0; | |
removeTargetFans = false; | |
isRunning = true; | |
RemoveFans(0); | |
}; | |
let StartRemoveFans = ()=> | |
{ | |
SetMode("running"); | |
InitParameters(); | |
clearLog(); | |
totalCount = 0; | |
removeTargetFans = true; | |
isRunning = true; | |
RemoveFans(0); | |
}; | |
let StopFunc = ()=> | |
{ | |
SetMode("ready"); | |
clearInterval(); | |
clearTimeout(); | |
isRunning = false; | |
}; | |
let SetDeepSearchParam = ()=>{ | |
let disable = document.body.querySelector("input#deepSearchMode").checked === false; | |
let params = document.body.querySelectorAll("input.DeepSearchParam"); | |
for(let i = 0; i < params.length; i++) | |
params[i].disabled = disable; | |
}; | |
let SetMode = (mode) =>{ | |
let params = document.querySelector("div.parameter").querySelectorAll("input"); | |
let readyPanel = document.querySelector("div.ready"); | |
let runningPanel = document.querySelector("div.running"); | |
if(mode === "running") | |
{ | |
for(let i = 0; i < params.length; i++) params[i].disabled = true; | |
readyPanel.style.display = "none"; | |
runningPanel.style.display = ""; | |
} | |
else if(mode === "ready") | |
{ | |
for(let i = 0; i < params.length; i++) params[i].disabled = false; | |
runningPanel.style.display = "none"; | |
readyPanel.style.display = ""; | |
} | |
}; | |
let ReplaceHTML = ()=> | |
{ | |
let list = document.querySelector("div.woo-box-flex.woo-tab-nav")?.querySelectorAll("a"); | |
let href = document.location.href; | |
if(list == null || list.length !== 5) { | |
if(document.location.href.includes("https://weibo.com/u/")) | |
{ | |
userID = document.location.href.replace("https://weibo.com/u/", ""); | |
let numIndex = 0; | |
for(numIndex = 0; numIndex < userID.length; numIndex++) | |
{ | |
if(userID[numIndex] < '0' || userID[numIndex] > '9') | |
break; | |
} | |
userID = userID.slice(0, numIndex); | |
}else { | |
console.error("无法找到用户id,请打开微博主页再运行代码"); | |
return; | |
} | |
}else{ | |
userID = list[4].href.replace("https://weibo.com/u/", ""); | |
href = "https://weibo.com/u/page/follow/"+ userID +"?relate=fans"; | |
} | |
document.head.innerHTML = '<meta charset="UTF-8"><title>柠檬精僵尸清道夫</title>'; | |
let body = '<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title><script src="RemoveZombiesUI.js"></script></head><body><div class="left" style="position: fixed; display: flex; flex-direction: column;top:0; left:0; bottom:0; width:500px; background-color: rgb(255, 102, 0);"><h1 style="text-align:center; margin: 10px;"> 柠檬精僵尸清道夫<span style="font-size: 20px;">v2023.06.22</span></h1><div class="parameter"><h3>快速清粉参数-博主微博@当时我就没憋住</h3><table style="width: 100%; font-size: 15px;" ><tr><td><input id="checkIsFromRecommended" type="checkbox" style="height: 15px; width: 15px;" checked>清除兴趣推荐用户</td><td><input id="checkIsFollowing" type="checkbox" style="height: 15px; width: 15px;" checked>排除相互关注</td><td><input id="checkIsNormalFollowed" type="checkbox" style="height: 15px; width: 15px;">只检查通过推荐关注</td></tr><tr style="height: 10px;"></tr><tr><td rowspan="2"><input id="checkIsInactive" type="checkbox" style="height: 15px; width: 15px;" checked>清除不活跃用户</td><td> 最低粉丝数<input id="fansMinCount" type="number" style="height: 15px; width: 60px;" min="0" value="2" ></td><td rowspan="2"><input id="checkFansOrWeibo" type="checkbox" style="height: 15px; width: 15px;" >满足任一条件即清除</td></tr><tr><td> 最低微博数<input id="weiboMinCount" type="number" style="height: 15px; width: 60px;" min="0" value="3" ></td></tr><tr><td><input id="checkIsDefaultIcon" type="checkbox" style="height: 15px; width: 15px;">清除默认头像用户</td></tr></table><h3>深度清粉参数-博主微博@当时我就没憋住</h3><table style="width: 100%; font-size: 15px;"><tr><td><input id="deepSearchMode" type="checkbox" style="height: 15px; width: 15px;" οnchange="SetDeepSearchParam();">开启深度清粉</td><td> 最小不活跃天数:<input class="DeepSearchParam" id="minDayCount" type="number" style="height: 15px; width: 40px;" min="0" value="365" disabled></td><td><input class="DeepSearchParam" id="ignoreLikes" type="checkbox" style="height: 15px; width: 15px;" checked disabled>无视用户最近点赞</td></tr></table></div><div class="ready"><button style="position: relative; height: 45px; width:48%; font-size: 16px; margin: 4px 2px;" οnclick="StartRemoveFans();">清粉</button><button style="position: relative; height: 45px; width:48%; font-size: 16px; margin: 4px 2px;" οnclick="StartCheckFans();">开始检测</button></div><div class="running" style="display: none"><button style="position: relative; height: 45px; width:98%; font-size: 16px; margin: 4px 2px;" οnclick="StopFunc();">停止</button></div><div style="position: relative; margin: 4px 2px; flex-grow: 1; width: 100%; background-color: Gold; overflow-y: scroll;"><p class="logger"></p></div><div style="position: absolute; bottom: 0px; height: 1px; width:100%; background-color: black"></div></div><div style="position: fixed; top:0; left: 500px; bottom: 0; right: 0;"><iframe class="web" src="" sandbox="allow-same-origin allow-scripts" style="width:100%; height:100%;"></iframe></div></body></html>'; | |
document.body.innerHTML = body.replaceAll('ο', 'o'); | |
let iframe = document.body.querySelector('iframe.web'); | |
iframe.src = href; | |
iframe.onload=()=>{ iframe.contentDocument.body.querySelector("div[node-type=outer]")?.remove(); } | |
} | |
ReplaceHTML(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment