parse id from xpi
This commit is contained in:
parent
71bd149737
commit
68da3644f8
42
.github/workflows/main.yml
vendored
42
.github/workflows/main.yml
vendored
@ -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
4
.gitignore
vendored
@ -1,4 +1,8 @@
|
||||
addon_infos.json
|
||||
xpis/
|
||||
caches/
|
||||
published/
|
||||
testAddons/
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
|
||||
@ -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
39
fallback_infos.py
Normal 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
132
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)
|
||||
|
||||
136
moz_addons.py
Normal file
136
moz_addons.py
Normal 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
2
requirements.txt
Normal file
@ -0,0 +1,2 @@
|
||||
requests
|
||||
argparse
|
||||
@ -1,4 +0,0 @@
|
||||
# Zotero Addons Scraper
|
||||
|
||||
Just for test.
|
||||
Do not modify this folder.
|
||||
@ -1,15 +0,0 @@
|
||||
{
|
||||
"id": "zoterostyle@polygon.org",
|
||||
"name": "Zotero Style",
|
||||
"repo": "MuiseDestiny/ZoteroStyle",
|
||||
"releases": [
|
||||
{
|
||||
"targetZoteroVersion": "7",
|
||||
"tagName": "pre"
|
||||
},
|
||||
{
|
||||
"targetZoteroVersion": "6",
|
||||
"tagName": "latest"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -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=' ')
|
||||
@ -1,11 +0,0 @@
|
||||
{
|
||||
"id": "zoteroAddons@ytshen.com",
|
||||
"name": "Zotero 插件合集",
|
||||
"repo": "syt2/zotero-addons",
|
||||
"releases": [
|
||||
{
|
||||
"targetZoteroVersion": "7",
|
||||
"tagName": "latest"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user