Utilities
zwylib.SingletonMeta
class SingletonMeta(type)Metaclass implementing the singleton pattern. Use it as the metaclass for any class that must have only one instance.
Example
class MyManager(metaclass=SingletonMeta):
...
a = MyManager()
b = MyManager()
assert a is b # Truezwylib.Callback1
zwylib.Callback1(fn: (Any) -> None)Wrapper class allowing a Python function to be passed into Java code via Chaquopy, emulating the Utilities.Callback Java interface.
Constructor Arguments
fn((Any) ->None): A Python function that accepts a single argument and returns nothing. Called from Java via.run(...).
Methods
run
Callback1.run(arg: Any) -> NoneCalled from Java, forwards the provided argument to the Python function. Exceptions are logged internally and not raised.
Example
def my_python_callback(value):
print(f"Received from Java: {value}")
callback = zwylib.Callback1(my_python_callback)
some_java_object.setCallback(callback)zwylib.PyRunnable
zwylib.PyRunnable(fn: () -> None)Wrapper class allowing a no-argument Python function to be passed into Java code as a java.lang.Runnable. Useful for Java setters that expect a Runnable callback (e.g. setOnDismissListener).
Constructor Arguments
fn(() ->None): A Python function that accepts no arguments and returns nothing. Called from Java via.run().
Methods
run
PyRunnable.run() -> NoneCalled from Java, invokes the wrapped Python function. Exceptions are logged internally and not raised.
Example
def on_dismiss():
print("Dismissed!")
sheet.setOnDismissListener(zwylib.PyRunnable(on_dismiss))Java Interop Helpers
zwylib.iterable_to_arraylist
zwylib.iterable_to_arraylist(
iterable: Optional[Iterable[Any]],
int_auto_convert=True
) -> Optional[ArrayList]Converts any Python iterable to a Java ArrayList, optionally auto-converting Python integers to Java jint to avoid overflow/type mismatches.
Arguments
iterable(Optional[Iterable[Any]]): The iterable to convert. IfNone, returnsNone.int_auto_convert(bool, defaultTrue): IfTrue, converts Pythonintvalues to Javajintwhen adding to theArrayList(falling back to a plain add on overflow).
Returns
Optional[ArrayList]: A JavaArrayListcontaining the elements of the input iterable, orNoneif the input isNone.
Example
java_array = zwylib.iterable_to_arraylist([1, "item2"])
# java_array contains [jint(1), "item2"]zwylib.arraylist_to_list
zwylib.arraylist_to_list(j_array_list: Optional[ArrayList]) -> Optional[List]Converts a Java ArrayList to a Python list.
Arguments
j_array_list(Optional[ArrayList]): The JavaArrayListto convert. IfNone, returnsNone.
Returns
Optional[List]: A Python list containing the elements of theArrayList, orNoneif the input isNone.
Example
java_array = ArrayList()
java_array.add("item1")
java_array.add("item2")
python_list = zwylib.arraylist_to_list(java_array)
# python_list is ["item1", "item2"]zwylib.list_to_arraylist
zwylib.list_to_arraylist(python_list: Optional[List], int_auto_convert=True)Deprecated: Use
iterable_to_arraylistinstead.
Kept for backward compatibility — internally delegates to iterable_to_arraylist. Calling it emits a deprecation warning.
Exception Formatting
zwylib.format_exc
zwylib.format_exc() -> strFormats the current exception traceback as a string, similar to traceback.format_exc().
Returns
str: A string containing the formatted traceback of the current exception, stripped of leading/trailing whitespace.
Example
try:
1 / 0
except ZeroDivisionError:
error_trace = zwylib.format_exc()
print(error_trace) # Prints the formatted tracebackzwylib.format_exc_from
zwylib.format_exc_from(e: Exception) -> strFormats the full traceback of a specific exception as a string.
Arguments
e(Exception): The exception whose traceback should be formatted.
Returns
str: A string containing the formatted traceback of the exception, stripped of leading/trailing whitespace.
Example
try:
1 / 0
except ZeroDivisionError as e:
error_trace = zwylib.format_exc_from(e)
print(error_trace) # Prints the formatted tracebackzwylib.format_exc_only
zwylib.format_exc_only(e: Exception) -> strFormats only the exception message and type (without the full traceback) as a string.
Arguments
e(Exception): The exception whose message and type should be formatted.
Returns
str: A string containing the formatted exception message and type, stripped of leading/trailing whitespace.
Example
try:
1 / 0
except ZeroDivisionError as e:
error_msg = zwylib.format_exc_only(e)
print(error_msg) # Prints: ZeroDivisionError: division by zeroVersion Checks
zwylib.is_zwylib_version_sufficient
zwylib.is_zwylib_version_sufficient(
plugin_name: str,
version: str,
notify: bool = True
) -> boolChecks whether the current ZwyLib version is greater than or equal to the required version. If the version is insufficient and notify is True, a bulletin is shown with a button allowing the user to navigate to the update.
Arguments
plugin_name(str): Plugin name shown in the bulletin.version(str): Minimum required ZwyLib version.notify(bool, defaultTrue): Whether to show a bulletin on version mismatch.
Returns
bool:Trueif current ZwyLib version is sufficient,Falseotherwise.
Example
zwylib.is_zwylib_version_sufficient("MyPlugin", "1.2.0")zwylib.is_client_version_sufficient
zwylib.is_client_version_sufficient(version_to_compare: str) -> boolChecks whether the current exteraGram client build is greater than or equal to version_to_compare. Returns False (and logs the error) if the comparison fails for any reason.
Arguments
version_to_compare(str): Minimum required client version.
Returns
bool:Trueif the current client version is sufficient,Falseotherwise.
Example
if not zwylib.is_client_version_sufficient("12.6.0"):
bulletins.show_error("This plugin requires exteraGram 12.6.0 or newer.")Helper Functions
zwylib.safe_func_call
zwylib.safe_func_call(func: Optional[Callable], args: Optional[List[Any]] = None) -> AnyCalls func with the given positional arguments, catching and logging any exception instead of letting it propagate. If func returns a coroutine, it’s automatically scheduled via zwylib.async_manager.run_task and the resulting Future is returned instead of the coroutine itself.
Arguments
func(Optional[Callable]): The function to call. IfNone, nothing happens.args(Optional[List[Any]], defaultNone): Positional arguments to pass tofunc.
Returns
Any: Whateverfuncreturns (or the scheduledFutureif it’s a coroutine function), orNoneiffuncisNoneor raised.
Example
zwylib.safe_func_call(on_finish_callback, [self])zwylib.create_bundle
zwylib.create_bundle(args: Dict[str, Union[bool, int, str, float]]) -> BundleBuilds an Android Bundle from a plain Python dict, automatically picking the right put* method based on each value’s type (int, bool, str, float). Keys whose value isn’t one of these types are silently skipped.
Arguments
args(Dict[str, Union[bool, int, str, float]]): Mapping of keys to values to put into the bundle.
Returns
Bundle: The populated AndroidBundle.
Example
args = zwylib.create_bundle({"isAlwaysShare": True, "chatAddType": 2})
activity = GroupCreateActivity(args)zwylib.open_message
zwylib.open_message(peer_id: int, msg_id: int, topic_id: Optional[int] = None) -> NoneOpens a specific message in a chat, using the app’s currently active activity. Assumes the peer is already locally accessible (i.e. the app already has its access hash) — for chats/channels that might not be, use safe_open_message instead.
Arguments
peer_id(int): ID of the peer (chat or user) to open.msg_id(int): ID of the message to scroll to.topic_id(Optional[int], defaultNone): ID of the topic containing the message, if applicable.
Example
zwylib.open_message(-12345, 67890)zwylib.safe_open_message
zwylib.safe_open_message(
peer_id: int,
msg_id: int,
topic_id=0,
chat_username: Optional[str] = None
) -> NoneCoroutine version of open_message that resolves the peer’s access hash first if it isn’t already known locally, using chat_username to look it up. This is what powers the bulletin *_with_post_redirect methods in InnerBulletinHelper.
Arguments
peer_id(int): ID of the peer (chat or user) to open.msg_id(int): ID of the message to scroll to.topic_id(int, default0): ID of the topic containing the message, if applicable.chat_username(Optional[str], defaultNone): Username used to resolve the peer if its access hash isn’t known locally yet. If it’s needed but not provided, the function logs an error and does nothing.
Example
async def go_to_post():
await zwylib.safe_open_message(-12345, 67890, chat_username="somepublicchat")
zwylib.async_manager.run_task(go_to_post())