feedback working
This commit is contained in:
parent
277ae924aa
commit
d2e0ab9a3f
7
pom.xml
7
pom.xml
|
@ -3,7 +3,7 @@
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>TelegramAPI</groupId>
|
<groupId>TelegramAPI</groupId>
|
||||||
<artifactId>TelegramAPI</artifactId>
|
<artifactId>TelegramAPI</artifactId>
|
||||||
<version>0.0.2</version>
|
<version>0.0.3</version>
|
||||||
<build>
|
<build>
|
||||||
<sourceDirectory>src/main/java</sourceDirectory>
|
<sourceDirectory>src/main/java</sourceDirectory>
|
||||||
<resources>
|
<resources>
|
||||||
|
@ -39,13 +39,13 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.json</groupId>
|
<groupId>org.json</groupId>
|
||||||
<artifactId>json</artifactId>
|
<artifactId>json</artifactId>
|
||||||
<version>20180813</version>
|
<version>20190722</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
<version>LATEST</version>
|
<version>1.18.12</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<properties>
|
<properties>
|
||||||
|
@ -54,5 +54,4 @@
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
</project>
|
</project>
|
|
@ -1,28 +1,11 @@
|
||||||
package de.mrbesen.telegram;
|
package de.mrbesen.telegram;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStreamWriter;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.net.URLEncoder;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Scanner;
|
|
||||||
|
|
||||||
import javax.net.ssl.HttpsURLConnection;
|
|
||||||
|
|
||||||
import de.mrbesen.telegram.commands.FeedbackCommand;
|
|
||||||
import de.mrbesen.telegram.commands.JSONCommandHandler;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
import org.json.JSONArray;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import de.mrbesen.telegram.AsyncHandler.Callback;
|
import de.mrbesen.telegram.AsyncHandler.Callback;
|
||||||
import de.mrbesen.telegram.AsyncHandler.Task;
|
import de.mrbesen.telegram.AsyncHandler.Task;
|
||||||
import de.mrbesen.telegram.MessageBuilder.AsyncSendable;
|
import de.mrbesen.telegram.MessageBuilder.AsyncSendable;
|
||||||
import de.mrbesen.telegram.MessageBuilder.SendableMessage;
|
import de.mrbesen.telegram.MessageBuilder.SendableMessage;
|
||||||
import de.mrbesen.telegram.commands.CommandManager;
|
import de.mrbesen.telegram.commands.CommandManager;
|
||||||
|
import de.mrbesen.telegram.commands.FeedbackCommand;
|
||||||
import de.mrbesen.telegram.event.Event;
|
import de.mrbesen.telegram.event.Event;
|
||||||
import de.mrbesen.telegram.event.EventManager;
|
import de.mrbesen.telegram.event.EventManager;
|
||||||
import de.mrbesen.telegram.event.events.UserCallbackEvent;
|
import de.mrbesen.telegram.event.events.UserCallbackEvent;
|
||||||
|
@ -37,14 +20,27 @@ import de.mrbesen.telegram.objects.TMessage;
|
||||||
import de.mrbesen.telegram.objects.TReplyMarkup;
|
import de.mrbesen.telegram.objects.TReplyMarkup;
|
||||||
import de.mrbesen.telegram.objects.TUser;
|
import de.mrbesen.telegram.objects.TUser;
|
||||||
import de.mrbesen.telegram.objects.TUser.Status;
|
import de.mrbesen.telegram.objects.TUser.Status;
|
||||||
import sun.awt.image.ImageWatched;
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
public class TelegramAPI implements Runnable {
|
public class TelegramAPI implements Runnable {
|
||||||
|
|
||||||
private static final String API_URL = "https://api.telegram.org/bot";
|
private static final String API_URL = "https://api.telegram.org/bot";
|
||||||
private static final String TOKENREGEX = "^\\d{4,10}:[\\w-]{12,64}$";
|
private static final String TOKENREGEX = "^\\d{4,10}:[\\w-]{12,64}$";
|
||||||
private static final int TELEGRAMFILESIZELIMIT = 20000000;//20MB filesize https://core.telegram.org/bots/api#sending-files
|
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
|
public static final String APIVERSION = "3.9";//Feb 29, 2020
|
||||||
|
|
||||||
private int msg_offset = 0;
|
private int msg_offset = 0;
|
||||||
private int updateInterval = 60;
|
private int updateInterval = 60;
|
||||||
|
@ -69,6 +65,8 @@ public class TelegramAPI implements Runnable {
|
||||||
private CommandManager cmdmgr = new CommandManager(this);
|
private CommandManager cmdmgr = new CommandManager(this);
|
||||||
private EventManager evntmgr = new EventManager();
|
private EventManager evntmgr = new EventManager();
|
||||||
|
|
||||||
|
private FeedbackCommand feedbackCmd;
|
||||||
|
|
||||||
//async
|
//async
|
||||||
private AsyncHandler async = new AsyncHandler(this);
|
private AsyncHandler async = new AsyncHandler(this);
|
||||||
public static Callback<Throwable, Void> IOE400supressor = new Callback<Throwable, Void>() {
|
public static Callback<Throwable, Void> IOE400supressor = new Callback<Throwable, Void>() {
|
||||||
|
@ -109,8 +107,8 @@ public class TelegramAPI implements Runnable {
|
||||||
if(thread == null) {
|
if(thread == null) {
|
||||||
if(!disableFeedback) {
|
if(!disableFeedback) {
|
||||||
//init Feedback
|
//init Feedback
|
||||||
FeedbackCommand fcmd = new FeedbackCommand();
|
feedbackCmd = new FeedbackCommand(this, admins);
|
||||||
cmdmgr.registerCommand("feedback", fcmd);
|
cmdmgr.registerCommand("feedback", feedbackCmd);
|
||||||
}
|
}
|
||||||
run = true;
|
run = true;
|
||||||
thread = new Thread(this, "TelegramAPI");
|
thread = new Thread(this, "TelegramAPI");
|
||||||
|
@ -506,6 +504,10 @@ public class TelegramAPI implements Runnable {
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
e = new UserSendMessageEvent(getMsg());
|
e = new UserSendMessageEvent(getMsg());
|
||||||
|
//call feedback cmd first
|
||||||
|
if(feedbackCmd.onMsg((UserSendMessageEvent) e)) {
|
||||||
|
e = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
getEventManager().callEvent(e);
|
getEventManager().callEvent(e);
|
||||||
}
|
}
|
||||||
|
@ -516,7 +518,10 @@ public class TelegramAPI implements Runnable {
|
||||||
String data = cbq.getString("data");
|
String data = cbq.getString("data");
|
||||||
String id = cbq.getString("id");
|
String id = cbq.getString("id");
|
||||||
TMessage msg = new TMessage(cbq.getJSONObject("message"), api);
|
TMessage msg = new TMessage(cbq.getJSONObject("message"), api);
|
||||||
getEventManager().callEvent(new UserCallbackEvent(from, data, id, msg));
|
UserCallbackEvent event = new UserCallbackEvent(from, data, id, msg);
|
||||||
|
|
||||||
|
if(!feedbackCmd.onCallback(event))
|
||||||
|
getEventManager().callEvent(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package de.mrbesen.telegram.commands;
|
package de.mrbesen.telegram.commands;
|
||||||
|
|
||||||
import com.sun.istack.internal.Nullable;
|
|
||||||
import de.mrbesen.telegram.AsyncHandler;
|
import de.mrbesen.telegram.AsyncHandler;
|
||||||
import de.mrbesen.telegram.MessageBuilder;
|
import de.mrbesen.telegram.MessageBuilder;
|
||||||
import de.mrbesen.telegram.TelegramAPI;
|
import de.mrbesen.telegram.TelegramAPI;
|
||||||
|
@ -10,36 +9,41 @@ import de.mrbesen.telegram.objects.TInlineKeyboardMarkup;
|
||||||
import de.mrbesen.telegram.objects.TMessage;
|
import de.mrbesen.telegram.objects.TMessage;
|
||||||
import de.mrbesen.telegram.objects.TUser;
|
import de.mrbesen.telegram.objects.TUser;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
public class FeedbackCommand implements JSONCommandHandler {
|
public class FeedbackCommand implements JSONCommandHandler {
|
||||||
|
|
||||||
protected final TInlineKeyboardMarkup cancel = new TInlineKeyboardMarkup(1);
|
protected final TInlineKeyboardMarkup cancelMarkup = new TInlineKeyboardMarkup(1);
|
||||||
protected final Map<Long, TMessage> awaitingFeedback = new TreeMap<>(); //maps users chat id -> message that said "please send feedback" TODO: make persistent?
|
protected final Map<Long, TMessage> awaitingFeedback = new TreeMap<>(); //maps users chat id -> message that said "please send feedback" TODO: make persistent?
|
||||||
|
protected final Map<Long, Long[]> awnser = new TreeMap<>(); // chatid -> [chatid, messageid] to awnser to; next message from key will be fwd to value[0] and reply to value[1]
|
||||||
protected final TelegramAPI api;
|
protected final TelegramAPI api;
|
||||||
protected final Collection<Long> admins;
|
protected final List<Long> admins;
|
||||||
protected final static String CANCELFEEDBACK = "cancelfeedback", REPLYFEEDBACK = "replyFeedback ";
|
protected final static String CANCELFEEDBACK = "cancelfeedback", REPLYFEEDBACK = "replyFeedback ";
|
||||||
|
|
||||||
|
|
||||||
public FeedbackCommand(TelegramAPI api, Collection<Long> admins) {
|
public FeedbackCommand(TelegramAPI api, List<Long> admins) {
|
||||||
this.api = api;
|
this.api = api;
|
||||||
this.admins = admins;
|
this.admins = (admins == null ? new LinkedList<>() : admins);
|
||||||
|
|
||||||
//build markup
|
//build markup
|
||||||
cancel.addCallbackButton("❌ cancel", CANCELFEEDBACK, 0);
|
cancelMarkup.addCallbackButton("❌ cancel", CANCELFEEDBACK, 1);
|
||||||
api.getEventManager().registerEvent(UserSendMessageEvent.class, this::onMsg);
|
|
||||||
api.getEventManager().registerEvent(UserCallbackEvent.class, this::onCallback);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCommand(TUser sender, String cmd, String[] args, TMessage json) {
|
public boolean onCommand(TUser sender, String cmd, String[] args, TMessage json) {
|
||||||
|
if(json.getChatID() < 0) return false; //do not handle group chats
|
||||||
|
|
||||||
if(cmd.equalsIgnoreCase("feedback")) {
|
if(cmd.equalsIgnoreCase("feedback")) {
|
||||||
removeMsg(sender.getID(), null);
|
removeMsg(sender.getID(), null);
|
||||||
awaitingFeedback.put(sender.getID(), null);
|
awaitingFeedback.put(sender.getID(), null);
|
||||||
MessageBuilder mb = new MessageBuilder();
|
MessageBuilder mb = new MessageBuilder();
|
||||||
mb.setAsync().setMarkup(cancel).setText("Your next Message will be forwarded to the admins.")
|
mb.setAsync().setReciver(sender).setMarkup(cancelMarkup).setText("Your next Message will be forwarded to the admins.")
|
||||||
.setCallback(new AsyncHandler.Callback<TMessage, Object>() {
|
.setCallback(new AsyncHandler.Callback<TMessage, Object>() {
|
||||||
@Override
|
@Override
|
||||||
public Object call(TMessage msg) throws Throwable {
|
public Object call(TMessage msg) {
|
||||||
awaitingFeedback.put(sender.getID(), msg);
|
awaitingFeedback.put(sender.getID(), msg);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -50,48 +54,98 @@ public class FeedbackCommand implements JSONCommandHandler {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onMsg(Object event) {
|
public boolean onMsg(UserSendMessageEvent e) {
|
||||||
UserSendMessageEvent e = (UserSendMessageEvent) event;
|
|
||||||
TUser user = e.getUser();
|
TUser user = e.getUser();
|
||||||
TMessage msg = e.getMessage();
|
TMessage msg = e.getMessage();
|
||||||
if(isFeedback(msg.getText())) {
|
System.out.println("msg: " + msg.getText());
|
||||||
if (awaitingFeedback.containsKey(user.getID())) {
|
if (awaitingFeedback.containsKey(user.getID())) {
|
||||||
removeMsg(user.getID(), e.getMessage());
|
removeMsg(user.getID(), e.getMessage());
|
||||||
|
if(isFeedback(msg.getText())) {
|
||||||
//fwd to admins
|
//fwd to admins
|
||||||
admins.forEach(a -> TMessage.forwardAsync(api, a, msg));
|
admins.forEach(a -> TMessage.forwardAsync(api, a, msg));
|
||||||
|
|
||||||
StringBuilder info = new StringBuilder("Feedbackinfo:\n");
|
MessageBuilder mb = createInfo(user, msg.getMessageID());
|
||||||
info.append("userid: ").append(user.getID());
|
admins.forEach(a -> api.sendMessage(mb.setReciver(a).build()));
|
||||||
info.append("\nusername: ").append(user.getName());
|
} else {
|
||||||
info.append("\nfullname: ").append(user.getFirstName()).append(' ').append(user.getLastName());
|
System.out.println("feedback omitted: " + msg.getText());
|
||||||
TInlineKeyboardMarkup markup = new TInlineKeyboardMarkup(1);
|
}
|
||||||
markup.addUrlButton("chat with user", "tg://" + user.getName(), 1);
|
return true;
|
||||||
markup.addCallbackButton("reply ↩", REPLYFEEDBACK + user.getID() + " " + msg.getMessageID(), 1);
|
} else {
|
||||||
admins.forEach(a -> api.sendAsync(a, info.toString()));
|
Long[] rplymsg = awnser.remove(user.getID());
|
||||||
|
if(rplymsg != null) {
|
||||||
|
if(admins.contains(user.getID())) {
|
||||||
|
System.out.println("is admin!");
|
||||||
|
//admin to user -> copy text send as new msg from bot to disguise admin
|
||||||
|
TInlineKeyboardMarkup mu = new TInlineKeyboardMarkup(1);
|
||||||
|
mu.addCallbackButton("↩ reply", buildRplyCallback(user.getID(), msg.getMessageID()), 1);
|
||||||
|
|
||||||
|
MessageBuilder mb = new MessageBuilder().setAsync().setReciver(rplymsg[0])
|
||||||
|
.setReplyTo(Math.toIntExact(rplymsg[1])).setText(msg.getText()).setMarkup(mu);
|
||||||
|
api.sendMessage(mb.build());
|
||||||
|
|
||||||
|
awnser.put(rplymsg[0], new Long[]{user.getID(), Long.valueOf(msg.getMessageID())});
|
||||||
|
} else {
|
||||||
|
System.out.println("is user!");
|
||||||
|
//user to admin -> fwd message
|
||||||
|
TMessage.forwardAsync(api, rplymsg[0], msg);
|
||||||
|
|
||||||
|
//send info
|
||||||
|
MessageBuilder mb = createInfo(user, msg.getMessageID());
|
||||||
|
api.sendMessage(mb.setReciver(rplymsg[0]).setReplyTo(Math.toIntExact(rplymsg[1])).build());
|
||||||
|
}
|
||||||
|
System.out.println("Feedback reply from " + user.getID() + " to " + rplymsg[0] + ":" + rplymsg[1] + " send.");
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onCallback(Object event) {
|
protected MessageBuilder createInfo(TUser u, int msgid) {
|
||||||
UserCallbackEvent e = (UserCallbackEvent) event;
|
String info = "Feedbackinfo:\n" + "userid: " + u.getID() + "\nusername: " + u.getName() + "\nfullname: " + u.getFirstName() + ' ' + u.getLastName();
|
||||||
|
TInlineKeyboardMarkup markup = new TInlineKeyboardMarkup(1);
|
||||||
|
markup.addUrlButton("chat with user", "https://t.me/" + u.getName(), 1);
|
||||||
|
markup.addCallbackButton("↩ reply", buildRplyCallback(u.getID(), msgid), 1);
|
||||||
|
return new MessageBuilder().setAsync().setText(info).setMarkup(markup);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onCallback(UserCallbackEvent e) {
|
||||||
if(e.getData().equals(CANCELFEEDBACK)) {
|
if(e.getData().equals(CANCELFEEDBACK)) {
|
||||||
removeMsg(e.getUser().getID(), e.getMsg());
|
removeMsg(e.getUser().getID(), e.getMsg());
|
||||||
api.answerCallbackQuery(e.getID(), "Cancelled", true);
|
api.answerCallbackQuery(e.getID(), "Cancelled", true);
|
||||||
|
return true;
|
||||||
} else if(e.getData().startsWith(REPLYFEEDBACK)) {
|
} else if(e.getData().startsWith(REPLYFEEDBACK)) {
|
||||||
String rest = e.getData().substring(REPLYFEEDBACK.length());
|
String rest = e.getData().substring(REPLYFEEDBACK.length());
|
||||||
//TODO
|
long chatid = 0;
|
||||||
|
long msgid = -1;
|
||||||
|
try {
|
||||||
|
String[] spl = rest.split(" ",2);
|
||||||
|
long adminid = Long.parseLong(spl[0]);
|
||||||
|
msgid = Integer.parseInt(spl[1]);
|
||||||
|
//convert admin id to chat id
|
||||||
|
if(adminid <= 0) {
|
||||||
|
chatid = admins.get((int) -adminid);
|
||||||
|
} else
|
||||||
|
chatid = adminid;
|
||||||
|
} catch (NumberFormatException ex) {
|
||||||
|
api.answerCallbackQuery(e.getID(), "Error. Sorry :/", true);
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
if(msgid > -1) {
|
||||||
|
awnser.put(e.getUser().getID(), new Long[]{chatid, msgid});
|
||||||
|
api.answerCallbackQuery(e.getID(), "Write a Message", true);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* looks up msg from awaiting feedback map, if not present try to use fallback_msg
|
* looks up msg from awaiting feedback map, if not present try to use fallback_msg
|
||||||
* is Async
|
* is Async
|
||||||
* removes user from awaitingFeedback
|
* removes user from awaitingFeedback
|
||||||
* @param chat
|
*
|
||||||
* @param fallback_msg
|
|
||||||
*/
|
*/
|
||||||
protected void removeMsg(long chat, @Nullable TMessage fallback_msg) {
|
protected void removeMsg(long chat, TMessage fallback_msg) {
|
||||||
TMessage msg = awaitingFeedback.remove(chat);
|
TMessage msg = awaitingFeedback.remove(chat);
|
||||||
if(msg == null) msg = fallback_msg;
|
if(msg == null) msg = fallback_msg;
|
||||||
|
|
||||||
|
@ -105,6 +159,20 @@ public class FeedbackCommand implements JSONCommandHandler {
|
||||||
*/
|
*/
|
||||||
protected boolean isFeedback(String msg) {
|
protected boolean isFeedback(String msg) {
|
||||||
msg = msg.trim();
|
msg = msg.trim();
|
||||||
return !msg.startsWith("/") && msg.length() > 5 && !msg.startsWith("http");
|
return (!msg.startsWith("/")) && (msg.length() > 5) && (!msg.startsWith("http"));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int getAdminID(long adminchat) {
|
||||||
|
for(int i = 0; i < admins.size(); i++) {
|
||||||
|
if(admins.get(i) == adminchat)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String buildRplyCallback(long user, int msgid) {
|
||||||
|
int adminid = getAdminID(user);
|
||||||
|
if(adminid >= 0) user = -adminid;
|
||||||
|
return REPLYFEEDBACK + user + " " + msgid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
package de.mrbesen.telegram.event;
|
package de.mrbesen.telegram.event;
|
||||||
|
|
||||||
import de.mrbesen.telegram.TelegramAPI;
|
|
||||||
|
|
||||||
import java.lang.reflect.Array;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
@ -33,8 +29,8 @@ public class EventManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Event callEvent(Event e) {
|
public Event callEvent(Event e) {
|
||||||
if(e == null)
|
if(e == null) return e;
|
||||||
throw new NullPointerException("event is not allowed to be null!");
|
|
||||||
ArrayList<Consumer> listner = listeners.get(e.getClass());
|
ArrayList<Consumer> listner = listeners.get(e.getClass());
|
||||||
if(listner != null) {
|
if(listner != null) {
|
||||||
for (Consumer listn : listner) {
|
for (Consumer listn : listner) {
|
||||||
|
|
Loading…
Reference in New Issue