pygpt-net 2.6.1__py3-none-any.whl → 2.6.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pygpt_net/CHANGELOG.txt +4 -0
- pygpt_net/__init__.py +3 -3
- pygpt_net/app.py +15 -1
- pygpt_net/controller/chat/response.py +5 -3
- pygpt_net/controller/chat/stream.py +40 -2
- pygpt_net/controller/plugins/plugins.py +25 -0
- pygpt_net/controller/presets/editor.py +33 -88
- pygpt_net/controller/presets/experts.py +20 -1
- pygpt_net/controller/presets/presets.py +2 -2
- pygpt_net/controller/ui/mode.py +17 -66
- pygpt_net/core/agents/runner.py +15 -7
- pygpt_net/core/experts/experts.py +3 -3
- pygpt_net/data/config/config.json +3 -3
- pygpt_net/data/config/models.json +3 -3
- pygpt_net/data/locale/locale.de.ini +2 -0
- pygpt_net/data/locale/locale.en.ini +2 -0
- pygpt_net/data/locale/locale.es.ini +2 -0
- pygpt_net/data/locale/locale.fr.ini +2 -0
- pygpt_net/data/locale/locale.it.ini +2 -0
- pygpt_net/data/locale/locale.pl.ini +3 -1
- pygpt_net/data/locale/locale.uk.ini +2 -0
- pygpt_net/data/locale/locale.zh.ini +2 -0
- pygpt_net/plugin/base/plugin.py +35 -3
- pygpt_net/plugin/bitbucket/__init__.py +12 -0
- pygpt_net/plugin/bitbucket/config.py +267 -0
- pygpt_net/plugin/bitbucket/plugin.py +125 -0
- pygpt_net/plugin/bitbucket/worker.py +569 -0
- pygpt_net/plugin/facebook/__init__.py +12 -0
- pygpt_net/plugin/facebook/config.py +359 -0
- pygpt_net/plugin/facebook/plugin.py +114 -0
- pygpt_net/plugin/facebook/worker.py +698 -0
- pygpt_net/plugin/github/__init__.py +12 -0
- pygpt_net/plugin/github/config.py +441 -0
- pygpt_net/plugin/github/plugin.py +124 -0
- pygpt_net/plugin/github/worker.py +674 -0
- pygpt_net/plugin/google/__init__.py +12 -0
- pygpt_net/plugin/google/config.py +367 -0
- pygpt_net/plugin/google/plugin.py +126 -0
- pygpt_net/plugin/google/worker.py +826 -0
- pygpt_net/plugin/slack/__init__.py +12 -0
- pygpt_net/plugin/slack/config.py +349 -0
- pygpt_net/plugin/slack/plugin.py +116 -0
- pygpt_net/plugin/slack/worker.py +639 -0
- pygpt_net/plugin/telegram/__init__.py +12 -0
- pygpt_net/plugin/telegram/config.py +308 -0
- pygpt_net/plugin/telegram/plugin.py +118 -0
- pygpt_net/plugin/telegram/worker.py +563 -0
- pygpt_net/plugin/twitter/__init__.py +12 -0
- pygpt_net/plugin/twitter/config.py +491 -0
- pygpt_net/plugin/twitter/plugin.py +126 -0
- pygpt_net/plugin/twitter/worker.py +837 -0
- pygpt_net/provider/agents/llama_index/legacy/openai_assistant.py +35 -3
- pygpt_net/ui/base/config_dialog.py +4 -0
- pygpt_net/ui/dialog/preset.py +34 -77
- pygpt_net/ui/layout/toolbox/presets.py +2 -2
- pygpt_net/ui/main.py +3 -1
- {pygpt_net-2.6.1.dist-info → pygpt_net-2.6.2.dist-info}/METADATA +145 -2
- {pygpt_net-2.6.1.dist-info → pygpt_net-2.6.2.dist-info}/RECORD +61 -33
- {pygpt_net-2.6.1.dist-info → pygpt_net-2.6.2.dist-info}/LICENSE +0 -0
- {pygpt_net-2.6.1.dist-info → pygpt_net-2.6.2.dist-info}/WHEEL +0 -0
- {pygpt_net-2.6.1.dist-info → pygpt_net-2.6.2.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# ================================================== #
|
|
4
|
+
# This file is a part of PYGPT package #
|
|
5
|
+
# Website: https://pygpt.net #
|
|
6
|
+
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
|
+
# MIT License #
|
|
8
|
+
# Created By : Marcin Szczygliński #
|
|
9
|
+
# Updated Date: 2025.08.15 00:00:00 #
|
|
10
|
+
# ================================================== #
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import mimetypes
|
|
16
|
+
import os
|
|
17
|
+
import time
|
|
18
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
19
|
+
from urllib.parse import quote
|
|
20
|
+
|
|
21
|
+
import requests
|
|
22
|
+
from PySide6.QtCore import Slot
|
|
23
|
+
|
|
24
|
+
from pygpt_net.plugin.base.worker import BaseWorker, BaseSignals
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class WorkerSignals(BaseSignals):
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Worker(BaseWorker):
|
|
32
|
+
"""
|
|
33
|
+
Bitbucket Cloud: Auth (Basic App Password or Bearer), Users, Workspaces, Repos,
|
|
34
|
+
Source/Contents (files), Issues, Pull Requests, Search.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, *args, **kwargs):
|
|
38
|
+
super(Worker, self).__init__()
|
|
39
|
+
self.signals = WorkerSignals()
|
|
40
|
+
self.args = args
|
|
41
|
+
self.kwargs = kwargs
|
|
42
|
+
self.plugin = None
|
|
43
|
+
self.cmds = None
|
|
44
|
+
self.ctx = None
|
|
45
|
+
self.msg = None
|
|
46
|
+
|
|
47
|
+
# ---------------------- Core runner ----------------------
|
|
48
|
+
|
|
49
|
+
@Slot()
|
|
50
|
+
def run(self):
|
|
51
|
+
try:
|
|
52
|
+
responses = []
|
|
53
|
+
for item in self.cmds:
|
|
54
|
+
if self.is_stopped():
|
|
55
|
+
break
|
|
56
|
+
try:
|
|
57
|
+
response = None
|
|
58
|
+
if item["cmd"] in self.plugin.allowed_cmds and self.plugin.has_cmd(item["cmd"]):
|
|
59
|
+
|
|
60
|
+
# -------- Auth / Setup --------
|
|
61
|
+
if item["cmd"] == "bb_auth_set_mode":
|
|
62
|
+
response = self.cmd_bb_auth_set_mode(item)
|
|
63
|
+
elif item["cmd"] == "bb_set_app_password":
|
|
64
|
+
response = self.cmd_bb_set_app_password(item)
|
|
65
|
+
elif item["cmd"] == "bb_set_bearer":
|
|
66
|
+
response = self.cmd_bb_set_bearer(item)
|
|
67
|
+
elif item["cmd"] == "bb_auth_check":
|
|
68
|
+
response = self.cmd_bb_auth_check(item)
|
|
69
|
+
|
|
70
|
+
# -------- Users / Workspaces --------
|
|
71
|
+
elif item["cmd"] == "bb_me":
|
|
72
|
+
response = self.cmd_bb_me(item)
|
|
73
|
+
elif item["cmd"] == "bb_user_get":
|
|
74
|
+
response = self.cmd_bb_user_get(item)
|
|
75
|
+
elif item["cmd"] == "bb_workspaces_list":
|
|
76
|
+
response = self.cmd_bb_workspaces_list(item)
|
|
77
|
+
|
|
78
|
+
# -------- Repos --------
|
|
79
|
+
elif item["cmd"] == "bb_repos_list":
|
|
80
|
+
response = self.cmd_bb_repos_list(item)
|
|
81
|
+
elif item["cmd"] == "bb_repo_get":
|
|
82
|
+
response = self.cmd_bb_repo_get(item)
|
|
83
|
+
elif item["cmd"] == "bb_repo_create":
|
|
84
|
+
response = self.cmd_bb_repo_create(item)
|
|
85
|
+
elif item["cmd"] == "bb_repo_delete":
|
|
86
|
+
response = self.cmd_bb_repo_delete(item)
|
|
87
|
+
|
|
88
|
+
# -------- Contents (files) --------
|
|
89
|
+
elif item["cmd"] == "bb_contents_get":
|
|
90
|
+
response = self.cmd_bb_contents_get(item)
|
|
91
|
+
elif item["cmd"] == "bb_file_put":
|
|
92
|
+
response = self.cmd_bb_file_put(item)
|
|
93
|
+
elif item["cmd"] == "bb_file_delete":
|
|
94
|
+
response = self.cmd_bb_file_delete(item)
|
|
95
|
+
|
|
96
|
+
# -------- Issues --------
|
|
97
|
+
elif item["cmd"] == "bb_issues_list":
|
|
98
|
+
response = self.cmd_bb_issues_list(item)
|
|
99
|
+
elif item["cmd"] == "bb_issue_create":
|
|
100
|
+
response = self.cmd_bb_issue_create(item)
|
|
101
|
+
elif item["cmd"] == "bb_issue_comment":
|
|
102
|
+
response = self.cmd_bb_issue_comment(item)
|
|
103
|
+
elif item["cmd"] == "bb_issue_update":
|
|
104
|
+
response = self.cmd_bb_issue_update(item)
|
|
105
|
+
|
|
106
|
+
# -------- Pull Requests --------
|
|
107
|
+
elif item["cmd"] == "bb_prs_list":
|
|
108
|
+
response = self.cmd_bb_prs_list(item)
|
|
109
|
+
elif item["cmd"] == "bb_pr_create":
|
|
110
|
+
response = self.cmd_bb_pr_create(item)
|
|
111
|
+
elif item["cmd"] == "bb_pr_merge":
|
|
112
|
+
response = self.cmd_bb_pr_merge(item)
|
|
113
|
+
|
|
114
|
+
# -------- Search --------
|
|
115
|
+
elif item["cmd"] == "bb_search_repos":
|
|
116
|
+
response = self.cmd_bb_search_repos(item)
|
|
117
|
+
|
|
118
|
+
if response:
|
|
119
|
+
responses.append(response)
|
|
120
|
+
|
|
121
|
+
except Exception as e:
|
|
122
|
+
responses.append(self.make_response(item, self.throw_error(e)))
|
|
123
|
+
|
|
124
|
+
if responses:
|
|
125
|
+
self.reply_more(responses)
|
|
126
|
+
if self.msg is not None:
|
|
127
|
+
self.status(self.msg)
|
|
128
|
+
except Exception as e:
|
|
129
|
+
self.error(e)
|
|
130
|
+
finally:
|
|
131
|
+
self.cleanup()
|
|
132
|
+
|
|
133
|
+
# ---------------------- HTTP / Helpers ----------------------
|
|
134
|
+
|
|
135
|
+
def _api_base(self) -> str:
|
|
136
|
+
return (self.plugin.get_option_value("api_base") or "https://api.bitbucket.org/2.0").rstrip("/")
|
|
137
|
+
|
|
138
|
+
def _timeout(self) -> int:
|
|
139
|
+
try:
|
|
140
|
+
return int(self.plugin.get_option_value("http_timeout") or 30)
|
|
141
|
+
except Exception:
|
|
142
|
+
return 30
|
|
143
|
+
|
|
144
|
+
def _now(self) -> int:
|
|
145
|
+
return int(time.time())
|
|
146
|
+
|
|
147
|
+
def _auth_mode(self) -> str:
|
|
148
|
+
# auto | basic | bearer
|
|
149
|
+
mode = (self.plugin.get_option_value("auth_mode") or "auto").strip().lower()
|
|
150
|
+
user = (self.plugin.get_option_value("bb_username") or "").strip()
|
|
151
|
+
app = (self.plugin.get_option_value("bb_app_password") or "").strip()
|
|
152
|
+
tok = (self.plugin.get_option_value("bb_access_token") or "").strip()
|
|
153
|
+
if mode == "basic":
|
|
154
|
+
return "basic"
|
|
155
|
+
if mode == "bearer":
|
|
156
|
+
return "bearer"
|
|
157
|
+
# auto preference: bearer if token present else basic if user+app present
|
|
158
|
+
if tok:
|
|
159
|
+
return "bearer"
|
|
160
|
+
if user and app:
|
|
161
|
+
return "basic"
|
|
162
|
+
return "bearer" if tok else "basic"
|
|
163
|
+
|
|
164
|
+
def _requests_auth(self):
|
|
165
|
+
# Let requests handle Basic header generation
|
|
166
|
+
if self._auth_mode() == "basic":
|
|
167
|
+
user = (self.plugin.get_option_value("bb_username") or "").strip()
|
|
168
|
+
pwd = (self.plugin.get_option_value("bb_app_password") or "").strip()
|
|
169
|
+
if not (user and pwd):
|
|
170
|
+
raise RuntimeError("Basic auth selected but username/app_password missing.")
|
|
171
|
+
return (user, pwd)
|
|
172
|
+
return None
|
|
173
|
+
|
|
174
|
+
def _headers(self) -> Dict[str, str]:
|
|
175
|
+
hdrs = {
|
|
176
|
+
"Accept": "application/json",
|
|
177
|
+
"User-Agent": "pygpt-net-bitbucket-plugin/1.0",
|
|
178
|
+
}
|
|
179
|
+
if self._auth_mode() == "bearer":
|
|
180
|
+
tok = (self.plugin.get_option_value("bb_access_token") or "").strip()
|
|
181
|
+
if not tok:
|
|
182
|
+
raise RuntimeError("Bearer auth selected but access token missing.")
|
|
183
|
+
hdrs["Authorization"] = f"Bearer {tok}"
|
|
184
|
+
return hdrs
|
|
185
|
+
|
|
186
|
+
def _handle_response(self, r: requests.Response) -> dict:
|
|
187
|
+
try:
|
|
188
|
+
data = r.json() if r.content else {}
|
|
189
|
+
except Exception:
|
|
190
|
+
data = {"raw": r.text}
|
|
191
|
+
|
|
192
|
+
if r.status_code not in (200, 201, 202, 204):
|
|
193
|
+
# Bitbucket typical error payload: {"error":{"message":"...","detail":"..."}}
|
|
194
|
+
msg = ""
|
|
195
|
+
if isinstance(data, dict):
|
|
196
|
+
err = data.get("error")
|
|
197
|
+
if isinstance(err, dict):
|
|
198
|
+
msg = err.get("message") or err.get("detail") or ""
|
|
199
|
+
elif isinstance(err, str):
|
|
200
|
+
msg = err
|
|
201
|
+
else:
|
|
202
|
+
msg = data.get("message") or data.get("raw") or ""
|
|
203
|
+
payload = {
|
|
204
|
+
"status": r.status_code,
|
|
205
|
+
"error": msg or (r.text or ""),
|
|
206
|
+
"www-authenticate": r.headers.get("WWW-Authenticate"),
|
|
207
|
+
}
|
|
208
|
+
if r.status_code == 401 and self._auth_mode() == "basic":
|
|
209
|
+
u = (self.plugin.get_option_value("bb_username") or "")
|
|
210
|
+
if "@" in u:
|
|
211
|
+
payload["hint"] = "Use Bitbucket username (handle), not email."
|
|
212
|
+
raise RuntimeError(json.dumps(payload, ensure_ascii=False))
|
|
213
|
+
|
|
214
|
+
# Attach meta
|
|
215
|
+
if isinstance(data, dict):
|
|
216
|
+
data["_meta"] = {
|
|
217
|
+
"status": r.status_code,
|
|
218
|
+
"x-rate-remaining": r.headers.get("x-rate-limit-remaining"),
|
|
219
|
+
"x-rate-reset": r.headers.get("x-rate-limit-reset"),
|
|
220
|
+
"x-rate-limit": r.headers.get("x-rate-limit-limit"),
|
|
221
|
+
}
|
|
222
|
+
return data
|
|
223
|
+
|
|
224
|
+
def _get(self, path: str, params: dict | None = None):
|
|
225
|
+
url = f"{self._api_base()}{path}"
|
|
226
|
+
r = requests.get(url, headers=self._headers(), params=params or {}, timeout=self._timeout(), auth=self._requests_auth())
|
|
227
|
+
return self._handle_response(r)
|
|
228
|
+
|
|
229
|
+
def _delete(self, path: str, params: dict | None = None):
|
|
230
|
+
url = f"{self._api_base()}{path}"
|
|
231
|
+
r = requests.delete(url, headers=self._headers(), params=params or {}, timeout=self._timeout(), auth=self._requests_auth())
|
|
232
|
+
return self._handle_response(r)
|
|
233
|
+
|
|
234
|
+
def _post_json(self, path: str, payload: dict, params: dict | None = None):
|
|
235
|
+
url = f"{self._api_base()}{path}"
|
|
236
|
+
r = requests.post(url, headers=self._headers(), params=params or {}, json=payload or {}, timeout=self._timeout(), auth=self._requests_auth())
|
|
237
|
+
return self._handle_response(r)
|
|
238
|
+
|
|
239
|
+
def _put_json(self, path: str, payload: dict, params: dict | None = None):
|
|
240
|
+
url = f"{self._api_base()}{path}"
|
|
241
|
+
r = requests.put(url, headers=self._headers(), params=params or {}, json=payload or {}, timeout=self._timeout(), auth=self._requests_auth())
|
|
242
|
+
return self._handle_response(r)
|
|
243
|
+
|
|
244
|
+
def _post_form(self, path: str, data: dict | List[tuple] | None = None, files: dict | None = None):
|
|
245
|
+
url = f"{self._api_base()}{path}"
|
|
246
|
+
r = requests.post(url, headers=self._headers(), data=data, files=files, timeout=self._timeout(), auth=self._requests_auth())
|
|
247
|
+
return self._handle_response(r)
|
|
248
|
+
|
|
249
|
+
def _guess_mime(self, path: str) -> str:
|
|
250
|
+
mt, _ = mimetypes.guess_type(path)
|
|
251
|
+
return mt or "application/octet-stream"
|
|
252
|
+
|
|
253
|
+
# ---------------------- Auth commands ----------------------
|
|
254
|
+
|
|
255
|
+
def cmd_bb_auth_set_mode(self, item: dict) -> dict:
|
|
256
|
+
p = item.get("params", {})
|
|
257
|
+
mode = (p.get("mode") or "").strip().lower()
|
|
258
|
+
if mode not in ("auto", "basic", "bearer"):
|
|
259
|
+
return self.make_response(item, "Param 'mode' must be: auto|basic|bearer")
|
|
260
|
+
self.plugin.set_option_value("auth_mode", mode)
|
|
261
|
+
return self.make_response(item, {"ok": True, "mode": mode})
|
|
262
|
+
|
|
263
|
+
def cmd_bb_set_app_password(self, item: dict) -> dict:
|
|
264
|
+
p = item.get("params", {})
|
|
265
|
+
user = (p.get("username") or "").strip()
|
|
266
|
+
app = (p.get("app_password") or "").strip()
|
|
267
|
+
if not (user and app):
|
|
268
|
+
return self.make_response(item, "Params 'username' and 'app_password' required")
|
|
269
|
+
self.plugin.set_option_value("bb_username", user)
|
|
270
|
+
self.plugin.set_option_value("bb_app_password", app)
|
|
271
|
+
if p.get("set_mode"):
|
|
272
|
+
self.plugin.set_option_value("auth_mode", "basic")
|
|
273
|
+
return self.make_response(item, {"ok": True, "mode": self._auth_mode()})
|
|
274
|
+
|
|
275
|
+
def cmd_bb_set_bearer(self, item: dict) -> dict:
|
|
276
|
+
p = item.get("params", {})
|
|
277
|
+
tok = (p.get("access_token") or "").strip()
|
|
278
|
+
if not tok:
|
|
279
|
+
return self.make_response(item, "Param 'access_token' required")
|
|
280
|
+
self.plugin.set_option_value("bb_access_token", tok)
|
|
281
|
+
if p.get("set_mode"):
|
|
282
|
+
self.plugin.set_option_value("auth_mode", "bearer")
|
|
283
|
+
return self.make_response(item, {"ok": True, "mode": self._auth_mode()})
|
|
284
|
+
|
|
285
|
+
def cmd_bb_auth_check(self, item: dict) -> dict:
|
|
286
|
+
# Simple /user ping with current mode
|
|
287
|
+
try:
|
|
288
|
+
res = self._get("/user")
|
|
289
|
+
# Cache username if present
|
|
290
|
+
data = res.get("data") or res
|
|
291
|
+
if isinstance(data, dict):
|
|
292
|
+
uu = data.get("uuid") or (data.get("user") or {}).get("uuid")
|
|
293
|
+
un = data.get("username") or data.get("nickname")
|
|
294
|
+
if uu:
|
|
295
|
+
self.plugin.set_option_value("user_uuid", uu)
|
|
296
|
+
if un:
|
|
297
|
+
self.plugin.set_option_value("username", un)
|
|
298
|
+
return self.make_response(item, {"ok": True, "mode": self._auth_mode(), "user": res})
|
|
299
|
+
except Exception as e:
|
|
300
|
+
return self.make_response(item, self.throw_error(e))
|
|
301
|
+
|
|
302
|
+
# ---------------------- Users / Workspaces ----------------------
|
|
303
|
+
|
|
304
|
+
def cmd_bb_me(self, item: dict) -> dict:
|
|
305
|
+
res = self._get("/user")
|
|
306
|
+
data = res.get("data") or res
|
|
307
|
+
if isinstance(data, dict):
|
|
308
|
+
if data.get("uuid"):
|
|
309
|
+
self.plugin.set_option_value("user_uuid", data["uuid"])
|
|
310
|
+
if data.get("username"):
|
|
311
|
+
self.plugin.set_option_value("username", data["username"])
|
|
312
|
+
return self.make_response(item, res)
|
|
313
|
+
|
|
314
|
+
def cmd_bb_user_get(self, item: dict) -> dict:
|
|
315
|
+
p = item.get("params", {})
|
|
316
|
+
username = p.get("username")
|
|
317
|
+
if not username:
|
|
318
|
+
return self.make_response(item, "Param 'username' required")
|
|
319
|
+
res = self._get(f"/users/{quote(username)}")
|
|
320
|
+
return self.make_response(item, res)
|
|
321
|
+
|
|
322
|
+
def cmd_bb_workspaces_list(self, item: dict) -> dict:
|
|
323
|
+
p = item.get("params", {}) or {}
|
|
324
|
+
params = {}
|
|
325
|
+
if p.get("page"): params["page"] = int(p["page"])
|
|
326
|
+
if p.get("pagelen"): params["pagelen"] = int(p["pagelen"])
|
|
327
|
+
res = self._get("/workspaces", params=params)
|
|
328
|
+
return self.make_response(item, res)
|
|
329
|
+
|
|
330
|
+
# ---------------------- Repositories ----------------------
|
|
331
|
+
|
|
332
|
+
def cmd_bb_repos_list(self, item: dict) -> dict:
|
|
333
|
+
p = item.get("params", {}) or {}
|
|
334
|
+
workspace = p.get("workspace")
|
|
335
|
+
params = {}
|
|
336
|
+
for k in ("role", "q", "sort", "page", "pagelen", "after"):
|
|
337
|
+
if p.get(k) is not None:
|
|
338
|
+
params[k] = p[k]
|
|
339
|
+
if workspace:
|
|
340
|
+
res = self._get(f"/repositories/{quote(workspace)}", params=params)
|
|
341
|
+
else:
|
|
342
|
+
res = self._get("/repositories", params=params)
|
|
343
|
+
return self.make_response(item, res)
|
|
344
|
+
|
|
345
|
+
def cmd_bb_repo_get(self, item: dict) -> dict:
|
|
346
|
+
p = item.get("params", {}) or {}
|
|
347
|
+
ws, repo = p.get("workspace"), p.get("repo")
|
|
348
|
+
if not (ws and repo):
|
|
349
|
+
return self.make_response(item, "Params 'workspace' and 'repo' required")
|
|
350
|
+
res = self._get(f"/repositories/{quote(ws)}/{quote(repo)}")
|
|
351
|
+
return self.make_response(item, res)
|
|
352
|
+
|
|
353
|
+
def cmd_bb_repo_create(self, item: dict) -> dict:
|
|
354
|
+
p = item.get("params", {}) or {}
|
|
355
|
+
ws, repo = p.get("workspace"), p.get("repo")
|
|
356
|
+
if not (ws and repo):
|
|
357
|
+
return self.make_response(item, "Params 'workspace' and 'repo' required")
|
|
358
|
+
payload: Dict[str, Any] = {
|
|
359
|
+
"scm": p.get("scm") or "git",
|
|
360
|
+
"is_private": bool(p.get("is_private", True)),
|
|
361
|
+
}
|
|
362
|
+
if p.get("description"): payload["description"] = p["description"]
|
|
363
|
+
if p.get("project_key"): payload["project"] = {"key": p["project_key"]}
|
|
364
|
+
res = self._post_json(f"/repositories/{quote(ws)}/{quote(repo)}", payload)
|
|
365
|
+
return self.make_response(item, res)
|
|
366
|
+
|
|
367
|
+
def cmd_bb_repo_delete(self, item: dict) -> dict:
|
|
368
|
+
p = item.get("params", {}) or {}
|
|
369
|
+
ws, repo = p.get("workspace"), p.get("repo")
|
|
370
|
+
confirm = bool(p.get("confirm", False))
|
|
371
|
+
if not (ws and repo):
|
|
372
|
+
return self.make_response(item, "Params 'workspace' and 'repo' required")
|
|
373
|
+
if not confirm:
|
|
374
|
+
return self.make_response(item, "Confirm deletion by setting 'confirm': true")
|
|
375
|
+
res = self._delete(f"/repositories/{quote(ws)}/{quote(repo)}")
|
|
376
|
+
return self.make_response(item, res)
|
|
377
|
+
|
|
378
|
+
# ---------------------- Contents (Source API) ----------------------
|
|
379
|
+
|
|
380
|
+
def cmd_bb_contents_get(self, item: dict) -> dict:
|
|
381
|
+
p = item.get("params", {}) or {}
|
|
382
|
+
ws, repo = p.get("workspace"), p.get("repo")
|
|
383
|
+
if not (ws and repo):
|
|
384
|
+
return self.make_response(item, "Params 'workspace' and 'repo' required")
|
|
385
|
+
path = (p.get("path") or "").strip().strip("/")
|
|
386
|
+
ref = (p.get("ref") or "").strip()
|
|
387
|
+
params: Dict[str, Any] = {}
|
|
388
|
+
if p.get("format"): params["format"] = p["format"] # meta|rendered
|
|
389
|
+
if p.get("q"): params["q"] = p["q"]
|
|
390
|
+
if p.get("sort"): params["sort"] = p["sort"]
|
|
391
|
+
if p.get("max_depth") is not None: params["max_depth"] = int(p["max_depth"])
|
|
392
|
+
if ref and path:
|
|
393
|
+
res = self._get(f"/repositories/{quote(ws)}/{quote(repo)}/src/{quote(ref)}/{path}", params=params)
|
|
394
|
+
elif ref and not path:
|
|
395
|
+
res = self._get(f"/repositories/{quote(ws)}/{quote(repo)}/src/{quote(ref)}", params=params)
|
|
396
|
+
elif path:
|
|
397
|
+
res = self._get(f"/repositories/{quote(ws)}/{quote(repo)}/src/{path}", params=params)
|
|
398
|
+
else:
|
|
399
|
+
res = self._get(f"/repositories/{quote(ws)}/{quote(repo)}/src", params=params)
|
|
400
|
+
return self.make_response(item, res)
|
|
401
|
+
|
|
402
|
+
def _read_local_bytes(self, local_path: str) -> bytes:
|
|
403
|
+
local = self.prepare_path(local_path)
|
|
404
|
+
if not os.path.exists(local):
|
|
405
|
+
raise RuntimeError(f"Local file not found: {local}")
|
|
406
|
+
with open(local, "rb") as fh:
|
|
407
|
+
return fh.read()
|
|
408
|
+
|
|
409
|
+
def cmd_bb_file_put(self, item: dict) -> dict:
|
|
410
|
+
p = item.get("params", {}) or {}
|
|
411
|
+
ws, repo, remote_path = p.get("workspace"), p.get("repo"), p.get("path")
|
|
412
|
+
if not (ws and repo and remote_path):
|
|
413
|
+
return self.make_response(item, "Params 'workspace','repo','path' required")
|
|
414
|
+
message = p.get("message") or f"Update {remote_path}"
|
|
415
|
+
branch = p.get("branch")
|
|
416
|
+
parents = p.get("parents")
|
|
417
|
+
content_str = p.get("content")
|
|
418
|
+
local_path = p.get("local_path")
|
|
419
|
+
|
|
420
|
+
files = None
|
|
421
|
+
data: Dict[str, Any] | List[tuple]
|
|
422
|
+
if local_path:
|
|
423
|
+
data_pairs: List[tuple] = [("message", message)]
|
|
424
|
+
if branch: data_pairs.append(("branch", branch))
|
|
425
|
+
if parents: data_pairs.append(("parents", parents))
|
|
426
|
+
b = self._read_local_bytes(local_path)
|
|
427
|
+
mime = self._guess_mime(local_path)
|
|
428
|
+
files = {remote_path: (os.path.basename(local_path), b, mime)}
|
|
429
|
+
data = data_pairs
|
|
430
|
+
elif content_str is not None:
|
|
431
|
+
data = {remote_path: content_str, "message": message}
|
|
432
|
+
if branch: data["branch"] = branch
|
|
433
|
+
if parents: data["parents"] = parents
|
|
434
|
+
else:
|
|
435
|
+
return self.make_response(item, "Provide 'content' or 'local_path'")
|
|
436
|
+
|
|
437
|
+
res = self._post_form(f"/repositories/{quote(ws)}/{quote(repo)}/src", data=data, files=files)
|
|
438
|
+
return self.make_response(item, res)
|
|
439
|
+
|
|
440
|
+
def cmd_bb_file_delete(self, item: dict) -> dict:
|
|
441
|
+
p = item.get("params", {}) or {}
|
|
442
|
+
ws, repo = p.get("workspace"), p.get("repo")
|
|
443
|
+
paths = p.get("paths") or p.get("path")
|
|
444
|
+
if isinstance(paths, str): paths = [paths]
|
|
445
|
+
if not (ws and repo and paths):
|
|
446
|
+
return self.make_response(item, "Params 'workspace','repo','paths' required")
|
|
447
|
+
message = p.get("message") or "Delete files"
|
|
448
|
+
branch = p.get("branch")
|
|
449
|
+
parents = p.get("parents")
|
|
450
|
+
data: List[tuple] = [("message", message)]
|
|
451
|
+
if branch: data.append(("branch", branch))
|
|
452
|
+
if parents: data.append(("parents", parents))
|
|
453
|
+
for path in paths:
|
|
454
|
+
data.append(("files", path))
|
|
455
|
+
res = self._post_form(f"/repositories/{quote(ws)}/{quote(repo)}/src", data=data)
|
|
456
|
+
return self.make_response(item, res)
|
|
457
|
+
|
|
458
|
+
# ---------------------- Issues ----------------------
|
|
459
|
+
|
|
460
|
+
def cmd_bb_issues_list(self, item: dict) -> dict:
|
|
461
|
+
p = item.get("params", {}) or {}
|
|
462
|
+
ws, repo = p.get("workspace"), p.get("repo")
|
|
463
|
+
if not (ws and repo):
|
|
464
|
+
return self.make_response(item, "Params 'workspace' and 'repo' required")
|
|
465
|
+
params: Dict[str, Any] = {}
|
|
466
|
+
for k in ("q", "sort", "page", "pagelen"):
|
|
467
|
+
if p.get(k) is not None: params[k] = p[k]
|
|
468
|
+
if p.get("state"):
|
|
469
|
+
q = params.get("q", "")
|
|
470
|
+
clause = f'state="{p["state"]}"'
|
|
471
|
+
params["q"] = f"{q} AND {clause}" if q else clause
|
|
472
|
+
res = self._get(f"/repositories/{quote(ws)}/{quote(repo)}/issues", params=params)
|
|
473
|
+
return self.make_response(item, res)
|
|
474
|
+
|
|
475
|
+
def cmd_bb_issue_create(self, item: dict) -> dict:
|
|
476
|
+
p = item.get("params", {}) or {}
|
|
477
|
+
ws, repo, title = p.get("workspace"), p.get("repo"), p.get("title")
|
|
478
|
+
if not (ws and repo and title):
|
|
479
|
+
return self.make_response(item, "Params 'workspace','repo','title' required")
|
|
480
|
+
payload: Dict[str, Any] = {"title": title}
|
|
481
|
+
if p.get("content"): payload["content"] = {"raw": p["content"]}
|
|
482
|
+
if p.get("assignee"): payload["assignee"] = {"username": p["assignee"]}
|
|
483
|
+
res = self._post_json(f"/repositories/{quote(ws)}/{quote(repo)}/issues", payload)
|
|
484
|
+
return self.make_response(item, res)
|
|
485
|
+
|
|
486
|
+
def cmd_bb_issue_comment(self, item: dict) -> dict:
|
|
487
|
+
p = item.get("params", {}) or {}
|
|
488
|
+
ws, repo, iid, content = p.get("workspace"), p.get("repo"), p.get("id"), p.get("content")
|
|
489
|
+
if not (ws and repo and iid and content):
|
|
490
|
+
return self.make_response(item, "Params 'workspace','repo','id','content' required")
|
|
491
|
+
payload = {"content": {"raw": content}}
|
|
492
|
+
res = self._post_json(f"/repositories/{quote(ws)}/{quote(repo)}/issues/{int(iid)}/comments", payload)
|
|
493
|
+
return self.make_response(item, res)
|
|
494
|
+
|
|
495
|
+
def cmd_bb_issue_update(self, item: dict) -> dict:
|
|
496
|
+
p = item.get("params", {}) or {}
|
|
497
|
+
ws, repo, iid = p.get("workspace"), p.get("repo"), p.get("id")
|
|
498
|
+
if not (ws and repo and iid):
|
|
499
|
+
return self.make_response(item, "Params 'workspace','repo','id' required")
|
|
500
|
+
payload: Dict[str, Any] = {}
|
|
501
|
+
if p.get("state"): payload["state"] = p["state"]
|
|
502
|
+
if p.get("title"): payload["title"] = p["title"]
|
|
503
|
+
if p.get("content"): payload["content"] = {"raw": p["content"]}
|
|
504
|
+
res = self._put_json(f"/repositories/{quote(ws)}/{quote(repo)}/issues/{int(iid)}", payload)
|
|
505
|
+
return self.make_response(item, res)
|
|
506
|
+
|
|
507
|
+
# ---------------------- Pull Requests ----------------------
|
|
508
|
+
|
|
509
|
+
def cmd_bb_prs_list(self, item: dict) -> dict:
|
|
510
|
+
p = item.get("params", {}) or {}
|
|
511
|
+
ws, repo = p.get("workspace"), p.get("repo")
|
|
512
|
+
if not (ws and repo):
|
|
513
|
+
return self.make_response(item, "Params 'workspace' and 'repo' required")
|
|
514
|
+
params: Dict[str, Any] = {}
|
|
515
|
+
for k in ("state", "page", "pagelen", "q", "sort"):
|
|
516
|
+
if p.get(k) is not None: params[k] = p[k]
|
|
517
|
+
res = self._get(f"/repositories/{quote(ws)}/{quote(repo)}/pullrequests", params=params)
|
|
518
|
+
return self.make_response(item, res)
|
|
519
|
+
|
|
520
|
+
def cmd_bb_pr_create(self, item: dict) -> dict:
|
|
521
|
+
p = item.get("params", {}) or {}
|
|
522
|
+
ws, repo = p.get("workspace"), p.get("repo")
|
|
523
|
+
title, src, dst = p.get("title"), p.get("source_branch"), p.get("destination_branch")
|
|
524
|
+
if not (ws and repo and title and src and dst):
|
|
525
|
+
return self.make_response(item, "Params 'workspace','repo','title','source_branch','destination_branch' required")
|
|
526
|
+
payload: Dict[str, Any] = {
|
|
527
|
+
"title": title,
|
|
528
|
+
"source": {"branch": {"name": src}},
|
|
529
|
+
"destination": {"branch": {"name": dst}},
|
|
530
|
+
}
|
|
531
|
+
if p.get("description"): payload["description"] = p["description"]
|
|
532
|
+
if p.get("draft") is not None: payload["draft"] = bool(p["draft"])
|
|
533
|
+
res = self._post_json(f"/repositories/{quote(ws)}/{quote(repo)}/pullrequests", payload)
|
|
534
|
+
return self.make_response(item, res)
|
|
535
|
+
|
|
536
|
+
def cmd_bb_pr_merge(self, item: dict) -> dict:
|
|
537
|
+
p = item.get("params", {}) or {}
|
|
538
|
+
ws, repo, pr_id = p.get("workspace"), p.get("repo"), p.get("id")
|
|
539
|
+
if not (ws and repo and pr_id):
|
|
540
|
+
return self.make_response(item, "Params 'workspace','repo','id' required")
|
|
541
|
+
payload: Dict[str, Any] = {}
|
|
542
|
+
if p.get("message"): payload["message"] = p["message"]
|
|
543
|
+
if p.get("close_source_branch") is not None: payload["close_source_branch"] = bool(p["close_source_branch"])
|
|
544
|
+
res = self._post_json(f"/repositories/{quote(ws)}/{quote(repo)}/pullrequests/{int(pr_id)}/merge", payload)
|
|
545
|
+
return self.make_response(item, res)
|
|
546
|
+
|
|
547
|
+
# ---------------------- Search ----------------------
|
|
548
|
+
|
|
549
|
+
def cmd_bb_search_repos(self, item: dict) -> dict:
|
|
550
|
+
p = item.get("params", {}) or {}
|
|
551
|
+
params: Dict[str, Any] = {}
|
|
552
|
+
if p.get("q"): params["q"] = p["q"] # BBQL
|
|
553
|
+
if p.get("sort"): params["sort"] = p["sort"]
|
|
554
|
+
if p.get("page"): params["page"] = int(p["page"])
|
|
555
|
+
if p.get("pagelen"): params["pagelen"] = int(p["pagelen"])
|
|
556
|
+
res = self._get("/repositories", params=params)
|
|
557
|
+
return self.make_response(item, res)
|
|
558
|
+
|
|
559
|
+
# ---------------------- FS helpers ----------------------
|
|
560
|
+
|
|
561
|
+
def prepare_path(self, path: str) -> str:
|
|
562
|
+
if path in [".", "./"]:
|
|
563
|
+
return self.plugin.window.core.config.get_user_dir("data")
|
|
564
|
+
if self.is_absolute_path(path):
|
|
565
|
+
return path
|
|
566
|
+
return os.path.join(self.plugin.window.core.config.get_user_dir("data"), path)
|
|
567
|
+
|
|
568
|
+
def is_absolute_path(self, path: str) -> bool:
|
|
569
|
+
return os.path.isabs(path)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# ================================================== #
|
|
4
|
+
# This file is a part of PYGPT package #
|
|
5
|
+
# Website: https://pygpt.net #
|
|
6
|
+
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
|
+
# MIT License #
|
|
8
|
+
# Created By : Marcin Szczygliński #
|
|
9
|
+
# Updated Date: 2025.06.30 02:00:00 #
|
|
10
|
+
# ================================================== #
|
|
11
|
+
|
|
12
|
+
from .plugin import *
|