welcomebot 0.1.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.
- welcomebot-0.1.0/LICENSE +21 -0
- welcomebot-0.1.0/PKG-INFO +40 -0
- welcomebot-0.1.0/README.md +29 -0
- welcomebot-0.1.0/pyproject.toml +23 -0
- welcomebot-0.1.0/setup.cfg +4 -0
- welcomebot-0.1.0/src/welcomebot/__init__.py +3 -0
- welcomebot-0.1.0/src/welcomebot/cnc.py +123 -0
- welcomebot-0.1.0/src/welcomebot/main.py +48 -0
- welcomebot-0.1.0/src/welcomebot/motd.py +58 -0
- welcomebot-0.1.0/src/welcomebot/store.py +84 -0
- welcomebot-0.1.0/src/welcomebot.egg-info/PKG-INFO +40 -0
- welcomebot-0.1.0/src/welcomebot.egg-info/SOURCES.txt +14 -0
- welcomebot-0.1.0/src/welcomebot.egg-info/dependency_links.txt +1 -0
- welcomebot-0.1.0/src/welcomebot.egg-info/entry_points.txt +2 -0
- welcomebot-0.1.0/src/welcomebot.egg-info/requires.txt +2 -0
- welcomebot-0.1.0/src/welcomebot.egg-info/top_level.txt +1 -0
welcomebot-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 cwren
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: welcomebot
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Requires-Python: >=3.14
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Dist: dotenv>=0.9.9
|
|
9
|
+
Requires-Dist: signalbot>=0.25.0
|
|
10
|
+
Dynamic: license-file
|
|
11
|
+
|
|
12
|
+
# json-rpc mode for the signalbot library
|
|
13
|
+
|
|
14
|
+
sudo docker run -d --name signal-api --restart=always -p 9922:8080 \
|
|
15
|
+
-v signal-state:/home/.local/share/signal-cli \
|
|
16
|
+
-e 'MODE=json-rpc' bbernhard/signal-cli-rest-api
|
|
17
|
+
|
|
18
|
+
docker container stop signal-api
|
|
19
|
+
docker container rm signal-api
|
|
20
|
+
|
|
21
|
+
- create .env with
|
|
22
|
+
- SIGNAL_SERVICE=localhost:9922
|
|
23
|
+
- PHONE_NUMBER The number of the signal account
|
|
24
|
+
- WELCOME_MANAGER The signal ID of the manager
|
|
25
|
+
- WELCOME_CNC The command and control group chat ID
|
|
26
|
+
|
|
27
|
+
uv sync
|
|
28
|
+
uv run pytest
|
|
29
|
+
uv run src/welcombot/main.py
|
|
30
|
+
|
|
31
|
+
if migrating an existing bot:
|
|
32
|
+
- signalbot_internal_state.db
|
|
33
|
+
- bot_memory.db
|
|
34
|
+
|
|
35
|
+
# native mode for the pysignalclirestapi library
|
|
36
|
+
|
|
37
|
+
sudo docker run -d --name signal-api --restart=always -p 9922:8080 \
|
|
38
|
+
-v signal-state:/home/.local/share/signal-cli \
|
|
39
|
+
-e 'MODE=native' bbernhard/signal-cli-rest-api
|
|
40
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# json-rpc mode for the signalbot library
|
|
2
|
+
|
|
3
|
+
sudo docker run -d --name signal-api --restart=always -p 9922:8080 \
|
|
4
|
+
-v signal-state:/home/.local/share/signal-cli \
|
|
5
|
+
-e 'MODE=json-rpc' bbernhard/signal-cli-rest-api
|
|
6
|
+
|
|
7
|
+
docker container stop signal-api
|
|
8
|
+
docker container rm signal-api
|
|
9
|
+
|
|
10
|
+
- create .env with
|
|
11
|
+
- SIGNAL_SERVICE=localhost:9922
|
|
12
|
+
- PHONE_NUMBER The number of the signal account
|
|
13
|
+
- WELCOME_MANAGER The signal ID of the manager
|
|
14
|
+
- WELCOME_CNC The command and control group chat ID
|
|
15
|
+
|
|
16
|
+
uv sync
|
|
17
|
+
uv run pytest
|
|
18
|
+
uv run src/welcombot/main.py
|
|
19
|
+
|
|
20
|
+
if migrating an existing bot:
|
|
21
|
+
- signalbot_internal_state.db
|
|
22
|
+
- bot_memory.db
|
|
23
|
+
|
|
24
|
+
# native mode for the pysignalclirestapi library
|
|
25
|
+
|
|
26
|
+
sudo docker run -d --name signal-api --restart=always -p 9922:8080 \
|
|
27
|
+
-v signal-state:/home/.local/share/signal-cli \
|
|
28
|
+
-e 'MODE=native' bbernhard/signal-cli-rest-api
|
|
29
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "welcomebot"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Add your description here"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.14"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"dotenv>=0.9.9",
|
|
9
|
+
"signalbot>=0.25.0",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
[dependency-groups]
|
|
13
|
+
dev = [
|
|
14
|
+
"pytest>=9.0.2",
|
|
15
|
+
"pytest-asyncio>=1.3.0",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.scripts]
|
|
19
|
+
welcomebot = "welcomebot:main"
|
|
20
|
+
|
|
21
|
+
[tool.pytest.ini_options]
|
|
22
|
+
pythonpath = ["src"]
|
|
23
|
+
asyncio_mode = "auto"
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
from signalbot import Command, Context, MessageType
|
|
4
|
+
|
|
5
|
+
HELP_MESSAGE = """you can use these commands:
|
|
6
|
+
list_groups: return known group names
|
|
7
|
+
set_motd: set_motd group <newline> message"""
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CNCCommand(Command):
|
|
11
|
+
def __init__(self, logger, managers, cnc, bs):
|
|
12
|
+
self.logger = logger
|
|
13
|
+
self.managers = managers
|
|
14
|
+
self.cnc = cnc
|
|
15
|
+
self.bs = bs
|
|
16
|
+
|
|
17
|
+
def _get_group_info(self):
|
|
18
|
+
my_group_ids = self.bs.list_groups()
|
|
19
|
+
known_groups = self.bot._groups_by_internal_id
|
|
20
|
+
group_ids = [ group_id for group_id in known_groups.keys() if group_id in my_group_ids]
|
|
21
|
+
groups = [ known_groups[group_id] for group_id in group_ids ]
|
|
22
|
+
group_info = [ { key: group[key] for key in ['name', 'internal_id'] } for group in groups ]
|
|
23
|
+
group_info = sorted(group_info, key=lambda x: x['name'])
|
|
24
|
+
for i, info in enumerate(group_info):
|
|
25
|
+
info['tag'] = i
|
|
26
|
+
return group_info
|
|
27
|
+
|
|
28
|
+
async def handle(self, context: Context) -> None:
|
|
29
|
+
if context.message.group != self.cnc: # guard against DMs
|
|
30
|
+
self.logger.info("cnc ignoring DM message")
|
|
31
|
+
reply = "I only reply to messages in the CNC channel\n"
|
|
32
|
+
await context.send(reply)
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
if context.message.type == MessageType.DATA_MESSAGE:
|
|
36
|
+
self.logger.info("cnc processing data message")
|
|
37
|
+
if context.message.source_uuid not in self.managers:
|
|
38
|
+
reply = "I only reply to messages from a manager\n"
|
|
39
|
+
await context.send(reply)
|
|
40
|
+
return
|
|
41
|
+
ops = context.message.text.split(maxsplit=2)
|
|
42
|
+
match(ops[0].lower()):
|
|
43
|
+
case 'help':
|
|
44
|
+
self.logger.info("cnc sending help message")
|
|
45
|
+
await context.send(HELP_MESSAGE)
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
case 'list_groups':
|
|
49
|
+
self.logger.info("cnc processing list request")
|
|
50
|
+
group_info = self._get_group_info()
|
|
51
|
+
reply = 'known groups:\n'
|
|
52
|
+
reply += '\n'.join([ f'{group['tag']}: {group["name"]}' for group in group_info ])
|
|
53
|
+
await context.send(reply)
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
case 'set_motd':
|
|
57
|
+
self.logger.info("cnc processing set_mod request")
|
|
58
|
+
if len(ops) < 2:
|
|
59
|
+
reply = f'unrecognized set_motd syntax'
|
|
60
|
+
await context.send(reply)
|
|
61
|
+
return
|
|
62
|
+
|
|
63
|
+
group_info = self._get_group_info()
|
|
64
|
+
group_tag = ops[1]
|
|
65
|
+
motd = ops[2] if len(ops) == 3 else None
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
group_tag = int(group_tag)
|
|
69
|
+
except ValueError:
|
|
70
|
+
reply = f'invalid group index: {group_tag}'
|
|
71
|
+
await context.send(reply)
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
if group_tag > len(group_info):
|
|
75
|
+
reply = f'group index out of range: {group_tag}'
|
|
76
|
+
await context.send(reply)
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
group = group_info[group_tag]
|
|
80
|
+
self.bs.put_motd(group['internal_id'], motd)
|
|
81
|
+
if motd:
|
|
82
|
+
reply = f'motd set for group {group_tag} ({group['name']})'
|
|
83
|
+
else:
|
|
84
|
+
reply = f'motd cleared for group {group_tag} ({group['name']}'
|
|
85
|
+
await context.send(reply)
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
case 'get_motd':
|
|
89
|
+
self.logger.info("cnc processing get_mod request")
|
|
90
|
+
if len(ops) < 2:
|
|
91
|
+
reply = f'unrecognized set_motd syntax'
|
|
92
|
+
await context.send(reply)
|
|
93
|
+
return
|
|
94
|
+
|
|
95
|
+
group_info = self._get_group_info()
|
|
96
|
+
group_tag = ops[1]
|
|
97
|
+
motd = ops[2] if len(ops) == 3 else None
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
group_tag = int(group_tag)
|
|
101
|
+
except ValueError:
|
|
102
|
+
reply = f'invalid group index: {group_tag}'
|
|
103
|
+
await context.send(reply)
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
if group_tag >= len(group_info):
|
|
107
|
+
reply = f'group index out of range: {group_tag}'
|
|
108
|
+
await context.send(reply)
|
|
109
|
+
return
|
|
110
|
+
|
|
111
|
+
group = group_info[group_tag]
|
|
112
|
+
motd = self.bs.get_motd(group['internal_id'])
|
|
113
|
+
if motd:
|
|
114
|
+
reply = f'motd for group {group_tag} ({group['name']}) is: \n{motd}'
|
|
115
|
+
else:
|
|
116
|
+
reply = f'there is no motd for group {group_tag} ({group['name']})'
|
|
117
|
+
await context.send(reply)
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
reply = """unknown command, type "help" for a list"""
|
|
122
|
+
await context.send(reply)
|
|
123
|
+
return
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from dotenv import load_dotenv
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
from signalbot import SignalBot, Config, SQLiteConfig, enable_console_logging
|
|
6
|
+
|
|
7
|
+
import cnc
|
|
8
|
+
import motd
|
|
9
|
+
import store
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger("welcomebot")
|
|
12
|
+
|
|
13
|
+
def main():
|
|
14
|
+
bot = SignalBot(
|
|
15
|
+
Config(
|
|
16
|
+
signal_service=os.environ["SIGNAL_SERVICE"],
|
|
17
|
+
phone_number=os.environ["PHONE_NUMBER"],
|
|
18
|
+
storage=SQLiteConfig(
|
|
19
|
+
sqlite_db='signalbot_internal_state.db',
|
|
20
|
+
)
|
|
21
|
+
)
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
cnc_id = os.environ["WELCOME_CNC"]
|
|
25
|
+
managers = re.split(r'[\s|,:]+', os.environ["WELCOME_MANAGER"])
|
|
26
|
+
|
|
27
|
+
bot_store = store.BotStore(logger)
|
|
28
|
+
bot.register(cnc.CNCCommand(logger, managers, cnc_id, bot_store), groups=[cnc_id]) # monitor other groups
|
|
29
|
+
bot.register(motd.MotDCommand(logger, cnc_id, bot_store)) # monitor other groups
|
|
30
|
+
bot.start()
|
|
31
|
+
logger.info("bot started")
|
|
32
|
+
|
|
33
|
+
if __name__ == "__main__":
|
|
34
|
+
# signalbot logs
|
|
35
|
+
enable_console_logging(logging.WARNING)
|
|
36
|
+
|
|
37
|
+
# welcomebot logs
|
|
38
|
+
logger.setLevel(logging.DEBUG)
|
|
39
|
+
handler = logging.StreamHandler()
|
|
40
|
+
formatter = logging.Formatter(
|
|
41
|
+
"%(asctime)s %(name)s %(filename)s [%(levelname)s] - %(message)s"
|
|
42
|
+
)
|
|
43
|
+
handler.setFormatter(formatter)
|
|
44
|
+
logger.addHandler(handler)
|
|
45
|
+
|
|
46
|
+
load_dotenv()
|
|
47
|
+
|
|
48
|
+
main()
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from signalbot import Command, Context, MessageType
|
|
2
|
+
|
|
3
|
+
class MotDCommand(Command):
|
|
4
|
+
def __init__(self, logger, cnc, bs):
|
|
5
|
+
self.logger = logger
|
|
6
|
+
self.cnc = cnc
|
|
7
|
+
self.bs = bs
|
|
8
|
+
|
|
9
|
+
async def handle(self, context: Context) -> None:
|
|
10
|
+
if context.message.group == self.cnc:
|
|
11
|
+
self.logger.info("social is ignoring cnc message")
|
|
12
|
+
return
|
|
13
|
+
|
|
14
|
+
if context.message.group == None:
|
|
15
|
+
self.logger.info("social rebuffing DM message")
|
|
16
|
+
reply = "I only reply to messages in the group chats"
|
|
17
|
+
await context.send(reply)
|
|
18
|
+
return
|
|
19
|
+
|
|
20
|
+
if context.message.type == MessageType.DATA_MESSAGE:
|
|
21
|
+
self.logger.info("social processing data message")
|
|
22
|
+
reply = f'I heard {context.message.text}'
|
|
23
|
+
self.logger.debug("social sending response")
|
|
24
|
+
await context.send(reply)
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
if context.message.type == MessageType.GROUP_UPDATE_MESSAGE:
|
|
28
|
+
self.logger.info("social processing group update")
|
|
29
|
+
post_group = self.bot._groups_by_internal_id[context.message.group]
|
|
30
|
+
prev_members = self.bs.get_members(context.message.group)
|
|
31
|
+
new_member = False
|
|
32
|
+
if prev_members:
|
|
33
|
+
for member in post_group["members"]:
|
|
34
|
+
self.logger.debug(f' looking for {member} in old group')
|
|
35
|
+
if member not in prev_members:
|
|
36
|
+
self.logger.debug(" found a new member of the group")
|
|
37
|
+
new_member = True
|
|
38
|
+
for member in prev_members:
|
|
39
|
+
self.logger.debug(f' looking for {member} in new group')
|
|
40
|
+
if member not in post_group["members"]:
|
|
41
|
+
self.logger.debug(" a member left the group")
|
|
42
|
+
else:
|
|
43
|
+
self.logger.debug(" found a new group")
|
|
44
|
+
# TODO post TOC
|
|
45
|
+
|
|
46
|
+
# update member cache
|
|
47
|
+
self.bs.put_members(context.message.group, post_group["members"])
|
|
48
|
+
self.bs.retain_only(list(self.bot._groups_by_internal_id.keys()))
|
|
49
|
+
|
|
50
|
+
if new_member:
|
|
51
|
+
motd = self.bs.get_motd(context.message.group)
|
|
52
|
+
# TODO don't send too frequently
|
|
53
|
+
if motd:
|
|
54
|
+
self.logger.info("sent the message of the day")
|
|
55
|
+
await context.send(motd)
|
|
56
|
+
else:
|
|
57
|
+
self.logger.info("no message of the day to send")
|
|
58
|
+
return
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import sqlite3
|
|
2
|
+
|
|
3
|
+
class BotStore():
|
|
4
|
+
def __init__(self, logger, db="bot_memory.db"):
|
|
5
|
+
self.logger = logger
|
|
6
|
+
self.con = sqlite3.connect(db)
|
|
7
|
+
cur = self.con.cursor()
|
|
8
|
+
cur.execute("""
|
|
9
|
+
CREATE TABLE IF NOT EXISTS group_members (
|
|
10
|
+
group_id TEXT,
|
|
11
|
+
member_id TEXT
|
|
12
|
+
);
|
|
13
|
+
""")
|
|
14
|
+
self.con.commit()
|
|
15
|
+
cur = self.con.cursor()
|
|
16
|
+
cur.execute("""
|
|
17
|
+
CREATE TABLE IF NOT EXISTS motd (
|
|
18
|
+
group_id TEXT,
|
|
19
|
+
motd TEXT
|
|
20
|
+
);
|
|
21
|
+
""")
|
|
22
|
+
self.con.commit()
|
|
23
|
+
cur.close()
|
|
24
|
+
|
|
25
|
+
def __del__(self):
|
|
26
|
+
self.con.close()
|
|
27
|
+
|
|
28
|
+
def list_groups(self):
|
|
29
|
+
cur = self.con.cursor()
|
|
30
|
+
res = cur.execute('SELECT DISTINCT group_id FROM group_members')
|
|
31
|
+
rows = res.fetchall()
|
|
32
|
+
cur.close()
|
|
33
|
+
return [ row[0] for row in rows ]
|
|
34
|
+
|
|
35
|
+
def retain_only(self, known_groups):
|
|
36
|
+
# TODO also prune old groups
|
|
37
|
+
saved_groups = self.list_groups()
|
|
38
|
+
obsolete_groups = [ group for group in saved_groups if group not in known_groups]
|
|
39
|
+
if obsolete_groups:
|
|
40
|
+
self.logger.debug(f'dropping {len(obsolete_groups)} obsolete groups')
|
|
41
|
+
cur = self.con.cursor()
|
|
42
|
+
placeholders = ', '.join('?' for _ in obsolete_groups)
|
|
43
|
+
cur.executemany(f'DELETE FROM group_members WHERE group_id = ({placeholders})', obsolete_groups)
|
|
44
|
+
self.con.commit()
|
|
45
|
+
cur.close()
|
|
46
|
+
else:
|
|
47
|
+
self.logger.debug('no obsolete groups to prune')
|
|
48
|
+
return obsolete_groups
|
|
49
|
+
|
|
50
|
+
def get_members(self, group):
|
|
51
|
+
cur = self.con.cursor()
|
|
52
|
+
res = cur.execute(f'SELECT member_id FROM group_members WHERE group_id = "{group}"')
|
|
53
|
+
rows = res.fetchall()
|
|
54
|
+
cur.close()
|
|
55
|
+
return [ row[0] for row in rows ]
|
|
56
|
+
|
|
57
|
+
def put_members(self, group, members):
|
|
58
|
+
cur = self.con.cursor()
|
|
59
|
+
cur.execute(f'DELETE FROM group_members WHERE group_id = "{group}"')
|
|
60
|
+
self.con.commit()
|
|
61
|
+
rows = [ (group, member) for member in members ]
|
|
62
|
+
cur = self.con.cursor()
|
|
63
|
+
cur.executemany("INSERT INTO group_members VALUES(?, ?)", rows)
|
|
64
|
+
self.con.commit()
|
|
65
|
+
cur.close()
|
|
66
|
+
|
|
67
|
+
def get_motd(self, group):
|
|
68
|
+
cur = self.con.cursor()
|
|
69
|
+
res = cur.execute(f'SELECT motd FROM motd WHERE group_id = "{group}"')
|
|
70
|
+
row = res.fetchone()
|
|
71
|
+
cur.close()
|
|
72
|
+
return row[0] if row else None
|
|
73
|
+
|
|
74
|
+
def put_motd(self, group, motd):
|
|
75
|
+
cur = self.con.cursor()
|
|
76
|
+
cur.execute(f'DELETE FROM motd WHERE group_id = "{group}"')
|
|
77
|
+
self.con.commit()
|
|
78
|
+
if motd:
|
|
79
|
+
cur = self.con.cursor()
|
|
80
|
+
cur.executemany("INSERT INTO motd VALUES(?, ?)", [ ( group, motd ) ] )
|
|
81
|
+
self.con.commit()
|
|
82
|
+
cur.close()
|
|
83
|
+
return motd
|
|
84
|
+
return None
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: welcomebot
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Requires-Python: >=3.14
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Dist: dotenv>=0.9.9
|
|
9
|
+
Requires-Dist: signalbot>=0.25.0
|
|
10
|
+
Dynamic: license-file
|
|
11
|
+
|
|
12
|
+
# json-rpc mode for the signalbot library
|
|
13
|
+
|
|
14
|
+
sudo docker run -d --name signal-api --restart=always -p 9922:8080 \
|
|
15
|
+
-v signal-state:/home/.local/share/signal-cli \
|
|
16
|
+
-e 'MODE=json-rpc' bbernhard/signal-cli-rest-api
|
|
17
|
+
|
|
18
|
+
docker container stop signal-api
|
|
19
|
+
docker container rm signal-api
|
|
20
|
+
|
|
21
|
+
- create .env with
|
|
22
|
+
- SIGNAL_SERVICE=localhost:9922
|
|
23
|
+
- PHONE_NUMBER The number of the signal account
|
|
24
|
+
- WELCOME_MANAGER The signal ID of the manager
|
|
25
|
+
- WELCOME_CNC The command and control group chat ID
|
|
26
|
+
|
|
27
|
+
uv sync
|
|
28
|
+
uv run pytest
|
|
29
|
+
uv run src/welcombot/main.py
|
|
30
|
+
|
|
31
|
+
if migrating an existing bot:
|
|
32
|
+
- signalbot_internal_state.db
|
|
33
|
+
- bot_memory.db
|
|
34
|
+
|
|
35
|
+
# native mode for the pysignalclirestapi library
|
|
36
|
+
|
|
37
|
+
sudo docker run -d --name signal-api --restart=always -p 9922:8080 \
|
|
38
|
+
-v signal-state:/home/.local/share/signal-cli \
|
|
39
|
+
-e 'MODE=native' bbernhard/signal-cli-rest-api
|
|
40
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/welcomebot/__init__.py
|
|
5
|
+
src/welcomebot/cnc.py
|
|
6
|
+
src/welcomebot/main.py
|
|
7
|
+
src/welcomebot/motd.py
|
|
8
|
+
src/welcomebot/store.py
|
|
9
|
+
src/welcomebot.egg-info/PKG-INFO
|
|
10
|
+
src/welcomebot.egg-info/SOURCES.txt
|
|
11
|
+
src/welcomebot.egg-info/dependency_links.txt
|
|
12
|
+
src/welcomebot.egg-info/entry_points.txt
|
|
13
|
+
src/welcomebot.egg-info/requires.txt
|
|
14
|
+
src/welcomebot.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
welcomebot
|