Compare commits

...

2 commits

Author SHA1 Message Date
pull[bot]
043d991aff
Merge pull request #277 from dmitmel/master
[pull] master from dmitmel:master
2021-06-01 21:08:52 +00:00
Dmytro Meleshko
d5b147564d [python] throw in some flake8 plugins and adjust all Python scripts accordingly 2021-06-01 22:33:54 +03:00
23 changed files with 197 additions and 141 deletions

View file

@ -3,8 +3,7 @@
import json import json
import os import os
from abc import abstractmethod from abc import abstractmethod
from typing import Dict, Iterable, List, Protocol, TextIO, runtime_checkable from typing import Dict, Iterator, List, Protocol, TextIO, runtime_checkable
__dir__ = os.path.dirname(__file__) __dir__ = os.path.dirname(__file__)
@ -12,16 +11,20 @@ __dir__ = os.path.dirname(__file__)
class Color: class Color:
def __init__(self, r: int, g: int, b: int) -> None: def __init__(self, r: int, g: int, b: int) -> None:
assert 0 <= r <= 0xff if not (0 <= r <= 0xff):
assert 0 <= g <= 0xff raise Exception("r component out of range")
assert 0 <= b <= 0xff if not (0 <= g <= 0xff):
raise Exception("g component out of range")
if not (0 <= b <= 0xff):
raise Exception("b component out of range")
self.r = r self.r = r
self.g = g self.g = g
self.b = b self.b = b
@classmethod @classmethod
def from_hex(cls, s: str) -> "Color": def from_hex(cls, s: str) -> "Color":
assert len(s) == 6 if len(s) != 6:
raise Exception("hex color string must be 6 characters long")
return Color(int(s[0:2], 16), int(s[2:4], 16), int(s[4:6], 16)) return Color(int(s[0:2], 16), int(s[2:4], 16), int(s[4:6], 16))
@property @property
@ -42,7 +45,7 @@ class Color:
else: else:
raise IndexError("color component index out of range") raise IndexError("color component index out of range")
def __iter__(self) -> Iterable[int]: def __iter__(self) -> Iterator[int]:
yield self.r yield self.r
yield self.g yield self.g
yield self.b yield self.b
@ -296,10 +299,6 @@ class ThemeGeneratorXfceTerminal(ThemeGenerator):
class ThemeGeneratorVscode(ThemeGenerator): class ThemeGeneratorVscode(ThemeGenerator):
def file_name(self) -> str:
return "vscode-colorCustomizations.json"
def generate(self, theme: Theme, output: TextIO) -> None:
ANSI_COLOR_NAMES = [ ANSI_COLOR_NAMES = [
"Black", "Black",
"Red", "Red",
@ -311,6 +310,11 @@ class ThemeGeneratorVscode(ThemeGenerator):
"White", "White",
] ]
def file_name(self) -> str:
return "vscode-colorCustomizations.json"
def generate(self, theme: Theme, output: TextIO) -> None:
colors: Dict[str, str] = { colors: Dict[str, str] = {
"terminal.background": theme.bg.css_hex, "terminal.background": theme.bg.css_hex,
"terminal.foreground": theme.fg.css_hex, "terminal.foreground": theme.fg.css_hex,
@ -320,8 +324,8 @@ class ThemeGeneratorVscode(ThemeGenerator):
} }
for is_bright in [False, True]: for is_bright in [False, True]:
for color_index, color_name in enumerate(ANSI_COLOR_NAMES): for color_index, color_name in enumerate(self.ANSI_COLOR_NAMES):
color = theme.ansi_colors[color_index + int(is_bright) * len(ANSI_COLOR_NAMES)] color = theme.ansi_colors[color_index + int(is_bright) * len(self.ANSI_COLOR_NAMES)]
colors["terminal.ansi" + ("Bright" if is_bright else "") + color_name] = color.css_hex colors["terminal.ansi" + ("Bright" if is_bright else "") + color_name] = color.css_hex
json.dump(colors, output, ensure_ascii=False, indent=2) json.dump(colors, output, ensure_ascii=False, indent=2)
@ -341,7 +345,7 @@ class ThemeGeneratorIterm(ThemeGenerator):
output.write('<plist version="1.0">\n') output.write('<plist version="1.0">\n')
output.write("<dict>\n") output.write("<dict>\n")
def write_color(key_name, color): def write_color(key_name: str, color: Color) -> None:
r, g, b = (float(component) / 0xff for component in color) r, g, b = (float(component) / 0xff for component in color)
output.write(" <key>{} Color</key>\n".format(key_name)) output.write(" <key>{} Color</key>\n".format(key_name))
output.write(" <dict>\n") output.write(" <dict>\n")

View file

@ -3,6 +3,10 @@
{ {
"root": "scripts", "root": "scripts",
"extraPaths": ["script-resources"] "extraPaths": ["script-resources"]
},
{
"root": "script-resources/welcome",
"extraPaths": ["script-resources/welcome"]
} }
] ]
} }

View file

@ -1,4 +1,27 @@
[flake8] [flake8]
select =
# <https://pycodestyle.pycqa.org/en/latest/intro.html#error-codes>
E W
# <https://flake8.pycqa.org/en/latest/user/error-codes.html#error-violation-codes>
F
# <https://github.com/zheller/flake8-quotes#warnings>
Q0
# <https://github.com/sco1/flake8-annotations#table-of-warnings>
ANN
# <https://github.com/PyCQA/pep8-naming#error-codes>
N8
# <https://github.com/gforcada/flake8-isort#error-codes>
I0
# <https://github.com/PyCQA/flake8-commas/>
C8
# <https://bandit.readthedocs.io/en/latest/plugins/index.html#complete-test-plugin-listing>
S
# Also see this:
# <https://github.com/wemake-services/wemake-python-styleguide>
# <https://wemake-python-stylegui.de/en/latest/pages/usage/violations/index.html>
ignore = ignore =
# Indent is not a multiple of 4 # Indent is not a multiple of 4
E111 E111
@ -13,8 +36,17 @@ ignore =
# Line too long # Line too long
E501 E501
# `except` without an exception type # `except` without an exception type
E722 # NOTE: write `except Exception` (or `except BaseException` if absolutely
# necessary) instead
# E722
# Newline before a binary operator # Newline before a binary operator
W503 W503
# Newline after a binary operator # Newline after a binary operator
W504 W504
# Missing type annotations for `self` and `cls` respectively
ANN101 ANN102
inline-quotes = "
multiline-quotes = "
docstring-quotes = "

View file

@ -3,7 +3,6 @@ based_on_style = google
column_limit = 99 column_limit = 99
indent_width = 2 indent_width = 2
continuation_indent_width = 2 continuation_indent_width = 2
blank_lines_between_top_level_imports_and_variables = 2
dedent_closing_brackets = true dedent_closing_brackets = true
coalesce_brackets = true coalesce_brackets = true
spaces_around_power_operator = true spaces_around_power_operator = true

View file

@ -1,6 +1,6 @@
import sys
import os import os
import subprocess import subprocess
import sys
from pathlib import Path from pathlib import Path
from typing import Iterable, NoReturn from typing import Iterable, NoReturn

View file

@ -5,7 +5,7 @@
# <https://www.devdungeon.com/content/working-binary-data-python> # <https://www.devdungeon.com/content/working-binary-data-python>
import struct import struct
from typing import Any, IO from typing import IO, Any
def read_bool(buf: IO[bytes]) -> bool: def read_bool(buf: IO[bytes]) -> bool:

View file

@ -2,7 +2,7 @@ from math import *
from fractions import Fraction from fractions import Fraction
def factors(n): def factors(n: int) -> "set[int]":
result = set() result = set()
for i in range(1, int(sqrt(n)) + 1): for i in range(1, int(sqrt(n)) + 1):
if n % i == 0: if n % i == 0:
@ -11,7 +11,7 @@ def factors(n):
return result return result
def solve_quadratic(a, b, c): def solve_quadratic(a: int, b: int, c: int) -> None:
if a == 0: if a == 0:
raise Exception("not a quadratic equation") raise Exception("not a quadratic equation")
else: else:

View file

@ -1,22 +1,25 @@
from typing import List
from colorama import Fore, Style, ansi from colorama import Fore, Style, ansi
COLORS: List[str] = [ansi.code_to_chars(30 + color_index) for color_index in range(0, 8)]
COLORS = [ansi.code_to_chars(30 + color_index) for color_index in range(0, 8)]
def colored(string, *colors): def colored(string: str, *colors: str) -> str:
return "".join(colors + (string, Style.RESET_ALL)) return "".join(colors + (string, Style.RESET_ALL))
def bright_colored(string, *colors): def bright_colored(string: str, *colors: str) -> str:
return "".join(colors + (Style.BRIGHT, string, Style.RESET_ALL)) return "".join(colors + (Style.BRIGHT, string, Style.RESET_ALL))
def colorize_percent(percent, warning, critical, inverse=False): def colorize_percent(
COLORS = [Fore.GREEN, Fore.YELLOW, Fore.RED] percent: float, warning: float, critical: float, inverse: bool = False
) -> str:
colors = [Fore.GREEN, Fore.YELLOW, Fore.RED]
color_index = 0 if percent < warning else 1 if percent < critical else 2 color_index = 0 if percent < warning else 1 if percent < critical else 2
if inverse: if inverse:
color_index = 2 - color_index color_index = 2 - color_index
return colored("%.2f%%" % percent, COLORS[color_index]) return colored("%.2f%%" % percent, colors[color_index])

View file

@ -1,11 +1,15 @@
def humanize_timedelta(timedelta): from datetime import timedelta
result = [] from typing import List, Tuple
def humanize_timedelta(timedelta: timedelta) -> str:
result: List[str] = []
days = timedelta.days days = timedelta.days
mm, ss = divmod(timedelta.seconds, 60) mm, ss = divmod(timedelta.seconds, 60)
hh, mm = divmod(mm, 60) hh, mm = divmod(mm, 60)
def plural(n): def plural(n: int) -> Tuple[int, str]:
return n, "s" if abs(n) != 1 else "" return n, "s" if abs(n) != 1 else ""
if days > 0: if days > 0:
@ -20,7 +24,7 @@ def humanize_timedelta(timedelta):
return ", ".join(result) return ", ".join(result)
def humanize_bytes(bytes): def humanize_bytes(bytes: int) -> str:
units = ["B", "kB", "MB", "GB"] units = ["B", "kB", "MB", "GB"]
factor = 1 factor = 1

View file

@ -5,7 +5,6 @@ import re
from colors import COLORS, Style from colors import COLORS, Style
from system_info import get_system_info from system_info import get_system_info
print("") print("")
logo_lines, info_lines = get_system_info() logo_lines, info_lines = get_system_info()

View file

@ -3,17 +3,17 @@ import platform
import socket import socket
from datetime import datetime, timedelta from datetime import datetime, timedelta
from getpass import getuser from getpass import getuser
from typing import Dict, List, Optional, Tuple, cast
import psutil import psutil
from colors import Fore, Style, bright_colored, colored, colorize_percent from colors import Fore, Style, bright_colored, colored, colorize_percent
from humanize import humanize_bytes, humanize_timedelta from humanize import humanize_bytes, humanize_timedelta
def get_system_info(): def get_system_info() -> Tuple[List[str], List[str]]:
info_lines = [] info_lines: List[str] = []
def info(name, value, *format_args): def info(name: str, value: str, *format_args) -> None:
line = bright_colored(name + ":", Fore.YELLOW) + " " + value line = bright_colored(name + ":", Fore.YELLOW) + " " + value
if format_args: if format_args:
line = line % format_args line = line % format_args
@ -66,27 +66,27 @@ def get_system_info():
return logo_lines, info_lines return logo_lines, info_lines
def _get_hostname(): def _get_hostname() -> str:
hostname = socket.gethostname() hostname = socket.gethostname()
return hostname return hostname
def _get_uptime(): def _get_uptime() -> timedelta:
return datetime.now() - datetime.fromtimestamp(psutil.boot_time()) return datetime.now() - datetime.fromtimestamp(psutil.boot_time())
def _get_users(): def _get_users() -> str:
users = {} users: Dict[str, List[str]] = {}
for user in psutil.users(): for user in psutil.users():
name = user.name name: str = user.name
terminal = user.terminal terminal: str = user.terminal
if name in users: if name in users:
users[name].append(terminal) users[name].append(terminal)
else: else:
users[name] = [terminal] users[name] = [terminal]
result = [] result: List[str] = []
for name in users: for name in users:
terminals = users[name] terminals = users[name]
@ -102,13 +102,13 @@ def _get_users():
return ", ".join(result) return ", ".join(result)
def _get_shell(): def _get_shell() -> Optional[str]:
return os.environ.get("SHELL") return os.environ.get("SHELL")
def _get_cpu_usage(): def _get_cpu_usage() -> Optional[str]:
try: try:
percent = psutil.cpu_percent() percent = cast(float, psutil.cpu_percent())
except Exception as e: except Exception as e:
print("Error in _get_cpu_usage:", e) print("Error in _get_cpu_usage:", e)
return None return None
@ -116,7 +116,7 @@ def _get_cpu_usage():
return colorize_percent(percent, warning=60, critical=80) return colorize_percent(percent, warning=60, critical=80)
def _get_memory(): def _get_memory() -> Tuple[str, str, str]:
memory = psutil.virtual_memory() memory = psutil.virtual_memory()
return ( return (
humanize_bytes(memory.used), humanize_bytes(memory.used),
@ -125,8 +125,8 @@ def _get_memory():
) )
def _get_disks(): def _get_disks() -> List[Tuple[str, str, str, str]]:
result = [] result: List[Tuple[str, str, str, str]] = []
for disk in psutil.disk_partitions(all=False): for disk in psutil.disk_partitions(all=False):
if psutil.WINDOWS and ("cdrom" in disk.opts or disk.fstype == ""): if psutil.WINDOWS and ("cdrom" in disk.opts or disk.fstype == ""):
@ -146,7 +146,7 @@ def _get_disks():
return result return result
def _get_battery(): def _get_battery() -> Optional[Tuple[str, str]]:
if not hasattr(psutil, "sensors_battery"): if not hasattr(psutil, "sensors_battery"):
return None return None
@ -167,8 +167,8 @@ def _get_battery():
return colorize_percent(percent, critical=10, warning=20, inverse=True), status return colorize_percent(percent, critical=10, warning=20, inverse=True), status
def _get_local_ipv4_addresses(): def _get_local_ipv4_addresses() -> List[Tuple[str, str]]:
result = [] result: List[Tuple[str, str]] = []
for interface, addresses in psutil.net_if_addrs().items(): for interface, addresses in psutil.net_if_addrs().items():
for address in addresses: for address in addresses:
@ -184,7 +184,7 @@ def _get_local_ipv4_addresses():
return result return result
def _get_distro_info(): def _get_distro_info() -> Tuple[str, str, str, str]:
if psutil.WINDOWS: if psutil.WINDOWS:
return "windows", platform.system(), platform.release(), "" return "windows", platform.system(), platform.release(), ""
elif psutil.OSX: elif psutil.OSX:
@ -194,9 +194,13 @@ def _get_distro_info():
sw_vers = plistlib.load(f) sw_vers = plistlib.load(f)
return "mac", sw_vers["ProductName"], sw_vers["ProductVersion"], "" return "mac", sw_vers["ProductName"], sw_vers["ProductVersion"], ""
elif _is_android(): elif _is_android():
from subprocess import check_output import subprocess
android_version = check_output(["getprop", "ro.build.version.release"]) android_version = subprocess.run(
["getprop", "ro.build.version.release"],
check=True,
stdout=subprocess.PIPE,
).stdout
return "android", "Android", android_version.decode().strip(), "" return "android", "Android", android_version.decode().strip(), ""
elif psutil.LINUX: elif psutil.LINUX:
import distro import distro
@ -206,5 +210,5 @@ def _get_distro_info():
raise NotImplementedError("unsupported OS") raise NotImplementedError("unsupported OS")
def _is_android(): def _is_android() -> bool:
return os.path.isdir("/system/app") and os.path.isdir("/system/priv-app") return os.path.isdir("/system/app") and os.path.isdir("/system/priv-app")

View file

@ -1,19 +1,17 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import sys
import os
from pathlib import Path
from configparser import ConfigParser
import json import json
from typing import Any, Generator, Optional, Union import os
import sys
import urllib.parse import urllib.parse
import urllib.request import urllib.request
from configparser import ConfigParser
from pathlib import Path
from typing import Any, Generator, Optional, Union
sys.path.insert(1, os.path.join(os.path.dirname(__file__), "..", "script-resources")) sys.path.insert(1, os.path.join(os.path.dirname(__file__), "..", "script-resources"))
import common_script_utils import common_script_utils
DEFAULT_REGISTRY_DUMP_URL = "https://stronghold.crosscode.ru/~ccbot/emote-registry.json" DEFAULT_REGISTRY_DUMP_URL = "https://stronghold.crosscode.ru/~ccbot/emote-registry.json"
if os.name == "posix": if os.name == "posix":
@ -49,7 +47,8 @@ def emote_downloader_and_iterator() -> Generator[str, None, None]:
with urllib.request.urlopen(registry_dump_url, timeout=10) as response: with urllib.request.urlopen(registry_dump_url, timeout=10) as response:
emote_registry_data = json.load(response) emote_registry_data = json.load(response)
assert emote_registry_data["version"] == 1 if emote_registry_data["version"] != 1:
raise Exception("unsupported emote registry version")
allow_nsfw = config.getboolean("default", "allow_nsfw", fallback=False) allow_nsfw = config.getboolean("default", "allow_nsfw", fallback=False)
emotes = [emote for emote in emote_registry_data["list"] if emote["safe"] or allow_nsfw] emotes = [emote for emote in emote_registry_data["list"] if emote["safe"] or allow_nsfw]

View file

@ -1,19 +1,19 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import base64
import argparse import argparse
import base64
import sys
from hashlib import md5 from hashlib import md5
from typing import IO from typing import IO
from Crypto.Cipher import AES
from Crypto import Random
import sys
from Crypto import Random
from Crypto.Cipher import AES
CC_ENCRYPTION_MARKER_BYTES = b"[-!_0_!-]" CC_ENCRYPTION_MARKER_BYTES = b"[-!_0_!-]"
CC_ENCRYPTION_PASSPHRASE = b":_.NaN0" CC_ENCRYPTION_PASSPHRASE = b":_.NaN0"
def main(): def main() -> None:
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
# NOTE: Empty help strings are necessary for subparsers to show up in help. # NOTE: Empty help strings are necessary for subparsers to show up in help.
subparsers = parser.add_subparsers(required=True, metavar="COMMAND") subparsers = parser.add_subparsers(required=True, metavar="COMMAND")
@ -34,14 +34,15 @@ def main():
def cmd_pipe_decrypt(args: argparse.Namespace) -> None: def cmd_pipe_decrypt(args: argparse.Namespace) -> None:
input_file: IO[bytes] = ( input_file: IO[bytes] = (
sys.stdin.buffer if args.input_file == "-" else open(args.input_file, 'rb') sys.stdin.buffer if args.input_file == "-" else open(args.input_file, "rb")
) )
output_file: IO[bytes] = ( output_file: IO[bytes] = (
sys.stdout.buffer if args.output_file == "-" else open(args.output_file, 'wb') sys.stdout.buffer if args.output_file == "-" else open(args.output_file, "wb")
) )
encrypted = input_file.read() encrypted = input_file.read()
assert encrypted.startswith(CC_ENCRYPTION_MARKER_BYTES) if not encrypted.startswith(CC_ENCRYPTION_MARKER_BYTES):
raise Exception()
encrypted = encrypted[len(CC_ENCRYPTION_MARKER_BYTES):] encrypted = encrypted[len(CC_ENCRYPTION_MARKER_BYTES):]
decrypted = CryptoJsBridge.decrypt(encrypted, CC_ENCRYPTION_PASSPHRASE) decrypted = CryptoJsBridge.decrypt(encrypted, CC_ENCRYPTION_PASSPHRASE)
output_file.write(decrypted) output_file.write(decrypted)
@ -49,10 +50,10 @@ def cmd_pipe_decrypt(args: argparse.Namespace) -> None:
def cmd_pipe_encrypt(args: argparse.Namespace) -> None: def cmd_pipe_encrypt(args: argparse.Namespace) -> None:
input_file: IO[bytes] = ( input_file: IO[bytes] = (
sys.stdin.buffer if args.input_file == "-" else open(args.input_file, 'rb') sys.stdin.buffer if args.input_file == "-" else open(args.input_file, "rb")
) )
output_file: IO[bytes] = ( output_file: IO[bytes] = (
sys.stdout.buffer if args.output_file == "-" else open(args.output_file, 'wb') sys.stdout.buffer if args.output_file == "-" else open(args.output_file, "wb")
) )
decrypted = input_file.read() decrypted = input_file.read()
@ -87,7 +88,8 @@ class CryptoJsBridge:
""" """
Extended from <https://gist.github.com/gsakkis/4546068/1d65cea035562e36da2cc160d6a9e4821b553fa8#file-aes-py-L49-L57>. Extended from <https://gist.github.com/gsakkis/4546068/1d65cea035562e36da2cc160d6a9e4821b553fa8#file-aes-py-L49-L57>.
""" """
assert len(salt) == cls.SALT_SIZE if len(salt) != cls.SALT_SIZE:
raise Exception("invalid salt length")
data += salt data += salt
key = md5(data).digest() key = md5(data).digest()
final_key = key final_key = key
@ -114,7 +116,8 @@ class CryptoJsBridge:
Equivalent to `CryptoJS.AES.decrypt(encrypted, passphrase).toString(CryptoJS.enc.Utf8)`. Equivalent to `CryptoJS.AES.decrypt(encrypted, passphrase).toString(CryptoJS.enc.Utf8)`.
""" """
encrypted = base64.b64decode(encrypted) encrypted = base64.b64decode(encrypted)
assert encrypted.startswith(cls.SALTED_MARKER) if not encrypted.startswith(cls.SALTED_MARKER):
raise Exception("expected salt marker")
encrypted = encrypted[len(cls.SALTED_MARKER):] encrypted = encrypted[len(cls.SALTED_MARKER):]
salt, ciphertext = encrypted[:cls.SALT_SIZE], encrypted[cls.SALT_SIZE:] salt, ciphertext = encrypted[:cls.SALT_SIZE], encrypted[cls.SALT_SIZE:]
key_iv = cls.bytes_to_key(passphrase, salt, cls.KEY_SIZE + cls.IV_SIZE) key_iv = cls.bytes_to_key(passphrase, salt, cls.KEY_SIZE + cls.IV_SIZE)

View file

@ -3,16 +3,16 @@
# https://discord.com/developers/docs/reference#snowflakes # https://discord.com/developers/docs/reference#snowflakes
import sys import sys
import colorama
import time import time
import colorama
DISCORD_EPOCH = 1420070400000 # milliseconds DISCORD_EPOCH = 1420070400000 # milliseconds
user_snowflake = int(sys.argv[1]) user_snowflake = int(sys.argv[1])
def print_field(name, value): def print_field(name: str, value: object) -> None:
print( print(
"{}{}:{} {}".format(colorama.Style.BRIGHT, name.rjust(21), colorama.Style.RESET_ALL, value) "{}{}:{} {}".format(colorama.Style.BRIGHT, name.rjust(21), colorama.Style.RESET_ALL, value)
) )

View file

@ -1,9 +1,10 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import discord
import sys
import os import os
import sys
from typing import Optional, cast
import discord
guild_id = int(sys.argv[1]) guild_id = int(sys.argv[1])
voice_channel_id = int(sys.argv[2]) voice_channel_id = int(sys.argv[2])
@ -16,17 +17,17 @@ bot = discord.Client()
@bot.event @bot.event
async def on_ready(): async def on_ready() -> None:
print("logged in as {0} ({0.id})".format(bot.user)) print("logged in as {0} ({0.id})".format(bot.user))
guild: discord.Guild = bot.get_guild(guild_id) guild: Optional[discord.Guild] = bot.get_guild(guild_id)
if guild is None: if guild is None:
raise Exception("guild not found") raise Exception("guild not found")
voice_channel: discord.VoiceChannel = guild.get_channel(voice_channel_id) voice_channel: Optional[discord.VoiceChannel] = guild.get_channel(voice_channel_id)
if voice_channel is None: if voice_channel is None:
raise Exception("channel not found") raise Exception("channel not found")
voice_client = await voice_channel.connect() voice_client = cast(discord.voice_client.VoiceClient, await voice_channel.connect())
print("connected to {0} ({0.id}) in {1} ({1.id})".format(voice_channel, guild)) print("connected to {0} ({0.id}) in {1} ({1.id})".format(voice_channel, guild))
source = discord.FFmpegPCMAudio(pulseaudio_device, before_options="-f pulse") source = discord.FFmpegPCMAudio(pulseaudio_device, before_options="-f pulse")

View file

@ -4,15 +4,16 @@
# <https://discord.com/developers/docs/resources/user#user-object#get-user> # <https://discord.com/developers/docs/resources/user#user-object#get-user>
# <https://discord.com/developers/docs/reference> # <https://discord.com/developers/docs/reference>
import sys
import os
import urllib.request
import urllib.error
import colorama
import time
import argparse import argparse
import json import json
import os
import sys
import time
import urllib.error
import urllib.request
from typing import Dict, List, Optional
import colorama
DISCORD_EPOCH = 1420070400000 # milliseconds DISCORD_EPOCH = 1420070400000 # milliseconds
# https://discord.com/developers/docs/resources/user#user-object-user-flags # https://discord.com/developers/docs/resources/user#user-object-user-flags
@ -37,17 +38,17 @@ parser.add_argument("user_snowflake", type=int)
parser.add_argument("--bot-token", type=str) parser.add_argument("--bot-token", type=str)
parser.add_argument("--image-size", type=int) parser.add_argument("--image-size", type=int)
parser.add_argument("--get-prop", type=str) parser.add_argument("--get-prop", type=str)
parser.add_argument("--api-response", action='store_true') parser.add_argument("--api-response", action="store_true")
cli_args = parser.parse_args() cli_args = parser.parse_args()
user_snowflake = cli_args.user_snowflake user_snowflake: int = cli_args.user_snowflake
bot_token = cli_args.bot_token bot_token: Optional[str] = cli_args.bot_token
if bot_token is None: if bot_token is None:
with open(os.path.expanduser("~/.config/dotfiles/discord-tools-bot-token.txt")) as f: with open(os.path.expanduser("~/.config/dotfiles/discord-tools-bot-token.txt")) as f:
bot_token = f.read().strip() bot_token = f.read().strip()
image_size = cli_args.image_size image_size: Optional[int] = cli_args.image_size
if not (image_size is None or (image_size > 0 and image_size & (image_size - 1)) == 0): if not (image_size is None or (image_size > 0 and image_size & (image_size - 1)) == 0):
parser.error("image_size must be greater than zero and a power of two") parser.error("image_size must be greater than zero and a power of two")
@ -70,10 +71,10 @@ except urllib.error.HTTPError as err:
if cli_args.api_response: if cli_args.api_response:
json.dump(raw_data, sys.stdout, ensure_ascii=False, indent=2) json.dump(raw_data, sys.stdout, ensure_ascii=False, indent=2)
sys.stdout.write('\n') sys.stdout.write("\n")
sys.exit() sys.exit()
data = {} data: Dict[str, str] = {}
data["ID"] = raw_data["id"] data["ID"] = raw_data["id"]
data["Name"] = "{}#{}".format(raw_data["username"], raw_data["discriminator"]) data["Name"] = "{}#{}".format(raw_data["username"], raw_data["discriminator"])
@ -108,7 +109,7 @@ user_flags = raw_data["public_flags"]
if user_flags == 0: if user_flags == 0:
data["Flags"] = "<none>" data["Flags"] = "<none>"
else: else:
user_flag_names = [] user_flag_names: List[str] = []
for flag_name, bitmask in DISCORD_FLAGS.items(): for flag_name, bitmask in DISCORD_FLAGS.items():
if user_flags & bitmask: if user_flags & bitmask:
user_flag_names.append(flag_name) user_flag_names.append(flag_name)

View file

@ -4,12 +4,11 @@
# <https://www.dropbox.com/sh/uscmj9y3cjfwpsr/AAD35_ZZu64EBi0awLA07fxga?dl=0> # <https://www.dropbox.com/sh/uscmj9y3cjfwpsr/AAD35_ZZu64EBi0awLA07fxga?dl=0>
# <https://github.com/credomane/factoriomodsettings/blob/master/src/FactorioModSettings.js> # <https://github.com/credomane/factoriomodsettings/blob/master/src/FactorioModSettings.js>
import sys
import os
from pathlib import Path
import struct
import json import json
import os
import struct
import sys
from pathlib import Path
sys.path.insert(1, os.path.join(os.path.dirname(__file__), "..", "script-resources")) sys.path.insert(1, os.path.join(os.path.dirname(__file__), "..", "script-resources"))
import factorio.property_tree import factorio.property_tree
@ -23,7 +22,8 @@ with open(Path.home() / ".factorio" / "mods" / "mod-settings.dat", "rb") as f:
version_main, version_major, version_minor, version_developer = struct.unpack("<HHHH", f.read(8)) version_main, version_major, version_minor, version_developer = struct.unpack("<HHHH", f.read(8))
always_false_flag = factorio.property_tree.read_bool(f) always_false_flag = factorio.property_tree.read_bool(f)
assert not always_false_flag if always_false_flag:
raise Exception("the always-False-flag is True for some reason")
deserialized_data = { deserialized_data = {
"factorio_version": { "factorio_version": {

View file

@ -1,12 +1,12 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import argparse import argparse
from pathlib import Path
from typing import Union
import plyvel
import json
from sys import stdout
import base64 import base64
import json
from pathlib import Path
from sys import stdout
from typing import Union
import plyvel
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
encoding_names = ["utf8", "base16", "base32", "base64", "base85"] encoding_names = ["utf8", "base16", "base32", "base64", "base85"]
@ -32,7 +32,7 @@ def bytes_to_json(b: bytes, encoding: str) -> Union[str, list[int]]:
elif encoding == "base85": elif encoding == "base85":
return base64.b85encode(b).decode("ascii") return base64.b85encode(b).decode("ascii")
else: else:
assert False raise Exception("unreachable")
key_encoding: str = cli_args.key_encoding or cli_args.encoding key_encoding: str = cli_args.key_encoding or cli_args.encoding

View file

@ -1,13 +1,12 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# Taken from <https://unix.stackexchange.com/a/509417/411555> # Taken from <https://unix.stackexchange.com/a/509417/411555>
import gi
import sys import sys
import gi
gi.require_version("Gtk", "3.0") gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gio, GLib from gi.repository import Gio, GLib, Gtk
rec_mgr = Gtk.RecentManager.get_default() rec_mgr = Gtk.RecentManager.get_default()
for arg in sys.argv[1:]: for arg in sys.argv[1:]:

View file

@ -39,12 +39,13 @@
# created by this script. # created by this script.
import argparse import argparse
import mwclient
import json
from urllib.parse import urlencode
import html import html
import json
import re import re
from typing import Dict, List, cast
from urllib.parse import urlencode
import mwclient
LANG = "en" LANG = "en"
LANG_TEXT_DIRECTION = "ltr" LANG_TEXT_DIRECTION = "ltr"
@ -116,7 +117,7 @@ MODULES_PRELOAD_SCRIPTS = {
# ported from <https://github.com/wikimedia/mediawiki/blob/c15ded31a6ca79fa65c00d151a7220632ad90b6d/includes/parser/Sanitizer.php#L1205-L1222> # ported from <https://github.com/wikimedia/mediawiki/blob/c15ded31a6ca79fa65c00d151a7220632ad90b6d/includes/parser/Sanitizer.php#L1205-L1222>
def escape_css_class(class_str): def escape_css_class(class_str: str) -> str:
class_str = re.sub( class_str = re.sub(
r"""(^[0-9\-])|[\x00-\x20!"#$%&'()*+,.\/:;<=>?@[\]^`{|}~]|\xA0""", r"""(^[0-9\-])|[\x00-\x20!"#$%&'()*+,.\/:;<=>?@[\]^`{|}~]|\xA0""",
"_", "_",
@ -127,8 +128,8 @@ def escape_css_class(class_str):
return class_str return class_str
def json_dumps_compact(data): def json_dumps_compact(data: object) -> str:
return json.dumps(data, indent=None, separators=(",", ":")) return json.dumps(data, indent=None, separators=(",", ":"), ensure_ascii=False)
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
@ -147,7 +148,7 @@ cli_args = parser.parse_args()
site = mwclient.Site(cli_args.site, scheme=cli_args.scheme) site = mwclient.Site(cli_args.site, scheme=cli_args.scheme)
def get_load_script_url(**args): def get_load_script_url(**args: object) -> str:
return "{path}load{ext}?{args}".format( return "{path}load{ext}?{args}".format(
path=site.path, path=site.path,
ext=site.ext, ext=site.ext,
@ -177,7 +178,11 @@ result = site.post(
)["parse"] )["parse"]
def get_modules(page_modules, added_modules_dict, blocked_modules_dict={}): def get_modules(
page_modules: List[str],
added_modules_dict: Dict[str, List[str]],
blocked_modules_dict: Dict[str, List[str]] = {}
) -> List[str]:
modules = page_modules + added_modules_dict[cli_args.skin] modules = page_modules + added_modules_dict[cli_args.skin]
for blocked_module in blocked_modules_dict.get(cli_args.skin, []): for blocked_module in blocked_modules_dict.get(cli_args.skin, []):
try: try:
@ -272,7 +277,7 @@ rendered_html = """\
), ),
skin=html.escape(cli_args.skin), skin=html.escape(cli_args.skin),
page_class=html.escape(escape_css_class(result["displaytitle"])), page_class=html.escape(escape_css_class(result["displaytitle"])),
title=html.escape(result["displaytitle"]), title=html.escape(cast(str, result["displaytitle"])),
indicators_html="\n".join([ indicators_html="\n".join([
'<div id="mw-indicator-{}" class="mw-indicator">{}</div>'.format( '<div id="mw-indicator-{}" class="mw-indicator">{}</div>'.format(
indicator["name"], indicator["*"] indicator["name"], indicator["*"]

View file

@ -1,11 +1,11 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import gi
import argparse import argparse
import gi
gi.require_version("Gtk", "3.0") gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gdk, Pango gi.require_version("Gdk", "3.0")
from gi.repository import Gdk, Gtk, Pango
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument("message", type=str, nargs="+") parser.add_argument("message", type=str, nargs="+")
@ -24,13 +24,13 @@ scrolled_window.add(label)
window.add(scrolled_window) window.add(scrolled_window)
def on_key_release(target, event): def on_key_release(_target, event) -> None:
key = event.keyval key = event.keyval
if key in [Gdk.KEY_Escape, Gdk.KEY_q, Gdk.KEY_Q]: if key in [Gdk.KEY_Escape, Gdk.KEY_q, Gdk.KEY_Q]:
window.close() window.close()
def on_configure(target, event): def on_configure(target, event) -> None:
if target != window or event.type != Gdk.EventType.CONFIGURE: if target != window or event.type != Gdk.EventType.CONFIGURE:
return return
font_desc = Pango.FontDescription() font_desc = Pango.FontDescription()

View file

@ -7,15 +7,15 @@
# TODO: Update the menu on player status changes. # TODO: Update the menu on player status changes.
import math import math
import gi
import sys import sys
import gi
gi.require_version("Playerctl", "2.0") gi.require_version("Playerctl", "2.0")
gi.require_version("Gtk", "3.0") gi.require_version("Gtk", "3.0")
gi.require_version("Gdk", "3.0") gi.require_version("Gdk", "3.0")
gi.require_version("Pango", "1.0") gi.require_version("Pango", "1.0")
from gi.repository import Playerctl, Gtk, Gdk, GLib, Pango from gi.repository import Gdk, GLib, Gtk, Pango, Playerctl
# Larger priority values will make the player with this name appear higher in # Larger priority values will make the player with this name appear higher in
# the menu. The default priority is 0. # the menu. The default priority is 0.
@ -39,7 +39,7 @@ PLAYER_PLAYBACK_STATUS_EMOJIS = {
} }
def humanize_duration(duration): def humanize_duration(duration: float) -> str:
minutes, seconds = divmod(math.floor(duration), 60) minutes, seconds = divmod(math.floor(duration), 60)
hours, minutes = divmod(minutes, 60) hours, minutes = divmod(minutes, 60)
text = "{:02}:{:02}".format(minutes, seconds) text = "{:02}:{:02}".format(minutes, seconds)

View file

@ -7,15 +7,14 @@
# <https://stackoverflow.com/a/740183/12005228> # <https://stackoverflow.com/a/740183/12005228>
# <https://wiki.mozilla.org/Places:BookmarksComments> # <https://wiki.mozilla.org/Places:BookmarksComments>
import sys
import os import os
from pathlib import Path
from configparser import ConfigParser
import tempfile
import shutil import shutil
import sqlite3 import sqlite3
from typing import Optional, Tuple, Generator import sys
import tempfile
from configparser import ConfigParser
from pathlib import Path
from typing import Generator, Optional, Tuple
sys.path.insert(1, os.path.join(os.path.dirname(__file__), "..", "script-resources")) sys.path.insert(1, os.path.join(os.path.dirname(__file__), "..", "script-resources"))
import common_script_utils import common_script_utils