package main; import com.mashape.unirest.http.HttpResponse; import com.mashape.unirest.http.JsonNode; import com.mashape.unirest.http.Unirest; import com.mashape.unirest.http.exceptions.UnirestException; import elements.Chat; import elements.Persistence; import elements.exceptions.*; import elements.messages.ChannelMessage; import elements.messages.GroupMessage; import elements.messages.Message; import elements.messages.PrivateMessage; import org.json.JSONArray; import org.json.JSONObject; import java.time.LocalDate; import java.util.*; /** * Created by Guillermo Serrahima on 4/1/16. */ public class Geiserbot { private static final String TOKEN = "267229954:AAHx49MXLmT1nT0QkccrSIzgmRVCQbjbJaQ"; private static final String USERNAME = "gserbot"; private static final String BASE_URL = "https://api.telegram.org/bot" + TOKEN; private static final String PARSE_MARKDOWN = "Markdown"; private static final String PARSE_HTML = "HTML"; private static final int WARNING_COUNT_START = 0; private static final int START_MESSAGE = WARNING_COUNT_START; private static final int HELP_MESSAGE = START_MESSAGE + 1; private static final int WARNING_UNKNOWN_ERROR = HELP_MESSAGE + 1; private static final int WARNING_NO_USERNAME = WARNING_UNKNOWN_ERROR + 1; private static final int WARNING_NOT_IMPLEMENTED = WARNING_NO_USERNAME + 1; private static final int WARNING_NO_GROUP = WARNING_NOT_IMPLEMENTED + 1; private static final int WARNING_COUNT_END = WARNING_NO_GROUP + 1; //private List commands; private Map chats; public Geiserbot() throws ReadErrorListException, ReadErrorChatException { chats = Persistence.getInstance().readChats(); } public String getUsername() { return USERNAME; } public String getToken() { return TOKEN; } public String myName() { return "R. Geiser Pont"; } //--- Http handling public HttpResponse sendMessage(Long chatId, String text) throws UnirestException { System.out.println("Sending message: \n" + text + "\n"); return Unirest.post(BASE_URL + "/sendMessage") .field("chat_id", chatId) .field("text", text) .asJson(); } public HttpResponse sendMessage(Long chatId, String text, String parseMode) throws UnirestException { System.out.println("Sending message: \n" + text + "\n"); return Unirest.post(BASE_URL + "/sendMessage") .field("chat_id", chatId) .field("text", text) .field("parse_mode", parseMode) .asJson(); } public HttpResponse sendMessage(String channel, String text) throws UnirestException { System.out.println("Sending message to " + channel + ": \n" + text + "\n"); return Unirest.post(BASE_URL + "/sendMessage") .field("chat_id", channel) .field("text", text) .asJson(); } public HttpResponse sendMessage(String channel, String text, String parseMode) throws UnirestException { System.out.println("Sending message to " + channel + ": \n" + text + "\n"); return Unirest.post(BASE_URL + "/sendMessage") .field("chat_id", channel) .field("text", text) .field("parse_mode", parseMode) .asJson(); } public HttpResponse replyMessage(Long chatId, Integer repliedMessage, String text) throws UnirestException { System.out.println("Sending message: \n" + text + "\n"); return Unirest.post(BASE_URL + "/sendMessage") .field("chat_id", chatId) .field("text", text) .field("reply_to_message_id", repliedMessage) .asJson(); } public HttpResponse getUpdates(Integer offset) throws UnirestException { return Unirest.post(BASE_URL + "/getUpdates") .field("offset", offset) .asJson(); } public HttpResponse getUpdates(Integer offset, Integer timeout) throws UnirestException { return Unirest.post(BASE_URL + "/getUpdates") .field("offset", offset) .field("timeout", timeout) .asJson(); } //--- bot thinking public void run() throws UnirestException { int last_upd_id = 0; HttpResponse response; System.out.println("Listening.\n"); while (true) { response = getUpdates(last_upd_id++); if (response.getStatus() == 200) { JSONArray responses = response.getBody().getObject().getJSONArray("result"); if (responses.isNull(0)) continue; else last_upd_id = responses.getJSONObject(responses.length() - 1).getInt("update_id") + 1; for (int i = 0; i < responses.length(); i++) { if(responses.getJSONObject(i).has("message")) { JSONObject message = responses.getJSONObject(i).getJSONObject("message"); processMessage(message); } } } } } private void processMessage(JSONObject message) throws UnirestException { //Process message into a nice and cozy java object Message m = null; try { m = Message.getMessage(message); } catch (MessageException e) { System.out.println(e.getMessage()); e.printStackTrace(); } //Console log printLog(true, m); //Actually read the message handleInput(m); } private void printLog(boolean full_log, Message m) { if (full_log) System.out.println("\n" + m); else { String log = "\nMessage"; if(m.isPrivate()) log += " received from a private chat"; else log += " received from chat " + m.getChatName(); log += " (chat id: " + m.getCid() + ")"; try { log += " by user @" + m.getUsername(); } catch (NoUsernameMessageException e) { log += " by user"; } catch (NoAuthorMessageException ignored) {} try { log += " named " + m.getAuthor(); } catch (NoAuthorMessageException ignored) {} System.out.println(log + "."); } } //--- Command handling private void handleInput(Message m) throws UnirestException { if (m.isChannel()) handleInputChannel((ChannelMessage) m); else if (m.isGroup()) handleInputGroup((GroupMessage) m); else if (m.isPrivate()) handleInputPrivate((PrivateMessage) m); } private boolean isCommand(String m, String command) { return (m.startsWith(command) && (!m.substring(command.length()).startsWith("@") || m.substring(command.length()).startsWith("@" + USERNAME))); } private String removeCommandHead(String m, String command) { String m2 = m.substring(command.length()).trim(); String uname = "@" + USERNAME; if(m2.startsWith(uname)) m2 = m2.substring(uname.length()); return m2; } private void handleInputGroup(GroupMessage m) throws UnirestException { try { if (m.isEvent()) { System.out.println("\nDetected group event."); handleGroupEvent(m); } else if (isCommand(m.getText(), "/start")) handleStart(m.getCid()); else if (isCommand(m.getText(), "/help")) handleHelp(m.getCid()); else if (isCommand(m.getText(), "/whoami")) handleWhoami(m); else if (isCommand(m.getText(), "/list")) handleList(m); else if (isCommand(m.getText(), "/foldl")) { if(removeCommandHead(m.getText(), "/foldl").startsWith("-v")) explainFoldHaskellVerbose(m.getCid()); else explainFoldHaskell(m.getCid()); } else if (isCommand(m.getText(), "/foldr")) { if(removeCommandHead(m.getText(), "/foldr").startsWith("-v")) explainFoldHaskellVerbose(m.getCid()); else explainFoldHaskell(m.getCid()); } } catch (NoTextMessageException ignored) {} } private void handleInputPrivate(PrivateMessage m) throws UnirestException { try { if (isCommand(m.getText(), "/start")) handleStart(m.getCid()); else if (isCommand(m.getText(), "/help")) handleHelp(m.getCid()); else if (isCommand(m.getText(), "/whoami")) handleWhoami(m); else if (isCommand(m.getText(), "/list")) handleList(m); else if (isCommand(m.getText(), "/foldl")) { if(removeCommandHead(m.getText(), "/foldl").startsWith("-v")) explainFoldHaskellVerbose(m.getCid()); else explainFoldHaskell(m.getCid()); } else if (isCommand(m.getText(), "/foldr")) { if(removeCommandHead(m.getText(), "/foldr").startsWith("-v")) explainFoldHaskellVerbose(m.getCid()); else explainFoldHaskell(m.getCid()); } } catch (NoTextMessageException ignored) {} } private void handleInputChannel(ChannelMessage m) throws UnirestException { } private void handleStart(long chat_id) throws UnirestException { sendMessage(chat_id, "Hola. Soy el bot personal de Guillermo Serrahima."); return; } private void handleHelp(long chat_id) throws UnirestException { String help = "Aquí tienes una lista de comandos que acepto:\n\n" + "/start - Repite el saludo inicial\n" + "/help - Saca esta lista\n" + "/whoami - Muestra información visible sobre ti mismo\n" + "/list [nombre de la lista] - Permite crear, mostrar o " + "modificar una lista\n"; sendMessage(chat_id, help.trim()); sendMessage(chat_id, "_" + myName() + " v1.0.0_", PARSE_MARKDOWN); } private void handleWhoami(Message m) throws UnirestException { String s = "Estamos en"; if(m.isPrivate()) s += " un chat privado"; else s += " el chat " + m.getChatName(); s += " de ID " + m.getCid() + ".\n"; try { s += "Eres " + m.getAuthor() + ", con"; try { s += " nombre de usuario @" + m.getUsername() +" e"; } catch (NoUsernameMessageException ignored) {} s += " ID de usuario " + m.getUid() + ".\n"; } catch (NoAuthorMessageException ignored) {} s += "En el PC que me mantiene, la fecha actual es " + LocalDate.now() + " en el formato ISO-8601.\n"; s+= "Tu mensaje tiene hora de " + m.getTimestamp() + "\n"; replyMessage(m.getCid(), m.getMid(), s.trim()); } private void handleGroupEvent(GroupMessage m) throws NoTextMessageException, UnirestException { System.out.print("Bot @" + USERNAME + " detects event: "); if(m.userJoined()) { System.out.println("JOINING"); System.out.println("New chat participant: " + m.getText()); if(m.getText().equals("@" + USERNAME)) handleStart(m.getCid()); } else if(m.userLeft()) { System.out.println("LEAVING"); System.out.println("Leaving chat participant: " + m.getText()); } else if(m.chatNameChanged()) { System.out.println("CHANGING CHAT NAME"); System.out.println("New chat name: " + m.getText()); try { chats.get(m.getCid()).setName(m.getText()); saveChatIndex(); } catch (SaveErrorChatException e) { sendMessage(m.getCid(), "Ha habido un error guardando el nuevo nombre de este chat."); } } else { System.out.println("UNKNOWN"); } } private void handleList(GroupMessage m) throws UnirestException, NoTextMessageException { String[] reading = m.getText().split(" ", 2); if (reading.length < 2) { sendMessage(m.getCid(), "Formato incorrecto. " + "Debes poner un espacio entre el comando /list y el nombre de la lista."); return; } String list = reading[1]; if(!exists(m.getCid())) { try { chats.put(m.getCid(), new Chat(m.getChatName(), m.getCid())); saveChatIndex(); } catch (SaveErrorChatException e) { sendMessage(m.getCid(), "Ha habido un error guardando los datos de este chat."); return; } } if(!exists(m.getCid(), list)) { try { chats.get(m.getCid()).add(list); saveChat(m.getCid()); } catch (SaveErrorChatException e) { sendMessage(m.getCid(), "Ha habido un error creando la lista."); return; } } sendMessage(m.getCid(), getWarning(WARNING_NOT_IMPLEMENTED)); } private void handleList(PrivateMessage m) throws UnirestException, NoTextMessageException { String[] reading = m.getText().split(" ", 2); if(reading.length < 2) { sendMessage(m.getCid(), "Formato incorrecto. " + "Debes poner un espacio entre el comando /list y el nombre de la lista."); return; } String list = reading[1]; if(!exists(m.getCid())) { try { chats.put(m.getCid(), new Chat(m.getChatName(), m.getCid())); saveChatIndex(); } catch (SaveErrorChatException e) { sendMessage(m.getCid(), "Ha habido un error guardando los datos de este chat."); return; } } if(!exists(m.getCid(), list)) { try { chats.get(m.getCid()).add(list); saveChat(m.getCid()); } catch (SaveErrorChatException e) { sendMessage(m.getCid(), "Ha habido un error creando la lista."); return; } } sendMessage(m.getCid(), getWarning(WARNING_NOT_IMPLEMENTED)); } private void explainFoldHaskell(long chat_id) throws UnirestException { String fold = "The recursion for `foldr f x ys` where `ys = [y1,y2,...,yk]` looks like\n" + "\n" + "`f y1 (f y2 (... (f yk x) ...))`\n" + "\n" + "whereas the recursion for `foldl f x ys` looks like\n" + "\n" + "`f (... (f (f x y1) y2) ...) yk`"; sendMessage(chat_id, fold, PARSE_MARKDOWN); } private void explainFoldHaskellVerbose(long chat_id) throws UnirestException { String fold = "The recursion for `foldr f x ys` where `ys = [y1,y2,...,yk]` looks like\n" + "\n" + "`f y1 (f y2 (... (f yk x) ...))`\n" + "\n" + "whereas the recursion for `foldl f x ys` looks like\n" + "\n" + "`f (... (f (f x y1) y2) ...) yk`\n" + "\n" + "An important difference here is that if the result of `f x y` can be computed using only the value of `x`, then `foldr` doesn't need to examine the entire list. For example:\n" + "\n" + "`foldr (&&) False (repeat False)`\n" + "returns `False` whereas\n" + "`foldl (&&) False (repeat False)`\n" + "never terminates. (Note: `repeat False` creates an infinite list where every element is `False`.)\n" + "\n" + "On the other hand, `foldl'` is tail recursive and strict. If you know that you'll have to traverse the whole list no matter what (e.g., summing the numbers in a list), then `foldl'` is more space- (and probably time-) efficient than `foldr`."; sendMessage(chat_id, fold, PARSE_MARKDOWN); } private boolean exists(long chat_id) { return chats.containsKey(chat_id); } private boolean exists(long chat_id, String list) { return chats.containsKey(chat_id) && chats.get(chat_id).exists(list); } private void saveChat(long chat_id) throws SaveErrorChatException { Persistence.getInstance().saveChat(chats.get(chat_id)); } private void saveChatIndex() throws SaveErrorChatException { Persistence.getInstance().saveChatIndex(chats); } private String getWarning(int warning_id) { switch (warning_id) { case WARNING_UNKNOWN_ERROR: return "Algo ha ido MUY mal."; case WARNING_NOT_IMPLEMENTED: return "Aún no está implementado."; case WARNING_NO_GROUP: return "Debes estar en un grupo para poder hacer eso."; case WARNING_NO_USERNAME: return "No tienes nombre de usuario. Regístrate con uno en la " + "configuración de Telegram para poder utilizarme."; default: return "Entrada incorrecta."; } } private String getFormatExample(int command) { return "Entrada incorrecta."; } }