코드 설명
웹 브라우저가 아닌 파이썬 리퀘스트 모듈을 사용해 안정성을 높이 엔트리 이야기에 글을 15분마다 자동으로 홍보해주는 파이썬 코드입니다.
서치 애프터는 귀찮아서 구현 안했으니까 알아서 하시길
코드
더보기
더보기
/**
* 엔트리(playentry.org) 계정 전체 정리 스크립트 (최종 통합본 V5)
* 1. xToken 및 userId 동적 추출 (지시된 방식)
* 2. 자신의 작품(Projects) 전체 삭제 (진행률 및 이름 출력)
* 3. 자신의 커뮤니티 글(Entry Story, Q&A, Tips) 전체 삭제 (진행률 및 제목/내용 출력)
* 4. 자신의 팔로잉(Followings) 전체 언팔로우 (진행률 및 닉네임 출력)
* 5. 자신의 팔로워(Followers) 전체 삭제 (진행률 및 닉네임 출력)
*/
(async () => {
const colors = {
primary: "#00c73c",
info: "#3da9ff",
warn: "#ffb900",
error: "#ff5252",
sub: "#a0a0a0",
text: "#e0e0e0"
};
console.log("%c[Entry Total Cleaner V5] 작업을 시작합니다...", `color: ${colors.primary}; font-weight: bold; font-size: 18px; text-shadow: 1px 1px 2px rgba(0,0,0,0.5);`);
const DELAY_TIME = 1000;
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
async function getCsrfToken() {
const nextData = document.getElementById("__NEXT_DATA__");
if (nextData) {
const nj = JSON.parse(nextData.innerText);
return nj.props.initialProps.csrfToken;
}
const res = await fetch("https://playentry.org/");
const text = await res.text();
const match = text.match(/name="csrf-token" content="([^"]+)"/);
return match ? match[1] : "";
}
try {
// [1] xToken 추출
console.log(`%c[1/6] xToken 추출 중...`, `color: ${colors.info};`);
const xToken = await fetch("https://playentry.org/")
.then(r => r.text())
.then(t => {
t = t.substr(t.indexOf('"xToken"') + 10);
return t.substr(0, t.indexOf('"'));
});
if (!xToken) throw new Error("xToken 추출 실패");
// [2] userId 추출
console.log(`%c[2/6] userId 추출 중...`, `color: ${colors.info};`);
let userId = "";
try {
const urlParts = location.href.split("/");
if (urlParts[4]) userId = urlParts[4].substr(0, 24);
} catch (e) {}
if (!userId) {
const csrf = await getCsrfToken();
const topicRes = await fetch("https://playentry.org/graphql", {
method: "POST",
headers: { "content-type": "application/json", "csrf-token": csrf, "x-token": xToken },
body: JSON.stringify({
query: `query SELECT_TOPICS($pageParam: PageParam){ topicList(pageParam: $pageParam) { list { target } } }`,
variables: { pageParam: { display: 1 } }
})
}).then(r => r.json());
userId = topicRes?.data?.topicList?.list[0]?.target;
}
if (!userId) throw new Error("userId 추출 실패");
console.log(`%c[Info] 대상 userId: %c${userId}`, `color: ${colors.sub};`, `color: ${colors.primary}; font-weight: bold;`);
const getHeaders = async () => ({
"accept": "*/*",
"content-type": "application/json",
"csrf-token": await getCsrfToken(),
"x-token": xToken
});
// [3] 작품 삭제
const projectRes = await fetch("https://playentry.org/graphql", {
method: "POST",
headers: await getHeaders(),
body: JSON.stringify({
query: `query SELECT_USER_PROJECTS($user: String!, $pageParam: PageParam) {
userProjectList(user: $user, pageParam: $pageParam) {
list { id name }
}
}`,
variables: { user: userId, pageParam: { display: 200, sort: "created" } }
})
}).then(r => r.json());
const projects = projectRes?.data?.userProjectList?.list || [];
console.group(`%c[3/6] 작품 삭제 진행 (${projects.length}개)`, `color: ${colors.info}; font-weight: bold;`);
for (let i = 0; i < projects.length; i++) {
const p = projects[i];
console.log(`%c(${i + 1}/${projects.length}) %c삭제 중: %c${p.name || p.id}`, `color: ${colors.info};`, `color: ${colors.sub};`, `color: ${colors.text}; font-weight: bold;`);
await fetch("https://playentry.org/graphql", {
method: "POST",
headers: await getHeaders(),
body: JSON.stringify({
query: `mutation DELETE_PROJECT($id: ID!) { deleteProject(id: $id) { status result } }`,
variables: { id: p.id }
})
});
await delay(DELAY_TIME);
}
console.groupEnd();
// [4] 커뮤니티 글 삭제 (Free, QnA, Tips 통합)
const categories = ["free", "qna", "tips"];
let allDiscussions = [];
for (const cat of categories) {
const res = await fetch("https://playentry.org/graphql", {
method: "POST",
headers: await getHeaders(),
body: JSON.stringify({
query: `query SELECT_DISCUSS_LIST($user: String!, $category: String, $pageParam: PageParam) {
discussList(user: $user, category: $category, pageParam: $pageParam) {
list { id title content category }
}
}`,
variables: { user: userId, category: cat, pageParam: { display: 200, sort: "created" } }
})
}).then(r => r.json());
allDiscussions = allDiscussions.concat(res?.data?.discussList?.list || []);
}
console.group(`%c[4/6] 커뮤니티 글 삭제 진행 (${allDiscussions.length}개)`, `color: ${colors.info}; font-weight: bold;`);
for (let i = 0; i < allDiscussions.length; i++) {
const d = allDiscussions[i];
// 엔트리이야기(free)는 content 출력, 나머지는 title 출력
const logTitle = d.category === "free" ? d.content.replace(/<[^>]*>?/gm, '').substr(0, 20) : (d.title || d.id);
console.log(`%c(${i + 1}/${allDiscussions.length}) %c글 삭제 중: %c${logTitle}`, `color: ${colors.info};`, `color: ${colors.sub};`, `color: ${colors.text};`);
await fetch("https://playentry.org/graphql", {
method: "POST",
headers: await getHeaders(),
body: JSON.stringify({
query: `mutation REMOVE_DISCUSS($id: ID) { removeDiscuss(id: $id) { id } }`,
variables: { id: d.id }
})
});
await delay(DELAY_TIME);
}
console.groupEnd();
// [5] 팔로잉 언팔로우
const followingRes = await fetch("https://playentry.org/graphql", {
method: "POST",
headers: await getHeaders(),
body: JSON.stringify({
query: `query SELECT_FOLLOWINGS($user: String, $query: String, $pageParam: PageParam, $searchAfter: JSON){
followings(user: $user, query: $query, pageParam: $pageParam, searchAfter: $searchAfter) {
list { id follow { id nickname } }
}
}`,
variables: { user: userId, pageParam: { display: 100 } }
})
}).then(r => r.json());
const followings = followingRes?.data?.followings?.list || [];
console.group(`%c[5/6] 팔로잉 언팔로우 진행 (${followings.length}명)`, `color: ${colors.info}; font-weight: bold;`);
for (let i = 0; i < followings.length; i++) {
const f = followings[i];
console.log(`%c(${i + 1}/${followings.length}) %c언팔로우: %c${f.follow.nickname}`, `color: ${colors.info};`, `color: ${colors.sub};`, `color: ${colors.text};`);
await fetch("https://playentry.org/graphql", {
method: "POST",
headers: await getHeaders(),
body: JSON.stringify({
query: `mutation UNFOLLOW($id: ID) { unfollow(id: $id) { status } }`,
variables: { id: f.id }
})
});
await delay(DELAY_TIME);
}
console.groupEnd();
// [6] 팔로워 삭제
const followerRes = await fetch("https://playentry.org/graphql", {
method: "POST",
headers: await getHeaders(),
body: JSON.stringify({
query: `query SELECT_FOLLOWERS($user: String, $query: String, $pageParam: PageParam, $searchAfter: JSON){
followers(user: $user, query: $query, pageParam: $pageParam, searchAfter: $searchAfter) {
list { id user { id nickname } }
}
}`,
variables: { user: userId, pageParam: { display: 100 } }
})
}).then(r => r.json());
const followers = followerRes?.data?.followers?.list || [];
console.group(`%c[6/6] 팔로워 삭제 진행 (${followers.length}명)`, `color: ${colors.info}; font-weight: bold;`);
for (let i = 0; i < followers.length; i++) {
const f = followers[i];
console.log(`%c(${i + 1}/${followers.length}) %c팔로워 삭제 중: %c${f.user.nickname}`, `color: ${colors.info};`, `color: ${colors.sub};`, `color: ${colors.text};`);
await fetch("https://playentry.org/graphql", {
method: "POST",
headers: await getHeaders(),
body: JSON.stringify({
query: `mutation UNFOLLOW($id: ID) { unfollow(id: $id) { status } }`,
variables: { id: f.id }
})
});
await delay(DELAY_TIME);
}
console.groupEnd();
console.log(`%c--------------------------------------------------`, `color: ${colors.sub};`);
console.log(`%c[최종 완료] 모든 작업이 성공적으로 끝났습니다.`, `color: ${colors.primary}; font-weight: bold; font-size: 16px;`);
} catch (error) {
console.error(`%c[오류] 실행 중 문제가 발생했습니다:`, `color: ${colors.error}; font-weight: bold;`, error);
}
})();
코드 사용법
콘솔에다가 입력
참고 사항
더보기
더보기
이 코드를 사용해 일어나는 피해와 책임은 모두 사용자에게 있습니다.
또한 엔트리는 커뮤니티 가이드라인 개정을 통해 자동화된 수단 사용을 지양하고 있으니 연구 목적으로 참고바랍니다.
기타 코드 문의는 댓글로 해주세요.