Quick 'n' dirty script to transfer data from the LogBlock plugin's database to CoreProtect. (SQLite only)
0
migrate-logblock-to-coreprotect.ts
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);