Skip to content

Instantly share code, notes, and snippets.

@MahouSirin
Last active March 21, 2022 11:47
Show Gist options
  • Save MahouSirin/35de14fe03e0ee7f33fdea14a197adc6 to your computer and use it in GitHub Desktop.
Save MahouSirin/35de14fe03e0ee7f33fdea14a197adc6 to your computer and use it in GitHub Desktop.
몰라레후
// ==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">&nbsp;추첨</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 = '&nbsp;<b>추첨중..</b>';
const res = await fetchComment(boardId, articleId, rewardCount, excludeList, invalidAfter);
// 버튼 HTML 원상복구
document.querySelector('#btnWrap').innerHTML = '&nbsp;추첨';
// 최종 알림
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