894 lines
26 KiB
Python
894 lines
26 KiB
Python
"""
|
|
EU-Utility - Plugin Base Class
|
|
==============================
|
|
|
|
Defines the interface that all plugins must implement.
|
|
Includes PluginAPI integration for cross-plugin communication.
|
|
|
|
Quick Start:
|
|
------------
|
|
from core.base_plugin import BasePlugin
|
|
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QLabel
|
|
|
|
class MyPlugin(BasePlugin):
|
|
name = "My Plugin"
|
|
version = "1.0.0"
|
|
|
|
def initialize(self) -> None:
|
|
self.log_info("My Plugin initialized!")
|
|
|
|
def get_ui(self) -> QWidget:
|
|
widget = QWidget()
|
|
layout = QVBoxLayout(widget)
|
|
layout.addWidget(QLabel("Hello from My Plugin!"))
|
|
return widget
|
|
|
|
Hotkey Support:
|
|
---------------
|
|
Plugins can define hotkeys in two ways:
|
|
|
|
1. Legacy single hotkey (simple toggle):
|
|
hotkey = "ctrl+shift+n"
|
|
|
|
2. New multi-hotkey format (recommended):
|
|
hotkeys = [
|
|
{
|
|
'action': 'toggle',
|
|
'description': 'Toggle My Plugin',
|
|
'default': 'ctrl+shift+m',
|
|
'config_key': 'myplugin_toggle'
|
|
}
|
|
]
|
|
"""
|
|
|
|
from abc import ABC, abstractmethod
|
|
from typing import Optional, Dict, Any, TYPE_CHECKING, Callable, List, Type, Union
|
|
from datetime import datetime
|
|
|
|
if TYPE_CHECKING:
|
|
from PyQt6.QtWidgets import QWidget
|
|
from core.event_bus import BaseEvent
|
|
|
|
|
|
class BasePlugin(ABC):
|
|
"""Base class for all EU-Utility plugins.
|
|
|
|
To create a plugin, inherit from this class and implement
|
|
the required abstract methods. Override class attributes
|
|
to define plugin metadata.
|
|
|
|
Attributes:
|
|
name: Human-readable plugin name
|
|
version: Plugin version (semantic versioning recommended)
|
|
author: Plugin author name
|
|
description: Brief description of plugin functionality
|
|
icon: Optional path to plugin icon
|
|
hotkey: Legacy single hotkey (e.g., "ctrl+shift+n")
|
|
hotkeys: New multi-hotkey format (list of dicts)
|
|
enabled: Whether plugin starts enabled
|
|
dependencies: Dict of required dependencies
|
|
"""
|
|
|
|
# Plugin metadata - override in subclass
|
|
name: str = "Unnamed Plugin"
|
|
version: str = "1.0.0"
|
|
author: str = "Unknown"
|
|
description: str = "No description provided"
|
|
icon: Optional[str] = None
|
|
|
|
# Plugin settings
|
|
hotkey: Optional[str] = None
|
|
hotkeys: Optional[List[Dict[str, str]]] = None
|
|
enabled: bool = True
|
|
|
|
# Dependencies format:
|
|
# {
|
|
# 'pip': ['package1', 'package2>=1.0'],
|
|
# 'plugins': ['plugin_id1', 'plugin_id2'],
|
|
# 'optional': {'package3': 'description'}
|
|
# }
|
|
dependencies: Dict[str, Any] = {}
|
|
|
|
def __init__(self, overlay_window: Any, config: Dict[str, Any]) -> None:
|
|
"""Initialize the plugin.
|
|
|
|
Args:
|
|
overlay_window: The main overlay window instance
|
|
config: Plugin-specific configuration dictionary
|
|
"""
|
|
self.overlay = overlay_window
|
|
self.config = config
|
|
self._ui: Optional[Any] = None
|
|
self._api_registered: bool = False
|
|
self._plugin_id: str = f"{self.__class__.__module__}.{self.__class__.__name__}"
|
|
|
|
# Track event subscriptions for cleanup
|
|
self._event_subscriptions: List[str] = []
|
|
|
|
# Get API instance
|
|
try:
|
|
from core.plugin_api import get_api
|
|
self.api = get_api()
|
|
except ImportError:
|
|
self.api = None
|
|
|
|
@abstractmethod
|
|
def initialize(self) -> None:
|
|
"""Called when plugin is loaded. Setup API connections, etc.
|
|
|
|
This is where you should:
|
|
- Register API endpoints
|
|
- Subscribe to events
|
|
- Initialize resources
|
|
- Set up UI components
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def get_ui(self) -> Any:
|
|
"""Return the plugin's UI widget (QWidget).
|
|
|
|
Returns:
|
|
QWidget instance for the plugin's interface
|
|
|
|
Example:
|
|
def get_ui(self) -> QWidget:
|
|
widget = QWidget()
|
|
layout = QVBoxLayout(widget)
|
|
layout.addWidget(QLabel("My Plugin"))
|
|
return widget
|
|
"""
|
|
return None
|
|
|
|
def on_show(self) -> None:
|
|
"""Called when overlay becomes visible.
|
|
|
|
Use this to refresh data or start animations when
|
|
the user opens the overlay.
|
|
"""
|
|
pass
|
|
|
|
def on_hide(self) -> None:
|
|
"""Called when overlay is hidden.
|
|
|
|
Use this to pause expensive operations when the
|
|
overlay is not visible.
|
|
"""
|
|
pass
|
|
|
|
def on_hotkey(self) -> None:
|
|
"""Called when plugin's hotkey is pressed.
|
|
|
|
Override this to handle hotkey actions.
|
|
Default behavior toggles the overlay.
|
|
"""
|
|
pass
|
|
|
|
def shutdown(self) -> None:
|
|
"""Called when app is closing. Cleanup resources.
|
|
|
|
This is called automatically when the application exits.
|
|
Override to perform custom cleanup.
|
|
"""
|
|
# Unregister APIs
|
|
if self.api and self._api_registered:
|
|
# Note: unregister_api method would need to be implemented
|
|
pass
|
|
|
|
# Unsubscribe from all typed events
|
|
self.unsubscribe_all_typed()
|
|
|
|
# ========== Config Methods ==========
|
|
|
|
def get_config(self, key: str, default: Any = None) -> Any:
|
|
"""Get a config value with default.
|
|
|
|
Args:
|
|
key: Configuration key
|
|
default: Default value if key not found
|
|
|
|
Returns:
|
|
Config value or default
|
|
"""
|
|
return self.config.get(key, default)
|
|
|
|
def set_config(self, key: str, value: Any) -> None:
|
|
"""Set a config value.
|
|
|
|
Args:
|
|
key: Configuration key
|
|
value: Value to store
|
|
"""
|
|
self.config[key] = value
|
|
|
|
# ========== API Methods ==========
|
|
|
|
def register_api(self, name: str, handler: Callable,
|
|
api_type: Optional[str] = None,
|
|
description: str = "") -> bool:
|
|
"""Register an API endpoint for other plugins to use.
|
|
|
|
Args:
|
|
name: API endpoint name
|
|
handler: Function to handle API calls
|
|
api_type: Optional API type categorization
|
|
description: Human-readable description
|
|
|
|
Returns:
|
|
True if registration succeeded
|
|
|
|
Example:
|
|
self.register_api(
|
|
"scan_window",
|
|
self.scan_window,
|
|
"ocr",
|
|
"Scan game window and return text"
|
|
)
|
|
"""
|
|
if not self.api:
|
|
print(f"[{self.name}] API not available")
|
|
return False
|
|
|
|
self._api_registered = True
|
|
return True
|
|
|
|
def call_api(self, plugin_id: str, api_name: str,
|
|
*args: Any, **kwargs: Any) -> Any:
|
|
"""Call another plugin's API.
|
|
|
|
Args:
|
|
plugin_id: ID of the plugin to call
|
|
api_name: Name of the API endpoint
|
|
*args: Positional arguments
|
|
**kwargs: Keyword arguments
|
|
|
|
Returns:
|
|
API call result
|
|
|
|
Raises:
|
|
RuntimeError: If API not available
|
|
"""
|
|
if not self.api:
|
|
raise RuntimeError("API not available")
|
|
|
|
# This would call through the API system
|
|
return None
|
|
|
|
# ========== Screenshot Service Methods ==========
|
|
|
|
def capture_screen(self, full_screen: bool = True) -> Optional[Any]:
|
|
"""Capture screenshot.
|
|
|
|
Args:
|
|
full_screen: If True, capture entire screen
|
|
|
|
Returns:
|
|
PIL Image object or None
|
|
"""
|
|
if not self.api:
|
|
return None
|
|
|
|
return self.api.capture_screen() if hasattr(self.api, 'capture_screen') else None
|
|
|
|
def capture_region(self, x: int, y: int, width: int, height: int) -> Optional[Any]:
|
|
"""Capture specific screen region.
|
|
|
|
Args:
|
|
x: Left coordinate
|
|
y: Top coordinate
|
|
width: Region width
|
|
height: Region height
|
|
|
|
Returns:
|
|
PIL Image object or None
|
|
"""
|
|
if not self.api:
|
|
return None
|
|
|
|
return self.api.capture_screen(region=(x, y, width, height))
|
|
|
|
# ========== OCR Service Methods ==========
|
|
|
|
def ocr_capture(self, region: Optional[tuple] = None) -> Dict[str, Any]:
|
|
"""Capture screen and perform OCR.
|
|
|
|
Args:
|
|
region: Optional (x, y, width, height) tuple
|
|
|
|
Returns:
|
|
Dict with 'text', 'confidence', 'error' keys
|
|
"""
|
|
if not self.api:
|
|
return {"text": "", "confidence": 0, "error": "API not available"}
|
|
|
|
try:
|
|
text = self.api.recognize_text(region=region)
|
|
return {"text": text, "confidence": 1.0, "error": None}
|
|
except Exception as e:
|
|
return {"text": "", "confidence": 0, "error": str(e)}
|
|
|
|
# ========== Log Reader Methods ==========
|
|
|
|
def read_log(self, lines: int = 50, filter_text: Optional[str] = None) -> List[str]:
|
|
"""Read recent game log lines.
|
|
|
|
Args:
|
|
lines: Number of lines to read
|
|
filter_text: Optional text to filter lines
|
|
|
|
Returns:
|
|
List of log line strings
|
|
"""
|
|
if not self.api:
|
|
return []
|
|
|
|
log_lines = self.api.read_log_lines(lines)
|
|
if filter_text:
|
|
log_lines = [line for line in log_lines if filter_text in line]
|
|
return log_lines
|
|
|
|
# ========== Data Store Methods ==========
|
|
|
|
def get_shared_data(self, key: str, default: Any = None) -> Any:
|
|
"""Get shared data from other plugins.
|
|
|
|
Args:
|
|
key: Data key
|
|
default: Default value if not found
|
|
|
|
Returns:
|
|
Stored data or default
|
|
"""
|
|
if not self.api:
|
|
return default
|
|
|
|
return self.api.get_data(key, default)
|
|
|
|
def set_shared_data(self, key: str, value: Any) -> None:
|
|
"""Set shared data for other plugins.
|
|
|
|
Args:
|
|
key: Data key
|
|
value: Value to store
|
|
"""
|
|
if self.api:
|
|
self.api.set_data(key, value)
|
|
|
|
# ========== Legacy Event System ==========
|
|
|
|
def publish_event(self, event_type: str, data: Dict[str, Any]) -> bool:
|
|
"""Publish an event for other plugins to consume (legacy).
|
|
|
|
Args:
|
|
event_type: Event type string
|
|
data: Event data dictionary
|
|
|
|
Returns:
|
|
True if published
|
|
"""
|
|
if self.api:
|
|
return self.api.publish(event_type, data)
|
|
return False
|
|
|
|
def subscribe(self, event_type: str, callback: Callable) -> str:
|
|
"""Subscribe to events from other plugins (legacy).
|
|
|
|
Args:
|
|
event_type: Event type string
|
|
callback: Function to call when event occurs
|
|
|
|
Returns:
|
|
Subscription ID
|
|
"""
|
|
if self.api:
|
|
return self.api.subscribe(event_type, callback)
|
|
return ""
|
|
|
|
# ========== Enhanced Typed Event System ==========
|
|
|
|
def publish_typed(self, event: 'BaseEvent') -> bool:
|
|
"""Publish a typed event to the Event Bus.
|
|
|
|
Args:
|
|
event: A typed event instance
|
|
|
|
Returns:
|
|
True if published
|
|
|
|
Example:
|
|
from core.event_bus import LootEvent
|
|
|
|
self.publish_typed(LootEvent(
|
|
mob_name="Daikiba",
|
|
items=[{"name": "Animal Oil", "value": 0.05}],
|
|
total_tt_value=0.05
|
|
))
|
|
"""
|
|
# This would integrate with the EventBus
|
|
return True
|
|
|
|
def subscribe_typed(
|
|
self,
|
|
event_class: Type['BaseEvent'],
|
|
callback: Callable,
|
|
**filter_kwargs: Any
|
|
) -> str:
|
|
"""Subscribe to a specific event type with optional filtering.
|
|
|
|
Args:
|
|
event_class: The event class to subscribe to
|
|
callback: Function to call when matching events occur
|
|
**filter_kwargs: Additional filter criteria
|
|
|
|
Returns:
|
|
Subscription ID
|
|
"""
|
|
if not self.api:
|
|
print(f"[{self.name}] API not available for event subscription")
|
|
return ""
|
|
|
|
sub_id = f"sub_{id(callback)}"
|
|
self._event_subscriptions.append(sub_id)
|
|
return sub_id
|
|
|
|
def unsubscribe_typed(self, subscription_id: str) -> bool:
|
|
"""Unsubscribe from a specific typed event subscription.
|
|
|
|
Args:
|
|
subscription_id: The subscription ID returned by subscribe_typed
|
|
|
|
Returns:
|
|
True if subscription was found and removed
|
|
"""
|
|
if subscription_id in self._event_subscriptions:
|
|
self._event_subscriptions.remove(subscription_id)
|
|
return True
|
|
return False
|
|
|
|
def unsubscribe_all_typed(self) -> None:
|
|
"""Unsubscribe from all typed event subscriptions."""
|
|
self._event_subscriptions.clear()
|
|
|
|
def get_recent_events(
|
|
self,
|
|
event_type: Optional[Type['BaseEvent']] = None,
|
|
count: int = 100,
|
|
category: Optional[str] = None
|
|
) -> List['BaseEvent']:
|
|
"""Get recent events from history.
|
|
|
|
Args:
|
|
event_type: Filter by event class
|
|
count: Maximum number of events to return
|
|
category: Filter by event category
|
|
|
|
Returns:
|
|
List of matching events
|
|
"""
|
|
return []
|
|
|
|
# ========== Utility Methods ==========
|
|
|
|
def format_ped(self, value: float) -> str:
|
|
"""Format PED value.
|
|
|
|
Args:
|
|
value: PED amount
|
|
|
|
Returns:
|
|
Formatted string (e.g., "12.34 PED")
|
|
"""
|
|
return f"{value:.2f} PED"
|
|
|
|
def format_pec(self, value: float) -> str:
|
|
"""Format PEC value.
|
|
|
|
Args:
|
|
value: PEC amount
|
|
|
|
Returns:
|
|
Formatted string (e.g., "123 PEC")
|
|
"""
|
|
return f"{value:.0f} PEC"
|
|
|
|
def calculate_dpp(self, damage: float, ammo: int, decay: float) -> float:
|
|
"""Calculate Damage Per PEC.
|
|
|
|
Args:
|
|
damage: Damage dealt
|
|
ammo: Ammo consumed
|
|
decay: Weapon decay in PED
|
|
|
|
Returns:
|
|
DPP value
|
|
"""
|
|
if damage <= 0:
|
|
return 0.0
|
|
ammo_cost = ammo * 0.01
|
|
total_cost = ammo_cost + decay
|
|
if total_cost <= 0:
|
|
return 0.0
|
|
return damage / (total_cost / 100)
|
|
|
|
def calculate_markup(self, price: float, tt: float) -> float:
|
|
"""Calculate markup percentage.
|
|
|
|
Args:
|
|
price: Market price
|
|
tt: TT value
|
|
|
|
Returns:
|
|
Markup percentage
|
|
"""
|
|
if tt <= 0:
|
|
return 0.0
|
|
return (price / tt) * 100
|
|
|
|
# ========== Audio Service Methods ==========
|
|
|
|
def play_sound(self, filename_or_key: str, blocking: bool = False) -> bool:
|
|
"""Play a sound by key or filename.
|
|
|
|
Args:
|
|
filename_or_key: Sound key or file path
|
|
blocking: If True, wait for sound to complete
|
|
|
|
Returns:
|
|
True if sound was queued/played
|
|
"""
|
|
if not self.api:
|
|
return False
|
|
|
|
return self.api.play_sound(filename_or_key)
|
|
|
|
def set_volume(self, volume: float) -> None:
|
|
"""Set global audio volume.
|
|
|
|
Args:
|
|
volume: Volume level from 0.0 to 1.0
|
|
"""
|
|
if self.api and hasattr(self.api, 'set_volume'):
|
|
self.api.set_volume(volume)
|
|
|
|
def get_volume(self) -> float:
|
|
"""Get current audio volume.
|
|
|
|
Returns:
|
|
Current volume level (0.0 to 1.0)
|
|
"""
|
|
if self.api and hasattr(self.api, 'get_volume'):
|
|
return self.api.get_volume()
|
|
return 0.0
|
|
|
|
# ========== Background Task Methods ==========
|
|
|
|
def run_in_background(self, func: Callable, *args: Any,
|
|
priority: str = 'normal',
|
|
on_complete: Optional[Callable] = None,
|
|
on_error: Optional[Callable] = None,
|
|
**kwargs: Any) -> str:
|
|
"""Run a function in a background thread.
|
|
|
|
Args:
|
|
func: Function to execute in background
|
|
*args: Positional arguments for the function
|
|
priority: 'high', 'normal', or 'low'
|
|
on_complete: Called with result when task completes
|
|
on_error: Called with exception when task fails
|
|
**kwargs: Keyword arguments for the function
|
|
|
|
Returns:
|
|
Task ID for tracking/cancellation
|
|
"""
|
|
if not self.api:
|
|
raise RuntimeError("API not available")
|
|
|
|
return self.api.run_task(func, *args, callback=on_complete,
|
|
error_handler=on_error)
|
|
|
|
# ========== Nexus API Methods ==========
|
|
|
|
def nexus_search(self, query: str, entity_type: str = "items",
|
|
limit: int = 20) -> List[Dict[str, Any]]:
|
|
"""Search for entities via Entropia Nexus API.
|
|
|
|
Args:
|
|
query: Search query string
|
|
entity_type: Type of entity (items, weapons, mobs, etc.)
|
|
limit: Maximum number of results
|
|
|
|
Returns:
|
|
List of search result dictionaries
|
|
"""
|
|
if not self.api:
|
|
return []
|
|
|
|
return self.api.search_items(query, limit)
|
|
|
|
def nexus_get_item_details(self, item_id: str) -> Optional[Dict[str, Any]]:
|
|
"""Get detailed information about a specific item.
|
|
|
|
Args:
|
|
item_id: The item's unique identifier
|
|
|
|
Returns:
|
|
Dictionary with item details, or None
|
|
"""
|
|
if not self.api:
|
|
return None
|
|
|
|
return self.api.get_item_details(item_id)
|
|
|
|
def nexus_get_market_data(self, item_id: str) -> Optional[Dict[str, Any]]:
|
|
"""Get market data for a specific item.
|
|
|
|
Args:
|
|
item_id: The item's unique identifier
|
|
|
|
Returns:
|
|
Dictionary with market data, or None
|
|
"""
|
|
if not self.api:
|
|
return None
|
|
|
|
# This would call the market data endpoint
|
|
return None
|
|
|
|
def nexus_is_available(self) -> bool:
|
|
"""Check if Nexus API is available.
|
|
|
|
Returns:
|
|
True if Nexus API service is ready
|
|
"""
|
|
if not self.api:
|
|
return False
|
|
|
|
return True
|
|
|
|
# ========== HTTP Client Methods ==========
|
|
|
|
def http_get(self, url: str, cache_ttl: int = 300,
|
|
headers: Optional[Dict[str, str]] = None, **kwargs: Any) -> Dict[str, Any]:
|
|
"""Make an HTTP GET request with caching.
|
|
|
|
Args:
|
|
url: The URL to fetch
|
|
cache_ttl: Cache TTL in seconds
|
|
headers: Additional headers
|
|
**kwargs: Additional arguments
|
|
|
|
Returns:
|
|
Response dictionary
|
|
"""
|
|
if not self.api:
|
|
raise RuntimeError("API not available")
|
|
|
|
return self.api.http_get(url, cache=True, cache_duration=cache_ttl)
|
|
|
|
# ========== DataStore Methods ==========
|
|
|
|
def save_data(self, key: str, data: Any) -> bool:
|
|
"""Save data to persistent storage.
|
|
|
|
Data is automatically scoped to this plugin and survives app restarts.
|
|
|
|
Args:
|
|
key: Key to store data under
|
|
data: Data to store (must be JSON serializable)
|
|
|
|
Returns:
|
|
True if saved successfully
|
|
"""
|
|
if not self.api:
|
|
return False
|
|
|
|
return self.api.set_data(key, data)
|
|
|
|
def load_data(self, key: str, default: Any = None) -> Any:
|
|
"""Load data from persistent storage.
|
|
|
|
Args:
|
|
key: Key to load data from
|
|
default: Default value if key doesn't exist
|
|
|
|
Returns:
|
|
Stored data or default value
|
|
"""
|
|
if not self.api:
|
|
return default
|
|
|
|
return self.api.get_data(key, default)
|
|
|
|
def delete_data(self, key: str) -> bool:
|
|
"""Delete data from persistent storage.
|
|
|
|
Args:
|
|
key: Key to delete
|
|
|
|
Returns:
|
|
True if deleted (or didn't exist)
|
|
"""
|
|
if not self.api:
|
|
return False
|
|
|
|
return self.api.delete_data(key)
|
|
|
|
def get_all_data_keys(self) -> List[str]:
|
|
"""Get all data keys stored by this plugin.
|
|
|
|
Returns:
|
|
List of key names
|
|
"""
|
|
if not self.api:
|
|
return []
|
|
|
|
# This would return keys from the data store
|
|
return []
|
|
|
|
# ========== Window Manager Methods ==========
|
|
|
|
def get_eu_window(self) -> Optional[Dict[str, Any]]:
|
|
"""Get information about the Entropia Universe game window.
|
|
|
|
Returns:
|
|
Dict with window info or None
|
|
"""
|
|
if not self.api:
|
|
return None
|
|
|
|
return self.api.get_eu_window()
|
|
|
|
def is_eu_focused(self) -> bool:
|
|
"""Check if Entropia Universe window is currently focused.
|
|
|
|
Returns:
|
|
True if EU is the active window
|
|
"""
|
|
if not self.api:
|
|
return False
|
|
|
|
return self.api.is_eu_focused()
|
|
|
|
def is_eu_visible(self) -> bool:
|
|
"""Check if Entropia Universe window is visible.
|
|
|
|
Returns:
|
|
True if EU window is visible
|
|
"""
|
|
if not self.api:
|
|
return False
|
|
|
|
return self.api.is_eu_visible()
|
|
|
|
def bring_eu_to_front(self) -> bool:
|
|
"""Bring Entropia Universe window to front and focus it.
|
|
|
|
Returns:
|
|
True if successful
|
|
"""
|
|
if not self.api:
|
|
return False
|
|
|
|
return self.api.bring_eu_to_front()
|
|
|
|
# ========== Clipboard Methods ==========
|
|
|
|
def copy_to_clipboard(self, text: str) -> bool:
|
|
"""Copy text to system clipboard.
|
|
|
|
Args:
|
|
text: Text to copy
|
|
|
|
Returns:
|
|
True if successful
|
|
"""
|
|
if not self.api:
|
|
return False
|
|
|
|
return self.api.copy_to_clipboard(text)
|
|
|
|
def paste_from_clipboard(self) -> str:
|
|
"""Paste text from system clipboard.
|
|
|
|
Returns:
|
|
Clipboard content or empty string
|
|
"""
|
|
if not self.api:
|
|
return ""
|
|
|
|
return self.api.paste_from_clipboard()
|
|
|
|
# ========== Notification Methods ==========
|
|
|
|
def notify(self, title: str, message: str,
|
|
notification_type: str = 'info',
|
|
sound: bool = False,
|
|
duration_ms: int = 5000) -> str:
|
|
"""Show a toast notification.
|
|
|
|
Args:
|
|
title: Notification title
|
|
message: Notification message
|
|
notification_type: 'info', 'warning', 'error', or 'success'
|
|
sound: Play notification sound
|
|
duration_ms: How long to show notification
|
|
|
|
Returns:
|
|
Notification ID
|
|
"""
|
|
if not self.api:
|
|
return ""
|
|
|
|
return self.api.show_notification(title, message, duration_ms, sound)
|
|
|
|
def notify_info(self, title: str, message: str, sound: bool = False) -> str:
|
|
"""Show info notification (convenience method)."""
|
|
return self.notify(title, message, 'info', sound)
|
|
|
|
def notify_success(self, title: str, message: str, sound: bool = False) -> str:
|
|
"""Show success notification (convenience method)."""
|
|
return self.notify(title, message, 'success', sound)
|
|
|
|
def notify_warning(self, title: str, message: str, sound: bool = False) -> str:
|
|
"""Show warning notification (convenience method)."""
|
|
return self.notify(title, message, 'warning', sound)
|
|
|
|
def notify_error(self, title: str, message: str, sound: bool = True) -> str:
|
|
"""Show error notification (convenience method)."""
|
|
return self.notify(title, message, 'error', sound)
|
|
|
|
# ========== Settings Methods ==========
|
|
|
|
def get_setting(self, key: str, default: Any = None) -> Any:
|
|
"""Get a global EU-Utility setting.
|
|
|
|
Args:
|
|
key: Setting key
|
|
default: Default value if not set
|
|
|
|
Returns:
|
|
Setting value
|
|
"""
|
|
if not self.api:
|
|
return default
|
|
|
|
# This would get from global settings
|
|
return default
|
|
|
|
def set_setting(self, key: str, value: Any) -> bool:
|
|
"""Set a global EU-Utility setting.
|
|
|
|
Args:
|
|
key: Setting key
|
|
value: Value to set
|
|
|
|
Returns:
|
|
True if saved
|
|
"""
|
|
if not self.api:
|
|
return False
|
|
|
|
return False
|
|
|
|
# ========== Logging Methods ==========
|
|
|
|
def log_debug(self, message: str) -> None:
|
|
"""Log debug message (development only)."""
|
|
print(f"[DEBUG][{self.name}] {message}")
|
|
|
|
def log_info(self, message: str) -> None:
|
|
"""Log info message."""
|
|
print(f"[INFO][{self.name}] {message}")
|
|
|
|
def log_warning(self, message: str) -> None:
|
|
"""Log warning message."""
|
|
print(f"[WARNING][{self.name}] {message}")
|
|
|
|
def log_error(self, message: str) -> None:
|
|
"""Log error message."""
|
|
print(f"[ERROR][{self.name}] {message}")
|
|
|
|
|
|
# Re-export for convenience
|
|
__all__ = ['BasePlugin']
|