### Get Plugin Setting Example Source: https://plugins.exteragram.app/docs/plugin-settings Demonstrates how to retrieve the current value of a specific setting using its key. Provides a default value if the setting has not been previously saved. ```python # Get the value of "test_switch_key", defaulting to False if it was never saved. is_enabled = self.get_setting("test_switch_key", False) ``` -------------------------------- ### Example: Dialog with Items Source: https://plugins.exteragram.app/docs/alert-dialog-builder An example demonstrating how to create and display a dialog with a list of selectable items. ```APIDOC ## Example: Dialog with Items ```python from ui.alert import AlertDialogBuilder from client_utils import get_last_fragment from android_utils import log def on_item_click(bld: AlertDialogBuilder, which: int): items_list = ["Option A", "Option B", "Option C"] log(f"Item '{items_list[which]}' (index {which}) selected.") bld.dismiss() item_builder = AlertDialogBuilder(activity) item_builder.set_title("Choose an Option") item_builder.set_items( ["Option A", "Option B", "Option C"], on_item_click ) item_builder.set_negative_button("Cancel", lambda b, w: b.dismiss()) item_builder.show() ``` ``` -------------------------------- ### Set Plugin Setting Example Source: https://plugins.exteragram.app/docs/plugin-settings Shows how to save a new value for a setting. This example toggles a boolean setting by retrieving its current value and then setting the opposite. ```python # Example: toggle a boolean setting. current_value = self.get_setting("test_switch_key", False) self.set_setting("test_switch_key", not current_value) # If changing one setting should rebuild the whole page, ``` -------------------------------- ### Loading Dialog Example Source: https://plugins.exteragram.app/docs/alert-dialog-builder Illustrates how to create and display a loading dialog with a spinner. ```APIDOC ## POST /api/loading_dialog ### Description This endpoint demonstrates how to create and display a loading dialog using `AlertDialogBuilder` with a spinner. ### Method POST ### Endpoint /api/loading_dialog ### Parameters #### Request Body - **message** (string) - Required - The message to display in the loading dialog. - **title** (string) - Optional - The title of the loading dialog. ### Request Example ```json { "message": "Please wait while data is being fetched.", "title": "Loading Data..." } ``` ### Response #### Success Response (200) - **status** (string) - Indicates the success of the operation. #### Response Example ```json { "status": "Loading dialog displayed successfully" } ``` ``` -------------------------------- ### Complete First Plugin with Message Hook Source: https://plugins.exteragram.app/docs/first-plugin A complete plugin example that registers an outgoing message hook to rewrite messages starting with '.hello'. It includes settings for a custom greeting template and handles message parsing and formatting. ```python from typing import Any, List from base_plugin import BasePlugin, HookResult, HookStrategy from ui.settings import Header, Input, Text __id__ = "hello_world" __name__ = "Hello World" __description__ = "Rewrites .hello commands into a friendly message" __author__ = "Your Name" __version__ = "1.0.0" __icon__ = "exteraPlugins/1" __app_version__ = ">=12.5.1" __sdk_version__ = ">=1.4.3.6" DEFAULT_TEMPLATE = "Hello, {name}!" class HelloWorldPlugin(BasePlugin): def on_plugin_load(self): # Register the outgoing-message hook once when the plugin is loaded. self.add_on_send_message_hook() self.log("Hello World plugin loaded") def create_settings(self) -> List[Any]: return [ Header(text="Hello World"), Input( key="template", text="Greeting template", default=DEFAULT_TEMPLATE, subtext="Use {name} where the entered name should appear.", icon="msg_edit", ), Text( text="Example", subtext=".hello Alice -> Hello, Alice!", icon="msg_info", ), ] def on_send_message_hook(self, account: int, params: Any) -> HookResult: # Some sends are media-only, so always validate that we really have text. if not isinstance(getattr(params, "message", None), str): return HookResult() raw_text = params.message.strip() if not raw_text.startswith(".hello"): return HookResult() # Split only once so ".hello Alice Smith" keeps the full name. parts = raw_text.split(" ", 1) name = parts[1].strip() if len(parts) > 1 else "" if not name: params.message = "Usage: .hello " return HookResult(strategy=HookStrategy.MODIFY, params=params) # Read the saved template or fall back to the default one. template = self.get_setting("template", DEFAULT_TEMPLATE) try: params.message = template.format(name=name) except Exception: # If the user saved a broken template, fail softly and explain why. params.message = "Template must contain valid Python format placeholders, for example {name}." # Tell the plugin engine that we changed outgoing message params. return HookResult(strategy=HookStrategy.MODIFY, params=params) ``` -------------------------------- ### Create Sticker View Example Source: https://plugins.exteragram.app/docs/plugin-settings Demonstrates creating a custom view for sticker display, intended for use with SimpleSettingFactory. It sets up the layout and initializes a BackupImageView. ```python from typing import Any, List from android.view import Gravity, View from android.widget import LinearLayout from org.telegram.messenger import AndroidUtilities, MediaDataController, UserConfig from org.telegram.ui.Components import BackupImageView, LayoutHelper, UItem from base_plugin import BasePlugin from ui.settings import ( Custom, Divider, EditText, Header, Input, Selector, SimpleSettingFactory, Switch, Text, ) def create_sticker_view(context, list_view, current_account: int, class_guid: int, resources_provider): main_layout = LinearLayout(context) main_layout.setOrientation(LinearLayout.VERTICAL) main_layout.setGravity(Gravity.CENTER) main_layout.setPadding( AndroidUtilities.dp(20), AndroidUtilities.dp(20), AndroidUtilities.dp(20), AndroidUtilities.dp(20), ) image_view = BackupImageView(context) image_view.setRoundRadius(AndroidUtilities.dp(45)) # The view is created here once. Do not bind item-specific data here yet. main_layout.addView(image_view, LayoutHelper.createLinear(130, 130, Gravity.CENTER, 0, 0, 0, 16)) return main_layout def bind_sticker_view(view, item, divider: bool, adapter, list_view): sticker_set_name = "CactusPlugins" sticker_index = 0 # This runs when the view should display actual content. MediaDataController.getInstance(UserConfig.selectedAccount).setPlaceholderImageByIndex( view.getChildAt(0), sticker_set_name, sticker_index, "130_130", ) StickerSettingFactory = SimpleSettingFactory( create_sticker_view, bind_sticker_view, is_clickable=False, is_shadow=False, ) ``` -------------------------------- ### Basic Usage Example Source: https://plugins.exteragram.app/docs/alert-dialog-builder Demonstrates how to create and display a simple message dialog with OK and Cancel buttons. ```APIDOC ## POST /api/example ### Description This endpoint demonstrates the basic usage of `AlertDialogBuilder` to show a simple message dialog. ### Method POST ### Endpoint /api/example ### Parameters #### Request Body - **message** (string) - Required - The message to display in the dialog. - **title** (string) - Optional - The title of the dialog. ### Request Example ```json { "message": "This is an important message from the plugin.", "title": "My Plugin Alert" } ``` ### Response #### Success Response (200) - **status** (string) - Indicates the success of the operation. #### Response Example ```json { "status": "Dialog displayed successfully" } ``` ``` -------------------------------- ### Quick Start: Creating a Java Subclass Source: https://plugins.exteragram.app/docs/class-proxy This example demonstrates how to create a Python class that extends a Java class (ArrayList) and overrides one of its methods. It shows the basic usage of `@java_subclass`, `jfield`, and `@joverload` to create a custom list that tracks the number of added items. ```python from extera_utils.classes import Base, java_subclass, joverload, jfield from java.util import ArrayList @java_subclass(ArrayList) class CountingList(Base): added_count = jfield("int", default=0) @joverload("add", ["java.lang.Object"]) def add_item(self, value): self.added_count += 1 return super().add_item(value) items = CountingList.new_instance() items.add("Hello") items.add("world") ``` -------------------------------- ### Full Smoke Test Example Source: https://plugins.exteragram.app/docs/class-proxy This example verifies the entire class proxy API by subclassing `java.util.ArrayList`. It demonstrates field creation, constructor preprocessing, method overrides, and custom Java methods. Ensure all necessary imports are present. ```python from base_plugin import BasePlugin from extera_utils.classes import ( Base, java_subclass, jfield, joverride, joverload, jmethod, jconstructor, jpreconstructor, ) from java.util import ArrayList @java_subclass(ArrayList) class DebugList(Base): add_calls = jfield("int", default=0) remove_calls = jfield("int", default=0) last_action = jfield("java.lang.String", default="created") @jpreconstructor(["int"]) def normalize_capacity(cls, capacity): # Ensure capacity is never negative before Java constructor runs. return [max(0, capacity)] def __init__(self, *args): # Python-only state. This is not a Java field. self.history = [] @jconstructor(["int"]) def init_with_capacity(self, capacity): self.last_action = f"capacity:{capacity}" self.history.append(f"ctor(capacity={capacity})") @joverride() def size(self): # Example of overriding a normal method and still delegating to parent. current = super().size() self.history.append(f"size() -> {current}") return current @joverload("add", ["java.lang.Object"]) def add_item(self, value): self.add_calls += 1 self.last_action = f"add:{value}" self.history.append(f"add(value={value})") return super().add_item(value) @joverload("add", ["int", "java.lang.Object"]) def add_at_index(self, index, value): self.add_calls += 1 self.last_action = f"add_at:{index}:{value}" self.history.append(f"add(index={index}, value={value})") return super().add_at_index(index, value) @joverload("remove", ["int"]) def remove_at_index(self, index): self.remove_calls += 1 self.last_action = f"remove_at:{index}" self.history.append(f"remove(index={index})") return super().remove_at_index(index) @joverload("remove", ["java.lang.Object"]) def remove_item(self, value): self.remove_calls += 1 self.last_action = f"remove:{value}" self.history.append(f"remove(value={value})") return super().remove_item(value) @jmethod("debugSummary", "java.lang.String", []) def debug_summary(self): return ( f"size={self.size()}, " f"add_calls={self.add_calls}, " f"remove_calls={self.remove_calls}, " f"last_action={self.last_action}" ) @jmethod("historyDump", "java.lang.String", []) def history_dump(self): return " | ".join(self.history) class ProxyDebugPlugin(BasePlugin): def on_plugin_load(self): debug_list = DebugList.new_instance(4) debug_list.add("one") debug_list.add("two") debug_list.add(1, "inserted") debug_list.remove("two") self.log(f"Class name: {debug_list.getClass().getName()}") self.log(f"Java size(): {debug_list.size()}") self.log(f"Python field add_calls: {debug_list.add_calls}") self.log(f"Python field remove_calls: {debug_list.remove_calls}") self.log(f"Java method debugSummary(): {debug_list.debugSummary()}") self.log(f"Java method historyDump(): {debug_list.historyDump()}") # Access raw Java instance from the Python peer if needed. self.log(f"Same object hash: {debug_list.java.hashCode()}") ``` -------------------------------- ### MethodHook Example Source: https://plugins.exteragram.app/docs/xposed-hooking Use MethodHook when you need to execute logic before and/or after a hooked method. Ensure 'base_plugin' is imported. ```python from base_plugin import MethodHook class TitleLoggerHook(MethodHook): def before_hooked_method(self, param): title = param.args[0] print(f"ActionBar title is about to become: {title}") def after_hooked_method(self, param): print(f"Method finished with result: {param.getResult()}") ``` -------------------------------- ### Plugin Settings Creation Example Source: https://plugins.exteragram.app/docs/plugin-settings Illustrates how to define a list of settings for a plugin, including various control types like Header, Switch, Selector, Input, EditText, Divider, Text, and Custom controls. Each setting can have associated callbacks for user interactions. ```python class MyPlugin(BasePlugin): def _on_test_switch_change(self, new_value: bool): self.log(f"Test switch changed to: {new_value}") def _on_test_input_change(self, new_value: str): self.log(f"Test input changed to: {new_value}") def _on_test_selector_change(self, new_index: int): self.log(f"Test selector changed to index: {new_index}") def _on_text_click(self, view: View): self.log("Text item clicked!") def _create_sub_page(self) -> List[Any]: return [ Header(text="This is a Sub-Page"), Text(text="You can nest settings pages."), ] def create_settings(self) -> List[Any]: return [ Header(text="General Settings"), Switch( key="test_switch_key", text="Test Switch", default=True, subtext="This is a sample switch control.", icon="msg_settings", on_change=self._on_test_switch_change, link_alias="test_switch", ), Selector( key="test_selector_key", text="Test Selector", default=1, items=["Option A", "Option B", "Option C"], icon="msg_list", on_change=self._on_test_selector_change, ), Input( key="test_input_key", text="Test Input", default="Hello, World!", subtext="A simple single-line text input.", icon="msg_text", on_change=self._on_test_input_change, ), EditText( key="multiline_key", hint="Enter multiple lines of text here...", default="", multiline=True, max_length=1000, ), Divider(text="This is a divider with text."), Text( text="Click for Sub-Page", icon="msg_arrow_forward", on_click=self._on_text_click, create_sub_fragment=self._create_sub_page, link_alias="sub_page_link", ), Text( text="This is red text", icon="msg_error", red=True, ), Divider(), Header(text="Custom Controls"), Custom(factory=StickerSettingFactory.instance.java), Custom(item=UItem.asShadow("This is a custom UItem shadow.")), ] ``` -------------------------------- ### Modify Arguments Before Method Call (Toast Example) Source: https://plugins.exteragram.app/docs/xposed-hooking This example demonstrates modifying the arguments of the `makeText` method in `android.widget.Toast` before it is called. It prepends `(Plugin) ` to the original text. ```python from base_plugin import MethodHook from hook_utils import find_class from java import jint class ToastHook(MethodHook): def before_hooked_method(self, param): # Method signature: makeText(Context context, CharSequence text, int duration) original_text = param.args[1] param.args[1] = f"(Plugin) {original_text}" ToastClass = find_class("android.widget.Toast") ContextClass = find_class("android.content.Context") CharSequenceClass = find_class("java.lang.CharSequence") make_text_method = ToastClass.getDeclaredMethod("makeText", ContextClass, CharSequenceClass, jint) self.hook_method(make_text_method, ToastHook()) ``` -------------------------------- ### Create and Show Spinner Loading Dialog Source: https://plugins.exteragram.app/docs/alert-dialog-builder Example of creating a dialog with an indeterminate spinner, suitable for indicating ongoing loading processes. It's recommended to set cancelable behavior after creation or showing the dialog. ```python # Loading dialog example loading_builder = AlertDialogBuilder(activity, AlertDialogBuilder.ALERT_TYPE_SPINNER) loading_builder.set_title("Loading Data...") loading_builder.set_message("Please wait while data is being fetched.") loading_builder.show() loading_builder.set_cancelable(False) # Best called after create() or show() # Later, when loading is done: # loading_builder.dismiss() ``` -------------------------------- ### Full Example of Java Subclass with Chaquopy Decorators Source: https://plugins.exteragram.app/docs/class-proxy Illustrates creating a Java subclass in Python using `java_subclass` and defining fields, constructors, and methods with Chaquopy decorators like `jfield`, `jpreconstructor`, `jconstructor`, `joverride`, `joverload`, and `jmethod`. This example shows how to manage Python state and interact with Java methods. ```python from extera_utils.classes import ( Base, java_subclass, joverride, joverload, jmethod, jfield, jconstructor, jpreconstructor, ) @java_subclass(SomeJavaClass) class DemoProxy(Base): counter = jfield("int", default=0) title = jfield("java.lang.String", default="Demo") @jpreconstructor(["java.lang.String", "int"]) def normalize_args(cls, title, counter): return [title.strip(), max(0, counter)] def __init__(self, title, counter): self.python_state = {"created_with": title} @jconstructor(["java.lang.String", "int"]) def init_fields(self, title, counter): self.title = title self.counter = counter @joverride() def onAttached(self): self.counter += 1 return super().onAttached() @joverload("setValue", ["int"]) def set_value_int(self, value): self.counter = value return super().set_value_int(value) @joverload("setValue", ["java.lang.String"]) def set_value_text(self, value): self.title = value @jmethod("debugLabel", "java.lang.String", ["int"]) def build_debug_label(self, extra): return f"{self.title} ({self.counter}, extra={extra})" instance = DemoProxy.new_instance(" Example ", 5) java_instance = instance.java ``` -------------------------------- ### Functional Hook Example Source: https://plugins.exteragram.app/docs/xposed-hooking For simpler cases, use functional hooks with 'before=' and 'after=' arguments directly. The SDK wraps this into BaseHook internally. ```python self.hook_method( some_method, before=lambda param: self.log("Method is about to run!"), after=lambda param: self.log(f"Method finished with result: {param.getResult()}"), ) ``` -------------------------------- ### Run Task on Specific Queue with Delay Source: https://plugins.exteragram.app/docs/client-utils Execute tasks on a specific queue with an optional delay. This example uses GLOBAL_QUEUE with a 2.5-second delay. ```python from client_utils import GLOBAL_QUEUE, run_on_queue # Run on GLOBAL_QUEUE after a 2.5 second delay. run_on_queue(lambda: my_long_task("other_data"), GLOBAL_QUEUE, 2500) ``` -------------------------------- ### MethodReplacement Example Source: https://plugins.exteragram.app/docs/xposed-hooking Use MethodReplacement to completely replace the original Java method's implementation. Requires 'base_plugin' import. ```python from base_plugin import MethodReplacement class TitleReplacer(MethodReplacement): def replace_hooked_method(self, param): print("Original method was replaced.") return None ``` -------------------------------- ### Create a Custom Setting Row with SimpleSettingFactory Source: https://plugins.exteragram.app/docs/plugin-settings Use SimpleSettingFactory to define the view creation, data binding, and click handling for a custom setting row. This example demonstrates building an interactive row. ```python from android.view import Gravity from android.widget import LinearLayout, TextView from org.telegram.messenger import AndroidUtilities from org.telegram.ui.Components import LayoutHelper from ui.bulletin import BulletinHelper from ui.settings import Custom, SimpleSettingFactory def create_view(context, list_view, current_account: int, class_guid: int, resources_provider): main_layout = LinearLayout(context) main_layout.setOrientation(LinearLayout.VERTICAL) main_layout.setGravity(Gravity.CENTER) main_layout.setPadding( AndroidUtilities.dp(20), AndroidUtilities.dp(20), AndroidUtilities.dp(20), AndroidUtilities.dp(20), ) # Build the row skeleton once. title = TextView(context) title.setText("Tap me") main_layout.addView(title, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT)) return main_layout def bind_view(view, item, divider: bool, adapter, list_view): # Bind current data here. title = view.getChildAt(0) title.setText("Tap me") def on_click(plugin, item, view): BulletinHelper.show_info(f"Hello from {plugin.getName()}!") InteractiveRowFactory = SimpleSettingFactory( create_view, bind_view, is_clickable=True, on_click=on_click, ) setting = Custom(factory=InteractiveRowFactory.instance.java) ``` -------------------------------- ### Settings Storage Helpers Source: https://plugins.exteragram.app/docs/plugin-class Utilize the built-in `BasePlugin` helpers for persistent plugin settings, including getting, setting, exporting, and importing settings. `reload_settings` parameter controls immediate UI updates. ```python current_value = self.get_setting("enabled", True) self.set_setting("enabled", not current_value, reload_settings=True) backup = self.export_settings() self.import_settings(backup, reload_settings=False) ``` -------------------------------- ### Exact Signature Dispatch Example Source: https://plugins.exteragram.app/docs/class-proxy The proxy system uses a full signature key for Java to Python calls, enabling correct handling of overloaded methods and constructors. Ensure argument types exactly match the Java signature, using primitive type names like \"int\" instead of wrapper classes. ```text methodName:int:java.lang.String ``` -------------------------------- ### Run Task on Default Queue Source: https://plugins.exteragram.app/docs/client-utils Use `run_on_queue` to execute tasks off the UI thread. This example runs a long task on the default PLUGINS_QUEUE. ```python import time from android_utils import log from client_utils import run_on_queue def my_long_task(parameter: str): log(f"Task started with: {parameter}") time.sleep(5) # Simulate a long operation. log(f"Task finished for: {parameter}") # Run on the default PLUGINS_QUEUE. run_on_queue(lambda: my_long_task("some_data")) ``` -------------------------------- ### Implementing Pre- and Post-Request Hooks Source: https://plugins.exteragram.app/docs/plugin-class Implement `pre_request_hook` to intercept and potentially modify or cancel incoming requests, and `post_request_hook` to react to the outcome of outgoing requests. This example blocks typing requests and forces an offline status. ```python from typing import Any, List from base_plugin import BasePlugin, HookResult, HookStrategy from ui.settings import Switch TYPING_REQUESTS = ["TL_messages_setTyping", "TL_messages_setEncryptedTyping"] class GhostModePlugin(BasePlugin): def on_plugin_load(self): for req_name in TYPING_REQUESTS: self.add_hook(req_name) self.add_hook("TL_account_updateStatus") def pre_request_hook(self, request_name: str, account: int, request: Any) -> HookResult: if request_name in TYPING_REQUESTS and self.get_setting("dont_send_typing", True): self.log(f"Blocking request: {request_name}") return HookResult(strategy=HookStrategy.CANCEL) if request_name == "TL_account_updateStatus" and self.get_setting("force_offline", True): request.offline = True return HookResult(strategy=HookStrategy.MODIFY, request=request) return HookResult() def post_request_hook(self, request_name: str, account: int, response: Any, error: Any) -> HookResult: if request_name == "TL_messages_sendMessage" and not error: self.log("Successfully sent a message!") return HookResult() def create_settings(self) -> List[Any]: return [ Switch(key="dont_send_typing", text="Don't send typing status", default=True), Switch(key="force_offline", text="Always appear offline", default=True), ] ``` -------------------------------- ### Customizing Message Templates Source: https://plugins.exteragram.app/docs/first-plugin This example shows how to use a custom template for message responses, allowing dynamic insertion of parameters like the user's name. The template is defined in the plugin settings. ```python def on_send_message(self, message: str, params: SendMessageParams) -> HookResult: if message == ".hello Alice": return HookResult(message="Welcome back, Alice.") return HookResult(message=message) ``` -------------------------------- ### Handle Application Events Source: https://plugins.exteragram.app/docs/plugin-class Respond to application lifecycle events such as start, stop, pause, and resume by implementing the `on_app_event` method. Use the `AppEvent` enum to differentiate event types. ```python from base_plugin import AppEvent, BasePlugin class DebugPlugin(BasePlugin): def on_app_event(self, event_type: AppEvent): if event_type == AppEvent.START: self.log("App is starting") elif event_type == AppEvent.STOP: self.log("App is stopping") elif event_type == AppEvent.PAUSE: self.log("App moved to background") elif event_type == AppEvent.RESUME: self.log("App returned to foreground") ``` -------------------------------- ### Declare Plugin Dependencies Source: https://plugins.exteragram.app/docs/pip List external libraries required by your plugin in the `__requirements__` variable. This should be a list of strings following the standard PEP 508 requirement format. exteraGram will automatically download these libraries when the plugin is installed. ```python __id__ = "inline_calculator" __name__ = "Inline Calculator" __requirements__ = ["mpmath", "tinydb"] from base_plugin import BasePlugin, HookResult, HookStrategy from mpmath import mp from tinydb import TinyDB class CalculatorPlugin(BasePlugin): ... ``` -------------------------------- ### Intercepting Outgoing Messages Source: https://plugins.exteragram.app/docs/plugin-class Implement `on_send_message_hook` to intercept and modify outgoing messages before they are sent. This example checks if a message starts with ".hello" and replaces it with a greeting. ```python def on_send_message_hook(self, account: int, params: Any) -> HookResult: result = HookResult() if hasattr(params, "message") and isinstance(params.message, str): if params.message.startswith(".hello"): params.message = params.message.replace(".hello", "Hello from plugin") result.strategy = HookStrategy.MODIFY result.params = params return result ``` -------------------------------- ### Minimal Starter Plugin File Source: https://plugins.exteragram.app/docs/setup This is a basic Python file for a new ExteraGram plugin. It includes essential metadata and a placeholder class that extends BasePlugin. Ensure the `__id__` matches your project's filename for proper loading. ```python from base_plugin import BasePlugin __id__ = "hello_world" __name__ = "Hello World" __description__ = "My first exteraGram plugin" __author__ = "Your Name" __version__ = "1.0.0" __icon__ = "exteraPlugins/1" __app_version__ = ">=12.5.1" __sdk_version__ = ">=1.4.3.6" class HelloWorldPlugin(BasePlugin): pass ``` -------------------------------- ### Class-based HookFilter Example Source: https://plugins.exteragram.app/docs/xposed-hooking Apply filters to class-based hooks using the '@hook_filters' decorator. This example shows filtering based on the first argument being null and the result not being null. ```python from base_plugin import HookFilter, MethodHook, hook_filters class ExampleHook(MethodHook): # Run only if the first argument is null. @hook_filters(HookFilter.ArgumentIsNull(0)) def before_hooked_method(self, param): ... # Run only if the original result is not null. @hook_filters(HookFilter.RESULT_NOT_NULL) def after_hooked_method(self, param): ... ``` -------------------------------- ### Modify Return Value After Method Call (BuildVars Example) Source: https://plugins.exteragram.app/docs/xposed-hooking This example shows how to intercept the return value of a method after it has executed. The `after_hooked_method` logs the original result and then sets a new result using `param.setResult(False)`. ```python from base_plugin import MethodHook from hook_utils import find_class class BuildVarsHook(MethodHook): def __init__(self, plugin): self.plugin = plugin def after_hooked_method(self, param): original_result = param.getResult() self.plugin.log(f"Original result: {original_result}") param.setResult(False) BuildVarsClass = find_class("org.telegram.messenger.BuildVars") is_main_app_method = BuildVarsClass.getDeclaredMethod("isMainApp") self.hook_method(is_main_app_method, BuildVarsHook(self)) ``` -------------------------------- ### Implement Settings Screen Source: https://plugins.exteragram.app/docs/plugin-class To expose a settings screen, implement the `create_settings` method. This method should return a list of UI elements for the settings. ```python from typing import Any, List from base_plugin import BasePlugin from ui.settings import Header, Switch class MyPlugin(BasePlugin): def create_settings(self) -> List[Any]: return [ Header(text="General"), Switch(key="enabled", text="Enable feature", default=True), ] ``` -------------------------------- ### Implement Plugin Settings Source: https://plugins.exteragram.app/docs/plugin-settings Implement the `create_settings` method on your `BasePlugin` to return a list of dataclass instances from `ui.settings` for your plugin's settings screen. ```python def create_settings(self) -> List[Any]: return [ # Dataclass instances from ui.settings ] ``` -------------------------------- ### Basic Message Handling in ExteraGram Source: https://plugins.exteragram.app/docs/first-plugin This snippet demonstrates how to respond to a specific command like ".hello" with a personalized message. Ensure the hook is properly registered in `on_plugin_load`. ```python def on_send_message(self, message: str, params: SendMessageParams) -> HookResult: if message == ".hello Alice": return HookResult(message="Hello, Alice!") return HookResult(message=message) ``` -------------------------------- ### SimpleSettingFactory Constructor Options Source: https://plugins.exteragram.app/docs/plugin-settings Illustrates the available options for the SimpleSettingFactory constructor, including callbacks for view creation, binding, item creation, click handling, and equality checks. ```python SimpleSettingFactory( create_view, bind_view, is_clickable=False, is_shadow=False, create_item=None, on_click=None, on_long_click=None, attached_view=None, equals=None, content_equals=None, ) ``` -------------------------------- ### Create and Show Basic Alert Dialog Source: https://plugins.exteragram.app/docs/alert-dialog-builder Demonstrates how to create a simple message dialog with OK and Cancel buttons. Ensure you have a valid activity context and handle cases where the fragment or activity might not be available. ```python from ui.alert import AlertDialogBuilder from client_utils import get_last_fragment from android_utils import log # Get current activity (context) current_fragment = get_last_fragment() if not current_fragment: log("Cannot show dialog, no current fragment.") # return or handle error activity = current_fragment.getParentActivity() if not activity: log("Cannot show dialog, no parent activity.") # return or handle error # Create a simple message dialog builder = AlertDialogBuilder(activity) # Default is ALERT_TYPE_MESSAGE builder.set_title("My Plugin Alert") builder.set_message("This is an important message from the plugin.") # Add buttons def on_positive_click(bld: AlertDialogBuilder, which: int): log("Positive button clicked!") bld.dismiss() def on_negative_click(bld: AlertDialogBuilder, which: int): log("Negative button clicked!") bld.dismiss() builder.set_positive_button("OK", on_positive_click) builder.set_negative_button("Cancel", on_negative_click) # create() is optional, but useful if you want to tweak flags before show() builder.create() builder.set_cancelable(True) builder.show() ``` -------------------------------- ### Test Plugin Implementation Source: https://plugins.exteragram.app/docs/class-proxy A sample plugin that loads and creates custom settings using the `PluginCellSettingFactory`. It handles plugin loading and setting creation. ```python class TestPlugin(BasePlugin): def on_plugin_load(self): # plugin_cell_setting_factory_instance self.pcsfi = PluginCellSettingFactory.new_java_instance() UItem.UItemFactory.setup(self.pcsfi) def create_settings(self): plugins = PluginsController.getInstance().plugins.values().toArray() if not plugins: return [ Text("Restart plugin please, no plugins found") ] return [ Custom( factory=self.pcsfi, factory_args=_p ) for _p in plugins ] ``` -------------------------------- ### Removing Menu Items Source: https://plugins.exteragram.app/docs/plugin-class Provides an example of how to manually remove a previously added menu item using its `item_id`. ```APIDOC ## DELETE /websites/plugins_exteragram_app/menu_items/{item_id} ### Description This endpoint allows for the manual removal of a specific menu item that was previously added by a plugin. Menu items are typically removed automatically when the plugin unloads, but this provides explicit control. ### Method DELETE ### Endpoint /websites/plugins_exteragram_app/menu_items/{item_id} ### Parameters #### Path Parameters - **item_id** (str) - Required - The unique identifier of the menu item to remove. ### Request Example ```bash DELETE /websites/plugins_exteragram_app/menu_items/my_custom_log_item_id ``` ### Response #### Success Response (200) - **message** (str) - Confirmation message indicating the item was removed. ``` -------------------------------- ### Show Basic Bulletins Source: https://plugins.exteragram.app/docs/bulletin-helper Demonstrates how to display simple informational, error, and success bulletins. Imports include BulletinHelper and an optional fragment utility. ```python from ui.bulletin import BulletinHelper from client_utils import get_last_fragment # Optional, for explicit fragment passing from org.telegram.messenger import R as R_tg # For Telegram's R.raw Lottie animations # Get current fragment (optional) current_fragment = get_last_fragment() # Show a simple informational bulletin BulletinHelper.show_info("This is some information.", current_fragment) # Show an error bulletin BulletinHelper.show_error("An error occurred processing your request.", current_fragment) # Show a success bulletin BulletinHelper.show_success("Action completed successfully!", current_fragment) ``` -------------------------------- ### HookFilter ArgumentIsNull Example Source: https://plugins.exteragram.app/docs/xposed-hooking Use 'before_filters' with HookFilter.ArgumentIsNull to execute a hook only when a specific argument is null. Requires 'base_plugin' and 'HookFilter' imports. ```python from base_plugin import HookFilter self.hook_method( some_method, before=lambda p: self.log("Arg 0 is null!"), before_filters=[HookFilter.ArgumentIsNull(0)], ) ``` -------------------------------- ### Get Generated Java Class Source: https://plugins.exteragram.app/docs/class-proxy Retrieve the generated Java class object associated with a Python subclass using the .java_class() method. ```python proxy_java_class = MySubclass.java_class() ``` -------------------------------- ### Generic Initialization with __init__ and on_post_init Source: https://plugins.exteragram.app/docs/class-proxy Use Python's `__init__` for general initialization and `on_post_init` for logic after the Java object is ready. The initialization order is `@jpreconstructor`, Java parent constructor, Python `__init__`, `@jconstructor`, and `on_post_init`. ```python @java_subclass(SomeJavaClass) class MySubclass(Base): def __init__(self, text): self.python_only_state = text def on_post_init(self, text): print("Java object is ready:", self.java) ``` -------------------------------- ### Practical J(...) Helper Usage Source: https://plugins.exteragram.app/docs/class-proxy Demonstrates practical usage of the J(...) helper for accessing Java methods and fields, including handling non-public members and chaining method calls. Requires `android_utils` and `extera_utils.classes` imports. ```python from android_utils import log from extera_utils.classes import J def debug_view(view): helper = J(view) # Use getter if present. log(f"class = {helper.getClass().getName()}") # Reach non-public members only through the explicit access-all wrapper. try: hidden_tag = helper.secretTag except AttributeError: hidden_tag = helper.JAccessAll.secretTag log(f"hidden_tag = {hidden_tag}") # Switch to chain mode for void-style mutators. helper.JIgnoreResult.requestLayout().invalidate() ``` -------------------------------- ### Ensure Directory Exists Source: https://plugins.exteragram.app/docs/file-utils Create a directory, including any necessary parent directories, if it does not already exist. Pair with `write_file` if creating a directory for a file. ```python from file_utils import ensure_dir_exists, get_plugins_dir import os # Ensure a dedicated data directory for your plugin exists my_plugin_data_dir = os.path.join(get_plugins_dir(), "my_plugin_data") ensure_dir_exists(my_plugin_data_dir) ``` -------------------------------- ### Get Standard Telegram Directories Source: https://plugins.exteragram.app/docs/file-utils Access standard Telegram directories for storing plugin data, cache, images, videos, audios, and documents. ```python from file_utils import ( get_plugins_dir, get_cache_dir, get_files_dir, get_images_dir, get_videos_dir, get_audios_dir, get_documents_dir ) # Get the path to the directory where plugins are stored plugins_path = get_plugins_dir() # Get the path to Telegram's main cache directory cache_path = get_cache_dir() # Get paths to media-specific directories files_path = get_files_dir() images_path = get_images_dir() videos_path = get_videos_dir() audios_path = get_audios_dir() documents_path = get_documents_dir() ``` -------------------------------- ### BulletinHelper - Basic Usage Source: https://plugins.exteragram.app/docs/bulletin-helper Demonstrates how to show simple informational, error, and success bulletins using the BulletinHelper class. ```APIDOC ## BulletinHelper - Basic Usage This section covers the basic methods for displaying standard notification bulletins. ### Methods - `BulletinHelper.show_info(message: str, fragment: Optional[BaseFragment] = None)`: Shows a bulletin with a default info icon. - `BulletinHelper.show_error(message: str, fragment: Optional[BaseFragment] = None)`: Shows a bulletin with a default error icon. - `BulletinHelper.show_success(message: str, fragment: Optional[BaseFragment] = None)`: Shows a bulletin with a default success icon. All `show_...` methods automatically handle UI thread execution. ### Request Example ```python from ui.bulletin import BulletinHelper from client_utils import get_last_fragment current_fragment = get_last_fragment() # Optional: get current fragment BulletinHelper.show_info("This is some information.", current_fragment) BulletinHelper.show_error("An error occurred processing your request.", current_fragment) BulletinHelper.show_success("Action completed successfully!", current_fragment) ``` ``` -------------------------------- ### Create Custom Setting with UItem Source: https://plugins.exteragram.app/docs/plugin-settings Use the `Custom` setting type with an existing `UItem` for lightweight custom settings rows. ```python from org.telegram.ui.Components import UItem from ui.settings import Custom setting = Custom( item=UItem.asShadow("This is a custom UItem shadow.") ) ``` -------------------------------- ### Get Raw DispatchQueue by Name Source: https://plugins.exteragram.app/docs/client-utils Retrieve the raw Java `DispatchQueue` object by its name using `get_queue_by_name`. This allows direct interaction with DispatchQueue methods. ```python from client_utils import PLUGINS_QUEUE, get_queue_by_name plugins_dispatch_queue = get_queue_by_name(PLUGINS_QUEUE) if plugins_dispatch_queue: # You can call DispatchQueue methods directly here. pass ``` -------------------------------- ### Get Static Private Field Value Source: https://plugins.exteragram.app/docs/hook-utils Retrieves the value of a static private or public field from a Java class. Returns None if the field is not found. ```python from hook_utils import find_class, get_static_private_field # Get the static 'configLoaded' field from ExteraConfig ExteraConfigClass = find_class("com.exteragram.messenger.ExteraConfig") if ExteraConfigClass: config_loaded = get_static_private_field(ExteraConfigClass, "configLoaded") self.log(f"Config loaded: {config_loaded}") ``` -------------------------------- ### Send Telegram API Request Source: https://plugins.exteragram.app/docs/client-utils Send a raw Telegram `TLObject` request using `send_request`. This example sends a `TL_messages_readMessageContents` request and handles the response or error. ```python from java.lang import Integer from org.telegram.tgnet import TLRPC from android_utils import log from client_utils import send_request def handle_read_contents_response(response, error): if error: log(f"Error reading message contents: {error.text}") return if response and isinstance(response, TLRPC.TL_messages_affectedMessages): log(f"Successfully read contents. PTS: {response.pts}, Count: {response.pts_count}") else: log(f"Unexpected response type: {type(response)}") # Create the request object. req = TLRPC.TL_messages_readMessageContents() req.id.add(Integer(12345)) # Send the request. The return value is the ConnectionsManager request id. request_id = send_request(req, handle_read_contents_response) log(f"Sent TL_messages_readMessageContents, request ID: {request_id}") ``` -------------------------------- ### Using SimpleSettingFactory as a Callable Builder Source: https://plugins.exteragram.app/docs/plugin-settings Demonstrates how SimpleSettingFactory can be used directly as a callable to create settings with specific arguments, such as a link alias. ```python StickerFactory = SimpleSettingFactory(create_sticker_view, bind_sticker_view) setting = StickerFactory(link_alias="sticker_preview") ``` -------------------------------- ### Get Private Instance Field Value Source: https://plugins.exteragram.app/docs/hook-utils Retrieves the value of a private or public instance field from a Java object, searching the class hierarchy. Returns None if the field is not found. ```python from hook_utils import get_private_field # Get the value of the private 'chatListView' field from a ChatActivity instance chat_list_view = get_private_field(chatActivity, "chatListView") if chat_list_view: self.log("Successfully accessed chatListView.") ``` -------------------------------- ### Adding Menu Items Source: https://plugins.exteragram.app/docs/plugin-class Demonstrates how to add menu items to the message context menu and profile action menu using the `add_menu_item` method and `MenuItemData`. ```APIDOC ## POST /websites/plugins_exteragram_app/menu_items ### Description This endpoint allows plugins to add custom menu items to various application menus. Menu items can be configured with text, icons, click handlers, and other properties. ### Method POST ### Endpoint /websites/plugins_exteragram_app/menu_items ### Parameters #### Request Body - **menu_type** (MenuItemType) - Required - The target menu for the item. - **text** (str) - Required - The visible text for the menu item. - **on_click** (Callable[[Dict[str, Any]], None]) - Required - The function to call when the item is clicked. - **item_id** (Optional[str]) - Optional - A stable ID for the menu item, useful for removal. - **icon** (Optional[str]) - Optional - The name of the drawable icon to display. - **subtext** (Optional[str]) - Optional - Secondary text displayed below the main title. - **condition** (Optional[str]) - Optional - An MVEL expression to control item visibility. - **priority** (int) - Optional - Determines the order of the item; larger values usually appear earlier. ### Request Example ```json { "menu_type": "MESSAGE_CONTEXT_MENU", "text": "Log Message Info", "on_click": "handle_message_click", "icon": "msg_info", "subtext": "Debug helper", "priority": 10 } ``` ### Response #### Success Response (200) - **item_id** (str) - The unique identifier for the added menu item. ``` -------------------------------- ### Display Info Bulletin Source: https://plugins.exteragram.app/docs/client-utils Use `BulletinHelper.show_info` to display informational messages as bulletins or bottom notifications. ```python from ui.bulletin import BulletinHelper BulletinHelper.show_info("This is an informational message.") ``` -------------------------------- ### Registering Event Hooks in a Plugin Source: https://plugins.exteragram.app/docs/plugin-class Use `self.add_hook()` and `self.add_on_send_message_hook()` within `on_plugin_load` to register event hooks. This example shows how to register hooks for typing indicators and account status updates. ```python class GhostModePlugin(BasePlugin): def on_plugin_load(self): self.add_hook("TL_messages_setTyping") self.add_hook("TL_messages_setEncryptedTyping") self.add_hook("TL_account_updateStatus") self.add_on_send_message_hook() ``` -------------------------------- ### MVEL Condition Filter Example Source: https://plugins.exteragram.app/docs/xposed-hooking Use HookFilter.Condition with an MVEL expression for complex filtering logic. The 'object' parameter can be accessed within the MVEL string. Requires 'base_plugin', 'MethodHook', and 'hook_filters' imports. ```python from base_plugin import HookFilter, MethodHook, hook_filters class ConditionalHook(MethodHook): @hook_filters( HookFilter.Condition( "param.args[0] == object || this instanceof android.view.View", object=500, ) ) def before_hooked_method(self, param): ... ``` -------------------------------- ### Handling Update Hooks Source: https://plugins.exteragram.app/docs/plugin-class Implement `on_update_hook` to intercept specific Telegram updates by name, and `on_updates_hook` to process containers of updates. This example logs when a new message update or an updates container is received. ```python def on_update_hook(self, update_name: str, account: int, update: Any) -> HookResult: result = HookResult() if update_name == "TL_updateNewMessage": self.log(f"Intercepted update: {update_name}") return result def on_updates_hook(self, container_name: str, account: int, updates: Any) -> HookResult: result = HookResult() if container_name == "TL_updates": self.log(f"Received updates container: {container_name}") return result ``` -------------------------------- ### Initializing Java Helper Source: https://plugins.exteragram.app/docs/class-proxy Wrap a Java object with `J(...)` to create a helper instance. This provides a Python-friendly interface for interacting with Java members. ```python from extera_utils.classes import J # or JavaHelper helper = J(some_java_object) ``` -------------------------------- ### Minimal Plugin Structure Source: https://plugins.exteragram.app/docs/first-plugin Every plugin file must include metadata as top-level constants and a class inheriting from BasePlugin. ```python from base_plugin import BasePlugin __id__ = "hello_world" __name__ = "Hello World" __description__ = "My first exteraGram plugin" __author__ = "Your Name" __version__ = "1.0.0" __icon__ = "exteraPlugins/1" __app_version__ = ">=12.5.1" __sdk_version__ = ">=1.4.3.6" class HelloWorldPlugin(BasePlugin): pass ```