Reformat codebase (#97)

* Reformat codebase with black

* Create black.yml and add black style badge to README.md
This commit is contained in:
MiguelX413 2022-10-07 13:51:40 -07:00 committed by GitHub
parent c8a8097673
commit a51e4dd69e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 6139 additions and 3682 deletions

10
.github/workflows/black.yml vendored Normal file
View file

@ -0,0 +1,10 @@
name: Black
on: [push, pull_request]
jobs:
black:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: psf/black@stable

View file

@ -8,6 +8,7 @@
<br> <br>
<img alt="GitHub commit activity" src="https://img.shields.io/github/commit-activity/y/Dpeta/pesterchum-alt-servers?style=for-the-badge"> <img alt="GitHub commit activity" src="https://img.shields.io/github/commit-activity/y/Dpeta/pesterchum-alt-servers?style=for-the-badge">
<img alt="Lines of code" src="https://img.shields.io/tokei/lines/github/Dpeta/pesterchum-alt-servers?style=for-the-badge"> <img alt="Lines of code" src="https://img.shields.io/tokei/lines/github/Dpeta/pesterchum-alt-servers?style=for-the-badge">
<a href="https://github.com/psf/black"><img alt="Code style: black" src="https://img.shields.io/badge/code%20style-black-000000?style=for-the-badge"></a>
</h1> </h1>
<img alt="PESTERCHUM" align="right" src="Pesterchum.png"> <img alt="PESTERCHUM" align="right" src="Pesterchum.png">

View file

@ -1,7 +1,7 @@
# vim: set autoindent ts=4 sts=4 sw=4 textwidth=79 expandtab: # vim: set autoindent ts=4 sts=4 sw=4 textwidth=79 expandtab:
# -*- coding=UTF-8; tab-width: 4 -*- # -*- coding=UTF-8; tab-width: 4 -*-
#import os # import os
#from os import remove # from os import remove
import sys import sys
import traceback import traceback
import time import time
@ -17,17 +17,20 @@ except ImportError:
from PyQt5.QtWidgets import QAction from PyQt5.QtWidgets import QAction
import dataobjs import dataobjs
#import generic
#import memos # import generic
#import parsetools # import memos
# import parsetools
import ostools import ostools
#from version import _pcVersion
# from version import _pcVersion
from pnc.dep.attrdict import AttrDict from pnc.dep.attrdict import AttrDict
PchumLog = logging.getLogger('pchumLogger') PchumLog = logging.getLogger("pchumLogger")
class ConsoleWindow(QtWidgets.QDialog): class ConsoleWindow(QtWidgets.QDialog):
#~class ConsoleWindow(styler.PesterBaseWindow): # ~class ConsoleWindow(styler.PesterBaseWindow):
# A simple console class, cobbled together from the corpse of another. # A simple console class, cobbled together from the corpse of another.
stylesheet_path = "main/defaultwindow/style" stylesheet_path = "main/defaultwindow/style"
@ -127,7 +130,7 @@ class ConsoleWindow(QtWidgets.QDialog):
def initTheme(self, theme): def initTheme(self, theme):
# Set up our style/window specifics # Set up our style/window specifics
self.changeTheme(theme) self.changeTheme(theme)
self.resize(400,600) self.resize(400, 600)
def changeTheme(self, theme): def changeTheme(self, theme):
self.setStyleSheet(theme[self.stylesheet_path]) self.setStyleSheet(theme[self.stylesheet_path])
@ -150,18 +153,23 @@ class ConsoleWindow(QtWidgets.QDialog):
wgt = QtWidgets.QApplication.widgetAt(pos) wgt = QtWidgets.QApplication.widgetAt(pos)
if wgt is None: if wgt is None:
# Don't set None, for now. May change this later. # Don't set None, for now. May change this later.
self.addMessage("You need to have your cursor over something " + \ self.addMessage(
"in Pesterchum to use that.", "You need to have your cursor over something "
direction=direction) + "in Pesterchum to use that.",
direction=direction,
)
return return
self.selected_widget = wgt self.selected_widget = wgt
nchild = len(wgt.children()) nchild = len(wgt.children())
output = [] output = []
output.append("CONSOLE.selected_widget = {0!r}".format(wgt)) output.append("CONSOLE.selected_widget = {0!r}".format(wgt))
output.append("{0: <4}Parent: {1!r}".format('', wgt.parent())) output.append("{0: <4}Parent: {1!r}".format("", wgt.parent()))
output.append("{0: <4}{1:4d} child{2}".format('', output.append(
nchild, ("ren" if abs(nchild) != 1 else "") )) "{0: <4}{1:4d} child{2}".format(
"", nchild, ("ren" if abs(nchild) != 1 else "")
)
)
if self.show_info_on_select: if self.show_info_on_select:
qtss = None qtss = None
uses_ss = None uses_ss = None
@ -211,12 +219,13 @@ class ConsoleWindow(QtWidgets.QDialog):
else: else:
# We got a stylesheet out of this! # We got a stylesheet out of this!
uses_ss, ss_msg = True, "Yes" uses_ss, ss_msg = True, "Yes"
#~ss_par_msg = "{0: <4}...on parent ↑{1:d}: {2!r}".format('', # ~ss_par_msg = "{0: <4}...on parent ↑{1:d}: {2!r}".format('',
ss_par_msg = "{0: <4}...on parent #{1:d}: {2!r}".format('', ss_par_msg = "{0: <4}...on parent #{1:d}: {2!r}".format(
i, ss_par) "", i, ss_par
)
msg = [] msg = []
msg.append("{0: <4}QtSS?: {1}".format('', ss_msg)) msg.append("{0: <4}QtSS?: {1}".format("", ss_msg))
# A stylesheet analyzer would be wonderful here. Perhaps something # A stylesheet analyzer would be wonderful here. Perhaps something
# that tells us how many parent classes define stylesheets? # that tells us how many parent classes define stylesheets?
if uses_ss: if uses_ss:
@ -224,16 +233,15 @@ class ConsoleWindow(QtWidgets.QDialog):
# We got this stylesheet from a parent object somewhere. # We got this stylesheet from a parent object somewhere.
msg.append(ss_par_msg) msg.append(ss_par_msg)
msg.append("{0: <4}".format("Stylesheet:")) msg.append("{0: <4}".format("Stylesheet:"))
for ln in qtss.split('\n'): for ln in qtss.split("\n"):
msg.append("{0: <8}".format(ln)) msg.append("{0: <8}".format(ln))
# Actually add this stuff to the result we're constructing # Actually add this stuff to the result we're constructing
output.extend(msg) output.extend(msg)
output = '\n'.join(output) output = "\n".join(output)
self.addMessage(output, direction=direction) self.addMessage(output, direction=direction)
# Actual console stuff. # Actual console stuff.
def execInConsole(self, scriptstr, env=None): def execInConsole(self, scriptstr, env=None):
# Since that's what imports *us*, this should be okay # Since that's what imports *us*, this should be okay
@ -247,16 +255,16 @@ class ConsoleWindow(QtWidgets.QDialog):
# Fetch from the class/instance first. # Fetch from the class/instance first.
_CUSTOM_ENV = self._CUSTOM_ENV.copy() _CUSTOM_ENV = self._CUSTOM_ENV.copy()
# Modify with some hard-coded environmental additions. # Modify with some hard-coded environmental additions.
_CUSTOM_ENV.update({ _CUSTOM_ENV.update(
{
"CONSOLE": self, "CONSOLE": self,
"MAINWIN": self.mainwindow, "MAINWIN": self.mainwindow,
"PCONFIG": self.mainwindow.config, "PCONFIG": self.mainwindow.config,
"exit": lambda: self.mainwindow.exitaction.trigger() "exit": lambda: self.mainwindow.exitaction.trigger(),
}) }
)
# Aliases. # Aliases.
_CUSTOM_ENV.update({ _CUSTOM_ENV.update({"quit": _CUSTOM_ENV["exit"]})
"quit": _CUSTOM_ENV["exit"]
})
# Add whatever additions were set in the main pesterchum file. # Add whatever additions were set in the main pesterchum file.
_CUSTOM_ENV.update(pchum._CONSOLE_ENV) _CUSTOM_ENV.update(pchum._CONSOLE_ENV)
@ -284,7 +292,7 @@ class ConsoleWindow(QtWidgets.QDialog):
# Replace the old writer (for now) # Replace the old writer (for now)
sysout, sys.stdout = sys.stdout, self sysout, sys.stdout = sys.stdout, self
try: try:
code = compile(scriptstr + '\n', "<string>", "single") code = compile(scriptstr + "\n", "<string>", "single")
# Will using cenv instead of env cause problems?... # Will using cenv instead of env cause problems?...
result = eval(code, cenv) result = eval(code, cenv)
except: except:
@ -309,7 +317,7 @@ class ConsoleWindow(QtWidgets.QDialog):
# We only ever use this for receiving, so it's safe to assume the # We only ever use this for receiving, so it's safe to assume the
# direction is always -1. # direction is always -1.
if not isinstance(data, list): if not isinstance(data, list):
data = data.split('\n') data = data.split("\n")
for line in data: for line in data:
if len(line): if len(line):
self.addMessage(line, -1) self.addMessage(line, -1)
@ -332,7 +340,7 @@ class ConsoleText(QtWidgets.QTextEdit):
def __init__(self, theme, parent=None): def __init__(self, theme, parent=None):
super(ConsoleText, self).__init__(parent) super(ConsoleText, self).__init__(parent)
if hasattr(self.window(), 'mainwindow'): if hasattr(self.window(), "mainwindow"):
self.mainwindow = self.window().mainwindow self.mainwindow = self.window().mainwindow
else: else:
self.mainwindow = self.window() self.mainwindow = self.window()
@ -362,7 +370,7 @@ class ConsoleText(QtWidgets.QTextEdit):
# be too hard - it's what dicts are for. # be too hard - it's what dicts are for.
# Add the rest. # Add the rest.
stylesheet += '\n' + self.stylesheet_template stylesheet += "\n" + self.stylesheet_template
stylesheet = stylesheet.format(style=theme) stylesheet = stylesheet.format(style=theme)
self.setStyleSheet(stylesheet) self.setStyleSheet(stylesheet)
@ -371,12 +379,12 @@ class ConsoleText(QtWidgets.QTextEdit):
# Direction > 0 == out (sent by us); < 0 == in (sent by script). # Direction > 0 == out (sent by us); < 0 == in (sent by script).
if len(msg) == 0: if len(msg) == 0:
return return
#~color = chum.colorcmd() # ~color = chum.colorcmd()
#~initials = chum.initials() # ~initials = chum.initials()
parent = self.window() parent = self.window()
mwindow = parent.mainwindow mwindow = parent.mainwindow
#systemColor = QtGui.QColor(mwindow.theme["convo/systemMsgColor"]) # systemColor = QtGui.QColor(mwindow.theme["convo/systemMsgColor"])
if mwindow.config.showTimeStamps(): if mwindow.config.showTimeStamps():
if mwindow.config.time12Format(): if mwindow.config.time12Format():
@ -406,7 +414,7 @@ class ConsoleText(QtWidgets.QTextEdit):
# Later, this will have to escape things so we don't parse them, # Later, this will have to escape things so we don't parse them,
# likely...hm. # likely...hm.
#~result = "<span style=\"color:#000000\">{} {} {!r}</span>" # ~result = "<span style=\"color:#000000\">{} {} {!r}</span>"
# The input we get is already repr'd...we pass it via print, and thus # The input we get is already repr'd...we pass it via print, and thus
# do that there. # do that there.
result = "{}{} {}\n" result = "{}{} {}\n"
@ -442,8 +450,12 @@ class ConsoleText(QtWidgets.QTextEdit):
# should. # should.
# karxi: Test for tab changing? # karxi: Test for tab changing?
if self.window().text.input: if self.window().text.input:
if event.key() not in (QtCore.Qt.Key.Key_PageUp, QtCore.Qt.Key.Key_PageDown, if event.key() not in (
QtCore.Qt.Key.Key_Up, QtCore.Qt.Key.Key_Down): QtCore.Qt.Key.Key_PageUp,
QtCore.Qt.Key.Key_PageDown,
QtCore.Qt.Key.Key_Up,
QtCore.Qt.Key.Key_Down,
):
self.window().text.input.keyPressEvent(event) self.window().text.input.keyPressEvent(event)
super(ConsoleText, self).keyPressEvent(event) super(ConsoleText, self).keyPressEvent(event)
@ -463,7 +475,9 @@ class ConsoleText(QtWidgets.QTextEdit):
QtWidgets.QApplication.clipboard().setText(url) QtWidgets.QApplication.clipboard().setText(url)
else: else:
# This'll probably be removed. May change the lexer out. # This'll probably be removed. May change the lexer out.
QtGui.QDesktopServices.openUrl(QtCore.QUrl(url, QtCore.QUrl.ParsingMode.TolerantMode)) QtGui.QDesktopServices.openUrl(
QtCore.QUrl(url, QtCore.QUrl.ParsingMode.TolerantMode)
)
super(ConsoleText, self).mousePressEvent(event) super(ConsoleText, self).mousePressEvent(event)
@ -477,8 +491,13 @@ class ConsoleText(QtWidgets.QTextEdit):
# PyQt5 # PyQt5
pos = event.pos() pos = event.pos()
if self.anchorAt(pos): if self.anchorAt(pos):
if self.viewport().cursor().shape != QtCore.Qt.CursorShape.PointingHandCursor: if (
self.viewport().setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.PointingHandCursor)) self.viewport().cursor().shape
!= QtCore.Qt.CursorShape.PointingHandCursor
):
self.viewport().setCursor(
QtGui.QCursor(QtCore.Qt.CursorShape.PointingHandCursor)
)
else: else:
self.viewport().setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.IBeamCursor)) self.viewport().setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.IBeamCursor))
@ -489,6 +508,7 @@ class ConsoleText(QtWidgets.QTextEdit):
class ConsoleInput(QtWidgets.QLineEdit): class ConsoleInput(QtWidgets.QLineEdit):
"""The actual text entry box on a ConsoleWindow.""" """The actual text entry box on a ConsoleWindow."""
# I honestly feel like this could just be made a private class of # I honestly feel like this could just be made a private class of
# ConsoleWindow, but...best not to overcomplicate things. # ConsoleWindow, but...best not to overcomplicate things.
stylesheet_path = "convo/input/style" stylesheet_path = "convo/input/style"

507
convo.py
View file

@ -14,16 +14,12 @@ except ImportError:
import ostools import ostools
from dataobjs import PesterHistory from dataobjs import PesterHistory
from parsetools import (convertTags, from parsetools import convertTags, lexMessage, mecmd, colorBegin, colorEnd, smiledict
lexMessage,
mecmd,
colorBegin,
colorEnd,
smiledict)
import parsetools import parsetools
from pnc.dep.attrdict import AttrDict from pnc.dep.attrdict import AttrDict
PchumLog = logging.getLogger('pchumLogger') PchumLog = logging.getLogger("pchumLogger")
class PesterTabWindow(QtWidgets.QFrame): class PesterTabWindow(QtWidgets.QFrame):
def __init__(self, mainwindow, parent=None, convo="convo"): def __init__(self, mainwindow, parent=None, convo="convo"):
@ -41,18 +37,26 @@ class PesterTabWindow(QtWidgets.QFrame):
self.shortcuts = AttrDict() self.shortcuts = AttrDict()
self.shortcuts.tabNext = QShortcut( self.shortcuts.tabNext = QShortcut(
QtGui.QKeySequence('Ctrl+j'), self, QtGui.QKeySequence("Ctrl+j"),
context=QtCore.Qt.ShortcutContext.WidgetWithChildrenShortcut) self,
context=QtCore.Qt.ShortcutContext.WidgetWithChildrenShortcut,
)
self.shortcuts.tabLast = QShortcut( self.shortcuts.tabLast = QShortcut(
QtGui.QKeySequence('Ctrl+k'), self, QtGui.QKeySequence("Ctrl+k"),
context=QtCore.Qt.ShortcutContext.WidgetWithChildrenShortcut) self,
context=QtCore.Qt.ShortcutContext.WidgetWithChildrenShortcut,
)
# Note that we use reversed keys here. # Note that we use reversed keys here.
self.shortcuts.tabUp = QShortcut( self.shortcuts.tabUp = QShortcut(
QtGui.QKeySequence('Ctrl+PgDown'), self, QtGui.QKeySequence("Ctrl+PgDown"),
context=QtCore.Qt.ShortcutContext.WidgetWithChildrenShortcut) self,
context=QtCore.Qt.ShortcutContext.WidgetWithChildrenShortcut,
)
self.shortcuts.tabDn = QShortcut( self.shortcuts.tabDn = QShortcut(
QtGui.QKeySequence('Ctrl+PgUp'), self, QtGui.QKeySequence("Ctrl+PgUp"),
context=QtCore.Qt.ShortcutContext.WidgetWithChildrenShortcut) self,
context=QtCore.Qt.ShortcutContext.WidgetWithChildrenShortcut,
)
self.shortcuts.tabNext.activated.connect(self.nudgeTabNext) self.shortcuts.tabNext.activated.connect(self.nudgeTabNext)
self.shortcuts.tabUp.activated.connect(self.nudgeTabNext) self.shortcuts.tabUp.activated.connect(self.nudgeTabNext)
@ -61,7 +65,7 @@ class PesterTabWindow(QtWidgets.QFrame):
self.initTheme(self.mainwindow.theme) self.initTheme(self.mainwindow.theme)
self.layout = QtWidgets.QVBoxLayout() self.layout = QtWidgets.QVBoxLayout()
self.layout.setContentsMargins(0,0,0,0) self.layout.setContentsMargins(0, 0, 0, 0)
self.layout.addWidget(self.tabs) self.layout.addWidget(self.tabs)
self.setLayout(self.layout) self.setLayout(self.layout)
self.convos = {} self.convos = {}
@ -83,6 +87,7 @@ class PesterTabWindow(QtWidgets.QFrame):
self.tabs.removeTab(i) self.tabs.removeTab(i)
self.changedTab = False self.changedTab = False
return c return c
def addChat(self, convo): def addChat(self, convo):
self.convos[convo.title()] = convo self.convos[convo.title()] = convo
# either addTab or setCurrentIndex will trigger changed() # either addTab or setCurrentIndex will trigger changed()
@ -90,6 +95,7 @@ class PesterTabWindow(QtWidgets.QFrame):
self.tabIndices[convo.title()] = newindex self.tabIndices[convo.title()] = newindex
self.tabs.setCurrentIndex(newindex) self.tabs.setCurrentIndex(newindex)
self.tabs.setTabIcon(newindex, convo.icon()) self.tabs.setTabIcon(newindex, convo.icon())
def showChat(self, handle): def showChat(self, handle):
tabi = self.tabIndices[handle] tabi = self.tabIndices[handle]
if self.tabs.currentIndex() == tabi: if self.tabs.currentIndex() == tabi:
@ -98,6 +104,7 @@ class PesterTabWindow(QtWidgets.QFrame):
self.convos[handle].raiseChat() self.convos[handle].raiseChat()
else: else:
self.tabs.setCurrentIndex(tabi) self.tabs.setCurrentIndex(tabi)
""" """
There are two instances of "convoHasFocus" for some reason? There are two instances of "convoHasFocus" for some reason?
This one seems to just get redefined. This one seems to just get redefined.
@ -115,29 +122,35 @@ class PesterTabWindow(QtWidgets.QFrame):
# TODO: Clean this up. Our text areas now call this. # TODO: Clean this up. Our text areas now call this.
keypress = event.key() keypress = event.key()
mods = event.modifiers() mods = event.modifiers()
if ((mods & QtCore.Qt.KeyboardModifier.ControlModifier) and if (
keypress == QtCore.Qt.Key.Key_Tab): mods & QtCore.Qt.KeyboardModifier.ControlModifier
) and keypress == QtCore.Qt.Key.Key_Tab:
handles = list(self.convos.keys()) handles = list(self.convos.keys())
waiting = self.mainwindow.waitingMessages.waitingHandles() waiting = self.mainwindow.waitingMessages.waitingHandles()
waitinghandles = list(set(handles) & set(waiting)) waitinghandles = list(set(handles) & set(waiting))
if len(waitinghandles) > 0: if len(waitinghandles) > 0:
nexti = self.tabIndices[waitinghandles[0]] nexti = self.tabIndices[waitinghandles[0]]
else: else:
nexti = (self.tabIndices[self.currentConvo.title()] + 1) % self.tabs.count() nexti = (
self.tabIndices[self.currentConvo.title()] + 1
) % self.tabs.count()
self.tabs.setCurrentIndex(nexti) self.tabs.setCurrentIndex(nexti)
@QtCore.pyqtSlot() @QtCore.pyqtSlot()
def nudgeTabNext(self): return self.nudgeTabIndex(+1) def nudgeTabNext(self):
return self.nudgeTabIndex(+1)
@QtCore.pyqtSlot() @QtCore.pyqtSlot()
def nudgeTabLast(self): return self.nudgeTabIndex(-1) def nudgeTabLast(self):
return self.nudgeTabIndex(-1)
def nudgeTabIndex(self, direction): def nudgeTabIndex(self, direction):
# Inverted controls. Might add an option for this if people want # Inverted controls. Might add an option for this if people want
# it. # it.
#~if keypress == QtCore.Qt.Key.Key_PageDown: # ~if keypress == QtCore.Qt.Key.Key_PageDown:
#~ direction = 1 # ~ direction = 1
#~elif keypress == QtCore.Qt.Key.Key_PageUp: # ~elif keypress == QtCore.Qt.Key.Key_PageUp:
#~ direction = -1 # ~ direction = -1
# ...Processing... # ...Processing...
tabs = self.tabs tabs = self.tabs
# Pick our new index by sliding up or down the tab range. # Pick our new index by sliding up or down the tab range.
@ -161,7 +174,7 @@ class PesterTabWindow(QtWidgets.QFrame):
tabs.setCurrentIndex(nind) tabs.setCurrentIndex(nind)
def contextMenuEvent(self, event): def contextMenuEvent(self, event):
#~if event.reason() == QtGui.QContextMenuEvent.Reason.Mouse: # ~if event.reason() == QtGui.QContextMenuEvent.Reason.Mouse:
tabi = self.tabs.tabAt(event.pos()) tabi = self.tabs.tabAt(event.pos())
if tabi < 0: if tabi < 0:
tabi = self.tabs.currentIndex() tabi = self.tabs.currentIndex()
@ -179,12 +192,14 @@ class PesterTabWindow(QtWidgets.QFrame):
def closeSoft(self): def closeSoft(self):
self.softclose = True self.softclose = True
self.close() self.close()
def updateBlocked(self, handle): def updateBlocked(self, handle):
i = self.tabIndices[handle] i = self.tabIndices[handle]
icon = QtGui.QIcon(self.mainwindow.theme["main/chums/moods/blocked/icon"]) icon = QtGui.QIcon(self.mainwindow.theme["main/chums/moods/blocked/icon"])
self.tabs.setTabIcon(i, icon) self.tabs.setTabIcon(i, icon)
if self.tabs.currentIndex() == i: if self.tabs.currentIndex() == i:
self.setWindowIcon(icon) self.setWindowIcon(icon)
def updateMood(self, handle, mood, unblocked=False): def updateMood(self, handle, mood, unblocked=False):
i = self.tabIndices[handle] i = self.tabIndices[handle]
if handle in self.mainwindow.config.getBlocklist() and not unblocked: if handle in self.mainwindow.config.getBlocklist() and not unblocked:
@ -194,37 +209,42 @@ class PesterTabWindow(QtWidgets.QFrame):
self.tabs.setTabIcon(i, icon) self.tabs.setTabIcon(i, icon)
if self.tabs.currentIndex() == i: if self.tabs.currentIndex() == i:
self.setWindowIcon(icon) self.setWindowIcon(icon)
def closeEvent(self, event): def closeEvent(self, event):
if not self.softclose: if not self.softclose:
while self.tabs.count() > 0: while self.tabs.count() > 0:
self.tabClose(0) self.tabClose(0)
self.windowClosed.emit() self.windowClosed.emit()
def focusInEvent(self, event): def focusInEvent(self, event):
# make sure we're not switching tabs! # make sure we're not switching tabs!
i = self.tabs.tabAt(self.mapFromGlobal(QtGui.QCursor.pos())) i = self.tabs.tabAt(self.mapFromGlobal(QtGui.QCursor.pos()))
if i == -1: if i == -1:
i = self.tabs.currentIndex() i = self.tabs.currentIndex()
handle = str(self.tabs.tabText(i)) handle = str(self.tabs.tabText(i))
self.clearNewMessage(handle) self.clearNewMessage(handle)
def convoHasFocus(self, handle): def convoHasFocus(self, handle):
i = self.tabIndices[handle] i = self.tabIndices[handle]
if (self.tabs.currentIndex() == i and if self.tabs.currentIndex() == i and (self.hasFocus() or self.tabs.hasFocus()):
(self.hasFocus() or self.tabs.hasFocus())):
return True return True
else: else:
return False return False
def notifyNewMessage(self, handle): def notifyNewMessage(self, handle):
i = self.tabIndices[handle] i = self.tabIndices[handle]
self.tabs.setTabTextColor(i, self.tabs.setTabTextColor(
QtGui.QColor(self.mainwindow.theme["%s/tabs/newmsgcolor" i, QtGui.QColor(self.mainwindow.theme["%s/tabs/newmsgcolor" % (self.type)])
% (self.type)])) )
convo = self.convos[handle] convo = self.convos[handle]
# Create a function for the icon to use # Create a function for the icon to use
# TODO: Let us disable this. # TODO: Let us disable this.
def func(): def func():
convo.showChat() convo.showChat()
self.mainwindow.waitingMessages.addMessage(handle, func) self.mainwindow.waitingMessages.addMessage(handle, func)
# set system tray # set system tray
def clearNewMessage(self, handle): def clearNewMessage(self, handle):
try: try:
i = self.tabIndices[handle] i = self.tabIndices[handle]
@ -232,13 +252,15 @@ class PesterTabWindow(QtWidgets.QFrame):
except KeyError: except KeyError:
pass pass
self.mainwindow.waitingMessages.messageAnswered(handle) self.mainwindow.waitingMessages.messageAnswered(handle)
def initTheme(self, theme): def initTheme(self, theme):
self.resize(*theme["convo/size"]) self.resize(*theme["convo/size"])
self.setStyleSheet(theme["convo/tabwindow/style"]) self.setStyleSheet(theme["convo/tabwindow/style"])
self.tabs.setShape(QtWidgets.QTabBar.Shape(theme["convo/tabs/tabstyle"])) self.tabs.setShape(QtWidgets.QTabBar.Shape(theme["convo/tabs/tabstyle"]))
self.tabs.setStyleSheet("QTabBar::tab{ %s } QTabBar::tab:selected { %s }" self.tabs.setStyleSheet(
% (theme["convo/tabs/style"], "QTabBar::tab{ %s } QTabBar::tab:selected { %s }"
theme["convo/tabs/selectedstyle"])) % (theme["convo/tabs/style"], theme["convo/tabs/selectedstyle"])
)
def changeTheme(self, theme): def changeTheme(self, theme):
self.initTheme(theme) self.initTheme(theme)
@ -255,20 +277,20 @@ class PesterTabWindow(QtWidgets.QFrame):
def tabClose(self, i): def tabClose(self, i):
handle = str(self.tabs.tabText(i)) handle = str(self.tabs.tabText(i))
self.mainwindow.waitingMessages.messageAnswered(handle) self.mainwindow.waitingMessages.messageAnswered(handle)
#print(self.convos.keys()) # print(self.convos.keys())
# I, legit don' t know why this is an issue, but, uh, yeah- # I, legit don' t know why this is an issue, but, uh, yeah-
try: try:
convo = self.convos[handle] convo = self.convos[handle]
except: except:
#handle = handle.replace("&","") # handle = handle.replace("&","")
handle = ''.join(handle.split('&', 1)) handle = "".join(handle.split("&", 1))
convo = self.convos[handle] convo = self.convos[handle]
del self.convos[handle] del self.convos[handle]
del self.tabIndices[handle] del self.tabIndices[handle]
self.tabs.removeTab(i) self.tabs.removeTab(i)
for (h, j) in self.tabIndices.items(): for (h, j) in self.tabIndices.items():
if j > i: if j > i:
self.tabIndices[h] = j-1 self.tabIndices[h] = j - 1
self.layout.removeWidget(convo) self.layout.removeWidget(convo)
convo.close() convo.close()
if self.tabs.count() == 0: if self.tabs.count() == 0:
@ -312,10 +334,12 @@ class PesterTabWindow(QtWidgets.QFrame):
windowClosed = QtCore.pyqtSignal() windowClosed = QtCore.pyqtSignal()
class PesterMovie(QtGui.QMovie): class PesterMovie(QtGui.QMovie):
def __init__(self, parent): def __init__(self, parent):
super(PesterMovie, self).__init__(parent) super(PesterMovie, self).__init__(parent)
self.textwindow = parent self.textwindow = parent
@QtCore.pyqtSlot(int) @QtCore.pyqtSlot(int)
def animate(self, frame): def animate(self, frame):
text = self.textwindow text = self.textwindow
@ -327,20 +351,25 @@ class PesterMovie(QtGui.QMovie):
if text.hasTabs: if text.hasTabs:
i = text.tabobject.tabIndices[text.parent().title()] i = text.tabobject.tabIndices[text.parent().title()]
if text.tabobject.tabs.currentIndex() == i: if text.tabobject.tabs.currentIndex() == i:
text.document().addResource(QtGui.QTextDocument.ResourceType.ImageResource.value, text.document().addResource(
text.urls[movie], movie.currentPixmap()) QtGui.QTextDocument.ResourceType.ImageResource.value,
text.urls[movie],
movie.currentPixmap(),
)
text.setLineWrapColumnOrWidth(text.lineWrapColumnOrWidth()) text.setLineWrapColumnOrWidth(text.lineWrapColumnOrWidth())
else: else:
text.document().addResource(QtGui.QTextDocument.ResourceType.ImageResource.value, text.document().addResource(
text.urls[movie], QtGui.QTextDocument.ResourceType.ImageResource.value,
movie.currentPixmap()) text.urls[movie],
movie.currentPixmap(),
)
text.setLineWrapColumnOrWidth(text.lineWrapColumnOrWidth()) text.setLineWrapColumnOrWidth(text.lineWrapColumnOrWidth())
class PesterText(QtWidgets.QTextEdit): class PesterText(QtWidgets.QTextEdit):
def __init__(self, theme, parent=None): def __init__(self, theme, parent=None):
super(PesterText, self).__init__(parent) super(PesterText, self).__init__(parent)
if hasattr(self.parent(), 'mainwindow'): if hasattr(self.parent(), "mainwindow"):
self.mainwindow = self.parent().mainwindow self.mainwindow = self.parent().mainwindow
else: else:
self.mainwindow = self.parent() self.mainwindow = self.parent()
@ -356,8 +385,12 @@ class PesterText(QtWidgets.QTextEdit):
self.copyAvailable[bool].connect(self.textReady) self.copyAvailable[bool].connect(self.textReady)
self.urls = {} self.urls = {}
for k in smiledict: for k in smiledict:
self.addAnimation(QtCore.QUrl("smilies/%s" % (smiledict[k])), "smilies/%s" % (smiledict[k])) self.addAnimation(
#self.mainwindow.animationSetting[bool].connect(self.animateChanged) QtCore.QUrl("smilies/%s" % (smiledict[k])),
"smilies/%s" % (smiledict[k]),
)
# self.mainwindow.animationSetting[bool].connect(self.animateChanged)
def addAnimation(self, url, fileName): def addAnimation(self, url, fileName):
# We don't need to treat images formats like .png as animation, # We don't need to treat images formats like .png as animation,
# this always opens a file handler otherwise, "movie.frameCount() > 1" isn't sufficient. # this always opens a file handler otherwise, "movie.frameCount() > 1" isn't sufficient.
@ -393,29 +426,37 @@ class PesterText(QtWidgets.QTextEdit):
@QtCore.pyqtSlot(bool) @QtCore.pyqtSlot(bool)
def textReady(self, ready): def textReady(self, ready):
self.textSelected = ready self.textSelected = ready
def initTheme(self, theme): def initTheme(self, theme):
if "convo/scrollbar" in theme: if "convo/scrollbar" in theme:
self.setStyleSheet("QTextEdit { %s }" self.setStyleSheet(
"QScrollBar:vertical { %s }" "QTextEdit { %s }"
"QScrollBar::handle:vertical { %s }" "QScrollBar:vertical { %s }"
"QScrollBar::add-line:vertical { %s }" "QScrollBar::handle:vertical { %s }"
"QScrollBar::sub-line:vertical { %s }" "QScrollBar::add-line:vertical { %s }"
"QScrollBar:up-arrow:vertical { %s }" "QScrollBar::sub-line:vertical { %s }"
"QScrollBar:down-arrow:vertical { %s }" "QScrollBar:up-arrow:vertical { %s }"
% (theme["convo/textarea/style"], "QScrollBar:down-arrow:vertical { %s }"
theme["convo/scrollbar/style"], % (
theme["convo/scrollbar/handle"], theme["convo/textarea/style"],
theme["convo/scrollbar/downarrow"], theme["convo/scrollbar/style"],
theme["convo/scrollbar/uparrow"], theme["convo/scrollbar/handle"],
theme["convo/scrollbar/uarrowstyle"], theme["convo/scrollbar/downarrow"],
theme["convo/scrollbar/darrowstyle"])) theme["convo/scrollbar/uparrow"],
theme["convo/scrollbar/uarrowstyle"],
theme["convo/scrollbar/darrowstyle"],
)
)
else: else:
self.setStyleSheet("QTextEdit { %s }" % (theme["convo/textarea/style"])) self.setStyleSheet("QTextEdit { %s }" % (theme["convo/textarea/style"]))
def addMessage(self, lexmsg, chum): def addMessage(self, lexmsg, chum):
if len(lexmsg) == 0: if len(lexmsg) == 0:
return return
color = chum.colorcmd() color = chum.colorcmd()
systemColor = QtGui.QColor(self.parent().mainwindow.theme["convo/systemMsgColor"]) systemColor = QtGui.QColor(
self.parent().mainwindow.theme["convo/systemMsgColor"]
)
initials = chum.initials() initials = chum.initials()
parent = self.parent() parent = self.parent()
window = parent.mainwindow window = parent.mainwindow
@ -438,28 +479,34 @@ class PesterText(QtWidgets.QTextEdit):
time = "" time = ""
if lexmsg[0] == "PESTERCHUM:BEGIN": if lexmsg[0] == "PESTERCHUM:BEGIN":
parent.setChumOpen(True) parent.setChumOpen(True)
pmsg = chum.pestermsg(me, systemColor, window.theme["convo/text/beganpester"]) pmsg = chum.pestermsg(
me, systemColor, window.theme["convo/text/beganpester"]
)
window.chatlog.log(chum.handle, pmsg) window.chatlog.log(chum.handle, pmsg)
self.append(convertTags(pmsg)) self.append(convertTags(pmsg))
elif lexmsg[0] == "PESTERCHUM:CEASE": elif lexmsg[0] == "PESTERCHUM:CEASE":
parent.setChumOpen(False) parent.setChumOpen(False)
pmsg = chum.pestermsg(me, systemColor, window.theme["convo/text/ceasepester"]) pmsg = chum.pestermsg(
me, systemColor, window.theme["convo/text/ceasepester"]
)
window.chatlog.log(chum.handle, pmsg) window.chatlog.log(chum.handle, pmsg)
self.append(convertTags(pmsg)) self.append(convertTags(pmsg))
elif lexmsg[0] == "PESTERCHUM:BLOCK": elif lexmsg[0] == "PESTERCHUM:BLOCK":
pmsg = chum.pestermsg(me, systemColor, window.theme['convo/text/blocked']) pmsg = chum.pestermsg(me, systemColor, window.theme["convo/text/blocked"])
window.chatlog.log(chum.handle, pmsg) window.chatlog.log(chum.handle, pmsg)
self.append(convertTags(pmsg)) self.append(convertTags(pmsg))
elif lexmsg[0] == "PESTERCHUM:UNBLOCK": elif lexmsg[0] == "PESTERCHUM:UNBLOCK":
pmsg = chum.pestermsg(me, systemColor, window.theme['convo/text/unblocked']) pmsg = chum.pestermsg(me, systemColor, window.theme["convo/text/unblocked"])
window.chatlog.log(chum.handle, pmsg) window.chatlog.log(chum.handle, pmsg)
self.append(convertTags(pmsg)) self.append(convertTags(pmsg))
elif lexmsg[0] == "PESTERCHUM:BLOCKED": elif lexmsg[0] == "PESTERCHUM:BLOCKED":
pmsg = chum.pestermsg(me, systemColor, window.theme['convo/text/blockedmsg']) pmsg = chum.pestermsg(
me, systemColor, window.theme["convo/text/blockedmsg"]
)
window.chatlog.log(chum.handle, pmsg) window.chatlog.log(chum.handle, pmsg)
self.append(convertTags(pmsg)) self.append(convertTags(pmsg))
elif lexmsg[0] == "PESTERCHUM:IDLE": elif lexmsg[0] == "PESTERCHUM:IDLE":
imsg = chum.idlemsg(systemColor, window.theme['convo/text/idle']) imsg = chum.idlemsg(systemColor, window.theme["convo/text/idle"])
window.chatlog.log(chum.handle, imsg) window.chatlog.log(chum.handle, imsg)
self.append(convertTags(imsg)) self.append(convertTags(imsg))
elif type(lexmsg[0]) is mecmd: elif type(lexmsg[0]) is mecmd:
@ -471,30 +518,34 @@ class PesterText(QtWidgets.QTextEdit):
self.append(time + convertTags(memsg)) self.append(time + convertTags(memsg))
else: else:
if not parent.chumopen and chum is not me: if not parent.chumopen and chum is not me:
beginmsg = chum.pestermsg(me, systemColor, window.theme["convo/text/beganpester"]) beginmsg = chum.pestermsg(
me, systemColor, window.theme["convo/text/beganpester"]
)
parent.setChumOpen(True) parent.setChumOpen(True)
window.chatlog.log(chum.handle, beginmsg) window.chatlog.log(chum.handle, beginmsg)
self.append(convertTags(beginmsg)) self.append(convertTags(beginmsg))
lexmsg[0:0] = [colorBegin("<c=%s>" % (color), color), lexmsg[0:0] = [colorBegin("<c=%s>" % (color), color), "%s: " % (initials)]
"%s: " % (initials)]
lexmsg.append(colorEnd("</c>")) lexmsg.append(colorEnd("</c>"))
self.append("<span style=\"color:#000000\">" self.append(
+ time '<span style="color:#000000">' + time + convertTags(lexmsg) + "</span>"
+ convertTags(lexmsg) )
+ "</span>") # self.append('<img src="/Users/lexi/pesterchum-lex/smilies/tab.gif" />'
#self.append('<img src="/Users/lexi/pesterchum-lex/smilies/tab.gif" />'
# + '<img src="/Users/lexi/pesterchum/smilies/tab.gif" />' # + '<img src="/Users/lexi/pesterchum/smilies/tab.gif" />'
# + '<img src="/Applications/Pesterchum.app/Contents/Resources/smilies/tab.gif" />' # + '<img src="/Applications/Pesterchum.app/Contents/Resources/smilies/tab.gif" />'
# + '<img src="smilies/tab.gif" />'); # + '<img src="smilies/tab.gif" />');
if chum is me: if chum is me:
window.chatlog.log(parent.chum.handle, lexmsg) window.chatlog.log(parent.chum.handle, lexmsg)
else: else:
if ((window.idler.auto or window.idler.manual) and parent.chumopen if (
and not parent.isBot(chum.handle)): (window.idler.auto or window.idler.manual)
and parent.chumopen
and not parent.isBot(chum.handle)
):
idlethreshhold = 60 idlethreshhold = 60
if (not hasattr(self, 'lastmsg')) or \ if (
datetime.now() - self.lastmsg > timedelta(0,idlethreshhold): not hasattr(self, "lastmsg")
) or datetime.now() - self.lastmsg > timedelta(0, idlethreshhold):
verb = window.theme["convo/text/idle"] verb = window.theme["convo/text/idle"]
idlemsg = me.idlemsg(systemColor, verb) idlemsg = me.idlemsg(systemColor, verb)
parent.textArea.append(convertTags(idlemsg)) parent.textArea.append(convertTags(idlemsg))
@ -502,10 +553,12 @@ class PesterText(QtWidgets.QTextEdit):
parent.messageSent.emit("PESTERCHUM:IDLE", parent.title()) parent.messageSent.emit("PESTERCHUM:IDLE", parent.title())
self.lastmsg = datetime.now() self.lastmsg = datetime.now()
window.chatlog.log(chum.handle, lexmsg) window.chatlog.log(chum.handle, lexmsg)
def changeTheme(self, theme): def changeTheme(self, theme):
self.initTheme(theme) self.initTheme(theme)
sb = self.verticalScrollBar() sb = self.verticalScrollBar()
sb.setValue(sb.maximum()) sb.setValue(sb.maximum())
def focusInEvent(self, event): def focusInEvent(self, event):
self.parent().clearNewMessage() self.parent().clearNewMessage()
QtWidgets.QTextEdit.focusInEvent(self, event) QtWidgets.QTextEdit.focusInEvent(self, event)
@ -516,12 +569,16 @@ class PesterText(QtWidgets.QTextEdit):
def keyPressEvent(self, event): def keyPressEvent(self, event):
# First parent is the PesterConvo containing this. # First parent is the PesterConvo containing this.
# Second parent is the PesterTabWindow containing *it*. # Second parent is the PesterTabWindow containing *it*.
pass_to_super = (QtCore.Qt.Key.Key_PageUp, QtCore.Qt.Key.Key_PageDown, pass_to_super = (
QtCore.Qt.Key.Key_Up, QtCore.Qt.Key.Key_Down) QtCore.Qt.Key.Key_PageUp,
QtCore.Qt.Key.Key_PageDown,
QtCore.Qt.Key.Key_Up,
QtCore.Qt.Key.Key_Down,
)
parent = self.parent() parent = self.parent()
key = event.key() key = event.key()
#keymods = event.modifiers() # keymods = event.modifiers()
if hasattr(parent, 'textInput') and key not in pass_to_super: if hasattr(parent, "textInput") and key not in pass_to_super:
# TODO: Shift focus here on bare (no modifiers) alphanumerics. # TODO: Shift focus here on bare (no modifiers) alphanumerics.
parent.textInput.keyPressEvent(event) parent.textInput.keyPressEvent(event)
@ -546,9 +603,11 @@ class PesterText(QtWidgets.QTextEdit):
if event.modifiers() == QtCore.Qt.KeyboardModifier.ControlModifier: if event.modifiers() == QtCore.Qt.KeyboardModifier.ControlModifier:
QtWidgets.QApplication.clipboard().setText(url) QtWidgets.QApplication.clipboard().setText(url)
else: else:
QtGui.QDesktopServices.openUrl(QtCore.QUrl(url, QtGui.QDesktopServices.openUrl(
QtCore.QUrl.ParsingMode.TolerantMode)) QtCore.QUrl(url, QtCore.QUrl.ParsingMode.TolerantMode)
)
QtWidgets.QTextEdit.mousePressEvent(self, event) QtWidgets.QTextEdit.mousePressEvent(self, event)
def mouseMoveEvent(self, event): def mouseMoveEvent(self, event):
QtWidgets.QTextEdit.mouseMoveEvent(self, event) QtWidgets.QTextEdit.mouseMoveEvent(self, event)
try: try:
@ -558,8 +617,13 @@ class PesterText(QtWidgets.QTextEdit):
# PyQt5 # PyQt5
pos = event.pos() pos = event.pos()
if self.anchorAt(pos): if self.anchorAt(pos):
if self.viewport().cursor().shape != QtCore.Qt.CursorShape.PointingHandCursor: if (
self.viewport().setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.PointingHandCursor)) self.viewport().cursor().shape
!= QtCore.Qt.CursorShape.PointingHandCursor
):
self.viewport().setCursor(
QtGui.QCursor(QtCore.Qt.CursorShape.PointingHandCursor)
)
else: else:
self.viewport().setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.IBeamCursor)) self.viewport().setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.IBeamCursor))
@ -567,17 +631,20 @@ class PesterText(QtWidgets.QTextEdit):
textMenu = self.createStandardContextMenu() textMenu = self.createStandardContextMenu()
textMenu.exec(event.globalPos()) textMenu.exec(event.globalPos())
class PesterInput(QtWidgets.QLineEdit): class PesterInput(QtWidgets.QLineEdit):
stylesheet_path = "convo/input/style" stylesheet_path = "convo/input/style"
def __init__(self, theme, parent=None): def __init__(self, theme, parent=None):
super(PesterInput, self).__init__(parent) super(PesterInput, self).__init__(parent)
self.changeTheme(theme) self.changeTheme(theme)
def changeTheme(self, theme): def changeTheme(self, theme):
# Explicitly set color if not already set. # Explicitly set color if not already set.
# (Some platforms seem to default to white instead of black.) # (Some platforms seem to default to white instead of black.)
StyleSheet = theme[self.stylesheet_path] StyleSheet = theme[self.stylesheet_path]
if "color:" not in theme[self.stylesheet_path].replace(' ', ''): if "color:" not in theme[self.stylesheet_path].replace(" ", ""):
StyleSheet = "color: black; " + StyleSheet StyleSheet = "color: black; " + StyleSheet
self.setStyleSheet(StyleSheet) self.setStyleSheet(StyleSheet)
@ -585,6 +652,7 @@ class PesterInput(QtWidgets.QLineEdit):
self.parent().clearNewMessage() self.parent().clearNewMessage()
self.parent().textArea.textCursor().clearSelection() self.parent().textArea.textCursor().clearSelection()
super(PesterInput, self).focusInEvent(event) super(PesterInput, self).focusInEvent(event)
def keyPressEvent(self, event): def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key.Key_Up: if event.key() == QtCore.Qt.Key.Key_Up:
text = str(self.text()) text = str(self.text())
@ -600,6 +668,7 @@ class PesterInput(QtWidgets.QLineEdit):
self.parent().mainwindow.idler.time = 0 self.parent().mainwindow.idler.time = 0
super(PesterInput, self).keyPressEvent(event) super(PesterInput, self).keyPressEvent(event)
class PesterConvo(QtWidgets.QFrame): class PesterConvo(QtWidgets.QFrame):
def __init__(self, chum, initiated, mainwindow, parent=None): def __init__(self, chum, initiated, mainwindow, parent=None):
super(PesterConvo, self).__init__(parent) super(PesterConvo, self).__init__(parent)
@ -610,9 +679,9 @@ class PesterConvo(QtWidgets.QFrame):
self.mainwindow = mainwindow self.mainwindow = mainwindow
theme = self.mainwindow.theme theme = self.mainwindow.theme
self.resize(*theme["convo/size"]) self.resize(*theme["convo/size"])
self.setStyleSheet("QtWidgets.QFrame#%s { %s }" self.setStyleSheet(
% (chum.handle, "QtWidgets.QFrame#%s { %s }" % (chum.handle, theme["convo/style"])
theme["convo/style"])) )
self.setWindowIcon(self.icon()) self.setWindowIcon(self.icon())
self.setWindowTitle(self.title()) self.setWindowTitle(self.title())
@ -620,12 +689,22 @@ class PesterConvo(QtWidgets.QFrame):
self.chumLabel = QtWidgets.QLabel(t.safe_substitute(handle=chum.handle), self) self.chumLabel = QtWidgets.QLabel(t.safe_substitute(handle=chum.handle), self)
self.chumLabel.setStyleSheet(self.mainwindow.theme["convo/chumlabel/style"]) self.chumLabel.setStyleSheet(self.mainwindow.theme["convo/chumlabel/style"])
self.chumLabel.setAlignment(self.aligndict["h"][self.mainwindow.theme["convo/chumlabel/align/h"]] self.chumLabel.setAlignment(
| self.aligndict["v"][self.mainwindow.theme["convo/chumlabel/align/v"]]) self.aligndict["h"][self.mainwindow.theme["convo/chumlabel/align/h"]]
self.chumLabel.setMaximumHeight(self.mainwindow.theme["convo/chumlabel/maxheight"]) | self.aligndict["v"][self.mainwindow.theme["convo/chumlabel/align/v"]]
self.chumLabel.setMinimumHeight(self.mainwindow.theme["convo/chumlabel/minheight"]) )
self.chumLabel.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, self.chumLabel.setMaximumHeight(
QtWidgets.QSizePolicy.Policy.MinimumExpanding)) self.mainwindow.theme["convo/chumlabel/maxheight"]
)
self.chumLabel.setMinimumHeight(
self.mainwindow.theme["convo/chumlabel/minheight"]
)
self.chumLabel.setSizePolicy(
QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Policy.MinimumExpanding,
QtWidgets.QSizePolicy.Policy.MinimumExpanding,
)
)
self.textArea = PesterText(self.mainwindow.theme, self) self.textArea = PesterText(self.mainwindow.theme, self)
self.textInput = PesterInput(self.mainwindow.theme, self) self.textInput = PesterInput(self.mainwindow.theme, self)
self.textInput.setFocus() self.textInput.setFocus()
@ -638,28 +717,45 @@ class PesterConvo(QtWidgets.QFrame):
self.layout.addWidget(self.textInput) self.layout.addWidget(self.textInput)
self.layout.setSpacing(0) self.layout.setSpacing(0)
margins = self.mainwindow.theme["convo/margins"] margins = self.mainwindow.theme["convo/margins"]
self.layout.setContentsMargins(margins["left"], margins["top"], self.layout.setContentsMargins(
margins["right"], margins["bottom"]) margins["left"], margins["top"], margins["right"], margins["bottom"]
)
self.setLayout(self.layout) self.setLayout(self.layout)
self.optionsMenu = QtWidgets.QMenu(self) self.optionsMenu = QtWidgets.QMenu(self)
self.optionsMenu.setStyleSheet(self.mainwindow.theme["main/defaultwindow/style"]) self.optionsMenu.setStyleSheet(
self.addChumAction = QAction(self.mainwindow.theme["main/menus/rclickchumlist/addchum"], self) self.mainwindow.theme["main/defaultwindow/style"]
)
self.addChumAction = QAction(
self.mainwindow.theme["main/menus/rclickchumlist/addchum"], self
)
self.addChumAction.triggered.connect(self.addThisChum) self.addChumAction.triggered.connect(self.addThisChum)
self.blockAction = QAction(self.mainwindow.theme["main/menus/rclickchumlist/blockchum"], self) self.blockAction = QAction(
self.mainwindow.theme["main/menus/rclickchumlist/blockchum"], self
)
self.blockAction.triggered.connect(self.blockThisChum) self.blockAction.triggered.connect(self.blockThisChum)
self.quirksOff = QAction(self.mainwindow.theme["main/menus/rclickchumlist/quirksoff"], self) self.quirksOff = QAction(
self.mainwindow.theme["main/menus/rclickchumlist/quirksoff"], self
)
self.quirksOff.setCheckable(True) self.quirksOff.setCheckable(True)
self.quirksOff.toggled[bool].connect(self.toggleQuirks) self.quirksOff.toggled[bool].connect(self.toggleQuirks)
self.oocToggle = QAction(self.mainwindow.theme["main/menus/rclickchumlist/ooc"], self) self.oocToggle = QAction(
self.mainwindow.theme["main/menus/rclickchumlist/ooc"], self
)
self.oocToggle.setCheckable(True) self.oocToggle.setCheckable(True)
self.oocToggle.toggled[bool].connect(self.toggleOOC) self.oocToggle.toggled[bool].connect(self.toggleOOC)
self.unblockchum = QAction(self.mainwindow.theme["main/menus/rclickchumlist/unblockchum"], self) self.unblockchum = QAction(
self.mainwindow.theme["main/menus/rclickchumlist/unblockchum"], self
)
self.unblockchum.triggered.connect(self.unblockChumSlot) self.unblockchum.triggered.connect(self.unblockChumSlot)
self.reportchum = QAction(self.mainwindow.theme["main/menus/rclickchumlist/report"], self) self.reportchum = QAction(
self.mainwindow.theme["main/menus/rclickchumlist/report"], self
)
self.reportchum.triggered.connect(self.reportThisChum) self.reportchum.triggered.connect(self.reportThisChum)
self.logchum = QAction(self.mainwindow.theme["main/menus/rclickchumlist/viewlog"], self) self.logchum = QAction(
self.mainwindow.theme["main/menus/rclickchumlist/viewlog"], self
)
self.logchum.triggered.connect(self.openChumLogs) self.logchum.triggered.connect(self.openChumLogs)
# For this, we'll want to use setChecked to toggle these so they match # For this, we'll want to use setChecked to toggle these so they match
@ -670,25 +766,32 @@ class PesterConvo(QtWidgets.QFrame):
# TODO: Look into setting up theme support here. # TODO: Look into setting up theme support here.
# Theme support :3c # Theme support :3c
#if self.mainwindow.theme.has_key("main/menus/rclickchumlist/beeponmessage"): # if self.mainwindow.theme.has_key("main/menus/rclickchumlist/beeponmessage"):
try: try:
self._beepToggle = QAction(self.mainwindow.theme["main/menus/rclickchumlist/beeponmessage"], self) self._beepToggle = QAction(
self.mainwindow.theme["main/menus/rclickchumlist/beeponmessage"], self
)
except: except:
self._beepToggle = QAction("BEEP ON MESSAGE", self) self._beepToggle = QAction("BEEP ON MESSAGE", self)
self._beepToggle.setCheckable(True) self._beepToggle.setCheckable(True)
self._beepToggle.toggled[bool].connect(self.toggleBeep) self._beepToggle.toggled[bool].connect(self.toggleBeep)
#if self.mainwindow.theme.has_key("main/menus/rclickchumlist/flashonmessage"): # if self.mainwindow.theme.has_key("main/menus/rclickchumlist/flashonmessage"):
try: try:
self._flashToggle = QAction(self.mainwindow.theme["main/menus/rclickchumlist/flashonmessage"], self) self._flashToggle = QAction(
self.mainwindow.theme["main/menus/rclickchumlist/flashonmessage"], self
)
except: except:
self._flashToggle = QAction("FLASH ON MESSAGE", self) self._flashToggle = QAction("FLASH ON MESSAGE", self)
self._flashToggle.setCheckable(True) self._flashToggle.setCheckable(True)
self._flashToggle.toggled[bool].connect(self.toggleFlash) self._flashToggle.toggled[bool].connect(self.toggleFlash)
#if self.mainwindow.theme.has_key("main/menus/rclickchumlist/mutenotifications"): # if self.mainwindow.theme.has_key("main/menus/rclickchumlist/mutenotifications"):
try: try:
self._muteToggle = QAction(self.mainwindow.theme["main/menus/rclickchumlist/mutenotifications"], self) self._muteToggle = QAction(
self.mainwindow.theme["main/menus/rclickchumlist/mutenotifications"],
self,
)
except: except:
self._muteToggle = QAction("MUTE NOTIFICATIONS", self) self._muteToggle = QAction("MUTE NOTIFICATIONS", self)
self._muteToggle.setCheckable(True) self._muteToggle.setCheckable(True)
@ -717,7 +820,11 @@ class PesterConvo(QtWidgets.QFrame):
if parent: if parent:
parent.addChat(self) parent.addChat(self)
if initiated: if initiated:
msg = self.mainwindow.profile().pestermsg(self.chum, QtGui.QColor(self.mainwindow.theme["convo/systemMsgColor"]), self.mainwindow.theme["convo/text/beganpester"]) msg = self.mainwindow.profile().pestermsg(
self.chum,
QtGui.QColor(self.mainwindow.theme["convo/systemMsgColor"]),
self.mainwindow.theme["convo/text/beganpester"],
)
self.setChumOpen(True) self.setChumOpen(True)
self.textArea.append(convertTags(msg)) self.textArea.append(convertTags(msg))
self.mainwindow.chatlog.log(self.title(), msg) self.mainwindow.chatlog.log(self.title(), msg)
@ -726,8 +833,10 @@ class PesterConvo(QtWidgets.QFrame):
def title(self): def title(self):
return self.chum.handle return self.chum.handle
def icon(self): def icon(self):
return self.chum.mood.icon(self.mainwindow.theme) return self.chum.mood.icon(self.mainwindow.theme)
def myUpdateMood(self, mood): def myUpdateMood(self, mood):
chum = self.mainwindow.profile() chum = self.mainwindow.profile()
syscolor = QtGui.QColor(self.mainwindow.theme["convo/systemMsgColor"]) syscolor = QtGui.QColor(self.mainwindow.theme["convo/systemMsgColor"])
@ -740,12 +849,12 @@ class PesterConvo(QtWidgets.QFrame):
def updateMood(self, mood, unblocked=False, old=None): def updateMood(self, mood, unblocked=False, old=None):
syscolor = QtGui.QColor(self.mainwindow.theme["convo/systemMsgColor"]) syscolor = QtGui.QColor(self.mainwindow.theme["convo/systemMsgColor"])
#~ if mood.name() == "offline" and self.chumopen == True and not unblocked: # ~ if mood.name() == "offline" and self.chumopen == True and not unblocked:
#~ self.mainwindow.ceasesound.play() # ~ self.mainwindow.ceasesound.play()
#~ msg = self.chum.pestermsg(self.mainwindow.profile(), syscolor, self.mainwindow.theme["convo/text/ceasepester"]) # ~ msg = self.chum.pestermsg(self.mainwindow.profile(), syscolor, self.mainwindow.theme["convo/text/ceasepester"])
#~ self.textArea.append(convertTags(msg)) # ~ self.textArea.append(convertTags(msg))
#~ self.mainwindow.chatlog.log(self.title(), msg) # ~ self.mainwindow.chatlog.log(self.title(), msg)
#~ self.chumopen = False # ~ self.chumopen = False
if old and old.name() != mood.name(): if old and old.name() != mood.name():
msg = self.chum.moodmsg(mood, syscolor, self.mainwindow.theme) msg = self.chum.moodmsg(mood, syscolor, self.mainwindow.theme)
self.textArea.append(convertTags(msg)) self.textArea.append(convertTags(msg))
@ -754,7 +863,9 @@ class PesterConvo(QtWidgets.QFrame):
self.parent().updateMood(self.title(), mood, unblocked) self.parent().updateMood(self.title(), mood, unblocked)
else: else:
if self.chum.blocked(self.mainwindow.config) and not unblocked: if self.chum.blocked(self.mainwindow.config) and not unblocked:
self.setWindowIcon(QtGui.QIcon(self.mainwindow.theme["main/chums/moods/blocked/icon"])) self.setWindowIcon(
QtGui.QIcon(self.mainwindow.theme["main/chums/moods/blocked/icon"])
)
self.optionsMenu.addAction(self.unblockchum) self.optionsMenu.addAction(self.unblockchum)
self.optionsMenu.removeAction(self.blockAction) self.optionsMenu.removeAction(self.blockAction)
else: else:
@ -762,17 +873,21 @@ class PesterConvo(QtWidgets.QFrame):
self.optionsMenu.removeAction(self.unblockchum) self.optionsMenu.removeAction(self.unblockchum)
self.optionsMenu.addAction(self.blockAction) self.optionsMenu.addAction(self.blockAction)
# print mood update? # print mood update?
def updateBlocked(self): def updateBlocked(self):
if self.parent(): if self.parent():
self.parent().updateBlocked(self.title()) self.parent().updateBlocked(self.title())
else: else:
self.setWindowIcon(QtGui.QIcon(self.mainwindow.theme["main/chums/moods/blocked/icon"])) self.setWindowIcon(
QtGui.QIcon(self.mainwindow.theme["main/chums/moods/blocked/icon"])
)
self.optionsMenu.addAction(self.unblockchum) self.optionsMenu.addAction(self.unblockchum)
self.optionsMenu.removeAction(self.blockAction) self.optionsMenu.removeAction(self.blockAction)
def updateColor(self, color): def updateColor(self, color):
PchumLog.debug("convo updateColor: " + str(color)) PchumLog.debug("convo updateColor: " + str(color))
self.chum.color = color self.chum.color = color
def addMessage(self, msg, me=True): def addMessage(self, msg, me=True):
if type(msg) in [str, str]: if type(msg) in [str, str]:
lexmsg = lexMessage(msg) lexmsg = lexMessage(msg)
@ -796,16 +911,19 @@ class PesterConvo(QtWidgets.QFrame):
memoblink &= self.mainwindow.config.MBLINK memoblink &= self.mainwindow.config.MBLINK
pesterblink &= self.mainwindow.config.PBLINK pesterblink &= self.mainwindow.config.PBLINK
mutednots = self.notifications_muted mutednots = self.notifications_muted
#mtsrc = self # mtsrc = self
if parent: if parent:
try: try:
mutednots = parent.notifications_muted mutednots = parent.notifications_muted
#mtsrc = parent # mtsrc = parent
except: except:
pass pass
if not (self.hasFocus() or self.textArea.hasFocus() or if not (
self.textInput.hasFocus() or self.hasFocus()
(parent and parent.convoHasFocus(title))): or self.textArea.hasFocus()
or self.textInput.hasFocus()
or (parent and parent.convoHasFocus(title))
):
# ok if it has a tabconvo parent, send that the notify. # ok if it has a tabconvo parent, send that the notify.
if parent: if parent:
# Just let the icon highlight normally. # Just let the icon highlight normally.
@ -818,11 +936,11 @@ class PesterConvo(QtWidgets.QFrame):
# the checks there. # the checks there.
# PesterTabWindow -> MemoTabWindow # PesterTabWindow -> MemoTabWindow
if isinstance(parent, MemoTabWindow): if isinstance(parent, MemoTabWindow):
if self.always_flash or memoblink: if self.always_flash or memoblink:
self.mainwindow.gainAttention.emit(parent) self.mainwindow.gainAttention.emit(parent)
elif isinstance(parent, PesterTabWindow): elif isinstance(parent, PesterTabWindow):
if self.always_flash or pesterblink: if self.always_flash or pesterblink:
self.mainwindow.gainAttention.emit(parent) self.mainwindow.gainAttention.emit(parent)
# if not change the window title and update system tray # if not change the window title and update system tray
else: else:
self.newmessage = True self.newmessage = True
@ -831,15 +949,16 @@ class PesterConvo(QtWidgets.QFrame):
# entirely sure how much of this directly affects what we see. # entirely sure how much of this directly affects what we see.
def func(): def func():
self.showChat() self.showChat()
self.mainwindow.waitingMessages.addMessage(title, func) self.mainwindow.waitingMessages.addMessage(title, func)
if not mutednots: if not mutednots:
# Once again, PesterMemo inherits from PesterConvo. # Once again, PesterMemo inherits from PesterConvo.
if isinstance(self, PesterMemo): if isinstance(self, PesterMemo):
if self.always_flash or memoblink: if self.always_flash or memoblink:
self.mainwindow.gainAttention.emit(self) self.mainwindow.gainAttention.emit(self)
elif isinstance(self, PesterConvo): elif isinstance(self, PesterConvo):
if self.always_flash or pesterblink: if self.always_flash or pesterblink:
self.mainwindow.gainAttention.emit(self) self.mainwindow.gainAttention.emit(self)
def clearNewMessage(self): def clearNewMessage(self):
if self.parent(): if self.parent():
@ -849,6 +968,7 @@ class PesterConvo(QtWidgets.QFrame):
self.setWindowTitle(self.title()) self.setWindowTitle(self.title())
self.mainwindow.waitingMessages.messageAnswered(self.title()) self.mainwindow.waitingMessages.messageAnswered(self.title())
# reset system tray # reset system tray
def focusInEvent(self, event): def focusInEvent(self, event):
self.clearNewMessage() self.clearNewMessage()
self.textInput.setFocus() self.textInput.setFocus()
@ -862,68 +982,101 @@ class PesterConvo(QtWidgets.QFrame):
if self.parent(): if self.parent():
self.parent().showChat(self.title()) self.parent().showChat(self.title())
self.raiseChat() self.raiseChat()
def contextMenuEvent(self, event): def contextMenuEvent(self, event):
if event.reason() == QtGui.QContextMenuEvent.Reason.Mouse: if event.reason() == QtGui.QContextMenuEvent.Reason.Mouse:
self.optionsMenu.popup(event.globalPos()) self.optionsMenu.popup(event.globalPos())
def closeEvent(self, event): def closeEvent(self, event):
self.mainwindow.waitingMessages.messageAnswered(self.title()) self.mainwindow.waitingMessages.messageAnswered(self.title())
for movie in self.textArea.urls: for movie in self.textArea.urls:
movie.setFileName("") # Required, sometimes, for some reason. . . movie.setFileName("") # Required, sometimes, for some reason. . .
movie.stop() movie.stop()
del movie del movie
self.windowClosed.emit(self.title()) self.windowClosed.emit(self.title())
def setChumOpen(self, o): def setChumOpen(self, o):
self.chumopen = o self.chumopen = o
def changeTheme(self, theme): def changeTheme(self, theme):
self.resize(*theme["convo/size"]) self.resize(*theme["convo/size"])
self.setStyleSheet("QtWidgets.QFrame#%s { %s }" % (self.chum.handle, theme["convo/style"])) self.setStyleSheet(
"QtWidgets.QFrame#%s { %s }" % (self.chum.handle, theme["convo/style"])
)
margins = theme["convo/margins"] margins = theme["convo/margins"]
self.layout.setContentsMargins(margins["left"], margins["top"], self.layout.setContentsMargins(
margins["right"], margins["bottom"]) margins["left"], margins["top"], margins["right"], margins["bottom"]
)
self.setWindowIcon(self.icon()) self.setWindowIcon(self.icon())
t = Template(self.mainwindow.theme["convo/chumlabel/text"]) t = Template(self.mainwindow.theme["convo/chumlabel/text"])
self.chumLabel.setText(t.safe_substitute(handle=self.title())) self.chumLabel.setText(t.safe_substitute(handle=self.title()))
self.chumLabel.setStyleSheet(theme["convo/chumlabel/style"]) self.chumLabel.setStyleSheet(theme["convo/chumlabel/style"])
self.chumLabel.setAlignment(self.aligndict["h"][self.mainwindow.theme["convo/chumlabel/align/h"]] | self.aligndict["v"][self.mainwindow.theme["convo/chumlabel/align/v"]]) self.chumLabel.setAlignment(
self.chumLabel.setMaximumHeight(self.mainwindow.theme["convo/chumlabel/maxheight"]) self.aligndict["h"][self.mainwindow.theme["convo/chumlabel/align/h"]]
self.chumLabel.setMinimumHeight(self.mainwindow.theme["convo/chumlabel/minheight"]) | self.aligndict["v"][self.mainwindow.theme["convo/chumlabel/align/v"]]
self.chumLabel.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.Expanding)) )
self.quirksOff.setText(self.mainwindow.theme["main/menus/rclickchumlist/quirksoff"]) self.chumLabel.setMaximumHeight(
self.addChumAction.setText(self.mainwindow.theme["main/menus/rclickchumlist/addchum"]) self.mainwindow.theme["convo/chumlabel/maxheight"]
self.blockAction.setText(self.mainwindow.theme["main/menus/rclickchumlist/blockchum"]) )
self.unblockchum.setText(self.mainwindow.theme["main/menus/rclickchumlist/unblockchum"]) self.chumLabel.setMinimumHeight(
self.mainwindow.theme["convo/chumlabel/minheight"]
)
self.chumLabel.setSizePolicy(
QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Policy.MinimumExpanding,
QtWidgets.QSizePolicy.Policy.Expanding,
)
)
self.quirksOff.setText(
self.mainwindow.theme["main/menus/rclickchumlist/quirksoff"]
)
self.addChumAction.setText(
self.mainwindow.theme["main/menus/rclickchumlist/addchum"]
)
self.blockAction.setText(
self.mainwindow.theme["main/menus/rclickchumlist/blockchum"]
)
self.unblockchum.setText(
self.mainwindow.theme["main/menus/rclickchumlist/unblockchum"]
)
self.logchum.setText(self.mainwindow.theme["main/menus/rclickchumlist/viewlog"]) self.logchum.setText(self.mainwindow.theme["main/menus/rclickchumlist/viewlog"])
#if self.mainwindow.theme.has_key("main/menus/rclickchumlist/beeponmessage"): # if self.mainwindow.theme.has_key("main/menus/rclickchumlist/beeponmessage"):
try: try:
self._beepToggle.setText(self.mainwindow.theme["main/menus/rclickchumlist/beeponmessage"]) self._beepToggle.setText(
self.mainwindow.theme["main/menus/rclickchumlist/beeponmessage"]
)
except: except:
self._beepToggle.setText("BEEP ON MESSAGE") self._beepToggle.setText("BEEP ON MESSAGE")
#if self.mainwindow.theme.has_key("main/menus/rclickchumlist/flashonmessage"): # if self.mainwindow.theme.has_key("main/menus/rclickchumlist/flashonmessage"):
try: try:
self._flashToggle.setText(self.mainwindow.theme["main/menus/rclickchumlist/flashonmessage"]) self._flashToggle.setText(
self.mainwindow.theme["main/menus/rclickchumlist/flashonmessage"]
)
except: except:
self._flashToggle.setText("FLASH ON MESSAGE", self) self._flashToggle.setText("FLASH ON MESSAGE", self)
#if self.mainwindow.theme.has_key("main/menus/rclickchumlist/mutenotifications"): # if self.mainwindow.theme.has_key("main/menus/rclickchumlist/mutenotifications"):
try: try:
self._muteToggle.setText(self.mainwindow.theme["main/menus/rclickchumlist/mutenotifications"]) self._muteToggle.setText(
self.mainwindow.theme["main/menus/rclickchumlist/mutenotifications"]
)
except: except:
self._muteToggle.setText("MUTE NOTIFICATIONS") self._muteToggle.setText("MUTE NOTIFICATIONS")
#if self.mainwindow.theme.has_key("main/menus/rclickchumlist/report"): # if self.mainwindow.theme.has_key("main/menus/rclickchumlist/report"):
try: try:
self.reportchum.setText(self.mainwindow.theme["main/menus/rclickchumlist/report"]) self.reportchum.setText(
self.mainwindow.theme["main/menus/rclickchumlist/report"]
)
except: except:
pass pass
self.textArea.changeTheme(theme) self.textArea.changeTheme(theme)
self.textInput.changeTheme(theme) self.textInput.changeTheme(theme)
@QtCore.pyqtSlot() @QtCore.pyqtSlot()
def sentMessage(self): def sentMessage(self):
# Offloaded to another function, like its sisters. # Offloaded to another function, like its sisters.
@ -936,29 +1089,36 @@ class PesterConvo(QtWidgets.QFrame):
@QtCore.pyqtSlot() @QtCore.pyqtSlot()
def addThisChum(self): def addThisChum(self):
self.mainwindow.addChum(self.chum) self.mainwindow.addChum(self.chum)
@QtCore.pyqtSlot() @QtCore.pyqtSlot()
def blockThisChum(self): def blockThisChum(self):
self.mainwindow.blockChum(self.chum.handle) self.mainwindow.blockChum(self.chum.handle)
@QtCore.pyqtSlot() @QtCore.pyqtSlot()
def reportThisChum(self): def reportThisChum(self):
self.mainwindow.reportChum(self.chum.handle) self.mainwindow.reportChum(self.chum.handle)
@QtCore.pyqtSlot() @QtCore.pyqtSlot()
def unblockChumSlot(self): def unblockChumSlot(self):
self.mainwindow.unblockChum(self.chum.handle) self.mainwindow.unblockChum(self.chum.handle)
@QtCore.pyqtSlot(bool) @QtCore.pyqtSlot(bool)
def toggleQuirks(self, toggled): def toggleQuirks(self, toggled):
self.applyquirks = not toggled self.applyquirks = not toggled
@QtCore.pyqtSlot(bool) @QtCore.pyqtSlot(bool)
def toggleOOC(self, toggled): def toggleOOC(self, toggled):
self.ooc = toggled self.ooc = toggled
@QtCore.pyqtSlot() @QtCore.pyqtSlot()
def openChumLogs(self): def openChumLogs(self):
currentChum = self.chum.handle currentChum = self.chum.handle
self.mainwindow.chumList.pesterlogviewer = PesterLogViewer(currentChum, self.mainwindow.chumList.pesterlogviewer = PesterLogViewer(
self.mainwindow.config, currentChum, self.mainwindow.config, self.mainwindow.theme, self.mainwindow
self.mainwindow.theme, )
self.mainwindow) self.mainwindow.chumList.pesterlogviewer.rejected.connect(
self.mainwindow.chumList.pesterlogviewer.rejected.connect(self.mainwindow.chumList.closeActiveLog) self.mainwindow.chumList.closeActiveLog
)
self.mainwindow.chumList.pesterlogviewer.show() self.mainwindow.chumList.pesterlogviewer.show()
self.mainwindow.chumList.pesterlogviewer.raise_() self.mainwindow.chumList.pesterlogviewer.raise_()
self.mainwindow.chumList.pesterlogviewer.activateWindow() self.mainwindow.chumList.pesterlogviewer.activateWindow()
@ -975,15 +1135,22 @@ class PesterConvo(QtWidgets.QFrame):
def toggleMute(self, toggled): def toggleMute(self, toggled):
self.notifications_muted = toggled self.notifications_muted = toggled
messageSent = QtCore.pyqtSignal('QString', 'QString') messageSent = QtCore.pyqtSignal("QString", "QString")
windowClosed = QtCore.pyqtSignal('QString') windowClosed = QtCore.pyqtSignal("QString")
aligndict = {
"h": {
"center": QtCore.Qt.AlignmentFlag.AlignHCenter,
"left": QtCore.Qt.AlignmentFlag.AlignLeft,
"right": QtCore.Qt.AlignmentFlag.AlignRight,
},
"v": {
"center": QtCore.Qt.AlignmentFlag.AlignVCenter,
"top": QtCore.Qt.AlignmentFlag.AlignTop,
"bottom": QtCore.Qt.AlignmentFlag.AlignBottom,
},
}
aligndict = {"h": {"center": QtCore.Qt.AlignmentFlag.AlignHCenter,
"left": QtCore.Qt.AlignmentFlag.AlignLeft,
"right": QtCore.Qt.AlignmentFlag.AlignRight },
"v": {"center": QtCore.Qt.AlignmentFlag.AlignVCenter,
"top": QtCore.Qt.AlignmentFlag.AlignTop,
"bottom": QtCore.Qt.AlignmentFlag.AlignBottom } }
# the import is way down here to avoid recursive imports # the import is way down here to avoid recursive imports
from logviewer import PesterLogViewer from logviewer import PesterLogViewer

View file

@ -1,6 +1,7 @@
import logging import logging
import ostools import ostools
PchumLog = logging.getLogger('pchumLogger')
PchumLog = logging.getLogger("pchumLogger")
try: try:
from PyQt6 import QtGui from PyQt6 import QtGui
except ImportError: except ImportError:
@ -11,15 +12,17 @@ import re
import random import random
from mood import Mood from mood import Mood
from parsetools import (timeDifference, from parsetools import (
convertTags, timeDifference,
lexMessage, convertTags,
parseRegexpFunctions, lexMessage,
smiledict) parseRegexpFunctions,
smiledict,
)
from mispeller import mispeller from mispeller import mispeller
_urlre = re.compile(r"(?i)(?:^|(?<=\s))(?:(?:https?|ftp)://|magnet:)[^\s]+") _urlre = re.compile(r"(?i)(?:^|(?<=\s))(?:(?:https?|ftp)://|magnet:)[^\s]+")
#_url2re = re.compile(r"(?i)(?<!//)\bwww\.[^\s]+?\.") # _url2re = re.compile(r"(?i)(?<!//)\bwww\.[^\s]+?\.")
_groupre = re.compile(r"\\([0-9]+)") _groupre = re.compile(r"\\([0-9]+)")
_upperre = re.compile(r"upper\(([\w<>\\]+)\)") _upperre = re.compile(r"upper\(([\w<>\\]+)\)")
_lowerre = re.compile(r"lower\(([\w<>\\]+)\)") _lowerre = re.compile(r"lower\(([\w<>\\]+)\)")
@ -30,6 +33,7 @@ _smilere = re.compile("|".join(list(smiledict.keys())))
_memore = re.compile(r"(\s|^)(#[A-Za-z0-9_]+)") _memore = re.compile(r"(\s|^)(#[A-Za-z0-9_]+)")
_handlere = re.compile(r"(\s|^)(@[A-Za-z0-9_]+)") _handlere = re.compile(r"(\s|^)(@[A-Za-z0-9_]+)")
class pesterQuirk(object): class pesterQuirk(object):
def __init__(self, quirk): def __init__(self, quirk):
if type(quirk) != dict: if type(quirk) != dict:
@ -60,7 +64,7 @@ class pesterQuirk(object):
fr = self.quirk["from"] fr = self.quirk["from"]
if not first and len(fr) > 0 and fr[0] == "^": if not first and len(fr) > 0 and fr[0] == "^":
return string return string
if not last and len(fr) > 0 and fr[len(fr)-1] == "$": if not last and len(fr) > 0 and fr[len(fr) - 1] == "$":
return string return string
to = self.quirk["to"] to = self.quirk["to"]
pt = parseRegexpFunctions(to) pt = parseRegexpFunctions(to)
@ -71,15 +75,17 @@ class pesterQuirk(object):
fr = self.quirk["from"] fr = self.quirk["from"]
if not first and len(fr) > 0 and fr[0] == "^": if not first and len(fr) > 0 and fr[0] == "^":
return string return string
if not last and len(fr) > 0 and fr[len(fr)-1] == "$": if not last and len(fr) > 0 and fr[len(fr) - 1] == "$":
return string return string
def randomrep(mo): def randomrep(mo):
choice = random.choice(self.quirk["randomlist"]) choice = random.choice(self.quirk["randomlist"])
pt = parseRegexpFunctions(choice) pt = parseRegexpFunctions(choice)
return pt.expand(mo) return pt.expand(mo)
return re.sub(self.quirk["from"], randomrep, string) return re.sub(self.quirk["from"], randomrep, string)
elif self.type == "spelling": elif self.type == "spelling":
percentage = self.quirk["percentage"]/100.0 percentage = self.quirk["percentage"] / 100.0
words = string.split(" ") words = string.split(" ")
newl = [] newl = []
ctag = re.compile("(</?c=?.*?>)", re.I) ctag = re.compile("(</?c=?.*?>)", re.I)
@ -108,27 +114,37 @@ class pesterQuirk(object):
elif self.type == "replace": elif self.type == "replace":
return "REPLACE %s WITH %s" % (self.quirk["from"], self.quirk["to"]) return "REPLACE %s WITH %s" % (self.quirk["from"], self.quirk["to"])
elif self.type == "regexp": elif self.type == "regexp":
return "REGEXP: %s REPLACED WITH %s" % (self.quirk["from"], self.quirk["to"]) return "REGEXP: %s REPLACED WITH %s" % (
self.quirk["from"],
self.quirk["to"],
)
elif self.type == "random": elif self.type == "random":
return "REGEXP: %s RANDOMLY REPLACED WITH %s" % (self.quirk["from"], [r for r in self.quirk["randomlist"]]) return "REGEXP: %s RANDOMLY REPLACED WITH %s" % (
self.quirk["from"],
[r for r in self.quirk["randomlist"]],
)
elif self.type == "spelling": elif self.type == "spelling":
return "MISPELLER: %d%%" % (self.quirk["percentage"]) return "MISPELLER: %d%%" % (self.quirk["percentage"])
class pesterQuirks(object): class pesterQuirks(object):
def __init__(self, quirklist): def __init__(self, quirklist):
self.quirklist = [] self.quirklist = []
for q in quirklist: for q in quirklist:
self.addQuirk(q) self.addQuirk(q)
def plainList(self): def plainList(self):
return [q.quirk for q in self.quirklist] return [q.quirk for q in self.quirklist]
def addQuirk(self, q): def addQuirk(self, q):
if type(q) == dict: if type(q) == dict:
self.quirklist.append(pesterQuirk(q)) self.quirklist.append(pesterQuirk(q))
elif type(q) == pesterQuirk: elif type(q) == pesterQuirk:
self.quirklist.append(q) self.quirklist.append(q)
def apply(self, lexed, first=False, last=False): def apply(self, lexed, first=False, last=False):
prefix = [q for q in self.quirklist if q.type=='prefix'] prefix = [q for q in self.quirklist if q.type == "prefix"]
suffix = [q for q in self.quirklist if q.type=='suffix'] suffix = [q for q in self.quirklist if q.type == "suffix"]
newlist = [] newlist = []
for (i, o) in enumerate(lexed): for (i, o) in enumerate(lexed):
@ -140,7 +156,7 @@ class pesterQuirks(object):
newlist.append(string) newlist.append(string)
newlist.append(o) newlist.append(o)
continue continue
lastStr = (i == len(lexed)-1) lastStr = i == len(lexed) - 1
string = o string = o
for q in self.quirklist: for q in self.quirklist:
try: try:
@ -170,8 +186,8 @@ class pesterQuirks(object):
excludes.sort(key=lambda exclude: exclude.start()) excludes.sort(key=lambda exclude: exclude.start())
# Recursion check. # Recursion check.
# Strings like http://:3: require this. # Strings like http://:3: require this.
for n in range(0, len(excludes)-1): for n in range(0, len(excludes) - 1):
if excludes[n].end() > excludes[n+1].start(): if excludes[n].end() > excludes[n + 1].start():
excludes.pop(n) excludes.pop(n)
# Seperate parts to be quirked. # Seperate parts to be quirked.
sendparts = list() sendparts = list()
@ -180,7 +196,7 @@ class pesterQuirks(object):
sendparts.append(string[:until]) sendparts.append(string[:until])
# Add strings between excludes. # Add strings between excludes.
for part in range(1, len(excludes)): for part in range(1, len(excludes)):
after = excludes[part-1].end() after = excludes[part - 1].end()
until = excludes[part].start() until = excludes[part].start()
sendparts.append(string[after:until]) sendparts.append(string[after:until])
# Add string after exclude at last index. # Add string after exclude at last index.
@ -191,50 +207,46 @@ class pesterQuirks(object):
recvparts = list() recvparts = list()
for part in sendparts: for part in sendparts:
# No split, apply like normal. # No split, apply like normal.
if q.type == 'regexp' or q.type == 'random': if q.type == "regexp" or q.type == "random":
recvparts.append(q.apply(part, recvparts.append(
first=(i==0), q.apply(part, first=(i == 0), last=lastStr)
last=lastStr)) )
elif q.type == 'prefix' and i == 0: elif q.type == "prefix" and i == 0:
recvparts.append(q.apply(part)) recvparts.append(q.apply(part))
elif q.type == 'suffix' and lastStr: elif q.type == "suffix" and lastStr:
recvparts.append(q.apply(part)) recvparts.append(q.apply(part))
else: else:
recvparts.append(q.apply(part)) recvparts.append(q.apply(part))
# Reconstruct and update string. # Reconstruct and update string.
string = '' string = ""
#print("excludes: " + str(excludes)) # print("excludes: " + str(excludes))
#print("sendparts: " + str(sendparts)) # print("sendparts: " + str(sendparts))
#print("recvparts: " + str(recvparts)) # print("recvparts: " + str(recvparts))
for part in range(0, len(excludes)): for part in range(0, len(excludes)):
string += recvparts[part] string += recvparts[part]
string += excludes[part].group() string += excludes[part].group()
string += recvparts[-1] string += recvparts[-1]
else: else:
# No split, apply like normal. # No split, apply like normal.
if q.type != 'prefix' and q.type != 'suffix': if q.type != "prefix" and q.type != "suffix":
if q.type == 'regexp' or q.type == 'random': if q.type == "regexp" or q.type == "random":
string = q.apply(string, string = q.apply(string, first=(i == 0), last=lastStr)
first=(i==0),
last=lastStr)
else: else:
string = q.apply(string) string = q.apply(string)
elif q.type == 'prefix' and i == 0: elif q.type == "prefix" and i == 0:
string = q.apply(string) string = q.apply(string)
elif q.type == 'suffix' and lastStr: elif q.type == "suffix" and lastStr:
string = q.apply(string) string = q.apply(string)
else: else:
# No split, apply like normal. # No split, apply like normal.
if q.type != 'prefix' and q.type != 'suffix': if q.type != "prefix" and q.type != "suffix":
if q.type == 'regexp' or q.type == 'random': if q.type == "regexp" or q.type == "random":
string = q.apply(string, string = q.apply(string, first=(i == 0), last=lastStr)
first=(i==0),
last=lastStr)
else: else:
string = q.apply(string) string = q.apply(string)
elif q.type == 'prefix' and i == 0: elif q.type == "prefix" and i == 0:
string = q.apply(string) string = q.apply(string)
elif q.type == 'suffix' and lastStr: elif q.type == "suffix" and lastStr:
string = q.apply(string) string = q.apply(string)
newlist.append(string) newlist.append(string)
final = [] final = []
@ -249,8 +261,17 @@ class pesterQuirks(object):
for q in self.quirklist: for q in self.quirklist:
yield q yield q
class PesterProfile(object): class PesterProfile(object):
def __init__(self, handle, color=None, mood=Mood("offline"), group=None, notes="", chumdb=None): def __init__(
self,
handle,
color=None,
mood=Mood("offline"),
group=None,
notes="",
chumdb=None,
):
self.handle = handle self.handle = handle
if color is None: if color is None:
if chumdb: if chumdb:
@ -266,6 +287,7 @@ class PesterProfile(object):
group = "Chums" group = "Chums"
self.group = group self.group = group
self.notes = notes self.notes = notes
def initials(self, time=None): def initials(self, time=None):
handle = self.handle handle = self.handle
caps = [l for l in handle if l.isupper()] caps = [l for l in handle if l.isupper()]
@ -275,37 +297,46 @@ class PesterProfile(object):
PchumLog.debug("caps = " + str(caps)) PchumLog.debug("caps = " + str(caps))
# Fallback for invalid string # Fallback for invalid string
try: try:
initials = (handle[0]+caps[0]).upper() initials = (handle[0] + caps[0]).upper()
except: except:
PchumLog.exception('') PchumLog.exception("")
initials = "XX" initials = "XX"
PchumLog.debug("initials = " + str(initials)) PchumLog.debug("initials = " + str(initials))
if hasattr(self, 'time') and time: if hasattr(self, "time") and time:
if self.time > time: if self.time > time:
return "F"+initials return "F" + initials
elif self.time < time: elif self.time < time:
return "P"+initials return "P" + initials
else: else:
return "C"+initials return "C" + initials
else: else:
return initials return initials
def colorhtml(self): def colorhtml(self):
if self.color: if self.color:
return self.color.name() return self.color.name()
else: else:
return "#000000" return "#000000"
def colorcmd(self): def colorcmd(self):
if self.color: if self.color:
(r, g, b, a) = self.color.getRgb() (r, g, b, a) = self.color.getRgb()
return "%d,%d,%d" % (r,g,b) return "%d,%d,%d" % (r, g, b)
else: else:
return "0,0,0" return "0,0,0"
def plaindict(self): def plaindict(self):
return (self.handle, {"handle": self.handle, return (
"mood": self.mood.name(), self.handle,
"color": str(self.color.name()), {
"group": str(self.group), "handle": self.handle,
"notes": str(self.notes)}) "mood": self.mood.name(),
"color": str(self.color.name()),
"group": str(self.group),
"notes": str(self.notes),
},
)
def blocked(self, config): def blocked(self, config):
return self.handle in config.getBlocklist() return self.handle in config.getBlocklist()
@ -315,112 +346,248 @@ class PesterProfile(object):
uppersuffix = suffix.upper() uppersuffix = suffix.upper()
if time is not None: if time is not None:
handle = "%s %s" % (time.temporal, self.handle) handle = "%s %s" % (time.temporal, self.handle)
initials = time.pcf+self.initials()+time.number+uppersuffix initials = time.pcf + self.initials() + time.number + uppersuffix
else: else:
handle = self.handle handle = self.handle
initials = self.initials()+uppersuffix initials = self.initials() + uppersuffix
return "<c=%s>-- %s%s <c=%s>[%s]</c> %s --</c>" % (syscolor.name(), handle, suffix, self.colorhtml(), initials, msg) return "<c=%s>-- %s%s <c=%s>[%s]</c> %s --</c>" % (
syscolor.name(),
handle,
suffix,
self.colorhtml(),
initials,
msg,
)
def pestermsg(self, otherchum, syscolor, verb): def pestermsg(self, otherchum, syscolor, verb):
return "<c=%s>-- %s <c=%s>[%s]</c> %s %s <c=%s>[%s]</c> at %s --</c>" % (syscolor.name(), self.handle, self.colorhtml(), self.initials(), verb, otherchum.handle, otherchum.colorhtml(), otherchum.initials(), datetime.now().strftime("%H:%M")) return "<c=%s>-- %s <c=%s>[%s]</c> %s %s <c=%s>[%s]</c> at %s --</c>" % (
syscolor.name(),
self.handle,
self.colorhtml(),
self.initials(),
verb,
otherchum.handle,
otherchum.colorhtml(),
otherchum.initials(),
datetime.now().strftime("%H:%M"),
)
def moodmsg(self, mood, syscolor, theme): def moodmsg(self, mood, syscolor, theme):
return "<c=%s>-- %s <c=%s>[%s]</c> changed their mood to %s <img src='%s' /> --</c>" % (syscolor.name(), self.handle, self.colorhtml(), self.initials(), mood.name().upper(), theme["main/chums/moods"][mood.name()]["icon"]) return (
"<c=%s>-- %s <c=%s>[%s]</c> changed their mood to %s <img src='%s' /> --</c>"
% (
syscolor.name(),
self.handle,
self.colorhtml(),
self.initials(),
mood.name().upper(),
theme["main/chums/moods"][mood.name()]["icon"],
)
)
def idlemsg(self, syscolor, verb): def idlemsg(self, syscolor, verb):
return "<c=%s>-- %s <c=%s>[%s]</c> %s --</c>" % (syscolor.name(), self.handle, self.colorhtml(), self.initials(), verb) return "<c=%s>-- %s <c=%s>[%s]</c> %s --</c>" % (
syscolor.name(),
self.handle,
self.colorhtml(),
self.initials(),
verb,
)
def memoclosemsg(self, syscolor, initials, verb): def memoclosemsg(self, syscolor, initials, verb):
if type(initials) == type(list()): if type(initials) == type(list()):
return "<c=%s><c=%s>%s</c> %s.</c>" % (syscolor.name(), self.colorhtml(), ", ".join(initials), verb) return "<c=%s><c=%s>%s</c> %s.</c>" % (
syscolor.name(),
self.colorhtml(),
", ".join(initials),
verb,
)
else: else:
return "<c=%s><c=%s>%s%s%s</c> %s.</c>" % (syscolor.name(), self.colorhtml(), initials.pcf, self.initials(), initials.number, verb) return "<c=%s><c=%s>%s%s%s</c> %s.</c>" % (
syscolor.name(),
self.colorhtml(),
initials.pcf,
self.initials(),
initials.number,
verb,
)
def memonetsplitmsg(self, syscolor, initials): def memonetsplitmsg(self, syscolor, initials):
if len(initials) <= 0: if len(initials) <= 0:
return "<c=%s>Netsplit quits: <c=black>None</c></c>" % (syscolor.name()) return "<c=%s>Netsplit quits: <c=black>None</c></c>" % (syscolor.name())
else: else:
return "<c=%s>Netsplit quits: <c=black>%s</c></c>" % (syscolor.name(), ", ".join(initials)) return "<c=%s>Netsplit quits: <c=black>%s</c></c>" % (
syscolor.name(),
", ".join(initials),
)
def memoopenmsg(self, syscolor, td, timeGrammar, verb, channel): def memoopenmsg(self, syscolor, td, timeGrammar, verb, channel):
(temporal, pcf, when) = (timeGrammar.temporal, timeGrammar.pcf, timeGrammar.when) (temporal, pcf, when) = (
timeGrammar.temporal,
timeGrammar.pcf,
timeGrammar.when,
)
timetext = timeDifference(td) timetext = timeDifference(td)
PchumLog.debug("pre pcf+self.initials()") PchumLog.debug("pre pcf+self.initials()")
initials = pcf+self.initials() initials = pcf + self.initials()
PchumLog.debug("post pcf+self.initials()") PchumLog.debug("post pcf+self.initials()")
return "<c=%s><c=%s>%s</c> %s %s %s.</c>" % \ return "<c=%s><c=%s>%s</c> %s %s %s.</c>" % (
(syscolor.name(), self.colorhtml(), initials, timetext, verb, channel[1:].upper().replace("_", " ")) syscolor.name(),
self.colorhtml(),
initials,
timetext,
verb,
channel[1:].upper().replace("_", " "),
)
def memobanmsg(self, opchum, opgrammar, syscolor, initials, reason): def memobanmsg(self, opchum, opgrammar, syscolor, initials, reason):
opinit = opgrammar.pcf+opchum.initials()+opgrammar.number opinit = opgrammar.pcf + opchum.initials() + opgrammar.number
if type(initials) == type(list()): if type(initials) == type(list()):
if opchum.handle == reason: if opchum.handle == reason:
return "<c=%s>%s</c> banned <c=%s>%s</c> from responding to memo." % \ return "<c=%s>%s</c> banned <c=%s>%s</c> from responding to memo." % (
(opchum.colorhtml(), opinit, self.colorhtml(), ", ".join(initials)) opchum.colorhtml(),
opinit,
self.colorhtml(),
", ".join(initials),
)
else: else:
return "<c=%s>%s</c> banned <c=%s>%s</c> from responding to memo: <c=black>[%s]</c>." % \ return (
(opchum.colorhtml(), opinit, self.colorhtml(), ", ".join(initials), str(reason)) "<c=%s>%s</c> banned <c=%s>%s</c> from responding to memo: <c=black>[%s]</c>."
% (
opchum.colorhtml(),
opinit,
self.colorhtml(),
", ".join(initials),
str(reason),
)
)
else: else:
# Is timeGrammar defined? Not sure if this works as intented, added try except block to be safe. # Is timeGrammar defined? Not sure if this works as intented, added try except block to be safe.
try: try:
initials = timeGrammar.pcf+self.initials()+timeGrammar.number initials = timeGrammar.pcf + self.initials() + timeGrammar.number
if opchum.handle == reason: if opchum.handle == reason:
return "<c=%s>%s</c> banned <c=%s>%s</c> from responding to memo." % \ return (
(opchum.colorhtml(), opinit, self.colorhtml(), initials) "<c=%s>%s</c> banned <c=%s>%s</c> from responding to memo."
% (opchum.colorhtml(), opinit, self.colorhtml(), initials)
)
else: else:
return "<c=%s>%s</c> banned <c=%s>%s</c> from responding to memo: <c=black>[%s]</c>." % \ return (
(opchum.colorhtml(), opinit, self.colorhtml(), initials, str(reason)) "<c=%s>%s</c> banned <c=%s>%s</c> from responding to memo: <c=black>[%s]</c>."
% (
opchum.colorhtml(),
opinit,
self.colorhtml(),
initials,
str(reason),
)
)
except: except:
PchumLog.exception('') PchumLog.exception("")
initials = self.initials() initials = self.initials()
if opchum.handle == reason: if opchum.handle == reason:
return "<c=%s>%s</c> banned <c=%s>%s</c> from responding to memo." % \ return (
(opchum.colorhtml(), opinit, self.colorhtml(), initials) "<c=%s>%s</c> banned <c=%s>%s</c> from responding to memo."
% (opchum.colorhtml(), opinit, self.colorhtml(), initials)
)
else: else:
return "<c=%s>%s</c> banned <c=%s>%s</c> from responding to memo: <c=black>[%s]</c>." % \ return (
(opchum.colorhtml(), opinit, self.colorhtml(), initials, str(reason)) "<c=%s>%s</c> banned <c=%s>%s</c> from responding to memo: <c=black>[%s]</c>."
% (
opchum.colorhtml(),
opinit,
self.colorhtml(),
initials,
str(reason),
)
)
# As far as I'm aware, there's no IRC reply for this, this seems impossible to check for in practice. # As far as I'm aware, there's no IRC reply for this, this seems impossible to check for in practice.
def memopermabanmsg(self, opchum, opgrammar, syscolor, timeGrammar): def memopermabanmsg(self, opchum, opgrammar, syscolor, timeGrammar):
initials = (timeGrammar.pcf initials = timeGrammar.pcf + self.initials() + timeGrammar.number
+ self.initials() opinit = opgrammar.pcf + opchum.initials() + opgrammar.number
+ timeGrammar.number) return "<c=%s>%s</c> permabanned <c=%s>%s</c> from the memo." % (
opinit = (opgrammar.pcf opchum.colorhtml(),
+ opchum.initials() opinit,
+ opgrammar.number) self.colorhtml(),
return "<c=%s>%s</c> permabanned <c=%s>%s</c> from the memo." % \ initials,
(opchum.colorhtml(), opinit, self.colorhtml(), initials) )
def memojoinmsg(self, syscolor, td, timeGrammar, verb): def memojoinmsg(self, syscolor, td, timeGrammar, verb):
#(temporal, pcf, when) = (timeGrammar.temporal, timeGrammar.pcf, timeGrammar.when) # (temporal, pcf, when) = (timeGrammar.temporal, timeGrammar.pcf, timeGrammar.when)
timetext = timeDifference(td) timetext = timeDifference(td)
initials = timeGrammar.pcf+self.initials()+timeGrammar.number initials = timeGrammar.pcf + self.initials() + timeGrammar.number
return "<c=%s><c=%s>%s %s [%s]</c> %s %s.</c>" % \ return "<c=%s><c=%s>%s %s [%s]</c> %s %s.</c>" % (
(syscolor.name(), self.colorhtml(), timeGrammar.temporal, self.handle, syscolor.name(),
initials, timetext, verb) self.colorhtml(),
timeGrammar.temporal,
self.handle,
initials,
timetext,
verb,
)
def memoopmsg(self, opchum, opgrammar, syscolor): def memoopmsg(self, opchum, opgrammar, syscolor):
opinit = opgrammar.pcf+opchum.initials()+opgrammar.number opinit = opgrammar.pcf + opchum.initials() + opgrammar.number
return "<c=%s>%s</c> made <c=%s>%s</c> an OP." % \ return "<c=%s>%s</c> made <c=%s>%s</c> an OP." % (
(opchum.colorhtml(), opinit, self.colorhtml(), self.initials()) opchum.colorhtml(),
opinit,
self.colorhtml(),
self.initials(),
)
def memodeopmsg(self, opchum, opgrammar, syscolor): def memodeopmsg(self, opchum, opgrammar, syscolor):
opinit = opgrammar.pcf+opchum.initials()+opgrammar.number opinit = opgrammar.pcf + opchum.initials() + opgrammar.number
return "<c=%s>%s</c> took away <c=%s>%s</c>'s OP powers." % \ return "<c=%s>%s</c> took away <c=%s>%s</c>'s OP powers." % (
(opchum.colorhtml(), opinit, self.colorhtml(), self.initials()) opchum.colorhtml(),
opinit,
self.colorhtml(),
self.initials(),
)
def memovoicemsg(self, opchum, opgrammar, syscolor): def memovoicemsg(self, opchum, opgrammar, syscolor):
opinit = opgrammar.pcf+opchum.initials()+opgrammar.number opinit = opgrammar.pcf + opchum.initials() + opgrammar.number
return "<c=%s>%s</c> gave <c=%s>%s</c> voice." % \ return "<c=%s>%s</c> gave <c=%s>%s</c> voice." % (
(opchum.colorhtml(), opinit, self.colorhtml(), self.initials()) opchum.colorhtml(),
opinit,
self.colorhtml(),
self.initials(),
)
def memodevoicemsg(self, opchum, opgrammar, syscolor): def memodevoicemsg(self, opchum, opgrammar, syscolor):
opinit = opgrammar.pcf+opchum.initials()+opgrammar.number opinit = opgrammar.pcf + opchum.initials() + opgrammar.number
return "<c=%s>%s</c> took away <c=%s>%s</c>'s voice." % \ return "<c=%s>%s</c> took away <c=%s>%s</c>'s voice." % (
(opchum.colorhtml(), opinit, self.colorhtml(), self.initials()) opchum.colorhtml(),
opinit,
self.colorhtml(),
self.initials(),
)
def memomodemsg(self, opchum, opgrammar, syscolor, modeverb, modeon): def memomodemsg(self, opchum, opgrammar, syscolor, modeverb, modeon):
opinit = opgrammar.pcf+opchum.initials()+opgrammar.number opinit = opgrammar.pcf + opchum.initials() + opgrammar.number
if modeon: modeon = "now" if modeon:
else: modeon = "no longer" modeon = "now"
return "<c=%s>Memo is %s <c=black>%s</c> by <c=%s>%s</c></c>" % \ else:
(syscolor.name(), modeon, modeverb, opchum.colorhtml(), opinit) modeon = "no longer"
return "<c=%s>Memo is %s <c=black>%s</c> by <c=%s>%s</c></c>" % (
syscolor.name(),
modeon,
modeverb,
opchum.colorhtml(),
opinit,
)
def memoquirkkillmsg(self, opchum, opgrammar, syscolor): def memoquirkkillmsg(self, opchum, opgrammar, syscolor):
opinit = opgrammar.pcf+opchum.initials()+opgrammar.number opinit = opgrammar.pcf + opchum.initials() + opgrammar.number
return "<c=%s><c=%s>%s</c> turned off your quirk.</c>" % \ return "<c=%s><c=%s>%s</c> turned off your quirk.</c>" % (
(syscolor.name(), opchum.colorhtml(), opinit) syscolor.name(),
opchum.colorhtml(),
opinit,
)
@staticmethod @staticmethod
def checkLength(handle): def checkLength(handle):
return len(handle) <= 256 return len(handle) <= 256
@staticmethod @staticmethod
def checkValid(handle): def checkValid(handle):
caps = [l for l in handle if l.isupper()] caps = [l for l in handle if l.isupper()]
@ -432,11 +599,13 @@ class PesterProfile(object):
return (False, "Only alphanumeric characters allowed") return (False, "Only alphanumeric characters allowed")
return (True,) return (True,)
class PesterHistory(object): class PesterHistory(object):
def __init__(self): def __init__(self):
self.history = [] self.history = []
self.current = 0 self.current = 0
self.saved = None self.saved = None
def next(self, text): def next(self, text):
if self.current == 0: if self.current == 0:
return None return None
@ -445,20 +614,25 @@ class PesterHistory(object):
self.current -= 1 self.current -= 1
text = self.history[self.current] text = self.history[self.current]
return text return text
def prev(self): def prev(self):
self.current += 1 self.current += 1
if self.current >= len(self.history): if self.current >= len(self.history):
self.current = len(self.history) self.current = len(self.history)
return self.retrieve() return self.retrieve()
return self.history[self.current] return self.history[self.current]
def reset(self): def reset(self):
self.current = len(self.history) self.current = len(self.history)
self.saved = None self.saved = None
def save(self, text): def save(self, text):
self.saved = text self.saved = text
def retrieve(self): def retrieve(self):
return self.saved return self.saved
def add(self, text): def add(self, text):
if len(self.history) == 0 or text != self.history[len(self.history)-1]: if len(self.history) == 0 or text != self.history[len(self.history) - 1]:
self.history.append(text) self.history.append(text)
self.reset() self.reset()

View file

@ -5,30 +5,40 @@ except ImportError:
from PyQt5 import QtGui, QtWidgets from PyQt5 import QtGui, QtWidgets
from datetime import timedelta from datetime import timedelta
class mysteryTime(timedelta): class mysteryTime(timedelta):
def __sub__(self, other): def __sub__(self, other):
return self return self
def __eq__(self, other): def __eq__(self, other):
return (type(other) is mysteryTime) return type(other) is mysteryTime
def __neq__(self, other): def __neq__(self, other):
return (type(other) is not mysteryTime) return type(other) is not mysteryTime
class CaseInsensitiveDict(dict): class CaseInsensitiveDict(dict):
def __setitem__(self, key, value): def __setitem__(self, key, value):
super(CaseInsensitiveDict, self).__setitem__(key.lower(), value) super(CaseInsensitiveDict, self).__setitem__(key.lower(), value)
def __getitem__(self, key): def __getitem__(self, key):
return super(CaseInsensitiveDict, self).__getitem__(key.lower()) return super(CaseInsensitiveDict, self).__getitem__(key.lower())
def __contains__(self, key): def __contains__(self, key):
return super(CaseInsensitiveDict, self).__contains__(key.lower()) return super(CaseInsensitiveDict, self).__contains__(key.lower())
def has_key(self, key): def has_key(self, key):
return key.lower() in super(CaseInsensitiveDict, self) return key.lower() in super(CaseInsensitiveDict, self)
def __delitem__(self, key): def __delitem__(self, key):
super(CaseInsensitiveDict, self).__delitem__(key.lower()) super(CaseInsensitiveDict, self).__delitem__(key.lower())
class PesterList(list): class PesterList(list):
def __init__(self, l): def __init__(self, l):
self.extend(l) self.extend(l)
class PesterIcon(QtGui.QIcon): class PesterIcon(QtGui.QIcon):
def __init__(self, *x): def __init__(self, *x):
super(PesterIcon, self).__init__(x[0]) super(PesterIcon, self).__init__(x[0])
@ -36,6 +46,7 @@ class PesterIcon(QtGui.QIcon):
self.icon_pixmap = QtGui.QPixmap(x[0]) self.icon_pixmap = QtGui.QPixmap(x[0])
else: else:
self.icon_pixmap = None self.icon_pixmap = None
def realsize(self): def realsize(self):
if self.icon_pixmap: if self.icon_pixmap:
return self.icon_pixmap.size() return self.icon_pixmap.size()
@ -45,18 +56,21 @@ class PesterIcon(QtGui.QIcon):
except IndexError: except IndexError:
return None return None
class RightClickList(QtWidgets.QListWidget): class RightClickList(QtWidgets.QListWidget):
def contextMenuEvent(self, event): def contextMenuEvent(self, event):
#fuckin Qt <--- I feel that </3 # fuckin Qt <--- I feel that </3
if event.reason() == QtGui.QContextMenuEvent.Reason.Mouse: if event.reason() == QtGui.QContextMenuEvent.Reason.Mouse:
listing = self.itemAt(event.pos()) listing = self.itemAt(event.pos())
self.setCurrentItem(listing) self.setCurrentItem(listing)
optionsMenu = self.getOptionsMenu() optionsMenu = self.getOptionsMenu()
if optionsMenu: if optionsMenu:
optionsMenu.popup(event.globalPos()) optionsMenu.popup(event.globalPos())
def getOptionsMenu(self): def getOptionsMenu(self):
return self.optionsMenu return self.optionsMenu
class RightClickTree(QtWidgets.QTreeWidget): class RightClickTree(QtWidgets.QTreeWidget):
def contextMenuEvent(self, event): def contextMenuEvent(self, event):
if event.reason() == QtGui.QContextMenuEvent.Reason.Mouse: if event.reason() == QtGui.QContextMenuEvent.Reason.Mouse:
@ -65,9 +79,11 @@ class RightClickTree(QtWidgets.QTreeWidget):
optionsMenu = self.getOptionsMenu() optionsMenu = self.getOptionsMenu()
if optionsMenu: if optionsMenu:
optionsMenu.popup(event.globalPos()) optionsMenu.popup(event.globalPos())
def getOptionsMenu(self): def getOptionsMenu(self):
return self.optionsMenu return self.optionsMenu
class MultiTextDialog(QtWidgets.QDialog): class MultiTextDialog(QtWidgets.QDialog):
def __init__(self, title, parent, *queries): def __init__(self, title, parent, *queries):
super(MultiTextDialog, self).__init__(parent) super(MultiTextDialog, self).__init__(parent)
@ -98,6 +114,7 @@ class MultiTextDialog(QtWidgets.QDialog):
layout_0.addLayout(layout_ok) layout_0.addLayout(layout_ok)
self.setLayout(layout_0) self.setLayout(layout_0)
def getText(self): def getText(self):
r = self.exec() r = self.exec()
if r == QtWidgets.QDialog.DialogCode.Accepted: if r == QtWidgets.QDialog.DialogCode.Accepted:
@ -108,6 +125,7 @@ class MultiTextDialog(QtWidgets.QDialog):
else: else:
return None return None
class MovingWindow(QtWidgets.QFrame): class MovingWindow(QtWidgets.QFrame):
# Qt supports starting a system-specific move operation since 5.15, so we shouldn't need to manually set position like this anymore. # Qt supports starting a system-specific move operation since 5.15, so we shouldn't need to manually set position like this anymore.
# https://doc.qt.io/qt-5/qwindow.html#startSystemMove # https://doc.qt.io/qt-5/qwindow.html#startSystemMove
@ -116,6 +134,7 @@ class MovingWindow(QtWidgets.QFrame):
super(MovingWindow, self).__init__(*x, **y) super(MovingWindow, self).__init__(*x, **y)
self.moving = None self.moving = None
self.moveupdate = 0 self.moveupdate = 0
def mouseMoveEvent(self, event): def mouseMoveEvent(self, event):
if self.moving: if self.moving:
move = event.globalPos() - self.moving move = event.globalPos() - self.moving
@ -124,6 +143,7 @@ class MovingWindow(QtWidgets.QFrame):
if self.moveupdate > 5: if self.moveupdate > 5:
self.moveupdate = 0 self.moveupdate = 0
self.update() self.update()
def mousePressEvent(self, event): def mousePressEvent(self, event):
# Assuming everything is supported, we only need this function to call "self.windowHandle().startSystemMove()". # Assuming everything is supported, we only need this function to call "self.windowHandle().startSystemMove()".
# If not supported, startSystemMove() returns False and the legacy code runs anyway. # If not supported, startSystemMove() returns False and the legacy code runs anyway.
@ -135,18 +155,27 @@ class MovingWindow(QtWidgets.QFrame):
print("PyQt <= 5.14?") print("PyQt <= 5.14?")
print(str(e)) print(str(e))
if event.button() == 1: if event.button() == 1:
self.moving = event.globalPos() - self.pos() self.moving = event.globalPos() - self.pos()
def mouseReleaseEvent(self, event): def mouseReleaseEvent(self, event):
if event.button() == 1: if event.button() == 1:
self.update() self.update()
self.moving = None self.moving = None
class NoneSound(object): class NoneSound(object):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
pass pass
def play(self): pass
def setVolume(self, v): pass def play(self):
def set_volume(self, v): pass pass
def setVolume(self, v):
pass
def set_volume(self, v):
pass
class WMButton(QtWidgets.QPushButton): class WMButton(QtWidgets.QPushButton):
def __init__(self, icon, parent=None): def __init__(self, icon, parent=None):

479
irc.py

File diff suppressed because it is too large Load diff

View file

@ -4,6 +4,7 @@ import codecs
import re import re
import ostools import ostools
from time import strftime, strptime from time import strftime, strptime
try: try:
from PyQt6 import QtCore, QtGui, QtWidgets from PyQt6 import QtCore, QtGui, QtWidgets
except ImportError: except ImportError:
@ -15,13 +16,15 @@ from convo import PesterText
_datadir = ostools.getDataDir() _datadir = ostools.getDataDir()
class PesterLogSearchInput(QtWidgets.QLineEdit): class PesterLogSearchInput(QtWidgets.QLineEdit):
def __init__(self, theme, parent=None): def __init__(self, theme, parent=None):
QtWidgets.QLineEdit.__init__(self, parent) QtWidgets.QLineEdit.__init__(self, parent)
self.setStyleSheet(theme["convo/input/style"] + "; margin-right:0px;") self.setStyleSheet(theme["convo/input/style"] + "; margin-right:0px;")
def keyPressEvent(self, event): def keyPressEvent(self, event):
QtWidgets.QLineEdit.keyPressEvent(self, event) QtWidgets.QLineEdit.keyPressEvent(self, event)
if hasattr(self.parent(), 'textArea'): if hasattr(self.parent(), "textArea"):
if event.key() == QtCore.Qt.Key.Key_Return: if event.key() == QtCore.Qt.Key.Key_Return:
self.parent().logSearch(self.text()) self.parent().logSearch(self.text())
if self.parent().textArea.find(self.text()): if self.parent().textArea.find(self.text()):
@ -29,6 +32,7 @@ class PesterLogSearchInput(QtWidgets.QLineEdit):
else: else:
self.parent().logSearch(self.text()) self.parent().logSearch(self.text())
class PesterLogHighlighter(QtGui.QSyntaxHighlighter): class PesterLogHighlighter(QtGui.QSyntaxHighlighter):
def __init__(self, parent): def __init__(self, parent):
QtGui.QSyntaxHighlighter.__init__(self, parent) QtGui.QSyntaxHighlighter.__init__(self, parent)
@ -36,11 +40,16 @@ class PesterLogHighlighter(QtGui.QSyntaxHighlighter):
self.hilightstyle = QtGui.QTextCharFormat() self.hilightstyle = QtGui.QTextCharFormat()
self.hilightstyle.setBackground(QtGui.QBrush(QtCore.Qt.GlobalColor.green)) self.hilightstyle.setBackground(QtGui.QBrush(QtCore.Qt.GlobalColor.green))
self.hilightstyle.setForeground(QtGui.QBrush(QtCore.Qt.GlobalColor.black)) self.hilightstyle.setForeground(QtGui.QBrush(QtCore.Qt.GlobalColor.black))
def highlightBlock(self, text): def highlightBlock(self, text):
for i in range(0, len(text)-(len(self.searchTerm)-1)): for i in range(0, len(text) - (len(self.searchTerm) - 1)):
if str(text[i:i+len(self.searchTerm)]).lower() == str(self.searchTerm).lower(): if (
str(text[i : i + len(self.searchTerm)]).lower()
== str(self.searchTerm).lower()
):
self.setFormat(i, len(self.searchTerm), self.hilightstyle) self.setFormat(i, len(self.searchTerm), self.hilightstyle)
class PesterLogUserSelect(QtWidgets.QDialog): class PesterLogUserSelect(QtWidgets.QDialog):
def __init__(self, config, theme, parent): def __init__(self, config, theme, parent):
QtWidgets.QDialog.__init__(self, parent) QtWidgets.QDialog.__init__(self, parent)
@ -49,7 +58,7 @@ class PesterLogUserSelect(QtWidgets.QDialog):
self.theme = theme self.theme = theme
self.parent = parent self.parent = parent
self.handle = parent.profile().handle self.handle = parent.profile().handle
self.logpath = _datadir+"logs" self.logpath = _datadir + "logs"
self.setStyleSheet(self.theme["main/defaultwindow/style"]) self.setStyleSheet(self.theme["main/defaultwindow/style"])
self.setWindowTitle("Pesterlogs") self.setWindowTitle("Pesterlogs")
@ -72,7 +81,9 @@ class PesterLogUserSelect(QtWidgets.QDialog):
for (i, t) in enumerate(chumMemoList): for (i, t) in enumerate(chumMemoList):
item = QtWidgets.QListWidgetItem(t) item = QtWidgets.QListWidgetItem(t)
item.setForeground(QtGui.QBrush(QtGui.QColor(self.theme["main/chums/userlistcolor"]))) item.setForeground(
QtGui.QBrush(QtGui.QColor(self.theme["main/chums/userlistcolor"]))
)
self.chumsBox.addItem(item) self.chumsBox.addItem(item)
self.search = PesterLogSearchInput(theme, self) self.search = PesterLogSearchInput(theme, self)
@ -109,10 +120,12 @@ class PesterLogUserSelect(QtWidgets.QDialog):
@QtCore.pyqtSlot() @QtCore.pyqtSlot()
def viewActivatedLog(self): def viewActivatedLog(self):
selectedchum = self.selectedchum().text() selectedchum = self.selectedchum().text()
if not hasattr(self, 'pesterlogviewer'): if not hasattr(self, "pesterlogviewer"):
self.pesterlogviewer = None self.pesterlogviewer = None
if not self.pesterlogviewer: if not self.pesterlogviewer:
self.pesterlogviewer = PesterLogViewer(selectedchum, self.config, self.theme, self.parent) self.pesterlogviewer = PesterLogViewer(
selectedchum, self.config, self.theme, self.parent
)
self.pesterlogviewer.rejected.connect(self.closeActiveLog) self.pesterlogviewer.rejected.connect(self.closeActiveLog)
self.pesterlogviewer.show() self.pesterlogviewer.show()
self.pesterlogviewer.raise_() self.pesterlogviewer.raise_()
@ -126,7 +139,13 @@ class PesterLogUserSelect(QtWidgets.QDialog):
@QtCore.pyqtSlot() @QtCore.pyqtSlot()
def openDir(self): def openDir(self):
QtGui.QDesktopServices.openUrl(QtCore.QUrl("file:///" + os.path.join(_datadir, "logs"), QtCore.QUrl.ParsingMode.TolerantMode)) QtGui.QDesktopServices.openUrl(
QtCore.QUrl(
"file:///" + os.path.join(_datadir, "logs"),
QtCore.QUrl.ParsingMode.TolerantMode,
)
)
class PesterLogViewer(QtWidgets.QDialog): class PesterLogViewer(QtWidgets.QDialog):
def __init__(self, chum, config, theme, parent): def __init__(self, chum, config, theme, parent):
@ -140,18 +159,27 @@ class PesterLogViewer(QtWidgets.QDialog):
self.handle = parent.profile().handle self.handle = parent.profile().handle
self.chum = chum self.chum = chum
self.convos = {} self.convos = {}
self.logpath = _datadir+"logs" self.logpath = _datadir + "logs"
self.setStyleSheet(self.theme["main/defaultwindow/style"]) self.setStyleSheet(self.theme["main/defaultwindow/style"])
self.setWindowTitle("Pesterlogs with " + self.chum) self.setWindowTitle("Pesterlogs with " + self.chum)
self.format = "bbcode" self.format = "bbcode"
if os.path.exists("%s/%s/%s/%s" % (self.logpath, self.handle, chum, self.format)): if os.path.exists(
self.logList = os.listdir("%s/%s/%s/%s/" % (self.logpath, self.handle, self.chum, self.format)) "%s/%s/%s/%s" % (self.logpath, self.handle, chum, self.format)
):
self.logList = os.listdir(
"%s/%s/%s/%s/" % (self.logpath, self.handle, self.chum, self.format)
)
else: else:
self.logList = [] self.logList = []
if not os.path.exists("%s/%s/%s/%s" % (self.logpath, self.handle, chum, self.format)) or len(self.logList) == 0: if (
not os.path.exists(
"%s/%s/%s/%s" % (self.logpath, self.handle, chum, self.format)
)
or len(self.logList) == 0
):
instructions = QtWidgets.QLabel("No Pesterlogs were found") instructions = QtWidgets.QLabel("No Pesterlogs were found")
self.ok = QtWidgets.QPushButton("CLOSE", self) self.ok = QtWidgets.QPushButton("CLOSE", self)
@ -166,15 +194,28 @@ class PesterLogViewer(QtWidgets.QDialog):
self.setLayout(layout_0) self.setLayout(layout_0)
else: else:
self.instructions = QtWidgets.QLabel("Pesterlog with " +self.chum+ " on") self.instructions = QtWidgets.QLabel("Pesterlog with " + self.chum + " on")
self.textArea = PesterLogText(theme, self.parent) self.textArea = PesterLogText(theme, self.parent)
self.textArea.setReadOnly(True) self.textArea.setReadOnly(True)
self.textArea.setFixedWidth(600) self.textArea.setFixedWidth(600)
if "convo/scrollbar" in theme: if "convo/scrollbar" in theme:
self.textArea.setStyleSheet("QTextEdit { width:500px; %s } QScrollBar:vertical { %s } QScrollBar::handle:vertical { %s } QScrollBar::add-line:vertical { %s } QScrollBar::sub-line:vertical { %s } QScrollBar:up-arrow:vertical { %s } QScrollBar:down-arrow:vertical { %s }" % (theme["convo/textarea/style"], theme["convo/scrollbar/style"], theme["convo/scrollbar/handle"], theme["convo/scrollbar/downarrow"], theme["convo/scrollbar/uparrow"], theme["convo/scrollbar/uarrowstyle"], theme["convo/scrollbar/darrowstyle"] )) self.textArea.setStyleSheet(
"QTextEdit { width:500px; %s } QScrollBar:vertical { %s } QScrollBar::handle:vertical { %s } QScrollBar::add-line:vertical { %s } QScrollBar::sub-line:vertical { %s } QScrollBar:up-arrow:vertical { %s } QScrollBar:down-arrow:vertical { %s }"
% (
theme["convo/textarea/style"],
theme["convo/scrollbar/style"],
theme["convo/scrollbar/handle"],
theme["convo/scrollbar/downarrow"],
theme["convo/scrollbar/uparrow"],
theme["convo/scrollbar/uarrowstyle"],
theme["convo/scrollbar/darrowstyle"],
)
)
else: else:
self.textArea.setStyleSheet("QTextEdit { width:500px; %s }" % (theme["convo/textarea/style"])) self.textArea.setStyleSheet(
"QTextEdit { width:500px; %s }" % (theme["convo/textarea/style"])
)
self.logList.sort() self.logList.sort()
self.logList.reverse() self.logList.reverse()
@ -184,20 +225,31 @@ class PesterLogViewer(QtWidgets.QDialog):
self.tree.setFixedSize(260, 300) self.tree.setFixedSize(260, 300)
self.tree.header().hide() self.tree.header().hide()
if "convo/scrollbar" in theme: if "convo/scrollbar" in theme:
self.tree.setStyleSheet("QTreeWidget { %s } QScrollBar:vertical { %s } QScrollBar::handle:vertical { %s } QScrollBar::add-line:vertical { %s } QScrollBar::sub-line:vertical { %s } QScrollBar:up-arrow:vertical { %s } QScrollBar:down-arrow:vertical { %s }" % (theme["convo/textarea/style"], theme["convo/scrollbar/style"], theme["convo/scrollbar/handle"], theme["convo/scrollbar/downarrow"], theme["convo/scrollbar/uparrow"], theme["convo/scrollbar/uarrowstyle"], theme["convo/scrollbar/darrowstyle"] )) self.tree.setStyleSheet(
"QTreeWidget { %s } QScrollBar:vertical { %s } QScrollBar::handle:vertical { %s } QScrollBar::add-line:vertical { %s } QScrollBar::sub-line:vertical { %s } QScrollBar:up-arrow:vertical { %s } QScrollBar:down-arrow:vertical { %s }"
% (
theme["convo/textarea/style"],
theme["convo/scrollbar/style"],
theme["convo/scrollbar/handle"],
theme["convo/scrollbar/downarrow"],
theme["convo/scrollbar/uparrow"],
theme["convo/scrollbar/uarrowstyle"],
theme["convo/scrollbar/darrowstyle"],
)
)
else: else:
self.tree.setStyleSheet("%s" % (theme["convo/textarea/style"])) self.tree.setStyleSheet("%s" % (theme["convo/textarea/style"]))
self.tree.itemSelectionChanged.connect(self.loadSelectedLog) self.tree.itemSelectionChanged.connect(self.loadSelectedLog)
self.tree.setSortingEnabled(False) self.tree.setSortingEnabled(False)
child_1 = None child_1 = None
last = ["",""] last = ["", ""]
#blackbrush = QtGui.QBrush(QtCore.Qt.GlobalColor.black) # blackbrush = QtGui.QBrush(QtCore.Qt.GlobalColor.black)
for (i,l) in enumerate(self.logList): for (i, l) in enumerate(self.logList):
my = self.fileToMonthYear(l) my = self.fileToMonthYear(l)
if my[0] != last[0]: if my[0] != last[0]:
child_1 = QtWidgets.QTreeWidgetItem(["%s %s" % (my[0], my[1])]) child_1 = QtWidgets.QTreeWidgetItem(["%s %s" % (my[0], my[1])])
#child_1.setForeground(0, blackbrush) # child_1.setForeground(0, blackbrush)
self.tree.addTopLevelItem(child_1) self.tree.addTopLevelItem(child_1)
if i == 0: if i == 0:
child_1.setExpanded(True) child_1.setExpanded(True)
@ -205,7 +257,8 @@ class PesterLogViewer(QtWidgets.QDialog):
last = self.fileToMonthYear(l) last = self.fileToMonthYear(l)
self.hilight = PesterLogHighlighter(self.textArea) self.hilight = PesterLogHighlighter(self.textArea)
if len(self.logList) > 0: self.loadLog(self.logList[0]) if len(self.logList) > 0:
self.loadLog(self.logList[0])
self.search = PesterLogSearchInput(theme, self) self.search = PesterLogSearchInput(theme, self)
self.search.setFocus() self.search.setFocus()
@ -246,29 +299,48 @@ class PesterLogViewer(QtWidgets.QDialog):
self.loadLog(self.timeToFile(self.tree.currentItem().text(0))) self.loadLog(self.timeToFile(self.tree.currentItem().text(0)))
def loadLog(self, fname): def loadLog(self, fname):
fp = codecs.open("%s/%s/%s/%s/%s" % (self.logpath, self.handle, self.chum, self.format, fname), encoding='utf-8', mode='r') fp = codecs.open(
"%s/%s/%s/%s/%s"
% (self.logpath, self.handle, self.chum, self.format, fname),
encoding="utf-8",
mode="r",
)
self.textArea.clear() self.textArea.clear()
for line in fp: for line in fp:
cline = line.replace("\r\n", "").replace("[/color]","</c>").replace("[url]","").replace("[/url]","") cline = (
line.replace("\r\n", "")
.replace("[/color]", "</c>")
.replace("[url]", "")
.replace("[/url]", "")
)
cline = re.sub("\[color=(#.{6})]", r"<c=\1>", cline) cline = re.sub("\[color=(#.{6})]", r"<c=\1>", cline)
self.textArea.append(convertTags(cline)) self.textArea.append(convertTags(cline))
textCur = self.textArea.textCursor() textCur = self.textArea.textCursor()
#textCur.movePosition(1) # textCur.movePosition(1)
self.textArea.setTextCursor(textCur) self.textArea.setTextCursor(textCur)
self.instructions.setText("Pesterlog with " +self.chum+ " on " + self.fileToTime(str(fname))) self.instructions.setText(
"Pesterlog with " + self.chum + " on " + self.fileToTime(str(fname))
)
def logSearch(self, search): def logSearch(self, search):
self.hilight.searchTerm = search self.hilight.searchTerm = search
self.hilight.rehighlight() self.hilight.rehighlight()
def fileToMonthYear(self, fname): def fileToMonthYear(self, fname):
time = strptime(fname[(fname.index(".")+1):fname.index(".txt")], "%Y-%m-%d.%H.%M") time = strptime(
fname[(fname.index(".") + 1) : fname.index(".txt")], "%Y-%m-%d.%H.%M"
)
return [strftime("%B", time), strftime("%Y", time)] return [strftime("%B", time), strftime("%Y", time)]
def fileToTime(self, fname): def fileToTime(self, fname):
timestr = fname[(fname.index(".")+1):fname.index(".txt")] timestr = fname[(fname.index(".") + 1) : fname.index(".txt")]
return strftime("%a %d %b %Y %H %M", strptime(timestr, "%Y-%m-%d.%H.%M")) return strftime("%a %d %b %Y %H %M", strptime(timestr, "%Y-%m-%d.%H.%M"))
def timeToFile(self, time): def timeToFile(self, time):
return self.chum + strftime(".%Y-%m-%d.%H.%M.txt", strptime(str(time), "%a %d %b %Y %H %M")) return self.chum + strftime(
".%Y-%m-%d.%H.%M.txt", strptime(str(time), "%a %d %b %Y %H %M")
)
class PesterLogText(PesterText): class PesterLogText(PesterText):
def __init__(self, theme, parent=None): def __init__(self, theme, parent=None):
@ -276,6 +348,7 @@ class PesterLogText(PesterText):
def focusInEvent(self, event): def focusInEvent(self, event):
QtWidgets.QTextEdit.focusInEvent(self, event) QtWidgets.QTextEdit.focusInEvent(self, event)
def mousePressEvent(self, event): def mousePressEvent(self, event):
try: try:
# PyQt6 # PyQt6
@ -290,8 +363,11 @@ class PesterLogText(PesterText):
handle = str(url[1:]) handle = str(url[1:])
self.parent().parent.newConversation(handle) self.parent().parent.newConversation(handle)
else: else:
QtGui.QDesktopServices.openUrl(QtCore.QUrl(url, QtCore.QUrl.ParsingMode.TolerantMode)) QtGui.QDesktopServices.openUrl(
QtCore.QUrl(url, QtCore.QUrl.ParsingMode.TolerantMode)
)
QtWidgets.QTextEdit.mousePressEvent(self, event) QtWidgets.QTextEdit.mousePressEvent(self, event)
def mouseMoveEvent(self, event): def mouseMoveEvent(self, event):
QtWidgets.QTextEdit.mouseMoveEvent(self, event) QtWidgets.QTextEdit.mouseMoveEvent(self, event)
try: try:
@ -301,8 +377,13 @@ class PesterLogText(PesterText):
# PyQt5 # PyQt5
pos = event.pos() pos = event.pos()
if self.anchorAt(pos): if self.anchorAt(pos):
if self.viewport().cursor().shape != QtCore.Qt.CursorShape.PointingHandCursor: if (
self.viewport().setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.PointingHandCursor)) self.viewport().cursor().shape
!= QtCore.Qt.CursorShape.PointingHandCursor
):
self.viewport().setCursor(
QtGui.QCursor(QtCore.Qt.CursorShape.PointingHandCursor)
)
else: else:
self.viewport().setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.IBeamCursor)) self.viewport().setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.IBeamCursor))

1156
memos.py

File diff suppressed because it is too large Load diff

691
menus.py

File diff suppressed because it is too large Load diff

View file

@ -1,29 +1,55 @@
import random import random
kbloc = [[x for x in "1234567890-="], kbloc = [
[x for x in "qwertyuiop[]"], [x for x in "1234567890-="],
[x for x in "asdfghjkl:;'"], [x for x in "qwertyuiop[]"],
[x for x in "zxcvbnm,.>/?"]] [x for x in "asdfghjkl:;'"],
[x for x in "zxcvbnm,.>/?"],
]
kbdict = {} kbdict = {}
for (i, l) in enumerate(kbloc): for (i, l) in enumerate(kbloc):
for (j, k) in enumerate(l): for (j, k) in enumerate(l):
kbdict[k] = (i, j) kbdict[k] = (i, j)
sounddict = {"a": "e", "b": "d", "c": "k", "d": "g", "e": "eh", sounddict = {
"f": "ph", "g": "j", "h": "h", "i": "ai", "j": "ge", "a": "e",
"k": "c", "l": "ll", "m": "n", "n": "m", "o": "oa", "b": "d",
"p": "b", "q": "kw", "r": "ar", "s": "ss", "t": "d", "c": "k",
"u": "you", "v": "w", "w": "wn", "x": "cks", "y": "uy", "z": "s"} "d": "g",
"e": "eh",
"f": "ph",
"g": "j",
"h": "h",
"i": "ai",
"j": "ge",
"k": "c",
"l": "ll",
"m": "n",
"n": "m",
"o": "oa",
"p": "b",
"q": "kw",
"r": "ar",
"s": "ss",
"t": "d",
"u": "you",
"v": "w",
"w": "wn",
"x": "cks",
"y": "uy",
"z": "s",
}
def mispeller(word): def mispeller(word):
if len(word) <= 6: if len(word) <= 6:
num = 1 num = 1
else: else:
num = random.choice([1,2]) num = random.choice([1, 2])
wordseq = list(range(0, len(word))) wordseq = list(range(0, len(word)))
random.shuffle(wordseq) random.shuffle(wordseq)
letters = wordseq[0:num] letters = wordseq[0:num]
def mistype(string, i): def mistype(string, i):
l = string[i] l = string[i]
if l not in kbdict: if l not in kbdict:
@ -31,30 +57,42 @@ def mispeller(word):
lpos = kbdict[l] lpos = kbdict[l]
newpos = lpos newpos = lpos
while newpos == lpos: while newpos == lpos:
newpos = ((lpos[0] + random.choice([-1, 0, 1])) % len(kbloc), newpos = (
(lpos[1] + random.choice([-1,0,1])) % len(kbloc[0])) (lpos[0] + random.choice([-1, 0, 1])) % len(kbloc),
string = string[0:i]+kbloc[newpos[0]][newpos[1]]+string[i+1:] (lpos[1] + random.choice([-1, 0, 1])) % len(kbloc[0]),
)
string = string[0:i] + kbloc[newpos[0]][newpos[1]] + string[i + 1 :]
return string return string
def transpose(string, i): def transpose(string, i):
j = (i + random.choice([-1,1])) % len(string) j = (i + random.choice([-1, 1])) % len(string)
l = [c for c in string] l = [c for c in string]
l[i], l[j] = l[j], l[i] l[i], l[j] = l[j], l[i]
return "".join(l) return "".join(l)
def randomletter(string, i): def randomletter(string, i):
string = string[0:i+1]+random.choice("abcdefghijklmnopqrstuvwxyz")+string[i+1:] string = (
string[0 : i + 1]
+ random.choice("abcdefghijklmnopqrstuvwxyz")
+ string[i + 1 :]
)
return string return string
def randomreplace(string, i): def randomreplace(string, i):
string = string[0:i]+random.choice("abcdefghijklmnopqrstuvwxyz")+string[i+1:] string = (
string[0:i] + random.choice("abcdefghijklmnopqrstuvwxyz") + string[i + 1 :]
)
return string return string
def soundalike(string, i): def soundalike(string, i):
try: try:
c = sounddict[string[i]] c = sounddict[string[i]]
except: except:
return string return string
string = string[0:i]+c+string[i+1:] string = string[0:i] + c + string[i + 1 :]
return string return string
func = random.choice([mistype, transpose, randomletter, randomreplace,
soundalike]) func = random.choice([mistype, transpose, randomletter, randomreplace, soundalike])
for i in letters: for i in letters:
word = func(word, i) word = func(word, i)
return word return word

74
mood.py
View file

@ -6,28 +6,76 @@ except ImportError:
from generic import PesterIcon from generic import PesterIcon
class Mood(object): class Mood(object):
moods = ["chummy", "rancorous", "offline", "pleasant", "distraught", moods = [
"pranky", "smooth", "ecstatic", "relaxed", "discontent", "chummy",
"devious", "sleek", "detestful", "mirthful", "manipulative", "rancorous",
"vigorous", "perky", "acceptant", "protective", "mystified", "offline",
"amazed", "insolent", "bemused" ] "pleasant",
"distraught",
"pranky",
"smooth",
"ecstatic",
"relaxed",
"discontent",
"devious",
"sleek",
"detestful",
"mirthful",
"manipulative",
"vigorous",
"perky",
"acceptant",
"protective",
"mystified",
"amazed",
"insolent",
"bemused",
]
moodcats = ["chums", "trolls", "other"] moodcats = ["chums", "trolls", "other"]
revmoodcats = {'discontent': 'trolls', 'insolent': 'chums', 'rancorous': 'chums', 'sleek': 'trolls', 'bemused': 'chums', 'mystified': 'chums', 'pranky': 'chums', 'distraught': 'chums', 'offline': 'chums', 'chummy': 'chums', 'protective': 'other', 'vigorous': 'trolls', 'ecstatic': 'trolls', 'relaxed': 'trolls', 'pleasant': 'chums', 'manipulative': 'trolls', 'detestful': 'trolls', 'smooth': 'chums', 'mirthful': 'trolls', 'acceptant': 'trolls', 'perky': 'trolls', 'devious': 'trolls', 'amazed': 'chums'} revmoodcats = {
"discontent": "trolls",
"insolent": "chums",
"rancorous": "chums",
"sleek": "trolls",
"bemused": "chums",
"mystified": "chums",
"pranky": "chums",
"distraught": "chums",
"offline": "chums",
"chummy": "chums",
"protective": "other",
"vigorous": "trolls",
"ecstatic": "trolls",
"relaxed": "trolls",
"pleasant": "chums",
"manipulative": "trolls",
"detestful": "trolls",
"smooth": "chums",
"mirthful": "trolls",
"acceptant": "trolls",
"perky": "trolls",
"devious": "trolls",
"amazed": "chums",
}
def __init__(self, mood): def __init__(self, mood):
if type(mood) is int: if type(mood) is int:
self.mood = mood self.mood = mood
else: else:
self.mood = self.moods.index(mood) self.mood = self.moods.index(mood)
def value(self): def value(self):
return self.mood return self.mood
def name(self): def name(self):
try: try:
name = self.moods[self.mood] name = self.moods[self.mood]
except IndexError: except IndexError:
name = "chummy" name = "chummy"
return name return name
def icon(self, theme): def icon(self, theme):
try: try:
f = theme["main/chums/moods"][self.name()]["icon"] f = theme["main/chums/moods"][self.name()]["icon"]
@ -35,15 +83,18 @@ class Mood(object):
return PesterIcon(theme["main/chums/moods/chummy/icon"]) return PesterIcon(theme["main/chums/moods/chummy/icon"])
return PesterIcon(f) return PesterIcon(f)
class PesterMoodAction(QtCore.QObject): class PesterMoodAction(QtCore.QObject):
def __init__(self, m, func): def __init__(self, m, func):
QtCore.QObject.__init__(self) QtCore.QObject.__init__(self)
self.mood = m self.mood = m
self.func = func self.func = func
@QtCore.pyqtSlot() @QtCore.pyqtSlot()
def updateMood(self): def updateMood(self):
self.func(self.mood) self.func(self.mood)
class PesterMoodHandler(QtCore.QObject): class PesterMoodHandler(QtCore.QObject):
def __init__(self, parent, *buttons): def __init__(self, parent, *buttons):
QtCore.QObject.__init__(self) QtCore.QObject.__init__(self)
@ -55,13 +106,16 @@ class PesterMoodHandler(QtCore.QObject):
b.setSelected(True) b.setSelected(True)
b.clicked.connect(b.updateMood) b.clicked.connect(b.updateMood)
b.moodUpdated[int].connect(self.updateMood) b.moodUpdated[int].connect(self.updateMood)
def removeButtons(self): def removeButtons(self):
for b in list(self.buttons.values()): for b in list(self.buttons.values()):
b.close() b.close()
def showButtons(self): def showButtons(self):
for b in list(self.buttons.values()): for b in list(self.buttons.values()):
b.show() b.show()
b.raise_() b.raise_()
@QtCore.pyqtSlot(int) @QtCore.pyqtSlot(int)
def updateMood(self, m): def updateMood(self, m):
# update MY mood # update MY mood
@ -81,12 +135,15 @@ class PesterMoodHandler(QtCore.QObject):
self.mainwindow.userprofile.setLastMood(newmood) self.mainwindow.userprofile.setLastMood(newmood)
if self.mainwindow.currentMoodIcon: if self.mainwindow.currentMoodIcon:
moodicon = newmood.icon(self.mainwindow.theme) moodicon = newmood.icon(self.mainwindow.theme)
self.mainwindow.currentMoodIcon.setPixmap(moodicon.pixmap(moodicon.realsize())) self.mainwindow.currentMoodIcon.setPixmap(
moodicon.pixmap(moodicon.realsize())
)
if oldmood.name() != newmood.name(): if oldmood.name() != newmood.name():
for c in list(self.mainwindow.convos.values()): for c in list(self.mainwindow.convos.values()):
c.myUpdateMood(newmood) c.myUpdateMood(newmood)
self.mainwindow.moodUpdated.emit() self.mainwindow.moodUpdated.emit()
class PesterMoodButton(QtWidgets.QPushButton): class PesterMoodButton(QtWidgets.QPushButton):
def __init__(self, parent, **options): def __init__(self, parent, **options):
icon = PesterIcon(options["icon"]) icon = PesterIcon(options["icon"])
@ -100,13 +157,16 @@ class PesterMoodButton(QtWidgets.QPushButton):
self.setStyleSheet(self.unselectedSheet) self.setStyleSheet(self.unselectedSheet)
self.mainwindow = parent self.mainwindow = parent
self.mood = Mood(options["mood"]) self.mood = Mood(options["mood"])
def setSelected(self, selected): def setSelected(self, selected):
if selected: if selected:
self.setStyleSheet(self.selectedSheet) self.setStyleSheet(self.selectedSheet)
else: else:
self.setStyleSheet(self.unselectedSheet) self.setStyleSheet(self.unselectedSheet)
@QtCore.pyqtSlot() @QtCore.pyqtSlot()
def updateMood(self): def updateMood(self):
# updates OUR mood # updates OUR mood
self.moodUpdated.emit(self.mood.value()) self.moodUpdated.emit(self.mood.value())
moodUpdated = QtCore.pyqtSignal(int) moodUpdated = QtCore.pyqtSignal(int)

View file

@ -1,16 +1,13 @@
# Hardcoded messages that NickServ sends and what to display to the user instead # Hardcoded messages that NickServ sends and what to display to the user instead
messages = { messages = {
"Your nick isn't registered.": "Your nick isn't registered.": "", # display the same
"", # display the same "Password accepted - you are now recognized.": "", # display the same
"Password accepted - you are now recognized.": "If you do not change within one minute, I will change your nick.": "You have 1 minute to identify.",
"", # display the same "If you do not change within 20 seconds, I will change your nick.": "You have 20 seconds to identify.",
"If you do not change within one minute, I will change your nick.":
"You have 1 minute to identify.",
"If you do not change within 20 seconds, I will change your nick.":
"You have 20 seconds to identify."
} }
def translate(msg): def translate(msg):
if msg in messages: if msg in messages:
if messages[msg] == "": if messages[msg] == "":

View file

@ -8,30 +8,37 @@ except ImportError:
print("PyQt5 fallback (ostools.py)") print("PyQt5 fallback (ostools.py)")
from PyQt5.QtCore import QStandardPaths from PyQt5.QtCore import QStandardPaths
def isOSX(): def isOSX():
return sys.platform == "darwin" return sys.platform == "darwin"
def isWin32(): def isWin32():
return sys.platform == "win32" return sys.platform == "win32"
def isLinux(): def isLinux():
return sys.platform.startswith("linux") return sys.platform.startswith("linux")
def isOSXBundle(): def isOSXBundle():
return isOSX() and (os.path.abspath('.').find(".app") != -1) return isOSX() and (os.path.abspath(".").find(".app") != -1)
def isOSXLeopard(): def isOSXLeopard():
return isOSX() and platform.mac_ver()[0].startswith("10.5") return isOSX() and platform.mac_ver()[0].startswith("10.5")
def osVer(): def osVer():
if isWin32(): if isWin32():
return " ".join(platform.win32_ver()) return " ".join(platform.win32_ver())
elif isOSX(): elif isOSX():
ver = platform.mac_ver(); ver = platform.mac_ver()
return " ".join((ver[0], " (", ver[2], ")")) return " ".join((ver[0], " (", ver[2], ")"))
elif isLinux(): elif isLinux():
return " ".join(platform.linux_distribution()) return " ".join(platform.linux_distribution())
def validateDataDir(): def validateDataDir():
"""Checks if data directory is present""" """Checks if data directory is present"""
# Define paths # Define paths
@ -50,20 +57,36 @@ def validateDataDir():
# pesterchum.js # pesterchum.js
if not os.path.exists(js_pchum): if not os.path.exists(js_pchum):
with open(js_pchum, 'w') as f: with open(js_pchum, "w") as f:
f.write("{}") f.write("{}")
def getDataDir(): def getDataDir():
# Temporary fix for non-ascii usernames # Temporary fix for non-ascii usernames
# If username has non-ascii characters, just store userdata # If username has non-ascii characters, just store userdata
# in the Pesterchum install directory (like before) # in the Pesterchum install directory (like before)
try: try:
if isOSX(): if isOSX():
return os.path.join(QStandardPaths.writableLocation(QStandardPaths.StandardLocation.AppLocalDataLocation), "Pesterchum/") return os.path.join(
QStandardPaths.writableLocation(
QStandardPaths.StandardLocation.AppLocalDataLocation
),
"Pesterchum/",
)
elif isLinux(): elif isLinux():
return os.path.join(QStandardPaths.writableLocation(QStandardPaths.StandardLocation.HomeLocation), ".pesterchum/") return os.path.join(
QStandardPaths.writableLocation(
QStandardPaths.StandardLocation.HomeLocation
),
".pesterchum/",
)
else: else:
return os.path.join(QStandardPaths.writableLocation(QStandardPaths.StandardLocation.AppLocalDataLocation), "pesterchum/") return os.path.join(
QStandardPaths.writableLocation(
QStandardPaths.StandardLocation.AppLocalDataLocation
),
"pesterchum/",
)
except UnicodeDecodeError as e: except UnicodeDecodeError as e:
print(e) print(e)
return '' return ""

View file

@ -16,7 +16,8 @@
# THE SOFTWARE. # THE SOFTWARE.
import logging import logging
PchumLog = logging.getLogger('pchumLogger')
PchumLog = logging.getLogger("pchumLogger")
import logging import logging
import socket import socket
@ -29,17 +30,18 @@ from oyoyo.parse import parse_raw_irc_command
from oyoyo import helpers from oyoyo import helpers
from oyoyo.cmdhandler import CommandError from oyoyo.cmdhandler import CommandError
class IRCClientError(Exception): class IRCClientError(Exception):
pass pass
class IRCClient: class IRCClient:
""" IRC Client class. This handles one connection to a server. """IRC Client class. This handles one connection to a server.
This can be used either with or without IRCApp ( see connect() docs ) This can be used either with or without IRCApp ( see connect() docs )
""" """
def __init__(self, cmd_handler, **kwargs): def __init__(self, cmd_handler, **kwargs):
""" the first argument should be an object with attributes/methods named """the first argument should be an object with attributes/methods named
as the irc commands. You may subclass from one of the classes in as the irc commands. You may subclass from one of the classes in
oyoyo.cmdhandler for convenience but it is not required. The oyoyo.cmdhandler for convenience but it is not required. The
methods should have arguments (prefix, args). prefix is methods should have arguments (prefix, args). prefix is
@ -93,7 +95,7 @@ class IRCClient:
self._end = False self._end = False
def send(self, *args, **kwargs): def send(self, *args, **kwargs):
""" send a message to the connected server. all arguments are joined """send a message to the connected server. all arguments are joined
with a space for convenience, for example the following are identical with a space for convenience, for example the following are identical
>>> cli.send("JOIN %s" % some_room) >>> cli.send("JOIN %s" % some_room)
@ -109,19 +111,24 @@ class IRCClient:
if self._end == True: if self._end == True:
return return
# Convert all args to bytes if not already # Convert all args to bytes if not already
encoding = kwargs.get('encoding') or 'utf8' encoding = kwargs.get("encoding") or "utf8"
bargs = [] bargs = []
for arg in args: for arg in args:
if isinstance(arg, str): if isinstance(arg, str):
bargs.append(bytes(arg, encoding)) bargs.append(bytes(arg, encoding))
elif isinstance(arg, bytes): elif isinstance(arg, bytes):
bargs.append(arg) bargs.append(arg)
elif type(arg).__name__ == 'unicode': elif type(arg).__name__ == "unicode":
bargs.append(arg.encode(encoding)) bargs.append(arg.encode(encoding))
else: else:
PchumLog.warning('Refusing to send one of the args from provided: %s'% repr([(type(arg), arg) for arg in args])) PchumLog.warning(
raise IRCClientError('Refusing to send one of the args from provided: %s' "Refusing to send one of the args from provided: %s"
% repr([(type(arg), arg) for arg in args])) % repr([(type(arg), arg) for arg in args])
)
raise IRCClientError(
"Refusing to send one of the args from provided: %s"
% repr([(type(arg), arg) for arg in args])
)
msg = bytes(" ", "UTF-8").join(bargs) msg = bytes(" ", "UTF-8").join(bargs)
PchumLog.info('---> send "%s"' % msg) PchumLog.info('---> send "%s"' % msg)
@ -135,7 +142,9 @@ class IRCClient:
self._end = True self._end = True
break break
try: try:
ready_to_read, ready_to_write, in_error = select.select([], [self.socket], []) ready_to_read, ready_to_write, in_error = select.select(
[], [self.socket], []
)
for x in ready_to_write: for x in ready_to_write:
x.sendall(msg + bytes("\r\n", "UTF-8")) x.sendall(msg + bytes("\r\n", "UTF-8"))
break break
@ -157,10 +166,7 @@ class IRCClient:
# socket.timeout is deprecated in 3.10 # socket.timeout is deprecated in 3.10
PchumLog.warning("TimeoutError in on send, " + str(e)) PchumLog.warning("TimeoutError in on send, " + str(e))
raise socket.timeout raise socket.timeout
except (OSError, except (OSError, IndexError, ValueError, Exception) as e:
IndexError,
ValueError,
Exception) as e:
PchumLog.warning("Unkown error on send, " + str(e)) PchumLog.warning("Unkown error on send, " + str(e))
if tries >= 9: if tries >= 9:
raise e raise e
@ -168,23 +174,25 @@ class IRCClient:
PchumLog.warning("Retrying send. (attempt %s)" % str(tries)) PchumLog.warning("Retrying send. (attempt %s)" % str(tries))
time.sleep(0.1) time.sleep(0.1)
PchumLog.debug("ready_to_write (len %s): " % str(len(ready_to_write)) + str(ready_to_write)) PchumLog.debug(
"ready_to_write (len %s): " % str(len(ready_to_write))
+ str(ready_to_write)
)
except Exception as se: except Exception as se:
PchumLog.warning("Send Exception %s" % str(se)) PchumLog.warning("Send Exception %s" % str(se))
try: try:
if not self.blocking and se.errno == 11: if not self.blocking and se.errno == 11:
pass pass
else: else:
#raise se # raise se
self._end = True # This ok? self._end = True # This ok?
except AttributeError: except AttributeError:
#raise se # raise se
self._end = True # This ok? self._end = True # This ok?
def connect(self, verify_hostname=True): def connect(self, verify_hostname=True):
""" initiates the connection to the server set in self.host:self.port """initiates the connection to the server set in self.host:self.port"""
""" PchumLog.info("connecting to %s:%s" % (self.host, self.port))
PchumLog.info('connecting to %s:%s' % (self.host, self.port))
if self.ssl == True: if self.ssl == True:
context = ssl.create_default_context() context = ssl.create_default_context()
@ -193,9 +201,9 @@ class IRCClient:
context.verify_mode = ssl.CERT_NONE context.verify_mode = ssl.CERT_NONE
bare_sock = socket.create_connection((self.host, self.port)) bare_sock = socket.create_connection((self.host, self.port))
self.socket = context.wrap_socket(bare_sock, self.socket = context.wrap_socket(
server_hostname=self.host, bare_sock, server_hostname=self.host, do_handshake_on_connect=False
do_handshake_on_connect=False) )
while True: while True:
try: try:
self.socket.do_handshake() self.socket.do_handshake()
@ -224,7 +232,7 @@ class IRCClient:
elif self.blocking: elif self.blocking:
self.socket.setblocking(True) self.socket.setblocking(True)
#try: # try:
# self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) # self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
# if hasattr(socket, "TCP_KEEPIDLE"): # if hasattr(socket, "TCP_KEEPIDLE"):
# self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 1) # self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 1)
@ -232,7 +240,7 @@ class IRCClient:
# self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 1) # self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 1)
# if hasattr(socket, "TCP_KEEPCNT"): # if hasattr(socket, "TCP_KEEPCNT"):
# self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 1) # self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 1)
#except Exception as e: # except Exception as e:
# print(e) # print(e)
helpers.nick(self, self.nick) helpers.nick(self, self.nick)
@ -241,7 +249,7 @@ class IRCClient:
self.connect_cb(self) self.connect_cb(self)
def conn(self): def conn(self):
"""returns a generator object. """ """returns a generator object."""
try: try:
buffer = bytes() buffer = bytes()
while not self._end: while not self._end:
@ -256,7 +264,9 @@ class IRCClient:
self._end = True self._end = True
break break
try: try:
ready_to_read, ready_to_write, in_error = select.select([self.socket], [], []) ready_to_read, ready_to_write, in_error = select.select(
[self.socket], [], []
)
for x in ready_to_read: for x in ready_to_read:
buffer += x.recv(1024) buffer += x.recv(1024)
break break
@ -278,15 +288,14 @@ class IRCClient:
# socket.timeout is deprecated in 3.10 # socket.timeout is deprecated in 3.10
PchumLog.warning("TimeoutError in on send, " + str(e)) PchumLog.warning("TimeoutError in on send, " + str(e))
raise socket.timeout raise socket.timeout
except (OSError, except (OSError, IndexError, ValueError, Exception) as e:
IndexError,
ValueError,
Exception) as e:
PchumLog.debug("Miscellaneous exception in conn, " + str(e)) PchumLog.debug("Miscellaneous exception in conn, " + str(e))
if tries >= 9: if tries >= 9:
raise e raise e
tries += 1 tries += 1
PchumLog.debug("Possibly retrying recv. (attempt %s)" % str(tries)) PchumLog.debug(
"Possibly retrying recv. (attempt %s)" % str(tries)
)
time.sleep(0.1) time.sleep(0.1)
except socket.timeout as e: except socket.timeout as e:
@ -322,7 +331,7 @@ class IRCClient:
for el in data: for el in data:
tags, prefix, command, args = parse_raw_irc_command(el) tags, prefix, command, args = parse_raw_irc_command(el)
#print(tags, prefix, command, args) # print(tags, prefix, command, args)
try: try:
# Only need tags with tagmsg # Only need tags with tagmsg
if command.upper() == "TAGMSG": if command.upper() == "TAGMSG":
@ -339,7 +348,7 @@ class IRCClient:
except (OSError, ssl.SSLEOFError) as se: except (OSError, ssl.SSLEOFError) as se:
PchumLog.debug("problem: %s" % (str(se))) PchumLog.debug("problem: %s" % (str(se)))
if self.socket: if self.socket:
PchumLog.info('error: closing socket') PchumLog.info("error: closing socket")
self.socket.close() self.socket.close()
raise se raise se
except Exception as e: except Exception as e:
@ -348,26 +357,32 @@ class IRCClient:
else: else:
PchumLog.debug("ending while, end is %s" % self._end) PchumLog.debug("ending while, end is %s" % self._end)
if self.socket: if self.socket:
PchumLog.info('finished: closing socket') PchumLog.info("finished: closing socket")
self.socket.close() self.socket.close()
yield False yield False
def close(self): def close(self):
# with extreme prejudice # with extreme prejudice
if self.socket: if self.socket:
PchumLog.info('shutdown socket') PchumLog.info("shutdown socket")
#print("shutdown socket") # print("shutdown socket")
self._end = True self._end = True
try: try:
self.socket.shutdown(socket.SHUT_RDWR) self.socket.shutdown(socket.SHUT_RDWR)
except OSError as e: except OSError as e:
PchumLog.debug("Error while shutting down socket, already broken? %s" % str(e)) PchumLog.debug(
"Error while shutting down socket, already broken? %s" % str(e)
)
try: try:
self.socket.close() self.socket.close()
except OSError as e: except OSError as e:
PchumLog.debug("Error while closing socket, already broken? %s" % str(e)) PchumLog.debug(
"Error while closing socket, already broken? %s" % str(e)
)
class IRCApp: class IRCApp:
""" This class manages several IRCClient instances without the use of threads. """This class manages several IRCClient instances without the use of threads.
(Non-threaded) Timer functionality is also included. (Non-threaded) Timer functionality is also included.
""" """
@ -384,27 +399,27 @@ class IRCApp:
self.sleep_time = 0.5 self.sleep_time = 0.5
def addClient(self, client, autoreconnect=False): def addClient(self, client, autoreconnect=False):
""" add a client object to the application. setting autoreconnect """add a client object to the application. setting autoreconnect
to true will mean the application will attempt to reconnect the client to true will mean the application will attempt to reconnect the client
after every disconnect. you can also set autoreconnect to a number after every disconnect. you can also set autoreconnect to a number
to specify how many reconnects should happen. to specify how many reconnects should happen.
warning: if you add a client that has blocking set to true, warning: if you add a client that has blocking set to true,
timers will no longer function properly """ timers will no longer function properly"""
PchumLog.info('added client %s (ar=%s)' % (client, autoreconnect)) PchumLog.info("added client %s (ar=%s)" % (client, autoreconnect))
self._clients[client] = self._ClientDesc(autoreconnect=autoreconnect) self._clients[client] = self._ClientDesc(autoreconnect=autoreconnect)
def addTimer(self, seconds, cb): def addTimer(self, seconds, cb):
""" add a timed callback. accuracy is not specified, you can only """add a timed callback. accuracy is not specified, you can only
garuntee the callback will be called after seconds has passed. garuntee the callback will be called after seconds has passed.
( the only advantage to these timers is they dont use threads ) ( the only advantage to these timers is they dont use threads )
""" """
assert callable(cb) assert callable(cb)
PchumLog.info('added timer to call %s in %ss' % (cb, seconds)) PchumLog.info("added timer to call %s in %ss" % (cb, seconds))
self._timers.append((time.time() + seconds, cb)) self._timers.append((time.time() + seconds, cb))
def run(self): def run(self):
""" run the application. this will block until stop() is called """ """run the application. this will block until stop() is called"""
# TODO: convert this to use generators too? # TODO: convert this to use generators too?
self.running = True self.running = True
while self.running: while self.running:
@ -417,7 +432,7 @@ class IRCApp:
try: try:
next(clientdesc.con) next(clientdesc.con)
except Exception as e: except Exception as e:
PchumLog.error('client error %s' % str(e)) PchumLog.error("client error %s" % str(e))
PchumLog.error(traceback.format_exc()) PchumLog.error(traceback.format_exc())
if clientdesc.autoreconnect: if clientdesc.autoreconnect:
clientdesc.con = None clientdesc.con = None
@ -430,7 +445,7 @@ class IRCApp:
found_one_alive = True found_one_alive = True
if not found_one_alive: if not found_one_alive:
PchumLog.info('nothing left alive... quiting') PchumLog.info("nothing left alive... quiting")
self.stop() self.stop()
now = time.time() now = time.time()
@ -438,7 +453,7 @@ class IRCApp:
self._timers = [] self._timers = []
for target_time, cb in timers: for target_time, cb in timers:
if now > target_time: if now > target_time:
PchumLog.info('calling timer cb %s' % cb) PchumLog.info("calling timer cb %s" % cb)
cb() cb()
else: else:
self._timers.append((target_time, cb)) self._timers.append((target_time, cb))
@ -446,9 +461,5 @@ class IRCApp:
time.sleep(self.sleep_time) time.sleep(self.sleep_time)
def stop(self): def stop(self):
""" stop the application """ """stop the application"""
self.running = False self.running = False

View file

@ -20,10 +20,11 @@ import inspect
from oyoyo import helpers from oyoyo import helpers
from oyoyo.parse import parse_nick from oyoyo.parse import parse_nick
PchumLog = logging.getLogger('pchumLogger') PchumLog = logging.getLogger("pchumLogger")
def protected(func): def protected(func):
""" decorator to protect functions from being called """ """decorator to protect functions from being called"""
func.protected = True func.protected = True
return func return func
@ -32,17 +33,19 @@ class CommandError(Exception):
def __init__(self, cmd): def __init__(self, cmd):
self.cmd = cmd self.cmd = cmd
class NoSuchCommandError(CommandError): class NoSuchCommandError(CommandError):
def __str__(self): def __str__(self):
return 'No such command "%s"' % ".".join(self.cmd) return 'No such command "%s"' % ".".join(self.cmd)
class ProtectedCommandError(CommandError): class ProtectedCommandError(CommandError):
def __str__(self): def __str__(self):
return 'Command "%s" is protected' % ".".join(self.cmd) return 'Command "%s" is protected' % ".".join(self.cmd)
class CommandHandler(object): class CommandHandler(object):
""" The most basic CommandHandler """ """The most basic CommandHandler"""
def __init__(self, client): def __init__(self, client):
self.client = client self.client = client
@ -59,13 +62,13 @@ class CommandHandler(object):
["command", "sub", "func"]. ["command", "sub", "func"].
""" """
if isinstance(in_command_parts, (str, bytes)): if isinstance(in_command_parts, (str, bytes)):
in_command_parts = in_command_parts.split('.') in_command_parts = in_command_parts.split(".")
command_parts = in_command_parts[:] command_parts = in_command_parts[:]
p = self p = self
while command_parts: while command_parts:
cmd = command_parts.pop(0) cmd = command_parts.pop(0)
if cmd.startswith('_'): if cmd.startswith("_"):
raise ProtectedCommandError(in_command_parts) raise ProtectedCommandError(in_command_parts)
try: try:
@ -73,7 +76,7 @@ class CommandHandler(object):
except AttributeError: except AttributeError:
raise NoSuchCommandError(in_command_parts) raise NoSuchCommandError(in_command_parts)
if hasattr(f, 'protected'): if hasattr(f, "protected"):
raise ProtectedCommandError(in_command_parts) raise ProtectedCommandError(in_command_parts)
if isinstance(f, CommandHandler) and command_parts: if isinstance(f, CommandHandler) and command_parts:
@ -84,10 +87,10 @@ class CommandHandler(object):
@protected @protected
def run(self, command, *args): def run(self, command, *args):
""" finds and runs a command """ """finds and runs a command"""
arguments_str = '' arguments_str = ""
for x in args: for x in args:
arguments_str += str(x) + ' ' arguments_str += str(x) + " "
PchumLog.debug("processCommand %s(%s)" % (command, arguments_str.strip())) PchumLog.debug("processCommand %s(%s)" % (command, arguments_str.strip()))
try: try:
@ -97,14 +100,17 @@ class CommandHandler(object):
self.__unhandled__(command, *args) self.__unhandled__(command, *args)
return return
PchumLog.debug('f %s' % f) PchumLog.debug("f %s" % f)
try: try:
f(*args) f(*args)
except TypeError as e: except TypeError as e:
PchumLog.info("Failed to pass command, did the server pass an unsupported paramater? " + str(e)) PchumLog.info(
"Failed to pass command, did the server pass an unsupported paramater? "
+ str(e)
)
except Exception as e: except Exception as e:
#logging.info("Failed to pass command, %s" % str(e)) # logging.info("Failed to pass command, %s" % str(e))
PchumLog.exception("Failed to pass command") PchumLog.exception("Failed to pass command")
@protected @protected
@ -112,39 +118,45 @@ class CommandHandler(object):
"""The default handler for commands. Override this method to """The default handler for commands. Override this method to
apply custom behavior (example, printing) unhandled commands. apply custom behavior (example, printing) unhandled commands.
""" """
PchumLog.debug('unhandled command %s(%s)' % (cmd, args)) PchumLog.debug("unhandled command %s(%s)" % (cmd, args))
class DefaultCommandHandler(CommandHandler): class DefaultCommandHandler(CommandHandler):
""" CommandHandler that provides methods for the normal operation of IRC. """CommandHandler that provides methods for the normal operation of IRC.
If you want your bot to properly respond to pings, etc, you should subclass this. If you want your bot to properly respond to pings, etc, you should subclass this.
""" """
def ping(self, prefix, server): def ping(self, prefix, server):
self.client.send('PONG', server) self.client.send("PONG", server)
class DefaultBotCommandHandler(CommandHandler): class DefaultBotCommandHandler(CommandHandler):
""" default command handler for bots. methods/attributes are made """default command handler for bots. methods/attributes are made
available as commands """ available as commands"""
@protected @protected
def getVisibleCommands(self, obj=None): def getVisibleCommands(self, obj=None):
test = (lambda x: isinstance(x, CommandHandler) or \ test = (
inspect.ismethod(x) or inspect.isfunction(x)) lambda x: isinstance(x, CommandHandler)
or inspect.ismethod(x)
or inspect.isfunction(x)
)
members = inspect.getmembers(obj or self, test) members = inspect.getmembers(obj or self, test)
return [m for m, _ in members return [
if (not m.startswith('_') and m
not hasattr(getattr(obj, m), 'protected'))] for m, _ in members
if (not m.startswith("_") and not hasattr(getattr(obj, m), "protected"))
]
def help(self, sender, dest, arg=None): def help(self, sender, dest, arg=None):
"""list all available commands or get help on a specific command""" """list all available commands or get help on a specific command"""
PchumLog.info('help sender=%s dest=%s arg=%s' % (sender, dest, arg)) PchumLog.info("help sender=%s dest=%s arg=%s" % (sender, dest, arg))
if not arg: if not arg:
commands = self.getVisibleCommands() commands = self.getVisibleCommands()
commands.sort() commands.sort()
helpers.msg(self.client, dest, helpers.msg(
"available commands: %s" % " ".join(commands)) self.client, dest, "available commands: %s" % " ".join(commands)
)
else: else:
try: try:
f = self.get(arg) f = self.get(arg)
@ -163,7 +175,7 @@ class DefaultBotCommandHandler(CommandHandler):
class BotCommandHandler(DefaultCommandHandler): class BotCommandHandler(DefaultCommandHandler):
""" complete command handler for bots """ """complete command handler for bots"""
def __init__(self, client, command_handler): def __init__(self, client, command_handler):
DefaultCommandHandler.__init__(self, client) DefaultCommandHandler.__init__(self, client)
@ -174,7 +186,7 @@ class BotCommandHandler(DefaultCommandHandler):
@protected @protected
def tryBotCommand(self, prefix, dest, msg): def tryBotCommand(self, prefix, dest, msg):
""" tests a command to see if its a command for the bot, returns True """tests a command to see if its a command for the bot, returns True
and calls self.processBotCommand(cmd, sender) if its is. and calls self.processBotCommand(cmd, sender) if its is.
""" """
@ -183,13 +195,13 @@ class BotCommandHandler(DefaultCommandHandler):
if dest == self.client.nick: if dest == self.client.nick:
dest = parse_nick(prefix)[0] dest = parse_nick(prefix)[0]
elif msg.startswith(self.client.nick): elif msg.startswith(self.client.nick):
msg = msg[len(self.client.nick)+1:] msg = msg[len(self.client.nick) + 1 :]
else: else:
return False return False
msg = msg.strip() msg = msg.strip()
parts = msg.split(' ', 1) parts = msg.split(" ", 1)
command = parts[0] command = parts[0]
arg = parts[1:] arg = parts[1:]
@ -198,13 +210,3 @@ class BotCommandHandler(DefaultCommandHandler):
except CommandError as e: except CommandError as e:
helpers.msg(self.client, dest, str(e)) helpers.msg(self.client, dest, str(e))
return True return True

View file

@ -20,14 +20,16 @@
import logging import logging
import random import random
PchumLog = logging.getLogger('pchumLogger') PchumLog = logging.getLogger("pchumLogger")
def msg(cli, user, msg): def msg(cli, user, msg):
for line in msg.split('\n'): for line in msg.split("\n"):
cli.send("PRIVMSG", user, ":%s" % line) cli.send("PRIVMSG", user, ":%s" % line)
def names(cli, *channels): def names(cli, *channels):
tmp = __builtins__['list'](channels) tmp = __builtins__["list"](channels)
msglist = [] msglist = []
while len(tmp) > 0: while len(tmp) > 0:
msglist.append(tmp.pop()) msglist.append(tmp.pop())
@ -38,12 +40,15 @@ def names(cli, *channels):
if len(msglist) > 0: if len(msglist) > 0:
cli.send("NAMES %s" % (",".join(msglist))) cli.send("NAMES %s" % (",".join(msglist)))
def channel_list(cli): def channel_list(cli):
cli.send("LIST") cli.send("LIST")
def kick(cli, handle, channel, reason=""): def kick(cli, handle, channel, reason=""):
cli.send("KICK %s %s %s" % (channel, handle, reason)) cli.send("KICK %s %s %s" % (channel, handle, reason))
def mode(cli, channel, mode, options=None): def mode(cli, channel, mode, options=None):
PchumLog.debug("mode = " + str(mode)) PchumLog.debug("mode = " + str(mode))
PchumLog.debug("options = " + str(options)) PchumLog.debug("options = " + str(options))
@ -52,9 +57,10 @@ def mode(cli, channel, mode, options=None):
cmd += " %s" % (options) cmd += " %s" % (options)
cli.send(cmd) cli.send(cmd)
def ctcp(cli, handle, cmd, msg=""): def ctcp(cli, handle, cmd, msg=""):
# Space breaks protocol if msg is absent # Space breaks protocol if msg is absent
if msg=="": if msg == "":
cli.send("PRIVMSG", handle, "\x01%s\x01" % (cmd)) cli.send("PRIVMSG", handle, "\x01%s\x01" % (cmd))
else: else:
cli.send("PRIVMSG", handle, "\x01%s %s\x01" % (cmd, msg)) cli.send("PRIVMSG", handle, "\x01%s %s\x01" % (cmd, msg))
@ -63,77 +69,96 @@ def ctcp(cli, handle, cmd, msg=""):
def ctcp_reply(cli, handle, cmd, msg=""): def ctcp_reply(cli, handle, cmd, msg=""):
notice(cli, str(handle), "\x01%s %s\x01" % (cmd.upper(), msg)) notice(cli, str(handle), "\x01%s %s\x01" % (cmd.upper(), msg))
def metadata(cli, target, subcommand, *params): def metadata(cli, target, subcommand, *params):
# IRC metadata draft specification # IRC metadata draft specification
# https://gist.github.com/k4bek4be/92c2937cefd49990fbebd001faf2b237 # https://gist.github.com/k4bek4be/92c2937cefd49990fbebd001faf2b237
cli.send("METADATA", target, subcommand, *params) cli.send("METADATA", target, subcommand, *params)
def cap(cli, subcommand, *params): def cap(cli, subcommand, *params):
# Capability Negotiation # Capability Negotiation
# https://ircv3.net/specs/extensions/capability-negotiation.html # https://ircv3.net/specs/extensions/capability-negotiation.html
cli.send("CAP", subcommand, *params) cli.send("CAP", subcommand, *params)
def msgrandom(cli, choices, dest, user=None): def msgrandom(cli, choices, dest, user=None):
o = "%s: " % user if user else "" o = "%s: " % user if user else ""
o += random.choice(choices) o += random.choice(choices)
msg(cli, dest, o) msg(cli, dest, o)
def _makeMsgRandomFunc(choices): def _makeMsgRandomFunc(choices):
def func(cli, dest, user=None): def func(cli, dest, user=None):
msgrandom(cli, choices, dest, user) msgrandom(cli, choices, dest, user)
return func return func
msgYes = _makeMsgRandomFunc(['yes', 'alright', 'ok'])
msgOK = _makeMsgRandomFunc(['ok', 'done']) msgYes = _makeMsgRandomFunc(["yes", "alright", "ok"])
msgNo = _makeMsgRandomFunc(['no', 'no-way']) msgOK = _makeMsgRandomFunc(["ok", "done"])
msgNo = _makeMsgRandomFunc(["no", "no-way"])
def ns(cli, *args): def ns(cli, *args):
msg(cli, "NickServ", " ".join(args)) msg(cli, "NickServ", " ".join(args))
def cs(cli, *args): def cs(cli, *args):
msg(cli, "ChanServ", " ".join(args)) msg(cli, "ChanServ", " ".join(args))
def identify(cli, passwd, authuser="NickServ"): def identify(cli, passwd, authuser="NickServ"):
msg(cli, authuser, "IDENTIFY %s" % passwd) msg(cli, authuser, "IDENTIFY %s" % passwd)
def quit(cli, msg): def quit(cli, msg):
cli.send("QUIT %s" % (msg)) cli.send("QUIT %s" % (msg))
def user(cli, username, realname): def user(cli, username, realname):
cli.send("USER", cli.send("USER", username, "0", "*", ":" + realname)
username,
'0',
'*',
':' + realname)
_simple = ( _simple = (
'join', "join",
'part', "part",
'nick', "nick",
'notice', "notice",
'invite', "invite",
) )
def _addsimple(): def _addsimple():
import sys import sys
def simplecmd(cmd_name): def simplecmd(cmd_name):
def f(cli, *args): def f(cli, *args):
cli.send(cmd_name, *args) cli.send(cmd_name, *args)
return f return f
m = sys.modules[__name__] m = sys.modules[__name__]
for t in _simple: for t in _simple:
setattr(m, t, simplecmd(t.upper())) setattr(m, t, simplecmd(t.upper()))
_addsimple() _addsimple()
def _addNumerics(): def _addNumerics():
import sys import sys
from oyoyo import ircevents from oyoyo import ircevents
def numericcmd(cmd_num, cmd_name): def numericcmd(cmd_num, cmd_name):
def f(cli, *args): def f(cli, *args):
cli.send(cmd_num, *args) cli.send(cmd_num, *args)
return f return f
m = sys.modules[__name__] m = sys.modules[__name__]
for num, name in ircevents.numeric_events.items(): for num, name in ircevents.numeric_events.items():
setattr(m, name, numericcmd(num, name)) setattr(m, name, numericcmd(num, name))
_addNumerics() _addNumerics()

View file

@ -116,7 +116,7 @@ numeric_events = {
"374": "endofinfo", "374": "endofinfo",
"375": "motdstart", "375": "motdstart",
"376": "endofmotd", "376": "endofmotd",
"377": "motd2", # 1997-10-16 -- tkil "377": "motd2", # 1997-10-16 -- tkil
"381": "youreoper", "381": "youreoper",
"382": "rehashing", "382": "rehashing",
"384": "myportis", "384": "myportis",
@ -143,7 +143,7 @@ numeric_events = {
"423": "noadmininfo", "423": "noadmininfo",
"424": "fileerror", "424": "fileerror",
"431": "nonicknamegiven", "431": "nonicknamegiven",
"432": "erroneusnickname", # Thiss iz how its speld in thee RFC. "432": "erroneusnickname", # Thiss iz how its speld in thee RFC.
"433": "nicknameinuse", "433": "nicknameinuse",
"436": "nickcollision", "436": "nickcollision",
"437": "unavailresource", # "Nick temporally unavailable" "437": "unavailresource", # "Nick temporally unavailable"
@ -158,7 +158,7 @@ numeric_events = {
"462": "alreadyregistered", "462": "alreadyregistered",
"463": "nopermforhost", "463": "nopermforhost",
"464": "passwdmismatch", "464": "passwdmismatch",
"465": "yourebannedcreep", # I love this one... "465": "yourebannedcreep", # I love this one...
"466": "youwillbebanned", "466": "youwillbebanned",
"467": "keyset", "467": "keyset",
"471": "channelisfull", "471": "channelisfull",
@ -172,7 +172,7 @@ numeric_events = {
"481": "noprivileges", "481": "noprivileges",
"482": "chanoprivsneeded", "482": "chanoprivsneeded",
"483": "cantkillserver", "483": "cantkillserver",
"484": "restricted", # Connection is restricted "484": "restricted", # Connection is restricted
"485": "uniqopprivsneeded", "485": "uniqopprivsneeded",
"491": "nooperhost", "491": "nooperhost",
"492": "noservicehost", "492": "noservicehost",
@ -195,7 +195,7 @@ metadata_numeric_events = {
"768": "keynotset", "768": "keynotset",
"769": "keynopermission", "769": "keynopermission",
"770": "metadatasubok", "770": "metadatasubok",
} }
numeric_events.update(metadata_numeric_events) numeric_events.update(metadata_numeric_events)
generated_events = [ generated_events = [
@ -226,10 +226,7 @@ protocol_events = [
"nick", # We can get svsnicked "nick", # We can get svsnicked
"metadata", # Metadata specification "metadata", # Metadata specification
"tagmsg", # IRCv3 message tags extension "tagmsg", # IRCv3 message tags extension
"cap" # IRCv3 Client Capability Negotiation "cap", # IRCv3 Client Capability Negotiation
] ]
all_events = (generated_events all_events = generated_events + protocol_events + list(numeric_events.values())
+ protocol_events
+ list(numeric_events.values()))

View file

@ -19,7 +19,8 @@ import logging
from oyoyo.ircevents import numeric_events from oyoyo.ircevents import numeric_events
PchumLog = logging.getLogger('pchumLogger') PchumLog = logging.getLogger("pchumLogger")
def parse_raw_irc_command(element): def parse_raw_irc_command(element):
""" """
@ -60,15 +61,15 @@ def parse_raw_irc_command(element):
element = element.decode("utf-8") element = element.decode("utf-8")
except UnicodeDecodeError as e: except UnicodeDecodeError as e:
PchumLog.debug("utf-8 error %s" % str(e)) PchumLog.debug("utf-8 error %s" % str(e))
element = element.decode("latin-1", 'replace') element = element.decode("latin-1", "replace")
parts = element.strip().split(" ") parts = element.strip().split(" ")
if parts[0].startswith(':'): if parts[0].startswith(":"):
tags = None tags = None
prefix = parts[0][1:] prefix = parts[0][1:]
command = parts[1] command = parts[1]
args = parts[2:] args = parts[2:]
elif parts[0].startswith('@'): elif parts[0].startswith("@"):
# Message tag # Message tag
tags = parts[0] tags = parts[0]
prefix = parts[1][1:] prefix = parts[1][1:]
@ -84,14 +85,14 @@ def parse_raw_irc_command(element):
try: try:
command = numeric_events[command] command = numeric_events[command]
except KeyError: except KeyError:
PchumLog.info('unknown numeric event %s' % command) PchumLog.info("unknown numeric event %s" % command)
command = command.lower() command = command.lower()
if args[0].startswith(':'): if args[0].startswith(":"):
args = [" ".join(args)[1:]] args = [" ".join(args)[1:]]
else: else:
for idx, arg in enumerate(args): for idx, arg in enumerate(args):
if arg.startswith(':'): if arg.startswith(":"):
args = args[:idx] + [" ".join(args[idx:])[1:]] args = args[:idx] + [" ".join(args[idx:])[1:]]
break break
@ -99,23 +100,22 @@ def parse_raw_irc_command(element):
def parse_nick(name): def parse_nick(name):
""" parse a nickname and return a tuple of (nick, mode, user, host) """parse a nickname and return a tuple of (nick, mode, user, host)
<nick> [ '!' [<mode> = ] <user> ] [ '@' <host> ] <nick> [ '!' [<mode> = ] <user> ] [ '@' <host> ]
""" """
try: try:
nick, rest = name.split('!') nick, rest = name.split("!")
except ValueError: except ValueError:
return (name, None, None, None) return (name, None, None, None)
try: try:
mode, rest = rest.split('=') mode, rest = rest.split("=")
except ValueError: except ValueError:
mode, rest = None, rest mode, rest = None, rest
try: try:
user, host = rest.split('@') user, host = rest.split("@")
except ValueError: except ValueError:
return (name, mode, rest, None) return (name, mode, rest, None)
return (name, mode, user, host) return (name, mode, user, host)

View file

@ -1,117 +1,123 @@
# NickServ basic functions # NickServ basic functions
_nickservfuncs = ( _nickservfuncs = (
'register', "register",
'group', "group",
'glist', "glist",
'identify', "identify",
'access', "access",
'drop', "drop",
'recover', "recover",
'release', "release",
'sendpass', "sendpass",
'ghost', "ghost",
'alist', "alist",
'info', "info",
'list', "list",
'logout', "logout",
'status', "status",
'update' "update",
) )
# NickServ SET functions # NickServ SET functions
_nickservsetfuncs = ( _nickservsetfuncs = (
'display', "display",
'password', "password",
'language', "language",
'url', "url",
'email', "email",
'icq', "icq",
'greet', "greet",
'kill', "kill",
'secure', "secure",
'private', "private",
'hide', "hide",
'msg', "msg",
'autoop' "autoop",
) )
# ChanServ basic functions # ChanServ basic functions
_chanservfuncs = ( _chanservfuncs = (
'register', "register",
'identify', "identify",
'sop', "sop",
'aop', "aop",
'hop', "hop",
'vop', "vop",
'access', "access",
'levels', "levels",
'akick', "akick",
'drop', "drop",
'sendpass', "sendpass",
'ban', "ban",
'unban', "unban",
'clear', "clear",
'owner', "owner",
'deowner', "deowner",
'protect', "protect",
'deprotect', "deprotect",
'op', "op",
'deop', "deop",
'halfop', "halfop",
'dehalfop', "dehalfop",
'voice', "voice",
'devoice', "devoice",
'getkey', "getkey",
'invite', "invite",
'kick', "kick",
'list', "list",
'logout', "logout",
'topic', "topic",
'info', "info",
'appendtopic', "appendtopic",
'enforce' "enforce",
) )
_chanservsetfuncs = ( _chanservsetfuncs = (
'founder', "founder",
'successor', "successor",
'password', "password",
'desc', "desc",
'url', "url",
'email', "email",
'entrymsg', "entrymsg",
'bantype', "bantype",
'mlock', "mlock",
'keeptopic', "keeptopic",
'opnotice', "opnotice",
'peace', "peace",
'private', "private",
'restricted', "restricted",
'secure', "secure",
'secureops', "secureops",
'securefounder', "securefounder",
'signkick', "signkick",
'topiclock', "topiclock",
'xop' "xop",
) )
def _addServ(serv, funcs, prefix=""): def _addServ(serv, funcs, prefix=""):
def simplecmd(cmd_name): def simplecmd(cmd_name):
if prefix: if prefix:
cmd_name = prefix.upper() + " " + cmd_name cmd_name = prefix.upper() + " " + cmd_name
def f(cli, *args): def f(cli, *args):
print(cmd_name, " ".join(args)) print(cmd_name, " ".join(args))
#cli.send(cmd_name, serv.name, *args) # cli.send(cmd_name, serv.name, *args)
return f return f
for t in funcs: for t in funcs:
setattr(serv, t, simplecmd(t.upper())) setattr(serv, t, simplecmd(t.upper()))
class NickServ(object): class NickServ(object):
def __init__(self, nick="NickServ"): def __init__(self, nick="NickServ"):
self.name = nick self.name = nick
_addServ(self, _nickservfuncs) _addServ(self, _nickservfuncs)
_addServ(self, _nickservsetfuncs, "set") _addServ(self, _nickservsetfuncs, "set")
class ChanServ(object): class ChanServ(object):
def __init__(self, nick="ChanServ"): def __init__(self, nick="ChanServ"):
self.name = nick self.name = nick

View file

@ -12,51 +12,56 @@ except ImportError:
import dataobjs import dataobjs
import ostools import ostools
# karxi: My own contribution to this - a proper lexer. # karxi: My own contribution to this - a proper lexer.
import pnc.lexercon as lexercon import pnc.lexercon as lexercon
from generic import mysteryTime from generic import mysteryTime
from quirks import ScriptQuirks from quirks import ScriptQuirks
from pyquirks import PythonQuirks from pyquirks import PythonQuirks
#from luaquirks import LuaQuirks
PchumLog = logging.getLogger('pchumLogger') # from luaquirks import LuaQuirks
PchumLog = logging.getLogger("pchumLogger")
# I'll clean up the things that are no longer needed once the transition is # I'll clean up the things that are no longer needed once the transition is
# actually finished. # actually finished.
QString = str QString = str
_ctag_begin = re.compile(r'(?i)<c=(.*?)>') _ctag_begin = re.compile(r"(?i)<c=(.*?)>")
_gtag_begin = re.compile(r'(?i)<g[a-f]>') _gtag_begin = re.compile(r"(?i)<g[a-f]>")
_ctag_end = re.compile(r'(?i)</c>') _ctag_end = re.compile(r"(?i)</c>")
_ctag_rgb = re.compile(r'\d+,\d+,\d+') _ctag_rgb = re.compile(r"\d+,\d+,\d+")
_urlre = re.compile(r"(?i)(?:^|(?<=\s))(?:(?:https?|ftp)://|magnet:)[^\s]+") _urlre = re.compile(r"(?i)(?:^|(?<=\s))(?:(?:https?|ftp)://|magnet:)[^\s]+")
#_url2re = re.compile(r"(?i)(?<!//)\bwww\.[^\s]+?\.") # _url2re = re.compile(r"(?i)(?<!//)\bwww\.[^\s]+?\.")
_memore = re.compile(r"(\s|^)(#[A-Za-z0-9_]+)") _memore = re.compile(r"(\s|^)(#[A-Za-z0-9_]+)")
_handlere = re.compile(r"(\s|^)(@[A-Za-z0-9_]+)") _handlere = re.compile(r"(\s|^)(@[A-Za-z0-9_]+)")
_imgre = re.compile(r"""(?i)<img src=['"](\S+)['"]\s*/>""") _imgre = re.compile(r"""(?i)<img src=['"](\S+)['"]\s*/>""")
_mecmdre = re.compile(r"^(/me|PESTERCHUM:ME)(\S*)") _mecmdre = re.compile(r"^(/me|PESTERCHUM:ME)(\S*)")
_oocre = re.compile(r"([\[(\{])\1.*([\])\}])\2") _oocre = re.compile(r"([\[(\{])\1.*([\])\}])\2")
_format_begin = re.compile(r'(?i)<([ibu])>') _format_begin = re.compile(r"(?i)<([ibu])>")
_format_end = re.compile(r'(?i)</([ibu])>') _format_end = re.compile(r"(?i)</([ibu])>")
_honk = re.compile(r"(?i)\bhonk\b") _honk = re.compile(r"(?i)\bhonk\b")
_groupre = re.compile(r"\\([0-9]+)") _groupre = re.compile(r"\\([0-9]+)")
quirkloader = ScriptQuirks() quirkloader = ScriptQuirks()
_functionre = None _functionre = None
def loadQuirks(): def loadQuirks():
global quirkloader, _functionre global quirkloader, _functionre
quirkloader.add(PythonQuirks()) quirkloader.add(PythonQuirks())
#quirkloader.add(LuaQuirks()) # quirkloader.add(LuaQuirks())
quirkloader.loadAll() quirkloader.loadAll()
quirkloader.funcre() quirkloader.funcre()
_functionre = re.compile(r"%s" % quirkloader.funcre()) _functionre = re.compile(r"%s" % quirkloader.funcre())
def reloadQuirkFunctions(): def reloadQuirkFunctions():
quirkloader.loadAll() quirkloader.loadAll()
global _functionre global _functionre
_functionre = re.compile(r"%s" % quirkloader.funcre()) _functionre = re.compile(r"%s" % quirkloader.funcre())
def lexer(string, objlist): def lexer(string, objlist):
"""objlist is a list: [(objecttype, re),...] list is in order of preference""" """objlist is a list: [(objecttype, re),...] list is in order of preference"""
stringlist = [string] stringlist = [string]
@ -80,6 +85,7 @@ def lexer(string, objlist):
stringlist = copy(newstringlist) stringlist = copy(newstringlist)
return stringlist return stringlist
# karxi: All of these were derived from object before. I changed them to # karxi: All of these were derived from object before. I changed them to
# lexercon.Chunk so that I'd have an easier way to match against them until # lexercon.Chunk so that I'd have an easier way to match against them until
# they're redone/removed. # they're redone/removed.
@ -87,12 +93,13 @@ class colorBegin(lexercon.Chunk):
def __init__(self, string, color): def __init__(self, string, color):
self.string = string self.string = string
self.color = color self.color = color
def convert(self, format): def convert(self, format):
color = self.color color = self.color
if format == "text": if format == "text":
return "" return ""
if _ctag_rgb.match(color) is not None: if _ctag_rgb.match(color) is not None:
if format=='ctag': if format == "ctag":
return "<c=%s>" % (color) return "<c=%s>" % (color)
try: try:
qc = QtGui.QColor(*[int(c) for c in color.split(",")]) qc = QtGui.QColor(*[int(c) for c in color.split(",")])
@ -105,14 +112,16 @@ class colorBegin(lexercon.Chunk):
if format == "html": if format == "html":
return '<span style="color:%s">' % (qc.name()) return '<span style="color:%s">' % (qc.name())
elif format == "bbcode": elif format == "bbcode":
return '[color=%s]' % (qc.name()) return "[color=%s]" % (qc.name())
elif format == "ctag": elif format == "ctag":
(r,g,b,a) = qc.getRgb() (r, g, b, a) = qc.getRgb()
return '<c=%s,%s,%s>' % (r,g,b) return "<c=%s,%s,%s>" % (r, g, b)
class colorEnd(lexercon.Chunk): class colorEnd(lexercon.Chunk):
def __init__(self, string): def __init__(self, string):
self.string = string self.string = string
def convert(self, format): def convert(self, format):
if format == "html": if format == "html":
return "</span>" return "</span>"
@ -123,10 +132,12 @@ class colorEnd(lexercon.Chunk):
else: else:
return self.string return self.string
class formatBegin(lexercon.Chunk): class formatBegin(lexercon.Chunk):
def __init__(self, string, ftype): def __init__(self, string, ftype):
self.string = string self.string = string
self.ftype = ftype self.ftype = ftype
def convert(self, format): def convert(self, format):
if format == "html": if format == "html":
return "<%s>" % (self.ftype) return "<%s>" % (self.ftype)
@ -137,10 +148,12 @@ class formatBegin(lexercon.Chunk):
else: else:
return self.string return self.string
class formatEnd(lexercon.Chunk): class formatEnd(lexercon.Chunk):
def __init__(self, string, ftype): def __init__(self, string, ftype):
self.string = string self.string = string
self.ftype = ftype self.ftype = ftype
def convert(self, format): def convert(self, format):
if format == "html": if format == "html":
return "</%s>" % (self.ftype) return "</%s>" % (self.ftype)
@ -151,9 +164,11 @@ class formatEnd(lexercon.Chunk):
else: else:
return self.string return self.string
class hyperlink(lexercon.Chunk): class hyperlink(lexercon.Chunk):
def __init__(self, string): def __init__(self, string):
self.string = string self.string = string
def convert(self, format): def convert(self, format):
if format == "html": if format == "html":
return "<a href='%s'>%s</a>" % (self.string, self.string) return "<a href='%s'>%s</a>" % (self.string, self.string)
@ -162,16 +177,20 @@ class hyperlink(lexercon.Chunk):
else: else:
return self.string return self.string
class hyperlink_lazy(hyperlink): class hyperlink_lazy(hyperlink):
"""Deprecated since it doesn't seem to turn the full url into a link, """Deprecated since it doesn't seem to turn the full url into a link,
probably not required anyway, best to require a protocol prefix.""" probably not required anyway, best to require a protocol prefix."""
def __init__(self, string): def __init__(self, string):
self.string = "http://" + string self.string = "http://" + string
class imagelink(lexercon.Chunk): class imagelink(lexercon.Chunk):
def __init__(self, string, img): def __init__(self, string, img):
self.string = string self.string = string
self.img = img self.img = img
def convert(self, format): def convert(self, format):
if format == "html": if format == "html":
return self.string return self.string
@ -183,84 +202,104 @@ class imagelink(lexercon.Chunk):
else: else:
return "" return ""
class memolex(lexercon.Chunk): class memolex(lexercon.Chunk):
def __init__(self, string, space, channel): def __init__(self, string, space, channel):
self.string = string self.string = string
self.space = space self.space = space
self.channel = channel self.channel = channel
def convert(self, format): def convert(self, format):
if format == "html": if format == "html":
return "%s<a href='%s'>%s</a>" % (self.space, self.channel, self.channel) return "%s<a href='%s'>%s</a>" % (self.space, self.channel, self.channel)
else: else:
return self.string return self.string
class chumhandlelex(lexercon.Chunk): class chumhandlelex(lexercon.Chunk):
def __init__(self, string, space, handle): def __init__(self, string, space, handle):
self.string = string self.string = string
self.space = space self.space = space
self.handle = handle self.handle = handle
def convert(self, format): def convert(self, format):
if format == "html": if format == "html":
return "%s<a href='%s'>%s</a>" % (self.space, self.handle, self.handle) return "%s<a href='%s'>%s</a>" % (self.space, self.handle, self.handle)
else: else:
return self.string return self.string
class smiley(lexercon.Chunk): class smiley(lexercon.Chunk):
def __init__(self, string): def __init__(self, string):
self.string = string self.string = string
def convert(self, format): def convert(self, format):
if format == "html": if format == "html":
return "<img src='smilies/%s' alt='%s' title='%s' />" % (smiledict[self.string], self.string, self.string) return "<img src='smilies/%s' alt='%s' title='%s' />" % (
smiledict[self.string],
self.string,
self.string,
)
else: else:
return self.string return self.string
class honker(lexercon.Chunk): class honker(lexercon.Chunk):
def __init__(self, string): def __init__(self, string):
self.string = string self.string = string
def convert(self, format): def convert(self, format):
# No more 'honk' turning into an emote because everyone hated that :') # No more 'honk' turning into an emote because everyone hated that :')
#if format == "html": # if format == "html":
# return "<img src='smilies/honk.png' alt'honk' title='honk' />" # return "<img src='smilies/honk.png' alt'honk' title='honk' />"
#else: # else:
return self.string return self.string
class mecmd(lexercon.Chunk): class mecmd(lexercon.Chunk):
def __init__(self, string, mecmd, suffix): def __init__(self, string, mecmd, suffix):
self.string = string self.string = string
self.suffix = suffix self.suffix = suffix
def convert(self, format): def convert(self, format):
return self.string return self.string
kxpclexer = lexercon.Pesterchum() kxpclexer = lexercon.Pesterchum()
def kxlexMsg(string): def kxlexMsg(string):
# Do a bit of sanitization. # Do a bit of sanitization.
msg = str(string) msg = str(string)
# TODO: Let people paste line-by-line normally. Maybe have a mass-paste # TODO: Let people paste line-by-line normally. Maybe have a mass-paste
# right-click option? # right-click option?
msg = msg.replace('\n', ' ').replace('\r', ' ') msg = msg.replace("\n", " ").replace("\r", " ")
# Something the original doesn't seem to have accounted for. # Something the original doesn't seem to have accounted for.
# Replace tabs with 4 spaces. # Replace tabs with 4 spaces.
msg = msg.replace('\t', ' ' * 4) msg = msg.replace("\t", " " * 4)
# Begin lexing. # Begin lexing.
msg = kxpclexer.lex(msg) msg = kxpclexer.lex(msg)
# ...and that's it for this. # ...and that's it for this.
return msg return msg
def lexMessage(string): def lexMessage(string):
lexlist = [(mecmd, _mecmdre), lexlist = [
(colorBegin, _ctag_begin), (colorBegin, _gtag_begin), (mecmd, _mecmdre),
(colorEnd, _ctag_end), (colorBegin, _ctag_begin),
# karxi: Disabled this for now. No common versions of Pesterchum (colorBegin, _gtag_begin),
# actually use it, save for Chumdroid...which shouldn't. (colorEnd, _ctag_end),
# When I change out parsers, I might add it back in. # karxi: Disabled this for now. No common versions of Pesterchum
##(formatBegin, _format_begin), (formatEnd, _format_end), # actually use it, save for Chumdroid...which shouldn't.
(imagelink, _imgre), # When I change out parsers, I might add it back in.
(hyperlink, _urlre), ##(formatBegin, _format_begin), (formatEnd, _format_end),
(memolex, _memore), (imagelink, _imgre),
(chumhandlelex, _handlere), (hyperlink, _urlre),
(smiley, _smilere), (memolex, _memore),
(honker, _honk)] (chumhandlelex, _handlere),
(smiley, _smilere),
(honker, _honk),
]
string = str(string) string = str(string)
string = string.replace("\n", " ").replace("\r", " ") string = string.replace("\n", " ").replace("\r", " ")
@ -282,14 +321,15 @@ def lexMessage(string):
else: else:
balanced.append(o) balanced.append(o)
if beginc > endc: if beginc > endc:
for i in range(0, beginc-endc): for i in range(0, beginc - endc):
balanced.append(colorEnd("</c>")) balanced.append(colorEnd("</c>"))
if len(balanced) == 0: if len(balanced) == 0:
balanced.append("") balanced.append("")
if type(balanced[len(balanced)-1]) not in [str, str]: if type(balanced[len(balanced) - 1]) not in [str, str]:
balanced.append("") balanced.append("")
return balanced return balanced
def convertTags(lexed, format="html"): def convertTags(lexed, format="html"):
if format not in ["html", "bbcode", "ctag", "text"]: if format not in ["html", "bbcode", "ctag", "text"]:
raise ValueError("Color format not recognized") raise ValueError("Color format not recognized")
@ -297,11 +337,13 @@ def convertTags(lexed, format="html"):
if type(lexed) in [str, str]: if type(lexed) in [str, str]:
lexed = lexMessage(lexed) lexed = lexMessage(lexed)
escaped = "" escaped = ""
#firststr = True # firststr = True
for (i, o) in enumerate(lexed): for (i, o) in enumerate(lexed):
if type(o) in [str, str]: if type(o) in [str, str]:
if format == "html": if format == "html":
escaped += o.replace("&", "&amp;").replace(">", "&gt;").replace("<","&lt;") escaped += (
o.replace("&", "&amp;").replace(">", "&gt;").replace("<", "&lt;")
)
else: else:
escaped += o escaped += o
else: else:
@ -309,6 +351,7 @@ def convertTags(lexed, format="html"):
return escaped return escaped
def _max_msg_len(mask=None, target=None, nick=None, ident=None): def _max_msg_len(mask=None, target=None, nick=None, ident=None):
# karxi: Copied from another file of mine, and modified to work with # karxi: Copied from another file of mine, and modified to work with
# Pesterchum. # Pesterchum.
@ -339,7 +382,7 @@ def _max_msg_len(mask=None, target=None, nick=None, ident=None):
# ident length # ident length
limit -= len(ident) if nick is not None else 10 limit -= len(ident) if nick is not None else 10
# Maximum (?) host length # Maximum (?) host length
limit -= 63 # RFC 2812 limit -= 63 # RFC 2812
# The target is the place this is getting sent to - a channel or a nick # The target is the place this is getting sent to - a channel or a nick
if target is not None: if target is not None:
limit -= len(target) limit -= len(target)
@ -351,6 +394,7 @@ def _max_msg_len(mask=None, target=None, nick=None, ident=None):
return limit return limit
def kxsplitMsg(lexed, ctx, fmt="pchum", maxlen=None, debug=False): def kxsplitMsg(lexed, ctx, fmt="pchum", maxlen=None, debug=False):
"""Split messages so that they don't go over the length limit. """Split messages so that they don't go over the length limit.
Returns a list of the messages, neatly split. Returns a list of the messages, neatly split.
@ -384,11 +428,21 @@ def kxsplitMsg(lexed, ctx, fmt="pchum", maxlen=None, debug=False):
curlen = 0 curlen = 0
# Maximum number of characters *to* use. # Maximum number of characters *to* use.
if not maxlen: if not maxlen:
maxlen = _max_msg_len(None, None, ctx.mainwindow.profile().handle, ctx.mainwindow.irc.cli.realname) maxlen = _max_msg_len(
None, None, ctx.mainwindow.profile().handle, ctx.mainwindow.irc.cli.realname
)
elif maxlen < 0: elif maxlen < 0:
# Subtract the (negative) length, giving us less leeway in this # Subtract the (negative) length, giving us less leeway in this
# function. # function.
maxlen = _max_msg_len(None, None, ctx.mainwindow.profile().handle, ctx.mainwindow.irc.cli.realname) + maxlen maxlen = (
_max_msg_len(
None,
None,
ctx.mainwindow.profile().handle,
ctx.mainwindow.irc.cli.realname,
)
+ maxlen
)
# Defined here, but modified in the loop. # Defined here, but modified in the loop.
msglen = 0 msglen = 0
@ -398,7 +452,7 @@ def kxsplitMsg(lexed, ctx, fmt="pchum", maxlen=None, debug=False):
tags that will be needed.""" tags that will be needed."""
return maxlen - curlen - (len(open_ctags) * 4) return maxlen - curlen - (len(open_ctags) * 4)
#safekeeping = lexed[:] # safekeeping = lexed[:]
lexed = collections.deque(lexed) lexed = collections.deque(lexed)
rounds = 0 rounds = 0
# NOTE: This entire mess is due for a rewrite. I'll start splitting it into # NOTE: This entire mess is due for a rewrite. I'll start splitting it into
@ -448,14 +502,14 @@ def kxsplitMsg(lexed, ctx, fmt="pchum", maxlen=None, debug=False):
# instead? # instead?
subround += 1 subround += 1
if debug: if debug:
PchumLog.info("[Splitting round {}-{}...]".format( PchumLog.info(
rounds, subround "[Splitting round {}-{}...]".format(rounds, subround)
)) )
point = msg.rfind(' ', 0, lenl) point = msg.rfind(" ", 0, lenl)
if point < 0: if point < 0:
# No spaces to break on...ugh. Break at the last space # No spaces to break on...ugh. Break at the last space
# we can instead. # we can instead.
point = lenl ## - 1 point = lenl ## - 1
# NOTE: The - 1 is for safety (but probably isn't # NOTE: The - 1 is for safety (but probably isn't
# actually necessary.) # actually necessary.)
# Split and push what we have. # Split and push what we have.
@ -509,16 +563,20 @@ def kxsplitMsg(lexed, ctx, fmt="pchum", maxlen=None, debug=False):
cte = lexercon.CTagEnd("</c>", fmt, None) cte = lexercon.CTagEnd("</c>", fmt, None)
working.extend([cte] * len(open_ctags)) working.extend([cte] * len(open_ctags))
if debug: if debug:
print("\tRound {0} linebreak: Added {1} closing ctags".format( print(
"\tRound {0} linebreak: Added {1} closing ctags".format(
rounds, len(open_ctags) rounds, len(open_ctags)
)) )
)
# Run it through the lexer again to render it. # Run it through the lexer again to render it.
working = ''.join(kxpclexer.list_convert(working)) working = "".join(kxpclexer.list_convert(working))
if debug: if debug:
print("\tRound {0} add: len == {1} (of {2})".format( print(
"\tRound {0} add: len == {1} (of {2})".format(
rounds, len(working), maxlen rounds, len(working), maxlen
)) )
)
# Now that it's done the work for us, append and resume. # Now that it's done the work for us, append and resume.
output.append(working) output.append(working)
@ -598,22 +656,19 @@ def kxsplitMsg(lexed, ctx, fmt="pchum", maxlen=None, debug=False):
if len(working) > 0: if len(working) > 0:
if debug: if debug:
print("Adding end trails: {!r}".format(working)) print("Adding end trails: {!r}".format(working))
working = ''.join(working) working = "".join(working)
output.append(working) output.append(working)
# We're...done? # We're...done?
return output return output
def _is_ooc(msg, strict=True): def _is_ooc(msg, strict=True):
"""Check if a line is OOC. Note that Pesterchum *is* kind enough to strip """Check if a line is OOC. Note that Pesterchum *is* kind enough to strip
trailing spaces for us, even in the older versions, but we don't do that in trailing spaces for us, even in the older versions, but we don't do that in
this function. (It's handled by the calling one.)""" this function. (It's handled by the calling one.)"""
# Define the matching braces. # Define the matching braces.
braces = ( braces = (("(", ")"), ("[", "]"), ("{", "}"))
('(', ')'),
('[', ']'),
('{', '}')
)
oocDetected = _oocre.match(msg) oocDetected = _oocre.match(msg)
# Somewhat-improved matching. # Somewhat-improved matching.
@ -630,6 +685,7 @@ def _is_ooc(msg, strict=True):
return True return True
return False return False
def kxhandleInput(ctx, text=None, flavor=None): def kxhandleInput(ctx, text=None, flavor=None):
"""The function that user input that should be sent to the server is routed """The function that user input that should be sent to the server is routed
through. Handles lexing, splitting, and quirk application, as well as through. Handles lexing, splitting, and quirk application, as well as
@ -707,7 +763,7 @@ def kxhandleInput(ctx, text=None, flavor=None):
msgbox.exec() msgbox.exec()
return return
PchumLog.info("--> recv \"%s\"" % msg) PchumLog.info('--> recv "%s"' % msg)
# karxi: We have a list...but I'm not sure if we ever get anything else, so # karxi: We have a list...but I'm not sure if we ever get anything else, so
# best to play it safe. I may remove this during later refactoring. # best to play it safe. I may remove this during later refactoring.
if isinstance(msg, list): if isinstance(msg, list):
@ -718,23 +774,21 @@ def kxhandleInput(ctx, text=None, flavor=None):
# an object type I provided - just so I could pluck them out # an object type I provided - just so I could pluck them out
# later. # later.
msg[i] = m.convert(format="ctag") msg[i] = m.convert(format="ctag")
msg = ''.join(msg) msg = "".join(msg)
# Quirks have been applied. Lex the messages (finally). # Quirks have been applied. Lex the messages (finally).
msg = kxlexMsg(msg) msg = kxlexMsg(msg)
# Debug output. # Debug output.
#try: # try:
# print(repr(msg)) # print(repr(msg))
#except Exception as err: # except Exception as err:
# print("(Couldn't print lexed message: {!s})".format(err)) # print("(Couldn't print lexed message: {!s})".format(err))
# Remove coloring if this is a /me! # Remove coloring if this is a /me!
if is_action: if is_action:
# Filter out formatting specifiers (just ctags, at the moment). # Filter out formatting specifiers (just ctags, at the moment).
msg = [m for m in msg if not isinstance(m, msg = [m for m in msg if not isinstance(m, (lexercon.CTag, lexercon.CTagEnd))]
(lexercon.CTag, lexercon.CTagEnd)
)]
# We'll also add /me to the beginning of any new messages, later. # We'll also add /me to the beginning of any new messages, later.
# Put what's necessary in before splitting. # Put what's necessary in before splitting.
@ -752,9 +806,11 @@ def kxhandleInput(ctx, text=None, flavor=None):
# We'll use those later. # We'll use those later.
# Split the messages so we don't go over the buffer and lose text. # Split the messages so we don't go over the buffer and lose text.
maxlen = _max_msg_len(None, None, ctx.mainwindow.profile().handle, ctx.mainwindow.irc.cli.realname) maxlen = _max_msg_len(
# ctx.mainwindow.profile().handle ==> Get handle None, None, ctx.mainwindow.profile().handle, ctx.mainwindow.irc.cli.realname
# ctx.mainwindow.irc.cli.realname ==> Get ident (Same as realname in this case.) )
# ctx.mainwindow.profile().handle ==> Get handle
# ctx.mainwindow.irc.cli.realname ==> Get ident (Same as realname in this case.)
# Since we have to do some post-processing, we need to adjust the maximum # Since we have to do some post-processing, we need to adjust the maximum
# length we can use. # length we can use.
if flavor == "convo": if flavor == "convo":
@ -775,10 +831,8 @@ def kxhandleInput(ctx, text=None, flavor=None):
# Pester message handling. # Pester message handling.
if flavor == "convo": if flavor == "convo":
# if ceased, rebegin # if ceased, rebegin
if hasattr(ctx, 'chumopen') and not ctx.chumopen: if hasattr(ctx, "chumopen") and not ctx.chumopen:
ctx.mainwindow.newConvoStarted.emit( ctx.mainwindow.newConvoStarted.emit(QString(ctx.title()), True)
QString(ctx.title()), True
)
ctx.setChumOpen(True) ctx.setChumOpen(True)
# Post-process and send the messages. # Post-process and send the messages.
@ -804,11 +858,10 @@ def kxhandleInput(ctx, text=None, flavor=None):
# construct the messages. # construct the messages.
clientMsg = "<c={1}>{2}{3}{4}: {0}</c>".format( clientMsg = "<c={1}>{2}{3}{4}: {0}</c>".format(
clientMsg, colorcmd, grammar.pcf, initials, grammar.number clientMsg, colorcmd, grammar.pcf, initials, grammar.number
) )
# Not sure if this needs a space at the end...? # Not sure if this needs a space at the end...?
serverMsg = "<c={1}>{2}: {0}</c>".format( serverMsg = "<c={1}>{2}: {0}</c>".format(serverMsg, colorcmd, initials)
serverMsg, colorcmd, initials)
ctx.addMessage(clientMsg, True) ctx.addMessage(clientMsg, True)
if flavor != "menus": if flavor != "menus":
@ -825,7 +878,14 @@ def addTimeInitial(string, grammar):
# support Doc Scratch mode # support Doc Scratch mode
if (endoftag < 0 or endoftag > 16) or (endofi < 0 or endofi > 17): if (endoftag < 0 or endoftag > 16) or (endofi < 0 or endofi > 17):
return string return string
return string[0:endoftag+1]+grammar.pcf+string[endoftag+1:endofi]+grammar.number+string[endofi:] return (
string[0 : endoftag + 1]
+ grammar.pcf
+ string[endoftag + 1 : endofi]
+ grammar.number
+ string[endofi:]
)
def timeProtocol(cmd): def timeProtocol(cmd):
dir = cmd[0] dir = cmd[0]
@ -836,31 +896,32 @@ def timeProtocol(cmd):
try: try:
l = [int(x) for x in cmd.split(":")] l = [int(x) for x in cmd.split(":")]
except ValueError: except ValueError:
l = [0,0] l = [0, 0]
timed = timedelta(0, l[0]*3600+l[1]*60) timed = timedelta(0, l[0] * 3600 + l[1] * 60)
if dir == "P": if dir == "P":
timed = timed*-1 timed = timed * -1
return timed return timed
def timeDifference(td): def timeDifference(td):
if td == timedelta(microseconds=1): # mysteryTime replacement :( if td == timedelta(microseconds=1): # mysteryTime replacement :(
return "??:?? FROM ????" return "??:?? FROM ????"
if td < timedelta(0): if td < timedelta(0):
when = "AGO" when = "AGO"
else: else:
when = "FROM NOW" when = "FROM NOW"
atd = abs(td) atd = abs(td)
minutes = (atd.days*86400 + atd.seconds) // 60 minutes = (atd.days * 86400 + atd.seconds) // 60
hours = minutes // 60 hours = minutes // 60
leftoverminutes = minutes % 60 leftoverminutes = minutes % 60
if atd == timedelta(0): if atd == timedelta(0):
timetext = "RIGHT NOW" timetext = "RIGHT NOW"
elif atd < timedelta(0,3600): elif atd < timedelta(0, 3600):
if minutes == 1: if minutes == 1:
timetext = "%d MINUTE %s" % (minutes, when) timetext = "%d MINUTE %s" % (minutes, when)
else: else:
timetext = "%d MINUTES %s" % (minutes, when) timetext = "%d MINUTES %s" % (minutes, when)
elif atd < timedelta(0,3600*100): elif atd < timedelta(0, 3600 * 100):
if hours == 1 and leftoverminutes == 0: if hours == 1 and leftoverminutes == 0:
timetext = "%d:%02d HOUR %s" % (hours, leftoverminutes, when) timetext = "%d:%02d HOUR %s" % (hours, leftoverminutes, when)
else: else:
@ -869,16 +930,20 @@ def timeDifference(td):
timetext = "%d HOURS %s" % (hours, when) timetext = "%d HOURS %s" % (hours, when)
return timetext return timetext
def nonerep(text): def nonerep(text):
return text return text
class parseLeaf(object): class parseLeaf(object):
def __init__(self, function, parent): def __init__(self, function, parent):
self.nodes = [] self.nodes = []
self.function = function self.function = function
self.parent = parent self.parent = parent
def append(self, node): def append(self, node):
self.nodes.append(node) self.nodes.append(node)
def expand(self, mo): def expand(self, mo):
out = "" out = ""
for n in self.nodes: for n in self.nodes:
@ -891,12 +956,15 @@ class parseLeaf(object):
out = self.function(out) out = self.function(out)
return out return out
class backreference(object): class backreference(object):
def __init__(self, number): def __init__(self, number):
self.number = number self.number = number
def __str__(self): def __str__(self):
return self.number return self.number
def parseRegexpFunctions(to): def parseRegexpFunctions(to):
parsed = parseLeaf(nonerep, None) parsed = parseLeaf(nonerep, None)
current = parsed current = parsed
@ -907,7 +975,7 @@ def parseRegexpFunctions(to):
mo = _functionre.search(tmp) mo = _functionre.search(tmp)
if mo is not None: if mo is not None:
if mo.start() > 0: if mo.start() > 0:
current.append(to[curi:curi+mo.start()]) current.append(to[curi : curi + mo.start()])
backr = _groupre.search(mo.group()) backr = _groupre.search(mo.group())
if backr is not None: if backr is not None:
current.append(backreference(backr.group(1))) current.append(backreference(backr.group(1)))
@ -920,7 +988,7 @@ def parseRegexpFunctions(to):
current = current.parent current = current.parent
else: else:
current.append(")") current.append(")")
curi = mo.end()+curi curi = mo.end() + curi
else: else:
current.append(to[curi:]) current.append(to[curi:])
curi = len(to) curi = len(to)
@ -929,11 +997,14 @@ def parseRegexpFunctions(to):
def img2smiley(string): def img2smiley(string):
string = str(string) string = str(string)
def imagerep(mo): def imagerep(mo):
return reverse_smiley[mo.group(1)] return reverse_smiley[mo.group(1)]
string = re.sub(r'<img src="smilies/(\S+)" />', imagerep, string) string = re.sub(r'<img src="smilies/(\S+)" />', imagerep, string)
return string return string
smiledict = { smiledict = {
":rancorous:": "pc_rancorous.png", ":rancorous:": "pc_rancorous.png",
":apple:": "apple.png", ":apple:": "apple.png",
@ -998,78 +1069,166 @@ smiledict = {
":scorpio:": "scorpio.gif", ":scorpio:": "scorpio.gif",
":shades:": "shades.png", ":shades:": "shades.png",
":honk:": "honk.png", ":honk:": "honk.png",
} }
reverse_smiley = dict((v,k) for k, v in smiledict.items()) reverse_smiley = dict((v, k) for k, v in smiledict.items())
_smilere = re.compile("|".join(list(smiledict.keys()))) _smilere = re.compile("|".join(list(smiledict.keys())))
class ThemeException(Exception): class ThemeException(Exception):
def __init__(self, value): def __init__(self, value):
self.parameter = value self.parameter = value
def __str__(self): def __str__(self):
return repr(self.parameter) return repr(self.parameter)
def themeChecker(theme): def themeChecker(theme):
needs = ["main/size", "main/icon", "main/windowtitle", "main/style", \ needs = [
"main/background-image", "main/menubar/style", "main/menu/menuitem", \ "main/size",
"main/menu/style", "main/menu/selected", "main/close/image", \ "main/icon",
"main/close/loc", "main/minimize/image", "main/minimize/loc", \ "main/windowtitle",
"main/menu/loc", "main/menus/client/logviewer", \ "main/style",
"main/menus/client/addgroup", "main/menus/client/options", \ "main/background-image",
"main/menus/client/exit", "main/menus/client/userlist", \ "main/menubar/style",
"main/menus/client/memos", "main/menus/client/import", \ "main/menu/menuitem",
"main/menus/client/idle", "main/menus/client/reconnect", \ "main/menu/style",
"main/menus/client/_name", "main/menus/profile/quirks", \ "main/menu/selected",
"main/menus/profile/block", "main/menus/profile/color", \ "main/close/image",
"main/menus/profile/switch", "main/menus/profile/_name", \ "main/close/loc",
"main/menus/help/about", "main/menus/help/_name", "main/moodlabel/text", \ "main/minimize/image",
"main/moodlabel/loc", "main/moodlabel/style", "main/moods", \ "main/minimize/loc",
"main/addchum/style", "main/addchum/text", "main/addchum/size", \ "main/menu/loc",
"main/addchum/loc", "main/pester/text", "main/pester/size", \ "main/menus/client/logviewer",
"main/pester/loc", "main/block/text", "main/block/size", "main/block/loc", \ "main/menus/client/addgroup",
"main/mychumhandle/label/text", "main/mychumhandle/label/loc", \ "main/menus/client/options",
"main/mychumhandle/label/style", "main/mychumhandle/handle/loc", \ "main/menus/client/exit",
"main/mychumhandle/handle/size", "main/mychumhandle/handle/style", \ "main/menus/client/userlist",
"main/mychumhandle/colorswatch/size", "main/mychumhandle/colorswatch/loc", \ "main/menus/client/memos",
"main/defaultmood", "main/chums/size", "main/chums/loc", \ "main/menus/client/import",
"main/chums/style", "main/menus/rclickchumlist/pester", \ "main/menus/client/idle",
"main/menus/rclickchumlist/removechum", \ "main/menus/client/reconnect",
"main/menus/rclickchumlist/blockchum", "main/menus/rclickchumlist/viewlog", \ "main/menus/client/_name",
"main/menus/rclickchumlist/removegroup", \ "main/menus/profile/quirks",
"main/menus/rclickchumlist/renamegroup", \ "main/menus/profile/block",
"main/menus/rclickchumlist/movechum", "convo/size", \ "main/menus/profile/color",
"convo/tabwindow/style", "convo/tabs/tabstyle", "convo/tabs/style", \ "main/menus/profile/switch",
"convo/tabs/selectedstyle", "convo/style", "convo/margins", \ "main/menus/profile/_name",
"convo/chumlabel/text", "convo/chumlabel/style", "convo/chumlabel/align/h", \ "main/menus/help/about",
"convo/chumlabel/align/v", "convo/chumlabel/maxheight", \ "main/menus/help/_name",
"convo/chumlabel/minheight", "main/menus/rclickchumlist/quirksoff", \ "main/moodlabel/text",
"main/menus/rclickchumlist/addchum", "main/menus/rclickchumlist/blockchum", \ "main/moodlabel/loc",
"main/menus/rclickchumlist/unblockchum", \ "main/moodlabel/style",
"main/menus/rclickchumlist/viewlog", "main/trollslum/size", \ "main/moods",
"main/trollslum/style", "main/trollslum/label/text", \ "main/addchum/style",
"main/trollslum/label/style", "main/menus/profile/block", \ "main/addchum/text",
"main/chums/moods/blocked/icon", "convo/systemMsgColor", \ "main/addchum/size",
"convo/textarea/style", "convo/text/beganpester", "convo/text/ceasepester", \ "main/addchum/loc",
"convo/text/blocked", "convo/text/unblocked", "convo/text/blockedmsg", \ "main/pester/text",
"convo/text/idle", "convo/input/style", "memos/memoicon", \ "main/pester/size",
"memos/textarea/style", "memos/systemMsgColor", "convo/text/joinmemo", \ "main/pester/loc",
"memos/input/style", "main/menus/rclickchumlist/banuser", \ "main/block/text",
"main/menus/rclickchumlist/opuser", "main/menus/rclickchumlist/voiceuser", \ "main/block/size",
"memos/margins", "convo/text/openmemo", "memos/size", "memos/style", \ "main/block/loc",
"memos/label/text", "memos/label/style", "memos/label/align/h", \ "main/mychumhandle/label/text",
"memos/label/align/v", "memos/label/maxheight", "memos/label/minheight", \ "main/mychumhandle/label/loc",
"memos/userlist/style", "memos/userlist/width", "memos/time/text/width", \ "main/mychumhandle/label/style",
"memos/time/text/style", "memos/time/arrows/left", \ "main/mychumhandle/handle/loc",
"memos/time/arrows/style", "memos/time/buttons/style", \ "main/mychumhandle/handle/size",
"memos/time/arrows/right", "memos/op/icon", "memos/voice/icon", \ "main/mychumhandle/handle/style",
"convo/text/closememo", "convo/text/kickedmemo", \ "main/mychumhandle/colorswatch/size",
"main/chums/userlistcolor", "main/defaultwindow/style", \ "main/mychumhandle/colorswatch/loc",
"main/chums/moods", "main/chums/moods/chummy/icon", "main/menus/help/help", \ "main/defaultmood",
"main/menus/help/calsprite", "main/menus/help/nickserv", "main/menus/help/chanserv", \ "main/chums/size",
"main/menus/rclickchumlist/invitechum", "main/menus/client/randen", \ "main/chums/loc",
"main/menus/rclickchumlist/memosetting", "main/menus/rclickchumlist/memonoquirk", \ "main/chums/style",
"main/menus/rclickchumlist/memohidden", "main/menus/rclickchumlist/memoinvite", \ "main/menus/rclickchumlist/pester",
"main/menus/rclickchumlist/memomute", "main/menus/rclickchumlist/notes"] "main/menus/rclickchumlist/removechum",
"main/menus/rclickchumlist/blockchum",
"main/menus/rclickchumlist/viewlog",
"main/menus/rclickchumlist/removegroup",
"main/menus/rclickchumlist/renamegroup",
"main/menus/rclickchumlist/movechum",
"convo/size",
"convo/tabwindow/style",
"convo/tabs/tabstyle",
"convo/tabs/style",
"convo/tabs/selectedstyle",
"convo/style",
"convo/margins",
"convo/chumlabel/text",
"convo/chumlabel/style",
"convo/chumlabel/align/h",
"convo/chumlabel/align/v",
"convo/chumlabel/maxheight",
"convo/chumlabel/minheight",
"main/menus/rclickchumlist/quirksoff",
"main/menus/rclickchumlist/addchum",
"main/menus/rclickchumlist/blockchum",
"main/menus/rclickchumlist/unblockchum",
"main/menus/rclickchumlist/viewlog",
"main/trollslum/size",
"main/trollslum/style",
"main/trollslum/label/text",
"main/trollslum/label/style",
"main/menus/profile/block",
"main/chums/moods/blocked/icon",
"convo/systemMsgColor",
"convo/textarea/style",
"convo/text/beganpester",
"convo/text/ceasepester",
"convo/text/blocked",
"convo/text/unblocked",
"convo/text/blockedmsg",
"convo/text/idle",
"convo/input/style",
"memos/memoicon",
"memos/textarea/style",
"memos/systemMsgColor",
"convo/text/joinmemo",
"memos/input/style",
"main/menus/rclickchumlist/banuser",
"main/menus/rclickchumlist/opuser",
"main/menus/rclickchumlist/voiceuser",
"memos/margins",
"convo/text/openmemo",
"memos/size",
"memos/style",
"memos/label/text",
"memos/label/style",
"memos/label/align/h",
"memos/label/align/v",
"memos/label/maxheight",
"memos/label/minheight",
"memos/userlist/style",
"memos/userlist/width",
"memos/time/text/width",
"memos/time/text/style",
"memos/time/arrows/left",
"memos/time/arrows/style",
"memos/time/buttons/style",
"memos/time/arrows/right",
"memos/op/icon",
"memos/voice/icon",
"convo/text/closememo",
"convo/text/kickedmemo",
"main/chums/userlistcolor",
"main/defaultwindow/style",
"main/chums/moods",
"main/chums/moods/chummy/icon",
"main/menus/help/help",
"main/menus/help/calsprite",
"main/menus/help/nickserv",
"main/menus/help/chanserv",
"main/menus/rclickchumlist/invitechum",
"main/menus/client/randen",
"main/menus/rclickchumlist/memosetting",
"main/menus/rclickchumlist/memonoquirk",
"main/menus/rclickchumlist/memohidden",
"main/menus/rclickchumlist/memoinvite",
"main/menus/rclickchumlist/memomute",
"main/menus/rclickchumlist/notes",
]
for n in needs: for n in needs:
try: try:

File diff suppressed because it is too large Load diff

View file

@ -9,24 +9,26 @@ class AttrDict(dict):
attributes are handled as they would be normally, and will not be attributes are handled as they would be normally, and will not be
overwritten. overwritten.
Overload _is_reserved if you want to change this.""" Overload _is_reserved if you want to change this."""
def __init__(self, init={}): def __init__(self, init={}):
super(AttrDict, self).__init__(init) super(AttrDict, self).__init__(init)
def __getstate__(self): def __getstate__(self):
return list(self.__dict__.items()) return list(self.__dict__.items())
def __setstate__(self, items): def __setstate__(self, items):
for key, val in items: self.__dict__[key] = val for key, val in items:
self.__dict__[key] = val
def __repr__(self): def __repr__(self):
return "{0}({1})".format( return "{0}({1})".format(type(self).__name__, super(AttrDict, self).__repr__())
type(self).__name__,
super(AttrDict, self).__repr__()
)
def __setitem__(self, name, value): def __setitem__(self, name, value):
return super(AttrDict, self).__setitem__(name, value) return super(AttrDict, self).__setitem__(name, value)
def __getitem__(self, name): def __getitem__(self, name):
return super(AttrDict, self).__getitem__(name) return super(AttrDict, self).__getitem__(name)
def __delitem__(self, name): def __delitem__(self, name):
return super(AttrDict, self).__delitem__(name) return super(AttrDict, self).__delitem__(name)
@ -78,7 +80,8 @@ class AttrDict(dict):
# See __setattr__. # See __setattr__.
return object.__delattr__(self, name) return object.__delattr__(self, name)
else: else:
try: del self[name] try:
del self[name]
except KeyError as err: except KeyError as err:
raise AttributeError(str(err)) raise AttributeError(str(err))
@ -86,13 +89,18 @@ class AttrDict(dict):
def _is_reserved(name): def _is_reserved(name):
"""Check if an attribute name is reserved for system use.""" """Check if an attribute name is reserved for system use."""
# A very simple method. # A very simple method.
result = name[:2] == name[-2:] == '__' result = name[:2] == name[-2:] == "__"
return result return result
def copy(self): return type(self)(self) def copy(self):
return type(self)(self)
__copy__ = copy __copy__ = copy
## end of http://code.activestate.com/recipes/473786/ }}} ## end of http://code.activestate.com/recipes/473786/ }}}
class DefAttrDict(AttrDict): class DefAttrDict(AttrDict):
default_factory = None default_factory = None
@ -106,8 +114,8 @@ class DefAttrDict(AttrDict):
self.default_factory, self.default_factory,
# We skip normal processing here, since AttrDict provides basic # We skip normal processing here, since AttrDict provides basic
# repr for classes in general, which we don't want. # repr for classes in general, which we don't want.
dict.__repr__(self) dict.__repr__(self),
) )
def __getitem__(self, name): def __getitem__(self, name):
try: try:
@ -130,7 +138,10 @@ class DefAttrDict(AttrDict):
raise raise
return result return result
def copy(self): return type(self)(self.default_factory, self) def copy(self):
return type(self)(self.default_factory, self)
__copy__ = copy __copy__ = copy
# vim: set autoindent ts=4 sts=4 sw=4 textwidth=79 expandtab: # vim: set autoindent ts=4 sts=4 sw=4 textwidth=79 expandtab:

View file

@ -20,7 +20,6 @@ except NameError:
# function appropriate to the given format - e.g. CTag.convert_pchum. # function appropriate to the given format - e.g. CTag.convert_pchum.
class Lexeme(object): class Lexeme(object):
def __init__(self, string, origin): def __init__(self, string, origin):
# The 'string' property is just what it came from; the original # The 'string' property is just what it came from; the original
@ -28,21 +27,26 @@ class Lexeme(object):
# shouldn't be. # shouldn't be.
self.string = string self.string = string
self.origin = origin self.origin = origin
def __str__(self): def __str__(self):
##return self.string ##return self.string
return self.convert(self.origin) return self.convert(self.origin)
def __len__(self): def __len__(self):
##return len(self.string) ##return len(self.string)
return len(str(self)) return len(str(self))
def convert(self, format): def convert(self, format):
# This is supposed to be overwritten by subclasses # This is supposed to be overwritten by subclasses
raise NotImplementedError raise NotImplementedError
def rebuild(self, format): def rebuild(self, format):
"""Builds a copy of the owning Lexeme as if it had 'come from' a """Builds a copy of the owning Lexeme as if it had 'come from' a
different original format, and returns the result.""" different original format, and returns the result."""
# TODO: This. Need to decide whether overloading will be required for # TODO: This. Need to decide whether overloading will be required for
# nearly every single subclass.... # nearly every single subclass....
raise NotImplementedError raise NotImplementedError
@classmethod @classmethod
def from_mo(cls, mo, origin): def from_mo(cls, mo, origin):
raise NotImplementedError raise NotImplementedError
@ -52,6 +56,7 @@ class Message(Lexeme):
"""An object designed to represent a message, possibly containing Lexeme """An object designed to represent a message, possibly containing Lexeme
objects in their native form as well. Intended to be a combination of a objects in their native form as well. Intended to be a combination of a
list and a string, combining the former with the latter's methods.""" list and a string, combining the former with the latter's methods."""
def __init__(self, contents, origin): def __init__(self, contents, origin):
lexer = Lexer.lexer_for(origin)() lexer = Lexer.lexer_for(origin)()
working = lexer.lex(contents) working = lexer.lex(contents)
@ -69,7 +74,8 @@ class Message(Lexeme):
working[i] = elt working[i] = elt
self.origin = origin self.origin = origin
self.contents = working self.contents = working
self.string = ''.join(lexer.list_convert(working)) self.string = "".join(lexer.list_convert(working))
# TODO: Finish all the rest of this. # TODO: Finish all the rest of this.
@ -81,40 +87,51 @@ class Specifier(Lexeme):
# If this form has a more compact form, use it # If this form has a more compact form, use it
compact = False compact = False
# Made so that certain odd message-ish things have a place to go. May have its # Made so that certain odd message-ish things have a place to go. May have its
# class changed later. # class changed later.
class Chunk(Specifier): class Chunk(Specifier):
pass pass
class FTag(Specifier): class FTag(Specifier):
pass pass
class CTag(Specifier): class CTag(Specifier):
"""Denotes the beginning or end of a color change.""" """Denotes the beginning or end of a color change."""
sets_color = True sets_color = True
def __init__(self, string, origin, color): def __init__(self, string, origin, color):
super(CTag, self).__init__(string, origin) super(CTag, self).__init__(string, origin)
# So we can also have None # So we can also have None
if isinstance(color, tuple): if isinstance(color, tuple):
if len(color) < 2: raise ValueError if len(color) < 2:
raise ValueError
self.color, self.bg_color = color[:2] self.color, self.bg_color = color[:2]
else: else:
self.color = color self.color = color
self.bg_color = None self.bg_color = None
def has_color(self): def has_color(self):
if self.color is not None or self.bg_color is not None: if self.color is not None or self.bg_color is not None:
return True return True
return False return False
def convert(self, format): def convert(self, format):
text = '' text = ""
color = self.color color = self.color
bg = self.bg_color bg = self.bg_color
if format == "irc": if format == "irc":
# Put in the control character for a color code. # Put in the control character for a color code.
text = '\x03' text = "\x03"
if color: if color:
text += color.ccode text += color.ccode
if bg: text += ',' + bg.ccode if bg:
elif bg: text += "99," + bg.ccode text += "," + bg.ccode
elif bg:
text += "99," + bg.ccode
elif format == "pchum": elif format == "pchum":
if not color: if not color:
text = "</c>" text = "</c>"
@ -138,7 +155,7 @@ class CTag(Specifier):
# far; use it. # far; use it.
text = hxs text = hxs
elif format == "plaintext": elif format == "plaintext":
text = '' text = ""
return text return text
@classmethod @classmethod
@ -147,10 +164,14 @@ class CTag(Specifier):
if origin == "irc": if origin == "irc":
text = mo.group() text = mo.group()
fg, bg = mo.groups() fg, bg = mo.groups()
try: fg = Color('\x03' + fg) try:
except: fg = None fg = Color("\x03" + fg)
try: bg = Color('\x03' + bg) except:
except: bg = None fg = None
try:
bg = Color("\x03" + bg)
except:
bg = None
inst = cls(text, origin, color=(fg, bg)) inst = cls(text, origin, color=(fg, bg))
elif origin == "pchum": elif origin == "pchum":
text = mo.group() text = mo.group()
@ -168,32 +189,49 @@ class CTag(Specifier):
except: except:
pass pass
return inst return inst
class CTagEnd(CTag): class CTagEnd(CTag):
# TODO: Make this a separate class - NOT a subclass of CTag like it is at # TODO: Make this a separate class - NOT a subclass of CTag like it is at
# present # present
resets_color = True resets_color = True
def convert(self, format): def convert(self, format):
text = '' text = ""
if format == "irc": return '\x03' if format == "irc":
elif format == "pchum": return "</c>" return "\x03"
elif format == "plaintext": return '' elif format == "pchum":
return "</c>"
elif format == "plaintext":
return ""
return text return text
def has_color(self): return False
def has_color(self):
return False
@classmethod @classmethod
def from_mo(cls, mo, origin): def from_mo(cls, mo, origin):
# Turns the whole match into it (for now) # Turns the whole match into it (for now)
return cls(mo.group(), origin, color=None) return cls(mo.group(), origin, color=None)
class LineColor(CTag): class LineColor(CTag):
pass pass
class LineColorEnd(CTagEnd): class LineColorEnd(CTagEnd):
pass pass
class FTagEnd(Specifier): class FTagEnd(Specifier):
resets_formatting = True resets_formatting = True
class ResetTag(CTagEnd, FTagEnd): class ResetTag(CTagEnd, FTagEnd):
def convert(self, format): def convert(self, format):
text = '' text = ""
if format == "irc": return '\x0F' if format == "irc":
return "\x0F"
elif format == "pchum": elif format == "pchum":
# Later on, this one is going to be infuriatingly tricky. # Later on, this one is going to be infuriatingly tricky.
# Supporting things like bold and so on doesn't really allow for an # Supporting things like bold and so on doesn't really allow for an
@ -201,14 +239,18 @@ class ResetTag(CTagEnd, FTagEnd):
# I *could* implement it, and it wouldn't be too hard, but it would # I *could* implement it, and it wouldn't be too hard, but it would
# probably confuse more people than it helped. # probably confuse more people than it helped.
return "</c>" return "</c>"
elif format == "plaintext": return '' elif format == "plaintext":
return ""
return text return text
class SpecifierEnd(CTagEnd, FTagEnd): class SpecifierEnd(CTagEnd, FTagEnd):
# This might not ever even be used, but you never know.... # This might not ever even be used, but you never know....
# If it does, we may need properties such as .resets_color, .resets_bold, # If it does, we may need properties such as .resets_color, .resets_bold,
# and so on and so forth # and so on and so forth
pass pass
# TODO: Consider using a metaclass to check those properties - e.g. if # TODO: Consider using a metaclass to check those properties - e.g. if
# a class .sets_color and a subclass .resets_color, set the subclass's # a class .sets_color and a subclass .resets_color, set the subclass's
# .sets_color to False # .sets_color to False
@ -218,9 +260,12 @@ class Lexer(object):
# Subclasses need to supply a ref themselves # Subclasses need to supply a ref themselves
ref = None ref = None
compress_tags = False compress_tags = False
def breakdown(self, string, objlist): def breakdown(self, string, objlist):
if not isinstance(string, basestr): msglist = string if not isinstance(string, basestr):
else: msglist = [string] msglist = string
else:
msglist = [string]
for obj, rxp in objlist: for obj, rxp in objlist:
working = [] working = []
for i, msg in enumerate(msglist): for i, msg in enumerate(msglist):
@ -248,12 +293,14 @@ class Lexer(object):
# Exchange the old list with the processed one, and continue # Exchange the old list with the processed one, and continue
msglist = working msglist = working
return msglist return msglist
def lex(self, string): def lex(self, string):
# Needs to be implemented by subclasses # Needs to be implemented by subclasses
return self.breakdown(string, []) return self.breakdown(string, [])
def list_convert(self, target, format=None): def list_convert(self, target, format=None):
if format is None: format = self.ref if format is None:
format = self.ref
converted = [] converted = []
for elt in target: for elt in target:
@ -266,6 +313,7 @@ class Lexer(object):
converted.append(elt) converted.append(elt)
return converted return converted
class Pesterchum(Lexer): class Pesterchum(Lexer):
ref = "pchum" ref = "pchum"
_ctag_begin = re.compile(r"<c=(.*?)>", flags=re.I) _ctag_begin = re.compile(r"<c=(.*?)>", flags=re.I)
@ -281,11 +329,11 @@ class Pesterchum(Lexer):
def lex(self, string): def lex(self, string):
lexlist = [ lexlist = [
##(mecmd, self._mecmdre), ##(mecmd, self._mecmdre),
(CTag, self._ctag_begin), (CTag, self._ctag_begin),
##(CTag, self._ctag_end) ##(CTag, self._ctag_end)
(CTagEnd, self._ctag_end) (CTagEnd, self._ctag_end),
] ]
lexed = self.breakdown(string, lexlist) lexed = self.breakdown(string, lexlist)
@ -324,7 +372,7 @@ class Pesterchum(Lexer):
## balanced.append(o) ## balanced.append(o)
# This will need to be re-evaluated to support the line end lexeme/etc. # This will need to be re-evaluated to support the line end lexeme/etc.
if beginc > endc: if beginc > endc:
for i in range(0, beginc-endc): for i in range(0, beginc - endc):
##balanced.append(colorEnd("</c>")) ##balanced.append(colorEnd("</c>"))
balanced.append(CTagEnd("</c>", self.ref, None)) balanced.append(CTagEnd("</c>", self.ref, None))
return balanced return balanced
@ -332,12 +380,15 @@ class Pesterchum(Lexer):
# TODO: Let us contextually set compression here or something, ugh. If # TODO: Let us contextually set compression here or something, ugh. If
# 'None' assume the self-set one. # 'None' assume the self-set one.
def list_convert(self, target, format=None): def list_convert(self, target, format=None):
if format is None: format = self.ref if format is None:
format = self.ref
converted = [] converted = []
cstack = [] cstack = []
##closecolor = lambda: converted.append(CTagEnd("</c>", self.ref, None)) ##closecolor = lambda: converted.append(CTagEnd("</c>", self.ref, None))
closecolor = lambda: converted.append(CTagEnd("</c>", self.ref, None).convert(format)) closecolor = lambda: converted.append(
CTagEnd("</c>", self.ref, None).convert(format)
)
for elt in target: for elt in target:
if isinstance(elt, LineColorEnd): if isinstance(elt, LineColorEnd):
@ -422,6 +473,7 @@ class Pesterchum(Lexer):
converted.append(elt) converted.append(elt)
return converted return converted
class RelayChat(Lexer): class RelayChat(Lexer):
ref = "irc" ref = "irc"
# This could use some cleaning up later, but it'll work for now, hopefully # This could use some cleaning up later, but it'll work for now, hopefully
@ -435,8 +487,8 @@ class RelayChat(Lexer):
lexlist = [ lexlist = [
(CTag, self._ccode_rxp), (CTag, self._ccode_rxp),
(CTagEnd, self._ccode_end_rxp), (CTagEnd, self._ccode_end_rxp),
(ResetTag, self._reset_rxp) (ResetTag, self._reset_rxp),
] ]
lexed = self.breakdown(string, lexlist) lexed = self.breakdown(string, lexlist)
@ -444,7 +496,8 @@ class RelayChat(Lexer):
return lexed return lexed
def list_convert(self, target, format=None): def list_convert(self, target, format=None):
if format is None: format = self.ref if format is None:
format = self.ref
converted = [] converted = []
cstack = [] cstack = []
@ -525,98 +578,99 @@ class RelayChat(Lexer):
return converted return converted
def _list_convert_new(self, target, format=None): def _list_convert_new(self, target, format=None):
if format is None: format = self.ref if format is None:
converted = [] format = self.ref
cstack = [] converted = []
cstack = []
for elt in target: for elt in target:
if isinstance(elt, LineColorEnd): if isinstance(elt, LineColorEnd):
# Go down the stack until we have a line color TO end # Go down the stack until we have a line color TO end
while cstack: while cstack:
# Add a </c> since we'll need one anyway # Add a </c> since we'll need one anyway
# Is closecolor accessible here?
try:
closecolor()
except Exception as e:
print(e)
##if isinstance(color, LineColor):
if isinstance(cstack.pop(), LineColor):
# We found what we wanted, and the color
# was already popped from the stack, so
# we're good
# Breaking here allows it to be appended
break
continue
elif isinstance(elt, ResetTag):
# If it says reset, reset - which means go down the
# stack to the most recent line color.
while cstack:
color = cstack[-1]
if not isinstance(color, LineColor):
# It's not a line color, so remove it
del cstack[-1]
# Add a </c>
# Is closecolor accessible here? # Is closecolor accessible here?
try: try:
closecolor() closecolor()
except Exception as e: except Exception as e:
print(e) print(e)
##if isinstance(color, LineColor):
if isinstance(cstack.pop(), LineColor):
# We found what we wanted, and the color
# was already popped from the stack, so
# we're good
# Breaking here allows it to be appended
break
continue
elif isinstance(elt, ResetTag):
# If it says reset, reset - which means go down the
# stack to the most recent line color.
while cstack:
color = cstack[-1]
if not isinstance(color, LineColor):
# It's not a line color, so remove it
del cstack[-1]
# Add a </c>
# Is closecolor accessible here?
try:
closecolor()
except Exception as e:
print(e)
else:
# It's a line color, so stop searching.
# Using break here prevents the 'else'
# clause of this while statement from
# executing.
break
else: else:
# We don't have any more entries in the stack; # It's a line color, so stop searching.
# just continue. # Using break here prevents the 'else'
continue # clause of this while statement from
## We found the line color, so add it and continue # executing.
##converted.append(color.convert(format)) break
else:
# We don't have any more entries in the stack;
# just continue.
continue continue
## TODO: Make this actually add the reset char ## We found the line color, so add it and continue
# The above shouldn't be necessary because this is Pesterchum's ##converted.append(color.convert(format))
# format, not IRC's continue
elif isinstance(elt, CTagEnd): ## TODO: Make this actually add the reset char
try: # The above shouldn't be necessary because this is Pesterchum's
# format, not IRC's
elif isinstance(elt, CTagEnd):
try:
color = cstack[-1]
# Remove the oldest color, the one we're exiting
if not isinstance(color, LineColor):
# If we got here, we don't have a line color,
# so we're free to act as usual
cstack.pop()
# Fetch the current nested color
color = cstack[-1] color = cstack[-1]
# Remove the oldest color, the one we're exiting else:
if not isinstance(color, LineColor): # We have a line color and the current lexeme
# If we got here, we don't have a line color, # is NOT a line color end; don't even bother
# so we're free to act as usual # adding it to the processed result
cstack.pop() continue
# Fetch the current nested color except LookupError:
color = cstack[-1] # We aren't nested in a color anymore
else: # Passing here causes us to fall through to normal
# We have a line color and the current lexeme # handling
# is NOT a line color end; don't even bother pass
# adding it to the processed result # Not necessary due to Pesterchum's format
continue ##else:
except LookupError: ## # We're still nested....
# We aren't nested in a color anymore ## ##converted.append(elt.convert(format))
# Passing here causes us to fall through to normal ## converted.append(color.convert(format))
# handling ## # We already added to the working list, so just
pass ## # skip the rest
# Not necessary due to Pesterchum's format ## continue
##else: elif isinstance(elt, CTag):
## # We're still nested.... # Push the color onto the stack - we're nested in it now
## ##converted.append(elt.convert(format)) cstack.append(elt)
## converted.append(color.convert(format)) # Falling through adds it to the converted result
## # We already added to the working list, so just
## # skip the rest
## continue
elif isinstance(elt, CTag):
# Push the color onto the stack - we're nested in it now
cstack.append(elt)
# Falling through adds it to the converted result
if isinstance(elt, Lexeme): if isinstance(elt, Lexeme):
elt = elt.convert(format) elt = elt.convert(format)
elif not isinstance(elt, basestr): elif not isinstance(elt, basestr):
# Tempted to make this toss an error, but for now, we'll be # Tempted to make this toss an error, but for now, we'll be
# safe and make it convert to str # safe and make it convert to str
elt = str(elt) elt = str(elt)
converted.append(elt) converted.append(elt)
return converted return converted

View file

@ -7,7 +7,6 @@ __all__ = ["Color"]
# in mind that this may be phased out in the future. # in mind that this may be phased out in the future.
from .dep.attrdict import AttrDict from .dep.attrdict import AttrDict
import collections import collections
@ -21,11 +20,12 @@ else:
basestr = str basestr = str
# A named tuple for containing CIE L*a*b* (CIELAB) information. # A named tuple for containing CIE L*a*b* (CIELAB) information.
# NOTE TO THOSE MAINTAINING: If you don't know what that means, you're going to # NOTE TO THOSE MAINTAINING: If you don't know what that means, you're going to
# hate yourself *and* me if you try to edit this. I know I did when I wrote it. # hate yourself *and* me if you try to edit this. I know I did when I wrote it.
LabTuple = collections.namedtuple("LabTuple", ['L', 'a', 'b']) LabTuple = collections.namedtuple("LabTuple", ["L", "a", "b"])
class Color(object): class Color(object):
# The threshold at which to consider two colors noticeably different, even # The threshold at which to consider two colors noticeably different, even
# if only barely # if only barely
@ -38,7 +38,7 @@ class Color(object):
# TODO: Split __init__, partly using __new__, so the former just has to do # TODO: Split __init__, partly using __new__, so the former just has to do
# conversions # conversions
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.ccode = '' self.ccode = ""
self.closest_name = self.name = None self.closest_name = self.name = None
nargs = len(args) nargs = len(args)
if nargs == 1: if nargs == 1:
@ -54,20 +54,25 @@ class Color(object):
if isinstance(arg, basestr): if isinstance(arg, basestr):
# If it's a string, we've probably got a hex code, but check # If it's a string, we've probably got a hex code, but check
# anyway just in case # anyway just in case
if arg.startswith('#'): if arg.startswith("#"):
self.hexstr = self.sanitize_hex(arg) self.hexstr = self.sanitize_hex(arg)
rgb = self.hexstr_to_rgb(self.hexstr) rgb = self.hexstr_to_rgb(self.hexstr)
self.red, self.green, self.blue = rgb self.red, self.green, self.blue = rgb
##return ##return
# TODO: This. # TODO: This.
elif (arg.startswith('\003') and len(arg) > 1 elif (
or len(arg) < 3 and arg.isdigit()): arg.startswith("\003")
and len(arg) > 1
or len(arg) < 3
and arg.isdigit()
):
# We have an IRC-style color code # We have an IRC-style color code
arg = arg.lstrip('\003') arg = arg.lstrip("\003")
# Just in case # Just in case
arg = arg.split(',')[0] arg = arg.split(",")[0]
cnum = int(arg) cnum = int(arg)
try: color = _irc_colors[cnum] try:
color = _irc_colors[cnum]
except LookupError: except LookupError:
raise ValueError("No color for ccode %r found" % cnum) raise ValueError("No color for ccode %r found" % cnum)
# We found a color; fall through and so on # We found a color; fall through and so on
@ -75,7 +80,8 @@ class Color(object):
else: else:
# Presumably we have a color name # Presumably we have a color name
name = arg.lower() name = arg.lower()
try: color = _svg_colors[name] try:
color = _svg_colors[name]
except LookupError: except LookupError:
raise ValueError("No color with name %r found" % name) raise ValueError("No color with name %r found" % name)
# We found a color; fall through so we make this one a copy # We found a color; fall through so we make this one a copy
@ -103,14 +109,20 @@ class Color(object):
self.x, self.y, self.z = self.rgb_to_xyz(*self.to_rgb_tuple()) self.x, self.y, self.z = self.rgb_to_xyz(*self.to_rgb_tuple())
# Calculate the LAB color # Calculate the LAB color
self.cielab = LabTuple(*self.xyz_to_cielab(*self.to_xyz_tuple())) self.cielab = LabTuple(*self.xyz_to_cielab(*self.to_xyz_tuple()))
if not self.closest_name: self.closest_name = self.get_svg_name() if not self.closest_name:
if not self.ccode: self.ccode = self.get_ccode() self.closest_name = self.get_svg_name()
if not self.ccode:
self.ccode = self.get_ccode()
def __eq__(self, other): def __eq__(self, other):
return hash(self) == hash(other) return hash(self) == hash(other)
def __ne__(self, other): return not self.__eq__(other)
def __ne__(self, other):
return not self.__eq__(other)
def __sub__(self, other): def __sub__(self, other):
if not isinstance(other, Color): raise TypeError if not isinstance(other, Color):
raise TypeError
return self.distance(other) return self.distance(other)
def __hash__(self): def __hash__(self):
@ -130,23 +142,27 @@ class Color(object):
##result ^= self.green ##result ^= self.green
##result ^= self.blue ##result ^= self.blue
##return result ##return result
def __repr__(self): def __repr__(self):
##return "%s(%r)" % (type(self).__name__, str(self)) ##return "%s(%r)" % (type(self).__name__, str(self))
return "%s(%r)" % (type(self).__name__, return "%s(%r)" % (type(self).__name__, self.reduce_hexstr(self.hexstr))
self.reduce_hexstr(self.hexstr))
def __str__(self): def __str__(self):
##return self.reduce_hexstr(self.hexstr) ##return self.reduce_hexstr(self.hexstr)
return self.name() return self.name()
# Builtins # Builtins
# These were yanked from Hostmask and changed around a bit # These were yanked from Hostmask and changed around a bit
def __getitem__(self, ind): return (self.red, self.green, self.blue)[ind] def __getitem__(self, ind):
return (self.red, self.green, self.blue)[ind]
def __iter__(self): def __iter__(self):
targs = (self.red, self.green, self.blue) targs = (self.red, self.green, self.blue)
for t in targs: for t in targs:
yield t yield t
# If we got here, we're out of attributes to provide # If we got here, we're out of attributes to provide
raise StopIteration raise StopIteration
##def __len__(self): ##def __len__(self):
## # Acceptable so long as we're returning RGB like we currently (at TOW) ## # Acceptable so long as we're returning RGB like we currently (at TOW)
## # are ## # are
@ -156,8 +172,8 @@ class Color(object):
def from_ccode(cls, ccode): def from_ccode(cls, ccode):
if isinstance(ccode, basestr): if isinstance(ccode, basestr):
# We were passed a string # We were passed a string
ccode = ccode.lstrip('\003') ccode = ccode.lstrip("\003")
ccode = ccode.split(',') ccode = ccode.split(",")
if len(ccode) < 2: if len(ccode) < 2:
fg = ccode[0] fg = ccode[0]
bg = None bg = None
@ -236,9 +252,9 @@ class Color(object):
# http://en.wikipedia.org/wiki/Color_difference # http://en.wikipedia.org/wiki/Color_difference
slab, olab = self.to_cielab_tuple(), other.to_cielab_tuple() slab, olab = self.to_cielab_tuple(), other.to_cielab_tuple()
# Calculate the distance between the points for each # Calculate the distance between the points for each
dist = list(map(lambda p1, p2: (p2 - p1)**2, slab, olab)) dist = list(map(lambda p1, p2: (p2 - p1) ** 2, slab, olab))
# Add the results up, and sqrt to compensate for earlier squaring # Add the results up, and sqrt to compensate for earlier squaring
dist = sum(dist) ** .5 dist = sum(dist) ** 0.5
return dist return dist
def rgb_distance(self, other): def rgb_distance(self, other):
@ -252,17 +268,17 @@ class Color(object):
### Square the results from the above ### Square the results from the above
##dist = [x**2 for x in dist] ##dist = [x**2 for x in dist]
# Do what we WOULD have done in those two lines with a single one # Do what we WOULD have done in those two lines with a single one
dist = list(map(lambda x1, x2: (x1 - x2)**2, srgb, orgb)) dist = list(map(lambda x1, x2: (x1 - x2) ** 2, srgb, orgb))
# Add the results up # Add the results up
dist = sum(dist) dist = sum(dist)
# Fetch the square root to compensate for the earlier squaring # Fetch the square root to compensate for the earlier squaring
dist **= .5 dist **= 0.5
return dist return dist
@classmethod @classmethod
def hexstr_to_rgb(cls, hexstr): def hexstr_to_rgb(cls, hexstr):
hexstr = cls.sanitize_hex(hexstr) hexstr = cls.sanitize_hex(hexstr)
hexstr = hexstr.lstrip('#') hexstr = hexstr.lstrip("#")
if len(hexstr) == 3: if len(hexstr) == 3:
# NOTE: This will presently never happen, due to the way # NOTE: This will presently never happen, due to the way
# sanitize_hex works. # sanitize_hex works.
@ -270,17 +286,14 @@ class Color(object):
# first. # first.
# Multiplying each element by 17 expands it. Dividing it does the # Multiplying each element by 17 expands it. Dividing it does the
# opposite. # opposite.
result = tuple( (int(h, 16) * 17) for h in hexstr ) result = tuple((int(h, 16) * 17) for h in hexstr)
else: else:
# This is ugly, but the purpose is simple and it's accomplished in # This is ugly, but the purpose is simple and it's accomplished in
# a single line...it just runs through the string, picking two # a single line...it just runs through the string, picking two
# characters at a time and converting them from hex values to ints. # characters at a time and converting them from hex values to ints.
result = tuple( result = tuple(int(hexstr[i : i + 2], 16) for i in range(0, len(hexstr), 2))
int(hexstr[i:i+2], 16) for i in range(0, len(hexstr), 2)
)
return result return result
@staticmethod @staticmethod
def rgb_to_hexstr(red, green, blue, compress=False): def rgb_to_hexstr(red, green, blue, compress=False):
rgb = [red, green, blue] rgb = [red, green, blue]
@ -303,7 +316,7 @@ class Color(object):
# All of our codes were doubles; compress them all down. # All of our codes were doubles; compress them all down.
result = [h[0] for h in result] result = [h[0] for h in result]
# Join and return the result # Join and return the result
return '#' + ''.join(result) return "#" + "".join(result)
# These next two are from http://www.easyrgb.com/index.php?X=MATH # These next two are from http://www.easyrgb.com/index.php?X=MATH
@staticmethod @staticmethod
@ -311,8 +324,10 @@ class Color(object):
rgb = [red, green, blue] rgb = [red, green, blue]
for i, n in enumerate(rgb): for i, n in enumerate(rgb):
n /= 255 n /= 255
if n > 0.04045: n = ( ( n + 0.055 ) / 1.055 ) ** 2.4 if n > 0.04045:
else: n /= 12.92 n = ((n + 0.055) / 1.055) ** 2.4
else:
n /= 12.92
rgb[i] = n * 100 rgb[i] = n * 100
r, g, b = rgb r, g, b = rgb
x = r * 0.4124 + g * 0.3576 + b * 0.1805 x = r * 0.4124 + g * 0.3576 + b * 0.1805
@ -322,6 +337,7 @@ class Color(object):
##y = 0.222491598 * r + 0.71688606 * g + 0.060621486 * b ##y = 0.222491598 * r + 0.71688606 * g + 0.060621486 * b
##z = 0.013929122 * r + 0.097097002 * g + 0.71418547 * b ##z = 0.013929122 * r + 0.097097002 * g + 0.71418547 * b
return x, y, z return x, y, z
@staticmethod @staticmethod
def xyz_to_cielab(x, y, z): def xyz_to_cielab(x, y, z):
# Reference X, Y, and Z # Reference X, Y, and Z
@ -331,13 +347,14 @@ class Color(object):
xyz = [x, y, z] xyz = [x, y, z]
for i, n in enumerate(xyz): for i, n in enumerate(xyz):
n /= refs[i] n /= refs[i]
if n > 0.008856: n **= 1/3 if n > 0.008856:
n **= 1 / 3
else: else:
n *= 7.787 n *= 7.787
n += 16/116 n += 16 / 116
xyz[i] = n xyz[i] = n
x, y, z = xyz x, y, z = xyz
l = (y*116) - 16 l = (y * 116) - 16
a = (x - y) * 500 a = (x - y) * 500
b = (y - z) * 200 b = (y - z) * 200
return l, a, b return l, a, b
@ -347,24 +364,23 @@ class Color(object):
"""Attempt to reduce a six-character hexadecimal color code down to a """Attempt to reduce a six-character hexadecimal color code down to a
four-character one.""" four-character one."""
orig = hexstr orig = hexstr
hexstr = hexstr.lstrip('#') hexstr = hexstr.lstrip("#")
strlen = len(hexstr) strlen = len(hexstr)
h = hexstr.upper() h = hexstr.upper()
for i in range(0, strlen, 2): for i in range(0, strlen, 2):
if h[i] != h[i+1]: if h[i] != h[i + 1]:
# We found a match that wouldn't work; give back the old value. # We found a match that wouldn't work; give back the old value.
return orig return orig
else: else:
# All of these can be reduced; do so and return. # All of these can be reduced; do so and return.
return '#' + hexstr[::2] return "#" + hexstr[::2]
@staticmethod @staticmethod
def sanitize_hex(hexstr): def sanitize_hex(hexstr):
orig = hexstr orig = hexstr
hexstr = hexstr.upper() hexstr = hexstr.upper()
# We don't need the leading hash mark for now # We don't need the leading hash mark for now
hexstr = hexstr.lstrip('#') hexstr = hexstr.lstrip("#")
strlen = len(hexstr) strlen = len(hexstr)
if strlen == 6: if strlen == 6:
# We just need to test this for validity. Fall through to the end. # We just need to test this for validity. Fall through to the end.
@ -372,228 +388,232 @@ class Color(object):
elif strlen == 3: elif strlen == 3:
# We have a short (CSS style) code; duplicate all of the characters # We have a short (CSS style) code; duplicate all of the characters
hexstr = [c + c for c in hexstr] hexstr = [c + c for c in hexstr]
hexstr = ''.join(hexstr) hexstr = "".join(hexstr)
else: else:
raise ValueError( raise ValueError("Invalid hexadecimal value provided: %s" % orig)
"Invalid hexadecimal value provided: %s" % orig
)
try: try:
# Make sure it works/is readable (no invalid characters). # Make sure it works/is readable (no invalid characters).
int(hexstr, 16) int(hexstr, 16)
except ValueError: except ValueError:
raise ValueError( raise ValueError("Invalid hexadecimal value provided: %s" % orig)
"Invalid hexadecimal value provided: %s" % orig return "#" + hexstr
)
return '#' + hexstr
def to_cielab_tuple(self): def to_cielab_tuple(self):
# For now, just return the stored CIELAB tuple # For now, just return the stored CIELAB tuple
return self.cielab return self.cielab
def to_rgb_tuple(self): return (self.red, self.green, self.blue)
def to_rgb_tuple(self):
return (self.red, self.green, self.blue)
# 2012-12-05T17:40:39-07:00: Changed 'self.blue' to 'self.z' like it SHOULD # 2012-12-05T17:40:39-07:00: Changed 'self.blue' to 'self.z' like it SHOULD
# have been in the FIRST place. Ugh. How did I fuck THAT one up? # have been in the FIRST place. Ugh. How did I fuck THAT one up?
def to_xyz_tuple(self): return (self.x, self.y, self.z) def to_xyz_tuple(self):
return (self.x, self.y, self.z)
# All of these are effectively equivalent to the Qt-provided colors, so they # All of these are effectively equivalent to the Qt-provided colors, so they
# could be phased out - but there's no need to, yet. # could be phased out - but there's no need to, yet.
_svg_colors = AttrDict() _svg_colors = AttrDict()
_irc_colors = {} _irc_colors = {}
_svg_colors.update({ _svg_colors.update(
"aliceblue": Color(240, 248, 255), {
"antiquewhite": Color(250, 235, 215), "aliceblue": Color(240, 248, 255),
"aqua": Color( 0, 255, 255), "antiquewhite": Color(250, 235, 215),
"aquamarine": Color(127, 255, 212), "aqua": Color(0, 255, 255),
"azure": Color(240, 255, 255), "aquamarine": Color(127, 255, 212),
"beige": Color(245, 245, 220), "azure": Color(240, 255, 255),
"bisque": Color(255, 228, 196), "beige": Color(245, 245, 220),
"black": Color( 0, 0, 0), "bisque": Color(255, 228, 196),
"blanchedalmond": Color(255, 235, 205), "black": Color(0, 0, 0),
"blue": Color( 0, 0, 255), "blanchedalmond": Color(255, 235, 205),
"blueviolet": Color(138, 43, 226), "blue": Color(0, 0, 255),
"brown": Color(165, 42, 42), "blueviolet": Color(138, 43, 226),
"burlywood": Color(222, 184, 135), "brown": Color(165, 42, 42),
"cadetblue": Color( 95, 158, 160), "burlywood": Color(222, 184, 135),
"chartreuse": Color(127, 255, 0), "cadetblue": Color(95, 158, 160),
"chocolate": Color(210, 105, 30), "chartreuse": Color(127, 255, 0),
"coral": Color(255, 127, 80), "chocolate": Color(210, 105, 30),
"cornflowerblue": Color(100, 149, 237), "coral": Color(255, 127, 80),
"cornsilk": Color(255, 248, 220), "cornflowerblue": Color(100, 149, 237),
"crimson": Color(220, 20, 60), "cornsilk": Color(255, 248, 220),
"cyan": Color( 0, 255, 255), "crimson": Color(220, 20, 60),
"darkblue": Color( 0, 0, 139), "cyan": Color(0, 255, 255),
"darkcyan": Color( 0, 139, 139), "darkblue": Color(0, 0, 139),
"darkgoldenrod": Color(184, 134, 11), "darkcyan": Color(0, 139, 139),
"darkgray": Color(169, 169, 169), "darkgoldenrod": Color(184, 134, 11),
"darkgreen": Color( 0, 100, 0), "darkgray": Color(169, 169, 169),
"darkgrey": Color(169, 169, 169), "darkgreen": Color(0, 100, 0),
"darkkhaki": Color(189, 183, 107), "darkgrey": Color(169, 169, 169),
"darkmagenta": Color(139, 0, 139), "darkkhaki": Color(189, 183, 107),
"darkolivegreen": Color( 85, 107, 47), "darkmagenta": Color(139, 0, 139),
"darkorange": Color(255, 140, 0), "darkolivegreen": Color(85, 107, 47),
"darkorchid": Color(153, 50, 204), "darkorange": Color(255, 140, 0),
"darkred": Color(139, 0, 0), "darkorchid": Color(153, 50, 204),
"darksalmon": Color(233, 150, 122), "darkred": Color(139, 0, 0),
"darkseagreen": Color(143, 188, 143), "darksalmon": Color(233, 150, 122),
"darkslateblue": Color( 72, 61, 139), "darkseagreen": Color(143, 188, 143),
"darkslategray": Color( 47, 79, 79), "darkslateblue": Color(72, 61, 139),
"darkslategrey": Color( 47, 79, 79), "darkslategray": Color(47, 79, 79),
"darkturquoise": Color( 0, 206, 209), "darkslategrey": Color(47, 79, 79),
"darkviolet": Color(148, 0, 211), "darkturquoise": Color(0, 206, 209),
"deeppink": Color(255, 20, 147), "darkviolet": Color(148, 0, 211),
"deepskyblue": Color( 0, 191, 255), "deeppink": Color(255, 20, 147),
"dimgray": Color(105, 105, 105), "deepskyblue": Color(0, 191, 255),
"dimgrey": Color(105, 105, 105), "dimgray": Color(105, 105, 105),
"dodgerblue": Color( 30, 144, 255), "dimgrey": Color(105, 105, 105),
"firebrick": Color(178, 34, 34), "dodgerblue": Color(30, 144, 255),
"floralwhite": Color(255, 250, 240), "firebrick": Color(178, 34, 34),
"forestgreen": Color( 34, 139, 34), "floralwhite": Color(255, 250, 240),
"fuchsia": Color(255, 0, 255), "forestgreen": Color(34, 139, 34),
"gainsboro": Color(220, 220, 220), "fuchsia": Color(255, 0, 255),
"ghostwhite": Color(248, 248, 255), "gainsboro": Color(220, 220, 220),
"gold": Color(255, 215, 0), "ghostwhite": Color(248, 248, 255),
"goldenrod": Color(218, 165, 32), "gold": Color(255, 215, 0),
"gray": Color(128, 128, 128), "goldenrod": Color(218, 165, 32),
"grey": Color(128, 128, 128), "gray": Color(128, 128, 128),
"green": Color( 0, 128, 0), "grey": Color(128, 128, 128),
"greenyellow": Color(173, 255, 47), "green": Color(0, 128, 0),
"honeydew": Color(240, 255, 240), "greenyellow": Color(173, 255, 47),
"hotpink": Color(255, 105, 180), "honeydew": Color(240, 255, 240),
"indianred": Color(205, 92, 92), "hotpink": Color(255, 105, 180),
"indigo": Color( 75, 0, 130), "indianred": Color(205, 92, 92),
"ivory": Color(255, 255, 240), "indigo": Color(75, 0, 130),
"khaki": Color(240, 230, 140), "ivory": Color(255, 255, 240),
"lavender": Color(230, 230, 250), "khaki": Color(240, 230, 140),
"lavenderblush": Color(255, 240, 245), "lavender": Color(230, 230, 250),
"lawngreen": Color(124, 252, 0), "lavenderblush": Color(255, 240, 245),
"lemonchiffon": Color(255, 250, 205), "lawngreen": Color(124, 252, 0),
"lightblue": Color(173, 216, 230), "lemonchiffon": Color(255, 250, 205),
"lightcoral": Color(240, 128, 128), "lightblue": Color(173, 216, 230),
"lightcyan": Color(224, 255, 255), "lightcoral": Color(240, 128, 128),
"lightgoldenrodyellow": Color(250, 250, 210), "lightcyan": Color(224, 255, 255),
"lightgray": Color(211, 211, 211), "lightgoldenrodyellow": Color(250, 250, 210),
"lightgreen": Color(144, 238, 144), "lightgray": Color(211, 211, 211),
"lightgrey": Color(211, 211, 211), "lightgreen": Color(144, 238, 144),
"lightpink": Color(255, 182, 193), "lightgrey": Color(211, 211, 211),
"lightsalmon": Color(255, 160, 122), "lightpink": Color(255, 182, 193),
"lightseagreen": Color( 32, 178, 170), "lightsalmon": Color(255, 160, 122),
"lightskyblue": Color(135, 206, 250), "lightseagreen": Color(32, 178, 170),
"lightslategray": Color(119, 136, 153), "lightskyblue": Color(135, 206, 250),
"lightslategrey": Color(119, 136, 153), "lightslategray": Color(119, 136, 153),
"lightsteelblue": Color(176, 196, 222), "lightslategrey": Color(119, 136, 153),
"lightyellow": Color(255, 255, 224), "lightsteelblue": Color(176, 196, 222),
"lime": Color( 0, 255, 0), "lightyellow": Color(255, 255, 224),
"limegreen": Color( 50, 205, 50), "lime": Color(0, 255, 0),
"linen": Color(250, 240, 230), "limegreen": Color(50, 205, 50),
"magenta": Color(255, 0, 255), "linen": Color(250, 240, 230),
"maroon": Color(128, 0, 0), "magenta": Color(255, 0, 255),
"mediumaquamarine": Color(102, 205, 170), "maroon": Color(128, 0, 0),
"mediumblue": Color( 0, 0, 205), "mediumaquamarine": Color(102, 205, 170),
"mediumorchid": Color(186, 85, 211), "mediumblue": Color(0, 0, 205),
"mediumpurple": Color(147, 112, 219), "mediumorchid": Color(186, 85, 211),
"mediumseagreen": Color( 60, 179, 113), "mediumpurple": Color(147, 112, 219),
"mediumslateblue": Color(123, 104, 238), "mediumseagreen": Color(60, 179, 113),
"mediumspringgreen": Color( 0, 250, 154), "mediumslateblue": Color(123, 104, 238),
"mediumturquoise": Color( 72, 209, 204), "mediumspringgreen": Color(0, 250, 154),
"mediumvioletred": Color(199, 21, 133), "mediumturquoise": Color(72, 209, 204),
"midnightblue": Color( 25, 25, 112), "mediumvioletred": Color(199, 21, 133),
"mintcream": Color(245, 255, 250), "midnightblue": Color(25, 25, 112),
"mistyrose": Color(255, 228, 225), "mintcream": Color(245, 255, 250),
"moccasin": Color(255, 228, 181), "mistyrose": Color(255, 228, 225),
"navajowhite": Color(255, 222, 173), "moccasin": Color(255, 228, 181),
"navy": Color( 0, 0, 128), "navajowhite": Color(255, 222, 173),
"oldlace": Color(253, 245, 230), "navy": Color(0, 0, 128),
"olive": Color(128, 128, 0), "oldlace": Color(253, 245, 230),
"olivedrab": Color(107, 142, 35), "olive": Color(128, 128, 0),
"orange": Color(255, 165, 0), "olivedrab": Color(107, 142, 35),
"orangered": Color(255, 69, 0), "orange": Color(255, 165, 0),
"orchid": Color(218, 112, 214), "orangered": Color(255, 69, 0),
"palegoldenrod": Color(238, 232, 170), "orchid": Color(218, 112, 214),
"palegreen": Color(152, 251, 152), "palegoldenrod": Color(238, 232, 170),
"paleturquoise": Color(175, 238, 238), "palegreen": Color(152, 251, 152),
"palevioletred": Color(219, 112, 147), "paleturquoise": Color(175, 238, 238),
"papayawhip": Color(255, 239, 213), "palevioletred": Color(219, 112, 147),
"peachpuff": Color(255, 218, 185), "papayawhip": Color(255, 239, 213),
"peru": Color(205, 133, 63), "peachpuff": Color(255, 218, 185),
"pink": Color(255, 192, 203), "peru": Color(205, 133, 63),
"plum": Color(221, 160, 221), "pink": Color(255, 192, 203),
"powderblue": Color(176, 224, 230), "plum": Color(221, 160, 221),
"purple": Color(128, 0, 128), "powderblue": Color(176, 224, 230),
"red": Color(255, 0, 0), "purple": Color(128, 0, 128),
"rosybrown": Color(188, 143, 143), "red": Color(255, 0, 0),
"royalblue": Color( 65, 105, 225), "rosybrown": Color(188, 143, 143),
"saddlebrown": Color(139, 69, 19), "royalblue": Color(65, 105, 225),
"salmon": Color(250, 128, 114), "saddlebrown": Color(139, 69, 19),
"sandybrown": Color(244, 164, 96), "salmon": Color(250, 128, 114),
"seagreen": Color( 46, 139, 87), "sandybrown": Color(244, 164, 96),
"seashell": Color(255, 245, 238), "seagreen": Color(46, 139, 87),
"sienna": Color(160, 82, 45), "seashell": Color(255, 245, 238),
"silver": Color(192, 192, 192), "sienna": Color(160, 82, 45),
"skyblue": Color(135, 206, 235), "silver": Color(192, 192, 192),
"slateblue": Color(106, 90, 205), "skyblue": Color(135, 206, 235),
"slategray": Color(112, 128, 144), "slateblue": Color(106, 90, 205),
"slategrey": Color(112, 128, 144), "slategray": Color(112, 128, 144),
"snow": Color(255, 250, 250), "slategrey": Color(112, 128, 144),
"springgreen": Color( 0, 255, 127), "snow": Color(255, 250, 250),
"steelblue": Color( 70, 130, 180), "springgreen": Color(0, 255, 127),
"tan": Color(210, 180, 140), "steelblue": Color(70, 130, 180),
"teal": Color( 0, 128, 128), "tan": Color(210, 180, 140),
"thistle": Color(216, 191, 216), "teal": Color(0, 128, 128),
"tomato": Color(255, 99, 71), "thistle": Color(216, 191, 216),
"turquoise": Color( 64, 224, 208), "tomato": Color(255, 99, 71),
"violet": Color(238, 130, 238), "turquoise": Color(64, 224, 208),
"wheat": Color(245, 222, 179), "violet": Color(238, 130, 238),
"white": Color(255, 255, 255), "wheat": Color(245, 222, 179),
"whitesmoke": Color(245, 245, 245), "white": Color(255, 255, 255),
"yellow": Color(255, 255, 0), "whitesmoke": Color(245, 245, 245),
"yellowgreen": Color(154, 205, 50) "yellow": Color(255, 255, 0),
}) "yellowgreen": Color(154, 205, 50),
}
)
for k, v in list(_svg_colors.items()): for k, v in list(_svg_colors.items()):
v.closest_name = v.name = k v.closest_name = v.name = k
# 2012-12-08T14:29-07:00: Copied over from Colors.hexstr_for_ccodes in the main # 2012-12-08T14:29-07:00: Copied over from Colors.hexstr_for_ccodes in the main
# textsub file, and subsequently modified. # textsub file, and subsequently modified.
_irc_colors.update({ _irc_colors.update(
# These are all taken from *MY* XChat settings - they aren't guaranteed to {
# please everyone! # These are all taken from *MY* XChat settings - they aren't guaranteed to
0: Color(0xFFFFFF), # please everyone!
1: Color(0x1F1F1F), 0: Color(0xFFFFFF),
2: Color(0x00007F), 1: Color(0x1F1F1F),
3: Color(0x007F00), 2: Color(0x00007F),
4: Color(0xFF0000), 3: Color(0x007F00),
5: Color(0x7F0000), 4: Color(0xFF0000),
6: Color(0x9C009C), 5: Color(0x7F0000),
7: Color(0xFC7F00), 6: Color(0x9C009C),
8: Color(0xFFFF00), 7: Color(0xFC7F00),
9: Color(0x00FC00), 8: Color(0xFFFF00),
##10: Color(0x009393), 9: Color(0x00FC00),
10: Color(0x008282), ##10: Color(0x009393),
11: Color(0x00FFFF), 10: Color(0x008282),
12: Color(0x0000FC), 11: Color(0x00FFFF),
13: Color(0xFF00FF), 12: Color(0x0000FC),
14: Color(0x7F7F7F), 13: Color(0xFF00FF),
15: Color(0xD2D2D2), 14: Color(0x7F7F7F),
# My local colors 15: Color(0xD2D2D2),
16: Color(0xCCCCCC), # My local colors
##17: Color(0x000000), # Commented out 'til readability checks are in 16: Color(0xCCCCCC),
17: Color(0x1F1F1F), ##17: Color(0x000000), # Commented out 'til readability checks are in
18: Color(0x000056), 17: Color(0x1F1F1F),
19: Color(0x008141), 18: Color(0x000056),
20: Color(0xE00707), 19: Color(0x008141),
21: Color(0xA10000), 20: Color(0xE00707),
22: Color(0x6A006A), 21: Color(0xA10000),
23: Color(0xA15000), 22: Color(0x6A006A),
24: Color(0xA1A100), 23: Color(0xA15000),
25: Color(0x416600), 24: Color(0xA1A100),
##26: Color(0x008282), 25: Color(0x416600),
26: Color(0x005682), ##26: Color(0x008282),
27: Color(0x00D5F2), 26: Color(0x005682),
28: Color(0x0715CD), 27: Color(0x00D5F2),
29: Color(0x99004D), 28: Color(0x0715CD),
30: Color(0x323232), 29: Color(0x99004D),
31: Color(0x929292), 30: Color(0x323232),
31: Color(0x929292),
99: Color(0x999999) # Until I think of a better solution to this 99: Color(0x999999), # Until I think of a better solution to this
}) }
)
for k, v in list(_irc_colors.items()): for k, v in list(_irc_colors.items()):
v.ccode = "%02d" % k v.ccode = "%02d" % k
del k, v del k, v

File diff suppressed because it is too large Load diff

View file

@ -5,424 +5,646 @@ import shutil
import PyInstaller.__main__ import PyInstaller.__main__
is_64bit = sys.maxsize > 2**32 is_64bit = sys.maxsize > 2**32
#is_linux = sys.platform.startswith("linux") # is_linux = sys.platform.startswith("linux")
exclude_modules = [] exclude_modules = []
#if is_linux == False: # if is_linux == False:
# print("Not Linux, excluding pygame.") # print("Not Linux, excluding pygame.")
# exclude_modules.append('pygame') # exclude_modules.append('pygame')
add_data = ['quirks;quirks', add_data = [
'smilies;smilies', "quirks;quirks",
'themes;themes', "smilies;smilies",
'docs;docs', "themes;themes",
'README.md;.', "docs;docs",
'LICENSE;.', "README.md;.",
'CHANGELOG.md;.', "LICENSE;.",
'PCskins.png;.', "CHANGELOG.md;.",
'Pesterchum.png;.'] "PCskins.png;.",
data_folders = {'quirks': 'quirks', "Pesterchum.png;.",
'smilies': 'smilies', ]
'themes': 'themes', data_folders = {
'docs': 'docs'} "quirks": "quirks",
data_files = {'README.md': 'README.md.txt', "smilies": "smilies",
'LICENSE': 'LICENSE.txt', "themes": "themes",
'CHANGELOG.md': 'CHANGELOG.md.txt', "docs": "docs",
'PCskins.png': '.', }
'Pesterchum.png': '.'} data_files = {
data_files_linux = {'README.md': 'README.md', "README.md": "README.md.txt",
'LICENSE': 'LICENSE.txt', "LICENSE": "LICENSE.txt",
'CHANGELOG.md': 'CHANGELOG.md', "CHANGELOG.md": "CHANGELOG.md.txt",
'PCskins.png': '.', "PCskins.png": ".",
'Pesterchum.png': '.'} "Pesterchum.png": ".",
}
data_files_linux = {
"README.md": "README.md",
"LICENSE": "LICENSE.txt",
"CHANGELOG.md": "CHANGELOG.md",
"PCskins.png": ".",
"Pesterchum.png": ".",
}
# Some of these might not be required anymore, # Some of these might not be required anymore,
# newer versions of PyInstaller claim to exclude certain problematic DDLs automatically. # newer versions of PyInstaller claim to exclude certain problematic DDLs automatically.
upx_exclude = ["qwindows.dll", upx_exclude = [
"qwindows.dll",
"Qt6Core.dll", "Qt6Core.dll",
"Qt6Gui.dll", "Qt6Gui.dll",
"vcruntime140.dll", "vcruntime140.dll",
"MSVCP140.dll", "MSVCP140.dll",
"MSVCP140_1.dll" "MSVCP140_1.dll" "api-ms-win-core-console-l1-1-0.dll",
"api-ms-win-core-console-l1-1-0.dll",\ "api-ms-win-core-console-l1-1-0.dll",
"api-ms-win-core-console-l1-1-0.dll",\ "api-ms-win-core-console-l1-2-0.dll",
"api-ms-win-core-console-l1-2-0.dll",\ "api-ms-win-core-datetime-l1-1-0.dll",
"api-ms-win-core-datetime-l1-1-0.dll",\ "api-ms-win-core-debug-l1-1-0.dll",
"api-ms-win-core-debug-l1-1-0.dll",\ "api-ms-win-core-errorhandling-l1-1-0.dll",
"api-ms-win-core-errorhandling-l1-1-0.dll",\ "api-ms-win-core-file-l1-1-0.dll",
"api-ms-win-core-file-l1-1-0.dll",\ "api-ms-win-core-file-l1-2-0.dll",
"api-ms-win-core-file-l1-2-0.dll",\ "api-ms-win-core-file-l2-1-0.dll",
"api-ms-win-core-file-l2-1-0.dll",\ "api-ms-win-core-handle-l1-1-0.dll",
"api-ms-win-core-handle-l1-1-0.dll",\ "api-ms-win-core-heap-l1-1-0.dll",
"api-ms-win-core-heap-l1-1-0.dll",\ "api-ms-win-core-interlocked-l1-1-0.dll",
"api-ms-win-core-interlocked-l1-1-0.dll",\ "api-ms-win-core-libraryloader-l1-1-0.dll",
"api-ms-win-core-libraryloader-l1-1-0.dll",\ "api-ms-win-core-localization-l1-2-0.dll",
"api-ms-win-core-localization-l1-2-0.dll",\ "api-ms-win-core-memory-l1-1-0.dll",
"api-ms-win-core-memory-l1-1-0.dll",\ "api-ms-win-core-namedpipe-l1-1-0.dll",
"api-ms-win-core-namedpipe-l1-1-0.dll",\ "api-ms-win-core-processenvironment-l1-1-0.dll",
"api-ms-win-core-processenvironment-l1-1-0.dll",\ "api-ms-win-core-processthreads-l1-1-0.dll",
"api-ms-win-core-processthreads-l1-1-0.dll",\ "api-ms-win-core-processthreads-l1-1-1.dll",
"api-ms-win-core-processthreads-l1-1-1.dll",\ "api-ms-win-core-profile-l1-1-0.dll",
"api-ms-win-core-profile-l1-1-0.dll",\ "api-ms-win-core-rtlsupport-l1-1-0.dll",
"api-ms-win-core-rtlsupport-l1-1-0.dll",\ "api-ms-win-core-string-l1-1-0.dll",
"api-ms-win-core-string-l1-1-0.dll",\ "api-ms-win-core-synch-l1-1-0.dll",
"api-ms-win-core-synch-l1-1-0.dll",\ "api-ms-win-core-synch-l1-2-0.dll",
"api-ms-win-core-synch-l1-2-0.dll",\ "api-ms-win-core-sysinfo-l1-1-0.dll",
"api-ms-win-core-sysinfo-l1-1-0.dll",\ "api-ms-win-core-timezone-l1-1-0.dll",
"api-ms-win-core-timezone-l1-1-0.dll",\ "api-ms-win-core-util-l1-1-0.dll",
"api-ms-win-core-util-l1-1-0.dll",\ "API-MS-Win-core-xstate-l2-1-0.dll",
"API-MS-Win-core-xstate-l2-1-0.dll",\ "api-ms-win-crt-conio-l1-1-0.dll",
"api-ms-win-crt-conio-l1-1-0.dll",\ "api-ms-win-crt-convert-l1-1-0.dll",
"api-ms-win-crt-convert-l1-1-0.dll",\ "api-ms-win-crt-environment-l1-1-0.dll",
"api-ms-win-crt-environment-l1-1-0.dll",\ "api-ms-win-crt-filesystem-l1-1-0.dll",
"api-ms-win-crt-filesystem-l1-1-0.dll",\ "api-ms-win-crt-heap-l1-1-0.dll",
"api-ms-win-crt-heap-l1-1-0.dll",\ "api-ms-win-crt-locale-l1-1-0.dll",
"api-ms-win-crt-locale-l1-1-0.dll",\ "api-ms-win-crt-math-l1-1-0.dll",
"api-ms-win-crt-math-l1-1-0.dll",\ "api-ms-win-crt-multibyte-l1-1-0.dll",
"api-ms-win-crt-multibyte-l1-1-0.dll",\ "api-ms-win-crt-private-l1-1-0.dll",
"api-ms-win-crt-private-l1-1-0.dll",\ "api-ms-win-crt-process-l1-1-0.dll",
"api-ms-win-crt-process-l1-1-0.dll",\ "api-ms-win-crt-runtime-l1-1-0.dll",
"api-ms-win-crt-runtime-l1-1-0.dll",\ "api-ms-win-crt-stdio-l1-1-0.dll",
"api-ms-win-crt-stdio-l1-1-0.dll",\ "api-ms-win-crt-string-l1-1-0.dll",
"api-ms-win-crt-string-l1-1-0.dll",\ "api-ms-win-crt-time-l1-1-0.dll",
"api-ms-win-crt-time-l1-1-0.dll",\ "api-ms-win-crt-utility-l1-1-0.dll",
"api-ms-win-crt-utility-l1-1-0.dll",\ "ucrtbase.dll",
"ucrtbase.dll"] ]
delete_builddist = '' delete_builddist = ""
upx_enabled = '' upx_enabled = ""
package_universal_crt = '' package_universal_crt = ""
onefile = '' onefile = ""
windowed = '' windowed = ""
try: try:
print("This is a script to make building with Pyinstaller a bit more conventient.") print("This is a script to make building with Pyinstaller a bit more conventient.")
while (delete_builddist != 'y') and (delete_builddist != 'n'): while (delete_builddist != "y") and (delete_builddist != "n"):
delete_builddist = input("Delete build & dist folders? (Y/N): ").lower() delete_builddist = input("Delete build & dist folders? (Y/N): ").lower()
if delete_builddist == "y": if delete_builddist == "y":
try: try:
shutil.rmtree('dist') shutil.rmtree("dist")
except FileNotFoundError as e: except FileNotFoundError as e:
print(e) print(e)
try: try:
shutil.rmtree('build') shutil.rmtree("build")
except FileNotFoundError as e: except FileNotFoundError as e:
print(e) print(e)
while (upx_enabled != 'y') and (upx_enabled != 'n'): while (upx_enabled != "y") and (upx_enabled != "n"):
upx_enabled = input("Enable UPX? (Y/N): ").lower() upx_enabled = input("Enable UPX? (Y/N): ").lower()
if upx_enabled == 'y': if upx_enabled == "y":
print("If upx is on your path you don't need to include anything here.") print("If upx is on your path you don't need to include anything here.")
if is_64bit == True: if is_64bit == True:
upx_dir = input("UPX directory [D:\\upx-3.96-win64]: ") upx_dir = input("UPX directory [D:\\upx-3.96-win64]: ")
if upx_dir == '': if upx_dir == "":
upx_dir = "D:\\upx-3.96-win64" # Default dir for me :) upx_dir = "D:\\upx-3.96-win64" # Default dir for me :)
else: else:
upx_dir = input("UPX directory [D:\\upx-3.96-win32]: ") upx_dir = input("UPX directory [D:\\upx-3.96-win32]: ")
if upx_dir == '': if upx_dir == "":
upx_dir = "D:\\upx-3.96-win32" # Default dir for me :) upx_dir = "D:\\upx-3.96-win32" # Default dir for me :)
print("upx_dir = " + upx_dir) print("upx_dir = " + upx_dir)
elif upx_enabled == 'n': elif upx_enabled == "n":
upx_dir = '' upx_dir = ""
while (windowed != 'y') and (windowed != 'n'): while (windowed != "y") and (windowed != "n"):
windowed = input("Build with '--windowed'? (Y/N): ").lower() windowed = input("Build with '--windowed'? (Y/N): ").lower()
if sys.platform == 'win32': if sys.platform == "win32":
print("(https://pyinstaller.readthedocs.io/en/stable/usage.html?highlight=sdk#windows)") print(
while (package_universal_crt != 'y') and (package_universal_crt != 'n'): "(https://pyinstaller.readthedocs.io/en/stable/usage.html?highlight=sdk#windows)"
package_universal_crt = input("Try to include universal CRT? (Y/N): ").lower() )
if package_universal_crt == 'y': while (package_universal_crt != "y") and (package_universal_crt != "n"):
package_universal_crt = input(
"Try to include universal CRT? (Y/N): "
).lower()
if package_universal_crt == "y":
if is_64bit == True: if is_64bit == True:
crt_path = input("Universal CRT: [C:\\Program Files (x86)\\Windows Kits\\10\\Redist\\10.0.19041.0\\ucrt\\DLLs\\x64]: ") crt_path = input(
if crt_path == '': "Universal CRT: [C:\\Program Files (x86)\\Windows Kits\\10\\Redist\\10.0.19041.0\\ucrt\\DLLs\\x64]: "
#crt_path = "C:\\Program Files (x86)\\Windows Kits\\10\\Redist\\10.0.19041.0\\ucrt\\DLLs\\x64" # Default directory. )
crt_path = os.path.join('C:%s' % os.sep, 'Program Files (x86)', 'Windows Kits', '10', '10.0.19041.0', 'ucrt', 'DLLs', 'x64') if crt_path == "":
# crt_path = "C:\\Program Files (x86)\\Windows Kits\\10\\Redist\\10.0.19041.0\\ucrt\\DLLs\\x64" # Default directory.
crt_path = os.path.join(
"C:%s" % os.sep,
"Program Files (x86)",
"Windows Kits",
"10",
"10.0.19041.0",
"ucrt",
"DLLs",
"x64",
)
else: else:
crt_path = input("Extra path: [C:\\Program Files (x86)\\Windows Kits\\10\\Redist\\10.0.19041.0\\ucrt\\DLLs\\x86]: ") crt_path = input(
if crt_path == '': "Extra path: [C:\\Program Files (x86)\\Windows Kits\\10\\Redist\\10.0.19041.0\\ucrt\\DLLs\\x86]: "
#crt_path = "C:\\Program Files (x86)\\Windows Kits\\10\\Redist\\10.0.19041.0\\ucrt\\DLLs\\x86" # Default directory. )
crt_path = os.path.join('C:%s' % os.sep, 'Program Files (x86)', 'Windows Kits', '10', '10.0.19041.0', 'ucrt', 'DLLs', 'x86') if crt_path == "":
# crt_path = "C:\\Program Files (x86)\\Windows Kits\\10\\Redist\\10.0.19041.0\\ucrt\\DLLs\\x86" # Default directory.
crt_path = os.path.join(
"C:%s" % os.sep,
"Program Files (x86)",
"Windows Kits",
"10",
"10.0.19041.0",
"ucrt",
"DLLs",
"x86",
)
print("crt_path = " + crt_path) print("crt_path = " + crt_path)
if (sys.platform == 'win32') or (sys.platform == 'linux'): if (sys.platform == "win32") or (sys.platform == "linux"):
while (onefile != 'y') and (onefile != 'n'): while (onefile != "y") and (onefile != "n"):
onefile = input("Build with '--onefile'? (Y/N): ").lower() onefile = input("Build with '--onefile'? (Y/N): ").lower()
except KeyboardInterrupt: except KeyboardInterrupt:
sys.exit("KeyboardInterrupt") sys.exit("KeyboardInterrupt")
#Windows # Windows
if sys.platform == 'win32': if sys.platform == "win32":
run_win32 = [ run_win32 = [
'pesterchum.py', "pesterchum.py",
'--name=Pesterchum', "--name=Pesterchum",
#'--noconfirm', # Overwrite output directory. #'--noconfirm', # Overwrite output directory.
#'--windowed', # Hide console #'--windowed', # Hide console
'--icon=pesterchum.ico', "--icon=pesterchum.ico",
'--clean', # Clear cache "--clean", # Clear cache
] ]
if (sys.version_info.major == 3) & (sys.version_info.minor == 8): if (sys.version_info.major == 3) & (sys.version_info.minor == 8):
exclude_modules.append('tkinter') exclude_modules.append("tkinter")
if upx_enabled == 'y': if upx_enabled == "y":
if os.path.isdir(upx_dir): if os.path.isdir(upx_dir):
run_win32.append('--upx-dir=%s' % upx_dir) run_win32.append("--upx-dir=%s" % upx_dir)
for x in upx_exclude: for x in upx_exclude:
run_win32.append('--upx-exclude=%s' % x ) run_win32.append("--upx-exclude=%s" % x)
# Lower case variants are required. # Lower case variants are required.
run_win32.append('--upx-exclude=%s' % x.lower() ) run_win32.append("--upx-exclude=%s" % x.lower())
elif upx_enabled == 'n': elif upx_enabled == "n":
run_win32.append('--noupx') run_win32.append("--noupx")
for x in exclude_modules: for x in exclude_modules:
run_win32.append('--exclude-module=%s' % x ) run_win32.append("--exclude-module=%s" % x)
if windowed == 'y': if windowed == "y":
run_win32.append('--windowed') run_win32.append("--windowed")
if onefile == 'y': if onefile == "y":
run_win32.append('--onefile') run_win32.append("--onefile")
elif onefile == 'n': elif onefile == "n":
for x in add_data: for x in add_data:
run_win32.append('--add-data=%s' % x ) run_win32.append("--add-data=%s" % x)
if package_universal_crt == "y":
if package_universal_crt == 'y': run_win32.append('--paths="%s"' % crt_path)
run_win32.append('--paths=\"%s\"' % crt_path)
if os.path.exists(crt_path): if os.path.exists(crt_path):
if is_64bit == False: if is_64bit == False:
run_win32.append('--add-binary=%s\\api-ms-win-core-console-l1-1-0.dll;.' % crt_path) run_win32.append(
run_win32.append('--add-binary=%s\\api-ms-win-core-console-l1-1-0.dll;.' % crt_path) "--add-binary=%s\\api-ms-win-core-console-l1-1-0.dll;." % crt_path
run_win32.append('--add-binary=%s\\api-ms-win-core-console-l1-2-0.dll;.' % crt_path) )
run_win32.append('--add-binary=%s\\api-ms-win-core-datetime-l1-1-0.dll;.' % crt_path) run_win32.append(
run_win32.append('--add-binary=%s\\api-ms-win-core-debug-l1-1-0.dll;.' % crt_path) "--add-binary=%s\\api-ms-win-core-console-l1-1-0.dll;." % crt_path
run_win32.append('--add-binary=%s\\api-ms-win-core-errorhandling-l1-1-0.dll;.' % crt_path) )
run_win32.append('--add-binary=%s\\api-ms-win-core-file-l1-1-0.dll;.' % crt_path) run_win32.append(
run_win32.append('--add-binary=%s\\api-ms-win-core-file-l1-2-0.dll;.' % crt_path) "--add-binary=%s\\api-ms-win-core-console-l1-2-0.dll;." % crt_path
run_win32.append('--add-binary=%s\\api-ms-win-core-file-l2-1-0.dll;.' % crt_path) )
run_win32.append('--add-binary=%s\\api-ms-win-core-handle-l1-1-0.dll;.' % crt_path) run_win32.append(
run_win32.append('--add-binary=%s\\api-ms-win-core-heap-l1-1-0.dll;.' % crt_path) "--add-binary=%s\\api-ms-win-core-datetime-l1-1-0.dll;." % crt_path
run_win32.append('--add-binary=%s\\api-ms-win-core-interlocked-l1-1-0.dll;.' % crt_path) )
run_win32.append('--add-binary=%s\\api-ms-win-core-libraryloader-l1-1-0.dll;.' % crt_path) run_win32.append(
run_win32.append('--add-binary=%s\\api-ms-win-core-localization-l1-2-0.dll;.' % crt_path) "--add-binary=%s\\api-ms-win-core-debug-l1-1-0.dll;." % crt_path
run_win32.append('--add-binary=%s\\api-ms-win-core-memory-l1-1-0.dll;.' % crt_path) )
run_win32.append('--add-binary=%s\\api-ms-win-core-namedpipe-l1-1-0.dll;.' % crt_path) run_win32.append(
run_win32.append('--add-binary=%s\\api-ms-win-core-processenvironment-l1-1-0.dll;.' % crt_path) "--add-binary=%s\\api-ms-win-core-errorhandling-l1-1-0.dll;."
run_win32.append('--add-binary=%s\\api-ms-win-core-processthreads-l1-1-0.dll;.' % crt_path) % crt_path
run_win32.append('--add-binary=%s\\api-ms-win-core-processthreads-l1-1-1.dll;.' % crt_path) )
run_win32.append('--add-binary=%s\\api-ms-win-core-profile-l1-1-0.dll;.' % crt_path) run_win32.append(
run_win32.append('--add-binary=%s\\api-ms-win-core-rtlsupport-l1-1-0.dll;.' % crt_path) "--add-binary=%s\\api-ms-win-core-file-l1-1-0.dll;." % crt_path
run_win32.append('--add-binary=%s\\api-ms-win-core-string-l1-1-0.dll;.' % crt_path) )
run_win32.append('--add-binary=%s\\api-ms-win-core-synch-l1-1-0.dll;.' % crt_path) run_win32.append(
run_win32.append('--add-binary=%s\\api-ms-win-core-synch-l1-2-0.dll;.' % crt_path) "--add-binary=%s\\api-ms-win-core-file-l1-2-0.dll;." % crt_path
run_win32.append('--add-binary=%s\\api-ms-win-core-sysinfo-l1-1-0.dll;.' % crt_path) )
run_win32.append('--add-binary=%s\\api-ms-win-core-timezone-l1-1-0.dll;.' % crt_path) run_win32.append(
run_win32.append('--add-binary=%s\\api-ms-win-core-util-l1-1-0.dll;.' % crt_path) "--add-binary=%s\\api-ms-win-core-file-l2-1-0.dll;." % crt_path
run_win32.append('--add-binary=%s\\API-MS-Win-core-xstate-l2-1-0.dll;.' % crt_path) )
run_win32.append('--add-binary=%s\\api-ms-win-crt-conio-l1-1-0.dll;.' % crt_path) run_win32.append(
run_win32.append('--add-binary=%s\\api-ms-win-crt-convert-l1-1-0.dll;.' % crt_path) "--add-binary=%s\\api-ms-win-core-handle-l1-1-0.dll;." % crt_path
run_win32.append('--add-binary=%s\\api-ms-win-crt-environment-l1-1-0.dll;.' % crt_path) )
run_win32.append('--add-binary=%s\\api-ms-win-crt-filesystem-l1-1-0.dll;.' % crt_path) run_win32.append(
run_win32.append('--add-binary=%s\\api-ms-win-crt-heap-l1-1-0.dll;.' % crt_path) "--add-binary=%s\\api-ms-win-core-heap-l1-1-0.dll;." % crt_path
run_win32.append('--add-binary=%s\\api-ms-win-crt-locale-l1-1-0.dll;.' % crt_path) )
run_win32.append('--add-binary=%s\\api-ms-win-crt-math-l1-1-0.dll;.' % crt_path) run_win32.append(
run_win32.append('--add-binary=%s\\api-ms-win-crt-multibyte-l1-1-0.dll;.' % crt_path) "--add-binary=%s\\api-ms-win-core-interlocked-l1-1-0.dll;."
run_win32.append('--add-binary=%s\\api-ms-win-crt-private-l1-1-0.dll;.' % crt_path) % crt_path
run_win32.append('--add-binary=%s\\api-ms-win-crt-process-l1-1-0.dll;.' % crt_path) )
run_win32.append('--add-binary=%s\\api-ms-win-crt-runtime-l1-1-0.dll;.' % crt_path) run_win32.append(
run_win32.append('--add-binary=%s\\api-ms-win-crt-stdio-l1-1-0.dll;.' % crt_path) "--add-binary=%s\\api-ms-win-core-libraryloader-l1-1-0.dll;."
run_win32.append('--add-binary=%s\\api-ms-win-crt-string-l1-1-0.dll;.' % crt_path) % crt_path
run_win32.append('--add-binary=%s\\api-ms-win-crt-time-l1-1-0.dll;.' % crt_path) )
run_win32.append('--add-binary=%s\\api-ms-win-crt-utility-l1-1-0.dll;.' % crt_path) run_win32.append(
run_win32.append('--add-binary=%s\\ucrtbase.dll;.' % crt_path) "--add-binary=%s\\api-ms-win-core-localization-l1-2-0.dll;."
% crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-core-memory-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-core-namedpipe-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-core-processenvironment-l1-1-0.dll;."
% crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-core-processthreads-l1-1-0.dll;."
% crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-core-processthreads-l1-1-1.dll;."
% crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-core-profile-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-core-rtlsupport-l1-1-0.dll;."
% crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-core-string-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-core-synch-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-core-synch-l1-2-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-core-sysinfo-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-core-timezone-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-core-util-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\API-MS-Win-core-xstate-l2-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-crt-conio-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-crt-convert-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-crt-environment-l1-1-0.dll;."
% crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-crt-filesystem-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-crt-heap-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-crt-locale-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-crt-math-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-crt-multibyte-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-crt-private-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-crt-process-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-crt-runtime-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-crt-stdio-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-crt-string-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-crt-time-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-crt-utility-l1-1-0.dll;." % crt_path
)
run_win32.append("--add-binary=%s\\ucrtbase.dll;." % crt_path)
elif is_64bit == True: elif is_64bit == True:
run_win32.append('--add-binary=%s\\api-ms-win-core-console-l1-1-0.dll;.' % crt_path) run_win32.append(
run_win32.append('--add-binary=%s\\api-ms-win-core-console-l1-2-0.dll;.' % crt_path) "--add-binary=%s\\api-ms-win-core-console-l1-1-0.dll;." % crt_path
run_win32.append('--add-binary=%s\\api-ms-win-core-datetime-l1-1-0.dll;.' % crt_path) )
run_win32.append('--add-binary=%s\\api-ms-win-core-debug-l1-1-0.dll;.' % crt_path) run_win32.append(
run_win32.append('--add-binary=%s\\api-ms-win-core-errorhandling-l1-1-0.dll;.' % crt_path) "--add-binary=%s\\api-ms-win-core-console-l1-2-0.dll;." % crt_path
run_win32.append('--add-binary=%s\\api-ms-win-core-file-l1-1-0.dll;.' % crt_path) )
run_win32.append('--add-binary=%s\\api-ms-win-core-file-l1-2-0.dll;.' % crt_path) run_win32.append(
run_win32.append('--add-binary=%s\\api-ms-win-core-file-l2-1-0.dll;.' % crt_path) "--add-binary=%s\\api-ms-win-core-datetime-l1-1-0.dll;." % crt_path
run_win32.append('--add-binary=%s\\api-ms-win-core-handle-l1-1-0.dll;.' % crt_path) )
run_win32.append('--add-binary=%s\\api-ms-win-core-heap-l1-1-0.dll;.' % crt_path) run_win32.append(
run_win32.append('--add-binary=%s\\api-ms-win-core-interlocked-l1-1-0.dll;.' % crt_path) "--add-binary=%s\\api-ms-win-core-debug-l1-1-0.dll;." % crt_path
run_win32.append('--add-binary=%s\\api-ms-win-core-libraryloader-l1-1-0.dll;.' % crt_path) )
run_win32.append('--add-binary=%s\\api-ms-win-core-localization-l1-2-0.dll;.' % crt_path) run_win32.append(
run_win32.append('--add-binary=%s\\api-ms-win-core-memory-l1-1-0.dll;.' % crt_path) "--add-binary=%s\\api-ms-win-core-errorhandling-l1-1-0.dll;."
run_win32.append('--add-binary=%s\\api-ms-win-core-namedpipe-l1-1-0.dll;.' % crt_path) % crt_path
run_win32.append('--add-binary=%s\\api-ms-win-core-processenvironment-l1-1-0.dll;.' % crt_path) )
run_win32.append('--add-binary=%s\\api-ms-win-core-processthreads-l1-1-0.dll;.' % crt_path) run_win32.append(
run_win32.append('--add-binary=%s\\api-ms-win-core-processthreads-l1-1-1.dll;.' % crt_path) "--add-binary=%s\\api-ms-win-core-file-l1-1-0.dll;." % crt_path
run_win32.append('--add-binary=%s\\api-ms-win-core-profile-l1-1-0.dll;.' % crt_path) )
run_win32.append('--add-binary=%s\\api-ms-win-core-rtlsupport-l1-1-0.dll;.' % crt_path) run_win32.append(
run_win32.append('--add-binary=%s\\api-ms-win-core-string-l1-1-0.dll;.' % crt_path) "--add-binary=%s\\api-ms-win-core-file-l1-2-0.dll;." % crt_path
run_win32.append('--add-binary=%s\\api-ms-win-core-synch-l1-1-0.dll;.' % crt_path) )
run_win32.append('--add-binary=%s\\api-ms-win-core-synch-l1-2-0.dll;.' % crt_path) run_win32.append(
run_win32.append('--add-binary=%s\\api-ms-win-core-sysinfo-l1-1-0.dll;.' % crt_path) "--add-binary=%s\\api-ms-win-core-file-l2-1-0.dll;." % crt_path
run_win32.append('--add-binary=%s\\api-ms-win-core-timezone-l1-1-0.dll;.' % crt_path) )
run_win32.append('--add-binary=%s\\api-ms-win-core-util-l1-1-0.dll;.' % crt_path) run_win32.append(
run_win32.append('--add-binary=%s\\api-ms-win-crt-conio-l1-1-0.dll;.' % crt_path) "--add-binary=%s\\api-ms-win-core-handle-l1-1-0.dll;." % crt_path
run_win32.append('--add-binary=%s\\api-ms-win-crt-convert-l1-1-0.dll;.' % crt_path) )
run_win32.append('--add-binary=%s\\api-ms-win-crt-environment-l1-1-0.dll;.' % crt_path) run_win32.append(
run_win32.append('--add-binary=%s\\api-ms-win-crt-filesystem-l1-1-0.dll;.' % crt_path) "--add-binary=%s\\api-ms-win-core-heap-l1-1-0.dll;." % crt_path
run_win32.append('--add-binary=%s\\api-ms-win-crt-heap-l1-1-0.dll;.' % crt_path) )
run_win32.append('--add-binary=%s\\api-ms-win-crt-locale-l1-1-0.dll;.' % crt_path) run_win32.append(
run_win32.append('--add-binary=%s\\api-ms-win-crt-math-l1-1-0.dll;.' % crt_path) "--add-binary=%s\\api-ms-win-core-interlocked-l1-1-0.dll;."
run_win32.append('--add-binary=%s\\api-ms-win-crt-multibyte-l1-1-0.dll;.' % crt_path) % crt_path
run_win32.append('--add-binary=%s\\api-ms-win-crt-private-l1-1-0.dll;.' % crt_path) )
run_win32.append('--add-binary=%s\\api-ms-win-crt-process-l1-1-0.dll;.' % crt_path) run_win32.append(
run_win32.append('--add-binary=%s\\api-ms-win-crt-runtime-l1-1-0.dll;.' % crt_path) "--add-binary=%s\\api-ms-win-core-libraryloader-l1-1-0.dll;."
run_win32.append('--add-binary=%s\\api-ms-win-crt-stdio-l1-1-0.dll;.' % crt_path) % crt_path
run_win32.append('--add-binary=%s\\api-ms-win-crt-string-l1-1-0.dll;.' % crt_path) )
run_win32.append('--add-binary=%s\\api-ms-win-crt-time-l1-1-0.dll;.' % crt_path) run_win32.append(
run_win32.append('--add-binary=%s\\api-ms-win-crt-utility-l1-1-0.dll;.' % crt_path) "--add-binary=%s\\api-ms-win-core-localization-l1-2-0.dll;."
run_win32.append('--add-binary=%s\\ucrtbase.dll;.' % crt_path) % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-core-memory-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-core-namedpipe-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-core-processenvironment-l1-1-0.dll;."
% crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-core-processthreads-l1-1-0.dll;."
% crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-core-processthreads-l1-1-1.dll;."
% crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-core-profile-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-core-rtlsupport-l1-1-0.dll;."
% crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-core-string-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-core-synch-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-core-synch-l1-2-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-core-sysinfo-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-core-timezone-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-core-util-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-crt-conio-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-crt-convert-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-crt-environment-l1-1-0.dll;."
% crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-crt-filesystem-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-crt-heap-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-crt-locale-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-crt-math-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-crt-multibyte-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-crt-private-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-crt-process-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-crt-runtime-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-crt-stdio-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-crt-string-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-crt-time-l1-1-0.dll;." % crt_path
)
run_win32.append(
"--add-binary=%s\\api-ms-win-crt-utility-l1-1-0.dll;." % crt_path
)
run_win32.append("--add-binary=%s\\ucrtbase.dll;." % crt_path)
print(run_win32) print(run_win32)
PyInstaller.__main__.run(run_win32) PyInstaller.__main__.run(run_win32)
if onefile == 'y': if onefile == "y":
# There's more proper ways to do this, but this doesn't require changing our paths # There's more proper ways to do this, but this doesn't require changing our paths
for x in data_folders: for x in data_folders:
print(x) print(x)
shutil.copytree(x, os.path.join('dist', data_folders[x]), shutil.copytree(
ignore=shutil.ignore_patterns('*.psd', x,
'*.xcf*', os.path.join("dist", data_folders[x]),
'ebg2.png', ignore=shutil.ignore_patterns(
'ebg1.png')) "*.psd", "*.xcf*", "ebg2.png", "ebg1.png"
),
)
for x in data_files: for x in data_files:
print(x) print(x)
shutil.copy(x, os.path.join('dist', data_files[x])) shutil.copy(x, os.path.join("dist", data_files[x]))
files = os.listdir('dist') files = os.listdir("dist")
try: try:
os.mkdir(os.path.join('dist', 'Pesterchum')) os.mkdir(os.path.join("dist", "Pesterchum"))
except FileExistsError: except FileExistsError:
pass pass
for x in files: for x in files:
shutil.move(os.path.join('dist',x), os.path.join('dist', 'Pesterchum')) shutil.move(os.path.join("dist", x), os.path.join("dist", "Pesterchum"))
#shutil.copy(os.path.join('build', 'Pesterchum', 'xref-Pesterchum.html'), # shutil.copy(os.path.join('build', 'Pesterchum', 'xref-Pesterchum.html'),
# os.path.join('dist', 'Pesterchum', 'xref-Pesterchum.html')) # os.path.join('dist', 'Pesterchum', 'xref-Pesterchum.html'))
#shutil.copy(os.path.join('build', 'Pesterchum', 'Analysis-00.toc'), # shutil.copy(os.path.join('build', 'Pesterchum', 'Analysis-00.toc'),
# os.path.join('dist', 'Pesterchum', 'Analysis-00.toc')) # os.path.join('dist', 'Pesterchum', 'Analysis-00.toc'))
#MacOS # MacOS
elif sys.platform == 'darwin' : elif sys.platform == "darwin":
run_darwin =[ run_darwin = [
'pesterchum.py', "pesterchum.py",
'--name=Pesterchum', "--name=Pesterchum",
#'--windowed', # Hide console #'--windowed', # Hide console
#'--noconfirm', # Overwrite output directory. #'--noconfirm', # Overwrite output directory.
'--icon=trayicon32.icns', # Icon "--icon=trayicon32.icns", # Icon
'--onedir', "--onedir",
'--clean', # Clear cache "--clean", # Clear cache
#'--noupx' #'--noupx'
] ]
if upx_enabled == 'y': if upx_enabled == "y":
if os.path.isdir(upx_dir): if os.path.isdir(upx_dir):
run_darwin.append('--upx-dir=%s' % upx_dir) run_darwin.append("--upx-dir=%s" % upx_dir)
for x in upx_exclude: for x in upx_exclude:
run_darwin.append('--upx-exclude=%s' % x ) run_darwin.append("--upx-exclude=%s" % x)
# Lower case variants are required. # Lower case variants are required.
run_darwin.append('--upx-exclude=%s' % x.lower() ) run_darwin.append("--upx-exclude=%s" % x.lower())
elif upx_enabled == 'n': elif upx_enabled == "n":
run_darwin.append('--noupx') run_darwin.append("--noupx")
if os.path.isdir(upx_dir): if os.path.isdir(upx_dir):
run_darwin.append('--upx-dir=%s' % upx_dir) run_darwin.append("--upx-dir=%s" % upx_dir)
for x in exclude_modules: for x in exclude_modules:
run_darwin.append('--exclude-module=%s' % x ) run_darwin.append("--exclude-module=%s" % x)
for x in add_data: for x in add_data:
run_darwin.append('--add-data=%s' % x.replace(';',':') ) run_darwin.append("--add-data=%s" % x.replace(";", ":"))
if windowed == 'y': if windowed == "y":
run_darwin.append('--windowed') run_darwin.append("--windowed")
PyInstaller.__main__.run(run_darwin) PyInstaller.__main__.run(run_darwin)
#Linux # Linux
elif sys.platform == 'linux': elif sys.platform == "linux":
run_linux =[ run_linux = [
'pesterchum.py', "pesterchum.py",
'--name=Pesterchum', "--name=Pesterchum",
#'--windowed', # Hide console #'--windowed', # Hide console
#'--noconfirm', # Overwrite output directory. #'--noconfirm', # Overwrite output directory.
'--icon=trayicon32.icns', # Icon "--icon=trayicon32.icns", # Icon
'--clean', # Clear cache "--clean", # Clear cache
] ]
if upx_enabled == 'y': if upx_enabled == "y":
if os.path.isdir(upx_dir): if os.path.isdir(upx_dir):
run_linux.append('--upx-dir=%s' % upx_dir) run_linux.append("--upx-dir=%s" % upx_dir)
for x in upx_exclude: for x in upx_exclude:
run_linux.append('--upx-exclude=%s' % x ) run_linux.append("--upx-exclude=%s" % x)
# Lower case variants are required. # Lower case variants are required.
run_linux.append('--upx-exclude=%s' % x.lower() ) run_linux.append("--upx-exclude=%s" % x.lower())
elif upx_enabled == 'n': elif upx_enabled == "n":
run_linux.append('--noupx') run_linux.append("--noupx")
for x in exclude_modules: for x in exclude_modules:
run_linux.append('--exclude-module=%s' % x ) run_linux.append("--exclude-module=%s" % x)
if onefile == 'y': if onefile == "y":
run_linux.append('--onefile') run_linux.append("--onefile")
elif onefile == 'n': elif onefile == "n":
for x in add_data: for x in add_data:
run_linux.append('--add-data=%s' % x.replace(';',':') ) run_linux.append("--add-data=%s" % x.replace(";", ":"))
if windowed == 'y': if windowed == "y":
run_linux.append('--windowed') run_linux.append("--windowed")
print(run_linux) print(run_linux)
PyInstaller.__main__.run(run_linux) PyInstaller.__main__.run(run_linux)
if onefile == 'y': if onefile == "y":
# There's more proper ways to do this, but this doesn't require changing our paths # There's more proper ways to do this, but this doesn't require changing our paths
for x in data_folders: for x in data_folders:
print(x) print(x)
shutil.copytree(x, os.path.join('dist', data_folders[x]), shutil.copytree(
ignore=shutil.ignore_patterns('*.psd', x,
'*.xcf*', os.path.join("dist", data_folders[x]),
'ebg2.png', ignore=shutil.ignore_patterns(
'ebg1.png')) "*.psd", "*.xcf*", "ebg2.png", "ebg1.png"
),
)
for x in data_files_linux: for x in data_files_linux:
print(x) print(x)
shutil.copy(x, os.path.join('dist', data_files_linux[x])) shutil.copy(x, os.path.join("dist", data_files_linux[x]))
files = os.listdir('dist') files = os.listdir("dist")
try: try:
os.mkdir(os.path.join('dist', '.cache')) os.mkdir(os.path.join("dist", ".cache"))
except FileExistsError as e: except FileExistsError as e:
print(e) print(e)
for x in files: for x in files:
try: try:
shutil.move(os.path.join('dist',x), os.path.join('dist', '.cache', x)) shutil.move(os.path.join("dist", x), os.path.join("dist", ".cache", x))
except FileExistsError as e: except FileExistsError as e:
print(e) print(e)
shutil.move(os.path.join('dist', '.cache'), os.path.join('dist', 'Pesterchum')) shutil.move(os.path.join("dist", ".cache"), os.path.join("dist", "Pesterchum"))
#shutil.copy(os.path.join('build', 'Pesterchum', 'xref-Pesterchum.html'), # shutil.copy(os.path.join('build', 'Pesterchum', 'xref-Pesterchum.html'),
# os.path.join('dist', 'Pesterchum', 'xref-Pesterchum.html')) # os.path.join('dist', 'Pesterchum', 'xref-Pesterchum.html'))
#shutil.copy(os.path.join('build', 'Pesterchum', 'Analysis-00.toc'), # shutil.copy(os.path.join('build', 'Pesterchum', 'Analysis-00.toc'),
# os.path.join('dist', 'Pesterchum', 'Analysis-00.toc')) # os.path.join('dist', 'Pesterchum', 'Analysis-00.toc'))
else: else:
print("Unknown platform.") print("Unknown platform.")
run_generic =[ run_generic = [
'pesterchum.py', "pesterchum.py",
'--name=Pesterchum' "--name=Pesterchum" "--clean", # Clear cache
'--clean', # Clear cache
] ]
if upx_enabled == 'y': if upx_enabled == "y":
if os.path.isdir(upx_dir): if os.path.isdir(upx_dir):
run_generic.append('--upx-dir=%s' % upx_dir) run_generic.append("--upx-dir=%s" % upx_dir)
else: else:
run_generic.append('--noupx') run_generic.append("--noupx")
for x in upx_exclude: for x in upx_exclude:
run_generic.append('--upx-exclude=%s' % x ) run_generic.append("--upx-exclude=%s" % x)
# Lower case variants are required. # Lower case variants are required.
run_generic.append('--upx-exclude=%s' % x.lower() ) run_generic.append("--upx-exclude=%s" % x.lower())
for x in exclude_modules: for x in exclude_modules:
run_generic.append('--exclude-module=%s' % x ) run_generic.append("--exclude-module=%s" % x)
for x in add_data: for x in add_data:
run_generic.append('--add-data=%s' % x.replace(';',':') ) run_generic.append("--add-data=%s" % x.replace(";", ":"))
if windowed == 'y': if windowed == "y":
run_win32.append('--windowed') run_win32.append("--windowed")
print(run_generic) print(run_generic)

View file

@ -10,12 +10,13 @@ except ImportError:
import ostools import ostools
from quirks import ScriptQuirks from quirks import ScriptQuirks
PchumLog = logging.getLogger('pchumLogger') PchumLog = logging.getLogger("pchumLogger")
class PythonQuirks(ScriptQuirks): class PythonQuirks(ScriptQuirks):
def loadModule(self, name, filename): def loadModule(self, name, filename):
# imp is deprecated since Python 3.4 # imp is deprecated since Python 3.4
#return imp.load_source(name, filename) # return imp.load_source(name, filename)
spec = importlib.util.spec_from_file_location(name, filename) spec = importlib.util.spec_from_file_location(name, filename)
module = importlib.util.module_from_spec(spec) module = importlib.util.module_from_spec(spec)
@ -23,25 +24,25 @@ class PythonQuirks(ScriptQuirks):
return module return module
def getExtension(self): def getExtension(self):
return '.py' return ".py"
def modHas(self, module, attr): def modHas(self, module, attr):
if attr == 'commands': if attr == "commands":
variables = vars(module) variables = vars(module)
for name, obj in variables.items(): for name, obj in variables.items():
if self.modHas(obj, 'command'): if self.modHas(obj, "command"):
return True return True
return hasattr(module, attr) return hasattr(module, attr)
def register(self, module): def register(self, module):
variables = vars(module) variables = vars(module)
for name, obj in variables.items(): for name, obj in variables.items():
if self.modHas(obj, 'command'): if self.modHas(obj, "command"):
try: try:
if not isinstance(obj("test"), str): if not isinstance(obj("test"), str):
raise Exception raise Exception
except: except:
#print("Quirk malformed: %s" % (obj.command)) # print("Quirk malformed: %s" % (obj.command))
PchumLog.error("Quirk malformed: %s" % (obj.command)) PchumLog.error("Quirk malformed: %s" % (obj.command))
# Since this is executed before QApplication is constructed, # Since this is executed before QApplication is constructed,
@ -55,4 +56,3 @@ class PythonQuirks(ScriptQuirks):
msgbox.exec() msgbox.exec()
else: else:
self.quirks[obj.command] = obj self.quirks[obj.command] = obj

View file

@ -1,12 +1,15 @@
import os import os
import socket import socket
class TwmnError(Exception): class TwmnError(Exception):
UNWN_ERR = -1 UNWN_ERR = -1
NO_TWMND = -2 NO_TWMND = -2
NO_CONF = -3 NO_CONF = -3
def __init__(self, code): def __init__(self, code):
self.code = code self.code = code
def __str__(self): def __str__(self):
if self.code == TwmnError.NO_TWMND: if self.code == TwmnError.NO_TWMND:
return "Unable to connect to twmnd" return "Unable to connect to twmnd"
@ -19,10 +22,12 @@ class TwmnError(Exception):
def confExists(): def confExists():
try: try:
from xdg import BaseDirectory from xdg import BaseDirectory
return os.path.join(BaseDirectory.xdg_config_home,"twmn/twmn.conf")
return os.path.join(BaseDirectory.xdg_config_home, "twmn/twmn.conf")
except: except:
return False return False
def init(host="127.0.0.1", port=None): def init(host="127.0.0.1", port=None):
if not port: if not port:
port = 9797 port = 9797
@ -32,8 +37,7 @@ def init(host="127.0.0.1", port=None):
return False return False
with open(fn) as f: with open(fn) as f:
for line in f.readlines(): for line in f.readlines():
if line.startswith("port=") and \ if line.startswith("port=") and line[5:-1].isdigit():
line[5:-1].isdigit():
port = int(line[5:-1]) port = int(line[5:-1])
break break
except IOError: except IOError:
@ -44,6 +48,7 @@ def init(host="127.0.0.1", port=None):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect((host, port)) s.connect((host, port))
class Notification(object): class Notification(object):
def __init__(self, title="", msg="", icon=""): def __init__(self, title="", msg="", icon=""):
self.title = str(title) self.title = str(title)
@ -59,17 +64,22 @@ class Notification(object):
def show(self): def show(self):
try: try:
if self.time is None: if self.time is None:
s.send("<root><title>" + self.title + "</title>" s.send(
"<root><title>" + self.title + "</title>"
"<content>" + self.msg + "</content>" "<content>" + self.msg + "</content>"
"<icon>" + self.icon + "</icon></root>") "<icon>" + self.icon + "</icon></root>"
)
else: else:
s.send("<root><title>" + self.title + "</title>" s.send(
"<root><title>" + self.title + "</title>"
"<content>" + self.msg + "</content>" "<content>" + self.msg + "</content>"
"<icon>" + self.icon + "</icon>" "<icon>" + self.icon + "</icon>"
"<duration>" + str(self.time) + "</duration></root>") "<duration>" + str(self.time) + "</duration></root>"
)
except: except:
raise TwmnError(TwmnError.NO_TWMND) raise TwmnError(TwmnError.NO_TWMND)
if __name__ == "__main__": if __name__ == "__main__":
init() init()
n = Notification("PyTwmn", "This is a notification!") n = Notification("PyTwmn", "This is a notification!")

View file

@ -4,7 +4,8 @@ import logging
import ostools import ostools
_datadir = ostools.getDataDir() _datadir = ostools.getDataDir()
PchumLog = logging.getLogger('pchumLogger') PchumLog = logging.getLogger("pchumLogger")
class ScriptQuirks(object): class ScriptQuirks(object):
def __init__(self): def __init__(self):
@ -13,7 +14,7 @@ class ScriptQuirks(object):
self.quirks = {} self.quirks = {}
self.last = {} self.last = {}
self.scripts = [] self.scripts = []
#self.load() # self.load()
def loadModule(self, name, filename): def loadModule(self, name, filename):
raise Exception raise Exception
@ -27,17 +28,17 @@ class ScriptQuirks(object):
for script in self.scripts: for script in self.scripts:
PchumLog.info(script.getExtension()) PchumLog.info(script.getExtension())
script.load() script.load()
#print script.quirks # print script.quirks
for q in script.quirks: for q in script.quirks:
self.quirks.update(script.quirks) self.quirks.update(script.quirks)
for k in self.last: for k in self.last:
if k in self.quirks: if k in self.quirks:
if self.last[k] == self.quirks[k]: if self.last[k] == self.quirks[k]:
del self.quirks[k] del self.quirks[k]
#print self.quirks # print self.quirks
if hasattr(self, 'quirks'): if hasattr(self, "quirks"):
# See https://stackoverflow.com/questions/12843099/python-logging-typeerror-not-all-arguments-converted-during-string-formatting # See https://stackoverflow.com/questions/12843099/python-logging-typeerror-not-all-arguments-converted-during-string-formatting
reg_quirks = ('Registered quirks:', '(), '.join(self.quirks) + "()") reg_quirks = ("Registered quirks:", "(), ".join(self.quirks) + "()")
PchumLog.info(reg_quirks) PchumLog.info(reg_quirks)
else: else:
PchumLog.warning("Couldn't find any script quirks") PchumLog.warning("Couldn't find any script quirks")
@ -50,18 +51,17 @@ class ScriptQuirks(object):
self.quirks.clear() self.quirks.clear()
extension = self.getExtension() extension = self.getExtension()
filenames = [] filenames = []
if not os.path.exists(os.path.join(self.home, 'quirks')): if not os.path.exists(os.path.join(self.home, "quirks")):
os.makedirs(os.path.join(self.home, 'quirks'), exist_ok=True) os.makedirs(os.path.join(self.home, "quirks"), exist_ok=True)
for fn in os.listdir(os.path.join(self.home, 'quirks')): for fn in os.listdir(os.path.join(self.home, "quirks")):
if fn.endswith(extension) and not fn.startswith('_'): if fn.endswith(extension) and not fn.startswith("_"):
filenames.append(os.path.join(self.home, 'quirks', fn)) filenames.append(os.path.join(self.home, "quirks", fn))
if hasattr(self, '_datadir'): if hasattr(self, "_datadir"):
if not os.path.exists(os.path.join(self._datadir, 'quirks')): if not os.path.exists(os.path.join(self._datadir, "quirks")):
os.makedirs(os.path.join(self._datadir, 'quirks'), exist_ok=True) os.makedirs(os.path.join(self._datadir, "quirks"), exist_ok=True)
for fn in os.listdir(os.path.join(self._datadir, 'quirks')): for fn in os.listdir(os.path.join(self._datadir, "quirks")):
if fn.endswith(extension) and not fn.startswith('_'): if fn.endswith(extension) and not fn.startswith("_"):
filenames.append(os.path.join(self._datadir, 'quirks', fn)) filenames.append(os.path.join(self._datadir, "quirks", fn))
modules = [] modules = []
for filename in filenames: for filename in filenames:
@ -72,11 +72,13 @@ class ScriptQuirks(object):
if module is None: if module is None:
continue continue
except Exception as e: except Exception as e:
PchumLog.warning("Error loading %s: %s (in quirks.py)" % (os.path.basename(name), e)) PchumLog.warning(
"Error loading %s: %s (in quirks.py)" % (os.path.basename(name), e)
)
else: else:
if self.modHas(module, 'setup'): if self.modHas(module, "setup"):
module.setup() module.setup()
if self.modHas(module, 'commands'): if self.modHas(module, "commands"):
self.register(module) self.register(module)
modules.append(name) modules.append(name)
for k in self.last: for k in self.last:
@ -85,10 +87,10 @@ class ScriptQuirks(object):
del self.quirks[k] del self.quirks[k]
def funcre(self): def funcre(self):
if not hasattr(self, 'quirks'): if not hasattr(self, "quirks"):
return r"\\[0-9]+" return r"\\[0-9]+"
f = r"(" f = r"("
for q in self.quirks: for q in self.quirks:
f = f + q+r"\(|" f = f + q + r"\(|"
f = f + r"\)|\\[0-9]+)" f = f + r"\)|\\[0-9]+)"
return f return f

View file

@ -1,17 +1,29 @@
import random import random
def upperrep(text): def upperrep(text):
return text.upper() return text.upper()
upperrep.command = "upper" upperrep.command = "upper"
def lowerrep(text): def lowerrep(text):
return text.lower() return text.lower()
lowerrep.command = "lower" lowerrep.command = "lower"
def scramblerep(text): def scramblerep(text):
return "".join(random.sample(text, len(text))) return "".join(random.sample(text, len(text)))
scramblerep.command = "scramble" scramblerep.command = "scramble"
def reverserep(text): def reverserep(text):
return text[::-1] return text[::-1]
reverserep.command = "reverse" reverserep.command = "reverse"

View file

@ -1,5 +1,6 @@
import re import re
def rainbow(text): def rainbow(text):
"""Example implementation of a gradient function, """Example implementation of a gradient function,
distributes colors over text, accounting for links, distributes colors over text, accounting for links,
@ -20,55 +21,51 @@ def rainbow(text):
^ There's more useful info here too :3c ^ There's more useful info here too :3c
""" """
# Values of 'gradient' can be any amount of hex/RGB colors. # Values of 'gradient' can be any amount of hex/RGB colors.
gradient = ["#ff0000", gradient = [
"#ff8000", "#ff0000",
"#ffff00", "#ff8000",
"#80ff00", "#ffff00",
"#00ff00", "#80ff00",
"#00ff80", "#00ff00",
"#00ffff", "#00ff80",
"#0080ff", "#00ffff",
"#0000ff", "#0080ff",
"#8000ff", "#0000ff",
"#ff00ff", "#8000ff",
"#ff0080"] "#ff00ff",
"#ff0080",
]
# Set base distribution of colors over text, # Set base distribution of colors over text,
# stored as list of lists. # stored as list of lists.
color_and_position = [] color_and_position = []
for color in range(0, len(gradient)): for color in range(0, len(gradient)):
ratio = len(text) / len(gradient) # To account for text length. ratio = len(text) / len(gradient) # To account for text length.
color_and_position.append( color_and_position.append([gradient[color], round(color * ratio)])
[gradient[color],
round(color * ratio)])
# Iterate through match object representing all links/smilies in text, # Iterate through match object representing all links/smilies in text,
# if a color tag is going to be placed within it, # if a color tag is going to be placed within it,
# move its position to after the link. # move its position to after the link.
for match in re.finditer(_urlre, text): for match in re.finditer(_urlre, text):
for cp in color_and_position: for cp in color_and_position:
if ((cp[1] >= match.start()) # cp[1] is pos if (cp[1] >= match.start()) and (cp[1] <= match.end()): # cp[1] is pos
and (cp[1] <= match.end())):
cp[1] = match.end() + 1 # Move to 1 character after link. cp[1] = match.end() + 1 # Move to 1 character after link.
for match in re.finditer(_smilere, text): for match in re.finditer(_smilere, text):
for cp in color_and_position: for cp in color_and_position:
if ((cp[1] >= match.start()) if (cp[1] >= match.start()) and (cp[1] <= match.end()):
and (cp[1] <= match.end())):
cp[1] = match.end() + 1 cp[1] = match.end() + 1
for match in re.finditer(_memore, text): for match in re.finditer(_memore, text):
for cp in color_and_position: for cp in color_and_position:
if ((cp[1] >= match.start()) if (cp[1] >= match.start()) and (cp[1] <= match.end()):
and (cp[1] <= match.end())):
cp[1] = match.end() + 1 cp[1] = match.end() + 1
for match in re.finditer(_handlere, text): for match in re.finditer(_handlere, text):
for cp in color_and_position: for cp in color_and_position:
if ((cp[1] >= match.start()) if (cp[1] >= match.start()) and (cp[1] <= match.end()):
and (cp[1] <= match.end())):
cp[1] = match.end() + 1 cp[1] = match.end() + 1
# Iterate through characters in text and write them to the output, # Iterate through characters in text and write them to the output,
# if a color tag should be placed, add it before the character. # if a color tag should be placed, add it before the character.
output = '' output = ""
for char in range(0, len(text)): for char in range(0, len(text)):
# Add color if at position. # Add color if at position.
for cp in color_and_position: for cp in color_and_position:
@ -82,6 +79,8 @@ def rainbow(text):
# Add character. # Add character.
output += text[char] output += text[char]
return output return output
rainbow.command = "rainbow" rainbow.command = "rainbow"
# These can't always be imported from their original functions, # These can't always be imported from their original functions,
@ -152,10 +151,10 @@ smiledict = {
":scorpio:": "scorpio.gif", ":scorpio:": "scorpio.gif",
":shades:": "shades.png", ":shades:": "shades.png",
":honk:": "honk.png", ":honk:": "honk.png",
} }
# Regular expression templates for detecting links/smilies. # Regular expression templates for detecting links/smilies.
_smilere = re.compile("|".join(list(smiledict.keys()))) _smilere = re.compile("|".join(list(smiledict.keys())))
_urlre = re.compile(r"(?i)(?:^|(?<=\s))(?:(?:https?|ftp)://|magnet:)[^\s]+") _urlre = re.compile(r"(?i)(?:^|(?<=\s))(?:(?:https?|ftp)://|magnet:)[^\s]+")
#_url2re = re.compile(r"(?i)(?<!//)\bwww\.[^\s]+?\.") # _url2re = re.compile(r"(?i)(?<!//)\bwww\.[^\s]+?\.")
_memore = re.compile(r"(\s|^)(#[A-Za-z0-9_]+)") _memore = re.compile(r"(\s|^)(#[A-Za-z0-9_]+)")
_handlere = re.compile(r"(\s|^)(@[A-Za-z0-9_]+)") _handlere = re.compile(r"(\s|^)(@[A-Za-z0-9_]+)")

View file

@ -8,10 +8,11 @@ except ImportError:
import ostools import ostools
PchumLog = logging.getLogger('pchumLogger') PchumLog = logging.getLogger("pchumLogger")
RANDNICK = "randomEncounter" RANDNICK = "randomEncounter"
class RandomHandler(QtCore.QObject): class RandomHandler(QtCore.QObject):
def __init__(self, parent): def __init__(self, parent):
QtCore.QObject.__init__(self, parent) QtCore.QObject.__init__(self, parent)
@ -30,14 +31,18 @@ class RandomHandler(QtCore.QObject):
def setRandomer(self, r): def setRandomer(self, r):
if r != self.mainwindow.userprofile.getRandom(): if r != self.mainwindow.userprofile.getRandom():
if r: code = "+" if r:
else: code = "-" code = "+"
else:
code = "-"
self.queue.append(code) self.queue.append(code)
self.mainwindow.sendNotice.emit(code, self.randNick) self.mainwindow.sendNotice.emit(code, self.randNick)
def setIdle(self, i): def setIdle(self, i):
if i: code = "~" if i:
else: code = "*" code = "~"
else:
code = "*"
self.queue.append(code) self.queue.append(code)
self.mainwindow.sendNotice.emit(code, self.randNick) self.mainwindow.sendNotice.emit(code, self.randNick)
@ -50,20 +55,20 @@ class RandomHandler(QtCore.QObject):
l = msg.split("=") l = msg.split("=")
code = l[0][0] code = l[0][0]
if code not in self.queue: if code not in self.queue:
return # Ignore if we didn't request this return # Ignore if we didn't request this
self.queue.remove(code) self.queue.remove(code)
if code == "?": if code == "?":
if l[1][0] == "y": if l[1][0] == "y":
self.mainwindow.userprofile.setRandom(True) self.mainwindow.userprofile.setRandom(True)
elif l[1][0] == "n": elif l[1][0] == "n":
self.mainwindow.userprofile.setRandom(False) self.mainwindow.userprofile.setRandom(False)
elif code in ["+","-"]: elif code in ["+", "-"]:
if l[1][0] == "k": if l[1][0] == "k":
if code == "+": if code == "+":
self.mainwindow.userprofile.setRandom(True) self.mainwindow.userprofile.setRandom(True)
else: else:
self.mainwindow.userprofile.setRandom(False) self.mainwindow.userprofile.setRandom(False)
elif code in ["~","*"]: elif code in ["~", "*"]:
if l[1][0] == "k": if l[1][0] == "k":
pass pass
elif code == "!": elif code == "!":

View file

@ -7,12 +7,12 @@ import os
import sys import sys
from subprocess import call from subprocess import call
#print(sys.argv) # print(sys.argv)
def main(argv): def main(argv):
arguments = '' arguments = ""
for x in argv[1:]: for x in argv[1:]:
arguments += x + ' ' arguments += x + " "
#print(arguments) # print(arguments)
directory_path = os.getcwd() directory_path = os.getcwd()
print("Working directory: " + directory_path) print("Working directory: " + directory_path)
@ -22,5 +22,6 @@ def main(argv):
retcode = call("python3 pesterchum.py " + " " + str(arguments), shell=True) retcode = call("python3 pesterchum.py " + " " + str(arguments), shell=True)
print(retcode) print(retcode)
if __name__ == "__main__": if __name__ == "__main__":
main(sys.argv) main(sys.argv)

205
setup.py
View file

@ -9,9 +9,11 @@ from version import buildVersion
if sys.version_info < (3, 0, 0): if sys.version_info < (3, 0, 0):
sys.exit("Python versions lower than 3 are not supported.") sys.exit("Python versions lower than 3 are not supported.")
def is_64bit() -> bool: def is_64bit() -> bool:
return sys.maxsize > 2**32 return sys.maxsize > 2**32
path = "" path = ""
base = None base = None
if sys.platform == "win32": if sys.platform == "win32":
@ -19,120 +21,131 @@ if sys.platform == "win32":
path = sys.path path = sys.path
if is_64bit() == True: if is_64bit() == True:
path.append(r"C:\Program Files (x86)\Windows Kits\10\Redist\10.0.22000.0\ucrt\DLLs\x64") path.append(
r"C:\Program Files (x86)\Windows Kits\10\Redist\10.0.22000.0\ucrt\DLLs\x64"
)
elif is_64bit() == False: elif is_64bit() == False:
path.append(r"C:\Program Files (x86)\Windows Kits\10\Redist\10.0.22000.0\ucrt\DLLs\x86") path.append(
r"C:\Program Files (x86)\Windows Kits\10\Redist\10.0.22000.0\ucrt\DLLs\x86"
)
print("Path = " + str(path)) print("Path = " + str(path))
includefiles = ["quirks", includefiles = [
"smilies", "quirks",
"themes", "smilies",
"docs", "themes",
"README.md", "docs",
"LICENSE", "README.md",
"CHANGELOG.md", "LICENSE",
"PCskins.png", "CHANGELOG.md",
"Pesterchum.png"] "PCskins.png",
"Pesterchum.png",
]
build_exe_options = { build_exe_options = {
# "includes": ['PyQt6.QtCore', # "includes": ['PyQt6.QtCore',
# 'PyQt6.QtGui', # 'PyQt6.QtGui',
# 'PyQt6.QtWidgets'], # 'PyQt6.QtWidgets'],
"excludes": ['collections.sys', "excludes": [
'collections._sre', "collections.sys",
'collections._json', "collections._sre",
'collections._locale', "collections._json",
'collections._struct', "collections._locale",
'collections.array', "collections._struct",
'collections._weakref', "collections.array",
'PyQt6.QtMultimedia', "collections._weakref",
'PyQt6.QtDBus', "PyQt6.QtMultimedia",
'PyQt6.QtDeclarative', "PyQt6.QtDBus",
'PyQt6.QtHelp', "PyQt6.QtDeclarative",
'PyQt6.QtNetwork', "PyQt6.QtHelp",
'PyQt6.QtSql', "PyQt6.QtNetwork",
'PyQt6.QtSvg', "PyQt6.QtSql",
'PyQt6.QtTest', "PyQt6.QtSvg",
'PyQt6.QtWebKit', "PyQt6.QtTest",
'PyQt6.QtXml', "PyQt6.QtWebKit",
'PyQt6.QtXmlPatterns', "PyQt6.QtXml",
'PyQt6.phonon', "PyQt6.QtXmlPatterns",
'PyQt6.QtAssistant', "PyQt6.phonon",
'PyQt6.QtDesigner', "PyQt6.QtAssistant",
'PyQt6.QAxContainer', "PyQt6.QtDesigner",
'pygame.docs' # Hopefully we can just not have pygame at all at some point =3 "PyQt6.QAxContainer",
# (when QtMultimedia stops relying on local codecs </3) "pygame.docs" # Hopefully we can just not have pygame at all at some point =3
'pygame.examples', # (when QtMultimedia stops relying on local codecs </3)
'pygame.tests', "pygame.examples",
'pydoc_data'], "pygame.tests",
"pydoc_data",
],
"include_files": includefiles, "include_files": includefiles,
"include_msvcr": True, # cx_freeze copies 64-bit binaries always? "include_msvcr": True, # cx_freeze copies 64-bit binaries always?
"path": path # Improved in 6.6, path to be safe "path": path # Improved in 6.6, path to be safe
# VCRUNTIME140.dll <3 # VCRUNTIME140.dll <3
} }
if (sys.platform == 'win32') & (sys.version_info.major == 3) & (sys.version_info.minor == 8): if (
build_exe_options["excludes"].append('tkinter') (sys.platform == "win32")
& (sys.version_info.major == 3)
& (sys.version_info.minor == 8)
):
build_exe_options["excludes"].append("tkinter")
bdist_mac_options = { bdist_mac_options = {"iconfile": "trayicon32.icns", "bundle_name": "Pesterchum"}
'iconfile': 'trayicon32.icns',
'bundle_name': "Pesterchum"
}
description = "Pesterchum" description = "Pesterchum"
icon = "pesterchum.ico" icon = "pesterchum.ico"
# See https://stackoverflow.com/questions/15734703/use-cx-freeze-to-create-an-msi-that-adds-a-shortcut-to-the-desktop # See https://stackoverflow.com/questions/15734703/use-cx-freeze-to-create-an-msi-that-adds-a-shortcut-to-the-desktop
shortcut_table = [ shortcut_table = [
("DesktopShortcut", # Shortcut (
"DesktopFolder", # Directory_ "DesktopShortcut", # Shortcut
"Pesterchum", # Name "DesktopFolder", # Directory_
"TARGETDIR", # Component_ "Pesterchum", # Name
"[TARGETDIR]pesterchum.exe",# Target "TARGETDIR", # Component_
None, # Arguments "[TARGETDIR]pesterchum.exe", # Target
description, # Description None, # Arguments
None, # Hotkey description, # Description
None, # Icon (Is inherited from pesterchum.exe) None, # Hotkey
None, # IconIndex None, # Icon (Is inherited from pesterchum.exe)
None, # ShowCmd None, # IconIndex
'TARGETDIR' # WkDir None, # ShowCmd
), "TARGETDIR", # WkDir
("StartMenuShortcut", # Shortcut ),
"StartMenuFolder", # Directory_ (
"Pesterchum", # Name "StartMenuShortcut", # Shortcut
"TARGETDIR", # Component_ "StartMenuFolder", # Directory_
"[TARGETDIR]pesterchum.exe",# Target "Pesterchum", # Name
None, # Arguments "TARGETDIR", # Component_
description, # Description "[TARGETDIR]pesterchum.exe", # Target
None, # Hotkey None, # Arguments
None, # Icon description, # Description
None, # IconIndex None, # Hotkey
None, # ShowCmd None, # Icon
'TARGETDIR' # WkDir None, # IconIndex
) None, # ShowCmd
] "TARGETDIR", # WkDir
),
]
msi_data = {"Shortcut": shortcut_table} msi_data = {"Shortcut": shortcut_table}
bdist_msi_options = {'data': msi_data, bdist_msi_options = {
'summary_data': { "data": msi_data,
'comments': "FL1P", "summary_data": {"comments": "FL1P", "keywords": "Pesterchum"},
'keywords': "Pesterchum"}, "upgrade_code": "{86740d75-f1f2-48e8-8266-f36395a2d77f}",
'upgrade_code': "{86740d75-f1f2-48e8-8266-f36395a2d77f}", "add_to_path": False, # !!!
'add_to_path': False, # !!! "all_users": False,
'all_users': False, "install_icon": "pesterchum.ico",
'install_icon': "pesterchum.ico"} }
setup( setup(
name = "Pesterchum", name="Pesterchum",
version = buildVersion, version=buildVersion,
url = "https://github.com/Dpeta/pesterchum-alt-servers", url="https://github.com/Dpeta/pesterchum-alt-servers",
description = description,#"P3ST3RCHUM", description=description, # "P3ST3RCHUM",
options = {"build_exe": build_exe_options, options={
"bdist_msi": bdist_msi_options, "build_exe": build_exe_options,
"bdist_mac": bdist_mac_options}, "bdist_msi": bdist_msi_options,
packages="", "bdist_mac": bdist_mac_options,
executables = [Executable("pesterchum.py", },
base=base, packages="",
icon=icon executables=[Executable("pesterchum.py", base=base, icon=icon)],
)]) )

237
toast.py
View file

@ -1,5 +1,6 @@
import os import os
#import time
# import time
import inspect import inspect
import logging import logging
@ -12,25 +13,28 @@ except ImportError:
import ostools import ostools
_datadir = ostools.getDataDir() _datadir = ostools.getDataDir()
PchumLog = logging.getLogger('pchumLogger') PchumLog = logging.getLogger("pchumLogger")
#try: # try:
# import pynotify # import pynotify
#except: # except:
# pynotify = None # pynotify = None
# Pynotify is broken. # Pynotify is broken.
pynotify = None pynotify = None
class DefaultToast(object): class DefaultToast(object):
def __init__(self, machine, title, msg, icon): def __init__(self, machine, title, msg, icon):
self.machine = machine self.machine = machine
self.title = title self.title = title
self.msg = msg self.msg = msg
self.icon = icon self.icon = icon
def show(self): def show(self):
print(self.title, self.msg, self.icon) print(self.title, self.msg, self.icon)
self.done() self.done()
def done(self): def done(self):
t = self.machine.toasts[0] t = self.machine.toasts[0]
if t.title == self.title and t.msg == self.msg and t.icon == self.icon: if t.title == self.title and t.msg == self.msg and t.icon == self.icon:
@ -38,16 +42,17 @@ class DefaultToast(object):
self.machine.displaying = False self.machine.displaying = False
PchumLog.info("Done") PchumLog.info("Done")
class ToastMachine(object): class ToastMachine(object):
class __Toast__(object): class __Toast__(object):
def __init__(self, machine, title, msg, time=3000, icon="", importance=0): def __init__(self, machine, title, msg, time=3000, icon="", importance=0):
self.machine = machine self.machine = machine
self.title = title self.title = title
self.msg = msg self.msg = msg
self.time = time self.time = time
if icon: if icon:
icon = os.path.abspath(icon) icon = os.path.abspath(icon)
self.icon = icon self.icon = icon
self.importance = importance self.importance = importance
if inspect.ismethod(self.title) or inspect.isfunction(self.title): if inspect.ismethod(self.title) or inspect.isfunction(self.title):
self.title = self.title() self.title = self.title()
@ -57,19 +62,32 @@ class ToastMachine(object):
self.title = title self.title = title
if inspect.ismethod(self.title) or inspect.isfunction(self.title): if inspect.ismethod(self.title) or inspect.isfunction(self.title):
self.title = self.title() self.title = self.title()
else: return self.title else:
return self.title
def msgM(self, msg=None): def msgM(self, msg=None):
if msg: self.msg = msg if msg:
else: return self.msg self.msg = msg
else:
return self.msg
def timeM(self, time=None): def timeM(self, time=None):
if time: self.time = time if time:
else: return self.time self.time = time
else:
return self.time
def iconM(self, icon=None): def iconM(self, icon=None):
if icon: self.icon = icon if icon:
else: return self.icon self.icon = icon
else:
return self.icon
def importanceM(self, importance=None): def importanceM(self, importance=None):
if importance != None: self.importance = importance if importance != None:
else: return self.importance self.importance = importance
else:
return self.importance
def show(self): def show(self):
if self.machine.on: if self.machine.on:
@ -85,7 +103,7 @@ class ToastMachine(object):
def realShow(self): def realShow(self):
self.machine.displaying = True self.machine.displaying = True
t = None t = None
for (k,v) in self.machine.types.items(): for (k, v) in self.machine.types.items():
if self.machine.type == k: if self.machine.type == k:
try: try:
args = inspect.getargspec(v.__init__).args args = inspect.getargspec(v.__init__).args
@ -93,10 +111,10 @@ class ToastMachine(object):
args = [] args = []
extras = {} extras = {}
if 'parent' in args: if "parent" in args:
extras['parent'] = self.machine.parent extras["parent"] = self.machine.parent
if 'time' in args: if "time" in args:
extras['time'] = self.time extras["time"] = self.time
if k == "libnotify" or k == "twmn": if k == "libnotify" or k == "twmn":
t = v(self.title, self.msg, self.icon, **extras) t = v(self.title, self.msg, self.icon, **extras)
else: else:
@ -111,28 +129,48 @@ class ToastMachine(object):
t.set_urgency(pynotify.URGENCY_LOW) t.set_urgency(pynotify.URGENCY_LOW)
break break
if not t: if not t:
if 'default' in self.machine.types: if "default" in self.machine.types:
if 'parent' in inspect.getargspec(self.machine.types['default'].__init__).args: if (
t = self.machine.types['default'](self.machine, self.title, self.msg, self.icon, self.machine.parent) "parent"
in inspect.getargspec(
self.machine.types["default"].__init__
).args
):
t = self.machine.types["default"](
self.machine,
self.title,
self.msg,
self.icon,
self.machine.parent,
)
else: else:
t = self.machine.types['default'](self.machine, self.title, self.msg, self.icon) t = self.machine.types["default"](
self.machine, self.title, self.msg, self.icon
)
else: else:
t = DefaultToast(self.title, self.msg, self.icon) t = DefaultToast(self.title, self.msg, self.icon)
t.show() t.show()
def __init__(self, parent, name, on=True, type="default", def __init__(
types=({'default' : DefaultToast, self,
'libnotify': pynotify.Notification} parent,
if pynotify else name,
{'default' : DefaultToast}), on=True,
extras={}): type="default",
self.parent = parent types=(
self.name = name {"default": DefaultToast, "libnotify": pynotify.Notification}
self.on = on if pynotify
else {"default": DefaultToast}
),
extras={},
):
self.parent = parent
self.name = name
self.on = on
types.update(extras) types.update(extras)
self.types = types self.types = types
self.type = "default" self.type = "default"
self.quit = False self.quit = False
self.displaying = False self.displaying = False
self.setCurrentType(type) self.setCurrentType(type)
@ -143,7 +181,7 @@ class ToastMachine(object):
return self.__Toast__(self, title, msg, time=time, icon=icon) return self.__Toast__(self, title, msg, time=time, icon=icon)
def setEnabled(self, on): def setEnabled(self, on):
self.on = (on is True) self.on = on is True
def currentType(self): def currentType(self):
return self.type return self.type
@ -157,15 +195,16 @@ class ToastMachine(object):
if not pynotify or not pynotify.init("ToastMachine"): if not pynotify or not pynotify.init("ToastMachine"):
PchumLog.info("Problem initilizing pynotify") PchumLog.info("Problem initilizing pynotify")
return return
#self.type = type = "default" # self.type = type = "default"
elif type == "twmn": elif type == "twmn":
import pytwmn import pytwmn
try: try:
pytwmn.init() pytwmn.init()
except pytwmn.ERROR as e: except pytwmn.ERROR as e:
PchumLog.error("Problem initilizing pytwmn: " + str(e)) PchumLog.error("Problem initilizing pytwmn: " + str(e))
return return
#self.type = type = "default" # self.type = type = "default"
self.type = type self.type = type
def appName(self): def appName(self):
@ -200,7 +239,11 @@ class PesterToast(QtWidgets.QWidget, DefaultToast):
if ostools.isWin32(): if ostools.isWin32():
self.setWindowFlags(QtCore.Qt.WindowType.ToolTip) self.setWindowFlags(QtCore.Qt.WindowType.ToolTip)
else: else:
self.setWindowFlags(QtCore.Qt.WindowType.WindowStaysOnTopHint | QtCore.Qt.WindowType.X11BypassWindowManagerHint | QtCore.Qt.WindowType.ToolTip) self.setWindowFlags(
QtCore.Qt.WindowType.WindowStaysOnTopHint
| QtCore.Qt.WindowType.X11BypassWindowManagerHint
| QtCore.Qt.WindowType.ToolTip
)
self.m_animation = QtCore.QParallelAnimationGroup() self.m_animation = QtCore.QParallelAnimationGroup()
anim = QtCore.QPropertyAnimation(self) anim = QtCore.QPropertyAnimation(self)
@ -219,7 +262,7 @@ class PesterToast(QtWidgets.QWidget, DefaultToast):
self.icon = QtWidgets.QLabel("") self.icon = QtWidgets.QLabel("")
iconPixmap = QtGui.QPixmap(icon).scaledToWidth(30) iconPixmap = QtGui.QPixmap(icon).scaledToWidth(30)
self.icon.setPixmap(iconPixmap) self.icon.setPixmap(iconPixmap)
#else: # else:
# self.icon.setPixmap(QtGui.QPixmap(30, 30)) # self.icon.setPixmap(QtGui.QPixmap(30, 30))
# self.icon.pixmap().fill(QtGui.QColor(0,0,0,0)) # self.icon.pixmap().fill(QtGui.QColor(0,0,0,0))
@ -228,8 +271,8 @@ class PesterToast(QtWidgets.QWidget, DefaultToast):
if self.icon: if self.icon:
layout_1 = QtWidgets.QGridLayout() layout_1 = QtWidgets.QGridLayout()
layout_1.addWidget(self.icon, 0,0, 1,1) layout_1.addWidget(self.icon, 0, 0, 1, 1)
layout_1.addWidget(self.title, 0,1, 1,7) layout_1.addWidget(self.title, 0, 1, 1, 7)
layout_1.setAlignment(self.msg, QtCore.Qt.AlignmentFlag.AlignTop) layout_1.setAlignment(self.msg, QtCore.Qt.AlignmentFlag.AlignTop)
layout_0.addLayout(layout_1) layout_0.addLayout(layout_1)
else: else:
@ -242,7 +285,12 @@ class PesterToast(QtWidgets.QWidget, DefaultToast):
self.setLayout(layout_0) self.setLayout(layout_0)
self.setGeometry(0,0, self.parent().theme["toasts/width"], self.parent().theme["toasts/height"]) self.setGeometry(
0,
0,
self.parent().theme["toasts/width"],
self.parent().theme["toasts/height"],
)
self.setStyleSheet(self.parent().theme["toasts/style"]) self.setStyleSheet(self.parent().theme["toasts/style"])
self.title.setStyleSheet(self.parent().theme["toasts/title/style"]) self.title.setStyleSheet(self.parent().theme["toasts/title/style"])
if self.icon: if self.icon:
@ -250,13 +298,17 @@ class PesterToast(QtWidgets.QWidget, DefaultToast):
self.msg.setStyleSheet(self.parent().theme["toasts/content/style"]) self.msg.setStyleSheet(self.parent().theme["toasts/content/style"])
self.layout().setSpacing(0) self.layout().setSpacing(0)
self.msg.setText(PesterToast.wrapText(self.msg.font(), self.msg.setText(
str(self.msg.text()), PesterToast.wrapText(
self.parent().theme["toasts/width"], self.msg.font(),
self.parent().theme["toasts/content/style"])) str(self.msg.text()),
self.parent().theme["toasts/width"],
self.parent().theme["toasts/content/style"],
)
)
screens = QtWidgets.QApplication.screens() screens = QtWidgets.QApplication.screens()
screen = screens[0] # Should be the main one right??? screen = screens[0] # Should be the main one right???
# This 100% doesn't work with multiple screens. # This 100% doesn't work with multiple screens.
p = screen.availableGeometry().bottomRight() p = screen.availableGeometry().bottomRight()
o = screen.geometry().bottomRight() o = screen.geometry().bottomRight()
@ -274,8 +326,7 @@ class PesterToast(QtWidgets.QWidget, DefaultToast):
def done(self): def done(self):
QtWidgets.QWidget.hide(self) QtWidgets.QWidget.hide(self)
t = self.machine.toasts[0] t = self.machine.toasts[0]
if t.title == str(self.title.text()) and \ if t.title == str(self.title.text()) and t.msg == str(self.content):
t.msg == str(self.content):
self.machine.toasts.pop(0) self.machine.toasts.pop(0)
self.machine.displaying = False self.machine.displaying = False
if self.machine.on: if self.machine.on:
@ -300,13 +351,13 @@ class PesterToast(QtWidgets.QWidget, DefaultToast):
@QtCore.pyqtSlot(QtCore.QVariant) @QtCore.pyqtSlot(QtCore.QVariant)
def updateBottomLeftAnimation(self, value): def updateBottomLeftAnimation(self, value):
#p = QtWidgets.QApplication.desktop().availableGeometry(self).bottomRight() # p = QtWidgets.QApplication.desktop().availableGeometry(self).bottomRight()
screens = QtWidgets.QApplication.screens() screens = QtWidgets.QApplication.screens()
screen = screens[0] # Main window? screen = screens[0] # Main window?
p = screen.availableGeometry().bottomRight() p = screen.availableGeometry().bottomRight()
val = (self.height())/100 val = (self.height()) / 100
# Does type casting this to an int have any negative consequences? # Does type casting this to an int have any negative consequences?
self.move(int(p.x()-self.width()), int(p.y() - (value * val) +1)) self.move(int(p.x() - self.width()), int(p.y() - (value * val) + 1))
self.layout().setSpacing(0) self.layout().setSpacing(0)
QtWidgets.QWidget.show(self) QtWidgets.QWidget.show(self)
@ -321,21 +372,21 @@ class PesterToast(QtWidgets.QWidget, DefaultToast):
ret = [] ret = []
metric = QtGui.QFontMetrics(font) metric = QtGui.QFontMetrics(font)
if "padding" in css: if "padding" in css:
if css[css.find("padding")+7] != "-": if css[css.find("padding") + 7] != "-":
colon = css.find(":", css.find("padding")) colon = css.find(":", css.find("padding"))
semicolon = css.find(";", css.find("padding")) semicolon = css.find(";", css.find("padding"))
if semicolon < 0: if semicolon < 0:
stuff = css[colon+1:] stuff = css[colon + 1 :]
else: else:
stuff = css[colon+1:semicolon] stuff = css[colon + 1 : semicolon]
stuff = stuff.replace("px", "").lstrip().rstrip() stuff = stuff.replace("px", "").lstrip().rstrip()
stuff = stuff.split(" ") stuff = stuff.split(" ")
if len(stuff) == 1: if len(stuff) == 1:
maxwidth -= int(stuff[0])*2 maxwidth -= int(stuff[0]) * 2
elif len(stuff) == 2: elif len(stuff) == 2:
maxwidth -= int(stuff[1])*2 maxwidth -= int(stuff[1]) * 2
elif len(stuff) == 3: elif len(stuff) == 3:
maxwidth -= int(stuff[1])*2 maxwidth -= int(stuff[1]) * 2
elif len(stuff) == 4: elif len(stuff) == 4:
maxwidth -= int(stuff[1]) + int(stuff[3]) maxwidth -= int(stuff[1]) + int(stuff[3])
else: else:
@ -343,9 +394,9 @@ class PesterToast(QtWidgets.QWidget, DefaultToast):
colon = css.find(":", css.find("padding-left")) colon = css.find(":", css.find("padding-left"))
semicolon = css.find(";", css.find("padding-left")) semicolon = css.find(";", css.find("padding-left"))
if semicolon < 0: if semicolon < 0:
stuff = css[colon+1:] stuff = css[colon + 1 :]
else: else:
stuff = css[colon+1:semicolon] stuff = css[colon + 1 : semicolon]
stuff = stuff.replace("px", "").lstrip().rstrip() stuff = stuff.replace("px", "").lstrip().rstrip()
if stuff.isdigit(): if stuff.isdigit():
maxwidth -= int(stuff) maxwidth -= int(stuff)
@ -353,9 +404,9 @@ class PesterToast(QtWidgets.QWidget, DefaultToast):
colon = css.find(":", css.find("padding-right")) colon = css.find(":", css.find("padding-right"))
semicolon = css.find(";", css.find("padding-right")) semicolon = css.find(";", css.find("padding-right"))
if semicolon < 0: if semicolon < 0:
stuff = css[colon+1:] stuff = css[colon + 1 :]
else: else:
stuff = css[colon+1:semicolon] stuff = css[colon + 1 : semicolon]
stuff = stuff.replace("px", "").lstrip().rstrip() stuff = stuff.replace("px", "").lstrip().rstrip()
if stuff.isdigit(): if stuff.isdigit():
maxwidth -= int(stuff) maxwidth -= int(stuff)
@ -367,28 +418,36 @@ class PesterToast(QtWidgets.QWidget, DefaultToast):
curspace = lastspace curspace = lastspace
while metric.horizontalAdvance(text, curspace) < maxwidth: while metric.horizontalAdvance(text, curspace) < maxwidth:
lastspace = curspace lastspace = curspace
curspace = text.find(" ", lastspace+1) curspace = text.find(" ", lastspace + 1)
if curspace == -1: if curspace == -1:
break break
if (metric.horizontalAdvance(text[:lastspace]) > maxwidth) or \ if (metric.horizontalAdvance(text[:lastspace]) > maxwidth) or len(
len(text[:lastspace]) < 1: text[:lastspace]
) < 1:
for i in range(len(text)): for i in range(len(text)):
if metric.horizontalAdvance(text[:i]) > maxwidth: if metric.horizontalAdvance(text[:i]) > maxwidth:
lastspace = i-1 lastspace = i - 1
break break
ret.append(text[:lastspace]) ret.append(text[:lastspace])
text = text[lastspace+1:] text = text[lastspace + 1 :]
ret.append(text) ret.append(text)
return "\n".join(ret) return "\n".join(ret)
class PesterToastMachine(ToastMachine, QtCore.QObject): class PesterToastMachine(ToastMachine, QtCore.QObject):
def __init__(self, parent, name, on=True, type="default", def __init__(
types=({'default' : DefaultToast, self,
'libnotify' : pynotify.Notification} parent,
if pynotify else name,
{'default' : DefaultToast}), on=True,
extras={}): type="default",
types=(
{"default": DefaultToast, "libnotify": pynotify.Notification}
if pynotify
else {"default": DefaultToast}
),
extras={},
):
ToastMachine.__init__(self, parent, name, on, type, types, extras) ToastMachine.__init__(self, parent, name, on, type, types, extras)
QtCore.QObject.__init__(self, parent) QtCore.QObject.__init__(self, parent)
@ -396,7 +455,7 @@ class PesterToastMachine(ToastMachine, QtCore.QObject):
oldon = self.on oldon = self.on
ToastMachine.setEnabled(self, on) ToastMachine.setEnabled(self, on)
if oldon != self.on: if oldon != self.on:
self.parent.config.set('notify', self.on) self.parent.config.set("notify", self.on)
if self.on: if self.on:
self.timer.start() self.timer.start()
else: else:
@ -406,7 +465,7 @@ class PesterToastMachine(ToastMachine, QtCore.QObject):
oldtype = self.type oldtype = self.type
ToastMachine.setCurrentType(self, type) ToastMachine.setCurrentType(self, type)
if oldtype != self.type: if oldtype != self.type:
self.parent.config.set('notifyType', self.type) self.parent.config.set("notifyType", self.type)
@QtCore.pyqtSlot() @QtCore.pyqtSlot()
def showNext(self): def showNext(self):
@ -414,9 +473,9 @@ class PesterToastMachine(ToastMachine, QtCore.QObject):
def run(self): def run(self):
pass pass
#~ self.timer = QtCore.QTimer(self) # ~ self.timer = QtCore.QTimer(self)
#~ self.timer.setInterval(1000) # ~ self.timer.setInterval(1000)
#~ self.connect(self.timer, QtCore.SIGNAL('timeout()'), # ~ self.connect(self.timer, QtCore.SIGNAL('timeout()'),
#~ self, QtCore.SLOT('showNext()')) # ~ self, QtCore.SLOT('showNext()'))
#~ if self.on: # ~ if self.on:
#~ self.timer.start() # ~ self.timer.start()