pygpt-net 2.4.41__py3-none-any.whl → 2.4.43__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.
- CHANGELOG.md +13 -0
- README.md +142 -70
- pygpt_net/CHANGELOG.txt +13 -0
- pygpt_net/__init__.py +3 -3
- pygpt_net/app.py +3 -1
- pygpt_net/controller/audio/__init__.py +2 -2
- pygpt_net/controller/camera.py +1 -10
- pygpt_net/controller/chat/attachment.py +36 -23
- pygpt_net/controller/chat/audio.py +2 -2
- pygpt_net/controller/config/placeholder.py +15 -1
- pygpt_net/controller/ui/mode.py +16 -21
- pygpt_net/core/attachments/__init__.py +1 -1
- pygpt_net/core/attachments/context.py +10 -8
- pygpt_net/core/audio/__init__.py +4 -1
- pygpt_net/core/audio/whisper.py +37 -0
- pygpt_net/core/bridge/worker.py +2 -2
- pygpt_net/core/db/__init__.py +2 -1
- pygpt_net/core/debug/events.py +22 -10
- pygpt_net/core/debug/tabs.py +6 -3
- pygpt_net/core/history.py +3 -2
- pygpt_net/core/idx/__init__.py +16 -4
- pygpt_net/core/idx/chat.py +15 -5
- pygpt_net/core/idx/indexing.py +24 -8
- pygpt_net/core/installer.py +2 -4
- pygpt_net/core/models.py +62 -17
- pygpt_net/core/modes.py +11 -13
- pygpt_net/core/notepad.py +4 -4
- pygpt_net/core/plugins.py +27 -16
- pygpt_net/core/presets.py +20 -9
- pygpt_net/core/profile.py +11 -3
- pygpt_net/core/render/web/parser.py +3 -1
- pygpt_net/core/settings.py +5 -5
- pygpt_net/core/tabs/tab.py +10 -2
- pygpt_net/core/tokens.py +8 -6
- pygpt_net/core/web/__init__.py +105 -0
- pygpt_net/core/{web.py → web/helpers.py} +93 -67
- pygpt_net/data/config/config.json +3 -3
- pygpt_net/data/config/models.json +3 -3
- pygpt_net/data/config/modes.json +3 -3
- pygpt_net/data/locale/locale.en.ini +1 -0
- pygpt_net/data/locale/plugin.cmd_web.en.ini +2 -0
- pygpt_net/data/locale/plugin.mailer.en.ini +21 -0
- pygpt_net/item/ctx.py +66 -3
- pygpt_net/migrations/Version20241215110000.py +25 -0
- pygpt_net/migrations/__init__.py +3 -1
- pygpt_net/plugin/agent/__init__.py +7 -2
- pygpt_net/plugin/audio_output/__init__.py +6 -1
- pygpt_net/plugin/base/plugin.py +58 -26
- pygpt_net/plugin/base/worker.py +20 -17
- pygpt_net/plugin/cmd_history/config.py +2 -2
- pygpt_net/plugin/cmd_web/__init__.py +3 -4
- pygpt_net/plugin/cmd_web/config.py +71 -3
- pygpt_net/plugin/cmd_web/websearch.py +20 -12
- pygpt_net/plugin/cmd_web/worker.py +67 -4
- pygpt_net/plugin/idx_llama_index/config.py +3 -3
- pygpt_net/plugin/mailer/__init__.py +123 -0
- pygpt_net/plugin/mailer/config.py +149 -0
- pygpt_net/plugin/mailer/runner.py +285 -0
- pygpt_net/plugin/mailer/worker.py +123 -0
- pygpt_net/provider/agents/base.py +5 -2
- pygpt_net/provider/agents/openai.py +4 -2
- pygpt_net/provider/agents/openai_assistant.py +4 -2
- pygpt_net/provider/agents/planner.py +4 -2
- pygpt_net/provider/agents/react.py +4 -2
- pygpt_net/provider/audio_output/openai_tts.py +5 -11
- pygpt_net/provider/core/assistant/base.py +5 -3
- pygpt_net/provider/core/assistant/json_file.py +8 -5
- pygpt_net/provider/core/assistant_file/base.py +4 -3
- pygpt_net/provider/core/assistant_file/db_sqlite/__init__.py +4 -3
- pygpt_net/provider/core/assistant_file/db_sqlite/storage.py +3 -2
- pygpt_net/provider/core/assistant_store/base.py +6 -4
- pygpt_net/provider/core/assistant_store/db_sqlite/__init__.py +5 -4
- pygpt_net/provider/core/assistant_store/db_sqlite/storage.py +5 -3
- pygpt_net/provider/core/attachment/base.py +5 -3
- pygpt_net/provider/core/attachment/json_file.py +4 -3
- pygpt_net/provider/core/calendar/base.py +5 -3
- pygpt_net/provider/core/calendar/db_sqlite/__init__.py +6 -5
- pygpt_net/provider/core/calendar/db_sqlite/storage.py +5 -4
- pygpt_net/provider/core/config/base.py +8 -6
- pygpt_net/provider/core/config/json_file.py +9 -7
- pygpt_net/provider/core/ctx/base.py +27 -25
- pygpt_net/provider/core/ctx/db_sqlite/__init__.py +51 -35
- pygpt_net/provider/core/ctx/db_sqlite/storage.py +92 -38
- pygpt_net/provider/core/ctx/db_sqlite/utils.py +37 -11
- pygpt_net/provider/core/index/base.py +129 -23
- pygpt_net/provider/core/index/db_sqlite/__init__.py +130 -23
- pygpt_net/provider/core/index/db_sqlite/storage.py +130 -23
- pygpt_net/provider/core/index/db_sqlite/utils.py +4 -2
- pygpt_net/provider/core/mode/base.py +5 -3
- pygpt_net/provider/core/mode/json_file.py +7 -6
- pygpt_net/provider/core/model/base.py +6 -4
- pygpt_net/provider/core/model/json_file.py +9 -7
- pygpt_net/provider/core/notepad/base.py +5 -3
- pygpt_net/provider/core/notepad/db_sqlite/__init__.py +5 -4
- pygpt_net/provider/core/notepad/db_sqlite/storage.py +4 -3
- pygpt_net/provider/core/plugin_preset/base.py +4 -2
- pygpt_net/provider/core/plugin_preset/json_file.py +5 -3
- pygpt_net/provider/core/preset/base.py +6 -4
- pygpt_net/provider/core/preset/json_file.py +9 -9
- pygpt_net/provider/core/prompt/base.py +6 -3
- pygpt_net/provider/core/prompt/json_file.py +11 -6
- pygpt_net/provider/gpt/assistants.py +15 -6
- pygpt_net/provider/gpt/audio.py +5 -5
- pygpt_net/provider/gpt/chat.py +7 -5
- pygpt_net/provider/gpt/completion.py +8 -4
- pygpt_net/provider/gpt/image.py +3 -3
- pygpt_net/provider/gpt/store.py +46 -12
- pygpt_net/provider/gpt/vision.py +16 -11
- pygpt_net/provider/llms/anthropic.py +7 -2
- pygpt_net/provider/llms/azure_openai.py +26 -5
- pygpt_net/provider/llms/base.py +47 -9
- pygpt_net/provider/llms/google.py +7 -2
- pygpt_net/provider/llms/hugging_face.py +13 -3
- pygpt_net/provider/llms/hugging_face_api.py +18 -4
- pygpt_net/provider/llms/local.py +7 -2
- pygpt_net/provider/llms/ollama.py +30 -6
- pygpt_net/provider/llms/openai.py +32 -6
- pygpt_net/provider/vector_stores/__init__.py +45 -14
- pygpt_net/provider/vector_stores/base.py +35 -8
- pygpt_net/provider/vector_stores/chroma.py +13 -3
- pygpt_net/provider/vector_stores/ctx_attachment.py +32 -13
- pygpt_net/provider/vector_stores/elasticsearch.py +12 -3
- pygpt_net/provider/vector_stores/pinecode.py +12 -3
- pygpt_net/provider/vector_stores/redis.py +12 -3
- pygpt_net/provider/vector_stores/simple.py +12 -3
- pygpt_net/provider/vector_stores/temp.py +16 -4
- pygpt_net/provider/web/base.py +10 -3
- pygpt_net/provider/web/google_custom_search.py +9 -3
- pygpt_net/provider/web/microsoft_bing.py +9 -3
- pygpt_net/tools/__init__.py +13 -5
- pygpt_net/tools/audio_transcriber/__init__.py +4 -3
- pygpt_net/tools/base.py +15 -8
- pygpt_net/tools/code_interpreter/__init__.py +4 -3
- pygpt_net/tools/html_canvas/__init__.py +4 -3
- pygpt_net/tools/image_viewer/__init__.py +10 -4
- pygpt_net/tools/indexer/__init__.py +8 -7
- pygpt_net/tools/media_player/__init__.py +4 -3
- pygpt_net/tools/text_editor/__init__.py +36 -10
- pygpt_net/ui/layout/chat/output.py +2 -2
- pygpt_net/ui/layout/ctx/ctx_list.py +1 -1
- pygpt_net/ui/menu/audio.py +12 -1
- {pygpt_net-2.4.41.dist-info → pygpt_net-2.4.43.dist-info}/METADATA +143 -71
- {pygpt_net-2.4.41.dist-info → pygpt_net-2.4.43.dist-info}/RECORD +146 -138
- {pygpt_net-2.4.41.dist-info → pygpt_net-2.4.43.dist-info}/LICENSE +0 -0
- {pygpt_net-2.4.41.dist-info → pygpt_net-2.4.43.dist-info}/WHEEL +0 -0
- {pygpt_net-2.4.41.dist-info → pygpt_net-2.4.43.dist-info}/entry_points.txt +0 -0
@@ -6,7 +6,7 @@
|
|
6
6
|
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
7
7
|
# MIT License #
|
8
8
|
# Created By : Marcin Szczygliński #
|
9
|
-
# Updated Date: 2024.12.
|
9
|
+
# Updated Date: 2024.12.15 01:00:00 #
|
10
10
|
# ================================================== #
|
11
11
|
|
12
12
|
import json
|
@@ -64,6 +64,9 @@ class Worker(BaseWorker):
|
|
64
64
|
elif item["cmd"] == "web_extract_images":
|
65
65
|
response = self.cmd_web_extract_images(item)
|
66
66
|
|
67
|
+
elif item["cmd"] == "web_request":
|
68
|
+
response = self.cmd_web_request(item)
|
69
|
+
|
67
70
|
if response:
|
68
71
|
responses.append(response)
|
69
72
|
|
@@ -363,7 +366,7 @@ class Worker(BaseWorker):
|
|
363
366
|
url = self.get_param(item, "url")
|
364
367
|
if not url:
|
365
368
|
return self.make_response(item, "No URL provided")
|
366
|
-
links = self.plugin.window.core.web.get_links(url)
|
369
|
+
links = self.plugin.window.core.web.helpers.get_links(url)
|
367
370
|
result = {
|
368
371
|
'links': links,
|
369
372
|
}
|
@@ -385,17 +388,77 @@ class Worker(BaseWorker):
|
|
385
388
|
download = bool(self.get_param(item, "download"))
|
386
389
|
if not url:
|
387
390
|
return self.make_response(item, "No URL provided")
|
388
|
-
images = self.plugin.window.core.web.get_images(url)
|
391
|
+
images = self.plugin.window.core.web.helpers.get_images(url)
|
389
392
|
result = {
|
390
393
|
'images': images,
|
391
394
|
}
|
392
395
|
if images and download:
|
393
396
|
for img in images:
|
394
397
|
try:
|
395
|
-
path = self.plugin.window.core.web.download_image(img)
|
398
|
+
path = self.plugin.window.core.web.helpers.download_image(img)
|
396
399
|
if path:
|
397
400
|
self.ctx.images_before.append(path)
|
398
401
|
except Exception as e:
|
399
402
|
print(e)
|
400
403
|
self.ctx.urls_before.append(url)
|
401
404
|
return self.make_response(item, result)
|
405
|
+
|
406
|
+
def cmd_web_request(self, item: dict) -> dict:
|
407
|
+
"""
|
408
|
+
Web request command
|
409
|
+
|
410
|
+
:param item: command item
|
411
|
+
:return: response item
|
412
|
+
"""
|
413
|
+
url = ""
|
414
|
+
method = "GET"
|
415
|
+
data = None
|
416
|
+
raw = None
|
417
|
+
json = None
|
418
|
+
headers = None
|
419
|
+
params = None
|
420
|
+
cookies = None
|
421
|
+
files = None
|
422
|
+
|
423
|
+
if self.has_param(item, "url"):
|
424
|
+
url = self.get_param(item, "url")
|
425
|
+
if not url:
|
426
|
+
return self.make_response(item, "No URL provided")
|
427
|
+
if self.has_param(item, "method"):
|
428
|
+
method = self.get_param(item, "method")
|
429
|
+
if self.has_param(item, "data_form"):
|
430
|
+
data = self.get_param(item, "data_form")
|
431
|
+
if self.has_param(item, "data"):
|
432
|
+
raw = self.get_param(item, "data")
|
433
|
+
if self.has_param(item, "data_json"):
|
434
|
+
json = self.get_param(item, "data_json")
|
435
|
+
if self.has_param(item, "headers"):
|
436
|
+
headers = self.get_param(item, "headers")
|
437
|
+
if self.has_param(item, "params"):
|
438
|
+
params = self.get_param(item, "params")
|
439
|
+
if self.has_param(item, "cookies"):
|
440
|
+
cookies = self.get_param(item, "cookies")
|
441
|
+
if self.has_param(item, "files"):
|
442
|
+
files = self.get_param(item, "files")
|
443
|
+
|
444
|
+
code, response = self.window.core.web.helpers.request(
|
445
|
+
url=url,
|
446
|
+
method=method,
|
447
|
+
headers=headers,
|
448
|
+
params=params,
|
449
|
+
data=data if data else raw,
|
450
|
+
json=json,
|
451
|
+
cookies=cookies,
|
452
|
+
files=files,
|
453
|
+
timeout=self.plugin.get_option_value("timeout"),
|
454
|
+
disable_ssl_verify=self.plugin.get_option_value("disable_ssl"),
|
455
|
+
allow_redirects=True,
|
456
|
+
stream=False,
|
457
|
+
user_agent=self.plugin.get_option_value("user_agent"))
|
458
|
+
result = {
|
459
|
+
'url': url,
|
460
|
+
'code': code,
|
461
|
+
'response': response,
|
462
|
+
}
|
463
|
+
self.ctx.urls_before.append(url)
|
464
|
+
return self.make_response(item, result)
|
@@ -6,7 +6,7 @@
|
|
6
6
|
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
7
7
|
# MIT License #
|
8
8
|
# Created By : Marcin Szczygliński #
|
9
|
-
# Updated Date: 2024.
|
9
|
+
# Updated Date: 2024.12.14 19:00:00 #
|
10
10
|
# ================================================== #
|
11
11
|
|
12
12
|
from pygpt_net.plugin.base.config import BaseConfig, BasePlugin
|
@@ -71,7 +71,7 @@ class Config(BaseConfig):
|
|
71
71
|
"model_prepare_question",
|
72
72
|
type="combo",
|
73
73
|
use="models",
|
74
|
-
value="gpt-
|
74
|
+
value="gpt-4o-mini",
|
75
75
|
label="Model for question preparation",
|
76
76
|
description="Model used to prepare question before asking Llama-index, default: gpt-3.5-turbo",
|
77
77
|
tooltip="Model",
|
@@ -88,7 +88,7 @@ class Config(BaseConfig):
|
|
88
88
|
plugin.add_option(
|
89
89
|
"model_query",
|
90
90
|
type="combo",
|
91
|
-
value="gpt-
|
91
|
+
value="gpt-4o-mini",
|
92
92
|
label="Model",
|
93
93
|
description="Model used for querying Llama-index, default: gpt-3.5-turbo",
|
94
94
|
tooltip="Query model",
|
@@ -0,0 +1,123 @@
|
|
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: 2024.12.15 04:00:00 #
|
10
|
+
# ================================================== #
|
11
|
+
|
12
|
+
from pygpt_net.plugin.base.plugin import BasePlugin
|
13
|
+
from pygpt_net.core.events import Event
|
14
|
+
from pygpt_net.item.ctx import CtxItem
|
15
|
+
|
16
|
+
from .config import Config
|
17
|
+
from .runner import Runner
|
18
|
+
from .worker import Worker
|
19
|
+
|
20
|
+
from pygpt_net.utils import trans
|
21
|
+
|
22
|
+
|
23
|
+
class Plugin(BasePlugin):
|
24
|
+
def __init__(self, *args, **kwargs):
|
25
|
+
super(Plugin, self).__init__(*args, **kwargs)
|
26
|
+
self.id = "mailer"
|
27
|
+
self.name = "Mailer"
|
28
|
+
self.description = "Provides email sending"
|
29
|
+
self.prefix = "Email"
|
30
|
+
self.type = [
|
31
|
+
'email',
|
32
|
+
]
|
33
|
+
self.order = 100
|
34
|
+
self.allowed_cmds = [
|
35
|
+
"send_mail",
|
36
|
+
"get_emails",
|
37
|
+
"get_email_body",
|
38
|
+
]
|
39
|
+
self.use_locale = True
|
40
|
+
self.runner = Runner(self)
|
41
|
+
self.worker = None
|
42
|
+
self.config = Config(self)
|
43
|
+
self.init_options()
|
44
|
+
|
45
|
+
def init_options(self):
|
46
|
+
"""Initialize options"""
|
47
|
+
self.config.from_defaults(self)
|
48
|
+
|
49
|
+
def handle(self, event: Event, *args, **kwargs):
|
50
|
+
"""
|
51
|
+
Handle dispatched event
|
52
|
+
|
53
|
+
:param event: event object
|
54
|
+
:param args: args
|
55
|
+
:param kwargs: kwargs
|
56
|
+
"""
|
57
|
+
name = event.name
|
58
|
+
data = event.data
|
59
|
+
ctx = event.ctx
|
60
|
+
silent = data.get("silent", False)
|
61
|
+
|
62
|
+
if name == Event.CMD_SYNTAX:
|
63
|
+
self.cmd_syntax(data)
|
64
|
+
|
65
|
+
elif name == Event.CMD_EXECUTE:
|
66
|
+
self.cmd(
|
67
|
+
ctx,
|
68
|
+
data['commands'],
|
69
|
+
silent,
|
70
|
+
)
|
71
|
+
|
72
|
+
def cmd_syntax(self, data: dict):
|
73
|
+
"""
|
74
|
+
Event: CMD_SYNTAX
|
75
|
+
|
76
|
+
:param data: event data dict
|
77
|
+
"""
|
78
|
+
for item in self.allowed_cmds:
|
79
|
+
if self.has_cmd(item):
|
80
|
+
cmd = self.get_cmd(item)
|
81
|
+
data['cmd'].append(cmd) # append command
|
82
|
+
|
83
|
+
def cmd(self, ctx: CtxItem, cmds: list, silent: bool = False):
|
84
|
+
"""
|
85
|
+
Event: CMD_EXECUTE
|
86
|
+
|
87
|
+
:param ctx: CtxItem
|
88
|
+
:param cmds: commands dict
|
89
|
+
:param silent: silent mode
|
90
|
+
"""
|
91
|
+
is_cmd = False
|
92
|
+
force = False
|
93
|
+
my_commands = []
|
94
|
+
for item in cmds:
|
95
|
+
if item["cmd"] in self.allowed_cmds:
|
96
|
+
my_commands.append(item)
|
97
|
+
is_cmd = True
|
98
|
+
if "force" in item and item["force"]:
|
99
|
+
force = True # call from tool
|
100
|
+
|
101
|
+
if not is_cmd:
|
102
|
+
return
|
103
|
+
|
104
|
+
# set state: busy
|
105
|
+
if not silent:
|
106
|
+
self.cmd_prepare(ctx, my_commands)
|
107
|
+
|
108
|
+
try:
|
109
|
+
worker = Worker()
|
110
|
+
worker.from_defaults(self)
|
111
|
+
worker.cmds = my_commands
|
112
|
+
worker.ctx = ctx
|
113
|
+
|
114
|
+
# connect signals
|
115
|
+
self.runner.attach_signals(worker.signals)
|
116
|
+
|
117
|
+
if not self.is_async(ctx) and not force:
|
118
|
+
worker.run()
|
119
|
+
return
|
120
|
+
worker.run_async()
|
121
|
+
|
122
|
+
except Exception as e:
|
123
|
+
self.error(e)
|
@@ -0,0 +1,149 @@
|
|
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: 2024.12.15 04:00:00 #
|
10
|
+
# ================================================== #
|
11
|
+
|
12
|
+
from pygpt_net.plugin.base.config import BaseConfig, BasePlugin
|
13
|
+
|
14
|
+
|
15
|
+
class Config(BaseConfig):
|
16
|
+
def __init__(self, plugin: BasePlugin = None, *args, **kwargs):
|
17
|
+
super(Config, self).__init__(plugin)
|
18
|
+
self.plugin = plugin
|
19
|
+
|
20
|
+
def from_defaults(self, plugin: BasePlugin = None):
|
21
|
+
"""
|
22
|
+
Set default options for plugin
|
23
|
+
|
24
|
+
:param plugin: plugin instance
|
25
|
+
"""
|
26
|
+
plugin.add_option(
|
27
|
+
"smtp_host",
|
28
|
+
type="text",
|
29
|
+
value="",
|
30
|
+
label="SMTP Host",
|
31
|
+
persist=True,
|
32
|
+
tab="smtp",
|
33
|
+
)
|
34
|
+
plugin.add_option(
|
35
|
+
"smtp_port_inbox",
|
36
|
+
type="int",
|
37
|
+
value=995,
|
38
|
+
label="SMTP Port (Inbox)",
|
39
|
+
persist=True,
|
40
|
+
tab="smtp",
|
41
|
+
)
|
42
|
+
plugin.add_option(
|
43
|
+
"smtp_port_outbox",
|
44
|
+
type="int",
|
45
|
+
value=465,
|
46
|
+
label="SMTP Port (Outbox)",
|
47
|
+
persist=True,
|
48
|
+
tab="smtp",
|
49
|
+
)
|
50
|
+
plugin.add_option(
|
51
|
+
"smtp_user",
|
52
|
+
type="text",
|
53
|
+
value="",
|
54
|
+
label="SMTP User",
|
55
|
+
persist=True,
|
56
|
+
tab="smtp",
|
57
|
+
)
|
58
|
+
plugin.add_option(
|
59
|
+
"smtp_password",
|
60
|
+
type="text",
|
61
|
+
value="",
|
62
|
+
label="SMTP Password",
|
63
|
+
secret=True,
|
64
|
+
persist=True,
|
65
|
+
tab="smtp",
|
66
|
+
)
|
67
|
+
plugin.add_option(
|
68
|
+
"from_email",
|
69
|
+
type="text",
|
70
|
+
value="",
|
71
|
+
label="From (email)",
|
72
|
+
)
|
73
|
+
plugin.add_cmd(
|
74
|
+
"send_email",
|
75
|
+
instruction="Send email to specified address",
|
76
|
+
params=[
|
77
|
+
{
|
78
|
+
"name": "recipient",
|
79
|
+
"type": "str",
|
80
|
+
"description": "Recipient email address",
|
81
|
+
"required": True,
|
82
|
+
},
|
83
|
+
{
|
84
|
+
"name": "subject",
|
85
|
+
"type": "str",
|
86
|
+
"description": "Email subject",
|
87
|
+
"required": True,
|
88
|
+
},
|
89
|
+
{
|
90
|
+
"name": "message",
|
91
|
+
"type": "str",
|
92
|
+
"description": "Email message",
|
93
|
+
"required": True,
|
94
|
+
},
|
95
|
+
],
|
96
|
+
enabled=True,
|
97
|
+
description="Allows sending emails",
|
98
|
+
tab="general",
|
99
|
+
)
|
100
|
+
plugin.add_cmd(
|
101
|
+
"get_emails",
|
102
|
+
instruction="Retrieve emails from the inbox. Use this to access emails from the inbox when the user "
|
103
|
+
"requests fetching emails from their server.",
|
104
|
+
params=[
|
105
|
+
{
|
106
|
+
"name": "limit",
|
107
|
+
"type": "int",
|
108
|
+
"description": "Limit of emails to receive, default: 10",
|
109
|
+
"required": True,
|
110
|
+
},
|
111
|
+
{
|
112
|
+
"name": "offset",
|
113
|
+
"type": "int",
|
114
|
+
"description": "Offset of emails to receive, default: 0",
|
115
|
+
"required": False,
|
116
|
+
},
|
117
|
+
{
|
118
|
+
"name": "order",
|
119
|
+
"type": "text",
|
120
|
+
"description": "Sort order, default: desc, options: asc, desc",
|
121
|
+
"required": False,
|
122
|
+
},
|
123
|
+
],
|
124
|
+
enabled=True,
|
125
|
+
description="Allows receiving emails",
|
126
|
+
tab="general",
|
127
|
+
)
|
128
|
+
plugin.add_cmd(
|
129
|
+
"get_email_body",
|
130
|
+
instruction="Retrieve the complete email content from the inbox. Use this to access the message body. "
|
131
|
+
"To obtain email IDs, use the 'get_emails' function first.",
|
132
|
+
params=[
|
133
|
+
{
|
134
|
+
"name": "id",
|
135
|
+
"type": "int",
|
136
|
+
"description": "Email ID (index)",
|
137
|
+
"required": True,
|
138
|
+
},
|
139
|
+
{
|
140
|
+
"name": "format",
|
141
|
+
"type": "text",
|
142
|
+
"description": "Display format, default: text, options: text, html",
|
143
|
+
"required": False,
|
144
|
+
},
|
145
|
+
],
|
146
|
+
enabled=True,
|
147
|
+
description="Allows receiving email body",
|
148
|
+
tab="general",
|
149
|
+
)
|
@@ -0,0 +1,285 @@
|
|
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: 2024.12.15 04:00:00 #
|
10
|
+
# ================================================== #
|
11
|
+
|
12
|
+
import datetime
|
13
|
+
import smtplib
|
14
|
+
import poplib
|
15
|
+
|
16
|
+
from email.parser import BytesParser
|
17
|
+
from typing import Any
|
18
|
+
from bs4 import BeautifulSoup
|
19
|
+
|
20
|
+
from pygpt_net.item.ctx import CtxItem
|
21
|
+
|
22
|
+
|
23
|
+
class Runner:
|
24
|
+
def __init__(self, plugin=None):
|
25
|
+
"""
|
26
|
+
Cmd Runner
|
27
|
+
|
28
|
+
:param plugin: plugin
|
29
|
+
"""
|
30
|
+
self.plugin = plugin
|
31
|
+
self.signals = None
|
32
|
+
|
33
|
+
def attach_signals(self, signals):
|
34
|
+
"""
|
35
|
+
Attach signals
|
36
|
+
|
37
|
+
:param signals: signals
|
38
|
+
"""
|
39
|
+
self.signals = signals
|
40
|
+
|
41
|
+
def parse_email(self, msg, as_text: bool = True) -> str:
|
42
|
+
"""
|
43
|
+
Parse email message
|
44
|
+
|
45
|
+
:param msg: email message
|
46
|
+
:param as_text: return as text
|
47
|
+
:return: parsed email
|
48
|
+
"""
|
49
|
+
body = ""
|
50
|
+
is_html = False
|
51
|
+
if msg.is_multipart():
|
52
|
+
for part in msg.walk():
|
53
|
+
content_type = part.get_content_type()
|
54
|
+
content_disposition = str(part.get('Content-Disposition'))
|
55
|
+
if content_type == 'text/plain' and 'attachment' not in content_disposition:
|
56
|
+
body = part.get_payload(decode=True).decode(part.get_content_charset('utf-8'))
|
57
|
+
elif content_type == 'text/html' and 'attachment' not in content_disposition:
|
58
|
+
body = part.get_payload(decode=True).decode(part.get_content_charset('utf-8'))
|
59
|
+
is_html = True
|
60
|
+
else:
|
61
|
+
body = msg.get_payload(decode=True).decode(msg.get_content_charset('utf-8'))
|
62
|
+
if is_html and as_text:
|
63
|
+
body = BeautifulSoup(body, 'html.parser').get_text()
|
64
|
+
return body
|
65
|
+
|
66
|
+
def smtp_get_email_body(self, ctx: CtxItem, item: dict, request: dict) -> dict:
|
67
|
+
"""
|
68
|
+
Receive emails from mailbox
|
69
|
+
|
70
|
+
:param ctx: CtxItem
|
71
|
+
:param item: command item
|
72
|
+
:param request: request item
|
73
|
+
:return: response dict
|
74
|
+
"""
|
75
|
+
server = self.plugin.get_option_value("smtp_host")
|
76
|
+
port = self.plugin.get_option_value("smtp_port_inbox")
|
77
|
+
id = item["params"].get("id", "")
|
78
|
+
username = self.plugin.get_option_value("smtp_user")
|
79
|
+
password = self.plugin.get_option_value("smtp_password")
|
80
|
+
msg = "Receiving email from: {}".format(server)
|
81
|
+
format = "text"
|
82
|
+
as_text = True
|
83
|
+
if "format" in item["params"]:
|
84
|
+
format = item["params"]["format"].lower()
|
85
|
+
if format == "html":
|
86
|
+
as_text = False
|
87
|
+
self.log(msg)
|
88
|
+
try:
|
89
|
+
if port in [995]:
|
90
|
+
pop3 = poplib.POP3_SSL(server, port)
|
91
|
+
else:
|
92
|
+
pop3 = poplib.POP3(server, port)
|
93
|
+
|
94
|
+
msg = pop3.getwelcome().decode('utf-8')
|
95
|
+
self.log(msg)
|
96
|
+
pop3.user(username)
|
97
|
+
pop3.pass_(password)
|
98
|
+
response, lines, octets = pop3.retr(id)
|
99
|
+
msg_content = b'\r\n'.join(lines)
|
100
|
+
msg = BytesParser().parsebytes(msg_content)
|
101
|
+
result = {
|
102
|
+
"ID": id,
|
103
|
+
"From": msg["From"],
|
104
|
+
"Subject": msg["Subject"],
|
105
|
+
"Date": msg["Date"],
|
106
|
+
"Message": self.parse_email(msg, as_text)
|
107
|
+
}
|
108
|
+
pop3.quit()
|
109
|
+
except Exception as e:
|
110
|
+
self.error(e)
|
111
|
+
result = "Error: {}".format(str(e))
|
112
|
+
return {
|
113
|
+
"request": request,
|
114
|
+
"result": str(result)
|
115
|
+
}
|
116
|
+
|
117
|
+
def smtp_receive_emails(self, ctx: CtxItem, item: dict, request: dict) -> dict:
|
118
|
+
"""
|
119
|
+
Receive emails from mailbox
|
120
|
+
|
121
|
+
:param ctx: CtxItem
|
122
|
+
:param item: command item
|
123
|
+
:param request: request item
|
124
|
+
:return: response dict
|
125
|
+
"""
|
126
|
+
server = self.plugin.get_option_value("smtp_host")
|
127
|
+
port = self.plugin.get_option_value("smtp_port_inbox")
|
128
|
+
limit = item["params"].get("limit", 10)
|
129
|
+
offset = item["params"].get("offset", 0)
|
130
|
+
username = self.plugin.get_option_value("smtp_user")
|
131
|
+
password = self.plugin.get_option_value("smtp_password")
|
132
|
+
msg = "Receiving emails from: {}".format(server)
|
133
|
+
order = "desc"
|
134
|
+
if "order" in item["params"]:
|
135
|
+
tmp_order = item["params"]["order"].lower()
|
136
|
+
if tmp_order in ["asc", "desc"]:
|
137
|
+
order = tmp_order
|
138
|
+
self.log(msg)
|
139
|
+
|
140
|
+
messages = []
|
141
|
+
try:
|
142
|
+
if port in [995]:
|
143
|
+
pop3 = poplib.POP3_SSL(server, port)
|
144
|
+
else:
|
145
|
+
pop3 = poplib.POP3(server, port)
|
146
|
+
|
147
|
+
msg = pop3.getwelcome().decode('utf-8')
|
148
|
+
self.log(msg)
|
149
|
+
pop3.user(username)
|
150
|
+
pop3.pass_(password)
|
151
|
+
|
152
|
+
message_count, mailbox_size = pop3.stat()
|
153
|
+
total = message_count
|
154
|
+
print(f'Num of messages: {message_count}, Inbox size: {mailbox_size} bytes')
|
155
|
+
|
156
|
+
if order == "desc":
|
157
|
+
if offset > 0:
|
158
|
+
message_count -= offset
|
159
|
+
if message_count < limit:
|
160
|
+
limit = message_count
|
161
|
+
range_max = message_count - limit
|
162
|
+
if limit == 0:
|
163
|
+
range_max = 0
|
164
|
+
for i in range(message_count, range_max, -1):
|
165
|
+
response, lines, octets = pop3.top(i, 0)
|
166
|
+
msg_content = b'\r\n'.join(lines)
|
167
|
+
msg = BytesParser().parsebytes(msg_content)
|
168
|
+
msg_body = {
|
169
|
+
"ID": i,
|
170
|
+
"From": msg["From"],
|
171
|
+
"Subject": msg["Subject"],
|
172
|
+
"Date": msg["Date"],
|
173
|
+
}
|
174
|
+
messages.append(msg_body)
|
175
|
+
else:
|
176
|
+
if limit == 0:
|
177
|
+
limit = message_count
|
178
|
+
else:
|
179
|
+
limit += offset
|
180
|
+
if limit > message_count:
|
181
|
+
limit = message_count
|
182
|
+
for i in range(offset + 1, limit + 1):
|
183
|
+
response, lines, octets = pop3.top(i, 0)
|
184
|
+
msg_content = b'\r\n'.join(lines)
|
185
|
+
msg = BytesParser().parsebytes(msg_content)
|
186
|
+
msg_body = {
|
187
|
+
"ID": i,
|
188
|
+
"From": msg["From"],
|
189
|
+
"Subject": msg["Subject"],
|
190
|
+
"Date": msg["Date"],
|
191
|
+
}
|
192
|
+
messages.append(msg_body)
|
193
|
+
|
194
|
+
pop3.quit()
|
195
|
+
result = {
|
196
|
+
"total": total,
|
197
|
+
"messages": messages,
|
198
|
+
}
|
199
|
+
except Exception as e:
|
200
|
+
self.error(e)
|
201
|
+
result = "Error: {}".format(str(e))
|
202
|
+
return {
|
203
|
+
"request": request,
|
204
|
+
"result": str(result)
|
205
|
+
}
|
206
|
+
|
207
|
+
def smtp_send_email(self, ctx: CtxItem, item: dict, request: dict) -> dict:
|
208
|
+
"""
|
209
|
+
Execute system command on host
|
210
|
+
|
211
|
+
:param ctx: CtxItem
|
212
|
+
:param item: command item
|
213
|
+
:param request: request item
|
214
|
+
:return: response dict
|
215
|
+
"""
|
216
|
+
msg = "Sending email to: {}".format(item["params"]['recipient'])
|
217
|
+
self.log(msg)
|
218
|
+
try:
|
219
|
+
email_to = item["params"]['recipient']
|
220
|
+
message = item["params"]['message']
|
221
|
+
subject = item["params"]['subject']
|
222
|
+
from_addr = self.plugin.get_option_value("from_email")
|
223
|
+
host = self.plugin.get_option_value("smtp_host")
|
224
|
+
port = self.plugin.get_option_value("smtp_port_outbox")
|
225
|
+
user = self.plugin.get_option_value("smtp_user")
|
226
|
+
password = self.plugin.get_option_value("smtp_password")
|
227
|
+
date = datetime.datetime.now().strftime("%d/%m/%Y %H:%M")
|
228
|
+
body = """From: {sender}\nTo: {to}\nDate: {date}\nSubject: {subject}\n\n{msg}
|
229
|
+
""".format(sender=from_addr, to=email_to, date=date, subject=subject, msg=message)
|
230
|
+
if port in [465]:
|
231
|
+
server = smtplib.SMTP_SSL(host, port)
|
232
|
+
else:
|
233
|
+
server = smtplib.SMTP(host, port)
|
234
|
+
server.ehlo()
|
235
|
+
if port in [587]:
|
236
|
+
server.starttls()
|
237
|
+
server.login(user, password)
|
238
|
+
server.sendmail(from_addr, email_to, body)
|
239
|
+
server.close()
|
240
|
+
result = "OK"
|
241
|
+
except Exception as e:
|
242
|
+
self.error(e)
|
243
|
+
result = "Error: {}".format(str(e))
|
244
|
+
return {
|
245
|
+
"request": request,
|
246
|
+
"result": str(result)
|
247
|
+
}
|
248
|
+
|
249
|
+
def error(self, err: Any):
|
250
|
+
"""
|
251
|
+
Log error message
|
252
|
+
|
253
|
+
:param err: exception or error message
|
254
|
+
"""
|
255
|
+
if self.signals is not None:
|
256
|
+
self.signals.error.emit(err)
|
257
|
+
|
258
|
+
def status(self, msg: str):
|
259
|
+
"""
|
260
|
+
Send status message
|
261
|
+
|
262
|
+
:param msg: status message
|
263
|
+
"""
|
264
|
+
if self.signals is not None:
|
265
|
+
self.signals.status.emit(msg)
|
266
|
+
|
267
|
+
def debug(self, msg: Any):
|
268
|
+
"""
|
269
|
+
Log debug message
|
270
|
+
|
271
|
+
:param msg: message to log
|
272
|
+
"""
|
273
|
+
if self.signals is not None:
|
274
|
+
self.signals.debug.emit(msg)
|
275
|
+
|
276
|
+
def log(self, msg, sandbox: bool = False):
|
277
|
+
"""
|
278
|
+
Log message to console
|
279
|
+
|
280
|
+
:param msg: message to log
|
281
|
+
:param sandbox: is sandbox mode
|
282
|
+
"""
|
283
|
+
full_msg = '[SMTP]' + ' ' + str(msg)
|
284
|
+
if self.signals is not None:
|
285
|
+
self.signals.log.emit(full_msg)
|