@Override
public void onUpdateReceived(Update update) {
UpdatesReceiver.handleUpdates(update);
}
аны мен иштеткичке өткөрүп берем (өздүк UpdatesReceiver классы):
public static void handleUpdates(Update update) {
...
if (update.hasMessage() && update.getMessage().hasText()){
log.info("[Update (id {}) типа \"Текстовое сообщение\"]", update.getUpdateId());
new TextMessageHandler(update, replyGenerator).handleTextMessage();
}
else if (update.hasCallbackQuery()) {
//логгирование
new CallbackQueryHandler(update, replyGenerator).handleCallBackQuery();
}
else {
//логгирование
replyGenerator.sendTextMessage(update.getMessage().getChatId(), "Я могу принимать только текстовые messages!");
}
}
UpdatesReceiver - бул борбордук иштеткич, ал жаңыртуу түрүнө жараша башкарууну башка адистештирилген иштеткичке өткөрүп берет: TextMessageHandler же CallbackQueryHandler, алардын конструкторлоруна мен жаңыртууну чынжырдан ары өткөрүп берем. Жаңыртуу - бот менен иштөөдө эң маанилүү нерсе жана аны жоготууга болбойт, анткени жаңыланууда сакталган маалыматтын жардамы менен биз жооп кайсы колдонуучуга жана кайсы чатка жөнөтүлүшү керек экенин билебиз. Колдонуучуга жоопторду түзүү үчүн мен өзүнчө класс жаздым. Ал кадимки текст кабарын, саптык баскычтоп менен билдирүүнү, сүрөтү бар билдирүүнү жана жооп баскычтобу менен билдирүүнү жөнөтө алат. Киргизилген клавиатура мындай көрүнөт: Ал баскычтарды аныктайт, аларды басуу менен колдонуучу serverге кайра чалуу жөнөтөт, аны кадимки билдирүүлөр сыяктуу эле иштетүүгө болот. Аны "камдоо" үчүн сизге өзүңүздүн иштеткичиңиз керек. Биз ар бир баскычка иш-аракетти койдук, ал андан кийин Жаңыртуу an objectисине жазылат. Ошол. "Чыгым" баскычы үчүн биз кайра чалуу үчүн "/баа" сыпаттамасын койдук, аны кийинчерээк жаңыртуудан ала алабыз. Андан кийин, өзүнчө класста мен бул кайра чалууну иштете алам:
public void handleCallBackQuery() {
String call_data = update.getCallbackQuery().getData();
long message_id = update.getCallbackQuery().getMessage().getMessageId();
long chat_id = update.getCallbackQuery().getMessage().getChatId();
switch (call_date){
case "/price" :
//тут что-то сделать
break;
...
Жооп берүү клавиатурасы төмөнкүдөй көрүнөт: Негизи, ал колдонуучунун терүүсүн алмаштырат. "Китепкана" баскычын басуу ботко "Китепкана" билдирүүсүн тез жөнөтөт. Ар бир клавиатура түрү үчүн мен Builder үлгүсүн ишке ашыруу менен өз классымды жаздым: inline жана жооп берүү . Натыйжада, сиз өзүңүздүн талаптарыңызга жараша каалаган клавиатураны "чыйра аласыз". Бул абдан ыңгайлуу, анткени клавиатура ар кандай болушу мүмкүн, бирок принцип ошол эле бойдон калууда. Бул жерде ички баскычтоп менен билдирүү жөнөтүүнүн интуитивдик ыкмасы:
public synchronized void sendInlineKeyboardMessage(long chat_id, String gameTitle) {
SendMessage keyboard = InlineKeyboardMarkupBuilder.create(chat_id)
.setText("Вы может узнать следующую информацию об игре " + gameTitle)
.row()
.button("Стоимость " + "\uD83D\uDCB0", "/price " + gameTitle)
.button("Обновлено " + "\uD83D\uDDD3", "/updated " + gameTitle)
.button("Версия " + "\uD83D\uDEE0", "/version " + gameTitle)
.endRow()
.row()
.button("Требования " + "\uD83D\uDCF5", "/requirements " + gameTitle)
.button("Покупки " + "\uD83D\uDED2", "/iap " + gameTitle)
.button("Размер " + "\uD83D\uDD0E", "/size " + gameTitle)
.endRow()
.row()
.button("Получить всю информацию об игре" + "\uD83D\uDD79", "/all " + gameTitle)
.endRow()
.row()
.button("Скрыть клавиатуру", "close")
.endRow()
.build();
try {
execute(keyboard);
} catch (TelegramApiException e) {
log.error("[Не удалось отправить сообщение с -inline- клавиатурой]: {}", e.getMessage());
}
}
Ботко катуу функционалдуулук берүү үчүн, слэш белгисин колдонуу менен атайын буйруктар ойлоп табылган: /library, /help, /game ж.б. Болбосо, колдонуучу жазган таштандыны кайра иштетүүгө туура келет. Чынында, бул MessageHandler үчүн жазылган:
if (message.equals(ChatCommands.START.getDescription())) {
replyGenerator.sendTextMessage(chat_id, new StartMessageHandler().reply());
replyGenerator.sendReplyKeyboardMessage(chat_id);
}
else if (message.equals(ChatCommands.HELP.getDescription())
|| message.equalsIgnoreCase("Помощь")) {
replyGenerator.sendTextMessage(chat_id, new HelpMessageHandler().reply());
}
...
Ошентип, ботко кандай буйрук жибергениңизге жараша, ишке атайын иштеткич кошулат. Андан ары барып, талдоочу менен китепкананын ишин карап көрөлү. Эгер сиз ботко Google Play дүкөнүндөгү оюнга шилтеме жөнөтсөңүз, анда атайын иштеткич автоматтык түрдө иштейт . Жооп катары колдонуучу оюн жөнүндө маалыматты төмөнкү формада алат: Ошол эле учурда, оюнду боттун китепканасына кошууга аракет кылган ыкма чакырылат (адегенде жергorктүү картага, андан кийин -> json файлына ). Эгерде оюн мурунтан эле китепканада болсо, анда текшерүү жүргүзүлөт (кадимки хэшмаптагыдай), ал эми талаа маалыматтары (мисалы, versionнын номери өзгөргөн болсо), китепканадагы оюн кайра жазылат. Эгерде эч кандай өзгөртүүлөр аныкталбаса, анда эч кандай жазуулар жасалbyte. Эгерде китепканада таптакыр оюн жок болсо, анда ал адегенде жергorктүү картага ( tyk сыяктуу an object ) жазылып, андан кийин json файлына жазылат, анткени serverдеги тиркеме күтүүсүз жабылып калса, маалыматтар жоголгон, бирок аны ар дайым файлды колдонуп окуса болот. Чынында, программа башталганда, китепкана ар дайым статикалык блоктогу файлдан биринчи жолу жүктөлөт:
static {
TypeFactory typeFactory = mapper.getTypeFactory();
MapType mapType = typeFactory.constructMapType(ConcurrentSkipListMap.class, String.class, GooglePlayGame.class);
try {
Path path = Paths.get(LIBRARY_PATH);
if (!Files.exists(path)) {
Files.createDirectories(path.getParent());
Files.createFile(path);
log.info("[Файл библиотеки создан]");
}
else {
ConcurrentMap<string, googleplaygame=""> temporary = mapper.readValue(new File(LIBRARY_PATH), mapType);
games.putAll(temporary);
log.info("[Количество игр в загруженной библиотеке] = " + games.size());
}
}
catch (IOException e) {
log.error("[Ошибка при чтении/записи file] {}", e.getMessage());
}
}
Бул жерде сиз кошумча түрдө файлдан маалыматты убактылуу картага окушуңуз керек, андан кийин файлдагы оюнду издөөдө регистрге маани бербөө үчүн толук картага “көчүрүлөт” (tITan QuEST жазуу менен, бот дагы эле табат китепканадагы Titan Quest оюну). Башка чечимди табуу мүмкүн болгон жок, бул Джексондун жардамы менен сериядан чыгаруунун өзгөчөлүктөрү. Ошентип, ар бир шилтемеге суроо-талап менен оюн мүмкүн болсо, китепканага кошулуп, китепкана ошону менен кеңейет. Белгилүү бир оюн жөнүндө кошумча маалыматты /libraryGame_Name буйругу аркылуу алса болот . Сиз бир эле учурда белгилүү бир параметрди (мисалы, учурдагы version) жана бардык параметрлерди биле аласыз. Бул мурун талкууланган саптык клавиатураны колдонуу менен ишке ашырылат. Иш учурунда бул жерден алган шык-жөндөмдөрүмдү маселелерди чечүүдө да колдондум. Мисалы, китепканада жайгашкан кокус оюндардын аталыштарынын тизмеси (опция /library буйругун колдонуу менен жеткorктүү):
private String getRandomTitles(){
if (LibraryService.getLibrary().size() < 10){
return String.join("\n", LibraryService.getLibrary().keySet());
}
List<string> keys = new ArrayList<>(LibraryService.getLibrary().keySet());
Collections.shuffle(keys);
List<string> randomKeys = keys.subList(0, 10);
return String.join("\n", randomKeys);
}
Бот шилтемелерди кантип иштетет? Ал алардын Google Play'ге (хост, протокол, порт) таандык экендигин текшерүү үчүн текшерет:
private static class GooglePlayCorrectURL {
private static final String VALID_HOST = "play.google.com";
private static final String VALID_PROTOCOL = "https";
private static final int VALID_PORT = -1;
private static boolean isLinkValid(URI link) {
return (isHostExist(link) && isProtocolExist(link) && link.getPort() == VALID_PORT);
}
private static boolean isProtocolExist(URI link) {
if (link.getScheme() != null) {
return link.getScheme().equals(VALID_PROTOCOL);
}
else {
return false;
}
}
private static boolean isHostExist(URI link) {
if (link.getHost() != null) {
return link.getHost().equals(VALID_HOST);
}
else {
return false;
}
}
Эгерде баары өз нугунда болсо, анда бот Jsoup китепканасын колдонуу менен шилтеме аркылуу туташат, бул барактын HTML codeун алууга мүмкүндүк берет, ал андан ары талдоо жана талдоо жүргүзүүгө тийиш. Сиз ботту туура эмес же зыяндуу шилтеме менен алдай албайсыз.
if (GooglePlayCorrectURL.isLinkValid(link)){
if (!link.getPath().contains("apps")){
throw new InvalidGooglePlayLinkException("К сожалению, бот работает исключительно с играми. Введите другую ссылку.");
}
URL = forceToRusLocalization(URL);
document = Jsoup.connect(URL).get();
}
else {
throw new NotGooglePlayLinkException();
}
...
Бул жерде биз аймактык орнотуулар менен көйгөйдү чечүүгө туура келди. Бот Google Play дүкөнүнө Европада жайгашкан serverден туташат, андыктан Google Play дүкөнүндөгү баракча тиешелүү тилде ачылат. Мен балдакты жазууга туура келди, ал беттин орусча versionсына мажбурлап "багыттоо" (долбоор, баары бир, биздин аудиторияга багытталган). Бул үчүн, шилтеменин аягында кылдаттык менен hl: &hl=ru параметрин Google Play serverине GET өтүнүчүн кошуу керек .
private String forceToRusLocalization(String URL) {
if (URL.endsWith("&hl=ru")){
return URL;
}
else {
if (URL.contains("&hl=")){
URL = URL.replace(
URL.substring(URL.length()-"&hl=ru".length()), "&hl=ru");
}
else {
URL += "&hl=ru";
}
}
return URL;
}
Ийгorктүү туташуудан кийин биз талдоо жана талдоо үчүн даяр HTML documentин алабыз, бирок бул макаланын алкагынан чыкпайт. Талдоочу codeу бул жерде . Талдоочу өзү керектүү маалыматты алат жана оюн менен an objectти түзөт, ал кийинчерээк керек болсо китепканага кошулат. <h2>Кыскача айтканда</h2>Бот белгилүү бир функцияны камтыган бир нече буйруктарды колдойт. Ал колдонуучудан билдирүүлөрдү кабыл алат жана аларды буйруктары менен дал келтирет. Эгер бул шилтеме же /game + шилтеме буйругу болсо, ал шилтемени Google Play'ге таандык же жокпу, текшерет. Шилтеме туура болсо, ал Jsoup аркылуу туташып, HTML documentин алат. Бул document жазылган талдоочу негизинде талданат. Оюн жөнүндө керектүү маалымат documentтен чыгарылат, андан кийин оюну бар an object бул маалыматтар менен толтурулат. Андан кийин, оюну бар an object жергorктүү сактагычка жайгаштырылат (эгерде оюн али жок болсо) жана маалыматтарды жоготуп албаш үчүн дароо файлга жазылат. Китепканада жазылган оюнду (оюндун аталышы картанын ачкычы, оюну бар an object картанын мааниси) /library Game_name командасынын жардамы менен алынышы мүмкүн. Көрсөтүлгөн оюн боттун китепканасынан табылса, колдонуучуга оюн жөнүндө маалымат ала турган саптык клавиатура кайтарылып берилет. Эгер оюн табылбаса, анда сиз же аталыштын туура жазылганын текшеришиңиз керек (ал Google Play дүкөнүндөгү оюндун аталышына толугу менен дал келиши керек, учурдан тышкары) же ботту жөнөтүү менен оюнду китепканага кошуңуз оюнга шилтеме. Мен ботту герокуга жайгаштырдым жана келечекте өз ботун жазып, аны герокуда бекер жайгаштырууну пландап жаткандар үчүн, сиз кабылышы мүмкүн болгон кыйынчылыктарды чечүү боюнча бир нече сунуштарды берем (анткени мен аларга өзүм туш болгонмун). Тилекке каршы, Heroku табиятынан улам, бот китепканасы ар 24 саатта бир жолу "кайра орнотулат". Менин планым Heroku serverлеринде файлдарды сактоону колдобойт, ошондуктан ал жөн гана менин оюн файлымды Github'дан тартып алат. Бир нече чечимдер бар болчу: маалымат базасын колдонуңуз же бул файлды оюн менен кошо сактай турган башка serverди издеңиз. Мен азыр эч нерсе кылбайм деп чечтим, анткени бот анчалык деле пайдалуу эмес. Бул мага толук тажрыйба алуу үчүн керек болчу, бул мен жетиштим. Ошентип, Heroku үчүн сунуштар:
-
Эгер сиз Россияда жашасаңыз, VPN аркылуу герокуда катталууга туура келет.
-
Долбоордун түбүнө сиз Procfile деп аталган кеңейтүүсүз файлды коюшуңуз керек. Анын мазмуну мындай болушу керек: https://github.com/miroha/Telegram-Bot/blob/master/Procfile
-
pom.xml ичинде, мисалга ылайык, төмөнкү саптарды кошуңуз , мында mainClass тегинде негизги методду камтыган класска жол көрсөтүлөт: bot.BotApplication (эгерде BotApplication классы бот папкасында болсо).
-
Mvn пакетинин буйруктарын ж.б. колдонуп эч кандай долбоорду курбаңыз, Heroku сиз үчүн баарын чогултат.
-
Бул долбоорго gitignore кошуу максатка ылайыктуу, мисалы, бул:
# Log file *.log # Compiled resources target # Tests test # IDEA files .idea *.iml
-
Чындыгында долбоорду githubга жүктөп, андан кийин репозиторийди Heroku менен туташтырыңыз (же башка ыкмаларды колдонуңуз, эгер жаңылбасам, алардын 3ү бар).
-
Жүктөп алуу ийгorктүү болсо ("Ийгorктүү куруу"), Dynos конфигурациясына өтүңүз:
жана сыдырманы которуңуз, андан кийин анын КҮЙҮК абалында экенин текшериңиз (мен муну жасабагандыктан, менин ботум иштебей калды жана мен бир-эки күн мээмди кысып, көп керексиз кыймылдарды жасадым. ).
-
Githubда бот белгисин жашырыңыз. Бул үчүн, сиз чөйрө өзгөрмөсүнөн белгини алышыңыз керек:
public class Bot extends TelegramLongPollingBot { private static final String BOT_TOKEN = System.getenv("TOKEN"); @Override public String getBotToken() { return BOT_TOKEN; } ... }
Анан ботту жайгаштыргандан кийин, бул өзгөрмөнү Орнотуулар өтмөгүндөгү Heroku тактасына орнотуңуз (TOKENдин оң жагында VALUE талаасы болот, боттун белгисин ошол жерге көчүрүңүз):
- Java тorнде жазылган толук иштеп жаткан долбоорду алды;
- үчүнчү тарап API (Telegram Bot API) менен иштөөнү үйрөнгөн;
- практикада мен сериялаштырууга тереңирээк кирдим, JSON жана Джексон китепканасы менен көп иштедим (башында мен GSON колдондум, бирок аны менен көйгөйлөр бар);
- файлдар менен иштөөдө көндүмдөрүм бекемделди, Java NIO менен тааныштым;
- конфигурация .xml файлдары менен иштөөнү үйрөндүм жана журналга көнүп калдым;
- Өнүктүрүү чөйрөсүндө жакшыртылган тажрыйба (IDEA);
- git менен иштөөнү үйрөндү жана gitignore баалуулугун үйрөндү;
- веб-баракчаларды талдоо боюнча көндүмдөрдү алган (Jsoup library);
- бир нече дизайн үлгүлөрүн үйрөнгөн жана колдонгон;
- codeду жакшыртуу сезимин жана каалоосун иштеп чыккан (рефакторинг);
- Чечимдерди интернеттен тапканды жана жооп таба албаган суроолорду берүүдөн тартынбоону үйрөндүм.
GO TO FULL VERSION