Exemples d'extensions
Implémentations concrètes d'extensions et cas d'usage
Exemples d'extensions
Apprenez avec des exemples concrets
Ces implémentations d'extensions prêtes pour la production démontrent les bonnes pratiques et les patterns courants avec VoltAPI.
Générateur de mot de passe
Un générateur de mots de passe sécurisés avec plusieurs modes :
// Extension Password Generator
// Déclencheurs : "pass", "password", "pwd"
interface Plugin {
id: string;
name: string;
description: string;
enabled: boolean;
canHandle(context: PluginContext): boolean;
match(context: PluginContext): PluginResult[];
execute(result: PluginResult): Promise<void>;
}
interface PluginContext {
query: string;
settings?: Record<string, unknown>;
}
interface PluginResult {
id: string;
type: any;
title: string;
subtitle?: string;
icon?: string;
score: number;
data?: Record<string, unknown>;
}
const TRIGGERS = ['pass', 'password', 'pwd'];
const CHARSET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&\*';
function generatePassword(length: number): string {
const array = new Uint32Array(length);
crypto.getRandomValues(array);
return Array.from(array, (num) => CHARSET[num % CHARSET.length]).join('');
}
function calculateStrength(length: number): string {
if (length >= 20) return 'Très fort';
if (length >= 16) return 'Fort';
if (length >= 12) return 'Bon';
return 'Faible';
}
class PasswordGeneratorPlugin implements Plugin {
id = 'password-generator';
name = 'Password Generator';
description = 'Générer des mots de passe cryptographiquement sûrs';
enabled = true;
canHandle(context: PluginContext): boolean {
const query = context.query.toLowerCase().trim();
return TRIGGERS.some(trigger => query.startsWith(trigger));
}
match(context: PluginContext): PluginResult[] {
const { PluginResultType } = window.VoltAPI.types;
const query = context.query.toLowerCase().trim();
// Extraire la longueur de la requête (ex. "pass 20")
const parts = query.split(/\s+/);
const lengthArg = parts.find(p => /^\d+$/.test(p));
const length = lengthArg ? Math.min(128, Math.max(8, parseInt(lengthArg))) : 16;
const password = generatePassword(length);
const strength = calculateStrength(length);
return [{
id: 'password',
type: PluginResultType.Password,
title: password,
subtitle: `${length} car. • ${strength} • Entrée pour copier`,
icon: '🔐',
score: 100,
data: { password, length }
}];
}
async execute(result: PluginResult): Promise<void> {
const password = result.data?.password as string;
const success = await window.VoltAPI.utils.copyToClipboard(password);
if (success) {
window.VoltAPI.notify('Mot de passe copié !', 'success');
} else {
window.VoltAPI.notify('Échec de la copie', 'error');
}
}
}
export default PasswordGeneratorPlugin;{
"id": "password-generator",
"name": "Password Generator",
"version": "1.0.0",
"description": "Générer des mots de passe cryptographiquement sûrs",
"author": { "name": "Volt Community" },
"main": "index.ts",
"category": "utilities",
"keywords": ["password", "security", "generator"],
"minVoltVersion": "0.4.0",
"permissions": ["clipboard"]
}Utilisation :
pass→ mot de passe de 16 caractèrespass 24→ mot de passe de 24 caractèrespassword 32→ mot de passe de 32 caractères
Recherche web
Chercher sur plusieurs moteurs de recherche :
// Extension Web Search
// Déclencheurs : "?", "search ", "web "
interface Plugin {
id: string;
name: string;
description: string;
enabled: boolean;
canHandle(context: PluginContext): boolean;
match(context: PluginContext): PluginResult[];
execute(result: PluginResult): Promise<void>;
}
interface PluginContext {
query: string;
}
interface PluginResult {
id: string;
type: any;
title: string;
subtitle?: string;
icon?: string;
score: number;
data?: Record<string, unknown>;
}
const TRIGGERS = ['?', 'search ', 'web ', 'google ', 'bing '];
const SEARCH_ENGINES = [
{ id: 'google', name: 'Google', url: 'https://google.com/search?q=', icon: '🔍' },
{ id: 'duckduckgo', name: 'DuckDuckGo', url: 'https://duckduckgo.com/?q=', icon: '🦆' },
{ id: 'bing', name: 'Bing', url: 'https://bing.com/search?q=', icon: '🅱️' },
];
class WebSearchPlugin implements Plugin {
id = 'web-search';
name = 'Web Search';
description = 'Chercher sur le web avec différents moteurs';
enabled = true;
canHandle(context: PluginContext): boolean {
const query = context.query.toLowerCase();
return TRIGGERS.some(t => query.startsWith(t));
}
match(context: PluginContext): PluginResult[] {
const { PluginResultType } = window.VoltAPI.types;
// Extraire la requête de recherche
let searchQuery = context.query;
for (const trigger of TRIGGERS) {
if (searchQuery.toLowerCase().startsWith(trigger)) {
searchQuery = searchQuery.slice(trigger.length).trim();
break;
}
}
if (!searchQuery) {
return [{
id: 'hint',
type: PluginResultType.Info,
title: 'Tapez votre requête',
subtitle: 'Exemple : ? what is volt launcher',
icon: '💡',
score: 50
}];
}
return SEARCH_ENGINES.map((engine, i) => ({
id: engine.id,
type: PluginResultType.WebSearch,
title: `Chercher « ${searchQuery} »`,
subtitle: `Ouvrir dans ${engine.name}`,
icon: engine.icon,
score: 100 - i,
data: {
url: engine.url + encodeURIComponent(searchQuery),
engine: engine.name
}
}));
}
async execute(result: PluginResult): Promise<void> {
const url = result.data?.url as string;
if (url) {
await window.VoltAPI.utils.openUrl(url);
}
}
}
export default WebSearchPlugin;{
"id": "web-search",
"name": "Web Search",
"version": "1.0.0",
"description": "Chercher sur le web avec différents moteurs",
"author": { "name": "Volt Community" },
"main": "index.ts",
"category": "utilities",
"keywords": ["search", "web", "google", "bing"],
"minVoltVersion": "0.4.0",
"permissions": ["network"]
}Utilisation :
?what is typescript→ Chercher sur tous les moteurssearch météo→ Chercher météogoogle react docs→ Chercher sur Google
Calculatrice
Évaluer des expressions mathématiques :
// Extension Calculator
// Déclencheur : expressions mathématiques
interface Plugin {
id: string;
name: string;
description: string;
enabled: boolean;
canHandle(context: PluginContext): boolean;
match(context: PluginContext): PluginResult[];
execute(result: PluginResult): Promise<void>;
}
interface PluginContext {
query: string;
}
interface PluginResult {
id: string;
type: any;
title: string;
subtitle?: string;
icon?: string;
score: number;
data?: Record<string, unknown>;
}
// Regex d'expression math simple
const MATH_REGEX = /^[\d\s+\-*/().,%^]+$/;
function evaluate(expression: string): number {
// Retirer les espaces et remplacer les symboles courants
const cleaned = expression
.replace(/\s/g, '')
.replace(/,/g, '')
.replace(/%/g, '/100')
.replace(/\^/g, '\*\*');
// Utiliser le constructeur Function pour une évaluation sûre
// Permet uniquement les opérations mathématiques
const fn = new Function(`return (${cleaned})`);
return fn();
}
function formatResult(num: number): string {
if (Number.isInteger(num)) {
return num.toLocaleString();
}
// Arrondir à 10 décimales pour éviter les erreurs de virgule flottante
return parseFloat(num.toFixed(10)).toLocaleString(undefined, {
maximumFractionDigits: 10
});
}
class CalculatorPlugin implements Plugin {
id = 'calculator';
name = 'Calculator';
description = 'Évaluer des expressions mathématiques';
enabled = true;
canHandle(context: PluginContext): boolean {
const query = context.query.trim();
// Doit contenir au moins un chiffre et un opérateur
return MATH_REGEX.test(query) &&
/\d/.test(query) &&
/[+\-*/.^%]/.test(query);
}
match(context: PluginContext): PluginResult[] {
const { PluginResultType } = window.VoltAPI.types;
const query = context.query.trim();
try {
const result = evaluate(query);
if (!isFinite(result)) {
return [{
id: 'error',
type: PluginResultType.Calculator,
title: 'Calcul invalide',
subtitle: 'Division par zéro ou expression invalide',
icon: '⚠️',
score: 50
}];
}
const formatted = formatResult(result);
return [{
id: 'result',
type: PluginResultType.Calculator,
title: formatted,
subtitle: `${query} = ${formatted} • Entrée pour copier`,
icon: '🔢',
score: 100,
data: { result: formatted, expression: query }
}];
} catch (error) {
return [];
}
}
async execute(result: PluginResult): Promise<void> {
const value = result.data?.result as string;
if (value) {
const success = await window.VoltAPI.utils.copyToClipboard(value);
if (success) {
window.VoltAPI.notify('Résultat copié !', 'success');
}
}
}
}
export default CalculatorPlugin;{
"id": "calculator",
"name": "Calculator",
"version": "1.0.0",
"description": "Évaluer des expressions mathématiques",
"author": { "name": "Volt Community" },
"main": "index.ts",
"category": "utilities",
"keywords": ["calculator", "math", "compute"],
"minVoltVersion": "0.4.0",
"permissions": ["clipboard"]
}Utilisation :
2 + 2→ 4100 * 1.15→ 115(50 + 25) / 3→ 252^10→ 1 024
Minuteur
Créer des minuteurs avec notifications :
// Extension Timer
// Déclencheurs : "timer", "remind"
interface Plugin {
id: string;
name: string;
description: string;
enabled: boolean;
canHandle(context: PluginContext): boolean;
match(context: PluginContext): PluginResult[];
execute(result: PluginResult): Promise<void>;
}
interface PluginContext {
query: string;
}
interface PluginResult {
id: string;
type: any;
title: string;
subtitle?: string;
icon?: string;
score: number;
data?: Record<string, unknown>;
}
const TIME_REGEX = /^(?:timer|remind)\s+(\d+)\s\*(s|sec|seconds?|m|min|minutes?|h|hours?)?$/i;
function parseTime(amount: number, unit: string): number {
const u = (unit || 's').toLowerCase();
if (u.startsWith('h')) return amount _ 3600;
if (u.startsWith('m')) return amount _ 60;
return amount;
}
function formatDuration(seconds: number): string {
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
const s = seconds % 60;
const parts = [];
if (h > 0) parts.push(`${h}h`);
if (m > 0) parts.push(`${m}m`);
if (s > 0 || parts.length === 0) parts.push(`${s}s`);
return parts.join(' ');
}
class TimerPlugin implements Plugin {
id = 'timer';
name = 'Timer';
description = 'Créer des minuteurs et des rappels';
enabled = true;
canHandle(context: PluginContext): boolean {
const query = context.query.toLowerCase().trim();
return query.startsWith('timer') || query.startsWith('remind');
}
match(context: PluginContext): PluginResult[] {
const { PluginResultType } = window.VoltAPI.types;
const query = context.query.trim();
const match = TIME_REGEX.exec(query);
if (!match) {
return [{
id: 'hint',
type: PluginResultType.Timer,
title: 'Définir un minuteur',
subtitle: 'Exemple : timer 5m, timer 30s, timer 1h',
icon: '⏱️',
score: 50
}];
}
const [, amountStr, unit] = match;
const amount = parseInt(amountStr);
const seconds = parseTime(amount, unit);
const formatted = formatDuration(seconds);
return [{
id: 'timer',
type: PluginResultType.Timer,
title: `Minuteur de ${formatted}`,
subtitle: 'Appuyez sur Entrée pour démarrer',
icon: '⏱️',
score: 100,
data: { seconds, display: formatted }
}];
}
async execute(result: PluginResult): Promise<void> {
const seconds = result.data?.seconds as number;
const display = result.data?.display as string;
if (!seconds) return;
window.VoltAPI.notify(`Minuteur de ${display} démarré`, 'success');
// Démarrer le minuteur
setTimeout(() => {
// Notification à la fin
if ('Notification' in window && Notification.permission === 'granted') {
new Notification('Minuteur terminé !', {
body: `Votre minuteur de ${display} est terminé`,
icon: '⏱️'
});
}
window.VoltAPI.notify(`Minuteur terminé ! (${display})`, 'info');
}, seconds * 1000);
}
}
export default TimerPlugin;{
"id": "timer",
"name": "Timer",
"version": "1.0.0",
"description": "Créer des minuteurs et des rappels",
"author": { "name": "Volt Community" },
"main": "index.ts",
"category": "utilities",
"keywords": ["timer", "reminder", "countdown"],
"minVoltVersion": "0.4.0",
"permissions": ["notifications"]
}Utilisation :
timer 30s→ minuteur de 30 secondestimer 5m→ minuteur de 5 minutestimer 1h→ minuteur d'1 heure
Générateur d'UUID
Générer des identifiants uniques :
// Extension UUID Generator
// Déclencheurs : "uuid", "guid"
interface Plugin {
id: string;
name: string;
description: string;
enabled: boolean;
canHandle(context: PluginContext): boolean;
match(context: PluginContext): PluginResult[];
execute(result: PluginResult): Promise<void>;
}
interface PluginContext {
query: string;
}
interface PluginResult {
id: string;
type: any;
title: string;
subtitle?: string;
icon?: string;
score: number;
data?: Record<string, unknown>;
}
function generateUUID(): string {
// Utiliser crypto.randomUUID si disponible (navigateurs modernes)
if (typeof crypto.randomUUID === 'function') {
return crypto.randomUUID();
}
// Implémentation de repli
const array = new Uint8Array(16);
crypto.getRandomValues(array);
// Définir version (4) et variant (10xx)
array[6] = (array[6] & 0x0f) | 0x40;
array[8] = (array[8] & 0x3f) | 0x80;
const hex = Array.from(array, b => b.toString(16).padStart(2, '0')).join('');
return `${hex.slice(0,8)}-${hex.slice(8,12)}-${hex.slice(12,16)}-${hex.slice(16,20)}-${hex.slice(20)}`;
}
class UUIDPlugin implements Plugin {
id = 'uuid-generator';
name = 'UUID Generator';
description = 'Générer des identifiants uniques';
enabled = true;
canHandle(context: PluginContext): boolean {
const query = context.query.toLowerCase().trim();
return query === 'uuid' || query === 'guid' || query.startsWith('uuid ');
}
match(context: PluginContext): PluginResult[] {
const { PluginResultType } = window.VoltAPI.types;
const query = context.query.toLowerCase().trim();
// L'utilisateur demande-t-il plusieurs UUIDs ?
const countMatch = query.match(/^uuid\s+(\d+)$/);
const count = countMatch ? Math.min(10, parseInt(countMatch[1])) : 1;
const results: PluginResult[] = [];
for (let i = 0; i < count; i++) {
const uuid = generateUUID();
results.push({
id: `uuid-${i}`,
type: PluginResultType.Info,
title: uuid,
subtitle: 'Appuyez sur Entrée pour copier',
icon: '🔑',
score: 100 - i,
data: { uuid }
});
}
return results;
}
async execute(result: PluginResult): Promise<void> {
const uuid = result.data?.uuid as string;
if (uuid) {
const success = await window.VoltAPI.utils.copyToClipboard(uuid);
if (success) {
window.VoltAPI.notify('UUID copié !', 'success');
}
}
}
}
export default UUIDPlugin;{
"id": "uuid-generator",
"name": "UUID Generator",
"version": "1.0.0",
"description": "Générer des identifiants uniques",
"author": { "name": "Volt Community" },
"main": "index.ts",
"category": "development",
"keywords": ["uuid", "guid", "unique", "id"],
"minVoltVersion": "0.4.0",
"permissions": ["clipboard"]
}Utilisation :
uuid→ générer un UUIDuuid 5→ générer 5 UUIDs
Exemple de plugin backend (Rust)
Pour les intégrations système, utilisez des plugins backend en Rust :
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Serialize, Deserialize)]
pub struct GameInfo {
pub id: String,
pub name: String,
pub path: PathBuf,
pub icon: Option<String>,
}
pub struct SteamPlugin {
steam_path: PathBuf,
}
impl SteamPlugin {
pub fn new() -> Self {
let steam_path = Self::find_steam_path();
Self { steam_path }
}
fn find_steam_path() -> PathBuf {
#[cfg(target_os = "windows")]
{ PathBuf::from("C:\\Program Files (x86)\\Steam") }
#[cfg(target_os = "macos")]
{ PathBuf::from("/Users/Shared/Steam") }
#[cfg(target_os = "linux")]
{ PathBuf::from("~/.steam/steam") }
}
pub fn scan_games(&self) -> Vec<GameInfo> {
let library_path = self.steam_path.join("steamapps");
let mut games = Vec::new();
if let Ok(entries) = std::fs::read_dir(&library_path) {
for entry in entries.flatten() {
if let Some(name) = entry.file_name().to_str() {
if name.starts_with("appmanifest_") && name.ends_with(".acf") {
if let Ok(game) = self.parse_manifest(&entry.path()) {
games.push(game);
}
}
}
}
}
games
}
fn parse_manifest(&self, path: &PathBuf) -> Result<GameInfo, Box<dyn std::error::Error>> {
let content = std::fs::read_to_string(path)?;
let id = Self::extract_value(&content, "appid")?;
let name = Self::extract_value(&content, "name")?;
let install_dir = Self::extract_value(&content, "installdir")?;
Ok(GameInfo {
id,
name,
path: self.steam_path.join("steamapps/common").join(&install_dir),
icon: None,
})
}
fn extract_value(content: &str, key: &str) -> Result<String, Box<dyn std::error::Error>> {
let pattern = format!("\"{}\"\\s+\"([^\"]+)\"", key);
let re = regex::Regex::new(&pattern)?;
re.captures(content)
.and_then(|c| c.get(1))
.map(|m| m.as_str().to_string())
.ok_or_else(|| "Clé introuvable".into())
}
}
// Commande Tauri à exposer au frontend
#[tauri::command]
pub async fn scan_steam_games() -> VoltResult<Vec<GameInfo>> {
let plugin = SteamPlugin::new();
Ok(plugin.scan_games())
}Plugin GitHub
Recherchez dépôts, issues, PRs et gists GitHub directement depuis Volt. Disponible dans la Boutique d'extensions.
// Plugin GitHub — Rechercher repos, issues, PRs, gists
// Déclencheurs : "gh:" ou "gh "
const GITHUB_API = 'https://api.github.com';
class GitHubPlugin implements Plugin {
id = 'github';
name = 'GitHub';
description = 'Rechercher dépôts, issues et pull requests GitHub';
enabled = true;
private headers: Record<string, string> = {
'Accept': 'application/vnd.github.v3+json',
};
canHandle(context: PluginContext): boolean {
const q = context.query.toLowerCase().trim();
return q.startsWith('gh:') || q.startsWith('gh ');
}
async match(context: PluginContext): Promise<PluginResult[]> {
const { PluginResultType } = window.VoltAPI.types;
const query = context.query.replace(/^gh[:\s]/, '').trim();
if (!query) {
return [{
id: 'hint',
type: PluginResultType.Info,
title: 'Rechercher sur GitHub',
subtitle: 'gh:react, gh:user/repo, gh:issues react',
icon: '🐙',
score: 50
}];
}
try {
const response = await fetch(
`${GITHUB_API}/search/repositories?q=${encodeURIComponent(query)}&per_page=5`,
{ headers: this.headers }
);
const data = await response.json();
return data.items.map((repo: any, i: number) => ({
id: `repo-${repo.id}`,
type: PluginResultType.Info,
title: repo.full_name,
subtitle: `⭐ ${repo.stargazers_count} • ${repo.description || 'Aucune description'}`,
icon: '📦',
score: 100 - i,
data: { url: repo.html_url }
}));
} catch {
return [];
}
}
async execute(result: PluginResult): Promise<void> {
const url = result.data?.url as string;
if (url) await window.VoltAPI.utils.openUrl(url);
}
}
export default GitHubPlugin;{
"id": "github",
"name": "GitHub",
"version": "1.0.0",
"description": "Rechercher dépôts, issues, pull requests et gists GitHub",
"author": {
"name": "VoltLaunchr Community",
"github": "VoltLaunchr"
},
"main": "src/index.ts",
"prefix": "gh",
"category": "productivity",
"keywords": ["github", "search", "repositories", "issues", "pull-requests"],
"repository": "https://github.com/VoltLaunchr/volt-extensions",
"license": "MIT",
"minVoltVersion": "0.4.0",
"permissions": ["network"]
}Rechercher des dépôts :
gh:react— Chercher des repos correspondant à « react »gh:language:rust stars:>1000— Filtrer par langage et étoiles
Rechercher issues & PRs :
gh:issues react— Chercher des issues ouvertesgh:prs typescript— Chercher des pull requests
Parcourir des repos utilisateur :
gh:user/repo— Voir un dépôt spécifique
Tendances :
gh:trending— Afficher les repos tendance
Authentification (optionnelle) :
Définissez la variable d'env GITHUB_TOKEN pour 5 000 req/heure (contre 60 non authentifié).
Plugin Notion
Recherchez dans votre espace Notion — pages, bases de données et blocs. Disponible dans la Boutique d'extensions.
// Plugin Notion — Rechercher dans l'espace Notion
// Déclencheurs : "notion:" ou "n:"
const NOTION_API = 'https://api.notion.com/v1';
class NotionPlugin implements Plugin {
id = 'notion';
name = 'Notion';
description = 'Rechercher dans votre espace Notion';
enabled = true;
canHandle(context: PluginContext): boolean {
const q = context.query.toLowerCase().trim();
return q.startsWith('notion:') || q.startsWith('n:');
}
async match(context: PluginContext): Promise<PluginResult[]> {
const { PluginResultType } = window.VoltAPI.types;
const query = context.query.replace(/^(notion|n):/, '').trim();
if (!query) {
return [{
id: 'hint',
type: PluginResultType.Info,
title: 'Rechercher dans Notion',
subtitle: 'n:notes réunion, notion:databases, n:recent',
icon: '📝',
score: 50
}];
}
try {
const apiKey = await window.VoltAPI.invoke('get_secure_value', {
key: 'NOTION_API_KEY'
});
const response = await fetch(`${NOTION_API}/search`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Notion-Version': '2024-02-15',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query,
page_size: 5,
}),
});
const data = await response.json();
return data.results.map((page: any, i: number) => ({
id: `notion-${page.id}`,
type: PluginResultType.Info,
title: page.properties?.title?.title?.[0]?.plain_text || 'Sans titre',
subtitle: `${page.object} • Modifié le ${new Date(page.last_edited_time).toLocaleDateString()}`,
icon: page.icon?.emoji || '📄',
score: 100 - i,
data: { url: page.url }
}));
} catch {
return [];
}
}
async execute(result: PluginResult): Promise<void> {
const url = result.data?.url as string;
if (url) await window.VoltAPI.utils.openUrl(url);
}
}
export default NotionPlugin;{
"id": "notion",
"name": "Notion",
"version": "1.0.0",
"description": "Rechercher dans votre espace Notion — pages, bases de données et blocs",
"author": {
"name": "VoltLaunchr Community",
"github": "VoltLaunchr"
},
"main": "src/index.ts",
"prefix": "notion",
"category": "productivity",
"keywords": ["notion", "notes", "workspace", "search", "databases"],
"repository": "https://github.com/VoltLaunchr/volt-extensions",
"license": "MIT",
"minVoltVersion": "0.4.0",
"permissions": ["network"]
}Recherche texte intégral :
notion:notes de réunion— Chercher dans l'espacen:feuille de route projet— Préfixe court
Lister les bases :
notion:databases— Afficher toutes les bases
Pages récentes :
n:recent— Voir les pages récemment modifiées
Authentification (requise) :
Définissez NOTION_API_KEY dans Paramètres → Intégrations. Créez une intégration sur notion.so/my-integrations.
Rate limits : 3 req/sec (plan gratuit), best effort (payant).