@Override
public void onUpdateReceived(Update update) {
UpdatesReceiver.handleUpdates(update);
}
جسے میں ہینڈلر کو دیتا ہوں (اپنی اپڈیٹس ریسیور کلاس):
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;
...
جوابی کی بورڈ اس طرح لگتا ہے: اور جوہر میں، یہ صارف کی ٹائپنگ کی جگہ لے لیتا ہے۔ "لائبریری" بٹن پر کلک کرنے سے بوٹ کو فوری طور پر "لائبریری" کا پیغام بھیج دیا جائے گا۔ ہر قسم کے کی بورڈ کے لیے، میں نے بلڈر پیٹرن کو نافذ کرتے ہوئے اپنی کلاس لکھی: 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());
}
...
اس طرح، اس بات پر منحصر ہے کہ آپ بوٹ کو کیا کمانڈ بھیجتے ہیں، اس کام میں ایک خاص ہینڈلر شامل کیا جائے گا۔ آئیے مزید آگے بڑھتے ہیں اور تجزیہ کار اور لائبریری کے کام کو دیکھتے ہیں۔ اگر آپ بوٹ کو گوگل پلے اسٹور میں کسی گیم کا لنک بھیجتے ہیں تو ایک خصوصی ہینڈلر خود بخود کام کرے گا ۔ جواب میں، صارف کو گیم کے بارے میں درج ذیل شکل میں معلومات موصول ہوں گی: ساتھ ہی، ایک طریقہ بھی کہا جائے گا جو گیم کو بوٹ کی لائبریری میں شامل کرنے کی کوشش کرے گا (پہلے مقامی نقشے پر، پھر -> json فائل )۔ اگر گیم پہلے سے ہی لائبریری میں ہے، تو پھر ایک چیک کیا جائے گا (جیسا کہ ایک باقاعدہ ہیش میپ میں ہے)، اور اگر فیلڈ ڈیٹا (مثال کے طور پر، ورژن نمبر بدل گیا ہے)، تو لائبریری میں موجود گیم کو اوور رائٹ کر دیا جائے گا۔ اگر کوئی تبدیلیاں نہیں پائی جاتی ہیں، تو کوئی اندراج نہیں کیا جائے گا۔ اگر لائبریری میں بالکل بھی کوئی گیم نہیں تھی، تو اسے پہلے مقامی نقشے پر لکھا جاتا ہے (ایک چیز جیسے tyk )، اور پھر json فائل میں لکھا جاتا ہے، کیونکہ اگر سرور پر ایپلیکیشن غیر متوقع طور پر بند ہو جاتی ہے، تو ڈیٹا کھو گیا، لیکن اسے ہمیشہ فائل کا استعمال کرتے ہوئے پڑھا جا سکتا ہے۔ دراصل، جب پروگرام شروع ہوتا ہے، لائبریری ہمیشہ پہلی بار کسی سٹیٹک بلاک سے فائل سے لوڈ ہوتی ہے۔
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 لکھ کر، بوٹ پھر بھی تلاش کرے گا۔ لائبریری میں کھیل ٹائٹن کویسٹ)۔ دوسرا حل تلاش کرنا ممکن نہیں تھا، یہ جیکسن کا استعمال کرتے ہوئے ڈی سیریلائزیشن کی خصوصیات ہیں۔ لہذا، لنک کی ہر درخواست کے ساتھ، اگر ممکن ہو تو گیم کو لائبریری میں شامل کیا جاتا ہے، اور اس طرح لائبریری پھیل جاتی ہے۔ کسی مخصوص گیم کے بارے میں مزید معلومات /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);
}
بوٹ پروسیس کیسے لنک کرتا ہے؟ یہ انہیں چیک کرتا ہے کہ آیا وہ گوگل پلے (میزبان، پروٹوکول، پورٹ) سے تعلق رکھتے ہیں:
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();
}
...
یہاں ہمیں علاقائی ترتیبات کے ساتھ ایک مسئلہ حل کرنا تھا۔ بوٹ یورپ میں واقع سرور سے گوگل پلے اسٹور سے جڑتا ہے، اس لیے گوگل پلے اسٹور میں صفحہ مناسب زبان میں کھلتا ہے۔ مجھے ایک بیساکھی لکھنی پڑی جو صفحہ کے روسی ورژن پر زبردستی "ری ڈائریکٹ" کرتی ہے (اس منصوبے کا مقصد ہمارے سامعین کے لیے تھا)۔ ایسا کرنے کے لیے، لنک کے آخر میں آپ کو گوگل پلے سرور پر GET کی درخواست میں hl: &hl=ru پیرامیٹر کو احتیاط سے شامل کرنے کی ضرورت ہے ۔
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>بوٹ کئی کمانڈز کو سپورٹ کرتا ہے جن میں مخصوص فعالیت ہوتی ہے۔ یہ صارف سے پیغامات وصول کرتا ہے اور انہیں اس کے حکموں سے ملاتا ہے۔ اگر یہ لنک ہے یا /گیم + لنک کمانڈ، تو یہ اس لنک کو چیک کرتا ہے کہ آیا یہ گوگل پلے سے تعلق رکھتا ہے۔ اگر لنک درست ہے، تو یہ Jsoup کے ذریعے جڑتا ہے اور HTML دستاویز وصول کرتا ہے۔ اس دستاویز کا تجزیہ تحریری تجزیہ کار کی بنیاد پر کیا گیا ہے۔ گیم کے بارے میں ضروری معلومات کو دستاویز سے نکالا جاتا ہے، اور پھر گیم والی چیز اس ڈیٹا سے بھر جاتی ہے۔ اس کے بعد، گیم کے ساتھ آبجیکٹ کو مقامی اسٹوریج میں رکھا جاتا ہے (اگر گیم ابھی موجود نہیں ہے) اور ڈیٹا کے نقصان سے بچنے کے لیے فوری طور پر ایک فائل پر لکھا جاتا ہے۔ لائبریری میں ریکارڈ کردہ گیم (گیم کا نام نقشے کی کلید ہے، گیم کے ساتھ آبجیکٹ نقشہ کی قدر ہے) کمانڈ /library Game_name کا استعمال کرتے ہوئے حاصل کیا جا سکتا ہے۔ اگر مخصوص گیم بوٹ کی لائبریری میں پائی جاتی ہے، تو صارف کو ایک ان لائن کی بورڈ واپس کر دیا جائے گا، جس سے وہ گیم کے بارے میں معلومات حاصل کر سکتا ہے۔ اگر گیم نہیں ملتی ہے، تو آپ کو یا تو یقینی بنانا ہوگا کہ نام کی ہجے درست ہے (اسے گوگل پلے اسٹور میں گیم کے نام سے مکمل طور پر مماثل ہونا چاہیے، سوائے کیس کے)، یا بوٹ بھیج کر گیم کو لائبریری میں شامل کریں۔ گیم کا لنک۔ میں نے بوٹ کو ہیروکو پر تعینات کیا اور ان لوگوں کے لیے جو مستقبل میں اپنا بوٹ لکھنے اور اسے ہیروکو پر مفت میں میزبانی کرنے کا ارادہ رکھتے ہیں، میں آپ کو درپیش مشکلات کو حل کرنے کے لیے چند تجاویز پیش کروں گا (چونکہ میں نے خود ان کا سامنا کیا ہے)۔ بدقسمتی سے، ہیروکو کی نوعیت کی وجہ سے، بوٹ لائبریری ہر 24 گھنٹے میں ایک بار مسلسل "ری سیٹ" ہوتی ہے۔ میرا منصوبہ ہیروکو سرورز پر فائلوں کو ذخیرہ کرنے کی حمایت نہیں کرتا ہے، لہذا یہ صرف گیتھب سے میری گیم فائل کو کھینچتا ہے۔ اس کے کئی حل تھے: ڈیٹا بیس کا استعمال کریں، یا کوئی اور سرور تلاش کریں جو اس فائل کو گیم کے ساتھ اسٹور کرے۔ میں نے ابھی کچھ نہ کرنے کا فیصلہ کیا، کیونکہ بنیادی طور پر بوٹ اتنا مفید نہیں ہے۔ مجھے ایک مکمل تجربہ حاصل کرنے کے بجائے اس کی ضرورت تھی، جو بنیادی طور پر میں نے حاصل کیا۔ لہذا، ہیروکو کے لیے سفارشات:
-
اگر آپ روس میں رہتے ہیں تو آپ کو VPN کا استعمال کرتے ہوئے ہیروکو پر رجسٹر ہونا پڑے گا۔
-
پروجیکٹ کی جڑ میں آپ کو بغیر کسی توسیع کے فائل ڈالنے کی ضرورت ہے جسے Procfile کہتے ہیں۔ اس کا مواد اس طرح ہونا چاہئے: https://github.com/miroha/Telegram-Bot/blob/master/Procfile
-
pom.xml میں، مثال کے مطابق درج ذیل لائنیں شامل کریں ، جہاں mainClass ٹیگ میں کلاس کے راستے کی نشاندہی کرتا ہے جس میں بنیادی طریقہ ہے: bot.BotApplication (اگر BotApplication کلاس بوٹ فولڈر میں ہے)۔
-
ایم وی این پیکیج کمانڈز وغیرہ کا استعمال کرتے ہوئے کوئی پروجیکٹ نہ بنائیں، ہیروکو آپ کے لیے سب کچھ جمع کردے گا۔
-
اس منصوبے میں ایک gitignore شامل کرنے کا مشورہ دیا جاتا ہے، مثال کے طور پر:
# Log file *.log # Compiled resources target # Tests test # IDEA files .idea *.iml
-
دراصل پروجیکٹ کو گیتھب پر اپ لوڈ کریں، اور پھر ریپوزٹری کو ہیروکو سے جوڑیں (یا دوسرے طریقے استعمال کریں، ان میں سے 3 ہیں، اگر میں غلط نہیں ہوں)۔
-
اگر ڈاؤن لوڈ کامیاب رہا ("تعمیر کامیاب")، تو یقینی بنائیں کہ کنفیگر ڈائنوس پر جائیں:
اور سلائیڈر کو سوئچ کریں، اور پھر اس بات کو یقینی بنائیں کہ یہ آن پوزیشن میں ہے (اس حقیقت کی وجہ سے کہ میں نے ایسا نہیں کیا، میرا بوٹ کام نہیں کیا اور میں نے کچھ دنوں تک اپنے دماغ کو ریک کیا اور بہت سی غیر ضروری حرکتیں کیں )۔
-
گیتھب پر بوٹ ٹوکن چھپائیں۔ ایسا کرنے کے لیے، آپ کو ماحولیاتی متغیر سے ٹوکن حاصل کرنے کی ضرورت ہے:
public class Bot extends TelegramLongPollingBot { private static final String BOT_TOKEN = System.getenv("TOKEN"); @Override public String getBotToken() { return BOT_TOKEN; } ... }
اور پھر بوٹ کو تعینات کرنے کے بعد، اس متغیر کو ہیروکو ڈیش بورڈ میں سیٹنگز ٹیب میں سیٹ کریں (ٹوکن کے دائیں جانب ایک VALUE فیلڈ ہوگی، وہاں اپنے بوٹ کا ٹوکن کاپی کریں):
- جاوا میں لکھا ہوا مکمل طور پر کام کرنے والا پروجیکٹ موصول ہوا؛
- تھرڈ پارٹی API (ٹیلیگرام بوٹ API) کے ساتھ کام کرنا سیکھا؛
- عملی طور پر، میں نے سیریلائزیشن کی گہرائی تک رسائی حاصل کی، JSON اور Jackson لائبریری کے ساتھ بہت کام کیا (شروع میں میں نے GSON استعمال کیا، لیکن اس میں مسائل تھے)؛
- فائلوں کے ساتھ کام کرتے وقت اپنی صلاحیتوں کو مضبوط کیا، جاوا این آئی او سے واقف ہوا؛
- میں نے کنفیگریشن .xml فائلوں کے ساتھ کام کرنا سیکھا اور خود کو لاگنگ کرنے کا عادی بنایا۔
- ترقیاتی ماحول (IDEA) میں بہتر مہارت؛
- git کے ساتھ کام کرنا سیکھا اور gitignore کی قدر سیکھی۔
- ویب پیج پارسنگ (Jsoup لائبریری) میں مہارت حاصل کی؛
- کئی ڈیزائن پیٹرن سیکھے اور استعمال کیے؛
- کوڈ (ری فیکٹرنگ) کو بہتر بنانے کا احساس اور خواہش پیدا کی؛
- میں نے آن لائن حل تلاش کرنا سیکھا اور ایسے سوالات پوچھنے میں شرم محسوس نہ کرنا جن کا مجھے جواب نہیں مل سکا۔
GO TO FULL VERSION