changeset 75:d3e9310598b1

*: refactor some stuff text: "TextParagraph"s are now called sections, because that's the actual word for it :P text: new classes: Line and OneLineSection, solves many problems with paragraphs that are only one line long (ex. going out of bounds) http: reworked http stuff to allow threaded get requests, also moved it to its own file to (hopefully) remove clutter eventually I'll make a threaded post request method and use that in the "basic" function
author Paper <mrpapersonic@gmail.com>
date Wed, 04 Oct 2023 01:42:30 -0400
parents 5ccb99bfa605
children 3364fadc8a36
files CMakeLists.txt include/core/http.h include/gui/widgets/poster.h include/gui/widgets/text.h src/core/http.cpp src/gui/pages/statistics.cpp src/gui/widgets/anime_info.cpp src/gui/widgets/poster.cpp src/gui/widgets/sidebar.cpp src/gui/widgets/text.cpp src/services/anilist.cpp
diffstat 11 files changed, 276 insertions(+), 145 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Tue Oct 03 06:12:43 2023 -0400
+++ b/CMakeLists.txt	Wed Oct 04 01:42:30 2023 -0400
@@ -56,6 +56,7 @@
 	src/core/config.cpp
 	src/core/date.cpp
 	src/core/filesystem.cpp
+	src/core/http.cpp
 	src/core/json.cpp
 	src/core/strings.cpp
 	src/core/time.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/include/core/http.h	Wed Oct 04 01:42:30 2023 -0400
@@ -0,0 +1,33 @@
+#ifndef __core__http_h
+#define __core__http_h
+
+#include <string>
+#include <vector>
+#include <QThread>
+
+class QObject;
+
+namespace HTTP {
+
+class HttpGetThread : public QThread {
+		Q_OBJECT
+
+	public:
+		HttpGetThread(std::string url, std::vector<std::string> headers = {}, QObject* parent = nullptr);
+
+	private:
+		static size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userdata);
+		void run() override;
+		std::string _url;
+		std::vector<std::string> _headers;
+
+	signals:
+		void resultReady(const QByteArray& arr);
+};
+
+/* Performs a basic (blocking) post request */
+std::string PerformBasicPostRequest(std::string url, std::string data, std::vector<std::string> headers = {});
+
+}
+
+#endif // __core__http_h
--- a/include/gui/widgets/poster.h	Tue Oct 03 06:12:43 2023 -0400
+++ b/include/gui/widgets/poster.h	Wed Oct 04 01:42:30 2023 -0400
@@ -2,6 +2,7 @@
 #define __gui__widgets__poster_h
 #include <QFrame>
 #include <QImage>
+#include <QByteArray>
 
 class QWidget;
 class ClickableLabel;
@@ -14,6 +15,8 @@
 
 	protected:
 		void resizeEvent(QResizeEvent*) override;
+		void ImageDownloadFinished(QByteArray arr);
+		void RenderToLabel();
 
 	private:
 		QImage img;
--- a/include/gui/widgets/text.h	Tue Oct 03 06:12:43 2023 -0400
+++ b/include/gui/widgets/text.h	Wed Oct 04 01:42:30 2023 -0400
@@ -5,6 +5,7 @@
 #include <QString>
 #include <QSize>
 #include <QPlainTextEdit>
+#include <QLineEdit>
 
 class QFrame;
 class QLabel;
@@ -33,14 +34,25 @@
 		QSize sizeHint() const override;
 };
 
-/* technically a paragraph and a heading is actually a
-   "section", but that name is equally as confusing as
-   "text paragraph". */
-class TextParagraph : public QWidget {
+class Line : public QLineEdit {
 		Q_OBJECT
 
 	public:
-		TextParagraph(QString title, QString data, QWidget* parent = nullptr);
+		Line(QString text, QWidget* parent = nullptr);
+};
+
+class Title : public Line {
+		Q_OBJECT
+
+	public:
+		Title(QString title, QWidget* parent = nullptr);
+};
+
+class Section : public QWidget {
+		Q_OBJECT
+
+	public:
+		Section(QString title, QString data, QWidget* parent = nullptr);
 		Header* GetHeader();
 		Paragraph* GetParagraph();
 
@@ -49,11 +61,11 @@
 		Paragraph* paragraph;
 };
 
-class LabelledTextParagraph : public QWidget {
+class LabelledSection : public QWidget {
 		Q_OBJECT
 
 	public:
-		LabelledTextParagraph(QString title, QString label, QString data, QWidget* parent = nullptr);
+		LabelledSection(QString title, QString label, QString data, QWidget* parent = nullptr);
 		Header* GetHeader();
 		Paragraph* GetLabels();
 		Paragraph* GetParagraph();
@@ -64,11 +76,11 @@
 		Paragraph* paragraph;
 };
 
-class SelectableTextParagraph : public QWidget {
+class SelectableSection : public QWidget {
 		Q_OBJECT
 
 	public:
-		SelectableTextParagraph(QString title, QString data, QWidget* parent = nullptr);
+		SelectableSection(QString title, QString data, QWidget* parent = nullptr);
 		Header* GetHeader();
 		Paragraph* GetParagraph();
 
@@ -77,11 +89,17 @@
 		Paragraph* paragraph;
 };
 
-class Title : public Paragraph {
+class OneLineSection : public QWidget {
 		Q_OBJECT
 
 	public:
-		Title(QString title, QWidget* parent = nullptr);
+		OneLineSection(QString title, QString data, QWidget* parent = nullptr);
+		Header* GetHeader();
+		Line* GetLine();
+
+	private:
+		Header* header;
+		Line* line;
 };
 
 } // namespace TextWidgets
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/core/http.cpp	Wed Oct 04 01:42:30 2023 -0400
@@ -0,0 +1,86 @@
+#include "core/http.h"
+#include "core/session.h"
+#include <QByteArray>
+#include <QMessageBox>
+#include <QThread>
+#include <curl/curl.h>
+#include <string>
+#include <vector>
+
+namespace HTTP {
+
+HttpGetThread::HttpGetThread(std::string url, std::vector<std::string> headers, QObject* parent) : QThread(parent) {
+	_url = url;
+	_headers = headers;
+}
+
+size_t HttpGetThread::WriteCallback(void* contents, size_t size, size_t nmemb, void* userdata) {
+	reinterpret_cast<QByteArray*>(userdata)->append(reinterpret_cast<char*>(contents), size * nmemb);
+	return size * nmemb;
+}
+
+void HttpGetThread::run() {
+	struct curl_slist* list = NULL;
+	QByteArray userdata;
+
+	CURL* curl = curl_easy_init();
+	if (curl) {
+		for (const std::string& h : _headers) {
+			list = curl_slist_append(list, h.c_str());
+		}
+		curl_easy_setopt(curl, CURLOPT_URL, _url.c_str());
+		curl_easy_setopt(curl, CURLOPT_WRITEDATA, &userdata);
+		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &WriteCallback);
+		/* Use system certs... useful on Windows. */
+		curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA);
+		CURLcode res = curl_easy_perform(curl);
+		session.IncrementRequests();
+		curl_easy_cleanup(curl);
+		if (res != CURLE_OK) {
+			QMessageBox box(QMessageBox::Icon::Critical, "",
+			                QString("curl_easy_perform(curl) failed!: ") + QString(curl_easy_strerror(res)));
+			box.exec();
+		}
+	}
+	emit resultReady(userdata);
+}
+
+static size_t CurlWriteStringCallback(void* contents, size_t size, size_t nmemb, void* userdata) {
+	reinterpret_cast<std::string*>(userdata)->append(reinterpret_cast<char*>(contents), size * nmemb);
+	return size * nmemb;
+}
+
+/* Performs a basic (blocking) post request */
+std::string PerformBasicPostRequest(std::string url, std::string data, std::vector<std::string> headers) {
+	struct curl_slist* list = NULL;
+	std::string userdata;
+	CURL* curl = curl_easy_init();
+	if (curl) {
+		for (const std::string& h : headers) {
+			list = curl_slist_append(list, h.c_str());
+		}
+		curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
+		curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str());
+		curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
+		curl_easy_setopt(curl, CURLOPT_WRITEDATA, &userdata);
+		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &CurlWriteStringCallback);
+		/* Use system certs... useful on Windows. */
+		curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA);
+		CURLcode res = curl_easy_perform(curl);
+		session.IncrementRequests();
+		curl_slist_free_all(list);
+		curl_easy_cleanup(curl);
+		if (res != CURLE_OK) {
+			QMessageBox box(QMessageBox::Icon::Critical, "",
+			                QString("curl_easy_perform(curl) failed!: ") + QString(curl_easy_strerror(res)));
+			box.exec();
+			return "";
+		}
+		return userdata;
+	}
+	return "";
+}
+
+}
+
+#include "core/moc_http.cpp"
--- a/src/gui/pages/statistics.cpp	Tue Oct 03 06:12:43 2023 -0400
+++ b/src/gui/pages/statistics.cpp	Wed Oct 04 01:42:30 2023 -0400
@@ -22,14 +22,14 @@
 	setPalette(pal);
 	setAutoFillBackground(true);
 
-	TextWidgets::LabelledTextParagraph* anime_list_paragraph = new TextWidgets::LabelledTextParagraph(
+	TextWidgets::LabelledSection* anime_list_paragraph = new TextWidgets::LabelledSection(
 	    tr("Anime list"),
 	    tr("Anime count:\nEpisode count:\nTime spent watching:\nTime to complete:\nAverage score:\nScore deviation:"),
 	    "\n\n\n\n\n\n", this);
 	anime_list_data = anime_list_paragraph->GetParagraph();
 
-	TextWidgets::LabelledTextParagraph* application_paragraph =
-	    new TextWidgets::LabelledTextParagraph(tr("Minori"), tr("Uptime:\nRequests made:"), "\n\n", this);
+	TextWidgets::LabelledSection* application_paragraph =
+	    new TextWidgets::LabelledSection(tr("Minori"), tr("Uptime:\nRequests made:"), "\n\n", this);
 	application_data = application_paragraph->GetParagraph();
 
 	layout->addWidget(anime_list_paragraph);
--- a/src/gui/widgets/anime_info.cpp	Tue Oct 03 06:12:43 2023 -0400
+++ b/src/gui/widgets/anime_info.cpp	Wed Oct 04 01:42:30 2023 -0400
@@ -10,9 +10,8 @@
 	QVBoxLayout* layout = new QVBoxLayout(this);
 
 	/* alt titles */
-	TextWidgets::SelectableTextParagraph* title = new TextWidgets::SelectableTextParagraph(
-	    tr("Alternative titles"), QString::fromUtf8(Strings::Implode(anime.GetTitleSynonyms(), ", ").c_str()), this);
-	title->GetParagraph()->setWordWrapMode(QTextOption::NoWrap);
+	TextWidgets::OneLineSection* title = new TextWidgets::OneLineSection(
+	    tr("Alternative titles"), Strings::ToQString(Strings::Implode(anime.GetTitleSynonyms(), ", ").c_str()), this);
 	layout->addWidget(title);
 
 	/* details */
@@ -24,12 +23,12 @@
 	               << Translate::ToString(anime.GetSeason()).c_str() << " " << anime.GetAirDate().GetYear() << "\n"
 	               << Strings::Implode(anime.GetGenres(), ", ").c_str() << "\n"
 	               << anime.GetAudienceScore() << "%";
-	layout->addWidget(new TextWidgets::LabelledTextParagraph(
+	layout->addWidget(new TextWidgets::LabelledSection(
 	    tr("Details"), tr("Type:\nEpisodes:\nStatus:\nSeason:\nGenres:\nScore:"), details_data, this));
 
 	/* synopsis */
-	TextWidgets::SelectableTextParagraph* synopsis =
-	    new TextWidgets::SelectableTextParagraph(tr("Synopsis"), QString::fromUtf8(anime.GetSynopsis().c_str()), this);
+	TextWidgets::SelectableSection* synopsis =
+	    new TextWidgets::SelectableSection(tr("Synopsis"), QString::fromUtf8(anime.GetSynopsis().c_str()), this);
 
 	synopsis->GetParagraph()->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
 	layout->addWidget(synopsis);
--- a/src/gui/widgets/poster.cpp	Tue Oct 03 06:12:43 2023 -0400
+++ b/src/gui/widgets/poster.cpp	Wed Oct 04 01:42:30 2023 -0400
@@ -1,6 +1,7 @@
 #include "gui/widgets/poster.h"
 #include "gui/widgets/clickable_label.h"
 #include "core/anime_db.h"
+#include "core/http.h"
 #include "core/strings.h"
 #include "core/session.h"
 #include <QFrame>
@@ -14,32 +15,6 @@
 #include <QPixmap>
 #include <curl/curl.h>
 
-static size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userdata) {
-	reinterpret_cast<QByteArray*>(userdata)->append(reinterpret_cast<char*>(contents), size * nmemb);
-	return size * nmemb;
-}
-
-static QByteArray SendRequest(std::string url) {
-	QByteArray userdata;
-	CURL* curl = curl_easy_init();
-	if (curl) {
-		curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
-		curl_easy_setopt(curl, CURLOPT_WRITEDATA, &userdata);
-		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &WriteCallback);
-		/* Use system certs... useful on Windows. */
-		curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA);
-		CURLcode res = curl_easy_perform(curl);
-		session.IncrementRequests();
-		curl_easy_cleanup(curl);
-		if (res != CURLE_OK) {
-			QMessageBox box(QMessageBox::Icon::Critical, "",
-			                QString("curl_easy_perform(curl) failed!: ") + QString(curl_easy_strerror(res)));
-			box.exec();
-		}
-	}
-	return userdata;
-}
-
 Poster::Poster(int id, QWidget* parent) : QFrame(parent) {
 	QHBoxLayout* layout = new QHBoxLayout(this);
 	layout->setContentsMargins(1, 1, 1, 1);
@@ -50,9 +25,12 @@
 	setFrameShadow(QFrame::Plain);
 	
 	const Anime::Anime& anime = Anime::db.items[id];
-	QByteArray ret = SendRequest(anime.GetPosterUrl());
 
-	img.loadFromData(ret);
+	HTTP::HttpGetThread *image_thread = new HTTP::HttpGetThread(anime.GetPosterUrl(), {}, this);
+	connect(image_thread, &HTTP::HttpGetThread::resultReady, this, &Poster::ImageDownloadFinished);
+	connect(image_thread, &HTTP::HttpGetThread::finished, image_thread, &QObject::deleteLater);
+	image_thread->start();
+
 	QPixmap pixmap = QPixmap::fromImage(img);
 
 	label = new ClickableLabel(this);
@@ -63,9 +41,19 @@
 	layout->addWidget(label);
 }
 
+void Poster::ImageDownloadFinished(QByteArray arr) {
+	img.loadFromData(arr);
+	RenderToLabel();
+}
+
+void Poster::RenderToLabel() {
+	QPixmap pixmap = QPixmap::fromImage(img);
+	if (pixmap.isNull()) return;
+	label->setPixmap(pixmap.scaled(label->size(), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation));
+}
+
 void Poster::resizeEvent(QResizeEvent*) {
-	QPixmap pixmap = QPixmap::fromImage(img).scaled(label->size(), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
-	label->setPixmap(pixmap);
+	RenderToLabel();
 }
 
 #include "gui/widgets/moc_poster.cpp"
--- a/src/gui/widgets/sidebar.cpp	Tue Oct 03 06:12:43 2023 -0400
+++ b/src/gui/widgets/sidebar.cpp	Wed Oct 04 01:42:30 2023 -0400
@@ -1,9 +1,7 @@
 #include "gui/widgets/sidebar.h"
-#include <QDebug>
 #include <QFrame>
 #include <QListWidget>
 #include <QListWidgetItem>
-#include <QMessageBox>
 #include <QMouseEvent>
 
 SideBar::SideBar(QWidget* parent) : QListWidget(parent) {
--- a/src/gui/widgets/text.cpp	Tue Oct 03 06:12:43 2023 -0400
+++ b/src/gui/widgets/text.cpp	Wed Oct 04 01:42:30 2023 -0400
@@ -1,9 +1,7 @@
 #include "gui/widgets/text.h"
 #include "core/session.h"
-#include <QDebug>
 #include <QFrame>
 #include <QLabel>
-#include <QPixmap>
 #include <QTextBlock>
 #include <QVBoxLayout>
 
@@ -36,7 +34,68 @@
 	static_text_title->setText(text);
 }
 
-TextParagraph::TextParagraph(QString title, QString data, QWidget* parent) : QWidget(parent) {
+/* inherits QPlainTextEdit and gives a much more reasonable minimum size */
+Paragraph::Paragraph(QString text, QWidget* parent) : QPlainTextEdit(text, parent) {
+	setTextInteractionFlags(Qt::TextBrowserInteraction);
+	setFrameShape(QFrame::NoFrame);
+	setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+	setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+
+	QPalette pal;
+	pal.setColor(QPalette::Window, Qt::transparent);
+	setPalette(pal);
+
+	setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+}
+
+void Paragraph::SetText(QString text) {
+	QTextDocument* document = new QTextDocument(this);
+	document->setDocumentLayout(new QPlainTextDocumentLayout(document));
+	document->setPlainText(text);
+	setDocument(document);
+}
+
+/* highly based upon... some stackoverflow answer for PyQt */
+QSize Paragraph::minimumSizeHint() const {
+	return QSize(0, 0);
+}
+
+QSize Paragraph::sizeHint() const {
+	QTextDocument* doc = document();
+	doc->adjustSize();
+	long h = 0;
+	for (QTextBlock line = doc->begin(); line != doc->end(); line = line.next()) {
+		h += doc->documentLayout()->blockBoundingRect(line).height();
+	}
+	return QSize(doc->size().width(), h);
+}
+
+/* Equivalent to Paragraph(), but is only capable of showing one line. Only
+   exists because sometimes with SelectableSection it will let you go
+   out of bounds */
+Line::Line(QString text, QWidget* parent) : QLineEdit(text, parent) {
+	setFrame(false);
+	setReadOnly(true);
+
+	QPalette pal;
+	pal.setColor(QPalette::Window, Qt::transparent);
+	setPalette(pal);
+
+	setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
+}
+
+Title::Title(QString title, QWidget* parent) : Line(title, parent) {
+	QFont fnt(font());
+	fnt.setPixelSize(16);
+	setFont(fnt);
+
+	QPalette pal(palette());
+	pal.setColor(QPalette::Window, Qt::transparent);
+	pal.setColor(QPalette::Text, QColor(0x00, 0x33, 0x99));
+	setPalette(pal);
+}
+
+Section::Section(QString title, QString data, QWidget* parent) : QWidget(parent) {
 	QVBoxLayout* layout = new QVBoxLayout(this);
 
 	header = new Header(title, this);
@@ -60,15 +119,15 @@
 	layout->setContentsMargins(0, 0, 0, 0);
 }
 
-Header* TextParagraph::GetHeader() {
+Header* Section::GetHeader() {
 	return header;
 }
 
-Paragraph* TextParagraph::GetParagraph() {
+Paragraph* Section::GetParagraph() {
 	return paragraph;
 }
 
-LabelledTextParagraph::LabelledTextParagraph(QString title, QString label, QString data, QWidget* parent)
+LabelledSection::LabelledSection(QString title, QString label, QString data, QWidget* parent)
     : QWidget(parent) {
 	QVBoxLayout* layout = new QVBoxLayout(this);
 
@@ -104,19 +163,19 @@
 	layout->setContentsMargins(0, 0, 0, 0);
 }
 
-Header* LabelledTextParagraph::GetHeader() {
+Header* LabelledSection::GetHeader() {
 	return header;
 }
 
-Paragraph* LabelledTextParagraph::GetLabels() {
+Paragraph* LabelledSection::GetLabels() {
 	return labels;
 }
 
-Paragraph* LabelledTextParagraph::GetParagraph() {
+Paragraph* LabelledSection::GetParagraph() {
 	return paragraph;
 }
 
-SelectableTextParagraph::SelectableTextParagraph(QString title, QString data, QWidget* parent) : QWidget(parent) {
+SelectableSection::SelectableSection(QString title, QString data, QWidget* parent) : QWidget(parent) {
 	QVBoxLayout* layout = new QVBoxLayout(this);
 
 	header = new Header(title, this);
@@ -137,67 +196,41 @@
 	layout->setContentsMargins(0, 0, 0, 0);
 }
 
-Header* SelectableTextParagraph::GetHeader() {
+Header* SelectableSection::GetHeader() {
 	return header;
 }
 
-Paragraph* SelectableTextParagraph::GetParagraph() {
+Paragraph* SelectableSection::GetParagraph() {
 	return paragraph;
 }
 
-/* inherits QPlainTextEdit and gives a much more reasonable minimum size */
-Paragraph::Paragraph(QString text, QWidget* parent) : QPlainTextEdit(text, parent) {
-	setTextInteractionFlags(Qt::TextBrowserInteraction);
-	setFrameShape(QFrame::NoFrame);
-	setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
-	setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+OneLineSection::OneLineSection(QString title, QString text, QWidget* parent) : QWidget(parent) {
+	QVBoxLayout* layout = new QVBoxLayout(this);
+
+	header = new Header(title, this);
 
-	QPalette pal;
-	pal.setColor(QPalette::Window, QColor(0, 0, 0, 0));
-	setPalette(pal);
+	QWidget* content = new QWidget(this);
+	QHBoxLayout* content_layout = new QHBoxLayout(content);
+
+	line = new Line(text, content);
 
-	setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
-}
+	content_layout->addWidget(line);
+	content_layout->setSpacing(0);
+	content_layout->setContentsMargins(0, 0, 0, 0);
+	content->setContentsMargins(12, 0, 0, 0);
 
-void Paragraph::SetText(QString text) {
-	QTextDocument* document = new QTextDocument(this);
-	document->setDocumentLayout(new QPlainTextDocumentLayout(document));
-	document->setPlainText(text);
-	setDocument(document);
-}
-
-/* highly based upon... some stackoverflow answer for PyQt */
-QSize Paragraph::minimumSizeHint() const {
-	return QSize(0, 0);
+	layout->addWidget(header);
+	layout->addWidget(content);
+	layout->setSpacing(0);
+	layout->setContentsMargins(0, 0, 0, 0);
 }
 
-QSize Paragraph::sizeHint() const {
-	QTextDocument* doc = document();
-	doc->adjustSize();
-	long h = 0;
-	for (QTextBlock line = doc->begin(); line != doc->end(); line = line.next()) {
-		h += doc->documentLayout()->blockBoundingRect(line).height();
-	}
-	return QSize(doc->size().width(), h);
+Header* OneLineSection::GetHeader() {
+	return header;
 }
 
-Title::Title(QString title, QWidget* parent) : Paragraph(title, parent) {
-	setReadOnly(true);
-	setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
-	setWordWrapMode(QTextOption::NoWrap);
-	setFrameShape(QFrame::NoFrame);
-	setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
-	setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
-	setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
-
-	QFont fnt(font());
-	fnt.setPixelSize(16);
-	setFont(fnt);
-
-	QPalette pal(palette());
-	pal.setColor(QPalette::Window, Qt::transparent);
-	pal.setColor(QPalette::Text, QColor(0x00, 0x33, 0x99));
-	setPalette(pal);
+Line* OneLineSection::GetLine() {
+	return line;
 }
 
 } // namespace TextWidgets
--- a/src/services/anilist.cpp	Tue Oct 03 06:12:43 2023 -0400
+++ b/src/services/anilist.cpp	Wed Oct 04 01:42:30 2023 -0400
@@ -3,6 +3,7 @@
 #include "core/anime_db.h"
 #include "core/config.h"
 #include "core/json.h"
+#include "core/http.h"
 #include "core/session.h"
 #include "core/strings.h"
 #include "gui/translate/anilist.h"
@@ -12,7 +13,6 @@
 #include <QMessageBox>
 #include <QUrl>
 #include <chrono>
-#include <curl/curl.h>
 #include <exception>
 #define CLIENT_ID "13706"
 
@@ -37,41 +37,13 @@
 
 static Account account;
 
-static size_t CurlWriteCallback(void* contents, size_t size, size_t nmemb, void* userdata) {
-	reinterpret_cast<std::string*>(userdata)->append(reinterpret_cast<char*>(contents), size * nmemb);
-	return size * nmemb;
-}
-
-/* A wrapper around cURL to send requests to AniList */
 std::string SendRequest(std::string data) {
-	struct curl_slist* list = NULL;
-	std::string userdata;
-	CURL* curl = curl_easy_init();
-	if (curl) {
-		std::string bearer = "Authorization: Bearer " + account.AuthToken();
-		list = curl_slist_append(list, "Accept: application/json");
-		list = curl_slist_append(list, "Content-Type: application/json");
-		list = curl_slist_append(list, bearer.c_str());
-		curl_easy_setopt(curl, CURLOPT_URL, "https://graphql.anilist.co");
-		curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str());
-		curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
-		curl_easy_setopt(curl, CURLOPT_WRITEDATA, &userdata);
-		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &CurlWriteCallback);
-		/* Use system certs... useful on Windows. */
-		curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA);
-		CURLcode res = curl_easy_perform(curl);
-		session.IncrementRequests();
-		curl_slist_free_all(list);
-		curl_easy_cleanup(curl);
-		if (res != CURLE_OK) {
-			QMessageBox box(QMessageBox::Icon::Critical, "",
-			                QString("curl_easy_perform(curl) failed!: ") + QString(curl_easy_strerror(res)));
-			box.exec();
-			return "";
-		}
-		return userdata;
-	}
-	return "";
+	std::vector<std::string> headers = {
+		"Authorization: Bearer " + account.AuthToken(),
+		"Accept: application/json",
+		"Content-Type: application/json"
+	};
+	return HTTP::PerformBasicPostRequest("https://graphql.anilist.co", data, headers);
 }
 
 void ParseListStatus(std::string status, Anime::Anime& anime) {