package org.telegram.abilitybots.api.bot; import com.google.common.collect.ListMultimap; import com.google.common.collect.Multimap; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.telegram.abilitybots.api.db.DBContext; import org.telegram.abilitybots.api.objects.Ability; import org.telegram.abilitybots.api.objects.MessageContext; import org.telegram.abilitybots.api.objects.Privacy; import org.telegram.abilitybots.api.util.AbilityExtension; import org.telegram.abilitybots.api.util.AbilityUtils; import org.telegram.abilitybots.api.util.Pair; import org.telegram.telegrambots.meta.api.methods.GetFile; import org.telegram.telegrambots.meta.api.methods.send.SendDocument; import org.telegram.telegrambots.meta.api.objects.InputFile; import org.telegram.telegrambots.meta.api.objects.Message; import org.telegram.telegrambots.meta.api.objects.Update; import org.telegram.telegrambots.meta.api.objects.User; import org.telegram.telegrambots.meta.exceptions.TelegramApiException; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.PrintStream; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.StringJoiner; import static com.google.common.base.Strings.isNullOrEmpty; import static com.google.common.collect.MultimapBuilder.hashKeys; import static java.lang.String.format; import static java.util.Comparator.comparing; import static java.util.Objects.nonNull; import static java.util.stream.Collectors.joining; import static org.apache.commons.lang3.StringUtils.isEmpty; import static org.telegram.abilitybots.api.objects.Ability.builder; import static org.telegram.abilitybots.api.objects.Flag.DOCUMENT; import static org.telegram.abilitybots.api.objects.Flag.MESSAGE; import static org.telegram.abilitybots.api.objects.Flag.REPLY; import static org.telegram.abilitybots.api.objects.Locality.ALL; import static org.telegram.abilitybots.api.objects.Locality.USER; import static org.telegram.abilitybots.api.objects.Privacy.ADMIN; import static org.telegram.abilitybots.api.objects.Privacy.CREATOR; import static org.telegram.abilitybots.api.objects.Privacy.PUBLIC; import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_BAN_FAIL; import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_BAN_SUCCESS; import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_CLAIM_FAIL; import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_CLAIM_SUCCESS; import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_COMMANDS_NOT_FOUND; import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_DEMOTE_FAIL; import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_DEMOTE_SUCCESS; import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_PROMOTE_FAIL; import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_PROMOTE_SUCCESS; import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_RECOVER_ERROR; import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_RECOVER_FAIL; import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_RECOVER_MESSAGE; import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_RECOVER_SUCCESS; import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_UNBAN_FAIL; import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_UNBAN_SUCCESS; import static org.telegram.abilitybots.api.util.AbilityMessageCodes.USER_NOT_FOUND; import static org.telegram.abilitybots.api.util.AbilityUtils.addTag; import static org.telegram.abilitybots.api.util.AbilityUtils.escape; import static org.telegram.abilitybots.api.util.AbilityUtils.getLocalizedMessage; import static org.telegram.abilitybots.api.util.AbilityUtils.shortName; import static org.telegram.abilitybots.api.util.AbilityUtils.stripTag; public final class DefaultAbilities implements AbilityExtension { // Default commands public static final String CLAIM = "claim"; public static final String BAN = "ban"; public static final String PROMOTE = "promote"; public static final String DEMOTE = "demote"; public static final String UNBAN = "unban"; public static final String BACKUP = "backup"; public static final String RECOVER = "recover"; public static final String COMMANDS = "commands"; public static final String REPORT = "report"; public static final String STATS = "stats"; private static final Logger log = LoggerFactory.getLogger(DefaultAbilities.class); private final BaseAbilityBot bot; public DefaultAbilities(BaseAbilityBot bot) { this.bot = bot; } /** *
* Format of the report: *
* [command1] - [description1] *
* [command2] - [description2] *
* ... *
* Once you invoke it, the bot will send the available commands to the chat. This is a public ability so anyone can invoke it. *
* Usage: /commands
*
* @return the ability to report commands defined by the child bot.
*/
public Ability reportCommands() {
return builder()
.name(REPORT)
.locality(ALL)
.privacy(CREATOR)
.input(0)
.action(ctx -> {
String commands = bot.abilities().values().stream()
.filter(ability -> nonNull(ability.info()))
.map(ability -> {
String name = ability.name();
String info = ability.info();
return format("%s - %s", name, info);
})
.sorted()
.reduce((a, b) -> format("%s%n%s", a, b))
.orElse(getLocalizedMessage(ABILITY_COMMANDS_NOT_FOUND, ctx.user().getLanguageCode()));
bot.silent.send(commands, ctx.chatId());
})
.build();
}
/**
* Default format:
*
* PUBLIC *
* [command1] - [description1] *
* [command2] - [description2] *
* GROUP_ADMIN *
* [command1] - [description1] *
* ...
*
* @return the ability to print commands based on the privacy of the requesting user
*/
public Ability commands() {
return builder()
.name(COMMANDS)
.locality(USER)
.privacy(PUBLIC)
.input(0)
.action(ctx -> {
Privacy privacy = bot.getPrivacy(ctx.update(), ctx.user().getId());
ListMultimap
* This is a high-profile ability and is restricted to the CREATOR only.
*
* Usage:
* The bot recovery process hugely depends on the implementation of the recovery method of {@link DBContext}.
*
* Usage:
* Usage:
* Note that admins who try to ban the creator, get banned.
*
* @return the ability to ban the user from any kind of bot interaction
*/
public Ability banUser() {
return builder()
.name(BAN)
.locality(ALL)
.privacy(ADMIN)
.input(1)
.action(ctx -> {
String username = stripTag(ctx.firstArg());
int userId = getUserIdSendError(username, ctx);
String bannedUser;
// Protection from abuse
if (userId == bot.creatorId()) {
userId = ctx.user().getId();
bannedUser = isNullOrEmpty(ctx.user().getUserName()) ? addTag(ctx.user().getUserName()) : shortName(ctx.user());
} else {
bannedUser = addTag(username);
}
Set/backup
*
* @return the ability to back-up the database of the bot
*/
public Ability backupDB() {
return builder()
.name(BACKUP)
.locality(USER)
.privacy(CREATOR)
.input(0)
.action(ctx -> {
File backup = new File("backup.json");
try (PrintStream printStream = new PrintStream(backup)) {
printStream.print(bot.db.backup());
bot.sender.sendDocument(SendDocument.builder()
.document(new InputFile(backup))
.chatId(ctx.chatId().toString())
.build()
);
} catch (FileNotFoundException e) {
log.error("Error while fetching backup", e);
} catch (TelegramApiException e) {
log.error("Error while sending document/backup file", e);
}
})
.build();
}
/**
* Recovers the bot database using {@link DBContext#recover(Object)}.
* /recover
*
* @return the ability to recover the database of the bot
*/
public Ability recoverDB() {
return builder()
.name(RECOVER)
.locality(USER)
.privacy(CREATOR)
.input(0)
.action(ctx -> bot.silent.forceReply(
getLocalizedMessage(ABILITY_RECOVER_MESSAGE, ctx.user().getLanguageCode()), ctx.chatId()))
.reply(update -> {
String replyToMsg = update.getMessage().getReplyToMessage().getText();
String recoverMessage = getLocalizedMessage(ABILITY_RECOVER_MESSAGE, AbilityUtils.getUser(update).getLanguageCode());
if (!replyToMsg.equals(recoverMessage))
return;
String fileId = update.getMessage().getDocument().getFileId();
try (FileReader reader = new FileReader(downloadFileWithId(fileId))) {
String backupData = IOUtils.toString(reader);
if (bot.db.recover(backupData)) {
send(ABILITY_RECOVER_SUCCESS, update);
} else {
send(ABILITY_RECOVER_FAIL, update);
}
} catch (Exception e) {
log.error("Could not recover DB from backup", e);
send(ABILITY_RECOVER_ERROR, update);
}
}, MESSAGE, DOCUMENT, REPLY)
.build();
}
/**
* Banned users are accumulated in the blacklist. Use {@link DBContext#getSet(String)} with name specified by {@link BaseAbilityBot#BLACKLIST}.
* /ban @username
* /unban @username
*
* @return the ability to unban a user
*/
public Ability unbanUser() {
return builder()
.name(UNBAN)
.locality(ALL)
.privacy(ADMIN)
.input(1)
.action(ctx -> {
String username = stripTag(ctx.firstArg());
Integer userId = getUserIdSendError(username, ctx);
Set