├── .gitmodules ├── @types └── index.d.ts ├── .github └── FUNDING.yml ├── .dockerignore ├── .vs ├── ProjectSettings.json ├── slnx.sqlite ├── Botbear2.0 │ └── v16 │ │ └── .suo └── VSWorkspaceState.json ├── web ├── public │ ├── img │ │ ├── reddit.png │ │ ├── LETSPEPE.png │ │ └── spotify_logo.png │ └── js │ │ └── script.js ├── views │ ├── partials │ │ └── header.ejs │ ├── success.ejs │ ├── resolved.ejs │ ├── suggestions.ejs │ ├── music.ejs │ └── pets.ejs ├── routes │ ├── twitch │ │ ├── success.js │ │ ├── auth.js │ │ ├── login.js │ │ └── authCallback.js │ ├── suggestions.js │ ├── resolved.js │ ├── pets.js │ ├── commands.js │ ├── music.js │ └── spotify │ │ ├── login.js │ │ └── callback.js └── app.js ├── tools ├── bannedPhrases.js ├── regex.js ├── markovLogger.js ├── whisperHandler.js ├── twitchAuth.js ├── spotifyTools.js ├── fetchEmotes.js └── messageHandler.js ├── .pre-commit-config.yaml ├── sql ├── README.md └── migrations │ └── 2_update.sql ├── reminders ├── index.js ├── constants.js └── cdr.js ├── defaults ├── command.category.default.js ├── command.default.js └── env.default.txt ├── tsconfig.json ├── commands ├── hint.js ├── wifecheck.js ├── github.js ├── giveflower.js ├── hug.js ├── suggestions.js ├── coinflip.js ├── flower.js ├── bible.js ├── spam.js ├── whisper.js ├── dog.js ├── duck.js ├── cat.js ├── rabbit.js ├── cum.js ├── test2.js ├── restart.js ├── piss.js ├── shit.js ├── uptime.js ├── temperature.js ├── shiba.js ├── bot.js ├── quran.js ├── filesay.js ├── permission.js ├── ping.js ├── randomdalle.js ├── suggest.js ├── deveval.js ├── customCommands │ └── apollo.js ├── oldestmod.js ├── oldestvip.js ├── randomask.js ├── latestmod.js ├── latestvip.js ├── reddit.js ├── code.js ├── vipcount.js ├── followcount.js ├── modcount.js ├── pyramid.js ├── delay.js ├── offlineonly.js ├── say.js ├── founders.js ├── gametime.js ├── profilepic.js ├── update.js ├── nasa.js ├── follows.js ├── chatters.js ├── apexmap.js ├── emotecount.js ├── accage.js ├── totalvods.js ├── reconnect.js ├── followage.js ├── vipcheck.js ├── deletesubs.js ├── botstats.js ├── color.js ├── modcheck.js ├── check.js ├── randomemote.js ├── enablenewcommands.js ├── optout.js ├── unoptout.js ├── help.js ├── channels.js ├── title.js ├── trivia2.js ├── tags.js ├── commands.js ├── downdetector.js ├── ban.js ├── unmute.js ├── recap.js ├── removed.js ├── rl.js ├── announce.js ├── markov.js ├── botsubs.js ├── thumbnail.js ├── mute.js ├── emotes.js ├── uid.js ├── dalle.js ├── suball.js ├── fl.js ├── vodsearch.js ├── add.js ├── lm.js ├── currentgametime.js ├── eval.js └── index.js ├── .eslintrc.js ├── Dockerfile ├── README.md ├── got └── index.js ├── package.json ├── connect └── connect.js ├── .gitignore ├── start.js └── configsetup.sh /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /@types/index.d.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log -------------------------------------------------------------------------------- /.vs/ProjectSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "CurrentProjectSetting": null 3 | } -------------------------------------------------------------------------------- /.vs/slnx.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hotbear1110/botbear/HEAD/.vs/slnx.sqlite -------------------------------------------------------------------------------- /.vs/Botbear2.0/v16/.suo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hotbear1110/botbear/HEAD/.vs/Botbear2.0/v16/.suo -------------------------------------------------------------------------------- /web/public/img/reddit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hotbear1110/botbear/HEAD/web/public/img/reddit.png -------------------------------------------------------------------------------- /web/public/img/LETSPEPE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hotbear1110/botbear/HEAD/web/public/img/LETSPEPE.png -------------------------------------------------------------------------------- /.vs/VSWorkspaceState.json: -------------------------------------------------------------------------------- 1 | { 2 | "ExpandedNodes": [ 3 | "" 4 | ], 5 | "PreviewInSolutionExplorer": false 6 | } -------------------------------------------------------------------------------- /tools/bannedPhrases.js: -------------------------------------------------------------------------------- 1 | exports.bannedPhrases = ['nik ger', 'nig ker', 'nig ger', 'nik ker', 'nigga', 'ոigga', 'ո']; -------------------------------------------------------------------------------- /web/public/img/spotify_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hotbear1110/botbear/HEAD/web/public/img/spotify_logo.png -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/mirrors-eslint 3 | rev: 'v8.19.0' 4 | hooks: 5 | - id: eslint -------------------------------------------------------------------------------- /web/views/partials/header.ejs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sql/README.md: -------------------------------------------------------------------------------- 1 | ### How to add own migrations 2 | 3 | Create a new file entry [here](./migrations/) with the format `VERSION_description.sql` 4 | Update [types](./../@types/global.d.ts) -------------------------------------------------------------------------------- /reminders/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is reminders to ThePositiveBot cookie & cdr commands. 3 | */ 4 | 5 | exports.CONSTANTS = require('./constants.js'); 6 | exports.cdr = require('./cdr.js'); 7 | exports.cookie = require('./cookie.js'); 8 | -------------------------------------------------------------------------------- /defaults/command.category.default.js: -------------------------------------------------------------------------------- 1 | /* Categories commands can be set to 2 | ///// Core command ///// 3 | ///// Notify command ///// 4 | ///// Info command ///// 5 | ///// Random command ///// 6 | ///// Dev command ///// 7 | ///// channelSpecific command ///// 8 | */ -------------------------------------------------------------------------------- /web/routes/twitch/success.js: -------------------------------------------------------------------------------- 1 | module.exports = (function () { 2 | const router = require('express').Router(); 3 | 4 | router.get('/', async (req, res) => { 5 | res.render('success'); 6 | }); 7 | 8 | return router; 9 | })(); 10 | -------------------------------------------------------------------------------- /reminders/constants.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | POSITIVE_BOT: '425363834', 3 | MODES: { 4 | 'whereAte': 0, 5 | 'ownChannel': 1, 6 | 'botChannel': 2 7 | }, 8 | ALREADY_CLAIMED: 'you have already claimed a cookie', 9 | API: (route) => `https://api.roaringiron.com/${route}` 10 | }; 11 | -------------------------------------------------------------------------------- /web/views/success.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Success 9 | 10 | 11 | 12 | 13 |

Success

14 | 15 | 16 | -------------------------------------------------------------------------------- /web/routes/suggestions.js: -------------------------------------------------------------------------------- 1 | module.exports = (function () { 2 | const sql = require('../../sql/index.js'); 3 | const router = require('express').Router(); 4 | 5 | /* /suggestions */ 6 | router.get('/', async (req, res) => { 7 | const suggestionList = await sql.Query('SELECT * FROM Suggestions'); 8 | 9 | res.render('suggestions', { suggestions: suggestionList.reverse() }); 10 | }); 11 | 12 | return router; 13 | })(); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2021", 5 | "sourceMap": true, 6 | "strictNullChecks": true, 7 | "allowJs": true, 8 | "alwaysStrict": true, 9 | "checkJs": false, 10 | "typeRoots": [ 11 | "./@types" 12 | ] 13 | }, 14 | "include": [ 15 | "./@types/**/*.d.ts", 16 | ], 17 | "exclude": [ 18 | "node_modules", 19 | ] 20 | } -------------------------------------------------------------------------------- /web/routes/resolved.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const router = require('express').Router(); 3 | const sql = require('../../sql/index.js'); 4 | 5 | module.exports = (function () { 6 | router.get('/', async (req, res) => { 7 | let state = req.query.state || ''; 8 | 9 | const hasState = (await sql.Query('SELECT * FROM Spotify WHERE state = ?', [state])).length; 10 | 11 | res.render('resolved', { hasState: hasState }); 12 | }); 13 | 14 | return router; 15 | })(); -------------------------------------------------------------------------------- /commands/hint.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'hint', 3 | ping: true, 4 | description: 'This command will give you a hint for the trivia. (If there is an active trivia)', 5 | permission: 100, 6 | category: 'Random command', 7 | noBanphrase: true, 8 | execute: async (channel, user, input, perm) => { 9 | try { 10 | if (module.exports.permission > perm) { 11 | return; 12 | } 13 | return; 14 | } catch (err) { 15 | console.log(err); 16 | return 'FeelsDankMan Error'; 17 | } 18 | } 19 | }; -------------------------------------------------------------------------------- /commands/wifecheck.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'wifecheck', 3 | ping: true, 4 | description: 'This command will make the bot respond with "doctorWTF"', 5 | permission: 100, 6 | category: 'Random command', 7 | noBanphrase: true, 8 | execute: async (channel, user, input, perm) => { 9 | try { 10 | if (module.exports.permission > perm) { 11 | return; 12 | } 13 | return 'doctorWTF'; 14 | } catch (err) { 15 | console.log(err); 16 | return 'FeelsDankMan Error'; 17 | } 18 | } 19 | }; -------------------------------------------------------------------------------- /commands/github.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'github', 3 | ping: true, 4 | description: 'This command will give you a link to my github', 5 | permission: 100, 6 | category: 'Core command', 7 | noBanphrase: true, 8 | execute: async (channel, user, input, perm) => { 9 | try { 10 | if (module.exports.permission > perm) { 11 | return; 12 | } 13 | return 'https://github.com/hotbear1110/botbear'; 14 | } catch (err) { 15 | console.log(err); 16 | return 'FeelsDankMan Error'; 17 | } 18 | } 19 | }; -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'env': { 3 | 'node': true, 4 | 'browser': false, 5 | 'commonjs': true, 6 | 'es2021': true 7 | }, 8 | 'extends': 'eslint:recommended', 9 | 'parserOptions': { 10 | 'ecmaVersion': 'latest' 11 | }, 12 | 'rules': { 13 | 'quotes': [ 14 | 'error', 15 | 'single' 16 | ], 17 | 'semi': [ 18 | 'error', 19 | 'always' 20 | ], 21 | 'no-async-promise-executor': 'off', 22 | 'no-unused-vars': [1, { 23 | 'varsIgnorePattern': '_' 24 | }] 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /commands/giveflower.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'giveflower', 3 | ping: false, 4 | description: 'Give a chatter a flower nymnHappy', 5 | permission: 100, 6 | category: 'Random command', 7 | execute: async (channel, user, input, perm) => { 8 | try { 9 | if (module.exports.permission > perm) { 10 | return; 11 | } 12 | if (input[2]) { 13 | return `${input[2]}, nymnFlower`; 14 | } 15 | return `${user.username}, nymnFlower`; 16 | } catch (err) { 17 | console.log(err); 18 | return 'FeelsDankMan Error'; 19 | } 20 | } 21 | }; -------------------------------------------------------------------------------- /commands/hug.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'hug', 3 | ping: false, 4 | description: 'This command will hug a given user. Example: "bb hug NymN"', 5 | permission: 100, 6 | category: 'Random command', 7 | execute: async (channel, user, input, perm) => { 8 | try { 9 | if (module.exports.permission > perm) { 10 | return; 11 | } 12 | if (input[2]) { 13 | return `${input[2]} dankHug`; 14 | } 15 | return `${user.username} dankHug`; 16 | 17 | } catch (err) { 18 | console.log(err); 19 | return 'FeelsDankMan Error'; 20 | } 21 | } 22 | }; -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | # Multi-stage build setup for running a shell script then Node JS code 3 | FROM ubuntu 4 | COPY "configsetup.sh" . 5 | RUN ["chmod", "+x", "./configsetup.sh"] 6 | ENTRYPOINT [ "./configsetup.sh" ] 7 | 8 | # Using lightweight node version 9 | FROM node:18.7.0-alpine 10 | 11 | # Create app directory 12 | WORKDIR /usr/src/app 13 | 14 | # Install app dependencies 15 | COPY package*.json ./ 16 | RUN npm install 17 | 18 | # Bundle app source 19 | COPY . . 20 | 21 | # Expose the listener ports 22 | EXPOSE 8080 23 | EXPOSE 3306 24 | 25 | # Init 26 | CMD [ "node", "start.js" ] -------------------------------------------------------------------------------- /commands/suggestions.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'suggestions', 3 | ping: true, 4 | description: 'This command will give you a link to a list of all the suggestions for the bot and their current status.', 5 | permission: 100, 6 | category: 'Info command', 7 | noBanphrase: true, 8 | execute: async (channel, user, input, perm) => { 9 | try { 10 | if (module.exports.permission > perm) { 11 | return; 12 | } 13 | return 'List of suggestions: https://hotbear.org/suggestions'; 14 | } catch (err) { 15 | console.log(err); 16 | return `FeelsDankMan Sql error: ${err.sqlMessage}`; 17 | } 18 | } 19 | }; -------------------------------------------------------------------------------- /tools/regex.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-misleading-character-class */ 2 | /* eslint-disable no-useless-escape */ 3 | // eslint-disable-next-line no-misleading-character-class 4 | exports.racism = new RegExp(/(?:(?:\b(? { 9 | try { 10 | if (module.exports.permission > perm) { 11 | return; 12 | } 13 | 14 | let responses = ['HEADS(yes)', 'TAILS(no)']; 15 | 16 | let number = Math.floor(Math.random() * (1 - 0 + 1)) + 0; 17 | 18 | return `${responses[number]}`; 19 | 20 | } catch (err) { 21 | console.log(err); 22 | return 'FeelsDankMan Error'; 23 | } 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /defaults/command.default.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'NAME', 3 | ping: true, 4 | description: 'DESCRIPTION', 5 | permission: 100, 6 | cooldown: 3, //in seconds 7 | category: 'CATEGORY [ ./command.category.default.js ]', 8 | opt_outable: false, 9 | showDelay: false, 10 | noBanphrase: false, 11 | channelSpecific: false, 12 | activeChannel: '', 13 | // eslint-disable-next-line no-unused-vars 14 | execute: async (channel, user, input, perm, aliascommand) => { 15 | try { 16 | if (module.exports.permission > perm) { 17 | return; 18 | } 19 | return 'THIS'; 20 | } catch (err) { 21 | console.log(err); 22 | return 'FeelsDankMan Error'; 23 | } 24 | } 25 | }; -------------------------------------------------------------------------------- /commands/flower.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'flower', 3 | ping: false, 4 | description: 'This command will let a user give flower to someone.', 5 | permission: 100, 6 | category: 'Random command', 7 | execute: async (channel, user, input, perm) => { 8 | try { 9 | if (module.exports.permission > perm) { 10 | return; 11 | } 12 | const flowers=['💐','🌸','💮','🏵️','🌹','🌺','🌻','🌼','🌷','nymnFlower']; 13 | return `${user.username} gave a flower to ${input[2] ?? 'no one (no friends) Sadge'} ${flowers[~~(Math.random() * flowers.length)]}`; 14 | //example --> bb flower yourmom 15 | 16 | } catch (err) { 17 | console.log(err); 18 | return 'FeelsDankMan Error'; 19 | } 20 | } 21 | }; -------------------------------------------------------------------------------- /web/routes/pets.js: -------------------------------------------------------------------------------- 1 | module.exports = (function () { 2 | const router = require('express').Router(); 3 | const sql = require('./../../sql/index.js'); 4 | 5 | /* /suggestions */ 6 | router.get('/(:page)?', async (req, res) => { 7 | let pets = await sql.Query('SELECT * FROM Yabbe_pet',); 8 | 9 | let perPage = 20; 10 | let page = req.params.page || 1; 11 | 12 | let count = pets.length; 13 | 14 | pets = pets.slice((perPage * page) - perPage, perPage * page); 15 | 16 | res.render('pets', { 17 | pets: pets, 18 | current: page, 19 | pages: Math.ceil(count / perPage) 20 | }); 21 | }); 22 | 23 | return router; 24 | })(); -------------------------------------------------------------------------------- /commands/bible.js: -------------------------------------------------------------------------------- 1 | const { got } = require('./../got'); 2 | 3 | module.exports = { 4 | name: 'bible', 5 | ping: true, 6 | description: 'This command will give a random bible quote', 7 | permission: 100, 8 | category: 'Random command', 9 | execute: async (channel, user, input, perm) => { 10 | try { 11 | if (module.exports.permission > perm) { 12 | return; 13 | } 14 | 15 | const url = 'https://labs.bible.org/api/?passage=random&type=json'; 16 | 17 | const response = await got(url).json(); 18 | return `[${response[0].bookname} ${response[0].chapter}:${response[0].verse}]: ${response[0].text} Prayge`; 19 | 20 | } catch (err) { 21 | console.log(err); 22 | return 'FeelsDankMan lost my bible'; 23 | } 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # botbear 2 | Dank bot that does stuff, idk 3 | Link to my commands: https://hotbear.org/ 4 | If you just want this bot in your channel just write "bb join" in #botbear1110 or #HotBear1110 :) 5 | Made by: Mads Hauberg 6 | 7 | ## Building 8 | 9 | ```bash 10 | $ git clone https://github.com/hotbear1110/botbear 11 | $ cd botbear 12 | $ ./configsetup.sh # optional script to setup needed credentials 13 | ``` 14 | 15 | Pre-commit setup 16 | ```bash 17 | $ pip install pre-commit 18 | $ pre-commit install 19 | ``` 20 | 21 | Docker setup 22 | 23 | ```bash 24 | $ cd .. 25 | $ sudo docker build --tag bot botbear 26 | $ sudo docker run -p 3306:3306 -p 8080:8080 bot 27 | # note that mysql database needs to be setup manually inside the container 28 | ``` 29 | -------------------------------------------------------------------------------- /commands/spam.js: -------------------------------------------------------------------------------- 1 | const cc = require('../bot.js').cc; 2 | 3 | module.exports = { 4 | name: 'spam', 5 | ping: true, 6 | description: 'spam something', 7 | permission: 2000, 8 | category: 'Dev command', 9 | execute: async (channel, user, input, perm) => { 10 | try { 11 | if (module.exports.permission > perm) { 12 | return; 13 | } 14 | let message = input.slice(); 15 | message.shift(); 16 | message.shift(); 17 | message.shift(); 18 | 19 | message = message.toString().replaceAll(',', ' '); 20 | 21 | await Promise.allSettled(Array.from({length: input[2]}).fill(message).map(x => cc.say(channel, x))); 22 | 23 | return; 24 | } catch (err) { 25 | console.log(err); 26 | return 'FeelsDankMan Error'; 27 | } 28 | } 29 | }; -------------------------------------------------------------------------------- /got/index.js: -------------------------------------------------------------------------------- 1 | module.exports = (() => { 2 | return new class { 3 | /** @type { import('got').Got } */ 4 | #got = null; 5 | 6 | async Setup() { 7 | this.#got = (await import('got')) 8 | .default 9 | .extend({ 10 | headers: { 11 | 'User-Agent': 'Botbear' 12 | }, 13 | timeout: { 14 | request: 20000 15 | }, 16 | http2: true 17 | }); 18 | } 19 | 20 | /** 21 | * @returns { import('got').Got } 22 | */ 23 | get got() { 24 | return this.#got; 25 | } 26 | }; 27 | })(); 28 | -------------------------------------------------------------------------------- /commands/whisper.js: -------------------------------------------------------------------------------- 1 | let whisperHandler = require('../tools/whisperHandler.js').whisperHandler; 2 | const tools = require('../tools/tools.js'); 3 | 4 | module.exports = { 5 | name: 'whisper', 6 | ping: false, 7 | description: 'This command makes the bot whisper a user', 8 | permission: 2000, 9 | category: 'Dev command', 10 | execute: async (channel, user, input, perm) => { 11 | try { 12 | if (module.exports.permission > perm) { 13 | return; 14 | } 15 | 16 | const message = input.slice(3).join(' '); 17 | 18 | const userID = tools.getUserIDs([input[2]]); 19 | 20 | new whisperHandler(userID, message).newWhisper(); 21 | 22 | return; 23 | } catch (err) { 24 | console.log(err); 25 | return 'FeelsDankMan Error'; 26 | } 27 | } 28 | }; -------------------------------------------------------------------------------- /commands/dog.js: -------------------------------------------------------------------------------- 1 | const { got } = require('./../got'); 2 | 3 | module.exports = { 4 | name: 'dog', 5 | ping: true, 6 | description: 'This command will give you a link to a picture of a random dog', 7 | permission: 100, 8 | category: 'Random command', 9 | execute: async (channel, user, input, perm) => { 10 | try { 11 | if (module.exports.permission > perm) { 12 | return; 13 | } 14 | const image = await got('https://dog.ceo/api/breeds/image/random').json(); 15 | 16 | return `OhMyDog ${image.message}`; 17 | 18 | } catch (err) { 19 | console.log(err); 20 | if (err.name) { 21 | if (err.name === 'TimeoutError') { 22 | return `FeelsDankMan api error: ${err.name}`; 23 | } 24 | } 25 | return 'FeelsDankMan Error'; 26 | } 27 | } 28 | }; -------------------------------------------------------------------------------- /commands/duck.js: -------------------------------------------------------------------------------- 1 | const { got } = require('./../got'); 2 | 3 | module.exports = { 4 | name: 'duck', 5 | ping: true, 6 | description: 'This command will give you a link to a picture of a random duck', 7 | permission: 100, 8 | category: 'Random command', 9 | execute: async (channel, user, input, perm) => { 10 | try { 11 | if (module.exports.permission > perm) { 12 | return; 13 | } 14 | const image = await got('https://random-d.uk/api/v2/random').json(); 15 | 16 | return `DuckerZ quack ${image.url}`; 17 | 18 | } catch (err) { 19 | console.log(err); 20 | if (err.name) { 21 | if (err.name === 'TimeoutError') { 22 | return `FeelsDankMan api error: ${err.name}`; 23 | } 24 | } 25 | return 'FeelsDankMan Error'; 26 | } 27 | } 28 | }; -------------------------------------------------------------------------------- /commands/cat.js: -------------------------------------------------------------------------------- 1 | const { got } = require('./../got'); 2 | 3 | module.exports = { 4 | name: 'cat', 5 | ping: true, 6 | description: 'This command will give you a link to a picture of a random cat', 7 | permission: 100, 8 | category: 'Random command', 9 | execute: async (channel, user, input, perm) => { 10 | try { 11 | if (module.exports.permission > perm) { 12 | return; 13 | } 14 | const image = await got('https://api.thecatapi.com/v1/images/search').json(); 15 | 16 | return `nymnAww ${image[0].url}`; 17 | 18 | } catch (err) { 19 | console.log(err); 20 | if (err.name) { 21 | if (err.name === 'TimeoutError') { 22 | return `FeelsDankMan api error: ${err.name}`; 23 | } 24 | } 25 | return 'FeelsDankMan Error'; 26 | } 27 | } 28 | }; -------------------------------------------------------------------------------- /commands/rabbit.js: -------------------------------------------------------------------------------- 1 | const { got } = require('./../got'); 2 | 3 | module.exports = { 4 | name: 'rabbit', 5 | ping: true, 6 | description: 'This command will give you a link to a picture of a random rabbit', 7 | permission: 100, 8 | category: 'Random command', 9 | execute: async (channel, user, input, perm) => { 10 | try { 11 | if (module.exports.permission > perm) { 12 | return; 13 | } 14 | const image = await got('https://rabbit-api-two.vercel.app/api/random').json(); 15 | 16 | return `🐰 ${image.url}`; 17 | 18 | } catch (err) { 19 | console.log(err); 20 | if (err.name) { 21 | if (err.name === 'TimeoutError') { 22 | return `FeelsDankMan api error: ${err.name}`; 23 | } 24 | } 25 | return 'FeelsDankMan Error'; 26 | } 27 | } 28 | }; -------------------------------------------------------------------------------- /commands/cum.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'cum', 3 | ping: false, 4 | description: 'This command will make the bot cum in different ways. Example: "bb cum on NymN"', 5 | permission: 100, 6 | category: 'Random command', 7 | execute: async (channel, user, input, perm) => { 8 | try { 9 | if (module.exports.permission > perm) { 10 | return; 11 | } 12 | input.shift(); 13 | input.shift(); 14 | 15 | let msg = input.toString() 16 | .replaceAll(',', ' ') 17 | .replaceAll(/(?:^|\W)me(?:$|\W)/g, ' you ') 18 | .replaceAll(/(?:^|\W)my(?:$|\W)/g, ' your '); 19 | 20 | return `/me I came ${msg}`; 21 | } catch (err) { 22 | console.log(err); 23 | return 'FeelsDankMan Error'; 24 | } 25 | } 26 | }; -------------------------------------------------------------------------------- /commands/test2.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const sql = require('./../sql/index.js'); 3 | const { got } = require('./../got'); 4 | 5 | module.exports = { 6 | name: 'test2', 7 | ping: true, 8 | description: 'test', 9 | permission: 2000, 10 | category: 'Dev command', 11 | execute: async (channel, user, input, perm) => { 12 | try { 13 | if (module.exports.permission > perm) { 14 | return; 15 | } 16 | let msg = 'db test add name:"bibi" pet:"rat" user:"forsen" link:"test"'; 17 | 18 | this.user = [...msg.matchAll(/name:"([a-z\s0-9]+)"/gi)][0]; 19 | this.user = (this.user) ? this.user[1] : 'test123'; 20 | 21 | return this.user; 22 | } catch (err) { 23 | console.log(err); 24 | return `FeelsDankMan Sql error: ${err.sqlMessage}`; 25 | } 26 | } 27 | }; -------------------------------------------------------------------------------- /commands/restart.js: -------------------------------------------------------------------------------- 1 | let messageHandler = require('../tools/messageHandler.js').messageHandler; 2 | const shell = require('child_process'); 3 | 4 | module.exports = { 5 | name: 'restart', 6 | ping: false, 7 | description: 'restarts botbear', 8 | permission: 2000, 9 | cooldown: 3, //in seconds 10 | category: 'Dev command', 11 | // eslint-disable-next-line no-unused-vars 12 | execute: async (channel, user, input, perm, aliascommand) => { 13 | try { 14 | if (module.exports.permission > perm) { 15 | return; 16 | } 17 | Promise.all([await new messageHandler(channel, 'ppCircle restarting...', true).newMessage()]); 18 | 19 | shell.execSync('sudo reboot'); 20 | return; 21 | } catch (err) { 22 | console.log(err); 23 | return 'FeelsDankMan Error'; 24 | } 25 | } 26 | }; -------------------------------------------------------------------------------- /commands/piss.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'piss', 3 | ping: false, 4 | description: 'This command will make the bot piss in different ways. Example: "bb piss on NymN"', 5 | permission: 100, 6 | category: 'Random command', 7 | execute: async (channel, user, input, perm) => { 8 | try { 9 | if (module.exports.permission > perm) { 10 | return; 11 | } 12 | input.shift(); 13 | input.shift(); 14 | 15 | let msg = input.toString() 16 | .replaceAll(',', ' ') 17 | .replaceAll(/(?:^|\W)me(?:$|\W)/g, ' you ') 18 | .replaceAll(/(?:^|\W)my(?:$|\W)/g, ' your '); 19 | 20 | return `/me I pissed ${msg}`; 21 | } catch (err) { 22 | console.log(err); 23 | return 'FeelsDankMan Error'; 24 | } 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /web/routes/commands.js: -------------------------------------------------------------------------------- 1 | module.exports = (function () { 2 | const sql = require('../../sql/index.js'); 3 | const router = require('express').Router(); 4 | 5 | router.get('/', async (req, res) => { 6 | const commandlist = await sql.Query('SELECT * FROM Commands'); 7 | 8 | let categorylist = []; 9 | 10 | for (const command of commandlist) { 11 | if (!categorylist.includes(command.Category)) { 12 | categorylist.push(command.Category); 13 | } 14 | } 15 | 16 | categorylist = [categorylist[4], categorylist[5], categorylist[1], categorylist[3], categorylist[0], categorylist[2]]; 17 | 18 | res.render('commands', { commands: commandlist, categories: categorylist }); 19 | }); 20 | 21 | return router; 22 | })(); 23 | -------------------------------------------------------------------------------- /commands/shit.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'shit', 3 | ping: false, 4 | description: 'This command will make the bot shit in different ways. Example: "bb shit on NymN"', 5 | permission: 100, 6 | category: 'Random command', 7 | execute: async (channel, user, input, perm) => { 8 | try { 9 | if (module.exports.permission > perm) { 10 | return; 11 | } 12 | input.shift(); 13 | input.shift(); 14 | 15 | /** @type {string} */ 16 | let msg = input.toString() 17 | .replaceAll(',', ' ') 18 | .replaceAll(/(?:^|\W)me(?:$|\W)/g, ' you ') 19 | .replaceAll(/(?:^|\W)my(?:$|\W)/g, ' your '); 20 | 21 | return `/me I shat ${msg}`; 22 | } catch (err) { 23 | console.log(err); 24 | return 'FeelsDankMan Error'; 25 | } 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /commands/uptime.js: -------------------------------------------------------------------------------- 1 | const uptime = require('../bot.js').uptime; 2 | const tools = require('../tools/tools.js'); 3 | const shell = require('child_process'); 4 | 5 | 6 | module.exports = { 7 | name: 'uptime', 8 | ping: true, 9 | description: 'This command will tell you how long the bot has been live for', 10 | permission: 100, 11 | category: 'Info command', 12 | execute: async (channel, user, input, perm) => { 13 | try { 14 | if (module.exports.permission > perm) { 15 | return; 16 | } 17 | let now = new Date().getTime(); 18 | 19 | let ms = now - uptime; 20 | 21 | const commitCount = shell.execSync('git rev-list --all --count'); 22 | 23 | return `Uptime: ${tools.humanizeDuration(ms)} - commit: ${commitCount}`; 24 | 25 | } catch (err) { 26 | console.log(err); 27 | return 'FeelsDankMan Error'; 28 | } 29 | } 30 | }; -------------------------------------------------------------------------------- /commands/temperature.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'temperature', 3 | ping: true, 4 | description: 'This command will convert temperatures', 5 | permission: 100, 6 | cooldown: 3, //in seconds 7 | category: 'Random command', 8 | // eslint-disable-next-line no-unused-vars 9 | execute: async (channel, user, input, perm, aliascommand) => { 10 | try { 11 | if (module.exports.permission > perm) { 12 | return; 13 | } 14 | 15 | let value; 16 | switch (input[2]) { 17 | case 'toC': 18 | value=~~((input[3] - 32) * 5/9)+'°C'; 19 | break; 20 | case 'toF': 21 | value=~~(input[3] * 9/5) + 32+'°F'; 22 | break; 23 | default: return 'Available temperature commands: [toC, toF]. Example: bb temperature toC 90'; 24 | } 25 | return value; 26 | } catch (err) { 27 | console.log(err); 28 | return 'FeelsDankMan Error'; 29 | } 30 | } 31 | }; -------------------------------------------------------------------------------- /commands/shiba.js: -------------------------------------------------------------------------------- 1 | const { got } = require('./../got'); 2 | 3 | module.exports = { 4 | name: 'shiba', 5 | ping: true, 6 | description: 'This command will give you a link to a picture of a random shiba inu', 7 | permission: 100, 8 | category: 'Random command', 9 | execute: async (channel, user, input, perm) => { 10 | try { 11 | if (module.exports.permission > perm) { 12 | return; 13 | } 14 | const image = await got('http://shibe.online/api/shibes', { 15 | searchParams: { 16 | 'count': 1, 17 | 'urls': true, 18 | 'httpsUrls': true 19 | } 20 | }).json(); 21 | 22 | return `nymnAww ${image}`; 23 | } catch (err) { 24 | console.log(err); 25 | if (err.name === 'TimeoutError') { 26 | return `FeelsDankMan api error: ${err.name}`; 27 | } 28 | return 'FeelsDankMan Error'; 29 | } 30 | } 31 | }; -------------------------------------------------------------------------------- /commands/bot.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'bot', 3 | ping: true, 4 | description: 'This command will give a short explanation of the bot', 5 | permission: 100, 6 | category: 'Core command', 7 | noBanphrase: true, 8 | execute: async (channel, user, input, perm) => { 9 | try { 10 | if (module.exports.permission > perm) { 11 | return; 12 | } 13 | return 'nymnDank botbear1110 is a bot that can notify you in chat when a streamer goes live/changes title/changes game, by pinging you in chat. You can even set specific games that should ping you. The bot also has some other chat improving commands, that gives the chatters useful/fun information about the chat, other users and the stream :) The bot is writen in node.js and is made by: @hotbear1110. Link to my GitHub: https://github.com/hotbear1110/botbear'; 14 | } catch (err) { 15 | console.log(err); 16 | return 'FeelsDankMan Error'; 17 | } 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /commands/quran.js: -------------------------------------------------------------------------------- 1 | const { got } = require('./../got'); 2 | 3 | module.exports = { 4 | name: 'quran', 5 | ping: true, 6 | description: 'This command will give a random quran quote', 7 | permission: 100, 8 | category: 'Random command', 9 | execute: async (channel, user, input, perm) => { 10 | try { 11 | if (module.exports.permission > perm) { 12 | return; 13 | } 14 | let number = Math.floor(Math.random() * 6236) + 1; 15 | 16 | const url = `https://api.alquran.cloud/ayah/${number}/en.asad`; 17 | 18 | const response = await got(url).json(); 19 | 20 | console.log(response); 21 | return `[${response.data.surah.englishName}(${response.data.surah.englishNameTranslation}) ${response.data.surah.number}:${response.data.numberInSurah}]: ${response.data.text} Prayge`; 22 | 23 | } catch (err) { 24 | console.log(err.body); 25 | return 'FeelsDankMan lost my quran'; 26 | } 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /commands/filesay.js: -------------------------------------------------------------------------------- 1 | const { got } = require('./../got'); 2 | const cc = require('../bot.js').cc; 3 | 4 | module.exports = { 5 | name: 'filesay', 6 | ping: true, 7 | description: 'writes a message in chat for every line in the given file', 8 | permission: 2000, 9 | cooldown: 3, //in seconds 10 | category: 'Dev command', 11 | opt_outable: false, 12 | showDelay: false, 13 | noBanphrase: false, 14 | channelSpecific: false, 15 | activeChannel: '', 16 | // eslint-disable-next-line no-unused-vars 17 | execute: async (channel, user, input, perm, aliascommand) => { 18 | try { 19 | if (module.exports.permission > perm) { 20 | return; 21 | } 22 | let apicall = await got(input[2]); 23 | await Promise.allSettled(apicall.body.split('\n').map(x => cc.say(input[3] ?? channel, x))); 24 | 25 | return; 26 | } catch (err) { 27 | console.log(err); 28 | return 'FeelsDankMan Error'; 29 | } 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /commands/permission.js: -------------------------------------------------------------------------------- 1 | const sql = require('./../sql/index.js'); 2 | 3 | module.exports = { 4 | name: 'permission', 5 | ping: false, 6 | description: 'This command will change a given users permission. This will allow/disallow the user to do certain commands. Example: "bb permission NymN 2000"(will change Nymn´s permission to 2000)', 7 | permission: 2000, 8 | category: 'Dev command', 9 | noBanphrase: true, 10 | execute: async (channel, user, input, perm) => { 11 | try { 12 | if (module.exports.permission > perm) { 13 | return; 14 | } 15 | if (!input[2]) { 16 | return 'Please provide both the user and the permission level. Example: bb permission nymn 123'; 17 | } 18 | await sql.Query('UPDATE Users SET permission=? WHERE username=?', [input[3], input[2]]); 19 | return `${input[2]} now has permission ${input[3]}`; 20 | 21 | } catch (err) { 22 | console.log(err); 23 | return `FeelsDankMan Sql error: ${err.sqlMessage}`; 24 | } 25 | } 26 | }; -------------------------------------------------------------------------------- /commands/ping.js: -------------------------------------------------------------------------------- 1 | const sql = require('./../sql/index.js'); 2 | 3 | module.exports = { 4 | name: 'ping', 5 | ping: true, 6 | description: 'This command will make the bot respond with "pong!" and the tmi delay aswell as the internal delay, if the bot is online.', 7 | permission: 100, 8 | category: 'Core command', 9 | showDelay: true, 10 | noBanphrase: true, 11 | execute: async (channel, user, input, perm) => { 12 | try { 13 | if (module.exports.permission > perm) { 14 | return; 15 | } 16 | const latency = await sql.Query('SELECT Latency FROM Latency'); 17 | let delay; 18 | 19 | if (!latency.length) { 20 | delay = '*no data yet*'; 21 | } 22 | else { 23 | delay = JSON.parse(latency[latency.length - 1].Latency) * 1000 + 'ms'; 24 | } 25 | 26 | return `nymnDank pong! - Tmi delay: ${delay} - Internal delay:`; 27 | } catch (err) { 28 | console.log(err); 29 | return `FeelsDankMan Sql error: ${err.sqlMessage}`; 30 | } 31 | } 32 | }; -------------------------------------------------------------------------------- /commands/randomdalle.js: -------------------------------------------------------------------------------- 1 | const sql = require('./../sql/index.js'); 2 | 3 | module.exports = { 4 | name: 'randomdalle', 5 | ping: true, 6 | description: 'Gives you an random dalle image, another user has generated', 7 | permission: 100, 8 | cooldown: 3, //in seconds 9 | category: 'Random command', 10 | opt_outable: false, 11 | showDelay: false, 12 | noBanphrase: false, 13 | // eslint-disable-next-line no-unused-vars 14 | execute: async (channel, user, input, perm, aliascommand) => { 15 | try { 16 | if (module.exports.permission > perm) { 17 | return; 18 | } 19 | 20 | const prompts = await sql.Query('SELECT * FROM Dalle',); 21 | 22 | const prompt = prompts[~~(Math.random() * prompts.length - 1)]; 23 | 24 | return `Random dalle prompt | User: ${prompt.User} | Prompt: ${prompt.Prompt} | Image: ${prompt.Image} | TimeStamp: ${prompt.Timestamp.toLocaleString()}`; 25 | } catch (err) { 26 | console.log(err); 27 | return 'FeelsDankMan Error'; 28 | } 29 | } 30 | }; -------------------------------------------------------------------------------- /commands/suggest.js: -------------------------------------------------------------------------------- 1 | const sql = require('./../sql/index.js'); 2 | 3 | module.exports = { 4 | name: 'suggest', 5 | ping: true, 6 | description: 'This command will add a suggestion to my database, so I can read them and maybe add them. Example: "bb suggest Please add this command :) "', 7 | permission: 100, 8 | category: 'Core command', 9 | noBanphrase: true, 10 | execute: async (channel, user, input, perm) => { 11 | try { 12 | if (module.exports.permission > perm) { 13 | return; 14 | } 15 | input = input.splice(2); 16 | 17 | const msg = input.join(' '); 18 | 19 | await sql.Query('INSERT INTO Suggestions (User, Suggestion) values (?, ?)', [user.username, msg]); 20 | 21 | const IDs = await sql.Query('SELECT MAX(ID) FROM Suggestions WHERE User=?', [user.username]); 22 | 23 | return `Your suggestion was saved as 'ID ${IDs[0]['MAX(ID)']}' nymnDank 👍 `; 24 | } catch (err) { 25 | console.log(err); 26 | return `FeelsDankMan Sql error: ${err.sqlMessage}`; 27 | } 28 | } 29 | }; -------------------------------------------------------------------------------- /commands/deveval.js: -------------------------------------------------------------------------------- 1 | const tools = require('../tools/tools.js'); 2 | const { got } = require('./../got'); 3 | const sql = require('./../sql/index.js'); 4 | 5 | module.exports = { 6 | name: 'deveval', 7 | ping: true, 8 | description: 'This command will let you execute js code in the bot and make it return the result. Example: "bb deveval "lole ".repeat(10);', 9 | permission: 2000, 10 | cooldown: 3, //in seconds 11 | category: 'Dev command', 12 | opt_outable: false, 13 | showDelay: false, 14 | noBanphrase: false, 15 | // eslint-disable-next-line no-unused-vars 16 | execute: async (channel, user, input, perm, aliascommand) => { 17 | try { 18 | if (module.exports.permission > perm) { 19 | return; 20 | } 21 | input = input.splice(2); 22 | let msg = input.join(' '); 23 | const js = await eval(`(async () => { ${msg}; })()`); 24 | 25 | return await JSON.stringify(js); 26 | } catch (err) { 27 | console.log(err); 28 | return 'FeelsDankMan Error'; 29 | } 30 | } 31 | }; -------------------------------------------------------------------------------- /commands/customCommands/apollo.js: -------------------------------------------------------------------------------- 1 | const { got } = require('./../../got'); 2 | 3 | module.exports = { 4 | name: 'apollo', 5 | ping: true, 6 | description: 'The bot responds with a random picture of apollo', 7 | permission: 100, 8 | cooldown: 3, //in seconds 9 | category: 'channelSpecific command', 10 | opt_outable: false, 11 | showDelay: false, 12 | noBanphrase: true, 13 | channelSpecific: true, 14 | activeChannel: ['nymn'], 15 | // eslint-disable-next-line no-unused-vars 16 | execute: async (channel, user, input, perm, aliascommand) => { 17 | try { 18 | if (module.exports.permission > perm) { 19 | return; 20 | } 21 | const apolloRequest = await got('https://pastebin.com/raw/UXbPxmd5'); 22 | 23 | const json = JSON.parse(apolloRequest.body); 24 | 25 | console.log(json[0]); 26 | const image = json[~~(Math.random() * json.length - 1)].url; 27 | 28 | return `nymnAww ${image}`; 29 | } catch (err) { 30 | console.log(err); 31 | return 'FeelsDankMan Error'; 32 | } 33 | } 34 | }; -------------------------------------------------------------------------------- /commands/oldestmod.js: -------------------------------------------------------------------------------- 1 | const tools = require('../tools/tools.js'); 2 | 3 | module.exports = { 4 | name: 'oldestmod', 5 | ping: true, 6 | description: 'This command will give you the name if the oldest mod in a given channel', 7 | permission: 100, 8 | category: 'Info command', 9 | execute: async (channel, user, input, perm) => { 10 | try { 11 | if (module.exports.permission > perm) { 12 | return; 13 | } 14 | let realchannel = channel; 15 | if (input[2]) { 16 | realchannel = input[2]; 17 | } 18 | let mods = await tools.getMods(realchannel); 19 | 20 | let ms = new Date().getTime() - Date.parse(mods[0].grantedAt); 21 | return `The oldest M OMEGALUL D in #${tools.unpingUser(realchannel)} is ${mods[0].displayName}, they were added ${tools.humanizeDuration(ms)} ago.`; 22 | 23 | } catch (err) { 24 | console.log(err); 25 | if (err.name) { 26 | if (err.name === 'TimeoutError') { 27 | return `FeelsDankMan api error: ${err.name}`; 28 | } 29 | } 30 | return 'FeelsDankMan Error'; 31 | } 32 | } 33 | }; -------------------------------------------------------------------------------- /commands/oldestvip.js: -------------------------------------------------------------------------------- 1 | const tools = require('../tools/tools.js'); 2 | 3 | module.exports = { 4 | name: 'oldestvip', 5 | ping: true, 6 | description: 'This command will give you the name if the oldest vip in a given channel', 7 | permission: 100, 8 | category: 'Info command', 9 | execute: async (channel, user, input, perm) => { 10 | try { 11 | if (module.exports.permission > perm) { 12 | return; 13 | } 14 | let realchannel = channel; 15 | if (input[2]) { 16 | realchannel = input[2]; 17 | } 18 | let vips = await tools.getVips(realchannel); 19 | 20 | let ms = new Date().getTime() - Date.parse(vips[0].grantedAt); 21 | return `The oldest vip😬 in #${realchannel[0]}\u034f${realchannel.slice(1)} is ${vips[0].displayName}, they were added ${tools.humanizeDuration(ms)} ago.`; 22 | 23 | } catch (err) { 24 | console.log(err); 25 | if (err.name) { 26 | if (err.name === 'TimeoutError') { 27 | return `FeelsDankMan api error: ${err.name}`; 28 | } 29 | } 30 | return 'FeelsDankMan Error'; 31 | } 32 | } 33 | }; -------------------------------------------------------------------------------- /commands/randomask.js: -------------------------------------------------------------------------------- 1 | const sql = require('./../sql/index.js'); 2 | 3 | module.exports = { 4 | name: 'randomask', 5 | ping: true, 6 | description: 'Gives you an random ask response', 7 | permission: 100, 8 | cooldown: 3, //in seconds 9 | category: 'Random command', 10 | opt_outable: false, 11 | showDelay: false, 12 | noBanphrase: false, 13 | // eslint-disable-next-line no-unused-vars 14 | execute: async (channel, user, input, perm, aliascommand) => { 15 | try { 16 | if (module.exports.permission > perm) { 17 | return; 18 | } 19 | 20 | input.shift(); 21 | input.shift(); 22 | 23 | const msg = input.toString(); 24 | 25 | const prompts = await sql.Query(`SELECT Response FROM Ask WHERE Response LIKE "%${msg}%"`,); 26 | 27 | const prompt = prompts[~~(Math.random() * prompts.length - 1)]; 28 | 29 | if (!prompt.Response) { 30 | return `Could not find any ask with: ${msg}`; 31 | } 32 | 33 | return prompt.Response; 34 | } catch (err) { 35 | console.log(err); 36 | return 'FeelsDankMan Error'; 37 | } 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /commands/latestmod.js: -------------------------------------------------------------------------------- 1 | const tools = require('../tools/tools.js'); 2 | 3 | module.exports = { 4 | name: 'latestmod', 5 | ping: true, 6 | description: 'This command will give you the name if the newest mod in a given channel', 7 | permission: 100, 8 | category: 'Info command', 9 | noBanphrase: true, 10 | execute: async (channel, user, input, perm) => { 11 | try { 12 | if (module.exports.permission > perm) { 13 | return; 14 | } 15 | let realchannel = channel; 16 | if (input[2]) { 17 | realchannel = input[2]; 18 | } 19 | let mods = await tools.getMods(realchannel); 20 | let ms = new Date().getTime() - Date.parse(mods[mods.length - 1].grantedAt); 21 | return `The newest M OMEGALUL D in #${realchannel[0]}\u034f${realchannel.slice(1)} is ${mods[mods.length - 1].displayName}, they were added ${tools.humanizeDuration(ms)} ago.`; 22 | 23 | } catch (err) { 24 | console.log(err); 25 | if (err.name) { 26 | if (err.name === 'TimeoutError') { 27 | return `FeelsDankMan api error: ${err.name}`; 28 | } 29 | } 30 | return 'FeelsDankMan Error'; 31 | } 32 | } 33 | }; -------------------------------------------------------------------------------- /commands/latestvip.js: -------------------------------------------------------------------------------- 1 | const tools = require('../tools/tools.js'); 2 | 3 | module.exports = { 4 | name: 'latestvip', 5 | ping: true, 6 | description: 'This command will give you the name if the newest vip in a given channel', 7 | permission: 100, 8 | category: 'Info command', 9 | noBanphrase: true, 10 | execute: async (channel, user, input, perm) => { 11 | try { 12 | if (module.exports.permission > perm) { 13 | return; 14 | } 15 | let realchannel = channel; 16 | if (input[2]) { 17 | realchannel = input[2]; 18 | } 19 | let vips = await tools.getVips(realchannel); 20 | 21 | let ms = new Date().getTime() - Date.parse(vips[vips.length - 1].grantedAt); 22 | return `The newest vip😬 in #${realchannel[0]}\u034f${realchannel.slice(1)} is ${vips[vips.length - 1].displayName}, they were added ${tools.humanizeDuration(ms)} ago.`; 23 | 24 | } catch (err) { 25 | console.log(err); 26 | if (err.name) { 27 | if (err.name === 'TimeoutError') { 28 | return `FeelsDankMan api error: ${err.name}`; 29 | } 30 | } 31 | return 'FeelsDankMan Error'; 32 | } 33 | } 34 | }; -------------------------------------------------------------------------------- /commands/reddit.js: -------------------------------------------------------------------------------- 1 | const { got } = require('./../got'); 2 | /* lists top 3 posts from NymN reddit */ 3 | module.exports = { 4 | name: 'reddit', 5 | ping: true, 6 | description: 'This command will list the top 3 posts on the NymN subreddit.', 7 | permission: 100, 8 | category: 'Random command', 9 | execute: async (channel, user, input, perm) => { 10 | try { 11 | if (module.exports.permission > perm) { 12 | return; 13 | } 14 | 15 | const response = await got('https://www.reddit.com/r/RedditAndChill/.json').json(); 16 | const posts = response.data.children; 17 | 18 | let top3 = []; 19 | let count = 0; 20 | for (let i = 0; i < posts.length; i++) { 21 | if (!posts[i].data.stickied && count < 3) { 22 | top3.push(`${count+1}. ${posts[i].data.title} - https://redd.it/${posts[i].data.id} by ${posts[i].data.author}`); 23 | count++; 24 | } 25 | } 26 | 27 | return `Top 3 posts in redditandchill: ${top3.join(' | ')}`; 28 | } catch (err) { 29 | console.log(err); 30 | return 'FeelsDankMan Error'; 31 | } 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /commands/code.js: -------------------------------------------------------------------------------- 1 | const sql = require('./../sql/index.js'); 2 | const tools = require('../tools/tools.js'); 3 | 4 | module.exports = { 5 | name: 'code', 6 | ping: true, 7 | description: 'Gives the user a link to the source code of a given command', 8 | permission: 100, 9 | category: 'Info command', 10 | execute: async (channel, user, input, perm) => { 11 | try { 12 | if (module.exports.permission > perm) { 13 | return; 14 | } 15 | 16 | if (!input[2]) { 17 | return 'No command specified - https://github.com/hotbear1110/botbear/blob/main/commands/'; 18 | } 19 | 20 | 21 | let commandList = await sql.Query(` 22 | SELECT * 23 | FROM Commands`); 24 | 25 | input.splice(1,1); 26 | input = await tools.Alias(input.join(' ')); 27 | 28 | if (!commandList.filter(x => x.Name === input[1]).length) { 29 | return `${input[1]} is not a command! Do: "bb commands" to see a list of available commands`; 30 | } 31 | 32 | return `https://github.com/hotbear1110/botbear/blob/main/commands/${input[1]}.js`; 33 | } catch (err) { 34 | console.log(err); 35 | return 'FeelsDankMan Error'; 36 | } 37 | } 38 | }; -------------------------------------------------------------------------------- /commands/vipcount.js: -------------------------------------------------------------------------------- 1 | const { got } = require('./../got'); 2 | 3 | module.exports = { 4 | name: 'vipcount', 5 | ping: true, 6 | description: 'This command will give you the number of channels a given user is a vip in. Example: "bb vipcount NymN"', 7 | permission: 100, 8 | category: 'Info command', 9 | noBanphrase: true, 10 | execute: async (channel, user, input, perm) => { 11 | try { 12 | if (module.exports.permission > perm) { 13 | return; 14 | } 15 | let username = user.username; 16 | if (input[2]) { 17 | if (input[2].startsWith('@')) { 18 | input[2] = input[2].substring(1); 19 | } 20 | username = input[2]; 21 | } 22 | let vipcount = await got(`https://api.twitchdatabase.com/roles/${username}`).json(); 23 | const isvip = vipcount.vipIn['total']; 24 | if (isvip === 0) { 25 | return 'That user is not a vip in any channel :)'; 26 | } 27 | return `That user is a vip😬 in ${isvip} channel('s)`; 28 | } catch (err) { 29 | console.log(err); 30 | if (err.name) { 31 | if (err.name === 'TimeoutError') { 32 | return `FeelsDankMan api error: ${err.name}`; 33 | } 34 | } 35 | return 'FeelsDankMan Error'; 36 | } 37 | } 38 | }; -------------------------------------------------------------------------------- /commands/followcount.js: -------------------------------------------------------------------------------- 1 | const { got } = require('./../got'); 2 | 3 | module.exports = { 4 | name: 'followcount', 5 | ping: true, 6 | description: 'This command will give you the amount of followers a specified channel has. Example: "bb followcount NymN"', 7 | permission: 100, 8 | category: 'Info command', 9 | noBanphrase: true, 10 | execute: async (channel, user, input, perm) => { 11 | try { 12 | if (module.exports.permission > perm) { 13 | return; 14 | } 15 | let realchannel = channel; 16 | if (input[2]) { 17 | if (input[2].startsWith('@')) { 18 | input[2] = input[2].substring(1); 19 | } 20 | realchannel = input[2]; 21 | } 22 | 23 | const followcount = await got(`https://decapi.me/twitch/followcount/${realchannel}`).json(); 24 | 25 | if (followcount === 0) { 26 | return `Could not find the channel ${realchannel}`; 27 | } 28 | 29 | return `#${realchannel} has ${followcount} followers!`; 30 | 31 | } catch (err) { 32 | console.log(err); 33 | if (err.name) { 34 | if (err.name === 'TimeoutError') { 35 | return `FeelsDankMan api error: ${err.name}`; 36 | } 37 | } 38 | return 'FeelsDankMan Error'; 39 | } 40 | } 41 | }; -------------------------------------------------------------------------------- /commands/modcount.js: -------------------------------------------------------------------------------- 1 | const { got } = require('./../got'); 2 | 3 | module.exports = { 4 | name: 'modcount', 5 | ping: true, 6 | description: 'This command will give you the number of channels a given user is a mod in. Example: "bb modcount NymN"', 7 | permission: 100, 8 | category: 'Info command', 9 | noBanphrase: true, 10 | execute: async (channel, user, input, perm) => { 11 | try { 12 | if (module.exports.permission > perm) { 13 | return; 14 | } 15 | let username = user.username; 16 | if (input[2]) { 17 | if (input[2].startsWith('@')) { 18 | input[2] = input[2].substring(1); 19 | } 20 | username = input[2]; 21 | } 22 | let modcount = await got(`https://modlookup.3v.fi/api/user-totals/${username}`).json(); 23 | const ismod = modcount['total']; 24 | if (ismod === 0) { 25 | return 'That user is not a mod in any channel :)'; 26 | } 27 | return `That user is a M OMEGALUL D in ${ismod} channel('s)`; 28 | } catch (err) { 29 | console.log(err); 30 | if (err.name) { 31 | if (err.name === 'TimeoutError') { 32 | return `FeelsDankMan api error: ${err.name}`; 33 | } 34 | } 35 | return 'FeelsDankMan Error'; 36 | } 37 | } 38 | }; -------------------------------------------------------------------------------- /commands/pyramid.js: -------------------------------------------------------------------------------- 1 | const cc = require('../bot.js').cc; 2 | 3 | module.exports = { 4 | name: 'pyramid', 5 | ping: true, 6 | description: 'spam pyramids', 7 | permission: 2000, 8 | category: 'Dev command', 9 | execute: async (channel, user, input, perm) => { 10 | try { 11 | if (module.exports.permission > perm) { 12 | return; 13 | } 14 | let message = input.slice(); 15 | message.shift(); 16 | message.shift(); 17 | message.shift(); 18 | 19 | message = message.join(' '); 20 | let width = input[2]; 21 | let length = input[2]*2; 22 | let peakLength = message.repeat(length); 23 | if (peakLength.length > 500) { 24 | return 'nymnDank message is too long'; 25 | } 26 | for (let i = 0; i < length; i++) { 27 | let msgLength = (i < width) ? i+1 : length - (i+1); 28 | let tempMsg = Array.from({length: msgLength}).fill(message).join(' '); 29 | console.log(tempMsg); 30 | cc.say(channel, tempMsg); 31 | } 32 | 33 | return; 34 | } catch (err) { 35 | console.log(err); 36 | return 'FeelsDankMan Error'; 37 | } 38 | } 39 | }; -------------------------------------------------------------------------------- /tools/markovLogger.js: -------------------------------------------------------------------------------- 1 | 2 | const redis = require('redis'); 3 | const cc = require('../bot.js').cc; 4 | 5 | const redisC = redis.createClient({ url: process.env.REDIS_ADDRESS, legacyMode: true }); 6 | 7 | redisC.on('connect', function () { 8 | console.log('Connected!'); 9 | }); 10 | 11 | redisC.connect(); 12 | 13 | // Register our event handlers (defined below) 14 | cc.on('message', onMessageHandler); 15 | 16 | // Called every time a message comes in 17 | function onMessageHandler(target, context, msg, self) { 18 | if (self || context.username.match(/(supibot|thepositivebot|ksyncbot|dontaddthisbot|streamelements|botnextdoor)/gi)) { 19 | return; 20 | } 21 | redisC.get(`Markov:${target.substring(1)}`, function (err, reply) { 22 | 23 | if (reply && reply !== undefined) { 24 | reply = JSON.parse(reply); 25 | 26 | reply.push(msg); 27 | if (reply.length > 1000) { 28 | reply.splice(0, reply.length - 999); 29 | } 30 | let send = JSON.stringify(reply); 31 | 32 | redisC.set(`Markov:${target.substring(1)}`, send); 33 | } else { 34 | let send = JSON.stringify([msg]); 35 | redisC.set(`Markov:${target.substring(1)}`, send); 36 | } 37 | }); 38 | } 39 | 40 | 41 | module.exports = { redisC }; 42 | -------------------------------------------------------------------------------- /web/routes/music.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const router = require('express').Router(); 3 | const sql = require('./../../sql/index.js'); 4 | const { got } = require('./../../got'); 5 | 6 | module.exports = (function () { 7 | router.get('/', async (req, res) => { 8 | let cookies = req.cookies || ''; 9 | let query = req.query; 10 | 11 | let cookieToken = cookies.token; 12 | 13 | let userInfo = await sql.Query('SELECT * FROM Spotify WHERE cookieToken = ?', [cookieToken]); 14 | 15 | const hasToken = userInfo.length; 16 | 17 | if (hasToken) { 18 | userInfo = userInfo[0]; 19 | try { 20 | const [getImage] = await got(`https://api.ivr.fi/v2/twitch/user?id=${userInfo.uid}`).json(); 21 | userInfo['logo'] = getImage.logo; 22 | } catch (err) { 23 | userInfo['logo'] = 'https://static-cdn.jtvnw.net/user-default-pictures-uv/13e5fa74-defa-11e9-809c-784f43822e80-profile_image-600x600.png'; //Fallback image 24 | } 25 | 26 | } 27 | 28 | res.render('music', { cookieToken: hasToken, userInfo: userInfo, query: query }); 29 | }); 30 | 31 | return router; 32 | })(); -------------------------------------------------------------------------------- /commands/delay.js: -------------------------------------------------------------------------------- 1 | const requireDir = require('require-dir'); 2 | const tools = require('../tools/tools.js'); 3 | 4 | module.exports = { 5 | name: 'delay', 6 | ping: true, 7 | description: 'This command will add the bot´s internal delay, to the end of the response. Example: "bb delay randomline NymN"', 8 | permission: 2000, 9 | category: 'Dev command', 10 | showDelay: true, 11 | execute: async (channel, user, input, perm) => { 12 | try { 13 | if (module.exports.permission > perm) { 14 | return; 15 | } 16 | input.splice(1, 1); 17 | 18 | let msg = input.join(' '); 19 | input = await tools.Alias(msg); 20 | let realcommand = input[1]; 21 | 22 | if (realcommand === 'ping' || realcommand === 'delay') { 23 | return; 24 | } 25 | 26 | const commands = requireDir('../commands'); 27 | 28 | if (typeof commands[realcommand] === 'undefined') { 29 | console.log('undefined'); 30 | return; 31 | } 32 | 33 | let result = await commands[realcommand].execute(channel, user, input, perm); 34 | 35 | 36 | if (!result) { 37 | return; 38 | } 39 | 40 | return result; 41 | 42 | } catch (err) { 43 | console.log(err); 44 | return 'FeelsDankMan Error'; 45 | } 46 | } 47 | }; -------------------------------------------------------------------------------- /commands/offlineonly.js: -------------------------------------------------------------------------------- 1 | const tools = require('../tools/tools.js'); 2 | const sql = require('./../sql/index.js'); 3 | 4 | module.exports = { 5 | name: 'offlineonly', 6 | ping: true, 7 | description: 'This command will let you change the bot to/from offline only mode (toggle), live/game/title notify will still work! (This is a mod command)', 8 | permission: 100, 9 | category: 'Core command', 10 | noBanphrase: true, 11 | execute: async (channel, user, input, perm) => { 12 | try { 13 | if (module.exports.permission > perm) { 14 | return; 15 | } 16 | if (!await tools.isMod(user, channel) && perm < 2000) { 17 | return; 18 | } 19 | const offlinemode = await sql.Query(` 20 | SELECT offlineonly 21 | FROM Streamers 22 | WHERE username=?`, 23 | [channel]); 24 | 25 | let mode = Math.abs(offlinemode[0].offlineonly - 1); 26 | 27 | await sql.Query('UPDATE Streamers SET offlineonly=? WHERE username=?', [mode, channel]); 28 | 29 | if (mode === 1) { 30 | return 'Offline only mode is now on!'; 31 | } 32 | 33 | return 'Offline only mode is now off!'; 34 | 35 | } catch (err) { 36 | console.log(err); 37 | return 'FeelsDankMan Error'; 38 | } 39 | } 40 | }; -------------------------------------------------------------------------------- /web/routes/twitch/auth.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const querystring = require('querystring'); 3 | 4 | module.exports = (function () { 5 | const router = require('express').Router(); 6 | 7 | const client_id = process.env.TWITCH_CLIENTID; 8 | const redirect_uri = 'https://hotbear.org/twitch/auth/callback'; 9 | 10 | /* /Login */ 11 | router.get('/', async (req, res) => { 12 | 13 | let state = generateRandomString(16); 14 | let scope = 'moderator:manage:banned_users chat:edit chat:read'; 15 | 16 | res.redirect('https://id.twitch.tv/oauth2/authorize?' + 17 | querystring.stringify({ 18 | response_type: 'code', 19 | client_id: client_id, 20 | scope: scope, 21 | redirect_uri: redirect_uri, 22 | state: state 23 | })); 24 | }); 25 | 26 | return router; 27 | })(); 28 | 29 | const generateRandomString = function(length) { 30 | let text = ''; 31 | let possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 32 | 33 | for (let i = 0; i < length; i++) { 34 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 35 | } 36 | return text; 37 | }; -------------------------------------------------------------------------------- /web/routes/spotify/login.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const querystring = require('querystring'); 3 | 4 | module.exports = (function () { 5 | const router = require('express').Router(); 6 | 7 | const client_id = process.env.SPOTIFY_CLIENT_ID; 8 | const redirect_uri = 'https://hotbear.org/spotify/callback'; 9 | 10 | /* /Login */ 11 | router.get('/', async (req, res) => { 12 | let state = generateRandomString(16); 13 | let scope = 'user-read-playback-state user-read-private user-modify-playback-state streaming user-read-email'; 14 | 15 | res.redirect('https://accounts.spotify.com/authorize?' + 16 | querystring.stringify({ 17 | response_type: 'code', 18 | client_id: client_id, 19 | scope: scope, 20 | redirect_uri: redirect_uri, 21 | state: state 22 | })); 23 | }); 24 | 25 | return router; 26 | })(); 27 | 28 | const generateRandomString = function(length) { 29 | let text = ''; 30 | let possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 31 | 32 | for (let i = 0; i < length; i++) { 33 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 34 | } 35 | return text; 36 | }; 37 | -------------------------------------------------------------------------------- /web/views/resolved.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Resolved 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 |

Botbear Spotify integration

18 | Spotify Logo 19 |
20 | <%if (hasState) { %> 21 |
22 |

You have successfully linked your spotify with botbear!

23 |

You can now use commands such as
`bb spotify` or `bb grab @Username`

24 |

This page can now be closed.

25 | LETS PEPE 26 |
27 | <% } else { %> 28 |

You are not supposed to be here!

29 | <% } %> 30 |
31 |
32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /defaults/env.default.txt: -------------------------------------------------------------------------------- 1 | # Twitch Account Stuff 2 | TWITCH_USER=The bot's Twitch username 3 | TWITCH_PASSWORD=The bot's oauth 4 | TWITCH_UID=The bot's Twitch user-id 5 | TWITCH_PREFIX=The prefix you want for the bot 6 | 7 | TWITCH_OWNERUID=Your own Twitch user-id 8 | TWITCH_OWNERNAME=Your own Twitch username 9 | 10 | TWITCH_CLIENTID=Your bot's application client-id 11 | TWITCH_AUTH=Your bot's bearer auth key 12 | TWITCH_SECRET=Your bot's application secret 13 | 14 | BOT_VERIFIED=Boolean - If your bot is verified or not 15 | 16 | # Optional 17 | THREELETTERAPI_CLIENTID=Dank threeletterapi client-id 18 | 19 | # Database stuff 20 | DB_HOST=Your database ip 21 | DB_USER=Your database user 22 | DB_PASSWORD=Your database user password 23 | DB_DATABASE=Your database name 24 | 25 | REDIS_ADDRESS=Your redis address - redis://localhost:6379/0 26 | 27 | # Supibot Stuff 28 | SUPI_USERID=Your supibot user-id 29 | SUPI_AUTH=Your supibot auth key 30 | 31 | # Random secret stuff for apis 32 | OPENAI_API_KEY=Your openAI api key 33 | 34 | AZTRO_API_KEY=Your aztro api key - https://docs.rapidapi.com/ 35 | 36 | APEX_API=Your apexlegends api key - apexlegendsapi.com 37 | 38 | NASA_API=Your nasa api key - https://api.nasa.gov/ 39 | 40 | SPOTIFY_CLIENT_ID=Your spotify app client id 41 | SPOTIFY_CLIENT_SECRET=Your spotify app client secret 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "botbear-2.0", 3 | "version": "1.0.0", 4 | "description": "Dank bot, idk", 5 | "main": "bot.js", 6 | "scripts": { 7 | "start": "node ./start.js", 8 | "web": "node ./web/app.js", 9 | "lint": "eslint . --ext .js" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/hotbear1110/botbear.git" 14 | }, 15 | "author": "", 16 | "license": "ISC", 17 | "bugs": { 18 | "url": "https://github.com/hotbear1110/botbear/issues" 19 | }, 20 | "homepage": "https://github.com/hotbear1110/botbear#readme", 21 | "dependencies": { 22 | "cookie-parser": "^1.4.7", 23 | "date-and-time": "^2.0.0", 24 | "dotenv": "^10.0.0", 25 | "ejs": "^3.1.10", 26 | "express": "^4.21.1", 27 | "express-favicon": "^2.0.1", 28 | "got": "^12.1.0", 29 | "hastebin": "^0.2.1", 30 | "humanize-duration": "^3.27.0", 31 | "markov-strings": "^3.0.1", 32 | "mysql2": "^3.9.8", 33 | "redis": "^4.2.0", 34 | "require-dir": "^1.2.0", 35 | "tmi.js": "^1.8.5", 36 | "underscore": "^1.13.1", 37 | "vm2": "^3.9.18", 38 | "xml-js": "^1.6.11", 39 | "youtube-search-api": "^1.1.1" 40 | }, 41 | "types": "./@types/index.d.ts", 42 | "devDependencies": { 43 | "@types/express": "^4.17.13", 44 | "@types/tmi.js": "^1.8.1", 45 | "eslint": "^8.19.0" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /commands/say.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const regex = require('../tools/regex.js'); 3 | 4 | module.exports = { 5 | name: 'say', 6 | ping: false, 7 | description: 'This command will let you make the bot say anything in chat. (The message gets checked for massping, banphrases etc.). Example: "bb say NymN is soy lole"', 8 | permission: 100, 9 | category: 'Random command', 10 | execute: async (channel, user, input, perm) => { 11 | try { 12 | if (module.exports.permission > perm) { 13 | return; 14 | } 15 | input = input.splice(2); 16 | let msg = input.join(' '); 17 | 18 | msg.replace(regex.invisChar, ''); 19 | 20 | if (perm < 2000 && msg.match(/[&|$|/|.|?|-]|\bkb\b|^\bmelon\b|^\bkloy\b/gm)) { // ignores &, $, kb, /, ., ?, !, - bot prefixes (. and / are twitch reserved prefixes) 21 | msg = '. ' + msg.charAt(0) + '\u034f' + msg.substring(1); 22 | } 23 | if (msg.match(/^!/gm)) { 24 | msg = '❗ ' + msg.replace('!', ''); 25 | } 26 | 27 | if (perm < 2000 && msg.match(/(\.|\/)color/gm)) { 28 | return 'cmonBruh don\'t change my color'; 29 | } 30 | const banRegex = new RegExp(`[./](ban|timeout|unmod) ${process.env.TWITCH_OWNERNAME}`,'gi'); 31 | if (msg.match(banRegex)) { 32 | return `nymnWeird too far @${user.username}`; 33 | } 34 | 35 | return msg; 36 | } catch (err) { 37 | console.log(err); 38 | return 'FeelsDankMan Error'; 39 | } 40 | } 41 | }; -------------------------------------------------------------------------------- /commands/founders.js: -------------------------------------------------------------------------------- 1 | const { got } = require('./../got'); 2 | const tools = require('../tools/tools.js'); 3 | 4 | module.exports = { 5 | name: 'founders', 6 | ping: true, 7 | description: 'Gives you a list of founders from a given channel. * means that the user is currently subbed', 8 | permission: 100, 9 | cooldown: 3, //in seconds 10 | category: 'Info command', 11 | // eslint-disable-next-line no-unused-vars 12 | execute: async (channel, user, input, perm, aliascommand) => { 13 | try { 14 | if (module.exports.permission > perm) { 15 | return; 16 | } 17 | let realchannel = channel; 18 | if (input[2]) { 19 | realchannel = input[2].toLowerCase(); 20 | } 21 | let founders = await got(`https://api.ivr.fi/v2/twitch/founders/${realchannel}`).json(); 22 | founders = founders['founders']; 23 | 24 | if (!founders.length) { 25 | return 'There are no users with points in this channel'; 26 | } 27 | founders = founders.map(x => `${tools.unpingUser(x.login)}${(x.isSubscribed) ? '*' : ''}`) 28 | .toString() 29 | .replaceAll(',', ', '); 30 | 31 | return `Founders in #${realchannel}: ${founders}`; 32 | } catch (err) { 33 | console.log(err); 34 | if (err.response.statusCode === 404) { 35 | return JSON.parse(err.response.body).error.message; 36 | } 37 | return 'FeelsDankMan Error'; 38 | } 39 | } 40 | }; -------------------------------------------------------------------------------- /commands/gametime.js: -------------------------------------------------------------------------------- 1 | const tools = require('../tools/tools.js'); 2 | const sql = require('./../sql/index.js'); 3 | 4 | module.exports = { 5 | name: 'gametime', 6 | ping: true, 7 | description: 'This command gives you the amount a time the streamer has been in the current category', 8 | permission: 100, 9 | category: 'Info command', 10 | execute: async (channel, user, input, perm) => { 11 | try { 12 | if (module.exports.permission > perm) { 13 | return; 14 | } 15 | let realchannel = channel; 16 | 17 | if (input[2]) { 18 | if (input[2].startsWith('@')) { 19 | input[2] = input[2].substring(1); 20 | } 21 | realchannel = input[2]; 22 | } 23 | const gameTimedata = await sql.Query('SELECT game, game_time FROM Streamers WHERE username=?', [realchannel]); 24 | if (!gameTimedata[0]) { 25 | return 'That streamer is not in my database'; 26 | } 27 | let game = gameTimedata[0].game; 28 | 29 | let oldgameTime = JSON.parse(gameTimedata[0].game_time); 30 | if (oldgameTime !== null && oldgameTime !== 2147483647) { 31 | 32 | const ms = new Date().getTime() - oldgameTime; 33 | 34 | return `#${realchannel[0]}\u034f${realchannel.slice(1)} has been in the category: (${game}), for ${tools.humanizeDuration(ms)}`; 35 | } 36 | return `#${realchannel}'s current game is: (${game})`; 37 | } catch (err) { 38 | console.log(err); 39 | return `FeelsDankMan Sql error: ${err.sqlMessage}`; 40 | } 41 | } 42 | }; -------------------------------------------------------------------------------- /commands/profilepic.js: -------------------------------------------------------------------------------- 1 | const { got } = require('./../got'); 2 | 3 | module.exports = { 4 | name: 'profilepic', 5 | ping: true, 6 | description: 'Get the profile pic of a twitch user', 7 | permission: 100, 8 | cooldown: 3, //in seconds 9 | category: 'Random command', 10 | opt_outable: false, 11 | showDelay: false, 12 | noBanphrase: true, 13 | channelSpecific: false, 14 | activeChannel: '', 15 | // eslint-disable-next-line no-unused-vars 16 | execute: async (channel, user, input, perm, aliascommand) => { 17 | try { 18 | if (module.exports.permission > perm) { 19 | return; 20 | } 21 | 22 | let username = input[2] ?? user.username; 23 | 24 | const profile = await got('https://api.twitch.tv/helix/users', { 25 | headers: { 26 | 'client-id': process.env.TWITCH_CLIENTID, 27 | 'Authorization': process.env.TWITCH_AUTH 28 | }, 29 | searchParams: { 30 | 'login': username 31 | }, 32 | throwHttpErrors: false 33 | }).json(); 34 | 35 | if (profile.error || !profile.data.length || !profile.data) { 36 | return "No user with that username found"; 37 | } 38 | 39 | return profile.data[0].profile_image_url.replace('300x300', '600x600'); 40 | } catch (err) { 41 | console.log(err); 42 | return 'FeelsDankMan Error'; 43 | } 44 | } 45 | }; -------------------------------------------------------------------------------- /commands/update.js: -------------------------------------------------------------------------------- 1 | let messageHandler = require('../tools/messageHandler.js').messageHandler; 2 | const { promisify } = require('node:util'); 3 | const { exec } = require('node:child_process'); 4 | 5 | module.exports = { 6 | name: 'update', 7 | ping: false, 8 | description: 'updates botbear, git pull', 9 | permission: 2000, 10 | cooldown: 3, //in seconds 11 | category: 'Dev command', 12 | // eslint-disable-next-line no-unused-vars 13 | execute: async (channel, user, input, perm, aliascommand) => { 14 | try { 15 | if (module.exports.permission > perm) { 16 | return; 17 | } 18 | const shell = promisify(exec); 19 | 20 | await new messageHandler(channel, 'ppCircle git pull...', true).newMessage(); 21 | 22 | const response = await shell('sudo git pull'); 23 | 24 | await response; 25 | 26 | if (response.stdout === 'Already up to date.\n') { 27 | return 'FeelsDankMan Already up to date.'; 28 | } 29 | else { 30 | await Promise.all([await new messageHandler(channel, 'Updated.... restarting bot ppCircle', true).newMessage()]); 31 | shell('sudo systemctl restart botbear-site'); 32 | 33 | setTimeout(() => { 34 | shell('sudo systemctl restart botbear2'); 35 | }, 2000); 36 | 37 | return; 38 | } 39 | } catch (err) { 40 | console.log(err); 41 | return 'FeelsDankMan Error'; 42 | } 43 | } 44 | }; -------------------------------------------------------------------------------- /commands/nasa.js: -------------------------------------------------------------------------------- 1 | const { got } = require('./../got'); 2 | require('dotenv').config(); 3 | 4 | //Made by: @sougataghar477 5 | module.exports = { 6 | name: 'nasa', 7 | ping: true, 8 | description: 'This command will give random image from NASA', 9 | permission: 100, 10 | category: 'Random command', 11 | execute: async (channel, user, input, perm) => { 12 | try { 13 | if (module.exports.permission > perm) { 14 | return; 15 | } 16 | const apiKey = process.env.NASA_API; 17 | if (!apiKey) { 18 | return 'This command is currently not available'; 19 | } 20 | 21 | let url = `https://api.nasa.gov/planetary/apod?api_key=${process.env.NASA_API}`; 22 | switch (input[2]) { 23 | case 'random': { 24 | let nasa = await got(`${url}&count=1`,).json(); 25 | 26 | return `Random Astronomy Picture Of The Day (${nasa[0].date}): ${nasa[0].title} - ${nasa[0].hdurl ?? nasa[0].url}`; 27 | } 28 | default: { 29 | let nasa = await got(url).json(); 30 | 31 | return `Astronomy Picture Of The Day: ${nasa.title} - ${nasa.hdurl ?? nasa.url}`; 32 | } 33 | } 34 | 35 | } catch (err) { 36 | console.log(err); 37 | if (err.name) { 38 | if (err.name === 'TimeoutError') { 39 | return `FeelsDankMan api error: ${err.name}`; 40 | } 41 | } 42 | return 'FeelsDankMan Error'; 43 | } 44 | }, 45 | }; 46 | -------------------------------------------------------------------------------- /commands/follows.js: -------------------------------------------------------------------------------- 1 | const { got } = require('./../got'); 2 | 3 | module.exports = { 4 | name: 'follows', 5 | ping: true, 6 | description: 'This command will show you how many people you follow', 7 | permission: 100, 8 | category: 'Info command', 9 | noBanphrase: true, 10 | execute: async (channel, user, input, perm) => { 11 | try { 12 | if (module.exports.permission > perm) { 13 | return; 14 | } 15 | let realuid = user['user-id']; 16 | let realuser = user.username; 17 | 18 | if (input[2]) { 19 | try { 20 | const getuid = await got(`https://api.ivr.fi/v2/twitch/user/${input[2]}`).json(); 21 | realuid = getuid.id; 22 | realuser = input[2]; 23 | } 24 | catch (err) { 25 | return `User "${input[2]}" was not found`; 26 | } 27 | } 28 | const follows = await got(`https://api.twitch.tv/helix/users/follows?from_id=${realuid}`, { 29 | headers: { 30 | 'client-id': process.env.TWITCH_CLIENTID, 31 | 'Authorization': process.env.TWITCH_AUTH 32 | } 33 | }).json(); 34 | 35 | let followscount = follows.total; 36 | if (input[2]) { 37 | return `${realuser} is following ${followscount} users`; 38 | } 39 | return `You are following ${followscount} users`; 40 | 41 | } catch (err) { 42 | console.log(err); 43 | if (err.name) { 44 | if (err.name === 'TimeoutError') { 45 | return `FeelsDankMan api error: ${err.name}`; 46 | } 47 | } 48 | return 'FeelsDankMan Error'; 49 | } 50 | } 51 | }; -------------------------------------------------------------------------------- /commands/chatters.js: -------------------------------------------------------------------------------- 1 | const { got } = require('./../got'); 2 | const redis = require('./../tools/redis.js'); 3 | 4 | const CACHE_TIME = 5 * 60; 5 | 6 | module.exports = { 7 | name: 'chatters', 8 | ping: true, 9 | description: 'This command will give you the number of users, that are currently in the chat', 10 | permission: 100, 11 | category: 'Info command', 12 | noBanphrase: true, 13 | execute: async (channel, user, input, perm) => { 14 | try { 15 | if (module.exports.permission > perm) { 16 | return; 17 | } 18 | const realchannel = input[2] ?? channel; 19 | const cache = await redis.Get().Get(`${realchannel}:chatters_count`); 20 | if (cache) { 21 | return (realchannel === channel) ? `There are ${cache} users in chat rn :O` : `There are ${cache} users in that chat rn :O`; 22 | } 23 | 24 | const { chatter_count } = await got(`https://tmi.twitch.tv/group/user/${realchannel}/chatters`).json(); 25 | const b = await redis.Get().Set(`${realchannel}:chatters_count`, chatter_count); 26 | b(CACHE_TIME); 27 | 28 | return (realchannel === channel) ? `There are ${chatter_count} users in chat rn :O` : `There are ${chatter_count} users in that chat rn :O`; 29 | } catch (err) { 30 | console.log(err); 31 | if (err.name) { 32 | if (err.name === 'TimeoutError') { 33 | return `FeelsDankMan api error: ${err.name}`; 34 | } 35 | } 36 | return `FeelsDankMan Error: ${err.response.data.error}`; 37 | } 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /commands/apexmap.js: -------------------------------------------------------------------------------- 1 | const { got } = require('./../got'); 2 | const tools = require('../tools/tools.js'); 3 | 4 | //Made by: @sougataghar477 5 | module.exports = { 6 | name: 'apexmap', 7 | ping: true, 8 | description: 'This command will give you the current map in apex pubs', 9 | permission: 100, 10 | category: 'Random command', 11 | noBanphrase: true, 12 | execute: async (channel, user, input, perm) => { 13 | try { 14 | if (module.exports.permission > perm) { 15 | return; 16 | } 17 | const authKey = process.env.APEX_API; 18 | if (!authKey) { 19 | return 'This command is not available at the moment'; 20 | } 21 | 22 | let map = JSON.parse((await got( 23 | `https://api.mozambiquehe.re/maprotation?version=2&auth=${process.env.APEX_API}`, 24 | )).body); 25 | 26 | let { map: currentMap, end } = map.battle_royale.current; 27 | 28 | let { map: nextMap, DurationInMinutes } = map.battle_royale.next; 29 | 30 | let remainingTime = (end * 1000) - (Date.now()); 31 | return `Current map is ${currentMap} which lasts for ${tools.humanizeDuration(remainingTime)}. Next map is ${nextMap} which lasts for ${DurationInMinutes} minutes.`; 32 | } catch (err) { 33 | console.log(err); 34 | if (err.name) { 35 | if (err.name === 'TimeoutError') { 36 | return `FeelsDankMan api error: ${err.name}`; 37 | } 38 | } 39 | return 'FeelsDankMan Error'; 40 | } 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /commands/emotecount.js: -------------------------------------------------------------------------------- 1 | const sql = require('./../sql/index.js'); 2 | 3 | module.exports = { 4 | name: 'emotecount', 5 | ping: true, 6 | description: 'This command will give you the number of 3rd party emotes, that are currently activated in the chat.', 7 | permission: 100, 8 | category: 'Info command', 9 | noBanphrase: true, 10 | execute: async (channel, user, input, perm) => { 11 | try { 12 | if (module.exports.permission > perm) { 13 | return; 14 | } 15 | 16 | const emoteChannel = input[2] ?? channel; 17 | 18 | const streamer = await sql.Query(`SELECT emote_list FROM Streamers WHERE username="${emoteChannel}"`); 19 | 20 | if (!streamer.length) { 21 | return 'I don\'t have that streamer in my database' 22 | } 23 | 24 | let emotes = JSON.parse(streamer[0].emote_list); 25 | 26 | 27 | let seventvcount = emotes.filter(emote => emote.includes('["7tv"]') || emote.includes('7tv') || emote.includes('7tv_ZERO_WIDTH')); 28 | let bttvccount = emotes.filter(emote => emote.includes('bttv')); 29 | let ffzcount = emotes.filter(emote => emote.includes('ffz')); 30 | 31 | if (!emotes.length) { 32 | return `there are no 3rd party emotes in #${emoteChannel}.`; 33 | } 34 | else { 35 | return `There are ${emotes.length} 3rd party emotes in #${emoteChannel} | BTTV: ${bttvccount.length} FFZ: ${ffzcount.length} 7TV: ${seventvcount.length}`; 36 | } 37 | } catch (err) { 38 | console.log(err); 39 | return `FeelsDankMan Sql error: ${err.sqlMessage}`; 40 | } 41 | } 42 | }; -------------------------------------------------------------------------------- /web/views/suggestions.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Suggestions 9 | 10 | 11 | 12 | 13 | <%- include("partials/header") %> 14 | 15 |
16 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 26 | <% suggestions.forEach(function(suggestion) { %> 27 | 28 | 31 | 34 | 37 | 40 | 43 | 44 | <% }) %> 45 |
ID: 20 | User: 21 | Suggestion:Status:Description:
29 | <%- suggestion.ID %> 30 | 32 | <%- suggestion.User %> 33 | 35 | <%- suggestion.Suggestion%> 36 | 38 | <%- suggestion.Status%> 39 | 41 | <%- suggestion.Description%> 42 |
46 |
47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /tools/whisperHandler.js: -------------------------------------------------------------------------------- 1 | let talkedRecently = {}; 2 | const tools = require('./tools.js'); 3 | 4 | exports.whisperHandler = class Cooldown { 5 | constructor(recipient, message) { 6 | this.sender = process.env.TWITCH_UID; 7 | this.recipient = recipient; 8 | this.message = message; 9 | this.noCD = 0; 10 | } 11 | 12 | async Cooldown() { 13 | let cooldown = 1250; 14 | if (talkedRecently[this.recipient]) { 15 | cooldown = 1250 * (talkedRecently[this.recipient].length); 16 | 17 | } 18 | return cooldown; 19 | } 20 | 21 | async newWhisper() { 22 | const cc = require('../bot.js').cc; 23 | 24 | if (talkedRecently[this.recipient]) { 25 | this.noCD = 0; 26 | let tempList = talkedRecently[this.recipient]; 27 | tempList.push(this.message); 28 | talkedRecently[this.recipient] = tempList; 29 | } else { 30 | await tools.sendWhisper(this.recipient, this.sender, this.message); 31 | this.noCD = 1; 32 | let tempList = []; 33 | tempList.push(this.message); 34 | talkedRecently[this.recipient] = tempList; 35 | } 36 | 37 | setTimeout(async () => { 38 | let tempList = talkedRecently[this.recipient]; 39 | if (this.noCD === 0) { 40 | await tools.sendWhisper(this.recipient, this.sender, this.message); 41 | } 42 | tempList.shift(); 43 | talkedRecently[this.recipient] = tempList; 44 | if (!talkedRecently[this.recipient].length) { 45 | delete talkedRecently[this.recipient]; 46 | this.noCD = 0; 47 | } 48 | 49 | }, await this.Cooldown()); 50 | return; 51 | } 52 | 53 | }; -------------------------------------------------------------------------------- /commands/accage.js: -------------------------------------------------------------------------------- 1 | const tools = require('../tools/tools.js'); 2 | const { got } = require('../got'); 3 | 4 | module.exports = { 5 | name: 'accage', 6 | ping: true, 7 | description: 'This command will tell you the specified users account age. Example: "bb accage NymN"', 8 | permission: 100, 9 | category: 'Info command', 10 | noBanphrase: true, 11 | execute: async (channel, user, input, perm) => { 12 | try { 13 | if (module.exports.permission > perm) { 14 | return; 15 | } 16 | let username = user.username; 17 | 18 | if (input[2]) { 19 | if (input[2].startsWith('@')) { 20 | input[2] = input[2].substring(1); 21 | } 22 | username = input[2]; 23 | } 24 | 25 | const twitchdata = await got(`https://api.ivr.fi/v2/twitch/user?login=${username}`).json(); 26 | 27 | const ms = new Date().getTime() - Date.parse(twitchdata[0].createdAt); 28 | 29 | return `Account is ${tools.humanizeDuration(ms)} old`; 30 | } catch (err) { 31 | console.error(err); 32 | if (err.name === 'TimeoutError') { 33 | return `FeelsDankMan api error: ${err.name}`; 34 | } else if (err.response && err.response.data) { 35 | return `FeelsDankMan Error: ${err.response.data.error}`; 36 | } else { 37 | return 'FeelsDankMan Error'; 38 | } 39 | } 40 | } 41 | }; -------------------------------------------------------------------------------- /commands/totalvods.js: -------------------------------------------------------------------------------- 1 | const { got } = require('./../got'); 2 | 3 | module.exports = { 4 | name: 'totalvods', 5 | ping: true, 6 | description: 'This command will give the total amount of available vods. I can only get the last 100 vods', 7 | permission: 100, 8 | category: 'Info command', 9 | execute: async (channel, user, input, perm) => { 10 | try { 11 | if (module.exports.permission > perm) { 12 | return; 13 | } 14 | let realchannel = channel; 15 | 16 | if (input[2]) { 17 | if (input[2].startsWith('@')) { 18 | input[2] = input[2].substring(1); 19 | } 20 | realchannel = input[2]; 21 | } 22 | 23 | const [userID] = await got(`https://api.ivr.fi/v2/twitch/user?login=${realchannel}`).json(); 24 | 25 | if (userID.status === 404) { 26 | return `Could not find user: "${realchannel}"`; 27 | } 28 | 29 | let vodList = await got(`https://api.twitch.tv/helix/videos?user_id=${userID.id}&type=archive&first=100`, { 30 | headers: { 31 | 'client-id': process.env.TWITCH_CLIENTID, 32 | 'Authorization': process.env.TWITCH_AUTH 33 | } 34 | }).json(); 35 | 36 | if (!vodList.data.length) { 37 | return 'That channel has no vods'; 38 | } else { 39 | return `This channel has ${vodList.data.length} vod(s)`; 40 | } 41 | } catch (err) { 42 | console.log(err); 43 | if (err.name) { 44 | if (err.name === 'TimeoutError') { 45 | return `FeelsDankMan api error: ${err.name}`; 46 | } 47 | } 48 | return `FeelsDankMan Error: ${err.response.data.error}`; 49 | } 50 | } 51 | }; -------------------------------------------------------------------------------- /commands/reconnect.js: -------------------------------------------------------------------------------- 1 | const cc = require('../bot.js').cc; 2 | const { messageHandler } = require('../tools/messageHandler.js'); 3 | const sql = require('./../sql/index.js'); 4 | const tools = require('../tools/tools.js'); 5 | 6 | module.exports = { 7 | name: 'reconnect', 8 | ping: true, 9 | description: 'Reconnects the bot to a given channel.', 10 | permission: 100, 11 | category: 'Dev command', 12 | execute: async (channel, user, input, perm) => { 13 | try { 14 | if (module.exports.permission > perm) { 15 | return; 16 | } 17 | let username = user.username; 18 | if (input[2]) { 19 | username = input[2]; 20 | if (username.startsWith('@')) { 21 | username = username.substring(1); 22 | } 23 | } 24 | 25 | let modresponse = await tools.isMod(user, username); 26 | 27 | if (!await modresponse && perm < 2000) { 28 | return 'You can only reconnect to your own chat or a chat you moderate'; 29 | } 30 | 31 | const isinDB = await sql.Query('SELECT username FROM Streamers WHERE username=?', [username]); 32 | if (!isinDB[0]) { 33 | return 'That streamer is not in my database'; 34 | } 35 | cc.part(username).catch((err) => { 36 | console.log(err); 37 | }); 38 | 39 | cc.join(username).catch((err) => { 40 | console.log(err); 41 | }); 42 | new messageHandler(`#${username}`, 'nymnDank reconnected to the chat!', true).newMessage(); 43 | 44 | return `successfully reconnected to #${username}`; 45 | } catch (err) { 46 | console.log(err); 47 | return 'FeelsDankMan Error'; 48 | } 49 | } 50 | }; -------------------------------------------------------------------------------- /commands/followage.js: -------------------------------------------------------------------------------- 1 | const { got } = require('./../got'); 2 | const tools = require('../tools/tools.js'); 3 | 4 | 5 | module.exports = { 6 | name: 'followage', 7 | ping: false, 8 | description: 'This command will give you the time a given user has followed a given channel. Example: "bb followage HotBear1110 NymN"', 9 | permission: 100, 10 | category: 'Info command', 11 | execute: async (channel, user, input, perm) => { 12 | try { 13 | if (module.exports.permission > perm) { 14 | return; 15 | } 16 | let username = user.username; 17 | if (input[2]) { 18 | if (input[2].startsWith('@')) { 19 | username = input[2].substring(1); 20 | } else { 21 | username = input[2]; 22 | } 23 | } 24 | let realchannel = channel; 25 | if (input[3]) { 26 | if (input[3].startsWith('@')) { 27 | realchannel = input[3].substring(1); 28 | } else { 29 | realchannel = input[3]; 30 | } 31 | } 32 | 33 | const followcheck = await got(`https://api.ivr.fi/v2/twitch/subage/${username}/${realchannel}`).json(); 34 | 35 | if (followcheck['followedAt']) { 36 | const ms = new Date().getTime() - Date.parse(followcheck['followedAt']); 37 | return `${username} has been following #${realchannel} for (${tools.humanizeDuration(ms)})`; 38 | } 39 | return `${username} does not follow #${realchannel}.`; 40 | } catch (err) { 41 | console.log(err); 42 | if (err.name) { 43 | if (err.name === 'TimeoutError') { 44 | return `FeelsDankMan api error: ${err.name}`; 45 | } 46 | } 47 | return 'FeelsDankMan Error'; 48 | } 49 | } 50 | }; -------------------------------------------------------------------------------- /commands/vipcheck.js: -------------------------------------------------------------------------------- 1 | const tools = require('../tools/tools.js'); 2 | 3 | module.exports = { 4 | name: 'vipcheck', 5 | ping: true, 6 | description: 'This command will tell you if a given user is a vip in a given channel. And for how long. Example: "bb vipcheck HotBear1110 NymN"(this will check HotBear1110´s vip status in Nymn´s channel)', 7 | permission: 100, 8 | category: 'Info command', 9 | execute: async (channel, user, input, perm) => { 10 | try { 11 | if (module.exports.permission > perm) { 12 | return; 13 | } 14 | let username = user.username; 15 | if (input[2]) { 16 | if (input[2].startsWith('@')) { 17 | input[2] = input[2].substring(1); 18 | } 19 | username = input[2].toLowerCase(); 20 | } 21 | let realchannel = channel; 22 | if (input[3]) { 23 | realchannel = input[3]; 24 | } 25 | const isvip = await tools.getVips(realchannel); 26 | let vipresponse = ''; 27 | 28 | for (const vipstatus of isvip) { 29 | if (vipstatus.login == username) { 30 | let vipdate = vipstatus.grantedAt; 31 | const ms = new Date().getTime() - Date.parse(vipdate); 32 | vipresponse = `that user has been a vip😬 in #${realchannel} for - (${tools.humanizeDuration(ms)})`; 33 | } 34 | } 35 | 36 | if (vipresponse != '') { 37 | return vipresponse; 38 | } 39 | return `That user is not a vip in #${realchannel} :) `; 40 | } catch (err) { 41 | console.log(err); 42 | if (err.name === 'TimeoutError') { 43 | return `FeelsDankMan api error: ${err.name}`; 44 | } 45 | return 'FeelsDankMan Error'; 46 | } 47 | } 48 | }; -------------------------------------------------------------------------------- /web/routes/twitch/login.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const querystring = require('querystring'); 3 | const sql = require('../../../sql/index.js'); 4 | 5 | module.exports = (function () { 6 | const router = require('express').Router(); 7 | 8 | const client_id = process.env.TWITCH_CLIENTID; 9 | const redirect_uri = 'https://hotbear.org/twitch/callback'; 10 | 11 | /* /Login */ 12 | router.get('/', async (req, res) => { 13 | 14 | let cookies = req.cookies || ''; 15 | 16 | let cookieToken = cookies.token; 17 | 18 | if (cookieToken) { 19 | const hasToken = await sql.Query('SELECT * FROM Spotify WHERE cookieToken = ?', [cookieToken]); 20 | 21 | if (hasToken.length) { 22 | res.redirect('../music'); 23 | return router; 24 | } 25 | } 26 | let state = generateRandomString(16); 27 | let scope = ''; 28 | 29 | res.redirect('https://id.twitch.tv/oauth2/authorize?' + 30 | querystring.stringify({ 31 | response_type: 'code', 32 | client_id: client_id, 33 | scope: scope, 34 | redirect_uri: redirect_uri, 35 | state: state 36 | })); 37 | }); 38 | 39 | return router; 40 | })(); 41 | 42 | const generateRandomString = function(length) { 43 | let text = ''; 44 | let possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 45 | 46 | for (let i = 0; i < length; i++) { 47 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 48 | } 49 | return text; 50 | }; -------------------------------------------------------------------------------- /commands/deletesubs.js: -------------------------------------------------------------------------------- 1 | const { got } = require('./../got'); 2 | 3 | module.exports = { 4 | name: 'deletesubs', 5 | ping: false, 6 | description: 'This command is for deleting all eventsubs', 7 | permission: 2000, 8 | category: 'Dev command', 9 | noBanphrase: true, 10 | execute: async (channel, user, input, perm) => { 11 | try { 12 | if (module.exports.permission > perm) { 13 | return; 14 | } 15 | let allsubs = []; 16 | let haspagnation = true; 17 | let pagnation = ''; 18 | while (haspagnation) { 19 | let subs = await got(`https://api.twitch.tv/helix/eventsub/subscriptions?after=${pagnation}`, { 20 | headers: { 21 | 'client-id': process.env.TWITCH_CLIENTID, 22 | 'Authorization': process.env.TWITCH_AUTH 23 | } 24 | }); 25 | subs = JSON.parse(subs.body); 26 | if (subs.pagination.cursor) { 27 | pagnation = subs.pagination.cursor; 28 | } else { 29 | haspagnation = false; 30 | } 31 | subs = subs.data; 32 | allsubs = allsubs.concat(subs); 33 | } 34 | 35 | for (let i = 0; i < allsubs.length; i++) { 36 | setTimeout(async function () { 37 | 38 | let sub = allsubs[i]; 39 | console.log(sub); 40 | await got.delete(`https://api.twitch.tv/helix/eventsub/subscriptions?id=${sub.id}`, { 41 | headers: { 42 | 'client-id': process.env.TWITCH_CLIENTID, 43 | 'Authorization': process.env.TWITCH_AUTH 44 | }, 45 | }); 46 | }, 100 * i); 47 | } 48 | 49 | 50 | return 'Okayge done!!'; 51 | } catch (err) { 52 | console.log(err); 53 | return 'FeelsDankMan Error'; 54 | } 55 | } 56 | }; -------------------------------------------------------------------------------- /commands/botstats.js: -------------------------------------------------------------------------------- 1 | const shell = require('child_process'); 2 | const sql = require('./../sql/index.js'); 3 | 4 | module.exports = { 5 | name: 'botstats', 6 | ping: true, 7 | description: 'This command will give you some info about the bot', 8 | permission: 100, 9 | category: 'Info command', 10 | noBanphrase: true, 11 | execute: async (channel, user, input, perm) => { 12 | try { 13 | if (module.exports.permission > perm) { 14 | return; 15 | } 16 | 17 | const test = shell.execSync('free -h'); 18 | 19 | let total = test.toString().split(':')[1]; 20 | 21 | total = total.split(' '); 22 | 23 | let used = total[17]; 24 | total = total[10]; 25 | 26 | const totalused = used.slice(3, -1); 27 | used = used.slice(0, -2); 28 | total = total.slice(0, -1); 29 | 30 | const cpu = shell.execSync('mpstat'); 31 | 32 | let cpuused = cpu.toString().split('all')[1]; 33 | console.log(cpuused); 34 | cpuused = `${cpuused.split('.')[0].replaceAll(' ', '')}.${cpuused.split('.')[1].slice(0, -2).replaceAll(' ', '')}`; 35 | 36 | let temp = shell.execSync('sensors'); 37 | 38 | temp = temp.toString().split('+')[1]; 39 | temp = temp.split(' ')[0]; 40 | 41 | const commits = shell.execSync('git rev-list --all --count'); 42 | 43 | let streamerCount = await sql.Query('SELECT uid FROM Streamers'); 44 | 45 | return `CPU: ${cpuused}% - Memory: ${used}${totalused}B/${total}B - Temperature: ${temp} - Commits: ${commits} KKona - Currently active in ${streamerCount.length} channels.`; 46 | } catch (err) { 47 | console.log(err); 48 | return 'FeelsDankMan Error'; 49 | } 50 | } 51 | }; -------------------------------------------------------------------------------- /reminders/cdr.js: -------------------------------------------------------------------------------- 1 | const sql = require('../sql/index.js'); 2 | const CONSTANTS = require('./constants'); 3 | 4 | exports.validateIsPositiveBot = (user, input) => { 5 | if (user['user-id'] !== CONSTANTS.POSITIVE_BOT) return false; 6 | if (!input.join(' ').includes('your cooldown has been reset!')) return false; 7 | return true; 8 | }; 9 | 10 | /** 11 | * 12 | * @param { import("tmi.js").ChatUserstate } user 13 | * @param { String[] } input 14 | * @param { String } channel 15 | * @returns { Promise<{ Status: 'Confirmed' | '', User: String }> } 16 | */ 17 | exports.setCdr = async (input, channel) => { 18 | return new Promise(async (Resolve) => { 19 | const realuser = input[1].replace(',', ''); 20 | 21 | await sql.Query(`SELECT COUNT(*) AS Count FROM Cdr AS Foo 22 | WHERE EXISTS(SELECT * FROM Cdr WHERE User = ?)`, [realuser]) 23 | .then(async(res) => { 24 | const response = 'Confirmed'; 25 | const time = Date.now() + 10800000; 26 | 27 | if (res[0].Count !== 0) { 28 | await sql.Query(`UPDATE Cdr 29 | SET Status=?, 30 | Channel=?, 31 | RemindTime=? 32 | WHERE User=?`, [response, channel, time, realuser]); 33 | 34 | Resolve({ Status: response, User: realuser }); 35 | } else { 36 | Resolve({ Status: '', User: '', Channel: ''}); 37 | } 38 | return; 39 | }); 40 | }); 41 | }; 42 | -------------------------------------------------------------------------------- /commands/color.js: -------------------------------------------------------------------------------- 1 | const { got } = require('./../got'); 2 | 3 | module.exports = { 4 | name: 'color', 5 | ping: true, 6 | description: 'This command will give you the color name and hex of a given users username color. Example: "bb color NymN"', 7 | permission: 100, 8 | category: 'Info command', 9 | noBanphrase: false, 10 | execute: async (channel, user, input, perm) => { 11 | try { 12 | if (module.exports.permission > perm) { 13 | return; 14 | } 15 | let username = user.username; 16 | if (input[2]) { 17 | if (input[2].startsWith('@')) { 18 | input[2] = input[2].substring(1); 19 | } 20 | username = input[2]; 21 | 22 | } 23 | let iscolor = false; 24 | if (input[2]) { 25 | if (input[2].startsWith('#')) { 26 | iscolor = true; 27 | } 28 | } 29 | let color = ''; 30 | if (iscolor === false) { 31 | let [userColor] = await got(`https://api.ivr.fi/v2/twitch/user?login=${username}`).json(); 32 | 33 | color = userColor.chatColor; 34 | } else { 35 | color = input[2]; 36 | } 37 | 38 | if (username === user.username) { 39 | color = user['color']; 40 | } 41 | 42 | const colorName = await got(`https://www.thecolorapi.com/id?hex=${color.replace('#', '')}`).json(); 43 | 44 | if (iscolor === true) { 45 | return `That hex is the color: ${colorName.name.value} ${color}`; 46 | } 47 | 48 | return `That user has the color: ${colorName.name.value} ${color}`; 49 | } catch (err) { 50 | console.log(err); 51 | if (err.name) { 52 | if (err.name === 'TimeoutError') { 53 | return `FeelsDankMan api error: ${err.name}`; 54 | } 55 | } 56 | return 'FeelsDankMan Error'; 57 | } 58 | } 59 | }; 60 | -------------------------------------------------------------------------------- /commands/modcheck.js: -------------------------------------------------------------------------------- 1 | const tools = require('../tools/tools.js'); 2 | 3 | module.exports = { 4 | name: 'modcheck', 5 | ping: true, 6 | description: 'This command will tell you if a given user is a mod in a given channel. And for how long. Example: "bb modcheck Fawcan NymN"(this will check Fawcan´s mod status in Nymn´s channel)', 7 | permission: 100, 8 | category: 'Info command', 9 | noBanphrase: true, 10 | execute: async (channel, user, input, perm) => { 11 | try { 12 | if (module.exports.permission > perm) { 13 | return; 14 | } 15 | let username = user.username; 16 | if (input[2]) { 17 | if (input[2].startsWith('@')) { 18 | input[2] = input[2].substring(1); 19 | } 20 | username = input[2].toLowerCase(); 21 | } 22 | let realchannel = channel; 23 | if (input[3]) { 24 | realchannel = input[3]; 25 | } 26 | let ismod = await tools.getMods(realchannel); 27 | let modresponse = ''; 28 | for (const modstatus of ismod) { 29 | if (modstatus.login == username) { 30 | let moddate = modstatus.grantedAt; 31 | const ms = new Date().getTime() - Date.parse(moddate); 32 | modresponse = `that user has been a M OMEGALUL D in #${tools.unpingUser(realchannel)} for - (${tools.humanizeDuration(ms)})`; 33 | } 34 | } 35 | if (modresponse != '') { 36 | return modresponse; 37 | } 38 | else { 39 | return `That user is not a mod in #${tools.unpingUser(realchannel)} :)`; 40 | } 41 | 42 | } catch (err) { 43 | console.log(err); 44 | if (err.name) { 45 | if (err.name === 'TimeoutError') { 46 | return `FeelsDankMan api error: ${err.name}`; 47 | } 48 | } 49 | return 'FeelsDankMan Error'; 50 | } 51 | } 52 | }; -------------------------------------------------------------------------------- /commands/check.js: -------------------------------------------------------------------------------- 1 | const tools = require('../tools/tools.js'); 2 | const sql = require('./../sql/index.js'); 3 | 4 | module.exports = { 5 | name: 'check', 6 | ping: true, 7 | description: 'This command will check info about other users.', 8 | permission: 100, 9 | category: 'Info command', 10 | execute: async (channel, user, input, perm) => { 11 | try { 12 | if (module.exports.permission > perm) { 13 | return; 14 | } 15 | let username = user.username; 16 | switch (input[2]) { 17 | case 'permission': { 18 | if (input[3]) { 19 | if (input[3]?.startsWith('@')) { 20 | input[3] = input[3].substring(1); 21 | } 22 | username = input[3]; 23 | } 24 | /** @type { SQL.Users[] } */ 25 | const User = await sql.Query('SELECT permission FROM Users WHERE username=?', [username]); 26 | 27 | return `${username}'s permission is: ${User[0].permission}`; 28 | } 29 | case 'triviacooldown': { 30 | if (input[3]) { 31 | if (input[3].startsWith('@')) { 32 | input[3] = input[3].substring(1); 33 | } 34 | channel = input[3]; 35 | } 36 | 37 | const TriviaCD = await sql.Query('SELECT trivia_cooldowns FROM Streamers WHERE username=?', [channel]); 38 | 39 | if (!TriviaCD.length) { 40 | return; 41 | } 42 | 43 | return `#${channel}'s trivia cooldown is ${TriviaCD[0].trivia_cooldowns / 1000}s`; 44 | 45 | } 46 | default: 47 | return 'Stuff available to check: permission, triviacooldown'; 48 | } 49 | } catch (err) { 50 | console.log(err); 51 | return `FeelsDankMan Sql error: ${err.sqlMessage}`; 52 | } 53 | } 54 | }; -------------------------------------------------------------------------------- /commands/randomemote.js: -------------------------------------------------------------------------------- 1 | const { got } = require('./../got'); 2 | const sql = require('./../sql/index.js'); 3 | const redis = require('./../tools/redis.js'); 4 | 5 | const CACHE_TIME = 60 * 60; 6 | 7 | const getGlobals = async () => { 8 | const cache = await redis.Get().Get('helix:globals'); 9 | if (cache) { 10 | return JSON.parse(cache); 11 | } 12 | const globalEmotes = await got('https://api.twitch.tv/helix/chat/emotes/global', { 13 | headers: { 14 | 'client-id': process.env.TWITCH_CLIENTID, 15 | 'Authorization': process.env.TWITCH_AUTH 16 | } 17 | }).json(); 18 | 19 | saveGlobals(globalEmotes.data); 20 | 21 | return globalEmotes.data; 22 | }; 23 | 24 | const saveGlobals = async (emotes) => { 25 | const b = await redis.Get().Set('helix:globals', JSON.stringify(emotes)); 26 | await b(CACHE_TIME); 27 | }; 28 | 29 | module.exports = { 30 | name: 'randomemote', 31 | ping: false, 32 | description: 'This command will respond with a random emote', 33 | permission: 100, 34 | category: 'Random command', 35 | noBanphrase: true, 36 | execute: async (channel, user, input, perm) => { 37 | try { 38 | if (module.exports.permission > perm) { 39 | return; 40 | } 41 | const streamer = await sql.Query(`SELECT emote_list FROM Streamers WHERE username="${channel}"`); 42 | let emotes = JSON.parse(streamer[0].emote_list); 43 | 44 | const globalEmotes = await getGlobals(); 45 | 46 | emotes = emotes.concat(globalEmotes); 47 | let number = Math.floor(Math.random() * (emotes.length - 0) + 0); 48 | 49 | return emotes[number][0] || emotes[number].name; 50 | } catch (err) { 51 | console.log(err); 52 | return 'FeelsDankMan Error'; 53 | } 54 | } 55 | }; -------------------------------------------------------------------------------- /commands/enablenewcommands.js: -------------------------------------------------------------------------------- 1 | const sql = require('./../sql/index.js'); 2 | 3 | module.exports = { 4 | name: 'enablenewcommands', 5 | ping: true, 6 | description: 'Decide if you want new commands to be enabled or disabled as default', 7 | permission: 100, 8 | category: 'Core command', 9 | noBanphrase: true, 10 | execute: async (channel, user, input, perm) => { 11 | try { 12 | if (module.exports.permission > perm) { 13 | return; 14 | } 15 | if (!user.mod && channel !== user.username && user['user-id'] != process.env.TWITCH_OWNERUID) { 16 | return; 17 | } 18 | switch (input[2]) { 19 | case 'true': { 20 | const disableCommand = await sql.Query('SELECT command_default FROM Streamers WHERE username = ?', [channel]); 21 | 22 | if (disableCommand[0].command_default === 1) { 23 | sql.Query('UPDATE Streamers SET command_default=? WHERE username=?', [0, channel]); 24 | return 'New commands are now enabled by default'; 25 | } else { 26 | return 'New commands are already enabled by default'; 27 | } 28 | } 29 | case 'false': { 30 | const disableCommand = await sql.Query('SELECT command_default FROM Streamers WHERE username = ?', [channel]); 31 | 32 | if (disableCommand[0].command_default === 0) { 33 | sql.Query('UPDATE Streamers SET command_default=? WHERE username=?', [1, channel]); 34 | return 'New commands are now disabled by default'; 35 | } else { 36 | return 'New commands are already disabled by default'; 37 | } 38 | } 39 | default: 40 | return '"bb enablenewcommands true/false" decide if you want new commands to be enabled or disabled as default'; 41 | } 42 | } 43 | catch (err) { 44 | console.log(err); 45 | return 'FeelsDankMan Error'; 46 | } 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /commands/optout.js: -------------------------------------------------------------------------------- 1 | const requireDir = require('require-dir'); 2 | const commands = requireDir('./'); 3 | const sql = require('../sql/index.js'); 4 | const tools = require('../tools/tools.js'); 5 | 6 | module.exports = { 7 | name: 'optout', 8 | ping: true, 9 | description: 'This command will let you opt-out of other commands', 10 | permission: 100, 11 | cooldown: 3, //in seconds 12 | category: 'Core command', 13 | opt_outable: false, 14 | // eslint-disable-next-line no-unused-vars 15 | execute: async (channel, user, input, perm, aliascommand) => { 16 | try { 17 | if (module.exports.permission > perm) { 18 | return; 19 | } 20 | input.splice(1,1); 21 | input = await tools.Alias(input.join(' ')); 22 | 23 | const command = input[1]; 24 | 25 | if (typeof commands[command] === 'undefined') { 26 | return `${command} is not a command`; 27 | } 28 | 29 | if (!commands[command].opt_outable) { 30 | return `You cannot opt-out of ${command}`; 31 | } 32 | 33 | let optOutList = await sql.Query('SELECT opt_out FROM Commands WHERE Name = ?', [command]); 34 | optOutList = JSON.parse(optOutList[0].opt_out); 35 | 36 | if (optOutList.includes(user.username)) { 37 | return 'FeelsDankMan You are already opted-out of that command'; 38 | } 39 | 40 | optOutList.push(user.username); 41 | 42 | optOutList = JSON.stringify(optOutList); 43 | 44 | 45 | sql.Query('UPDATE Commands SET opt_out=? WHERE Name=?', [optOutList, command]); 46 | 47 | 48 | return `You are now opted-out of the ${command} command`; 49 | } catch (err) { 50 | console.log(err); 51 | return 'FeelsDankMan Error'; 52 | } 53 | } 54 | }; -------------------------------------------------------------------------------- /sql/migrations/2_update.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS `Ask` ( 2 | `ID` int(11) NOT NULL AUTO_INCREMENT, 3 | `User` varchar(255) DEFAULT NULL, 4 | `Channel` varchar(255) DEFAULT NULL, 5 | `Prompt` varchar(255) DEFAULT NULL, 6 | `Response` longtext DEFAULT NULL, 7 | `Timestamp` int(11) DEFAULT unix_timestamp(current_timestamp()), 8 | PRIMARY KEY (`ID`) 9 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4; 10 | 11 | CREATE TABLE IF NOT EXISTS `Dalle` ( 12 | `ID` int(11) NOT NULL AUTO_INCREMENT, 13 | `User` varchar(255) DEFAULT NULL, 14 | `Channel` varchar(255) DEFAULT NULL, 15 | `Prompt` longtext DEFAULT NULL, 16 | `Image` varchar(255) DEFAULT NULL, 17 | `Timestamp` int(11) DEFAULT unix_timestamp(current_timestamp()), 18 | PRIMARY KEY (`ID`) 19 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4; 20 | 21 | CREATE TABLE IF NOT EXISTS `Spotify` ( 22 | `username` varchar(255) DEFAULT NULL, 23 | `uid` int(11) DEFAULT NULL, 24 | `state` varchar(255) NOT NULL DEFAULT '0', 25 | `access_token` varchar(255) NOT NULL DEFAULT '', 26 | `refresh_token` varchar(255) NOT NULL DEFAULT '', 27 | `expires_in` varchar(255) DEFAULT NULL, 28 | `opt_in` varchar(255) DEFAULT 'false' 29 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4; 30 | 31 | CREATE TABLE IF NOT EXISTS `Yabbe_bans` ( 32 | `Command` varchar(255) DEFAULT NULL, 33 | `User` varchar(255) DEFAULT NULL 34 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4; 35 | 36 | CREATE TABLE IF NOT EXISTS `Yabbe_pet` ( 37 | `ID` int(11) NOT NULL AUTO_INCREMENT, 38 | `User` varchar(255) DEFAULT NULL, 39 | `Pet` varchar(255) DEFAULT NULL, 40 | `Pet_name` varchar(255) DEFAULT NULL, 41 | `Image` varchar(255) DEFAULT NULL, 42 | PRIMARY KEY (`ID`) 43 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4; 44 | -------------------------------------------------------------------------------- /commands/unoptout.js: -------------------------------------------------------------------------------- 1 | const requireDir = require('require-dir'); 2 | const commands = requireDir('./'); 3 | const sql = require('../sql/index.js'); 4 | const tools = require('../tools/tools.js'); 5 | 6 | module.exports = { 7 | name: 'unoptout', 8 | ping: true, 9 | description: 'This command will let you unopt-out of other commands', 10 | permission: 100, 11 | cooldown: 3, //in seconds 12 | category: 'Core command', 13 | opt_outable: false, 14 | // eslint-disable-next-line no-unused-vars 15 | execute: async (channel, user, input, perm, aliascommand) => { 16 | try { 17 | if (module.exports.permission > perm) { 18 | return; 19 | } 20 | input.splice(1,1); 21 | input = await tools.Alias(input.join(' ')); 22 | 23 | const command = input[1]; 24 | 25 | if (typeof commands[command] === 'undefined') { 26 | return `${command} is not a command`; 27 | } 28 | 29 | if (!commands[command].opt_outable) { 30 | return `You cannot opt-out of ${command} in the first place`; 31 | } 32 | 33 | let optOutList = await sql.Query('SELECT opt_out FROM Commands WHERE Name = ?', [command]); 34 | optOutList = JSON.parse(optOutList[0].opt_out); 35 | 36 | if (!optOutList.includes(user.username)) { 37 | return 'FeelsDankMan You are not opted-out of that command'; 38 | } 39 | 40 | optOutList.splice(optOutList.indexOf(user.username), 1); 41 | 42 | optOutList = JSON.stringify(optOutList); 43 | 44 | 45 | sql.Query('UPDATE Commands SET opt_out=? WHERE Name=?', [optOutList, command]); 46 | 47 | 48 | return `You are no longer opted-out of the ${command} command`; 49 | } catch (err) { 50 | console.log(err); 51 | return 'FeelsDankMan Error'; 52 | } 53 | } 54 | }; -------------------------------------------------------------------------------- /commands/help.js: -------------------------------------------------------------------------------- 1 | const tools = require('../tools/tools.js'); 2 | const sql = require('./../sql/index.js'); 3 | 4 | module.exports = { 5 | name: 'help', 6 | ping: true, 7 | description: 'This command will give you information about any onther command. Example: "bb help followage"', 8 | permission: 100, 9 | category: 'Core command', 10 | noBanphrase: true, 11 | execute: async (channel, user, input, perm) => { 12 | try { 13 | if (module.exports.permission > perm) { 14 | return; 15 | } 16 | if (!input[2]) { 17 | return 'List of commands: https://hotbear.org/ - If you want help with a command, write: "bb help *command*"'; 18 | } 19 | 20 | input.splice(1,1); 21 | input = await tools.Alias(input.join(' ')); 22 | 23 | const realcommand = input[1]; 24 | /** @type { Array } */ 25 | const commandlist = await sql.Query('SELECT * FROM Commands WHERE Name=?', [realcommand]); 26 | 27 | if (!commandlist.length) { 28 | return 'Command not found FeelsDankMan'; 29 | } 30 | 31 | let cooldown = commandlist[0].Cooldown; 32 | if (!commandlist[0].Cooldown) { 33 | cooldown = 3; 34 | } 35 | 36 | if (commandlist[0].Name === 'trivia' || commandlist[0].Name === 'trivia2') { 37 | let cd = await sql.Query('SELECT trivia_cooldowns FROM Streamers WHERE username = ?', [channel]); 38 | 39 | if (cd[0].trivia_cooldowns === null) { 40 | cd[0].trivia_cooldowns === 30000; 41 | sql.Query('UPDATE Streamers SET trivia_cooldowns = 30000 WHERE username = ?', [channel]); 42 | } 43 | 44 | cooldown = cd[0].trivia_cooldowns / 1000; 45 | } 46 | 47 | 48 | 49 | return `${commandlist[0].Command} - Permission lvl: ${commandlist[0].Perm} - Cooldown: ${cooldown}s`; 50 | 51 | } catch (err) { 52 | console.log(err); 53 | return `FeelsDankMan Sql error: ${err.sqlMessage}`; 54 | } 55 | } 56 | }; -------------------------------------------------------------------------------- /commands/channels.js: -------------------------------------------------------------------------------- 1 | const tools = require('../tools/tools.js'); 2 | const sql = require('./../sql/index.js'); 3 | const { got } = require('./../got'); 4 | 5 | module.exports = { 6 | name: 'channels', 7 | ping: true, 8 | description: 'Posts a list of all the channels the bot is active in', 9 | permission: 100, 10 | cooldown: 3, //in seconds 11 | category: 'Info command', 12 | opt_outable: false, 13 | showDelay: false, 14 | noBanphrase: true, 15 | channelSpecific: false, 16 | activeChannel: '', 17 | // eslint-disable-next-line no-unused-vars 18 | execute: async (channel, user, input, perm, aliascommand) => { 19 | try { 20 | if (module.exports.permission > perm) { 21 | return; 22 | } 23 | const Streamers = await sql.Query('SELECT username FROM Streamers'); 24 | 25 | let streamer_list = Streamers.map(x => x.username); 26 | 27 | const markovAPI = await got('https://magnolia.melon095.live/api/markov/list').json(); 28 | console.log(markovAPI); 29 | let markov_channels = markovAPI.data.channels; 30 | 31 | let markov_list = markov_channels.map(x => !streamer_list.includes(x.username) && x.username).filter(Boolean); 32 | 33 | streamer_list = streamer_list.toString().replaceAll(',', '\n'); 34 | markov_list = markov_list.toString().replaceAll(',', '\n'); 35 | 36 | let hastebinlist = await tools.makehastebin(`All channels the bot is active in:\n\n${streamer_list}\n\n\nExtra channels available for makov (If you try a channel not on the list, it will get added to the list and work after 100 messages are logged):\n\n${markov_list}`); 37 | 38 | return `All channels the bot is active in: ${hastebinlist}`; 39 | 40 | } catch (err) { 41 | console.log(err); 42 | return 'FeelsDankMan Error'; 43 | } 44 | } 45 | }; -------------------------------------------------------------------------------- /commands/title.js: -------------------------------------------------------------------------------- 1 | const tools = require('../tools/tools.js'); 2 | const { got } = require('./../got'); 3 | const sql = require('./../sql/index.js'); 4 | 5 | module.exports = { 6 | name: 'title', 7 | ping: true, 8 | description: 'This command will give you the title of a given streamer', 9 | permission: 100, 10 | category: 'Info command', 11 | noBanphrase: true, 12 | execute: async (channel, user, input, perm) => { 13 | try { 14 | if (module.exports.permission > perm) { 15 | return; 16 | } 17 | let realchannel = channel; 18 | 19 | if (input[2]) { 20 | if (input[2].startsWith('@')) { 21 | input[2] = input[2].substring(1); 22 | } 23 | realchannel = input[2]; 24 | } 25 | let title = ''; 26 | const streamTitle = await sql.Query('SELECT title, title_time FROM Streamers WHERE username=?', [realchannel]); 27 | if (!streamTitle[0]) { 28 | let userID = await got(`https://api.ivr.fi/v2/twitch/user?login=${input[2]}`).json(); 29 | 30 | userID = userID.id; 31 | 32 | title = await got(`https://api.twitch.tv/helix/channels?broadcaster_id=${userID}`, { 33 | headers: { 34 | 'client-id': process.env.TWITCH_CLIENTID, 35 | 'Authorization': process.env.TWITCH_AUTH 36 | } 37 | }).json(); 38 | title = title.data[0].title; 39 | } else { 40 | let oldtitleTime = JSON.parse(streamTitle[0].title_time); 41 | title = streamTitle[0].title; 42 | 43 | if (oldtitleTime !== null) { 44 | const ms = new Date().getTime() - oldtitleTime; 45 | 46 | return `@${realchannel[0]}\u034f${realchannel.slice(1)}'s current title is: "${title}". Title changed ${tools.humanizeDuration(ms)} ago.`; 47 | } 48 | } 49 | 50 | 51 | return `@${realchannel}'s current title is: "${title}"`; 52 | } catch (err) { 53 | console.log(err); 54 | return `FeelsDankMan Sql error: ${err.sqlMessage}`; 55 | } 56 | } 57 | }; -------------------------------------------------------------------------------- /commands/trivia2.js: -------------------------------------------------------------------------------- 1 | const { got } = require('./../got'); 2 | const tools = require('../tools/tools.js'); 3 | 4 | module.exports = { 5 | name: 'trivia2', 6 | ping: false, 7 | description: 'This command will start a new trivia in chat, Example: \' bb trivia2 forsen \' (To see the cooldown on this command, do: "bb check triviacooldown") - Api used: https://gazatu.xyz/ - Categories available: https://gazatu.xyz/trivia/categories', 8 | permission: 100, 9 | category: 'Random command', 10 | execute: async (channel, user, input, perm) => { 11 | try { 12 | if (module.exports.permission > perm) { 13 | return; 14 | } 15 | 16 | let inputCategory = input[2] ?? false; 17 | 18 | let exludeCategories = '[anime,hentai,weeb,d dansgame ta,vorkosigan_saga,dota]' 19 | 20 | 21 | if (inputCategory) { 22 | exludeCategories = '[]' 23 | 24 | if (inputCategory.toLowerCase() === 'anime') { 25 | inputCategory = false 26 | } 27 | } 28 | 29 | 30 | 31 | const url = `https://api.gazatu.xyz/trivia/questions?count=1&exclude=${encodeURIComponent(exludeCategories)}` 32 | + ((inputCategory) ? `&include=${encodeURIComponent(`[${inputCategory}]`)}` : ''); 33 | 34 | const questions = await got(url).json(); 35 | 36 | const question = decodeURIComponent(questions[0].question); 37 | let correct_answer = decodeURIComponent(questions[0].answer); 38 | const hint1 = questions[0].hint1; 39 | const hint2 = questions[0].hint2; 40 | const category = decodeURIComponent(questions[0].category); 41 | correct_answer = tools.removeTrailingStuff(correct_answer); 42 | 43 | return [`(Trivia) [${category}] Question: ${question}`, correct_answer, hint1, hint2]; 44 | 45 | } catch (err) { 46 | console.log(err); 47 | if (err.name === 'TimeoutError') { 48 | return `FeelsDankMan api error: ${err.name}`; 49 | } 50 | return `FeelsDankMan Error, ${err}`; 51 | } 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /commands/tags.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const { got } = require('./../got'); 3 | const tools = require('../tools/tools.js'); 4 | 5 | module.exports = { 6 | name: 'tags', 7 | ping: true, 8 | description: 'This command will give you a list of tags in a given channel', 9 | permission: 100, 10 | category: 'Info command', 11 | execute: async (channel, user, input, perm) => { 12 | try { 13 | if (module.exports.permission > perm) { 14 | return; 15 | } 16 | if (!process.env.THREELETTERAPI_CLIENTID) { 17 | return 'FeelsDankMan Error: THREELETTERAPI_CLIENTID isn`t set'; 18 | } 19 | 20 | let realchannel = channel; 21 | 22 | if (input[2]) { 23 | if (input[2].startsWith('@')) { 24 | input[2] = input[2].substring(1); 25 | } 26 | realchannel = input[2]; 27 | } 28 | let query = ` 29 | query { 30 | user(login: "${realchannel}") { 31 | freeformTags { 32 | name 33 | } 34 | } 35 | }`; 36 | 37 | let ThreeLetterApiCall = await got.post('https://gql.twitch.tv/gql', { 38 | headers: { 39 | 'Client-ID': process.env.THREELETTERAPI_CLIENTID, 40 | 'Content-Type': 'application/json', 41 | }, 42 | body: JSON.stringify({ 43 | query: query 44 | }), 45 | }).json(); 46 | realchannel = tools.unpingUser(realchannel); 47 | 48 | if (!ThreeLetterApiCall.data.user) { 49 | return `FeelsDankMan ${realchannel} is not a real username`; 50 | } else if (!ThreeLetterApiCall.data.user.freeformTags.length) { 51 | return `${realchannel} does not have any tags`; 52 | } 53 | 54 | let tags = ThreeLetterApiCall.data.user.freeformTags.map(x => x.name).join(', '); 55 | console.log(`${realchannel}'s tags: ${tags}`); 56 | return `${realchannel}'s tags: ${tags}`; 57 | } catch (err) { 58 | console.log(err); 59 | return `FeelsDankMan Sql error: ${err.sqlMessage}`; 60 | } 61 | } 62 | }; -------------------------------------------------------------------------------- /commands/commands.js: -------------------------------------------------------------------------------- 1 | const tools = require('../tools/tools.js'); 2 | const sql = require('./../sql/index.js'); 3 | 4 | module.exports = { 5 | name: 'commands', 6 | ping: true, 7 | description: 'This command will give you a link to the bot´s commands. You can add "local" to the end of the command, to see the commands enabled in the chat.', 8 | permission: 100, 9 | category: 'Core command', 10 | noBanphrase: true, 11 | execute: async (channel, user, input, perm) => { 12 | try { 13 | if (module.exports.permission > perm) { 14 | return; 15 | } 16 | if (input[2]) { 17 | if (input[2] === 'local') { 18 | let disabledList = await sql.Query(` 19 | SELECT disabled_commands 20 | FROM Streamers 21 | WHERE username=?`, 22 | [channel]); 23 | 24 | disabledList = JSON.parse(disabledList[0].disabled_commands); 25 | 26 | if (!disabledList.length) { 27 | return 'This channel has all commands enabled: https://hotbear.org/'; 28 | } 29 | 30 | let commandList = await sql.Query(` 31 | SELECT * 32 | FROM Commands`); 33 | 34 | let commandsListNames = []; 35 | 36 | for (const command of commandList) { 37 | commandsListNames.push(command.Name.toLowerCase()); 38 | } 39 | 40 | for (const commandName of disabledList) { 41 | commandsListNames.splice(commandsListNames.indexOf(commandName), 1); 42 | } 43 | 44 | commandsListNames = commandsListNames.toString().replaceAll(',', '\n'); 45 | 46 | let hastebinlist = await tools.makehastebin(`List of enabled commands in #${channel}:\n\n${commandsListNames}`); 47 | 48 | return `Local command list: ${hastebinlist}.txt`; 49 | 50 | } 51 | } 52 | return 'List of commands: https://hotbear.org/'; 53 | } catch (err) { 54 | console.log(err); 55 | return `FeelsDankMan Sql error: ${err.sqlMessage}`; 56 | } 57 | } 58 | }; -------------------------------------------------------------------------------- /commands/downdetector.js: -------------------------------------------------------------------------------- 1 | const { got } = require('./../got'); 2 | const tools = require('../tools/tools.js'); 3 | 4 | module.exports = { 5 | name: 'downdetector', 6 | ping: true, 7 | description: 'checks if a website is down', 8 | permission: 100, 9 | cooldown: 3, //in seconds 10 | category: 'Info command', 11 | opt_outable: false, 12 | showDelay: false, 13 | noBanphrase: false, 14 | channelSpecific: false, 15 | activeChannel: '', 16 | // eslint-disable-next-line no-unused-vars 17 | execute: async (channel, user, input, perm, aliascommand) => { 18 | try { 19 | if (module.exports.permission > perm) { 20 | return; 21 | } 22 | let url = (input[2].match(/^(https?:\/\/)/)) ? input[2] : 'https://' + input[2]; 23 | url = (url.match(/(\.\w+)$/)) ? url : url + '.com'; 24 | 25 | let response = 404; 26 | let isup = false; 27 | try { 28 | const connection = await got(url, { 29 | timeout: { 30 | socket: 2000, 31 | lookup: 2000, 32 | connect: 2000, 33 | }, 34 | throwHttpErrors: false 35 | }); 36 | response = (connection.statusCode) ? connection.statusCode : 404; 37 | 38 | isup = true; 39 | } catch (err) { 40 | response = err.code; 41 | isup = false; 42 | console.error(err); 43 | } 44 | return isup ? tools.unpingUser(url) + ' seems to be working | response: ' + response : tools.unpingUser(url) + ' seems to be down | response: ' + erros[response] ?? response; 45 | } catch (err) { 46 | console.log(err); 47 | return 'FeelsDankMan Error'; 48 | } 49 | } 50 | }; 51 | 52 | const erros = { 53 | ETIMEDOUT: 'Server timed out', 54 | ENOTFOUND: 'Server not found', 55 | EAI_AGAIN : 'DNS server failed to fulfill the request', 56 | }; -------------------------------------------------------------------------------- /connect/connect.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const sql = require('../sql/index.js'); 3 | const { got } = require('./../got'); 4 | const querystring = require('querystring'); 5 | 6 | const channelOptions = [process.env.TWITCH_OWNERNAME]; 7 | 8 | const client_id = process.env.TWITCH_CLIENTID; 9 | const client_secret = process.env.TWITCH_SECRET; 10 | let password = process.env.TWITCH_PASSWORD; 11 | 12 | exports.TMISettings = { 13 | options: { 14 | joinInterval: process.env.BOT_VERIFIED ? 300 : 2000, 15 | }, 16 | connection: { 17 | secure: true, 18 | reconnect: true, 19 | }, 20 | identity: { 21 | username: process.env.TWITCH_USER, 22 | password: password, 23 | }, 24 | channels: channelOptions, 25 | }; 26 | 27 | exports.setupChannels = new Promise(async (Resolve) => { 28 | (await sql.Query('SELECT username FROM Streamers WHERE `banned` = ? AND `have_left` = ?', [0, 0])) 29 | .map(({ username }) => (username === process.env.TWITCH_OWNERNAME) ? true : channelOptions.push(username)); 30 | 31 | console.log(`Imported channels from database: ${channelOptions}`); 32 | 33 | const old_refresh_token = (await sql.Query('SELECT refresh_token FROM Auth_users WHERE uid = ?', [process.env.TWITCH_UID]))[0].refresh_token; 34 | 35 | const refresh = await got.post('https://id.twitch.tv/oauth2/token?' + 36 | querystring.stringify({ 37 | client_id: client_id, 38 | client_secret: client_secret, 39 | grant_type: 'refresh_token', 40 | refresh_token: old_refresh_token 41 | })).json(); 42 | 43 | if (!refresh.error) { 44 | const expires_in = Date.now() + refresh.expires_in; 45 | 46 | await sql.Query('UPDATE Auth_users SET access_token = ?, refresh_token = ?, expires_in = ? WHERE uid = ?', [refresh.access_token, refresh.refresh_token, expires_in, process.env.TWITCH_UID]); 47 | 48 | this.TMISettings.identity.password = 'oauth:' + refresh.access_token; 49 | } else { 50 | throw('setupChannels error'); 51 | } 52 | Resolve(password); 53 | }); -------------------------------------------------------------------------------- /commands/ban.js: -------------------------------------------------------------------------------- 1 | const { got } = require('./../got'); 2 | const sql = require('./../sql/index.js'); 3 | const tools = require('../tools/tools.js'); 4 | 5 | module.exports = { 6 | name: 'ban', 7 | ping: true, 8 | description: 'bans a user for every line in the given file', 9 | permission: 2000, 10 | cooldown: 3, //in seconds 11 | category: 'Dev command', 12 | opt_outable: false, 13 | showDelay: false, 14 | noBanphrase: false, 15 | channelSpecific: false, 16 | activeChannel: '', 17 | // eslint-disable-next-line no-unused-vars 18 | execute: async (channel, user, input, perm, aliascommand) => { 19 | try { 20 | if (module.exports.permission > perm) { 21 | return; 22 | } 23 | const apicall = await got(input[2]); 24 | const min = input[3] ?? 0; 25 | const reason = input.slice(4).join(' '); 26 | const channel_uid = (await sql.Query('SELECT uid FROM Streamers WHERE username=?', [channel]))[0].uid.toString(); 27 | 28 | const users = apicall.body.split(/\r?\n/); 29 | 30 | const userIDs = await tools.getUserIDs(users); 31 | 32 | 33 | await Promise.allSettled(userIDs.map(async (uid) => { 34 | try { 35 | await got.post(`https://api.twitch.tv/helix/moderation/bans?broadcaster_id=${channel_uid}&moderator_id=${process.env.TWITCH_UID}`, { 36 | headers: { 37 | 'client-id': process.env.TWITCH_CLIENTID, 38 | 'Authorization': process.env.TWITCH_AUTH, 39 | 'Content-Type': 'application/json' 40 | }, 41 | json: { 42 | 'data': { 43 | 'user_id': uid, 44 | 'duration': min * 60, 45 | 'reason': reason 46 | } 47 | } 48 | }).json(); 49 | } catch (err) { 50 | console.log(err); 51 | } 52 | })); 53 | 54 | return; 55 | } catch (err) { 56 | console.log(err); 57 | return 'FeelsDankMan Error'; 58 | } 59 | } 60 | }; 61 | -------------------------------------------------------------------------------- /commands/unmute.js: -------------------------------------------------------------------------------- 1 | const redis = require('./../tools/redis.js'); 2 | const { got } = require('./../got'); 3 | const twitchAuth = require('../tools/twitchAuth.js'); 4 | const sql = require('./../sql/index.js'); 5 | 6 | module.exports = { 7 | name: 'unmute', 8 | ping: true, 9 | description: 'Unmutes bot notifications', 10 | permission: 100, 11 | cooldown: 3, //in seconds 12 | category: 'Notify command', 13 | opt_outable: false, 14 | showDelay: false, 15 | noBanphrase: true, 16 | channelSpecific: false, 17 | activeChannel: '', 18 | // eslint-disable-next-line no-unused-vars 19 | execute: async (channel, user, input, perm, aliascommand) => { 20 | try { 21 | if (module.exports.permission > perm) { 22 | return; 23 | } 24 | const isMod = user.mod || user['user-type'] === 'mod'; 25 | const isBroadcaster = channel.toLowerCase() === user.username.toLowerCase(); 26 | 27 | if (!isMod && !isBroadcaster && perm < 2000) { 28 | return; 29 | } 30 | 31 | await redis.Get().Set(`${channel}:unmute_time`, 0); 32 | 33 | if (channel === 'nymn') { 34 | const twitch_user = await twitchAuth.fetchToken(process.env.TWITCH_UID); 35 | 36 | if (twitch_user.error) { 37 | return 'Something went wrong when refreshing user token DinkDonk @HotBear1110'; 38 | } 39 | 40 | if (twitch_user.no_auth) { 41 | return 'Bot is not authorized DinkDonk @HotBear1110'; 42 | } 43 | 44 | const access_token = twitch_user.access_token; 45 | 46 | await got.delete(`https://api.twitch.tv/helix/moderation/bans?broadcaster_id=62300805&moderator_id=${process.env.TWITCH_UID}&user_id=268612479`, { 47 | headers: { 48 | 'client-id': process.env.TWITCH_CLIENTID, 49 | 'Authorization': 'Bearer ' + access_token, 50 | } 51 | } ).json(); 52 | } 53 | 54 | return 'Successfully unmuted notifications'; 55 | } catch (err) { 56 | console.log(err); 57 | return 'FeelsDankMan Error'; 58 | } 59 | } 60 | }; -------------------------------------------------------------------------------- /tools/twitchAuth.js: -------------------------------------------------------------------------------- 1 | const { got } = require('./../got'); 2 | const sql = require('../sql/index.js'); 3 | require('dotenv').config(); 4 | const client_id = process.env.TWITCH_CLIENTID; 5 | const client_secret = process.env.TWITCH_SECRET; 6 | 7 | exports.refreshToken = async function(uid, refresh_token) { 8 | 9 | let authOptions = { 10 | url: 'https://id.twitch.tv/oauth2/token', 11 | form: { 12 | refresh_token: refresh_token, 13 | grant_type: 'refresh_token', 14 | client_secret: client_secret, 15 | client_id: client_id 16 | }, 17 | headers: { 18 | 'Content-Type': 'application/x-www-form-urlencoded' 19 | }, 20 | json: true 21 | }; 22 | 23 | const twitchRefresh = await got.post(authOptions.url, { 24 | headers: authOptions.headers, 25 | form: authOptions.form 26 | }).json(); 27 | 28 | console.log(twitchRefresh); 29 | 30 | const new_access_token = twitchRefresh.access_token; 31 | 32 | const expires_in = Date.now() + twitchRefresh.expires_in; 33 | 34 | await sql.Query('UPDATE Auth_users SET access_token = ?, expires_in = ? WHERE uid = ?', 35 | [new_access_token, expires_in, uid]); 36 | 37 | return new_access_token; 38 | }; 39 | 40 | exports.fetchToken = async function(uid) { 41 | const twitch_user = await sql.Query('SELECT * FROM Auth_users WHERE uid = ?',[uid]); 42 | 43 | if (!twitch_user.length){ 44 | return { no_auth: true }; 45 | } 46 | 47 | let access_token = twitch_user[0].access_token; 48 | const refresh_token = twitch_user[0].refresh_token; 49 | const expires_in = twitch_user[0].expires_in; 50 | 51 | if(Date.now() > expires_in) { 52 | try { 53 | access_token = await this.refreshToken(uid, refresh_token); 54 | } catch (err) { 55 | console.log(err); 56 | return { error: 'Failed to refresh token' }; 57 | } 58 | } 59 | 60 | return { access_token: access_token }; 61 | 62 | }; -------------------------------------------------------------------------------- /commands/recap.js: -------------------------------------------------------------------------------- 1 | const { got } = require("./../got"); 2 | 3 | module.exports = { 4 | name: "recap", 5 | ping: true, 6 | description: 7 | 'This command will give you a random logged line from either yourself or a specified user in the chat (Only works if logs are available in the channel, logs used: "https://logs.ivr.fi/"). Example: "bb rl NymN"', 8 | permission: 100, 9 | category: "Info command", 10 | execute: async (channel, user, input, perm) => { 11 | try { 12 | if (module.exports.permission > perm) { 13 | return; 14 | } 15 | let realuser = user.username; 16 | if (input[2]) { 17 | if (input[2].startsWith("@")) { 18 | input[2] = input[2].substring(1); 19 | } 20 | realuser = input[2]; 21 | } 22 | 23 | let realchannel = channel; 24 | if (input[3]) { 25 | realchannel = input[3]; 26 | } 27 | 28 | let logs = await got( 29 | `https://logs.ivr.fi/channel/${realchannel}/user/${realuser}?from=2024-12-03T14%3A27%3A27.000Z&to=2025-12-03T14%3A27%3A27.000Z&json=true`, 30 | ).json(); 31 | 32 | return ( 33 | `Total messages from @${realuser} sent this year in #${realchannel}: ` + 34 | logs.messages.length 35 | ); 36 | } catch (err) { 37 | console.log(err); 38 | if ( 39 | err.toString().startsWith("HTTPError: Response code 403 (Forbidden)") 40 | ) { 41 | return "User or channel has opted out"; 42 | } 43 | if ( 44 | err 45 | .toString() 46 | .startsWith("HTTPError: Response code 500 (Internal Server Error)") 47 | ) { 48 | return "Could not load logs. Most likely the user either doesn't exist or doesn't have any logs here."; 49 | } 50 | if (err.name) { 51 | if (err.name === "HTTPError") { 52 | return "That user does not exist"; 53 | } 54 | return `FeelsDankMan api error: ${err.name}`; 55 | } 56 | return "FeelsDankMan Error"; 57 | } 58 | }, 59 | }; 60 | -------------------------------------------------------------------------------- /commands/removed.js: -------------------------------------------------------------------------------- 1 | const tools = require('../tools/tools.js'); 2 | const sql = require('./../sql/index.js'); 3 | 4 | module.exports = { 5 | name: 'removed', 6 | ping: true, 7 | description: 'This command will give you a list of the last 6 removed 3rd part emotes.', 8 | permission: 100, 9 | category: 'Info command', 10 | noBanphrase: true, 11 | execute: async (channel, user, input, perm) => { 12 | try { 13 | if (module.exports.permission > perm) { 14 | return; 15 | } 16 | const streamer = await sql.Query(`SELECT emote_removed FROM Streamers WHERE username="${channel}"`); 17 | let emotes = JSON.parse(streamer[0].emote_removed); 18 | 19 | if (!emotes.length) { 20 | return 'there are no removed emotes in this channel yet.'; 21 | } 22 | 23 | if (input[2]) { 24 | if (input[2].startsWith('-') || input[2] === '0') { 25 | return '2nd input can\'t be negative or 0'; 26 | 27 | } 28 | let isnumber = !isNaN(input[2]); 29 | if (!isnumber) { 30 | return '2nd input should be a number'; 31 | } 32 | if (input[2] !== '1') { 33 | emotes = emotes.slice(-(12 * (input[2] - 1))).reverse(); 34 | emotes = emotes.slice((6 * (input[2] - 2) + (6 * (input[2] - 1)))); 35 | } else { 36 | emotes = emotes.slice(-6).reverse(); 37 | } 38 | } else { 39 | emotes = emotes.slice(-6).reverse(); 40 | } 41 | if (!emotes.length) { 42 | return 'monkaS You are going too far now'; 43 | } 44 | 45 | const now = new Date().getTime(); 46 | 47 | for (const emote of emotes) { 48 | emote[2] = `(${tools.humanizeDuration(now - emote[2])})`; 49 | 50 | if (emote[3]) { 51 | emote.pop(); 52 | } 53 | 54 | emote.splice(1, 1); 55 | 56 | } 57 | 58 | emotes = emotes.toString().replaceAll(',', ' '); 59 | 60 | if (input[2]) { 61 | return `Removed emotes page[${input[2]}]: ${emotes}`; 62 | } 63 | return `The latest removed emotes are: ${emotes}`; 64 | 65 | } catch (err) { 66 | console.log(err); 67 | return `FeelsDankMan Sql error: ${err.sqlMessage}`; 68 | } 69 | } 70 | }; -------------------------------------------------------------------------------- /commands/rl.js: -------------------------------------------------------------------------------- 1 | const { got } = require('./../got'); 2 | const tools = require('../tools/tools.js'); 3 | 4 | 5 | module.exports = { 6 | name: 'rl', 7 | ping: false, 8 | description: 'This command will give you a random logged line from either a random user in the chat (Only works if logs are available in the channel, logs used: "https://logs.ivr.fi/"). Example: "bb rl NymN"', 9 | permission: 100, 10 | category: 'Info command', 11 | execute: async (channel, user, input, perm) => { 12 | try { 13 | if (module.exports.permission > perm) { 14 | return; 15 | } 16 | if (input[2]) { 17 | channel = input[2]; 18 | } 19 | 20 | if (channel.match(/@?titleChange_bot,?/gi)) { 21 | return; 22 | } 23 | 24 | const rl = await got(`https://logs.ivr.fi/channel/${channel}/random/?json`).json(); 25 | 26 | if (rl.messages[0].username.match(/@?titleChange_bot,?/gi)) { 27 | return; 28 | } 29 | 30 | let message = tools.splitLine(rl.messages[0].text, 350); 31 | 32 | const timeago = new Date().getTime() - Date.parse(rl.messages[0].timestamp); 33 | 34 | if (rl.status !== 404) { 35 | if (message[1]) { 36 | return `#${channel} ${rl.messages[0].displayName}: ${message[0]}... - (${tools.humanizeDuration(timeago)} ago)`; 37 | } 38 | return `#${channel[0]}\u034f${channel.slice(1)} ${rl.messages[0].displayName}: ${message} - (${tools.humanizeDuration(timeago)} ago)`; 39 | } 40 | 41 | } catch (err) { 42 | console.log(err); 43 | if (err.toString().startsWith('HTTPError: Response code 403 (Forbidden)')) { 44 | return 'User or channel has opted out'; 45 | } 46 | if (err.toString().startsWith('HTTPError: Response code 500 (Internal Server Error)')) { 47 | return 'Could not load logs. Most likely the user either doesn\'t exist or doesn\'t have any logs here.'; 48 | } 49 | if (err.name) { 50 | if (err.name === 'HTTPError') { 51 | return 'No logs available for the user/channel'; 52 | } 53 | return `FeelsDankMan api error: ${err.name}`; 54 | } 55 | return 'FeelsDankMan Error'; 56 | } 57 | } 58 | }; -------------------------------------------------------------------------------- /commands/announce.js: -------------------------------------------------------------------------------- 1 | const { got } = require('./../got'); 2 | const sql = require('./../sql/index.js'); 3 | 4 | module.exports = { 5 | name: 'announce', 6 | ping: true, 7 | description: 'spam announces something', 8 | permission: 1500, 9 | cooldown: 3, //in seconds 10 | category: 'Dev command', 11 | opt_outable: false, 12 | showDelay: false, 13 | noBanphrase: false, 14 | channelSpecific: false, 15 | activeChannel: '', 16 | // eslint-disable-next-line no-unused-vars 17 | execute: async (channel, user, input, perm, aliascommand) => { 18 | try { 19 | if (module.exports.permission > perm) { 20 | return; 21 | } 22 | const channel_uid = (await sql.Query('SELECT uid FROM Streamers WHERE username=?', [channel]))[0].uid.toString(); 23 | 24 | let spamCount = input[2]; 25 | let message = input.slice(); 26 | message.shift(); 27 | message.shift(); 28 | 29 | if (+spamCount) { 30 | message.shift(); 31 | } else { 32 | spamCount = 1; 33 | } 34 | 35 | message = message.join(' '); 36 | 37 | 38 | await Promise.allSettled(Array.from({length: spamCount}) 39 | .fill(message) 40 | .map(async (message) => { 41 | try { 42 | await got.post(`https://api.twitch.tv/helix/chat/announcements?broadcaster_id=${channel_uid}&moderator_id=${process.env.TWITCH_UID}`, { 43 | headers: { 44 | 'client-id': process.env.TWITCH_CLIENTID, 45 | 'Authorization': process.env.TWITCH_AUTH, 46 | 'Content-Type': 'application/json' 47 | }, 48 | json: { 49 | 'message': message, 50 | 'color':'purple' 51 | } 52 | }).json(); 53 | } catch (err) { 54 | console.log(err); 55 | } 56 | })); 57 | 58 | return; 59 | } catch (err) { 60 | console.log(err); 61 | return 'FeelsDankMan Error'; 62 | } 63 | } 64 | }; 65 | -------------------------------------------------------------------------------- /commands/markov.js: -------------------------------------------------------------------------------- 1 | const Markov = require('markov-strings').default; 2 | const redisC = require('../tools/markovLogger.js').redisC; 3 | const tools = require('../tools/tools.js'); 4 | require('dotenv').config(); 5 | 6 | 7 | module.exports = { 8 | name: 'markov', 9 | ping: false, 10 | description: 'Markov', 11 | permission: 100, 12 | cooldown: 120, //in seconds 13 | category: 'Random command', 14 | opt_outable: false, 15 | // eslint-disable-next-line no-unused-vars 16 | execute: async (channel, user, input, perm, aliascommand) => { 17 | try { 18 | if (module.exports.permission > perm) { 19 | return; 20 | } 21 | 22 | input = input.splice(2); 23 | const channelName = input.filter(x => x.startsWith('channel:'))[0]?.split(':')[1] ?? channel; 24 | 25 | const channelID = await tools.getUserID(channelName); 26 | if (!channelID) { 27 | return 'Error: Channel not found'; 28 | } 29 | input = input.filter(x => x !== `channel:${channelName}`); 30 | 31 | let msg = input.join(' '); 32 | let markovStatusCode = 200; 33 | 34 | console.log(msg); 35 | 36 | let result; 37 | 38 | await redisC.get(`Markov:${channelName.toLowerCase()}`, async function (err, reply) { 39 | let jsonData = JSON.parse(reply); 40 | 41 | 42 | const markov = new Markov({ stateSize: 1 }); 43 | 44 | markov.addData(jsonData); 45 | 46 | const options = { 47 | maxTries: 10000, 48 | prng: Math.random, 49 | filter: (result) => { return result.score > 5 } 50 | }; 51 | 52 | result = markov.generate(options); 53 | 54 | console.log("aa: " + result.string) 55 | 56 | return `🔖 ${await tools.unpingString(result.string, channel)}`; 57 | }); 58 | 59 | } catch (err) { 60 | console.log(err); 61 | return 'FeelsDankMan Error'; 62 | } 63 | } 64 | }; 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | .env.save 106 | .env.save.1 107 | 108 | # Jetbrains IDE 109 | *.idea 110 | -------------------------------------------------------------------------------- /tools/spotifyTools.js: -------------------------------------------------------------------------------- 1 | const { got } = require('./../got'); 2 | const sql = require('../sql/index.js'); 3 | require('dotenv').config(); 4 | const client_id = process.env.SPOTIFY_CLIENT_ID; 5 | const client_secret = process.env.SPOTIFY_CLIENT_SECRET; 6 | 7 | exports.refreshToken = async function(uid, refresh_token) { 8 | 9 | let authOptions = { 10 | url: 'https://accounts.spotify.com/api/token', 11 | form: { 12 | refresh_token: refresh_token, 13 | grant_type: 'refresh_token', 14 | }, 15 | headers: { 16 | 'Authorization': 'Basic ' + (Buffer.from(client_id + ':' + client_secret).toString('base64')) 17 | }, 18 | json: true 19 | }; 20 | 21 | const spotifyRefresh = await got.post(authOptions.url, { 22 | headers: authOptions.headers, 23 | form: authOptions.form 24 | }).json(); 25 | 26 | console.log(spotifyRefresh); 27 | 28 | const new_access_token = spotifyRefresh.access_token; 29 | 30 | const expires_in = Date.now() + spotifyRefresh.expires_in * 1000; 31 | 32 | await sql.Query('UPDATE Spotify SET access_token = ?, expires_in = ? WHERE uid = ?', 33 | [new_access_token, expires_in, uid]); 34 | 35 | return new_access_token; 36 | }; 37 | 38 | exports.fetchToken = async function(uid) { 39 | const spotify_user = await sql.Query('SELECT * FROM Spotify WHERE uid = ?',[uid]); 40 | 41 | if (!spotify_user.length){ 42 | return { no_auth: true }; 43 | } 44 | 45 | let access_token = spotify_user[0].access_token; 46 | const refresh_token = spotify_user[0].refresh_token; 47 | const expires_in = spotify_user[0].expires_in; 48 | 49 | if(Date.now() > expires_in) { 50 | try { 51 | access_token = await this.refreshToken(uid, refresh_token); 52 | } catch (err) { 53 | console.log(err); 54 | return { error: 'Failed to refresh token' }; 55 | } 56 | } 57 | 58 | return { opt_in: spotify_user[0].opt_in, access_token: access_token }; 59 | 60 | }; 61 | 62 | exports.millisToMinutesAndSeconds = function(ms) { 63 | let minutes = Math.floor(ms / 60000); 64 | let seconds = ((ms % 60000) / 1000).toFixed(0); 65 | return ( 66 | seconds == 60 ? 67 | (minutes+1) + ':00' : 68 | minutes + ':' + (seconds < 10 ? '0' : '') + seconds); 69 | }; -------------------------------------------------------------------------------- /start.js: -------------------------------------------------------------------------------- 1 | (async () => { 2 | require('dotenv').config(); 3 | const sql = require('./sql/index.js'); 4 | 5 | const sql_opts = { 6 | host: process.env.DB_HOST, 7 | user: process.env.DB_USER, 8 | password: process.env.DB_PASSWORD, 9 | database: process.env.DB_DATABASE, 10 | connectTimeout: 10000, 11 | waitForConnections: true, 12 | connectionLimit: 20, 13 | maxIdle: 10, 14 | idleTimeout: 60000, 15 | queueLimit: 0 16 | }; 17 | 18 | await sql.New(sql_opts); 19 | //await sql.Migrate(); 20 | 21 | const got = require('./got'); 22 | await got.Setup(); 23 | 24 | await require('./connect/connect.js').setupChannels; 25 | 26 | /** @type { Array } */ 27 | const channels = await sql.Query('SELECT username, uid FROM Streamers'); 28 | const { joinChannel } = require('./tools/tools.js'); 29 | 30 | /// TODO: uid are defined as ints in the database, we don't want that, they are defined as strings by twitch themselfes. 31 | /// As such if in the future there would be changes to uid's such as adding a letter or something, we would have a big problem. 32 | /// We should change the uid's to strings in the database. 33 | /// And remove that String casting. 34 | const checkOwner = channels.find(({ uid }) => String(uid) === process.env.TWITCH_OWNERUID); 35 | if (!checkOwner) { 36 | const user = { 37 | username: process.env.TWITCH_OWNER, 38 | uid: process.env.TWITCH_OWNERUID, 39 | }; 40 | 41 | await joinChannel(user, false); 42 | } 43 | 44 | // Check if bot channel is in the database. 45 | if (!channels?.find(({ username }) => username === process.env.TWITCH_USER)) { 46 | const opts = { 47 | username: process.env.TWITCH_USER, 48 | uid: process.env.TWITCH_UID 49 | }; 50 | await joinChannel(opts); 51 | } 52 | 53 | await require('./commands/index.js').Load(); 54 | 55 | const redis = require('./tools/redis.js').Get(); 56 | await redis.Connect(); 57 | await redis.Subscribe('EventSub'); 58 | 59 | require('./tools/markovLogger.js'); 60 | require('./bot.js'); 61 | require('./loops/loops.js'); 62 | //await require('./tools/fetchEmotes.js').STV_emotes; 63 | //require('./tools/fetchEmotes.js').STV_events; 64 | 65 | console.log('Ready!'); 66 | })(); 67 | -------------------------------------------------------------------------------- /web/app.js: -------------------------------------------------------------------------------- 1 | (async function() { 2 | require('dotenv').config(); 3 | const sql = require('./../sql/index.js'); 4 | const Redis = require('./../tools/redis.js'); 5 | const got = require('./../got'); 6 | const cookieParser = require('cookie-parser'); 7 | 8 | await Redis.Get().Connect(); 9 | await got.Setup(); 10 | 11 | const sql_opts = { 12 | host: process.env.DB_HOST, 13 | user: process.env.DB_USER, 14 | password: process.env.DB_PASSWORD, 15 | database: process.env.DB_DATABASE, 16 | connectTimeout: 10000, 17 | }; 18 | 19 | await sql.New(sql_opts); 20 | 21 | const port = 3000; 22 | 23 | const routes = [ 24 | { router: 'commands', path: '/' }, 25 | { router: 'suggestions', path: '/suggestions'}, 26 | { router: 'eventsub', path: '/eventsub' }, 27 | { router: 'spotify/callback', path: '/spotify/callback' }, 28 | { router: 'spotify/login', path: '/spotify/login'}, 29 | { router: 'newemotes', path: '/newemotes/nymn' }, 30 | { router: 'pets', path: '/pets/yabbe' }, 31 | { router: 'twitch/callback', path: '/twitch/callback' }, 32 | { router: 'twitch/login', path: '/twitch/login'}, 33 | { router: 'music', path: '/music'}, 34 | { router: 'resolved', path: '/resolved'}, 35 | { router: 'twitch/authCallback', path: '/twitch/auth/callback' }, 36 | { router: 'twitch/auth', path: '/twitch/auth'}, 37 | { router: 'twitch/success', path: '/twitch/success'}, 38 | ]; 39 | 40 | const express = require('express'); 41 | const favicon = require('express-favicon'); 42 | const { join } = require('path'); 43 | 44 | const app = express(); 45 | 46 | app.use(favicon(join(__dirname, 'public/img/LETSPEPE.png'))); 47 | app.use(express.static('./public')); 48 | app.use(cookieParser()); 49 | app.use('/css', express.static(join(__dirname, 'public/css'))); 50 | app.use('/js', express.static(join(__dirname, 'public/js'))); 51 | 52 | app.set('views', join(__dirname, 'views')); 53 | app.set('view engine', 'ejs'); 54 | 55 | for (const route of routes) { 56 | app.use(route.path, await require(`./routes/${route.router}`)); 57 | } 58 | 59 | app.get('*', (req, res) => res.sendStatus(404).end()); 60 | 61 | app.listen(port, () => console.log(`Listening on port ${port}`)); 62 | })(); -------------------------------------------------------------------------------- /commands/botsubs.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const { got } = require('./../got'); 3 | 4 | module.exports = { 5 | name: 'botsubs', 6 | ping: true, 7 | description: 'This command will return random sub-emotes from channels the bot is subbed to', 8 | permission: 100, 9 | category: 'Info command', 10 | execute: async (channel, user, input, perm) => { 11 | try { 12 | if (module.exports.permission > perm) { 13 | return; 14 | } 15 | if (!process.env.THREELETTERAPI_CLIENTID) { 16 | return 'FeelsDankMan Error: THREELETTERAPI_CLIENTID isn`t set'; 17 | } 18 | 19 | let query = ` 20 | query { 21 | user(id: "${process.env.TWITCH_UID}") { 22 | subscriptionBenefits(criteria: {}) { 23 | edges { 24 | node { 25 | user { 26 | subscriptionProducts { 27 | tier, 28 | emotes { 29 | token 30 | } 31 | } 32 | } 33 | } 34 | } 35 | } 36 | } 37 | }`; 38 | 39 | let ThreeLetterApiCall = await got.post('https://gql.twitch.tv/gql', { 40 | headers: { 41 | 'Client-ID': process.env.THREELETTERAPI_CLIENTID, 42 | 'Authorization': process.env.THREELETTERAPI_AUTH, 43 | 'Content-Type': 'application/json', 44 | }, 45 | body: JSON.stringify({ 46 | query: query 47 | }), 48 | }).json(); 49 | 50 | 51 | if (!ThreeLetterApiCall.data.user) { 52 | return 'FeelsDankMan Something went wrong!'; 53 | } 54 | 55 | console.log(ThreeLetterApiCall); 56 | let emotes = ThreeLetterApiCall. 57 | data. 58 | user. 59 | subscriptionBenefits. 60 | edges. 61 | map(x => x.node.user.subscriptionProducts[0].emotes[~~(Math.random() * x.node.user.subscriptionProducts[0].emotes.length)].token); 62 | 63 | console.log(emotes); 64 | return emotes.join(' '); 65 | } catch (err) { 66 | console.log(err); 67 | return `FeelsDankMan Sql error: ${err.sqlMessage}`; 68 | } 69 | } 70 | }; -------------------------------------------------------------------------------- /web/views/music.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Music 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 |

Botbear Spotify integration

18 | Spotify Logo 19 |
20 | <%if (!cookieToken) { %> 21 |
22 |

Step 1:

23 | (Redirects to twitch.tv) 24 | Log in with twitch 25 |
26 | <% if (query.error){ %> 27 |
28 |

Error: <%= query.error%>!

29 |
30 | <% } %> 31 | 32 | <% } else { %> 33 | <% const username=userInfo.username; %> 34 | <% const uid=userInfo.uid; %> 35 | <% const logo=userInfo.logo; %> 36 |
37 |

Step 1:

38 | Twitch 39 | linked! 40 |
41 |
42 | User logo 43 |

<%= username%>

44 |
45 |
46 |

Step 2:

47 | (Redirects to spotify.com) 48 | Link 49 | spotify account 50 |
51 | <% if (query.error){ %> 52 |
53 |

Error: <%= query.error%>!

54 |
55 | <% } %> 56 | <% } %> 57 |
58 |
59 |
60 | 61 | 62 | -------------------------------------------------------------------------------- /commands/thumbnail.js: -------------------------------------------------------------------------------- 1 | const { got } = require('./../got'); 2 | 3 | module.exports = { 4 | name: 'thumbnail', 5 | ping: true, 6 | description: 'Will return the latest thumbnail image of a given streamer', 7 | permission: 100, 8 | cooldown: 3, //in seconds 9 | category: 'Random command', 10 | opt_outable: false, 11 | showDelay: false, 12 | noBanphrase: true, 13 | channelSpecific: false, 14 | activeChannel: '', 15 | // eslint-disable-next-line no-unused-vars 16 | execute: async (channel, user, input, perm, aliascommand) => { 17 | try { 18 | if (module.exports.permission > perm) { 19 | return; 20 | } 21 | 22 | if (!process.env.THREELETTERAPI_CLIENTID) { 23 | return 'FeelsDankMan Error: THREELETTERAPI_CLIENTID isn`t set'; 24 | } 25 | 26 | const realchannel = input[2] ?? channel; 27 | 28 | let thumbnail = (await got(`https://static-cdn.jtvnw.net/previews-ttv/live_user_${realchannel}.jpg`)).url; 29 | 30 | if (thumbnail === "https://static-cdn.jtvnw.net/ttv-static/404_preview.jpg") { 31 | const query = ` 32 | query { 33 | user(login:"${realchannel}") { 34 | videoShelves { 35 | edges { 36 | node { 37 | items { 38 | ...on Video { 39 | previewThumbnailURL(width:0 height:0) 40 | } 41 | } 42 | } 43 | } 44 | } 45 | } 46 | }`; 47 | 48 | const ThreeLetterApiCall = await got.post('https://gql.twitch.tv/gql', { 49 | headers: { 50 | 'Client-ID': process.env.THREELETTERAPI_CLIENTID, 51 | 'Content-Type': 'application/json', 52 | }, 53 | body: JSON.stringify({ 54 | query: query 55 | }), 56 | }).json(); 57 | 58 | if(!ThreeLetterApiCall.data.user.videoShelves) { 59 | return `FeelsDankMan That user does not exist` 60 | } 61 | 62 | if (!ThreeLetterApiCall["data"]["user"]["videoShelves"]["edges"].length) { 63 | return `FeelsDankMan That user is not live and does not have any VODs`; 64 | } 65 | 66 | thumbnail = ThreeLetterApiCall["data"]["user"]["videoShelves"]["edges"][1]["node"]["items"][0]["previewThumbnailURL"]; 67 | 68 | return '[OFFLINE] | VOD Thumbnail: ' + thumbnail; 69 | } 70 | 71 | thumbnail += '?cum'; 72 | 73 | const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 74 | 75 | for (let i = 0; i < 5; i++) { 76 | thumbnail += possible.charAt(Math.floor(Math.random() * possible.length)); 77 | } 78 | 79 | return thumbnail; 80 | } catch (err) { 81 | console.log(err); 82 | return 'FeelsDankMan Error'; 83 | } 84 | } 85 | }; 86 | -------------------------------------------------------------------------------- /commands/mute.js: -------------------------------------------------------------------------------- 1 | const redis = require('./../tools/redis.js'); 2 | const { got } = require('./../got'); 3 | const twitchAuth = require('../tools/twitchAuth.js'); 4 | const sql = require('./../sql/index.js'); 5 | 6 | module.exports = { 7 | name: 'mute', 8 | ping: true, 9 | description: 'Mutes bot notifications for x minutes. Default is 5 min', 10 | permission: 100, 11 | cooldown: 3, //in seconds 12 | category: 'Notify command', 13 | opt_outable: false, 14 | showDelay: false, 15 | noBanphrase: true, 16 | channelSpecific: false, 17 | activeChannel: '', 18 | // eslint-disable-next-line no-unused-vars 19 | execute: async (channel, user, input, perm, aliascommand) => { 20 | try { 21 | if (module.exports.permission > perm) { 22 | return; 23 | } 24 | const isMod = user.mod || user['user-type'] === 'mod'; 25 | const isBroadcaster = channel.toLowerCase() === user.username.toLowerCase(); 26 | 27 | if (!isMod && !isBroadcaster && perm < 2000) { 28 | return; 29 | } 30 | 31 | let min = input[2]?.replaceAll('m', '') ?? '5'; 32 | 33 | if (min.startsWith('-') || min === '0') { 34 | return '2nd input can\'t be negative or 0'; 35 | 36 | } 37 | let isnumber = parseInt(min); 38 | if (isNaN(isnumber)) { 39 | return '2nd input should be a number'; 40 | } 41 | 42 | let duration = min * 60 * 1000; 43 | 44 | let unmuteTime = Date.now() + duration; 45 | 46 | const mute = await redis.Get().Set(`${channel}:unmute_time`, unmuteTime); 47 | mute(duration / 1000); 48 | 49 | if (channel === 'nymn') { 50 | 51 | const twitch_user = await twitchAuth.fetchToken(process.env.TWITCH_UID); 52 | 53 | if (twitch_user.error) { 54 | return 'Something went wrong when refreshing user token DinkDonk @HotBear1110'; 55 | } 56 | 57 | if (twitch_user.no_auth) { 58 | return 'Bot is not authorized DinkDonk @HotBear1110'; 59 | } 60 | 61 | 62 | const access_token = twitch_user.access_token; 63 | 64 | await got.post(`https://api.twitch.tv/helix/moderation/bans?broadcaster_id=62300805&moderator_id=${process.env.TWITCH_UID}`, { 65 | headers: { 66 | 'client-id': process.env.TWITCH_CLIENTID, 67 | 'Authorization': 'Bearer ' + access_token, 68 | 'Content-Type': 'application/json' 69 | }, 70 | json: { 71 | 'data': { 72 | 'user_id': '268612479', 73 | 'duration': min * 60, 74 | 'reason': 'Muted for notification spam' 75 | } 76 | } 77 | } ).json(); 78 | } 79 | 80 | return `Successfully muted notifications for ${min} min`; 81 | } catch (err) { 82 | console.log(err); 83 | return 'FeelsDankMan Error'; 84 | } 85 | } 86 | }; -------------------------------------------------------------------------------- /configsetup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export NPM_CONFIG_LOGLEVEL="silent npm version" 3 | printf "Your bot's Twitch username: " 4 | read TWITCH_USER 5 | printf "TWITCH_USER=${TWITCH_USER}" >> .env 6 | 7 | printf "\nYour bot's Twitch OAuth: " 8 | read TWITCH_PASSWORD 9 | printf "\nTWITCH_PASSWORD=${TWITCH_PASSWORD}" >> .env 10 | 11 | printf "\nYour bot's Twitch user ID: " 12 | read TWITCH_UID 13 | printf "\nTWITCH_UID=${TWITCH_UID}" >> .env 14 | 15 | printf "\nChoose a command prefix for your bot: " 16 | read TWITCH_PREFIX 17 | printf "\nTWITCH_PREFIX=${TWITCH_PREFIX}" >> .env 18 | 19 | printf "\nBot's owner Twitch username: " 20 | read TWITCH_OWNERNAME 21 | printf "\nTWITCH_OWNERNAME=${TWITCH_OWNERNAME}" >> .env 22 | 23 | printf "\nBot's owner Twitch user ID: " 24 | read TWITCH_OWNERUID 25 | printf "\nTWITCH_OWNERUID=${TWITCH_OWNERUID}" >> .env 26 | 27 | printf "\nBot's client ID: " 28 | read TWITCH_CLIENTID 29 | printf "\nTWITCH_CLIENTID=${TWITCH_CLIENTID}" >> .env 30 | 31 | printf "\nBot's Twitch bearer token: " 32 | read TWITCH_AUTH 33 | printf "\nTWITCH_AUTH=${TWITCH_AUTH}" >> .env 34 | 35 | printf "\nBot's Twitch secret token: " 36 | read TWITCH_SECRET 37 | printf "\nTWITCH_SECRET=${TWITCH_SECRET}" >> .env 38 | 39 | printf "\nDatabase name: " 40 | read DB_DATABASE 41 | printf "\nDB_DATABASE=${DB_DATABASE}" >> .env 42 | 43 | printf "\nDatabase IP address (press enter for localhost): " 44 | read DB_HOST 45 | if [ -z "$DB_HOST" ] 46 | then 47 | DB_HOST="127.0.0.1" 48 | fi 49 | printf "\nDB_HOST=${DB_HOST}" >> .env 50 | 51 | printf "\nDatabase username (press enter for root): " 52 | read DB_USER 53 | if [ -z "$DB_USER" ] 54 | then 55 | DB_USER="root" 56 | fi 57 | printf "\nDB_USER=${DB_USER}" >> .env 58 | 59 | printf "\nDatabase password (press enter for empty): " 60 | read DB_PASSWORD 61 | if [ -z "$DB_PASSWORD" ] 62 | then 63 | DB_PASSWORD="" 64 | fi 65 | printf "\nDB_PASSWORD=${DB_PASSWORD}" >> .env 66 | 67 | printf "\nSupinic bot alive check user ID (press enter to skip): " 68 | read SUPI_USERID 69 | if [ -z "$SUPI_USERID" ] 70 | then 71 | SUPI_USERID="" 72 | fi 73 | printf "\nSUPI_USERID=${SUPI_USERID}" >> .env 74 | 75 | printf "\nSupinic bot alive check auth token (press enter to skip): " 76 | read SUPI_AUTH 77 | if [ -z "$SUPI_AUTH" ] 78 | then 79 | SUPI_AUTH="" 80 | fi 81 | printf "\nSUPI_AUTH=${SUPI_AUTH}" >> .env 82 | 83 | printf "\nRedis address (in a format of redis://username:password@ip:6379/db) (press enter to skip): " 84 | read REDIS_ADDRESS 85 | if [ -z "$REDIS_ADDRESS" ] 86 | then 87 | REDIS_ADDRESS="" 88 | fi 89 | printf "\nREDIS_ADDRESS=${REDIS_ADDRESS}" >> .env 90 | 91 | printf "\nOpenai API key (press enter to skip): " 92 | read OPENAI_API_KEY 93 | if [ -z "$OPENAI_API_KEY" ] 94 | then 95 | OPENAI_API_KEY="" 96 | fi 97 | printf "\nOPENAI_API_KEY=${OPENAI_API_KEY}" >> .env -------------------------------------------------------------------------------- /commands/emotes.js: -------------------------------------------------------------------------------- 1 | const tools = require('../tools/tools.js'); 2 | const sql = require('./../sql/index.js'); 3 | const fetchEmote = require('../tools/fetchEmotes.js'); 4 | 5 | module.exports = { 6 | name: 'emotes', 7 | ping: true, 8 | description: 'This command will give you a list of the last 6 added 3rd part emotes.', 9 | permission: 100, 10 | category: 'Info command', 11 | noBanphrase: true, 12 | execute: async (channel, user, input, perm) => { 13 | try { 14 | if (module.exports.permission > perm) { 15 | return; 16 | } 17 | 18 | this.channel = input.filter(x => x.startsWith('channel:'))[0]?.split(':')[1] ?? channel; 19 | input = input.filter(x => x !== `channel:${this.channel}`); 20 | 21 | /* 22 | TEMP SOLUTION UNTILL THE NEW EVENTSOURCE IS FINISHED 23 | */ 24 | 25 | let emote_list = [] 26 | 27 | try { 28 | const uid = await tools.getUserID(this.channel); 29 | 30 | if (!uid) { 31 | return 'Unable to find the channel #' + this.channel; 32 | } 33 | 34 | emote_list = JSON.parse((await fetchEmote.STV_user_emotes(uid)).emote_list).reverse(); 35 | } catch { 36 | console.log(err); 37 | } 38 | 39 | let emotes = emote_list; 40 | 41 | /* 42 | 43 | try { 44 | this.streamer = await sql.Query(`SELECT emote_list FROM Streamers WHERE username="${this.channel}"`) ?? ''; 45 | } catch(err) { 46 | console.log(err); 47 | this.streamer = ''; 48 | } 49 | 50 | if (!this.streamer.length) { 51 | return 'I don\'t have an emote list for that channel.'; 52 | } 53 | let emotes = JSON.parse(this.streamer[0].emote_list).reverse(); 54 | 55 | */ 56 | 57 | if (!emotes.length) { 58 | return 'there are no 3rd party emotes in this channel.'; 59 | } 60 | 61 | let index = input[2] || '1'; 62 | 63 | if (index.startsWith('-') || index === '0') { 64 | return '2nd input can\'t be negative or 0'; 65 | } 66 | let isnumber = !isNaN(index); 67 | if (!isnumber) { 68 | return '2nd input should be a number'; 69 | } 70 | 71 | emotes = emotes.slice((6*(index-1)), (6*index)); 72 | 73 | if (!emotes.length) { 74 | return 'monkaS You are going too far now'; 75 | } 76 | 77 | const now = new Date().getTime(); 78 | 79 | const responseList = [] 80 | 81 | for (const emote of emotes) { 82 | responseList.push(`${emote.name} (${tools.humanizeDuration(now - emote.time_added)})`) 83 | } 84 | 85 | emotes = responseList.join(' '); 86 | 87 | if (input[2]) { 88 | return `Added emotes ${(this.channel === channel) ? '' : `in #${this.channel}`} page[${input[2]}]: ${emotes}`; 89 | } 90 | return `the latest added emotes ${(this.channel === channel) ? '' : `in #${this.channel}`} are: ${emotes}`; 91 | 92 | } catch (err) { 93 | console.log(err); 94 | return `FeelsDankMan Sql error: ${err.sqlMessage}`; 95 | } 96 | } 97 | }; 98 | -------------------------------------------------------------------------------- /web/routes/spotify/callback.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const querystring = require('querystring'); 3 | const { got } = require('../../../got'); 4 | 5 | module.exports = (function () { 6 | const sql = require('../../../sql/index.js'); 7 | const router = require('express').Router(); 8 | 9 | const client_id = process.env.SPOTIFY_CLIENT_ID; 10 | const client_secret = process.env.SPOTIFY_CLIENT_SECRET; 11 | const redirect_uri = 'https://hotbear.org/spotify/callback'; 12 | 13 | /* /Callback */ 14 | router.get('/', async (req, res) => { 15 | let code = req.query.code || null; 16 | let state = req.query.state || null; 17 | 18 | if (req.query.error === 'access_denied') { 19 | res.redirect('../music?error=access_denied'); 20 | return router; 21 | } 22 | 23 | let cookies = req.cookies || ''; 24 | 25 | let cookieToken = cookies.token; 26 | 27 | if (!cookieToken) { 28 | res.redirect('../music'); 29 | return router; 30 | } 31 | 32 | const hasToken = await sql.Query('SELECT * FROM Spotify WHERE cookieToken = ?', [cookieToken]); 33 | 34 | if (!hasToken.length) { 35 | res.redirect('../music'); 36 | return router; 37 | } 38 | 39 | if (state) { 40 | let authOptions = { 41 | url: 'https://accounts.spotify.com/api/token', 42 | form: { 43 | code: code, 44 | redirect_uri: redirect_uri, 45 | grant_type: 'authorization_code' 46 | }, 47 | headers: { 48 | 'Authorization': 'Basic ' + (Buffer.from(client_id + ':' + client_secret).toString('base64')) 49 | }, 50 | json: true 51 | }; 52 | let spotifyToken; 53 | try { 54 | spotifyToken = await got.post(authOptions.url, { 55 | headers: authOptions.headers, 56 | form: authOptions.form, 57 | }).json(); 58 | 59 | } catch(err) { 60 | res.redirect('../music?error=api_error'); 61 | return router; 62 | } 63 | 64 | 65 | const expires_in = Date.now() + spotifyToken.expires_in * 1000; 66 | 67 | await sql.Query('UPDATE Spotify SET state = ?, access_token = ?, expires_in = ?, refresh_token = ? WHERE cookieToken = ? ', [state, spotifyToken.access_token, expires_in, spotifyToken.refresh_token, cookieToken]); 68 | 69 | 70 | res.redirect('/resolved?' + 71 | querystring.stringify({ 72 | state: state 73 | }) 74 | ); 75 | } else { 76 | res.redirect('/#' + 77 | querystring.stringify({ 78 | error: 'state_mismatch' 79 | })); } 80 | }); 81 | 82 | return router; 83 | })(); 84 | -------------------------------------------------------------------------------- /web/views/pets.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | pets 9 | 10 | 11 | 12 | 13 |
14 |

Chat's Pets

15 |

This is a collection of images of chat's pets.

16 |

You can add your own pets either by submitting them on the discord, asking @NinaTurbo or by using the command: bb pet add

17 |

Example: bb pet add user:"yabbe" pet:"Cat" name:"Minus" link:"Link To Image"'

18 |
19 |
20 | 21 |
22 | 23 | <% pets.forEach(function(pet) { %> 24 |
25 |
26 | <% if (pet.Image.endsWith(".mp4")) { %> 27 | 30 | <% } else { %> 31 | 32 | <% } %> 33 |
Pet Name: <%-pet.Pet_name %>
34 |
Pet: <%-pet.Pet %>
35 |
User: <%-pet.User %>
36 |
37 |
38 | <% }) %> 39 | 40 |
41 | 42 |
43 | <% if (pages > 0) { %> 44 | 70 | <% } %> 71 | 72 | 73 | -------------------------------------------------------------------------------- /commands/uid.js: -------------------------------------------------------------------------------- 1 | const { got } = require('./../got'); 2 | require('dotenv').config(); 3 | 4 | module.exports = { 5 | name: 'uid', 6 | ping: true, 7 | description: 'This command will give you the user-id of a specified user. Example: "bb uid NymN"', 8 | permission: 100, 9 | category: 'Info command', 10 | execute: async (channel, user, input, perm) => { 11 | let response = ''; 12 | try { 13 | if (module.exports.permission > perm) { 14 | return; 15 | } 16 | let uiduser = user.username; 17 | 18 | let userID = ''; 19 | 20 | if (input[2]) { 21 | if (input[2].startsWith('@')) { 22 | input[2] = input[2].substring(1); 23 | } 24 | try { 25 | const userData = await got(`https://api.twitch.tv/helix/users?id=${input[2]}`, { 26 | headers: { 27 | 'client-id': process.env.TWITCH_CLIENTID, 28 | 'Authorization': process.env.TWITCH_AUTH 29 | } 30 | }).json(); 31 | if (userData.data) { 32 | uiduser = userData.data[0]; 33 | uiduser = uiduser['login']; 34 | const uidBanned = await got(`https://api.ivr.fi/v2/twitch/user/${uiduser}`).json(); 35 | if (uidBanned.banned === true) { 36 | response = `Username found: ${uiduser} - nymnS [BANNED USER] | Type: ${uidBanned.banReason}`; 37 | } else { 38 | response = `Username found: ${uiduser}`; 39 | } 40 | } 41 | } catch (err) { 42 | uiduser = input[2]; 43 | } 44 | 45 | userID = await got(`https://api.ivr.fi/v2/twitch/user?login=${input[2]}`).json(); 46 | 47 | } else { 48 | userID = await got(`https://api.ivr.fi/v2/twitch/user?login=${uiduser}`).json(); 49 | } 50 | 51 | if (response.length) { 52 | if (userID.status === 404 || userID === '') { 53 | return response; 54 | } 55 | 56 | userID = userID[0]; 57 | 58 | if (userID.banned === true) { 59 | response = `Multiple users found. ${response} | User-ID found: ${userID['id']} - nymnS [BANNED USER] | Type: ${userID.banReason}`; 60 | } else { 61 | response = `Multiple users found. ${response} | User-ID found: ${userID['id']}`; 62 | } 63 | } else { 64 | if (userID.statusCode === 404) { 65 | return 'No users found'; 66 | } 67 | 68 | userID = userID[0]; 69 | 70 | if (userID.banned === true) { 71 | response = `User-ID found: ${userID.id} - nymnS [BANNED USER] | Type: ${userID.banReason}`; 72 | } else { 73 | response = `User-ID found: ${userID.id}`; 74 | } 75 | } 76 | 77 | return response; 78 | 79 | } catch (err) { 80 | console.log(err); 81 | if (response.length) { 82 | return response; 83 | } 84 | if (err.name === 'TimeoutError') { 85 | return `FeelsDankMan api error: ${err.name}`; 86 | } 87 | if (err.response.statusCode === 404) { 88 | return 'That user does not exist'; 89 | } 90 | return `FeelsDankMan Error: ${err.response.error}`; 91 | } 92 | } 93 | }; 94 | -------------------------------------------------------------------------------- /commands/dalle.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const { got } = require('./../got'); 3 | let messageHandler = require('../tools/messageHandler.js').messageHandler; 4 | const sql = require('./../sql/index.js'); 5 | 6 | module.exports = { 7 | name: 'dalle', 8 | ping: true, 9 | description: 'Make any image you can think of using the power of dalle2 ai! - The cooldown on this is 1 hour.', 10 | permission: 100, 11 | cooldown: 3600, 12 | category: 'Random command', 13 | execute: async (channel, user, input, perm) => { 14 | try { 15 | const { FormData, Blob } = (await import('formdata-node')); 16 | 17 | if (module.exports.permission > perm) { 18 | return; 19 | } 20 | //temp change 21 | if (![62300805, 135186096, 11148817, 84180052].includes(+user['room-id']) && !(perm >= 1500)) { 22 | return 'This command is currently disabled :)'; 23 | } 24 | 25 | if (!input[2]) { 26 | return 'FeelsDankMan No input text'; 27 | } 28 | 29 | input = input.splice(2); 30 | let msg = input.join(' '); 31 | 32 | const url = 'https://api.openai.com/v1/images/generations'; 33 | const params = { 34 | 'prompt': msg, 35 | 'n': 1, 36 | 'size': '1024x1024', 37 | 'user': user.username 38 | }; 39 | const headers = { 40 | 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`, 41 | 'Content-Type': 'application/json' 42 | }; 43 | 44 | 45 | 46 | 47 | try { 48 | 49 | await new messageHandler(channel, user.username + ' ppCircle generating dalle2 image...', true).newMessage(); 50 | 51 | 52 | const response = await got.post(url, { json: params, headers: headers }).json(); 53 | 54 | const dalleImageToBlob = async (url) => 55 | new Blob([await got(url).buffer()], { type: 'image/png' }); 56 | 57 | const imgurl = response.data[0].url; 58 | const img = await dalleImageToBlob(imgurl); 59 | 60 | const formData = new FormData(); 61 | formData.append('file', img, 'file.png'); 62 | 63 | 64 | const imageURL = await got.post('http://localhost:9005', { 65 | headers: { 66 | 'Authorization': process.env.hotbearIMG, 67 | }, 68 | body: formData, 69 | throwHttpErrors: false 70 | }); 71 | 72 | console.log(imageURL); 73 | 74 | await sql.Query(`INSERT INTO Dalle 75 | (User, Channel, Prompt, Image) 76 | values 77 | (?, ?, ?, ?)`, 78 | [user.username, channel, msg, imageURL.body] 79 | ); 80 | 81 | return `"${msg}": ${imageURL.body}`; 82 | } catch (err) { 83 | console.log(err); 84 | if (err.response.statusCode === 429) { 85 | return 'nymnIme I\'m currently rate limited. Try again in 5 min'; 86 | } 87 | if (err.response.statusCode === 400) { 88 | return 'nymnIme Bad request..... Maybe read up on the rules: https://labs.openai.com/policies/content-policy'; 89 | } 90 | } 91 | } catch (err) { 92 | console.log(err); 93 | return 'FeelsDankMan Error'; 94 | } 95 | } 96 | }; 97 | -------------------------------------------------------------------------------- /commands/suball.js: -------------------------------------------------------------------------------- 1 | const { got } = require('./../got'); 2 | const sql = require('./../sql/index.js'); 3 | 4 | module.exports = { 5 | name: 'suball', 6 | ping: false, 7 | description: 'This command is for subbing to channel.update, stream.online and stream.offline for all streamers in db', 8 | permission: 2000, 9 | category: 'Dev command', 10 | execute: async (channel, user, input, perm) => { 11 | try { 12 | if (module.exports.permission > perm) { 13 | return; 14 | } 15 | const streamers = await sql.Query(` 16 | SELECT uid 17 | FROM Streamers`); 18 | 19 | 20 | for (let i = 0; i < streamers.length; i++) { 21 | setTimeout(async function () { 22 | let uid = streamers[i].uid; 23 | 24 | let data = JSON.stringify({ 25 | 'type': 'channel.update', 26 | 'version': '1', 27 | 'condition': { 'broadcaster_user_id': uid.toString() }, 28 | 'transport': { 'method': 'webhook', 'callback': 'https://hotbear.org/eventsub', 'secret': process.env.TWITCH_SECRET } 29 | }); 30 | console.log(uid); 31 | await got.post('https://api.twitch.tv/helix/eventsub/subscriptions', { 32 | headers: { 33 | 'client-id': process.env.TWITCH_CLIENTID, 34 | 'Authorization': process.env.TWITCH_AUTH, 35 | 'Content-Type': 'application/json' 36 | }, 37 | body: data 38 | }); 39 | }, 100 * i); 40 | 41 | setTimeout(async function () { 42 | let uid = streamers[i].uid; 43 | 44 | let data = JSON.stringify({ 45 | 'type': 'stream.online', 46 | 'version': '1', 47 | 'condition': { 'broadcaster_user_id': uid.toString() }, 48 | 'transport': { 'method': 'webhook', 'callback': 'https://hotbear.org/eventsub', 'secret': process.env.TWITCH_SECRET } 49 | }); 50 | console.log(uid); 51 | await got.post('https://api.twitch.tv/helix/eventsub/subscriptions', { 52 | headers: { 53 | 'client-id': process.env.TWITCH_CLIENTID, 54 | 'Authorization': process.env.TWITCH_AUTH, 55 | 'Content-Type': 'application/json' 56 | }, 57 | body: data 58 | }); 59 | }, 100 * i); 60 | 61 | setTimeout(async function () { 62 | let uid = streamers[i].uid; 63 | 64 | let data = JSON.stringify({ 65 | 'type': 'stream.offline', 66 | 'version': '1', 67 | 'condition': { 'broadcaster_user_id': uid.toString() }, 68 | 'transport': { 'method': 'webhook', 'callback': 'https://hotbear.org/eventsub', 'secret': process.env.TWITCH_SECRET } 69 | }); 70 | console.log(uid); 71 | await got.post('https://api.twitch.tv/helix/eventsub/subscriptions', { 72 | headers: { 73 | 'client-id': process.env.TWITCH_CLIENTID, 74 | 'Authorization': process.env.TWITCH_AUTH, 75 | 'Content-Type': 'application/json' 76 | }, 77 | body: data 78 | }); 79 | }, 100 * i); 80 | } 81 | return 'Okayge done!!'; 82 | } catch (err) { 83 | console.log(err); 84 | return 'FeelsDankMan Error'; 85 | } 86 | } 87 | }; -------------------------------------------------------------------------------- /web/routes/twitch/authCallback.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const querystring = require('querystring'); 3 | const { got } = require('../../../got'); 4 | 5 | module.exports = (function () { 6 | const sql = require('../../../sql/index.js'); 7 | const router = require('express').Router(); 8 | 9 | const client_id = process.env.TWITCH_CLIENTID; 10 | const client_secret = process.env.TWITCH_SECRET; 11 | 12 | /* /Callback */ 13 | router.get('/', async (req, res) => { 14 | let code = req.query.code; 15 | let state = req.query.state || null; 16 | 17 | 18 | if (state) { 19 | let userAuthOptions = { 20 | url: 'https://id.twitch.tv/oauth2/token?', 21 | params: querystring.stringify({ 22 | client_id: client_id, 23 | client_secret: client_secret, 24 | code: code, 25 | grant_type: 'authorization_code', 26 | redirect_uri: 'https://hotbear.org/twitch/auth/callback' 27 | }) 28 | }; 29 | 30 | 31 | let userAuth; 32 | try { 33 | userAuth = await got.post(userAuthOptions.url + userAuthOptions.params).json(); 34 | } catch (err) { 35 | console.log(err); 36 | return router; 37 | } 38 | 39 | let authOptions = { 40 | url: 'https://api.twitch.tv/helix/users', 41 | headers: { 42 | 'Authorization': 'Bearer ' + userAuth.access_token, 43 | 'Client-Id': client_id 44 | } 45 | }; 46 | 47 | let twitchRequest; 48 | 49 | try { 50 | 51 | twitchRequest = await got(authOptions.url, { 52 | headers: authOptions.headers, 53 | form: authOptions.form, 54 | }).json(); 55 | 56 | } catch (err) { 57 | console.log(err); 58 | return router; 59 | } 60 | 61 | 62 | const hasID = await sql.Query('SELECT * FROM Auth_users WHERE uid = ?', [twitchRequest.data[0].id]); 63 | 64 | const expires_in = Date.now() + userAuth.expires_in; 65 | 66 | if (hasID.length) { 67 | await sql.Query('UPDATE Auth_users SET access_token = ?, refresh_token = ?, expires_in = ? WHERE uid = ? ', [userAuth.access_token, userAuth.refresh_token, expires_in, twitchRequest.data[0].id]); 68 | 69 | res.redirect('../success'); 70 | 71 | return router; 72 | } 73 | 74 | await sql.Query(`INSERT INTO Auth_users 75 | (uid, access_token, refresh_token, expires_in) 76 | values 77 | (?, ?, ?, ?)`, 78 | [twitchRequest.data[0].id, userAuth.access_token, userAuth.refresh_token, expires_in] 79 | ); 80 | 81 | res.redirect('../success'); 82 | 83 | } else { 84 | res.redirect('/#' + 85 | querystring.stringify({ 86 | error: 'state_mismatch' 87 | })); } 88 | }); 89 | 90 | return router; 91 | })(); -------------------------------------------------------------------------------- /web/public/js/script.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | let Nom_reversed = false; 4 | let Viewers_reversed = false; 5 | let Nymn_reversed = false; 6 | 7 | function reverseNominations() { 8 | let table = document.getElementById('Nomination-emoteSort'), 9 | newTbody = document.createElement('tbody'), 10 | oldTbody = table.tBodies[0], 11 | rows = oldTbody.rows, 12 | i = rows.length - 2; 13 | newTbody.appendChild(rows[0]); 14 | console.log(newTbody); 15 | while (i >= 0) { 16 | newTbody.appendChild(rows[i]); 17 | i -= 1; 18 | } 19 | 20 | oldTbody.parentNode.replaceChild(newTbody, oldTbody); 21 | 22 | Nom_reversed = (Nom_reversed) ? false : true; 23 | let sorted = document.getElementById('Nomination-sorted'); 24 | let Sort = document.getElementById('Nomination-sort-button'); 25 | if (!Nom_reversed) { 26 | sorted.innerText = 'Sorted by: New'; 27 | Sort.innerText = 'Date added ▼'; 28 | } else { 29 | sorted.innerText = 'Sorted by: Old'; 30 | Sort.innerText = 'Date added ▲'; 31 | } 32 | } 33 | 34 | function reverseViewers() { 35 | let table = document.getElementById('Viewers-emoteSort'), 36 | newTbody = document.createElement('tbody'), 37 | oldTbody = table.tBodies[0], 38 | rows = oldTbody.rows, 39 | i = rows.length - 2; 40 | newTbody.appendChild(rows[0]); 41 | while (i >= 0) { 42 | newTbody.appendChild(rows[i]); 43 | i -= 1; 44 | } 45 | 46 | oldTbody.parentNode.replaceChild(newTbody, oldTbody); 47 | 48 | Viewers_reversed = (Viewers_reversed) ? false : true; 49 | let sorted = document.getElementById('Viewers-sorted'); 50 | let Sort = document.getElementById('Viewers-sort-button'); 51 | if (!Viewers_reversed) { 52 | sorted.innerText = 'Sorted by: New'; 53 | Sort.innerText = 'Date added ▼'; 54 | } else { 55 | sorted.innerText = 'Sorted by: Old'; 56 | Sort.innerText = 'Date added ▲'; 57 | } 58 | } 59 | 60 | function reverseNymnMods() { 61 | let table = document.getElementById('Nymn/mods-emoteSort'), 62 | newTbody = document.createElement('tbody'), 63 | oldTbody = table.tBodies[0], 64 | rows = oldTbody.rows, 65 | i = rows.length - 2; 66 | newTbody.appendChild(rows[0]); 67 | console.log(newTbody); 68 | while (i >= 0) { 69 | newTbody.appendChild(rows[i]); 70 | i -= 1; 71 | } 72 | 73 | oldTbody.parentNode.replaceChild(newTbody, oldTbody); 74 | 75 | Nymn_reversed = (Nymn_reversed) ? false : true; 76 | let sorted = document.getElementById('Nymn/mods-sorted'); 77 | let Sort = document.getElementById('Nymn/mods-sort-button'); 78 | if (!Nymn_reversed) { 79 | sorted.innerText = 'Sorted by: New'; 80 | Sort.innerText = 'Date added ▼'; 81 | } else { 82 | sorted.innerText = 'Sorted by: Old'; 83 | Sort.innerText = 'Date added ▲'; 84 | } 85 | } -------------------------------------------------------------------------------- /commands/fl.js: -------------------------------------------------------------------------------- 1 | const { got } = require('./../got'); 2 | const tools = require('../tools/tools.js'); 3 | 4 | 5 | module.exports = { 6 | name: 'fl', 7 | ping: true, 8 | description: 'This command will give you the first logged line from a specific user in the chat (Only works if logs are available in the channel, logs used: "https://logs.ivr.fi/"). Example: "bb fl NymN"', 9 | permission: 100, 10 | category: 'Info command', 11 | execute: async (channel, user, input, perm) => { 12 | try { 13 | if (module.exports.permission > perm) { 14 | return; 15 | } 16 | let uid = user['user-id']; 17 | let realname = user.username; 18 | if (input[2]) { 19 | if (input[2].startsWith('@')) { 20 | input[2] = input[2].substring(1); 21 | } 22 | [uid] = await got(`https://api.ivr.fi/v2/twitch/user?login=${input[2]}`).json(); 23 | realname = uid.login; 24 | uid = uid.id; 25 | } 26 | if (realname.match(/@?titleChange_bot,?/gi)) { 27 | return; 28 | } 29 | let realchannel = channel; 30 | if (input[3]) { 31 | realchannel = input[3]; 32 | } 33 | 34 | let logDate = await got(`https://logs.ivr.fi/list?channel=${realchannel}&userid=${uid}`).json(); 35 | 36 | logDate = logDate.availableLogs; 37 | 38 | let messageFound = false; 39 | let i = 1; 40 | let realmessages = ''; 41 | let fl = null; 42 | while (!messageFound) { 43 | let year = logDate[logDate.length - i].year; 44 | let month = logDate[logDate.length - i].month; 45 | 46 | fl = await got(`https://logs.ivr.fi/channel/${realchannel}/userid/${uid}/${year}/${month}?json`).json(); 47 | 48 | realmessages = fl.messages.filter((message) => { 49 | if (message.type !== 1) { 50 | return false; 51 | } 52 | return true; 53 | }); 54 | 55 | i++; 56 | if (realmessages[0]) { 57 | messageFound = true; 58 | realmessages = realmessages[0]; 59 | } else if (i > logDate.length) { 60 | return `FeelsDankMan @${realname}, has never said anything in #${realchannel}`; 61 | } 62 | } 63 | 64 | let message = tools.splitLine(realmessages.text, 350); 65 | 66 | const timeago = new Date().getTime() - Date.parse(realmessages.timestamp); 67 | if (fl?.status !== 404) { 68 | if (message[1]) { 69 | return `#${realchannel} ${realmessages.displayName}: ${message[0]}... - (${tools.humanizeDuration(timeago)} ago)`; 70 | } 71 | return `nymnDank ${realmessages.displayName}'s first message in #${realchannel[0]}\u034f${realchannel.slice(1)} was: ${message} - (${tools.humanizeDuration(timeago)} ago)`; 72 | } 73 | 74 | } catch (err) { 75 | console.log(err); 76 | 77 | if (err.toString().startsWith('HTTPError: Response code 403 (Forbidden)')) { 78 | return 'User or channel has opted out'; 79 | } 80 | if (err.toString().startsWith('HTTPError: Response code 500 (Internal Server Error)')) { 81 | return 'Could not load logs. Most likely the user either doesn\'t exist or doesn\'t have any logs here.'; 82 | } 83 | if (err.name) { 84 | if (err.name === 'HTTPError') { 85 | return 'No logs available for the user/channel'; 86 | } 87 | return `FeelsDankMan api error: ${err.name}`; 88 | } 89 | return 'FeelsDankMan Error'; 90 | } 91 | } 92 | }; -------------------------------------------------------------------------------- /commands/vodsearch.js: -------------------------------------------------------------------------------- 1 | const { got } = require('./../got'); 2 | const requireDir = require('require-dir'); 3 | const date = require('date-and-time'); 4 | 5 | 6 | module.exports = { 7 | name: 'vodsearch', 8 | ping: true, 9 | description: 'This command will give you a list of vods that correlates to the given date and time input. Input should be: "bb vodsearch yyyy-mm-dd time=*time in CET*"(The time is not needed if you just wish to find the vod with no timestamp). Example: "bb vodsearch nymn 2021-09-30 time=16:00" or "bb vodsearch nymn 2021-09-30"', 10 | permission: 100, 11 | category: 'Info command', 12 | execute: async (channel, user, input, perm) => { 13 | try { 14 | if (module.exports.permission > perm) { 15 | return; 16 | } 17 | let realchannel = input[2]; 18 | let voddate = input[3]; 19 | let vodtime = null; 20 | 21 | if (input[4]) { 22 | if (input[4].startsWith('time')) { 23 | vodtime = input[4].split('=')[1]; 24 | } else { 25 | return 'To specify time, do: "time=hh:mm" at the end (Time should be in CET)'; 26 | } 27 | } 28 | 29 | const [userID] = await got(`https://api.ivr.fi/v2/twitch/user?login=${realchannel}`).json(); 30 | 31 | if (userID.status === 404) { 32 | return `Could not find user: "${realchannel}"`; 33 | } 34 | 35 | let vodList = await got('https://api.twitch.tv/helix/videos', { 36 | headers: { 37 | 'client-id': process.env.TWITCH_CLIENTID, 38 | 'Authorization': process.env.TWITCH_AUTH 39 | }, 40 | searchParams: { 41 | 'user_id': userID.id, 42 | 'type': 'archive', 43 | 'first': 100 44 | } 45 | }).json(); 46 | 47 | if (!vodList.data.length) { 48 | return 'That user has no vods'; 49 | } 50 | 51 | let vodfound = 0; 52 | let urls = []; 53 | 54 | 55 | for (const vod of vodList.data) { 56 | let newCreatedat = new Date(vod.created_at); 57 | newCreatedat = date.addHours(newCreatedat, 2); 58 | newCreatedat = newCreatedat.toISOString(); 59 | 60 | if (newCreatedat.split('T')[0] === voddate) { 61 | vodfound = 1; 62 | urls.push(vod.url); 63 | break; 64 | } 65 | } 66 | if (vodfound === 0) { 67 | return 'No vod was found on that date [only the last 100 vods are available] (date format should be "yyyy-mm-dd")'; 68 | } 69 | 70 | if (vodtime === null) { 71 | return `${urls.length} vod('s) were found on that date: ${urls.toString().replaceAll(',', ' - ')}`; 72 | } 73 | 74 | const findTime = requireDir('../commands'); 75 | 76 | for (const url of urls) { 77 | let result = await findTime['vodtime'].execute(channel, user, ['bb', 'vodtime', url, vodtime], perm); 78 | 79 | if (!result.startsWith(vodtime)) { 80 | return result; 81 | } 82 | } 83 | 84 | return `No vod were found at that time, but here are the vod('s) from that date: ${urls.toString().replaceAll(',', ' - ')}`; 85 | } catch (err) { 86 | console.log(err); 87 | if (err.name) { 88 | if (err.name === 'TimeoutError') { 89 | return `FeelsDankMan api error: ${err.name}`; 90 | } 91 | } 92 | return `FeelsDankMan Error: ${err.response.data.error}`; 93 | } 94 | } 95 | }; -------------------------------------------------------------------------------- /tools/fetchEmotes.js: -------------------------------------------------------------------------------- 1 | const sql = require('../sql/index.js'); 2 | const { got } = require('./../got'); 3 | const EventSource = require('eventsource') 4 | 5 | exports.STV_user_emotes = async (uid) => { 6 | try { 7 | const STV_api = await got(`https://7tv.io/v3/users/twitch/${uid}`).json(); 8 | 9 | if (STV_api.emote_set.emotes.length) { 10 | const emote_list = STV_api.emote_set.emotes.reduce( 11 | (emote_list, emote) => { 12 | emote_list.push( 13 | { 14 | "name": emote.name, 15 | "id": emote.id, 16 | "time_added": emote.timestamp, 17 | "uploader": (emote.data.owner?.username !== undefined) ? emote.data.owner.username : 'Unknown', 18 | "platform": "7TV" 19 | } 20 | ); return emote_list; 21 | }, []); 22 | 23 | return { "emote_set": STV_api.emote_set.id, "emote_list": JSON.stringify(emote_list) } 24 | } 25 | 26 | } catch (error) { 27 | console.log(error); 28 | } 29 | } 30 | 31 | exports.STV_emotes = new Promise(async (Resolve) => { 32 | const user_ids = await sql.Query('SELECT uid FROM Streamers'); 33 | 34 | 35 | 36 | for (const user of user_ids) { 37 | 38 | const fetchedEmotes = await STV_user_emotes(user.uid) 39 | 40 | await sql.Query('UPDATE Streamers SET emote_set=?, emote_list=? WHERE uid=?', [fetchedEmotes.emote_set, fetchedEmotes.emote_list, user.uid]); 41 | } 42 | console.log(`Fetched all 7TV emotes`); 43 | Resolve() 44 | } 45 | ) 46 | 47 | /* 48 | exports.STV_events = new Promise(async() => { 49 | const emote_set_ids = await sql.Query('SELECT emote_set FROM Streamers'); 50 | 51 | const baseURL = 'https://events.7tv.io/v3@'; 52 | 53 | const events = [] 54 | 55 | emote_set_ids.forEach(emote_set => { 56 | if (emote_set.emote_set) { 57 | events.push(`emote_set.update`) 58 | } 59 | }); 60 | 61 | const URL = baseURL + events.toString(); 62 | 63 | const subscription = new EventSource(URL); 64 | 65 | subscription.addEventListener('open', () => { 66 | console.log('Connection opened') 67 | }); 68 | 69 | subscription.addEventListener('close', () => { 70 | console.log('Connection closed') 71 | }); 72 | 73 | subscription.addEventListener('error', (err) => { 74 | console.error("Subscription error: " + JSON.stringify(err)) 75 | }); 76 | 77 | 78 | subscription.addEventListener('dispatch', (update) => { 79 | if (JSON.parse(update.data).body.pushed) { 80 | console.log(JSON.parse(update.data).body.pushed.value.data) 81 | 82 | const id = JSON.parse(update.data).body.pushed.value.id 83 | const name = JSON.parse(update.data).body.pushed.value.name 84 | const time_added = JSON.parse(update.data).body.pushed.value.timestamp 85 | 86 | } 87 | if (JSON.parse(update.data).body.pulled) { 88 | console.log(JSON.parse(update.data).body.pulled) 89 | 90 | const id = JSON.parse(update.data).body.pulled.old_value.id 91 | } 92 | }); 93 | 94 | } 95 | )*/ -------------------------------------------------------------------------------- /commands/add.js: -------------------------------------------------------------------------------- 1 | const sql = require('./../sql/index.js'); 2 | 3 | module.exports = { 4 | name: 'add', 5 | ping: true, 6 | description: 'Can add "features" to the bot. This to add: "bb add alias [command] [alias(es)]"', 7 | permission: 2000, 8 | category: 'Dev command', 9 | noBanphrase: true, 10 | execute: async (channel, user, input, perm) => { 11 | try { 12 | if (module.exports.permission > perm) { 13 | return; 14 | } 15 | switch (input[2]) { 16 | case 'alias': { 17 | let command = input[3]; 18 | const commandlist = await sql.Query('SELECT * FROM Commands WHERE Name=?', [command]); 19 | 20 | if (!commandlist.length) { 21 | return `${command} is not a command`; 22 | } 23 | let aliases = input.slice(4); 24 | 25 | let data = await sql.Query('SELECT Aliases FROM Aliases'); 26 | data = data[0].Aliases; 27 | 28 | let newdata = data.toString().replaceAll('[', '').replaceAll(']', '').split(','); 29 | 30 | let alreadyAlias = []; 31 | let addedAliases = []; 32 | let iscommand = []; 33 | for (const alias of aliases) { 34 | const commandlist = await sql.Query('SELECT * FROM Commands WHERE Name=?', [alias]); 35 | if (commandlist.length) { 36 | iscommand.push(alias); 37 | } else { 38 | for (const oldalias of newdata) { 39 | if (oldalias[alias]) { 40 | alreadyAlias.push(alias); 41 | } 42 | } 43 | if (!alreadyAlias.includes(alias)) { 44 | newdata.push(`{ 45 | "${alias}": "${command}" 46 | }`); 47 | addedAliases.push(alias); 48 | } 49 | } 50 | } 51 | await sql.Query('UPDATE Aliases SET Aliases=?', [`[${newdata.toString()}]`]); 52 | 53 | let iscommand2 = ''; 54 | if (iscommand.length) { 55 | iscommand2 = `- The follow aliases are command names: (${iscommand.toString().replaceAll(',', ', ')})`; 56 | } 57 | 58 | let alreadyAlias2 = ''; 59 | if (alreadyAlias.length) { 60 | alreadyAlias2 = `- The follow aliases already exists: (${alreadyAlias.toString().replaceAll(',', ', ')})`; 61 | } 62 | 63 | if (!addedAliases.length) { 64 | return `${alreadyAlias2} ${iscommand2}`; 65 | } 66 | 67 | return `Successfully added the aliases (${addedAliases.toString().replaceAll(',', ', ')}) to ${command}. ${alreadyAlias2} ${iscommand2}`; 68 | } 69 | } 70 | } catch (err) { 71 | console.error(err); 72 | return 'FeelsDankMan Error'; 73 | } 74 | } 75 | }; -------------------------------------------------------------------------------- /commands/lm.js: -------------------------------------------------------------------------------- 1 | const { got } = require('./../got'); 2 | const tools = require('../tools/tools.js'); 3 | 4 | module.exports = { 5 | name: 'lm', 6 | ping: true, 7 | description: 'This command will give you the last logged line from a specific user in the chat (Only works if logs are available in the channel, logs used: "https://logs.ivr.fi/"). Example: "bb fl NymN"', 8 | permission: 100, 9 | category: 'Info command', 10 | execute: async (channel, user, input, perm) => { 11 | try { 12 | if (module.exports.permission > perm) { 13 | return; 14 | } 15 | let uid = user['user-id']; 16 | let realuser = user.username; 17 | if (input[2]) { 18 | if (input[2].startsWith('@')) { 19 | input[2] = input[2].substring(1); 20 | } 21 | uid = await got(`https://api.ivr.fi/v2/twitch/user?login=${input[2]}`).json(); 22 | uid = uid[0].id; 23 | realuser = input[2]; 24 | } 25 | if (realuser.match(/@?titleChange_bot,?/gi)) { 26 | return; 27 | } 28 | let realchannel = channel; 29 | if (input[3]) { 30 | realchannel = input[3]; 31 | } 32 | 33 | if (channel === realchannel && user['user-id'] === uid) { 34 | return 'nymnDank But you are right here ❓❗'; 35 | } 36 | let logDate = await got(`https://logs.ivr.fi/list?channel=${realchannel}&userid=${uid}`).json(); 37 | 38 | logDate = logDate.availableLogs; 39 | 40 | let messageFound = false; 41 | let i = 0; 42 | let realmessages = ''; 43 | let lm = ''; 44 | while (!messageFound) { 45 | let year = logDate[i].year; 46 | let month = logDate[i].month; 47 | 48 | lm = await got(`https://logs.ivr.fi/channel/${realchannel}/userid/${uid}/${year}/${month}?json&reverse`).json(); 49 | 50 | 51 | realmessages = lm.messages.filter(filterByID); 52 | 53 | i++; 54 | if (realmessages[0]) { 55 | messageFound = true; 56 | realmessages = realmessages[0]; 57 | } else if (i > logDate.length - 1) { 58 | return `FeelsDankMan @${realuser}, has never said anything in #${realchannel}`; 59 | } 60 | } 61 | 62 | let message = tools.splitLine(realmessages.text, 350); 63 | 64 | const timeago = new Date().getTime() - Date.parse(realmessages.timestamp); 65 | 66 | if (lm.status !== 404) { 67 | if (message[1]) { 68 | return `#${realchannel} ${realmessages[0].displayName}: ${message[0]}... - (${tools.humanizeDuration(timeago)} ago)`; 69 | } 70 | return `nymnDank ${realmessages.displayName}'s last message in #${realchannel[0]}\u034f${realchannel.slice(1)} was: ${message} - (${tools.humanizeDuration(timeago)} ago)`; 71 | } 72 | 73 | } catch (err) { 74 | console.log(err); 75 | 76 | if (err.toString().startsWith('HTTPError: Response code 403 (Forbidden)')) { 77 | return 'User or channel has opted out'; 78 | } 79 | if (err.toString().startsWith('HTTPError: Response code 500 (Internal Server Error)')) { 80 | return 'Could not load logs. Most likely the user either doesn\'t exist or doesn\'t have any logs here.'; 81 | } 82 | if (err.name) { 83 | if (err.name === 'HTTPError') { 84 | return 'No logs available for the user/channel'; 85 | } 86 | return `FeelsDankMan api error: ${err.name}`; 87 | } 88 | return 'FeelsDankMan Error'; 89 | } 90 | } 91 | }; 92 | 93 | 94 | function filterByID(message) { 95 | if (message.type !== 1) { 96 | return false; 97 | } 98 | return true; 99 | } -------------------------------------------------------------------------------- /commands/currentgametime.js: -------------------------------------------------------------------------------- 1 | 2 | const { got } = require('./../got'); 3 | const sql = require('./../sql/index.js'); 4 | const htmlparser = require('node-html-parser') 5 | const tools = require('../tools/tools.js'); 6 | 7 | module.exports = { 8 | name: 'currentgametime', 9 | ping: true, 10 | description: 'Gets the total time the streamer has been in the current category, ever', 11 | permission: 100, 12 | cooldown: 3, //in seconds 13 | category: 'Info command', 14 | opt_outable: false, 15 | showDelay: false, 16 | noBanphrase: false, 17 | channelSpecific: false, 18 | activeChannel: '', 19 | // eslint-disable-next-line no-unused-vars 20 | execute: async (channel, user, input, perm, aliascommand) => { 21 | try { 22 | if (module.exports.permission > perm) { 23 | return; 24 | } 25 | const gameData = await sql.Query('SELECT game FROM Streamers WHERE username=?', [channel]); 26 | 27 | const game = gameData[0].game; 28 | 29 | const categoryData = await got(`https://api.twitch.tv/helix/search/categories?query=${game}&first=1`, { 30 | headers: { 31 | 'client-id': process.env.TWITCH_CLIENTID, 32 | 'Authorization': process.env.TWITCH_AUTH 33 | } 34 | }).json(); 35 | 36 | const categoryID = categoryData.data[0].id; 37 | 38 | const response = await got(`https://twitchtracker.com/${channel}/games/${categoryID}`, { headers: { 'Cookie': 'cf_clearance=5f0QiGMwCgLB6BeAD_6iU0Le1ymg6n.EZoR7AbyhQpk-1736891853-1.2.1.1-8_6lTET6UE2aydR4dJXxkx8M9Qycs92tuCf7XGdkDfgqsUsQnJdIm91IzhjMHDNCdz.Mu.8vV7WvIWYAnGk9xk8CICja60zSfGgB4YzaR.rRYV0YFwGWSD0LeC8VmL4mZJm7PVUYE7dQfln0IyFsO1lnYqWGYyt2lWrZSIDKs2rXx4FpL9JAIQTM9_gyrSX.UFyjW6SP62Bu.rnRjVinMyHXzS6m.paqu2PnB.h4GS_JmriRmBGnQldETP2l6X4A4ZMcNZ_NM7uAPrw3x1WPimeIeSuTd58.obGONo1GpST92HGDqm_tWQxauUE0xQgtE7mp74YBSbo1u9EkJCaoUA', 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0' } }); 39 | 40 | const html = htmlparser.parse(response.body) 41 | 42 | const divs = []; 43 | 44 | html.querySelectorAll(".g-x-s-block").forEach((x) => divs.push(x.textContent)) 45 | 46 | if(!divs.length) { 47 | return 'I unable to find any data for this category' 48 | } 49 | 50 | const min = divs.find((x) => { if (x.includes("Time streamed")) {return true} }).split('\n')[1]; 51 | 52 | const ms = min * 60000 53 | 54 | const humanize_options = { 55 | language: 'shortEn', 56 | languages: { 57 | shortEn: { 58 | y: () => 'y', 59 | mo: () => 'mo', 60 | w: () => 'w', 61 | d: () => 'd', 62 | h: () => ' hours', 63 | m: () => ' minutes', 64 | s: () => 's', 65 | ms: () => 'ms', 66 | }, 67 | }, 68 | units: ['h', 'm'], 69 | largest: 3, 70 | round: true, 71 | conjunction: ' and ', 72 | spacer: '', 73 | 74 | } 75 | 76 | return `${channel} has streamed ${game} for a total of ${tools.humanizeDuration(ms, humanize_options)}`; 77 | } catch (err) { 78 | console.log(err); 79 | return 'FeelsDankMan Error'; 80 | } 81 | } 82 | }; -------------------------------------------------------------------------------- /commands/eval.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const tools = require('../tools/tools.js'); 3 | const regex = require('../tools/regex.js'); 4 | const {VM} = require('vm2'); 5 | 6 | module.exports = { 7 | name: 'eval', 8 | ping: false, 9 | description: 'This command will let you execute js code in the bot and make it return the result. (The message gets checked for massping, banphrases etc.). Example: "bb eval "lole ".repeat(10);"', 10 | permission: 100, 11 | category: 'Random command', 12 | execute: async (channel, user, input, perm) => { 13 | try { 14 | if (module.exports.permission > perm) { 15 | return; 16 | } 17 | input = input.splice(2); 18 | let msg = input.join(' '); 19 | const invisChar2 = new RegExp([ 20 | '[\u{13fe}-\u{13ff}]', 21 | '[\u{17b4}-\u{17b5}]', 22 | '[\u{180b}-\u{180f}]', 23 | '[\u{1bca0}-\u{1bca3}]', 24 | '[\u{1d173}-\u{1d17a}]', 25 | '[\u{2000}-\u{200f}]', 26 | '[\u{2028}-\u{202f}]', 27 | '[\u{2060}-\u{206f}]', 28 | '[\u{61c}-\u{61d}]', 29 | '[\u{80}-\u{9f}]', 30 | '[\u{e0000}-\u{e0fff}]', 31 | '[\u{e47}-\u{e4d}]', 32 | '[\u{fe00}-\u{feff}]', 33 | '[\u{fff0}-\u{ffff}]', 34 | '\u{115f}', 35 | '\u{1160}', 36 | '\u{2800}', 37 | '\u{3164}', 38 | '\u{34f}', 39 | '\u{ad}', 40 | '\u{ffa0}', 41 | '\u034f' 42 | ].join('|'), 'gmu'); 43 | 44 | 45 | if ((msg.match(/Array\((\d+)\)/) ? msg.match(/Array\((\d+)\)/)[1] : null) > 1000000) { 46 | return 'monkaS 👉 JavaScript heap out of memory'; 47 | } 48 | const vm = new VM({ 49 | timeout: 3000, 50 | allowAsync: false, 51 | wasm: false, 52 | eval: false, 53 | console: 'off', 54 | sandbox: {btoa, atob} 55 | }); 56 | const semicolonTerminated = msg.endsWith(';'); 57 | 58 | msg = msg.toString().split(';'); 59 | if (semicolonTerminated) msg = msg.slice(0, -1); 60 | 61 | 62 | if(!/\breturn\b/.test(msg[msg.length - 1])) { msg[msg.length - 1] = `return ${msg[msg.length - 1].trim()}`; } 63 | 64 | msg = await vm.run(`(() => { ${msg.join(';')} })()`)?.toString() ?? 'undefined'; 65 | 66 | 67 | msg = msg.replace(regex.invisChar, '').replace(invisChar2, ''); 68 | 69 | if (perm < 2000 && msg.match(/[&|$|/|.|?|-]|\bkb\b|^\bmelon\b/gm)) { // ignores &, $, kb, /, ., ?, !, - bot prefixes (. and / are twitch reserved prefixes) 70 | msg = '. ' + msg.charAt(0) + '\u034f' + msg.substring(1); 71 | } 72 | if (msg.match(/^!/gm)) { 73 | msg = '❗ ' + msg.replace('!', ''); 74 | } 75 | 76 | if (perm < 2000 && msg.match(/(\.|\/)color/gm)) { 77 | return 'cmonBruh don\'t change my color'; 78 | } 79 | 80 | const banRegex = new RegExp(`[./](ban|timeout|unmod) ${process.env.TWITCH_OWNERNAME}`,'gi'); 81 | if (msg.match(banRegex)) { 82 | return `nymnWeird too far @${user.username}`; 83 | } 84 | 85 | return msg; 86 | } catch (err) { 87 | console.log(err); 88 | return 'FeelsDankMan ' + err.toString().split('\n')[0]; 89 | } 90 | } 91 | }; -------------------------------------------------------------------------------- /commands/index.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('node:path'); 2 | const fs = require('node:fs'); 3 | const sql = require('./../sql/index.js'); 4 | 5 | module.exports = { 6 | Find: (path) => { 7 | const commands = []; 8 | const dir = fs.readdirSync(path, { withFileTypes: true }); 9 | for (const file of dir) { 10 | if (file.isFile()) { 11 | if (file.name === 'index.js') continue; 12 | 13 | const name = resolve( 14 | path, 15 | file.name 16 | ); 17 | 18 | const res = require(name); 19 | commands.push(res); 20 | } 21 | } 22 | return commands; 23 | }, 24 | Load: async function () { 25 | const commands = this.Find(__dirname).concat(this.Find(__dirname+'/customCommands')); 26 | const dbCommands = await sql.Query('SELECT * FROM Commands'); 27 | const disableCommand = await sql.Query('SELECT username FROM Streamers WHERE command_default = ?', [1]); 28 | 29 | // TODO Melon: This could look better. 30 | for (const command of commands) { 31 | for (const dbcommand of dbCommands) { 32 | if (dbcommand.Name === command.name) { 33 | if ( 34 | dbcommand.Command !== command.description || 35 | dbcommand.Perm !== command.permission || 36 | dbcommand.Category !== command.category || 37 | dbcommand.Cooldown !== command.cooldown 38 | ) { 39 | sql.Query('UPDATE Commands SET Command=?, Perm=?, Category=?, Cooldown=? WHERE Name=?', [command.description, command.permission, command.category, command.cooldown, command.name]); 40 | } 41 | } 42 | } 43 | } 44 | 45 | const commandNames = commands.map(x => x.name); 46 | const dbcommandNames = dbCommands.map(x => x.Name); 47 | const commandDiff = commands.filter(x => !dbcommandNames.includes(x.name)); 48 | const dbcommandDiff = dbCommands.filter(x => !commandNames.includes(x.Name)); 49 | for (const command of commandDiff) { 50 | if (disableCommand.length && command.category !== 'Core command' && command.category !== 'Dev command' && !command.channelSpecific) { 51 | for (const user of disableCommand) { 52 | let disabledList = await sql.Query(` 53 | SELECT disabled_commands 54 | FROM Streamers 55 | WHERE username=?`, 56 | [user.username]); 57 | 58 | disabledList = JSON.parse(disabledList[0].disabled_commands); 59 | disabledList.push(command.name); 60 | disabledList = JSON.stringify(disabledList); 61 | 62 | await sql.Query('UPDATE Streamers SET disabled_commands=? WHERE username=?', [disabledList, user.username]); 63 | 64 | } 65 | } 66 | await sql.Query('INSERT INTO Commands (Name, Command, Perm, Category, Cooldown) values (?, ?, ?, ?, ?)', [command.name, command.description, command.permission, command.category, command.cooldown]); 67 | } 68 | 69 | for (const dbCommand of dbcommandDiff) { 70 | await sql.Query('DELETE FROM Commands WHERE Name=?', [dbCommand.Name]); 71 | } 72 | 73 | } 74 | }; -------------------------------------------------------------------------------- /tools/messageHandler.js: -------------------------------------------------------------------------------- 1 | const tools = require('./tools.js'); 2 | require('dotenv'); 3 | const talkedRecently = {}; 4 | 5 | let oldmessage = ''; 6 | 7 | exports.messageHandler = class Cooldown { 8 | constructor(channel, message, noBanphrase, showDelay, start, ping, user, spamAllowed) { 9 | this.channel = channel; 10 | this.message = message; 11 | this.noBanphrase = noBanphrase || false; 12 | this.showDelay = showDelay || false; 13 | this.start = start || 0; 14 | this.ping = ping || false; 15 | this.user = user || null; 16 | this.spamAllowed = spamAllowed || false; 17 | this.noCD = 0; 18 | } 19 | Cooldown() { 20 | let cooldown = 1250; 21 | if (talkedRecently[this.channel]) { 22 | cooldown = 1250 * (talkedRecently[this.channel].length); 23 | 24 | } 25 | return cooldown; 26 | } 27 | 28 | async newMessage() { 29 | const cc = require('../bot.js').cc; 30 | require('../bot.js').start; 31 | 32 | if (process.env.TWITCH_USER === 'devbear1110' && this.channel !== process.env.TWITCH_OWNERNAME) { 33 | console.log(`Channel: #${this.channel} - msg: ${this.message}`); 34 | return; 35 | } else if (process.env.TWITCH_USER === 'devbear1110') { 36 | this.noBanphrase = true; 37 | } 38 | if (this.channel === '#forsen') { 39 | let newmessage = tools.splitLine(this.message, 150); 40 | if (newmessage[1]) { 41 | this.message = newmessage[0] + ' ...'; 42 | } 43 | } 44 | 45 | if (this.ping) { 46 | this.message = `${this.user['display-name']}, ${this.message}`; 47 | } 48 | if (this.showDelay) { 49 | this.end = new Date().getTime(); 50 | this.message = `${this.message} ${this.end - this.start}ms`; 51 | 52 | } 53 | 54 | if (talkedRecently[this.channel] && !this.spamAllowed) { 55 | this.noCD = 0; 56 | let tempList = talkedRecently[this.channel]; 57 | tempList.push(this.message); 58 | talkedRecently[this.channel] = tempList; 59 | } else { 60 | if (this.message === oldmessage) { 61 | this.message = (this.message.endsWith(' 󠀀 ')) ? this.message.substring(this.message.length-4, 0) : this.message + ' 󠀀 '; 62 | } 63 | if (!this.noBanphrase) { 64 | try { 65 | this.message = await tools.checkAllBanphrases(this.message, this.channel); 66 | } catch (err) { 67 | console.log(err); 68 | this.message = 'Failed to check for banphrases'; 69 | } 70 | } 71 | await cc.say(this.channel, this.message); 72 | this.noCD = 1; 73 | let tempList = []; 74 | tempList.push(this.message); 75 | talkedRecently[this.channel] = tempList; 76 | } 77 | 78 | setTimeout(async () => { 79 | let tempList = talkedRecently[this.channel]; 80 | if (this.noCD === 0) { 81 | if (this.message === oldmessage) { 82 | this.message = (this.message.endsWith(' 󠀀 ')) ? this.message.substring(this.message.length-4, 0) : this.message + ' 󠀀 '; 83 | } 84 | if (!this.noBanphrase) { 85 | try { 86 | this.message = await tools.checkAllBanphrases(this.message, this.channel); 87 | } catch (err) { 88 | console.log(err); 89 | this.message = 'Failed to check for banphrases'; 90 | } 91 | } 92 | await cc.say(this.channel, this.message); 93 | } 94 | oldmessage = this.message; 95 | tempList.shift(); 96 | talkedRecently[this.channel] = tempList; 97 | if (!talkedRecently[this.channel].length) { 98 | delete talkedRecently[this.channel]; 99 | this.noCD = 0; 100 | } 101 | 102 | }, this.Cooldown()); 103 | return; 104 | } 105 | 106 | }; --------------------------------------------------------------------------------