// SPDX-License-Identifier: GPL-3.0-only /* * Prism Launcher - Minecraft Launcher * Copyright (c) 2022 flowln * Copyright (C) 2022 Sefa Eyeoglu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * This file incorporates work covered by the following copyright and * permission notice: * * 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 "ImgurAPI.h" #include "BuildConfig.h" #include "net/RPCSink.h" #include "net/RawHeaderProxy.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace ImgurAPI { std::pair makeUpload(ScreenShot::Ptr shot) { auto getPayload = [shot] -> std::expected { auto* file = new QFile(shot->m_file.absoluteFilePath()); if (!file->open(QFile::ReadOnly)) { qWarning() << "Could not open file" << shot->m_file.absoluteFilePath() << "for reading:" << file->errorString(); file->deleteLater(); return std::unexpected(QObject::tr("Could not open file %1 for reading: %2").arg(shot->m_file.absoluteFilePath(), file->errorString())); } QHttpPart filePart; filePart.setBodyDevice(file); filePart.setHeader(QNetworkRequest::ContentTypeHeader, "image/png"); filePart.setHeader(QNetworkRequest::ContentDispositionHeader, R"(form-data; name="image"; filename=")" + file->fileName() + "\""); QHttpPart typePart; typePart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"type\""); typePart.setBody("file"); QHttpPart namePart; namePart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"title\""); namePart.setBody(shot->m_file.baseName().toUtf8()); auto* multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType); file->setParent(multipart); multipart->append(filePart); multipart->append(typePart); multipart->append(namePart); return multipart; }; auto parseFunc = [shot](const QByteArray& response) -> Net::RPC::Sink::ParseResult { QJsonParseError jsonError; QJsonDocument doc = QJsonDocument::fromJson(response, &jsonError); if (jsonError.error != QJsonParseError::NoError) { qDebug() << "imgur server did not reply with JSON" << jsonError.errorString(); return std::unexpected("Invalid json reply"); } auto object = doc.object(); if (!object.value("success").toBool()) { qDebug() << "Screenshot upload not successful:" << doc.toJson(); return std::unexpected("Screenshot was not uploaded successfully"); } const auto data = object.value("data").toObject(); shot->m_imgurId = data.value("id").toString(); shot->m_url = data.value("link").toString(); shot->m_imgurDeleteHash = data.value("deletehash").toString(); return shot->m_url; }; auto dl = Net::Request::makeCustomRequest( { .method = Net::HttpMethod::Post, .url = QUrl(BuildConfig.IMGUR_BASE_URL + "image"), .data = getPayload }); auto sink = std::make_unique>(std::move(parseFunc)); auto* result = sink->result(); dl->setSink(std::move(sink)); dl->addHeaderProxy(std::make_unique(QList{ { .headerName = "Authorization", .headerValue = QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toUtf8() }, { .headerName = "Accept", .headerValue = "application/json" } })); return { dl, result }; } std::pair makeAlbum(const QList& screenshots) { // this payload needs to be generated after the screenshots are uploaded auto getPayload = [screenshots]() { QStringList hashes; for (const auto& shot : screenshots) { hashes.append(shot->m_imgurDeleteHash); } return "deletehashes=" + hashes.join("&deletehashes=").toUtf8() + "&title=Minecraft%20Screenshots&privacy=hidden"; }; auto parseFunc = [](const QByteArray& response) -> Net::RPC::Sink::ParseResult { QJsonParseError jsonError; QJsonDocument doc = QJsonDocument::fromJson(response, &jsonError); if (jsonError.error != QJsonParseError::NoError) { qDebug() << jsonError.errorString(); return std::unexpected("Invalid json reply"); } auto object = doc.object(); if (!object.value("success").toBool()) { qDebug() << doc.toJson(); return std::unexpected("Failed to create album"); } return AlbumResult{ .deleteHash = object.value("data").toObject().value("deletehash").toString(), .id = object.value("data").toObject().value("id").toString() }; }; auto dl = Net::Request::makeCustomRequest( { .method = Net::HttpMethod::Post, .url = QUrl(BuildConfig.IMGUR_BASE_URL + "album"), .data = getPayload }); auto sink = std::make_unique>(std::move(parseFunc)); auto* result = sink->result(); dl->setSink(std::move(sink)); dl->addHeaderProxy(std::make_unique(QList{ { .headerName = "Content-Type", .headerValue = "application/x-www-form-urlencoded" }, { .headerName = "Authorization", .headerValue = QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toUtf8() }, { .headerName = "Accept", .headerValue = "application/json" } })); return { dl, result }; } } // namespace ImgurAPI