├── _config.yml ├── .gitattributes ├── resources ├── icons │ ├── logo.icns │ ├── logo.ico │ ├── logo.png │ ├── minimizeWindow.svg │ ├── snapWindowSize.svg │ ├── PFPMask.svg │ ├── closeWindow.svg │ ├── GuildNewsChannel.svg │ ├── GuildVoiceChannel.svg │ ├── checkmark.svg │ ├── GuildVoiceChannelBlocked.svg │ ├── GuildTextChannel.svg │ ├── pullOut.svg │ ├── categoryArrow.svg │ ├── GuildTextChannelBlocked.svg │ ├── logo.svg │ ├── embedIcon.svg │ └── logoLarge.svg ├── images │ ├── Barry.png │ └── default.png └── fonts │ ├── whitney.ttf │ ├── UniSans-Heavy.ttf │ ├── whitney-light.otf │ ├── UniSans-Regular.otf │ ├── UniSans-SemiBold.otf │ └── whitney-medium.otf ├── .gitignore ├── themes └── template.css ├── css ├── fonts.css ├── titlebar.css ├── dmList.css ├── rcMenu.css ├── memberList.css ├── embeds.css ├── memberMenu.css ├── miscs.css ├── messageBar.css ├── channelList.css ├── guildList.css ├── textFormat.css ├── master.css └── splashScreen.css ├── js ├── embedMenu.js ├── titlebar.js ├── guildSelect.js ├── handleThemes.js ├── splashScreen.js ├── customSettingsFuncs.js ├── command.js ├── buildMemberMenu.js ├── rcMenuFuncs.js ├── handleScripts.js ├── parseFunctions.js ├── app.js ├── addMemberList.js ├── setToken.js ├── typingStatus.js ├── buildTeamMemberCards.js ├── messageGeneration.js ├── channelSelect.js ├── errorHandler.js ├── parseMessage.js ├── channelList.js ├── guildList.js └── dmList.js ├── package.json ├── .github └── ISSUE_TEMPLATE │ └── bug_report.md ├── scripts ├── template.js └── how_to_write_livebot_scripts.md ├── start.js └── README.md /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.exe filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /resources/icons/logo.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebOuellette/LiveBot/HEAD/resources/icons/logo.icns -------------------------------------------------------------------------------- /resources/icons/logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebOuellette/LiveBot/HEAD/resources/icons/logo.ico -------------------------------------------------------------------------------- /resources/icons/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebOuellette/LiveBot/HEAD/resources/icons/logo.png -------------------------------------------------------------------------------- /resources/images/Barry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebOuellette/LiveBot/HEAD/resources/images/Barry.png -------------------------------------------------------------------------------- /resources/fonts/whitney.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebOuellette/LiveBot/HEAD/resources/fonts/whitney.ttf -------------------------------------------------------------------------------- /resources/images/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebOuellette/LiveBot/HEAD/resources/images/default.png -------------------------------------------------------------------------------- /resources/fonts/UniSans-Heavy.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebOuellette/LiveBot/HEAD/resources/fonts/UniSans-Heavy.ttf -------------------------------------------------------------------------------- /resources/fonts/whitney-light.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebOuellette/LiveBot/HEAD/resources/fonts/whitney-light.otf -------------------------------------------------------------------------------- /resources/fonts/UniSans-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebOuellette/LiveBot/HEAD/resources/fonts/UniSans-Regular.otf -------------------------------------------------------------------------------- /resources/fonts/UniSans-SemiBold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebOuellette/LiveBot/HEAD/resources/fonts/UniSans-SemiBold.otf -------------------------------------------------------------------------------- /resources/fonts/whitney-medium.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SebOuellette/LiveBot/HEAD/resources/fonts/whitney-medium.otf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Folders 2 | node_modules 3 | resources/app 4 | 5 | # File types to emit 6 | *.log 7 | *.bat 8 | *.db 9 | *.exe 10 | 11 | # Some other files that don't need to be uploaded to github 12 | package-lock.json 13 | logins.json 14 | scripts/* 15 | themes/* 16 | !themes/template.css 17 | !scripts/template.js -------------------------------------------------------------------------------- /themes/template.css: -------------------------------------------------------------------------------- 1 | /* It's as easy as just finding the class or the ID and changing the properties 2 | You can also use github or some other site so people don't have to download the theme every time 3 | Don't forget if you do that to give the user some way to customize it with global css variables*/ 4 | #memberBar { 5 | background-color: darkred; 6 | } 7 | -------------------------------------------------------------------------------- /css/fonts.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: UniSans-regular; 3 | src: url("../resources/fonts/UniSans-Regular.otf"); 4 | } 5 | 6 | @font-face { 7 | font-family: UniSans-bold; 8 | src: url("../resources/fonts/UniSans-SemiBold.otf"); 9 | } 10 | 11 | @font-face { 12 | font-family: Whitney; 13 | src: url("../resources/fonts/whitney-medium.otf"); 14 | font-weight: 100; 15 | } 16 | 17 | @font-face { 18 | font-family: Whitney-light; 19 | src: url("../resources/fonts/whitney-light.otf"); 20 | font-weight: 100; 21 | } 22 | 23 | @font-face { 24 | font-family: UniSans; 25 | src: url(../resources/fonts/UniSans-Heavy.ttf); 26 | } 27 | -------------------------------------------------------------------------------- /js/embedMenu.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Sebastian Ouellette 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | function buildEmbedMenu() {} 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "livebot", 3 | "version": "v1.3.2-alpha", 4 | "description": "Discord for bots!", 5 | "main": "start.js", 6 | "scripts": { 7 | "test": "electron .", 8 | "start": "electron ." 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/SebOuellette/LiveBot.git" 13 | }, 14 | "keywords": [ 15 | "discord", 16 | "livebot", 17 | "discord-livebot" 18 | ], 19 | "author": "SebOuellette", 20 | "license": "Apache-2.0", 21 | "bugs": { 22 | "url": "https://github.com/SebOuellette/LiveBot/issues" 23 | }, 24 | "homepage": "https://github.com/SebOuellette/LiveBot#readme", 25 | "dependencies": { 26 | "discord.js": "^14.18.0", 27 | "electron": "^35.0.0" 28 | }, 29 | "devDependencies": { 30 | "@electron/packager": "^18.3.6" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: A template for bug reports 4 | title: Issue name goes here 5 | labels: Bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | #### What branch/release are you using? 11 | 12 | - Branch/Release: 13 | 14 | #### Errors 15 | 16 | 17 | #### Steps required to reproduce the bug 18 | 19 | - Steps: 20 | 21 | 22 | #### Screenshot (In most cases required) 23 | 25 | - Screenshot: 26 | 27 | #### Description 28 | 29 | -------------------------------------------------------------------------------- /css/titlebar.css: -------------------------------------------------------------------------------- 1 | #titlebar { 2 | height: 20px !important; 3 | width: 100vw; 4 | background-color: #202225; 5 | margin: -8px 0 0 -8px; 6 | -webkit-user-select: none; 7 | user-select: none; 8 | -webkit-app-region: drag; 9 | } 10 | 11 | #titlebar span { 12 | font-family: UniSans-bold, "Helvetica Neue", Helvetica, Arial, sans-serif; 13 | color: #aaa; 14 | font-weight: lighter; 15 | letter-spacing: 2px; 16 | font-size: 13px; 17 | margin-left: 10px; 18 | user-select: none; 19 | display: inline-block; 20 | } 21 | 22 | /* Titlebar buttons */ 23 | 24 | .titleButton { 25 | -webkit-app-region: no-drag; 26 | height: 100%; 27 | width: 25px; 28 | right: 2px; 29 | float: right; 30 | cursor: pointer; 31 | } 32 | 33 | .titleButton:hover { 34 | background-color: #282b2e; 35 | } 36 | 37 | #exit:hover { 38 | background-color: #ed4245; 39 | } 40 | 41 | .titleButton img { 42 | margin-left: 50%; 43 | transform: translate(calc(-50% - 1px)); 44 | } 45 | -------------------------------------------------------------------------------- /js/titlebar.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Sebastian Ouellette 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | const { ipcRenderer } = require('electron'); 18 | 19 | document.getElementById('minimize').addEventListener('click', function (e) { 20 | ipcRenderer.send('titlebar', 'minimize'); 21 | }); 22 | 23 | document.getElementById('screenSnap').addEventListener('click', function (e) { 24 | ipcRenderer.send('titlebar', 'screenSnap'); 25 | }); 26 | 27 | document.getElementById('exit').addEventListener('click', function (e) { 28 | ipcRenderer.send('titlebar', 'exit'); 29 | }); 30 | -------------------------------------------------------------------------------- /css/dmList.css: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Sebastian Ouellette 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. */ 14 | 15 | .dmChannel { 16 | user-select: none; 17 | margin: 2px 0 0 -5px; 18 | padding: 5px 20px; 19 | cursor: pointer; 20 | border-radius: 3px; 21 | height: 25px; 22 | } 23 | 24 | .dmChannelImage { 25 | height: 25; 26 | width: 25; 27 | border-radius: 50%; 28 | user-select: none; 29 | position: relative; 30 | margin-bottom: 6px; 31 | } 32 | 33 | #dmChannel-elements h5 { 34 | transform: translateY(-5px); 35 | display: inline-block; 36 | margin: 0 !important; 37 | padding: 7px 0 7px 10px; 38 | width: 170px; 39 | } 40 | 41 | .dmChannel:hover { 42 | background-color: #34373c; 43 | color: #b4b8bc; 44 | } 45 | 46 | /* Channel ... formatting */ 47 | .dmChannel h5 { 48 | max-width: calc(100% - 40px); 49 | overflow: hidden; 50 | text-overflow: ellipsis; 51 | white-space: nowrap; 52 | user-select: none; 53 | } 54 | 55 | .selectedChan { 56 | background-color: #393c43 !important; 57 | color: white !important; 58 | } 59 | 60 | .dmChannel.newMsg { 61 | color: white !important; 62 | } 63 | -------------------------------------------------------------------------------- /resources/icons/minimizeWindow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 52 | -------------------------------------------------------------------------------- /resources/icons/snapWindowSize.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 52 | -------------------------------------------------------------------------------- /scripts/template.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Sebastian Ouellette 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | module.exports.info = { 16 | author: 'Your discord username and tag (ex: SharkFin#1504)', 17 | title: 'Name of your script', 18 | description: 'Description of your script', 19 | version: '1.0.0', 20 | }; 21 | 22 | module.exports.start = () => { 23 | /* 24 | Global Variables Available: 25 | bot - the Discord.client() that is signed in 26 | selectedChan - the current channel you're in (Object, not the DOM) 27 | selectedGuild - the current guild you're in 28 | selectedVoice - the last selected voice channel 29 | This isn't really supported, but it's here for future use. If you need better accessibility with this variable, 30 | ask for it in the discord (which you can find on the readme) and we'll see what we can do 31 | */ 32 | 33 | // Your code goes here 34 | 35 | myFunction(); 36 | }; 37 | 38 | module.exports.stop = () => { 39 | // This code will call when the script should be unloaded 40 | }; 41 | 42 | function myFunction() { 43 | // You can make normal functions and call them from start() 44 | } 45 | -------------------------------------------------------------------------------- /resources/icons/PFPMask.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 49 | -------------------------------------------------------------------------------- /resources/icons/closeWindow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 60 | -------------------------------------------------------------------------------- /resources/icons/GuildNewsChannel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /css/rcMenu.css: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Sebastian Ouellette 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. */ 14 | 15 | #rcMenu { 16 | position: fixed; 17 | width: 160px; 18 | background-color: #18191c; 19 | border-radius: 5px; 20 | z-index: 100; 21 | display: none; 22 | padding: 5px 0; 23 | } 24 | 25 | #rcMenu.open { 26 | display: block; 27 | } 28 | 29 | /* Options */ 30 | .rcOption { 31 | padding: 8px 10px; 32 | margin: 0 8px; 33 | color: #b9bbbe; 34 | border-radius: 3px; 35 | cursor: pointer; 36 | font-size: 15px; 37 | } 38 | 39 | .rcBreak { 40 | border: none; 41 | padding: 2px 0 0 0; 42 | background-color: #393c43; 43 | margin: 0px 15px 0px 15px; 44 | } 45 | 46 | .rcOption:hover { 47 | color: white !important; 48 | background-color: #5c6fb1; 49 | } 50 | 51 | .rcOption.red { 52 | color: #f04747; 53 | } 54 | 55 | .rcOption:hover.red { 56 | background-color: #f04747; 57 | } 58 | 59 | .rcOption.greyed { 60 | color: #b9bbbe !important; 61 | cursor: default; 62 | background-color: transparent !important; 63 | opacity: 0.4; 64 | } 65 | 66 | /* Edit message textfield */ 67 | .editTextarea::-webkit-scrollbar { 68 | width: 0; 69 | } 70 | 71 | .editTextarea { 72 | width: calc(100% - 50px); 73 | display: block; 74 | background-color: #484b51; 75 | border: none; 76 | padding: 10px; 77 | border-radius: 5px; 78 | outline: none; 79 | color: white; 80 | resize: none; 81 | position: relative; 82 | margin: 5px 0; 83 | } 84 | 85 | .firstmsg .editTextarea { 86 | margin-top: -26px; 87 | } 88 | -------------------------------------------------------------------------------- /js/guildSelect.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Sebastian Ouellette 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | // Selecting new guild 18 | let guildSelect = (g, img) => { 19 | // Update the selected guild 20 | selectedGuild = g; 21 | 22 | //document.getElementById('guildIndicator').style.display = 'block'; 23 | if (oldimg) oldimg.classList.remove('selectedGuild'); 24 | img.classList.add('selectedGuild'); 25 | 26 | // this should be done another way 27 | document.getElementById('guildIndicator').style.marginTop = `${ 28 | img.offsetTop - 64 29 | }px`; 30 | document.getElementById('guildIndicator').style.display = 'block'; 31 | 32 | oldimg = img; 33 | 34 | // Make the text display 'block' (default) incase switching from DMs 35 | document.getElementById('members-text').style.display = 'block'; 36 | document.getElementById('guildName').classList.remove('directMsg'); 37 | // Set the count to begin 38 | document.getElementById('members-count').innerText = g.memberCount; 39 | 40 | // Update guild profile name 41 | let name = g.name; 42 | document.getElementById('guildName').innerText = name; 43 | 44 | // Update guild profile image 45 | let icon = g.iconURL(); 46 | if (!icon) { 47 | icon = 'resources/images/default.png'; 48 | } 49 | document.getElementById('guildImg').src = icon; 50 | 51 | // Clear the message list 52 | let messages = document.getElementById('message-list'); 53 | while (messages.firstChild) { 54 | messages.removeChild(messages.firstChild); 55 | } 56 | 57 | // Create the channels 58 | createChannels(g); 59 | }; 60 | -------------------------------------------------------------------------------- /js/handleThemes.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Sebastian Ouellette 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | let loadedThemes = []; 18 | 19 | function loadThemes() { 20 | // Set the directory of the themes 21 | let dir = './themes/'; 22 | 23 | // Get all the files in the themes folder 24 | let files = fs.readdirSync(dir); 25 | 26 | // Remove the template file out of the equation if there is one 27 | if (files.includes('template.css')) 28 | files.splice(files.indexOf('template.css'), 1); 29 | 30 | // Don't bother loading if there is nothing 31 | if (!files.length) return; 32 | 33 | console.log('Loading themes...'); 34 | 35 | files.forEach((file) => { 36 | if (!file.endsWith('.css')) return; 37 | 38 | let script = document.createElement('link'); 39 | script.id = file; 40 | script.rel = 'stylesheet'; 41 | script.href = dir + file; 42 | 43 | loadedThemes.push([script, file]); 44 | 45 | document.head.appendChild(script); 46 | console.log('\n%c ' + file + ' loaded', 'color:Lime'); 47 | }); 48 | } 49 | 50 | function unloadThemes() { 51 | // If there's nothing there don't bother 52 | if (!loadedThemes.length) return; 53 | 54 | // Display that the themes are being unloaded 55 | console.log('Unloading themes...'); 56 | 57 | // Go through each of the elements that are saved in the memory 58 | for (element of loadedThemes) { 59 | document.head.removeChild(element[0]); 60 | console.log('%c ' + element[1] + ' unloaded', 'color:Red'); 61 | } 62 | // Delete all the elements that were once loaded 63 | loadedThemes = []; 64 | } 65 | -------------------------------------------------------------------------------- /js/splashScreen.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Sebastian Ouellette 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | function hideSplashScreen() { 18 | if (!settings.options.splash) return; 19 | 20 | settings.options.splash = false; 21 | document.getElementById('splashLoading').style.opacity = '0'; 22 | setTimeout( 23 | () => (document.getElementById('percentageText').style.opacity = '0'), 24 | 2000 25 | ); 26 | setTimeout( 27 | () => (document.getElementById('loadingBar').style.opacity = '0'), 28 | 2000 29 | ); 30 | setTimeout( 31 | () => (document.getElementById('splashScreen').style.opacity = '0'), 32 | 3000 33 | ); 34 | setTimeout(() => { 35 | document.getElementById('splashScreen').style.visibility = 'hidden'; 36 | 37 | // Reset opacity of inner elements 38 | setTimeout(() => { 39 | document.getElementById('percentageText').style.opacity = '1'; 40 | document.getElementById('loadingBar').style.opacity = '1'; 41 | document.getElementById('splashScreen').style.opacity = '1'; 42 | setLoadingPerc(0); 43 | }, 1500); 44 | }, 3500); 45 | } 46 | 47 | async function showSplashScreen(token = undefined, save = false) { 48 | if (settings.options.splash) return; 49 | 50 | if (!token) return; 51 | let error; 52 | if (save) error = await saveToken(token); 53 | else error = await setToken(token); 54 | 55 | if (!error[0]) { 56 | settings.options.splash = true; 57 | document.getElementById('splashScreen').style.visibility = 'visible'; 58 | } else { 59 | errorHandler(error[1]); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /js/customSettingsFuncs.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Sebastian Ouellette 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | // Set the activity 18 | function setActivity(dropdowns, activityName, streamurl) { 19 | let status = dropdowns[0]; 20 | let activity = dropdowns[1]; 21 | 22 | if (status.includes('Not')) { 23 | status = 'dnd'; 24 | } 25 | 26 | if (activity == 'None') 27 | bot.user.setPresence({ 28 | activities: [], 29 | status: status.toLowerCase(), 30 | }); 31 | else 32 | bot.user.setPresence({ 33 | activities: [ 34 | { 35 | name: activityName, 36 | type: Discord.ActivityType[activity], 37 | url: streamurl || undefined, 38 | }, 39 | ], 40 | status: status.toLowerCase(), 41 | }); 42 | } 43 | 44 | // Set the new username 45 | async function setUsername(name) { 46 | try { 47 | if (!name.replace(/ |#/, '').length) { 48 | errorHandler('EMPTY-NAME'); 49 | throw 'EMPTY-NAME'; 50 | } 51 | await bot.user.setUsername(name).catch((err) => { 52 | errorHandler(err); 53 | throw err; 54 | }); 55 | document.getElementById('userCardName').innerText = bot.user.username; 56 | } catch (err) { 57 | document 58 | .getElementsByClassName('newNameInput')[0] 59 | .animate(animations.flashRed, { duration: 500 }); 60 | } 61 | } 62 | 63 | // Generate the invite code 64 | function generateInvite(items) { 65 | let sum; 66 | if (items.length) { 67 | sum = items.reduce((a, b) => a + b); 68 | } else { 69 | sum = 0; 70 | } 71 | let invite = `https://discordapp.com/oauth2/authorize?client_id=${bot.user.id}&scope=bot&permissions=${sum}`; 72 | console.log(`Copied to Clipboard: ${invite}`); 73 | clipboard.writeText(invite); 74 | } 75 | -------------------------------------------------------------------------------- /resources/icons/GuildVoiceChannel.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /js/command.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Sebastian Ouellette 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | let command = (text, del = NaN) => { 18 | let div = document.createElement('div'); 19 | div.id = 'messageCont'; 20 | div.classList.add('barryCommand'); 21 | div.style.backgroundColor = 'rgba(50,50,50,0.4)'; 22 | document.getElementById('message-list').appendChild(div); 23 | 24 | let img = document.createElement('img'); 25 | img.classList.add('messageImg'); 26 | img.classList.add('barryImg'); 27 | img.src = './resources/images/Barry.png'; 28 | div.appendChild(img); 29 | 30 | let inlineContainer = document.createElement('div'); 31 | inlineContainer.classList.add('inlineMsgCont'); 32 | div.appendChild(inlineContainer); 33 | 34 | let name = document.createElement('p'); 35 | let username; 36 | 37 | username = document.createTextNode('Barry'); 38 | name.appendChild(username); 39 | name.id = 'messageUsername'; 40 | name.style.color = `#999999`; 41 | inlineContainer.appendChild(name); 42 | 43 | let text2 = document.createElement('p'); 44 | 45 | console.log(text); 46 | if (text.split('\n').length > 1) { 47 | for (let i = 0; i < text.split('\n').length; i++) { 48 | text2.innerHTML += parseMessage(text.split('\n')[i]); 49 | text2.classList.add('messageText'); 50 | 51 | let contentBreak = document.createElement('br'); 52 | text2.appendChild(contentBreak); 53 | } 54 | } else { 55 | text2.innerHTML = parseMessage(text); 56 | text2.classList.add('messageText'); 57 | } 58 | inlineContainer.appendChild(text2); 59 | document.getElementById('message-list').scrollTop = 60 | document.getElementById('message-list').scrollHeight; 61 | document.getElementById('msgbox').value = ''; 62 | barry = true; 63 | if (del && del > 1) 64 | setTimeout(() => { 65 | document.getElementById('message-list').removeChild(div); 66 | }, del); 67 | }; 68 | -------------------------------------------------------------------------------- /css/memberList.css: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Sebastian Ouellette 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. */ 14 | 15 | #memberBar { 16 | height: calc(100vh - 40px); 17 | width: 201px; 18 | margin-top: calc(-100vh + 60px); 19 | margin-left: calc(100vw - 209px); 20 | background-color: #2f3136; 21 | overflow-y: auto; 22 | padding: 20px 10px 0 0px; 23 | overflow-y: scroll; 24 | } 25 | 26 | #memberBar::-webkit-scrollbar { 27 | background-color: transparent; 28 | width: 6px; 29 | } 30 | 31 | #memberBar::-webkit-scrollbar-track { 32 | background-color: transparent; 33 | } 34 | 35 | #memberBar::-webkit-scrollbar-thumb { 36 | background-color: #25272b; 37 | border-radius: 3px; 38 | } 39 | 40 | .roleContainer { 41 | margin-bottom: 20px; 42 | margin-left: 15px; 43 | } 44 | 45 | .roleTitle { 46 | font-family: Whitney, "Helvetica Neue", Helvetica, Arial, sans-serif; 47 | color: #8e9297; 48 | font-size: 12px; 49 | text-transform: uppercase; 50 | } 51 | 52 | .mLUserDiv { 53 | margin-left: -10px; 54 | padding: 7px 10px 4px 10px; 55 | user-select: none; 56 | border-radius: 3px; 57 | } 58 | 59 | .mLUserDivOffline { 60 | opacity: 0.3; 61 | } 62 | 63 | .mLUserDiv:hover { 64 | cursor: pointer; 65 | opacity: 1; 66 | background-color: #34373c; 67 | } 68 | 69 | .mLIcon { 70 | height: 30px; 71 | border-radius: 50%; 72 | display: inline-block; 73 | margin-right: 7px; 74 | -webkit-mask-image: url("../resources/icons/PFPMask.svg"); 75 | mask-image: url("../resources/icons/PFPMask.svg"); 76 | -webkit-mask-size: contain; 77 | mask-size: contain; 78 | } 79 | 80 | .mLUsername { 81 | font-family: Whitney, "Helvetica Neue", Helvetica, Arial, sans-serif; 82 | font-weight: 100; 83 | font-size: 16px; 84 | color: #8e9297; 85 | margin: 10px 0 0 0; 86 | padding: 0; 87 | display: inline-block; 88 | transform: translateY(-9px); 89 | text-overflow: ellipsis; 90 | max-width: calc(100% - 40px); 91 | overflow: hidden; 92 | white-space: nowrap; 93 | } 94 | 95 | #offlineUsersTransparent { 96 | opacity: 0.3; 97 | } 98 | -------------------------------------------------------------------------------- /css/embeds.css: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Sebastian Ouellette 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. */ 14 | 15 | .embed { 16 | margin: 0 0 7px 0; 17 | background-color: #2f3136; 18 | padding: 6px 15px 15px 15px; 19 | border-radius: 5px; 20 | border-left: 5px solid; 21 | color: white; 22 | min-height: 50px; 23 | max-width: 512px; 24 | } 25 | 26 | .messageTimestamp + .embed { 27 | margin-top: -20px; 28 | } 29 | 30 | .embedLargeIcon { 31 | height: 90px; 32 | border-radius: 5px; 33 | float: right; 34 | } 35 | 36 | .embedSmallIcon { 37 | height: 40px; 38 | border-radius: 5px; 39 | float: right; 40 | } 41 | 42 | .embedArticleLargeIcon { 43 | height: 212px; 44 | padding: 15px 0px 0px; 45 | border-radius: 5px; 46 | } 47 | 48 | /*Author*/ 49 | .embedAuthor { 50 | width: 100%; 51 | margin: 0 0 5px 0; 52 | } 53 | 54 | .embedAuthorImg { 55 | height: 20px; 56 | border-radius: 50%; 57 | display: inline-block; 58 | margin: 0; 59 | } 60 | 61 | .embedAuthorName { 62 | display: inline-block; 63 | margin: 0 0 0 5px; 64 | font-size: 14px; 65 | transform: translateY(-4px); 66 | color: #aaa; 67 | } 68 | 69 | /*Title section*/ 70 | .embedTitle { 71 | margin: 0; 72 | font-size: 17px; 73 | font-weight: bold; 74 | } 75 | 76 | .embedDescription { 77 | margin: 5px 0 0 0; 78 | } 79 | 80 | .embedContent { 81 | color: #aaa; 82 | font-size: 15px; 83 | } 84 | 85 | .embedProviderName { 86 | margin: 5px 0 5px 0; 87 | font-size: 13px; 88 | } 89 | 90 | /*Fields*/ 91 | .field { 92 | margin: 10px 20px 0 0; 93 | } 94 | 95 | .fieldName { 96 | color: #ddd; 97 | margin: 0; 98 | } 99 | 100 | .fieldText { 101 | margin: 0 0 0 5px; 102 | } 103 | 104 | /* Image */ 105 | .embedImage { 106 | max-width: 100%; 107 | margin-top: 10px; 108 | border-radius: 5px; 109 | } 110 | 111 | /*Footer*/ 112 | .footer { 113 | color: #888; 114 | margin: 10px 0 0 0; 115 | } 116 | 117 | .footerText { 118 | font-size: 12px; 119 | margin: 0; 120 | } 121 | -------------------------------------------------------------------------------- /resources/icons/checkmark.svg: -------------------------------------------------------------------------------- 1 | 2 | 60 | -------------------------------------------------------------------------------- /resources/icons/GuildVoiceChannelBlocked.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/icons/GuildTextChannel.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/icons/pullOut.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 84 | -------------------------------------------------------------------------------- /resources/icons/categoryArrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 84 | -------------------------------------------------------------------------------- /css/memberMenu.css: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Sebastian Ouellette 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. */ 14 | 15 | .memberMenu { 16 | position: absolute; 17 | background-color: #2f3136; 18 | height: 400px; 19 | width: 300px; 20 | margin-left: -330px; 21 | border-radius: 7px; 22 | cursor: default; 23 | overflow: hidden; 24 | text-align: center; 25 | z-index: 1; 26 | } 27 | .memberMenuUserDiv { 28 | width: 100%; 29 | background-color: #202225; 30 | padding: 20px 0; 31 | } 32 | 33 | .memberMenuRolesDiv { 34 | width: 100%; 35 | background-color: #2f3136; 36 | padding: 20px 0; 37 | } 38 | 39 | .memberIcon { 40 | height: 80px; 41 | width: 80px; 42 | border-radius: 50%; 43 | margin-left: auto; 44 | margin-right: auto; 45 | display: block; 46 | -webkit-mask-image: url("../resources/icons/PFPMask.svg"); 47 | mask-image: url("../resources/icons/PFPMask.svg"); 48 | -webkit-mask-size: contain; 49 | mask-size: contain; 50 | } 51 | 52 | .memberTopName { 53 | color: white; 54 | font-size: 16px; 55 | font-weight: 600; 56 | display: block; 57 | margin-top: 10px; 58 | } 59 | 60 | .memberTopDisc { 61 | color: #b9bbbe; 62 | } 63 | 64 | .memberBottomName { 65 | color: #b9bbbe; 66 | font-size: 14px; 67 | display: block; 68 | } 69 | 70 | .menuPresenceDiv { 71 | margin-top: 15px; 72 | width: 80%; 73 | margin-left: auto; 74 | margin-right: auto; 75 | display: block; 76 | } 77 | 78 | .memberCustomPresence { 79 | color: #b9bbbe; 80 | font-size: 15px; 81 | display: inline-block; 82 | word-wrap: break-word; 83 | white-space: pre-wrap; 84 | max-width: 100%; 85 | } 86 | 87 | .menuRolesDiv { 88 | width: 80%; 89 | margin-left: auto; 90 | margin-right: auto; 91 | display: block; 92 | } 93 | 94 | .memberRoles { 95 | color: #b9bbbe; 96 | font-size: 15px; 97 | display: inline-block; 98 | word-wrap: break-word; 99 | white-space: pre-wrap; 100 | max-width: 100%; 101 | } 102 | 103 | .memberRoles span { 104 | background-color: #202225; 105 | margin: 4px; 106 | padding: 4px 7px; 107 | border-radius: 5px; 108 | display: inline-block; 109 | } 110 | 111 | .memberCustomEmoji { 112 | display: inline-block; 113 | transform: translateY(4px); 114 | padding: 5px; 115 | } 116 | -------------------------------------------------------------------------------- /resources/icons/GuildTextChannelBlocked.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /css/miscs.css: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Sebastian Ouellette 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. */ 14 | 15 | @charset "UTF-8"; 16 | /** 17 | * 18 | * three-dots.css v0.1.0 19 | * 20 | * https://nzbin.github.io/three-dots/ 21 | * 22 | * Copyright (c) 2018 nzbin 23 | * 24 | * Released under the MIT license 25 | * 26 | */ 27 | 28 | .dot-bricks { 29 | position: relative; 30 | top: 8px; 31 | left: -9999px; 32 | width: 10px; 33 | height: 10px; 34 | border-radius: 5px; 35 | background-color: #9880ff; 36 | color: #9880ff; 37 | box-shadow: 9991px -16px 0 0 #9880ff, 9991px 0 0 0 #9880ff, 38 | 10007px 0 0 0 #9880ff; 39 | animation: dotBricks 2s infinite ease; 40 | } 41 | 42 | @keyframes dotBricks { 43 | 0% { 44 | box-shadow: 9991px -16px 0 0 #9880ff, 9991px 0 0 0 #9880ff, 45 | 10007px 0 0 0 #9880ff; 46 | } 47 | 8.333% { 48 | box-shadow: 10007px -16px 0 0 #9880ff, 9991px 0 0 0 #9880ff, 49 | 10007px 0 0 0 #9880ff; 50 | } 51 | 16.667% { 52 | box-shadow: 10007px -16px 0 0 #9880ff, 9991px -16px 0 0 #9880ff, 53 | 10007px 0 0 0 #9880ff; 54 | } 55 | 25% { 56 | box-shadow: 10007px -16px 0 0 #9880ff, 9991px -16px 0 0 #9880ff, 57 | 9991px 0 0 0 #9880ff; 58 | } 59 | 33.333% { 60 | box-shadow: 10007px 0 0 0 #9880ff, 9991px -16px 0 0 #9880ff, 61 | 9991px 0 0 0 #9880ff; 62 | } 63 | 41.667% { 64 | box-shadow: 10007px 0 0 0 #9880ff, 10007px -16px 0 0 #9880ff, 65 | 9991px 0 0 0 #9880ff; 66 | } 67 | 50% { 68 | box-shadow: 10007px 0 0 0 #9880ff, 10007px -16px 0 0 #9880ff, 69 | 9991px -16px 0 0 #9880ff; 70 | } 71 | 58.333% { 72 | box-shadow: 9991px 0 0 0 #9880ff, 10007px -16px 0 0 #9880ff, 73 | 9991px -16px 0 0 #9880ff; 74 | } 75 | 66.666% { 76 | box-shadow: 9991px 0 0 0 #9880ff, 10007px 0 0 0 #9880ff, 77 | 9991px -16px 0 0 #9880ff; 78 | } 79 | 75% { 80 | box-shadow: 9991px 0 0 0 #9880ff, 10007px 0 0 0 #9880ff, 81 | 10007px -16px 0 0 #9880ff; 82 | } 83 | 83.333% { 84 | box-shadow: 9991px -16px 0 0 #9880ff, 10007px 0 0 0 #9880ff, 85 | 10007px -16px 0 0 #9880ff; 86 | } 87 | 91.667% { 88 | box-shadow: 9991px -16px 0 0 #9880ff, 9991px 0 0 0 #9880ff, 89 | 10007px -16px 0 0 #9880ff; 90 | } 91 | 100% { 92 | box-shadow: 9991px -16px 0 0 #9880ff, 9991px 0 0 0 #9880ff, 93 | 10007px 0 0 0 #9880ff; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /resources/icons/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 85 | -------------------------------------------------------------------------------- /start.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Sebastian Ouellette 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | const electron = require('electron'); 18 | const { app, BrowserWindow, ipcMain } = electron; 19 | const path = require('path'); 20 | const url = require('url'); 21 | const pack = require('./package.json'); 22 | 23 | let win; 24 | 25 | function createWindow() { 26 | win = new BrowserWindow({ 27 | width: 1300, 28 | height: 750, 29 | frame: false, 30 | backgroundColor: '#FFF', 31 | webPreferences: { 32 | nodeIntegration: true, // Use node in the render js files 33 | contextIsolation: false, // Makes node in the render js files work in newer electron versions 34 | }, 35 | icon: __dirname + '/resources/icons/logo.png', 36 | }); 37 | 38 | // `remote` alternative 39 | ipcMain.on('titlebar', (event, message) => { 40 | switch (message) { 41 | case 'minimize': 42 | win.minimize(); 43 | break; 44 | case 'screenSnap': 45 | if (!win.isMaximized()) { 46 | win.maximize(); 47 | } else { 48 | win.unmaximize(); 49 | } 50 | break; 51 | case 'exit': 52 | win.close(); 53 | break; 54 | default: 55 | break; 56 | } 57 | }); 58 | 59 | win.loadURL( 60 | url.pathToFileURL(path.join(__dirname, 'dontOpenMe.html')).toString() 61 | ); 62 | 63 | // win.webContents.on('new-window', (e, url) => { 64 | // e.preventDefault(); 65 | // electron.shell.openExternal(url.replace(/\/$/, '')); 66 | // }); 67 | win.webContents.setWindowOpenHandler(({ url }) => { 68 | electron.shell.openExternal(url.replace(/\/$/, '')); 69 | return { action: 'allow' }; 70 | }); 71 | 72 | // win.webContents.on('did-create-window', child => { 73 | // // For example... 74 | // child.webContents('will-navigate', e => { 75 | // e.preventDefault() 76 | // }) 77 | // }) 78 | 79 | win.on('closed', () => { 80 | win = null; 81 | }); 82 | } 83 | 84 | app.on('ready', createWindow); 85 | 86 | app.on('window-all-closed', () => { 87 | if (process.platform !== 'darwin') { 88 | app.quit(); 89 | } 90 | }); 91 | 92 | app.on('activate', () => { 93 | if (win === null) { 94 | createWindow(); 95 | } 96 | }); 97 | 98 | app.on('web-contents-created', (e, contents) => { 99 | contents.on('new-window', (newEvent) => { 100 | console.log("Blocked by 'new-window'"); 101 | newEvent.preventDefault(); 102 | }); 103 | 104 | contents.on('will-navigate', (newEvent) => { 105 | console.log("Blocked by 'will-navigate'"); 106 | newEvent.preventDefault(); 107 | }); 108 | 109 | contents.setWindowOpenHandler(({ url }) => { 110 | setImmediate(() => { 111 | shell.openExternal(url); 112 | }); 113 | return { action: 'allow' }; 114 | }); 115 | }); 116 | -------------------------------------------------------------------------------- /resources/icons/embedIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 84 | -------------------------------------------------------------------------------- /css/messageBar.css: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Sebastian Ouellette 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. */ 14 | 15 | /* Send message bar */ 16 | #sendmsg { 17 | position: absolute; 18 | margin-left: 310px; 19 | margin-top: -20px; 20 | width: calc(100vw - 518px); 21 | height: 40px; 22 | padding-bottom: 10px; 23 | z-index: 2; 24 | } 25 | 26 | #sendmsg::before { 27 | content: ""; 28 | width: 100%; 29 | background-color: #36393e; 30 | height: 4000px; 31 | position: absolute; 32 | top: 20px; 33 | z-index: -1; 34 | } 35 | 36 | #messageBar { 37 | white-space: nowrap; 38 | overflow: hidden; 39 | width: calc(100% - 40px); 40 | background-color: #484b51; 41 | height: 100%; 42 | margin: 0 0 0 15px; 43 | border-radius: 5px; 44 | padding: 0 10px 0 0; 45 | display: flex; 46 | 47 | /* display: table; */ 48 | } 49 | 50 | #msgbox::-webkit-scrollbar { 51 | width: 0; 52 | } 53 | 54 | #msgbox { 55 | display: block; 56 | background-color: transparent; 57 | border: none; 58 | margin: 0; 59 | outline: none; 60 | color: white; 61 | resize: none; 62 | display: inline-block; 63 | padding: 10px; 64 | width: 100%; 65 | } 66 | 67 | .messageBoxText { 68 | font-family: Whitney-light, "Helvetica Neue", Helvetica, Arial, sans-serif; 69 | font-size: 16px; 70 | font-weight: 400; 71 | padding: 2px 0 1px 0; 72 | margin: 0; 73 | color: #dcddde; 74 | /* width: calc(100% - 40px); */ 75 | word-spacing: 2px; 76 | position: relative; 77 | /* display: table-row; */ 78 | } 79 | 80 | /* Additional message bar icons */ 81 | #msgMisc { 82 | display: block; 83 | height: 100%; 84 | float: right; 85 | margin-top: 4px; 86 | display: grid; 87 | grid-auto-flow: column; 88 | grid-column-gap: 4px; 89 | } 90 | 91 | #msgMisc img { 92 | height: 25px; 93 | float: left; 94 | cursor: pointer; 95 | padding: 3px; 96 | opacity: 0.6; 97 | border-radius: 3px; 98 | } 99 | 100 | #msgMisc img:hover { 101 | opacity: 1; 102 | } 103 | 104 | /* Typing Indicator */ 105 | #typingIndicator { 106 | font-size: 14px; 107 | color: white; 108 | position: absolute; 109 | z-index: 1; 110 | user-select: none; 111 | margin: 2px 0 0 0; 112 | left: 50px; 113 | } 114 | 115 | #typingDots { 116 | position: absolute; 117 | display: none; 118 | margin-top: 20px; 119 | } 120 | 121 | #typingDots.enabled { 122 | display: block; 123 | } 124 | 125 | .bold { 126 | font-family: Whitney; 127 | font-weight: bold; 128 | } 129 | 130 | .typingDot { 131 | height: 8px; 132 | width: 8px; 133 | background-color: #686a6e; 134 | position: absolute; 135 | top: -13px; 136 | left: 15px; 137 | border-radius: 50%; 138 | animation: typing 2s infinite; 139 | } 140 | 141 | .dot1 { 142 | animation-delay: 0s; 143 | } 144 | 145 | .dot2 { 146 | left: 25px; 147 | animation-delay: 0.2s; 148 | } 149 | 150 | .dot3 { 151 | left: 35px; 152 | animation-delay: 0.4s; 153 | } 154 | 155 | @keyframes typing { 156 | 0%, 157 | 20% { 158 | background-color: #686a6e; 159 | transform: scale(0.5); 160 | } 161 | 50% { 162 | transform: scale(1); 163 | background-color: white; 164 | } 165 | 80%, 166 | 100% { 167 | background-color: #686a6e; 168 | transform: scale(0.5); 169 | } 170 | } 171 | 172 | /* */ 173 | -------------------------------------------------------------------------------- /css/channelList.css: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Sebastian Ouellette 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. */ 14 | 15 | #channel-list { 16 | font-family: Whitney, "Helvetica Neue", Helvetica, Arial, sans-serif; 17 | height: calc(100% - 90px); 18 | margin-left: 50px; 19 | margin-top: 40px; 20 | width: 250px; 21 | padding-left: 10px; 22 | padding-top: 10px; 23 | padding-bottom: -10px; 24 | background-color: #2f3136; 25 | color: #606266; 26 | overflow-y: auto; 27 | overflow-x: hidden; 28 | font-weight: 100; 29 | } 30 | 31 | #channel-list::-webkit-scrollbar { 32 | background-color: transparent; 33 | width: 6px; 34 | } 35 | 36 | #channel-list::-webkit-scrollbar-track { 37 | background-color: transparent; 38 | } 39 | 40 | #channel-list::-webkit-scrollbar-thumb { 41 | background-color: #25272b; 42 | border-radius: 3px; 43 | } 44 | 45 | .categoryNameContainer { 46 | display: block; 47 | } 48 | 49 | .categoryText { 50 | /* font-variant: small-caps; */ 51 | text-transform: uppercase; 52 | user-select: none; 53 | padding: 15px 0 0 0 !important; 54 | height: 20px; 55 | font-size: 12px; 56 | font-weight: bold; 57 | 58 | transform: translateY(5px) !important; 59 | } 60 | 61 | .categoryNameContainer { 62 | cursor: pointer; 63 | } 64 | 65 | .categoryNameContainer:hover .categoryText { 66 | color: #b4b8bc; 67 | } 68 | 69 | .categorySVG { 70 | height: 10px; 71 | position: relative; 72 | top: 5px; 73 | margin-right: 8px; 74 | transform: rotate(90deg); 75 | transition-duration: 0.1s; 76 | } 77 | 78 | .open .categorySVG { 79 | transform: rotate(180deg); 80 | } 81 | 82 | .GuildNews { 83 | height: 18px !important; 84 | width: 18px !important; 85 | padding: 1px; 86 | } 87 | 88 | .channelContainer { 89 | display: none; 90 | } 91 | 92 | .open .channelContainer { 93 | display: block; 94 | } 95 | 96 | .channel { 97 | user-select: none; 98 | margin: 2px 0 0 -5px; 99 | } 100 | 101 | .GuildVoice { 102 | user-select: none; 103 | margin-left: -5px; 104 | } 105 | 106 | h5 { 107 | font-weight: 100; 108 | font-size: 16px; 109 | } 110 | 111 | .channelSVG { 112 | height: 20px; 113 | width: 20px; 114 | display: inline-box; 115 | transform: translateY(-9px); 116 | } 117 | 118 | #channel-elements h5 { 119 | transform: translateY(-5px); 120 | display: inline-block; 121 | margin: 0 !important; 122 | padding: 7px 0 7px 10px; 123 | } 124 | 125 | .channel { 126 | /* margin: 0 0 0 20px; */ 127 | padding: 5px 10px; 128 | cursor: pointer; 129 | border-radius: 3px; 130 | height: 25px; 131 | } 132 | 133 | .channel:hover { 134 | background-color: #34373c; 135 | color: #b4b8bc; 136 | } 137 | 138 | /* Channel ... formatting */ 139 | .channel h5 { 140 | max-width: calc(100% - 30px); 141 | overflow: hidden; 142 | text-overflow: ellipsis; 143 | white-space: nowrap; 144 | user-select: none; 145 | } 146 | 147 | .selectedChan { 148 | background-color: #393c43 !important; 149 | color: white !important; 150 | } 151 | 152 | .channel.newMsg { 153 | color: white !important; 154 | } 155 | 156 | .blocked { 157 | cursor: not-allowed; 158 | /* color: #904d3c; */ 159 | color: #fc5c65; 160 | } 161 | 162 | .blocked h5 { 163 | opacity: 0.35; 164 | } 165 | 166 | .blocked:hover { 167 | color: #fc5c65; 168 | /* background-color: #4d4040; */ 169 | background-color: #3c3434; 170 | } 171 | 172 | .blocked:hover h5 { 173 | opacity: 1; 174 | } 175 | -------------------------------------------------------------------------------- /resources/icons/logoLarge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 96 | -------------------------------------------------------------------------------- /js/buildMemberMenu.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Sebastian Ouellette 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | function buildMemberMenu(parent) { 18 | let member = selectedGuild.members.cache.get(parent.firstChild.id); 19 | 20 | let div = document.createElement('div'); 21 | div.classList.add('memberMenu'); 22 | parent.appendChild(div); 23 | 24 | // The top section of the member menu 25 | let user = document.createElement('div'); 26 | user.classList.add('memberMenuUserDiv'); 27 | div.appendChild(user); 28 | 29 | // The top section of the member menu 30 | let roles = document.createElement('div'); 31 | roles.classList.add('memberMenuRolesDiv'); 32 | div.appendChild(roles); 33 | 34 | // The user icon 35 | let userIcon = document.createElement('img'); 36 | userIcon.classList.add('memberIcon'); 37 | userIcon.src = member.displayAvatarURL({ size: 512 }); 38 | 39 | user.appendChild(userIcon); 40 | 41 | // Username and nickname 42 | let topName = document.createElement('span'); 43 | topName.classList.add('memberTopName'); 44 | topName.innerText = member.displayName; 45 | user.appendChild(topName); 46 | 47 | if (member.nickname) { 48 | // Create the full tag below 49 | let bottomName = document.createElement('span'); 50 | bottomName.classList.add('memberBottomName'); 51 | bottomName.innerText = member.user.tag; 52 | user.appendChild(bottomName); 53 | } else { 54 | // Add the discriminator to the top name 55 | let topDisc = document.createElement('span'); 56 | topDisc.classList.add('memberTopDisc'); 57 | topDisc.innerText = `#${member.user.discriminator}`; 58 | topName.appendChild(topDisc); 59 | } 60 | 61 | // Custom presence div 62 | let presenceDiv = document.createElement('div'); 63 | presenceDiv.classList.add('menuPresenceDiv'); 64 | user.appendChild(presenceDiv); 65 | 66 | // Custom presence 67 | let custPresence = member.presence?.activities.find( 68 | (a) => a.type == Discord.ActivityType.Custom 69 | ); 70 | if (custPresence) { 71 | if (custPresence.emoji) { 72 | // Status emoji 73 | let custEmoji = document.createElement('div'); 74 | custEmoji.classList.add('memberCustomEmoji'); 75 | if (custPresence.emoji.id) { 76 | // Custom emoji 77 | } else { 78 | // Twemoji 79 | custEmoji.innerHTML = twemoji.parse(custPresence.emoji.name); 80 | } 81 | presenceDiv.appendChild(custEmoji); 82 | } 83 | 84 | // Status text 85 | let custPres = document.createElement('span'); 86 | custPres.classList.add('memberCustomPresence'); 87 | custPres.innerText = custPresence.state; 88 | presenceDiv.appendChild(custPres); 89 | } 90 | 91 | // roles :D 92 | let rolesDiv = document.createElement('div'); 93 | rolesDiv.classList.add('menuRolesDiv'); 94 | roles.appendChild(rolesDiv); 95 | 96 | let rolestext = document.createElement('div'); 97 | rolestext.classList.add('memberRoles'); 98 | 99 | member.roles.cache.each((role) => { 100 | const spans = document.createElement('span'); 101 | spans.innerText = role.name; 102 | rolestext.appendChild(spans); 103 | }); 104 | 105 | rolesDiv.appendChild(rolestext); 106 | 107 | // Set the final position of the menu 108 | let borderOffset = 40; 109 | let y = window.pageYOffset + parent.getBoundingClientRect().top - 20; 110 | if (y + div.clientHeight > window.innerHeight) 111 | y = window.innerHeight - div.clientHeight - borderOffset; 112 | 113 | div.style.top = `${y}px`; 114 | } 115 | -------------------------------------------------------------------------------- /css/guildList.css: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Sebastian Ouellette 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. */ 14 | 15 | .homeBreak { 16 | border: none; 17 | margin: 9px 8px 8px 7px; 18 | padding: 2px 0 0 0; 19 | background-color: #72767d; 20 | } 21 | 22 | .guild-icon { 23 | border-radius: 50%; 24 | margin-left: 10px; 25 | transition-duration: 0.3s; 26 | user-select: none; 27 | border-radius: 50%; 28 | margin-top: 5px; 29 | } 30 | 31 | .guild-icon.selectedGuild { 32 | border-radius: 25% !important; 33 | } 34 | 35 | .guild-icon:hover { 36 | cursor: pointer; 37 | border-radius: 25%; 38 | } 39 | 40 | #guildAbrev { 41 | position: absolute; 42 | overflow-x: hidden; 43 | color: White; 44 | font-family: Whitney-light, "Helvetica Neue", Helvetica, Arial, sans-serif; 45 | text-align: center; 46 | font-size: 13px; 47 | width: 40px; 48 | } 49 | 50 | #guild-list { 51 | width: 60px; 52 | height: 100%; 53 | margin: 0 -10px 10px -10px; 54 | padding-top: 2px; 55 | background-color: #202225; 56 | overflow-y: auto; 57 | position: absolute; 58 | padding-bottom: 2px; 59 | } 60 | 61 | #guild-list::-webkit-scrollbar { 62 | display: none; 63 | } 64 | 65 | #guildIndicator { 66 | background-color: white; 67 | height: 40px; 68 | width: 6px; 69 | margin-left: 0px; 70 | position: absolute; 71 | z-index: 100; 72 | border-radius: 3px; 73 | display: none; 74 | transition-duration: 0.2s; 75 | } 76 | 77 | .guildIconDiv { 78 | color: transparent; 79 | width: auto; 80 | cursor: default; 81 | display: flex; 82 | transition-duration: 0.3s !important; 83 | } 84 | 85 | .guildIconDiv .guild-icon { 86 | order: -1; 87 | } 88 | 89 | /* DEBUG TESTING */ 90 | .guildNameContainer::before { 91 | left: -10px; 92 | top: 8px; 93 | margin: 3px 0 0 0px; 94 | border: 6px solid transparent; 95 | content: " "; 96 | position: absolute; 97 | border-right-color: transparent; 98 | /* transition-duration: 0.1s !important; */ 99 | } 100 | 101 | .guildNameContainer { 102 | color: transparent; 103 | height: 35px; 104 | /* margin-top: 12px; */ 105 | margin-left: 70px; 106 | z-index: 1; 107 | visibility: hidden; 108 | position: fixed; 109 | border-radius: 5px; 110 | background-color: transparent; 111 | padding: 0 10px; 112 | font-weight: bold; 113 | /* transition-duration: 0.3s !important; */ 114 | } 115 | 116 | .guildName { 117 | color: inherit; 118 | margin: 3px 4px 0; 119 | display: inherit; 120 | position: relative; 121 | font-size: 13px; 122 | line-height: 27px; 123 | } 124 | 125 | .guild-icon:hover + .guildNameContainer::before { 126 | display: block; 127 | border-right-color: black; 128 | } 129 | 130 | .guild-icon:hover + .guildNameContainer { 131 | color: white; 132 | visibility: visible; 133 | background-color: black; 134 | } 135 | 136 | /* WORKING COPY - DO NOT CHANGE */ 137 | /* .guildNameContainer::before { 138 | left: -10px; 139 | top: 4px; 140 | margin: 3px 0 0 0px; 141 | border: 6px solid transparent; 142 | content: ' '; 143 | position: absolute; 144 | border-right-color: transparent; 145 | transition-duration: 0.1s !important; 146 | } 147 | 148 | .guildNameContainer { 149 | color: transparent; 150 | height: 26px; 151 | margin: 12px 0 0 70px; 152 | z-index: 1; 153 | visibility: hidden; 154 | position: fixed; 155 | border-radius: 5px; 156 | background-color: transparent; 157 | transition-duration: 0.3s !important; 158 | } 159 | 160 | .guildName { 161 | color: inherit; 162 | margin: 3px 4px 0; 163 | display: inherit; 164 | position: fixed; 165 | } 166 | 167 | .guild-icon:hover + .guildNameContainer::before { 168 | display: block; 169 | border-right-color: black; 170 | } 171 | 172 | .guild-icon:hover + .guildNameContainer{ 173 | color: white; 174 | visibility: visible; 175 | background-color: black; 176 | } */ 177 | -------------------------------------------------------------------------------- /js/rcMenuFuncs.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Sebastian Ouellette 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | // -------- MESSAGE MENU -------- 18 | 19 | // Used for removing the edit message DOM and replacing it with the original 20 | let editDOM = (target, textarea, elementText) => { 21 | // Remove the text field, replace with normal message 22 | target.removeChild(textarea); 23 | 24 | let newMsgElement = document.createElement('p'); 25 | newMsgElement.classList.add('messageText'); 26 | newMsgElement.innerHTML = elementText; 27 | target.appendChild(newMsgElement); 28 | }; 29 | 30 | let checkEditDoms = () => { 31 | // Check if there's any edit message menu already open and close it 32 | let messageMenus = document.getElementsByClassName('editTextarea'); 33 | 34 | for (let messageMenu of messageMenus) { 35 | let id = messageMenu.classList[2]; 36 | let text = selectedChan.messages.cache.get(id).cleanContent; 37 | editDOM(messageMenu.parentElement, messageMenu, text); 38 | } 39 | }; 40 | 41 | // Edit a message 42 | function editMsg(target) { 43 | // Safe check so there is only one DOM to edit messages 44 | checkEditDoms(); 45 | 46 | // Find and delete the message text
element 47 | let textElement = target.querySelector('.messageText'); 48 | 49 | // Get the text in the message 50 | let elementText = textElement.innerHTML; 51 | let msg = selectedChan.messages.cache.get(target.id); 52 | let text = msg.cleanContent; 53 | 54 | target.removeChild(textElement); 55 | 56 | let textarea = document.createElement('textarea'); 57 | textarea.value = text; 58 | textarea.classList.add('editTextarea'); 59 | textarea.classList.add('messageBoxText'); 60 | textarea.classList.add(target.id); 61 | textarea.rows = '1'; 62 | setRows(textarea); 63 | target.appendChild(textarea); 64 | 65 | textarea.addEventListener('keydown', (e) => { 66 | if (e.key == 'Enter' && !e.shiftKey) { 67 | if (textarea.value == text) return editDOM(target, textarea, text); 68 | let newText = textarea.value; 69 | newText = newText.replace( 70 | /()/gm, 71 | (a, b, c, d) => { 72 | if (c != '!') { 73 | return `${b}!${d}`; 74 | } 75 | return a; 76 | } 77 | ); 78 | 79 | newText = parseSend(newText); 80 | 81 | msg.edit(newText).catch((e) => { 82 | command('Message failed to send\nError: ' + e.message); 83 | }); 84 | 85 | editDOM(target, textarea, elementText); 86 | } 87 | }); 88 | 89 | textarea.addEventListener('input', (e) => setRows(textarea)); 90 | } 91 | 92 | function setRows(textarea) { 93 | let rows = textarea.value.split('\n').length; 94 | if (rows > 6) rows = 6; 95 | if (rows == 0) rows++; 96 | textarea.rows = rows; 97 | } 98 | 99 | function pinMsg(id, pin) { 100 | try { 101 | pin 102 | ? selectedChan.messages.cache.get(id).pin() 103 | : selectedChan.messages.cache.get(id).unpin(); 104 | } catch (e) { 105 | console.log(e); 106 | } 107 | } 108 | 109 | function deleteMsg(id) { 110 | try { 111 | selectedChan.messages.cache.get(id).delete(); 112 | } catch (e) { 113 | console.log(e); 114 | } 115 | } 116 | 117 | function copyMessageLink(id, msg) { 118 | clipboard.writeText(msg.url); 119 | } 120 | 121 | function copyMessageID(id) { 122 | clipboard.writeText(id); 123 | } 124 | 125 | // -------- USER MENU -------- 126 | 127 | function dmUser(user) { 128 | dmChannelSelect(user); 129 | } 130 | 131 | function mentionUser(id) { 132 | let msgBox = document.getElementById('msgbox'); 133 | msgBox.value += `<@${id}>`; 134 | } 135 | 136 | function copyUserID(id) { 137 | clipboard.writeText(id); 138 | } 139 | 140 | function copyAvatarLink(member) { 141 | member = member.user || member; 142 | clipboard.writeText(member.displayAvatarURL()); 143 | } 144 | -------------------------------------------------------------------------------- /js/handleScripts.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Sebastian Ouellette 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | let loadAllScripts = () => { 18 | let files = fs.readdirSync('./scripts'); 19 | 20 | // Remove the template file out of the equation if there is one 21 | if (files.includes('template.js')) 22 | files.splice(files.indexOf('template.js'), 1); 23 | 24 | // If there are no scripts don't even bother 25 | if (!files.length) return; 26 | 27 | // Preload all the scripts 28 | console.log('Loading scripts...'); 29 | files.forEach((file) => { 30 | if (file.endsWith('.js') && file != 'template.js') { 31 | delete require.cache[require.resolve(`./scripts/${file}`)]; 32 | 33 | require('./scripts/' + file); 34 | console.log(`%c ${file} loaded!`, 'color:Green'); 35 | } 36 | }); 37 | 38 | // Start all the loaded scripts 39 | console.log('Starting scripts...'); 40 | files.forEach((file) => { 41 | if (!file.endsWith('.js')) return; 42 | 43 | try { 44 | const script = require.cache[require.resolve(`./scripts/${file}`)].exports; 45 | 46 | // Modern API: script is a function 47 | if (typeof script === 'function') { 48 | script(global.bot); 49 | console.log(`%c ${file} started! (function)`, 'color:Green'); 50 | return; 51 | } 52 | 53 | // Legacy API: script.start exists 54 | if (typeof script.start === 'function') { 55 | script.start(global.bot); 56 | console.log(`%c ${file} started! (start method)`, 'color:Green'); 57 | return; 58 | } 59 | 60 | // No valid entry point 61 | console.log(`%c ${file} has no start function`, 'color:Orange'); 62 | 63 | } catch (err) { 64 | console.log( 65 | `%c ${file} was unable to start\n${err}`, 66 | 'color:Red' 67 | ); 68 | } 69 | }); 70 | }; 71 | 72 | let unloadAllScripts = () => { 73 | let files = fs.readdirSync('./scripts'); 74 | 75 | // Stop and unload all the scripts 76 | console.log('Stopping scripts...'); 77 | files.forEach((file) => { 78 | if ( 79 | file.endsWith('.js') && 80 | file != 'template.js' && 81 | require.cache[require.resolve(`./scripts/${file}`)] 82 | ) { 83 | try { 84 | if ( 85 | !require.cache[require.resolve(`./scripts/${file}`)].exports 86 | .stop 87 | ) 88 | throw { message: '.exports.stop is not a function' }; 89 | console.log(`%c ${file} stopped!`, 'color:Red'); 90 | require.cache[ 91 | require.resolve(`./scripts/${file}`) 92 | ].exports.stop(); 93 | } catch (err) { 94 | // If it's missing the start function then display a short error and say which plugin it is 95 | if (err.message.includes('.exports.stop is not a function')) 96 | return console.log( 97 | `%c ${file} was unable to stop`, 98 | 'color:Red' 99 | ); 100 | console.log( 101 | `%c ${file} was unable to stop\n${err}`, 102 | 'color:Red' 103 | ); 104 | } 105 | 106 | delete require.cache[require.resolve(`./scripts/${file}`)]; 107 | console.log(`%c ${file} unloaded!`, 'color:Red'); 108 | } 109 | }); 110 | }; 111 | 112 | let getData = () => { 113 | let object = {}; 114 | 115 | let files = fs.readdirSync('./scripts'); 116 | 117 | files.forEach((file) => { 118 | if (file.endsWith('.js') && file != 'template.js') { 119 | object[file] = 120 | require.cache[ 121 | require.resolve(`./scripts/${file}`) 122 | ].exports.info; 123 | object[file].file = file; 124 | } 125 | }); 126 | 127 | return object; 128 | }; 129 | -------------------------------------------------------------------------------- /js/parseFunctions.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Sebastian Ouellette 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | function parseHTML(text) { 18 | text = text.replace(/<|>|&/gm, (s) => 19 | s == '<' ? '<' : s == '>' ? '>' : '&' 20 | ); 21 | return text; 22 | } 23 | 24 | function parsePings(msg, text, embeddedLink, ping, embed) { 25 | // Format pings in embeds 26 | if (ping || !embed) { 27 | let dms = selectedChan.type == Discord.ChannelType.DM; 28 | text = formatEmbedPings(msg, text, dms); 29 | text = formatPings(msg, text, dms); 30 | } 31 | // Format links in embeds 32 | if (embeddedLink) { 33 | text = text.replace( 34 | /(?:\[(?:<(?:[\w\W]+?>([\w\.!@#$%^&*\-\/"=\[\];]+?)<(?:[\w\W\/]+?)>)|([\w\.!@#$%^&*\-\/"=<>\]\[; ]+?))\]\((?: ]+?)\)))/gm, 35 | (a, b, c, d, e) => { 36 | return `${ 37 | b ? b : c 38 | }`; 39 | } 40 | ); 41 | } 42 | 43 | return text; 44 | } 45 | 46 | function parseLinks(text) { 47 | text = text.replace( 48 | /https?:\/\/((?:\w|.)+?)(?=\/|(?= )|[>)}\]:; ]|$)(?:[\w\.!@#$%^&*\-\/]+?)*(?:\?.*?(?=[>)}\]:; ]|$))?/gm, 49 | (a, b, c) => { 50 | // Add '/' to the end if it's not already there 51 | a.endsWith('/') ? undefined : (a += '/'); 52 | // Return the html for the link 53 | return `${a}`; 54 | } 55 | ); 56 | return text; 57 | } 58 | 59 | function parseStyling(text, embed) { 60 | let code = false; 61 | // Check for major codeblock 62 | text = text.replace( 63 | /(? { 65 | code = true; 66 | c = c.length ? c : b; 67 | // console.log(b) 68 | return `
`; 71 | } 72 | ); 73 | // Check for inline codeblock 74 | text = text.replace(/(? { 75 | if (code) return a; 76 | code = true; 77 | return `${b}`; 78 | }); 79 | 80 | if (code == false) { 81 | // General styling 82 | text = text.replace( 83 | /(?$1' 85 | ); 86 | text = text.replace( 87 | /(?$1' 89 | ); 90 | text = text.replace(/(?$1'); 91 | text = text.replace(/(?$1'); 92 | text = text.replace(/(?$1'); 93 | text = text.replace( 94 | /(?$1' 96 | ); 97 | text = text.replace(/(?$1'); 98 | } 99 | 100 | return text; 101 | } 102 | 103 | function parseUnicodeEmojis(text) { 104 | if ( 105 | !text.replace( 106 | /((\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])| |(<a?:!?.+?:\d{17,19}?>))/g, 107 | '' 108 | ).length 109 | ) { 110 | text = `${text}`; 111 | } 112 | return text; 113 | } 114 | 115 | function parseCustomEmojis(text) { 116 | text = text.replace( 117 | /<(a)?:!?(.+?):(\d{17,19}?)>/gm, 118 | (original, animated, name, id) => { 119 | if (id !== undefined) 120 | return `Cache cleared! Now you can restart LiveBot
"; 46 | }; 47 | 48 | document.getElementById('msgbox').addEventListener('keydown', (event) => { 49 | if (event.key == 'Enter' && !event.shiftKey) { 50 | event.preventDefault(); 51 | 52 | // If the message was able to be sent, reset the message box size 53 | if (!sendmsg()) { 54 | // Reset the textbox height 55 | let msgBox = document.getElementById('sendmsg'); 56 | msgBox.style.height = '38px'; // Reset the height first 57 | msgBox.style.transform = ''; 58 | } 59 | } 60 | }); 61 | 62 | document.getElementById('msgbox').addEventListener('input', (event) => { 63 | let textElem = document.getElementById('msgbox'); 64 | let rows = textElem.value.split('\n').length; 65 | rows = rows == 0 ? 1 : rows; 66 | // document.getElementById("msgbox").rows = rows; 67 | 68 | let msgBox = document.getElementById('sendmsg'); 69 | 70 | if (textElem.scrollHeight < 38 * 5) { 71 | msgBox.style.height = '0px'; // Reset the height first 72 | msgBox.style.height = `${textElem.scrollHeight}px`; 73 | msgBox.style.transform = `translateY(-${ 74 | textElem.scrollHeight - 38 75 | }px)`; 76 | } 77 | }); 78 | 79 | // Call the settings menu builder 80 | buildSettingsMenu(jsonSettings); 81 | 82 | // Call the general click event listener script 83 | addDocListener(); 84 | 85 | // Load the bot with the token in storage or throw an error if there isn't any 86 | setLoadingPerc(0); 87 | if (settings.defaultToken) { 88 | var error = await load(settings.defaultToken); 89 | if (error[0]) { 90 | buildSplashToken(); 91 | } 92 | } else { 93 | buildSplashToken(); 94 | // errorHandler('NO-TOKEN'); 95 | } 96 | } 97 | 98 | // Alert that you are typing 99 | function typing() { 100 | if (!selectedChan || !document.getElementById('msgbox').value) return; 101 | if (!bot.user._typing?.has(selectedChan.id)) selectedChan.sendTyping(); 102 | } 103 | 104 | // Why has this code been in livebot this long???? 105 | // Options on the right pane 106 | function options(type, content) { 107 | switch (type) { 108 | case 'username': 109 | bot.user.setUsername(content); 110 | document.getElementById('userCardName').innerText = content; 111 | break; 112 | 113 | case 'invite': 114 | selectedChan 115 | .createInvite() 116 | .then((invite) => 117 | command( 118 | 'Created invite for ' + 119 | invite.guild.name + 120 | ' \nhttps://discord.gg/' + 121 | invite.code 122 | ) 123 | ); 124 | break; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /js/addMemberList.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Sebastian Ouellette 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | async function addMemberList(guild) { 18 | const listDiv = document.getElementById('memberBar'); 19 | 20 | // Clear members list 21 | listDiv.innerHTML = ''; 22 | 23 | if (!selectedChan) return; 24 | 25 | let members = guild.members.cache; 26 | let roles = new Discord.Collection(); 27 | 28 | if (members.size !== guild.memberCount) 29 | members = await guild.members.fetch(); 30 | 31 | guild.roles.cache 32 | .sort((role1, role2) => role2.rawPosition - role1.rawPosition) 33 | .forEach((role) => 34 | roles.set(role.id, { id: role.id, name: role.name }) 35 | ); 36 | 37 | // add Online and Offline roles to display members without hoist role 38 | roles.set('online', { id: 'online', name: 'Online' }); 39 | roles.set('offline', { id: 'offline', name: 'Offline' }); 40 | 41 | members 42 | .sort((member1, member2) => { 43 | if (member1.displayName < member2.displayName) return -1; 44 | if (member1.displayName > member2.displayName) return 1; 45 | 46 | return 0; 47 | }) 48 | .forEach((member) => { 49 | if ( 50 | !member 51 | .permissionsIn(selectedChan) 52 | .has(Discord.PermissionFlagsBits.ViewChannel) 53 | ) 54 | return; 55 | 56 | let role; 57 | 58 | if (!member.presence || member.presence.status == 'offline') { 59 | if (members.size < 1000) role = roles.get('offline'); 60 | else return; 61 | } else if (member.roles.hoist) 62 | role = roles.get(member.roles.hoist.id); 63 | else if (member.presence.status != 'offline') 64 | role = roles.get('online'); 65 | 66 | if (!role.container) { 67 | role.container = document.createElement('div'); 68 | role.container.id = role.id; 69 | role.container.classList.add('roleContainer'); 70 | 71 | role.container.name = document.createElement('span'); 72 | role.container.name.classList.add('roleTitle'); 73 | role.container.appendChild(role.container.name); 74 | } 75 | 76 | // Create the outer div 77 | let outerDiv = document.createElement('div'); 78 | outerDiv.classList.add('mLOuterDiv'); 79 | role.container.appendChild(outerDiv); 80 | 81 | // Make the div for the user 82 | let userDiv = document.createElement('div'); 83 | userDiv.id = member.id; 84 | userDiv.classList.add('mLUserDiv'); 85 | outerDiv.appendChild(userDiv); 86 | 87 | // Add the user icon 88 | let icon = document.createElement('img'); 89 | icon.classList.add('mLIcon'); 90 | icon.src = member.displayAvatarURL({ size: 64, forceStatic: true }); 91 | userDiv.onmouseenter = (e) => { 92 | icon.src = member.displayAvatarURL({ size: 64 }); 93 | }; 94 | userDiv.onmouseleave = (e) => { 95 | icon.src = member.displayAvatarURL({ 96 | size: 64, 97 | forceStatic: true, 98 | }); 99 | }; 100 | userDiv.appendChild(icon); 101 | 102 | // Make the username text 103 | let username = document.createElement('p'); 104 | username.classList.add('mLUsername'); 105 | let name = member.displayName; 106 | username.innerText = name; 107 | username.style.color = member.roles.color?.hexColor || '#8E9297'; 108 | userDiv.appendChild(username); 109 | }); 110 | 111 | roles 112 | .filter((role) => role.container) 113 | .forEach((role) => { 114 | // Add the role name with member count 115 | role.container.name.innerText = `${role.name} — ${ 116 | role.container.childElementCount - 1 117 | }`; 118 | 119 | listDiv.appendChild(role.container); 120 | }); 121 | } 122 | -------------------------------------------------------------------------------- /js/setToken.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Sebastian Ouellette 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | async function setToken(token) { 18 | let client = new Discord.Client({ 19 | partials: [ 20 | Discord.Partials.Channel, // get DM messages 21 | ], 22 | intents: [ 23 | Discord.GatewayIntentBits.Guilds, 24 | Discord.GatewayIntentBits.GuildMessages, 25 | Discord.GatewayIntentBits.GuildMembers, 26 | Discord.GatewayIntentBits.GuildVoiceStates, 27 | Discord.GatewayIntentBits.MessageContent, 28 | Discord.GatewayIntentBits.GuildMessageTyping, 29 | Discord.GatewayIntentBits.GuildPresences, 30 | Discord.GatewayIntentBits.DirectMessageTyping, 31 | Discord.GatewayIntentBits.DirectMessages, 32 | ], 33 | }); 34 | 35 | let error = [false, 'none']; 36 | if (global.bot && bot.token == token) { 37 | return [true, 'SAME-TOKEN']; 38 | } 39 | try { 40 | setLoadingPerc(0.05); 41 | error = await validateToken(token); 42 | if (error[0]) { 43 | throw error[1]; 44 | } 45 | await client.login(token).catch((err) => { 46 | throw err; 47 | }); 48 | client.destroy(); 49 | 50 | setLoadingPerc(0.1); // Refreshing the everything 51 | // Clear the list of channels 52 | let channels = document.getElementById('channel-elements'); 53 | while (channels.firstChild) { 54 | channels.removeChild(channels.firstChild); 55 | } 56 | 57 | // Delete the list of the guilds 58 | let guildContainer = document.getElementById('guildContainer'); 59 | if (guildContainer && guildContainer.parentElement) { 60 | guildContainer.parentElement.removeChild(guildContainer); 61 | } 62 | 63 | // Clear the message list 64 | let messages = document.getElementById('message-list'); 65 | while (messages.firstChild) { 66 | messages.removeChild(messages.firstChild); 67 | } 68 | 69 | // Clear the member list 70 | let memberList = document.getElementById('memberBar'); 71 | memberList.innerHTML = ''; 72 | 73 | // Stop the current bot, unload scripts, and then load into the new token 74 | if (global.bot !== undefined) { 75 | bot.destroy(); 76 | } 77 | await unloadAllScripts(); 78 | await unloadThemes(); 79 | load(token); 80 | cachedGuilds = []; 81 | } catch (err) { 82 | // Set the error to true so it doesn't save the token 83 | error[0] = true; 84 | error[1] = err; 85 | } 86 | // Return if there's been an error or not 87 | 88 | return error; 89 | } 90 | 91 | async function saveToken(token) { 92 | let error = [false, 'none']; 93 | if (global.bot === undefined) error = await load(token); 94 | else error = await setToken(token); 95 | 96 | if (!error[0]) { 97 | // Set the default token 98 | settings.defaultToken = token; 99 | return error; 100 | } else { 101 | console.warn(`The token won't be saved since there was an error`); 102 | return error; 103 | } 104 | } 105 | 106 | function validateToken(token = '') { 107 | if (token.length == 0) return [true, 'EMPTY-TOKEN']; 108 | 109 | if (token.replace('.', '').length < 50) return [true, 'TOKEN-SHORT']; 110 | 111 | let invalidChars = [' ', '\t', '\r', '\n']; 112 | if (token.split('').filter((c) => invalidChars.includes(c)).length > 0) 113 | return [true, 'TOKEN-WHITESPACE']; 114 | 115 | if (token.replace(/\w|-|_|\./gm, '').length > 0) 116 | return [true, 'INVALID-TOKEN-CHARACTERS']; 117 | 118 | let tA = token.split('.'); 119 | if (tA.length != 3) return [true, 'INVALID-TOKEN-FORMAT']; 120 | // tA[0] = tA[0].length; 121 | // tA[1] = tA[1].length; 122 | // tA[2] = tA[2].length; 123 | // if ( 124 | // tA[0] < 24 || 125 | // tA[0] > 24 || 126 | // tA[1] < 6 || 127 | // tA[1] > 6 || 128 | // tA[2] < 27 || 129 | // tA[2] > 27 130 | // ) 131 | // return [true, 'INVALID-TOKEN-FORMAT']; 132 | 133 | return [false, 'none']; 134 | } 135 | -------------------------------------------------------------------------------- /css/textFormat.css: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Sebastian Ouellette 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. */ 14 | 15 | /* Message Container */ 16 | .inlineMsgCont { 17 | margin: -60px 0 20px 90px; 18 | } 19 | 20 | /* Emojis */ 21 | .emoji { 22 | height: 22px; 23 | margin: -2px 0 0 0; 24 | transform: translateY(2px); 25 | } 26 | 27 | .embed .emoji { 28 | height: 18px !important; 29 | } 30 | 31 | .bigEmoji .emoji { 32 | height: 50px !important; 33 | } 34 | 35 | .spoilerBlock .emoji { 36 | opacity: 0; 37 | } 38 | 39 | .discovered .emoji { 40 | opacity: 1; 41 | } 42 | 43 | /* Links */ 44 | a { 45 | color: #00b0f4; 46 | text-decoration: none; 47 | } 48 | 49 | a:hover { 50 | text-decoration: underline; 51 | } 52 | 53 | /* Image Previews */ 54 | .previewImage { 55 | margin-bottom: 10px; 56 | height: auto; 57 | } 58 | 59 | /* Message Text */ 60 | .messageText { 61 | font-family: Whitney-light, "Helvetica Neue", Helvetica, Arial, sans-serif; 62 | font-size: 16px; 63 | font-weight: 400; 64 | padding: 3px 0 3px 0; 65 | margin: 0; 66 | color: #dcddde; 67 | width: calc(100% - 40px); 68 | word-spacing: 2px; 69 | position: relative; 70 | overflow: hidden; 71 | } 72 | 73 | .messageCont { 74 | width: 100%; 75 | /* border-top: 1px solid #484B51; */ 76 | padding: 5px 0; 77 | min-height: 57px; 78 | overflow-y: hidden; 79 | overflow-x: auto; 80 | padding-bottom: -30px; 81 | } 82 | 83 | .messageCont::-webkit-scrollbar { 84 | background-color: transparent; 85 | height: 6px; 86 | } 87 | 88 | .messageCont::-webkit-scrollbar-track { 89 | background-color: transparent; 90 | } 91 | 92 | .messageCont::-webkit-scrollbar-thumb { 93 | background-color: #25272b; 94 | border-radius: 3px; 95 | } 96 | 97 | .messageTimestamp + .messageText { 98 | margin-top: -26px; 99 | } 100 | 101 | .messageBlock { 102 | display: block; 103 | position: relative; 104 | padding-left: 90px; 105 | border-radius: 3px; 106 | } 107 | 108 | .messageBlock.firstmsg { 109 | padding-top: 5px; 110 | padding-bottom: 2px; 111 | } 112 | 113 | .messageBlock:hover { 114 | background-color: #32353b; 115 | z-index: 0; 116 | } 117 | 118 | .messageUsername { 119 | -webkit-user-drag: none; 120 | cursor: default; 121 | display: inline-block; 122 | margin: 3px 0; 123 | left: -40px; 124 | top: -26px; 125 | position: relative; 126 | } 127 | 128 | .messageUsername:hover { 129 | text-decoration: underline; 130 | } 131 | 132 | .messageTimestamp { 133 | color: #72767d; 134 | font-size: 12px; 135 | display: inline-block; 136 | margin: 3px 0; 137 | top: -26px; 138 | left: -35px; 139 | position: relative; 140 | } 141 | 142 | .messageImg { 143 | height: 40px; 144 | left: -60px; 145 | border-radius: 50%; 146 | user-select: none; 147 | z-index: 1; 148 | position: relative; 149 | } 150 | 151 | .barryImg { 152 | left: 30px; 153 | top: 5px; 154 | user-select: none; 155 | } 156 | 157 | /* Code Block */ 158 | .codeBlock { 159 | width: 100%; 160 | background-color: #2f3136; 161 | border: 1px solid #202225; 162 | border-radius: 3px; 163 | padding: 5px; 164 | font-family: monospace !important; 165 | font-size: 14px; 166 | color: #b9bbbe; 167 | } 168 | 169 | .codeBlockEmbed { 170 | margin-top: 10px; 171 | background-color: #202225; 172 | } 173 | 174 | /* Inline Code Block */ 175 | .inlineCodeBlock { 176 | background-color: #2f3136; 177 | padding: 2px; 178 | font-family: monospace; 179 | font-size: 14px; 180 | } 181 | 182 | /* Spoiler Tag */ 183 | .spoilerBlock { 184 | background-color: #202225; 185 | color: transparent; 186 | cursor: pointer; 187 | border-radius: 3px; 188 | user-select: none; 189 | min-width: 20px; 190 | height: 22px; 191 | display: inline-block; 192 | } 193 | 194 | .spoilerBlock:hover { 195 | background-color: #24262a; 196 | } 197 | 198 | .spoilerBlock.discovered { 199 | color: #dcddde; 200 | background-color: #474a4f; 201 | } 202 | 203 | .spoilerBlock.discovered:hover { 204 | background-color: #474a4f; 205 | } 206 | 207 | /* Mention */ 208 | .ping { 209 | background-color: #3d414f; 210 | padding: 0 2px 2px 2px; 211 | color: #7289da; 212 | border-radius: 3px; 213 | } 214 | 215 | .ping:hover { 216 | background-color: #7289da; 217 | cursor: pointer; 218 | color: #dcddde; 219 | } 220 | 221 | .emoji { 222 | height: 22px; 223 | } 224 | 225 | .emoji.big { 226 | height: 50px; 227 | } 228 | 229 | .edited { 230 | font-size: 10px; 231 | color: #72767d; 232 | user-select: none; 233 | } 234 | -------------------------------------------------------------------------------- /js/typingStatus.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Sebastian Ouellette 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | let typingTimer = { 18 | indicator: { 19 | timer: null, // Current timer 20 | func: function () { 21 | // Function 22 | typingStatus(false); 23 | }, 24 | check: function (time, size) { 25 | if (this.timer == null && size) { 26 | this.timer = setInterval(this.func, time); 27 | } else if (!size && this.timer != null) { 28 | clearInterval(this.timer); 29 | this.timer = null; 30 | } 31 | }, 32 | }, 33 | typingTimeout: { 34 | timers: {}, // Current timers 35 | check: function (id) { 36 | if (id in this.timers) { 37 | this.timers[id]['timeout'] = bot.user._typing.get(id).count; 38 | return; 39 | } 40 | 41 | let timeout = bot.user._typing.get(id).count; 42 | let channel = bot.channels.cache.find((e) => e.id == id); 43 | 44 | if (!channel) return; 45 | 46 | let interval = setInterval(() => { 47 | this.decrease(id, channel); 48 | }, 1000); 49 | this.timers[id] = { timeout, interval }; 50 | }, 51 | decrease: function (id, channel) { 52 | this.timers[id]['timeout']--; 53 | if (this.timers[id]['timeout'] < 0) { 54 | clearInterval(this.timers[id]['interval']); 55 | delete this.timers[id]; 56 | } 57 | }, 58 | }, 59 | }; 60 | 61 | function typingStatus(override = false, m = undefined) { 62 | if (!selectedChan) return; 63 | let dms = selectedChan.type == Discord.ChannelType.DM; 64 | let indicator = document.getElementById('typingIndicator'); 65 | let users = []; 66 | selectedChan._typing?.each((e) => 67 | users.push( 68 | dms 69 | ? bot.users.cache.get(e.user.id) 70 | : selectedChan.members.get(e.user.id) 71 | ) 72 | ); 73 | let text = ''; 74 | 75 | let self = users.find((member) => member.user == bot.user); 76 | if (self) users.splice(users.indexOf(self), 1); 77 | 78 | let length = users.length; 79 | 80 | if (length) { 81 | document.getElementById('typingDots').classList.add('enabled'); 82 | } else { 83 | document.getElementById('typingDots').classList.remove('enabled'); 84 | } 85 | 86 | for (let user in users) { 87 | let name = dms ? users[user].username : users[user].displayName; 88 | if (user != length - 1) text += name + ', '; 89 | else text += name; 90 | } 91 | 92 | let boldText = length > 4 ? `Several people are typing...` : `${text}`; 93 | let endText = `${ 94 | length == 0 ? '' : length == 1 ? ' is typing...' : ' are typing...' 95 | }`; 96 | 97 | if (indicator.innerText != boldText + endText) { 98 | indicator.innerHTML = ''; 99 | // Create the element for the bold text 100 | let boldTextElement = document.createElement('span'); 101 | boldTextElement.innerText = boldText; 102 | boldTextElement.classList.add('bold'); 103 | indicator.appendChild(boldTextElement); 104 | 105 | indicator.innerHTML += endText; 106 | } 107 | 108 | let shortestTime = 1000; 109 | if (selectedChan._typing?.size) { 110 | // Needs a set timer so it doesn't create 1000 timers at a time 111 | // The timings can be found in each users typing variable and checking by the smallest is the best bet 112 | selectedChan._typing.forEach((e) => { 113 | if (m != undefined) { 114 | let id = m.author.id; 115 | if (e.user.id == id && selectedChan._typing.has(id)) { 116 | clearTimeout(selectedChan._typing.get(id).timeout); 117 | selectedChan._typing.delete(id); 118 | m.author._typing.delete(selectedChan.id); 119 | return typingStatus(); 120 | } 121 | } 122 | if (lastTime < e.elapsedTime) { 123 | shortestTime = e.elapsedTime; 124 | let elapsedTime = 125 | new Date().getTime() - new Date(e.lastTimestamp).getTime(); 126 | if (shortestTime > shortestTime - elapsedTime) 127 | shortestTime = elapsedTime; 128 | } 129 | }); 130 | } 131 | 132 | typingTimer.indicator.check(shortestTime, selectedChan._typing?.size || 0); 133 | } 134 | 135 | let lastTime = 0; 136 | 137 | // setInterval(typingStatus, 1000); 138 | -------------------------------------------------------------------------------- /js/buildTeamMemberCards.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Sebastian Ouellette 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | function buildTeamMemberCards(funky) { 18 | bot.application.owner.members.each((m) => { 19 | // Create the container 20 | let container = document.createElement('div'); 21 | container.classList.add('teamMember'); 22 | document.getElementById('selectMember').appendChild(container); 23 | 24 | // Create the area for the icon and name 25 | let userArea = document.createElement('div'); 26 | userArea.classList.add('userArea'); 27 | container.appendChild(userArea); 28 | 29 | // Create the icon and username 30 | let img = document.createElement('img'); 31 | img.src = m.user.displayAvatarURL(); 32 | img.classList.add('teamMemberIcon'); 33 | userArea.appendChild(img); 34 | 35 | let tag = document.createElement('div'); 36 | tag.classList.add('teamMemberTag'); 37 | userArea.appendChild(tag); 38 | 39 | // Username 40 | let username = document.createElement('span'); 41 | username.classList.add('teamMemberName'); 42 | username.innerText = m.user.username; 43 | tag.appendChild(username); 44 | 45 | // Discriminator 46 | let discrim = document.createElement('span'); 47 | discrim.classList.add('teamMemberDisc'); 48 | discrim.innerText = `#${m.user.discriminator}`; 49 | tag.appendChild(discrim); 50 | 51 | // Buttons 52 | let once = document.createElement('div'); 53 | once.classList.add('teamMemberButton'); 54 | container.appendChild(once); 55 | 56 | let onceSpan = document.createElement('span'); 57 | onceSpan.classList.add('oneline'); 58 | onceSpan.innerText = 'Sign in once'; 59 | once.appendChild(onceSpan); 60 | 61 | // Default button 62 | let defaultButton = document.createElement('div'); 63 | defaultButton.classList.add('teamMemberButton'); 64 | container.appendChild(defaultButton); 65 | 66 | let defaultSpan = document.createElement('span'); 67 | defaultSpan.innerText = 'Sign in and set as default'; 68 | defaultButton.appendChild(defaultSpan); 69 | 70 | // Event listeners 71 | once.addEventListener('click', () => { 72 | bot.owner = m.user; 73 | funky(); 74 | }); 75 | defaultButton.addEventListener('click', () => { 76 | bot.owner = m.user; 77 | settings.tokenSettings = { teamUser: m.user.id }; 78 | 79 | funky(); 80 | }); 81 | }); 82 | } 83 | 84 | function buildSplashToken() { 85 | let container = document.createElement('div'); 86 | container.classList.add('splashTokenContainer'); 87 | document.getElementById('selectMember').appendChild(container); 88 | 89 | // Create the token input field 90 | let input = document.createElement('input'); 91 | input.classList.add('splashScreenToken'); 92 | input.classList.add('tokenbox'); // Added cause of the animation 93 | input.type = 'password'; 94 | input.placeholder = 'Input your token'; 95 | container.appendChild(input); 96 | 97 | // One time button 98 | let oneTimeButton = document.createElement('div'); 99 | oneTimeButton.classList.add('tokenAddButton'); 100 | container.appendChild(oneTimeButton); 101 | 102 | let oneTimeSpan = document.createElement('span'); 103 | oneTimeSpan.innerText = 'One time login'; 104 | oneTimeButton.appendChild(oneTimeSpan); 105 | 106 | // Default button 107 | let defaultButton = document.createElement('div'); 108 | defaultButton.classList.add('tokenAddButton'); 109 | container.appendChild(defaultButton); 110 | 111 | let defaultSpan = document.createElement('span'); 112 | defaultSpan.innerText = 'Log in and save as default'; 113 | 10; 114 | defaultButton.appendChild(defaultSpan); 115 | 116 | // Event listeners 117 | oneTimeButton.addEventListener('click', async () => { 118 | let token = input.value; 119 | let error = [false, 'none']; 120 | 121 | if (global.bot === undefined) error = await load(token); 122 | else error = await setToken(token); 123 | 124 | if (!error[0]) { 125 | document.getElementById('selectMember').removeChild(container); 126 | setLoadingPerc(0); 127 | } else { 128 | errorHandler(error[1]); 129 | } 130 | }); 131 | defaultButton.addEventListener('click', async () => { 132 | let token = input.value; 133 | let error = await saveToken(token); 134 | if (!error[0]) { 135 | document.getElementById('selectMember').removeChild(container); 136 | setLoadingPerc(0.05); 137 | } else { 138 | errorHandler(error[1]); 139 | } 140 | }); 141 | } 142 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
20 |
21 | LiveBot is a program built with Electron and Discord.js that will allow you to control your bot from a client resembling the official one for users. You are able to view messages in channels, send messages, interact with people and see what is going on in the servers your bot is in.
22 |
23 | LiveBot has been built so that the style and flow is similar to discord, and while it is not exact, it is pretty close. It is also built from scratch using no frameworks, just straight HTML, CSS, and JavaScript. Due to this, and having a very small development team, additional features may take a while to be added. The point is to built from scratch, we know very well that it's not the fastest way to make something like this. However, LiveBot is slowly being filled with cool exclusive features like the pull-up user settings menu, the soon to come embed builder, native support for scripts, and more!
24 |
25 | ## Installation
26 | ### Release (recommended)
27 |
28 | To download a binary executable file, simply click on the latest [Release](../../releases) on the right side of the page, and download whichever zip or tar.gz file suits your needs. For example, if you use Windows, you should download the zip file that says "win32", if you use Linux, you will download the file that says "linux", and if you use macOS you will download the file that says "darwin". Once you download and decompress the file, just execute it as you would any other application.
29 |
30 |
31 | ### From Source
32 |
33 | LiveBot requires the [Node.js] JavaScript runtime to run.
34 |
35 | You can download the source with [Git] (and get updates easily) or you can download as a zipped (compressed) file from GitHub. If you wish to download as a zip file, GitHub provides a big green "Code" button which you can press to download the file.
36 |
37 | #### Step 1
38 | If you want to download with Git, a bash command to do so is found below:
39 | ```sh
40 | # with SSH (requires public key to be set)
41 | git clone "git@github.com:SebOuellette/LiveBot.git"
42 |
43 | # with HTTP
44 | git clone "https://github.com/SebOuellette/LiveBot.git"
45 | ```
46 |
47 | #### Step 2
48 | After cloning with Git or decompressing the zip file, you must open the directory the source is located in and install the dependencies with npm (or your preferred package manager) with the following command:
49 | ```sh
50 | npm install
51 | ```
52 |
53 | #### Step 3
54 | As of 2025, LiveBot requires a patch for internal libraries. This patch is included in the repository, and can be applied using the following command:
55 | ```sh
56 | tar -xvf patch.tar
57 | ```
58 |
59 | #### Step 4
60 | When your package manager is finished installing, you may start the program with:
61 | ```sh
62 | npm start
63 | ```
64 |
65 | ## Features
66 | ### Token switcher (User menu)
67 | This box is how you log into a profile after already logging in. To log into a token by default, put it into this box then press the save button. Otherwise if you are just logging into a token just for a look, input the token into the box then press `enter`.
68 |
69 | The token changer used to be at the top of the main screen, but has moved to the user options menu.
70 |
71 |
72 |
73 | ### Barry
74 | Barry is the LiveBot version of discord's Clyde. Except Barry has some fun commands! Just use the prefix `/` and to see the commands write `/help`. Only you can see what barry says, so don't worry about interfering with any conversations.
76 |
77 | ### Profile card
78 | The profile card is able to tell you the username, the avatar image, the discriminator, and something discord does not have. The profile card will tell you if the account you are signed into is a bot or a user. This can be handy if you are not quite sure. As you can no longer login with a user account it's just legacy code and for the looks. This card also has the pull up menu on the right side, so you can get to all the settings.
81 |
82 | ### Generate Invite
83 |
84 | LiveBot gives you the ability to generate an invite quickly from within the app, instead of having to go to your bot's application page in the Discord Developer portal. It can be found in the user settings page in the pull up menu. Just toggle all the permissions you'd like to give the invite (by default, everything recommended to use LiveBot's features is enabled).
85 |
86 |
87 |
88 | [node.js]: https://nodejs.org
89 | [git]: https://git-scm.com
90 |
--------------------------------------------------------------------------------
/css/master.css:
--------------------------------------------------------------------------------
1 | /* Copyright 2017 Sebastian Ouellette
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License. */
14 |
15 | div > * {
16 | user-select: text;
17 | }
18 |
19 | div {
20 | user-select: none;
21 | }
22 |
23 | /* Some text styling */
24 | p {
25 | font-size: 16px;
26 | overflow-wrap: break-word;
27 | word-wrap: break-word;
28 | white-space: pre-wrap;
29 | }
30 |
31 | img {
32 | -webkit-user-drag: none;
33 | }
34 |
35 | body {
36 | background-color: #202225;
37 | font-family: Whitney-light, "Helvetica Neue", Helvetica, Arial, sans-serif;
38 | text-rendering: optimizeLegibility;
39 | }
40 |
41 | .window-icon-bg {
42 | height: 20px !important;
43 | margin-top: 5px;
44 | }
45 |
46 | .button {
47 | z-index: 101;
48 | margin-top: -10px;
49 | }
50 |
51 | #max-btn {
52 | background-color: grey;
53 | height: 18px;
54 | width: 30px;
55 | margin-left: calc(100vw - 50px);
56 | position: absolute;
57 | z-index: 101;
58 | margin-top: -3px;
59 | }
60 |
61 | .drag {
62 | background-color: #202225;
63 | height: 30px;
64 | width: 100vw;
65 | margin-top: -10px;
66 | margin-left: -8px;
67 | z-index: 100;
68 | position: absolute;
69 | }
70 |
71 | #main {
72 | position: fixed;
73 | height: calc(100vh - 20px);
74 | }
75 |
76 | /* User card */
77 | #userCard {
78 | height: 45px;
79 | border-bottom: 2px solid #202225;
80 | margin-bottom: 10px;
81 | }
82 |
83 | #userCardIcon {
84 | margin-top: 6px;
85 | margin-left: 45px;
86 | border-radius: 50%;
87 | }
88 |
89 | #userCardName {
90 | margin-top: -35px;
91 | margin-left: 80px;
92 | position: absolute;
93 | width: 140px;
94 | text-overflow: ellipsis;
95 | white-space: nowrap;
96 | overflow: hidden;
97 | }
98 |
99 | #userCardDiscrim {
100 | font-weight: 900;
101 | margin-top: -18px;
102 | font-size: 12px;
103 | margin-left: 80px;
104 | position: absolute;
105 | color: #999;
106 | }
107 |
108 | #userCardBot {
109 | position: absolute;
110 | font-size: 13px;
111 | margin-top: 13px;
112 | margin-left: 8px;
113 | background-color: #7289da;
114 | padding: 2px 3px 2px 2px;
115 | border-radius: 4px;
116 | }
117 |
118 | /* Channel stuff I think */
119 |
120 | #serverName {
121 | height: 40px;
122 | width: 260px;
123 | background-color: grey;
124 | margin: 0px 0 0px 50px;
125 | background-color: #2f3136;
126 | font-family: Whitney, "Helvetica Neue", Helvetica, Arial, sans-serif;
127 | position: absolute;
128 | border-bottom: 1px solid #202225;
129 | border-radius: 10px 0 0 0;
130 | }
131 |
132 | #guildName {
133 | padding-left: 10px;
134 | padding-top: 3px;
135 | margin-top: 0;
136 | margin-bottom: 10px;
137 | color: #bbb;
138 | width: calc(100% - 70px);
139 | white-space: nowrap;
140 | overflow: hidden;
141 | text-overflow: ellipsis;
142 | }
143 |
144 | #guildImg {
145 | height: 35px;
146 | margin-top: -30px;
147 | margin-left: 220px;
148 | border-radius: 50%;
149 | position: absolute;
150 | }
151 |
152 | #members-text {
153 | margin-top: -10px;
154 | font-weight: bold;
155 | color: #666;
156 | }
157 |
158 | #members-count {
159 | margin-left: 70px;
160 | color: #999;
161 | }
162 |
163 | .members {
164 | font-size: 12px;
165 | margin-top: -25px;
166 | margin-left: 10px;
167 | }
168 |
169 | .directMsg {
170 | transform: translate(10px, 7px);
171 | font-weight: bold;
172 | }
173 |
174 | /* Message List */
175 | #message-list {
176 | background-color: #36393e;
177 | height: calc(100vh - 80px);
178 | border-bottom: 1px solid #36393e;
179 | width: calc(100vw - 518px);
180 | position: absolute;
181 | margin-top: calc(-100vh + 60px);
182 | margin-left: 310px;
183 | overflow-y: auto;
184 | font-family: Whitney-light, "Helvetica Neue", Helvetica, Arial, sans-serif;
185 | overflow-x: hidden;
186 | position: absolute;
187 | padding-bottom: 60px;
188 | }
189 |
190 | #message-list::-webkit-scrollbar {
191 | background-color: transparent;
192 | width: 6px;
193 | }
194 |
195 | #message-list::-webkit-scrollbar-track {
196 | background-color: transparent;
197 | }
198 |
199 | #message-list::-webkit-scrollbar-thumb {
200 | background-color: #25272b;
201 | border-radius: 3px;
202 | }
203 |
204 | .timeSeparated {
205 | border-top: 1px solid #484b51;
206 | }
207 |
208 | /* Apology for not being able to view messages */
209 | .sorryNoLoad {
210 | height: 50px;
211 | text-align: center;
212 | color: #b9bbbe;
213 | font-family: Whitney-light, "Helvetica Neue", Helvetica, Arial, sans-serif;
214 | /* border-bottom: 1px solid #484B51; */
215 | }
216 |
217 | /* Settings stuff */
218 |
219 | .optionBox {
220 | width: 150px;
221 | padding: 5px;
222 | background-color: #484b51;
223 | border-radius: 5px;
224 | margin-left: auto;
225 | margin-right: auto;
226 | display: block;
227 | color: white;
228 | margin-top: 5px;
229 | border: none;
230 | outline: none;
231 | }
232 |
233 | #inviteBtn {
234 | cursor: pointer;
235 | }
236 |
237 | #inviteBtn:hover {
238 | background-color: #393939;
239 | }
240 |
--------------------------------------------------------------------------------
/js/messageGeneration.js:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Sebastian Ouellette
2 |
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 |
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | 'use strict';
16 |
17 | function findTimeDiff(m, previousMessage, count) {
18 | let bunch = false;
19 | let timebunch = false;
20 |
21 | if (previousMessage && previousMessage.author.id == m.author.id) {
22 | bunch = true;
23 |
24 | if (
25 | Math.floor(
26 | previousMessage.createdTimestamp / 1000 / 60 / 60 / 24
27 | ) != Math.floor(m.createdTimestamp / 1000 / 60 / 60 / 24)
28 | ) {
29 | bunch = false;
30 | timebunch = true;
31 | }
32 | } else {
33 | bunch = false;
34 | }
35 |
36 | return [bunch, timebunch];
37 | }
38 |
39 | function generateMsgHTML(
40 | m,
41 | previousMessage,
42 | count = -1,
43 | fetchSize = undefined
44 | ) {
45 | // Check if the messages should be grouped or not
46 | let result = [false, false];
47 | if (count == -1 || (count > 2 && count <= fetchSize)) {
48 | result = findTimeDiff(m, previousMessage);
49 | }
50 |
51 | // The bunched values
52 | let bunch = result[0];
53 | let timebunch = result[1];
54 |
55 | // Create the div for the dark background
56 | let darkBG = document.createElement('div');
57 | darkBG.classList.add('messageBlock');
58 | darkBG.id = m.id;
59 |
60 | // Create the messages
61 | let div;
62 | if (!bunch) {
63 | // Create message div
64 | div = document.createElement('div');
65 | div.classList.add('messageCont');
66 | div.classList.add(m.author.id);
67 | if (m.channel.type == Discord.ChannelType.DM) div.classList.add('dms');
68 | if (timebunch) {
69 | div.classList.add('timeSeparated');
70 | }
71 |
72 | // Inline message container
73 | // messageContainer = document.createElement("div");
74 | // messageContainer.classList.add(m.author.id);
75 | // messageContainer.classList.add('inlineMsgCont');
76 | // div.appendChild(messageContainer);
77 |
78 | // Create the dark background
79 | darkBG.classList.add('firstmsg');
80 | div.appendChild(darkBG);
81 |
82 | // Create user image
83 | let img = document.createElement('img');
84 | let userImg = (m.member || m.author).displayAvatarURL({ size: 64 });
85 | if (m.author.avatar && m.author.avatar.startsWith('a_')) {
86 | let userGif = m.author.displayAvatarURL({ size: 128 });
87 | img.src = userGif;
88 | darkBG.onmouseenter = (e) => {
89 | img.src = userGif;
90 | };
91 | darkBG.onmouseleave = (e) => {
92 | img.src = userImg;
93 | };
94 | }
95 | img.classList.add('messageImg');
96 | img.src = userImg;
97 | img.height = '40';
98 | img.width = '40';
99 | darkBG.appendChild(img);
100 |
101 | // Create user's name
102 | let name = document.createElement('p');
103 | name.innerText = m.member?.nickname || m.author.username;
104 | name.classList.add('messageUsername');
105 |
106 | // Find the colour of their name
107 | // Use the highest role for their color
108 | name.style.color = m.member?.roles.color?.hexColor || '#fff';
109 |
110 | darkBG.appendChild(name);
111 |
112 | // Create timestamp
113 | let timestamp = document.createElement('p');
114 | timestamp.innerText =
115 | ' ' +
116 | m.createdAt.toLocaleString('en-US', {
117 | day: '2-digit',
118 | month: '2-digit',
119 | year: 'numeric',
120 | hour: '2-digit',
121 | minute: '2-digit',
122 | });
123 | timestamp.classList.add('messageTimestamp');
124 | darkBG.appendChild(timestamp);
125 | } else {
126 | div = document.getElementsByClassName(m.author.id);
127 | div = div[div.length - 1];
128 | div.appendChild(darkBG);
129 | }
130 |
131 | // Prepend message text
132 | if (m.cleanContent.length) {
133 | // Render message text
134 | let text = document.createElement('p');
135 | text.classList.add('messageText');
136 | text.innerHTML = parseMessage(m.cleanContent, m, false);
137 |
138 | if (m.editedAt)
139 | text.innerHTML += '';
140 |
141 | darkBG.appendChild(text);
142 | }
143 |
144 | // Prepend attachments
145 | if(m.attachments) {
146 | m.attachments.forEach((i) => {
147 | let attachText;
148 | if(i.contentType.includes("image")) {
149 | attachText = document.createElement('p');
150 | showEmbed({ type: "image", thumbnail: { proxy_url: i.url, width: i.width, height: i.height } }, attachText, m);
151 | }
152 | if(i.contentType.includes("video")) {
153 | attachText = document.createElement('p');
154 | showEmbed({ type: "video", video: { url: i.url, width: i.width, height: i.height } }, attachText, m);
155 | }
156 | if(attachText) {
157 | attachText.classList.add('messageText');
158 | darkBG.appendChild(attachText)
159 | }
160 | });
161 | }
162 |
163 | // Append embeds
164 | m.embeds.forEach((embed) => {
165 | showEmbed(embed.data, darkBG, m);
166 | });
167 | return div;
168 | }
169 |
--------------------------------------------------------------------------------
/js/channelSelect.js:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Sebastian Ouellette
2 |
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 |
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | 'use strict';
16 |
17 | let channelSelect = (c, name) => {
18 | let messages = document.getElementById('message-list');
19 | let fetchSize = 100;
20 |
21 | if (!Discord.Constants.TextBasedChannelTypes.includes(c.type)) {
22 | selectedVoice = c;
23 | return;
24 | }
25 |
26 | if (generatingMessages) {
27 | return;
28 | }
29 |
30 | selectedChan = c;
31 | selectedChanDiv = name;
32 | name.style.color = '#eee';
33 | messageCreate();
34 |
35 | // Refresh the typing indicator
36 | typingStatus(true);
37 |
38 | // Set the message bar placeholder
39 | document.getElementById('msgbox').placeholder = `Message #${c.name}`;
40 |
41 | // Remove the notification class
42 | name.classList.remove('newMsg');
43 |
44 | // Clear the messages
45 | while (messages.firstChild) {
46 | messages.removeChild(messages.firstChild);
47 | }
48 |
49 | // Creates the loading dots
50 | var container = document.createElement('div'); // Centred container
51 | var loadingDots = document.createElement('div'); // Loading dots
52 | loadingDots.classList.add('dot-bricks');
53 | container.style.position = 'absolute';
54 | container.style.top = '50%';
55 | container.style.left = '50%';
56 | container.style.transform = 'translate(-50%, -50%)';
57 | container.id = 'loading-container';
58 | container.appendChild(loadingDots);
59 | messages.appendChild(container);
60 |
61 | // Set colour of the channel
62 | try {
63 | selectedChanDiv.style.color = '#606266';
64 | name.addEventListener('mouseover', () => {
65 | if (name.style.color != 'rgb(238, 238, 238)') {
66 | name.style.color = '#B4B8BC';
67 | }
68 | });
69 |
70 | name.addEventListener('mouseleave', () => {
71 | if (name.style.color != 'rgb(238, 238, 238)') {
72 | name.style.color = '#606266';
73 | }
74 | });
75 | } catch (err) {
76 | console.log(err);
77 | }
78 |
79 | // Create the member list
80 | addMemberList(c.guild);
81 |
82 | // Create message
83 | async function messageCreate() {
84 | generatingMessages = true;
85 | // Loop through messages
86 | let count = 0;
87 | await c.messages.fetch({ limit: fetchSize }).then((messages) => {
88 | messages
89 | .toJSON()
90 | .reverse()
91 | .forEach((m) => {
92 | count++;
93 | let message = generateMsgHTML(
94 | m,
95 | messages.toJSON().reverse()[count - 2],
96 | count,
97 | fetchSize
98 | );
99 | document
100 | .getElementById('message-list')
101 | .appendChild(message);
102 | });
103 | });
104 | // Add the no load apology
105 | let shell = document.createElement('div');
106 | shell.classList.add('sorryNoLoad');
107 | let text = document.createElement('p');
108 | text.innerText =
109 | 'Sorry! No messages beyond this point can be displayed.';
110 | shell.appendChild(text);
111 | document.getElementById('message-list').prepend(shell);
112 |
113 | messages.scrollTop = messages.scrollHeight;
114 | generatingMessages = false;
115 |
116 | // Remove the loading dots
117 | messages.removeChild(document.getElementById('loading-container'));
118 | }
119 | };
120 |
121 | let dmChannelSelect = async (u, name = 'test') => {
122 | if (u.bot || bot.user == u) return;
123 | let messages = document.getElementById('message-list');
124 | let fetchSize = 100;
125 |
126 | if (!u.dmChannel) {
127 | await u.createDM();
128 | }
129 |
130 | let c = u.dmChannel;
131 |
132 | if (generatingMessages) {
133 | return;
134 | }
135 | if (!u.openDM) u.openDM = true;
136 |
137 | if (selectedChatDiv) {
138 | selectedChatDiv.classList.remove('selectedChan');
139 | selectedChatDiv = undefined;
140 | }
141 |
142 | selectedChan = c;
143 |
144 | messageCreate();
145 |
146 | // Refresh the typing indicator
147 | typingStatus(true);
148 |
149 | // Set the message bar placeholder
150 | document.getElementById(
151 | 'msgbox'
152 | ).placeholder = `Message #${c.recipient.username}`;
153 |
154 | // Clear the messages
155 | while (messages.firstChild) {
156 | messages.removeChild(messages.firstChild);
157 | }
158 |
159 | // Create message
160 | async function messageCreate() {
161 | generatingMessages = true;
162 | // Loop through messages
163 | let count = 0;
164 | await c.messages.fetch({ limit: fetchSize }).then((messages) => {
165 | messages
166 | .toJSON()
167 | .reverse()
168 | .forEach((m) => {
169 | count++;
170 | let message = generateMsgHTML(
171 | m,
172 | messages.toJSON().reverse()[count - 2],
173 | count,
174 | fetchSize
175 | );
176 | document
177 | .getElementById('message-list')
178 | .appendChild(message);
179 | });
180 | });
181 | // Add the no load apology
182 | let shell = document.createElement('div');
183 | shell.classList.add('sorryNoLoad');
184 | let text = document.createElement('p');
185 | text.innerText =
186 | 'Sorry! No messages beyond this point can be displayed.';
187 | shell.appendChild(text);
188 | document.getElementById('message-list').prepend(shell);
189 |
190 | messages.scrollTop = messages.scrollHeight;
191 | generatingMessages = false;
192 | }
193 | };
194 |
--------------------------------------------------------------------------------
/scripts/how_to_write_livebot_scripts.md:
--------------------------------------------------------------------------------
1 | # How to Write LiveBot Scripts
2 | A reference guide for extending and customizing LiveBot using standalone JavaScript modules.
3 |
4 | LiveBot supports user-created scripts that can modify the UI, automate moderation tasks, add developer tools, and enhance the overall client experience. This guide explains how to write clean, safe, and maintainable scripts that integrate smoothly with LiveBot’s environment.
5 |
6 | ---
7 |
8 | ## ✅ 1. Overview
9 | LiveBot loads all `.js` files placed inside the `/scripts` directory.
10 | Each script runs in the LiveBot renderer process and has access to:
11 |
12 | - The Discord.js client (`window.bot`)
13 | - The DOM of the LiveBot UI
14 | - Standard browser APIs (MutationObserver, fetch, localStorage, etc.)
15 | - Node.js modules (via `require()`)
16 |
17 | Scripts are **modular**, **self-contained**, and **do not require modifying LiveBot’s core files**.
18 |
19 | ---
20 |
21 | ## ✅ 2. Basic Script Structure
22 |
23 | A typical LiveBot script looks like this:
24 |
25 | ```js
26 | (function() {
27 | console.log("My script loaded!");
28 |
29 | // Your logic here
30 | })();
31 | ```
32 |
33 | Wrapping your code in an IIFE:
34 | - Prevents global namespace pollution
35 | - Ensures your script runs immediately
36 | - Keeps variables isolated and safe
37 |
38 | ---
39 |
40 | ## ✅ 3. Accessing the Discord Client
41 |
42 | LiveBot exposes the Discord.js client as:
43 |
44 | ```js
45 | window.bot
46 | ```
47 |
48 | Example:
49 |
50 | ```js
51 | const guilds = window.bot.guilds.cache;
52 | console.log("Loaded guilds:", guilds.map(g => g.name));
53 | ```
54 |
55 | This gives you full access to:
56 |
57 | - Guilds
58 | - Channels
59 | - Roles
60 | - Members
61 | - Permissions
62 | - Events
63 |
64 | LiveBot typically uses Discord.js v14, so permission flags follow:
65 |
66 | ```js
67 | PermissionsBitField.Flags.SendMessages
68 | PermissionsBitField.Flags.CreateInstantInvite
69 | ```
70 |
71 | ---
72 |
73 | ## ✅ 4. Detecting the Current Guild
74 |
75 | LiveBot does not expose a global “current guild” variable.
76 | The most reliable method is to detect the **selected channel** in the UI and resolve its guild:
77 |
78 | ```js
79 | function getCurrentGuild() {
80 | const selected = document.querySelector(".channel.selectedChan");
81 | if (!selected) return null;
82 |
83 | const channelId = selected.id;
84 |
85 | return window.bot.guilds.cache.find(g =>
86 | g.channels.cache.has(channelId)
87 | );
88 | }
89 | ```
90 |
91 | This works across all LiveBot versions.
92 |
93 | ---
94 |
95 | ## ✅ 5. Watching for UI Changes
96 |
97 | LiveBot’s UI re-renders frequently.
98 | To safely inject buttons, panels, or UI elements, use a `MutationObserver`:
99 |
100 | ```js
101 | new MutationObserver(() => {
102 | const list = document.getElementById("channel-list");
103 | if (!list) return;
104 |
105 | if (!document.getElementById("myButton")) {
106 | const btn = document.createElement("button");
107 | btn.id = "myButton";
108 | btn.textContent = "Click Me";
109 | list.prepend(btn);
110 | }
111 | }).observe(document.body, { childList: true, subtree: true });
112 | ```
113 |
114 | This ensures your UI elements persist even after LiveBot refreshes the DOM.
115 |
116 | ---
117 |
118 | ## ✅ 6. Editing Permissions (Discord.js v14)
119 |
120 | Example: Lock all channels for @everyone:
121 |
122 | ```js
123 | const { PermissionsBitField } = require("discord.js");
124 |
125 | async function lockAllChannels(guild) {
126 | const everyone = guild.roles.everyone;
127 |
128 | guild.channels.cache.forEach(async channel => {
129 | try {
130 | await channel.permissionOverwrites.edit(everyone, {
131 | [PermissionsBitField.Flags.SendMessages]: false
132 | });
133 | } catch (err) {
134 | console.error("Failed to lock:", channel.name, err);
135 | }
136 | });
137 | }
138 | ```
139 |
140 | ---
141 |
142 | ## ✅ 7. Adding Custom UI Panels
143 |
144 | You can create floating panels, toolbars, or menus:
145 |
146 | ```js
147 | function createPanel() {
148 | if (document.getElementById("myPanel")) return;
149 |
150 | const panel = document.createElement("div");
151 | panel.id = "myPanel";
152 | panel.style.cssText = `
153 | position: absolute;
154 | top: 80px;
155 | left: 10px;
156 | width: 250px;
157 | background: #2f3136;
158 | padding: 12px;
159 | border-radius: 8px;
160 | color: white;
161 | z-index: 9999;
162 | `;
163 |
164 | panel.innerHTML = `
165 |