Compare commits

..

22 commits
main ... main

Author SHA1 Message Date
missytake 3f09be5b25 Mark messages as read after someone talked about it in a relay group
fix #15
2024-08-20 13:50:19 +02:00
missytake 9c40e5f3af added docstring 2024-08-20 13:34:48 +02:00
missytake 32faf3d281 fix: offboarding: remove ppl only from groups they were part of
fix #16
2024-08-20 13:34:48 +02:00
missytake db8ff5caec feat: offboarding: remove ex-admins from relay groups
fix #6
2024-08-20 12:27:42 +02:00
missytake c1ccfcb546
require more recent deltachat bindings version 2024-08-20 07:41:13 +02:00
missytake f9186fe0c0 get relay group from pickle instead of guessing 2024-08-20 07:37:37 +02:00
missytake 18e5a67aa7
chore: specify dev dependencies in setup.cfg, add CI (#12)
* chore: specify dev dependencies in setup.cfg
* added CI
* fix lint
* fix flaky test
2024-08-19 14:11:46 +02:00
missytake f3daeee8d9
increased version to 1.1.0 2024-04-30 11:27:22 +02:00
missytake 9faeb7cdce fix: invite links don't work; simply return openpgp4fpr instead 2024-04-30 11:20:54 +02:00
missytake 85625578a8 feat: new command to generate an invite link 2024-04-30 11:20:54 +02:00
missytake defc179dcf test: added test for creating an invite link and joining as an outsider 2024-04-30 11:20:54 +02:00
missytake 5d8c11d43b chore: upgrade deltachat library and fix tests 2024-04-30 11:20:54 +02:00
missytake 73172941e4 test: new helper function to get crew from team member's perspective 2024-04-30 11:20:54 +02:00
missytake 93c0405629
Merge pull request #3 from hagenest/rm_log_message
Remove logging of incoming messages
2024-04-10 14:11:39 +02:00
hagi d6e302c408 remove logging of incoming messages 2024-04-09 15:30:05 +02:00
missytake dda17c2469 updated version to 1.0.0 2024-04-03 14:52:23 +02:00
missytake 6f785969b3 rename teams-bot to team-bot 2024-02-03 17:21:58 +01:00
missytake 15885c4dfc remove duplicate enable-linger 2024-02-03 17:06:02 +01:00
missytake 32ca1a4ee6 set BCC_self to True, so sent messages can be looked up in Thunderbird 2024-02-03 17:04:30 +01:00
missytake cc1625fa29 fix: forward to unencrypted outside chat. fix #16 2024-01-21 13:04:01 +01:00
missytake b899b6c3eb send error to relay group if sending outside message fails 2023-12-21 01:34:37 +01:00
link2xt 68e1003046 fix: set mvbox_move before starting an account the first time
Otherwise IMAP connection for DeltaChat folder is not started,
so messages are moved to DeltaChat folder but are not picked up
from there until the bot is restarted.
2023-12-17 11:52:40 +00:00
15 changed files with 294 additions and 142 deletions

23
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,23 @@
name: CI
on:
pull_request:
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.8', '3.11']
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install '.[dev]'
- run: tox

2
.gitignore vendored
View file

@ -3,6 +3,8 @@
__pycache__/
/.tox/
/build/
/dist/
/venv/
bot.db/
teams_bot_data/
team_bot_data/

View file

@ -1,4 +1,4 @@
# Teams Bot
# Team Bot
This bot connects your team to the outside
and makes it addressable.
@ -22,8 +22,8 @@ To install this bot,
run:
```
git clone https://git.0x90.space/missytake/teams-bot
cd teams-bot
git clone https://github.com/deltachat-bot/team-bot
cd team-bot
pip install .
```
@ -32,19 +32,19 @@ with an email address
you want to use as a team:
```
teams-bot init --email helpdesk@example.org --password p455w0rD
team-bot init --email helpdesk@example.org --password p455w0rD
```
This command will show a QR code;
scan it with Delta Chat
to become part of the "team",
the verified group which manages the Teams Bot.
the verified group which manages the Team Bot.
Now to run it,
simply execute:
```
teams-bot run -v
team-bot run -v
```
The bot only works as long as this command is running.
@ -59,9 +59,9 @@ you can deploy this bot with it.
Just import it into your [deploy.py file](https://docs.pyinfra.com/en/2.x/getting-started.html#create-a-deploy) like this:
```
from teams_bot.pyinfra import deploy_teams_bot
from team_bot.pyinfra import deploy_team_bot
deploy_teams_bot(
deploy_team_bot(
unix_user='root', # an existing UNIX user (doesn't need root or sudo privileges)
bot_email='helpdesk@example.org', # the email address your team wants to use
bot_passwd='p4ssw0rd', # the password to the email account
@ -79,7 +79,7 @@ login to the user with ssh
and run:
```
export $(cat ~/.env | xargs) && ~/.local/lib/teams-bot.venv/bin/teams-bot init
export $(cat ~/.env | xargs) && ~/.local/lib/team-bot.venv/bin/team-bot init
```
Then,
@ -88,11 +88,11 @@ and keep it running in the background,
run:
```
systemctl --user enable --now teams-bot
systemctl --user enable --now team-bot
```
You can view the log output
with `journalctl --user -fu teams-bot`
with `journalctl --user -fu team-bot`
to confirm that it works.
## Development Environment
@ -103,7 +103,6 @@ run:
```
python3 -m venv venv
. venv/bin/activate
pip install pytest tox black pytest-xdist pytest-timeout
pip install -e .
pip install -e .[dev]
tox
```

View file

@ -1,17 +1,17 @@
[metadata]
name = teams-bot
version = 0.0.1
name = team_bot
version = 1.1.0
author = missytake
author_email = missytake@systemli.org
description = This bot connects your team to the outside and makes it addressable.
long_description = file: README.md
long_description_content_type = text/markdown
url = https://git.0x90.space/missytake/teams-bot
url = https://github.com/deltachat-bot/team-bot
project_urls =
Bug Tracker = https://git.0x90.space/missytake/teams-bot/issues
Bug Tracker = https://github.com/deltachat-bot/team-bot/issues
classifiers =
Programming Language :: Python :: 3
License :: OSI Approved :: ISC License (ISCL)
License :: OSI Approved :: MIT License
Operating System :: OS Independent
[options]
@ -24,14 +24,22 @@ install_requires =
pyinfra
pickleDB
qrcode
deltachat
deltachat>=1.142.7
[options.extras_require]
dev =
pytest
tox
black
pytest-xdist
pytest-timeout
[options.packages.find]
where = src
[options.entry_points]
console_scripts =
teams-bot = teams_bot.cli:main
team-bot = team_bot.cli:main
[tox:tox]
envlist = lint, py310

View file

@ -11,6 +11,7 @@ from .commands import (
crew_help,
set_display_name,
set_avatar,
generate_invite,
start_chat,
outside_help,
set_outside_help,
@ -40,6 +41,7 @@ class SetupPlugin:
class RelayPlugin:
def __init__(self, account: deltachat.Account, kvstore: pickledb.PickleDB):
self.account = account
self.account.set_config("bcc_self", "1")
self.kvstore = kvstore
self.crew = account.get_chat_by_id(kvstore.get("crew_id"))
if not kvstore.get("relays"):
@ -64,15 +66,14 @@ class RelayPlugin:
relay_group = self.get_relay_group(message.chat.id)
relay_group.send_text(f"Sending Message failed:\n\n{error}")
@account_hookimpl
def ac_member_removed(self, chat, contact, actor, message):
if chat == self.crew:
self.offboard(contact)
@account_hookimpl
def ac_incoming_message(self, message: deltachat.Message):
"""This method is called on every incoming message and decides what to do with it."""
logging.info(
"New message from %s in chat %s: %s",
message.get_sender_contact().addr,
message.chat.get_name(),
message.text,
)
if message.is_system_message():
if message.chat.id == self.crew.id:
@ -113,6 +114,9 @@ class RelayPlugin:
if arguments[0] == "/set_avatar":
result = set_avatar(self.account, message, self.crew)
self.reply(message.chat, result, quote=message)
if arguments[0] == "/generate-invite":
text = generate_invite(self.account)
self.reply(message.chat, text, quote=message)
if arguments[0] == "/start_chat":
outside_chat, result = start_chat(
self.account,
@ -152,6 +156,7 @@ class RelayPlugin:
else:
logging.debug("Ignoring message, just the crew chatting")
else:
self.mark_last_messages_read(message.chat)
logging.debug("Ignoring message, just the crew chatting")
else:
@ -194,7 +199,11 @@ class RelayPlugin:
)
return
""":TODO don't forward if message is the explanation message"""
outside_chat.send_msg(message)
try:
outside_chat.send_msg(message)
except Exception as e:
self.reply(message.chat, "Sending message failed.", quote=message)
raise e
def forward_to_relay_group(self, message: deltachat.Message, started_by_crew=False):
"""forward a request to a relay group; create one if it doesn't exist yet."""
@ -231,21 +240,10 @@ class RelayPlugin:
def is_relay_group(self, chat: deltachat.Chat) -> bool:
"""Check whether a chat is a relay group."""
if not chat.get_name().startswith(
"[%s] " % (self.account.get_config("addr").split("@")[0],)
):
return False # all relay groups' names begin with a [tag] with the localpart of the teamsbot's address
if (
chat.get_messages()[0].get_sender_contact()
!= self.account.get_self_contact()
):
return False # all relay groups were started by the teamsbot
if chat.is_protected():
return False # relay groups don't need to be protected, so they are not
for crew_member in self.crew.get_contacts():
if crew_member not in chat.get_contacts():
return False # all crew members have to be in any relay group
return True
for mapping in self.kvstore.get("relays"):
if mapping[1] == chat.id:
return True
return False
def get_outside_chat(self, relay_group_id: int) -> deltachat.Chat:
"""Get the corresponding outside chat for the ID of a relay group.
@ -270,3 +268,22 @@ class RelayPlugin:
if mapping[0] == outside_id:
return self.account.get_chat_by_id(mapping[1])
return None
def offboard(self, ex_admin: deltachat.Contact) -> None:
"""Remove a former crew member from all relay groups they are part of.
:param ex_admin: a contact which just got removed from the crew.
"""
for mapping in self.kvstore.get("relays"):
relay_group = self.account.get_chat_by_id(mapping[1])
if ex_admin in relay_group.get_contacts():
relay_group.remove_contact(ex_admin)
def mark_last_messages_read(self, relay_group: deltachat.Chat) -> None:
"""Mark the last incoming messages as read for a corresponding relay group.
:param relay_group: the relay group in which the messages which should marked read were forwarded.
"""
outside_chat = self.get_outside_chat(relay_group.id)
for msg in outside_chat.get_messages():
msg.mark_seen()

View file

@ -29,11 +29,11 @@ def set_log_level(verbose: int, db: str):
cls=click.Group, context_settings={"help_option_names": ["-h", "--help"]}
)
@click.pass_context
def teams_bot(ctx):
def team_bot(ctx):
"""This bot connects your team to the outside and makes it addressable."""
@teams_bot.command()
@team_bot.command()
@click.option("--email", type=str, default=None, help="the email account for the bot")
@click.option(
"--password", type=str, default=None, help="the password of the email account"
@ -41,7 +41,7 @@ def teams_bot(ctx):
@click.option(
"--dbdir",
type=str,
default="teams_bot_data",
default="team_bot_data",
help="path to the bot's database",
envvar="TEAMS_DBDIR",
)
@ -59,9 +59,9 @@ def init(ctx, email: str, password: str, dbdir: str, verbose: int):
set_log_level(verbose, delta_db)
ac = deltachat.Account(delta_db)
ac.run_account(addr=email, password=password, show_ffi=verbose)
ac.set_config("mvbox_move", "1")
ac.set_config("sentbox_watch", "0")
ac.run_account(addr=email, password=password, show_ffi=verbose)
crew_id_old = kvstore.get("crew_id")
@ -123,11 +123,11 @@ def init(ctx, email: str, password: str, dbdir: str, verbose: int):
logging.info("Successfully changed crew ID to the new group.")
@teams_bot.command()
@team_bot.command()
@click.option(
"--dbdir",
type=str,
default="teams_bot_data",
default="team_bot_data",
help="path to the bot's database",
envvar="TEAMS_DBDIR",
)
@ -157,11 +157,11 @@ def run(ctx, dbdir: str, verbose: int):
ac.wait_shutdown()
@teams_bot.command()
@team_bot.command()
@click.option(
"--dbdir",
type=str,
default="teams_bot_data",
default="team_bot_data",
help="path to the bot's database",
envvar="TEAMS_DBDIR",
)
@ -191,7 +191,7 @@ def verify_crypto(ctx, dbdir: str, verbose: int):
def main():
teams_bot(auto_envvar_prefix="TEAMS")
team_bot(auto_envvar_prefix="TEAMS")
if __name__ == "__main__":

View file

@ -15,6 +15,7 @@ def crew_help() -> str:
Start a chat:\t/start_chat alice@example.org,bob@example.org Chat_Title Hello friends!
Change the bot's name:\t/set_name Name
Change the bot's avatar:\t/set_avatar <attach image>
Generate invite link:\t\t/generate-invite
Show this help text:\t\t/help
Change the help message for outsiders:\t/set_outside_help Hello outsider
"""
@ -63,6 +64,14 @@ def set_avatar(
return "Avatar changed to this image."
def generate_invite(account: deltachat.Account) -> str:
"""Return a https://i.delta.chat invite link for chatting with the bot.
:return: the invite link, e.g.: https://i.delta.chat
"""
return account.get_setup_contact_qr()
def start_chat(
ac: deltachat.Account,
command: deltachat.Message,

View file

@ -6,21 +6,19 @@ from pyinfra import host
from pyinfra.facts.systemd import SystemdStatus
def deploy_teams_bot(
unix_user: str, bot_email: str, bot_passwd: str, dbdir: str = None
):
def deploy_team_bot(unix_user: str, bot_email: str, bot_passwd: str, dbdir: str = None):
"""Deploy TeamsBot to a UNIX user, with specified credentials
:param unix_user: the existing UNIX user of the bot
:param bot_email: the email address for the bot account
:param bot_passwd: the password for the bot's email account
:param dbdir: the directory where the bot's data will be stored. default: ~/.config/teams-bot/email@example.org
:param dbdir: the directory where the bot's data will be stored. default: ~/.config/team-bot/email@example.org
"""
clone_repo = git.repo(
name="Pull the teams-bot repository",
src="https://git.0x90.space/missytake/teams-bot",
dest=f"/home/{unix_user}/teams-bot",
name="Pull the team-bot repository",
src="https://github.com/deltachat-bot/team-bot",
dest=f"/home/{unix_user}/team-bot",
rebase=True,
_su_user=unix_user,
_use_su_login=True,
@ -28,7 +26,7 @@ def deploy_teams_bot(
if clone_repo.changed:
server.script(
name="Setup virtual environment for teams-bot",
name="Setup virtual environment for team-bot",
src=importlib.resources.files(__package__)
/ "pyinfra_assets"
/ "setup-venv.sh",
@ -37,16 +35,16 @@ def deploy_teams_bot(
)
server.shell(
name="Compile teams-bot",
name="Compile team-bot",
commands=[
f". .local/lib/teams-bot.venv/bin/activate && cd /home/{unix_user}/teams-bot && pip install ."
f". .local/lib/team-bot.venv/bin/activate && cd /home/{unix_user}/team-bot && pip install ."
],
_su_user=unix_user,
_use_su_login=True,
)
if not dbdir:
dbdir = f"/home/{unix_user}/.config/teams_bot/{bot_email}/"
dbdir = f"/home/{unix_user}/.config/team_bot/{bot_email}/"
secrets = [
f"TEAMS_DBDIR={dbdir}",
f"TEAMS_INIT_EMAIL={bot_email}",
@ -70,23 +68,18 @@ def deploy_teams_bot(
)
files.template(
name="upload teams-bot systemd unit",
name="upload team-bot systemd unit",
src=importlib.resources.files(__package__)
/ "pyinfra_assets"
/ "teams-bot.service.j2",
dest=f"/home/{unix_user}/.config/systemd/user/teams-bot.service",
/ "team-bot.service.j2",
dest=f"/home/{unix_user}/.config/systemd/user/team-bot.service",
user=unix_user,
unix_user=unix_user,
bot_email=bot_email,
)
server.shell(
name=f"enable {unix_user}'s systemd units to auto-start at boot",
commands=[f"loginctl enable-linger {unix_user}"],
)
systemd.daemon_reload(
name=f"{unix_user}: load teams-bot systemd service",
name=f"{unix_user}: load team-bot systemd service",
user_name=unix_user,
user_mode=True,
_su_user=unix_user,
@ -106,10 +99,10 @@ def deploy_teams_bot(
_use_su_login=True,
)
try:
if services["teams-bot.service"]:
if services["team-bot.service"]:
systemd.service(
name=f"{unix_user}: restart teams-bot systemd service",
service="teams-bot.service",
name=f"{unix_user}: restart team-bot systemd service",
service="team-bot.service",
running=True,
restarted=True,
user_mode=True,

View file

@ -0,0 +1,6 @@
#!/usr/bin/env bash
python3 -m venv ~/.local/lib/team-bot.venv
source ~/.local/lib/team-bot.venv/bin/activate
pip install -U pip wheel

View file

@ -1,8 +1,8 @@
[Unit]
Description=run deltachat teams-bot: {{ bot_email }}
Description=run deltachat team-bot: {{ bot_email }}
[Service]
ExecStart=/home/{{ unix_user }}/.local/lib/teams-bot.venv/bin/teams-bot run -v
ExecStart=/home/{{ unix_user }}/.local/lib/team-bot.venv/bin/team-bot run -v
EnvironmentFile=/home/{{ unix_user }}/.env
Restart=on-failure
RestartSec=5s

View file

@ -1,6 +0,0 @@
#!/usr/bin/env bash
python3 -m venv ~/.local/lib/teams-bot.venv
source ~/.local/lib/teams-bot.venv/bin/activate
pip install -U pip wheel

View file

@ -7,7 +7,7 @@ import deltachat
import pytest
from _pytest.pytester import LineMatcher
from teams_bot.bot import RelayPlugin
from team_bot.bot import RelayPlugin
class ClickRunner:
@ -65,9 +65,9 @@ def _perform_match(output, fnl):
@pytest.fixture
def cmd():
"""invoke a command line subcommand."""
from teams_bot.cli import teams_bot
from team_bot.cli import team_bot
return ClickRunner(teams_bot)
return ClickRunner(team_bot)
@pytest.fixture
@ -80,7 +80,7 @@ def tmp_file_path(request, tmpdir):
@pytest.fixture
def relaycrew(crew):
def relaycrew(crew) -> deltachat.Chat:
crew.bot.relayplugin = RelayPlugin(crew.bot, crew.kvstore)
crew.bot.add_account_plugin(crew.bot.relayplugin)
assert not crew.bot.relayplugin.is_relay_group(crew)
@ -88,28 +88,24 @@ def relaycrew(crew):
@pytest.fixture
def crew(teams_bot, teams_user, tmpdir):
from teams_bot.bot import SetupPlugin
def crew(team_bot, team_user, tmpdir) -> deltachat.Chat:
from team_bot.bot import SetupPlugin
crew = teams_bot.create_group_chat(
f"Team: {teams_bot.get_config('addr')}", verified=True
crew = team_bot.create_group_chat(
f"Team: {team_bot.get_config('addr')}", verified=True
)
setupplugin = SetupPlugin(crew.id)
teams_bot.add_account_plugin(setupplugin)
team_bot.add_account_plugin(setupplugin)
qr = crew.get_join_qr()
teams_user.qr_join_chat(qr)
team_user.qr_join_chat(qr)
setupplugin.member_added.wait(timeout=30)
crew.user = teams_user
crew.bot = teams_bot
crew.user = team_user
crew.bot = team_bot
crew.bot.setupplugin = setupplugin
# wait until old user is properly added to crew
last_message = teams_user.wait_next_incoming_message().text
while (
f"Member Me ({teams_user.get_config('addr')}) added by bot" not in last_message
):
print("User received message:", last_message)
last_message = teams_user.wait_next_incoming_message().text
team_user._evtracker.wait_securejoin_joiner_progress(1000)
team_user._evtracker.wait_next_incoming_message() # member added message
crew.kvstore = pickledb.load(tmpdir + "pickle.db", True)
crew.kvstore.set("crew_id", crew.id)
@ -117,7 +113,7 @@ def crew(teams_bot, teams_user, tmpdir):
@pytest.fixture
def teams_bot(tmpdir):
def team_bot(tmpdir) -> deltachat.Account:
ac = account(tmpdir + "/bot.sqlite", show_ffi=True)
yield ac
ac.shutdown()
@ -125,7 +121,7 @@ def teams_bot(tmpdir):
@pytest.fixture
def teams_user(tmpdir):
def team_user(tmpdir) -> deltachat.Account:
ac = account(tmpdir + "/user.sqlite")
yield ac
ac.shutdown()
@ -133,19 +129,20 @@ def teams_user(tmpdir):
@pytest.fixture
def outsider(tmpdir):
def outsider(tmpdir) -> deltachat.Account:
ac = account(tmpdir + "/outsider.sqlite")
yield ac
ac.shutdown()
ac.wait_shutdown()
def account(db_path, show_ffi=False):
def account(db_path, show_ffi=False) -> deltachat.Account:
token = os.environ.get(
"DCC_NEW_TMP_EMAIL", "https://nine.testrun.org/cgi-bin/newemail.py"
)
print(token)
ac = deltachat.Account(str(db_path))
ac._evtracker = ac.add_account_plugin(deltachat.events.FFIEventTracker(ac))
credentials = requests.post(token).json()
email = credentials["email"]
password = credentials["password"]

View file

@ -6,46 +6,90 @@ import pytest
from deltachat.capi import lib as dclib
TIMEOUT = 20
TIMEOUT = 40
@pytest.mark.timeout(60)
def test_not_relay_groups(relaycrew, outsider):
def get_user_crew(crewuser: deltachat.Account, id=11) -> deltachat.Chat:
"""Get the Team chat from the team member's point of view.
:param crewuser: the account object of the team member
:return: the chat object of the team chat
"""
for chat in crewuser.get_chats():
print(chat.id, chat.get_name())
user_crew = crewuser.get_chat_by_id(id)
assert user_crew.get_name().startswith("Team")
return user_crew
@pytest.mark.timeout(TIMEOUT)
def test_not_relay_groups(relaycrew, outsider, lp):
bot = relaycrew.bot
user = relaycrew.user
# bot <-> outsider 1:1 chat
def find_msg(ac, text):
for chat in ac.get_chats():
for msg in chat.get_messages():
if msg.text == text:
return msg
text = "outsider -> bot 1:1 chat"
lp.sec(text)
outsider_botcontact = outsider.create_contact(bot.get_config("addr"))
outsider_outside_chat = outsider.create_chat(outsider_botcontact)
outsider_outside_chat.send_text("test 1:1 message to bot")
bot_message_from_outsider = bot.wait_next_incoming_message()
outsider_outside_chat.send_text(text)
lp.sec("receiving message from outsider in 1:1 chat")
bot_message_from_outsider = bot._evtracker.wait_next_incoming_message()
bot_outside_chat = bot_message_from_outsider.chat
assert bot_message_from_outsider.text == text
assert not bot.relayplugin.is_relay_group(bot_outside_chat)
# bot <-> outsider group chat
lp.sec("leave relay group with user")
relayed_msg = find_msg(user, text)
if not relayed_msg:
relayed_msg = user._evtracker.wait_next_incoming_message()
relayed_msg.chat.remove_contact(user.get_config("addr"))
leave_msg = bot._evtracker.wait_next_incoming_message()
assert bot.relayplugin.is_relay_group(leave_msg.chat)
text = "outsider -> bot group chat"
lp.sec(text)
outsider_bot_group = outsider.create_group_chat(
"test with outsider", contacts=[outsider_botcontact]
)
outsider_bot_group.send_text("test message to outsider group")
bot_message_from_outsider = bot.wait_next_incoming_message()
outsider_bot_group.send_text(text)
lp.sec("receiving message from outsider in group chat")
bot_message_from_outsider = bot._evtracker.wait_next_incoming_message()
assert bot_message_from_outsider.text == text
assert not bot.relayplugin.is_relay_group(bot_message_from_outsider.chat)
# bot <-> user 1:1 chat
text = "user -> bot 1:1 chat"
lp.sec(text)
user_botcontact = user.create_contact(bot.get_config("addr"))
user_to_bot = user.create_chat(user_botcontact)
user_to_bot.send_text("test message to bot")
bot_message_from_user = bot.wait_next_incoming_message()
user_to_bot.send_text(text)
lp.sec("receiving message from user in 1:1 chat")
# somehow the message doesn't trigger DC_EVENT_INCOMING_MSG
# bot._evtracker.wait_next_incoming_message()
bot_message_from_user = find_msg(bot, text)
while not bot_message_from_user:
bot_message_from_user = find_msg(bot, text)
time.sleep(1)
assert bot_message_from_user.text == text
assert not bot.relayplugin.is_relay_group(bot_message_from_user.chat)
# bot <-> user group chat
text = "user -> bot group chat"
lp.sec(text)
user_group = user.create_group_chat("test with user", contacts=[user_botcontact])
user_group.send_text("testing message to user group")
bot_message_from_user = bot.wait_next_incoming_message()
user_group.send_text(text)
lp.sec("receiving message from user in group chat")
bot_message_from_user = bot._evtracker.wait_next_incoming_message()
assert bot_message_from_user.text == text
assert not bot.relayplugin.is_relay_group(bot_message_from_user.chat)
@pytest.mark.timeout(60)
@pytest.mark.timeout(TIMEOUT)
def test_relay_group_forwarding(relaycrew, outsider):
bot = relaycrew.bot
user = relaycrew.user
@ -56,18 +100,18 @@ def test_relay_group_forwarding(relaycrew, outsider):
outsider_outside_chat.send_text("test 1:1 message to bot")
# get outside chat
message_from_outsider = bot.wait_next_incoming_message()
message_from_outsider = bot._evtracker.wait_next_incoming_message()
bot_outside_chat = message_from_outsider.chat
assert not bot.relayplugin.is_relay_group(bot_outside_chat)
assert message_from_outsider.is_in_fresh()
# get relay group
user.wait_next_incoming_message() # group added message
user_forwarded_message_from_outsider = user.wait_next_incoming_message()
user_forwarded_message_from_outsider = user._evtracker.wait_next_incoming_message()
user_relay_group = user_forwarded_message_from_outsider.create_chat()
user_relay_group.send_text(
"Chatter in relay group"
) # send normal reply, not forwarded
bot_chatter_in_relay_group = bot.wait_next_incoming_message()
bot_chatter_in_relay_group = bot._evtracker.wait_next_incoming_message()
bot_relay_group = bot_chatter_in_relay_group.chat
# check if relay group has relay group properties
@ -89,9 +133,10 @@ def test_relay_group_forwarding(relaycrew, outsider):
user._dc_context, user_relay_group.id, user_direct_reply._dc_msg
)
assert sent_id == user_direct_reply.id
assert message_from_outsider.is_in_seen()
# check that direct reply was forwarded to outsider
outsider_direct_reply = outsider.wait_next_incoming_message()
outsider_direct_reply = outsider._evtracker.wait_next_incoming_message()
assert outsider_direct_reply.text == "This should be forwarded to the outsider"
assert outsider_direct_reply.chat == outsider_outside_chat
assert outsider_direct_reply.get_sender_contact() == outsider_botcontact
@ -105,7 +150,7 @@ def test_relay_group_forwarding(relaycrew, outsider):
outsider_outside_chat.send_text("Second message by outsider")
# check that outsider's reply ends up in the same chat
user_second_message_from_outsider = user.wait_next_incoming_message()
user_second_message_from_outsider = user._evtracker.wait_next_incoming_message()
assert user_second_message_from_outsider.chat == user_relay_group
# check that relay group explanation is not forwarded to outsider
@ -114,6 +159,44 @@ def test_relay_group_forwarding(relaycrew, outsider):
assert "This is the relay group for" not in msg.text
@pytest.mark.timeout(TIMEOUT)
def test_offboarding(team_bot, relaycrew, outsider, team_user):
# outsider sends message, creates relay group
outsider_botcontact = outsider.create_contact(team_bot.get_config("addr"))
outsider_outside_chat = outsider.create_chat(outsider_botcontact)
outsider_outside_chat.send_text("test 1:1 message to bot")
# get relay group
user_relay_group = team_user._evtracker.wait_next_incoming_message().chat
bot_relay_group = team_bot.get_chats()[-1]
# outsider gets added to crew
qr = relaycrew.get_join_qr()
outsider.qr_join_chat(qr)
outsider._evtracker.wait_securejoin_joiner_progress(1000)
# user kicks outsider from crew
user_crew = get_user_crew(team_user)
user_crew.remove_contact(team_user.create_contact(outsider))
team_bot._evtracker.wait_next_incoming_message()
# user leaves crew
user_crew.remove_contact(team_user)
# make sure they are also offboarded from relay group
team_bot._evtracker.wait_next_incoming_message()
team_user._evtracker.wait_next_incoming_message()
team_user._evtracker.wait_next_incoming_message()
team_user._evtracker.wait_next_incoming_message()
for contact in bot_relay_group.get_contacts():
assert team_user.get_config("addr") != contact.addr
# make sure there is no message in relay group that outsider was kicked
for msg in user_relay_group.get_messages():
print(msg.text)
assert outsider.get_config("addr") + " removed by " not in msg.text
@pytest.mark.timeout(TIMEOUT)
def test_default_outside_help(relaycrew, outsider):
bot = relaycrew.bot
user = relaycrew.user
@ -124,7 +207,7 @@ def test_default_outside_help(relaycrew, outsider):
outsider_outside_chat.send_text("/help")
# get response
outside_help_message = outsider.wait_next_incoming_message()
outside_help_message = outsider._evtracker.wait_next_incoming_message()
assert "I forward messages to the " in outside_help_message.text
# assert no relay group was created
@ -132,6 +215,7 @@ def test_default_outside_help(relaycrew, outsider):
assert len(user.get_chats()) == 1
@pytest.mark.timeout(TIMEOUT)
def test_empty_outside_help(relaycrew, outsider):
bot = relaycrew.bot
user = relaycrew.user
@ -143,7 +227,7 @@ def test_empty_outside_help(relaycrew, outsider):
assert user_crew.get_name().startswith("Team")
user_crew.send_text("/set_outside_help")
# ensure /set_outside_help arrives before sending /help
bot.wait_next_incoming_message()
bot._evtracker.wait_next_incoming_message()
# create outside chat
outsider_botcontact = outsider.create_contact(bot.get_config("addr"))
@ -151,12 +235,13 @@ def test_empty_outside_help(relaycrew, outsider):
outsider_outside_chat.send_text("/help")
# get forwarded /help message
user.wait_next_incoming_message() # group added message
user.wait_next_incoming_message() # explanation message
user_forwarded_message_from_outsider = user.wait_next_incoming_message()
user._evtracker.wait_next_incoming_message() # "Removed help message for outsiders"
user._evtracker.wait_next_incoming_message() # explanation message
user_forwarded_message_from_outsider = user._evtracker.wait_next_incoming_message()
assert user_forwarded_message_from_outsider.text == "/help"
@pytest.mark.timeout(TIMEOUT)
def test_changed_outside_help(relaycrew, outsider):
bot = relaycrew.bot
user = relaycrew.user
@ -169,7 +254,7 @@ def test_changed_outside_help(relaycrew, outsider):
outside_help_text = "Hi friend :) send me messages to chat with the team"
user_crew.send_text("/set_outside_help " + outside_help_text)
# ensure /set_outside_help arrives before sending /help
bot.wait_next_incoming_message()
bot._evtracker.wait_next_incoming_message()
# create outside chat
outsider_botcontact = outsider.create_contact(bot.get_config("addr"))
@ -177,7 +262,7 @@ def test_changed_outside_help(relaycrew, outsider):
outsider_outside_chat.send_text("/help")
# get response
outside_help_message = outsider.wait_next_incoming_message()
outside_help_message = outsider._evtracker.wait_next_incoming_message()
assert outside_help_message.text == outside_help_text
# assert no relay group was created
@ -185,6 +270,7 @@ def test_changed_outside_help(relaycrew, outsider):
assert len(user.get_chats()) == 1
@pytest.mark.timeout(TIMEOUT)
def test_change_avatar(relaycrew):
bot = relaycrew.bot
user = relaycrew.user
@ -202,25 +288,23 @@ def test_change_avatar(relaycrew):
pytest.skip(f"example image not available: {example_png_path}")
# set avatar to example image
for chat in user.get_chats():
print(chat.id, chat.get_name())
user_crew = user.get_chat_by_id(11)
assert user_crew.get_name().startswith("Team")
user_crew = get_user_crew(user)
msg = deltachat.Message.new_empty(user, "image")
msg.set_text("/set_avatar")
msg.set_file(example_png_path)
sent_id = dclib.dc_send_msg(user._dc_context, user_crew.id, msg._dc_msg)
assert sent_id == msg.id
group_avatar_changed_msg = user.wait_next_incoming_message()
group_avatar_changed_msg = user._evtracker.wait_next_incoming_message()
assert "Group image changed" in group_avatar_changed_msg.text
assert user_crew.get_profile_image()
confirmation_msg = user.wait_next_incoming_message()
confirmation_msg = user._evtracker.wait_next_incoming_message()
assert confirmation_msg.text == "Avatar changed to this image."
assert botcontact.get_profile_image()
@pytest.mark.timeout(TIMEOUT * 2)
def test_forward_sending_errors_to_relay_group(relaycrew):
usercrew = relaycrew.user.get_chats()[-1]
usercrew.send_text("/start_chat alice@example.org This_Message_will_fail test")
@ -237,7 +321,9 @@ def test_forward_sending_errors_to_relay_group(relaycrew):
while len(relaycrew.user.get_chats()) < 2 and int(time.time()) < begin + TIMEOUT:
time.sleep(0.1)
relay_group = relaycrew.user.get_chats()[-2]
for chat in relaycrew.user.get_chats():
if "This Message will fail" in chat.get_name():
relay_group = chat
while len(relay_group.get_messages()) < 3 and int(time.time()) < begin + TIMEOUT:
print(relay_group.get_messages()[-1].text)
@ -250,3 +336,21 @@ def test_forward_sending_errors_to_relay_group(relaycrew):
"Invalid unencrypted mail to <alice@example.org>"
in relay_group.get_messages()[-1].text
)
@pytest.mark.timeout(TIMEOUT)
def test_public_invite(relaycrew, outsider):
crew = get_user_crew(relaycrew.user)
crew.send_text("/generate-invite")
result = relaycrew.user._evtracker.wait_next_incoming_message()
# assert result.filename
# assert result.text.startswith("https://i.delta.chat")
# qr = result.filename
# invite = "OPENPGP4FPR:" + result.text[22::]
chat = outsider.qr_setup_contact(result.text)
outsider._evtracker.wait_securejoin_joiner_progress(1000)
while not chat.is_protected():
print(chat.get_messages()[-1].text)
time.sleep(1)

View file

@ -2,7 +2,7 @@ def test_help(cmd):
cmd.run_ok(
[],
"""
Usage: teams-bot [OPTIONS] COMMAND [ARGS]...
Usage: team-bot [OPTIONS] COMMAND [ARGS]...
* -h, --help Show this message and exit.
* init Scan a QR code to create a crew and join it
* run Run the bot, so it relays messages from and to the outside