mirror of
https://github.com/arthur-pbty/xiao.git
synced 2026-06-03 15:07:42 +02:00
Public :)
This commit is contained in:
@@ -3,8 +3,7 @@
|
||||
# Xiao
|
||||
[](https://travis-ci.org/dragonfire535/xiao)
|
||||
[](https://www.paypal.me/dragonfire535)
|
||||
|
||||
> This bot is not available for invite.
|
||||
[](https://discord.gg/mTr83zt)
|
||||
|
||||
Xiao is a Discord bot coded in JavaScript with
|
||||
[discord.js](https://discord.js.org/) using the
|
||||
@@ -13,6 +12,8 @@ Xiao is a Discord bot coded in JavaScript with
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Invite](#invite)
|
||||
- [Permissions](#permissions)
|
||||
- [Fun Information](#fun-information)
|
||||
- [Installing](#installing)
|
||||
* [Before You Begin](#before-you-begin)
|
||||
@@ -44,6 +45,35 @@ Xiao is a Discord bot coded in JavaScript with
|
||||
- [Licensing](#licensing)
|
||||
- [Credit](#credit)
|
||||
|
||||
## Invite
|
||||
- [Home Server](https://discord.gg/mTr83zt)
|
||||
- [Invite](https://discordapp.com/api/oauth2/authorize?client_id=278305350804045834&permissions=104721601&scope=bot)
|
||||
|
||||
## Permissions
|
||||
|
||||
Xiao needs several permissions to be able to do what she does. Below
|
||||
is every permission Xiao asks for, and what commands you lose if you
|
||||
don't grant that permission.
|
||||
|
||||
- **Create Instant Invite** is needed to allow owners to join your server to test if needed.
|
||||
* You lose no commands by turning this off, but you might hinder support.
|
||||
- **View Audit Log** is not needed yet, but is something Xiao might utilize in the future.
|
||||
- **Change Nickname** is not _needed_, but is included as a basic permission.
|
||||
- **View Channels** is required for every single command to work.
|
||||
- **Send Messages** is required for every single command to work.
|
||||
- **Manage Messages** allows Xiao to use the `prune` command.
|
||||
- **Embed Links** is required to allow commands that send embeds to work. Too many commands to list use it.
|
||||
- **Attach Files** is required to allow commands that send files to work. Too many commands to list use it.
|
||||
- **Read Message History** allows Xiao to use the `first-message` and `prune` commands.
|
||||
- **Use External Emojis** allows Xiao to use custom emoji in certain commands.
|
||||
* While the commands benefit from it, it is not required for the commands to work.
|
||||
- **Add Reactions** allows Xiao to use commands that add reactions to messages in certain commands.
|
||||
* While the commands benefit from it, it is not requried for the commands to work.
|
||||
- **Connect** allows Xiao to join voice channels. It is required for `mafia` to work.
|
||||
- **Speak** allows Xiao to speak in voice channels. It is required for `mafia` to work.
|
||||
- **Use Voice Activity** is not needed, but is activiated just in case so `mafia` won't break.
|
||||
|
||||
|
||||
## Fun Information
|
||||
- 350+ Commands
|
||||
- 17,000+ lines of JavaScript
|
||||
@@ -92,7 +122,7 @@ Xiao is a Discord bot coded in JavaScript with
|
||||
|
||||
## Commands
|
||||
|
||||
Total: 363
|
||||
Total: 361
|
||||
|
||||
### Utility:
|
||||
|
||||
@@ -332,8 +362,6 @@ Total: 363
|
||||
* **battle:** Engage in a turn-based battle against another user or the AI.
|
||||
* **emoji-emoji-revolution:** Can you type arrow emoji faster than anyone else has ever typed them before?
|
||||
* **gunfight:** Engage in a western gunfight against another user. High noon.
|
||||
* **mafia-role:** Displays your current role during Mafia games.
|
||||
* **mafia:** Who is the Mafia? Who is the detective? Will the Mafia kill them all?
|
||||
* **quiz-duel:** Answer a series of quiz questions against an opponent.
|
||||
* **russian-roulette:** Who will pull the trigger and die first?
|
||||
* **tic-tac-toe:** Play a game of tic-tac-toe with another user.
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
[
|
||||
"CREATE_INSTANT_INVITE",
|
||||
"ADD_REACTIONS",
|
||||
"VIEW_AUDIT_LOG",
|
||||
"VIEW_CHANNEL",
|
||||
"SEND_MESSAGES",
|
||||
"MANAGE_MESSAGES",
|
||||
"EMBED_LINKS",
|
||||
"ATTACH_FILES",
|
||||
"READ_MESSAGE_HISTORY",
|
||||
"USE_EXTERNAL_EMOJIS",
|
||||
"CONNECT",
|
||||
"SPEAK",
|
||||
"USE_VAD",
|
||||
"CHANGE_NICKNAME"
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,22 +0,0 @@
|
||||
const Command = require('../../structures/Command');
|
||||
|
||||
module.exports = class MafiaRoleCommand extends Command {
|
||||
constructor(client) {
|
||||
super(client, {
|
||||
name: 'mafia-role',
|
||||
aliases: ['mafia-me'],
|
||||
group: 'mp-games',
|
||||
memberName: 'mafia-role',
|
||||
description: 'Displays your current role during Mafia games.'
|
||||
});
|
||||
}
|
||||
|
||||
run(msg) {
|
||||
const games = this.client.games.filter(game => game.players.has(msg.author.id) && game.name === 'mafia');
|
||||
if (!games.size) return msg.reply('You aren\'t a member of any games.');
|
||||
return msg.reply(games.map(game => {
|
||||
const { role } = game.players.get(msg.author.id);
|
||||
return `**${game.channel.guild.name} (${game.channel}):** ${role}`;
|
||||
}).join('\n'));
|
||||
}
|
||||
};
|
||||
@@ -1,90 +0,0 @@
|
||||
const Command = require('../../structures/Command');
|
||||
const Game = require('../../structures/mafia/Game');
|
||||
const { verify } = require('../../util/Util');
|
||||
const storyCount = 21;
|
||||
|
||||
module.exports = class MafiaCommand extends Command {
|
||||
constructor(client) {
|
||||
super(client, {
|
||||
name: 'mafia',
|
||||
group: 'mp-games',
|
||||
memberName: 'mafia',
|
||||
description: 'Who is the Mafia? Who is the detective? Will the Mafia kill them all?',
|
||||
guildOnly: true,
|
||||
credit: [
|
||||
{
|
||||
name: 'TheMicroKill#9052',
|
||||
reason: 'Voice Acting'
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
async run(msg) {
|
||||
const current = this.client.games.get(msg.channel.id);
|
||||
if (current) return msg.reply(`Please wait until the current game of \`${current.name}\` is finished.`);
|
||||
const voiceChannel = msg.member.voice.channel;
|
||||
if (!voiceChannel) return msg.reply('You must be in a voice channel to start a game.');
|
||||
for (const member of voiceChannel.members.values()) await msg.guild.members.fetch(member.id);
|
||||
if (voiceChannel.members.size > 15) return msg.reply('Please do not have more than 15 players.');
|
||||
if (voiceChannel.members.size < 6) return msg.reply('Please have at least 5 players before starting.');
|
||||
const game = new Game(this.client, msg.channel, voiceChannel);
|
||||
this.client.games.set(msg.channel.id, game);
|
||||
try {
|
||||
await game.init();
|
||||
await game.generate(voiceChannel.members.filter(m => !m.user.bot).map(m => m.user));
|
||||
await game.playAudio('init');
|
||||
await game.playAudio('rule-ask');
|
||||
await msg.say('Type `yes` to hear a rule explanation.');
|
||||
const rules = await verify(msg.channel, msg.author);
|
||||
if (rules) await game.playAudio('rules');
|
||||
while (!game.shouldEnd) {
|
||||
let killed = null;
|
||||
await game.playAudio(`night-${game.turn}`);
|
||||
await game.playAudio('mafia');
|
||||
const mafia = game.players.filter(p => p.role === 'mafia');
|
||||
const choices = await Promise.all(mafia.map(player => player.dmRound()));
|
||||
const randomizer = choices.filter(c => c !== null);
|
||||
if (randomizer.length) killed = game.players.get(randomizer[Math.floor(Math.random() * randomizer.length)]);
|
||||
await game.playAudio('mafia-decision-made');
|
||||
const detective = game.players.find(p => p.role === 'detective');
|
||||
if (detective) {
|
||||
await game.playAudio('detective');
|
||||
await detective.dmRound();
|
||||
await game.playAudio('detective-decision-made');
|
||||
}
|
||||
await game.playAudio(`day-${game.turn}`);
|
||||
if (killed) {
|
||||
const story = Math.floor(Math.random() * storyCount) + 1;
|
||||
await game.playAudio(`story-${story}`);
|
||||
await game.playAudio('reveal-deceased');
|
||||
await msg.say(`Deceased: **${killed}**`);
|
||||
game.players.delete(killed.id);
|
||||
} else {
|
||||
await game.playAudio('no-deceased');
|
||||
}
|
||||
await game.playAudio('vote');
|
||||
const playersArr = Array.from(game.players.values());
|
||||
const votes = await game.getVotes(playersArr);
|
||||
if (!votes) {
|
||||
await game.playAudio('no-votes');
|
||||
continue;
|
||||
}
|
||||
const hanged = game.getHanged(votes, playersArr);
|
||||
await game.playAudio('hanged');
|
||||
await msg.say(`Hanged: **${hanged.user}**`);
|
||||
game.players.delete(hanged.id);
|
||||
++game.turn;
|
||||
}
|
||||
const mafia = game.players.find(p => p.role === 'mafia');
|
||||
if (mafia) await game.playAudio('mafia-wins');
|
||||
else await game.playAudio('mafia-loses');
|
||||
await game.playAudio('credits');
|
||||
game.end();
|
||||
return null;
|
||||
} catch (err) {
|
||||
game.end();
|
||||
return msg.reply(`Oh no, an error occurred: \`${err.message}\`. Try again later!`);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,10 +1,11 @@
|
||||
const Command = require('../../structures/Command');
|
||||
const { MessageEmbed, version: djsVersion, Permissions } = require('discord.js');
|
||||
const { MessageEmbed, version: djsVersion } = require('discord.js');
|
||||
const { version: commandoVersion } = require('discord.js-commando');
|
||||
const moment = require('moment');
|
||||
require('moment-duration-format');
|
||||
const { formatNumber } = require('../../util/Util');
|
||||
const { version, dependencies } = require('../../package');
|
||||
const permissions = require('../../assets/json/permissions');
|
||||
const { XIAO_GITHUB_REPO_USERNAME, XIAO_GITHUB_REPO_NAME } = process.env;
|
||||
const source = XIAO_GITHUB_REPO_NAME && XIAO_GITHUB_REPO_USERNAME;
|
||||
|
||||
@@ -22,7 +23,7 @@ module.exports = class InfoCommand extends Command {
|
||||
}
|
||||
|
||||
async run(msg) {
|
||||
const invite = await this.client.generateInvite(Permissions.ALL);
|
||||
const invite = await this.client.generateInvite(permissions);
|
||||
const embed = new MessageEmbed()
|
||||
.setColor(0x00AE86)
|
||||
.setFooter('©2017-2020 dragonfire535#8081')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const Command = require('../../structures/Command');
|
||||
const { stripIndents } = require('common-tags');
|
||||
const { XIAO_GITHUB_REPO_NAME, XIAO_GITHUB_REPO_USERNAME } = process.env;
|
||||
const permissions = require('../../assets/json/permissions');
|
||||
|
||||
module.exports = class InviteCommand extends Command {
|
||||
constructor(client) {
|
||||
@@ -14,13 +14,14 @@ module.exports = class InviteCommand extends Command {
|
||||
});
|
||||
}
|
||||
|
||||
run(msg) {
|
||||
async run(msg) {
|
||||
const invite = await this.client.generateInvite(permissions);
|
||||
return msg.say(stripIndents`
|
||||
You cannot invite me to your server, but you can join my home server to use me:
|
||||
${this.client.options.invite || 'Coming soon...'}
|
||||
Invite me using this link:
|
||||
<${invite}>
|
||||
|
||||
You can also self-host me if you prefer:
|
||||
<https://github.com/${XIAO_GITHUB_REPO_USERNAME}/${XIAO_GITHUB_REPO_NAME}>
|
||||
Join my home server for support and announcements:
|
||||
${this.client.options.invite || 'Coming soon...'}
|
||||
`);
|
||||
}
|
||||
};
|
||||
|
||||
+1
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xiao",
|
||||
"version": "111.1.7",
|
||||
"version": "112.0.0",
|
||||
"description": "Your personal server companion.",
|
||||
"main": "Xiao.js",
|
||||
"scripts": {
|
||||
@@ -32,7 +32,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@discordjs/collection": "^0.1.5",
|
||||
"@discordjs/opus": "^0.1.0",
|
||||
"@vitalets/google-translate-api": "^3.0.0",
|
||||
"canvas": "^2.6.1",
|
||||
"cheerio": "^1.0.0-rc.3",
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
const Collection = require('@discordjs/collection');
|
||||
const path = require('path');
|
||||
const { stripIndents } = require('common-tags');
|
||||
const Player = require('./Player');
|
||||
const { shuffle } = require('../../util/Util');
|
||||
const { SUCCESS_EMOJI_ID } = process.env;
|
||||
|
||||
module.exports = class Game {
|
||||
constructor(client, channel, voiceChannel) {
|
||||
Object.defineProperty(this, 'client', { value: client });
|
||||
|
||||
this.name = 'mafia';
|
||||
this.players = new Collection();
|
||||
this.channel = channel;
|
||||
this.voiceChannelRaw = voiceChannel;
|
||||
this.connection = null;
|
||||
this.dispatcher = null;
|
||||
this.turn = 1;
|
||||
}
|
||||
|
||||
determineRoles(playerCount) {
|
||||
const roles = ['detective', 'mafia', 'mafia'];
|
||||
for (let i = 0; i < (playerCount - 3); i++) roles.push('innocent');
|
||||
return shuffle(roles);
|
||||
}
|
||||
|
||||
async generate(list) {
|
||||
const roles = this.determineRoles(list.length);
|
||||
let i = 0;
|
||||
for (const user of list) {
|
||||
try {
|
||||
await user.send(`You are ${roles[i] === 'detective' ? 'the' : 'a part of the'} **${roles[i]}**.`);
|
||||
} catch (err) {
|
||||
await this.channel.send(
|
||||
`${user}, I couldn't send a DM to you. Please allow DMs and use the \`mafia-role\` command to see your role.`
|
||||
);
|
||||
}
|
||||
const player = new Player(this, user, roles[i]);
|
||||
this.players.set(user.id, player);
|
||||
i++;
|
||||
}
|
||||
return this.players;
|
||||
}
|
||||
|
||||
async init() {
|
||||
this.connection = await this.voiceChannel.join();
|
||||
return this;
|
||||
}
|
||||
|
||||
end() {
|
||||
if (this.voiceChannel) this.voiceChannel.leave();
|
||||
this.client.games.delete(this.channel.id);
|
||||
return this;
|
||||
}
|
||||
|
||||
playAudio(id) {
|
||||
this.dispatcher = this.connection.play(path.join(__dirname, '..', '..', 'assets', 'sounds', 'mafia', `${id}.mp3`), {
|
||||
volume: 2
|
||||
});
|
||||
return new Promise((res, rej) => {
|
||||
this.dispatcher.once('finish', () => {
|
||||
this.dispatcher = null;
|
||||
return res(true);
|
||||
});
|
||||
this.dispatcher.once('error', err => {
|
||||
this.dispatcher = null;
|
||||
return rej(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async getVotes(playersArr) {
|
||||
await this.channel.send(stripIndents`
|
||||
Who do you think is a Mafia member? Please type the number.
|
||||
${playersArr.map((p, i) => `**${i + 1}.** ${p.user.tag}`).join('\n')}
|
||||
`);
|
||||
const voted = [];
|
||||
const filter = res => {
|
||||
if (!this.players.some(p => p.user.id === res.author.id)) return false;
|
||||
if (voted.includes(res.author.id)) return false;
|
||||
if (!playersArr[Number.parseInt(res.content, 10) - 1]) return false;
|
||||
voted.push(res.author.id);
|
||||
res.react(SUCCESS_EMOJI_ID || '✅').catch(() => null);
|
||||
return true;
|
||||
};
|
||||
const votes = await this.channel.awaitMessages(filter, {
|
||||
max: this.players.size,
|
||||
time: 90000
|
||||
});
|
||||
if (!votes.size) return null;
|
||||
return votes;
|
||||
}
|
||||
|
||||
getHanged(votes, playersArr) {
|
||||
const counts = new Collection();
|
||||
for (const vote of votes.values()) {
|
||||
const player = this.players.get(playersArr[Number.parseInt(vote.content, 10) - 1].id);
|
||||
if (counts.has(player.id)) {
|
||||
++counts.get(player.id).votes;
|
||||
} else {
|
||||
counts.set(player.id, {
|
||||
id: player.id,
|
||||
votes: 1,
|
||||
user: player.user
|
||||
});
|
||||
}
|
||||
}
|
||||
return counts.sort((a, b) => b.votes - a.votes).first();
|
||||
}
|
||||
|
||||
get shouldEnd() {
|
||||
return this.players.size < 4 && !this.players.some(p => p.role === 'mafia');
|
||||
}
|
||||
|
||||
get voiceChannel() {
|
||||
return this.connection ? this.connection.channel : this.voiceChannelRaw;
|
||||
}
|
||||
};
|
||||
@@ -1,43 +0,0 @@
|
||||
const { stripIndents } = require('common-tags');
|
||||
const questions = {
|
||||
mafia: 'Who would you like to kill?',
|
||||
detective: 'Who do you think is a Mafia member?'
|
||||
};
|
||||
|
||||
module.exports = class Player {
|
||||
constructor(game, user, role) {
|
||||
this.game = game;
|
||||
this.user = user;
|
||||
this.id = user.id;
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.user.toString();
|
||||
}
|
||||
|
||||
async dmRound() {
|
||||
const valid = Array.from(this.game.players.filter(p => p.role !== this.role).values());
|
||||
await this.user.send(stripIndents`
|
||||
${questions[this.role]} Please type the number.
|
||||
${valid.map((p, i) => `**${i + 1}.** ${p.user.tag}`).join('\n')}
|
||||
`);
|
||||
const filter = res => valid[Number.parseInt(res.content, 10) - 1];
|
||||
const decision = await this.user.dmChannel.awaitMessages(filter, {
|
||||
max: 1,
|
||||
time: 120000
|
||||
});
|
||||
if (!decision.size) {
|
||||
await this.user.send('Sorry, time is up!');
|
||||
return null;
|
||||
}
|
||||
const choice = valid[Number.parseInt(decision.first().content, 10) - 1].id;
|
||||
if (this.role === 'detective') {
|
||||
const isMafia = this.game.players.get(choice).role === 'mafia';
|
||||
await this.user.send(isMafia ? 'Yes, they are a Mafioso.' : 'No, they are not a Mafioso.');
|
||||
} else {
|
||||
await this.user.send(`**${this.game.players.get(choice).user.tag}** is your choice...`);
|
||||
}
|
||||
return choice;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user