Compare commits

...

4 Commits

12 changed files with 224 additions and 80 deletions

View File

@ -13,6 +13,7 @@ num2words = "*"
pyaudio = "*"
soundfile = "*"
numpy = "*"
pillow = "*"
[requires]
python_version = "3.9"

117
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "ec8c647f9d261eaa99df3678f82e3ad988581a7f8722d31c01a5057f443c0826"
"sha256": "ea641cba1add8b89dcbf9d467bc289fb4a29e8d6bceff2d9d7786be2a33365e8"
},
"pipfile-spec": 6,
"requires": {
@ -26,14 +26,23 @@
"cffi": {
"hashes": [
"sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813",
"sha256:04c468b622ed31d408fea2346bec5bbffba2cc44226302a0de1ade9f5ea3d373",
"sha256:06d7cd1abac2ffd92e65c0609661866709b4b2d82dd15f611e602b9b188b0b69",
"sha256:06db6321b7a68b2bd6df96d08a5adadc1fa0e8f419226e25b2a5fbf6ccc7350f",
"sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06",
"sha256:0f861a89e0043afec2a51fd177a567005847973be86f709bbb044d7f42fc4e05",
"sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea",
"sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee",
"sha256:1bf1ac1984eaa7675ca8d5745a8cb87ef7abecb5592178406e55858d411eadc0",
"sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396",
"sha256:24a570cd11895b60829e941f2613a4f79df1a27344cbbb82164ef2e0116f09c7",
"sha256:24ec4ff2c5c0c8f9c6b87d5bb53555bf267e1e6f70e52e5a9740d32861d36b6f",
"sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73",
"sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315",
"sha256:293e7ea41280cb28c6fcaaa0b1aa1f533b8ce060b9e701d78511e1e6c4a1de76",
"sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1",
"sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49",
"sha256:3c3f39fa737542161d8b0d680df2ec249334cd70a8f420f71c9304bd83c3cbed",
"sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892",
"sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482",
"sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058",
@ -41,6 +50,7 @@
"sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53",
"sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045",
"sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3",
"sha256:681d07b0d1e3c462dd15585ef5e33cb021321588bebd910124ef4f4fb71aef55",
"sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5",
"sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e",
"sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c",
@ -58,8 +68,10 @@
"sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e",
"sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991",
"sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6",
"sha256:cc5a8e069b9ebfa22e26d0e6b97d6f9781302fe7f4f2b8776c3e1daea35f1adc",
"sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1",
"sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406",
"sha256:df5052c5d867c1ea0b311fb7c3cd28b19df469c056f7fdcfe88c7473aa63e333",
"sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d",
"sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"
],
@ -105,33 +117,72 @@
},
"numpy": {
"hashes": [
"sha256:2428b109306075d89d21135bdd6b785f132a1f5a3260c371cee1fae427e12727",
"sha256:377751954da04d4a6950191b20539066b4e19e3b559d4695399c5e8e3e683bf6",
"sha256:4703b9e937df83f5b6b7447ca5912b5f5f297aba45f91dbbbc63ff9278c7aa98",
"sha256:471c0571d0895c68da309dacee4e95a0811d0a9f9f532a48dc1bea5f3b7ad2b7",
"sha256:61d5b4cf73622e4d0c6b83408a16631b670fc045afd6540679aa35591a17fe6d",
"sha256:6c915ee7dba1071554e70a3664a839fbc033e1d6528199d4621eeaaa5487ccd2",
"sha256:6e51e417d9ae2e7848314994e6fc3832c9d426abce9328cf7571eefceb43e6c9",
"sha256:719656636c48be22c23641859ff2419b27b6bdf844b36a2447cb39caceb00935",
"sha256:780ae5284cb770ade51d4b4a7dce4faa554eb1d88a56d0e8b9f35fca9b0270ff",
"sha256:878922bf5ad7550aa044aa9301d417e2d3ae50f0f577de92051d739ac6096cee",
"sha256:924dc3f83de20437de95a73516f36e09918e9c9c18d5eac520062c49191025fb",
"sha256:97ce8b8ace7d3b9288d88177e66ee75480fb79b9cf745e91ecfe65d91a856042",
"sha256:9c0fab855ae790ca74b27e55240fe4f2a36a364a3f1ebcfd1fb5ac4088f1cec3",
"sha256:9cab23439eb1ebfed1aaec9cd42b7dc50fc96d5cd3147da348d9161f0501ada5",
"sha256:a8e6859913ec8eeef3dbe9aed3bf475347642d1cdd6217c30f28dee8903528e6",
"sha256:aa046527c04688af680217fffac61eec2350ef3f3d7320c07fd33f5c6e7b4d5f",
"sha256:abc81829c4039e7e4c30f7897938fa5d4916a09c2c7eb9b244b7a35ddc9656f4",
"sha256:bad70051de2c50b1a6259a6df1daaafe8c480ca98132da98976d8591c412e737",
"sha256:c73a7975d77f15f7f68dacfb2bca3d3f479f158313642e8ea9058eea06637931",
"sha256:d15007f857d6995db15195217afdbddfcd203dfaa0ba6878a2f580eaf810ecd6",
"sha256:d76061ae5cab49b83a8cf3feacefc2053fac672728802ac137dd8c4123397677",
"sha256:e8e4fbbb7e7634f263c5b0150a629342cc19b47c5eba8d1cd4363ab3455ab576",
"sha256:e9459f40244bb02b2f14f6af0cd0732791d72232bbb0dc4bab57ef88e75f6935",
"sha256:edb1f041a9146dcf02cd7df7187db46ab524b9af2515f392f337c7cbbf5b52cd"
"sha256:1676b0a292dd3c99e49305a16d7a9f42a4ab60ec522eac0d3dd20cdf362ac010",
"sha256:16f221035e8bd19b9dc9a57159e38d2dd060b48e93e1d843c49cb370b0f415fd",
"sha256:43909c8bb289c382170e0282158a38cf306a8ad2ff6dfadc447e90f9961bef43",
"sha256:4e465afc3b96dbc80cf4a5273e5e2b1e3451286361b4af70ce1adb2984d392f9",
"sha256:55b745fca0a5ab738647d0e4db099bd0a23279c32b31a783ad2ccea729e632df",
"sha256:5d050e1e4bc9ddb8656d7b4f414557720ddcca23a5b88dd7cff65e847864c400",
"sha256:637d827248f447e63585ca3f4a7d2dfaa882e094df6cfa177cc9cf9cd6cdf6d2",
"sha256:6690080810f77485667bfbff4f69d717c3be25e5b11bb2073e76bb3f578d99b4",
"sha256:66fbc6fed94a13b9801fb70b96ff30605ab0a123e775a5e7a26938b717c5d71a",
"sha256:67d44acb72c31a97a3d5d33d103ab06d8ac20770e1c5ad81bdb3f0c086a56cf6",
"sha256:6ca2b85a5997dabc38301a22ee43c82adcb53ff660b89ee88dded6b33687e1d8",
"sha256:6e51534e78d14b4a009a062641f465cfaba4fdcb046c3ac0b1f61dd97c861b1b",
"sha256:70eb5808127284c4e5c9e836208e09d685a7978b6a216db85960b1a112eeace8",
"sha256:830b044f4e64a76ba71448fce6e604c0fc47a0e54d8f6467be23749ac2cbd2fb",
"sha256:8b7bb4b9280da3b2856cb1fc425932f46fba609819ee1c62256f61799e6a51d2",
"sha256:a9c65473ebc342715cb2d7926ff1e202c26376c0dcaaee85a1fd4b8d8c1d3b2f",
"sha256:c1c09247ccea742525bdb5f4b5ceeacb34f95731647fe55774aa36557dbb5fa4",
"sha256:c5bf0e132acf7557fc9bb8ded8b53bbbbea8892f3c9a1738205878ca9434206a",
"sha256:db250fd3e90117e0312b611574cd1b3f78bec046783195075cbd7ba9c3d73f16",
"sha256:e515c9a93aebe27166ec9593411c58494fa98e5fcc219e47260d9ab8a1cc7f9f",
"sha256:e55185e51b18d788e49fe8305fd73ef4470596b33fc2c1ceb304566b99c71a69",
"sha256:ea9cff01e75a956dbee133fa8e5b68f2f92175233de2f88de3a682dd94deda65",
"sha256:f1452578d0516283c87608a5a5548b0cdde15b99650efdfd85182102ef7a7c17",
"sha256:f39a995e47cb8649673cfa0579fbdd1cdd33ea497d1728a6cb194d6252268e48"
],
"index": "pypi",
"version": "==1.20.2"
"version": "==1.20.3"
},
"pillow": {
"hashes": [
"sha256:01425106e4e8cee195a411f729cff2a7d61813b0b11737c12bd5991f5f14bcd5",
"sha256:031a6c88c77d08aab84fecc05c3cde8414cd6f8406f4d2b16fed1e97634cc8a4",
"sha256:083781abd261bdabf090ad07bb69f8f5599943ddb539d64497ed021b2a67e5a9",
"sha256:0d19d70ee7c2ba97631bae1e7d4725cdb2ecf238178096e8c82ee481e189168a",
"sha256:0e04d61f0064b545b989126197930807c86bcbd4534d39168f4aa5fda39bb8f9",
"sha256:12e5e7471f9b637762453da74e390e56cc43e486a88289995c1f4c1dc0bfe727",
"sha256:22fd0f42ad15dfdde6c581347eaa4adb9a6fc4b865f90b23378aa7914895e120",
"sha256:238c197fc275b475e87c1453b05b467d2d02c2915fdfdd4af126145ff2e4610c",
"sha256:3b570f84a6161cf8865c4e08adf629441f56e32f180f7aa4ccbd2e0a5a02cba2",
"sha256:463822e2f0d81459e113372a168f2ff59723e78528f91f0bd25680ac185cf797",
"sha256:4d98abdd6b1e3bf1a1cbb14c3895226816e666749ac040c4e2554231068c639b",
"sha256:5afe6b237a0b81bd54b53f835a153770802f164c5570bab5e005aad693dab87f",
"sha256:5b70110acb39f3aff6b74cf09bb4169b167e2660dabc304c1e25b6555fa781ef",
"sha256:5cbf3e3b1014dddc45496e8cf38b9f099c95a326275885199f427825c6522232",
"sha256:624b977355cde8b065f6d51b98497d6cd5fbdd4f36405f7a8790e3376125e2bb",
"sha256:63728564c1410d99e6d1ae8e3b810fe012bc440952168af0a2877e8ff5ab96b9",
"sha256:66cc56579fd91f517290ab02c51e3a80f581aba45fd924fcdee01fa06e635812",
"sha256:6c32cc3145928c4305d142ebec682419a6c0a8ce9e33db900027ddca1ec39178",
"sha256:8bb1e155a74e1bfbacd84555ea62fa21c58e0b4e7e6b20e4447b8d07990ac78b",
"sha256:95d5ef984eff897850f3a83883363da64aae1000e79cb3c321915468e8c6add5",
"sha256:a013cbe25d20c2e0c4e85a9daf438f85121a4d0344ddc76e33fd7e3965d9af4b",
"sha256:a787ab10d7bb5494e5f76536ac460741788f1fbce851068d73a87ca7c35fc3e1",
"sha256:a7d5e9fad90eff8f6f6106d3b98b553a88b6f976e51fce287192a5d2d5363713",
"sha256:aac00e4bc94d1b7813fe882c28990c1bc2f9d0e1aa765a5f2b516e8a6a16a9e4",
"sha256:b91c36492a4bbb1ee855b7d16fe51379e5f96b85692dc8210831fbb24c43e484",
"sha256:c03c07ed32c5324939b19e36ae5f75c660c81461e312a41aea30acdd46f93a7c",
"sha256:c5236606e8570542ed424849f7852a0ff0bce2c4c8d0ba05cc202a5a9c97dee9",
"sha256:c6b39294464b03457f9064e98c124e09008b35a62e3189d3513e5148611c9388",
"sha256:cb7a09e173903541fa888ba010c345893cd9fc1b5891aaf060f6ca77b6a3722d",
"sha256:d68cb92c408261f806b15923834203f024110a2e2872ecb0bd2a110f89d3c602",
"sha256:dc38f57d8f20f06dd7c3161c59ca2c86893632623f33a42d592f097b00f720a9",
"sha256:e98eca29a05913e82177b3ba3d198b1728e164869c613d76d0de4bde6768a50e",
"sha256:f217c3954ce5fd88303fc0c317af55d5e0204106d86dea17eb8205700d47dec2"
],
"index": "pypi",
"version": "==8.2.0"
},
"pyaudio": {
"hashes": [
@ -211,14 +262,6 @@
"index": "pypi",
"version": "==2.25.1"
},
"six": {
"hashes": [
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.15.0"
},
"soundfile": {
"hashes": [
"sha256:2d17e0a6fc2af0d6c1d868bafa5ec80aae6e186a97fec8db07ad6af29842fbc7",
@ -248,11 +291,11 @@
},
"websocket-client": {
"hashes": [
"sha256:44b5df8f08c74c3d82d28100fdc81f4536809ce98a17f0757557813275fbb663",
"sha256:63509b41d158ae5b7f67eb4ad20fecbb4eee99434e73e140354dc3ff8e09716f"
"sha256:5051b38a2f4c27fbd7ca077ebb23ec6965a626ded5a95637f36be1b35b6c4f81",
"sha256:57f876f1af4731cacb806cf54d02f5fbf75dee796053b9a5b94fd7c1d9621db9"
],
"index": "pypi",
"version": "==0.58.0"
"version": "==1.0.0"
}
},
"develop": {}

View File

@ -14,9 +14,10 @@ class LIFECYCLE_MESSAGES(Enum):
class ChatProcess(Process, ABC):
def __init__(self, event_poll_frequency):
def __init__(self, event_poll_frequency, process_name):
super().__init__()
self._event_poll_frequency = event_poll_frequency
self.__class__.__name__ = process_name
self._message_queue = Queue()
self._pipe, self._caller_pipe = Pipe()

View File

@ -34,7 +34,7 @@ class Process(ChatProcess):
running = not self.state == STATES.RUNNING
text = 'Fake chat activated!' if running else 'Disabled fake chat'
sys_msg = Message(text, Process.CHAT_NAME, self.__class__.__name__, AUTHOR_TYPES.SYSTEM)
sys_msg = Message(text, Process.CHAT_NAME, self.__class__.__name__, AUTHOR_TYPES.SYSTEM, self.__class__.__name__)
self._message_queue.put(sys_msg)
return STATES.RUNNING if running else STATES.PAUSED
@ -71,7 +71,7 @@ class Process(ChatProcess):
'USRE VERY EXCITE POGGGG POG POGGGGGGGGGGGGGGGGGGGGGGGG POGPOGPOGGGG',
'spaamamspmapmdpmaspmspsapmspmapsmpasmspmapmpasmspmapmspampsmpaspaspamapmspmapmspmapsmpamspamspmapsmpmaspmapmspamspmapsmpamspmpamspms',
])
fake_message = Message(text, author_name, author_id, author_type)
fake_message = Message(text, author_name, author_id, author_type, self.__class__.__name__)
self._message_queue.put(fake_message)
return random.random() * self._max_delay

View File

@ -3,13 +3,26 @@ import time
from multiprocessing import Process, Pipe
from enum import Enum, auto
from traceback import format_exception
import os
import tempfile
from itertools import chain, islice
import requests
from miniirc import ircv3_message_parser
from PIL import Image
from . import AUTHOR_TYPES, Message
from .ChatProcess import ChatProcess
def split_every(n, iterable):
i = iter(iterable)
piece = list(islice(i, n))
while piece:
yield piece
piece = list(islice(i, n))
class CONTROL_MESSAGES(Enum):
RESTART = auto()
@ -82,6 +95,9 @@ class Process(ChatProcess):
self.state = STATES.CONNECTING
self._tmp = tempfile.TemporaryDirectory()
self._emotes = {}
@property
def keybinds(self):
return {
@ -99,7 +115,7 @@ class Process(ChatProcess):
message = status_messages.get(new_state)
if message is not None:
sys_msg = Message(message, Process.CHAT_NAME, self.__class__.__name__, AUTHOR_TYPES.SYSTEM)
sys_msg = Message(message, Process.CHAT_NAME, self.__class__.__name__, AUTHOR_TYPES.SYSTEM, self.__class__.__name__)
self._message_queue.put(sys_msg)
def on_connecting(self, next_state):
@ -125,7 +141,7 @@ class Process(ChatProcess):
for message in messages.splitlines():
cmd, hostmask, tags, args = ircv3_message_parser(message)
if cmd == 'PRIVMSG':
normalized_message = Process.normalize_message(hostmask, tags, args)
normalized_message = self.normalize_message(hostmask, tags, args)
self._message_queue.put(normalized_message)
elif cmd == 'USERNOTICE':
normalized_message = Process.normalize_event(hostmask, tags, args)
@ -150,24 +166,71 @@ class Process(ChatProcess):
self._ws.terminate()
return None
@classmethod
def parse_badges(self, tags):
if isinstance(tags.get('badges'), str):
return [badge.split('/')[0] for badge in tags['badges'].split(',')]
else:
return []
@classmethod
def parse_message(self, args):
# First arg is the room (which we dont care about, since it should only ever be #user)
# First character of the second arg is the data delimiter (:)
return ' '.join(args[1:])[1:]
@classmethod
def normalize_message(cls, hostmask, tags, args):
# TODO: Amongus dum poster on model forhead please and yes thank
def parse_emotes(self, message, tags):
raw_emotes = tags.get('emotes')
if not isinstance(raw_emotes, str):
return message, []
badges = Process.parse_badges(tags)
message = Process.parse_message(args)
points_where_we_put_the_delimiters = []
emotes_used = []
for emote in raw_emotes.split('/'):
emote_id, indexesisis = emote.split(':')
usages = [[int(index) for index in usage.split('-')] for usage in indexesisis.split(',')]
emote_name = message[usages[0][0]:usages[0][1] + 1].replace(':', '_')
points_where_we_put_the_delimiters.extend(chain.from_iterable(usages))
emotes_used.append(emote_name)
if self._emotes.get(emote_name) is not None:
continue
response = requests.get(f'http://static-cdn.jtvnw.net/emoticons/v1/{emote_id}/2.0', stream=True)
response.raise_for_status()
filename = os.path.join(self._tmp.name, emote_id + '.png')
Image.open(response.raw).convert(mode='RGBA').save(filename)
# with open(filename, 'wb') as f:
# f.write(response.content)
self._emotes[emote_name] = filename
slice = []
indecies = list(split_every(2, sorted(points_where_we_put_the_delimiters)))
for index in range(len(indecies)):
start, end = indecies[index]
next_item = indecies[index + 1] if index + 1 < len(indecies) else None
if index == 0:
slice.append(message[:start])
slice.append(message[start:end + 1].replace(':', '_'))
if next_item:
slice.append(message[end + 1:next_item[0]])
else:
slice.append(message[end + 1:])
return ':'.join(slice), emotes_used
def normalize_message(self, hostmask, tags, args):
badges = self.parse_badges(tags)
message = self.parse_message(args)
message, emotes_used = self.parse_emotes(message, tags)
emote_map = {emote_name: self._emotes[emote_name] for emote_name in emotes_used} if len(emotes_used) else None
bits = tags.get('bits')
if bits:
@ -186,13 +249,12 @@ class Process(ChatProcess):
author_color = tags['color'] if isinstance(tags.get('color'), str) else None
return Message(message, tags.get('display-name', hostmask[0]), tags.get('user-id'), author_type,
author_color=author_color, monitization=monitization)
return Message(message, tags.get('display-name', hostmask[0]), tags.get('user-id'), author_type, self.__class__.__name__,
author_color=author_color, monitization=monitization, emotes=emote_map)
@classmethod
def normalize_event(cls, hostmask, tags, args):
badges = Process.parse_badges(tags)
user_message = Process.parse_message(args)
def normalize_event(self, hostmask, tags, args):
badges = self.parse_badges(tags)
user_message = self.parse_message(args)
message = f"*{tags['system-msg'].strip()}*"
if user_message != '':
@ -211,5 +273,5 @@ class Process(ChatProcess):
event = EVENTS.__members__[tags['msg-id'].upper()]
return Message(message, tags.get('display-name', hostmask[0]), tags.get('user-id'), author_type,
return Message(message, tags.get('display-name', hostmask[0]), tags.get('user-id'), author_type, self.__class__.__name__,
author_color=author_color, for_event=event)

View File

@ -113,7 +113,7 @@ class Process(ChatProcess):
self._page_token = data['nextPageToken']
if len(data['items']):
for message in data['items']:
normalized = Process.normalize_message(message)
normalized = self.normalize_message(message)
self._message_queue.put(normalized)
sleep_milis = max(data['pollingIntervalMillis'], 5000)
@ -132,7 +132,7 @@ class Process(ChatProcess):
message = status_messages.get(new_state)
if message is not None:
sys_msg = Message(message, Process.CHAT_NAME, self.__class__.__name__, AUTHOR_TYPES.SYSTEM)
sys_msg = Message(message, Process.CHAT_NAME, self.__class__.__name__, AUTHOR_TYPES.SYSTEM, self.__class__.__name__)
self._message_queue.put(sys_msg)
def process_messages(self, message_type, args, next_state):
@ -142,14 +142,15 @@ class Process(ChatProcess):
elif message_type == CONTROL_MESSAGES.RESTART:
if self.state == STATES.POLLING:
self._message_queue.put(Message('Restart requested, but service is in healthy state! Ignoring...',
Process.CHAT_NAME, self.__class__.__name__, AUTHOR_TYPES.SYSTEM))
Process.CHAT_NAME, self.__class__.__name__, AUTHOR_TYPES.SYSTEM,
self.__class__.__name__))
else:
self._message_queue.put(Message('Restarting service...',
Process.CHAT_NAME, self.__class__.__name__, AUTHOR_TYPES.SYSTEM))
Process.CHAT_NAME, self.__class__.__name__, AUTHOR_TYPES.SYSTEM,
self.__class__.__name__))
return None, None
@classmethod
def normalize_message(cls, message):
def normalize_message(self, message):
if message['authorDetails']['isChatOwner']:
author_type = AUTHOR_TYPES.OWNER
elif message['authorDetails']['isChatModerator']:
@ -162,7 +163,7 @@ class Process(ChatProcess):
text = message['snippet']['displayMessage']
author_name = message['authorDetails']['displayName']
author_id = message['authorDetails']['channelId']
return Message(text, author_name, author_id, author_type)
return Message(text, author_name, author_id, author_type, self.__class__.__name__)
def request_oauth_consent(self):
params = {

View File

@ -11,17 +11,19 @@ class AUTHOR_TYPES(Enum):
class Message:
def __init__(self, text, author_name, author_id, author_type,
author_color=None, monitization=None, for_event=None):
def __init__(self, text, author_name, author_id, author_type, via,
author_color=None, monitization=None, for_event=None, emotes=None):
self.text = text
self.author_name = author_name
self.author_id = author_id
if not isinstance(author_type, AUTHOR_TYPES):
raise ValueError('author_type must be an instance of AUTHOR_TYPES enum')
self.author_type = author_type
self.via = via
self.author_color = author_color
self.monitization_local, self.monitization = monitization or (None, None)
self.for_event = for_event
self.emotes = emotes
def __repr__(self):
return f"{self.author_name} - \"{self.text}\" : author_type = {self.author_type}, monitization = {self.monitization}, for_event = {self.for_event}"

14
main.py
View File

@ -15,7 +15,7 @@ if __name__ == '__main__':
EVENT_POLL_FREQUENCY = config['event_poll_frequency']
# Load dynamic compoents
chat_processes = []
chat_processes = {}
for chat_name, chat_config in config.get('chat', {}).items():
if not chat_config['enabled']:
continue
@ -23,12 +23,12 @@ if __name__ == '__main__':
chat_module = importlib.import_module(f'.{chat_name}', package='chats')
try:
chat_process = chat_module.Process(EVENT_POLL_FREQUENCY, **chat_config)
chat_processes.append(chat_process)
chat_process = chat_module.Process(EVENT_POLL_FREQUENCY, chat_name, **chat_config)
chat_processes[chat_name] = chat_process
except Exception as e:
print(f'Failed to initalize {chat_name} - {e}')
if len(chat_processes) == 0:
if len(chat_processes.keys()) == 0:
print('No chats configured!')
plugins = []
@ -45,7 +45,7 @@ if __name__ == '__main__':
# Start the app and subprocesses
app = App(chat_processes, plugins)
for process in chat_processes:
for process in chat_processes.values():
process.start()
app.start()
@ -54,7 +54,7 @@ if __name__ == '__main__':
if not app.is_alive():
break
for process in chat_processes:
for process in chat_processes.values():
# HACK: need to find a generic way to do this
if process.control_pipe.poll():
chat_control_msg = process.control_pipe.recv()
@ -67,7 +67,7 @@ if __name__ == '__main__':
time.sleep(EVENT_POLL_FREQUENCY)
# Cleanup
for process in chat_processes:
for process in chat_processes.values():
process.control_pipe.send({'type': LIFECYCLE_MESSAGES.SHUTDOWN})
process.join(10)
if process.exitcode is None:

View File

@ -49,7 +49,7 @@ class Plugin(PluginBase):
if not (message.monitization is not None and message.monitization == float(criterion['monitization'])):
continue
if criterion.get('regex'):
if re.search(criterion['regex'], message.text) is None:
if re.search(criterion['regex'], message.text, flags=re.IGNORECASE) is None:
continue
return True

View File

@ -38,13 +38,13 @@ class App(Process):
if event.type == pygame.QUIT:
self._running = False
elif event.type == pygame.KEYDOWN:
for process in self._chat_processes:
for process in self._chat_processes.values():
bound_action = process.keybinds.get(event.key)
if bound_action is not None:
process.control_pipe.send(bound_action)
def tick(self, dt):
for process in self._chat_processes:
for process in self._chat_processes.values():
if not process.message_queue.empty():
message = process.message_queue.get()
for plugin in self._plugins:

16
ui/ImageFragment.py Normal file
View File

@ -0,0 +1,16 @@
import pygame.image
class ImageFragment:
def __init__(self, filepath):
self._image = pygame.image.load(filepath)
@property
def rect(self):
return self._image.get_rect()
def tick(self, dt):
pass
def draw(self):
return self._image.convert_alpha()

View File

@ -9,8 +9,10 @@ from chats import AUTHOR_TYPES
from . import BG_COLOR, XTERM_COLORS
from .TextFragment import TextFragment
from .ImageFragment import ImageFragment
hex_color_regex = re.compile('^#[a-fA-F0-9]{6}$')
emote_regex = re.compile(':([A-z0-9]+):')
def closest_xterm(target_color):
@ -21,7 +23,7 @@ def closest_xterm(target_color):
xr, xg, xb = xterm_color
distance = math.sqrt(abs(r - xr) ** 2 + abs(g - xg) ** 2 + abs(b - xb) ** 2)
if distance == 0:
return xterm_color
return index
distances.append((distance, index))
return min(distances)[1]
@ -70,19 +72,35 @@ class MessageView:
# TODO: Handle icons, emoji with special fragment
fragments = list(TextFragment.from_unwrapped(f'[ {author_name} ]', tag_fg, tag_bg, screen_width))
fragments.extend(
TextFragment.from_unwrapped(text, text_fg, text_bg, screen_width, first_line_offset=fragments[-1].rect.width)
)
offset_x = fragments[-1].rect.width
for index, text_or_emote in enumerate(emote_regex.split(text)):
if index % 2 == 0:
if text_or_emote == '':
continue
fragments.extend(
TextFragment.from_unwrapped(text_or_emote.strip(), text_fg, text_bg, screen_width, first_line_offset=offset_x)
)
offset_x = fragments[-1].rect.width
else:
img_fragment = ImageFragment(message.emotes[text_or_emote])
offset_x += img_fragment.rect.width
fragments.append(img_fragment)
if offset_x > self.screen_size[0]:
offset_x = 0
return fragments
def layout_fragments(self):
offset_x, offset_y = 0, 0
offset_x, offset_y, max_y = 0, 0, 0
for fragment in self._fragments:
max_y = max(max_y, fragment.rect.height)
if offset_x + fragment.rect.width > self.screen_size[0]:
offset_y += fragment.rect.height
offset_y += max_y
yield 0, offset_y, fragment
offset_x = fragment.rect.width + self._padding[0]
max_y = 0
else:
yield offset_x, offset_y, fragment
offset_x += fragment.rect.width + self._padding[0]