mirror of
https://github.com/chatmail/core.git
synced 2026-04-17 21:46:35 +03:00
Refine Python CI
Add lint environment to `deltachat-rpc-client/` and set line length to 120, same as in `python/`. Switch from flake8 to ruff. Fix ruff warnings.
This commit is contained in:
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -144,7 +144,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
|
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
|
||||||
working-directory: deltachat-rpc-client
|
working-directory: deltachat-rpc-client
|
||||||
run: tox -e py3
|
run: tox -e py3,lint
|
||||||
|
|
||||||
- name: install pypy
|
- name: install pypy
|
||||||
if: ${{ matrix.python }}
|
if: ${{ matrix.python }}
|
||||||
|
|||||||
@@ -27,9 +27,7 @@ async def log_error(event):
|
|||||||
|
|
||||||
@hooks.on(events.MemberListChanged)
|
@hooks.on(events.MemberListChanged)
|
||||||
async def on_memberlist_changed(event):
|
async def on_memberlist_changed(event):
|
||||||
logging.info(
|
logging.info("member %s was %s", event.member, "added" if event.member_added else "removed")
|
||||||
"member %s was %s", event.member, "added" if event.member_added else "removed"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@hooks.on(events.GroupImageChanged)
|
@hooks.on(events.GroupImageChanged)
|
||||||
|
|||||||
@@ -27,3 +27,13 @@ deltachat_rpc_client = [
|
|||||||
|
|
||||||
[project.entry-points.pytest11]
|
[project.entry-points.pytest11]
|
||||||
"deltachat_rpc_client.pytestplugin" = "deltachat_rpc_client.pytestplugin"
|
"deltachat_rpc_client.pytestplugin" = "deltachat_rpc_client.pytestplugin"
|
||||||
|
|
||||||
|
[tool.black]
|
||||||
|
line-length = 120
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
select = ["E", "F", "W", "N", "YTT", "B", "C4", "ISC", "ICN", "PT", "RET", "SIM", "TID", "ARG", "DTZ", "ERA", "PLC", "PLE", "PLW", "PIE", "COM"]
|
||||||
|
line-length = 120
|
||||||
|
|
||||||
|
[tool.isort]
|
||||||
|
profile = "black"
|
||||||
|
|||||||
@@ -8,3 +8,18 @@ from .contact import Contact
|
|||||||
from .deltachat import DeltaChat
|
from .deltachat import DeltaChat
|
||||||
from .message import Message
|
from .message import Message
|
||||||
from .rpc import Rpc
|
from .rpc import Rpc
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"Account",
|
||||||
|
"AttrDict",
|
||||||
|
"Bot",
|
||||||
|
"Chat",
|
||||||
|
"Client",
|
||||||
|
"Contact",
|
||||||
|
"DeltaChat",
|
||||||
|
"EventType",
|
||||||
|
"Message",
|
||||||
|
"Rpc",
|
||||||
|
"run_bot_cli",
|
||||||
|
"run_client_cli",
|
||||||
|
]
|
||||||
|
|||||||
@@ -30,12 +30,7 @@ class AttrDict(dict):
|
|||||||
"""Dictionary that allows accessing values usin the "dot notation" as attributes."""
|
"""Dictionary that allows accessing values usin the "dot notation" as attributes."""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs) -> None:
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
super().__init__(
|
super().__init__({_camel_to_snake(key): _to_attrdict(value) for key, value in dict(*args, **kwargs).items()})
|
||||||
{
|
|
||||||
_camel_to_snake(key): _to_attrdict(value)
|
|
||||||
for key, value in dict(*args, **kwargs).items()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
if attr in self:
|
if attr in self:
|
||||||
@@ -51,7 +46,7 @@ class AttrDict(dict):
|
|||||||
async def run_client_cli(
|
async def run_client_cli(
|
||||||
hooks: Optional[Iterable[Tuple[Callable, Union[type, "EventFilter"]]]] = None,
|
hooks: Optional[Iterable[Tuple[Callable, Union[type, "EventFilter"]]]] = None,
|
||||||
argv: Optional[list] = None,
|
argv: Optional[list] = None,
|
||||||
**kwargs
|
**kwargs,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Run a simple command line app, using the given hooks.
|
"""Run a simple command line app, using the given hooks.
|
||||||
|
|
||||||
@@ -65,7 +60,7 @@ async def run_client_cli(
|
|||||||
async def run_bot_cli(
|
async def run_bot_cli(
|
||||||
hooks: Optional[Iterable[Tuple[Callable, Union[type, "EventFilter"]]]] = None,
|
hooks: Optional[Iterable[Tuple[Callable, Union[type, "EventFilter"]]]] = None,
|
||||||
argv: Optional[list] = None,
|
argv: Optional[list] = None,
|
||||||
**kwargs
|
**kwargs,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Run a simple bot command line using the given hooks.
|
"""Run a simple bot command line using the given hooks.
|
||||||
|
|
||||||
@@ -80,7 +75,7 @@ async def _run_cli(
|
|||||||
client_type: Type["Client"],
|
client_type: Type["Client"],
|
||||||
hooks: Optional[Iterable[Tuple[Callable, Union[type, "EventFilter"]]]] = None,
|
hooks: Optional[Iterable[Tuple[Callable, Union[type, "EventFilter"]]]] = None,
|
||||||
argv: Optional[list] = None,
|
argv: Optional[list] = None,
|
||||||
**kwargs
|
**kwargs,
|
||||||
) -> None:
|
) -> None:
|
||||||
from .deltachat import DeltaChat
|
from .deltachat import DeltaChat
|
||||||
from .rpc import Rpc
|
from .rpc import Rpc
|
||||||
@@ -107,12 +102,9 @@ async def _run_cli(
|
|||||||
client = client_type(account, hooks)
|
client = client_type(account, hooks)
|
||||||
client.logger.debug("Running deltachat core %s", core_version)
|
client.logger.debug("Running deltachat core %s", core_version)
|
||||||
if not await client.is_configured():
|
if not await client.is_configured():
|
||||||
assert (
|
assert args.email, "Account is not configured and email must be provided"
|
||||||
args.email and args.password
|
assert args.password, "Account is not configured and password must be provided"
|
||||||
), "Account is not configured and email and password must be provided"
|
asyncio.create_task(client.configure(email=args.email, password=args.password))
|
||||||
asyncio.create_task(
|
|
||||||
client.configure(email=args.email, password=args.password)
|
|
||||||
)
|
|
||||||
await client.run_forever()
|
await client.run_forever()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -89,9 +89,7 @@ class Account:
|
|||||||
"""Configure an account."""
|
"""Configure an account."""
|
||||||
await self._rpc.configure(self.id)
|
await self._rpc.configure(self.id)
|
||||||
|
|
||||||
async def create_contact(
|
async def create_contact(self, obj: Union[int, str, Contact], name: Optional[str] = None) -> Contact:
|
||||||
self, obj: Union[int, str, Contact], name: Optional[str] = None
|
|
||||||
) -> Contact:
|
|
||||||
"""Create a new Contact or return an existing one.
|
"""Create a new Contact or return an existing one.
|
||||||
|
|
||||||
Calling this method will always result in the same
|
Calling this method will always result in the same
|
||||||
@@ -120,10 +118,7 @@ class Account:
|
|||||||
async def get_blocked_contacts(self) -> List[AttrDict]:
|
async def get_blocked_contacts(self) -> List[AttrDict]:
|
||||||
"""Return a list with snapshots of all blocked contacts."""
|
"""Return a list with snapshots of all blocked contacts."""
|
||||||
contacts = await self._rpc.get_blocked_contacts(self.id)
|
contacts = await self._rpc.get_blocked_contacts(self.id)
|
||||||
return [
|
return [AttrDict(contact=Contact(self, contact["id"]), **contact) for contact in contacts]
|
||||||
AttrDict(contact=Contact(self, contact["id"]), **contact)
|
|
||||||
for contact in contacts
|
|
||||||
]
|
|
||||||
|
|
||||||
async def get_contacts(
|
async def get_contacts(
|
||||||
self,
|
self,
|
||||||
@@ -148,10 +143,7 @@ class Account:
|
|||||||
|
|
||||||
if snapshot:
|
if snapshot:
|
||||||
contacts = await self._rpc.get_contacts(self.id, flags, query)
|
contacts = await self._rpc.get_contacts(self.id, flags, query)
|
||||||
return [
|
return [AttrDict(contact=Contact(self, contact["id"]), **contact) for contact in contacts]
|
||||||
AttrDict(contact=Contact(self, contact["id"]), **contact)
|
|
||||||
for contact in contacts
|
|
||||||
]
|
|
||||||
contacts = await self._rpc.get_contact_ids(self.id, flags, query)
|
contacts = await self._rpc.get_contact_ids(self.id, flags, query)
|
||||||
return [Contact(self, contact_id) for contact_id in contacts]
|
return [Contact(self, contact_id) for contact_id in contacts]
|
||||||
|
|
||||||
@@ -192,9 +184,7 @@ class Account:
|
|||||||
if alldone_hint:
|
if alldone_hint:
|
||||||
flags |= ChatlistFlag.ADD_ALLDONE_HINT
|
flags |= ChatlistFlag.ADD_ALLDONE_HINT
|
||||||
|
|
||||||
entries = await self._rpc.get_chatlist_entries(
|
entries = await self._rpc.get_chatlist_entries(self.id, flags, query, contact and contact.id)
|
||||||
self.id, flags, query, contact and contact.id
|
|
||||||
)
|
|
||||||
if not snapshot:
|
if not snapshot:
|
||||||
return [Chat(self, entry[0]) for entry in entries]
|
return [Chat(self, entry[0]) for entry in entries]
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ class Chat:
|
|||||||
"""
|
"""
|
||||||
if duration is not None:
|
if duration is not None:
|
||||||
assert duration > 0, "Invalid duration"
|
assert duration > 0, "Invalid duration"
|
||||||
dur: Union[str, dict] = dict(Until=duration)
|
dur: Union[str, dict] = {"Until": duration}
|
||||||
else:
|
else:
|
||||||
dur = "Forever"
|
dur = "Forever"
|
||||||
await self._rpc.set_chat_mute_duration(self.account.id, self.id, dur)
|
await self._rpc.set_chat_mute_duration(self.account.id, self.id, dur)
|
||||||
@@ -74,27 +74,19 @@ class Chat:
|
|||||||
|
|
||||||
async def pin(self) -> None:
|
async def pin(self) -> None:
|
||||||
"""Pin this chat."""
|
"""Pin this chat."""
|
||||||
await self._rpc.set_chat_visibility(
|
await self._rpc.set_chat_visibility(self.account.id, self.id, ChatVisibility.PINNED)
|
||||||
self.account.id, self.id, ChatVisibility.PINNED
|
|
||||||
)
|
|
||||||
|
|
||||||
async def unpin(self) -> None:
|
async def unpin(self) -> None:
|
||||||
"""Unpin this chat."""
|
"""Unpin this chat."""
|
||||||
await self._rpc.set_chat_visibility(
|
await self._rpc.set_chat_visibility(self.account.id, self.id, ChatVisibility.NORMAL)
|
||||||
self.account.id, self.id, ChatVisibility.NORMAL
|
|
||||||
)
|
|
||||||
|
|
||||||
async def archive(self) -> None:
|
async def archive(self) -> None:
|
||||||
"""Archive this chat."""
|
"""Archive this chat."""
|
||||||
await self._rpc.set_chat_visibility(
|
await self._rpc.set_chat_visibility(self.account.id, self.id, ChatVisibility.ARCHIVED)
|
||||||
self.account.id, self.id, ChatVisibility.ARCHIVED
|
|
||||||
)
|
|
||||||
|
|
||||||
async def unarchive(self) -> None:
|
async def unarchive(self) -> None:
|
||||||
"""Unarchive this chat."""
|
"""Unarchive this chat."""
|
||||||
await self._rpc.set_chat_visibility(
|
await self._rpc.set_chat_visibility(self.account.id, self.id, ChatVisibility.NORMAL)
|
||||||
self.account.id, self.id, ChatVisibility.NORMAL
|
|
||||||
)
|
|
||||||
|
|
||||||
async def set_name(self, name: str) -> None:
|
async def set_name(self, name: str) -> None:
|
||||||
"""Set name of this chat."""
|
"""Set name of this chat."""
|
||||||
@@ -133,9 +125,7 @@ class Chat:
|
|||||||
if isinstance(quoted_msg, Message):
|
if isinstance(quoted_msg, Message):
|
||||||
quoted_msg = quoted_msg.id
|
quoted_msg = quoted_msg.id
|
||||||
|
|
||||||
msg_id, _ = await self._rpc.misc_send_msg(
|
msg_id, _ = await self._rpc.misc_send_msg(self.account.id, self.id, text, file, location, quoted_msg)
|
||||||
self.account.id, self.id, text, file, location, quoted_msg
|
|
||||||
)
|
|
||||||
return Message(self.account, msg_id)
|
return Message(self.account, msg_id)
|
||||||
|
|
||||||
async def send_text(self, text: str) -> Message:
|
async def send_text(self, text: str) -> Message:
|
||||||
@@ -241,23 +231,17 @@ class Chat:
|
|||||||
timestamp_to: Optional[datetime] = None,
|
timestamp_to: Optional[datetime] = None,
|
||||||
) -> List[AttrDict]:
|
) -> List[AttrDict]:
|
||||||
"""Get list of location snapshots for the given contact in the given timespan."""
|
"""Get list of location snapshots for the given contact in the given timespan."""
|
||||||
time_from = (
|
time_from = calendar.timegm(timestamp_from.utctimetuple()) if timestamp_from else 0
|
||||||
calendar.timegm(timestamp_from.utctimetuple()) if timestamp_from else 0
|
|
||||||
)
|
|
||||||
time_to = calendar.timegm(timestamp_to.utctimetuple()) if timestamp_to else 0
|
time_to = calendar.timegm(timestamp_to.utctimetuple()) if timestamp_to else 0
|
||||||
contact_id = contact.id if contact else 0
|
contact_id = contact.id if contact else 0
|
||||||
|
|
||||||
result = await self._rpc.get_locations(
|
result = await self._rpc.get_locations(self.account.id, self.id, contact_id, time_from, time_to)
|
||||||
self.account.id, self.id, contact_id, time_from, time_to
|
|
||||||
)
|
|
||||||
locations = []
|
locations = []
|
||||||
contacts: Dict[int, Contact] = {}
|
contacts: Dict[int, Contact] = {}
|
||||||
for loc in result:
|
for loc in result:
|
||||||
loc = AttrDict(loc)
|
loc = AttrDict(loc)
|
||||||
loc["chat"] = self
|
loc["chat"] = self
|
||||||
loc["contact"] = contacts.setdefault(
|
loc["contact"] = contacts.setdefault(loc.contact_id, Contact(self.account, loc.contact_id))
|
||||||
loc.contact_id, Contact(self.account, loc.contact_id)
|
|
||||||
)
|
|
||||||
loc["message"] = Message(self.account, loc.msg_id)
|
loc["message"] = Message(self.account, loc.msg_id)
|
||||||
locations.append(loc)
|
locations.append(loc)
|
||||||
return locations
|
return locations
|
||||||
|
|||||||
@@ -47,15 +47,11 @@ class Client:
|
|||||||
self._should_process_messages = 0
|
self._should_process_messages = 0
|
||||||
self.add_hooks(hooks or [])
|
self.add_hooks(hooks or [])
|
||||||
|
|
||||||
def add_hooks(
|
def add_hooks(self, hooks: Iterable[Tuple[Callable, Union[type, EventFilter]]]) -> None:
|
||||||
self, hooks: Iterable[Tuple[Callable, Union[type, EventFilter]]]
|
|
||||||
) -> None:
|
|
||||||
for hook, event in hooks:
|
for hook, event in hooks:
|
||||||
self.add_hook(hook, event)
|
self.add_hook(hook, event)
|
||||||
|
|
||||||
def add_hook(
|
def add_hook(self, hook: Callable, event: Union[type, EventFilter] = RawEvent) -> None:
|
||||||
self, hook: Callable, event: Union[type, EventFilter] = RawEvent
|
|
||||||
) -> None:
|
|
||||||
"""Register hook for the given event filter."""
|
"""Register hook for the given event filter."""
|
||||||
if isinstance(event, type):
|
if isinstance(event, type):
|
||||||
event = event()
|
event = event()
|
||||||
@@ -64,7 +60,7 @@ class Client:
|
|||||||
isinstance(
|
isinstance(
|
||||||
event,
|
event,
|
||||||
(NewMessage, MemberListChanged, GroupImageChanged, GroupNameChanged),
|
(NewMessage, MemberListChanged, GroupImageChanged, GroupNameChanged),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
self._hooks.setdefault(type(event), set()).add((hook, event))
|
self._hooks.setdefault(type(event), set()).add((hook, event))
|
||||||
|
|
||||||
@@ -76,7 +72,7 @@ class Client:
|
|||||||
isinstance(
|
isinstance(
|
||||||
event,
|
event,
|
||||||
(NewMessage, MemberListChanged, GroupImageChanged, GroupNameChanged),
|
(NewMessage, MemberListChanged, GroupImageChanged, GroupNameChanged),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
self._hooks.get(type(event), set()).remove((hook, event))
|
self._hooks.get(type(event), set()).remove((hook, event))
|
||||||
|
|
||||||
@@ -95,9 +91,7 @@ class Client:
|
|||||||
"""Process events forever."""
|
"""Process events forever."""
|
||||||
await self.run_until(lambda _: False)
|
await self.run_until(lambda _: False)
|
||||||
|
|
||||||
async def run_until(
|
async def run_until(self, func: Callable[[AttrDict], Union[bool, Coroutine]]) -> AttrDict:
|
||||||
self, func: Callable[[AttrDict], Union[bool, Coroutine]]
|
|
||||||
) -> AttrDict:
|
|
||||||
"""Process events until the given callable evaluates to True.
|
"""Process events until the given callable evaluates to True.
|
||||||
|
|
||||||
The callable should accept an AttrDict object representing the
|
The callable should accept an AttrDict object representing the
|
||||||
@@ -122,9 +116,7 @@ class Client:
|
|||||||
if stop:
|
if stop:
|
||||||
return event
|
return event
|
||||||
|
|
||||||
async def _on_event(
|
async def _on_event(self, event: AttrDict, filter_type: Type[EventFilter] = RawEvent) -> None:
|
||||||
self, event: AttrDict, filter_type: Type[EventFilter] = RawEvent
|
|
||||||
) -> None:
|
|
||||||
for hook, evfilter in self._hooks.get(filter_type, []):
|
for hook, evfilter in self._hooks.get(filter_type, []):
|
||||||
if await evfilter.filter(event):
|
if await evfilter.filter(event):
|
||||||
try:
|
try:
|
||||||
@@ -133,11 +125,7 @@ class Client:
|
|||||||
self.logger.exception(ex)
|
self.logger.exception(ex)
|
||||||
|
|
||||||
async def _parse_command(self, event: AttrDict) -> None:
|
async def _parse_command(self, event: AttrDict) -> None:
|
||||||
cmds = [
|
cmds = [hook[1].command for hook in self._hooks.get(NewMessage, []) if hook[1].command]
|
||||||
hook[1].command
|
|
||||||
for hook in self._hooks.get(NewMessage, [])
|
|
||||||
if hook[1].command
|
|
||||||
]
|
|
||||||
parts = event.message_snapshot.text.split(maxsplit=1)
|
parts = event.message_snapshot.text.split(maxsplit=1)
|
||||||
payload = parts[1] if len(parts) > 1 else ""
|
payload = parts[1] if len(parts) > 1 else ""
|
||||||
cmd = parts.pop(0)
|
cmd = parts.pop(0)
|
||||||
@@ -202,11 +190,7 @@ class Client:
|
|||||||
for message in await self.account.get_fresh_messages_in_arrival_order():
|
for message in await self.account.get_fresh_messages_in_arrival_order():
|
||||||
snapshot = await message.get_snapshot()
|
snapshot = await message.get_snapshot()
|
||||||
await self._on_new_msg(snapshot)
|
await self._on_new_msg(snapshot)
|
||||||
if (
|
if snapshot.is_info and snapshot.system_message_type != SystemMessageType.WEBXDC_INFO_MESSAGE:
|
||||||
snapshot.is_info
|
|
||||||
and snapshot.system_message_type
|
|
||||||
!= SystemMessageType.WEBXDC_INFO_MESSAGE
|
|
||||||
):
|
|
||||||
await self._handle_info_msg(snapshot)
|
await self._handle_info_msg(snapshot)
|
||||||
await snapshot.message.mark_seen()
|
await snapshot.message.mark_seen()
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from .const import EventType
|
|||||||
|
|
||||||
def _tuple_of(obj, type_: type) -> tuple:
|
def _tuple_of(obj, type_: type) -> tuple:
|
||||||
if not obj:
|
if not obj:
|
||||||
return tuple()
|
return ()
|
||||||
if isinstance(obj, type_):
|
if isinstance(obj, type_):
|
||||||
obj = (obj,)
|
obj = (obj,)
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ class EventFilter(ABC):
|
|||||||
"""Return True if two event filters are equal."""
|
"""Return True if two event filters are equal."""
|
||||||
|
|
||||||
def __ne__(self, other):
|
def __ne__(self, other):
|
||||||
return not self.__eq__(other)
|
return not self == other
|
||||||
|
|
||||||
async def _call_func(self, event) -> bool:
|
async def _call_func(self, event) -> bool:
|
||||||
if not self.func:
|
if not self.func:
|
||||||
@@ -65,9 +65,7 @@ class RawEvent(EventFilter):
|
|||||||
should be dispatched or not.
|
should be dispatched or not.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, types: Union[None, EventType, Iterable[EventType]] = None, **kwargs):
|
||||||
self, types: Union[None, EventType, Iterable[EventType]] = None, **kwargs
|
|
||||||
):
|
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
try:
|
try:
|
||||||
self.types = _tuple_of(types, EventType)
|
self.types = _tuple_of(types, EventType)
|
||||||
|
|||||||
@@ -49,22 +49,14 @@ class Message:
|
|||||||
"""Mark the message as seen."""
|
"""Mark the message as seen."""
|
||||||
await self._rpc.markseen_msgs(self.account.id, [self.id])
|
await self._rpc.markseen_msgs(self.account.id, [self.id])
|
||||||
|
|
||||||
async def send_webxdc_status_update(
|
async def send_webxdc_status_update(self, update: Union[dict, str], description: str) -> None:
|
||||||
self, update: Union[dict, str], description: str
|
|
||||||
) -> None:
|
|
||||||
"""Send a webxdc status update. This message must be a webxdc."""
|
"""Send a webxdc status update. This message must be a webxdc."""
|
||||||
if not isinstance(update, str):
|
if not isinstance(update, str):
|
||||||
update = json.dumps(update)
|
update = json.dumps(update)
|
||||||
await self._rpc.send_webxdc_status_update(
|
await self._rpc.send_webxdc_status_update(self.account.id, self.id, update, description)
|
||||||
self.account.id, self.id, update, description
|
|
||||||
)
|
|
||||||
|
|
||||||
async def get_webxdc_status_updates(self, last_known_serial: int = 0) -> list:
|
async def get_webxdc_status_updates(self, last_known_serial: int = 0) -> list:
|
||||||
return json.loads(
|
return json.loads(await self._rpc.get_webxdc_status_updates(self.account.id, self.id, last_known_serial))
|
||||||
await self._rpc.get_webxdc_status_updates(
|
|
||||||
self.account.id, self.id, last_known_serial
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
async def get_webxdc_info(self) -> dict:
|
async def get_webxdc_info(self) -> dict:
|
||||||
return await self._rpc.get_webxdc_info(self.account.id, self.id)
|
return await self._rpc.get_webxdc_info(self.account.id, self.id)
|
||||||
|
|||||||
@@ -67,9 +67,7 @@ class ACFactory:
|
|||||||
) -> Message:
|
) -> Message:
|
||||||
if not from_account:
|
if not from_account:
|
||||||
from_account = (await self.get_online_accounts(1))[0]
|
from_account = (await self.get_online_accounts(1))[0]
|
||||||
to_contact = await from_account.create_contact(
|
to_contact = await from_account.create_contact(await to_account.get_config("addr"))
|
||||||
await to_account.get_config("addr")
|
|
||||||
)
|
|
||||||
if group:
|
if group:
|
||||||
to_chat = await from_account.create_group(group)
|
to_chat = await from_account.create_group(group)
|
||||||
await to_chat.add_contact(to_contact)
|
await to_chat.add_contact(to_contact)
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class Rpc:
|
|||||||
"deltachat-rpc-server",
|
"deltachat-rpc-server",
|
||||||
stdin=asyncio.subprocess.PIPE,
|
stdin=asyncio.subprocess.PIPE,
|
||||||
stdout=asyncio.subprocess.PIPE,
|
stdout=asyncio.subprocess.PIPE,
|
||||||
**self._kwargs
|
**self._kwargs,
|
||||||
)
|
)
|
||||||
self.id = 0
|
self.id = 0
|
||||||
self.event_queues = {}
|
self.event_queues = {}
|
||||||
@@ -46,7 +46,7 @@ class Rpc:
|
|||||||
await self.start()
|
await self.start()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
async def __aexit__(self, exc_type, exc, tb):
|
async def __aexit__(self, _exc_type, _exc, _tb):
|
||||||
await self.close()
|
await self.close()
|
||||||
|
|
||||||
async def reader_loop(self) -> None:
|
async def reader_loop(self) -> None:
|
||||||
@@ -97,5 +97,6 @@ class Rpc:
|
|||||||
raise JsonRpcError(response["error"])
|
raise JsonRpcError(response["error"])
|
||||||
if "result" in response:
|
if "result" in response:
|
||||||
return response["result"]
|
return response["result"]
|
||||||
|
return None
|
||||||
|
|
||||||
return method
|
return method
|
||||||
|
|||||||
@@ -6,14 +6,14 @@ from deltachat_rpc_client import EventType, events
|
|||||||
from deltachat_rpc_client.rpc import JsonRpcError
|
from deltachat_rpc_client.rpc import JsonRpcError
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio()
|
||||||
async def test_system_info(rpc) -> None:
|
async def test_system_info(rpc) -> None:
|
||||||
system_info = await rpc.get_system_info()
|
system_info = await rpc.get_system_info()
|
||||||
assert "arch" in system_info
|
assert "arch" in system_info
|
||||||
assert "deltachat_core_version" in system_info
|
assert "deltachat_core_version" in system_info
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio()
|
||||||
async def test_email_address_validity(rpc) -> None:
|
async def test_email_address_validity(rpc) -> None:
|
||||||
valid_addresses = [
|
valid_addresses = [
|
||||||
"email@example.com",
|
"email@example.com",
|
||||||
@@ -27,7 +27,7 @@ async def test_email_address_validity(rpc) -> None:
|
|||||||
assert not await rpc.check_email_validity(addr)
|
assert not await rpc.check_email_validity(addr)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio()
|
||||||
async def test_acfactory(acfactory) -> None:
|
async def test_acfactory(acfactory) -> None:
|
||||||
account = await acfactory.new_configured_account()
|
account = await acfactory.new_configured_account()
|
||||||
while True:
|
while True:
|
||||||
@@ -41,7 +41,7 @@ async def test_acfactory(acfactory) -> None:
|
|||||||
print("Successful configuration")
|
print("Successful configuration")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio()
|
||||||
async def test_configure_starttls(acfactory) -> None:
|
async def test_configure_starttls(acfactory) -> None:
|
||||||
account = await acfactory.new_preconfigured_account()
|
account = await acfactory.new_preconfigured_account()
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ async def test_configure_starttls(acfactory) -> None:
|
|||||||
assert await account.is_configured()
|
assert await account.is_configured()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio()
|
||||||
async def test_account(acfactory) -> None:
|
async def test_account(acfactory) -> None:
|
||||||
alice, bob = await acfactory.get_online_accounts(2)
|
alice, bob = await acfactory.get_online_accounts(2)
|
||||||
|
|
||||||
@@ -111,7 +111,7 @@ async def test_account(acfactory) -> None:
|
|||||||
await alice.stop_io()
|
await alice.stop_io()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio()
|
||||||
async def test_chat(acfactory) -> None:
|
async def test_chat(acfactory) -> None:
|
||||||
alice, bob = await acfactory.get_online_accounts(2)
|
alice, bob = await acfactory.get_online_accounts(2)
|
||||||
|
|
||||||
@@ -177,7 +177,7 @@ async def test_chat(acfactory) -> None:
|
|||||||
await group.get_locations()
|
await group.get_locations()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio()
|
||||||
async def test_contact(acfactory) -> None:
|
async def test_contact(acfactory) -> None:
|
||||||
alice, bob = await acfactory.get_online_accounts(2)
|
alice, bob = await acfactory.get_online_accounts(2)
|
||||||
|
|
||||||
@@ -195,7 +195,7 @@ async def test_contact(acfactory) -> None:
|
|||||||
await alice_contact_bob.create_chat()
|
await alice_contact_bob.create_chat()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio()
|
||||||
async def test_message(acfactory) -> None:
|
async def test_message(acfactory) -> None:
|
||||||
alice, bob = await acfactory.get_online_accounts(2)
|
alice, bob = await acfactory.get_online_accounts(2)
|
||||||
|
|
||||||
@@ -226,7 +226,7 @@ async def test_message(acfactory) -> None:
|
|||||||
await message.send_reaction("😎")
|
await message.send_reaction("😎")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio()
|
||||||
async def test_bot(acfactory) -> None:
|
async def test_bot(acfactory) -> None:
|
||||||
mock = MagicMock()
|
mock = MagicMock()
|
||||||
user = (await acfactory.get_online_accounts(1))[0]
|
user = (await acfactory.get_online_accounts(1))[0]
|
||||||
@@ -237,25 +237,20 @@ async def test_bot(acfactory) -> None:
|
|||||||
|
|
||||||
hook = lambda e: mock.hook(e.msg_id), events.RawEvent(EventType.INCOMING_MSG)
|
hook = lambda e: mock.hook(e.msg_id), events.RawEvent(EventType.INCOMING_MSG)
|
||||||
bot.add_hook(*hook)
|
bot.add_hook(*hook)
|
||||||
event = await acfactory.process_message(
|
event = await acfactory.process_message(from_account=user, to_client=bot, text="Hello!")
|
||||||
from_account=user, to_client=bot, text="Hello!"
|
|
||||||
)
|
|
||||||
mock.hook.assert_called_once_with(event.msg_id)
|
mock.hook.assert_called_once_with(event.msg_id)
|
||||||
bot.remove_hook(*hook)
|
bot.remove_hook(*hook)
|
||||||
|
|
||||||
track = lambda e: mock.hook(e.message_snapshot.id)
|
def track(e):
|
||||||
|
mock.hook(e.message_snapshot.id)
|
||||||
|
|
||||||
mock.hook.reset_mock()
|
mock.hook.reset_mock()
|
||||||
hook = track, events.NewMessage(r"hello")
|
hook = track, events.NewMessage(r"hello")
|
||||||
bot.add_hook(*hook)
|
bot.add_hook(*hook)
|
||||||
bot.add_hook(track, events.NewMessage(command="/help"))
|
bot.add_hook(track, events.NewMessage(command="/help"))
|
||||||
event = await acfactory.process_message(
|
event = await acfactory.process_message(from_account=user, to_client=bot, text="hello")
|
||||||
from_account=user, to_client=bot, text="hello"
|
|
||||||
)
|
|
||||||
mock.hook.assert_called_with(event.msg_id)
|
mock.hook.assert_called_with(event.msg_id)
|
||||||
event = await acfactory.process_message(
|
event = await acfactory.process_message(from_account=user, to_client=bot, text="hello!")
|
||||||
from_account=user, to_client=bot, text="hello!"
|
|
||||||
)
|
|
||||||
mock.hook.assert_called_with(event.msg_id)
|
mock.hook.assert_called_with(event.msg_id)
|
||||||
await acfactory.process_message(from_account=user, to_client=bot, text="hey!")
|
await acfactory.process_message(from_account=user, to_client=bot, text="hey!")
|
||||||
assert len(mock.hook.mock_calls) == 2
|
assert len(mock.hook.mock_calls) == 2
|
||||||
@@ -263,7 +258,5 @@ async def test_bot(acfactory) -> None:
|
|||||||
|
|
||||||
mock.hook.reset_mock()
|
mock.hook.reset_mock()
|
||||||
await acfactory.process_message(from_account=user, to_client=bot, text="hello")
|
await acfactory.process_message(from_account=user, to_client=bot, text="hello")
|
||||||
event = await acfactory.process_message(
|
event = await acfactory.process_message(from_account=user, to_client=bot, text="/help")
|
||||||
from_account=user, to_client=bot, text="/help"
|
|
||||||
)
|
|
||||||
mock.hook.assert_called_once_with(event.msg_id)
|
mock.hook.assert_called_once_with(event.msg_id)
|
||||||
|
|||||||
@@ -3,16 +3,14 @@ import pytest
|
|||||||
from deltachat_rpc_client import EventType
|
from deltachat_rpc_client import EventType
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio()
|
||||||
async def test_webxdc(acfactory) -> None:
|
async def test_webxdc(acfactory) -> None:
|
||||||
alice, bob = await acfactory.get_online_accounts(2)
|
alice, bob = await acfactory.get_online_accounts(2)
|
||||||
|
|
||||||
bob_addr = await bob.get_config("addr")
|
bob_addr = await bob.get_config("addr")
|
||||||
alice_contact_bob = await alice.create_contact(bob_addr, "Bob")
|
alice_contact_bob = await alice.create_contact(bob_addr, "Bob")
|
||||||
alice_chat_bob = await alice_contact_bob.create_chat()
|
alice_chat_bob = await alice_contact_bob.create_chat()
|
||||||
await alice_chat_bob.send_message(
|
await alice_chat_bob.send_message(text="Let's play chess!", file="../test-data/webxdc/chess.xdc")
|
||||||
text="Let's play chess!", file="../test-data/webxdc/chess.xdc"
|
|
||||||
)
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
event = await bob.wait_for_event()
|
event = await bob.wait_for_event()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
isolated_build = true
|
isolated_build = true
|
||||||
envlist =
|
envlist =
|
||||||
py3
|
py3
|
||||||
|
lint
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
commands =
|
commands =
|
||||||
@@ -16,3 +17,13 @@ deps =
|
|||||||
pytest-asyncio
|
pytest-asyncio
|
||||||
aiohttp
|
aiohttp
|
||||||
aiodns
|
aiodns
|
||||||
|
|
||||||
|
[testenv:lint]
|
||||||
|
skipsdist = True
|
||||||
|
skip_install = True
|
||||||
|
deps =
|
||||||
|
ruff
|
||||||
|
black
|
||||||
|
commands =
|
||||||
|
black --check src/ examples/ tests/
|
||||||
|
ruff src/ examples/ tests/
|
||||||
|
|||||||
@@ -11,7 +11,8 @@
|
|||||||
# All configuration values have a default; values that are commented out
|
# All configuration values have a default; values that are commented out
|
||||||
# serve to show the default.
|
# serve to show the default.
|
||||||
|
|
||||||
import sys, os
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
# |version| and |release|, also used in various other places throughout the
|
# |version| and |release|, also used in various other places throughout the
|
||||||
|
|||||||
@@ -34,8 +34,10 @@ class GroupTrackingPlugin:
|
|||||||
def ac_member_added(self, chat, contact, actor, message):
|
def ac_member_added(self, chat, contact, actor, message):
|
||||||
print(
|
print(
|
||||||
"ac_member_added {} to chat {} from {}".format(
|
"ac_member_added {} to chat {} from {}".format(
|
||||||
contact.addr, chat.id, actor or message.get_sender_contact().addr
|
contact.addr,
|
||||||
)
|
chat.id,
|
||||||
|
actor or message.get_sender_contact().addr,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
for member in chat.get_contacts():
|
for member in chat.get_contacts():
|
||||||
print("chat member: {}".format(member.addr))
|
print("chat member: {}".format(member.addr))
|
||||||
@@ -44,8 +46,10 @@ class GroupTrackingPlugin:
|
|||||||
def ac_member_removed(self, chat, contact, actor, message):
|
def ac_member_removed(self, chat, contact, actor, message):
|
||||||
print(
|
print(
|
||||||
"ac_member_removed {} from chat {} by {}".format(
|
"ac_member_removed {} from chat {} by {}".format(
|
||||||
contact.addr, chat.id, actor or message.get_sender_contact().addr
|
contact.addr,
|
||||||
)
|
chat.id,
|
||||||
|
actor or message.get_sender_contact().addr,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ def datadir():
|
|||||||
datadir = path.join("test-data")
|
datadir = path.join("test-data")
|
||||||
if datadir.isdir():
|
if datadir.isdir():
|
||||||
return datadir
|
return datadir
|
||||||
else:
|
pytest.skip("test-data directory not found")
|
||||||
pytest.skip("test-data directory not found")
|
return None
|
||||||
|
|
||||||
|
|
||||||
def test_echo_quit_plugin(acfactory, lp):
|
def test_echo_quit_plugin(acfactory, lp):
|
||||||
@@ -47,7 +47,7 @@ def test_group_tracking_plugin(acfactory, lp):
|
|||||||
botproc.fnmatch_lines(
|
botproc.fnmatch_lines(
|
||||||
"""
|
"""
|
||||||
*ac_configure_completed*
|
*ac_configure_completed*
|
||||||
"""
|
""",
|
||||||
)
|
)
|
||||||
ac1.add_account_plugin(FFIEventLogger(ac1))
|
ac1.add_account_plugin(FFIEventLogger(ac1))
|
||||||
ac2.add_account_plugin(FFIEventLogger(ac2))
|
ac2.add_account_plugin(FFIEventLogger(ac2))
|
||||||
@@ -61,7 +61,7 @@ def test_group_tracking_plugin(acfactory, lp):
|
|||||||
botproc.fnmatch_lines(
|
botproc.fnmatch_lines(
|
||||||
"""
|
"""
|
||||||
*ac_chat_modified*bot test group*
|
*ac_chat_modified*bot test group*
|
||||||
"""
|
""",
|
||||||
)
|
)
|
||||||
|
|
||||||
lp.sec("adding third member {}".format(ac2.get_config("addr")))
|
lp.sec("adding third member {}".format(ac2.get_config("addr")))
|
||||||
@@ -76,8 +76,9 @@ def test_group_tracking_plugin(acfactory, lp):
|
|||||||
"""
|
"""
|
||||||
*ac_member_added {}*from*{}*
|
*ac_member_added {}*from*{}*
|
||||||
""".format(
|
""".format(
|
||||||
contact3.addr, ac1.get_config("addr")
|
contact3.addr,
|
||||||
)
|
ac1.get_config("addr"),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
lp.sec("contact successfully added, now removing")
|
lp.sec("contact successfully added, now removing")
|
||||||
@@ -86,6 +87,7 @@ def test_group_tracking_plugin(acfactory, lp):
|
|||||||
"""
|
"""
|
||||||
*ac_member_removed {}*from*{}*
|
*ac_member_removed {}*from*{}*
|
||||||
""".format(
|
""".format(
|
||||||
contact3.addr, ac1.get_config("addr")
|
contact3.addr,
|
||||||
)
|
ac1.get_config("addr"),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -44,5 +44,9 @@ git_describe_command = "git describe --dirty --tags --long --match py-*.*"
|
|||||||
[tool.black]
|
[tool.black]
|
||||||
line-length = 120
|
line-length = 120
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
select = ["E", "F", "W", "YTT", "C4", "ISC", "ICN", "TID", "DTZ", "PLC", "PLE", "PLW", "PIE", "COM"]
|
||||||
|
line-length = 120
|
||||||
|
|
||||||
[tool.isort]
|
[tool.isort]
|
||||||
profile = "black"
|
profile = "black"
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ register_global_plugin(events)
|
|||||||
|
|
||||||
def run_cmdline(argv=None, account_plugins=None):
|
def run_cmdline(argv=None, account_plugins=None):
|
||||||
"""Run a simple default command line app, registering the specified
|
"""Run a simple default command line app, registering the specified
|
||||||
account plugins."""
|
account plugins.
|
||||||
|
"""
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
if argv is None:
|
if argv is None:
|
||||||
|
|||||||
@@ -102,8 +102,8 @@ def find_header(flags):
|
|||||||
printf("%s", _dc_header_file_location());
|
printf("%s", _dc_header_file_location());
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
"""
|
""",
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
cwd = os.getcwd()
|
cwd = os.getcwd()
|
||||||
try:
|
try:
|
||||||
@@ -198,7 +198,7 @@ def ffibuilder():
|
|||||||
typedef int... time_t;
|
typedef int... time_t;
|
||||||
void free(void *ptr);
|
void free(void *ptr);
|
||||||
extern int dc_event_has_string_data(int);
|
extern int dc_event_has_string_data(int);
|
||||||
"""
|
""",
|
||||||
)
|
)
|
||||||
function_defs = extract_functions(flags)
|
function_defs = extract_functions(flags)
|
||||||
defines = extract_defines(flags)
|
defines = extract_defines(flags)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
""" Account class implementation. """
|
"""Account class implementation."""
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ def get_core_info():
|
|||||||
ffi.gc(
|
ffi.gc(
|
||||||
lib.dc_context_new(as_dc_charpointer(""), as_dc_charpointer(path.name), ffi.NULL),
|
lib.dc_context_new(as_dc_charpointer(""), as_dc_charpointer(path.name), ffi.NULL),
|
||||||
lib.dc_context_unref,
|
lib.dc_context_unref,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -172,10 +172,7 @@ class Account(object):
|
|||||||
namebytes = name.encode("utf8")
|
namebytes = name.encode("utf8")
|
||||||
if isinstance(value, (int, bool)):
|
if isinstance(value, (int, bool)):
|
||||||
value = str(int(value))
|
value = str(int(value))
|
||||||
if value is not None:
|
valuebytes = value.encode("utf8") if value is not None else ffi.NULL
|
||||||
valuebytes = value.encode("utf8")
|
|
||||||
else:
|
|
||||||
valuebytes = ffi.NULL
|
|
||||||
lib.dc_set_config(self._dc_context, namebytes, valuebytes)
|
lib.dc_set_config(self._dc_context, namebytes, valuebytes)
|
||||||
|
|
||||||
def get_config(self, name: str) -> str:
|
def get_config(self, name: str) -> str:
|
||||||
@@ -225,9 +222,10 @@ class Account(object):
|
|||||||
return bool(lib.dc_is_configured(self._dc_context))
|
return bool(lib.dc_is_configured(self._dc_context))
|
||||||
|
|
||||||
def is_open(self) -> bool:
|
def is_open(self) -> bool:
|
||||||
"""Determine if account is open
|
"""Determine if account is open.
|
||||||
|
|
||||||
:returns True if account is open."""
|
:returns True if account is open.
|
||||||
|
"""
|
||||||
return bool(lib.dc_context_is_open(self._dc_context))
|
return bool(lib.dc_context_is_open(self._dc_context))
|
||||||
|
|
||||||
def set_avatar(self, img_path: Optional[str]) -> None:
|
def set_avatar(self, img_path: Optional[str]) -> None:
|
||||||
@@ -543,7 +541,7 @@ class Account(object):
|
|||||||
return from_dc_charpointer(res)
|
return from_dc_charpointer(res)
|
||||||
|
|
||||||
def check_qr(self, qr):
|
def check_qr(self, qr):
|
||||||
"""check qr code and return :class:`ScannedQRCode` instance representing the result"""
|
"""check qr code and return :class:`ScannedQRCode` instance representing the result."""
|
||||||
res = ffi.gc(lib.dc_check_qr(self._dc_context, as_dc_charpointer(qr)), lib.dc_lot_unref)
|
res = ffi.gc(lib.dc_check_qr(self._dc_context, as_dc_charpointer(qr)), lib.dc_lot_unref)
|
||||||
lot = DCLot(res)
|
lot = DCLot(res)
|
||||||
if lot.state() == const.DC_QR_ERROR:
|
if lot.state() == const.DC_QR_ERROR:
|
||||||
@@ -662,7 +660,7 @@ class Account(object):
|
|||||||
return lib.dc_all_work_done(self._dc_context)
|
return lib.dc_all_work_done(self._dc_context)
|
||||||
|
|
||||||
def start_io(self):
|
def start_io(self):
|
||||||
"""start this account's IO scheduling (Rust-core async scheduler)
|
"""start this account's IO scheduling (Rust-core async scheduler).
|
||||||
|
|
||||||
If this account is not configured an Exception is raised.
|
If this account is not configured an Exception is raised.
|
||||||
You need to call account.configure() and account.wait_configure_finish()
|
You need to call account.configure() and account.wait_configure_finish()
|
||||||
@@ -705,12 +703,10 @@ class Account(object):
|
|||||||
"""
|
"""
|
||||||
lib.dc_maybe_network(self._dc_context)
|
lib.dc_maybe_network(self._dc_context)
|
||||||
|
|
||||||
def configure(self, reconfigure: bool = False) -> ConfigureTracker:
|
def configure(self) -> ConfigureTracker:
|
||||||
"""Start configuration process and return a Configtracker instance
|
"""Start configuration process and return a Configtracker instance
|
||||||
on which you can block with wait_finish() to get a True/False success
|
on which you can block with wait_finish() to get a True/False success
|
||||||
value for the configuration process.
|
value for the configuration process.
|
||||||
|
|
||||||
:param reconfigure: deprecated, doesn't need to be checked anymore.
|
|
||||||
"""
|
"""
|
||||||
if not self.get_config("addr") or not self.get_config("mail_pw"):
|
if not self.get_config("addr") or not self.get_config("mail_pw"):
|
||||||
raise MissingCredentials("addr or mail_pwd not set in config")
|
raise MissingCredentials("addr or mail_pwd not set in config")
|
||||||
@@ -733,7 +729,8 @@ class Account(object):
|
|||||||
|
|
||||||
def shutdown(self) -> None:
|
def shutdown(self) -> None:
|
||||||
"""shutdown and destroy account (stop callback thread, close and remove
|
"""shutdown and destroy account (stop callback thread, close and remove
|
||||||
underlying dc_context)."""
|
underlying dc_context).
|
||||||
|
"""
|
||||||
if self._dc_context is None:
|
if self._dc_context is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
""" Chat and Location related API. """
|
"""Chat and Location related API."""
|
||||||
|
|
||||||
import calendar
|
import calendar
|
||||||
import json
|
import json
|
||||||
@@ -37,7 +37,7 @@ class Chat(object):
|
|||||||
return self.id == getattr(other, "id", None) and self.account._dc_context == other.account._dc_context
|
return self.id == getattr(other, "id", None) and self.account._dc_context == other.account._dc_context
|
||||||
|
|
||||||
def __ne__(self, other) -> bool:
|
def __ne__(self, other) -> bool:
|
||||||
return not (self == other)
|
return not self == other
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return "<Chat id={} name={}>".format(self.id, self.get_name())
|
return "<Chat id={} name={}>".format(self.id, self.get_name())
|
||||||
@@ -74,19 +74,19 @@ class Chat(object):
|
|||||||
return lib.dc_chat_get_type(self._dc_chat) == const.DC_CHAT_TYPE_GROUP
|
return lib.dc_chat_get_type(self._dc_chat) == const.DC_CHAT_TYPE_GROUP
|
||||||
|
|
||||||
def is_single(self) -> bool:
|
def is_single(self) -> bool:
|
||||||
"""Return True if this chat is a single/direct chat, False otherwise"""
|
"""Return True if this chat is a single/direct chat, False otherwise."""
|
||||||
return lib.dc_chat_get_type(self._dc_chat) == const.DC_CHAT_TYPE_SINGLE
|
return lib.dc_chat_get_type(self._dc_chat) == const.DC_CHAT_TYPE_SINGLE
|
||||||
|
|
||||||
def is_mailinglist(self) -> bool:
|
def is_mailinglist(self) -> bool:
|
||||||
"""Return True if this chat is a mailing list, False otherwise"""
|
"""Return True if this chat is a mailing list, False otherwise."""
|
||||||
return lib.dc_chat_get_type(self._dc_chat) == const.DC_CHAT_TYPE_MAILINGLIST
|
return lib.dc_chat_get_type(self._dc_chat) == const.DC_CHAT_TYPE_MAILINGLIST
|
||||||
|
|
||||||
def is_broadcast(self) -> bool:
|
def is_broadcast(self) -> bool:
|
||||||
"""Return True if this chat is a broadcast list, False otherwise"""
|
"""Return True if this chat is a broadcast list, False otherwise."""
|
||||||
return lib.dc_chat_get_type(self._dc_chat) == const.DC_CHAT_TYPE_BROADCAST
|
return lib.dc_chat_get_type(self._dc_chat) == const.DC_CHAT_TYPE_BROADCAST
|
||||||
|
|
||||||
def is_multiuser(self) -> bool:
|
def is_multiuser(self) -> bool:
|
||||||
"""Return True if this chat is a multi-user chat (group, mailing list or broadcast), False otherwise"""
|
"""Return True if this chat is a multi-user chat (group, mailing list or broadcast), False otherwise."""
|
||||||
return lib.dc_chat_get_type(self._dc_chat) in (
|
return lib.dc_chat_get_type(self._dc_chat) in (
|
||||||
const.DC_CHAT_TYPE_GROUP,
|
const.DC_CHAT_TYPE_GROUP,
|
||||||
const.DC_CHAT_TYPE_MAILINGLIST,
|
const.DC_CHAT_TYPE_MAILINGLIST,
|
||||||
@@ -94,11 +94,11 @@ class Chat(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def is_self_talk(self) -> bool:
|
def is_self_talk(self) -> bool:
|
||||||
"""Return True if this chat is the self-chat (a.k.a. "Saved Messages"), False otherwise"""
|
"""Return True if this chat is the self-chat (a.k.a. "Saved Messages"), False otherwise."""
|
||||||
return bool(lib.dc_chat_is_self_talk(self._dc_chat))
|
return bool(lib.dc_chat_is_self_talk(self._dc_chat))
|
||||||
|
|
||||||
def is_device_talk(self) -> bool:
|
def is_device_talk(self) -> bool:
|
||||||
"""Returns True if this chat is the "Device Messages" chat, False otherwise"""
|
"""Returns True if this chat is the "Device Messages" chat, False otherwise."""
|
||||||
return bool(lib.dc_chat_is_device_talk(self._dc_chat))
|
return bool(lib.dc_chat_is_device_talk(self._dc_chat))
|
||||||
|
|
||||||
def is_muted(self) -> bool:
|
def is_muted(self) -> bool:
|
||||||
@@ -109,12 +109,12 @@ class Chat(object):
|
|||||||
return bool(lib.dc_chat_is_muted(self._dc_chat))
|
return bool(lib.dc_chat_is_muted(self._dc_chat))
|
||||||
|
|
||||||
def is_pinned(self) -> bool:
|
def is_pinned(self) -> bool:
|
||||||
"""Return True if this chat is pinned, False otherwise"""
|
"""Return True if this chat is pinned, False otherwise."""
|
||||||
return lib.dc_chat_get_visibility(self._dc_chat) == const.DC_CHAT_VISIBILITY_PINNED
|
return lib.dc_chat_get_visibility(self._dc_chat) == const.DC_CHAT_VISIBILITY_PINNED
|
||||||
|
|
||||||
def is_archived(self) -> bool:
|
def is_archived(self) -> bool:
|
||||||
"""Return True if this chat is archived, False otherwise.
|
"""Return True if this chat is archived, False otherwise.
|
||||||
:returns: True if archived, False otherwise
|
:returns: True if archived, False otherwise.
|
||||||
"""
|
"""
|
||||||
return lib.dc_chat_get_visibility(self._dc_chat) == const.DC_CHAT_VISIBILITY_ARCHIVED
|
return lib.dc_chat_get_visibility(self._dc_chat) == const.DC_CHAT_VISIBILITY_ARCHIVED
|
||||||
|
|
||||||
@@ -136,7 +136,7 @@ class Chat(object):
|
|||||||
|
|
||||||
def can_send(self) -> bool:
|
def can_send(self) -> bool:
|
||||||
"""Check if messages can be sent to a give chat.
|
"""Check if messages can be sent to a give chat.
|
||||||
This is not true eg. for the contact requests or for the device-talk
|
This is not true eg. for the contact requests or for the device-talk.
|
||||||
|
|
||||||
:returns: True if the chat is writable, False otherwise
|
:returns: True if the chat is writable, False otherwise
|
||||||
"""
|
"""
|
||||||
@@ -167,7 +167,7 @@ class Chat(object):
|
|||||||
|
|
||||||
def get_color(self):
|
def get_color(self):
|
||||||
"""return the color of the chat.
|
"""return the color of the chat.
|
||||||
:returns: color as 0x00rrggbb
|
:returns: color as 0x00rrggbb.
|
||||||
"""
|
"""
|
||||||
return lib.dc_chat_get_color(self._dc_chat)
|
return lib.dc_chat_get_color(self._dc_chat)
|
||||||
|
|
||||||
@@ -178,21 +178,18 @@ class Chat(object):
|
|||||||
return json.loads(s)
|
return json.loads(s)
|
||||||
|
|
||||||
def mute(self, duration: Optional[int] = None) -> None:
|
def mute(self, duration: Optional[int] = None) -> None:
|
||||||
"""mutes the chat
|
"""mutes the chat.
|
||||||
|
|
||||||
:param duration: Number of seconds to mute the chat for. None to mute until unmuted again.
|
:param duration: Number of seconds to mute the chat for. None to mute until unmuted again.
|
||||||
:returns: None
|
:returns: None
|
||||||
"""
|
"""
|
||||||
if duration is None:
|
mute_duration = -1 if duration is None else duration
|
||||||
mute_duration = -1
|
|
||||||
else:
|
|
||||||
mute_duration = duration
|
|
||||||
ret = lib.dc_set_chat_mute_duration(self.account._dc_context, self.id, mute_duration)
|
ret = lib.dc_set_chat_mute_duration(self.account._dc_context, self.id, mute_duration)
|
||||||
if not bool(ret):
|
if not bool(ret):
|
||||||
raise ValueError("Call to dc_set_chat_mute_duration failed")
|
raise ValueError("Call to dc_set_chat_mute_duration failed")
|
||||||
|
|
||||||
def unmute(self) -> None:
|
def unmute(self) -> None:
|
||||||
"""unmutes the chat
|
"""unmutes the chat.
|
||||||
|
|
||||||
:returns: None
|
:returns: None
|
||||||
"""
|
"""
|
||||||
@@ -252,7 +249,8 @@ class Chat(object):
|
|||||||
def get_encryption_info(self) -> Optional[str]:
|
def get_encryption_info(self) -> Optional[str]:
|
||||||
"""Return encryption info for this chat.
|
"""Return encryption info for this chat.
|
||||||
|
|
||||||
:returns: a string with encryption preferences of all chat members"""
|
:returns: a string with encryption preferences of all chat members
|
||||||
|
"""
|
||||||
res = lib.dc_get_chat_encrinfo(self.account._dc_context, self.id)
|
res = lib.dc_get_chat_encrinfo(self.account._dc_context, self.id)
|
||||||
return from_dc_charpointer(res)
|
return from_dc_charpointer(res)
|
||||||
|
|
||||||
@@ -463,7 +461,7 @@ class Chat(object):
|
|||||||
|
|
||||||
def get_contacts(self):
|
def get_contacts(self):
|
||||||
"""get all contacts for this chat.
|
"""get all contacts for this chat.
|
||||||
:returns: list of :class:`deltachat.contact.Contact` objects for this chat
|
:returns: list of :class:`deltachat.contact.Contact` objects for this chat.
|
||||||
"""
|
"""
|
||||||
from .contact import Contact
|
from .contact import Contact
|
||||||
|
|
||||||
@@ -547,19 +545,10 @@ class Chat(object):
|
|||||||
:param timespan_to: a datetime object or None (indicating up till now)
|
:param timespan_to: a datetime object or None (indicating up till now)
|
||||||
:returns: list of :class:`deltachat.chat.Location` objects.
|
:returns: list of :class:`deltachat.chat.Location` objects.
|
||||||
"""
|
"""
|
||||||
if timestamp_from is None:
|
time_from = 0 if timestamp_from is None else calendar.timegm(timestamp_from.utctimetuple())
|
||||||
time_from = 0
|
time_to = 0 if timestamp_to is None else calendar.timegm(timestamp_to.utctimetuple())
|
||||||
else:
|
|
||||||
time_from = calendar.timegm(timestamp_from.utctimetuple())
|
|
||||||
if timestamp_to is None:
|
|
||||||
time_to = 0
|
|
||||||
else:
|
|
||||||
time_to = calendar.timegm(timestamp_to.utctimetuple())
|
|
||||||
|
|
||||||
if contact is None:
|
contact_id = 0 if contact is None else contact.id
|
||||||
contact_id = 0
|
|
||||||
else:
|
|
||||||
contact_id = contact.id
|
|
||||||
|
|
||||||
dc_array = lib.dc_get_locations(self.account._dc_context, self.id, contact_id, time_from, time_to)
|
dc_array = lib.dc_get_locations(self.account._dc_context, self.id, contact_id, time_from, time_to)
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
""" Contact object. """
|
"""Contact object."""
|
||||||
|
|
||||||
from datetime import date, datetime, timezone
|
from datetime import date, datetime, timezone
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
@@ -28,7 +28,7 @@ class Contact(object):
|
|||||||
return self.account._dc_context == other.account._dc_context and self.id == other.id
|
return self.account._dc_context == other.account._dc_context and self.id == other.id
|
||||||
|
|
||||||
def __ne__(self, other):
|
def __ne__(self, other):
|
||||||
return not (self == other)
|
return not self == other
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<Contact id={} addr={} dc_context={}>".format(self.id, self.addr, self.account._dc_context)
|
return "<Contact id={} addr={} dc_context={}>".format(self.id, self.addr, self.account._dc_context)
|
||||||
@@ -76,7 +76,7 @@ class Contact(object):
|
|||||||
return lib.dc_contact_is_verified(self._dc_contact)
|
return lib.dc_contact_is_verified(self._dc_contact)
|
||||||
|
|
||||||
def get_verifier(self, contact):
|
def get_verifier(self, contact):
|
||||||
"""Return the address of the contact that verified the contact"""
|
"""Return the address of the contact that verified the contact."""
|
||||||
return from_dc_charpointer(lib.dc_contact_get_verifier_addr(contact._dc_contact))
|
return from_dc_charpointer(lib.dc_contact_get_verifier_addr(contact._dc_contact))
|
||||||
|
|
||||||
def get_profile_image(self) -> Optional[str]:
|
def get_profile_image(self) -> Optional[str]:
|
||||||
|
|||||||
@@ -79,15 +79,17 @@ class DirectImap:
|
|||||||
|
|
||||||
def select_config_folder(self, config_name: str):
|
def select_config_folder(self, config_name: str):
|
||||||
"""Return info about selected folder if it is
|
"""Return info about selected folder if it is
|
||||||
configured, otherwise None."""
|
configured, otherwise None.
|
||||||
|
"""
|
||||||
if "_" not in config_name:
|
if "_" not in config_name:
|
||||||
config_name = "configured_{}_folder".format(config_name)
|
config_name = "configured_{}_folder".format(config_name)
|
||||||
foldername = self.account.get_config(config_name)
|
foldername = self.account.get_config(config_name)
|
||||||
if foldername:
|
if foldername:
|
||||||
return self.select_folder(foldername)
|
return self.select_folder(foldername)
|
||||||
|
return None
|
||||||
|
|
||||||
def list_folders(self) -> List[str]:
|
def list_folders(self) -> List[str]:
|
||||||
"""return list of all existing folder names"""
|
"""return list of all existing folder names."""
|
||||||
assert not self._idling
|
assert not self._idling
|
||||||
return [folder.name for folder in self.conn.folder.list()]
|
return [folder.name for folder in self.conn.folder.list()]
|
||||||
|
|
||||||
@@ -103,7 +105,7 @@ class DirectImap:
|
|||||||
|
|
||||||
def get_all_messages(self) -> List[MailMessage]:
|
def get_all_messages(self) -> List[MailMessage]:
|
||||||
assert not self._idling
|
assert not self._idling
|
||||||
return [mail for mail in self.conn.fetch()]
|
return list(self.conn.fetch())
|
||||||
|
|
||||||
def get_unread_messages(self) -> List[str]:
|
def get_unread_messages(self) -> List[str]:
|
||||||
assert not self._idling
|
assert not self._idling
|
||||||
@@ -221,5 +223,4 @@ class IdleManager:
|
|||||||
|
|
||||||
def done(self):
|
def done(self):
|
||||||
"""send idle-done to server if we are currently in idle mode."""
|
"""send idle-done to server if we are currently in idle mode."""
|
||||||
res = self.direct_imap.conn.idle.stop()
|
return self.direct_imap.conn.idle.stop()
|
||||||
return res
|
|
||||||
|
|||||||
@@ -32,12 +32,11 @@ class FFIEvent:
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self.name == "DC_EVENT_INFO":
|
if self.name == "DC_EVENT_INFO":
|
||||||
return "INFO {data2}".format(data2=self.data2)
|
return "INFO {data2}".format(data2=self.data2)
|
||||||
elif self.name == "DC_EVENT_WARNING":
|
if self.name == "DC_EVENT_WARNING":
|
||||||
return "WARNING {data2}".format(data2=self.data2)
|
return "WARNING {data2}".format(data2=self.data2)
|
||||||
elif self.name == "DC_EVENT_ERROR":
|
if self.name == "DC_EVENT_ERROR":
|
||||||
return "ERROR {data2}".format(data2=self.data2)
|
return "ERROR {data2}".format(data2=self.data2)
|
||||||
else:
|
return "{name} data1={data1} data2={data2}".format(**self.__dict__)
|
||||||
return "{name} data1={data1} data2={data2}".format(**self.__dict__)
|
|
||||||
|
|
||||||
|
|
||||||
class FFIEventLogger:
|
class FFIEventLogger:
|
||||||
@@ -135,7 +134,8 @@ class FFIEventTracker:
|
|||||||
def wait_for_connectivity(self, connectivity):
|
def wait_for_connectivity(self, connectivity):
|
||||||
"""Wait for the specified connectivity.
|
"""Wait for the specified connectivity.
|
||||||
This only works reliably if the connectivity doesn't change
|
This only works reliably if the connectivity doesn't change
|
||||||
again too quickly, otherwise we might miss it."""
|
again too quickly, otherwise we might miss it.
|
||||||
|
"""
|
||||||
while 1:
|
while 1:
|
||||||
if self.account.get_connectivity() == connectivity:
|
if self.account.get_connectivity() == connectivity:
|
||||||
return
|
return
|
||||||
@@ -143,12 +143,13 @@ class FFIEventTracker:
|
|||||||
|
|
||||||
def wait_for_connectivity_change(self, previous, expected_next):
|
def wait_for_connectivity_change(self, previous, expected_next):
|
||||||
"""Wait until the connectivity changes to `expected_next`.
|
"""Wait until the connectivity changes to `expected_next`.
|
||||||
Fails the test if it changes to something else."""
|
Fails the test if it changes to something else.
|
||||||
|
"""
|
||||||
while 1:
|
while 1:
|
||||||
current = self.account.get_connectivity()
|
current = self.account.get_connectivity()
|
||||||
if current == expected_next:
|
if current == expected_next:
|
||||||
return
|
return
|
||||||
elif current != previous:
|
if current != previous:
|
||||||
raise Exception("Expected connectivity " + str(expected_next) + " but got " + str(current))
|
raise Exception("Expected connectivity " + str(expected_next) + " but got " + str(current))
|
||||||
|
|
||||||
self.get_matching("DC_EVENT_CONNECTIVITY_CHANGED")
|
self.get_matching("DC_EVENT_CONNECTIVITY_CHANGED")
|
||||||
@@ -183,7 +184,8 @@ class FFIEventTracker:
|
|||||||
- ac1 and ac2 are created
|
- ac1 and ac2 are created
|
||||||
- ac1 sends a message to ac2
|
- ac1 sends a message to ac2
|
||||||
- ac2 is still running FetchExsistingMsgs job and thinks it's an existing, old message
|
- ac2 is still running FetchExsistingMsgs job and thinks it's an existing, old message
|
||||||
- therefore no DC_EVENT_INCOMING_MSG is sent"""
|
- therefore no DC_EVENT_INCOMING_MSG is sent
|
||||||
|
"""
|
||||||
self.get_info_contains("INBOX: Idle entering")
|
self.get_info_contains("INBOX: Idle entering")
|
||||||
|
|
||||||
def wait_next_incoming_message(self):
|
def wait_next_incoming_message(self):
|
||||||
@@ -193,14 +195,15 @@ class FFIEventTracker:
|
|||||||
|
|
||||||
def wait_next_messages_changed(self):
|
def wait_next_messages_changed(self):
|
||||||
"""wait for and return next message-changed message or None
|
"""wait for and return next message-changed message or None
|
||||||
if the event contains no msgid"""
|
if the event contains no msgid
|
||||||
|
"""
|
||||||
ev = self.get_matching("DC_EVENT_MSGS_CHANGED")
|
ev = self.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||||
if ev.data2 > 0:
|
if ev.data2 > 0:
|
||||||
return self.account.get_message_by_id(ev.data2)
|
return self.account.get_message_by_id(ev.data2)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def wait_next_reactions_changed(self):
|
def wait_next_reactions_changed(self):
|
||||||
"""wait for and return next reactions-changed message"""
|
"""wait for and return next reactions-changed message."""
|
||||||
ev = self.get_matching("DC_EVENT_REACTIONS_CHANGED")
|
ev = self.get_matching("DC_EVENT_REACTIONS_CHANGED")
|
||||||
assert ev.data1 > 0
|
assert ev.data1 > 0
|
||||||
return self.account.get_message_by_id(ev.data2)
|
return self.account.get_message_by_id(ev.data2)
|
||||||
@@ -292,10 +295,10 @@ class EventThread(threading.Thread):
|
|||||||
if data1 == 0 or data1 == 1000:
|
if data1 == 0 or data1 == 1000:
|
||||||
success = data1 == 1000
|
success = data1 == 1000
|
||||||
comment = ffi_event.data2
|
comment = ffi_event.data2
|
||||||
yield "ac_configure_completed", dict(success=success, comment=comment)
|
yield "ac_configure_completed", {"success": success, "comment": comment}
|
||||||
elif name == "DC_EVENT_INCOMING_MSG":
|
elif name == "DC_EVENT_INCOMING_MSG":
|
||||||
msg = account.get_message_by_id(ffi_event.data2)
|
msg = account.get_message_by_id(ffi_event.data2)
|
||||||
yield map_system_message(msg) or ("ac_incoming_message", dict(message=msg))
|
yield map_system_message(msg) or ("ac_incoming_message", {"message": msg})
|
||||||
elif name == "DC_EVENT_MSGS_CHANGED":
|
elif name == "DC_EVENT_MSGS_CHANGED":
|
||||||
if ffi_event.data2 != 0:
|
if ffi_event.data2 != 0:
|
||||||
msg = account.get_message_by_id(ffi_event.data2)
|
msg = account.get_message_by_id(ffi_event.data2)
|
||||||
@@ -303,19 +306,19 @@ class EventThread(threading.Thread):
|
|||||||
res = map_system_message(msg)
|
res = map_system_message(msg)
|
||||||
if res and res[0].startswith("ac_member"):
|
if res and res[0].startswith("ac_member"):
|
||||||
yield res
|
yield res
|
||||||
yield "ac_outgoing_message", dict(message=msg)
|
yield "ac_outgoing_message", {"message": msg}
|
||||||
elif msg.is_in_fresh():
|
elif msg.is_in_fresh():
|
||||||
yield map_system_message(msg) or (
|
yield map_system_message(msg) or (
|
||||||
"ac_incoming_message",
|
"ac_incoming_message",
|
||||||
dict(message=msg),
|
{"message": msg},
|
||||||
)
|
)
|
||||||
elif name == "DC_EVENT_REACTIONS_CHANGED":
|
elif name == "DC_EVENT_REACTIONS_CHANGED":
|
||||||
assert ffi_event.data1 > 0
|
assert ffi_event.data1 > 0
|
||||||
msg = account.get_message_by_id(ffi_event.data2)
|
msg = account.get_message_by_id(ffi_event.data2)
|
||||||
yield "ac_reactions_changed", dict(message=msg)
|
yield "ac_reactions_changed", {"message": msg}
|
||||||
elif name == "DC_EVENT_MSG_DELIVERED":
|
elif name == "DC_EVENT_MSG_DELIVERED":
|
||||||
msg = account.get_message_by_id(ffi_event.data2)
|
msg = account.get_message_by_id(ffi_event.data2)
|
||||||
yield "ac_message_delivered", dict(message=msg)
|
yield "ac_message_delivered", {"message": msg}
|
||||||
elif name == "DC_EVENT_CHAT_MODIFIED":
|
elif name == "DC_EVENT_CHAT_MODIFIED":
|
||||||
chat = account.get_chat_by_id(ffi_event.data1)
|
chat = account.get_chat_by_id(ffi_event.data1)
|
||||||
yield "ac_chat_modified", dict(chat=chat)
|
yield "ac_chat_modified", {"chat": chat}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
""" Hooks for Python bindings to Delta Chat Core Rust CFFI"""
|
"""Hooks for Python bindings to Delta Chat Core Rust CFFI."""
|
||||||
|
|
||||||
import pluggy
|
import pluggy
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
""" The Message object. """
|
"""The Message object."""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
@@ -59,10 +59,7 @@ class Message(object):
|
|||||||
:param view_type: the message type code or one of the strings:
|
:param view_type: the message type code or one of the strings:
|
||||||
"text", "audio", "video", "file", "sticker", "videochat", "webxdc"
|
"text", "audio", "video", "file", "sticker", "videochat", "webxdc"
|
||||||
"""
|
"""
|
||||||
if isinstance(view_type, int):
|
view_type_code = view_type if isinstance(view_type, int) else get_viewtype_code_from_name(view_type)
|
||||||
view_type_code = view_type
|
|
||||||
else:
|
|
||||||
view_type_code = get_viewtype_code_from_name(view_type)
|
|
||||||
return Message(
|
return Message(
|
||||||
account,
|
account,
|
||||||
ffi.gc(lib.dc_msg_new(account._dc_context, view_type_code), lib.dc_msg_unref),
|
ffi.gc(lib.dc_msg_new(account._dc_context, view_type_code), lib.dc_msg_unref),
|
||||||
@@ -129,7 +126,7 @@ class Message(object):
|
|||||||
|
|
||||||
@props.with_doc
|
@props.with_doc
|
||||||
def filemime(self) -> str:
|
def filemime(self) -> str:
|
||||||
"""mime type of the file (if it exists)"""
|
"""mime type of the file (if it exists)."""
|
||||||
return from_dc_charpointer(lib.dc_msg_get_filemime(self._dc_msg))
|
return from_dc_charpointer(lib.dc_msg_get_filemime(self._dc_msg))
|
||||||
|
|
||||||
def get_status_updates(self, serial: int = 0) -> list:
|
def get_status_updates(self, serial: int = 0) -> list:
|
||||||
@@ -141,7 +138,7 @@ class Message(object):
|
|||||||
:param serial: The last known serial. Pass 0 if there are no known serials to receive all updates.
|
:param serial: The last known serial. Pass 0 if there are no known serials to receive all updates.
|
||||||
"""
|
"""
|
||||||
return json.loads(
|
return json.loads(
|
||||||
from_dc_charpointer(lib.dc_get_webxdc_status_updates(self.account._dc_context, self.id, serial))
|
from_dc_charpointer(lib.dc_get_webxdc_status_updates(self.account._dc_context, self.id, serial)),
|
||||||
)
|
)
|
||||||
|
|
||||||
def send_status_update(self, json_data: Union[str, dict], description: str) -> bool:
|
def send_status_update(self, json_data: Union[str, dict], description: str) -> bool:
|
||||||
@@ -158,8 +155,11 @@ class Message(object):
|
|||||||
json_data = json.dumps(json_data, default=str)
|
json_data = json.dumps(json_data, default=str)
|
||||||
return bool(
|
return bool(
|
||||||
lib.dc_send_webxdc_status_update(
|
lib.dc_send_webxdc_status_update(
|
||||||
self.account._dc_context, self.id, as_dc_charpointer(json_data), as_dc_charpointer(description)
|
self.account._dc_context,
|
||||||
)
|
self.id,
|
||||||
|
as_dc_charpointer(json_data),
|
||||||
|
as_dc_charpointer(description),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
def send_reaction(self, reaction: str):
|
def send_reaction(self, reaction: str):
|
||||||
@@ -232,16 +232,18 @@ class Message(object):
|
|||||||
ts = lib.dc_msg_get_received_timestamp(self._dc_msg)
|
ts = lib.dc_msg_get_received_timestamp(self._dc_msg)
|
||||||
if ts:
|
if ts:
|
||||||
return datetime.fromtimestamp(ts, timezone.utc)
|
return datetime.fromtimestamp(ts, timezone.utc)
|
||||||
|
return None
|
||||||
|
|
||||||
@props.with_doc
|
@props.with_doc
|
||||||
def ephemeral_timer(self):
|
def ephemeral_timer(self):
|
||||||
"""Ephemeral timer in seconds
|
"""Ephemeral timer in seconds.
|
||||||
|
|
||||||
:returns: timer in seconds or None if there is no timer
|
:returns: timer in seconds or None if there is no timer
|
||||||
"""
|
"""
|
||||||
timer = lib.dc_msg_get_ephemeral_timer(self._dc_msg)
|
timer = lib.dc_msg_get_ephemeral_timer(self._dc_msg)
|
||||||
if timer:
|
if timer:
|
||||||
return timer
|
return timer
|
||||||
|
return None
|
||||||
|
|
||||||
@props.with_doc
|
@props.with_doc
|
||||||
def ephemeral_timestamp(self):
|
def ephemeral_timestamp(self):
|
||||||
@@ -255,23 +257,25 @@ class Message(object):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def quoted_text(self) -> Optional[str]:
|
def quoted_text(self) -> Optional[str]:
|
||||||
"""Text inside the quote
|
"""Text inside the quote.
|
||||||
|
|
||||||
:returns: Quoted text"""
|
:returns: Quoted text
|
||||||
|
"""
|
||||||
return from_optional_dc_charpointer(lib.dc_msg_get_quoted_text(self._dc_msg))
|
return from_optional_dc_charpointer(lib.dc_msg_get_quoted_text(self._dc_msg))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def quote(self):
|
def quote(self):
|
||||||
"""Quote getter
|
"""Quote getter.
|
||||||
|
|
||||||
:returns: Quoted message, if found in the database"""
|
:returns: Quoted message, if found in the database
|
||||||
|
"""
|
||||||
msg = lib.dc_msg_get_quoted_msg(self._dc_msg)
|
msg = lib.dc_msg_get_quoted_msg(self._dc_msg)
|
||||||
if msg:
|
if msg:
|
||||||
return Message(self.account, ffi.gc(msg, lib.dc_msg_unref))
|
return Message(self.account, ffi.gc(msg, lib.dc_msg_unref))
|
||||||
|
|
||||||
@quote.setter
|
@quote.setter
|
||||||
def quote(self, quoted_message):
|
def quote(self, quoted_message):
|
||||||
"""Quote setter"""
|
"""Quote setter."""
|
||||||
lib.dc_msg_set_quote(self._dc_msg, quoted_message._dc_msg)
|
lib.dc_msg_set_quote(self._dc_msg, quoted_message._dc_msg)
|
||||||
|
|
||||||
def force_plaintext(self) -> None:
|
def force_plaintext(self) -> None:
|
||||||
@@ -286,7 +290,7 @@ class Message(object):
|
|||||||
|
|
||||||
:returns: email-mime message object (with headers only, no body).
|
:returns: email-mime message object (with headers only, no body).
|
||||||
"""
|
"""
|
||||||
import email.parser
|
import email
|
||||||
|
|
||||||
mime_headers = lib.dc_get_mime_headers(self.account._dc_context, self.id)
|
mime_headers = lib.dc_get_mime_headers(self.account._dc_context, self.id)
|
||||||
if mime_headers:
|
if mime_headers:
|
||||||
@@ -297,7 +301,7 @@ class Message(object):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def error(self) -> Optional[str]:
|
def error(self) -> Optional[str]:
|
||||||
"""Error message"""
|
"""Error message."""
|
||||||
return from_optional_dc_charpointer(lib.dc_msg_get_error(self._dc_msg))
|
return from_optional_dc_charpointer(lib.dc_msg_get_error(self._dc_msg))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -493,7 +497,8 @@ def get_viewtype_code_from_name(view_type_name):
|
|||||||
if code is not None:
|
if code is not None:
|
||||||
return code
|
return code
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"message typecode not found for {!r}, " "available {!r}".format(view_type_name, list(_view_type_mapping.keys()))
|
"message typecode not found for {!r}, "
|
||||||
|
"available {!r}".format(view_type_name, list(_view_type_mapping.keys())),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -506,14 +511,11 @@ def map_system_message(msg):
|
|||||||
if msg.is_system_message():
|
if msg.is_system_message():
|
||||||
res = parse_system_add_remove(msg.text)
|
res = parse_system_add_remove(msg.text)
|
||||||
if not res:
|
if not res:
|
||||||
return
|
return None
|
||||||
action, affected, actor = res
|
action, affected, actor = res
|
||||||
affected = msg.account.get_contact_by_addr(affected)
|
affected = msg.account.get_contact_by_addr(affected)
|
||||||
if actor == "me":
|
actor = None if actor == "me" else msg.account.get_contact_by_addr(actor)
|
||||||
actor = None
|
d = {"chat": msg.chat, "contact": affected, "actor": actor, "message": msg}
|
||||||
else:
|
|
||||||
actor = msg.account.get_contact_by_addr(actor)
|
|
||||||
d = dict(chat=msg.chat, contact=affected, actor=actor, message=msg)
|
|
||||||
return "ac_member_" + res[0], d
|
return "ac_member_" + res[0], d
|
||||||
|
|
||||||
|
|
||||||
@@ -528,8 +530,8 @@ def extract_addr(text):
|
|||||||
def parse_system_add_remove(text):
|
def parse_system_add_remove(text):
|
||||||
"""return add/remove info from parsing the given system message text.
|
"""return add/remove info from parsing the given system message text.
|
||||||
|
|
||||||
returns a (action, affected, actor) triple"""
|
returns a (action, affected, actor) triple
|
||||||
|
"""
|
||||||
# You removed member a@b.
|
# You removed member a@b.
|
||||||
# You added member a@b.
|
# You added member a@b.
|
||||||
# Member Me (x@y) removed by a@b.
|
# Member Me (x@y) removed by a@b.
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ def with_doc(f):
|
|||||||
# copied over unmodified from
|
# copied over unmodified from
|
||||||
# https://github.com/devpi/devpi/blob/master/common/devpi_common/types.py
|
# https://github.com/devpi/devpi/blob/master/common/devpi_common/types.py
|
||||||
def cached(f):
|
def cached(f):
|
||||||
"""returns a cached property that is calculated by function f"""
|
"""returns a cached property that is calculated by function f."""
|
||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
try:
|
try:
|
||||||
@@ -17,8 +17,9 @@ def cached(f):
|
|||||||
self._property_cache = {}
|
self._property_cache = {}
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
x = self._property_cache[f] = f(self)
|
res = f(self)
|
||||||
return x
|
self._property_cache[f] = res
|
||||||
|
return res
|
||||||
|
|
||||||
def set(self, val):
|
def set(self, val):
|
||||||
propcache = self.__dict__.setdefault("_property_cache", {})
|
propcache = self.__dict__.setdefault("_property_cache", {})
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ class ProviderNotFoundError(Exception):
|
|||||||
|
|
||||||
|
|
||||||
class Provider(object):
|
class Provider(object):
|
||||||
"""Provider information.
|
"""
|
||||||
|
Provider information.
|
||||||
|
|
||||||
:param domain: The email to get the provider info for.
|
:param domain: The email to get the provider info for.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
""" The Reactions object. """
|
"""The Reactions object."""
|
||||||
|
|
||||||
from .capi import ffi, lib
|
from .capi import ffi, lib
|
||||||
from .cutil import from_dc_charpointer, iter_array
|
from .cutil import from_dc_charpointer, iter_array
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ def pytest_addoption(parser):
|
|||||||
"--liveconfig",
|
"--liveconfig",
|
||||||
action="store",
|
action="store",
|
||||||
default=None,
|
default=None,
|
||||||
help="a file with >=2 lines where each line " "contains NAME=VALUE config settings for one account",
|
help="a file with >=2 lines where each line contains NAME=VALUE config settings for one account",
|
||||||
)
|
)
|
||||||
group.addoption(
|
group.addoption(
|
||||||
"--ignored",
|
"--ignored",
|
||||||
@@ -124,7 +124,7 @@ def pytest_report_header(config, startdir):
|
|||||||
info["deltachat_core_version"],
|
info["deltachat_core_version"],
|
||||||
info["sqlite_version"],
|
info["sqlite_version"],
|
||||||
info["journal_mode"],
|
info["journal_mode"],
|
||||||
)
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
cfg = config.option.liveconfig
|
cfg = config.option.liveconfig
|
||||||
@@ -180,7 +180,7 @@ class TestProcess:
|
|||||||
if res.status_code != 200:
|
if res.status_code != 200:
|
||||||
pytest.fail("newtmpuser count={} code={}: '{}'".format(index, res.status_code, res.text))
|
pytest.fail("newtmpuser count={} code={}: '{}'".format(index, res.status_code, res.text))
|
||||||
d = res.json()
|
d = res.json()
|
||||||
config = dict(addr=d["email"], mail_pw=d["password"])
|
config = {"addr": d["email"], "mail_pw": d["password"]}
|
||||||
print("newtmpuser {}: addr={}".format(index, config["addr"]))
|
print("newtmpuser {}: addr={}".format(index, config["addr"]))
|
||||||
self._configlist.append(config)
|
self._configlist.append(config)
|
||||||
yield config
|
yield config
|
||||||
@@ -229,7 +229,7 @@ def write_dict_to_dir(dic, target_dir):
|
|||||||
path.write_bytes(content)
|
path.write_bytes(content)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture()
|
||||||
def data(request):
|
def data(request):
|
||||||
class Data:
|
class Data:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
@@ -253,6 +253,7 @@ def data(request):
|
|||||||
if os.path.exists(fn):
|
if os.path.exists(fn):
|
||||||
return fn
|
return fn
|
||||||
print("WARNING: path does not exist: {!r}".format(fn))
|
print("WARNING: path does not exist: {!r}".format(fn))
|
||||||
|
return None
|
||||||
|
|
||||||
def read_path(self, bn, mode="r"):
|
def read_path(self, bn, mode="r"):
|
||||||
fn = self.get_path(bn)
|
fn = self.get_path(bn)
|
||||||
@@ -264,8 +265,11 @@ def data(request):
|
|||||||
|
|
||||||
|
|
||||||
class ACSetup:
|
class ACSetup:
|
||||||
"""accounts setup helper to deal with multiple configure-process
|
"""
|
||||||
and io & imap initialization phases. From tests, use the higher level
|
Accounts setup helper to deal with multiple configure-process
|
||||||
|
and io & imap initialization phases.
|
||||||
|
|
||||||
|
From tests, use the higher level
|
||||||
public ACFactory methods instead of its private helper class.
|
public ACFactory methods instead of its private helper class.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -289,7 +293,7 @@ class ACSetup:
|
|||||||
self._account2state[account] = self.CONFIGURED
|
self._account2state[account] = self.CONFIGURED
|
||||||
self.log("added already configured account", account, account.get_config("addr"))
|
self.log("added already configured account", account, account.get_config("addr"))
|
||||||
|
|
||||||
def start_configure(self, account, reconfigure=False):
|
def start_configure(self, account):
|
||||||
"""add an account and start its configure process."""
|
"""add an account and start its configure process."""
|
||||||
|
|
||||||
class PendingTracker:
|
class PendingTracker:
|
||||||
@@ -299,7 +303,7 @@ class ACSetup:
|
|||||||
|
|
||||||
account.add_account_plugin(PendingTracker(), name="pending_tracker")
|
account.add_account_plugin(PendingTracker(), name="pending_tracker")
|
||||||
self._account2state[account] = self.CONFIGURING
|
self._account2state[account] = self.CONFIGURING
|
||||||
account.configure(reconfigure=reconfigure)
|
account.configure()
|
||||||
self.log("started configure on", account)
|
self.log("started configure on", account)
|
||||||
|
|
||||||
def wait_one_configured(self, account):
|
def wait_one_configured(self, account):
|
||||||
@@ -411,7 +415,8 @@ class ACFactory:
|
|||||||
acc.disable_logging()
|
acc.disable_logging()
|
||||||
|
|
||||||
def get_next_liveconfig(self):
|
def get_next_liveconfig(self):
|
||||||
"""Base function to get functional online configurations
|
"""
|
||||||
|
Base function to get functional online configurations
|
||||||
where we can make valid SMTP and IMAP connections with.
|
where we can make valid SMTP and IMAP connections with.
|
||||||
"""
|
"""
|
||||||
configdict = next(self._liveconfig_producer).copy()
|
configdict = next(self._liveconfig_producer).copy()
|
||||||
@@ -465,8 +470,7 @@ class ACFactory:
|
|||||||
if fname_pub and fname_sec:
|
if fname_pub and fname_sec:
|
||||||
account._preconfigure_keypair(addr, fname_pub, fname_sec)
|
account._preconfigure_keypair(addr, fname_pub, fname_sec)
|
||||||
return True
|
return True
|
||||||
else:
|
print("WARN: could not use preconfigured keys for {!r}".format(addr))
|
||||||
print("WARN: could not use preconfigured keys for {!r}".format(addr))
|
|
||||||
|
|
||||||
def get_pseudo_configured_account(self, passphrase: Optional[str] = None) -> Account:
|
def get_pseudo_configured_account(self, passphrase: Optional[str] = None) -> Account:
|
||||||
# do a pseudo-configured account
|
# do a pseudo-configured account
|
||||||
@@ -476,14 +480,14 @@ class ACFactory:
|
|||||||
acname = ac._logid
|
acname = ac._logid
|
||||||
addr = "{}@offline.org".format(acname)
|
addr = "{}@offline.org".format(acname)
|
||||||
ac.update_config(
|
ac.update_config(
|
||||||
dict(
|
{
|
||||||
addr=addr,
|
"addr": addr,
|
||||||
displayname=acname,
|
"displayname": acname,
|
||||||
mail_pw="123",
|
"mail_pw": "123",
|
||||||
configured_addr=addr,
|
"configured_addr": addr,
|
||||||
configured_mail_pw="123",
|
"configured_mail_pw": "123",
|
||||||
configured="1",
|
"configured": "1",
|
||||||
)
|
},
|
||||||
)
|
)
|
||||||
self._preconfigure_key(ac, addr)
|
self._preconfigure_key(ac, addr)
|
||||||
self._acsetup.init_logging(ac)
|
self._acsetup.init_logging(ac)
|
||||||
@@ -494,12 +498,12 @@ class ACFactory:
|
|||||||
configdict = self.get_next_liveconfig()
|
configdict = self.get_next_liveconfig()
|
||||||
else:
|
else:
|
||||||
# XXX we might want to transfer the key to the new account
|
# XXX we might want to transfer the key to the new account
|
||||||
configdict = dict(
|
configdict = {
|
||||||
addr=cloned_from.get_config("addr"),
|
"addr": cloned_from.get_config("addr"),
|
||||||
mail_pw=cloned_from.get_config("mail_pw"),
|
"mail_pw": cloned_from.get_config("mail_pw"),
|
||||||
imap_certificate_checks=cloned_from.get_config("imap_certificate_checks"),
|
"imap_certificate_checks": cloned_from.get_config("imap_certificate_checks"),
|
||||||
smtp_certificate_checks=cloned_from.get_config("smtp_certificate_checks"),
|
"smtp_certificate_checks": cloned_from.get_config("smtp_certificate_checks"),
|
||||||
)
|
}
|
||||||
configdict.update(kwargs)
|
configdict.update(kwargs)
|
||||||
ac = self._get_cached_account(addr=configdict["addr"]) if cache else None
|
ac = self._get_cached_account(addr=configdict["addr"]) if cache else None
|
||||||
if ac is not None:
|
if ac is not None:
|
||||||
@@ -600,7 +604,7 @@ class ACFactory:
|
|||||||
acc._evtracker.wait_next_incoming_message()
|
acc._evtracker.wait_next_incoming_message()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture()
|
||||||
def acfactory(request, tmpdir, testprocess, data):
|
def acfactory(request, tmpdir, testprocess, data):
|
||||||
am = ACFactory(request=request, tmpdir=tmpdir, testprocess=testprocess, data=data)
|
am = ACFactory(request=request, tmpdir=tmpdir, testprocess=testprocess, data=data)
|
||||||
yield am
|
yield am
|
||||||
@@ -665,12 +669,12 @@ class BotProcess:
|
|||||||
ignored.append(line)
|
ignored.append(line)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture()
|
||||||
def tmp_db_path(tmpdir):
|
def tmp_db_path(tmpdir):
|
||||||
return tmpdir.join("test.db").strpath
|
return tmpdir.join("test.db").strpath
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture()
|
||||||
def lp():
|
def lp():
|
||||||
class Printer:
|
class Printer:
|
||||||
def sec(self, msg: str) -> None:
|
def sec(self, msg: str) -> None:
|
||||||
|
|||||||
@@ -77,11 +77,11 @@ class ConfigureTracker:
|
|||||||
self.account.remove_account_plugin(self)
|
self.account.remove_account_plugin(self)
|
||||||
|
|
||||||
def wait_smtp_connected(self):
|
def wait_smtp_connected(self):
|
||||||
"""wait until smtp is configured."""
|
"""Wait until SMTP is configured."""
|
||||||
self._smtp_finished.wait()
|
self._smtp_finished.wait()
|
||||||
|
|
||||||
def wait_imap_connected(self):
|
def wait_imap_connected(self):
|
||||||
"""wait until smtp is configured."""
|
"""Wait until IMAP is configured."""
|
||||||
self._imap_finished.wait()
|
self._imap_finished.wait()
|
||||||
|
|
||||||
def wait_progress(self, data1=None):
|
def wait_progress(self, data1=None):
|
||||||
@@ -91,7 +91,8 @@ class ConfigureTracker:
|
|||||||
break
|
break
|
||||||
|
|
||||||
def wait_finish(self, timeout=None):
|
def wait_finish(self, timeout=None):
|
||||||
"""wait until configure is completed.
|
"""
|
||||||
|
Wait until configure is completed.
|
||||||
|
|
||||||
Raise Exception if Configure failed
|
Raise Exception if Configure failed
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -15,5 +15,5 @@ if __name__ == "__main__":
|
|||||||
p,
|
p,
|
||||||
"-w",
|
"-w",
|
||||||
workspacedir,
|
workspacedir,
|
||||||
]
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ def test_fetch_existing(acfactory, lp, mvbox_move):
|
|||||||
# would also find the "Sent" folder, but it would be too late:
|
# would also find the "Sent" folder, but it would be too late:
|
||||||
# The sentbox thread, started by `start_io()`, would have seen that there is no
|
# The sentbox thread, started by `start_io()`, would have seen that there is no
|
||||||
# ConfiguredSentboxFolder and do nothing.
|
# ConfiguredSentboxFolder and do nothing.
|
||||||
acfactory._acsetup.start_configure(ac1, reconfigure=True)
|
acfactory._acsetup.start_configure(ac1)
|
||||||
acfactory.bring_accounts_online()
|
acfactory.bring_accounts_online()
|
||||||
assert_folders_configured(ac1)
|
assert_folders_configured(ac1)
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ def test_basic_imap_api(acfactory, tmpdir):
|
|||||||
imap2.shutdown()
|
imap2.shutdown()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.ignored
|
@pytest.mark.ignored()
|
||||||
def test_configure_generate_key(acfactory, lp):
|
def test_configure_generate_key(acfactory, lp):
|
||||||
# A slow test which will generate new keys.
|
# A slow test which will generate new keys.
|
||||||
acfactory.remove_preconfigured_keys()
|
acfactory.remove_preconfigured_keys()
|
||||||
@@ -510,7 +510,7 @@ def test_send_and_receive_message_markseen(acfactory, lp):
|
|||||||
idle2.wait_for_seen()
|
idle2.wait_for_seen()
|
||||||
|
|
||||||
lp.step("1")
|
lp.step("1")
|
||||||
for i in range(2):
|
for _i in range(2):
|
||||||
ev = ac1._evtracker.get_matching("DC_EVENT_MSG_READ")
|
ev = ac1._evtracker.get_matching("DC_EVENT_MSG_READ")
|
||||||
assert ev.data1 > const.DC_CHAT_ID_LAST_SPECIAL
|
assert ev.data1 > const.DC_CHAT_ID_LAST_SPECIAL
|
||||||
assert ev.data2 > const.DC_MSG_ID_LAST_SPECIAL
|
assert ev.data2 > const.DC_MSG_ID_LAST_SPECIAL
|
||||||
@@ -529,7 +529,7 @@ def test_send_and_receive_message_markseen(acfactory, lp):
|
|||||||
pass # mark_seen_messages() has generated events before it returns
|
pass # mark_seen_messages() has generated events before it returns
|
||||||
|
|
||||||
|
|
||||||
def test_moved_markseen(acfactory, lp):
|
def test_moved_markseen(acfactory):
|
||||||
"""Test that message already moved to DeltaChat folder is marked as seen."""
|
"""Test that message already moved to DeltaChat folder is marked as seen."""
|
||||||
ac1 = acfactory.new_online_configuring_account()
|
ac1 = acfactory.new_online_configuring_account()
|
||||||
ac2 = acfactory.new_online_configuring_account(mvbox_move=True)
|
ac2 = acfactory.new_online_configuring_account(mvbox_move=True)
|
||||||
@@ -553,7 +553,7 @@ def test_moved_markseen(acfactory, lp):
|
|||||||
ac2.mark_seen_messages([msg])
|
ac2.mark_seen_messages([msg])
|
||||||
uid = idle2.wait_for_seen()
|
uid = idle2.wait_for_seen()
|
||||||
|
|
||||||
assert len([a for a in ac2.direct_imap.conn.fetch(AND(seen=True, uid=U(uid, "*")))]) == 1
|
assert len(list(ac2.direct_imap.conn.fetch(AND(seen=True, uid=U(uid, "*"))))) == 1
|
||||||
|
|
||||||
|
|
||||||
def test_message_override_sender_name(acfactory, lp):
|
def test_message_override_sender_name(acfactory, lp):
|
||||||
@@ -832,7 +832,7 @@ def test_send_first_message_as_long_unicode_with_cr(acfactory, lp):
|
|||||||
lp.sec("sending multi-line non-unicode message from ac1 to ac2")
|
lp.sec("sending multi-line non-unicode message from ac1 to ac2")
|
||||||
text1 = (
|
text1 = (
|
||||||
"hello\nworld\nthis is a very long message that should be"
|
"hello\nworld\nthis is a very long message that should be"
|
||||||
+ " wrapped using format=flowed and unwrapped on the receiver"
|
" wrapped using format=flowed and unwrapped on the receiver"
|
||||||
)
|
)
|
||||||
msg_out = chat.send_text(text1)
|
msg_out = chat.send_text(text1)
|
||||||
assert not msg_out.is_encrypted()
|
assert not msg_out.is_encrypted()
|
||||||
@@ -894,7 +894,7 @@ def test_dont_show_emails(acfactory, lp):
|
|||||||
|
|
||||||
message in Drafts that is moved to Sent later
|
message in Drafts that is moved to Sent later
|
||||||
""".format(
|
""".format(
|
||||||
ac1.get_config("configured_addr")
|
ac1.get_config("configured_addr"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
ac1.direct_imap.append(
|
ac1.direct_imap.append(
|
||||||
@@ -908,7 +908,7 @@ def test_dont_show_emails(acfactory, lp):
|
|||||||
|
|
||||||
message in Sent
|
message in Sent
|
||||||
""".format(
|
""".format(
|
||||||
ac1.get_config("configured_addr")
|
ac1.get_config("configured_addr"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
ac1.direct_imap.append(
|
ac1.direct_imap.append(
|
||||||
@@ -922,7 +922,7 @@ def test_dont_show_emails(acfactory, lp):
|
|||||||
|
|
||||||
Unknown message in Spam
|
Unknown message in Spam
|
||||||
""".format(
|
""".format(
|
||||||
ac1.get_config("configured_addr")
|
ac1.get_config("configured_addr"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
ac1.direct_imap.append(
|
ac1.direct_imap.append(
|
||||||
@@ -936,7 +936,7 @@ def test_dont_show_emails(acfactory, lp):
|
|||||||
|
|
||||||
Unknown & malformed message in Spam
|
Unknown & malformed message in Spam
|
||||||
""".format(
|
""".format(
|
||||||
ac1.get_config("configured_addr")
|
ac1.get_config("configured_addr"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
ac1.direct_imap.append(
|
ac1.direct_imap.append(
|
||||||
@@ -950,7 +950,7 @@ def test_dont_show_emails(acfactory, lp):
|
|||||||
|
|
||||||
Unknown & malformed message in Spam
|
Unknown & malformed message in Spam
|
||||||
""".format(
|
""".format(
|
||||||
ac1.get_config("configured_addr")
|
ac1.get_config("configured_addr"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
ac1.direct_imap.append(
|
ac1.direct_imap.append(
|
||||||
@@ -964,7 +964,7 @@ def test_dont_show_emails(acfactory, lp):
|
|||||||
|
|
||||||
Actually interesting message in Spam
|
Actually interesting message in Spam
|
||||||
""".format(
|
""".format(
|
||||||
ac1.get_config("configured_addr")
|
ac1.get_config("configured_addr"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
ac1.direct_imap.append(
|
ac1.direct_imap.append(
|
||||||
@@ -978,7 +978,7 @@ def test_dont_show_emails(acfactory, lp):
|
|||||||
|
|
||||||
Unknown message in Junk
|
Unknown message in Junk
|
||||||
""".format(
|
""".format(
|
||||||
ac1.get_config("configured_addr")
|
ac1.get_config("configured_addr"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1710,7 +1710,7 @@ def test_system_group_msg_from_blocked_user(acfactory, lp):
|
|||||||
assert contact.is_blocked()
|
assert contact.is_blocked()
|
||||||
chat_on_ac2.remove_contact(ac1)
|
chat_on_ac2.remove_contact(ac1)
|
||||||
ac1._evtracker.get_matching("DC_EVENT_CHAT_MODIFIED")
|
ac1._evtracker.get_matching("DC_EVENT_CHAT_MODIFIED")
|
||||||
assert not ac1.get_self_contact() in chat_on_ac1.get_contacts()
|
assert ac1.get_self_contact() not in chat_on_ac1.get_contacts()
|
||||||
|
|
||||||
|
|
||||||
def test_set_get_group_image(acfactory, data, lp):
|
def test_set_get_group_image(acfactory, data, lp):
|
||||||
@@ -1784,7 +1784,7 @@ def test_connectivity(acfactory, lp):
|
|||||||
|
|
||||||
lp.sec(
|
lp.sec(
|
||||||
"Test that after calling start_io(), maybe_network() and waiting for `all_work_done()`, "
|
"Test that after calling start_io(), maybe_network() and waiting for `all_work_done()`, "
|
||||||
+ "all messages are fetched"
|
"all messages are fetched",
|
||||||
)
|
)
|
||||||
|
|
||||||
ac1.direct_imap.select_config_folder("inbox")
|
ac1.direct_imap.select_config_folder("inbox")
|
||||||
@@ -2146,7 +2146,7 @@ def test_group_quote(acfactory, lp):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"folder,move,expected_destination,",
|
("folder", "move", "expected_destination"),
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"xyz",
|
"xyz",
|
||||||
@@ -2298,7 +2298,7 @@ class TestOnlineConfigureFails:
|
|||||||
def test_invalid_password(self, acfactory):
|
def test_invalid_password(self, acfactory):
|
||||||
configdict = acfactory.get_next_liveconfig()
|
configdict = acfactory.get_next_liveconfig()
|
||||||
ac1 = acfactory.get_unconfigured_account()
|
ac1 = acfactory.get_unconfigured_account()
|
||||||
ac1.update_config(dict(addr=configdict["addr"], mail_pw="123"))
|
ac1.update_config({"addr": configdict["addr"], "mail_pw": "123"})
|
||||||
configtracker = ac1.configure()
|
configtracker = ac1.configure()
|
||||||
configtracker.wait_progress(500)
|
configtracker.wait_progress(500)
|
||||||
configtracker.wait_progress(0)
|
configtracker.wait_progress(0)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ from deltachat.tracker import ImexFailed
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"msgtext,res",
|
("msgtext", "res"),
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"Member Me (tmp1@x.org) removed by tmp2@x.org.",
|
"Member Me (tmp1@x.org) removed by tmp2@x.org.",
|
||||||
@@ -108,7 +108,7 @@ class TestOfflineAccountBasic:
|
|||||||
|
|
||||||
def test_update_config(self, acfactory):
|
def test_update_config(self, acfactory):
|
||||||
ac1 = acfactory.get_unconfigured_account()
|
ac1 = acfactory.get_unconfigured_account()
|
||||||
ac1.update_config(dict(mvbox_move=False))
|
ac1.update_config({"mvbox_move": False})
|
||||||
assert ac1.get_config("mvbox_move") == "0"
|
assert ac1.get_config("mvbox_move") == "0"
|
||||||
|
|
||||||
def test_has_savemime(self, acfactory):
|
def test_has_savemime(self, acfactory):
|
||||||
@@ -229,11 +229,11 @@ class TestOfflineContact:
|
|||||||
|
|
||||||
|
|
||||||
class TestOfflineChat:
|
class TestOfflineChat:
|
||||||
@pytest.fixture
|
@pytest.fixture()
|
||||||
def ac1(self, acfactory):
|
def ac1(self, acfactory):
|
||||||
return acfactory.get_pseudo_configured_account()
|
return acfactory.get_pseudo_configured_account()
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture()
|
||||||
def chat1(self, ac1):
|
def chat1(self, ac1):
|
||||||
return ac1.create_contact("some1@example.org", name="some1").create_chat()
|
return ac1.create_contact("some1@example.org", name="some1").create_chat()
|
||||||
|
|
||||||
@@ -257,7 +257,7 @@ class TestOfflineChat:
|
|||||||
assert chat2.id == chat1.id
|
assert chat2.id == chat1.id
|
||||||
assert chat2.get_name() == chat1.get_name()
|
assert chat2.get_name() == chat1.get_name()
|
||||||
assert chat1 == chat2
|
assert chat1 == chat2
|
||||||
assert not (chat1 != chat2)
|
assert not chat1.__ne__(chat2)
|
||||||
assert chat1 != chat3
|
assert chat1 != chat3
|
||||||
|
|
||||||
for ichat in ac1.get_chats():
|
for ichat in ac1.get_chats():
|
||||||
@@ -450,7 +450,7 @@ class TestOfflineChat:
|
|||||||
assert msg.filemime == "image/png"
|
assert msg.filemime == "image/png"
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"fn,typein,typeout",
|
("fn", "typein", "typeout"),
|
||||||
[
|
[
|
||||||
("r", None, "application/octet-stream"),
|
("r", None, "application/octet-stream"),
|
||||||
("r.txt", None, "text/plain"),
|
("r.txt", None, "text/plain"),
|
||||||
@@ -458,7 +458,7 @@ class TestOfflineChat:
|
|||||||
("r.txt", "image/png", "image/png"),
|
("r.txt", "image/png", "image/png"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_message_file(self, ac1, chat1, data, lp, fn, typein, typeout):
|
def test_message_file(self, chat1, data, lp, fn, typein, typeout):
|
||||||
lp.sec("sending file")
|
lp.sec("sending file")
|
||||||
fp = data.get_path(fn)
|
fp = data.get_path(fn)
|
||||||
msg = chat1.send_file(fp, typein)
|
msg = chat1.send_file(fp, typein)
|
||||||
@@ -694,7 +694,7 @@ class TestOfflineChat:
|
|||||||
chat1.set_draft(None)
|
chat1.set_draft(None)
|
||||||
assert chat1.get_draft() is None
|
assert chat1.get_draft() is None
|
||||||
|
|
||||||
def test_qr_setup_contact(self, acfactory, lp):
|
def test_qr_setup_contact(self, acfactory):
|
||||||
ac1 = acfactory.get_pseudo_configured_account()
|
ac1 = acfactory.get_pseudo_configured_account()
|
||||||
ac2 = acfactory.get_pseudo_configured_account()
|
ac2 = acfactory.get_pseudo_configured_account()
|
||||||
qr = ac1.get_setup_contact_qr()
|
qr = ac1.get_setup_contact_qr()
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ def test_empty_context():
|
|||||||
capi.lib.dc_context_unref(ctx)
|
capi.lib.dc_context_unref(ctx)
|
||||||
|
|
||||||
|
|
||||||
def test_dc_close_events(tmpdir, acfactory):
|
def test_dc_close_events(acfactory):
|
||||||
ac1 = acfactory.get_unconfigured_account()
|
ac1 = acfactory.get_unconfigured_account()
|
||||||
|
|
||||||
# register after_shutdown function
|
# register after_shutdown function
|
||||||
|
|||||||
@@ -50,18 +50,14 @@ commands =
|
|||||||
skipsdist = True
|
skipsdist = True
|
||||||
skip_install = True
|
skip_install = True
|
||||||
deps =
|
deps =
|
||||||
flake8
|
ruff
|
||||||
# isort 5.11.0 is broken: https://github.com/PyCQA/isort/issues/2031
|
|
||||||
isort<5.11.0
|
|
||||||
black
|
black
|
||||||
# pygments required by rst-lint
|
# pygments required by rst-lint
|
||||||
pygments
|
pygments
|
||||||
restructuredtext_lint
|
restructuredtext_lint
|
||||||
commands =
|
commands =
|
||||||
isort --check setup.py install_python_bindings.py src/deltachat examples/ tests/
|
|
||||||
black --check setup.py install_python_bindings.py src/deltachat examples/ tests/
|
black --check setup.py install_python_bindings.py src/deltachat examples/ tests/
|
||||||
flake8 src/deltachat
|
ruff src/deltachat tests/ examples/
|
||||||
flake8 tests/ examples/
|
|
||||||
rst-lint --encoding 'utf-8' README.rst
|
rst-lint --encoding 'utf-8' README.rst
|
||||||
|
|
||||||
[testenv:mypy]
|
[testenv:mypy]
|
||||||
@@ -102,7 +98,3 @@ timeout = 150
|
|||||||
timeout_func_only = True
|
timeout_func_only = True
|
||||||
markers =
|
markers =
|
||||||
ignored: ignore this test in default test runs, use --ignored to run.
|
ignored: ignore this test in default test runs, use --ignored to run.
|
||||||
|
|
||||||
[flake8]
|
|
||||||
max-line-length = 120
|
|
||||||
ignore = E203, E266, E501, W503
|
|
||||||
|
|||||||
@@ -2,12 +2,13 @@
|
|||||||
Remove old "dc" indices except for master which always stays.
|
Remove old "dc" indices except for master which always stays.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from requests import Session
|
|
||||||
import datetime
|
import datetime
|
||||||
import sys
|
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
MAXDAYS=7
|
from requests import Session
|
||||||
|
|
||||||
|
MAXDAYS = 7
|
||||||
|
|
||||||
session = Session()
|
session = Session()
|
||||||
session.headers["Accept"] = "application/json"
|
session.headers["Accept"] = "application/json"
|
||||||
@@ -54,7 +55,8 @@ def run():
|
|||||||
if not dates:
|
if not dates:
|
||||||
print(
|
print(
|
||||||
"%s has no releases" % (baseurl + username + "/" + indexname),
|
"%s has no releases" % (baseurl + username + "/" + indexname),
|
||||||
file=sys.stderr)
|
file=sys.stderr,
|
||||||
|
)
|
||||||
date = datetime.datetime.now()
|
date = datetime.datetime.now()
|
||||||
else:
|
else:
|
||||||
date = datetime.datetime(*max(dates))
|
date = datetime.datetime(*max(dates))
|
||||||
@@ -67,6 +69,5 @@ def run():
|
|||||||
subprocess.check_call(["devpi", "index", "-y", "--delete", url])
|
subprocess.check_call(["devpi", "index", "-y", "--delete", url])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
if __name__ == '__main__':
|
|
||||||
run()
|
run()
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import os
|
|
||||||
import json
|
import json
|
||||||
import re
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ def read_toml_version(relpath):
|
|||||||
res = regex_matches(relpath, rex)
|
res = regex_matches(relpath, rex)
|
||||||
if res is not None:
|
if res is not None:
|
||||||
return res.group(1)
|
return res.group(1)
|
||||||
raise ValueError("no version found in {}".format(relpath))
|
raise ValueError(f"no version found in {relpath}")
|
||||||
|
|
||||||
|
|
||||||
def replace_toml_version(relpath, newversion):
|
def replace_toml_version(relpath, newversion):
|
||||||
@@ -34,8 +34,8 @@ def replace_toml_version(relpath, newversion):
|
|||||||
for line in open(str(p)):
|
for line in open(str(p)):
|
||||||
m = rex.match(line)
|
m = rex.match(line)
|
||||||
if m is not None:
|
if m is not None:
|
||||||
print("{}: set version={}".format(relpath, newversion))
|
print(f"{relpath}: set version={newversion}")
|
||||||
f.write('version = "{}"\n'.format(newversion))
|
f.write(f'version = "{newversion}"\n')
|
||||||
else:
|
else:
|
||||||
f.write(line)
|
f.write(line)
|
||||||
os.rename(tmp_path, str(p))
|
os.rename(tmp_path, str(p))
|
||||||
@@ -44,7 +44,7 @@ def replace_toml_version(relpath, newversion):
|
|||||||
def read_json_version(relpath):
|
def read_json_version(relpath):
|
||||||
p = pathlib.Path(relpath)
|
p = pathlib.Path(relpath)
|
||||||
assert p.exists()
|
assert p.exists()
|
||||||
with open(p, "r") as f:
|
with open(p) as f:
|
||||||
json_data = json.loads(f.read())
|
json_data = json.loads(f.read())
|
||||||
return json_data["version"]
|
return json_data["version"]
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ def read_json_version(relpath):
|
|||||||
def update_package_json(relpath, newversion):
|
def update_package_json(relpath, newversion):
|
||||||
p = pathlib.Path(relpath)
|
p = pathlib.Path(relpath)
|
||||||
assert p.exists()
|
assert p.exists()
|
||||||
with open(p, "r") as f:
|
with open(p) as f:
|
||||||
json_data = json.loads(f.read())
|
json_data = json.loads(f.read())
|
||||||
json_data["version"] = newversion
|
json_data["version"] = newversion
|
||||||
with open(p, "w") as f:
|
with open(p, "w") as f:
|
||||||
@@ -63,7 +63,7 @@ def main():
|
|||||||
parser = ArgumentParser(prog="set_core_version")
|
parser = ArgumentParser(prog="set_core_version")
|
||||||
parser.add_argument("newversion")
|
parser.add_argument("newversion")
|
||||||
|
|
||||||
json_list = ["package.json", "deltachat-jsonrpc/typescript/package.json"]
|
json_list = ["package.json", "deltachat-jsonrpc/typescript/package.json"]
|
||||||
toml_list = [
|
toml_list = [
|
||||||
"Cargo.toml",
|
"Cargo.toml",
|
||||||
"deltachat-ffi/Cargo.toml",
|
"deltachat-ffi/Cargo.toml",
|
||||||
@@ -75,9 +75,9 @@ def main():
|
|||||||
except SystemExit:
|
except SystemExit:
|
||||||
print()
|
print()
|
||||||
for x in toml_list:
|
for x in toml_list:
|
||||||
print("{}: {}".format(x, read_toml_version(x)))
|
print(f"{x}: {read_toml_version(x)}")
|
||||||
for x in json_list:
|
for x in json_list:
|
||||||
print("{}: {}".format(x, read_json_version(x)))
|
print(f"{x}: {read_json_version(x)}")
|
||||||
print()
|
print()
|
||||||
raise SystemExit("need argument: new version, example: 1.25.0")
|
raise SystemExit("need argument: new version, example: 1.25.0")
|
||||||
|
|
||||||
@@ -92,19 +92,19 @@ def main():
|
|||||||
if "alpha" not in newversion:
|
if "alpha" not in newversion:
|
||||||
for line in open("CHANGELOG.md"):
|
for line in open("CHANGELOG.md"):
|
||||||
## 1.25.0
|
## 1.25.0
|
||||||
if line.startswith("## "):
|
if line.startswith("## ") and line[2:].strip().startswith(newversion):
|
||||||
if line[2:].strip().startswith(newversion):
|
break
|
||||||
break
|
|
||||||
else:
|
else:
|
||||||
raise SystemExit("CHANGELOG.md contains no entry for version: {}".format(newversion))
|
raise SystemExit(
|
||||||
|
f"CHANGELOG.md contains no entry for version: {newversion}"
|
||||||
|
)
|
||||||
|
|
||||||
for toml_filename in toml_list:
|
for toml_filename in toml_list:
|
||||||
replace_toml_version(toml_filename, newversion)
|
replace_toml_version(toml_filename, newversion)
|
||||||
|
|
||||||
for json_filename in json_list:
|
for json_filename in json_list:
|
||||||
update_package_json(json_filename, newversion)
|
update_package_json(json_filename, newversion)
|
||||||
|
|
||||||
|
|
||||||
print("running cargo check")
|
print("running cargo check")
|
||||||
subprocess.call(["cargo", "check"])
|
subprocess.call(["cargo", "check"])
|
||||||
|
|
||||||
@@ -114,13 +114,12 @@ def main():
|
|||||||
|
|
||||||
print("after commit, on master make sure to: ")
|
print("after commit, on master make sure to: ")
|
||||||
print("")
|
print("")
|
||||||
print(" git tag -a {}".format(newversion))
|
print(f" git tag -a {newversion}")
|
||||||
print(" git push origin {}".format(newversion))
|
print(f" git push origin {newversion}")
|
||||||
print(" git tag -a py-{}".format(newversion))
|
print(f" git tag -a py-{newversion}")
|
||||||
print(" git push origin py-{}".format(newversion))
|
print(f" git push origin py-{newversion}")
|
||||||
print("")
|
print("")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user