Last active
March 21, 2022 11:47
-
-
Save MahouSirin/35de14fe03e0ee7f33fdea14a197adc6 to your computer and use it in GitHub Desktop.
몰라레후
This file contains 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
// ==UserScript== | |
// @name Arca Commenter | |
// @version 0.0.3 | |
// @author Raiden-Ei | |
// @description Arca Live Extension | |
// @match https://*.arca.live/* | |
// @match https://arca.live/* | |
// @exclude-match https://st*.arca.live/* | |
// @namespace Raiden-Ei | |
// @noframes | |
// @run-at document-start | |
// @grant GM_xmlhttpRequest | |
// @grant unsafeWindow | |
// ==/UserScript== | |
(async () => { | |
// Element 생성 | |
const d = await waitForElm('.edit-menu'); | |
const lBtn = document.createElement('a'); | |
const icon = document.createElement('span'); | |
const sep = document.createElement('span'); | |
// 속성 설정 | |
lBtn.setAttribute('href', 'javascript:void(0)'); | |
lBtn.setAttribute('class', 'cmtLottery'); | |
lBtn.addEventListener('click', (e) => { | |
e.preventDefault(); | |
initComment(); | |
}); | |
lBtn.innerHTML = '<span id="btnWrap"> 추첨</span>'; | |
icon.setAttribute('class', 'ion-beer'); | |
sep.setAttribute('class', 'sep'); | |
// 삽입 | |
lBtn.prepend(icon); | |
if (d.childElementCount) d.prepend(sep); | |
d.prepend(lBtn); | |
})(); | |
// 내부 함수 영역 | |
// 코멘트 처리 초기 부분 | |
async function initComment() { | |
const rewardCount = Number(prompt([ | |
'당첨 대상자가 몇 명인지 지정해주세요. (기본값: 1)', | |
].join('\n')) || 1); | |
console.log(rewardCount); | |
if (Number.isNaN(rewardCount) || rewardCount < 1 || !Number.isFinite(rewardCount)) { | |
return alert('올바른 값이 아닙니다.'); | |
} | |
const invalidAfter = Date.parse(prompt([ | |
'언제까지 유효한 참여로 칠 것인지 입력해주세요', | |
'날짜는 ISO8601 포맷을 따르며, 올바르지 않은 값인 경우 무시됩니다.', | |
].join('\n'))); | |
console.log(invalidAfter); | |
let excludeList = prompt([ | |
'당첨 목록에서 제외할 사용자의 이름을 쉼표 (,)로 구분해주세요.', | |
'비로그인 사용자는 자동으로 대상에서 제외됩니다.', | |
'예시: HoYoLAB, ㅇㅇ#11111111', | |
].join('\n')); | |
excludeList = (excludeList === '' || !excludeList) ? [] : excludeList.split(',').map((item) => item.trim()); | |
const path = new URL(location.href).pathname.split('/'); | |
const boardId = path[2], articleId = path[3]; | |
document.querySelector('#btnWrap').innerHTML = ' <b>추첨중..</b>'; | |
const res = await fetchComment(boardId, articleId, rewardCount, excludeList, invalidAfter); | |
// 버튼 HTML 원상복구 | |
document.querySelector('#btnWrap').innerHTML = ' 추첨'; | |
// 최종 알림 | |
alert(`당첨자: ${res.join(', ') || '(당첨자 없음)'}`); | |
} | |
// 실제 API에서 코멘트 받아오는 함수 | |
async function fetchComment(boardId, articleId, rewardCount, excludeList, invalidAfter) { | |
let repeat = true, lastId = undefined, commentList = []; | |
while (repeat) { | |
const comments = await fetch(`https://arca.live/api/app/list/comment/${boardId}/${articleId}?limit=100&since=${lastId}`, { | |
cache: 'no-cache', | |
credentials: 'include', | |
headers: { | |
'User-Agent': 'live.arca.android/0.5.23' | |
}, | |
}).then((res) => res.json()) | |
.then((res) => { | |
if (!Number.isNaN(invalidAfter)) return res | |
.filter((cmt) => Date.parse(cmt.createdAt) <= invalidAfter); | |
return res; | |
}); | |
commentList.push(comments); | |
if (!Array.isArray(comments) || comments.length < 100) break; | |
lastId = comments[comments.length - 1].id; | |
await waitFor(200); | |
} | |
// Flat 및 중복 제거 | |
commentList = [...new Set(commentList.flat(Infinity) | |
.filter((elem) => !elem.ip) | |
.map((elem) => elem.publicId ? `${elem.nickname}#${elem.publicId}` : elem.nickname))]; | |
console.log(commentList); | |
// 추첨 타임 | |
const rewardList = []; | |
// 제외 대상자 배열에서 제거 | |
console.log(excludeList); | |
for (const value of excludeList) { | |
commentList.splice(commentList.findIndex((i) => i === value), 1); | |
} | |
// 랜덤 추첨 | |
rewardCount = rewardCount <= commentList.length ? rewardCount : 1; | |
for (let i = 0; i < rewardCount; i++) { | |
const reward = commentList.random(); | |
rewardList.push(reward); | |
commentList.splice(commentList.findIndex((i) => i === reward), 1); | |
} | |
return rewardList; | |
} | |
// MutationObserver를 활용한 Element 대기 | |
function waitForElm(selector) { | |
return new Promise((resolve) => { | |
if (document.querySelector(selector)) { | |
return resolve(document.querySelector(selector)); | |
} | |
const observer = new MutationObserver((mutations) => { | |
if (document.querySelector(selector)) { | |
resolve(document.querySelector(selector)); | |
observer.disconnect(); | |
} | |
}); | |
observer.observe(document, { | |
childList: true, | |
subtree: true, | |
}); | |
}); | |
} | |
// Sleep 함수 | |
function waitFor(ms) { | |
return new Promise((r) => setTimeout(r, ms)); | |
} | |
// 프로토타입 함수 재정의 | |
Object.defineProperty(Array.prototype, 'random', { | |
value() { | |
return this[Math.floor(Math.random() * this.length)]; | |
}, | |
configurable: true, | |
writable: true | |
}); | |
Object.defineProperty(Object.prototype, 'filter', { | |
value(fn) { | |
return Object.keys(this) | |
.filter(key => fn(this)) | |
.reduce((res, key) => (res[key] = this[key], res), {}); | |
}, | |
configurable: true, | |
writable: true, | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment