Наконец-то разобрался со своим провайдером и доказал тех.поддержке, что проблемы с загрузкой Google из-за их глюков в DNS. Теперь все работает как полагается и можно спокойно приступать к работе с Google API.
Начать решил с Google Calendar API по двум причинам.
Во-первых, потому что сам частенько использую этот сервис как напоминалку о ближайших событиях и встречах, когда необходимо получить уведомление в виде SMS (к сожалению, в Хронометре такой функции не предусмотрено).
Во-вторых, думаю в свободное время начать работу над новой версией Хронометра и скрестить его функционал с этим он-лайн сервисом Google. Думаю, получиться должно неплохо. На данный момент в руках уже есть класс, используемый для авторизации в аккаунте, поэтому сразу приступил к работе с целевым API. Итак, что можно сделать, используя Celendar API:
- Получить список календарей (весь или только тех, владельцами которых Вы являетесь)
- Добавить новый, редактировать или удалить календарь (основной календарь удалить нельзя, судя по описанию API)
- Добавить, удалить или редактировать подписчиков
- Работать с событиями в календарях
- и ещё много чего
В общем работы навалом и это только с одним из списка доступных API Google для использования в настольных приложениях.
Первое о чем стоит в данном случае позаботиться (если планировать разработку модулей для нескольких API) — это предусмотреть единый класс для работы с запросами. Можно было бы пойти более простым путем и взять, например, Indy или Synapse в качестве дополнительных библиотек и спокойно толкать туда-сюда запросы, анализировать результат и т.д. Но, почему-то мне кажется, что это не вариант. Лучше, наверное, создать свой небольшой класс на основе WinInet для отправки запросов, необходимых для работы с API и не волочь каждый раз за собой библиотеку на 1,5 Mb.
Именно с этого я и начал — с создания своего класса для работы с запросами. При этом необходимо было учесть, что в API Google используются как простые http- так и https-протоколы, а запросы не ограничиваются простыми GET и POST. В том же Celendar API используются дополнительно PUT и DELETE.
Какие функции из WinInet нам нужны? Для того, чтобы подключиться к серверу и, например, скачать информацию нам необходимо последовательно вызвать ряд функций:
- InternetOpen — получить дескриптор и использовать его в следующей функции
- InternetConnect — создать подключение к серверу и получить дескриптор этого подключения для использовании в следующей функции
- HttpOpenRequest — создать новый запрос, опять же получив дескриптор, на этот раз HTTP-запроса, который использовать в функции
- HttpSendRequest — отправить запрос, используя дескриптор HTTP-запроса и читать данные функцией InternetReadFile.
Как видите, в самом простом случае надо использовать пять функций. При этом не учитывается то, что нередко (а в случае работы с Google API — постоянно) приходится включать в запрос специфические заголовки, наподобие версии API и т.д., использовать различные параметры, читать ответные заголовки и т.д. В этом случае каждый раз писать по-новой функцию на отправку/получение данных, мягко говоря может вызвать геморрой и несварение желудка.
На данный момент класс THTTPSender имеет следующее описание:
type THTTPSender = class private FHeaders: TStringList; FAgent: string; FMethod: string; FProtocol: string; FPort: integer; //порт FDomain: string; //домен без http FParamStr: string; //всё, что стоит в адресе после / кроме параметров FParameters: string; procedure ParseURL(var cURL:string); public constructor Create; function SendRequest(fURL:string):WideString; property Headers: TStringList read FHeaders write FHeaders; property Agent: string read FAgent write FAgent; property Method: string read FMethod write FMethod; property Protocol:string read FProtocol write FProtocol; end;
Пока работа с запросами проходит следующим образом. Например, нам необходимо получить список календарей из своего аккаунта. Для этого в запросе необходимо указать два специфических заголовка:
GData-Version: 2
Заголовок, указывающий какую версию API мы используем.
Authorization: GoogleLogin auth=yourAuthToken
Заголовок для доступа к календарям, где yourAuthToken — это параметр Auth, полученный при авторизации с использованием GoogleLogin.
Отправляем запрос и получаем ответ в виде строки:
with THTTPSender.Create do begin Method:='GET'; Headers.Add('GData-Version: 2'); Headers.Add('Authorization: GoogleLogin auth='+FAccount.Auth); ResponseText:=SendRequest('http://www.google.com/calendar/feeds/default/allcalendars/full'); end;
Функция SendRequest работает следующим образом:
Анализируем URL:
procedure THTTPSender.ParseURL(var cURL: string); var lencurl: cardinal; aURL: string; aURLc: TURLComponents; begin lencurl:=INTERNET_MAX_URL_LENGTH; if pos('http',cURL)<=0 then cURL:='http://'+cURL; SetLength(aURL, lencurl); //каноникализируем URL InternetCanonicalizeUrl(PChar(cURL),PChar(aURL),lencurl,ICU_BROWSER_MODE); //разбиваем УРЛ на составные части with aURLc do begin lpszscheme := nil; dwschemelength := internet_max_scheme_length; lpszhostname := nil; dwhostnamelength := internet_max_host_name_length; lpszusername := nil; dwusernamelength := internet_max_user_name_length; lpszpassword := nil; dwpasswordlength := internet_max_password_length; lpszurlpath := nil; dwurlpathlength := internet_max_path_length; lpszextrainfo := nil; dwextrainfolength := internet_max_path_length; dwstructsize := sizeof(aurl); end; if InternetCrackUrl(PChar(aURL), Length(aURL), 0, aURLC) then begin if aURLc.lpszUrlPath='/' then FDomain:=ReplaceStr(aURLC.lpszHostName,'/','') else begin FDomain:=copy(aURLC.lpszHostName,1,length(aURLC.lpszHostName)-length(aURLC.lpszUrlPath)); end; FPort:=aURLc.nPort; FParamStr:=aURLc.lpszUrlPath; FParameters:=aURLc.lpszExtraInfo; case aURLc.nScheme of INTERNET_SCHEME_DEFAULT:FProtocol:='HTTP'; INTERNET_SCHEME_FTP:FProtocol:='FTP'; INTERNET_SCHEME_HTTP:FProtocol:='HTTP'; INTERNET_SCHEME_HTTPS:FProtocol:='HTTPS'; INTERNET_SCHEME_FILE:FProtocol:='FILE'; INTERNET_SCHEME_MAILTO:FProtocol:='MAILTO'; end; end else MessageBox(0, PChar('Ошибка WinInet #'+IntToStr(GetLastError)),'Ошибка', MB_OK); end;
Все данные, необходимые для осуществления запроса сохраняются в соответствующих полях класса. Далее последовательно выполняем функции:
if FProtocol='HTTP' then hConnect:=InternetConnect(hInet, PChar(FDomain), INTERNET_DEFAULT_HTTP_PORT,'anonymous', nil, INTERNET_SERVICE_HTTP, 0, 0) else if FProtocol='HTTPS' then hConnect:=InternetConnect(hInet, PChar(FDomain), INTERNET_DEFAULT_HTTPS_PORT,'anonymous', nil, INTERNET_SERVICE_HTTP, 0, 0) else //уничтожаем дескриптор и выходим if hConnect<>nil then hRequest:=HttpOpenRequest(hConnect,PChar(FMethod),PChar(FParamStr),nil,nil,nil,INTERNET_FLAG_PRAGMA_NOCACHE or INTERNET_FLAG_RELOAD,0) else //уничтожаем дескрипторы и выходим
Получили дескриптор hRequest, необходимы для отправки запроса. При этом если протокол не HTTP и не HTTPS, то завершаем работу предварительно убив дескрипторы.
Далее, нам необходимо добавить в запрос свои заголовки. Для того, чтобы добавить в запрос свои заголовки можно воспользоваться функцией HttpAddRequestHeaders или указать свои заголовки непосредственно в функции HttpSendRequest.
В первом случае, в функцию HttpAddRequestHeaders необходимо передать следующие параметры:
hConnect — дескриптор, полученный при вызове HttpOpenRequest
lpszHeaders -указатель на строку, содержащую заголовки, при это каждый заголовок отделяется символами #10#13
dwHeadersLength — общая длина заголовков (в символах)
dwModifiers — флаги-модификаторы. Для нас достаточно использование флага HTTP_ADDREQ_FLAG_ADD.
Ну, а дальше используем уже известный, наверное каждому, кто имел дело с WinInet алгоритм чтения данных:
if HttpSendRequest(hRequest,nil,0,PChar(FParameters),length(PChar(FParameters)))then begin SetLength(Buff, 1); B:=false; I:=1; while true do begin DataAvailable(hRequest, L); if L = 0 then break; SetLength(Buff, I + L); B:=InternetReadFile(hRequest, @Buff[I], L, ReadedSize); if NOT B then break; inc(I, ReadedSize); end; Buff[I] := #0; if B then begin result := WideString(Buff); end else result := ''; end;
Пока использовал класс в работе с запросами GET к календарям Google. Вроде бы видимых косяков не нашел. Как доработаю полностью — выложу в доступ.
Теперь, что касается непосредственно API. На данный момент класс для работы с календарями умеет совсем немного — получать доступ к аккаунту и получать информацию по всем календарям. Выглядит он, соответственно, проще простого:
type TCelendar = record id: string; etag:string; Author:string; Title: string; Description: string; timezone:string; hidden:boolean; Color:string; selected:boolean; accesslevel:string; where:string; end; type TCelendarList = array of TCelendar; type TGoogleCalendar = class private FAccount: TGoogleLogin; FCelendars: TCelendarList; //календари пользователя function GetCelendars: TCelendarList; public constructor Create(const email, password: string); function Login: boolean; property Account: TGoogleLogin read FAccount write FAccount; property Celendars:TCelendarList read GetCelendars; end;
Те, кто читал заметку про ClientLogin, поймут без проблем, что такое TGoogleLogin. При этом вызов функции Login возвращает нам только true или false в зависимости от результата авторизации, а вся информация, в т.ч. и по каптче (если её ввод требуется) спокойно хранятся в поле FAccount.
При чтении списка календарей происходит анализ небольшой XML-документа и заполнение массива TCelendarList. Сейчас буду дорабатывать класс для работы с запросами и сразу его испытывать при работе с API Google.
Кстати, информация для тех, кто скачивал модуль GoogleLogin. В модуле есть небольшой косяк — строку 155 необходимо изменить на
if pos('AUTH',UpperCase(List[i]))>0 then
иначе параметр Auth не читается. А в целом вроде бы больше проблем не наблюдалось.
На сегодня все. Ждите продолжения :).
Вот уж не проблема прикрутить к Хронометру отправку SMS. Хотя бы вызывать SimpleSMS через ShellExecute. Проблема — где достать второй сотовый, чтобы присоединить его к компьютеру, и который будет отправлять SMS.
C ГуглКалендарь удобно работать через файлы календарей (ics), это позволяет синхронизировать совместную работу множества календарных программ.
ссылка: http://www.simplesms.ru/
Класс, спасибо! =)
Да пока, вроде, не за что. Я только начал, а к чему это приведет…пока трудно предположить, но API довольно объемное, только константы часовых поясов чего стоят — 309 штук :)
А с Google Translate API вы собираетесь разобраться? :)
Есть такая задумка. Последовательно пройтись по нескольким API, в том числе и по переводчику, правда у переводчика только AJAX API поэтому придётся делать «финт ушами»….
Автор, опять велосипед изобретаем. на питоне это в разы меньше и проще.
А Google Translate API в Делфи уже реализован http://code.google.com/p/imadering/source/browse/branches/to_delphi_2010/GtransUnit.pas
Спасибо Эдуарду Толмачёву.
За ссылку спасибо. А зачем мне питон? :) API Google и на С++ реализован и на питоне и даже на JavaScript есть. Получается тот, кто разрабатывал библиотеки, например под С++ велосипед изобретал?
dobrovestnik ru,
Cпасибо за то, что сообщили насчёт готового модуля. Было бы просто не удобно перед Vlad’ом, если бы это обнаружилось после того, как он приступил бы к исследованиям.
Насчёт велосипеда — данный труд однозначно не зря. Как минимум по той причине, что перенос с языка на язык иногда бывает довольно трудным процессом. Я лично столкнулся с проблемами в адресной арифметике, когда писал код на CryptoAPI с упрощёным примером на C++. И это было не сладко :)
Возможно, нубский вопрос, но я не могу понять:
для чего после вызова InternetCrackUrl, проверяетсяa URLc.lpszUrlPath?