Make abilities case-insensitive, fix msg markdown bug and add group-admin privacy
This commit is contained in:
parent
066ad7f675
commit
dc9f34196e
@ -10,6 +10,7 @@ import org.telegram.abilitybots.api.util.AbilityUtils;
|
||||
import org.telegram.abilitybots.api.util.Pair;
|
||||
import org.telegram.abilitybots.api.util.Trio;
|
||||
import org.telegram.telegrambots.api.methods.GetFile;
|
||||
import org.telegram.telegrambots.api.methods.groupadministration.GetChatAdministrators;
|
||||
import org.telegram.telegrambots.api.methods.send.SendDocument;
|
||||
import org.telegram.telegrambots.api.objects.Message;
|
||||
import org.telegram.telegrambots.api.objects.Update;
|
||||
@ -24,10 +25,7 @@ import java.io.FileReader;
|
||||
import java.io.PrintStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
@ -413,10 +411,10 @@ public abstract class AbilityBot extends TelegramLongPollingBot {
|
||||
|
||||
Set<Integer> blacklist = blacklist();
|
||||
if (blacklist.contains(userId))
|
||||
silent.sendMd(format("%s is already *banned*.", bannedUser), ctx.chatId());
|
||||
silent.sendMd(format("%s is already *banned*.", escape(bannedUser)), ctx.chatId());
|
||||
else {
|
||||
blacklist.add(userId);
|
||||
silent.sendMd(format("%s is now *banned*.", bannedUser), ctx.chatId());
|
||||
silent.sendMd(format("%s is now *banned*.", escape(bannedUser)), ctx.chatId());
|
||||
}
|
||||
})
|
||||
.post(commitTo(db))
|
||||
@ -441,9 +439,9 @@ public abstract class AbilityBot extends TelegramLongPollingBot {
|
||||
Set<Integer> blacklist = blacklist();
|
||||
|
||||
if (!blacklist.remove(userId))
|
||||
silent.sendMd(format("@%s is *not* on the *blacklist*.", username), ctx.chatId());
|
||||
silent.sendMd(format("@%s is *not* on the *blacklist*.", escape(username)), ctx.chatId());
|
||||
else {
|
||||
silent.sendMd(format("@%s, your ban has been *lifted*.", username), ctx.chatId());
|
||||
silent.sendMd(format("@%s, your ban has been *lifted*.", escape(username)), ctx.chatId());
|
||||
}
|
||||
})
|
||||
.post(commitTo(db))
|
||||
@ -465,10 +463,10 @@ public abstract class AbilityBot extends TelegramLongPollingBot {
|
||||
|
||||
Set<Integer> admins = admins();
|
||||
if (admins.contains(userId))
|
||||
silent.sendMd(format("@%s is already an *admin*.", username), ctx.chatId());
|
||||
silent.sendMd(format("@%s is already an *admin*.", escape(username)), ctx.chatId());
|
||||
else {
|
||||
admins.add(userId);
|
||||
silent.sendMd(format("@%s has been *promoted*.", username), ctx.chatId());
|
||||
silent.sendMd(format("@%s has been *promoted*.", escape(username)), ctx.chatId());
|
||||
}
|
||||
}).post(commitTo(db))
|
||||
.build();
|
||||
@ -489,9 +487,9 @@ public abstract class AbilityBot extends TelegramLongPollingBot {
|
||||
|
||||
Set<Integer> admins = admins();
|
||||
if (admins.remove(userId)) {
|
||||
silent.sendMd(format("@%s has been *demoted*.", username), ctx.chatId());
|
||||
silent.sendMd(format("@%s has been *demoted*.", escape(username)), ctx.chatId());
|
||||
} else {
|
||||
silent.sendMd(format("@%s is *not* an *admin*.", username), ctx.chatId());
|
||||
silent.sendMd(format("@%s is *not* an *admin*.", escape(username)), ctx.chatId());
|
||||
}
|
||||
})
|
||||
.post(commitTo(db))
|
||||
@ -540,7 +538,7 @@ public abstract class AbilityBot extends TelegramLongPollingBot {
|
||||
abilities = stream(this.getClass().getMethods())
|
||||
.filter(method -> method.getReturnType().equals(Ability.class))
|
||||
.map(this::returnAbility)
|
||||
.collect(toMap(Ability::name, identity()));
|
||||
.collect(toMap(ability -> ability.name().toLowerCase(), identity()));
|
||||
|
||||
Stream<Reply> methodReplies = stream(this.getClass().getMethods())
|
||||
.filter(method -> method.getReturnType().equals(Reply.class))
|
||||
@ -617,7 +615,7 @@ public abstract class AbilityBot extends TelegramLongPollingBot {
|
||||
boolean isOk = abilityTokens == 0 || (tokens.length > 0 && tokens.length == abilityTokens);
|
||||
|
||||
if (!isOk)
|
||||
silent.send(String.format("Sorry, this feature requires %d additional %s.", abilityTokens, abilityTokens == 1 ? "input" : "inputs"), getChatId(trio.a()));
|
||||
silent.send(format("Sorry, this feature requires %d additional %s.", abilityTokens, abilityTokens == 1 ? "input" : "inputs"), getChatId(trio.a()));
|
||||
return isOk;
|
||||
}
|
||||
|
||||
@ -629,7 +627,7 @@ public abstract class AbilityBot extends TelegramLongPollingBot {
|
||||
boolean isOk = abilityLocality == ALL || locality == abilityLocality;
|
||||
|
||||
if (!isOk)
|
||||
silent.send(String.format("Sorry, %s-only feature.", abilityLocality.toString().toLowerCase()), getChatId(trio.a()));
|
||||
silent.send(format("Sorry, %s-only feature.", abilityLocality.toString().toLowerCase()), getChatId(trio.a()));
|
||||
return isOk;
|
||||
}
|
||||
|
||||
@ -639,15 +637,24 @@ public abstract class AbilityBot extends TelegramLongPollingBot {
|
||||
Privacy privacy;
|
||||
int id = user.id();
|
||||
|
||||
privacy = isCreator(id) ? CREATOR : isAdmin(id) ? ADMIN : PUBLIC;
|
||||
privacy = isCreator(id) ? CREATOR : isAdmin(id) ? ADMIN : isGroupAdmin(update, id)? GROUP_ADMIN : PUBLIC;
|
||||
|
||||
boolean isOk = privacy.compareTo(trio.b().privacy()) >= 0;
|
||||
|
||||
if (!isOk)
|
||||
silent.send(String.format("Sorry, %s-only feature.", trio.b().privacy().toString().toLowerCase()), getChatId(trio.a()));
|
||||
silent.send("Sorry, you don't have the required access level to do that.", getChatId(trio.a()));
|
||||
|
||||
return isOk;
|
||||
}
|
||||
|
||||
private boolean isGroupAdmin(Update update, int id) {
|
||||
GetChatAdministrators admins = new GetChatAdministrators().setChatId(getChatId(update));
|
||||
|
||||
return isGroupUpdate(update) && silent.execute(admins)
|
||||
.orElse(new ArrayList<>()).stream()
|
||||
.anyMatch(member -> member.getUser().getId() == id);
|
||||
}
|
||||
|
||||
private boolean isCreator(int id) {
|
||||
return id == creatorId();
|
||||
}
|
||||
@ -667,11 +674,10 @@ public abstract class AbilityBot extends TelegramLongPollingBot {
|
||||
if (!update.hasMessage() || !msg.hasText())
|
||||
return Trio.of(update, abilities.get(DEFAULT), new String[]{});
|
||||
|
||||
// Priority goes to text before captions
|
||||
String[] tokens = msg.getText().split(" ");
|
||||
|
||||
if (tokens[0].startsWith("/")) {
|
||||
String abilityToken = stripBotUsername(tokens[0].substring(1));
|
||||
String abilityToken = stripBotUsername(tokens[0].substring(1)).toLowerCase();
|
||||
Ability ability = abilities.get(abilityToken);
|
||||
tokens = Arrays.copyOfRange(tokens, 1, tokens.length);
|
||||
return Trio.of(update, ability, tokens);
|
||||
@ -743,4 +749,8 @@ public abstract class AbilityBot extends TelegramLongPollingBot {
|
||||
private File downloadFileWithId(String fileId) throws TelegramApiException {
|
||||
return sender.downloadFile(sender.execute(new GetFile().setFileId(fileId)));
|
||||
}
|
||||
|
||||
private String escape(String username) {
|
||||
return username.replace("_", "\\_");
|
||||
}
|
||||
}
|
@ -10,6 +10,10 @@ public enum Privacy {
|
||||
* Anybody who is not a bot admin or its creator will be considered as a public user.
|
||||
*/
|
||||
PUBLIC,
|
||||
/**
|
||||
* Only group admins would get to initiate this command.
|
||||
*/
|
||||
GROUP_ADMIN,
|
||||
/**
|
||||
* A global admin of the bot, regardless of the group the bot is in.
|
||||
*/
|
||||
|
@ -64,6 +64,28 @@ public final class AbilityUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A "best-effort" boolean stating whether the update is a group message or not.
|
||||
*
|
||||
* @param update a Telegram {@link Update}
|
||||
* @return whether the update is linked to a group
|
||||
*/
|
||||
public static boolean isGroupUpdate(Update update) {
|
||||
if (MESSAGE.test(update)) {
|
||||
return update.getMessage().isGroupMessage();
|
||||
} else if (CALLBACK_QUERY.test(update)) {
|
||||
return update.getCallbackQuery().getMessage().isGroupMessage();
|
||||
} else if (CHANNEL_POST.test(update)) {
|
||||
return update.getChannelPost().isGroupMessage();
|
||||
} else if (EDITED_CHANNEL_POST.test(update)) {
|
||||
return update.getEditedChannelPost().isGroupMessage();
|
||||
} else if (EDITED_MESSAGE.test(update)) {
|
||||
return update.getEditedMessage().isGroupMessage();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the direct chat ID of the specified update.
|
||||
*
|
||||
|
@ -2,7 +2,6 @@ package org.telegram.abilitybots.api.bot;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.io.Files;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
@ -13,6 +12,7 @@ import org.telegram.abilitybots.api.sender.MessageSender;
|
||||
import org.telegram.abilitybots.api.sender.SilentSender;
|
||||
import org.telegram.abilitybots.api.util.Pair;
|
||||
import org.telegram.abilitybots.api.util.Trio;
|
||||
import org.telegram.telegrambots.api.methods.groupadministration.GetChatAdministrators;
|
||||
import org.telegram.telegrambots.api.objects.*;
|
||||
import org.telegram.telegrambots.exceptions.TelegramApiException;
|
||||
|
||||
@ -21,11 +21,14 @@ import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.google.common.collect.Lists.newArrayList;
|
||||
import static com.google.common.collect.Sets.newHashSet;
|
||||
import static java.lang.String.format;
|
||||
import static java.util.Collections.emptySet;
|
||||
import static java.util.Optional.empty;
|
||||
import static org.apache.commons.io.FileUtils.deleteQuietly;
|
||||
import static org.apache.commons.lang3.ArrayUtils.addAll;
|
||||
import static org.apache.commons.lang3.StringUtils.EMPTY;
|
||||
@ -43,8 +46,7 @@ import static org.telegram.abilitybots.api.objects.Flag.MESSAGE;
|
||||
import static org.telegram.abilitybots.api.objects.Locality.ALL;
|
||||
import static org.telegram.abilitybots.api.objects.Locality.GROUP;
|
||||
import static org.telegram.abilitybots.api.objects.MessageContext.newContext;
|
||||
import static org.telegram.abilitybots.api.objects.Privacy.ADMIN;
|
||||
import static org.telegram.abilitybots.api.objects.Privacy.PUBLIC;
|
||||
import static org.telegram.abilitybots.api.objects.Privacy.*;
|
||||
|
||||
public class AbilityBotTest {
|
||||
private static final String[] EMPTY_ARRAY = {};
|
||||
@ -77,7 +79,7 @@ public class AbilityBotTest {
|
||||
|
||||
bot.onUpdateReceived(update);
|
||||
|
||||
verify(silent, times(1)).send(format("Sorry, %s-only feature.", "admin"), MUSER.id());
|
||||
verify(silent, times(1)).send("Sorry, you don't have the required access level to do that.", MUSER.id());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -342,20 +344,61 @@ public class AbilityBotTest {
|
||||
Message message = mock(Message.class);
|
||||
org.telegram.telegrambots.api.objects.User user = mock(User.class);
|
||||
Ability publicAbility = getDefaultBuilder().privacy(PUBLIC).build();
|
||||
Ability groupAdminAbility = getDefaultBuilder().privacy(GROUP_ADMIN).build();
|
||||
Ability adminAbility = getDefaultBuilder().privacy(ADMIN).build();
|
||||
Ability creatorAbility = getDefaultBuilder().privacy(Privacy.CREATOR).build();
|
||||
|
||||
Trio<Update, Ability, String[]> publicTrio = Trio.of(update, publicAbility, TEXT);
|
||||
Trio<Update, Ability, String[]> groupAdminTrio = Trio.of(update, groupAdminAbility, TEXT);
|
||||
Trio<Update, Ability, String[]> adminTrio = Trio.of(update, adminAbility, TEXT);
|
||||
Trio<Update, Ability, String[]> creatorTrio = Trio.of(update, creatorAbility, TEXT);
|
||||
|
||||
mockUser(update, message, user);
|
||||
|
||||
assertEquals("Unexpected result when checking for privacy", true, bot.checkPrivacy(publicTrio));
|
||||
assertEquals("Unexpected result when checking for privacy", false, bot.checkPrivacy(groupAdminTrio));
|
||||
assertEquals("Unexpected result when checking for privacy", false, bot.checkPrivacy(adminTrio));
|
||||
assertEquals("Unexpected result when checking for privacy", false, bot.checkPrivacy(creatorTrio));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canValidateGroupAdminPrivacy() throws TelegramApiException {
|
||||
Update update = mock(Update.class);
|
||||
Message message = mock(Message.class);
|
||||
org.telegram.telegrambots.api.objects.User user = mock(User.class);
|
||||
Ability groupAdminAbility = getDefaultBuilder().privacy(GROUP_ADMIN).build();
|
||||
|
||||
Trio<Update, Ability, String[]> groupAdminTrio = Trio.of(update, groupAdminAbility, TEXT);
|
||||
|
||||
mockUser(update, message, user);
|
||||
when(message.isGroupMessage()).thenReturn(true);
|
||||
|
||||
ChatMember member = mock(ChatMember.class);
|
||||
when(member.getUser()).thenReturn(user);
|
||||
when(member.getUser()).thenReturn(user);
|
||||
|
||||
when(silent.execute(any(GetChatAdministrators.class))).thenReturn(Optional.of(newArrayList(member)));
|
||||
|
||||
assertEquals("Unexpected result when checking for privacy", true, bot.checkPrivacy(groupAdminTrio));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canRestrictNormalUsersFromGroupAdminAbilities() throws TelegramApiException {
|
||||
Update update = mock(Update.class);
|
||||
Message message = mock(Message.class);
|
||||
org.telegram.telegrambots.api.objects.User user = mock(User.class);
|
||||
Ability groupAdminAbility = getDefaultBuilder().privacy(GROUP_ADMIN).build();
|
||||
|
||||
Trio<Update, Ability, String[]> groupAdminTrio = Trio.of(update, groupAdminAbility, TEXT);
|
||||
|
||||
mockUser(update, message, user);
|
||||
when(message.isGroupMessage()).thenReturn(true);
|
||||
|
||||
when(silent.execute(any(GetChatAdministrators.class))).thenReturn(empty());
|
||||
|
||||
assertEquals("Unexpected result when checking for privacy", false, bot.checkPrivacy(groupAdminTrio));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canBlockAdminsFromCreatorAbilities() {
|
||||
Update update = mock(Update.class);
|
||||
@ -447,6 +490,25 @@ public class AbilityBotTest {
|
||||
assertEquals("Wrong ability was fetched", expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canFetchAbilityCaseInsensitive() {
|
||||
Update update = mock(Update.class);
|
||||
Message message = mock(Message.class);
|
||||
|
||||
String text = "/tESt";
|
||||
when(update.hasMessage()).thenReturn(true);
|
||||
when(update.getMessage()).thenReturn(message);
|
||||
when(update.getMessage().hasText()).thenReturn(true);
|
||||
when(message.getText()).thenReturn(text);
|
||||
|
||||
Trio<Update, Ability, String[]> trio = bot.getAbility(update);
|
||||
|
||||
Ability expected = bot.testAbility();
|
||||
Ability actual = trio.b();
|
||||
|
||||
assertEquals("Wrong ability was fetched", expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canFetchDefaultAbility() {
|
||||
Update update = mock(Update.class);
|
||||
|
@ -31,7 +31,7 @@ public class MapDBContextTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canRecoverDB() throws IOException {
|
||||
public void canRecoverDB() {
|
||||
Map<Integer, EndUser> users = db.getMap(USERS);
|
||||
Map<String, Integer> userIds = db.getMap(USER_ID);
|
||||
users.put(CREATOR.id(), CREATOR);
|
||||
|
Loading…
Reference in New Issue
Block a user