Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
109 commits
Select commit Hold shift + click to select a range
41c93d4
idk most of the indie game heardle shit
VINXIS Jun 14, 2025
63756a3
obv fixes
VINXIS Jun 14, 2025
3c65345
migration
VINXIS Jun 14, 2025
1b99956
only load svelte component if songdata exists
VINXIS Jun 14, 2025
e78fe34
move to folder and add check for existing song for game
VINXIS Jun 15, 2025
e9f609f
fix query
VINXIS Jun 15, 2025
ca87328
metadata endpoint with pagination
VINXIS Jun 15, 2025
eaa3b87
bypass hmac for tunicwilds too
VINXIS Jun 17, 2025
10a243f
add tunicwildsurl env
VINXIS Jun 17, 2025
5e398d6
remove member tunicwild relation and add seed
VINXIS Jun 18, 2025
e58bb1b
endpoint for list of games
VINXIS Jun 18, 2025
02e4c3b
mvoe tunicwilds/metadata GET to tunicwilds GET
VINXIS Jun 18, 2025
5ddeda0
idk refactor endpoint for batch + single uploads
VINXIS Jun 18, 2025
8c81b87
Rename vars
cl8n Jun 18, 2025
2cc0f32
Check for both env vars
cl8n Jun 18, 2025
da2c6e5
client:load for TunicwildsApp
cl8n Jun 18, 2025
ab5b2f9
Better type for songList and fetch songData from client
cl8n Jun 18, 2025
b12290d
Lint
cl8n Jun 18, 2025
99947df
Ignore incorrect svelte warning
cl8n Jun 18, 2025
803db21
Fix TunicwildsApp props
cl8n Jun 18, 2025
cb41c81
Filter and sort song lookups by title, game, composer
cl8n Jun 18, 2025
1d14dbf
Sort all properties together and disable 1 char filter
cl8n Jun 18, 2025
4846019
Declare global type for SessionData
cl8n Jun 19, 2025
4ba0b2f
officiallink prop
VINXIS Jun 19, 2025
a31a0de
Add route for getting today's tunicwild
cl8n Jun 19, 2025
41459c6
Merge indiegameheardle upstream
cl8n Jun 19, 2025
6f1fea3
add skipping and change emojis for sharing
VINXIS Jun 19, 2025
c3af0f5
another colour
VINXIS Jun 19, 2025
fa6d51a
song list overlay
VINXIS Jun 19, 2025
b175e3e
remove extra comma
VINXIS Jun 19, 2025
db02058
error handling
VINXIS Jun 19, 2025
3c2e981
things list addition
VINXIS Jun 19, 2025
ab68464
Add route to submit new guess
cl8n Jun 19, 2025
b69addc
Return session in tunicwilds today response
cl8n Jun 19, 2025
e2da7e0
Fix songlist columns & type
cl8n Jun 19, 2025
b7a6e3a
Complete tunicwild session after 6 guesses
cl8n Jun 19, 2025
a26a643
Don't allow submitting more guesses than max
cl8n Jun 19, 2025
3d0c48a
Track win state instead of completed
cl8n Jun 19, 2025
bb4df99
Add command to update daily tunicwild state
cl8n Jun 19, 2025
c1ebc87
date initialized in const
VINXIS Jun 20, 2025
f89c3f2
Fix today endpoint for new dailyfile format
cl8n Jun 20, 2025
a6ccd1d
Add todo
cl8n Jun 20, 2025
5bbcc54
Redirect instead of rewrite after guess submit
cl8n Jun 20, 2025
8df713c
Serve tunicwilds rendered files in dev server
cl8n Jun 20, 2025
9ee0d6e
Copy codec when clipping mp3
cl8n Jun 20, 2025
e8fafe7
Fix tunicwilds upload spec
cl8n Jun 20, 2025
e09bc3c
Same env checks in upload route as daily script
cl8n Jun 20, 2025
e8a0a7f
Add capability for development.sh to run arbitrary commands
cl8n Jun 20, 2025
a92acf4
add quotes
VINXIS Jun 21, 2025
e1593f3
Run command on files after rendering
cl8n Jun 21, 2025
96435d5
plcaeholder audio files for tunicwilds
VINXIS Jun 21, 2025
a7d965d
remove errors event hog ti doesnt work yet
VINXIS Jun 21, 2025
5d7b867
refactor guess state to use /today post and refactor share func for r…
VINXIS Jun 21, 2025
37dcf46
reorder the main divs in the page
VINXIS Jun 21, 2025
47b8080
css nuke
VINXIS Jun 21, 2025
2dbe364
progress bar with evil update effect method
VINXIS Jun 21, 2025
6974fcd
kill more padding and put game over state on top again
VINXIS Jun 21, 2025
b741f88
force reload audio url in html
VINXIS Jun 21, 2025
18c9660
send full 32s clip when game is over
VINXIS Jun 21, 2025
693e9e5
less aids guess arr class addition
VINXIS Jun 21, 2025
fb33a6b
shorten lengths
VINXIS Jun 22, 2025
d41da4d
fix progress bar max width
VINXIS Jun 22, 2025
645812d
more changinghowthingslook stuff
VINXIS Jun 22, 2025
b3b8da4
timeout isnt necessary at all
VINXIS Jun 22, 2025
dc78f31
actually show song list
VINXIS Jun 22, 2025
ed07031
use css var for songlist background from themes
VINXIS Jun 22, 2025
d87d6c7
change isplaying var in the bigass effect thing
VINXIS Jun 22, 2025
63476aa
Use array length instead of hardcode
cl8n Jun 22, 2025
32cfa1c
Use IDs from created tunicwilds to seed audio files
cl8n Jun 22, 2025
e3cf214
Use ffmpeg to get duration instead of reading from metadata
cl8n Jun 22, 2025
7fb4f90
Map only first audio stream for duration check
cl8n Jun 22, 2025
9f7de23
Fix match for duration
cl8n Jun 22, 2025
22e9c02
Use VBR with q0 for audio clips
cl8n Jun 22, 2025
f566176
445 effect
cl8n Jun 22, 2025
1181b61
Include correctness info in guesses array
cl8n Jun 22, 2025
19e93e0
Refactor of guess array thing
cl8n Jun 22, 2025
4951f9a
Use new guess format and add styles for other guess results
cl8n Jun 22, 2025
331259a
nvm combine those again
cl8n Jun 22, 2025
477e353
Add SMILEY
cl8n Jun 22, 2025
e5c2baa
Slight reword of win msg
cl8n Jun 22, 2025
d92f2a5
Remove unnecessary checks
cl8n Jun 23, 2025
4257f9c
Remove unncessary state
cl8n Jun 23, 2025
f896f4c
Use Object.groupBy
cl8n Jun 24, 2025
1a60146
Consistent format with other song titles
cl8n Jun 24, 2025
10c324a
Extract redundant container def
cl8n Jun 24, 2025
68f3d2f
Add member relation to tunicwild
cl8n Jun 24, 2025
d109eac
Simplify tunicwilds file upload
cl8n Jun 24, 2025
0946dbe
Don't need to check env here
cl8n Jun 24, 2025
daab00e
Never send memberDiscord to client
cl8n Jun 24, 2025
37e7b49
Skip getting songData during SSR
cl8n Jun 24, 2025
79e004a
Fix type of tunicwild on client
cl8n Jun 24, 2025
7a6bcf5
Misc style changes to game over box
cl8n Jun 24, 2025
bbb9f99
Show explainer text for correct game or title
cl8n Jun 24, 2025
09d2e86
Show all relevant hints
cl8n Jun 24, 2025
79676ee
Use case insensitive check for correctTitle
cl8n Jun 24, 2025
83a5a3a
Wrong color
cl8n Jun 24, 2025
79b38c0
Remove game and title include conditions from server
cl8n Jun 24, 2025
f7c9b78
Better date style to fit into sentence
cl8n Jun 24, 2025
710ec01
Slim down hint margins
cl8n Jun 24, 2025
2b3227c
Devinxis song list overlay
cl8n Jun 24, 2025
4172b72
db utils and case insensitive comparisons
VINXIS Jul 2, 2025
236fbec
case insensitive searching in GET too
VINXIS Jul 2, 2025
8f7dd83
retry mechanism for timed out reqs
VINXIS Jul 2, 2025
9c43ba3
move tunicwilds.astro to index
VINXIS Jul 2, 2025
2b49c35
pause -> stop
VINXIS Jul 2, 2025
ca519f9
send jsonerror if no tunicwilds file
VINXIS Jul 4, 2025
4bf448e
remove extrahint and rebalance when hints show
VINXIS Jul 5, 2025
369b7e7
make every song link instead of game
VINXIS Jul 5, 2025
3531958
remove extrahint in post req
VINXIS Jul 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions db/migration/019-add-tunicwilds.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
CREATE TABLE "Tunicwild" (
"id" integer PRIMARY KEY AUTOINCREMENT,
"memberDiscord" text NOT NULL,
"composer" text NOT NULL,
"title" text NOT NULL,
"game" text NOT NULL,
"releaseDate" text NOT NULL,
"officialLink" text NOT NULL,
FOREIGN KEY ("memberDiscord") REFERENCES "Member" ("discord")
);
22 changes: 20 additions & 2 deletions development.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,40 @@ export CORPORATE_URL=
export DATABASE_URL="file:./$database"
export DIGITALOCEAN_DNS_TOKEN=
export LATEX_WEB_SYSTEMD_UNIT=
export PUBLIC_DIRECTORY='public'
export SECRET_HMAC_KEY='dev'
export SIGHTS_RUN_AFTER_UPLOAD=
export SIGHTS_UPLOAD_DIRECTORY='dev/sights'
export SOUNDS_RUN_AFTER_UPLOAD=
export SOUNDS_UPLOAD_DIRECTORY='dev/sounds'
export TUNICWILDS_DAILY_FILE='dev/tunicwilds-daily.json'
export TUNICWILDS_RENDERED_DIRECTORY='dev/tunicwilds-rendered'
export TUNICWILDS_RUN_AFTER_RENDER=
export TUNICWILDS_UPLOAD_DIRECTORY='dev/tunicwilds'
export TUNICWILDS_UPLOAD_URL=
export WORDS_RUN_AFTER_UPLOAD=
export WORDS_UPLOAD_DIRECTORY='dev/words'
export PUBLIC_DIRECTORY='public'

if test $# -gt 0; then
exec "$@"
fi

# Clean up files from last run
rm -rf "$database" "$SIGHTS_UPLOAD_DIRECTORY" "$SOUNDS_UPLOAD_DIRECTORY" "$WORDS_UPLOAD_DIRECTORY"
rm -rf \
"$database" \
"$SIGHTS_UPLOAD_DIRECTORY" \
"$SOUNDS_UPLOAD_DIRECTORY" \
"$WORDS_UPLOAD_DIRECTORY" \
"$TUNICWILDS_DAILY_FILE" \
"$TUNICWILDS_RENDERED_DIRECTORY" \
"$TUNICWILDS_UPLOAD_DIRECTORY"

# Create directories
mkdir -p "$SIGHTS_UPLOAD_DIRECTORY"
mkdir -p "$SOUNDS_UPLOAD_DIRECTORY"
mkdir -p "$WORDS_UPLOAD_DIRECTORY"
mkdir -p "$TUNICWILDS_RENDERED_DIRECTORY"
mkdir -p "$TUNICWILDS_UPLOAD_DIRECTORY"

# Create database
node db/migrate.mjs "$database"
Expand Down
186 changes: 186 additions & 0 deletions src/bin/start-tunicwild-day.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import { execFileSync, spawnSync } from "node:child_process";
import { randomBytes } from "node:crypto";
import { readFile, unlink, writeFile } from "node:fs/promises";
import { join, resolve } from "node:path";
import { notInArray, sql } from "drizzle-orm";
import db from "../database/db";
import { Tunicwild } from "../database/schema";

if (!process.env.TUNICWILDS_DAILY_FILE) {
console.error("TUNICWILDS_DAILY_FILE not set");
process.exit(1);
}

if (!process.env.TUNICWILDS_RENDERED_DIRECTORY) {
console.error("TUNICWILDS_RENDERED_DIRECTORY not set");
process.exit(1);
}

if (!process.env.TUNICWILDS_UPLOAD_URL && !process.env.TUNICWILDS_UPLOAD_DIRECTORY) {
console.error("TUNICWILDS_UPLOAD_* not set");
process.exit(1);
}

interface DailyInfo {
active: {
audioFilenames: string[];
date: string;
id: number;
}[];
recentIds: number[];
startDate: string;
}

const audioLengths = [0.5, 1, 2, 4, 8, 16] as const;

async function renderAudio(id: number): Promise<string[]> {
let audioBuffer: NodeJS.ArrayBufferView;
if (process.env.TUNICWILDS_UPLOAD_URL) {
const response = await fetch(`${process.env.TUNICWILDS_UPLOAD_URL}/${id}`);
audioBuffer = await response.bytes();
} else if (process.env.TUNICWILDS_UPLOAD_DIRECTORY) {
audioBuffer = await readFile(join(process.env.TUNICWILDS_UPLOAD_DIRECTORY, id.toString()));
} else {
throw new Error();
}

const ffmpegStatsResult = spawnSync("ffmpeg", [
"-v", "quiet",
"-stats",
"-i", "pipe:",
"-map", "0:a:0",
"-f", "null",
"-",
], {
encoding: "utf8",
input: audioBuffer,
stdio: ["pipe", "ignore", "pipe"],
});

if (ffmpegStatsResult.error != null) {
throw ffmpegStatsResult.error;
}

const timeMatch = ffmpegStatsResult.stderr.match(/time=(\d{2,}):(\d{2}):(\d{2}\.\d{2})[^\r]*$/);

if (timeMatch == null) {
throw new Error(`Unexpected ffmpeg output:\n${ffmpegStatsResult}`);
}

const durationSeconds =
Number.parseInt(timeMatch[1]!, 10) * 60 * 60 +
Number.parseInt(timeMatch[2]!, 10) * 60 +
Number.parseFloat(timeMatch[3]!);

const filenames = audioLengths.map(() => `${randomBytes(16).toString("hex")}.mp3`);
const maxAudioLength = audioLengths[audioLengths.length - 1]!;
const maxAudioLengthFilename = filenames[audioLengths.length - 1]!;

execFileSync("ffmpeg", [
"-v", "quiet",
"-ss", Math.max(0, Math.floor((durationSeconds - maxAudioLength) * Math.random())).toString(),
"-t", maxAudioLength.toString(),
"-i", "pipe:",
"-map", "0:a:0",
"-map_metadata", "-1",
"-c:a", "libmp3lame",
"-q:a", "0",
join(process.env.TUNICWILDS_RENDERED_DIRECTORY!, maxAudioLengthFilename),
], {
input: audioBuffer,
stdio: ["pipe", "ignore", "ignore"],
});

for (let i = 0; i < audioLengths.length - 1; i++) {
execFileSync("ffmpeg", [
"-v", "quiet",
"-t", audioLengths[i]!.toString(),
"-i", join(process.env.TUNICWILDS_RENDERED_DIRECTORY!, maxAudioLengthFilename),
"-c", "copy",
join(process.env.TUNICWILDS_RENDERED_DIRECTORY!, filenames[i]!),
], {
stdio: "ignore",
});
}

if (process.env.TUNICWILDS_RUN_AFTER_RENDER) {
execFileSync(
process.env.TUNICWILDS_RUN_AFTER_RENDER,
filenames.map(
(filename) => join(process.env.TUNICWILDS_RENDERED_DIRECTORY!, filename),
),
{ stdio: "ignore" },
);
}

return filenames;
}

// From "dist/server/bin/"
process.chdir(resolve(import.meta.dirname, "../../.."));

let dailyInfo: DailyInfo;
try {
dailyInfo = JSON.parse(await readFile(process.env.TUNICWILDS_DAILY_FILE, "utf8"));
} catch {
dailyInfo = {
active: [],
recentIds: [],
startDate: new Date().toISOString().slice(0, 10),
};
}

if (dailyInfo.active.length === 0) {
const tunicwilds = await db
.select()
.from(Tunicwild)
.orderBy(sql`RANDOM()`)
.limit(3);

if (tunicwilds.length !== 3) {
console.error("Not enough Tunicwilds in database");
process.exit(1);
}

for (let i = 0; i < tunicwilds.length; i++) {
dailyInfo.active.push({
audioFilenames: await renderAudio(tunicwilds[i]!.id),
date: new Date(Date.now() + (i - 1) * 1000 * 60 * 60 * 24).toISOString().slice(0, 10),
id: tunicwilds[i]!.id,
});
dailyInfo.recentIds.push(tunicwilds[i]!.id);
}
} else {
const tunicwild = await db
.select()
.from(Tunicwild)
.where(notInArray(Tunicwild.id, dailyInfo.recentIds))
.orderBy(sql`RANDOM()`)
.get();

if (tunicwild == null) {
console.error("Not enough Tunicwilds in database");
process.exit(1);
}

dailyInfo.active.push({
audioFilenames: await renderAudio(tunicwild.id),
date: new Date(Date.now() + 1000 * 60 * 60 * 24).toISOString().slice(0, 10),
id: tunicwild.id,
});
dailyInfo.recentIds.push(tunicwild.id);

if (dailyInfo.active.length > 3) {
const dropped = dailyInfo.active.shift()!;

await Promise.all(dropped.audioFilenames.map(
(filename) => unlink(join(process.env.TUNICWILDS_RENDERED_DIRECTORY!, filename)),
));
}

if (dailyInfo.recentIds.length > 30) {
dailyInfo.recentIds.shift();
}
}

await writeFile(process.env.TUNICWILDS_DAILY_FILE, JSON.stringify(dailyInfo));
Loading