mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-11-04 10:47:32 +00:00
core: merge master into names registration
This commit is contained in:
commit
02b828b915
724 files changed
+114095
-5611
No files matched your search
@@ -56,15 +56,6 @@ ruff check && pyright && pytest tests/
|
||||
|
||||
Wire types under `src/simplex_chat/types/_*.py` are generated. Regenerate with `cabal test simplex-chat-test --test-options='--match Python'`.
|
||||
|
||||
## Release
|
||||
|
||||
Manual for now. Bump `_version.py:__version__`, build a wheel, upload to PyPI:
|
||||
|
||||
```bash
|
||||
uv build --wheel
|
||||
uv publish --token "$PYPI_TOKEN"
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
[AGPL-3.0](./LICENSE)
|
||||
@@ -1,16 +1,16 @@
|
||||
"""Package version and the simplex-chat-libs release it loads.
|
||||
|
||||
Written by scripts/ci/packages_release.py: the release PR bumps the version
|
||||
Written by scripts/ci/packages_release.py prepare: it bumps the version
|
||||
and pins new library releases with the SHA-256 of their assets.
|
||||
"""
|
||||
|
||||
__version__ = "7.1.0b6" # PEP 440 — read by hatchling for wheel metadata
|
||||
LIBS_RELEASE = "core-7.1.0.11" # simplex-chat-libs release tag
|
||||
__version__ = "7.1.0b6.post2" # PEP 440 — read by hatchling for wheel metadata
|
||||
LIBS_RELEASE = "core-7.1.0.14" # simplex-chat-libs release tag
|
||||
LIBS_SHA256 = {
|
||||
"simplex-chat-libs-linux-aarch64.zip": "d66aa1b30b9fa20225dad8ef75a9c605961e1c0adde2ba0283e2fcac4e15d260",
|
||||
"simplex-chat-libs-linux-x86_64-postgres.zip": "925b3166f969b12082ff8ee659e5f2a9933fe40120deb80c9aa3002a5ba6c803",
|
||||
"simplex-chat-libs-linux-x86_64.zip": "dabad0824e8813ea4a084fbf37e1ba4cf6dcea9cd8ccadc521fec200f246208a",
|
||||
"simplex-chat-libs-macos-aarch64.zip": "aa74608b149fff7508a47eaac4284dcc6d189c6ed8b7c18428df9158bc176a61",
|
||||
"simplex-chat-libs-macos-x86_64.zip": "a0e21457e1077be7f71b46c1c27452945b2cee2b6c50efb0004412ce5b7042ec",
|
||||
"simplex-chat-libs-windows-x86_64.zip": "c01dad574daa101a1cbfd31ddbf2b3d0d986a4a42f97d789d2f878b2050b2b4b",
|
||||
"simplex-chat-libs-linux-aarch64.zip": "375a6021f0c2e5d79b2cd192e17c6c22c8eae73f46e3758fba24ee5c26a671aa",
|
||||
"simplex-chat-libs-linux-x86_64-postgres.zip": "c3e8a54d37801e685c6361c4e7e53e1f5163d3aa9195dcfe686b6cf90e668ef3",
|
||||
"simplex-chat-libs-linux-x86_64.zip": "cc41ab9049d61be3b386d799fa9394f2870e8ae3b2b9b8745dd3f53ba99f211e",
|
||||
"simplex-chat-libs-macos-aarch64.zip": "ac3a46004f25976f71b3ead8a20b9a01c170fe12f125a0a6799ae97ea990f234",
|
||||
"simplex-chat-libs-macos-x86_64.zip": "0d85ffcccc3a6dfe3e0da18d0b7be9ef36ad27381f047ef66cfde67ee4a7e882",
|
||||
"simplex-chat-libs-windows-x86_64.zip": "f1d1c7efc333a5ec5328a5843c9d3c9ef038fb2886c0b60ac12e87a5c1e85bbd",
|
||||
}
|
||||
@@ -96,6 +96,7 @@ class Bot(Client):
|
||||
update_profile: bool = True,
|
||||
auto_accept: bool = True,
|
||||
business_address: bool = False,
|
||||
chat_layout: T.ChatLayout | None = None,
|
||||
allow_files: bool = False,
|
||||
log_contacts: bool = True,
|
||||
log_network: bool = False,
|
||||
@@ -115,6 +116,7 @@ class Bot(Client):
|
||||
self._update_address = update_address
|
||||
self._auto_accept = auto_accept
|
||||
self._business_address = business_address
|
||||
self._chat_layout = chat_layout
|
||||
self._allow_files = allow_files
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
@@ -153,6 +155,8 @@ class Bot(Client):
|
||||
desired: T.AddressSettings = {"businessAddress": self._business_address}
|
||||
if self._auto_accept:
|
||||
desired["autoAccept"] = {"acceptIncognito": False}
|
||||
if self._chat_layout is not None:
|
||||
desired["chatLayout"] = self._chat_layout
|
||||
if self._welcome is not None:
|
||||
desired["autoReply"] = (
|
||||
{"type": "text", "text": self._welcome}
|
||||
|
||||
@@ -23,6 +23,7 @@ class AddRelayResult(TypedDict):
|
||||
class AddressSettings(TypedDict):
|
||||
businessAddress: bool
|
||||
autoAccept: NotRequired["AutoAccept"]
|
||||
chatLayout: NotRequired["ChatLayout"]
|
||||
autoReply: NotRequired["MsgContent"]
|
||||
|
||||
class AgentCryptoError_DECRYPT_AES(TypedDict):
|
||||
@@ -188,6 +189,9 @@ class BadgeProof(TypedDict):
|
||||
class BadgeRedeemError_invalidCode(TypedDict):
|
||||
type: Literal["invalidCode"]
|
||||
|
||||
class BadgeRedeemError_invalidReceipt(TypedDict):
|
||||
type: Literal["invalidReceipt"]
|
||||
|
||||
class BadgeRedeemError_serviceNotConfigured(TypedDict):
|
||||
type: Literal["serviceNotConfigured"]
|
||||
|
||||
@@ -210,6 +214,7 @@ class BadgeRedeemError_credentialNotVerified(TypedDict):
|
||||
|
||||
BadgeRedeemError = (
|
||||
BadgeRedeemError_invalidCode
|
||||
| BadgeRedeemError_invalidReceipt
|
||||
| BadgeRedeemError_serviceNotConfigured
|
||||
| BadgeRedeemError_badgeActive
|
||||
| BadgeRedeemError_serviceError
|
||||
@@ -218,14 +223,85 @@ BadgeRedeemError = (
|
||||
| BadgeRedeemError_credentialNotVerified
|
||||
)
|
||||
|
||||
BadgeRedeemError_Tag = Literal["invalidCode", "serviceNotConfigured", "badgeActive", "serviceError", "invalidResponse", "unknownKeyIndex", "credentialNotVerified"]
|
||||
BadgeRedeemError_Tag = Literal["invalidCode", "invalidReceipt", "serviceNotConfigured", "badgeActive", "serviceError", "invalidResponse", "unknownKeyIndex", "credentialNotVerified"]
|
||||
|
||||
BadgeServiceErrorCode = Literal["bad_request", "unsupported_version", "unknown_purchase_key", "unknown_offer_id", "offer_disabled", "offer_mismatch", "product_unavailable", "payment_not_entitled", "payment_pending", "provider_unavailable", "rate_limited", "code_invalid", "code_used", "code_expired", "receipt_invalid", "receipt_used", "internal"]
|
||||
BadgeServiceErrorCode = Literal["bad_request", "unsupported_version", "unknown_purchase_key", "unknown_offer_id", "offer_disabled", "offer_mismatch", "product_unavailable", "payment_not_entitled", "payment_pending", "provider_unavailable", "provider_not_configured", "rate_limited", "code_invalid", "code_used", "code_expired", "receipt_invalid", "receipt_used", "test_purchase", "internal"]
|
||||
|
||||
BadgeStatus = Literal["active", "expired", "expiredOld", "failed", "unknownKey"]
|
||||
|
||||
BadgeType = Literal["supporter", "legend", "investor"]
|
||||
|
||||
class BlockToken_lineBreak(TypedDict):
|
||||
type: Literal["lineBreak"]
|
||||
|
||||
class BlockToken_softBreak(TypedDict):
|
||||
type: Literal["softBreak"]
|
||||
|
||||
class BlockToken_hardBreak(TypedDict):
|
||||
type: Literal["hardBreak"]
|
||||
|
||||
class BlockToken_paragraphBreak(TypedDict):
|
||||
type: Literal["paragraphBreak"]
|
||||
lineBreaks: int # int
|
||||
|
||||
class BlockToken_indent(TypedDict):
|
||||
type: Literal["indent"]
|
||||
|
||||
class BlockToken_headingMark(TypedDict):
|
||||
type: Literal["headingMark"]
|
||||
level: int # int
|
||||
|
||||
class BlockToken_headingClose(TypedDict):
|
||||
type: Literal["headingClose"]
|
||||
|
||||
class BlockToken_headingUnderline(TypedDict):
|
||||
type: Literal["headingUnderline"]
|
||||
level: int # int
|
||||
|
||||
class BlockToken_listMark(TypedDict):
|
||||
type: Literal["listMark"]
|
||||
number: NotRequired[str]
|
||||
|
||||
class BlockToken_quoteMark(TypedDict):
|
||||
type: Literal["quoteMark"]
|
||||
|
||||
class BlockToken_fenceMark(TypedDict):
|
||||
type: Literal["fenceMark"]
|
||||
language: NotRequired[str]
|
||||
|
||||
class BlockToken_rowMark(TypedDict):
|
||||
type: Literal["rowMark"]
|
||||
|
||||
class BlockToken_cellMark(TypedDict):
|
||||
type: Literal["cellMark"]
|
||||
|
||||
class BlockToken_delimiterRow(TypedDict):
|
||||
type: Literal["delimiterRow"]
|
||||
columns: list["TableAlign"]
|
||||
|
||||
class BlockToken_ruleMark(TypedDict):
|
||||
type: Literal["ruleMark"]
|
||||
|
||||
BlockToken = (
|
||||
BlockToken_lineBreak
|
||||
| BlockToken_softBreak
|
||||
| BlockToken_hardBreak
|
||||
| BlockToken_paragraphBreak
|
||||
| BlockToken_indent
|
||||
| BlockToken_headingMark
|
||||
| BlockToken_headingClose
|
||||
| BlockToken_headingUnderline
|
||||
| BlockToken_listMark
|
||||
| BlockToken_quoteMark
|
||||
| BlockToken_fenceMark
|
||||
| BlockToken_rowMark
|
||||
| BlockToken_cellMark
|
||||
| BlockToken_delimiterRow
|
||||
| BlockToken_ruleMark
|
||||
)
|
||||
|
||||
BlockToken_Tag = Literal["lineBreak", "softBreak", "hardBreak", "paragraphBreak", "indent", "headingMark", "headingClose", "headingUnderline", "listMark", "quoteMark", "fenceMark", "rowMark", "cellMark", "delimiterRow", "ruleMark"]
|
||||
|
||||
class BlockingInfo(TypedDict):
|
||||
reason: "BlockingReason"
|
||||
notice: NotRequired["ClientNotice"]
|
||||
@@ -688,6 +764,7 @@ class CIQuote(TypedDict):
|
||||
sentAt: str # ISO-8601 timestamp
|
||||
content: "MsgContent"
|
||||
formattedText: NotRequired[list["FormattedText"]]
|
||||
markdownBlocks: NotRequired[list["MarkdownBlock"]] # non-empty
|
||||
|
||||
class CIReaction(TypedDict):
|
||||
chatDir: "CIDirection"
|
||||
@@ -1220,6 +1297,7 @@ class ChatItem(TypedDict):
|
||||
content: "CIContent"
|
||||
mentions: dict[str, "CIMention"]
|
||||
formattedText: NotRequired[list["FormattedText"]]
|
||||
markdownBlocks: NotRequired[list["MarkdownBlock"]] # non-empty
|
||||
quotedItem: NotRequired["CIQuote"]
|
||||
reactions: list["CIReactionCount"]
|
||||
file: NotRequired["CIFile"]
|
||||
@@ -1230,6 +1308,8 @@ class ChatItemDeletion(TypedDict):
|
||||
deletedChatItem: "AChatItem"
|
||||
toChatItem: NotRequired["AChatItem"]
|
||||
|
||||
ChatLayout = Literal["classic", "terminal"]
|
||||
|
||||
class ChatListQuery_filters(TypedDict):
|
||||
type: Literal["filters"]
|
||||
favorite: bool
|
||||
@@ -1243,7 +1323,7 @@ ChatListQuery = ChatListQuery_filters | ChatListQuery_search
|
||||
|
||||
ChatListQuery_Tag = Literal["filters", "search"]
|
||||
|
||||
ChatPeerType = Literal["human", "bot", "business"]
|
||||
ChatPeerType = Literal["human", "bot", "business", "agent"]
|
||||
|
||||
# Used in API commands. Chat scope can only be passed with groups.
|
||||
|
||||
@@ -1523,6 +1603,7 @@ class Contact(TypedDict):
|
||||
chatTags: list[int] # int64
|
||||
chatItemTTL: NotRequired[int] # int64
|
||||
uiThemes: NotRequired["UIThemeEntityOverrides"]
|
||||
chatLayout: NotRequired["ChatLayout"]
|
||||
chatDeleted: bool
|
||||
customData: NotRequired[dict[str, object]]
|
||||
|
||||
@@ -1564,6 +1645,7 @@ class ContactShortLinkData(TypedDict):
|
||||
profile: "Profile"
|
||||
message: NotRequired["MsgContent"]
|
||||
business: bool
|
||||
chatLayout: NotRequired["ChatLayout"]
|
||||
localBadge: NotRequired["LocalBadge"]
|
||||
|
||||
ContactStatus = Literal["active", "deleted", "deletedByUser", "rejected"]
|
||||
@@ -1795,8 +1877,11 @@ class FileTransferMeta(TypedDict):
|
||||
|
||||
FileType = Literal["normal", "roster"]
|
||||
|
||||
# The format markup is a token of block Markdown syntax; its text is the syntax. The format escape is the backslash of a backslash escape. Bold singleStar: written with single stars.
|
||||
|
||||
class Format_bold(TypedDict):
|
||||
type: Literal["bold"]
|
||||
singleStar: bool
|
||||
|
||||
class Format_italic(TypedDict):
|
||||
type: Literal["italic"]
|
||||
@@ -1850,6 +1935,13 @@ class Format_email(TypedDict):
|
||||
class Format_phone(TypedDict):
|
||||
type: Literal["phone"]
|
||||
|
||||
class Format_escape(TypedDict):
|
||||
type: Literal["escape"]
|
||||
|
||||
class Format_markup(TypedDict):
|
||||
type: Literal["markup"]
|
||||
token: "BlockToken"
|
||||
|
||||
Format = (
|
||||
Format_bold
|
||||
| Format_italic
|
||||
@@ -1866,9 +1958,11 @@ Format = (
|
||||
| Format_mention
|
||||
| Format_email
|
||||
| Format_phone
|
||||
| Format_escape
|
||||
| Format_markup
|
||||
)
|
||||
|
||||
Format_Tag = Literal["bold", "italic", "strikeThrough", "snippet", "secret", "small", "colored", "uri", "hyperLink", "simplexLink", "simplexName", "command", "mention", "email", "phone"]
|
||||
Format_Tag = Literal["bold", "italic", "strikeThrough", "snippet", "secret", "small", "colored", "uri", "hyperLink", "simplexLink", "simplexName", "command", "mention", "email", "phone", "escape", "markup"]
|
||||
|
||||
class FormattedText(TypedDict):
|
||||
format: NotRequired["Format"]
|
||||
@@ -2086,6 +2180,7 @@ class GroupProfile(TypedDict):
|
||||
publicGroup: NotRequired["PublicGroupProfile"]
|
||||
groupPreferences: NotRequired["GroupPreferences"]
|
||||
memberAdmission: NotRequired["GroupMemberAdmission"]
|
||||
chatLayout: NotRequired["ChatLayout"]
|
||||
|
||||
class GroupRelay(TypedDict):
|
||||
groupRelayId: int # int64
|
||||
@@ -2191,6 +2286,11 @@ class LinkPreview(TypedDict):
|
||||
image: str
|
||||
content: NotRequired["LinkContent"]
|
||||
|
||||
class ListEntry(TypedDict):
|
||||
number: NotRequired[str]
|
||||
content: "SegmentRange"
|
||||
items: list["ListEntry"]
|
||||
|
||||
class LocalBadge(TypedDict):
|
||||
badge: "BadgeInfo"
|
||||
status: "BadgeStatus"
|
||||
@@ -2210,6 +2310,50 @@ class LocalProfile(TypedDict):
|
||||
contactDomain: NotRequired["SimplexDomainClaim"]
|
||||
contactDomainVerified: NotRequired["DomainVerification"]
|
||||
|
||||
class MarkdownBlock_paragraph(TypedDict):
|
||||
type: Literal["paragraph"]
|
||||
content: "SegmentRange"
|
||||
|
||||
class MarkdownBlock_heading(TypedDict):
|
||||
type: Literal["heading"]
|
||||
level: int # int
|
||||
content: "SegmentRange"
|
||||
|
||||
class MarkdownBlock_codeBlock(TypedDict):
|
||||
type: Literal["codeBlock"]
|
||||
language: NotRequired[str]
|
||||
content: "SegmentRange"
|
||||
|
||||
class MarkdownBlock_quote(TypedDict):
|
||||
type: Literal["quote"]
|
||||
content: "SegmentRange"
|
||||
|
||||
class MarkdownBlock_list(TypedDict):
|
||||
type: Literal["list"]
|
||||
items: list["ListEntry"] # non-empty
|
||||
|
||||
class MarkdownBlock_table(TypedDict):
|
||||
type: Literal["table"]
|
||||
columns: list["TableAlign"] # non-empty
|
||||
header: list["SegmentRange"]
|
||||
rows: list[list["SegmentRange"]]
|
||||
|
||||
class MarkdownBlock_rule(TypedDict):
|
||||
type: Literal["rule"]
|
||||
mark: "SegmentRange"
|
||||
|
||||
MarkdownBlock = (
|
||||
MarkdownBlock_paragraph
|
||||
| MarkdownBlock_heading
|
||||
| MarkdownBlock_codeBlock
|
||||
| MarkdownBlock_quote
|
||||
| MarkdownBlock_list
|
||||
| MarkdownBlock_table
|
||||
| MarkdownBlock_rule
|
||||
)
|
||||
|
||||
MarkdownBlock_Tag = Literal["paragraph", "heading", "codeBlock", "quote", "list", "table", "rule"]
|
||||
|
||||
MemberCriteria = Literal["all"]
|
||||
|
||||
# Connection link sent in a message - only short links are allowed.
|
||||
@@ -2977,6 +3121,11 @@ class SecurityCode(TypedDict):
|
||||
securityCode: str
|
||||
verifiedAt: str # ISO-8601 timestamp
|
||||
|
||||
SegmentRange = TypedDict("SegmentRange", {
|
||||
"from": int, # int
|
||||
"to": int, # int
|
||||
})
|
||||
|
||||
class SimplePreference(TypedDict):
|
||||
allow: "FeatureAllowed"
|
||||
|
||||
@@ -3629,6 +3778,8 @@ class SupportGroupPreference(TypedDict):
|
||||
|
||||
SwitchPhase = Literal["started", "confirmed", "secured", "completed"]
|
||||
|
||||
TableAlign = Literal["none", "left", "center", "right"]
|
||||
|
||||
class TimedMessagesGroupPreference(TypedDict):
|
||||
enable: "GroupFeatureEnabled"
|
||||
ttl: NotRequired[int] # int
|
||||
@@ -3691,6 +3842,7 @@ class UIThemeEntityOverride(TypedDict):
|
||||
class UIThemeEntityOverrides(TypedDict):
|
||||
light: NotRequired["UIThemeEntityOverride"]
|
||||
dark: NotRequired["UIThemeEntityOverride"]
|
||||
chatLayout: NotRequired["ChatLayout"]
|
||||
|
||||
class UpdatedMessage(TypedDict):
|
||||
msgContent: "MsgContent"
|
||||
|
||||
@@ -729,6 +729,7 @@ class _StubApi:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.profiles: list[dict] = []
|
||||
self.address_settings: list[dict] = []
|
||||
self.user: dict = {"userId": 1, "profile": {"displayName": "x", "fullName": ""}}
|
||||
self.address: dict | None = None
|
||||
|
||||
@@ -758,8 +759,8 @@ class _StubApi:
|
||||
async def api_get_user_address(self, _user_id):
|
||||
return self.address
|
||||
|
||||
async def api_set_address_settings(self, _user_id, _settings):
|
||||
pass
|
||||
async def api_set_address_settings(self, _user_id, settings):
|
||||
self.address_settings.append(settings)
|
||||
|
||||
async def send_chat_cmd(self, _cmd):
|
||||
return {"type": "cmdOk"}
|
||||
@@ -942,6 +943,55 @@ def test_sync_profile_keeps_the_bot_address_in_the_profile(monkeypatch):
|
||||
assert api.profiles[0]["contactLink"] == "https://l"
|
||||
|
||||
|
||||
def test_bot_address_settings_include_the_chat_layout(monkeypatch):
|
||||
import simplex_chat.client as client_mod
|
||||
|
||||
api = _StubApi()
|
||||
api.address = {
|
||||
"connLinkContact": {"connFullLink": "https://l"},
|
||||
"addressSettings": {"businessAddress": False, "autoAccept": {"acceptIncognito": False}},
|
||||
}
|
||||
monkeypatch.setattr(client_mod, "ChatApi", _init_returning(api))
|
||||
bot = Bot(
|
||||
profile=BotProfile(display_name="x"),
|
||||
db=SqliteDb(file_prefix="/tmp/test"),
|
||||
update_profile=False,
|
||||
chat_layout="terminal",
|
||||
)
|
||||
|
||||
async def go():
|
||||
async with bot:
|
||||
pass
|
||||
|
||||
asyncio.run(go())
|
||||
assert api.address_settings == [
|
||||
{"businessAddress": False, "autoAccept": {"acceptIncognito": False}, "chatLayout": "terminal"}
|
||||
]
|
||||
|
||||
|
||||
def test_bot_address_settings_remove_the_chat_layout_not_in_bot_settings(monkeypatch):
|
||||
import simplex_chat.client as client_mod
|
||||
|
||||
api = _StubApi()
|
||||
api.address = {
|
||||
"connLinkContact": {"connFullLink": "https://l"},
|
||||
"addressSettings": {"businessAddress": False, "chatLayout": "terminal"},
|
||||
}
|
||||
monkeypatch.setattr(client_mod, "ChatApi", _init_returning(api))
|
||||
bot = Bot(
|
||||
profile=BotProfile(display_name="x"),
|
||||
db=SqliteDb(file_prefix="/tmp/test"),
|
||||
update_profile=False,
|
||||
)
|
||||
|
||||
async def go():
|
||||
async with bot:
|
||||
pass
|
||||
|
||||
asyncio.run(go())
|
||||
assert api.address_settings == [{"businessAddress": False, "autoAccept": {"acceptIncognito": False}}]
|
||||
|
||||
|
||||
def test_profile_can_be_replaced(monkeypatch):
|
||||
c, _ = _client_with_stub_api(monkeypatch)
|
||||
c.profile = Profile(display_name="other", full_name="Other")
|
||||
|
||||
Reference in new issue
Block a user