diff --git a/src/de/mrbesen/telegram/TelegramAPI.java b/src/de/mrbesen/telegram/TelegramAPI.java index c6b665b..d68baf0 100644 --- a/src/de/mrbesen/telegram/TelegramAPI.java +++ b/src/de/mrbesen/telegram/TelegramAPI.java @@ -1,29 +1,49 @@ package de.mrbesen.telegram; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.net.URL; +import java.util.LinkedList; +import java.util.Scanner; + +import javax.net.ssl.HttpsURLConnection; + +import org.json.JSONArray; import org.json.JSONObject; +import de.mrbesen.telegram.commands.CommandManager; +import de.mrbesen.telegram.event.Event; +import de.mrbesen.telegram.event.EventManager; +import de.mrbesen.telegram.event.events.UserSendAudioEvent; +import de.mrbesen.telegram.event.events.UserSendMessageEvent; +import objects.TMessage; +import objects.TUser; +import objects.JSONBased.Member; +import objects.TAudio; + public class TelegramAPI implements Runnable { - private int delay = 1500; + private static final String API_URL = "https://api.telegram.org/bot"; + private static final String TOKENREGEX = "^\\d{4,9}:[\\w-]{12,64}$"; + + private int msg_offset = 0; + private int updateInterval = 1500; private String apikey; private Thread thread; private boolean run = true; - private static final String api_url = "https://api.telegram.org/bot"; + private String helpmessage = "generic helppage\nuse TelegramAPI.setHelpText(java.lang.String) to change this."; + + private LinkedList users = new LinkedList<>(); + private CommandManager cmdmgr = new CommandManager(); + private EventManager evntmgr = new EventManager(); public TelegramAPI(String apikey) { + if (!apikey.matches(TOKENREGEX) ) { + throw new IllegalArgumentException("Invalid API key"); + } this.apikey = apikey; } - public void setDelay(int d) { - if(d < 0) - throw new IllegalArgumentException("delay is not allowed to be negative."); - delay = d; - } - - public int getDelay() { - return delay; - } - public void start() { if(thread == null) { run = true; @@ -33,12 +53,29 @@ public class TelegramAPI implements Runnable { throw new IllegalStateException("Still Running."); } } - - JSONObject request(String request) { - String url = api_url + apikey + "/" + request; + + public JSONObject request(String request, String parameter) throws IOException { //do https stuff - - return null;//error + URL url = new URL(API_URL + apikey + "/" + request); + HttpsURLConnection con = (HttpsURLConnection)url.openConnection(); + con.setDoInput(true); + con.setDoOutput(true); + OutputStreamWriter wr = new OutputStreamWriter(con.getOutputStream()); + wr.write(parameter); + wr.flush(); + System.out.println( this.getClass().getSimpleName() + ": request: " + request + " -> " + con.getResponseCode() + ", " + con.getResponseMessage()); + if(con.getResponseCode() == 200) { + Scanner s = new Scanner(con.getInputStream()); + StringBuilder sb_apianswer = new StringBuilder(); + while(s.hasNextLine()) { + sb_apianswer.append(s.nextLine()).append('\n'); + } + s.close(); + + return new JSONObject(sb_apianswer.toString()); + } else { + throw new IOException("API request returned HTTP " + con.getResponseCode() + " (" + con.getResponseMessage() + ") for action " + request ); + } } public void stop() { @@ -55,19 +92,182 @@ public class TelegramAPI implements Runnable { thread = null; } - public boolean isRunning() { - return thread.isAlive(); - } - @Override public void run() { while(run) { - //do stuff + fetchUpdates(); try { - Thread.sleep(delay); + Thread.sleep(updateInterval); } catch (InterruptedException e) { break; } } } + + public boolean isRunning() { + return thread.isAlive(); + } + + private void fetchUpdates() { + try { + processUpdates(request("getUpdates", "offset=" + msg_offset)); + } catch (IOException e) { + System.err.println("error getting updates."); + e.printStackTrace(); + } + } + + private void processUpdates(JSONObject object) { + if(object == null) + return; + JSONArray arr_results = object.getJSONArray("result"); + for(int i = 0; !arr_results.isNull(i); i++) { + JSONObject entry = arr_results.getJSONObject(i); + TelegramAPIUpdate upd = new TelegramAPIUpdate(entry, this); + msg_offset = (upd.update_id+1 > msg_offset ? upd.update_id+1 : msg_offset); + } + } + + public TUser getUser(String name) { + for(TUser us : users) { + if(us.getName().equals(name)) + return us; + } + return null; + } + + public TUser getUser(int id) { + for(TUser us : users) { + if(us.getID() == id) + return us; + } + return null; + } + + /** + * gets a user by id from the known user lists, + * if no user found, it creates a new User and adds it to the list. + * @param json + * @return + */ + public TUser getUser(JSONObject json) { + int id = json.getInt("id"); + TUser user = getUser(id); + if(user != null) + return user; + user = new TUser(json, this); + users.add(user); + return user; + } + + public void setHelpText(String helptext) { + helpmessage = ( helptext == null ? "" : helptext); + } + + public CommandManager getCommandManager() { + return cmdmgr; + } + + public EventManager getEventManager() { + return evntmgr; + } + + public void setUpdateInterval(int d) { + if(d < 0) + throw new IllegalArgumentException("UpdateInterval is not allowed to be negative."); + updateInterval = d; + } + + public String getHelpMessage() { + return helpmessage; + } + + public int getupdateInterval() { + return updateInterval; + } + + protected class TelegramAPIUpdate { + protected int update_id = 0; + private TMessage msg = null; + private boolean isCommand = false; + + protected TelegramAPIUpdate(JSONObject json, TelegramAPI api) { + update_id = json.getInt("update_id"); + + if(json.has("message")) { + msg = new TMessage(json.getJSONObject("message"), api); + + String text = msg.getText(); + if(text != null) { + if(text.startsWith("/" )) { + text = text.substring(1);//remove '/' + isCommand = true; + cmdmgr.onCommand(text, msg.getFrom()); + } else { + Event e = null; + if(getMsg().has(Member.audio)) { + e = new UserSendAudioEvent(getUser(), new TAudio((JSONObject) getMsg().get(Member.audio))); + } else if(getMsg().has(Member.video)) { + + } else if(getMsg().has(Member.document)) { + + } else if(getMsg().has(Member.invoice)) { + + } else if(getMsg().has(Member.location)) { + + } else if(getMsg().has(Member.video_note)) { + + } else if(getMsg().has(Member.game)) { + + } else if(getMsg().has(Member.contact)) { + + } else if(getMsg().has(Member.sticker)) { + + } else { + e = new UserSendMessageEvent(getMsg()); + } + getEventManager().callEvent(e); + } + } + } + } + + protected int getUpdate_id() { + return update_id; + } + + protected TMessage getMsg() { + return msg; + } + + protected TUser getUser() { + return msg.getFrom(); + } + + protected boolean isCommand() { + return isCommand; + } + } + + public enum JSONObjectType { + User, + Chat, + Message, + MessageEntity, + Audio, + Document, + Game, + Sticker, + Video, + Voice, + Videonote, + Location, + Venue, + Contact; + + boolean isArray = false; + void setArray(boolean b) { + isArray = b; + } + } } diff --git a/src/de/mrbesen/telegram/TelegramUser.java b/src/de/mrbesen/telegram/TelegramUser.java deleted file mode 100644 index c121f5d..0000000 --- a/src/de/mrbesen/telegram/TelegramUser.java +++ /dev/null @@ -1,18 +0,0 @@ -package de.mrbesen.telegram; - -public class TelegramUser { - - private int chatid; - private String uname; - private TelegramAPI api; - - public TelegramUser(int chatid, String uname, TelegramAPI api) { - this.chatid = chatid; - this.uname = uname; - this.api = api; - } - - public void sendMessage(String msg) { - - } -} diff --git a/src/de/mrbesen/telegram/commands/CommandHandler.java b/src/de/mrbesen/telegram/commands/CommandHandler.java index 7fc1c53..381fbe4 100644 --- a/src/de/mrbesen/telegram/commands/CommandHandler.java +++ b/src/de/mrbesen/telegram/commands/CommandHandler.java @@ -1,6 +1,6 @@ package de.mrbesen.telegram.commands; -import de.mrbesen.telegram.TelegramUser; +import objects.TUser; public interface CommandHandler { @@ -11,6 +11,6 @@ public interface CommandHandler { * @param args * @return */ - public boolean onCommand(TelegramUser sender, String cmd, String[] args); + public boolean onCommand(TUser sender, String cmd, String[] args); } diff --git a/src/de/mrbesen/telegram/commands/CommandManager.java b/src/de/mrbesen/telegram/commands/CommandManager.java index 3cf7751..9fdf648 100644 --- a/src/de/mrbesen/telegram/commands/CommandManager.java +++ b/src/de/mrbesen/telegram/commands/CommandManager.java @@ -3,29 +3,31 @@ package de.mrbesen.telegram.commands; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; -import de.mrbesen.telegram.TelegramUser; +import objects.TUser; public class CommandManager { - private static String CMDPATTERN = "^[\\w-]+$"; + private final static String CMDPATTERN = "^[\\w-]+$"; private Multimap handlerlist = ArrayListMultimap.create();//list of all registered CommandHandler public CommandManager() { } - void onCommand(String line, TelegramUser sender) {//called by the api (/-prefix allready removed) + public void onCommand(String line, TUser sender) {//called by the api (/-prefix already removed) line = line.trim(); String[] split = line.split(" ",2); String cmd = split[0].toLowerCase(); + boolean result = false; if(cmd.matches(CMDPATTERN)) { String[] args = new String[0]; if(split.length == 2) { args = split[1].split(" "); } //call +// System.out.println("cmd " + cmd + " args: " + args.length); for(CommandHandler cmdhand : handlerlist.get(cmd)) { try { - boolean result = cmdhand.onCommand(sender, cmd, args); + result = cmdhand.onCommand(sender, cmd, args); if(result) break; } catch(Throwable t) { @@ -33,8 +35,10 @@ public class CommandManager { t.printStackTrace(); } } - } else { - //unknown cmd! + } + //do smth. with result + if(!result) { + sender.sendHelpPage(); } } @@ -54,7 +58,7 @@ public class CommandManager { } if(cmd.matches(CMDPATTERN)) { handlerlist.put(cmd.toLowerCase(), handler); - //registered successfull! + //registered successfully! } else { throw new IllegalArgumentException("cmd contains unallowed characters. Allowed: a-zA-Z0-9_-"); } diff --git a/src/de/mrbesen/telegram/event/Event.java b/src/de/mrbesen/telegram/event/Event.java index 41b6408..2235cc0 100644 --- a/src/de/mrbesen/telegram/event/Event.java +++ b/src/de/mrbesen/telegram/event/Event.java @@ -1,16 +1,16 @@ package de.mrbesen.telegram.event; -import de.mrbesen.telegram.TelegramUser; +import objects.TUser; public class Event { - private TelegramUser user; + private TUser user; - public Event(TelegramUser u) { + public Event(TUser u) { user = u; } - public TelegramUser getUser() { + public TUser getUser() { return user; } diff --git a/src/de/mrbesen/telegram/event/EventHandler.java b/src/de/mrbesen/telegram/event/EventHandler.java index 82c3045..da814b3 100644 --- a/src/de/mrbesen/telegram/event/EventHandler.java +++ b/src/de/mrbesen/telegram/event/EventHandler.java @@ -2,8 +2,11 @@ package de.mrbesen.telegram.event; import static java.lang.annotation.ElementType.METHOD; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(METHOD) +@Retention(RetentionPolicy.RUNTIME) public @interface EventHandler { } diff --git a/src/de/mrbesen/telegram/event/EventManager.java b/src/de/mrbesen/telegram/event/EventManager.java index e2dc932..0f61732 100644 --- a/src/de/mrbesen/telegram/event/EventManager.java +++ b/src/de/mrbesen/telegram/event/EventManager.java @@ -8,9 +8,7 @@ public class EventManager { private ArrayList listeners = new ArrayList(); private ArrayList handler = new ArrayList(); - public EventManager() { - - } + public EventManager() {} @SuppressWarnings("unchecked") public void registerEvent(EventListener listener) { @@ -22,13 +20,20 @@ public class EventManager { } listeners.add(listener); //scanning handler + int found = 0; for(Method met : listener.getClass().getMethods()) { EventHandler h = met.getAnnotation(EventHandler.class); if(h != null) {//handler annotation vorhanden if(met.getParameterCount() == 1) {//only one parameter required - Class parameterclass = met.getParameters()[0].getClass(); - if(Event.class.isAssignableFrom(parameterclass) && !parameterclass.equals(Event.class)) {//the needed parameter is a Event-subclass) - handler.add(new EventMethod((Class) parameterclass, met, listener)); + Class parameterclass = met.getParameters()[0].getType(); + if(Event.class.isAssignableFrom(parameterclass) ) {//the needed parameter is a Event-subclass) + + if(met.getReturnType().equals(void.class)) { + if((met.getModifiers() & Method.PUBLIC) == Method.PUBLIC) { + handler.add(new EventMethod((Class) parameterclass, met, listener)); + found ++; + } + } } else { //warn / error? } @@ -37,9 +42,14 @@ public class EventManager { } } } + if(found == 0) { + System.err.println("no @EventHandler found!"); + } } public Event callEvent(Event e) { + if(e == null) + throw new NullPointerException("event is not allowed to be null!"); for(EventMethod met : handler) { try { e = met.callEvent(e); @@ -67,7 +77,9 @@ public class EventManager { try { met.invoke(listener, e); } catch(Throwable t) { + System.err.println("Failed to invoke event! at " + listener.getClass().getSimpleName()); t.printStackTrace(); + t.getCause().printStackTrace(); } } return e; diff --git a/src/de/mrbesen/telegram/event/events/UserSendAudioEvent.java b/src/de/mrbesen/telegram/event/events/UserSendAudioEvent.java new file mode 100644 index 0000000..9322c85 --- /dev/null +++ b/src/de/mrbesen/telegram/event/events/UserSendAudioEvent.java @@ -0,0 +1,19 @@ +package de.mrbesen.telegram.event.events; + +import de.mrbesen.telegram.event.Event; +import objects.TAudio; +import objects.TUser; + +public class UserSendAudioEvent extends Event { + + private TAudio audio; + + public UserSendAudioEvent(TUser u, TAudio a) { + super(u); + audio = a; + } + + public TAudio getAudio() { + return audio; + } +} diff --git a/src/de/mrbesen/telegram/event/events/UserSendMessageEvent.java b/src/de/mrbesen/telegram/event/events/UserSendMessageEvent.java new file mode 100644 index 0000000..4eecad1 --- /dev/null +++ b/src/de/mrbesen/telegram/event/events/UserSendMessageEvent.java @@ -0,0 +1,23 @@ +package de.mrbesen.telegram.event.events; + +import de.mrbesen.telegram.event.Event; +import objects.TMessage; +import objects.TUser; + +public class UserSendMessageEvent extends Event { + + private TMessage m; + + public UserSendMessageEvent(TMessage m) { + super(m.getFrom()); + this.m = m; + } + + public TMessage getMessage() { + return m; + } + + public TUser getUser() { + return m.getFrom(); + } +} diff --git a/src/de/mrbesen/test/Main.java b/src/de/mrbesen/test/Main.java new file mode 100644 index 0000000..6419ef5 --- /dev/null +++ b/src/de/mrbesen/test/Main.java @@ -0,0 +1,61 @@ +package de.mrbesen.test; + +import java.util.Random; +import java.util.Scanner; + +import de.mrbesen.telegram.TelegramAPI; +import de.mrbesen.telegram.commands.CommandHandler; +import de.mrbesen.telegram.event.EventHandler; +import de.mrbesen.telegram.event.EventListener; +import de.mrbesen.telegram.event.events.UserSendMessageEvent; +import objects.TInlineKeyboardMarkup; +import objects.TUser; +import objects.TUser.Status; + +public class Main implements CommandHandler, Runnable, EventListener { + + TelegramAPI api; + + public static void main(String[] args) { + new Main().run(); + } + + public Main() { + api = new TelegramAPI("577609137:AAGrlfMHAOQPzN6w9ceNSgFZZCbaiK4gAnw"); + api.getCommandManager().registerCommand("test", this); + api.getEventManager().registerEvent(this); + } + + @EventHandler + public void eventbla(UserSendMessageEvent e) { + e.getUser().sendMessage(e.getMessage().getText().replace(' ', '-')); + int i = new Random().nextInt(Status.values().length); + e.getUser().sendStatus(Status.values()[i]); + } + + @Override + public void run() { + api.start(); + Scanner s = new Scanner(System.in); + s.nextLine(); + s.close(); + api.stop(); + System.out.println("Terminated."); + } + + @Override + public boolean onCommand(TUser sender, String cmd, String[] args) { + if(cmd.equals("test")) { +// sender.sendMessage("Ja moin!"); + + sender.sendMessage("Links zu antippen:", + new TInlineKeyboardMarkup(2) + .addButton("gg","https://green-gaming.de", 1) + .addButton("ok", "https://oliver-kaestner.de", 1) + .addButton("mrB", "https://mrbesen.de", 2)); + + return true; + } + return false; + } +} diff --git a/src/objects/JSONBased.java b/src/objects/JSONBased.java new file mode 100644 index 0000000..74ee5d7 --- /dev/null +++ b/src/objects/JSONBased.java @@ -0,0 +1,70 @@ +package objects; + +import org.json.JSONObject; + +public class JSONBased { + + private JSONObject base_json = null; + + public JSONBased(JSONObject o) { + base_json = o; + } + + public boolean has(Member mem) { + if(base_json == null) + return false; + return base_json.has(mem.name()); + } + + public Object get(Member mem) { + if(base_json == null) + return null; + return base_json.get(mem.name()); + } + + public enum Member { + + // ===== MESSAGE ==== + forward_from_message_id,//int + forward_signature,//String + forward_date,//long + reply_to_message,//Message + edit_date,//long + media_group_id,//String + author_signature, //String + audio, //Audio + document, //Document + game, //Game + photo, //Array of PhotoSize + sticker, //Sticker + video, //Video + voice, //voice + video_note, //videoNote + caption, // String + contact, //Contact + location, //Location + venue, //Venue + new_chat_members, // Array of TUser + left_chat_members, // Array of TUser + new_chat_title, //String + new_chat_photo, //Array of Photosize + delete_chat_photo, // true + group_chat_created, // true + supergroup_chat_created, // true + channel_chat_created, //true + migrate_to_chat_id, // int + migrate_from_chat_id, // int + pinned_message, //TMessage + invoice, //Invoice + successful_payment, //SuccessfulPayment + connected_website, // String + //==== END MESSAGE ==== + + //==== AUDIO ====== + performer, //String + title,//String + mime_type, // String + file_size; // int + //===== END AUDIO ==== + } +} diff --git a/src/objects/TAudio.java b/src/objects/TAudio.java new file mode 100644 index 0000000..0d04777 --- /dev/null +++ b/src/objects/TAudio.java @@ -0,0 +1,12 @@ +package objects; + +import org.json.JSONObject; + +public class TAudio extends JSONBased { + private String file_id; + private int duration; + + public TAudio(JSONObject o) { + super(o); + } +} diff --git a/src/objects/TInlineKeyboardMarkup.java b/src/objects/TInlineKeyboardMarkup.java new file mode 100644 index 0000000..7cfcba8 --- /dev/null +++ b/src/objects/TInlineKeyboardMarkup.java @@ -0,0 +1,36 @@ +package objects; + +import java.util.ArrayList; + +import org.json.JSONArray; +import org.json.JSONObject; + +public class TInlineKeyboardMarkup implements TReplyMarkup { + + ArrayList arr_btn = new ArrayList<>(); + + public TInlineKeyboardMarkup(int rows) { + if(rows < 1) + throw new IllegalArgumentException("You need at least one row."); + arr_btn = new ArrayList(); + + for( int row = 0; row < rows; row++ ) { + arr_btn.add(new JSONArray()); + } + System.out.println("break here"); + } + + public TInlineKeyboardMarkup addButton(String title, String url, int row) { + arr_btn.get(row-1).put(new JSONObject().put("text", title).put("url", url)); + return this; + } + + @Override + public String toJSONString() { + JSONArray ja = new JSONArray(arr_btn); + JSONObject jo = new JSONObject(); + jo.put("inline_keyboard", ja); + return jo.toString(); + } + +} diff --git a/src/objects/TMessage.java b/src/objects/TMessage.java new file mode 100644 index 0000000..f2fab44 --- /dev/null +++ b/src/objects/TMessage.java @@ -0,0 +1,51 @@ +package objects; + +import org.json.JSONObject; + +import de.mrbesen.telegram.TelegramAPI; + +public class TMessage extends JSONBased { + + private int message_id; + private TUser from = null;//optional + private long date = -1; + private TUser forward_from = null; //optional + private String text = null;//optional + + protected TelegramAPI api; + + public void sendTo(TUser u) { + //if this message already exists forward, if not send directly. + } + + public TMessage(JSONObject json, TelegramAPI api) { + super(json); + this.api = api; + message_id = json.getInt("message_id"); + date = json.getLong("date"); + if(json.has("from")) + from = api.getUser(json.getJSONObject("from")); + + if(json.has("forward_from")) + forward_from = api.getUser(json.getJSONObject("forward_from")); + + if(json.has("text") ) + text = json.getString("text"); + } + + public TUser getFrom() { + return from; + } + + public String getText() { + return text; + } + + public long getDate() { + return date; + } + + public TUser getForward_from() { + return forward_from; + } +} diff --git a/src/objects/TReplyMarkup.java b/src/objects/TReplyMarkup.java new file mode 100644 index 0000000..dc51396 --- /dev/null +++ b/src/objects/TReplyMarkup.java @@ -0,0 +1,15 @@ +package objects; + +public interface TReplyMarkup { + + public static enum RelayMarkup { + InlineKeyboardMarkup, + ReplyKeyboardMarkup, + ReplyKeyboardRemove, + ForceReply; + } + + public String toJSONString(); + + +} diff --git a/src/objects/TUser.java b/src/objects/TUser.java new file mode 100644 index 0000000..ba520a8 --- /dev/null +++ b/src/objects/TUser.java @@ -0,0 +1,119 @@ +package objects; + +import java.io.IOException; +import java.net.URLEncoder; + +import org.json.JSONObject; + +import de.mrbesen.telegram.TelegramAPI; + +public class TUser { + + private int id; + + private String uname;//optional + private String firstname; + private String lastname;//optional + private String langcode; // optional + private boolean isBot = false; + + private TelegramAPI api = null; + + + TUser(int chatid, String uname, TelegramAPI api) { + this.id = chatid; + this.uname = uname; + this.api = api; + } + + public TUser(JSONObject o, TelegramAPI api) { + this.api = api; + firstname = o.getString("first_name"); + isBot = o.getBoolean("is_bot"); + id = o.getInt("id"); + if(o.has("last_name")) + lastname = o.getString("last_name"); + if(o.has("username")) + uname = o.getString("username"); + if(o.has("language_code")) + langcode = o.getString("language_code"); + } + + public String getFirstName() { + return firstname; + } + + public String getLastName() { + return lastname; + } + + public String getLangcode() { + return langcode; + } + + public boolean isBot() { + return isBot; + } + + public int getID() { + return id; + } + + public String getName() { + return uname; + } + + public void sendHelpPage() { + sendMessage(api.getHelpMessage()); + } + + public void sendMessage(String text) { + if(api == null) { + System.err.println("api == null!"); + return; + } + try { + api.request("sendMessage", "chat_id=" + id + "&text=" + URLEncoder.encode(text, "UTF-8")); + } catch (IOException e) { + e.printStackTrace(); + } + } + public void sendMessage(String text, TReplyMarkup rm) { + if(api == null) { + System.err.println("api == null!"); + return; + } + try { + api.request("sendMessage", "chat_id=" + id + + "&text=" + URLEncoder.encode(text, "UTF-8") + + "&reply_markup=" + URLEncoder.encode(rm.toJSONString(), "UTF-8") ); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void sendStatus(Status status) { + try { + api.request("sendChatAction", "chat_id="+id+"&action=" + status.getAPIName()); + } catch (Exception e) { + System.err.println("Failed to send status."); + } + } + + public enum Status { + Typing, + Upload_Photo, + Record_Video, + Upload_Video, + Record_Audio, + Upload_Audio, + Upload_Document, + Find_Location, + Record_Video_Note, + Upload_Video_Note; + + String getAPIName() { + return name().toLowerCase(); + } + } +}