TransferBot/src/main/java/it/cavallium/TransferUtils.java
2020-12-06 12:56:14 +01:00

262 lines
8.5 KiB
Java

package it.cavallium;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
import it.tdlight.jni.TdApi;
import it.tdlight.jni.TdApi.Chat;
import it.tdlight.jni.TdApi.ChatListMain;
import it.tdlight.jni.TdApi.ChatMember;
import it.tdlight.jni.TdApi.ChatMemberStatus;
import it.tdlight.jni.TdApi.ChatMembers;
import it.tdlight.jni.TdApi.ChatPosition;
import it.tdlight.jni.TdApi.Chats;
import it.tdlight.jni.TdApi.GetChat;
import it.tdlight.jni.TdApi.GetChats;
import it.tdlight.jni.TdApi.GetSupergroupMembers;
import it.tdlight.jni.TdApi.SupergroupFullInfo;
import it.tdlight.utils.MonoUtils;
import java.time.Duration;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import org.slf4j.event.Level;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
public class TransferUtils {
public static int chatIdToChatEntityId(long id) {
if (id <= -1000000000000L) {
return (int) (Math.abs(id) - 1000000000000L);
}
if (id < 0) {
return (int) Math.abs(id);
} else {
return (int) Math.abs(id);
}
}
public static long chatEntityIdToChatId(int chatEntityId, TChatType chatType) {
switch (chatType) {
case BASICGROUP:
return -Math.abs(chatEntityId);
case SUPERGROUP:
return -1000000000000L - (long) Math.abs(chatEntityId);
case USER:
return Math.abs(chatEntityId);
default:
throw new UnsupportedOperationException("Unsupported chat id type: " + chatEntityId);
}
}
public static long getLongPhoneNumber(PhoneNumber phoneNumber) {
return Long.parseLong(PhoneNumberUtil
.getInstance()
.format(phoneNumber, PhoneNumberFormat.E164)
.replace("+", ""));
}
private static class ChatIdAndOrderOffsets {
private final long chatIdOffset;
private final long orderOffset;
private ChatIdAndOrderOffsets(long chatIdOffset, long orderOffset) {
this.chatIdOffset = chatIdOffset;
this.orderOffset = orderOffset;
}
public long getChatIdOffset() {
return chatIdOffset;
}
public long getOrderOffset() {
return orderOffset;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ChatIdAndOrderOffsets that = (ChatIdAndOrderOffsets) o;
if (chatIdOffset != that.chatIdOffset) {
return false;
}
return orderOffset == that.orderOffset;
}
@Override
public int hashCode() {
int result = (int) (chatIdOffset ^ (chatIdOffset >>> 32));
result = 31 * result + (int) (orderOffset ^ (orderOffset >>> 32));
return result;
}
}
public static Mono<Set<Chat>> getAllHomeChatsSet(TransferClient client) {
return getAllHomeChats(client).collect(Collectors.toSet());
}
/**
* @return flux of home chats. They can repeat themselves
*/
public static Flux<Chat> getAllHomeChats(TransferClient client) {
App.getLogService().append(Level.DEBUG, "Getting the full chat list");
var singleScheduler = Schedulers.newSingle("getallchats");
return Mono
.deferWithContext((context) -> {
var offsets = Objects.requireNonNull(context.<AtomicReference<ChatIdAndOrderOffsets>>get("offsets"));
var offsetsValue = offsets.get();
App.getLogService().append(Level.TRACE, "Requesting GetChats");
return client.<Chats>send(new GetChats(new ChatListMain(),
offsetsValue.getOrderOffset(),
offsetsValue.getChatIdOffset(),
100
))
.flatMap(MonoUtils::orElseThrow)
.publishOn(singleScheduler)
.flatMapMany(chats -> Flux.fromStream(Arrays.stream(chats.chatIds).boxed()))
.flatMap(chatId -> {
App.getLogService().append(Level.TRACE, "Received ChatId: " + chatId);
return client.<Chat>send(new GetChat(chatId))
.publishOn(singleScheduler)
.flatMap(MonoUtils::orElseThrow);
})
.doOnNext(chat -> {
App.getLogService().append(Level.TRACE, "Received Chat: " + chat.toString().replace('\n', ' ').replace(" ", "").replace("\t", ""));
})
.collectList()
.doOnNext(chats -> {
App.getLogService().append(Level.TRACE, "Received Chats: " + chats.toString().replace('\n', ' ').replace(" ", "").replace("\t", ""));
if (!chats.isEmpty()) {
var lastChat = chats.get(chats.size() - 1);
getMainChatListPosition(lastChat.positions).ifPresentOrElse(lastChatPosition -> {
offsets.set(new ChatIdAndOrderOffsets(lastChat.id, lastChatPosition.order));
}, () -> {
offsets.set(new ChatIdAndOrderOffsets(lastChat.id, 0L));
});
} else {
offsets.set(new ChatIdAndOrderOffsets(9223372036854775807L, 0L));
}
})
.filter(chats1 -> !chats1.isEmpty())
.subscriberContext(context);
})
.repeatWhen(nFlux -> nFlux.takeWhile(n -> n > 0))
.doOnNext(chats -> {
App.getLogService().append(Level.DEBUG, "Received " + chats.size() + " home chats");
})
.flatMap(Flux::fromIterable)
.subscriberContext(ctx -> ctx.put("offsets",
new AtomicReference<>(new ChatIdAndOrderOffsets(0L, 9223372036854775807L))
))
.doOnTerminate(() -> App.getLogService().append(Level.DEBUG, "Home chats retrieved"));
}
private static Optional<ChatPosition> getMainChatListPosition(ChatPosition[] positions) {
for (ChatPosition position : positions) {
if (position.list.getConstructor() == ChatListMain.CONSTRUCTOR) {
return Optional.of(position);
}
}
return Optional.empty();
}
public static final String[] searchTable = {"a", "b", "c", "d", "e", "f", "g", "h", "i", "l", "m", "o", "p", "p", "q",
"r", "r", "s", "t", "u", "v", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "bot", "#"};
/**
* Reimport supergroup or channel members fully
*/
public static Set<Integer> getSupergroupMembers(TransferClient client, int supergroupId) {
var supergroupFullInfo = Objects.requireNonNull(
client.<SupergroupFullInfo>send(new TdApi.GetSupergroupFullInfo(supergroupId))
.flatMap(MonoUtils::orElseThrow)
.block());
var fullChatMemberList = new HashMap<Integer, ChatMemberStatus>(supergroupFullInfo.memberCount + 20);
final var timeout = 24 * 60 * 60 * 1000; /* 1 day */
final var limit = 200;
{
var offset = 0;
final var filter = new TdApi.SupergroupMembersFilterSearch("");
while (true) {
TdApi.ChatMembers members = client.<ChatMembers>send(new TdApi.GetSupergroupMembers(supergroupId,
filter,
offset,
limit
)).timeout(Duration.ofMinutes(2)).flatMap(MonoUtils::orElseThrow).blockOptional().orElseThrow();
for (ChatMember member : members.members) {
fullChatMemberList.put(member.userId, member.status);
}
offset += members.members.length;
if (members.members.length == 0) {
break;
}
}
}
// if upgradedFromBasicGroupId != 0 is a group
// if upgradedFromMaxMessageId != 0 is a group
if (supergroupFullInfo.upgradedFromBasicGroupId == 0 && supergroupFullInfo.upgradedFromMaxMessageId == 0
&& supergroupFullInfo.memberCount > 500) {
TdApi.Chat info = client
.<Chat>send(new TdApi.GetChat(chatEntityIdToChatId(supergroupId, TChatType.SUPERGROUP)))
.flatMap(MonoUtils::orElseThrow)
.timeout(Duration.ofMinutes(2))
.blockOptional()
.orElseThrow();
if (info.type.getConstructor() == TdApi.ChatTypeSupergroup.CONSTRUCTOR) {
var type = (TdApi.ChatTypeSupergroup) info.type;
if (type.isChannel) {
final var filterChannel = new TdApi.ChatMembersFilterMembers();
for (String q : searchTable) {
int offset = 0;
while (true) {
final var filter = new TdApi.SupergroupMembersFilterSearch(q);
var members = client.<ChatMembers>send(new GetSupergroupMembers(supergroupId, filter, offset, limit))
.flatMap(MonoUtils::orElseThrow)
.timeout(Duration.ofMinutes(2))
.blockOptional()
.orElseThrow();
for (int i = 0; i < members.members.length; i++) {
var member = members.members[i];
var userId = member.userId;
if (!fullChatMemberList.containsKey(userId)) {
fullChatMemberList.put(userId, member.status);
}
}
offset += members.members.length;
if (members.members.length == 0) {
break;
}
}
}
}
}
}
return fullChatMemberList.keySet();
}
}