aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/com/juick/TelegramBotManager.java
diff options
context:
space:
mode:
authorGravatar Vitaly Takmazov2020-04-04 01:15:01 +0300
committerGravatar Vitaly Takmazov2020-04-04 01:15:01 +0300
commita608baeed738894433aacfa041e2617f60ce959f (patch)
tree1e0de7056417ff0833ae3d4600de9fec6eb81631 /src/main/java/com/juick/TelegramBotManager.java
parent7a2f89266c8f6337e4e81a2fd8488e0f80f4f9bd (diff)
Initialize all components from configuration
Diffstat (limited to 'src/main/java/com/juick/TelegramBotManager.java')
-rw-r--r--src/main/java/com/juick/TelegramBotManager.java465
1 files changed, 465 insertions, 0 deletions
diff --git a/src/main/java/com/juick/TelegramBotManager.java b/src/main/java/com/juick/TelegramBotManager.java
new file mode 100644
index 00000000..3538a27b
--- /dev/null
+++ b/src/main/java/com/juick/TelegramBotManager.java
@@ -0,0 +1,465 @@
+/*
+ * Copyright (C) 2008-2020, Juick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.juick;
+
+import com.juick.model.User;
+import com.juick.model.AnonymousUser;
+import com.juick.model.CommandResult;
+import com.juick.www.api.SystemActivity;
+import com.juick.util.HttpUtils;
+import com.juick.service.MessagesService;
+import com.juick.service.TelegramService;
+import com.juick.service.UserService;
+import com.juick.service.component.SystemEvent;
+import com.juick.service.component.NotificationListener;
+import com.juick.service.component.PingEvent;
+import com.juick.util.MessageUtils;
+import com.pengrad.telegrambot.Callback;
+import com.pengrad.telegrambot.TelegramBot;
+import com.pengrad.telegrambot.UpdatesListener;
+import com.pengrad.telegrambot.model.Message;
+import com.pengrad.telegrambot.model.MessageEntity;
+import com.pengrad.telegrambot.model.PhotoSize;
+import com.pengrad.telegrambot.model.Update;
+import com.pengrad.telegrambot.model.request.ParseMode;
+import com.pengrad.telegrambot.request.GetFile;
+import com.pengrad.telegrambot.request.SendMessage;
+import com.pengrad.telegrambot.request.SendPhoto;
+import com.pengrad.telegrambot.request.SetWebhook;
+import com.pengrad.telegrambot.response.GetFileResponse;
+import com.pengrad.telegrambot.response.SendResponse;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.Pair;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.web.util.UriComponents;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import javax.annotation.Nonnull;
+import javax.annotation.PostConstruct;
+import javax.inject.Inject;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URL;
+import java.util.*;
+
+import static com.juick.util.formatters.PlainTextFormatter.formatPost;
+import static com.juick.util.formatters.PlainTextFormatter.formatUrl;
+
+/**
+ * Created by vt on 12/05/16.
+ */
+public class TelegramBotManager implements NotificationListener {
+ private static final Logger logger = LoggerFactory.getLogger("Telegram");
+
+ private TelegramBot bot;
+
+ @Value("${telegram_api_url:}")
+ private String apiUrl;
+ @Value("${telegram_file_api_url:}")
+ private String fileApiUrl;
+ @Value("${telegram_webhook_url:}")
+ private String webhookUrl;
+ @Value("${telegram_token:12345678}")
+ private String telegramToken;
+ @Value("${telegram_debug:false}")
+ private boolean telegramDebug;
+ @Inject
+ private TelegramService telegramService;
+ @Inject
+ private MessagesService messagesService;
+ @Inject
+ private UserService userService;
+ @Inject
+ private CommandsManager commandsManager;
+ @Inject
+ private ApplicationEventPublisher applicationEventPublisher;
+ @Value("${upload_tmp_dir:#{systemEnvironment['TEMP'] ?: '/tmp'}}")
+ private String tmpDir;
+ @Value("${service_user:juick}")
+ private String serviceUser;
+
+ private static final String MSG_LINK = "🔗";
+
+ @PostConstruct
+ public void init() {
+ TelegramBot.Builder tgBuilder = new TelegramBot.Builder(telegramToken);
+ if (StringUtils.isNotEmpty(apiUrl)) {
+ tgBuilder.apiUrl(apiUrl).fileApiUrl(fileApiUrl);
+ }
+ bot = tgBuilder.build();
+ if (!telegramDebug) {
+ try {
+ SetWebhook webhook = new SetWebhook().url(webhookUrl);
+ if (!bot.execute(webhook).isOk()) {
+ logger.error("error setting webhook");
+ }
+ } catch (Exception e) {
+ logger.warn("couldn't initialize telegram bot", e);
+ }
+ } else {
+ bot.setUpdatesListener(updates -> {
+ logger.info("got updates: {}", updates);
+ updates.forEach(this::processUpdate);
+ return UpdatesListener.CONFIRMED_UPDATES_ALL;
+ });
+ }
+ }
+
+ public void processUpdate(Update update) {
+ Message message = update.message();
+ if (update.message() == null) {
+ message = update.editedMessage();
+ if (message == null) {
+ logger.error("error parsing telegram update: {}", update);
+ return;
+ }
+ User user_from = userService.getUserByUID(telegramService.getUser(message.chat().id())).orElse(AnonymousUser.INSTANCE);
+ logger.info("Found juick user {}", user_from.getUid());
+ Optional<Pair<Integer, Integer>> originalMessageData = messagesService.findMessageByProperty("durovId",
+ String.valueOf(message.messageId()));
+ if (originalMessageData.isPresent()) {
+ int mid = originalMessageData.get().getLeft();
+ int rid = originalMessageData.get().getRight();
+ // TODO: this is copypaste from api, need switch to api
+ com.juick.model.Message originalMessage = rid == 0 ? messagesService.getMessage(mid).orElseThrow(IllegalStateException::new)
+ : messagesService.getReply(mid, rid);
+ User author = originalMessage.getUser();
+ String newMessageText = StringUtils.defaultString(message.text());
+ if (user_from.equals(author) && canUpdateMessage(originalMessage, newMessageText)) {
+ if (messagesService.updateMessage(mid, rid, newMessageText)) {
+ telegramNotify(message.chat().id(), "Message updated", new com.juick.model.Message());
+ return;
+ }
+ }
+ telegramNotify(message.chat().id(), "Error updating message", new com.juick.model.Message());
+ }
+ } else {
+ User user_from = userService.getUserByUID(telegramService.getUser(message.chat().id())).orElse(AnonymousUser.INSTANCE);
+ logger.info("Found juick user {}", user_from.getUid());
+
+ String username = message.from().username();
+ if (username == null) {
+ username = message.from().firstName();
+ }
+ if (!user_from.isAnonymous()) {
+ URI attachment = URI.create(StringUtils.EMPTY);
+ if (message.photo() != null) {
+ String fileId = Arrays.stream(message.photo()).max(Comparator.comparingInt(PhotoSize::fileSize))
+ .orElse(new PhotoSize()).fileId();
+ if (StringUtils.isNotEmpty(fileId)) {
+ GetFile request = new GetFile(fileId);
+ GetFileResponse response = bot.execute(request);
+ logger.info("got file {}", response.file());
+ try {
+ URL fileURL = new URL(bot.getFullFilePath(response.file()));
+ attachment = HttpUtils.downloadImage(fileURL, tmpDir);
+ } catch (Exception e) {
+ logger.warn("attachment exception", e);
+ }
+ logger.info("received {}", attachment);
+ }
+ }
+ String text = message.text();
+ if (StringUtils.isBlank(text)) {
+ text = message.caption();
+ }
+ if (StringUtils.isBlank(text)) {
+ text = StringUtils.EMPTY;
+ }
+ if (StringUtils.isNotEmpty(text) || StringUtils.isNotEmpty(attachment.toString())) {
+ if (text.equalsIgnoreCase("LOGIN")
+ || text.equalsIgnoreCase("PING")
+ || text.equalsIgnoreCase("HELP")
+ || text.equalsIgnoreCase("/login")
+ || text.equalsIgnoreCase("/logout")
+ || text.equalsIgnoreCase("/start")
+ || text.equalsIgnoreCase("/help")) {
+ String msgUrl = "http://juick.com/login?hash=" + userService.getHashByUID(user_from.getUid());
+ String msg = String.format("Hi, %s!\nYou can post messages and images to Juick there.\n" +
+ "Tap to [log into website](%s) to get more info", user_from.getName(), msgUrl);
+ telegramNotify(message.from().id().longValue(), msg, new com.juick.model.Message());
+ } else {
+ Message replyMessage = message.replyToMessage();
+ if (replyMessage != null) {
+ MessageEntity[] entities = replyMessage.entities();
+ if (entities == null) {
+ entities = replyMessage.captionEntities();
+ }
+ if (entities != null) {
+ Optional<MessageEntity> juickLink = Arrays.stream(entities)
+ .filter(this::isJuickLink)
+ .findFirst();
+ if (juickLink.isPresent()) {
+ if (StringUtils.isNotEmpty(juickLink.get().url())) {
+ UriComponents uriComponents = UriComponentsBuilder.fromUriString(
+ juickLink.get().url()).build();
+ String path = uriComponents.getPath();
+ if (StringUtils.isNotEmpty(path) && path.length() > 1) {
+ int mid;
+ try {
+ mid = Integer.parseInt(path.substring(3));
+ } catch (NumberFormatException e) {
+ logger.warn("wrong mid received");
+ return;
+ }
+ String prefix = String.format("#%d ", mid);
+ if (StringUtils.isNotEmpty(uriComponents.getFragment())) {
+ int rid = Integer.parseInt(uriComponents.getFragment());
+ prefix = String.format("#%d/%d ", mid, rid);
+ }
+ executeCommand(message.messageId(), message.from().id().longValue(),
+ user_from, prefix + text, attachment);
+ } else {
+ logger.warn("invalid path: {}", path);
+ }
+ } else {
+ logger.warn("invalid entity: {}", juickLink);
+ }
+ } else {
+ telegramNotify(message.from().id().longValue(),
+ "Can not reply to this message", replyMessage.messageId(), new com.juick.model.Message());
+ }
+ } else {
+ telegramNotify(message.from().id().longValue(),
+ "Can not reply to this message", replyMessage.messageId(), new com.juick.model.Message());
+ }
+ } else {
+ executeCommand(message.messageId(), message.from().id().longValue(),
+ user_from, text, attachment);
+ }
+ }
+ }
+ messagesService.getUnread(user_from).forEach(mid -> messagesService.setRead(user_from, mid));
+ } else {
+ List<Long> chats = telegramService.getAnonymous();
+ if (!chats.contains(message.chat().id())) {
+ logger.info("added chat with {}", username);
+ telegramService.createTelegramUser(message.from().id(), username);
+ }
+ telegramSignupNotify(message.from().id().longValue(), userService.getSignUpHashByTelegramID(message.from().id().longValue(), username));
+ }
+ }
+ }
+
+ /*
+ validate user input
+ */
+ private boolean canUpdateMessage(com.juick.model.Message message, String newData) {
+ if (StringUtils.isEmpty(newData)) {
+ // allow empty text only when image is present
+ return StringUtils.isNotEmpty(message.getAttachmentType());
+ }
+ return true;
+ }
+
+ private void executeCommand(Integer messageId, Long userId, User user_from, String text, URI attachment) {
+ try {
+ CommandResult result = commandsManager.processCommand(user_from, text, attachment);
+ if (result.getNewMessage().isPresent()) {
+ com.juick.model.Message newMessage = result.getNewMessage().get();
+ messagesService.setMessageProperty(newMessage.getMid(), newMessage.getRid(), "durovId",
+ String.valueOf(messageId));
+ }
+ String messageTxt = StringUtils.isNotEmpty(result.getMarkdown()) ? result.getMarkdown()
+ : "Unknown error or unsupported command";
+ telegramNotify(userId, messageTxt, new com.juick.model.Message());
+ } catch (Exception e) {
+ logger.warn("telegram exception", e);
+ }
+ }
+
+ private boolean isJuickLink(MessageEntity e) {
+ return e.offset() == 0 && e.type().equals(MessageEntity.Type.text_link) && e.length() == 2;
+ }
+
+ public void telegramNotify(Long chatId, String msg, @Nonnull com.juick.model.Message source) {
+ telegramNotify(chatId, msg, 0, source);
+ }
+
+ public void telegramNotify(Long chatId, String msg, Integer replyTo, @Nonnull com.juick.model.Message source) {
+ String attachment = MessageUtils.attachmentUrl(source);
+ boolean isSendTxt = true;
+ if (!StringUtils.isEmpty(attachment)) {
+ SendPhoto telegramPhoto = new SendPhoto(chatId, attachment);
+ if (replyTo > 0) {
+ telegramPhoto.replyToMessageId(replyTo);
+ }
+ telegramPhoto.parseMode(ParseMode.Markdown);
+ if(msg.length() < 1024) {
+ telegramPhoto.caption(msg);
+ isSendTxt = false;
+ }
+ bot.execute(telegramPhoto, new Callback<>() {
+ @Override
+ public void onResponse(SendPhoto request, SendResponse response) {
+ processTelegramResponse(chatId, response, source);
+ }
+
+ @Override
+ public void onFailure(SendPhoto request, IOException e) {
+ logger.warn("telegram failure", e);
+ }
+ });
+ }
+ if (isSendTxt){
+ SendMessage telegramMessage = new SendMessage(chatId, msg);
+ if (replyTo > 0) {
+ telegramMessage.replyToMessageId(replyTo);
+ }
+ telegramMessage.parseMode(ParseMode.Markdown).disableWebPagePreview(true);
+ bot.execute(telegramMessage, new Callback<>() {
+ @Override
+ public void onResponse(SendMessage request, SendResponse response) {
+ processTelegramResponse(chatId, response, source);
+ }
+
+ @Override
+ public void onFailure(SendMessage request, IOException e) {
+ logger.warn("telegram failure", e);
+ }
+ });
+ }
+ }
+
+ private void processTelegramResponse(Long chatId, SendResponse response, com.juick.model.Message source) {
+ int userId = telegramService.getUser(chatId);
+ if (!response.isOk()) {
+ if (response.errorCode() == 403) {
+ // remove from anonymous users
+ telegramService.getAnonymous().stream().filter(c -> c.equals(chatId)).findFirst().ifPresent(
+ d -> {
+ telegramService.deleteAnonymous(d);
+ logger.info("deleted {} chat", d);
+ }
+ );
+ if (userId > 0) {
+ User userToDelete = userService.getUserByUID(userId)
+ .orElseThrow(IllegalStateException::new);
+ boolean status = telegramService.deleteTelegramUser(userToDelete.getUid());
+ logger.info("deleting telegram id of @{} : {}", userToDelete.getName(), status);
+ }
+ } else {
+ logger.warn("error response, isOk: {}, errorCode: {}, description: {}",
+ response.isOk(), response.errorCode(), response.description());
+ }
+ } else {
+ if (MessageUtils.isReply(source)) {
+ messagesService.setLastReadComment(userService.getUserByUID(userId)
+ .orElseThrow(IllegalStateException::new), source.getMid(), source.getRid());
+ User user = userService.getUserByUID(userId).orElseThrow(IllegalStateException::new);
+ userService.updateLastSeen(user);
+ applicationEventPublisher.publishEvent(
+ new SystemEvent(this, SystemActivity.read(user, source)));
+ }
+ }
+ }
+
+ public void telegramSignupNotify(Long telegramId, String hash) {
+ bot.execute(new SendMessage(telegramId,
+ String.format("You are subscribed to all Juick messages. " +
+ "[Create or link](http://juick.com/signup?type=durov&hash=%s) " +
+ "an existing Juick account to get your subscriptions and ability to post messages", hash))
+ .parseMode(ParseMode.Markdown), new Callback<>() {
+ @Override
+ public void onResponse(SendMessage request, SendResponse response) {
+ logger.info("got response: {}", response.message());
+ }
+
+ @Override
+ public void onFailure(SendMessage request, IOException e) {
+ logger.warn("telegram failure", e);
+ }
+ });
+ }
+
+ @Override
+ public void processSystemEvent(SystemEvent systemEvent) {
+ var activity = systemEvent.getActivity();
+ var type = activity.getType();
+ if (type.equals(SystemActivity.ActivityType.message)) {
+ processMessage(activity.getMessage(), activity.getTo());
+ } else if (type.equals(SystemActivity.ActivityType.like)) {
+ if (systemEvent.getActivity().getFrom().getName().equals(serviceUser)) {
+ processTop(systemEvent.getActivity().getMessage());
+ } else {
+ processLike(activity.getFrom(), activity.getMessage(), activity.getTo());
+ }
+ } else if (type.equals(SystemActivity.ActivityType.follow)) {
+ processFollow(activity.getFrom(), activity.getTo());
+ }
+ }
+ private void processMessage(com.juick.model.Message jmsg, List<User> subscribedUsers) {
+ if (jmsg.isService()) {
+ return;
+ }
+ String msgUrl = formatUrl(jmsg);
+ if (MessageUtils.isPM(jmsg)) {
+ telegramService.getTelegramIdentifiers(Collections.singletonList(jmsg.getTo()))
+ .forEach(c -> telegramNotify(c, formatPost(jmsg, true), jmsg));
+ } else if (MessageUtils.isReply(jmsg)) {
+ String fmsg = String.format("[%s](%s) %s", MSG_LINK, msgUrl, formatPost(jmsg, true));
+ telegramService.getTelegramIdentifiers(
+ subscribedUsers
+ ).forEach(c -> telegramNotify(c, fmsg, jmsg));
+ } else {
+ String msg = String.format("[%s](%s) %s", MSG_LINK, msgUrl, formatPost(jmsg, true));
+
+ List<Long> users = telegramService.getTelegramIdentifiers(subscribedUsers);
+ List<Long> chats = telegramService.getAnonymous();
+ // registered subscribed users
+
+ users.forEach(c -> telegramNotify(c, msg, jmsg));
+ // anonymous
+ chats.stream().filter(u -> telegramService.getUser(u) == 0).forEach(c -> telegramNotify(c, msg, jmsg));
+ }
+ }
+
+ private void processLike(User liker, com.juick.model.Message message, List<User> subscribers) {
+ if (!liker.getName().equals(serviceUser)) {
+ logger.info("Like received in tg listener");
+ if (!userService.isInBLAny(message.getUser().getUid(), liker.getUid())) {
+ telegramService.getTelegramIdentifiers(Collections.singletonList(message.getUser()))
+ .forEach(c -> telegramNotify(c, String.format("%s recommends your [post](%s)",
+ MessageUtils.getMarkdownUser(liker), formatUrl(message)), new com.juick.model.Message()));
+ }
+ telegramService.getTelegramIdentifiers(subscribers)
+ .forEach(c -> telegramNotify(c, String.format("%s recommends you someone's [post](%s)",
+ MessageUtils.getMarkdownUser(liker), formatUrl(message)), new com.juick.model.Message()));
+ }
+ }
+
+ @Override
+ public void processPingEvent(PingEvent pingEvent) {
+
+ }
+
+ private void processTop(com.juick.model.Message message) {
+ telegramService.getTelegramIdentifiers(Collections.singletonList(message.getUser()))
+ .forEach(c -> telegramNotify(c, String.format("Your [post](%s) became popular!",
+ formatUrl(message)), new com.juick.model.Message()));
+ }
+
+ private void processFollow(User subscriber, List<User> target) {
+ telegramService.getTelegramIdentifiers(target)
+ .forEach(c -> telegramNotify(c, String.format("%s subscribed to your blog",
+ MessageUtils.getMarkdownUser(subscriber)), new com.juick.model.Message()));
+ }
+}