Created base files
This commit is contained in:
0
pytchat/processors/__init__.py
Normal file
0
pytchat/processors/__init__.py
Normal file
27
pytchat/processors/chat_processor.py
Normal file
27
pytchat/processors/chat_processor.py
Normal file
@@ -0,0 +1,27 @@
|
||||
class ChatProcessor:
|
||||
'''
|
||||
Listenerからチャットデータ(actions)を受け取り
|
||||
チャットデータを加工するクラスの抽象クラス
|
||||
'''
|
||||
def process(self, chat_components: list):
|
||||
'''
|
||||
チャットデータの加工を表すインターフェース
|
||||
Listenerから呼び出される。
|
||||
Parameter
|
||||
----------
|
||||
chat_components: list<component>
|
||||
component : dict {
|
||||
"video_id" : str
|
||||
動画ID
|
||||
"timeout" : int
|
||||
次のチャットの再読み込みまでの時間(秒)
|
||||
"chatdata" : list<object>
|
||||
チャットデータ(actions)のリスト
|
||||
}
|
||||
'''
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
0
pytchat/processors/compatible/__init__.py
Normal file
0
pytchat/processors/compatible/__init__.py
Normal file
43
pytchat/processors/compatible/parser.py
Normal file
43
pytchat/processors/compatible/parser.py
Normal file
@@ -0,0 +1,43 @@
|
||||
|
||||
from .renderer.textmessage import LiveChatTextMessageRenderer
|
||||
from .renderer.paidmessage import LiveChatPaidMessageRenderer
|
||||
from .renderer.paidsticker import LiveChatPaidStickerRenderer
|
||||
from .renderer.legacypaid import LiveChatLegacyPaidMessageRenderer
|
||||
|
||||
def parse(sitem):
|
||||
|
||||
action = sitem.get("addChatItemAction")
|
||||
if action:
|
||||
item = action.get("item")
|
||||
if item is None: return None
|
||||
rd={}
|
||||
try:
|
||||
renderer = get_renderer(item)
|
||||
if renderer == None:
|
||||
return None
|
||||
|
||||
rd["kind"] = "youtube#liveChatMessage"
|
||||
rd["etag"] = ""
|
||||
rd["id"] = 'LCC.' + renderer.get_id()
|
||||
rd["snippet"] = renderer.get_snippet()
|
||||
rd["authorDetails"] = renderer.get_authordetails()
|
||||
except (KeyError,TypeError,AttributeError) as e:
|
||||
print(f"------{str(type(e))}-{str(e)}----------")
|
||||
print(sitem)
|
||||
return None
|
||||
|
||||
return rd
|
||||
|
||||
def get_renderer(item):
|
||||
if item.get("liveChatTextMessageRenderer"):
|
||||
renderer = LiveChatTextMessageRenderer(item)
|
||||
elif item.get("liveChatPaidMessageRenderer"):
|
||||
renderer = LiveChatPaidMessageRenderer(item)
|
||||
elif item.get( "liveChatPaidStickerRenderer"):
|
||||
renderer = LiveChatPaidStickerRenderer(item)
|
||||
elif item.get("liveChatLegacyPaidMessageRenderer"):
|
||||
renderer = LiveChatLegacyPaidMessageRenderer(item)
|
||||
else:
|
||||
renderer = None
|
||||
return renderer
|
||||
|
||||
39
pytchat/processors/compatible/processor.py
Normal file
39
pytchat/processors/compatible/processor.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from . import parser
|
||||
import json
|
||||
import os
|
||||
import traceback
|
||||
import datetime
|
||||
import time
|
||||
class CompatibleProcessor():
|
||||
|
||||
def process(self, chat_components: list):
|
||||
|
||||
chatlist = []
|
||||
timeout = 0
|
||||
ret={}
|
||||
ret["kind"] = "youtube#liveChatMessageListResponse"
|
||||
ret["etag"] = ""
|
||||
ret["nextPageToken"] = ""
|
||||
|
||||
if chat_components:
|
||||
for chat_component in chat_components:
|
||||
timeout += chat_component.get('timeout', 0)
|
||||
chatdata = chat_component.get('chatdata')
|
||||
|
||||
if chatdata is None: break
|
||||
for action in chatdata:
|
||||
if action is None: continue
|
||||
if action.get('addChatItemAction') is None: continue
|
||||
if action['addChatItemAction'].get('item') is None: continue
|
||||
|
||||
chat = parser.parse(action)
|
||||
if chat:
|
||||
chatlist.append(chat)
|
||||
ret["pollingIntervalMillis"] = int(timeout*1000)
|
||||
ret["pageInfo"]={
|
||||
"totalResults":len(chatlist),
|
||||
"resultsPerPage":len(chatlist),
|
||||
}
|
||||
ret["items"] = chatlist
|
||||
|
||||
return ret
|
||||
0
pytchat/processors/compatible/renderer/__init__.py
Normal file
0
pytchat/processors/compatible/renderer/__init__.py
Normal file
83
pytchat/processors/compatible/renderer/base.py
Normal file
83
pytchat/processors/compatible/renderer/base.py
Normal file
@@ -0,0 +1,83 @@
|
||||
import datetime, pytz
|
||||
|
||||
class BaseRenderer:
|
||||
def __init__(self, item, chattype):
|
||||
self.renderer = list(item.values())[0]
|
||||
self.chattype = chattype
|
||||
|
||||
|
||||
def get_snippet(self):
|
||||
|
||||
message = self.get_message(self.renderer)
|
||||
|
||||
return {
|
||||
"type" : self.chattype,
|
||||
"liveChatId" : "",
|
||||
"authorChannelId" : self.renderer.get("authorExternalChannelId"),
|
||||
"publishedAt" : self.get_publishedat(self.renderer.get("timestampUsec",0)),
|
||||
"hasDisplayContent" : True,
|
||||
"displayMessage" : message,
|
||||
"textMessageDetails": {
|
||||
"messageText" : message
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def get_authordetails(self):
|
||||
authorExternalChannelId = self.renderer.get("authorExternalChannelId")
|
||||
#parse subscriber type
|
||||
isVerified, isChatOwner, isChatSponsor, isChatModerator = (
|
||||
self.get_badges(self.renderer)
|
||||
)
|
||||
return {
|
||||
"channelId" : authorExternalChannelId,
|
||||
"channelUrl" : "http://www.youtube.com/channel/"+authorExternalChannelId,
|
||||
"displayName" : self.renderer["authorName"]["simpleText"],
|
||||
"profileImageUrl" : self.renderer["authorPhoto"]["thumbnails"][1]["url"] ,
|
||||
"isVerified" : isVerified,
|
||||
"isChatOwner" : isChatOwner,
|
||||
"isChatSponsor" : isChatSponsor,
|
||||
"isChatModerator" : isChatModerator
|
||||
}
|
||||
|
||||
|
||||
def get_message(self,renderer):
|
||||
message = ''
|
||||
if renderer.get("message"):
|
||||
runs=renderer["message"].get("runs")
|
||||
if runs:
|
||||
for r in runs:
|
||||
if r:
|
||||
if r.get('emoji'):
|
||||
message += r['emoji'].get('shortcuts',[''])[0]
|
||||
else:
|
||||
message += r.get('text','')
|
||||
return message
|
||||
|
||||
def get_badges(self,renderer):
|
||||
isVerified = False
|
||||
isChatOwner = False
|
||||
isChatSponsor = False
|
||||
isChatModerator = False
|
||||
badges=renderer.get("authorBadges")
|
||||
if badges:
|
||||
for badge in badges:
|
||||
author_type = badge["liveChatAuthorBadgeRenderer"]["accessibility"]["accessibilityData"]["label"]
|
||||
if author_type == '確認済み':
|
||||
isVerified = True
|
||||
if author_type == '所有者':
|
||||
isChatOwner = True
|
||||
if 'メンバー' in author_type:
|
||||
isChatSponsor = True
|
||||
if author_type == 'モデレーター':
|
||||
isChatModerator = True
|
||||
return isVerified, isChatOwner, isChatSponsor, isChatModerator
|
||||
|
||||
def get_id(self):
|
||||
return self.renderer.get('id')
|
||||
|
||||
def get_publishedat(self,timestamp):
|
||||
dt = datetime.datetime.fromtimestamp(int(timestamp)/1000000)
|
||||
return dt.astimezone(pytz.utc).isoformat(
|
||||
timespec='milliseconds').replace('+00:00','Z')
|
||||
|
||||
37
pytchat/processors/compatible/renderer/currency.py
Normal file
37
pytchat/processors/compatible/renderer/currency.py
Normal file
@@ -0,0 +1,37 @@
|
||||
'''
|
||||
YouTubeスーパーチャットで使用される通貨の記号とレート検索用の略号の
|
||||
対応表
|
||||
Key:
|
||||
YouTubeスーパーチャットで使用される通貨の記号
|
||||
(アルファベットで終わる場合、0xA0(&npsp)が付く)
|
||||
Value:
|
||||
fxtext: 3文字の通貨略称
|
||||
jptest: 日本語テキスト
|
||||
'''
|
||||
symbols = {
|
||||
"$": {"fxtext": "USD", "jptext": "米・ドル"},
|
||||
"A$": {"fxtext": "AUD", "jptext": "オーストラリア・ドル"},
|
||||
"CA$": {"fxtext": "CAD", "jptext": "カナダ・ドル"},
|
||||
"CHF\xa0": {"fxtext": "CHF", "jptext": "スイス・フラン"},
|
||||
"COP\xa0": {"fxtext": "COP", "jptext": "コロンビア・ペソ"},
|
||||
"HK$": {"fxtext": "HKD", "jptext": "香港・ドル"},
|
||||
"HUF\xa0": {"fxtext": "HUF", "jptext": "ハンガリー・フォリント"},
|
||||
"MX$": {"fxtext": "MXN", "jptext": "メキシコ・ペソ"},
|
||||
"NT$": {"fxtext": "TWD", "jptext": "台湾・ドル"},
|
||||
"NZ$": {"fxtext": "NZD", "jptext": "ニュージーランド・ドル"},
|
||||
"PHP\xa0": {"fxtext": "PHP", "jptext": "フィリピン・ペソ"},
|
||||
"PLN\xa0": {"fxtext": "PLN", "jptext": "ポーランド・ズロチ"},
|
||||
"R$": {"fxtext": "BRL", "jptext": "ブラジル・レアル"},
|
||||
"RUB\xa0": {"fxtext": "RUB", "jptext": "ロシア・ルーブル"},
|
||||
"SEK\xa0": {"fxtext": "SEK", "jptext": "スウェーデン・クローネ"},
|
||||
"£": {"fxtext": "GBP", "jptext": "英・ポンド"},
|
||||
"₩": {"fxtext": "KRW", "jptext": "韓国・ウォン"},
|
||||
"€": {"fxtext": "EUR", "jptext": "欧・ユーロ"},
|
||||
"₹": {"fxtext": "INR", "jptext": "インド・ルピー"},
|
||||
"¥": {"fxtext": "JPY", "jptext": "日本・円"},
|
||||
"PEN\xa0": {"fxtext": "PEN", "jptext": "ペルー・ヌエボ・ソル"},
|
||||
"ARS\xa0": {"fxtext": "ARS", "jptext": "アルゼンチン・ペソ"},
|
||||
"CLP\xa0": {"fxtext": "CLP", "jptext": "チリ・ペソ"},
|
||||
"NOK\xa0": {"fxtext": "NOK", "jptext": "ノルウェー・クローネ"},
|
||||
"BAM\xa0": {"fxtext": "BAM", "jptext": "ボスニア・兌換マルカ"}
|
||||
}
|
||||
43
pytchat/processors/compatible/renderer/legacypaid.py
Normal file
43
pytchat/processors/compatible/renderer/legacypaid.py
Normal file
@@ -0,0 +1,43 @@
|
||||
from .base import BaseRenderer
|
||||
class LiveChatLegacyPaidMessageRenderer(BaseRenderer):
|
||||
def __init__(self, item):
|
||||
super().__init__(item, "newSponsorEvent")
|
||||
|
||||
def get_snippet(self):
|
||||
|
||||
message = self.get_message(self.renderer)
|
||||
|
||||
return {
|
||||
"type" : self.chattype,
|
||||
"liveChatId" : "",
|
||||
"authorChannelId" : self.renderer.get("authorExternalChannelId"),
|
||||
"publishedAt" : self.get_publishedat(self.renderer.get("timestampUsec",0)),
|
||||
"hasDisplayContent" : True,
|
||||
"displayMessage" : message,
|
||||
|
||||
}
|
||||
|
||||
def get_authordetails(self):
|
||||
authorExternalChannelId = self.renderer.get("authorExternalChannelId")
|
||||
#parse subscriber type
|
||||
isVerified, isChatOwner, _, isChatModerator = (
|
||||
self.get_badges(self.renderer)
|
||||
)
|
||||
return {
|
||||
"channelId" : authorExternalChannelId,
|
||||
"channelUrl" : "http://www.youtube.com/channel/"+authorExternalChannelId,
|
||||
"displayName" : self.renderer["authorName"]["simpleText"],
|
||||
"profileImageUrl" : self.renderer["authorPhoto"]["thumbnails"][1]["url"] ,
|
||||
"isVerified" : isVerified,
|
||||
"isChatOwner" : isChatOwner,
|
||||
"isChatSponsor" : True,
|
||||
"isChatModerator" : isChatModerator
|
||||
}
|
||||
|
||||
|
||||
def get_message(self,renderer):
|
||||
message = (renderer["eventText"]["runs"][0]["text"]
|
||||
)+' / '+(renderer["detailText"]["simpleText"])
|
||||
return message
|
||||
|
||||
|
||||
41
pytchat/processors/compatible/renderer/paidmessage.py
Normal file
41
pytchat/processors/compatible/renderer/paidmessage.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import re
|
||||
from . import currency
|
||||
from .base import BaseRenderer
|
||||
superchat_regex = re.compile(r"^(\D*)(\d{1,3}(,\d{3})*(\.\d*)*\b)$")
|
||||
|
||||
class LiveChatPaidMessageRenderer(BaseRenderer):
|
||||
def __init__(self, item):
|
||||
super().__init__(item, "superChatEvent")
|
||||
|
||||
def get_snippet(self):
|
||||
authorName = self.renderer["authorName"]["simpleText"]
|
||||
message = self.get_message(self.renderer)
|
||||
amountDisplayString, symbol, amountMicros =(
|
||||
self.get_amountdata(self.renderer)
|
||||
)
|
||||
return {
|
||||
"type" : self.chattype,
|
||||
"liveChatId" : "",
|
||||
"authorChannelId" : self.renderer.get("authorExternalChannelId"),
|
||||
"publishedAt" : self.get_publishedat(self.renderer.get("timestampUsec",0)),
|
||||
"hasDisplayContent" : True,
|
||||
"displayMessage" : amountDisplayString+" from "+authorName+': \"'+ message+'\"',
|
||||
"superChatDetails" : {
|
||||
"amountMicros" : amountMicros,
|
||||
"currency" : currency.symbols[symbol]["fxtext"] if currency.symbols.get(symbol) else symbol,
|
||||
"amountDisplayString" : amountDisplayString,
|
||||
"tier" : 0,
|
||||
"backgroundColor" : self.renderer.get("bodyBackgroundColor", 0)
|
||||
}
|
||||
}
|
||||
|
||||
def get_amountdata(self,renderer):
|
||||
amountDisplayString = renderer["purchaseAmountText"]["simpleText"]
|
||||
m = superchat_regex.search(amountDisplayString)
|
||||
if m:
|
||||
symbol = m.group(1)
|
||||
amountMicros = int(float(m.group(2).replace(',',''))*1000000)
|
||||
else:
|
||||
symbol = ""
|
||||
amountMicros = 0
|
||||
return amountDisplayString, symbol, amountMicros
|
||||
48
pytchat/processors/compatible/renderer/paidsticker.py
Normal file
48
pytchat/processors/compatible/renderer/paidsticker.py
Normal file
@@ -0,0 +1,48 @@
|
||||
import re
|
||||
from . import currency
|
||||
from .base import BaseRenderer
|
||||
superchat_regex = re.compile(r"^(\D*)(\d{1,3}(,\d{3})*(\.\d*)*\b)$")
|
||||
|
||||
class LiveChatPaidStickerRenderer(BaseRenderer):
|
||||
def __init__(self, item):
|
||||
super().__init__(item, "superStickerEvent")
|
||||
|
||||
def get_snippet(self):
|
||||
authorName = self.renderer["authorName"]["simpleText"]
|
||||
amountDisplayString, symbol, amountMicros =(
|
||||
self.get_amountdata(self.renderer)
|
||||
)
|
||||
|
||||
return {
|
||||
"type" : self.chattype,
|
||||
"liveChatId" : "",
|
||||
"authorChannelId" : self.renderer.get("authorExternalChannelId"),
|
||||
"publishedAt" : self.get_publishedat(self.renderer.get("timestampUsec",0)),
|
||||
"hasDisplayContent" : True,
|
||||
"displayMessage" : "Super Sticker " + amountDisplayString + " from "+authorName,
|
||||
"superStickerDetails" : {
|
||||
"superStickerMetaData" : {
|
||||
"stickerId": "",
|
||||
"altText": "",
|
||||
"language": ""
|
||||
},
|
||||
"amountMicros" : amountMicros,
|
||||
"currency" : currency.symbols[symbol]["fxtext"] if currency.symbols.get(symbol) else symbol,
|
||||
"amountDisplayString" : amountDisplayString,
|
||||
"tier" : 0,
|
||||
"backgroundColor" : self.renderer.get("bodyBackgroundColor", 0)
|
||||
}
|
||||
}
|
||||
|
||||
def get_amountdata(self,renderer):
|
||||
amountDisplayString = renderer["purchaseAmountText"]["simpleText"]
|
||||
m = superchat_regex.search(amountDisplayString)
|
||||
if m:
|
||||
symbol = m.group(1)
|
||||
amountMicros = int(float(m.group(2).replace(',',''))*1000000)
|
||||
else:
|
||||
symbol = ""
|
||||
amountMicros = 0
|
||||
return amountDisplayString, symbol, amountMicros
|
||||
|
||||
|
||||
4
pytchat/processors/compatible/renderer/textmessage.py
Normal file
4
pytchat/processors/compatible/renderer/textmessage.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from .base import BaseRenderer
|
||||
class LiveChatTextMessageRenderer(BaseRenderer):
|
||||
def __init__(self, item):
|
||||
super().__init__(item, "textMessageEvent")
|
||||
0
pytchat/processors/default/__init__.py
Normal file
0
pytchat/processors/default/__init__.py
Normal file
39
pytchat/processors/default/parser.py
Normal file
39
pytchat/processors/default/parser.py
Normal file
@@ -0,0 +1,39 @@
|
||||
|
||||
from .renderer.textmessage import LiveChatTextMessageRenderer
|
||||
from .renderer.paidmessage import LiveChatPaidMessageRenderer
|
||||
from .renderer.paidsticker import LiveChatPaidStickerRenderer
|
||||
from .renderer.legacypaid import LiveChatLegacyPaidMessageRenderer
|
||||
|
||||
def parse(sitem):
|
||||
|
||||
action = sitem.get("addChatItemAction")
|
||||
if action:
|
||||
item = action.get("item")
|
||||
if item is None: return None
|
||||
try:
|
||||
renderer = get_renderer(item)
|
||||
if renderer == None:
|
||||
return None
|
||||
|
||||
renderer.get_snippet()
|
||||
renderer.get_authordetails()
|
||||
except (KeyError,TypeError,AttributeError) as e:
|
||||
print(f"------{str(type(e))}-{str(e)}----------")
|
||||
print(sitem)
|
||||
return None
|
||||
|
||||
return renderer
|
||||
|
||||
def get_renderer(item):
|
||||
if item.get("liveChatTextMessageRenderer"):
|
||||
renderer = LiveChatTextMessageRenderer(item)
|
||||
elif item.get("liveChatPaidMessageRenderer"):
|
||||
renderer = LiveChatPaidMessageRenderer(item)
|
||||
elif item.get( "liveChatPaidStickerRenderer"):
|
||||
renderer = LiveChatPaidStickerRenderer(item)
|
||||
elif item.get("liveChatLegacyPaidMessageRenderer"):
|
||||
renderer = LiveChatLegacyPaidMessageRenderer(item)
|
||||
else:
|
||||
renderer = None
|
||||
return renderer
|
||||
|
||||
44
pytchat/processors/default/processor.py
Normal file
44
pytchat/processors/default/processor.py
Normal file
@@ -0,0 +1,44 @@
|
||||
from . import parser
|
||||
import asyncio
|
||||
import time
|
||||
|
||||
|
||||
class Chatdata:
|
||||
def __init__(self,chatlist:list, timeout:float):
|
||||
self.items = chatlist
|
||||
self.interval = timeout
|
||||
|
||||
def tick(self):
|
||||
if self.interval == 0:
|
||||
time.sleep(3)
|
||||
return
|
||||
time.sleep(self.interval/len(self.items))
|
||||
|
||||
async def tick_async(self):
|
||||
if self.interval == 0:
|
||||
await asyncio.sleep(3)
|
||||
return
|
||||
await asyncio.sleep(self.interval/len(self.items))
|
||||
|
||||
class DefaultProcessor:
|
||||
def process(self, chat_components: list):
|
||||
|
||||
chatlist = []
|
||||
timeout = 0
|
||||
|
||||
if chat_components:
|
||||
for component in chat_components:
|
||||
timeout += component.get('timeout', 0)
|
||||
chatdata = component.get('chatdata')
|
||||
|
||||
if chatdata is None: continue
|
||||
for action in chatdata:
|
||||
if action is None: continue
|
||||
if action.get('addChatItemAction') is None: continue
|
||||
if action['addChatItemAction'].get('item') is None: continue
|
||||
|
||||
chat = parser.parse(action)
|
||||
if chat:
|
||||
chatlist.append(chat)
|
||||
return Chatdata(chatlist, float(timeout))
|
||||
|
||||
0
pytchat/processors/default/renderer/__init__.py
Normal file
0
pytchat/processors/default/renderer/__init__.py
Normal file
80
pytchat/processors/default/renderer/base.py
Normal file
80
pytchat/processors/default/renderer/base.py
Normal file
@@ -0,0 +1,80 @@
|
||||
from datetime import datetime
|
||||
|
||||
class Author:
|
||||
pass
|
||||
class BaseRenderer:
|
||||
def __init__(self, item, chattype):
|
||||
self.renderer = list(item.values())[0]
|
||||
self.chattype = chattype
|
||||
self.author = Author()
|
||||
|
||||
def get_snippet(self):
|
||||
self.type = self.chattype
|
||||
self.id = self.renderer.get('id')
|
||||
timestampUsec = int(self.renderer.get("timestampUsec",0))
|
||||
self.timestamp = int(timestampUsec/1000)
|
||||
self.datetime = self.get_datetime(timestampUsec)
|
||||
self.message = self.get_message(self.renderer)
|
||||
self.id = self.renderer.get('id')
|
||||
self.amountValue= 0.0
|
||||
self.amountString = ""
|
||||
self.currency= ""
|
||||
self.bgColor = 0
|
||||
|
||||
def get_authordetails(self):
|
||||
self.author.badgeUrl = ""
|
||||
(self.author.isVerified,
|
||||
self.author.isChatOwner,
|
||||
self.author.isChatSponsor,
|
||||
self.author.isChatModerator) = (
|
||||
self.get_badges(self.renderer)
|
||||
)
|
||||
self.author.channelId = self.renderer.get("authorExternalChannelId")
|
||||
self.author.channelUrl = "http://www.youtube.com/channel/"+self.author.channelId
|
||||
self.author.name = self.renderer["authorName"]["simpleText"]
|
||||
self.author.imageUrl= self.renderer["authorPhoto"]["thumbnails"][1]["url"]
|
||||
|
||||
|
||||
|
||||
def get_message(self,renderer):
|
||||
message = ''
|
||||
if renderer.get("message"):
|
||||
runs=renderer["message"].get("runs")
|
||||
if runs:
|
||||
for r in runs:
|
||||
if r:
|
||||
if r.get('emoji'):
|
||||
message += r['emoji'].get('shortcuts',[''])[0]
|
||||
else:
|
||||
message += r.get('text','')
|
||||
return message
|
||||
|
||||
def get_badges(self,renderer):
|
||||
isVerified = False
|
||||
isChatOwner = False
|
||||
isChatSponsor = False
|
||||
isChatModerator = False
|
||||
badges=renderer.get("authorBadges")
|
||||
if badges:
|
||||
for badge in badges:
|
||||
author_type = badge["liveChatAuthorBadgeRenderer"]["accessibility"]["accessibilityData"]["label"]
|
||||
if author_type == '確認済み':
|
||||
isVerified = True
|
||||
if author_type == '所有者':
|
||||
isChatOwner = True
|
||||
if 'メンバー' in author_type:
|
||||
isChatSponsor = True
|
||||
self.get_badgeurl(badge)
|
||||
if author_type == 'モデレーター':
|
||||
isChatModerator = True
|
||||
return isVerified, isChatOwner, isChatSponsor, isChatModerator
|
||||
|
||||
|
||||
def get_badgeurl(self,badge):
|
||||
self.author.badgeUrl = badge["liveChatAuthorBadgeRenderer"]["customThumbnail"]["thumbnails"][0]["url"]
|
||||
|
||||
|
||||
|
||||
def get_datetime(self,timestamp):
|
||||
dt = datetime.fromtimestamp(timestamp/1000000)
|
||||
return dt.strftime('%Y-%m-%d %H:%M:%S')
|
||||
37
pytchat/processors/default/renderer/currency.py
Normal file
37
pytchat/processors/default/renderer/currency.py
Normal file
@@ -0,0 +1,37 @@
|
||||
'''
|
||||
YouTubeスーパーチャットで使用される通貨の記号とレート検索用の略号の
|
||||
対応表
|
||||
Key:
|
||||
YouTubeスーパーチャットで使用される通貨の記号
|
||||
(アルファベットで終わる場合、0xA0(&npsp)が付く)
|
||||
Value:
|
||||
fxtext: 3文字の通貨略称
|
||||
jptest: 日本語テキスト
|
||||
'''
|
||||
symbols = {
|
||||
"$": {"fxtext": "USD", "jptext": "米・ドル"},
|
||||
"A$": {"fxtext": "AUD", "jptext": "オーストラリア・ドル"},
|
||||
"CA$": {"fxtext": "CAD", "jptext": "カナダ・ドル"},
|
||||
"CHF\xa0": {"fxtext": "CHF", "jptext": "スイス・フラン"},
|
||||
"COP\xa0": {"fxtext": "COP", "jptext": "コロンビア・ペソ"},
|
||||
"HK$": {"fxtext": "HKD", "jptext": "香港・ドル"},
|
||||
"HUF\xa0": {"fxtext": "HUF", "jptext": "ハンガリー・フォリント"},
|
||||
"MX$": {"fxtext": "MXN", "jptext": "メキシコ・ペソ"},
|
||||
"NT$": {"fxtext": "TWD", "jptext": "台湾・ドル"},
|
||||
"NZ$": {"fxtext": "NZD", "jptext": "ニュージーランド・ドル"},
|
||||
"PHP\xa0": {"fxtext": "PHP", "jptext": "フィリピン・ペソ"},
|
||||
"PLN\xa0": {"fxtext": "PLN", "jptext": "ポーランド・ズロチ"},
|
||||
"R$": {"fxtext": "BRL", "jptext": "ブラジル・レアル"},
|
||||
"RUB\xa0": {"fxtext": "RUB", "jptext": "ロシア・ルーブル"},
|
||||
"SEK\xa0": {"fxtext": "SEK", "jptext": "スウェーデン・クローネ"},
|
||||
"£": {"fxtext": "GBP", "jptext": "英・ポンド"},
|
||||
"₩": {"fxtext": "KRW", "jptext": "韓国・ウォン"},
|
||||
"€": {"fxtext": "EUR", "jptext": "欧・ユーロ"},
|
||||
"₹": {"fxtext": "INR", "jptext": "インド・ルピー"},
|
||||
"¥": {"fxtext": "JPY", "jptext": "日本・円"},
|
||||
"PEN\xa0": {"fxtext": "PEN", "jptext": "ペルー・ヌエボ・ソル"},
|
||||
"ARS\xa0": {"fxtext": "ARS", "jptext": "アルゼンチン・ペソ"},
|
||||
"CLP\xa0": {"fxtext": "CLP", "jptext": "チリ・ペソ"},
|
||||
"NOK\xa0": {"fxtext": "NOK", "jptext": "ノルウェー・クローネ"},
|
||||
"BAM\xa0": {"fxtext": "BAM", "jptext": "ボスニア・兌換マルカ"}
|
||||
}
|
||||
18
pytchat/processors/default/renderer/legacypaid.py
Normal file
18
pytchat/processors/default/renderer/legacypaid.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from .base import BaseRenderer
|
||||
class LiveChatLegacyPaidMessageRenderer(BaseRenderer):
|
||||
def __init__(self, item):
|
||||
super().__init__(item, "newSponsor")
|
||||
|
||||
|
||||
|
||||
def get_authordetails(self):
|
||||
super().get_authordetails()
|
||||
self.author.isChatSponsor = True
|
||||
|
||||
|
||||
def get_message(self,renderer):
|
||||
message = (renderer["eventText"]["runs"][0]["text"]
|
||||
)+' / '+(renderer["detailText"]["simpleText"])
|
||||
return message
|
||||
|
||||
|
||||
36
pytchat/processors/default/renderer/paidmessage.py
Normal file
36
pytchat/processors/default/renderer/paidmessage.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import re
|
||||
from . import currency
|
||||
from .base import BaseRenderer
|
||||
superchat_regex = re.compile(r"^(\D*)(\d{1,3}(,\d{3})*(\.\d*)*\b)$")
|
||||
|
||||
class LiveChatPaidMessageRenderer(BaseRenderer):
|
||||
def __init__(self, item):
|
||||
super().__init__(item, "superChat")
|
||||
|
||||
|
||||
def get_snippet(self):
|
||||
super().get_snippet()
|
||||
|
||||
self.author.name = self.renderer["authorName"]["simpleText"]
|
||||
|
||||
amountDisplayString, symbol, amount =(
|
||||
self.get_amountdata(self.renderer)
|
||||
)
|
||||
self.message = self.get_message(self.renderer)
|
||||
self.amountValue= amount
|
||||
self.amountString = amountDisplayString
|
||||
self.currency= currency.symbols[symbol]["fxtext"] if currency.symbols.get(symbol) else symbol
|
||||
self.bgColor= self.renderer.get("bodyBackgroundColor", 0)
|
||||
|
||||
|
||||
|
||||
def get_amountdata(self,renderer):
|
||||
amountDisplayString = renderer["purchaseAmountText"]["simpleText"]
|
||||
m = superchat_regex.search(amountDisplayString)
|
||||
if m:
|
||||
symbol = m.group(1)
|
||||
amount = float(m.group(2).replace(',',''))
|
||||
else:
|
||||
symbol = ""
|
||||
amount = 0.0
|
||||
return amountDisplayString, symbol, amount
|
||||
13
pytchat/processors/default/renderer/paidsticker.py
Normal file
13
pytchat/processors/default/renderer/paidsticker.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import re
|
||||
from . import currency
|
||||
from .paidmessage import LiveChatPaidMessageRenderer
|
||||
|
||||
class LiveChatPaidStickerRenderer(LiveChatPaidMessageRenderer):
|
||||
def __init__(self, item):
|
||||
super().__init__(item, "superSticker")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
4
pytchat/processors/default/renderer/textmessage.py
Normal file
4
pytchat/processors/default/renderer/textmessage.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from .base import BaseRenderer
|
||||
class LiveChatTextMessageRenderer(BaseRenderer):
|
||||
def __init__(self, item):
|
||||
super().__init__(item, "textMessage")
|
||||
13
pytchat/processors/json_display_processor.py
Normal file
13
pytchat/processors/json_display_processor.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import json
|
||||
from .chat_processor import ChatProcessor
|
||||
|
||||
class JsonDisplayProcessor(ChatProcessor):
|
||||
|
||||
def process(self,chat_components: list):
|
||||
if chat_components:
|
||||
for component in chat_components:
|
||||
chatdata = component.get('chatdata')
|
||||
if chatdata:
|
||||
for chat in chatdata:
|
||||
print(json.dumps(chat,ensure_ascii=False)[:200])
|
||||
|
||||
46
pytchat/processors/jsonfile_archive_processor.py
Normal file
46
pytchat/processors/jsonfile_archive_processor.py
Normal file
@@ -0,0 +1,46 @@
|
||||
import json
|
||||
import os
|
||||
import datetime
|
||||
from .chat_processor import ChatProcessor
|
||||
|
||||
class JsonfileArchiveProcessor(ChatProcessor):
|
||||
def __init__(self,filepath):
|
||||
super().__init__()
|
||||
if os.path.exists(filepath):
|
||||
print('filepath is already exists!: ')
|
||||
print(' '+filepath)
|
||||
newpath=os.path.dirname(filepath) + \
|
||||
'/'+datetime.datetime.now() \
|
||||
.strftime('%Y-%m-%d %H-%M-%S')+'.data'
|
||||
|
||||
print('created alternate filename:')
|
||||
print(' '+newpath)
|
||||
self.filepath = newpath
|
||||
else:
|
||||
print('filepath: '+filepath)
|
||||
self.filepath = filepath
|
||||
|
||||
def process(self,chat_components: list):
|
||||
if chat_components:
|
||||
with open(self.filepath, mode='a', encoding = 'utf-8') as f:
|
||||
for component in chat_components:
|
||||
if component:
|
||||
chatdata = component.get('chatdata')
|
||||
for action in chatdata:
|
||||
if action:
|
||||
if action.get("addChatItemAction"):
|
||||
if action["addChatItemAction"]["item"].get(
|
||||
"liveChatViewerEngagementMessageRenderer"):
|
||||
continue
|
||||
s = json.dumps(action,ensure_ascii = False)
|
||||
#print(s[:200])
|
||||
f.writelines(s+'\n')
|
||||
|
||||
def _parsedir(self,_dir):
|
||||
if _dir[-1]=='\\' or _dir[-1]=='/':
|
||||
separator =''
|
||||
else:
|
||||
separator ='/'
|
||||
os.makedirs(_dir + separator, exist_ok=True)
|
||||
return _dir + separator
|
||||
|
||||
61
pytchat/processors/simple_display_processor.py
Normal file
61
pytchat/processors/simple_display_processor.py
Normal file
@@ -0,0 +1,61 @@
|
||||
import json
|
||||
import os
|
||||
import traceback
|
||||
import datetime
|
||||
import time
|
||||
from .chat_processor import ChatProcessor
|
||||
##version 2
|
||||
class SimpleDisplayProcessor(ChatProcessor):
|
||||
|
||||
def process(self, chat_components: list):
|
||||
chatlist = []
|
||||
timeout = 0
|
||||
|
||||
if chat_components is None:
|
||||
return {"timeout":timeout, "chatlist":chatlist}
|
||||
for component in chat_components:
|
||||
timeout += component.get('timeout', 0)
|
||||
chatdata = component.get('chatdata')
|
||||
|
||||
if chatdata is None:break
|
||||
for action in chatdata:
|
||||
if action is None:continue
|
||||
if action.get('addChatItemAction') is None:continue
|
||||
if action['addChatItemAction'].get('item') is None:continue
|
||||
|
||||
root = action['addChatItemAction']['item'].get('liveChatTextMessageRenderer')
|
||||
|
||||
if root:
|
||||
author_name = root['authorName']['simpleText']
|
||||
message = self._parse_message(root.get('message'))
|
||||
purchase_amount_text = ''
|
||||
else:
|
||||
root = ( action['addChatItemAction']['item'].get('liveChatPaidMessageRenderer') or
|
||||
action['addChatItemAction']['item'].get('liveChatPaidMessageRenderer') )
|
||||
if root:
|
||||
author_name = root['authorName']['simpleText']
|
||||
message = self._parse_message(root.get('message'))
|
||||
purchase_amount_text = root['purchaseAmountText']['simpleText']
|
||||
else:
|
||||
continue
|
||||
chatlist.append(f'[{author_name}]: {message} {purchase_amount_text}')
|
||||
return {"timeout":timeout, "chatlist":chatlist}
|
||||
|
||||
def _parse_message(self,message):
|
||||
if message is None:
|
||||
return ''
|
||||
if message.get('simpleText'):
|
||||
return message['simpleText']
|
||||
elif message.get('runs'):
|
||||
runs = message['runs']
|
||||
tmp = ''
|
||||
for run in runs:
|
||||
if run.get('emoji'):
|
||||
tmp+=(run['emoji']['shortcuts'][0])
|
||||
elif run.get('text'):
|
||||
tmp+=(run['text'])
|
||||
return tmp
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
||||
Reference in New Issue
Block a user