@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 ซึ่งฉันส่งผ่านตัวสร้างการอัปเดตต่อไปในห่วงโซ่ การอัปเดตเป็นสิ่งสำคัญที่สุดเมื่อทำงานกับบอทและไม่ควรสูญหาย เนื่องจากด้วยความช่วยเหลือของข้อมูลที่เก็บไว้ในการอัปเดต เราจะค้นหาว่าผู้ใช้คนไหนและแชทใดที่ควรส่งการตอบกลับ เพื่อสร้างคำตอบให้กับผู้ใช้ ฉันจึงเขียนชั้นเรียนแยกต่างหาก สามารถส่งข้อความปกติ ข้อความด้วยแป้นพิมพ์อินไลน์ ข้อความพร้อมรูปภาพ และข้อความด้วยแป้นพิมพ์ตอบกลับ แป้นพิมพ์อินไลน์มีลักษณะดังนี้: 
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;
...
แป้นพิมพ์ตอบกลับมีลักษณะดังนี้: 
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());
}
...
ดังนั้นขึ้นอยู่กับคำสั่งที่คุณส่งไปยังบอท ตัวจัดการพิเศษจะรวมอยู่ในงาน มาดูผลงานของ parser และไลบรารีกันดีกว่า หากคุณส่งลิงก์ไปยังเกมใน Google Play สโตร์ให้บอท ตัวจัดการ พิเศษจะทำงานโดย อัตโนมัติ ในการตอบสนอง ผู้ใช้จะได้รับข้อมูลเกี่ยวกับเกมในรูปแบบต่อไปนี้: 
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 ในห้องสมุด) ไม่สามารถหาวิธีแก้ปัญหาอื่นได้ นี่คือคุณลักษณะของการดีซีเรียลไลซ์โดยใช้ Jackson ดังนั้นทุกครั้งที่มีการร้องขอลิงก์ เกมจะถูกเพิ่มเข้าไปในห้องสมุด ถ้าเป็นไปได้ และห้องสมุดก็จะขยายออกไปด้วย ข้อมูลเพิ่มเติมเกี่ยวกับเกมเฉพาะสามารถรับได้โดยใช้คำสั่ง/libraryGame_Name คุณสามารถค้นหาทั้งพารามิเตอร์เฉพาะ (เช่น เวอร์ชันปัจจุบัน) และพารามิเตอร์ทั้งหมดในคราวเดียว สิ่งนี้ถูกนำมาใช้โดยใช้แป้นพิมพ์อินไลน์ซึ่งได้กล่าวไว้ก่อนหน้านี้ ในระหว่างทำงาน ฉันยังใช้ทักษะที่ได้รับที่นี่พร้อมกับแก้ไขปัญหาด้วย ตัวอย่างเช่น รายชื่อเกมสุ่มที่อยู่ในไลบรารี (ตัวเลือกนี้สามารถใช้งานได้โดยใช้คำสั่ง /library):
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 ของเพจ ซึ่งจะต้องมีการวิเคราะห์และแยกวิเคราะห์เพิ่มเติม คุณจะไม่สามารถหลอกบอทด้วยลิงก์ที่ไม่ถูกต้องหรือเป็นอันตรายได้
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 Store จากเซิร์ฟเวอร์ที่ตั้งอยู่ในยุโรป ดังนั้นหน้าใน Google Play Store จะเปิดขึ้นในภาษาที่เหมาะสม ฉันต้องเขียนไม้ยันรักแร้ที่จะบังคับให้ "เปลี่ยนเส้นทาง" ไปยังหน้าเวอร์ชันรัสเซีย (ท้ายที่สุดแล้วโครงการนี้มุ่งเป้าไปที่ผู้ชมของเรา) ใน การ ดำเนินการนี้ ในตอนท้ายของลิงก์ คุณจะต้องเพิ่มพารามิเตอร์ hl: &hl=ru อย่างระมัดระวัง ในคำขอ GET ไปยังเซิร์ฟเวอร์ Google Play
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;
}
หลังจากการเชื่อมต่อสำเร็จ เราได้รับเอกสาร HTML ที่พร้อมสำหรับการวิเคราะห์และแยกวิเคราะห์ แต่นี่อยู่นอกเหนือขอบเขตของบทความนี้ รหัสพาร์เซอร์อยู่ที่นี่ ตัวแยกวิเคราะห์จะดึงข้อมูลที่จำเป็นและสร้างวัตถุพร้อมกับเกม ซึ่งจะถูกเพิ่มลงในไลบรารีในภายหลังหากจำเป็น <h2>เพื่อสรุป</h2>บอทรองรับหลายคำสั่งที่มีฟังก์ชันการทำงานบางอย่าง ได้รับข้อความจากผู้ใช้และจับคู่กับคำสั่ง หากเป็นลิงก์หรือคำสั่ง /game + link ระบบจะตรวจสอบลิงก์นั้นเพื่อดูว่าเป็นของ Google Play หรือไม่ หากลิงค์ถูกต้องจะเชื่อมต่อผ่าน Jsoup และรับเอกสาร HTML เอกสารนี้ได้รับการวิเคราะห์ตามตัวแยกวิเคราะห์ที่เขียน ข้อมูลที่จำเป็นเกี่ยวกับเกมจะถูกดึงออกมาจากเอกสาร จากนั้นวัตถุในเกมจะถูกเติมด้วยข้อมูลนี้ ถัดไป วัตถุที่มีเกมจะถูกวางไว้ในที่จัดเก็บในตัวเครื่อง (หากเกมยังไม่มี) และเขียนลงในไฟล์ทันทีเพื่อหลีกเลี่ยงการสูญหายของข้อมูล เกมที่บันทึกไว้ในไลบรารี (ชื่อของเกมคือกุญแจสำหรับแผนที่ วัตถุที่มีเกมคือค่าของแผนที่) สามารถรับได้โดยใช้คำสั่ง /library Game_name หากพบเกมที่ระบุในไลบรารีของบอท ผู้ใช้จะได้รับแป้นพิมพ์อินไลน์ส่งคืน ซึ่งเขาสามารถรับข้อมูลเกี่ยวกับเกมได้ หากไม่พบเกม คุณต้องตรวจสอบให้แน่ใจว่าสะกดชื่อถูกต้อง (ชื่อนั้นจะต้องตรงกับชื่อเกมใน Google Play Store โดยสมบูรณ์ ยกเว้นในกรณีนี้) หรือเพิ่มเกมลงในไลบรารีโดยการส่งบอท ลิงก์ไปยังเกม ฉันปรับใช้บอทบน heroku และสำหรับผู้ที่วางแผนจะเขียนบอทของตัวเองในอนาคตและโฮสต์มันฟรีบน heroku ฉันจะให้คำแนะนำสองสามข้อในการแก้ปัญหาที่คุณอาจเผชิญ (เนื่องจากฉันเจอพวกเขาด้วยตัวเอง) น่าเสียดาย เนื่องจากลักษณะของ Heroku ไลบรารีบอทจึงถูก "รีเซ็ต" อย่างต่อเนื่องทุกๆ 24 ชั่วโมง แผนของฉันไม่รองรับการจัดเก็บไฟล์บนเซิร์ฟเวอร์ Heroku ดังนั้นจึงดึงไฟล์เกมของฉันจาก Github มีวิธีแก้ไขปัญหาหลายประการ: ใช้ฐานข้อมูลหรือค้นหาเซิร์ฟเวอร์อื่นที่จะจัดเก็บไฟล์นี้พร้อมกับเกม ฉันตัดสินใจว่าจะไม่ทำอะไรในตอนนี้ เนื่องจากโดยพื้นฐานแล้วบอทไม่ได้มีประโยชน์ขนาดนั้น ฉันต้องการมันมากกว่าที่จะได้รับประสบการณ์เต็มรูปแบบ ซึ่งโดยพื้นฐานแล้วคือสิ่งที่ฉันประสบความสำเร็จ ดังนั้นคำแนะนำสำหรับ Heroku:
-
คุณมักจะต้องลงทะเบียนกับ 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 แล้วเชื่อมต่อ Repository กับ Heroku (หรือใช้วิธีอื่นก็มี 3 วิธีนะ ถ้าจำไม่ผิด)
-
หากการดาวน์โหลดสำเร็จ ("สร้างสำเร็จ") อย่าลืมไปที่กำหนดค่า 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
- เรียนรู้การทำงานกับ API บุคคลที่สาม (Telegram Bot API)
- ในทางปฏิบัติ ฉันเจาะลึกลงไปในการทำให้เป็นอนุกรม ทำงานกับ JSON และไลบรารี Jackson เป็นอย่างมาก (ตอนแรกฉันใช้ GSON แต่มีปัญหากับมัน)
- เสริมทักษะของฉันเมื่อทำงานกับไฟล์ ทำความคุ้นเคยกับ Java NIO
- เรียนรู้การทำงานกับไฟล์การกำหนดค่า .xml และคุ้นเคยกับการบันทึก
- ปรับปรุงความสามารถในสภาพแวดล้อมการพัฒนา (IDEA)
- เรียนรู้ที่จะทำงานกับ git และเรียนรู้คุณค่าของ gitignore
- ได้รับทักษะในการแยกวิเคราะห์หน้าเว็บ (ไลบรารี Jsoup)
- เรียนรู้และใช้รูปแบบการออกแบบหลายแบบ
- พัฒนาความรู้สึกและความปรารถนาที่จะปรับปรุงโค้ด (การปรับโครงสร้างใหม่)
- ฉันเรียนรู้ที่จะค้นหาวิธีแก้ปัญหาทางออนไลน์ และไม่ต้องอายที่จะถามคำถามที่หาคำตอบไม่ได้

GO TO FULL VERSION