/* * 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 . */ package com.juick.server; import com.juick.User; import com.juick.server.component.MessageEvent; import com.juick.server.util.HttpUtils; import com.juick.service.MessagesService; import com.juick.service.SubscriptionService; import com.juick.service.TelegramService; import com.juick.service.UserService; import com.juick.util.MessageUtils; import com.pengrad.telegrambot.BotUtils; import com.pengrad.telegrambot.Callback; import com.pengrad.telegrambot.TelegramBot; 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.ApplicationListener; import org.springframework.stereotype.Component; 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.URL; import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.Optional; import static com.juick.formatters.PlainTextFormatter.formatPost; import static com.juick.formatters.PlainTextFormatter.formatUrl; /** * Created by vt on 12/05/16. */ @Component public class TelegramBotManager implements ApplicationListener { private static final Logger logger = LoggerFactory.getLogger(TelegramBotManager.class); private TelegramBot bot; @Value("${telegram_token:12345678}") private String telegramToken; @Inject private TelegramService telegramService; @Inject private MessagesService messagesService; @Inject private SubscriptionService subscriptionService; @Inject private UserService userService; @Inject private ServerManager serverManager; @Value("${upload_tmp_dir:/var/www/juick.com/i/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); 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); } } public void processUpdate(String data) throws Exception { Update update = BotUtils.parseUpdate(data); 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(new User()); logger.info("Found juick user {}", user_from.getUid()); List chats = telegramService.getChats(); String username = message.from().username(); if (username == null) { username = message.from().firstName(); } if (!chats.contains(message.chat().id())) { telegramService.addChat(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)); } else { if (user_from.getUid() == 0) { telegramSignupNotify(message.from().id().longValue(), userService.getSignUpHashByTelegramID(message.from().id().longValue(), username)); } else { String attachment = 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()); URL fileURL = new URL(bot.getFullFilePath(response.file())); attachment = HttpUtils.downloadImage(fileURL, tmpDir); 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)) { if (text.equalsIgnoreCase("LOGIN") || text.equalsIgnoreCase("PING") || text.equalsIgnoreCase("HELP") || text.equalsIgnoreCase("/login") || 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, StringUtils.EMPTY); } else { Message replyMessage = message.replyToMessage(); if (replyMessage != null) { MessageEntity [] entities = replyMessage.entities(); if (entities == null) { entities = replyMessage.captionEntities(); } if (entities != null) { Optional 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 = Integer.valueOf(path.substring(1)); String prefix = String.format("#%d ", mid); if (StringUtils.isNotEmpty(uriComponents.getFragment())) { int rid = Integer.valueOf(uriComponents.getFragment()); prefix = String.format("#%d/%d ", mid, rid); } serverManager.processMessage(user_from, prefix + text, attachment); telegramNotify(message.from().id().longValue(), "Reply sent", StringUtils.EMPTY); } 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(), StringUtils.EMPTY); } } else { telegramNotify(message.from().id().longValue(), "Can not reply to this message", replyMessage.messageId(), StringUtils.EMPTY); } } else { serverManager.processMessage(user_from, text, attachment); telegramNotify(message.from().id().longValue(), "Message sent", StringUtils.EMPTY); } } } } } } private boolean isJuickLink(MessageEntity e) { return e.offset() == 0 && e.type().equals(MessageEntity.Type.text_link) && e.length() == 2; } @Override public void onApplicationEvent(@Nonnull MessageEvent event) { com.juick.Message jmsg = event.getMessage(); String msgUrl = formatUrl(jmsg); if (jmsg.getMid() > 0 && jmsg.getRid() == 0) { String msg = String.format("[%s](%s) %s", MSG_LINK, msgUrl, formatPost(jmsg, true)); List users = telegramService.getTelegramIdentifiers(subscriptionService.getSubscribedUsers(jmsg.getUser().getUid(), jmsg.getMid())); List chats = telegramService.getChats(); // registered subscribed users users.forEach(c -> telegramNotify(c, msg, MessageUtils.attachmentUrl(jmsg))); // anonymous chats.stream().filter(u -> telegramService.getUser(u) == 0).forEach(c -> telegramNotify(c, msg, MessageUtils.attachmentUrl(jmsg))); } else if (jmsg.getRid() > 0) { // get quote com.juick.Message msg = messagesService.getReply(jmsg.getMid(), jmsg.getRid()); String fmsg = String.format("[%s](%s) %s", MSG_LINK, msgUrl, formatPost(msg, true)); telegramService.getTelegramIdentifiers( subscriptionService.getUsersSubscribedToComments(jmsg.getMid(), jmsg.getUser().getUid()) ).forEach(c -> telegramNotify(c, fmsg, MessageUtils.attachmentUrl(jmsg))); } } public void telegramNotify(Long chatId, String msg, String attachment) { telegramNotify(chatId, msg, 0, attachment); } public void telegramNotify(Long chatId, String msg, Integer replyTo, String attachment) { 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() { @Override public void onResponse(SendMessage request, SendResponse response) { processTelegramResponse(chatId, response); } @Override public void onFailure(SendMessage request, IOException e) { logger.warn("telegram failure", e); } }); } else { SendPhoto telegramPhoto = new SendPhoto(chatId, attachment); String trimmedPost = msg.length() > 160 ? msg.substring(0, 159) + "..." : msg; telegramPhoto.caption(trimmedPost); if (replyTo > 0) { telegramPhoto.replyToMessageId(replyTo); } telegramPhoto.parseMode(ParseMode.Markdown); bot.execute(telegramPhoto, new Callback() { @Override public void onResponse(SendPhoto request, SendResponse response) { processTelegramResponse(chatId, response); } @Override public void onFailure(SendPhoto request, IOException e) { logger.warn("telegram failure", e); } }); } } private void processTelegramResponse(Long chatId, SendResponse response) { if (!response.isOk()) { if (response.errorCode() == 403) { // remove from anonymous chat telegramService.getChats().stream().filter(c -> c.equals(chatId)).findFirst().ifPresent( d -> { telegramService.deleteChat(d); logger.info("deleted {} chat", d); } ); int userId = telegramService.getUser(chatId); if (userId > 0) { User userToDelete = userService.getUserByUID(userId) .orElse(new User()); boolean status = telegramService.deleteTelegramUser(userToDelete.getUid()); logger.info("deleting telegram id of @{} : {}", userToDelete.getName(), status); boolean chatStatus = telegramService.deleteChat(chatId); logger.info("deleting telegram chat {} : {}", chatId, chatStatus); } } else { logger.warn("error response, isOk: {}, errorCode: {}, description: {}", response.isOk(), response.errorCode(), response.description()); } } } 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); } }); } }