From b266595c4bbf70ff38aebbf3cb880c09666a47b2 Mon Sep 17 00:00:00 2001 From: kimpure Date: Fri, 22 May 2026 07:21:06 +0000 Subject: [PATCH] Update saferKorean: better code --- TODO.md | 1 + package-lock.json | 526 +++++++++++ package.json | 8 +- packages/db/generated/prisma/client.ts | 4 +- .../db/generated/prisma/internal/class.ts | 24 +- .../prisma/internal/prismaNamespace.ts | 23 +- .../prisma/models/DiscordGuildProfile.ts | 7 +- .../prisma/models/DiscordUserProfile.ts | 7 +- packages/index.ts | 5 +- packages/utils/saferKorean.ts | 836 ++++++++++-------- 10 files changed, 1053 insertions(+), 388 deletions(-) diff --git a/TODO.md b/TODO.md index 8a5aa78..d68ff0c 100644 --- a/TODO.md +++ b/TODO.md @@ -1,2 +1,3 @@ UNUSED 지우기 +rust 쪽에서 highpass 필터와 eq 먹이기 (audacity 프로젝트 확인하기) diff --git a/package-lock.json b/package-lock.json index 898c6a9..024fce2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "@discordjs/voice": "^0.19.2", "@prisma/adapter-pg": "^7.3.0", "@prisma/client": "^7.8.0", + "@qwreey-js/kotlin-scope-func": "^1.0.0", "@snazzah/davey": "^0.1.11", "discord.js": "^14.26.4", "dotenv": "^17.4.2", @@ -27,6 +28,7 @@ "@types/pg": "^8.20.0", "eslint": "^10.4.0", "prettier": "^3.8.3", + "tsx": "^4.22.3", "typescript": "^5.9.3", "typescript-eslint": "^8.59.4" } @@ -306,6 +308,448 @@ "tslib": "^2.4.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", @@ -901,6 +1345,12 @@ "react-dom": "^18.0.0 || ^19.0.0" } }, + "node_modules/@qwreey-js/kotlin-scope-func": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@qwreey-js/kotlin-scope-func/-/kotlin-scope-func-1.0.0.tgz", + "integrity": "sha512-TqrDgWwRr0XpRRq6bmhktsf7MbPKc9ZTVEzakDmP2mziB5+lS2M6uVbv6+Wug24BA2PEXnFstwn7zLW9oUmzOA==", + "license": "MIT" + }, "node_modules/@radix-ui/primitive": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", @@ -2147,6 +2597,48 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/esbuild": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" + } + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -2667,6 +3159,21 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "license": "ISC" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/gauge": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", @@ -4174,6 +4681,25 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tsx": { + "version": "4.22.3", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.3.tgz", + "integrity": "sha512-mdoNxBC/cSQObGGVQ5Bpn5i+yv7j68gk3Nfm3wFjcJg3Z0Mix9jzAFfP12prmm5eVGmDKtp0yyArrs0Q+8gZHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/package.json b/package.json index eaf8491..401561d 100644 --- a/package.json +++ b/package.json @@ -13,13 +13,14 @@ "build:tsc": "./node_modules/typescript/bin/tsc", "build": "npm run build:prisma && npm run build:tsc", "start": "prisma migrate deploy && node --import=extensionless/register .", - "dev": "npm run build && node --import=extensionless/register ." + "dev": "tsx packages/index.ts" }, "dependencies": { "@discordjs/opus": "^0.10.0", "@discordjs/voice": "^0.19.2", "@prisma/adapter-pg": "^7.3.0", "@prisma/client": "^7.8.0", + "@qwreey-js/kotlin-scope-func": "^1.0.0", "@snazzah/davey": "^0.1.11", "discord.js": "^14.26.4", "dotenv": "^17.4.2", @@ -38,7 +39,8 @@ "@types/pg": "^8.20.0", "eslint": "^10.4.0", "prettier": "^3.8.3", - "typescript-eslint": "^8.59.4", - "typescript": "^5.9.3" + "tsx": "^4.22.3", + "typescript": "^5.9.3", + "typescript-eslint": "^8.59.4" } } diff --git a/packages/db/generated/prisma/client.ts b/packages/db/generated/prisma/client.ts index c766dea..3e4310e 100644 --- a/packages/db/generated/prisma/client.ts +++ b/packages/db/generated/prisma/client.ts @@ -28,7 +28,9 @@ export * from "./enums" * Type-safe database client for TypeScript * @example * ``` - * const prisma = new PrismaClient() + * const prisma = new PrismaClient({ + * adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL }) + * }) * // Fetch zero or more DiscordUserProfiles * const discordUserProfiles = await prisma.discordUserProfile.findMany() * ``` diff --git a/packages/db/generated/prisma/internal/class.ts b/packages/db/generated/prisma/internal/class.ts index 09f0acb..6f7d27b 100644 --- a/packages/db/generated/prisma/internal/class.ts +++ b/packages/db/generated/prisma/internal/class.ts @@ -17,18 +17,26 @@ import type * as Prisma from "./prismaNamespace" const config: runtime.GetPrismaClientConfig = { "previewFeatures": [], - "clientVersion": "7.3.0", - "engineVersion": "9d6ad21cbbceab97458517b147a6a09ff43aa735", + "clientVersion": "7.8.0", + "engineVersion": "3c6e192761c0362d496ed980de936e2f3cebcd3a", "activeProvider": "postgresql", "inlineSchema": "generator client {\n provider = \"prisma-client\"\n output = \"../packages/db/generated/prisma\"\n specifying = [\"prismaSchemaFolder\"]\n}\n\ndatasource db {\n provider = \"postgresql\"\n}\n\nmodel DiscordUserProfile {\n id String @id @default(cuid())\n userId String @unique\n voice Voice @default(Papago)\n nya Boolean @default(false)\n canTypecast Boolean @default(false)\n userSupertonicStyle String @default(\"\")\n}\n\nmodel DiscordGuildProfile {\n id String @id @default(cuid())\n guildId String @unique\n readChannel String[] @default([])\n}\n\nenum Voice {\n TypeCast\n Papago\n Supertonic\n}\n", "runtimeDataModel": { "models": {}, "enums": {}, "types": {} + }, + "parameterizationSchema": { + "strings": [], + "graph": "" } } config.runtimeDataModel = JSON.parse("{\"models\":{\"DiscordUserProfile\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"voice\",\"kind\":\"enum\",\"type\":\"Voice\"},{\"name\":\"nya\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"canTypecast\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"userSupertonicStyle\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null},\"DiscordGuildProfile\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"guildId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"readChannel\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}") +config.parameterizationSchema = { + strings: JSON.parse("[\"where\",\"DiscordUserProfile.findUnique\",\"DiscordUserProfile.findUniqueOrThrow\",\"orderBy\",\"cursor\",\"DiscordUserProfile.findFirst\",\"DiscordUserProfile.findFirstOrThrow\",\"DiscordUserProfile.findMany\",\"data\",\"DiscordUserProfile.createOne\",\"DiscordUserProfile.createMany\",\"DiscordUserProfile.createManyAndReturn\",\"DiscordUserProfile.updateOne\",\"DiscordUserProfile.updateMany\",\"DiscordUserProfile.updateManyAndReturn\",\"create\",\"update\",\"DiscordUserProfile.upsertOne\",\"DiscordUserProfile.deleteOne\",\"DiscordUserProfile.deleteMany\",\"having\",\"_count\",\"_min\",\"_max\",\"DiscordUserProfile.groupBy\",\"DiscordUserProfile.aggregate\",\"DiscordGuildProfile.findUnique\",\"DiscordGuildProfile.findUniqueOrThrow\",\"DiscordGuildProfile.findFirst\",\"DiscordGuildProfile.findFirstOrThrow\",\"DiscordGuildProfile.findMany\",\"DiscordGuildProfile.createOne\",\"DiscordGuildProfile.createMany\",\"DiscordGuildProfile.createManyAndReturn\",\"DiscordGuildProfile.updateOne\",\"DiscordGuildProfile.updateMany\",\"DiscordGuildProfile.updateManyAndReturn\",\"DiscordGuildProfile.upsertOne\",\"DiscordGuildProfile.deleteOne\",\"DiscordGuildProfile.deleteMany\",\"DiscordGuildProfile.groupBy\",\"DiscordGuildProfile.aggregate\",\"AND\",\"OR\",\"NOT\",\"id\",\"guildId\",\"readChannel\",\"equals\",\"has\",\"hasEvery\",\"hasSome\",\"in\",\"notIn\",\"lt\",\"lte\",\"gt\",\"gte\",\"contains\",\"startsWith\",\"endsWith\",\"not\",\"userId\",\"Voice\",\"voice\",\"nya\",\"canTypecast\",\"userSupertonicStyle\",\"set\",\"push\"]"), + graph: "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_ACAXAAA_ACAwIAAAAAE9IAA-ACECMCAAAAABPSAAPwAhBxUAADcAIBYAAEEAIBcAAEEAIDAAAABAAjQAAABACDUAAABACD0AAEBAIgQwAAAAQAI0AAAAQAg1AAAAQAg9AABBQCIJKgAAQgAwKwAABAAQLAAAQgAwLQEAOgAhPgEAOgAhQAAAQ0AiQSAARAAhQiAARAAhQwEAOgAhBDAAAABAAjQAAABACDUAAABACD0AAEFAIgIwIAAAAAE9IAA_ACEAAAABRAEAAAABAkQBAAAABEUBAAAABQFEAQAAAAQAAAABRAAAAEACAUQgAAAAAQAAAAADFQAGFgAHFwAIAAAAAxUABhYABxcACAAAAAMVAA4WAA8XABAAAAADFQAOFgAPFwAQAQIBAgMBBQYBBgcBBwgBCQoBCgwCCw0DDA8BDRECDhIEERMBEhQBExUCGBgFGRkJGhsKGxwKHB8KHSAKHiEKHyMKICUCISYLIigKIyoCJCsMJSwKJi0KJy4CKDENKTIR" +} async function decodeBase64AsWasm(wasmBase64: string): Promise { const { Buffer } = await import('node:buffer') @@ -59,7 +67,9 @@ export interface PrismaClientConstructor { * Type-safe database client for TypeScript * @example * ``` - * const prisma = new PrismaClient() + * const prisma = new PrismaClient({ + * adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL }) + * }) * // Fetch zero or more DiscordUserProfiles * const discordUserProfiles = await prisma.discordUserProfile.findMany() * ``` @@ -81,7 +91,9 @@ export interface PrismaClientConstructor { * Type-safe database client for TypeScript * @example * ``` - * const prisma = new PrismaClient() + * const prisma = new PrismaClient({ + * adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL }) + * }) * // Fetch zero or more DiscordUserProfiles * const discordUserProfiles = await prisma.discordUserProfile.findMany() * ``` @@ -166,9 +178,9 @@ export interface PrismaClient< * ]) * ``` * - * Read more in our [docs](https://www.prisma.io/docs/concepts/components/prisma-client/transactions). + * Read more in our [docs](https://www.prisma.io/docs/orm/prisma-client/queries/transactions). */ - $transaction

[]>(arg: [...P], options?: { isolationLevel?: Prisma.TransactionIsolationLevel }): runtime.Types.Utils.JsPromise> + $transaction

[]>(arg: [...P], options?: { maxWait?: number, timeout?: number, isolationLevel?: Prisma.TransactionIsolationLevel }): runtime.Types.Utils.JsPromise> $transaction(fn: (prisma: Omit) => runtime.Types.Utils.JsPromise, options?: { maxWait?: number, timeout?: number, isolationLevel?: Prisma.TransactionIsolationLevel }): runtime.Types.Utils.JsPromise diff --git a/packages/db/generated/prisma/internal/prismaNamespace.ts b/packages/db/generated/prisma/internal/prismaNamespace.ts index 223da22..755d726 100644 --- a/packages/db/generated/prisma/internal/prismaNamespace.ts +++ b/packages/db/generated/prisma/internal/prismaNamespace.ts @@ -80,12 +80,12 @@ export type PrismaVersion = { } /** - * Prisma Client JS version: 7.3.0 - * Query Engine version: 9d6ad21cbbceab97458517b147a6a09ff43aa735 + * Prisma Client JS version: 7.8.0 + * Query Engine version: 3c6e192761c0362d496ed980de936e2f3cebcd3a */ export const prismaVersion: PrismaVersion = { - client: "7.3.0", - engine: "9d6ad21cbbceab97458517b147a6a09ff43aa735" + client: "7.8.0", + engine: "3c6e192761c0362d496ed980de936e2f3cebcd3a" } /** @@ -776,6 +776,21 @@ export type PrismaClientOptions = ({ * ``` */ comments?: runtime.SqlCommenterPlugin[] + /** + * Optional maximum size for the query plan cache. If not provided, a default size will be used. + * A value of `0` can be used to disable the cache entirely. A higher cache size can improve + * performance for applications that execute a large number of unique queries, while a smaller + * cache size can reduce memory usage. + * + * @example + * ``` + * const prisma = new PrismaClient({ + * adapter, + * queryPlanCacheMaxSize: 100, + * }) + * ``` + */ + queryPlanCacheMaxSize?: number } export type GlobalOmitConfig = { discordUserProfile?: Prisma.DiscordUserProfileOmit diff --git a/packages/db/generated/prisma/models/DiscordGuildProfile.ts b/packages/db/generated/prisma/models/DiscordGuildProfile.ts index 37e6d65..adddbbe 100644 --- a/packages/db/generated/prisma/models/DiscordGuildProfile.ts +++ b/packages/db/generated/prisma/models/DiscordGuildProfile.ts @@ -140,7 +140,7 @@ export type DiscordGuildProfileGroupByOutputType = { _max: DiscordGuildProfileMaxAggregateOutputType | null } -type GetDiscordGuildProfileGroupByPayload = Prisma.PrismaPromise< +export type GetDiscordGuildProfileGroupByPayload = Prisma.PrismaPromise< Array< Prisma.PickEnumerable & { @@ -909,6 +909,11 @@ export type DiscordGuildProfileFindManyArgs = Prisma.PrismaPromise< +export type GetDiscordUserProfileGroupByPayload = Prisma.PrismaPromise< Array< Prisma.PickEnumerable & { @@ -994,6 +994,11 @@ export type DiscordUserProfileFindManyArgs { await bot.registerCommands(); - await bot.registerEvents(); + bot.registerEvents(); console.log( "registerCommands: \n| " + @@ -20,4 +21,4 @@ bot.client.once("clientReady", async (client) => { ); }); -bot.client.login(DISCORD_TOKEN); +await bot.client.login(DISCORD_TOKEN); diff --git a/packages/utils/saferKorean.ts b/packages/utils/saferKorean.ts index fc36308..403335e 100644 --- a/packages/utils/saferKorean.ts +++ b/packages/utils/saferKorean.ts @@ -4,202 +4,7 @@ import IntegerKorean from "./integerKorean.js"; import PhoneNumberKorean from "./phoneNumberKorean.js"; import EmojiDescriptions from "./emoji-descriptions.json" with { type: "json" }; -export const IsolatedSymbolMap = { - "?": "물음표", - "!": "느낌표", - "'": "쿼트", - '"': "더블쿼트", -}; -export const SymbolMap = { - "%": "퍼센트", - $: "달러", - "^": "캐럿", - "&": "엔드", - "*": "스타", - "#": "샵", - "@": "엣", - ".": "쩜", - "-": "마이너스", - "+": "플러스", - _: "언더바", - "=": "이퀄", - "/": "슬래쉬", - "~": "물결표", - "\\": "역슬래쉬", - "♡": "하트 ", - "|": "", - ">": "", - "<": "", - ":": "콜론", - ";": "세미콜론", -}; -export const VersionPostfix = { - a: "알파", - b: "베타", -}; - -export const LangPrefixes = { - typescript: "타입스크립트", - javascript: "자바스크립트", - java: "자바", - kotlin: "코틀린", - rust: "러스트", - lua: "루아", - json: "제이슨", - yaml: "야믈", - yml: "야믈", - toml: "토믈", - xml: "엑스엠엘", - julia: "줄리아", - matlab: "매트랩", - erlang: "얼랭", - elxir: "엘릭서", - zig: "지그", - txt: "텍스트", - vim: "빔", - perl: "펄", - php: "피에이치피", - lisp: "리스프", - postscript: "포스트스크립트", - ghostscript: "고스트스크립트", - fortran: "포트란", - algol: "알골", - scala: "스칼라", - haskell: "하스켈", - basic: "베이직", - - cpp: "씨플플", - "c++": "씨플플", - csharp: "씨샵", - cs: "씨샵", - "c#": "씨샵", - c: "씨", - h: "헤더", - - d: "디", - awk: "에이더블류케이", - pl: "펄", - pwsh: "파워쉘", - powershell: "파워쉘", - cmd: "씨엠디", - sh: "쉘", - ps1: "파워셀", - bat: "배치파일", - bash: "베시스크립트", - tex: "텍", - dart: "다트", - go: "고랭", - python: "파이썬", - swift: "스위프트", - css: "씨에스에스", - html: "에이치티엠엘", - - latex: "레이텍", - md: "마크다운", - markdown: "마크다운", - - py: "파이썬", - hs: "하스켈", - rs: "러스트", - kt: "코틀린", - js: "자스", - ts: "타스", - tsx: "리액트 타입스크립트", - jsx: "리액트 자바스크립트", - an: "에이엔", - parlance: "팔렌스", -}; -export const LangPrefixMaxLength = (() => { - let max = 0; - for (const key in LangPrefixes) { - max = Math.max(key.length, max); - } - return max; -})(); -export const ChoseongMap = { - ㄱ: "기역", - ㄴ: "니은", - ㄷ: "디귿", - ㄹ: "리을", - ㅁ: "미음", - ㅂ: "비읍", - ㅅ: "시옷", - ㅇ: "이응", - ㅈ: "지읒", - ㅊ: "치읓", - ㅋ: "키읔", - ㅌ: "티읕", - ㅍ: "피읖", - ㅎ: "히읗", - ㄲ: "쌍기역", - ㄸ: "쌍디귿", - ㅃ: "쌍비읍", - ㅆ: "쌍시옷", - ㅉ: "쌍지읒", -}; - -export const SIPrefix = { - k: "킬로", - ki: "키비", - m: "메가", - mi: "메비", - g: "기가", - gi: "기비", - t: "테라", - ti: "테비", - p: "페타", - pi: "페비", - e: "엑사", - ei: "엑시", - z: "제타", - zi: "제비", - y: "요타", - yi: "요비", -}; -export const LiterPrefix = { - m: "밀리", - "": "", -}; -export const MeterPrefix = { - m: "밀리", - c: "센치", - "": "", - k: "킬로", -}; - -export const GIFMap = { - "tenor.com/view/majo-no-tabitabi-the-journey-of-elaina-elaina-windy-hair-gif-19187698": - "화난 일레이나", - "tenor.com/view/majo-no-tabitabi-the-journey-of-elaina-elaina-sparkle-amazed-gif-18827847": - "일레이나 반짝반짝!", - "images-ext-1.discordapp.net/external/C3xPFuUxs16jY25AR3NvsIDezaOtib9wozhLBWejZk4/https/media.tenor.com/bUd8mk4ufwsAAAPo/anime-disappointment.mp4": - "일레이나 절래절래", - "images-ext-1.discordapp.net/external/SXv4qgpy2r1Gx-dNxhcfJle6AXDaH_SToRjEBYYaup0/https/media.tenor.com/nDDxJc4FDwEAAAPo/cute.mp4": - "일레이나 끄덕", - "tenor.com/view/majo-no-tabitabi-the-journey-of-elaina-elaina-what-gif-19011602": - "당황한 일레이나", - "images-ext-1.discordapp.net/external/2R41WcvNJwYMD69UKls2cDa_hEL-rzCRCFvOi2DDOVo/https/media.tenor.com/sU3RCOixDbgAAAPo/majo-no-tabitabi-the-journey-of-elaina.mp4": - "일레이나 손짓", -}; - -export const UnicodeSymbols = { - "㎢": "제곱킬로미터", - "㎡": "제곱미터", - "↑": "위쪽 화살표", - "↓": "아래쪽 화살표", - "←": "왼쪽 화살표", - "→": "오른쪽 화살표", - "↔": "좌우 화살표", - "↖": "왼쪽 위 화살표", - "↗": "오른쪽 위 화살표", - "↘": "오른쪽 아래 화살표", - "↙": "왼쪽 아래 화살표", -}; -export const UnicodeSymbolsRegex = new RegExp( - "[" + Object.keys(UnicodeSymbols).join() + "]", - "gu", -); - +// Process tailing dots export function processDots(input: string): string { return input .replace(/[.,]+$/, "") @@ -207,187 +12,478 @@ export function processDots(input: string): string { .replace(/[.,]\s/g, " "); } -export function saferKorean(input: string): string { - return ( - processDots(input.normalize() + " ") - // Process isolated symbols - .replace(/^[?!'"]+ $/, (total) => - [...total] - .map( - (element) => - IsolatedSymbolMap[element as keyof typeof IsolatedSymbolMap], - ) - .join(""), - ) - .replace(/\s\|\|\s/g, " 오얼 ") - .replace(/\s&&\s/g, " 엔드 ") +// Process korean letter, choseong shortens +export function processKorean(input: string): string { + input = input.replace(/[아ㅏ]{3,}/g, "아아아"); - // Process codeblock - .replace(/```([\s\S]*?)```/g, (_, content: string) => { - const code = content.substring(0, LangPrefixMaxLength).toLowerCase(); - let lang = ""; - for (const [key, value] of Object.entries(LangPrefixes)) { - if (code.startsWith(key + "\n")) { - lang = value + " "; - break; - } - } - return lang + "코드블럭"; + return input.replace(/[ㄱ-ㅎㄲㄸㅃㅆㅉ]+/g, (i) => + i + .replace(processKorean.DoubleMixedChoseongMapRegex, (content: string) => { + // ㅇㅋ => 오키, ㅇㄴ => 아니, ... + const key = content.substring( + 0, + 2, + ) as keyof typeof processKorean.DoubleMixedChoseongMap; + const length = Math.min(Math.floor(content.length / 2), 2); + return processKorean.DoubleMixedChoseongMap[key].repeat(length); }) + .replace(processKorean.RepeatedChoseongMapRegex, (content: string) => { + // process ㄴㄴ ㄱㄱ ㅋㅋ ㄷㄷ, ... + const key = (content[0] ?? + "") as keyof typeof processKorean.RepeatedChoseongMap; + const item = processKorean.RepeatedChoseongMap[key]; - // Process link - .replace(/[hH][tT]{2}[pP][sS]?:\/\/(\S+)/g, (_, url: string) => { - const mapped = GIFMap[url as keyof typeof GIFMap] as string | undefined; - if (mapped) return mapped; - - if (url.startsWith("tenor.com/view")) { - return "움짤!"; - } - return "링크"; - }) - - // Process koreans - .replace(/[아ㅏ]{3,}/g, "아아아") - .replace(/ㄹㅇ/g, (content: string) => { - return "리얼".repeat(Math.min(Math.floor(content.length / 2), 2)); - }) - .replace(/(ㅇㄴ)+/g, (content: string) => { - return "아니".repeat(Math.min(Math.floor(content.length / 2), 2)); - }) - .replace(/(ㅇㅎ)+/g, (content: string) => { - return "아하".repeat(Math.min(Math.floor(content.length / 2), 2)); - }) - .replace(/(ㅇㅋ)+/g, (content: string) => { - return "오키".repeat(Math.min(Math.floor(content.length / 2), 2)); - }) - .replace(/(ㅊㅋ)+/g, (content: string) => { - return "추카".repeat(Math.min(Math.floor(content.length / 2), 2)); - }) - .replace(/ㄱ+/g, (content: string) => { - if (content.length == 2) { - return "고고"; - } else if (content.length == 3) { - return "고고고"; + if (typeof item == "string") { + return item; + } else if (typeof item == "function") { + return item(content); } return content; }) - .replace(/ㅋ{2,}/g, (content) => "크".repeat(content.length)) - .replace(/ㅌ{2,}/g, "틔틔") - .replace(/ㄷ{2,}/g, "덜덜") - .replace(/ㄴ{2,}/g, "노노") - .replace(/ㅇ{2,}/g, "응응") - .replace(/ㅊ{2,}/g, "추추") - .replace(/ㅠ{2,}/g, "유유") - .replace(/ㅜ{2,}/g, "우우") .replace( /[ㄱ-ㅎㄲㄸㅃㅆㅉ]/g, - (char: string) => ChoseongMap[char as keyof typeof ChoseongMap], - ) - - // Process number, unit - .replace( - /(\+\d+[\s-]+)?([\d-]+)/g, - (_, prefix: string | undefined, phone: string) => { - const all = (prefix ?? "") + phone; - if (!phone.includes("-")) return all; - return PhoneNumberKorean.convert(all); - }, - ) - .replace( - /([\d,]+)([kKMmgGtTpPeEzZyY][iI]?)[bB]/g, - (_, num: string, mod: string) => { - // 10kib => 십키비바이트 - num = IntegerKorean.convertFromString(num); - mod = SIPrefix[mod.toLowerCase() as keyof typeof SIPrefix]; - return `${num} ${mod}바이트 `; - }, - ) - .replace(/([\d,]+)([m]?)[lL]\s/g, (_, num: string, mod: string) => { - // 10l => 십리터 - num = IntegerKorean.convertFromString(num); - mod = LiterPrefix[mod as keyof typeof LiterPrefix]; - return `${num} ${mod}리터 `; - }) - .replace(/([\d,]+)([mck]?)m\s/g, (_, num: string, mod: string) => { - // 10m => 십미터 - num = IntegerKorean.convertFromString(num); - mod = MeterPrefix[mod as keyof typeof MeterPrefix]; - return `${num} ${mod}미터 `; - }) - .replace( - /([\d.]+)\s*([개살시평명])/g, - (_, num: string, postfix: string) => { - // 10명 => 열명 - if (num.includes(".")) { - return num + postfix; - } - const intNum = parseInt(num); - if (CallingNumberKorean.canConvert(intNum)) { - return CallingNumberKorean.convert(intNum) + postfix; - } else { - return IntegerKorean.convertFromString(num) + postfix; - } - }, - ) - .replace(/[\d,]+/g, (num: string) => { - // 1,000 원 => 천원 - if (!num.includes(",")) return num; - return IntegerKorean.convertFromString(num); - }) - .replace( - /(v?)([\d.]+)([ab]?)/g, - (_, suffix: string, num: string, postfix: string) => { - const dotCount = [...num.matchAll(/\./g)].length; - const hasNoSuffix = suffix == ""; - - if (hasNoSuffix && dotCount == 0) { - // 일반 숫자는 인트로 읽음 - return IntegerKorean.convertFromString(num) + postfix; - } else if (hasNoSuffix && dotCount == 1) { - // 소수는 . 앞은 인트로, 뒤는 플로트로 읽음 - const [intPart, floatPart] = num.split(/\./); - return ( - IntegerKorean.convertFromString(intPart ?? "") + - "쩜" + - FloatKorean.convert(floatPart ?? "") + - postfix - ); - } else if ((suffix == "v" || postfix.length) && dotCount > 1) { - // 버전표기는 버전을 붙여서 - return ( - "버전" + - FloatKorean.convert(num) + - (VersionPostfix[postfix as keyof typeof VersionPostfix] ?? "") - ); - } else { - // 모든 경우에 속하지 않으면 영일이삼사 형태로 읽음 - // (예: 111.111.111.111 ip address) - return FloatKorean.convert(num) + postfix; - } - }, - ) - - // Process symbol - .replace( - /[%^&*#@.\-+_=/\\♡$|:;><]/g, - (t) => SymbolMap[t as keyof typeof SymbolMap], - ) - .replace(/([?!]+)/g, (_, content: string): string => content[0] ?? "") - .replace(/[ \t\f\r]+/g, " ") - - // Process emoji - .replace( - UnicodeSymbolsRegex, - (content: string) => - UnicodeSymbols[content as keyof typeof UnicodeSymbols] ?? content, - ) - .replace(/\p{Extended_Pictographic}/gu, (content: string) => { - return ( - EmojiDescriptions[content as keyof typeof EmojiDescriptions] ?? - content - ); - }) - .replace(/\p{Emoji}/u, " 이모지 ") - .trim() + (char: string) => + processKorean.ChoseongMap[ + char as keyof typeof processKorean.ChoseongMap + ] ?? char, + ), ); } +export namespace processKorean { + export const DoubleMixedChoseongMap = { + ㅎㅇ: "하이", + ㅅㄹ: "싫어", + ㄱㄷ: "기달", + ㅈㅂ: "제발", + ㅁㄹ: "몰라", + ㅅㅂ: "시바", + ㅇㄷ: "어디", + ㄴㅈ: "노잼", + ㅂㅂ: "바바", + ㅂㅇ: "바이", + ㅈㅅ: "죄송", + ㅇㄴ: "아니", + ㅃㄹ: "빨리", + ㅇㅈ: "인정", + ㄴㄴ: "노노", + ㄱㅅ: "감사", + ㅉㅉ: "쯧쯧", + ㅈㄹ: "지랄", + ㄹㅇ: "리얼", + ㅇㅎ: "아하", + ㅇㅋ: "오키", + ㅊㅋ: "추카", + ㄲㅈ: "꺼져", + }; + export const DoubleMixedChoseongMapRegex = new RegExp( + Object.keys(DoubleMixedChoseongMap) + .map((k) => `(?:${k})+`) + .join("|"), + "g", + ); + + export const RepeatedChoseongMap = { + ㅌ: "틔틔", + ㄷ: "덜덜", + ㄴ: "노노", + ㅇ: "응응", + ㅊ: "추추", + ㅠ: "유유", + ㅜ: "우우", + ㅋ: (content: string) => "크".repeat(content.length), + ㅎ: (content: string) => "흐".repeat(content.length), + ㄱ: (content: string) => { + if (content.length == 2) { + return "고고"; + } else if (content.length == 3) { + return "고고고"; + } + return content; + }, + }; + export const RepeatedChoseongMapRegex = new RegExp( + Object.keys(RepeatedChoseongMap) + .map((k) => `${k}{2,}`) + .join("|"), + "g", + ); + + // prettier-ignore + export const ChoseongMap = { + ㄱ: "기역", ㄴ: "니은", ㄷ: "디귿", ㄹ: "리을", ㅁ: "미음", ㅂ: "비읍", + ㅅ: "시옷", ㅇ: "이응", ㅈ: "지읒", ㅊ: "치읓", ㅋ: "키읔", ㅌ: "티읕", + ㅍ: "피읖", ㅎ: "히읗", ㄲ: "쌍기역", ㄸ: "쌍디귿", ㅃ: "쌍비읍", + ㅆ: "쌍시옷", ㅉ: "쌍지읒", + }; +} + +// Process 10km 1,000 1.1, ... numbers +export function processNumber(input: string): string { + return input + .replace( + /(\+\d+[\s-]+)?([\d-]+)/g, + (_, prefix: string | undefined, phone: string) => { + const all = (prefix ?? "") + phone; + if (!phone.includes("-")) return all; + return PhoneNumberKorean.convert(all); + }, + ) + .replace( + /([\d,]+)(?:(?[kKMmgGtTpPeEzZyY][iI]?)(?[bB])|(?[m]?)(?[lL])|(?[mck]?)(?m))(?[^a-zA-Z])/g, + (_, num: string, ...last: any): string => { + const group = last[last.length - 1] as { + prefix: string; + unit: string; + tail: string; + }; + const tail = group.tail; + const unit = group.unit.toLocaleLowerCase(); + const numStr = IntegerKorean.convertFromString(num); + let prefix = group.prefix; + + if (unit == "b") { + // 10kib => 십키비바이트 + prefix = + processNumber.DatasizePrefix[ + prefix.toLowerCase() as keyof typeof processNumber.DatasizePrefix + ]; + return `${numStr} ${prefix}바이트 ${tail}`; + } + if (unit == "l") { + // 10l => 십리터 + prefix = + processNumber.LiterPrefix[ + prefix.toLowerCase() as keyof typeof processNumber.LiterPrefix + ]; + return `${numStr} ${prefix}리터 ${tail}`; + } + if (unit == "m") { + // 10m => 십미터 + prefix = + processNumber.MeterPrefix[ + prefix as keyof typeof processNumber.MeterPrefix + ]; + return `${numStr} ${prefix}미터 ${tail}`; + } + return `${num}${prefix}${unit}${tail}`; + }, + ) + .replace( + /([\d.,]+)\s*([개살시평명])/g, + (_, num: string, postfix: string) => { + // 10명 => 열명 + if (num.includes(".")) { + return num + postfix; + } + const intNum = parseInt(num.replace(/,/g, "")); + if (CallingNumberKorean.canConvert(intNum)) { + return CallingNumberKorean.convert(intNum) + postfix; + } else { + return IntegerKorean.convertFromString(num) + postfix; + } + }, + ) + .replace(/[\d,]+/g, (num: string) => { + // 1,000 원 => 천원 + if (!num.includes(",")) return num; + return IntegerKorean.convertFromString(num); + }) + .replace( + /(v?)([\d.]+)([ab]?)/g, + (_, suffix: string, num: string, postfix: string) => { + const dotCount = [...num.matchAll(/\./g)].length; + const hasNoSuffix = suffix == ""; + + if (hasNoSuffix && dotCount == 0) { + // 일반 숫자는 인트로 읽음 + return IntegerKorean.convertFromString(num) + postfix; + } else if (hasNoSuffix && dotCount == 1) { + // 소수는 . 앞은 인트로, 뒤는 플로트로 읽음 + const [intPart, floatPart] = num.split(/\./); + return ( + IntegerKorean.convertFromString(intPart ?? "") + + "쩜" + + FloatKorean.convert(floatPart ?? "") + + postfix + ); + } else if ((suffix == "v" || postfix.length) && dotCount > 1) { + // 버전표기는 버전을 붙여서 + return ( + "버전" + + FloatKorean.convert(num) + + (processNumber.VersionPostfix[ + postfix as keyof typeof processNumber.VersionPostfix + ] ?? "") + ); + } else { + // 모든 경우에 속하지 않으면 영일이삼사 형태로 읽음 + // (예: 111.111.111.111 ip address) + return FloatKorean.convert(num) + postfix; + } + }, + ); +} +export namespace processNumber { + // prettier-ignore + export const DatasizePrefix = { + k: "킬로", ki: "키비", m: "메가", mi: "메비", + g: "기가", gi: "기비", t: "테라", ti: "테비", + p: "페타", pi: "페비", e: "엑사", ei: "엑시", + z: "제타", zi: "제비", y: "요타", yi: "요비", + }; + // prettier-ignore + export const LiterPrefix = { m: "밀리", "": "" }; + // prettier-ignore + export const MeterPrefix = { + m: "밀리", c: "센치", "": "", k: "킬로", + }; + // prettier-ignore + export const VersionPostfix = { + a: "알파", b: "베타", + }; +} + +// Process unicode emojis and unicode symbols +export function processEmoji(input: string): string { + return input + .replace( + processEmoji.UnicodeSymbolsRegex, + (content: string) => + processEmoji.UnicodeSymbols[ + content as keyof typeof processEmoji.UnicodeSymbols + ] ?? content, + ) + .replace(/\p{Extended_Pictographic}/gu, (content: string) => { + return ( + EmojiDescriptions[content as keyof typeof EmojiDescriptions] ?? content + ); + }) + .replace(/\p{Emoji}/u, " 이모지 "); +} +export namespace processEmoji { + export const UnicodeSymbols = { + "㎢": "제곱킬로미터", + "㎡": "제곱미터", + "↑": "위쪽 화살표", + "↓": "아래쪽 화살표", + "←": "왼쪽 화살표", + "→": "오른쪽 화살표", + "↔": "좌우 화살표", + "↖": "왼쪽 위 화살표", + "↗": "오른쪽 위 화살표", + "↘": "오른쪽 아래 화살표", + "↙": "왼쪽 아래 화살표", + "™": "티앰", + }; + export const UnicodeSymbolsRegex = new RegExp( + "[" + Object.keys(UnicodeSymbols).join() + "]", + "gu", + ); +} + +// Process ```codeblock``` and https://link +export function processMarkdown(input: string): string { + return input + .replace(/```([\s\S]*?)```/g, (_, content: string) => { + // Process codeblock + const code = content + .substring(0, processMarkdown.LangPrefixMaxLength) + .toLowerCase(); + let lang = ""; + for (const [key, value] of Object.entries(processMarkdown.LangPrefixes)) { + if (code.startsWith(key + "\n")) { + lang = value + " "; + break; + } + } + return lang + "코드블럭"; + }) + + .replace(/[hH][tT]{2}[pP][sS]?:\/\/(\S+)/g, (_, url: string) => { + // Process link + const mapped = processMarkdown.GIFMap[ + url as keyof typeof processMarkdown.GIFMap + ] as string | undefined; + if (mapped) return mapped; + + if (url.startsWith("tenor.com/view")) { + return "움짤!"; + } + return "링크"; + }); +} +export namespace processMarkdown { + export const LangPrefixes = { + typescript: "타입스크립트", + javascript: "자바스크립트", + java: "자바", + kotlin: "코틀린", + rust: "러스트", + lua: "루아", + json: "제이슨", + yaml: "야믈", + yml: "야믈", + toml: "토믈", + xml: "엑스엠엘", + julia: "줄리아", + matlab: "매트랩", + erlang: "얼랭", + elxir: "엘릭서", + zig: "지그", + txt: "텍스트", + vim: "빔", + perl: "펄", + php: "피에이치피", + lisp: "리스프", + postscript: "포스트스크립트", + ghostscript: "고스트스크립트", + fortran: "포트란", + algol: "알골", + scala: "스칼라", + haskell: "하스켈", + basic: "베이직", + + cpp: "씨플플", + "c++": "씨플플", + csharp: "씨샵", + cs: "씨샵", + "c#": "씨샵", + c: "씨", + h: "헤더", + + d: "디", + awk: "에이더블류케이", + pl: "펄", + pwsh: "파워쉘", + powershell: "파워쉘", + cmd: "씨엠디", + sh: "쉘", + ps1: "파워셀", + bat: "배치파일", + bash: "베시스크립트", + tex: "텍", + dart: "다트", + go: "고랭", + python: "파이썬", + swift: "스위프트", + css: "씨에스에스", + html: "에이치티엠엘", + + latex: "레이텍", + md: "마크다운", + markdown: "마크다운", + + py: "파이썬", + hs: "하스켈", + rs: "러스트", + kt: "코틀린", + js: "자스", + ts: "타스", + tsx: "리액트 타입스크립트", + jsx: "리액트 자바스크립트", + an: "에이엔", + parlance: "팔렌스", + }; + export const LangPrefixMaxLength = (() => { + let max = 0; + for (const key in LangPrefixes) { + max = Math.max(key.length, max); + } + return max; + })(); + export const GIFMap = { + "tenor.com/view/majo-no-tabitabi-the-journey-of-elaina-elaina-windy-hair-gif-19187698": + "화난 일레이나", + "tenor.com/view/majo-no-tabitabi-the-journey-of-elaina-elaina-sparkle-amazed-gif-18827847": + "일레이나 반짝반짝!", + "images-ext-1.discordapp.net/external/C3xPFuUxs16jY25AR3NvsIDezaOtib9wozhLBWejZk4/https/media.tenor.com/bUd8mk4ufwsAAAPo/anime-disappointment.mp4": + "일레이나 절래절래", + "images-ext-1.discordapp.net/external/SXv4qgpy2r1Gx-dNxhcfJle6AXDaH_SToRjEBYYaup0/https/media.tenor.com/nDDxJc4FDwEAAAPo/cute.mp4": + "일레이나 끄덕", + "tenor.com/view/majo-no-tabitabi-the-journey-of-elaina-elaina-what-gif-19011602": + "당황한 일레이나", + "images-ext-1.discordapp.net/external/2R41WcvNJwYMD69UKls2cDa_hEL-rzCRCFvOi2DDOVo/https/media.tenor.com/sU3RCOixDbgAAAPo/majo-no-tabitabi-the-journey-of-elaina.mp4": + "일레이나 손짓", + }; +} + +// Process %$*&... symbols to readable korean +export function processSymbol(input: string): string { + return input + .replace( + processSymbol.SymbolMapRegExp, + (t) => processSymbol.SymbolMap[t as keyof typeof processSymbol.SymbolMap], + ) + .replace(/([?!]+)/g, (_, content: string): string => content[0] ?? "") + .replace(/[ \t\f\r]+/g, " "); +} +export namespace processSymbol { + export const SymbolMap = { + "%": "퍼센트", + $: "달러", + "^": "캐럿", + "&": "엔드", + "*": "스타", + "#": "샵", + "@": "엣", + ".": "쩜", + "-": "마이너스", + "+": "플러스", + _: "언더바", + "=": "이퀄", + "/": "슬래쉬", + "~": "물결표", + "\\": "역슬래쉬", + "♡": "하트 ", + "|": "", + ">": "", + "<": "", + ":": "콜론", + ";": "세미콜론", + }; + export const SymbolMapRegExp = new RegExp( + "[" + + Object.keys(SymbolMap) + .map((i) => "\\" + i) + .join() + + "]", + "g", + ); +} + +// Process isolated symbols +export function processIsolatedSymbol(input: string): string { + return input + .replace(/^[?!'"]+ $/, (total) => + [...total] + .map( + (element) => + processIsolatedSymbol.IsolatedSymbolMap[ + element as keyof typeof processIsolatedSymbol.IsolatedSymbolMap + ], + ) + .join(""), + ) + .replace(/\s\|\|\s/g, " 오얼 ") + .replace(/\s&&\s/g, " 엔드 "); +} +export namespace processIsolatedSymbol { + export const IsolatedSymbolMap = { + "?": "물음표", + "!": "느낌표", + "'": "쿼트", + '"': "더블쿼트", + }; +} + +export function saferKorean(input: string): string { + return (input.normalize() + " ") + .let((i) => processDots(i)) + .let((i) => processIsolatedSymbol(i)) + .let((i) => processMarkdown(i)) + .let((i) => processKorean(i)) + .let((i) => processNumber(i)) + .let((i) => processSymbol(i)) + .let((i) => processEmoji(i)) + .trim(); +}