TelegramAPI/src/main/java/de/mrbesen/telegram/TelegramAPI.java

588 lines
15 KiB
Java
Raw Normal View History

2018-07-17 19:23:54 +02:00
package de.mrbesen.telegram;
2018-07-18 04:44:04 +02:00
import java.io.IOException;
2019-02-08 03:15:31 +01:00
import java.io.InputStream;
2018-07-18 04:44:04 +02:00
import java.io.OutputStreamWriter;
import java.net.URL;
2019-02-11 17:24:21 +01:00
import java.net.URLEncoder;
2018-07-18 04:44:04 +02:00
import java.util.LinkedList;
import java.util.Scanner;
import javax.net.ssl.HttpsURLConnection;
2019-04-11 01:41:15 +02:00
import de.mrbesen.telegram.commands.JSONCommandHandler;
2018-07-18 04:44:04 +02:00
import org.json.JSONArray;
2018-07-17 19:23:54 +02:00
import org.json.JSONObject;
2019-02-07 06:30:28 +01:00
import de.mrbesen.telegram.AsyncHandler.Callback;
import de.mrbesen.telegram.AsyncHandler.Task;
2019-02-07 18:35:42 +01:00
import de.mrbesen.telegram.MessageBuilder.AsyncSendable;
2018-07-18 22:31:05 +02:00
import de.mrbesen.telegram.MessageBuilder.SendableMessage;
2018-07-18 04:44:04 +02:00
import de.mrbesen.telegram.commands.CommandManager;
import de.mrbesen.telegram.event.Event;
import de.mrbesen.telegram.event.EventManager;
2019-02-06 23:52:15 +01:00
import de.mrbesen.telegram.event.events.UserCallbackEvent;
2018-07-18 04:44:04 +02:00
import de.mrbesen.telegram.event.events.UserSendAudioEvent;
import de.mrbesen.telegram.event.events.UserSendMessageEvent;
2018-09-15 17:49:25 +02:00
import de.mrbesen.telegram.log.Log;
import de.mrbesen.telegram.log.Log4JLog;
import de.mrbesen.telegram.log.SimpleLog;
2018-07-18 22:31:05 +02:00
import de.mrbesen.telegram.objects.JSONBased.Member;
import de.mrbesen.telegram.objects.TAudio;
import de.mrbesen.telegram.objects.TMessage;
2019-02-11 17:39:59 +01:00
import de.mrbesen.telegram.objects.TReplyMarkup;
import de.mrbesen.telegram.objects.TUser;
2018-09-15 17:49:25 +02:00
import de.mrbesen.telegram.objects.TUser.Status;
2018-07-18 04:44:04 +02:00
2018-07-17 19:23:54 +02:00
public class TelegramAPI implements Runnable {
2018-07-18 04:44:04 +02:00
private static final String API_URL = "https://api.telegram.org/bot";
2019-11-20 17:41:51 +01:00
private static final String TOKENREGEX = "^\\d{4,10}:[\\w-]{12,64}$";
2019-03-29 17:59:23 +01:00
private static final int TELEGRAMFILESIZELIMIT = 20000000;//20MB filesize https://core.telegram.org/bots/api#sending-files
public static final String APIVERSION = "3.8";//May 18, 2019
2018-07-18 04:44:04 +02:00
private int msg_offset = 0;
private int updateInterval = 60;
2018-07-17 19:23:54 +02:00
private String apikey;
2019-04-28 22:26:31 +02:00
private String botname;
2018-07-17 19:23:54 +02:00
private Thread thread;
private boolean run = true;
private boolean longpolling = true;
2019-02-25 02:51:17 +01:00
public boolean isLongpolling() {
return longpolling;
}
public void setLongpolling(boolean longpolling) {
this.longpolling = longpolling;
}
2018-07-18 04:44:04 +02:00
private String helpmessage = "generic helppage\nuse TelegramAPI.setHelpText(java.lang.String) to change this.";
2019-02-08 07:27:00 +01:00
//stats
protected int fetchedUpdates = 0;
protected long start = 0;
2019-02-11 17:24:21 +01:00
2018-07-18 04:44:04 +02:00
private LinkedList<TUser> users = new LinkedList<>();
2019-02-25 02:51:17 +01:00
private CommandManager cmdmgr = new CommandManager(this);
2018-07-18 04:44:04 +02:00
private EventManager evntmgr = new EventManager();
2019-02-07 06:30:28 +01:00
//async
private AsyncHandler async = new AsyncHandler(this);
public static Callback<Throwable, Void> IOE400supressor = new Callback<Throwable, Void>() {
@Override
public Void call(Throwable t) throws Throwable {
if(t instanceof IOException) {
if(t.getMessage().startsWith("Server returned HTTP response code: 400"))
return null;
}
throw t;
}
};
2019-02-01 20:24:50 +01:00
Log log = new Log4JLog();
2018-07-17 19:23:54 +02:00
2019-04-28 22:26:31 +02:00
/**
* please only use if bot is not used in groups
* @param apikey
*/
2018-07-17 19:23:54 +02:00
public TelegramAPI(String apikey) {
2019-04-28 22:26:31 +02:00
botname = "";
if (!apikey.matches(TOKENREGEX) ) {
throw new IllegalArgumentException("Invalid API key: " + apikey);
}
this.apikey = apikey;
}
public TelegramAPI(String apikey, String botname) {
this.botname = botname;
2018-07-18 04:44:04 +02:00
if (!apikey.matches(TOKENREGEX) ) {
2019-03-01 04:49:20 +01:00
throw new IllegalArgumentException("Invalid API key: " + apikey);
2018-07-18 04:44:04 +02:00
}
2018-07-17 19:23:54 +02:00
this.apikey = apikey;
}
public void start() {
if(thread == null) {
run = true;
thread = new Thread(this, "TelegramAPI");
thread.start();
} else {
throw new IllegalStateException("Still Running.");
}
2018-07-17 19:23:54 +02:00
}
2018-07-18 04:44:04 +02:00
2019-02-07 06:30:28 +01:00
public void request(Task task) {
async.enque(task);
}
2019-02-25 00:21:31 +01:00
public void requestAsync(String request, String parameter) {
this.async.enque(request, parameter);
2019-02-07 06:30:28 +01:00
}
2019-02-25 00:21:31 +01:00
2018-07-18 04:44:04 +02:00
public JSONObject request(String request, String parameter) throws IOException {
2019-02-25 00:21:31 +01:00
return request(request, parameter, true);
}
public JSONObject request(String request, String parameter, boolean logging) throws IOException {
2018-07-17 19:23:54 +02:00
//do https stuff
2018-07-18 04:44:04 +02:00
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();
2019-02-07 06:30:28 +01:00
2019-02-25 00:21:31 +01:00
if(logging)
log.log( "request: " + request + " content " + parameter + " -> " + con.getResponseCode() + ", " + con.getResponseMessage());
2019-02-07 06:30:28 +01:00
2018-07-18 04:44:04 +02:00
if(con.getResponseCode() == 200) {
2019-02-08 03:15:31 +01:00
return new JSONObject(readfromIS(con.getInputStream()));
2018-07-18 04:44:04 +02:00
} else {
2019-02-08 03:15:31 +01:00
String errdesc = "[No description available]";
try {
2019-04-11 01:41:15 +02:00
//try to read error message
JSONObject returned = new JSONObject(readfromIS(con.getErrorStream()));
errdesc = returned.getString("description");
} catch(Throwable ignore) { }
2019-02-08 03:15:31 +01:00
throw new APIError(parameter, request, con.getResponseCode(), null, errdesc);
//throw new IOException("API request returned HTTP " + con.getResponseCode() + " (" + con.getResponseMessage() + ") for action " + request + "\nurl: " + url.toString() + "\nparams: " + parameter + "\nerror description: " + errdesc);
2018-07-18 04:44:04 +02:00
}
2018-07-17 19:23:54 +02:00
}
2019-02-11 17:24:21 +01:00
2019-02-08 07:27:00 +01:00
protected String readfromIS(InputStream is) {
2019-02-08 03:15:31 +01:00
Scanner s = new Scanner(is);
StringBuilder sb = new StringBuilder();
while(s.hasNextLine()) {
sb.append(s.nextLine()).append('\n');
}
s.close();
return sb.toString();
}
2019-05-07 16:10:50 +02:00
public void sendAsync(long chatid, String msg) {
sendMessage(new MessageBuilder().setAsync().setReciver(chatid).setText(msg).build());
}
2018-07-17 19:23:54 +02:00
public TMessage sendMessage(TUser user, String text) {
MessageBuilder builder = new MessageBuilder();
builder.setReciver(user.getID());
builder.setText(text);
return sendMessage(builder.build());
}
2018-07-18 22:31:05 +02:00
public TMessage sendMessage(SendableMessage msg) {
try {
2019-02-07 18:35:42 +01:00
if(msg instanceof AsyncSendable) {
2019-02-08 03:15:31 +01:00
AsyncSendable asyncm = (AsyncSendable) msg;
2019-02-07 18:35:42 +01:00
Callback<JSONObject, TMessage> adapter = getCallbackAdapter(this);
2019-02-08 03:15:31 +01:00
adapter.next = asyncm.callback;
2019-02-07 18:35:42 +01:00
Task t = new Task(msg.getCommand(), msg.getQ(), adapter);
2019-02-08 03:15:31 +01:00
t.setExceptionhandl(asyncm.excpt == null ? IOE400supressor : asyncm.excpt);
2019-02-07 18:35:42 +01:00
async.enque(t);
} else {
2019-02-25 00:21:31 +01:00
JSONObject o = request(msg.getCommand(), msg.getQ(), true);
2019-02-07 18:35:42 +01:00
return new TMessage(o.getJSONObject("result"), this);
}
2018-07-18 22:31:05 +02:00
} catch(IOException e) {
2019-02-07 18:35:42 +01:00
log.log("", e);
2018-07-18 22:31:05 +02:00
}
return null;
}
2019-02-11 17:24:21 +01:00
public void sendTypedMessage(final String msg, final TUser user, final int seconds) {
new Thread(new Runnable() {
@Override
public void run() {
user.sendStatus(Status.Typing);
try {
Thread.sleep(seconds*1000);
} catch (InterruptedException e) {
}
sendMessage(new MessageBuilder().setText(msg).setReciver(user.getID()).build());
}
}).start();
}
2019-10-20 17:08:25 +02:00
public void answerCallbackQuery(String callbackid, String text, boolean async) {
if(callbackid == null)
return;
if(async) {
requestAsync("answerCallbackQuery", "callback_query_id=" + callbackid + "&text=" + text);
} else {
try {
request("answerCallbackQuery", "callback_query_id=" + callbackid + "&text=" + text);
} catch(IOException e) {
e.printStackTrace();
}
}
}
2019-02-25 00:21:31 +01:00
/**
2019-04-11 12:31:07 +02:00
* creates internal APIEror, when message is not modyfied!
2019-02-25 00:21:31 +01:00
* @param newCaption
* @param chatid
* @param msg_id
* @param rm
* @param async
* @param clb
*/
2019-04-13 01:58:28 +02:00
public void updateCaption(final String newCaption, long chatid, int msg_id, TReplyMarkup rm, boolean async, Callback<JSONObject, ?> clb) {
2019-02-11 17:24:21 +01:00
try {
2019-02-11 17:39:59 +01:00
String rply = "";
if(rm != null)
rply = "&reply_markup=" + URLEncoder.encode(rm.toJSONString(), "UTF-8");
String q = "chat_id=" + chatid + "&message_id=" + msg_id + "&caption=" + URLEncoder.encode(newCaption, "UTF-8") + rply;
2019-02-11 17:24:21 +01:00
if(async) {
2019-04-11 12:31:07 +02:00
Task t = new Task("editMessageCaption", q);
t.setExceptionhandl(new Callback<Throwable, Void>() {
@Override
public Void call(Throwable j) throws Throwable {
if(j instanceof APIError) {
String errmsg = ((APIError) j).getMessage();
if(errmsg.equals("Bad Request: message is not modified") || errmsg.equals("Bad Request: message to edit not found")) {
//both have code 400
return null;
}
}
throw j;
}
});
this.async.enque(t);
2019-02-11 17:24:21 +01:00
} else {
request("editMessageCaption", q);
}
} catch(IOException e) {
log.log("", e);
}
}
2019-04-13 01:58:28 +02:00
public void updateMarkup(long chatid, int msg_id, TReplyMarkup rm, boolean async) {
2019-03-02 04:52:57 +01:00
try {
if(rm == null) return;//nope
String q = "chat_id=" + chatid + "&message_id=" + msg_id + "&reply_markup=" + URLEncoder.encode(rm.toJSONString(), "UTF-8");
if(async) {
this.async.enque("editMessageReplyMarkup", q);
} else {
request("editMessageReplyMarkup", q);
}
} catch(IOException e) {
log.log("", e);
}
}
2018-07-17 19:23:54 +02:00
public void stop() {
run = false;
thread.interrupt();
Thread.yield();//try to not get into that loop
while(isRunning()) {
thread.interrupt();
Thread.yield();
try {
Thread.sleep(10);
} catch(InterruptedException e) {}
}
2019-01-04 12:19:04 +01:00
log.log("tapi stoped.");
2018-07-17 19:23:54 +02:00
thread = null;
}
@Override
public void run() {
2019-02-08 07:27:00 +01:00
start = System.currentTimeMillis();
2019-02-25 15:59:36 +01:00
if(longpolling) {
while(run) {
fetchUpdates();
fetchedUpdates++;
}
} else {
while(run) {
long runstart = System.currentTimeMillis();
fetchUpdates();
fetchedUpdates++;
try {
int wait = (int) (updateInterval - (System.currentTimeMillis() - runstart));
if(wait > 0)
Thread.sleep(wait);
} catch (InterruptedException e) {
break;
}
2018-07-17 19:23:54 +02:00
}
}
}
2018-07-18 04:44:04 +02:00
public boolean isRunning() {
return thread.isAlive();
}
private void fetchUpdates() {
try {
2019-02-25 15:59:36 +01:00
processUpdates(request("getUpdates", "offset=" + msg_offset + "&timeout=" + (longpolling ? updateInterval : 1), false));
2018-07-18 04:44:04 +02:00
} catch (IOException e) {
2018-09-15 17:49:25 +02:00
log.log("error getting updates.", e);
2019-11-08 10:30:12 +01:00
try {
Thread.sleep(100);
} catch(InterruptedException ignored) {}
}
2018-07-18 04:44:04 +02:00
}
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);
}
}
2018-07-18 04:44:04 +02:00
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;
}
2019-02-05 20:49:35 +01:00
TUser u = new TUser(id, this);
users.add(u);
return u;
2018-07-18 04:44:04 +02:00
}
/**
* 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);
}
2018-09-15 17:49:25 +02:00
/**
* Set the logging method, use {@link Log4JLog} for usage of LOG4J,
* or {@link SimpleLog} for usage of System.out.println();
* use Null t odisable logging the default is {@link SimpleLog}
* @param l
*/
public void setLog(Log l) {
if(l == null)
log = new Log();
else
log = l;
}
2018-07-18 04:44:04 +02:00
public CommandManager getCommandManager() {
return cmdmgr;
}
public EventManager getEventManager() {
return evntmgr;
}
/**
* seconds to wait for longpolling or milliseconds to wait petween shortpolling
* @param d
*/
2018-07-18 04:44:04 +02:00
public void setUpdateInterval(int d) {
if(d < 0)
throw new IllegalArgumentException("UpdateInterval is not allowed to be negative.");
updateInterval = d;
}
2018-07-18 04:44:04 +02:00
public String getHelpMessage() {
return helpmessage;
}
public int getupdateInterval() {
return updateInterval;
}
2019-02-11 17:24:21 +01:00
2019-02-08 07:27:00 +01:00
public float getUpdatesperSecond() {
if(start == 0)
return -1;
return fetchedUpdates / ((float) (System.currentTimeMillis() - start) / 1000);
}
2018-07-18 04:44:04 +02:00
2019-03-29 17:59:23 +01:00
public static boolean isSendable(long filesize) {
return filesize < TELEGRAMFILESIZELIMIT;
}
2019-02-07 06:30:28 +01:00
public static Callback<JSONObject, TMessage> getCallbackAdapter(TelegramAPI api) {
return new Callback<JSONObject, TMessage>() {
@Override
public TMessage call(JSONObject j) {
return new TMessage(j.getJSONObject("result"), api);
}
};
}
2018-07-18 04:44:04 +02:00
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);
2018-07-18 04:44:04 +02:00
String text = msg.getText();
if(text != null) {
2019-04-28 22:26:31 +02:00
if(text.matches("^\\/(\\w*)(@(\\w*))?")) {
2018-07-18 04:44:04 +02:00
text = text.substring(1);//remove '/'
isCommand = true;
2019-04-28 22:26:31 +02:00
if(text.contains("@")) {//check name
int at = text.indexOf('@');
int end = text.indexOf(' ', at+1);
if(end == -1)
end = text.length();
String botname = text.substring(at+1, end);
if(botname.equalsIgnoreCase(api.botname)) {
cmdmgr.onCommand(text, msg.getFrom(), msg);
} else
api.log.log("other botname found: " + botname);
} else {
cmdmgr.onCommand(text, msg.getFrom(), msg);
}
2018-07-18 04:44:04 +02:00
} 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)) {
2018-07-18 04:44:04 +02:00
} else if(getMsg().has(Member.document)) {
2018-07-18 04:44:04 +02:00
} else if(getMsg().has(Member.invoice)) {
2018-07-18 04:44:04 +02:00
} else if(getMsg().has(Member.location)) {
2018-07-18 04:44:04 +02:00
} else if(getMsg().has(Member.video_note)) {
2018-07-18 04:44:04 +02:00
} else if(getMsg().has(Member.game)) {
2018-07-18 04:44:04 +02:00
} else if(getMsg().has(Member.contact)) {
2018-07-18 04:44:04 +02:00
} else if(getMsg().has(Member.sticker)) {
2018-07-18 04:44:04 +02:00
} else {
e = new UserSendMessageEvent(getMsg());
}
getEventManager().callEvent(e);
}
}
2019-02-06 23:52:15 +01:00
} else if(json.has("callback_query")) {
JSONObject cbq = json.getJSONObject("callback_query");
TUser from = api.getUser(cbq.getJSONObject("from"));
String data = cbq.getString("data");
2019-10-20 17:08:25 +02:00
String id = cbq.getString("id");
2019-02-07 03:24:55 +01:00
TMessage msg = new TMessage(cbq.getJSONObject("message"), api);
2019-10-20 17:08:25 +02:00
getEventManager().callEvent(new UserCallbackEvent(from, data, id, msg));
2018-07-18 04:44:04 +02:00
}
}
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;
}
}
2019-02-11 17:24:21 +01:00
2019-02-08 03:15:31 +01:00
public class APIError extends IOException {
/**
*
*/
private static final long serialVersionUID = 1L;
String params;
String method;
int returncode;
public String getParams() {
return params;
}
public String getMethod() {
return method;
}
public int getReturncode() {
return returncode;
}
public void setParams(String params) {
this.params = params;
}
public void setMethod(String method) {
this.method = method;
}
public void setReturncode(int returncode) {
this.returncode = returncode;
}
public APIError(String params, String method, int returncode, Throwable t, String msg) {
super(msg, t);
this.params = params;
this.method = method;
this.returncode = returncode;
}
public APIError(String arg0, Throwable arg1) {
super(arg0, arg1);
}
public APIError(String arg0) {
super(arg0);
}
public APIError(Throwable arg0) {
super(arg0);
}
}
2018-07-17 19:23:54 +02:00
}