You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

135 lines
4.5 KiB

  1. # Copyright (c) 2020 Tulir Asokan
  2. #
  3. # This Source Code Form is subject to the terms of the Mozilla Public
  4. # License, v. 2.0. If a copy of the MPL was not distributed with this
  5. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6. from typing import Dict, Optional
  7. from hashlib import sha256
  8. import mimetypes
  9. import argparse
  10. import os.path
  11. import asyncio
  12. import string
  13. import json
  14. try:
  15. import magic
  16. except ImportError:
  17. print("[Warning] Magic is not installed, using file extensions to guess mime types")
  18. magic = None
  19. from .lib import matrix, util
  20. def convert_name(name: str) -> str:
  21. name_translate = {
  22. ord(" "): ord("_"),
  23. }
  24. allowed_chars = string.ascii_letters + string.digits + "_-/.#"
  25. return "".join(filter(lambda char: char in allowed_chars, name.translate(name_translate)))
  26. async def upload_sticker(file: str, directory: str, old_stickers: Dict[str, matrix.StickerInfo]
  27. ) -> Optional[matrix.StickerInfo]:
  28. if file.startswith("."):
  29. return None
  30. path = os.path.join(directory, file)
  31. if not os.path.isfile(path):
  32. return None
  33. if magic:
  34. mime = magic.from_file(path, mime=True)
  35. else:
  36. mime, _ = mimetypes.guess_type(file)
  37. if not mime.startswith("image/"):
  38. return None
  39. print(f"Processing {file}", end="", flush=True)
  40. try:
  41. with open(path, "rb") as image_file:
  42. image_data = image_file.read()
  43. except Exception as e:
  44. print(f"... failed to read file: {e}")
  45. return None
  46. name = os.path.splitext(file)[0]
  47. # If the name starts with "number-", remove the prefix
  48. name_split = name.split("-", 1)
  49. if len(name_split) == 2 and name_split[0].isdecimal():
  50. name = name_split[1]
  51. sticker_id = f"sha256:{sha256(image_data).hexdigest()}"
  52. print(".", end="", flush=True)
  53. if sticker_id in old_stickers:
  54. sticker = {
  55. **old_stickers[sticker_id],
  56. "body": name,
  57. }
  58. print(f".. using existing upload")
  59. else:
  60. image_data, width, height = util.convert_image(image_data)
  61. print(".", end="", flush=True)
  62. mxc = await matrix.upload(image_data, "image/png", file)
  63. print(".", end="", flush=True)
  64. sticker = util.make_sticker(mxc, width, height, len(image_data), name)
  65. sticker["id"] = sticker_id
  66. print(" uploaded", flush=True)
  67. return sticker
  68. async def main(args: argparse.Namespace) -> None:
  69. await matrix.load_config(args.config)
  70. dirname = os.path.basename(os.path.abspath(args.path))
  71. meta_path = os.path.join(args.path, "pack.json")
  72. try:
  73. with open(meta_path) as pack_file:
  74. pack = json.load(pack_file)
  75. print(f"Loaded existing pack meta from {meta_path}")
  76. except FileNotFoundError:
  77. pack = {
  78. "title": args.title or dirname,
  79. "id": args.id or convert_name(dirname),
  80. "stickers": [],
  81. }
  82. old_stickers = {}
  83. else:
  84. old_stickers = {sticker["id"]: sticker for sticker in pack["stickers"]}
  85. pack["stickers"] = []
  86. for file in sorted(os.listdir(args.path)):
  87. sticker = await upload_sticker(file, args.path, old_stickers=old_stickers)
  88. if sticker:
  89. pack["stickers"].append(sticker)
  90. with open(meta_path, "w") as pack_file:
  91. json.dump(pack, pack_file)
  92. print(f"Wrote pack to {meta_path}")
  93. if args.add_to_index:
  94. picker_file_name = f"{pack['id']}.json"
  95. picker_pack_path = os.path.join(args.add_to_index, picker_file_name)
  96. with open(picker_pack_path, "w") as pack_file:
  97. json.dump(pack, pack_file)
  98. print(f"Copied pack to {picker_pack_path}")
  99. util.add_to_index(picker_file_name, args.add_to_index)
  100. parser = argparse.ArgumentParser()
  101. parser.add_argument("--config",
  102. help="Path to JSON file with Matrix homeserver and access_token",
  103. type=str, default="config.json", metavar="file")
  104. parser.add_argument("--title", help="Override the sticker pack displayname", type=str,
  105. metavar="title")
  106. parser.add_argument("--id", help="Override the sticker pack ID", type=str, metavar="id")
  107. parser.add_argument("--add-to-index", help="Sticker picker pack directory (usually 'web/packs/')",
  108. type=str, metavar="path")
  109. parser.add_argument("path", help="Path to the sticker pack directory", type=str)
  110. def cmd():
  111. asyncio.get_event_loop().run_until_complete(main(parser.parse_args()))
  112. if __name__ == "__main__":
  113. cmd()