From ee4b696fc5a03e03f75643fbbbedd55272a5d4ed Mon Sep 17 00:00:00 2001 From: taizan-hokuto <55448286+taizan-hokuto@users.noreply.github.com> Date: Thu, 23 Jul 2020 16:20:12 +0900 Subject: [PATCH 1/6] Add colors attribute --- .../processors/default/renderer/legacypaid.py | 2 +- .../processors/default/renderer/paidmessage.py | 15 +++++++++++++++ .../processors/default/renderer/paidsticker.py | 17 +++++++++++++++-- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/pytchat/processors/default/renderer/legacypaid.py b/pytchat/processors/default/renderer/legacypaid.py index ee238cf..dc6b2e4 100644 --- a/pytchat/processors/default/renderer/legacypaid.py +++ b/pytchat/processors/default/renderer/legacypaid.py @@ -12,4 +12,4 @@ class LiveChatLegacyPaidMessageRenderer(BaseRenderer): def get_message(self, renderer): message = (renderer["eventText"]["runs"][0]["text"] ) + ' / ' + (renderer["detailText"]["simpleText"]) - return message + return message, [message] diff --git a/pytchat/processors/default/renderer/paidmessage.py b/pytchat/processors/default/renderer/paidmessage.py index 9e69ab4..70bd055 100644 --- a/pytchat/processors/default/renderer/paidmessage.py +++ b/pytchat/processors/default/renderer/paidmessage.py @@ -4,6 +4,10 @@ from .base import BaseRenderer superchat_regex = re.compile(r"^(\D*)(\d{1,3}(,\d{3})*(\.\d*)*\b)$") +class Colors: + pass + + class LiveChatPaidMessageRenderer(BaseRenderer): def __init__(self, item): super().__init__(item, "superChat") @@ -18,6 +22,7 @@ class LiveChatPaidMessageRenderer(BaseRenderer): self.currency = currency.symbols[symbol]["fxtext"] if currency.symbols.get( symbol) else symbol self.bgColor = self.renderer.get("bodyBackgroundColor", 0) + self.colors = self.get_colors() def get_amountdata(self, renderer): amountDisplayString = renderer["purchaseAmountText"]["simpleText"] @@ -29,3 +34,13 @@ class LiveChatPaidMessageRenderer(BaseRenderer): symbol = "" amount = 0.0 return amountDisplayString, symbol, amount + + def get_colors(self): + colors = Colors() + colors.headerBackgroundColor = self.renderer.get("headerBackgroundColor", 0) + colors.headerTextColor = self.renderer.get("headerTextColor", 0) + colors.bodyBackgroundColor = self.renderer.get("bodyBackgroundColor", 0) + colors.bodyTextColor = self.renderer.get("bodyTextColor", 0) + colors.timestampColor = self.renderer.get("timestampColor", 0) + colors.authorNameTextColor = self.renderer.get("authorNameTextColor", 0) + return colors diff --git a/pytchat/processors/default/renderer/paidsticker.py b/pytchat/processors/default/renderer/paidsticker.py index b474e71..723eb88 100644 --- a/pytchat/processors/default/renderer/paidsticker.py +++ b/pytchat/processors/default/renderer/paidsticker.py @@ -4,6 +4,10 @@ from .base import BaseRenderer superchat_regex = re.compile(r"^(\D*)(\d{1,3}(,\d{3})*(\.\d*)*\b)$") +class Colors: + pass + + class LiveChatPaidStickerRenderer(BaseRenderer): def __init__(self, item): super().__init__(item, "superSticker") @@ -18,8 +22,9 @@ class LiveChatPaidStickerRenderer(BaseRenderer): self.currency = currency.symbols[symbol]["fxtext"] if currency.symbols.get( symbol) else symbol self.bgColor = self.renderer.get("moneyChipBackgroundColor", 0) - self.sticker = "https:" + \ - self.renderer["sticker"]["thumbnails"][0]["url"] + self.sticker = "".join(("https:", + self.renderer["sticker"]["thumbnails"][0]["url"])) + self.colors = self.get_colors() def get_amountdata(self, renderer): amountDisplayString = renderer["purchaseAmountText"]["simpleText"] @@ -31,3 +36,11 @@ class LiveChatPaidStickerRenderer(BaseRenderer): symbol = "" amount = 0.0 return amountDisplayString, symbol, amount + + def get_colors(self): + colors = Colors() + colors.moneyChipBackgroundColor = self.renderer.get("moneyChipBackgroundColor", 0) + colors.moneyChipTextColor = self.renderer.get("moneyChipTextColor", 0) + colors.backgroundColor = self.renderer.get("backgroundColor", 0) + colors.authorNameTextColor = self.renderer.get("authorNameTextColor", 0) + return colors From 6e6bb8e01951e909735d97361eee6b752fc4cc08 Mon Sep 17 00:00:00 2001 From: taizan-hokuto <55448286+taizan-hokuto@users.noreply.github.com> Date: Thu, 23 Jul 2020 16:20:38 +0900 Subject: [PATCH 2/6] Add tests --- tests/test_default_processor.py | 228 ++++++++++++++++++ .../testdata/default/newSponsor_current.json | 100 ++++++++ tests/testdata/default/newSponsor_lagacy.json | 82 +++++++ .../testdata/default/replay_member_text.json | 112 +++++++++ tests/testdata/default/superchat.json | 184 ++++++++++++++ tests/testdata/default/supersticker.json | 99 ++++++++ tests/testdata/default/textmessage.json | 79 ++++++ 7 files changed, 884 insertions(+) create mode 100644 tests/test_default_processor.py create mode 100644 tests/testdata/default/newSponsor_current.json create mode 100644 tests/testdata/default/newSponsor_lagacy.json create mode 100644 tests/testdata/default/replay_member_text.json create mode 100644 tests/testdata/default/superchat.json create mode 100644 tests/testdata/default/supersticker.json create mode 100644 tests/testdata/default/textmessage.json diff --git a/tests/test_default_processor.py b/tests/test_default_processor.py new file mode 100644 index 0000000..1ea7a3d --- /dev/null +++ b/tests/test_default_processor.py @@ -0,0 +1,228 @@ +import json +from pytchat.parser.live import Parser +from pytchat.processors.default.processor import DefaultProcessor + + +def test_textmessage(mocker): + '''text message''' + processor = DefaultProcessor() + parser = Parser(is_replay=False) + _json = _open_file("tests/testdata/default/textmessage.json") + + _, chatdata = parser.parse(parser.get_contents(json.loads(_json))) + data = { + "video_id": "", + "timeout": 7, + "chatdata": chatdata + } + + ret = processor.process([data]).items[0] + assert ret.chattype == "textMessage" + assert ret.id == "dummy_id" + assert ret.message == "dummy_message" + assert ret.timestamp == 1570678496000 + assert ret.datetime == "2019-10-10 12:34:56" + assert ret.author.name == "author_name" + assert ret.author.channelId == "author_channel_id" + assert ret.author.channelUrl == "http://www.youtube.com/channel/author_channel_id" + assert ret.author.imageUrl == "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s64-x-x-xx-xx-xx-c0xffffff/photo.jpg" + assert ret.author.badgeUrl == "" + assert ret.author.isVerified is False + assert ret.author.isChatOwner is False + assert ret.author.isChatSponsor is False + assert ret.author.isChatModerator is False + + +def test_textmessage_replay_member(mocker): + '''text message replay member''' + processor = DefaultProcessor() + parser = Parser(is_replay=True) + _json = _open_file("tests/testdata/default/replay_member_text.json") + + _, chatdata = parser.parse(parser.get_contents(json.loads(_json))) + data = { + "video_id": "", + "timeout": 7, + "chatdata": chatdata + } + + ret = processor.process([data]).items[0] + assert ret.chattype == "textMessage" + assert ret.type == "textMessage" + assert ret.id == "dummy_id" + assert ret.message == "dummy_message" + assert ret.messageEx == ["dummy_message"] + assert ret.timestamp == 1570678496000 + assert ret.datetime == "2019-10-10 12:34:56" + assert ret.elapsedTime == "1:23:45" + assert ret.author.name == "author_name" + assert ret.author.channelId == "author_channel_id" + assert ret.author.channelUrl == "http://www.youtube.com/channel/author_channel_id" + assert ret.author.imageUrl == "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s64-x-x-xx-xx-xx-c0xffffff/photo.jpg" + assert ret.author.badgeUrl == "https://yt3.ggpht.com/X=s16-c-k" + assert ret.author.isVerified is False + assert ret.author.isChatOwner is False + assert ret.author.isChatSponsor is True + assert ret.author.isChatModerator is False + + +def test_superchat(mocker): + '''superchat''' + processor = DefaultProcessor() + parser = Parser(is_replay=False) + _json = _open_file("tests/testdata/default/superchat.json") + + _, chatdata = parser.parse(parser.get_contents(json.loads(_json))) + data = { + "video_id": "", + "timeout": 7, + "chatdata": chatdata + } + + ret = processor.process([data]).items[0] + print(json.dumps(chatdata, ensure_ascii=False)) + assert ret.chattype == "superChat" + assert ret.type == "superChat" + assert ret.id == "dummy_id" + assert ret.message == "dummy_message" + assert ret.messageEx == ["dummy_message"] + assert ret.timestamp == 1570678496000 + assert ret.datetime == "2019-10-10 12:34:56" + assert ret.elapsedTime == "" + assert ret.amountValue == 800 + assert ret.amountString == "¥800" + assert ret.currency == "JPY" + assert ret.bgColor == 4280150454 + assert ret.author.name == "author_name" + assert ret.author.channelId == "author_channel_id" + assert ret.author.channelUrl == "http://www.youtube.com/channel/author_channel_id" + assert ret.author.imageUrl == "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s64-x-x-xx-xx-xx-c0xffffff/photo.jpg" + assert ret.author.badgeUrl == "" + assert ret.author.isVerified is False + assert ret.author.isChatOwner is False + assert ret.author.isChatSponsor is False + assert ret.author.isChatModerator is False + assert ret.colors.headerBackgroundColor == 4278239141 + assert ret.colors.headerTextColor == 4278190080 + assert ret.colors.bodyBackgroundColor == 4280150454 + assert ret.colors.bodyTextColor == 4278190080 + assert ret.colors.authorNameTextColor == 2315255808 + assert ret.colors.timestampColor == 2147483648 + + +def test_supersticker(mocker): + '''supersticker''' + processor = DefaultProcessor() + parser = Parser(is_replay=False) + _json = _open_file("tests/testdata/default/supersticker.json") + + _, chatdata = parser.parse(parser.get_contents(json.loads(_json))) + data = { + "video_id": "", + "timeout": 7, + "chatdata": chatdata + } + + ret = processor.process([data]).items[0] + print(json.dumps(chatdata, ensure_ascii=False)) + assert ret.chattype == "superSticker" + assert ret.type == "superSticker" + assert ret.id == "dummy_id" + assert ret.message == "" + assert ret.messageEx == [] + assert ret.timestamp == 1570678496000 + assert ret.datetime == "2019-10-10 12:34:56" + assert ret.elapsedTime == "" + assert ret.amountValue == 200 + assert ret.amountString == "¥200" + assert ret.currency == "JPY" + assert ret.bgColor == 4278248959 + assert ret.sticker == "https://lh3.googleusercontent.com/param_s=s72-rp" + assert ret.author.name == "author_name" + assert ret.author.channelId == "author_channel_id" + assert ret.author.channelUrl == "http://www.youtube.com/channel/author_channel_id" + assert ret.author.imageUrl == "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s64-x-x-xx-xx-xx-c0xffffff/photo.jpg" + assert ret.author.badgeUrl == "" + assert ret.author.isVerified is False + assert ret.author.isChatOwner is False + assert ret.author.isChatSponsor is False + assert ret.author.isChatModerator is False + assert ret.colors.backgroundColor == 4278237396 + assert ret.colors.moneyChipBackgroundColor == 4278248959 + assert ret.colors.moneyChipTextColor == 4278190080 + assert ret.colors.authorNameTextColor == 3003121664 + + +def test_sponsor(mocker): + '''sponsor(membership)''' + processor = DefaultProcessor() + parser = Parser(is_replay=False) + _json = _open_file("tests/testdata/default/newSponsor_current.json") + + _, chatdata = parser.parse(parser.get_contents(json.loads(_json))) + data = { + "video_id": "", + "timeout": 7, + "chatdata": chatdata + } + + ret = processor.process([data]).items[0] + print(json.dumps(chatdata, ensure_ascii=False)) + assert ret.chattype == "newSponsor" + assert ret.type == "newSponsor" + assert ret.id == "dummy_id" + assert ret.message == "新規メンバー" + assert ret.messageEx == ["新規メンバー"] + assert ret.timestamp == 1570678496000 + assert ret.datetime == "2019-10-10 12:34:56" + assert ret.elapsedTime == "" + assert ret.bgColor == 0 + assert ret.author.name == "author_name" + assert ret.author.channelId == "author_channel_id" + assert ret.author.channelUrl == "http://www.youtube.com/channel/author_channel_id" + assert ret.author.imageUrl == "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s64-x-x-xx-xx-xx-c0xffffff/photo.jpg" + assert ret.author.badgeUrl == "https://yt3.ggpht.com/X=s32-c-k" + assert ret.author.isVerified is False + assert ret.author.isChatOwner is False + assert ret.author.isChatSponsor is True + assert ret.author.isChatModerator is False + + +def test_sponsor_legacy(mocker): + '''lagacy sponsor(membership)''' + processor = DefaultProcessor() + parser = Parser(is_replay=False) + _json = _open_file("tests/testdata/default/newSponsor_lagacy.json") + + _, chatdata = parser.parse(parser.get_contents(json.loads(_json))) + data = { + "video_id": "", + "timeout": 7, + "chatdata": chatdata + } + + ret = processor.process([data]).items[0] + print(json.dumps(chatdata, ensure_ascii=False)) + assert ret.chattype == "newSponsor" + assert ret.type == "newSponsor" + assert ret.id == "dummy_id" + assert ret.message == "新規メンバー / ようこそ、author_name!" + assert ret.messageEx == ["新規メンバー / ようこそ、author_name!"] + assert ret.timestamp == 1570678496000 + assert ret.datetime == "2019-10-10 12:34:56" + assert ret.elapsedTime == "" + assert ret.bgColor == 0 + assert ret.author.name == "author_name" + assert ret.author.channelId == "author_channel_id" + assert ret.author.channelUrl == "http://www.youtube.com/channel/author_channel_id" + assert ret.author.imageUrl == "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s64-x-x-xx-xx-xx-c0xffffff/photo.jpg" + assert ret.author.badgeUrl == "" + assert ret.author.isVerified is False + assert ret.author.isChatOwner is False + assert ret.author.isChatSponsor is True + assert ret.author.isChatModerator is False + + +def _open_file(path): + with open(path, mode='r', encoding='utf-8') as f: + return f.read() diff --git a/tests/testdata/default/newSponsor_current.json b/tests/testdata/default/newSponsor_current.json new file mode 100644 index 0000000..346fba3 --- /dev/null +++ b/tests/testdata/default/newSponsor_current.json @@ -0,0 +1,100 @@ +{ + "response": { + "responseContext": { + "webResponseContextExtensionData": "" + }, + "continuationContents": { + "liveChatContinuation": { + "continuations": [ + { + "invalidationContinuationData": { + "invalidationId": { + "objectSource": 1000, + "objectId": "___objectId___", + "topic": "chat~00000000000~0000000", + "subscribeToGcmTopics": true, + "protoCreationTimestampMs": "1577804400000" + }, + "timeoutMs": 10000, + "continuation": "___continuation___" + } + } + ], + "actions": [ + { + "addChatItemAction": { + "item": { + "liveChatMembershipItemRenderer": { + "id": "dummy_id", + "timestampUsec": 1570678496000000, + "authorExternalChannelId": "author_channel_id", + "headerSubtext": { + "runs": [ + { + "text": "新規メンバー" + } + ] + }, + "authorName": { + "simpleText": "author_name" + }, + "authorPhoto": { + "thumbnails": [ + { + "url": "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s32-x-x-xx-xx-xx-c0xffffff/photo.jpg", + "width": 32, + "height": 32 + }, + { + "url": "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s64-x-x-xx-xx-xx-c0xffffff/photo.jpg", + "width": 64, + "height": 64 + } + ] + }, + "authorBadges": [ + { + "liveChatAuthorBadgeRenderer": { + "customThumbnail": { + "thumbnails": [ + { + "url": "https://yt3.ggpht.com/X=s32-c-k" + }, + { + "url": "https://yt3.ggpht.com/X=s64-c-k" + } + ] + }, + "tooltip": "新規メンバー", + "accessibility": { + "accessibilityData": { + "label": "新規メンバー" + } + } + } + } + ], + "contextMenuEndpoint": { + "commandMetadata": { + "webCommandMetadata": { + "ignoreNavigation": true + } + }, + "liveChatItemContextMenuEndpoint": { + "params": "___params___" + } + }, + "contextMenuAccessibility": { + "accessibilityData": { + "label": "コメントの操作" + } + } + } + } + } + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/testdata/default/newSponsor_lagacy.json b/tests/testdata/default/newSponsor_lagacy.json new file mode 100644 index 0000000..aa9cc9b --- /dev/null +++ b/tests/testdata/default/newSponsor_lagacy.json @@ -0,0 +1,82 @@ +{ + "response": { + "responseContext": { + "webResponseContextExtensionData": "" + }, + "continuationContents": { + "liveChatContinuation": { + "continuations": [ + { + "invalidationContinuationData": { + "invalidationId": { + "objectSource": 1000, + "objectId": "___objectId___", + "topic": "chat~00000000000~0000000", + "subscribeToGcmTopics": true, + "protoCreationTimestampMs": "1577804400000" + }, + "timeoutMs": 10000, + "continuation": "___continuation___" + } + } + ], + "actions": [ + { + "addChatItemAction": { + "item": { + "liveChatLegacyPaidMessageRenderer": { + "id": "dummy_id", + "timestampUsec": 1570678496000000, + "eventText": { + "runs": [ + { + "text": "新規メンバー" + } + ] + }, + "detailText": { + "simpleText": "ようこそ、author_name!" + }, + "authorName": { + "simpleText": "author_name" + }, + "authorPhoto": { + "thumbnails": [ + { + "url": "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s32-x-x-xx-xx-xx-c0xffffff/photo.jpg", + "width": 32, + "height": 32 + }, + { + "url": "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s64-x-x-xx-xx-xx-c0xffffff/photo.jpg", + "width": 64, + "height": 64 + } + ] + }, + "authorExternalChannelId": "author_channel_id", + "contextMenuEndpoint": { + "clickTrackingParams": "___clickTrackingParams___", + "commandMetadata": { + "webCommandMetadata": { + "ignoreNavigation": true + } + }, + "liveChatItemContextMenuEndpoint": { + "params": "___params___" + } + }, + "contextMenuAccessibility": { + "accessibilityData": { + "label": "コメントの操作" + } + } + } + } + } + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/testdata/default/replay_member_text.json b/tests/testdata/default/replay_member_text.json new file mode 100644 index 0000000..eaaf4b2 --- /dev/null +++ b/tests/testdata/default/replay_member_text.json @@ -0,0 +1,112 @@ +{ + "response": { + "responseContext": { + "webResponseContextExtensionData": "data" + }, + "continuationContents": { + "liveChatContinuation": { + "continuations": [ + { + "liveChatReplayContinuationData": { + "invalidationId": { + "objectSource": 1000, + "objectId": "___objectId___", + "topic": "chat~00000000000~0000000", + "subscribeToGcmTopics": true, + "protoCreationTimestampMs": "1577804400000" + }, + "timeoutMs": 10000, + "continuation": "___continuation___" + } + } + ], + "actions": [ + { + "replayChatItemAction": { + "actions": [ + { + "addChatItemAction": { + "item": { + "liveChatTextMessageRenderer": { + "message": { + "runs": [ + { + "text": "dummy_message" + } + ] + }, + "authorName": { + "simpleText": "author_name" + }, + "authorPhoto": { + "thumbnails": [ + { + "url": "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s32-x-x-xx-xx-xx-c0xffffff/photo.jpg", + "width": 32, + "height": 32 + }, + { + "url": "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s64-x-x-xx-xx-xx-c0xffffff/photo.jpg", + "width": 64, + "height": 64 + } + ] + }, + "contextMenuEndpoint": { + "clickTrackingParams": "___clickTrackingParams___", + "commandMetadata": { + "webCommandMetadata": { + "ignoreNavigation": true + } + }, + "liveChatItemContextMenuEndpoint": { + "params": "___params___" + } + }, + "id": "dummy_id", + "timestampUsec": 1570678496000000, + "authorBadges": [ + { + "liveChatAuthorBadgeRenderer": { + "customThumbnail": { + "thumbnails": [ + { + "url": "https://yt3.ggpht.com/X=s16-c-k" + }, + { + "url": "https://yt3.ggpht.com/X=s32-c-k" + } + ] + }, + "tooltip": "メンバー(1 か月)", + "accessibility": { + "accessibilityData": { + "label": "メンバー(1 か月)" + } + } + } + } + ], + "authorExternalChannelId": "author_channel_id", + "contextMenuAccessibility": { + "accessibilityData": { + "label": "コメントの操作" + } + }, + "timestampText": { + "simpleText": "1:23:45" + } + } + }, + "clientId": "dummy_client_id" + } + } + ], + "videoOffsetTimeMsec": "5025120" + } + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/testdata/default/superchat.json b/tests/testdata/default/superchat.json new file mode 100644 index 0000000..a5b41d1 --- /dev/null +++ b/tests/testdata/default/superchat.json @@ -0,0 +1,184 @@ +{ + "response": { + "responseContext": { + "webResponseContextExtensionData": "" + }, + "continuationContents": { + "liveChatContinuation": { + "continuations": [ + { + "invalidationContinuationData": { + "invalidationId": { + "objectSource": 1000, + "objectId": "___objectId___", + "topic": "chat~00000000000~0000000", + "subscribeToGcmTopics": true, + "protoCreationTimestampMs": "1577804400000" + }, + "timeoutMs": 10000, + "continuation": "___continuation___" + } + } + ], + "actions": [ + { + "addChatItemAction": { + "item": { + "liveChatPaidMessageRenderer": { + "id": "dummy_id", + "timestampUsec": 1570678496000000, + "authorName": { + "simpleText": "author_name" + }, + "authorPhoto": { + "thumbnails": [ + { + "url": "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s32-x-x-xx-xx-xx-c0xffffff/photo.jpg", + "width": 32, + "height": 32 + }, + { + "url": "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s64-x-x-xx-xx-xx-c0xffffff/photo.jpg", + "width": 64, + "height": 64 + } + ] + }, + "purchaseAmountText": { + "simpleText": "¥800" + }, + "message": { + "runs": [ + { + "text": "dummy_message" + } + ] + }, + "headerBackgroundColor": 4278239141, + "headerTextColor": 4278190080, + "bodyBackgroundColor": 4280150454, + "bodyTextColor": 4278190080, + "authorExternalChannelId": "author_channel_id", + "authorNameTextColor": 2315255808, + "contextMenuEndpoint": { + "commandMetadata": { + "webCommandMetadata": { + "ignoreNavigation": true + } + }, + "liveChatItemContextMenuEndpoint": { + "params": "___params___" + } + }, + "timestampColor": 2147483648, + "contextMenuAccessibility": { + "accessibilityData": { + "label": "コメントの操作" + } + } + } + } + } + }, + { + "addLiveChatTickerItemAction": { + "item": { + "liveChatTickerPaidMessageItemRenderer": { + "id": "dummy_id", + "amount": { + "simpleText": "¥846" + }, + "amountTextColor": 4278190080, + "startBackgroundColor": 4280150454, + "endBackgroundColor": 4278239141, + "authorPhoto": { + "thumbnails": [ + { + "url": "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s32-x-x-xx-xx-xx-c0xffffff/photo.jpg", + "width": 32, + "height": 32 + }, + { + "url": "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s32-x-x-xx-xx-xx-c0xffffff/photo.jpg", + "width": 64, + "height": 64 + } + ] + }, + "durationSec": 120, + "showItemEndpoint": { + "commandMetadata": { + "webCommandMetadata": { + "ignoreNavigation": true + } + }, + "showLiveChatItemEndpoint": { + "renderer": { + "liveChatPaidMessageRenderer": { + "id": "dummy_id", + "timestampUsec": 1570678496000000, + "authorName": { + "simpleText": "author_name" + }, + "authorPhoto": { + "thumbnails": [ + { + "url": "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s32-x-x-xx-xx-xx-c0xffffff/photo.jpg", + "width": 32, + "height": 32 + }, + { + "url": "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s64-x-x-xx-xx-xx-c0xffffff/photo.jpg", + "width": 64, + "height": 64 + } + ] + }, + "purchaseAmountText": { + "simpleText": "¥846" + }, + "message": { + "runs": [ + { + "text": "dummy_message" + } + ] + }, + "headerBackgroundColor": 4278239141, + "headerTextColor": 4278190080, + "bodyBackgroundColor": 4280150454, + "bodyTextColor": 4278190080, + "authorExternalChannelId": "author_channel_id", + "authorNameTextColor": 2315255808, + "contextMenuEndpoint": { + "commandMetadata": { + "webCommandMetadata": { + "ignoreNavigation": true + } + }, + "liveChatItemContextMenuEndpoint": { + "params": "___params___" + } + }, + "timestampColor": 2147483648, + "contextMenuAccessibility": { + "accessibilityData": { + "label": "コメントの操作" + } + } + } + } + } + }, + "authorExternalChannelId": "http://www.youtube.com/channel/author_channel_url", + "fullDurationSec": 120 + } + }, + "durationSec": "120" + } + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/testdata/default/supersticker.json b/tests/testdata/default/supersticker.json new file mode 100644 index 0000000..fddf26d --- /dev/null +++ b/tests/testdata/default/supersticker.json @@ -0,0 +1,99 @@ +{ + "response": { + "responseContext": { + "webResponseContextExtensionData": "" + }, + "continuationContents": { + "liveChatContinuation": { + "continuations": [ + { + "invalidationContinuationData": { + "invalidationId": { + "objectSource": 1000, + "objectId": "___objectId___", + "topic": "chat~00000000000~0000000", + "subscribeToGcmTopics": true, + "protoCreationTimestampMs": "1577804400000" + }, + "timeoutMs": 10000, + "continuation": "___continuation___" + } + } + ], + "actions": [ + { + "addChatItemAction": { + "item": { + "liveChatPaidStickerRenderer": { + "id": "dummy_id", + "contextMenuEndpoint": { + "commandMetadata": { + "webCommandMetadata": { + "ignoreNavigation": true + } + }, + "liveChatItemContextMenuEndpoint": { + "params": "___params___" + } + }, + "contextMenuAccessibility": { + "accessibilityData": { + "label": "コメントの操作" + } + }, + "timestampUsec": 1570678496000000, + "authorPhoto": { + "thumbnails": [ + { + "url": "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s32-x-x-xx-xx-xx-c0xffffff/photo.jpg", + "width": 32, + "height": 32 + }, + { + "url": "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s64-x-x-xx-xx-xx-c0xffffff/photo.jpg", + "width": 64, + "height": 64 + } + ] + }, + "authorName": { + "simpleText": "author_name" + }, + "authorExternalChannelId": "author_channel_id", + "sticker": { + "thumbnails": [ + { + "url": "//lh3.googleusercontent.com/param_s=s72-rp", + "width": 72, + "height": 72 + }, + { + "url": "//lh3.googleusercontent.com/param_s=s144-rp", + "width": 144, + "height": 144 + } + ], + "accessibility": { + "accessibilityData": { + "label": "___sticker_label___" + } + } + }, + "moneyChipBackgroundColor": 4278248959, + "moneyChipTextColor": 4278190080, + "purchaseAmountText": { + "simpleText": "¥200" + }, + "stickerDisplayWidth": 72, + "stickerDisplayHeight": 72, + "backgroundColor": 4278237396, + "authorNameTextColor": 3003121664 + } + } + } + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/testdata/default/textmessage.json b/tests/testdata/default/textmessage.json new file mode 100644 index 0000000..474b26b --- /dev/null +++ b/tests/testdata/default/textmessage.json @@ -0,0 +1,79 @@ +{ + "response": { + "responseContext": { + "webResponseContextExtensionData": "" + }, + "continuationContents": { + "liveChatContinuation": { + "continuations": [ + { + "invalidationContinuationData": { + "invalidationId": { + "objectSource": 1000, + "objectId": "___objectId___", + "topic": "chat~00000000000~0000000", + "subscribeToGcmTopics": true, + "protoCreationTimestampMs": "1577804400000" + }, + "timeoutMs": 10000, + "continuation": "___continuation___" + } + } + ], + "actions": [ + { + "addChatItemAction": { + "item": { + "liveChatTextMessageRenderer": { + "message": { + "runs": [ + { + "text": "dummy_message" + } + ] + }, + "authorName": { + "simpleText": "author_name" + }, + "authorPhoto": { + "thumbnails": [ + { + "url": "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s32-x-x-xx-xx-xx-c0xffffff/photo.jpg", + "width": 32, + "height": 32 + }, + { + "url": "https://yt3.ggpht.com/------------/AAAAAAAAAAA/AAAAAAAAAAA/xxxxxxxxxxxx/s64-x-x-xx-xx-xx-c0xffffff/photo.jpg", + "width": 64, + "height": 64 + } + ] + }, + "contextMenuEndpoint": { + "commandMetadata": { + "webCommandMetadata": { + "ignoreNavigation": true + } + }, + "liveChatItemContextMenuEndpoint": { + "params": "___params___" + } + }, + "id": "dummy_id", + "timestampUsec": 1570678496000000, + "authorExternalChannelId": "author_channel_id", + "contextMenuAccessibility": { + "accessibilityData": { + "label": "コメントの操作" + } + } + } + }, + "clientId": "dummy_client_id" + } + } + ] + } + } + } +} \ No newline at end of file From 0abf8dd9f0bae6ff3b0175265301672b583e27e1 Mon Sep 17 00:00:00 2001 From: taizan-hokuto <55448286+taizan-hokuto@users.noreply.github.com> Date: Fri, 24 Jul 2020 14:03:07 +0900 Subject: [PATCH 3/6] Make it possible to extract video id from url --- pytchat/cli/__init__.py | 18 ++++++++++++------ pytchat/cli/arguments.py | 11 +++++++---- pytchat/core_async/livechat.py | 3 ++- pytchat/core_multithread/livechat.py | 3 ++- pytchat/processors/html_archiver.py | 2 +- pytchat/tool/extract/extractor.py | 3 ++- pytchat/tool/videoinfo.py | 5 +++-- pytchat/util/extract_video_id.py | 25 +++++++++++++++++++++++++ 8 files changed, 54 insertions(+), 16 deletions(-) create mode 100644 pytchat/util/extract_video_id.py diff --git a/pytchat/cli/__init__.py b/pytchat/cli/__init__.py index 696af45..62237ff 100644 --- a/pytchat/cli/__init__.py +++ b/pytchat/cli/__init__.py @@ -1,5 +1,6 @@ import argparse from pathlib import Path +from pytchat.util.extract_video_id import extract_video_id from .arguments import Arguments from .. exceptions import InvalidVideoIdException, NoContents from .. processors.html_archiver import HTMLArchiver @@ -19,16 +20,19 @@ https://github.com/PetterKraabol/Twitch-Chat-Downloader def main(): # Arguments parser = argparse.ArgumentParser(description=f'pytchat v{__version__}') - parser.add_argument('-v', f'--{Arguments.Name.VIDEO}', type=str, - help='Video IDs separated by commas without space.\n' + # parser.add_argument('VideoID_or_URL', type=str, default='__NONE__',nargs='?', + # help='Video ID, or URL that includes id.\n' + # 'If ID starts with a hyphen (-), enclose the ID in square brackets.') + parser.add_argument('-v', f'--{Arguments.Name.VIDEO_IDS}', type=str, + help='Video ID (or URL that includes Video ID). You can specify multiple video IDs by separating them with commas without spaces.\n' 'If ID starts with a hyphen (-), enclose the ID in square brackets.') parser.add_argument('-o', f'--{Arguments.Name.OUTPUT}', type=str, help='Output directory (end with "/"). default="./"', default='./') parser.add_argument(f'--{Arguments.Name.VERSION}', action='store_true', - help='Settings version') + help='Show version') Arguments(parser.parse_args().__dict__) if Arguments().print_version: - print(f'pytchat v{__version__}') + print(f'pytchat v{__version__} © 2019 taizan-hokuto') return # Extractor @@ -43,14 +47,16 @@ def main(): f" channel: {info.get_channel_name()}\n" f" title: {info.get_title()}") path = Path(Arguments().output + video_id + '.html') - print(f"output path: {path.resolve()}") + print(f" output path: {path.resolve()}") Extractor(video_id, processor=HTMLArchiver( Arguments().output + video_id + '.html'), callback=_disp_progress ).extract() print("\nExtraction end.\n") - except (InvalidVideoIdException, NoContents) as e: + except InvalidVideoIdException: + print("Invalid Video ID or URL:", video_id) + except (TypeError, NoContents) as e: print(e) return parser.print_help() diff --git a/pytchat/cli/arguments.py b/pytchat/cli/arguments.py index d6fea2b..6f62548 100644 --- a/pytchat/cli/arguments.py +++ b/pytchat/cli/arguments.py @@ -16,8 +16,8 @@ class Arguments(metaclass=Singleton): class Name: VERSION: str = 'version' - OUTPUT: str = 'output' - VIDEO: str = 'video' + OUTPUT: str = 'output_dir' + VIDEO_IDS: str = 'video_id' def __init__(self, arguments: Optional[Dict[str, Union[str, bool, int]]] = None): @@ -35,6 +35,9 @@ class Arguments(metaclass=Singleton): self.output: str = arguments[Arguments.Name.OUTPUT] self.video_ids: List[int] = [] # Videos - if arguments[Arguments.Name.VIDEO]: + if arguments[Arguments.Name.VIDEO_IDS]: self.video_ids = [video_id - for video_id in arguments[Arguments.Name.VIDEO].split(',')] + for video_id in arguments[Arguments.Name.VIDEO_IDS].split(',')] + + + diff --git a/pytchat/core_async/livechat.py b/pytchat/core_async/livechat.py index 2cc3ff8..17f91f2 100644 --- a/pytchat/core_async/livechat.py +++ b/pytchat/core_async/livechat.py @@ -15,6 +15,7 @@ from .. import exceptions from ..paramgen import liveparam, arcparam from ..processors.default.processor import DefaultProcessor from ..processors.combinator import Combinator +from ..util.extract_video_id import extract_video_id headers = config.headers MAX_RETRY = 10 @@ -86,7 +87,7 @@ class LiveChatAsync: topchat_only=False, logger=config.logger(__name__), ): - self._video_id = video_id + self._video_id = extract_video_id(video_id) self.seektime = seektime if isinstance(processor, tuple): self.processor = Combinator(processor) diff --git a/pytchat/core_multithread/livechat.py b/pytchat/core_multithread/livechat.py index 7f99c55..f439026 100644 --- a/pytchat/core_multithread/livechat.py +++ b/pytchat/core_multithread/livechat.py @@ -14,6 +14,7 @@ from .. import exceptions from ..paramgen import liveparam, arcparam from ..processors.default.processor import DefaultProcessor from ..processors.combinator import Combinator +from ..util.extract_video_id import extract_video_id headers = config.headers MAX_RETRY = 10 @@ -84,7 +85,7 @@ class LiveChat: topchat_only=False, logger=config.logger(__name__) ): - self._video_id = video_id + self._video_id = extract_video_id(video_id) self.seektime = seektime if isinstance(processor, tuple): self.processor = Combinator(processor) diff --git a/pytchat/processors/html_archiver.py b/pytchat/processors/html_archiver.py index dedab39..3676770 100644 --- a/pytchat/processors/html_archiver.py +++ b/pytchat/processors/html_archiver.py @@ -47,7 +47,7 @@ class HTMLArchiver(ChatProcessor): super().__init__() self.save_path = self._checkpath(save_path) self.processor = DefaultProcessor() - self.emoji_table = {} # table for custom emojis. key: emoji_id, value: base64 encoded image binary. + self.emoji_table = {} # tuble for custom emojis. key: emoji_id, value: base64 encoded image binary. self.header = [HEADER_HTML] self.body = ['
\n', '