commit f63ad619c5a668aee13e3aca5de5ccea7e4a5b51 Author: Pavel Belyaev Date: Wed Oct 18 10:28:55 2023 +0500 start diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..d37a38f --- /dev/null +++ b/pom.xml @@ -0,0 +1,127 @@ + + + + 4.0.0 + + org.example + HomeBot + 1.0-SNAPSHOT + + HomeBot + + http://www.example.com + + + UTF-8 + 1.8 + 1.8 + + + + + jitpack.io + https://jitpack.io + + + + + + junit + junit + 4.11 + test + + + org.json + json + 20230618 + + + com.github.demidko + aot + 2022.11.28 + + + + + org.apache.commons + commons-numbers-core + 1.1 + + + + org.apache.commons + commons-math3 + 3.6.1 + + + org.apache.commons + commons-lang3 + 3.12.0 + + + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.1.0 + + + + true + lib/ + org.example.App + + + + + + + + maven-clean-plugin + 3.1.0 + + + + maven-resources-plugin + 3.0.2 + + + maven-compiler-plugin + 3.8.0 + + + maven-surefire-plugin + 2.22.1 + + + maven-jar-plugin + 3.0.2 + + + maven-install-plugin + 2.5.2 + + + maven-deploy-plugin + 2.8.2 + + + + maven-site-plugin + 3.7.1 + + + maven-project-info-reports-plugin + 3.0.0 + + + + + diff --git a/src/main/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 0000000..f9ec9c1 --- /dev/null +++ b/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: org.example.App + diff --git a/src/main/java/org/example/App.java b/src/main/java/org/example/App.java new file mode 100644 index 0000000..8c44a06 --- /dev/null +++ b/src/main/java/org/example/App.java @@ -0,0 +1,132 @@ +package org.example; + +import org.pavlik.bot.*; +import org.pavlik.helpers.CoreHelp; +import org.pavlik.helpers.Morph; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.Deque; + +import java.util.HashMap; +import java.util.Properties; + + + + +public class App +{ + protected static String tgToken; + protected static TelegramAdapter tgAdapter; + public static boolean stop_signal=false; + public static BotDialogController botDialogController = null; + public static BotCommands botCommands = null; + public static String admin_chat; + public static HashMap Dialogs = new HashMap<>(); + + public static void loadConf() { + + File file = new File("settings.properties"); + Properties properties = new Properties(); + try { + properties.load(new FileReader(file)); + } catch (IOException e) { + System.out.println("файл с настройками не грузанулся"); + throw new RuntimeException(e); + } + + tgToken = properties.getProperty("tg.token"); + admin_chat = properties.getProperty("tg.admin_id"); + + System.out.println("tgTokenLoaded"); + } + + + public static void printStat() { + Runtime runtime = Runtime.getRuntime(); + long totalMemory = runtime.totalMemory(); + long freeMemory = runtime.freeMemory(); + long usedMemory = totalMemory - freeMemory; + + + + System.out.println("Total memory: " + totalMemory); + System.out.println("Free memory: " + freeMemory); + System.out.println("Used memory: " + usedMemory); + + //String path = System.getProperty("user.dir"); + } + + public static void initApp() { + loadConf(); + App.botDialogController = new BotDialogController(); + App.botCommands = new BotCommands(); + tgAdapter = new TelegramAdapter(tgToken); + startMsg(); + } + + + //сообщение о запуске админу + public static void startMsg(){ + String res = null; + + while (res==null) { + CoreHelp.sleep(5000); + + res = tgAdapter.sendMessage(admin_chat,"Я родился!"); + } + } + + + + + /** + * Вытащить в асинхрон заполнение очереди и разгребание + * + * @param args + */ + public static void main( String[] args ) + { + initApp(); + + + while (!stop_signal) { + + Long offset = tgAdapter.last_update + 1; + Deque out = tgAdapter.getUpdates(offset.toString()); + + //если проблемы с соединением, то поспать 5 секунд и повторить... + if (out==null) { + System.out.println("result error"); + + CoreHelp.sleep(5000); + + continue; + } + + if (out.size()>0) { + while(!out.isEmpty()) { + + MessageObj r = out.pop(); + if (r.message_id.isEmpty()) continue; + + + if (!Dialogs.containsKey(r.message_id)) { + Dialogs.put(r.message_id, new BotDialog(tgAdapter)); + } + + App.botDialogController.answer(Dialogs.get(r.message_id),r); + + } + } + + } + + + + + } + + +} diff --git a/src/main/java/org/pavlik/bot/BotCommands.java b/src/main/java/org/pavlik/bot/BotCommands.java new file mode 100644 index 0000000..224531a --- /dev/null +++ b/src/main/java/org/pavlik/bot/BotCommands.java @@ -0,0 +1,88 @@ +package org.pavlik.bot; + +import org.example.App; +import org.apache.commons.lang3.math.NumberUtils; + +public class BotCommands { + + + + public String runCmd(BotDialog dialog, MessageObj r) { + + String cmd1 = r.text.replaceAll("^/",""); + String cmd0 = dialog.lastCmd; + + String cmd = (cmd0 !=null && !cmd0.isEmpty()) ? cmd0 : cmd1; + + + + + + switch (cmd) { + case ("stop"): + return this.cmdStop(dialog, cmd1); + + case ("start"): + return "start"; + + case ("summ"): + return this.cmdSumm(dialog,cmd1); + + case("no"): + return "выражайся четче, я твои команды не пойму!"; + } + + return "Неизвестная команда: "+cmd+" для справки введите /help"; + + } + + protected String cmdStop(BotDialog dialog, String cmd) { + + if (!dialog.is_admin) return "В доступе отказано"; + + if (cmd.equals("stop") && dialog.lastCmd==null) { + + dialog.lastCmd=cmd; + return "Ты хочешь остановить бота, он умрет, если ты уверен, то ответь /yes или /no для отмены"; + } + + if (cmd.equals("yes")) { + App.stop_signal=true; + return "Аааааа, я подыхаю, всё, ты меня убил....\n Программа завершила свою работу."; + } + if (cmd.equals("no")) { + App.stop_signal=true; + dialog.lastCmd=null; + return "Ура, я жив, процесс убийства бота отменен."; + } + + return "Вводи или /yes или /no других путей нет!!!"; + + } + + + + protected String cmdSumm(BotDialog dialog, String cmd) { + if (dialog.lastCmd==null) { + + dialog.lastCmd=cmd; + return "Запущен навык сумма, вводите числа и в конце напишите /res"; + } + + + if (cmd.equals("res")) { + //return "Да я хз, что я Архимед????"; + dialog.lastCmd=null; + return "Общая сумма:"+dialog.summ; + } + + cmd = cmd.replace(",",".").trim(); + if (NumberUtils.isParsable(cmd)) { + double ent = Double.parseDouble(cmd); + dialog.summ+=ent; + return "Число "+ent+" добавлено\nтекущая сумма:"+dialog.summ+"\nдля завершения рассчетов введи /res"; + } + + return "Я таких чисел понять не могу, можешь нормально ввести???"; + } +} diff --git a/src/main/java/org/pavlik/bot/BotDialog.java b/src/main/java/org/pavlik/bot/BotDialog.java new file mode 100644 index 0000000..d3e5cd2 --- /dev/null +++ b/src/main/java/org/pavlik/bot/BotDialog.java @@ -0,0 +1,22 @@ +package org.pavlik.bot; + +import org.example.App; + + +public class BotDialog { + public String lastName, firstName, userName, userId; + public boolean is_admin = false; + public String transportId; + public MessageAdapter adapter; + public boolean zdorova; + public boolean answName; + public String lastCmd; + public int cmdStage=0; + public double summ=0.0; + + + public BotDialog (MessageAdapter adapter) { + this.adapter = adapter; + } + +} diff --git a/src/main/java/org/pavlik/bot/BotDialogController.java b/src/main/java/org/pavlik/bot/BotDialogController.java new file mode 100644 index 0000000..7ab7c35 --- /dev/null +++ b/src/main/java/org/pavlik/bot/BotDialogController.java @@ -0,0 +1,69 @@ +package org.pavlik.bot; + +import org.example.App; + + +public class BotDialogController { + + + + public void answer(BotDialog dialog, MessageObj r) + { + dialog.is_admin = checkAdmin(r); + String response = genResponse(r,dialog); + + if (response!=null && !response.isEmpty() && !r.message_id.isEmpty()) { + dialog.adapter.sendMessage(r.message_id, response); + } + + } + + + /** + * Генерация ответа на сообщение + * @param r - request, объект входящего сообщения с данными о пользователе, об объектах в сообщении + * @param dialog - контекст диалога с конкретным собеседником + * @return - текстовая строка с ответом на сообщение + */ + protected String genResponse(MessageObj r, BotDialog dialog) { + if (r.text == null) { + return "Ты шлешь мне какую-то дичь, еще раз пришлешь, не буду с тобой разговаривать."; + } + + if (!r.text.isEmpty()) { + if (r.is_command || dialog.lastCmd!=null) { + //обработчик команд + return App.botCommands.runCmd(dialog,r); + } + else { + return govorilka(r,dialog); + } + } + + return ""; + + } + + + protected String govorilka(MessageObj r, BotDialog dialog) { + if (!dialog.zdorova) { + dialog.zdorova = true; + return "Здарова, давай поговорим, скажи как к тебе обращаться?"; + } + if (!dialog.answName) { + dialog.answName = true; + dialog.userName = r.text; + return "Хорошо, "+dialog.userName+", так и буду тебя называть!!!"; + } + + return dialog.userName+", на твое сообщение <"+r.text+"> я пока ответов не знаю, я маленький и глупый..."; + } + + + protected boolean checkAdmin(MessageObj r) { + return r.message_id.equals("1187023222") && r.user_id.equals("pavelbbb"); + } + + + +} diff --git a/src/main/java/org/pavlik/bot/MessageAdapter.java b/src/main/java/org/pavlik/bot/MessageAdapter.java new file mode 100644 index 0000000..a3284cf --- /dev/null +++ b/src/main/java/org/pavlik/bot/MessageAdapter.java @@ -0,0 +1,9 @@ +package org.pavlik.bot; + +import java.util.Deque; + +public interface MessageAdapter { + String sendMessage(String chatId, String text); + + Deque getUpdates(String offset); +} diff --git a/src/main/java/org/pavlik/bot/MessageObj.java b/src/main/java/org/pavlik/bot/MessageObj.java new file mode 100644 index 0000000..8eb4828 --- /dev/null +++ b/src/main/java/org/pavlik/bot/MessageObj.java @@ -0,0 +1,9 @@ +package org.pavlik.bot; + +public class MessageObj { + public String text; + public String message_id; + public Long update_id; + public boolean is_command=false; + public String user_id; +} diff --git a/src/main/java/org/pavlik/bot/TelegramAdapter.java b/src/main/java/org/pavlik/bot/TelegramAdapter.java new file mode 100644 index 0000000..e201d34 --- /dev/null +++ b/src/main/java/org/pavlik/bot/TelegramAdapter.java @@ -0,0 +1,130 @@ +package org.pavlik.bot; + +//import java.math.BigInteger; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayDeque; +import java.util.Deque; + +import org.json.*; +import org.pavlik.helpers.RestHelper; + + +public class TelegramAdapter implements MessageAdapter { + String token; + RestHelper Rest; + public Long last_update = 0l; + protected String qUrl; + + /** + * добавить исключение при пустом токене + * @param token + */ + public TelegramAdapter(String token) { + this.token = token; + this.Rest = new RestHelper(); + qUrl = "https://api.telegram.org/bot"+token; + } + + @Override + public String sendMessage(String chatId, String text) { + + + String encodedMsg = null; + try { + encodedMsg = URLEncoder.encode(text, StandardCharsets.UTF_8.toString()); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + + String queryUrl = qUrl+"/sendMessage?chat_id="+chatId+"&text="+encodedMsg; + String respBody = Rest.queryGetBody(queryUrl); + + System.out.println("--------------------------\n send message("+chatId+"):\n"+text); + + return respBody; + + } + + /** + * добавить маскирование токена + * @param offset + * @return + */ + @Override + public Deque getUpdates(String offset) { + String queryUrl = qUrl+"/getUpdates?offset="+offset+"&limit=10&timeout=60"; + + String respBody = Rest.queryGetBody(queryUrl); + System.out.println("-----------------------------------------"); + System.out.println(respBody); + System.out.println("-----------------------------------------"); + + if (respBody == null) return null; + + return parseResp(respBody); + + } + + protected Deque parseResp(String jsonBody) { + JSONObject obj = new JSONObject(jsonBody); + + Deque list = new ArrayDeque<>(); + + if (obj.getBoolean("ok")) { + + JSONArray res_obj = obj.getJSONArray("result"); + for (int i = 0; i < res_obj.length(); i++) { + JSONObject curr = res_obj.getJSONObject(i); + MessageObj r = new MessageObj(); + + Long update_id = curr.getLong("update_id"); + last_update = update_id; + + if (!curr.has("message")) continue; + + + JSONObject message = curr.getJSONObject("message"); + JSONObject from = message.getJSONObject("from"); + String chat_id = from.getBigInteger("id").toString(); + if (message.has("text")) { + r.text = message.getString("text"); + } + + + if (from.has("username")) + r.user_id = from.getString("username"); + + //detect command + if (message.has("entities")) { + JSONArray entities = message.getJSONArray("entities"); + for (int e=0;e queryHeaders = new HashMap<>(); + queryHeaders.put("CONTENT-TYPE","text/plain2; charset=UTF-8"); + + + + HttpResponse resp = Rest.queryPostRaw(queryUrl,queryBody,queryHeaders); + String respBody = Rest.getRespBody(resp); + System.out.println(respBody); + } +} \ No newline at end of file diff --git a/src/main/java/org/pavlik/helpers/Morph.java b/src/main/java/org/pavlik/helpers/Morph.java new file mode 100644 index 0000000..1cb41fe --- /dev/null +++ b/src/main/java/org/pavlik/helpers/Morph.java @@ -0,0 +1,72 @@ +package org.pavlik.helpers; +import com.github.demidko.aot.WordformMeaning; + +import java.util.List; + +import static com.github.demidko.aot.WordformMeaning.lookupForMeanings; +//import java.util.ArrayList; +//import java.util.List; + +public class Morph { + public String[] message2words (String message) { + message = message.replaceAll("[^a-zA-Zа-яА-ЯёЁ]"," "); + message = message.replaceAll("\\s{2,}"," "); + + return message.split(" "); + } + + public String morphAnalyze1(String message) { + String[] wordArr = message2words(message); + String out=""; + + for (int i=0;i meanings = lookupForMeanings(word); + String out = ""; + if (meanings.isEmpty()) { + out = "слова <"+word+"> не слыхал"; + } + else { + out = word+": "+meanings.get(0).getMorphology().toString(); + } + + return out; + + } + + + public void Morph(String word) { + List meanings = lookupForMeanings(word); + + + System.out.println(meanings.size()); + //out.println(meanings.size()); + /* 1 */ + + + + System.out.println(meanings.get(0).getMorphology()); + + //meanings.get(0).getMorphology(). + + //meanings. + /* [С, мр, им, мн] */ + + System.out.println(meanings.get(0).getLemma()); + /* человек */ + + + for (WordformMeaning t : meanings.get(0).getTransformations()) { + System.out.println(t.toString() + " " + t.getMorphology()); + + } + } + +} diff --git a/src/main/java/org/pavlik/helpers/RestHelper.java b/src/main/java/org/pavlik/helpers/RestHelper.java new file mode 100644 index 0000000..21d83e8 --- /dev/null +++ b/src/main/java/org/pavlik/helpers/RestHelper.java @@ -0,0 +1,185 @@ +package org.pavlik.helpers; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.config.CookieSpecs; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.ssl.PrivateKeyStrategy; +import org.apache.http.ssl.SSLContexts; +import org.apache.http.util.EntityUtils; +import javax.net.ssl.SSLContext; +import java.io.*; +import java.security.*; +import java.util.HashMap; + +/** + * RestHelper by Pavel Belyaev + */ +public class RestHelper { + + public String codepage="UTF-8"; + protected HttpClient httpClient = null; + + public RestHelper () { + this(null,"", "", "", null, "", ""); + } + + public RestHelper (String keypath,String keytype,String keypass, String keyAlias, String trustpath, String trusttype, String trustpass) { + boolean withTrust = false; + KeyStore truststore_material = null; + + + KeyStore keystore_material = (keypath !=null) ? this.readKeyStore(keypath, keytype, keypass) : null; + PrivateKeyStrategy privateKeyStrategy = keyAlias == null ? null : (aliases, socket) -> keyAlias; + + + if (trustpath !=null) { + truststore_material = this.readKeyStore(trustpath, trusttype, trustpass); + withTrust = true; + } + + SSLContext sslContext = (withTrust) ? + genSSLContext(privateKeyStrategy,keystore_material,truststore_material,keypass) : + genSSLContextAllTrust(privateKeyStrategy,keystore_material,keypass); + + int timeout=61; + RequestConfig reqConf = RequestConfig.custom() + .setCookieSpec(CookieSpecs.STANDARD) + .setConnectTimeout(timeout * 1000) + .setConnectionRequestTimeout(timeout * 1000) + .setSocketTimeout(timeout * 1000) + .build(); + + this.httpClient = HttpClients.custom().setSSLContext(sslContext).setDefaultRequestConfig(reqConf).build(); + + } + + /** + * Only keystore init + * @param keypath + * @param keytype + * @param keypass + * @param keyAlias + */ + public RestHelper (String keypath,String keytype,String keypass, String keyAlias) { + this(keypath,keytype,keypass,keyAlias, null, null, null); + } + + /** + * Подгружает файл JKS или PKCS12 + * @param path - путь до jks/p12 + * @param type - JKS или PKCS12 + * @param pwd - пароль к хранилищу + * @return - хранилище для SSLContext + */ + protected KeyStore readKeyStore(String path, String type, String pwd) { + try { + FileInputStream KeyStoreFile = new FileInputStream(new File(path)); + KeyStore keyStore = KeyStore.getInstance(type); + keyStore.load(KeyStoreFile, pwd.toCharArray()); + return keyStore; + + } catch (Exception e){ + throw new RuntimeException(e); + } + } + + + + /** + * mtls - взаимная аутентификация + * @return + */ + SSLContext genSSLContext (PrivateKeyStrategy privateKeyStrategy,KeyStore keyStore, KeyStore trustStore, String keypass) { + try { + return SSLContexts.custom() + .loadTrustMaterial(trustStore,null) + .loadKeyMaterial(keyStore, keypass.toCharArray(),privateKeyStrategy) + .build(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Аутентификация только клиента, серверный сертификат не проверяется + * @return + */ + SSLContext genSSLContextAllTrust (PrivateKeyStrategy privateKeyStrategy,KeyStore keyStore, String keypass) { + + try { + return SSLContexts.custom() + .loadTrustMaterial(null, (x509CertChain, authType) -> true) //вариант принимающий всё + .loadKeyMaterial(keyStore, keypass.toCharArray(),privateKeyStrategy) // use null as second param if you don't have a separate key password + .build(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + protected void map2headers(HttpPost query,HashMap HeaderMap) { + for (String key : HeaderMap.keySet()) { + query.setHeader(key,HeaderMap.get(key)); + } + } + + public String getRespBody(HttpResponse resp){ + try { + HttpEntity entity = resp.getEntity(); + String body = EntityUtils.toString(entity, this.codepage); + EntityUtils.consume(entity); + return body; + + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + + + /** + * Отправляет запрос "как есть" строкой + * @param queryUrl - url + * @param queryBody - тело запроса + * @param headerMap - заголовки запроса + */ + public HttpResponse queryPostRaw(String queryUrl, String queryBody, HashMap headerMap) { + HttpPost query = new HttpPost(queryUrl); + if (headerMap != null) this.map2headers(query, headerMap); + + try { + query.setEntity(new StringEntity(queryBody,this.codepage)); //тело + return this.httpClient.execute(query); + } catch (Exception e) { + throw new RuntimeException(e); + } + + } + + public HttpResponse queryGet(String queryUrl) { + HttpGet httpGet = new HttpGet(queryUrl); + + try { + //query.setEntity(new StringEntity(queryBody,this.codepage)); //тело + return this.httpClient.execute(httpGet); + } catch (Exception e) { + //throw new RuntimeException(e); + return null; + } + + } + + public String queryGetBody(String queryUrl) { + System.out.println("Query:"+queryUrl); + HttpResponse res = queryGet(queryUrl); + if (res==null) return null; + + return getRespBody(res); + } + + } \ No newline at end of file diff --git a/src/main/resources/META-INF/MANIFEST.MF b/src/main/resources/META-INF/MANIFEST.MF new file mode 100644 index 0000000..f9ec9c1 --- /dev/null +++ b/src/main/resources/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: org.example.App + diff --git a/src/main/resources/META-INF/todo.txt b/src/main/resources/META-INF/todo.txt new file mode 100644 index 0000000..45ace25 --- /dev/null +++ b/src/main/resources/META-INF/todo.txt @@ -0,0 +1 @@ +https://github.com/AKuznetsov/russianmorphology diff --git a/src/test/java/org/example/AppTest.java b/src/test/java/org/example/AppTest.java new file mode 100644 index 0000000..6a1d2d7 --- /dev/null +++ b/src/test/java/org/example/AppTest.java @@ -0,0 +1,20 @@ +package org.example; + +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +/** + * Unit test for simple App. + */ +public class AppTest +{ + /** + * Rigorous Test :-) + */ + @Test + public void shouldAnswerWithTrue() + { + assertTrue( true ); + } +}