started support for global messages

This commit is contained in:
Oliver Hartmann 2023-05-21 00:20:02 +02:00
parent e822834bcb
commit 4d64879c29
8 changed files with 193 additions and 120 deletions

View File

@ -12,6 +12,10 @@ Chat:
ty: 'Thank you and good luck.'
sold: 'Sorry, {item} is already sold'
Parser:
re_log: '(?P<date>\d\d\d\d/\d\d/\d\d \d\d:\d\d:\d\d) (\d+) (\S+) \[(?P<level>\S+) (\S+) (\d+)\] (?P<channel>[#@%$&]?)(?P<ToFrom>To|From)?\s?(?P<guild><\S+>)? ?(?P<user>[^:]+): (?P<message>.*)'
re_log: '(?P<date>\d\d\d\d/\d\d/\d\d \d\d:\d\d:\d\d) (\d+) (\S+) \[(?P<level>\S+) (\S+) (\d+)\] (?P<message>.*)'
re_comm: '(?P<channel>[#@%$&]?)(?P<ToFrom>To|From)?\s?(?P<guild><\S+>)? ?(?P<user>[^:]+): (?P<message>.*)'
re_joined: '(?P<user>\S+) has joined the area.'
re_trade: 'Hi, I(( would)|(''d)) like to buy your ?(?P<nrItems>\d*) (?P<item>.+?) (listed )?for (my )?(?P<amount>\d+(\.\d+)?) (?P<currency>\D+) in (?P<league>\w+)\.?( \(stash tab \"(?P<tab>.+)\"; position: left (?P<col>\d+), top (?P<row>\d+)\))?'
re_clipboard_prefix: '^@(?P<user>\S+) ' # this regex is used as a prefix for re_trade

View File

@ -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<item>.+) listed for (?P<amount>\d+) (?P<currency>\S+) in (?P<league>\S+) '
r'\(stash tab "(?P<tab>.+)"; position: left (?P<col>\d+), top (?P<row>\d+)\)'
)
re_log = re.compile(
r'(?P<date>\d\d\d\d/\d\d/\d\d \d\d:\d\d:\d\d) (\d+) (\S+) \[(?P<level>\S+) (\S+) (\d+)\] '
r'(?P<channel>[#@%$&]?)(?P<ToFrom>To|From)?\s?(?P<guild><\S+>)? ?(?P<user>[^:]+): (?P<message>.*)'
)
re_log = re.compile(r'(?P<date>\d\d\d\d/\d\d/\d\d \d\d:\d\d:\d\d) (\d+) (\S+) \[(?P<level>\S+) (\S+) (\d+)\] (?P<message>.*)')
re_comm = re.compile(r'(?P<channel>[#@%$&]?)(?P<ToFrom>To|From)?\s?(?P<guild><\S+>)? ?(?P<user>[^:]+): (?P<message>.*)')
re_joined = re.compile(r'(?P<user>\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

View File

@ -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("<ButtonPress-1>", self.start_move)
self.tab_control.bind("<ButtonRelease-1>", self.stop_move)
self.tab_control.bind("<B1-Motion>", 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()

View File

@ -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()

View File

@ -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()

View File

@ -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}')

View File

@ -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}.')

View File

@ -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 <SETSU?> 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 <SETSU?> 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