mirror of
https://github.com/liyunze-coding/TwitchPlaysX.git
synced 2026-11-02 20:03:33 +00:00
144 lines
4.7 KiB
Python
144 lines
4.7 KiB
Python
import time
|
|
import subprocess
|
|
import platform
|
|
import socket
|
|
import re
|
|
import asyncio
|
|
import win32api # Windows only
|
|
import win32con # Windows only
|
|
|
|
|
|
class TwitchPlaysX:
|
|
def __init__(
|
|
self, key_duration, key_delay, keymap, channel_name, x_limit, kill_commands
|
|
) -> None:
|
|
self.os = self.get_os()
|
|
self.key_duration = key_duration
|
|
self.key_delay = key_delay
|
|
self.keymap = keymap
|
|
self.channel_name = channel_name.lower()
|
|
self.x_limit = x_limit
|
|
self.kill_commands = kill_commands
|
|
self.controls_queue = []
|
|
|
|
# not tested
|
|
def sendKeyLinux(self, controls):
|
|
button = controls["control"]
|
|
duration = controls["duration"] / 1000 # convert milliseconds to seconds
|
|
|
|
subprocess.run(["xdotool", "keydown", button])
|
|
time.sleep(duration)
|
|
subprocess.run(["xdotool", "keyup", button])
|
|
time.sleep(self.key_delay)
|
|
|
|
def sendKeyWindows(self, controls):
|
|
button = controls["control"]
|
|
duration = controls["duration"] / 500 # convert milliseconds to half-seconds
|
|
|
|
if duration < 1:
|
|
duration = 1
|
|
|
|
for i in range(int(duration)):
|
|
win32api.keybd_event(self.keymap[button], 0, 0, 0)
|
|
time.sleep(0.3)
|
|
win32api.keybd_event(self.keymap[button], 0, win32con.KEYEVENTF_KEYUP, 0)
|
|
|
|
def get_os(self):
|
|
os_name = platform.system()
|
|
if os_name == "Windows":
|
|
return "Windows"
|
|
elif os_name == "Linux":
|
|
return "Linux"
|
|
else:
|
|
return "Unknown"
|
|
|
|
def sendKey(self, button, os_type):
|
|
if os_type.lower() == "windows":
|
|
self.sendKeyWindows(button)
|
|
else:
|
|
self.sendKeyLinux(button)
|
|
|
|
def all_valid_chars(self, char_array):
|
|
pattern = re.compile(r"^[a-z]+\d*$")
|
|
return all(pattern.match(char) for char in char_array)
|
|
|
|
def compile_chars(self, char_array):
|
|
compiled_chars = []
|
|
for char in char_array:
|
|
match = re.match(r"([a-z]+)(\d+)(ms|s)", char)
|
|
if match:
|
|
control, duration, unit = match.groups()
|
|
if control not in self.keymap:
|
|
return []
|
|
duration = int(duration or "1")
|
|
if unit == "s":
|
|
duration *= 1000 # convert seconds to milliseconds
|
|
|
|
if duration > self.x_limit * 1000:
|
|
duration = self.x_limit * 1000
|
|
|
|
compiled_chars.append({"control": control, "duration": duration})
|
|
elif char in self.keymap:
|
|
compiled_chars.append({"control": char, "duration": self.key_duration})
|
|
|
|
return compiled_chars
|
|
|
|
def process_message(self, author, message, is_mod):
|
|
if is_mod and (message.lower() in self.kill_commands):
|
|
print("Killing TwitchPlaysX")
|
|
exit()
|
|
|
|
characters = message.lower().split(" ")
|
|
|
|
os_type = self.get_os()
|
|
|
|
compiled_characters = self.compile_chars(characters)
|
|
|
|
if compiled_characters:
|
|
self.controls_queue.extend(compiled_characters)
|
|
|
|
async def execute_controls(self):
|
|
while True:
|
|
if self.controls_queue:
|
|
print(self.controls_queue)
|
|
control = self.controls_queue.pop(0)
|
|
if self.get_os() == "Windows":
|
|
self.sendKeyWindows(control)
|
|
else:
|
|
self.sendKeyLinux(control)
|
|
else:
|
|
await asyncio.sleep(0.1) # Sleep for a short time if the queue is empty
|
|
|
|
async def play(self):
|
|
asyncio.create_task(self.execute_controls())
|
|
|
|
reader, writer = await asyncio.open_connection("irc.chat.twitch.tv", 6667)
|
|
|
|
writer.write(f"NICK justinfan1234\r\n".encode("utf-8"))
|
|
writer.write(
|
|
f"CAP REQ :twitch.tv/tags\r\n".encode("utf-8")
|
|
) # Enable IRCv3 tags
|
|
writer.write(f"JOIN #{self.channel_name}\r\n".encode("utf-8"))
|
|
|
|
while True:
|
|
data = await reader.read(2048)
|
|
message = data.decode().strip()
|
|
|
|
if message.startswith("PING"):
|
|
writer.write(b"PONG\r\n")
|
|
await writer.drain()
|
|
|
|
elif message:
|
|
tags, author, msg = message.split(" ", 2)
|
|
author = author.split("!", 1)[0].split(":", 1)[-1]
|
|
msg = (
|
|
msg.split(f"#{self.channel_name.lower()}", 1)[-1]
|
|
.split(":", 1)[-1]
|
|
.strip()
|
|
)
|
|
|
|
is_mod = ("user-type=mod" in tags) or (
|
|
"badges=broadcaster" in tags
|
|
) # Check if the user is a moderator
|
|
|
|
self.process_message(author, msg, is_mod)
|