From 565225dcb92e9fa953fab255a8db2bbe82f06c90 Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Thu, 20 Jul 2023 20:15:14 +0900 Subject: [PATCH] Add: Add role intgration --- .gitignore | 144 +++++++ .../app/controllers/api/discord_controller.rb | 65 ++-- backend/app/models/user.rb | 21 ++ backend/config/application.rb | 2 +- backend/config/routes.rb | 2 +- backend/lib/discord_request.rb | 1 + frontend/i18n.js | 2 +- frontend/i18n/ja.yml | 8 + frontend/pages/discord/error.tsx | 32 ++ tasks/package.json | 23 ++ tasks/pnpm-lock.yaml | 351 ++++++++++++++++++ tasks/src/env.ts | 15 + tasks/src/registerRoleConnection.ts | 16 + tasks/tsconfig.json | 3 + 14 files changed, 661 insertions(+), 24 deletions(-) create mode 100644 frontend/pages/discord/error.tsx create mode 100644 tasks/package.json create mode 100644 tasks/pnpm-lock.yaml create mode 100644 tasks/src/env.ts create mode 100644 tasks/src/registerRoleConnection.ts create mode 100644 tasks/tsconfig.json diff --git a/.gitignore b/.gitignore index 50cbaa5..a40dcfc 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,147 @@ data .env .env.prod rails_master.key +# Created by https://www.toptal.com/developers/gitignore/api/node +# Edit at https://www.toptal.com/developers/gitignore?templates=node + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +### Node Patch ### +# Serverless Webpack directories +.webpack/ + +# Optional stylelint cache + +# SvelteKit build / generate output +.svelte-kit + +# End of https://www.toptal.com/developers/gitignore/api/node diff --git a/backend/app/controllers/api/discord_controller.rb b/backend/app/controllers/api/discord_controller.rb index b3f6ed8..29c009e 100644 --- a/backend/app/controllers/api/discord_controller.rb +++ b/backend/app/controllers/api/discord_controller.rb @@ -8,10 +8,10 @@ def redirect_uri # "HOST", # (Rails.env.development? ? "http://" : "https://") + request.host_with_port # ) + "/api/discord/callback" - "https://example.com/api/discord/callback" + "http://localhost:3100/api/discord/callback" end def scope - %w[identify guilds.join guilds] + %w[identify guilds role_connections.write] end def my_discord require_login! @@ -27,7 +27,7 @@ def my_discord avatar: session.user.discord_avatar } end - def authorize + def link state = SecureRandom.urlsafe_base64(32) $redis.with do |conn| conn.set( @@ -53,15 +53,15 @@ def callback return end - data = - $redis - .with { |conn| conn.get("discord_auth_token/#{params[:state]}") } - &.then { |json| JSON.parse(json, symbolize_names: true) } + # data = + # $redis + # .with { |conn| conn.get("discord_auth_token/#{params[:state]}") } + # &.then { |json| JSON.parse(json, symbolize_names: true) } - unless data && data[:user_id] == session[:user_id] - render json: { error: "Invalid state" }, status: :bad_request - return - end + # unless data && data[:user_id] == session[:user_id] + # render json: { error: "Invalid state" }, status: :bad_request + # return + # end payload = { client_id: ENV["DISCORD_CLIENT_ID"], @@ -69,10 +69,16 @@ def callback grant_type: "authorization_code", code: params[:code], redirect_uri: redirect_uri, - scope: scope.join("+") + scope: scope.join(" ") } - response = $discord.post("/oauth2/token", form: payload) + response = + begin + $discord.post("/oauth2/token", form: payload) + rescue StandardError + redirect_to "/discord/error?code=discord_error" + return + end session_user = User.find_by(id: session[:user_id]) session_user.update!( @@ -83,7 +89,13 @@ def callback $redis.with { |conn| conn.del("discord_auth_token/#{params[:state]}") } - discord_user = session_user.discord.get("/users/@me") + discord_user = + begin + session_user.discord.get("/users/@me") + rescue StandardError + redirect_to "/discord/error?code=discord_error" + return + end session_user.update!( **if discord_user["discriminator"] == "0" { @@ -122,19 +134,30 @@ def callback end ) + member = + begin + $discord.get( + "/guilds/#{ENV["DISCORD_GUILD_ID"]}/members/#{discord_user["id"]}" + ) + rescue RuntimeError + redirect_to "/discord/error?code=notInGuild" + end + begin - $discord.get( - "/guilds/#{ENV["DISCORD_GUILD_ID"]}/members/#{discord_user["id"]}" - ) - rescue RuntimeError - $discord.put( - "/guilds/#{ENV["DISCORD_GUILD_ID"]}/members/#{discord_user["id"]}", + session_user.discord.put( + "/users/@me/applications/#{ENV["DISCORD_CLIENT_ID"]}/role-connection", json: { - access_token: response["access_token"] + "platform_name" => "Chart Cyanvas / Sonolus", + "platform_username" => session_user.to_s } ) + rescue StandardError + redirect_to "/discord/error?code=discordError" end redirect_to "/charts/upload" + rescue StandardError => e + Rails.logger.error e + redirect_to "/discord/error?code=unknown" end end diff --git a/backend/app/models/user.rb b/backend/app/models/user.rb index 1351421..28e32e1 100644 --- a/backend/app/models/user.rb +++ b/backend/app/models/user.rb @@ -29,8 +29,29 @@ def to_frontend } end + def to_s + "#{name}##{handle}" + end + def discord return unless discord_token + refresh_discord_token if discord_expires_at < Time.now @discord ||= DiscordRequest.new(bearer_token: discord_token) end + + def refresh_discord_token + payload = { + client_id: ENV["DISCORD_CLIENT_ID"], + client_secret: ENV["DISCORD_CLIENT_SECRET"], + grant_type: "refresh_token", + refresh_token: discord_refresh_token + } + response = $discord.post("/oauth2/token", form: payload) + + update!( + discord_token: response["access_token"], + discord_refresh_token: response["refresh_token"], + discord_expires_at: Time.now + response["expires_in"].to_i.seconds + ) + end end diff --git a/backend/config/application.rb b/backend/config/application.rb index 18c6d9c..85c4e14 100644 --- a/backend/config/application.rb +++ b/backend/config/application.rb @@ -8,7 +8,7 @@ # you've limited to :test, :development, or :production. Bundler.require(*Rails.groups) -Dotenv.load( +Dotenv.overload( *( %w[../.env ../.env.local ../../.env ../../.env.local].map do |f| __dir__ + "/" + f diff --git a/backend/config/routes.rb b/backend/config/routes.rb index c246769..fec6a91 100644 --- a/backend/config/routes.rb +++ b/backend/config/routes.rb @@ -41,7 +41,7 @@ post "/admin/reconvert_sus", to: "api/admin#reconvert_sus" get "/my/discord", to: "api/discord#my_discord" - get "/discord/authorize", to: "api/discord#authorize" + get "/discord/link", to: "api/discord#link" get "/discord/callback", to: "api/discord#callback" end diff --git a/backend/lib/discord_request.rb b/backend/lib/discord_request.rb index cf9441b..653614b 100644 --- a/backend/lib/discord_request.rb +++ b/backend/lib/discord_request.rb @@ -48,6 +48,7 @@ def request(method, path, **options) end end + Rails.logger.info("Discord: #{method.to_s.upcase} #{path}") response = HTTP.request(method, "https://discord.com/api/v10#{path}", **options) diff --git a/frontend/i18n.js b/frontend/i18n.js index 02ed105..dc9a825 100644 --- a/frontend/i18n.js +++ b/frontend/i18n.js @@ -30,7 +30,7 @@ module.exports = { "/users/[handle]": ["user"], "/users/alts": ["myAlts"], - "/discord/callback": ["discordCallback"], + "/discord/error": ["discordError"], "/admin": ["admin"], }, diff --git a/frontend/i18n/ja.yml b/frontend/i18n/ja.yml index df1ee72..1a3e6c0 100644 --- a/frontend/i18n/ja.yml +++ b/frontend/i18n/ja.yml @@ -188,3 +188,11 @@ myAlts: errors: tooShort: 名前が短すぎます。 tooLong: 名前が長すぎます。 + +discordError: + title: 連携エラー + description: Discordとの連携に失敗しました({{code}})。 + error: + discordError: Discordとの通信に失敗しました + notInGuild: サーバーに参加していません + unknown: 不明なエラーが発生しました diff --git a/frontend/pages/discord/error.tsx b/frontend/pages/discord/error.tsx new file mode 100644 index 0000000..4f45757 --- /dev/null +++ b/frontend/pages/discord/error.tsx @@ -0,0 +1,32 @@ +import type { NextPage } from "next" + +import Head from "next/head" +import useTranslation from "next-translate/useTranslation" + +import { useEffect, useRef } from "react" + +const DiscordError: NextPage = () => { + const { t } = useTranslation("discordError") + const { t: rootT } = useTranslation() + + const code = useRef("") + + useEffect(() => { + code.current = + new URLSearchParams(window.location.search).get("code") ?? "unknown" + }, [code]) + + return ( +
+ + {t("title") + " | " + rootT("name")} + +
+

{t("title")}

+

{t("description", { code: t(`error.${code.current}`) })}

+
+
+ ) +} + +export default DiscordError diff --git a/tasks/package.json b/tasks/package.json new file mode 100644 index 0000000..ee7ddda --- /dev/null +++ b/tasks/package.json @@ -0,0 +1,23 @@ +{ + "name": "tasks", + "version": "1.0.0", + "description": "", + "scripts": { + "registerRoleConnection": "ts-node ./src/registerRoleConnection.ts", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "MIT", + "devDependencies": { + "@tsconfig/node16": "16.1.0", + "discord-api-types": "0.37.48", + "ts-node": "10.9.1", + "typescript": "5.1.6" + }, + "dependencies": { + "@discordjs/rest": "1.7.1", + "axios": "1.4.0", + "dotenv": "16.3.1" + } +} diff --git a/tasks/pnpm-lock.yaml b/tasks/pnpm-lock.yaml new file mode 100644 index 0000000..c6a2a6a --- /dev/null +++ b/tasks/pnpm-lock.yaml @@ -0,0 +1,351 @@ +lockfileVersion: '6.1' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +dependencies: + '@discordjs/rest': + specifier: 1.7.1 + version: 1.7.1 + axios: + specifier: 1.4.0 + version: 1.4.0 + dotenv: + specifier: 16.3.1 + version: 16.3.1 + +devDependencies: + '@tsconfig/node16': + specifier: 16.1.0 + version: 16.1.0 + discord-api-types: + specifier: 0.37.48 + version: 0.37.48 + ts-node: + specifier: 10.9.1 + version: 10.9.1(@types/node@20.4.2)(typescript@5.1.6) + typescript: + specifier: 5.1.6 + version: 5.1.6 + +packages: + + /@cspotcode/source-map-support@0.8.1: + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + dev: true + + /@discordjs/collection@1.5.1: + resolution: {integrity: sha512-aWEc9DCf3TMDe9iaJoOnO2+JVAjeRNuRxPZQA6GVvBf+Z3gqUuWYBy2NWh4+5CLYq5uoc3MOvUQ5H5m8CJBqOA==} + engines: {node: '>=16.9.0'} + dev: false + + /@discordjs/rest@1.7.1: + resolution: {integrity: sha512-Ofa9UqT0U45G/eX86cURQnX7gzOJLG2oC28VhIk/G6IliYgQF7jFByBJEykPSHE4MxPhqCleYvmsrtfKh1nYmQ==} + engines: {node: '>=16.9.0'} + dependencies: + '@discordjs/collection': 1.5.1 + '@discordjs/util': 0.3.1 + '@sapphire/async-queue': 1.5.0 + '@sapphire/snowflake': 3.5.1 + discord-api-types: 0.37.48 + file-type: 18.5.0 + tslib: 2.6.0 + undici: 5.22.1 + dev: false + + /@discordjs/util@0.3.1: + resolution: {integrity: sha512-HxXKYKg7vohx2/OupUN/4Sd02Ev3PBJ5q0gtjdcvXb0ErCva8jNHWfe/v5sU3UKjIB/uxOhc+TDOnhqffj9pRA==} + engines: {node: '>=16.9.0'} + dev: false + + /@jridgewell/resolve-uri@3.1.1: + resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} + engines: {node: '>=6.0.0'} + dev: true + + /@jridgewell/sourcemap-codec@1.4.15: + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + dev: true + + /@jridgewell/trace-mapping@0.3.9: + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + dependencies: + '@jridgewell/resolve-uri': 3.1.1 + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + + /@sapphire/async-queue@1.5.0: + resolution: {integrity: sha512-JkLdIsP8fPAdh9ZZjrbHWR/+mZj0wvKS5ICibcLrRI1j84UmLMshx5n9QmL8b95d4onJ2xxiyugTgSAX7AalmA==} + engines: {node: '>=v14.0.0', npm: '>=7.0.0'} + dev: false + + /@sapphire/snowflake@3.5.1: + resolution: {integrity: sha512-BxcYGzgEsdlG0dKAyOm0ehLGm2CafIrfQTZGWgkfKYbj+pNNsorZ7EotuZukc2MT70E0UbppVbtpBrqpzVzjNA==} + engines: {node: '>=v14.0.0', npm: '>=7.0.0'} + dev: false + + /@tokenizer/token@0.3.0: + resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + dev: false + + /@tsconfig/node10@1.0.9: + resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} + dev: true + + /@tsconfig/node12@1.0.11: + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + dev: true + + /@tsconfig/node14@1.0.3: + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + dev: true + + /@tsconfig/node16@1.0.4: + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + dev: true + + /@tsconfig/node16@16.1.0: + resolution: {integrity: sha512-cfwhqrdZEKS+Iqu1OPDwmKsOV/eo7q4sPhWzOXc1rU77nnPFV3+77yPg8uKQ2e8eir6mERCvrKnd+EGa4qo4bQ==} + dev: true + + /@types/node@20.4.2: + resolution: {integrity: sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw==} + dev: true + + /acorn-walk@8.2.0: + resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} + engines: {node: '>=0.4.0'} + dev: true + + /acorn@8.10.0: + resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + + /arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + dev: true + + /asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: false + + /axios@1.4.0: + resolution: {integrity: sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==} + dependencies: + follow-redirects: 1.15.2 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + dev: false + + /busboy@1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} + dependencies: + streamsearch: 1.1.0 + dev: false + + /combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + dev: false + + /create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + dev: true + + /delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dev: false + + /diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + dev: true + + /discord-api-types@0.37.48: + resolution: {integrity: sha512-vu2NQJD7SZRjpKDC2DPNsxTz34KS53OrotA+LGRW6mcyT55Hjqu66aRrouzjYhea7tllL9I7rvWVX7bg3aT2AQ==} + + /dotenv@16.3.1: + resolution: {integrity: sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==} + engines: {node: '>=12'} + dev: false + + /file-type@18.5.0: + resolution: {integrity: sha512-yvpl5U868+V6PqXHMmsESpg6unQ5GfnPssl4dxdJudBrr9qy7Fddt7EVX1VLlddFfe8Gj9N7goCZH22FXuSQXQ==} + engines: {node: '>=14.16'} + dependencies: + readable-web-to-node-stream: 3.0.2 + strtok3: 7.0.0 + token-types: 5.0.1 + dev: false + + /follow-redirects@1.15.2: + resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + dev: false + + /form-data@4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: false + + /ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + dev: false + + /inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + dev: false + + /make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + dev: true + + /mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + dev: false + + /mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 + dev: false + + /peek-readable@5.0.0: + resolution: {integrity: sha512-YtCKvLUOvwtMGmrniQPdO7MwPjgkFBtFIrmfSbYmYuq3tKDV/mcfAhBth1+C3ru7uXIZasc/pHnb+YDYNkkj4A==} + engines: {node: '>=14.16'} + dev: false + + /proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + dev: false + + /readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + dev: false + + /readable-web-to-node-stream@3.0.2: + resolution: {integrity: sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==} + engines: {node: '>=8'} + dependencies: + readable-stream: 3.6.2 + dev: false + + /safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + dev: false + + /streamsearch@1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + dev: false + + /string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + dependencies: + safe-buffer: 5.2.1 + dev: false + + /strtok3@7.0.0: + resolution: {integrity: sha512-pQ+V+nYQdC5H3Q7qBZAz/MO6lwGhoC2gOAjuouGf/VO0m7vQRh8QNMl2Uf6SwAtzZ9bOw3UIeBukEGNJl5dtXQ==} + engines: {node: '>=14.16'} + dependencies: + '@tokenizer/token': 0.3.0 + peek-readable: 5.0.0 + dev: false + + /token-types@5.0.1: + resolution: {integrity: sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg==} + engines: {node: '>=14.16'} + dependencies: + '@tokenizer/token': 0.3.0 + ieee754: 1.2.1 + dev: false + + /ts-node@10.9.1(@types/node@20.4.2)(typescript@5.1.6): + resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.9 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.4.2 + acorn: 8.10.0 + acorn-walk: 8.2.0 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.1.6 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + dev: true + + /tslib@2.6.0: + resolution: {integrity: sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==} + dev: false + + /typescript@5.1.6: + resolution: {integrity: sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==} + engines: {node: '>=14.17'} + hasBin: true + dev: true + + /undici@5.22.1: + resolution: {integrity: sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw==} + engines: {node: '>=14.0'} + dependencies: + busboy: 1.6.0 + dev: false + + /util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + dev: false + + /v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + dev: true + + /yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + dev: true diff --git a/tasks/src/env.ts b/tasks/src/env.ts new file mode 100644 index 0000000..66cabf5 --- /dev/null +++ b/tasks/src/env.ts @@ -0,0 +1,15 @@ +import { config } from "dotenv"; + +config({ path: "../.env" }); + +const getEnv = (name: string): string => { + const value = process.env[name]; + if (!value) { + throw new Error(`Missing environment variable: ${name}`); + } + return value; +}; + +export const token = getEnv("DISCORD_TOKEN"); +export const clientId = getEnv("DISCORD_CLIENT_ID"); +export const guildId = getEnv("DISCORD_GUILD_ID"); diff --git a/tasks/src/registerRoleConnection.ts b/tasks/src/registerRoleConnection.ts new file mode 100644 index 0000000..7ff41a8 --- /dev/null +++ b/tasks/src/registerRoleConnection.ts @@ -0,0 +1,16 @@ +import { REST } from "@discordjs/rest"; +import { + ApplicationRoleConnectionMetadataType, + RESTPutAPIApplicationRoleConnectionMetadataJSONBody, + Routes, +} from "discord-api-types/v10"; +import { clientId, token } from "./env"; +import "dotenv/config"; + +const rest = new REST({ version: "10" }).setToken(token); + +const registerRoleConnection = async () => { + rest.put(Routes.applicationRoleConnectionMetadata(clientId), { + body: [] satisfies RESTPutAPIApplicationRoleConnectionMetadataJSONBody, + }); +}; diff --git a/tasks/tsconfig.json b/tasks/tsconfig.json new file mode 100644 index 0000000..e9c0baf --- /dev/null +++ b/tasks/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "@tsconfig/node16/tsconfig.json" +}