├── README.md ├── xgithub.cmake ├── xgithub.pri ├── LICENSE ├── xgithub.h └── xgithub.cpp /README.md: -------------------------------------------------------------------------------- 1 | # XGitHub 2 | 3 | GitHub API 4 | -------------------------------------------------------------------------------- /xgithub.cmake: -------------------------------------------------------------------------------- 1 | include_directories(${CMAKE_CURRENT_LIST_DIR}) 2 | 3 | set(XGITHUB_SOURCES 4 | ${XGITHUB_SOURCES} 5 | ${CMAKE_CURRENT_LIST_DIR}/xgithub.cpp 6 | ${CMAKE_CURRENT_LIST_DIR}/xgithub.h 7 | ) 8 | -------------------------------------------------------------------------------- /xgithub.pri: -------------------------------------------------------------------------------- 1 | QT += network 2 | 3 | INCLUDEPATH += $$PWD 4 | DEPENDPATH += $$PWD 5 | 6 | HEADERS += \ 7 | $$PWD/xgithub.h 8 | 9 | SOURCES += \ 10 | $$PWD/xgithub.cpp 11 | 12 | DISTFILES += \ 13 | $$PWD/README.md 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-2025 hors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /xgithub.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2020-2025 hors 2 | * 3 | * Permission is hereby granted, free of charge, to any person obtaining a copy 4 | * of this software and associated documentation files (the "Software"), to deal 5 | * in the Software without restriction, including without limitation the rights 6 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | * copies of the Software, and to permit persons to whom the Software is 8 | * furnished to do so, subject to the following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in all 11 | * copies or substantial portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | * SOFTWARE. 20 | */ 21 | #ifndef XGITHUB_H 22 | #define XGITHUB_H 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | class XGitHub : public QObject { 32 | Q_OBJECT 33 | 34 | public: 35 | struct RELEASE_RECORD { 36 | QString sName; 37 | QString sSrc; 38 | qint64 nSize; 39 | QDateTime dt; 40 | }; 41 | 42 | struct RELEASE_HEADER { 43 | bool bValid; 44 | bool bNetworkError; 45 | QString sName; 46 | QString sTag; 47 | QString sBody; 48 | QDateTime dt; 49 | QList listRecords; 50 | }; 51 | 52 | explicit XGitHub(const QString &sUserName, const QString &sRepoName, QObject *pParent = nullptr); 53 | ~XGitHub(); 54 | 55 | RELEASE_HEADER getLatestRelease(bool bPrerelease); 56 | RELEASE_HEADER getTagRelease(QString sTag); 57 | static QList getDownloadLinks(QString sString); 58 | void setCredentials(QString sUser, QString sToken); 59 | 60 | struct WEBFILE { 61 | bool bValid; 62 | QString sContent; 63 | QString sNetworkError; 64 | bool bRedirect; 65 | QString sRedirectUrl; 66 | }; 67 | 68 | static WEBFILE getWebFile(const QString &sUrl); 69 | 70 | private: 71 | RELEASE_HEADER _handleReleaseJson(QJsonObject jsonObject); 72 | RELEASE_HEADER _getRelease(const QString &sUrl); 73 | 74 | signals: 75 | void errorMessage(QString sText); 76 | 77 | private: 78 | QString m_sUserName; 79 | QString m_sRepoName; 80 | QString m_sAuthUser; 81 | QString m_sAuthToken; 82 | bool m_bIsStop; 83 | QSet m_stReplies; 84 | QNetworkAccessManager m_naManager; 85 | }; 86 | 87 | #endif // XGITHUB_H 88 | -------------------------------------------------------------------------------- /xgithub.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2020-2025 hors 2 | * 3 | * Permission is hereby granted, free of charge, to any person obtaining a copy 4 | * of this software and associated documentation files (the "Software"), to deal 5 | * in the Software without restriction, including without limitation the rights 6 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | * copies of the Software, and to permit persons to whom the Software is 8 | * furnished to do so, subject to the following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in all 11 | * copies or substantial portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | * SOFTWARE. 20 | */ 21 | #include "xgithub.h" 22 | 23 | XGitHub::XGitHub(const QString &sUserName, const QString &sRepoName, QObject *pParent) : QObject(pParent) 24 | { 25 | this->m_sUserName = sUserName; 26 | this->m_sRepoName = sRepoName; 27 | 28 | m_bIsStop = false; 29 | } 30 | 31 | XGitHub::~XGitHub() 32 | { 33 | m_bIsStop = true; 34 | 35 | QSetIterator i(m_stReplies); 36 | 37 | while (i.hasNext()) { 38 | QNetworkReply *pReply = i.next(); 39 | 40 | pReply->abort(); 41 | } 42 | // TODO Check 43 | // TODO wait 44 | } 45 | 46 | XGitHub::RELEASE_HEADER XGitHub::getTagRelease(QString sTag) 47 | { 48 | QString sURL = QString("https://api.github.com/repos/%1/%2/releases/tags/%3").arg(m_sUserName, m_sRepoName, sTag); 49 | 50 | return _getRelease(sURL); 51 | } 52 | 53 | XGitHub::RELEASE_HEADER XGitHub::getLatestRelease(bool bPrerelease) 54 | { 55 | QString sURL; 56 | 57 | if (!bPrerelease) { 58 | sURL = QString("https://api.github.com/repos/%1/%2/releases/latest").arg(m_sUserName, m_sRepoName); 59 | } else { 60 | sURL = QString("https://api.github.com/repos/%1/%2/releases").arg(m_sUserName, m_sRepoName); 61 | } 62 | 63 | return _getRelease(sURL); 64 | } 65 | 66 | QList XGitHub::getDownloadLinks(QString sString) 67 | { 68 | QList listResult; 69 | 70 | qint32 nCount = sString.count("]("); 71 | 72 | for (qint32 i = 0; i < nCount; i++) { 73 | QString sLink = sString.section("](", i + 1, i + 1); 74 | sLink = sLink.section(")", 0, 0); 75 | 76 | listResult.append(sLink); 77 | } 78 | 79 | return listResult; 80 | } 81 | 82 | XGitHub::RELEASE_HEADER XGitHub::_handleReleaseJson(QJsonObject jsonObject) 83 | { 84 | RELEASE_HEADER result = {}; 85 | 86 | result.bValid = true; 87 | result.sName = jsonObject["name"].toString(); 88 | result.sTag = jsonObject["tag_name"].toString(); 89 | result.sBody = jsonObject["body"].toString(); 90 | result.dt = QDateTime::fromString(jsonObject["published_at"].toString(), "yyyy-MM-ddThh:mm:ssZ"); 91 | 92 | QJsonArray jsonArray = jsonObject["assets"].toArray(); 93 | 94 | qint32 nCount = jsonArray.count(); 95 | 96 | for (qint32 i = 0; i < nCount; i++) { 97 | RELEASE_RECORD record = {}; 98 | 99 | QJsonObject _object = jsonArray.at(i).toObject(); 100 | 101 | record.sSrc = _object["browser_download_url"].toString(); 102 | record.nSize = _object["size"].toInt(); 103 | record.sName = _object["name"].toString(); 104 | record.dt = QDateTime::fromString(_object["updated_at"].toString(), "yyyy-MM-ddThh:mm:ssZ"); 105 | 106 | result.listRecords.append(record); 107 | } 108 | 109 | return result; 110 | } 111 | 112 | void XGitHub::setCredentials(QString sUser, QString sToken) 113 | { 114 | m_sAuthUser = sUser; 115 | m_sAuthToken = sToken; 116 | } 117 | 118 | XGitHub::WEBFILE XGitHub::getWebFile(const QString &sUrl) 119 | { 120 | WEBFILE result = {}; 121 | 122 | QNetworkAccessManager nam; 123 | QUrl url(sUrl); 124 | QNetworkRequest req(url); 125 | QNetworkReply *pReply = nam.get(req); 126 | QEventLoop loop; 127 | QObject::connect(pReply, SIGNAL(finished()), &loop, SLOT(quit())); 128 | 129 | if (!(pReply->isFinished())) { 130 | loop.exec(); 131 | } 132 | 133 | if (pReply->error() == QNetworkReply::NoError) { 134 | if (pReply->bytesAvailable()) { 135 | result.sContent = pReply->readAll(); 136 | result.bValid = true; 137 | } else { 138 | QString sRedirectUrl = pReply->attribute(QNetworkRequest::RedirectionTargetAttribute).toString(); 139 | 140 | if (sRedirectUrl != "") { 141 | result = getWebFile(sRedirectUrl); 142 | result.bRedirect = true; 143 | result.sRedirectUrl = sRedirectUrl; 144 | } 145 | } 146 | } else { 147 | result.bValid = false; 148 | result.sNetworkError = pReply->errorString(); 149 | } 150 | 151 | return result; 152 | } 153 | 154 | XGitHub::RELEASE_HEADER XGitHub::_getRelease(const QString &sUrl) 155 | { 156 | XGitHub::RELEASE_HEADER result = {}; 157 | 158 | QNetworkRequest req; 159 | req.setUrl(QUrl(QString(sUrl))); 160 | 161 | // Add credentials if supplied 162 | if (!m_sAuthUser.isEmpty()) { 163 | QString auth = m_sAuthUser + ":" + m_sAuthToken; 164 | auth = "Basic " + auth.toLocal8Bit().toBase64(); 165 | req.setRawHeader("Authorization", auth.toLocal8Bit()); 166 | } 167 | 168 | QNetworkReply *pReply = m_naManager.get(req); 169 | 170 | m_stReplies.insert(pReply); 171 | 172 | QEventLoop loop; 173 | QObject::connect(pReply, SIGNAL(finished()), &loop, SLOT(quit())); 174 | loop.exec(); 175 | 176 | if (!m_bIsStop) { 177 | if (!pReply->error()) { 178 | QByteArray baData = pReply->readAll(); 179 | QJsonDocument document = QJsonDocument::fromJson(baData); 180 | 181 | #ifdef QT_DEBUG 182 | QString strJson(document.toJson(QJsonDocument::Indented)); 183 | qDebug(strJson.toLatin1().data()); 184 | #endif 185 | 186 | if (document.isArray()) { 187 | QJsonArray jsArray = document.array(); 188 | 189 | if (jsArray.count()) { 190 | result = _handleReleaseJson(jsArray.at(0).toObject()); 191 | } 192 | } else { 193 | result = _handleReleaseJson(document.object()); 194 | } 195 | } else { 196 | QString sErrorString = pReply->errorString(); 197 | 198 | if (sErrorString.contains("server replied: rate limit exceeded\n")) { 199 | sErrorString += "Github has the limit is 60 requests per hour for unauthenticated users (and 5000 for authenticated users).\n\n"; 200 | sErrorString += "TRY AGAIN IN ONE HOUR!"; 201 | } 202 | 203 | emit errorMessage(sErrorString); 204 | 205 | #ifdef QT_DEBUG 206 | qDebug(sErrorString.toLatin1().data()); 207 | #endif 208 | 209 | result.bNetworkError = true; 210 | } 211 | } 212 | 213 | m_stReplies.remove(pReply); 214 | 215 | return result; 216 | } 217 | --------------------------------------------------------------------------------