با سلام خدمت دوستان
ادامه مقاله رو به درخواست دوستان می خوام تکمیل کنم و مباحث حرفه ای در مورد ربات تلگرام و هندلینگ اون با دلفی رو توضیح بدم. و سعی بر اینه که دلفی کارهای محترم از سایر زبان ها تو این زمینه عقب نیفتن.
در این مقاله سعی میکنم نحوه نوشتن سرویس بصورت Thread و مبحث long Polling که قطعا قسمت مهم کار هست رو توضیح بدم و همچنین طریقه افزودن ReplyKeyboardMarkup و InlineKeyboardMarkup و Callbackquery رو و مهم اینکه چطوری از پایگاه داده دیتا مورد نظر رو با توجه به درخواست به طرف نمایش بدیم.
دکمه های ReplyKeyboardMarkup دکمه هایی هستند که زیر بخش نوشتن پیام اضافه میشن و می تونن به عنوان منو استفاده بشن.
دکمه های InlineKeyboardMarkup دکمه هایی هستن که زیر پیام ظاهر میشن و با CallBack Query مدیریت میشن و میشه کلیک کاربر رو فهمید. بطور مثال نظرسنجی
خوب، ما به دو طریق می تونیم با ربات تلگرام ارتباط داشته باشیم و پیام ها و دستوراتی که با بات ارسال میشه رو دریافت کنیم . یکی Webhook هست که قطعا به یک هاست و سرویس اینترنتی نیاز داره، در واقع webhook آدرس اینترنتی هست که به بات معرفی میشه و بات از این به بعد هر پیام رو که دریافت می کنه بصورت اتوماتیک به آدرس Webhook میفرسته.خوب ما اینجا چون یه سرویس ویندوزی می نویسیم با این امکان کار نداریم پس باید خودمون دست به کار بشیم و پیام های بات رو در یک سرویس که تعطیل نمیشه هی واکشی کنیم و جواب مناسب رو به هر پیام بدیم.
قبلا عرض کرده بودم که بات فقط با Chat_id کار میکنه و هر شخص یا بهتر بگیم اکانت تلگرام یه Chat_ID منحصر بفرد داره که می تونیم برای اطلاع رسانی های بعدی این Chat_id رو تو بانک ذخیره کنیم.
خوب بریم سراغ کد نویسی
چیزی که برای شروع کار بهش نیاز داریم یک ربات خشک و خالی هست که با BotFather@ می سازید و Token ربات رو دریافت میکنید. که با چند دستور ساده تلگرام برای شما میسازه.
یک عدد دلفی و اینترنت
در این کد نویسی من از indy و کامپوننت idHttp استفاده می کنم روی دلفی 7 . (کوزه گریم دیگه)
کنار نرم افزارمون چند تا dll هم استفاده میشه که libeay32.dll و ssleay32.dll هست دقت کنید این Dllها با ورژن ایندی باید مطابقت داشته باشه. ایندی من 9.00.10 هست.
بسیار عالی، کنار نرم افزار ما یه setting.ini هست با Notepad بازش کنید و توکن بات رو توش قرار بدین. کانکشن بانک اطلاعاتی هم می تونه اینجا قرار بگیره . تو این کد من فعلا غیر فعالش کردم . خودتون بخش های غیر فعال شده رو می تونید درست کنید.
(ببخشید یخورده جو گرفته منو بذارین خودمونی تر بگم)
حالا می خوام کدها رو شرح بدم خدمت شما:
uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, IdBaseComponent, IdComponent, IdTCPConnection,
IdTCPClient, IdHTTP,IdSSLOpenSSL, Buttons, IdMultipartFormData, OleCtrls,
SHDocVw,IniFiles, DB, ADODB, ExtCtrls;
بخش uses که معرفی حضورتون هست
خوب برای اینکه بتونیم Long Polling پیاده کنیم من از thread برای این کار استفاده کردم
type TBotThread = class(TThread)
private
protected
procedure Execute; override;
Procedure WriteToLog(st:string);
procedure SendMessage(ChatID, Text, parse_mode:string;disable_notification:boolean);
procedure SendMessageAndKey(ChatID,Text,Keys, parse_mode:string;disable_notification:boolean);
// Get Data From DB For Learn!
Function GetDataFromDB(ChatID:String;mode: Integer) : WideString;
Function CheckNumbers(NCode:String) : Boolean;
public
FActive: Boolean ;
FidHttpGet : TIdHTTP;
FidHttpSend : TIdHTTP;
Fmemo : TMemo;
FConnection: TADOConnection;
FAdoDataset : TADODataSet;
FBotUser : String;
FShowBotLink : Boolean;
constructor Create(Active:Boolean;IdHTTPGet,idHttpSend : TIdHTTP;memo : TMemo);
destructor Destroy; override;
end;
در این نرم افزار کوچک ولی هوشمند، من از سه کامپوننت idHttp استفاده می کنم یکی برای گرفتن اطلاعات ربات، یکی برای دریافت پیام های بات و یکی هم برای ارسال پیام.
در کد بالا دو متد SendMessage و SendMessageAndkey رو پیاده کردم که متد اول پیام ساده ارسال می کنه و متد دوم علاوه بر پیام ساده یک کیبورد هم اضافه می کنه که همون منوهای پایین بات هست.
یه سری از کامپوننت های روی فرم اصلی رو ترد همینطوری نمیشناسه پس باید براش تعریف کنیم و موقع ساخت بهش بدیم مثل دو تا idHttp کانکشن بان و همچنین یه Dataset نام کاربری بات رو برای نمایش ته هر پیام.
متدی هم به نام GetDataFromDB براش تعریف کردم تا داده ها رو از بانک اطلاعاتی بخونه و با توجه به درخواست کاربر بهش نمایش بده. شما می تونید متد های مختلفی داشته باشید که هر متد اطلاعات خاصی رو از بانک واکشی کنه.
یه متد WriteToLog هم گذاشتم تا خطاهای Thread لاگ کنه.
خوب بریم سراغ تعریف های اولیه و ساخت THread
var fmMain: TfmMain;
API : string;
ini : TIniFile;
ConnStr : string;
TelegService : TBotThread;
implementation
uses uLkJSON, un_SyncLog, ConstUnit, UGutility;
تنظیماتی مثل Token بات و همچنین کانکشن بانک رو از فایل Setting.ini کنار برنامه می خونم.
procedure TfmMain.LoadSetting;var
i, Timecount : integer;
begin
try
ini := TIniFile.Create(ExtractFilePath(Application.ExeNam e)+'Setting.ini');
Connstr := ini.ReadString('Server','ConnectionString','');
API := ini.ReadString('Telegram' , 'Token' , '');
finally
ini.Free;
end;
end;
این متد پایین لاگ رو پر میکنه:
procedure TfmMain.WriteToLog(st: string);var logFile : TextFile;
logdate : String;
begin
AssignFile(logFile, 'TelegLog.txt');
if FileExists('TelegLog.txt') then
Append(logFile)
else
Rewrite(logFile);
logdate := DateTimeToStr(Now);
Writeln(logFile,logdate + ' - ' + st);
CloseFile(logFile);
end;
procedure TfmMain.ResetLog;
var logFile : TextFile;
begin
AssignFile(logFile, 'TelegLog.txt');
Rewrite(logFile);
CloseFile(logFile);
end;
متد زیر هم اطلاعات بات رو که با متد Getme از تلگرام در قالب json گرفته میشه رو Deserialize می کنه و تو دو تا Label نمایش میده.
procedure TfmMain.DeserializeJSONBotInfo(returnval: string);var
systems : TStrings;
returnval2: string;
json , item , ResultNode , OKNode , msgNode ,fromNode :TlkJSONbase;
i,j: integer;
list: TlkJSONObject;
okVal : boolean;
nodename : String;
begin
json:= TlkJSON.ParseText(returnval);
OKNode := TlkJSONboolean(json).Field['ok'];
okVal := OKNode.Value;
ResultNode := TlkJSONlist(json).Field['result'];
if okVal then
begin
lblBotName.Caption := VarToStr(ResultNode.Field['first_name'].Value);
lblUserName.Caption := '@' + VarToStr(ResultNode.Field['username'].Value);
end;
end;
خوب حالا بریم سراغ متد های Thread
متد constructor برای ترد که دوتا idHttp و یک Memo رو توش میفرستم تا پیام های دریافتی رو تو Memo مونیتور کنه.
constructor TBotThread.Create(Active: Boolean;IdHTTPGet,idHttpSend : TIdHTTP;memo : TMemo);begin
inherited Create(True);
FActive := Active;
FIDHTTPGet := IdHTTPGet;
FidHttpSend := idHttpSend;
Fmemo := memo;
end;
اینم Destroy
destructor TBotThread.Destroy;begin
inherited Destroy;
end;
خوب متد های SendMessage و SendMessageAndKey رو قبل از متد Execute که متد اصلی ما هست توضیح میدم: ابتدا کل متد و سپس خط به خط توضیحش رو میدم تا براتون روشنتر بشه قضیه:
procedure TBotThread.SendMessage(ChatID, Text, parse_mode: string;disable_notification:boolean);Var
Stream: TStringStream;
Params: TIdMultipartFormDataStream;
//Text : WideString;
msg : WideString;
Src : string;
LHandler: TIdSSLIOHandlerSocket;
begin
try
try
if FShowBotLink then
Text := Text + LineBreak + FBotUser;
msg := '/sendmessage';
Stream := TStringStream.Create('');
Params := TIdMultipartFormDataStream.Create;
Params.AddFormField('chat_id',ChatID);
if parse_mode <> '' then
Params.AddFormField('parse_mode',parse_mode);
if disable_notification then
Params.AddFormField('disable_notification','true')
else
Params.AddFormField('disable_notification','false' );
Params.AddFormField('disable_web_page_preview','tr ue');
Params.AddFormField('text',UTF8Encode(Text));
LHandler := TIdSSLIOHandlerSocket.Create(nil);
FidHttpSend.ReadTimeout := 30000;
FidHttpSend.IOHandler:=LHandler;
LHandler.SSLOptions.Method := sslvTLSv1;
LHandler.SSLOptions.Mode := sslmUnassigned;
FidHttpSend.HandleRedirects := true;
FidHttpSend.Post(BaseUrl + API + msg, Params, Stream);
finally
Params.Free;
Stream.Free;
ENd;
except
on E: EIdHTTPProtocolException do
begin
if E.ReplyErrorCode = 403 then
begin
WriteToLog('Bot was blocked by the user');
end;
end;
end;
end;
متد SendMessage پارامترهایی داره که دوتاش مشخصه پارامتر Parse_mode تو ارسال پیام تلگرام می تونه با مقادیر html و markdown استفاده بشه که نمایانگر فرمت پیام شماست. حالا پیام شما می تنه در قالب html و یا با کاراکتر هایی که برای تلگرام کلاینت معنا و مفهوم خاصی داره، ارسال بشه. برای اینکه بهتر درک کنید یه سر به اینجا بزنید
پارامتر disable_notification هم به پیام میگه بصورت Silent بره یا نه با سر و صدا بره!
یه پارامتر هم به نام disable_web_page_preview تو این کد استفاده کردم که باعث میشه اگر متن پیامتون حاوی لینک باشه پایین پیام Preview این صفحه اینترنتی رو نمایش بده یا نه.
دیتا رو هم بصورت MultipartFormData به سرور تلگرام ارسال میکنیم.
خوب حالا متد SendMessageAndKey رو توضیحش رو خدمت شما بدم.
procedure TBotThread.SendMessageAndKey(ChatID, Text,Keys, parse_mode: string;disable_notification:Boolean);Var
Stream: TStringStream;
Params: TIdMultipartFormDataStream;
msg : WideString;
Src : string;
LHandler: TIdSSLIOHandlerSocket;
Json : TMemoryStream;
btn : string;
UTF8 : UTF8String;
BytesCount : integer;
begin
btn := keys;
Json := TMemoryStream.Create;
UTF8 := AnsiToUtf8(btn);
BytesCount := Length(UTF8);
Json.WriteBuffer(UTF8[1], BytesCount);
Json.Position := 0;
try
try
if FShowBotLink then
Text := Text + LineBreak + FBotUser;
msg := '/sendmessage';
Stream := TStringStream.Create('');
Params := TIdMultipartFormDataStream.Create;
Params.AddObject('reply_markup','application/json',Json);
Params.AddFormField('chat_id',ChatID);
if parse_mode <> '' then
Params.AddFormField('parse_mode',parse_mode);
if disable_notification then
Params.AddFormField('disable_notification','true')
else
Params.AddFormField('disable_notification','false' );
Params.AddFormField('text',UTF8Encode(Text));
LHandler := TIdSSLIOHandlerSocket.Create(nil);
FidHttpSend.ReadTimeout := 30000;
FidHttpSend.IOHandler:=LHandler;
LHandler.SSLOptions.Method := sslvTLSv1;
LHandler.SSLOptions.Mode := sslmUnassigned;
FidHttpSend.HandleRedirects := true;
FidHttpSend.Post(BaseUrl + API + msg, Params, Stream);
finally
Params.Free;
Stream.Free;
ENd;
except
on E: EIdHTTPProtocolException do
begin
if E.ReplyErrorCode = 403 then
begin
WriteToLog('Bot was blocked by the user');
end;
end;
end;
end;
این متد علاوه بر پارامتر های متد SendMessage یه پارامترKeys هم اضافه داره که این پارامتر حاوی اون کیبوردی هست که می خوای پایین بات یا زیر پیامتون نمایش داده بشه.
این کیبورد ها در قالب شی Json همراه با متن پیام ما ارسال میشه یه نمونه از این کیبوردها اینطوری هست:
MainMenu = '{"keyboard": [["منوي2","منوي1"],["منوي4", "منوي3"],["ارتباط با ما" ,"منوي5"]], "resize_keyboard": true }';
دو نوع کیبود داریم که یکی ReplyKeyboardMarkup هست و دیگری InlineKeyboardMarkup
نمونه inline keyboard :
LikeMenuInline = '{"inline_keyboard": [[{ "text": "Delphi", "callback_data": "Delphi" }],[{ "text": "Asp.net", "callback_data": "Asp.net" }],[{ "text": "Java","callback_data": "Java" }]]}';
ReplyKeyboardMarkup در پایین بات ظاهر میشه و InlineKeyboardMarkup پایین پیام ارسالی.
ReplyKeyboardMarkup یک آرایه ای از آرایه های متنی هست که بسته به تعداد آرایه های داخلی، تعداد سطر و ستون دکمه ها تعیین میشه. کاربر با کلیک بر روی هر دکمه باعث میشه متن دکمه ارسال بشه بصورت پیام ساده که حاوی شی JSON Message هست
InlineKeyboardMarkup هم یه آرایه ای از InlineKeyboardButton هست که خود اون فیلدهایی مثل Text و Callback_data یا همون مقداری که برگشت داده میشه هست. کاربر با کلیک روی این دکمه باعث میشه فرمتی متفاوت از پیام معمولی برای بات ارسال بشه که حاوی شی JSON به نام callback_query هست و اطلاعاتی از قیبل ارسال کننده، پیام و مهم تر از همه data هست که همون مقدار callback_data دکمه هست.
نمونه یک دریافت کلیک InlineKeyboardMarkup به صورت json:
'{"ok":true,"result":[{ "update_id":485550139,'#$A'
"callback_query":{
"id":"339563465400824028",
"from":{
"id":11111111,
"first_name":"mehdi",
"last_name":"Jafari",
"username":"ShahvarIMS"
},
"message":{
"message_id":165,
"from":{
"id":200483732,
"first_name":"Shahvar2Bot",
"username":"Shahvar2bot"
},
"chat":{
"id":11111111,
"first_name":"mehdi",
"last_name":"Jafari",
"username":"ShahvarIMS",
"type":"private"
},
"date":1474169323,
"text":"\u0634\u0645\u0627 \u062f\u0631\u062e\u0648\u0627\u0633\u062a \u0641\u0639\u0627\u0644\u0633\u0627\u0632\u064a
\u0648 \u0627\u0637\u0644\u0627\u0639 \u0631\u0633\u0627\u0646\u064a \u0628\u0631\u0627\u064a
\u0647\u0646\u0631\u0622\u0645\u0648\u0632 \u0634\u064a\u0631\u0632\u0627\u062f \u0622\u0698\u064a\u0631
\u0631\u0627 \u062f\u0627\u0631\u064a\u062f \u0622\u064a\u0627 \u062a\u0627\u064a\u064a\u062f
\u0645\u064a \u0646\u0645\u0627\u064a\u064a\u062f\u061f\n@Shahv ar2bot",
"entities":[{
"type":"mention",
"offset":90,
"length":12
}]
},
"data":"1"
}
}]}'
خوب حالا میرم سراغ قسمت اصلی ماجرا یعنی متد Execute ترد:
procedure TBotThread.Execute;var
Offset:integer;
msg : WideString;
Src : WideString;
LHandler: TIdSSLIOHandlerSocket;
last_update_id : int64;
json , item , ResultNode , OKNode , msgNode ,fromNode :TlkJSONbase;
i,j: integer;
list: TlkJSONObject;
okVal : boolean;
nodename : String;
MessageText : WideString;
ChatID, username , NCode , CallbackData: String;
IsOKCommand : Boolean;
SaveRet : integer;
begin
msg := UTF8Encode('/getUpdates');
LHandler := TIdSSLIOHandlerSocket.Create(nil);
FidHttpGet.ReadTimeout := 60000;
FidHttpGet.IOHandler:=LHandler;
LHandler.SSLOptions.Method := sslvTLSv1;
LHandler.SSLOptions.Mode := sslmUnassigned;
FidHttpGet.HandleRedirects := true;
last_update_id := 0;
while not Terminated do
Begin
try
Src := FidHttpGet.Get(BotUrl + API + msg + '?offset=' + IntToStr(last_update_id + 1));
json:= TlkJSON.ParseText(Src);
OKNode := TlkJSONboolean(json).Field['ok'];
okVal := OKNode.Value;
ResultNode := TlkJSONlist(json).Field['result'];
if ResultNode.Count > 0 then
Begin
Fmemo.Clear;
item := TlkJSONlist(ResultNode).child[ResultNode.Count - 1];
last_update_id := StrToInt64(VarToStr(item.Field['update_id'].Value));
for i := 0 to pred(ResultNode.Count) do
begin
CallbackData := '';
IsOKCommand := False;
MessageText := 'No Message';
item := TlkJSONlist(ResultNode).child[i];
if TlkJSONObject(item).Field['message'] <> nil then
Begin
msgNode := TlkJSONObject(item).Field['message'];
if msgNode.Field['update_id'] <> nil then
Fmemo.Lines.Add('Update_id: ' + VarToStr(item.Field['update_id'].Value));
if msgNode.Field['from'].Field['id'] <> nil then
Begin
ChatID := VarToStr(msgNode.Field['from'].Field['id'].Value);
Fmemo.Lines.Add('Chat_id: ' + ChatID);
End;
if msgNode.Field['from'].Field['first_name'] <> nil then
Fmemo.Lines.Add('first_name: ' + VarToStr(msgNode.Field['from'].Field['first_name'].Value));
if msgNode.Field['from'].Field['last_name'] <> nil then
Fmemo.Lines.Add('last_name: ' + VarToStr(msgNode.Field['from'].Field['last_name'].Value));
if msgNode.Field['from'].Field['username'] <> nil then
Begin
username := VarToStr(msgNode.Field['from'].Field['username'].Value);
Fmemo.Lines.Add('username: ' + username);
End;
if msgNode.Field['text'] <> nil then
Begin
MessageText := VarToStr(msgNode.Field['text'].Value);
Fmemo.Lines.Add('Message Text: ' + MessageText);
End;
End
Else
Begin
if TlkJSONObject(item).Field['callback_query'] <> nil then
Begin
msgNode := TlkJSONObject(item).Field['callback_query'];
if msgNode.Field['from'].Field['id'] <> nil then
Begin
ChatID := VarToStr(msgNode.Field['from'].Field['id'].Value);
Fmemo.Lines.Add('Chat_id: ' + ChatID);
End;
if msgNode.Field['from'].Field['first_name'] <> nil then
Fmemo.Lines.Add('first_name: ' + VarToStr(msgNode.Field['from'].Field['first_name'].Value));
if msgNode.Field['from'].Field['last_name'] <> nil then
Fmemo.Lines.Add('last_name: ' + VarToStr(msgNode.Field['from'].Field['last_name'].Value));
if msgNode.Field['from'].Field['username'] <> nil then
Begin
username := VarToStr(msgNode.Field['from'].Field['username'].Value);
Fmemo.Lines.Add('username: ' + username);
End;
if msgNode.Field['data'] <> nil then
Begin
CallbackData := VarToStr(msgNode.Field['data'].Value);
Fmemo.Lines.Add('Callback_data: ' + CallbackData);
End;
End;
End;
if (UpperCase(MessageText) = '/START') OR (UpperCase(MessageText) = '/ACTIVATE') then
Begin
SendMessageAndKey(ChatID, 'درود بر شما ' + LineBreak + 'به ربات هوشمند من خوش آمديد'+
LineBreak + 'تبريک ربات کار ميکنه' ,MainMenu,'',false);
WriteToLog('ChatID:' + ChatID + ' username:' + username );
Continue;
//Sleep(500);
//SendMessageAndKey(ChatID, 'درود بر شما ' + #13#10 + 'به ربات هوشمند شاهوار خوش آمديد');
End
else
Begin
if (MessageText = 'No Message') And (CallbackData = '') then
Begin
SendMessage(ChatID, IncorrectChoose,Markdown,True);
IsOKCommand := true;
Continue;
End
Else
Begin
if CallbackData = 'yes' then
Begin
SendMessageAndKey(ChatID, congratulation,MainMenu,Markdown,True)
End;
if CallbackData = 'no' then
Begin
SendMessageAndKey(ChatID, 'شما خير را انتخاب کرديد!!!',MainMenu,Markdown,True)
End;
if MessageText = Widestring(Menu_Item1) then
SendMessageAndKey(ChatID, PleaseChooseItem,SubMenu,Markdown,True);
if MessageText = Widestring(Menu_Item2) then
SendMessageAndKey(ChatID, 'آيا مطمئن هستيد؟' + LineBreak + 'نمونه پرسيدن يک سوال از کاربر',ConfirmMenuInLine,Markdown,True);
if MessageText = Widestring(Menu_Item3) then
SendMessageAndKey(ChatID, 'نمونه نظر سنجي' + LineBreak + 'کدام زبان برنامه نويسي رو دوست داريد؟',LikeMenuInline,Markdown,True);
if MessageText = Widestring(Menu_MainMenu) then
SendMessageAndKey(ChatID, PleaseChooseItem,MainMenu,Markdown,True);
if MessageText = Widestring(Menu_ContactUs) then
SendMessage(ChatID, GetCompanyInfo ,Markdown,False);
// Get SbMenu Items And Answer it From Data Base
if MessageText = Widestring(SubMenu_LastItem) then
SendMessageAndKey(ChatID, GetDataFromDB(ChatID,1),SubMenu,HTML,False);
if MessageText = Widestring(SubMenu_3Item) then
SendMessageAndKey(ChatID, GetDataFromDB(ChatID,2),SubMenu,HTML,False);
if MessageText = Widestring(SubMenu_AllItems) then
SendMessageAndKey(ChatID, GetDataFromDB(ChatID,3),SubMenu,HTML,False);
if CallbackData = 'Delphi' then
Begin
SendMessageAndKey(ChatID, 'شما ' + CallbackData + ' را انتخاب کرديد',MainMenu,Markdown,True)
End;
if CallbackData = 'Asp.net' then
Begin
SendMessageAndKey(ChatID, 'شما ' + CallbackData + ' را انتخاب کرديد',MainMenu,Markdown,True)
End;
if CallbackData = 'Java' then
Begin
SendMessageAndKey(ChatID, 'شما ' + CallbackData + ' را انتخاب کرديد',MainMenu,Markdown,True)
End;
End;
//SendMessage(ChatID, 'شما گزينه ' + '"' + MessageText + '" ' + 'را انتخاب کرديد');
Sleep(500);
End;
end;
End
Else
Continue;
except
on E: EIdHTTPProtocolException do
begin
if not ((E.ReplyErrorCode = 301) or (E.ReplyErrorCode = 302)) then raise;
Src := E.ErrorMessage;
ShowMessage(Src);
end
end;
ENd;
end;
بسیار خوب این متد رو توضیحش رو شروع میکنم:
در این متد با استفاده از متد GetUpdates تلگرام ما پیام های ارسالی به بات رو واکشی میکنیم. این متد یه پارامتر به نام Offset داره که می تونه حاوی Update_id یک پیام باشه. چنانچه GetUpdate بدون پارامتر فراخونی بشه تلگرام 100 پیام آخر بات رو برای ما میاره . ولی ما نمی خوای هی پیامهایی که پاسخ داده شده رو دوباره بیاره پس offset رو با آخرین update_id+1 پر میکنیم اینکار باعث میشه پیامهای پیشین بصورت اتوماتیک تلگرام اونها رو confirm کنه و دیگه تو getupdate نیاد.
مبحث Long polling رو که خدمت شما عرض کردم این حلقه اون رو پوشش میده یعنی تا زمانی که بات پیام داره باید پاسخگو باشیم .
while not Terminated do
Begin
واکشی پیام ها: این خط از کد پیام ها رو واکشی میکنه: ما ابتدا last_update_id برابر صفر میذاریم و بعد با آخرین update_id پرش میکنیم.
Src := FidHttpGet.Get(BotUrl + API + msg + '?offset=' + IntToStr(last_update_id + 1));
پیامهای واکشی شده در Src بصورت json پر مشن و با تیکه کد زیر اونها رو deserialize میکنیم که ResultNode حاوی پیام ها خواهد بود.
json:= TlkJSON.ParseText(Src); OKNode := TlkJSONboolean(json).Field['ok'];
okVal := OKNode.Value;
ResultNode := TlkJSONlist(json).Field['result'];
حالا در حلقه For پایین تمام پیام ها رو بررسی میکنیم و پاسخ مناسب هر پیام رو میدیم.
if ResultNode.Count > 0 then Begin
Fmemo.Clear;
item := TlkJSONlist(ResultNode).child[ResultNode.Count - 1];
last_update_id := StrToInt64(VarToStr(item.Field['update_id'].Value));
for i := 0 to pred(ResultNode.Count) do
begin
این تیکه کد حاوی یک پیام از مجموعه پیام های بات هست
item := TlkJSONlist(ResultNode).child[i];
حالا ممکنه دو حالت پیش بیاد یک اینکه پیام حاوی شی Message باشه یا حاوی callback_query . اگر تو کد دقت کنید بنده دو حالت رو با if چک کردم .
if TlkJSONObject(item).Field['message'] <> nil then
و
if TlkJSONObject(item).Field['callback_query'] <> nil then
تو حالت اول یه پیام ساده یا کلیک روی ReplaykeyboardMarkup هست و تو حالت دوم InlineKeyboard کلیک شده.
خوب حالا ما متن پیام یا Callback_data رو تو IFهای بالا دریافت می کنیم و همچنین مشخصات پیام و نفرارسالی و مهمتراز همه Chat_id فرد رو.
حالا پاسخگویی رو شروع میکنیم
if (UpperCase(MessageText) = '/START') OR (UpperCase(MessageText) = '/ACTIVATE') then Begin
SendMessageAndKey(ChatID, 'درود بر شما ' + LineBreak + 'به ربات هوشمند من خوش آمديد'+
LineBreak + 'تبريک ربات کار ميکنه' ,MainMenu,'',false);
WriteToLog('ChatID:' + ChatID + ' username:' + username );
Continue;
//Sleep(500);
//SendMessageAndKey(ChatID, 'درود بر شما ' + #13#10 + 'به ربات هوشمند شاهوار خوش آمديد');
End
else
Begin
if (MessageText = 'No Message') And (CallbackData = '') then
Begin
SendMessage(ChatID, IncorrectChoose,Markdown,True);
IsOKCommand := true;
Continue;
End
Else
Begin
if CallbackData = 'yes' then
Begin
SendMessageAndKey(ChatID, congratulation,MainMenu,Markdown,True)
End;
if CallbackData = 'no' then
Begin
SendMessageAndKey(ChatID, 'شما خير را انتخاب کرديد!!!',MainMenu,Markdown,True)
End;
if MessageText = Widestring(Menu_Item1) then
SendMessageAndKey(ChatID, PleaseChooseItem,SubMenu,Markdown,True);
if MessageText = Widestring(Menu_Item2) then
SendMessageAndKey(ChatID, 'آيا مطمئن هستيد؟' + LineBreak + 'نمونه پرسيدن يک سوال از کاربر',ConfirmMenuInLine,Markdown,True);
if MessageText = Widestring(Menu_Item3) then
SendMessageAndKey(ChatID, 'نمونه نظر سنجي' + LineBreak + 'کدام زبان برنامه نويسي رو دوست داريد؟',LikeMenuInline,Markdown,True);
if MessageText = Widestring(Menu_MainMenu) then
SendMessageAndKey(ChatID, PleaseChooseItem,MainMenu,Markdown,True);
if MessageText = Widestring(Menu_ContactUs) then
SendMessage(ChatID, GetCompanyInfo ,Markdown,False);
// Get SbMenu Items And Answer it From Data Base
if MessageText = Widestring(SubMenu_LastItem) then
SendMessageAndKey(ChatID, GetDataFromDB(ChatID,1),SubMenu,HTML,False);
if MessageText = Widestring(SubMenu_3Item) then
SendMessageAndKey(ChatID, GetDataFromDB(ChatID,2),SubMenu,HTML,False);
if MessageText = Widestring(SubMenu_AllItems) then
SendMessageAndKey(ChatID, GetDataFromDB(ChatID,3),SubMenu,HTML,False);
if CallbackData = 'Delphi' then
Begin
SendMessageAndKey(ChatID, 'شما ' + CallbackData + ' را انتخاب کرديد',MainMenu,Markdown,True)
End;
if CallbackData = 'Asp.net' then
Begin
SendMessageAndKey(ChatID, 'شما ' + CallbackData + ' را انتخاب کرديد',MainMenu,Markdown,True)
End;
if CallbackData = 'Java' then
Begin
SendMessageAndKey(ChatID, 'شما ' + CallbackData + ' را انتخاب کرديد',MainMenu,Markdown,True)
End;
End;
خوب کسی که اولین بار بات شما رو ادد میکنه ابتدا با کامند Start/ شروع میکنه و باقی ماجرا دیگه تیکه کد بالا مشخصه که متن پیام با گزینه هایی مقایسه می شه و پاسخ داده میشه و با هر پاسخ امکان داره Keyboard نمایش داده شده تو بات تغییر کنه.
این کد دکمه استارت سرویس:
procedure TfmMain.btnStartServiceClick(Sender: TObject);begin
TelegService := TBotThread.Create(True,IdHTTP1,IdHTTP3,memoRespons e);
TelegService.FConnection := schlConn;
TelegService.FAdoDataset := ADODataSet1;
TelegService.FBotUser := lblUserName.Caption;
TelegService.FShowBotLink := True;
TelegService.FreeOnTerminate := true;
TelegService.Resume;
btnStartService.Enabled := false;
btnStopService.Enabled := true;
end;
توجه: حتما ابتدا یه بات بسازید و توکن اون رو تو setting.ini بذارید و سپس اجرا کنید
خوب در پایان هم کد براتون میذارم ولی حدود چهار ماه چشام در اومد تا به اینجا رسید
یا علی مدد
دوستدار همتون هستم
مهدی جعفری