diff --git a/.gitignore b/.gitignore index 5bc6e21..129023e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ dist/ __pycache__/ .mypy_cache/ +*.egg-info diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 35630a7..8c8d3c3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,6 +27,6 @@ repos: name: mypy stages: [commit] language: system - entry: poetry run mypy **/*.py + entry: poetry run mypy pykeybasebot/ examples/ types: [python] pass_filenames: false diff --git a/Makefile b/Makefile index d309327..5988db5 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,10 @@ test: poetry run black . --check types: - @mkdir -p pykeybasebot/types/{keybase1,gregor1,chat1,stellar1}/ + @mkdir -p pykeybasebot/types/keybase1 + @mkdir -p pykeybasebot/types/gregor1 + @mkdir -p pykeybasebot/types/chat1 + @mkdir -p pykeybasebot/types/stellar1 $(AVDLC) -b -l python -t -o pykeybasebot/types/keybase1 $(PROTOCOL_PATH)/avdl/keybase1/*.avdl $(AVDLC) -b -l python -t -o pykeybasebot/types/gregor1 $(PROTOCOL_PATH)/avdl/gregor1/*.avdl $(AVDLC) -b -l python -t -o pykeybasebot/types/chat1 $(PROTOCOL_PATH)/avdl/chat1/*.avdl diff --git a/README.md b/README.md index 6c811d6..772963c 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,16 @@ poetry install This will set up a virtualenv for you and install all the dependencies needed into it! +Remember that if you're making changes to pykeybasebot and want to test them +locally, you'll need to first uninstall previously installed pykeybasebot, +then install your local version: + +``` +pip uninstall pykeybasebot +poetry build +pip install ./dist/pykeybasebot-{tags}.whl +``` + ### Static code analysis tools We use a few different static analysis tools to perform linting, type-checking, formatting, etc. The correct versions should be install when you run `poetry install`, but you'll probably want to configure your editor to work with: diff --git a/examples/0_echo_everything.py b/examples/0_echo_everything.py index 7220fb2..4abb4c4 100644 --- a/examples/0_echo_everything.py +++ b/examples/0_echo_everything.py @@ -20,7 +20,9 @@ if "win32" in sys.platform: # Windows specific event-loop policy - asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) + asyncio.set_event_loop_policy( + asyncio.WindowsProactorEventLoopPolicy() # type: ignore + ) class Handler: diff --git a/examples/1_clap_for_everything.py b/examples/1_clap_for_everything.py index 5cfb6f0..5aefe84 100644 --- a/examples/1_clap_for_everything.py +++ b/examples/1_clap_for_everything.py @@ -18,7 +18,9 @@ if "win32" in sys.platform: # Windows specific event-loop policy - asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) + asyncio.set_event_loop_policy( + asyncio.WindowsProactorEventLoopPolicy() # type: ignore + ) async def handler(bot, event): diff --git a/examples/1_pingpong.py b/examples/1_pingpong.py index 6724551..9f7f597 100644 --- a/examples/1_pingpong.py +++ b/examples/1_pingpong.py @@ -20,7 +20,9 @@ if "win32" in sys.platform: # Windows specific event-loop policy - asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) + asyncio.set_event_loop_policy( + asyncio.WindowsProactorEventLoopPolicy() # type: ignore + ) class Handler: diff --git a/examples/2_get_paid.py b/examples/2_get_paid.py index 56399eb..6a0bc1d 100644 --- a/examples/2_get_paid.py +++ b/examples/2_get_paid.py @@ -19,7 +19,9 @@ if "win32" in sys.platform: # Windows specific event-loop policy - asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) + asyncio.set_event_loop_policy( + asyncio.WindowsProactorEventLoopPolicy() # type: ignore + ) DollarsPerLumen = 0.06 diff --git a/examples/3_async_locking.py b/examples/3_async_locking.py index 5c83129..0395181 100644 --- a/examples/3_async_locking.py +++ b/examples/3_async_locking.py @@ -22,7 +22,9 @@ if "win32" in sys.platform: # Windows specific event-loop policy - asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) + asyncio.set_event_loop_policy( + asyncio.WindowsProactorEventLoopPolicy() # type: ignore + ) async def alert(bot, wait_sec, channel): diff --git a/examples/3_poll_with_reactions.py b/examples/3_poll_with_reactions.py index c38ab31..d26a39e 100644 --- a/examples/3_poll_with_reactions.py +++ b/examples/3_poll_with_reactions.py @@ -21,7 +21,9 @@ if "win32" in sys.platform: # Windows specific event-loop policy - asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) + asyncio.set_event_loop_policy( + asyncio.WindowsProactorEventLoopPolicy() # type: ignore + ) async def make_a_poll(): diff --git a/examples/3_scrolling_messages.py b/examples/3_scrolling_messages.py index 1923bcb..426e402 100644 --- a/examples/3_scrolling_messages.py +++ b/examples/3_scrolling_messages.py @@ -21,7 +21,9 @@ if "win32" in sys.platform: # Windows specific event-loop policy - asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) + asyncio.set_event_loop_policy( + asyncio.WindowsProactorEventLoopPolicy() # type: ignore + ) def rotate(message): diff --git a/examples/3_simple_storage.py b/examples/3_simple_storage.py new file mode 100644 index 0000000..c6b185d --- /dev/null +++ b/examples/3_simple_storage.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 + +################################### +# WHAT IS IN THIS EXAMPLE? +# +# Keybase has added an encrypted key-value store intended to support +# security-conscious bot development with persistent state. It is a place to +# store small bits of data that are +# (1) encrypted for a team or user (via the user's implicit self-team: e.g. +# alice,alice), +# (2) persistent across logins +# (3) fast and durable. +# +# It supports putting, getting, listing, and deleting. There is also concurrency +# support, but check out 5_secret_storage.py for that. A team has many +# namespaces, a namespace has many entryKeys, and an entryKey has one current +# entryValue. Namespaces and entryKeys are in cleartext, and the Keybase client +# service will encrypt and sign the entryValue on the way in (as well as +# decrypt and verify on the way out) so keybase servers cannot see it or forge +# it. +# +# This example shows how you can use KVStoreClient to interact with the team +# encrypted key-value store. +################################### + +import asyncio +import logging +import sys + +from pykeybasebot import Bot +from pykeybasebot.errors import DeleteNonExistentError, RevisionError + +logging.basicConfig(level=logging.DEBUG) + +if "win32" in sys.platform: + # Windows specific event-loop policy + asyncio.set_event_loop_policy( + asyncio.WindowsProactorEventLoopPolicy() # type: ignore + ) + + +async def simple_user(): + team = "yourbookclub" + + def noop_handler(*args, **kwargs): + pass + + bot = Bot(handler=noop_handler()) + + namespace = "current-favorites" + key = "Sam" + + # put with default revision + # note: if revision=None, the server does a get (to get + # the latest revision number) then a put (with revision + # number + 1); this operation is not atomic. + value = "The Left Hand of Darkness" + res = await bot.kvstore.put(team, namespace, key, value, revision=None) + print("PUT: ", res) + rev = res.revision + + # fail put + try: + res = await bot.kvstore.put( + team, namespace, key, "Fahrenheit 451", revision=rev + ) + except RevisionError as e: + print("EXPECTING PUT FAIL: ", e) + + # list namespaces + res = await bot.kvstore.list_namespaces(team) + print("LIST NAMESPACES: ", res) + assert len(res.namespaces) > 0 + + # list entryKeys + res = await bot.kvstore.list_entrykeys(team, namespace) + print("LIST ENTRYKEYS: ", res) + assert len(res.entry_keys) > 0 + + # get + res = await bot.kvstore.get(team, namespace, key) + print("GET: ", res) + assert res.entry_value == value + + # fail delete + try: + res = await bot.kvstore.delete(team, namespace, key, revision=rev + 2) + except RevisionError as e: + print("EXPECTING DELETE FAIL: ", e) + + # delete + res = await bot.kvstore.delete(team, namespace, key, revision=rev + 1) + print("DELETE: ", res) + assert res.revision == rev + 1 + + # fail delete + try: + res = await bot.kvstore.delete(team, namespace, key, revision=rev + 2) + except DeleteNonExistentError as e: + print("EXPECTING DELETE FAIL: ", e) + + # get + res = await bot.kvstore.get(team, namespace, key) + print("GET: ", res) + assert res.entry_value == "" + + +async def main(): + print("Starting 3_simple_storage example...") + await simple_user() + print("...3_simple_storage example is complete.") + + +asyncio.run(main()) diff --git a/examples/4_framework.py b/examples/4_framework.py index d9ebd1c..3a259a0 100644 --- a/examples/4_framework.py +++ b/examples/4_framework.py @@ -24,7 +24,9 @@ if "win32" in sys.platform: # Windows specific event-loop policy - asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) + asyncio.set_event_loop_policy( + asyncio.WindowsProactorEventLoopPolicy() # type: ignore + ) def force_async(fn): diff --git a/examples/4_totp_storage.py b/examples/4_totp_storage.py new file mode 100644 index 0000000..3928781 --- /dev/null +++ b/examples/4_totp_storage.py @@ -0,0 +1,202 @@ +#!/usr/bin/env python3 + +################################### +# WHAT IS IN THIS EXAMPLE? +# +# Keybase has added an encrypted key-value store; see 3_simple_storage.py for +# more information. +# +# This example shows how you can build a simple TOTP chat bot that makes use of +# the team encrypted key-value store. +################################### + +import asyncio +import json +import logging +import os +import sys +from enum import Enum + +import pyotp # type: ignore + +from pykeybasebot import Bot, EventType + +logging.basicConfig(level=logging.DEBUG) + +if "win32" in sys.platform: + # Windows specific event-loop policy + asyncio.set_event_loop_policy( + asyncio.WindowsProactorEventLoopPolicy() # type: ignore + ) + + +class TotpMsg(Enum): + ADD = "add" + REMOVE = "remove" + NOW = "now" + LIST = "list" + HELP = "help" + + +class TotpHandler: + """ + TotpHandler handles commands sent via chat and uses the team key-value store to + provide TOTP client functionality. + + TotpHandler listens to chat messages of the form: + + `!totp add ` + `!totp {now|remove} ` + `!totp {list|help}` + + For each provisioned key, the handler stores in the namespace "totp" one + row, with the key "" and the json blob value "{"secret": <16 char base32 + secret>}". + + This Keybase bot can be used in place of MFA apps like Google Authenticator and Authy + for login processes that require two-step verification. This bot uses the + pyotp library, so it is also compatible with other MFA apps. + + A Keybase TOTP bot could be useful for logins where you're already using a strong + password, but are required to also use TOTP. Or it could be perfect for adding + replay attack resistance to the physical keypad lock protecting your team's ice + cream freezer.... Note that depending on the login process and how you use this bot + with your Keybase team setup, this bot may not be appropriate for threat models + that require a physical "second factor" device. + + For more information on TOTP, see https://pyotp.readthedocs.io/en/latest/, + https://tools.ietf.org/html/rfc6238. + """ + + MSG_PREFIX = "!totp" + NAMESPACE = "totp" + + def __init__(self): + self.handlers = { + TotpMsg.HELP.value: self.handle_help, + TotpMsg.LIST.value: self.handle_list, + TotpMsg.ADD.value: self.handle_add, + TotpMsg.REMOVE.value: self.handle_remove, + TotpMsg.NOW.value: self.handle_now, + } + + def to_json(self, secret): + return {"secret": secret} + + async def add(self, bot, team, issuer, secret): + val = json.dumps(self.to_json(secret)) + await bot.kvstore.put(team, self.NAMESPACE, issuer, val) + + async def remove(self, bot, team, issuer): + # throws exception if nothing to delete + await bot.kvstore.delete(team, self.NAMESPACE, issuer) + + async def list(self, bot, team): + # returns all TOTP entryKeys (the issuers) in this team + res = await bot.kvstore.list_entrykeys(team, self.NAMESPACE) + if res.entry_keys: + return [e.entry_key for e in res.entry_keys] + else: + return [] + + async def now(self, bot, team, issuer): + res = await bot.kvstore.get(team, self.NAMESPACE, issuer) + if bot.kvstore.is_present(res): + # if secret is present + secret = json.loads(res.entry_value)["secret"] + return pyotp.TOTP(secret).now() + else: + return None + + async def __call__(self, bot, event): + members_type = event.msg.channel.members_type + if not event.type == EventType.CHAT: + return + + channel = event.msg.channel + user = event.msg.sender.username + + # support teams and implicit self teams + team = channel.name + if members_type == "impteamnative" and channel.name == user: + team = "{0},{0}".format(channel.name) + + msg = "" + try: + msg = event.msg.content.text.body.strip().split(" ") + except AttributeError: + return + + if len(msg) < 2 or msg[0] != self.MSG_PREFIX: + return + + action = msg[1] + if action in self.handlers: + return await self.handlers[action](bot, channel, team, msg, action) + await bot.chat.send(channel, "invalid !totp command") + return + + async def handle_help(self, bot, channel, team, msg, action): + if len(msg) == 2: + # chat: "!totp help" + send_msg = "Available commands:\ + \n`!totp add `\ + \n`!totp {now|remove} `\ + \n`!totp {list|help}`" + await bot.chat.send(channel, send_msg) + return + + async def handle_list(self, bot, channel, team, msg, action): + if len(msg) == 2: + # chat: "!totp list" + ns = await self.list(bot, team) + await bot.chat.send(channel, str(ns)) + return + + async def handle_now(self, bot, channel, team, msg, action): + if len(msg) == 3: + # chat: "!totp now " + issuer = msg[2] + send_msg = "Error getting current TOTP for {}".format(issuer) + code = await self.now(bot, team, issuer) + if code: + send_msg = "Current TOTP for {}: {}".format(issuer, code) + await bot.chat.send(channel, send_msg) + return + + async def handle_add(self, bot, channel, team, msg, action): + if len(msg) == 4: + issuer, secret = msg[2], msg[3] + # chat: "!totp add " + send_msg = "Error adding TOTP for {0}".format(issuer) + try: + await self.add(bot, team, issuer, secret) + send_msg = "TOTP added for {}".format(issuer) + except Exception as e: + print(e) + finally: + await bot.chat.send(channel, send_msg) + return + + async def handle_remove(self, bot, channel, team, msg, action): + if len(msg) == 3: + issuer = msg[2] + # chat: "!totp remove " + send_msg = "No keys to remove for {}".format(issuer) + try: + await self.remove(bot, team, issuer) + send_msg = "Removed TOTP keys for {}".format(issuer) + except Exception as e: + print(e) + finally: + await bot.chat.send(channel, send_msg) + return + + +username = "yourbot" + +bot = Bot( + username=username, paperkey=os.environ["KEYBASE_PAPERKEY"], handler=TotpHandler() +) + +asyncio.run(bot.start({})) diff --git a/examples/5_customclient.py b/examples/5_customclient.py index 9396c69..5827a30 100644 --- a/examples/5_customclient.py +++ b/examples/5_customclient.py @@ -19,7 +19,9 @@ if "win32" in sys.platform: # Windows specific event-loop policy - asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) + asyncio.set_event_loop_policy( + asyncio.WindowsProactorEventLoopPolicy() # type: ignore + ) class CustomClient(ChatClient): diff --git a/examples/5_secret_storage.py b/examples/5_secret_storage.py new file mode 100644 index 0000000..9a15d36 --- /dev/null +++ b/examples/5_secret_storage.py @@ -0,0 +1,455 @@ +#!/usr/bin/env python3 + +################################### +# WHAT IS IN THIS EXAMPLE? +# +# Keybase has added an encrypted key-value store; see 3_simple_storage.py for +# more information. +# +# This example implements a simple bot to manage hackerspace tool rentals. It +# shows one way you can obfuscate entryKeys (which are not encrypted) by +# storing their HMACs, so that no one but your team (not even +# Keybase) can know about the names of all the cool tools you have; you can do +# something similar to hide namespaces. +# +# Additionally this example handles concurrent writes by using explicit revision +# numbers to prevent one user from unintentionally clobbering another user's +# rental updates. +# ################################### + +import asyncio +import hmac +import json +import logging +import secrets +import sys +from base64 import b64decode, b64encode +from typing import Dict, List, Tuple, Union + +from pykeybasebot import Bot, KVStoreClient +from pykeybasebot.errors import DeleteNonExistentError, RevisionError +from pykeybasebot.types import keybase1 + +logging.basicConfig(level=logging.DEBUG) + +if "win32" in sys.platform: + # Windows specific event-loop policy + asyncio.set_event_loop_policy( + asyncio.WindowsProactorEventLoopPolicy() # type: ignore + ) + + +class CustomKVStoreBot(Bot): + """ + Custom bot that has some stateful clients. + """ + + def __init__(self, *args, **kwargs): + basic_client = KVStoreClient(self) + secret_kvstore_client = SecretKeyKVStoreClient(basic_client) # is stateful + self._trying_secret_kvstore_client = TryingKVStoreClient(secret_kvstore_client) + super().__init__(*args, **kwargs) + + @property + def kvstore(self): + return self._trying_secret_kvstore_client + + +class TryingKVStoreClient: + """ + TryingKVStoreClient tries kvstore write actions with explicit revision numbers. + If it fails to write, it does a "get" and returns the get result. + """ + + def __init__(self, client): + self.kvstore = client + + async def put( + self, + team: str, + namespace: str, + entry_key: str, + entry_value: Dict[str, str], + revision: Union[int, None] = None, + ) -> Union[keybase1.KVPutResult, keybase1.KVGetResult]: + try: + res: keybase1.KVPutResult = await self.kvstore.put( + team, namespace, entry_key, entry_value, revision + ) + return res # successful put. return KVPutResult + except RevisionError: + get = await self.get(team, namespace, entry_key) + return get # failed put. return KVGetResult. + + async def delete( + self, + team: str, + namespace: str, + entry_key: str, + revision: Union[int, None] = None, + ) -> Union[keybase1.KVDeleteEntryResult, keybase1.KVGetResult]: + try: + res: keybase1.KVDeleteEntryResult = await self.kvstore.delete( + team, namespace, entry_key, revision + ) + return res # successful delete. return KVDeleteEntryResult + except (RevisionError, DeleteNonExistentError): + get = await self.get(team, namespace, entry_key) + return get # failed put. return KVGetResult. + return res + + async def get( + self, team: str, namespace: str, entry_key: str + ) -> keybase1.KVGetResult: + res = await self.kvstore.get(team, namespace, entry_key) + return res + + async def list_entrykeys( + self, team: str, namespace: str + ) -> keybase1.KVListEntryResult: + res = await self.kvstore.list_entrykeys(team, namespace) + return res + + +def bytes_to_str(x): + return b64encode(x).decode("utf-8") + + +def str_to_bytes(x): + return b64decode(x.encode("utf-8")) + + +class SecretKeyKVStoreClient: + """ + A SecretKeyKVStoreClient that hides the entryKeys from Keybase servers. + It does so by HMACing entryKeys using a per-(team, namespace) secret, + and storing the HMAC instead of the plaintext entryKey. This approach + does not handle any secret rotation, and does not expect the secret to + change. + + The plaintext entryKey is stored in it's corresponding JSON entryValue + under the key "_key" to enable listing. + + This approach does not hide memory access patterns. Also, Keybase + servers prevent a removed team member from continuing to access a team's + data, but if that were somehow bypassed*, a former team member who still + knows the HMAC secret can check for the presence of specific entryKeys + (*but you probably have bigger issues to deal with in that case...). + """ + + KEY_KEY = "_key" + SECRET_KEY = "_secret" + SECRET_NUM_BYTES = 32 + + def __init__(self, kvstore_client): + # secrets = {team: {namespace: secret}} + self.secrets: Dict[str, Dict[str, bytes]] = {} + self.kvstore: KVStoreClient = kvstore_client + + async def load_secret(self, team, namespace) -> bytes: + if team not in self.secrets or namespace not in self.secrets[team]: + secret = secrets.token_bytes(self.SECRET_NUM_BYTES) + try: + # we don't expect self.SECRET_KEY's revision > 0 + await self.kvstore.put( + team, namespace, self.SECRET_KEY, bytes_to_str(secret), revision=1 + ) + except RevisionError: + res: keybase1.KVGetResult = await self.kvstore.get( + team, namespace, self.SECRET_KEY + ) + secret = str_to_bytes(res.entry_value) + if team not in self.secrets: + self.secrets[team] = {} + self.secrets[team][namespace] = secret + return self.secrets[team][namespace] + + async def hmac_key(self, team, namespace, entry_key) -> str: + secret = await self.load_secret(team, namespace) + return hmac.new(secret, entry_key.encode("utf-8")).hexdigest() + + async def put( + self, + team: str, + namespace: str, + entry_key: str, + entry_value: Dict[str, str], + revision: Union[int, None] = None, + ) -> keybase1.KVPutResult: + entry_value[SecretKeyKVStoreClient.KEY_KEY] = entry_key + h = await self.hmac_key(team, namespace, entry_key) + res = await self.kvstore.put( + team, namespace, h, json.dumps(entry_value), revision + ) + res.entry_key = entry_key + return res + + async def delete( + self, + team: str, + namespace: str, + entry_key: str, + revision: Union[int, None] = None, + ) -> keybase1.KVDeleteEntryResult: + h = await self.hmac_key(team, namespace, entry_key) + res = await self.kvstore.delete(team, namespace, h, revision) + res.entry_key = entry_key + return res + + async def get( + self, team: str, namespace: str, entry_key: str + ) -> keybase1.KVGetResult: + h = await self.hmac_key(team, namespace, entry_key) + res = await self.kvstore.get(team, namespace, h) + res.entry_key = entry_key + return res + + async def list_entrykeys( + self, team: str, namespace: str + ) -> keybase1.KVListEntryResult: + res = await self.kvstore.list_entrykeys(team, namespace) + if res.entry_keys: + for e in res.entry_keys: + if not e.entry_key.startswith("_"): + get_res = await self.kvstore.get(team, namespace, e.entry_key) + e.entry_key = json.loads(get_res.entry_value)[self.KEY_KEY] + return res + + +class RentalBotClient: + """ + Wraps a KVStoreClient to expose methods to handle tool rentals. + """ + + NAMESPACE = "rental" + + def __init__(self, bot): + self.kvstore = bot.kvstore + + async def lookup(self, team, tool) -> keybase1.KVGetResult: + res = await self.kvstore.get(team, self.NAMESPACE, tool) + return res + + async def add(self, team, tool) -> Tuple[bool, Union[keybase1.KVGetResult, None]]: + """ + returns tuple + (whether action is successful, + most recent get result if applicable) + """ + res = await self.lookup(team, tool) + if res.entry_value != "": + return (True, res) # if tool already exists, return get + expected_revision = res.revision + 1 + res = await self.kvstore.put(team, self.NAMESPACE, tool, {}, expected_revision) + if type(res) == keybase1.KVGetResult: + return (False, res) + else: + return (True, None) + + async def remove( + self, team, tool + ) -> Tuple[bool, Union[keybase1.KVGetResult, None]]: + """ + returns tuple + (whether action is successful, + most recent get result if applicable) + """ + res = await self.lookup(team, tool) + if res.entry_value == "": + return (True, res) # if tool already doesn't exist, return get + expected_revision = res.revision + 1 + res = await self.kvstore.delete(team, self.NAMESPACE, tool, expected_revision) + if type(res) == keybase1.KVGetResult: + return (False, res) + else: + return (True, None) + + async def reserve( + self, team, user, tool, day + ) -> Tuple[bool, Union[keybase1.KVGetResult, None]]: + """ + reserve a tool for a given day if that day is already not reserved. + note: if you reserve a not-added or deleted tool, it will add the tool + + returns tuple + (whether action is successful, + most recent get result if applicable) + """ + res = await self.lookup(team, tool) + info = json.loads(res.entry_value) if res.entry_value != "" else {} + if day in info: + return (False, res) # failed to put because day is already reserved. + else: + info[day] = user + expected_revision = res.revision + 1 + res = await self.kvstore.put( + team, self.NAMESPACE, tool, info, expected_revision + ) + if type(res) == keybase1.KVGetResult: + return (False, res) + else: + return (True, None) + + async def unreserve( + self, team, user, tool, day + ) -> Tuple[bool, Union[keybase1.KVGetResult, None]]: + """ + unreserve a tool for a given day if that day is currently reserved by + the given user. + note: if you unreserve a not-added or deleted tool, it will not add the tool + + returns tuple + (whether action is successful, + most recent get result if applicable) + """ + res = await self.lookup(team, tool) + info = json.loads(res.entry_value) if res.entry_value != "" else {} + if day not in info: + # a noop because currently not reserved + return (True, res) + if day in info and info[day] != user: + # failed to put because current reserver is not user + return (False, res) + expected_revision = res.revision + 1 + res = await self.kvstore.put( + team, self.NAMESPACE, tool, info, expected_revision + ) + if type(res) == keybase1.KVGetResult: + return (False, res) + else: + return (True, None) + + async def list_tools(self, team) -> List[str]: + res = await self.kvstore.list_entrykeys(team, self.NAMESPACE) + keys = ( + [e.entry_key for e in res.entry_keys if not e.entry_key.startswith("_")] + if res.entry_keys + else [] + ) + return keys + + +async def basic_rental_users(bot, rental, team): + user1 = "Jo" + user2 = "Charlie" + date1 = "2044-03-12" + date2 = "2044-06-12" + date3 = "2044-06-13" + tool = "laz0rs" + + (ok, res) = await rental.remove(team, tool) + print("REMOVE: ", ok, res) + assert ok + + res = await rental.list_tools(team) + print("LIST TOOLS: ", res) + + res = await rental.lookup(team, tool) + print("LOOKUP: ", res) + + (ok, res) = await rental.add(team, tool) + print("ADD: ", ok, res) + assert ok + + (ok, res) = await rental.remove(team, tool) + print("REMOVE: ", ok, res) + assert ok + + (ok, res) = await rental.add(team, tool) + print("ADD: ", ok, res) + assert ok + + (ok, res) = await rental.reserve(team, user1, tool, date1) + print("RESERVE: ", ok, res) + assert ok + + (ok, res) = await rental.reserve(team, user1, tool, date1) + print("EXPECTING RESERVE FAIL: ", ok, res) + assert not ok + + (ok, res) = await rental.reserve(team, user2, tool, date2) + print("RESERVE: ", ok, res) + assert ok + + res = await rental.lookup(team, tool) + print("LOOKUP: ", res) + + (ok, res) = await rental.unreserve(team, user1, tool, date3) + print("UNRESERVE: ", ok, res) + assert ok + + (ok, res) = await rental.unreserve(team, user1, tool, date2) + print("EXPECTING UNRESERVE FAIL: ", ok, res) + assert not ok + + (ok, res) = await rental.unreserve(team, user1, tool, date1) + print("UNRESERVE: ", ok, res) + assert ok + + res = await rental.lookup(team, tool) + print("LOOKUP: ", res) + + +async def concurrent_rental_users(bot, rental, team): + tool = "time travel machine" + + async def concurrent_rental_user(user_id: int): + date = "2044-10-0{}".format(user_id) + user = "user{}".format(user_id) + + i = 0 + while True: + # keep trying to reserve for user's unique date until successful + (ok, res) = await rental.reserve(team, user, tool, date) + i += 1 + print("{}, attempt {}, TRY TO RESERVE: {}, {}".format(user, i, ok, res)) + if ok: + # success + return + + async def pre(): + while True: + (ok, res) = await rental.remove(team, tool) + if ok: + print("REMOVE: ", ok, res) + break + + async def post(): + # check that the tool has been reserved for all 5 unique dates + res = await rental.lookup(team, tool) + print("LOOKUP: {}".format(res)) + assert len(json.loads(res.entry_value)) == 6 # one key is "_key" + + await asyncio.wait_for(pre(), timeout=5.0) + # have 5 users concurrently try to reserve the same tool for 5 unique dates + await asyncio.gather( + concurrent_rental_user(1), + concurrent_rental_user(2), + concurrent_rental_user(3), + concurrent_rental_user(4), + concurrent_rental_user(5), + ) + await post() + + +async def main(): + print("Starting 5_secret_storage example...") + + team = "yourhackerspace" + + def noop_handler(*args, **kwargs): + pass + + bot = CustomKVStoreBot(handler=noop_handler(), keybase="/home/user/keybase") + rental = RentalBotClient(bot) + + print("...basic rental actions...") + await basic_rental_users(bot, rental, team) + + print("...multiple users try to reserve...") + await concurrent_rental_users(bot, rental, team) + + print("...5_secret_storage example is complete.") + + +asyncio.run(main()) diff --git a/poetry.lock b/poetry.lock index 5a7f0fc..9d0a3b6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -20,7 +20,7 @@ description = "Classes Without Boilerplate" name = "attrs" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "19.1.0" +version = "19.3.0" [[package]] category = "dev" @@ -54,7 +54,18 @@ description = "Python package for providing Mozilla's CA Bundle." name = "certifi" optional = false python-versions = "*" -version = "2019.6.16" +version = "2019.9.11" + +[[package]] +category = "dev" +description = "Foreign Function Interface for Python calling C code." +name = "cffi" +optional = false +python-versions = "*" +version = "1.13.1" + +[package.dependencies] +pycparser = "*" [[package]] category = "dev" @@ -87,7 +98,7 @@ description = "Easily serialize dataclasses to and from JSON" name = "dataclasses-json" optional = false python-versions = ">=3.6" -version = "0.3.2" +version = "0.3.5" [package.dependencies] marshmallow = ">=3.0.1,<4.0.0" @@ -136,10 +147,11 @@ version = "2.8" [[package]] category = "dev" description = "Read metadata from Python packages" +marker = "python_version < \"3.8\"" name = "importlib-metadata" optional = false python-versions = ">=2.7,!=3.0,!=3.1,!=3.2,!=3.3" -version = "0.19" +version = "0.23" [package.dependencies] zipp = ">=0.5" @@ -158,7 +170,7 @@ description = "A lightweight library for converting complex datatypes to and fro name = "marshmallow" optional = false python-versions = ">=3.5" -version = "3.0.3" +version = "3.2.1" [[package]] category = "main" @@ -206,7 +218,7 @@ description = "Experimental type system extensions for programs checked with the name = "mypy-extensions" optional = false python-versions = "*" -version = "0.4.1" +version = "0.4.3" [[package]] category = "dev" @@ -214,10 +226,9 @@ description = "Core utilities for Python packages" name = "packaging" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "19.1" +version = "19.2" [package.dependencies] -attrs = "*" pyparsing = ">=2.0.2" six = "*" @@ -235,10 +246,12 @@ description = "plugin and hook calling mechanisms for python" name = "pluggy" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "0.12.0" +version = "0.13.0" [package.dependencies] -importlib-metadata = ">=0.12" +[package.dependencies.importlib-metadata] +python = "<3.8" +version = ">=0.12" [[package]] category = "dev" @@ -256,6 +269,14 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "2.5.0" +[[package]] +category = "dev" +description = "C parser in Python" +name = "pycparser" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.19" + [[package]] category = "dev" description = "passive checker of Python programs" @@ -272,6 +293,26 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" version = "2.4.2" +[[package]] +category = "dev" +description = "Python binding to the Networking and Cryptography (NaCl) library" +name = "pynacl" +optional = false +python-versions = "*" +version = "1.3.0" + +[package.dependencies] +cffi = ">=1.4.1" +six = "*" + +[[package]] +category = "dev" +description = "Python One Time Password Library" +name = "pyotp" +optional = false +python-versions = "*" +version = "2.3.0" + [[package]] category = "dev" description = "Python parsing module" @@ -286,7 +327,7 @@ description = "pytest: simple powerful testing with Python" name = "pytest" optional = false python-versions = ">=3.5" -version = "5.1.1" +version = "5.2.2" [package.dependencies] atomicwrites = ">=1.0" @@ -371,7 +412,7 @@ description = "Fast, Extensible Progress Meter" name = "tqdm" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*" -version = "4.35.0" +version = "4.36.1" [[package]] category = "dev" @@ -379,7 +420,7 @@ description = "Collection of utilities for publishing packages on PyPI" name = "twine" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.13.0" +version = "1.15.0" [package.dependencies] pkginfo = ">=1.4.2" @@ -422,10 +463,11 @@ description = "Runtime inspection utilities for typing module." name = "typing-inspect" optional = false python-versions = "*" -version = "0.4.0" +version = "0.5.0" [package.dependencies] mypy-extensions = ">=0.3.0" +typing-extensions = ">=3.7.4" [[package]] category = "dev" @@ -433,7 +475,7 @@ description = "HTTP library with thread-safe connection pooling, file post, and name = "urllib3" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4" -version = "1.25.3" +version = "1.25.6" [[package]] category = "dev" @@ -454,6 +496,7 @@ version = "0.5.1" [[package]] category = "dev" description = "Backport of pathlib-compatible object wrapper for zip files" +marker = "python_version < \"3.8\"" name = "zipp" optional = false python-versions = ">=2.7" @@ -463,54 +506,58 @@ version = "0.6.0" more-itertools = "*" [metadata] -content-hash = "d7c99caee6a2a73116d45f1f353961edfc153b294e18839d8c81aa3b90128469" +content-hash = "6fcdcbc809e43240cdcf32d22cce32ab0a87ce290f9fd0a6e7e2703eab4639ce" python-versions = "^3.7" [metadata.hashes] appdirs = ["9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", "d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"] atomicwrites = ["03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", "75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"] -attrs = ["69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", "f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399"] +attrs = ["08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", "f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"] black = ["817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739", "e030a9a28f542debc08acceb273f228ac422798e5215ba2a791a6ddeaaca22a5"] bleach = ["213336e49e102af26d9cde77dd2d0397afabc5a6bf2fed985dc35b5d1e285a16", "3fdf7f77adcf649c9911387df51254b813185e32b2c6619f690b593a617e19fa"] -certifi = ["046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939", "945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695"] +certifi = ["e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50", "fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef"] +cffi = ["00d890313797d9fe4420506613384b43099ad7d2b905c0752dbcc3a6f14d80fa", "0cf9e550ac6c5e57b713437e2f4ac2d7fd0cd10336525a27224f5fc1ec2ee59a", "0ea23c9c0cdd6778146a50d867d6405693ac3b80a68829966c98dd5e1bbae400", "193697c2918ecdb3865acf6557cddf5076bb39f1f654975e087b67efdff83365", "1ae14b542bf3b35e5229439c35653d2ef7d8316c1fffb980f9b7647e544baa98", "1e389e069450609c6ffa37f21f40cce36f9be7643bbe5051ab1de99d5a779526", "263242b6ace7f9cd4ea401428d2d45066b49a700852334fd55311bde36dcda14", "33142ae9807665fa6511cfa9857132b2c3ee6ddffb012b3f0933fc11e1e830d5", "364f8404034ae1b232335d8c7f7b57deac566f148f7222cef78cf8ae28ef764e", "47368f69fe6529f8f49a5d146ddee713fc9057e31d61e8b6dc86a6a5e38cecc1", "4895640844f17bec32943995dc8c96989226974dfeb9dd121cc45d36e0d0c434", "558b3afef987cf4b17abd849e7bedf64ee12b28175d564d05b628a0f9355599b", "5ba86e1d80d458b338bda676fd9f9d68cb4e7a03819632969cf6d46b01a26730", "63424daa6955e6b4c70dc2755897f5be1d719eabe71b2625948b222775ed5c43", "6381a7d8b1ebd0bc27c3bc85bc1bfadbb6e6f756b4d4db0aa1425c3719ba26b4", "6381ab708158c4e1639da1f2a7679a9bbe3e5a776fc6d1fd808076f0e3145331", "6fd58366747debfa5e6163ada468a90788411f10c92597d3b0a912d07e580c36", "728ec653964655d65408949b07f9b2219df78badd601d6c49e28d604efe40599", "7cfcfda59ef1f95b9f729c56fe8a4041899f96b72685d36ef16a3440a0f85da8", "819f8d5197c2684524637f940445c06e003c4a541f9983fd30d6deaa2a5487d8", "825ecffd9574557590e3225560a8a9d751f6ffe4a49e3c40918c9969b93395fa", "8a2bcae2258d00fcfc96a9bde4a6177bc4274fe033f79311c5dd3d3148c26518", "9009e917d8f5ef780c2626e29b6bc126f4cb2a4d43ca67aa2b40f2a5d6385e78", "9c77564a51d4d914ed5af096cd9843d90c45b784b511723bd46a8a9d09cf16fc", "a19089fa74ed19c4fe96502a291cfdb89223a9705b1d73b3005df4256976142e", "a40ed527bffa2b7ebe07acc5a3f782da072e262ca994b4f2085100b5a444bbb2", "b8f09f21544b9899defb09afbdaeb200e6a87a2b8e604892940044cf94444644", "bb75ba21d5716abc41af16eac1145ab2e471deedde1f22c6f99bd9f995504df0", "e22a00c0c81ffcecaf07c2bfb3672fa372c50e2bd1024ffee0da191c1b27fc71", "e55b5a746fb77f10c83e8af081979351722f6ea48facea79d470b3731c7b2891", "ec2fa3ee81707a5232bf2dfbd6623fdb278e070d596effc7e2d788f2ada71a05", "fd82eb4694be712fcae03c717ca2e0fc720657ac226b80bbb597e971fc6928c2"] chardet = ["84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", "fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"] click = ["2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"] colorama = ["05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", "f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"] -dataclasses-json = ["065a94e07599b28830b55ac17e3a26c49ef88e209513140f2ef1a1954dd303c6", "d5883036560707fb785bc705fd965a0c9b94b44d10acca8ddde9dcc40dc12d61"] +dataclasses-json = ["3f348a132c6c84772b99fca50c447ef3b8382d274fd9a539c958dd9b93ba4806", "8851be971187d22a898247ffff9b23de6d5d1db93b8e648997a71a2e4023d13c"] docutils = ["6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0", "9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827", "a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99"] entrypoints = ["589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", "c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"] flake8 = ["19241c1cbc971b9962473e4438a2ca19749a7dd002dd1a946eaba171b4114548", "8e9dfa3cecb2400b3738a42c54c3043e821682b9c840b0448c0503f781130696"] idna = ["c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", "ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"] -importlib-metadata = ["23d3d873e008a513952355379d93cbcab874c58f4f034ff657c7a87422fa64e8", "80d2de76188eabfbfcf27e6a37342c2827801e59c4cc14b0371c56fed43820e3"] +importlib-metadata = ["aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26", "d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af"] isort = ["54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1", "6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"] -marshmallow = ["159c3ed37094d66867bbacdf2e7effd7c7ad88c6b11f9b398ff5ea1d118508c3", "51188df086da5c427c3c193faddf7f95857ee4053dbf2d083e5cbfd846b2fb29"] +marshmallow = ["077b4612f5d3b9333b736fdc6b963d2b46d409070f44ff3e6c4109645c673e83", "9a2f3e8ea5f530a9664e882d7d04b58650f46190178b2264c72b7d20399d28f0"] marshmallow-enum = ["38e697e11f45a8e64b4a1e664000897c659b60aa57bfa18d44e226a9920b6e58", "57161ab3dbfde4f57adeb12090f39592e992b9c86d206d02f6bd03ebec60f072"] mccabe = ["ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", "dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"] more-itertools = ["409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", "92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4"] mypy = ["0107bff4f46a289f0e4081d59b77cef1c48ea43da5a0dbf0005d54748b26df2a", "07957f5471b3bb768c61f08690c96d8a09be0912185a27a68700f3ede99184e4", "10af62f87b6921eac50271e667cc234162a194e742d8e02fc4ddc121e129a5b0", "11fd60d2f69f0cefbe53ce551acf5b1cec1a89e7ce2d47b4e95a84eefb2899ae", "15e43d3b1546813669bd1a6ec7e6a11d2888db938e0607f7b5eef6b976671339", "352c24ba054a89bb9a35dd064ee95ab9b12903b56c72a8d3863d882e2632dc76", "437020a39417e85e22ea8edcb709612903a9924209e10b3ec6d8c9f05b79f498", "49925f9da7cee47eebf3420d7c0e00ec662ec6abb2780eb0a16260a7ba25f9c4", "6724fcd5777aa6cebfa7e644c526888c9d639bd22edd26b2a8038c674a7c34bd", "7a17613f7ea374ab64f39f03257f22b5755335b73251d0d253687a69029701ba", "cdc1151ced496ca1496272da7fc356580e95f2682be1d32377c22ddebdf73c91"] -mypy-extensions = ["37e0e956f41369209a3d5f34580150bcacfabaa57b33a15c0b25f4b5725e0812", "b16cabe759f55e3409a7d231ebd2841378fb0c27a5d1994719e340e4f429ac3e"] -packaging = ["a7ac867b97fdc07ee80a8058fe4435ccd274ecc3b0ed61d852d7d53055528cf9", "c491ca87294da7cc01902edbe30a5bc6c4c28172b5138ab4e4aa1b9d7bfaeafe"] +mypy-extensions = ["090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", "2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"] +packaging = ["28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47", "d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108"] pkginfo = ["7424f2c8511c186cd5424bbf31045b77435b37a8d604990b79d4e70d741148bb", "a6d9e40ca61ad3ebd0b72fbadd4fba16e4c0e4df0428c041e01e06eb6ee71f32"] -pluggy = ["0825a152ac059776623854c1543d65a4ad408eb3d33ee114dff91e57ec6ae6fc", "b9817417e95936bf75d85d3f8767f7df6cdde751fc40aed3bb3074cbcb77757c"] +pluggy = ["0db4b7601aae1d35b4a033282da476845aa19185c1e6964b25cf324b5e4ec3e6", "fa5fa1622fa6dd5c030e9cad086fa19ef6a0cf6d7a2d12318e10cb49d6d68f34"] py = ["64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", "dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"] pycodestyle = ["95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", "e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"] +pycparser = ["a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"] pyflakes = ["17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", "d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"] pygments = ["71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", "881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297"] +pynacl = ["05c26f93964373fc0abe332676cb6735f0ecad27711035b9472751faa8521255", "0c6100edd16fefd1557da078c7a31e7b7d7a52ce39fdca2bec29d4f7b6e7600c", "0d0a8171a68edf51add1e73d2159c4bc19fc0718e79dec51166e940856c2f28e", "1c780712b206317a746ace34c209b8c29dbfd841dfbc02aa27f2084dd3db77ae", "2424c8b9f41aa65bbdbd7a64e73a7450ebb4aa9ddedc6a081e7afcc4c97f7621", "2d23c04e8d709444220557ae48ed01f3f1086439f12dbf11976e849a4926db56", "30f36a9c70450c7878053fa1344aca0145fd47d845270b43a7ee9192a051bf39", "37aa336a317209f1bb099ad177fef0da45be36a2aa664507c5d72015f956c310", "4943decfc5b905748f0756fdd99d4f9498d7064815c4cf3643820c9028b711d1", "53126cd91356342dcae7e209f840212a58dcf1177ad52c1d938d428eebc9fee5", "57ef38a65056e7800859e5ba9e6091053cd06e1038983016effaffe0efcd594a", "5bd61e9b44c543016ce1f6aef48606280e45f892a928ca7068fba30021e9b786", "6482d3017a0c0327a49dddc8bd1074cc730d45db2ccb09c3bac1f8f32d1eb61b", "7d3ce02c0784b7cbcc771a2da6ea51f87e8716004512493a2b69016326301c3b", "a14e499c0f5955dcc3991f785f3f8e2130ed504fa3a7f44009ff458ad6bdd17f", "a39f54ccbcd2757d1d63b0ec00a00980c0b382c62865b61a505163943624ab20", "aabb0c5232910a20eec8563503c153a8e78bbf5459490c49ab31f6adf3f3a415", "bd4ecb473a96ad0f90c20acba4f0bf0df91a4e03a1f4dd6a4bdc9ca75aa3a715", "bf459128feb543cfca16a95f8da31e2e65e4c5257d2f3dfa8c0c1031139c9c92", "e2da3c13307eac601f3de04887624939aca8ee3c9488a0bb0eca4fb9401fc6b1", "f67814c38162f4deb31f68d590771a29d5ae3b1bd64b75cf232308e5c74777e0"] +pyotp = ["c88f37fd47541a580b744b42136f387cdad481b560ef410c0d85c957eb2a2bc0", "fc537e8acd985c5cbf51e11b7d53c42276fee017a73aec7c07380695671ca1a1"] pyparsing = ["6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80", "d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4"] -pytest = ["95b1f6db806e5b1b5b443efeb58984c24945508f93a866c1719e1a507a957d7c", "c3d5020755f70c82eceda3feaf556af9a341334414a8eca521a18f463bcead88"] +pytest = ["27abc3fef618a01bebb1f0d6d303d2816a99aa87a5968ebc32fe971be91eb1e6", "58cee9e09242937e136dbb3dab466116ba20d6b7828c7620f23947f37eb4dae4"] readme-renderer = ["bb16f55b259f27f75f640acf5e00cf897845a8b3e4731b5c1a436e4b8529202f", "c8532b79afc0375a85f10433eca157d6b50f7d6990f337fa498c96cd4bfc203d"] requests = ["11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", "9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"] requests-toolbelt = ["380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f", "968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"] six = ["3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", "d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"] stringcase = ["48a06980661908efe8d9d34eab2b6c13aefa2163b3ced26972902e3bdfd87008"] toml = ["229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", "235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e", "f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"] -tqdm = ["1be3e4e3198f2d0e47b928e9d9a8ec1b63525db29095cec1467f4c5a4ea8ebf9", "7e39a30e3d34a7a6539378e39d7490326253b7ee354878a92255656dc4284457"] -twine = ["0fb0bfa3df4f62076cab5def36b1a71a2e4acb4d1fa5c97475b048117b1a6446", "d6c29c933ecfc74e9b1d9fa13aa1f87c5d5770e119f5a4ce032092f0ff5b14dc"] -typed-ast = ["18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", "262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e", "2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0", "354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c", "4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631", "630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4", "66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34", "71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b", "95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a", "bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233", "cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1", "d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36", "d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d", "d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a", "ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12"] +tqdm = ["abc25d0ce2397d070ef07d8c7e706aede7920da163c64997585d42d3537ece3d", "dd3fcca8488bb1d416aa7469d2f277902f26260c45aa86b667b074cd44b3b115"] +twine = ["630fadd6e342e725930be6c696537e3f9ccc54331742b16245dab292a17d0460", "a3d22aab467b4682a22de4a422632e79d07eebd07ff2a7079effb13f8a693787"] +typed-ast = ["1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161", "18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", "262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e", "2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0", "354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c", "48e5b1e71f25cfdef98b013263a88d7145879fbb2d5185f2a0c79fa7ebbeae47", "4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631", "630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4", "66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34", "71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b", "7954560051331d003b4e2b3eb822d9dd2e376fa4f6d98fee32f452f52dd6ebb2", "838997f4310012cf2e1ad3803bce2f3402e9ffb71ded61b5ee22617b3a7f6b6e", "95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a", "bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233", "cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1", "d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36", "d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d", "d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a", "fdc1c9bbf79510b76408840e009ed65958feba92a88833cdceecff93ae8fff66", "ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12"] typing = ["91dfe6f3f706ee8cc32d38edbbf304e9b7583fb37108fef38229617f8b3eba23", "c8cabb5ab8945cd2f54917be357d134db9cc1eb039e59d1606dc1e60cb1d9d36", "f38d83c5a7a7086543a0f649564d661859c5146a85775ab90c0d2f93ffaa9714"] typing-extensions = ["2ed632b30bb54fc3941c382decfd0ee4148f5c591651c9272473fea2c6397d95", "b1edbbf0652660e32ae780ac9433f4231e7339c7f9a8057d0f042fcbcea49b87", "d8179012ec2c620d3791ca6fe2bf7979d979acdbef1fca0bc56b37411db682ed"] -typing-inspect = ["a7cb36c4a47d034766a67ea6467b39bd995cd00db8d4db1aa40001bf2d674a9b", "cf41eb276cc8955a45e03c15cd1efa6c181a8775a38ff0bfda99d28af97bcda3", "e319dfa0c9a646614c9b6abab3bdd5f860a98609998d420f33e41a6e01cbbddb"] -urllib3 = ["b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1", "dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232"] +typing-inspect = ["75c97b7854426a129f3184c68588db29091ff58e6908ed520add1d52fc44df6e", "811b44f92e780b90cfe7bac94249a4fae87cfaa9b40312765489255045231d9c", "c6ed1cd34860857c53c146a6704a96da12e1661087828ce350f34addc6e5eee3"] +urllib3 = ["3de946ffbed6e6746608990594d08faac602528ac7015ac28d33cee6a45b7398", "9a107b99a5393caf59c7aa3c1249c16e6879447533d0887f4336dde834c7be86"] wcwidth = ["3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", "f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"] webencodings = ["a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", "b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"] zipp = ["3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", "f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335"] diff --git a/pykeybasebot/bot.py b/pykeybasebot/bot.py index 1b3b86b..b54b610 100644 --- a/pykeybasebot/bot.py +++ b/pykeybasebot/bot.py @@ -6,6 +6,7 @@ from .chat_client import ChatClient from .cli import KeybaseNotConnectedError, kblisten, kbsubmit +from .kvstore_client import KVStoreClient RETRY_ATTEMPTS = 100 SLEEP_SECS_BETWEEEN_RETRIES = 1 @@ -108,6 +109,10 @@ def keybase_cli(self) -> str: def chat(self): return ChatClient(self) + @property + def kvstore(self): + return KVStoreClient(self) + async def ensure_initialized(self): if not await self._is_initialized(): await self._initialize() diff --git a/pykeybasebot/errors.py b/pykeybasebot/errors.py new file mode 100644 index 0000000..5528a25 --- /dev/null +++ b/pykeybasebot/errors.py @@ -0,0 +1,31 @@ +from typing import Union + + +class Error(Exception): + def __init__(self, msg: str): + self.msg = msg + + def __str__(self): + return self.msg + + +class RevisionError(Error): + CODE = 2760 + + +class DeleteNonExistentError(Error): + CODE = 2762 + + +def disambiguate_error(e: Exception) -> Union[Exception, Error]: + """ + Try to convert Exception presumably from kbsubmit() + (from CLI response json) into our custom Error types. + """ + if e.args[0]["code"] == RevisionError.CODE: + return RevisionError(e.args[0]["message"]) + elif e.args[0]["code"] == DeleteNonExistentError.CODE: + return DeleteNonExistentError(e.args[0]["message"]) + else: + # return original exception + return e diff --git a/pykeybasebot/kvstore_client.py b/pykeybasebot/kvstore_client.py new file mode 100644 index 0000000..c7a7c92 --- /dev/null +++ b/pykeybasebot/kvstore_client.py @@ -0,0 +1,107 @@ +import json +from typing import Any, Dict, Union + +from .errors import disambiguate_error +from .types import keybase1 + + +class KVStoreClient: + def __init__(self, bot): + self.bot = bot + + async def put( + self, + team: str, + namespace: str, + entry_key: str, + entry_value: str, + revision: Union[int, None] = None, + ) -> keybase1.KVPutResult: + await self.bot.ensure_initialized() + args: Dict[str, Any] = { + "method": "put", + "params": { + "options": { + "team": team, + "namespace": namespace, + "entryKey": entry_key, + "entryValue": entry_value, + } + }, + } + if revision: + args["params"]["options"]["revision"] = revision + try: + res = await self.execute(args) + return keybase1.KVPutResult.from_dict(res) + except Exception as e: + raise disambiguate_error(e) + + async def delete( + self, + team: str, + namespace: str, + entry_key: str, + revision: Union[int, None] = None, + ) -> keybase1.KVDeleteEntryResult: + await self.bot.ensure_initialized() + args: Dict[str, Any] = { + "method": "del", + "params": { + "options": {"team": team, "namespace": namespace, "entryKey": entry_key} + }, + } + if revision: + args["params"]["options"]["revision"] = revision + try: + res = await self.execute(args) + return keybase1.KVDeleteEntryResult.from_dict(res) + except Exception as e: + raise disambiguate_error(e) + + async def get( + self, team: str, namespace: str, entry_key: str + ) -> keybase1.KVGetResult: + await self.bot.ensure_initialized() + res = await self.execute( + { + "method": "get", + "params": { + "options": { + "team": team, + "namespace": namespace, + "entryKey": entry_key, + } + }, + } + ) + return keybase1.KVGetResult.from_dict(res) + + async def list_namespaces(self, team: str) -> keybase1.KVListNamespaceResult: + await self.bot.ensure_initialized() + res = await self.execute( + {"method": "list", "params": {"options": {"team": team}}} + ) + return keybase1.KVListNamespaceResult.from_dict(res) + + async def list_entrykeys( + self, team: str, namespace: str + ) -> keybase1.KVListEntryResult: + await self.bot.ensure_initialized() + res = await self.execute( + { + "method": "list", + "params": {"options": {"team": team, "namespace": namespace}}, + } + ) + return keybase1.KVListEntryResult.from_dict(res) + + def is_deleted(self, res: keybase1.KVGetResult) -> bool: + return res.revision > 0 and res.entry_value == "" + + def is_present(self, res: keybase1.KVGetResult) -> bool: + return res.revision > 0 and res.entry_value != "" + + async def execute(self, command): + resp = await self.bot.submit("kvstore api", json.dumps(command).encode("utf-8")) + return resp["result"] diff --git a/pykeybasebot/types/chat1/__init__.py b/pykeybasebot/types/chat1/__init__.py index 80f9c71..225b637 100644 --- a/pykeybasebot/types/chat1/__init__.py +++ b/pykeybasebot/types/chat1/__init__.py @@ -1,6 +1,6 @@ """chat.1 -Auto-generated to Python types by avdl-compiler v1.4.4 (https://github.com/keybase/node-avdl-compiler) +Auto-generated to Python types by avdl-compiler v1.4.6 (https://github.com/keybase/node-avdl-compiler) Input files: - ../client/protocol/avdl/chat1/api.avdl - ../client/protocol/avdl/chat1/chat_ui.avdl @@ -65,12 +65,28 @@ class MsgSender(DataClassJsonMixin): ) +@dataclass +class MsgBotInfo(DataClassJsonMixin): + bot_uid: str = field(metadata=config(field_name="bot_uid")) + bot_username: Optional[str] = field( + default=None, metadata=config(field_name="bot_username") + ) + + @dataclass class ResetConvMemberAPI(DataClassJsonMixin): conversation_id: str = field(metadata=config(field_name="conversationID")) username: str = field(metadata=config(field_name="username")) +@dataclass +class DeviceInfo(DataClassJsonMixin): + device_id: str = field(metadata=config(field_name="id")) + device_description: str = field(metadata=config(field_name="description")) + device_type: str = field(metadata=config(field_name="type")) + device_ctime: int = field(metadata=config(field_name="ctime")) + + @dataclass class UIPagination(DataClassJsonMixin): next: str = field(metadata=config(field_name="next")) @@ -112,6 +128,14 @@ class UIInboxBigTeamChannelRow(DataClassJsonMixin): draft: Optional[str] = field(default=None, metadata=config(field_name="draft")) +@dataclass +class UIInboxReselectInfo(DataClassJsonMixin): + old_conv_id: str = field(metadata=config(field_name="oldConvID")) + new_conv_id: Optional[str] = field( + default=None, metadata=config(field_name="newConvID") + ) + + @dataclass class UnverifiedInboxUIItemMetadata(DataClassJsonMixin): channel_name: str = field(metadata=config(field_name="channelName")) @@ -163,21 +187,21 @@ class UIAssetUrlInfo(DataClassJsonMixin): @dataclass class UIPaymentInfo(DataClassJsonMixin): - amount_description: str = field(metadata=config(field_name="amountDescription")) + status_description: str = field(metadata=config(field_name="statusDescription")) + issuer_description: str = field(metadata=config(field_name="issuerDescription")) worth: str = field(metadata=config(field_name="worth")) worth_at_send_time: str = field(metadata=config(field_name="worthAtSendTime")) delta: stellar1.BalanceDelta = field(metadata=config(field_name="delta")) note: str = field(metadata=config(field_name="note")) payment_id: stellar1.PaymentID = field(metadata=config(field_name="paymentID")) status: stellar1.PaymentStatus = field(metadata=config(field_name="status")) - status_description: str = field(metadata=config(field_name="statusDescription")) + amount_description: str = field(metadata=config(field_name="amountDescription")) status_detail: str = field(metadata=config(field_name="statusDetail")) show_cancel: bool = field(metadata=config(field_name="showCancel")) from_username: str = field(metadata=config(field_name="fromUsername")) to_username: str = field(metadata=config(field_name="toUsername")) source_amount: str = field(metadata=config(field_name="sourceAmount")) source_asset: stellar1.Asset = field(metadata=config(field_name="sourceAsset")) - issuer_description: str = field(metadata=config(field_name="issuerDescription")) account_id: Optional[stellar1.AccountID] = field( default=None, metadata=config(field_name="accountID") ) @@ -202,6 +226,7 @@ class MessageUnboxedState(Enum): ERROR = 2 OUTBOX = 3 PLACEHOLDER = 4 + JOURNEYCARD = 5 class MessageUnboxedStateStrings(Enum): @@ -209,6 +234,7 @@ class MessageUnboxedStateStrings(Enum): ERROR = "error" OUTBOX = "outbox" PLACEHOLDER = "placeholder" + JOURNEYCARD = "journeycard" @dataclass @@ -1065,10 +1091,14 @@ class MessageSystemTypeStrings(Enum): @dataclass class MessageSystemAddedToTeam(DataClassJsonMixin): team: str = field(metadata=config(field_name="team")) - adder: str = field(metadata=config(field_name="adder")) addee: str = field(metadata=config(field_name="addee")) - owners: Optional[Optional[List[str]]] = field( - default=None, metadata=config(field_name="owners") + role: keybase1.TeamRole = field(metadata=config(field_name="role")) + adder: str = field(metadata=config(field_name="adder")) + bulk_adds: Optional[Optional[List[str]]] = field( + default=None, metadata=config(field_name="bulkAdds") + ) + restricted_bots: Optional[Optional[List[str]]] = field( + default=None, metadata=config(field_name="restrictedBots") ) admins: Optional[Optional[List[str]]] = field( default=None, metadata=config(field_name="admins") @@ -1082,8 +1112,8 @@ class MessageSystemAddedToTeam(DataClassJsonMixin): bots: Optional[Optional[List[str]]] = field( default=None, metadata=config(field_name="bots") ) - restricted_bots: Optional[Optional[List[str]]] = field( - default=None, metadata=config(field_name="restrictedBots") + owners: Optional[Optional[List[str]]] = field( + default=None, metadata=config(field_name="owners") ) @@ -1096,6 +1126,7 @@ class MessageSystemInviteAddedToTeam(DataClassJsonMixin): invite_type: keybase1.TeamInviteCategory = field( metadata=config(field_name="inviteType") ) + role: keybase1.TeamRole = field(metadata=config(field_name="role")) @dataclass @@ -1191,6 +1222,7 @@ class OutboxErrorType(Enum): ALREADY_DELETED = 7 UPLOADFAILED = 8 RESTRICTEDBOT = 9 + MINWRITER = 10 class OutboxErrorTypeStrings(Enum): @@ -1204,6 +1236,7 @@ class OutboxErrorTypeStrings(Enum): ALREADY_DELETED = "already_deleted" UPLOADFAILED = "uploadfailed" RESTRICTEDBOT = "restrictedbot" + MINWRITER = "minwriter" class HeaderPlaintextVersion(Enum): @@ -1286,6 +1319,28 @@ class MessageUnboxedErrorTypeStrings(Enum): PAIRWISE_MISSING = "pairwise_missing" +class JourneycardType(Enum): + WELCOME = 0 + POPULAR_CHANNELS = 1 + ADD_PEOPLE = 2 + CREATE_CHANNELS = 3 + MSG_ATTENTION = 4 + USER_AWAY_FOR_LONG = 5 + CHANNEL_INACTIVE = 6 + MSG_NO_ANSWER = 7 + + +class JourneycardTypeStrings(Enum): + WELCOME = "welcome" + POPULAR_CHANNELS = "popular_channels" + ADD_PEOPLE = "add_people" + CREATE_CHANNELS = "create_channels" + MSG_ATTENTION = "msg_attention" + USER_AWAY_FOR_LONG = "user_away_for_long" + CHANNEL_INACTIVE = "channel_inactive" + MSG_NO_ANSWER = "msg_no_answer" + + @dataclass class UnreadFirstNumLimit(DataClassJsonMixin): num_read: int = field(metadata=config(field_name="NumRead")) @@ -1296,6 +1351,7 @@ class UnreadFirstNumLimit(DataClassJsonMixin): @dataclass class ConversationLocalParticipant(DataClassJsonMixin): username: str = field(metadata=config(field_name="username")) + in_conv_name: bool = field(metadata=config(field_name="inConvName")) fullname: Optional[str] = field( default=None, metadata=config(field_name="fullname") ) @@ -1380,6 +1436,16 @@ class GetThreadNonblockPgModeStrings(Enum): SERVER = "server" +class InboxLayoutReselectMode(Enum): + DEFAULT = 0 + FORCE = 1 + + +class InboxLayoutReselectModeStrings(Enum): + DEFAULT = "default" + FORCE = "force" + + class PreviewLocationTyp(Enum): URL = 0 FILE = 1 @@ -1543,13 +1609,11 @@ class TyperInfo(DataClassJsonMixin): class StaleUpdateType(Enum): CLEAR = 0 NEWACTIVITY = 1 - CONVUPDATE = 2 class StaleUpdateTypeStrings(Enum): CLEAR = "clear" NEWACTIVITY = "newactivity" - CONVUPDATE = "convupdate" class MessageBoxedVersion(Enum): @@ -1748,12 +1812,12 @@ class MsgFlipContent(DataClassJsonMixin): @dataclass class ConvSummary(DataClassJsonMixin): + member_status: str = field(metadata=config(field_name="member_status")) id: str = field(metadata=config(field_name="id")) - channel: ChatChannel = field(metadata=config(field_name="channel")) unread: bool = field(metadata=config(field_name="unread")) active_at: int = field(metadata=config(field_name="active_at")) active_at_ms: int = field(metadata=config(field_name="active_at_ms")) - member_status: str = field(metadata=config(field_name="member_status")) + channel: ChatChannel = field(metadata=config(field_name="channel")) reset_users: Optional[Optional[List[str]]] = field( default=None, metadata=config(field_name="reset_users") ) @@ -1814,6 +1878,13 @@ class GetResetConvMembersRes(DataClassJsonMixin): ) +@dataclass +class GetDeviceInfoRes(DataClassJsonMixin): + devices: Optional[Optional[List[DeviceInfo]]] = field( + default=None, metadata=config(field_name="devices") + ) + + @dataclass class UIInboxBigTeamRow__LABEL(DataClassJsonMixin): state: Literal[UIInboxBigTeamRowTypStrings.LABEL] @@ -1833,6 +1904,7 @@ class UIInboxBigTeamRow__CHANNEL(DataClassJsonMixin): class UIParticipant(DataClassJsonMixin): type: UIParticipantType = field(metadata=config(field_name="type")) assertion: str = field(metadata=config(field_name="assertion")) + in_conv_name: bool = field(metadata=config(field_name="inConvName")) full_name: Optional[str] = field( default=None, metadata=config(field_name="fullName") ) @@ -1841,6 +1913,13 @@ class UIParticipant(DataClassJsonMixin): ) +@dataclass +class UIMessageJourneycard(DataClassJsonMixin): + ordinal: float = field(metadata=config(field_name="ordinal")) + card_type: JourneycardType = field(metadata=config(field_name="cardType")) + highlight_msg_id: MessageID = field(metadata=config(field_name="highlightMsgID")) + + @dataclass class UIMaybeMentionInfo__UNKNOWN(DataClassJsonMixin): status: Literal[UIMaybeMentionStatusStrings.UNKNOWN] @@ -1978,24 +2057,11 @@ class ChannelNameMention(DataClassJsonMixin): @dataclass class GetInboxQuery(DataClassJsonMixin): + skip_bg_loads: bool = field(metadata=config(field_name="skipBgLoads")) unread_only: bool = field(metadata=config(field_name="unreadOnly")) read_only: bool = field(metadata=config(field_name="readOnly")) compute_active_list: bool = field(metadata=config(field_name="computeActiveList")) summarize_max_msgs: bool = field(metadata=config(field_name="summarizeMaxMsgs")) - skip_bg_loads: bool = field(metadata=config(field_name="skipBgLoads")) - conv_id: Optional[ConversationID] = field( - default=None, metadata=config(field_name="convID") - ) - topic_type: Optional[TopicType] = field( - default=None, metadata=config(field_name="topicType") - ) - tlf_id: Optional[TLFID] = field(default=None, metadata=config(field_name="tlfID")) - tlf_visibility: Optional[keybase1.TLFVisibility] = field( - default=None, metadata=config(field_name="tlfVisibility") - ) - before: Optional[gregor1.Time] = field( - default=None, metadata=config(field_name="before") - ) after: Optional[gregor1.Time] = field( default=None, metadata=config(field_name="after") ) @@ -2008,8 +2074,8 @@ class GetInboxQuery(DataClassJsonMixin): status: Optional[Optional[List[ConversationStatus]]] = field( default=None, metadata=config(field_name="status") ) - member_status: Optional[Optional[List[ConversationMemberStatus]]] = field( - default=None, metadata=config(field_name="memberStatus") + topic_type: Optional[TopicType] = field( + default=None, metadata=config(field_name="topicType") ) existences: Optional[Optional[List[ConversationExistence]]] = field( default=None, metadata=config(field_name="existences") @@ -2020,6 +2086,19 @@ class GetInboxQuery(DataClassJsonMixin): conv_i_ds: Optional[Optional[List[ConversationID]]] = field( default=None, metadata=config(field_name="convIDs") ) + conv_id: Optional[ConversationID] = field( + default=None, metadata=config(field_name="convID") + ) + tlf_id: Optional[TLFID] = field(default=None, metadata=config(field_name="tlfID")) + tlf_visibility: Optional[keybase1.TLFVisibility] = field( + default=None, metadata=config(field_name="tlfVisibility") + ) + before: Optional[gregor1.Time] = field( + default=None, metadata=config(field_name="before") + ) + member_status: Optional[Optional[List[ConversationMemberStatus]]] = field( + default=None, metadata=config(field_name="memberStatus") + ) @dataclass @@ -2142,19 +2221,19 @@ class RetentionPolicy__EPHEMERAL(DataClassJsonMixin): @dataclass class SearchOpts(DataClassJsonMixin): + before_context: int = field(metadata=config(field_name="beforeContext")) is_regex: bool = field(metadata=config(field_name="isRegex")) - sent_by: str = field(metadata=config(field_name="sentBy")) sent_to: str = field(metadata=config(field_name="sentTo")) match_mentions: bool = field(metadata=config(field_name="matchMentions")) sent_before: gregor1.Time = field(metadata=config(field_name="sentBefore")) sent_after: gregor1.Time = field(metadata=config(field_name="sentAfter")) max_hits: int = field(metadata=config(field_name="maxHits")) max_messages: int = field(metadata=config(field_name="maxMessages")) - before_context: int = field(metadata=config(field_name="beforeContext")) + sent_by: str = field(metadata=config(field_name="sentBy")) after_context: int = field(metadata=config(field_name="afterContext")) + max_convs_hit: int = field(metadata=config(field_name="maxConvsHit")) reindex_mode: ReIndexingMode = field(metadata=config(field_name="reindexMode")) max_convs_searched: int = field(metadata=config(field_name="maxConvsSearched")) - max_convs_hit: int = field(metadata=config(field_name="maxConvsHit")) max_name_convs: int = field(metadata=config(field_name="maxNameConvs")) initial_pagination: Optional[Pagination] = field( default=None, metadata=config(field_name="initialPagination") @@ -2221,6 +2300,12 @@ class RemoteUserTypingUpdate(DataClassJsonMixin): typing: bool = field(metadata=config(field_name="typing")) +@dataclass +class TeamMemberRoleUpdate(DataClassJsonMixin): + tlf_id: TLFID = field(metadata=config(field_name="tlfID")) + role: keybase1.TeamRole = field(metadata=config(field_name="role")) + + @dataclass class ConversationUpdate(DataClassJsonMixin): conv_id: ConversationID = field(metadata=config(field_name="convID")) @@ -2337,21 +2422,22 @@ class BodyPlaintextUnsupported(DataClassJsonMixin): @dataclass class MessageUnboxedError(DataClassJsonMixin): + sender_device_type: str = field(metadata=config(field_name="senderDeviceType")) err_type: MessageUnboxedErrorType = field(metadata=config(field_name="errType")) - err_msg: str = field(metadata=config(field_name="errMsg")) internal_err_msg: str = field(metadata=config(field_name="internalErrMsg")) version_kind: VersionKind = field(metadata=config(field_name="versionKind")) version_number: int = field(metadata=config(field_name="versionNumber")) is_critical: bool = field(metadata=config(field_name="isCritical")) sender_username: str = field(metadata=config(field_name="senderUsername")) sender_device_name: str = field(metadata=config(field_name="senderDeviceName")) - sender_device_type: str = field(metadata=config(field_name="senderDeviceType")) + err_msg: str = field(metadata=config(field_name="errMsg")) message_id: MessageID = field(metadata=config(field_name="messageID")) message_type: MessageType = field(metadata=config(field_name="messageType")) ctime: gregor1.Time = field(metadata=config(field_name="ctime")) is_ephemeral: bool = field(metadata=config(field_name="isEphemeral")) is_ephemeral_expired: bool = field(metadata=config(field_name="isEphemeralExpired")) etime: gregor1.Time = field(metadata=config(field_name="etime")) + bot_username: str = field(metadata=config(field_name="botUsername")) @dataclass @@ -2360,6 +2446,14 @@ class MessageUnboxedPlaceholder(DataClassJsonMixin): hidden: bool = field(metadata=config(field_name="hidden")) +@dataclass +class MessageUnboxedJourneycard(DataClassJsonMixin): + prev_id: MessageID = field(metadata=config(field_name="prevID")) + ordinal: int = field(metadata=config(field_name="ordinal")) + card_type: JourneycardType = field(metadata=config(field_name="cardType")) + highlight_msg_id: MessageID = field(metadata=config(field_name="highlightMsgID")) + + @dataclass class ConversationSettingsLocal(DataClassJsonMixin): min_writer_role_info: Optional[ConversationMinWriterRoleInfoLocal] = field( @@ -2606,6 +2700,12 @@ class PinMessageRes(DataClassJsonMixin): ) +@dataclass +class LocalMtimeUpdate(DataClassJsonMixin): + conv_id: ConversationID = field(metadata=config(field_name="convID")) + mtime: gregor1.Time = field(metadata=config(field_name="mtime")) + + @dataclass class SetAppNotificationSettingsInfo(DataClassJsonMixin): conv_id: ConversationID = field(metadata=config(field_name="convID")) @@ -2732,6 +2832,9 @@ class RemoteBotCommandsAdvertisementTLFID(DataClassJsonMixin): @dataclass class BotCommandConv(DataClassJsonMixin): uid: gregor1.UID = field(metadata=config(field_name="uid")) + untrusted_team_role: keybase1.TeamRole = field( + metadata=config(field_name="untrustedTeamRole") + ) conv_id: ConversationID = field(metadata=config(field_name="convID")) vers: CommandConvVers = field(metadata=config(field_name="vers")) mtime: gregor1.Time = field(metadata=config(field_name="mtime")) @@ -2889,6 +2992,12 @@ class UIInboxLayout(DataClassJsonMixin): big_teams: Optional[Optional[List[UIInboxBigTeamRow]]] = field( default=None, metadata=config(field_name="bigTeams") ) + reselect_info: Optional[UIInboxReselectInfo] = field( + default=None, metadata=config(field_name="reselectInfo") + ) + widget_list: Optional[Optional[List[UIInboxSmallTeamRow]]] = field( + default=None, metadata=config(field_name="widgetList") + ) @dataclass @@ -3054,25 +3163,22 @@ class ReactionMap(DataClassJsonMixin): @dataclass class MessageClientHeader(DataClassJsonMixin): conv: ConversationIDTriple = field(metadata=config(field_name="conv")) - tlf_name: str = field(metadata=config(field_name="tlfName")) tlf_public: bool = field(metadata=config(field_name="tlfPublic")) message_type: MessageType = field(metadata=config(field_name="messageType")) supersedes: MessageID = field(metadata=config(field_name="supersedes")) + tlf_name: str = field(metadata=config(field_name="tlfName")) sender: gregor1.UID = field(metadata=config(field_name="sender")) sender_device: gregor1.DeviceID = field(metadata=config(field_name="senderDevice")) pairwise_macs: Dict[str, str] = field(metadata=config(field_name="pm")) + bot_uid: Optional[gregor1.UID] = field( + default=None, metadata=config(field_name="b") + ) kbfs_crypt_keys_used: Optional[bool] = field( default=None, metadata=config(field_name="kbfsCryptKeysUsed") ) deletes: Optional[Optional[List[MessageID]]] = field( default=None, metadata=config(field_name="deletes") ) - prev: Optional[Optional[List[MessagePreviousPointer]]] = field( - default=None, metadata=config(field_name="prev") - ) - delete_history: Optional[MessageDeleteHistory] = field( - default=None, metadata=config(field_name="deleteHistory") - ) merkle_root: Optional[MerkleRoot] = field( default=None, metadata=config(field_name="merkleRoot") ) @@ -3085,27 +3191,24 @@ class MessageClientHeader(DataClassJsonMixin): ephemeral_metadata: Optional[MsgEphemeralMetadata] = field( default=None, metadata=config(field_name="em") ) - bot_uid: Optional[gregor1.UID] = field( - default=None, metadata=config(field_name="b") + prev: Optional[Optional[List[MessagePreviousPointer]]] = field( + default=None, metadata=config(field_name="prev") + ) + delete_history: Optional[MessageDeleteHistory] = field( + default=None, metadata=config(field_name="deleteHistory") ) @dataclass class MessageClientHeaderVerified(DataClassJsonMixin): conv: ConversationIDTriple = field(metadata=config(field_name="conv")) - tlf_name: str = field(metadata=config(field_name="tlfName")) tlf_public: bool = field(metadata=config(field_name="tlfPublic")) message_type: MessageType = field(metadata=config(field_name="messageType")) sender: gregor1.UID = field(metadata=config(field_name="sender")) sender_device: gregor1.DeviceID = field(metadata=config(field_name="senderDevice")) + tlf_name: str = field(metadata=config(field_name="tlfName")) rtime: gregor1.Time = field(metadata=config(field_name="rt")) has_pairwise_macs: bool = field(metadata=config(field_name="pm")) - prev: Optional[Optional[List[MessagePreviousPointer]]] = field( - default=None, metadata=config(field_name="prev") - ) - kbfs_crypt_keys_used: Optional[bool] = field( - default=None, metadata=config(field_name="kbfsCryptKeysUsed") - ) merkle_root: Optional[MerkleRoot] = field( default=None, metadata=config(field_name="merkleRoot") ) @@ -3118,21 +3221,27 @@ class MessageClientHeaderVerified(DataClassJsonMixin): ephemeral_metadata: Optional[MsgEphemeralMetadata] = field( default=None, metadata=config(field_name="em") ) + prev: Optional[Optional[List[MessagePreviousPointer]]] = field( + default=None, metadata=config(field_name="prev") + ) bot_uid: Optional[gregor1.UID] = field( default=None, metadata=config(field_name="b") ) + kbfs_crypt_keys_used: Optional[bool] = field( + default=None, metadata=config(field_name="kbfsCryptKeysUsed") + ) @dataclass class Asset(DataClassJsonMixin): + enc_hash: Hash = field(metadata=config(field_name="encHash")) filename: str = field(metadata=config(field_name="filename")) - region: str = field(metadata=config(field_name="region")) endpoint: str = field(metadata=config(field_name="endpoint")) bucket: str = field(metadata=config(field_name="bucket")) path: str = field(metadata=config(field_name="path")) size: int = field(metadata=config(field_name="size")) mime_type: str = field(metadata=config(field_name="mimeType")) - enc_hash: Hash = field(metadata=config(field_name="encHash")) + region: str = field(metadata=config(field_name="region")) key: str = field(metadata=config(field_name="key")) verify_key: str = field(metadata=config(field_name="verifyKey")) title: str = field(metadata=config(field_name="title")) @@ -3231,6 +3340,9 @@ class ExpungePayload(DataClassJsonMixin): @dataclass class UpdateConversationMembership(DataClassJsonMixin): inbox_vers: InboxVers = field(metadata=config(field_name="inboxVers")) + team_member_role_update: Optional[TeamMemberRoleUpdate] = field( + default=None, metadata=config(field_name="teamMemberRoleUpdate") + ) joined: Optional[Optional[List[ConversationMember]]] = field( default=None, metadata=config(field_name="joined") ) @@ -3334,18 +3446,18 @@ class OutboxState__ERROR(DataClassJsonMixin): @dataclass class HeaderPlaintextV1(DataClassJsonMixin): conv: ConversationIDTriple = field(metadata=config(field_name="conv")) - tlf_name: str = field(metadata=config(field_name="tlfName")) tlf_public: bool = field(metadata=config(field_name="tlfPublic")) message_type: MessageType = field(metadata=config(field_name="messageType")) sender: gregor1.UID = field(metadata=config(field_name="sender")) sender_device: gregor1.DeviceID = field(metadata=config(field_name="senderDevice")) + tlf_name: str = field(metadata=config(field_name="tlfName")) body_hash: Hash = field(metadata=config(field_name="bodyHash")) + bot_uid: Optional[gregor1.UID] = field( + default=None, metadata=config(field_name="b") + ) prev: Optional[Optional[List[MessagePreviousPointer]]] = field( default=None, metadata=config(field_name="prev") ) - kbfs_crypt_keys_used: Optional[bool] = field( - default=None, metadata=config(field_name="kbfsCryptKeysUsed") - ) outbox_info: Optional[OutboxInfo] = field( default=None, metadata=config(field_name="outboxInfo") ) @@ -3361,8 +3473,8 @@ class HeaderPlaintextV1(DataClassJsonMixin): ephemeral_metadata: Optional[MsgEphemeralMetadata] = field( default=None, metadata=config(field_name="em") ) - bot_uid: Optional[gregor1.UID] = field( - default=None, metadata=config(field_name="b") + kbfs_crypt_keys_used: Optional[bool] = field( + default=None, metadata=config(field_name="kbfsCryptKeysUsed") ) @@ -3394,16 +3506,9 @@ class GetThreadQuery(DataClassJsonMixin): @dataclass class GetInboxLocalQuery(DataClassJsonMixin): + compute_active_list: bool = field(metadata=config(field_name="computeActiveList")) unread_only: bool = field(metadata=config(field_name="unreadOnly")) read_only: bool = field(metadata=config(field_name="readOnly")) - compute_active_list: bool = field(metadata=config(field_name="computeActiveList")) - name: Optional[NameQuery] = field(default=None, metadata=config(field_name="name")) - topic_name: Optional[str] = field( - default=None, metadata=config(field_name="topicName") - ) - conv_i_ds: Optional[Optional[List[ConversationID]]] = field( - default=None, metadata=config(field_name="convIDs") - ) topic_type: Optional[TopicType] = field( default=None, metadata=config(field_name="topicType") ) @@ -3413,8 +3518,8 @@ class GetInboxLocalQuery(DataClassJsonMixin): before: Optional[gregor1.Time] = field( default=None, metadata=config(field_name="before") ) - after: Optional[gregor1.Time] = field( - default=None, metadata=config(field_name="after") + topic_name: Optional[str] = field( + default=None, metadata=config(field_name="topicName") ) one_chat_type_per_tlf: Optional[bool] = field( default=None, metadata=config(field_name="oneChatTypePerTLF") @@ -3425,6 +3530,13 @@ class GetInboxLocalQuery(DataClassJsonMixin): member_status: Optional[Optional[List[ConversationMemberStatus]]] = field( default=None, metadata=config(field_name="memberStatus") ) + name: Optional[NameQuery] = field(default=None, metadata=config(field_name="name")) + conv_i_ds: Optional[Optional[List[ConversationID]]] = field( + default=None, metadata=config(field_name="convIDs") + ) + after: Optional[gregor1.Time] = field( + default=None, metadata=config(field_name="after") + ) @dataclass @@ -3923,6 +4035,9 @@ class NewMessagePayload(DataClassJsonMixin): message: MessageBoxed = field(metadata=config(field_name="message")) inbox_vers: InboxVers = field(metadata=config(field_name="inboxVers")) topic_type: TopicType = field(metadata=config(field_name="topicType")) + untrusted_team_role: keybase1.TeamRole = field( + metadata=config(field_name="untrustedTeamRole") + ) unread_update: Optional[UnreadUpdate] = field( default=None, metadata=config(field_name="unreadUpdate") ) @@ -4029,8 +4144,8 @@ class MessageUnfurl(DataClassJsonMixin): @dataclass class MsgContent(DataClassJsonMixin): type_name: str = field(metadata=config(field_name="type")) - text: Optional[MessageText] = field( - default=None, metadata=config(field_name="text") + flip: Optional[MsgFlipContent] = field( + default=None, metadata=config(field_name="flip") ) attachment: Optional[MessageAttachment] = field( default=None, metadata=config(field_name="attachment") @@ -4047,8 +4162,8 @@ class MsgContent(DataClassJsonMixin): metadata: Optional[MessageConversationMetadata] = field( default=None, metadata=config(field_name="metadata") ) - headline: Optional[MessageHeadline] = field( - default=None, metadata=config(field_name="headline") + text: Optional[MessageText] = field( + default=None, metadata=config(field_name="text") ) attachment_uploaded: Optional[MessageAttachmentUploaded] = field( default=None, metadata=config(field_name="attachment_uploaded") @@ -4065,8 +4180,8 @@ class MsgContent(DataClassJsonMixin): unfurl: Optional[MessageUnfurl] = field( default=None, metadata=config(field_name="unfurl") ) - flip: Optional[MsgFlipContent] = field( - default=None, metadata=config(field_name="flip") + headline: Optional[MessageHeadline] = field( + default=None, metadata=config(field_name="headline") ) @@ -4196,20 +4311,22 @@ class MessageBody__PIN(DataClassJsonMixin): @dataclass class MsgSummary(DataClassJsonMixin): id: MessageID = field(metadata=config(field_name="id")) - conv_id: str = field(metadata=config(field_name="conversation_id")) channel: ChatChannel = field(metadata=config(field_name="channel")) sender: MsgSender = field(metadata=config(field_name="sender")) sent_at: int = field(metadata=config(field_name="sent_at")) sent_at_ms: int = field(metadata=config(field_name="sent_at_ms")) content: MsgContent = field(metadata=config(field_name="content")) unread: bool = field(metadata=config(field_name="unread")) - prev: Optional[Optional[List[MessagePreviousPointer]]] = field( - default=None, metadata=config(field_name="prev") + conv_id: str = field(metadata=config(field_name="conversation_id")) + bot_info: Optional[MsgBotInfo] = field( + default=None, metadata=config(field_name="bot_info") ) revoked_device: Optional[bool] = field( default=None, metadata=config(field_name="revoked_device") ) - offline: Optional[bool] = field(default=None, metadata=config(field_name="offline")) + prev: Optional[Optional[List[MessagePreviousPointer]]] = field( + default=None, metadata=config(field_name="prev") + ) kbfs_encrypted: Optional[bool] = field( default=None, metadata=config(field_name="kbfs_encrypted") ) @@ -4237,7 +4354,7 @@ class MsgSummary(DataClassJsonMixin): channel_name_mentions: Optional[Optional[List[UIChannelNameMention]]] = field( default=None, metadata=config(field_name="channel_name_mentions") ) - bot_uid: Optional[str] = field(default=None, metadata=config(field_name="bot_uid")) + offline: Optional[bool] = field(default=None, metadata=config(field_name="offline")) @dataclass diff --git a/pykeybasebot/types/gregor1/__init__.py b/pykeybasebot/types/gregor1/__init__.py index 4778748..d4eabc6 100644 --- a/pykeybasebot/types/gregor1/__init__.py +++ b/pykeybasebot/types/gregor1/__init__.py @@ -1,6 +1,6 @@ """gregor.1 -Auto-generated to Python types by avdl-compiler v1.4.4 (https://github.com/keybase/node-avdl-compiler) +Auto-generated to Python types by avdl-compiler v1.4.6 (https://github.com/keybase/node-avdl-compiler) Input files: - ../client/protocol/avdl/gregor1/auth.avdl - ../client/protocol/avdl/gregor1/auth_internal.avdl @@ -12,11 +12,9 @@ """ from dataclasses import dataclass, field -from enum import Enum -from typing import Dict, List, Optional, Union +from typing import List, Optional from dataclasses_json import DataClassJsonMixin, config -from typing_extensions import Literal DurationMsec = int DurationSec = int diff --git a/pykeybasebot/types/keybase1/__init__.py b/pykeybasebot/types/keybase1/__init__.py index 2d95923..1b9181d 100644 --- a/pykeybasebot/types/keybase1/__init__.py +++ b/pykeybasebot/types/keybase1/__init__.py @@ -1,6 +1,6 @@ """keybase.1 -Auto-generated to Python types by avdl-compiler v1.4.4 (https://github.com/keybase/node-avdl-compiler) +Auto-generated to Python types by avdl-compiler v1.4.6 (https://github.com/keybase/node-avdl-compiler) Input files: - ../client/protocol/avdl/keybase1/account.avdl - ../client/protocol/avdl/keybase1/airdrop.avdl @@ -49,6 +49,7 @@ - ../client/protocol/avdl/keybase1/kex2provisionee.avdl - ../client/protocol/avdl/keybase1/kex2provisionee2.avdl - ../client/protocol/avdl/keybase1/kex2provisioner.avdl + - ../client/protocol/avdl/keybase1/kvstore.avdl - ../client/protocol/avdl/keybase1/log.avdl - ../client/protocol/avdl/keybase1/log_ui.avdl - ../client/protocol/avdl/keybase1/login.avdl @@ -770,6 +771,7 @@ class StatusCode(Enum): SCTeamWritePermDenied = 2625 SCTeamBadGeneration = 2636 SCNoOp = 2638 + SCTeamInviteBadCancel = 2645 SCTeamInviteBadToken = 2646 SCTeamTarDuplicate = 2663 SCTeamTarNotFound = 2664 @@ -803,6 +805,9 @@ class StatusCode(Enum): SCTeamProvisionalCanKey = 2721 SCTeamProvisionalCannotKey = 2722 SCTeamFTLOutdated = 2736 + SCTeamStorageWrongRevision = 2760 + SCTeamStorageBadGeneration = 2761 + SCTeamStorageNotFound = 2762 SCEphemeralKeyBadGeneration = 2900 SCEphemeralKeyUnexpectedBox = 2901 SCEphemeralKeyMissingBox = 2902 @@ -856,6 +861,7 @@ class StatusCode(Enum): SCTeambotKeyGenerationExists = 3800 SCTeambotKeyOldBoxedGeneration = 3801 SCTeambotKeyBadGeneration = 3802 + SCAirdropRegisterFailedMisc = 4207 class StatusCodeStrings(Enum): @@ -998,6 +1004,7 @@ class StatusCodeStrings(Enum): SCTeamWritePermDenied = "scteamwritepermdenied" SCTeamBadGeneration = "scteambadgeneration" SCNoOp = "scnoop" + SCTeamInviteBadCancel = "scteaminvitebadcancel" SCTeamInviteBadToken = "scteaminvitebadtoken" SCTeamTarDuplicate = "scteamtarduplicate" SCTeamTarNotFound = "scteamtarnotfound" @@ -1031,6 +1038,9 @@ class StatusCodeStrings(Enum): SCTeamProvisionalCanKey = "scteamprovisionalcankey" SCTeamProvisionalCannotKey = "scteamprovisionalcannotkey" SCTeamFTLOutdated = "scteamftloutdated" + SCTeamStorageWrongRevision = "scteamstoragewrongrevision" + SCTeamStorageBadGeneration = "scteamstoragebadgeneration" + SCTeamStorageNotFound = "scteamstoragenotfound" SCEphemeralKeyBadGeneration = "scephemeralkeybadgeneration" SCEphemeralKeyUnexpectedBox = "scephemeralkeyunexpectedbox" SCEphemeralKeyMissingBox = "scephemeralkeymissingbox" @@ -1084,6 +1094,7 @@ class StatusCodeStrings(Enum): SCTeambotKeyGenerationExists = "scteambotkeygenerationexists" SCTeambotKeyOldBoxedGeneration = "scteambotkeyoldboxedgeneration" SCTeambotKeyBadGeneration = "scteambotkeybadgeneration" + SCAirdropRegisterFailedMisc = "scairdropregisterfailedmisc" ED25519PublicKey = Optional[str] @@ -1132,6 +1143,18 @@ class DbTypeStrings(Enum): DbValue = str +class OnLoginStartupStatus(Enum): + UNKNOWN = 0 + DISABLED = 1 + ENABLED = 2 + + +class OnLoginStartupStatusStrings(Enum): + UNKNOWN = "unknown" + DISABLED = "disabled" + ENABLED = "enabled" + + @dataclass class FirstStepResult(DataClassJsonMixin): val_plus_two: int = field(metadata=config(field_name="valPlusTwo")) @@ -1680,6 +1703,52 @@ class PassphraseStream(DataClassJsonMixin): HelloRes = str +@dataclass +class KVGetResult(DataClassJsonMixin): + team_name: str = field(metadata=config(field_name="teamName")) + namespace: str = field(metadata=config(field_name="namespace")) + entry_key: str = field(metadata=config(field_name="entryKey")) + entry_value: str = field(metadata=config(field_name="entryValue")) + revision: int = field(metadata=config(field_name="revision")) + + +@dataclass +class KVPutResult(DataClassJsonMixin): + team_name: str = field(metadata=config(field_name="teamName")) + namespace: str = field(metadata=config(field_name="namespace")) + entry_key: str = field(metadata=config(field_name="entryKey")) + revision: int = field(metadata=config(field_name="revision")) + + +@dataclass +class EncryptedKVEntry(DataClassJsonMixin): + v: int = field(metadata=config(field_name="v")) + e: str = field(metadata=config(field_name="e")) + n: str = field(metadata=config(field_name="n")) + + +@dataclass +class KVListNamespaceResult(DataClassJsonMixin): + team_name: str = field(metadata=config(field_name="teamName")) + namespaces: Optional[Optional[List[str]]] = field( + default=None, metadata=config(field_name="namespaces") + ) + + +@dataclass +class KVListEntryKey(DataClassJsonMixin): + entry_key: str = field(metadata=config(field_name="entryKey")) + revision: int = field(metadata=config(field_name="revision")) + + +@dataclass +class KVDeleteEntryResult(DataClassJsonMixin): + team_name: str = field(metadata=config(field_name="teamName")) + namespace: str = field(metadata=config(field_name="namespace")) + entry_key: str = field(metadata=config(field_name="entryKey")) + revision: int = field(metadata=config(field_name="revision")) + + class ResetPromptType(Enum): COMPLETE = 0 ENTER_NO_DEVICES = 1 @@ -1699,6 +1768,18 @@ class ResetPromptInfo(DataClassJsonMixin): has_wallet: bool = field(metadata=config(field_name="hasWallet")) +class ResetPromptResponse(Enum): + NOTHING = 0 + CANCEL_RESET = 1 + CONFIRM_RESET = 2 + + +class ResetPromptResponseStrings(Enum): + NOTHING = "nothing" + CANCEL_RESET = "cancel_reset" + CONFIRM_RESET = "confirm_reset" + + class PassphraseRecoveryPromptType(Enum): ENCRYPTED_PGP_KEYS = 0 @@ -1707,6 +1788,26 @@ class PassphraseRecoveryPromptTypeStrings(Enum): ENCRYPTED_PGP_KEYS = "encrypted_pgp_keys" +class ResetMessage(Enum): + ENTERED_VERIFIED = 0 + ENTERED_PASSWORDLESS = 1 + REQUEST_VERIFIED = 2 + NOT_COMPLETED = 3 + CANCELED = 4 + COMPLETED = 5 + RESET_LINK_SENT = 6 + + +class ResetMessageStrings(Enum): + ENTERED_VERIFIED = "entered_verified" + ENTERED_PASSWORDLESS = "entered_passwordless" + REQUEST_VERIFIED = "request_verified" + NOT_COMPLETED = "not_completed" + CANCELED = "canceled" + COMPLETED = "completed" + RESET_LINK_SENT = "reset_link_sent" + + KBFSRootHash = str MerkleStoreSupportedVersion = int MerkleStoreKitHash = str @@ -1754,8 +1855,8 @@ class WalletAccountInfo(DataClassJsonMixin): @dataclass class NotificationChannels(DataClassJsonMixin): + pgp: bool = field(metadata=config(field_name="pgp")) session: bool = field(metadata=config(field_name="session")) - users: bool = field(metadata=config(field_name="users")) kbfs: bool = field(metadata=config(field_name="kbfs")) kbfsdesktop: bool = field(metadata=config(field_name="kbfsdesktop")) kbfslegacy: bool = field(metadata=config(field_name="kbfslegacy")) @@ -1767,7 +1868,7 @@ class NotificationChannels(DataClassJsonMixin): service: bool = field(metadata=config(field_name="service")) app: bool = field(metadata=config(field_name="app")) chat: bool = field(metadata=config(field_name="chat")) - pgp: bool = field(metadata=config(field_name="pgp")) + users: bool = field(metadata=config(field_name="users")) kbfsrequest: bool = field(metadata=config(field_name="kbfsrequest")) badges: bool = field(metadata=config(field_name="badges")) reachability: bool = field(metadata=config(field_name="reachability")) @@ -2119,6 +2220,7 @@ class ParamProofUsernameConfig(DataClassJsonMixin): class ParamProofLogoConfig(DataClassJsonMixin): svg_black: str = field(metadata=config(field_name="svg_black")) svg_full: str = field(metadata=config(field_name="svg_full")) + svg_white: str = field(metadata=config(field_name="svg_white")) @dataclass @@ -2502,6 +2604,7 @@ class SubscriptionTopic(Enum): JOURNAL_STATUS = 1 ONLINE_STATUS = 2 DOWNLOAD_STATUS = 3 + FILES_TAB_BADGE = 4 class SubscriptionTopicStrings(Enum): @@ -2509,6 +2612,7 @@ class SubscriptionTopicStrings(Enum): JOURNAL_STATUS = "journal_status" ONLINE_STATUS = "online_status" DOWNLOAD_STATUS = "download_status" + FILES_TAB_BADGE = "files_tab_badge" class PathSubscriptionTopic(Enum): @@ -2521,6 +2625,20 @@ class PathSubscriptionTopicStrings(Enum): STAT = "stat" +class FilesTabBadge(Enum): + NONE = 0 + UPLOADING_STUCK = 1 + AWAITING_UPLOAD = 2 + UPLOADING = 3 + + +class FilesTabBadgeStrings(Enum): + NONE = "none" + UPLOADING_STUCK = "uploading_stuck" + AWAITING_UPLOAD = "awaiting_upload" + UPLOADING = "uploading" + + class GUIViewType(Enum): DEFAULT = 0 TEXT = 1 @@ -2569,6 +2687,7 @@ class TeamApplication(Enum): GIT_METADATA = 4 SEITAN_INVITE_TOKEN = 5 STELLAR_RELAY = 6 + KVSTORE = 7 class TeamApplicationStrings(Enum): @@ -2578,6 +2697,7 @@ class TeamApplicationStrings(Enum): GIT_METADATA = "git_metadata" SEITAN_INVITE_TOKEN = "seitan_invite_token" STELLAR_RELAY = "stellar_relay" + KVSTORE = "kvstore" class TeamStatus(Enum): @@ -2786,8 +2906,8 @@ class BulkRes(DataClassJsonMixin): @dataclass class TeamOperation(DataClassJsonMixin): + set_retention_policy: bool = field(metadata=config(field_name="setRetentionPolicy")) manage_members: bool = field(metadata=config(field_name="manageMembers")) - manage_subteams: bool = field(metadata=config(field_name="manageSubteams")) create_channel: bool = field(metadata=config(field_name="createChannel")) chat: bool = field(metadata=config(field_name="chat")) delete_channel: bool = field(metadata=config(field_name="deleteChannel")) @@ -2801,7 +2921,7 @@ class TeamOperation(DataClassJsonMixin): ) set_team_showcase: bool = field(metadata=config(field_name="setTeamShowcase")) set_member_showcase: bool = field(metadata=config(field_name="setMemberShowcase")) - set_retention_policy: bool = field(metadata=config(field_name="setRetentionPolicy")) + manage_subteams: bool = field(metadata=config(field_name="manageSubteams")) set_min_writer_role: bool = field(metadata=config(field_name="setMinWriterRole")) change_open_team: bool = field(metadata=config(field_name="changeOpenTeam")) leave_team: bool = field(metadata=config(field_name="leaveTeam")) @@ -3106,16 +3226,16 @@ class TeamIDWithVisibility(DataClassJsonMixin): @dataclass class PublicKey(DataClassJsonMixin): + device_id: DeviceID = field(metadata=config(field_name="deviceID")) kid: KID = field(metadata=config(field_name="KID")) - pgp_fingerprint: str = field(metadata=config(field_name="PGPFingerprint")) + e_time: Time = field(metadata=config(field_name="eTime")) is_sibkey: bool = field(metadata=config(field_name="isSibkey")) is_eldest: bool = field(metadata=config(field_name="isEldest")) parent_id: str = field(metadata=config(field_name="parentID")) - device_id: DeviceID = field(metadata=config(field_name="deviceID")) + pgp_fingerprint: str = field(metadata=config(field_name="PGPFingerprint")) device_description: str = field(metadata=config(field_name="deviceDescription")) device_type: str = field(metadata=config(field_name="deviceType")) c_time: Time = field(metadata=config(field_name="cTime")) - e_time: Time = field(metadata=config(field_name="eTime")) is_revoked: bool = field(metadata=config(field_name="isRevoked")) pgp_identities: Optional[Optional[List[PGPIdentity]]] = field( default=None, metadata=config(field_name="PGPIdentities") @@ -3220,14 +3340,14 @@ class ClientDetails(DataClassJsonMixin): @dataclass class Config(DataClassJsonMixin): + path: str = field(metadata=config(field_name="path")) server_uri: str = field(metadata=config(field_name="serverURI")) - socket_file: str = field(metadata=config(field_name="socketFile")) label: str = field(metadata=config(field_name="label")) run_mode: str = field(metadata=config(field_name="runMode")) gpg_exists: bool = field(metadata=config(field_name="gpgExists")) gpg_path: str = field(metadata=config(field_name="gpgPath")) version: str = field(metadata=config(field_name="version")) - path: str = field(metadata=config(field_name="path")) + socket_file: str = field(metadata=config(field_name="socketFile")) binary_realpath: str = field(metadata=config(field_name="binaryRealpath")) config_path: str = field(metadata=config(field_name="configPath")) version_short: str = field(metadata=config(field_name="versionShort")) @@ -3674,6 +3794,22 @@ class PerUserKeyBox(DataClassJsonMixin): receiver_kid: KID = field(metadata=config(field_name="receiver_kid")) +@dataclass +class KVEntryID(DataClassJsonMixin): + team_id: TeamID = field(metadata=config(field_name="teamID")) + namespace: str = field(metadata=config(field_name="namespace")) + entry_key: str = field(metadata=config(field_name="entryKey")) + + +@dataclass +class KVListEntryResult(DataClassJsonMixin): + team_name: str = field(metadata=config(field_name="teamName")) + namespace: str = field(metadata=config(field_name="namespace")) + entry_keys: Optional[Optional[List[KVListEntryKey]]] = field( + default=None, metadata=config(field_name="entryKeys") + ) + + @dataclass class ConfiguredAccount(DataClassJsonMixin): username: str = field(metadata=config(field_name="username")) @@ -3862,17 +3998,17 @@ class ParamProofJSON(DataClassJsonMixin): @dataclass class ParamProofServiceConfig(DataClassJsonMixin): + brand_color: str = field(metadata=config(field_name="brand_color")) version: int = field(metadata=config(field_name="version")) - domain: str = field(metadata=config(field_name="domain")) display_name: str = field(metadata=config(field_name="display_name")) + check_url: str = field(metadata=config(field_name="check_url")) description: str = field(metadata=config(field_name="description")) username_config: ParamProofUsernameConfig = field( metadata=config(field_name="username") ) - brand_color: str = field(metadata=config(field_name="brand_color")) + domain: str = field(metadata=config(field_name="domain")) prefill_url: str = field(metadata=config(field_name="prefill_url")) profile_url: str = field(metadata=config(field_name="profile_url")) - check_url: str = field(metadata=config(field_name="check_url")) logo: Optional[ParamProofLogoConfig] = field( default=None, metadata=config(field_name="logo") ) @@ -3896,6 +4032,9 @@ class ProveParameters(DataClassJsonMixin): logo_black: Optional[Optional[List[SizedImage]]] = field( default=None, metadata=config(field_name="logoBlack") ) + logo_white: Optional[Optional[List[SizedImage]]] = field( + default=None, metadata=config(field_name="logoWhite") + ) @dataclass @@ -4114,6 +4253,7 @@ class TeambotKeyMetadata(DataClassJsonMixin): puk_generation: PerUserKeyGeneration = field( metadata=config(field_name="puk_generation") ) + application: TeamApplication = field(metadata=config(field_name="application")) @dataclass @@ -4431,6 +4571,7 @@ class InterestingPerson(DataClassJsonMixin): uid: UID = field(metadata=config(field_name="uid")) username: str = field(metadata=config(field_name="username")) fullname: str = field(metadata=config(field_name="fullname")) + service_map: Dict[str, str] = field(metadata=config(field_name="serviceMap")) @dataclass @@ -4442,6 +4583,11 @@ class CanLogoutRes(DataClassJsonMixin): ) +@dataclass +class UserPassphraseStateMsg(DataClassJsonMixin): + passphrase_state: PassphraseState = field(metadata=config(field_name="state")) + + @dataclass class APIUserKeybaseResult(DataClassJsonMixin): username: str = field(metadata=config(field_name="username")) @@ -4553,13 +4699,13 @@ class Contact(DataClassJsonMixin): @dataclass class ProcessedContact(DataClassJsonMixin): + full_name: str = field(metadata=config(field_name="fullName")) contact_index: int = field(metadata=config(field_name="contactIndex")) - contact_name: str = field(metadata=config(field_name="contactName")) component: ContactComponent = field(metadata=config(field_name="component")) resolved: bool = field(metadata=config(field_name="resolved")) uid: UID = field(metadata=config(field_name="uid")) username: str = field(metadata=config(field_name="username")) - full_name: str = field(metadata=config(field_name="fullName")) + contact_name: str = field(metadata=config(field_name="contactName")) following: bool = field(metadata=config(field_name="following")) service_map: Dict[str, str] = field(metadata=config(field_name="serviceMap")) assertion: str = field(metadata=config(field_name="assertion")) @@ -4708,48 +4854,51 @@ class HomeScreenPeopleNotificationContactMulti(DataClassJsonMixin): @dataclass class Identify3Row(DataClassJsonMixin): gui_id: Identify3GUIID = field(metadata=config(field_name="guiID")) - key: str = field(metadata=config(field_name="key")) value: str = field(metadata=config(field_name="value")) priority: int = field(metadata=config(field_name="priority")) site_url: str = field(metadata=config(field_name="siteURL")) + key: str = field(metadata=config(field_name="key")) proof_url: str = field(metadata=config(field_name="proofURL")) sig_id: SigID = field(metadata=config(field_name="sigID")) ctime: Time = field(metadata=config(field_name="ctime")) state: Identify3RowState = field(metadata=config(field_name="state")) color: Identify3RowColor = field(metadata=config(field_name="color")) + kid: Optional[KID] = field(default=None, metadata=config(field_name="kid")) site_icon: Optional[Optional[List[SizedImage]]] = field( default=None, metadata=config(field_name="siteIcon") ) + metas: Optional[Optional[List[Identify3RowMeta]]] = field( + default=None, metadata=config(field_name="metas") + ) site_icon_full: Optional[Optional[List[SizedImage]]] = field( default=None, metadata=config(field_name="siteIconFull") ) - metas: Optional[Optional[List[Identify3RowMeta]]] = field( - default=None, metadata=config(field_name="metas") + site_icon_white: Optional[Optional[List[SizedImage]]] = field( + default=None, metadata=config(field_name="siteIconWhite") ) - kid: Optional[KID] = field(default=None, metadata=config(field_name="kid")) @dataclass class IdentifyOutcome(DataClassJsonMixin): + num_proof_failures: int = field(metadata=config(field_name="numProofFailures")) username: str = field(metadata=config(field_name="username")) + for_pgp_pull: bool = field(metadata=config(field_name="forPGPPull")) + track_options: TrackOptions = field(metadata=config(field_name="trackOptions")) track_status: TrackStatus = field(metadata=config(field_name="trackStatus")) num_track_failures: int = field(metadata=config(field_name="numTrackFailures")) num_track_changes: int = field(metadata=config(field_name="numTrackChanges")) - num_proof_failures: int = field(metadata=config(field_name="numProofFailures")) - num_revoked: int = field(metadata=config(field_name="numRevoked")) num_proof_successes: int = field(metadata=config(field_name="numProofSuccesses")) - track_options: TrackOptions = field(metadata=config(field_name="trackOptions")) - for_pgp_pull: bool = field(metadata=config(field_name="forPGPPull")) + num_revoked: int = field(metadata=config(field_name="numRevoked")) reason: IdentifyReason = field(metadata=config(field_name="reason")) status: Optional[Status] = field(default=None, metadata=config(field_name="status")) - warnings: Optional[Optional[List[str]]] = field( - default=None, metadata=config(field_name="warnings") + revoked: Optional[Optional[List[TrackDiff]]] = field( + default=None, metadata=config(field_name="revoked") ) track_used: Optional[TrackSummary] = field( default=None, metadata=config(field_name="trackUsed") ) - revoked: Optional[Optional[List[TrackDiff]]] = field( - default=None, metadata=config(field_name="revoked") + warnings: Optional[Optional[List[str]]] = field( + default=None, metadata=config(field_name="warnings") ) @@ -4789,14 +4938,14 @@ class CheckResult(DataClassJsonMixin): @dataclass class UserCard(DataClassJsonMixin): + website: str = field(metadata=config(field_name="website")) following: int = field(metadata=config(field_name="following")) - followers: int = field(metadata=config(field_name="followers")) uid: UID = field(metadata=config(field_name="uid")) full_name: str = field(metadata=config(field_name="fullName")) location: str = field(metadata=config(field_name="location")) bio: str = field(metadata=config(field_name="bio")) bio_decorated: str = field(metadata=config(field_name="bioDecorated")) - website: str = field(metadata=config(field_name="website")) + followers: int = field(metadata=config(field_name="followers")) twitter: str = field(metadata=config(field_name="twitter")) you_follow_them: bool = field(metadata=config(field_name="youFollowThem")) they_follow_you: bool = field(metadata=config(field_name="theyFollowYou")) @@ -4890,32 +5039,29 @@ class MetadataResponse(DataClassJsonMixin): @dataclass class BadgeState(DataClassJsonMixin): new_tlfs: int = field(metadata=config(field_name="newTlfs")) - rekeys_needed: int = field(metadata=config(field_name="rekeysNeeded")) + reset_state: ResetState = field(metadata=config(field_name="resetState")) new_followers: int = field(metadata=config(field_name="newFollowers")) inbox_vers: int = field(metadata=config(field_name="inboxVers")) home_todo_items: int = field(metadata=config(field_name="homeTodoItems")) unverified_emails: int = field(metadata=config(field_name="unverifiedEmails")) unverified_phones: int = field(metadata=config(field_name="unverifiedPhones")) - reset_state: ResetState = field(metadata=config(field_name="resetState")) + rekeys_needed: int = field(metadata=config(field_name="rekeysNeeded")) new_devices: Optional[Optional[List[DeviceID]]] = field( default=None, metadata=config(field_name="newDevices") ) - revoked_devices: Optional[Optional[List[DeviceID]]] = field( - default=None, metadata=config(field_name="revokedDevices") - ) conversations: Optional[Optional[List[BadgeConversationInfo]]] = field( default=None, metadata=config(field_name="conversations") ) new_git_repo_global_unique_i_ds: Optional[Optional[List[str]]] = field( default=None, metadata=config(field_name="newGitRepoGlobalUniqueIDs") ) - new_team_names: Optional[Optional[List[str]]] = field( - default=None, metadata=config(field_name="newTeamNames") + new_teams: Optional[Optional[List[TeamID]]] = field( + default=None, metadata=config(field_name="newTeams") ) deleted_teams: Optional[Optional[List[DeletedTeamInfo]]] = field( default=None, metadata=config(field_name="deletedTeams") ) - new_team_access_requests: Optional[Optional[List[str]]] = field( + new_team_access_requests: Optional[Optional[List[TeamID]]] = field( default=None, metadata=config(field_name="newTeamAccessRequests") ) teams_with_reset_users: Optional[Optional[List[TeamMemberOutReset]]] = field( @@ -4924,6 +5070,9 @@ class BadgeState(DataClassJsonMixin): unread_wallet_accounts: Optional[Optional[List[WalletAccountInfo]]] = field( default=None, metadata=config(field_name="unreadWalletAccounts") ) + revoked_devices: Optional[Optional[List[DeviceID]]] = field( + default=None, metadata=config(field_name="revokedDevices") + ) @dataclass @@ -5205,28 +5354,28 @@ class LinkTripleAndTime(DataClassJsonMixin): @dataclass class FastTeamSigChainState(DataClassJsonMixin): + per_team_key_seeds_verified: Dict[str, PerTeamKeySeed] = field( + metadata=config(field_name="perTeamKeySeedsVerified") + ) id: TeamID = field(metadata=config(field_name="ID")) - public: bool = field(metadata=config(field_name="public")) root_ancestor: TeamName = field(metadata=config(field_name="rootAncestor")) name_depth: int = field(metadata=config(field_name="nameDepth")) + link_i_ds: Dict[str, LinkID] = field(metadata=config(field_name="linkIDs")) per_team_keys: Dict[str, PerTeamKey] = field( metadata=config(field_name="perTeamKeys") ) - per_team_key_seeds_verified: Dict[str, PerTeamKeySeed] = field( - metadata=config(field_name="perTeamKeySeedsVerified") - ) + public: bool = field(metadata=config(field_name="public")) down_pointers: Dict[str, DownPointer] = field( metadata=config(field_name="downPointers") ) per_team_key_c_time: UnixTime = field(metadata=config(field_name="perTeamKeyCTime")) - link_i_ds: Dict[str, LinkID] = field(metadata=config(field_name="linkIDs")) merkle_info: Dict[str, MerkleRootV2] = field( metadata=config(field_name="merkleInfo") ) - last: Optional[LinkTriple] = field(default=None, metadata=config(field_name="last")) last_up_pointer: Optional[UpPointer] = field( default=None, metadata=config(field_name="lastUpPointer") ) + last: Optional[LinkTriple] = field(default=None, metadata=config(field_name="last")) @dataclass @@ -5398,8 +5547,8 @@ class MemberInfo(DataClassJsonMixin): @dataclass class AnnotatedMemberInfo(DataClassJsonMixin): + role: TeamRole = field(metadata=config(field_name="role")) user_id: UID = field(metadata=config(field_name="uid")) - team_id: TeamID = field(metadata=config(field_name="team_id")) username: str = field(metadata=config(field_name="username")) full_name: str = field(metadata=config(field_name="full_name")) fq_name: str = field(metadata=config(field_name="fq_name")) @@ -5408,14 +5557,14 @@ class AnnotatedMemberInfo(DataClassJsonMixin): metadata=config(field_name="implicit_team_display_name") ) is_open_team: bool = field(metadata=config(field_name="is_open_team")) - role: TeamRole = field(metadata=config(field_name="role")) + team_id: TeamID = field(metadata=config(field_name="team_id")) + is_member_showcased: bool = field(metadata=config(field_name="is_member_showcased")) needs_puk: bool = field(metadata=config(field_name="needsPUK")) member_count: int = field(metadata=config(field_name="member_count")) eldest_seqno: Seqno = field(metadata=config(field_name="member_eldest_seqno")) allow_profile_promote: bool = field( metadata=config(field_name="allow_profile_promote") ) - is_member_showcased: bool = field(metadata=config(field_name="is_member_showcased")) status: TeamMemberStatus = field(metadata=config(field_name="status")) implicit: Optional[ImplicitRole] = field( default=None, metadata=config(field_name="implicit") @@ -5528,6 +5677,9 @@ class ProofSuggestion(DataClassJsonMixin): profile_icon: Optional[Optional[List[SizedImage]]] = field( default=None, metadata=config(field_name="profileIcon") ) + profile_icon_white: Optional[Optional[List[SizedImage]]] = field( + default=None, metadata=config(field_name="profileIconWhite") + ) picker_icon: Optional[Optional[List[SizedImage]]] = field( default=None, metadata=config(field_name="pickerIcon") ) @@ -5557,16 +5709,16 @@ class ReferenceCountRes(DataClassJsonMixin): @dataclass class UserPlusKeys(DataClassJsonMixin): uid: UID = field(metadata=config(field_name="uid")) - username: str = field(metadata=config(field_name="username")) eldest_seqno: Seqno = field(metadata=config(field_name="eldestSeqno")) status: StatusCode = field(metadata=config(field_name="status")) + username: str = field(metadata=config(field_name="username")) pgp_key_count: int = field(metadata=config(field_name="pgpKeyCount")) uvv: UserVersionVector = field(metadata=config(field_name="uvv")) device_keys: Optional[Optional[List[PublicKey]]] = field( default=None, metadata=config(field_name="deviceKeys") ) - revoked_device_keys: Optional[Optional[List[RevokedKey]]] = field( - default=None, metadata=config(field_name="revokedDeviceKeys") + resets: Optional[Optional[List[ResetSummary]]] = field( + default=None, metadata=config(field_name="resets") ) deleted_device_keys: Optional[Optional[List[PublicKey]]] = field( default=None, metadata=config(field_name="deletedDeviceKeys") @@ -5574,16 +5726,16 @@ class UserPlusKeys(DataClassJsonMixin): per_user_keys: Optional[Optional[List[PerUserKey]]] = field( default=None, metadata=config(field_name="perUserKeys") ) - resets: Optional[Optional[List[ResetSummary]]] = field( - default=None, metadata=config(field_name="resets") + revoked_device_keys: Optional[Optional[List[RevokedKey]]] = field( + default=None, metadata=config(field_name="revokedDeviceKeys") ) @dataclass class ExtendedStatus(DataClassJsonMixin): standalone: bool = field(metadata=config(field_name="standalone")) - passphrase_stream_cached: bool = field( - metadata=config(field_name="passphraseStreamCached") + ui_router_mapping: Dict[str, int] = field( + metadata=config(field_name="uiRouterMapping") ) tsec_cached: bool = field(metadata=config(field_name="tsecCached")) device_sig_key_cached: bool = field( @@ -5597,34 +5749,31 @@ class ExtendedStatus(DataClassJsonMixin): stored_secret: bool = field(metadata=config(field_name="storedSecret")) secret_prompt_skip: bool = field(metadata=config(field_name="secretPromptSkip")) remember_passphrase: bool = field(metadata=config(field_name="rememberPassphrase")) - log_dir: str = field(metadata=config(field_name="logDir")) - default_username: str = field(metadata=config(field_name="defaultUsername")) - platform_info: PlatformInfo = field(metadata=config(field_name="platformInfo")) default_device_id: DeviceID = field(metadata=config(field_name="defaultDeviceID")) - ui_router_mapping: Dict[str, int] = field( - metadata=config(field_name="uiRouterMapping") - ) - device: Optional[Device] = field(default=None, metadata=config(field_name="device")) - device_err: Optional[LoadDeviceErr] = field( - default=None, metadata=config(field_name="deviceErr") + platform_info: PlatformInfo = field(metadata=config(field_name="platformInfo")) + log_dir: str = field(metadata=config(field_name="logDir")) + passphrase_stream_cached: bool = field( + metadata=config(field_name="passphraseStreamCached") ) - session: Optional[SessionStatus] = field( - default=None, metadata=config(field_name="session") + default_username: str = field(metadata=config(field_name="defaultUsername")) + local_db_stats: Optional[Optional[List[str]]] = field( + default=None, metadata=config(field_name="localDbStats") ) provisioned_usernames: Optional[Optional[List[str]]] = field( default=None, metadata=config(field_name="provisionedUsernames") ) - configured_accounts: Optional[Optional[List[ConfiguredAccount]]] = field( - default=None, metadata=config(field_name="configuredAccounts") - ) clients: Optional[Optional[List[ClientStatus]]] = field( default=None, metadata=config(field_name="Clients") ) device_ek_names: Optional[Optional[List[str]]] = field( default=None, metadata=config(field_name="deviceEkNames") ) - local_db_stats: Optional[Optional[List[str]]] = field( - default=None, metadata=config(field_name="localDbStats") + device_err: Optional[LoadDeviceErr] = field( + default=None, metadata=config(field_name="deviceErr") + ) + device: Optional[Device] = field(default=None, metadata=config(field_name="device")) + configured_accounts: Optional[Optional[List[ConfiguredAccount]]] = field( + default=None, metadata=config(field_name="configuredAccounts") ) local_chat_db_stats: Optional[Optional[List[str]]] = field( default=None, metadata=config(field_name="localChatDbStats") @@ -5638,6 +5787,9 @@ class ExtendedStatus(DataClassJsonMixin): cache_dir_size_info: Optional[Optional[List[DirSizeInfo]]] = field( default=None, metadata=config(field_name="cacheDirSizeInfo") ) + session: Optional[SessionStatus] = field( + default=None, metadata=config(field_name="session") + ) @dataclass @@ -5836,17 +5988,17 @@ class TeamMembersDetails(DataClassJsonMixin): @dataclass class FastTeamData(DataClassJsonMixin): + max_continuous_ptk_generation: PerTeamKeyGeneration = field( + metadata=config(field_name="maxContinuousPTKGeneration") + ) frozen: bool = field(metadata=config(field_name="frozen")) - subversion: int = field(metadata=config(field_name="subversion")) tombstoned: bool = field(metadata=config(field_name="tombstoned")) name: TeamName = field(metadata=config(field_name="name")) chain: FastTeamSigChainState = field(metadata=config(field_name="chain")) per_team_key_seeds_unverified: Dict[str, PerTeamKeySeed] = field( metadata=config(field_name="perTeamKeySeedsUnverified") ) - max_continuous_ptk_generation: PerTeamKeyGeneration = field( - metadata=config(field_name="maxContinuousPTKGeneration") - ) + subversion: int = field(metadata=config(field_name="subversion")) seed_checks: Dict[str, PerTeamSeedCheck] = field( metadata=config(field_name="seedChecks") ) @@ -5899,13 +6051,13 @@ class SeitanKeyAndLabel__V2(DataClassJsonMixin): @dataclass class LoadTeamArg(DataClassJsonMixin): + force_full_reload: bool = field(metadata=config(field_name="forceFullReload")) id: TeamID = field(metadata=config(field_name="ID")) - name: str = field(metadata=config(field_name="name")) public: bool = field(metadata=config(field_name="public")) need_admin: bool = field(metadata=config(field_name="needAdmin")) refresh_uid_mapper: bool = field(metadata=config(field_name="refreshUIDMapper")) refreshers: TeamRefreshers = field(metadata=config(field_name="refreshers")) - force_full_reload: bool = field(metadata=config(field_name="forceFullReload")) + name: str = field(metadata=config(field_name="name")) force_repoll: bool = field(metadata=config(field_name="forceRepoll")) stale_ok: bool = field(metadata=config(field_name="staleOK")) allow_name_lookup_burst_cache: bool = field( @@ -6031,6 +6183,9 @@ class NonUserDetails(DataClassJsonMixin): site_icon_full: Optional[Optional[List[SizedImage]]] = field( default=None, metadata=config(field_name="siteIconFull") ) + site_icon_white: Optional[Optional[List[SizedImage]]] = field( + default=None, metadata=config(field_name="siteIconWhite") + ) @dataclass @@ -6054,12 +6209,12 @@ class UserPlusAllKeys(DataClassJsonMixin): @dataclass class FullStatus(DataClassJsonMixin): + service: KbServiceStatus = field(metadata=config(field_name="service")) username: str = field(metadata=config(field_name="username")) - config_path: str = field(metadata=config(field_name="configPath")) cur_status: CurrentStatus = field(metadata=config(field_name="curStatus")) ext_status: ExtendedStatus = field(metadata=config(field_name="extStatus")) client: KbClientStatus = field(metadata=config(field_name="client")) - service: KbServiceStatus = field(metadata=config(field_name="service")) + config_path: str = field(metadata=config(field_name="configPath")) kbfs: KBFSStatus = field(metadata=config(field_name="kbfs")) desktop: DesktopStatus = field(metadata=config(field_name="desktop")) updater: UpdaterStatus = field(metadata=config(field_name="updater")) @@ -6201,17 +6356,17 @@ class TeamDetails(DataClassJsonMixin): @dataclass class HiddenTeamChain(DataClassJsonMixin): + last_per_team_keys: Dict[str, Seqno] = field( + metadata=config(field_name="lastPerTeamKeys") + ) id: TeamID = field(metadata=config(field_name="id")) - subversion: int = field(metadata=config(field_name="subversion")) public: bool = field(metadata=config(field_name="public")) frozen: bool = field(metadata=config(field_name="frozen")) tombstoned: bool = field(metadata=config(field_name="tombstoned")) last: Seqno = field(metadata=config(field_name="last")) last_full: Seqno = field(metadata=config(field_name="lastFull")) latest_seqno_hint: Seqno = field(metadata=config(field_name="latestSeqnoHint")) - last_per_team_keys: Dict[str, Seqno] = field( - metadata=config(field_name="lastPerTeamKeys") - ) + subversion: int = field(metadata=config(field_name="subversion")) outer: Dict[str, LinkID] = field(metadata=config(field_name="outer")) inner: Dict[str, HiddenTeamChainLink] = field(metadata=config(field_name="inner")) reader_per_team_keys: Dict[str, Seqno] = field( @@ -6229,25 +6384,29 @@ class HiddenTeamChain(DataClassJsonMixin): @dataclass class TeamSigChainState(DataClassJsonMixin): + per_team_keys: Dict[str, PerTeamKey] = field( + metadata=config(field_name="perTeamKeys") + ) reader: UserVersion = field(metadata=config(field_name="reader")) - id: TeamID = field(metadata=config(field_name="id")) implicit: bool = field(metadata=config(field_name="implicit")) public: bool = field(metadata=config(field_name="public")) root_ancestor: TeamName = field(metadata=config(field_name="rootAncestor")) name_depth: int = field(metadata=config(field_name="nameDepth")) + tlf_legacy_upgrade: Dict[str, TeamLegacyTLFUpgradeChainInfo] = field( + metadata=config(field_name="tlfLegacyUpgrade") + ) last_seqno: Seqno = field(metadata=config(field_name="lastSeqno")) last_link_id: LinkID = field(metadata=config(field_name="lastLinkID")) last_high_seqno: Seqno = field(metadata=config(field_name="lastHighSeqno")) last_high_link_id: LinkID = field(metadata=config(field_name="lastHighLinkID")) + bots: Dict[str, TeamBotSettings] = field(metadata=config(field_name="bots")) user_log: Dict[str, Optional[List[UserLogPoint]]] = field( metadata=config(field_name="userLog") ) subteam_log: Dict[str, Optional[List[SubteamLogPoint]]] = field( metadata=config(field_name="subteamLog") ) - per_team_keys: Dict[str, PerTeamKey] = field( - metadata=config(field_name="perTeamKeys") - ) + id: TeamID = field(metadata=config(field_name="id")) max_per_team_key_generation: PerTeamKeyGeneration = field( metadata=config(field_name="maxPerTeamKeyGeneration") ) @@ -6262,22 +6421,18 @@ class TeamSigChainState(DataClassJsonMixin): ) open: bool = field(metadata=config(field_name="open")) open_team_join_as: TeamRole = field(metadata=config(field_name="openTeamJoinAs")) - bots: Dict[str, TeamBotSettings] = field(metadata=config(field_name="bots")) - tlf_legacy_upgrade: Dict[str, TeamLegacyTLFUpgradeChainInfo] = field( - metadata=config(field_name="tlfLegacyUpgrade") - ) merkle_roots: Dict[str, MerkleRootV2] = field( metadata=config(field_name="merkleRoots") ) - name_log: Optional[Optional[List[TeamNameLogPoint]]] = field( - default=None, metadata=config(field_name="nameLog") - ) parent_id: Optional[TeamID] = field( default=None, metadata=config(field_name="parentID") ) tlf_i_ds: Optional[Optional[List[TLFID]]] = field( default=None, metadata=config(field_name="tlfIDs") ) + name_log: Optional[Optional[List[TeamNameLogPoint]]] = field( + default=None, metadata=config(field_name="nameLog") + ) head_merkle: Optional[MerkleRootV2] = field( default=None, metadata=config(field_name="headMerkle") ) @@ -6430,12 +6585,12 @@ class OpDescription__GET_REVISIONS(DataClassJsonMixin): @dataclass class TeamData(DataClassJsonMixin): + chain: TeamSigChainState = field(metadata=config(field_name="chain")) subversion: int = field(metadata=config(field_name="v")) - frozen: bool = field(metadata=config(field_name="frozen")) tombstoned: bool = field(metadata=config(field_name="tombstoned")) secretless: bool = field(metadata=config(field_name="secretless")) name: TeamName = field(metadata=config(field_name="name")) - chain: TeamSigChainState = field(metadata=config(field_name="chain")) + frozen: bool = field(metadata=config(field_name="frozen")) per_team_key_seeds_unverified: Dict[str, PerTeamKeySeedItem] = field( metadata=config(field_name="perTeamKeySeedsUnverified") ) @@ -6471,26 +6626,26 @@ class PublicKeyV2__PGP(DataClassJsonMixin): @dataclass class UserPlusKeysV2(DataClassJsonMixin): + device_keys: Dict[str, PublicKeyV2NaCl] = field( + metadata=config(field_name="deviceKeys") + ) uid: UID = field(metadata=config(field_name="uid")) - username: str = field(metadata=config(field_name="username")) eldest_seqno: Seqno = field(metadata=config(field_name="eldestSeqno")) status: StatusCode = field(metadata=config(field_name="status")) - device_keys: Dict[str, PublicKeyV2NaCl] = field( - metadata=config(field_name="deviceKeys") + remote_tracks: Dict[str, RemoteTrack] = field( + metadata=config(field_name="remoteTracks") ) + username: str = field(metadata=config(field_name="username")) pgp_keys: Dict[str, PublicKeyV2PGPSummary] = field( metadata=config(field_name="pgpKeys") ) - remote_tracks: Dict[str, RemoteTrack] = field( - metadata=config(field_name="remoteTracks") - ) unstubbed: bool = field(metadata=config(field_name="unstubbed")) - per_user_keys: Optional[Optional[List[PerUserKey]]] = field( - default=None, metadata=config(field_name="perUserKeys") - ) stellar_account_id: Optional[str] = field( default=None, metadata=config(field_name="stellarAccountID") ) + per_user_keys: Optional[Optional[List[PerUserKey]]] = field( + default=None, metadata=config(field_name="perUserKeys") + ) reset: Optional[ResetSummary] = field( default=None, metadata=config(field_name="reset") ) @@ -6698,6 +6853,14 @@ class FolderSyncConfigAndStatusWithFolder(DataClassJsonMixin): status: FolderSyncStatus = field(metadata=config(field_name="status")) +@dataclass +class FolderWithFavFlags(DataClassJsonMixin): + folder: Folder = field(metadata=config(field_name="folder")) + is_favorite: bool = field(metadata=config(field_name="isFavorite")) + is_ignored: bool = field(metadata=config(field_name="isIgnored")) + is_new: bool = field(metadata=config(field_name="isNew")) + + @dataclass class TLFBreak(DataClassJsonMixin): breaks: Optional[Optional[List[TLFIdentifyFailure]]] = field( diff --git a/pykeybasebot/types/stellar1/__init__.py b/pykeybasebot/types/stellar1/__init__.py index b69b1dd..38b4a2f 100644 --- a/pykeybasebot/types/stellar1/__init__.py +++ b/pykeybasebot/types/stellar1/__init__.py @@ -1,6 +1,6 @@ """stellar.1 -Auto-generated to Python types by avdl-compiler v1.4.4 (https://github.com/keybase/node-avdl-compiler) +Auto-generated to Python types by avdl-compiler v1.4.6 (https://github.com/keybase/node-avdl-compiler) Input files: - ../client/protocol/avdl/stellar1/bundle.avdl - ../client/protocol/avdl/stellar1/common.avdl @@ -114,21 +114,23 @@ class AccountBundleSecretUnsupported(DataClassJsonMixin): @dataclass class Asset(DataClassJsonMixin): + show_deposit_button: bool = field(metadata=config(field_name="showDepositButton")) type: str = field(metadata=config(field_name="type")) - code: str = field(metadata=config(field_name="code")) issuer: str = field(metadata=config(field_name="issuer")) verified_domain: str = field(metadata=config(field_name="verifiedDomain")) issuer_name: str = field(metadata=config(field_name="issuerName")) desc: str = field(metadata=config(field_name="desc")) info_url: str = field(metadata=config(field_name="infoUrl")) info_url_text: str = field(metadata=config(field_name="infoUrlText")) - show_deposit_button: bool = field(metadata=config(field_name="showDepositButton")) + code: str = field(metadata=config(field_name="code")) deposit_button_text: str = field(metadata=config(field_name="depositButtonText")) show_withdraw_button: bool = field(metadata=config(field_name="showWithdrawButton")) withdraw_button_text: str = field(metadata=config(field_name="withdrawButtonText")) withdraw_type: str = field(metadata=config(field_name="withdrawType")) transfer_server: str = field(metadata=config(field_name="transferServer")) auth_endpoint: str = field(metadata=config(field_name="authEndpoint")) + deposit_req_auth: bool = field(metadata=config(field_name="depositReqAuth")) + withdraw_req_auth: bool = field(metadata=config(field_name="withdrawReqAuth")) @dataclass @@ -547,8 +549,10 @@ class PaymentNotificationMsg(DataClassJsonMixin): @dataclass class AccountAssetLocal(DataClassJsonMixin): + available_to_send_worth: str = field( + metadata=config(field_name="availableToSendWorth") + ) name: str = field(metadata=config(field_name="name")) - asset_code: str = field(metadata=config(field_name="assetCode")) issuer_name: str = field(metadata=config(field_name="issuerName")) issuer_account_id: str = field(metadata=config(field_name="issuerAccountID")) issuer_verified_domain: str = field( @@ -560,15 +564,13 @@ class AccountAssetLocal(DataClassJsonMixin): ) worth_currency: str = field(metadata=config(field_name="worthCurrency")) worth: str = field(metadata=config(field_name="worth")) - available_to_send_worth: str = field( - metadata=config(field_name="availableToSendWorth") - ) + asset_code: str = field(metadata=config(field_name="assetCode")) + show_withdraw_button: bool = field(metadata=config(field_name="showWithdrawButton")) desc: str = field(metadata=config(field_name="desc")) info_url: str = field(metadata=config(field_name="infoUrl")) info_url_text: str = field(metadata=config(field_name="infoUrlText")) show_deposit_button: bool = field(metadata=config(field_name="showDepositButton")) deposit_button_text: str = field(metadata=config(field_name="depositButtonText")) - show_withdraw_button: bool = field(metadata=config(field_name="showWithdrawButton")) withdraw_button_text: str = field(metadata=config(field_name="withdrawButtonText")) reserves: Optional[Optional[List[AccountReserve]]] = field( default=None, metadata=config(field_name="reserves") @@ -630,19 +632,19 @@ class SendPaymentResLocal(DataClassJsonMixin): @dataclass class RequestDetailsLocal(DataClassJsonMixin): + amount: str = field(metadata=config(field_name="amount")) id: KeybaseRequestID = field(metadata=config(field_name="id")) - from_assertion: str = field(metadata=config(field_name="fromAssertion")) from_current_user: bool = field(metadata=config(field_name="fromCurrentUser")) to_user_type: ParticipantType = field(metadata=config(field_name="toUserType")) to_assertion: str = field(metadata=config(field_name="toAssertion")) - amount: str = field(metadata=config(field_name="amount")) - amount_description: str = field(metadata=config(field_name="amountDescription")) + from_assertion: str = field(metadata=config(field_name="fromAssertion")) worth_at_request_time: str = field(metadata=config(field_name="worthAtRequestTime")) + amount_description: str = field(metadata=config(field_name="amountDescription")) status: RequestStatus = field(metadata=config(field_name="status")) - asset: Optional[Asset] = field(default=None, metadata=config(field_name="asset")) currency: Optional[OutsideCurrencyCode] = field( default=None, metadata=config(field_name="currency") ) + asset: Optional[Asset] = field(default=None, metadata=config(field_name="asset")) @dataclass @@ -670,46 +672,46 @@ class SendResultCLILocal(DataClassJsonMixin): @dataclass class PaymentCLILocal(DataClassJsonMixin): + summary_advanced: str = field(metadata=config(field_name="summaryAdvanced")) tx_id: TransactionID = field(metadata=config(field_name="txID")) - time: TimeMs = field(metadata=config(field_name="time")) status: str = field(metadata=config(field_name="status")) status_detail: str = field(metadata=config(field_name="statusDetail")) amount: str = field(metadata=config(field_name="amount")) asset: Asset = field(metadata=config(field_name="asset")) + public_note_type: str = field(metadata=config(field_name="publicNoteType")) + public_note: str = field(metadata=config(field_name="publicNote")) source_amount_max: str = field(metadata=config(field_name="sourceAmountMax")) source_amount_actual: str = field(metadata=config(field_name="sourceAmountActual")) source_asset: Asset = field(metadata=config(field_name="sourceAsset")) is_advanced: bool = field(metadata=config(field_name="isAdvanced")) - summary_advanced: str = field(metadata=config(field_name="summaryAdvanced")) + time: TimeMs = field(metadata=config(field_name="time")) + unread: bool = field(metadata=config(field_name="unread")) from_stellar: AccountID = field(metadata=config(field_name="fromStellar")) - note: str = field(metadata=config(field_name="note")) note_err: str = field(metadata=config(field_name="noteErr")) - unread: bool = field(metadata=config(field_name="unread")) - public_note: str = field(metadata=config(field_name="publicNote")) - public_note_type: str = field(metadata=config(field_name="publicNoteType")) + note: str = field(metadata=config(field_name="note")) fee_charged_description: str = field( metadata=config(field_name="feeChargedDescription") ) - display_amount: Optional[str] = field( - default=None, metadata=config(field_name="displayAmount") + to_username: Optional[str] = field( + default=None, metadata=config(field_name="toUsername") ) - display_currency: Optional[str] = field( - default=None, metadata=config(field_name="displayCurrency") + to_assertion: Optional[str] = field( + default=None, metadata=config(field_name="toAssertion") ) - operations: Optional[Optional[List[str]]] = field( - default=None, metadata=config(field_name="operations") + from_username: Optional[str] = field( + default=None, metadata=config(field_name="fromUsername") ) to_stellar: Optional[AccountID] = field( default=None, metadata=config(field_name="toStellar") ) - from_username: Optional[str] = field( - default=None, metadata=config(field_name="fromUsername") + operations: Optional[Optional[List[str]]] = field( + default=None, metadata=config(field_name="operations") ) - to_username: Optional[str] = field( - default=None, metadata=config(field_name="toUsername") + display_currency: Optional[str] = field( + default=None, metadata=config(field_name="displayCurrency") ) - to_assertion: Optional[str] = field( - default=None, metadata=config(field_name="toAssertion") + display_amount: Optional[str] = field( + default=None, metadata=config(field_name="displayAmount") ) @@ -779,6 +781,7 @@ class PaymentDirectPost(DataClassJsonMixin): @dataclass class PaymentRelayPost(DataClassJsonMixin): + display_currency: str = field(metadata=config(field_name="displayCurrency")) from_device_id: keybase1.DeviceID = field( metadata=config(field_name="fromDeviceID") ) @@ -786,10 +789,9 @@ class PaymentRelayPost(DataClassJsonMixin): relay_account: AccountID = field(metadata=config(field_name="relayAccount")) team_id: keybase1.TeamID = field(metadata=config(field_name="teamID")) display_amount: str = field(metadata=config(field_name="displayAmount")) - display_currency: str = field(metadata=config(field_name="displayCurrency")) + quick_return: bool = field(metadata=config(field_name="quickReturn")) box_b_64: str = field(metadata=config(field_name="boxB64")) signed_transaction: str = field(metadata=config(field_name="signedTransaction")) - quick_return: bool = field(metadata=config(field_name="quickReturn")) batch_id: str = field(metadata=config(field_name="batchID")) to: Optional[keybase1.UserVersion] = field( default=None, metadata=config(field_name="to") @@ -835,8 +837,8 @@ class RelayOp(DataClassJsonMixin): @dataclass class PaymentSummaryDirect(DataClassJsonMixin): + from_display_amount: str = field(metadata=config(field_name="fromDisplayAmount")) kb_tx_id: KeybaseTransactionID = field(metadata=config(field_name="kbTxID")) - tx_id: TransactionID = field(metadata=config(field_name="txID")) tx_status: TransactionStatus = field(metadata=config(field_name="txStatus")) tx_err_msg: str = field(metadata=config(field_name="txErrMsg")) from_stellar: AccountID = field(metadata=config(field_name="fromStellar")) @@ -845,10 +847,13 @@ class PaymentSummaryDirect(DataClassJsonMixin): metadata=config(field_name="fromDeviceID") ) to_stellar: AccountID = field(metadata=config(field_name="toStellar")) + source_amount_actual: str = field(metadata=config(field_name="sourceAmountActual")) amount: str = field(metadata=config(field_name="amount")) asset: Asset = field(metadata=config(field_name="asset")) + source_amount_max: str = field(metadata=config(field_name="sourceAmountMax")) + from_airdrop: bool = field(metadata=config(field_name="fromAirdrop")) note_b_64: str = field(metadata=config(field_name="noteB64")) - from_display_amount: str = field(metadata=config(field_name="fromDisplayAmount")) + tx_id: TransactionID = field(metadata=config(field_name="txID")) from_display_currency: str = field( metadata=config(field_name="fromDisplayCurrency") ) @@ -860,18 +865,15 @@ class PaymentSummaryDirect(DataClassJsonMixin): unread: bool = field(metadata=config(field_name="unread")) from_primary: bool = field(metadata=config(field_name="fromPrimary")) batch_id: str = field(metadata=config(field_name="batchID")) - from_airdrop: bool = field(metadata=config(field_name="fromAirdrop")) - source_amount_max: str = field(metadata=config(field_name="sourceAmountMax")) - source_amount_actual: str = field(metadata=config(field_name="sourceAmountActual")) source_asset: Asset = field(metadata=config(field_name="sourceAsset")) - to: Optional[keybase1.UserVersion] = field( - default=None, metadata=config(field_name="to") + display_currency: Optional[str] = field( + default=None, metadata=config(field_name="displayCurrency") ) display_amount: Optional[str] = field( default=None, metadata=config(field_name="displayAmount") ) - display_currency: Optional[str] = field( - default=None, metadata=config(field_name="displayCurrency") + to: Optional[keybase1.UserVersion] = field( + default=None, metadata=config(field_name="to") ) @@ -911,23 +913,23 @@ class RequestPost(DataClassJsonMixin): @dataclass class RequestDetails(DataClassJsonMixin): id: KeybaseRequestID = field(metadata=config(field_name="id")) - from_user: keybase1.UserVersion = field(metadata=config(field_name="fromUser")) + status: RequestStatus = field(metadata=config(field_name="status")) + funding_kb_tx_id: KeybaseTransactionID = field( + metadata=config(field_name="fundingKbTxID") + ) to_assertion: str = field(metadata=config(field_name="toAssertion")) amount: str = field(metadata=config(field_name="amount")) + to_display_currency: str = field(metadata=config(field_name="toDisplayCurrency")) + from_user: keybase1.UserVersion = field(metadata=config(field_name="fromUser")) from_display_amount: str = field(metadata=config(field_name="fromDisplayAmount")) from_display_currency: str = field( metadata=config(field_name="fromDisplayCurrency") ) to_display_amount: str = field(metadata=config(field_name="toDisplayAmount")) - to_display_currency: str = field(metadata=config(field_name="toDisplayCurrency")) - funding_kb_tx_id: KeybaseTransactionID = field( - metadata=config(field_name="fundingKbTxID") - ) - status: RequestStatus = field(metadata=config(field_name="status")) + asset: Optional[Asset] = field(default=None, metadata=config(field_name="asset")) to_user: Optional[keybase1.UserVersion] = field( default=None, metadata=config(field_name="toUser") ) - asset: Optional[Asset] = field(default=None, metadata=config(field_name="asset")) currency: Optional[OutsideCurrencyCode] = field( default=None, metadata=config(field_name="currency") ) @@ -1056,13 +1058,13 @@ class StellarServerDefinitions(DataClassJsonMixin): @dataclass class WalletAccountLocal(DataClassJsonMixin): + account_mode: AccountMode = field(metadata=config(field_name="accountMode")) account_id: AccountID = field(metadata=config(field_name="accountID")) - is_default: bool = field(metadata=config(field_name="isDefault")) name: str = field(metadata=config(field_name="name")) balance_description: str = field(metadata=config(field_name="balanceDescription")) seqno: str = field(metadata=config(field_name="seqno")) currency_local: CurrencyLocal = field(metadata=config(field_name="currencyLocal")) - account_mode: AccountMode = field(metadata=config(field_name="accountMode")) + is_default: bool = field(metadata=config(field_name="isDefault")) account_mode_editable: bool = field( metadata=config(field_name="accountModeEditable") ) @@ -1074,9 +1076,8 @@ class WalletAccountLocal(DataClassJsonMixin): @dataclass class PaymentLocal(DataClassJsonMixin): - id: PaymentID = field(metadata=config(field_name="id")) tx_id: TransactionID = field(metadata=config(field_name="txID")) - time: TimeMs = field(metadata=config(field_name="time")) + id: PaymentID = field(metadata=config(field_name="id")) status_simplified: PaymentStatus = field( metadata=config(field_name="statusSimplified") ) @@ -1094,6 +1095,7 @@ class PaymentLocal(DataClassJsonMixin): from_account_id: AccountID = field(metadata=config(field_name="fromAccountID")) from_account_name: str = field(metadata=config(field_name="fromAccountName")) from_username: str = field(metadata=config(field_name="fromUsername")) + time: TimeMs = field(metadata=config(field_name="time")) to_account_name: str = field(metadata=config(field_name="toAccountName")) to_username: str = field(metadata=config(field_name="toUsername")) to_assertion: str = field(metadata=config(field_name="toAssertion")) @@ -1112,34 +1114,34 @@ class PaymentLocal(DataClassJsonMixin): batch_id: str = field(metadata=config(field_name="batchID")) from_airdrop: bool = field(metadata=config(field_name="fromAirdrop")) is_inflation: bool = field(metadata=config(field_name="isInflation")) - issuer_account_id: Optional[AccountID] = field( - default=None, metadata=config(field_name="issuerAccountID") - ) - to_account_id: Optional[AccountID] = field( - default=None, metadata=config(field_name="toAccountID") + trustline: Optional[PaymentTrustlineLocal] = field( + default=None, metadata=config(field_name="trustline") ) operations: Optional[Optional[List[str]]] = field( default=None, metadata=config(field_name="operations") ) + issuer_account_id: Optional[AccountID] = field( + default=None, metadata=config(field_name="issuerAccountID") + ) inflation_source: Optional[str] = field( default=None, metadata=config(field_name="inflationSource") ) - trustline: Optional[PaymentTrustlineLocal] = field( - default=None, metadata=config(field_name="trustline") + to_account_id: Optional[AccountID] = field( + default=None, metadata=config(field_name="toAccountID") ) @dataclass class BuildPaymentResLocal(DataClassJsonMixin): + worth_info: str = field(metadata=config(field_name="worthInfo")) ready_to_review: bool = field(metadata=config(field_name="readyToReview")) - from_: AccountID = field(metadata=config(field_name="from")) to_err_msg: str = field(metadata=config(field_name="toErrMsg")) amount_err_msg: str = field(metadata=config(field_name="amountErrMsg")) secret_note_err_msg: str = field(metadata=config(field_name="secretNoteErrMsg")) public_memo_err_msg: str = field(metadata=config(field_name="publicMemoErrMsg")) public_memo_override: str = field(metadata=config(field_name="publicMemoOverride")) worth_description: str = field(metadata=config(field_name="worthDescription")) - worth_info: str = field(metadata=config(field_name="worthInfo")) + from_: AccountID = field(metadata=config(field_name="from")) worth_amount: str = field(metadata=config(field_name="worthAmount")) worth_currency: str = field(metadata=config(field_name="worthCurrency")) display_amount_xlm: str = field(metadata=config(field_name="displayAmountXLM")) @@ -1227,20 +1229,23 @@ class OwnAccountCLILocal(DataClassJsonMixin): @dataclass class BatchResultLocal(DataClassJsonMixin): + wait_chat_duration_ms: TimeMs = field( + metadata=config(field_name="waitChatDurationMs") + ) start_time: TimeMs = field(metadata=config(field_name="startTime")) - prepared_time: TimeMs = field(metadata=config(field_name="preparedTime")) all_submitted_time: TimeMs = field(metadata=config(field_name="allSubmittedTime")) all_complete_time: TimeMs = field(metadata=config(field_name="allCompleteTime")) end_time: TimeMs = field(metadata=config(field_name="endTime")) + avg_relay_duration_ms: TimeMs = field( + metadata=config(field_name="avgRelayDurationMs") + ) overall_duration_ms: TimeMs = field(metadata=config(field_name="overallDurationMs")) prepare_duration_ms: TimeMs = field(metadata=config(field_name="prepareDurationMs")) submit_duration_ms: TimeMs = field(metadata=config(field_name="submitDurationMs")) wait_payments_duration_ms: TimeMs = field( metadata=config(field_name="waitPaymentsDurationMs") ) - wait_chat_duration_ms: TimeMs = field( - metadata=config(field_name="waitChatDurationMs") - ) + prepared_time: TimeMs = field(metadata=config(field_name="preparedTime")) count_success: int = field(metadata=config(field_name="countSuccess")) count_direct: int = field(metadata=config(field_name="countDirect")) count_relay: int = field(metadata=config(field_name="countRelay")) @@ -1253,9 +1258,6 @@ class BatchResultLocal(DataClassJsonMixin): avg_direct_duration_ms: TimeMs = field( metadata=config(field_name="avgDirectDurationMs") ) - avg_relay_duration_ms: TimeMs = field( - metadata=config(field_name="avgRelayDurationMs") - ) avg_error_duration_ms: TimeMs = field( metadata=config(field_name="avgErrorDurationMs") ) @@ -1266,15 +1268,15 @@ class BatchResultLocal(DataClassJsonMixin): @dataclass class ValidateStellarURIResultLocal(DataClassJsonMixin): + asset_code: str = field(metadata=config(field_name="assetCode")) operation: str = field(metadata=config(field_name="operation")) - origin_domain: str = field(metadata=config(field_name="originDomain")) message: str = field(metadata=config(field_name="message")) callback_url: str = field(metadata=config(field_name="callbackURL")) xdr: str = field(metadata=config(field_name="xdr")) summary: TxDisplaySummary = field(metadata=config(field_name="summary")) recipient: str = field(metadata=config(field_name="recipient")) amount: str = field(metadata=config(field_name="amount")) - asset_code: str = field(metadata=config(field_name="assetCode")) + origin_domain: str = field(metadata=config(field_name="originDomain")) asset_issuer: str = field(metadata=config(field_name="assetIssuer")) memo: str = field(metadata=config(field_name="memo")) memo_type: str = field(metadata=config(field_name="memoType")) @@ -1301,20 +1303,20 @@ class PaymentOp(DataClassJsonMixin): @dataclass class PaymentSummaryStellar(DataClassJsonMixin): + is_inflation: bool = field(metadata=config(field_name="isInflation")) tx_id: TransactionID = field(metadata=config(field_name="txID")) - from_: AccountID = field(metadata=config(field_name="from")) to: AccountID = field(metadata=config(field_name="to")) amount: str = field(metadata=config(field_name="amount")) asset: Asset = field(metadata=config(field_name="asset")) ctime: TimeMs = field(metadata=config(field_name="ctime")) cursor_token: str = field(metadata=config(field_name="cursorToken")) unread: bool = field(metadata=config(field_name="unread")) - is_inflation: bool = field(metadata=config(field_name="isInflation")) + from_: AccountID = field(metadata=config(field_name="from")) + summary_advanced: str = field(metadata=config(field_name="summaryAdvanced")) source_amount_max: str = field(metadata=config(field_name="sourceAmountMax")) source_amount_actual: str = field(metadata=config(field_name="sourceAmountActual")) source_asset: Asset = field(metadata=config(field_name="sourceAsset")) is_advanced: bool = field(metadata=config(field_name="isAdvanced")) - summary_advanced: str = field(metadata=config(field_name="summaryAdvanced")) inflation_source: Optional[str] = field( default=None, metadata=config(field_name="inflationSource") ) @@ -1328,8 +1330,8 @@ class PaymentSummaryStellar(DataClassJsonMixin): @dataclass class PaymentSummaryRelay(DataClassJsonMixin): + amount: str = field(metadata=config(field_name="amount")) kb_tx_id: KeybaseTransactionID = field(metadata=config(field_name="kbTxID")) - tx_id: TransactionID = field(metadata=config(field_name="txID")) tx_status: TransactionStatus = field(metadata=config(field_name="txStatus")) tx_err_msg: str = field(metadata=config(field_name="txErrMsg")) from_stellar: AccountID = field(metadata=config(field_name="fromStellar")) @@ -1337,28 +1339,28 @@ class PaymentSummaryRelay(DataClassJsonMixin): from_device_id: keybase1.DeviceID = field( metadata=config(field_name="fromDeviceID") ) + batch_id: str = field(metadata=config(field_name="batchID")) to_assertion: str = field(metadata=config(field_name="toAssertion")) relay_account: AccountID = field(metadata=config(field_name="relayAccount")) - amount: str = field(metadata=config(field_name="amount")) + tx_id: TransactionID = field(metadata=config(field_name="txID")) + cursor_token: str = field(metadata=config(field_name="cursorToken")) + team_id: keybase1.TeamID = field(metadata=config(field_name="teamID")) ctime: TimeMs = field(metadata=config(field_name="ctime")) rtime: TimeMs = field(metadata=config(field_name="rtime")) box_b_64: str = field(metadata=config(field_name="boxB64")) - team_id: keybase1.TeamID = field(metadata=config(field_name="teamID")) - cursor_token: str = field(metadata=config(field_name="cursorToken")) - batch_id: str = field(metadata=config(field_name="batchID")) from_airdrop: bool = field(metadata=config(field_name="fromAirdrop")) - to: Optional[keybase1.UserVersion] = field( - default=None, metadata=config(field_name="to") - ) - display_amount: Optional[str] = field( - default=None, metadata=config(field_name="displayAmount") - ) display_currency: Optional[str] = field( default=None, metadata=config(field_name="displayCurrency") ) claim: Optional[ClaimSummary] = field( default=None, metadata=config(field_name="claim") ) + display_amount: Optional[str] = field( + default=None, metadata=config(field_name="displayAmount") + ) + to: Optional[keybase1.UserVersion] = field( + default=None, metadata=config(field_name="to") + ) @dataclass diff --git a/pyproject.toml b/pyproject.toml index 32ff580..c05cb8a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,7 @@ black = {version = "^18.3-alpha.0", allows-prereleases = true} isort = "^4.3" flake8 = "^3.7" mypy = "^0.720.0" +pyotp = "^2.3" [tool.isort] combine_as_imports = true diff --git a/tests/fixtures/payment.json b/tests/fixtures/payment.json index a0bba3e..15c35fb 100644 --- a/tests/fixtures/payment.json +++ b/tests/fixtures/payment.json @@ -41,8 +41,10 @@ "infoUrlText": "", "showDepositButton": false, "depositButtonText": "", + "depositReqAuth": "", "showWithdrawButton": false, "withdrawButtonText": "", + "withdrawReqAuth": "", "withdrawType": "", "transferServer": "", "authEndpoint": ""