add stat channels feature
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m25s
All checks were successful
Deploy to Dokku / docker (ubuntu-latest) (push) Successful in 1m25s
This commit is contained in:
parent
6d98977198
commit
87a56700ec
@ -0,0 +1,25 @@
|
||||
package cc.fascinated.bat.features.statschannel;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* @author Fascinated (fascinated7)
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
public class StatsChannel {
|
||||
/**
|
||||
* The display of the stats channel
|
||||
*/
|
||||
private final String display;
|
||||
|
||||
/**
|
||||
* The last value of the stats channel, used to compare
|
||||
* the new value to the old value, so we don't have to
|
||||
* update the channel every time
|
||||
*/
|
||||
private String lastValue;
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package cc.fascinated.bat.features.statschannel;
|
||||
|
||||
import cc.fascinated.bat.features.Feature;
|
||||
import cc.fascinated.bat.features.FeatureProfile;
|
||||
import cc.fascinated.bat.features.statschannel.command.StatsChannelCommand;
|
||||
import cc.fascinated.bat.model.BatGuild;
|
||||
import cc.fascinated.bat.service.CommandService;
|
||||
import cc.fascinated.bat.service.DiscordService;
|
||||
import cc.fascinated.bat.service.GuildService;
|
||||
import lombok.NonNull;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author Fascinated (fascinated7)
|
||||
*/
|
||||
@Component
|
||||
@Log4j2(topic = "Stats Channel Feature")
|
||||
public class StatsChannelFeature extends Feature {
|
||||
private final GuildService guildService;
|
||||
|
||||
@Autowired
|
||||
public StatsChannelFeature(@NonNull ApplicationContext context, @NonNull CommandService commandService, @NonNull GuildService guildService) {
|
||||
super("Stats Channels", FeatureProfile.FeatureState.DISABLED, true);
|
||||
this.guildService = guildService;
|
||||
|
||||
super.registerCommand(commandService, context.getBean(StatsChannelCommand.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Scheduled task to update the stats channel
|
||||
* This will run every hour
|
||||
*/
|
||||
@Scheduled(cron = "0 0 * * * *")
|
||||
public void updateStatsChannel() {
|
||||
for (Guild guild : DiscordService.JDA.getGuilds()) {
|
||||
BatGuild batGuild = guildService.getGuild(guild.getId());
|
||||
assert batGuild != null;
|
||||
if (batGuild.getFeatureProfile().isFeatureDisabled(this)) {
|
||||
continue;
|
||||
}
|
||||
StatsChannelProfile profile = batGuild.getStatsChannelProfile();
|
||||
profile.refreshChannels(batGuild);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the placeholders that the user can use
|
||||
*
|
||||
* @param replacements What to replace the placeholders using
|
||||
* @return The placeholders
|
||||
*/
|
||||
public static String getPlaceholders(Object... replacements) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("**Available Placeholders:**\n");
|
||||
for (StatsPlaceholders placeholder : StatsPlaceholders.values()) {
|
||||
String placeholderString = placeholder.getPlaceholder();
|
||||
builder.append("`").append(placeholderString).append("` - ")
|
||||
.append(StatsPlaceholders.replaceAllPlaceholders(placeholderString, replacements)).append("\n");
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
package cc.fascinated.bat.features.statschannel;
|
||||
|
||||
import cc.fascinated.bat.common.Serializable;
|
||||
import cc.fascinated.bat.model.BatGuild;
|
||||
import cc.fascinated.bat.service.DiscordService;
|
||||
import com.google.gson.Gson;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
|
||||
import org.bson.Document;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Fascinated (fascinated7)
|
||||
*/
|
||||
public class StatsChannelProfile extends Serializable {
|
||||
/**
|
||||
* The channels to show stats on.
|
||||
*/
|
||||
private final Map<String, StatsChannel> channels = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Gets the channels to show stats on.
|
||||
*
|
||||
* @return the channels
|
||||
*/
|
||||
public Map<VoiceChannel, StatsChannel> getChannels() {
|
||||
Map<VoiceChannel, StatsChannel> textChannels = new HashMap<>();
|
||||
for (Map.Entry<String, StatsChannel> entry : channels.entrySet()) {
|
||||
VoiceChannel textChannel = DiscordService.JDA.getVoiceChannelById(entry.getKey());
|
||||
if (textChannel != null) {
|
||||
textChannels.put(textChannel, entry.getValue());
|
||||
}
|
||||
}
|
||||
return textChannels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a channel
|
||||
*
|
||||
* @param id the id of the channel
|
||||
* @return the channel
|
||||
*/
|
||||
public VoiceChannel getChannel(String id) {
|
||||
if (!channels.containsKey(id)) {
|
||||
return null;
|
||||
}
|
||||
return DiscordService.JDA.getVoiceChannelById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new stats channel
|
||||
*
|
||||
* @param channel the channel
|
||||
* @param text the text to display
|
||||
*/
|
||||
public void addChannel(VoiceChannel channel, String text) {
|
||||
channels.put(channel.getId(), new StatsChannel(text, ""));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a channel
|
||||
*
|
||||
* @param channel the channel
|
||||
*/
|
||||
public void removeChannel(VoiceChannel channel) {
|
||||
channels.remove(channel.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates all the stats channels for the guild
|
||||
*/
|
||||
public void refreshChannels(BatGuild guild) {
|
||||
for (Map.Entry<String, StatsChannel> entry : this.channels.entrySet()) {
|
||||
VoiceChannel channel = DiscordService.JDA.getVoiceChannelById(entry.getKey());
|
||||
if (channel == null) {
|
||||
continue;
|
||||
}
|
||||
StatsChannel statsChannel = entry.getValue();
|
||||
String display = StatsPlaceholders.replaceAllPlaceholders(statsChannel.getDisplay(), guild);
|
||||
if (statsChannel.getLastValue().equals(display)) { // Don't update if the value is the same
|
||||
continue;
|
||||
}
|
||||
channel.getManager().setName(display).queue();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load(Document document, Gson gson) {
|
||||
Document channelsDocument = (Document) document.getOrDefault("channels", new Document());
|
||||
for (Map.Entry<String, Object> entry : channelsDocument.entrySet()) {
|
||||
Document channelDocument = (Document) entry.getValue();
|
||||
channels.put(entry.getKey(), new StatsChannel(
|
||||
channelDocument.getString("display"),
|
||||
channelDocument.getString("lastValue")
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document serialize(Gson gson) {
|
||||
Document document = new Document();
|
||||
Document channelsDocument = new Document();
|
||||
for (Map.Entry<String, StatsChannel> entry : channels.entrySet()) {
|
||||
StatsChannel statsChannel = entry.getValue();
|
||||
Document channelDocument = new Document();
|
||||
channelDocument.put("display", statsChannel.getDisplay());
|
||||
channelDocument.put("lastValue", statsChannel.getLastValue());
|
||||
channelsDocument.put(entry.getKey(), channelDocument);
|
||||
}
|
||||
document.put("channels", channelsDocument);
|
||||
return document;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
channels.clear();
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
package cc.fascinated.bat.features.statschannel;
|
||||
|
||||
import cc.fascinated.bat.common.NumberFormatter;
|
||||
import cc.fascinated.bat.model.BatGuild;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author Fascinated (fascinated7)
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum StatsPlaceholders {
|
||||
MEMBER_COUNT("{member_count}", BatGuild.class) {
|
||||
@Override
|
||||
public String replacePlaceholder(Object object) {
|
||||
return NumberFormatter.simpleFormat(((BatGuild) object).getDiscordGuild().getMembers().size());
|
||||
}
|
||||
},
|
||||
BOT_COUNT("{bot_count}", BatGuild.class) {
|
||||
@Override
|
||||
public String replacePlaceholder(Object object) {
|
||||
return NumberFormatter.simpleFormat(((BatGuild) object).getDiscordGuild().getMembers().stream()
|
||||
.filter(member -> member.getUser().isBot()).count());
|
||||
}
|
||||
},
|
||||
CHANNEL_COUNT("{channel_count}", BatGuild.class) {
|
||||
@Override
|
||||
public String replacePlaceholder(Object object) {
|
||||
return NumberFormatter.simpleFormat(((BatGuild) object).getDiscordGuild().getChannels().size());
|
||||
}
|
||||
},
|
||||
ROLE_COUNT("{role_count}", BatGuild.class) {
|
||||
@Override
|
||||
public String replacePlaceholder(Object object) {
|
||||
return NumberFormatter.simpleFormat(((BatGuild) object).getDiscordGuild().getRoles().size());
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The placeholder string that will get replaced
|
||||
*/
|
||||
private final String placeholder;
|
||||
|
||||
/**
|
||||
* The class that the placeholder is associated with
|
||||
*/
|
||||
private final Class<?> clazz;
|
||||
|
||||
/**
|
||||
* Replaces the placeholder with the string based on the overridden method
|
||||
*
|
||||
* @param object The object to replace the placeholder with
|
||||
* @return The string with the placeholder replaced
|
||||
*/
|
||||
public String replacePlaceholder(Object object) {
|
||||
if (clazz != null && !clazz.isInstance(object)) {
|
||||
throw new IllegalArgumentException("Object is not an instance of " + clazz.getName());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces all placeholders in a message with the objects provided
|
||||
*
|
||||
* @param message The message to replace the placeholders in
|
||||
* @param objects The objects to replace the placeholders with
|
||||
* @return The message with the placeholders replaced
|
||||
*/
|
||||
public static String replaceAllPlaceholders(String message, Object... objects) {
|
||||
for (StatsPlaceholders placeholder : values()) {
|
||||
for (Object object : objects) {
|
||||
if (placeholder.getClazz() != null && !placeholder.getClazz().isInstance(object)) {
|
||||
continue;
|
||||
}
|
||||
message = message.replace(placeholder.getPlaceholder(), placeholder.replacePlaceholder(object));
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
package cc.fascinated.bat.features.statschannel.command;
|
||||
|
||||
import cc.fascinated.bat.Emojis;
|
||||
import cc.fascinated.bat.command.BatCommand;
|
||||
import cc.fascinated.bat.command.CommandInfo;
|
||||
import cc.fascinated.bat.common.EmbedDescriptionBuilder;
|
||||
import cc.fascinated.bat.common.EmbedUtils;
|
||||
import cc.fascinated.bat.features.statschannel.StatsChannelProfile;
|
||||
import cc.fascinated.bat.model.BatGuild;
|
||||
import cc.fascinated.bat.model.BatUser;
|
||||
import lombok.NonNull;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionType;
|
||||
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* @author Fascinated (fascinated7)
|
||||
*/
|
||||
@Component("stats-channel.add:sub")
|
||||
@CommandInfo(
|
||||
name = "add",
|
||||
description = "Add a stats channel"
|
||||
)
|
||||
public class AddSubCommand extends BatCommand {
|
||||
public AddSubCommand() {
|
||||
super.addOptions(new OptionData(OptionType.STRING, "display", "The text to display in the stats channel", true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
|
||||
OptionMapping displayOption = event.getOption("display");
|
||||
assert displayOption != null;
|
||||
|
||||
String display = displayOption.getAsString();
|
||||
if (display.length() > 24) {
|
||||
event.replyEmbeds(EmbedUtils.errorEmbed()
|
||||
.setDescription("%s The channel name can only be a maximum of 32 characters".formatted(Emojis.CROSS_MARK_EMOJI))
|
||||
.build()).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
StatsChannelProfile profile = guild.getStatsChannelProfile();
|
||||
if (profile.getChannels().size() >= 5) {
|
||||
event.replyEmbeds(EmbedUtils.errorEmbed()
|
||||
.setDescription("%s You can only have a maximum of 5 stats channels".formatted(Emojis.CROSS_MARK_EMOJI))
|
||||
.build()).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
guild.getDiscordGuild().createVoiceChannel("stats channel").queue(voiceChannel -> {
|
||||
// Disallow everyone from connecting
|
||||
voiceChannel.getManager().putPermissionOverride(
|
||||
guild.getDiscordGuild().getPublicRole(),
|
||||
null,
|
||||
Collections.singleton(Permission.VOICE_CONNECT)
|
||||
).queue();
|
||||
|
||||
profile.addChannel(voiceChannel, display);
|
||||
profile.refreshChannels(guild); // For the stat channels to be updated
|
||||
|
||||
event.replyEmbeds(EmbedUtils.successEmbed()
|
||||
.setDescription(new EmbedDescriptionBuilder("%s The stats chanel %s has been created".formatted(
|
||||
Emojis.CHECK_MARK_EMOJI,
|
||||
voiceChannel.getAsMention()
|
||||
))
|
||||
.appendLine("Display: `%s`".formatted(display), true)
|
||||
.build())
|
||||
.build()).queue();
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package cc.fascinated.bat.features.statschannel.command;
|
||||
|
||||
import cc.fascinated.bat.command.BatCommand;
|
||||
import cc.fascinated.bat.command.CommandInfo;
|
||||
import cc.fascinated.bat.common.EmbedDescriptionBuilder;
|
||||
import cc.fascinated.bat.common.EmbedUtils;
|
||||
import cc.fascinated.bat.features.statschannel.StatsChannel;
|
||||
import cc.fascinated.bat.features.statschannel.StatsChannelFeature;
|
||||
import cc.fascinated.bat.features.statschannel.StatsChannelProfile;
|
||||
import cc.fascinated.bat.model.BatGuild;
|
||||
import cc.fascinated.bat.model.BatUser;
|
||||
import lombok.NonNull;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
|
||||
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
|
||||
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Fascinated (fascinated7)
|
||||
*/
|
||||
@Component("stats-channel.current:sub")
|
||||
@CommandInfo(
|
||||
name = "current",
|
||||
description = "Shows the current stats channels"
|
||||
)
|
||||
public class CurrentSubCommand extends BatCommand {
|
||||
@Override
|
||||
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
|
||||
StatsChannelProfile profile = guild.getStatsChannelProfile();
|
||||
EmbedDescriptionBuilder builder = new EmbedDescriptionBuilder("Stats Channels");
|
||||
if (profile.getChannels().isEmpty()) {
|
||||
builder.appendLine("You have no stats channels set up", true);
|
||||
} else {
|
||||
for (Map.Entry<VoiceChannel, StatsChannel> entry : profile.getChannels().entrySet()) {
|
||||
builder.appendLine("%s: `%s`".formatted(entry.getKey().getAsMention(), entry.getValue().getDisplay()), true);
|
||||
}
|
||||
}
|
||||
builder.emptyLine();
|
||||
builder.appendLine(StatsChannelFeature.getPlaceholders(guild), false);
|
||||
|
||||
event.replyEmbeds(EmbedUtils.successEmbed()
|
||||
.setDescription(builder.build())
|
||||
.build())
|
||||
.queue();
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package cc.fascinated.bat.features.statschannel.command;
|
||||
|
||||
import cc.fascinated.bat.Emojis;
|
||||
import cc.fascinated.bat.command.BatCommand;
|
||||
import cc.fascinated.bat.command.CommandInfo;
|
||||
import cc.fascinated.bat.common.EmbedUtils;
|
||||
import cc.fascinated.bat.features.statschannel.StatsChannelProfile;
|
||||
import cc.fascinated.bat.model.BatGuild;
|
||||
import cc.fascinated.bat.model.BatUser;
|
||||
import lombok.NonNull;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.channel.ChannelType;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
|
||||
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
|
||||
import net.dv8tion.jda.api.entities.channel.unions.GuildChannelUnion;
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionType;
|
||||
import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author Fascinated (fascinated7)
|
||||
*/
|
||||
@Component("stats-channel.remove:sub")
|
||||
@CommandInfo(
|
||||
name = "remove",
|
||||
description = "Remove a stats channel"
|
||||
)
|
||||
public class RemoveSubCommand extends BatCommand {
|
||||
public RemoveSubCommand() {
|
||||
super.addOptions(new OptionData(OptionType.CHANNEL, "channel", "The stats channel to remove", true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
|
||||
OptionMapping channelOption = event.getOption("channel");
|
||||
assert channelOption != null;
|
||||
|
||||
GuildChannelUnion channelUnion = channelOption.getAsChannel();
|
||||
if (channelUnion.getType() != ChannelType.VOICE) {
|
||||
event.replyEmbeds(EmbedUtils.errorEmbed()
|
||||
.setDescription("%s The channel must be a voice channel".formatted(Emojis.CROSS_MARK_EMOJI))
|
||||
.build()).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
StatsChannelProfile profile = guild.getStatsChannelProfile();
|
||||
VoiceChannel statsChannel = profile.getChannel(channelUnion.getId());
|
||||
if (statsChannel == null) {
|
||||
event.replyEmbeds(EmbedUtils.errorEmbed()
|
||||
.setDescription("%s The channel is not a stats channel".formatted(Emojis.CROSS_MARK_EMOJI))
|
||||
.build()).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
statsChannel.delete().queue(then -> {
|
||||
profile.removeChannel(statsChannel);
|
||||
event.replyEmbeds(EmbedUtils.successEmbed()
|
||||
.setDescription("%s The stats channel has been removed".formatted(Emojis.CHECK_MARK_EMOJI))
|
||||
.build()).queue();
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package cc.fascinated.bat.features.statschannel.command;
|
||||
|
||||
import cc.fascinated.bat.command.BatCommand;
|
||||
import cc.fascinated.bat.command.CommandInfo;
|
||||
import lombok.NonNull;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author Fascinated (fascinated7)
|
||||
*/
|
||||
@Component
|
||||
@CommandInfo(
|
||||
name = "stats-channel",
|
||||
description = "Change your stats channel settings",
|
||||
requiredPermissions = Permission.MANAGE_SERVER
|
||||
)
|
||||
public class StatsChannelCommand extends BatCommand {
|
||||
@Autowired
|
||||
public StatsChannelCommand(@NonNull ApplicationContext context) {
|
||||
super.addSubCommands(
|
||||
context.getBean(AddSubCommand.class),
|
||||
context.getBean(RemoveSubCommand.class),
|
||||
context.getBean(CurrentSubCommand.class)
|
||||
);
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ import cc.fascinated.bat.features.logging.LogProfile;
|
||||
import cc.fascinated.bat.features.minecraft.MinecraftProfile;
|
||||
import cc.fascinated.bat.features.namehistory.profile.guild.NameHistoryProfile;
|
||||
import cc.fascinated.bat.features.reminder.ReminderProfile;
|
||||
import cc.fascinated.bat.features.statschannel.StatsChannelProfile;
|
||||
import cc.fascinated.bat.features.welcomer.WelcomerProfile;
|
||||
import cc.fascinated.bat.premium.PremiumProfile;
|
||||
import cc.fascinated.bat.service.DiscordService;
|
||||
@ -180,6 +181,15 @@ public class BatGuild extends ProfileHolder {
|
||||
return getProfile(MinecraftProfile.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the stats channel profile
|
||||
*
|
||||
* @return the stats channel profile
|
||||
*/
|
||||
public StatsChannelProfile getStatsChannelProfile() {
|
||||
return getProfile(StatsChannelProfile.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the user
|
||||
*/
|
||||
|
Loading…
x
Reference in New Issue
Block a user