Skip to content

Commit eb7962a

Browse files
authored
Merge pull request #391 from gobo7793/development
release
2 parents df83a02 + 3c2fb84 commit eb7962a

20 files changed

Lines changed: 1067 additions & 1745 deletions

Geckarbot.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class Geckarbot(BaseBot):
3232
Basic bot info
3333
"""
3434
NAME = "Geckarbot"
35-
VERSION = "2.18.1"
35+
VERSION = "2.19.0"
3636
PLUGIN_DIR = "plugins"
3737
CORE_PLUGIN_DIR = "coreplugins"
3838
CONFIG_DIR = "config"

base/data.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,7 @@ class Lang(metaclass=_Singleton):
335335
"unmute": "🔊",
336336
"startup": "🔨",
337337
"debug": "🐞",
338+
"notfound": "🙈",
338339
"lettermap": [
339340
"🇦", # a
340341
"🇧", # b

botutils/sheetsclient.py

Lines changed: 111 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import logging
22
import os
33
import re
4+
import string
45
import urllib.parse
56
from typing import Optional, Dict, Tuple, Union, List
67

@@ -22,142 +23,199 @@ class Cell:
2223
"""
2324
Representation of a sheet cell
2425
"""
25-
def __init__(self, column: int, row: int, grid=None):
26+
27+
MAX_COLUMNS = 18_278
28+
MAX_ROWS = 200_000_000
29+
30+
def __init__(self, column: int, row: int, grid: 'CellRange' = None):
2631
"""
2732
Representation of a single cell. Note: rows and columns in a grid begin at 1!
2833
2934
:param column: column coordinate
3035
:param row: row coordinate
3136
:param grid: CellRange the cell coordinates are dependent on
32-
:type grid: CellRange
3337
"""
3438

3539
self.column = column
3640
self.row = row
3741
self.grid = grid
3842

3943
@classmethod
40-
def from_a1(cls, a1_notation: str):
44+
def from_a1(cls, a1_notation: str, maximize_if_undefined: bool = False) -> 'Cell':
4145
"""
4246
Building the cell from the A1-notation.
4347
4448
:param a1_notation: A1-notation of the cell e.g. "A4" or "BE34"
45-
:rtype: Cell
49+
:param maximize_if_undefined: if the notation does not define for row or column, the value will be set to
50+
maximum row/column instead of the first.
4651
:return: Cell
4752
:raises ValueError: if invalid notation
4853
"""
49-
extract = re.search("(?P<col>[A-Z]+)(?P<row>\\d+)", a1_notation)
50-
if extract:
51-
groupdict = extract.groupdict()
52-
# Converts the column title into the corresponding column number
53-
column = sum((x*y for x, y in zip([26**i for i in range(len(groupdict['col']))][::-1],
54-
(ord(b) - 64 for b in groupdict['col']))))
55-
return cls(column, int(groupdict['row']))
56-
raise ValueError
54+
extract = re.search("(?P<col>[a-zA-Z]*)(?P<row>\\d*)", a1_notation)
55+
if not extract:
56+
raise ValueError
57+
groupdict = extract.groupdict()
58+
column = groupdict['col']
59+
row = groupdict['row']
60+
if not column:
61+
column = Cell.MAX_COLUMNS if maximize_if_undefined else 1
62+
if not row:
63+
row = Cell.MAX_ROWS if maximize_if_undefined else 1
64+
return cls(column if isinstance(column, int) else Cell.get_column_number(column), int(row))
5765

5866
def cellname(self) -> str:
5967
"""Returns cell in A1-notation"""
60-
chars = []
61-
num = self.column
62-
if self.grid:
63-
num += self.grid.column - 1
64-
while num > 0:
65-
num, d = divmod(num, 26)
66-
if d == 0:
67-
num, d = num - 1, 26
68-
chars.append(chr(64 + d))
68+
col_num = self.column
6969
row_num = self.row
7070
if self.grid:
71-
row_num += self.grid.row - 1
72-
return ''.join(reversed(chars)) + str(row_num)
71+
col_num += self.grid.start_column - 1
72+
row_num += self.grid.start_row - 1
73+
return self.get_column_name(col_num) + str(row_num)
7374

74-
def translate(self, columns: int, rows: int):
75+
def translate(self, columns: int, rows: int) -> 'Cell':
7576
"""
7677
Returns cell translated by the given number of columns and rows
7778
7879
:param columns: number of columns the cell should be moved
7980
:param rows: number of rows the rows the cell should be moved
80-
:rtype: Cell
8181
:return: resulting cell
8282
"""
8383
return Cell(column=self.column + columns,
8484
row=self.row + rows,
8585
grid=self.grid)
8686

87+
@staticmethod
88+
def get_column_number(col: str) -> int:
89+
"""
90+
Returns the column number
91+
92+
:param col: cell or column name
93+
:return: column number
94+
:raises ValueError: if non-ascii-letters are included
95+
"""
96+
num = 0
97+
for letter in col.upper():
98+
num *= 26
99+
num += string.ascii_uppercase.index(letter) + 1
100+
return num
101+
102+
@staticmethod
103+
def get_column_name(num: int) -> str:
104+
"""
105+
Converts number n into the name of the n'th column
106+
107+
:param num: n'th Column
108+
:return: Column name
109+
"""
110+
name = ""
111+
while num > 0:
112+
num, digit = divmod(num - 1, 26)
113+
name = chr(digit + 65) + name
114+
return name
115+
87116

88117
class CellRange:
89118
"""
90-
Represents a range of cells
119+
Representation of a range of cells. Note: rows and columns in a grid begin at 1!
120+
121+
:param start_cell: top-left Cell
122+
:param width: number of columns
123+
:param height: number of rows
91124
"""
92125
def __init__(self, start_cell: Cell, width: int, height: int):
93-
"""
94-
Representation of a range of cells. Note: rows and columns in a grid begin at 1!
95-
96-
:param start_cell: top-left Cell
97-
:param width: number of columns
98-
:param height: number of rows
99-
"""
100-
self.column = start_cell.column
101-
self.row = start_cell.row
126+
self.start_column = start_cell.column
127+
self.start_row = start_cell.row
102128
self.width = width
103129
self.height = height
104130

131+
@property
132+
def end_column(self):
133+
return self.start_column + self.width - 1
134+
135+
@property
136+
def end_row(self):
137+
return self.start_row + self.height - 1
138+
105139
@classmethod
106-
def from_a1(cls, a1_notation: str):
140+
def from_a1(cls, a1_notation: str) -> 'CellRange':
107141
"""
108142
Builds a CellRange object from "A1:B4" notation
109143
110144
:param a1_notation: notation string
111-
:rtype: CellRange
112145
:return: Corresponding CellRange object
113146
:raises ValueError: if invalid notation
114147
"""
115-
extract = re.search("(?P<cell1>[A-Z]+\\d+):(?P<cell2>[A-Z]+\\d+)", a1_notation)
148+
extract = re.search("(?P<cell1>[a-zA-Z]*[a-zA-Z\\d]\\d*):(?P<cell2>[a-zA-Z]*[a-zA-Z\\d]\\d*)", a1_notation)
116149
if extract:
117150
groupdict = extract.groupdict()
118-
return cls.from_cells(Cell.from_a1(groupdict['cell1']), Cell.from_a1(groupdict['cell2']))
151+
return cls.from_cells(Cell.from_a1(groupdict['cell1']), Cell.from_a1(groupdict['cell2'],
152+
maximize_if_undefined=True))
119153
raise ValueError
120154

121155
@classmethod
122-
def from_cells(cls, start_cell: Cell, end_cell: Cell):
156+
def from_cells(cls, start_cell: Cell, end_cell: Cell) -> 'CellRange':
123157
"""
124158
Builds a CellRange object by passing two corners of the range rectangle.
125159
126160
:param start_cell: top left
127161
:param end_cell: bottom right
128-
:rtype: CellRange
129162
:return: Corresponding CellRange object
130163
"""
131164
width = end_cell.column - start_cell.column + 1
132165
height = end_cell.row - start_cell.row + 1
133166
return cls(start_cell, width, height)
134167

168+
@classmethod
169+
def columns(cls, start: int, end: int = None):
170+
"""CellRange of complete columns"""
171+
return cls(Cell(start, 1), end - start + 1 if end else 1, Cell.MAX_ROWS)
172+
173+
@classmethod
174+
def rows(cls, start: int, end: int = None):
175+
"""CellRange of complete rows"""
176+
return cls(Cell(1, start), Cell.MAX_COLUMNS, end - start + 1 if end else 1)
177+
178+
def overlay_range(self, other: 'CellRange') -> Optional['CellRange']:
179+
"""
180+
Returns the overlaying range with the other CellRange
181+
182+
:param other: other CellRange
183+
:return: CellRange of overlay area if existing, otherwise None
184+
"""
185+
start_column = max(self.start_column, other.start_column)
186+
start_row = max(self.start_row, other.start_row)
187+
end_column = min(self.end_column, other.end_column)
188+
end_row = min(self.end_row, other.end_row)
189+
if start_column > end_column or start_row > end_row:
190+
return None
191+
return CellRange.from_cells(Cell(start_column, start_row), Cell(end_column, end_row))
192+
135193
def rangename(self) -> str:
136194
"""Returns cell range in A1-notation"""
137-
return "{}:{}".format(Cell(self.column, self.row).cellname(),
138-
Cell(self.column + self.width - 1, self.row + self.height - 1).cellname())
195+
return "{}:{}".format(Cell(self.start_column, self.start_row).cellname(),
196+
Cell(self.end_column, self.end_row).cellname())
139197

140-
def translate(self, columns: int, rows: int):
198+
def translate(self, columns: int, rows: int) -> 'CellRange':
141199
"""
142200
Returns cell range translated by the given number of columns and rows
143201
144202
:param columns: number of columns the range should be moved
145203
:param rows: number of rows the rows the range should be moved
146-
:rtype: CellRange
147204
:return: resulting cell range
148205
"""
149-
return CellRange(start_cell=Cell(column=self.column + columns,
150-
row=self.row + rows),
206+
return CellRange(start_cell=Cell(column=self.start_column + columns,
207+
row=self.start_row + rows),
151208
width=self.width,
152209
height=self.height)
153210

154211
def expand(self, top: int = 0, bottom: int = 0, left: int = 0, right: int = 0):
155212
"""
156213
Returns cell range expanded by the given amount in each direction
157214
"""
158-
if self.column <= left or self.row <= top or left + right <= -self.width or top + bottom <= -self.height:
215+
if self.start_column <= left or self.start_row <= top or left + right <= -self.width \
216+
or top + bottom <= -self.height:
159217
raise ValueError
160-
return CellRange(start_cell=Cell(column=self.column - left, row=self.row - top),
218+
return CellRange(start_cell=Cell(column=self.start_column - left, row=self.start_row - top),
161219
width=self.width + left + right,
162220
height=self.height + top + bottom)
163221

@@ -558,10 +616,10 @@ def find_and_replace(self, find: str, replace: str, match_case: bool = True, mat
558616
else:
559617
request['range'] = {
560618
"sheetId": sheet_id,
561-
"startRowIndex": cell_range.row,
562-
"endRowIndex": cell_range.row + cell_range.height,
563-
"startColumnIndex": cell_range.column,
564-
"endColumnIndex": cell_range.column + cell_range.width
619+
"startRowIndex": cell_range.start_row,
620+
"endRowIndex": cell_range.end_row,
621+
"startColumnIndex": cell_range.start_column,
622+
"endColumnIndex": cell_range.end_column
565623
}
566624
else:
567625
return None

botutils/uiutils.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class CoroButton(ui.Button):
2222
:param data: Opaque object
2323
"""
2424
def __init__(self,
25-
coro: Callable[[ui.Button, Interaction], Coroutine],
25+
coro: Callable[['CoroButton', Interaction], Coroutine],
2626
*,
2727
style=ButtonStyle.secondary, label=None, disabled=False, custom_id=None, emoji=None, row=None,
2828
data: Any = None):
@@ -187,7 +187,7 @@ class SingleConfirmView(MultiItemView):
187187
then there is no timeout.
188188
"""
189189
def __init__(self,
190-
confirm_coro: Optional[Callable[[ui.Button, Interaction], Coroutine]] = None,
190+
confirm_coro: Optional[Callable[[CoroButton, Interaction], Coroutine]] = None,
191191
*,
192192
confirm_label: str = "Confirm",
193193
abort_label: str = "X",
@@ -203,7 +203,7 @@ def __init__(self,
203203
self.confirm_coro = confirm_coro
204204
self.user_id = user_id
205205

206-
async def confirm(self, button: ui.Button, interaction: Interaction):
206+
async def confirm(self, button: CoroButton, interaction: Interaction):
207207
"""
208208
Action to perform when confirm button is pressed
209209

botutils/utils.py

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Union, Optional, Coroutine, Any, Callable
1+
from typing import Union, Optional, Coroutine, Any, Callable, List
22
import datetime
33
import random
44
import inspect
@@ -49,8 +49,34 @@ def paginate_embed(embed: Embed):
4949
else:
5050
embed.description = f"{embed.description[0:2046]} …"
5151
if len(embed) > 6000:
52-
raise Exception(f"Embed is still to long! Title: {embed.title}")
53-
52+
raise Exception(f"Embed is still too long! Title: {embed.title}")
53+
54+
def paginate_embeds(embeds: List[Embed]) -> List[List[Embed]]:
55+
"""
56+
Paginate a list of embeds.
57+
58+
:param embeds: List of embeds to paginate
59+
:return: Paginated list of lists of embeds
60+
:raises Exception: If a single embed is too long
61+
"""
62+
embeds = embeds[:]
63+
paginated: List[List[Embed]] = []
64+
while embeds:
65+
embed_page: List[Embed] = []
66+
len_sum = 0
67+
while embeds:
68+
embed = embeds.pop(0)
69+
if len(embed) > 6000:
70+
raise Exception(f"Embed is too long! Title: {embed.title}")
71+
len_sum += len(embed)
72+
if len_sum > 6000:
73+
embeds.insert(0, embed)
74+
break
75+
embed_page.append(embed)
76+
if len(embed_page) >= 10:
77+
break
78+
paginated.append(embed_page)
79+
return paginated
5480

5581
async def _write_to_channel(channel_id: int = 0, message: Union[str, Embed] = None,
5682
channel_type: str = ""):

lang/customcmd.json

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@
7878
"help_cmd_aliasclear": "Removes all aliases from a user command",
7979
"usage_cmd_aliasclear": "<cmd name>",
8080
"desc_cmd_aliasclear": "Removes all aliases from a user command.",
81+
"help_cmd_random": "Random cmd text",
82+
"desc_cmd_random": "Sends a random text entry from all created text commands.",
83+
"help_cmd_image": "Random cmd image",
84+
"desc_cmd_image": "Sends a random link from all created text commands.",
8185

8286
"Embed Help": "",
8387
"help_cmd_embed": "Creates and manages custom embed commands",
@@ -161,7 +165,7 @@
161165
"desc_cmd_list": "Listet alle Benutzer-Kommandos. Wenn [full] angegeben ist, werden mehr Informationen zu den Kommandos ausgegeben.",
162166
"help_cmd_info": "Gibt alle Infos zu einem Benutzer-Kommando aus",
163167
"usage_cmd_info": "<command> [# | #+ | #++]",
164-
"desc_cmd_info": "Gibt alle Infos zu einem Benutzer-Kommando aus, inklusive den Ausgabetexten und ihre Ersteller.\n\nOptionen:\n # - Gibt den Ausgabetext mit der Nummer # aus.\n #+ - Gibt den Ausgabetext mit der Nummer # sowie alle folgenden, die in eine Nachricht passen, aus.\n #++ - Gibt alle Ausgabetexte mit der Nummer # sowie alle folgenden aus.",
168+
"desc_cmd_info": "Gibt alle Infos zu einem Benutzer-Kommando aus, inklusive den Ausgabetexten und ihre Ersteller.\n\nOptionen:\n # - Gibt den Ausgabetext mit der Nummer # aus.\n #+ - Gibt den Ausgabetext mit der Nummer # sowie alle folgenden, die in eine Nachricht passen, aus.\n #++ - Gibt alle Ausgabetexte mit der Nummer # sowie alle folgenden aus.\n last - Gibt den letzten Ausgabetext aus.\n all - Gibt alle Ausgabetexte aus.",
165169
"help_cmd_search": "Sucht in den Ausgabetexten eines Benutzer-Kommandos",
166170
"usage_cmd_search": "<Kommandoname> <Suchbegriffe>",
167171
"desc_cmd_search": "Findet alle Ausgabetexte im Kommando, die jeweils alle Suchbegriffe enthalten.\nDie Suche unterscheidet nicht zwischen Groß- und Kleinschreibung.",
@@ -178,9 +182,13 @@
178182
"help_cmd_aliasclear": "Entfernt alle Alias von einem Benutzer-Kommando.",
179183
"usage_cmd_aliasclear": "<Kommando>",
180184
"desc_cmd_aliasclear": "Entfernt alle Alias von einem Benutzer-Komando.",
185+
"help_cmd_random": "Zufälliges Kommando",
186+
"desc_cmd_random": "Sendet einen zufälligen Text aus allen Text-Kommandos.",
187+
"help_cmd_image": "Zufälliges Bild",
188+
"desc_cmd_image": "Sendet einen zufälligen Link aus allen Text-Kommandos.",
181189

182190
"Embed Help": "",
183-
"help_cmd_embed": "Creates and manages custom embed commands",
191+
"help_cmd_embed": "Verwaltet Embed-Kommandos",
184192
"desc_cmd_embed": "Verwaltet Embed-Kommandos.\nEmbeds bestehen aus beliebig vielen Feldern und einem optionalen Header. Felder wiederum bestehen aus einem Titel und dem Inhalt. Um ein Embed-Kommando zu erstellen:\n(1) Erstelle das initiale Embed-Kommando\n(2) Optional: Setze den Header\n(3) Setze Titel und Inhalt des ersten Felds\n(4) Füge weitere Felder hinzu und setze ihre Titel und Inhalte",
185193
"usage_cmd_embed": "",
186194
"help_cmd_embed_add": "Fügt ein neues Embed-Cmd oder Feld hinzu",

0 commit comments

Comments
 (0)