Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Updates #224

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
#!/bin/bash
black $(git ls-files '*.py')
git add -u
6 changes: 3 additions & 3 deletions contact/contactbot.py
Original file line number Diff line number Diff line change
Expand Up @@ -374,9 +374,9 @@ async def inbound_sms_handler(request: web.Request) -> web.Response:
if not msg_data:
msg_data["text"] = await request.text()
recipient = utils.get_secret("ADMIN")
msg_data[
"note"
] = "fallback, signal destination not found for this sms destination"
msg_data["note"] = (
"fallback, signal destination not found for this sms destination"
)
if agent := request.headers.get("User-Agent"):
msg_data["user-agent"] = agent
# send the admin the full post body, not just the user-friendly part
Expand Down
83 changes: 37 additions & 46 deletions forest/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,8 @@ async def start_process(self) -> None:
path += " --download-path /tmp"
else:
path += " --trust-new-identities always"
command = f"{path} --config {utils.ROOT_DIR} --user {self.bot_number} jsonRpc".split()
logging.info(command)
command = f"{path} --config {utils.ROOT_DIR}/ --user {self.bot_number} jsonRpc".split()
logging.info(" ".join(command))
proc_launch_time = time.time()
# this ought to FileNotFoundError but doesn't
self.proc = await asyncio.create_subprocess_exec(
Expand Down Expand Up @@ -391,7 +391,7 @@ async def signal_rpc_request(self, method: str, **params: Any) -> Message:
"""Sends a jsonRpc command to signal-cli or auxin-cli"""
return await self.wait_for_response(req=rpc(method, **params))

async def set_profile_auxin(
async def set_profile(
self,
given_name: Optional[str] = "",
family_name: Optional[str] = "",
Expand All @@ -401,18 +401,22 @@ async def set_profile_auxin(
) -> str:
"""set given and family name, payment address (must be b64 format),
and profile picture"""
params: JSON = {"name": {"givenName": given_name}}
params: JSON = {"givenName": given_name}
if given_name and family_name:
params["name"]["familyName"] = family_name
params["familyName"] = family_name
if payment_address:
params["mobilecoinAddress"] = payment_address
if payment_address[0] != "C":
payment_address = mc_util.b58_wrapper_to_b64_public_address(
payment_address
)
params["mobileCoinAddress"] = payment_address
if profile_path:
params["avatarFile"] = profile_path
params["avatar"] = profile_path
for parameter, value in kwargs.items():
if value:
params[parameter] = value
rpc_id = f"setProfile-{get_uid()}"
await self.outbox.put(rpc("setProfile", params, rpc_id))
await self.outbox.put(rpc("updateProfile", params, rpc_id))
return rpc_id

async def save_sent_message(self, rpc_id: str, params: dict[str, str]) -> None:
Expand Down Expand Up @@ -521,7 +525,7 @@ async def respond(
logging.error(json.dumps(target_msg.blob))
if target_msg.group:
return await self.send_message(
None, msg, group=target_msg.group_id, **other_params
None, msg, group=target_msg.group, **other_params
)
destination = target_msg.source or target_msg.uuid
return await self.send_message(destination, msg, **other_params)
Expand Down Expand Up @@ -565,7 +569,7 @@ async def send_typing(
# https://github.com/signalapp/Signal-Android/blob/master/app/src/main/java/org/thoughtcrime/securesms/components/TypingStatusRepository.java#L32
"Send a typing indicator to the person or group the message is from"
if msg:
group = msg.group_id or ""
group = msg.group or ""
recipient = msg.source
if utils.AUXIN:
content: dict = {
Expand All @@ -582,7 +586,7 @@ async def send_typing(
await self.send_message(recipient, "", content=content)
return
if group:
await self.outbox.put(rpc("sendTyping", group_id=[group], stop=stop))
await self.outbox.put(rpc("sendTyping", group=[group], stop=stop))
else:
await self.outbox.put(rpc("sendTyping", recipient=[recipient], stop=stop))

Expand All @@ -609,7 +613,7 @@ async def send_sticker(
}
content = {"dataMessage": {"body": None, "sticker": stick}}
if msg.group:
await self.send_message(None, "", group=msg.group_id, content=content)
await self.send_message(None, "", group=msg.group, content=content)
else:
await self.send_message(msg.source, "", content=content)
return
Expand Down Expand Up @@ -965,34 +969,24 @@ async def async_exec(stmts: str, env: Optional[dict] = None) -> Any:
return exception_traceback
return None

def get_recipients(self) -> list[dict[str, str]]:
"""Returns a list of all known recipients by parsing underlying datastore."""
return json.loads(
open(f"data/{self.bot_number}.d/recipients-store").read()
).get("recipients", [])

def get_uuid_by_phone(self, phonenumber: str) -> Optional[str]:
"""Queries the recipients-store file for a UUID, provided a phone number."""
async def get_uuid_by_phone(self, phonenumber: str) -> Optional[str]:
"""Queries signal-cli's recipients-store for a UUID, provided a phone number."""
if phonenumber.startswith("+"):
maybe_recipient = [
recipient
for recipient in self.get_recipients()
if phonenumber == recipient.get("number")
]
if maybe_recipient:
return maybe_recipient[0]["uuid"]
return (
(await self.signal_rpc_request("listContacts", recipient=phonenumber))
.blob.get("result", {})
.get("uuid")
)
return None

def get_number_by_uuid(self, uuid_: str) -> Optional[str]:
"""Queries the recipients-store file for a phone number, provided a uuid."""
async def get_number_by_uuid(self, uuid_: str) -> Optional[str]:
"""Queries signal-cli's recipients-store for a phone number, provided a uuid."""
if uuid_.count("-") == 4:
maybe_recipient = [
recipient
for recipient in self.get_recipients()
if uuid_ == recipient.get("uuid")
]
if maybe_recipient:
return maybe_recipient[0]["number"]
return (
(await self.signal_rpc_request("listContacts", recipient=uuid_))
.blob.get("result", {})
.get("number")
)
return None


Expand Down Expand Up @@ -1084,9 +1078,7 @@ async def do_fsr(self, msg: Message) -> Response:

@requires_admin
async def do_setup(self, _: Message) -> str:
if not utils.AUXIN:
return "Can't set payment address without auxin"
await self.set_profile_auxin(
await self.set_profile(
mobilecoin_address=mc_util.b58_wrapper_to_b64_public_address(
await self.mobster.ensure_address()
)
Expand Down Expand Up @@ -1177,7 +1169,7 @@ async def do_set_profile(self, message: Message) -> Response:
attachments = await get_attachment_paths(message)
user_image = attachments[0] if attachments else None
if user_image or (message.tokens and len(message.tokens) > 0):
await self.set_profile_auxin(
await self.set_profile(
given_name=message.arg1,
family_name=message.arg2,
payment_address=message.arg3,
Expand Down Expand Up @@ -1291,10 +1283,11 @@ async def send_payment( # pylint: disable=too-many-locals
"build_transaction",
account_id=account_id,
recipient_public_address=address,
value_pmob=str(int(amount_pmob)),
fee=str(int(1e12 * 0.0004)),
amount=dict(value=str(int(amount_pmob)), token_id="0"),
**params,
)
if raw_prop.get("error"):
raise ValueError
prop = raw_prop.get("result", {}).get("tx_proposal")
tx_id = raw_prop.get("result", {}).get("transaction_log_id")
# this is to NOT log transactions into the full service DB if the sender
Expand Down Expand Up @@ -1338,6 +1331,7 @@ async def send_payment( # pylint: disable=too-many-locals
)
)["result"]["receiver_receipts"][0]
# this gets us a Receipt protobuf
full_service_receipt["object"] = "receiver_receipt"
b64_receipt = mc_util.full_service_receipt_to_b64_receipt(full_service_receipt)
if utils.AUXIN:
content = compose_payment_content(b64_receipt, receipt_message)
Expand Down Expand Up @@ -1406,7 +1400,6 @@ def __init__(self, bot_number: Optional[str] = None) -> None:
super().__init__(bot_number)

async def handle_message(self, message: Message) -> Response:

# import pdb;pdb.set_trace()
pending_answer, probably_future = get_source_or_uuid_from_dict(
message, self.pending_answers
Expand Down Expand Up @@ -1499,7 +1492,6 @@ async def ask_intable_question(
recipient, question_text, require_first_device
)
if answer and not answer.isnumeric():

# cancel if user replies with any of the terminal answers "stop, cancel, quit, etc. defined above"
if answer.lower() in self.TERMINAL_ANSWERS:
return None
Expand Down Expand Up @@ -1670,7 +1662,6 @@ async def ask_multiple_choice_question( # pylint: disable=too-many-arguments

# when there is a match
if answer and answer.lower() in lower_dict_options.keys():

# if confirmation is required ask for it as a yes/no question
if require_confirmation:
confirmation_text = (
Expand Down Expand Up @@ -1796,7 +1787,7 @@ async def do_setup(self, msg: Message) -> str:
attachments = await get_attachment_paths(msg)
if attachments:
fields["profile_path"] = attachments[0]
await self.set_profile_auxin(**fields)
await self.set_profile(**fields)
return f"set {', '.join(fields)}"


Expand Down
32 changes: 9 additions & 23 deletions forest/extra.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,11 @@ def visit_Call(self, node: ast.Call) -> None:
and getattr(node.func.value, "attr", "") == "dialog"
):
vals = [
c.value
if isinstance(c, ast.Constant)
else f"(python) `{self.get_source(c)}`"
(
c.value
if isinstance(c, ast.Constant)
else f"(python) `{self.get_source(c)}`"
)
for c in node.args
if c
]
Expand Down Expand Up @@ -129,33 +131,17 @@ async def get_displayname(self, uuid: str) -> str:
uuid = await self.displayname_lookup_cache.get(uuid, uuid)
# phone number, not uuid provided
if uuid.startswith("+"):
uuid = self.get_uuid_by_phone(uuid) or uuid
uuid = await self.get_uuid_by_phone(uuid) or uuid
maybe_displayname = await self.displayname_cache.get(uuid)
if (
maybe_displayname
and "givenName" not in maybe_displayname
and "ivenName" not in maybe_displayname
and " " not in maybe_displayname
):
return maybe_displayname
maybe_user_profile = await self.profile_cache.get(uuid)
# if no luck, but we have a valid uuid
user_given = ""
if (
not maybe_user_profile or not maybe_user_profile.get("givenName", "")
) and uuid.count("-") == 4:
try:
maybe_user_profile = (
await self.signal_rpc_request("getprofile", peer_name=uuid)
).blob or {}
user_given = maybe_user_profile.get("givenName", "")
await self.profile_cache.set(uuid, maybe_user_profile)
except AttributeError:
# this returns a Dict containing an error key
user_given = "[error]"
elif maybe_user_profile and "givenName" in maybe_user_profile:
user_given = maybe_user_profile["givenName"]
if not user_given:
user_given = "givenName"
result = await self.signal_rpc_request("listContacts", recipient=uuid)
user_given = result.blob.get("result", {}).get("profile", {}).get("givenName")
user_given = user_given.replace(" ", "_")
if uuid and ("+" not in uuid and "-" in uuid):
user_short = f"{user_given}_{uuid.split('-')[1]}"
Expand Down
2 changes: 0 additions & 2 deletions forest/fuse.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,6 @@ class FUSE(object):
def __init__(
self, operations, mountpoint, raw_fi=False, encoding="utf-8", **kwargs
) -> None:

"""
Setting raw_fi to True will cause FUSE to pass the fuse_file_info
class as is to Operations, instead of just the fh field.
Expand Down Expand Up @@ -719,7 +718,6 @@ def readdir(self, path, buf, filler, offset, fip):
for item in self.operations(
"readdir", self._decode_optional_path(path), fip.contents.fh
):

if isinstance(item, str):
name, st, offset = item, None, 0
else:
Expand Down
1 change: 1 addition & 0 deletions forest/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
breaks our typing if we expect Message.attachments to be list[str].
Using `or` like this is a bit of a hack, but it's what we've got.
"""

import shlex
import unicodedata
import json
Expand Down
35 changes: 21 additions & 14 deletions forest/payments_monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,14 @@ def __init__(self, url: str = "") -> None:
if not url:
url = (
utils.get_secret("FULL_SERVICE_URL") or "http://localhost:9090/"
).removesuffix("/wallet") + "/wallet"
).removesuffix("/wallet") + "/wallet/v2"

self.account_id: Optional[str] = None
logging.info("full-service url: %s", url)
self.url = url

async def req_(self, method: str, **params: Any) -> dict:
logging.info("full-service request: %s", method)
logging.info("full-service request: %s / %s", method, params)
result = await self.req({"method": method, "params": params})
if "error" in result:
logging.error(result)
Expand All @@ -117,7 +117,14 @@ async def req(self, data: dict) -> dict:
data=json.dumps(better_data),
headers={"Content-Type": "application/json"},
) as resp:
return await resp.json()
result = await resp.json()
if (
"invalid type: null" in json.dumps(result)
and data.get("params") == None
):
data["params"] = {}
return await self.req(data)
return result

async def get_all_txos_for_account(self) -> dict[str, dict]:
txos = (
Expand Down Expand Up @@ -257,9 +264,12 @@ async def get_my_address(self) -> str:
"""Returns either the address set, or the address specified by the secret
or the first address in the full service instance in that order"""
acc_id = await self.get_account()
res = await self.req({"method": "get_all_accounts"})
res = await self.fsr_get_accounts()
return res["result"]["account_map"][acc_id]["main_address"]

async def fsr_get_accounts(self) -> dict:
return await self.req({"method": "get_accounts", "params": {}})

async def get_account(self, account_name: Optional[str] = None) -> str:
"""returns the account id matching account_name in Full Service Wallet"""

Expand All @@ -270,16 +280,12 @@ async def get_account(self, account_name: Optional[str] = None) -> str:
account_name = utils.get_secret("FS_ACCOUNT_NAME")

## get all account IDs for the Wallet / fullservice instance
account_ids = (await self.req({"method": "get_all_accounts"}))["result"][
"account_ids"
]
account_ids = (await self.fsr_get_accounts())["result"]["account_ids"]
maybe_account_id = []
if account_name is not None:
## get the account map for the accounts in the wallet
account_map = [
(await self.req({"method": "get_all_accounts"}))["result"][
"account_map"
][x]
(await self.fsr_get_accounts())["result"]["account_map"][x]
for x in account_ids
]

Expand Down Expand Up @@ -314,20 +320,21 @@ async def get_receipt_amount_pmob(self, receipt_str: str) -> Optional[int]:
if tx["result"]["receipt_transaction_status"] == "TransactionPending":
await asyncio.sleep(1)
continue
pmob = int(tx["result"]["txo"]["value_pmob"])
pmob = int(tx["result"]["txo"]["value"])
return pmob

account_id: Optional[str] = None

async def get_balance(self) -> int:
account_id = await self.get_account()
value = (
await self.req(
{
"method": "get_balance_for_account",
"params": {"account_id": await self.get_account()},
"method": "get_balance",
"params": {"account_id": account_id},
}
)
)["result"]["balance"]["unspent_pmob"]
)["result"]["balance_per_token"]["0"]["unspent"]
return int(value)

async def get_transactions(self, account_id: str) -> dict[str, dict[str, str]]:
Expand Down
Loading