Last active
November 22, 2023 15:38
-
-
Save yingziwu/4079fb0ce4521499a257e389a912c613 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
(async () => { | |
const lotteryStatusUrl = 'https://bgme.me/@bgme/105206731005287886'; // 抽奖嘟文URL | |
const lotteryType = 'reblog'; // 抽奖类型:转发(reblog),收藏(favourite) | |
const candidateNumber = 5; // 候选中奖者人数 | |
main(); | |
async function main() { | |
const domain = document.location.hostname; | |
const token = JSON.parse(document.querySelector('#initial-state').text).meta.access_token; | |
const API = { | |
'verify': `https://${domain}/api/v1/accounts/verify_credentials`, | |
'notifications': `https://${domain}/api/v1/notifications`, | |
'status': `https://${domain}/api/v1/statuses/`, | |
}; | |
const searchParamMap = new Map([ | |
['reblog', 'exclude_types[]=follow&exclude_types[]=follow_request&exclude_types[]=favourite&exclude_types[]=mention&exclude_types[]=poll'], | |
['favourite', 'exclude_types[]=follow&exclude_types[]=follow_request&exclude_types[]=reblog&exclude_types[]=mention&exclude_types[]=poll'], | |
]); | |
const searchParam = new URLSearchParams(searchParamMap.get(lotteryType)); | |
const statusID = lotteryStatusUrl.match(/(\d+)$/)[0]; | |
let statusTNumber; | |
let lotterLog; | |
logout(`开始抽奖……\n当前浏览器:${navigator.userAgent}\n开始时间:${(new Date()).toISOString()}`); | |
logout(`抽奖嘟文:${lotteryStatusUrl},抽奖类型:${lotteryType},候选中奖者人数:${candidateNumber}\n\n`); | |
let verify; | |
[verify, statusTNumber] = await doVerify(API, lotteryStatusUrl, statusID, lotteryType, statusTNumber); | |
if (!verify) { | |
throw Error('抽奖嘟文非本人发送'); | |
} | |
const matchAccouts = await getmatchAccouts(API, statusID, statusTNumber, searchParam); | |
randomTest(matchAccouts); | |
const luckGuys = getLuckGuy(matchAccouts); | |
const cadidatesText = getCandidate(luckGuys, candidateNumber); | |
const notificationText = `嘿!感谢各位参与本次小抽奖活动。\n${cadidatesText}\n\n希望这条艾特您的信息没有造成骚扰,如您对奖品感兴趣请和我私信联系吧?`; | |
await postStatus(notificationText, statusID, 'public'); | |
logout(`抽奖结束!\n结束时间:${(new Date()).toISOString()}`); | |
saveFile(lotterLog, `lotterLog-${Date.now()}.log`, 'text/plain; charset=utf-8'); | |
async function doVerify(API, lotteryStatusUrl, statusID, lotteryType, statusTNumber) { | |
const v = await request(API.verify); | |
const s = await request(`${API.status}${statusID}`); | |
logout(`抽奖嘟文URL:${lotteryStatusUrl}\n回复数:${s.replies_count},转发数:${s.reblogs_count},收藏数:${s.favourites_count}`); | |
const numbers = new Map([['reblog', s.reblogs_count], ['favourite', s.favourites_count]]); | |
if (numbers.has(lotteryType)) { | |
statusTNumber = numbers.get(lotteryType); | |
} else { | |
throw Error('抽奖类型设置不正确'); | |
} | |
if (v.acct === s.account.acct && (new URL(s.account.url)).hostname === (new URL(lotteryStatusUrl)).hostname) { | |
return [true, statusTNumber]; | |
} else { | |
return [false, statusTNumber]; | |
} | |
} | |
async function getmatchAccouts(API, statusID, statusTNumber, searchParam) { | |
const matchAccouts = []; | |
while (matchAccouts.length !== statusTNumber) { | |
const nlist = await request(`${API.notifications}?${searchParam.toString()}`); | |
if (!nlist.length) { | |
logout('nlist.lenth === 0, end loop!!!') | |
break; | |
} | |
const nextID = nlist.slice(-1)[0].id; | |
searchParam.set('max_id', nextID); | |
nlist.forEach((obj) => { | |
if (obj.status && obj.status.id === statusID) { | |
matchAccouts.push(obj.account.acct); | |
} | |
}); | |
} | |
matchAccouts.sort(); | |
logout(`共有${matchAccouts.length}名符合条件的抽奖参与者\n她们是:`); | |
matchAccouts.forEach(logout); | |
return matchAccouts; | |
} | |
function randomTest(matchAccouts) { | |
logout('随机函数测试:'); | |
const testResults = []; | |
const n = 20; | |
for (let i = 0; i < (n * 20); i++) { | |
testResults.push(getRandomIndex(matchAccouts)); | |
} | |
for (let i = 0; i < n; i++) { | |
logout(testResults.slice((i * 20), ((i + 1) * 20)).join(', ')); | |
} | |
} | |
function getLuckGuy(matchAccouts) { | |
const luckGuys = []; | |
const n = matchAccouts.length; | |
const luckGuysMap = new Map(); | |
for (let i = 0; i < (n * 100); i++) { | |
const luckGuy = matchAccouts[getRandomIndex(matchAccouts)]; | |
if (luckGuysMap.get(luckGuy)) { | |
luckGuysMap.set(luckGuy, luckGuysMap.get(luckGuy) + 1); | |
} else { | |
luckGuysMap.set(luckGuy, 1); | |
} | |
} | |
luckGuysMap.forEach((v, k, map) => { | |
luckGuys.push([k, v]); | |
}); | |
luckGuys.sort((a, b) => (b[1] - a[1])); | |
return luckGuys; | |
} | |
function getCandidate(luckGuys, candidateNumber) { | |
if (candidateNumber > luckGuys.length) { | |
throw Error('抽奖参与者太少!') | |
} | |
let output = '本次抽奖备选中奖者:'; | |
for (let i = 0; i < candidateNumber; i++) { | |
output = `${output}\nNo.${i + 1}:@${luckGuys[i][0]} (幸运指数:${luckGuys[i][1]})`; | |
} | |
logout(output); | |
return output; | |
} | |
function getRandomIndex(arr) { | |
return Math.floor(arr.length * Math.random()); | |
} | |
async function request(url) { | |
logout(`正在请求:${url}`); | |
const resp = await fetch(url, { | |
headers: { | |
Authorization: `Bearer ${token}`, | |
}, | |
method: 'GET', | |
}); | |
const date = new Date(resp.headers.get('date')); | |
const request_id = resp.headers.get('x-request-id'); | |
const runtime = resp.headers.get('x-runtime'); | |
const ratelimit_remaining = resp.headers.get('x-ratelimit-remaining'); | |
logout(`请求 ${url} 完成\n请求时间:${date.toISOString()},API剩余限额:${ratelimit_remaining},x-runtime:${runtime},x-request-id:${request_id}`); | |
return await resp.json(); | |
} | |
function logout(text) { | |
console.log(text); | |
if (lotterLog) { | |
lotterLog = lotterLog + '\n' + text; | |
} else { | |
lotterLog = text; | |
} | |
} | |
function saveFile(data, filename, type) { | |
const file = new Blob([data], {type: type}); | |
const a = document.createElement('a'); | |
const url = URL.createObjectURL(file); | |
a.href = url; | |
a.download = filename; | |
document.body.appendChild(a); | |
a.click(); | |
setTimeout(function() { | |
document.body.removeChild(a); | |
window.URL.revokeObjectURL(url); | |
}, 0); | |
} | |
async function postStatus(text, in_reply_to_id, visibility='public') { | |
const postDate = { | |
'in_reply_to_id': in_reply_to_id, | |
'media_ids': [], | |
'poll': null, | |
'sensitive': false, | |
'spoiler_text': '', | |
'status': text, | |
'visibility': visibility, | |
}; | |
logout(`发送嘟文中……\n嘟文内容:\n${text}\n回复嘟文ID:${in_reply_to_id}\n可见范围:${visibility}`); | |
const resp = await fetch(API.status, { | |
'headers': { | |
'Content-Type': 'application/json;charset=utf-8', | |
'Authorization': `Bearer ${token}`, | |
}, | |
'body': JSON.stringify(postDate), | |
'method': 'POST', | |
'mode': 'cors', | |
}); | |
const date = new Date(resp.headers.get('date')); | |
const request_id = resp.headers.get('x-request-id'); | |
const runtime = resp.headers.get('x-runtime'); | |
const ratelimit_remaining = resp.headers.get('x-ratelimit-remaining'); | |
logout(`嘟文发送完成,完成请求时间:${date.toISOString()},API剩余限额:${ratelimit_remaining},x-runtime:${runtime},x-request-id:${request_id}`); | |
return await resp.json(); | |
} | |
} | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment