├── howto.gif ├── .gitignore ├── .prettierrc.js ├── myScripts ├── example.js └── sendThenDelete.js ├── LICENSE ├── types.d.ts ├── index.js └── README.md /howto.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rigwild/discord-self-bot-console/HEAD/howto.gif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | myScripts/* 4 | !myScripts/example.js 5 | !myScripts/sendThenDelete.js 6 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: false, 3 | singleQuote: true, 4 | printWidth: 200, 5 | useTabs: false, 6 | trailingComma: 'all', 7 | arrowParens: 'avoid', 8 | } 9 | -------------------------------------------------------------------------------- /myScripts/example.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | const { api } = require('../index.js') 3 | 4 | // Copy paste the below code inside the Discord console 5 | ;(async () => { 6 | const user = await api.getCurrentUser() 7 | 8 | console.log(user.username) 9 | })() 10 | -------------------------------------------------------------------------------- /myScripts/sendThenDelete.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | const { api } = require('../index.js') 3 | 4 | // Copy paste the below code inside the Discord console 5 | ;(async () => { 6 | const user = await api.getCurrentUser() 7 | 8 | api.update_guildId_and_channelId_withCurrentlyVisible() 9 | let channelId = api.getConfig().channelId 10 | 11 | // Send a message 12 | let sentMessage = await api.sendMessage(channelId, `Hello! 👋 My name is ${user.username}!`) 13 | 14 | await api.delay(2000) 15 | 16 | // Edit a message 17 | let editedMessage = await api.editMessage(channelId, sentMessage.id, 'Hello, edited! ✌️') 18 | 19 | await api.delay(2000) 20 | 21 | // Delete a message 22 | await api.deleteMessage(channelId, editedMessage.id) 23 | 24 | await api.delay(2000) 25 | 26 | // Log the last 100 messages in the console 27 | let messages = await api.getMessages(channelId) 28 | console.log(messages) 29 | })() 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 rigwild (https://rigwild.dev/) 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 | -------------------------------------------------------------------------------- /types.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | export interface RequestOptions { 4 | method?: string 5 | headers?: { [key: string]: string } 6 | body?: any 7 | } 8 | 9 | export interface Message { 10 | id: string 11 | type: number 12 | content: string 13 | channel_id: string 14 | author: { 15 | id: string 16 | username: string 17 | avatar: string 18 | discriminator: string 19 | public_flags: number 20 | flags: number 21 | banner: any 22 | accent_color: any 23 | global_name: string 24 | avatar_decoration: any 25 | banner_color: any 26 | } 27 | attachments: any[] 28 | embeds: any[] 29 | mentions: any[] 30 | mention_roles: any[] 31 | pinned: boolean 32 | mention_everyone: boolean 33 | tts: boolean 34 | timestamp: string 35 | edited_timestamp: any 36 | flags: number 37 | components: any[] 38 | nonce: string 39 | referenced_message: any 40 | } 41 | 42 | export interface Embed { 43 | title: string 44 | description: string 45 | // ... 46 | } 47 | 48 | export interface Thread { 49 | id: string 50 | name: string 51 | // ... 52 | } 53 | 54 | export interface Role { 55 | id: string 56 | name: string 57 | // ... 58 | } 59 | 60 | export interface Emoji { 61 | id: string 62 | name: string 63 | // ... 64 | } 65 | 66 | export interface User { 67 | id: string 68 | username: string 69 | avatar: string 70 | discriminator: string 71 | public_flags: number 72 | flags: number 73 | banner: any 74 | accent_color: any 75 | global_name: string 76 | avatar_decoration: any 77 | banner_color: any 78 | mfa_enabled: boolean 79 | locale: string 80 | premium_type: number 81 | email: string 82 | verified: boolean 83 | phone: string 84 | nsfw_allowed: boolean 85 | linked_users: any[] 86 | bio: string 87 | } 88 | 89 | export interface Guild { 90 | id: string 91 | name: string 92 | // ... 93 | } 94 | 95 | export interface ApplicationCommand { 96 | id: string 97 | type: number 98 | application_id: string 99 | version: string 100 | name: string 101 | description: string 102 | options?: { 103 | type: number 104 | name: string 105 | description: string 106 | required: boolean 107 | autocomplete: boolean 108 | choices?: { 109 | name: string 110 | value: string 111 | }[] 112 | }[] 113 | dm_permission: boolean 114 | integration_types: number[] 115 | global_popularity_rank?: number 116 | guild_id?: string 117 | default_member_permissions?: string 118 | permissions?: { 119 | roles: { 120 | [key: string]: boolean 121 | } 122 | } 123 | } 124 | 125 | export interface GuildCommandsIndex { 126 | applications: { 127 | id: string 128 | name: string 129 | description: string 130 | icon: string 131 | bot_id: string 132 | flags: string 133 | }[] 134 | application_commands: ApplicationCommand[] 135 | version: string 136 | } 137 | 138 | export type api = { 139 | getMessages(channelOrThreadId: string, limit?: number, params?: any): Promise 140 | sendMessage(channelOrThreadId: string, message: string, tts?: boolean, body?: any): Promise 141 | replyToMessage(channelOrThreadId: string, repliedMessageId: string, message: string, tts: boolean, body?: any): Promise 142 | editMessage(channelOrThreadId: string, messageId: string, newMessage: string, body?: any): Promise 143 | deleteMessage(channelOrThreadId: string, messageId: string): Promise 144 | 145 | createThread(channelId: string, toOpenThreadInmessageId: string, name: string, autoArchiveDuration?: number, body?: any): Promise 146 | createThreadWithoutMessage(channelId: string, name: string, autoArchiveDuration?: number, body?: any): Promise 147 | deleteThread(threadId: string): Promise 148 | 149 | sendEmbed(channelOrThreadId: string, embed?: Embed): Promise 150 | 151 | getRoles(guildId: string): Promise 152 | createRole(guildId: string, name: string): Promise 153 | deleteRole(guildId: string, roleId: string): Promise 154 | 155 | getBans(guildId: string): Promise 156 | banUser(guildId: string, userId: string, reason: string): Promise 157 | unbanUser(guildId: string, userId: string): Promise 158 | kickUser(guildId: string, userId: string): Promise 159 | 160 | addRole(guildId: string, userId: string, roleId: string): Promise 161 | removeRole(guildId: string, userId: string, roleId: string): Promise 162 | 163 | auditLogs(guildId: string): Promise 164 | 165 | getChannels(guildId: string): Promise 166 | createChannel(guildId: string, name: string, type: string): Promise 167 | deleteChannel(channelId: string): Promise 168 | getChannel(channelOrThreadId: string): Promise 169 | 170 | pinnedMessages(channelId: string): Promise 171 | addPin(channelId: string, messageId: string): Promise 172 | deletePin(channelId: string, messageId: string): Promise 173 | 174 | listEmojis(guildId: string): Promise 175 | getEmoji(guildId: string, emojiId: string): Promise 176 | createEmoji(guildId: string, name: string, image: string, roles: string[]): Promise 177 | editEmoji(guildId: string, emojiId: string, name: string, roles: string[]): Promise 178 | deleteEmoji(guildId: string, emojiId: string): Promise 179 | 180 | getGuildCommandsAndApplications(guildId: string): Promise 181 | searchSlashCommands(guildId: string, searchWord: string): Promise 182 | sendSlashCommand(guildId: string, channelOrThreadId: string, command: any, commandOptions?: any[]): Promise 183 | 184 | changeNick(guildId: string, nick: string): Promise 185 | leaveServer(guildId: string): Promise 186 | 187 | getServers(): Promise 188 | getGuilds(): Promise 189 | listCurrentUserGuilds(): Promise 190 | 191 | getDMs(): Promise 192 | getUser(userId: string): Promise 193 | 194 | getDirectFriendInviteLinks(): Promise 195 | createDirectFriendInviteLink(): Promise 196 | deleteDirectFriendInviteLinks(): Promise 197 | 198 | getCurrentUser(): Promise 199 | editCurrentUser(username?: string, bio?: string, body?: any): Promise 200 | 201 | setCustomStatus(emojiId: string, emojiName: string, expiresAt: string, text: string): Promise 202 | deleteCustomStatus(): Promise 203 | 204 | listReactions(channelOrThreadId: string, messageId: string, emojiUrl: string): Promise 205 | addReaction(channelOrThreadId: string, messageId: string, emojiUrl: string): Promise 206 | deleteReaction(channelOrThreadId: string, messageId: string, emojiUrl: string): Promise 207 | 208 | typing(channelOrThreadId: string): Promise 209 | 210 | delay(ms: number): Promise 211 | downloadFileByUrl: (url: string, filename: string) => Promise 212 | apiCall(apiPath: string, body?: any, method?: string, options?: RequestOptions): Promise 213 | id(log?: boolean): void 214 | update_guildId_and_channelId_withCurrentlyVisible(log?: boolean): void 215 | getConfig(): Readonly<{ 216 | authHeader: string 217 | autoUpdateToken: boolean 218 | gid: string 219 | /** Alias for `gid` */ 220 | guildId: string 221 | cid: string 222 | /** Alias for `cid` */ 223 | channelId: string 224 | }> 225 | setConfigAuthHeader(token: string): void 226 | 227 | setConfigAutoUpdateToken: (autoUpdateToken: boolean) => void 228 | setConfigGid: (guildId: string) => void 229 | setConfigGuildId: (guildId: string) => void 230 | setConfigCid: (channelId: string) => void 231 | setConfigChannelId: (channelId: string) => void 232 | } 233 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | /// 3 | 4 | // From https://github.com/rigwild/discord-self-bot-console 5 | 6 | { 7 | var gid = '' // Current guild id 8 | var cid = '' // Current channel id 9 | var authHeader = '' // Authorization token 10 | var autoUpdateToken = undefined // Should the token be updated automatically when a request with the token is intercepted? 11 | 12 | // Call this to update `cid` and `gid` to current channel and guild id 13 | var update_guildId_and_channelId_withCurrentlyVisible = (log = true) => { 14 | gid = window.location.href.split('/').slice(4)[0] 15 | cid = window.location.href.split('/').slice(4)[1] 16 | if (log) { 17 | console.log(`\`gid\` was set to the guild id you are currently looking at (${gid})`) 18 | console.log(`\`cid\` was set to the channel id you are currently looking at (${cid})`) 19 | } 20 | } 21 | var id = update_guildId_and_channelId_withCurrentlyVisible 22 | 23 | /** @type {import('./types').api['delay']} */ 24 | var delay = ms => new Promise(res => setTimeout(res, ms)) 25 | // prettier-ignore 26 | var qs = obj => Object.entries(obj).map(([k, v]) => `${k}=${v}`).join('&') 27 | 28 | /** @type {import('./types').api['apiCall']} */ 29 | var apiCall = (apiPath, body, method = 'GET', options = {}) => { 30 | if (!authHeader) throw new Error("The authorization token is missing. Did you forget to set it? `authHeader = 'your_token'`") 31 | 32 | const fetchOptions = { 33 | body: body ? body : undefined, 34 | method, 35 | headers: { 36 | Accept: '*/*', 37 | 'Accept-Language': 'en-US', 38 | Authorization: authHeader, 39 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) discord/1.0.9015 Chrome/108.0.5359.215 Electron/22.3.12 Safari/537.36', 40 | 'X-Super-Properties': btoa( 41 | JSON.stringify({ 42 | os: 'Windows', 43 | browser: 'Discord Client', 44 | release_channel: 'stable', 45 | client_version: '1.0.9163', 46 | os_version: '10.0.22631', 47 | os_arch: 'x64', 48 | app_arch: 'x64', 49 | system_locale: 'en-US', 50 | browser_user_agent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) discord/1.0.9163 Chrome/124.0.6367.243 Electron/30.2.0 Safari/537.36', 51 | browser_version: '30.2.0', 52 | os_sdk_version: '22631', 53 | client_build_number: 327338, 54 | native_build_number: 52153, 55 | client_event_source: null, 56 | }), 57 | ), 58 | }, 59 | ...options, 60 | } 61 | const isFormData = body?.constructor?.name === 'FormData' 62 | if (!isFormData) { 63 | fetchOptions.headers['Content-Type'] = 'application/json' 64 | fetchOptions.body = JSON.stringify(body) 65 | } 66 | return fetch(`https://discord.com/api/v9${apiPath}`, fetchOptions) 67 | .then(res => { 68 | if (res.ok) return res.json() 69 | throw new Error(`Failed to fetch: ${res.status} ${res.statusText}`) 70 | }) 71 | .catch(err => { 72 | console.error(err) 73 | throw new Error('An error occurred while fetching the API.') 74 | }) 75 | } 76 | 77 | /** @type {import('./types').api} */ 78 | var api = { 79 | getMessages: (channelOrThreadId, limit = 100, params = {}) => apiCall(`/channels/${channelOrThreadId}/messages?limit=${limit ?? 100}&${qs(params)}`), 80 | sendMessage: (channelOrThreadId, message, tts, body = {}) => apiCall(`/channels/${channelOrThreadId}/messages`, { content: message, tts: !!tts, ...body }, 'POST'), 81 | replyToMessage: (channelOrThreadId, repliedMessageId, message, tts, body = {}) => 82 | apiCall(`/channels/${channelOrThreadId}/messages`, { content: message, message_reference: { message_id: repliedMessageId }, tts: !!tts, ...body }, 'POST'), 83 | editMessage: (channelOrThreadId, messageId, newMessage, body = {}) => apiCall(`/channels/${channelOrThreadId}/messages/${messageId}`, { content: newMessage, ...body }, 'PATCH'), 84 | deleteMessage: (channelOrThreadId, messageId) => apiCall(`/channels/${channelOrThreadId}/messages/${messageId}`, null, 'DELETE'), 85 | 86 | createThread: (channelId, toOpenThreadInmessageId, name, autoArchiveDuration = 1440, body = {}) => 87 | apiCall(`/channels/${channelId}/messages/${toOpenThreadInmessageId}/threads`, { name, auto_archive_duration: autoArchiveDuration, location: 'Message', type: 11, ...body }, 'POST'), 88 | createThreadWithoutMessage: (channelId, name, autoArchiveDuration = 1440, body = {}) => 89 | apiCall(`/channels/${channelId}/threads`, { name, auto_archive_duration: autoArchiveDuration, location: 'Message', type: 11, ...body }, 'POST'), 90 | deleteThread: threadId => apiCall(`/channels/${threadId}`, null, 'DELETE'), 91 | 92 | // Use this generator: https://discord.club/dashboard 93 | // Click `+` at the bottom in the embed section then copy the `embed` key in the JSON output. 94 | // Does not work with user account anymore! 95 | sendEmbed: (channelOrThreadId, embed = { title: 'Title', description: 'Description' }) => apiCall(`/channels/${channelOrThreadId}/messages`, { embed }, 'POST'), 96 | 97 | getRoles: guildId => apiCall(`/guilds/${guildId}/roles`), 98 | createRole: (guildId, name) => apiCall(`/guilds/${guildId}/roles`, { name }, 'POST'), 99 | deleteRole: (guildId, roleId) => apiCall(`/guilds/${guildId}/roles/${roleId}`, null, 'DELETE'), 100 | 101 | getBans: guildId => apiCall(`/guilds/${guildId}/bans`), 102 | banUser: (guildId, userId, reason) => apiCall(`/guilds/${guildId}/bans/${userId}`, { delete_message_days: '7', reason }, 'PUT'), 103 | unbanUser: (guildId, userId) => apiCall(`/guilds/${guildId}/bans/${userId}`, null, 'DELETE'), 104 | kickUser: (guildId, userId) => apiCall(`/guilds/${guildId}/members/${userId}`, null, 'DELETE'), 105 | 106 | addRole: (guildId, userId, roleId) => apiCall(`/guilds/${guildId}/members/${userId}/roles/${roleId}`, null, 'PUT'), 107 | removeRole: (guildId, userId, roleId) => apiCall(`/guilds/${guildId}/members/${userId}/roles/${roleId}`, null, 'DELETE'), 108 | 109 | auditLogs: guildId => apiCall(`/guilds/${guildId}/audit-logs`), 110 | 111 | getChannels: guildId => apiCall(`/guilds/${guildId}/channels`), 112 | createChannel: (guildId, name, type) => apiCall(`/guilds/${guildId}/channels`, { name, type }, 'POST'), 113 | deleteChannel: channelId => apiCall(`/channels/${channelId}`, null, 'DELETE'), 114 | getChannel: channelOrThreadId => apiCall(`/channels/${channelOrThreadId}`), 115 | 116 | pinnedMessages: channelId => apiCall(`/channels/${channelId}/pins`), 117 | addPin: (channelId, messageId) => apiCall(`/channels/${channelId}/pins/${messageId}`, null, 'PUT'), 118 | deletePin: (channelId, messageId) => apiCall(`/channels/${channelId}/pins/${messageId}`, null, 'DELETE'), 119 | 120 | listEmojis: guildId => apiCall(`/guilds/${guildId}/emojis`), 121 | getEmoji: (guildId, emojiId) => apiCall(`/guilds/${guildId}/emojis/${emojiId}`), 122 | createEmoji: (guildId, name, image, roles) => apiCall(`/guilds/${guildId}`, { name, image, roles }, 'POST'), 123 | editEmoji: (guildId, emojiId, name, roles) => apiCall(`/guilds/${guildId}/${emojiId}`, { name, roles }, 'PATCH'), 124 | deleteEmoji: (guildId, emojiId) => apiCall(`/guilds/${guildId}/${emojiId}`, null, 'DELETE'), 125 | 126 | getGuildCommandsAndApplications: guildId => apiCall(`/guilds/${guildId}/application-command-index`), 127 | searchSlashCommands: async (guildId, searchWord = '') => { 128 | const contextData = await apiCall(`/guilds/${guildId}/application-command-index`) 129 | const commands = contextData.application_commands.filter(cmd => cmd.name.includes(searchWord)) 130 | if (contextData.application_commands?.length > 0 && commands.length === 0) { 131 | throw new Error(`Command '${searchWord}' not found.`) 132 | } 133 | return commands 134 | }, 135 | sendSlashCommand: (guildId, channelOrThreadId, command, commandOptions = []) => { 136 | const formData = new FormData() 137 | formData.append( 138 | 'payload_json', 139 | JSON.stringify({ 140 | type: 2, 141 | application_id: command.application_id, 142 | guild_id: guildId, 143 | channel_id: channelOrThreadId, 144 | session_id: 'requiredButUnchecked', 145 | nonce: Math.floor(Math.random() * 1000000) + '', 146 | data: { 147 | ...command, 148 | options: commandOptions, 149 | application_command: { 150 | ...command, 151 | }, 152 | }, 153 | }), 154 | ) 155 | return apiCall('/interactions', formData, 'POST') 156 | }, 157 | 158 | changeNick: (guildId, nick) => apiCall(`/guilds/${guildId}/members/@me/nick`, { nick }, 'PATCH'), 159 | leaveServer: guildId => apiCall(`/users/@me/guilds/${guildId}`, null, 'DELETE'), 160 | 161 | getServers: () => apiCall(`/users/@me/guilds`), 162 | getGuilds: () => apiCall(`/users/@me/guilds`), 163 | listCurrentUserGuilds: () => apiCall('/users/@me/guilds'), 164 | 165 | getDMs: () => apiCall(`/users/@me/channels`), 166 | getUser: userId => apiCall(`/users/${userId}`), 167 | 168 | getDirectFriendInviteLinks: () => apiCall(`/users/@me/invites`), 169 | createDirectFriendInviteLink: () => apiCall(`/users/@me/invites`, null, 'POST'), 170 | deleteDirectFriendInviteLinks: () => apiCall(`/users/@me/invites`, null, 'DELETE'), 171 | 172 | getCurrentUser: () => apiCall('/users/@me'), 173 | editCurrentUser: (username, bio, body = {}) => apiCall('/users/@me', { username: username ?? undefined, bio: bio ?? undefined, ...body }, 'PATCH'), 174 | 175 | setCustomStatus: (emojiId, emojiName, expiresAt, text) => 176 | apiCall(`/users/@me/settings`, { custom_status: { emoji_id: emojiId, emoji_name: emojiName, expires_at: expiresAt, text: text } }, 'PATCH'), 177 | deleteCustomStatus: () => apiCall(`/users/@me/settings`, { custom_status: { expires_at: new Date().toJSON() } }, 'PATCH'), 178 | 179 | listReactions: (channelOrThreadId, messageId, emojiUrl) => apiCall(`/channels/${channelOrThreadId}/messages/${messageId}/reactions/${emojiUrl}/@me`), 180 | addReaction: (channelOrThreadId, messageId, emojiUrl) => apiCall(`/channels/${channelOrThreadId}/messages/${messageId}/reactions/${emojiUrl}/@me`, null, 'PUT'), 181 | deleteReaction: (channelOrThreadId, messageId, emojiUrl) => apiCall(`/channels/${channelOrThreadId}/messages/${messageId}/reactions/${emojiUrl}/@me`, null, 'DELETE'), 182 | 183 | typing: channelOrThreadId => apiCall(`/channels/${channelOrThreadId}/typing`, null, 'POST'), 184 | 185 | delay, 186 | downloadFileByUrl: (url, filename) => 187 | fetch(url) 188 | .then(response => response.blob()) 189 | .then(blob => { 190 | const link = document.createElement('a') 191 | link.href = URL.createObjectURL(blob) 192 | link.download = filename 193 | link.click() 194 | }) 195 | .catch(console.error), 196 | apiCall, 197 | id, 198 | update_guildId_and_channelId_withCurrentlyVisible, 199 | getConfig: () => Object.freeze({ authHeader, autoUpdateToken, guildId: gid, channelId: cid, gid, cid }), 200 | setConfigAuthHeader: token => (authHeader = token), 201 | setConfigAutoUpdateToken: bool => (autoUpdateToken = bool), 202 | setConfigGid: id => (gid = id), 203 | setConfigGuildId: id => (gid = id), 204 | setConfigCid: id => (cid = id), 205 | setConfigChannelId: id => (cid = id), 206 | } 207 | 208 | console.log('\n\n\n\nSelfbot loaded! Use it like this: `await api.someFunction()`') 209 | console.log('Abusing this could get you banned from Discord, use at your own risk!') 210 | console.log() 211 | console.log( 212 | 'This script does **not** work with bot accounts! ' + 213 | 'If you have a bot account, use Node.js (or a proper lib like discord.js!) with the modified script ' + 214 | 'https://github.com/rigwild/discord-self-bot-console/discussions/4#discussioncomment-1438231', 215 | ) 216 | console.log() 217 | console.log('Use the `id()` function to update the variable `gid` guild id and `cid` channel id to what you are currently watching.') 218 | console.log('https://github.com/rigwild/discord-self-bot-console') 219 | 220 | id(false) 221 | 222 | // Do not replace configuration when reusing script in same context 223 | // @ts-ignore 224 | if (!authHeader) { 225 | // 226 | // Set your authorization token HERE (or use the auto update, send a message in any chat!) 227 | // 228 | authHeader = '' 229 | autoUpdateToken = true 230 | } 231 | 232 | // @ts-ignore 233 | if (!XMLHttpRequest_setRequestHeader) { 234 | var XMLHttpRequest_setRequestHeader = XMLHttpRequest.prototype.setRequestHeader 235 | } 236 | // Auto update the authHeader when a request with the token is intercepted 237 | XMLHttpRequest.prototype.setRequestHeader = function () { 238 | if (autoUpdateToken && arguments[0] === 'Authorization' && authHeader !== arguments[1]) { 239 | authHeader = arguments[1] 240 | console.log(`Updated the Auth token! <${authHeader.slice(0, 30)}...>`) 241 | } 242 | XMLHttpRequest_setRequestHeader.apply(this, arguments) 243 | } 244 | 245 | if (!module) { 246 | // Allow pasting this script in the console 247 | // @ts-ignore 248 | var module = {} 249 | } 250 | module.exports = { api, id, delay, update_guildId_and_channelId_withCurrentlyVisible } 251 | } 252 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Discord self-bot console 2 | 3 | A simple Discord Self-bot using console. Intended for quick scripts runnable directly from the devtools. Can also be [used with Node.js](https://github.com/rigwild/discord-self-bot-console/discussions/4#discussioncomment-1438231) for quick simple scripts! 4 | 5 | # Disclaimer 6 | 7 | Automating user accounts is against [Discord's Terms of Service](https://discord.com/terms). You might get banned if you abuse it (too much spam, unusual activity). 8 | 9 | # Usage 10 | 11 | 1. Close Discord client (`Quit Discord` in system tray) 12 | 2. Open the settings file, it's location depends on your OS: 13 | - Windows: `%appdata%\discord\settings.json` 14 | - macOS: `~/Library/Application Support/Discord/settings.json` 15 | - Linux: `~/.config/discord/settings.json` 16 | 3. Add `"DANGEROUS_ENABLE_DEVTOOLS_ONLY_ENABLE_IF_YOU_KNOW_WHAT_YOURE_DOING": true,` (this is necessary [since last update](https://www.reddit.com/r/discordapp/comments/sc61n3/comment/hu4fw5x)) 17 | 4. Save the file 18 | 5. Restart Discord client 19 | 6. Open Chrome devtools on Discord using `Ctrl + shift + i` 20 | 7. Go to the console tab and paste the entire [`index.js`](./index.js) script 21 | 8. Send a message in any channel/DM, this will automatically set your auth token into the script 22 | 9. ... 23 | 10. Profit! 24 | 25 | If you want to disable the token from being automatically set, use `autoUpdateToken = false` at the start of your bot script. You can extract the auth token manually by doing this: 26 | 27 | 6. Open Chrome devtools on Discord using `Ctrl + shift + i` 28 | 7. Go to the console tab and paste the entire [`index.js`](./index.js) script 29 | 8. Go to the network tab and send a message in any channel/DM 30 | 9. A new entry should appear, click it then copy the `Authorization` header (in the `Request Headers` section) 31 | 10. Paste it in `authHeader` at the end of the script in the console 32 | 33 | ![How to use video](./howto.gif) 34 | 35 | You can now use any function provided by this script in the console like `await api.someFunction()`. Don't forget `await` or the server's response will not be printed to the console. 36 | 37 | Use the `api.id()` function to update the variable `gid` guild id and `cid` channel id to what you are currently watching. 38 | 39 | **Note:** It's a good idea to wrap your code in its own scope `{ code }` or you might get an error when reusing the same variable names later! 40 | 41 | # Examples 42 | 43 | ## Basic example 44 | 45 | Update `cid` to the channel you are watching, get the last 100 messages, send a message, edit then delete. 46 | 47 | ```js 48 | { 49 | api.id() 50 | // or api.update_guildId_and_channelId_withCurrentlyVisible() 51 | 52 | let channelId = cid 53 | // or let channelId = api.getConfig().channelId 54 | 55 | // Send a message 56 | let sentMessage = await api.sendMessage(channelId, `Hello! 👋 My name is ${user.username}!`) 57 | 58 | await api.delay(2000) 59 | 60 | // Edit a message 61 | let editedMessage = await api.editMessage(channelId, sentMessage.id, 'Hello, edited! ✌️') 62 | 63 | await api.delay(2000) 64 | 65 | // Delete a message 66 | await api.deleteMessage(channelId, editedMessage.id) 67 | 68 | await api.delay(2000) 69 | 70 | // Log the last 100 messages in the console 71 | let messages = await api.getMessages(channelId) 72 | console.log(messages) 73 | } 74 | ``` 75 | 76 | ## Develop in IDE and get typings 77 | 78 | Types are provided at [`types.d.ts`](./types.d.ts). You can use them in your IDE to get typings and develop your script in a better environment. The typings are not fully complete, but it's better than nothing ([PRs are welcome!](https://github.com/rigwild/discord-self-bot-console/issues/60)). 79 | 80 | The script will not work with Node.js (and not supposed to be made for it), you need to copy/paste it in the Discord console to run it. 81 | 82 | Don't forget to paste the full [`index.js`](./index.js) script in the console first to get the `api` variable! 83 | 84 | ```bash 85 | git clone git@github.com:rigwild/discord-self-bot-console.git 86 | # or 87 | git clone https://github.com/rigwild/discord-self-bot-console.git 88 | 89 | cd discord-self-bot-console 90 | code . 91 | ``` 92 | 93 | Doing the following, you should be able to get typings. Open [`myScripts/example.js`](./myScripts/example.js). 94 | 95 | ```js 96 | // myScripts/example.js 97 | 98 | // @ts-check 99 | const { api } = require('../index.js') 100 | 101 | // Copy paste the below code inside the Discord console 102 | ;(async () => { 103 | const user = await api.getCurrentUser() 104 | 105 | console.log(user.username) 106 | })() 107 | ``` 108 | 109 | ## Send an embed 110 | 111 | **SENDING EMBEDS AS A USER ACCOUNT IS NOT POSSIBLE ANYMORE, DISCORD UPDATED ITS API (see [this reddit post](https://web.archive.org/web/20220209223900/https://www.reddit.com/r/Discord_selfbots/comments/sa0hc2/discord_embeds_patched/))** 112 | 113 | See [How to send an embed?](https://github.com/rigwild/discord-self-bot-console/discussions/6#discussioncomment-1779730) 114 | 115 | ![embed example](https://user-images.githubusercontent.com/26366184/145406696-8734410a-fc08-40e3-abf8-de12d8d89db9.png) 116 | 117 | ## Run in Node.js 118 | 119 | See [Can I run it without opening Discord?](https://github.com/rigwild/discord-self-bot-console/discussions/4#discussioncomment-1438231) 120 | 121 | ## Use multiple account tokens 122 | 123 | See [Is it possible to use multiple tokens?](https://github.com/rigwild/discord-self-bot-console/discussions/21#discussioncomment-3048097) 124 | 125 | ## Use a bot account 126 | 127 | This specific script only works for user accounts. If you want to use a bot account, you need to use the provided Node.js version (see above). 128 | 129 | [Why can't I use a bot account with the Discord client?](https://github.com/rigwild/discord-self-bot-console/discussions/4#discussioncomment-1999680) 130 | 131 | ## List all custom emojis urls from all guilds you are a member of 132 | 133 | See [How to list all custom emojis urls from all guilds you are a member of](https://github.com/rigwild/discord-self-bot-console/discussions/2#discussioncomment-986874) 134 | 135 | ## React with emoji or custom emoji 136 | 137 | See [How to react with emoji or custom emoji ? 🤔](https://github.com/rigwild/discord-self-bot-console/discussions/23#discussioncomment-3135937) 138 | 139 | ## Send slash commands (/something) 140 | 141 | See [Send slash commands (/something)](https://github.com/rigwild/discord-self-bot-console/discussions/31#discussioncomment-3442385) 142 | 143 | ## Add and remove reactions to every message on a channel 144 | 145 | See [Add and remove reactions to every message on a channel](https://github.com/rigwild/discord-self-bot-console/discussions/32#discussioncomment-3620234) 146 | 147 | ## Open a thread 148 | 149 | ```js 150 | { 151 | api.id() 152 | let channelId = cid 153 | 154 | const sentMessage = await api.sendMessage(channelId, 'Hello, please open a thread here! 💕') 155 | 156 | // A new thread opened in reply to a message 157 | const createdThread = await api.createThread(channelId, sentMessage.id, 'A cool thread 🤔') 158 | const sentMessage2 = await api.sendMessage(createdThread.id, 'Here it is, this is a thread! 😁') 159 | await api.replyToMessage(createdThread.id, sentMessage2.id, 'Thanks! ✌️') 160 | 161 | // A clean new thread without any message in it (not opened on any message!) 162 | await api.createThreadWithoutMessage(channelId, 'Another thread 😁') 163 | } 164 | ``` 165 | 166 | ## Farm XP 167 | 168 | Send a `message` to a channel (`channelId`) every minute then delete it (useful for XP farming in some servers). 169 | 170 | You can use `loop = false` at any time to stop it. 171 | 172 | ```js 173 | { 174 | api.id() 175 | let channelId = cid 176 | let message = 'Hi, I like spamming 🦜' 177 | 178 | var loop = true 179 | let count = 0 180 | while (loop) { 181 | const sentMessage = await api.sendMessage(channelId, message) 182 | await api.deleteMessage(channelId, sentMessage.id) 183 | console.log(`Sent ${++count} messages`) 184 | await api.delay(61000) // 61 seconds 185 | } 186 | } 187 | ``` 188 | 189 | To farm XP with multiple accounts, check [Is it possible to farm XP using multiple tokens?](https://github.com/rigwild/discord-self-bot-console/discussions/21#discussioncomment-3136195) 190 | 191 | ## Clear messages of user 192 | 193 | Delete the `amount` messages from user (`userId`) sent to a channel/DM (`channelId`) appearing before message (`beforeMessageId`) and wait `delayMs` milliseconds everytime. 194 | 195 | I use sometimes to fully clear my DMs as Discord does not offer it as a feature. 196 | 197 | You can use `loop = false` at any time to stop it. 198 | 199 | Discord recently made its rate limiting strictier. I recommend 1100ms as a minimum to not get rate limited. Make it even bigger if you are affraid of getting banned. 200 | 201 | ```js 202 | { 203 | api.id() 204 | let channelId = cid 205 | let userId = '012345678987654321' 206 | let amount = 99999999 207 | let delayMs = 1100 208 | 209 | let beforeMessageId = '8999999999999999999' // Leave it like this to delete from latest 210 | 211 | let deletionCount = 0 212 | var loop = true 213 | while (loop) { 214 | const messages = await api.getMessages(channelId, 100, { before: beforeMessageId }) 215 | 216 | // We reached the start of the conversation 217 | if (messages.length < 100 && messages.filter(x => x.author.id === userId && (x.type === 0 || x.type === 19)).length === 0) { 218 | loop = false 219 | console.log(`[${deletionCount}/${amount}] Reached the start of the conversations! Ending.`) 220 | continue 221 | } 222 | 223 | // Update last message snowflake for next iteration 224 | beforeMessageId = messages[0].id 225 | 226 | for (const aMessage of messages) { 227 | if (loop === false) break 228 | 229 | // Check if the max amount was reached 230 | if (deletionCount >= amount) { 231 | loop = false 232 | console.log(`[${deletionCount}/${amount}] Deleted the requested amount of messages! Ending.`) 233 | break 234 | } 235 | 236 | // Update last message snowflake for next iteration 237 | beforeMessageId = aMessage.id 238 | 239 | // Check if the message should be deleted 240 | if (aMessage.author.id === userId && (aMessage.type === 0 || aMessage.type === 19)) { 241 | await api.deleteMessage(channelId, aMessage.id) 242 | deletionCount++ 243 | console.log(`[${deletionCount}/${amount}] Deleted a message!`) 244 | if (deletionCount < amount) await api.delay(delayMs) 245 | } 246 | } 247 | await api.delay(delayMs) 248 | } 249 | } 250 | ``` 251 | 252 | ## Do anything to every messages in a text channel 253 | 254 | Pass your custom function! 255 | 256 | This example will apply all reactions already there on all messages, then add 👋 if message says `hi!!` or `hello`. 257 | 258 | ```js 259 | { 260 | api.id() 261 | let channelId = cid 262 | let amount = 99999999 263 | let delayMs = 500 264 | 265 | let actionFn = async (channelId, message) => { 266 | // 267 | // Your custom code here 268 | // 269 | let wasActiontriggered = false 270 | 271 | // Copy all reactions already present on message 272 | for (const reaction of message.reactions || []) { 273 | let reactionToAdd = reaction.emoji.id ? `${reaction.emoji.name}:${reaction.emoji.id}` : reaction.emoji.name 274 | await api.addReaction(channelId, message.id, reactionToAdd) 275 | wasActiontriggered = true 276 | await api.delay(delayMs) 277 | } 278 | 279 | // If person said `hello!!!` or `hi!`, react with waving hand 👋 280 | if (message.content.match(/^(?:hi|hello)!*$/)) { 281 | await api.addReaction(channelId, message.id, '👋') 282 | wasActiontriggered = true 283 | } 284 | 285 | // Return a boolean indicating if you did something to the message 286 | // If true, will log and apply delay 287 | return wasActiontriggered 288 | } 289 | 290 | let beforeMessageId = '8999999999999999999' // Leave it like this to react from latest 291 | 292 | let count = 0 293 | var loop = true 294 | while (loop) { 295 | const messages = await api.getMessages(channelId, 100, { before: beforeMessageId }) 296 | 297 | // We reached the start of the conversation 298 | if (messages.length < 100 && messages.filter(x => x.type === 0 || x.type === 19).length === 0) { 299 | loop = false 300 | console.log(`[${count}/${amount}] Reached the start of the conversation! Ending.`) 301 | continue 302 | } 303 | 304 | // Update last message snowflake for next iteration 305 | beforeMessageId = messages[0].id 306 | 307 | for (const aMessage of messages) { 308 | if (loop === false) break 309 | 310 | // Check if the max amount was reached 311 | if (count >= amount) { 312 | loop = false 313 | console.log(`[${count}/${amount}] Treated the requested amount of messages! Ending.`) 314 | break 315 | } 316 | 317 | // Update last message snowflake for next iteration 318 | beforeMessageId = aMessage.id 319 | 320 | // Check if the message should be reacted 321 | if (aMessage.type === 0 || aMessage.type === 19) { 322 | let wasActiontriggered = await actionFn(channelId, aMessage) 323 | // Apply delay and log only if return true 324 | if (wasActiontriggered) { 325 | count++ 326 | console.log(`[${count}/${amount}] Treated a message! ID=${aMessage.id}`) 327 | if (count < amount) await api.delay(delayMs) 328 | } 329 | } 330 | } 331 | await api.delay(delayMs) 332 | } 333 | } 334 | ``` 335 | 336 | # FAQ 337 | 338 | ## Will I get banned if I do x? 339 | 340 | I don't know, maybe. I have used lots of scripts in the past, often deleted 100k+ messages of mine accross private messages and servers and never got banned, ever. 341 | 342 | But I can't guarantee anything. Use at your own risk. 343 | 344 | Automating user accounts is againt [Discord's Terms of Service](https://discord.com/terms). 345 | 346 | ## Listen to events, do some advanced stuff 347 | 348 | This is intended for small scripts, not to implement a full-featured bot. 349 | 350 | If you need to listen to events or do something more advanced you can use the [discord.js](https://github.com/discordjs/discord.js) package with your user token (with v11.3.2 and below though, they deprecated user token support starting v11.4.0!). 351 | 352 | **Note:** As they don't support user bots anymore, it may break at any time (with Discord changing their APIs). 353 | 354 | ## Can it do x? Can you help me? 355 | 356 | Post your requests in the [Discussions](https://github.com/rigwild/discord-self-bot-console/discussions) tab. Please search if your request was not mentionned in an earlier post before asking. 357 | 358 | ## I made a nice/useful script, can I share? 359 | 360 | Of course! Post it in the [Discussions](https://github.com/rigwild/discord-self-bot-console/discussions) tab. Please search if a similar script was shared earlier before posting. 361 | 362 | ## Why this repo? 363 | 364 | Initially, this was posted as [a gist for myself](https://gist.github.com/rigwild/28f5d9479e3e122070e27db84e104719). As there's interest for such a thing, I figured out making a proper repo was better to share scripts. 365 | 366 | # API 367 | 368 | ## Full list 369 | 370 | The full list of available functions is available in [`types.d.ts`](./types.d.ts). 371 | 372 | ```js 373 | api.id() 374 | api.update_guildId_and_channelId_withCurrentlyVisible() 375 | api.delay(ms) 376 | api.apiCall(apiPath, body, method = 'GET') 377 | 378 | api.getMessages(channelOrThreadId, limit?, params = {}) 379 | api.sendMessage(channelOrThreadId, message, tts?, body = {}) 380 | api.replyToMessage(channelOrThreadId, repliedMessageId, message, tts?, body = {}) 381 | api.editMessage(channelOrThreadId, messageId, newMessage, body = {}) 382 | api.deleteMessage(channelOrThreadId, messageId) 383 | 384 | api.createThread(channelId, toOpenThreadInmessageId, name, autoArchiveDuration?, body = {}) 385 | 386 | api.getCurrentUser() 387 | api.editCurrentUser(username?, bio?, body = {}) 388 | // and more... 389 | ``` 390 | 391 | ## `api.delay(ms)` 392 | 393 | `api.delay(ms: number) => Promise` 394 | 395 | Wait for `ms` milliseconds. 396 | 397 | ```js 398 | await delay(1500) 399 | await api.delay(1500) 400 | ``` 401 | 402 | ## `api.downloadFileByUrl(url, filename)` 403 | 404 | `api.downloadFileByUrl(url: string, filename: string) => Promise` 405 | 406 | Download a file at `url` and name it `filename`. 407 | 408 | See [How to download attachments (file, image) from a message?](https://github.com/rigwild/discord-self-bot-console/discussions/66) 409 | 410 | ```js 411 | await api.downloadFileByUrl('https://cataas.com/cat', 'cat.png') 412 | await api.downloadFileByUrl(messages[0].attachments[0].url, messages[0].attachments[0].filename) 413 | ``` 414 | 415 | ## `api.id()` 416 | 417 | `api.id() => void` (old alias) 418 | 419 | `api.update_guildId_and_channelId_withCurrentlyVisible() => void` 420 | 421 | Update the variable `gid` guild id and `cid` channel id to what you are currently watching in the Discord client. 422 | 423 | ```js 424 | id() 425 | api.id() 426 | ``` 427 | 428 | ## `api.getConfig()` 429 | 430 | ```ts 431 | api.getConfig(): Readonly<{ 432 | authHeader: string, 433 | autoUpdateToken: boolean, 434 | guildId: string, 435 | channelId: string, 436 | gid: string, 437 | cid: string, 438 | }> 439 | ``` 440 | 441 | Returns the current configuration, read-only. Useful if you want to use typings in your IDE. 442 | 443 | Set configuration 444 | 445 | ``` 446 | api.setConfigAutoUpdateToken(autoUpdateToken: boolean): void 447 | api.setConfigGid(guildId: string): void 448 | api.setConfigGuildId(guildId: string): void 449 | api.setConfigCid(channelId: string): void 450 | api.setConfigChannelId(channelId: string): void 451 | ``` 452 | 453 | ## Variables 454 | 455 | - `authHeader`: You Discord account auth token 456 | - `autoUpdateToken`: If true, the token will be automatically updated in the bot when a request is sent from the client (e.g sending a message) 457 | - `gid`: Current guild id (update to what you are currently watching using `api.id()`) 458 | - `cid`: Current channel id (update to what you are currently watching using `api.id()`) 459 | 460 | # License 461 | 462 | [The MIT License](./LICENSE) 463 | --------------------------------------------------------------------------------