Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 0 additions & 29 deletions .env.sample

This file was deleted.

226 changes: 226 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
# Tommy Bot

Creator: @MaxZK [R-DEV]Max_

Maintainer: @emilekm [R-CON]cassius23

## Requirements

- Discord server
- FTP server
- HTTP server
- Node.js

If you're using Windows you might need to follow the installation instructions for [node-gyp](https://github.com/nodejs/node-gyp?tab=readme-ov-file#on-windows).

## Running

```sh
# Install dependencies
npm install
# Run the bot
npm start
```

## Features

Below is a list of features the bot provides along with configuration for each of the features.

The full configuration can be found in `config.yaml.sample`.
Copy the file to `config.yaml` and fill in the required fields.

### PRISM

PRISM is used for running in-game commands, reading chat and alerts.

The general channel is for all chat messages coming into PRISM.
There is a special feed for teamkills, which you can direct to a specific channel.

```yaml
prism:
auth:
ip:
port:
username:
password:
generalChannelID:
teamkillChannelID:
```

### Demos

Demos are uploaded using FTP at the round end. After upload the bot will post a message with the demo links.

Additionally the bot will upload JSON files produced by the server at the round end.

```yaml
demos:
channelID:
ftp:
host:
username:
password:

# URL where tracker viewer can be accesseded
# Domain needs to be the same as the one used for prdemo (for CORS)
# Example: https://demos.myserver.gg/realitytracker/
trackerViewerUrl:

# source is the path where the files can be found
# destination is the path where the files will be uploaded via FTP
# url is the URL where the files can be accessed on the internet

bf2:
# Example: /pr/server/mods/pr/demos/
source:
# Example: demos/bf2/
destination:
# Example: https://demos.myserver.gg/bf2/
url:

pr:
# Example: /pr/server/demos/
source:
# Example: demos/pr/
destination:
# Example: https://demos.myserver.gg/pr/
url:

json:
# Example: /pr/server/json/
source:
# Example: demos/json/
destination:
```

### Logs

Join, commands and ban logs are read from files and posted to Discord channels.

Additionally tickets log is used to determine winner of the round.

- `bans` - `mods/pr/settings/banlist_info.log`
- `commands` - `admin/logs/commandlog.log`
- `joins` - `admin/logs/joinlog.log`
- `tickets` - `admin/logs/tickets.log`

Paths need to be absolute.

```yaml
logs:
bans:
# {SV_DIR}/mods/pr/settings/banlist_info.log
path:
publicChannelID:
privateChannelID:
commands:
# {SV_DIR}/admin/logs/ra_adminlog.txt
path:
publicChannelID:
privateChannelID:
joinlog:
# {SV_DIR}/admin/logs/joinlog.log
path:
privateChannelID:
tickets:
# {SV_DIR}/admin/logs/tickets.log
path:
```

### Server information

Server information is fetched from PRSPY and posted as two channels names.

First channel is the map name, second contains other details: gamemode, layer and player count.

It is recommended to create a voice channel that no one can join, but everyone can see.

```yaml
prspy:
# PRSPY server ID
# Open your server on PRSPY and grab the ID from the URL
# This ID changes on IP or port change (and probably some other things)
# Example: ceab9bd17f51d08acc4da77120574bd57e248c98
id:
mapChannelID:
detailsChannelID:
```

## Interactive features

Commands can render buttons for users to interact with the bot or can have a direct action.
Most commands side effect is some kind of log/message on Discord.
Most of them have two types: public and private - first providing less information that the latter.

### ~~Admin Hash ID~~

*This command is currently disabled, this functionality is poorly implemented and needs to be reworked. **Contributions are welcome**.*

### Contact admin

**Command**: `/contactadmin`

This command will render 3 buttons:

- Admin Application
- Ban Appeal
- Report Incident

These 3 buttons will open a form where the user can fill in the required information.

The channel where the form is posted needs to be a Forum channel.
A sent form will be posted to the configured channel as a Post.
Specified tags will be assigned to the post.

It is common to create one Forum channel and direct all forms to it with different tags.

```yaml
contactadmin:
# The role that should be tagged when a form is posted
adminRoleID:
application:
public:
channelID:
tagIDs: []
private:
channelID:
tagIDs: []
appeal:
public:
channelID:
tagIDs: []
private:
channelID:
tagIDs: []
report:
public:
channelID:
tagIDs: []
private:
channelID:
tagIDs: []
```

### Ban and unban player

**Commands**:
- `/prban [hashid] [prname] [durationvalue] [durationformat] [reason] <attachment>`
- `/prunban [hashid] [reason]`

This command uses PRISM to ban/unban a player.

It posts a message to two configured channels.

Configuration:
```yaml
prban:
firsChannelID:
secondChannelID:
prunban:
firsChannelID:
secondChannelID:
```

### Run admin command on the server

**Command**: `/prprism [command]`
Binary file added assets/hash-id.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 3 additions & 4 deletions bot.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import dotenv from "dotenv";
dotenv.config();

import fs from "fs";
import { Client, GatewayIntentBits, Events, Collection } from "discord.js";

import config from "./config.js";
import { getAdmins } from "./helpers/getAdmins.js";


const client = new Client({
intents: [
GatewayIntentBits.Guilds,
Expand All @@ -15,7 +14,7 @@ const client = new Client({
],
});

client.login(process.env.TOKEN);
client.login(config.discord.token);

client.once(Events.ClientReady, () => {
client.commands = new Collection();
Expand Down
2 changes: 1 addition & 1 deletion commands/information/adminhashid.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default {
"Prompt buttons for admins to enter their hash corectly...."
),
async execute(interaction) {
const file = new AttachmentBuilder("logs/images/flags/hash-id.gif");
const file = new AttachmentBuilder("assets/hash-id.gif");
const embed = new EmbedBuilder()
.setColor("#0074ba")
.setTitle("🔷 Admin Hash-ID")
Expand Down
10 changes: 5 additions & 5 deletions commands/information/contactadmin.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ export default {
.setName("contactadmin")
.setDescription("Prompt buttons for Ban Appeals, Admin Application and Reports"),
async execute(interaction) {
const file = new AttachmentBuilder("logs/images/flags/hash-id.gif");
const file = new AttachmentBuilder("assets/hash-id.gif");
const embed = new EmbedBuilder()
.setColor("#e98f27")
.setTitle("🔶 Contact Admins")
.setDescription(`
**Click** one of the **buttons** below to **either**:\n
> 🔵 **Apply** for an admin role on our Project Reality server.\n>
> 🟢 **Appeal** a ban from our Project Reality server.\n>
> 🔵 **Apply** for an admin role on our Project Reality server.\n>
> 🟢 **Appeal** a ban from our Project Reality server.\n>
> 🔴 **Report** an incident that happened on our Discord or Project Reality servers\n\n
If you are having issues finding your Hash-ID check the .GIF image bellow to learn how to find it.`)
.setImage("attachment://hash-id.gif");
Expand All @@ -40,8 +40,8 @@ export default {
);
await interaction.reply({
embeds: [embed],
components: [row],
components: [row],
files: [file]
});
},
};
};
12 changes: 6 additions & 6 deletions commands/moderation/prban.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { SlashCommandBuilder, AttachmentBuilder, EmbedBuilder } from "discord.js";
import { ServerCommands } from "../../functions/handlePRISM.js";


import config from "../../config.js";
import { ServerCommands } from "../../functions/handlePRISM.js";

function sleep(ms) {
return new Promise((resolve) => {
Expand Down Expand Up @@ -79,8 +79,8 @@ export default {
.setTimestamp(new Date())
.setFooter({ text: "DISCORD" });
await interaction.reply({ embeds: [embedReply] });
await interaction.member.guild.channels.cache.get("995387208947204257").send({ embeds: [embedReply] });
await interaction.member.guild.channels.cache.get("995520998554218557").send({ embeds: [embedReply] });
await interaction.member.guild.channels.cache.get(config.prban.firstChannelID).send({ embeds: [embedReply] });
await interaction.member.guild.channels.cache.get(config.prban.secondChannelID).send({ embeds: [embedReply] });
} else {
const file = new AttachmentBuilder(attachment.attachment);
console.log(file.attachment.split("/")[-1]);
Expand All @@ -92,8 +92,8 @@ export default {
.setTimestamp(new Date())
.setFooter({ text: "DISCORD" });
await interaction.reply({ embeds: [embedReply], files: [file] });
await interaction.member.guild.channels.cache.get("995387208947204257").send({ embeds: [embedReply], files: [file] });
await interaction.member.guild.channels.cache.get("995520998554218557").send({ embeds: [embedReply], files: [file] });
await interaction.member.guild.channels.cache.get(config.prban.firstChannelID).send({ embeds: [embedReply], files: [file] });
await interaction.member.guild.channels.cache.get(config.prban.secondChannelID).send({ embeds: [embedReply], files: [file] });
}
},
};
8 changes: 4 additions & 4 deletions commands/moderation/prprism.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ export default {
data: new SlashCommandBuilder()
.setName("prprism")
.setDescription("Execute a command from PRISM")
.addStringOption(subcommand => subcommand
.setName("hashid")
.addStringOption(option => option
.setName("command")
.setDescription("Command as if you typing it in PRISM or in-game")
.setRequired(true)),
async execute(interaction) {
writeSayToPrism(`${interaction.options.getString("hashid")} - Discord User ${interaction.user.username}`);
writeSayToPrism(`${interaction.options.getString("command")} - Discord User ${interaction.user.username}`);
await interaction.reply({
content: `addKeyToBanList ${interaction.options.getString("hashid")} ${interaction.options.getString("duration")} ${interaction.options.getString("reason")}`,
content: `Ran command \`${interaction.options.getString("command")}\` using PRISM.`,
ephemeral: true
});
},
Expand Down
6 changes: 4 additions & 2 deletions commands/moderation/prunban.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { SlashCommandBuilder, EmbedBuilder } from "discord.js";

import config from "../../config.js";
import { ServerCommands } from "../../functions/handlePRISM.js";

export default {
Expand Down Expand Up @@ -27,7 +29,7 @@ export default {
.setTimestamp(new Date())
.setFooter({ text: "DISCORD" });
await interaction.reply({ embeds: [embedReply] });
await interaction.member.guild.channels.cache.get("995387208947204257").send({ embeds: [embedReply] });
await interaction.member.guild.channels.cache.get("995520998554218557").send({ embeds: [embedReply] });
await interaction.member.guild.channels.cache.get(config.prunban.firstChannelID).send({ embeds: [embedReply] });
await interaction.member.guild.channels.cache.get(config.prunban.secondChannelID).send({ embeds: [embedReply] });
},
};
Loading