Unverified Commit 1de3be8a authored by chayleaf's avatar chayleaf
Browse files

maubot: update plugins

parent 0cbe9c51
Loading
Loading
Loading
Loading
+108 −80
Original line number Diff line number Diff line
@@ -66,19 +66,20 @@
      }
    },
    "github": {
      "hash": "sha256-O3FhZ6US4iACEzEKdHLjBZfOJlHNGEeLSrHdqWULFvk=",
      "hash": "sha256-RUwZ6SOsWiygyb10GnDmvskAurSiW9rFwDylYgr6wII=",
      "owner": "rom4nik",
      "repo": "maubot-alternatingcaps",
      "rev": "v0.1.2"
      "rev": "v0.1.3"
    },
    "manifest": {
      "id": "pl.rom4nik.maubot.alternatingcaps",
      "license": "MIT",
      "main_class": "AlternatingCaps",
      "maubot": "0.4.0",
      "modules": [
        "alternatingcaps"
      ],
      "version": "0.1.2"
      "version": "0.1.3"
    }
  },
  "animemanga": {
@@ -202,41 +203,6 @@
      "version": "1.0.0"
    }
  },
  "characterai": {
    "attrs": {
      "meta": {
        "changelog": "https://github.com/Matthieu-LAURENT39/maubot-characterai/releases",
        "description": "Chat with characters from [character.ai](https://character.ai/) in your Matrix rooms! Very customizable.",
        "downloadPage": "https://github.com/Matthieu-LAURENT39/maubot-characterai/releases",
        "homepage": "https://github.com/Matthieu-LAURENT39/maubot-characterai"
      }
    },
    "github": {
      "hash": "sha256-nyVz0PDyNGAIFCxakWzEe8AG/PU+HlZJQQ85SL1bEvs=",
      "owner": "Matthieu-LAURENT39",
      "repo": "maubot-characterai",
      "rev": "v0.2.1"
    },
    "manifest": {
      "config": true,
      "database": true,
      "database_type": "asyncpg",
      "dependencies": [
        "characterai"
      ],
      "extra_files": [
        "base-config.yaml"
      ],
      "id": "com.github.Matthieu-LAURENT39.maubot-characterai",
      "license": "MIT",
      "main_class": "CAIBot",
      "maubot": "0.1.0",
      "modules": [
        "cai"
      ],
      "version": "0.2.1"
    }
  },
  "chatgpt": {
    "attrs": {
      "meta": {
@@ -337,10 +303,10 @@
      }
    },
    "github": {
      "hash": "sha256-wO63G2mdpz2FWjatVY5R+L7Chki087Ev7oMfpgyOnxM=",
      "hash": "sha256-DNKdlbxeLdxb/OSujANzlZkgX1Bg6Q+4Ubxwq/0QV5M=",
      "owner": "williamkray",
      "repo": "maubot-communitybot",
      "rev": "v0.1.7"
      "rev": "v0.1.17"
    },
    "manifest": {
      "database": true,
@@ -355,7 +321,7 @@
      "modules": [
        "community"
      ],
      "version": "0.1.7"
      "version": "0.1.17"
    }
  },
  "dice": {
@@ -542,10 +508,10 @@
      }
    },
    "github": {
      "hash": "sha256-VtZp4c3bbKCgbqQoJRnkle7Qn1zSGhgSPFAIlijQDOs=",
      "hash": "sha256-071iR5GlO+1WfMm6IK7by5nDfPhfOJCPr6WwB6qew/U=",
      "owner": "williamkray",
      "repo": "maubot-gifme",
      "rev": "a896a07fba53c90455431e79904f79d949c91f92"
      "rev": "v0.1.1"
    },
    "manifest": {
      "database": true,
@@ -560,7 +526,7 @@
      "modules": [
        "gifme"
      ],
      "version": "0.1.0"
      "version": "0.1.1"
    }
  },
  "giphy": {
@@ -603,10 +569,10 @@
      }
    },
    "github": {
      "hash": "sha256-Qc0KH8iGqMDa+1BXaB5fHtRIcsZRpTF2IufGMEXqV6Q=",
      "hash": "sha256-XTX600ugWnaXyk1SFRwXWGCwRLcjXDBo1Vy0BG4kj5g=",
      "owner": "maubot",
      "repo": "github",
      "rev": "v0.1.2"
      "rev": "v0.2.0"
    },
    "isOfficial": true,
    "manifest": {
@@ -618,11 +584,11 @@
      "id": "xyz.maubot.github",
      "license": "AGPL-3.0-or-later",
      "main_class": "GitHubBot",
      "maubot": "0.3.0",
      "maubot": "0.4.1",
      "modules": [
        "github"
      ],
      "version": "0.1.2",
      "version": "0.2.0",
      "webapp": true
    }
  },
@@ -796,6 +762,36 @@
      "webapp": false
    }
  },
  "idonthavespotify": {
    "attrs": {
      "meta": {
        "changelog": "https://github.com/HarHarLinks/maubot-idonthavespotify/releases",
        "description": "Reply to Spotify links with alternative streaming services.",
        "downloadPage": "https://github.com/HarHarLinks/maubot-idonthavespotify/releases",
        "homepage": "https://github.com/HarHarLinks/maubot-idonthavespotify"
      }
    },
    "github": {
      "hash": "sha256-BFB/eyl1+I5P65RCSNYwEeitGCWBB8u7qpd70qLFJuY=",
      "owner": "HarHarLinks",
      "repo": "maubot-idonthavespotify",
      "rev": "v1.1.0"
    },
    "manifest": {
      "config": true,
      "extra_files": [
        "base-config.yaml"
      ],
      "id": "de.sosnowkadub.idonthavespotify",
      "license": "MIT",
      "main_class": "idonthavespotify/IDontHaveSpotifyPlugin",
      "maubot": "0.1.0",
      "modules": [
        "idonthavespotify"
      ],
      "version": "1.1.0"
    }
  },
  "invite": {
    "attrs": {
      "meta": {
@@ -867,10 +863,10 @@
      }
    },
    "github": {
      "hash": "sha256-6bggnk3196M0eCkfYTJWLhiIwIVTtluffQzc58yIYzw=",
      "hash": "sha256-ML4NEqn5fa/rBqzrocq0bUiktr81eyh4Uob5lJak+lk=",
      "owner": "williamkray",
      "repo": "maubot-join",
      "rev": "v0.3.1"
      "rev": "v0.3.2"
    },
    "manifest": {
      "database": false,
@@ -884,7 +880,7 @@
      "modules": [
        "join"
      ],
      "version": "0.3.1"
      "version": "0.3.2"
    }
  },
  "karma": {
@@ -1126,31 +1122,33 @@
        "description": "A plugin create Discourse forum post from messages in Matrix or Bridged rooms and perform advanced forum searches directly from Matrix or Bridged rooms. Perfect for community building and engagement.",
        "downloadPage": "https://github.com/gitayam/matrix-to-discourse/releases",
        "homepage": "https://github.com/gitayam/matrix-to-discourse"
      }
      },
      "postPatch": "cd plugin"
    },
    "github": {
      "hash": "sha256-QIFgCQL9O/SVemXfxlXzPcPQ/qx68IU7ntArCk946iA=",
      "hash": "sha256-GMLpfpNsDKX/3PPeWrHwMVq8ppygJTPY/QhKe1JDphE=",
      "owner": "gitayam",
      "repo": "matrix-to-discourse",
      "rev": "v0.1.0.1"
      "rev": "v1.2.1"
    },
    "manifest": {
      "config": true,
      "database": false,
      "dependencies": [
        "aiohttp",
        "maubot",
        "mautrix",
        "openai",
        "pyyaml",
        "requests"
        "pyyaml"
      ],
      "extra_files": [
        "base-config.yaml"
      ],
      "id": "com.irregularchat.matrix_to_discourse",
      "id": "url.irregularchat.matrix_to_discourse",
      "license": "GPL-3.0",
      "main_class": "MatrixToDiscourseBot",
      "maubot": "0.1.0",
      "modules": [
        "bot"
        "MatrixToDiscourseBot"
      ],
      "version": "0.1.0.0"
      "version": "1.2.1"
    }
  },
  "media": {
@@ -1214,10 +1212,10 @@
      }
    },
    "gitlab": {
      "hash": "sha256-6522dVqhGoPc/qjz65D3kXHks5LLb3yVe0K5abqdXrw=",
      "hash": "sha256-8Xsw/yO5Ma4weKAhk8DPQeCrn2ksk9c7J3oRzlAy2rw=",
      "owner": "999eagle",
      "repo": "maubot-ntfy",
      "rev": "256aa8f315cbb184eba0256c2ec818abbdd2d408"
      "rev": "4e930c0e7100e06570707564fc471fc3c931708e"
    },
    "manifest": {
      "config": true,
@@ -1249,10 +1247,10 @@
      }
    },
    "github": {
      "hash": "sha256-vw2MT4pwmUUWolgzkq0nZ/YaAlKUANrN0NPXXFf7B1k=",
      "hash": "sha256-PoHZhMy5sCgl1pf6xfbFpF0J9cSNk/NaVm6XeIgeAzU=",
      "owner": "tcpipuk",
      "repo": "maubot-openai-translate",
      "rev": "v0.3.1"
      "rev": "v0.4.0"
    },
    "manifest": {
      "config": true,
@@ -1266,7 +1264,7 @@
      "modules": [
        "openaitranslate"
      ],
      "version": "0.3.1"
      "version": "0.4.0"
    }
  },
  "ovgumensabot": {
@@ -1279,10 +1277,10 @@
      }
    },
    "github": {
      "hash": "sha256-nuOLUPwE0F15FgOtbq3+qmNNd2eHRrRNJPMM+v1Ksy0=",
      "hash": "sha256-Gzw6YWvku0PbN/sNKABGjPPOpskubOHUVuiwMjD3m+c=",
      "owner": "v411e",
      "repo": "ovgumensabot",
      "rev": "v0.0.8"
      "rev": "v0.0.9"
    },
    "manifest": {
      "database": true,
@@ -1300,7 +1298,7 @@
      "modules": [
        "ovgumensabot"
      ],
      "version": "0.0.8"
      "version": "0.0.9"
    }
  },
  "pingcheck": {
@@ -1395,17 +1393,17 @@
  "pretix-inviter": {
    "attrs": {
      "meta": {
        "changelog": "https://github.com/fedora-infra/maubot-pretix-invite/blob/v0.3.2/CHANGELOG.md",
        "changelog": "https://github.com/fedora-infra/maubot-pretix-invite/blob/v0.4.1.1/CHANGELOG.md",
        "description": "A maubot plugin for inviting event participants from the pretix ticketing platform into a matrix room",
        "downloadPage": "https://github.com/fedora-infra/maubot-pretix-invite/releases",
        "homepage": "https://github.com/fedora-infra/maubot-pretix-invite"
      }
    },
    "github": {
      "hash": "sha256-KgWGvZ7QHcH0/u6+kodW8MAXtco4MM5MpbKscW903nQ=",
      "hash": "sha256-3LfIh4THe9lGl2RJHuXA1SKJ9pvi8851F8n+HPSID+o=",
      "owner": "fedora-infra",
      "repo": "maubot-pretix-invite",
      "rev": "v0.3.2"
      "rev": "v0.4.1.1"
    },
    "manifest": {
      "config": true,
@@ -1418,7 +1416,7 @@
      "modules": [
        "event_helper"
      ],
      "version": "0.3.2",
      "version": "0.4.1",
      "webapp": true
    }
  },
@@ -1654,10 +1652,10 @@
      }
    },
    "github": {
      "hash": "sha256-p/xJpJbzsOeQGcowvOhJSclPtmZyNyBaZBz+mexVqIY=",
      "hash": "sha256-1Wac/j8qOTA31BCI4emOSYAEYEbtOjdB5ACz1qnY6h8=",
      "owner": "maubot",
      "repo": "rss",
      "rev": "v0.3.2"
      "rev": "v0.4.1"
    },
    "isOfficial": true,
    "manifest": {
@@ -1676,7 +1674,37 @@
      "modules": [
        "rss"
      ],
      "version": "0.3.2"
      "version": "0.4.1"
    }
  },
  "rsvc": {
    "attrs": {
      "meta": {
        "changelog": "https://github.com/maubot/rsvc/releases",
        "description": "A bot to check the version of servers in room.",
        "downloadPage": "https://github.com/maubot/rsvc/releases",
        "homepage": "https://github.com/maubot/rsvc"
      }
    },
    "github": {
      "hash": "sha256-4VvC2a0WZ9mlwV4l1Nz9eWatCy0nbhEXFhoAhUhrT1A=",
      "owner": "maubot",
      "repo": "rsvc",
      "rev": "d67750085437fd50a6054bc1b6623b1a1341c0b8"
    },
    "isOfficial": true,
    "manifest": {
      "extra_files": [
        "base-config.yaml"
      ],
      "id": "xyz.maubot.rsvc",
      "license": "AGPL-3.0-or-later",
      "main_class": "ServerCheckerBot",
      "maubot": "0.1.0",
      "modules": [
        "rsvc"
      ],
      "version": "1.0.0"
    }
  },
  "satwcomic": {
@@ -1783,10 +1811,10 @@
      }
    },
    "github": {
      "hash": "sha256-naHY6f034uGnPIHidI7WXjcf2h/t0IYaPkO5QfKkXMs=",
      "hash": "sha256-pea3NlDExMkgfWfP003xO5Nt+RnidpVq9PzDOxzz7Ow=",
      "owner": "ggogel",
      "repo": "SocialMediaDownloadMaubot",
      "rev": "1.4.2"
      "rev": "1.4.4"
    },
    "manifest": {
      "config": true,
@@ -1802,7 +1830,7 @@
        "instaloader",
        "socialmediadownload"
      ],
      "version": "1.4.2"
      "version": "1.4.4"
    }
  },
  "songwhip": {
+143 −115
Original line number Diff line number Diff line
@@ -10,142 +10,164 @@ import sys
import toml
import zipfile

from typing import Dict, List

HOSTNAMES = {
    'git.skeg1.se': 'gitlab',
    'edugit.org': 'gitlab',
    'codeberg.org': 'gitea',
    "git.skeg1.se": "gitlab",
    "edugit.org": "gitlab",
    "codeberg.org": "gitea",
}
PLUGINS: Dict[str, dict] = {}
PLUGINS: dict[str, dict] = {}
# https://github.com/maubot/plugins.maubot.xyz/pull/45
SKIP = {"characterai"}
DIRS = {"matrix-to-discourse": "plugin"}

yaml = ruamel.yaml.YAML(typ="safe")

yaml = ruamel.yaml.YAML(typ='safe')
TMP = os.environ.get("TEMPDIR", "/tmp")

TMP = os.environ.get('TEMPDIR', '/tmp')

def process_repo(path: str, official: bool):
    global PLUGINS
    with open(path, 'rt') as f:
    with open(path, "rt") as f:
        data = yaml.load(f)
    name, repourl, license, desc = data['name'], data['repo'], data['license'], data['description']
    name, repourl, license, desc = (
        data["name"],
        data["repo"],
        data["license"],
        data["description"],
    )
    if name in SKIP:
        return
    origurl = repourl
    if '/' in name or ' ' in name:
        name = os.path.split(path)[-1].removesuffix('.yaml')
    name = name.replace('_', '-').lower()
    if "/" in name or " " in name:
        name = os.path.split(path)[-1].removesuffix(".yaml")
    name = name.replace("_", "-").lower()
    if name in PLUGINS.keys():
        raise ValueError(f'Duplicate plugin {name}, refusing to continue')
    repodir = os.path.join(TMP, 'maubot-plugins', name)
        raise ValueError(f"Duplicate plugin {name}, refusing to continue")
    repodir = os.path.join(TMP, "maubot-plugins", name)
    plugindir = repodir
    if '/tree/' in repourl:
        repourl, rev_path = repourl.split('/tree/')
        rev, subdir = rev_path.strip('/').split('/')
    if "/tree/" in repourl:
        repourl, rev_path = repourl.split("/tree/")
        rev, subdir = rev_path.strip("/").split("/")
        plugindir = os.path.join(plugindir, subdir)
    elif name in DIRS.keys():
        subdir = DIRS[name]
        plugindir = os.path.join(plugindir, subdir)
    else:
        rev = None
        subdir = None

    if repourl.startswith('http:'):
        repourl = 'https' + repourl[4:]
    repourl = repourl.rstrip('/')
    if repourl.startswith("http:"):
        repourl = "https" + repourl[4:]
    repourl = repourl.rstrip("/")
    if not os.path.exists(repodir):
        print('Fetching', name)
        repo = git.Repo.clone_from(repourl + '.git', repodir)
        print("Fetching", name)
        repo = git.Repo.clone_from(repourl + ".git", repodir)
    else:
        repo = git.Repo(repodir)
    tags = sorted(repo.tags, key=lambda t: t.commit.committed_datetime)
    tags = list(filter(lambda x: 'rc' not in str(x), tags))
    tags = list(filter(lambda x: "rc" not in str(x), tags))
    if tags:
        repo.git.checkout(tags[-1])
        rev = str(tags[-1])
    else:
        rev = str(repo.commit('HEAD'))
    ret: dict = {'attrs':{}}
        rev = str(repo.commit("HEAD"))
    ret: dict = {"attrs": {}}
    if subdir:
        ret['attrs']['postPatch'] = f'cd {subdir}'
    domain, query = repourl.removeprefix('https://').split('/', 1)
    hash = subprocess.run([
        'nurl',
        '--hash',
        f'file://{repodir}',
        rev
    ], capture_output=True, check=True).stdout.decode('utf-8')
    ret['attrs']['meta'] = {
        'description': desc,
        'homepage': origurl,
        ret["attrs"]["postPatch"] = f"cd {subdir}"
    domain, query = repourl.removeprefix("https://").split("/", 1)
    hash = subprocess.run(
        ["nurl", "--hash", f"file://{repodir}", rev], capture_output=True, check=True
    ).stdout.decode("utf-8")
    ret["attrs"]["meta"] = {
        "description": desc,
        "homepage": origurl,
    }
    if domain == 'github.com':
        owner, repo = query.split('/')
        ret['github'] = {
            'owner': owner,
            'repo': repo,
            'rev': rev,
            'hash': hash,
    if domain == "github.com":
        owner, repo = query.split("/")
        ret["github"] = {
            "owner": owner,
            "repo": repo,
            "rev": rev,
            "hash": hash,
        }
        ret['attrs']['meta']['downloadPage'] = f'{repourl}/releases'
        ret['attrs']['meta']['changelog'] = f'{repourl}/releases'
        repobase = f'{repourl}/blob/{rev}'
    elif HOSTNAMES.get(domain, 'gitea' if 'gitea.' in domain or 'forgejo.' in domain else None) == 'gitea':
        owner, repo = query.split('/')
        ret['gitea'] = {
            'domain': domain,
            'owner': owner,
            'repo': repo,
            'rev': rev,
            'hash': hash,
        ret["attrs"]["meta"]["downloadPage"] = f"{repourl}/releases"
        ret["attrs"]["meta"]["changelog"] = f"{repourl}/releases"
        repobase = f"{repourl}/blob/{rev}"
    elif (
        HOSTNAMES.get(
            domain, "gitea" if "gitea." in domain or "forgejo." in domain else None
        )
        == "gitea"
    ):
        owner, repo = query.split("/")
        ret["gitea"] = {
            "domain": domain,
            "owner": owner,
            "repo": repo,
            "rev": rev,
            "hash": hash,
        }
        repobase = f'{repourl}/src/commit/{rev}'
        ret['attrs']['meta']['downloadPage'] = f'{repourl}/releases'
        ret['attrs']['meta']['changelog'] = f'{repourl}/releases'
    elif HOSTNAMES.get(domain, 'gitlab' if 'gitlab.' in domain else None) == 'gitlab':
        owner, repo = query.split('/')
        ret['gitlab'] = {
            'owner': owner,
            'repo': repo,
            'rev': rev,
            'hash': hash,
        repobase = f"{repourl}/src/commit/{rev}"
        ret["attrs"]["meta"]["downloadPage"] = f"{repourl}/releases"
        ret["attrs"]["meta"]["changelog"] = f"{repourl}/releases"
    elif HOSTNAMES.get(domain, "gitlab" if "gitlab." in domain else None) == "gitlab":
        owner, repo = query.split("/")
        ret["gitlab"] = {
            "owner": owner,
            "repo": repo,
            "rev": rev,
            "hash": hash,
        }
        if domain != 'gitlab.com':
            ret['gitlab']['domain'] = domain
        repobase = f'{repourl}/-/blob/{rev}'
        if domain != "gitlab.com":
            ret["gitlab"]["domain"] = domain
        repobase = f"{repourl}/-/blob/{rev}"
    else:
        raise ValueError(f'Is {domain} Gitea or Gitlab, or something else? Please specify in the Python script!')
    if os.path.exists(os.path.join(plugindir, 'CHANGELOG.md')):
        ret['attrs']['meta']['changelog'] = f'{repobase}/CHANGELOG.md'
    if os.path.exists(os.path.join(plugindir, 'maubot.yaml')):
        with open(os.path.join(plugindir, 'maubot.yaml'), 'rt') as f:
            ret['manifest'] = yaml.load(f)
    elif os.path.exists(os.path.join(plugindir, 'pyproject.toml')):
        ret['isPoetry'] = True
        with open(os.path.join(plugindir, 'pyproject.toml'), 'rt') as f:
        raise ValueError(
            f"Is {domain} Gitea or Gitlab, or something else? Please specify in the Python script!"
        )
    if os.path.exists(os.path.join(plugindir, "CHANGELOG.md")):
        ret["attrs"]["meta"]["changelog"] = f"{repobase}/CHANGELOG.md"
    if os.path.exists(os.path.join(plugindir, "maubot.yaml")):
        with open(os.path.join(plugindir, "maubot.yaml"), "rt") as f:
            ret["manifest"] = yaml.load(f)
    elif os.path.exists(os.path.join(plugindir, "pyproject.toml")):
        ret["isPoetry"] = True
        with open(os.path.join(plugindir, "pyproject.toml"), "rt") as f:
            data = toml.load(f)
        deps = []
        for key, val in data['tool']['poetry'].get('dependencies', {}).items():
            if key in ['maubot', 'mautrix', 'python']:
        for key, val in data["tool"]["poetry"].get("dependencies", {}).items():
            if key in ["maubot", "mautrix", "python"]:
                continue
            reqs = []
            for req in val.split(','):
            for req in val.split(","):
                reqs.extend(poetry_to_pep(req))
            deps.append(key + ', '.join(reqs))
        ret['manifest'] = data['tool']['maubot']
        ret['manifest']['id'] = data['tool']['poetry']['name']
        ret['manifest']['version'] = data['tool']['poetry']['version']
        ret['manifest']['license'] = data['tool']['poetry']['license']
            deps.append(key + ", ".join(reqs))
        ret["manifest"] = data["tool"]["maubot"]
        ret["manifest"]["id"] = data["tool"]["poetry"]["name"]
        ret["manifest"]["version"] = data["tool"]["poetry"]["version"]
        ret["manifest"]["license"] = data["tool"]["poetry"]["license"]
        if deps:
            ret['manifest']['dependencies'] = deps
            ret["manifest"]["dependencies"] = deps
    else:
        raise ValueError(f'No maubot.yaml or pyproject.toml found in {repodir}')
        raise ValueError(f"No maubot.yaml or pyproject.toml found in {repodir}")
    # normalize non-spdx-conformant licenses this way
    # (and fill out missing license info)
    if 'license' not in ret['manifest'] or ret['manifest']['license'] in ['GPLv3', 'AGPL 3.0']:
        ret['attrs']['meta']['license'] = license
    elif ret['manifest']['license'] != license:
        print(f"Warning: licenses for {repourl} don't match! {ret['manifest']['license']} != {license}")
    if "license" not in ret["manifest"] or ret["manifest"]["license"] in [
        "GPLv3",
        "AGPL 3.0",
    ]:
        ret["attrs"]["meta"]["license"] = license
    elif ret["manifest"]["license"] != license:
        print(
            f"Warning: licenses for {repourl} don't match! {ret['manifest']['license']} != {license}"
        )
    if official:
        ret['isOfficial'] = official
        ret["isOfficial"] = official
    PLUGINS[name] = ret


def next_incomp(ver_s: str) -> str:
    ver = ver_s.split('.')
    ver = ver_s.split(".")
    zero = False
    for i in range(len(ver)):
        try:
@@ -156,45 +178,51 @@ def next_incomp(ver_s: str) -> str:
                break
            continue
        if zero:
            ver[i] = '0'
            ver[i] = "0"
        elif seg:
            ver[i] = str(seg + 1)
            zero = True
    return '.'.join(ver)

def poetry_to_pep(ver_req: str) -> List[str]:
    if '*' in ver_req:
        raise NotImplementedError('Wildcard poetry versions not implemented!')
    if ver_req.startswith('^'):
        return ['>=' + ver_req[1:], '<' + next_incomp(ver_req[1:])]
    if ver_req.startswith('~'):
        return ['~=' + ver_req[1:]]
    return ".".join(ver)


def poetry_to_pep(ver_req: str) -> list[str]:
    if "*" in ver_req:
        raise NotImplementedError("Wildcard poetry versions not implemented!")
    if ver_req.startswith("^"):
        return [">=" + ver_req[1:], "<" + next_incomp(ver_req[1:])]
    if ver_req.startswith("~"):
        return ["~=" + ver_req[1:]]
    return [ver_req]


def main():
    cache_path = os.path.join(TMP, 'maubot-plugins')
    cache_path = os.path.join(TMP, "maubot-plugins")
    if not os.path.exists(cache_path):
        os.makedirs(cache_path)
        git.Repo.clone_from('https://github.com/maubot/plugins.maubot.xyz', os.path.join(cache_path, '_repo'))
        git.Repo.clone_from(
            "https://github.com/maubot/plugins.maubot.xyz",
            os.path.join(cache_path, "_repo"),
        )
    else:
        pass

    repodir = os.path.join(cache_path, '_repo')
    repodir = os.path.join(cache_path, "_repo")

    for suffix, official in (('official', True), ('thirdparty', False)):
        directory = os.path.join(repodir, 'data', 'plugins', suffix)
    for suffix, official in (("official", True), ("thirdparty", False)):
        directory = os.path.join(repodir, "data", "plugins", suffix)
        for plugin_name in os.listdir(directory):
            process_repo(os.path.join(directory, plugin_name), official)

    if os.path.isdir('pkgs/tools/networking/maubot/plugins'):
        generated = 'pkgs/tools/networking/maubot/plugins/generated.json'
    if os.path.isdir("pkgs/tools/networking/maubot/plugins"):
        generated = "pkgs/tools/networking/maubot/plugins/generated.json"
    else:
        script_dir = os.path.dirname(os.path.realpath(__file__))
        generated = os.path.join(script_dir, 'generated.json')
        generated = os.path.join(script_dir, "generated.json")

    with open(generated, "wt") as file:
        json.dump(PLUGINS, file, indent="  ", separators=(",", ": "), sort_keys=True)
        file.write("\n")

    with open(generated, 'wt') as file:
        json.dump(PLUGINS, file, indent='  ', separators=(',', ': '), sort_keys=True)
        file.write('\n')

if __name__ == '__main__':
if __name__ == "__main__":
    main()