Update saferKorean: better code

This commit is contained in:
kimpure 2026-05-22 07:21:06 +00:00
parent 4868ccd6b2
commit b266595c4b
No known key found for this signature in database
10 changed files with 1053 additions and 388 deletions

View file

@ -1,2 +1,3 @@
UNUSED 지우기 UNUSED 지우기
rust 쪽에서 highpass 필터와 eq 먹이기 (audacity 프로젝트 확인하기)

526
package-lock.json generated
View file

@ -9,6 +9,7 @@
"@discordjs/voice": "^0.19.2", "@discordjs/voice": "^0.19.2",
"@prisma/adapter-pg": "^7.3.0", "@prisma/adapter-pg": "^7.3.0",
"@prisma/client": "^7.8.0", "@prisma/client": "^7.8.0",
"@qwreey-js/kotlin-scope-func": "^1.0.0",
"@snazzah/davey": "^0.1.11", "@snazzah/davey": "^0.1.11",
"discord.js": "^14.26.4", "discord.js": "^14.26.4",
"dotenv": "^17.4.2", "dotenv": "^17.4.2",
@ -27,6 +28,7 @@
"@types/pg": "^8.20.0", "@types/pg": "^8.20.0",
"eslint": "^10.4.0", "eslint": "^10.4.0",
"prettier": "^3.8.3", "prettier": "^3.8.3",
"tsx": "^4.22.3",
"typescript": "^5.9.3", "typescript": "^5.9.3",
"typescript-eslint": "^8.59.4" "typescript-eslint": "^8.59.4"
} }
@ -306,6 +308,448 @@
"tslib": "^2.4.0" "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": { "node_modules/@eslint-community/eslint-utils": {
"version": "4.9.1", "version": "4.9.1",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", "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" "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": { "node_modules/@radix-ui/primitive": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
@ -2147,6 +2597,48 @@
"url": "https://github.com/sponsors/sindresorhus" "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": { "node_modules/escape-string-regexp": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@ -2667,6 +3159,21 @@
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"license": "ISC" "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": { "node_modules/gauge": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
@ -4174,6 +4681,25 @@
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD" "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": { "node_modules/type-check": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",

View file

@ -13,13 +13,14 @@
"build:tsc": "./node_modules/typescript/bin/tsc", "build:tsc": "./node_modules/typescript/bin/tsc",
"build": "npm run build:prisma && npm run build:tsc", "build": "npm run build:prisma && npm run build:tsc",
"start": "prisma migrate deploy && node --import=extensionless/register .", "start": "prisma migrate deploy && node --import=extensionless/register .",
"dev": "npm run build && node --import=extensionless/register ." "dev": "tsx packages/index.ts"
}, },
"dependencies": { "dependencies": {
"@discordjs/opus": "^0.10.0", "@discordjs/opus": "^0.10.0",
"@discordjs/voice": "^0.19.2", "@discordjs/voice": "^0.19.2",
"@prisma/adapter-pg": "^7.3.0", "@prisma/adapter-pg": "^7.3.0",
"@prisma/client": "^7.8.0", "@prisma/client": "^7.8.0",
"@qwreey-js/kotlin-scope-func": "^1.0.0",
"@snazzah/davey": "^0.1.11", "@snazzah/davey": "^0.1.11",
"discord.js": "^14.26.4", "discord.js": "^14.26.4",
"dotenv": "^17.4.2", "dotenv": "^17.4.2",
@ -38,7 +39,8 @@
"@types/pg": "^8.20.0", "@types/pg": "^8.20.0",
"eslint": "^10.4.0", "eslint": "^10.4.0",
"prettier": "^3.8.3", "prettier": "^3.8.3",
"typescript-eslint": "^8.59.4", "tsx": "^4.22.3",
"typescript": "^5.9.3" "typescript": "^5.9.3",
"typescript-eslint": "^8.59.4"
} }
} }

View file

@ -28,7 +28,9 @@ export * from "./enums"
* Type-safe database client for TypeScript * Type-safe database client for TypeScript
* @example * @example
* ``` * ```
* const prisma = new PrismaClient() * const prisma = new PrismaClient({
* adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL })
* })
* // Fetch zero or more DiscordUserProfiles * // Fetch zero or more DiscordUserProfiles
* const discordUserProfiles = await prisma.discordUserProfile.findMany() * const discordUserProfiles = await prisma.discordUserProfile.findMany()
* ``` * ```

View file

@ -17,18 +17,26 @@ import type * as Prisma from "./prismaNamespace"
const config: runtime.GetPrismaClientConfig = { const config: runtime.GetPrismaClientConfig = {
"previewFeatures": [], "previewFeatures": [],
"clientVersion": "7.3.0", "clientVersion": "7.8.0",
"engineVersion": "9d6ad21cbbceab97458517b147a6a09ff43aa735", "engineVersion": "3c6e192761c0362d496ed980de936e2f3cebcd3a",
"activeProvider": "postgresql", "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", "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": { "runtimeDataModel": {
"models": {}, "models": {},
"enums": {}, "enums": {},
"types": {} "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.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<WebAssembly.Module> { async function decodeBase64AsWasm(wasmBase64: string): Promise<WebAssembly.Module> {
const { Buffer } = await import('node:buffer') const { Buffer } = await import('node:buffer')
@ -59,7 +67,9 @@ export interface PrismaClientConstructor {
* Type-safe database client for TypeScript * Type-safe database client for TypeScript
* @example * @example
* ``` * ```
* const prisma = new PrismaClient() * const prisma = new PrismaClient({
* adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL })
* })
* // Fetch zero or more DiscordUserProfiles * // Fetch zero or more DiscordUserProfiles
* const discordUserProfiles = await prisma.discordUserProfile.findMany() * const discordUserProfiles = await prisma.discordUserProfile.findMany()
* ``` * ```
@ -81,7 +91,9 @@ export interface PrismaClientConstructor {
* Type-safe database client for TypeScript * Type-safe database client for TypeScript
* @example * @example
* ``` * ```
* const prisma = new PrismaClient() * const prisma = new PrismaClient({
* adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL })
* })
* // Fetch zero or more DiscordUserProfiles * // Fetch zero or more DiscordUserProfiles
* const discordUserProfiles = await prisma.discordUserProfile.findMany() * 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<P extends Prisma.PrismaPromise<any>[]>(arg: [...P], options?: { isolationLevel?: Prisma.TransactionIsolationLevel }): runtime.Types.Utils.JsPromise<runtime.Types.Utils.UnwrapTuple<P>> $transaction<P extends Prisma.PrismaPromise<any>[]>(arg: [...P], options?: { maxWait?: number, timeout?: number, isolationLevel?: Prisma.TransactionIsolationLevel }): runtime.Types.Utils.JsPromise<runtime.Types.Utils.UnwrapTuple<P>>
$transaction<R>(fn: (prisma: Omit<PrismaClient, runtime.ITXClientDenyList>) => runtime.Types.Utils.JsPromise<R>, options?: { maxWait?: number, timeout?: number, isolationLevel?: Prisma.TransactionIsolationLevel }): runtime.Types.Utils.JsPromise<R> $transaction<R>(fn: (prisma: Omit<PrismaClient, runtime.ITXClientDenyList>) => runtime.Types.Utils.JsPromise<R>, options?: { maxWait?: number, timeout?: number, isolationLevel?: Prisma.TransactionIsolationLevel }): runtime.Types.Utils.JsPromise<R>

View file

@ -80,12 +80,12 @@ export type PrismaVersion = {
} }
/** /**
* Prisma Client JS version: 7.3.0 * Prisma Client JS version: 7.8.0
* Query Engine version: 9d6ad21cbbceab97458517b147a6a09ff43aa735 * Query Engine version: 3c6e192761c0362d496ed980de936e2f3cebcd3a
*/ */
export const prismaVersion: PrismaVersion = { export const prismaVersion: PrismaVersion = {
client: "7.3.0", client: "7.8.0",
engine: "9d6ad21cbbceab97458517b147a6a09ff43aa735" engine: "3c6e192761c0362d496ed980de936e2f3cebcd3a"
} }
/** /**
@ -776,6 +776,21 @@ export type PrismaClientOptions = ({
* ``` * ```
*/ */
comments?: runtime.SqlCommenterPlugin[] 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 = { export type GlobalOmitConfig = {
discordUserProfile?: Prisma.DiscordUserProfileOmit discordUserProfile?: Prisma.DiscordUserProfileOmit

View file

@ -140,7 +140,7 @@ export type DiscordGuildProfileGroupByOutputType = {
_max: DiscordGuildProfileMaxAggregateOutputType | null _max: DiscordGuildProfileMaxAggregateOutputType | null
} }
type GetDiscordGuildProfileGroupByPayload<T extends DiscordGuildProfileGroupByArgs> = Prisma.PrismaPromise< export type GetDiscordGuildProfileGroupByPayload<T extends DiscordGuildProfileGroupByArgs> = Prisma.PrismaPromise<
Array< Array<
Prisma.PickEnumerable<DiscordGuildProfileGroupByOutputType, T['by']> & Prisma.PickEnumerable<DiscordGuildProfileGroupByOutputType, T['by']> &
{ {
@ -909,6 +909,11 @@ export type DiscordGuildProfileFindManyArgs<ExtArgs extends runtime.Types.Extens
* Skip the first `n` DiscordGuildProfiles. * Skip the first `n` DiscordGuildProfiles.
*/ */
skip?: number skip?: number
/**
* {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs}
*
* Filter by unique combinations of DiscordGuildProfiles.
*/
distinct?: Prisma.DiscordGuildProfileScalarFieldEnum | Prisma.DiscordGuildProfileScalarFieldEnum[] distinct?: Prisma.DiscordGuildProfileScalarFieldEnum | Prisma.DiscordGuildProfileScalarFieldEnum[]
} }

View file

@ -165,7 +165,7 @@ export type DiscordUserProfileGroupByOutputType = {
_max: DiscordUserProfileMaxAggregateOutputType | null _max: DiscordUserProfileMaxAggregateOutputType | null
} }
type GetDiscordUserProfileGroupByPayload<T extends DiscordUserProfileGroupByArgs> = Prisma.PrismaPromise< export type GetDiscordUserProfileGroupByPayload<T extends DiscordUserProfileGroupByArgs> = Prisma.PrismaPromise<
Array< Array<
Prisma.PickEnumerable<DiscordUserProfileGroupByOutputType, T['by']> & Prisma.PickEnumerable<DiscordUserProfileGroupByOutputType, T['by']> &
{ {
@ -994,6 +994,11 @@ export type DiscordUserProfileFindManyArgs<ExtArgs extends runtime.Types.Extensi
* Skip the first `n` DiscordUserProfiles. * Skip the first `n` DiscordUserProfiles.
*/ */
skip?: number skip?: number
/**
* {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs}
*
* Filter by unique combinations of DiscordUserProfiles.
*/
distinct?: Prisma.DiscordUserProfileScalarFieldEnum | Prisma.DiscordUserProfileScalarFieldEnum[] distinct?: Prisma.DiscordUserProfileScalarFieldEnum | Prisma.DiscordUserProfileScalarFieldEnum[]
} }

View file

@ -1,12 +1,13 @@
import { Routes } from "discord.js"; import { Routes } from "discord.js";
import { DiscordBot } from "./bot"; import { DiscordBot } from "./bot";
import { APPLICATION_ID, DISCORD_TOKEN } from "./env"; import { APPLICATION_ID, DISCORD_TOKEN } from "./env";
import "@qwreey-js/kotlin-scope-func";
export const bot = new DiscordBot(DISCORD_TOKEN); export const bot = new DiscordBot(DISCORD_TOKEN);
bot.client.once("clientReady", async (client) => { bot.client.once("clientReady", async (client) => {
await bot.registerCommands(); await bot.registerCommands();
await bot.registerEvents(); bot.registerEvents();
console.log( console.log(
"registerCommands: \n| " + "registerCommands: \n| " +
@ -20,4 +21,4 @@ bot.client.once("clientReady", async (client) => {
); );
}); });
bot.client.login(DISCORD_TOKEN); await bot.client.login(DISCORD_TOKEN);

View file

@ -4,202 +4,7 @@ import IntegerKorean from "./integerKorean.js";
import PhoneNumberKorean from "./phoneNumberKorean.js"; import PhoneNumberKorean from "./phoneNumberKorean.js";
import EmojiDescriptions from "./emoji-descriptions.json" with { type: "json" }; import EmojiDescriptions from "./emoji-descriptions.json" with { type: "json" };
export const IsolatedSymbolMap = { // Process tailing dots
"?": "물음표",
"!": "느낌표",
"'": "쿼트",
'"': "더블쿼트",
};
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",
);
export function processDots(input: string): string { export function processDots(input: string): string {
return input return input
.replace(/[.,]+$/, "") .replace(/[.,]+$/, "")
@ -207,187 +12,478 @@ export function processDots(input: string): string {
.replace(/[.,]\s/g, " "); .replace(/[.,]\s/g, " ");
} }
export function saferKorean(input: string): string { // Process korean letter, choseong shortens
return ( export function processKorean(input: string): string {
processDots(input.normalize() + " ") input = input.replace(/[아ㅏ]{3,}/g, "아아아");
// Process isolated symbols
.replace(/^[?!'"]+ $/, (total) =>
[...total]
.map(
(element) =>
IsolatedSymbolMap[element as keyof typeof IsolatedSymbolMap],
)
.join(""),
)
.replace(/\s\|\|\s/g, " 오얼 ")
.replace(/\s&&\s/g, " 엔드 ")
// Process codeblock return input.replace(/[ㄱ-ㅎㄲㄸㅃㅆㅉ]+/g, (i) =>
.replace(/```([\s\S]*?)```/g, (_, content: string) => { i
const code = content.substring(0, LangPrefixMaxLength).toLowerCase(); .replace(processKorean.DoubleMixedChoseongMapRegex, (content: string) => {
let lang = ""; // ㅇㅋ => 오키, ㅇㄴ => 아니, ...
for (const [key, value] of Object.entries(LangPrefixes)) { const key = content.substring(
if (code.startsWith(key + "\n")) { 0,
lang = value + " "; 2,
break; ) as keyof typeof processKorean.DoubleMixedChoseongMap;
} const length = Math.min(Math.floor(content.length / 2), 2);
} return processKorean.DoubleMixedChoseongMap[key].repeat(length);
return lang + "코드블럭";
}) })
.replace(processKorean.RepeatedChoseongMapRegex, (content: string) => {
// process ㄴㄴ ㄱㄱ ㅋㅋ ㄷㄷ, ...
const key = (content[0] ??
"") as keyof typeof processKorean.RepeatedChoseongMap;
const item = processKorean.RepeatedChoseongMap[key];
// Process link if (typeof item == "string") {
.replace(/[hH][tT]{2}[pP][sS]?:\/\/(\S+)/g, (_, url: string) => { return item;
const mapped = GIFMap[url as keyof typeof GIFMap] as string | undefined; } else if (typeof item == "function") {
if (mapped) return mapped; return item(content);
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 "고고고";
} }
return 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( .replace(
/[ㄱ-ㅎㄲㄸㅃㅆㅉ]/g, /[ㄱ-ㅎㄲㄸㅃㅆㅉ]/g,
(char: string) => ChoseongMap[char as keyof typeof ChoseongMap], (char: string) =>
) processKorean.ChoseongMap[
char as keyof typeof processKorean.ChoseongMap
// Process number, unit ] ?? char,
.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()
); );
} }
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,]+)(?:(?<prefix>[kKMmgGtTpPeEzZyY][iI]?)(?<unit>[bB])|(?<prefix>[m]?)(?<unit>[lL])|(?<prefix>[mck]?)(?<unit>m))(?<tail>[^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();
}