Quick 'n' dirty script to transfer data from the LogBlock plugin's database to CoreProtect. (SQLite only)
0
migrate-logblock-to-coreprotect.ts
185 lines 5.5 kB view raw
1#!/usr/bin/env bun 2 3import { SQL } from "bun"; 4 5const lb = new SQL("sqlite://./plugins/LogBlock/log.db"); 6const co = new SQL("sqlite://./plugins/CoreProtect/database.db"); 7 8const lbLogs = <LBBlockLog[]>await lb`SELECT * FROM block_log ORDER BY id ASC`; 9 10interface LBBlockLog { 11 id: number; 12 timestamp: number; // long of time millis 13 14 player: string; // uuid 15 action: "BREAK" | "PLACE"; 16 17 world: string; // world name 18 x: number; 19 y: number; 20 z: number; 21 22 block_type: string; // bukkit material string 23} 24 25// co_user 26interface COUser { 27 id: number; 28 user: string; // username 29 uuid: string; 30} 31 32// co_world 33interface COWorld { 34 id: number; 35 world: string; 36} 37 38// co_material_map 39interface COMaterialMap { 40 id: number; 41 material: string; // minecraft:namespaced_material_id 42} 43 44// co_block 45interface COBlock { 46 time: number; 47 48 user: number; // we store user id as number 49 wid: number; // we store world id as number 50 type: number; // we store material id from co_material_map 51 52 x: number; 53 y: number; 54 z: number; 55 56 action: number; // interaction (we only have place/break, respectively 1/0) 57 data: 0; 58 rolled_back: 0; 59} 60 61const coNewUsers: COUser[] = []; 62const coNewWorlds: COWorld[] = []; 63const coNewMaterials: COMaterialMap[] = []; 64 65const userStartId = 66 (await co`SELECT id FROM co_user ORDER BY id DESC LIMIT 1`)?.[0]?.id ?? 0; 67const worldStartId = 68 (await co`SELECT id FROM co_world ORDER BY id DESC LIMIT 1`)?.[0]?.id ?? 0; 69const materialStartId = 70 (await co`SELECT id FROM co_material_map ORDER BY id DESC LIMIT 1`)?.[0] 71 ?.id ?? 0; 72 73async function getWorld(name: string): Promise<COWorld> { 74 const [existingWorld] = 75 await co`SELECT * FROM co_world WHERE world = ${name}`; 76 const knownWorld = 77 existingWorld ?? coNewWorlds.find((world) => world.world == name); 78 if (knownWorld) return knownWorld; 79 80 const newWorld = { 81 id: worldStartId + 1 + coNewWorlds.length, 82 world: name, 83 }; 84 coNewWorlds.push(newWorld); 85 return newWorld; 86} 87 88async function getMaterial(material: string): Promise<COMaterialMap> { 89 const [existingMaterial] = 90 await co`SELECT * FROM co_material_map WHERE material = ${material}`; 91 const knownMaterial = 92 existingMaterial ?? 93 coNewMaterials.find((materialMap) => materialMap.material == material); 94 if (knownMaterial) return knownMaterial; 95 96 const newMaterial = { 97 id: materialStartId + 1 + coNewMaterials.length, 98 material: material, 99 }; 100 coNewMaterials.push(newMaterial); 101 return newMaterial; 102} 103 104async function getUser(uuid: string): Promise<COUser> { 105 const [existingUser] = await co`SELECT * FROM co_user WHERE uuid = ${uuid}`; 106 const knownUser = 107 <COUser>existingUser ?? coNewUsers.find((user) => user.uuid == uuid); 108 if (knownUser) return knownUser; 109 110 let res: Response; 111 console.log(`Requesting name for UUID ${uuid}`); 112 res = await fetch(`https://api.mojang.com/user/profile/${uuid}`); 113 if (!res.ok) { 114 throw new Error( 115 `Failed to fetch username for UUID ${uuid}: ${res.status} ${res.statusText}`, 116 ); 117 } 118 119 try { 120 const json = <{ name: string; id: string }>await res.json(); 121 const newUser = { 122 id: userStartId + 1 + coNewUsers.length, 123 uuid: uuid, 124 user: json.name, 125 }; 126 coNewUsers.push(newUser); 127 return newUser; 128 } catch (e) { 129 console.log(res); 130 throw e; 131 } 132} 133 134async function translateLog(log: LBBlockLog): Promise<COBlock> { 135 let user = (await getUser(log.player)).id; 136 let wid = (await getWorld(log.world)).id; 137 let type = (await getMaterial(`minecraft:${log.block_type.toLowerCase()}`)) 138 .id; 139 140 return { 141 action: log.action == "PLACE" ? 1 : 0, // co action: 1 = place, 0 = break 142 143 user, 144 wid, 145 type, 146 147 x: log.x, 148 y: log.y, 149 z: log.z, 150 151 data: 0, 152 time: Math.floor(log.timestamp / 1000), // translate millis to seconds 153 rolled_back: 0, 154 }; 155} 156 157let migratedLogCount = 0; 158await co.transaction(async (cp) => { 159 for (const orig_log of lbLogs) { 160 const log = await translateLog(orig_log); 161 162 const existingLogs = 163 await cp`SELECT * FROM co_block WHERE time = ${log.time} AND user = ${log.user} AND wid = ${log.wid} AND x = ${log.x} AND y = ${log.y} AND z = ${log.z} AND action = ${log.action} AND type = ${log.type}`; 164 if (existingLogs.length > 0) continue; 165 166 await cp`INSERT INTO co_block (time, user, wid, x, y, z, type, data, action, rolled_back) 167 VALUES (${log.time}, ${log.user}, ${log.wid}, ${log.x}, ${log.y}, ${log.z}, ${log.type}, ${log.data}, ${log.action}, ${log.rolled_back})`; 168 migratedLogCount++; 169 } 170 171 for (const coWorld of coNewWorlds) { 172 await cp`INSERT INTO co_world (id, world) VALUES (${coWorld.id}, ${coWorld.world})`; 173 } 174 for (const coUser of coNewUsers) { 175 await cp`INSERT INTO co_user (id, user, uuid) VALUES (${coUser.id}, ${coUser.user}, ${coUser.uuid})`; 176 } 177 for (const coMaterial of coNewMaterials) { 178 await cp`INSERT INTO co_material_map VALUES (${coMaterial.id}, ${coMaterial.material})`; 179 } 180}); 181 182console.info("OK: migrated"); 183console.info("\tusers: ", coNewUsers.length); 184console.info("\tmaterials: ", coNewMaterials.length); 185console.info("\tlogs: ", migratedLogCount);