From d3fafd75c6d86685ba5a24dcd2168efec56f61dd Mon Sep 17 00:00:00 2001 From: Whidix Date: Fri, 10 Jul 2026 09:02:20 +0200 Subject: [PATCH] feat: add translated names for Devil Fruits and update related schemas - Added support for translated names and French translated names in the DevilFruitRecord and Character interfaces. - Updated JSON import/export scripts to handle new translated name fields. - Enhanced scraping logic to extract translated names from the source. - Modified database schema to include translated names for Devil Fruits. - Updated daily fruit selection to include translated names for characters. --- drizzle/0005_uneven_talkback.sql | 2 + drizzle/meta/0005_snapshot.json | 1274 ++++++++++++++++++++++++++++++ drizzle/meta/_journal.json | 7 + scripts/import-json.ts | 8 +- scripts/scrape-onepiece.ts | 57 +- src/lib/server/daily-fruit.ts | 3 + src/lib/server/db/schema.ts | 2 + 7 files changed, 1336 insertions(+), 17 deletions(-) create mode 100644 drizzle/0005_uneven_talkback.sql create mode 100644 drizzle/meta/0005_snapshot.json diff --git a/drizzle/0005_uneven_talkback.sql b/drizzle/0005_uneven_talkback.sql new file mode 100644 index 0000000..c8a817c --- /dev/null +++ b/drizzle/0005_uneven_talkback.sql @@ -0,0 +1,2 @@ +ALTER TABLE `devil_fruit` ADD `translated_name` text;--> statement-breakpoint +ALTER TABLE `devil_fruit` ADD `fr_translated_name` text; \ No newline at end of file diff --git a/drizzle/meta/0005_snapshot.json b/drizzle/meta/0005_snapshot.json new file mode 100644 index 0000000..8339f8c --- /dev/null +++ b/drizzle/meta/0005_snapshot.json @@ -0,0 +1,1274 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "e2bbe292-e2c9-496f-bc69-ea1e5c2008c2", + "prevId": "5d8f2d8f-e66d-4b6f-8216-9e37d2fbff59", + "tables": { + "arc": { + "name": "arc", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "fr_name": { + "name": "fr_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "start_chapter": { + "name": "start_chapter", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "end_chapter": { + "name": "end_chapter", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "character": { + "name": "character", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "fr_name": { + "name": "fr_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "gender": { + "name": "gender", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "age": { + "name": "age", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "affiliation": { + "name": "affiliation", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fr_affiliation": { + "name": "fr_affiliation", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "devil_fruit_id": { + "name": "devil_fruit_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haki_observation": { + "name": "haki_observation", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "haki_armament": { + "name": "haki_armament", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "haki_conqueror": { + "name": "haki_conqueror", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "bounty": { + "name": "bounty", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "height": { + "name": "height", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "origin": { + "name": "origin", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fr_origin": { + "name": "fr_origin", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "first_appearance": { + "name": "first_appearance", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "picture_url": { + "name": "picture_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "epithets": { + "name": "epithets", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fr_epithets": { + "name": "fr_epithets", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "arc_id": { + "name": "arc_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fr_url": { + "name": "fr_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_in_daily_mode": { + "name": "is_in_daily_mode", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "character_devil_fruit_id_devil_fruit_id_fk": { + "name": "character_devil_fruit_id_devil_fruit_id_fk", + "tableFrom": "character", + "tableTo": "devil_fruit", + "columnsFrom": [ + "devil_fruit_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "character_arc_id_arc_id_fk": { + "name": "character_arc_id_arc_id_fk", + "tableFrom": "character", + "tableTo": "arc", + "columnsFrom": [ + "arc_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "character_history": { + "name": "character_history", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "character_id": { + "name": "character_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "won": { + "name": "won", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "character_history_date_unique": { + "name": "character_history_date_unique", + "columns": [ + "date" + ], + "isUnique": true + } + }, + "foreignKeys": { + "character_history_character_id_character_id_fk": { + "name": "character_history_character_id_character_id_fk", + "tableFrom": "character_history", + "tableTo": "character", + "columnsFrom": [ + "character_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "character_scrape_validation": { + "name": "character_scrape_validation", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "fr_name": { + "name": "fr_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "gender": { + "name": "gender", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "age": { + "name": "age", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "affiliation": { + "name": "affiliation", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fr_affiliation": { + "name": "fr_affiliation", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "devil_fruit_id": { + "name": "devil_fruit_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "haki_observation": { + "name": "haki_observation", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "haki_armament": { + "name": "haki_armament", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "haki_conqueror": { + "name": "haki_conqueror", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "bounty": { + "name": "bounty", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "height": { + "name": "height", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "origin": { + "name": "origin", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fr_origin": { + "name": "fr_origin", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "first_appearance": { + "name": "first_appearance", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "picture_url": { + "name": "picture_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "epithets": { + "name": "epithets", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fr_epithets": { + "name": "fr_epithets", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "arc_id": { + "name": "arc_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fr_url": { + "name": "fr_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_deleted": { + "name": "is_deleted", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "character_scrape_validation_devil_fruit_id_devil_fruit_id_fk": { + "name": "character_scrape_validation_devil_fruit_id_devil_fruit_id_fk", + "tableFrom": "character_scrape_validation", + "tableTo": "devil_fruit", + "columnsFrom": [ + "devil_fruit_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "character_scrape_validation_arc_id_arc_id_fk": { + "name": "character_scrape_validation_arc_id_arc_id_fk", + "tableFrom": "character_scrape_validation", + "tableTo": "arc", + "columnsFrom": [ + "arc_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "config": { + "name": "config", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "devil_fruit": { + "name": "devil_fruit", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "translated_name": { + "name": "translated_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fr_translated_name": { + "name": "fr_translated_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "devil_fruit_name_unique": { + "name": "devil_fruit_name_unique", + "columns": [ + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "devil_fruit_history": { + "name": "devil_fruit_history", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "devil_fruit_id": { + "name": "devil_fruit_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "won": { + "name": "won", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "devil_fruit_history_date_unique": { + "name": "devil_fruit_history_date_unique", + "columns": [ + "date" + ], + "isUnique": true + } + }, + "foreignKeys": { + "devil_fruit_history_devil_fruit_id_devil_fruit_id_fk": { + "name": "devil_fruit_history_devil_fruit_id_devil_fruit_id_fk", + "tableFrom": "devil_fruit_history", + "tableTo": "devil_fruit", + "columnsFrom": [ + "devil_fruit_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "friendship": { + "name": "friendship", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "requester_id": { + "name": "requester_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "addressee_id": { + "name": "addressee_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "friendship_requester_id_addressee_id_unique": { + "name": "friendship_requester_id_addressee_id_unique", + "columns": [ + "requester_id", + "addressee_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "friendship_requester_id_user_id_fk": { + "name": "friendship_requester_id_user_id_fk", + "tableFrom": "friendship", + "tableTo": "user", + "columnsFrom": [ + "requester_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "friendship_addressee_id_user_id_fk": { + "name": "friendship_addressee_id_user_id_fk", + "tableFrom": "friendship", + "tableTo": "user", + "columnsFrom": [ + "addressee_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_character_history": { + "name": "user_character_history", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "character_history_id": { + "name": "character_history_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "try_count": { + "name": "try_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tried_character_ids": { + "name": "tried_character_ids", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "user_character_history_user_id_character_history_id_unique": { + "name": "user_character_history_user_id_character_history_id_unique", + "columns": [ + "user_id", + "character_history_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "user_character_history_user_id_user_id_fk": { + "name": "user_character_history_user_id_user_id_fk", + "tableFrom": "user_character_history", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_character_history_character_history_id_character_history_id_fk": { + "name": "user_character_history_character_history_id_character_history_id_fk", + "tableFrom": "user_character_history", + "tableTo": "character_history", + "columnsFrom": [ + "character_history_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "account": { + "name": "account", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "account_userId_idx": { + "name": "account_userId_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "session_token_unique": { + "name": "session_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "session_userId_idx": { + "name": "session_userId_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email_verified": { + "name": "email_verified", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_admin": { + "name": "is_admin", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "user_username_unique": { + "name": "user_username_unique", + "columns": [ + "username" + ], + "isUnique": true + }, + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "verification": { + "name": "verification", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + "identifier" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 50a4698..24ba087 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -36,6 +36,13 @@ "when": 1782152097346, "tag": "0004_magenta_screwball", "breakpoints": true + }, + { + "idx": 5, + "version": "6", + "when": 1783545248600, + "tag": "0005_uneven_talkback", + "breakpoints": true } ] } \ No newline at end of file diff --git a/scripts/import-json.ts b/scripts/import-json.ts index b02bff9..2bbf650 100644 --- a/scripts/import-json.ts +++ b/scripts/import-json.ts @@ -18,6 +18,8 @@ type ArcRecord = { type DevilFruitRecord = { id: string; name: string; + translatedName?: string | null; + frTranslatedName?: string | null; type?: DevilFruitType | string | null; url?: string | null; }; @@ -94,7 +96,7 @@ function toJsonArray(value: string[] | string | null | undefined): string[] | nu function toDevilFruitType(value: DevilFruitType | string | null | undefined): DevilFruitType | null { if (!value) return null; - if (value === 'Paramecia' || value === 'Zoan' || value === 'Logia' || value === 'Smile' || value === 'Unknown') { + if (value === 'Paramecia' || value === 'Zoan' || value === 'Logia' || value === 'Unknown') { return value; } return 'Unknown'; @@ -229,6 +231,8 @@ async function importFromJson(): Promise { .values({ id: item.id, name: item.name, + translatedName: toNullable(item.translatedName), + frTranslatedName: toNullable(item.frTranslatedName), type: toDevilFruitType(item.type), url: toNullable(item.url) }) @@ -236,6 +240,8 @@ async function importFromJson(): Promise { target: devilFruit.id, set: { name: item.name, + translatedName: toNullable(item.translatedName), + frTranslatedName: toNullable(item.frTranslatedName), type: toDevilFruitType(item.type), url: toNullable(item.url) } diff --git a/scripts/scrape-onepiece.ts b/scripts/scrape-onepiece.ts index 9c1e856..dcfb2c3 100644 --- a/scripts/scrape-onepiece.ts +++ b/scripts/scrape-onepiece.ts @@ -23,6 +23,8 @@ interface Character { frOrigin: string | null; devilFruitId: string | null; devilFruitName: string | null; + devilFruitTranslatedName: string | null; + frDevilFruitTranslatedName: string | null; devilFruitUrl: string | null; affiliation: string | null; frAffiliation: string | null; @@ -49,12 +51,16 @@ interface CharacterListItem { interface DevilFruitData { devilFruitId: string; devilFruitTitle: string; + devilFruitTranslatedName?: string | null; + frDevilFruitTranslatedName?: string | null; devilFruitUrl: string; } interface DevilFruit { id: string; name: string; + translatedName?: string | null; + frTranslatedName?: string | null; type: string | null; url: string; } @@ -173,17 +179,6 @@ function removeParentheticalNotes(text: string): string { return text.replace(/\([^)]*\)/g, '').trim(); } -/** - * Get the French link from the API response links array - */ - -function getFrLink(links: { lang: string; ['*']: string; url: string }[]): { url: string } | null { - // Get french url by getting parse.langlinks where lang is "fr" and extract the name from there - const frLink = links.find( - (link: { lang: string; ['*']: string; url: string }) => link.lang === 'fr' - ); - return frLink ? { url: frLink['url'] } : null; -} /** * Normalize string by decoding URI components, punctuation, and replacing spaces with underscores @@ -485,9 +480,10 @@ async function fetchCharacter( const epithets = extractEpithets($); // Extract devil fruit - const devilFruitData = await extractDevilFruit($); + const devilFruitData = await extractDevilFruit($, 'en'); const devilFruitId = devilFruitData?.devilFruitId || null; const devilFruitName = devilFruitData?.devilFruitTitle || null; + const devilFruitTranslatedName = devilFruitData?.devilFruitTranslatedName || null; const devilFruitUrl = devilFruitData?.devilFruitUrl || null; // Extract haki from JSON categories @@ -546,6 +542,8 @@ async function fetchCharacter( const frOrigin = frPage ? extractOrigin(frPage) : null; + const frDevilFruitTranslatedName = frPage ? (await extractDevilFruit(frPage, 'fr'))?.devilFruitTranslatedName || null : null; + if (name !== jsonData.parse?.title) { frName = name; } @@ -563,6 +561,8 @@ async function fetchCharacter( frOrigin, devilFruitId, devilFruitName, + devilFruitTranslatedName, + frDevilFruitTranslatedName, devilFruitUrl, affiliation, frAffiliation, @@ -703,7 +703,7 @@ function extractEpithets($: cheerio.CheerioAPI): string[] { * Extract devil fruit from infobox * Returns both normalized ID and URL */ -async function extractDevilFruit($: cheerio.CheerioAPI): Promise { +async function extractDevilFruit($: cheerio.CheerioAPI, lang: 'en' | 'fr'): Promise { // dfname or dfename are used as data-source for devil fruit in the infobox, we check both to be safe const fruit = $('[data-source="dfname"] .pi-data-value, [data-source="dfename"] .pi-data-value').first(); const link = fruit.find('a').first(); @@ -712,6 +712,20 @@ async function extractDevilFruit($: cheerio.CheerioAPI): Promise 0) { + translatedName = translatedNameBox.text().trim(); + } + } else if (lang === 'fr') { + const translatedNameBox = $('[data-source="dfnomf"] .pi-data-value').first(); + if (translatedNameBox.length > 0) { + translatedName = translatedNameBox.text().trim(); + } + } + const href = link.attr('href'); if (!href || !href.startsWith('/wiki/')) return null; @@ -727,6 +741,7 @@ async function extractDevilFruit($: cheerio.CheerioAPI): Promise { async function fetchDevilFruit( devilFruitUrl: string, devilFruitTitle: string, + devilFruitTranslatedName: string | null, + frdevilFruitTranslatedName: string | null, devilFruitId: string ): Promise { try { @@ -956,6 +973,8 @@ async function fetchDevilFruit( return { id: devilFruitId, name: devilFruitTitle.toLowerCase().includes('smile') ? devilFruitTitle : name, + translatedName: devilFruitTranslatedName || null, + frTranslatedName: frdevilFruitTranslatedName || null, type, url: devilFruitUrl }; @@ -984,6 +1003,8 @@ async function saveDevilFruitsToCSV(devilFruits: DevilFruit[]): Promise { header: [ { id: 'id', title: 'ID' }, { id: 'name', title: 'Name' }, + { id: 'translatedName', title: 'Translated Name' }, + { id: 'frTranslatedName', title: 'Translated Name (FR)' }, { id: 'type', title: 'Type' }, { id: 'url', title: 'URL' } ] @@ -994,6 +1015,8 @@ async function saveDevilFruitsToCSV(devilFruits: DevilFruit[]): Promise { .map((df) => ({ id: df.id || '', name: df.name || '', + translatedName: df.translatedName || '', + frTranslatedName: df.frTranslatedName || '', type: df.type || '', url: df.url || '' })); @@ -1056,7 +1079,9 @@ async function main(): Promise { const name = c.devilFruitName!; const url = c.devilFruitUrl!; const id = normalizeId(name); - return [`${normalizeId(url)}::${id}`, { url, name, id }] as const; + const translatedName = c.devilFruitTranslatedName || null; + const frTranslatedName = c.frDevilFruitTranslatedName || null; + return [`${normalizeId(url)}::${id}`, { url, name, id, translatedName, frTranslatedName }] as const; }) ) .values() @@ -1064,7 +1089,7 @@ async function main(): Promise { console.log(`✓ Found ${devilFruitEntries.length} unique devil fruits\n`); // Step 3: Scraping Devil Fruits - console.log('=== Step 2: Scraping Devil Fruits ===\n'); + console.log('=== Step 3: Scraping Devil Fruits ===\n'); if (devilFruitEntries.length === 0) { console.warn('No devil fruits found from characters, skipping...\n'); @@ -1075,7 +1100,7 @@ async function main(): Promise { const batch = devilFruitEntries.slice(i, i + FETCH_CONCURRENCY); const batchResults = await Promise.all( batch.map(async (entry) => { - const data = await fetchDevilFruit(entry.url, entry.name, entry.id); + const data = await fetchDevilFruit(entry.url, entry.name, entry.translatedName, entry.frTranslatedName, entry.id); return { entry, data }; }) ); diff --git a/src/lib/server/daily-fruit.ts b/src/lib/server/daily-fruit.ts index 70e7972..97477c4 100644 --- a/src/lib/server/daily-fruit.ts +++ b/src/lib/server/daily-fruit.ts @@ -5,6 +5,9 @@ import { eq, and } from 'drizzle-orm'; const characterWithFruitSelect = { id: character.id, name: character.name, + frName: character.frName, + translatedName: character.translatedName, + frTranslatedName: character.frTranslatedName, devilFruitId: character.devilFruitId, devilFruitName: devilFruit.name, devilFruitType: devilFruit.type, diff --git a/src/lib/server/db/schema.ts b/src/lib/server/db/schema.ts index c4f12ae..27ac0a7 100644 --- a/src/lib/server/db/schema.ts +++ b/src/lib/server/db/schema.ts @@ -30,6 +30,8 @@ export type Arc = InferSelectModel; export const devilFruit = sqliteTable('devil_fruit', { id: text('id').primaryKey(), name: text('name').notNull().unique(), + translatedName: text('translated_name'), + frTranslatedName: text('fr_translated_name'), type: text('type').$type(), url: text('url') });