diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4c2f865..fb17b78 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -5,13 +5,19 @@ on: schedule: - cron: '0 3 * * *' # 每天早上3点执行 +permissions: + contents: write + +env: + TZ: Asia/Shanghai + jobs: scrape_data: runs-on: ubuntu-latest steps: - name: Checkout Repository - uses: actions/checkout@master + uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 @@ -20,12 +26,38 @@ jobs: - name: Install Dependencies run: | - pip install requests - pip install argparse + pip install -r requirements.txt + + - name: Cache Folder + uses: actions/cache@v3 + with: + path: | + caches + key: ${{ runner.os }}-files-${{ hashFiles('caches/**') }} + restore-keys: | + ${{ runner.os }}-files- - name: Run Script run: | - python main.py -i addons -o addon_infos.json --github_repository ${GITHUB_REPOSITORY} --github_token ${GITHUB_TOKEN} - + python main.py \ + -i addons \ + -o published/addon_infos.json \ + --github_repository ${GITHUB_REPOSITORY} \ + --github_token ${GITHUB_TOKEN} \ + --cache_directory caches \ + --runtime_xpi_directory xpis \ + --previous_info_urls \ + https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/publish/addon_infos.json \ + https://github.com/${GITHUB_REPOSITORY}/releases/latest/download/addon_infos.json env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Publish in Repository + uses: peaceiris/actions-gh-pages@v3 + with: + deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }} + publish_branch: publish + user_name: 'github-actions[bot]' + user_email: 'github-actions[bot]@users.noreply.github.com' + publish_dir: ./published + diff --git a/.gitignore b/.gitignore index a9619a4..1ae61c8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ addon_infos.json +xpis/ +caches/ +published/ +testAddons/ # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/addon_keys.py b/addon_keys.py index 8b82ea0..d70c531 100644 --- a/addon_keys.py +++ b/addon_keys.py @@ -19,6 +19,7 @@ star = "star" author = "author" url = "url" avatar = "avatar" +xpiInfo = "xpiInfo" # from https://github.com/zotero-chinese/zotero-plugins/blob/main/src/plugins.ts @@ -64,6 +65,13 @@ avatar = "avatar" # releaseData?: string; # downloadCount?: number; # assetId?: number; +# +# TODO:视 zotero-chinese/zotero-plugins情况更新 +# xpiInfo?: { +# id?: string; +# name?: string; +# currentVersion?: string; +# }; # }>; # # description?: string; diff --git a/fallback_infos.py b/fallback_infos.py new file mode 100644 index 0000000..11f2d35 --- /dev/null +++ b/fallback_infos.py @@ -0,0 +1,39 @@ +from addon_keys import * + +fallback_associated_unique_id = 'p_unique_associated_id##80502300-9DE6-4510-8768-EC42B0EF14E6' +fallback_associated_exclude_ids = 'p_exclude_associated_ids##073F5543-C70A-46AA-8529-E05168852D8F' + +fallback_associated_map = { + fallback_associated_unique_id: repo, + fallback_associated_exclude_ids: [], + repo: { + releases: { + fallback_associated_unique_id: tagName, + } + } +} + + +def fallback_if_need(current, previous, associate_map=None): + if associate_map is None: + associate_map = fallback_associated_map + if previous and current and isinstance(previous, type(current)): + if isinstance(current, dict): + for key, value in previous.items(): + if key in associate_map.get(fallback_associated_exclude_ids, []): + continue + if key in current and (isinstance(value, (list, dict))): + if current[key]: + fallback_if_need(current[key], value, associate_map.get(key, {})) + else: + current[key] = value + elif key not in current: + current[key] = value + + elif isinstance(current, list) and (uid := associate_map.get(fallback_associated_unique_id)): + for value in previous: + if not isinstance(value, dict): + continue + if cur := next((x for x in current if isinstance(x, dict) and x[uid] == value[uid]), None): + fallback_if_need(cur, value, associate_map.get(uid, {})) + return current diff --git a/main.py b/main.py index b7543f7..98e5d4c 100644 --- a/main.py +++ b/main.py @@ -5,12 +5,41 @@ import time import urllib.parse import requests import argparse +import shutil from addon_keys import * +from fallback_infos import fallback_if_need +from moz_addons import addon_details + + +def download_xpi(xpi_url: str, download_dir: str, unique_name: str, force_download: bool, **kwargs): + try: + download_filepath = os.path.join(download_dir, unique_name) + folder = os.path.dirname(download_filepath) + if folder and not os.path.exists(folder): + os.makedirs(folder) + + if cache_dir := kwargs.get('cache_dir'): + cache_filepath = os.path.join(cache_dir, unique_name) + if not force_download and os.path.isfile(cache_filepath): + shutil.copy(cache_filepath, download_filepath) # copy to download filepath + return download_filepath + + if not force_download and os.path.isfile(download_filepath): + return download_filepath + + response = requests.get(xpi_url, stream=True) + print(f'download {unique_name} from {xpi_url}') # TODO: comment + with open(download_filepath, "wb") as file: + for chunk in response.iter_content(chunk_size=1024): + file.write(chunk) + return download_filepath + except Exception as e: + print(f'download {unique_name} from {xpi_url} failed: {e}') # 输出格式参考 [zotero-chinese/zotero-plugins](https://github.com/zotero-chinese/zotero-plugins) def parse(plugin, **kwargs): - if repo not in plugin or releases not in plugin or len(plugin[releases]) <= 0: + if repo not in plugin or len(plugin[repo].split('/')) != 2 or releases not in plugin or len(plugin[releases]) <= 0: return if name not in plugin: plugin[name] = plugin[repo].split('/')[-1] @@ -82,25 +111,71 @@ def parse(plugin, **kwargs): release_asset = release_assets[0] if 'browser_download_url' not in release_asset: continue + xpi_url = release_asset['browser_download_url'] if 'id' in release_asset: release[assetId] = release_asset['id'] if 'download_count' in release_asset: release[downloadCount] = release_asset['download_count'] release[releaseData] = release_asset['updated_at'] release[xpiDownloadUrl] = { - 'github': release_asset['browser_download_url'], - 'ghProxy': 'https://ghproxy.com/?q=' + urllib.parse.quote(release_asset['browser_download_url']), - 'kgithub': release_asset['browser_download_url'].replace('github.com', 'kkgithub.com'), + 'github': xpi_url, + 'ghProxy': 'https://ghproxy.com/?q=' + urllib.parse.quote(xpi_url), + 'kgithub': xpi_url.replace('github.com', 'kkgithub.com'), } + + xpi_filename = f'{plugin[repo].replace("/", "#")}+' \ + f'{release.get(currentVersion, "0")}@' \ + f'{release.get(assetId, "0")}.xpi' + details = {} + try: + xpi_filepath = download_xpi(xpi_url=xpi_url, + download_dir=kwargs.get('runtime_xpi_directory'), + unique_name=xpi_filename, + force_download=False, + cache_dir=kwargs.get('cache_directory')) + details = addon_details(xpi_filepath) + except: + try: + xpi_filepath = download_xpi(xpi_url=xpi_url, + download_dir=kwargs.get('runtime_xpi_directory'), + unique_name=xpi_filename, + force_download=True) + details = addon_details(xpi_filepath) + except Exception as e: + print(f'fetch addon detail of {plugin[repo]} failed: {e}') + if details: + xpi_info = {} + if "id" in details: + xpi_info[id] = details["id"] + if "name" in details: + xpi_info[name] = details["name"] + if "version" in details: + xpi_info[currentVersion] = details["version"] + if xpi_info: + release[xpiInfo] = xpi_info release_infos.append(release) + except Exception as e: - print(f'request {release_url} failed: {e}') + print(f'handle {plugin[repo]} request {release_url} failed: {e}') plugin[releases] = release_infos return plugin +def fallback(addon_infos, previous_info_url, **kwargs): + headers = {} + if github_token := kwargs.get('github_token'): + headers['Authorization'] = f'token {github_token}' + try: + repos_resp = requests.get(previous_info_url, headers=headers) + repos_info = json.loads(repos_resp.content) + addon_infos = fallback_if_need(addon_infos, repos_info) + except Exception as e: + print(f'request previous addon_info.json to merge failed: {e}') + return addon_infos + + def parse_addon_infos(input_dir, output_filepath, **kwargs): plugins = [] for addon_json_filename in os.listdir(input_dir): @@ -112,15 +187,27 @@ def parse_addon_infos(input_dir, output_filepath, **kwargs): addon_infos = [] with concurrent.futures.ThreadPoolExecutor() as executor: - futures = [executor.submit(parse, plugin, github_token=kwargs.get('github_token')) for plugin in plugins] + futures = [executor.submit(parse, + plugin, + github_token=kwargs.get('github_token'), + cache_directory=kwargs.get('cache_directory'), + runtime_xpi_directory=kwargs.get('runtime_xpi_directory')) + for plugin in plugins] + for future in concurrent.futures.as_completed(futures): if addon_info := future.result(): addon_infos.append(addon_info) + for previous_info_url in kwargs.get('previous_info_urls', []): + addon_infos = fallback(addon_infos, previous_info_url, github_token=kwargs.get('github_token')) + addon_infos.sort(key=lambda item: item[star] if star in item else 0, reverse=True) + dir = os.path.dirname(output_filepath) + if dir and not os.path.exists(dir): + os.makedirs(dir) with open(output_filepath, "w") as json_file: - json.dump(addon_infos, json_file, ensure_ascii=False) + json.dump(addon_infos, json_file, ensure_ascii=False, indent=' ') return addon_infos @@ -175,6 +262,16 @@ def create_release(github_repository, **kwargs): print(f'create release failed: {e}') +def update_cache(cache_directory, runtime_xpi_directory): + if not cache_directory or not runtime_xpi_directory or cache_directory == runtime_xpi_directory: + return + try: + shutil.rmtree(cache_directory) + shutil.move(runtime_xpi_directory, cache_directory) + except Exception as e: + print(f'update cache failed: {e}') + + if __name__ == '__main__': parser = argparse.ArgumentParser(description='params') parser.add_argument('--github_repository', nargs='?', type=str, required=True, help='github repository') @@ -182,12 +279,29 @@ if __name__ == '__main__': parser.add_argument('-i', '--input', nargs='?', type=str, default="addons", help='input addon dir') parser.add_argument('-o', '--output', nargs='?', type=str, default="addon_infos.json", help='output addon filepath') + parser.add_argument('--cache_directory', nargs='?', default="caches", type=str, help='folder for caches') + parser.add_argument('--runtime_xpi_directory', nargs='?', default="xpis", type=str, help='folder for download xpi') + parser.add_argument('--previous_info_urls', nargs='+', default=[], help='previous published info json to fallback') + args = parser.parse_args() if not args.github_repository: raise 'Need specific github repository' - parse_addon_infos(args.input, args.output, github_token=args.github_token) + try: + if not os.path.isdir(args.cache_directory): + os.makedirs(args.cache_directory, exist_ok=True) + if not os.path.isdir(args.runtime_xpi_directory): + os.makedirs(args.runtime_xpi_directory, exist_ok=True) + except Exception as e: + print(f'create cache_directory failed: {e}') + + parse_addon_infos(args.input, + args.output, + github_token=args.github_token, + cache_directory=args.cache_directory, + runtime_xpi_directory=args.runtime_xpi_directory, + previous_info_urls=args.previous_info_urls) if release_id := create_release(args.github_repository, github_token=args.github_token): upload_json_to_release(args.github_repository, @@ -195,3 +309,5 @@ if __name__ == '__main__': upload_file_name='addon_infos.json', upload_file=args.output, github_token=args.github_token) + + update_cache(args.cache_directory, args.runtime_xpi_directory) diff --git a/moz_addons.py b/moz_addons.py new file mode 100644 index 0000000..6e566a2 --- /dev/null +++ b/moz_addons.py @@ -0,0 +1,136 @@ +import zipfile +import json +import os +from xml.dom import minidom + + +# modify from `https://github.com/mozilla/gecko-dev/blob/ac19e2c0d7c09a2deaedbe6afc4cdcf1a4561456/testing/mozbase/mozprofile/mozprofile/addons.py#L213` +def addon_details(addon_path): + """ + Returns a dictionary of details about the addon. + + :param addon_path: path to the add-on directory or XPI + + Returns:: + + {'id': u'rainbow@colors.org', # id of the addon + 'version': u'1.4', # version of the addon + 'name': u'Rainbow', # name of the addon + 'unpack': False } # whether to unpack the addon + """ + + details = {"id": None, "unpack": False, "name": None, "version": None} + + def get_namespace_id(doc, url): + attributes = doc.documentElement.attributes + namespace = "" + for i in range(attributes.length): + if attributes.item(i).value == url: + if ":" in attributes.item(i).name: + # If the namespace is not the default one remove 'xlmns:' + namespace = attributes.item(i).name.split(":")[1] + ":" + break + return namespace + + def get_text(element): + """Retrieve the text value of a given node""" + rc = [] + for node in element.childNodes: + if node.nodeType == node.TEXT_NODE: + rc.append(node.data) + return "".join(rc).strip() + + if not os.path.exists(addon_path): + raise IOError("Add-on path does not exist: %s" % addon_path) + + is_webext = False + try: + if zipfile.is_zipfile(addon_path): + # Bug 944361 - We cannot use 'with' together with zipFile because + # it will cause an exception thrown in Python 2.6. + try: + compressed_file = zipfile.ZipFile(addon_path, "r") + filenames = [f.filename for f in (compressed_file).filelist] + if "manifest.json" in filenames: + is_webext = True + manifest = compressed_file.read("manifest.json").decode() + manifest = json.loads(manifest) + elif "install.rdf" in filenames: + manifest = compressed_file.read("install.rdf") + else: + raise KeyError("No manifest") + finally: + compressed_file.close() + elif os.path.isdir(addon_path): + try: + with open(os.path.join(addon_path, "manifest.json")) as f: + manifest = json.loads(f.read()) + is_webext = True + except IOError: + with open(os.path.join(addon_path, "install.rdf")) as f: + manifest = f.read() + else: + raise IOError( + "Add-on path is neither an XPI nor a directory: %s" % addon_path + ) + except (IOError, KeyError) as e: + # reraise(AddonFormatError, AddonFormatError(str(e)), sys.exc_info()[2]) + raise e + + if is_webext: + details["version"] = manifest["version"] + details["name"] = manifest["name"] + # Bug 1572404 - we support two locations for gecko-specific + # metadata. + for location in ("applications", "browser_specific_settings"): + for app in ("gecko", "zotero"): + try: + details["id"] = manifest[location][app]["id"] + break + except KeyError: + pass + # if details["id"] is None: + # details["id"] = cls._gen_iid(addon_path) + details["unpack"] = False + else: + try: + doc = minidom.parseString(manifest) + + # Get the namespaces abbreviations + em = get_namespace_id(doc, "http://www.mozilla.org/2004/em-rdf#") + rdf = get_namespace_id( + doc, "http://www.w3.org/1999/02/22-rdf-syntax-ns#" + ) + + description = doc.getElementsByTagName(rdf + "Description").item(0) + try: + descriptions = [e for e in doc.getElementsByTagName(rdf + "Description") + if len(e.getElementsByTagName(em + "targetApplication")) > 0] + if descriptions: + description = descriptions[0] + except Exception as e: + pass + + for entry, value in description.attributes.items(): + # Remove the namespace prefix from the tag for comparison + entry = entry.replace(em, "") + if entry in details.keys(): + details.update({entry: value}) + for node in description.childNodes: + # Remove the namespace prefix from the tag for comparison + entry = node.nodeName.replace(em, "") + if entry in details.keys(): + details.update({entry: get_text(node)}) + except Exception as e: + raise e + + # turn unpack into a true/false value + if isinstance(details["unpack"], str): + details["unpack"] = details["unpack"].lower() == "true" + + # If no ID is set, the add-on is invalid + # if details.get("id") is None and not is_webext: + # raise AddonFormatError("Add-on id could not be found.") + + return details + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..cd6b55d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +requests +argparse \ No newline at end of file diff --git a/testAddons/README.md b/testAddons/README.md deleted file mode 100644 index 7df319b..0000000 --- a/testAddons/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Zotero Addons Scraper - -Just for test. -Do not modify this folder. \ No newline at end of file diff --git a/testAddons/ZoteroStyle.json b/testAddons/ZoteroStyle.json deleted file mode 100644 index 8597064..0000000 --- a/testAddons/ZoteroStyle.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "id": "zoterostyle@polygon.org", - "name": "Zotero Style", - "repo": "MuiseDestiny/ZoteroStyle", - "releases": [ - { - "targetZoteroVersion": "7", - "tagName": "pre" - }, - { - "targetZoteroVersion": "6", - "tagName": "latest" - } - ] -} \ No newline at end of file diff --git a/testAddons/convert_script.py b/testAddons/convert_script.py deleted file mode 100644 index f1e3f17..0000000 --- a/testAddons/convert_script.py +++ /dev/null @@ -1,513 +0,0 @@ -# 从 zotero-chinese 仓库解析json并更新此仓库的json -import json -import os.path - -from addon_keys import * - -# 从 https://raw.githubusercontent.com/zotero-chinese/zotero-plugins/main/src/plugins.ts 替换为最新的plugins信息 -plugins = [ - { - name: "Zotero 插件合集", - repo: "syt2/zotero-addons", - releases: [ - { - targetZoteroVersion: "7", - tagName: "latest", - }, - { - targetZoteroVersion: "6", - tagName: "0.6.0-3", - }, - ], - }, - { - name: "Actions and Tags for Zotero", - repo: "windingwind/zotero-actions-tags", - releases: [ - { - targetZoteroVersion: "7", - tagName: "pre", - }, - { - targetZoteroVersion: "6", - tagName: "latest", - }, - ], - }, - { - name: "Awesome GPT", - repo: "MuiseDestiny/zotero-gpt", - releases: [ - { - targetZoteroVersion: "7", - tagName: "pre", - }, - { - targetZoteroVersion: "6", - tagName: "latest", - }, - ], - }, - { - name: "Better BibTex for Zotero", - repo: "retorquere/zotero-better-bibtex", - releases: [ - { - targetZoteroVersion: "7", - tagName: "latest", - }, - { - targetZoteroVersion: "6", - tagName: "latest", - }, - ], - }, - { - name: "Better Notes for Zotero", - repo: "windingwind/zotero-better-notes", - releases: [ - { - targetZoteroVersion: "7", - tagName: "pre", - }, - { - targetZoteroVersion: "6", - tagName: "latest", - }, - ], - }, - { - name: "Chartero", - repo: "volatile-static/Chartero", - releases: [ - { - targetZoteroVersion: "6", - tagName: "1.3.3", - }, - { - targetZoteroVersion: "7", - tagName: "latest", - }, - ], - }, - { - name: "Crush Reference", - repo: "MuiseDestiny/zotero-reference", - releases: [ - { - targetZoteroVersion: "7", - tagName: "pre", - }, - { - targetZoteroVersion: "6", - tagName: "latest", - }, - ], - }, - { - name: "DelItemWithAtt", - repo: "redleafnew/delitemwithatt", - releases: [ - { - targetZoteroVersion: "7", - tagName: "latest", - }, - { - targetZoteroVersion: "6", - tagName: "0.1.06", - }, - ], - }, - { - name: "Eaiser Citation", - repo: "MuiseDestiny/eaiser-citation", - releases: [ - { - targetZoteroVersion: "7", - tagName: "latest", - }, - { - targetZoteroVersion: "6", - tagName: "0.3.1", - }, - ], - }, - { - name: "Ethereal Style", - repo: "MuiseDestiny/ZoteroStyle", - releases: [ - { - targetZoteroVersion: "7", - tagName: "pre", - }, - { - targetZoteroVersion: "6", - tagName: "latest", - }, - ], - }, - { - name: "Green Frog", - repo: "redleafnew/zotero-updateifsE", - releases: [ - { - targetZoteroVersion: "7", - tagName: "latest", - }, - { - targetZoteroVersion: "6", - tagName: "0.13.0", - }, - ], - }, - { - name: "PMCID fetcher for Zotero", - repo: "retorquere/zotero-pmcid-fetcher", - releases: [ - { - targetZoteroVersion: "7", - tagName: "latest", - }, - ], - }, - { - name: "Jasminum", - repo: "l0o0/jasminum", - releases: [ - { - targetZoteroVersion: "7", - tagName: "pre", - }, - { - targetZoteroVersion: "6", - tagName: "latest", - }, - ], - }, - { - name: "KeepZotero", - repo: "yhmtsai/KeepZotero", - releases: [ - { - targetZoteroVersion: "6", - tagName: "latest", - }, - ], - }, - { - name: "Linter for Zotero", - repo: "northword/zotero-format-metadata", - releases: [ - { - targetZoteroVersion: "7", - tagName: "latest", - }, - { - targetZoteroVersion: "6", - tagName: "0.4.4", - }, - ], - }, - { - name: "LyZ", - repo: "wshanks/lyz", - releases: [ - { - targetZoteroVersion: "6", - tagName: "latest", - }, - ], - }, - { - name: "MarkDBConnect", - repo: "daeh/zotero-obsidian-citations", - releases: [ - { - targetZoteroVersion: "6", - tagName: "latest", - }, - ], - }, - { - name: "Mdnotes for Zotero", - repo: "argenos/zotero-mdnotes", - releases: [ - { - targetZoteroVersion: "6", - tagName: "latest", - }, - ], - }, - { - name: "Night for Zotero", - repo: "tefkah/zotero-night", - releases: [ - { - targetZoteroVersion: "7", - tagName: "pre", - }, - { - targetZoteroVersion: "6", - tagName: "latest", - }, - ], - }, - { - name: "Notero", - repo: "dvanoni/notero", - releases: [ - { - targetZoteroVersion: "7", - tagName: "latest", - }, - { - targetZoteroVersion: "6", - tagName: "latest", - }, - ], - }, - { - name: "Open PDF", - repo: "retorquere/zotero-open-pdf", - releases: [ - { - targetZoteroVersion: "7", - tagName: "latest", - }, - { - targetZoteroVersion: "6", - tagName: "latest", - }, - ], - }, - { - name: "PDF Figure", - repo: "MuiseDestiny/zotero-figure", - releases: [ - { - targetZoteroVersion: "7", - tagName: "latest", - }, - { - targetZoteroVersion: "6", - tagName: "0.0.7", - }, - ], - }, - { - name: "Preview for Zotero", - repo: "windingwind/zotero-pdf-preview", - releases: [ - { - targetZoteroVersion: "6", - tagName: "latest", - }, - { - targetZoteroVersion: "7", - tagName: "pre", - }, - ], - }, - { - name: "Reading List for Zotero", - repo: "Dominic-DallOsto/zotero-reading-list", - releases: [ - { - targetZoteroVersion: "6", - tagName: "latest", - }, - ], - }, - { - name: "Sci-Hub Plugin for Zotero", - repo: "ethanwillis/zotero-scihub", - releases: [ - { - targetZoteroVersion: "6", - tagName: "latest", - }, - ], - }, - { - name: "scite Plugin for Zotero", - repo: "scitedotai/scite-zotero-plugin", - releases: [ - { - targetZoteroVersion: "6", - tagName: "latest", - }, - ], - }, - { - name: "Storage Scanner for Zotero", - repo: "retorquere/zotero-storage-scanner", - releases: [ - { - targetZoteroVersion: "6", - tagName: "latest", - }, - ], - }, - { - name: "Tara", - repo: "l0o0/tara", - releases: [ - { - targetZoteroVersion: "6", - tagName: "latest", - }, - ], - }, - { - name: "Translate for Zotero", - repo: "windingwind/zotero-pdf-translate", - releases: [ - { - targetZoteroVersion: "7", - tagName: "pre", - }, - { - targetZoteroVersion: "6", - tagName: "latest", - }, - ], - }, - { - name: "ZotCard", - repo: "018/zotcard", - releases: [ - { - targetZoteroVersion: "6", - tagName: "latest", - }, - ], - }, - { - name: "Zotero Better Authors", - repo: "github-young/zotero-better-authors", - releases: [ - { - targetZoteroVersion: "7", - tagName: "latest", - }, - ], - }, - { - name: "Zotero Citation Counts Manager", - repo: "eschnett/zotero-citationcounts", - releases: [ - { - targetZoteroVersion: "6", - tagName: "latest", - }, - ], - }, - { - name: "Zotero Storage Scanner", - repo: "retorquere/zotero-storage-scanner", - releases: [ - { - targetZoteroVersion: "6", - tagName: "latest", - }, - ], - }, - { - name: "Zotero Inspire", - repo: "fkguo/zotero-inspire", - releases: [ - { - targetZoteroVersion: "7", - tagName: "pre", - }, - { - targetZoteroVersion: "6", - tagName: "latest", - }, - ], - }, - { - name: "Zotero OCR", - repo: "UB-Mannheim/zotero-ocr", - releases: [ - { - targetZoteroVersion: "6", - tagName: "latest", - }, - ], - }, - { - name: "Zotero ShortDOI", - repo: "bwiernik/zotero-shortdoi", - releases: [ - { - targetZoteroVersion: "6", - tagName: "latest", - }, - ], - }, - { - name: "Zotero TL;DR", - repo: "syt2/Zotero-TLDR", - releases: [ - { - targetZoteroVersion: "7", - tagName: "latest", - }, - ], - }, - { - name: "Zotero 更新影响因子", - repo: "redleafnew/zotero-updateifs", - releases: [ - { - targetZoteroVersion: "6", - tagName: "latest", - }, - ], - }, - { - name: "ZotFile", - repo: "jlegewie/zotfile", - releases: [ - { - targetZoteroVersion: "6", - tagName: "latest", - }, - ], - }, - { - name: "ZotFile 汉化版", - repo: "lychichem/zotfile", - releases: [ - { - targetZoteroVersion: "6", - tagName: "latest", - }, - ], - }, - { - name: "Zutilo Utility for Zotero", - repo: "wshanks/Zutilo", - releases: [ - { - targetZoteroVersion: "6", - tagName: "latest", - }, - ], - }, -] - -for plugin in plugins: - json_name = plugin[repo].replace('/', '#') - file_path = os.path.join('../addons', json_name + '.json') - - content = {} - if os.path.isfile(file_path): - with open(file_path, 'r') as file: - content = json.load(file) - - for key in plugin: - content[key] = plugin[key] - with open(file_path, "w") as json_file: - json.dump(content, json_file, ensure_ascii=False, indent=' ') diff --git a/testAddons/zotero-addons.json b/testAddons/zotero-addons.json deleted file mode 100644 index 121a135..0000000 --- a/testAddons/zotero-addons.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "id": "zoteroAddons@ytshen.com", - "name": "Zotero 插件合集", - "repo": "syt2/zotero-addons", - "releases": [ - { - "targetZoteroVersion": "7", - "tagName": "latest" - } - ] -} \ No newline at end of file diff --git a/testAddons/zotero-format-metadata.json b/testAddons/zotero-format-metadata.json deleted file mode 100644 index 0d5360d..0000000 --- a/testAddons/zotero-format-metadata.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "id": "zotero-format-metadata@northword.cn", - "name": "Zotero Format Metadata", - "repo": "northword/zotero-format-metadata", - "releases": [ - { - "targetZoteroVersion": "7", - "tagName": "latest" - }, - { - "targetZoteroVersion": "6", - "tagName": "0.4.4" - } - ] -} \ No newline at end of file