├── .github ├── FUNDING.yml └── workflows │ └── docker-image.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── Dockerfile ├── LICENSE.md ├── README.md ├── config.js ├── embeds.js ├── lang.json ├── package.json └── tempVCs.js /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: larshagrid 2 | 3 | -------------------------------------------------------------------------------- /.github/workflows/docker-image.yml: -------------------------------------------------------------------------------- 1 | name: Docker Image CI 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | jobs: 10 | 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout Code 16 | uses: actions/checkout@v2 17 | 18 | - name: Login to GitHub Container Registry 19 | uses: docker/login-action@v1 20 | with: 21 | registry: ghcr.io 22 | username: ${{ github.actor }} 23 | password: ${{ secrets.GITHUB_TOKEN }} 24 | 25 | - name: Build and Push Docker Image 26 | run: | 27 | docker build . --tag ghcr.io/interfacegui/discord-temp-vc-bot:latest 28 | docker push ghcr.io/interfacegui/discord-temp-vc-bot:latest 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | .config.json 106 | index copy.js 107 | db/ 108 | package-lock.json 109 | config.js 110 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # 貢獻者公約 3 | 4 | ## 我們的承諾 5 | 6 | 為了促進一個開放透明且受歡迎的環境,我們作為貢獻者和維護者保證,無論年齡、種族、民族、性別認同和表達、體型、殘疾、經驗水平、國籍、個人表現、宗教或性別取向,在我們的專案以及社群的參與者都有不被騷擾的體驗。 7 | 8 | ## 我們的準則 9 | 10 | 舉例來說有助於創造正面環境的行為包括: 11 | 12 | * 使用歡迎和包容性語言 13 | * 尊重不同的觀點和經驗 14 | * 優雅地接受建設性批評 15 | * 關注在對於社群最好的事情上 16 | * 對其他社群成員的表現友善 17 | 18 | 舉例來說身為參與者不能接受的行為包括: 19 | 20 | * 使用與性有關的言語或是圖像,以及不受歡迎的性騷擾 21 | * 酸民/反串/釣魚行為或進行侮辱/貶損的評論,人身攻擊及政治攻擊 22 | * 公開或私下的騷擾 23 | * 未經許可地發布他人的個人資料,例如住址或是電子地址 24 | * 其他可以被合理地認定為不恰當或者違反職業操守的行為 25 | 26 | ## 我們的責任 27 | 28 | 專案維護者有責任為"可接受的行為"準則做出詮釋,以及對已發生的不被接受的行為採取恰當且公平的糾正措施。 29 | 30 | 專案維護者有權力及責任去刪除、編輯、拒絕與本行為準則有所違背的評論 (comments)、提交 (commits)、程式碼、wiki 編輯、問題 (issues) 和其他貢獻,以及專案維護者可暫時或永久性的禁止任何他們認為有不適當、威脅、冒犯、有害行為的貢獻者。 31 | 32 | ## 使用範圍 33 | 34 | 當一個人代表該專案或是其社群時,本行為準則適用於其專案平台和公共平台。 35 | 36 | 代表專案或是社群的情況,舉例來說包括使用官方專案的電子郵件地址、通過官方的社群媒體帳號發布或線上或線下事件中擔任指定代表。 37 | 38 | 該專案的呈現方式可由其專案維護者進行進一步的定義及解釋。 39 | 40 | ## 強制執行 41 | 42 | 可以透過[interfacegui@gmail.com],來聯繫專案團隊來報告濫用、騷擾或其他不被接受的行為。 43 | 44 | 任何維護團隊認為有必要且適合的所有投訴都將進行審查及調查,並做出相對應的回應。專案小組有對事件回報者有保密的義務。具體執行的方針近一步細節可能會單獨公佈。 45 | 46 | 沒有真誠的遵守或是執行本行為準則的專案維護人員,可能會因專案領導人或是其他成員的決定,暫時或是永久的取消其身份。 47 | 48 | ## 來源 49 | 50 | 本行為準則改編自[貢獻者公約][首頁],版本 1.4 51 | 可在此觀看https://www.contributor-covenant.org/zh-tw/version/1/4/code-of-conduct.html 52 | 53 | [首頁]: https://www.contributor-covenant.org 54 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18 2 | 3 | WORKDIR /app 4 | 5 | COPY package.json /app 6 | 7 | COPY . . 8 | 9 | RUN npm install 10 | CMD [ "node", "tempVCs.js" ] 11 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Larshagrid 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
中文說明 2 | 3 | # Discord 語音包廂 BOT 4 | 5 | ## 概述 6 | 想要在Discord裡面有個不被打擾或是不想給別人加入的語音頻道 7 | 但是又不想麻煩管理員為了你而特別開設頻道? 8 | 或是語音地區出問題? 想更換但管理員都不再線上 9 | 又或者有破壞規矩、故意騷擾的用戶在,想要將他踢出語音頻道? 10 | 11 | 這Bot剛好可以達成您的需求! 12 | 私人語音包廂可以讓一般使用者無需特別權限就能建立語音頻道 13 | 14 | 並且有獨立的控制專區,可以讓開房的房主做到: 15 | 1. 踢人 16 | 2. Ban人(對某人隱藏語音頻道) 17 | 3. 隱藏頻道 18 | 4. 頻道上鎖 (看的到 但無法加入) 19 | 5. 自訂頻道名稱 20 | 6. 更改語音地區 21 | 7. 白名單 22 | 8. 限制人數 23 | 9. 禁音其他人 (對後來加入的有效) 24 | 25 | 對伺服器主人有: 26 | 1. 限定預設身分組 27 | 2. 一鍵清除所有包廂 28 | 3. 房主離開超過3分鐘自動清除語音頻道 29 | 4. Log紀錄 30 | 31 | 32 | #### 完全無需輸入指令! 33 | 除了特定指令(管理員刪除、呼叫控制台) 34 | 其餘都是使用 Discord 互動系統 (按鈕、表單、選單) 35 | 36 | ## 如何安裝及啟動? 37 | 38 | 1. 下載原始碼 39 | 2. 完成 `config.json` 所需要的資料 ( Token、guild、HUBvcChannelID、DefaultRoleID、categoryID、owners ) 40 | 2. 安裝必要元件 `npm install` 41 | 3. 啟動bot `node tempVCs.js` 42 | 43 | 44 | 45 | ## 大感謝 46 | 47 | 靈感來自Autocode 平台上的 MeltedButter77 所做的 [Temp VCs](https://autocode.com/MeltedButter77/apps/tempvoice/) 48 | 49 |


50 | 51 |
52 | 53 | 54 | [1]:https://github.com/InterfaceGUI/Discord-TempVoice-Bot/actions/workflows/docker-image.yml/badge.svg?branch=master 55 | [2]:https://img.shields.io/github/license/interfacegui/Discord-TempVoice-Bot 56 | [3]:https://img.shields.io/github/package-json/v/interfacegui/Discord-TempVoice-Bot 57 | 58 | # Discord Temp Voice Channel Bot 59 | 60 | 61 | [![Docker Image CI][1]](https://github.com/InterfaceGUI/Discord-TempVoice-Bot/actions/workflows/docker-image.yml) ![GitHub][2] ![GitHub package.json version][3] 62 | 63 | This BOT allows the average user to create channels, edit names, numbers, visibility,etc. 64 | without the need for administrator intervention! 65 | 66 | And with the independent control interface, users can do: 67 | 1. Kick the user out of the voice channel. 68 | 2. Block the user from the voice channel. 69 | 3. Set a maximum number of users. 70 | 4. Change the region of the voice channel. 71 | 5. Change the name of the voice channel. 72 | 6. Set a whitelist. 73 | 7. Mute new users who join after the mute has been applied. 74 | 8. Hide the channel. 75 | 9. Lock the channel. 76 | 77 | Server owners can: 78 | 1. Set the default role. 79 | 2. Clear all temp vcs 80 | 3. Log 81 | 82 | And all temp vc will automatically clear the voice channel when the user leaves for more than 3 minutes 83 | 84 | 85 | ## Install 86 | 87 | 1. Download Repository 88 | 2. Fill in the necessary information in `config.json`
( Token、guild、HUBvcChannelID、DefaultRoleID、categoryID、owners ) 89 | 2. `npm install` 90 | 3. `node tempVCs.js` 91 | 92 | 93 | This program is inspired by [Temp VCs](https://autocode.com/MeltedButter77/apps/tempvoice/) made by MeltedButter77 on the Autocode platform 94 | 95 | 96 | ## Docker compose 97 | ```yml 98 | version: "3.9" 99 | services: 100 | DiscordWelcomeBot: 101 | image: "ghcr.io/interfacegui/discord-temp-vc-bot:latest" 102 | environment: 103 | TOKEN: "YOUR Discord TOKEN" 104 | SERVER_ID: "YOUR Discord ServerID" 105 | CATEGORY_ID: "Your category ID" 106 | HUB_ID: "Your HUB Voice Channel ID" 107 | ROLE_ID: "Defualt role | if you use @everyone pls put server id here" 108 | PREFIX: "[" 109 | OWNERS: "123456,123456 " 110 | ``` 111 | 112 | ### Environmental Variables 113 | 114 | * `TOKEN` 115 | Discord bot token 116 | 117 | * `SERVER_ID` 118 | Your server ID 119 | 120 | * `CATEGORY_ID` 121 | Category ID 122 | 123 | * `HUB_ID` 124 | Hub voice channel ID
125 | 126 | * `ROLE_ID` 127 | Defualt role
128 | VC Show/Lock will use this role
129 | If you want to use `@everyone` please place the ServerID here 130 | 131 | * `PREFIX` 132 | The command perfix to create Hub text. 133 | 134 | * `OWNERS` 135 | Administrator ID 136 | If there are more than one, please separate them with a comma(`,`)
137 | Note: There must not be any spaces! 138 | 139 | ## Demo 140 | 141 | ![Create VCs](https://media.discordapp.net/attachments/920732721981038712/1015285658857775154/VC1.gif) 142 | 143 | ![show/hide](https://media.discordapp.net/attachments/920732721981038712/1015285659105230858/VC2.gif?width=512&height=371) 144 | 145 | ![Lock](https://media.discordapp.net/attachments/920732721981038712/1015285659352711268/VC3.gif?width=512&height=371) 146 | 147 | ![ChangeRTC](https://cdn.discordapp.com/attachments/920732721981038712/1015291018880487444/VC4s.gif) 148 | 149 | ![ChangeOwner](https://media.discordapp.net/attachments/920732721981038712/1015285660158005460/VC5.gif?width=512&height=371) 150 | 151 | ![deleteVC](https://media.discordapp.net/attachments/920732721981038712/1015285660451622983/VC6.gif?width=512&height=371) 152 | 153 | ![deleteVConDisconnect](https://media.discordapp.net/attachments/920732721981038712/1015285660824895588/VC7.gif?width=512&height=371) 154 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | var config = { 2 | "token": "TOKEN", 3 | "guild": "SERVER ID", 4 | "HUBtxtChannelID": "leaveBlank", 5 | "HUBvcChannelID": "", 6 | "DefaultRoleID": "", 7 | "categoryID": "", 8 | "status": "", 9 | "enable_slash": true, 10 | "prefix": "]", 11 | "owners": [ 12 | "" 13 | ] 14 | } 15 | //use env 16 | config.token = process.env.TOKEN || '' 17 | config.guild = process.env.SERVER_ID || '' 18 | config.HUBvcChannelID = process.env.HUB_ID || '' 19 | config.DefaultRoleID = process.env.ROLE_ID || '' 20 | config.categoryID = process.env.CATEGORY_ID || '' 21 | config.prefix = process.env.PREFIX || ']' 22 | config.owners = process.env.OWNERS ? process.env.OWNERS.split(',') : null || [''] 23 | 24 | module.exports = { config } 25 | -------------------------------------------------------------------------------- /embeds.js: -------------------------------------------------------------------------------- 1 | const { Client, PermissionsBitField, Permissions, TextInputComponent, MessageSelectMenu, UserSelectMenuBuilder, GatewayIntentBits, SlashCommandBuilder, PermissionFlagsBits, ChannelType, RoleSelectMenuBuilder, ButtonBuilder, ActionRowBuilder, EmbedBuilder } = require('discord.js') 2 | const { config } = require('./config') 3 | 4 | 5 | /*--------------------------------------------------------------------------------------------------------- 6 | * The BOT was created by lars. I hope those who use this BOT will keep the flooter part. 7 | * The Author can be changed at will. If there are any improvements needed, please open a Pullrequest on Github. 8 | * Thank you. 9 | */ 10 | const flooter = { 11 | "text": "TempVCs by Larshagrid | ver 2023.12.18", 12 | "iconURL": "https://cdn.discordapp.com/attachments/920732721981038712/986987686751506512/-1.jpg" 13 | } 14 | //---------------------------------------------------------------------------------------------------------- 15 | 16 | const Author = { 17 | name: "拉斯的私人語音包廂助理", 18 | iconURL: "https://cdn.discordapp.com/attachments/920732721981038712/986987686751506512/-1.jpg" 19 | } 20 | 21 | const HubEmbed = new EmbedBuilder() 22 | .setTitle(`臨時私人語音聊天`) 23 | .setColor(0x00B9FF) 24 | .setDescription( 25 | `加入 <#${config.HUBvcChannelID}> 來創建您自己的Temp VC,您將成為房主。 預設為顯示,除非手動點選按鈕開啟權限!`, 26 | ) 27 | .setFooter(flooter) 28 | 29 | const AdminForceDelete = new EmbedBuilder() 30 | .setTitle(`管理員以使用修復指令!`) 31 | .setDescription(`頻道將於 $timeLeft 刪除。`) 32 | .setColor(0xe80000) 33 | .setFooter(flooter) 34 | 35 | const consoleembed = new EmbedBuilder() 36 | .setTitle("臨時私人語音聊天控制") 37 | .setDescription("**使用下面的按鈕可以快速更改一些設置!**. \n以下是按鈕的說明:\n\n使用 `Lock/Unlock` 鎖定 VC,以便只有列入白名單的用戶才能連接.\n使用 `Hide/Unhide` 將 VC 設為私有,以便只有列入白名單的用戶才能看到 VC。\n使用 `Mute/Unmute` 只允許白名單用戶發言,只影響新加入的用戶.\n使用 `Ban/Unban` 禁止指定用戶加入VC. \n使用 `Whitelist/Remove` 白名單.\n使用 `Limit` 限制可以加入的用戶數量.\n使用 `Change Owner` 轉讓包廂房主.\n使用 `Change Name` 將 VC 的名稱更改為您想要的任何名稱。 (記得遵守規則)\n使用 `Get Mention` 獲取語音邀請網址,立即邀請他們加入到語音頻道!!\n\n 轉讓房主會將原先的限制權限清除!!。") 38 | .setColor(0x00B9FF) 39 | .setFooter(flooter) 40 | 41 | const Whitelisttembed = new EmbedBuilder() 42 | .setTitle('白名單清單') 43 | .setColor(0xaf40de) 44 | .setAuthor(Author) 45 | 46 | const banliste = new EmbedBuilder() 47 | .setTitle('封鎖清單') 48 | .setColor(0xaf40de) 49 | .setAuthor(Author) 50 | 51 | const predelete_Cancel = new EmbedBuilder() 52 | .setTitle("刪除已取消!") 53 | .setDescription(`排定的刪除程序已取消!`) 54 | .setColor(0x02cf21) 55 | .setAuthor(Author) 56 | 57 | const predelete = new EmbedBuilder() 58 | .setTitle("已排定刪除!") 59 | .setDescription(`房主決定將 $tempVcId 刪除。 \n頻道將於 $timeLeft 刪除。 \n如要取消,請再次按下DeleteVC按鈕。`) 60 | .setColor(0xff0000) 61 | .setAuthor(Author) 62 | 63 | const ownerLeave = new EmbedBuilder() 64 | .setTitle("房主已離開語音包廂") 65 | .setDescription(`房主已離開 $tempVcId。 \n頻道將於 $timeLeft 刪除。 \n如要取消,請重新加入語音頻道。`) 66 | .setColor(0x00B9FF) 67 | .setFooter(flooter) 68 | .setAuthor(Author) 69 | const ownerLeavegivenext = new EmbedBuilder() 70 | .setTitle("房主已離開語音包廂") 71 | .setDescription(`房主已離開 $tempVcId。 \n房主將於 $timeLeft 賦予下一位用戶,若無人將自動刪除。 \n如要取消,請重新加入語音頻道。`) 72 | .setColor(0x00B9FF) 73 | .setFooter(flooter) 74 | .setAuthor(Author) 75 | 76 | const NewOwner = new EmbedBuilder() 77 | .setTitle("新房主!") 78 | .setDescription(`前個房主已離開 3 分鐘。 \n此頻道以自動延順房主給: $user 。 \n\n如找不到控制台,請使用 $command `) 79 | .setColor(0x00B9FF) 80 | .setFooter(flooter) 81 | .setAuthor(Author) 82 | 83 | 84 | module.exports = { 85 | embedFlooter: flooter, 86 | template_createHubEmbed: HubEmbed, 87 | template_adminDeleteEmbed: AdminForceDelete, 88 | template_controlsEmbed: consoleembed, 89 | template_whitelist_Embed: Whitelisttembed, 90 | template_banlist_Embed: banliste, 91 | template_CancelPreDelete_Embed: predelete_Cancel, 92 | template_predelete_Embed: predelete, 93 | template_ownerLeave_Embed: ownerLeave, 94 | template_ownerLeaveNext_Embed: ownerLeavegivenext, 95 | template_ownerNew_Embed: NewOwner, 96 | } -------------------------------------------------------------------------------- /lang.json: -------------------------------------------------------------------------------- 1 | { 2 | "commands": { 3 | "vc-fix": { 4 | "en-US": "Fix tempvc database(Will delete all tempvc)", 5 | "zh-TW": "修復語音包廂" 6 | }, 7 | "vc-hubmsg":{ 8 | "en-US": "Send HUB message to current channel.", 9 | "zh-TW": "傳送HUB訊息" 10 | }, 11 | "vc-control":{ 12 | "en-US": "Show tempVC control UI.", 13 | "zh-TW": "顯示包廂控制介面" 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "discord_temp_vcs", 3 | "version": "1.1.1", 4 | "description": "Discord Temp voice bot, that makes voice channel easy to create, manage, and change regions.", 5 | "main": "tempVCs.js", 6 | "scripts": { 7 | "test": "node tempVCs.js", 8 | "start": "node tempVCs.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/InterfaceGUI/DiscordTempVCs.git" 13 | }, 14 | "keywords": [ 15 | "Discord_Bot", 16 | "Discord-js-v14", 17 | "Discord_Buttons", 18 | "TempVCs", 19 | "24/7_Online", 20 | "Button_interactions" 21 | ], 22 | "author": "Larshagrid", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/InterfaceGUI/DiscordTempVCs/issues" 26 | }, 27 | "homepage": "https://github.com/InterfaceGUI/DiscordTempVCs", 28 | "dependencies": { 29 | "@discordjs/rest": "^2.1.0", 30 | "discord-api-types": "^0.37.61", 31 | "discord.js": "^14.11.1", 32 | "level": "^8.0.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tempVCs.js: -------------------------------------------------------------------------------- 1 | //Discord Client 2 | const { Client, PermissionsBitField, Permissions, TextInputBuilder, TextInputStyle, UserSelectMenuBuilder, GatewayIntentBits, SlashCommandBuilder, PermissionFlagsBits, ChannelType, RoleSelectMenuBuilder, ButtonBuilder, ActionRowBuilder, EmbedBuilder, ModalBuilder, StringSelectMenuBuilder } = require('discord.js') 3 | 4 | const client = new Client({ 5 | intents: [ 6 | GatewayIntentBits.GuildMembers, 7 | GatewayIntentBits.GuildEmojisAndStickers, 8 | GatewayIntentBits.Guilds, 9 | GatewayIntentBits.GuildVoiceStates] 10 | }) 11 | const lang = require('./lang.json') 12 | const { REST, RequestData } = require('@discordjs/rest'); 13 | const { Routes, ButtonStyle } = require('discord-api-types/v10'); 14 | 15 | const { config } = require('./config') 16 | const { 17 | template_banlist_Embed, 18 | template_createHubEmbed, 19 | template_adminDeleteEmbed, 20 | template_controlsEmbed, 21 | template_whitelist_Embed, 22 | template_ownerNew_Embed, 23 | template_CancelPreDelete_Embed, 24 | template_predelete_Embed, 25 | template_ownerLeave_Embed, 26 | template_ownerLeaveNext_Embed 27 | } = require('./embeds.js') 28 | 29 | console.log(`Config Loaded prefix: ${config.prefix}`) 30 | var owners = config.owners 31 | 32 | //import LevelUP levelDown 33 | const { Level } = require('level') 34 | const db = new Level('./db', { valueEncoding: 'json' }) 35 | 36 | var guild; 37 | var RTCRegions; 38 | var VC_control_command 39 | 40 | //房主離開用 41 | var removeTimeouts = {}; 42 | var removeTimeoutsMsg = {}; 43 | 44 | var removeTimeouts2 = {}; 45 | var removeTimeoutsMsg2 = {}; 46 | 47 | 48 | //Ready Event 49 | client.on('ready', async () => { 50 | console.log(`${client.user.tag} is Ready!`) 51 | 52 | /* custom status 53 | client.user.setPresence({ 54 | status: "online", 55 | activities: [{ 56 | name: config.status, 57 | type: "LISTENING", 58 | }] 59 | }) 60 | */ 61 | 62 | RTCRegions = await client.fetchVoiceRegions() 63 | guild = await client.guilds.cache.get(config.guild); 64 | await guild.members.fetch() 65 | await guild.roles.fetch() 66 | 67 | //Registering Slash 68 | if (config.enable_slash) { 69 | const rest = new REST({ version: '10' }).setToken(config.token); 70 | 71 | //const rest = new REST({ version: '9' }).setToken(config.token) 72 | const cmd1 = new SlashCommandBuilder() 73 | .setName('vc-fix') 74 | .setDescription('修復語音包廂') 75 | .setDescriptionLocalizations(lang.commands['vc-fix']) 76 | .setDefaultMemberPermissions(PermissionFlagsBits.Administrator) 77 | const cmd2 = new SlashCommandBuilder() 78 | .setName('vc-hubmsg') 79 | .setDescription('建立HUB訊息') 80 | .setDescriptionLocalizations(lang.commands['vc-hubmsg']) 81 | .setDefaultMemberPermissions(PermissionFlagsBits.Administrator) 82 | const cmd3 = new SlashCommandBuilder() 83 | .setName('vc-control') 84 | .setDescription('呼叫主控台') 85 | .setDescriptionLocalizations(lang.commands['vc-control']) 86 | const commands = [cmd1.toJSON(), cmd2.toJSON(), cmd3.toJSON()] 87 | 88 | try { 89 | console.log('[INFO] Started refreshing application (/) commands.') 90 | 91 | await rest.put( 92 | Routes.applicationCommands(client.user.id), 93 | { body: commands }, 94 | ); 95 | const commands11 = await rest.get( 96 | Routes.applicationCommands(client.user.id) 97 | ); 98 | 99 | VC_control_command = commands11.find(cmd => cmd.name === cmd3.name); 100 | console.log('[INFO] Successfully reloaded application (/) commands.') 101 | } 102 | catch (error) { 103 | console.error(error) 104 | } 105 | 106 | } 107 | cleanHUB_Timeban() 108 | 109 | }) 110 | 111 | client.on('voiceStateUpdate', async (oldMember, newMember) => { 112 | 113 | let newUserChannel = newMember.channelId; 114 | let userid = newMember.id; 115 | 116 | /* 117 | console.log(newMember) 118 | console.log('User:',userid) 119 | console.log('channel:',newUserChannel); 120 | console.log('=======') 121 | */ 122 | 123 | let tempVcId = await db.get(`tempVcIdKey_${userid}`).catch(err => { }) //= await kvs1.get('tempVcIdKey_'+userid); 124 | let tempTimestamp = await db.get(`tempTimestampKey_${userid}`).catch(err => { }) //= await kvs1.get('tempTimestampKey_'+userid); 125 | 126 | if (newUserChannel == config.HUBvcChannelID) { 127 | if (typeof tempVcId === 'undefined') { 128 | setTimeout(MoveUser, 1000, userid, newMember) 129 | return 130 | } 131 | } 132 | 133 | if (typeof tempVcId === 'undefined') return 134 | /* /// 房主離開 自動刪除頻道 135 | if (!newUserChannel || newUserChannel != tempVcId) { 136 | if (typeof tempVcId !== 'undefined') { 137 | if (removeTimeouts2[userid]) return 138 | if (removeTimeouts[userid]) return 139 | 140 | //ownerLeave_Embed.setDescription(`房主已離開 <#${tempVcId}>。 \n頻道將於 刪除。 \n如要取消,請重新加入語音頻道。`) 141 | ownerLeave_Embed.data.description = ownerLeave_Embed.data.description 142 | .replace('$tempVcId',`<#${tempVcId}>`) 143 | ownerLeave_Embed.data.description = ownerLeave_Embed.data.description 144 | .replace('$timeLeft',``) 145 | 146 | let Vc = await guild.channels.cache.find(channel => channel.id == tempVcId) 147 | if (!Vc) { // 找查頻道是否存在,不存在自動清除資料庫記憶 148 | await db.del(`tempVcIdKey_${userid}`).catch(err => { console.log('kv del error!') }) 149 | await db.del(`tempmsgIdKey_${userid}`).catch(err => { console.log('kv del error!') }) 150 | await db.del(`tempTimestampKey_${userid}`).catch(err => { console.log('kv del error!') }) 151 | return 152 | } 153 | //傳送訊息,並且設定Timeout排程刪除 154 | let msg = await Vc.send({ content: `<@${newMember.id}>`, embeds: [ownerLeave_Embed] }) 155 | let tout = setTimeout(RemoveVTChannels, 180000, userid, tempVcId) 156 | removeTimeouts[userid] = tout 157 | removeTimeoutsMsg[userid] = msg.id 158 | } 159 | }*/ 160 | 161 | /// 改為 房主離開 自動給下一個使用者房主 162 | if (!newUserChannel || newUserChannel != tempVcId) { 163 | if (typeof tempVcId !== 'undefined') { 164 | if (removeTimeouts2[userid]) return 165 | if (removeTimeouts[userid]) return 166 | 167 | 168 | let Vc = await guild.channels.cache.find(channel => channel.id == tempVcId) 169 | if (!Vc) { // 找查頻道是否存在,不存在自動清除資料庫記憶 170 | await db.del(`tempVcIdKey_${userid}`).catch(err => { console.log('kv del error!') }) 171 | await db.del(`tempmsgIdKey_${userid}`).catch(err => { console.log('kv del error!') }) 172 | await db.del(`tempTimestampKey_${userid}`).catch(err => { console.log('kv del error!') }) 173 | return 174 | } 175 | 176 | //ownerLeave_Embed.setDescription(`房主已離開 <#${tempVcId}>。 \n頻道將於 刪除。 \n如要取消,請重新加入語音頻道。`) 177 | let ownerLeaveNext_Embed = template_ownerLeaveNext_Embed 178 | ownerLeaveNext_Embed.data.description = template_ownerLeaveNext_Embed.data.description 179 | .replace('$tempVcId', `<#${tempVcId}>`) 180 | ownerLeaveNext_Embed.data.description = template_ownerLeaveNext_Embed.data.description 181 | .replace('$timeLeft', ``) 182 | 183 | //傳送訊息,並且設定Timeout排程 184 | let msg = await Vc.send({ content: `<@${newMember.id}>`, embeds: [ownerLeaveNext_Embed] }) 185 | //let tout = setTimeout(RemoveVTChannels2, 180000, userid, tempVcId) 186 | let tout = setTimeout(RemoveVTChannels2, 180000, userid, tempVcId) 187 | removeTimeouts[userid] = tout 188 | removeTimeoutsMsg[userid] = msg.id 189 | } 190 | } 191 | if (newUserChannel == tempVcId) { 192 | if (removeTimeouts[userid]) { 193 | clearTimeout(removeTimeouts[userid]) 194 | delete removeTimeouts[userid] 195 | let Vc = await guild.channels.cache.find(channel => channel.id == tempVcId) 196 | let Controlmsg = await Vc.messages.fetch(`${removeTimeoutsMsg[userid]}`) 197 | await Controlmsg.delete().catch(() => { }) 198 | } 199 | } 200 | 201 | }) 202 | 203 | 204 | async function RemoveMsg(msg) { 205 | 206 | await msg.delete().catch(() => { }) 207 | 208 | } 209 | 210 | async function MoveUser(userid, Member) { 211 | //下次更新需添加 冷卻時間 預防進進出出 212 | let HUBVC = await guild.channels.cache.find(channel => channel.id == config.HUBvcChannelID) 213 | await HUBVC.permissionOverwrites.create(userid, { Connect: false }) 214 | 215 | //await db.put(`tempHUBBan_${userid}`, Date.now() / 1000).catch() 216 | 217 | let newVCid = await CreatVTChannels(userid); 218 | let VC = await guild.channels.cache.find(channel => channel.id == newVCid) 219 | 220 | Member.setChannel(newVCid).then(async () => { 221 | 222 | await HUBVC.permissionOverwrites.delete(userid) 223 | 224 | }).catch(async err => { 225 | await Member.member.send(`系統無法將您移動到新的 語音頻道! 已經將保留的VC刪除! 60秒後才可再次創建新的語音頻道。\n\nThe system cannot move you to a new voice channel! The reserved VC has been deleted! A new voice channel can be created again after 60 seconds.`).catch(() => { console.log }) 226 | var UBBans = await db.get(`tempHUBBans`).catch(async () => { 227 | await db.put('tempHUBBans', {}) 228 | return await db.get(`tempHUBBans`) 229 | }) 230 | if (Object.keys(UBBans).length === 0) setTimeout(cleanHUB_Timeban, 1000 * 5) 231 | UBBans[`${userid}`] = Date.now() 232 | await db.put('tempHUBBans', UBBans) 233 | 234 | if (typeof VC !== 'undefined') VC.delete().catch(() => { console.log('VC delete error') }) 235 | await db.del(`tempVcIdKey_${userid}`).catch(err => { console.log('kv del error!') }) 236 | await db.del(`tempmsgIdKey_${userid}`).catch(err => { console.log('kv del error!') }) 237 | await db.del(`tempTimestampKey_${userid}`).catch(err => { console.log('kv del error!') }) 238 | return 239 | }) 240 | } 241 | 242 | async function cleanHUB_Timeban() { 243 | var UBBans = await db.get(`tempHUBBans`).catch(async () => { 244 | await db.put('tempHUBBans', {}) 245 | return await db.get(`tempHUBBans`) 246 | }) 247 | Object.keys(UBBans).forEach(async key => { 248 | //console.log((Date.now() - UBBans[key]) / 1000) 249 | if (Date.now() - UBBans[key] > 60 * 1000) { 250 | delete UBBans[key]; 251 | let HUBVC = await guild.channels.cache.find(channel => channel.id == config.HUBvcChannelID) 252 | await HUBVC.permissionOverwrites.delete(key) 253 | } 254 | }); 255 | await db.put('tempHUBBans', UBBans) 256 | if (Object.keys(UBBans).length === 0) return 257 | setTimeout(cleanHUB_Timeban, 1000 * 5) 258 | } 259 | 260 | function sleep(ms) { 261 | return new Promise((resolve) => { 262 | setTimeout(resolve, ms); 263 | }); 264 | } 265 | 266 | async function RemoveVTChannels2(UserID, VCID) { 267 | 268 | delete removeTimeouts2[UserID] 269 | delete removeTimeouts[UserID] 270 | //await sleep(5000); 271 | let VC = await guild.channels.fetch(VCID) 272 | 273 | if (VC.members.size == 0) { 274 | if (typeof VC !== 'undefined') VC.delete().catch(() => { console.log('VC delete error') }) 275 | await db.del(`tempVcIdKey_${UserID}`).catch(err => { console.log('kv del error!') }) 276 | await db.del(`tempmsgIdKey_${UserID}`).catch(err => { console.log('kv del error!') }) 277 | await db.del(`tempTimestampKey_${UserID}`).catch(err => { console.log('kv del error!') }) 278 | 279 | } else { 280 | 281 | let newUser = VC.members.find(member => !member.user.bot) 282 | let Controlmsg = await VC.messages.fetch(`${removeTimeoutsMsg[UserID]}`) 283 | let VCPermissions = await VC.permissionsFor(config.DefaultRoleID).serialize() 284 | Controlmsg.edit(await CreateControlMsg(newUser.id, newUser.user.username, VCPermissions)) 285 | 286 | await db.del(`tempVcIdKey_${UserID}`).catch(err => { console.log('kv del error!') }) 287 | await db.del(`tempmsgIdKey_${UserID}`).catch(err => { console.log('kv del error!') }) 288 | await db.del(`tempTimestampKey_${UserID}`).catch(err => { console.log('kv del error!') }) 289 | 290 | await db.put(`tempVcIdKey_${newUser.id}`, VCID).catch(err => { console.log('kv save error!') }) 291 | await db.put(`tempmsgIdKey_${newUser.id}`, Controlmsg.id).catch(err => { console.log('kv save error!') }) 292 | await db.put(`tempTimestampKey_${newUser.id}`, Date.now() / 1000).catch(err => { console.log('kv save error!') }) 293 | 294 | let ownerNew_Embed = template_ownerNew_Embed 295 | ownerNew_Embed.data.description = template_ownerNew_Embed.data.description 296 | .replace('$user', `<@${newUser.id}>`) 297 | ownerNew_Embed.data.description = template_ownerNew_Embed.data.description 298 | .replace('$command', ``) 299 | 300 | await VC.send({ content: `<@${newUser.id}>`, embeds: [ownerNew_Embed] }) 301 | //await interaction.update({ content: `OK. GoodBye ${user}.`, components: [] }); 302 | 303 | /// 如果想要自動更改名子 請取下下方註解 304 | //await VC.setName(`${newUser.nickname ? newUser.nickname : newUser.user.username}'s VC`).catch(err => { console.log('kv save error!') }) 305 | return 306 | } 307 | 308 | } 309 | 310 | async function RemoveVTChannels(UserID, VCID) { 311 | 312 | delete removeTimeouts2[UserID] 313 | delete removeTimeouts[UserID] 314 | let VC = guild.channels.cache.find(channel => channel.id == VCID) 315 | if (typeof VC !== 'undefined') VC.delete().catch(() => { console.log('VC delete error') }) 316 | await db.del(`tempVcIdKey_${UserID}`).catch(err => { console.log('kv del error!') }) 317 | await db.del(`tempmsgIdKey_${UserID}`).catch(err => { console.log('kv del error!') }) 318 | await db.del(`tempTimestampKey_${UserID}`).catch(err => { console.log('kv del error!') }) 319 | 320 | } 321 | 322 | async function CreatVTChannels(UserID) { 323 | 324 | let User = client.users.cache.find(user => user.id == UserID) 325 | const guild = await client.guilds.cache.get(config.guild) 326 | 327 | const newTempVoiceChannel = await guild.channels.create({ 328 | name: `${User.username}'s VC`, 329 | type: ChannelType.GuildVoice, 330 | parent: config.categoryID, 331 | permissionOverwrites: [ 332 | { 333 | id: config.DefaultRoleID, 334 | allow: [PermissionsBitField.Flags.ViewChannel], 335 | }, 336 | { 337 | id: UserID, 338 | allow: [PermissionsBitField.Flags.Connect], 339 | }, 340 | { 341 | id: UserID, 342 | allow: [PermissionsBitField.Flags.Speak], 343 | }, 344 | ], 345 | }); 346 | 347 | await newTempVoiceChannel.lockPermissions().catch(console.error); 348 | await newTempVoiceChannel.permissionOverwrites.edit(UserID, { Speak: true, Connect: true, ViewChannel: true }) 349 | const cmsg = await CreateControlMsg(UserID, User.username) 350 | const msg = await newTempVoiceChannel.send(cmsg) 351 | 352 | //console.log("newVC",newTempVoiceChannel) 353 | await db.put(`tempVcIdKey_${UserID}`, newTempVoiceChannel.id).catch(err => { console.log('kv save error!') }) 354 | await db.put(`tempmsgIdKey_${UserID}`, msg.id).catch(err => { console.log('kv save error!') }) 355 | await db.put(`tempTimestampKey_${UserID}`, Date.now() / 1000).catch(err => { console.log('kv save error!') }) 356 | return newTempVoiceChannel.id 357 | //await kvs1.set('tempVcIdKey_'+UserID,newTempVoiceChannel.id); 358 | //await kvs1.set('tempTcIdKey_'+UserID,newTempTxTChannel.id); 359 | //await kvs1.set('tempTimestampKey_'+UserID,Date.now()); 360 | 361 | } 362 | 363 | client.on("interactionCreate", async (interaction) => { 364 | if (!interaction.isRoleSelectMenu() && !interaction.isRoleSelectMenu && !interaction.isModalSubmit() && !interaction.isButton() && !interaction.isSelectMenu() && !interaction.isCommand()) return 365 | const guild = client.guilds.cache.get(config.guild); 366 | let textchannel = interaction.channel 367 | let user = interaction.user 368 | let tempVcId = await db.get(`tempVcIdKey_${user.id}`).catch(err => { }) //= await kvs1.get('tempVcIdKey_'+userid); 369 | let tempmsgid = await db.get(`tempmsgIdKey_${user.id}`).catch(err => { }) //= await kvs1.get('tempVcIdKey_'+userid); 370 | let tempTimestamp = await db.get(`tempTimestampKey_${user.id}`).catch(err => { }) //= await kvs1.get('tempVcIdKey_'+userid); 371 | let VC = await guild.channels.fetch(tempVcId) 372 | 373 | if (interaction.isRoleSelectMenu() || interaction.isUserSelectMenu()) { 374 | if (interaction.customId == "menu_whitelist_member" || interaction.customId == "menu_whitelist_role") { 375 | //whitelist_Embed.setDescription(".....") 376 | let whitelist_Embed = template_whitelist_Embed 377 | await interaction.update({ ephemeral: true, components: [], content: "", embeds: [whitelist_Embed] }) 378 | 379 | 380 | if (interaction.customId == "menu_whitelist_role") { 381 | let wrole = guild.roles.cache.find(role => role.id == interaction.values[0]) 382 | //console.log(await VC.permissionsFor(wrole.id)) 383 | let VCPermissions = await VC.permissionsFor(wrole.id).serialize() 384 | //console.log(typeof(VCPermissions.VIEW_CHANNEL)) 385 | //console.log(VCPermissions) 386 | if (!VCPermissions.ViewChannel) { 387 | await VC.permissionOverwrites.create(wrole.id, { ViewChannel: true, Connect: true }) 388 | } else { 389 | await VC.permissionOverwrites.delete(wrole.id) 390 | } 391 | 392 | } else { 393 | 394 | let wUserid = client.users.cache.find(user => user.id == interaction.values[0]) 395 | let VCPermissions = await VC.permissionsFor(wUserid.id).serialize() 396 | //console.log(typeof(VCPermissions.VIEW_CHANNEL)) 397 | //console.log(VCPermissions) 398 | if (!VCPermissions.ViewChannel) { 399 | await VC.permissionOverwrites.create(wUserid.id, { ViewChannel: true, Connect: true }) 400 | } else { 401 | await VC.permissionOverwrites.delete(wUserid.id) 402 | } 403 | } 404 | var str1_role = "", str1_member = "" 405 | VC.permissionOverwrites.cache.forEach((value) => { 406 | let VCperm = value.allow.serialize() 407 | if (!VCperm.ViewChannel) return 408 | if (value.type == 0) { 409 | str1_role += `<@&${value.id}>\n` 410 | } else if (value.type == 1) { 411 | str1_member += `<@${value.id}>\n` 412 | } 413 | }) 414 | whitelist_Embed.setFields([ 415 | { name: 'Roles', value: `${str1_role != "" ? str1_role : 'none'}`, inline: true }, 416 | { name: 'Members', value: `${str1_member != "" ? str1_member : 'none'}`, inline: true } 417 | ]) 418 | const userselectmenu = new UserSelectMenuBuilder() 419 | .setMaxValues(1) 420 | .setCustomId('menu_whitelist_member') 421 | .setPlaceholder('Pick one or more users') 422 | 423 | 424 | const roleselectmenu = new RoleSelectMenuBuilder() 425 | .setMaxValues(1) 426 | .setCustomId('menu_whitelist_role') 427 | .setPlaceholder('Pick one or more roles') 428 | 429 | const row1 = new ActionRowBuilder() 430 | .addComponents(userselectmenu) 431 | const row2 = new ActionRowBuilder() 432 | .addComponents(roleselectmenu) 433 | return await interaction.editReply({ ephemeral: true, content: "", components: [row1, row2], embeds: [whitelist_Embed] }); 434 | 435 | 436 | } else if (interaction.customId == "menu_banlist_member" || interaction.customId == "menu_banlist_role") { 437 | await interaction.update({ ephemeral: true, components: [], content: "" }) 438 | 439 | 440 | if (interaction.customId == "menu_banlist_role") { 441 | let banrole = guild.roles.cache.find(role => role.id == interaction.values[0]) 442 | let VCPermissions = VC.permissionOverwrites.cache.find(value => value.id == banrole.id) 443 | 444 | if (typeof VCPermissions === 'undefined' || !VCPermissions.deny.serialize().ViewChannel) { 445 | await VC.permissionOverwrites.create(banrole.id, { ViewChannel: false }) 446 | } else { 447 | await VC.permissionOverwrites.delete(banrole.id) 448 | } 449 | } else { 450 | let banUserid = guild.members.cache.find(user => user.id == interaction.values[0]) 451 | if (banUserid.voice.channelId == VC.id) await banUserid.voice.disconnect().catch() 452 | 453 | let VCPermissions = VC.permissionOverwrites.cache.find(value => value.id == banUserid.id) 454 | if (typeof VCPermissions === 'undefined' || !VCPermissions.deny.serialize().ViewChannel) { 455 | await VC.permissionOverwrites.create(banUserid.id, { ViewChannel: false }) 456 | } else { 457 | await VC.permissionOverwrites.delete(banUserid.id) 458 | } 459 | 460 | } 461 | var str1_role = "", str1_member = "" 462 | VC.permissionOverwrites.cache.forEach((value) => { 463 | let VCperm = value.allow.serialize() 464 | if (VCperm.ViewChannel || typeof value === 'undefined') return 465 | if (value.type == 0) { 466 | str1_role += `<@&${value.id}>\n` 467 | } else if (value.type == 1) { 468 | str1_member += `<@${value.id}>\n` 469 | } 470 | }) 471 | let banlist_Embed = template_banlist_Embed 472 | banlist_Embed.setFields([ 473 | { name: 'Roles', value: `${str1_role != "" ? str1_role : 'none'}`, inline: true }, 474 | { name: 'Members', value: `${str1_member != "" ? str1_member : 'none'}`, inline: true } 475 | ]) 476 | 477 | const userselectmenu = new UserSelectMenuBuilder() 478 | .setMaxValues(1) 479 | .setCustomId('menu_banlist_member') 480 | .setPlaceholder('Pick one or more users') 481 | 482 | const roleselectmenu = new RoleSelectMenuBuilder() 483 | .setMaxValues(1) 484 | .setCustomId('menu_banlist_role') 485 | .setPlaceholder('Pick one or more roles') 486 | 487 | const row1 = new ActionRowBuilder() 488 | .addComponents(userselectmenu) 489 | const row2 = new ActionRowBuilder() 490 | .addComponents(roleselectmenu) 491 | return await interaction.editReply({ ephemeral: true, components: [row1, row2], embeds: [banlist_Embed] }); 492 | 493 | } 494 | } else if (interaction.isStringSelectMenu()) { 495 | 496 | if (interaction.customId === 'select_kick') { 497 | 498 | let kickUser = guild.members.cache.find(user => user.id == interaction.values[0]) 499 | 500 | await kickUser.voice.disconnect() 501 | return await interaction.update({ content: `OK. GoodBye ${kickUser}.`, components: [] }); 502 | 503 | } 504 | else if (interaction.customId === 'select_changeowner') { 505 | 506 | let newUser = guild.members.cache.find(user => user.id == interaction.values[0]) 507 | //console.log(`${newUser.nickname?newUser.nickname:newUser.user.username}'s VC`) 508 | 509 | //await VC.permissionOverwrites.edit(user.id, { VIEW_CHANNEL: null }) 510 | 511 | let Controlmsg = await VC.messages.fetch(`${tempmsgid}`) 512 | let VCPermissions = await VC.permissionsFor(config.DefaultRoleID).serialize() 513 | Controlmsg.edit(await CreateControlMsg(newUser.id, newUser.user.username, VCPermissions)) 514 | if (removeTimeouts[user.id]) { 515 | clearTimeout(removeTimeouts[user.id]) 516 | delete removeTimeouts[userid] 517 | let Controlmsg = await VC.messages.fetch(`${removeTimeoutsMsg[userid]}`) 518 | await Controlmsg.delete().catch(() => { }) 519 | } 520 | 521 | if (removeTimeouts2[user.id]) return await interaction.update({ content: `No. Vc is delete now.`, components: [] }); 522 | 523 | await db.del(`tempVcIdKey_${user.id}`).catch(err => { console.log('kv del error!') }) 524 | await db.del(`tempmsgIdKey_${user.id}`).catch(err => { console.log('kv del error!') }) 525 | await db.del(`tempTimestampKey_${user.id}`).catch(err => { console.log('kv del error!') }) 526 | 527 | await db.put(`tempVcIdKey_${newUser.id}`, tempVcId).catch(err => { console.log('kv save error!') }) 528 | await db.put(`tempmsgIdKey_${newUser.id}`, tempmsgid).catch(err => { console.log('kv save error!') }) 529 | await db.put(`tempTimestampKey_${newUser.id}`, tempTimestamp).catch(err => { console.log('kv save error!') }) 530 | 531 | await interaction.update({ content: `OK. GoodBye ${user}.`, components: [] }); 532 | 533 | await VC.setName(`${newUser.nickname ? newUser.nickname : newUser.user.username}'s VC`).catch(err => { console.log('kv save error!') }) 534 | return 535 | 536 | } 537 | else if (interaction.customId === 'select_Region') { 538 | 539 | await VC.setRTCRegion(interaction.values[0] == "null" ? null : interaction.values[0]) 540 | 541 | return await interaction.update({ content: `OK. VC RTCRegion now is ${VC.rtcRegion ? VC.rtcRegion : "Auto"}.`, components: [] }); 542 | 543 | } 544 | } else if (interaction.isModalSubmit()) { 545 | 546 | if (interaction.customId === 'modal_limit') { 547 | 548 | let inputnum = interaction.fields.getTextInputValue('_value') 549 | if (isNaN(Number(inputnum)) || Number(inputnum) > 99 || Number(inputnum) < 0) return interaction.reply({ content: "Please enter the correct value by the owner before pressing the button.\n請輸入正確的數量後再按下按鈕。", ephemeral: true }).catch(err => { VC.send("Unknow Error. Pls try again.") }) 550 | 551 | await VC.setUserLimit(Number(inputnum)) 552 | return interaction.reply({ content: `OK. The UserLimit is set to ${Number(inputnum)}.\n好的,最大語音人數已設定為${Number(inputnum)}.`, ephemeral: true }).catch(err => { VC.send(`Something is wrong. Please try again.\nError catch: \n\`\`\` ${err}\n\`\`\``) }) 553 | 554 | } else if (interaction.customId === 'modal_changename') { 555 | await interaction.deferReply({ ephemeral: true }) 556 | let inputstr = interaction.fields.getTextInputValue('_value') 557 | 558 | await VC.setName(inputstr) 559 | return interaction.editReply({ content: `OK. VC name is change to ${inputstr}.\n好的,已將名子更改為${inputstr}.`, ephemeral: true }).catch(err => { VC.send(`Something is wrong. Please try again.\nError catch: \n\`\`\` ${err}\n\`\`\``) }) 560 | 561 | } 562 | 563 | } else if (interaction.isButton()) { 564 | if (tempVcId != textchannel.id) { 565 | return await interaction.reply({ content: "Uhhhhh... This is not your VC.", ephemeral: true }).catch(err => { console.log(err) }) 566 | } 567 | 568 | let VCPermissions = await VC.permissionsFor(config.DefaultRoleID).serialize() 569 | //console.log(VCPermissions) 570 | 571 | if (interaction.customId === "button_lock") { 572 | 573 | await interaction.deferReply({ ephemeral: true }) 574 | let Controlmsg = await VC.messages.fetch(`${tempmsgid}`) 575 | var VcPermissions = VCPermissions 576 | 577 | //console.log(VCPermissions) 578 | VcPermissions.Connect = !VcPermissions.Connect 579 | await VC.permissionOverwrites.edit(user.id, { Connect: true }) 580 | await VC.permissionOverwrites.edit(config.DefaultRoleID, { Connect: VcPermissions.Connect }) 581 | 582 | 583 | await Controlmsg.edit(await CreateControlMsg(user.id, user.username, VcPermissions)) 584 | await interaction.editReply({ content: `Now everyone **can${!VcPermissions.Connect ? "not** " : "** "}connect to this channel.`, ephemeral: true }).catch(err => { VC.send(`Something is wrong. Please try again.\nError catch: \n\`\`\` ${err}\n\`\`\``) }) 585 | return 586 | 587 | } 588 | else if (interaction.customId === "button_hide") { 589 | await interaction.deferReply({ ephemeral: true }) 590 | var VcPermissions = VCPermissions 591 | VcPermissions.ViewChannel = !VcPermissions.ViewChannel 592 | let Controlmsg = await VC.messages.fetch(`${tempmsgid}`) 593 | //console.log(VCPermissions) 594 | await VC.permissionOverwrites.edit(user.id, { ViewChannel: true }) 595 | await VC.permissionOverwrites.edit(config.DefaultRoleID, { ViewChannel: VcPermissions.ViewChannel }) 596 | 597 | await Controlmsg.edit(await CreateControlMsg(user.id, user.username, VcPermissions)) 598 | await interaction.editReply({ content: `Now everyone **can${!VcPermissions.ViewChannel ? "not** " : "** "}see this channel.`, ephemeral: true }).catch(err => { VC.send(`Something is wrong. Please try again.\nError catch: \n\`\`\` ${err}\n\`\`\``) }) 599 | return 600 | } 601 | else if (interaction.customId === "button_mute") { 602 | await interaction.deferReply({ ephemeral: true }) 603 | 604 | var VcPermissions = VCPermissions 605 | VcPermissions.Speak = !VcPermissions.Speak 606 | let Controlmsg = await VC.messages.fetch(`${tempmsgid}`) 607 | 608 | //console.log(VCPermissions) 609 | await VC.permissionOverwrites.edit(user.id, { Speak: true }) 610 | await VC.permissionOverwrites.edit(config.DefaultRoleID, { Speak: VcPermissions.Speak }) 611 | 612 | await Controlmsg.edit(await CreateControlMsg(user.id, user.username, VcPermissions)) 613 | await interaction.editReply({ content: `Now new join user **can${VcPermissions.Speak ? "**" : "not**"} Speak.`, ephemeral: true }).catch(err => { VC.send(`Something is wrong. Please try again.\nError catch: \n\`\`\` ${err}\n\`\`\``) }) 614 | return 615 | } 616 | else if (interaction.customId === "button_limit") { 617 | 618 | const modal = new ModalBuilder() 619 | .setCustomId('modal_limit') 620 | .setTitle('Set User limit'); 621 | const MessageInput = new TextInputBuilder() 622 | .setCustomId("_value") 623 | .setLabel("User limit") 624 | .setPlaceholder("Type number here") 625 | .setRequired(true) 626 | .setMinLength(1) 627 | .setMaxLength(2) 628 | .setStyle(TextInputStyle.Short) 629 | modal.addComponents(new ActionRowBuilder().addComponents(MessageInput)); 630 | return await interaction.showModal(modal); 631 | 632 | } 633 | else if (interaction.customId === "button_ban") { 634 | 635 | var str1_role = "", str1_member = "" 636 | VC.permissionOverwrites.cache.forEach((value) => { 637 | let VCperm = value.allow.serialize() 638 | if (VCperm.ViewChannel || typeof value === 'undefined') return 639 | if (value.type == 0) { 640 | str1_role += `<@&${value.id}>\n` 641 | } else if (value.type == 1) { 642 | str1_member += `<@${value.id}>\n` 643 | } 644 | }) 645 | let banlist_Embed = template_banlist_Embed 646 | banlist_Embed.setFields([ 647 | { name: 'Roles', value: `${str1_role != "" ? str1_role : 'none'}`, inline: true }, 648 | { name: 'Members', value: `${str1_member != "" ? str1_member : 'none'}`, inline: true } 649 | ]) 650 | 651 | const userselectmenu = new UserSelectMenuBuilder() 652 | .setMaxValues(1) 653 | .setCustomId('menu_banlist_member') 654 | .setPlaceholder('Pick one or more users') 655 | 656 | const roleselectmenu = new RoleSelectMenuBuilder() 657 | .setMaxValues(1) 658 | .setCustomId('menu_banlist_role') 659 | .setPlaceholder('Pick one or more roles') 660 | 661 | const row1 = new ActionRowBuilder() 662 | .addComponents(userselectmenu) 663 | const row2 = new ActionRowBuilder() 664 | .addComponents(roleselectmenu) 665 | return await interaction.reply({ ephemeral: true, components: [row1, row2], embeds: [banlist_Embed] }); 666 | 667 | 668 | } 669 | else if (interaction.customId === "button_kick") { 670 | await interaction.deferReply({ ephemeral: true }) 671 | let kickUser = VC.members 672 | 673 | //console.log(banUser) 674 | if (kickUser.size <= 1) return interaction.editReply({ content: `Error cannot find user.`, ephemeral: true }).catch(err => { VC.send(`Something is wrong. Please try again.\nError catch: \n\`\`\` ${err}\n\`\`\``) }) 675 | const row = new ActionRowBuilder() 676 | 677 | if (kickUser.size > 1) { 678 | var options = [] 679 | var index = 0 680 | kickUser.forEach((user) => { 681 | if (user.id == interaction.user.id) return 682 | index += 1 683 | if (index <= 25) { 684 | options.push({ 685 | label: user.user.username, 686 | description: user.nickname ? user.nickname : user.user.username, 687 | value: user.id, 688 | }) 689 | } 690 | }); 691 | 692 | let msmComponents = new StringSelectMenuBuilder() 693 | .setCustomId('select_kick') 694 | .setPlaceholder('Nothing selected') 695 | .addOptions(options) 696 | row.addComponents(msmComponents); 697 | } 698 | 699 | return await interaction.editReply({ content: 'Please select the one you want.', components: [row], ephemeral: true }); 700 | 701 | 702 | } 703 | else if (interaction.customId === "button_changename") { 704 | 705 | const modal = new ModalBuilder() 706 | .setCustomId('modal_changename') 707 | .setTitle('Change VC Name'); 708 | const MessageInput = new TextInputBuilder() 709 | .setCustomId("_value") 710 | .setLabel("Name") 711 | .setPlaceholder("Type name here") 712 | .setRequired(true) 713 | .setMinLength(1) 714 | .setMaxLength(4000) 715 | .setStyle(TextInputStyle.Short) 716 | modal.addComponents(new ActionRowBuilder().addComponents(MessageInput)); 717 | return await interaction.showModal(modal); 718 | 719 | } 720 | else if (interaction.customId === "button_changeowner") { 721 | await interaction.deferReply({ ephemeral: true }) 722 | let finedUser = VC.members 723 | //console.log(banUser) 724 | if (finedUser.size <= 1) return interaction.editReply({ content: `Error cannot find user.`, ephemeral: true }).catch(err => { VC.send(`Something is wrong. Please try again.\nError catch: \n\`\`\` ${err}\n\`\`\``) }) 725 | const row = new ActionRowBuilder() 726 | 727 | if (finedUser.size > 1) { 728 | var options = [] 729 | var index = 0 730 | finedUser.forEach((user) => { 731 | 732 | index += 1 733 | if (index <= 25) { 734 | options.push({ 735 | label: user.user.username, 736 | description: user.nickname ? user.nickname : user.user.username, 737 | value: user.id, 738 | default: user.id == interaction.user.id, 739 | }) 740 | } 741 | }); 742 | 743 | let msmComponents = new StringSelectMenuBuilder() 744 | .setCustomId('select_changeowner') 745 | .setPlaceholder('Nothing selected') 746 | .addOptions(options) 747 | row.addComponents(msmComponents); 748 | } 749 | 750 | return await interaction.editReply({ content: 'Please select the one you want.', components: [row], ephemeral: true }); 751 | 752 | 753 | } 754 | else if (interaction.customId === "button_delete") { 755 | let Vc = await guild.channels.cache.find(channel => channel.id == tempVcId) 756 | if (removeTimeouts[user.id]) return 757 | if (removeTimeouts2[user.id]) { 758 | let cmsg = await Vc.send({ embeds: [template_CancelPreDelete_Embed] }) 759 | setTimeout(RemoveMsg, 5000, cmsg) 760 | let Controlmsg = await Vc.messages.fetch(`${removeTimeoutsMsg2[user.id]}`) 761 | clearTimeout(removeTimeouts2[user.id]) 762 | delete removeTimeouts2[user.id] 763 | await Controlmsg.delete().catch(err => { console.log(err) }) 764 | 765 | 766 | } else { 767 | 768 | //predelete_Embed.setDescription(`房主決定將<#${tempVcId}>刪除。 \n頻道將於 刪除。 \n如要取消,請再次按下DeleteVC。`) 769 | let predelete_Embed = template_predelete_Embed 770 | predelete_Embed.data.description = predelete_Embed.data.description 771 | .replace('$tempVcId', `<#${tempVcId}>`) 772 | predelete_Embed.data.description = predelete_Embed.data.description 773 | .replace('$timeLeft', ``) 774 | 775 | let msg = await Vc.send({ embeds: [predelete_Embed] }) 776 | let tout = setTimeout(RemoveVTChannels, 10000, user.id, tempVcId) 777 | removeTimeouts2[user.id] = tout 778 | removeTimeoutsMsg2[user.id] = msg.id 779 | 780 | } 781 | 782 | return interaction.deferUpdate() 783 | 784 | } 785 | else if (interaction.customId === "button_Region") { 786 | await interaction.deferReply({ ephemeral: true }) 787 | 788 | //console.log(RTCRegions) 789 | if (RTCRegions.size < 1) return interaction.editReply({ content: `Error cannot find RTCRegions.`, ephemeral: true }).catch(err => { VC.send(`Something is wrong. Please try again.\nError catch: \n\`\`\` ${err}\n\`\`\``) }) 790 | const row = new ActionRowBuilder() 791 | 792 | var options = [] 793 | options.push({ 794 | label: "Auto", 795 | value: "null", 796 | default: !VC.rtcRegion, 797 | }) 798 | RTCRegions.forEach((Region) => { 799 | 800 | options.push({ 801 | label: Region.name, 802 | value: Region.id, 803 | default: VC.rtcRegion == Region.id, 804 | }) 805 | }); 806 | 807 | let msmComponents = new StringSelectMenuBuilder() 808 | .setCustomId('select_Region') 809 | .setPlaceholder('Nothing selected') 810 | .addOptions(options) 811 | row.addComponents(msmComponents); 812 | 813 | return await interaction.editReply({ content: 'Please select a Region you want.', components: [row], ephemeral: true }); 814 | 815 | 816 | } 817 | else if (interaction.customId === "button_whitelist") { 818 | 819 | var str1_role = "", str1_member = "" 820 | VC.permissionOverwrites.cache.forEach((value) => { 821 | let VCperm = value.allow.serialize() 822 | if (!VCperm.ViewChannel) return 823 | if (value.type == 0) { 824 | str1_role += `<@&${value.id}>\n` 825 | } else if (value.type == 1) { 826 | str1_member += `<@${value.id}>\n` 827 | } 828 | }) 829 | let whitelist_Embed = template_whitelist_Embed 830 | whitelist_Embed.setFields([ 831 | { name: 'Roles', value: `${str1_role != "" ? str1_role : 'none'}`, inline: true }, 832 | { name: 'Members', value: `${str1_member != "" ? str1_member : 'none'}`, inline: true } 833 | ]) 834 | 835 | const userselectmenu = new UserSelectMenuBuilder() 836 | .setMaxValues(1) 837 | .setCustomId('menu_whitelist_member') 838 | .setPlaceholder('Pick one or more users') 839 | 840 | const roleselectmenu = new RoleSelectMenuBuilder() 841 | .setMaxValues(1) 842 | .setCustomId('menu_whitelist_role') 843 | .setPlaceholder('Pick one or more roles') 844 | 845 | const row1 = new ActionRowBuilder() 846 | .addComponents(userselectmenu) 847 | const row2 = new ActionRowBuilder() 848 | .addComponents(roleselectmenu) 849 | return await interaction.reply({ ephemeral: true, components: [row1, row2], embeds: [whitelist_Embed] }); 850 | 851 | } 852 | return await interaction.deferUpdate().catch() 853 | } else if (interaction.isCommand()) { 854 | if (interaction.commandName == "vc-fix") { 855 | await interaction.deferReply() 856 | if (!config.owners.includes(user.id)) return 857 | await db.del('tempHUBBans').catch() 858 | let HUBVC = await guild.channels.cache.find(channel => channel.id == config.HUBvcChannelID) 859 | HUBVC.permissionOverwrites.set([]) 860 | .then(updatedChannel => { 861 | console.log('權限覆蓋已成功清空'); 862 | }) 863 | .catch(error => { 864 | console.error('清空權限覆蓋時出錯:', error); 865 | }); 866 | for await (const key of db.keys()) { 867 | if (key.startsWith('tempVcIdKey_')) { 868 | //console.log(key) 869 | let uid = key.replace('tempVcIdKey_', '') 870 | let tempVcId = await db.get(`tempVcIdKey_${uid}`).catch(err => { }) //= await kvs1.get('tempVcIdKey_'+userid); 871 | let Vc = await guild.channels.cache.find(channel => channel.id == tempVcId) 872 | //adminDeleteEmbed.setDescription(`頻道將於 $timeLeft 刪除。`) 873 | let adminDeleteEmbed = template_adminDeleteEmbed 874 | adminDeleteEmbed.data.description = adminDeleteEmbed.data.description 875 | .replace('$timeLeft', ``) 876 | 877 | Vc.send({ content: `<@${uid}>`, embeds: [adminDeleteEmbed] }) 878 | setTimeout(RemoveVTChannels, 10000, uid, tempVcId) 879 | } 880 | } 881 | return await interaction.editReply({ content: 'OK. Clear database.', ephemeral: true }) 882 | } 883 | else if (interaction.commandName == "vc-hubmsg") { 884 | await interaction.deferReply() 885 | template_createHubEmbed.setAuthor({ 886 | name: (await guild.fetchOwner()).displayName, 887 | iconURL: (await guild.fetchOwner()).avatarURL() 888 | }) 889 | 890 | return interaction.editReply({ embeds: [template_createHubEmbed] }) 891 | } 892 | else if (interaction.commandName == "vc-control") { 893 | if (tempVcId != textchannel.id) { 894 | return await interaction.reply({ content: "Uhhhhh... This is not your VC.", ephemeral: true }).catch(err => { console.log(err) }) 895 | } 896 | 897 | await interaction.deferReply({ ephemeral: true }) 898 | 899 | return interaction.editReply(await CreateControlMsg(user.id, user.username, VcPermissions)) 900 | } 901 | 902 | } 903 | }) 904 | 905 | 906 | async function CreateControlMsg(UserID, userName, Permissinos = { "ViewChannel": false, "Connect": true, "Speak": true, }) { 907 | 908 | let button1 = new ButtonBuilder() 909 | .setStyle(ButtonStyle.Primary) 910 | .setEmoji(`${Permissinos.Connect ? "🔒" : "🔓"}`) 911 | .setLabel(`${Permissinos.Connect ? "Lock" : "Unlock"}`) 912 | .setCustomId("button_lock") 913 | let button2 = new ButtonBuilder() 914 | .setStyle(ButtonStyle.Primary) 915 | .setEmoji(`${Permissinos.ViewChannel ? "👤" : "👥"}`) 916 | .setLabel(`${Permissinos.ViewChannel ? "Hide" : "Show"}`) 917 | .setCustomId("button_hide") 918 | let button3 = new ButtonBuilder() 919 | .setStyle(ButtonStyle.Primary) 920 | .setEmoji(`${Permissinos.Speak ? "🔇" : "🔊"}`) 921 | .setLabel(`${Permissinos.Speak ? "Mute" : "Unmute"}`) 922 | .setCustomId("button_mute") 923 | let button4 = new ButtonBuilder() 924 | 925 | .setStyle(ButtonStyle.Danger) 926 | .setEmoji("🚫") 927 | .setLabel("ㅤBan/Unban") 928 | .setCustomId("button_ban") 929 | let button5 = new ButtonBuilder() 930 | .setStyle(ButtonStyle.Primary) 931 | .setEmoji("🗒️") 932 | .setLabel("ㅤWhitelist/Remove") 933 | .setCustomId("button_whitelist") 934 | let button6 = new ButtonBuilder() 935 | .setStyle(ButtonStyle.Primary) 936 | .setEmoji("⚠️") 937 | .setLabel(" Limit") 938 | .setCustomId("button_limit") 939 | let button7 = new ButtonBuilder() 940 | .setStyle(ButtonStyle.Danger) 941 | .setEmoji("📲") 942 | .setLabel("ㅤChange Owner") 943 | .setCustomId("button_changeowner") 944 | let button8 = new ButtonBuilder() 945 | .setStyle(ButtonStyle.Primary) 946 | .setEmoji("📝") 947 | .setLabel("ㅤChange Name") 948 | .setCustomId("button_changename") 949 | let button9 = new ButtonBuilder() 950 | .setStyle(ButtonStyle.Secondary) 951 | .setEmoji("👂") 952 | .setLabel("Get Mention") 953 | .setCustomId("button_getmention") 954 | let button10 = new ButtonBuilder() 955 | .setStyle(ButtonStyle.Secondary) 956 | .setEmoji("📃") 957 | .setLabel("ㅤW-list List") 958 | .setCustomId("button_w-list") 959 | let button11 = new ButtonBuilder() 960 | .setStyle(ButtonStyle.Secondary) 961 | .setEmoji("📜") 962 | .setLabel("ㅤBan List") 963 | .setCustomId("button_banlist") 964 | let button12 = new ButtonBuilder() 965 | .setStyle(ButtonStyle.Danger) 966 | .setEmoji("💢") 967 | .setLabel("ㅤKick") 968 | .setCustomId("button_kick") 969 | let button13 = new ButtonBuilder() 970 | .setStyle(ButtonStyle.Danger) 971 | .setEmoji("🗑️") 972 | .setLabel("ㅤDelete VC") 973 | .setCustomId("button_delete") 974 | let button14 = new ButtonBuilder() 975 | .setStyle(ButtonStyle.Primary) 976 | .setEmoji("🌎") 977 | .setLabel("ㅤChange Region") 978 | .setCustomId("button_Region") 979 | let button15 = new ButtonBuilder() 980 | .setStyle(ButtonStyle.Primary) 981 | .setEmoji("🔖") 982 | .setLabel("ㅤKeep VC") 983 | .setCustomId("button_vcKeep") 984 | let buttonRow1 = new ActionRowBuilder() 985 | .addComponents([button4, button12, button7, button13]) 986 | 987 | let buttonRow2 = new ActionRowBuilder() 988 | .addComponents([button1, button2, button3, button6]) 989 | 990 | let buttonRow3 = new ActionRowBuilder() 991 | .addComponents([button8, button5, button14]) 992 | let controlsEmbed = template_controlsEmbed 993 | controlsEmbed.setAuthor({ 994 | name: `${userName}'s VoiceChannel`, 995 | iconURL: `${client.users.cache.find(user => user.id == UserID).avatarURL()}` 996 | }) 997 | 998 | return { content: `<@${UserID}>`, embeds: [controlsEmbed], components: [buttonRow1, buttonRow2, buttonRow3] } 999 | } 1000 | 1001 | const banner = ` 1002 | ========================================== 1003 | 1004 | _____ _____ _ 1005 | |_ _|___ _____ ___| | |___|_|___ ___ 1006 | | | | -_| | . | | | . | | _| -_| 1007 | |_| |___|_|_|_| _|\\___/|___|_|___|___| 1008 | |_| 1009 | 1010 | _____ _ 1011 | | __ |___| |_ 1012 | | __ -| . | _| 1013 | |_____|___|_| 1014 | 1015 | 1016 | Ver. 2024.9.04 made by Lars. 1017 | 1018 | ========================================== 1019 | ` 1020 | console.log(banner) 1021 | client.login(config.token).catch(() => console.error('Invalid Token.Make Sure To Fill config.js or set ENV')) 1022 | 1023 | 1024 | --------------------------------------------------------------------------------