Merge pull request #151 from dada513/rss_not_begone

Re-add RSS feed with the new PolyMC website
This commit is contained in:
LennyMcLennington 2022-02-11 12:03:29 +00:00 committed by GitHub
commit 53ea261350
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 477 additions and 1 deletions

View File

@ -45,10 +45,14 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_NO_DEPRECATED_WARNINGS=Y")
##################################### Set Application options #####################################
######## Set URLs ########
set(Launcher_NEWS_RSS_URL "https://polymc.github.io/feed/feed.xml" CACHE STRING "URL to fetch PolyMC's news RSS feed from.")
set(Launcher_NEWS_OPEN_URL "https://polymc.github.io/news/" CACHE STRING "URL that gets opened when the user clicks 'More News'")
######## Set version numbers ########
set(Launcher_VERSION_MAJOR 1)
set(Launcher_VERSION_MINOR 0)
set(Launcher_VERSION_HOTFIX 5)
set(Launcher_VERSION_HOTFIX 6)
# Build number
set(Launcher_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.")

View File

@ -42,6 +42,7 @@ Config::Config()
VERSION_STR = "@Launcher_VERSION_STRING@";
NEWS_RSS_URL = "@Launcher_NEWS_RSS_URL@";
NEWS_OPEN_URL = "@Launcher_NEWS_OPEN_URL@";
IMGUR_CLIENT_ID = "@Launcher_IMGUR_CLIENT_ID@";
MSA_CLIENT_ID = "@Launcher_MSA_CLIENT_ID@";
META_URL = "@Launcher_META_URL@";

View File

@ -67,6 +67,11 @@ public:
*/
QString NEWS_RSS_URL;
/**
* URL that gets opened when the user clicks "More News"
*/
QString NEWS_OPEN_URL;
/**
* Client ID you can get from Imgur when you register an application
*/

View File

@ -181,6 +181,15 @@ set(NOTIFICATIONS_SOURCES
notifications/NotificationChecker.cpp
)
# Backend for the news bar... there's usually no news.
set(NEWS_SOURCES
# News System
news/NewsChecker.h
news/NewsChecker.cpp
news/NewsEntry.h
news/NewsEntry.cpp
)
# Icon interface
set(ICONS_SOURCES
# Icons System and related code

View File

@ -0,0 +1,132 @@
/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "NewsChecker.h"
#include <QByteArray>
#include <QDomDocument>
#include <QDebug>
NewsChecker::NewsChecker(shared_qobject_ptr<QNetworkAccessManager> network, const QString& feedUrl)
{
m_network = network;
m_feedUrl = feedUrl;
}
void NewsChecker::reloadNews()
{
// Start a netjob to download the RSS feed and call rssDownloadFinished() when it's done.
if (isLoadingNews())
{
qDebug() << "Ignored request to reload news. Currently reloading already.";
return;
}
qDebug() << "Reloading news.";
NetJob* job = new NetJob("News RSS Feed", m_network);
job->addNetAction(Net::Download::makeByteArray(m_feedUrl, &newsData));
QObject::connect(job, &NetJob::succeeded, this, &NewsChecker::rssDownloadFinished);
QObject::connect(job, &NetJob::failed, this, &NewsChecker::rssDownloadFailed);
m_newsNetJob.reset(job);
job->start();
}
void NewsChecker::rssDownloadFinished()
{
// Parse the XML file and process the RSS feed entries.
qDebug() << "Finished loading RSS feed.";
m_newsNetJob.reset();
QDomDocument doc;
{
// Stuff to store error info in.
QString errorMsg = "Unknown error.";
int errorLine = -1;
int errorCol = -1;
// Parse the XML.
if (!doc.setContent(newsData, false, &errorMsg, &errorLine, &errorCol))
{
QString fullErrorMsg = QString("Error parsing RSS feed XML. %s at %d:%d.").arg(errorMsg, errorLine, errorCol);
fail(fullErrorMsg);
newsData.clear();
return;
}
newsData.clear();
}
// If the parsing succeeded, read it.
QDomNodeList items = doc.elementsByTagName("entry");
m_newsEntries.clear();
for (int i = 0; i < items.length(); i++)
{
QDomElement element = items.at(i).toElement();
NewsEntryPtr entry;
entry.reset(new NewsEntry());
QString errorMsg = "An unknown error occurred.";
if (NewsEntry::fromXmlElement(element, entry.get(), &errorMsg))
{
qDebug() << "Loaded news entry" << entry->title;
m_newsEntries.append(entry);
}
else
{
qWarning() << "Failed to load news entry at index" << i << ":" << errorMsg;
}
}
succeed();
}
void NewsChecker::rssDownloadFailed(QString reason)
{
// Set an error message and fail.
fail(tr("Failed to load news RSS feed:\n%1").arg(reason));
}
QList<NewsEntryPtr> NewsChecker::getNewsEntries() const
{
return m_newsEntries;
}
bool NewsChecker::isLoadingNews() const
{
return m_newsNetJob.get() != nullptr;
}
QString NewsChecker::getLastLoadErrorMsg() const
{
return m_lastLoadError;
}
void NewsChecker::succeed()
{
m_lastLoadError = "";
qDebug() << "News loading succeeded.";
m_newsNetJob.reset();
emit newsLoaded();
}
void NewsChecker::fail(const QString& errorMsg)
{
m_lastLoadError = errorMsg;
qDebug() << "Failed to load news:" << errorMsg;
m_newsNetJob.reset();
emit newsLoadingFailed(errorMsg);
}

105
launcher/news/NewsChecker.h Normal file
View File

@ -0,0 +1,105 @@
/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QObject>
#include <QString>
#include <QList>
#include <net/NetJob.h>
#include "NewsEntry.h"
class NewsChecker : public QObject
{
Q_OBJECT
public:
/*!
* Constructs a news reader to read from the given RSS feed URL.
*/
NewsChecker(shared_qobject_ptr<QNetworkAccessManager> network, const QString& feedUrl);
/*!
* Returns the error message for the last time the news was loaded.
* Empty string if the last load was successful.
*/
QString getLastLoadErrorMsg() const;
/*!
* Returns true if the news has been loaded successfully.
*/
bool isNewsLoaded() const;
//! True if the news is currently loading. If true, reloadNews() will do nothing.
bool isLoadingNews() const;
/*!
* Returns a list of news entries.
*/
QList<NewsEntryPtr> getNewsEntries() const;
/*!
* Reloads the news from the website's RSS feed.
* If the news is already loading, this does nothing.
*/
void Q_SLOT reloadNews();
signals:
/*!
* Signal fired after the news has finished loading.
*/
void newsLoaded();
/*!
* Signal fired after the news fails to load.
*/
void newsLoadingFailed(QString errorMsg);
protected slots:
void rssDownloadFinished();
void rssDownloadFailed(QString reason);
protected: /* data */
//! The URL for the RSS feed to fetch.
QString m_feedUrl;
//! List of news entries.
QList<NewsEntryPtr> m_newsEntries;
//! The network job to use to load the news.
NetJob::Ptr m_newsNetJob;
//! True if news has been loaded.
bool m_loadedNews;
QByteArray newsData;
/*!
* Gets the error message that was given last time the news was loaded.
* If the last news load succeeded, this will be an empty string.
*/
QString m_lastLoadError;
shared_qobject_ptr<QNetworkAccessManager> m_network;
protected slots:
/// Emits newsLoaded() and sets m_lastLoadError to empty string.
void succeed();
/// Emits newsLoadingFailed() and sets m_lastLoadError to the given message.
void fail(const QString& errorMsg);
};

View File

@ -0,0 +1,65 @@
/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "NewsEntry.h"
#include <QDomNodeList>
#include <QVariant>
NewsEntry::NewsEntry(QObject* parent) :
QObject(parent)
{
this->title = tr("Untitled");
this->content = tr("No content.");
this->link = "";
}
NewsEntry::NewsEntry(const QString& title, const QString& content, const QString& link, QObject* parent) :
QObject(parent)
{
this->title = title;
this->content = content;
this->link = link;
}
/*!
* Gets the text content of the given child element as a QVariant.
*/
inline QString childValue(const QDomElement& element, const QString& childName, QString defaultVal="")
{
QDomNodeList nodes = element.elementsByTagName(childName);
if (nodes.count() > 0)
{
QDomElement element = nodes.at(0).toElement();
return element.text();
}
else
{
return defaultVal;
}
}
bool NewsEntry::fromXmlElement(const QDomElement& element, NewsEntry* entry, QString* errorMsg)
{
QString title = childValue(element, "title", tr("Untitled"));
QString content = childValue(element, "description", tr("No content."));
QString link = childValue(element, "id");
entry->title = title;
entry->content = content;
entry->link = link;
return true;
}

57
launcher/news/NewsEntry.h Normal file
View File

@ -0,0 +1,57 @@
/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QObject>
#include <QString>
#include <QDomElement>
#include <memory>
class NewsEntry : public QObject
{
Q_OBJECT
public:
/*!
* Constructs an empty news entry.
*/
explicit NewsEntry(QObject* parent=0);
/*!
* Constructs a new news entry.
* Note that content may contain HTML.
*/
NewsEntry(const QString& title, const QString& content, const QString& link, QObject* parent=0);
/*!
* Attempts to load information from the given XML element into the given news entry pointer.
* If this fails, the function will return false and store an error message in the errorMsg pointer.
*/
static bool fromXmlElement(const QDomElement& element, NewsEntry* entry, QString* errorMsg=0);
//! The post title.
QString title;
//! The post's content. May contain HTML.
QString content;
//! URL to the post.
QString link;
};
typedef std::shared_ptr<NewsEntry> NewsEntryPtr;

View File

@ -58,6 +58,7 @@
#include <BuildConfig.h>
#include <net/NetJob.h>
#include <net/Download.h>
#include <news/NewsChecker.h>
#include <notifications/NotificationChecker.h>
#include <tools/BaseProfiler.h>
#include <updater/DownloadTask.h>
@ -200,6 +201,7 @@ public:
//TranslatedAction actionRefresh;
TranslatedAction actionCheckUpdate;
TranslatedAction actionSettings;
TranslatedAction actionMoreNews;
TranslatedAction actionManageAccounts;
TranslatedAction actionLaunchInstance;
TranslatedAction actionRenameInstance;
@ -244,6 +246,7 @@ public:
TranslatedToolbar mainToolBar;
TranslatedToolbar instanceToolBar;
TranslatedToolbar newsToolBar;
QVector<TranslatedToolbar *> all_toolbars;
bool m_kill = false;
@ -426,6 +429,29 @@ public:
MainWindow->setStatusBar(statusBar);
}
void createNewsToolbar(QMainWindow *MainWindow)
{
newsToolBar = TranslatedToolbar(MainWindow);
newsToolBar->setObjectName(QStringLiteral("newsToolBar"));
newsToolBar->setMovable(false);
newsToolBar->setAllowedAreas(Qt::BottomToolBarArea);
newsToolBar->setIconSize(QSize(16, 16));
newsToolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
newsToolBar->setFloatable(false);
newsToolBar->setWindowTitle(QT_TRANSLATE_NOOP("MainWindow", "News Toolbar"));
actionMoreNews = TranslatedAction(MainWindow);
actionMoreNews->setObjectName(QStringLiteral("actionMoreNews"));
actionMoreNews->setIcon(APPLICATION->getThemedIcon("news"));
actionMoreNews.setTextId(QT_TRANSLATE_NOOP("MainWindow", "More news..."));
actionMoreNews.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the development blog to read more news about %1."));
all_actions.append(&actionMoreNews);
newsToolBar->addAction(actionMoreNews);
all_toolbars.append(&newsToolBar);
MainWindow->addToolBar(Qt::BottomToolBarArea, newsToolBar);
}
void createInstanceToolbar(QMainWindow *MainWindow)
{
instanceToolBar = TranslatedToolbar(MainWindow);
@ -610,6 +636,7 @@ public:
MainWindow->setCentralWidget(centralWidget);
createStatusBar(MainWindow);
createNewsToolbar(MainWindow);
createInstanceToolbar(MainWindow);
retranslateUi(MainWindow);
@ -664,6 +691,20 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow
connect(secretEventFilter, &KonamiCode::triggered, this, &MainWindow::konamiTriggered);
}
// Add the news label to the news toolbar.
{
m_newsChecker.reset(new NewsChecker(APPLICATION->network(), BuildConfig.NEWS_RSS_URL));
newsLabel = new QToolButton();
newsLabel->setIcon(APPLICATION->getThemedIcon("news"));
newsLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
newsLabel->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
newsLabel->setFocusPolicy(Qt::NoFocus);
ui->newsToolBar->insertWidget(ui->actionMoreNews, newsLabel);
QObject::connect(newsLabel, &QAbstractButton::clicked, this, &MainWindow::newsButtonClicked);
QObject::connect(m_newsChecker.get(), &NewsChecker::newsLoaded, this, &MainWindow::updateNewsLabel);
updateNewsLabel();
}
// Create the instance list widget
{
view = new InstanceView(ui->centralWidget);
@ -768,6 +809,13 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow
// TODO: refresh accounts here?
// auto accounts = APPLICATION->accounts();
// load the news
{
m_newsChecker->reloadNews();
updateNewsLabel();
}
if(BuildConfig.UPDATER_ENABLED)
{
bool updatesAllowed = APPLICATION->updatesAreAllowed();
@ -1141,6 +1189,29 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *ev)
return QMainWindow::eventFilter(obj, ev);
}
void MainWindow::updateNewsLabel()
{
if (m_newsChecker->isLoadingNews())
{
newsLabel->setText(tr("Loading news..."));
newsLabel->setEnabled(false);
}
else
{
QList<NewsEntryPtr> entries = m_newsChecker->getNewsEntries();
if (entries.length() > 0)
{
newsLabel->setText(entries[0]->title);
newsLabel->setEnabled(true);
}
else
{
newsLabel->setText(tr("No news available."));
newsLabel->setEnabled(false);
}
}
}
void MainWindow::updateAvailable(GoUpdate::Status status)
{
if(!APPLICATION->updatesAreAllowed())
@ -1614,6 +1685,24 @@ void MainWindow::on_actionReportBug_triggered()
DesktopServices::openUrl(QUrl(BuildConfig.BUG_TRACKER_URL));
}
void MainWindow::on_actionMoreNews_triggered()
{
DesktopServices::openUrl(QUrl(BuildConfig.NEWS_OPEN_URL));
}
void MainWindow::newsButtonClicked()
{
QList<NewsEntryPtr> entries = m_newsChecker->getNewsEntries();
if (entries.count() > 0)
{
DesktopServices::openUrl(QUrl(entries[0]->link));
}
else
{
DesktopServices::openUrl(QUrl(BuildConfig.NEWS_OPEN_URL));
}
}
void MainWindow::on_actionAbout_triggered()
{
AboutDialog dialog(this);

View File

@ -27,6 +27,7 @@
#include "updater/GoUpdate.h"
class LaunchController;
class NewsChecker;
class NotificationChecker;
class QToolButton;
class InstanceProxyModel;
@ -108,6 +109,10 @@ private slots:
void on_actionReportBug_triggered();
void on_actionMoreNews_triggered();
void newsButtonClicked();
void on_actionLaunchInstance_triggered();
void on_actionLaunchInstanceOffline_triggered();
@ -169,6 +174,8 @@ private slots:
void repopulateAccountsMenu();
void updateNewsLabel();
/*!
* Runs the DownloadTask and installs updates.
*/
@ -198,12 +205,14 @@ private:
// these are managed by Qt's memory management model!
InstanceView *view = nullptr;
InstanceProxyModel *proxymodel = nullptr;
QToolButton *newsLabel = nullptr;
QLabel *m_statusLeft = nullptr;
QLabel *m_statusCenter = nullptr;
QMenu *accountMenu = nullptr;
QToolButton *accountMenuButton = nullptr;
KonamiCode * secretEventFilter = nullptr;
unique_qobject_ptr<NewsChecker> m_newsChecker;
unique_qobject_ptr<NotificationChecker> m_notificationChecker;
InstancePtr m_selectedInstance;