Compare commits

...

7 Commits

21 changed files with 99 additions and 59 deletions

2
.gitignore vendored
View File

@@ -139,4 +139,4 @@ dist
pb/data
# config files
auth.json
.env.*

View File

@@ -107,7 +107,7 @@ VARIABLE|DEFAULT|FUNCTION|REQUIRED
`COOLDOWN`|24 Hours|Cooldown between letting users get a lootbox with `!getloot` in seconds|:x:
`CLIENT_ID`|None|Set the CLIENT_ID to authenticate the bot|:bangbang:
`CLIENT_SECRET`|None|Set the CLIENT_SECRET to authenticate the bot|:bangbang:
`REDIRECT_URI`|`https://qweri0p.github.io/url-params/`|The REDIRECT_URI set in the twitch dev console|:bangbang:
`REDIRECT_URI`|`https://qwerinope.github.io/url-params/`|The REDIRECT_URI set in the twitch dev console|:bangbang:
`OAUTH_CODE`|None|Authorization code for OAuth|:bangbang:
`DIFFERENT_BROADCASTER`|`false`|Set this to true when `BOT_NAME` and `CHANNEL` are different.|:white_check_mark:
`BROADCASER_OAUTH_CODE`|None|OAuth authorization code for the broadcaster (ignored if `DIFFERENT_BROADCASTER` is false)|:bangbang:

View File

@@ -5,7 +5,7 @@ import { changeItemCount } from "../lib/items";
import { changeBalance } from "../lib/userHelper";
import { vulnerableUsers } from "../lib/timeoutHelper";
const give = createBotCommand('give', async (params, { say, broadcasterId, userId, userName }) => {
const give = createBotCommand('give', async (params, { say, broadcasterId, userId }) => {
if (userId !== broadcasterId) return
const target = await api.users.getUserByName(params[0].replace(/[@]/g, ''))
@@ -19,10 +19,10 @@ const give = createBotCommand('give', async (params, { say, broadcasterId, userI
else if (data.reason === 'noexist') { await say(`Can't find item ${params[1]}`); return }
const selection = items.find(item => item.name === params[1].toLowerCase())
await say(`${target.name} now has ${data.count} ${params[1]}${data.count === 1 ? '' : selection?.plural}`)
await say(`${target.displayName} now has ${data.count} ${params[1]}${data.count === 1 ? '' : selection?.plural}`)
})
const vulnChatters = createBotCommand('vulnchatters', async (_params, { say, userId, broadcasterId, userName }) => {
const vulnChatters = createBotCommand('vulnchatters', async (_params, { say, userId, broadcasterId }) => {
if (userId !== broadcasterId) return
await say(`There are ${vulnerableUsers.length} vulnerable chatters`)

View File

@@ -15,7 +15,7 @@ function getTimeDifference(date1: number, date2: number) {
export default createBotCommand('getloot', async (_params, { reply, userId }) => {
const user = await api.users.getUserById(userId)
const data = await lootboxReady(user)
const data = await lootboxReady(user!)
if (!data.result) {
const { days, hours, minutes, seconds } = getTimeDifference(data.lastlootbox, Date.now() - COOLDOWN)
await reply(`lootbox ready in:

View File

@@ -5,9 +5,10 @@ import getloot from "./getloot";
import modme from "./modme";
import use from "./use";
import iteminfo from "./iteminfo"
import leaderboard from "./leaderboard"
import stats from "./stats"
import aliases from "./itemAliases"
import admin from "./admin"
export default [timeout, inventory, qbucks, getloot, modme, use, iteminfo, ...aliases, ...admin, ...stats]
export default [timeout, inventory, qbucks, getloot, modme, use, iteminfo, leaderboard, ...aliases, ...admin, ...stats]

View File

@@ -4,7 +4,7 @@ import api from "../lib/api";
import items from "../items";
import { HelixUser } from "@twurple/api";
export default createBotCommand('inv', async (params, { userName, say }) => {
export default createBotCommand('inv', async (params, { userName, say, userDisplayName }) => {
let user: HelixUser | null
if (params.length !== 0) {
user = await api.users.getUserByName(params[0].replace(/[@]/g, ''))
@@ -24,10 +24,10 @@ export default createBotCommand('inv', async (params, { userName, say }) => {
messagedata.push(`${itemselection?.prettyname}${amount === 1 ? '' : itemselection?.plural}: ${amount}`)
}
if (messagedata.length === 0) { await say(`${data.me ? userName : params[0]} has no items!`); return }
if (messagedata.length === 0) { await say(`${data.me ? userDisplayName : params[0]} has no items!`); return }
await say(`
inventory of ${data.me ? userName : params[0]}:
inventory of ${data.me ? userDisplayName : user.displayName}:
${messagedata.join(', ')}
`)
}, { aliases: ['inventory'] })

View File

@@ -2,30 +2,36 @@ import { BotCommand, createBotCommand } from "@twurple/easy-bot";
import api from "../lib/api";
import items from "../items";
import { ITEMBUSY, toggleBusy } from "../lib/items";
const aliascommands: BotCommand[] = []
for (const item of items) {
aliascommands.push(createBotCommand(item.name, async (params, { say, broadcasterId, userId }) => {
aliascommands.push(createBotCommand(item.name, async (params, { say, reply, broadcasterId, userId }) => {
if (ITEMBUSY) { await reply(`There is currently an item in use. Try again.`); return }
const user = await api.users.getUserById(userId)
toggleBusy()
switch (item.name) {
case 'blaster':
case 'silverbullet':
case 'revive':
case 'superrevive':
if (params[0] === undefined) { await say('nice miss bro'); return }
if (params[0] === undefined) { await reply('Please specify a target'); return }
await item.execute(user!, say, broadcasterId, params[0].replace(/[@]/g, ''))
break
case 'grenade':
case 'tnt':
await item.execute(user!, say, broadcasterId)
break
case 'lootbox':
await item.execute(user!, say)
break
case 'clipboard':
if (params[0] === undefined) { await say("Please specify what the clipboard asks") }
if (params[0] === undefined) { await reply("Please specify what the clipboard asks") }
await item.execute(user!, say, broadcasterId, params.join(' '))
break
}
toggleBusy()
}, { aliases: item.aliases }))
}

View File

@@ -0,0 +1,22 @@
import { createBotCommand } from "@twurple/easy-bot"
import pb, { User } from "../lib/pocketbase"
import { getTimeouts } from "../lib/userHelper"
type KDData = { user: User, KD: number }
export default createBotCommand('leaderboard', async (_params, { say }) => {
const users = await pb.collection('users').getFullList()
let userKDs: KDData[] = []
for (const user of users) {
const data = await getTimeouts(user.id)
if (data.hit.blaster < 5) continue
const KD = data.shot.blaster / data.hit.blaster
const obj: KDData = { user, KD }
userKDs.push(obj)
}
if (userKDs.length === 0) { await say('No users on leaderboard yet!'); return }
userKDs.sort((data1, data2) => data2.KD - data1.KD)
const textlist: string[] = []
for (let i = 0; i < (userKDs.length < 5 ? userKDs.length : 5); i++) textlist.push(`${i + 1}. ${userKDs.at(i)!.user.firstname}: ${userKDs.at(i)!.KD.toFixed(2)}`)
await say(`${textlist.join(' | ')}`)
}, { aliases: ['kdleaderboard'] })

View File

@@ -14,6 +14,6 @@ export default createBotCommand('balance', async (params, { userName, say }) =>
}
const data = await getBalance(user)
await say(`${user.name} has ${data.balance} qbucks`)
await say(`${user.displayName} has ${data.balance} qbucks`)
}, { aliases: ['qbucks', 'qweribucks', 'bal'] })

View File

@@ -3,7 +3,7 @@ import api from "../lib/api";
import { getStats } from "../lib/userHelper";
import { HelixUser } from "@twurple/api";
const stats = createBotCommand('stats', async (params, { say, userName }) => {
const stats = createBotCommand('stats', async (params, { say, userName, userDisplayName }) => {
let user: HelixUser | null
if (params.length !== 0) {
user = await api.users.getUserByName(params[0].replace(/[@]/g, ''))
@@ -21,9 +21,9 @@ const stats = createBotCommand('stats', async (params, { say, userName }) => {
await say(
`
THIS MONTH: Stats of ${data.me ? userName : params[0]}:
THIS MONTH: Stats of ${data.me ? userDisplayName : user.displayName}:
Users blasted: ${data.stats.shot.blaster},
Blasted by others: ${data.stats.hit.blaster} (${isNaN(KD) ? 0 : KD.toFixed(3)} K/D).
Blasted by others: ${data.stats.hit.blaster} (${isNaN(KD) ? 0 : KD.toFixed(2)} K/D).
Grenades lobbed: ${data.stats.used.grenade}
TNTs lit: ${data.stats.used.tnt},
Silver bullets fired: ${data.stats.shot.silverbullet},
@@ -32,7 +32,7 @@ const stats = createBotCommand('stats', async (params, { say, userName }) => {
)
})
const alltime = createBotCommand('alltime', async (params, { say, userName }) => {
const alltime = createBotCommand('alltime', async (params, { say, userName, userDisplayName }) => {
let user: HelixUser | null
if (params.length !== 0) {
user = await api.users.getUserByName(params[0].replace(/[@]/g, ''))
@@ -48,9 +48,9 @@ const alltime = createBotCommand('alltime', async (params, { say, userName }) =>
await say(
`
ALLTIME: Stats of ${data.me ? userName : params[0]}:
ALLTIME: Stats of ${data.me ? userDisplayName : user.displayName}:
Users blasted: ${data.stats.shot.blaster},
Blasted by others: ${data.stats.hit.blaster} (${isNaN(KD) ? 0 : KD.toFixed(3)} K/D).
Blasted by others: ${data.stats.hit.blaster} (${isNaN(KD) ? 0 : KD.toFixed(2)} K/D).
Grenades lobbed: ${data.stats.used.grenade}
TNTs lit: ${data.stats.used.tnt},
Silver bullets fired: ${data.stats.shot.silverbullet},

View File

@@ -3,7 +3,7 @@ import { addTimeoutToDB, timeout } from "../lib/timeoutHelper";
import { changeBalance, getBalance } from "../lib/userHelper";
import api from "../lib/api";
export default createBotCommand('timeout', async (params, { say, broadcasterId, userName }) => {
export default createBotCommand('timeout', async (params, { say, broadcasterId, userName, userDisplayName }) => {
const attacker = await api.users.getUserByName(userName)
const userbal = await getBalance(attacker!)
if (userbal.balance < 100) { await say('not enough qbucks'); return }
@@ -11,7 +11,7 @@ export default createBotCommand('timeout', async (params, { say, broadcasterId,
const target = await api.users.getUserByName(params[0].replace(/[@]/g, ''))
const status = await timeout(broadcasterId, target!, 60, `You got blasted by ${userName}`)
if (status.status) {
await say(`${params[0]} got blasted by ${userName}! ${userName} now has ${userbal.balance - 100} qbucks remaining`)
await say(`${target?.displayName} got blasted by ${userDisplayName}! ${userDisplayName} now has ${userbal.balance - 100} qbucks remaining`)
await changeBalance(attacker!, -100)
await addTimeoutToDB(attacker!, target!, 'blaster')
}
@@ -21,7 +21,7 @@ export default createBotCommand('timeout', async (params, { say, broadcasterId,
await say(`${params[0]} doesn't exist!`)
break
case 'banned':
await say(`${params[0]} is already dead!`)
await say(`${target?.displayName} is already dead!`)
break
case 'unknown':
await say(`NO!`)

View File

@@ -1,21 +1,25 @@
import { createBotCommand } from "@twurple/easy-bot";
import api from "../lib/api";
import items from "../items";
import { ITEMBUSY, toggleBusy } from "../lib/items";
export default createBotCommand('use', async (params, { say, broadcasterId, userId }) => {
export default createBotCommand('use', async (params, { say, reply, broadcasterId, userId }) => {
const user = await api.users.getUserById(userId)
if (params[0] === undefined) return
const selection = items.find(item => item.aliases.includes(params[0].toLowerCase()))
if (!selection) { say(`${params[0]} does not exist!`); return }
if (!selection) { reply(`${params[0]} does not exist!`); return }
if (ITEMBUSY) { await reply(`There is currently an item in use. Try again.`); return }
toggleBusy()
switch (selection.name) {
case 'blaster':
case 'silverbullet':
case 'revive':
case 'superrevive':
if (params[1] === undefined) { await say('nice miss bro'); return }
if (params[1] === undefined) { await reply('Please specify a target'); return }
await selection.execute(user!, say, broadcasterId, params[1].replace(/[@]/g, ''))
break
case 'grenade':
@@ -26,8 +30,9 @@ export default createBotCommand('use', async (params, { say, broadcasterId, user
await selection.execute(user!, say)
break
case 'clipboard':
if (params[1] === undefined) { await say("Please specify what the clipboard asks")}
if (params[1] === undefined) { await reply("Please specify what the clipboard asks") }
await selection.execute(user!, say, broadcasterId, params.slice(1).join(' '))
break
}
toggleBusy()
})

View File

@@ -17,9 +17,9 @@ export const blaster = {
if (!itemResult.result && itemResult.reason === 'negative') { await say('You have no blasters!'); return }
const result = await timeout(broadcasterId, target, 60, `You got blasted by ${user.name}`)
const result = await timeout(broadcasterId, target, 60, `You got blasted by ${user.displayName}`)
if (result.status) {
await say(`${targetname} got blasted by ${user.name}! ${user.name} has ${itemResult.count} blaster${itemResult.count === 1 ? '' : 's'} remaining`)
await say(`${target?.displayName} got blasted by ${user.displayName}! ${user.displayName} has ${itemResult.count} blaster${itemResult.count === 1 ? '' : 's'} remaining`)
await addTimeoutToDB(user, target!, 'blaster')
await addUsedItem(user, 'blaster')
await updateInventory(user, itemResult.inv!)
@@ -29,7 +29,7 @@ export const blaster = {
await say(`${targetname} doesn't exist!`)
break
case 'banned':
await say(`${targetname} is already dead!`)
await say(`${target?.displayName} is already dead!`)
break
case 'unknown':
await say(`NO!`)
@@ -52,7 +52,7 @@ export const silverbullet = {
const itemResult = await changeItemCount(user, 'silverbullet')
if (!itemResult.result && itemResult.reason === 'negative') { await say('You have no silver bullets!'); return }
const result = await timeout(broadcasterId, target, 60 * 60 * 24, `You got hit by a silver bullet fired by ${user.name}`)
const result = await timeout(broadcasterId, target, 60 * 60 * 24, `You got hit by a silver bullet fired by ${user.displayName}`)
if (result.status) {
await say(`${target?.name} got deleted.`)
await addTimeoutToDB(user, target!, 'silverbullet')
@@ -64,7 +64,7 @@ export const silverbullet = {
await say(`${targetname} doesn't exist!`)
break
case 'banned':
await say(`${targetname} is already dead!`)
await say(`${target?.displayName} is already dead!`)
break
case 'unknown':
await say(`NO!`)

View File

@@ -23,6 +23,6 @@ export const clipboard = {
if (!itemResult.result && itemResult.reason === 'negative') { await say('You have no clipboards!'); return }
await tempapi.polls.createPoll(broadcasterId, { choices: ['Yes', 'No'], duration: 120, title: question })
await say(`${user.name} used a clipboard! They have ${itemResult.count} clipboard${itemResult.count === 1 ? '' : 's'} remaining`)
await say(`${user.displayName} used a clipboard! They have ${itemResult.count} clipboard${itemResult.count === 1 ? '' : 's'} remaining`)
}
}

View File

@@ -28,9 +28,9 @@ export const grenade = {
if (!itemResult.result && itemResult.reason === 'negative') { await say('You have no grenades!'); return }
const target = await api.users.getUserById(vulnerableUsers[Math.floor(Math.random() * vulnerableUsers.length)])
const result = await timeout(broadcasterId, target!, 60, `You got hit by ${user.name}'s grenade`)
const result = await timeout(broadcasterId, target!, 60, `You got hit by ${user.displayName}'s grenade`)
if (result.status) {
await say(`${target?.name} got blown up by ${user.name}'s grenade!`)
await say(`${target?.displayName} got blown up by ${user.displayName}'s grenade!`)
await addTimeoutToDB(user, target!, 'grenade')
await addUsedItem(user, 'grenade')
await updateInventory(user, itemResult.inv!)
@@ -59,9 +59,9 @@ export const tnt = {
const soontobedeadusers = shuffle(vulnerableUsers).slice(vulnerableUsers.length - blastedusers)
const targets = await api.users.getUsersByIds(soontobedeadusers)
for (const target of targets) {
const result = await timeout(broadcasterId, target!, 60, `You got hit by ${user.name}'s TNT`)
const result = await timeout(broadcasterId, target!, 60, `You got hit by ${user.displayName}'s TNT`)
if (result.status) {
await say(`${target?.name} got blown up by TNT!`)
await say(`${target?.displayName} got blown up by TNT!`)
await addTimeoutToDB(user, target!, 'tnt')
await updateInventory(user, itemResult.inv!)
} else {
@@ -71,6 +71,6 @@ export const tnt = {
}
await addUsedItem(user, 'tnt')
await say(`${user.name} blew up ${blastedusers} chatters with their TNT! ${user.name} has ${itemResult.count} tnt${itemResult.count === 1 ? '' : 's'} remaining`)
await say(`${user.displayName} blew up ${blastedusers} chatters with their TNT! ${user.displayName} has ${itemResult.count} tnt${itemResult.count === 1 ? '' : 's'} remaining`)
}
}

View File

@@ -27,6 +27,6 @@ export const lootbox = {
await updateInventory(user, inventory)
await addUsedItem(user, 'lootbox')
await say(`${user.name} got: ${newitems.join(' and ')}`)
await say(`${user.displayName} got: ${newitems.join(' and ')}`)
}
}

View File

@@ -23,16 +23,16 @@ export const revive = {
await say(`${targetname} does not exist`)
break
case 'notbanned':
await say(`${targetname} doesn't need revives`)
await say(`${target?.displayName} doesn't need revives`)
break
case 'unknown':
await say("Something went wrong!")
break
case 'healed':
await say(`${targetname} got healed for 30 seconds by ${user.name}`)
await say(`${target?.displayName} got healed for 30 seconds by ${user.displayName}`)
break
case 'revived':
await say(`${targetname} got revived by ${user.name}`)
await say(`${target?.displayName} got revived by ${user.displayName}`)
break
}
}
@@ -57,16 +57,16 @@ export const superrevive = {
await say(`${targetname} does not exist`)
break
case 'notbanned':
await say(`${targetname} doesn't need revives`)
await say(`${target?.displayName} doesn't need revives`)
break
case 'unknown':
await say("Something went wrong!")
break
case 'healed':
await say(`${targetname} got healed for 12 hours by ${user.name}`)
await say(`${target?.displayName} got healed for 12 hours by ${user.displayName}`)
break
case 'revived':
await say(`${targetname} got revived by ${user.name}`)
await say(`${target?.displayName} got revived by ${user.displayName}`)
break
}
}

View File

@@ -30,7 +30,7 @@ async function firstAccess(main = true) {
const CLIENT_SECRET = process.env.CLIENT_SECRET
const OAUTH_CODE = process.env.OAUTH_CODE
const BROADCASTER_OAUTH_CODE = process.env.BROADCASTER_OAUTH_CODE
const REDIRECT_URI = process.env.REDIRECT_URI ?? 'https://qweri0p.github.io/url-params/'
const REDIRECT_URI = process.env.REDIRECT_URI ?? 'https://qwerinope.github.io/url-params/'
if (!CLIENT_ID) { console.error("No 'CLIENT_ID' for OAuth defined in environment variables."); process.exit(1) }
if (!CLIENT_SECRET) { console.error("No 'CLIENT_SECRET' for OAuth defined in environment variables."); process.exit(1) }

View File

@@ -26,3 +26,9 @@ export async function changeItemCount(user: HelixUser, item: string, amount = -1
return { result: true, reason: '', count: inv[item], inv }
}
export let ITEMBUSY = false
export function toggleBusy() {
ITEMBUSY = !ITEMBUSY
}

View File

@@ -17,18 +17,18 @@ interface statusmessage {
/** Ban a specific user out by another user for specified amout of time, with specific reason
* If the user does not exist or is already banned return status: false
* If the user is a moderator, make sure they get their status back after timeout has elapsed */
export async function timeout(broadcasterid: string, target: HelixUser|null, duration: number, reason: string): Promise<statusmessage> {
export async function timeout(broadcasterid: string, target: HelixUser | null, duration: number, reason: string): Promise<statusmessage> {
if (!target) return { status: false, reason: 'noexist' }
const tmpapi = broadcasterApi ?? api
if (target.name === process.env.BOT_NAME) return { status: false, reason: 'unknown' }
if (await tmpapi.moderation.checkUserBan(broadcasterid, target)) return { status: false, reason: 'banned' }
if (await tmpapi.moderation.checkUserBan(broadcasterid, target.id)) return { status: false, reason: 'banned' }
try {
if (await tmpapi.moderation.checkUserMod(broadcasterid, target)) {
await tmpapi.moderation.removeModerator(broadcasterid, target)
if (await tmpapi.moderation.checkUserMod(broadcasterid, target.id)) {
await tmpapi.moderation.removeModerator(broadcasterid, target.id)
remodMod(broadcasterid, target, duration * 1000, tmpapi)
}
await tmpapi.moderation.banUser(broadcasterid, { duration, reason, user: target })
await tmpapi.moderation.banUser(broadcasterid, { duration, reason, user: target.id })
await DBValidation(target)
return { status: true, reason: '' }
} catch (err) {
@@ -38,7 +38,7 @@ export async function timeout(broadcasterid: string, target: HelixUser|null, dur
}
/** Revive a specific target for a certain amount of time */
export async function reviveTarget(broadcasterId: string, target: HelixUser|null, duration: number): Promise<statusmessage> {
export async function reviveTarget(broadcasterId: string, target: HelixUser | null, duration: number): Promise<statusmessage> {
if (!target) return { status: false, reason: 'noexist' }
const tmpapi = broadcasterApi ?? api
const bandata = await tmpapi.moderation.getBannedUsers(broadcasterId, { userId: target.id })
@@ -48,10 +48,10 @@ export async function reviveTarget(broadcasterId: string, target: HelixUser|null
if (newduration < 3) { // If the target is going to be unbanned in duration + 3 seconds, unban them anyway
await tmpapi.moderation.unbanUser(broadcasterId, target)
if (MODS.includes(target.name)) remodMod(broadcasterId, target, 0, tmpapi)
return {status: true, reason: 'revived'}
return { status: true, reason: 'revived' }
} else {
await tmpapi.moderation.banUser(broadcasterId, { duration: newduration, reason: bandata.data[0].reason!, user: target })
if (MODS.includes(target.name)) remodMod(broadcasterId, target, newduration * 1000 , tmpapi)
await tmpapi.moderation.banUser(broadcasterId, { duration: newduration, reason: bandata.data[0].reason!, user: target.id })
if (MODS.includes(target.name)) remodMod(broadcasterId, target, newduration * 1000, tmpapi)
return { status: true, reason: 'healed' }
}
} catch (err) {
@@ -78,12 +78,12 @@ function remodMod(broadcasterid: string, target: HelixUser, duration: number, ap
setTimeout(async () => {
const bandata = await api.moderation.getBannedUsers(broadcasterid, { userId: target.id })
if (bandata.data.length !== 0) {
const timeoutleft = Date.parse(bandata.data[0].expiryDate?.toString()!) - Date.now() + 3000 // date when timeout expires - current date + 3 seconds constant
const timeoutleft = Date.parse(bandata.data[0].expiryDate?.toString()!) - Date.now() // date when timeout expires - current date + 3 seconds constant
remodMod(broadcasterid, target, timeoutleft, api) // Call the current function with new time (recursion)
} else { // If user is still timed out it doesn't try to remod the target
try {
await api.moderation.addModerator(broadcasterid, target)
} catch (err) {} // This triggers when the timeout got shortened. try/catch so no runtime error
await api.moderation.addModerator(broadcasterid, target.id)
} catch (err) { } // This triggers when the timeout got shortened. try/catch so no runtime error
}
}, duration + 3000) // callback gets called after duration of timeout + 3 seconds
}

View File

@@ -54,7 +54,7 @@ const BLASTERS = ['blaster', 'grenade', 'tnt']
/** Get the amount of times the user has (been) shot (by) another user
* The 'blaster' data is all timeouts excluding silver bullets */
async function getTimeouts(userId: string, monthdata?: string): Promise<timeoutsGetResult> {
export async function getTimeouts(userId: string, monthdata?: string): Promise<timeoutsGetResult> {
let monthquery = ''
if (monthdata) monthquery = ` && created~"${monthdata}"`
const hit = await pb.collection('timeouts').getFullList({ filter: `target="${userId}"${monthquery}` })