Skip to content

Commit d40246e

Browse files
authored
Merge branch 'plugin_api_v2' into plugin_api_v2
2 parents 262379f + 8562a63 commit d40246e

File tree

155 files changed

+4328
-3091
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

155 files changed

+4328
-3091
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: Deploy Website
2+
3+
on:
4+
push:
5+
branches: [ plugin_api_v2 ]
6+
paths:
7+
- 'plugins.json'
8+
workflow_dispatch:
9+
10+
jobs:
11+
dispatch:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- name: Dispatch event
15+
run: |
16+
http_status=$(curl -L -f -s -o /dev/null -w "%{http_code}" \
17+
-X POST \
18+
-H "Accept: application/vnd.github+json" \
19+
-H "Authorization: Bearer ${{ secrets.DEPLOY_WEBSITE }}" \
20+
https://api.github.com/repos/Flow-Launcher/flow-launcher.github.io/dispatches \
21+
-d '{"event_type":"deploy"}')
22+
if [ "$http_status" -ne 204 ]; then echo "Error: Deploy trigger failed, HTTP status code is $http_status"; exit 1; fi

README.md

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ This repository contains the information for community-made plugins used in [Flo
66

77
## Plugin list
88

9-
Looking for a list of currently available plugins in Flow? Visit [here](https://flow-launcher.github.io/docs/#/plugins)
9+
Looking for a list of currently available plugins in Flow? Visit [here](https://www.flowlauncher.com/plugins)
1010

1111
## How to submit your plugin
1212

13-
1. Create a file named `${name}-${uuid}.json` in the _plugins_ directory.
14-
2. Copy these items from your plugin project's plugin.json file:
13+
1. Create a file named `${name}-${uuid}.json` in the [plugins](https://github.com/Flow-Launcher/Flow.Launcher.PluginsManifest/tree/plugin_api_v2/plugins) directory.
14+
2. Copy these items from your plugin project's `plugin.json` file:
1515
- `ID`
1616
- `Name`
1717
- `Description`
@@ -23,16 +23,16 @@ Looking for a list of currently available plugins in Flow? Visit [here](https://
2323
4. It should look like this:
2424
```json
2525
{
26-
"ID": "Unique GUID from your plugin.json",
27-
"Name": "Plugin name",
28-
"Description": "Short description",
29-
"Author": "Author",
30-
"Version": "Version from your plugin.json",
31-
"Language": "Programming language",
32-
"Website": "Plugin website",
33-
"UrlDownload": "URL to download",
34-
"UrlSourceCode": "URL to source code",
35-
"IcoPath": "Plugin icon image's CDN URL, e.g. https://cdn.jsdelivr.net/gh/Flow-Launcher/Flow.Launcher/Plugins/Flow.Launcher.Plugin.Explorer/Images/explorer.png"
26+
"ID": "Unique GUID from your plugin.json, e.g. 2f4e384e-76ce-45c3-aea2-b16f5e5c328f",
27+
"Name": "Plugin name, e.g. Hello World Python",
28+
"Description": "Short description, e.g. Python Hello World example plugin",
29+
"Author": "Author, e.g. Flow Launcher",
30+
"Version": "Version from your plugin.json, e.g. 1.0.0",
31+
"Language": "Programming language, e.g. python",
32+
"Website": "Plugin website, e.g. https://github.com/Flow-Launcher/Flow.Launcher.Plugin.HelloWorldPython",
33+
"UrlDownload": "URL to download, e.g. https://github.com/Flow-Launcher/Flow.Launcher.Plugin.HelloWorldPython/releases/download/v1.0.0/Flow.Launcher.Plugin.HelloWorldPython.zip",
34+
"UrlSourceCode": "URL to source code, e.g. https://github.com/Flow-Launcher/Flow.Launcher.Plugin.HelloWorldPython/tree/main",
35+
"IcoPath": "Plugin icon image's CDN URL, e.g. https://cdn.jsdelivr.net/gh/Flow-Launcher/Flow.Launcher.Plugin.HelloWorldPython@main/Images/app.png"
3636
}
3737
```
3838
5. For `IcoPath`, use a CDN provider for global accessibility. [jsdelivr.com](https://www.jsdelivr.com/) for example as shown above, works well with GitHub repositories.
@@ -49,6 +49,8 @@ While the plugin has not yet appeared in the store, you and your users can manua
4949

5050
Every three hours the *CI* in this repository will check for new updates from plugins and automatically update them to the latest version.
5151

52+
So you do not need to manually submit a pull request after you make a new release.
53+
5254
## Plugin Store policy
5355

5456
Plugins that facilitate or contain any of the following will not be allowed:
@@ -65,7 +67,7 @@ Plugins that facilitate or contain any of the following will not be allowed:
6567
## Plugin Store
6668

6769
Users will be able to install your plugin via the store or type `pm install <your-plugin-name>`:
68-
<p align="center"><img src="https://user-images.githubusercontent.com/6903107/207155616-d559f0d2-ee95-4072-a7bc-3ffcc2faec27.png" width="800"></p>
70+
<p align="center"><img src="assets/plugin_store.png" width="800"></p>
6971

7072
## Have a plugin enhancement request or issue?
7173

assets/plugin_store.png

99.3 KB
Loading

ci/src/_utils.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
# If adding a third-party library here, check CI workflows Python files
99
# that are dependant on this and require pip install.
1010

11+
github_download_url_regex = re.compile(
12+
r"https://github\.com/(?P<author>[a-zA-Z0-9-]+)/(?P<repo>[a-zA-Z0-9\.\-\_]*)/releases/download/(?P<version>[a-zA-Z\.0-9]+)/(?P<filename>.*)\.zip"
13+
)
14+
1115
# path
1216
utils_path = Path(__file__).resolve()
1317

ci/src/test-python.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,15 +81,36 @@ def get_all_python_plugins(manifest: dict) -> list:
8181
return [plugin for plugin in manifest if plugin["Language"].lower() == "python"]
8282

8383
def run_plugin(plugin_name: str, plugin_path: str, execute_path: str) -> bool:
84-
"""Run plugin and check output. Returns true if test successfull else false"""
84+
"""Run plugin and check output. Returns true if test successful else false"""
8585
os.chdir(plugin_path)
8686
default_settings = init_settings(plugin_name, plugin_path)
8787
args = json.dumps(
8888
{"method": "query", "parameters": [""], "Settings": default_settings}
8989
)
90-
full_args = ["python", "-S", Path(plugin_path, execute_path), args]
90+
9191
# Older Flox used environmental variable to locate Images directory
9292
os.environ["PYTHONPATH"] = str(USER_PATH.joinpath("PythonEmbeddable"))
93+
94+
# Compose the sys.path setup and runpy logic as Flow Launcher does in
95+
# https://github.com/Flow-Launcher/Flow.Launcher/blob/dfe96160ed44684810bcdf853f86fda4305122d6/Flow.Launcher.Core/Plugin/PythonPlugin.cs#L69-L81
96+
sys_path_setup = (
97+
f"import sys; "
98+
f"sys.path.append(r'{plugin_path}'); "
99+
f"sys.path.append(r'{plugin_path}\\lib'); "
100+
f"sys.path.append(r'{plugin_path}\\lib\\win32\\lib'); "
101+
f"sys.path.append(r'{plugin_path}\\lib\\win32'); "
102+
f"sys.path.append(r'{plugin_path}\\plugin'); "
103+
f"import runpy; "
104+
f"runpy.run_path(r'{Path(plugin_path, execute_path)}', None, '__main__')"
105+
)
106+
107+
full_args = [
108+
"python",
109+
"-S",
110+
"-c",
111+
sys_path_setup,
112+
args
113+
]
93114
print_section("Input", full_args)
94115
p = Popen(full_args, text=True, stdout=PIPE, stderr=PIPE)
95116
stdout, stderr = p.communicate()
@@ -104,7 +125,7 @@ def run_plugin(plugin_name: str, plugin_path: str, execute_path: str) -> bool:
104125
return True
105126
else:
106127
print_section(f"{plugin['Name']} test FAILED!", "")
107-
print(f'Plugin returned a non-zero exit code: {max(exit_code, 1)}')
128+
print(f'Plugin has returned a non-zero exit code: {max(exit_code, 1)}')
108129
if stderr != "":
109130
print_section('Trace', stderr)
110131
if json_msg:

ci/src/updater.py

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
# -*-coding: utf-8 -*-
22
import asyncio
33
import aiohttp
4+
from zipfile import ZipFile
45
import re
6+
from io import BytesIO
57
from typing import List
68
from os import getenv
79
from sys import argv
@@ -12,11 +14,6 @@
1214
from _utils import *
1315
from discord import update_hook
1416

15-
github_download_url_regex = re.compile(
16-
r"https://github\.com/(?P<author>[a-zA-Z0-9-]+)/(?P<repo>[a-zA-Z0-9\.\-\_]*)/releases/download/(?P<version>[a-zA-Z\.0-9]+)/(?P<filename>.*)\.zip"
17-
)
18-
19-
2017
async def batch_github_plugin_info(
2118
info: P, tags: ETagsType, github_token=None, webhook_url: str | None = None
2219
) -> P:
@@ -68,14 +65,29 @@ async def batch_github_plugin_info(
6865
browser_download_url or assets[0]["browser_download_url"]
6966
)
7067

71-
if webhook_url:
72-
await send_notification(
73-
info,
74-
clean(latest_rel["tag_name"], "v"),
75-
latest_rel,
76-
webhook_url,
68+
latest_ver = clean(latest_rel["tag_name"], "v")
69+
if version_tuple(info[version]) != version_tuple(latest_ver):
70+
tqdm.write(f"Update detected: {info[plugin_name]} {latest_ver}")
71+
72+
metadata = await get_plugin_dot_json(
73+
info[url_download], session=session
7774
)
78-
info[version] = clean(latest_rel["tag_name"], "v")
75+
for key in (
76+
description,
77+
author,
78+
language_name,
79+
website,
80+
):
81+
info[key] = metadata[key]
82+
83+
if webhook_url:
84+
await send_notification(
85+
info,
86+
latest_ver,
87+
latest_rel,
88+
webhook_url,
89+
)
90+
info[version] = latest_ver
7991

8092
tags[info[id_name]] = res.headers.get(etag, "")
8193

@@ -86,6 +98,21 @@ async def batch_github_plugin_info(
8698
return info
8799

88100

101+
async def get_plugin_dot_json(download_url: str, *, session: aiohttp.ClientSession):
102+
async with session.get(download_url) as res:
103+
data = await res.read()
104+
105+
with ZipFile(BytesIO(data)) as zip:
106+
for fileinfo in zip.filelist:
107+
if not fileinfo.filename.endswith("plugin.json"):
108+
continue
109+
110+
with zip.open(fileinfo, "r") as file:
111+
return json.loads(file.read())
112+
113+
raise ValueError(f"plugin.json file not found. Download Url: {download_url}")
114+
115+
89116
async def batch_plugin_infos(
90117
plugin_infos: Ps, tags: ETagsType, github_token, webhook_url: str | None = None
91118
) -> Ps:
@@ -114,17 +141,15 @@ def remove_unused_etags(plugin_infos: PluginsType, etags: ETagsType) -> ETagsTyp
114141

115142

116143
async def send_notification(
117-
info: PluginType, latest_ver, release, webhook_url: str | None = None
144+
info: PluginType, latest_ver: str, release, webhook_url: str | None = None
118145
) -> None:
119146
if not webhook_url:
120147
return
121148

122-
if version_tuple(info[version]) != version_tuple(latest_ver):
123-
tqdm.write(f"Update detected: {info[plugin_name]} {latest_ver}")
124-
try:
125-
await update_hook(webhook_url, info, latest_ver, release)
126-
except Exception as e:
127-
tqdm.write(str(e))
149+
try:
150+
await update_hook(webhook_url, info, latest_ver, release)
151+
except Exception as e:
152+
tqdm.write(str(e))
128153

129154

130155
async def main():

ci/src/validator.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import uuid
33

44
from _utils import (check_url, clean, get_new_plugin_submission_ids, get_plugin_file_paths, get_plugin_filenames,
5-
icon_path, id_name, language_list, language_name, plugin_reader)
5+
icon_path, id_name, language_list, language_name, plugin_reader, github_download_url_regex, url_download)
66

77
plugin_infos = plugin_reader()
88

@@ -52,3 +52,7 @@ def test_submitted_plugin_id_is_valid_uuid():
5252
outcome = False
5353

5454
assert outcome is True, f"The submission plugin ID {id} is not a valid v4 UUID"
55+
56+
def test_valid_download_url():
57+
for info in plugin_infos:
58+
assert github_download_url_regex.fullmatch(info[url_download]), f" The plugin {info['Name']}-{info['ID']} does not have a valid download url: {info[url_download]}"

0 commit comments

Comments
 (0)