🖼️ Tampons, signatures & filigranes — Image SDK
L'objet global Image permet d'apposer un tampon, une signature ou un filigrane directement sur une image existante (contrat scanné, facture, photo). BotStrike télécharge les images depuis leur URL HTTPS, gère automatiquement le format (PNG/JPG/WEBP/GIF), redimensionne, applique l'opacité et exporte le résultat.
1 — Le principe
Le bot prend deux URLs :
- L'image de base — le contrat, la facture, le document à tamponner
- Le tampon ou la signature — un PNG transparent
BotStrike superpose le tampon sur l'image de base à la position (x, y) que vous indiquez. Le résultat est une nouvelle image tamponnée qui peut être renvoyée à l'utilisateur.
2 — API Image
Python — style namespace
| Méthode | Description |
|---|---|
Image.open(url) | Télécharge et ouvre une image depuis URL HTTPS. Renvoie un objet image. |
Image.blank(width, height, color) | Crée un fond vide (par défaut blanc) |
Image.paste(base, overlay, x, y, width, height, opacity) | Colle overlay SUR base. opacity 0..1 pour filigrane. |
Image.text(base, text, x, y, size, color) | Écrit du texte sur l'image |
Image.save(img, 'nom.png') | Sauvegarde et retourne le chemin |
JavaScript — style chaînable
| Méthode | Description |
|---|---|
await Image.open(url) | Renvoie un wrapper chaînable |
await Image.blank(w, h, color) | Fond vide chaînable |
await base.paste(overlay, {x, y, width, height, opacity}) | Superpose overlay |
await base.text(text, {x, y, size}) | Écrit du texte |
await base.save('nom.png') | Sauvegarde et renvoie le chemin |
await.3 — Exemple : tamponner un document
L'utilisateur fournit (via /setdoc, /settampon) les URLs de son document à tamponner et de son tampon. Le bot superpose le tampon dessus et renvoie le résultat.
# URLs configurées une fois et stockées dans Props
doc_url = Props.get('doc_url') # ex: https://i.imgur.com/contract.jpg
tampon_url = Props.get('tampon_url') # ex: https://i.imgur.com/stamp.png
# 1) Charger les deux images depuis leurs URLs HTTPS
base = Image.open(doc_url)
tampon = Image.open(tampon_url)
# 2) Apposer le tampon SUR le document (en bas Ă droite, 200px de large)
Image.paste(base, tampon, x=base.width - 250, y=base.height - 250, width=200)
# 3) Ajouter une mention manuscrite à côté
Image.text(base, 'Signe le 22/04/2026',
x=base.width - 250, y=base.height - 50, size=18, color='#000')
# 4) Sauvegarder et envoyer
path = Image.save(base, 'document_tamponne.png')
bot.sendPhoto(path, '✅ Document tamponné')
const docUrl = await Props.get('doc_url');
const tamponUrl = await Props.get('tampon_url');
// 1) Charger les deux images depuis URLs HTTPS
const base = await Image.open(docUrl);
const tampon = await Image.open(tamponUrl);
// 2) Apposer le tampon SUR le document
await base.paste(tampon, {
x: base.width - 250,
y: base.height - 250,
width: 200,
});
// 3) Texte à côté
await base.text('Signe le 22/04/2026', {
x: base.width - 250,
y: base.height - 50,
size: 32,
});
// 4) Sauvegarder et envoyer
const path = await base.save('document_tamponne.png');
await bot.sendPhoto(path, '✅ Document tamponné');
4 — Apposer signature + tampon sur un contrat
Cas typique : un contrat doit être signé par l'utilisateur. Le bot ajoute la signature à gauche et le cachet de l'entreprise à droite.
contrat_url = args.strip() # URL du contrat envoyée par l'utilisateur
signature_url = Props.get('signature_url')
tampon_url = Props.get('tampon_url')
# Charger le contrat (image scannée du document)
base = Image.open(contrat_url)
# Charger les overlays
sig = Image.open(signature_url)
tmp = Image.open(tampon_url)
# Coller la signature en bas Ă gauche, le tampon en bas Ă droite
Image.paste(base, sig, x=80, y=base.height - 220, width=180)
Image.paste(base, tmp, x=base.width - 280, y=base.height - 250, width=200)
# Date sous la signature
Image.text(base, 'Le 22/04/2026', x=100, y=base.height - 50, size=20)
path = Image.save(base, 'contrat_signe.png')
bot.sendDocument(path, '✅ Contrat signé et tamponné')
const contratUrl = (args || '').trim();
const signatureUrl = await Props.get('signature_url');
const tamponUrl = await Props.get('tampon_url');
const base = await Image.open(contratUrl);
const sig = await Image.open(signatureUrl);
const tmp = await Image.open(tamponUrl);
await base.paste(sig, {x: 80, y: base.height - 220, width: 180});
await base.paste(tmp, {x: base.width - 280, y: base.height - 250, width: 200});
await base.text('Le 22/04/2026', {x: 100, y: base.height - 50, size: 32});
const path = await base.save('contrat_signe.png');
await bot.sendDocument(path, '✅ Contrat signé et tamponné');
5 — Filigrane semi-transparent (watermark)
Pour protéger une image (preuve, brouillon), on superpose un logo ou un texte avec une opacité faible (0.2 - 0.4).
photo_url = args.strip()
logo_url = Props.get('logo_url')
base = Image.open(photo_url)
logo = Image.open(logo_url)
# Filigrane logo au centre, à 25% d'opacité
Image.paste(base, logo,
x=(base.width - 400) // 2,
y=(base.height - 400) // 2,
width=400,
opacity=0.25)
# Texte "BROUILLON" en rouge par-dessus
Image.text(base, 'BROUILLON', x=300, y=base.height - 200, size=80, color='#FF0000')
path = Image.save(base, 'protege.png')
bot.sendPhoto(path)
const photoUrl = (args || '').trim();
const logoUrl = await Props.get('logo_url');
const base = await Image.open(photoUrl);
const logo = await Image.open(logoUrl);
await base.paste(logo, {
x: Math.floor((base.width - 400) / 2),
y: Math.floor((base.height - 400) / 2),
width: 400,
opacity: 0.25,
});
await base.text('BROUILLON', {x: 300, y: base.height - 200, size: 64});
const path = await base.save('protege.png');
await bot.sendPhoto(path);
6 — Créer un certificat depuis un fond vide
Vous voulez générer un certificat de complétion personnalisé sans modèle de base ? Partez d'un fond blanc créé via Image.blank().
logo_url = Props.get('logo_url')
tampon_url = Props.get('tampon_url')
# Fond blanc 1200x800
base = Image.blank(1200, 800, color='#FFFFFF')
# Logo en haut, centre
logo = Image.open(logo_url)
Image.paste(base, logo, x=500, y=40, width=200)
# Textes
Image.text(base, 'CERTIFICAT DE FORMATION', x=270, y=260, size=42, color='#1a1a2e')
Image.text(base, 'Decerne a', x=520, y=360, size=22, color='#555')
Image.text(base, message['from']['first_name'], x=440, y=420, size=36, color='#000')
Image.text(base, 'Pour avoir complete la formation BotStrike Pro',
x=300, y=520, size=18, color='#333')
Image.text(base, 'Le 22/04/2026', x=500, y=620, size=16, color='#666')
# Tampon de l'organisation en bas Ă droite
tampon = Image.open(tampon_url)
Image.paste(base, tampon, x=900, y=600, width=200, opacity=0.85)
path = Image.save(base, 'certificat.png')
bot.sendPhoto(path, '🏆 Voici votre certificat !')
const logoUrl = await Props.get('logo_url');
const tamponUrl = await Props.get('tampon_url');
const base = await Image.blank(1200, 800, '#FFFFFF');
const logo = await Image.open(logoUrl);
await base.paste(logo, {x: 500, y: 40, width: 200});
await base.text('CERTIFICAT DE FORMATION', {x: 270, y: 260, size: 64});
await base.text('Decerne a', {x: 520, y: 360, size: 32});
await base.text(message.from.first_name, {x: 440, y: 420, size: 64});
await base.text('Pour avoir complete la formation BotStrike Pro',
{x: 300, y: 520, size: 32});
await base.text('Le 22/04/2026', {x: 500, y: 620, size: 32});
const tampon = await Image.open(tamponUrl);
await base.paste(tampon, {x: 900, y: 600, width: 200, opacity: 0.85});
const path = await base.save('certificat.png');
await bot.sendPhoto(path, '🏆 Voici votre certificat !');
7 — Configuration : commandes /settampon et /setsignature
L'admin enregistre une seule fois l'URL HTTPS de son tampon et de sa signature. Le bot vérifie le format et stocke dans Props.
# Commande : /settampon https://i.imgur.com/stamp.png
url = (args or '').strip()
if not url.startswith('https://'):
bot.sendMessage('❌ URL invalide. Fournissez un lien HTTPS.')
return
if not any(url.lower().endswith(ext) for ext in ['.png', '.jpg', '.jpeg', '.webp']):
bot.sendMessage('⚠️ Format non géré. Utilisez PNG (recommandé pour la transparence).')
return
Props.set('tampon_url', url)
bot.sendMessage('✅ Tampon enregistré !\n\nTestez avec /tamponner en envoyant le lien d\'un document.')
8 — Où héberger les images ?
- imgbb.com — Glisser-déposer, lien direct immédiat
- imgur.com — Copier l'URL avec extension
.pngà la fin - postimages.org — Pas de compte requis
.png, .jpg, …). Pas vers une page de galerie. Test : ouvrez l'URL en navigation privée — vous devez voir l'image seule, sans interface autour.9 — Préparer un PNG transparent pour signature/tampon
Pour qu'un tampon ou une signature s'intègre parfaitement (sans rectangle blanc autour), il faut un PNG avec fond transparent. Trois options :
- remove.bg — Glissez votre photo de signature/tampon → fond auto-supprimé → téléchargez le PNG.
- photopea.com — Photoshop gratuit dans le navigateur. Outil "baguette magique" pour effacer le blanc.
- Pour une signature manuscrite : signez sur papier blanc, photographiez, puis passez le résultat dans remove.bg.
Une fois le PNG transparent obtenu, hébergez-le et fournissez l'URL au bot.
10 — Bonnes pratiques
- âś… Toujours utiliser des PNG transparents pour les overlays (signature, tampon, logo).
- âś… Stocker toutes les URLs dans
Propspour pouvoir les changer sans redéployer. - ✅ Tester les coordonnées sur une image avant de figer le code (dépend de la résolution).
- âś… Utiliser
base.widthetbase.heightpour positionner relativement (en bas à droite, centre…). - ⚠️ Légal : apposer une signature scannée n'a pas la valeur d'une signature électronique qualifiée (eIDAS). Pour des contrats engageants, utilisez DocuSign / Yousign via leur API.
11 — Erreurs fréquentes
| Erreur | Cause | Solution |
|---|---|---|
| Fond blanc autour du tampon | PNG sans canal alpha | Refaire le PNG sur remove.bg |
| Tampon trop grand/petit | Paramètre width inadapté | Ajuster en pixels (200 = signature classique pour image 1200px) |
cannot identify image file | URL pointe vers HTML, pas image | Tester l'URL en navigation privée — image seule attendue |
| Erreur de téléchargement | URL HTTP au lieu de HTTPS | Toujours utiliser https:// |
| Tampon invisible | Coordonnées hors image | Vérifier que x < base.width et y < base.height |
| Image vide en JS | Oubli de await | Toujours await base.save(...) |