parse id from xpi

This commit is contained in:
ytshen 2023-11-11 13:30:15 +08:00
parent 71bd149737
commit 68da3644f8
12 changed files with 350 additions and 571 deletions

View File

@ -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

4
.gitignore vendored
View File

@ -1,4 +1,8 @@
addon_infos.json
xpis/
caches/
published/
testAddons/
# Byte-compiled / optimized / DLL files
__pycache__/

View File

@ -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;

39
fallback_infos.py Normal file
View File

@ -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

132
main.py
View File

@ -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)

136
moz_addons.py Normal file
View File

@ -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

2
requirements.txt Normal file
View File

@ -0,0 +1,2 @@
requests
argparse

View File

@ -1,4 +0,0 @@
# Zotero Addons Scraper
Just for test.
Do not modify this folder.

View File

@ -1,15 +0,0 @@
{
"id": "zoterostyle@polygon.org",
"name": "Zotero Style",
"repo": "MuiseDestiny/ZoteroStyle",
"releases": [
{
"targetZoteroVersion": "7",
"tagName": "pre"
},
{
"targetZoteroVersion": "6",
"tagName": "latest"
}
]
}

View File

@ -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=' ')

View File

@ -1,11 +0,0 @@
{
"id": "zoteroAddons@ytshen.com",
"name": "Zotero 插件合集",
"repo": "syt2/zotero-addons",
"releases": [
{
"targetZoteroVersion": "7",
"tagName": "latest"
}
]
}

View File

@ -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"
}
]
}