mirror of
https://github.com/veganhacktivists/arabot.git
synced 2025-10-30 14:07:40 +01:00
Merge pull request #68 from veganhacktivists/verification
feat(arabot): new verification system
This commit is contained in:
commit
57b17d6e28
@ -1,4 +1,4 @@
|
||||
FROM node:18-buster
|
||||
FROM node:18
|
||||
|
||||
WORKDIR /opt/app
|
||||
|
||||
@ -11,6 +11,8 @@ RUN npm install
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN npx prisma generate
|
||||
|
||||
RUN npm run build
|
||||
|
||||
RUN chown node:node /opt/app/
|
||||
|
||||
949
package-lock.json
generated
949
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
@ -26,31 +26,31 @@
|
||||
},
|
||||
"homepage": "https://github.com/veganhacktivists/arabot#readme",
|
||||
"dependencies": {
|
||||
"@discordjs/builders": "^1.2.0",
|
||||
"@discordjs/builders": "^1.3.0",
|
||||
"@prisma/client": "^4.0.0",
|
||||
"@sapphire/discord.js-utilities": "^5.0.0",
|
||||
"@sapphire/framework": "^3.1.1",
|
||||
"@sapphire/framework": "^3.1.3",
|
||||
"@sapphire/plugin-scheduled-tasks": "^4.0.0",
|
||||
"@sapphire/plugin-subcommands": "^3.0.0",
|
||||
"@sapphire/stopwatch": "^1.4.1",
|
||||
"@sapphire/ts-config": "^3.3.4",
|
||||
"@sapphire/utilities": "^3.9.2",
|
||||
"@types/node": "^18.0.3",
|
||||
"bullmq": "^1.89.1",
|
||||
"discord-api-types": "^0.33.3",
|
||||
"discord.js": "^13.10.3",
|
||||
"discord.js": "^13.12.0",
|
||||
"dotenv": "^16.0.1",
|
||||
"prisma": "^4.2.1",
|
||||
"ts-node": "^10.8.2",
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sapphire/ts-config": "^3.3.4",
|
||||
"@types/ioredis": "^4.28.10",
|
||||
"@typescript-eslint/eslint-plugin": "^5.30.7",
|
||||
"@typescript-eslint/parser": "^5.30.7",
|
||||
"eslint": "8.22.0",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-config-airbnb-typescript": "^17.0.0",
|
||||
"eslint-plugin-import": "^2.26.0"
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"prisma": "^4.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
14
prisma/migrations/20220806153942_/migration.sql
Normal file
14
prisma/migrations/20220806153942_/migration.sql
Normal file
@ -0,0 +1,14 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `balance` on the `User` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `lastDaily` on the `User` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `level` on the `User` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `xp` on the `User` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "User" DROP COLUMN "balance",
|
||||
DROP COLUMN "lastDaily",
|
||||
DROP COLUMN "level",
|
||||
DROP COLUMN "xp";
|
||||
@ -0,0 +1,8 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Added the required column `channelId` to the `VerifyUnblock` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Verify" ADD COLUMN "channelId" TEXT NOT NULL;
|
||||
@ -0,0 +1,10 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `time` on the `VerifyUnblock` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Verify" DROP COLUMN "time",
|
||||
ADD COLUMN "finishTime" TIMESTAMP(3),
|
||||
ADD COLUMN "startTime" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||
@ -0,0 +1,3 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Verify" ADD COLUMN "joinTime" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
ALTER COLUMN "startTime" DROP NOT NULL;
|
||||
14
prisma/migrations/20220806202004_change_id/migration.sql
Normal file
14
prisma/migrations/20220806202004_change_id/migration.sql
Normal file
@ -0,0 +1,14 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- The primary key for the `VerifyUnblock` table will be changed. If it partially fails, the table could be left without primary key constraint.
|
||||
- You are about to drop the column `channelId` on the `VerifyUnblock` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Verify" DROP CONSTRAINT "Verify_pkey",
|
||||
DROP COLUMN "channelId",
|
||||
ALTER COLUMN "id" DROP DEFAULT,
|
||||
ALTER COLUMN "id" SET DATA TYPE TEXT,
|
||||
ADD CONSTRAINT "Verify_pkey" PRIMARY KEY ("id");
|
||||
DROP SEQUENCE "Verify_id_seq";
|
||||
@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Verify" ALTER COLUMN "startTime" DROP DEFAULT;
|
||||
@ -0,0 +1,10 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Verify" ADD COLUMN "activist" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN "food" INTEGER,
|
||||
ADD COLUMN "length" INTEGER,
|
||||
ADD COLUMN "life" INTEGER,
|
||||
ADD COLUMN "reason" INTEGER,
|
||||
ADD COLUMN "reasoning" INTEGER,
|
||||
ADD COLUMN "trusted" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN "vegCurious" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN "where" INTEGER;
|
||||
@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Verify" ADD COLUMN "convinced" BOOLEAN NOT NULL DEFAULT false;
|
||||
@ -27,10 +27,6 @@ datasource db {
|
||||
|
||||
model User {
|
||||
id String @id @db.VarChar(255)
|
||||
level Int @default(0)
|
||||
xp Int @default(0)
|
||||
balance Int @default(0)
|
||||
lastDaily DateTime?
|
||||
vegan Boolean @default(false)
|
||||
trusted Boolean @default(false)
|
||||
activist Boolean @default(false)
|
||||
@ -52,16 +48,31 @@ model User {
|
||||
}
|
||||
|
||||
model Verify {
|
||||
id Int @id @default(autoincrement())
|
||||
user User @relation("verUser", fields: [userId], references: [id])
|
||||
id String @id
|
||||
user User @relation("verUser", fields: [userId], references: [id])
|
||||
userId String
|
||||
verifier User? @relation("verVerifier", fields: [verifierId], references: [id])
|
||||
verifier User? @relation("verVerifier", fields: [verifierId], references: [id])
|
||||
verifierId String?
|
||||
time DateTime @default(now())
|
||||
timedOut Boolean @default(false) // If they got kicked out of verification because they timed out
|
||||
vegan Boolean @default(false) // If they got verified as a vegan
|
||||
text Boolean @default(false) // If they used text verification
|
||||
serverVegan Boolean @default(false) // People that went vegan on the server
|
||||
joinTime DateTime @default(now())
|
||||
startTime DateTime?
|
||||
finishTime DateTime?
|
||||
timedOut Boolean @default(false) // If they got kicked out of verification because they timed out
|
||||
//complete Boolean @default(false) // If the verification was incomplete
|
||||
// Roles they got from verification
|
||||
vegan Boolean @default(false) // If they got verified as a vegan
|
||||
activist Boolean @default(false) // If they got the activist role when they verified
|
||||
trusted Boolean @default(false) // If they got the trusted role when they verified
|
||||
vegCurious Boolean @default(false) // If they got the Veg Curious role
|
||||
convinced Boolean @default(false)
|
||||
text Boolean @default(false) // If they used text verification
|
||||
serverVegan Boolean @default(false) // People that went vegan on the server
|
||||
// Stats on verification
|
||||
reason Int?
|
||||
where Int?
|
||||
length Int?
|
||||
reasoning Int?
|
||||
life Int?
|
||||
food Int?
|
||||
notes String?
|
||||
}
|
||||
|
||||
|
||||
@ -21,113 +21,19 @@ import { Command, RegisterBehavior, Args } from '@sapphire/framework';
|
||||
import {
|
||||
MessageEmbed, MessageActionRow, MessageButton, Constants, ButtonInteraction,
|
||||
} from 'discord.js';
|
||||
import type { Message, GuildMember } from 'discord.js';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { isMessageInstance } from '@sapphire/discord.js-utilities';
|
||||
import { addExistingUser, userExists } from '../../utils/dbExistingUser';
|
||||
import { addExistingUser, userExists } from '../../utils/database/dbExistingUser';
|
||||
import {
|
||||
addToDatabase,
|
||||
findNotes,
|
||||
getNote,
|
||||
deactivateNote,
|
||||
deactivateAllNotes,
|
||||
} from '../../utils/database/sus';
|
||||
import IDs from '../../utils/ids';
|
||||
|
||||
// TODO add a check when they join the server to give the user the sus role again
|
||||
|
||||
async function addToDatabase(userId: string, modId: string, message: string) {
|
||||
// Initialise the database connection
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Add the user to the database
|
||||
await prisma.sus.create({
|
||||
data: {
|
||||
user: {
|
||||
connect: {
|
||||
id: userId,
|
||||
},
|
||||
},
|
||||
mod: {
|
||||
connect: {
|
||||
id: modId,
|
||||
},
|
||||
},
|
||||
note: message,
|
||||
},
|
||||
});
|
||||
|
||||
// Close the database connection
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
|
||||
// Get a list of sus notes from the user
|
||||
async function findNotes(userId: string, active: boolean) {
|
||||
// Initialise the database connection
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Query to get the specific user's sus notes
|
||||
const note = await prisma.sus.findMany({
|
||||
where: {
|
||||
userId,
|
||||
active,
|
||||
},
|
||||
});
|
||||
|
||||
// Close the database connection
|
||||
await prisma.$disconnect();
|
||||
return note;
|
||||
}
|
||||
|
||||
// Get one note from the id
|
||||
async function getNote(noteId: number) {
|
||||
// Initialise the database connection
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Query to get the specific user's sus notes
|
||||
const note = await prisma.sus.findUnique({
|
||||
where: {
|
||||
id: noteId,
|
||||
},
|
||||
});
|
||||
|
||||
// Close the database connection
|
||||
await prisma.$disconnect();
|
||||
return note;
|
||||
}
|
||||
|
||||
async function deactivateNote(noteId: number) {
|
||||
// Initialise the database connection
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Query to deactivate the specific sus note
|
||||
await prisma.sus.update({
|
||||
where: {
|
||||
id: noteId,
|
||||
},
|
||||
data: {
|
||||
active: false,
|
||||
},
|
||||
});
|
||||
|
||||
// Close the database connection
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
|
||||
async function deactivateAllNotes(userId: string) {
|
||||
// Initialise the database connection
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Query to deactivate the specific user's sus notes
|
||||
await prisma.sus.updateMany({
|
||||
where: {
|
||||
userId: {
|
||||
contains: userId,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
active: false,
|
||||
},
|
||||
});
|
||||
|
||||
// Close the database connection
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
|
||||
// Main command
|
||||
class SusCommand extends Command {
|
||||
public constructor(context: Command.Context) {
|
||||
super(context, {
|
||||
|
||||
50
src/listeners/verification/joinServer.ts
Normal file
50
src/listeners/verification/joinServer.ts
Normal file
@ -0,0 +1,50 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2022 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Listener } from '@sapphire/framework';
|
||||
import type { GuildMember } from 'discord.js';
|
||||
import { fetchRoles } from '../../utils/database/dbExistingUser';
|
||||
import IDs from '../../utils/ids';
|
||||
import { blockTime } from '../../utils/database/verification';
|
||||
|
||||
class VerificationReady extends Listener {
|
||||
public constructor(context: Listener.Context, options: Listener.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
once: true,
|
||||
event: 'guildMemberAdd',
|
||||
});
|
||||
}
|
||||
|
||||
public async run(user: GuildMember) {
|
||||
// Add basic roles
|
||||
const roles = await fetchRoles(user.id);
|
||||
|
||||
// Check if the user has a verification block
|
||||
const timeout = await blockTime(user.id);
|
||||
if (timeout > 0) {
|
||||
roles.push(IDs.roles.verifyBlock);
|
||||
}
|
||||
|
||||
// Add roles if they don't have verification block
|
||||
await user.roles.add(roles);
|
||||
}
|
||||
}
|
||||
|
||||
export default VerificationReady;
|
||||
720
src/listeners/verification/joinVC.ts
Normal file
720
src/listeners/verification/joinVC.ts
Normal file
@ -0,0 +1,720 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2022 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { container, Listener } from '@sapphire/framework';
|
||||
import type {
|
||||
CategoryChannel,
|
||||
ColorResolvable,
|
||||
TextChannel,
|
||||
VoiceChannel,
|
||||
VoiceState,
|
||||
GuildMember,
|
||||
Guild,
|
||||
User,
|
||||
} from 'discord.js';
|
||||
import {
|
||||
ButtonInteraction,
|
||||
Constants,
|
||||
MessageActionRow,
|
||||
MessageButton,
|
||||
MessageEmbed,
|
||||
} from 'discord.js';
|
||||
import { time } from '@discordjs/builders';
|
||||
import { maxVCs, questionInfo, serverFind } from '../../utils/verificationConfig';
|
||||
import { joinVerification, startVerification, finishVerification } from '../../utils/database/verification';
|
||||
import { findNotes } from '../../utils/database/sus';
|
||||
import { userExists, addExistingUser } from '../../utils/database/dbExistingUser';
|
||||
import { rolesToString } from '../../utils/formatter';
|
||||
import IDs from '../../utils/ids';
|
||||
|
||||
class VerificationJoinVCListener extends Listener {
|
||||
public constructor(context: Listener.Context, options: Listener.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
event: 'voiceStateUpdate',
|
||||
});
|
||||
}
|
||||
|
||||
public async run(oldState: VoiceState, newState: VoiceState) {
|
||||
// If the event was not a user joining the channel
|
||||
if (oldState.channel?.parent?.id === IDs.categories.verification
|
||||
|| newState.channel?.parent?.id !== IDs.categories.verification
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Variable if this channel is a Verifiers only VC
|
||||
let verifier = false;
|
||||
|
||||
// Checks for not null
|
||||
const { channel } = newState;
|
||||
const { member } = newState;
|
||||
const { client } = container;
|
||||
const guild = client.guilds.cache.get(newState.guild.id);
|
||||
|
||||
if (channel === null || member === null || guild === undefined) {
|
||||
console.error('Verification channel not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get current category and channel
|
||||
const categoryGuild = guild.channels.cache.get(IDs.categories.verification);
|
||||
const currentChannelGuild = guild.channels.cache.get(channel.id);
|
||||
if (currentChannelGuild === undefined || categoryGuild === undefined) {
|
||||
console.error('Verification channel not found');
|
||||
return;
|
||||
}
|
||||
const currentChannel = currentChannelGuild as VoiceChannel;
|
||||
const category = categoryGuild as CategoryChannel;
|
||||
|
||||
const roles = rolesToString(member.roles.cache.map((r) => r.id));
|
||||
|
||||
// Checks if a verifier has joined
|
||||
if (channel.members.size === 2) {
|
||||
await newState.channel!.permissionOverwrites.set([
|
||||
{
|
||||
id: guild.roles.everyone,
|
||||
allow: ['SEND_MESSAGES'],
|
||||
},
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if a verifier joined a verification VC and update database
|
||||
if (channel.members.size === 2) {
|
||||
if (!channel.name.includes(' - Verification')) {
|
||||
return;
|
||||
}
|
||||
|
||||
await startVerification(channel.id);
|
||||
return;
|
||||
}
|
||||
|
||||
// Checks if there is more than one person who has joined or if the channel has members
|
||||
if (channel.members.size !== 1
|
||||
|| !channel.members.has(member.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the user has the verifiers role
|
||||
if (member.roles.cache.has(IDs.roles.staff.verifier)
|
||||
|| member.roles.cache.has(IDs.roles.staff.trialVerifier)) {
|
||||
await channel.setName('Verifier Meeting');
|
||||
verifier = true;
|
||||
} else {
|
||||
await channel.setName(`${member.displayName} - Verification`);
|
||||
await currentChannel.send(`Hiya ${member.user}, please be patient as a verifier has been called out to verify you.\n\n`
|
||||
+ 'If you leave this voice channel, you will automatically be given the non-vegan role where you gain access to this server and if you\'d like to verify as a vegan again, you\'d have to contact a Mod, which could be done via ModMail.');
|
||||
// Adds to the database that the user joined verification
|
||||
await joinVerification(channel.id, member);
|
||||
|
||||
// Remove all roles from the user
|
||||
await member.roles.remove([
|
||||
IDs.roles.vegan.vegan,
|
||||
IDs.roles.trusted,
|
||||
IDs.roles.nonvegan.nonvegan,
|
||||
IDs.roles.nonvegan.convinced,
|
||||
IDs.roles.nonvegan.vegCurious,
|
||||
]);
|
||||
|
||||
// Start 15-minute timer if verifier does not join
|
||||
// @ts-ignore
|
||||
this.container.tasks.create('verifyTimeout', {
|
||||
channelId: channel.id,
|
||||
userId: member.id,
|
||||
}, 900_000); // 15 minutes
|
||||
}
|
||||
|
||||
// Check how many voice channels there are
|
||||
const listVoiceChannels = category.children.filter((c) => c.type === 'GUILD_VOICE');
|
||||
|
||||
// Create a text channel for verifiers only
|
||||
// Checks if there are more than 10 voice channels
|
||||
if (!verifier) {
|
||||
const verificationText = await guild.channels.create(`✅┃${member.displayName}-verification`, {
|
||||
type: 'GUILD_TEXT',
|
||||
topic: `Channel for verifiers only. ${member.id} ${channel.id} (Please do not change this)`,
|
||||
parent: category.id,
|
||||
userLimit: 1,
|
||||
permissionOverwrites: [
|
||||
{
|
||||
id: guild.roles.everyone,
|
||||
deny: ['SEND_MESSAGES', 'VIEW_CHANNEL'],
|
||||
},
|
||||
{
|
||||
id: IDs.roles.verifyBlock,
|
||||
deny: ['VIEW_CHANNEL', 'SEND_MESSAGES'],
|
||||
},
|
||||
{
|
||||
id: IDs.roles.staff.verifier,
|
||||
allow: ['SEND_MESSAGES', 'VIEW_CHANNEL'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Send a message that someone wants to be verified
|
||||
const userInfoEmbed = await this.getUserInfo(member, roles);
|
||||
const susNotes = await this.getSus(member, guild);
|
||||
await verificationText.send({
|
||||
content: `${member.user} wants to be verified in ${channel}
|
||||
\n<@&${IDs.roles.staff.verifier}> <@&${IDs.roles.staff.trialVerifier}>`,
|
||||
embeds: [userInfoEmbed, susNotes],
|
||||
});
|
||||
|
||||
await this.verificationProcess(verificationText, channel.id, member, guild);
|
||||
}
|
||||
|
||||
// Create a new channel for others to join
|
||||
|
||||
// Checks if there are more than 10 voice channels
|
||||
if (listVoiceChannels.size > maxVCs - 1) {
|
||||
await guild.channels.create('Verification', {
|
||||
type: 'GUILD_VOICE',
|
||||
parent: category.id,
|
||||
userLimit: 1,
|
||||
permissionOverwrites: [
|
||||
{
|
||||
id: guild.roles.everyone,
|
||||
deny: ['SEND_MESSAGES', 'VIEW_CHANNEL'],
|
||||
},
|
||||
{
|
||||
id: IDs.roles.verifyBlock,
|
||||
deny: ['VIEW_CHANNEL', 'CONNECT', 'SEND_MESSAGES'],
|
||||
},
|
||||
{
|
||||
id: IDs.roles.nonvegan.nonvegan,
|
||||
allow: ['VIEW_CHANNEL'],
|
||||
deny: ['CONNECT'],
|
||||
},
|
||||
{
|
||||
id: IDs.roles.vegan.vegan,
|
||||
allow: ['VIEW_CHANNEL'],
|
||||
deny: ['CONNECT'],
|
||||
},
|
||||
{
|
||||
id: IDs.roles.vegan.activist,
|
||||
deny: ['VIEW_CHANNEL', 'CONNECT'],
|
||||
},
|
||||
{
|
||||
id: IDs.roles.staff.verifier,
|
||||
allow: ['SEND_MESSAGES', 'VIEW_CHANNEL'],
|
||||
},
|
||||
],
|
||||
});
|
||||
} else {
|
||||
await guild.channels.create('Verification', {
|
||||
type: 'GUILD_VOICE',
|
||||
parent: category.id,
|
||||
userLimit: 1,
|
||||
permissionOverwrites: [
|
||||
{
|
||||
id: guild.roles.everyone,
|
||||
deny: ['SEND_MESSAGES', 'VIEW_CHANNEL'],
|
||||
},
|
||||
{
|
||||
id: IDs.roles.verifyBlock,
|
||||
deny: ['VIEW_CHANNEL', 'CONNECT', 'SEND_MESSAGES'],
|
||||
},
|
||||
{
|
||||
id: IDs.roles.nonvegan.nonvegan,
|
||||
allow: ['VIEW_CHANNEL'],
|
||||
},
|
||||
{
|
||||
id: IDs.roles.vegan.vegan,
|
||||
allow: ['VIEW_CHANNEL'],
|
||||
},
|
||||
{
|
||||
id: IDs.roles.vegan.activist,
|
||||
deny: ['VIEW_CHANNEL', 'CONNECT'],
|
||||
},
|
||||
{
|
||||
id: IDs.roles.staff.verifier,
|
||||
allow: ['SEND_MESSAGES', 'VIEW_CHANNEL'],
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
// Change permissions to join the current channel
|
||||
await currentChannel.permissionOverwrites.set([
|
||||
{
|
||||
id: guild.roles.everyone,
|
||||
deny: ['SEND_MESSAGES', 'VIEW_CHANNEL'],
|
||||
},
|
||||
{
|
||||
id: IDs.roles.nonvegan.nonvegan,
|
||||
deny: ['VIEW_CHANNEL'],
|
||||
},
|
||||
{
|
||||
id: IDs.roles.vegan.vegan,
|
||||
deny: ['VIEW_CHANNEL'],
|
||||
},
|
||||
{
|
||||
id: member.id,
|
||||
allow: ['VIEW_CHANNEL'],
|
||||
},
|
||||
]);
|
||||
await currentChannel.setUserLimit(0);
|
||||
}
|
||||
|
||||
// Creates an embed for information about the user
|
||||
private async getUserInfo(user: GuildMember, roles: string) {
|
||||
const joinTime = time(user.joinedAt!);
|
||||
const registerTime = time(user.user.createdAt);
|
||||
|
||||
const embed = new MessageEmbed()
|
||||
.setColor(user.displayHexColor)
|
||||
.setTitle(`Information on ${user.user.username}`)
|
||||
.setThumbnail(user.user.avatarURL()!)
|
||||
.addFields(
|
||||
{ name: 'Joined:', value: `${joinTime}`, inline: true },
|
||||
{ name: 'Created:', value: `${registerTime}`, inline: true },
|
||||
{ name: 'Roles:', value: roles },
|
||||
);
|
||||
|
||||
return embed;
|
||||
}
|
||||
|
||||
// Creates the embed to display the sus note
|
||||
private async getSus(user: GuildMember, guild: Guild) {
|
||||
const notes = await findNotes(user.id, true);
|
||||
|
||||
const embed = new MessageEmbed()
|
||||
.setColor(user.displayHexColor)
|
||||
.setTitle(`${notes.length} sus notes for ${user.user.username}`);
|
||||
|
||||
// Add up to 10 of the latest sus notes to the embed
|
||||
for (let i = notes.length > 10 ? notes.length - 10 : 0; i < notes.length; i += 1) {
|
||||
// Get mod name
|
||||
const modGuildMember = guild!.members.cache.get(notes[i].modId);
|
||||
let mod = notes[i].modId;
|
||||
if (modGuildMember !== undefined) {
|
||||
mod = modGuildMember!.displayName;
|
||||
}
|
||||
// Add sus note to embed
|
||||
embed.addFields({
|
||||
name: `Sus ID: ${notes[i].id} | Moderator: ${mod} | Date: <t:${Math.floor(notes[i].time.getTime() / 1000)}>`,
|
||||
value: notes[i].note,
|
||||
});
|
||||
}
|
||||
|
||||
return embed;
|
||||
}
|
||||
|
||||
private async verificationProcess(
|
||||
channel: TextChannel,
|
||||
verId: string,
|
||||
user: GuildMember,
|
||||
guild: Guild,
|
||||
) {
|
||||
const embedColor = '#0099ff';
|
||||
const info = {
|
||||
page: 0,
|
||||
find: {
|
||||
reason: 0,
|
||||
where: 0,
|
||||
},
|
||||
length: 0,
|
||||
reasoning: 0,
|
||||
life: 0,
|
||||
food: 0,
|
||||
roles: {
|
||||
vegan: false,
|
||||
activist: false,
|
||||
trusted: false,
|
||||
vegCurious: false,
|
||||
convinced: false,
|
||||
},
|
||||
};
|
||||
|
||||
// TODO add a variable that tells if each order has a reversed value, e.g. 0-3 or 3-0
|
||||
const questionLength = questionInfo.length;
|
||||
|
||||
let embed = await this.createEmbed(questionInfo[0].question, embedColor);
|
||||
let buttons = await this.createButtons(questionInfo[0].buttons);
|
||||
|
||||
// Sends the note to verify this note is to be deleted
|
||||
const message = await channel.send({
|
||||
embeds: [embed],
|
||||
components: buttons,
|
||||
});
|
||||
|
||||
// Listen for the button presses
|
||||
const collector = channel.createMessageComponentCollector({
|
||||
// max: 2, // Maximum of 1 button press
|
||||
});
|
||||
|
||||
// Button pressed
|
||||
collector.on('collect', async (button: ButtonInteraction) => {
|
||||
// Select roles
|
||||
if (button.customId.includes('button')) {
|
||||
await button.deferUpdate();
|
||||
// Get the button choice
|
||||
const buttonChoice = this.getButtonValue(button.customId);
|
||||
if (Number.isNaN(buttonChoice)) {
|
||||
return;
|
||||
}
|
||||
// Set the value of the button choice to the page the question was on
|
||||
switch (info.page) {
|
||||
case 0: {
|
||||
info.find.reason = buttonChoice;
|
||||
if (buttonChoice !== 0 && info.find.reason === 0) {
|
||||
embed = await this.createEmbed(serverFind[info.page].question, embedColor);
|
||||
buttons = await this.createButtons(serverFind[info.page].buttons);
|
||||
await message.edit({
|
||||
embeds: [embed],
|
||||
components: buttons,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (info.find.reason !== 0) {
|
||||
info.find.where = buttonChoice;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
info.length = buttonChoice;
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
info.reasoning = buttonChoice;
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
info.life = buttonChoice;
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
info.food = buttonChoice;
|
||||
break;
|
||||
}
|
||||
// If they are definitely vegan or not
|
||||
case 5: {
|
||||
if (buttonChoice === 0) {
|
||||
info.roles.vegan = true;
|
||||
info.roles.trusted = true;
|
||||
} else {
|
||||
info.page += 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// If they are vegan but should get activist role
|
||||
case 6: {
|
||||
if (buttonChoice === 0) {
|
||||
info.roles.activist = true;
|
||||
}
|
||||
info.page += 1;
|
||||
break;
|
||||
}
|
||||
// If they should get vegan, convinced or non-vegan
|
||||
case 7: {
|
||||
if (buttonChoice === 0) {
|
||||
info.roles.vegan = true;
|
||||
} else if (buttonChoice === 1) {
|
||||
info.roles.convinced = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 8: {
|
||||
if (buttonChoice === 0) {
|
||||
info.roles.vegCurious = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
console.error('Button clicked out of range');
|
||||
return;
|
||||
}
|
||||
}
|
||||
info.page += 1;
|
||||
// Checks if finished all the questions
|
||||
if (info.page < questionLength) {
|
||||
embed = await this.createEmbed(questionInfo[info.page].question, embedColor);
|
||||
buttons = await this.createButtons(questionInfo[info.page].buttons);
|
||||
await message.edit({
|
||||
embeds: [embed],
|
||||
components: buttons,
|
||||
});
|
||||
}
|
||||
// Confirmation to give roles to the user being verified
|
||||
if (info.page === questionLength) {
|
||||
// Create embed with all the roles the user has
|
||||
embed = new MessageEmbed()
|
||||
.setColor(embedColor)
|
||||
.setTitle(`Give these roles to ${user.displayName}?`)
|
||||
.setThumbnail(user.avatarURL()!)
|
||||
.addFields(
|
||||
{ name: 'Roles:', value: this.getTextRoles(info.roles) },
|
||||
);
|
||||
|
||||
// Create buttons for input
|
||||
buttons = [new MessageActionRow<MessageButton>()
|
||||
.addComponents(
|
||||
new MessageButton()
|
||||
.setCustomId('confirm')
|
||||
.setLabel('Yes')
|
||||
.setStyle(Constants.MessageButtonStyles.SUCCESS),
|
||||
new MessageButton()
|
||||
.setCustomId('cancel')
|
||||
.setLabel('No')
|
||||
.setStyle(Constants.MessageButtonStyles.DANGER),
|
||||
)];
|
||||
await message.edit({
|
||||
embeds: [embed],
|
||||
components: buttons,
|
||||
});
|
||||
}
|
||||
}
|
||||
// Confirming and finishing the verification
|
||||
if (button.customId === 'confirm' && info.page >= questionLength) {
|
||||
// Check verifier is on the database
|
||||
const verifierGuildMember = await guild.members.cache.get(button.user.id);
|
||||
if (verifierGuildMember === undefined) {
|
||||
await message.edit({ content: 'Verifier not found!' });
|
||||
return;
|
||||
}
|
||||
// Add verifier to database if they're not on the database
|
||||
if (!(await userExists(verifierGuildMember))) {
|
||||
await addExistingUser(verifierGuildMember);
|
||||
}
|
||||
|
||||
// Add verification data to database
|
||||
await finishVerification(verId, button.user.id, info);
|
||||
// Give roles on Discord
|
||||
await this.giveRoles(user, info.roles);
|
||||
// Add timeout if they do not have activist role
|
||||
if (!info.roles.activist) {
|
||||
// @ts-ignore
|
||||
this.container.tasks.create('verifyUnblock', {
|
||||
userId: user.id,
|
||||
guildId: guild.id,
|
||||
}, (info.roles.vegan || info.roles.convinced) ? 604800000 : 1814400000);
|
||||
}
|
||||
// Add embed saying verification completed
|
||||
embed = new MessageEmbed()
|
||||
.setColor('#34c000')
|
||||
.setTitle(`Successfully verified ${user.displayName}!`)
|
||||
.setThumbnail(user.user.avatarURL()!)
|
||||
.addFields(
|
||||
{ name: 'Roles:', value: this.getTextRoles(info.roles) },
|
||||
);
|
||||
await message.edit({
|
||||
embeds: [embed],
|
||||
components: [],
|
||||
});
|
||||
// Send welcome message after verification
|
||||
await this.finishMessages(user.user, info.roles);
|
||||
}
|
||||
if (button.customId === 'cancel' && info.page >= questionLength) {
|
||||
info.page = 5;
|
||||
info.roles.vegan = false;
|
||||
info.roles.activist = false;
|
||||
info.roles.trusted = false;
|
||||
info.roles.vegCurious = false;
|
||||
info.roles.convinced = false;
|
||||
embed = await this.createEmbed(questionInfo[info.page].question, embedColor);
|
||||
buttons = await this.createButtons(questionInfo[info.page].buttons);
|
||||
await message.edit({
|
||||
embeds: [embed],
|
||||
components: buttons,
|
||||
});
|
||||
await button.deferUpdate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async createEmbed(title: string, color: ColorResolvable) {
|
||||
return new MessageEmbed()
|
||||
.setColor(color)
|
||||
.setTitle(title);
|
||||
}
|
||||
|
||||
private async createButtons(buttons: string[]) {
|
||||
const buttonActions = [];
|
||||
|
||||
for (let i = 0; i < buttons.length; i += 1) {
|
||||
// Check if it exceeds the maximum buttons in a ActionRow
|
||||
if (i % 5 === 0) {
|
||||
buttonActions.push(new MessageActionRow<MessageButton>());
|
||||
}
|
||||
buttonActions[Math.floor(i / 5)]
|
||||
.addComponents(
|
||||
new MessageButton()
|
||||
.setCustomId(`button${i}`)
|
||||
.setLabel(buttons[i])
|
||||
.setStyle(Constants.MessageButtonStyles.SECONDARY),
|
||||
);
|
||||
}
|
||||
|
||||
return buttonActions;
|
||||
}
|
||||
|
||||
// Finds the value of the choice in the button
|
||||
private getButtonValue(button: string) {
|
||||
const buttonChoice = button.at(button.length - 1);
|
||||
if (buttonChoice === undefined) {
|
||||
return NaN;
|
||||
}
|
||||
return parseInt(buttonChoice, 10);
|
||||
}
|
||||
|
||||
private getTextRoles(
|
||||
roles: {
|
||||
vegan: boolean,
|
||||
activist: boolean,
|
||||
trusted: boolean,
|
||||
vegCurious: boolean,
|
||||
convinced: boolean
|
||||
},
|
||||
) {
|
||||
let rolesText = '';
|
||||
if (roles.convinced) {
|
||||
rolesText += `<@&${IDs.roles.nonvegan.convinced}>`;
|
||||
}
|
||||
if (roles.vegan) {
|
||||
rolesText += `<@&${IDs.roles.vegan.vegan}>`;
|
||||
} else {
|
||||
rolesText += `<@&${IDs.roles.nonvegan.nonvegan}>`;
|
||||
}
|
||||
if (roles.activist) {
|
||||
rolesText += `<@&${IDs.roles.vegan.activist}>`;
|
||||
}
|
||||
if (roles.trusted) {
|
||||
rolesText += `<@&${IDs.roles.trusted}>`;
|
||||
}
|
||||
if (roles.vegCurious) {
|
||||
rolesText += `<@&${IDs.roles.nonvegan.vegCurious}>`;
|
||||
}
|
||||
return rolesText;
|
||||
}
|
||||
|
||||
private async giveRoles(
|
||||
user: GuildMember,
|
||||
roles: {
|
||||
vegan: boolean,
|
||||
activist: boolean,
|
||||
trusted: boolean,
|
||||
vegCurious: boolean,
|
||||
convinced: boolean
|
||||
},
|
||||
) {
|
||||
const rolesAdd = [];
|
||||
if (roles.convinced) {
|
||||
rolesAdd.push(IDs.roles.nonvegan.convinced);
|
||||
}
|
||||
if (roles.vegan) {
|
||||
rolesAdd.push(IDs.roles.vegan.vegan);
|
||||
} else {
|
||||
rolesAdd.push(IDs.roles.nonvegan.nonvegan);
|
||||
}
|
||||
if (roles.activist) {
|
||||
rolesAdd.push(IDs.roles.vegan.activist);
|
||||
} else {
|
||||
rolesAdd.push(IDs.roles.verifyBlock);
|
||||
}
|
||||
if (roles.trusted) {
|
||||
rolesAdd.push(IDs.roles.trusted);
|
||||
}
|
||||
if (roles.vegCurious) {
|
||||
rolesAdd.push(IDs.roles.nonvegan.vegCurious);
|
||||
}
|
||||
await user.roles.add(rolesAdd);
|
||||
}
|
||||
|
||||
// Messages after verifying the user
|
||||
private async finishMessages(user: User, roles: {
|
||||
vegan: boolean,
|
||||
activist: boolean,
|
||||
trusted: boolean,
|
||||
vegCurious: boolean,
|
||||
convinced: boolean
|
||||
}) {
|
||||
// Send a DM with when their verification is finished
|
||||
await this.finishDM(user, roles)
|
||||
.catch(() => console.error('Verification: Closed DMs'));
|
||||
|
||||
// Not vegan
|
||||
if (!roles.vegan) {
|
||||
const general = this.container.client.channels.cache.get(IDs.channels.nonVegan.general) as TextChannel | undefined;
|
||||
if (general === undefined) {
|
||||
return;
|
||||
}
|
||||
let msg = `${user}, you have been verified! Please check <#${IDs.channels.information.roles}> `
|
||||
+ `and remember to follow the <#${IDs.channels.information.conduct}> and to respect ongoing discussion and debates.`;
|
||||
// Add extra info if the user got veg curious or convinced.
|
||||
if (roles.vegCurious || roles.convinced) {
|
||||
msg += `\n\nYou also have access to <#${IDs.channels.dietSupport.main}> for help on going vegan.`;
|
||||
}
|
||||
await general.send(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
// Vegan
|
||||
const general = this.container.client.channels.cache.get(IDs.channels.vegan.general) as TextChannel | undefined;
|
||||
if (general === undefined) {
|
||||
return;
|
||||
}
|
||||
const msg = `Welcome ${user}! Please check out <#${IDs.channels.information.roles}> :)`;
|
||||
await general.send(msg);
|
||||
|
||||
// Activist role
|
||||
if (roles.activist) {
|
||||
const activist = this.container.client.channels.cache.get(IDs.channels.activism.activism) as TextChannel | undefined;
|
||||
if (activist === undefined) {
|
||||
return;
|
||||
}
|
||||
const activistMsg = `${user} you have been given the activist role! This means that if you'd wish to engage with non-vegans in `
|
||||
+ `<#${IDs.channels.nonVegan.general}>, you should follow these rules:\n\n`
|
||||
+ '1. Try to move conversations with non-vegans towards veganism/animal ethics\n'
|
||||
+ '2. Don\'t discuss social topics while activism is happening\n'
|
||||
+ '3. Have evidence for claims you make. "I don\'t know" is an acceptable answer. Chances are someone here knows or you can take time to find out\n'
|
||||
+ '4. Don\'t advocate for baby steps towards veganism. Participation in exploitation can stop today\n'
|
||||
+ '5. Differences in opinion between activists should be resolved in vegan spaces, not in the chat with non-vegans';
|
||||
await activist.send(activistMsg);
|
||||
}
|
||||
}
|
||||
|
||||
// Messages after verifying the user
|
||||
private async finishDM(user: User, roles: {
|
||||
vegan: boolean,
|
||||
activist: boolean,
|
||||
trusted: boolean,
|
||||
vegCurious: boolean,
|
||||
convinced: boolean
|
||||
}) {
|
||||
if (!roles.vegan && !roles.convinced) {
|
||||
const message = 'You\'ve been verified as non-vegan!'
|
||||
+ `\n\nYou can next verify on ${time(Math.round(Date.now() / 1000) + 1814400)}`;
|
||||
|
||||
await user.send(message);
|
||||
} else if (roles.convinced) {
|
||||
const message = 'You\'ve been verified as convinced!'
|
||||
+ `\n\nYou can next verify on ${time(Math.round(Date.now() / 1000) + 604800)}`;
|
||||
|
||||
await user.send(message);
|
||||
} else if (roles.vegan && !roles.activist) {
|
||||
const message = 'You\'ve been verified as a vegan!'
|
||||
+ `\n\nYou can next get verified on ${time(Math.round(Date.now() / 1000) + 604800)} if you would wish to have the activist role.`;
|
||||
|
||||
await user.send(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default VerificationJoinVCListener;
|
||||
183
src/listeners/verification/leaveVC.ts
Normal file
183
src/listeners/verification/leaveVC.ts
Normal file
@ -0,0 +1,183 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2022 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { container, Listener } from '@sapphire/framework';
|
||||
import type {
|
||||
VoiceState, CategoryChannel, VoiceChannel, TextChannel,
|
||||
} from 'discord.js';
|
||||
import { time } from '@discordjs/builders';
|
||||
import { maxVCs, leaveBan } from '../../utils/verificationConfig';
|
||||
import { getUser, checkFinish, countIncomplete } from '../../utils/database/verification';
|
||||
import { fetchRoles } from '../../utils/database/dbExistingUser';
|
||||
import { fibonacci } from '../../utils/mathsSeries';
|
||||
import IDs from '../../utils/ids';
|
||||
|
||||
class VerificationLeaveVCListener extends Listener {
|
||||
public constructor(context: Listener.Context, options: Listener.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
event: 'voiceStateUpdate',
|
||||
});
|
||||
}
|
||||
|
||||
public async run(oldState: VoiceState, newState: VoiceState) {
|
||||
// If the event was not a user joining the channel
|
||||
if (oldState.channel?.parent?.id !== IDs.categories.verification
|
||||
|| newState.channel?.parent?.id === IDs.categories.verification
|
||||
|| oldState.channel.members.size > 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
let verifier = false;
|
||||
|
||||
// Check for undefined variables
|
||||
const { client } = container;
|
||||
const { channel } = oldState;
|
||||
const guild = client.guilds.cache.get(newState.guild.id);
|
||||
|
||||
if (channel === null || guild === undefined) {
|
||||
console.error('Verification channel not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the category
|
||||
const categoryGuild = guild.channels.cache.get(IDs.categories.verification);
|
||||
if (categoryGuild === null) {
|
||||
console.error('Verification channel not found');
|
||||
return;
|
||||
}
|
||||
const category = categoryGuild as CategoryChannel;
|
||||
|
||||
// Get the user that was being verified
|
||||
const userSnowflake = await getUser(channel.id);
|
||||
if (userSnowflake === null) {
|
||||
verifier = true;
|
||||
}
|
||||
|
||||
// Allow more people to join VC if there are less than 10 VCs
|
||||
|
||||
if (!verifier) {
|
||||
const user = guild.members.cache.get(userSnowflake!)!;
|
||||
|
||||
// Remove verify as vegan and give non vegan role
|
||||
if (!await checkFinish(channel.id)) {
|
||||
await user.roles.remove(IDs.roles.verifyingAsVegan);
|
||||
|
||||
// Get roles to give back to the user
|
||||
const roles = await fetchRoles(user.id);
|
||||
roles.push(IDs.roles.verifyBlock);
|
||||
await user.roles.add(roles);
|
||||
// Create timeout block for user
|
||||
// Counts the recent times they have incomplete verifications
|
||||
const incompleteCount = await countIncomplete(user.id) % (leaveBan + 1);
|
||||
// Creates the length of the time for the ban
|
||||
const banLength = fibonacci(incompleteCount) * 3600_000;
|
||||
|
||||
// @ts-ignore
|
||||
this.container.tasks.create('verifyUnblock', {
|
||||
userId: user.id,
|
||||
guildId: guild.id,
|
||||
}, banLength);
|
||||
|
||||
await user.user.send('You have been timed out as a verifier had not joined for 15 minutes or you disconnected from verification.\n\n'
|
||||
+ `You can verify again at: ${time(Math.round(Date.now() / 1000) + (banLength / 1000))}`)
|
||||
.catch(() => console.error('Verification: Closed DMs'));
|
||||
}
|
||||
}
|
||||
|
||||
// Check how many voice channels there are
|
||||
const listVoiceChannels = category.children.filter((c) => c.type === 'GUILD_VOICE');
|
||||
|
||||
// Check that it is not deleting the 'Verification' channel (in case bot crashes)
|
||||
if (channel.name !== 'Verification') {
|
||||
// Delete the channel
|
||||
await channel.delete();
|
||||
}
|
||||
|
||||
// Delete text channel
|
||||
if (!verifier) {
|
||||
// Gets a list of all the text channels in the verification category
|
||||
const listTextChannels = category.children.filter((c) => c.type === 'GUILD_TEXT');
|
||||
listTextChannels.forEach((c) => {
|
||||
const textChannel = c as TextChannel;
|
||||
// Checks if the channel topic has the user's snowflake
|
||||
if (textChannel.topic!.includes(userSnowflake!)) {
|
||||
textChannel.delete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// If there are no VCs left in verification after having the channel deleted
|
||||
if (listVoiceChannels.size === 0) {
|
||||
// Create a verification channel
|
||||
await guild.channels.create('Verification', {
|
||||
type: 'GUILD_VOICE',
|
||||
parent: IDs.categories.verification,
|
||||
userLimit: 1,
|
||||
permissionOverwrites: [
|
||||
{
|
||||
id: guild.roles.everyone,
|
||||
deny: ['SEND_MESSAGES', 'VIEW_CHANNEL'],
|
||||
},
|
||||
{
|
||||
id: IDs.roles.verifyBlock,
|
||||
deny: ['VIEW_CHANNEL', 'CONNECT', 'SEND_MESSAGES'],
|
||||
},
|
||||
{
|
||||
id: IDs.roles.nonvegan.nonvegan,
|
||||
allow: ['VIEW_CHANNEL'],
|
||||
},
|
||||
{
|
||||
id: IDs.roles.vegan.vegan,
|
||||
allow: ['VIEW_CHANNEL'],
|
||||
},
|
||||
{
|
||||
id: IDs.roles.vegan.activist,
|
||||
deny: ['VIEW_CHANNEL', 'CONNECT'],
|
||||
},
|
||||
{
|
||||
id: IDs.roles.staff.verifier,
|
||||
allow: ['SEND_MESSAGES', 'VIEW_CHANNEL'],
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
// If there are less than 10, stop
|
||||
if (listVoiceChannels.size < maxVCs) {
|
||||
return;
|
||||
}
|
||||
|
||||
const verification = listVoiceChannels.last() as VoiceChannel;
|
||||
|
||||
await verification!.permissionOverwrites.set([
|
||||
{
|
||||
id: IDs.roles.nonvegan.nonvegan,
|
||||
allow: ['VIEW_CHANNEL'],
|
||||
},
|
||||
{
|
||||
id: IDs.roles.vegan.vegan,
|
||||
allow: ['VIEW_CHANNEL'],
|
||||
},
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
export default VerificationLeaveVCListener;
|
||||
111
src/listeners/verification/start.ts
Normal file
111
src/listeners/verification/start.ts
Normal file
@ -0,0 +1,111 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2022 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Listener } from '@sapphire/framework';
|
||||
import type {
|
||||
Client,
|
||||
CategoryChannel,
|
||||
TextChannel,
|
||||
VoiceChannel,
|
||||
} from 'discord.js';
|
||||
import IDs from '../../utils/ids';
|
||||
|
||||
class VerificationReady extends Listener {
|
||||
public constructor(context: Listener.Context, options: Listener.Options) {
|
||||
super(context, {
|
||||
...options,
|
||||
once: true,
|
||||
event: 'ready',
|
||||
});
|
||||
}
|
||||
|
||||
public async run(client: Client) {
|
||||
// Get verification category
|
||||
let category = client.channels.cache.get(IDs.categories.verification) as CategoryChannel | undefined;
|
||||
if (category === undefined) {
|
||||
category = await client.channels.fetch(IDs.categories.verification) as CategoryChannel | undefined;
|
||||
if (category === undefined) {
|
||||
console.error('verifyStart: Channel not found');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check how many voice channels there are
|
||||
let voiceChannels = category.children.filter((c) => c.type === 'GUILD_VOICE');
|
||||
const emptyVC: string[] = [];
|
||||
// Delete voice channels
|
||||
voiceChannels.forEach((c) => {
|
||||
const voiceChannel = c as VoiceChannel;
|
||||
if (voiceChannel.members.size === 0) {
|
||||
emptyVC.push(voiceChannel.id);
|
||||
voiceChannel.delete();
|
||||
}
|
||||
});
|
||||
|
||||
// Delete text channels
|
||||
const textChannels = category.children.filter((c) => c.type === 'GUILD_TEXT');
|
||||
textChannels.forEach((c) => {
|
||||
const textChannel = c as TextChannel;
|
||||
// Checks if the channel topic has the user's snowflake
|
||||
emptyVC.forEach((snowflake) => {
|
||||
if (textChannel.topic!.includes(snowflake)) {
|
||||
textChannel.delete();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Check if there is no voice channels, create verification
|
||||
voiceChannels = category.children.filter((c) => c.type === 'GUILD_VOICE');
|
||||
if (voiceChannels.size === emptyVC.length) {
|
||||
await category.guild.channels.create('Verification', {
|
||||
type: 'GUILD_VOICE',
|
||||
parent: IDs.categories.verification,
|
||||
userLimit: 1,
|
||||
permissionOverwrites: [
|
||||
{
|
||||
id: category.guild.roles.everyone,
|
||||
deny: ['SEND_MESSAGES', 'VIEW_CHANNEL'],
|
||||
},
|
||||
{
|
||||
id: IDs.roles.verifyBlock,
|
||||
deny: ['VIEW_CHANNEL', 'CONNECT', 'SEND_MESSAGES'],
|
||||
},
|
||||
{
|
||||
id: IDs.roles.nonvegan.nonvegan,
|
||||
allow: ['VIEW_CHANNEL'],
|
||||
},
|
||||
{
|
||||
id: IDs.roles.vegan.vegan,
|
||||
allow: ['VIEW_CHANNEL'],
|
||||
},
|
||||
{
|
||||
id: IDs.roles.vegan.activist,
|
||||
deny: ['VIEW_CHANNEL', 'CONNECT'],
|
||||
},
|
||||
{
|
||||
id: IDs.roles.staff.verifier,
|
||||
allow: ['SEND_MESSAGES', 'VIEW_CHANNEL'],
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default VerificationReady;
|
||||
56
src/scheduled-tasks/verifyTimeout.ts
Normal file
56
src/scheduled-tasks/verifyTimeout.ts
Normal file
@ -0,0 +1,56 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2022 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import type { VoiceChannel } from 'discord.js';
|
||||
import { ScheduledTask } from '@sapphire/plugin-scheduled-tasks';
|
||||
|
||||
export class VerifyTimeout extends ScheduledTask {
|
||||
public constructor(context: ScheduledTask.Context, options: ScheduledTask.Options) {
|
||||
super(context, options);
|
||||
}
|
||||
|
||||
public async run(payload: { channelId: string, userId: string }) {
|
||||
// Get the guild where the user is in
|
||||
let channel = this.container.client.channels.cache.get(payload.channelId) as VoiceChannel | undefined;
|
||||
if (channel === undefined) {
|
||||
channel = await this.container.client.channels.fetch(payload.channelId) as VoiceChannel | undefined;
|
||||
if (channel === undefined) {
|
||||
console.error('verifyTimeout: Channel not found!');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (channel.members.size < 2 && channel.members.has(payload.userId)) {
|
||||
const user = channel.members.get(payload.userId);
|
||||
if (user === undefined) {
|
||||
console.error('verifyTimeout: GuildMember not found!');
|
||||
return;
|
||||
}
|
||||
await user.voice.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare module '@sapphire/plugin-scheduled-tasks' {
|
||||
interface ScheduledTasks {
|
||||
verifyUnblock: never;
|
||||
}
|
||||
}
|
||||
|
||||
export default VerifyTimeout;
|
||||
60
src/scheduled-tasks/verifyUnblock.ts
Normal file
60
src/scheduled-tasks/verifyUnblock.ts
Normal file
@ -0,0 +1,60 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2022 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ScheduledTask } from '@sapphire/plugin-scheduled-tasks';
|
||||
import IDs from '../utils/ids';
|
||||
|
||||
export class VerifyUnblock extends ScheduledTask {
|
||||
public constructor(context: ScheduledTask.Context, options: ScheduledTask.Options) {
|
||||
super(context, options);
|
||||
}
|
||||
|
||||
public async run(payload: { userId: string, guildId: string }) {
|
||||
// Get the guild where the user is in
|
||||
let guild = this.container.client.guilds.cache.get(payload.guildId);
|
||||
if (guild === undefined) {
|
||||
guild = await this.container.client.guilds.fetch(payload.guildId);
|
||||
if (guild === undefined) {
|
||||
console.error('verifyUnblock: Guild not found!');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Find GuildMember for the user
|
||||
let user = guild.members.cache.get(payload.userId);
|
||||
if (user === undefined) {
|
||||
user = await guild.members.fetch(payload.userId);
|
||||
if (user === undefined) {
|
||||
console.error('verifyUnblock: GuildMember not found!');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the 'verify block' role
|
||||
await user.roles.remove(IDs.roles.verifyBlock);
|
||||
}
|
||||
}
|
||||
|
||||
declare module '@sapphire/plugin-scheduled-tasks' {
|
||||
interface ScheduledTasks {
|
||||
verifyUnblock: never;
|
||||
}
|
||||
}
|
||||
|
||||
export default VerifyUnblock;
|
||||
185
src/utils/database/dbExistingUser.ts
Normal file
185
src/utils/database/dbExistingUser.ts
Normal file
@ -0,0 +1,185 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2022 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import type { GuildMember, GuildMemberRoleManager } from 'discord.js';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import IDs from '../ids';
|
||||
|
||||
// Checks if the user exists on the database
|
||||
export async function userExists(user: GuildMember) {
|
||||
// Initialises Prisma Client
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Counts if the user is on the database by their snowflake
|
||||
const userQuery = await prisma.user.count({
|
||||
where: {
|
||||
id: user.id,
|
||||
},
|
||||
});
|
||||
|
||||
// Close the database connection
|
||||
await prisma.$disconnect();
|
||||
|
||||
// If the user is found on the database, then return true, otherwise, false.
|
||||
return userQuery > 0;
|
||||
}
|
||||
|
||||
function getRoles(roles: GuildMemberRoleManager) {
|
||||
// Checks what roles the user has
|
||||
const rolesDict = {
|
||||
vegan: roles.cache.has(IDs.roles.vegan.vegan),
|
||||
activist: roles.cache.has(IDs.roles.vegan.activist),
|
||||
plus: roles.cache.has(IDs.roles.vegan.plus),
|
||||
notVegan: roles.cache.has(IDs.roles.nonvegan.nonvegan),
|
||||
vegCurious: roles.cache.has(IDs.roles.nonvegan.vegCurious),
|
||||
convinced: roles.cache.has(IDs.roles.nonvegan.convinced),
|
||||
trusted: roles.cache.has(IDs.roles.trusted),
|
||||
muted: roles.cache.has(IDs.roles.restrictions.muted),
|
||||
};
|
||||
|
||||
return rolesDict;
|
||||
}
|
||||
|
||||
// Adds the user to the database if they were already on the server before the bot/database
|
||||
export async function addExistingUser(user: GuildMember) {
|
||||
// Initialises Prisma Client
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Counts if the user is on the database by their snowflake
|
||||
const userQuery = await prisma.user.count({
|
||||
where: {
|
||||
id: user.id,
|
||||
},
|
||||
});
|
||||
|
||||
// If the user is already in the database
|
||||
if (userQuery > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse all the roles into a dictionary
|
||||
const roles = getRoles(user.roles);
|
||||
|
||||
// Create the user in the database
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
id: user.id,
|
||||
vegan: roles.vegan,
|
||||
trusted: roles.trusted,
|
||||
activist: roles.activist,
|
||||
plus: roles.plus,
|
||||
notVegan: roles.notVegan,
|
||||
vegCurious: roles.vegCurious,
|
||||
convinced: roles.convinced,
|
||||
muted: roles.muted,
|
||||
},
|
||||
});
|
||||
|
||||
// Close the database connection
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
|
||||
export async function updateUser(user: GuildMember) {
|
||||
// Check if the user is already on the database
|
||||
if (!(await userExists(user))) {
|
||||
await addExistingUser(user);
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse all the roles into a dictionary
|
||||
const roles = getRoles(user.roles);
|
||||
|
||||
// Initialises Prisma Client
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
await prisma.user.update({
|
||||
where: {
|
||||
id: user.id,
|
||||
},
|
||||
data: {
|
||||
id: user.id,
|
||||
vegan: roles.vegan,
|
||||
trusted: roles.trusted,
|
||||
activist: roles.activist,
|
||||
plus: roles.plus,
|
||||
notVegan: roles.notVegan,
|
||||
vegCurious: roles.vegCurious,
|
||||
convinced: roles.convinced,
|
||||
muted: roles.muted,
|
||||
},
|
||||
});
|
||||
|
||||
// Close the database connection
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
|
||||
export async function fetchRoles(user: string) {
|
||||
// Initialises Prisma Client
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Get the user's roles
|
||||
const roleQuery = await prisma.user.findUnique({
|
||||
where: {
|
||||
id: user,
|
||||
},
|
||||
select: {
|
||||
vegan: true,
|
||||
trusted: true,
|
||||
activist: true,
|
||||
plus: true,
|
||||
notVegan: true,
|
||||
vegCurious: true,
|
||||
convinced: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Close the database connection
|
||||
await prisma.$disconnect();
|
||||
|
||||
// Assign roles to role snowflakes
|
||||
const roles = [];
|
||||
|
||||
if (roleQuery === null) {
|
||||
roles.push('');
|
||||
return roles;
|
||||
}
|
||||
if (roleQuery.vegan) {
|
||||
roles.push(IDs.roles.vegan.vegan);
|
||||
}
|
||||
if (roleQuery.trusted) {
|
||||
roles.push(IDs.roles.trusted);
|
||||
}
|
||||
if (roleQuery.activist) {
|
||||
roles.push(IDs.roles.vegan.activist);
|
||||
}
|
||||
if (roleQuery.plus) {
|
||||
roles.push(IDs.roles.vegan.plus);
|
||||
}
|
||||
if (roleQuery.notVegan) {
|
||||
roles.push(IDs.roles.nonvegan.nonvegan);
|
||||
}
|
||||
if (roleQuery.vegCurious) {
|
||||
roles.push(IDs.roles.nonvegan.vegCurious);
|
||||
}
|
||||
if (roleQuery.convinced) {
|
||||
roles.push(IDs.roles.nonvegan.convinced);
|
||||
}
|
||||
|
||||
return roles;
|
||||
}
|
||||
99
src/utils/database/sus.ts
Normal file
99
src/utils/database/sus.ts
Normal file
@ -0,0 +1,99 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
export async function addToDatabase(userId: string, modId: string, message: string) {
|
||||
// Initialise the database connection
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Add the user to the database
|
||||
await prisma.sus.create({
|
||||
data: {
|
||||
user: {
|
||||
connect: {
|
||||
id: userId,
|
||||
},
|
||||
},
|
||||
mod: {
|
||||
connect: {
|
||||
id: modId,
|
||||
},
|
||||
},
|
||||
note: message,
|
||||
},
|
||||
});
|
||||
|
||||
// Close the database connection
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
|
||||
// Get a list of sus notes from the user
|
||||
export async function findNotes(userId: string, active: boolean) {
|
||||
// Initialise the database connection
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Query to get the specific user's sus notes
|
||||
const note = await prisma.sus.findMany({
|
||||
where: {
|
||||
userId,
|
||||
active,
|
||||
},
|
||||
});
|
||||
|
||||
// Close the database connection
|
||||
await prisma.$disconnect();
|
||||
return note;
|
||||
}
|
||||
|
||||
// Get one note from the id
|
||||
export async function getNote(noteId: number) {
|
||||
// Initialise the database connection
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Query to get the specific user's sus notes
|
||||
const note = await prisma.sus.findUnique({
|
||||
where: {
|
||||
id: noteId,
|
||||
},
|
||||
});
|
||||
|
||||
// Close the database connection
|
||||
await prisma.$disconnect();
|
||||
return note;
|
||||
}
|
||||
|
||||
export async function deactivateNote(noteId: number) {
|
||||
// Initialise the database connection
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Query to deactivate the specific sus note
|
||||
await prisma.sus.update({
|
||||
where: {
|
||||
id: noteId,
|
||||
},
|
||||
data: {
|
||||
active: false,
|
||||
},
|
||||
});
|
||||
|
||||
// Close the database connection
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
|
||||
export async function deactivateAllNotes(userId: string) {
|
||||
// Initialise the database connection
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Query to deactivate the specific user's sus notes
|
||||
await prisma.sus.updateMany({
|
||||
where: {
|
||||
userId: {
|
||||
contains: userId,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
active: false,
|
||||
},
|
||||
});
|
||||
|
||||
// Close the database connection
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
230
src/utils/database/verification.ts
Normal file
230
src/utils/database/verification.ts
Normal file
@ -0,0 +1,230 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2022 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import type { GuildMember } from 'discord.js';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { updateUser } from './dbExistingUser';
|
||||
import { leaveBan } from '../verificationConfig';
|
||||
import { fibonacci } from '../mathsSeries';
|
||||
|
||||
export async function joinVerification(channelId: string, user: GuildMember) {
|
||||
// Update the user on the database with the current roles they have
|
||||
await updateUser(user);
|
||||
|
||||
// Initialises Prisma Client
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
await prisma.verify.create({
|
||||
data: {
|
||||
id: channelId,
|
||||
user: {
|
||||
connect: {
|
||||
id: user.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Close database connection
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
|
||||
export async function startVerification(channelId: string) {
|
||||
// Initialises Prisma Client
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
await prisma.verify.update({
|
||||
where: {
|
||||
id: channelId,
|
||||
},
|
||||
data: {
|
||||
startTime: new Date(),
|
||||
},
|
||||
});
|
||||
|
||||
// Close database connection
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
|
||||
export async function getUser(channelId: string) {
|
||||
// Initialises Prisma Client
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Get the snowflake of the user verifying
|
||||
const user = await prisma.verify.findUnique({
|
||||
where: {
|
||||
id: channelId,
|
||||
},
|
||||
select: {
|
||||
userId: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Close database connection
|
||||
await prisma.$disconnect();
|
||||
|
||||
// Check the user could be found
|
||||
if (user === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Return the user's snowflake
|
||||
return user.userId;
|
||||
}
|
||||
|
||||
export async function finishVerification(
|
||||
channelId: string,
|
||||
verifierId: string,
|
||||
info: {
|
||||
page: number,
|
||||
find: {
|
||||
reason: number,
|
||||
where: number
|
||||
},
|
||||
length: number,
|
||||
reasoning: number,
|
||||
life: number,
|
||||
food: number,
|
||||
roles: {
|
||||
vegan: boolean,
|
||||
activist: boolean,
|
||||
trusted: boolean,
|
||||
vegCurious: boolean,
|
||||
convinced: boolean
|
||||
} },
|
||||
) {
|
||||
// Initialises Prisma Client
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// TODO potentially add an incomplete tracker?
|
||||
await prisma.verify.update({
|
||||
where: {
|
||||
id: channelId,
|
||||
},
|
||||
data: {
|
||||
verifier: {
|
||||
connect: {
|
||||
id: verifierId,
|
||||
},
|
||||
},
|
||||
finishTime: new Date(),
|
||||
// Roles
|
||||
vegan: info.roles.vegan,
|
||||
activist: info.roles.activist,
|
||||
trusted: info.roles.trusted,
|
||||
vegCurious: info.roles.vegCurious,
|
||||
convinced: info.roles.convinced,
|
||||
// Statistics
|
||||
reason: info.find.reason,
|
||||
where: info.find.where,
|
||||
length: info.length,
|
||||
reasoning: info.reasoning,
|
||||
life: info.life,
|
||||
food: info.food,
|
||||
},
|
||||
});
|
||||
|
||||
// Close database connection
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
|
||||
// Checks if verification was complete
|
||||
export async function checkFinish(channelId: string) {
|
||||
// Initialises Prisma Client
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Get the snowflake of the user verifying
|
||||
const finish = await prisma.verify.findUnique({
|
||||
where: {
|
||||
id: channelId,
|
||||
},
|
||||
select: {
|
||||
finishTime: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Close database connection
|
||||
await prisma.$disconnect();
|
||||
|
||||
// Checks if query returned is null
|
||||
if (finish === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Return if a finish time has been set meaning verification is complete
|
||||
return finish.finishTime !== null;
|
||||
}
|
||||
|
||||
// Counts how many times the user has not had a verifier join their VC before leaving
|
||||
export async function countIncomplete(userId: string) {
|
||||
// Initialises Prisma Client
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Count how many times the user has not completed a verification
|
||||
const incompleteCount = await prisma.verify.count({
|
||||
where: {
|
||||
userId,
|
||||
finishTime: null,
|
||||
},
|
||||
});
|
||||
|
||||
// Close the database connection
|
||||
await prisma.$disconnect();
|
||||
|
||||
return incompleteCount;
|
||||
}
|
||||
|
||||
// Gets the amount of time left on the block
|
||||
export async function blockTime(userId: string) {
|
||||
// Initialises Prisma Client
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Count how many times the user has not completed a verification
|
||||
const verification = await prisma.verify.findFirst({
|
||||
where: {
|
||||
userId,
|
||||
},
|
||||
orderBy: {
|
||||
id: 'desc',
|
||||
},
|
||||
});
|
||||
|
||||
// Close the database connection
|
||||
await prisma.$disconnect();
|
||||
|
||||
if (verification === null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If user finished verification
|
||||
if (verification.finishTime !== null) {
|
||||
// Activist role
|
||||
if (verification.activist) {
|
||||
return 0;
|
||||
}
|
||||
const timeOff = new Date().getTime() - verification.finishTime.getTime();
|
||||
return ((verification.vegan || verification.convinced) ? 604800000 : 1814400000) - timeOff;
|
||||
}
|
||||
|
||||
// Timeouts
|
||||
const count = await countIncomplete(verification.userId) % (leaveBan + 1);
|
||||
const timeOff = new Date().getTime() - verification.joinTime.getTime();
|
||||
// Creates the length of the time for the ban
|
||||
return (fibonacci(count) * 3600_000) - timeOff;
|
||||
}
|
||||
@ -1,87 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2022 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import type { GuildMember } from 'discord.js';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import IDs from './ids';
|
||||
|
||||
// Checks if the user exists on the database
|
||||
export async function userExists(user: GuildMember) {
|
||||
// Initialises Prisma Client
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Counts if the user is on the database by their snowflake
|
||||
const userQuery = await prisma.user.count({
|
||||
where: {
|
||||
id: user.id,
|
||||
},
|
||||
});
|
||||
|
||||
// Close the database connection
|
||||
await prisma.$disconnect();
|
||||
|
||||
// If the user is found on the database, then return true, otherwise, false.
|
||||
return userQuery > 0;
|
||||
}
|
||||
|
||||
// Adds the user to the database if they were already on the server before the bot/database
|
||||
export async function addExistingUser(user: GuildMember) {
|
||||
// Initialises Prisma Client
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Counts if the user is on the database by their snowflake
|
||||
const userQuery = await prisma.user.count({
|
||||
where: {
|
||||
id: user.id,
|
||||
},
|
||||
});
|
||||
|
||||
// If the user is already in the database
|
||||
if (userQuery > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Checks what roles the user has
|
||||
const hasVegan = user.roles.cache.has(IDs.roles.vegan.vegan);
|
||||
const hasActivist = user.roles.cache.has(IDs.roles.vegan.activist);
|
||||
const hasPlus = user.roles.cache.has(IDs.roles.vegan.plus);
|
||||
const hasNotVegan = user.roles.cache.has(IDs.roles.nonvegan.nonvegan);
|
||||
const hasVegCurious = user.roles.cache.has(IDs.roles.nonvegan.vegCurious);
|
||||
const hasConvinced = user.roles.cache.has(IDs.roles.nonvegan.convinced);
|
||||
const hasTrusted = user.roles.cache.has(IDs.roles.trusted);
|
||||
const hasMuted = user.roles.cache.has(IDs.roles.restrictions.muted);
|
||||
|
||||
// Create the user in the database
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
id: user.id,
|
||||
vegan: hasVegan,
|
||||
trusted: hasTrusted,
|
||||
activist: hasActivist,
|
||||
plus: hasPlus,
|
||||
notVegan: hasNotVegan,
|
||||
vegCurious: hasVegCurious,
|
||||
convinced: hasConvinced,
|
||||
muted: hasMuted,
|
||||
},
|
||||
});
|
||||
|
||||
// Close the database connection
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
@ -60,15 +60,28 @@ const devIDs = {
|
||||
channels: {
|
||||
information: {
|
||||
news: '999431676058927247',
|
||||
conduct: '999431676058927248',
|
||||
roles: '999431676058927250',
|
||||
},
|
||||
staff: {
|
||||
coordinators: '999431676058927254',
|
||||
standup: '999431676289622183',
|
||||
verifiers: '999431677006860411',
|
||||
},
|
||||
dietSupport: {
|
||||
info: '999431677006860417',
|
||||
introduction: '999431677325615184',
|
||||
main: '999431677325615185',
|
||||
},
|
||||
nonVegan: {
|
||||
general: '999431677325615189',
|
||||
},
|
||||
vegan: {
|
||||
general: '999431677535338575',
|
||||
},
|
||||
activism: {
|
||||
activism: '999431678214807604',
|
||||
},
|
||||
diversity: {
|
||||
women: '999431679053660187',
|
||||
lgbtqia: '999431679053660188',
|
||||
|
||||
34
src/utils/formatter.ts
Normal file
34
src/utils/formatter.ts
Normal file
@ -0,0 +1,34 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2022 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import type { Snowflake } from 'discord-api-types/globals';
|
||||
|
||||
export function rolesToString(roles: Snowflake[]) {
|
||||
let output = '';
|
||||
|
||||
roles.forEach((role) => {
|
||||
output += `<@&${role}>`;
|
||||
});
|
||||
|
||||
if (output.length === 0) {
|
||||
output = 'None';
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
@ -52,23 +52,39 @@ let IDs = {
|
||||
moderator: '826157475815489598',
|
||||
trialModerator: '982074555596152904',
|
||||
verifier: '871802735031373856',
|
||||
trialVerifier: '982635638010572850',
|
||||
},
|
||||
stageHost: '854893757593419786',
|
||||
patron: '765370219207852055',
|
||||
patreon: '993848684640997406',
|
||||
verifyingAsVegan: '854725899576279060',
|
||||
verifyBlock: '1032765019269640203',
|
||||
},
|
||||
channels: {
|
||||
information: {
|
||||
news: '866000393259319306',
|
||||
conduct: '990728521531920385',
|
||||
roles: '990761562199457813',
|
||||
},
|
||||
staff: {
|
||||
coordinators: '1006240682505142354',
|
||||
standup: '996009201237233684',
|
||||
verifiers: '873215538627756072',
|
||||
},
|
||||
dietSupport: {
|
||||
info: '993891104346873888',
|
||||
introduction: '993272252743286874',
|
||||
main: '822665615612837918',
|
||||
},
|
||||
nonVegan: {
|
||||
general: '798967615636504657',
|
||||
},
|
||||
vegan: {
|
||||
general: '787738272616808509',
|
||||
},
|
||||
activism: {
|
||||
activism: '730907954877956179',
|
||||
},
|
||||
diversity: {
|
||||
women: '938808963544285324',
|
||||
lgbtqia: '956224226556272670',
|
||||
@ -77,6 +93,7 @@ let IDs = {
|
||||
},
|
||||
},
|
||||
categories: {
|
||||
verification: '797505409073676299',
|
||||
diversity: '933078380394459146',
|
||||
},
|
||||
};
|
||||
|
||||
34
src/utils/mathsSeries.ts
Normal file
34
src/utils/mathsSeries.ts
Normal file
@ -0,0 +1,34 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2022 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// Created because Stove loves Fibonacci sequences
|
||||
// A fibonacci sequence where n = 0 => 1
|
||||
export function fibonacci(position: number) {
|
||||
let previous = 0;
|
||||
let next = 1;
|
||||
let tempNext;
|
||||
|
||||
for (let i = 0; i < position; i += 1) {
|
||||
tempNext = next + previous;
|
||||
previous = next;
|
||||
next = tempNext;
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
141
src/utils/verificationConfig.ts
Normal file
141
src/utils/verificationConfig.ts
Normal file
@ -0,0 +1,141 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/*
|
||||
Animal Rights Advocates Discord Bot
|
||||
Copyright (C) 2022 Anthony Berg
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// The maximum amount of verification VCs there can be
|
||||
export const maxVCs = 10;
|
||||
|
||||
// The maximum amount of leaving bans before time resets
|
||||
export const leaveBan = 8;
|
||||
|
||||
export const questionInfo = [
|
||||
{
|
||||
question: 'Welcome to Animal Rights Advocates! How did you find the server?',
|
||||
buttons: [
|
||||
'Search',
|
||||
'Friend',
|
||||
'YouTube',
|
||||
'Another Server',
|
||||
'Vegan Org',
|
||||
],
|
||||
},
|
||||
{
|
||||
question: 'How long have you been vegan?',
|
||||
buttons: [
|
||||
'<1 month',
|
||||
'1-2 months',
|
||||
'3-6 months',
|
||||
'6 months - 1 year',
|
||||
'1-2 years',
|
||||
'2+ years',
|
||||
],
|
||||
},
|
||||
{
|
||||
question: 'Ask the user why they went vegan and to define veganism.\n'
|
||||
+ 'Do they cite ethical concerns and abstinence from at least meat, dairy, eggs, leather, and fur?',
|
||||
buttons: [
|
||||
'Yes',
|
||||
'Yes with prompting',
|
||||
'No',
|
||||
],
|
||||
},
|
||||
{
|
||||
question: 'Ask the user about their life as a vegan, including things like watching documentaries or social media content and interactions with family and friends. What are their stories like?',
|
||||
buttons: [
|
||||
'Believable',
|
||||
'Unbelievable',
|
||||
'Short',
|
||||
],
|
||||
},
|
||||
{
|
||||
question: 'Ask the user about food and nutrition. Do they seem to know how to live as a vegan?',
|
||||
buttons: [
|
||||
'Dietitian / Chef',
|
||||
'Acceptable',
|
||||
'Salads / Smoothies',
|
||||
'No clue',
|
||||
],
|
||||
},
|
||||
{
|
||||
question: 'Do you think this user is definitely vegan?',
|
||||
buttons: [
|
||||
'Yes',
|
||||
'No',
|
||||
],
|
||||
},
|
||||
{
|
||||
question: 'Offer to ask questions for Activist. Do you think they should get it?',
|
||||
buttons: [
|
||||
'Yes',
|
||||
'No',
|
||||
],
|
||||
},
|
||||
{
|
||||
question: 'Do some activism, asking Activist questions. Now which role should they get?',
|
||||
buttons: [
|
||||
'Vegan',
|
||||
'Convinced',
|
||||
'Non-vegan',
|
||||
],
|
||||
},
|
||||
{
|
||||
question: 'Should this user get Veg Curious?',
|
||||
buttons: [
|
||||
'Yes',
|
||||
'No',
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const serverFind = [
|
||||
// From a friend
|
||||
{
|
||||
question: 'Ask for username and indicate',
|
||||
buttons: [
|
||||
'Vegan',
|
||||
'Non-Vegan',
|
||||
'Unknown',
|
||||
],
|
||||
},
|
||||
// From a video
|
||||
{
|
||||
question: 'Ask what video',
|
||||
buttons: [
|
||||
'Troll video',
|
||||
'Our content',
|
||||
'Other',
|
||||
],
|
||||
},
|
||||
// From another server
|
||||
{
|
||||
question: 'Ask which server',
|
||||
buttons: [
|
||||
'Vegan',
|
||||
'Debate',
|
||||
'Other',
|
||||
],
|
||||
},
|
||||
// From a vegan organisation
|
||||
{
|
||||
question: 'Ask which one',
|
||||
buttons: [
|
||||
'Vegan Hacktivists',
|
||||
'Other',
|
||||
],
|
||||
},
|
||||
];
|
||||
@ -12,7 +12,7 @@
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
"target": "es2021", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||
@ -101,7 +101,7 @@
|
||||
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
// "skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user