Pull to refresh

Qt: работа с Vkontakte API и Phonon

Reading time 12 min
Views 20K
Qt
Статья описывает взаимодействие Qt c такими программными интерфейсами как Vkontakte API и Phonon, в реальных примерах и подробным описанием.
В конце статьи ссылка на репозиторий с исходным кодом который Вы можите свободно скачать и запустить.

Сразу хотелось бы отметить что я не гуру, а программный код можно, было бы, реализовать лучше. Я с удовольствием приму Ваши поправки, комментарии и критику.
Что бы не дублировать информацию — буду указывать ссылки на Wiki.
Так же, наверное, нужно отметить что программа тестировалась и разрабатывалась на платформе Gnu/Linux/Gentoo (KDE) и я не проверял ее под другими платформами и дистрибутивами но должно работать.

Общий алгоритм:

1. Авторизация в соц сети через Vkontakte API (далее ВК).
2. Получение списка избранных аудиозаписей.
3. Создание списка треков из полученного списка для Phonon.
4. Реализация возможности скачивания выбранной песни.

Авторизация

Согласно документации, для авторизации необходимо добавить компонент который позволил бы нам просматривать веб страницы. Для этого у Qt есть WebKit. В компонент необходимо загрузить страницу vkontakte.ru/login.php на которой пользователь сможет пройти процедуру авторизации. При успешной авторизации — ВК сделает перенаправление на страницу /login_success.html и в URL допишет необходимые для нас данные, такие как идентификатор сессии, соль и идентификатор пользователя, который нам нужно будет достать (спарсить) из ссылки.
Подключаем к проекту необходимые модули:
QT += webkit \
network \
xml \
phonon

Так как ВК возвращает данные в XML (или JSON) нам необходимо подключить модуль XML что бы упростить себе жизнь и не изобретать велосипедов.
Модуль network позволит загрузить необходимую песню с ВК и вообще работать с сетью.

Модуль Phonon позволит нам проиграть нашу видеозапись локально или из сети. Так же он поддерживает проигрывание видео файлов. Работать с этим программным интерфейсом одно удовольствием но о нем чуть позже.
Для работы с ВК у меня отвечает класс VKAuth в конструктор которого я передаю свой идентификатор приложения. Его можно получить зарегистрировав Ваше приложение в ВК.
  1. #include <QApplication>
  2. #include <QObject>
  3. #include "mainwindow.h"
  4. #include "VKAuth.h"
  5.  
  6. int main(int argc, char** argv)
  7. {
  8.     QApplication app(argc, argv);
  9.  
  10.     QApplication::setApplicationName("VKaudio");
  11.     QApplication::setApplicationVersion("0.01a");
  12.  
  13.     MainWindow window;
  14.     VKAuth vkAuth("2169954"); //my application id
  15.  
  16.     QObject::connect(&vkAuth, SIGNAL( unsuccess() ),
  17.                      &app   , SLOT( quit() )
  18.                      );
  19.     QObject::connect(&vkAuth, SIGNAL( success(QStringList) ),
  20.                      &window, SLOT( slotSuccess(QStringList) )
  21.                      );
  22.     vkAuth.show();
  23.     return app.exec();
  24. }

Класс VKAuth высылает сигнал success при успешной авторизации и как аргумент передает список ссылок на песни и сигнал unsuccess если пользователь отказывается от авторизации.
Сигнал об успешной авторизации соединяться со слотом главного окна приложение (самого плеера) который добавляет переданный список в список аудиозаписей, подробней этот процес будет описан позже. Сигнал отказа пользователя от авторизации соединяться со слотом завершения работы программы (выход).
  1. VKAuth::VKAuth(QString app,QWidget* parent) : QWebView(parent)
  2. {
  3.  
  4.     QObject::connect(this, SIGNAL( urlChanged(QUrl)      ),
  5.                              SLOT( slotLinkChanged(QUrl) )
  6.                      );
  7.     m_app = app;
  8.     loadLoginPage();
  9. }

Как я уже писал выше, в конструктор передается идентификатор приложения и указатель на объект предка. Класс унаследован от QWebView который реализует работу с WebKit и позволяет нам отображать пользователю необходимую веб-страницу. У QWebView есть сигнал который высылается каждый раз при смене адреса страницы. Так как в случай успешной авторизации ВК делает перенаправление (смену адреса) именно этот сигнал нам очень хорошо подходит что бы отловить этот момент. Как аргумент он передает новую ссылку, которую мы и будем парсить.
Метод loadLoginPage() реализует загрузку необходимой страницы для авторизации
  1. void VKAuth::loadLoginPage(){
  2.     QUrl url("vkontakte.ru/login.php");
  3.  
  4.     url.addQueryItem("app"     ,  m_app   );
  5.     url.addQueryItem("layout"  , "popup"  );
  6.     url.addQueryItem("type"    , "browser");
  7.     url.addQueryItem("settings""8");
  8.  
  9.     load(url);
  10. }

Класс QUrl делает работу со ссылками очень и очень удобной.
QUrl разбивает ссылку на части.
image
image
image
Метод addQueryItem добавляет GET параметры в ссылку, где первый аргумент — имя параметра, а второй — его значение.
Таблица со параметром и возможных значениями:
image
Значение settings нужно нам для того что бы пользователь разрешил нам доступ к его аудио записям.
Сгенерированный URL мы передаем в метод класса QwebView::load() он и загрузит страницу для авторизации.
image
Допустим пользователь прошел авторизацию- ВК делает перенаправление, высылается сигнал с новым адресом и передается управление слоту slotLinkChanged который отвечает за обработку сигнала о смене адреса.
  1. void VKAuth::slotLinkChanged(QUrl url)
  2. {
  3.     if("/api/login_success.html" == url.path()){
  4.         QRegExp regexp("\"mid\":([^,]+),\"secret\":\"([^,]+)\",\"sid\":\"([^,]+)\"");
  5.         QString str= url.fragment();
  6.  
  7.         if( -1 != regexp.indexIn(str) ){
  8.  
  9.            m_mid    = regexp.cap(1);
  10.            m_secret = regexp.cap(2);
  11.            m_sid    = regexp.cap(3);
  12.  
  13.            QUrl request("api.vkontakte.ru/api.php?");
  14.            QString sig(getSig(m_mid, m_secret,"api_id=" + m_app +"method=audio.get"+"uid="    + m_mid +"v=3.0"));
  15.  
  16.            request.addQueryItem("api_id",m_app);
  17.            request.addQueryItem("method""audio.get" );
  18.            request.addQueryItem("uid",m_mid);
  19.            request.addQueryItem("v","3.0");
  20.            request.addQueryItem("sig",sig);
  21.            request.addQueryItem("sid",m_sid);
  22.  
  23.  
  24.            QNetworkAccessManager *manager = new QNetworkAccessManager(this);
  25.            m_http = manager->get(QNetworkRequest(request));
  26.            QObject::connect(m_http,SIGNAL(finished()),this, SLOT(slotDone()));
  27.  
  28.         }
  29.     }else if("/api/login_failure.html" == url.path()){
  30.         emit unsuccess();
  31.     }
  32. }
  33. QByteArray VKAuth::getSig(QString mid,QString secret, QString params = ""){
  34.     QString str;
  35.  
  36.     str.append(mid);
  37.     if(!params.isEmpty())
  38.         str.append(params);
  39.     str.append(secret);
  40.  
  41.     return QCryptographicHash::hash( str.toUtf8(), QCryptographicHash::Md5 ).toHex();
  42. }

В слоте мы проверяем куда же нас перенаправили, если перенаправление было на login_success.html тогда авторизация прошла успешно (согласно документации).
Создаем регулярное выражение которое поможет нам получить необходимые данные для дальнейшей работы с ВК, а именно:
mid, secret, sid.
image
Для получения списка аудиозаписей нам необходимо вызвать метод audio.get.
Класс Qhttp упростить работу с высокоуровневым протоколом HTTP.
Передаем необходимые параметры как GET параметры и создаем хеш (метод — getSig).
Сигнал Qhttp::done который высылается при завершении запроса (получении ответа) соединяться со слотом slotDone.
  1. void VKAuth::slotDone(bool error){
  2.     if(error)
  3.        emit unsuccess();
  4.     else{
  5.         QStringList list; //list with mp3
  6.  
  7.        QDomDocument dom;
  8.        dom.setContent(m_http->readAll());
  9.        QDomElement  root         = dom.firstChildElement(); // <response> root element
  10.        QDomNode  audioElement = root.firstChildElement(); // <audio>
  11.  
  12.        while(!audioElement.isNull()){
  13.             QString url =  audioElement
  14.                                .toElement()
  15.                                .elementsByTagName("url")
  16.                                .item(0)
  17.                                .toElement()  //<url>
  18.                                .text();
  19.             list.append(url);
  20.             audioElement = audioElement.nextSibling(); //next element
  21.        }
  22.         emit success(list); // load player window with playlist
  23.         hide(); //hide this window
  24.     }
  25.  
  26.     m_http->close();
  27. }

Слот VKAuth::slotDone считывает полученный XML и создает список из ссылок на mp3 песни после чего высылает сигнал success с этим же списком, закрывает соединенее и прячет окно. Авторизация пройдена список песен получен.

Phonon

C Phonon работать очень просто, необходимо создать мета объект (класс MediaObject), создать аудио (и/или видео) вывод и объединить их через Phonon::createPath.
В методе MainWindow::appendToList() — мы добавляем получениый список на mp3 файлы к списку.
  1. void MainWindow::appendToList(){
  2.     if (VKAudioList.isEmpty())
  3.         return;
  4.  
  5.     int index = sources.size();
  6.     foreach (QString string, VKAudioList) {
  7.             Phonon::MediaSource source(string);
  8.  
  9.         sources.append(source);
  10.     }
  11.     if (!sources.isEmpty())
  12.         metaInformationResolver->setCurrentSource(sources.at(index));
  13. }


Скачивание выбранной песни

Для скачивания песни ее нужно выделить и нажать Download в верхней части окна программы, появиться диалог в котором необходимо выбрать место для загрузки файла, естественно у вас должны быть права на создание (запись) файла.
Сигнал клика по кнопке соединен со слотом MainWindow::slotDownload().
image
  1. void MainWindow::slotDownload(){
  2.     QString path = QFileDialog::getExistingDirectory(this,"Save to...");
  3.  
  4.     if(!path.isEmpty()){
  5.          m_file = new QFile;
  6.          int item     = musicTable->currentRow();
  7.          Phonon::MediaSource src = sources.at(item);
  8.          m_pSrcUrl          = new QUrl(src.url());
  9.          m_file->setFileName(path + "/" + musicTable->item(item,musicTable->currentColumn())->text() + ".mp3");
  10.  
  11.         if(m_file->open(QIODevice::ReadWrite)){
  12.             m_pTcpSocket = new QTcpSocket(this);
  13.  
  14.             m_pTcpSocket->connectToHost(m_pSrcUrl->host()80,QIODevice::ReadWrite);  
  15.             connect(m_pTcpSocket, SIGNAL(connected()),this, SLOT(slotConnected()));
  16.             connect(m_pTcpSocket, SIGNAL(readyRead()),this, SLOT(slotReadyRead()));
  17.             connect(m_pTcpSocket, SIGNAL(error(QAbstractSocket::SocketError)),
  18.                     this,         SLOT(slotDownloadError(QAbstractSocket::SocketError))
  19.                    );
  20.             connect(m_pTcpSocket,SIGNAL(disconnected()),this,SLOT(slotDisconnected()));
  21.         }else
  22.             QMessageBox::warning(this,"Warning","File open Err !");
  23.     }
  24.  
  25. }

После выбора директории и успешного открытия (создания) файла еще не скачаной песни, необходимо отправить запрос на сервер ВК что бы от «отдал» нам песню. В этом нам поможет QtcpSocket который реализует и очень упрощает работу с TCP/IP. После успешного соеденения с сервером ВК- отправляем ему HTTP запрос:
  1. void MainWindow::slotConnected(){
  2.     m_pTcpSocket->write(QString("GET " + m_pSrcUrl->path() + "\r\n").toAscii());
  3. }

На что он выдает нам содержимое песни которое и нужно записать в файл:
  1. void MainWindow::slotReadyRead(){
  2.     m_file->write(m_pTcpSocket->read(m_pTcpSocket->bytesAvailable()));
  3. }

После того как ВК отдаст все содержимое песни он закроет соединенее, а нам остается закрыть файл.
  1. void MainWindow::slotDisconnected(){
  2.     m_file->close();
  3.     m_pTcpSocket->close();
  4. }


Как и обещал, в начале статьи, ссылка на репозиторий (Mercurial): https://bitbucket.org/denis_medved/vkaudio
Огромное спасибо Максу Шлее и издательству bhv за замечательную книгу «Qt 4.5: Профессиональное программирование на С++» и конечно же всему открытому сообществу.

upd:
Вместо устарелого QHttp и QTcpSocket используется QNetworkAccessManager.
Tags:
Hubs:
+36
Comments 15
Comments Comments 15

Articles