diff --git a/package.json b/package.json index 2e6ef61..cf264a9 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "dotenv": "^16.3.1", "eventemitter3": "^5.0.1", "express": "^4.18.2", + "express-session": "^1.17.3", "ky": "^0.33.3", "lodash-es": "^4.17.21", "pinia": "^2.1.4", @@ -43,6 +44,7 @@ "@tsconfig/node18": "^18.2.0", "@types/bootstrap": "^5.2.6", "@types/express": "^4.17.17", + "@types/express-session": "^1.17.7", "@types/lodash-es": "^4.17.8", "@types/node": "^18.17.0", "@vitejs/plugin-vue": "^4.2.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f965bf8..766498f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,6 +38,9 @@ dependencies: express: specifier: ^4.18.2 version: 4.18.2 + express-session: + specifier: ^1.17.3 + version: 1.17.3 ky: specifier: ^0.33.3 version: 0.33.3 @@ -76,6 +79,9 @@ devDependencies: '@types/express': specifier: ^4.17.17 version: 4.17.17 + '@types/express-session': + specifier: ^1.17.7 + version: 1.17.7 '@types/lodash-es': specifier: ^4.17.8 version: 4.17.8 @@ -550,6 +556,12 @@ packages: '@types/send': 0.17.1 dev: true + /@types/express-session@1.17.7: + resolution: {integrity: sha512-L25080PBYoRLu472HY/HNCxaXY8AaGgqGC8/p/8+BYMhG0RDOLQ1wpXOpAzr4Gi5TGozTKyJv5BVODM5UNyVMw==} + dependencies: + '@types/express': 4.17.17 + dev: true + /@types/express@4.17.17: resolution: {integrity: sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==} dependencies: @@ -1311,6 +1323,11 @@ packages: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} dev: false + /cookie@0.4.2: + resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==} + engines: {node: '>= 0.6'} + dev: false + /cookie@0.5.0: resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} engines: {node: '>= 0.6'} @@ -1784,6 +1801,22 @@ packages: strip-final-newline: 3.0.0 dev: true + /express-session@1.17.3: + resolution: {integrity: sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==} + engines: {node: '>= 0.8.0'} + dependencies: + cookie: 0.4.2 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 2.0.0 + on-headers: 1.0.2 + parseurl: 1.3.3 + safe-buffer: 5.2.1 + uid-safe: 2.1.5 + transitivePeerDependencies: + - supports-color + dev: false + /express@4.18.2: resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} engines: {node: '>= 0.10.0'} @@ -2674,6 +2707,11 @@ packages: ee-first: 1.1.1 dev: false + /on-headers@1.0.2: + resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==} + engines: {node: '>= 0.8'} + dev: false + /once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: @@ -2907,6 +2945,11 @@ packages: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} dev: true + /random-bytes@1.0.0: + resolution: {integrity: sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==} + engines: {node: '>= 0.8'} + dev: false + /range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -3520,6 +3563,13 @@ packages: engines: {node: '>=14.17'} hasBin: true + /uid-safe@2.1.5: + resolution: {integrity: sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==} + engines: {node: '>= 0.8'} + dependencies: + random-bytes: 1.0.0 + dev: false + /unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} dependencies: diff --git a/server/data_source.ts b/server/data_source.ts new file mode 100644 index 0000000..4eebd21 --- /dev/null +++ b/server/data_source.ts @@ -0,0 +1,13 @@ +import "dotenv/config"; +import { DataSource } from "typeorm"; + +export const AppDataSource = new DataSource({ + type: "mysql", + host: process.env.DB_HOST, + database: "db/database.sqlite", + synchronize: true, + logging: false, + entities: [], + migrations: [], + subscribers: [], +}) \ No newline at end of file diff --git a/server/dotenv.d.ts b/server/dotenv.d.ts new file mode 100644 index 0000000..4693970 --- /dev/null +++ b/server/dotenv.d.ts @@ -0,0 +1,9 @@ +declare namespace NodeJS { + interface ProcessEnv { + NODE_ENV: 'development' | 'production'; + GAME_DB_SERVER: string; + GAME_DB_DATABASE: string; + GAME_DB_USER: string; + GAME_DB_PASSWORD: string; + } +} \ No newline at end of file diff --git a/server/entity/schemats.ts b/server/entity/schemats.ts new file mode 100644 index 0000000..2b2330e --- /dev/null +++ b/server/entity/schemats.ts @@ -0,0 +1,1079 @@ +/* tslint:disable */ + + +/** + * AUTO-GENERATED FILE @ 2023-08-05 12:06:36 - DO NOT EDIT! + * + * This file was automatically generated by schemats v.3.0.3 + * $ schemats generate -c mysql://username:password@127.0.0.1/hidche_hwe -C -t board -t city -t comment -t diplomacy -t emperior -t event -t general -t general_access_log -t general_record -t general_turn -t hall -t message -t nation -t nation_env -t nation_turn -t ng_auction -t ng_auction_bid -t ng_betting -t ng_diplomacy -t ng_games -t ng_history -t ng_old_generals -t ng_old_nations -t plock -t rank_data -t reserved_open -t select_npc_token -t select_pool -t statistic -t storage -t tournament -t troop -t user_record -t vote -t vote_comment -t world_history -s hidche_hwe + * + */ + +export type EnumTarget = 'MONTH' | 'OCCUPY_CITY' | 'DESTROY_NATION' | 'PRE_MONTH' | 'UNITED'; +export type EnumAuctionType = 'buyRice' | 'sellRice' | 'uniqueItem'; +export type EnumReqResource = 'gold' | 'rice' | 'inheritPoint'; +export type EnumLogType = 'action' | 'battle_brief' | 'battle' | 'history'; +export type EnumMsgType = 'private' | 'national' | 'public' | 'diplomacy'; +export type EnumPermission = 'normal' | 'auditor' | 'ambassador'; +export type EnumType = 'GAME' | 'ETC' | 'TOURNAMENT'; +export type EnumState = 'proposed' | 'activated' | 'cancelled' | 'replaced'; + +export namespace BoardFields { + export type no = number; + export type nationNo = number; + export type isSecret = boolean; + export type date = Date; + export type generalNo = number; + export type author = string; + export type authorIcon = string | null; + export type title = string; + export type text = string; + +} + +export interface Board { + no: BoardFields.no; + nationNo: BoardFields.nationNo; + isSecret: BoardFields.isSecret; + date: BoardFields.date; + generalNo: BoardFields.generalNo; + author: BoardFields.author; + authorIcon: BoardFields.authorIcon; + title: BoardFields.title; + text: BoardFields.text; + +} + +export namespace CityFields { + export type city = number; + export type name = string; + export type level = number; + export type nation = number; + export type supply = number; + export type front = number; + export type pop = number; + export type popMax = number; + export type agri = number; + export type agriMax = number; + export type comm = number; + export type commMax = number; + export type secu = number; + export type secuMax = number; + export type trust = number; + export type trade = number | null; + export type dead = number; + export type def = number; + export type defMax = number; + export type wall = number; + export type wallMax = number; + export type officerSet = number; + export type state = number; + export type region = number; + export type term = number; + export type conflict = string; + +} + +export interface City { + city: CityFields.city; + name: CityFields.name; + level: CityFields.level; + nation: CityFields.nation; + supply: CityFields.supply; + front: CityFields.front; + pop: CityFields.pop; + popMax: CityFields.popMax; + agri: CityFields.agri; + agriMax: CityFields.agriMax; + comm: CityFields.comm; + commMax: CityFields.commMax; + secu: CityFields.secu; + secuMax: CityFields.secuMax; + trust: CityFields.trust; + trade: CityFields.trade; + dead: CityFields.dead; + def: CityFields.def; + defMax: CityFields.defMax; + wall: CityFields.wall; + wallMax: CityFields.wallMax; + officerSet: CityFields.officerSet; + state: CityFields.state; + region: CityFields.region; + term: CityFields.term; + conflict: CityFields.conflict; + +} + +export namespace CommentFields { + export type no = number; + export type nationNo = number; + export type isSecret = boolean; + export type date = Date; + export type documentNo = number; + export type generalNo = number; + export type author = string; + export type text = string; + +} + +export interface Comment { + no: CommentFields.no; + nationNo: CommentFields.nationNo; + isSecret: CommentFields.isSecret; + date: CommentFields.date; + documentNo: CommentFields.documentNo; + generalNo: CommentFields.generalNo; + author: CommentFields.author; + text: CommentFields.text; + +} + +export namespace DiplomacyFields { + export type no = number; + export type me = number; + export type you = number; + export type state = number | null; + export type term = number | null; + export type dead = number | null; + export type showing = Date | null; + +} + +export interface Diplomacy { + no: DiplomacyFields.no; + me: DiplomacyFields.me; + you: DiplomacyFields.you; + state: DiplomacyFields.state; + term: DiplomacyFields.term; + dead: DiplomacyFields.dead; + showing: DiplomacyFields.showing; + +} + +export namespace EmperiorFields { + export type no = number; + export type serverId = string | null; + export type phase = string | null; + export type nationCount = string | null; + export type nationName = string | null; + export type nationHist = string | null; + export type genCount = string | null; + export type personalHist = string | null; + export type specialHist = string | null; + export type name = string | null; + export type type = string | null; + export type color = string | null; + export type year = number | null; + export type month = number | null; + export type power = number | null; + export type gennum = number | null; + export type citynum = number | null; + export type pop = string | null; + export type poprate = string | null; + export type gold = number | null; + export type rice = number | null; + export type l12Name = string | null; + export type l12Pic = string | null; + export type l11Name = string | null; + export type l11Pic = string | null; + export type l10Name = string | null; + export type l10Pic = string | null; + export type l9Name = string | null; + export type l9Pic = string | null; + export type l8Name = string | null; + export type l8Pic = string | null; + export type l7Name = string | null; + export type l7Pic = string | null; + export type l6Name = string | null; + export type l6Pic = string | null; + export type l5Name = string | null; + export type l5Pic = string | null; + export type tiger = string | null; + export type eagle = string | null; + export type gen = string | null; + export type history = string | null; + export type aux = string | null; + +} + +export interface Emperior { + no: EmperiorFields.no; + serverId: EmperiorFields.serverId; + phase: EmperiorFields.phase; + nationCount: EmperiorFields.nationCount; + nationName: EmperiorFields.nationName; + nationHist: EmperiorFields.nationHist; + genCount: EmperiorFields.genCount; + personalHist: EmperiorFields.personalHist; + specialHist: EmperiorFields.specialHist; + name: EmperiorFields.name; + type: EmperiorFields.type; + color: EmperiorFields.color; + year: EmperiorFields.year; + month: EmperiorFields.month; + power: EmperiorFields.power; + gennum: EmperiorFields.gennum; + citynum: EmperiorFields.citynum; + pop: EmperiorFields.pop; + poprate: EmperiorFields.poprate; + gold: EmperiorFields.gold; + rice: EmperiorFields.rice; + l12Name: EmperiorFields.l12Name; + l12Pic: EmperiorFields.l12Pic; + l11Name: EmperiorFields.l11Name; + l11Pic: EmperiorFields.l11Pic; + l10Name: EmperiorFields.l10Name; + l10Pic: EmperiorFields.l10Pic; + l9Name: EmperiorFields.l9Name; + l9Pic: EmperiorFields.l9Pic; + l8Name: EmperiorFields.l8Name; + l8Pic: EmperiorFields.l8Pic; + l7Name: EmperiorFields.l7Name; + l7Pic: EmperiorFields.l7Pic; + l6Name: EmperiorFields.l6Name; + l6Pic: EmperiorFields.l6Pic; + l5Name: EmperiorFields.l5Name; + l5Pic: EmperiorFields.l5Pic; + tiger: EmperiorFields.tiger; + eagle: EmperiorFields.eagle; + gen: EmperiorFields.gen; + history: EmperiorFields.history; + aux: EmperiorFields.aux; + +} + +export namespace EventFields { + export type id = number; + export type target = EnumTarget; + export type priority = number; + export type condition = string; + export type action = string; + +} + +export interface Event { + id: EventFields.id; + target: EventFields.target; + priority: EventFields.priority; + condition: EventFields.condition; + action: EventFields.action; + +} + +export namespace GeneralFields { + export type no = number; + export type owner = number; + export type npcmsg = string | null; + export type npc = number; + export type npcOrg = number | null; + export type affinity = number | null; + export type bornyear = number | null; + export type deadyear = number | null; + export type newmsg = number | null; + export type picture = string; + export type imgsvr = number; + export type name = string; + export type ownerName = string | null; + export type nation = number; + export type city = number; + export type troop = number; + export type leadership = number; + export type leadershipExp = number; + export type strength = number; + export type strengthExp = number; + export type intel = number; + export type intelExp = number; + export type injury = number; + export type experience = number; + export type dedication = number; + export type dex1 = number; + export type dex2 = number; + export type dex3 = number; + export type dex4 = number; + export type dex5 = number; + export type officerLevel = number; + export type officerCity = number; + export type permission = EnumPermission | null; + export type gold = number; + export type rice = number; + export type crew = number; + export type crewtype = number; + export type train = number; + export type atmos = number; + export type weapon = string; + export type book = string; + export type horse = string; + export type item = string; + export type turntime = Date; + export type recentWar = Date | null; + export type makelimit = number | null; + export type killturn = number | null; + export type block = number | null; + export type dedlevel = number | null; + export type explevel = number | null; + export type age = number | null; + export type startage = number | null; + export type belong = number | null; + export type betray = number | null; + export type personal = string; + export type special = string; + export type specage = number | null; + export type special2 = string; + export type specage2 = number | null; + export type defenceTrain = number | null; + export type tnmt = number | null; + export type myset = number | null; + export type tournament = number | null; + export type newvote = number | null; + export type lastTurn = string; + export type aux = string; + export type penalty = string | null; + +} + +export interface General { + no: GeneralFields.no; + owner: GeneralFields.owner; + npcmsg: GeneralFields.npcmsg; + npc: GeneralFields.npc; + npcOrg: GeneralFields.npcOrg; + affinity: GeneralFields.affinity; + bornyear: GeneralFields.bornyear; + deadyear: GeneralFields.deadyear; + newmsg: GeneralFields.newmsg; + picture: GeneralFields.picture; + imgsvr: GeneralFields.imgsvr; + name: GeneralFields.name; + ownerName: GeneralFields.ownerName; + nation: GeneralFields.nation; + city: GeneralFields.city; + troop: GeneralFields.troop; + leadership: GeneralFields.leadership; + leadershipExp: GeneralFields.leadershipExp; + strength: GeneralFields.strength; + strengthExp: GeneralFields.strengthExp; + intel: GeneralFields.intel; + intelExp: GeneralFields.intelExp; + injury: GeneralFields.injury; + experience: GeneralFields.experience; + dedication: GeneralFields.dedication; + dex1: GeneralFields.dex1; + dex2: GeneralFields.dex2; + dex3: GeneralFields.dex3; + dex4: GeneralFields.dex4; + dex5: GeneralFields.dex5; + officerLevel: GeneralFields.officerLevel; + officerCity: GeneralFields.officerCity; + permission: GeneralFields.permission; + gold: GeneralFields.gold; + rice: GeneralFields.rice; + crew: GeneralFields.crew; + crewtype: GeneralFields.crewtype; + train: GeneralFields.train; + atmos: GeneralFields.atmos; + weapon: GeneralFields.weapon; + book: GeneralFields.book; + horse: GeneralFields.horse; + item: GeneralFields.item; + turntime: GeneralFields.turntime; + recentWar: GeneralFields.recentWar; + makelimit: GeneralFields.makelimit; + killturn: GeneralFields.killturn; + block: GeneralFields.block; + dedlevel: GeneralFields.dedlevel; + explevel: GeneralFields.explevel; + age: GeneralFields.age; + startage: GeneralFields.startage; + belong: GeneralFields.belong; + betray: GeneralFields.betray; + personal: GeneralFields.personal; + special: GeneralFields.special; + specage: GeneralFields.specage; + special2: GeneralFields.special2; + specage2: GeneralFields.specage2; + defenceTrain: GeneralFields.defenceTrain; + tnmt: GeneralFields.tnmt; + myset: GeneralFields.myset; + tournament: GeneralFields.tournament; + newvote: GeneralFields.newvote; + lastTurn: GeneralFields.lastTurn; + aux: GeneralFields.aux; + penalty: GeneralFields.penalty; + +} + +export namespace GeneralAccessLogFields { + export type id = number; + export type generalId = number; + export type userId = number | null; + export type lastRefresh = Date | null; + export type refresh = number; + export type refreshTotal = number; + export type refreshScore = number; + export type refreshScoreTotal = number; + +} + +export interface GeneralAccessLog { + id: GeneralAccessLogFields.id; + generalId: GeneralAccessLogFields.generalId; + userId: GeneralAccessLogFields.userId; + lastRefresh: GeneralAccessLogFields.lastRefresh; + refresh: GeneralAccessLogFields.refresh; + refreshTotal: GeneralAccessLogFields.refreshTotal; + refreshScore: GeneralAccessLogFields.refreshScore; + refreshScoreTotal: GeneralAccessLogFields.refreshScoreTotal; + +} + +export namespace GeneralRecordFields { + export type id = number; + export type generalId = number; + export type logType = EnumLogType; + export type year = number; + export type month = number; + export type text = string; + +} + +export interface GeneralRecord { + id: GeneralRecordFields.id; + generalId: GeneralRecordFields.generalId; + logType: GeneralRecordFields.logType; + year: GeneralRecordFields.year; + month: GeneralRecordFields.month; + text: GeneralRecordFields.text; + +} + +export namespace GeneralTurnFields { + export type id = number; + export type generalId = number; + export type turnIdx = number; + export type action = string; + export type arg = string | null; + export type brief = string | null; + +} + +export interface GeneralTurn { + id: GeneralTurnFields.id; + generalId: GeneralTurnFields.generalId; + turnIdx: GeneralTurnFields.turnIdx; + action: GeneralTurnFields.action; + arg: GeneralTurnFields.arg; + brief: GeneralTurnFields.brief; + +} + +export namespace HallFields { + export type id = number; + export type serverId = string; + export type season = number; + export type scenario = number; + export type generalNo = number; + export type type = string; + export type value = number; + export type owner = number | null; + export type aux = string; + +} + +export interface Hall { + id: HallFields.id; + serverId: HallFields.serverId; + season: HallFields.season; + scenario: HallFields.scenario; + generalNo: HallFields.generalNo; + type: HallFields.type; + value: HallFields.value; + owner: HallFields.owner; + aux: HallFields.aux; + +} + +export namespace MessageFields { + export type id = number; + export type mailbox = number; + export type type = EnumMsgType; + export type src = number; + export type dest = number; + export type time = Date; + export type validUntil = Date; + export type message = string; + +} + +export interface Message { + id: MessageFields.id; + mailbox: MessageFields.mailbox; + type: MessageFields.type; + src: MessageFields.src; + dest: MessageFields.dest; + time: MessageFields.time; + validUntil: MessageFields.validUntil; + message: MessageFields.message; + +} + +export namespace NationFields { + export type nation = number; + export type name = string; + export type color = string; + export type capital = number | null; + export type capset = number | null; + export type gennum = number | null; + export type gold = number | null; + export type rice = number | null; + export type bill = number | null; + export type rate = number | null; + export type rateTmp = number | null; + export type secretlimit = number | null; + export type chiefSet = number; + export type scout = number | null; + export type war = number | null; + export type strategicCmdLimit = number | null; + export type surlimit = number | null; + export type tech = number | null; + export type power = number | null; + export type spy = string; + export type level = number | null; + export type type = string; + export type aux = string; + +} + +export interface Nation { + nation: NationFields.nation; + name: NationFields.name; + color: NationFields.color; + capital: NationFields.capital; + capset: NationFields.capset; + gennum: NationFields.gennum; + gold: NationFields.gold; + rice: NationFields.rice; + bill: NationFields.bill; + rate: NationFields.rate; + rateTmp: NationFields.rateTmp; + secretlimit: NationFields.secretlimit; + chiefSet: NationFields.chiefSet; + scout: NationFields.scout; + war: NationFields.war; + strategicCmdLimit: NationFields.strategicCmdLimit; + surlimit: NationFields.surlimit; + tech: NationFields.tech; + power: NationFields.power; + spy: NationFields.spy; + level: NationFields.level; + type: NationFields.type; + aux: NationFields.aux; + +} + +export namespace NationEnvFields { + export type id = number; + export type namespace = number; + export type key = string; + export type value = string; + +} + +export interface NationEnv { + id: NationEnvFields.id; + namespace: NationEnvFields.namespace; + key: NationEnvFields.key; + value: NationEnvFields.value; + +} + +export namespace NationTurnFields { + export type id = number; + export type nationId = number; + export type officerLevel = number; + export type turnIdx = number; + export type action = string; + export type arg = string | null; + export type brief = string | null; + +} + +export interface NationTurn { + id: NationTurnFields.id; + nationId: NationTurnFields.nationId; + officerLevel: NationTurnFields.officerLevel; + turnIdx: NationTurnFields.turnIdx; + action: NationTurnFields.action; + arg: NationTurnFields.arg; + brief: NationTurnFields.brief; + +} + +export namespace NgAuctionFields { + export type id = number; + export type type = EnumAuctionType; + export type finished = Buffer; + export type target = string | null; + export type hostGeneralId = number; + export type reqResource = EnumReqResource; + export type openDate = Date; + export type closeDate = Date; + export type detail = string; + +} + +export interface NgAuction { + id: NgAuctionFields.id; + type: NgAuctionFields.type; + finished: NgAuctionFields.finished; + target: NgAuctionFields.target; + hostGeneralId: NgAuctionFields.hostGeneralId; + reqResource: NgAuctionFields.reqResource; + openDate: NgAuctionFields.openDate; + closeDate: NgAuctionFields.closeDate; + detail: NgAuctionFields.detail; + +} + +export namespace NgAuctionBidFields { + export type no = number; + export type auctionId = number; + export type owner = number | null; + export type generalId = number; + export type amount = number; + export type date = Date; + export type aux = string; + +} + +export interface NgAuctionBid { + no: NgAuctionBidFields.no; + auctionId: NgAuctionBidFields.auctionId; + owner: NgAuctionBidFields.owner; + generalId: NgAuctionBidFields.generalId; + amount: NgAuctionBidFields.amount; + date: NgAuctionBidFields.date; + aux: NgAuctionBidFields.aux; + +} + +export namespace NgBettingFields { + export type id = number; + export type bettingId = number; + export type generalId = number; + export type userId = number | null; + export type bettingType = string; + export type amount = number; + +} + +export interface NgBetting { + id: NgBettingFields.id; + bettingId: NgBettingFields.bettingId; + generalId: NgBettingFields.generalId; + userId: NgBettingFields.userId; + bettingType: NgBettingFields.bettingType; + amount: NgBettingFields.amount; + +} + +export namespace NgDiplomacyFields { + export type no = number; + export type srcNationId = number; + export type destNationId = number; + export type prevNo = number | null; + export type state = EnumState; + export type textBrief = string; + export type textDetail = string; + export type date = Date; + export type srcSigner = number; + export type destSigner = number | null; + export type aux = string | null; + +} + +export interface NgDiplomacy { + no: NgDiplomacyFields.no; + srcNationId: NgDiplomacyFields.srcNationId; + destNationId: NgDiplomacyFields.destNationId; + prevNo: NgDiplomacyFields.prevNo; + state: NgDiplomacyFields.state; + textBrief: NgDiplomacyFields.textBrief; + textDetail: NgDiplomacyFields.textDetail; + date: NgDiplomacyFields.date; + srcSigner: NgDiplomacyFields.srcSigner; + destSigner: NgDiplomacyFields.destSigner; + aux: NgDiplomacyFields.aux; + +} + +export namespace NgGamesFields { + export type id = number; + export type serverId = string; + export type date = Date; + export type winnerNation = number | null; + export type map = string | null; + export type season = number; + export type scenario = number; + export type scenarioName = string; + export type env = string; + +} + +export interface NgGames { + id: NgGamesFields.id; + serverId: NgGamesFields.serverId; + date: NgGamesFields.date; + winnerNation: NgGamesFields.winnerNation; + map: NgGamesFields.map; + season: NgGamesFields.season; + scenario: NgGamesFields.scenario; + scenarioName: NgGamesFields.scenarioName; + env: NgGamesFields.env; + +} + +export namespace NgHistoryFields { + export type no = number; + export type serverId = string; + export type year = number | null; + export type month = number | null; + export type map = string | null; + export type globalHistory = string | null; + export type globalAction = string | null; + export type nations = string | null; + +} + +export interface NgHistory { + no: NgHistoryFields.no; + serverId: NgHistoryFields.serverId; + year: NgHistoryFields.year; + month: NgHistoryFields.month; + map: NgHistoryFields.map; + globalHistory: NgHistoryFields.globalHistory; + globalAction: NgHistoryFields.globalAction; + nations: NgHistoryFields.nations; + +} + +export namespace NgOldGeneralsFields { + export type id = number; + export type serverId = string; + export type generalNo = number; + export type owner = number | null; + export type name = string; + export type lastYearmonth = number; + export type turntime = Date; + export type data = string; + +} + +export interface NgOldGenerals { + id: NgOldGeneralsFields.id; + serverId: NgOldGeneralsFields.serverId; + generalNo: NgOldGeneralsFields.generalNo; + owner: NgOldGeneralsFields.owner; + name: NgOldGeneralsFields.name; + lastYearmonth: NgOldGeneralsFields.lastYearmonth; + turntime: NgOldGeneralsFields.turntime; + data: NgOldGeneralsFields.data; + +} + +export namespace NgOldNationsFields { + export type id = number; + export type serverId = string; + export type nation = number; + export type data = string; + export type date = Date; + +} + +export interface NgOldNations { + id: NgOldNationsFields.id; + serverId: NgOldNationsFields.serverId; + nation: NgOldNationsFields.nation; + data: NgOldNationsFields.data; + date: NgOldNationsFields.date; + +} + +export namespace PlockFields { + export type no = number; + export type type = EnumType; + export type plock = number; + export type locktime = Date; + +} + +export interface Plock { + no: PlockFields.no; + type: PlockFields.type; + plock: PlockFields.plock; + locktime: PlockFields.locktime; + +} + +export namespace RankDataFields { + export type id = number; + export type nationId = number; + export type generalId = number; + export type type = string; + export type value = number; + +} + +export interface RankData { + id: RankDataFields.id; + nationId: RankDataFields.nationId; + generalId: RankDataFields.generalId; + type: RankDataFields.type; + value: RankDataFields.value; + +} + +export namespace ReservedOpenFields { + export type id = number; + export type options = string | null; + export type date = Date | null; + +} + +export interface ReservedOpen { + id: ReservedOpenFields.id; + options: ReservedOpenFields.options; + date: ReservedOpenFields.date; + +} + +export namespace SelectNpcTokenFields { + export type id = number; + export type owner = number; + export type validUntil = Date; + export type pickMoreFrom = Date; + export type pickResult = string; + export type nonce = number; + +} + +export interface SelectNpcToken { + id: SelectNpcTokenFields.id; + owner: SelectNpcTokenFields.owner; + validUntil: SelectNpcTokenFields.validUntil; + pickMoreFrom: SelectNpcTokenFields.pickMoreFrom; + pickResult: SelectNpcTokenFields.pickResult; + nonce: SelectNpcTokenFields.nonce; + +} + +export namespace SelectPoolFields { + export type id = number; + export type uniqueName = string; + export type owner = number | null; + export type generalId = number | null; + export type reservedUntil = Date | null; + export type info = string; + +} + +export interface SelectPool { + id: SelectPoolFields.id; + uniqueName: SelectPoolFields.uniqueName; + owner: SelectPoolFields.owner; + generalId: SelectPoolFields.generalId; + reservedUntil: SelectPoolFields.reservedUntil; + info: SelectPoolFields.info; + +} + +export namespace StatisticFields { + export type no = number; + export type year = number | null; + export type month = number | null; + export type nationCount = number | null; + export type nationName = string | null; + export type nationHist = string | null; + export type genCount = string | null; + export type personalHist = string | null; + export type specialHist = string | null; + export type powerHist = string | null; + export type crewtype = string | null; + export type etc = string | null; + export type aux = string | null; + +} + +export interface Statistic { + no: StatisticFields.no; + year: StatisticFields.year; + month: StatisticFields.month; + nationCount: StatisticFields.nationCount; + nationName: StatisticFields.nationName; + nationHist: StatisticFields.nationHist; + genCount: StatisticFields.genCount; + personalHist: StatisticFields.personalHist; + specialHist: StatisticFields.specialHist; + powerHist: StatisticFields.powerHist; + crewtype: StatisticFields.crewtype; + etc: StatisticFields.etc; + aux: StatisticFields.aux; + +} + +export namespace StorageFields { + export type id = number; + export type namespace = string; + export type key = string; + export type value = string; + +} + +export interface Storage { + id: StorageFields.id; + namespace: StorageFields.namespace; + key: StorageFields.key; + value: StorageFields.value; + +} + +export namespace TournamentFields { + export type seq = number; + export type no = number | null; + export type npc = number | null; + export type name = string | null; + export type w = string | null; + export type b = string | null; + export type h = string | null; + export type leadership = number | null; + export type strength = number | null; + export type intel = number | null; + export type lvl = number | null; + export type grp = number | null; + export type grpNo = number | null; + export type win = number | null; + export type draw = number | null; + export type lose = number | null; + export type gl = number | null; + export type prmt = number | null; + +} + +export interface Tournament { + seq: TournamentFields.seq; + no: TournamentFields.no; + npc: TournamentFields.npc; + name: TournamentFields.name; + w: TournamentFields.w; + b: TournamentFields.b; + h: TournamentFields.h; + leadership: TournamentFields.leadership; + strength: TournamentFields.strength; + intel: TournamentFields.intel; + lvl: TournamentFields.lvl; + grp: TournamentFields.grp; + grpNo: TournamentFields.grpNo; + win: TournamentFields.win; + draw: TournamentFields.draw; + lose: TournamentFields.lose; + gl: TournamentFields.gl; + prmt: TournamentFields.prmt; + +} + +export namespace TroopFields { + export type troopLeader = number; + export type nation = number; + export type name = string; + +} + +export interface Troop { + troopLeader: TroopFields.troopLeader; + nation: TroopFields.nation; + name: TroopFields.name; + +} + +export namespace UserRecordFields { + export type id = number; + export type userId = number; + export type serverId = string; + export type logType = string; + export type year = number; + export type month = number; + export type date = Date | null; + export type text = string; + +} + +export interface UserRecord { + id: UserRecordFields.id; + userId: UserRecordFields.userId; + serverId: UserRecordFields.serverId; + logType: UserRecordFields.logType; + year: UserRecordFields.year; + month: UserRecordFields.month; + date: UserRecordFields.date; + text: UserRecordFields.text; + +} + +export namespace VoteFields { + export type id = number; + export type voteId = number; + export type generalId = number; + export type nationId = number; + export type selection = string; + +} + +export interface Vote { + id: VoteFields.id; + voteId: VoteFields.voteId; + generalId: VoteFields.generalId; + nationId: VoteFields.nationId; + selection: VoteFields.selection; + +} + +export namespace VoteCommentFields { + export type id = number; + export type voteId = number; + export type generalId = number; + export type nationId = number; + export type generalName = string; + export type nationName = string; + export type text = string; + export type date = Date | null; + +} + +export interface VoteComment { + id: VoteCommentFields.id; + voteId: VoteCommentFields.voteId; + generalId: VoteCommentFields.generalId; + nationId: VoteCommentFields.nationId; + generalName: VoteCommentFields.generalName; + nationName: VoteCommentFields.nationName; + text: VoteCommentFields.text; + date: VoteCommentFields.date; + +} + +export namespace WorldHistoryFields { + export type id = number; + export type nationId = number; + export type year = number; + export type month = number; + export type text = string; + +} + +export interface WorldHistory { + id: WorldHistoryFields.id; + nationId: WorldHistoryFields.nationId; + year: WorldHistoryFields.year; + month: WorldHistoryFields.month; + text: WorldHistoryFields.text; + +} diff --git a/server/index.ts b/server/index.ts index 1dda256..2986a4b 100644 --- a/server/index.ts +++ b/server/index.ts @@ -1,8 +1,17 @@ import 'reflect-metadata'; import { BasicQueryDTO } from './api/index.js'; import { validate } from 'class-validator'; +import express, { Request, Response } from "express" +import { AppDataSource } from "./data_source" +import dotenv from "dotenv"; +import session from "express-session"; +import path from 'node:path'; -export async function test(){ +dotenv.config({ + path: `.env.server.${process.env.NODE_ENV}` +}); + +export async function test() { console.log("Hello World!"); const dto = new BasicQueryDTO(); @@ -17,4 +26,39 @@ export async function test(){ await test(); + +AppDataSource.initialize().then(async () => { + + // create express app + const app = express() + app.use(bodyParser.json()) + app.use(session({ + secret: ownConfig.sessionSecret, + resave: false, + saveUninitialized: false, + })) + app.set('etag', false); + + initPKIjs(); + + app.use('/', rootRouter); + + await fs.mkdir(certDirPath, { recursive: true }); + if (!existsSync(rootSignKeyPath)) { + console.info('generate sign key'); + generateRootSignKey(); + } + + if (!existsSync(rootCertPath)) { + console.info('generate root certificate'); + await generateRootCertificateEasy(); + } + + // start express server + app.listen(ownConfig.port) + + console.log(`Express server has started on port ${ownConfig.port}`) + +}).catch(error => console.log(error)) + export default {}; \ No newline at end of file diff --git a/server/util/APIPathGen.d.ts b/server/util/APIPathGen.d.ts new file mode 100644 index 0000000..de2b18a --- /dev/null +++ b/server/util/APIPathGen.d.ts @@ -0,0 +1,26 @@ +export function APIPathGen( + obj: T, + callback: (path: string[], tail: V, pathParam?: Record) => unknown, + pathParam?: Record +): T; + +export function StrVar(paramKey: string): (next: NextCall) => { + [v in PathType]: NextCall +}; + +export function NumVar(paramKey: string, next: NextCall): { + [v: number]: NextCall +}; + +/* +const apiPath = { + SomePath: someFunc, + User: StrVar<'a'|'b'>('name')({ + Update: someFunc, + Delete: someFunc, + }), + NationInfo: NumVar('id', { + show: someFunc + }) +} +*/ \ No newline at end of file diff --git a/server/util/APIPathGen.js b/server/util/APIPathGen.js new file mode 100644 index 0000000..95093de --- /dev/null +++ b/server/util/APIPathGen.js @@ -0,0 +1,68 @@ +export function APIPathGen(obj, callback, path, pathParams) { + return new Proxy(obj, { + get(target, key) { + let nextPath; + if (path === undefined) { + nextPath = [key.toString()]; + } + else { + nextPath = [...path, key.toString()]; + } + + if (pathParams !== undefined) { + pathParams = { ...pathParams }; + } + + const varType = target.__nextVarType; + let varKey = target.__nextVarKey; + let next; + if (varType !== undefined && varKey !== undefined) { + if(varType == 'number'){ + if(key != Number(key)){ + throw `${key} is not ${varType}`; + } + key = Number(key); + } + else if ((typeof key) !== varType) { + throw `${key} is not ${varType}, but ${typeof key}`; + } + if(pathParams === undefined){ + pathParams = {} + } + pathParams[varKey] = key; + nextPath.pop(); + next = target.next; + } + else if (key in target) { + next = target[key]; + } + else { + throw `${nextPath} is not exists`; + } + + if (typeof (next) === 'function') { + return callback(nextPath, next, pathParams); + } + return APIPathGen(next, callback, nextPath, pathParams); + } + }) +} + +//generic 인자로 '자동'을 주려면 생략해야하므로 2단 호출 +export function StrVar(key) { + return (next) => { + return { + __nextVarType: 'string', + __nextVarKey: key, + next + } + } +} + +export function NumVar(key, next) { + return { + __nextVarType: 'number', + __nextVarKey: key, + next + } +} \ No newline at end of file diff --git a/server/util/BytesLike.ts b/server/util/BytesLike.ts new file mode 100644 index 0000000..abcd557 --- /dev/null +++ b/server/util/BytesLike.ts @@ -0,0 +1,2 @@ +export type Bytes = ArrayBuffer | DataView | Uint8Array; +export type BytesLike = Bytes | string; \ No newline at end of file diff --git a/server/util/Entries.ts b/server/util/Entries.ts new file mode 100644 index 0000000..9ff7a02 --- /dev/null +++ b/server/util/Entries.ts @@ -0,0 +1,3 @@ +export type Entries = { + [K in keyof T]: [K, T[K]]; +}[keyof T][]; \ No newline at end of file diff --git a/server/util/JosaUtil.ts b/server/util/JosaUtil.ts new file mode 100644 index 0000000..a707ef6 --- /dev/null +++ b/server/util/JosaUtil.ts @@ -0,0 +1,586 @@ +import { unwrap_err } from "./unwrap_err"; + +// https://github.com/coxcore/postposition 의 php 버전을 다시 typescript로 재 작성 +const KO_START_CODE = 44032; +const KO_FINISH_CODE = 55203; +const HANJA_START_CODE = 0x4e00; +const HANJA_FINISH_CODE = 0xfa0b; +const REG_INVALID_CHAR_W_HANJA = /[^a-zA-Z0-9ㄱ-ㅎ가-힣一-廓\s]+/u; +const REG_INVALID_CHAR = /[^a-zA-Z0-9ㄱ-ㅎ가-힣\s]+/u; +const REG_TARGET_CHAR = /^[\s\S]*?(\S*)\s*$/u; + +const PRE_REG_NORMAL_FIXED = [ + "check|[hm]ook|limit", +]; +const PRE_REG_SPECIAL_CHAR = [ + "[ㄱ-ㄷㅁ-ㅎ]", + "^[036]", + "[^a-zA-Z][036]", + "[a-zA-Z]9", + "^[mn]", + "\\S[mn]e?", + "\\S(?:[aeiom]|lu)b", + "(?:u|\\S[aei]|[^o]o)p", + "(?:^i|[^auh]i|\\Su|[^ei][ae]|[^oi]o)t", + "(?:\\S[iou]|[^e][ae])c?k", + "\\S[aeiou](?:c|ng)", + "foot|go+d|b[ai]g|private", + "^(?:app|kor)", +]; +const PRE_REG_SPECIAL_RO = [ + "[178ㄹ]", + "^[lr]", + "^\\Sr", + "\\Sle?", +]; + +const DEFAULT_POSTPOSITION = { + "은": "는", + "이": "가", + "과": "와", + "이나": "나", + "을": "를", + "으로": "로", + "이라": "라", + "이랑": "랑", +}; + +const JONGSUNG_HANJA = [ + 0x523b, 0x5374, 0x5404, 0x606a, 0x6164, 0x6bbc, 0x73cf, 0x811a, + 0x89ba, 0x89d2, 0x95a3, 0x4f83, 0x520a, 0x58be, 0x5978, 0x59e6, + 0x5e72, 0x5e79, 0x61c7, 0x63c0, 0x6746, 0x67ec, 0x687f, 0x6f97, + 0x764e, 0x770b, 0x78f5, 0x7a08, 0x7aff, 0x7c21, 0x809d, 0x826e, + 0x8271, 0x8aeb, 0x9593, 0x4e6b, 0x559d, 0x66f7, 0x6e34, 0x78a3, + 0x7aed, 0x845b, 0x8910, 0x874e, 0x97a8, 0x52d8, 0x574e, 0x582a, + 0x5d4c, 0x611f, 0x61be, 0x6221, 0x6562, 0x67d1, 0x6a44, 0x6e1b, + 0x7518, 0x75b3, 0x76e3, 0x77b0, 0x7d3a, 0x90af, 0x9451, 0x9452, + 0x9f95, 0x5323, 0x5cac, 0x7532, 0x80db, 0x9240, 0x9598, 0x525b, + 0x5808, 0x59dc, 0x5ca1, 0x5d17, 0x5eb7, 0x5f3a, 0x5f4a, 0x6177, + 0x6c5f, 0x757a, 0x7586, 0x7ce0, 0x7d73, 0x7db1, 0x7f8c, 0x8154, + 0x8221, 0x8591, 0x8941, 0x8b1b, 0x92fc, 0x964d, 0x9c47, 0x5580, + 0x5ba2, 0x5751, 0xf901, 0x7cb3, 0x7fb9, 0x91b5, 0x4e7e, 0x4ef6, + 0x5065, 0x5dfe, 0x5efa, 0x6106, 0x6957, 0x8171, 0x8654, 0x8e47, + 0x9375, 0x9a2b, 0x4e5e, 0x5091, 0x6770, 0x6840, 0x5109, 0x528d, + 0x5292, 0x6aa2, 0x77bc, 0x9210, 0x9ed4, 0x52ab, 0x602f, 0x8ff2, + 0x64ca, 0x683c, 0x6a84, 0x6fc0, 0x8188, 0x89a1, 0x9694, 0x5805, + 0x727d, 0x72ac, 0x7504, 0x7d79, 0x7e6d, 0x80a9, 0x898b, 0x8b74, + 0x9063, 0x9d51, 0x6289, 0x6c7a, 0x6f54, 0x7d50, 0x7f3a, 0x8a23, + 0x517c, 0x614a, 0x7b9d, 0x8b19, 0x9257, 0x938c, 0x4eac, 0x4fd3, + 0x501e, 0x50be, 0x5106, 0x52c1, 0x52cd, 0x537f, 0x5770, 0x5883, + 0x5e9a, 0x5f91, 0x6176, 0x61ac, 0x64ce, 0x656c, 0x666f, 0x66bb, + 0x66f4, 0x6897, 0x6d87, 0x7085, 0x70f1, 0x749f, 0x74a5, 0x74ca, + 0x75d9, 0x786c, 0x78ec, 0x7adf, 0x7af6, 0x7d45, 0x7d93, 0x8015, + 0x803f, 0x811b, 0x8396, 0x8b66, 0x8f15, 0x9015, 0x93e1, 0x9803, + 0x9838, 0x9a5a, 0x9be8, 0x54ed, 0x659b, 0x66f2, 0x688f, 0x7a40, + 0x8c37, 0x9d60, 0x56f0, 0x5764, 0x5d11, 0x6606, 0x68b1, 0x68cd, + 0x6efe, 0x7428, 0x889e, 0x9be4, 0x6c68, 0xf904, 0x9aa8, 0x4f9b, + 0x516c, 0x5171, 0x529f, 0x5b54, 0x5de5, 0x6050, 0x606d, 0x62f1, + 0x63a7, 0x653b, 0x73d9, 0x7a7a, 0x86a3, 0x8ca2, 0x978f, 0x4e32, + 0x5ed3, 0x69e8, 0x85ff, 0x90ed, 0xf905, 0x51a0, 0x5b98, 0x5bec, + 0x6163, 0x68fa, 0x6b3e, 0x704c, 0x742f, 0x74d8, 0x7ba1, 0x7f50, + 0x83c5, 0x89c0, 0x8cab, 0x95dc, 0x9928, 0x522e, 0x605d, 0x62ec, + 0x9002, 0x4f8a, 0x5149, 0x5321, 0x58d9, 0x5ee3, 0x66e0, 0x6d38, + 0x709a, 0x72c2, 0x73d6, 0x7b50, 0x80f1, 0x945b, 0x5b8f, 0x7d18, + 0x80b1, 0x8f5f, 0x570b, 0x5c40, 0x83ca, 0x97a0, 0x97ab, 0x9eb4, + 0x541b, 0x7a98, 0x7fa4, 0x88d9, 0x8ecd, 0x90e1, 0x5800, 0x5c48, + 0x6398, 0x7a9f, 0x5bae, 0x5f13, 0x7a79, 0x7aae, 0x828e, 0x8eac, + 0x5026, 0x5238, 0x52f8, 0x5377, 0x5708, 0x62f3, 0x6372, 0x6b0a, + 0x6dc3, 0x7737, 0x53a5, 0x7357, 0x8568, 0x8e76, 0x95d5, 0x52fb, + 0x5747, 0x7547, 0x7b60, 0x83cc, 0x921e, 0xf908, 0x6a58, 0x514b, + 0x524b, 0x5287, 0x621f, 0x68d8, 0x6975, 0x9699, 0x50c5, 0x52a4, + 0x52e4, 0x61c3, 0x65a4, 0x6839, 0x69ff, 0x747e, 0x7b4b, 0x82b9, + 0x83eb, 0x89b2, 0x8b39, 0x8fd1, 0x9949, 0xf909, 0x4eca, 0x5997, + 0x64d2, 0x6611, 0x6a8e, 0x7434, 0x7981, 0x79bd, 0x82a9, 0x887e, + 0x887f, 0x895f, 0xf90a, 0x9326, 0x4f0b, 0x53ca, 0x6025, 0x6271, + 0x6c72, 0x7d1a, 0x7d66, 0x4e98, 0x5162, 0x77dc, 0x80af, 0x7dca, + 0x4f76, 0x5409, 0x62ee, 0x6854, 0x91d1, 0x55ab, 0xf914, 0xf915, + 0xf916, 0xf917, 0xf918, 0x8afe, 0xf919, 0xf91a, 0xf91b, 0xf91c, + 0x6696, 0xf91d, 0x7156, 0xf91e, 0xf91f, 0x96e3, 0xf920, 0x634f, + 0x637a, 0x5357, 0xf921, 0x678f, 0x6960, 0x6e73, 0xf922, 0x7537, + 0xf923, 0xf924, 0xf925, 0x7d0d, 0xf926, 0xf927, 0x8872, 0x56ca, + 0x5a18, 0xf928, 0xf929, 0xf92a, 0xf92b, 0xf92c, 0xf92e, 0x5e74, + 0x649a, 0x79ca, 0x5ff5, 0x606c, 0x62c8, 0x637b, 0x5be7, 0x5bd7, + 0xf93b, 0xf93c, 0xf93d, 0xf93e, 0xf93f, 0xf940, 0xf941, 0xf942, + 0xf943, 0x6fc3, 0xf944, 0xf945, 0x81bf, 0x8fb2, 0x5ae9, 0x8a25, + 0xf952, 0xf953, 0xf954, 0xf955, 0xf956, 0xf957, 0x80fd, 0xf958, + 0xf959, 0x533f, 0x6eba, 0x4e39, 0x4eb6, 0x4f46, 0x55ae, 0x5718, + 0x58c7, 0x5f56, 0x65b7, 0x65e6, 0x6a80, 0x6bb5, 0x6e4d, 0x77ed, + 0x7aef, 0x7c1e, 0x7dde, 0x86cb, 0x8892, 0x9132, 0x935b, 0x64bb, + 0x6fbe, 0x737a, 0x75b8, 0x9054, 0x5556, 0x574d, 0x61ba, 0x64d4, + 0x66c7, 0x6de1, 0x6e5b, 0x6f6d, 0x6fb9, 0x75f0, 0x8043, 0x81bd, + 0x8541, 0x8983, 0x8ac7, 0x8b5a, 0x931f, 0x6c93, 0x7553, 0x7b54, + 0x8e0f, 0x905d, 0x5510, 0x5802, 0x5858, 0x5e62, 0x6207, 0x649e, + 0x68e0, 0x7576, 0x7cd6, 0x87b3, 0x9ee8, 0x5b85, 0x5fb7, 0x60b3, + 0x6bd2, 0x7006, 0x7258, 0x72a2, 0x7368, 0x7763, 0x79bf, 0x7be4, + 0x7e9b, 0x8b80, 0x58a9, 0x60c7, 0x6566, 0x65fd, 0x66be, 0x6c8c, + 0x711e, 0x71c9, 0x8c5a, 0x9813, 0x4e6d, 0x7a81, 0x4edd, 0x51ac, + 0x51cd, 0x52d5, 0x540c, 0x61a7, 0x6771, 0x6850, 0x68df, 0x6d1e, + 0x6f7c, 0x75bc, 0x77b3, 0x7ae5, 0x80f4, 0x8463, 0x9285, 0x5c6f, + 0x81c0, 0x829a, 0x9041, 0x906f, 0x920d, 0x5f97, 0x5d9d, 0x6a59, + 0x71c8, 0x767b, 0x7b49, 0x85e4, 0x8b04, 0x9127, 0x9a30, 0xf95c, + 0x6d1b, 0x70d9, 0x73de, 0x7d61, 0x843d, 0xf95d, 0x916a, 0x99f1, + 0xf95e, 0x4e82, 0x5375, 0x6b04, 0x6b12, 0x703e, 0x721b, 0x862d, + 0x9e1e, 0x524c, 0x8fa3, 0x5d50, 0x64e5, 0x652c, 0x6b16, 0x6feb, + 0x7c43, 0x7e9c, 0x85cd, 0x8964, 0x89bd, 0x62c9, 0x81d8, 0x881f, + 0x5eca, 0x6717, 0x6d6a, 0x72fc, 0x7405, 0x746f, 0x8782, 0x90de, + 0x51b7, 0x63a0, 0x7565, 0x4eae, 0x5006, 0x5169, 0x51c9, 0x6881, + 0x6a11, 0x7cae, 0x7cb1, 0x7ce7, 0x826f, 0x8ad2, 0x8f1b, 0x91cf, + 0x529b, 0x66c6, 0x6b77, 0x701d, 0x792b, 0x8f62, 0x9742, 0x6190, + 0x6200, 0x6523, 0x6f23, 0x7149, 0x7489, 0x7df4, 0x806f, 0x84ee, + 0x8f26, 0x9023, 0x934a, 0x51bd, 0x5217, 0x52a3, 0x6d0c, 0x70c8, + 0x88c2, 0x5ec9, 0x6582, 0x6bae, 0x6fc2, 0x7c3e, 0x7375, 0x4ee4, + 0x4f36, 0x56f9, 0xf95f, 0x5cba, 0x5dba, 0x601c, 0x73b2, 0x7b2d, + 0x7f9a, 0x7fce, 0x8046, 0x901e, 0x9234, 0x96f6, 0x9748, 0x9818, + 0x9f61, 0x788c, 0x797f, 0x7da0, 0x83c9, 0x9304, 0x9e7f, 0x9e93, + 0x8ad6, 0x58df, 0x5f04, 0x6727, 0x7027, 0x74cf, 0x7c60, 0x807e, + 0x9f8d, 0x516d, 0x622e, 0x9678, 0x4f96, 0x502b, 0x5d19, 0x6dea, + 0x7db8, 0x8f2a, 0x5f8b, 0x6144, 0x6817, 0xf961, 0x9686, 0x52d2, + 0x808b, 0x51dc, 0x51cc, 0x695e, 0x7a1c, 0x7dbe, 0x83f1, 0x9675, + 0x541d, 0x6f7e, 0x71d0, 0x7498, 0x85fa, 0x8eaa, 0x96a3, 0x9c57, + 0x9e9f, 0x6797, 0x6dcb, 0x7433, 0x81e8, 0x9716, 0x782c, 0x7acb, + 0x7b20, 0x7c92, 0x5bde, 0x5e55, 0x6f20, 0x819c, 0x83ab, 0x9088, + 0x4e07, 0x534d, 0x5a29, 0x5dd2, 0x5f4e, 0x6162, 0x633d, 0x6669, + 0x66fc, 0x6eff, 0x6f2b, 0x7063, 0x779e, 0x842c, 0x8513, 0x883b, + 0x8f13, 0x9945, 0x9c3b, 0x551c, 0x62b9, 0x672b, 0x6cab, 0x8309, + 0x896a, 0x977a, 0x4ea1, 0x5984, 0x5fd8, 0x5fd9, 0x671b, 0x7db2, + 0x7f54, 0x8292, 0x832b, 0x83bd, 0x8f1e, 0x9099, 0x8108, 0x8c8a, + 0x964c, 0x9a40, 0x9ea5, 0x5b5f, 0x6c13, 0x731b, 0x76f2, 0x76df, + 0x840c, 0x51aa, 0x8993, 0x514d, 0x5195, 0x52c9, 0x68c9, 0x6c94, + 0x7704, 0x7720, 0x7dbf, 0x7dec, 0x9762, 0x9eb5, 0x6ec5, 0x8511, + 0x51a5, 0x540d, 0x547d, 0x660e, 0x669d, 0x6927, 0x6e9f, 0x76bf, + 0x7791, 0x8317, 0x84c2, 0x879f, 0x9169, 0x9298, 0x9cf4, 0x6728, + 0x6c90, 0x7267, 0x76ee, 0x7766, 0x7a46, 0x9da9, 0x6b7f, 0x6c92, + 0x5922, 0x6726, 0x8499, 0x58a8, 0x9ed8, 0x5011, 0x520e, 0x543b, + 0x554f, 0x6587, 0x6c76, 0x7d0a, 0x7d0b, 0x805e, 0x868a, 0x9580, + 0x96ef, 0x52ff, 0x6c95, 0x7269, 0x5cb7, 0x60b6, 0x610d, 0x61ab, + 0x654f, 0x65fb, 0x65fc, 0x6c11, 0x6cef, 0x739f, 0x73c9, 0x7de1, + 0x9594, 0x5bc6, 0x871c, 0x8b10, 0x525d, 0x535a, 0x62cd, 0x640f, + 0x64b2, 0x6734, 0x6a38, 0x6cca, 0x73c0, 0x749e, 0x7b94, 0x7c95, + 0x7e1b, 0x818a, 0x8236, 0x8584, 0x8feb, 0x96f9, 0x99c1, 0x4f34, + 0x534a, 0x53cd, 0x53db, 0x62cc, 0x642c, 0x6500, 0x6591, 0x69c3, + 0x6cee, 0x6f58, 0x73ed, 0x7554, 0x7622, 0x76e4, 0x76fc, 0x78d0, + 0x78fb, 0x792c, 0x7d46, 0x822c, 0x87e0, 0x8fd4, 0x9812, 0x98ef, + 0x52c3, 0x62d4, 0x64a5, 0x6e24, 0x6f51, 0x767c, 0x8dcb, 0x91b1, + 0x9262, 0x9aee, 0x9b43, 0x5023, 0x508d, 0x574a, 0x59a8, 0x5c28, + 0x5e47, 0x5f77, 0x623f, 0x653e, 0x65b9, 0x65c1, 0x6609, 0x678b, + 0x699c, 0x6ec2, 0x78c5, 0x7d21, 0x80aa, 0x8180, 0x822b, 0x82b3, + 0x84a1, 0x868c, 0x8a2a, 0x8b17, 0x90a6, 0x9632, 0x9f90, 0x4f2f, + 0x4f70, 0x5e1b, 0x67cf, 0x6822, 0x767d, 0x767e, 0x9b44, 0x5e61, + 0x6a0a, 0x7169, 0x71d4, 0x756a, 0xf964, 0x7e41, 0x8543, 0x85e9, + 0x98dc, 0x4f10, 0x7b4f, 0x7f70, 0x95a5, 0x51e1, 0x5e06, 0x68b5, + 0x6c3e, 0x6c4e, 0x6cdb, 0x72af, 0x7bc4, 0x8303, 0x6cd5, 0x743a, + 0x50fb, 0x5288, 0x58c1, 0x64d8, 0x6a97, 0x74a7, 0x7656, 0x78a7, + 0x8617, 0x95e2, 0x9739, 0xf965, 0x535e, 0x5f01, 0x8b8a, 0x8fa8, + 0x8faf, 0x908a, 0x5225, 0x77a5, 0x9c49, 0x9f08, 0x4e19, 0x5002, + 0x5175, 0x5c5b, 0x5e77, 0x661e, 0x663a, 0x67c4, 0x68c5, 0x70b3, + 0x7501, 0x75c5, 0x79c9, 0x7add, 0x8f27, 0x9920, 0x9a08, 0x4f0f, + 0x50d5, 0x5310, 0x535c, 0x5b93, 0x5fa9, 0x670d, 0x798f, 0x8179, + 0x832f, 0x8514, 0x8907, 0x8986, 0x8f39, 0x8f3b, 0x99a5, 0x9c12, + 0x672c, 0x4e76, 0x4ff8, 0x5949, 0x5c01, 0x5cef, 0x5cf0, 0x6367, + 0x68d2, 0x70fd, 0x71a2, 0x742b, 0x7e2b, 0x84ec, 0x8702, 0x9022, + 0x92d2, 0x9cf3, 0x5317, 0x5206, 0x5429, 0x5674, 0x58b3, 0x5954, + 0x596e, 0x5fff, 0x61a4, 0x626e, 0x6610, 0x6c7e, 0x711a, 0x76c6, + 0x7c89, 0x7cde, 0x7d1b, 0x82ac, 0x8cc1, 0x96f0, 0xf967, 0x4f5b, + 0x5f17, 0x5f7f, 0x62c2, 0x5d29, 0x670b, 0x68da, 0x787c, 0x7e43, + 0x9d6c, 0x56ac, 0x5b2a, 0x5f6c, 0x658c, 0x6ab3, 0x6baf, 0x6d5c, + 0x6ff1, 0x7015, 0x725d, 0x73ad, 0x8ca7, 0x8cd3, 0x983b, 0x6191, + 0x6c37, 0x8058, 0x9a01, 0x524a, 0xf969, 0x6714, 0xf96a, 0x5098, + 0x522a, 0x5c71, 0x6563, 0x6c55, 0x73ca, 0x7523, 0x759d, 0x7b97, + 0x849c, 0x9178, 0x9730, 0x4e77, 0x6492, 0x6bba, 0x715e, 0x85a9, + 0x4e09, 0xf96b, 0x6749, 0x68ee, 0x6e17, 0x829f, 0x8518, 0x886b, + 0x63f7, 0x6f81, 0x9212, 0x98af, 0x4e0a, 0x50b7, 0x50cf, 0x511f, + 0x5546, 0x55aa, 0x5617, 0x5b40, 0x5c19, 0x5ce0, 0x5e38, 0x5e8a, + 0x5ea0, 0x5ec2, 0x60f3, 0x6851, 0x6a61, 0x6e58, 0x723d, 0x7240, + 0x72c0, 0x76f8, 0x7965, 0x7bb1, 0x7fd4, 0x88f3, 0x89f4, 0x8a73, + 0x8c61, 0x8cde, 0x971c, 0x55c7, 0xf96c, 0x7a61, 0x7d22, 0x8272, + 0x7272, 0x751f, 0x7525, 0xf96d, 0x7b19, 0x5915, 0x596d, 0x5e2d, + 0x60dc, 0x6614, 0x6673, 0x6790, 0x6c50, 0x6dc5, 0x6f5f, 0x77f3, + 0x78a9, 0x84c6, 0x91cb, 0x932b, 0x4ed9, 0x50ca, 0x5148, 0x5584, + 0x5b0b, 0x5ba3, 0x6247, 0x657e, 0x65cb, 0x6e32, 0x717d, 0x7401, + 0x7444, 0x7487, 0x74bf, 0x766c, 0x79aa, 0x7dda, 0x7e55, 0x7fa8, + 0x817a, 0x81b3, 0x8239, 0x861a, 0x87ec, 0x8a75, 0x8de3, 0x9078, + 0x9291, 0x9425, 0x994d, 0x9bae, 0x5368, 0x5c51, 0x6954, 0x6cc4, + 0x6d29, 0x6e2b, 0x820c, 0x859b, 0x893b, 0x8a2d, 0x8aaa, 0x96ea, + 0x9f67, 0x5261, 0x66b9, 0x6bb2, 0x7e96, 0x87fe, 0x8d0d, 0x9583, + 0x965d, 0x651d, 0x6d89, 0x71ee, 0xf96e, 0x57ce, 0x59d3, 0x5bac, + 0x6027, 0x60fa, 0x6210, 0x661f, 0x665f, 0x7329, 0x73f9, 0x76db, + 0x7701, 0x7b6c, 0x8056, 0x8072, 0x8165, 0x8aa0, 0x9192, 0x4fd7, + 0x5c6c, 0x675f, 0x6d91, 0x7c9f, 0x7e8c, 0x8b16, 0x8d16, 0x901f, + 0x5b6b, 0x5dfd, 0x640d, 0x84c0, 0x905c, 0x98e1, 0x7387, 0x5b8b, + 0x609a, 0x677e, 0x6dde, 0x8a1f, 0x8aa6, 0x9001, 0x980c, 0x53d4, + 0x587e, 0x5919, 0x5b70, 0x5bbf, 0x6dd1, 0x6f5a, 0x719f, 0x7421, + 0x74b9, 0x8085, 0x83fd, 0x5de1, 0x5f87, 0x5faa, 0x6042, 0x65ec, + 0x6812, 0x696f, 0x6a53, 0x6b89, 0x6d35, 0x6df3, 0x73e3, 0x76fe, + 0x77ac, 0x7b4d, 0x7d14, 0x8123, 0x821c, 0x8340, 0x84f4, 0x8563, + 0x8a62, 0x8ac4, 0x9187, 0x931e, 0x9806, 0x99b4, 0x620c, 0x8853, + 0x8ff0, 0x9265, 0x5d07, 0x5d27, 0x5d69, 0x745f, 0x819d, 0x8768, + 0x6fd5, 0x62fe, 0x7fd2, 0x8936, 0x8972, 0x4e1e, 0x4e58, 0x50e7, + 0x52dd, 0x5347, 0x627f, 0x6607, 0x7e69, 0x8805, 0x965e, 0x57f4, + 0x5bd4, 0x5f0f, 0x606f, 0x62ed, 0x690d, 0x6b96, 0x6e5c, 0x7184, + 0x7bd2, 0x8755, 0x8b58, 0x8efe, 0x98df, 0x98fe, 0x4f38, 0x4f81, + 0x4fe1, 0x547b, 0x5a20, 0x5bb8, 0x613c, 0x65b0, 0x6668, 0x71fc, + 0x7533, 0x795e, 0x7d33, 0x814e, 0x81e3, 0x8398, 0x85aa, 0x85ce, + 0x8703, 0x8a0a, 0x8eab, 0x8f9b, 0xf971, 0x8fc5, 0x5931, 0x5ba4, + 0x5be6, 0x6089, 0x5be9, 0x5c0b, 0x5fc3, 0x6c81, 0xf972, 0x6df1, + 0x700b, 0x751a, 0x82af, 0x8af6, 0x4ec0, 0x5341, 0xf973, 0x96d9, + 0x580a, 0x5cb3, 0x5dbd, 0x5e44, 0x60e1, 0x6115, 0x63e1, 0x6a02, + 0x6e25, 0x9102, 0x9354, 0x984e, 0x9c10, 0x9f77, 0x5b89, 0x5cb8, + 0x6309, 0x664f, 0x6848, 0x773c, 0x96c1, 0x978d, 0x9854, 0x9b9f, + 0x65a1, 0x8b01, 0x8ecb, 0x95bc, 0x5535, 0x5ca9, 0x5dd6, 0x5eb5, + 0x6697, 0x764c, 0x83f4, 0x95c7, 0x58d3, 0x62bc, 0x72ce, 0x9d28, + 0x4ef0, 0x592e, 0x600f, 0x663b, 0x6b83, 0x79e7, 0x9d26, 0x5384, + 0x627c, 0x6396, 0x6db2, 0x7e0a, 0x814b, 0x984d, 0x6afb, 0x7f4c, + 0x9daf, 0x9e1a, 0x5f31, 0xf975, 0xf976, 0x7d04, 0x82e5, 0x846f, + 0x84bb, 0x85e5, 0x8e8d, 0xf977, 0x4f6f, 0xf978, 0xf979, 0x58e4, + 0x5b43, 0x6059, 0x63da, 0x6518, 0x656d, 0x6698, 0xf97a, 0x694a, + 0x6a23, 0x6d0b, 0x7001, 0x716c, 0x75d2, 0x760d, 0x79b3, 0x7a70, + 0xf97b, 0x7f8a, 0xf97c, 0x8944, 0xf97d, 0x8b93, 0x91c0, 0x967d, + 0xf97e, 0x990a, 0x5104, 0x61b6, 0x6291, 0x6a8d, 0x81c6, 0x5043, + 0x5830, 0x5f66, 0x7109, 0x8a00, 0x8afa, 0x5b7c, 0x8616, 0x4ffa, + 0x513c, 0x56b4, 0x5944, 0x63a9, 0x6df9, 0x5daa, 0x696d, 0x5186, + 0x4ea6, 0xf98a, 0x57df, 0x5f79, 0x6613, 0xf98b, 0xf98c, 0x75ab, + 0x7e79, 0x8b6f, 0xf98d, 0x9006, 0x9a5b, 0x56a5, 0x5827, 0x59f8, + 0x5a1f, 0x5bb4, 0xf98e, 0x5ef6, 0xf98f, 0xf990, 0x6350, 0x633b, + 0xf991, 0x693d, 0x6c87, 0x6cbf, 0x6d8e, 0x6d93, 0x6df5, 0x6f14, + 0xf992, 0x70df, 0x7136, 0x7159, 0xf993, 0x71c3, 0x71d5, 0xf994, + 0x784f, 0x786f, 0xf995, 0x7b75, 0x7de3, 0xf996, 0x7e2f, 0xf997, + 0x884d, 0x8edf, 0xf998, 0xf999, 0xf99a, 0x925b, 0xf99b, 0x9cf6, + 0xf99c, 0xf99d, 0xf99e, 0x6085, 0x6d85, 0xf99f, 0x71b1, 0xf9a0, + 0xf9a1, 0x95b1, 0x53ad, 0xf9a2, 0xf9a3, 0xf9a4, 0x67d3, 0xf9a5, + 0x708e, 0x7130, 0x7430, 0x8276, 0x82d2, 0xf9a6, 0x95bb, 0x9ae5, + 0x9e7d, 0x66c4, 0xf9a7, 0x71c1, 0x8449, 0xf9a8, 0xf9a9, 0x584b, + 0xf9aa, 0xf9ab, 0x5db8, 0x5f71, 0xf9ac, 0x6620, 0x668e, 0x6979, + 0x69ae, 0x6c38, 0x6cf3, 0x6e36, 0x6f41, 0x6fda, 0x701b, 0x702f, + 0x7150, 0x71df, 0x7370, 0xf9ad, 0x745b, 0xf9ae, 0x74d4, 0x76c8, + 0x7a4e, 0x7e93, 0xf9af, 0xf9b0, 0x82f1, 0x8a60, 0x8fce, 0xf9b1, + 0x9348, 0xf9b2, 0x9719, 0xf9b3, 0xf9b4, 0x5c4b, 0x6c83, 0x7344, + 0x7389, 0x923a, 0x6eab, 0x7465, 0x761f, 0x7a69, 0x7e15, 0x860a, + 0x5140, 0x58c5, 0x64c1, 0x74ee, 0x7515, 0x7670, 0x7fc1, 0x9095, + 0x96cd, 0x9954, 0x5a49, 0x5b8c, 0x5b9b, 0x68a1, 0x6900, 0x6d63, + 0x73a9, 0x7413, 0x742c, 0x7897, 0x7de9, 0x7feb, 0x8118, 0x8155, + 0x839e, 0x8c4c, 0x962e, 0x9811, 0x66f0, 0x5f80, 0x65fa, 0x6789, + 0x6c6a, 0x738b, 0x617e, 0x6b32, 0x6d74, 0x7e1f, 0x8925, 0x8fb1, + 0x4fd1, 0x50ad, 0x5197, 0x52c7, 0x57c7, 0x5889, 0x5bb9, 0x5eb8, + 0x6142, 0x6995, 0x6d8c, 0x6e67, 0x6eb6, 0x7194, 0x7462, 0x7528, + 0x752c, 0x8073, 0x8338, 0x84c9, 0x8e0a, 0x9394, 0x93de, 0xf9c4, + 0x52d6, 0x5f67, 0x65ed, 0x6631, 0x682f, 0x715c, 0x7a36, 0x90c1, + 0x980a, 0x4e91, 0xf9c5, 0x6a52, 0x6b9e, 0x6f90, 0x7189, 0x8018, + 0x82b8, 0x8553, 0x904b, 0x9695, 0x96f2, 0x97fb, 0x851a, 0x9b31, + 0x4e90, 0x718a, 0x96c4, 0x5143, 0x539f, 0x54e1, 0x5713, 0x5712, + 0x57a3, 0x5a9b, 0x5ac4, 0x5bc3, 0x6028, 0x613f, 0x63f4, 0x6c85, + 0x6d39, 0x6e72, 0x6e90, 0x7230, 0x733f, 0x7457, 0x82d1, 0x8881, + 0x8f45, 0x9060, 0xf9c6, 0x9662, 0x9858, 0x9d1b, 0x6708, 0x8d8a, + 0x925e, 0xf9d1, 0x5809, 0xf9d2, 0x6bd3, 0x8089, 0x80b2, 0xf9d3, + 0xf9d4, 0x5141, 0x596b, 0x5c39, 0xf9d5, 0xf9d6, 0x6f64, 0x73a7, + 0x80e4, 0x8d07, 0xf9d7, 0x9217, 0x958f, 0xf9d8, 0xf9d9, 0xf9da, + 0xf9db, 0x807f, 0x620e, 0x701c, 0x7d68, 0x878d, 0xf9dc, 0x57a0, + 0x6069, 0x6147, 0x6bb7, 0x8abe, 0x9280, 0x96b1, 0x4e59, 0x541f, + 0x6deb, 0x852d, 0x9670, 0x97f3, 0x98ee, 0x63d6, 0x6ce3, 0x9091, + 0x51dd, 0x61c9, 0x81ba, 0x9df9, 0xf9eb, 0xf9ec, 0x7037, 0x76ca, + 0x7fca, 0x7fcc, 0x7ffc, 0x8b1a, 0x4eba, 0x4ec1, 0x5203, 0x5370, + 0xf9ed, 0x54bd, 0x56e0, 0x59fb, 0x5bc5, 0x5f15, 0x5fcd, 0x6e6e, + 0xf9ee, 0xf9ef, 0x7d6a, 0x8335, 0xf9f0, 0x8693, 0x8a8d, 0xf9f1, + 0x976d, 0x9777, 0xf9f2, 0xf9f3, 0x4e00, 0x4f5a, 0x4f7e, 0x58f9, + 0x65e5, 0x6ea2, 0x9038, 0x93b0, 0x99b9, 0x4efb, 0x58ec, 0x598a, + 0x59d9, 0x6041, 0xf9f4, 0xf9f5, 0x7a14, 0xf9f6, 0x834f, 0x8cc3, + 0x5165, 0x5344, 0xf9f7, 0xf9f8, 0xf9f9, 0x4ecd, 0x5269, 0x5b55, + 0x82bf, 0x4f5c, 0x52fa, 0x56bc, 0x65ab, 0x6628, 0x707c, 0x70b8, + 0x7235, 0x7dbd, 0x828d, 0x914c, 0x96c0, 0x9d72, 0x5b71, 0x68e7, + 0x6b98, 0x6f7a, 0x76de, 0x5c91, 0x66ab, 0x6f5b, 0x7bb4, 0x7c2a, + 0x8836, 0x96dc, 0x4e08, 0x4ed7, 0x5320, 0x5834, 0x58bb, 0x58ef, + 0x596c, 0x5c07, 0x5e33, 0x5e84, 0x5f35, 0x638c, 0x66b2, 0x6756, + 0x6a1f, 0x6aa3, 0x6b0c, 0x6f3f, 0x7246, 0xf9fa, 0x7350, 0x748b, + 0x7ae0, 0x7ca7, 0x8178, 0x81df, 0x81e7, 0x838a, 0x846c, 0x8523, + 0x8594, 0x85cf, 0x88dd, 0x8d13, 0x91ac, 0x9577, 0x969c, 0x722d, + 0x7b8f, 0x8acd, 0x931a, 0x52e3, 0x540a, 0x5ae1, 0x5bc2, 0x6458, + 0x6575, 0x6ef4, 0x72c4, 0xf9fb, 0x7684, 0x7a4d, 0x7b1b, 0x7c4d, + 0x7e3e, 0x7fdf, 0x837b, 0x8b2b, 0x8cca, 0x8d64, 0x8de1, 0x8e5f, + 0x8fea, 0x8ff9, 0x9069, 0x93d1, 0x4f43, 0x4f7a, 0x50b3, 0x5168, + 0x5178, 0x524d, 0x526a, 0x5861, 0x587c, 0x5960, 0x5c08, 0x5c55, + 0x5edb, 0x609b, 0x6230, 0x6813, 0x6bbf, 0x6c08, 0x6fb1, 0x714e, + 0x7420, 0x7530, 0x7538, 0x7551, 0x7672, 0x7b4c, 0x7b8b, 0x7bad, + 0x7bc6, 0x7e8f, 0x8a6e, 0x8f3e, 0x8f49, 0x923f, 0x9293, 0x9322, + 0x942b, 0x96fb, 0x985a, 0x986b, 0x991e, 0x5207, 0x622a, 0x6298, + 0x6d59, 0x7664, 0x7aca, 0x7bc0, 0x7d76, 0x5360, 0x5cbe, 0x5e97, + 0x6f38, 0x70b9, 0x7c98, 0x9711, 0x9b8e, 0x9ede, 0x63a5, 0x647a, + 0x8776, 0x4e01, 0x4e95, 0x4ead, 0x505c, 0x5075, 0x5448, 0x59c3, + 0x5b9a, 0x5e40, 0x5ead, 0x5ef7, 0x5f81, 0x60c5, 0x633a, 0x653f, + 0x6574, 0x65cc, 0x6676, 0x6678, 0x67fe, 0x6968, 0x6a89, 0x6b63, + 0x6c40, 0x6dc0, 0x6de8, 0x6e1f, 0x6e5e, 0x701e, 0x70a1, 0x738e, + 0x73fd, 0x753a, 0x775b, 0x7887, 0x798e, 0x7a0b, 0x7a7d, 0x7cbe, + 0x7d8e, 0x8247, 0x8a02, 0x8aea, 0x8c9e, 0x912d, 0x914a, 0x91d8, + 0x9266, 0x92cc, 0x9320, 0x9706, 0x9756, 0x975c, 0x9802, 0x9f0e, + 0x65cf, 0x7c07, 0x8db3, 0x93c3, 0x5b58, 0x5c0a, 0x5352, 0x62d9, + 0x731d, 0x5027, 0x5b97, 0x5f9e, 0x60b0, 0x616b, 0x68d5, 0x6dd9, + 0x742e, 0x7a2e, 0x7d42, 0x7d9c, 0x7e31, 0x816b, 0x8e2a, 0x8e35, + 0x937e, 0x9418, 0x7af9, 0x7ca5, 0x4fca, 0x5101, 0x51c6, 0x57c8, + 0x5bef, 0x5cfb, 0x6659, 0x6a3d, 0x6d5a, 0x6e96, 0x6fec, 0x710c, + 0x756f, 0x7ae3, 0x8822, 0x9021, 0x9075, 0x96cb, 0x99ff, 0x8301, + 0x4e2d, 0x4ef2, 0x8846, 0x91cd, 0x537d, 0x6adb, 0x696b, 0x6c41, + 0x847a, 0x589e, 0x618e, 0x66fe, 0x62ef, 0x70dd, 0x7511, 0x75c7, + 0x7e52, 0x84b8, 0x8b49, 0x8d08, 0x76f4, 0x7a19, 0x7a37, 0x7e54, + 0x8077, 0x5507, 0x55d4, 0x5875, 0x632f, 0x6422, 0x6649, 0x664b, + 0x686d, 0x699b, 0x6b84, 0x6d25, 0x6eb1, 0x73cd, 0x7468, 0x74a1, + 0x755b, 0x75b9, 0x76e1, 0x771e, 0x778b, 0x79e6, 0x7e09, 0x7e1d, + 0x81fb, 0x852f, 0x8897, 0x8a3a, 0x8cd1, 0x8eeb, 0x8fb0, 0x9032, + 0x93ad, 0x9663, 0x9673, 0x9707, 0x4f84, 0x53f1, 0x59ea, 0x5ac9, + 0x5e19, 0x684e, 0x74c6, 0x75be, 0x79e9, 0x7a92, 0x81a3, 0x86ed, + 0x8cea, 0x8dcc, 0x8fed, 0x659f, 0x6715, 0xf9fd, 0x57f7, 0x6f57, + 0x7ddd, 0x8f2f, 0x93f6, 0x96c6, 0x5fb5, 0x61f2, 0x6f84, 0x6349, + 0x643e, 0x7740, 0x7a84, 0x932f, 0x947f, 0x9f6a, 0x64b0, 0x6faf, + 0x71e6, 0x74a8, 0x74da, 0x7ac4, 0x7c12, 0x7e82, 0x7cb2, 0x7e98, + 0x8b9a, 0x8d0a, 0x947d, 0x9910, 0x994c, 0x5239, 0x5bdf, 0x64e6, + 0x672d, 0x7d2e, 0x50ed, 0x53c3, 0x5879, 0x6158, 0x6159, 0x61fa, + 0x65ac, 0x7ad9, 0x8b92, 0x8b96, 0x5009, 0x5021, 0x5275, 0x5531, + 0x5a3c, 0x5ee0, 0x5f70, 0x6134, 0x655e, 0x660c, 0x6636, 0x66a2, + 0x69cd, 0x6ec4, 0x6f32, 0x7316, 0x7621, 0x7a93, 0x8139, 0x8259, + 0x83d6, 0x84bc, 0x518a, 0x67f5, 0x7b56, 0x8cac, 0x501c, 0xf9ff, + 0x5254, 0x5c3a, 0x617d, 0x621a, 0x62d3, 0x64f2, 0x65a5, 0x6ecc, + 0x7620, 0x810a, 0x8e60, 0x965f, 0x96bb, 0x4edf, 0x5343, 0x5598, + 0x5929, 0x5ddd, 0x64c5, 0x6cc9, 0x6dfa, 0x7394, 0x7a7f, 0x821b, + 0x85a6, 0x8ce4, 0x8e10, 0x9077, 0x91e7, 0x95e1, 0x9621, 0x97c6, + 0x51f8, 0x54f2, 0x5586, 0x5fb9, 0x64a4, 0x6f88, 0x7db4, 0x8f1f, + 0x8f4d, 0x9435, 0x50c9, 0x5c16, 0x6cbe, 0x6dfb, 0x751b, 0x77bb, + 0x7c3d, 0x7c64, 0x8a79, 0x8ac2, 0x581e, 0x59be, 0x5e16, 0x6377, + 0x7252, 0x758a, 0x776b, 0x8adc, 0x8cbc, 0x8f12, 0x5ef3, 0x6674, + 0x6df8, 0x807d, 0x83c1, 0x8acb, 0x9751, 0x9bd6, 0x4fc3, 0x56d1, + 0x71ed, 0x77d7, 0x8700, 0x89f8, 0x5bf8, 0x5fd6, 0x6751, 0x90a8, + 0x53e2, 0x585a, 0x5bf5, 0x60a4, 0x6181, 0x6460, 0x7e3d, 0x8070, + 0x8525, 0x9283, 0x64ae, 0x4e11, 0x755c, 0x795d, 0x7afa, 0x7b51, + 0x7bc9, 0x7e2e, 0x84c4, 0x8e59, 0x8e74, 0x8ef8, 0x9010, 0x6625, + 0x693f, 0x7443, 0x51fa, 0x672e, 0x9edc, 0x5145, 0x5fe0, 0x6c96, + 0x87f2, 0x885d, 0x8877, 0x5074, 0x4ec4, 0x53a0, 0x60fb, 0x6e2c, + 0x5c64, 0x5247, 0x52c5, 0x98ed, 0x89aa, 0x4e03, 0x67d2, 0x6f06, + 0x4fb5, 0x5be2, 0x6795, 0x6c88, 0x6d78, 0x741b, 0x7827, 0x91dd, + 0x937c, 0x87c4, 0x79e4, 0x7a31, 0x502c, 0x5353, 0x5544, 0x577c, + 0xfa01, 0x6258, 0xfa02, 0x64e2, 0x666b, 0x67dd, 0x6fc1, 0x6fef, + 0x7422, 0x7438, 0x8a17, 0x9438, 0x5451, 0x5606, 0x5766, 0x5f48, + 0x619a, 0x6b4e, 0x7058, 0x70ad, 0x7dbb, 0x8a95, 0x596a, 0x812b, + 0x63a2, 0x7708, 0x803d, 0x8caa, 0x5854, 0x642d, 0x69bb, 0x5b95, + 0x5e11, 0x6e6f, 0xfa03, 0x8569, 0xfa04, 0x64c7, 0x6fa4, 0x6491, + 0x615f, 0x6876, 0xfa05, 0x75db, 0x7b52, 0x7d71, 0x901a, 0x615d, + 0x7279, 0x95d6, 0x5224, 0x5742, 0x677f, 0x7248, 0x74e3, 0x8ca9, + 0x8fa6, 0x9211, 0x962a, 0x516b, 0x53ed, 0x634c, 0x5f6d, 0x6f8e, + 0x70f9, 0x81a8, 0x610e, 0x4fbf, 0x504f, 0x6241, 0x7247, 0x7bc7, + 0x7de8, 0x7fe9, 0x904d, 0x97ad, 0x9a19, 0x8cb6, 0x576a, 0x5e73, + 0x67b0, 0x840d, 0x8a55, 0x5e45, 0x66b4, 0x66dd, 0x7011, 0x7206, + 0xfa07, 0x54c1, 0x7a1f, 0x6953, 0x8af7, 0x8c4a, 0x98a8, 0x99ae, + 0x5339, 0x5f3c, 0x5fc5, 0x6ccc, 0x73cc, 0x7562, 0x758b, 0x7b46, + 0x82fe, 0x999d, 0x4e4f, 0x903c, 0x58d1, 0x5b78, 0x8650, 0x8b14, + 0x9db4, 0x5bd2, 0x6068, 0x608d, 0x65f1, 0x6c57, 0x6f22, 0x6fa3, + 0x701a, 0x7f55, 0x7ff0, 0x9591, 0x9592, 0x9650, 0x97d3, 0x5272, + 0x8f44, 0x51fd, 0x542b, 0x54b8, 0x5563, 0x558a, 0x6abb, 0x6db5, + 0x7dd8, 0x8266, 0x929c, 0x9677, 0x9e79, 0x5408, 0x54c8, 0x76d2, + 0x86e4, 0x95a4, 0x95d4, 0x965c, 0x4ea2, 0x4f09, 0x59ee, 0x5ae6, + 0x5df7, 0x6052, 0x6297, 0x676d, 0x6841, 0x6c86, 0x6e2f, 0x7f38, + 0x809b, 0x822a, 0xfa08, 0xfa09, 0x9805, 0x52be, 0x6838, 0x5016, + 0x5e78, 0x674f, 0x8347, 0x884c, 0x4eab, 0x5411, 0x56ae, 0x73e6, + 0x9115, 0x97ff, 0x9909, 0x9957, 0x9999, 0x61b2, 0x6af6, 0x737b, + 0x8ed2, 0x6b47, 0x96aa, 0x9a57, 0x5955, 0x7200, 0x8d6b, 0x9769, + 0x4fd4, 0x5cf4, 0x5f26, 0x61f8, 0x665b, 0x6ceb, 0x70ab, 0x7384, + 0x73b9, 0x73fe, 0x7729, 0x774d, 0x7d43, 0x7d62, 0x7e23, 0x8237, + 0x8852, 0xfa0a, 0x8ce2, 0x9249, 0x986f, 0x5b51, 0x7a74, 0x8840, + 0x9801, 0x5acc, 0x4fe0, 0x5354, 0x593e, 0x5cfd, 0x633e, 0x6d79, + 0x72f9, 0x8105, 0x8107, 0x83a2, 0x92cf, 0x9830, 0x4ea8, 0x5144, + 0x5211, 0x578b, 0x5f62, 0x6cc2, 0x6ece, 0x7005, 0x7050, 0x70af, + 0x7192, 0x73e9, 0x7469, 0x834a, 0x87a2, 0x8861, 0x9008, 0x90a2, + 0x93a3, 0x99a8, 0x60d1, 0x6216, 0x9177, 0x5a5a, 0x660f, 0x6df7, + 0x6e3e, 0x743f, 0x9b42, 0x5ffd, 0x60da, 0x7b0f, 0x54c4, 0x5f18, + 0x6c5e, 0x6cd3, 0x6d2a, 0x70d8, 0x7d05, 0x8679, 0x8a0c, 0x9d3b, + 0xfa0b, 0x64f4, 0x652b, 0x78ba, 0x78bb, 0x7a6b, 0x4e38, 0x559a, + 0x5950, 0x5ba6, 0x5e7b, 0x60a3, 0x63db, 0x6b61, 0x6665, 0x6853, + 0x6e19, 0x7165, 0x74b0, 0x7d08, 0x9084, 0x9a69, 0x9c25, 0x6d3b, + 0x6ed1, 0x733e, 0x8c41, 0x95ca, 0x51f0, 0x5e4c, 0x5fa8, 0x604d, + 0x60f6, 0x6130, 0x614c, 0x6643, 0x6644, 0x69a5, 0x6cc1, 0x6e5f, + 0x6ec9, 0x6f62, 0x714c, 0x749c, 0x7687, 0x7bc1, 0x7c27, 0x8352, + 0x8757, 0x9051, 0x968d, 0x9ec3, 0x5283, 0x7372, 0x5b96, 0x6a6b, + 0x9404, 0x52db, 0x52f3, 0x5864, 0x58ce, 0x7104, 0x718f, 0x71fb, + 0x85b0, 0x8a13, 0x6688, 0x85a8, 0x55a7, 0x6684, 0x714a, 0x8431, + 0x6064, 0x8b4e, 0x9df8, 0x5147, 0x51f6, 0x5308, 0x6d36, 0x80f8, + 0x9ed1, 0x6615, 0x6b23, 0x7098, 0x75d5, 0x5403, 0x5c79, 0x7d07, + 0x8a16, 0x6b20, 0x6b3d, 0x6b46, 0x5438, 0x6070, 0x6d3d, 0x7fd5, + 0x8208, 0x8a70, 0xfa0b +]; + +const RO_HANJA = [ + 0x4e6b, 0x559d, 0x66f7, 0x6e34, 0x78a3, 0x7aed, 0x845b, 0x8910, + 0x874e, 0x97a8, 0x4e5e, 0x5091, 0x6770, 0x6840, 0x6289, 0x6c7a, + 0x6f54, 0x7d50, 0x7f3a, 0x8a23, 0x6c68, 0xf904, 0x9aa8, 0x522e, + 0x605d, 0x62ec, 0x9002, 0x5800, 0x5c48, 0x6398, 0x7a9f, 0x53a5, + 0x7357, 0x8568, 0x8e76, 0x95d5, 0x6a58, 0xf909, 0x4f76, 0x5409, + 0x62ee, 0x6854, 0x634f, 0x637a, 0x8a25, 0x64bb, 0x6fbe, 0x737a, + 0x75b8, 0x9054, 0x4e6d, 0x7a81, 0x524c, 0x8fa3, 0x51bd, 0x5217, + 0x52a3, 0x6d0c, 0x70c8, 0x88c2, 0x5f8b, 0x6144, 0x6817, 0xf961, + 0x551c, 0x62b9, 0x672b, 0x6cab, 0x8309, 0x896a, 0x977a, 0x6ec5, + 0x8511, 0x6b7f, 0x6c92, 0x52ff, 0x6c95, 0x7269, 0x5bc6, 0x871c, + 0x8b10, 0x52c3, 0x62d4, 0x64a5, 0x6e24, 0x6f51, 0x767c, 0x8dcb, + 0x91b1, 0x9262, 0x9aee, 0x9b43, 0x4f10, 0x7b4f, 0x7f70, 0x95a5, + 0x5225, 0x77a5, 0x9c49, 0x9f08, 0x4e76, 0xf967, 0x4f5b, 0x5f17, + 0x5f7f, 0x62c2, 0x4e77, 0x6492, 0x6bba, 0x715e, 0x85a9, 0x5368, + 0x5c51, 0x6954, 0x6cc4, 0x6d29, 0x6e2b, 0x820c, 0x859b, 0x893b, + 0x8a2d, 0x8aaa, 0x96ea, 0x9f67, 0x7387, 0x620c, 0x8853, 0x8ff0, + 0x9265, 0x745f, 0x819d, 0x8768, 0x5931, 0x5ba4, 0x5be6, 0x6089, + 0x65a1, 0x8b01, 0x8ecb, 0x95bc, 0x5b7c, 0x8616, 0xf99c, 0xf99d, + 0xf99e, 0x6085, 0x6d85, 0xf99f, 0x71b1, 0xf9a0, 0xf9a1, 0x95b1, + 0x5140, 0x66f0, 0x851a, 0x9b31, 0x4e90, 0x6708, 0x8d8a, 0x925e, + 0xf9d8, 0xf9d9, 0xf9da, 0xf9db, 0x807f, 0x4e59, 0x4e00, 0x4f5a, + 0x4f7e, 0x58f9, 0x65e5, 0x6ea2, 0x9038, 0x93b0, 0x99b9, 0x5207, + 0x622a, 0x6298, 0x6d59, 0x7664, 0x7aca, 0x7bc0, 0x7d76, 0x5352, + 0x62d9, 0x731d, 0x8301, 0x6adb, 0x4f84, 0x53f1, 0x59ea, 0x5ac9, + 0x5e19, 0x684e, 0x74c6, 0x75be, 0x79e9, 0x7a92, 0x81a3, 0x86ed, + 0x8cea, 0x8dcc, 0x8fed, 0x5239, 0x5bdf, 0x64e6, 0x672d, 0x7d2e, + 0x51f8, 0x54f2, 0x5586, 0x5fb9, 0x64a4, 0x6f88, 0x7db4, 0x8f1f, + 0x8f4d, 0x9435, 0x64ae, 0x51fa, 0x672e, 0x9edc, 0x4e03, 0x67d2, + 0x6f06, 0x596a, 0x812b, 0x516b, 0x53ed, 0x634c, 0x5339, 0x5f3c, + 0x5fc5, 0x6ccc, 0x73cc, 0x7562, 0x758b, 0x7b46, 0x82fe, 0x999d, + 0x5272, 0x8f44, 0x6b47, 0x5b51, 0x7a74, 0x8840, 0x9801, 0x5ffd, + 0x60da, 0x7b0f, 0x6d3b, 0x6ed1, 0x733e, 0x8c41, 0x95ca, 0x6064, + 0x8b4e, 0x9df8, 0x5403, 0x5c79, 0x7d07, 0x8a16, 0x8a70, +]; + +const regNormalFixed = new RegExp(`/(?:${PRE_REG_NORMAL_FIXED.join('|')})$/iu`); +const regSpecialChar = new RegExp(`/(?:${PRE_REG_SPECIAL_CHAR.join('|')})$/iu`); +const regSpecialRo = new RegExp(`/(?:${PRE_REG_SPECIAL_RO.join('|')})$/iu`); + +const jongsungHanja = new Set(); +for (const hanjaCode of JONGSUNG_HANJA) { + jongsungHanja.add(hanjaCode); +} +const roHanja = new Set(); +for (const hanjaCode of RO_HANJA) { + roHanja.add(hanjaCode); +} +const mapPostPosition = new Map(); +for (const [key, val] of Object.entries(DEFAULT_POSTPOSITION)) { + mapPostPosition.set(`(${key})${val}`, key); + mapPostPosition.set(key, key); + mapPostPosition.set(val, key); +} + +function checkText(text: string, isRo: boolean): boolean { + if (regNormalFixed.test(text)) { + return false; + } + + if (regSpecialChar.test(text)) { + return true; + } + + if (!isRo && regSpecialRo.test(text)) { + return true; + } + + return false; +} + +function checkCode(code: number, isRo: boolean): boolean { + const jongsung = (code - KO_START_CODE) % 28; + if (jongsung === 0) { + return false; + } + + if (isRo && jongsung === 8) { + return false; + } + + return true; +} + +export function check(text: string, type: string): boolean { + + const htarget = text.replace(REG_INVALID_CHAR_W_HANJA, ' ').replace(REG_TARGET_CHAR, '$1'); + if (!htarget) { + return false; + } + const hcode = htarget.charAt(htarget.length - 1).charCodeAt(0); + const isRo = type === '로' || type === '으로'; + + if (HANJA_START_CODE <= hcode && hcode <= HANJA_FINISH_CODE) { + if (isRo && roHanja.has(hcode)) { + return false; + } + if (jongsungHanja.has(hcode)) { + return true; + } + if (hcode < KO_START_CODE || KO_FINISH_CODE < hcode) { + return false; + } + } + + const target = text.replace(REG_INVALID_CHAR, ' ').replace(REG_TARGET_CHAR, '$1'); + const code = target.charAt(target.length - 1).charCodeAt(0); + + const isKorean = KO_START_CODE <= code && code <= KO_FINISH_CODE; + + return isKorean ? checkCode(code, isRo) : checkText(target, isRo); +} + +export function pick(text: string|number, wJongsung: string, woJongsung = ''): string { + /* NOTE:원본 코드와 인자 순서가 다름. + * 원본은 pick('바람', '랑', '이랑'); 이었다면 pick('바람', '이랑', '랑'); 으로 바뀜. + * pick('바람', '은', '는'); JosaUtil.pick('바람', '이', '가');처럼 쓰기 위해서임. + */ + if (!text) { + text = ''; + } else if(typeof text === 'number'){ + text = String(text); + } + + if(!woJongsung){ + if(!mapPostPosition.has(wJongsung)){ + throw `올바르지 않은 조사 지정: ${wJongsung}`; + } + wJongsung = unwrap_err(mapPostPosition.get(wJongsung), Error, `올바르지 않은 조사 지정: ${wJongsung}`); + woJongsung = DEFAULT_POSTPOSITION[wJongsung as keyof typeof DEFAULT_POSTPOSITION]; + } + + return check(text, wJongsung) ? wJongsung : woJongsung; +} + +export function put(text: string, wJongsung: string, woJongsung = ''): string{ + return `${text}${pick(text, wJongsung, woJongsung)}`; +} + +export function fix(wJongsung: string, woJongsung = ''){ + if(!woJongsung){ + if(!mapPostPosition.has(wJongsung)){ + throw `올바르지 않은 조사 지정: ${wJongsung}`; + } + wJongsung = unwrap_err(mapPostPosition.get(wJongsung), Error, `올바르지 않은 조사 지정: ${wJongsung}`); + woJongsung = DEFAULT_POSTPOSITION[wJongsung as keyof typeof DEFAULT_POSTPOSITION]; + } + return function(text: string){ + return put(text, wJongsung, woJongsung); + } +} + +export function batch(text: string, decorator = ';'){ + const regExp = new RegExp(`${decorator}([^${decorator}]+)${decorator}([^${decorator}]*)${decorator}([^${decorator}]+)${decorator}`, 'g'); + + let matchRes; + let lastIndex = 0; + + const result: string[] = []; + + while ((matchRes = regExp.exec(text)) !== null) { + const { + 0: partAll, + 1: body, + 2: filler, + 3: wJongsung, + index, + } = matchRes; + if(lastIndex != index){ + result.push(text.slice(lastIndex, index)); + } + + result.push(body, filler, pick(body, wJongsung)); + + lastIndex = index + partAll.length; + } + if(lastIndex != text.length){ + result.push(text.slice(lastIndex)); + } + + return result.join(''); +} \ No newline at end of file diff --git a/server/util/LiteHashDRBG.ts b/server/util/LiteHashDRBG.ts new file mode 100644 index 0000000..69cabd7 --- /dev/null +++ b/server/util/LiteHashDRBG.ts @@ -0,0 +1,232 @@ +import type { RNG } from "./RNG"; + +import { sha512 } from 'js-sha512'; + +import { convertBytesLikeToUint8Array } from "./convertBytesLikeToUint8Array"; +import type { BytesLike } from "./BytesLike"; + +const maxRngSupportBit = 53; +const maxInt = 0x1f_ffff_ffff_ffff; // NOTE: b 0, 10000110011, 11...11 +const maxIntMore1 = 0x20_0000_0000_0000n; //NOTE: b 0, 10000110100, 00...00 +const maxIntMore1f = Number(maxIntMore1); +export const bufferByteSize = 512 / 8; //SHA512 + +const intBitMapMask = new Map([ + [0x1n, 1], + [0x3n, 2], + [0x7n, 3], + [0xfn, 4], + [0x1fn, 5], + [0x3fn, 6], + [0x7fn, 7], + [0xffn, 8], + [0x1ffn, 9], + [0x3ffn, 10], + [0x7ffn, 11], + [0xfffn, 12], + [0x1fffn, 13], + [0x3fffn, 14], + [0x7fffn, 15], + [0xffffn, 16], + [0x1ffffn, 17], + [0x3ffffn, 18], + [0x7ffffn, 19], + [0xfffffn, 20], + [0x1fffffn, 21], + [0x3fffffn, 22], + [0x7fffffn, 23], + [0xffffffn, 24], + [0x1ffffffn, 25], + [0x3ffffffn, 26], + [0x7ffffffn, 27], + [0xfffffffn, 28], + [0x1fffffffn, 29], + [0x3fffffffn, 30], + [0x7fffffffn, 31], + [0xffffffffn, 32], + [0x1ffffffffn, 33], + [0x3ffffffffn, 34], + [0x7ffffffffn, 35], + [0xfffffffffn, 36], + [0x1fffffffffn, 37], + [0x3fffffffffn, 38], + [0x7fffffffffn, 39], + [0xffffffffffn, 40], + [0x1ffffffffffn, 41], + [0x3ffffffffffn, 42], + [0x7ffffffffffn, 43], + [0xfffffffffffn, 44], + [0x1fffffffffffn, 45], + [0x3fffffffffffn, 46], + [0x7fffffffffffn, 47], + [0xffffffffffffn, 48], + [0x1ffffffffffffn, 49], + [0x3ffffffffffffn, 50], + [0x7ffffffffffffn, 51], + [0xfffffffffffffn, 52], + [0x1fffffffffffffn, 53], +]); + +function calcBitMask(n: bigint): bigint { + n |= n >> 1n; + n |= n >> 2n; + n |= n >> 4n; + n |= n >> 8n; + n |= n >> 16n; + n |= n >> 32n; + + return n; +} +export class LiteHashDRBG implements RNG { + + protected buffer!: ArrayBuffer; + protected bufferIdx!: number; + protected hq: DataView; + protected hqIdxPos: number; + + public constructor(protected seed: BytesLike, protected stateIdx = 0, bufferIdx = 0) { + if(bufferIdx < 0){ + throw new Error(`bufferIdx ${bufferIdx} < 0`); + } + if(bufferIdx >= bufferByteSize){ + throw new Error(`bufferidx ${bufferIdx} >= ${bufferByteSize}`); + } + if(stateIdx < 0){ + throw new Error(`stateIdx ${stateIdx} < 0`); + } + + const seedU8 = convertBytesLikeToUint8Array(seed); + const hqBuffer = new ArrayBuffer(seedU8.byteLength + 4); + const hqU8 = new Uint8Array(hqBuffer); + + hqU8.set(seedU8, 0); + this.hq = new DataView(hqBuffer); + this.hqIdxPos = seedU8.byteLength; + + this.genNextBlock(); + this.bufferIdx = bufferIdx; + } + + protected genNextBlock(): void { + this.hq.setUint32(this.hqIdxPos, this.stateIdx, true); + const digest = sha512.arrayBuffer(this.hq.buffer); + this.buffer = digest; + this.bufferIdx = 0; + this.stateIdx += 1; + } + + public getMaxInt(): number { + return maxInt; + } + + public nextBytes(bytes: number, baseBytes?: number): Uint8Array { + bytes |= 0; + if (bytes <= 0) { + throw new Error(`${bytes} <= 0`); + } + + if (this.bufferIdx + bytes <= bufferByteSize) { + if(baseBytes === undefined || bytes >= baseBytes){ + const result = this.buffer.slice(this.bufferIdx, this.bufferIdx + bytes); + this.bufferIdx += bytes; + if (this.bufferIdx === bufferByteSize) { + this.genNextBlock(); + } + return new Uint8Array(result); + } + + const resultBuffer = new ArrayBuffer(Math.max(bytes, baseBytes)); + const result = new Uint8Array(resultBuffer); + result.set(new Uint8Array(this.buffer, this.bufferIdx, bytes)); + this.bufferIdx += bytes; + if( this.bufferIdx === bufferByteSize){ + this.genNextBlock(); + } + return result; + } + + const resultBuffer = new ArrayBuffer(baseBytes ? Math.max(bytes, baseBytes) : bytes); + const result = new Uint8Array(resultBuffer); + + result.set(new Uint8Array(this.buffer, this.bufferIdx)); + let offset = bufferByteSize - this.bufferIdx; + let remain = bytes - offset; + + while (remain > bufferByteSize) { + this.genNextBlock(); + result.set(new Uint8Array(this.buffer), offset); + offset += bufferByteSize; + remain -= bufferByteSize; + } + + this.genNextBlock(); + if (remain === 0) { + return result; + } + + result.set(new Uint8Array(this.buffer, 0, remain), offset); + this.bufferIdx = remain; + return result; + } + + public nextBits(bits: number, baseBytes?: number): Uint8Array { + bits |= 0; + const bytes = (bits + 7) >> 3; + const headBits = bits & 0x7; + + const result = this.nextBytes(bytes, baseBytes); + if (headBits === 0) { + return result; + } + + result[bytes - 1] &= 0xff >> (8 - headBits); + return result; + } + + protected _nextInt(bits: number): bigint{ + const buffer = this.nextBits(bits, 8); + const dataView = new DataView(buffer.buffer); + return dataView.getBigUint64(0, true); + } + + public nextInt(max?: number): number { + if (max === undefined || max === maxInt) { + return Number(this._nextInt(maxRngSupportBit)); + } + if (max > maxInt) { + throw new Error('Over max int'); + } + if (max === 0) { + return 0; + } + if (max < 0) { + return -this.nextInt(-max); + } + + const mask = calcBitMask(BigInt(max)); + const bits = intBitMapMask.get(mask) as number; + + let n = Number(this._nextInt(bits)); + while (n > max){ + n = Number(this._nextInt(bits)); + } + return n; + } + + public nextFloat1(): number { + // eslint-disable-next-line no-constant-condition + while(true){ + const nInt = this._nextInt(maxRngSupportBit + 1); + if(nInt < maxIntMore1){ + return Number(nInt) / maxIntMore1f; + } + if(nInt === maxIntMore1){ + return 1; + } + } + } + + public static build(seed: BytesLike, stateIdx = 0): LiteHashDRBG{ + return new LiteHashDRBG(seed, stateIdx); + } +} \ No newline at end of file diff --git a/server/util/NotNullExpected.ts b/server/util/NotNullExpected.ts new file mode 100644 index 0000000..1ea17d1 --- /dev/null +++ b/server/util/NotNullExpected.ts @@ -0,0 +1,4 @@ + +export class NotNullExpected extends TypeError { + public name = 'NotNullExpected'; +} diff --git a/server/util/Nullable.ts b/server/util/Nullable.ts new file mode 100644 index 0000000..164a705 --- /dev/null +++ b/server/util/Nullable.ts @@ -0,0 +1 @@ +export type Nullable = T | null | undefined; diff --git a/server/util/QueryActionHelper.ts b/server/util/QueryActionHelper.ts new file mode 100644 index 0000000..c6afd62 --- /dev/null +++ b/server/util/QueryActionHelper.ts @@ -0,0 +1,173 @@ +import type { TurnObj } from "@/defs"; +import { clone, isString, range } from "lodash-es"; +import { ref, type Ref } from "vue"; +import { unwrap } from "./unwrap"; + +export type TurnObjWithTime = TurnObj & { + time: string; + year?: number; + month?: number; + tooltip?: string; + style?: Record; +}; + +export const getEmptyTurn = (maxTurn: number): TurnObjWithTime[] => Array.from({ + length: maxTurn, +}).fill({ + arg: {}, + brief: "", + action: "", + year: undefined, + month: undefined, + time: "", +}); + +export class QueryActionHelper { + public readonly reservedCommandList: Ref; + public readonly selectedTurnList: Ref>; + public readonly prevSelectedTurnList: Ref>; + + constructor( + protected maxTurn: number, + ) { + this.reservedCommandList = ref(getEmptyTurn(maxTurn)); + this.selectedTurnList = ref(new Set()); + this.prevSelectedTurnList = ref(new Set([0])); + } + + toggleTurn(...reqTurnList: number[] | string[]) { + for (let turnIdx of reqTurnList) { + if (isString(turnIdx)) { + turnIdx = parseInt(turnIdx); + } + if (this.selectedTurnList.value.has(turnIdx)) { + this.selectedTurnList.value.delete(turnIdx); + } else { + this.selectedTurnList.value.add(turnIdx); + } + } + } + + selectTurn(...reqTurnList: number[] | string[]) { + this.selectedTurnList.value.clear(); + for (const turnIdx of reqTurnList) { + if (isString(turnIdx)) { + this.selectedTurnList.value.add(parseInt(turnIdx)); + } else { + this.selectedTurnList.value.add(turnIdx); + } + } + } + + selectStep(begin: number, step: number) { + this.selectedTurnList.value.clear(); + for (const idx of range(0, this.maxTurn)) { + if ((idx - begin) % step == 0) { + this.selectedTurnList.value.add(idx); + } + } + } + + selectAll() { + for (let i = 0; i < this.maxTurn; i++) { + this.selectedTurnList.value.add(i); + } + } + + getSelectedTurnList(useSort = true): number[] { + let result: number[]; + if (this.selectedTurnList.value.size) { + result = Array.from(this.selectedTurnList.value); + } + else if (this.prevSelectedTurnList.value.size) { + result = Array.from(this.prevSelectedTurnList.value); + } + else { + return [0]; + } + + if (useSort) { + return result.sort((a, b) => a - b); + } + return result; + } + + releaseSelectedTurnList() { + if (this.selectedTurnList.value.size > 0) { + this.prevSelectedTurnList.value.clear(); + for (const v of this.selectedTurnList.value) { + this.prevSelectedTurnList.value.add(v); + } + this.selectedTurnList.value.clear(); + } + } + + extractQueryActions(): [number[], TurnObj][] { + const reqTurnList = this.getSelectedTurnList(); + const selectedMinTurnIdx = unwrap(Math.min(...reqTurnList)); + + const buffer = new Map(); + for (const rawTurnIdx of reqTurnList) { + const turnIdx = rawTurnIdx - selectedMinTurnIdx; + const rawAction = this.reservedCommandList.value[rawTurnIdx] + const actionStr = JSON.stringify([rawAction.action, rawAction.arg]); + if (buffer.has(actionStr)) { + const items = unwrap(buffer.get(actionStr)); + items[0].push(turnIdx); + } + else { + buffer.set(actionStr, [[turnIdx], { + action: rawAction.action, + arg: clone(rawAction.arg), + brief: rawAction.brief + }]) + } + } + return Array.from(buffer.values()); + } + + amplifyQueryActions(rawActions: [number[], TurnObj][], reqTurnList: number[]): [number[], TurnObj][] { + if (reqTurnList.length < 1) { + return []; + } + + let minQueryIdx = this.maxTurn; + let maxQueryIdx = 0; + for (const [turnList] of rawActions) { + for (const turnIdx of turnList) { + minQueryIdx = Math.min(minQueryIdx, turnIdx); + maxQueryIdx = Math.max(maxQueryIdx, turnIdx); + } + } + const queryLength = maxQueryIdx - minQueryIdx + 1; + + const queryTurnList: number[] = [reqTurnList[0]]; + for (const reqTurnIdx of reqTurnList) { + const last = queryTurnList[queryTurnList.length - 1]; + if (reqTurnIdx < last + queryLength) { + continue; + } + queryTurnList.push(reqTurnIdx); + } + + const actions: [number[], TurnObj][] = []; + for (const [baseTurnList, action] of rawActions) { + const subTurnList: number[] = []; + for (const baseTurnIdx of baseTurnList) { + for (const queryTurnIdx of queryTurnList) { + const targetTurn = baseTurnIdx + queryTurnIdx; + if (targetTurn >= this.maxTurn) { + continue; + } + subTurnList.push(baseTurnIdx + queryTurnIdx); + } + } + if (subTurnList.length == 0) { + continue; + } + actions.push([subTurnList, action]); + } + + return actions; + } +} diff --git a/server/util/RNG.ts b/server/util/RNG.ts new file mode 100644 index 0000000..1e1595e --- /dev/null +++ b/server/util/RNG.ts @@ -0,0 +1,13 @@ +export interface RNG { + + /** + * nextInt()가 반환 가능한 최댓값 + */ + getMaxInt(): number; + + nextBytes(bytes: number): Uint8Array; + nextBits(bits: number): Uint8Array; + + nextInt(max?: number): number; + nextFloat1(): number; +} \ No newline at end of file diff --git a/server/util/RandUtil.ts b/server/util/RandUtil.ts new file mode 100644 index 0000000..d627d4a --- /dev/null +++ b/server/util/RandUtil.ts @@ -0,0 +1,144 @@ +import type { RNG } from './RNG'; + +export class RandUtil { + constructor(protected rng: RNG) { + + } + + public nextFloat1(): number { + return this.rng.nextFloat1(); + } + + public nextRange(min: number, max: number): number { + const range = max - min; + return this.nextFloat1() * (range) + min; + } + + public nextRangeInt(min: number, max: number): number { + const range = max - min; + return this.rng.nextInt(range) + min; + } + + public nextInt(max?: number): number { + return this.rng.nextInt(max); + } + + public nextBit(): boolean { + const view = new DataView(this.rng.nextBits(1) as ArrayBufferLike); + return view.getUint8(0) != 0; + } + + public nextBool(prob = 0.5): boolean { + if (prob >= 1) { + return true; + } + if (prob === 0.5){ + return this.nextBit(); + } + if (prob <= 0){ + return false; + } + return this.nextFloat1() < prob; + } + + public shuffle(srcArray: T[]): T[] { + const cnt = srcArray.length; + if(cnt === 0){ + return []; + } + if (cnt > this.rng.getMaxInt()) { + throw 'Invalid random int range'; + } + + const result: T[] = Array.from(srcArray); + for (let srcIdx = 0; srcIdx < cnt; srcIdx += 1) { + const destIdx = this.rng.nextInt(cnt - srcIdx - 1) + srcIdx; + if(srcIdx === destIdx){ + continue; + } + [result[srcIdx], result[destIdx]] = [result[destIdx], result[srcIdx]]; + } + + return result; + } + + //Object는 integer key에 예외가 있어 shuffleAssoc은 없음 + + public choice(items: T[] | Record | Set): T { + if (items instanceof Array) { + if(items.length === 0){ + throw new Error('Empty items'); + } + const idx = this.rng.nextInt(items.length - 1); + return items[idx]; + } + + if (items instanceof Set) { + return this.choice(Array.from(items.values())); + } + + return items[this.choice(Array.from(Object.keys(items)))]; + } + + public choiceUsingWeight(items: Record): string | number { + if(Object.keys(items).length === 0){ + throw new Error('Empty items'); + } + let sum = 0; + for (const value of Object.values(items)) { + if (value <= 0) { + continue; + } + sum += value; + } + + let rd = this.nextFloat1() * sum; + + for (const [item, value] of Object.entries(items)) { + if (value <= 0) { + if (rd <= 0) { + return item; + } + continue; + } + + if (rd <= value) { + return item; + } + rd -= value; + } + + throw new Error('Unreacheable'); + } + + public choiceUsingWeightPair(items: [T, number][]): T { + if(items.length === 0){ + throw new Error('Empty items'); + } + let sum = 0; + for (const [, value] of items) { + if (value <= 0) { + continue; + } + sum += value; + } + + let rd = this.nextFloat1() * sum; + + for (const [item, value] of items) { + if (value <= 0) { + if (rd <= 0) { + return item; + } + continue; + } + + if (rd <= value) { + return item; + } + rd -= value; + } + + throw new Error('Unreacheable'); + } +} \ No newline at end of file diff --git a/server/util/RuntimeError.ts b/server/util/RuntimeError.ts new file mode 100644 index 0000000..5992028 --- /dev/null +++ b/server/util/RuntimeError.ts @@ -0,0 +1,14 @@ +export class RuntimeError extends Error { + public name = 'RuntimeError'; + constructor(public message: string = '') { + super(message); + } + toString(): string { + if (this.message) { + return this.name + ': ' + this.message; + } + else { + return this.name; + } + } +} diff --git a/server/util/StoredActionsHelper.ts b/server/util/StoredActionsHelper.ts new file mode 100644 index 0000000..834bc79 --- /dev/null +++ b/server/util/StoredActionsHelper.ts @@ -0,0 +1,115 @@ +import type { TurnObj } from '@/defs'; +import { ref, watch, type Ref } from 'vue'; + + +export class StoredActionsHelper { + public readonly recentActions: Ref>; + public readonly storedActions = ref(new Map()); + public readonly clipboard = ref<[number[], TurnObj][] | undefined>(undefined); + public readonly activatedCategory = ref(""); + public readonly isEditMode = ref(false); + + public readonly recentActionsKey: string; + public readonly storedActionsKey: string; + public readonly clipboardKey: string; + public readonly activatedCategoryKey: string; + public readonly editModeKey: string; + + constructor(protected serverNick: string, protected type: 'general' | 'nation', protected mapName: string, protected unitSet: string, protected maxRecent = 10) { + this.recentActions = ref(new Map()); + const typeKey = `${serverNick}_${mapName}_${unitSet}_${type}`; + this.recentActionsKey = `${typeKey}RecentActions`; + this.storedActionsKey = `${typeKey}StoredActions`; + this.clipboardKey = `${typeKey}Clipboard`; + this.activatedCategoryKey = `${typeKey}ActivatedCategory`; + this.editModeKey = `${serverNick}_${type}_isEditMode`; + + this.loadRecentActions(); + this.loadStoredActions(); + + const rawClipboard = localStorage.getItem(this.clipboardKey); + if (rawClipboard !== null) { + this.clipboard.value = JSON.parse(rawClipboard); + } + watch(this.clipboard, (newValue) => { + localStorage.setItem(this.clipboardKey, JSON.stringify(newValue)); + }); + + const rawActivatedCategory = localStorage.getItem(this.activatedCategoryKey); + if (rawActivatedCategory !== null) { + this.activatedCategory.value = JSON.parse(rawActivatedCategory); + } + watch(this.activatedCategory, (newValue) => { + localStorage.setItem(this.activatedCategoryKey, JSON.stringify(newValue)); + }); + + this.isEditMode.value = localStorage.getItem(this.editModeKey) === '1'; + watch(this.isEditMode, (newValue) => { + localStorage.setItem(this.editModeKey, newValue ? '1' : '0') + }) + } + + loadRecentActions() { + try { + const rawRecentActions = JSON.parse(localStorage.getItem(this.recentActionsKey) ?? '[]') as TurnObj[]; + const recentActions = new Map(); + for (const action of rawRecentActions) { + const actionKey = JSON.stringify([action.action, action.arg]); + recentActions.set(actionKey, action); + } + this.recentActions.value = recentActions; + } + catch(e){ + console.log(`loadRecentActions error ${e}`); + } + } + + pushRecentActions(action: TurnObj) { + const actionKey = JSON.stringify([action.action, action.arg]); + if (this.recentActions.value.has(actionKey)) { + this.recentActions.value.delete(actionKey); + } + else if (this.recentActions.value.size > this.maxRecent) { + const firstKey = this.recentActions.value.keys().next().value; + this.recentActions.value.delete(firstKey); + } + this.recentActions.value.set(actionKey, action); + this.saveRecentActions(); + } + + saveRecentActions() { + localStorage.setItem(this.recentActionsKey, JSON.stringify(Array.from(this.recentActions.value.values()))); + } + + loadStoredActions() { + try { + const rawValue: [string, [number[], TurnObj][]][] = JSON.parse( + localStorage.getItem(this.storedActionsKey) ?? '[]' + ); + this.storedActions.value = new Map(rawValue); + } + catch(e){ + console.log(`loadStoredActions error ${e}`); + } + } + + setStoredActions(actionKey: string, actions: [number[], TurnObj][]) { + this.storedActions.value.set(actionKey, actions); + console.log(this.storedActions.value); + this.saveStoredActions(); + } + + deleteStoredActions(actionKey: string) { + if (this.storedActions.value.delete(actionKey)) { + this.saveStoredActions(); + } + } + + saveStoredActions() { + localStorage.setItem( + this.storedActionsKey, + JSON.stringify(Array.from(this.storedActions.value.entries())) + ); + } + +} \ No newline at end of file diff --git a/server/util/TemplateEngine.ts b/server/util/TemplateEngine.ts new file mode 100644 index 0000000..db5c44a --- /dev/null +++ b/server/util/TemplateEngine.ts @@ -0,0 +1,41 @@ +import { escapeHtml } from '@/legacy/escapeHtml'; +import linkifyStr from 'linkify-string'; +/** + * 단순한 Template 함수. <%변수명%>으로 template 가능 + * @see https://github.com/krasimir/absurd/blob/master/lib/processors/html/helpers/TemplateEngine.js + * @param {string} html + * @param {object} options + * @returns {string} + */ + +export function TemplateEngine(html: string, options: Record = {}): string { + const re = /<%(.+?)%>/g; + const reExp = /(^( )?(var|if|for|else|switch|case|break|{|}|;))(.*)?/g; + const code = ['with(obj) { var r=[];\n']; + let cursor = 0; + const add = function (line: string, js?: boolean) { + js ? code.push(line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n') : + code.push(line != '' ? 'r.push("' + line.replace(/"/g, '\\"') + '");\n' : ''); + return add; + }; + options.e = escapeHtml; + options.linkifyStr = linkifyStr; + for (; ;) { + const match = re.exec(html); + if (!match) { + break; + } + add(html.slice(cursor, match.index))(match[1], true); + cursor = match.index + match[0].length; + } + add(html.substr(cursor, html.length - cursor)); + + code.push('return r.join(""); }'); + const compiledCode = code.join('').replace(/[\r\t\n]/g, ' '); + try { + return new Function('obj', compiledCode).apply(options, [options]); + } catch (err: unknown) { + console.error(err, " in \n\nCode:\n", code, "\n"); + throw err; + } +} diff --git a/server/util/auto500px.ts b/server/util/auto500px.ts new file mode 100644 index 0000000..52d3db6 --- /dev/null +++ b/server/util/auto500px.ts @@ -0,0 +1,92 @@ +import { htmlReady } from "@util/htmlReady"; +import { unwrap } from "@util/unwrap"; +import { keyScreenMode, type ScreenModeType } from "@/defs"; + +export function auto500px(targetHeight = 700): void { + let deviceWidth = -1; + let viewportMeta!: HTMLMetaElement; + let oldMode: ScreenModeType = 'auto'; + + + function init() { + let _viewPortMeta = document.querySelector("meta[name=viewport]"); + if (_viewPortMeta) { + viewportMeta = _viewPortMeta; + return; + } + + const htmlTag = unwrap(document.querySelector("head")); + _viewPortMeta = document.createElement("meta"); + _viewPortMeta.name = 'viewport'; + _viewPortMeta.content = 'width=500'; + htmlTag.appendChild(_viewPortMeta); + viewportMeta = _viewPortMeta; + } + + function adjustViewportWidth() { + const screenMode = (localStorage.getItem(keyScreenMode) as ScreenModeType)??'auto'; + if(screenMode != oldMode){ + oldMode = screenMode; + deviceWidth = window.screen.availWidth; + + if(screenMode == '500px'){ + viewportMeta.content = 'width=500'; + return; + } + + if(screenMode == '1000px'){ + viewportMeta.content = 'width=1000'; + return; + } + + if(screenMode == 'auto'){ + deviceWidth = -1; + } + } + + if (deviceWidth == window.screen.availWidth) { + return; + } + + if(oldMode != 'auto'){ + oldMode = 'auto'; + adjustViewportWidth(); + return; + } + deviceWidth = window.screen.availWidth; + const innerHeight = window.innerHeight; + const selectorHeight = targetHeight; + + if (deviceWidth < 500) { + viewportMeta.content = 'width=500'; + return; + } + + if (innerHeight < selectorHeight) { + const maybeNextWidth = deviceWidth / innerHeight * selectorHeight; + if (maybeNextWidth >= 700) { + viewportMeta.content = 'width=1000'; + } + else { + viewportMeta.content = `height=${Math.ceil(selectorHeight)}`; + } + return; + } + else if(deviceWidth >= 700){ + viewportMeta.content = 'width=1000'; + } + else{ + viewportMeta.content = 'width=device-width, initial-scale=1'; + } + } + + htmlReady(() => { + init(); + adjustViewportWidth(); + window.addEventListener('scroll', adjustViewportWidth, true); + window.addEventListener('orientationchange', adjustViewportWidth, true); + document.addEventListener('tryChangeScreenMode', adjustViewportWidth, false); + + + }); +} \ No newline at end of file diff --git a/server/util/autoResizeTextarea.ts b/server/util/autoResizeTextarea.ts new file mode 100644 index 0000000..63eb09f --- /dev/null +++ b/server/util/autoResizeTextarea.ts @@ -0,0 +1,7 @@ +import { unwrap } from "@util/unwrap"; + +export function autoResizeTextarea(e: Event): void { + const el = unwrap(e.target) as HTMLInputElement; + el.style.height = 'auto'; + el.style.height = `${el.scrollHeight + 1}px`; +} \ No newline at end of file diff --git a/server/util/automata초성.ts b/server/util/automata초성.ts new file mode 100644 index 0000000..b6eb499 --- /dev/null +++ b/server/util/automata초성.ts @@ -0,0 +1,101 @@ +const convListLevel1: Record> = { + 'ㄱ': { + 'ㅅ': 'ㄳ', + }, + 'ㄴ': { + 'ㅈ': 'ㄵ', + 'ㅎ': 'ㄶ', + }, + 'ㄹ': { + 'ㅂ': 'ㄼ', + 'ㄱ': 'ㄺ', + 'ㅅ': 'ㄽ', + 'ㅁ': 'ㄻ', + 'ㅎ': 'ㅀ', + 'ㅌ': 'ㄾ', + 'ㅍ': 'ㄿ', + }, + 'ㅂ': { + 'ㅅ': 'ㅄ', + }, +} + +const convListLevel2: Record> = { + 'ㄱ': { + 'ㄱ': 'ㄲ', + 'ㅅ': 'ㄳ', + }, + 'ㄴ': { + 'ㅈ': 'ㄵ', + 'ㅎ': 'ㄶ', + }, + 'ㄷ': { + 'ㄷ': 'ㄸ', + }, + 'ㄹ': { + 'ㅂ': 'ㄼ', + 'ㄱ': 'ㄺ', + 'ㅅ': 'ㄽ', + 'ㅁ': 'ㄻ', + 'ㅎ': 'ㅀ', + 'ㅌ': 'ㄾ', + 'ㅍ': 'ㄿ', + }, + 'ㅂ': { + 'ㅂ': 'ㅃ', + 'ㅅ': 'ㅄ', + }, + 'ㅅ': { + 'ㅅ': 'ㅆ', + }, + 'ㅈ': { + 'ㅈ': 'ㅉ', + } +} + +function automata초성(text: string, convList: Record>): string{ + const result: string[] = []; + let head: undefined | string = undefined; + for (const ch of text) { + if (head === undefined) { + if(!(ch in convList)){ + result.push(ch); + continue; + } + head = ch; + continue; + } + + const nextConv = convList[head]; + if(ch in nextConv){ + result.push(nextConv[ch]); + head = undefined; + continue; + } + + result.push(head); + if(!(ch in convList)){ + result.push(ch); + head = undefined; + continue; + } + head = ch; + } + if(head !== undefined){ + result.push(head); + head = undefined; + } + return result.join(''); +} + +export function automata초성All(text: string): [string, string]{ + return [automata초성(text, convListLevel1), automata초성(text, convListLevel2)]; +} + +export function automata초성Level1(text: string): string{ + return automata초성(text, convListLevel1); +} + +export function automata초성Level2(text: string): string { + return automata초성(text, convListLevel2); +} \ No newline at end of file diff --git a/server/util/callSammoAPI.ts b/server/util/callSammoAPI.ts new file mode 100644 index 0000000..fa65dad --- /dev/null +++ b/server/util/callSammoAPI.ts @@ -0,0 +1,274 @@ +import ky from "ky"; +import { isArray, isEmpty } from "lodash-es"; + +export type ValidResponse = { + result: true; +}; + +export type APIRecoveryType = "login" | "2fa" | "gateway" | "game_login" | "game_quota"; +export const APIRecoveryConst = { + Login: 'login', + TwoFactorAuth: '2fa', + Gateway: 'gateway', + GameLogin: 'game_login', + GameQuota: 'game_quota', +} as const; + +export type InvalidResponse = { + result: false; + reason: string; + recovery?: APIRecoveryType; + recovery_arg?: string | number; +}; + +export type RawArgType = Record | Record[] | undefined; + +interface BasicAPICallT< + ArgType extends RawArgType, + ResultType extends ValidResponse, + ErrorType extends InvalidResponse +> { + (args: ArgType): Promise; + (args: ArgType, returnError: false): Promise; + (args: ArgType, returnError: true): Promise; +} + +interface EmptyAPICallT { + (): Promise; + (args: undefined): Promise; + (args: undefined, returnError: false): Promise; + (args: undefined, returnError: true): Promise; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type ArgTypeOf = T extends APICallT ? A : never; + +export type APICallT< + ArgType extends RawArgType, + ResultType extends ValidResponse = ValidResponse, + ErrorType extends InvalidResponse = InvalidResponse +> = ArgType extends undefined ? EmptyAPICallT : BasicAPICallT; + +type HttpMethod = "get" | "post" | "put" | "patch" | "head" | "delete"; +export type APITail = typeof GET | typeof POST | typeof PUT | typeof PATCH | typeof HEAD | typeof DELETE; + +const httpMethodMap = new Map([ + [GET, "get"], + [POST, "post"], + [PUT, "put"], + [PATCH, "patch"], + [HEAD, "head"], + [DELETE, "delete"], +]); + +export function extractHttpMethod(tail: APITail): HttpMethod { + return httpMethodMap.get(tail) ?? "post"; +} + +const apiTarget = "api.php"; +let apiPath = apiTarget; + +export function setSammoAPIPrefix(prefix: string) { + apiPath = `${prefix}/${apiTarget}`; +} + +export async function callSammoAPI( + method: HttpMethod, + path: string | string[], + args: RawArgType, + paramArgs: Record | undefined +): Promise; +export async function callSammoAPI( + method: HttpMethod, + path: string | string[], + args: RawArgType, + paramArgs: Record | undefined, + returnError: false +): Promise; +export async function callSammoAPI( + method: HttpMethod, + path: string | string[], + args: RawArgType, + paramArgs: Record | undefined, + returnError: true +): Promise; +export async function callSammoAPI( + method: HttpMethod, + path: string | string[], + args: RawArgType, + paramArgs: Record | undefined, + returnError = false +): Promise { + if (isArray(path)) { + path = path.join("/"); + } + + if (args && isEmpty(args)) { + args = undefined; + } + + const result = (await (() => { + if (method == "get") { + return ky(apiPath, { + searchParams: { + ...paramArgs, + ...(args as typeof paramArgs), + path, + }, + method, + headers: { + "content-type": "application/json", + }, + timeout: 30000, + retry: 0, + }); + } + return ky("api.php", { + searchParams: { + ...paramArgs, + path, + }, + method, + json: args, + headers: { + "content-type": "application/json", + }, + timeout: 30000, + retry: 0, + }); + })().json()) as ErrorType | ResultType; + + if (!result.result) { + if (returnError) { + return result; + } + throw result.reason; + } + return result; +} + +export async function GET( + args?: ArgType +): Promise; +export async function GET( + args: ArgType | undefined, + returnError: false +): Promise; +export async function GET< + ResultType extends ValidResponse, + ErrorType extends InvalidResponse, + ArgType extends undefined = undefined +>(args: ArgType | undefined, returnError: true): Promise; +export async function GET< + ResultType extends ValidResponse, + ErrorType extends InvalidResponse, + ArgType extends undefined = undefined +>(args?: ArgType, returnError = false): Promise { + console.error(`Can't directly call GET. ${args}, ${returnError}. Use auto-generated path API.`); + return callSammoAPI("get", [], args, undefined, true); +} + +export async function POST( + args?: ArgType +): Promise; +export async function POST( + args: ArgType | undefined, + returnError: false +): Promise; +export async function POST< + ResultType extends ValidResponse, + ErrorType extends InvalidResponse, + ArgType extends RawArgType = RawArgType +>(args: ArgType | undefined, returnError: true): Promise; +export async function POST< + ResultType extends ValidResponse, + ErrorType extends InvalidResponse, + ArgType extends RawArgType = RawArgType +>(args?: ArgType, returnError = false): Promise { + console.error(`Can't directly call POST. ${args}, ${returnError}. Use auto-generated path API.`); + return callSammoAPI("post", [], args, undefined, true); +} + +export async function PUT( + args?: ArgType +): Promise; +export async function PUT( + args: ArgType | undefined, + returnError: false +): Promise; +export async function PUT< + ResultType extends ValidResponse, + ErrorType extends InvalidResponse, + ArgType extends RawArgType = RawArgType +>(args: ArgType | undefined, returnError: true): Promise; +export async function PUT< + ResultType extends ValidResponse, + ErrorType extends InvalidResponse, + ArgType extends RawArgType = RawArgType +>(args?: ArgType, returnError = false): Promise { + console.error(`Can't directly call PUT. ${args}, ${returnError}. Use auto-generated path API.`); + return callSammoAPI("put", [], args, undefined, true); +} + +export async function PATCH( + args?: ArgType +): Promise; +export async function PATCH( + args: ArgType | undefined, + returnError: false +): Promise; +export async function PATCH< + ResultType extends ValidResponse, + ErrorType extends InvalidResponse, + ArgType extends RawArgType = RawArgType +>(args: ArgType | undefined, returnError: true): Promise; +export async function PATCH< + ResultType extends ValidResponse, + ErrorType extends InvalidResponse, + ArgType extends RawArgType = RawArgType +>(args?: ArgType, returnError = false): Promise { + console.error(`Can't directly call PATCH. ${args}, ${returnError}. Use auto-generated path API.`); + return callSammoAPI("patch", [], args, undefined, true); +} + +export async function HEAD( + args?: ArgType +): Promise; +export async function HEAD( + args: ArgType | undefined, + returnError: false +): Promise; +export async function HEAD< + ResultType extends ValidResponse, + ErrorType extends InvalidResponse, + ArgType extends undefined = undefined +>(args: ArgType | undefined, returnError: true): Promise; +export async function HEAD< + ResultType extends ValidResponse, + ErrorType extends InvalidResponse, + ArgType extends undefined = undefined +>(args?: ArgType, returnError = false): Promise { + console.error(`Can't directly call HEAD. ${args}, ${returnError}. Use auto-generated path API.`); + return callSammoAPI("head", [], args, undefined, true); +} + +export async function DELETE( + args?: ArgType +): Promise; +export async function DELETE( + args: ArgType | undefined, + returnError: false +): Promise; +export async function DELETE< + ResultType extends ValidResponse, + ErrorType extends InvalidResponse, + ArgType extends RawArgType = RawArgType +>(args: ArgType | undefined, returnError: true): Promise; +export async function DELETE< + ResultType extends ValidResponse, + ErrorType extends InvalidResponse, + ArgType extends RawArgType = RawArgType +>(args?: ArgType, returnError = false): Promise { + console.error(`Can't directly call DELETE. ${args}, ${returnError}. Use auto-generated path API.`); + return callSammoAPI("patch", [], args, undefined, true); +} diff --git a/server/util/combineArray.ts b/server/util/combineArray.ts new file mode 100644 index 0000000..c247f1d --- /dev/null +++ b/server/util/combineArray.ts @@ -0,0 +1,11 @@ +import { combineObject } from "../common_legacy"; + + +export function combineArray(array: V[][], columnList: K[]): Record[] { + const result: Record[] = []; + for (const key of array.keys()) { + const item = array[key]; + result[key] = combineObject(item, columnList); + } + return result; +} diff --git a/server/util/convertBytesLikeToArrayBuffer.ts b/server/util/convertBytesLikeToArrayBuffer.ts new file mode 100644 index 0000000..e98688c --- /dev/null +++ b/server/util/convertBytesLikeToArrayBuffer.ts @@ -0,0 +1,17 @@ +import type { BytesLike } from "./BytesLike"; + +export function convertBytesLikeToArrayBuffer(data: BytesLike, encodeUTF8 = true): ArrayBuffer{ + if (data instanceof ArrayBuffer) { + return data; + } + if (data instanceof Uint8Array) { + return data.buffer; + } + if (typeof(data) === 'string'){ + if(encodeUTF8){ + return (new TextEncoder()).encode(data); + } + return new Uint8Array(data.split('').map(s=>s.codePointAt(0) as number)); + } + return data.buffer; +} \ No newline at end of file diff --git a/server/util/convertBytesLikeToUint8Array.ts b/server/util/convertBytesLikeToUint8Array.ts new file mode 100644 index 0000000..269175c --- /dev/null +++ b/server/util/convertBytesLikeToUint8Array.ts @@ -0,0 +1,17 @@ +import type { BytesLike } from "./BytesLike"; + +export function convertBytesLikeToUint8Array(data: BytesLike, encodeUTF8 = true): Uint8Array { + if (data instanceof Uint8Array) { + return data; + } + if (data instanceof ArrayBuffer) { + return new Uint8Array(data); + } + if (typeof (data) === 'string') { + if(encodeUTF8){ + return (new TextEncoder()).encode(data); + } + return new Uint8Array(data.split('').map(s=>s.codePointAt(0) as number)); + } + return new Uint8Array(data.buffer); +} \ No newline at end of file diff --git a/server/util/convertFormData.ts b/server/util/convertFormData.ts new file mode 100644 index 0000000..046639b --- /dev/null +++ b/server/util/convertFormData.ts @@ -0,0 +1,35 @@ +import { isArray, isString, isNumber, isBoolean } from "lodash-es"; + +export function convertFormData(values: Record): FormData { + const formData = new FormData(); + + const simpleConv = (v: unknown, key: string): string => { + if (isString(v)) { + return v; + } + if (isNumber(v)) { + return v.toString(); + } + if (isBoolean(v)) { + return v ? 'true' : 'false'; + } + if (v === null) { + return ''; + } + throw new TypeError(`지원하지 않는 formData Type: ${key}`); + } + + for (const [key, value] of Object.entries(values)) { + if (isArray(value)) { + const arrKey = `${key}[]`; + for (const subValue of value) { + formData.append(arrKey, simpleConv(subValue, key)); + } + continue; + } + + formData.append(key, simpleConv(value, key)); + } + + return formData; +} \ No newline at end of file diff --git a/server/util/convertIDArray.ts b/server/util/convertIDArray.ts new file mode 100644 index 0000000..be6f7d6 --- /dev/null +++ b/server/util/convertIDArray.ts @@ -0,0 +1,9 @@ +import type { IDItem } from '@/defs'; + +export function convertIDArray(array: Iterable): IDItem[] { + const result: IDItem[] = []; + for (const id of array) { + result.push({ id }); + } + return result; +} \ No newline at end of file diff --git a/server/util/convertIterableToMap.ts b/server/util/convertIterableToMap.ts new file mode 100644 index 0000000..ad98f75 --- /dev/null +++ b/server/util/convertIterableToMap.ts @@ -0,0 +1,10 @@ +export function convertIterableToMap( + values: Iterable, + key: K +): Map { + const result = new Map(); + for (const obj of values) { + result.set(obj[key] as V, obj); + } + return result; +} diff --git a/server/util/convertSearch초성.ts b/server/util/convertSearch초성.ts new file mode 100644 index 0000000..6fdc545 --- /dev/null +++ b/server/util/convertSearch초성.ts @@ -0,0 +1,9 @@ +import { automata초성All } from "./automata초성"; +import { filter초성withAlphabet } from "./filter초성withAlphabet"; + +export function convertSearch초성(text: string): string[]{ + const [filteredTextH, filteredTextA] = filter초성withAlphabet(text.replace(/\s+/g, "")); + const [filteredTextHL1, filteredTextHL2] = automata초성All(filteredTextH); + + return [text, filteredTextA, filteredTextH, filteredTextHL1, filteredTextHL2]; +} \ No newline at end of file diff --git a/server/util/customCSS.ts b/server/util/customCSS.ts new file mode 100644 index 0000000..29e55ef --- /dev/null +++ b/server/util/customCSS.ts @@ -0,0 +1,9 @@ +export function insertCustomCSS(key = 'sam_customCSS'){ + const customCSS = localStorage.getItem(key); + if (customCSS) { + const css = document.createElement('style'); + css.innerHTML = customCSS; + console.log(css); + document.getElementsByTagName('head')[0].appendChild(css); + } +} diff --git a/server/util/delay.ts b/server/util/delay.ts new file mode 100644 index 0000000..aabec78 --- /dev/null +++ b/server/util/delay.ts @@ -0,0 +1,7 @@ +export function delay(ms: number): Promise { + return new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, ms); + }); +} \ No newline at end of file diff --git a/server/util/entriesWithType.ts b/server/util/entriesWithType.ts new file mode 100644 index 0000000..66fb096 --- /dev/null +++ b/server/util/entriesWithType.ts @@ -0,0 +1,7 @@ +type Entries = { + [K in keyof T]: [K, T[K]]; +}[keyof T][]; + +export function entriesWithType(value: T): Entries{ + return Object.entries(value) as unknown as Entries; +} \ No newline at end of file diff --git a/server/util/exportWindow.ts b/server/util/exportWindow.ts new file mode 100644 index 0000000..b768b1c --- /dev/null +++ b/server/util/exportWindow.ts @@ -0,0 +1,4 @@ +export function exportWindow(obj:unknown, objName: string, targetWindow?: unknown):void{ + const target:unknown = targetWindow ?? window; + (target as {[v: string]: unknown})[objName] = obj; +} \ No newline at end of file diff --git a/server/util/filter초성.ts b/server/util/filter초성.ts new file mode 100644 index 0000000..a4c1045 --- /dev/null +++ b/server/util/filter초성.ts @@ -0,0 +1,17 @@ +export function filter초성(text: string): string { + const 초성 = [ + "ㄱ", "ㄲ", "ㄴ", "ㄷ", "ㄸ", "ㄹ", "ㅁ", "ㅂ", "ㅃ", + "ㅅ", "ㅆ", "ㅇ", "ㅈ", "ㅉ", "ㅊ", "ㅋ", "ㅌ", "ㅍ", "ㅎ" + ]; + const result: string[] = []; + for (const char of text) { + const code = (char.codePointAt(0) ?? 0) - 44032; + if (0 <= code && code < 11172) { + result.push(초성[~~(code / 588)]); + } + else { + result.push(char); + } + } + return result.join(''); +} \ No newline at end of file diff --git a/server/util/filter초성withAlphabet.ts b/server/util/filter초성withAlphabet.ts new file mode 100644 index 0000000..a071b51 --- /dev/null +++ b/server/util/filter초성withAlphabet.ts @@ -0,0 +1,24 @@ +export function filter초성withAlphabet(text: string): [string, string] { + const 초성 = [ + "ㄱ", "ㄲ", "ㄴ", "ㄷ", "ㄸ", "ㄹ", "ㅁ", "ㅂ", "ㅃ", + "ㅅ", "ㅆ", "ㅇ", "ㅈ", "ㅉ", "ㅊ", "ㅋ", "ㅌ", "ㅍ", "ㅎ" + ]; + const alphabets = [ + "r", "R", "s", "e", "E", "f", "a", "q", "Q", + "t", "T", "d", "w", "W", "c", "z", "x", "v", "g" + ]; + const resultH: string[] = []; + const resultA: string[] = []; + for (const char of text) { + const code = (char.codePointAt(0) ?? 0) - 44032; + if (0 <= code && code < 11172) { + resultH.push(초성[~~(code / 588)]); + resultA.push(alphabets[~~(code / 588)]); + } + else { + resultH.push(char); + resultA.push(char); + } + } + return [resultH.join(''), resultA.join('')]; +} \ No newline at end of file diff --git a/server/util/formatTime.ts b/server/util/formatTime.ts new file mode 100644 index 0000000..3a3d0d5 --- /dev/null +++ b/server/util/formatTime.ts @@ -0,0 +1,18 @@ +import { format, formatISO9075 } from 'date-fns'; +//const DATE_TIME_FORMAT = 'yyyy-MM-dd HH:mm:ss'; +const DATE_TIME_FORMAT_WITH_FRACTION = 'yyyy-MM-dd HH:mm:ss.SSS'; + +export function formatTime(time: Date, withFraction?:boolean): string; +export function formatTime(time: Date, format:string): string; + +export function formatTime(time: Date, withFractionOrFormat:string|boolean = false): string { + if (typeof withFractionOrFormat === "string") { + return format(time, withFractionOrFormat); + } + else if(withFractionOrFormat){ + return format(time, DATE_TIME_FORMAT_WITH_FRACTION); + } + else { + return formatISO9075(time); + } +} \ No newline at end of file diff --git a/server/util/generalStats.ts b/server/util/generalStats.ts new file mode 100644 index 0000000..1615b33 --- /dev/null +++ b/server/util/generalStats.ts @@ -0,0 +1,140 @@ +interface Stats { min: number, max: number, total: number }; + +export function abilityRand(stats: Stats): [number, number, number] { + let leadership = Math.random() * 65 + 10; + let strength = Math.random() * 65 + 10; + let intel = Math.random() * 65 + 10; + const rate = leadership + strength + intel; + + leadership = Math.floor((leadership / rate) * stats.total); + strength = Math.floor((strength / rate) * stats.total); + intel = Math.floor((intel / rate) * stats.total); + + while (leadership + strength + intel < stats.total) { + leadership += 1; + } + + if ( + leadership > stats.max || + strength > stats.max || + intel > stats.max || + leadership < stats.min || + strength < stats.min || + intel < stats.min + ) { + return abilityRand(stats); + } + + return [leadership, strength, intel]; +} + +export function abilityLeadpow(stats: Stats): [number, number, number] { + let leadership = Math.random() * 6; + let strength = Math.random() * 6; + let intel = Math.random() * 1; + const rate = leadership + strength + intel; + + leadership = Math.floor((leadership / rate) * stats.total); + strength = Math.floor((strength / rate) * stats.total); + intel = Math.floor((intel / rate) * stats.total); + + while (leadership + strength + intel < stats.total) { + strength += 1; + } + + if (intel < stats.min) { + leadership -= stats.min - intel; + intel = stats.min; + } + + if (leadership > stats.max) { + strength += leadership - stats.max; + leadership = stats.max; + } + + if (strength > stats.max) { + leadership += strength - stats.max; + strength = stats.max; + } + + if (leadership > stats.max) { + intel += leadership - stats.max; + leadership = stats.max; + } + + return [leadership, strength, intel]; +} + +export function abilityLeadint(stats: Stats): [number, number, number] { + let leadership = Math.random() * 6; + let strength = Math.random() * 1; + let intel = Math.random() * 6; + const rate = leadership + strength + intel; + + leadership = Math.floor((leadership / rate) * stats.total); + strength = Math.floor((strength / rate) * stats.total); + intel = Math.floor((intel / rate) * stats.total); + + while (leadership + strength + intel < stats.total) { + intel += 1; + } + + if (strength < stats.min) { + leadership -= stats.min - strength; + strength = stats.min; + } + + if (leadership > stats.max) { + intel += leadership - stats.max; + leadership = stats.max; + } + + if (intel > stats.max) { + leadership += intel - stats.max; + intel = stats.max; + } + + if (leadership > stats.max) { + strength += leadership - stats.max; + leadership = stats.max; + } + + return [leadership, strength, intel]; +} + +export function abilityPowint(stats: Stats): [number, number, number] { + let leadership = Math.random() * 1; + let strength = Math.random() * 6; + let intel = Math.random() * 6; + const rate = leadership + strength + intel; + + leadership = Math.floor((leadership / rate) * stats.total); + strength = Math.floor((strength / rate) * stats.total); + intel = Math.floor((intel / rate) * stats.total); + + while (leadership + strength + intel < stats.total) { + intel += 1; + } + + if (leadership < stats.min) { + strength -= stats.min - leadership; + leadership = stats.min; + } + + if (strength > stats.max) { + intel += strength - stats.max; + strength = stats.max; + } + + if (intel > stats.max) { + strength += intel - stats.max; + intel = stats.max; + } + + if (strength > stats.max) { + leadership += strength - stats.max; + strength = stats.max; + } + + return [leadership, strength, intel]; +} \ No newline at end of file diff --git a/server/util/getBase64FromFileObject.ts b/server/util/getBase64FromFileObject.ts new file mode 100644 index 0000000..db3758b --- /dev/null +++ b/server/util/getBase64FromFileObject.ts @@ -0,0 +1,17 @@ +import { unwrap } from "./unwrap"; + +//https://stackoverflow.com/questions/36280818/how-to-convert-file-to-base64-in-javascript +export function getBase64FromFileObject(file: File): Promise { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = () => { + let encoded = unwrap(reader.result).toString().replace(/^data:(.*,)?/, ''); + if ((encoded.length % 4) > 0) { + encoded += '='.repeat(4 - (encoded.length % 4)); + } + resolve(encoded); + }; + reader.onerror = error => reject(error); + }); +} \ No newline at end of file diff --git a/server/util/getDateTimeNow.ts b/server/util/getDateTimeNow.ts new file mode 100644 index 0000000..88c89bd --- /dev/null +++ b/server/util/getDateTimeNow.ts @@ -0,0 +1,4 @@ +import { formatTime } from '@util/formatTime'; +export function getDateTimeNow(withFraction = false): string { + return formatTime(new Date(), withFraction); +} \ No newline at end of file diff --git a/server/util/getIconPath.ts b/server/util/getIconPath.ts new file mode 100644 index 0000000..74500af --- /dev/null +++ b/server/util/getIconPath.ts @@ -0,0 +1,8 @@ +export function getIconPath(imgsvr: boolean | 1 | 0, picture: string): string { + // ../d_shared/common_path.js 필요 + if (!imgsvr) { + return `${window.pathConfig.sharedIcon}/${picture}`; + } else { + return `${window.pathConfig.root}/d_pic/${picture}`; + } +} diff --git a/server/util/hexToRgb.ts b/server/util/hexToRgb.ts new file mode 100644 index 0000000..8c396f0 --- /dev/null +++ b/server/util/hexToRgb.ts @@ -0,0 +1,8 @@ +export function hexToRgb(hex: string): { r: number; g: number; b: number; } | null { + const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return result ? { + r: parseInt(result[1], 16), + g: parseInt(result[2], 16), + b: parseInt(result[3], 16) + } : null; +} diff --git a/server/util/htmlReady.ts b/server/util/htmlReady.ts new file mode 100644 index 0000000..40d14e4 --- /dev/null +++ b/server/util/htmlReady.ts @@ -0,0 +1,7 @@ +export function htmlReady(fn: () => unknown): void { + if (document.readyState != 'loading') { + fn(); + } else { + document.addEventListener('DOMContentLoaded', fn); + } +} diff --git a/server/util/installVue3Components.ts b/server/util/installVue3Components.ts new file mode 100644 index 0000000..5b7a4d1 --- /dev/null +++ b/server/util/installVue3Components.ts @@ -0,0 +1,17 @@ +import { BootstrapVueNext, BToastPlugin } from "bootstrap-vue-next"; +import { Directives } from "bootstrap-vue-next"; +import type { App } from "vue"; +import Multiselect from "vue-multiselect"; + +export function installVue3Components(app: App): App { + + app.use(BootstrapVueNext).use(BToastPlugin).component('v-multiselect', Multiselect); + for (const [name, directive] of Object.entries(Directives)) { + //BVN 0.7.3 directive 이름 hack + if (!name.startsWith('v')) { + continue; + } + app.directive(name.substring(1), directive); + } + return app; +} \ No newline at end of file diff --git a/server/util/isBrightColor.ts b/server/util/isBrightColor.ts new file mode 100644 index 0000000..3681ded --- /dev/null +++ b/server/util/isBrightColor.ts @@ -0,0 +1,11 @@ +import { unwrap } from "@util/unwrap"; +import { hexToRgb } from "@util/hexToRgb"; + +export function isBrightColor(color: string): boolean { + const cv = unwrap(hexToRgb(color)); + if ((cv.r * 0.299 + cv.g * 0.587 + cv.b * 0.114) > 140) { + return true; + } else { + return false; + } +} diff --git a/server/util/isNotNull.ts b/server/util/isNotNull.ts new file mode 100644 index 0000000..2e9cc2a --- /dev/null +++ b/server/util/isNotNull.ts @@ -0,0 +1,3 @@ +export function isNotNull(value: T|null|undefined):value is T{ + return value !== null && value !== undefined; +} \ No newline at end of file diff --git a/server/util/joinYearMonth.ts b/server/util/joinYearMonth.ts new file mode 100644 index 0000000..f06a954 --- /dev/null +++ b/server/util/joinYearMonth.ts @@ -0,0 +1,3 @@ +export function joinYearMonth(year: number, month: number): number { + return year * 12 + month - 1; +} \ No newline at end of file diff --git a/server/util/jqValidateForm.ts b/server/util/jqValidateForm.ts new file mode 100644 index 0000000..44f249f --- /dev/null +++ b/server/util/jqValidateForm.ts @@ -0,0 +1,118 @@ +import Schema, { type Rule, type Values } from "async-validator"; +import { isArray } from "lodash-es"; +import { mergeKVArray } from "@util/mergeKVArray"; +import $ from 'jquery'; + +type Option = { + preParse?: ($target?: JQuery)=>void, + postParse?: (values:Record, $target?: JQuery)=>[Record, Map] +} + +type DefaultFormDataType = Record; + +export type NamedRules> = { + [v in keyof T]: Rule +} + +export class JQValidateForm { + + public readonly validator: Schema; + public readonly inputs: JQuery; + constructor(public readonly target: JQuery, public readonly rule: NamedRules, public readonly option?:Option) { + this.validator = new Schema(rule); + this.inputs = target.find(':input'); + } + + public clearErrMsg():void { + this.inputs.removeClass('is-invalid'); + this.inputs.removeClass('is-valid'); + this.target.find('.invalid-feedback').detach(); + } + + public installChangeHandler():this{ + this.inputs.on('change', ()=>{ + void this.validate(); + }); + return this; + } + + public async validate(): Promise { + if(this.option?.preParse !== undefined){ + this.option.preParse(this.target); + } + let rawValues = mergeKVArray(this.inputs.serializeArray()); + let optMap: Map; + if(this.option?.postParse !== undefined){ + [rawValues, optMap] = this.option.postParse(rawValues, this.target); + } + else{ + optMap = new Map(); + } + console.log(rawValues); + + const validateResult = await this.validator.validate(rawValues).catch(({fields }) => { + if(fields === undefined){ + console.error('validator 에러, 조건 검사 구문을 확인하세요.'); + return; + } + this.clearErrMsg(); + for(const rawKey of Object.keys(fields)){ + let $item: JQuery; + const key = rawKey.split('.')[0]; + console.log(`ErrorType: ${key}:${rawValues[key]}`); + const errMsg = fields[rawKey][0].message; + + if(optMap.has(key)){ + $item = this.target.find(optMap.get(key) as string); + } + else if(isArray(rawValues[key])){ + $item = this.target.find(`:input[name='${key}[]']`); + } + else{ + $item = this.target.find(`:input[name='${key}']`); + } + + if($item.length == 0){ + continue; + } + + const $error = $(`${errMsg}`); + + $error.addClass( "invalid-feedback" ); + + if ("radio" == $item.prop( "type" )) { + const $target = $item.parents( ".btn-group" ); + $error.insertAfter( $target ); + $target.addClass('is-invalid'); + } + else if ("checkbox" == $item.prop( "type" )) { + let $target = $item.parent( "label" ); + if($target.parent(".btn-group").length){ + $target = $target.parent('.btn-group'); + } + $error.insertAfter( $target ); + $target.addClass('is-invalid'); + } else { + $error.insertAfter( $item ); + $item.addClass('is-invalid'); + + } + } + + this.inputs.each(function(){ + const name = (this as HTMLInputElement).name; + if(name in fields){ + return; + } + this.classList.add('is-valid'); + }); + return undefined; + }); + if(validateResult === undefined){ + return undefined; + } + this.clearErrMsg(); + this.inputs.addClass('is-valid'); + return validateResult as TypedValue; + } +} \ No newline at end of file diff --git a/server/util/mb_strimwidth.ts b/server/util/mb_strimwidth.ts new file mode 100644 index 0000000..94b24f7 --- /dev/null +++ b/server/util/mb_strimwidth.ts @@ -0,0 +1,31 @@ +import { mb_strwidth } from "@util/mb_strwidth"; + +/** + * mb_strimwidth + * @param String + * @param int + * @param int + * @param String + * @return String + * @see http://www.php.net/manual/function.mb-strimwidth.php + */ +export function mb_strimwidth(str: string, start: number, width: number, trimmarker = ''): string { + const trimmakerWidth = mb_strwidth(trimmarker); + const l = str.length; + let trimmedLength = 0; + const trimmedStr: string[] = []; + for (let i = start; i < l; i++) { + const c = str.charAt(i); + const charWidth = mb_strwidth(c); + const next = str.charAt(i + 1); + const nextWidth = mb_strwidth(next); + + trimmedLength += charWidth; + trimmedStr.push(c); + if (trimmedLength + trimmakerWidth + nextWidth > width) { + trimmedStr.push(trimmarker); + break; + } + } + return trimmedStr.join(''); +} diff --git a/server/util/mb_strwidth.ts b/server/util/mb_strwidth.ts new file mode 100644 index 0000000..37ef5b8 --- /dev/null +++ b/server/util/mb_strwidth.ts @@ -0,0 +1,24 @@ +//https://gist.github.com/demouth/3217440 +/** + * mb_strwidth + * @see http://php.net/manual/function.mb-strwidth.php + */ +export function mb_strwidth(str: string): number { + const l = str.length; + let length = 0; + for (let i = 0; i < l; i++) { + const c = str.charCodeAt(i); + if (0x0000 <= c && c <= 0x0019) { + length += 0; + } else if (0x0020 <= c && c <= 0x1FFF) { + length += 1; + } else if (0x2000 <= c && c <= 0xFF60) { + length += 2; + } else if (0xFF61 <= c && c <= 0xFF9F) { + length += 1; + } else if (0xFFA0 <= c) { + length += 2; + } + } + return length; +} diff --git a/server/util/merge2DArrToObjectArr.ts b/server/util/merge2DArrToObjectArr.ts new file mode 100644 index 0000000..3b3b680 --- /dev/null +++ b/server/util/merge2DArrToObjectArr.ts @@ -0,0 +1,20 @@ + +import type { ValuesOf } from "@/defs"; +import { zip } from "lodash-es"; + +export function merge2DArrToObjectArr>(column: (keyof T)[], list: ValuesOf[][]): T[]{ + const result: T[] = []; + for(const rawItem of list){ + const item: Record = {}; + + if(column.length != rawItem.length){ + throw `column과 item의 길이가 같지 않습니다: ${column.length} != ${list.length}, ${JSON.stringify(item)}`; + } + + for(const [key, value] of zip(column, rawItem)){ + item[key as string] = value; + } + result.push(item as T); + } + return result; +} \ No newline at end of file diff --git a/server/util/mergeKVArray.ts b/server/util/mergeKVArray.ts new file mode 100644 index 0000000..92c19d1 --- /dev/null +++ b/server/util/mergeKVArray.ts @@ -0,0 +1,35 @@ +import { isString } from "lodash-es"; + +interface NameValuePair { + name: string, + value: string +} + +export function mergeKVArray(array : NameValuePair[]):Record{ + const result:Record = {}; + + for(const {name, value} of array){ + if(!isString(name)){ + throw new TypeError(`${name} is not string`); + } + if(!isString(value)){ + throw new TypeError(`${value} is not string`); + } + + if(name === '' || name === '[]'){ + continue; + } + if(name.length > 2 && name.slice(-2) == '[]'){ + const keyHead = name.slice(0, -2); + if(!(keyHead in result)){ + result[keyHead] = [value]; + } + else{ + (result[keyHead] as string[]).push(value); + } + continue; + } + result[name] = value; + } + return result; +} \ No newline at end of file diff --git a/server/util/nl2br.ts b/server/util/nl2br.ts new file mode 100644 index 0000000..555c703 --- /dev/null +++ b/server/util/nl2br.ts @@ -0,0 +1,4 @@ + +export function nl2br(text: string): string { + return text.replace(/\n/g, "
"); +} diff --git a/server/util/numberWithCommas.ts b/server/util/numberWithCommas.ts new file mode 100644 index 0000000..971045f --- /dev/null +++ b/server/util/numberWithCommas.ts @@ -0,0 +1,5 @@ + + +export function numberWithCommas(x: number): string { + return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); +} diff --git a/server/util/parseTime.ts b/server/util/parseTime.ts new file mode 100644 index 0000000..e90f9ce --- /dev/null +++ b/server/util/parseTime.ts @@ -0,0 +1,5 @@ +import {parseISO} from 'date-fns'; + +export function parseTime(dateString: string): Date{ + return parseISO(dateString); +} \ No newline at end of file diff --git a/server/util/parseYearMonth.ts b/server/util/parseYearMonth.ts new file mode 100644 index 0000000..a174a98 --- /dev/null +++ b/server/util/parseYearMonth.ts @@ -0,0 +1,3 @@ +export function parseYearMonth(yearMonth: number): [number, number] { + return [(yearMonth / 12) | 0, yearMonth % 12 + 1]; +} \ No newline at end of file diff --git a/server/util/randStr.ts b/server/util/randStr.ts new file mode 100644 index 0000000..25bf16f --- /dev/null +++ b/server/util/randStr.ts @@ -0,0 +1,25 @@ +const charList = [ + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', + 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', +]; + +export function randStr(len: number): string { + const result = []; + const charListLen = charList.length; + let isStart = true; + + while(len > 0){ + const randChrIdx = Math.floor(Math.random() * charListLen); + if(isStart){ + if(randChrIdx == 0){ + continue; + } + isStart = false; + } + result.push(charList[randChrIdx]); + len -= 1; + } + return result.join(''); +} \ No newline at end of file diff --git a/server/util/scrollHardTo.ts b/server/util/scrollHardTo.ts new file mode 100644 index 0000000..8f081af --- /dev/null +++ b/server/util/scrollHardTo.ts @@ -0,0 +1,10 @@ +/** @deprecated */ +export function scrollHardTo(elementId: string): void { + const element = document.getElementById(elementId); + if(!element){ + return; + } + element.scrollIntoView({ + behavior: 'auto', + }); + } \ No newline at end of file diff --git a/server/util/scrollToSelector.ts b/server/util/scrollToSelector.ts new file mode 100644 index 0000000..b8bee24 --- /dev/null +++ b/server/util/scrollToSelector.ts @@ -0,0 +1,9 @@ +export function scrollToSelector(selector: string): void { + const element = document.querySelector(selector); + if(!element){ + return; + } + element.scrollIntoView({ + behavior: 'auto', + }); + } \ No newline at end of file diff --git a/server/util/setAxiosXMLHttpRequest.ts b/server/util/setAxiosXMLHttpRequest.ts new file mode 100644 index 0000000..05c7c08 --- /dev/null +++ b/server/util/setAxiosXMLHttpRequest.ts @@ -0,0 +1,6 @@ +import axios from "axios"; + +export function setAxiosXMLHttpRequest(): void{ + axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; + //TODO: X-Requested-With 믿지 말자. +} diff --git a/server/util/simpleSerialize.ts b/server/util/simpleSerialize.ts new file mode 100644 index 0000000..8478208 --- /dev/null +++ b/server/util/simpleSerialize.ts @@ -0,0 +1,18 @@ +import { isInteger, isString } from "lodash-es"; + +export function simpleSerialize(...values : (string|number)[]): string{ + const result = []; + for(const value of values){ + if(isString(value)){ + result.push(`str(${value.length},${value})`); + continue; + } + if(isInteger(value)){ + result.push(`int(${value})`); + continue; + } + const float6 = value.toLocaleString("en-US", {maximumFractionDigits: 6}); + result.push(`float(${float6})`); + } + return result.join('|'); +} \ No newline at end of file diff --git a/server/util/unwrap.ts b/server/util/unwrap.ts new file mode 100644 index 0000000..300ec83 --- /dev/null +++ b/server/util/unwrap.ts @@ -0,0 +1,9 @@ +import type { Nullable } from "@util/Nullable"; +import { NotNullExpected } from "@util/NotNullExpected"; + +export function unwrap(result: Nullable): T { + if (result === null || result === undefined) { + throw new NotNullExpected(); + } + return result; +} diff --git a/server/util/unwrap_any.ts b/server/util/unwrap_any.ts new file mode 100644 index 0000000..b17a84f --- /dev/null +++ b/server/util/unwrap_any.ts @@ -0,0 +1,10 @@ +import type { Nullable } from "@util/Nullable"; +import { NotNullExpected } from "@util/NotNullExpected"; + + +export function unwrap_any(result: Nullable): T { + if (result === null || result === undefined) { + throw new NotNullExpected(); + } + return result as T; +} diff --git a/server/util/unwrap_err.ts b/server/util/unwrap_err.ts new file mode 100644 index 0000000..4da401a --- /dev/null +++ b/server/util/unwrap_err.ts @@ -0,0 +1,10 @@ +import type { Nullable } from "@util/Nullable"; + +type ErrType = { new(msg?: string): T } + +export function unwrap_err(result: Nullable, errType: ErrType, errMsg?: string): T { + if (result === null || result === undefined) { + throw new errType(errMsg); + } + return result; +} diff --git a/tsconfig.json b/tsconfig.json index bea208e..a90e26c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,5 @@ { - "files": [], + //"files": [], "references": [ { "path": "./tsconfig.node.json" @@ -12,11 +12,11 @@ "experimentalDecorators": true, "emitDecoratorMetadata": true, "resolveJsonModule": true, - "noImplicitOverride": true, + //"noImplicitOverride": true, "noImplicitReturns": true, - "noPropertyAccessFromIndexSignature": true, + //"noPropertyAccessFromIndexSignature": true, "isolatedModules": true, - "verbatimModuleSyntax": true, + //"verbatimModuleSyntax": true, "allowSyntheticDefaultImports": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true,