Initial commit

This commit is contained in:
2026-07-26 12:29:43 +09:00
parent 5f290dc465
commit 50bfaeafdf
2253 changed files with 318636 additions and 196 deletions
@@ -0,0 +1,313 @@
import crypto from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import process from "node:process";
import pg from "pg";
import config from "../built/config/index.js";
const { Client } = pg;
const marker = "karaoke-service-rights-safe-test-data";
const createdAt = new Date();
function genId(date = new Date()) {
const time2000 = 946684800000;
const timestamp = Math.max(date.getTime() - time2000, 0).toString(36).padStart(8, "0");
const random = crypto.randomBytes(8).toString("base64url").replace(/[^a-z0-9]/gi, "").toLowerCase().slice(0, 8).padEnd(8, "0");
return `${timestamp}${random}`;
}
function md5(buffer) {
return crypto.createHash("md5").update(buffer).digest("hex");
}
function saveInternalFile(name, buffer) {
const accessKey = crypto.randomUUID();
fs.mkdirSync(config.mediaDir, { recursive: true });
fs.writeFileSync(path.join(config.mediaDir, accessKey), buffer);
return {
accessKey,
url: `${config.url}/files/${accessKey}`,
name,
size: buffer.byteLength,
md5: md5(buffer),
};
}
function createWav() {
const sampleRate = 44100;
const seconds = 16;
const samples = sampleRate * seconds;
const data = Buffer.alloc(samples * 2);
const notes = [261.63, 293.66, 329.63, 392.0, 349.23, 329.63, 293.66, 261.63];
for (let i = 0; i < samples; i++) {
const t = i / sampleRate;
const beat = Math.floor(t / 2) % notes.length;
const root = notes[beat] / 2;
const value =
Math.sin(2 * Math.PI * root * t) * 0.22 +
Math.sin(2 * Math.PI * root * 1.5 * t) * 0.12 +
Math.sin(2 * Math.PI * root * 2 * t) * 0.08;
data.writeInt16LE(Math.round(Math.max(-1, Math.min(1, value)) * 0x7fff), i * 2);
}
const header = Buffer.alloc(44);
header.write("RIFF", 0);
header.writeUInt32LE(36 + data.length, 4);
header.write("WAVE", 8);
header.write("fmt ", 12);
header.writeUInt32LE(16, 16);
header.writeUInt16LE(1, 20);
header.writeUInt16LE(1, 22);
header.writeUInt32LE(sampleRate, 24);
header.writeUInt32LE(sampleRate * 2, 28);
header.writeUInt16LE(2, 32);
header.writeUInt16LE(16, 34);
header.write("data", 36);
header.writeUInt32LE(data.length, 40);
return Buffer.concat([header, data]);
}
function varLen(value) {
let buffer = value & 0x7f;
while ((value >>= 7) > 0) {
buffer <<= 8;
buffer |= (value & 0x7f) | 0x80;
}
const bytes = [];
for (;;) {
bytes.push(buffer & 0xff);
if (buffer & 0x80) buffer >>= 8;
else break;
}
return Buffer.from(bytes);
}
function createMidi() {
const ticksPerQuarter = 480;
const events = [];
const push = (...bytes) => events.push(Buffer.from(bytes));
const pushDelta = (delta) => events.push(varLen(delta));
pushDelta(0);
push(0xff, 0x51, 0x03, 0x07, 0xa1, 0x20); // 120 BPM
pushDelta(0);
push(0xc0, 0x00);
const melody = [60, 62, 64, 67, 65, 64, 62, 60];
for (const note of melody) {
pushDelta(0);
push(0x90, note, 0x64);
pushDelta(ticksPerQuarter * 2);
push(0x80, note, 0x40);
}
pushDelta(0);
push(0xff, 0x2f, 0x00);
const track = Buffer.concat(events);
const header = Buffer.alloc(14);
header.write("MThd", 0);
header.writeUInt32BE(6, 4);
header.writeUInt16BE(0, 8);
header.writeUInt16BE(1, 10);
header.writeUInt16BE(ticksPerQuarter, 12);
const trackHeader = Buffer.alloc(8);
trackHeader.write("MTrk", 0);
trackHeader.writeUInt32BE(track.length, 4);
return Buffer.concat([header, trackHeader, track]);
}
function textBuffer(text) {
return Buffer.from(text.replace(/\n/g, "\r\n"), "utf8");
}
async function main() {
const client = new Client({
host: config.db.host,
port: config.db.port,
user: config.db.user,
password: config.db.pass,
database: config.db.db,
...config.db.extra,
});
await client.connect();
try {
const admin = await client.query(
`SELECT "id", "username", "host" FROM "user" WHERE "usernameLower" = 'admin' AND "host" IS NULL LIMIT 1`,
);
if (admin.rowCount === 0) {
throw new Error("Local @admin user was not found. Create @admin first, then rerun this script.");
}
const adminUser = admin.rows[0];
const existing = await client.query(
`SELECT "id", "fileIds" FROM "note" WHERE "userId" = $1 AND "text" ILIKE $2 LIMIT 1`,
[adminUser.id, `%${marker}%`],
);
const assets = [
{
name: "karaoke-test-accompaniment.wav",
type: "audio/wav",
comment: "KaraokeService test accompaniment. Original generated tone progression.",
buffer: createWav(),
},
{
name: "karaoke-test-lyrics.lrc",
type: "text/plain",
comment: "KaraokeService timed lyrics.",
buffer: textBuffer(`[00:00.000]Original karaoke test
[00:02.000]Sing the first clear tone
[00:04.000]Move to the next note
[00:06.000]Hold the bright third
[00:08.000]Reach the open fifth
[00:10.000]Return with steady timing
[00:12.000]Finish on the home note
[00:14.000]This data is rights-safe`),
},
{
name: "karaoke-test-pitch.mid",
type: "audio/midi",
comment: "KaraokeService MIDI pitch and tempo data.",
buffer: createMidi(),
},
{
name: "karaoke-test-score.json",
type: "application/json",
comment: "KaraokeService scoring metadata.",
buffer: textBuffer(JSON.stringify({
version: 1,
scoring: "midi-pitch",
pitchFile: "karaoke-test-pitch.mid",
toleranceCents: 50,
partialToleranceCents: 100,
extraSingingPenalty: true,
}, null, 2)),
},
{
name: "karaoke-test-song-info.txt",
type: "text/plain",
comment: "KaraokeService song metadata.",
buffer: textBuffer(`Title: Original Karaoke Test
Artist: FrozenFriendsYume test data
License: Public-domain equivalent test fixture generated for this repository
BPM: 120
Key: C major
Notes: All melody, lyrics, accompaniment, and metadata were generated locally for testing.`),
},
];
const files = assets.map((asset) => ({
id: genId(createdAt),
createdAt,
userId: adminUser.id,
userHost: null,
...saveInternalFile(asset.name, asset.buffer),
type: asset.type,
comment: asset.comment,
}));
await client.query("BEGIN");
for (const file of files) {
await client.query(
`INSERT INTO "drive_file" (
"id", "createdAt", "userId", "userHost", "md5", "name", "type", "size", "comment",
"blurhash", "properties", "storedInternal", "url", "thumbnailUrl", "webpublicUrl",
"webpublicType", "accessKey", "thumbnailAccessKey", "webpublicAccessKey", "uri", "src",
"folderId", "isSensitive", "allowDownload", "isLink", "requestHeaders", "requestIp"
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9,
NULL, '{}', TRUE, $10, NULL, NULL,
NULL, $11, NULL, NULL, NULL, NULL,
NULL, FALSE, FALSE, FALSE, NULL, NULL
)`,
[
file.id,
file.createdAt,
file.userId,
file.userHost,
file.md5,
file.name,
file.type,
file.size,
file.comment,
file.url,
file.accessKey,
],
);
}
const noteText = `Original Karaoke Test
Rights-safe generated karaoke test data.
Marker: ${marker}
#KaraokeService`;
if (existing.rowCount > 0) {
const noteId = existing.rows[0].id;
const oldFileIds = existing.rows[0].fileIds ?? [];
const oldFiles = oldFileIds.length > 0
? await client.query(`SELECT "id", "accessKey" FROM "drive_file" WHERE "id" = ANY($1)`, [oldFileIds])
: { rows: [] };
await client.query(
`UPDATE "note" SET "text" = $2, "fileIds" = $3, "attachedFileTypes" = $4, "updatedAt" = $5 WHERE "id" = $1`,
[noteId, noteText, files.map((file) => file.id), files.map((file) => file.type), new Date()],
);
if (oldFileIds.length > 0) {
await client.query(`DELETE FROM "drive_file" WHERE "id" = ANY($1)`, [oldFileIds]);
for (const file of oldFiles.rows) {
if (!file.accessKey) continue;
fs.rmSync(path.join(config.mediaDir, file.accessKey), { force: true });
}
}
await client.query("COMMIT");
console.log(`Recreated KaraokeService test media for existing @admin note: ${noteId}`);
return;
}
const noteId = genId(createdAt);
await client.query(
`INSERT INTO "note" (
"id", "createdAt", "replyId", "renoteId", "threadId", "text", "name", "cw",
"userId", "groupId", "localOnly", "renoteCount", "repliesCount", "viewCount",
"reactions", "visibility", "uri", "url", "score", "fileIds", "attachedFileTypes",
"visibleUserIds", "mentions", "mentionedRemoteUsers", "emojis", "tags", "hasPoll",
"channelId", "quoteAuthorization", "canQuote", "userHost", "replyUserId",
"replyUserHost", "renoteUserId", "renoteUserHost", "updatedAt"
) VALUES (
$1, $2, NULL, NULL, NULL, $3, NULL, NULL,
$4, NULL, FALSE, 0, 0, 0,
'{}', 'public', NULL, NULL, 0, $5, $6,
'{}', '{}', '[]', '{}', $7, FALSE,
NULL, NULL, TRUE, NULL, NULL,
NULL, NULL, NULL, NULL
)`,
[
noteId,
createdAt,
noteText,
adminUser.id,
files.map((file) => file.id),
files.map((file) => file.type),
["karaokeservice"],
],
);
await client.query(`UPDATE "user" SET "notesCount" = "notesCount" + 1 WHERE "id" = $1`, [adminUser.id]);
await client.query("COMMIT");
console.log(`Created KaraokeService test note as @admin: ${noteId}`);
} catch (err) {
await client.query("ROLLBACK").catch(() => undefined);
throw err;
} finally {
await client.end();
}
}
main().catch((err) => {
console.error(err);
process.exit(1);
});