reformat all remaining Python scripts
This commit is contained in:
parent
dbd76019c8
commit
f8c01686ee
|
@ -8,6 +8,8 @@ let s:ignored_errors += ['E111']
|
|||
let s:ignored_errors += ['E114']
|
||||
" Indent for continuation lines is smaller than expected
|
||||
let s:ignored_errors += ['E121']
|
||||
" Import not at the top of the file
|
||||
let s:ignored_errors += ['E402']
|
||||
" Line too long
|
||||
let s:ignored_errors += ['E501']
|
||||
|
||||
|
|
|
@ -9,86 +9,76 @@ from typing import Any, Generator, Optional, Union
|
|||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
|
||||
sys.path.insert(1, os.path.join(os.path.dirname(__file__), "..", "script-resources"))
|
||||
import common_script_utils
|
||||
|
||||
|
||||
DEFAULT_REGISTRY_DUMP_URL = "https://stronghold.crosscode.ru/~ccbot/emote-registry.json"
|
||||
|
||||
|
||||
if os.name == "posix":
|
||||
config_path: Path = (
|
||||
common_script_utils.DOTFILES_CONFIG_DIR / "copy-crosscode-emoji-url.ini"
|
||||
)
|
||||
default_registry_dump_file: Path = (
|
||||
common_script_utils.DOTFILES_CACHE_DIR / "dotfiles"
|
||||
)
|
||||
config_path: Path = (common_script_utils.DOTFILES_CONFIG_DIR / "copy-crosscode-emoji-url.ini")
|
||||
default_registry_dump_file: Path = (common_script_utils.DOTFILES_CACHE_DIR / "dotfiles")
|
||||
else:
|
||||
common_script_utils.platform_not_supported_error()
|
||||
common_script_utils.platform_not_supported_error()
|
||||
config = ConfigParser(interpolation=None)
|
||||
config.read(config_path)
|
||||
|
||||
|
||||
emotes: list[dict[str, Any]] = []
|
||||
|
||||
|
||||
def emote_downloader_and_iterator() -> Generator[str, None, None]:
|
||||
global emotes
|
||||
global emotes
|
||||
|
||||
registry_dump_file: Optional[Union[str, Path]] = config.get(
|
||||
"default", "ccbot_emote_registry_dump_file", fallback=None
|
||||
)
|
||||
if registry_dump_file is not None:
|
||||
registry_dump_file = os.path.expanduser(registry_dump_file)
|
||||
else:
|
||||
registry_dump_file = default_registry_dump_file
|
||||
registry_dump_file: Optional[Union[
|
||||
str, Path]] = config.get("default", "ccbot_emote_registry_dump_file", fallback=None)
|
||||
if registry_dump_file is not None:
|
||||
registry_dump_file = os.path.expanduser(registry_dump_file)
|
||||
else:
|
||||
registry_dump_file = default_registry_dump_file
|
||||
|
||||
registry_dump_url = config.get(
|
||||
"default", "ccbot_emote_registry_dump_url", fallback=DEFAULT_REGISTRY_DUMP_URL
|
||||
)
|
||||
registry_dump_url = config.get(
|
||||
"default", "ccbot_emote_registry_dump_url", fallback=DEFAULT_REGISTRY_DUMP_URL
|
||||
)
|
||||
|
||||
emote_registry_data: dict[str, Any]
|
||||
try:
|
||||
with open(registry_dump_file, "r") as f:
|
||||
emote_registry_data = json.load(f)
|
||||
except FileNotFoundError:
|
||||
with urllib.request.urlopen(registry_dump_url, timeout=10) as response:
|
||||
emote_registry_data = json.load(response)
|
||||
emote_registry_data: dict[str, Any]
|
||||
try:
|
||||
with open(registry_dump_file, "r") as f:
|
||||
emote_registry_data = json.load(f)
|
||||
except FileNotFoundError:
|
||||
with urllib.request.urlopen(registry_dump_url, timeout=10) as response:
|
||||
emote_registry_data = json.load(response)
|
||||
|
||||
assert emote_registry_data["version"] == 1
|
||||
assert emote_registry_data["version"] == 1
|
||||
|
||||
emotes = [emote for emote in emote_registry_data["list"] if emote["safe"]]
|
||||
emotes = [emote for emote in emote_registry_data["list"] if emote["safe"]]
|
||||
|
||||
for emote in emotes:
|
||||
yield "{emote[ref]} [{emote[guild_name]}]".format(emote=emote)
|
||||
for emote in emotes:
|
||||
yield "{emote[ref]} [{emote[guild_name]}]".format(emote=emote)
|
||||
|
||||
|
||||
chosen_index = common_script_utils.run_chooser(
|
||||
emote_downloader_and_iterator(), prompt="emote", async_read=True
|
||||
emote_downloader_and_iterator(), prompt="emote", async_read=True
|
||||
)
|
||||
if chosen_index >= 0:
|
||||
chosen_emote = emotes[chosen_index]
|
||||
chosen_emote = emotes[chosen_index]
|
||||
|
||||
emote_url: urllib.parse.ParseResult = urllib.parse.urlparse(chosen_emote["url"])
|
||||
emote_url_query: dict[str, list[str]] = urllib.parse.parse_qs(emote_url.query)
|
||||
emote_url: urllib.parse.ParseResult = urllib.parse.urlparse(chosen_emote["url"])
|
||||
emote_url_query: dict[str, list[str]] = urllib.parse.parse_qs(emote_url.query)
|
||||
|
||||
if config.getboolean("default", "add_emote_name_to_url", fallback=False):
|
||||
emote_url_query["name"] = [chosen_emote["name"]]
|
||||
if config.getboolean("default", "add_emote_name_to_url", fallback=False):
|
||||
emote_url_query["name"] = [chosen_emote["name"]]
|
||||
|
||||
default_emote_image_size = config.getint(
|
||||
"default", "default_emote_image_size", fallback=None
|
||||
)
|
||||
if default_emote_image_size is not None:
|
||||
emote_url_query["size"] = [str(default_emote_image_size)]
|
||||
default_emote_image_size = config.getint("default", "default_emote_image_size", fallback=None)
|
||||
if default_emote_image_size is not None:
|
||||
emote_url_query["size"] = [str(default_emote_image_size)]
|
||||
|
||||
emote_url_query_str = urllib.parse.urlencode(emote_url_query, doseq=True)
|
||||
emote_url_str = urllib.parse.urlunparse(
|
||||
emote_url._replace(query=emote_url_query_str)
|
||||
)
|
||||
emote_url_query_str = urllib.parse.urlencode(emote_url_query, doseq=True)
|
||||
emote_url_str = urllib.parse.urlunparse(emote_url._replace(query=emote_url_query_str))
|
||||
|
||||
common_script_utils.set_clipboard(emote_url_str)
|
||||
common_script_utils.set_clipboard(emote_url_str)
|
||||
|
||||
common_script_utils.send_notification(
|
||||
os.path.basename(__file__),
|
||||
"copied URL of {} to clipboard!".format(chosen_emote["ref"]),
|
||||
)
|
||||
common_script_utils.send_notification(
|
||||
os.path.basename(__file__),
|
||||
"copied URL of {} to clipboard!".format(chosen_emote["ref"]),
|
||||
)
|
||||
|
|
|
@ -6,17 +6,16 @@ import sys
|
|||
import colorama
|
||||
import time
|
||||
|
||||
|
||||
DISCORD_EPOCH = 1420070400000 # milliseconds
|
||||
|
||||
user_snowflake = int(sys.argv[1])
|
||||
|
||||
|
||||
def print_field(name, value):
|
||||
print(
|
||||
"{}{}:{} {}".format(
|
||||
colorama.Style.BRIGHT, name.rjust(21), colorama.Style.RESET_ALL, value
|
||||
)
|
||||
)
|
||||
print(
|
||||
"{}{}:{} {}".format(colorama.Style.BRIGHT, name.rjust(21), colorama.Style.RESET_ALL, value)
|
||||
)
|
||||
|
||||
|
||||
creation_time = (user_snowflake >> 22) + DISCORD_EPOCH
|
||||
|
@ -25,11 +24,11 @@ internal_process_id = (user_snowflake >> 12) & 0x1F
|
|||
increment = user_snowflake & 0xFFF
|
||||
|
||||
print_field(
|
||||
"Created at",
|
||||
"{}.{}".format(
|
||||
time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(creation_time // 1000)),
|
||||
creation_time % 1000,
|
||||
),
|
||||
"Created at",
|
||||
"{}.{}".format(
|
||||
time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(creation_time // 1000)),
|
||||
creation_time % 1000,
|
||||
),
|
||||
)
|
||||
print_field("Internal worker ID", internal_worker_id)
|
||||
print_field("Internal process ID", internal_process_id)
|
||||
|
|
|
@ -4,35 +4,33 @@ import discord
|
|||
import sys
|
||||
import os
|
||||
|
||||
|
||||
guild_id = int(sys.argv[1])
|
||||
voice_channel_id = int(sys.argv[2])
|
||||
pulseaudio_device = sys.argv[3]
|
||||
|
||||
with open(os.path.expanduser("~/.config/dotfiles/discord-tools-bot-token.txt")) as f:
|
||||
bot_token = f.read().strip()
|
||||
|
||||
bot_token = f.read().strip()
|
||||
|
||||
bot = discord.Client()
|
||||
|
||||
|
||||
@bot.event
|
||||
async def on_ready():
|
||||
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)
|
||||
if guild is None:
|
||||
raise Exception("guild not found")
|
||||
voice_channel: discord.VoiceChannel = guild.get_channel(voice_channel_id)
|
||||
if voice_channel is None:
|
||||
raise Exception("channel not found")
|
||||
guild: discord.Guild = bot.get_guild(guild_id)
|
||||
if guild is None:
|
||||
raise Exception("guild not found")
|
||||
voice_channel: discord.VoiceChannel = guild.get_channel(voice_channel_id)
|
||||
if voice_channel is None:
|
||||
raise Exception("channel not found")
|
||||
|
||||
voice_client = await voice_channel.connect()
|
||||
print("connected to {0} ({0.id}) in {1} ({1.id})".format(voice_channel, guild))
|
||||
voice_client = await voice_channel.connect()
|
||||
print("connected to {0} ({0.id}) in {1} ({1.id})".format(voice_channel, guild))
|
||||
|
||||
source = discord.FFmpegPCMAudio(pulseaudio_device, before_options="-f pulse")
|
||||
voice_client.play(
|
||||
source, after=lambda e: print("Player error: %s" % e) if e else None
|
||||
)
|
||||
source = discord.FFmpegPCMAudio(pulseaudio_device, before_options="-f pulse")
|
||||
voice_client.play(source, after=lambda e: print("Player error: %s" % e) if e else None)
|
||||
|
||||
|
||||
bot.run(bot_token)
|
||||
|
|
|
@ -18,22 +18,21 @@ import typing
|
|||
DISCORD_EPOCH = 1420070400000 # milliseconds
|
||||
# https://discord.com/developers/docs/resources/user#user-object-user-flags
|
||||
DISCORD_FLAGS = {
|
||||
"Discord Employee": 1 << 0,
|
||||
"Discord Partner": 1 << 1,
|
||||
"HypeSquad Events": 1 << 2,
|
||||
"Bug Hunter Level 1": 1 << 3,
|
||||
"House of Bravery": 1 << 6,
|
||||
"House of Brilliance": 1 << 7,
|
||||
"House of Balance": 1 << 8,
|
||||
"Early Supporter": 1 << 9,
|
||||
"Team User": 1 << 10,
|
||||
"System": 1 << 12,
|
||||
"Bug Hunter Level 2": 1 << 14,
|
||||
"Verified Bot": 1 << 16,
|
||||
"Verified Bot Developer": 1 << 17,
|
||||
"Discord Employee": 1 << 0,
|
||||
"Discord Partner": 1 << 1,
|
||||
"HypeSquad Events": 1 << 2,
|
||||
"Bug Hunter Level 1": 1 << 3,
|
||||
"House of Bravery": 1 << 6,
|
||||
"House of Brilliance": 1 << 7,
|
||||
"House of Balance": 1 << 8,
|
||||
"Early Supporter": 1 << 9,
|
||||
"Team User": 1 << 10,
|
||||
"System": 1 << 12,
|
||||
"Bug Hunter Level 2": 1 << 14,
|
||||
"Verified Bot": 1 << 16,
|
||||
"Verified Bot Developer": 1 << 17,
|
||||
}
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("user_snowflake", type=int)
|
||||
parser.add_argument("--bot-token", type=str)
|
||||
|
@ -45,32 +44,29 @@ user_snowflake = cli_args.user_snowflake
|
|||
|
||||
bot_token = cli_args.bot_token
|
||||
if bot_token is None:
|
||||
with open(
|
||||
os.path.expanduser("~/.config/dotfiles/discord-tools-bot-token.txt")
|
||||
) as f:
|
||||
bot_token = f.read().strip()
|
||||
with open(os.path.expanduser("~/.config/dotfiles/discord-tools-bot-token.txt")) as f:
|
||||
bot_token = f.read().strip()
|
||||
|
||||
image_size = cli_args.image_size
|
||||
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")
|
||||
|
||||
try:
|
||||
opener = urllib.request.build_opener()
|
||||
# Don't send the User-Agent header, Discord blocks the default one
|
||||
opener.addheaders = []
|
||||
with opener.open(
|
||||
urllib.request.Request(
|
||||
"http://discord.com/api/users/{}".format(user_snowflake),
|
||||
headers={"Authorization": "Bot {}".format(bot_token)},
|
||||
),
|
||||
timeout=10,
|
||||
) as response:
|
||||
raw_data = json.load(response)
|
||||
opener = urllib.request.build_opener()
|
||||
# Don't send the User-Agent header, Discord blocks the default one
|
||||
opener.addheaders = []
|
||||
with opener.open(
|
||||
urllib.request.Request(
|
||||
"http://discord.com/api/users/{}".format(user_snowflake),
|
||||
headers={"Authorization": "Bot {}".format(bot_token)},
|
||||
),
|
||||
timeout=10,
|
||||
) as response:
|
||||
raw_data = json.load(response)
|
||||
except urllib.error.HTTPError as err:
|
||||
print(err, file=sys.stderr)
|
||||
print(err.read(), file=sys.stderr)
|
||||
raise err
|
||||
print(err, file=sys.stderr)
|
||||
print(err.read(), file=sys.stderr)
|
||||
raise err
|
||||
|
||||
data = {}
|
||||
|
||||
|
@ -78,19 +74,17 @@ data["ID"] = raw_data["id"]
|
|||
data["Name"] = "{}#{}".format(raw_data["username"], raw_data["discriminator"])
|
||||
|
||||
default_avatar_url = "https://cdn.discordapp.com/embed/avatars/{}.png".format(
|
||||
int(raw_data["discriminator"], 10) % 5
|
||||
int(raw_data["discriminator"], 10) % 5
|
||||
)
|
||||
avatar_url = (
|
||||
"https://cdn.discordapp.com/avatars/{}/{}.{}".format(
|
||||
raw_data["id"],
|
||||
raw_data["avatar"],
|
||||
"gif" if raw_data["avatar"].startswith("a_") else "png",
|
||||
)
|
||||
if raw_data["avatar"] is not None
|
||||
else default_avatar_url
|
||||
"https://cdn.discordapp.com/avatars/{}/{}.{}".format(
|
||||
raw_data["id"],
|
||||
raw_data["avatar"],
|
||||
"gif" if raw_data["avatar"].startswith("a_") else "png",
|
||||
) if raw_data["avatar"] is not None else default_avatar_url
|
||||
)
|
||||
if image_size is not None:
|
||||
avatar_url += "?size={}".format(image_size)
|
||||
avatar_url += "?size={}".format(image_size)
|
||||
|
||||
data["Avatar"] = avatar_url
|
||||
data["Default avatar"] = default_avatar_url
|
||||
|
@ -101,38 +95,37 @@ data["System user"] = raw_data.get("system", False)
|
|||
# https://discord.com/developers/docs/reference#convert-snowflake-to-datetime
|
||||
snowflake_creation_time = (user_snowflake >> 22) + DISCORD_EPOCH
|
||||
data["Created at"] = "{}.{} UTC".format(
|
||||
time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(snowflake_creation_time // 1000)),
|
||||
snowflake_creation_time % 1000,
|
||||
time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(snowflake_creation_time // 1000)),
|
||||
snowflake_creation_time % 1000,
|
||||
)
|
||||
|
||||
user_flags = raw_data["public_flags"]
|
||||
if user_flags == 0:
|
||||
data["Flags"] = "<none>"
|
||||
data["Flags"] = "<none>"
|
||||
else:
|
||||
user_flag_names = []
|
||||
for flag_name, bitmask in DISCORD_FLAGS.items():
|
||||
if user_flags & bitmask:
|
||||
user_flag_names.append(flag_name)
|
||||
data["Flags"] = ", ".join(user_flag_names)
|
||||
|
||||
user_flag_names = []
|
||||
for flag_name, bitmask in DISCORD_FLAGS.items():
|
||||
if user_flags & bitmask:
|
||||
user_flag_names.append(flag_name)
|
||||
data["Flags"] = ", ".join(user_flag_names)
|
||||
|
||||
if cli_args.get_prop is None:
|
||||
max_name_length = max(map(len, data.keys()))
|
||||
for name, value in data.items():
|
||||
max_name_length = max(map(len, data.keys()))
|
||||
for name, value in data.items():
|
||||
|
||||
if value is True:
|
||||
value = "yes"
|
||||
elif value is False:
|
||||
value = "no"
|
||||
if value is True:
|
||||
value = "yes"
|
||||
elif value is False:
|
||||
value = "no"
|
||||
|
||||
print(
|
||||
"{}{:>{}}:{} {}".format(
|
||||
colorama.Style.BRIGHT,
|
||||
name,
|
||||
max_name_length + 1,
|
||||
colorama.Style.RESET_ALL,
|
||||
value,
|
||||
)
|
||||
)
|
||||
print(
|
||||
"{}{:>{}}:{} {}".format(
|
||||
colorama.Style.BRIGHT,
|
||||
name,
|
||||
max_name_length + 1,
|
||||
colorama.Style.RESET_ALL,
|
||||
value,
|
||||
)
|
||||
)
|
||||
else:
|
||||
print(data[cli_args.get_prop])
|
||||
print(data[cli_args.get_prop])
|
||||
|
|
|
@ -10,31 +10,29 @@ from pathlib import Path
|
|||
import struct
|
||||
import json
|
||||
|
||||
|
||||
sys.path.insert(1, os.path.join(os.path.dirname(__file__), "..", "script-resources"))
|
||||
import factorio.property_tree
|
||||
|
||||
|
||||
with open(Path.home() / ".factorio" / "mods" / "mod-settings.dat", "rb") as f:
|
||||
|
||||
version_main: int
|
||||
version_major: int
|
||||
version_minor: int
|
||||
version_developer: int
|
||||
version_main, version_major, version_minor, version_developer = struct.unpack(
|
||||
"<HHHH", f.read(8)
|
||||
)
|
||||
version_main: int
|
||||
version_major: int
|
||||
version_minor: int
|
||||
version_developer: int
|
||||
version_main, version_major, version_minor, version_developer = struct.unpack("<HHHH", f.read(8))
|
||||
|
||||
always_false_flag = factorio.property_tree.read_bool(f)
|
||||
assert not always_false_flag
|
||||
always_false_flag = factorio.property_tree.read_bool(f)
|
||||
assert not always_false_flag
|
||||
|
||||
deserialized_data = {
|
||||
"factorio_version": {
|
||||
"main": version_main,
|
||||
"major": version_major,
|
||||
"minor": version_minor,
|
||||
"developer": version_developer,
|
||||
},
|
||||
"data": factorio.property_tree.read(f),
|
||||
}
|
||||
deserialized_data = {
|
||||
"factorio_version": {
|
||||
"main": version_main,
|
||||
"major": version_major,
|
||||
"minor": version_minor,
|
||||
"developer": version_developer,
|
||||
},
|
||||
"data": factorio.property_tree.read(f),
|
||||
}
|
||||
|
||||
print(json.dumps(deserialized_data, indent=2))
|
||||
print(json.dumps(deserialized_data, indent=2))
|
||||
|
|
|
@ -7,6 +7,7 @@ import json
|
|||
from sys import stdout
|
||||
import base64
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
encoding_names = ["utf8", "base16", "base32", "base64", "base85"]
|
||||
parser.add_argument("--encoding", "-e", choices=encoding_names, default="utf8")
|
||||
|
@ -17,33 +18,33 @@ cli_args = parser.parse_args()
|
|||
|
||||
|
||||
def bytes_to_json(b: bytes, encoding: str) -> Union[str, list[int]]:
|
||||
if encoding == "utf8":
|
||||
try:
|
||||
return b.decode("utf8")
|
||||
except UnicodeDecodeError:
|
||||
return list(b)
|
||||
elif encoding == "base16":
|
||||
return base64.b16encode(b).decode("ascii")
|
||||
elif encoding == "base32":
|
||||
return base64.b32encode(b).decode("ascii")
|
||||
elif encoding == "base64":
|
||||
return base64.b64encode(b).decode("ascii")
|
||||
elif encoding == "base85":
|
||||
return base64.b85encode(b).decode("ascii")
|
||||
else:
|
||||
assert False
|
||||
if encoding == "utf8":
|
||||
try:
|
||||
return b.decode("utf8")
|
||||
except UnicodeDecodeError:
|
||||
return list(b)
|
||||
elif encoding == "base16":
|
||||
return base64.b16encode(b).decode("ascii")
|
||||
elif encoding == "base32":
|
||||
return base64.b32encode(b).decode("ascii")
|
||||
elif encoding == "base64":
|
||||
return base64.b64encode(b).decode("ascii")
|
||||
elif encoding == "base85":
|
||||
return base64.b85encode(b).decode("ascii")
|
||||
else:
|
||||
assert False
|
||||
|
||||
|
||||
key_encoding: str = cli_args.key_encoding or cli_args.encoding
|
||||
value_encoding: str = cli_args.value_encoding or cli_args.encoding
|
||||
db = plyvel.DB(str(cli_args.db_path), create_if_missing=False)
|
||||
with db.iterator() as iterator:
|
||||
for key, value in iterator:
|
||||
json.dump(
|
||||
{
|
||||
"key": bytes_to_json(key, key_encoding),
|
||||
"value": bytes_to_json(value, value_encoding),
|
||||
},
|
||||
stdout,
|
||||
)
|
||||
stdout.write("\n")
|
||||
for key, value in iterator:
|
||||
json.dump(
|
||||
{
|
||||
"key": bytes_to_json(key, key_encoding),
|
||||
"value": bytes_to_json(value, value_encoding),
|
||||
},
|
||||
stdout,
|
||||
)
|
||||
stdout.write("\n")
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
#!/usr/bin/python3
|
||||
#!/usr/bin/env python3
|
||||
# Taken from <https://unix.stackexchange.com/a/509417/411555>
|
||||
|
||||
import gi
|
||||
import sys
|
||||
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk, Gio, GLib # noqa E402
|
||||
from gi.repository import Gtk, Gio, GLib
|
||||
|
||||
|
||||
rec_mgr = Gtk.RecentManager.get_default()
|
||||
for arg in sys.argv[1:]:
|
||||
rec_mgr.add_item(Gio.File.new_for_path(arg).get_uri())
|
||||
rec_mgr.add_item(Gio.File.new_for_path(arg).get_uri())
|
||||
GLib.idle_add(Gtk.main_quit)
|
||||
Gtk.main()
|
||||
|
|
|
@ -38,7 +38,6 @@
|
|||
# - The module `skins.citizen.scripts` references search inputs which aren't
|
||||
# created by this script.
|
||||
|
||||
|
||||
import argparse
|
||||
import mwclient
|
||||
import json
|
||||
|
@ -51,85 +50,85 @@ LANG = "en"
|
|||
LANG_TEXT_DIRECTION = "ltr"
|
||||
|
||||
MODULES_POST_LOAD = {
|
||||
"vector": [
|
||||
"site",
|
||||
"mediawiki.page.startup",
|
||||
"mediawiki.page.ready",
|
||||
"mediawiki.toc",
|
||||
# "mediawiki.searchSuggest",
|
||||
# "mediawiki.page.watch.ajax",
|
||||
"skins.vector.js",
|
||||
],
|
||||
"citizen": [
|
||||
# "site",
|
||||
# "mediawiki.page.startup",
|
||||
# "mediawiki.page.ready",
|
||||
# "mediawiki.toc",
|
||||
# "skins.citizen.scripts.toc",
|
||||
# "skins.citizen.scripts.search",
|
||||
# "skins.citizen.styles.search",
|
||||
# "skins.citizen.icons.search",
|
||||
# "skins.citizen.scripts",
|
||||
],
|
||||
"vector": [
|
||||
"site",
|
||||
"mediawiki.page.startup",
|
||||
"mediawiki.page.ready",
|
||||
"mediawiki.toc",
|
||||
# "mediawiki.searchSuggest",
|
||||
# "mediawiki.page.watch.ajax",
|
||||
"skins.vector.js",
|
||||
],
|
||||
"citizen": [
|
||||
# "site",
|
||||
# "mediawiki.page.startup",
|
||||
# "mediawiki.page.ready",
|
||||
# "mediawiki.toc",
|
||||
# "skins.citizen.scripts.toc",
|
||||
# "skins.citizen.scripts.search",
|
||||
# "skins.citizen.styles.search",
|
||||
# "skins.citizen.icons.search",
|
||||
# "skins.citizen.scripts",
|
||||
],
|
||||
}
|
||||
|
||||
MODULES_POST_LOAD_BLOCKED = {
|
||||
"citizen": [
|
||||
"skins.citizen.scripts.toc",
|
||||
"skins.citizen.scripts.search",
|
||||
"skins.citizen.styles.search",
|
||||
"skins.citizen.icons.search",
|
||||
],
|
||||
"citizen": [
|
||||
"skins.citizen.scripts.toc",
|
||||
"skins.citizen.scripts.search",
|
||||
"skins.citizen.styles.search",
|
||||
"skins.citizen.icons.search",
|
||||
],
|
||||
}
|
||||
|
||||
MODULES_PRELOAD_STYLES = {
|
||||
"vector": [
|
||||
"mediawiki.legacy.commonPrint",
|
||||
"mediawiki.legacy.shared",
|
||||
"mediawiki.skinning.interface",
|
||||
"mediawiki.toc.styles",
|
||||
"skins.vector.styles",
|
||||
"site.styles",
|
||||
],
|
||||
"citizen": [
|
||||
# "mediawiki.legacy.commonPrint",
|
||||
# "mediawiki.legacy.shared",
|
||||
"mediawiki.skinning.content.externallinks",
|
||||
# "mediawiki.toc.styles",
|
||||
"skins.citizen.icons",
|
||||
"skins.citizen.styles",
|
||||
"skins.citizen.icons.ca",
|
||||
"skins.citizen.icons.es",
|
||||
"skins.citizen.icons.footer",
|
||||
"skins.citizen.icons.n",
|
||||
"skins.citizen.icons.pt",
|
||||
"skins.citizen.icons.t",
|
||||
"skins.citizen.styles.fonts",
|
||||
"skins.citizen.styles.toc",
|
||||
"site.styles",
|
||||
],
|
||||
"vector": [
|
||||
"mediawiki.legacy.commonPrint",
|
||||
"mediawiki.legacy.shared",
|
||||
"mediawiki.skinning.interface",
|
||||
"mediawiki.toc.styles",
|
||||
"skins.vector.styles",
|
||||
"site.styles",
|
||||
],
|
||||
"citizen": [
|
||||
# "mediawiki.legacy.commonPrint",
|
||||
# "mediawiki.legacy.shared",
|
||||
"mediawiki.skinning.content.externallinks",
|
||||
# "mediawiki.toc.styles",
|
||||
"skins.citizen.icons",
|
||||
"skins.citizen.styles",
|
||||
"skins.citizen.icons.ca",
|
||||
"skins.citizen.icons.es",
|
||||
"skins.citizen.icons.footer",
|
||||
"skins.citizen.icons.n",
|
||||
"skins.citizen.icons.pt",
|
||||
"skins.citizen.icons.t",
|
||||
"skins.citizen.styles.fonts",
|
||||
"skins.citizen.styles.toc",
|
||||
"site.styles",
|
||||
],
|
||||
}
|
||||
|
||||
MODULES_PRELOAD_SCRIPTS = {
|
||||
"vector": ["startup"],
|
||||
"citizen": ["startup"],
|
||||
"vector": ["startup"],
|
||||
"citizen": ["startup"],
|
||||
}
|
||||
|
||||
|
||||
# ported from <https://github.com/wikimedia/mediawiki/blob/c15ded31a6ca79fa65c00d151a7220632ad90b6d/includes/parser/Sanitizer.php#L1205-L1222>
|
||||
def escape_css_class(class_str):
|
||||
class_str = re.sub(
|
||||
r"""(^[0-9\-])|[\x00-\x20!"#$%&'()*+,.\/:;<=>?@[\]^`{|}~]|\xA0""",
|
||||
"_",
|
||||
class_str,
|
||||
)
|
||||
class_str = re.sub(r"_+", "_", class_str)
|
||||
class_str = class_str.rstrip("_")
|
||||
return class_str
|
||||
class_str = re.sub(
|
||||
r"""(^[0-9\-])|[\x00-\x20!"#$%&'()*+,.\/:;<=>?@[\]^`{|}~]|\xA0""",
|
||||
"_",
|
||||
class_str,
|
||||
)
|
||||
class_str = re.sub(r"_+", "_", class_str)
|
||||
class_str = class_str.rstrip("_")
|
||||
return class_str
|
||||
|
||||
|
||||
def json_dumps_compact(data):
|
||||
return json.dumps(data, indent=None, separators=(",", ":"))
|
||||
return json.dumps(data, indent=None, separators=(",", ":"))
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
|
@ -137,50 +136,55 @@ parser.add_argument("--site", type=str, required=True)
|
|||
parser.add_argument("--scheme", type=str, default="https")
|
||||
parser.add_argument("--skin", type=str, default="vector")
|
||||
parser.add_argument(
|
||||
"--input", type=str, required=True,
|
||||
"--input",
|
||||
type=str,
|
||||
required=True,
|
||||
)
|
||||
parser.add_argument("--title", type=str)
|
||||
parser.add_argument("--output", type=str, required=True)
|
||||
cli_args = parser.parse_args()
|
||||
|
||||
|
||||
site = mwclient.Site(cli_args.site, scheme=cli_args.scheme)
|
||||
|
||||
|
||||
def get_load_script_url(**args):
|
||||
return "{path}load{ext}?{args}".format(
|
||||
path=site.path,
|
||||
ext=site.ext,
|
||||
args=urlencode({"lang": LANG, "skin": cli_args.skin, **args}),
|
||||
)
|
||||
return "{path}load{ext}?{args}".format(
|
||||
path=site.path,
|
||||
ext=site.ext,
|
||||
args=urlencode({
|
||||
"lang": LANG,
|
||||
"skin": cli_args.skin,
|
||||
**args
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
with open(cli_args.input, "r") as f:
|
||||
wikitext_str = f.read()
|
||||
wikitext_str = f.read()
|
||||
|
||||
result = site.post(
|
||||
"parse",
|
||||
title=cli_args.title,
|
||||
text=wikitext_str,
|
||||
contentmodel="wikitext",
|
||||
prop="text|indicators|displaytitle|modules|jsconfigvars|categorieshtml",
|
||||
preview=True,
|
||||
pst=True, # pre-save transforms
|
||||
sectionpreview=False,
|
||||
disableeditsection=True, # disables "[edit]" links next to headers
|
||||
useskin=cli_args.skin,
|
||||
uselang=LANG,
|
||||
"parse",
|
||||
title=cli_args.title,
|
||||
text=wikitext_str,
|
||||
contentmodel="wikitext",
|
||||
prop="text|indicators|displaytitle|modules|jsconfigvars|categorieshtml",
|
||||
preview=True,
|
||||
pst=True, # pre-save transforms
|
||||
sectionpreview=False,
|
||||
disableeditsection=True, # disables "[edit]" links next to headers
|
||||
useskin=cli_args.skin,
|
||||
uselang=LANG,
|
||||
)["parse"]
|
||||
|
||||
|
||||
def get_modules(page_modules, added_modules_dict, blocked_modules_dict={}):
|
||||
modules = page_modules + added_modules_dict[cli_args.skin]
|
||||
for blocked_module in blocked_modules_dict.get(cli_args.skin, []):
|
||||
try:
|
||||
modules.remove(blocked_module)
|
||||
except ValueError:
|
||||
pass
|
||||
return modules
|
||||
modules = page_modules + added_modules_dict[cli_args.skin]
|
||||
for blocked_module in blocked_modules_dict.get(cli_args.skin, []):
|
||||
try:
|
||||
modules.remove(blocked_module)
|
||||
except ValueError:
|
||||
pass
|
||||
return modules
|
||||
|
||||
|
||||
rendered_html = """\
|
||||
|
@ -240,53 +244,43 @@ rendered_html = """\
|
|||
|
||||
</html>
|
||||
""".format(
|
||||
lang=html.escape(LANG),
|
||||
text_dir=html.escape(LANG_TEXT_DIRECTION),
|
||||
base_url=html.escape("{}://{}".format(site.scheme, site.host)),
|
||||
page_modules_state_json=json_dumps_compact(
|
||||
{
|
||||
"noscript": "ready",
|
||||
"user.options": "ready",
|
||||
"user.tokens": "loading",
|
||||
**{name: "ready" for name in MODULES_PRELOAD_STYLES[cli_args.skin]},
|
||||
}
|
||||
),
|
||||
page_config_json=json_dumps_compact(result["jsconfigvars"]),
|
||||
page_modules_json=json_dumps_compact(
|
||||
get_modules(result["modules"], MODULES_POST_LOAD, MODULES_POST_LOAD_BLOCKED)
|
||||
),
|
||||
style_url=html.escape(
|
||||
get_load_script_url(
|
||||
only="styles",
|
||||
modules="|".join(
|
||||
get_modules(result["modulestyles"], MODULES_PRELOAD_STYLES)
|
||||
),
|
||||
)
|
||||
),
|
||||
script_url=html.escape(
|
||||
get_load_script_url(
|
||||
only="scripts",
|
||||
modules="|".join(
|
||||
get_modules(result["modulescripts"], MODULES_PRELOAD_SCRIPTS)
|
||||
),
|
||||
raw="1",
|
||||
)
|
||||
),
|
||||
skin=html.escape(cli_args.skin),
|
||||
page_class=html.escape(escape_css_class(result["displaytitle"])),
|
||||
title=html.escape(result["displaytitle"]),
|
||||
indicators_html="\n".join(
|
||||
[
|
||||
'<div id="mw-indicator-{}" class="mw-indicator">{}</div>'.format(
|
||||
indicator["name"], indicator["*"]
|
||||
)
|
||||
for indicator in result["indicators"]
|
||||
]
|
||||
),
|
||||
content_html=result["text"]["*"],
|
||||
categories_html=result["categorieshtml"]["*"],
|
||||
lang=html.escape(LANG),
|
||||
text_dir=html.escape(LANG_TEXT_DIRECTION),
|
||||
base_url=html.escape("{}://{}".format(site.scheme, site.host)),
|
||||
page_modules_state_json=json_dumps_compact({
|
||||
"noscript": "ready",
|
||||
"user.options": "ready",
|
||||
"user.tokens": "loading",
|
||||
**{name: "ready" for name in MODULES_PRELOAD_STYLES[cli_args.skin]},
|
||||
}),
|
||||
page_config_json=json_dumps_compact(result["jsconfigvars"]),
|
||||
page_modules_json=json_dumps_compact(
|
||||
get_modules(result["modules"], MODULES_POST_LOAD, MODULES_POST_LOAD_BLOCKED)
|
||||
),
|
||||
style_url=html.escape(
|
||||
get_load_script_url(
|
||||
only="styles",
|
||||
modules="|".join(get_modules(result["modulestyles"], MODULES_PRELOAD_STYLES)),
|
||||
)
|
||||
),
|
||||
script_url=html.escape(
|
||||
get_load_script_url(
|
||||
only="scripts",
|
||||
modules="|".join(get_modules(result["modulescripts"], MODULES_PRELOAD_SCRIPTS)),
|
||||
raw="1",
|
||||
)
|
||||
),
|
||||
skin=html.escape(cli_args.skin),
|
||||
page_class=html.escape(escape_css_class(result["displaytitle"])),
|
||||
title=html.escape(result["displaytitle"]),
|
||||
indicators_html="\n".join([
|
||||
'<div id="mw-indicator-{}" class="mw-indicator">{}</div>'.format(
|
||||
indicator["name"], indicator["*"]
|
||||
) for indicator in result["indicators"]
|
||||
]),
|
||||
content_html=result["text"]["*"],
|
||||
categories_html=result["categorieshtml"]["*"],
|
||||
)
|
||||
|
||||
|
||||
with open(cli_args.output, "w") as f:
|
||||
f.write(rendered_html)
|
||||
f.write(rendered_html)
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
import gi
|
||||
import argparse
|
||||
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk, Gdk, Pango # noqa: E402
|
||||
from gi.repository import Gtk, Gdk, Pango
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
|
@ -12,7 +13,6 @@ args = parser.parse_args()
|
|||
|
||||
message = " ".join(args.message)
|
||||
|
||||
|
||||
window = Gtk.ApplicationWindow()
|
||||
window.set_keep_above(True)
|
||||
window.set_decorated(False)
|
||||
|
@ -25,17 +25,17 @@ window.add(scrolled_window)
|
|||
|
||||
|
||||
def on_key_release(target, event):
|
||||
key = event.keyval
|
||||
if key in [Gdk.KEY_Escape, Gdk.KEY_q, Gdk.KEY_Q]:
|
||||
window.close()
|
||||
key = event.keyval
|
||||
if key in [Gdk.KEY_Escape, Gdk.KEY_q, Gdk.KEY_Q]:
|
||||
window.close()
|
||||
|
||||
|
||||
def on_configure(target, event):
|
||||
if target != window or event.type != Gdk.EventType.CONFIGURE:
|
||||
return
|
||||
font_desc = Pango.FontDescription()
|
||||
font_desc.set_size(Pango.SCALE * event.height * 2 / 3)
|
||||
label.override_font(font_desc)
|
||||
if target != window or event.type != Gdk.EventType.CONFIGURE:
|
||||
return
|
||||
font_desc = Pango.FontDescription()
|
||||
font_desc.set_size(Pango.SCALE * event.height * 2 / 3)
|
||||
label.override_font(font_desc)
|
||||
|
||||
|
||||
window.connect("configure-event", on_configure)
|
||||
|
|
|
@ -9,162 +9,162 @@
|
|||
import math
|
||||
import gi
|
||||
|
||||
|
||||
gi.require_version("Playerctl", "2.0")
|
||||
gi.require_version("Gtk", "3.0")
|
||||
gi.require_version("Gdk", "3.0")
|
||||
gi.require_version("Pango", "1.0")
|
||||
from gi.repository import Playerctl, Gtk, Gdk, GLib, Pango # noqa: E402
|
||||
|
||||
from gi.repository import Playerctl, Gtk, Gdk, GLib, Pango
|
||||
|
||||
# Larger priority values will make the player with this name appear higher in
|
||||
# the menu. The default priority is 0.
|
||||
PLAYER_NAME_PRIORITIES = {
|
||||
"audacious": 2,
|
||||
"mpv": 1,
|
||||
"vlc": 1,
|
||||
"firefox": -1,
|
||||
"chrome": -2,
|
||||
"chromium": -2,
|
||||
"audacious": 2,
|
||||
"mpv": 1,
|
||||
"vlc": 1,
|
||||
"firefox": -1,
|
||||
"chrome": -2,
|
||||
"chromium": -2,
|
||||
}
|
||||
|
||||
PLAYER_ICON_NAME_FIXES = {
|
||||
"chrome": "google-chrome",
|
||||
"chrome": "google-chrome",
|
||||
}
|
||||
|
||||
PLAYER_PLAYBACK_STATUS_EMOJIS = {
|
||||
Playerctl.PlaybackStatus.PLAYING: "\u25B6",
|
||||
Playerctl.PlaybackStatus.PAUSED: "\u23F8",
|
||||
Playerctl.PlaybackStatus.STOPPED: "\u23F9",
|
||||
Playerctl.PlaybackStatus.PLAYING: "\u25B6",
|
||||
Playerctl.PlaybackStatus.PAUSED: "\u23F8",
|
||||
Playerctl.PlaybackStatus.STOPPED: "\u23F9",
|
||||
}
|
||||
|
||||
|
||||
def humanize_duration(duration):
|
||||
minutes, seconds = divmod(math.floor(duration), 60)
|
||||
hours, minutes = divmod(minutes, 60)
|
||||
text = "{:02}:{:02}".format(minutes, seconds)
|
||||
if hours > 0:
|
||||
text = "{}:{}".format(hours, text)
|
||||
return text
|
||||
minutes, seconds = divmod(math.floor(duration), 60)
|
||||
hours, minutes = divmod(minutes, 60)
|
||||
text = "{:02}:{:02}".format(minutes, seconds)
|
||||
if hours > 0:
|
||||
text = "{}:{}".format(hours, text)
|
||||
return text
|
||||
|
||||
|
||||
def iter_metadata_entries_for_player(player):
|
||||
metadata = player.props.metadata
|
||||
metadata = player.props.metadata
|
||||
|
||||
title = metadata.lookup_value("xesam:title")
|
||||
if title:
|
||||
yield title.get_string()
|
||||
title = metadata.lookup_value("xesam:title")
|
||||
if title:
|
||||
yield title.get_string()
|
||||
|
||||
album = metadata.lookup_value("xesam:album")
|
||||
if album:
|
||||
yield album.get_string()
|
||||
album = metadata.lookup_value("xesam:album")
|
||||
if album:
|
||||
yield album.get_string()
|
||||
|
||||
if player.props.can_seek:
|
||||
position_secs = player.props.position / 1e6
|
||||
duration = metadata.lookup_value("mpris:length")
|
||||
if duration is not None and duration.is_of_type(GLib.VariantType.new("x")):
|
||||
duration_secs = duration.get_int64() / 1e6
|
||||
yield "Time: {} / {}".format(
|
||||
humanize_duration(position_secs), humanize_duration(duration_secs)
|
||||
)
|
||||
if player.props.can_seek:
|
||||
position_secs = player.props.position / 1e6
|
||||
duration = metadata.lookup_value("mpris:length")
|
||||
if duration is not None and duration.is_of_type(GLib.VariantType.new("x")):
|
||||
duration_secs = duration.get_int64() / 1e6
|
||||
yield "Time: {} / {}".format(
|
||||
humanize_duration(position_secs), humanize_duration(duration_secs)
|
||||
)
|
||||
|
||||
|
||||
def iter_actions_for_player(player):
|
||||
if not player.props.can_control:
|
||||
yield ("This player can't be controlled!", None, False, None)
|
||||
return
|
||||
if not player.props.can_control:
|
||||
yield ("This player can't be controlled!", None, False, None)
|
||||
return
|
||||
|
||||
playback_status = player.props.playback_status
|
||||
if playback_status == Playerctl.PlaybackStatus.PLAYING:
|
||||
yield (
|
||||
"_Pause",
|
||||
"media-playback-pause",
|
||||
player.props.can_pause,
|
||||
player.pause,
|
||||
)
|
||||
elif playback_status == Playerctl.PlaybackStatus.PAUSED:
|
||||
yield (
|
||||
"Resume (_P)",
|
||||
"media-playback-start",
|
||||
player.props.can_play,
|
||||
player.play,
|
||||
)
|
||||
elif playback_status == Playerctl.PlaybackStatus.STOPPED:
|
||||
yield (
|
||||
"_Play",
|
||||
"media-playback-start",
|
||||
player.props.can_play,
|
||||
player.play,
|
||||
)
|
||||
|
||||
# See <https://github.com/altdesktop/playerctl/blob/c83a12a97031f64b260ea7f1be03386c3886b2d4/playerctl/playerctl-cli.c#L231-L235>
|
||||
playback_status = player.props.playback_status
|
||||
if playback_status == Playerctl.PlaybackStatus.PLAYING:
|
||||
yield (
|
||||
"_Stop",
|
||||
"media-playback-stop",
|
||||
player.props.can_play and playback_status != Playerctl.PlaybackStatus.STOPPED,
|
||||
player.stop,
|
||||
"_Pause",
|
||||
"media-playback-pause",
|
||||
player.props.can_pause,
|
||||
player.pause,
|
||||
)
|
||||
elif playback_status == Playerctl.PlaybackStatus.PAUSED:
|
||||
yield (
|
||||
"Resume (_P)",
|
||||
"media-playback-start",
|
||||
player.props.can_play,
|
||||
player.play,
|
||||
)
|
||||
elif playback_status == Playerctl.PlaybackStatus.STOPPED:
|
||||
yield (
|
||||
"_Play",
|
||||
"media-playback-start",
|
||||
player.props.can_play,
|
||||
player.play,
|
||||
)
|
||||
|
||||
# See <https://github.com/altdesktop/playerctl/blob/c83a12a97031f64b260ea7f1be03386c3886b2d4/playerctl/playerctl-cli.c#L231-L235>
|
||||
yield (
|
||||
"_Stop",
|
||||
"media-playback-stop",
|
||||
player.props.can_play and playback_status != Playerctl.PlaybackStatus.STOPPED,
|
||||
player.stop,
|
||||
)
|
||||
|
||||
yield (
|
||||
"_Mute" if player.props.volume != 0.0 else "Nor_mal volume",
|
||||
"audio-volume-muted" if player.props.volume != 0.0 else "audio-volume-high",
|
||||
True,
|
||||
lambda volume: player.set_volume(volume),
|
||||
0.0 if player.props.volume != 0.0 else 1.0,
|
||||
)
|
||||
yield (
|
||||
"Volume +10%",
|
||||
"audio-volume-medium",
|
||||
True,
|
||||
lambda: player.set_volume(min(player.props.volume + 0.1, 1.0)),
|
||||
)
|
||||
yield (
|
||||
"Volume -10%",
|
||||
"audio-volume-low",
|
||||
True,
|
||||
lambda: player.set_volume(max(player.props.volume - 0.1, 0.0)),
|
||||
)
|
||||
|
||||
yield (
|
||||
"_Next",
|
||||
"media-skip-forward",
|
||||
player.props.can_go_next,
|
||||
player.next,
|
||||
)
|
||||
yield (
|
||||
"Previous (_B)",
|
||||
"media-skip-backward",
|
||||
player.props.can_go_previous,
|
||||
player.previous,
|
||||
)
|
||||
|
||||
shuffle = player.props.shuffle
|
||||
yield (
|
||||
"Don't shuffle (_R)" if shuffle else "Shuffle (_R)",
|
||||
"media-playlist-shuffle",
|
||||
True,
|
||||
lambda: player.set_shuffle(not shuffle),
|
||||
)
|
||||
|
||||
loop_status = player.props.loop_status
|
||||
for loop_action_name, loop_action_status in [
|
||||
("Don't _loop", Playerctl.LoopStatus.NONE),
|
||||
("Loop _one", Playerctl.LoopStatus.TRACK),
|
||||
("Loop _all", Playerctl.LoopStatus.PLAYLIST),
|
||||
]:
|
||||
yield (
|
||||
"_Mute" if player.props.volume != 0.0 else "Nor_mal volume",
|
||||
"audio-volume-muted" if player.props.volume != 0.0 else "audio-volume-high",
|
||||
True,
|
||||
lambda volume: player.set_volume(volume),
|
||||
0.0 if player.props.volume != 0.0 else 1.0,
|
||||
)
|
||||
yield (
|
||||
"Volume +10%",
|
||||
"audio-volume-medium",
|
||||
True,
|
||||
lambda: player.set_volume(min(player.props.volume + 0.1, 1.0)),
|
||||
)
|
||||
yield (
|
||||
"Volume -10%",
|
||||
"audio-volume-low",
|
||||
True,
|
||||
lambda: player.set_volume(max(player.props.volume - 0.1, 0.0)),
|
||||
loop_action_name,
|
||||
"media-playlist-repeat",
|
||||
loop_action_status != loop_status,
|
||||
lambda loop_action_status: player.set_loop_status(loop_action_status),
|
||||
loop_action_status,
|
||||
)
|
||||
|
||||
yield (
|
||||
"_Next",
|
||||
"media-skip-forward",
|
||||
player.props.can_go_next,
|
||||
player.next,
|
||||
)
|
||||
yield (
|
||||
"Previous (_B)",
|
||||
"media-skip-backward",
|
||||
player.props.can_go_previous,
|
||||
player.previous,
|
||||
)
|
||||
|
||||
shuffle = player.props.shuffle
|
||||
yield (
|
||||
"Don't shuffle (_R)" if shuffle else "Shuffle (_R)",
|
||||
"media-playlist-shuffle",
|
||||
True,
|
||||
lambda: player.set_shuffle(not shuffle),
|
||||
)
|
||||
|
||||
loop_status = player.props.loop_status
|
||||
for loop_action_name, loop_action_status in [
|
||||
("Don't _loop", Playerctl.LoopStatus.NONE),
|
||||
("Loop _one", Playerctl.LoopStatus.TRACK),
|
||||
("Loop _all", Playerctl.LoopStatus.PLAYLIST),
|
||||
]:
|
||||
yield (
|
||||
loop_action_name,
|
||||
"media-playlist-repeat",
|
||||
loop_action_status != loop_status,
|
||||
lambda loop_action_status: player.set_loop_status(loop_action_status),
|
||||
loop_action_status,
|
||||
)
|
||||
|
||||
yield (
|
||||
"Play a_gain",
|
||||
"go-first",
|
||||
player.props.can_seek,
|
||||
lambda: player.set_position(0),
|
||||
)
|
||||
yield (
|
||||
"Play a_gain",
|
||||
"go-first",
|
||||
player.props.can_seek,
|
||||
lambda: player.set_position(0),
|
||||
)
|
||||
|
||||
|
||||
root_menu = Gtk.Menu()
|
||||
|
@ -172,93 +172,84 @@ root_menu = Gtk.Menu()
|
|||
player_names = Playerctl.list_players()
|
||||
|
||||
if len(player_names) > 0:
|
||||
players = []
|
||||
for player_name in player_names:
|
||||
player = Playerctl.Player.new_from_name(player_name)
|
||||
players.append(
|
||||
{
|
||||
"player": player,
|
||||
"player_name": player_name,
|
||||
"sorting_key": (
|
||||
player.props.playback_status != Playerctl.PlaybackStatus.PLAYING,
|
||||
-PLAYER_NAME_PRIORITIES.get(player_name.name, 0),
|
||||
player_name.instance,
|
||||
),
|
||||
}
|
||||
)
|
||||
players = sorted(
|
||||
players, key=lambda player_and_meta: player_and_meta["sorting_key"]
|
||||
players = []
|
||||
for player_name in player_names:
|
||||
player = Playerctl.Player.new_from_name(player_name)
|
||||
players.append({
|
||||
"player":
|
||||
player,
|
||||
"player_name":
|
||||
player_name,
|
||||
"sorting_key": (
|
||||
player.props.playback_status != Playerctl.PlaybackStatus.PLAYING,
|
||||
-PLAYER_NAME_PRIORITIES.get(player_name.name, 0),
|
||||
player_name.instance,
|
||||
),
|
||||
})
|
||||
players = sorted(players, key=lambda player_and_meta: player_and_meta["sorting_key"])
|
||||
|
||||
for player_and_meta in players:
|
||||
player_name = player_and_meta["player_name"]
|
||||
player = player_and_meta["player"]
|
||||
|
||||
player_menu_item = Gtk.ImageMenuItem.new_with_label(
|
||||
"{} [{}]".format(
|
||||
player_name.instance,
|
||||
PLAYER_PLAYBACK_STATUS_EMOJIS[player.props.playback_status],
|
||||
)
|
||||
)
|
||||
|
||||
for player_and_meta in players:
|
||||
player_name = player_and_meta["player_name"]
|
||||
player = player_and_meta["player"]
|
||||
player_icon_name = PLAYER_ICON_NAME_FIXES.get(player_name.name, player_name.name)
|
||||
player_icon = Gtk.Image.new_from_icon_name(player_icon_name, Gtk.IconSize.MENU)
|
||||
player_menu_item.set_image(player_icon)
|
||||
|
||||
player_menu_item = Gtk.ImageMenuItem.new_with_label(
|
||||
"{} [{}]".format(
|
||||
player_name.instance,
|
||||
PLAYER_PLAYBACK_STATUS_EMOJIS[player.props.playback_status],
|
||||
)
|
||||
actions_menu = Gtk.Menu()
|
||||
|
||||
track_metadata = player.props.metadata
|
||||
any_metadata_was_added = False
|
||||
for meta_entry_text in iter_metadata_entries_for_player(player):
|
||||
meta_menu_item = Gtk.MenuItem.new_with_label(meta_entry_text)
|
||||
meta_menu_item.set_sensitive(False)
|
||||
meta_menu_item_label = meta_menu_item.get_child()
|
||||
meta_menu_item_label.set_ellipsize(Pango.EllipsizeMode.END)
|
||||
meta_menu_item_label.set_max_width_chars(20)
|
||||
|
||||
actions_menu.append(meta_menu_item)
|
||||
any_metadata_was_added = True
|
||||
|
||||
if any_metadata_was_added:
|
||||
actions_menu.append(Gtk.SeparatorMenuItem.new())
|
||||
|
||||
for (
|
||||
action_name,
|
||||
action_icon_name,
|
||||
action_enabled,
|
||||
action_fn,
|
||||
*action_fn_args,
|
||||
) in iter_actions_for_player(player):
|
||||
action_menu_item = Gtk.ImageMenuItem.new_with_mnemonic(action_name)
|
||||
|
||||
if action_icon_name is not None:
|
||||
action_icon = Gtk.Image.new_from_icon_name(action_icon_name, Gtk.IconSize.MENU)
|
||||
action_menu_item.set_image(action_icon)
|
||||
|
||||
action_menu_item.set_sensitive(action_enabled)
|
||||
if action_fn is not None:
|
||||
action_menu_item.connect(
|
||||
"activate",
|
||||
lambda _menu_item, action_fn, action_fn_args: action_fn(*action_fn_args),
|
||||
action_fn,
|
||||
action_fn_args,
|
||||
)
|
||||
|
||||
player_icon_name = PLAYER_ICON_NAME_FIXES.get(
|
||||
player_name.name, player_name.name
|
||||
)
|
||||
player_icon = Gtk.Image.new_from_icon_name(player_icon_name, Gtk.IconSize.MENU)
|
||||
player_menu_item.set_image(player_icon)
|
||||
actions_menu.append(action_menu_item)
|
||||
|
||||
actions_menu = Gtk.Menu()
|
||||
|
||||
track_metadata = player.props.metadata
|
||||
any_metadata_was_added = False
|
||||
for meta_entry_text in iter_metadata_entries_for_player(player):
|
||||
meta_menu_item = Gtk.MenuItem.new_with_label(meta_entry_text)
|
||||
meta_menu_item.set_sensitive(False)
|
||||
meta_menu_item_label = meta_menu_item.get_child()
|
||||
meta_menu_item_label.set_ellipsize(Pango.EllipsizeMode.END)
|
||||
meta_menu_item_label.set_max_width_chars(20)
|
||||
|
||||
actions_menu.append(meta_menu_item)
|
||||
any_metadata_was_added = True
|
||||
|
||||
if any_metadata_was_added:
|
||||
actions_menu.append(Gtk.SeparatorMenuItem.new())
|
||||
|
||||
for (
|
||||
action_name,
|
||||
action_icon_name,
|
||||
action_enabled,
|
||||
action_fn,
|
||||
*action_fn_args,
|
||||
) in iter_actions_for_player(player):
|
||||
action_menu_item = Gtk.ImageMenuItem.new_with_mnemonic(action_name)
|
||||
|
||||
if action_icon_name is not None:
|
||||
action_icon = Gtk.Image.new_from_icon_name(
|
||||
action_icon_name, Gtk.IconSize.MENU
|
||||
)
|
||||
action_menu_item.set_image(action_icon)
|
||||
|
||||
action_menu_item.set_sensitive(action_enabled)
|
||||
if action_fn is not None:
|
||||
action_menu_item.connect(
|
||||
"activate",
|
||||
lambda _menu_item, action_fn, action_fn_args: action_fn(
|
||||
*action_fn_args
|
||||
),
|
||||
action_fn,
|
||||
action_fn_args,
|
||||
)
|
||||
|
||||
actions_menu.append(action_menu_item)
|
||||
|
||||
player_menu_item.set_submenu(actions_menu)
|
||||
root_menu.append(player_menu_item)
|
||||
player_menu_item.set_submenu(actions_menu)
|
||||
root_menu.append(player_menu_item)
|
||||
else:
|
||||
menu_item = Gtk.MenuItem.new_with_label("No players were detected!")
|
||||
menu_item.set_sensitive(False)
|
||||
root_menu.append(menu_item)
|
||||
|
||||
menu_item = Gtk.MenuItem.new_with_label("No players were detected!")
|
||||
menu_item.set_sensitive(False)
|
||||
root_menu.append(menu_item)
|
||||
|
||||
root_menu.connect("selection-done", Gtk.main_quit)
|
||||
root_menu.connect("deactivate", Gtk.main_quit)
|
||||
|
|
|
@ -16,35 +16,31 @@ import shutil
|
|||
import sqlite3
|
||||
from typing import Optional, Tuple, Generator
|
||||
|
||||
|
||||
sys.path.insert(1, os.path.join(os.path.dirname(__file__), "..", "script-resources"))
|
||||
import common_script_utils
|
||||
|
||||
|
||||
if sys.platform == "darwin":
|
||||
firefox_home: Path = Path.home() / "Library" / "Application Support" / "Firefox"
|
||||
firefox_home: Path = Path.home() / "Library" / "Application Support" / "Firefox"
|
||||
elif os.name == "posix":
|
||||
firefox_home: Path = Path.home() / ".mozilla" / "firefox"
|
||||
firefox_home: Path = Path.home() / ".mozilla" / "firefox"
|
||||
else:
|
||||
common_script_utils.platform_not_supported_error()
|
||||
|
||||
common_script_utils.platform_not_supported_error()
|
||||
|
||||
profiles_config = ConfigParser(interpolation=None)
|
||||
profiles_config.read(firefox_home / "profiles.ini")
|
||||
|
||||
installs_sections: list[str] = [
|
||||
s for s in profiles_config.sections() if s.startswith("Install")
|
||||
]
|
||||
installs_sections: list[str] = [s for s in profiles_config.sections() if s.startswith("Install")]
|
||||
if not installs_sections:
|
||||
raise Exception("no Firefox installations detected!")
|
||||
raise Exception("no Firefox installations detected!")
|
||||
if len(installs_sections) > 1:
|
||||
raise Exception("multiple Firefox installations are not supported!")
|
||||
raise Exception("multiple Firefox installations are not supported!")
|
||||
profile_dir: Path = firefox_home / profiles_config.get(installs_sections[0], "Default")
|
||||
|
||||
# should places.sqlite be used instead?
|
||||
db_path: Path = profile_dir / "weave" / "bookmarks.sqlite"
|
||||
if not db_path.is_file():
|
||||
raise Exception("'{}' is not a file".format(db_path))
|
||||
|
||||
raise Exception("'{}' is not a file".format(db_path))
|
||||
|
||||
# Firefox holds a lock over the database file, so I can't connect to it even
|
||||
# in the readonly mode: https://stackoverflow.com/a/7857866/12005228
|
||||
|
@ -55,78 +51,74 @@ os.close(db_copy_fd)
|
|||
chooser_entries: list[Tuple[str, str, Optional[str]]] = []
|
||||
|
||||
try:
|
||||
shutil.copyfile(db_path, db_copy_path)
|
||||
db = sqlite3.connect(db_copy_path)
|
||||
shutil.copyfile(db_path, db_copy_path)
|
||||
db = sqlite3.connect(db_copy_path)
|
||||
|
||||
urls: dict[int, str] = {}
|
||||
url_id: int
|
||||
url: str
|
||||
for url_id, url in db.execute("SELECT id, url FROM urls"):
|
||||
urls[url_id] = url
|
||||
urls: dict[int, str] = {}
|
||||
url_id: int
|
||||
url: str
|
||||
for url_id, url in db.execute("SELECT id, url FROM urls"):
|
||||
urls[url_id] = url
|
||||
|
||||
folders: dict[str, Tuple[Optional[str], str]] = {}
|
||||
folder_id: str
|
||||
parent_folder_id: str
|
||||
folder_title: str
|
||||
for folder_id, parent_folder_id, folder_title in db.execute(
|
||||
"SELECT guid, parentGuid, title FROM items WHERE kind = 3 AND validity AND NOT isDeleted"
|
||||
):
|
||||
folders[folder_id] = (
|
||||
parent_folder_id if parent_folder_id != folder_id else None,
|
||||
folder_title,
|
||||
)
|
||||
folders: dict[str, Tuple[Optional[str], str]] = {}
|
||||
folder_id: str
|
||||
parent_folder_id: str
|
||||
folder_title: str
|
||||
for folder_id, parent_folder_id, folder_title in db.execute(
|
||||
"SELECT guid, parentGuid, title FROM items WHERE kind = 3 AND validity AND NOT isDeleted"
|
||||
):
|
||||
folders[folder_id] = (
|
||||
parent_folder_id if parent_folder_id != folder_id else None,
|
||||
folder_title,
|
||||
)
|
||||
|
||||
url_title: str
|
||||
url_id: int
|
||||
url_keyword: str
|
||||
parent_folder_id: str
|
||||
for url_title, url_id, url_keyword, parent_folder_id in db.execute(
|
||||
"SELECT title, urlId, keyword, parentGuid FROM items WHERE kind = 1 AND validity AND NOT isDeleted"
|
||||
):
|
||||
url = urls[url_id]
|
||||
url_title: str
|
||||
url_id: int
|
||||
url_keyword: str
|
||||
parent_folder_id: str
|
||||
for url_title, url_id, url_keyword, parent_folder_id in db.execute(
|
||||
"SELECT title, urlId, keyword, parentGuid FROM items WHERE kind = 1 AND validity AND NOT isDeleted"
|
||||
):
|
||||
url = urls[url_id]
|
||||
|
||||
folder_path = list[str]()
|
||||
parent_folder_id_2: Optional[str] = parent_folder_id
|
||||
while parent_folder_id_2 is not None:
|
||||
folder = folders.get(parent_folder_id_2, None)
|
||||
if folder is None:
|
||||
# broken folder structure?
|
||||
folder_path.clear()
|
||||
break
|
||||
parent_folder_id_2, folder_title = folder
|
||||
if folder_title is not None:
|
||||
folder_path.append(folder_title)
|
||||
folder_path = list[str]()
|
||||
parent_folder_id_2: Optional[str] = parent_folder_id
|
||||
while parent_folder_id_2 is not None:
|
||||
folder = folders.get(parent_folder_id_2, None)
|
||||
if folder is None:
|
||||
# broken folder structure?
|
||||
folder_path.clear()
|
||||
break
|
||||
parent_folder_id_2, folder_title = folder
|
||||
if folder_title is not None:
|
||||
folder_path.append(folder_title)
|
||||
|
||||
folder_path_str = (
|
||||
("/" + "/".join(reversed(folder_path))) if len(folder_path) > 0 else None
|
||||
)
|
||||
folder_path_str = (("/" + "/".join(reversed(folder_path))) if len(folder_path) > 0 else None)
|
||||
|
||||
chooser_entries.append((url_title, url, folder_path_str))
|
||||
if url_keyword is not None:
|
||||
chooser_entries.append((url_keyword, url, folder_path_str))
|
||||
chooser_entries.append((url_title, url, folder_path_str))
|
||||
if url_keyword is not None:
|
||||
chooser_entries.append((url_keyword, url, folder_path_str))
|
||||
|
||||
finally:
|
||||
os.remove(db_copy_path)
|
||||
os.remove(db_copy_path)
|
||||
|
||||
|
||||
def chooser_entries_iter() -> Generator[str, None, None]:
|
||||
for title, url, folder_path_str in chooser_entries:
|
||||
entry_items = [title, url]
|
||||
if folder_path_str is not None:
|
||||
entry_items.append(folder_path_str)
|
||||
entry = " \u2014\u2014 ".join(entry_items)
|
||||
yield entry
|
||||
for title, url, folder_path_str in chooser_entries:
|
||||
entry_items = [title, url]
|
||||
if folder_path_str is not None:
|
||||
entry_items.append(folder_path_str)
|
||||
entry = " \u2014\u2014 ".join(entry_items)
|
||||
yield entry
|
||||
|
||||
|
||||
chosen_index = common_script_utils.run_chooser(
|
||||
chooser_entries_iter(), prompt="bookmark"
|
||||
)
|
||||
chosen_index = common_script_utils.run_chooser(chooser_entries_iter(), prompt="bookmark")
|
||||
|
||||
if chosen_index >= 0:
|
||||
_title, url, _folder_path_str = chooser_entries[chosen_index]
|
||||
print(url)
|
||||
_title, url, _folder_path_str = chooser_entries[chosen_index]
|
||||
print(url)
|
||||
|
||||
common_script_utils.set_clipboard(url)
|
||||
common_script_utils.send_notification(
|
||||
os.path.basename(__file__), "bookmark URL copied to clipboard!", url
|
||||
)
|
||||
common_script_utils.set_clipboard(url)
|
||||
common_script_utils.send_notification(
|
||||
os.path.basename(__file__), "bookmark URL copied to clipboard!", url
|
||||
)
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import random
|
||||
|
||||
|
||||
def randbyte() -> int:
|
||||
return random.randrange(0, 256)
|
||||
return random.randrange(0, 256)
|
||||
|
||||
|
||||
print("127.{}.{}.{}".format(randbyte(), randbyte(), randbyte()))
|
||||
|
|
Loading…
Reference in New Issue