사용자:Hsl2/게임 메타데이터
보이기
	
	
리버티게임, 모두가 만들어가는 자유로운 게임
					< 사용자:Hsl2
상태:    보류
게임 메타데이터 수집 및 게임 별 메타데이터 문서 생성
일정[편집 | 원본 편집]
- 미정
 
실행 환경[편집 | 원본 편집]
- 개인 컴퓨터의 브라우저 (개발자 도구 콘솔)에서 실행
 - 리버티게임:게임 목록의 전체 목록에서 실행
 
준비사항[편집 | 원본 편집]
- 메타데이터 규격 확정 및 봇 코드 수정
- 장르 분류 수정 필요
 
 - 문제가 되는 게임 예외처리하고 수동으로 작성하거나 게임 목록에서 제외
- 데이터를 수동으로 작성할 게임은 게임아이콘 틀에서 
예외인자에 아무 값이나 넣기 - 검토가 필요한 게임 (콘솔 경고로 표시)
- 일부 정보가 작성되지 않은 경우
 - 기본 이름공간에 있지 않은 경우
 - 게임 대문이 하위 문서인 경우
 - 개발자를 가리키는 문서가 사용자 이름공간의 최상위 문서가 아닌 경우
 - 개발자가 중복으로 작성된 경우
 - 개발자를 입력하지 않은 경우
 - 같은 장르에 동일한 내용의 게임아이콘이 여러개 있는 경우
 - 한 장르에 포함된 게임이 없는 경우
 
 - 수동으로 처리해야 하는 게임 (콘솔 오류로 표시)
- 게임 링크가 유효한 내부 링크가 아닌 경우
 - 개발자를 입력하였지만, 링크가 걸리지 않은 경우 (이 봇은 개발자 목록의 링크 주소를 추출함)
 - 충돌하는 정보가 있는 여러 게임아이콘이 있는 경우
 
 
 - 데이터를 수동으로 작성할 게임은 게임아이콘 틀에서 
 - 게임 목록에서 깨진 링크는 과감히 삭제
 - 봇 권한 받아두기
 - 이 봇으로 작성된 게임 메타데이터는 login 등 일부 필수 속성이 없으므로 직접 작성한 메타데이터와 구분할 수단 필요 (아직 구현되지 않음)
 - 마지막 메타데이터 작성 코드를 주석처리하고 문제되는 게임을 확인하고, 모두 처리하였으면 주석을 해제하여 자동 편집 시작하기
 - 부하 방지를 위한 timeout이 구현되지 않음. 테스트 위키에서 테스트 권장.
 
소스 코드[편집 | 원본 편집]
이 글을 보려면 오른쪽의 "펼치기"를 눌러 주세요.
function scrapMetadata(category) {
    return $('#gamelist-' + category + ' li').map(function() {
        var elem = this;
        var $status = $(this).find('.old-gameicon-status');
        var nameElement = this.querySelector('.old-gameicon-name a');
        if(!$status.length) {
            console.warn('유효하지 않은 게임아이콘:', elem);
            return;
        }
        if($status.is('.old-gameicon-noautomigration')) {
            console.info('건너뜀:', nameElement.innerText, elem);
            return;
        }
        var data = $status.data();
        data = {
            progress: data.progress === ""? null : data.progress,
            editpolicy: data.edit === ""? null : data.edit,
            platform: data.platform === ""? null : data.tech,
            rating: data.rating === ""? null : data.rating,
            genre: category
        };
        var url = new URL(nameElement.href);
        var title;
        if(url.pathname === '/w/index.php') title = url.searchParams.get('title');
        else if(url.pathname.startsWith('/wiki/')) title = url.pathname.slice(6);
        else {
            console.error('잘못된 링크:', nameElement, data, elem);
            throw new TypeError('게임 링크가 잘못되었습니다. 수동으로 미리 처리하십시오.');
        }
        title = decodeURIComponent(title);
        data.title = title;
        data.name = nameElement.innerText;
        if(data.title.includes('/')) console.warn('하위 문서:', data.title, data, elem);
        if(new mw.Title(data.title).getNamespacePrefix() !== '') console.warn('다른 이름공간:', data.title, data, elem);
        var maker = $(this).find('.old-gameicon-maker');
        data.contributor = maker.find('.old-gameicon-helper a').map(function() {
            var url = new URL(this.href);
            if(url.searchParams.has('title')) return url.searchParams.get('title').slice(4).split('/')[0].replace(/_/g, ' ');
            else return decodeURIComponent(url.pathname.slice(6)).slice(4).split('/')[0].replace(/_/g, ' ');
        }).toArray();
        if(!data.contributor.length) data.contributor = null;
        else if(data.contributor.length === 1) data.contributor = data.contributor[0];
        if(maker.find('.old-gameicon-helper:not(:has(a))').length) {
            console.error('조력자 없음:', data.title, data, elem);
            throw new TypeError('존재하지만 링크가 걸리지 않아 수집할 수 없는 조력자 발견. 수동으로 미리 처리하십시오.');
        }
        data.author = maker.find('a:not(.old-gameicon-helper a)').map(function() {
            var url = new URL(this.href);
            if(url.searchParams.has('title')) return new mw.Title(url.searchParams.get('title'));
            else return new mw.Title(decodeURI(url.pathname.slice(6)));
        }).toArray().map(function(title) {
            if(title.getNamespacePrefix() !== '사용자:')
                console.warn('사용자가 아닌 개발자:', title.getPrefixedText(), data.title, data, elem);
            return title.getRelativeText(mw.config.get('wgNamespaceIds')['사용자']);
        }).filter(function(user) {
            return user;
        });
        if(data.author.length === 1) data.author = data.author[0];
        else if(!data.author || !data.author.length) console.warn('개발자 없음:', data.title, data, elem);
        else if(new Set(data.author).size !== data.author.length) {
            console.warn('중복된 개발자:', data.author, data.title, data, elem)
            data.author = Array.from(new Set(data.author));
            if(data.author.length === 1) data.author = data.author[0];
        }
        switch(data.editpolicy) {
            case 0: data.editpolicy = 'closed'; break;
            case 1: data.editpolicy = 'limited'; break;
            case 2: data.editpolicy = 'open'; break;
            case 3:
                data.editpolicy = null;
                data.abandon = true;
                break;
        }
        switch(data.platform) {
            case "링크":
            case "CGI":
            case "DB":
            case "JS":
            case "Lua":
            case "루아":
                data.platform = 'web';
                break;
            case "윈도우":
                data.platform = "windows";
                break;
            case "기타":
                data.platform = "other";
                break;
        }
        if(data.rating === '전체') data.rating = 'all';
        else if(data.rating === '평가용') data.rating = 'test';
        data.rating = {
            libertygame: {
                age: data.rating
            }
        };
        data.elem = elem;
        return data;
    });
}
function compareMetadata(a, b) {
    return a.progress === b.progress && a.editpolicy === b.editpolicy && a.platform === b.platform && a.rating === b.rating && a.contributor === b.contributor && a.author === b.author && a.name === b.name && a.abandon === b.abandon;
}
function mergeMetadata(datas) {
    var datamap = {};
    for(const list of datas) {
        for(const data of list) {
            if(datamap[data.title]) {
                let category = datamap[data.title].genre;
                if(category.includes(data.category)) {
                    console.warn('같은 카테고리 중복 등록:', data.title, data.category, datamap[data.title], data, data.elem);
                }
                if(!compareMetadata(datamap[data.title], data)) {
                    console.error('데이터 불일치:', data.title, datamap[data.title], data, data.elem);
                    throw new TypeError('데이터가 일치하지 않는 중복 등록된 게임 발견. 수동으로 미리 처리하십시오.')
                }
                if(typeof category === 'string') category = [category, data.genre];
                else category.push(data.genre);
                datamap[data.title].genre = category;
            } else datamap[data.title] = data;
            
            delete data.elem;
            delete data.title;
            if(!data.contributor) delete data.contributor;
        }
    }
    return datamap;
}
function saveMetadata(map) {
    const api = new mw.Api();
    const tasks = [];
    for(const title in map) {
        tasks.push(api.create(title + '/game.json', {
            summary: "게임 메타데이터 생성",
            bot: true,
            watchlist: "nochange",
            contentformat: "application/json",
            contentmodel: "json"
        }, JSON.stringify(map[title]))).then(console.log);
    }
}
function createMetadata() {
    const categories = ["act", "adv", "brd", "cpn", "esc", "liv", "mag", "mlt", "mus", "nax", "prd", "puz", "qiz", "ral", "rnd", "rod", "sht", "wst"];
    const datas = [];
    for(const category of categories) {
        console.group('카테고리:', category);
        const thisData = scrapMetadata(category);
        datas.push(thisData);
        if(!thisData.length) console.warn('수집된 데이터 없음:', category, thisData);
        console.log('수집된 데이터:', category, thisData.length, thisData);
        console.groupEnd();
    }
    return mergeMetadata(datas);
}
try {
    console.group('메타데이터 수집');
    var metadatas = createMetadata();
    console.log('수집된 데이터:', Object.keys(metadatas).length, metadatas);
    console.groupEnd();
} catch(error) {
    console.groupEnd();
    throw error;
}
/*
try {
    console.group('메타데이터 저장');
    saveMetadata(metadatas);
    console.groupEnd();
} catch(error) {
    console.groupEnd();
    throw error;
}
*/
비상시 행동요령[편집 | 원본 편집]
- 실행자: 네트워크 탭에서 오프라인으로 전환
 - 관리자: 봇 차단
 - 이 사용자의 모든 기여 되돌리기