diff options
Diffstat (limited to 'src/main/java/com/juick/server/TelegramBotManager.java')
-rw-r--r-- | src/main/java/com/juick/server/TelegramBotManager.java | 412 |
1 files changed, 412 insertions, 0 deletions
diff --git a/src/main/java/com/juick/server/TelegramBotManager.java b/src/main/java/com/juick/server/TelegramBotManager.java new file mode 100644 index 00000000..8e8d0104 --- /dev/null +++ b/src/main/java/com/juick/server/TelegramBotManager.java @@ -0,0 +1,412 @@ +/* + * Copyright (C) 2008-2017, 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.server; + +import com.juick.User; +import com.juick.service.component.*; +import com.juick.model.AnonymousUser; +import com.juick.model.CommandResult; +import com.juick.server.util.HttpUtils; +import com.juick.service.MessagesService; +import com.juick.service.TelegramService; +import com.juick.service.UserService; +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.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.formatters.PlainTextFormatter.formatPost; +import static com.juick.formatters.PlainTextFormatter.formatUrl; + +/** + * Created by vt on 12/05/16. + */ +public class TelegramBotManager implements NotificationListener { + private static final Logger logger = LoggerFactory.getLogger(TelegramBotManager.class); + + private TelegramBot bot; + + @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; + + private static final String MSG_LINK = "🔗"; + + @PostConstruct + public void init() { + if (StringUtils.isBlank(telegramToken)) { + logger.info("telegram token is not set, exiting"); + return; + } + bot = new TelegramBot(telegramToken); + if (!telegramDebug) { + try { + SetWebhook webhook = new SetWebhook().url("https://api.juick.com/tlgmbtwbhk"); + 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 -> { + 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()); + + 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.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 = 0; + try { + mid = Integer.valueOf(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.valueOf(uriComponents.getFragment()); + prefix = String.format("#%d/%d ", mid, rid); + } + CommandResult result = null; + try { + result = commandsManager.processCommand(user_from, prefix + text, attachment); + String messageTxt = StringUtils.isNotEmpty(result.getMarkdown()) ? result.getMarkdown() + : "Unknown error or unsupported command"; + telegramNotify(message.from().id().longValue(), messageTxt, new com.juick.Message()); + } catch (Exception e) { + logger.warn("telegram exception", e); + } + } 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.Message()); + } + } else { + telegramNotify(message.from().id().longValue(), + "Can not reply to this message", replyMessage.messageId(), new com.juick.Message()); + } + } else { + try { + CommandResult result = commandsManager.processCommand(user_from, text, attachment); + String messageTxt = StringUtils.isNotEmpty(result.getMarkdown()) ? result.getMarkdown() + : "Unknown error or unsupported command"; + telegramNotify(message.from().id().longValue(), messageTxt, new com.juick.Message()); + } catch (Exception e) { + logger.warn("telegram reply exception", e); + } + } + } + } + } 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)); + } + } + + 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.Message source) { + telegramNotify(chatId, msg, 0, source); + } + + public void telegramNotify(Long chatId, String msg, Integer replyTo, @Nonnull com.juick.Message source) { + String attachment = MessageUtils.attachmentUrl(source); + if (StringUtils.isEmpty(attachment)) { + SendMessage telegramMessage = new SendMessage(chatId, msg); + if (replyTo > 0) { + telegramMessage.replyToMessageId(replyTo); + } + telegramMessage.parseMode(ParseMode.Markdown).disableWebPagePreview(true); + bot.execute(telegramMessage, new Callback<SendMessage, SendResponse>() { + @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); + } + }); + } else { + SendPhoto telegramPhoto = new SendPhoto(chatId, attachment); + String trimmedPost = msg.length() > 1024 ? msg.substring(0, 1023) + "..." : msg; + telegramPhoto.caption(trimmedPost); + if (replyTo > 0) { + telegramPhoto.replyToMessageId(replyTo); + } + telegramPhoto.parseMode(ParseMode.Markdown); + bot.execute(telegramPhoto, new Callback<SendPhoto, SendResponse>() { + @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); + } + }); + } + } + + private void processTelegramResponse(Long chatId, SendResponse response, com.juick.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 MessageReadEvent(this, 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<SendMessage, SendResponse>() { + @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 processMessageEvent(MessageEvent messageEvent) { + com.juick.Message jmsg = messageEvent.getMessage(); + List<User> subscribedUsers = messageEvent.getUsers(); + 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)) { + com.juick.Message op = messagesService.getMessage(jmsg.getMid()); + 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)); + } + } + + @Override + public void processLikeEvent(LikeEvent likeEvent) { + User liker = likeEvent.getUser(); + com.juick.Message message = likeEvent.getMessage(); + List<User> subscribers = likeEvent.getSubscribers(); + 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.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.Message())); + } + + @Override + public void processPingEvent(PingEvent pingEvent) { + + } + + @Override + public void processMessageReadEvent(MessageReadEvent messageReadEvent) { + + } + + @Override + public void processTopEvent(TopEvent topEvent) { + com.juick.Message message = topEvent.getMessage(); + telegramService.getTelegramIdentifiers(Collections.singletonList(message.getUser())) + .forEach(c -> telegramNotify(c, String.format("Your [post](%s) became popular!", + formatUrl(message)), new com.juick.Message())); + } + + @Override + public void processSubscribeEvent(SubscribeEvent subscribeEvent) { + User subscriber = subscribeEvent.getUser(); + User target = subscribeEvent.getToUser(); + telegramService.getTelegramIdentifiers(Collections.singletonList(target)) + .forEach(c -> telegramNotify(c, String.format("%s subscribed to your blog", + MessageUtils.getMarkdownUser(subscriber)), new com.juick.Message())); + } +} |