├── input ├── tokens.txt └── config.json ├── package.json ├── LICENSE ├── README.md └── src ├── utils └── authorizeTokens.js └── index.js /input/tokens.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /input/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "token": "", 3 | "secret": "", 4 | 5 | "host": "http://localhost", 6 | "port": 443, 7 | 8 | "invite_link": "https://discord.gg/tokenverse / tokenverse" 9 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "token-joiner", 3 | "version": "1.6.0", 4 | "description": "", 5 | "main": "./src/", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "endylus", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@endylus/logger": "^1.0.0", 14 | "discord.js": "^14.11.0", 15 | "express": "^4.18.2", 16 | "fs": "^0.0.1-security", 17 | "node-fetch": "^2.6.7", 18 | "path": "^0.12.7" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Endy 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 | # Discord oauth2 token joiner 2 | 3 | This project allows adding tokens to servers via bots using the Discord OAuth2 authorization protocol. 4 | 5 | ## Config 6 | 7 | 1. Go to [Discord developers](https://discord.com/developers/applications) 8 | 9 | 10 | 11 | https://github.com/user-attachments/assets/a672aba6-f54f-493d-baf5-425d07e52c42 12 | 13 | 14 | 2. `config.json` Edit the file to configure the program. An example configuration is as follows: 15 | 16 | ```json 17 | { 18 | "token": "TOKEN", 19 | "secret": "SECRET_CODE", 20 | 21 | "host": "http://localhost", 22 | "port": "443", 23 | 24 | "invite_link": "https://discord.gg/tokenverse" 25 | } 26 | ``` 27 | 28 | - `token`: Your Discord bot's token. 29 | - `secret`: Your Discord bot's secret code. 30 | - `host`: The web address where the program will run. For example: `http://localhost`. 31 | - `port`: The port number on which the program will run. The recommended value is usually `443` 32 | - `invite_link`: Invite link to join the server. For example: `https://discord.gg/tokenverse`, `tokenverse` 33 | 34 | ## Installation 35 | 36 | - Install Node.js - [Download](https://nodejs.org/dist/v20.11.0/node-v20.11.0-x64.msi) 37 | 38 | 1. Install the required packages: 39 | 40 | ``` 41 | npm install 42 | ``` 43 | 44 | 2. Run the script: 45 | 46 | ``` 47 | node . 48 | ``` 49 | 50 | ## Disclaimer 51 | No responsibility is accepted for any issues arising from the use of this project and its content. The project is the responsibility of users to configure and use on their own systems. Users should take necessary precautions and follow configuration instructions correctly before using the project. 52 | 53 | ## Support 54 | 55 | If you have any questions, feel free to join my Discord server: [https://discord.gg/tokenverse](https://discord.gg/tokenverse) 56 | -------------------------------------------------------------------------------- /src/utils/authorizeTokens.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | const fetch = require("node-fetch"); 4 | const Logger = require("@endylus/logger"); 5 | const log = new Logger({ filesLog: false }); 6 | const config = require("../../input/config.json"); 7 | const tokens = fs.readFileSync(path.join(process.cwd(), "./input/tokens.txt"), 'utf8').split(/\r?\n/).filter(token => token !== '' && token !== ' ') 8 | 9 | const OUTPUT_DIR = './output'; 10 | const OUTPUT_FILES = { 11 | JOINED: 'joined.txt', 12 | NOT_JOINED: 'not joined.txt', 13 | ALREADY_JOINED: 'already joined.txt', 14 | INVALID_FORMAT: 'invalid format.txt' 15 | }; 16 | 17 | if (!fs.existsSync(OUTPUT_DIR)) fs.mkdirSync(OUTPUT_DIR, { recursive: true }); 18 | 19 | Object.keys(OUTPUT_FILES).forEach(key => { 20 | OUTPUT_FILES[key] = path.join(OUTPUT_DIR, OUTPUT_FILES[key]); 21 | }); 22 | 23 | let stats = { 24 | joined: 0, 25 | notJoined: 0, 26 | alreadyJoined: 0, 27 | invalidFormat: 0, 28 | count: 1 29 | }; 30 | 31 | async function authorize(botId, token, index) { 32 | const tokenParts = token.split(':'); 33 | 34 | if (tokenParts.length === 2 || tokenParts.length > 3) { 35 | stats.invalidFormat++; 36 | log.warn(`Token: ${tokenParts[0].slice(0, 26)} - Invalid Format! [${index + 1}/${tokens.length}]`); 37 | fs.appendFileSync(OUTPUT_FILES.INVALID_FORMAT, `${token}\n`); 38 | return; 39 | } 40 | 41 | const tkn = tokenParts[tokenParts.length === 1 ? 0 : 2]; 42 | 43 | try { 44 | const startTime = new Date(); 45 | const decodedURL = decodeURIComponent(`https://discord.com/api/oauth2/authorize?client_id=${botId}&redirect_uri=${config.host}:${config.port}&response_type=code&scope=identify+guilds.join`); 46 | 47 | const response = await fetch(decodedURL, { 48 | headers: { authorization: tkn, "content-type": "application/json" }, 49 | method: "POST", 50 | body: JSON.stringify({ permissions: "0", authorize: true }) 51 | }); 52 | const data = await response.json(); 53 | 54 | if (data.location) { 55 | const result = await fetch(data.location).then(res => res.json()); 56 | const elapsedTime = (new Date() - startTime) / 1000; 57 | result.joined ? stats.joined++ : (result.message === "Already Joined" ? stats.alreadyJoined++ : stats.notJoined++); 58 | fs.appendFileSync(result.joined ? OUTPUT_FILES.JOINED : (result.message === "Already Joined" ? OUTPUT_FILES.ALREADY_JOINED : OUTPUT_FILES.NOT_JOINED), `${token}\n`); 59 | log.info(`Token: ${tkn.slice(0, 26)} - Joined: ${result.joined ? "Yes" : "No"} - Message: ${result.message} - Time: ${elapsedTime}s - [${stats.count++}/${tokens.length}]`); 60 | } else { 61 | stats.notJoined++; 62 | fs.appendFileSync(OUTPUT_FILES.NOT_JOINED, `${token}\n`); 63 | log.warn(`Token: ${tkn.slice(0, 26)} - Joined: No - Message: Something went wrong! [${index + 1}/${tokens.length}]`); 64 | } 65 | } catch (err) { 66 | console.log(err); 67 | stats.notJoined++; 68 | log.error(`Token: ${tkn.slice(0, 26)} - Error: ${err.message} [${index + 1}/${tokens.length}]`); 69 | fs.appendFileSync(OUTPUT_FILES.NOT_JOINED, `${token}\n`); 70 | } 71 | } 72 | 73 | async function authorizeTokens(botId, concurrencyLimit = 1, batchSize = 1) { 74 | let currentIndex = 0; 75 | 76 | async function nextBatch() { 77 | if (currentIndex >= tokens.length) return; 78 | 79 | const batch = tokens.slice(currentIndex, currentIndex + batchSize); 80 | currentIndex += batchSize; 81 | 82 | const promises = batch.map((token, index) => authorize(botId, token, currentIndex + index)); 83 | await Promise.all(promises); 84 | await nextBatch(); 85 | } 86 | 87 | const workers = []; 88 | for (let i = 0; i < concurrencyLimit; i++) { 89 | workers.push(nextBatch()); 90 | } 91 | 92 | await Promise.all(workers); 93 | 94 | log.info(`All tokens have been processed!`); 95 | log.info(`Joined: ${stats.joined} - Not Joined: ${stats.notJoined} - Already Joined: ${stats.alreadyJoined} - Invalid Format: ${stats.invalidFormat}`); 96 | } 97 | 98 | module.exports = authorizeTokens; -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const fetch = require('node-fetch'); 3 | const { Client, GatewayIntentBits } = require('discord.js'); 4 | const authorizeTokens = require('./utils/authorizeTokens'); 5 | const Logger = require("@endylus/logger"); 6 | const log = new Logger({ filesLog: false }); 7 | const config = require("../../input/config.json"); 8 | 9 | const app = express(); 10 | const client = new Client({ intents: [GatewayIntentBits.Guilds] }); 11 | const port = config.port; 12 | let guildId; 13 | let botId; 14 | 15 | async function connectToDiscord() { 16 | try { 17 | await client.login(config.token); 18 | const server = config.invite_link; 19 | const inviteLink = server.split('/').pop(); 20 | const inviteData = await fetch(`https://discordapp.com/api/v9/invites/${inviteLink}`).then(res => res.json()); 21 | 22 | if (inviteData.code === 10006) return log.error(`Invalid Invite Code: ${inviteLink}`); 23 | 24 | guildId = inviteData.guild.id; 25 | botId = client.user.id; 26 | const guild = client.guilds.cache.get(guildId); 27 | 28 | if (!guild) return log.error(`Couldn't find the guild with ID: ${guildId}`); 29 | 30 | log.info(`Connected to Guild: ${guild.name}`); 31 | log.info(`Guild ID: ${guildId}`); 32 | log.info(`Member Count: ${guild.memberCount}`); 33 | log.info(`Invite URL: ${server}`); 34 | 35 | authorizeTokens(botId); 36 | } catch (error) { 37 | log.error(`Failed to connect to Discord: ${error.message}`); 38 | } 39 | } 40 | 41 | async function getDiscordToken(code) { 42 | try { 43 | const tokenResponseData = await fetch('https://discord.com/api/oauth2/token', { 44 | headers: { 45 | 'Content-Type': 'application/x-www-form-urlencoded' 46 | }, 47 | body: new URLSearchParams({ 48 | client_id: botId, 49 | client_secret: config.secret, 50 | code: code, 51 | grant_type: 'authorization_code', 52 | redirect_uri: `${config.host}:${port}`, 53 | scope: 'identify', 54 | }), 55 | method: 'POST' 56 | }).then(res => res.json()); 57 | return tokenResponseData; 58 | } catch (error) { 59 | return { error: error.message } 60 | } 61 | } 62 | 63 | async function getDiscordUser(token) { 64 | try { 65 | const userResponseData = await fetch('https://discord.com/api/users/@me', { 66 | headers: { 67 | authorization: `${token.token_type} ${token.access_token}` 68 | }, 69 | method: 'GET' 70 | }).then(res => res.json()); 71 | return userResponseData; 72 | } catch (error) { 73 | return { error: error.message } 74 | } 75 | } 76 | 77 | app.get('/', async (req, res) => { 78 | try { 79 | const code = req.query.code; 80 | if (!code) return res.status(404).json({ joined: false, message: `Request missing 'code' parameter` }); 81 | 82 | const tokenResponseData = await getDiscordToken(code); 83 | if (tokenResponseData.error) return res.status(404).json({ joined: false, message: `Reason: ${tokenResponseData.error}` }); 84 | 85 | const userResponseData = await getDiscordUser(tokenResponseData); 86 | if (tokenResponseData.error) return res.status(404).json({ joined: false, message: `Reason: ${tokenResponseData.error}` }); 87 | 88 | const guild = client.guilds.cache.get(guildId); 89 | if (!guild) return res.status(404).json({ joined: false, message: `Couldn't find the guild with ID: ${guildId}` }); 90 | 91 | guild.members.add(userResponseData.id, { accessToken: tokenResponseData.access_token }) 92 | .then(() => { 93 | res.status(200).json({ joined: true, message: "Joined the server!" }); 94 | }).catch(err => { 95 | console.log(err); 96 | if (err.message === "Cannot read properties of undefined (reading 'id')") return res.status(404).json({ joined: false, message: "Already Joined" }); 97 | res.status(404).json({ joined: false, message: err.message }); 98 | }); 99 | } catch (err) { 100 | res.status(404).json({ joined: false, message: `Request failed: ${err.message}` }); 101 | } 102 | }); 103 | 104 | app.listen(port, () => { 105 | log.info(`Server listening on port ${port}`); 106 | connectToDiscord(); 107 | }); --------------------------------------------------------------------------------