메이플스토리 인벤 자유 게시판

전체보기

모바일 상단 메뉴

본문 페이지

[수다] 내 글 삭제 기록용

아이콘 아래층아저씨
조회: 101
2026-09-12 22:34:20
(async function(){
    'use strict';

    var Z=[10,10];
    var GAP=300;
    var VERIFY_GAP=700;
    var PAGE_GAP=400;
    var MAX_PAGE=200;
    var NL=String.fromCharCode(10);

    function sleep(ms){
        return new Promise(function(resolve){
            setTimeout(resolve,ms);
        });
    }

    function log(){
        var args=['[글정리]'].concat(Array.prototype.slice.call(arguments));
        console.log.apply(console,args);
    }

    function atLeast(value,limit){
        return Math.max(value,limit)===value;
    }

    function isDigits(value){
        var s=String(value==null?'':value).trim();
        var i;
        var c;

        if(!s){
            return false;
        }

        for(i=0;i<s.length;i++){
            c=s.charCodeAt(i);

            if(c<48 || c>57){
                return false;
            }
        }

        return true;
    }

    function compactText(value){
        var s=String(value==null?'':value);
        var out='';
        var spaced=false;
        var i;
        var c;

        for(i=0;i<s.length;i++){
            c=s.charCodeAt(i);

            if(c===9 || c===10 || c===13 || c===32){
                if(out && !spaced){
                    out+=' ';
                    spaced=true;
                }
            }else{
                out+=s.charAt(i);
                spaced=false;
            }
        }

        return out.trim();
    }

    function compactNoSpace(value){
        var s=String(value==null?'':value);
        var out='';
        var i;
        var c;

        for(i=0;i<s.length;i++){
            c=s.charCodeAt(i);

            if(c!==9 && c!==10 && c!==13 && c!==32){
                out+=s.charAt(i);
            }
        }

        return out.trim();
    }

    function num(value){
        var s=String(value==null?'':value).split(',').join('').trim();
        var sign=1;
        var started=false;
        var digits='';
        var i;
        var c;

        for(i=0;i<s.length;i++){
            c=s.charCodeAt(i);

            if(!started && s.charAt(i)==='-'){
                sign=-1;
                started=true;
                continue;
            }

            if(c>=48 && c<=57){
                digits+=s.charAt(i);
                started=true;
            }else if(digits){
                break;
            }
        }

        if(!digits){
            return NaN;
        }

        return sign*Number(digits);
    }

    function cleanPathParts(pathname){
        var raw=String(pathname||'').split('/');
        var out=[];
        var i;

        for(i=0;i<raw.length;i++){
            if(raw[i]){
                out.push(raw[i]);
            }
        }

        return out;
    }

    function idsFromUrl(value,base){
        try{
            var u=new URL(value,base||location.href);
            var parts=cleanPathParts(u.pathname);
            var index=parts.indexOf('board');

            if(index===-1){
                return null;
            }

            var boardId=parts[index+2]||'';
            var postId=parts[index+3]||'';

            if(!isDigits(boardId)){
                return null;
            }

            return {
                boardId:boardId,
                postId:isDigits(postId)?postId:''
            };

        } catch(error){
            return null;
        }
    }

    var currentUrl=new URL(location.href);

    if(currentUrl.searchParams.get('my')!=='post'){
        alert('내 글 목록 페이지에서 실행하세요.');
        return;
    }

    var BOARD_ID=currentUrl.searchParams.get('come_idx')||'';

    if(!isDigits(BOARD_ID)){
        var currentIds=idsFromUrl(location.href,location.href);

        if(currentIds){
            BOARD_ID=currentIds.boardId;
        }
    }

    if(!isDigits(BOARD_ID)){
        var linksForBoard=document.querySelectorAll('a[href]');
        var boardLinkIndex;

        for(boardLinkIndex=0;boardLinkIndex<linksForBoard.length;boardLinkIndex++){
            var boardIds=idsFromUrl(
                linksForBoard[boardLinkIndex].getAttribute('href'),
                location.href
            );

            if(boardIds && boardIds.postId){
                BOARD_ID=boardIds.boardId;
                break;
            }
        }
    }

    if(!isDigits(BOARD_ID)){
        alert('게시판 번호를 확인할 수 없습니다.');
        return;
    }

    var ignored=new Set();
    var deleted=0;
    var protectedCount=0;
    var skipped=0;

    window.__INVEN_POST_DELETE_STOP__=false;

    var box=document.createElement('div');

    box.style=
        'position:fixed;right:15px;bottom:15px;z-index:999999;'+
        'background:#111;color:#fff;padding:12px 15px;'+
        'font:13px/1.55 monospace;border-radius:7px;'+
        'max-width:500px;white-space:pre-wrap;';

    document.body.appendChild(box);

    function status(text){
        box.textContent=
            '[게시글 정리]'+
            NL+
            text+
            NL+
            '삭제 '+deleted+
            ' / 보호 '+protectedCount+
            ' / 제외 '+skipped;
    }

    function checkStop(){
        if(window.__INVEN_POST_DELETE_STOP__){
            throw new Error(
                '사용자가 작업을 중지했습니다.'
            );
        }
    }

    async function request(url,options){
        checkStop();

        var opts=options||{};

        opts.credentials='include';
        opts.cache='no-store';

        var response=await fetch(url,opts);

        if(
            response.status===403 ||
            response.status===429
        ){
            throw new Error(
                '서버 제한 감지 HTTP '+
                response.status
            );
        }

        if(!response.ok){
            throw new Error(
                'HTTP '+
                response.status
            );
        }

        return response;
    }

    function findHeaderIndex(headers,name){
        var i;
        var text;

        for(i=0;i<headers.length;i++){
            text=compactNoSpace(
                headers[i].textContent
            );

            if(text===name){
                return i;
            }
        }

        for(i=0;i<headers.length;i++){
            text=compactNoSpace(
                headers[i].textContent
            );

            if(
                name==='추천' &&
                text.indexOf('비추천')!==-1
            ){
                continue;
            }

            if(text.indexOf(name)!==-1){
                return i;
            }
        }

        return -1;
    }

    function postIdFromRow(row,baseUrl){
        var numCell=
            row.querySelector(
                'td.num'
            );

        var raw='';

        if(numCell){
            var span=
                numCell.querySelector(
                    'span'
                );

            raw=compactText(
                span
                    ? span.textContent
                    : numCell.textContent
            );

            if(isDigits(raw)){
                return raw;
            }
        }

        var links=
            row.querySelectorAll(
                'a[href]'
            );

        var i;

        for(i=0;i<links.length;i++){
            var ids=idsFromUrl(
                links[i].getAttribute('href'),
                baseUrl
            );

            if(
                ids &&
                ids.boardId===String(BOARD_ID) &&
                ids.postId
            ){
                return ids.postId;
            }
        }

        return '';
    }

    function subjectFromRow(row,id,baseUrl){
        var subject=
            row.querySelector(
                'a.subject-link[href]'
            );

        if(subject){
            return subject;
        }

        var links=
            row.querySelectorAll(
                'a[href]'
            );

        var i;

        for(i=0;i<links.length;i++){
            var ids=idsFromUrl(
                links[i].getAttribute('href'),
                baseUrl
            );

            if(
                ids &&
                ids.boardId===String(BOARD_ID) &&
                ids.postId===String(id)
            ){
                return links[i];
            }
        }

        return null;
    }

    async function getList(page){
        var u=new URL(location.href);

        u.searchParams.set(
            'my',
            'post'
        );

        u.searchParams.set(
            'p',
            String(page)
        );

        var response=
            await request(
                u.href
            );

        var html=
            await response.text();

        var d=
            new DOMParser()
                .parseFromString(
                    html,
                    'text/html'
                );

        var posts=
            new Map();

        var tables=
            d.querySelectorAll(
                'table'
            );

        var tableIndex;

        for(
            tableIndex=0;
            tableIndex<tables.length;
            tableIndex++
        ){
            var table=
                tables[tableIndex];

            var headers=
                table.querySelectorAll(
                    'thead th'
                );

            var recIndex=
                findHeaderIndex(
                    headers,
                    '추천'
                );

            var downIndex=
                findHeaderIndex(
                    headers,
                    '비추천'
                );

            var rows=
                table.querySelectorAll(
                    'tbody tr'
                );

            var rowIndex;

            for(
                rowIndex=0;
                rowIndex<rows.length;
                rowIndex++
            ){
                var row=
                    rows[rowIndex];

                var id=
                    postIdFromRow(
                        row,
                        u.href
                    );

                if(!isDigits(id)){
                    continue;
                }

                var subject=
                    subjectFromRow(
                        row,
                        id,
                        u.href
                    );

                if(!subject){
                    continue;
                }

                var cells=
                    row.querySelectorAll(
                        'td'
                    );

                var recommend=NaN;
                var notrecommend=NaN;

                if(
                    recIndex!==-1 &&
                    cells[recIndex]
                ){
                    recommend=
                        num(
                            cells[
                                recIndex
                            ].textContent
                        );
                }

                if(
                    downIndex!==-1 &&
                    cells[downIndex]
                ){
                    notrecommend=
                        num(
                            cells[
                                downIndex
                            ].textContent
                        );
                }

                if(
                    !Number.isFinite(
                        recommend
                    )
                ){
                    var recCell=
                        row.querySelector(
                            'td.recommend,td.recom,td.rec'
                        );

                    if(recCell){
                        recommend=
                            num(
                                recCell.textContent
                            );
                    }
                }

                if(
                    !Number.isFinite(
                        notrecommend
                    )
                ){
                    var downCell=
                        row.querySelector(
                            'td.notrecommend,td.not-recommend,td.down,td.dislike'
                        );

                    if(downCell){
                        notrecommend=
                            num(
                                downCell.textContent
                            );
                    }
                }

                posts.set(
                    id,
                    {
                        id:id,

                        title:
                            compactText(
                                subject.textContent
                            )||id,

                        recommend:
                            recommend,

                        notrecommend:
                            notrecommend
                    }
                );
            }
        }

        if(!posts.size){
            var allRows=
                d.querySelectorAll(
                    'tr'
                );

            var allRowIndex;

            for(
                allRowIndex=0;
                allRowIndex<allRows.length;
                allRowIndex++
            ){
                var row2=
                    allRows[
                        allRowIndex
                    ];

                var id2=
                    postIdFromRow(
                        row2,
                        u.href
                    );

                if(!isDigits(id2)){
                    continue;
                }

                var subject2=
                    subjectFromRow(
                        row2,
                        id2,
                        u.href
                    );

                if(!subject2){
                    continue;
                }

                var rec2=NaN;
                var down2=NaN;

                var rc=
                    row2.querySelector(
                        'td.recommend,td.recom,td.rec'
                    );

                var dc=
                    row2.querySelector(
                        'td.notrecommend,td.not-recommend,td.down,td.dislike'
                    );

                if(rc){
                    rec2=
                        num(
                            rc.textContent
                        );
                }

                if(dc){
                    down2=
                        num(
                            dc.textContent
                        );
                }

                posts.set(
                    id2,
                    {
                        id:id2,

                        title:
                            compactText(
                                subject2.textContent
                            )||id2,

                        recommend:
                            rec2,

                        notrecommend:
                            down2
                    }
                );
            }
        }

        return Array.from(
            posts.values()
        );
    }

    async function removePost(id,page){
        var body=
            new FormData();

        body.append(
            'come_idx',
            BOARD_ID
        );

        body.append(
            'p',
            String(page)
        );

        body.append(
            'l',
            String(id)
        );

        body.append(
            'my',
            'post'
        );

        await request(
            '/board/bbs/include/multi_delete.php',
            {
                method:'POST',

                headers:{
                    'X-Requested-With':
                        'XMLHttpRequest'
                },

                body:body
            }
        );

        return true;
    }

    try{
        var page=1;

        while(page<=MAX_PAGE){
            checkStop();

            status(
                page+
                '페이지 목록 확인'
            );

            var posts=
                await getList(page);

            if(!posts.length){
                log(
                    page+
                    '페이지 없음 → 종료'
                );

                break;
            }

            posts.reverse();

            log(
                page+
                '페이지 | 게시글 '+
                posts.length+
                '개'
            );

            var attempted=[];
            var i;

            for(
                i=0;
                i<posts.length;
                i++
            ){
                checkStop();

                var post=
                    posts[i];

                if(
                    ignored.has(
                        post.id
                    )
                ){
                    continue;
                }

                if(
                    !Number.isFinite(
                        post.recommend
                    ) ||
                    !Number.isFinite(
                        post.notrecommend
                    ) ||
                    atLeast(
                        post.recommend,
                        Z[0]
                    ) ||
                    atLeast(
                        post.notrecommend,
                        Z[1]
                    )
                ){
                    ignored.add(
                        post.id
                    );

                    protectedCount++;

                    log(
                        '보호 | '+
                        post.id+
                        ' | '+
                        post.title
                    );

                    continue;
                }

                status(
                    page+
                    'P '+
                    (i+1)+
                    '/'+
                    posts.length+
                    NL+
                    post.title
                );

                try{
                    await removePost(
                        post.id,
                        page
                    );

                    attempted.push(
                        post
                    );

                } catch(error3){

                    if(
                        String(
                            error3.message
                        ).indexOf('403')!==-1 ||
                        String(
                            error3.message
                        ).indexOf('429')!==-1
                    ){
                        throw error3;
                    }

                    ignored.add(
                        post.id
                    );

                    skipped++;

                    log(
                        '삭제 실패 | '+
                        post.title+
                        ' | '+
                        error3.message
                    );
                }

                await sleep(GAP);
            }

            if(attempted.length){
                status(
                    page+
                    '페이지 삭제 결과 확인'
                );

                await sleep(
                    VERIFY_GAP
                );

                var after=
                    await getList(page);

                var remaining=
                    new Set();

                var ai;

                for(
                    ai=0;
                    ai<after.length;
                    ai++
                ){
                    remaining.add(
                        after[ai].id
                    );
                }

                var changed=0;
                var ti;

                for(
                    ti=0;
                    ti<attempted.length;
                    ti++
                ){
                    var target=
                        attempted[ti];

                    if(
                        !remaining.has(
                            target.id
                        )
                    ){
                        deleted++;
                        changed++;

                        log(
                            '삭제 완료 | '+
                            target.title
                        );

                    }else{
                        ignored.add(
                            target.id
                        );

                        skipped++;

                        log(
                            '삭제 거부 | '+
                            target.title
                        );
                    }
                }

                if(changed!==0){
                    log(
                        page+
                        '페이지 '+
                        changed+
                        '개 삭제 → 재확인'
                    );

                    continue;
                }
            }

            log(
                page+
                '페이지 삭제 가능 게시글 없음 → '+
                (page+1)+
                '페이지'
            );

            page++;

            await sleep(
                PAGE_GAP
            );
        }

        status(
            '전체 완료'
        );

        log(
            '===== 전체 완료 ====='
        );

        log(
            '삭제 '+deleted
        );

        log(
            '보호 '+protectedCount
        );

        log(
            '삭제 불가/실패 '+
            skipped
        );

    } catch(error4){

        status(
            '중단'+
            NL+
            error4.message
        );

        log(
            '===== 자동 중단 ===== | '+
            error4.message
        );
    }
})();

모바일 게시판 하단버튼

댓글

새로고침
새로고침

모바일 게시판 하단버튼

지금 뜨는 인벤

더보기+

모바일 게시판 리스트

모바일 게시판 하단버튼

글쓰기

모바일 게시판 페이징

최근 HOT한 콘텐츠

  • 메이플
  • 게임
  • IT
  • 유머
  • 연예
AD