#include "vimagehosting.h" #include "utils/vutils.h" #include "veditor.h" #include "vfile.h" #include "utils/vclipboardutils.h" extern VConfigManager *g_config; VGithubImageHosting::VGithubImageHosting(VFile *p_file, QObject *p_parent) :QObject(p_parent), m_file(p_file) { reply = Q_NULLPTR; imageUploaded = false; } void VGithubImageHosting::handleUploadImageToGithubRequested() { qDebug() << "Start processing the image upload request to GitHub"; if(g_config->getGithubPersonalAccessToken().isEmpty() || g_config->getGithubReposName().isEmpty() || g_config->getGithubUserName().isEmpty()) { qDebug() << "Please configure the GitHub image hosting first!"; QMessageBox::warning(nullptr, tr("GitHub Image Hosting"), tr("Please configure the GitHub image hosting first!")); return; } authenticateGithubImageHosting(g_config->getGithubPersonalAccessToken()); } void VGithubImageHosting::authenticateGithubImageHosting(QString p_token) { qDebug() << "start the authentication process "; QApplication::setOverrideCursor(Qt::WaitCursor); QNetworkRequest request; QUrl url = QUrl("https://api.github.com"); QString ptoken = "token " + p_token; request.setRawHeader("Authorization", ptoken.toLocal8Bit()); request.setUrl(url); if(reply != Q_NULLPTR) { reply->deleteLater(); } reply = manager.get(request); connect(reply, &QNetworkReply::finished, this, &VGithubImageHosting::githubImageBedAuthFinished); } void VGithubImageHosting::githubImageBedAuthFinished() { switch (reply->error()) { case QNetworkReply::NoError: { QByteArray bytes = reply->readAll(); if(bytes.contains("Bad credentials")) { qDebug() << "Authentication failed"; QApplication::restoreOverrideCursor(); // Recovery pointer QMessageBox::warning(nullptr, tr("GitHub Image Hosting"), tr("Bad credentials! ") + tr("Please check your GitHub Image Hosting parameters!")); return; } else { qDebug() << "Authentication completed"; qDebug() << "The current article path is: " << m_file->fetchPath(); imageBasePath = m_file->fetchBasePath(); newFileContent = m_file->getContent(); QVector images = VUtils::fetchImagesFromMarkdownFile(m_file,ImageLink::LocalRelativeInternal); QApplication::restoreOverrideCursor(); // Recovery pointer if(images.size() > 0) { proDlg = new QProgressDialog(tr("Uploading images to GitHub..."), tr("Abort"), 0, images.size(), nullptr); proDlg->setWindowModality(Qt::WindowModal); proDlg->setWindowTitle(tr("Uploading Images To Github")); proDlg->setMinimumDuration(1); uploadImageCount = images.size(); uploadImageCountIndex = uploadImageCount; for(int i=0;igetName() << " No local images to upload"; QString info = tr("No local images to upload: %1").arg(m_file->getName()); QMessageBox::information(nullptr, tr("GitHub Image Hosting"), info); } } break; } default: { QApplication::restoreOverrideCursor(); // Recovery pointer qDebug() << "Network error: " << reply->errorString() << " error " << reply->error(); QString info = tr("Network error: ") + reply->errorString() + tr("\n\nPlease check your network!"); QMessageBox::warning(nullptr, tr("GitHub Image Hosting"), info); } } } void VGithubImageHosting::githubImageBedUploadManager() { uploadImageCountIndex--; QString imageToUpload; QMapIterator it(imageUrlMap); while(it.hasNext()) { it.next(); if(it.value() == "") { imageToUpload = it.key(); proDlg->setValue(uploadImageCount - 1 - uploadImageCountIndex); proDlg->setLabelText(tr("Uploading image: %1").arg(imageToUpload)); break; } } if(imageToUpload == "") { qDebug() << "All images have been uploaded"; githubImageBedReplaceLink(newFileContent, m_file->fetchPath()); return; } if(g_config->getGithubPersonalAccessToken().isEmpty() || g_config->getGithubReposName().isEmpty() || g_config->getGithubUserName().isEmpty()) { qDebug() << "Please configure the GitHub image hosting first!"; QMessageBox::warning(nullptr, tr("GitHub Image Hosting"), tr("Please configure the GitHub image hosting first!")); imageUrlMap.clear(); return; } QString path = imageBasePath + QDir::separator(); path += imageToUpload; githubImageBedUploadImage(g_config->getGithubUserName(), g_config->getGithubReposName(), path, g_config->getGithubPersonalAccessToken()); } void VGithubImageHosting::githubImageBedUploadImage(const QString &p_username, const QString &p_repository, const QString &p_imagePath, const QString &p_token) { QFileInfo fileInfo(p_imagePath.toLocal8Bit()); if(!fileInfo.exists()) { qDebug() << "The picture does not exist in this path: " << p_imagePath.toLocal8Bit(); QString info = tr("The picture does not exist in this path: ") + p_imagePath.toLocal8Bit(); QMessageBox::warning(nullptr, tr("GitHub Image Hosting"), info); imageUrlMap.clear(); if(imageUploaded) { githubImageBedReplaceLink(newFileContent, m_file->fetchPath()); } return; } QString fileSuffix = fileInfo.suffix(); // file extension QString fileName = fileInfo.fileName(); // filename QString uploadUrl; // Image upload URL uploadUrl = "https://api.github.com/repos/" + p_username + "/" + p_repository + "/contents/" + QString::number(QDateTime::currentDateTime().toTime_t()) + "_" + fileName; if(fileSuffix != QString::fromLocal8Bit("jpg") && fileSuffix != QString::fromLocal8Bit("png") && fileSuffix != QString::fromLocal8Bit("gif")) { qDebug() << "Unsupported type..."; QString info = tr("Unsupported type: ") + fileSuffix; QMessageBox::warning(nullptr, tr("GitHub Image Hosting"), info); imageUrlMap.clear(); if(imageUploaded) { githubImageBedReplaceLink(newFileContent, m_file->fetchPath()); } return; } QNetworkRequest request; QUrl url = QUrl(uploadUrl); QString ptoken = "token " + p_token; request.setRawHeader("Authorization", ptoken.toLocal8Bit()); request.setUrl(url); if(reply != Q_NULLPTR) { reply->deleteLater(); } QString param = githubImageBedGenerateParam(p_imagePath); QByteArray postData; postData.append(param); reply = manager.put(request, postData); qDebug() << "Start uploading images: " + p_imagePath + " Waiting for upload to complete"; uploadImageStatus = true; currentUploadImage = p_imagePath; connect(reply, &QNetworkReply::finished, this, &VGithubImageHosting::githubImageBedUploadFinished); } void VGithubImageHosting::githubImageBedUploadFinished() { if (proDlg->wasCanceled()) { qDebug() << "User stops uploading"; reply->abort(); // Stop network request imageUrlMap.clear(); // The ones that have been uploaded successfully before still need to stay if(imageUploaded) { githubImageBedReplaceLink(newFileContent, m_file->fetchPath()); } return; } switch (reply->error()) { case QNetworkReply::NoError: { QByteArray bytes = reply->readAll(); int httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if(httpStatus == 201) { qDebug() << "Upload success"; QString downloadUrl; QString imageName; QJsonDocument doucment = QJsonDocument::fromJson(bytes); if (!doucment.isNull()) { if (doucment.isObject()) { QJsonObject object = doucment.object(); if (object.contains("content")) { QJsonValue value = object.value("content"); if (value.isObject()) { QJsonObject obj = value.toObject(); if (obj.contains("download_url")) { QJsonValue value = obj.value("download_url"); if (value.isString()) { downloadUrl = value.toString(); qDebug() << "JSON decode: download_url : " << downloadUrl; imageUploaded = true; // On behalf of successfully uploaded images proDlg->setValue(uploadImageCount); } } if(obj.contains("name")) { QJsonValue value = obj.value("name"); if(value.isString()) { imageName = value.toString(); } } // Traverse key in imageurlmap QList klist = imageUrlMap.keys(); QString temp; for(int i=0;ifetchPath()); } QString info = tr("JSON decode error. Please contact the developer."); QMessageBox::warning(nullptr, tr("GitHub Image Hosting"), info); } } else { // If status is not 201, it means there is a problem. delete proDlg; imageUrlMap.clear(); qDebug() << "Upload failure"; if(imageUploaded) { githubImageBedReplaceLink(newFileContent, m_file->fetchPath()); } QString info = tr("GitHub status code != 201. Please contact the developer."); QMessageBox::warning(nullptr, tr("GitHub Image Hosting"), info); } break; } default: { delete proDlg; imageUrlMap.clear(); qDebug()<<"network error: " << reply->errorString() << " error " << reply->error(); QByteArray bytes = reply->readAll(); qDebug() << bytes; int httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); qDebug() << "status: " << httpStatus; if(imageUploaded) { githubImageBedReplaceLink(newFileContent, m_file->fetchPath()); } QString info = tr("Uploading ") + currentUploadImage + tr(" \n\nNetwork error: ") + reply->errorString() + tr("\n\nPlease check the network or image size"); QMessageBox::warning(nullptr, tr("GitHub Image Hosting"), info); } } } void VGithubImageHosting::githubImageBedReplaceLink(QString p_fileContent, const QString p_filePath) { Q_UNUSED(p_filePath); // This function must be executed when the upload is completed or fails in the middle. if(!g_config->getGithubKeepImgScale()) { // delete image scale p_fileContent.replace(QRegExp("\\s+=\\d+x"),""); } if(!g_config->getGithubDoNotReplaceLink()) { // Write content to file. m_editor->setContent(p_fileContent, true); } else { VClipboardUtils::setTextToClipboard(QApplication::clipboard(), p_fileContent); emit m_editor->object()->statusMessage(tr("Copied contents with new image links")); } // Reset. imageUrlMap.clear(); imageUploaded = false; } QString VGithubImageHosting::githubImageBedGenerateParam(const QString p_imagePath) { // According to the requirements of GitHub interface, pictures must be in Base64 format. // Image to base64. QByteArray hexed; QFile imgFile(p_imagePath); imgFile.open(QIODevice::ReadOnly); hexed = imgFile.readAll().toBase64(); QString imgBase64 = hexed; QJsonObject json; json.insert("message", QString("updatetest")); json.insert("content", imgBase64); QJsonDocument document; document.setObject(json); QByteArray byteArray = document.toJson(QJsonDocument::Compact); QString jsonStr(byteArray); return jsonStr; } void VGithubImageHosting::setEditor(VEditor *p_editor) { m_editor = p_editor; } VGiteeImageHosting::VGiteeImageHosting(VFile *p_file, QObject *p_parent) :QObject(p_parent), m_file(p_file) { reply = Q_NULLPTR; imageUploaded = false; } void VGiteeImageHosting::handleUploadImageToGiteeRequested() { qDebug() << "Start processing the image upload request to Gitee"; if(g_config->getGiteePersonalAccessToken().isEmpty() || g_config->getGiteeReposName().isEmpty() || g_config->getGiteeUserName().isEmpty()) { qDebug() << "Please configure the Gitee image hosting first!"; QMessageBox::warning(nullptr, tr("Gitee Image Hosting"), tr("Please configure the Gitee image hosting first!")); return; } authenticateGiteeImageHosting(g_config->getGiteeUserName(), g_config->getGiteeReposName(), g_config->getGiteePersonalAccessToken()); } void VGiteeImageHosting::authenticateGiteeImageHosting(const QString &p_username, const QString &p_repository, const QString &p_token) { qDebug() << "start the authentication process "; QApplication::setOverrideCursor(Qt::WaitCursor); QNetworkRequest request; QString url = "https://gitee.com/api/v5/repos/"; url += p_username + "/"; url += p_repository + "/branches/master?access_token="; url += p_token; qDebug() << "gitee url: " << url; QUrl qurl = QUrl(url); request.setUrl(qurl); if(reply != Q_NULLPTR) { reply->deleteLater(); } reply = manager.get(request); connect(reply, &QNetworkReply::finished, this, &VGiteeImageHosting::giteeImageBedAuthFinished); } void VGiteeImageHosting::giteeImageBedAuthFinished() { switch (reply->error()) { case QNetworkReply::NoError: { QByteArray bytes = reply->readAll(); qDebug() << "Authentication completed"; qDebug() << "The current article path is: " << m_file->fetchPath(); imageBasePath = m_file->fetchBasePath(); newFileContent = m_file->getContent(); QVector images = VUtils::fetchImagesFromMarkdownFile(m_file, ImageLink::LocalRelativeInternal); QApplication::restoreOverrideCursor(); // Recovery pointer if(images.size() > 0) { proDlg = new QProgressDialog(tr("Uploading images to Gitee..."), tr("Abort"), 0, images.size(), nullptr); proDlg->setWindowModality(Qt::WindowModal); proDlg->setWindowTitle(tr("Uploading Images To Gitee")); proDlg->setMinimumDuration(1); uploadImageCount = images.size(); uploadImageCountIndex = uploadImageCount; for(int i=0;igetName() << " No images to upload"; QString info = tr("No local images to upload: %1").arg(m_file->getName()); QMessageBox::information(nullptr, tr("Gitee Image Hosting"), info); } break; } default: { QApplication::restoreOverrideCursor(); // Recovery pointer int httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if(httpStatus == 401 || httpStatus == 404) { qDebug() << "Authentication failed: " << reply->errorString() << " error " << reply->error(); QString info = tr("Authentication failed: ") + reply->errorString() + tr("\n\nPlease check your Gitee Image Hosting parameters!"); QMessageBox::warning(nullptr, tr("Gitee Image Hosting"), info); } else { qDebug() << "Network error: " << reply->errorString() << " error " << reply->error(); QString info = tr("Network error: ") + reply->errorString() + tr("\n\nPlease check your network!"); QMessageBox::warning(nullptr, tr("Gitee Image Hosting"), info); } } } } void VGiteeImageHosting::giteeImageBedUploadManager() { uploadImageCountIndex--; QString imageToUpload; QMapIterator it(imageUrlMap); while(it.hasNext()) { it.next(); if(it.value() == "") { imageToUpload = it.key(); proDlg->setValue(uploadImageCount - 1 - uploadImageCountIndex); proDlg->setLabelText(tr("Uploading image: %1").arg(imageToUpload)); break; } } if(imageToUpload == "") { qDebug() << "All images have been uploaded"; giteeImageBedReplaceLink(newFileContent, m_file->fetchPath()); return; } if(g_config->getGiteePersonalAccessToken().isEmpty() || g_config->getGiteeReposName().isEmpty() || g_config->getGiteeUserName().isEmpty()) { qDebug() << "Please configure the Gitee image hosting first!"; QMessageBox::warning(nullptr, tr("Gitee Image Hosting"), tr("Please configure the Gitee image hosting first!")); imageUrlMap.clear(); return; } QString path = imageBasePath + QDir::separator(); path += imageToUpload; giteeImageBedUploadImage(g_config->getGiteeUserName(), g_config->getGiteeReposName(), path, g_config->getGiteePersonalAccessToken()); } void VGiteeImageHosting::giteeImageBedUploadImage(const QString &p_username, const QString &p_repository, const QString &p_imagePath, const QString &p_token) { QFileInfo fileInfo(p_imagePath.toLocal8Bit()); if(!fileInfo.exists()) { qDebug() << "The picture does not exist in this path: " << p_imagePath.toLocal8Bit(); QString info = tr("The picture does not exist in this path: ") + p_imagePath.toLocal8Bit(); QMessageBox::warning(nullptr, tr("Gitee Image Hosting"), info); imageUrlMap.clear(); if(imageUploaded) { giteeImageBedReplaceLink(newFileContent, m_file->fetchPath()); } return; } QString fileSuffix = fileInfo.suffix(); // file extension QString fileName = fileInfo.fileName(); // filename QString uploadUrl; // Image upload URL uploadUrl = "https://gitee.com/api/v5/repos/" + p_username + "/" + p_repository + "/contents/" + QString::number(QDateTime::currentDateTime().toTime_t()) + "_" + fileName; if(fileSuffix != QString::fromLocal8Bit("jpg") && fileSuffix != QString::fromLocal8Bit("png") && fileSuffix != QString::fromLocal8Bit("gif")) { qDebug() << "Unsupported type..."; QString info = tr("Unsupported type: ") + fileSuffix; QMessageBox::warning(nullptr, tr("Gitee Image Hosting"), info); imageUrlMap.clear(); if(imageUploaded) { giteeImageBedReplaceLink(newFileContent, m_file->fetchPath()); } return; } QNetworkRequest request; QUrl url = QUrl(uploadUrl); request.setUrl(url); request.setRawHeader("Content-Type", "application/json;charset=UTF-8"); if(reply != Q_NULLPTR) { reply->deleteLater(); } QString param = giteeImageBedGenerateParam(p_imagePath, p_token); QByteArray postData; postData.append(param); reply = manager.post(request, postData); qDebug() << "Start uploading images: " + p_imagePath + " Waiting for upload to complete"; uploadImageStatus = true; currentUploadImage = p_imagePath; connect(reply, &QNetworkReply::finished, this, &VGiteeImageHosting::giteeImageBedUploadFinished); } void VGiteeImageHosting::giteeImageBedUploadFinished() { if (proDlg->wasCanceled()) { qDebug() << "User stops uploading"; reply->abort(); // Stop network request imageUrlMap.clear(); // The ones that have been uploaded successfully before still need to stay if(imageUploaded) { giteeImageBedReplaceLink(newFileContent, m_file->fetchPath()); } return; } switch (reply->error()) { case QNetworkReply::NoError: { QByteArray bytes = reply->readAll(); int httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if(httpStatus == 201) { qDebug() << "Upload success"; QString downloadUrl; QString imageName; QJsonDocument doucment = QJsonDocument::fromJson(bytes); if (!doucment.isNull()) { if (doucment.isObject()) { QJsonObject object = doucment.object(); if (object.contains("content")) { QJsonValue value = object.value("content"); if (value.isObject()) { QJsonObject obj = value.toObject(); if (obj.contains("download_url")) { QJsonValue value = obj.value("download_url"); if (value.isString()) { downloadUrl = value.toString(); qDebug() << "json decode: download_url : " << downloadUrl; imageUploaded = true; // On behalf of successfully uploaded images proDlg->setValue(uploadImageCount); } } if(obj.contains("name")) { QJsonValue value = obj.value("name"); if(value.isString()) { imageName = value.toString(); } } // Traverse key in imageurlmap QList klist = imageUrlMap.keys(); QString temp; for(int i=0;ifetchPath()); } QString info = tr("JSON decode error. Please contact the developer."); QMessageBox::warning(nullptr, tr("Gitee Image Hosting"), info); } }else{ // If status is not 201, it means there is a problem. delete proDlg; imageUrlMap.clear(); qDebug() << "Upload failure"; if(imageUploaded) { giteeImageBedReplaceLink(newFileContent, m_file->fetchPath()); } QString info = tr("Gitee status code != 201. Please contact the developer."); QMessageBox::warning(nullptr, tr("Gitee Image Hosting"), info); } break; } default: { delete proDlg; imageUrlMap.clear(); qDebug()<<"network error: " << reply->errorString() << " error " << reply->error(); QByteArray bytes = reply->readAll(); qDebug() << bytes; int httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); qDebug() << "status: " << httpStatus; if(imageUploaded) { giteeImageBedReplaceLink(newFileContent, m_file->fetchPath()); } QString info = tr("Uploading ") + currentUploadImage + tr(" \n\nNetwork error: ") + reply->errorString() + tr("\n\nPlease check the network or image size"); QMessageBox::warning(nullptr, tr("Gitee Image Hosting"), info); } } } void VGiteeImageHosting::giteeImageBedReplaceLink(QString p_fileContent, const QString p_filePath) { Q_UNUSED(p_filePath); // This function must be executed when the upload is completed or fails in the middle. if(!g_config->getGiteeKeepImgScale()) { // delete image scale p_fileContent.replace(QRegExp("\\s+=\\d+x"),""); } if(!g_config->getGiteeDoNotReplaceLink()) { // Write content to file. m_editor->setContent(p_fileContent, true); } else { VClipboardUtils::setTextToClipboard(QApplication::clipboard(), p_fileContent); emit m_editor->object()->statusMessage(tr("Copied contents with new image links")); } // Reset. imageUrlMap.clear(); imageUploaded = false; } QString VGiteeImageHosting::giteeImageBedGenerateParam(const QString &p_imagePath, const QString &p_token) { // According to the requirements of GitHub interface, pictures must be in Base64 format. // Image to base64. QByteArray hexed; QFile imgFile(p_imagePath); imgFile.open(QIODevice::ReadOnly); hexed = imgFile.readAll().toBase64(); QString imgBase64 = hexed; QJsonObject json; json.insert("access_token", p_token); json.insert("message", QString("updatetest")); json.insert("content", imgBase64); QJsonDocument document; document.setObject(json); QByteArray byteArray = document.toJson(QJsonDocument::Compact); QString jsonStr(byteArray); return jsonStr; } void VGiteeImageHosting::setEditor(VEditor *p_editor) { m_editor = p_editor; } VWechatImageHosting::VWechatImageHosting(VFile *p_file, QObject *p_parent) :QObject(p_parent), m_file(p_file) { reply = Q_NULLPTR; imageUploaded = false; } void VWechatImageHosting::handleUploadImageToWechatRequested() { qDebug() << "Start processing image upload request to Wechat"; QString appid = g_config->getWechatAppid(); QString secret = g_config->getWechatSecret(); if(appid.isEmpty() || secret.isEmpty()) { qDebug() << "Please configure the Wechat image hosting first!"; QMessageBox::warning(nullptr, tr("Wechat Image Hosting"), tr("Please configure the Wechat image hosting first!")); return; } authenticateWechatImageHosting(appid, secret); } void VWechatImageHosting::authenticateWechatImageHosting(const QString p_appid, const QString p_secret) { qDebug() << "Start certification"; // Set the mouse to wait QApplication::setOverrideCursor(Qt::WaitCursor); QNetworkRequest request; QString auth_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + p_appid.toLocal8Bit() + "&secret=" + p_secret.toLocal8Bit(); QUrl url = QUrl(auth_url); request.setUrl(url); if(reply != Q_NULLPTR) { reply->deleteLater(); } reply = manager.get(request); connect(reply, &QNetworkReply::finished, this, &VWechatImageHosting::wechatImageBedAuthFinished); } void VWechatImageHosting::wechatImageBedAuthFinished() { switch (reply->error()) { case QNetworkReply::NoError: { QByteArray bytes = reply->readAll(); QJsonDocument document = QJsonDocument::fromJson(bytes); if(!document.isNull()) { if(document.isObject()){ QJsonObject object = document.object(); if(object.contains("access_token")) { QJsonValue value = object.value("access_token"); if(value.isString()) { qDebug() << "Authentication successful, get token"; // Parsing token. wechatAccessToken = value.toString(); qDebug() << "The current article path is: " << m_file->fetchPath(); imageBasePath = m_file->fetchBasePath(); newFileContent = m_file->getContent(); QVector images = VUtils::fetchImagesFromMarkdownFile(m_file,ImageLink::LocalRelativeInternal); QApplication::restoreOverrideCursor(); // Recovery pointer if(images.size() > 0) { proDlg = new QProgressDialog(tr("Uploading images to Wechat..."), tr("Abort"), 0, images.size(), nullptr); proDlg->setWindowModality(Qt::WindowModal); proDlg->setWindowTitle(tr("Uploading Images To Wechat")); proDlg->setMinimumDuration(1); uploadImageCount = images.size(); uploadImageCountIndex = uploadImageCount; for(int i=0;igetName() << " No pictures to upload"; QString info = tr("No local images to upload: %1").arg(m_file->getName()); QMessageBox::information(nullptr, tr("Wechat Image Hosting"), info); } } } else { qDebug() << "Authentication failed"; QString string = bytes; qDebug() << string; // You can refine the error here. QApplication::restoreOverrideCursor(); if(string.contains("invalid ip")) { QString ip = string.split(" ")[2]; QClipboard *board = QApplication::clipboard(); board->setText(ip); QMessageBox::warning(nullptr, tr("Wechat Image Hosting"), tr("Your ip address was set to the Clipboard!") + tr("\nPlease add the IP address: ") + ip + tr(" to the Wechat ip whitelist!")); } else { QMessageBox::warning(nullptr, tr("Wechat Image Hosting"), tr("Please check your Wechat Image Hosting parameters!\n") + string); } return; } } } else { delete proDlg; imageUrlMap.clear(); qDebug() << "Resolution failure!"; qDebug() << "Resolution failure's json: " << bytes; QString info = tr("JSON decode error. Please contact the developer."); QMessageBox::warning(nullptr, tr("Wechat Image Hosting"), info); } break; } default: { QApplication::restoreOverrideCursor(); qDebug() << "Network error: " << reply->errorString() << " error " << reply->error(); QString info = tr("Network error: ") + reply->errorString() + tr("\n\nPlease check your network!"); QMessageBox::warning(nullptr, tr("Wechat Image Hosting"), info); } } } void VWechatImageHosting::wechatImageBedUploadManager() { uploadImageCountIndex--; QString image_to_upload = ""; QMapIterator it(imageUrlMap); while(it.hasNext()) { it.next(); if(it.value() == "") { image_to_upload = it.key(); proDlg->setValue(uploadImageCount - 1 - uploadImageCountIndex); proDlg->setLabelText(tr("Uploading image: %1").arg(image_to_upload)); break; } } if(image_to_upload == "") { qDebug() << "All pictures have been uploaded"; // Copy content to clipboard. wechatImageBedReplaceLink(newFileContent, m_file->fetchPath()); return; } QString path = imageBasePath + QDir::separator(); path += image_to_upload; currentUploadRelativeImagePah = image_to_upload; wechatImageBedUploadImage(path, wechatAccessToken); } void VWechatImageHosting::wechatImageBedUploadImage(const QString p_imagePath, const QString p_token) { qDebug() << "To deal with: " << p_imagePath; QFileInfo fileInfo(p_imagePath.toLocal8Bit()); if(!fileInfo.exists()) { delete proDlg; imageUrlMap.clear(); qDebug() << "The picture does not exist in this path: " << p_imagePath.toLocal8Bit(); QString info = tr("The picture does not exist in this path: ") + p_imagePath.toLocal8Bit(); QMessageBox::warning(nullptr, tr("Wechat Image Hosting"), info); return; } QString file_suffix = fileInfo.suffix(); // File extension. QString file_name = fileInfo.fileName(); // Filename. if(file_suffix != QString::fromLocal8Bit("jpg") && file_suffix != QString::fromLocal8Bit("png")) { delete proDlg; imageUrlMap.clear(); qDebug() << "Unsupported type..."; QString info = tr("Unsupported type: ") + file_suffix; QMessageBox::warning(nullptr, tr("Wechat Image Hosting"), info); return; } qint64 file_size = fileInfo.size(); // Unit is byte. qDebug() << "Image size: " << file_size; if(file_size > 1024*1024) { delete proDlg; imageUrlMap.clear(); qDebug() << "The size of the picture is more than 1M"; QString info = tr("The size of the picture is more than 1M! Wechat API does not support!"); QMessageBox::warning(nullptr, tr("Wechat Image Hosting"), info); return; } QString upload_img_url = "https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token=" + p_token; QNetworkRequest request; request.setUrl(upload_img_url); if(reply != Q_NULLPTR) { reply->deleteLater(); } QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); QHttpPart imagePart; imagePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/png")); QString filename = p_imagePath.split(QDir::separator()).last(); QString contentVariant = QString("form-data; name=\"media\"; filename=\"%1\";").arg(filename); imagePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant(contentVariant)); QFile *file = new QFile(p_imagePath); if(!file->open(QIODevice::ReadOnly)) { qDebug() << "File open failed"; } imagePart.setBodyDevice(file); file->setParent(multiPart); multiPart->append(imagePart); // Set boundary // Because boundary is quoted by QNetworkAccessManager, the wechat api is not recognized... QByteArray m_boundary; m_boundary.append("multipart/form-data; boundary="); m_boundary.append(multiPart->boundary()); request.setRawHeader(QByteArray("Content-Type"), m_boundary); reply = manager.post(request, multiPart); multiPart->setParent(reply); qDebug() << "Start uploading images: " + p_imagePath + " Waiting for upload to complete"; uploadImageStatus=true; currentUploadImage = p_imagePath; connect(reply, &QNetworkReply::finished, this, &VWechatImageHosting::wechatImageBedUploadFinished); } void VWechatImageHosting::wechatImageBedUploadFinished() { if(proDlg->wasCanceled()) { qDebug() << "User stops uploading"; reply->abort(); // If the upload was successful, don't use it!!! imageUrlMap.clear(); return; } switch (reply->error()) { case QNetworkReply::NoError: { QByteArray bytes = reply->readAll(); QJsonDocument document = QJsonDocument::fromJson(bytes); if(!document.isNull()) { if(document.isObject()) { QJsonObject object = document.object(); if(object.contains("url")) { QJsonValue value = object.value("url"); if(value.isString()) { qDebug() << "Authentication successful, get online link"; imageUploaded = true; proDlg->setValue(uploadImageCount); imageUrlMap.insert(currentUploadRelativeImagePah, value.toString()); newFileContent.replace(currentUploadRelativeImagePah, value.toString()); // Start calling the method. // Whether the value in the map is empty determines whether to stop wechatImageBedUploadManager(); } } else { delete proDlg; imageUrlMap.clear(); qDebug() << "Upload failure: "; QString error = bytes; qDebug() << bytes; QString info = tr("Upload failed! Please contact the developer."); QMessageBox::warning(nullptr, tr("Wechat Image Hosting"), info); } } } else { delete proDlg; imageUrlMap.clear(); qDebug() << "Resolution failure!"; qDebug() << "Resolution failure's json: " << bytes; QString info = tr("JSON decode error. Please contact the developer."); QMessageBox::warning(nullptr, tr("Wechat Image Hosting"), info); } break; } default: { delete proDlg; qDebug()<<"Network error: " << reply->errorString() << " error " << reply->error(); QString info = tr("Uploading ") + currentUploadImage + tr(" \n\nNetwork error: ") + reply->errorString() + tr("\n\nPlease check the network or image size"); QMessageBox::warning(nullptr, tr("Wechat Image Hosting"), info); } } } void VWechatImageHosting::wechatImageBedReplaceLink(QString &p_fileContent, const QString &p_filePath) { Q_UNUSED(p_filePath); if(!g_config->getWechatKeepImgScale()) { // delete image scale p_fileContent.replace(QRegExp("\\s+=\\d+x"),""); } if(!g_config->getWechatDoNotReplaceLink()) { // Write content to file. m_editor->setContent(p_fileContent, true); } VClipboardUtils::setTextToClipboard(QApplication::clipboard(), p_fileContent); emit m_editor->object()->statusMessage(tr("Copied contents with new image links")); QString url = g_config->getMarkdown2WechatToolUrl(); if(!url.isEmpty()) { QMessageBox::StandardButton result; result = QMessageBox::question(nullptr, tr("Wechat Image Hosting"), tr("Contents with new image links are copied. ") + tr("Do you want to open the link of Markdown2Wechat tool?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if(result == QMessageBox::Yes) { QDesktopServices::openUrl(QUrl(url)); } } // Reset. imageUrlMap.clear(); imageUploaded = false; } void VWechatImageHosting::setEditor(VEditor *p_editor) { m_editor = p_editor; } VTencentImageHosting::VTencentImageHosting(VFile *p_file, QObject *p_parent) :QObject(p_parent), m_file(p_file) { reply = Q_NULLPTR; imageUploaded = false; } void VTencentImageHosting::handleUploadImageToTencentRequested() { qDebug() << "Start processing the image upload request to Tencent Cloud"; if(g_config->getTencentAccessDomainName().isEmpty() || g_config->getTencentSecretId().isEmpty() || g_config->getTencentSecretKey().isEmpty()) { qDebug() << "Please configure the Tencent Cloud image hosting first!"; QMessageBox::warning(nullptr, tr("Tencent Cloud Image Hosting"), tr("Please configure the Tencent Cloud image hosting first!")); return; } findAndStartUploadImage(); } void VTencentImageHosting::findAndStartUploadImage() { qDebug() << "The current article path is: " << m_file->fetchPath(); imageBasePath = m_file->fetchBasePath(); newFileContent = m_file->getContent(); QVector images = VUtils::fetchImagesFromMarkdownFile(m_file,ImageLink::LocalRelativeInternal); if(images.size() > 0) { proDlg = new QProgressDialog(tr("Uploading images to Tencent Cloud..."), tr("Abort"), 0, images.size(), nullptr); proDlg->setWindowModality(Qt::WindowModal); proDlg->setWindowTitle(tr("Uploading Images To Tencent Cloud")); proDlg->setMinimumDuration(1); uploadImageCount = images.size(); uploadImageCountIndex = uploadImageCount; for(int i=0;igetName() << " No images to upload"; QString info = tr("No local images to upload: %1").arg(m_file->getName()); QMessageBox::information(nullptr, tr("Tencent Cloud Image Hosting"), info); } } void VTencentImageHosting::tencentImageBedUploadManager() { uploadImageCountIndex--; QString image_to_upload = ""; QMapIterator it(imageUrlMap); while(it.hasNext()) { it.next(); if(it.value() == "") { image_to_upload = it.key(); proDlg->setValue(uploadImageCount - 1 - uploadImageCountIndex); proDlg->setLabelText(tr("Uploading image: %1").arg(image_to_upload)); break; } } if(image_to_upload == "") { qDebug() << "All pictures have been uploaded"; tencentImageBedReplaceLink(newFileContent, m_file->fetchPath()); return; } QString path = imageBasePath + QDir::separator(); path += image_to_upload; currentUploadRelativeImagePah = image_to_upload; tencentImageBedUploadImage(path, g_config->getTencentAccessDomainName(), g_config->getTencentSecretId(), g_config->getTencentSecretKey()); } void VTencentImageHosting::tencentImageBedUploadImage(const QString &p_imagePath, const QString &p_accessDomainName, const QString &p_secretId, const QString &p_secretKey) { QFileInfo fileInfo(p_imagePath.toLocal8Bit()); if(!fileInfo.exists()) { qDebug() << "The picture does not exist in this path: " << p_imagePath.toLocal8Bit(); QString info = tr("The picture does not exist in this path: ") + p_imagePath.toLocal8Bit(); QMessageBox::warning(nullptr, tr("Tencent Cloud Image Hosting"), info); imageUrlMap.clear(); if(imageUploaded) { tencentImageBedReplaceLink(newFileContent, m_file->fetchPath()); } return; } QString fileSuffix = fileInfo.suffix(); // file extension QString fileName = fileInfo.fileName(); // filename QString uploadUrl; // Image upload URL new_file_name = QString::number(QDateTime::currentDateTime().toTime_t()) +"_" + fileName; if(fileSuffix != QString::fromLocal8Bit("jpg") && fileSuffix != QString::fromLocal8Bit("png")) { qDebug() << "Unsupported type..."; QString info = tr("Unsupported type: ") + fileSuffix; QMessageBox::warning(nullptr, tr("Tencent Cloud Image Hosting"), info); imageUrlMap.clear(); if(imageUploaded) { tencentImageBedReplaceLink(newFileContent, m_file->fetchPath()); } return; } QByteArray postData = getImgContent(p_imagePath); QNetworkRequest request; uploadUrl = "https://" + p_accessDomainName + "/" + new_file_name; request.setRawHeader(QByteArray("Host"), p_accessDomainName.toUtf8()); if(fileSuffix == QString::fromLocal8Bit("jpg")) { request.setHeader(QNetworkRequest::ContentTypeHeader, "image/jpeg"); } else if(fileSuffix == QString::fromLocal8Bit("png")) { request.setHeader(QNetworkRequest::ContentTypeHeader, "image/png"); } request.setHeader(QNetworkRequest::ContentLengthHeader, postData.size()); QString str = getAuthorizationString(p_secretId, p_secretKey, new_file_name); request.setRawHeader(QByteArray("Authorization"), str.toUtf8()); request.setRawHeader(QByteArray("Connection"), QByteArray("close")); request.setUrl(QUrl(uploadUrl)); if(reply != Q_NULLPTR) { reply->deleteLater(); } reply = manager.put(request, postData); qDebug() << "Start uploading images: " + p_imagePath + " Waiting for upload to complete"; uploadImageStatus = true; currentUploadImage = p_imagePath; connect(reply, &QNetworkReply::finished, this, &VTencentImageHosting::tencentImageBedUploadFinished); } void VTencentImageHosting::tencentImageBedUploadFinished() { if(proDlg->wasCanceled()) { qDebug() << "User stops uploading"; reply->abort(); // Stop network request imageUrlMap.clear(); // The ones that have been uploaded successfully before still need to stay if(imageUploaded) { tencentImageBedReplaceLink(newFileContent, m_file->fetchPath()); } return; } switch (reply->error()) { case QNetworkReply::NoError: { QByteArray bytes = reply->readAll(); QString replyContent = bytes; if(replyContent.size() > 0) { // If there is a value returned, it means there is a problem. qDebug() << "Upload failure"; qDebug() << replyContent; delete proDlg; imageUrlMap.clear(); if(imageUploaded) { tencentImageBedReplaceLink(newFileContent, m_file->fetchPath()); } QMessageBox::warning(nullptr, tr("Tencent Cloud Image Hosting"), replyContent); } else { qDebug() << "Upload success"; imageUploaded = true; // On behalf of successfully uploaded images proDlg->setValue(uploadImageCount); QString downloadUrl = "https://" + g_config->getTencentAccessDomainName() + "/" + new_file_name; imageUrlMap.insert(currentUploadRelativeImagePah, downloadUrl); newFileContent.replace(currentUploadRelativeImagePah, downloadUrl); // Start calling the method. // Whether the value in the map is empty determines whether to stop. tencentImageBedUploadManager(); } break; } default: { delete proDlg; imageUrlMap.clear(); if(imageUploaded) { tencentImageBedReplaceLink(newFileContent, m_file->fetchPath()); } qDebug()<<"network error: " << reply->error(); if(reply->error() == 3) // HostNotFoundError { QString info = tr(" \n\nNetwork error: HostNotFoundError") + tr("\n\nPlease check your network"); QMessageBox::warning(nullptr, tr("Tencent Cloud Image Hosting"), info); } else { QByteArray bytes = reply->readAll(); QString replyContent = bytes; qDebug() << replyContent; QString errorCode; QString errorMessage; QRegExp rc("([a-zA-Z0-9 ]+)"); QRegExp rx("([a-zA-Z0-9 ]+)"); if(rc.indexIn(replyContent) != -1) { qDebug() << "Error Code: " + rc.cap(1); errorCode = rc.cap(1); } if(rx.indexIn(replyContent) != -1) { qDebug() << "Error Message: " + rx.cap(1); errorMessage = rx.cap(1); } QString info = tr("Uploading ") + currentUploadImage + "\n\n" + errorCode + "\n\n" + errorMessage; QMessageBox::warning(nullptr, tr("Tencent Cloud Image Hosting"), info); } } } } void VTencentImageHosting::tencentImageBedReplaceLink(QString &p_fileContent, const QString &p_filePath) { Q_UNUSED(p_filePath); if(!g_config->getTencentKeepImgScale()) { // delete image scale p_fileContent.replace(QRegExp("\\s+=\\d+x"),""); } if(!g_config->getTencentDoNotReplaceLink()) { // Write content to file. m_editor->setContent(p_fileContent, true); } else { VClipboardUtils::setTextToClipboard(QApplication::clipboard(), p_fileContent); emit m_editor->object()->statusMessage(tr("Copied contents with new image links")); } // Reset. imageUrlMap.clear(); imageUploaded = false; } QByteArray VTencentImageHosting::hmacSha1(const QByteArray &p_key, const QByteArray &p_baseString) { int blockSize = 64; QByteArray key = p_key; if (key.length() > blockSize) { key = QCryptographicHash::hash(key, QCryptographicHash::Sha1); } QByteArray innerPadding(blockSize, char(0x36)); QByteArray outerPadding(blockSize, char(0x5c)); for (int i = 0; i < key.length(); i++) { innerPadding[i] = innerPadding[i] ^ key.at(i); outerPadding[i] = outerPadding[i] ^ key.at(i); } QByteArray total = outerPadding; QByteArray part = innerPadding; part.append(p_baseString); total.append(QCryptographicHash::hash(part, QCryptographicHash::Sha1)); QByteArray hashed = QCryptographicHash::hash(total, QCryptographicHash::Sha1); return hashed.toHex(); } QString VTencentImageHosting::getAuthorizationString(const QString &p_secretId, const QString &p_secretKey, const QString &p_imgName) { unsigned int now=QDateTime::currentDateTime().toTime_t(); QString keyTime=QString::number(now) + ";" + QString::number(now+3600); QString SignKey=hmacSha1(p_secretKey.toUtf8(), keyTime.toUtf8()); QString HttpString="put\n/" + p_imgName + "\n\n\n"; QString StringToSign = "sha1\n" + keyTime + "\n" + QCryptographicHash::hash(HttpString.toUtf8(), QCryptographicHash::Sha1).toHex() + "\n"; QString signature=hmacSha1(SignKey.toUtf8(), StringToSign.toUtf8()); QString Authorization; Authorization = "q-sign-algorithm=sha1"; Authorization += "&q-ak="; Authorization += p_secretId; Authorization += "&q-sign-time="; Authorization += keyTime; Authorization += "&q-key-time="; Authorization += keyTime; Authorization += "&q-header-list="; Authorization += "&q-url-param-list="; Authorization += "&q-signature="; Authorization += signature; return Authorization; } QByteArray VTencentImageHosting::getImgContent(const QString &p_imagePath) { QFile file(p_imagePath); file.open(QIODevice::ReadOnly); QByteArray fdata = file.readAll(); file.close(); return fdata; } void VTencentImageHosting::setEditor(VEditor *p_editor) { m_editor = p_editor; }