1 /*
2 * Copyright © 2015-2016 Antti Lamminsalo
3 *
4 * This file is part of Orion.
5 *
6 * Orion is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * You should have received a copy of the GNU General Public License
12 * along with Orion. If not, see <http://www.gnu.org/licenses/>.
13 */
14
15 #include "jsonparser.h"
16 #include "../model/settingsmanager.h"
17
parseStreams(const QByteArray & data)18 PagedResult<Channel*> JsonParser::parseStreams(const QByteArray &data)
19 {
20 PagedResult<Channel*> out;
21
22 QJsonParseError error;
23 QJsonDocument doc = QJsonDocument::fromJson(data,&error);
24 if (error.error == QJsonParseError::NoError){
25 QJsonObject json = doc.object();
26
27 //Online streams
28 QJsonArray arr = json["streams"].toArray();
29 foreach (const QJsonValue &item, arr){
30 out.items.append(JsonParser::parseStreamJson(item.toObject(), true));
31 }
32
33 out.total = json["_total"].toInt();
34
35 //Caller must use request context to determine offline streams
36 }
37
38 return out;
39 }
40
parseStream(const QByteArray & data)41 Channel *JsonParser::parseStream(const QByteArray &data)
42 {
43 QJsonParseError error;
44 QJsonDocument doc = QJsonDocument::fromJson(data,&error);
45 if (error.error == QJsonParseError::NoError){
46 return parseStreamJson(doc.object(), false);
47 }
48 return new Channel();
49 }
50
parseStreamJson(const QJsonObject & json,const bool expectChannel)51 Channel* JsonParser::parseStreamJson(const QJsonObject &json, const bool expectChannel)
52 {
53 Channel* channel = new Channel();
54
55 QJsonObject jsonObj;
56
57 if (!jsonObj["stream"].isNull()) {
58 jsonObj = jsonObj["stream"].toObject();
59 } else {
60 jsonObj = json;
61 }
62
63 if (!jsonObj["preview"].isNull()){
64
65 QJsonObject preview = jsonObj["preview"].toObject();
66
67 if (!preview["large"].isNull()){
68 channel->setPreviewurl(preview["large"].toString());
69 }
70 }
71
72 if (!jsonObj["viewers"].isNull()){
73 channel->setViewers(jsonObj["viewers"].toInt());
74 }
75
76 if (!jsonObj["game"].isNull()){
77 channel->setGame(jsonObj["game"].toString());
78 }
79
80 if (!jsonObj["channel"].isNull()){
81
82 Channel *c = parseChannelJson(jsonObj["channel"].toObject());
83 channel->setServiceName(c->getServiceName());
84 channel->setId(c->getId());
85 channel->setName(c->getName());
86 channel->setLogourl(c->getLogourl());
87 channel->setInfo(c->getInfo());
88
89 delete c;
90 }
91 else if (expectChannel) {
92 qDebug() << "expected channel; stream will not have channel id to correlate";
93 }
94
95 channel->setOnline(true);
96
97 return channel;
98 }
99
parseGames(const QByteArray & data)100 QList<Game*> JsonParser::parseGames(const QByteArray &data)
101 {
102 QList<Game*> games;
103
104 QJsonParseError error;
105 QJsonDocument doc = QJsonDocument::fromJson(data,&error);
106 if (error.error == QJsonParseError::NoError) {
107 QJsonObject json = doc.object();
108
109 QString arg = (!json["top"].isNull() ? "top" : (!json["games"].isNull() ? "games" : ""));
110
111 if (!arg.isEmpty()){
112 QJsonArray arr = json[arg].toArray();
113 foreach (const QJsonValue &item, arr){
114 Game* game = parseGame(item.toObject());
115 if (!game->getName().isEmpty()){
116 games.append(game);
117 }
118 }
119 }
120 }
121
122 return games;
123 }
124
125
parseGame(const QJsonObject & json)126 Game* JsonParser::parseGame(const QJsonObject &json)
127 {
128 Game* game = new Game();
129
130 //From top games
131 if (json.contains("game") && !json["game"].isNull()){
132 const QJsonObject gameObj = json["game"].toObject();
133
134 if (!gameObj["_id"].isNull())
135 game->setId(gameObj["_id"].toInt());
136
137 if (!json["viewers"].isNull())
138 game->setViewers(json["viewers"].toInt());
139
140 if (!gameObj["name"].isNull())
141 game->setName(gameObj["name"].toString());
142
143 if (!gameObj["box"].isNull() && !gameObj["box"].toObject()["medium"].isNull())
144 game->setLogo(gameObj["box"].toObject()["medium"].toString());
145
146 if (!gameObj["logo"].isNull() && !gameObj["logo"].toObject()["medium"].isNull())
147 game->setPreview(gameObj["logo"].toObject()["medium"].toString());
148 }
149 //From games search
150 else {
151 if (!json["_id"].isNull())
152 game->setId(json["_id"].toInt());
153
154 if (!json["name"].isNull())
155 game->setName(json["name"].toString());
156
157 if (!json["viewers"].isNull())
158 game->setViewers(json["viewers"].toInt());
159
160 if (!json["box"].isNull() && !json["box"].toObject()["medium"].isNull())
161 game->setLogo(json["box"].toObject()["medium"].toString());
162
163 if (!json["logo"].isNull() && !json["logo"].toObject()["medium"].isNull())
164 game->setPreview(json["logo"].toObject()["medium"].toString());
165 }
166
167 return game;
168 }
169
parseChannel(const QByteArray & data)170 Channel* JsonParser::parseChannel(const QByteArray &data){
171 QJsonParseError error;
172 QJsonDocument doc = QJsonDocument::fromJson(data,&error);
173 if (error.error == QJsonParseError::NoError){
174 return parseChannelJson(doc.object());
175 }
176 return new Channel();
177 }
178
parseChannelJson(const QJsonObject & json)179 Channel* JsonParser::parseChannelJson(const QJsonObject &json)
180 {
181 Channel* channel = new Channel();
182
183 if (!json["name"].isNull()){
184
185 channel->setServiceName(json["name"].toString());
186
187 // qDebug() << "Parsing channel data for " << channel.getUriName();
188
189 if (!json["name"].isNull()){
190 channel->setServiceName(json["name"].toString());
191 }
192
193 if (!json["display_name"].isNull()){
194 channel->setName(json["display_name"].toString());
195 }
196
197 if (!json["status"].isNull()){
198 channel->setInfo(json["status"].toString());
199 }
200
201 if (!json["logo"].isNull()){
202 channel->setLogourl(json["logo"].toString());
203 }
204
205 if (!json["_id"].isNull()){
206 const QJsonValue & _id = json["_id"];
207 channel->setId(_id.isString() ? _id.toString().toInt() : static_cast<quint32>(_id.toDouble()));
208 }
209 }
210
211 return channel;
212 }
213
parseVod(const QJsonObject & json)214 Vod *JsonParser::parseVod(const QJsonObject &json)
215 {
216 Vod *vod = new Vod();
217
218 if (!json["_id"].isNull())
219 vod->setId(json["_id"].toString());
220
221 if (!json["preview"].isNull()) {
222 const QJsonValue & preview = json["preview"];
223 if (preview.isString()) {
224 vod->setPreview(preview.toString());
225 }
226 else if (preview.isObject()) {
227 const QJsonValue & previewUrl = preview.toObject()["large"];
228 if (previewUrl.isString()) {
229 vod->setPreview(previewUrl.toString());
230 }
231 }
232 }
233
234 if (!json["seek_previews_url"].isNull())
235 vod->setSeekPreviews(json["seek_previews_url"].toString());
236
237 if (!json["title"].isNull())
238 vod->setTitle(json["title"].toString());
239
240 if (!json["length"].isNull())
241 vod->setDuration(json["length"].toInt());
242
243 if (!json["game"].isNull())
244 vod->setGame(json["game"].toString());
245
246 if (!json["views"].isNull())
247 vod->setViews(json["views"].toInt());
248
249 if (!json["created_at"].isNull())
250 vod->setCreatedAt(json["created_at"].toString());
251
252 return vod;
253 }
254
parseChannels(const QByteArray & data)255 PagedResult<Channel*> JsonParser::parseChannels(const QByteArray &data)
256 {
257 PagedResult<Channel*> out;
258
259 QJsonParseError error;
260 QJsonDocument doc = QJsonDocument::fromJson(data,&error);
261 if (error.error == QJsonParseError::NoError){
262 QJsonObject json = doc.object();
263
264 QJsonArray arr = json["channels"].toArray();
265 foreach (const QJsonValue &item, arr){
266 out.items.append(JsonParser::parseChannelJson(item.toObject()));
267 }
268
269 out.total = json["_total"].toInt();
270 }
271
272 return out;
273 }
274
parseFavourites(const QByteArray & data)275 PagedResult<Channel *> JsonParser::parseFavourites(const QByteArray &data)
276 {
277 PagedResult<Channel *> out;
278
279 QJsonParseError error;
280 QJsonDocument doc = QJsonDocument::fromJson(data,&error);
281 if (error.error == QJsonParseError::NoError){
282 QJsonObject json = doc.object();
283
284 out.total = json["_total"].toInt();
285
286 QJsonArray arr = json["follows"].toArray();
287 foreach (const QJsonValue &item, arr){
288 out.items.append(JsonParser::parseChannelJson(item.toObject()["channel"].toObject()));
289 }
290 }
291
292 return out;
293 }
294
parseFeatured(const QByteArray & data)295 QList<Channel *> JsonParser::parseFeatured(const QByteArray &data)
296 {
297 QList<Channel*> channels;
298
299 QJsonParseError error;
300 QJsonDocument doc = QJsonDocument::fromJson(data,&error);
301 if (error.error == QJsonParseError::NoError){
302 QJsonObject json = doc.object();
303
304 if (!json["featured"].isNull()){
305 foreach (const QJsonValue &item, json["featured"].toArray()){
306 channels.append(JsonParser::parseStreamJson(item.toObject()["stream"].toObject(), true));
307 }
308 }
309 }
310
311 return channels;
312 }
313
parseVods(const QByteArray & data)314 QList<Vod *> JsonParser::parseVods(const QByteArray &data)
315 {
316 QList<Vod *> vods;
317
318 QJsonParseError error;
319 QJsonDocument doc = QJsonDocument::fromJson(data,&error);
320 if (error.error == QJsonParseError::NoError){
321 QJsonObject json = doc.object();
322
323 if (!json["videos"].isNull()){
324 foreach (const QJsonValue &item, json["videos"].toArray()){
325 vods.append(JsonParser::parseVod(item.toObject()));
326 }
327 }
328 }
329
330 return vods;
331 }
332
parseChannelStreamExtractionInfo(const QByteArray & data)333 QString JsonParser::parseChannelStreamExtractionInfo(const QByteArray &data)
334 {
335 QString url;
336
337 QJsonParseError error;
338 QJsonDocument doc = QJsonDocument::fromJson(data,&error);
339 if (error.error == QJsonParseError::NoError){
340 QJsonObject json = doc.object();
341
342 QString tokenData = json["token"].toString();
343
344 //Strip escape markings and spaces
345 //tokenData = tokenData.trimmed().remove("\\");
346
347 QString channel;
348
349 QJsonDocument tokenDoc = QJsonDocument::fromJson(tokenData.toUtf8(), &error);
350 if (error.error == QJsonParseError::NoError){
351 QJsonObject tokenJson = tokenDoc.object();
352 channel = tokenJson["channel"].toString();
353 }
354
355 QString sig = json["sig"].toString();
356
357 url = QString("http://usher.twitch.tv/api/channel/hls/%1").arg(channel + QString(".m3u8"))
358 + QString("?player=twitchweb")
359 + QString("&token=") + QUrl::toPercentEncoding(tokenData)
360 + QString("&sig=%1").arg(sig)
361 + QString("&allow_source=true&$allow_audio_only=true");
362 }
363
364 return url;
365 }
366
parseVodExtractionInfo(const QByteArray & data)367 QString JsonParser::parseVodExtractionInfo(const QByteArray &data)
368 {
369 QString url;
370
371 QJsonParseError error;
372 QJsonDocument doc = QJsonDocument::fromJson(data,&error);
373 if (error.error == QJsonParseError::NoError){
374 QJsonObject json = doc.object();
375
376 QString tokenData = json["token"].toString();
377 QString sig = json["sig"].toString();
378
379 //Strip escape markings and spaces
380 tokenData = tokenData.trimmed().remove("\\");
381
382 QString vod;
383
384 QJsonDocument tokenDoc = QJsonDocument::fromJson(tokenData.toUtf8(), &error);
385 if (error.error == QJsonParseError::NoError){
386 QJsonObject tokenJson = tokenDoc.object();
387 vod = QString::number(tokenJson["vod_id"].toInt());
388 }
389
390 url = QString("http://usher.twitch.tv/vod/%1").arg(vod)
391 + QString("?nauth=%1").arg(tokenData)
392 + QString("&nauthsig=%1").arg(sig)
393 + QString("&p=%1").arg(qrand() * 999999)
394 + "&type=any"
395 "&player=twitchweb"
396 "&allow_source=true"
397 "&allow_audio_only=true";
398 }
399
400 return url;
401 }
402
parseUser(const QByteArray & data)403 QPair<QString, quint64> JsonParser::parseUser(const QByteArray &data)
404 {
405 QString displayName;
406 quint64 userId = 0;
407 QJsonParseError error;
408 QJsonDocument doc = QJsonDocument::fromJson(data,&error);
409
410 if (error.error == QJsonParseError::NoError){
411 QJsonObject json = doc.object();
412 if (!json["name"].isNull())
413 displayName = json["name"].toString();
414 userId = json["_id"].toString().toULongLong();
415 }
416
417 return qMakePair(displayName, userId);
418 }
419
parseUsers(const QByteArray & data)420 QList<quint64> JsonParser::parseUsers(const QByteArray &data)
421 {
422 QList<quint64> out;
423
424 QJsonParseError error;
425 QJsonDocument doc = QJsonDocument::fromJson(data, &error);
426
427 if (error.error == QJsonParseError::NoError) {
428 QJsonObject json = doc.object();
429 for (const auto & user : json["users"].toArray()) {
430 auto userId = user.toObject()["_id"];
431 if (userId.isDouble()) {
432 out.append(static_cast<quint64>(userId.toDouble()));
433 }
434 else {
435 out.append(userId.toString().toULongLong());
436 }
437 }
438 }
439
440 return out;
441 }
442
parseEmoteSets(const QByteArray & data)443 QMap<int, QMap<int, QString>> JsonParser::parseEmoteSets(const QByteArray &data) {
444 QMap<int, QMap<int, QString>> out;
445
446 QJsonParseError error;
447 QJsonDocument doc = QJsonDocument::fromJson(data, &error);
448
449 //qDebug() << "parsing emote sets response" << data;
450
451 if (error.error == QJsonParseError::NoError) {
452 QJsonObject json = doc.object();
453 if (!json["emoticon_sets"].isNull()) {
454 auto emoticon_sets = json["emoticon_sets"].toObject();
455 for (auto emoticonSetEntry = emoticon_sets.begin(); emoticonSetEntry != emoticon_sets.end(); emoticonSetEntry++) {
456 auto emoticonSetID = emoticonSetEntry.key();
457 QMap<int, QString> curSetEmoticons;
458 auto emoticons = emoticonSetEntry.value().toArray();
459 for (auto emoticonEntry = emoticons.begin(); emoticonEntry != emoticons.end(); emoticonEntry++) {
460 auto emoticonObj = emoticonEntry->toObject();
461 auto id = emoticonObj["id"];
462 auto code = emoticonObj["code"];
463 if (id.isDouble() && code.isString()) {
464 curSetEmoticons.insert(id.toInt(), code.toString());
465 }
466 }
467 int setId = emoticonSetID.toInt();
468 //qDebug() << "saving set id" << setId;
469 out.insert(setId, curSetEmoticons);
470 }
471 }
472 }
473
474 return out;
475 }
476
parseChannelBadgeUrls(const QByteArray & data)477 QMap<QString, QMap<QString, QString>> JsonParser::parseChannelBadgeUrls(const QByteArray &data) {
478 QMap<QString, QMap<QString, QString>> out;
479
480 QJsonParseError error;
481 QJsonDocument doc = QJsonDocument::fromJson(data, &error);
482
483 if (error.error == QJsonParseError::NoError) {
484 QJsonObject json = doc.object();
485 for (auto badgeEntry = json.begin(); badgeEntry != json.end(); badgeEntry++) {
486 if (badgeEntry.value().isNull()) continue;
487 QMap<QString, QString> urls;
488 QJsonObject badgeEntryJson = badgeEntry.value().toObject();
489 for (auto urlEntry = badgeEntryJson.begin(); urlEntry != badgeEntryJson.end(); urlEntry++) {
490 urls.insert(urlEntry.key(), urlEntry.value().toString());
491 }
492 out.insert(badgeEntry.key(), urls);
493 }
494 }
495
496 return out;
497 }
498
convertJsonStringMap(const QJsonObject & obj)499 QMap<QString, QString> convertJsonStringMap(const QJsonObject & obj) {
500 QMap<QString, QString> out;
501
502 for (auto entry = obj.constBegin(); entry != obj.constEnd(); entry++) {
503 if (entry.value().isString()) {
504 out.insert(entry.key(), entry.value().toString());
505 }
506 }
507
508 return out;
509 }
510
parseBadgeUrlsBetaFormat(const QByteArray & data)511 QMap<QString, QMap<QString, QMap<QString, QString>>> JsonParser::parseBadgeUrlsBetaFormat(const QByteArray &data) {
512 QMap<QString, QMap<QString, QMap<QString, QString>>> out;
513
514 QJsonParseError error;
515 QJsonDocument doc = QJsonDocument::fromJson(data, &error);
516
517 if (error.error == QJsonParseError::NoError) {
518 QJsonObject json = doc.object();
519 if (!json["badge_sets"].isNull()) {
520 auto badge_sets = json["badge_sets"].toObject();
521 for (auto badge_set_entry = badge_sets.constBegin(); badge_set_entry != badge_sets.end(); badge_set_entry++) {
522 QString badge_set_name = badge_set_entry.key();
523
524 if (!badge_set_entry.value().isNull()) {
525 auto badge_set_json = badge_set_entry.value().toObject();
526 if (!badge_set_json["versions"].isNull()) {
527
528 QMap<QString, QMap<QString, QString>> loadedBadgeSet;
529
530 auto versions_json = badge_set_json["versions"].toObject();
531 for (auto version_entry = versions_json.constBegin(); version_entry != versions_json.constEnd(); version_entry++) {
532 QString version_str = version_entry.key();
533
534 if (version_entry.value().isObject()) {
535 loadedBadgeSet.insert(version_str, convertJsonStringMap(version_entry.value().toObject()));
536 }
537 }
538
539 out.insert(badge_set_name, loadedBadgeSet);
540
541 }
542
543 }
544 }
545 }
546 }
547
548 return out;
549 }
550
parseBitsData(const QByteArray & data,QMap<QString,QMap<QString,QString>> & outUrls,QMap<QString,QMap<QString,QString>> & outColors)551 void JsonParser::parseBitsData(const QByteArray &data, QMap<QString, QMap<QString, QString>> & outUrls, QMap<QString, QMap<QString, QString>> & outColors)
552 {
553 const QString BITS_THEME = "dark";
554 const QString BITS_TYPE = "animated";
555 const QString BITS_SIZE_LODPI = "1";
556 const QString BITS_SIZE_HIDPI = "2";
557
558 const QString BITS_SIZE = SettingsManager::getInstance()->hiDpi() ? BITS_SIZE_HIDPI : BITS_SIZE_LODPI;
559
560 QJsonParseError error;
561 QJsonDocument doc = QJsonDocument::fromJson(data, &error);
562
563 if (error.error == QJsonParseError::NoError) {
564 QJsonObject json = doc.object();
565
566 auto actions = json["actions"].toArray();
567 for (const auto & actionEntry : actions) {
568
569 QMap<QString, QString> actionUrlsMap;
570 QMap<QString, QString> actionColorsMap;
571
572 const QJsonObject & actionObj = actionEntry.toObject();
573 QString actionPrefix = actionObj["prefix"].toString();
574
575 const QJsonArray & tiers = actionObj["tiers"].toArray();
576 for (const auto & tierEntry : tiers) {
577 const QJsonObject & tierObj = tierEntry.toObject();
578
579 int minBits = tierObj["min_bits"].toInt();
580
581 const QString & url = tierObj["images"].toObject()[BITS_THEME].toObject()[BITS_TYPE].toObject()[BITS_SIZE].toString();
582 qDebug() << "bits url for" << actionPrefix << "minBits" << minBits << "is" << url;
583 actionUrlsMap.insert(QString::number(minBits), url);
584
585 const QString & color = tierObj["color"].toString();
586 actionColorsMap.insert(QString::number(minBits), color);
587 }
588
589 if (actionUrlsMap.size() > 0) {
590 outUrls.insert(actionPrefix, actionUrlsMap);
591 }
592
593 if (actionColorsMap.size() > 0) {
594 outColors.insert(actionPrefix, actionColorsMap);
595 }
596 }
597 }
598 }
599
parseTotal(const QByteArray & data)600 int JsonParser::parseTotal(const QByteArray &data)
601 {
602 int total = 0;
603 QJsonParseError error;
604 QJsonDocument doc = QJsonDocument::fromJson(data,&error);
605
606 if (error.error == QJsonParseError::NoError){
607 QJsonObject json = doc.object();
608 if (!json["_total"].isNull())
609 total = json["_total"].toInt();
610 }
611
612 return total;
613 }
614
unicodeLen(const QString & text)615 int unicodeLen(const QString & text) {
616 int out = 0;
617 for (auto i = 0; i < text.length(); i++) {
618 auto ch = text.at(i);
619 if ((ch < 0xd800) || (ch > 0xdbff)) {
620 ++out;
621 }
622 }
623 return out;
624 }
625
parseVodChatEntry(const QJsonValue & entry)626 ReplayChatMessage parseVodChatEntry(const QJsonValue &entry) {
627 ReplayChatMessage out;
628
629 const QJsonObject & attributes = entry.toObject();
630
631 auto msgId = attributes["_id"].toString();
632 out.id = msgId;
633
634 auto commenter = attributes["commenter"].toObject();
635
636 auto name = commenter["name"].toString();
637 out.from = name;
638 QString state = attributes["state"].toString();
639 bool deleted = state != QString("published");
640 out.deleted = deleted; // XXX other types to take as valid?
641
642 auto message = attributes["message"].toObject();
643 out.message = message["body"].toString();
644 auto channelId = attributes["channel_id"].toString();
645 out.room = channelId;
646 out.videoOffset = attributes["content_offset_seconds"].toDouble() * 1000.0;
647 out.timestamp = out.videoOffset;
648
649 auto source = attributes["source"].toString();
650 if (source == QString("chat")) {
651 out.command = "PRIVMSG";
652 }
653 else {
654 // XXX need some more guesses for these
655 qDebug() << "unknown message source" << source; // XXX remove
656 out.command = "PRIVMSG";
657 }
658
659 int unicodePos = 0;
660 auto fragments = message["fragments"].toArray();
661
662 QSet<int> emoteIdsSeen;
663
664 for (const auto & fragment : fragments) {
665 auto fragmentObj = fragment.toObject();
666 auto text = fragmentObj["text"].toString();
667
668 auto curTextUnicodeLen = unicodeLen(text);
669
670 if (!fragmentObj["emoticon"].isNull()) {
671 auto emoteObj = fragmentObj["emoticon"].toObject();
672
673 QString emotioconIDStr = emoteObj["emoticon_id"].toString();
674 int first = unicodePos;
675 int last = unicodePos + curTextUnicodeLen - 1; // XXX off by one?
676 int emoteId = emotioconIDStr.toInt();
677 if (emoteIdsSeen.constFind(emoteId) == emoteIdsSeen.constEnd()) {
678 emoteIdsSeen.insert(emoteId);
679 out.emoteList.append(emoteId);
680 }
681 out.emotePositionsMap.insert(first, qMakePair(last, emoteId));
682 }
683
684 unicodePos += curTextUnicodeLen;
685
686 }
687
688 // XXX tags collection stuff not hooked up yet
689 // system-msg
690
691 // @badges=staff/1,broadcaster/1,turbo/1;color=#008000;display-name=TWITCH_UserName;emotes=;mod=0;msg-id=resub;msg-param-months=6;room-id=1337;subscriber=1;system-msg=TWITCH_UserName\shas\ssubscribed\sfor\s6\smonths!;login=twitch_username;turbo=1;user-id=1337;user-type=staff :tmi.twitch.tv USERNOTICE #channel :Great stream -- keep it up!
692 QList<QString> badges;
693 for (const auto & badge : message["user_badges"].toArray()) {
694 auto badgeObj = badge.toObject();
695 auto badgeId = badgeObj["_id"].toString();
696 badges.append(badgeId + QString("/") + badgeObj["version"].toString());
697
698 // not sure if anything in the front end needs these as tags if we added the badges directly
699 if (badgeId == QString("moderator")) {
700 out.tags.insert("mod", true);
701 }
702
703 if (badgeId == QString("subscriber")) {
704 out.tags.insert("subscriber", true);
705 }
706
707 if (badgeId == QString("turbo")) {
708 out.tags.insert("turbo", true);
709 }
710 }
711
712 if (!badges.isEmpty()) {
713 out.tags.insert("badges", badges.join(","));
714 }
715
716 // most of this stuff is being populated for backward compatibility with things that currently operate on the tags
717 // and can be taken out if nothing uses it
718 out.tags.insert("msg-id", msgId);
719 out.tags.insert("display-name", commenter["display_name"].toString());
720 out.tags.insert("login", name);
721 out.tags.insert("color", message["user_color"].toString());
722 out.tags.insert("room-id", channelId);
723 out.tags.insert("user-id", commenter["_id"].toString());
724 out.tags.insert("user-type", commenter["type"].toString());
725
726 return out;
727 }
728
parseVodChatPiece(const QByteArray & data)729 ReplayChatPiece JsonParser::parseVodChatPiece(const QByteArray &data)
730 {
731 ReplayChatPiece out;
732 QJsonParseError error;
733 QJsonDocument doc = QJsonDocument::fromJson(data, &error);
734
735 if (error.error == QJsonParseError::NoError) {
736 QJsonObject json = doc.object();
737
738 if (!json["comments"].isNull() && json["comments"].isArray()) {
739 const QJsonArray & chatEntries = json["comments"].toArray();
740 for (const auto & entry : chatEntries) {
741 out.comments.append(parseVodChatEntry(entry));
742 }
743 }
744
745 out.next = json["_next"].toString();
746 out.prev = json["_prev"].toString();
747 }
748
749 return out;
750 }
751
parseChatterList(const QByteArray & data)752 QMap<QString, QList<QString>> JsonParser::parseChatterList(const QByteArray &data)
753 {
754 QMap<QString, QList<QString>> out;
755
756 QJsonParseError error;
757 QJsonDocument doc = QJsonDocument::fromJson(data, &error);
758
759 if (error.error == QJsonParseError::NoError) {
760 QJsonObject json = doc.object();
761
762 QJsonObject chatters = json["chatters"].toObject();
763
764 for (auto groupEntry = chatters.constBegin(); groupEntry != chatters.constEnd(); groupEntry++) {
765 QList<QString> groupChatters;
766 const QJsonArray & groupChattersJson = groupEntry.value().toArray();
767 for (const auto & chatter : groupChattersJson) {
768 groupChatters.append(chatter.toString());
769 }
770 out.insert(groupEntry.key(), groupChatters);
771 }
772 }
773
774 return out;
775
776 }
777
parseBlockList(const QByteArray & data)778 PagedResult<QString> JsonParser::parseBlockList(const QByteArray &data)
779 {
780 PagedResult<QString> out;
781
782 QJsonParseError error;
783 QJsonDocument doc = QJsonDocument::fromJson(data, &error);
784
785 if (error.error == QJsonParseError::NoError) {
786 QJsonObject json = doc.object();
787
788 out.total = json["_total"].toInt();
789
790 QJsonArray blocks = json["blocks"].toArray();
791
792 for (const auto & block : blocks) {
793 const auto & blockObj = block.toObject();
794 const auto & name = blockObj["user"].toObject()["name"].toString();
795 if (!name.isEmpty()) {
796 out.items.append(name);
797 }
798 }
799 }
800
801 return out;
802 }
803
804
parseBttvEmotesData(const QByteArray & data)805 QMap<QString, QString> JsonParser::parseBttvEmotesData(const QByteArray &data)
806 {
807 QMap<QString, QString> out;
808
809 QJsonParseError error;
810 QJsonDocument doc = QJsonDocument::fromJson(data, &error);
811
812 if (error.error == QJsonParseError::NoError) {
813
814 QJsonArray emotes;
815
816 if (doc.isArray()) {
817 // handle global emotes
818 emotes = doc.array();
819 } else {
820 // handle channel and shared emotes
821 QJsonObject obj = doc.object();
822 emotes = doc["sharedEmotes"].toArray();
823
824 // merge channelEmotes into sharedEmotes
825 for (const auto & emote : doc["channelEmotes"].toArray()) {
826 emotes.push_back(emote);
827 }
828 }
829
830 for (const auto & emote : emotes) {
831 const auto & emoteObj = emote.toObject();
832 const auto & id = emoteObj["id"].toString();
833 const auto & code = emoteObj["code"].toString();
834 if (!id.isEmpty() && !code.isEmpty()) {
835 out.insert(code, id);
836 }
837 }
838 }
839
840 return out;
841 }
842
parseVersion(const QByteArray & data)843 QPair<QString,QString> JsonParser::parseVersion(const QByteArray &data)
844 {
845 QJsonParseError error;
846 QJsonDocument doc = QJsonDocument::fromJson(data, &error);
847 QString version;
848 QString url;
849
850 if (error.error == QJsonParseError::NoError) {
851 QJsonObject json = doc.object();
852 version = json["name"].toString();
853 url = json["html_url"].toString();
854 }
855
856 return qMakePair<QString,QString>(version, url);
857 }