mirror of
https://gitlab.com/qwerinope/qweribot.git
synced 2026-02-04 06:46:58 +01:00
added invulnerable chatters, completely reworked the way vulnerable chatters and admins is stored
This commit is contained in:
13
README.md
13
README.md
@@ -8,6 +8,16 @@ Admins are defined by the streamer and can use special administrative commands o
|
|||||||
Admins don't need to have moderator status in the channel.
|
Admins don't need to have moderator status in the channel.
|
||||||
The chatterbot and streamer always have admin status and cannot be stripped of admin powers.
|
The chatterbot and streamer always have admin status and cannot be stripped of admin powers.
|
||||||
Only the streamer and chatterbot have the power to add and remove admins.
|
Only the streamer and chatterbot have the power to add and remove admins.
|
||||||
|
Admins have the power to destroy the item economy. Be very careful with admin powers.
|
||||||
|
|
||||||
|
### Invulns
|
||||||
|
|
||||||
|
Invulns, or invulnerable chatters cannot be shot with items and cannot get hit by explosives.
|
||||||
|
They can however use items.
|
||||||
|
The intended use for invulns is for when you need to talk to a chatter, or for using other bots.
|
||||||
|
Invulns don't need moderator or vip status in the channel.
|
||||||
|
The chatterbot and streamer always are invuln and cannot be stripped of this status.
|
||||||
|
Only the streamer and chatterbot have the power to add and remove invulns.
|
||||||
|
|
||||||
### Commands
|
### Commands
|
||||||
|
|
||||||
@@ -86,9 +96,12 @@ COMMAND|FUNCTION|USER|ALIASES|DISABLEABLE
|
|||||||
`enablecommand {command/item}`|Re-enable a specific command/item|admins|`enablecommand`|:x:
|
`enablecommand {command/item}`|Re-enable a specific command/item|admins|`enablecommand`|:x:
|
||||||
`disablecheer {cheer}`|Disable a specific cheer event|admins|`disablecheer`|:x:
|
`disablecheer {cheer}`|Disable a specific cheer event|admins|`disablecheer`|:x:
|
||||||
`enablecheer {cheer}`|Re-enable a specific cheer event|admins|`enablecheer`|:x:
|
`enablecheer {cheer}`|Re-enable a specific cheer event|admins|`enablecheer`|:x:
|
||||||
|
`getinvulns`|Get a list of every invulnerable chatter in the channel|anyone|`getinvulns`|:x:
|
||||||
`getadmins`|Get a list of every admin in the channel|anyone|`getadmins`|:x:
|
`getadmins`|Get a list of every admin in the channel|anyone|`getadmins`|:x:
|
||||||
`itemlock {target}`|Toggle the itemlock on the specified target|admins|`itemlock`|:x:
|
`itemlock {target}`|Toggle the itemlock on the specified target|admins|`itemlock`|:x:
|
||||||
`testcheer {amount} [args]`|Create a fake cheering event|streamer/chatterbot|`testcheer`|:x:
|
`testcheer {amount} [args]`|Create a fake cheering event|streamer/chatterbot|`testcheer`|:x:
|
||||||
|
`addinvuln {target}`|Adds an invuln user|streamer/chatterbot|`addinvuln`|:x:
|
||||||
|
`removeinvuln {target}`|Removes an invuln user| streamer/chatterbot|`removeinvuln`|:x:
|
||||||
`addadmin {target}`|Adds an admin|streamer/chatterbot|`addadmin`|:x:
|
`addadmin {target}`|Adds an admin|streamer/chatterbot|`addadmin`|:x:
|
||||||
`removeadmin {target}`|Removes an admin|streamer/chatterbot|`removeadmin`|:x:
|
`removeadmin {target}`|Removes an admin|streamer/chatterbot|`removeadmin`|:x:
|
||||||
|
|
||||||
|
|||||||
27
flake.lock
generated
Normal file
27
flake.lock
generated
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1751984180,
|
||||||
|
"narHash": "sha256-LwWRsENAZJKUdD3SpLluwDmdXY9F45ZEgCb0X+xgOL0=",
|
||||||
|
"owner": "nixos",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "9807714d6944a957c2e036f84b0ff8caf9930bc0",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nixos",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
18
flake.nix
Normal file
18
flake.nix
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
inputs = { nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; };
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs }:
|
||||||
|
let
|
||||||
|
system = "x86_64-linux";
|
||||||
|
pkgs = import nixpkgs { inherit system; };
|
||||||
|
in {
|
||||||
|
devShells."${system}" = {
|
||||||
|
default = pkgs.mkShell {
|
||||||
|
packages = with pkgs; [ bun nodejs deno ];
|
||||||
|
shellHook = ''
|
||||||
|
echo Loaded the qweribot dev shell
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -9,6 +9,6 @@ export default new Command('addadmin', ['addadmin'], 'streamer', async msg => {
|
|||||||
const target = await User.initUsername(args[0].toLowerCase());
|
const target = await User.initUsername(args[0].toLowerCase());
|
||||||
if (!target) { await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId); return; };
|
if (!target) { await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId); return; };
|
||||||
const data = await addAdmin(target.id);
|
const data = await addAdmin(target.id);
|
||||||
if (data === 1) await sendMessage(`${target.displayName} is now an admin`, msg.messageId);
|
if (data === "OK") await sendMessage(`${target.displayName} is now an admin`, msg.messageId);
|
||||||
else await sendMessage(`${target.displayName} is already an admin`, msg.messageId);
|
else await sendMessage(`${target.displayName} is already an admin`, msg.messageId);
|
||||||
}, false);
|
}, false);
|
||||||
|
|||||||
14
src/commands/addinvuln.ts
Normal file
14
src/commands/addinvuln.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { Command, sendMessage } from ".";
|
||||||
|
import { addInvuln } from "../lib/invuln";
|
||||||
|
import parseCommandArgs from "../lib/parseCommandArgs";
|
||||||
|
import { User } from "../user";
|
||||||
|
|
||||||
|
export default new Command('addinvuln', ['addinvuln'], 'streamer', async msg => {
|
||||||
|
const args = parseCommandArgs(msg.messageText);
|
||||||
|
if (!args[0]) { await sendMessage('Please specify a target', msg.messageId); return; };
|
||||||
|
const target = await User.initUsername(args[0].toLowerCase());
|
||||||
|
if (!target) { await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId); return; };
|
||||||
|
const data = await addInvuln(target.id);
|
||||||
|
if (data === "OK") await sendMessage(`${target.displayName} is now an invuln`, msg.messageId);
|
||||||
|
else await sendMessage(`${target.displayName} is already an invuln`, msg.messageId);
|
||||||
|
}, false);
|
||||||
13
src/commands/getinvulns.ts
Normal file
13
src/commands/getinvulns.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { Command, sendMessage } from ".";
|
||||||
|
import { getInvulns } from "../lib/invuln";
|
||||||
|
import { User } from "../user";
|
||||||
|
|
||||||
|
export default new Command('getinvulns', ['getinvulns'], 'chatter', async msg => {
|
||||||
|
const invulns = await getInvulns()
|
||||||
|
const invulnnames: string[] = [];
|
||||||
|
for (const id of invulns) {
|
||||||
|
const invuln = await User.initUserId(id);
|
||||||
|
invulnnames.push(invuln?.displayName!);
|
||||||
|
};
|
||||||
|
await sendMessage(`Current invulnerable chatters: ${invulnnames.join(', ')}`, msg.messageId);
|
||||||
|
}, false);
|
||||||
16
src/commands/removeinvuln.ts
Normal file
16
src/commands/removeinvuln.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { Command, sendMessage } from ".";
|
||||||
|
import { streamerUsers } from "..";
|
||||||
|
import { removeInvuln } from "../lib/invuln";
|
||||||
|
import parseCommandArgs from "../lib/parseCommandArgs";
|
||||||
|
import { User } from "../user";
|
||||||
|
|
||||||
|
export default new Command('removeinvuln', ['removeinvuln'], 'streamer', async msg => {
|
||||||
|
const args = parseCommandArgs(msg.messageText);
|
||||||
|
if (!args[0]) { await sendMessage('Please specify a target', msg.messageId); return; };
|
||||||
|
const target = await User.initUsername(args[0].toLowerCase());
|
||||||
|
if (!target) { await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId); return; };
|
||||||
|
if (streamerUsers.includes(target.id)) { await sendMessage(`Can't remove invulnerability from ${target.displayName} as they are managed by the bot program`, msg.messageId); return; };
|
||||||
|
const data = await removeInvuln(target.id);
|
||||||
|
if (data === 1) await sendMessage(`${target.displayName} is no longer invulnerable`, msg.messageId);
|
||||||
|
else await sendMessage(`${target.displayName} isn't invulnerable`, msg.messageId);
|
||||||
|
}, false);
|
||||||
@@ -2,7 +2,7 @@ import { redis } from "bun";
|
|||||||
import { Command, sendMessage } from ".";
|
import { Command, sendMessage } from ".";
|
||||||
|
|
||||||
export default new Command('vulnchatters', ['vulnchatters', 'vulnc'], 'chatter', async msg => {
|
export default new Command('vulnchatters', ['vulnchatters', 'vulnc'], 'chatter', async msg => {
|
||||||
const data = await redis.keys('vulnchatters:*');
|
const data = await redis.keys('user:*:vulnerable');
|
||||||
const one = data.length === 1;
|
const one = data.length === 1;
|
||||||
await sendMessage(`There ${one ? 'is' : 'are'} ${data.length} vulnerable chatter${one ? '' : 's'}`, msg.messageId);
|
await sendMessage(`There ${one ? 'is' : 'are'} ${data.length} vulnerable chatter${one ? '' : 's'}`, msg.messageId);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { isAdmin } from "../lib/admins";
|
|||||||
import cheers from "../cheers";
|
import cheers from "../cheers";
|
||||||
import logger from "../lib/logger";
|
import logger from "../lib/logger";
|
||||||
import { addMessageToChatWidget } from "../chatwidget/message";
|
import { addMessageToChatWidget } from "../chatwidget/message";
|
||||||
|
import { isInvuln } from "../lib/invuln";
|
||||||
|
|
||||||
logger.info(`Loaded the following commands: ${commands.keys().toArray().join(', ')}`);
|
logger.info(`Loaded the following commands: ${commands.keys().toArray().join(', ')}`);
|
||||||
|
|
||||||
@@ -14,8 +15,6 @@ eventSub.onChannelChatMessage(streamerId, streamerId, parseChatMessage);
|
|||||||
|
|
||||||
async function parseChatMessage(msg: EventSubChannelChatMessageEvent) {
|
async function parseChatMessage(msg: EventSubChannelChatMessageEvent) {
|
||||||
addMessageToChatWidget(msg);
|
addMessageToChatWidget(msg);
|
||||||
if (!singleUserMode && msg.chatterId === chatterId) return;
|
|
||||||
// return if double user mode is on and the chatter says something, we don't need them
|
|
||||||
|
|
||||||
const user = await User.initUsername(msg.chatterName);
|
const user = await User.initUsername(msg.chatterName);
|
||||||
|
|
||||||
@@ -27,7 +26,7 @@ async function parseChatMessage(msg: EventSubChannelChatMessageEvent) {
|
|||||||
// and both are usable to target the same user (id is the same)
|
// and both are usable to target the same user (id is the same)
|
||||||
// The only problem would be if a user changed their name and someone else took their name right after
|
// The only problem would be if a user changed their name and someone else took their name right after
|
||||||
|
|
||||||
if (!streamerUsers.includes(msg.chatterId)) user?.makeVulnerable(); // Make the user vulnerable to explosions if not streamerbot or chatterbot
|
if (!await isInvuln(user?.id!)) user?.setVulnerable(); // Make the user vulnerable to explosions if not marked as invuln
|
||||||
|
|
||||||
if (!msg.isCheer && !msg.isRedemption) await handleChatMessage(msg, user!)
|
if (!msg.isCheer && !msg.isRedemption) await handleChatMessage(msg, user!)
|
||||||
else if (msg.isCheer && !msg.isRedemption) await handleCheer(msg, msg.bits, user!);
|
else if (msg.isCheer && !msg.isRedemption) await handleCheer(msg, msg.bits, user!);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { ApiClient } from "@twurple/api";
|
|||||||
import { EventSubWsListener } from "@twurple/eventsub-ws";
|
import { EventSubWsListener } from "@twurple/eventsub-ws";
|
||||||
import { addAdmin } from "./lib/admins";
|
import { addAdmin } from "./lib/admins";
|
||||||
import logger from "./lib/logger";
|
import logger from "./lib/logger";
|
||||||
|
import { addInvuln } from "./lib/invuln";
|
||||||
|
|
||||||
const CHATTERINTENTS = ["user:read:chat", "user:write:chat", "user:bot"];
|
const CHATTERINTENTS = ["user:read:chat", "user:write:chat", "user:bot"];
|
||||||
const STREAMERINTENTS = ["user:read:chat", "moderation:read", "channel:manage:moderators", "moderator:manage:banned_users", "bits:read"];
|
const STREAMERINTENTS = ["user:read:chat", "moderation:read", "channel:manage:moderators", "moderator:manage:banned_users", "bits:read"];
|
||||||
@@ -28,7 +29,7 @@ export const eventSub = new EventSubWsListener({ apiClient: streamerApi });
|
|||||||
export const commandPrefix = process.env.COMMAND_PREFIX ?? "!";
|
export const commandPrefix = process.env.COMMAND_PREFIX ?? "!";
|
||||||
|
|
||||||
export const streamerUsers = [chatterId, streamerId];
|
export const streamerUsers = [chatterId, streamerId];
|
||||||
streamerUsers.forEach(async id => await addAdmin(id));
|
streamerUsers.forEach(async id => await Promise.all([addAdmin(id), addInvuln(id)]));
|
||||||
|
|
||||||
await import("./events");
|
await import("./events");
|
||||||
|
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ export default new Item(ITEMNAME, 'Grenade', 's',
|
|||||||
async (msg, user) => {
|
async (msg, user) => {
|
||||||
const userObj = await getUserRecord(user);
|
const userObj = await getUserRecord(user);
|
||||||
if (userObj.inventory[ITEMNAME]! < 1) { await sendMessage(`You don't have any grenades!`, msg.messageId); return; };
|
if (userObj.inventory[ITEMNAME]! < 1) { await sendMessage(`You don't have any grenades!`, msg.messageId); return; };
|
||||||
const targets = await redis.keys('vulnchatters:*');
|
const targets = await redis.keys(`user:*:vulnerable`);
|
||||||
if (targets.length === 0) { await sendMessage('No vulnerable chatters to blow up', msg.messageId); return; };
|
if (targets.length === 0) { await sendMessage('No vulnerable chatters to blow up', msg.messageId); return; };
|
||||||
const selection = targets[Math.floor(Math.random() * targets.length)]!;
|
const selection = targets[Math.floor(Math.random() * targets.length)]!;
|
||||||
const target = await User.initUserId(selection.split(':')[1]!);
|
const target = await User.initUserId(selection.slice(5, -11));
|
||||||
|
|
||||||
await getUserRecord(target!); // make sure the user record exist in the database
|
await getUserRecord(target!); // make sure the user record exist in the database
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export default new Item(ITEMNAME, 'TNT', 's',
|
|||||||
async (msg, user) => {
|
async (msg, user) => {
|
||||||
const userObj = await getUserRecord(user);
|
const userObj = await getUserRecord(user);
|
||||||
if (userObj.inventory[ITEMNAME]! < 1) { await sendMessage(`You don't have any TNTs!`, msg.messageId); return; };
|
if (userObj.inventory[ITEMNAME]! < 1) { await sendMessage(`You don't have any TNTs!`, msg.messageId); return; };
|
||||||
const vulntargets = await redis.keys('vulnchatters:*');
|
const vulntargets = await redis.keys('user:*:vulnerable').then(a => a.map(b => b.slice(5, -11)));
|
||||||
if (vulntargets.length === 0) { await sendMessage('No vulnerable chatters to blow up', msg.messageId); return; };
|
if (vulntargets.length === 0) { await sendMessage('No vulnerable chatters to blow up', msg.messageId); return; };
|
||||||
const targets = getTNTTargets(vulntargets);
|
const targets = getTNTTargets(vulntargets);
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ export default new Item(ITEMNAME, 'TNT', 's',
|
|||||||
await user.setLock();
|
await user.setLock();
|
||||||
|
|
||||||
await Promise.all(targets.map(async targetid => {
|
await Promise.all(targets.map(async targetid => {
|
||||||
const target = await User.initUserId(targetid.split(':')[1]!);
|
const target = await User.initUserId(targetid);
|
||||||
await getUserRecord(target!); // make sure the user record exist in the database
|
await getUserRecord(target!); // make sure the user record exist in the database
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
timeout(target!, `You got hit by ${user.displayName}'s TNT!`, 60),
|
timeout(target!, `You got hit by ${user.displayName}'s TNT!`, 60),
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import { redis } from "bun";
|
import { redis } from "bun";
|
||||||
|
|
||||||
export async function getAdmins() {
|
export async function getAdmins() {
|
||||||
return await redis.smembers('admins');
|
const data = await redis.keys('user:*:admin');
|
||||||
|
return data.map(a => a.slice(5, -6));
|
||||||
};
|
};
|
||||||
export async function isAdmin(userid: string) {
|
export async function isAdmin(userid: string) {
|
||||||
return await redis.sismember('admins', userid);
|
return await redis.exists(`user:${userid}:admin`);
|
||||||
};
|
};
|
||||||
export async function addAdmin(userid: string) {
|
export async function addAdmin(userid: string) {
|
||||||
return await redis.sadd('admins', userid);
|
return await redis.set(`user:${userid}:admin`, '1');
|
||||||
};
|
};
|
||||||
export async function removeAdmin(userid: string) {
|
export async function removeAdmin(userid: string) {
|
||||||
return await redis.srem('admins', userid);
|
return await redis.del(`user:${userid}:admin`);
|
||||||
};
|
};
|
||||||
|
|||||||
16
src/lib/invuln.ts
Normal file
16
src/lib/invuln.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { redis } from "bun";
|
||||||
|
|
||||||
|
export async function getInvulns() {
|
||||||
|
const data = await redis.keys('user:*:invulnerable');
|
||||||
|
return data.map(a => a.slice(5, -13));
|
||||||
|
};
|
||||||
|
export async function isInvuln(userid: string) {
|
||||||
|
return await redis.exists(`user:${userid}:invulnerable`);
|
||||||
|
};
|
||||||
|
export async function addInvuln(userid: string) {
|
||||||
|
await redis.del(`user:${userid}:vulnerable`);
|
||||||
|
return await redis.set(`user:${userid}:invulnerable`, '1');
|
||||||
|
};
|
||||||
|
export async function removeInvuln(userid: string) {
|
||||||
|
return await redis.del(`user:${userid}:invulnerable`);
|
||||||
|
};
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import { streamerApi, streamerId, streamerUsers } from "..";
|
import { streamerApi, streamerId } from "..";
|
||||||
import logger from "./logger";
|
import logger from "./logger";
|
||||||
import { User } from "../user";
|
import { User } from "../user";
|
||||||
|
import { isInvuln } from "./invuln";
|
||||||
|
|
||||||
type SuccessfulTimeout = { status: true };
|
type SuccessfulTimeout = { status: true; };
|
||||||
type UnSuccessfulTimeout = { status: false; reason: 'banned' | 'unknown' | 'illegal' };
|
type UnSuccessfulTimeout = { status: false; reason: 'banned' | 'unknown' | 'illegal'; };
|
||||||
type TimeoutResult = SuccessfulTimeout | UnSuccessfulTimeout;
|
type TimeoutResult = SuccessfulTimeout | UnSuccessfulTimeout;
|
||||||
|
|
||||||
/** Give a user a timeout/ban
|
/** Give a user a timeout/ban
|
||||||
@@ -11,7 +12,7 @@ type TimeoutResult = SuccessfulTimeout | UnSuccessfulTimeout;
|
|||||||
* @param reason - reason for timeout/ban
|
* @param reason - reason for timeout/ban
|
||||||
* @param duration - duration of timeout. don't specifiy for ban */
|
* @param duration - duration of timeout. don't specifiy for ban */
|
||||||
export const timeout = async (user: User, reason: string, duration?: number): Promise<TimeoutResult> => {
|
export const timeout = async (user: User, reason: string, duration?: number): Promise<TimeoutResult> => {
|
||||||
if (streamerUsers.includes(user.id)) return { status: false, reason: 'illegal' };
|
if (await isInvuln(user.id)) return { status: false, reason: 'illegal' }; // Don't timeout invulnerable chatters
|
||||||
|
|
||||||
// Check if user already has a timeout
|
// Check if user already has a timeout
|
||||||
const banStatus = await streamerApi.moderation.getBannedUsers(streamerId, { userId: user.id }).then(a => a.data);
|
const banStatus = await streamerApi.moderation.getBannedUsers(streamerId, { userId: user.id }).then(a => a.data);
|
||||||
|
|||||||
10
src/user.ts
10
src/user.ts
@@ -89,12 +89,12 @@ export class User {
|
|||||||
await redis.set(`user:${this.id}:itemlock`, '0');
|
await redis.set(`user:${this.id}:itemlock`, '0');
|
||||||
};
|
};
|
||||||
|
|
||||||
public async makeVulnerable(): Promise<void> {
|
public async setVulnerable(): Promise<void> {
|
||||||
await redis.set(`vulnchatters:${this.id}`, this.displayName);
|
await redis.set(`user:${this.id}:vulnerable`, '1');
|
||||||
await redis.expire(`vulnchatters:${this.id}`, Math.floor(EXPIRETIME / 2)); // Vulnerable chatter gets removed from the pool after 30 minutes
|
await redis.expire(`user:${this.id}:vulnerable`, Math.floor(EXPIRETIME / 2)); // Vulnerable chatter gets removed from the pool after 30 minutes
|
||||||
};
|
};
|
||||||
|
|
||||||
public async makeInvulnerable(): Promise<void> {
|
public async clearVulnerable(): Promise<void> {
|
||||||
await redis.del(`vulnchatters:${this.id}`);
|
await redis.del(`user:${this.id}:vulnerable`);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user