Commit 9a98c5c1 authored by nanahira's avatar nanahira

first

parents
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
/build
/output
.git*
.dockerignore
Dockerfile
.gitlab-ci.yml
/config.yaml
.idea
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
/build
/output
/config.yaml
.idea
stages:
- build
- deploy
variables:
GIT_DEPTH: "1"
CONTAINER_TEST_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
CONTAINER_RELEASE_IMAGE: $CI_REGISTRY_IMAGE:latest
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
build:
stage: build
tags:
- docker
script:
- docker build --pull -t $CONTAINER_TEST_IMAGE .
- docker push $CONTAINER_TEST_IMAGE
deploy_latest:
stage: deploy
tags:
- docker
script:
- docker pull $CONTAINER_TEST_IMAGE
- docker tag $CONTAINER_TEST_IMAGE $CONTAINER_RELEASE_IMAGE
- docker push $CONTAINER_RELEASE_IMAGE
only:
- master
deploy_tag:
stage: deploy
tags:
- docker
variables:
CONTAINER_TAG_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
script:
- docker pull $CONTAINER_TEST_IMAGE
- docker tag $CONTAINER_TEST_IMAGE $CONTAINER_TAG_IMAGE
- docker push $CONTAINER_TAG_IMAGE
only:
- tags
FROM node:buster-slim
RUN apt update && apt -y install python3 build-essential && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
WORKDIR /usr/src/app
COPY ./package*.json ./
RUN npm ci
COPY . ./
RUN npm run build
CMD ["npm", "run", "start"]
This diff is collapsed.
import axios, {AxiosRequestConfig} from "axios";
import {Config, loadConfig, ParticipantData, writeConfig, axiosPostConfig} from "./src/config";
import qs from "querystring";
import _ from "underscore";
let config: Config;
async function createTournament(oldTournamentData: any) {
const oldData = _.clone(oldTournamentData);
delete oldData.url;
oldData.name += " Imported";
const {data} = await axios.post(" https://api.challonge.com/v1/tournaments.json", {
api_key: config.apiKey,
tournament: oldData,
}, {
validateStatus: function (status) {
return true;
},
...axiosPostConfig
});
console.log(data);
config.targetTournament = data.url;
await writeConfig(config);
return config.targetTournament;
}
async function main() {
config = await loadConfig();
console.log(`Reading tournament ${config.sourceTournament}`);
const {data: {tournament}} = await axios.get(` https://api.challonge.com/v1/tournaments/${config.sourceTournament}.json`, {
responseType: "json",
paramsSerializer: qs.stringify,
params: {
api_key: config.apiKey,
include_participants: 1
},
});
if(!config.targetTournament) {
console.log(`Target tournament does not exist. Creating one.`);
const newTournamentUrl = await createTournament(tournament);
console.log(`Tournament ${newTournamentUrl} created.`);
}
const oldParticipantDatas = tournament.participants as ParticipantData[];
const oldParticipants = oldParticipantDatas.map(oldp => oldp.participant);
console.log(`Creating participants.`);
await axios.post(`https://api.challonge.com/v1/tournaments/${config.targetTournament}/participants/bulk_add.json`, {
api_key: config.apiKey,
participants: oldParticipants.map(p => ({name: p.name, seed: p.seed})),
}, axiosPostConfig);
console.log(`Starting tournament.`);
await axios.post(`https://api.challonge.com/v1/tournaments/${config.targetTournament}/start.json`, {
api_key: config.apiKey
}, axiosPostConfig);
console.log(`Finished.`);
}
main();
\ No newline at end of file
import {axiosPostConfig, Config, loadConfig, Match, MatchData, Participant, ParticipantData} from "./src/config";
import axios from "axios";
import qs from "querystring";
let config: Config;
let oldMatches: Match[];
let oldParticipantNameMap: Map<string, Participant> = new Map();
let oldParticipantIdMap: Map<number, Participant> = new Map();
let newMatches: Match[];
let newParticipantNameMap: Map<string, Participant> = new Map();
let newParticipantIdMap: Map<number, Participant> = new Map();
async function getTournamentData(tournamentUrl: string) {
const {data: {tournament}} = await axios.get(` https://api.challonge.com/v1/tournaments/${tournamentUrl}.json`, {
responseType: "json",
paramsSerializer: qs.stringify,
params: {
api_key: config.apiKey,
include_participants: 1,
include_matches: 1
},
});
const participants = (tournament.participants as ParticipantData[]).map(pd => pd.participant);
const matches = (tournament.matches as MatchData[]).map(pd => pd.match);
return {participants, matches}
}
async function loadOldTournamentData() {
const {participants, matches} = await getTournamentData(config.sourceTournament);
oldParticipantNameMap.clear();
oldParticipantIdMap.clear();
for(let participant of participants) {
//console.log(`Inserting old participant ${participant.id} ${participant.name}`);
oldParticipantNameMap.set(participant.name, participant);
oldParticipantIdMap.set(participant.id, participant);
}
oldMatches = matches;
}
async function loadNewTournamentData() {
const {participants, matches} = await getTournamentData(config.targetTournament);
newParticipantIdMap.clear();
newParticipantNameMap.clear();
for(let participant of participants) {
//console.log(`Inserting new participant ${participant.id} ${participant.name}`);
newParticipantNameMap.set(participant.name, participant);
newParticipantIdMap.set(participant.id, participant);
}
newMatches = matches;
}
async function checkQuit(round: number, oldPlayerId: number) {
const oldPlayer = oldParticipantIdMap.get(oldPlayerId);
if(oldPlayer.active) {
return;
}
const nextRoundMatch = oldMatches.find(m => m.round === round + 1 && (m.player1_id === oldPlayerId || m.player2_id === oldPlayerId));
if(!nextRoundMatch) {
console.log(`Player ${oldPlayer.name} quitted in round ${round}.`);
const newPlayerId = newParticipantNameMap.get(oldPlayer.name).id;
await axios.post(`https://api.challonge.com/v1/tournaments/${config.targetTournament}/participants/${newPlayerId}.json`, {
api_key: config.apiKey,
_method: "delete"
}, axiosPostConfig);
}
}
async function migrateMatch(match: Match) {
const currentMatchPlayer1Name = newParticipantIdMap.get(match.player1_id).name;
const currentMatchPlayer2Name = newParticipantIdMap.get(match.player2_id).name;
const oldMatch = oldMatches.find(m => {
if(m.round !== match.round) {
return false;
}
const oldMatchPlayer1Name = oldParticipantIdMap.get(m.player1_id).name;
const oldMatchPlayer2Name = oldParticipantIdMap.get(m.player2_id).name;
return currentMatchPlayer1Name === oldMatchPlayer1Name && oldMatchPlayer2Name === currentMatchPlayer2Name;
});
if(!oldMatch) {
console.error(`Match for ${currentMatchPlayer1Name} vs ${currentMatchPlayer2Name} not found.`);
return;
}
let winner_id: string | number;
if(oldMatch.winner_id === "tie") {
winner_id = "tie";
console.log(`Migrating match ${currentMatchPlayer1Name} vs ${currentMatchPlayer2Name} with tie.`);
} else {
const winnerName = oldParticipantIdMap.get(oldMatch.winner_id as number).name;
winner_id = newParticipantNameMap.get(winnerName).id;
console.log(`Migrating match ${currentMatchPlayer1Name} vs ${currentMatchPlayer2Name} with winner ${winnerName}.`);
}
const oldPlayerIds = [oldMatch.player1_id, oldMatch.player2_id]
await Promise.all(oldPlayerIds.map(pid => checkQuit(match.round, pid)));
await axios.put(`https://api.challonge.com/v1/tournaments/${config.targetTournament}/matches/${match.id}.json`, {
api_key: config.apiKey,
match: {
scores_csv: oldMatch.scores_csv,
winner_id
}
}, axiosPostConfig);
console.log(`Migrated match ${currentMatchPlayer1Name} vs ${currentMatchPlayer2Name}.`);
}
async function main() {
config = await loadConfig();
console.log(`Reading old tournament ${config.sourceTournament}.`);
await loadOldTournamentData();
for(let round = 1; ;++round) {
console.log(`Handling round ${round}.`);
await loadNewTournamentData();
const matches = newMatches.filter(m => m.round === round && m.state === "open");
if(!matches.length) {
console.log(`No available matches in round ${round}. exiting.`);
break;
}
await Promise.all(matches.map(match => migrateMatch(match)));
}
console.log(`Finished.`);
}
main();
\ No newline at end of file
{
"name": "challonge-migrator",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@types/node": {
"version": "14.14.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.10.tgz",
"integrity": "sha512-J32dgx2hw8vXrSbu4ZlVhn1Nm3GbeCFNw2FWL8S5QKucHGY0cyNwjdQdO+KMBZ4wpmC7KhLCiNsdk1RFRIYUQQ=="
},
"@types/qs": {
"version": "6.9.5",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.5.tgz",
"integrity": "sha512-/JHkVHtx/REVG0VVToGRGH2+23hsYLHdyG+GrvoUGlGAd0ErauXDyvHtRI/7H7mzLm+tBCKA7pfcpkQ1lf58iQ=="
},
"@types/underscore": {
"version": "1.10.24",
"resolved": "https://registry.npmjs.org/@types/underscore/-/underscore-1.10.24.tgz",
"integrity": "sha512-T3NQD8hXNW2sRsSbLNjF/aBo18MyJlbw0lSpQHB/eZZtScPdexN4HSa8cByYwTw9Wy7KuOFr81mlDQcQQaZ79w=="
},
"axios": {
"version": "0.21.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.0.tgz",
"integrity": "sha512-fmkJBknJKoZwem3/IKSSLpkdNXZeBu5Q7GA/aRsr2btgrptmSCxi2oFjZHqGdK9DoTil9PIHlPIZw2EcRJXRvw==",
"requires": {
"follow-redirects": "^1.10.0"
}
},
"follow-redirects": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz",
"integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA=="
},
"moment": {
"version": "2.29.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
},
"qs": {
"version": "6.9.4",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz",
"integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ=="
},
"typescript": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.2.tgz",
"integrity": "sha512-thGloWsGH3SOxv1SoY7QojKi0tc+8FnOmiarEGMbd/lar7QOEd3hvlx3Fp5y6FlDUGl9L+pd4n2e+oToGMmhRQ=="
},
"underscore": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.0.tgz",
"integrity": "sha512-21rQzss/XPMjolTiIezSu3JAjgagXKROtNrYFEOWK109qY1Uv2tVjPTZ1ci2HgvQDA16gHYSthQIJfB+XId/rQ=="
},
"yaml": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz",
"integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg=="
}
}
}
{
"name": "challonge-migrator",
"version": "1.0.0",
"description": "",
"main": "build/create.ts",
"scripts": {
"build": "tsc",
"create": "node build/create.ts",
"migrate": "node build/mirate.ts"
},
"repository": {
"type": "git",
"url": "git@git.mycard.moe:nanahira/challonge-migrator.git"
},
"author": "Nanahira",
"license": "ISC",
"dependencies": {
"@types/node": "^14.14.10",
"@types/qs": "^6.9.5",
"@types/underscore": "^1.10.24",
"axios": "^0.21.0",
"moment": "^2.29.1",
"qs": "^6.9.4",
"typescript": "^4.1.2",
"underscore": "^1.12.0",
"yaml": "^1.10.0"
}
}
import yaml from "yaml";
import fs from "fs";
import {AxiosRequestConfig} from "axios";
export interface Config {
apiKey: string;
sourceTournament: string;
targetTournament: string;
maxRounds: number;
}
export interface Participant {
id: number;
name: string;
seed: number;
active: boolean;
}
export interface ParticipantData {
participant: Participant;
}
export interface Match {
id: number;
player1_id: number;
player2_id: number;
scores_csv: string;
winner_id: string | number;
state: string;
round: number;
}
export interface MatchData {
match: Match;
}
export const axiosPostConfig: AxiosRequestConfig = {
responseType: "json",
headers: {
"Content-Type": "application/json;charset=utf-8"
}
};
export async function loadConfig(): Promise<Config> {
return yaml.parse(await fs.promises.readFile("./config.yaml", "utf-8"))
}
export async function writeConfig(config: Config) {
await fs.promises.writeFile("./config.yaml", yaml.stringify(config));
}
\ No newline at end of file
This diff is collapsed.
{
"compilerOptions": {
"outDir": "build",
"module": "commonjs",
"target": "esnext",
"esModuleInterop": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"sourceMap": true
},
"compileOnSave": true,
"allowJs": true,
"include": [
"*.ts",
"src/*.ts"
]
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment