├── kbot-website
├── .dockerignore
├── src
│ ├── lib
│ │ ├── db.ts
│ │ └── serviceConnectorBridge.ts
│ └── app
│ │ ├── favicon.ico
│ │ ├── api
│ │ ├── auth
│ │ │ └── [...nextauth]
│ │ │ │ └── route.ts
│ │ ├── commands
│ │ │ └── route.ts
│ │ ├── user
│ │ │ └── settings
│ │ │ │ └── route.ts
│ │ ├── connected-apps
│ │ │ └── route.ts
│ │ └── emotes
│ │ │ └── route.ts
│ │ ├── globals.css
│ │ ├── account
│ │ └── layout.tsx
│ │ ├── providers
│ │ └── SessionProvider.tsx
│ │ ├── components
│ │ ├── DocsSection.tsx
│ │ ├── HomeSection.tsx
│ │ └── SharedLayout.tsx
│ │ └── layout.tsx
├── public
│ ├── kbot-logo.png
│ ├── github-logo.png
│ ├── vercel.svg
│ ├── window.svg
│ ├── file.svg
│ ├── globe.svg
│ └── next.svg
├── postcss.config.mjs
├── middleware.ts
├── next.config.ts
├── eslint.config.mjs
├── tailwind.config.ts
├── .gitignore
├── types
│ └── next-auth.d.ts
├── tsconfig.json
├── Dockerfile
├── package.json
└── README.md
├── k8s
├── deployments
│ ├── glk-loki
│ │ ├── glk-namespace.yaml
│ │ ├── glk-loki-service.yaml
│ │ ├── glk-loki-config-map.yaml
│ │ └── glk-loki-deployment.yaml
│ ├── docker-registry
│ │ └── deployment.sh
│ ├── glk-promtail
│ │ ├── glk-promtail-service-account.yaml
│ │ ├── glk-promtail-cluster-role-binding.yaml
│ │ ├── glk-promtail-cluster-role.yaml
│ │ └── glk-promtail-daemon-set.yaml
│ ├── music-bot
│ │ ├── docker-deploy.sh
│ │ ├── Dockerfile
│ │ └── manifest.yaml
│ ├── kbot-api
│ │ ├── docker-deploy.sh
│ │ ├── kbot-api-service-node-port.yaml
│ │ └── kbot-api-deployment.yaml
│ ├── kbot-backend
│ │ ├── docker-deploy.sh
│ │ └── kbot-backend-deployment.yaml
│ ├── kbot-mirrors
│ │ ├── docker-deploy.sh
│ │ ├── update-emotes-list-service.yaml
│ │ ├── chat-notice-logger-deployment.yaml
│ │ ├── twitch-chat-message-logger-service.yaml
│ │ ├── twitch-chat-notice-logger-service.yaml
│ │ ├── twitch-chat-queue-filler-deployment.yaml
│ │ ├── twitch-chat-queue-filler-service.yaml
│ │ ├── twitch-chat-message-logger-deployment.yaml
│ │ ├── twitch-chat-banphrased-message-logger-service.yaml
│ │ ├── twitch-chat-banphrased-message-logger-deployment.yaml
│ │ ├── update-emotes-list-deployment.yaml
│ │ ├── reddit-live-thread-to-discord-deployment.yaml
│ │ └── reddit-live-thread-to-discord-service.yaml
│ ├── kbot-task-manager
│ │ ├── docker-deploy.sh
│ │ └── kbot-task-manager-deployment.yaml
│ ├── kbot-website
│ │ ├── docker-deploy.sh
│ │ ├── kbot-website-service-node-port.yaml
│ │ └── kbot-website-deployment.yaml
│ ├── mysql
│ │ ├── mysql-service-cluster-ip.yaml
│ │ ├── mysql-service-node-port.yaml
│ │ ├── mysql-deployment.yaml
│ │ └── mysql-stateful.yaml
│ ├── redis
│ │ ├── redis-service-cluster-ip.yaml
│ │ ├── redis-service-node-port.yaml
│ │ ├── redis-persistent-volume.yaml
│ │ └── redis-deployment.yaml
│ ├── glk-grafana
│ │ ├── glk-grafana-service-node-port.yaml
│ │ ├── glk-grafana-config-map.yaml
│ │ └── glk-grafana-deployment.yaml
│ ├── rapidapi-solutions
│ │ └── ads-scraper
│ │ │ ├── ads-scraper-service.yaml
│ │ │ └── ads-scraper-deployment.yaml
│ └── rabbitmq
│ │ ├── rabbit-service-node-port.yaml
│ │ ├── rabbit-persistent-volume.yaml
│ │ └── rabbit-deployment.yaml
├── cron-jobs
│ └── mysql-backup
│ │ ├── docker-deploy.sh
│ │ ├── Dockerfile
│ │ └── manifest.yaml
└── helpers
│ └── minikube-setup
│ ├── minikube-startup.sh
│ ├── minikube-services.service
│ └── minikube-services.sh
├── commons
├── connector
│ ├── utils
│ │ ├── sleep.js
│ │ ├── prepareMessage.js
│ │ └── endecrypt.js
│ └── services
│ │ ├── healthcheckMiddleware.js
│ │ ├── redditClient.js
│ │ └── discordClient.js
├── repositories
│ └── ResponseRepository.js
└── Commons.js
├── README.md
├── .github
└── dependabot.yml
├── api
├── src
│ ├── endpoints
│ │ ├── healthcheckGet.js
│ │ ├── randomEmoteGet.js
│ │ ├── userGet.js
│ │ └── statsGet.js
│ ├── site
│ │ ├── pageRequest.js
│ │ ├── pageCommandsCodeRedirect.js
│ │ ├── pageGenres.js
│ │ ├── pageConnections.js
│ │ ├── pageCommandsCode.js
│ │ ├── pageLastfm.js
│ │ ├── pageColors.js
│ │ ├── pageCommands.js
│ │ ├── pageLastfmResolved.js
│ │ ├── pageSpotifyResolved.js
│ │ └── pageCountdown.js
│ ├── webhookHandlerWildcard.js
│ └── consts
│ │ └── consts.json
├── utils
│ ├── getAllPlatformsEmoteCount.js
│ ├── getModuleData.js
│ ├── getFileNames.js
│ ├── modifyOutput.js
│ ├── initializeMethodRecurse.js
│ ├── formatEmotesDate.js
│ ├── expressSetup.js
│ ├── createColorsGetResponse.js
│ ├── prepareCommandsRow.js
│ ├── prepareEmotesRow.js
│ └── gitWebhookMiddleware.js
├── Dockerfile
├── api.js
└── api.test.js
├── lib
├── utils
│ ├── songHandlerWhitelist.js
│ ├── consts
│ │ └── response-settings.json
│ ├── isContentBanphrased.js
│ └── postprocessCommandResponse.js
├── commands
│ ├── website
│ │ └── website.js
│ ├── commands
│ │ └── commands.js
│ ├── register
│ │ └── register.js
│ ├── unregister
│ │ └── unregister.js
│ ├── ping
│ │ └── ping.js
│ ├── music
│ │ ├── subCommands
│ │ │ ├── allow.js
│ │ │ ├── disallow.js
│ │ │ ├── queue.js
│ │ │ └── play.js
│ │ └── utils
│ │ │ ├── userValidator.js
│ │ │ ├── spotifyErrorResponses.js
│ │ │ ├── searchSpotifySong.js
│ │ │ └── executeSpotifyAction.js
│ └── namechange
│ │ ├── namechange.js
│ │ └── utils
│ │ └── namechangeValidator.js
├── mirrors
│ ├── Dockerfile
│ ├── emote-checker
│ │ └── utils
│ │ │ ├── getEmoteId.js
│ │ │ ├── getEmoteDate.js
│ │ │ ├── ffzEmoteCheck.js
│ │ │ ├── seventvEmoteCheck.js
│ │ │ └── bttvEmoteCheck.js
│ ├── mirrors.js
│ ├── twitch-chat-logger
│ │ ├── twitchChatNoticeLogger.js
│ │ ├── twitchChatBanphrasedMessageLogger.js
│ │ └── twitchChatQueueFiller.js
│ └── reddit-live-thread-to-discord
│ │ ├── utils
│ │ └── sendRedditMessageToQueue.js
│ │ └── redditLiveThreadToDiscord.js
├── job-manager
│ ├── tasks
│ │ ├── refreshModuleStats.js
│ │ ├── pingSupibotActivityCheck.js
│ │ ├── channelEmotesUpdaterQueueFill.js
│ │ ├── refreshViewCount.js
│ │ ├── cleanExpiredRegistrationAttempts.js
│ │ ├── refreshChannelList.js
│ │ ├── refreshAliasList.js
│ │ ├── renewTwitchToken.js
│ │ ├── refreshEstimatedRepoLines.js
│ │ └── updateUsageStats.js
│ └── Dockerfile
├── commands_old
│ ├── google.js
│ ├── npm.js
│ ├── github.js
│ ├── rf.js
│ ├── bible.js
│ ├── spacex.js
│ ├── surah.js
│ ├── countdown.js
│ ├── 3rd-party
│ │ └── ps.js
│ ├── uptime.js
│ ├── verify.js
│ ├── query.js
│ ├── unban.js
│ ├── 4head.js
│ ├── joemama.js
│ ├── rp.js
│ ├── dank.js
│ ├── ban.js
│ ├── suggest.js
│ ├── vypyr.js
│ ├── help.js
│ ├── id.js
│ ├── banphrase.js
│ └── stream.js
├── credentials
│ ├── config_template.js
│ └── login.js
└── examples
│ └── example_chat_message.json
├── .gitignore
├── .prettierrc
├── Dockerfile
├── consts
├── regex.js
└── serviceSettings.json
├── test
└── server.js
├── package.json
└── data
└── controls.json
/kbot-website/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .next
3 | .git
4 | *.log
--------------------------------------------------------------------------------
/kbot-website/src/lib/db.ts:
--------------------------------------------------------------------------------
1 | export { getServiceConnector as default } from './serviceConnectorBridge';
2 |
--------------------------------------------------------------------------------
/kbot-website/public/kbot-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUNszg/kbot/HEAD/kbot-website/public/kbot-logo.png
--------------------------------------------------------------------------------
/kbot-website/src/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUNszg/kbot/HEAD/kbot-website/src/app/favicon.ico
--------------------------------------------------------------------------------
/k8s/deployments/glk-loki/glk-namespace.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Namespace
3 | metadata:
4 | name: logging
5 |
--------------------------------------------------------------------------------
/kbot-website/public/github-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KUNszg/kbot/HEAD/kbot-website/public/github-logo.png
--------------------------------------------------------------------------------
/k8s/deployments/docker-registry/deployment.sh:
--------------------------------------------------------------------------------
1 | helm install -f values.yaml docker-registry --kube-context kbot --namespace default twuni/docker-registry
2 |
--------------------------------------------------------------------------------
/kbot-website/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/commons/connector/utils/sleep.js:
--------------------------------------------------------------------------------
1 | const sleep = (time) => {
2 | return new Promise(resolve => setTimeout(resolve, time));
3 | }
4 |
5 | module.exports = sleep;
--------------------------------------------------------------------------------
/k8s/deployments/glk-promtail/glk-promtail-service-account.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ServiceAccount
3 | metadata:
4 | name: promtail
5 | namespace: logging
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started
2 |
3 | This is a Twitch and Discord chatbot
4 |
5 | Readme is work in progress
6 |
7 |
8 | Project website [here](https://kunszg.com)
9 |
10 |
--------------------------------------------------------------------------------
/k8s/deployments/music-bot/docker-deploy.sh:
--------------------------------------------------------------------------------
1 | docker build -t docker-registry.kunszg.com/kbot-depl-music-bot:latest .
2 | docker push docker-registry.kunszg.com/kbot-depl-music-bot:latest
3 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "npm"
4 | directory: "/"
5 | schedule:
6 | interval: "daily"
7 | labels:
8 | - "dependencies"
9 |
--------------------------------------------------------------------------------
/k8s/cron-jobs/mysql-backup/docker-deploy.sh:
--------------------------------------------------------------------------------
1 | docker build -t docker-registry.kunszg.com/kbot-cron-mysql-backup:latest .
2 | docker push docker-registry.kunszg.com/kbot-cron-mysql-backup:latest
3 |
--------------------------------------------------------------------------------
/kbot-website/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('postcss-load-config').Config} */
2 | const config = {
3 | plugins: {
4 | tailwindcss: {},
5 | },
6 | };
7 |
8 | export default config;
9 |
--------------------------------------------------------------------------------
/k8s/deployments/kbot-api/docker-deploy.sh:
--------------------------------------------------------------------------------
1 | docker build -t docker-registry.kunszg.com/kbot-api:latest -f ../../../api/Dockerfile ../../../
2 | docker push docker-registry.kunszg.com/kbot-api:latest
3 |
--------------------------------------------------------------------------------
/k8s/deployments/kbot-backend/docker-deploy.sh:
--------------------------------------------------------------------------------
1 | docker build -t docker-registry.kunszg.com/kbot-backend:latest -f ../../../Dockerfile ../../../
2 | docker push docker-registry.kunszg.com/kbot-backend:latest
3 |
--------------------------------------------------------------------------------
/k8s/deployments/kbot-mirrors/docker-deploy.sh:
--------------------------------------------------------------------------------
1 | docker build -t docker-registry.kunszg.com/kbot-mirrors:latest -f ../../../lib/mirrors/Dockerfile ../../../
2 | docker push docker-registry.kunszg.com/kbot-mirrors:latest
3 |
--------------------------------------------------------------------------------
/k8s/deployments/kbot-task-manager/docker-deploy.sh:
--------------------------------------------------------------------------------
1 | docker build -t docker-registry.kunszg.com/kbot-job-manager:latest -f ../../../lib/job-manager/Dockerfile ../../../
2 | docker push docker-registry.kunszg.com/kbot-job-manager:latest
3 |
--------------------------------------------------------------------------------
/k8s/deployments/kbot-website/docker-deploy.sh:
--------------------------------------------------------------------------------
1 | docker build -t docker-registry.kunszg.com/kbot-website:latest -f ../../../kbot-website/Dockerfile ../../.. --no-cache --pull
2 | docker push docker-registry.kunszg.com/kbot-website:latest
3 |
--------------------------------------------------------------------------------
/kbot-website/src/app/api/auth/[...nextauth]/route.ts:
--------------------------------------------------------------------------------
1 | import NextAuth from 'next-auth';
2 | import { authOptions } from '@/lib/auth';
3 |
4 | const handler = NextAuth(authOptions);
5 |
6 | export { handler as GET, handler as POST };
7 |
--------------------------------------------------------------------------------
/api/src/endpoints/healthcheckGet.js:
--------------------------------------------------------------------------------
1 | const healthcheckGet = services => {
2 | const { app } = services;
3 | app.get('/healthcheck', (req, res) => {
4 | res.status(200).send("OK");
5 | });
6 | }
7 |
8 | module.exports = healthcheckGet;
9 |
--------------------------------------------------------------------------------
/k8s/cron-jobs/mysql-backup/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:20.12.2-alpine
2 |
3 | WORKDIR /usr/src/app
4 |
5 | COPY mysqlBackup.js ./mysql-backup/
6 |
7 | WORKDIR /usr/src/app/mysql-backup
8 |
9 | EXPOSE 8080
10 |
11 | CMD ["node", "mysqlBackup.js"]
--------------------------------------------------------------------------------
/lib/utils/songHandlerWhitelist.js:
--------------------------------------------------------------------------------
1 | const songHandlers = {
2 | '#iaaras2': 'kb spotify @iaaras2',
3 | '#nymn': 'kb spotify @nymn',
4 | '#nymn2': 'kb spotify @nymn',
5 | '#mande': 'kb spotify @mande',
6 | };
7 |
8 | module.exports = songHandlers;
--------------------------------------------------------------------------------
/k8s/deployments/glk-loki/glk-loki-service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: loki
5 | namespace: logging
6 | spec:
7 | ports:
8 | - port: 3100
9 | targetPort: 3100
10 | name: http
11 | selector:
12 | app: loki
--------------------------------------------------------------------------------
/api/utils/getAllPlatformsEmoteCount.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash');
2 |
3 | const getAllPlatformsEmoteCount = (emotesAdded, platform) => {
4 | return _.filter(emotesAdded, emote => emote.type === platform).length;
5 | };
6 |
7 | module.exports = getAllPlatformsEmoteCount;
8 |
--------------------------------------------------------------------------------
/kbot-website/src/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | :root {
6 | --background: #0a0a0a;
7 | --foreground: #ffffff;
8 | }
9 |
10 | @layer base {
11 | body {
12 | @apply text-white bg-background;
13 | }
14 | }
--------------------------------------------------------------------------------
/lib/commands/website/website.js:
--------------------------------------------------------------------------------
1 | const website = async ({}, { Commons }) => {
2 | const responses = Commons.ResponseRepository().getResponses();
3 |
4 | return {
5 | response: responses.WEBSITE.SUCCESS.URL
6 | };
7 | };
8 |
9 | module.exports.invocation = website;
10 |
--------------------------------------------------------------------------------
/kbot-website/src/app/account/layout.tsx:
--------------------------------------------------------------------------------
1 | import SessionProvider from '../providers/SessionProvider';
2 | import React from 'react';
3 |
4 | export default function AccountLayout({ children }: { children: React.ReactNode }) {
5 | return {children};
6 | }
7 |
--------------------------------------------------------------------------------
/lib/commands/commands/commands.js:
--------------------------------------------------------------------------------
1 | const commands = async ({}, { Commons }) => {
2 | const responses = Commons.ResponseRepository().getResponses();
3 |
4 | return {
5 | response: responses.COMMANDS.SUCCESS.LIST
6 | };
7 | };
8 |
9 | module.exports.invocation = commands;
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | lib/credentials/config.js
2 | amp.js
3 | node_modules
4 | config.js
5 | test.js
6 | .eslintrc.json
7 | .vscode
8 | .idea
9 | data/temp_api_restarting.txt
10 | data/temp_api_uptime.txt
11 | data/aliases.json
12 | *.txt
13 | /coverage/
14 | manifests/jobs/kubernetes-startup-scripts.sh
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 95,
3 | "tabWidth": 2,
4 | "useTabs": false,
5 | "semi": true,
6 | "singleQuote": true,
7 | "trailingComma": "none",
8 | "bracketSpacing": true,
9 | "jsxBracketSameLine": false,
10 | "arrowParens": "avoid",
11 | "proseWrap": "always"
12 | }
13 |
--------------------------------------------------------------------------------
/api/utils/getModuleData.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash');
2 |
3 | const getModuleData = (input, modules) => {
4 | const moduleData = _.filter(modules, i => i.type === 'module' && i.sha === input);
5 |
6 | return Date.parse(_.get(_.first(moduleData), "date"));
7 | };
8 |
9 | module.exports = getModuleData;
--------------------------------------------------------------------------------
/lib/commands/register/register.js:
--------------------------------------------------------------------------------
1 | const register = async (context, { Commons }) => {
2 | const responses = Commons.ResponseRepository().getResponses();
3 |
4 | return {
5 | response: responses.REGISTER.SUCCESS.REGISTER_LINK
6 | };
7 | };
8 |
9 | module.exports.invocation = register;
10 |
--------------------------------------------------------------------------------
/k8s/deployments/mysql/mysql-service-cluster-ip.yaml:
--------------------------------------------------------------------------------
1 | kind: Service
2 | apiVersion: v1
3 | metadata:
4 | name: mysql-clusterip
5 | namespace: default
6 | spec:
7 | ports:
8 | - protocol: TCP
9 | port: 3306
10 | targetPort: 3306
11 | selector:
12 | app: mysql
13 | type: ClusterIP
--------------------------------------------------------------------------------
/k8s/deployments/redis/redis-service-cluster-ip.yaml:
--------------------------------------------------------------------------------
1 | kind: Service
2 | apiVersion: v1
3 | metadata:
4 | name: redis-clusterip
5 | namespace: default
6 | spec:
7 | ports:
8 | - protocol: TCP
9 | port: 6379
10 | targetPort: 6379
11 | selector:
12 | app: redis
13 | type: ClusterIP
--------------------------------------------------------------------------------
/k8s/deployments/glk-grafana/glk-grafana-service-node-port.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: grafana
5 | namespace: logging
6 | spec:
7 | type: NodePort
8 | ports:
9 | - port: 3005
10 | targetPort: 3005
11 | nodePort: 30305
12 | selector:
13 | app: grafana
--------------------------------------------------------------------------------
/k8s/deployments/kbot-website/kbot-website-service-node-port.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: kbot-website-service
5 | spec:
6 | selector:
7 | app: kbot-website
8 | ports:
9 | - protocol: TCP
10 | port: 40100
11 | targetPort: 3000
12 | type: NodePort
13 |
--------------------------------------------------------------------------------
/lib/commands/unregister/unregister.js:
--------------------------------------------------------------------------------
1 | const unregister = async (context, { Commons }) => {
2 | const responses = Commons.ResponseRepository().getResponses();
3 |
4 | return {
5 | response: responses.REGISTER.SUCCESS.UNREGISTER_LINK
6 | };
7 | };
8 |
9 | module.exports.invocation = unregister;
10 |
--------------------------------------------------------------------------------
/api/src/site/pageRequest.js:
--------------------------------------------------------------------------------
1 | const pageRequest = services => {
2 | const { app } = services;
3 |
4 | app.get('/request', async (req, res) => {
5 | const html = `Coming soon...`;
6 |
7 | res.send(html);
8 | });
9 | };
10 |
11 | module.exports = pageRequest;
12 |
--------------------------------------------------------------------------------
/api/utils/getFileNames.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | const getFileNames = filenames => {
4 | const result = [];
5 |
6 | for (let i = 0; i < filenames.length; i++) {
7 | result.push(path.parse(filenames[i]).name);
8 | }
9 |
10 | return result.join(', ');
11 | };
12 |
13 | module.exports = getFileNames;
14 |
--------------------------------------------------------------------------------
/k8s/deployments/kbot-api/kbot-api-service-node-port.yaml:
--------------------------------------------------------------------------------
1 | kind: Service
2 | apiVersion: v1
3 | metadata:
4 | name: kbot-api
5 | labels:
6 | app: kbot-api
7 | spec:
8 | ports:
9 | - protocol: TCP
10 | port: 8080
11 | targetPort: 8080
12 | selector:
13 | app: kbot-api
14 | type: NodePort
15 |
16 |
--------------------------------------------------------------------------------
/k8s/deployments/kbot-mirrors/update-emotes-list-service.yaml:
--------------------------------------------------------------------------------
1 | kind: Service
2 | apiVersion: v1
3 | metadata:
4 | name: kbot-api
5 | labels:
6 | app: kbot-api
7 | spec:
8 | ports:
9 | - protocol: TCP
10 | port: 8080
11 | targetPort: 8080
12 | selector:
13 | app: kbot-api
14 | type: NodePort
15 |
16 |
--------------------------------------------------------------------------------
/api/utils/modifyOutput.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash');
2 |
3 | const modifyOutput = (input, truncateLength) => {
4 | return input.length > truncateLength
5 | ? `${_.truncate(input, {
6 | length: truncateLength,
7 | omission: '(...)',
8 | })}`
9 | : input;
10 | };
11 |
12 | module.exports = modifyOutput;
--------------------------------------------------------------------------------
/k8s/deployments/kbot-mirrors/chat-notice-logger-deployment.yaml:
--------------------------------------------------------------------------------
1 | kind: Service
2 | apiVersion: v1
3 | metadata:
4 | name: kbot-api
5 | labels:
6 | app: kbot-api
7 | spec:
8 | ports:
9 | - protocol: TCP
10 | port: 8080
11 | targetPort: 8080
12 | selector:
13 | app: kbot-api
14 | type: NodePort
15 |
16 |
--------------------------------------------------------------------------------
/k8s/deployments/kbot-mirrors/twitch-chat-message-logger-service.yaml:
--------------------------------------------------------------------------------
1 | kind: Service
2 | apiVersion: v1
3 | metadata:
4 | name: kbot-api
5 | labels:
6 | app: kbot-api
7 | spec:
8 | ports:
9 | - protocol: TCP
10 | port: 8080
11 | targetPort: 8080
12 | selector:
13 | app: kbot-api
14 | type: NodePort
15 |
16 |
--------------------------------------------------------------------------------
/k8s/deployments/kbot-mirrors/twitch-chat-notice-logger-service.yaml:
--------------------------------------------------------------------------------
1 | kind: Service
2 | apiVersion: v1
3 | metadata:
4 | name: kbot-api
5 | labels:
6 | app: kbot-api
7 | spec:
8 | ports:
9 | - protocol: TCP
10 | port: 8080
11 | targetPort: 8080
12 | selector:
13 | app: kbot-api
14 | type: NodePort
15 |
16 |
--------------------------------------------------------------------------------
/k8s/deployments/kbot-mirrors/twitch-chat-queue-filler-deployment.yaml:
--------------------------------------------------------------------------------
1 | kind: Service
2 | apiVersion: v1
3 | metadata:
4 | name: kbot-api
5 | labels:
6 | app: kbot-api
7 | spec:
8 | ports:
9 | - protocol: TCP
10 | port: 8080
11 | targetPort: 8080
12 | selector:
13 | app: kbot-api
14 | type: NodePort
15 |
16 |
--------------------------------------------------------------------------------
/k8s/deployments/kbot-mirrors/twitch-chat-queue-filler-service.yaml:
--------------------------------------------------------------------------------
1 | kind: Service
2 | apiVersion: v1
3 | metadata:
4 | name: kbot-api
5 | labels:
6 | app: kbot-api
7 | spec:
8 | ports:
9 | - protocol: TCP
10 | port: 8080
11 | targetPort: 8080
12 | selector:
13 | app: kbot-api
14 | type: NodePort
15 |
16 |
--------------------------------------------------------------------------------
/k8s/deployments/redis/redis-service-node-port.yaml:
--------------------------------------------------------------------------------
1 | kind: Service
2 | apiVersion: v1
3 | metadata:
4 | name: redis
5 | labels:
6 | app: redis
7 | spec:
8 | ports:
9 | - protocol: TCP
10 | port: 6379
11 | targetPort: 6379
12 | nodePort: 30637
13 | selector:
14 | app: redis
15 | type: NodePort
16 |
17 |
--------------------------------------------------------------------------------
/k8s/deployments/kbot-mirrors/twitch-chat-message-logger-deployment.yaml:
--------------------------------------------------------------------------------
1 | kind: Service
2 | apiVersion: v1
3 | metadata:
4 | name: kbot-api
5 | labels:
6 | app: kbot-api
7 | spec:
8 | ports:
9 | - protocol: TCP
10 | port: 8080
11 | targetPort: 8080
12 | selector:
13 | app: kbot-api
14 | type: NodePort
15 |
16 |
--------------------------------------------------------------------------------
/k8s/deployments/kbot-mirrors/twitch-chat-banphrased-message-logger-service.yaml:
--------------------------------------------------------------------------------
1 | kind: Service
2 | apiVersion: v1
3 | metadata:
4 | name: kbot-api
5 | labels:
6 | app: kbot-api
7 | spec:
8 | ports:
9 | - protocol: TCP
10 | port: 8080
11 | targetPort: 8080
12 | selector:
13 | app: kbot-api
14 | type: NodePort
15 |
16 |
--------------------------------------------------------------------------------
/k8s/deployments/rapidapi-solutions/ads-scraper/ads-scraper-service.yaml:
--------------------------------------------------------------------------------
1 | kind: Service
2 | apiVersion: v1
3 | metadata:
4 | name: ads-scraper
5 | labels:
6 | app: ads-scraper
7 | spec:
8 | ports:
9 | - protocol: TCP
10 | port: 8510
11 | targetPort: 3000
12 | selector:
13 | app: ads-scraper
14 | type: NodePort
15 |
16 |
--------------------------------------------------------------------------------
/kbot-website/middleware.ts:
--------------------------------------------------------------------------------
1 | import { withAuth } from 'next-auth/middleware';
2 |
3 | export default withAuth(function middleware(req) {}, {
4 | callbacks: {
5 | authorized: ({ token }) => !!token
6 | }
7 | });
8 |
9 | export const config = {
10 | matcher: ['/account/:path*', '/api/account/:path*', '/api/connected-apps/:path*']
11 | };
12 |
--------------------------------------------------------------------------------
/k8s/deployments/kbot-mirrors/twitch-chat-banphrased-message-logger-deployment.yaml:
--------------------------------------------------------------------------------
1 | kind: Service
2 | apiVersion: v1
3 | metadata:
4 | name: kbot-api
5 | labels:
6 | app: kbot-api
7 | spec:
8 | ports:
9 | - protocol: TCP
10 | port: 8080
11 | targetPort: 8080
12 | selector:
13 | app: kbot-api
14 | type: NodePort
15 |
16 |
--------------------------------------------------------------------------------
/lib/mirrors/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:20.12.2-alpine
2 |
3 | WORKDIR /usr/src/app
4 |
5 | COPY ./commons ./commons
6 | COPY ./consts ./consts
7 |
8 | WORKDIR /usr/src/app/lib/mirrors
9 |
10 | COPY ./lib/mirrors .
11 |
12 | EXPOSE 8080
13 |
14 | CMD [ "node", "mirrors.js" ]
15 |
16 | # for local development manual mount of node_modules in config is required
--------------------------------------------------------------------------------
/k8s/deployments/mysql/mysql-service-node-port.yaml:
--------------------------------------------------------------------------------
1 | kind: Service
2 | apiVersion: v1
3 | metadata:
4 | name: mysql
5 | namespace: default
6 | labels:
7 | app: mysql
8 | spec:
9 | ports:
10 | - protocol: TCP
11 | port: 3306
12 | targetPort: 3306
13 | nodePort: 30306
14 | selector:
15 | app: mysql
16 | type: NodePort
17 |
--------------------------------------------------------------------------------
/k8s/deployments/glk-promtail/glk-promtail-cluster-role-binding.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: rbac.authorization.k8s.io/v1
2 | kind: ClusterRoleBinding
3 | metadata:
4 | name: promtail
5 | roleRef:
6 | apiGroup: rbac.authorization.k8s.io
7 | kind: ClusterRole
8 | name: promtail
9 | subjects:
10 | - kind: ServiceAccount
11 | name: promtail
12 | namespace: logging
--------------------------------------------------------------------------------
/k8s/deployments/rabbitmq/rabbit-service-node-port.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: rabbitmq-service
5 | spec:
6 | selector:
7 | app: rabbitmq
8 | ports:
9 | - name: amqp
10 | port: 5672
11 | targetPort: 5672
12 | - name: management
13 | port: 15672
14 | targetPort: 15672
15 | type: NodePort
16 |
--------------------------------------------------------------------------------
/k8s/deployments/glk-grafana/glk-grafana-config-map.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ConfigMap
3 | metadata:
4 | name: grafana-datasources
5 | namespace: logging
6 | data:
7 | datasources.yaml: |
8 | apiVersion: 1
9 | datasources:
10 | - name: Loki
11 | type: loki
12 | access: proxy
13 | url: http://loki:3100
14 | isDefault: true
15 |
--------------------------------------------------------------------------------
/api/utils/initializeMethodRecurse.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash');
2 |
3 | const initializeMethodRecurse = (method, params) => {
4 | if (_.isPlainObject(method)) {
5 | _.forEach(method, invocation => {
6 | initializeMethodRecurse(invocation, params);
7 | });
8 | }
9 | else {
10 | method(params);
11 | }
12 | };
13 |
14 | module.exports = initializeMethodRecurse;
--------------------------------------------------------------------------------
/lib/mirrors/emote-checker/utils/getEmoteId.js:
--------------------------------------------------------------------------------
1 | const getEmoteId = (emoteId, emoteType) => {
2 | switch (emoteType) {
3 | case 'bttv':
4 | return { emoteId: null, sevenTvId: null };
5 | case '7tv':
6 | return { emoteId: null, sevenTvId: emoteId };
7 | case 'ffz':
8 | return { emoteId, sevenTvId: null };
9 | }
10 | };
11 |
12 | module.exports = getEmoteId;
13 |
--------------------------------------------------------------------------------
/k8s/deployments/glk-promtail/glk-promtail-cluster-role.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: rbac.authorization.k8s.io/v1
2 | kind: ClusterRole
3 | metadata:
4 | name: promtail
5 | rules:
6 | - apiGroups:
7 | - ""
8 | resources:
9 | - nodes
10 | - nodes/proxy
11 | - services
12 | - endpoints
13 | - pods
14 | verbs:
15 | - get
16 | - watch
17 | - list
18 |
--------------------------------------------------------------------------------
/api/utils/formatEmotesDate.js:
--------------------------------------------------------------------------------
1 | const Commons = require('../../commons/Commons');
2 |
3 | const formatEmotesDate = timestamp => {
4 | if (!timestamp) {
5 | return 'No emotes were recently updated.';
6 | }
7 | const time = Date.now() - Date.parse(timestamp);
8 | return `${Commons.UtilityRepository().humanizeDuration(time / 1000)} ago`;
9 | };
10 |
11 | module.exports = formatEmotesDate;
12 |
--------------------------------------------------------------------------------
/kbot-website/public/window.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/mirrors/emote-checker/utils/getEmoteDate.js:
--------------------------------------------------------------------------------
1 | const moment = require('moment');
2 |
3 | const getEmoteDate = timestamp => {
4 | if (!timestamp) {
5 | return null;
6 | }
7 |
8 | const date = moment(timestamp).format('YYYY-MM-DD HH:mm:ss');
9 |
10 | if (date === 'Invalid date') {
11 | return null;
12 | }
13 |
14 | return date;
15 | };
16 |
17 | module.exports = getEmoteDate;
18 |
--------------------------------------------------------------------------------
/k8s/deployments/music-bot/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.10-slim
2 |
3 | WORKDIR /usr/src/app
4 |
5 | RUN apt-get update && apt-get install -y \
6 | git \
7 | ffmpeg \
8 | && apt-get clean
9 |
10 | RUN git clone https://github.com/KUNszg/MusicBot
11 |
12 | WORKDIR /usr/src/app/MusicBot
13 |
14 | RUN pip install --no-cache-dir -r requirements.txt
15 |
16 | CMD ["python", "run.py"]
17 |
18 |
19 |
--------------------------------------------------------------------------------
/kbot-website/public/file.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/kbot-website/src/app/providers/SessionProvider.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { SessionProvider as NextAuthProvider } from 'next-auth/react';
4 | import { ReactNode } from 'react';
5 |
6 | interface SessionProviderProps {
7 | children: ReactNode;
8 | }
9 |
10 | export default function SessionProvider({ children }: SessionProviderProps) {
11 | return {children};
12 | }
13 |
--------------------------------------------------------------------------------
/api/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:20.12.2-alpine
2 |
3 | WORKDIR /usr/src/app
4 |
5 | COPY ./commons ./commons
6 | COPY ./consts ./consts
7 | COPY ./data ./data/
8 |
9 | COPY ./lib/credentials/config.js ./lib/credentials/config.js
10 |
11 | WORKDIR /usr/src/app/kbot-api
12 |
13 | COPY ./api .
14 |
15 | EXPOSE 8080
16 |
17 | CMD [ "node", "api.js" ]
18 |
19 | # for local development manual mount of node_modules in config is required
--------------------------------------------------------------------------------
/k8s/helpers/minikube-setup/minikube-startup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | minikube start --profile=kbot --driver=docker \
4 | --mount-string="/var/lib/mysql:/home/docker/mysql" \
5 | --mount-string="/home/kunszg/kbot-env/node_modules:/home/docker/kbot/node_modules" \
6 | --extra-config=apiserver.allow-privileged=true \
7 | --extra-config=kubelet.allowed-unsafe-sysctls=net.*
8 |
9 | minikube -p kbot addons enable metrics-server
10 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:20.12.2-alpine
2 |
3 | WORKDIR /usr/src/app
4 |
5 | COPY ./commons ./commons
6 | COPY ./consts ./consts
7 | COPY ./data ./data/
8 |
9 | COPY ./lib/credentials/config.js ./lib/credentials/config.js
10 |
11 | WORKDIR /usr/src/app/kbot-backend
12 |
13 | COPY ./lib .
14 |
15 | EXPOSE 8080
16 |
17 | CMD [ "node", "commandManager.js" ]
18 |
19 | # for local development manual mount of node_modules in config is required
--------------------------------------------------------------------------------
/lib/job-manager/tasks/refreshModuleStats.js:
--------------------------------------------------------------------------------
1 | const refreshModuleStats = async kb => {
2 | const modules = await kb.sqlClient.query('SELECT * FROM stats');
3 |
4 | if (!modules) {
5 | throw new Error("MySql connection error: query on table 'stats' returned no data");
6 | }
7 |
8 | await kb.redisClient.set(
9 | 'kb:global:stats',
10 | modules,
11 | 1e8
12 | );
13 | };
14 |
15 | module.exports = refreshModuleStats;
16 |
--------------------------------------------------------------------------------
/kbot-website/next.config.ts:
--------------------------------------------------------------------------------
1 | import type { NextConfig } from 'next';
2 | import path from 'path';
3 |
4 | const nextConfig: NextConfig = {
5 | webpack: config => {
6 | config.resolve.modules = [
7 | ...(config.resolve.modules || []),
8 | path.resolve(__dirname, 'node_modules'),
9 | path.resolve(__dirname, '../node_modules')
10 | ];
11 |
12 | return config;
13 | }
14 | };
15 |
16 | export default nextConfig;
17 |
--------------------------------------------------------------------------------
/api/src/site/pageCommandsCodeRedirect.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const _ = require('lodash');
3 |
4 | const pageCommandsCodeRedirect = services => {
5 | const { app } = services;
6 |
7 | app.get('/commands/code', async (req, res) => {
8 | res.send(
9 | _.toString(
10 | fs.readFileSync('../../kbot-website/html/express_pages/commandCodeRedirect.html')
11 | )
12 | );
13 | });
14 | };
15 |
16 | module.exports = pageCommandsCodeRedirect;
17 |
--------------------------------------------------------------------------------
/k8s/deployments/redis/redis-persistent-volume.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: PersistentVolume
3 | metadata:
4 | name: redis-pv
5 | spec:
6 | capacity:
7 | storage: 1Gi
8 | accessModes:
9 | - ReadWriteOnce
10 | hostPath:
11 | path: /mnt/data
12 | ---
13 | apiVersion: v1
14 | kind: PersistentVolumeClaim
15 | metadata:
16 | name: redis-pvc
17 | spec:
18 | accessModes:
19 | - ReadWriteOnce
20 | resources:
21 | requests:
22 | storage: 1Gi
23 |
--------------------------------------------------------------------------------
/lib/commands/ping/ping.js:
--------------------------------------------------------------------------------
1 | const ping = async ({}, { kb, Commons }) => {
2 | const responses = Commons.ResponseRepository().getResponses();
3 | const uptimeMs =
4 | (await kb.redisClient.get('kb:command-manager:botUptime')) || process.uptime();
5 | const uptimeFormatted = Commons.UtilityRepository().humanizeDuration(uptimeMs);
6 |
7 | return {
8 | response: responses.PING.SUCCESS.PONG(uptimeFormatted)
9 | };
10 | };
11 |
12 | module.exports.invocation = ping;
13 |
--------------------------------------------------------------------------------
/kbot-website/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import { dirname } from "path";
2 | import { fileURLToPath } from "url";
3 | import { FlatCompat } from "@eslint/eslintrc";
4 |
5 | const __filename = fileURLToPath(import.meta.url);
6 | const __dirname = dirname(__filename);
7 |
8 | const compat = new FlatCompat({
9 | baseDirectory: __dirname,
10 | });
11 |
12 | const eslintConfig = [
13 | ...compat.extends("next/core-web-vitals", "next/typescript"),
14 | ];
15 |
16 | export default eslintConfig;
17 |
--------------------------------------------------------------------------------
/lib/job-manager/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:20.12.2-alpine
2 |
3 | WORKDIR /usr/src/app
4 |
5 | COPY ./commons ./commons
6 | COPY ./consts ./consts
7 | COPY ./lib/credentials/config.js ./lib/credentials/config.js
8 |
9 | WORKDIR /usr/src/app/kbot-job-manager
10 |
11 | COPY ./lib/job-manager ./lib
12 |
13 | WORKDIR /usr/src/app/kbot-job-manager/lib
14 |
15 | EXPOSE 8080
16 |
17 | CMD [ "node", "taskManager.js" ]
18 |
19 | # for local development manual mount of node_modules in config is required
--------------------------------------------------------------------------------
/k8s/deployments/rabbitmq/rabbit-persistent-volume.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: PersistentVolume
3 | metadata:
4 | name: rabbitmq-pv
5 | spec:
6 | capacity:
7 | storage: 5Gi
8 | accessModes:
9 | - ReadWriteOnce
10 | hostPath:
11 | path: /mnt/data/rabbitmq
12 | ---
13 | apiVersion: v1
14 | kind: PersistentVolumeClaim
15 | metadata:
16 | name: rabbitmq-pvc
17 | spec:
18 | accessModes:
19 | - ReadWriteOnce
20 | resources:
21 | requests:
22 | storage: 5Gi
23 |
--------------------------------------------------------------------------------
/kbot-website/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "tailwindcss";
2 |
3 | export default {
4 | content: [
5 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
6 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
7 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
8 | ],
9 | theme: {
10 | extend: {
11 | colors: {
12 | background: "var(--background)",
13 | foreground: "var(--foreground)",
14 | },
15 | },
16 | },
17 | plugins: [],
18 | } satisfies Config;
19 |
--------------------------------------------------------------------------------
/kbot-website/src/app/components/DocsSection.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 |
3 | export default function DocsSection() {
4 | return (
5 |
6 |
Documentation
7 |
Learn how to integrate and use KsyncBot.
8 |
13 | View on GitHub
14 |
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/commons/connector/utils/prepareMessage.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash');
2 | const regex = require('../../../consts/regex');
3 |
4 | const endecrypt = require("./endecrypt");
5 |
6 | const prepareMessage = (messageChunk, lastMessage) => {
7 | messageChunk = _.join(messageChunk, '');
8 |
9 | const messageHash = _.get(endecrypt.encrypt(messageChunk), 'encryptedData');
10 |
11 | if (messageHash === lastMessage) {
12 | messageChunk += '\u{E0000}';
13 | }
14 |
15 | return messageChunk;
16 | };
17 |
18 | module.exports = prepareMessage;
19 |
--------------------------------------------------------------------------------
/lib/utils/consts/response-settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "channelsRestrictingLinks": [
3 | "forsen"
4 | ],
5 | "commandsWithAllowedMassping": [
6 | "hug",
7 | "dank"
8 | ],
9 | "receivedNoticeTypesToIgnore": [
10 | "raid",
11 | "unraid",
12 | "host_on",
13 | "host_off",
14 | "host_target_went_offline",
15 | "followers_on",
16 | "followers_off",
17 | "followers_on_zero",
18 | "r9k_on",
19 | "r9k_off",
20 | "giftpaidupgrade",
21 | "communitypayforward",
22 | "standardpayforward"
23 | ]
24 | }
--------------------------------------------------------------------------------
/k8s/deployments/rapidapi-solutions/ads-scraper/ads-scraper-deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: ads-scraper
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | app: ads-scraper
10 | template:
11 | metadata:
12 | labels:
13 | app: ads-scraper
14 | spec:
15 | containers:
16 | - name: ads-scraper
17 | image: docker-registry.kunszg.com/ads-scraper:latest
18 | imagePullPolicy: Always
19 | imagePullSecrets:
20 | - name: regcred-v1
21 |
--------------------------------------------------------------------------------
/lib/commands/music/subCommands/allow.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash');
2 |
3 | const allow = async (context, { kb, Commons }) => {
4 | const responses = Commons.ResponseRepository().getResponses();
5 | const userId = _.get(context, 'userstate.user-id');
6 |
7 | await kb.sqlClient.query(
8 | `
9 | UPDATE access_token
10 | SET allowLookup='Y'
11 | WHERE platform='spotify' AND user=?`,
12 | [userId]
13 | );
14 |
15 | return {
16 | response: responses.MUSIC.COMMANDS.ALLOW
17 | };
18 | };
19 |
20 | module.exports.invocation = allow;
21 |
--------------------------------------------------------------------------------
/lib/commands/music/subCommands/disallow.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash');
2 |
3 | const disallow = async (context, { kb, Commons }) => {
4 | const responses = Commons.ResponseRepository().getResponses();
5 | const userId = _.get(context, 'userstate.user-id');
6 |
7 | await kb.sqlClient.query(
8 | `
9 | UPDATE access_token
10 | SET allowLookup='N'
11 | WHERE platform='spotify' AND user=?`,
12 | [userId]
13 | );
14 |
15 | return {
16 | response: responses.MUSIC.COMMANDS.DISALLOW
17 | };
18 | };
19 |
20 | module.exports.invocation = disallow;
21 |
--------------------------------------------------------------------------------
/lib/commands_old/google.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict';
3 |
4 | const utils = require('../utils/utils.js');
5 |
6 | module.exports = {
7 | name: 'kb google',
8 | invocation: async (channel, user, message) => {
9 | if ((await utils.checkPermissions(user['username'])) < 1) {
10 | return '';
11 | }
12 |
13 | const msg = utils.getParam(message);
14 |
15 | if (!msg[0]) {
16 | return `${user['username']}, You have not provided any input FeelsDankMan`;
17 | }
18 |
19 | return encodeURI(`google.com/search?q=${msg.join(' ')}`);
20 | },
21 | };
22 |
--------------------------------------------------------------------------------
/lib/job-manager/tasks/pingSupibotActivityCheck.js:
--------------------------------------------------------------------------------
1 | const got = require('got');
2 | const _ = require('lodash');
3 |
4 | const pingSupibotActivityCheck = async kb => {
5 | const tokens = await kb.sqlClient.query(`
6 | SELECT user, access_token
7 | FROM access_token
8 | WHERE platform="supibot"`);
9 |
10 | const token = _.first(tokens);
11 |
12 | await got(
13 | `https://supinic.com/api/bot-program/bot/active?auth_user=${token.user}&auth_key=${token.access_token}`,
14 | {
15 | method: 'PUT',
16 | }
17 | ).json();
18 | };
19 |
20 | module.exports = pingSupibotActivityCheck;
21 |
--------------------------------------------------------------------------------
/consts/regex.js:
--------------------------------------------------------------------------------
1 | exports.racism = new RegExp(
2 | /(?:(?:\b(? {
4 | const channelsToUpdate = await kb.sqlClient.query(`
5 | SELECT userId, channel
6 | FROM channels_logger
7 | WHERE emotesUpdate < (NOW() - INTERVAL 1 DAY)
8 | AND status="enabled";
9 | `);
10 |
11 | _.map(channelsToUpdate, async channelMetaData => {
12 | await kb.rabbitClient.sendToQueue(
13 | 'KB_JOB_MANAGER_CHANNEL_TO_UPDATE_EMOTES',
14 | channelMetaData
15 | );
16 | });
17 | };
18 |
19 | module.exports = channelEmotesUpdaterQueueFill;
20 |
--------------------------------------------------------------------------------
/api/src/site/pageGenres.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const _ = require('lodash');
3 |
4 | const pageGenres = services => {
5 | const { app, Commons } = services;
6 |
7 | app.get('/genres', (req, res) => {
8 | const genres = fs.readFileSync('../data/genres.json');
9 |
10 | const html = _.toString(
11 | fs.readFileSync('../../kbot-website/html/express_pages/genres.html')
12 | );
13 |
14 | const page = Commons.UtilityRepository().complementHtmlPageTemplates(html, [
15 | {
16 | genres
17 | }
18 | ]);
19 |
20 | res.send(page);
21 | });
22 | };
23 |
24 | module.exports = pageGenres;
25 |
--------------------------------------------------------------------------------
/kbot-website/src/app/components/HomeSection.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { motion } from 'framer-motion';
4 | import React from 'react';
5 |
6 | interface HomeSectionProps {
7 | botName: React.ReactNode;
8 | }
9 |
10 | export default function HomeSection({ botName }: HomeSectionProps) {
11 | return (
12 |
13 |
19 | {botName}
20 |
21 |
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/k8s/helpers/minikube-setup/minikube-services.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=Minikube Dashboard, Port Forwards and Mounts
3 | After=network.target
4 |
5 | [Service]
6 | Type=simple
7 | User=youruser
8 | Environment="KUBECONFIG=/home/youruser/.kube/config"
9 | ExecStart=/usr/local/bin/minikube-services.sh
10 | Restart=always
11 | RestartSec=5
12 |
13 | [Install]
14 | WantedBy=multi-user.target
15 |
16 |
17 | # Replace 'youruser' with your username
18 | #sudo sed -i "s/youruser/$USER/g" /etc/systemd/system/minikube-services.service
19 |
20 | #sudo systemctl daemon-reload
21 | #sudo systemctl enable minikube-services
22 | #sudo systemctl start minikube-services
--------------------------------------------------------------------------------
/commons/repositories/ResponseRepository.js:
--------------------------------------------------------------------------------
1 | const responses = require('../../consts/responses');
2 |
3 | const CommonRepository = require('./CommonRepository');
4 |
5 | class ResponseRepository extends CommonRepository {
6 | constructor(serviceConnector = {}) {
7 | super(serviceConnector);
8 | }
9 |
10 | static getInstance(serviceConnector) {
11 | if (!ResponseRepository.instance) {
12 | ResponseRepository.instance = new ResponseRepository(serviceConnector);
13 | }
14 | return ResponseRepository.instance;
15 | }
16 |
17 | getResponses() {
18 | return responses;
19 | }
20 | }
21 |
22 | module.exports = ResponseRepository;
23 |
--------------------------------------------------------------------------------
/api/src/endpoints/randomEmoteGet.js:
--------------------------------------------------------------------------------
1 | const randomEmoteGet = services => {
2 | const { app, Commons } = services;
3 |
4 | app.get('/randomemote', async (req, res) => {
5 | const randomemote = await Commons.ServiceConnector.Connector.sqlClient.query(`
6 | SELECT *
7 | FROM emotes
8 | ORDER BY RAND()
9 | LIMIT 3`);
10 |
11 | res.send([
12 | { emote: randomemote[0].emote, emoteUrl: randomemote[0].url },
13 | { emote: randomemote[1].emote, emoteUrl: randomemote[1].url },
14 | { emote: randomemote[2].emote, emoteUrl: randomemote[2].url },
15 | ]);
16 | });
17 | };
18 |
19 | module.exports = randomEmoteGet;
20 |
--------------------------------------------------------------------------------
/kbot-website/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.*
7 | .yarn/*
8 | !.yarn/patches
9 | !.yarn/plugins
10 | !.yarn/releases
11 | !.yarn/versions
12 |
13 | # testing
14 | /coverage
15 |
16 | # next.js
17 | /.next/
18 | /out/
19 |
20 | # production
21 | /build
22 |
23 | # misc
24 | .DS_Store
25 | *.pem
26 |
27 | # debug
28 | npm-debug.log*
29 | yarn-debug.log*
30 | yarn-error.log*
31 | .pnpm-debug.log*
32 |
33 | # env files (can opt-in for committing if needed)
34 | .env*
35 |
36 | # vercel
37 | .vercel
38 |
39 | # typescript
40 | *.tsbuildinfo
41 | next-env.d.ts
42 |
--------------------------------------------------------------------------------
/k8s/deployments/kbot-website/kbot-website-deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: kbot-website
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | app: kbot-website
10 | template:
11 | metadata:
12 | labels:
13 | app: kbot-website
14 | spec:
15 | containers:
16 | - name: kbot-website
17 | image: docker-registry.kunszg.com/kbot-website:latest
18 | imagePullPolicy: Always
19 | envFrom:
20 | - configMapRef:
21 | name: kbot-config
22 | - secretRef:
23 | name: nextauth-secret
24 | imagePullSecrets:
25 | - name: regcred-v1
26 |
--------------------------------------------------------------------------------
/test/server.js:
--------------------------------------------------------------------------------
1 | const { Server } = require('socket.io');
2 | const http = require('http');
3 |
4 | const server = http.createServer();
5 | const io = new Server(server, {
6 | cors: {
7 | origin: '*',
8 | },
9 | });
10 |
11 | io.on('connection', socket => {
12 | console.log('Client connected');
13 |
14 | setInterval(() => {
15 | socket.emit('stats', {
16 | viewers: Math.floor(Math.random() * 500),
17 | messages: Math.floor(Math.random() * 1000),
18 | });
19 | }, 5000);
20 |
21 | socket.on('disconnect', () => {
22 | console.log('Client disconnected');
23 | });
24 | });
25 |
26 | server.listen(4000, () => {
27 | console.log('WebSocket server running on port 4000');
28 | });
29 |
--------------------------------------------------------------------------------
/kbot-website/types/next-auth.d.ts:
--------------------------------------------------------------------------------
1 | import { DefaultSession } from 'next-auth';
2 |
3 | declare module 'next-auth' {
4 | interface Session {
5 | user: {
6 | id: string;
7 | username: string;
8 | email: string;
9 | image: string;
10 | name?: string | null;
11 | } & DefaultSession['user'];
12 | accessToken: string;
13 | }
14 |
15 | interface User {
16 | id: string;
17 | username: string;
18 | email: string;
19 | image: string;
20 | }
21 | }
22 |
23 | declare module 'next-auth/jwt' {
24 | interface JWT {
25 | id: string;
26 | username: string;
27 | email: string;
28 | image: string;
29 | accessToken: string;
30 | refreshToken: string;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/kbot-website/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2017",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./src/*"]
23 | }
24 | },
25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------
/lib/commands_old/npm.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict';
3 |
4 | const utils = require('../utils/utils.js');
5 |
6 | module.exports = {
7 | name: 'kb npm',
8 | invocation: async (channel, user, message) => {
9 | try {
10 | if ((await utils.checkPermissions(user['username'])) < 1) {
11 | return '';
12 | }
13 |
14 | const msg = utils.getParam(message);
15 |
16 | if (!msg[0]) {
17 | return `${user['username']}, You have not provided any input FeelsDankMan`;
18 | }
19 |
20 | return 'https://' + encodeURI(`www.npmjs.com/search?q=${msg.join(' ')}`);
21 | } catch (err) {
22 | utils.errorLog(err);
23 | return `${user['username']}, ${err} FeelsDankMan !!!`;
24 | }
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/lib/job-manager/tasks/refreshViewCount.js:
--------------------------------------------------------------------------------
1 | const refreshViewCount = async kb => {
2 | const channelsViewCount = await kb.sqlClient.query(
3 | 'SELECT viewerCount FROM channels WHERE viewerCount > 0'
4 | );
5 |
6 | if (!channelsViewCount) {
7 | throw new Error("MySql connection error: query on table 'channels' returned no data");
8 | }
9 |
10 | if (channelsViewCount.length) {
11 | let totalViewCount = channelsViewCount.map(i => Number(i.viewerCount));
12 |
13 | totalViewCount = totalViewCount.reduce((accumulator, curr) => accumulator + curr);
14 |
15 | kb.websocketClient.websocketEmitter.emit('wsl', {
16 | type: 'totalViewCount',
17 | data: totalViewCount,
18 | });
19 | }
20 | };
21 |
22 | module.exports = refreshViewCount;
23 |
--------------------------------------------------------------------------------
/kbot-website/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:20.12.2-alpine AS builder
2 |
3 | WORKDIR /build
4 |
5 | COPY commons ./commons
6 | COPY lib ./lib
7 | COPY kbot-website ./kbot-website
8 |
9 | WORKDIR /build/kbot-website
10 |
11 | RUN npm install
12 |
13 | RUN npm run build
14 |
15 | FROM node:20.12.2-alpine AS runner
16 |
17 | WORKDIR /app
18 |
19 | ENV NODE_ENV=production
20 |
21 | COPY --from=builder /build/kbot-website/package.json ./
22 | COPY --from=builder /build/kbot-website/.next ./.next
23 | COPY --from=builder /build/kbot-website/node_modules ./node_modules
24 | COPY --from=builder /build/kbot-website/public ./public
25 |
26 | COPY --from=builder /build/commons ../commons
27 | COPY --from=builder /build/lib ../lib
28 |
29 | EXPOSE 3000
30 |
31 | CMD ["npm", "run", "start"]
--------------------------------------------------------------------------------
/lib/commands_old/github.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict';
3 |
4 | const utils = require('../utils/utils.js');
5 |
6 | module.exports = {
7 | name: 'kb github',
8 | invocation: async (channel, user) => {
9 | try {
10 | const commitDate = await utils.query(`
11 | SELECT *
12 | FROM stats
13 | WHERE type="ping"
14 | `);
15 |
16 | const diff = Math.abs(commitDate[0].date - new Date());
17 |
18 | return `${user['username']}, my public repo Okayga 👉 https://github.com/KUNszg/kbot
19 | last commit: ${utils.humanizeDuration(diff / 1000)} ago`;
20 | } catch (err) {
21 | utils.errorLog(err);
22 | return `${user['username']}, ${err} FeelsDankMan !!!`;
23 | }
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/k8s/deployments/redis/redis-deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: StatefulSet
3 | metadata:
4 | name: redis
5 | spec:
6 | serviceName: "redis"
7 | replicas: 1
8 | selector:
9 | matchLabels:
10 | app: redis
11 | template:
12 | metadata:
13 | labels:
14 | app: redis
15 | spec:
16 | containers:
17 | - name: redis
18 | image: redis:7.2.4
19 | ports:
20 | - containerPort: 6379
21 | name: redis
22 | volumeMounts:
23 | - name: redis-storage
24 | mountPath: /data
25 | volumeClaimTemplates:
26 | - metadata:
27 | name: redis-storage
28 | spec:
29 | accessModes: [ "ReadWriteOnce" ]
30 | resources:
31 | requests:
32 | storage: 1Gi
33 |
--------------------------------------------------------------------------------
/lib/commands/music/subCommands/queue.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash');
2 |
3 | const executeSpotifyAction = require('../utils/executeSpotifyAction');
4 |
5 | const queue = async (context, { Commons }) => {
6 | const responses = Commons.ResponseRepository().getResponses();
7 |
8 | const action = {
9 | type: 'queue',
10 | execute: async (Commons, track, userId) => {
11 | await Commons.UserRepository().spotifyFetchWithOauth(
12 | 'POST',
13 | `/v1/me/player/queue?uri=${_.get(track, 'uri')}`,
14 | {
15 | userId,
16 | isSender: true
17 | }
18 | );
19 | },
20 | responseMessage: responses.MUSIC.SUCCESS.QUEUED
21 | };
22 |
23 | return executeSpotifyAction(context, { Commons }, action);
24 | };
25 |
26 | module.exports.invocation = queue;
27 |
--------------------------------------------------------------------------------
/commons/connector/services/healthcheckMiddleware.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const _ = require('lodash');
3 |
4 | const { healthcheckPath } = require('../../../consts/serviceSettings.json');
5 |
6 | const healthcheckMiddleware = serviceSettings => {
7 | const serviceName = _.get(serviceSettings, 'name');
8 | const servicePort = _.get(serviceSettings, 'port');
9 |
10 | const app = express();
11 |
12 | app.get(healthcheckPath + serviceName, (req, res) => {
13 | const memoryUsage = process.memoryUsage();
14 |
15 | res.json({
16 | status: 200,
17 | message: 'healthy',
18 | serviceName,
19 | serviceHeapUsageMB: (memoryUsage.heapUsed / (1024 * 1024)).toFixed(2),
20 | });
21 | });
22 |
23 | app.listen(servicePort);
24 | };
25 |
26 | module.exports = healthcheckMiddleware;
--------------------------------------------------------------------------------
/lib/commands/music/subCommands/play.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash');
2 |
3 | const executeSpotifyAction = require('../utils/executeSpotifyAction');
4 |
5 | const play = async (context, { Commons }) => {
6 | const responses = Commons.ResponseRepository().getResponses();
7 |
8 | const action = {
9 | type: 'play',
10 | execute: async (Commons, track, userId) => {
11 | const payload = { uris: [_.get(track, 'uri')], position_ms: 0 };
12 | await Commons.UserRepository().spotifyFetchWithOauth('PUT', `/v1/me/player/play`, {
13 | userId,
14 | isSender: true,
15 | payload
16 | });
17 | },
18 | responseMessage: responses.MUSIC.SUCCESS.NOW_PLAYING
19 | };
20 |
21 | return executeSpotifyAction(context, { Commons }, action);
22 | };
23 |
24 | module.exports.invocation = play;
25 |
--------------------------------------------------------------------------------
/lib/mirrors/mirrors.js:
--------------------------------------------------------------------------------
1 | if (process.env.APP_NAME === "UPDATE_EMOTES_LIST") {
2 | require("./emote-checker/updateEmotesList")
3 | }
4 |
5 | if (process.env.APP_NAME === "REDDIT_LIVE_THREAD_TO_DISCORD") {
6 | require("./reddit-live-thread-to-discord/redditLiveThreadToDiscord")
7 | }
8 |
9 | if (process.env.APP_NAME === "TWITCH_CHAT_BANPHRASED_MESSAGE_LOGGER") {
10 | require("./twitch-chat-logger/twitchChatBanphrasedMessageLogger")
11 | }
12 |
13 | if (process.env.APP_NAME === "TWITCH_CHAT_MESSAGE_LOGGER") {
14 | require("./twitch-chat-logger/twitchChatMessageLogger")
15 | }
16 |
17 | if (process.env.APP_NAME === "TWITCH_CHAT_NOTICE_LOGGER") {
18 | require("./twitch-chat-logger/twitchChatNoticeLogger")
19 | }
20 |
21 | if (process.env.APP_NAME === "TWITCH_CHAT_QUEUE_FILLER") {
22 | require("./twitch-chat-logger/twitchChatQueueFiller")
23 | }
--------------------------------------------------------------------------------
/lib/job-manager/tasks/cleanExpiredRegistrationAttempts.js:
--------------------------------------------------------------------------------
1 | const cleanExpiredRegistrationAttempts = async kb => {
2 | await kb.sqlClient.query(`
3 | DELETE FROM access_token
4 | WHERE platform="spotify"
5 | AND date < now() - interval 15 MINUTE
6 | AND user IS NULL
7 | AND code != "Resolved"`);
8 |
9 | await kb.sqlClient.query(`
10 | DELETE FROM access_token
11 | WHERE platform="lastfm"
12 | AND date < now() - interval 15 MINUTE
13 | AND code != "lastfm"
14 | AND userName IS NULL`);
15 |
16 | await kb.sqlClient.query(`
17 | DELETE FROM access_token
18 | WHERE platform IS NULL
19 | AND DATE < now() - interval 15 MINUTE
20 | AND user IS NULL
21 | AND code != "Resolved"
22 | AND refresh_token IS NULL`);
23 | };
24 |
25 | module.exports = cleanExpiredRegistrationAttempts;
26 |
--------------------------------------------------------------------------------
/lib/job-manager/tasks/refreshChannelList.js:
--------------------------------------------------------------------------------
1 | const refreshChannelList = async kb => {
2 | const channels = await kb.sqlClient.query('SELECT channel FROM channels');
3 | const channelsLogger = await kb.sqlClient.query(`SELECT * FROM channels_logger`);
4 |
5 | if (!channels) {
6 | throw new Error("MySql connection error: query on table 'channels' returned no data");
7 | }
8 |
9 | await kb.redisClient.set(
10 | 'kb:global:channel-list',
11 | channels.map(channel => channel.channel),
12 | 1e8
13 | );
14 |
15 | await kb.redisClient.set(
16 | 'kb:global:channel-logger-list',
17 | channelsLogger.map(channel => channel.channel),
18 | 1e8
19 | );
20 |
21 | await kb.redisClient.set(
22 | 'kb:global:channel-logger-list:detail',
23 | channelsLogger,
24 | 1e8
25 | );
26 | };
27 |
28 | module.exports = refreshChannelList;
29 |
--------------------------------------------------------------------------------
/lib/commands_old/rf.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict';
3 |
4 | const utils = require('../utils/utils.js');
5 |
6 | module.exports = {
7 | name: 'kb rf',
8 | invocation: async (channel, user, message, platform) => {
9 | try {
10 | const got = require('got');
11 |
12 | if ((channel === '#kiansly' || channel === '#phayp') && platform != 'whisper') {
13 | const res = await got('https://uselessfacts.jsph.pl/random.json?language=de').json();
14 | return `${user['username']}, ${res.text.toLowerCase()} 🤔`;
15 | }
16 |
17 | const res = await got('https://uselessfacts.jsph.pl/random.json?language=en').json();
18 | return `${user['username']}, ${res.text.toLowerCase()} 🤔`;
19 | } catch (err) {
20 | utils.errorLog(err);
21 | return `${user['username']}, ${err} FeelsDankMan !!!`;
22 | }
23 | },
24 | };
25 |
--------------------------------------------------------------------------------
/api/utils/expressSetup.js:
--------------------------------------------------------------------------------
1 | const rateLimit = require('express-rate-limit');
2 | const bodyParser = require('body-parser');
3 |
4 | const expressSetup = (app, webhookHandler, sqlClient) => {
5 | const apiLimiter = rateLimit({
6 | windowMs: 60 * 1000,
7 | max: 100,
8 | });
9 |
10 | app.enable('trust proxy');
11 | app.set('trust proxy', 1);
12 |
13 | app.use('/api/', apiLimiter);
14 |
15 | app.use(async function (req, res, next) {
16 | await sqlClient.query(
17 | `
18 | INSERT INTO web_connections (url, method, protocol, route, userAgent, date)
19 | VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
20 | [req.originalUrl, req.method, req.protocol, 'API', req.headers['user-agent']]
21 | );
22 | next();
23 | });
24 |
25 | app.use(bodyParser.json());
26 | app.use(webhookHandler);
27 | };
28 |
29 | module.exports = expressSetup;
30 |
--------------------------------------------------------------------------------
/kbot-website/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from 'next';
2 | import React from 'react';
3 | import { Geist, Geist_Mono } from 'next/font/google';
4 |
5 | import './globals.css';
6 |
7 | const geistSans = Geist({
8 | variable: '--font-geist-sans',
9 | subsets: ['latin']
10 | });
11 |
12 | const geistMono = Geist_Mono({
13 | variable: '--font-geist-mono',
14 | subsets: ['latin']
15 | });
16 |
17 | export const metadata: Metadata = {
18 | title: 'KsyncBot',
19 | description: 'Utility bot for Twitch and Discord chats'
20 | };
21 |
22 | export default function RootLayout({
23 | children
24 | }: Readonly<{
25 | children: React.ReactNode;
26 | }>) {
27 | return (
28 |
29 |
30 | {children}
31 |
32 |
33 | );
34 | }
35 |
--------------------------------------------------------------------------------
/lib/commands/music/utils/userValidator.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash');
2 |
3 | const validateUser = async (context, { Commons }, username = null) => {
4 | const responses = Commons.ResponseRepository().getResponses();
5 |
6 | const getUser = username ? await Commons.UserRepository().getUser({ username }) : null;
7 |
8 | const userId = _.get(getUser, 'userId') || _.get(context, 'userstate.user-id');
9 |
10 | if (_.isEmpty(userId)) {
11 | return { error: true, response: responses.COMMON.ERRORS.USER_NOT_FOUND };
12 | }
13 |
14 | const isUserOptedOut = await Commons.UserRepository().isUserOptedOut('music', { userId });
15 |
16 | if (isUserOptedOut && _.get(context, 'userstate.user-id') !== userId) {
17 | return { error: true, response: responses.COMMON.ERRORS.OPTED_OUT };
18 | }
19 |
20 | return { error: false, userId, username };
21 | };
22 |
23 | module.exports = validateUser;
24 |
--------------------------------------------------------------------------------
/k8s/deployments/kbot-api/kbot-api-deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: kbot-api
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | app: kbot-api
10 | template:
11 | metadata:
12 | labels:
13 | app: kbot-api
14 | spec:
15 | containers:
16 | - name: kbot-api
17 | image: docker-registry.kunszg.com/kbot-api:latest
18 | imagePullPolicy: Always
19 | volumeMounts:
20 | - name: node-modules-volume
21 | mountPath: /usr/src/app/node_modules
22 | envFrom:
23 | - configMapRef:
24 | name: kbot-config
25 | imagePullSecrets:
26 | - name: regcred-v1
27 | volumes:
28 | - name: node-modules-volume
29 | hostPath:
30 | path: /home/docker/kbot/node_modules
31 | type: Directory
32 |
--------------------------------------------------------------------------------
/k8s/deployments/kbot-mirrors/update-emotes-list-deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: kbot-api
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | app: kbot-api
10 | template:
11 | metadata:
12 | labels:
13 | app: kbot-api
14 | spec:
15 | containers:
16 | - name: kbot-api
17 | image: docker-registry.kunszg.com/kbot-api:latest
18 | imagePullPolicy: Always
19 | volumeMounts:
20 | - name: node-modules-volume
21 | mountPath: /usr/src/app/node_modules
22 | envFrom:
23 | - configMapRef:
24 | name: kbot-config
25 | imagePullSecrets:
26 | - name: regcred-v1
27 | volumes:
28 | - name: node-modules-volume
29 | hostPath:
30 | path: /home/docker/node_modules
31 | type: Directory
32 |
--------------------------------------------------------------------------------
/k8s/deployments/kbot-mirrors/reddit-live-thread-to-discord-deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: kbot-api
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | app: kbot-api
10 | template:
11 | metadata:
12 | labels:
13 | app: kbot-api
14 | spec:
15 | containers:
16 | - name: kbot-api
17 | image: docker-registry.kunszg.com/kbot-api:latest
18 | imagePullPolicy: Always
19 | volumeMounts:
20 | - name: node-modules-volume
21 | mountPath: /usr/src/app/node_modules
22 | envFrom:
23 | - configMapRef:
24 | name: kbot-config
25 | imagePullSecrets:
26 | - name: regcred-v1
27 | volumes:
28 | - name: node-modules-volume
29 | hostPath:
30 | path: /home/docker/node_modules
31 | type: Directory
32 |
--------------------------------------------------------------------------------
/k8s/deployments/kbot-mirrors/reddit-live-thread-to-discord-service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: kbot-api
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | app: kbot-api
10 | template:
11 | metadata:
12 | labels:
13 | app: kbot-api
14 | spec:
15 | containers:
16 | - name: kbot-api
17 | image: docker-registry.kunszg.com/kbot-api:latest
18 | imagePullPolicy: Always
19 | volumeMounts:
20 | - name: node-modules-volume
21 | mountPath: /usr/src/app/node_modules
22 | envFrom:
23 | - configMapRef:
24 | name: kbot-config
25 | imagePullSecrets:
26 | - name: regcred-v1
27 | volumes:
28 | - name: node-modules-volume
29 | hostPath:
30 | path: /home/docker/node_modules
31 | type: Directory
32 |
--------------------------------------------------------------------------------
/k8s/deployments/kbot-backend/kbot-backend-deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: kbot-backend
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | app: kbot-backend
10 | template:
11 | metadata:
12 | labels:
13 | app: kbot-backend
14 | spec:
15 | containers:
16 | - name: kbot-backend
17 | image: docker-registry.kunszg.com/kbot-backend:latest
18 | imagePullPolicy: Always
19 | volumeMounts:
20 | - name: node-modules-volume
21 | mountPath: /usr/src/app/node_modules
22 | envFrom:
23 | - configMapRef:
24 | name: kbot-config
25 | imagePullSecrets:
26 | - name: regcred-v1
27 | volumes:
28 | - name: node-modules-volume
29 | hostPath:
30 | path: /home/docker/kbot/node_modules
31 | type: Directory
32 |
--------------------------------------------------------------------------------
/api/src/webhookHandlerWildcard.js:
--------------------------------------------------------------------------------
1 | const handleGithubWebhookMessage = require('../utils/handleGithubWebhookMessage');
2 |
3 | const webhookHandlerWildcard = services => {
4 | const { Commons, webhookHandler } = services;
5 |
6 | const kb = Commons.ServiceConnector.Connector;
7 |
8 | webhookHandler.on('*', async function (event, repo, data, head) {
9 | kb.websocketClient.websocketEmitter.emit('wsl', {
10 | type: 'github',
11 | data: [{ event }, { repo }, { data }, { head }],
12 | });
13 |
14 | const githubWebhookTwitchResponse = await handleGithubWebhookMessage(
15 | { kb },
16 | event,
17 | repo,
18 | data
19 | );
20 |
21 | if (githubWebhookTwitchResponse) {
22 | await kb.tmiClient.sender.say(
23 | Commons.CommonRepository.botUsername,
24 | githubWebhookTwitchResponse
25 | );
26 | }
27 | });
28 | };
29 |
30 | module.exports = webhookHandlerWildcard;
31 |
--------------------------------------------------------------------------------
/k8s/cron-jobs/mysql-backup/manifest.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: batch/v1
2 | kind: CronJob
3 | metadata:
4 | name: mysql-backup
5 | namespace: services
6 | spec:
7 | schedule: "0 0 * * *"
8 | jobTemplate:
9 | spec:
10 | template:
11 | spec:
12 | containers:
13 | - name: mysql-backup
14 | image: docker-registry.kunszg.com/kbot-cron-mysql-backup:latest
15 | volumeMounts:
16 | - name: node-modules-volume
17 | mountPath: /usr/src/app/mysql-backup/node_modules
18 | envFrom:
19 | - configMapRef:
20 | name: mysql-backup-config
21 | imagePullSecrets:
22 | - name: regcred-v1
23 | restartPolicy: OnFailure
24 | volumes:
25 | - name: node-modules-volume
26 | hostPath:
27 | path: /home/docker/kbot/node_modules
28 | - name: mysqld-volume
--------------------------------------------------------------------------------
/lib/commands/music/utils/spotifyErrorResponses.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash');
2 |
3 | const handleSpotifyError = (error, username, Commons) => {
4 | const responses = Commons.ResponseRepository().getResponses();
5 |
6 | if (error.message === 'Spotify Oauth token not found') {
7 | return { response: responses.COMMON.ERRORS.NOT_REGISTERED(username) };
8 | }
9 |
10 | if (error.message === 'Spotify Oauth token could not be refreshed') {
11 | return { response: responses.MUSIC.ERRORS.TOKEN_REFRESH_FAILED };
12 | }
13 |
14 | if (error.message === 'Spotify lookup not allowed') {
15 | return { response: responses.MUSIC.ERRORS.LOOKUP_NOT_ALLOWED };
16 | }
17 |
18 | if (
19 | _.includes(_.get(error, 'response.body'), 'Player command failed: No active device found')
20 | ) {
21 | return { response: responses.MUSIC.ERRORS.NO_ACTIVE_DEVICE };
22 | }
23 |
24 | throw error;
25 | };
26 |
27 | module.exports = handleSpotifyError;
28 |
--------------------------------------------------------------------------------
/k8s/deployments/kbot-task-manager/kbot-task-manager-deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: kbot-job-manager
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | app: kbot-job-manager
10 | template:
11 | metadata:
12 | labels:
13 | app: kbot-job-manager
14 | spec:
15 | containers:
16 | - name: kbot-job-manager
17 | image: docker-registry.kunszg.com/kbot-job-manager:latest
18 | imagePullPolicy: Always
19 | volumeMounts:
20 | - name: node-modules-volume
21 | mountPath: /usr/src/app/node_modules
22 | envFrom:
23 | - configMapRef:
24 | name: kbot-config
25 | imagePullSecrets:
26 | - name: regcred-v1
27 | volumes:
28 | - name: node-modules-volume
29 | hostPath:
30 | path: /home/docker/kbot/node_modules
31 | type: Directory
32 |
--------------------------------------------------------------------------------
/api/src/site/pageConnections.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const _ = require('lodash');
3 |
4 | const pageConnections = services => {
5 | const { app, Commons } = services;
6 |
7 | const kb = Commons.ServiceConnector.Connector;
8 |
9 | app.get('/connections', async (req, res) => {
10 | const [commandExecutionsCount, spotifyAndLastfmUserLoggedInCount] = await kb
11 | .multi()
12 | .get('kb:api:website-pages:command-executions-count')
13 | .get('kb:api:website-pages:spotify-and-lastfm-user-logged-in-count')
14 | .exec();
15 |
16 | const html = _.toString(
17 | fs.readFileSync('../../kbot-website/html/express_pages/connections.html')
18 | );
19 |
20 | const page = Commons.UtilityRepository().complementHtmlPageTemplates(html, [
21 | {
22 | execs: commandExecutionsCount,
23 | users: spotifyAndLastfmUserLoggedInCount
24 | }
25 | ]);
26 |
27 | res.send(page);
28 | });
29 | };
30 |
31 | module.exports = pageConnections;
32 |
--------------------------------------------------------------------------------
/commons/connector/utils/endecrypt.js:
--------------------------------------------------------------------------------
1 | const crypto = require('crypto');
2 |
3 | const key = crypto.randomBytes(32);
4 |
5 | const iv = crypto.randomBytes(16);
6 |
7 | const endecrypt = (text, algorithm = 'aes-256-cbc') => {
8 | let cipher = crypto.createCipheriv(algorithm, Buffer.from(key), iv);
9 | let encrypted = cipher.update(text);
10 |
11 | encrypted = Buffer.concat([encrypted, cipher.final()]);
12 |
13 | return { iv: iv.toString('hex'), encryptedData: encrypted.toString('hex'), algorithm };
14 | };
15 |
16 | const decrypt = text => {
17 | let iv = Buffer.from(text.iv, 'hex');
18 | let encryptedText = Buffer.from(text.encryptedData, 'hex');
19 | let algorithm = text.algorithm;
20 |
21 | let decipher = crypto.createDecipheriv(algorithm, Buffer.from(key), iv);
22 |
23 | let decrypted = decipher.update(encryptedText);
24 | decrypted = Buffer.concat([decrypted, decipher.final()]);
25 |
26 | return decrypted.toString();
27 | };
28 |
29 | module.exports = { encrypt: endecrypt, decrypt };
30 |
--------------------------------------------------------------------------------
/lib/commands/music/utils/searchSpotifySong.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash');
2 |
3 | const searchSpotifySong = async (spotifyFetchWithOauth, message, userId, Commons) => {
4 | const responses = Commons.ResponseRepository().getResponses();
5 |
6 | const searchResult = await spotifyFetchWithOauth(
7 | 'GET',
8 | `/v1/search?q=${_.join(message, '%20')}&type=track&limit=1`,
9 | {
10 | userId,
11 | isSender: true
12 | }
13 | );
14 |
15 | if (!_.get(searchResult, 'spotifyUser.isPremium')) {
16 | return {
17 | error: true,
18 | response: responses.MUSIC.ERRORS.NO_PREMIUM
19 | };
20 | }
21 |
22 | const spotifySearchEndpointResponse = _.get(searchResult, 'endpointResponse');
23 |
24 | if (!_.size(_.get(spotifySearchEndpointResponse, 'tracks.items'))) {
25 | return {
26 | error: true,
27 | response: responses.MUSIC.ERRORS.NO_TRACKS_FOUND
28 | };
29 | }
30 |
31 | return searchResult;
32 | };
33 |
34 | module.exports = searchSpotifySong;
35 |
--------------------------------------------------------------------------------
/lib/commands_old/bible.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict';
3 |
4 | const got = require('got');
5 | const utils = require('../utils/utils.js');
6 |
7 | module.exports = {
8 | name: 'kb bible',
9 | invocation: async (channel, user, message) => {
10 | try {
11 | const api = await utils.Get.api().url(message);
12 | const randomBibleVerse = await got(api).json();
13 |
14 | if (`${randomBibleVerse[0].chapter}:${randomBibleVerse[0].verse}` === '18:22') {
15 | return `${user['username']}, chapter 18:22 is kinda TOS monkaS`;
16 | }
17 |
18 | return (
19 | user['username'] +
20 | ', ' +
21 | randomBibleVerse[0].bookname +
22 | ': ' +
23 | randomBibleVerse[0].text +
24 | ' ' +
25 | randomBibleVerse[0].chapter +
26 | ':' +
27 | randomBibleVerse[0].verse
28 | );
29 | } catch (err) {
30 | utils.errorLog(err);
31 | return `${user['username']}, ${err} FeelsDankMan !!!`;
32 | }
33 | },
34 | };
35 |
--------------------------------------------------------------------------------
/kbot-website/public/globe.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/job-manager/tasks/refreshAliasList.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash');
2 |
3 | const refreshAliasList = async kb => {
4 | let commands = await kb.sqlClient.query('SELECT aliases, command FROM commands');
5 |
6 | commands = _.compact(commands);
7 |
8 | if (!commands) {
9 | throw new Error("MySql connection error: query on table 'commands' returned no data");
10 | }
11 |
12 | let aliases = [];
13 |
14 | for (const command of commands) {
15 | aliases.push(_.split(_.replace(command.aliases, /\//g, command.command), ';'));
16 | }
17 |
18 | aliases = [].concat.apply([], aliases);
19 |
20 | aliases = aliases.map(aliasRaw => {
21 | const separateAlias = _.split(aliasRaw, '>');
22 |
23 | const key = _.first(separateAlias);
24 | const value = _.get(separateAlias, '1');
25 |
26 | return !!key ? { key, value } : null;
27 | });
28 |
29 | aliases = _.compact(aliases);
30 |
31 | await kb.redisClient.set('kb:global:aliases', aliases, 1e8);
32 | };
33 |
34 | module.exports = refreshAliasList;
35 |
--------------------------------------------------------------------------------
/kbot-website/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "kbot-website",
3 | "version": "1.0.3",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev --turbopack",
7 | "build": "next build",
8 | "start": "next start",
9 | "run-with-build": "next build && next start",
10 | "lint": "next lint"
11 | },
12 | "dependencies": {
13 | "framer-motion": "^12.4.7",
14 | "lodash": "^4.17.21",
15 | "mysql2": "^3.15.2",
16 | "next": "15.4.7",
17 | "next-auth": "^4.24.11",
18 | "react": "^19.0.0",
19 | "react-dom": "^19.0.0",
20 | "react-icons": "^5.5.0",
21 | "socket.io-client": "^4.8.1",
22 | "spotify-web-api-node": "^5.0.2"
23 | },
24 | "devDependencies": {
25 | "@eslint/eslintrc": "^3",
26 | "@types/node": "^20",
27 | "@types/react": "^19",
28 | "@types/react-dom": "^19",
29 | "@types/three": "^0.173.0",
30 | "eslint": "^9",
31 | "eslint-config-next": "15.1.7",
32 | "postcss": "^8",
33 | "tailwindcss": "^3.4.1",
34 | "typescript": "^5"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/api/src/site/pageCommandsCode.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const _ = require('lodash');
3 |
4 | const pageCommandsCode = services => {
5 | const { app, Commons } = services;
6 |
7 | app.get('/commands/code/:commandName', async (req, res) => {
8 | const query = req.params.commandName;
9 |
10 | if (query) {
11 | try {
12 | const requestedFile = fs.readFileSync(`../lib/commands/${query}.js`);
13 |
14 | let html = _.toString(
15 | fs.readFileSync('../../kbot-website/html/express_pages/commandCode.html')
16 | );
17 |
18 | const page = Commons.UtilityRepository().complementHtmlPageTemplates(html, [
19 | {
20 | requestedFile,
21 | query
22 | }
23 | ]);
24 |
25 | res.send(page);
26 | } catch (err) {
27 | res.send('Error: command not found
');
28 | }
29 | } else {
30 | res.send('Error: command not found
');
31 | }
32 | });
33 | };
34 |
35 | module.exports = pageCommandsCode;
36 |
--------------------------------------------------------------------------------
/k8s/deployments/mysql/mysql-deployment.yaml:
--------------------------------------------------------------------------------
1 | kind: Deployment
2 | apiVersion: apps/v1
3 | metadata:
4 | name: mysql
5 | namespace: default
6 | labels:
7 | app: mysql
8 | spec:
9 | replicas: 1
10 | selector:
11 | matchLabels:
12 | app: mysql
13 | template:
14 | metadata:
15 | labels:
16 | app: mysql
17 | spec:
18 | volumes:
19 | - name: mysql-storage
20 | hostPath:
21 | path: /home/docker/mysql
22 | containers:
23 | - name: mysql
24 | image: mysql:8.0.36
25 | ports:
26 | - name: mysql
27 | containerPort: 3306
28 | protocol: TCP
29 | envFrom:
30 | - configMapRef:
31 | name: mysql-server-config
32 | volumeMounts:
33 | - name: mysql-storage
34 | mountPath: /var/lib/mysql
35 | imagePullPolicy: IfNotPresent
36 | restartPolicy: Always
37 | terminationGracePeriodSeconds: 30
38 | dnsPolicy: ClusterFirst
39 |
--------------------------------------------------------------------------------
/lib/mirrors/emote-checker/utils/ffzEmoteCheck.js:
--------------------------------------------------------------------------------
1 | const got = require('got');
2 | const _ = require('lodash');
3 |
4 | const ffzEmoteCheck = async userId => {
5 | let result = null;
6 |
7 | try {
8 | result = await got({
9 | method: 'GET',
10 | url: `https://api.frankerfacez.com/v1/room/id/${userId}`,
11 | }).json();
12 | } catch (err) {
13 | console.error({
14 | message: 'Error while fetching ffz emotes for channel',
15 | userId,
16 | timestamp: new Date(),
17 | });
18 | }
19 |
20 | const set = _.get(result, 'room.set');
21 | const emotes = _.get(result, `sets.${set}.emoticons`);
22 |
23 | return _.map(emotes, (emote = {}) => {
24 | const name = _.get(emote, 'name');
25 | const id = _.get(emote, 'id');
26 |
27 | if (!!name && !!id) {
28 | return {
29 | type: 'ffz',
30 | name,
31 | id,
32 | timestamp: null,
33 | emotePicture: `https://cdn.frankerfacez.com/emote/${id}/1`,
34 | };
35 | }
36 | });
37 | };
38 |
39 | module.exports = ffzEmoteCheck;
40 |
--------------------------------------------------------------------------------
/lib/mirrors/emote-checker/utils/seventvEmoteCheck.js:
--------------------------------------------------------------------------------
1 | const got = require('got');
2 | const _ = require('lodash');
3 |
4 | const seventvEmoteCheck = async userId => {
5 | let result = null;
6 |
7 | try {
8 | result = await got({
9 | method: 'GET',
10 | url: `https://7tv.io/v3/users/twitch/${userId}`,
11 | }).json();
12 | } catch (err) {
13 | console.error({
14 | message: 'Error while fetching 7tv emotes for channel',
15 | userId,
16 | timestamp: new Date(),
17 | });
18 | }
19 |
20 | const emotes = _.get(result, 'emote_set.emotes');
21 |
22 | return _.map(emotes, (emote = {}) => {
23 | const name = _.get(emote, 'name');
24 | const id = _.get(emote, 'id');
25 | const timestamp = _.get(emote, 'timestamp') || null;
26 |
27 | if (!!name && !!id) {
28 | return {
29 | type: '7tv',
30 | name,
31 | id,
32 | timestamp,
33 | emotePicture: `https://cdn.7tv.app/emote/${id}/1x.webp`,
34 | };
35 | }
36 | });
37 | };
38 |
39 | module.exports = seventvEmoteCheck;
40 |
--------------------------------------------------------------------------------
/k8s/deployments/mysql/mysql-stateful.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: StatefulSet
3 | metadata:
4 | name: mysql
5 | namespace: default
6 | labels:
7 | app: mysql
8 | spec:
9 | serviceName: "mysql-clusterip"
10 | replicas: 1
11 | selector:
12 | matchLabels:
13 | app: mysql
14 | template:
15 | metadata:
16 | labels:
17 | app: mysql
18 | spec:
19 | volumes:
20 | - name: mysql-storage
21 | hostPath:
22 | path: /home/docker/mysql
23 | containers:
24 | - name: mysql
25 | image: mysql:8.0.36
26 | ports:
27 | - name: mysql
28 | containerPort: 3306
29 | protocol: TCP
30 | envFrom:
31 | - configMapRef:
32 | name: mysql-server-config
33 | volumeMounts:
34 | - name: mysql-storage
35 | mountPath: /var/lib/mysql
36 | imagePullPolicy: IfNotPresent
37 | restartPolicy: Always
38 | terminationGracePeriodSeconds: 30
39 | dnsPolicy: ClusterFirst
40 |
--------------------------------------------------------------------------------
/lib/commands/namechange/namechange.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash');
2 |
3 | const validateNamechange = require('./utils/namechangeValidator');
4 |
5 | const namechange = async (context, { kb, Commons }) => {
6 | const responses = Commons.ResponseRepository().getResponses();
7 | const userInput = _.first(_.split(_.get(context, 'standarizedMessageParts.userInput'), ' '));
8 | const username = !userInput ? context.userstate.username : _.replace(userInput, /@|,/g, '');
9 |
10 | const validation = await validateNamechange(context, { Commons }, username);
11 | if (validation.error) {
12 | return validation.response;
13 | }
14 |
15 | const { userToCheck } = validation;
16 | let usernames = _.join(
17 | _.map(userToCheck, i => i.username),
18 | ' => '
19 | );
20 |
21 | if (usernames.length > 445) {
22 | usernames = _.join(_.slice(_.split(usernames, ''), 0, 440), '') + '...';
23 | }
24 |
25 | return {
26 | response: responses.NAMECHANGE.SUCCESS.NAME_CHANGES(_.size(userToCheck) - 1, usernames)
27 | };
28 | };
29 |
30 | module.exports.invocation = namechange;
31 |
--------------------------------------------------------------------------------
/lib/mirrors/emote-checker/utils/bttvEmoteCheck.js:
--------------------------------------------------------------------------------
1 | const got = require('got');
2 | const _ = require('lodash');
3 |
4 | const bttvEmoteCheck = async userId => {
5 | let result = null;
6 |
7 | try {
8 | result = await got({
9 | method: 'GET',
10 | url: `https://api.betterttv.net/3/cached/users/twitch/${userId}`,
11 | }).json();
12 | } catch (err) {
13 | console.error({
14 | message: 'Error while fetching bttv emotes for channel',
15 | userId,
16 | timestamp: new Date(),
17 | });
18 | }
19 |
20 | const emotes = _.concat(
21 | _.get(result, 'sharedEmotes', []),
22 | _.get(result, 'channelEmotes', [])
23 | );
24 |
25 | return _.map(emotes, (emote = {}) => {
26 | const name = _.get(emote, 'code');
27 | const id = _.get(emote, 'id');
28 |
29 | if (!!name && !!id) {
30 | return {
31 | type: 'bttv',
32 | name,
33 | id,
34 | timestamp: null,
35 | emotePicture: `https://cdn.betterttv.net/emote/${id}/1x`,
36 | };
37 | }
38 | });
39 | };
40 |
41 | module.exports = bttvEmoteCheck;
42 |
--------------------------------------------------------------------------------
/lib/utils/isContentBanphrased.js:
--------------------------------------------------------------------------------
1 | const got = require('got');
2 | const _ = require('lodash');
3 | const serviceConnector = require('../../commons/connector/serviceConnector');
4 |
5 | // todo: moved to Commons, move remaining usages in future and delete this file
6 | const isContentBanphrased = async (content, channel, sqlClient) => {
7 | channel = channel.replace('#', '');
8 |
9 | if (!sqlClient) {
10 | const kb = await serviceConnector.Connector.dependencies(['sql']);
11 |
12 | sqlClient = kb.sqlClient;
13 | }
14 |
15 | const data = await sqlClient.query(
16 | `
17 | SELECT *
18 | FROM channel_banphrase_apis
19 | WHERE channel=? AND status=?`,
20 | [channel, 'enabled']
21 | );
22 |
23 | if (!_.size(data)) {
24 | return { banned: false };
25 | }
26 |
27 | return got(encodeURI(_.get(_.first(data), 'url')), {
28 | method: 'POST',
29 | body: 'message=' + encodeURIComponent(content),
30 | headers: {
31 | 'Content-Type': 'application/x-www-form-urlencoded',
32 | },
33 | }).json();
34 | };
35 |
36 | module.exports = isContentBanphrased;
37 |
--------------------------------------------------------------------------------
/lib/commands/namechange/utils/namechangeValidator.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash');
2 |
3 | const validateNamechange = async (context, { Commons }, username = null) => {
4 | const responses = Commons.ResponseRepository().getResponses();
5 |
6 | let userToCheck = await Commons.UserRepository().getUser({ username });
7 |
8 | if (!_.size(userToCheck)) {
9 | return { error: true, response: responses.NAMECHANGE.ERRORS.USERNAME_NOT_FOUND };
10 | }
11 |
12 | const userId = _.get(_.first(userToCheck), 'userId');
13 | userToCheck = await Commons.UserRepository().getUser({ userId });
14 |
15 | if (userToCheck < 2) {
16 | return { error: true, response: responses.NAMECHANGE.ERRORS.NO_NAME_CHANGES };
17 | }
18 |
19 | const isUserOptedOut = await Commons.UserRepository().isUserOptedOut('namechange', {
20 | userId
21 | });
22 |
23 | if (isUserOptedOut && context.userstate['user-id'] !== userId) {
24 | return { error: true, response: responses.COMMON.ERRORS.OPTED_OUT };
25 | }
26 |
27 | return { error: false, userToCheck, userId };
28 | };
29 |
30 | module.exports = validateNamechange;
31 |
--------------------------------------------------------------------------------
/lib/utils/postprocessCommandResponse.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash');
2 |
3 | const regex = require('../../consts/regex');
4 |
5 | const postprocessCommandResponse = (commandResult, responseSettings) => {
6 | let result = commandResult.invokedCommandResult.response;
7 |
8 | if (
9 | _.includes(responseSettings.channelsRestrictingLinks, commandResult.context.channelName)
10 | ) {
11 | const links = _.get(commandResult, 'response', '').match(regex.url);
12 |
13 | if (!_.isEmpty(links)) {
14 | result = _.reduce(
15 | links,
16 | (updatedResult, link) => {
17 | const sanitizedLink = _.replace(
18 | _.replace(link, /(https:\/\/|http:\/\/)?(www\.)?/g, ''),
19 | /\./g,
20 | '(dot)'
21 | );
22 | return _.replace(updatedResult, link, sanitizedLink);
23 | },
24 | result
25 | );
26 | }
27 | }
28 |
29 | if (result.match(regex.racism) != null) {
30 | return "can't post response message - bad word detected in response :z";
31 | }
32 |
33 | return result;
34 | };
35 |
36 | module.exports = postprocessCommandResponse;
37 |
--------------------------------------------------------------------------------
/api/src/consts/consts.json:
--------------------------------------------------------------------------------
1 | {
2 | "emoteHeaders": {
3 | "ID": " ",
4 | "name": "",
5 | "emote": " ",
6 | "type": " ",
7 | "added": " "
8 | },
9 | "emoteHeadersRemoved": {
10 | "ID": " ",
11 | "name": "",
12 | "emote": " ",
13 | "type": " ",
14 | "removed": " "
15 | },
16 | "emoteHeadersEmptyResponse": {
17 | "ID": "-
",
18 | "name": "-
",
19 | "emote": "-
",
20 | "type": "-
",
21 | "removed": "-
"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/lib/commands_old/spacex.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict';
3 |
4 | const utils = require('../utils/utils.js');
5 | const got = require('got');
6 |
7 | module.exports = {
8 | name: 'kb spacex',
9 | invocation: async (channel, user, message) => {
10 | try {
11 | const api = await utils.Get.api().url(message);
12 |
13 | const spacexNextLaunch = await got(`${api}/launches/next`).json();
14 | const timeDiff = Math.abs(spacexNextLaunch.date_unix - Date.now() / 1000);
15 |
16 | const [rocket, launchpad] = await Promise.all([
17 | got(`${api}/rockets/${spacexNextLaunch.rocket}`).json(),
18 | got(`${api}/launchpads/${spacexNextLaunch.launchpad}`).json(),
19 | ]);
20 |
21 | return (
22 | `Next rocket launch by SpaceX in ${utils.humanizeDuration(timeDiff)}, ` +
23 | `rocket ${rocket.name}, mission ${spacexNextLaunch.name}, flight number ${spacexNextLaunch.flight_number}, ` +
24 | `${launchpad.name} - ${launchpad.locality}`
25 | );
26 | } catch (err) {
27 | utils.errorLog(err);
28 | return `${user['username']}, ${err} FeelsDankMan !!!`;
29 | }
30 | },
31 | };
32 |
--------------------------------------------------------------------------------
/lib/commands_old/surah.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict';
3 |
4 | const utils = require('../utils/utils.js');
5 | const got = require('got');
6 |
7 | module.exports = {
8 | name: 'kb surah',
9 | invocation: async (channel, user, message) => {
10 | try {
11 | const randomNumberFromRange = Math.floor(Math.random() * 6237) + 1;
12 | const quran = `http://api.alquran.cloud/v1/ayah/${randomNumberFromRange}/editions/quran-uthmani,en.pickthall`;
13 | const quranApi = await got(quran).json();
14 |
15 | const output =
16 | quranApi.data[0].surah.englishName +
17 | ' - ' +
18 | quranApi.data[0].surah.englishNameTranslation +
19 | ': ' +
20 | quranApi.data[0].text.split(' ').reverse().join(' ') +
21 | ' - ' +
22 | quranApi.data[1].text +
23 | ' ' +
24 | quranApi.data[0].page +
25 | ':' +
26 | quranApi.data[0].surah.numberOfAyahs;
27 |
28 | return `${user['username']}, ${output}`;
29 | } catch (err) {
30 | utils.errorLog(err);
31 | return `${user['username']}, API returned an error. Please wait until it goes back online FeelsDankMan`;
32 | }
33 | },
34 | };
35 |
--------------------------------------------------------------------------------
/k8s/deployments/rabbitmq/rabbit-deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: rabbitmq
5 | labels:
6 | app: rabbitmq
7 | spec:
8 | replicas: 1
9 | selector:
10 | matchLabels:
11 | app: rabbitmq
12 | template:
13 | metadata:
14 | labels:
15 | app: rabbitmq
16 | spec:
17 | containers:
18 | - name: rabbitmq
19 | image: rabbitmq:3-management
20 | ports:
21 | - containerPort: 5672 # RabbitMQ main port
22 | - containerPort: 15672 # Management dashboard port
23 | env:
24 | - name: RABBITMQ_DEFAULT_USER
25 | valueFrom:
26 | secretKeyRef:
27 | name: rabbitmq-admin
28 | key: RABBITMQ_DEFAULT_USER
29 | - name: RABBITMQ_DEFAULT_PASS
30 | valueFrom:
31 | secretKeyRef:
32 | name: rabbitmq-admin
33 | key: RABBITMQ_DEFAULT_PASS
34 | volumeMounts:
35 | - name: rabbitmq-data
36 | mountPath: /var/lib/rabbitmq
37 | volumes:
38 | - name: rabbitmq-data
39 | persistentVolumeClaim:
40 | claimName: rabbitmq-pvc
41 |
--------------------------------------------------------------------------------
/lib/mirrors/twitch-chat-logger/twitchChatNoticeLogger.js:
--------------------------------------------------------------------------------
1 | const serviceConnector = require('../../../commons/connector/serviceConnector');
2 | const serviceSettings = require('../../../consts/serviceSettings.json');
3 |
4 | const service = serviceSettings.services.twitchChatNoticeLogger;
5 |
6 | (async () => {
7 | const kb = await serviceConnector.Connector.dependencies(['sql', 'rabbit'], {
8 | enableHealthcheck: true,
9 | service,
10 | });
11 |
12 | await kb.rabbitClient.createRabbitChannel(
13 | service.queues.KB_TWITCH_CHAT_NOTICE,
14 | async (msg, consumer, rawMsg) => {
15 | const { channel, message, msgId, moduleName } = msg;
16 |
17 | if (!channel || !message || !msgId || !moduleName) {
18 | console.error(
19 | 'ERROR: Received invalid message in notice logger: ' + JSON.stringify(msg)
20 | );
21 | consumer.ack(rawMsg);
22 | return;
23 | }
24 |
25 | await kb.sqlClient.query(
26 | `
27 | INSERT INTO notice (msgid, message, channel, module, date)
28 | VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)`,
29 | [msgId, message, channel, moduleName]
30 | );
31 |
32 | consumer.ack(rawMsg);
33 | }
34 | );
35 | })();
36 |
--------------------------------------------------------------------------------
/lib/job-manager/tasks/renewTwitchToken.js:
--------------------------------------------------------------------------------
1 | const got = require('got');
2 | const _ = require('lodash');
3 |
4 | const renewTwitchToken = async (kb, config) => {
5 | const tokens = await kb.sqlClient.query(`
6 | SELECT refresh_token
7 | FROM access_token
8 | WHERE platform="twitch"`);
9 |
10 | const refreshToken = _.get(_.first(tokens), 'refresh_token');
11 |
12 | const token = await got(
13 | `https://id.twitch.tv/oauth2/token?client_secret=${config.client_secret}&grant_type=refresh_token&refresh_token=${refreshToken}`,
14 | {
15 | method: 'POST',
16 | headers: {
17 | 'Client-ID': config.client_id,
18 | 'Content-Type': 'application/x-www-form-urlencoded'
19 | }
20 | }
21 | ).json();
22 |
23 | if (!token || !_.get(token, 'access_token')) {
24 | throw new Error('Twitch refresh token request returned no data');
25 | }
26 |
27 | await kb.sqlClient.query(
28 | `
29 | UPDATE access_token
30 | SET access_token=?
31 | WHERE platform="twitch"`,
32 | [token.access_token]
33 | );
34 |
35 | await kb.redisClient.set(
36 | 'kb:global:twitch-access-token',
37 | token.access_token,
38 | token.expires_in
39 | );
40 | };
41 |
42 | module.exports = renewTwitchToken;
43 |
--------------------------------------------------------------------------------
/api/src/site/pageLastfm.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const _ = require('lodash');
3 |
4 | const pageLastfm = services => {
5 | const { app, Commons } = services;
6 |
7 | const kb = Commons.ServiceConnector.Connector;
8 |
9 | app.get('/lastfm', async (req, res) => {
10 | if (!_.get(req, 'query.code')) {
11 | res.status(400).send('Error - no verifcode/user provided');
12 | return;
13 | }
14 |
15 | let verifCode = Commons.UtilityRepository().stringGenerator();
16 |
17 | while (await kb.redisClient.get(`kb:site:lastfm:code:${verifCode}`)) {
18 | verifCode = Commons.UtilityRepository().stringGenerator();
19 | }
20 |
21 | await kb.redisClient.set(`kb:site:lastfm:code:${verifCode}`);
22 |
23 | await kb.sqlClient.query(
24 | `
25 | INSERT INTO access_token (code)
26 | VALUES (?)`,
27 | [verifCode]
28 | );
29 |
30 | const html = _.toString(
31 | fs.readFileSync('../../kbot-website/html/express_pages/lastfm.html')
32 | );
33 |
34 | const page = Commons.UtilityRepository().complementHtmlPageTemplates(html, [
35 | {
36 | code: verifCode
37 | }
38 | ]);
39 |
40 | res.send(page);
41 | });
42 | };
43 |
44 | module.exports = pageLastfm;
45 |
--------------------------------------------------------------------------------
/api/utils/createColorsGetResponse.js:
--------------------------------------------------------------------------------
1 | const createColorsGetResponse = colors => {
2 | return {
3 | type: 'horizontal column',
4 | title: {
5 | position: 'center',
6 | label: {
7 | text: 'Total data for top 100 most popular user colors on twitch.tv',
8 | },
9 | },
10 | palette: colors.map(i => i.color),
11 | legend: {
12 | layout: 'vertical',
13 | position: 'inside top right',
14 | customEntries: colors.map(i => {
15 | const colorName = i.color.replace('gray', 'no color');
16 | return {
17 | name: colorName,
18 | icon: 'none',
19 | value: String(i.count),
20 | };
21 | }),
22 | },
23 | defaultPoint: {
24 | tooltip: '%value %name',
25 | },
26 | series: [
27 | {
28 | points: colors.map(i => {
29 | const colorName = i.color.replace('gray', 'no color');
30 | return {
31 | name: colorName,
32 | color: i.color,
33 | x: colorName,
34 | y: Number(i.count),
35 | };
36 | }),
37 | },
38 | ],
39 | };
40 | };
41 |
42 | module.exports = createColorsGetResponse;
43 |
--------------------------------------------------------------------------------
/kbot-website/src/app/api/commands/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server';
2 | import getServiceConnector from '@/lib/serviceConnectorBridge';
3 |
4 | interface Command {
5 | ID: number;
6 | command: string;
7 | aliases: string | null;
8 | cooldown: number;
9 | permissions: number;
10 | date: string;
11 | description_formatted: string | null;
12 | description: string;
13 | optoutable: 'Y' | 'N';
14 | usage: string | null;
15 | }
16 |
17 | export async function GET() {
18 | try {
19 | const { sqlClient } = await getServiceConnector();
20 |
21 | const commands = await sqlClient.query(
22 | `
23 | SELECT *
24 | FROM kbot.commands
25 | WHERE permissions < 5
26 | ORDER BY command
27 | `
28 | );
29 |
30 | if (!commands || commands.length === 0) {
31 | return NextResponse.json({
32 | commands: [],
33 | message: 'No commands found'
34 | });
35 | }
36 |
37 | return NextResponse.json({
38 | commands: Array.isArray(commands) ? commands : [],
39 | total: commands.length
40 | });
41 | } catch (error) {
42 | console.error('Error fetching commands:', error);
43 | return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/lib/commands_old/countdown.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict';
3 |
4 | const utils = require('../utils/utils.js');
5 |
6 | module.exports = {
7 | name: 'kb countdown',
8 | invocation: async (channel, user, message) => {
9 | try {
10 | const msg = utils.getParam(message);
11 |
12 | if (!msg[0]) {
13 | return `${user['username']}, you have to provide time in seconds to generate result.`;
14 | }
15 |
16 | this.msg = msg[0].match(/[0-9]{1,}/g) ?? '';
17 |
18 | if (this.msg > 31556926) {
19 | // 1 year
20 | return `${user['username']}, value out of range, maximum value is 1 year`;
21 | }
22 |
23 | const seconds = Date.now() / 1000 + Number(this.msg);
24 |
25 | const code = utils.genString();
26 |
27 | await utils.query(
28 | `
29 | INSERT INTO countdown (verifcode, seconds, username, date)
30 | VALUES (?, ?, ?, CURRENT_TIMESTAMP)`,
31 | [code, seconds, user['username']]
32 | );
33 |
34 | return `${user['username']}, your countdown will end in ${utils.humanizeDuration(
35 | this.msg
36 | )} https://kunszg.com/countdown?verifcode=${code}`;
37 | } catch (err) {
38 | utils.errorLog(err);
39 | return `${user['username']}, ${err} FeelsDankMan !!!`;
40 | }
41 | },
42 | };
43 |
--------------------------------------------------------------------------------
/kbot-website/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/api/src/site/pageColors.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const _ = require('lodash');
3 |
4 | const createColorsGetResponse = require('../../utils/createColorsGetResponse');
5 |
6 | const pageColors = services => {
7 | const { app, Commons } = services;
8 |
9 | const kb = Commons.ServiceConnector.Connector;
10 |
11 | app.get('/colors', async (req, res) => {
12 | res.send('Page is under maintenance.');
13 | return;
14 |
15 | const html = _.toString(
16 | fs.readFileSync('../../kbot-website/html/express_pages/colors.html')
17 | );
18 |
19 | let colors;
20 |
21 | const stats = await kb.redisClient.get('kb:api:colors:stats');
22 |
23 | if (!stats) {
24 | const colorsData = await kb.sqlClient.query(`
25 | SELECT color, COUNT(*) AS count
26 | FROM user_list
27 | GROUP BY color
28 | HAVING count >= 100
29 | ORDER BY count DESC
30 | LIMIT 100`);
31 |
32 | colors = createColorsGetResponse(colorsData);
33 |
34 | await kb.redisClient.set('kb:api:colors:stats', colors, 10800);
35 | } else {
36 | colors = stats;
37 | }
38 |
39 | const page = Commons.UtilityRepository().complementHtmlPageTemplates(html, [
40 | {
41 | colors
42 | }
43 | ]);
44 |
45 | res.send(page);
46 | });
47 | };
48 |
49 | module.exports = pageColors;
50 |
--------------------------------------------------------------------------------
/lib/credentials/config_template.js:
--------------------------------------------------------------------------------
1 | /* Find the API's mentioned below and change this file's name to "config.js" */
2 |
3 | exports.oauth = 'oauth:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'; // Twitch oauth
4 | exports.db_user = 'MYSQL_USER'; // mysql database username, leave blank for no username
5 | exports.db_pass = 'MYSQL_PASS'; // mysql database password, leave blank for no pass
6 | exports.db_name = 'MYSQL_DB_NAME'; // name of database
7 | exports.db_host = 'MYSQL_HOST'; // localhost by default
8 | exports.db_server_user = 'root'; // root by default
9 | exports.client_id = 'Twitch client ID';
10 | exports.client_secret = 'Twitch client secret';
11 | exports.client_id_spotify = 'Spotify client ID'; // ./lib/commands/spotify
12 | exports.client_secret_spotify = 'Spotify client secret'; // ./lib/commands/spotify
13 | exports.youtube = 'youtube API key'; // ./lib/commands/rt
14 | exports.randomTrack = 'musixmatch API Key'; // ./lib/commands/rt
15 | exports.locate = 'ipstack API key'; // ./lib/commands/locate
16 | exports.supinic = 'https://supinic.com/api/bot/active?auth_user=USER_ID&auth_key=AUTH_KEY'; // API to ping supinic's bot alive check, required by default, you can remove it from code located in ./lib/misc/loops refering to this endpoint to skip this part
17 | exports.lastfmApiKey = 'lastfm api key'; // ./lib/commands/spotify
18 | exports.discord = 'discord api key'; // not yet implemented
19 |
--------------------------------------------------------------------------------
/lib/mirrors/twitch-chat-logger/twitchChatBanphrasedMessageLogger.js:
--------------------------------------------------------------------------------
1 | const serviceConnector = require('../../../commons/connector/serviceConnector');
2 | const serviceSettings = require('../../../consts/serviceSettings.json');
3 |
4 | const isContentBanphrased = require('../../utils/isContentBanphrased');
5 |
6 | const service = serviceSettings.services.twitchChatBanphrasedMessageLogger;
7 |
8 | (async () => {
9 | const kb = await serviceConnector.Connector.dependencies(['sql', 'rabbit'], {
10 | enableHealthcheck: true,
11 | service,
12 | });
13 |
14 | await kb.rabbitClient.createRabbitChannel(
15 | service.queues.KB_DETECTED_BANPHRASE,
16 | async (msg, consumer, rawMsg) => {
17 | const { channel, username, message, date } = msg;
18 |
19 | if (!channel || !username || !message || !date) {
20 | await consumer.ack(rawMsg);
21 | return;
22 | }
23 |
24 | const banphraseCheck = await isContentBanphrased(username, channel, kb.sqlClient);
25 |
26 | await kb.sqlClient.query(
27 | `
28 | INSERT INTO bruh (username, channel, message, date, detectedByApi)
29 | VALUES (?, ?, ?, ?)`,
30 | [username, channel, message, date, banphraseCheck.banned]
31 | );
32 |
33 | await consumer.ack(rawMsg);
34 | },
35 | { prefetchCount: 1, delayProcessing: 2_000 }
36 | );
37 | })();
38 |
--------------------------------------------------------------------------------
/api/api.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const requireDir = require('require-dir');
3 | const _ = require('lodash');
4 |
5 | const Commons = require('../commons/Commons');
6 |
7 | const expressSetup = require('./utils/expressSetup');
8 | const initializeMethodRecurse = require('./utils/initializeMethodRecurse');
9 | const GithubWebHook = require('./utils/gitWebhookMiddleware');
10 |
11 | const creds = require('../lib/credentials/config');
12 |
13 | const serviceSettings = require('../consts/serviceSettings.json');
14 |
15 | const service = serviceSettings.services.api;
16 |
17 | const app = express();
18 |
19 | const secret = creds.webhook_github_secret;
20 | const webhookHandler = GithubWebHook({ path: '/webhooks/github', secret: secret });
21 |
22 | (async () => {
23 | const kb = await Commons.ServiceConnector.Connector.dependencies(
24 | ['sql', 'tmi', 'rabbit', 'redis', 'rabbit'],
25 | {
26 | enableHealthcheck: true,
27 | service,
28 | disableTMIAutojoin: true
29 | }
30 | );
31 |
32 | expressSetup(app, webhookHandler, kb.sqlClient);
33 |
34 | const endpoints = requireDir('src', {
35 | recurse: true,
36 | extensions: ['.js']
37 | });
38 |
39 | _.forEach(endpoints, invocation => {
40 | initializeMethodRecurse(invocation, {
41 | Commons,
42 | app,
43 | webhookHandler
44 | });
45 | });
46 |
47 | app.listen(8080, '0.0.0.0');
48 | })();
49 |
--------------------------------------------------------------------------------
/lib/job-manager/tasks/refreshEstimatedRepoLines.js:
--------------------------------------------------------------------------------
1 | const got = require('got');
2 | const _ = require('lodash');
3 |
4 | const ignoredFiles = [
5 | 'package.json',
6 | 'package-lock.json',
7 | '.github/',
8 | 'README.md',
9 | 'LICENSE.md'
10 | ];
11 |
12 | const refreshEstimatedRepoLines = async (kb, config) => {
13 | const GITHUB_TOKEN = process.env.githubAppAccessToken || config.githubAppAccessToken;
14 |
15 | const apiUrl = `https://api.github.com/repos/kunszg/kbot/git/trees/master?recursive=1`;
16 |
17 | const data = await got({
18 | url: apiUrl,
19 | headers: {
20 | Authorization: `${GITHUB_TOKEN}`,
21 | Accept: 'application/vnd.github.v3+json'
22 | },
23 | throwHttpErrors: false
24 | }).json();
25 |
26 | if (data.tree) {
27 | let totalSize = 0;
28 |
29 | data.tree.forEach(file => {
30 | if (file.type === 'blob' && !_.some(ignoredFiles, str => _.includes(str, file.path))) {
31 | totalSize += file.size;
32 | }
33 | });
34 |
35 | const averageBytesPerLine = 50;
36 | const estimatedLines = Math.ceil(totalSize / averageBytesPerLine);
37 |
38 | await kb.redisClient.set('kb:job-manager:estimatedRepoLines', estimatedLines, 1e8);
39 | } else {
40 | console.log('No data found for the repository tree.');
41 |
42 | await kb.redisClient.set('kb:job-manager:estimatedRepoLines', 0, 1e8);
43 | }
44 | };
45 |
46 | module.exports = refreshEstimatedRepoLines;
47 |
--------------------------------------------------------------------------------
/k8s/helpers/minikube-setup/minikube-services.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | REAL_USER=${SUDO_USER:-$USER}
4 | HOME_DIR=$(eval echo ~$REAL_USER)
5 |
6 | port_forward_retry() {
7 | local resource=$1
8 | local ports=$2
9 | local namespace=${3:-default}
10 |
11 | while true; do
12 | if [ "$namespace" = "default" ]; then
13 | kubectl port-forward "$resource" "$ports"
14 | else
15 | kubectl port-forward "$resource" "$ports" -n "$namespace"
16 | fi
17 | echo "Port-forward $resource $ports lost connection, reconnecting in 3 seconds..."
18 | sleep 3
19 | done
20 | }
21 |
22 | minikube -p kbot dashboard --url --port=40000 &
23 |
24 | port_forward_retry "svc/docker-registry" "5000:5000" &
25 | port_forward_retry "svc/rabbitmq-service" "15672:15672" &
26 | port_forward_retry "svc/rabbitmq-service" "5672:5672" &
27 | port_forward_retry "svc/redis" "6379:6379" &
28 | port_forward_retry "svc/mysql" "3306:3306" &
29 | port_forward_retry "svc/kbot-api" "8080:8080" &
30 | port_forward_retry "svc/kbot-website-service" "40100:40100" &
31 | port_forward_retry "svc/ads-scraper" "8510:8510" &
32 | port_forward_retry "svc/grafana" "3005:3005" "logging" &
33 |
34 | minikube mount "$HOME_DIR/musicbot/music-bot-github:/home/docker/musicbot" &
35 | minikube mount "/home/kunszg/kbot-env/node_modules:/home/docker/kbot/node_modules" &
36 |
37 | wait
38 |
39 |
40 | # place in /usr/local/bin/ then:
41 | # sudo chmod +x /usr/local/bin/minikube-services.sh
--------------------------------------------------------------------------------
/kbot-website/src/lib/serviceConnectorBridge.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-require-imports */
2 | const { SqlClient } = require('../../../commons/connector/services/sqlClient');
3 | const { sqlConfig } = require('../../../commons/connector/consts/serviceConfigs');
4 | /* eslint-enable @typescript-eslint/no-require-imports */
5 |
6 | export interface SqlClientInterface {
7 | query(query: string, params?: unknown[]): Promise;
8 | isConnected: boolean;
9 | }
10 |
11 | export interface ServiceConnectorType {
12 | sqlClient: SqlClientInterface;
13 | }
14 |
15 | let serviceConnectorInstance: ServiceConnectorType | null = null;
16 |
17 | export async function getServiceConnector(): Promise {
18 | if (!serviceConnectorInstance) {
19 | try {
20 | const customSqlConfig = {
21 | ...sqlConfig,
22 | database: 'kbot_website'
23 | };
24 |
25 | const sqlClient = SqlClient.withCustomConfig(customSqlConfig);
26 | await sqlClient.connect();
27 |
28 | serviceConnectorInstance = {
29 | sqlClient: sqlClient as SqlClientInterface
30 | };
31 |
32 | console.log(
33 | 'ServiceConnector initialized for Next.js with database:',
34 | customSqlConfig.database
35 | );
36 | } catch (error) {
37 | console.error('Failed to initialize SQL Client:', error);
38 | throw error;
39 | }
40 | }
41 |
42 | return serviceConnectorInstance;
43 | }
44 |
45 | export default getServiceConnector;
46 |
--------------------------------------------------------------------------------
/lib/mirrors/reddit-live-thread-to-discord/utils/sendRedditMessageToQueue.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash');
2 |
3 | const sendRedditMessageToQueue = (kb, queue) => {
4 | const sendToQueue = async data => {
5 | const checkCache = await kb.redisClient.get(`kb:handler:reddit:livethreads:${data.id}`);
6 |
7 | if (!checkCache) {
8 | const livethreadsFromLastDay = await kb.sqlClient.query(
9 | 'SELECT * FROM livethreads WHERE DATE > NOW() - INTERVAL 1 DAY LIMIT 100"'
10 | );
11 |
12 | let similarities = 0;
13 | let total = 0;
14 |
15 | _.forEach(livethreadsFromLastDay, livethread => {
16 | const splitBodyDb = _.split(JSON.parse(livethread.body), ' ');
17 | const splitBodyIncoming = _.split(data.body, ' ');
18 |
19 | total = _.max([_.size(splitBodyDb), _.size(splitBodyIncoming)]);
20 |
21 | _.forEach(splitBodyIncoming, word => {
22 | if (splitBodyDb.includes(word)) {
23 | similarities++;
24 | }
25 | });
26 | });
27 |
28 | const isSimilarityReached = (similarities / total) * 100 < 60;
29 |
30 | if (!isSimilarityReached) {
31 | await kb.rabbitClient.sendToQueue(queue, data);
32 |
33 | await kb.redisClient.set(
34 | `kb:handler:reddit:livethreads:${data.id}`,
35 | true,
36 | 60 * 60 * 24
37 | );
38 | }
39 | }
40 | };
41 |
42 | kb.redditClient.native.getLivethread('18hnzysb1elcs').stream.on('update', sendToQueue);
43 | };
44 |
45 | module.exports = sendRedditMessageToQueue;
46 |
--------------------------------------------------------------------------------
/kbot-website/src/app/components/SharedLayout.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import React, { useState } from 'react';
4 | import { useSession } from 'next-auth/react';
5 | import { FiMenu } from 'react-icons/fi';
6 | import Sidebar from './Sidebar';
7 | import StatsBox from './StatsBox';
8 |
9 | interface SharedLayoutProps {
10 | children: React.ReactNode;
11 | showStats?: boolean;
12 | }
13 |
14 | export default function SharedLayout({ children, showStats = false }: SharedLayoutProps) {
15 | const { data: session } = useSession();
16 | const [sidebarOpen, setSidebarOpen] = useState(false);
17 | const [stats] = useState({ users: 0, messages: 0, uptime: '0h 0m' });
18 |
19 | return (
20 |
21 |
27 |
28 |
{}}
31 | session={session}
32 | sidebarOpen={sidebarOpen}
33 | setSidebarOpen={setSidebarOpen}
34 | />
35 |
36 | {sidebarOpen && (
37 | setSidebarOpen(false)}
40 | >
41 | )}
42 |
43 | {showStats && }
44 |
45 | {children}
46 |
47 | );
48 | }
49 |
--------------------------------------------------------------------------------
/commons/connector/services/redditClient.js:
--------------------------------------------------------------------------------
1 | const snoowrap = require('snoowrap');
2 | const { redditConfig } = require('../consts/serviceConfigs');
3 |
4 | /**
5 | * Singleton class for managing Reddit API connections.
6 | */
7 | class RedditClient {
8 | constructor() {
9 | if (!RedditClient.instance) {
10 | RedditClient.instance = this;
11 | this.client = null;
12 | this.isConnected = false;
13 | }
14 |
15 | return RedditClient.instance;
16 | }
17 |
18 | /**
19 | * Establishes a connection to the Reddit API.
20 | * If a connection already exists, it returns the existing connection.
21 | * @returns {snoowrap} The Reddit client instance.
22 | */
23 | async connect() {
24 | if (!this.client) {
25 | this.client = new snoowrap(redditConfig);
26 | RedditClient.instance.native = this.client;
27 |
28 | try {
29 | await this.client.getMe();
30 | this.isConnected = true;
31 | console.log('[Connector-Reddit] Connected');
32 | } catch (error) {
33 | console.error('[Connector-Reddit] Connection error:', error);
34 | this.isConnected = false;
35 | this.client = null;
36 | }
37 | }
38 |
39 | return this.client;
40 | }
41 |
42 | async close() {
43 | if (this.client) {
44 | console.log('[Connector-Reddit] Closing connection...');
45 | this.client = null;
46 | this.isConnected = false;
47 | console.log('[Connector-Reddit] Connection closed');
48 | }
49 | }
50 | }
51 |
52 | module.exports = {
53 | redditClient: new RedditClient()
54 | };
55 |
--------------------------------------------------------------------------------
/kbot-website/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | # or
12 | pnpm dev
13 | # or
14 | bun dev
15 | ```
16 |
17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18 |
19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20 |
21 | This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
22 |
23 | ## Learn More
24 |
25 | To learn more about Next.js, take a look at the following resources:
26 |
27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29 |
30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
31 |
32 | ## Deploy on Vercel
33 |
34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35 |
36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
37 |
--------------------------------------------------------------------------------
/lib/commands_old/3rd-party/ps.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict';
3 |
4 | const utils = require('../../utils/utils.js');
5 |
6 | module.exports = {
7 | invocation: async (channel, user, message) => {
8 | try {
9 | const msg = utils.getParam(message, 1);
10 |
11 | if (!msg[0]) {
12 | return '';
13 | }
14 |
15 | if (channel != '#supinic') {
16 | return '';
17 | }
18 |
19 | const getPSData = await utils.query(
20 | `
21 | SELECT *
22 | FROM playsounds
23 | WHERE name=?`,
24 | [msg[0]]
25 | );
26 |
27 | if (!getPSData.length) {
28 | return '';
29 | }
30 | if (
31 | getPSData[0].last_executed === 'null' ||
32 | (Date.parse(getPSData[0].last_executed) - Date.parse(new Date())) / 1000 > 1
33 | ) {
34 | return '';
35 | }
36 | Date.prototype.addSeconds = function (seconds) {
37 | var copiedDate = new Date(this.getTime());
38 | return new Date(copiedDate.getTime() + seconds * 1000);
39 | };
40 | const now = new Date();
41 | const time = now
42 | .addSeconds(getPSData[0].cooldown / 1000)
43 | .toISOString()
44 | .slice(0, 19)
45 | .replace('T', ' ');
46 |
47 | await utils.query(
48 | `
49 | UPDATE playsounds
50 | SET last_executed=?
51 | WHERE name=?`,
52 | [time, msg[0]]
53 | );
54 |
55 | return '';
56 | } catch (err) {
57 | utils.errorLog(err);
58 | }
59 | },
60 | };
61 |
--------------------------------------------------------------------------------
/lib/commands_old/uptime.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict';
3 |
4 | const utils = require('../utils/utils.js');
5 | const os = require('os');
6 | const shell = require('child_process');
7 |
8 | module.exports = {
9 | name: 'kb uptime',
10 | invocation: async (channel, user, message) => {
11 | try {
12 | this.memoryUsage = await utils.query(`SELECT * FROM memory`);
13 |
14 | this.memoryUsage = (
15 | Number(this.memoryUsage[0].memory) +
16 | Number(this.memoryUsage[1].memory) +
17 | Number(this.memoryUsage[2].memory) +
18 | Number((process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2))
19 | ).toFixed(2);
20 |
21 | const serverUptime = os.uptime();
22 | const lines = shell.execSync(
23 | `find . -name '*.js' -not -path "./node_modules*" | xargs wc -l | tail -1`
24 | );
25 |
26 | const commitData = await utils.query(`
27 | SELECT *
28 | FROM stats
29 | WHERE type="ping"`);
30 |
31 | const diff = Math.abs(commitData[0].date - new Date());
32 |
33 | return (
34 | user['username'] +
35 | ', code has' +
36 | lines +
37 | ' lines, latest commit: ' +
38 | utils.humanizeDuration(diff / 1000) +
39 | ' ago, memory usage: ' +
40 | this.memoryUsage +
41 | ' MB, server is up for ' +
42 | utils.humanizeDuration(serverUptime) +
43 | ' FeelsDankMan'
44 | );
45 | } catch (err) {
46 | utils.errorLog(err);
47 | return `${user['username']}, ${err} FeelsDankMan !!!`;
48 | }
49 | },
50 | };
51 |
--------------------------------------------------------------------------------
/k8s/deployments/glk-promtail/glk-promtail-daemon-set.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: DaemonSet
3 | metadata:
4 | name: promtail
5 | namespace: logging
6 | spec:
7 | selector:
8 | matchLabels:
9 | name: promtail
10 | template:
11 | metadata:
12 | labels:
13 | name: promtail
14 | spec:
15 | serviceAccountName: promtail
16 | containers:
17 | - name: promtail
18 | image: grafana/promtail:2.9.2
19 | args:
20 | - -config.file=/etc/promtail/promtail.yaml
21 | env:
22 | - name: HOSTNAME
23 | valueFrom:
24 | fieldRef:
25 | fieldPath: spec.nodeName
26 | resources:
27 | limits:
28 | memory: 128Mi
29 | cpu: 100m
30 | requests:
31 | memory: 64Mi
32 | cpu: 50m
33 | volumeMounts:
34 | - name: config
35 | mountPath: /etc/promtail
36 | - mountPath: /var/log/pods
37 | name: pods
38 | readOnly: true
39 | - mountPath: /var/lib/docker/containers
40 | name: varlibdockercontainers
41 | readOnly: true
42 | ports:
43 | - containerPort: 9080
44 | name: http-metrics
45 | volumes:
46 | - name: config
47 | configMap:
48 | name: promtail-config
49 | - name: pods
50 | hostPath:
51 | path: /var/log/pods
52 | - name: varlibdockercontainers
53 | hostPath:
54 | path: /var/lib/docker/containers
--------------------------------------------------------------------------------
/api/src/site/pageCommands.js:
--------------------------------------------------------------------------------
1 | const Table = require('table-builder');
2 | const _ = require('lodash');
3 | const fs = require('fs');
4 |
5 | const prepareCommandsRow = require('../../utils/prepareCommandsRow');
6 |
7 | const pageCommands = services => {
8 | const { app, Commons } = services;
9 |
10 | const kb = Commons.ServiceConnector.Connector;
11 |
12 | app.get('/commands', async (req, res) => {
13 | const commands = await kb.sqlClient.query(`
14 | SELECT *
15 | FROM commands
16 | WHERE permissions < 5
17 | ORDER BY command
18 | ASC`);
19 |
20 | const tableData = prepareCommandsRow(commands);
21 |
22 | const headers = {
23 | ID: ` `,
24 | command: ` `,
25 | cooldown: ` `,
26 | 'opt-out': ` `,
27 | code: ` `,
28 | usage: ` `,
29 | description: ` `
30 | };
31 |
32 | const table = new Table({ class: 'table-context' })
33 | .setHeaders(headers)
34 | .setData(tableData)
35 | .render();
36 |
37 | const html = _.toString(
38 | fs.readFileSync('../../kbot-website/html/express_pages/commands.html')
39 | );
40 |
41 | const page = Commons.UtilityRepository().complementHtmlPageTemplates(html, [
42 | {
43 | table
44 | }
45 | ]);
46 |
47 | res.send(page);
48 | });
49 | };
50 |
51 | module.exports = pageCommands;
52 |
--------------------------------------------------------------------------------
/consts/serviceSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "healthcheckPath": "/healthcheck/",
3 | "services": {
4 | "twitchChatNoticeLogger": {
5 | "name": "twitchChatNoticeLogger",
6 | "queues": {
7 | "KB_TWITCH_CHAT_NOTICE": "KB_TWITCH_CHAT_NOTICE"
8 | },
9 | "port": 30001
10 | },
11 | "twitchChatMessageLogger": {
12 | "name": "twitchChatMessageLogger",
13 | "queues": {
14 | "KB_TWITCH_CHAT_MESSAGES": "KB_TWITCH_CHAT_MESSAGES",
15 | "KB_DETECTED_BANPHRASE": "KB_DETECTED_BANPHRASE"
16 | },
17 | "port": 30002
18 | },
19 | "twitchChatQueueFiller": {
20 | "name": "twitchChatQueueFiller",
21 | "queues": {
22 | "KB_TWITCH_CHAT_MESSAGES": "KB_TWITCH_CHAT_MESSAGES",
23 | "KB_TWITCH_CHAT_NOTICE": "KB_TWITCH_CHAT_NOTICE"
24 | },
25 | "port": 30003
26 | },
27 | "redditLiveThreadToDiscord": {
28 | "name": "redditLiveThreadToDiscord",
29 | "queues": {
30 | "KB_HANDLER_REDDIT_LIVETHREADS": "KB_HANDLER_REDDIT_LIVETHREADS"
31 | },
32 | "port": 30004
33 | },
34 | "twitchChatBanphrasedMessageLogger": {
35 | "name": "twitchChatBanphrasedMessageLogger",
36 | "queues": {
37 | "KB_DETECTED_BANPHRASE": "KB_DETECTED_BANPHRASE"
38 | },
39 | "port": 30005
40 | },
41 | "api": {
42 | "name": "api",
43 | "port": 30006
44 | },
45 | "updateEmotesList": {
46 | "name": "updateEmotesList",
47 | "queues": {
48 | "KB_JOB_MANAGER_CHANNEL_TO_UPDATE_EMOTES": "KB_JOB_MANAGER_CHANNEL_TO_UPDATE_EMOTES"
49 | },
50 | "port": 30007
51 | }
52 | }
53 | }
54 |
55 |
--------------------------------------------------------------------------------
/api/utils/prepareCommandsRow.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash');
2 | const prepareCommandsRow = commands => {
3 | let tableData = [];
4 |
5 | _.forEach(commands, (command, id) => {
6 | const commandDesc = _.replace(
7 | _.get(command, 'description'),
8 | /-/g,
9 | ' - [...]
'
10 | ) + " ";
11 |
12 | const commandUsage = _.replace(_.get(command, 'usage') ?? 'NULL', /;/g, '
');
13 | const commandText = _.get(command, 'command');
14 | const commandCooldown = _.get(command, 'cooldown', 0) / 1000;
15 | const commandOptoutable = _.get(command, 'optoutable') === 'Y' ? '✅' : '❌';
16 |
17 | const inline = `class='table-contents' style='text-align: center;'`;
18 | const buttonDiv = `
19 |

20 |
`;
21 |
22 | tableData.push({
23 | ID: `${id}
`,
24 | command: `${commandText}
`,
25 | cooldown: `${commandCooldown}s
`,
26 | 'opt-out': `${commandOptoutable}
`,
27 | code: `${buttonDiv}`,
28 | usage: `${commandUsage}
`,
29 | description: ``,
30 | });
31 | });
32 |
33 | return tableData;
34 | };
35 |
36 | module.exports = prepareCommandsRow;
37 |
--------------------------------------------------------------------------------
/lib/commands_old/verify.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash');
2 | const moment = require('moment/moment');
3 |
4 | const utils = require('../utils/utils');
5 |
6 | module.exports = {
7 | name: 'kb verify-lastfm',
8 | invocation: async (channel, user, message, event, messageState, kb) => {
9 | const code = utils.getParam(message);
10 |
11 | const { userId = user['user-id'], username } = user;
12 |
13 | const checkCode = await kb.sqlClient.query(
14 | `
15 | SELECT platform
16 | FROM access_token
17 | WHERE code=?`,
18 | [code]
19 | );
20 |
21 | if (_.isEmpty(checkCode)) {
22 | return `${user.username}, provided code is invalid.`;
23 | }
24 |
25 | const platform = _.get(checkCode, '0.platform');
26 |
27 | const checkUser = await kb.sqlClient.query(
28 | `
29 | SELECT COUNT(platform) as count
30 | FROM access_token
31 | WHERE user? AND platform=?`,
32 | [userId, platform]
33 | );
34 |
35 | if (_.size(checkUser)) {
36 | await kb.sqlClient.query(`DELETE FROM access_token WHERE code=?`, [code]);
37 |
38 | return `${user.username}, you are already registered for command kb ${platform}`;
39 | }
40 |
41 | await kb.sqlClient.query(
42 | `
43 | UPDATE access_token
44 | SET userName=?,
45 | user=?,
46 | code="Resolved",
47 | lastRenew=?
48 | WHERE code=?`,
49 | [username.replace('#', ''), userId, moment().format('YYYY-MM-DD hh:mm:ss'), code]
50 | );
51 |
52 | return `${user.username} All done! You can now use the kb ${platform} command :) Check the command syntax at https://kunszg.com/commands`;
53 | },
54 | };
55 |
--------------------------------------------------------------------------------
/k8s/deployments/glk-grafana/glk-grafana-deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: grafana
5 | namespace: logging
6 | spec:
7 | replicas: 1
8 | selector:
9 | matchLabels:
10 | app: grafana
11 | template:
12 | metadata:
13 | labels:
14 | app: grafana
15 | spec:
16 | securityContext:
17 | fsGroup: 472
18 | runAsUser: 472
19 | containers:
20 | - name: grafana
21 | image: grafana/grafana:10.2.0
22 | ports:
23 | - containerPort: 3005
24 | resources:
25 | limits:
26 | memory: 512Mi
27 | cpu: 300m
28 | requests:
29 | memory: 256Mi
30 | cpu: 150m
31 | env:
32 | # - name: GF_SECURITY_ADMIN_USER
33 | # value: "user"
34 | # - name: GF_SECURITY_ADMIN_PASSWORD
35 | # value: "pass"
36 | - name: GF_USERS_ALLOW_SIGN_UP
37 | value: "false"
38 | - name: GF_SERVER_HTTP_PORT
39 | value: "3005"
40 | volumeMounts:
41 | - name: grafana-storage
42 | mountPath: /var/lib/grafana
43 | - name: grafana-datasources
44 | mountPath: /etc/grafana/provisioning/datasources
45 | readinessProbe:
46 | httpGet:
47 | path: /api/health
48 | port: 3005
49 | initialDelaySeconds: 30
50 | timeoutSeconds: 2
51 | volumes:
52 | - name: grafana-storage
53 | hostPath:
54 | path: /tmp/grafana-storage
55 | - name: grafana-datasources
56 | configMap:
57 | name: grafana-datasources
--------------------------------------------------------------------------------
/lib/commands_old/query.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict';
3 |
4 | const utils = require('../utils/utils.js');
5 | const kb = require('../commandManager_old.js').kb;
6 | const database = require('../credentials/login.js').con;
7 | const mysql = require('mysql2');
8 |
9 | module.exports = {
10 | name: 'kb query',
11 | invocation: async (channel, user, message) => {
12 | try {
13 | const owner = await utils.Get.user().owner();
14 |
15 | if (user['user-id'] != owner[0].userId) {
16 | return '';
17 | }
18 |
19 | const query = (query, data = []) =>
20 | new Promise((resolve, reject) => {
21 | database.execute(mysql.format(query, data), async (err, results, fields) => {
22 | if (err) {
23 | kb.whisper('kunszg', 'error');
24 | reject(err);
25 | } else {
26 | resolve(results);
27 | }
28 | });
29 | });
30 |
31 | const msg = utils.getParam(message);
32 | const msgJoined = msg.join(' ').toLowerCase();
33 |
34 | const queryResp = await query(`${msg.join(' ')}`);
35 | if (
36 | msgJoined.includes('insert') ||
37 | msgJoined.includes('update') ||
38 | msgJoined.includes('delete')
39 | ) {
40 | return `${user['username']}, query completed, ${queryResp.info})`;
41 | }
42 |
43 | if (!msg.join(' ').includes('query')) {
44 | kb.whisper(
45 | user['username'],
46 | `forgot to add "As query" to the string FeelsDankMan !!!`
47 | );
48 | }
49 |
50 | return queryResp[0].query.toString();
51 | } catch (err) {
52 | utils.errorLog(err);
53 | kb.say(channel, err);
54 | }
55 | },
56 | };
57 |
--------------------------------------------------------------------------------
/kbot-website/src/app/api/user/settings/route.ts:
--------------------------------------------------------------------------------
1 | import { NextRequest, NextResponse } from 'next/server';
2 | import { getServerSession } from 'next-auth';
3 | import { authOptions } from '@/lib/auth';
4 | import { UserModel } from '@/lib/models/User';
5 |
6 | export async function GET() {
7 | try {
8 | const session = await getServerSession(authOptions);
9 |
10 | if (!session) {
11 | return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
12 | }
13 |
14 | const settings = await UserModel.getUserSettings(session.user.id);
15 | return NextResponse.json({ settings });
16 | } catch (error) {
17 | console.error('Error fetching user settings:', error);
18 | return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
19 | }
20 | }
21 |
22 | export async function POST(request: NextRequest) {
23 | try {
24 | const session = await getServerSession(authOptions);
25 |
26 | if (!session) {
27 | return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
28 | }
29 |
30 | const { key, value } = await request.json();
31 |
32 | if (!key || value === undefined) {
33 | return NextResponse.json({ error: 'Key and value are required' }, { status: 400 });
34 | }
35 |
36 | const success = await UserModel.setSetting(session.user.id, key, value);
37 |
38 | if (success) {
39 | return NextResponse.json({ message: 'Setting updated successfully' });
40 | } else {
41 | return NextResponse.json({ error: 'Failed to update setting' }, { status: 500 });
42 | }
43 | } catch (error) {
44 | console.error('Error updating user setting:', error);
45 | return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/k8s/deployments/glk-loki/glk-loki-config-map.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ConfigMap
3 | metadata:
4 | name: loki-config
5 | namespace: logging
6 | data:
7 | loki.yaml: |
8 | auth_enabled: false
9 |
10 | server:
11 | http_listen_port: 3100
12 | grpc_listen_port: 9096
13 |
14 | common:
15 | instance_addr: 127.0.0.1
16 | path_prefix: /tmp/loki
17 | storage:
18 | filesystem:
19 | chunks_directory: /tmp/loki/chunks
20 | rules_directory: /tmp/loki/rules
21 | replication_factor: 1
22 | ring:
23 | kvstore:
24 | store: inmemory
25 |
26 | query_range:
27 | results_cache:
28 | cache:
29 | embedded_cache:
30 | enabled: true
31 | max_size_mb: 1000
32 |
33 | schema_config:
34 | configs:
35 | - from: 2020-10-24
36 | store: boltdb-shipper
37 | object_store: filesystem
38 | schema: v11
39 | index:
40 | prefix: index_
41 | period: 24h
42 |
43 | ruler:
44 | alertmanager_url: http://localhost:9093
45 |
46 | limits_config:
47 | enforce_metric_name: false
48 | reject_old_samples: true
49 | reject_old_samples_max_age: 168h
50 | max_global_streams_per_user: 5000
51 | max_query_series: 5000
52 |
53 | table_manager:
54 | retention_deletes_enabled: true
55 | retention_period: 168h
56 |
57 | compactor:
58 | working_directory: /tmp/loki/boltdb-shipper-compactor
59 | shared_store: filesystem
60 |
61 | ingester:
62 | max_transfer_retries: 0
63 | lifecycler:
64 | address: 127.0.0.1
65 | ring:
66 | kvstore:
67 | store: inmemory
68 | replication_factor: 1
69 |
--------------------------------------------------------------------------------
/lib/commands/music/utils/executeSpotifyAction.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash');
2 |
3 | const searchSpotifySong = require('./searchSpotifySong');
4 |
5 | const executeSpotifyAction = async (context, { Commons }, action) => {
6 | const responses = Commons.ResponseRepository().getResponses();
7 | const message = _.get(context, 'userInputPartsWithNoSubCommand');
8 |
9 | if (_.isEmpty(message)) {
10 | return {
11 | response: responses.MUSIC.ERRORS.NO_SONG_PROVIDED(action.type)
12 | };
13 | }
14 |
15 | const userId = _.get(context, 'userstate.user-id');
16 |
17 | try {
18 | const searchResult = await searchSpotifySong(
19 | Commons.UserRepository().spotifyFetchWithOauth.bind(Commons.UserRepository()),
20 | message,
21 | userId,
22 | Commons
23 | );
24 |
25 | if (_.get(searchResult, 'error') && _.get(searchResult, 'response')) {
26 | return searchResult.response;
27 | }
28 |
29 | const spotifySearchEndpointResponse = _.get(searchResult, 'endpointResponse');
30 | const track = _.get(spotifySearchEndpointResponse, 'tracks.items.0');
31 |
32 | const [artistName, songTitle] = Commons.UtilityRepository().limitInputLength([
33 | _.get(track, 'artists.0.name'),
34 | _.get(track, 'name')
35 | ]);
36 |
37 | await action.execute(Commons, track, userId);
38 |
39 | return {
40 | response: action.responseMessage(songTitle, artistName)
41 | };
42 | } catch (error) {
43 | if (
44 | _.includes(
45 | _.get(error, 'response.body'),
46 | 'Player command failed: No active device found'
47 | )
48 | ) {
49 | return {
50 | response: responses.MUSIC.ERRORS.NO_ACTIVE_DEVICE
51 | };
52 | }
53 | throw error;
54 | }
55 | };
56 |
57 | module.exports = executeSpotifyAction;
58 |
--------------------------------------------------------------------------------
/lib/commands_old/unban.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict';
3 |
4 | const utils = require('../utils/utils.js');
5 |
6 | module.exports = {
7 | name: 'kb unban',
8 | invocation: async (channel, user, message, platform) => {
9 | try {
10 | if ((await utils.checkPermissions(user['username'])) < 3) {
11 | return '';
12 | }
13 |
14 | if (platform === 'whisper') {
15 | return 'This command is disabled on this platform';
16 | }
17 |
18 | const msg = utils.getParam(message);
19 | const comment = utils.getParam(message, 3);
20 |
21 | const got = require('got');
22 | const userid = await got(`https://api.ivr.fi/twitch/resolve/${msg[0]}`).json();
23 |
24 | const checkRepeatedInsert = await utils.query(
25 | `
26 | SELECT *
27 | FROM ban_list
28 | WHERE user_id=?`,
29 | [userid.id]
30 | );
31 |
32 | if (!checkRepeatedInsert.length) {
33 | return `${user['username']}, no such user found in the database.`;
34 | }
35 |
36 | // delete the row with unbanned user
37 | await utils.query(
38 | `
39 | DELETE FROM ban_list
40 | WHERE username=?`,
41 | [msg[0].toLowerCase()]
42 | );
43 |
44 | // insert into a table to store previously banned users
45 | await utils.query(
46 | `
47 | INSERT INTO unbanned_list (username, user_id, unbanned_by, date)
48 | VALUES (?, ?, ?, CURRENT_TIMESTAMP)`,
49 | [msg[0], userid.id, user['username']]
50 | );
51 |
52 | return `${user['username']}, user with ID ${userid.id} has been unbanned from the bot`;
53 | } catch (err) {
54 | utils.errorLog(err);
55 | return `${user['username']}, ${err} FeelsDankMan !!!`;
56 | }
57 | },
58 | };
59 |
--------------------------------------------------------------------------------
/lib/commands_old/4head.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict';
3 |
4 | const utils = require('../utils/utils.js');
5 | const kb = require('../commandManager_old.js').kb;
6 |
7 | // allow only one execution at the same time
8 | const stallTheCommand = new Set();
9 |
10 | module.exports = {
11 | stall: stallTheCommand,
12 | name: 'kb 4Head',
13 | invocation: async (channel, user, message, platform, messageState) => {
14 | try {
15 | if (stallTheCommand.has('busy')) {
16 | return '';
17 | }
18 | stallTheCommand.add('busy');
19 | setTimeout(() => {
20 | stallTheCommand.delete('busy');
21 | }, 6000);
22 |
23 | const joke = await kb.query('SELECT * FROM jokes ORDER BY RAND() LIMIT 1');
24 |
25 | setTimeout(() => {
26 | if (platform === 'whisper') {
27 | kb.whisper(
28 | user['username'],
29 | `${utils.lCase(joke[0].punchline.replace(/\./g, ''))} 4Head`
30 | );
31 | } else {
32 | kb.say(channel, `${utils.lCase(joke[0].punchline.replace(/\./g, ''))} 4Head`);
33 | }
34 | }, 3000);
35 |
36 | setTimeout(() => {
37 | stallTheCommand.delete('busy');
38 | }, 4500);
39 |
40 | if (platform === 'discord') {
41 | messageState.channel.send(utils.lCase(joke[0].setup)).then(sentMessage => {
42 | setTimeout(() => {
43 | sentMessage.edit(
44 | utils.lCase(joke[0].setup) +
45 | '\n\n' +
46 | utils.lCase(joke[0].punchline.replace(/\./g, ''))
47 | );
48 | }, 3000);
49 | });
50 | return '';
51 | }
52 |
53 | return `${user['username']}, ${utils.lCase(joke[0].setup)}`;
54 | } catch (err) {
55 | utils.errorLog(err);
56 | return `${user['username']} ${err} FeelsDankMan !!!`;
57 | }
58 | },
59 | };
60 |
--------------------------------------------------------------------------------
/k8s/deployments/music-bot/manifest.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: kbot-music-bot
5 | namespace: default
6 | spec:
7 | replicas: 1
8 | selector:
9 | matchLabels:
10 | app: music-bot
11 | template:
12 | metadata:
13 | labels:
14 | app: music-bot
15 | spec:
16 | containers:
17 | - name: music-bot
18 | image: docker-registry.kunszg.com/kbot-depl-music-bot:latest
19 | imagePullPolicy: Always
20 | volumeMounts:
21 | - name: options-volume
22 | mountPath: /usr/src/app/MusicBot/config/options.ini
23 | - name: permissions-volume
24 | mountPath: /usr/src/app/MusicBot/config/permissions.ini
25 | - name: cookies-volume
26 | mountPath: /usr/src/app/MusicBot/data/cookies.txt
27 | - name: playlist-volume
28 | mountPath: /usr/src/app/MusicBot/config/playlists/default.txt
29 | - name: requirements-volume
30 | mountPath: /usr/src/app/MusicBot/requirements.txt
31 | imagePullSecrets:
32 | - name: regcred-v1
33 | volumes:
34 | - name: options-volume
35 | hostPath:
36 | path: /home/docker/musicbot/options.ini
37 | type: File
38 | - name: permissions-volume
39 | hostPath:
40 | path: /home/docker/musicbot/permissions.ini
41 | type: File
42 | - name: cookies-volume
43 | hostPath:
44 | path: /home/docker/musicbot/cookies.txt
45 | type: File
46 | - name: playlist-volume
47 | hostPath:
48 | path: /home/docker/musicbot/playlists/default.txt
49 | type: File
50 | - name: requirements-volume
51 | hostPath:
52 | path: /home/docker/musicbot/requirements.txt
53 | type: File
54 |
55 |
--------------------------------------------------------------------------------
/lib/credentials/login.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict';
3 |
4 | // first connection to database to receive channel list
5 | const creds = require('./config.js');
6 | const mysql = require('mysql2');
7 | const con = mysql.createConnection({
8 | host: creds.db_host,
9 | user: creds.db_server_user,
10 | password: creds.db_pass,
11 | database: creds.db_name,
12 | socketPath: '/var/run/mysqld/mysqld.sock'
13 | });
14 |
15 | con.on('error', err => {
16 | if (err.fatal) {
17 | con.destroy();
18 | }
19 | throw err;
20 | });
21 |
22 | const getChannels = () =>
23 | new Promise((resolve, reject) => {
24 | con.query('SELECT * FROM channels', (err, results) => {
25 | if (err) {
26 | reject(err);
27 | } else {
28 | resolve(results);
29 | }
30 | });
31 | });
32 | const channelList = [];
33 | const channelOptions = [];
34 | async function res() {
35 | channelList.push(await getChannels());
36 | await channelList[0].forEach(i => channelOptions.push(i.channel));
37 | }
38 | res();
39 |
40 | function sleep(milliseconds) {
41 | var start = new Date().getTime();
42 | for (var i = 0; i < 1e7; i++) {
43 | if (new Date().getTime() - start > milliseconds) {
44 | break;
45 | }
46 | }
47 | }
48 | sleep(1500);
49 |
50 | let options = {
51 | options: {
52 | debug: false,
53 | },
54 | connection: {
55 | secure: true,
56 | },
57 | identity: {
58 | username: 'ksyncbot',
59 | password: creds.oauth,
60 | },
61 | channels: channelOptions,
62 | };
63 |
64 | if (process.platform === 'win32') {
65 | options = {
66 | options: {
67 | debug: false,
68 | },
69 | connection: {
70 | secure: true,
71 | },
72 | identity: {
73 | username: 'ksyncbot',
74 | password: creds.oauth,
75 | },
76 | channels: ['ksyncbot'],
77 | };
78 | }
79 |
80 | module.exports = { options, con };
81 |
82 | channelList.length = 0;
83 | channelOptions.length = 0;
84 |
--------------------------------------------------------------------------------
/lib/job-manager/tasks/updateUsageStats.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash');
2 | const moment = require('moment');
3 |
4 | const updateUsageStats = async kb => {
5 | const userCount = await kb.sqlClient.query('SELECT COUNT(*) as count FROM user_list');
6 | const commandsCount = await kb.sqlClient.query('SELECT COUNT(*) as count FROM executions');
7 |
8 | const dateNow = moment().format('YYYY-MM-DD HH:mm:ss');
9 |
10 | await kb.sqlClient.query(`
11 | UPDATE stats
12 | SET count="${commandsCount[0].count}",
13 | date="${dateNow}"
14 | WHERE type="statsApi" AND sha="commandExecs"`);
15 |
16 | await kb.sqlClient.query(`
17 | UPDATE stats
18 | SET count="${userCount[0].count}",
19 | date="${dateNow}"
20 | WHERE type="statsApi" AND sha="totalUsers"`);
21 |
22 | let userLoggedInCount = await kb.sqlClient.query(`
23 | SELECT COUNT(*) AS count
24 | FROM access_token
25 | WHERE platform="spotify" OR platform="lastfm" AND user IS NOT NULL`);
26 |
27 | let commandExecutionsCount = await kb.sqlClient.query(`
28 | SELECT COUNT(*) AS count
29 | FROM executions
30 | WHERE command LIKE "%spotify%"`);
31 |
32 | [commandExecutionsCount, userLoggedInCount] = [
33 | _.get(commandExecutionsCount, '0.count'),
34 | _.get(userLoggedInCount, '0.count'),
35 | ];
36 |
37 | if (commandExecutionsCount && userLoggedInCount) {
38 | await kb.redisClient
39 | .multi()
40 | .set(
41 | 'kb:api:website-pages:command-executions-count',
42 | JSON.stringify(commandExecutionsCount)
43 | )
44 | .set(
45 | 'kb:api:website-pages:spotify-and-lastfm-user-logged-in-count',
46 | JSON.stringify(userLoggedInCount)
47 | )
48 | .exec();
49 | }
50 |
51 | kb.websocketClient.websocketEmitter.emit('wsl', {
52 | type: 'updateCache',
53 | data: true,
54 | });
55 | };
56 |
57 | module.exports = updateUsageStats;
58 |
--------------------------------------------------------------------------------
/commons/connector/services/discordClient.js:
--------------------------------------------------------------------------------
1 | const Discord = require('discord.js');
2 | const { discordConfig } = require('../consts/serviceConfigs');
3 |
4 | /**
5 | * Singleton class for managing Discord bot connections.
6 | */
7 | class DiscordClient {
8 | constructor() {
9 | if (!DiscordClient.instance) {
10 | DiscordClient.instance = this;
11 | this.client = null;
12 | this.isConnected = false;
13 | }
14 |
15 | return DiscordClient.instance;
16 | }
17 |
18 | /**
19 | * Establishes a connection to the Discord bot.
20 | * If a connection already exists, it returns the existing connection.
21 | * @returns {Promise} The Discord client instance.
22 | * @throws {Error} If there is an error establishing the connection.
23 | */
24 | async connect() {
25 | if (!this.client) {
26 | this.client = new Discord.Client({ intents: ['GUILDS', 'GUILD_MESSAGES'] });
27 |
28 | this.client.once('ready', () => {
29 | this.isConnected = true;
30 | console.log('[Connector-Discord] Connected');
31 | });
32 |
33 | this.client.on('error', error => {
34 | console.error('[Connector-Discord] Client error:', error);
35 | this.isConnected = false;
36 | });
37 |
38 | try {
39 | await this.client.login(discordConfig.discordLogin);
40 | DiscordClient.instance.native = this.client;
41 | } catch (error) {
42 | console.error('[Connector-Discord] Connection error:', error);
43 | this.isConnected = false;
44 | }
45 | }
46 |
47 | return this.client;
48 | }
49 |
50 | async close() {
51 | if (this.client) {
52 | console.log('[Connector-Discord] Closing connection...');
53 | await this.client.destroy();
54 | this.client = null;
55 | this.isConnected = false;
56 | console.log('[Connector-Discord] Connection closed');
57 | }
58 | }
59 | }
60 |
61 | module.exports = {
62 | discordClient: new DiscordClient()
63 | };
64 |
--------------------------------------------------------------------------------
/kbot-website/src/app/api/connected-apps/route.ts:
--------------------------------------------------------------------------------
1 | import { NextRequest, NextResponse } from 'next/server';
2 | import { getServerSession } from 'next-auth';
3 | import { authOptions } from '@/lib/auth';
4 | import { UserModel } from '@/lib/models/User';
5 |
6 | export async function GET() {
7 | try {
8 | const session = await getServerSession(authOptions);
9 |
10 | if (!session) {
11 | return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
12 | }
13 |
14 | const connectedApps = await UserModel.getConnectedApps(session.user.id);
15 |
16 | const sanitizedApps = connectedApps.map(app => ({
17 | id: app.id,
18 | app_name: app.app_name,
19 | app_type: app.app_type,
20 | permissions: app.permissions,
21 | connected_at: app.connected_at
22 | }));
23 |
24 | return NextResponse.json({ connectedApps: sanitizedApps });
25 | } catch (error) {
26 | console.error('Error fetching connected apps:', error);
27 | return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
28 | }
29 | }
30 |
31 | export async function DELETE(request: NextRequest) {
32 | try {
33 | const session = await getServerSession(authOptions);
34 |
35 | if (!session) {
36 | return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
37 | }
38 |
39 | const { appType } = await request.json();
40 |
41 | if (!appType) {
42 | return NextResponse.json({ error: 'App type is required' }, { status: 400 });
43 | }
44 |
45 | const success = await UserModel.removeConnectedApp(session.user.id, appType);
46 |
47 | if (success) {
48 | return NextResponse.json({ message: 'App disconnected successfully' });
49 | } else {
50 | return NextResponse.json({ error: 'Failed to disconnect app' }, { status: 500 });
51 | }
52 | } catch (error) {
53 | console.error('Error disconnecting app:', error);
54 | return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/lib/commands_old/joemama.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict';
3 |
4 | const utils = require('../utils/utils.js');
5 |
6 | module.exports = {
7 | name: 'kb joemama',
8 | invocation: async (channel, user, message, platform) => {
9 | try {
10 | const msg = utils.getParam(message);
11 |
12 | // get data from file
13 | const joke = (await utils.query('SELECT * FROM momjokes ORDER BY RAND() LIMIT 1'))[0]
14 | .joke;
15 |
16 | // get a random emote from database
17 | const randomEmote = await utils.query(
18 | `
19 | SELECT *
20 | FROM emotes
21 | WHERE channel=?
22 | ORDER BY RAND()
23 | LIMIT 1`,
24 | [channel.replace('#', '')]
25 | );
26 |
27 | // check for . or ! at the end of string
28 | const patt = /[.|!]$/g;
29 | const response = patt.test(joke) ? joke.slice(0, -1) : joke;
30 |
31 | if (platform === 'whisper') {
32 | return `${utils.lCase(response)}}`;
33 | }
34 |
35 | let username = user.username;
36 |
37 | if (msg[0]) {
38 | const _user = await utils.Get.user().byUsername(
39 | msg[0].replace(/@|,/g, '').toLowerCase()
40 | );
41 |
42 | if (_user.length) {
43 | const checkIfOptedOut = await utils.query(
44 | `
45 | SELECT *
46 | FROM optout
47 | WHERE command=? AND username=?`,
48 | ['joemama', _user[0].username]
49 | );
50 |
51 | if (checkIfOptedOut.length && user.username !== _user[0].username) {
52 | return `${user.username}, that user has opted out from being a target of this command.`;
53 | }
54 |
55 | username = _user[0].username;
56 | }
57 | }
58 |
59 | return `${username}, ${utils.lCase(response)} ${
60 | randomEmote.length ? randomEmote[0].emote : 'LUL'
61 | }`;
62 | } catch (err) {
63 | await utils.errorLog(err);
64 | }
65 | },
66 | };
67 |
--------------------------------------------------------------------------------
/k8s/deployments/glk-loki/glk-loki-deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: loki
5 | namespace: logging
6 | spec:
7 | replicas: 1
8 | selector:
9 | matchLabels:
10 | app: loki
11 | template:
12 | metadata:
13 | labels:
14 | app: loki
15 | spec:
16 | securityContext:
17 | runAsUser: 10001
18 | runAsGroup: 10001
19 | fsGroup: 10001
20 | initContainers:
21 | - name: init-loki
22 | image: busybox:1.35
23 | command: ['sh', '-c', 'mkdir -p /tmp/loki/chunks /tmp/loki/rules /tmp/loki/boltdb-shipper-compactor && chown -R 10001:10001 /tmp/loki']
24 | volumeMounts:
25 | - name: storage
26 | mountPath: /tmp/loki
27 | securityContext:
28 | runAsUser: 0
29 | containers:
30 | - name: loki
31 | image: grafana/loki:2.9.2
32 | args:
33 | - -config.file=/etc/loki/loki.yaml
34 | ports:
35 | - containerPort: 3100
36 | name: http-metrics
37 | - containerPort: 9096
38 | name: grpc
39 | resources:
40 | limits:
41 | memory: 1Gi
42 | cpu: 500m
43 | requests:
44 | memory: 512Mi
45 | cpu: 250m
46 | volumeMounts:
47 | - name: config
48 | mountPath: /etc/loki
49 | - name: storage
50 | mountPath: /tmp/loki
51 | readinessProbe:
52 | httpGet:
53 | path: /ready
54 | port: 3100
55 | initialDelaySeconds: 30
56 | timeoutSeconds: 5
57 | livenessProbe:
58 | httpGet:
59 | path: /ready
60 | port: 3100
61 | initialDelaySeconds: 45
62 | timeoutSeconds: 5
63 | volumes:
64 | - name: config
65 | configMap:
66 | name: loki-config
67 | - name: storage
68 | emptyDir: {}
--------------------------------------------------------------------------------
/lib/commands_old/rp.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict';
3 |
4 | const utils = require('../utils/utils.js');
5 | const got = require('got');
6 |
7 | module.exports = {
8 | name: 'kb rp',
9 | invocation: async (channel, user, message, platform) => {
10 | try {
11 | if (platform === 'whisper') {
12 | return 'This command is disabled on this platform';
13 | }
14 |
15 | const msg = utils.getParam(message);
16 |
17 | Date.prototype.addSeconds = function (seconds) {
18 | var copiedDate = new Date(this.getTime());
19 | return new Date(copiedDate.getTime() + seconds * 1000);
20 | };
21 | const now = new Date();
22 |
23 | if (channel === '#supinic') {
24 | if (msg[0] === 'update' && (await utils.checkPermissions(user['username'])) >= 2) {
25 | const playsound = await got('https://supinic.com/api/bot/playsound/list').json();
26 |
27 | playsound.data.playsounds.forEach(i =>
28 | utils.query(
29 | `
30 | INSERT INTO playsounds (name, cooldown, last_executed)
31 | VALUES (?, ?, ?)`,
32 | [i.name, i.cooldown, new Date().toISOString().slice(0, 19).replace('T', ' ')]
33 | )
34 | );
35 | return `${user['username']}, done :)`;
36 | }
37 | const getPS = await utils.query(`
38 | SELECT *
39 | FROM playsounds
40 | WHERE last_executed < NOW()
41 | GROUP BY RAND()
42 | LIMIT 1`);
43 |
44 | const time = now
45 | .addSeconds(getPS[0].cooldown / 1000)
46 | .toISOString()
47 | .slice(0, 19)
48 | .replace('T', ' ');
49 |
50 | await utils.query(
51 | `
52 | UPDATE playsounds
53 | SET last_executed=?
54 | WHERE name=?`,
55 | [time, getPS[0].name]
56 | );
57 |
58 | return `$ps ${getPS[0].name}`;
59 | }
60 | } catch (err) {
61 | utils.errorLog(err);
62 | return `${user['username']}, ${err} FeelsDankMan !!!`;
63 | }
64 | },
65 | };
66 |
--------------------------------------------------------------------------------
/lib/commands_old/dank.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict';
3 |
4 | const utils = require('../utils/utils.js');
5 |
6 | module.exports = {
7 | name: 'kb dank',
8 | invocation: async (channel, user, message) => {
9 | try {
10 | const msg = utils.getParam(message.toLowerCase());
11 |
12 | if (!msg[0] || msg[0] === user['username']) {
13 | return `${user['username']}, FeelsDankMan oh zoinks, you just got flippin'
14 | danked by yourself FeelsDankMan FeelsDankMan FeelsDankMan`;
15 | }
16 |
17 | if (msg.length > 5) {
18 | return `Danking more than 5 people is illegal < FeelsDankMan /`;
19 | }
20 |
21 | const checkIfOptedOut = async () => {
22 | for (let i = 0; i < msg.length; i++) {
23 | const findUser = await utils.query(
24 | `
25 | SELECT *
26 | FROM optout
27 | WHERE command=? AND username=?`,
28 | ['stats', msg[i].toLowerCase().replace(/@|,/g, '')]
29 | );
30 |
31 | if (
32 | findUser.length &&
33 | user['username'] != msg[i].toLowerCase().replace(/@|,/g, '')
34 | ) {
35 | return findUser;
36 | continue;
37 | }
38 | }
39 | };
40 | if ((await checkIfOptedOut())?.length ?? false) {
41 | return `${user['username']}, that user has opted out from being a target of this command.`;
42 | }
43 |
44 | if (msg.length > 1) {
45 | const formattedMessage = msg
46 | .reverse()
47 | .join(', ')
48 | .replace(', ', ' and ')
49 | .split(',')
50 | .reverse()
51 | .join(', ');
52 | return `oh zoinks ${formattedMessage} just got flippin' danked < FeelsDankMan /`;
53 | }
54 |
55 | return `${user['username']} danked ${msg[0]
56 | .toLowerCase()
57 | .replace(/@|,/g, '')} FeelsDankMan 👍`;
58 | } catch (err) {
59 | utils.errorLog(err);
60 | return `${user['username']}, ${err} FeelsDankMan !!!`;
61 | }
62 | },
63 | };
64 |
--------------------------------------------------------------------------------
/api/src/endpoints/userGet.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash');
2 |
3 | const userGet = services => {
4 | const { app, Commons } = services;
5 |
6 | const sendErrorResponse = (res, status, message) => {
7 | res.send({
8 | status,
9 | message,
10 | });
11 | };
12 |
13 | const processUser = async (res, userRepository, userId) => {
14 | const users = await userRepository.getUser({ userId });
15 |
16 | if (!users.length) {
17 | return sendErrorResponse(res, 404, 'user not found');
18 | }
19 |
20 | const isOptedOut = await userRepository.isUserOptedOut('namechange', userId);
21 |
22 | if (isOptedOut) {
23 | return sendErrorResponse(
24 | res,
25 | 403,
26 | 'user has opted out from being searched by this endpoint'
27 | );
28 | }
29 |
30 | const pastUsernames = users.map(({ username, color, added }) => ({
31 | username,
32 | color,
33 | foundUTC: added,
34 | foundTimestamp: Date.parse(added),
35 | }));
36 |
37 | res.send({
38 | status: 200,
39 | userid: userId,
40 | currentUsername: users[users.length - 1].username,
41 | nameHistory: pastUsernames,
42 | });
43 | };
44 |
45 | app.get('/api/user', async (req, res) => {
46 | const receivedUserId = _.get(req, 'headers.userid') || _.get(req, 'query.userid');
47 | const receivedUsername = _.get(req, 'headers.username') || _.get(req, 'query.username');
48 |
49 | if (!receivedUserId && !receivedUsername) {
50 | return sendErrorResponse(res, 400, 'bad request');
51 | }
52 |
53 | const userRepository = Commons.UserRepository(Commons.ServiceConnector.Connector);
54 |
55 | if (receivedUsername) {
56 | const user = await userRepository.getUser({ username: receivedUsername });
57 |
58 | if (!user.length) {
59 | return sendErrorResponse(res, 404, 'user not found');
60 | }
61 |
62 | const userId = _.get(user, '0.userId');
63 | await processUser(res, userRepository, userId);
64 | } else if (receivedUserId) {
65 | await processUser(res, userRepository, receivedUserId);
66 | }
67 | });
68 | };
69 |
70 | module.exports = userGet;
71 |
--------------------------------------------------------------------------------
/lib/mirrors/reddit-live-thread-to-discord/redditLiveThreadToDiscord.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash');
2 |
3 | const serviceConnector = require('../../../commons/connector/serviceConnector');
4 | const serviceSettings = require('../../../consts/serviceSettings.json');
5 |
6 | const sendRedditMessageToQueue = require('./utils/sendRedditMessageToQueue');
7 |
8 | const service = serviceSettings.services.redditLiveThreadToDiscord;
9 |
10 | (async () => {
11 | const kb = await serviceConnector.Connector.dependencies(
12 | ['sql', 'redis', 'rabbit', 'reddit', 'discord'],
13 | {
14 | enableHealthcheck: true,
15 | service,
16 | }
17 | );
18 |
19 | sendRedditMessageToQueue(kb, service.queues.KB_HANDLER_REDDIT_LIVETHREADS);
20 |
21 | kb.discordClient.native.on('ready', async () => {
22 | await kb.rabbitClient.createRabbitChannel(
23 | service.queues.KB_HANDLER_REDDIT_LIVETHREADS,
24 | async (msg, consumer, msgRaw) => {
25 | const jsonMessage = JSON.parse(msg);
26 |
27 | let body = _.get(jsonMessage, 'body', '');
28 |
29 | if (!body) {
30 | await consumer.ack(msgRaw);
31 | return;
32 | }
33 |
34 | body = body.replace('https://twitter', 'https://fxtwitter');
35 |
36 | const liveThreadChannels = await kb.sqlClient.query(
37 | 'SELECT * FROM liveThreadChannels'
38 | );
39 |
40 | _.forEach(liveThreadChannels, liveThreadChannel => {
41 | const channel = kb.discordClient.native.channels.cache.find(
42 | channel => channel.id === liveThreadChannel.channelID
43 | );
44 |
45 | kb.sqlClient.query(
46 | `INSERT INTO livethreads (data, date) VALUES (?, CURRENT_TIMESTAMP)`,
47 | [msg]
48 | );
49 |
50 | if (liveThreadChannel.threadID) {
51 | const thread = channel.threads.cache.find(
52 | thread => thread.id === liveThreadChannel.threadID
53 | );
54 |
55 | thread.send(body);
56 | } else if (channel) {
57 | channel.send(body);
58 | }
59 | });
60 |
61 | await consumer.ack(msgRaw);
62 | }
63 | );
64 | });
65 | })();
66 |
--------------------------------------------------------------------------------
/api/src/site/pageLastfmResolved.js:
--------------------------------------------------------------------------------
1 | const got = require('got');
2 | const creds = require('../../../lib/credentials/config');
3 | const fs = require('fs');
4 | const _ = require('lodash');
5 |
6 | const pageLastfmResolved = services => {
7 | const { app, Commons } = services;
8 |
9 | const kb = Commons.ServiceConnector.Connector;
10 |
11 | app.get('/lastfmresolved', async (req, res) => {
12 | if (!_.get(req, 'query.verifcode') || !_.get(req, 'query.user')) {
13 | res.status(400).send('Error - no verifcode/user provided');
14 | return;
15 | }
16 |
17 | const checkIfUserExists = await got(
18 | `http://ws.audioscrobbler.com/2.0/?method=user.getinfo&user=${req.query.user}&api_key=${creds.lastfmApiKey}&format=json&limit=2`
19 | ).json();
20 |
21 | if (!_.get(checkIfUserExists, 'user.name')) {
22 | res.send('This username does not exist on Lastfm.');
23 | return;
24 | }
25 |
26 | let html = _.toString(
27 | fs.readFileSync('../../kbot-website/html/express_pages/lastfmResolve.html')
28 | );
29 |
30 | const page = Commons.UtilityRepository.complementHtmlPageTemplates(html, [
31 | {
32 | code: _.get(req, 'query.verifcode')
33 | }
34 | ]);
35 |
36 | try {
37 | await (async () => {
38 | res.send(page);
39 |
40 | await kb.sqlClient.query(
41 | `
42 | UPDATE access_token
43 | SET access_token=?,
44 | refresh_token="lastfm currently playing",
45 | platform="lastfm",
46 | premium="N",
47 | allowlookup="N",
48 | scopes="lastfm currently playing"
49 | WHERE code=?`,
50 | [req.query.user, req.query.verifcode]
51 | );
52 | })();
53 | } catch (err) {
54 | if (err.message === 'Response code 400 (Bad Request)') {
55 | res.send('Your code has expired, repeat the process.');
56 | return;
57 | }
58 |
59 | if (err.message === 'no query') {
60 | res.send('Invalid code.');
61 | }
62 | }
63 | });
64 | };
65 |
66 | module.exports = pageLastfmResolved;
67 |
--------------------------------------------------------------------------------
/lib/examples/example_chat_message.json:
--------------------------------------------------------------------------------
1 | {
2 | "badge-info": "subscriber/66",
3 | "badges": [
4 | {
5 | "name": "vip",
6 | "version": "1"
7 | },
8 | {
9 | "name": "subscriber",
10 | "version": "12"
11 | },
12 | {
13 | "name": "bits-charity",
14 | "version": "1"
15 | }
16 | ],
17 | "color": "#9ACD32",
18 | "display-name": "Leppunen",
19 | "emotes": [],
20 | "first-msg": "0",
21 | "flags": [],
22 | "id": "6cddefd1-97f0-4784-a8ac-f0b20c9a6538",
23 | "mod": "0",
24 | "returning-chatter": "0",
25 | "room-id": "31400525",
26 | "subscriber": "1",
27 | "tmi-sent-ts": "1734961284882",
28 | "turbo": "0",
29 | "user-id": "42239452",
30 | "user-type": "",
31 | "vip": "1",
32 | "username": "leppunen",
33 | "message-type": "chat",
34 | "rawSource": "@badge-info=subscriber/66;badges=vip/1,subscriber/12,bits-charity/1;color=#9ACD32;display-name=Leppunen;emotes=;first-msg=0;flags=;id=6cddefd1-97f0-4784-a8ac-f0b20c9a6538;mod=0;returning-chatter=0;room-id=31400525;subscriber=1;tmi-sent-ts=1734961284882;turbo=0;user-id=42239452;user-type=;vip=1 :leppunen!leppunen@leppunen.tmi.twitch.tv PRIVMSG #supinic :and now it's going away",
35 | "ircPrefixRaw": "leppunen!leppunen@leppunen.tmi.twitch.tv",
36 | "ircPrefix": {
37 | "nickname": "leppunen",
38 | "username": "leppunen",
39 | "hostname": "leppunen.tmi.twitch.tv"
40 | },
41 | "ircCommand": "PRIVMSG",
42 | "ircParameters": [
43 | "#supinic",
44 | "and now it's going away"
45 | ],
46 | "channelName": "supinic",
47 | "messageText": "and now it's going away",
48 | "isAction": false,
49 | "senderUsername": "leppunen",
50 | "channelID": "31400525",
51 | "senderUserID": "42239452",
52 | "badgeInfo": [
53 | {
54 | "name": "subscriber",
55 | "version": "66"
56 | }
57 | ],
58 | "badgeInfoRaw": "subscriber/66",
59 | "badgesRaw": "vip/1,subscriber/12,bits-charity/1",
60 | "colorRaw": "#9ACD32",
61 | "displayName": "Leppunen",
62 | "emotesRaw": "",
63 | "flagsRaw": "",
64 | "messageID": "6cddefd1-97f0-4784-a8ac-f0b20c9a6538",
65 | "isMod": false,
66 | "isModRaw": "0",
67 | "serverTimestamp": "2024-12-23T13:41:24.882Z",
68 | "serverTimestampRaw": "1734961284882"
69 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "kunszgbot",
3 | "version": "3.4.8",
4 | "description": "Utility and entertainment bot providing commands with a goal to have more fun in offline chats.",
5 | "main": "init.js",
6 | "private": true,
7 | "scripts": {
8 | "test": "jest",
9 | "website-dev": "cd website && next dev -p 31900",
10 | "website-build": "cd website && next build",
11 | "website-start": "cd website && next start -p 31900",
12 | "website-lint": "cd website && next lint"
13 | },
14 | "jest": {
15 | "verbose": true
16 | },
17 | "author": "kunszg",
18 | "license": "ISC",
19 | "dependencies": {
20 | "@mastondzn/dank-twitch-irc": "^7.2.0",
21 | "@nextui-org/react": "^2.2.10",
22 | "amqplib": "^0.10.3",
23 | "body-parser": "1.20.3",
24 | "buffer-equal-constant-time": "^1.0.1",
25 | "discord.js": "^14.16.3",
26 | "express": "5.0.1",
27 | "express-rate-limit": "^7.1.3",
28 | "googleapis": "^144.0.0",
29 | "got": "^11.8.5",
30 | "humanize-duration": "^3.30.0",
31 | "inquirer": "^8.2.4",
32 | "lodash": "^4.17.21",
33 | "moment": "^2.29.4",
34 | "moment-timezone": "^0.5.43",
35 | "mysql2": "3.9.8",
36 | "next": "15.3.1",
37 | "os": "^0.1.2",
38 | "react": "^18.3.1",
39 | "react-dom": "^18.3.1",
40 | "redis": "^4.6.10",
41 | "require-dir": "^1.2.0",
42 | "snoowrap": "^1.23.0",
43 | "socket.io": "^4.8.1",
44 | "socket.io-client": "^4.8.1",
45 | "table-builder": "^2.1.1",
46 | "tailwindcss": "^4.0.8",
47 | "tmi.js": "1.8.5",
48 | "ws": "^8.16.0",
49 | "yt-search": "^2.12.1",
50 | "ytsearcher": "1.2.4",
51 | "zlib": "^1.0.5"
52 | },
53 | "directories": {
54 | "lib": "lib"
55 | },
56 | "repository": {
57 | "type": "git",
58 | "url": "git+https://github.com/KUNszg/kbot.git"
59 | },
60 | "bugs": {
61 | "url": "https://github.com/KUNszg/kbot/issues"
62 | },
63 | "homepage": "https://github.com/KUNszg/kbot#readme",
64 | "devDependencies": {
65 | "@types/node": "^20",
66 | "@types/react": "^18",
67 | "@types/react-dom": "^18",
68 | "eslint": "^8",
69 | "eslint-config-next": "14.1.3",
70 | "jest": "^29.7.0",
71 | "prettier": "^3.5.3",
72 | "typescript": "^5"
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/lib/mirrors/twitch-chat-logger/twitchChatQueueFiller.js:
--------------------------------------------------------------------------------
1 | const moment = require('moment');
2 | const _ = require('lodash');
3 |
4 | const serviceSettings = require('../../../consts/serviceSettings.json');
5 | const serviceConnector = require('../../../commons/connector/serviceConnector');
6 |
7 | const service = serviceSettings.services.twitchChatQueueFiller;
8 |
9 | const moduleName = 'logger';
10 |
11 | (async () => {
12 | const kb = await serviceConnector.Connector.dependencies(['websocket', 'tmi', 'rabbit'], {
13 | enableHealthcheck: true,
14 | service,
15 | });
16 |
17 | const mpsCache = [];
18 |
19 | kb.tmiClient.consumer.tmiEmitter.on('message', async (channel, user, message) => {
20 | mpsCache.push(moment().unix());
21 |
22 | await kb.rabbitClient.sendToQueue(service.queues.KB_TWITCH_CHAT_MESSAGES, {
23 | channel: _.replace(channel, '#', _.stubString()),
24 | username: user['username'],
25 | 'user-id': user['user-id'],
26 | color: user['color'],
27 | message,
28 | date: moment().format('YYYY-MM-DD HH:mm:ss'),
29 | });
30 | });
31 |
32 | kb.tmiClient.consumer.tmiEmitter.on('notice', async (channel, msgId, message) => {
33 | if (msgId === 'host_target_went_offline' || msgId === 'host_on') {
34 | return;
35 | }
36 |
37 | await kb.rabbitClient.sendToQueue(service.queues.KB_TWITCH_CHAT_NOTICE, {
38 | channel: _.replace(channel, '#', _.stubString()),
39 | message,
40 | msgId,
41 | moduleName,
42 | });
43 | });
44 |
45 | kb.tmiClient.consumer.tmiEmitter.on('usernotice', async msg => {
46 | const msgId = msg.messageTypeID;
47 | const channel = msg.channelName;
48 |
49 | if (msgId === 'host_target_went_offline' || msgId === 'host_on') {
50 | return;
51 | }
52 |
53 | await kb.rabbitClient.sendToQueue(service.queues.KB_TWITCH_CHAT_NOTICE, {
54 | msgId,
55 | message: _.get(msg, 'systemMessage'),
56 | channel: _.replace(channel, '#', _.stubString()),
57 | moduleName,
58 | });
59 | });
60 |
61 | setInterval(() => {
62 | kb.websocketClient.websocketEmitter.emit('/wsl', {
63 | type: 'mps',
64 | data: mpsCache.filter(mps => mps < moment().unix() - 1000),
65 | });
66 |
67 | mpsCache.length = 0;
68 | }, 3000);
69 | })();
70 |
--------------------------------------------------------------------------------
/lib/commands_old/ban.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict';
3 |
4 | const utils = require('../utils/utils.js');
5 | const got = require('got');
6 |
7 | module.exports = {
8 | name: 'kb ban',
9 | invocation: async (channel, user, message, platform) => {
10 | try {
11 | if ((await utils.checkPermissions(user['username'])) < 3) {
12 | return '';
13 | }
14 |
15 | if (platform === 'whisper') {
16 | return 'This usage is disabled on this platform';
17 | }
18 |
19 | const msg = utils.getParam(message);
20 | const comment = utils.getParam(message, 3);
21 |
22 | const api = await utils.Get.api().url(message);
23 | const userid = await got(api + msg[0]).json();
24 |
25 | const checkRepeatedInsert = await utils.query(
26 | `
27 | SELECT *
28 | FROM ban_list
29 | WHERE user_id=?`,
30 | [userid.id]
31 | );
32 |
33 | if (checkRepeatedInsert.length != 0) {
34 | return `${user['username']}, user with ID ${userid.id} is already banned.`;
35 | }
36 |
37 | if (comment.length != 0) {
38 | if (!comment.join(' ').startsWith('//')) {
39 | return `${user['username']}, syntax error, use // before comments.`;
40 | }
41 |
42 | // insert into the database to ban the user (if there is a comment)
43 | await utils.query(
44 | `
45 | INSERT INTO ban_list (username, user_id, comment, banned_by, date)
46 | VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)`,
47 | [msg[0], userid.id, comment.join(' ').replace('//', ''), user['username']]
48 | );
49 |
50 | return `${user['username']}, user with ID ${userid.id} is now banned from the bot.`;
51 | }
52 |
53 | // insert into the database to ban the user (if there is no comment)
54 | await utils.query(
55 | `
56 | INSERT INTO ban_list (username, user_id, banned_by, date)
57 | VALUES (?, ?, ?, CURRENT_TIMESTAMP)`,
58 | [msg[0], userid.id, user['username']]
59 | );
60 |
61 | return `${user['username']}, user with ID ${userid.id} is now banned from the bot.`;
62 | } catch (err) {
63 | utils.errorLog(err);
64 | return `${user['username']}, ${err} FeelsDankMan !!!`;
65 | }
66 | },
67 | };
68 |
--------------------------------------------------------------------------------
/api/api.test.js:
--------------------------------------------------------------------------------
1 | const got = require('got');
2 | const _ = require('lodash');
3 | const childProcess = require('child_process');
4 |
5 | const config = require('../lib/credentials/config');
6 |
7 | const PORT = process.env.PORT || 8080;
8 |
9 | const sleep = time => {
10 | return new Promise(resolve => setTimeout(resolve, time));
11 | };
12 |
13 | const request = endpoint => {
14 | return got('http://127.0.0.1' + endpoint, {
15 | port: PORT,
16 | });
17 | };
18 |
19 | beforeAll(async () => {
20 | childProcess.exec(config.puttyExecutable);
21 |
22 | await sleep(3000);
23 |
24 | require('./api');
25 |
26 | await sleep(35000);
27 | }, 60000);
28 |
29 | describe('Integration tests', () => {
30 | test('healthcheckGet_WhenResourceRequested_Returns200AndOK', async () => {
31 | const response = await request('/healthcheck');
32 |
33 | expect(response.body).toBe('OK');
34 | expect(response.statusCode).toBe(200);
35 | });
36 |
37 | describe('API', () => {
38 | test('channelsGet_WhenResourceRequested_ReturnsListOfChannels', async () => {
39 | const response = await request('/api/channels').json();
40 |
41 | const responseDataSize = _.size(_.get(response, 'data'));
42 |
43 | expect(responseDataSize).toBeGreaterThan(0);
44 | });
45 |
46 | test('statsGet_WhenResourceRequested_ReturnsObjectWithStats', async () => {
47 | const response = await request('/api/stats').json();
48 |
49 | expect(_.isEmpty(response)).toBeFalsy();
50 | });
51 |
52 | test('userGet_WhenResourceRequested_ReturnsListOfUserNameHistory', async () => {
53 | const response = await request('/api/user?username=eppunen').json();
54 |
55 | const responseDataSize = _.size(_.get(response, "nameHistory"));
56 |
57 | expect(responseDataSize).toBeGreaterThan(0);
58 | });
59 |
60 | test('randomEmoteGet_WhenResourceRequested_ReturnsListOfRandomTwitchEmotes', async () => {
61 | const response = await request('/randomemote').json();
62 |
63 | const responseDataSize = _.size(response);
64 |
65 | expect(responseDataSize).toBeGreaterThan(0);
66 | });
67 | });
68 | });
69 |
70 | afterAll(
71 | () =>
72 | setTimeout(async () => {
73 | childProcess.exec('taskkill /IM putty.exe /t /f');
74 |
75 | await sleep(3000);
76 |
77 | process.exit();
78 | }, 1000),
79 | 60000
80 | );
81 |
--------------------------------------------------------------------------------
/data/controls.json:
--------------------------------------------------------------------------------
1 | {
2 | "instrument": {
3 | "aRed": [192, 0, 112],
4 | "aOrange": [192, 1, 112],
5 | "aYellow": [192, 2, 112],
6 | "aGreen": [192, 3, 112],
7 |
8 | "bRed": [192, 4, 112],
9 | "bOrange": [192, 5, 112],
10 | "bYellow": [192, 6, 112],
11 | "bGreen": [192, 7, 112],
12 |
13 | "cRed": [192, 8, 112],
14 | "cOrange": [192, 9, 112],
15 | "cYellow": [192, 10, 112],
16 | "cGreen": [192, 11, 112],
17 |
18 | "dRed": [192, 12, 112],
19 | "dOrange": [192, 13, 112],
20 | "dYellow": [192, 14, 112],
21 | "dGreen": [192, 15, 112]
22 | },
23 |
24 | "inst": {
25 | "rmod": [176, 10, 0],
26 | "vib": [176, 10, 1],
27 | "slice": [176, 10, 2],
28 | "uvb": [176, 10, 3],
29 | "slp": [176, 10, 4],
30 | "syn": [176, 10, 5],
31 | "tsc": [176, 10, 6],
32 | "bst": [176, 10, 7],
33 | "acs": [176, 10, 8],
34 | "bss": [176, 10, 9],
35 | "bypass": [176, 10, 10],
36 | "cmp": [176, 10, 11],
37 | "chrs": [176, 10, 12],
38 | "trm": [176, 10, 13],
39 | "roto": [176, 10, 14],
40 | "phs": [176, 10, 15],
41 | "flg": [176, 10, 16],
42 | "rvs": [176, 10, 17],
43 | "mog": [176, 10, 18],
44 | "oct": [176, 10, 19],
45 | "pshf": [176, 10, 20],
46 | "env": [176, 10, 21],
47 | "wah": [176, 10, 22]
48 | },
49 |
50 | "stomp": {
51 | "delayFeedback": [176, 21, 0],
52 | "delayLevel": [176, 23, 0],
53 | "reverbLevel": [176, 31, 0]
54 | },
55 |
56 | "tuner": {
57 | "on": [144, 21, 127],
58 | "off": [144, 21, 0]
59 | },
60 |
61 | "tapTempo": {
62 | "on": [176, 56, 127],
63 | "off": [176, 56, 0]
64 | },
65 |
66 | "amps": {
67 | "butcher": [176, 8, 1],
68 | "classic": [176, 8, 2],
69 | "xxx": [176, 8, 3],
70 | "6534": [176, 8, 4],
71 | "6505": [176, 8, 5],
72 | "buddha": [176, 8, 6],
73 | "twn": [176, 8, 7],
74 | "traceAcs": [176, 8, 8],
75 | "ecous": [176, 8, 9],
76 | "traceBss": [176, 8, 10],
77 | "peavey": [176, 8, 11],
78 | "british": [176, 8, 12]
79 | },
80 |
81 | "ampsChannels": {
82 | "red": [176, 12, 0],
83 | "orange": [176, 12, 1],
84 | "green": [176, 12, 2]
85 | },
86 |
87 | "modifiers": {
88 | "pre-gain": [176, 16, 0],
89 | "low": [176, 17, 0],
90 | "mid": [176, 18, 0],
91 | "high": [176, 19, 0],
92 | "post-gain": [176, 20, 0]
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/lib/commands_old/suggest.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict';
3 |
4 | const utils = require('../utils/utils.js');
5 | const kb = require('../commandManager_old.js').kb;
6 |
7 | module.exports = {
8 | name: 'kb suggest',
9 | invocation: async (channel, user, message) => {
10 | try {
11 | const msg = utils.getParam(message);
12 |
13 | if (!msg[0]) {
14 | return `${user.username}, you have to provide a message along with this command.`;
15 | }
16 |
17 | if (msg[0] === 'YOUR_CHANNEL_NAME') {
18 | return `${user.username}, it's a template text, type in your channel name instead you danker FeelsDankMan`;
19 | }
20 |
21 | const repeats = await kb.query(
22 | `
23 | SELECT *
24 | FROM suggestions
25 | WHERE status=? AND LOWER(message)=LOWER(?)`,
26 | ['new', msg[0].toLowerCase()]
27 | );
28 |
29 | if (repeats.length) {
30 | const findUser = await utils.Get.user().byUsername(msg[0].toLowerCase());
31 |
32 | if (findUser.length) {
33 | return `${user.username}, this channel has already been requested, please wait until it gets processed.`;
34 | }
35 | }
36 |
37 | await kb.query(
38 | `
39 | INSERT INTO suggestions (username, message, created)
40 | VALUES (?, ?, CURRENT_TIMESTAMP)`,
41 | [user.username, msg.join(' ')]
42 | );
43 |
44 | const suggestionID = (
45 | await kb.query(
46 | `
47 | SELECT ID
48 | FROM suggestions
49 | WHERE username=?
50 | ORDER BY created
51 | DESC`,
52 | [user.username]
53 | )
54 | )[0].ID;
55 |
56 | if (msg[0] === user.username) {
57 | return `${user.username}, (ref ID #${suggestionID}) Thank you for requesting the bot for your channel. Currently channel requests can take a lot of time to process since my server is currently running out of storage space, thank you for patience.`;
58 | }
59 | return `${user.username}, (ref ID #${suggestionID}) Thank you for your suggestion, it will be processed as soon as possible. State of progress will be whispered to you.`;
60 | } catch (err) {
61 | utils.errorLog(err);
62 | return `${user.username}, ${err} FeelsDankMan !!!`;
63 | }
64 | },
65 | };
66 |
--------------------------------------------------------------------------------
/lib/commands_old/vypyr.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict';
3 |
4 | const utils = require('../utils/utils.js');
5 | const controls = require('../../data/controls.json');
6 |
7 | module.exports = {
8 | name: 'kb vypyr',
9 | invocation: async (channel, user, message) => {
10 | try {
11 | if ((await utils.checkPermissions(user['username'])) < 5) {
12 | return '';
13 | }
14 |
15 | const msg = utils.getParam(message);
16 |
17 | if (msg[0] === 'save' && msg[1] && msg[2] && msg[3] && msg[4]) {
18 | await utils.query(
19 | `
20 | INSERT INTO vypyr_presets (preset, program, ctrlr, value)
21 | VALUES (?, ?, ?)`,
22 | [msg[1], msg[2], msg[3], msg[4]]
23 | );
24 |
25 | return '';
26 | }
27 |
28 | if (msg[0] === 'use' && msg[1]) {
29 | const preset = await utils.query(
30 | `
31 | SELECT *
32 | FROM vypyr_presets
33 | WHERE preset=?`,
34 | [msg[1]]
35 | );
36 |
37 | await utils.query(
38 | `
39 | INSERT INTO vypyr_execs (program, ctrlr, value)
40 | VALUES (?, ?, ?)`,
41 | [preset[0].program, preset[0].ctrlr, preset[0].value]
42 | );
43 |
44 | return '';
45 | }
46 |
47 | if (msg[0] === 'select' && msg[1]) {
48 | const values = Object.assign([], ...Object.values(controls));
49 |
50 | if (!values.length) {
51 | return `${user['username']}, predefined preset not found`;
52 | }
53 |
54 | await utils.query(
55 | `
56 | INSERT INTO vypyr_execs (program, ctrlr, value)
57 | VALUES (?, ?, ?)`,
58 | [values[msg[1]][0], values[msg[1]][1], values[msg[1]][2]]
59 | );
60 |
61 | return '';
62 | }
63 |
64 | if (!isNaN(msg[0]) && !isNaN(msg[1]) && !isNaN(msg[2])) {
65 | await utils.query(
66 | `
67 | INSERT INTO vypyr_execs (program, ctrlr, value)
68 | VALUES (?, ?, ?)`,
69 | [msg[0], msg[1], msg[2]]
70 | );
71 |
72 | return '';
73 | }
74 | } catch (err) {
75 | utils.errorLog(err);
76 | console.log(err);
77 | return `${user['username']}, error FeelsDankMan !!!`;
78 | }
79 | },
80 | };
81 |
--------------------------------------------------------------------------------
/api/src/site/pageSpotifyResolved.js:
--------------------------------------------------------------------------------
1 | const creds = require('../../../lib/credentials/config');
2 | const got = require('got');
3 | const fs = require('fs');
4 | const _ = require('lodash');
5 | const pageSpotifyResolved = services => {
6 | const { app, Commons } = services;
7 |
8 | const kb = Commons.ServiceConnector.Connector;
9 |
10 | app.get('/resolved', async (req, res) => {
11 | if (!_.get(req, 'query.code')) {
12 | res.status(400).send('Error - no verifcode/user provided');
13 | return;
14 | }
15 |
16 | const verifCode = Commons.UtilityRepository().stringGenerator();
17 |
18 | try {
19 | await (async () => {
20 | const api = `https://accounts.spotify.com/api/token?grant_type=authorization_code&client_id=0a53ae5438f24d0da272a2e663c615c3&client_secret=${creds.client_secret_spotify}&code=${req.query.code}&redirect_uri=https://kunszg.com/resolved`;
21 | const spotifyToken = await got(api, {
22 | method: 'POST',
23 | headers: {
24 | 'Content-Type': 'application/x-www-form-urlencoded'
25 | }
26 | }).json();
27 |
28 | const profile = await got(`https://api.spotify.com/v1/me`, {
29 | method: 'GET',
30 | headers: {
31 | Authorization: `Bearer ${spotifyToken.access_token}`,
32 | 'Content-Type': 'application/x-www-form-urlencoded'
33 | }
34 | }).json();
35 |
36 | await kb.sqlClient.query(
37 | `
38 | INSERT INTO access_token (access_token, refresh_token, premium, code, platform)
39 | VALUES (?, ?, ?, ?, "spotify")`,
40 | [
41 | spotifyToken.access_token,
42 | spotifyToken.refresh_token,
43 | profile.product === 'open' ? 'N' : 'Y',
44 | verifCode
45 | ]
46 | );
47 | })();
48 | } catch (err) {
49 | if (err.message === 'Response code 400 (Bad Request)') {
50 | res.send('Your code has expired, repeat the process.');
51 | }
52 | }
53 |
54 | const html = _.toString(
55 | fs.readFileSync('../../kbot-website/html/express_pages/spotifyResolve.html')
56 | );
57 |
58 | const page = Commons.UtilityRepository().complementHtmlPageTemplates(html, [
59 | {
60 | code: verifCode
61 | }
62 | ]);
63 |
64 | res.send(page);
65 | });
66 | };
67 |
68 | module.exports = pageSpotifyResolved;
69 |
--------------------------------------------------------------------------------
/lib/commands_old/help.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict';
3 |
4 | const utils = require('../utils/utils.js');
5 | const shell = require('child_process');
6 |
7 | module.exports = {
8 | name: 'kb help',
9 | invocation: async (channel, user, message, platform) => {
10 | try {
11 | const msg = utils.getParam(message);
12 |
13 | const command = await utils.query(
14 | `
15 | SELECT *
16 | FROM commands
17 | WHERE command=?`,
18 | [msg[0]]
19 | );
20 |
21 | const npmVersion = shell.execSync('npm -v');
22 |
23 | // default response
24 | if (!msg[0]) {
25 | return `${user['username']}, Node JS ${process.version} (npm ${npmVersion
26 | .toString()
27 | .trim()}), running on
28 | Ubuntu 20.04 ${process.platform} ${
29 | process.arch
30 | }. For commands list visit https://kunszg.com/commands`;
31 | }
32 |
33 | if (!command.length) {
34 | return `${user['username']}, this command does not exist.`;
35 | }
36 |
37 | if (command[0].description === null) {
38 | return `${user['username']}, this command does not have a description.`;
39 | }
40 |
41 | let cooldown = command[0].cooldown;
42 |
43 | const sub = val => {
44 | return cooldown - cooldown * val;
45 | };
46 |
47 | const perms = await utils.checkPermissions(user['username']);
48 |
49 | switch (Number(perms)) {
50 | case 1:
51 | cooldown = sub(0.3); // reduce cooldown by 30% for permission 1
52 | break;
53 |
54 | case 2:
55 | cooldown = sub(0.5);
56 | break;
57 |
58 | case 3:
59 | cooldown = sub(0.65);
60 | break;
61 |
62 | case 4:
63 | cooldown = sub(0.75);
64 | break;
65 |
66 | case 5:
67 | cooldown = sub(0.9);
68 | break;
69 | }
70 |
71 | if (platform === 'discord') {
72 | cooldown = cooldown[0].cooldown;
73 | }
74 |
75 | if (user['username'] === 'kunszg') {
76 | cooldown = 0;
77 | }
78 |
79 | return `${user['username']}, ${command[0].description} -- cooldown ${(
80 | cooldown / 1000
81 | ).toFixed(2)}sec`;
82 | } catch (err) {
83 | utils.errorLog(err);
84 | return `${user['username']}, ${err} FeelsDankMan !!!`;
85 | }
86 | },
87 | };
88 |
--------------------------------------------------------------------------------
/api/utils/prepareEmotesRow.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash');
2 | const modifyOutput = require('./modifyOutput');
3 | const formatEmotesDate = require('./formatEmotesDate');
4 |
5 | const prepareEmotesRow = (emotes, emoteState) => {
6 | const tableData = [];
7 |
8 | _.forEach(emotes, (emote, id) => {
9 | const {
10 | emote: emoteText,
11 | type: emoteType,
12 | url: emoteUrl,
13 | date: emoteDate,
14 | emoteId,
15 | sevenTvId,
16 | } = emote;
17 |
18 | const emoteName = modifyOutput(emoteText, 20);
19 |
20 | let emoteCDN = '#';
21 |
22 | if (_.get(emote, 'url')) {
23 | if (emoteType === 'bttv') {
24 | emoteCDN = emoteUrl
25 | .replace('https://cdn.betterttv.net/emote/', 'https://betterttv.com/emotes/')
26 | .replace('/1x', '');
27 | }
28 | if (emoteType === 'ffz') {
29 | emoteCDN = `https://www.frankerfacez.com/emoticon/${emoteId}-${emoteText}`;
30 | }
31 | if (emoteType === '7tv') {
32 | emoteCDN = `https://7tv.app/emotes/${sevenTvId}`;
33 | }
34 | }
35 |
36 | const inline = `class="table-contents" style="text-align: center;"`;
37 | const emoteClickableName = `${emoteName}`;
38 | const emoteClickablePicture = `
39 |
40 |
41 | `;
42 |
43 | if (emoteState === 'removed') {
44 | tableData.push({
45 | ID: `${id + 1}
`,
46 | name: `${emoteClickableName}
`,
47 | emote: `${emoteClickablePicture}
`,
48 | type: `${emoteType}
`,
49 | removed: `${formatEmotesDate(emoteDate)}
`,
50 | });
51 | } else {
52 | tableData.push({
53 | ID: `${id + 1}
`,
54 | name: `${emoteClickableName}
`,
55 | emote: `${emoteClickablePicture}
`,
56 | type: `${emoteType}
`,
57 | added: `${formatEmotesDate(emoteDate)}
`,
58 | });
59 | }
60 | });
61 |
62 | return tableData;
63 | };
64 |
65 | module.exports = prepareEmotesRow;
66 |
--------------------------------------------------------------------------------
/lib/commands_old/id.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict';
3 |
4 | const utils = require('../utils/utils.js');
5 |
6 | module.exports = {
7 | name: 'kb id',
8 | invocation: async (channel, user, message) => {
9 | try {
10 | const msg = utils.getParam(message);
11 |
12 | if (!msg[0]) {
13 | const getSenderData = await utils.Get.user(user);
14 |
15 | if (!getSenderData.length) {
16 | return `${user['username']}, you are not being logged in my database.`;
17 | }
18 |
19 | const dateDiff = Math.abs(new Date() - new Date(getSenderData[0].added));
20 | const dateToSec = (dateDiff / 1000).toFixed(0);
21 |
22 | return (
23 | `${user['username']}, Your ID in my user list is ${getSenderData[0].ID} and your Twitch ID is ${user['user-id']} ` +
24 | `you were first seen by the bot ${utils.humanizeDuration(
25 | dateToSec
26 | )} ago in channel ` +
27 | `${
28 | getSenderData[0].firstSeen === 'haxk'
29 | ? '[EXPUNGED]'
30 | : getSenderData[0].firstSeen.replace(/^(.{2})/, '$1\u{E0000}')
31 | }.`
32 | );
33 | }
34 |
35 | this.msg = msg[0].toLowerCase().replace(/@|,/g, '');
36 |
37 | const checkIfOptedOut = await utils.Get.user().optout('id', this.msg, 'username');
38 |
39 | if (checkIfOptedOut.length && user['username'] != this.msg) {
40 | return `${user['username']}, that user has opted out from being a target of this command.`;
41 | }
42 |
43 | const getUserData = await utils.Get.user().byUsername(this.msg);
44 |
45 | if (!getUserData.length) {
46 | return `${user['username']}, that user does not exist in my database.`;
47 | }
48 |
49 | const dateDiff = Math.abs(new Date() - new Date(getUserData[0].added));
50 | const dateToSec = (dateDiff / 1000).toFixed(0);
51 |
52 | return (
53 | `${user['username']}, ${getUserData[0].username.replace(
54 | /^(.{2})/,
55 | '$1\u{E0000}'
56 | )}'s ` +
57 | `ID in my user list is ${getUserData[0].ID} and their Twitch ID is ${getUserData[0].userId} ` +
58 | `they were first seen by the bot ${utils.humanizeDuration(dateToSec)} ago ` +
59 | `in channel ${
60 | getUserData[0].firstSeen === 'haxk'
61 | ? '[EXPUNGED]'
62 | : getUserData[0].firstSeen.replace(/^(.{2})/, '$1\u{E0000}')
63 | }.`
64 | );
65 | } catch (err) {
66 | utils.errorLog(err);
67 | return `${user['username']}, ${err} FeelsDankMan !!!`;
68 | }
69 | },
70 | };
71 |
--------------------------------------------------------------------------------
/api/src/site/pageCountdown.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const _ = require('lodash');
3 |
4 | const pageCountdown = services => {
5 | const { app, Commons } = services;
6 |
7 | const kb = Commons.ServiceConnector.Connector;
8 |
9 | app.get('/countdown', async (req, res) => {
10 | try {
11 | if (!_.get(req, 'query.verifcode')) {
12 | const verifCode = utils.genString();
13 |
14 | const html = _.toString(
15 | fs.readFileSync('../../kbot-website/html/express_pages/countdownAlternative.html')
16 | );
17 |
18 | const page = Commons.UtilityRepository().complementHtmlPageTemplates(html, [
19 | {
20 | verifCode
21 | }
22 | ]);
23 |
24 | await kb.sqlClient.query(
25 | `
26 | INSERT INTO countdown (verifcode, date)
27 | VALUES (?, CURRENT_TIMESTAMP)`,
28 | [verifCode]
29 | );
30 |
31 | res.send(page);
32 | return;
33 | }
34 |
35 | if (!_.get(req, 'query.seconds')) {
36 | req.query.seconds = 120;
37 | }
38 |
39 | const checkIfUpdated = await kb.sqlClient.query(
40 | `
41 | SELECT *
42 | FROM countdown
43 | WHERE verifcode=?`,
44 | [req.query.verifcode]
45 | );
46 |
47 | if (!checkIfUpdated.length) {
48 | res.send(
49 | 'Combination not found, refresh the previous page and try again'
50 | );
51 | return;
52 | }
53 |
54 | if (_.get(checkIfUpdated, '0.seconds') === null) {
55 | await kb.sqlClient.query(
56 | `
57 | UPDATE countdown SET seconds=?
58 | WHERE verifcode=?`,
59 | [Date.now() / 1000 + Number(req.query.seconds), req.query.verifcode]
60 | );
61 | }
62 |
63 | const seconds = await kb.sqlClient.query(
64 | `
65 | SELECT *
66 | FROM countdown
67 | WHERE verifcode=?`,
68 | [req.query.verifcode]
69 | );
70 |
71 | let html = _.toString(
72 | fs.readFileSync('../../kbot-website/html/express_pages/countdown.html')
73 | );
74 |
75 | const page = new utils.Swapper(html, [
76 | {
77 | seconds: seconds[0].seconds,
78 | code: req.query.verifcode,
79 | secValue: req.query.seconds,
80 | stringLength:
81 | `https://kunszg.com/countdown?seconds=${req.query.seconds}&verifCode=${req.query.verifcode}`
82 | .length + 8
83 | }
84 | ]);
85 |
86 | res.send(page);
87 | } catch (err) {
88 | console.log(err);
89 | }
90 | });
91 | };
92 |
93 | module.exports = pageCountdown;
94 |
--------------------------------------------------------------------------------
/kbot-website/src/app/api/emotes/route.ts:
--------------------------------------------------------------------------------
1 | import { NextRequest, NextResponse } from 'next/server';
2 | import getServiceConnector from '@/lib/serviceConnectorBridge';
3 |
4 | interface Emote {
5 | ID: number;
6 | userId: string;
7 | channel: string;
8 | emote: string;
9 | url: string;
10 | type: 'bttv' | 'ffz' | '7tv';
11 | emoteId: number;
12 | sevenTvId: string;
13 | date: string;
14 | }
15 |
16 | export async function GET(request: NextRequest) {
17 | try {
18 | const searchParams = request.nextUrl.searchParams;
19 | const search = searchParams.get('search')?.toLowerCase();
20 |
21 | if (!search) {
22 | return NextResponse.json({ error: 'Search parameter is required' }, { status: 400 });
23 | }
24 |
25 | const { sqlClient } = await getServiceConnector();
26 |
27 | const emotesAdded = await sqlClient.query(
28 | `
29 | SELECT ID, userId, channel, emote, url, type, emoteId, sevenTvId, date
30 | FROM kbot.emotes
31 | WHERE channel = ?
32 | ORDER BY date DESC
33 | `,
34 | [search]
35 | );
36 |
37 | const emotesRemoved = await sqlClient.query(
38 | `
39 | SELECT ID, userId, channel, emote, url, type, emoteId, sevenTvId, date
40 | FROM kbot.emotes_removed
41 | WHERE channel = ?
42 | ORDER BY date DESC
43 | `,
44 | [search]
45 | );
46 |
47 | const updateResult = await sqlClient.query<{ emotesUpdate: string }[]>(
48 | `SELECT emotesUpdate FROM kbot.channels_logger WHERE channel = ?`,
49 | [search]
50 | );
51 |
52 | if (!emotesAdded || emotesAdded.length === 0) {
53 | return NextResponse.json({
54 | emotesAdded: [],
55 | emotesRemoved: [],
56 | stats: { bttv: 0, ffz: 0, '7tv': 0, total: 0 },
57 | lastUpdate: null
58 | });
59 | }
60 |
61 | const stats = {
62 | bttv: emotesAdded.filter(e => e.type === 'bttv').length,
63 | ffz: emotesAdded.filter(e => e.type === 'ffz').length,
64 | '7tv': emotesAdded.filter(e => e.type === '7tv').length,
65 | total: emotesAdded.length
66 | };
67 |
68 | const lastUpdate =
69 | updateResult && updateResult.length > 0
70 | ? new Date(updateResult[0].emotesUpdate).toLocaleString()
71 | : null;
72 |
73 | return NextResponse.json({
74 | emotesAdded: Array.isArray(emotesAdded) ? emotesAdded : [],
75 | emotesRemoved: Array.isArray(emotesRemoved) ? emotesRemoved : [],
76 | stats,
77 | lastUpdate
78 | });
79 | } catch (error) {
80 | console.error('Error fetching emotes:', error);
81 | return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/api/utils/gitWebhookMiddleware.js:
--------------------------------------------------------------------------------
1 | const EventEmitter = require('events').EventEmitter;
2 | const crypto = require('crypto');
3 | const bufferEq = require('buffer-equal-constant-time');
4 |
5 | function signData(secret, data) {
6 | return 'sha256=' + crypto.createHmac('sha256', secret).update(data).digest('hex');
7 | }
8 |
9 | function verifySignature(secret, data, signature, signData) {
10 | return bufferEq(new Buffer(signature), new Buffer(signData(secret, data)));
11 | }
12 |
13 | const GithubWebhook = function (options) {
14 | options.secret = options.secret || '';
15 | options.deliveryHeader = options.deliveryHeader || 'x-github-delivery';
16 | options.eventHeader = options.eventHeader || 'x-github-event';
17 | options.signatureHeader = options.signatureHeader || 'x-hub-signature-256';
18 | options.signData = options.signData || signData;
19 |
20 | // Make handler able to emit events
21 | Object.assign(githookHandler, EventEmitter.prototype);
22 | EventEmitter.call(githookHandler);
23 |
24 | return githookHandler;
25 |
26 | function githookHandler(req, res, next) {
27 | if (req.method !== 'POST' || req.url.split('?').shift() !== options.path) {
28 | return next();
29 | }
30 |
31 | function reportError(message) {
32 | // respond error to sender
33 | res.status(400).send({
34 | error: message,
35 | });
36 |
37 | // emit error
38 | githookHandler.emit('error', new Error(message), req, res);
39 | }
40 |
41 | // check header fields
42 | let id = req.headers[options.deliveryHeader];
43 | if (!id) {
44 | return reportError('Failed to verify signature');
45 | }
46 |
47 | let event = req.headers[options.eventHeader];
48 | if (!event) {
49 | return reportError('Failed to verify signature');
50 | }
51 |
52 | let sign = req.headers[options.signatureHeader] || '';
53 | if (options.secret && !sign) {
54 | return reportError('Failed to verify signature');
55 | }
56 |
57 | // verify signature (if any)
58 | if (
59 | options.secret &&
60 | !verifySignature(options.secret, JSON.stringify(req.body), sign, options.signData)
61 | ) {
62 | return reportError('Failed to verify signature');
63 | }
64 |
65 | // parse payload
66 | let payloadData = req.body;
67 | let headers = req.headers;
68 | const repo = payloadData.repository && payloadData.repository.name;
69 |
70 | // emit events
71 | githookHandler.emit('*', event, repo, payloadData, headers);
72 | githookHandler.emit(event, repo, payloadData, headers);
73 | if (repo) {
74 | githookHandler.emit(repo, event, payloadData, headers);
75 | }
76 |
77 | res.status(200).send('OK');
78 | }
79 | };
80 |
81 | module.exports = GithubWebhook;
82 |
--------------------------------------------------------------------------------
/api/src/endpoints/statsGet.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash');
2 | const got = require('got');
3 |
4 | const creds = require('../../../lib/credentials/config');
5 |
6 | const getModuleData = require('../../utils/getModuleData');
7 |
8 | const statsGet = services => {
9 | const { app, Commons } = services;
10 |
11 | const kb = Commons.ServiceConnector.Connector;
12 |
13 | app.get('/api/stats', async (req, res) => {
14 | const modules = (await kb.redisClient.get(`kb:global:stats`)) || [];
15 | const channels = (await kb.redisClient.get('kb:global:channel-list')) || [];
16 | const lines = (await kb.redisClient.get('kb:job-manager:estimatedRepoLines')) || 0;
17 | const uptimeData = (await kb.redisClient.get('kb:command-manager:botUptime')) || process.uptime();
18 |
19 | const executions = await kb.sqlClient.query(
20 | 'SELECT count FROM stats WHERE type="statsApi" AND sha="commandExecs"'
21 | );
22 |
23 | const usersLogged = await kb.sqlClient.query(
24 | 'SELECT count FROM stats WHERE type="statsApi" AND sha="totalUsers"'
25 | );
26 |
27 | const totalViewCount = _.reduce(
28 | _.map(
29 | _.filter(channels, i => _.toInteger(i.viewerCount) > 0),
30 | i => _.toInteger(i.viewerCount)
31 | ),
32 | (x, y) => {
33 | return x + y;
34 | },
35 | 0
36 | );
37 |
38 | const githubResponse = await got(
39 | {
40 | url: 'https://api.github.com/repos/kunszg/kbot/commits?per_page=1&page=1',
41 | headers: {
42 | Authorization: process.env.githubAppAccessToken || creds.githubAppAccessToken,
43 | },
44 | }
45 | ).json();
46 |
47 | const commits =
48 | _.toNumber(
49 | _.last(_.split(_.get(githubResponse, 'headers.link'), '&')).replace(/[^0-9]/g, '')
50 | ) || 0;
51 |
52 | const linesOfCode = _.toInteger(_.get(_.split(_.toString(lines), ' '), '1'));
53 | const _usersLogged = _.toInteger(_.get(_.first(usersLogged), 'count'));
54 | const commandExecutions = _.toInteger(_.get(_.first(executions), 'count'));
55 |
56 | res.send({
57 | modules: {
58 | remindersLastSeen: getModuleData('reminders', modules),
59 | loggerLastSeen: getModuleData('logger', modules),
60 | apiLastSeen: getModuleData('api', modules),
61 | botLastSeen: getModuleData('bot', modules),
62 | },
63 | bot: {
64 | isRestarting: false,
65 | codeUptime: Date.now() - Math.trunc(_.toInteger(uptimeData) * 1000),
66 | linesOfCode,
67 | usersLogged: _usersLogged,
68 | commandExecutions,
69 | },
70 | github: {
71 | commits,
72 | },
73 | twitch: {
74 | totalViewCount,
75 | },
76 | });
77 | });
78 | };
79 |
80 | module.exports = statsGet;
81 |
--------------------------------------------------------------------------------
/lib/commands_old/banphrase.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict';
3 |
4 | const utils = require('../utils/utils.js');
5 |
6 | module.exports = {
7 | name: 'kb banphrase',
8 | invocation: async (channel, user, message, platform) => {
9 | try {
10 | if ((await utils.checkPermissions(user['username'])) < 3) {
11 | return '';
12 | }
13 |
14 | if (platform === 'whisper') {
15 | return 'This usage is disabled on this platform';
16 | }
17 |
18 | // syntax check
19 | const msg = utils.getParam(message);
20 |
21 | if (!msg[0]) {
22 | return `${user['username']}, no parameter provided`;
23 | }
24 |
25 | // add banphrase
26 | if (msg[0] === '+') {
27 | this.msg = msg.join(' ').replace('+ ', '');
28 | // check for repeated inserts
29 | const checkRepeated = await utils.query(
30 | `
31 | SELECT *
32 | FROM internal_banphrases
33 | WHERE banphrase=?`,
34 | [this.msg]
35 | );
36 |
37 | if (checkRepeated.length != 0) {
38 | return `${user['username']}, this banphrase already exists.`;
39 | }
40 |
41 | await utils.query(
42 | `
43 | INSERT INTO internal_banphrases (banphrase, date)
44 | VALUES (?, CURRENT_TIMESTAMP)`,
45 | [this.msg]
46 | );
47 |
48 | const getID = await utils.query(
49 | `
50 | SELECT *
51 | FROM internal_banphrases
52 | WHERE banphrase=?`,
53 | [this.msg]
54 | );
55 |
56 | return `${user['username']}, successfully added a banphrase
57 | "${this.msg}" with ID ${getID[0].ID}.`;
58 | }
59 |
60 | // remove banphrase
61 | if (msg[0] === 'del' || msg[0] === '-') {
62 | // check if banphrase exists
63 | const checkRepeated = await utils.query(
64 | `
65 | SELECT *
66 | FROM internal_banphrases
67 | WHERE banphrase=?`,
68 | [msg[1]]
69 | );
70 |
71 | if (!checkRepeated.length) {
72 | return `${user['username']}, this banphrase doesn't exist.`;
73 | }
74 |
75 | await utils.query(
76 | `
77 | DELETE FROM internal_banphrases
78 | WHERE banphrase=?`,
79 | [msg[1]]
80 | );
81 |
82 | return `${user['username']}, successfully removed the banphrase.`;
83 | }
84 |
85 | return `${user['username']}, invalid parameter.`;
86 | } catch (err) {
87 | utils.errorLog(err);
88 | return `${user['username']}, ${err} FeelsDankMan !!!`;
89 | }
90 | },
91 | };
92 |
--------------------------------------------------------------------------------
/lib/commands_old/stream.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict';
3 |
4 | const utils = require('../utils/utils.js');
5 | const got = require('got');
6 | const creds = require('../credentials/config.js');
7 |
8 | module.exports = {
9 | name: 'kb dank',
10 | invocation: async (channel, user, message) => {
11 | try {
12 | if ((await utils.checkPermissions(user['username'])) < 2) {
13 | return '';
14 | }
15 |
16 | const msg =
17 | message.split(' ')[1].toLowerCase() === 'title' ||
18 | message.split(' ')[1].toLowerCase() === 'game'
19 | ? utils.getParam(message, 1)
20 | : utils.getParam(message);
21 |
22 | const getAccessToken = await utils.query(`SELECT * FROM access_token`);
23 |
24 | const owner = await utils.Get.user().owner();
25 |
26 | if (msg[0] === 'title') {
27 | await got(
28 | `https://api.twitch.tv/helix/channels?broadcaster_id=${
29 | owner[0].userId
30 | }&title=${encodeURI(msg.splice(1).join(' '))}`,
31 | {
32 | method: 'PATCH',
33 | headers: {
34 | Authorization: `Bearer ${getAccessToken[0].access_token}`,
35 | 'Client-ID': creds.client_id,
36 | 'Content-Type': 'application/x-www-form-urlencoded',
37 | },
38 | }
39 | );
40 |
41 | return `${user['username']}, done`;
42 | }
43 |
44 | if (msg[0] === 'game') {
45 | const gameId = await got(
46 | `https://api.twitch.tv/helix/games?name=${encodeURI(msg.splice(1).join(' '))}`,
47 | {
48 | method: 'GET',
49 | headers: {
50 | Authorization: `Bearer ${getAccessToken[0].access_token}`,
51 | 'Client-ID': creds.client_id,
52 | 'Content-Type': 'application/x-www-form-urlencoded',
53 | },
54 | }
55 | ).json();
56 |
57 | if (!gameId.data.length) {
58 | return `${user['username']}, specified category was not found.`;
59 | }
60 |
61 | await got(
62 | `https://api.twitch.tv/helix/channels?broadcaster_id=${owner[0].userId}&game_id=${gameId.data[0].id}`,
63 | {
64 | method: 'PATCH',
65 | headers: {
66 | Authorization: `Bearer ${getAccessToken[0].access_token}`,
67 | 'Client-ID': creds.client_id,
68 | 'Content-Type': 'application/x-www-form-urlencoded',
69 | },
70 | }
71 | );
72 |
73 | return `${user['username']}, changed category to ${gameId.data[0].name}`;
74 | }
75 |
76 | return `${user['username']}, invalid type.`;
77 | } catch (err) {
78 | utils.errorLog(err);
79 | return `${user['username']}, ${err} FeelsDankMan !!!`;
80 | }
81 | },
82 | };
83 |
--------------------------------------------------------------------------------
/commons/Commons.js:
--------------------------------------------------------------------------------
1 | const UserRepository = require('./repositories/UserRepository');
2 | const ChannelRepository = require('./repositories/ChannelRepository');
3 | const UtilityRepository = require('./repositories/UtilityRepository');
4 | const ResponseRepository = require('./repositories/ResponseRepository');
5 |
6 | const CommonRepository = require('./repositories/CommonRepository');
7 |
8 | const ServiceConnector = require('./connector/serviceConnector');
9 |
10 | let instances = {};
11 |
12 | /**
13 | * Returns a singleton instance of the specified repository class.
14 | * If the instance does not exist, it creates a new one with the provided serviceConnector.
15 | * @param {UserRepository|{}} RepositoryClass - The repository class to instantiate.
16 | * @param {Object} serviceConnector - Client connection manager.
17 | * @returns The singleton instance of the specified repository.
18 | */
19 | function getRepositoryInstance(RepositoryClass, serviceConnector) {
20 | const className = RepositoryClass.name;
21 |
22 | if (!instances[className]) {
23 | instances[className] = RepositoryClass.getInstance(serviceConnector);
24 | }
25 |
26 | return instances[className];
27 | }
28 |
29 | /**
30 | * Gets the singleton instance of UserRepository.
31 | * @param {Object} [serviceConnector] - Client connection manager.
32 | * @returns The singleton instance of UserRepository.
33 | */
34 | function getUserRepositoryInstance(serviceConnector) {
35 | return getRepositoryInstance(UserRepository, serviceConnector);
36 | }
37 |
38 | /**
39 | * Gets the singleton instance of ChannelRepository.
40 | * @param {Object} [serviceConnector] - Client connection manager.
41 | * @returns The singleton instance of ChannelRepository.
42 | */
43 | function getChannelRepositoryInstance(serviceConnector) {
44 | return getRepositoryInstance(ChannelRepository, serviceConnector);
45 | }
46 |
47 | /**
48 | * Gets the singleton instance of UtilityRepository.
49 | * @param {Object} [serviceConnector] - Client connection manager.
50 | * @returns The singleton instance of UtilityRepository.
51 | */
52 | function getUtilityRepositoryInstance(serviceConnector) {
53 | return getRepositoryInstance(UtilityRepository, serviceConnector);
54 | }
55 |
56 | /**
57 | * Gets the singleton instance of ResponseRepository.
58 | * @param {Object} [serviceConnector] - Client connection manager.
59 | * @returns The singleton instance of ResponseRepository.
60 | */
61 | function getResponseRepositoryInstance(serviceConnector) {
62 | return getRepositoryInstance(ResponseRepository, serviceConnector);
63 | }
64 |
65 | module.exports = {
66 | UserRepository: getUserRepositoryInstance,
67 | ChannelRepository: getChannelRepositoryInstance,
68 | UtilityRepository: getUtilityRepositoryInstance,
69 | ResponseRepository: getResponseRepositoryInstance,
70 | CommonRepository,
71 | ServiceConnector
72 | };
73 |
--------------------------------------------------------------------------------