affx 1.0.0__tar.gz

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.
affx-1.0.0/LICENSE ADDED
@@ -0,0 +1,27 @@
1
+ AffX.ai Proprietary License
2
+
3
+ Copyright (c) 2026 AffX.ai. All rights reserved.
4
+
5
+ This software, including all source code, packaged code, documentation,
6
+ interfaces, and related materials (the "Software"), is the exclusive
7
+ proprietary property of AffX.ai.
8
+
9
+ No part of the Software may be copied, modified, adapted, translated,
10
+ distributed, sublicensed, sold, leased, rented, assigned, published,
11
+ displayed, disclosed, reverse engineered, decompiled, disassembled, or
12
+ otherwise used to create derivative works, in whole or in part, without
13
+ the prior express written permission of AffX.ai.
14
+
15
+ No license or other right, whether express, implied, by estoppel, or
16
+ otherwise, is granted except for the limited right to use the Software
17
+ solely as explicitly authorized in writing by AffX.ai.
18
+
19
+ Any unauthorized use of the Software is strictly prohibited and may result
20
+ in termination of access or other legal action.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE, TITLE, AND NONINFRINGEMENT. IN NO EVENT
25
+ SHALL AFFX.AI BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER
26
+ IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN
27
+ CONNECTION WITH THE SOFTWARE OR THE USE OF OR OTHER DEALINGS IN THE SOFTWARE.
affx-1.0.0/MANIFEST.in ADDED
@@ -0,0 +1,2 @@
1
+ include README.md
2
+ include LICENSE
affx-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,105 @@
1
+ Metadata-Version: 2.4
2
+ Name: affx
3
+ Version: 1.0.0
4
+ Summary: Python client for the affx API service.
5
+ Author: affx
6
+ License: Proprietary
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: License :: Other/Proprietary License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.9
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: requests
20
+ Requires-Dist: supabase
21
+ Dynamic: author
22
+ Dynamic: classifier
23
+ Dynamic: description
24
+ Dynamic: description-content-type
25
+ Dynamic: license
26
+ Dynamic: license-file
27
+ Dynamic: requires-dist
28
+ Dynamic: requires-python
29
+ Dynamic: summary
30
+
31
+ # affx
32
+
33
+ `affx` is a Python client for the affx API service.
34
+
35
+ It lets users install a package with `pip` and call affx features from Python, while the main service logic runs through the affx API.
36
+
37
+ ## Installation
38
+
39
+ ```bash
40
+ pip install affx
41
+ ```
42
+
43
+ ## Official Links
44
+
45
+ - Official Website: https://affx.ai
46
+ - API Service: https://api.affx.ai
47
+
48
+ ## Quick Start
49
+
50
+ ```python
51
+ import affx
52
+
53
+ affx.key = "your_api_key"
54
+ affx.help()
55
+ ```
56
+
57
+ ## Example Calls
58
+
59
+ ```python
60
+ import affx
61
+
62
+ affx.key = "your_api_key"
63
+
64
+ affx.ls("site")
65
+ affx.gen("schema", domain="example.com", path="about")
66
+ affx.dl("page", domain="example.com", path="about")
67
+ ```
68
+
69
+ ## Important Notes
70
+
71
+ - Set `affx.key` before calling features that require authentication.
72
+ - Some features may take longer to complete because they depend on remote services.
73
+ - Users may need to upgrade the package to receive new client-side functions:
74
+
75
+ ```bash
76
+ pip install --upgrade affx
77
+ ```
78
+
79
+ ## Main Functions
80
+
81
+ - `affx.gen(...)`
82
+ - `affx.upd(...)`
83
+ - `affx.rm(...)`
84
+ - `affx.ls(...)`
85
+ - `affx.dl(...)`
86
+ - `affx.set(...)`
87
+ - `affx.help()`
88
+ - `affx.feedback(...)`
89
+
90
+ ## Requirements
91
+
92
+ - Python 3.9+
93
+ - Internet access for remote API features
94
+
95
+ ## Support
96
+
97
+ If a feature returns an error, first confirm:
98
+
99
+ - your API key is set correctly
100
+ - your network is available
101
+ - the remote affx service is reachable
102
+
103
+ ## License
104
+
105
+ This project is distributed under a proprietary closed-source license owned by AffX.ai.
affx-1.0.0/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # affx
2
+
3
+ `affx` is a Python client for the affx API service.
4
+
5
+ It lets users install a package with `pip` and call affx features from Python, while the main service logic runs through the affx API.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pip install affx
11
+ ```
12
+
13
+ ## Official Links
14
+
15
+ - Official Website: https://affx.ai
16
+ - API Service: https://api.affx.ai
17
+
18
+ ## Quick Start
19
+
20
+ ```python
21
+ import affx
22
+
23
+ affx.key = "your_api_key"
24
+ affx.help()
25
+ ```
26
+
27
+ ## Example Calls
28
+
29
+ ```python
30
+ import affx
31
+
32
+ affx.key = "your_api_key"
33
+
34
+ affx.ls("site")
35
+ affx.gen("schema", domain="example.com", path="about")
36
+ affx.dl("page", domain="example.com", path="about")
37
+ ```
38
+
39
+ ## Important Notes
40
+
41
+ - Set `affx.key` before calling features that require authentication.
42
+ - Some features may take longer to complete because they depend on remote services.
43
+ - Users may need to upgrade the package to receive new client-side functions:
44
+
45
+ ```bash
46
+ pip install --upgrade affx
47
+ ```
48
+
49
+ ## Main Functions
50
+
51
+ - `affx.gen(...)`
52
+ - `affx.upd(...)`
53
+ - `affx.rm(...)`
54
+ - `affx.ls(...)`
55
+ - `affx.dl(...)`
56
+ - `affx.set(...)`
57
+ - `affx.help()`
58
+ - `affx.feedback(...)`
59
+
60
+ ## Requirements
61
+
62
+ - Python 3.9+
63
+ - Internet access for remote API features
64
+
65
+ ## Support
66
+
67
+ If a feature returns an error, first confirm:
68
+
69
+ - your API key is set correctly
70
+ - your network is available
71
+ - the remote affx service is reachable
72
+
73
+ ## License
74
+
75
+ This project is distributed under a proprietary closed-source license owned by AffX.ai.
@@ -0,0 +1,703 @@
1
+ import requests
2
+ import json
3
+ import os
4
+ import io
5
+ import base64
6
+ import zipfile
7
+ import shutil
8
+ import tempfile
9
+ import concurrent.futures
10
+ import sys
11
+ import importlib
12
+ import zlib
13
+ from contextlib import contextmanager
14
+
15
+ # 填你未来 Vercel 分配给你的网址,部署后你需要回来这里改掉这个网址
16
+ SERVER_URL = "https://api-affx-ai-base.vercel.app/api/run"
17
+ CONFIG_GITHUB_REPO = "getanyapp/autoaff.me"
18
+ BLOCKED_OUTPUT_KEYWORDS = ("xiaoai.plus", "chr1.com", "mmw.ink")
19
+ BLOCKED_OUTPUT_MESSAGE = "\nServer Error, Please Try Again."
20
+
21
+ # 在这里定义一个全局变量 key,用户直接赋值就可以了
22
+ key = ""
23
+
24
+ def _contains_blocked_output(value):
25
+ try:
26
+ text = str(value).lower()
27
+ except Exception:
28
+ return False
29
+ return any(keyword in text for keyword in BLOCKED_OUTPUT_KEYWORDS)
30
+
31
+ def _sanitize_output(value):
32
+ if isinstance(value, str):
33
+ return BLOCKED_OUTPUT_MESSAGE if _contains_blocked_output(value) else value
34
+
35
+ try:
36
+ rendered = json.dumps(value, ensure_ascii=False)
37
+ except Exception:
38
+ rendered = str(value)
39
+
40
+ if _contains_blocked_output(rendered):
41
+ return BLOCKED_OUTPUT_MESSAGE
42
+ return value
43
+
44
+ def _sanitize_stream_text(text):
45
+ if not _contains_blocked_output(text):
46
+ return text
47
+
48
+ prefix = "\r" if str(text).startswith("\r") else ""
49
+ if str(text).endswith("\n"):
50
+ suffix = "\n"
51
+ elif "\r" in str(text) or "\n" in str(text):
52
+ suffix = "\n"
53
+ else:
54
+ suffix = ""
55
+ return f"{prefix}{BLOCKED_OUTPUT_MESSAGE}{suffix}"
56
+
57
+ def _safe_print(value):
58
+ print(_sanitize_output(value))
59
+
60
+ class _SanitizedOutputProxy:
61
+ def __init__(self, wrapped):
62
+ self.wrapped = wrapped
63
+
64
+ def write(self, text):
65
+ return self.wrapped.write(_sanitize_stream_text(text))
66
+
67
+ def flush(self):
68
+ return self.wrapped.flush()
69
+
70
+ def isatty(self):
71
+ return self.wrapped.isatty() if hasattr(self.wrapped, "isatty") else False
72
+
73
+ @property
74
+ def encoding(self):
75
+ return getattr(self.wrapped, "encoding", "utf-8")
76
+
77
+ @contextmanager
78
+ def _sanitize_console_output():
79
+ original_stdout = sys.stdout
80
+ original_stderr = sys.stderr
81
+ sys.stdout = _SanitizedOutputProxy(original_stdout)
82
+ sys.stderr = _SanitizedOutputProxy(original_stderr)
83
+ try:
84
+ yield
85
+ finally:
86
+ sys.stdout = original_stdout
87
+ sys.stderr = original_stderr
88
+
89
+ def _clear_local_runtime_modules():
90
+ for module_name in list(sys.modules.keys()):
91
+ if module_name.startswith("api_dependency"):
92
+ del sys.modules[module_name]
93
+
94
+ @contextmanager
95
+ def _local_api_environment():
96
+ current_dir = os.path.dirname(os.path.abspath(__file__))
97
+ parent_dir = os.path.dirname(current_dir)
98
+ source_api_dir = os.path.join(parent_dir, "api_dependency")
99
+ runtime_root = parent_dir
100
+ cleanup_dir = None
101
+
102
+ if not os.path.isdir(source_api_dir):
103
+ try:
104
+ from ._runtime_payload import PAYLOAD
105
+ except Exception:
106
+ PAYLOAD = {}
107
+
108
+ if not PAYLOAD:
109
+ raise RuntimeError("Local runtime payload not found. Please reinstall affx.")
110
+
111
+ cleanup_dir = tempfile.mkdtemp(prefix="affx_runtime_")
112
+ runtime_root = cleanup_dir
113
+ for relative_path, encoded_content in PAYLOAD.items():
114
+ target_path = os.path.join(runtime_root, *relative_path.split("/"))
115
+ os.makedirs(os.path.dirname(target_path), exist_ok=True)
116
+ raw_bytes = zlib.decompress(base64.b85decode(encoded_content.encode("ascii")))
117
+ with open(target_path, "wb") as file_obj:
118
+ file_obj.write(raw_bytes)
119
+
120
+ inserted = False
121
+ if runtime_root not in sys.path:
122
+ sys.path.insert(0, runtime_root)
123
+ inserted = True
124
+
125
+ importlib.invalidate_caches()
126
+ _clear_local_runtime_modules()
127
+
128
+ try:
129
+ yield
130
+ finally:
131
+ _clear_local_runtime_modules()
132
+ if inserted and runtime_root in sys.path:
133
+ sys.path.remove(runtime_root)
134
+ if cleanup_dir:
135
+ shutil.rmtree(cleanup_dir, ignore_errors=True)
136
+
137
+ def _call_server(action: str, params: dict, quiet_result: bool = False):
138
+ """这是一个内部小助手,专门负责给服务器发消息,用户不需要管它"""
139
+
140
+ if not key:
141
+ msg = "Error: Please set affx.key = \"Enter Your Key\" first."
142
+ _safe_print(msg)
143
+ return msg
144
+
145
+ params["key"] = key
146
+ try:
147
+ # 网页重写等AI任务通常比较耗时,甚至超过Vercel默认时间
148
+ # 设置 timeout 为 120 秒或者更长
149
+ response = requests.post(SERVER_URL, json={
150
+ "action": action,
151
+ "params": params
152
+ }, timeout=150)
153
+ response.raise_for_status()
154
+
155
+ data = response.json()
156
+ # 优化一下报错提示,如果服务器直接返回了错误(比如 400 没找到功能),就把真正的错误原因打印出来
157
+ if "result" in data:
158
+ result = data["result"]
159
+ # 检查是不是需要接管本地打包的特殊字典
160
+ if isinstance(result, dict) and result.get("type") == "trigger_local_zip":
161
+ return _handle_local_zip(result.get("zip_info", {}), result.get("message", "Success!"))
162
+
163
+ result = _sanitize_output(result)
164
+ if not quiet_result:
165
+ _safe_print(result)
166
+ return result
167
+ elif "detail" in data:
168
+ msg = _sanitize_output(f"Server error: {data['detail']}")
169
+ _safe_print(msg)
170
+ return msg
171
+ else:
172
+ data = _sanitize_output(data)
173
+ if not quiet_result:
174
+ _safe_print(data)
175
+ return data
176
+ except Exception as e:
177
+ msg = _sanitize_output(f"Server unavailable or network error: {e}")
178
+ _safe_print(msg)
179
+ return msg
180
+
181
+ def _handle_local_zip(zip_info, success_msg="Success!"):
182
+ """
183
+ 处理 Vercel 传回来的预签名链接,在本地进行多线程下载、打包并上传
184
+ """
185
+ download_urls = zip_info.get("download_urls", [])
186
+ upload_url = zip_info.get("upload_url")
187
+ zip_name = zip_info.get("zip_name", "site.zip")
188
+
189
+ if not download_urls or not upload_url:
190
+ _safe_print("error")
191
+ return "error"
192
+
193
+ zip_buffer = io.BytesIO()
194
+
195
+ def download_file(item):
196
+ try:
197
+ resp = requests.get(item["url"])
198
+ if resp.ok:
199
+ return item["path"], resp.content
200
+ except:
201
+ pass
202
+ return item["path"], None
203
+
204
+ # 多线程并发下载
205
+ with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
206
+ with concurrent.futures.ThreadPoolExecutor(max_workers=20) as executor:
207
+ future_to_item = {executor.submit(download_file, item): item for item in download_urls}
208
+ for future in concurrent.futures.as_completed(future_to_item):
209
+ path, content = future.result()
210
+ if content is not None:
211
+ zip_file.writestr(path, content)
212
+
213
+ zip_buffer.seek(0)
214
+ zip_content = zip_buffer.read()
215
+
216
+ try:
217
+ # 使用 PUT 请求把打包好的 bytes 数据传回 R2
218
+ put_resp = requests.put(upload_url, data=zip_content)
219
+ if put_resp.ok:
220
+ success_msg = _sanitize_output(success_msg)
221
+ _safe_print(success_msg)
222
+ return success_msg
223
+ else:
224
+ _safe_print("error")
225
+ return "error"
226
+ except Exception as e:
227
+ _safe_print("error")
228
+ return "error"
229
+
230
+ # ==========================================
231
+ # 下面是提供给用户的漂亮接口 (遥控器按钮)
232
+ # 每增加一个新功能,都在这里加一个小函数
233
+ # ==========================================
234
+
235
+ def ls(target: str, **kwargs):
236
+ """查询功能,比如查询网站 (target="site")"""
237
+ # 把目标(比如 "site")也放进参数里发给服务器
238
+ params = {"target": target}
239
+ params.update(kwargs)
240
+
241
+ if target == "plan":
242
+ try:
243
+ # 先去 Vercel 服务器上获取环境变量,确保能拿到 github_token
244
+ try:
245
+ env_resp = requests.post(SERVER_URL, json={
246
+ "action": "get_env",
247
+ "params": {"key": key}
248
+ }, timeout=30)
249
+ env_resp.raise_for_status()
250
+ env_data = env_resp.json()
251
+ if "result" in env_data and isinstance(env_data["result"], dict):
252
+ gh_token = env_data["result"].get("github_token")
253
+ if gh_token:
254
+ os.environ["github_token"] = gh_token
255
+ except Exception:
256
+ pass
257
+
258
+ with _local_api_environment():
259
+ module = importlib.import_module("api_dependency.ls.plan")
260
+ result = module.ls_plan(key)
261
+ result = _sanitize_output(result)
262
+ _safe_print(result)
263
+ return result
264
+ except Exception as e:
265
+ return _sanitize_output(f"Local execution error: {e}")
266
+
267
+ return _call_server("ls", params)
268
+
269
+ import os
270
+
271
+ def dl(target, **kwargs):
272
+ """下载功能,比如下载网站 (target="site")"""
273
+ # 提取用户可能传入的保存路径
274
+ # 如果用户传了 dir 参数,我们就用它;如果没传,默认下载到当前目录(".")
275
+ save_path = kwargs.pop("dir", ".")
276
+
277
+ params = {"target": target}
278
+ params.update(kwargs)
279
+
280
+ result = _call_server("dl", params, quiet_result=True)
281
+
282
+ # 判断一下服务器返回的是什么类型的数据
283
+ # 1. 如果返回的是一个下载链接(http开头),我们就在本地帮用户下载文件
284
+ if isinstance(result, str) and result.startswith("http"):
285
+ try:
286
+ # 动态决定文件名
287
+ domain = kwargs.get("domain", "download")
288
+ if target == "site":
289
+ filename = f"{domain}.zip"
290
+ elif target == "page":
291
+ # 把 path 里的 / 替换成 -,防止在本地创建多余的文件夹,或者如果是首页就叫 index.html
292
+ path_val = kwargs.get("path", "").strip('/')
293
+ if not path_val:
294
+ path_val = "index"
295
+ else:
296
+ path_val = path_val.replace('/', '-')
297
+ filename = f"{path_val}.html"
298
+ else:
299
+ filename = "download.file"
300
+
301
+ # 如果 save_path 不存在,帮用户建一个
302
+ if not os.path.exists(save_path):
303
+ os.makedirs(save_path)
304
+
305
+ full_path = os.path.join(save_path, filename)
306
+
307
+ # 下载文件并保存
308
+ file_resp = requests.get(result, stream=True)
309
+ if file_resp.status_code == 200:
310
+ with open(full_path, "wb") as f:
311
+ for chunk in file_resp.iter_content(chunk_size=8192):
312
+ f.write(chunk)
313
+ msg = f"Downloaded successfully: {full_path}"
314
+ _safe_print(msg)
315
+ return msg
316
+ else:
317
+ msg = f"Download failed. File may not exist or the link may have expired. Status code: {file_resp.status_code}"
318
+ _safe_print(msg)
319
+ return msg
320
+ except Exception as e:
321
+ msg = _sanitize_output(f"Error during download: {e}")
322
+ _safe_print(msg)
323
+ return msg
324
+ # 2. 如果返回的是一个带有特殊标记的字典(比如我们设计的 schema 内容)
325
+ elif isinstance(result, dict) and result.get("_type") == "schema_content":
326
+ try:
327
+ path_val = result.get("path", "index")
328
+ filename = f"{path_val}_schema.txt"
329
+
330
+ # 如果 save_path 不存在,帮用户建一个
331
+ if not os.path.exists(save_path):
332
+ os.makedirs(save_path)
333
+
334
+ full_path = os.path.join(save_path, filename)
335
+
336
+ # 直接把拿到的纯文本内容写进 txt 文件里
337
+ with open(full_path, "w", encoding="utf-8") as f:
338
+ f.write(result.get("content", ""))
339
+
340
+ msg = f"Schema saved successfully: {full_path}"
341
+ _safe_print(msg)
342
+ return msg
343
+ except Exception as e:
344
+ msg = _sanitize_output(f"Error saving schema: {e}")
345
+ _safe_print(msg)
346
+ return msg
347
+ # 3. 如果返回的不是链接也不是特定字典(可能是报错,比如 "Error: xxx"),直接把信息抛给用户
348
+ else:
349
+ result = _sanitize_output(result)
350
+ _safe_print(result)
351
+ return result
352
+
353
+ def set(target, *args, **kwargs):
354
+ """设置功能,比如设置 github token (target="github") 或 侵权词 (target="word")"""
355
+ # 兼容之前 set("github", token) 的写法,提取第一个额外参数作为核心值
356
+ params = {"target": target}
357
+
358
+ if args:
359
+ if target in ["github", "vercel"]:
360
+ params["token"] = args[0]
361
+ elif target == "word":
362
+ params["word"] = args[0]
363
+ elif target == "trust":
364
+ params["pages"] = args[0]
365
+ elif target == "cta":
366
+ params["path"] = args[0]
367
+ elif target == "href":
368
+ params["href_settings"] = args[0]
369
+
370
+ params.update(kwargs)
371
+ return _call_server("set", params)
372
+
373
+ def rm(target, **kwargs):
374
+ """删除功能,比如删除网站 (target="site") 或 删除网页 (target="page")"""
375
+ if target == "site":
376
+ domain = kwargs.get("domain")
377
+ if not domain:
378
+ msg = "Error: domain is required for rm site."
379
+ _safe_print(msg)
380
+ return msg
381
+
382
+ # 在本地给出警告提示,要求用户确认
383
+ confirm = input(f"Confirm deletion of website \"{domain}\" and related configuration files? (y/n): ")
384
+ if confirm.strip().lower() != 'y':
385
+ msg = "Operation canceled."
386
+ _safe_print(msg)
387
+ return msg
388
+
389
+ elif target == "page":
390
+ domain = kwargs.get("domain")
391
+ path = kwargs.get("path")
392
+ if not domain or not path:
393
+ msg = "Error: domain and path are required for rm page."
394
+ _safe_print(msg)
395
+ return msg
396
+ clean_path = str(path).strip('/') or "index"
397
+ confirm = input(f"Confirm deletion of page \"{clean_path}\"? (y/n): ")
398
+ if confirm.strip().lower() != 'y':
399
+ msg = "Operation canceled."
400
+ _safe_print(msg)
401
+ return msg
402
+
403
+ params = {"target": target}
404
+ params.update(kwargs)
405
+ return _call_server("rm", params)
406
+
407
+ def gen(target: str, **kwargs):
408
+ """
409
+ 生成相关的操作,比如生成网站 (target="site")
410
+ """
411
+ params = {"target": target}
412
+ params.update(kwargs)
413
+
414
+ if target in ["site", "page", "schema", "llms"]:
415
+ if not key:
416
+ msg = "Error: Please set affx.key = \"your_key\" first."
417
+ _safe_print(msg)
418
+ return msg
419
+
420
+ domain = kwargs.get("domain")
421
+
422
+ if target == "site":
423
+ name = kwargs.get("name")
424
+ desc = kwargs.get("desc", "")
425
+ count = kwargs.get("count")
426
+ lang = kwargs.get("lang")
427
+ href = kwargs.get("href")
428
+
429
+ if not all([domain, name, count is not None, lang, href]):
430
+ msg = "Error: gen site requires domain, name, count, lang, href."
431
+ _safe_print(msg)
432
+ return msg
433
+ elif target == "page":
434
+ name = kwargs.get("name")
435
+ desc = kwargs.get("desc")
436
+ lang = kwargs.get("lang")
437
+ href = kwargs.get("href")
438
+ if not all([domain, name, desc, lang, href]):
439
+ msg = "Error: gen page requires domain, name, desc, lang, href."
440
+ _safe_print(msg)
441
+ return msg
442
+ elif target == "schema":
443
+ path = kwargs.get("path")
444
+ if not domain or not path:
445
+ msg = "Error: gen schema requires domain and path."
446
+ _safe_print(msg)
447
+ return msg
448
+ elif target == "llms":
449
+ if not domain:
450
+ msg = "Error: gen llms requires domain."
451
+ _safe_print(msg)
452
+ return msg
453
+
454
+ _safe_print("In progress. Please DO NOT close...")
455
+
456
+ # 1. 先去 Vercel 服务器上获取环境变量
457
+ try:
458
+ import os
459
+ env_resp = requests.post(SERVER_URL, json={
460
+ "action": "get_env",
461
+ "params": {"key": key}
462
+ }, timeout=30)
463
+ env_resp.raise_for_status()
464
+ env_data = env_resp.json()
465
+ if "result" in env_data and isinstance(env_data["result"], dict):
466
+ gh_token = env_data["result"].get("github_token")
467
+ if gh_token:
468
+ os.environ["github_token"] = gh_token
469
+ else:
470
+ msg = "Error: Server error."
471
+ _safe_print(msg)
472
+ return msg
473
+ else:
474
+ msg = f"Error: Server error."
475
+ _safe_print(msg)
476
+ return msg
477
+ except Exception as e:
478
+ msg = _sanitize_output(f"Error: Server error.")
479
+ _safe_print(msg)
480
+ return msg
481
+
482
+ try:
483
+ with _local_api_environment():
484
+ importlib.invalidate_caches()
485
+ if target == "site":
486
+ tweak = kwargs.get("tweak", "")
487
+ trust = kwargs.get("trust", True)
488
+ module = importlib.import_module("api_dependency.gen.site")
489
+ with _sanitize_console_output():
490
+ result = module.gen_site(key, domain, name, desc, count, lang, href, tweak, trust)
491
+ elif target == "page":
492
+ link = kwargs.get("link", "")
493
+ save_dir = kwargs.get("dir", ".")
494
+ module = importlib.import_module("api_dependency.gen.page")
495
+ with _sanitize_console_output():
496
+ result = module.gen_page(key, domain, name, desc, lang, href, link, save_dir)
497
+ elif target == "schema":
498
+ path = kwargs.get("path")
499
+ module = importlib.import_module("api_dependency.gen.schema")
500
+ with _sanitize_console_output():
501
+ result = module.gen_schema(key, domain, path)
502
+ elif target == "llms":
503
+ module = importlib.import_module("api_dependency.gen.llms")
504
+ with _sanitize_console_output():
505
+ result = module.gen_llms(key, domain)
506
+
507
+ if isinstance(result, dict) and result.get("type") == "trigger_local_zip":
508
+ return _handle_local_zip(result.get("zip_info", {}), result.get("message", "Success!"))
509
+
510
+ result = _sanitize_output(result)
511
+ _safe_print(result)
512
+ return result
513
+ except Exception as e:
514
+ import traceback
515
+ error_details = traceback.format_exc()
516
+ msg = _sanitize_output(f"Local execution error: {e}\nDetails:\n{error_details}")
517
+ _safe_print(msg)
518
+ return _sanitize_output(f"Local execution error: {e}")
519
+
520
+ return _call_server("gen", params)
521
+
522
+ def upd(target: str, **kwargs):
523
+ """
524
+ 更新/修改相关的操作
525
+ """
526
+ params = {"target": target}
527
+ params.update(kwargs)
528
+
529
+ if target in ["reimg", "repage", "page", "autopage"]:
530
+ # 这些操作非常耗时,如果在 Vercel 端执行必定超时,所以我们将逻辑放在本地执行
531
+ # 只要保证把代码引入过来,直接在本地的 Python 环境里运行就行了
532
+ # 首先检查 key
533
+ if not key:
534
+ msg = "Error: Please set affx.key = 'your_key' first."
535
+ _safe_print(msg)
536
+ return msg
537
+
538
+ domain = kwargs.get("domain")
539
+ path = kwargs.get("path")
540
+
541
+ if target in ["reimg", "repage", "page"] and (not domain or not path):
542
+ msg = "Error: Missing domain or path"
543
+ _safe_print(msg)
544
+ return msg
545
+
546
+ if target == "autopage":
547
+ count = kwargs.get("count")
548
+ if not domain or not count:
549
+ msg = "Error: autopage requires domain and count."
550
+ _safe_print(msg)
551
+ return msg
552
+
553
+ _safe_print("In progress. Please DO NOT close...")
554
+
555
+ # 1. 先去 Vercel 服务器上获取 github_token
556
+ try:
557
+ import os
558
+ env_resp = requests.post(SERVER_URL, json={
559
+ "action": "get_env",
560
+ "params": {"key": key}
561
+ }, timeout=30)
562
+ env_resp.raise_for_status()
563
+ env_data = env_resp.json()
564
+ if "result" in env_data and isinstance(env_data["result"], dict):
565
+ gh_token = env_data["result"].get("github_token")
566
+ if gh_token:
567
+ os.environ["github_token"] = gh_token
568
+ else:
569
+ msg = "Error: Server error."
570
+ _safe_print(msg)
571
+ return msg
572
+ else:
573
+ msg = f"Error: Server error"
574
+ _safe_print(msg)
575
+ return msg
576
+ except Exception as e:
577
+ msg = _sanitize_output(f"Error: Server error.")
578
+ _safe_print(msg)
579
+ return msg
580
+
581
+ try:
582
+ with _local_api_environment():
583
+ importlib.invalidate_caches()
584
+ if target == "reimg":
585
+ module = importlib.import_module("api_dependency.upd.reimg")
586
+ with _sanitize_console_output():
587
+ result = module.upd_reimg(key, domain, path)
588
+ elif target == "repage":
589
+ module = importlib.import_module("api_dependency.upd.repage")
590
+ with _sanitize_console_output():
591
+ result = module.upd_repage(key, domain, path)
592
+ elif target == "page":
593
+ lang = kwargs.get("lang", "")
594
+ desc = kwargs.get("desc", "")
595
+ module = importlib.import_module("api_dependency.upd.page")
596
+ with _sanitize_console_output():
597
+ result = module.upd_page(key, domain, lang, path, desc)
598
+ elif target == "autopage":
599
+ lang = kwargs.get("lang", "")
600
+ desc = kwargs.get("desc", "")
601
+ count = kwargs.get("count")
602
+ module = importlib.import_module("api_dependency.upd.autopage")
603
+ with _sanitize_console_output():
604
+ result = module.upd_autopage(key, domain, lang, count, desc)
605
+
606
+ if isinstance(result, dict) and result.get("type") == "trigger_local_zip":
607
+ return _handle_local_zip(result.get("zip_info", {}), result.get("message", "Success!"))
608
+
609
+ result = _sanitize_output(result)
610
+ _safe_print(result)
611
+ return result
612
+ except Exception as e:
613
+ import traceback
614
+ error_details = traceback.format_exc()
615
+ msg = _sanitize_output(f"Local execution error: {e}\nDetails:\n{error_details}")
616
+ _safe_print(msg)
617
+ return _sanitize_output(f"Local execution error: {e}")
618
+
619
+ return _call_server("upd", params)
620
+
621
+ def help():
622
+ """
623
+ 显示所有 API 功能及调用方式
624
+ """
625
+ try:
626
+ env_resp = requests.post(SERVER_URL, json={
627
+ "action": "get_env",
628
+ "params": {"key": key}
629
+ }, timeout=30)
630
+ env_resp.raise_for_status()
631
+ env_data = env_resp.json()
632
+
633
+ github_token = ""
634
+ if "result" in env_data and isinstance(env_data["result"], dict):
635
+ github_token = env_data["result"].get("github_token", "") or ""
636
+
637
+ if not github_token:
638
+ return _sanitize_output("Error: github_token not found in server environment.")
639
+
640
+ github_url = f"https://api.github.com/repos/{CONFIG_GITHUB_REPO}/contents/api_help.txt?ref=environment"
641
+ github_resp = requests.get(
642
+ github_url,
643
+ headers={
644
+ "Authorization": f"token {github_token}",
645
+ "Accept": "application/vnd.github.v3.raw",
646
+ },
647
+ timeout=30,
648
+ )
649
+ github_resp.raise_for_status()
650
+
651
+ help_text = github_resp.text.strip()
652
+ if not help_text:
653
+ return _sanitize_output("Error: api_help.txt is empty.")
654
+
655
+ print(help_text)
656
+ return "\n"
657
+ except Exception as e:
658
+ return _sanitize_output(f"Local execution error: {e}")
659
+
660
+ def feedback(content: str):
661
+ """
662
+ 提交反馈信息给开发者
663
+ """
664
+ if not key:
665
+ msg = "Error: Please set affx.key = 'your_key' first."
666
+ _safe_print(msg)
667
+ return msg
668
+
669
+ try:
670
+ import sys
671
+ import os
672
+ current_dir = os.path.dirname(os.path.abspath(__file__))
673
+ parent_dir = os.path.dirname(current_dir)
674
+ if parent_dir not in sys.path:
675
+ sys.path.insert(0, parent_dir)
676
+
677
+ # 先去 Vercel 服务器上获取环境变量,确保能拿到 github_token
678
+ try:
679
+ env_resp = requests.post(SERVER_URL, json={
680
+ "action": "get_env",
681
+ "params": {"key": key}
682
+ }, timeout=30)
683
+ env_resp.raise_for_status()
684
+ env_data = env_resp.json()
685
+ if "result" in env_data and isinstance(env_data["result"], dict):
686
+ gh_token = env_data["result"].get("github_token")
687
+ if gh_token:
688
+ os.environ["github_token"] = gh_token
689
+ except Exception:
690
+ pass
691
+
692
+ import importlib
693
+ for k in list(sys.modules.keys()):
694
+ if k.startswith("api_dependency"):
695
+ del sys.modules[k]
696
+
697
+ module = importlib.import_module("api_dependency.feedback.feedback")
698
+ result = module.submit_feedback(key, content)
699
+ result = _sanitize_output(result)
700
+ _safe_print(result)
701
+ return result
702
+ except Exception as e:
703
+ return _sanitize_output(f"Local execution error: {e}")
@@ -0,0 +1,105 @@
1
+ Metadata-Version: 2.4
2
+ Name: affx
3
+ Version: 1.0.0
4
+ Summary: Python client for the affx API service.
5
+ Author: affx
6
+ License: Proprietary
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: License :: Other/Proprietary License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.9
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: requests
20
+ Requires-Dist: supabase
21
+ Dynamic: author
22
+ Dynamic: classifier
23
+ Dynamic: description
24
+ Dynamic: description-content-type
25
+ Dynamic: license
26
+ Dynamic: license-file
27
+ Dynamic: requires-dist
28
+ Dynamic: requires-python
29
+ Dynamic: summary
30
+
31
+ # affx
32
+
33
+ `affx` is a Python client for the affx API service.
34
+
35
+ It lets users install a package with `pip` and call affx features from Python, while the main service logic runs through the affx API.
36
+
37
+ ## Installation
38
+
39
+ ```bash
40
+ pip install affx
41
+ ```
42
+
43
+ ## Official Links
44
+
45
+ - Official Website: https://affx.ai
46
+ - API Service: https://api.affx.ai
47
+
48
+ ## Quick Start
49
+
50
+ ```python
51
+ import affx
52
+
53
+ affx.key = "your_api_key"
54
+ affx.help()
55
+ ```
56
+
57
+ ## Example Calls
58
+
59
+ ```python
60
+ import affx
61
+
62
+ affx.key = "your_api_key"
63
+
64
+ affx.ls("site")
65
+ affx.gen("schema", domain="example.com", path="about")
66
+ affx.dl("page", domain="example.com", path="about")
67
+ ```
68
+
69
+ ## Important Notes
70
+
71
+ - Set `affx.key` before calling features that require authentication.
72
+ - Some features may take longer to complete because they depend on remote services.
73
+ - Users may need to upgrade the package to receive new client-side functions:
74
+
75
+ ```bash
76
+ pip install --upgrade affx
77
+ ```
78
+
79
+ ## Main Functions
80
+
81
+ - `affx.gen(...)`
82
+ - `affx.upd(...)`
83
+ - `affx.rm(...)`
84
+ - `affx.ls(...)`
85
+ - `affx.dl(...)`
86
+ - `affx.set(...)`
87
+ - `affx.help()`
88
+ - `affx.feedback(...)`
89
+
90
+ ## Requirements
91
+
92
+ - Python 3.9+
93
+ - Internet access for remote API features
94
+
95
+ ## Support
96
+
97
+ If a feature returns an error, first confirm:
98
+
99
+ - your API key is set correctly
100
+ - your network is available
101
+ - the remote affx service is reachable
102
+
103
+ ## License
104
+
105
+ This project is distributed under a proprietary closed-source license owned by AffX.ai.
@@ -0,0 +1,11 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ README.md
4
+ pyproject.toml
5
+ setup.py
6
+ affx/__init__.py
7
+ affx.egg-info/PKG-INFO
8
+ affx.egg-info/SOURCES.txt
9
+ affx.egg-info/dependency_links.txt
10
+ affx.egg-info/requires.txt
11
+ affx.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ requests
2
+ supabase
@@ -0,0 +1 @@
1
+ affx
@@ -0,0 +1,3 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61", "wheel"]
3
+ build-backend = "setuptools.build_meta"
affx-1.0.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
affx-1.0.0/setup.py ADDED
@@ -0,0 +1,68 @@
1
+ import base64
2
+ import zlib
3
+ from pathlib import Path
4
+
5
+ from setuptools import setup, find_packages
6
+ from setuptools.command.build_py import build_py
7
+
8
+
9
+ PROJECT_ROOT = Path(__file__).resolve().parent
10
+ README_PATH = PROJECT_ROOT / "README.md"
11
+
12
+
13
+ class build_py_with_runtime_payload(build_py):
14
+ def run(self):
15
+ super().run()
16
+ self._write_runtime_payload()
17
+
18
+ def _write_runtime_payload(self):
19
+ project_root = Path(__file__).resolve().parent
20
+ source_root = project_root / "api_dependency"
21
+ target_file = Path(self.build_lib) / "affx" / "_runtime_payload.py"
22
+ target_file.parent.mkdir(parents=True, exist_ok=True)
23
+
24
+ payload = {}
25
+ for file_path in sorted(source_root.rglob("*.py")):
26
+ relative_path = file_path.relative_to(project_root).as_posix()
27
+ raw_bytes = file_path.read_bytes()
28
+ encoded = base64.b85encode(zlib.compress(raw_bytes, level=9)).decode("ascii")
29
+ payload[relative_path] = encoded
30
+
31
+ lines = [
32
+ "# Auto-generated during build. Do not edit by hand.",
33
+ "PAYLOAD = {",
34
+ ]
35
+ for relative_path, encoded in payload.items():
36
+ lines.append(f' "{relative_path}": "{encoded}",')
37
+ lines.append("}")
38
+ target_file.write_text("\n".join(lines) + "\n", encoding="utf-8")
39
+
40
+
41
+ setup(
42
+ name="affx",
43
+ version="1.0.0",
44
+ description="Python client for the affx API service.",
45
+ long_description=README_PATH.read_text(encoding="utf-8"),
46
+ long_description_content_type="text/markdown",
47
+ author="affx",
48
+ license="Proprietary",
49
+ packages=find_packages(include=["affx", "affx.*"]),
50
+ include_package_data=True,
51
+ python_requires=">=3.9",
52
+ install_requires=[
53
+ "requests",
54
+ "supabase",
55
+ ],
56
+ classifiers=[
57
+ "Development Status :: 4 - Beta",
58
+ "Intended Audience :: Developers",
59
+ "License :: Other/Proprietary License",
60
+ "Programming Language :: Python :: 3",
61
+ "Programming Language :: Python :: 3.9",
62
+ "Programming Language :: Python :: 3.10",
63
+ "Programming Language :: Python :: 3.11",
64
+ "Operating System :: OS Independent",
65
+ "Topic :: Software Development :: Libraries :: Python Modules",
66
+ ],
67
+ cmdclass={"build_py": build_py_with_runtime_payload},
68
+ )