Files
2026-01-16 12:10:33 +03:00

188 lines
5.7 KiB
Python

"""Utility functions for tn-cli."""
from __future__ import print_function
import base64
import json
from PIL import Image
try:
from io import BytesIO as memory_io
except ImportError:
from cStringIO import StringIO as memory_io
import mimetypes
import os
from tn_globals import stdoutln
# Maximum in-band (included directly into the message) attachment size which fits into
# a message of 256K in size, assuming base64 encoding and 1024 bytes of overhead.
# This is size of an object *before* base64 encoding is applied.
MAX_INBAND_ATTACHMENT_SIZE = 195840
# Absolute maximum attachment size to be used with the server = 8MB.
MAX_EXTERN_ATTACHMENT_SIZE = 1 << 23
# Maximum allowed linear dimension of an inline image in pixels.
MAX_IMAGE_DIM = 768
# String used as a delete marker. I.e. when a value needs to be deleted, use this string
DELETE_MARKER = 'DEL!'
# Unicode DEL character used internally by Tinode when a value needs to be deleted.
TINODE_DEL = ''
# Python is retarded.
class dotdict(dict):
"""dot.notation access to dictionary attributes"""
__getattr__ = dict.get
__setattr__ = dict.__setitem__
__delattr__ = dict.__delitem__
# Pack name, description, and avatar into a theCard.
def makeTheCard(fn, note, photofile):
card = None
if (fn != None and fn.strip() != "") or photofile != None or note != None:
card = {}
if fn != None:
fn = fn.strip()
card['fn'] = TINODE_DEL if fn == DELETE_MARKER or fn == '' else fn
if note != None:
note = note.strip()
card['note'] = TINODE_DEL if note == DELETE_MARKER or note == '' else note
if photofile != None:
if photofile == '' or photofile == DELETE_MARKER:
# Delete the avatar.
card['photo'] = {
'data': TINODE_DEL
}
else:
try:
f = open(photofile, 'rb')
# File extension is used as a file type
mimetype = mimetypes.guess_type(photofile)
if mimetype[0]:
mimetype = mimetype[0].split("/")[1]
else:
mimetype = 'jpeg'
data = base64.b64encode(f.read())
# python3 fix.
if type(data) is not str:
data = data.decode()
card['photo'] = {
'data': data,
'type': mimetype
}
f.close()
except IOError as err:
stdoutln("Error opening '" + photofile + "':", err)
return card
# Create drafty representation of a message with an inline image.
def inline_image(filename):
try:
im = Image.open(filename, 'r')
width = im.width
height = im.height
format = im.format if im.format else "JPEG"
if width > MAX_IMAGE_DIM or height > MAX_IMAGE_DIM:
# Scale the image
scale = min(min(width, MAX_IMAGE_DIM) / width, min(height, MAX_IMAGE_DIM) / height)
width = int(width * scale)
height = int(height * scale)
resized = im.resize((width, height))
im.close()
im = resized
mimetype = 'image/' + format.lower()
bitbuffer = memory_io()
im.save(bitbuffer, format=format)
data = base64.b64encode(bitbuffer.getvalue())
# python3 fix.
if type(data) is not str:
data = data.decode()
result = {
'txt': ' ',
'fmt': [{'len': 1}],
'ent': [{'tp': 'IM', 'data':
{'val': data, 'mime': mimetype, 'width': width, 'height': height,
'name': os.path.basename(filename)}}]
}
im.close()
return result
except IOError as err:
stdoutln("Failed processing image '" + filename + "':", err)
return None
# Create a drafty message with an *in-band* attachment.
def attachment(filename):
try:
f = open(filename, 'rb')
# Try to guess the mime type.
mimetype = mimetypes.guess_type(filename)[0]
data = base64.b64encode(f.read())
# python3 fix.
if type(data) is not str:
data = data.decode()
result = {
'fmt': [{'at': -1}],
'ent': [{'tp': 'EX', 'data':{
'val': data, 'mime': mimetype, 'name':os.path.basename(filename)
}}]
}
f.close()
return result
except IOError as err:
stdoutln("Error processing attachment '" + filename + "':", err)
return None
# encode_to_bytes converts the 'src' to a byte array.
# An object/dictionary is first converted to json string then it's converted to bytes.
# A string is directly converted to bytes.
def encode_to_bytes(src):
if src == None:
return None
if isinstance(src, str):
return ('"' + src + '"').encode()
return json.dumps(src).encode('utf-8')
# Parse credentials
def parse_cred(cred):
result = None
if cred != None:
result = []
for c in cred.split(","):
parts = c.split(":")
from tinode_grpc import pb
result.append(pb.ClientCred(method=parts[0] if len(parts) > 0 else None,
value=parts[1] if len(parts) > 1 else None,
response=parts[2] if len(parts) > 2 else None))
return result
# Parse trusted values: [staff,rm-verified].
def parse_trusted(trusted):
result = None
if trusted != None:
result = {}
for t in trusted.split(","):
t = t.strip()
if t.startswith("rm-"):
result[t[3:]] = False
else:
result[t] = True
return result