summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorTor Arne Vestbø <tor.arne.vestbo@qt.io>2023-06-26 17:02:25 +0200
committerTor Arne Vestbø <tor.arne.vestbo@qt.io>2023-06-26 18:01:15 +0200
commite9c98355d139958c05feb5e201cd69e37e97e2a9 (patch)
treecb370dc88fb1592bd76c2765cb669d08b4ba9d1a /tests
parent3e7c834b40cb952cb6685329dd86748386201806 (diff)
Move twitter oauth example to manual test
Pick-to: 6.5 6.6 Change-Id: Ie861a97ccf33ec406aeab8480cb546699b675d85 Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
Diffstat (limited to 'tests')
-rw-r--r--tests/manual/examples/twittertimeline/CMakeLists.txt47
-rw-r--r--tests/manual/examples/twittertimeline/doc/images/twittertimeline-example.pngbin0 -> 24972 bytes
-rw-r--r--tests/manual/examples/twittertimeline/doc/src/qtnetworkauth-twittertimeline.qdoc22
-rw-r--r--tests/manual/examples/twittertimeline/main.cpp78
-rw-r--r--tests/manual/examples/twittertimeline/twitter.cpp47
-rw-r--r--tests/manual/examples/twittertimeline/twitter.h31
-rw-r--r--tests/manual/examples/twittertimeline/twitterdialog.ui73
-rw-r--r--tests/manual/examples/twittertimeline/twittertimeline.pro19
-rw-r--r--tests/manual/examples/twittertimeline/twittertimelinemodel.cpp151
-rw-r--r--tests/manual/examples/twittertimeline/twittertimelinemodel.h49
10 files changed, 517 insertions, 0 deletions
diff --git a/tests/manual/examples/twittertimeline/CMakeLists.txt b/tests/manual/examples/twittertimeline/CMakeLists.txt
new file mode 100644
index 0000000..fdf2633
--- /dev/null
+++ b/tests/manual/examples/twittertimeline/CMakeLists.txt
@@ -0,0 +1,47 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+# Generated from twittertimeline.pro.
+
+cmake_minimum_required(VERSION 3.16)
+project(twittertimeline LANGUAGES CXX)
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTORCC ON)
+set(CMAKE_AUTOUIC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/oauth/twittertimeline")
+
+find_package(Qt6 COMPONENTS Core)
+find_package(Qt6 COMPONENTS Widgets)
+find_package(Qt6 COMPONENTS Network)
+find_package(Qt6 COMPONENTS NetworkAuth)
+
+qt_add_executable(twittertimeline
+ main.cpp
+ twitter.cpp twitter.h
+ twitterdialog.ui
+ twittertimelinemodel.cpp twittertimelinemodel.h
+)
+set_target_properties(twittertimeline PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ MACOSX_BUNDLE FALSE
+)
+target_link_libraries(twittertimeline PUBLIC
+ Qt::Core
+ Qt::Network
+ Qt::NetworkAuth
+ Qt::Widgets
+)
+
+install(TARGETS twittertimeline
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/tests/manual/examples/twittertimeline/doc/images/twittertimeline-example.png b/tests/manual/examples/twittertimeline/doc/images/twittertimeline-example.png
new file mode 100644
index 0000000..99d66c9
--- /dev/null
+++ b/tests/manual/examples/twittertimeline/doc/images/twittertimeline-example.png
Binary files differ
diff --git a/tests/manual/examples/twittertimeline/doc/src/qtnetworkauth-twittertimeline.qdoc b/tests/manual/examples/twittertimeline/doc/src/qtnetworkauth-twittertimeline.qdoc
new file mode 100644
index 0000000..65ee395
--- /dev/null
+++ b/tests/manual/examples/twittertimeline/doc/src/qtnetworkauth-twittertimeline.qdoc
@@ -0,0 +1,22 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \example twittertimeline
+ \title Twitter Timeline Example
+ \ingroup examples-qtnetworkauth
+ \brief Demonstrates authenticating with OAuth to access a Twitter timeline.
+ \excludefromcreator
+ \image twittertimeline-example.png Screenshot of the example
+
+ The \e {Twitter Timeline} example uses OAuth, as supported by
+ \l {Qt Network Authorization}, to sign in to Twitter and display a timeline
+ of tweets (in text format) associated with the authenticated user.
+
+ To use this example, a consumer key and secret from Twitter are needed.
+ To register the application visit https://apps.twitter.com.
+ You’ll need to add \e http://127.0.0.1:1337/callback as a callback URL
+ in your Twitter app settings.
+
+ \include examples-run.qdocinc
+*/
diff --git a/tests/manual/examples/twittertimeline/main.cpp b/tests/manual/examples/twittertimeline/main.cpp
new file mode 100644
index 0000000..ec553a4
--- /dev/null
+++ b/tests/manual/examples/twittertimeline/main.cpp
@@ -0,0 +1,78 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "ui_twitterdialog.h"
+#include "twittertimelinemodel.h"
+
+#include <functional>
+
+#include <QUrl>
+#include <QApplication>
+#include <QNetworkReply>
+#include <QNetworkRequest>
+#include <QCommandLineParser>
+#include <QCommandLineOption>
+
+int main(int argc, char **argv)
+{
+ QApplication app(argc, argv);
+
+ app.setApplicationName("Twitter Timeline");
+ app.setApplicationDisplayName("Twitter Timeline");
+ app.setOrganizationDomain("qt.io");
+ app.setOrganizationName("The Qt Company");
+
+ QCommandLineParser parser;
+ QCommandLineOption token(QStringList() << "k" << "consumer-key",
+ "Application consumer key", "key");
+ QCommandLineOption secret(QStringList() << "s" << "consumer-secret",
+ "Application consumer secret", "secret");
+ QCommandLineOption connect(QStringList() << "c" << "connect",
+ "Connects to twitter. Requires consumer-key and consumer-secret");
+
+ parser.addOptions({ token, secret, connect });
+ parser.process(app);
+
+ struct TwitterDialog : QDialog, Ui::TwitterDialog {
+ TwitterTimelineModel model;
+
+ TwitterDialog()
+ : QDialog()
+ {
+ setupUi(this);
+ view->setModel(&model);
+ view->horizontalHeader()->hideSection(0);
+ view->horizontalHeader()->hideSection(1);
+ }
+ } twitterDialog;
+
+ const auto authenticate = [&]() {
+ const auto clientIdentifier = twitterDialog.clientIdLineEdit->text();
+ const auto clientSharedSecret = twitterDialog.clientSecretLineEdit->text();
+ twitterDialog.model.authenticate(qMakePair(clientIdentifier, clientSharedSecret));
+ };
+ const auto buttonSlot = [&]() {
+ if (twitterDialog.model.status() == Twitter::Status::Granted)
+ twitterDialog.model.updateTimeline();
+ else
+ authenticate();
+ };
+
+ twitterDialog.clientIdLineEdit->setText(parser.value(token));
+ twitterDialog.clientSecretLineEdit->setText(parser.value(secret));
+ if (parser.isSet(connect)) {
+ if (parser.value(token).isEmpty() || parser.value(secret).isEmpty()) {
+ parser.showHelp();
+ } else {
+ authenticate();
+ twitterDialog.view->setFocus();
+ }
+ }
+
+ QObject::connect(twitterDialog.pushButton, &QPushButton::clicked, buttonSlot);
+ QObject::connect(&twitterDialog.model, &TwitterTimelineModel::authenticated,
+ std::bind(&QPushButton::setText, twitterDialog.pushButton, "&Update"));
+
+ twitterDialog.show();
+ return app.exec();
+}
diff --git a/tests/manual/examples/twittertimeline/twitter.cpp b/tests/manual/examples/twittertimeline/twitter.cpp
new file mode 100644
index 0000000..562121c
--- /dev/null
+++ b/tests/manual/examples/twittertimeline/twitter.cpp
@@ -0,0 +1,47 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "twitter.h"
+
+#include <QtGui>
+#include <QtCore>
+#include <QtNetwork>
+
+Twitter::Twitter(QObject *parent) :
+ Twitter(QString(), qMakePair(QString(), QString()), parent)
+{}
+
+Twitter::Twitter(const QPair<QString, QString> &clientCredentials, QObject *parent) :
+ Twitter(QString(), clientCredentials, parent)
+{}
+
+Twitter::Twitter(const QString &screenName,
+ const QPair<QString, QString> &clientCredentials,
+ QObject *parent) :
+ QOAuth1(clientCredentials.first, clientCredentials.second, nullptr, parent)
+{
+ replyHandler = new QOAuthHttpServerReplyHandler(1337, this);
+ replyHandler->setCallbackPath("callback");
+ setReplyHandler(replyHandler);
+ setTemporaryCredentialsUrl(QUrl("https://api.twitter.com/oauth/request_token"));
+ setAuthorizationUrl(QUrl("https://api.twitter.com/oauth/authenticate"));
+ setTokenCredentialsUrl(QUrl("https://api.twitter.com/oauth/access_token"));
+
+ connect(this, &QAbstractOAuth::authorizeWithBrowser, [=](QUrl url) {
+ QUrlQuery query(url);
+
+ // Forces the user to enter their credentials to authorize the correct
+ // user account
+ query.addQueryItem("force_login", "true");
+
+ if (!screenName.isEmpty())
+ query.addQueryItem("screen_name", screenName);
+ url.setQuery(query);
+ QDesktopServices::openUrl(url);
+ });
+
+ connect(this, &QOAuth1::granted, this, &Twitter::authenticated);
+
+ if (!clientCredentials.first.isEmpty() && !clientCredentials.second.isEmpty())
+ grant();
+}
diff --git a/tests/manual/examples/twittertimeline/twitter.h b/tests/manual/examples/twittertimeline/twitter.h
new file mode 100644
index 0000000..9fbd7d0
--- /dev/null
+++ b/tests/manual/examples/twittertimeline/twitter.h
@@ -0,0 +1,31 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef TWITTERTIMELINE_TWITTER_H
+#define TWITTERTIMELINE_TWITTER_H
+
+#include <QtCore>
+#include <QtNetwork>
+#include <QtNetworkAuth>
+
+class Twitter : public QOAuth1
+{
+ Q_OBJECT
+
+public:
+ Twitter(QObject *parent = nullptr);
+ Twitter(const QPair<QString, QString> &clientCredentials, QObject *parent = nullptr);
+ Twitter(const QString &screenName,
+ const QPair<QString, QString> &clientCredentials,
+ QObject *parent = nullptr);
+
+signals:
+ void authenticated();
+
+private:
+ Q_DISABLE_COPY(Twitter)
+
+ QOAuthHttpServerReplyHandler *replyHandler = nullptr;
+};
+
+#endif // TWITTERTIMELINE_TWITTER_H
diff --git a/tests/manual/examples/twittertimeline/twitterdialog.ui b/tests/manual/examples/twittertimeline/twitterdialog.ui
new file mode 100644
index 0000000..d8be245
--- /dev/null
+++ b/tests/manual/examples/twittertimeline/twitterdialog.ui
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>TwitterDialog</class>
+ <widget class="QDialog" name="TwitterDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>800</width>
+ <height>600</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Twitter Timeline</string>
+ </property>
+ <layout class="QFormLayout" name="formLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="clientIdLabel">
+ <property name="text">
+ <string>C&amp;lient Id:</string>
+ </property>
+ <property name="buddy">
+ <cstring>clientIdLineEdit</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="clientIdLineEdit"/>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="clientSecretLabel">
+ <property name="text">
+ <string>Client &amp;secret:</string>
+ </property>
+ <property name="buddy">
+ <cstring>clientSecretLineEdit</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="clientSecretLineEdit"/>
+ </item>
+ <item row="2" column="0" colspan="2">
+ <widget class="QPushButton" name="pushButton">
+ <property name="text">
+ <string>&amp;Connect</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0" colspan="2">
+ <widget class="QTableView" name="view">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::NoSelection</enum>
+ </property>
+ <attribute name="horizontalHeaderStretchLastSection">
+ <bool>true</bool>
+ </attribute>
+ <attribute name="verticalHeaderVisible">
+ <bool>false</bool>
+ </attribute>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/tests/manual/examples/twittertimeline/twittertimeline.pro b/tests/manual/examples/twittertimeline/twittertimeline.pro
new file mode 100644
index 0000000..c0dcee2
--- /dev/null
+++ b/tests/manual/examples/twittertimeline/twittertimeline.pro
@@ -0,0 +1,19 @@
+QT = core widgets network networkauth
+requires(qtConfig(tableview))
+CONFIG -= app_bundle
+
+HEADERS += \
+ twitter.h \
+ twittertimelinemodel.h
+
+SOURCES += \
+ main.cpp \
+ twitter.cpp \
+ twittertimelinemodel.cpp
+
+FORMS += \
+ twitterdialog.ui
+
+# install
+target.path = $$[QT_INSTALL_EXAMPLES]/oauth/twittertimeline
+INSTALLS += target
diff --git a/tests/manual/examples/twittertimeline/twittertimelinemodel.cpp b/tests/manual/examples/twittertimeline/twittertimelinemodel.cpp
new file mode 100644
index 0000000..d8635e7
--- /dev/null
+++ b/tests/manual/examples/twittertimeline/twittertimelinemodel.cpp
@@ -0,0 +1,151 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "twittertimelinemodel.h"
+
+#include <QtGui>
+#include <QtCore>
+#include <QtWidgets>
+
+TwitterTimelineModel::TwitterTimelineModel(QObject *parent) : QAbstractTableModel(parent)
+{
+ connect(&twitter, &Twitter::authenticated, this, &TwitterTimelineModel::authenticated);
+ connect(&twitter, &Twitter::authenticated, this, &TwitterTimelineModel::updateTimeline);
+}
+
+int TwitterTimelineModel::rowCount(const QModelIndex &parent) const
+{
+#if defined(QT_DEBUG)
+ Q_ASSERT(!parent.isValid());
+#else
+ Q_UNUSED(parent);
+#endif
+ return tweets.size();
+}
+
+QVariant TwitterTimelineModel::data(const QModelIndex &index, int role) const
+{
+ if (role != Qt::DisplayRole)
+ return QVariant();
+
+ auto it = tweets.begin();
+ std::advance(it, index.row());
+ switch (index.column())
+ {
+ case 0:
+ return QString::number(it->id);
+ case 1:
+ return it->createdAt.toString(Qt::ISODateWithMs);
+ case 2:
+ return it->user;
+ case 3:
+ return it->text;
+ }
+ return QVariant();
+}
+
+int TwitterTimelineModel::columnCount(const QModelIndex &) const
+{
+ return 4;
+}
+
+QVariant TwitterTimelineModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (role != Qt::DisplayRole)
+ return QVariant();
+
+ if (orientation == Qt::Horizontal) {
+ switch (section) {
+ case 0:
+ return QStringLiteral("Id");
+ case 1:
+ return QStringLiteral("Created at");
+ case 2:
+ return QStringLiteral("User");
+ case 3:
+ return QStringLiteral("Text");
+ }
+ }
+ return section;
+}
+
+void TwitterTimelineModel::authenticate(const QPair<QString, QString> &clientCredentials)
+{
+ twitter.setClientCredentials(clientCredentials);
+ twitter.grant();
+}
+
+QAbstractOAuth::Status TwitterTimelineModel::status() const
+{
+ return twitter.status();
+}
+
+void TwitterTimelineModel::updateTimeline()
+{
+ if (twitter.status() != Twitter::Status::Granted)
+ QMessageBox::warning(nullptr, qApp->applicationName(), "Not authenticated");
+
+ QUrl url("https://api.twitter.com/1.1/statuses/home_timeline.json");
+ QVariantMap parameters;
+ if (tweets.size()) {
+ // Tweets are time-ordered, newest first. Pass the most recent
+ // ID we have to request everything more recent than it:
+ parameters.insert("since_id", QString::number(tweets.first().id));
+ // From https://dev.twitter.com/rest/reference/get/search/tweets:
+ // Returns results with an ID greater than (that is, more recent than)
+ // the specified ID. There are limits to the number of Tweets which can
+ // be accessed through the API. If the limit of Tweets has occurred
+ // since the since_id, the since_id will be forced to the oldest ID
+ // available.
+ }
+ QNetworkReply *reply = twitter.get(url, parameters);
+ connect(reply, &QNetworkReply::finished, this, &TwitterTimelineModel::parseJson);
+}
+
+void TwitterTimelineModel::parseJson()
+{
+ QJsonParseError parseError;
+ auto reply = qobject_cast<QNetworkReply*>(sender());
+ Q_ASSERT(reply);
+ const auto data = reply->readAll();
+ const auto document = QJsonDocument::fromJson(data, &parseError);
+ if (parseError.error) {
+ qCritical() << "TwitterTimelineModel::parseJson. Error at:" << parseError.offset
+ << parseError.errorString();
+ return;
+ } else if (document.isObject()) {
+ // Error received :(
+ const auto object = document.object();
+ const auto errorArray = object.value("errors").toArray();
+ Q_ASSERT_X(errorArray.size(), "parse", data);
+ QStringList errors;
+ for (const auto error : errorArray) {
+ Q_ASSERT(error.isObject());
+ Q_ASSERT(error.toObject().contains("message"));
+ errors.append(error.toObject().value("message").toString());
+ }
+ QMessageBox::warning(nullptr, qApp->applicationName(), errors.join("<br />"));
+ return;
+ }
+
+ Q_ASSERT_X(document.isArray(), "parse", data);
+ const auto array = document.array();
+ if (array.size()) {
+ beginInsertRows(QModelIndex(), 0, array.size() - 1);
+ auto before = tweets.begin();
+ for (const auto &value : array) {
+ Q_ASSERT(value.isObject());
+ const auto object = value.toObject();
+ const auto createdAt = QDateTime::fromString(object.value("created_at").toString(),
+ "ddd MMM dd HH:mm:ss +0000 yyyy");
+ before = tweets.insert(before, Tweet{
+ object.value("id").toVariant().toULongLong(),
+ createdAt,
+ object.value("user").toObject().value("name").toString(),
+ object.value("text").toString()
+ });
+ std::advance(before, 1);
+ }
+ endInsertRows();
+ }
+}
diff --git a/tests/manual/examples/twittertimeline/twittertimelinemodel.h b/tests/manual/examples/twittertimeline/twittertimelinemodel.h
new file mode 100644
index 0000000..17b81f5
--- /dev/null
+++ b/tests/manual/examples/twittertimeline/twittertimelinemodel.h
@@ -0,0 +1,49 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef TWITTERTIMELINEMODEL_H
+#define TWITTERTIMELINEMODEL_H
+
+#include "twitter.h"
+
+#include <QtCore>
+#include <QtNetwork>
+
+class TwitterTimelineModel : public QAbstractTableModel
+{
+ Q_OBJECT
+
+public:
+ TwitterTimelineModel(QObject *parent = nullptr);
+
+ int rowCount(const QModelIndex &parent) const override;
+ QVariant data(const QModelIndex &index, int role) const override;
+ int columnCount(const QModelIndex &parent) const override;
+ QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
+
+ void authenticate(const QPair<QString, QString> &clientCredentials);
+ QAbstractOAuth::Status status() const;
+
+public slots:
+ void updateTimeline();
+
+signals:
+ void authenticated();
+
+private:
+ Q_DISABLE_COPY(TwitterTimelineModel)
+
+ void parseJson();
+
+ struct Tweet {
+ quint64 id;
+ QDateTime createdAt;
+ QString user;
+ QString text;
+ };
+
+ QList<Tweet> tweets;
+ Twitter twitter;
+};
+
+#endif // TWITTERTIMELINEMODEL_H