From 4d64879c29b6cdbe5540df3d4a15e728b539fad5 Mon Sep 17 00:00:00 2001 From: Oliver Hartmann Date: Sun, 21 May 2023 00:20:02 +0200 Subject: [PATCH] started support for global messages --- config.yaml | 6 ++- src/data.py | 67 ++++++++++++++++++++------ src/gui.py | 59 ++++++++++++++--------- src/pyside6/gui_pyside6.py | 6 +-- src/pyside6/trade_widget.py | 31 ++++++------ src/sendkeys.py | 46 +++++++++--------- src/trader.py | 2 +- tests/test_messages.py | 96 +++++++++++++++++++++---------------- 8 files changed, 193 insertions(+), 120 deletions(-) diff --git a/config.yaml b/config.yaml index 33f0e70..f391da1 100644 --- a/config.yaml +++ b/config.yaml @@ -12,6 +12,10 @@ Chat: ty: 'Thank you and good luck.' sold: 'Sorry, {item} is already sold' Parser: - re_log: '(?P\d\d\d\d/\d\d/\d\d \d\d:\d\d:\d\d) (\d+) (\S+) \[(?P\S+) (\S+) (\d+)\] (?P[#@%$&]?)(?PTo|From)?\s?(?P<\S+>)? ?(?P[^:]+): (?P.*)' + re_log: '(?P\d\d\d\d/\d\d/\d\d \d\d:\d\d:\d\d) (\d+) (\S+) \[(?P\S+) (\S+) (\d+)\] (?P.*)' + + re_comm: '(?P[#@%$&]?)(?PTo|From)?\s?(?P<\S+>)? ?(?P[^:]+): (?P.*)' + + re_joined: '(?P\S+) has joined the area.' re_trade: 'Hi, I(( would)|(''d)) like to buy your ?(?P\d*) (?P.+?) (listed )?for (my )?(?P\d+(\.\d+)?) (?P\D+) in (?P\w+)\.?( \(stash tab \"(?P.+)\"; position: left (?P\d+), top (?P\d+)\))?' re_clipboard_prefix: '^@(?P\S+) ' # this regex is used as a prefix for re_trade diff --git a/src/data.py b/src/data.py index 7833794..8dd4d4f 100644 --- a/src/data.py +++ b/src/data.py @@ -2,16 +2,19 @@ from enum import Enum from typing import Union import re import datetime -import logging, coloredlogs +import logging +import coloredlogs re_trade = re.compile( r'Hi, I would like to buy your (?P.+) listed for (?P\d+) (?P\S+) in (?P\S+) ' r'\(stash tab "(?P.+)"; position: left (?P\d+), top (?P\d+)\)' ) -re_log = re.compile( - r'(?P\d\d\d\d/\d\d/\d\d \d\d:\d\d:\d\d) (\d+) (\S+) \[(?P\S+) (\S+) (\d+)\] ' - r'(?P[#@%$&]?)(?PTo|From)?\s?(?P<\S+>)? ?(?P[^:]+): (?P.*)' -) +re_log = re.compile(r'(?P\d\d\d\d/\d\d/\d\d \d\d:\d\d:\d\d) (\d+) (\S+) \[(?P\S+) (\S+) (\d+)\] (?P.*)') + +re_comm = re.compile(r'(?P[#@%$&]?)(?PTo|From)?\s?(?P<\S+>)? ?(?P[^:]+): (?P.*)') + +re_joined = re.compile(r'(?P\S+) has joined the area.') + re_clipboard = None re_clipboard_prefix = None @@ -21,10 +24,14 @@ logging.basicConfig(level=logging.DEBUG, format='%(levelname)-8s:: %(message)s') def compile_regex(conf: dict): - global re_trade, re_log, re_clipboard, re_clipboard_prefix + global re_trade, re_log, re_clipboard, re_clipboard_prefix, re_comm, re_joined if 'General' in conf: if 're_log' in conf['Parser']: re_log = re.compile(conf['Parser']['re_log']) + if 're_comm' in conf['Parser']: + re_comm = re.compile(conf['Parser']['re_comm']) + if 're_joined' in conf['Parser']: + re_joined = re.compile(conf['Parser']['re_joined']) if 're_trade' in conf['Parser']: re_trade = re.compile(conf['Parser']['re_trade']) if 're_clipboard_prefix' in conf['Parser']: @@ -97,16 +104,26 @@ class Trade(): and self.league == __o.league) -class Message(): +class Entered(): + def __init__(self, user: str) -> None: + self.user = user + + @classmethod + def from_message(cls, message: str): + result = re_joined.search(message) + if not result: + return None + return cls(result.group('user')) + + +class Communcation(): def __init__(self, message: str, - date: datetime.datetime, user: str, channel: Channel, guild: Union[str, None] = None, to_from: Union[str, None] = None) -> None: self.message = message - self.date = date self.channel = channel self.user = user self.guild = guild @@ -117,24 +134,21 @@ class Message(): @classmethod def from_text(cls, text: str): - result = re_log.search(text) + result = re_comm.search(text) if not result: log.debug(f'Result is none for text "{text}"') return None - date = datetime.datetime.strptime( - result.group('date'), '%Y/%m/%d %H:%M:%S') guild = result.group('guild') if guild: guild = guild.strip('<>') return cls(result.group('message'), - date, result.group('user'), channel_mapping[result.group('channel')], guild, result.group('ToFrom')) def __str__(self) -> str: - text = f'Message: {self.date} - {self.channel.name}: ' + text = f'Message: {self.channel.name}: ' if self.to_from: text = text + f'{self.to_from} ' if self.guild: @@ -159,3 +173,28 @@ class Message(): else: # Item in a stash from another char return hash((self.trade.item, self.trade.league)) + + +class Message(): + def __init__(self, + message: str, + date: datetime.datetime) -> None: + self.message = message + self.date = date + self.communication = Communcation.from_text(message) + self.entered = Entered.from_message(message) + + @classmethod + def from_text(cls, text: str): + result = re_log.search(text) + if not result: + log.debug(f'Result is none for text "{text}"') + return None + date = datetime.datetime.strptime( + result.group('date'), '%Y/%m/%d %H:%M:%S') + return cls(result.group('message'), + date) + + def __str__(self) -> str: + text = f'Message: {self.date} - {self.message}: ' + return text diff --git a/src/gui.py b/src/gui.py index 9cabdd5..1d19e99 100644 --- a/src/gui.py +++ b/src/gui.py @@ -3,7 +3,7 @@ from tkinter import Tk from tkinter import Button, Label from .data import Message from src import sendkeys -from typing import Callable +from typing import Callable, Dict from src.data import log import sv_ttk @@ -14,7 +14,7 @@ class Gui(Tk): sv_ttk.set_theme("dark") self.title("Uber Trader") # self.geometry("300x200+10+20") - self.overrideredirect(1) # remove border + self.overrideredirect(True) # remove border self.wm_withdraw() # Hide window self.attributes('-topmost', True) # always on top # style = ttk.Style() @@ -24,22 +24,27 @@ class Gui(Tk): self.tab_control.bind("", self.start_move) self.tab_control.bind("", self.stop_move) self.tab_control.bind("", self.do_move) - self.tabs = dict() + self.tabs: Dict = dict() def add_tab(self, number: int, message: Message) -> None: + if not message.communication: + return self.wm_deiconify() - if message.user not in self.tabs: - self.tabs[message.user] = {'frame': ttk.Frame(self.tab_control)} - self.tabs[message.user]['items'] = {} - frame = self.tabs[message.user]['frame'] - if message.trade is not None: - if message.trade.item not in self.tabs[message.user]['items']: - row = len(self.tabs[message.user]['items']) - self.tabs[message.user]['items'][message.trade.item] = {} - self.tabs[message.user]['items'][message.trade.item]['row'] = row - self.tabs[message.user]['items'][message.trade.item]['message'] = message - ttk.Label(frame, text=message.trade.item).grid(column=0, row=row) - Label(frame, text=f'{message.trade.amount} {message.trade.currency}').grid(column=1, row=row) + user = message.communication.user + if user not in self.tabs: + self.tabs[user] = {'frame': ttk.Frame(self.tab_control)} + self.tabs[user]['items'] = {} + frame = self.tabs[user]['frame'] + if message.communication.trade is not None: + item = message.communication.trade.item + if item not in self.tabs[user]['items']: + row = len(self.tabs[user]['items']) + self.tabs[user]['items'][item] = {} + self.tabs[user]['items'][item]['row'] = row + self.tabs[user]['items'][item]['message'] = message + ttk.Label(frame, text=item).grid(column=0, row=row) + Label(frame, text=f'{message.communication.trade.amount}' + ' {message.communication.trade.currency}').grid(column=1, row=row) self.add_button(tab=frame, text='Inv', callback=self.inv_callback, message=message).grid(column=2, row=row) self.add_button(tab=frame, text='Trade', callback=self.trade_callback, @@ -51,7 +56,7 @@ class Gui(Tk): callback=self.wait_callback, message=message).grid(column=5, row=row) self.add_button(tab=frame, text='X', callback=self.destroy_tab, message=message).grid(column=6, row=row) - self.tab_control.add(frame, text=message.user) + self.tab_control.add(frame, text=user) else: log.warning(f'Trade in message "{str(message)}" is None') @@ -89,16 +94,24 @@ class Gui(Tk): sendkeys.send_to_format(type='wait', message=message) def destroy_tab(self, message: Message, tab: ttk.Frame) -> None: - if message.user in self.tabs: - if message.trade.item in self.tabs[message.user]['items']: + if message.communication is None: + log.error(f'Error destroying tab: no communication in message {message.message}') + return + if message.communication.trade is None: + log.error(f'Error destroying tab: no trade in message {message.message}') + return + user = message.communication.user + item = message.communication.trade.item + if message.communication.user in self.tabs: + if message.communication.trade.item in self.tabs[message.communication.user]['items']: # Delete the item - row = self.tabs[message.user]['items'][message.trade.item]['row'] - for widget in self.tabs[message.user]['frame'].grid_slaves(row=row): + row = self.tabs[message.communication.user]['items'][item]['row'] + for widget in self.tabs[user]['frame'].grid_slaves(row=row): widget.destroy() - del self.tabs[message.user]['items'][message.trade.item] - if len(self.tabs[message.user]['items']) == 0: + del self.tabs[user]['items'][item] + if len(self.tabs[user]['items']) == 0: # If no item from this user is left, then also delete the tab - del self.tabs[message.user] + del self.tabs[user] tab.destroy() if not self.tabs: self.wm_withdraw() diff --git a/src/pyside6/gui_pyside6.py b/src/pyside6/gui_pyside6.py index 505382b..103f211 100644 --- a/src/pyside6/gui_pyside6.py +++ b/src/pyside6/gui_pyside6.py @@ -40,14 +40,14 @@ class ResizingTabWidget(QTabWidget): self.main_window.updateSizes() def new_trade(self, message: Message): - if message.trade: - unique_item = message.unique_user_item() + if message.communication.trade: + unique_item = message.communication.unique_user_item() if unique_item in self.trade_in_collections: self.trade_in_collections[unique_item].add_trade(message) else: collection = TradeCollection(self, unique_item) collection.add_trade(message) - self.addTab(collection, message.trade.item) + self.addTab(collection, message.communication.trade.item) self.trade_in_collections[unique_item] = collection self.main_window.show() diff --git a/src/pyside6/trade_widget.py b/src/pyside6/trade_widget.py index c338a1f..56fff7c 100644 --- a/src/pyside6/trade_widget.py +++ b/src/pyside6/trade_widget.py @@ -22,7 +22,7 @@ class TradeWidget(QWidget): self.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) self.main_layout.setContentsMargins(0, 0, 0, 0) self.setLayout(self.main_layout) - assert message.trade + assert message.communication.trade def new_button(self, icon_filename: str, tooltip: str, callback: Callable[[], None]) -> QPushButton: icon = QIcon(icon_filename) @@ -56,11 +56,12 @@ class Trade_In_Widget(TradeWidget): self.close_button = self.new_button('icons/material_close.svg', 'Dismiss', self.close_callback) # self.label_item = self.new_Label(message.trade.item) - self.label_price = self.new_Label(f'{message.trade.amount} {message.trade.currency}') - self.label_user = self.new_Label(message.user) - assert message.trade - if message.trade.tab: - self.label_tab = self.new_Label(message.trade.tab) + assert message.communication.trade + self.label_price = self.new_Label(f'{message.communication.trade.amount} {message.communication.trade.currency}') + self.label_user = self.new_Label(message.communication.user) + assert message.communication.trade + if message.communication.trade.tab: + self.label_tab = self.new_Label(message.communication.trade.tab) def inv_callback(self): sendkeys.invite(message=self.message) @@ -96,9 +97,9 @@ class Trade_Out_Widget(TradeWidget): self.close_button = self.new_button('icons/material_close.svg', 'Dismiss', self.close_callback) # self.label_item = self.new_Label(message.trade.item) - assert message.trade - self.label_price = self.new_Label(f'{message.trade.amount} {message.trade.currency}') - self.label_user = self.new_Label(message.user) + assert message.communication.trade + self.label_price = self.new_Label(f'{message.communication.trade.amount} {message.communication.trade.currency}') + self.label_user = self.new_Label(message.communication.user) def join_callback(self): sendkeys.join(message=self.message) @@ -148,20 +149,20 @@ class TradeCollection(QWidget): return super().childEvent(event) def add_trade(self, message: Message) -> None: - assert message.trade - if message.unique_trade() in self.trades: + assert message.communication.trade + if message.communication.unique_trade() in self.trades: return - if message.to_from == 'From': + if message.communication.to_from == 'From': trade = Trade_In_Widget(message, self) else: trade = Trade_Out_Widget(message, self) self.main_layout.addWidget(trade) - self.trades[message.unique_trade()] = trade + self.trades[message.communication.unique_trade()] = trade def del_trade(self, message: Message): - assert message.trade - del self.trades[message.unique_trade()] + assert message.communication.trade + del self.trades[message.communication.unique_trade()] if not self.trades: self.parent.del_collection(self.unique_user_item) self.deleteLater() diff --git a/src/sendkeys.py b/src/sendkeys.py index 544e937..a2b2b84 100644 --- a/src/sendkeys.py +++ b/src/sendkeys.py @@ -45,36 +45,36 @@ def send_to_format(type: str, message: data.Message) -> None: message (data.Message): Message data. Placeholders in the message type will be replaced with this data. conf (dict): Configution dictionary where the messages are stored. """ - if message.trade: - text = config.conf['Chat'][type].format(message=message.message, + if message.communication.trade: + text = config.conf['Chat'][type].format(message=message.communication.message, date=message.date, - channel=message.channel.name, - user=message.user, - guild=message.guild, - to_from=message.to_from, - item=message.trade.item, - amount=message.trade.amount, - currency=message.trade.currency, - tab=message.trade.tab, - row=message.trade.row, - col=message.trade.col, - league=message.trade.league) + channel=message.communication.channel.name, + user=message.communication.user, + guild=message.communication.guild, + to_from=message.communication.to_from, + item=message.communication.trade.item, + amount=message.communication.trade.amount, + currency=message.communication.trade.currency, + tab=message.communication.trade.tab, + row=message.communication.trade.row, + col=message.communication.trade.col, + league=message.communication.trade.league) else: - text = config.conf['Chat'][type].format(message=message.message, + text = config.conf['Chat'][type].format(message=message.communication.message, date=message.date, - channel=message.channel.name, - user=message.user, - guild=message.guild, - to_from=message.to_from) - send_to(message.user, text) + channel=message.communication.channel.name, + user=message.communication.user, + guild=message.communication.guild, + to_from=message.communication.to_from) + send_to(message.communication.user, text) def invite(message: data.Message) -> None: - send_text(f'/invite {message.user}') + send_text(f'/invite {message.communication.user}') def join(message: data.Message) -> None: - send_text(f'/hideout {message.user}') + send_text(f'/hideout {message.communication.user}') def return_to_ho() -> None: @@ -82,7 +82,7 @@ def return_to_ho() -> None: def kick(message: data.Message) -> None: - send_text(f'/kick {message.user}') + send_text(f'/kick {message.communication.user}') def leave() -> None: @@ -90,4 +90,4 @@ def leave() -> None: def trade(message: data.Message) -> None: - send_text(f'/tradewith {message.user}') + send_text(f'/tradewith {message.communication.user}') diff --git a/src/trader.py b/src/trader.py index 4651b5c..99040c1 100644 --- a/src/trader.py +++ b/src/trader.py @@ -22,7 +22,7 @@ class Log_Reader(QtCore.QObject): for line in loglines: message = Message.from_text(line) log.debug(message) - if message and message.trade: + if message and message.communication and message.communication.trade: self.new_trade_signal.emit(message) except IOError: log.error(f'Error opening log file {logfile}.') diff --git a/tests/test_messages.py b/tests/test_messages.py index 968f143..26dea68 100644 --- a/tests/test_messages.py +++ b/tests/test_messages.py @@ -1,6 +1,6 @@ -from src import config -from src import data from datetime import datetime + +from src import config, data from src.data import Channel @@ -11,42 +11,56 @@ def test_message_from(): 'listed for 18 chaos in Ritual (stash tab "$"; position: left 22, top 5)' message = data.Message.from_text(text) assert message - assert message.user == 'NyhaiPuki' - assert message.message == 'Hi, I would like to buy your level 21 23% Vaal Impurity of Lightning ' \ + assert message.communication + assert message.communication.user == 'NyhaiPuki' + assert message.communication.message == 'Hi, I would like to buy your level 21 23% Vaal Impurity of Lightning ' \ 'listed for 18 chaos in Ritual (stash tab "$"; position: left 22, top 5)' assert message.date == datetime(2021, 3, 8, 23, 24, 52) - assert message.channel == Channel.WHISPER - assert message.guild is None - assert message.to_from == 'From' + assert message.communication.channel == Channel.WHISPER + assert message.communication.guild is None + assert message.communication.to_from == 'From' - assert message.trade - assert message.trade.amount == 18 - assert message.trade.col == 22 - assert message.trade.row == 5 - assert message.trade.currency == 'chaos' - assert message.trade.item == 'level 21 23% Vaal Impurity of Lightning' - assert message.trade.league == 'Ritual' + assert message.communication.trade + assert message.communication.trade.amount == 18 + assert message.communication.trade.col == 22 + assert message.communication.trade.row == 5 + assert message.communication.trade.currency == 'chaos' + assert message.communication.trade.item == 'level 21 23% Vaal Impurity of Lightning' + assert message.communication.trade.league == 'Ritual' def test_message_from_float(): conf = config.read_config(r'config.yaml') data.compile_regex(conf) - text = '2023/01/02 23:57:26 15123437 cffb0734 [INFO Client 16668] @From LASTTRYPOEenjoyer: Hi, I would like to buy your Watcher\'s Eye, Prismatic Jewel listed for 2.5 divine in Sanctum (stash tab "$2"; position: left 8, top 7)' + text = '2023/01/02 23:57:26 15123437 cffb0734 [INFO Client 16668] @From LASTTRYPOEenjoyer: ' \ + 'Hi, I would like to buy your Watcher\'s Eye, Prismatic Jewel listed for 2.5 divine in Sanctum (stash tab "$2"; position: left 8, top 7)' message = data.Message.from_text(text) assert message - assert message.user == 'LASTTRYPOEenjoyer' - assert message.message == 'Hi, I would like to buy your Watcher\'s Eye, Prismatic Jewel listed for 2.5 divine in Sanctum (stash tab "$2"; position: left 8, top 7)' - assert message.date == datetime(2021, 3, 8, 23, 24, 52) - assert message.channel == Channel.WHISPER - assert message.guild is None - assert message.to_from == 'From' - assert message.trade - assert message.trade.amount == 18 - assert message.trade.col == 22 - assert message.trade.row == 5 - assert message.trade.currency == 'chaos' - assert message.trade.item == 'level 21 23% Vaal Impurity of Lightning' - assert message.trade.league == 'Ritual' + assert message.communication + assert message.communication.user == 'LASTTRYPOEenjoyer' + assert message.communication.message == 'Hi, I would like to buy your Watcher\'s Eye, Prismatic Jewel listed for 2.5 divine' \ + ' in Sanctum (stash tab "$2"; position: left 8, top 7)' + assert message.date == datetime(2023, 1, 2, 23, 57, 26) + assert message.communication.channel == Channel.WHISPER + assert message.communication.guild == 'SETSU?' + assert message.communication.to_from == 'From' + assert message.communication.trade + assert message.communication.trade.amount == 2.5 + assert message.communication.trade.col == 8 + assert message.communication.trade.row == 7 + assert message.communication.trade.currency == 'divine' + assert message.communication.trade.item == 'Watcher\'s Eye, Prismatic Jewel' + assert message.communication.trade.league == 'Sanctum' + + +def test_entered(): + conf = config.read_config(r'config.yaml') + data.compile_regex(conf) + text = r'2023/05/07 18:18:03 35114562 cffb0719 [INFO Client 6416] : Oathussy has joined the area.' + message = data.Message.from_text(text) + assert message + assert message.entered + assert message.entered.user == 'Oathussy' def test_message_to(): @@ -55,13 +69,14 @@ def test_message_to(): text_to = '2021/01/24 23:11:23 17039703 bb2 [INFO Client 10144] @To EraseAndDelete: Thank you & good luck!' message = data.Message.from_text(text_to) assert message - assert message.user == 'EraseAndDelete' - assert message.message == 'Thank you & good luck!' + assert message.communication + assert message.communication.user == 'EraseAndDelete' + assert message.communication.message == 'Thank you & good luck!' assert message.date == datetime(2021, 1, 24, 23, 11, 23) - assert message.channel == Channel.WHISPER - assert message.guild is None - assert message.to_from == 'To' - assert message.trade is None + assert message.communication.channel == Channel.WHISPER + assert message.communication.guild is None + assert message.communication.to_from == 'To' + assert message.communication.trade is None def test_message_global(): @@ -70,10 +85,11 @@ def test_message_global(): text_global = '2021/02/10 19:01:56 2720500 bb2 [INFO Client 3464] #HarvestScarab: so by the time it was finally juiced you couldnt do it' message = data.Message.from_text(text_global) assert message - assert message.user == 'HarvestScarab' - assert message.message == 'so by the time it was finally juiced you couldnt do it' + assert message.communication + assert message.communication.user == 'HarvestScarab' + assert message.communication.message == 'so by the time it was finally juiced you couldnt do it' assert message.date == datetime(2021, 2, 10, 19, 1, 56) - assert message.channel == Channel.GLOBAL - assert message.guild is None - assert message.to_from is None - assert message.trade is None + assert message.communication.channel == Channel.GLOBAL + assert message.communication.guild is None + assert message.communication.to_from is None + assert message.communication.trade is None