├── .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 |

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 |

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 | | ID: |
19 |
20 | User:
21 | |
22 | Suggestion: |
23 | Status: |
24 | Description: |
25 |
26 | <% suggestions.forEach(function(suggestion) { %>
27 |
28 | |
29 | <%- suggestion.ID %>
30 | |
31 |
32 | <%- suggestion.User %>
33 | |
34 |
35 | <%- suggestion.Suggestion%>
36 | |
37 |
38 | <%- suggestion.Status%>
39 | |
40 |
41 | <%- suggestion.Description%>
42 | |
43 |
44 | <% }) %>
45 |
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 |

19 |
20 | <%if (!cookieToken) { %>
21 |
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 |
41 |
42 |

43 |
<%= username%>
44 |
45 |
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 |
45 | <% if (current == 1) { %>
46 |
First
47 | <% } else { %>
48 |
First
49 | <% } %>
50 | <% let i = (Number(current) > 5 ? Number(current) - 4 : 1) %>
51 | <% if (i !== 1) { %>
52 |
...
53 | <% } %>
54 | <% for (; i <= (Number(current) + 4) && i <= pages; i++) { %>
55 | <% if (i == current) { %>
56 |
<%= i %>
57 | <% } else { %>
58 |
<%= i %>
59 | <% } %>
60 | <% if (i == Number(current) + 4 && i < pages) { %>
61 |
...
62 | <% } %>
63 | <% } %>
64 | <% if (current == pages) { %>
65 |
Last
66 | <% } else { %>
67 |
Last
68 | <% } %>
69 |
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 | };
--------------------------------------------------------------------------------