Update year in copyright notices
Annotate for file utils.py
2022-02-20 E. 1 # irgramd: IRC-Telegram gateway
01:25:27 ' 2 # utils.py: Helper functions
' 3 #
' 4 # Copyright (c) 2019 Peter Bui <pbui@bx612.space>
2026-06-21 E. 5 # Copyright (c) 2020-2026 E. Bosch <presidev@AT@gmail.com>
2022-02-20 E. 6 #
01:25:27 ' 7 # Use of this source code is governed by a MIT style license that
' 8 # can be found in the LICENSE file included in this project.
2020-11-19 E. 9
19:41:24 ' 10 import itertools
2022-01-24 E. 11 import textwrap
2022-01-30 E. 12 import re
2022-03-19 E. 13 import datetime
2023-05-06 E. 14 import zoneinfo
2023-04-15 E. 15 import difflib
2023-12-20 E. 16 import logging
2026-05-30 E. 17 import random
2020-11-19 E. 18
2022-02-09 E. 19 # Constants
21:58:31 ' 20
2026-05-30 E. 21 FILENAME_INVALID_CHARS = re.compile('[\0-\x1F/{}<>"\'\\|*&#%?\x7F]')
2022-02-19 E. 22 SIMPLE_URL = re.compile('http(|s)://[^ ]+')
2022-02-09 E. 23
2024-11-02 E. 24 from include import MAX_LINE
19:20:45 ' 25
2020-11-19 E. 26 # Utilities
19:41:24 ' 27
2023-06-22 E. 28 class command:
19:57:16 ' 29 async def parse_command(self, line, nick):
2023-06-25 E. 30 command = line.partition(' ')[0].lower()
2023-06-22 E. 31 self.tmp_ircnick = nick
19:57:16 ' 32 if command in self.commands.keys():
2023-06-25 E. 33 handler, min_args, max_args, maxsplit = self.commands[command]
22:17:22 ' 34 words = line.split(maxsplit=maxsplit)[1:]
2023-06-22 E. 35 num_words = len(words)
19:57:16 ' 36 if num_words < min_args or num_words > max_args:
' 37 reply = ('Wrong number of arguments',)
' 38 else:
' 39 reply = await handler(*words)
' 40 else:
' 41 reply = ('Unknown command',)
' 42
' 43 return reply
' 44
2023-06-25 E. 45 class HELP:
22:17:22 ' 46 desc = 1
' 47 brief = 2
' 48
2024-08-30 E. 49 class LOGL:
17:00:53 ' 50 debug = False
' 51
2020-11-19 E. 52 def chunks(iterable, n, fillvalue=None):
19:41:24 ' 53 ''' Return iterable consisting of a sequence of n-length chunks '''
' 54 args = [iter(iterable)] * n
' 55 return itertools.zip_longest(*args, fillvalue=fillvalue)
2021-02-21 E. 56
00:12:58 ' 57 def set_replace(set, item, new_item):
' 58 if item in set:
' 59 set.remove(item)
' 60 set.add(new_item)
2022-01-24 E. 61
21:35:55 ' 62 def get_continued(items, mark, length):
' 63 # Add "continued" mark to lines, except last one
' 64 return (x + mark if n != length else x for n, x in enumerate(items, start=1))
' 65
' 66 def split_lines(message):
' 67 messages_limited = []
2024-11-02 E. 68 wr = textwrap.TextWrapper(width=MAX_LINE)
2022-01-24 E. 69
21:35:55 ' 70 # Split when Telegram original message has breaks
' 71 messages = message.splitlines()
' 72 lm = len(messages)
' 73 if lm > 1:
' 74 # Add "continued line" mark (\) for lines that belong to the same message
' 75 # (split previously)
' 76 messages = get_continued(messages, ' \\', lm)
' 77 for m in messages:
' 78 wrapped = wr.wrap(text=m)
' 79 lw = len(wrapped)
' 80 if lw > 1:
' 81 # Add double "continued line" mark (\\) for lines that belong to the same message
' 82 # and have been wrapped to not exceed IRC limits
' 83 messages_limited += get_continued(wrapped, ' \\\\', lw)
' 84 else:
' 85 messages_limited += wrapped
' 86 del wr
' 87 return messages_limited
2022-01-30 E. 88
00:29:08 ' 89 def sanitize_filename(fn):
2026-05-30 E. 90 def hexize(m):
22:26:34 ' 91 return '-{:x}-'.format(ord(m.group(0)))
' 92
' 93 new_fn = FILENAME_INVALID_CHARS.sub(hexize, fn)
' 94 return new_fn.lstrip('-').replace(' ','_')
2022-02-08 E. 95
2023-12-17 E. 96 def add_filename(filename, add):
01:49:18 ' 97 if add:
' 98 aux = filename.rsplit('.', 1)
' 99 name = aux[0]
2026-05-30 E. 100 last_dot = '.'
2024-04-27 E. 101 try:
22:28:22 ' 102 ext = aux[1]
' 103 except:
' 104 ext = ''
2026-05-30 E. 105 last_dot = ''
20:25:32 ' 106 return '{}-{}{}{}'.format(name, add, last_dot, ext)
2023-12-17 E. 107 else:
01:49:18 ' 108 return filename
' 109
2022-02-08 E. 110 def remove_slash(url):
01:04:55 ' 111 return url[:-1] if url[-1:] == '/' else url
' 112
' 113 def remove_http_s(url):
' 114 if url[:8] == 'https://':
' 115 surl = url[8:]
' 116 elif url[:7] == 'http://':
' 117 surl = url[7:]
' 118 else:
' 119 surl = url
' 120 return remove_slash(surl)
2022-02-09 E. 121
2022-02-19 E. 122 def is_url_equiv(url1, url2):
01:10:00 ' 123 if url1 and url2:
' 124 return url1 == url2 or remove_slash(remove_http_s(url1)) == remove_slash(remove_http_s(url2))
' 125 else:
' 126 return False
' 127
' 128 def extract_url(text):
' 129 url = SIMPLE_URL.search(text)
' 130 return url.group() if url else None
' 131
2022-02-09 E. 132 def get_human_size(size):
22:52:06 ' 133 human_units = ('', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
' 134
' 135 def get_human_size_values(size, unit_pos=0):
' 136 aux = size / 1024.0
' 137 if aux > 1: return get_human_size_values(aux, unit_pos + 1)
' 138 else: return size, human_units[unit_pos]
' 139
' 140 if size <= 1237940039285380274899124224: # 1024Y
' 141 num, unit = get_human_size_values(size)
' 142 else:
' 143 num = size / 1208925819614629174706176 # 1Y
' 144 unit = 'Y'
' 145
' 146 fs = '{:.1f}{}' if num < 10 else '{:.0f}{}'
' 147
' 148 return fs.format(num, unit)
' 149
' 150 def get_human_duration(duration):
' 151 res = ''
' 152 x, s = divmod(duration, 60)
' 153 h, m = divmod(x, 60)
' 154
' 155 if h > 0: res = str(h) + 'h'
' 156 if m > 0: res += str(m) + 'm'
2023-12-14 E. 157 if s > 0 or duration < 60: res += str(s) + 's'
2022-02-09 E. 158 return res
2022-03-19 E. 159
2023-05-07 E. 160 def compact_date(date, tz):
2024-08-29 E. 161 delta = current_date() - date
2023-05-07 E. 162 date_local = date.astimezone(zoneinfo.ZoneInfo(tz))
2022-03-19 E. 163
22:26:56 ' 164 if delta.days < 1:
2023-05-07 E. 165 compact_date = date_local.strftime('%H:%M')
2023-03-19 E. 166 elif delta.days < 365:
2023-05-07 E. 167 compact_date = date_local.strftime('%d-%b')
2022-03-19 E. 168 else:
2023-05-07 E. 169 compact_date = date_local.strftime('%Y')
2022-03-19 E. 170
22:26:56 ' 171 return compact_date
2023-04-15 E. 172
2024-08-29 E. 173 def current_date():
23:58:06 ' 174 return datetime.datetime.now(datetime.timezone.utc)
' 175
2023-04-15 E. 176 def get_highlighted(a, b):
23:13:07 ' 177 awl = len(a.split())
' 178 bwl = len(b.split())
' 179 delta_size = abs(awl - bwl)
' 180 highlighted = True
' 181
' 182 if not a:
' 183 res = '> {}'.format(b)
' 184 elif delta_size > 5:
' 185 res = b
' 186 highlighted = False
' 187 else:
' 188 al = a.split(' ')
' 189 bl = b.split(' ')
' 190 diff = difflib.ndiff(al, bl)
' 191 ld = list(diff)
' 192 res = ''
' 193 d = ''
' 194 eq = 0
' 195
' 196 for i in ld:
' 197 if i == '- ' or i[0] == '?':
' 198 continue
' 199 elif i == ' ' or i == '+ ':
' 200 res += ' '
' 201 continue
2023-05-07 E. 202 # deletion of words
2023-04-15 E. 203 elif i[0] == '-':
2023-05-07 E. 204 res += '-{}- '.format(i[2:])
00:24:08 ' 205 # addition of words
2023-04-15 E. 206 elif i[0] == '+':
2023-11-18 E. 207 res += '+{}+ '.format(i[2:])
2023-04-15 E. 208 else:
23:13:07 ' 209 res += '{} '.format(i[2:])
' 210 eq += 1
' 211
' 212 delta_eq = bwl - eq
' 213 if delta_eq > 3:
' 214 res = b
' 215 highlighted = False
' 216
' 217 return res, highlighted
2023-04-26 E. 218
18:45:17 ' 219 def fix_braces(text):
' 220 # Remove braces not closed, if the text was truncated
' 221 if text.endswith(' {...'):
' 222 subtext = text[:-5]
' 223 if not '{}' in subtext:
' 224 return '{}...'.format(subtext)
' 225 return text
2023-05-06 E. 226
23:31:34 ' 227 def format_timestamp(format, tz, date):
' 228 date_local = date.astimezone(zoneinfo.ZoneInfo(tz))
' 229 return date_local.strftime(format)
2023-12-20 E. 230
00:50:56 ' 231 def parse_loglevel(level):
' 232 levelu = level.upper()
2024-08-30 E. 233 if levelu == 'DEBUG':
17:00:53 ' 234 LOGL.debug = True
2023-12-20 E. 235 if levelu == 'NONE':
00:50:56 ' 236 l = None
' 237 elif levelu in ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'):
' 238 l = getattr(logging, levelu)
' 239 else:
' 240 l = False
' 241 return l
2024-08-24 E. 242
23:21:01 ' 243 def pretty(object):
2024-08-30 E. 244 return object.stringify() if LOGL.debug and object else object
2026-05-30 E. 245
19:33:57 ' 246 class token:
' 247 def __init__(self, alpha):
' 248 self.alpha = alpha
' 249 self.long_alpha = len(alpha)
' 250
' 251 def gen_token(self, long_token):
' 252 if long_token == 1:
' 253 return self.alpha[random.randrange(self.long_alpha)]
' 254 else:
' 255 aux = self.gen_token(long_token - 1)
' 256 return aux + self.alpha[random.randrange(self.long_alpha)]