├── 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": "
ID
", 4 | "name": "
name
", 5 | "emote": "
emote
", 6 | "type": "
type
", 7 | "added": "
added
" 8 | }, 9 | "emoteHeadersRemoved": { 10 | "ID": "
ID
", 11 | "name": "
name
", 12 | "emote": "
emote
", 13 | "type": "
type
", 14 | "removed": "
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: `
ID
`, 24 | command: `
command
`, 25 | cooldown: `
cooldown
`, 26 | 'opt-out': `
opt-out
`, 27 | code: `
code
`, 28 | usage: `
usage
`, 29 | description: `
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 | Error displaying image 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: `
${commandDesc}
`, 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 | ${emoteName} 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 | --------------------------------------------------------------------------------