Skip to content

Commit

Permalink
Activities rework (#174)
Browse files Browse the repository at this point in the history
* Fixed commands being disabled, had to read commandGroup ID not name

* Removed debug logs

* Improved error logging

* Bug fixes on logging statements

* Stamps small fix, did not set commands enabled after setting up stamps

* Added logging to check issues, found none, left logging

* Workshop and Activity rework

Activity is a single principle class. Workshop class extends the
activity class to add workshop functionality. !init-workshop creates
a new workshop. No activity console yet. Moved all activity related
classes to /classes/activities/ folder. Old activities class still in
use, file called old-activities.js.

* JSDoc activity class documentation

* Activity static function for role prompts

Added a static function to the Activity class to prompt the user for
the roles that can participate in the activity as it will be used by
all other subclasses when instantiating themselves. Using it on
new-activity command.

* Created the CoffeeChats classes

The coffeeChats class extends the activity class, it sets up the
coffee chats activity, it took away the group shuffle from the
activity manager. Uses an collection to store the groups.

* More functions for the coffee chats

When adding voice channels, the number of groups increases as well.
Function to reset the teams. Added a timer to prompt stating the
activity is full.

* Bug fix on logger

* init() returns self

* Rename commands to new- instead of init-. JSDoc bug fix

* new-workshop use of activity static prompt for participant roles

* Added archive category to botGuild

The archive category will be used by activities, it is created by default with the botGuild setUp() function. In the future we could not create it until needed.

* Added features property to Activity class

The features collection holds all the features of the activity, these features hold a name, description, emoji and callback. Features are presented in the activity console. When the emoji is reacted, the callback is triggered.

* Added the sendAdminConsole function to the activity class, called in init().

* Async bug fix

* Bug fixes on archive category creation

* Added features to activity

Every activity has a list of features, these features are added to the
admin console and have an emoji and a callback. The callback is triggered
when the emoji is reacted. Subclasses can add features, they must call
the super after adding the features. Activity constructor calls the
function.

* Activity delete removes the console message

* Bug fixes for all activities

* Added Voice Callback feature to activity

The voice callback will move all members in activity's voice channels to
one voice channel decided by the user. For this, added a discordServices
function that shows the user a list of channels, the user can then choose
a channel using numbers. Also, feature callbacks will get the user who
triggered the feature.

* Const used to set server loggers to log to console

* Added shuffle and role shuffle to activity

These two features will shuffle all members or those with a role
around the activity's voice channels. Role shuffle uses shuffle, shuffle
accepts a filter.

* Added logging to the admin console collector

* Moved stamp functions to stamps-manager class.

* Added distribute stamp feature to activity and removed activity manager file

* Bug fix on distribute stamp

* Added Rule Validation and Archive fix

Rule validation is what make among us was but now generalized to any
kind of activity. It locks the activity behind an emoji reaction,
the user must read the rules and react to gain access. Uses individual
permissions to work. Also fixed Archive feature.

* Clean out New Activity and commands

!new-activity now uses the new activity class. Removed all previous
activity commands that will not be used. Changed activity features embed
to put emoji in field title.

* Coffe chats init use new chooseChannel function

* Added a reaction picker, given options user reacts to choose

* Improved add/remove channels

Users can now add and remove both voice and text channels. To add a
channel, user must react, the decide between voice or text using new
reaction chooser, then give name. Workshops also ask if the channel
is for TA. To remove, user reacts and then chooses from a list by
using the discordServices channel chooser.

* Moved channel chooser to prompt class

* JSDoc bug fix

* Import fixes

* Bug fixes on chooseChannel

* Activity Code Restructure

Code was moved around to follow the following format:
- Constructor
- init
- features
- class functions
- feature callbacks

There is a comment between features and class functions. Moved the
addDefaultFeatures() call from constructor to the init() function.
Channels property has safeChannels, this channels can't be removed by
users. AddChannelHelper has an option to add the created channel to
this list.

* Coffee Chats Code Restructure

Following the activity restructure. Added the features to the features
list with a addDefaultFeatures(). Added a add team slot feature. Regular
add channel does not add team slots. Bug fixes on groupShuffle().

* Workshop Code Restructure

Followed new structure in activity class. Polls are added dynamically to
the features list in addDefaultFeatures(). Had to add an emojiName
property to the pollInfo typedef. Polling will send it to general text
or prompt for a channel if not available.

* Activity addDefaultFeatures uses workshop and coffee chats better format using one list and then foreach

* Removed ActivityCommand as it is not used any more

* More merge fixes

* More merge fixes pt 2
  • Loading branch information
JPGarCar authored Mar 1, 2021
1 parent c752ede commit ba5caa1
Show file tree
Hide file tree
Showing 31 changed files with 1,624 additions and 1,732 deletions.
569 changes: 569 additions & 0 deletions classes/activities/activity.js

Large diffs are not rendered by default.

201 changes: 201 additions & 0 deletions classes/activities/coffee-chats.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
const Activity = require("./activity");
const { MessageEmbed, TextChannel, User, GuildMember, Collection, VoiceChannel } = require('discord.js');
const { memberPrompt, chooseChannel } = require("../prompt");
const winston = require("winston");
const { sendMsgToChannel } = require("../../discord-services");

/**
* A CoffeeChat is a special activity where users join as a team. The teams are then
* scattered around in voice channels to talk with mentors or other teams.
* @class
* @extends Activity
*/
class CoffeeChats extends Activity {

/**
* Basic constructor for a coffee chats.
* @param {Activity.ActivityInfo} activityInfo
* @param {Number} numOfTeams
*/
constructor(activityInfo, numOfTeams) {
super(activityInfo);

/**
* A collection of the groups that will attend this coffee chat.
* @type {Collection<Number, GuildMember[]>} - <group number, group members as array>
*/
this.teams = new Collection();

/**
* The number of groups available in this coffee chat
* @type {Number}
*/
this.numOfTeams = numOfTeams || 0;

/**
* The channel where users join the activity.
* @type {TextChannel}
*/
this.joinActivityChannel;

/**
* The main voice channel where everyone starts.
* @type {VoiceChannel}
*/
this.mainVoiceChannel;

winston.loggers.get(this.guild.id).event(`The activity ${this.name} was created as a coffee chats.`, {event: "Activity"});
}

/**
* Initializes the activity by creating the necessary channels.
* @returns {Promise<CoffeeChats>}
* @param {TextChannel} channel
* @param {String} userId
* @override
*/
async init(channel, userId) {
await super.init();

this.mainVoiceChannel = await chooseChannel('What channel will the teams join first before being shuffled?', this.channels.voiceChannels.array(), channel, userId);
this.channels.safeChannels.set(this.mainVoiceChannel.id, this.mainVoiceChannel);

for (var i = 0; i < this.numOfTeams; i++) {
this.addChannelHelper(`voice-${i}`, {type: 'voice'});
}

this.joinActivityChannel = await this.addChannelHelper('☕' + 'join-activity', {
type: 'text',
topic: 'This channel is only intended to add your team to the activity list! Please do not use it for anything else!',
}, [], true);

this.joinActivityConsole();

return this;
}


/**
* @override
*/
addDefaultFeatures() {
/** @type {Activity.ActivityFeature[]} */
let localFeatures = [
{
name: 'Team Shuffle',
description: 'Shuffle all the teams from the main voice channel to the other channels.',
emoji: '👨‍👩‍👧‍👦',
callback: () => this.groupShuffle(),
},
{
name: 'Reset Teams',
description: 'Remove all the signed up teams.',
emoji: '🗜️',
callback: () => this.resetTeams(),
},
{
name: 'Add Team Slot',
description: 'Adds a team slot and a voice channel for them.',
emoji: '☝️',
callback: () => this.addTeamSlot(),
}
]

localFeatures.forEach(feature => this.features.set(feature.name, feature));

super.addDefaultFeatures();
}


/**
* Will send the console for users to join the activity as a group.
* @private
* @async
*/
async joinActivityConsole() {
// reaction to use
var emoji = '⛷️';

// send embed and react with emoji
const msgEmbed = new MessageEmbed()
.setColor(this.botGuild.colors.embedColor)
.setTitle('Join the activity!')
.setDescription('If you want to join this activity, please react to this message with ' + emoji +' and follow my instructions!\n If the emojis are not working' +
' it means the activity is full. Check the activity text channel for other activity times!');
var joinMsg = await this.joinActivityChannel.send(msgEmbed);
await joinMsg.react(emoji);

// reactor collector and its filter
const emojiFilter = (reaction, user) => !user.bot && reaction.emoji.name === emoji;
const emojiCollector = joinMsg.createReactionCollector(emojiFilter);

emojiCollector.on('collect', async (reaction, user) => {

// check to make sure there are spots left
if (this.teams.size > this.numOfTeams) {
sendMsgToChannel(this.joinActivityChannel, user.id, "Sorry, but the activity is full!", 10);
return;
}

let members = await memberPrompt({prompt: "Who are you team members? Let me know in ONE message!", channel: this.joinActivityChannel, userId: user.id});

// add team captain to members list
members.set(user.id, this.guild.member(user));

// add the team to the team list
this.teams.set(this.teams.size, members.array());

this.joinActivityChannel.send('<@' + user.id + '> Your team has been added to the activity! Make sure you follow the instructions in the main channel.').then(msg => {
msg.delete({ timeout: 5000 });
});
});
}

/**
* FEATURES FROM THIS POINT DOWN.
*/


/**
* Shuffle users in general voice as groups in firebase
*/
groupShuffle() {
let channels = this.channels.voiceChannels;
let voiceChannels = channels.filter(voiceChannel => voiceChannel.id != this.mainVoiceChannel.id).array();

// loop over the groups and channels at the same time using an index, add users for each group in a single voice channel
for (var index = 0; index < this.teams.size; index++) {
this.teams.get(index).forEach(member => {
try {
if (member.voice.channel)
member.voice.setChannel(voiceChannels[index % voiceChannels.length]);
} catch (error) {
// do nothing, sad!
winston.loggers.get(this.guild.id).warning(`For activity named ${this.name} I could not pull in user ${member.id} into the voice channel ${voiceChannels[index].name}.`, { event: "Coffee Chats" });
}
});
}

winston.loggers.get(this.guild.id).event(`Activity named ${this.name} had its groups shuffled.`, { event: "Coffee Chats" });
}


/**
* Resets the teams to have no teams.
*/
resetTeams() {
this.teams = new Collection();
}


/**
* Add a team slot to the activity and adds a voice channel for them.
*/
addTeamSlot() {
this.numOfTeams += 1;
this.addChannelHelper(`voice-${this.numOfTeams - 1}`, { type: 'voice' }); // -1 because we start from 0
}

}

module.exports = CoffeeChats;
Loading

0 comments on commit ba5caa1

Please sign in to comment.