Add Activepieces integration for workflow automation

- Add Activepieces fork with SmoothSchedule custom piece
- Create integrations app with Activepieces service layer
- Add embed token endpoint for iframe integration
- Create Automations page with embedded workflow builder
- Add sidebar visibility fix for embed mode
- Add list inactive customers endpoint to Public API
- Include SmoothSchedule triggers: event created/updated/cancelled
- Include SmoothSchedule actions: create/update/cancel events, list resources/services/customers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
poduck
2025-12-18 22:59:37 -05:00
parent 9848268d34
commit 3aa7199503
16292 changed files with 1284892 additions and 4708 deletions

View File

@@ -0,0 +1,18 @@
{
"extends": ["../../../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}

View File

@@ -0,0 +1,7 @@
# pieces-bluesky
This library was generated with [Nx](https://nx.dev).
## Building
Run `nx build pieces-bluesky` to build the library.

View File

@@ -0,0 +1,11 @@
{
"name": "@activepieces/piece-bluesky",
"version": "0.0.8",
"type": "commonjs",
"main": "./src/index.js",
"types": "./src/index.d.ts",
"dependencies": {
"tslib": "^2.3.0",
"@atproto/api": "0.16.0"
}
}

View File

@@ -0,0 +1,65 @@
{
"name": "pieces-bluesky",
"$schema": "../../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "packages/pieces/community/bluesky/src",
"projectType": "library",
"release": {
"version": {
"manifestRootsToUpdate": [
"dist/{projectRoot}"
],
"currentVersionResolver": "git-tag",
"fallbackCurrentVersionResolver": "disk"
}
},
"tags": [],
"targets": {
"build": {
"executor": "@nx/js:tsc",
"outputs": [
"{options.outputPath}"
],
"options": {
"outputPath": "dist/packages/pieces/community/bluesky",
"tsConfig": "packages/pieces/community/bluesky/tsconfig.lib.json",
"packageJson": "packages/pieces/community/bluesky/package.json",
"main": "packages/pieces/community/bluesky/src/index.ts",
"assets": [
"packages/pieces/community/bluesky/*.md",
{
"input": "packages/pieces/community/bluesky/src/i18n",
"output": "./src/i18n",
"glob": "**/!(i18n.json)"
}
],
"buildableProjectDepsInPackageJsonType": "dependencies",
"updateBuildableProjectDepsInPackageJson": true
},
"dependsOn": [
"^build",
"prebuild"
]
},
"nx-release-publish": {
"options": {
"packageRoot": "dist/{projectRoot}"
}
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": [
"{options.outputFile}"
]
},
"prebuild": {
"executor": "nx:run-commands",
"options": {
"cwd": "packages/pieces/community/bluesky",
"command": "bun install --no-save --silent"
},
"dependsOn": [
"^build"
]
}
}
}

View File

@@ -0,0 +1,136 @@
{
"PDS Host": "PDS-Host",
"Identifier": "Identifier",
"Password": "Kennwort",
"The Personal Data Server host. Leave empty for default Bluesky network (https://bsky.social)": "Der Personal Data Server Host. Leer lassen für das Standard Bluesky Netzwerk (https://bsky.social)",
"Your Bluesky handle or email address": "Ihr Bluesky Handle oder Ihre E-Mail-Adresse",
"Your Bluesky account password or app password": "Ihr Bluesky Konto-Passwort oder App-Passwort",
"\nTo authenticate with Bluesky:\n\n1. **PDS Host**: The Personal Data Server host. Leave empty to use the default Bluesky network (https://bsky.social).\n2. **Identifier**: Your Bluesky handle (e.g., yourhandle.bsky.social) or email address.\n3. **Password**: Your Bluesky account password or app password.\n\nFor enhanced security, consider using an app password from your Bluesky account settings.\n": "\nZur Authentifizierung mit Bluesky:\n\n1. **PDS Host**: Der Personal Data Server Host. Leer lassen um das Standard Bluesky Netzwerk (https://bsky.social) zu verwenden.\n2. **Identifier**: Ihr Bluesky Handle (z. yourhandle.bsky.social) oder E-Mail-Adresse.\n3. **Passwort**: Ihr Bluesky Konto-Passwort oder App-Passwort.\n\nUm die Sicherheit zu verbessern, sollten Sie ein App-Passwort aus Ihren Bluesky-Kontoeinstellungen verwenden.\n",
"Create Post": "Beitrag erstellen",
"Like Post": "Like Beitrag",
"Repost Post": "Beitrag wiederholen",
"Find Post": "Post finden",
"Find Thread": "Thread finden",
"Create a new post on Bluesky": "Erstelle einen neuen Beitrag auf Bluesky",
"Like a post on Bluesky": "Gefällt mir einen Beitrag auf Bluesky",
"Share someone else's post to your timeline": "Teile jemandes Beitrag auf deiner Zeitleiste",
"Get detailed information about a specific post": "Erhalte detaillierte Informationen zu einem bestimmten Beitrag",
"Get a full conversation thread with replies": "Erhalte einen vollständigen Thread mit Antworten",
"Post Type": "Beitragstyp",
"Post Text": "Beitragstext",
"Post Language": "Post-Sprache",
"Image URLs": "Bild-URLs",
"Image Descriptions": "Bildbeschreibungen",
"Video URL": "Video-URL",
"Video Description": "Videobeschreibung",
"Video Captions": "Videounterlagen",
"Link to Share": "Link zum Teilen",
"Reply to Post": "Auf Beitrag antworten",
"Thread Posts": "Thread-Beiträge",
"Hashtags": "Hashtags",
"Content Warnings": "Inhaltswarnungen",
"Audience": "Zielgruppe",
"Select Method": "Methode auswählen",
"Select Post": "Beitrag auswählen",
"Post URL": "Post-URL",
"Thread Depth": "Thread-Tiefe",
"Parent Height": "Übergeordnete Höhe",
"Type of content you're sharing": "Art der Inhalte, die Sie teilen",
"What do you want to post? (Max 300 characters)": "Was möchtest du posten? (max. 300 Zeichen)",
"Language of your post": "Sprache Ihres Beitrags",
"Add up to 4 images by URL": "Bis zu 4 Bilder nach URL hinzufügen",
"Describe each image for accessibility": "Beschreiben Sie jedes Bild für Barrierefreiheit",
"Link to video file (MP4, max 100MB)": "Link zur Videodatei (MP4, max 100MB)",
"Describe the video for accessibility": "Beschreiben Sie das Video für Barrierefreiheit",
"Caption file URLs (optional)": "Untertitel-Datei-URLs (optional)",
"URL to share with your post": "URL zum Teilen mit Ihrem Beitrag",
"URL of post to reply to": "URL des zu antwortenden Beitrags",
"Create additional connected posts": "Erstelle weitere verbundene Beiträge",
"Add hashtags (e.g., tech,bluesky)": "Hashtags hinzufügen (z. B. tech,bluesky)",
"Add warnings for sensitive content": "Warnungen für vertrauliche Inhalte hinzufügen",
"Who can see this post": "Wer kann diesen Beitrag sehen",
"How to choose the post": "Wie man den Beitrag auswählt",
"Choose from your recent timeline posts (only when \"From my timeline\" is selected above)": "Wählen Sie aus Ihren letzten Timeline-Beiträgen (nur wenn \"Aus meiner Timeline\" oben ausgewählt ist)",
"Paste the Bluesky post URL": "Die Bluesky Post URL einfügen",
"Paste the Bluesky post URL (e.g., https://bsky.app/profile/username.bsky.social/post/xxx)": "Füge die Bluesky Post-URL ein (z.B. https://bsky.app/profile/username.bsky.social/post/xxx)",
"How many levels deep to retrieve replies": "Wie viele Level tief um Antworten zu erhalten",
"How many parent posts to retrieve": "Wie viele übergeordnete Beiträge abrufen sollen",
"Text Post": "Text-Beitrag",
"Photo Post": "Foto-Beitrag",
"Link Share": "Link teilen",
"Reply": "Antwort",
"Repost with Comment": "Reposten mit Kommentar",
"English": "Englisch",
"Spanish": "Spanisch",
"French": "Französisch",
"German": "Deutsch",
"Italian": "Italienisch",
"Portuguese": "Portugiesisch",
"Japanese": "Japanisch",
"Korean": "Koreanisch",
"Chinese": "Chinesisch",
"Russian": "Russisch",
"Arabic": "Arabisch",
"Hindi": "Hannah",
"Dutch": "Niederländisch",
"Swedish": "Schwedisch",
"Other": "Andere",
"Adult Content": "Erwachseneninhalt",
"Graphic Content": "Grafikinhalt",
"Sensitive Topic": "Sensitives Thema",
"Violence": "Gewalt",
"Spam/Promotional": "Spam/Werbung",
"Everyone (Public)": "Alle (öffentlich)",
"Followers only": "Nur Follower",
"Private/Unlisted": "Privat/Nicht gelistet",
"From my timeline": "Aus meiner Zeitleiste",
"Enter URL manually": "URL manuell eingeben",
"1 level": "1 Level",
"2 levels": "2 Stufen",
"3 levels": "3 Stufen",
"5 levels": "5 Stufen",
"10 levels": "10 Level",
"20 levels": "20 Level",
"50 levels": "50 Level",
"100 levels (max)": "100 Level (max)",
"No parents": "Keine Eltern",
"1 parent": "1 Elternteil",
"2 parents": "2 Eltern",
"3 parents": "3 Eltern",
"5 parents": "5 Eltern",
"10 parents": "10 Eltern",
"20 parents": "20 Eltern",
"All parents (80 max)": "Alle Eltern (80 max)",
"New Posts by Author": "Neue Beiträge vom Autor",
"New Follower on Account": "Neuer Follower auf Konto",
"New Timeline Posts": "Neue Timeline-Beiträge",
"New Post (with Search Options)": "Neuer Beitrag (mit Suchoptionen)",
"Triggers when a selected author creates a new post": "Löst aus, wenn ein ausgewählter Autor einen neuen Beitrag erstellt",
"Triggers when someone new follows your Bluesky account": "Löst aus, wenn jemand neu deinem Bluesky-Konto folgt",
"Triggers when new posts appear in your timeline": "Auslöser wenn neue Beiträge in der Zeitleiste erscheinen",
"Triggers when posts match your search criteria": "Löst aus, wenn Beiträge Ihren Suchkriterien entsprechen",
"How to select author?": "Wie wählt man den Autor aus?",
"Select Author": "Autor auswählen",
"Author Handle": "Autoren-Handle",
"Include Replies": "Antworten einbeziehen",
"Include Reposts": "Reposts einbeziehen",
"Search Query": "Suchanfrage",
"Language Filter": "Sprachfilter",
"Filter by Images": "Nach Bildern filtern",
"Filter by Videos": "Nach Videos filtern",
"Sort Order": "Sortierung",
"Choose how to select the author": "Wählen Sie, wie der Autor ausgewählt wird",
"Choose from accounts you follow": "Wählen Sie aus Konten, denen Sie folgen",
"Enter the Bluesky username (e.g., username.bsky.social)": "Geben Sie den Bluesky Benutzernamen ein (z. B. username.bsky.social)",
"Include reply posts by this author": "Antwortbeiträge dieses Autors einbeziehen",
"Include posts that this author reposted": "Einfügen von Beiträgen, die dieser Autor reposted",
"Keywords, hashtags (#example), or mentions (@handle) to find": "Stichwörter, Hashtags (#Beispiel) oder Erwähnungen (@handle) zu finden",
"Filter by language": "Nach Sprache filtern",
"Only posts with/without images": "Nur Beiträge mit/ohne Bilder",
"Only posts with/without videos": "Nur Beiträge mit/ohne Videos",
"How to sort results": "Ergebnisse sortieren",
"From my following list": "Von meiner folgenden Liste",
"Enter handle manually": "Handle manuell eingeben",
"Latest First": "Neueste zuerst",
"Most Popular": "Beliebteste"
}

View File

@@ -0,0 +1,136 @@
{
"PDS Host": "Host PDS",
"Identifier": "Identifier",
"Password": "Contraseña",
"The Personal Data Server host. Leave empty for default Bluesky network (https://bsky.social)": "El servidor de datos personales. Dejar vacío para la red Bluesky predeterminada (https://bsky.social)",
"Your Bluesky handle or email address": "Su nombre de usuario o dirección de correo electrónico Bluesky",
"Your Bluesky account password or app password": "Su contraseña de cuenta Bluesky o su contraseña de aplicación",
"\nTo authenticate with Bluesky:\n\n1. **PDS Host**: The Personal Data Server host. Leave empty to use the default Bluesky network (https://bsky.social).\n2. **Identifier**: Your Bluesky handle (e.g., yourhandle.bsky.social) or email address.\n3. **Password**: Your Bluesky account password or app password.\n\nFor enhanced security, consider using an app password from your Bluesky account settings.\n": "\nTo authenticate with Bluesky:\n\n1. **PDS Host**: The Personal Data Server host. Leave empty to use the default Bluesky network (https://bsky.social).\n2. **Identifier**: Your Bluesky handle (e.g., yourhandle.bsky.social) or email address.\n3. **Password**: Your Bluesky account password or app password.\n\nFor enhanced security, consider using an app password from your Bluesky account settings.\n",
"Create Post": "Crear publicación",
"Like Post": "Mensaje Me Gusta",
"Repost Post": "Repostar post",
"Find Post": "Buscar post",
"Find Thread": "Buscar hilo",
"Create a new post on Bluesky": "Crear una nueva publicación en Bluesky",
"Like a post on Bluesky": "Como un post en Bluesky",
"Share someone else's post to your timeline": "Comparte la publicación de otra persona en tu línea de tiempo",
"Get detailed information about a specific post": "Obtener información detallada sobre un post específico",
"Get a full conversation thread with replies": "Obtén un hilo de conversación completo con respuestas",
"Post Type": "Tipo de Post",
"Post Text": "Publicar texto",
"Post Language": "Idioma del post",
"Image URLs": "URLs de imagen",
"Image Descriptions": "Descripciones de imagen",
"Video URL": "URL del vídeo",
"Video Description": "Descripción del vídeo",
"Video Captions": "Subtítulos de vídeo",
"Link to Share": "Enlace para compartir",
"Reply to Post": "Responder al mensaje",
"Thread Posts": "Mensajes del hilo",
"Hashtags": "Etiquetas",
"Content Warnings": "Advertencias de contenido",
"Audience": "Público",
"Select Method": "Seleccionar método",
"Select Post": "Seleccionar Post",
"Post URL": "Publicar URL",
"Thread Depth": "Profundidad del hilo",
"Parent Height": "Altura padre",
"Type of content you're sharing": "Tipo de contenido que estás compartiendo",
"What do you want to post? (Max 300 characters)": "¿Qué quieres publicar? (máximo 300 caracteres)",
"Language of your post": "Idioma de tu publicación",
"Add up to 4 images by URL": "Añadir hasta 4 imágenes por URL",
"Describe each image for accessibility": "Describa cada imagen para su accesibilidad",
"Link to video file (MP4, max 100MB)": "Enlace al archivo de vídeo (MP4, max 100MB)",
"Describe the video for accessibility": "Describa el vídeo para su accesibilidad",
"Caption file URLs (optional)": "URL del archivo de subtítulos (opcional)",
"URL to share with your post": "URL para compartir con tu publicación",
"URL of post to reply to": "URL del mensaje al que responder",
"Create additional connected posts": "Crear mensajes adicionales conectados",
"Add hashtags (e.g., tech,bluesky)": "Añadir hashtags (por ej., tech,bluesky)",
"Add warnings for sensitive content": "Añadir advertencias para contenido sensible",
"Who can see this post": "Quién puede ver esta publicación",
"How to choose the post": "Cómo elegir el post",
"Choose from your recent timeline posts (only when \"From my timeline\" is selected above)": "Elija entre sus publicaciones recientes de la línea de tiempo (sólo cuando \"De mi línea de tiempo\" está seleccionado arriba)",
"Paste the Bluesky post URL": "Pegar la URL del post Bluesky",
"Paste the Bluesky post URL (e.g., https://bsky.app/profile/username.bsky.social/post/xxx)": "Pegar la URL del post Bluesky (por ejemplo, https://bsky.app/profile/username.bsky.social/post/xxx)",
"How many levels deep to retrieve replies": "Cuántos niveles hay para recuperar respuestas",
"How many parent posts to retrieve": "Cuántos mensajes padre recuperar",
"Text Post": "Mensaje de texto",
"Photo Post": "Post de foto",
"Link Share": "Compartir Enlace",
"Reply": "Responder",
"Repost with Comment": "Republicar con comentario",
"English": "Inglés",
"Spanish": "Español",
"French": "Francés",
"German": "Alemán",
"Italian": "Italiano",
"Portuguese": "Portugués",
"Japanese": "Japonés",
"Korean": "Coreano",
"Chinese": "Chino",
"Russian": "Ruso",
"Arabic": "Árabe",
"Hindi": "Hindú",
"Dutch": "Holandés",
"Swedish": "Sueco",
"Other": "Otro",
"Adult Content": "Contenido adulto",
"Graphic Content": "Contenido gráfico",
"Sensitive Topic": "Tema sensible",
"Violence": "Violencia",
"Spam/Promotional": "Spam/Promocional",
"Everyone (Public)": "Todo el mundo (público)",
"Followers only": "Sólo seguidores",
"Private/Unlisted": "Privado/No listado",
"From my timeline": "Desde mi línea de tiempo",
"Enter URL manually": "Introducir URL manualmente",
"1 level": "1 nivel",
"2 levels": "2 niveles",
"3 levels": "3 niveles",
"5 levels": "5 niveles",
"10 levels": "10 niveles",
"20 levels": "20 niveles",
"50 levels": "50 niveles",
"100 levels (max)": "100 niveles (máximo)",
"No parents": "Sin padres",
"1 parent": "1 padre",
"2 parents": "2 padres",
"3 parents": "3 padres",
"5 parents": "5 padres",
"10 parents": "10 padres",
"20 parents": "20 padres",
"All parents (80 max)": "Todos los padres (80 máx.)",
"New Posts by Author": "Nuevos posts por autor",
"New Follower on Account": "Nuevo seguidor en la cuenta",
"New Timeline Posts": "Nuevos mensajes del cronograma",
"New Post (with Search Options)": "Nuevo mensaje (con opciones de búsqueda)",
"Triggers when a selected author creates a new post": "Dispara cuando un autor seleccionado crea una nueva publicación",
"Triggers when someone new follows your Bluesky account": "Dispara cuando alguien nuevo sigue tu cuenta Bluesky",
"Triggers when new posts appear in your timeline": "Dispara cuando los nuevos mensajes aparecen en tu línea de tiempo",
"Triggers when posts match your search criteria": "Dispara cuando los mensajes coincidan con tus criterios de búsqueda",
"How to select author?": "¿Cómo seleccionar el autor?",
"Select Author": "Seleccionar autor",
"Author Handle": "Manejo del autor",
"Include Replies": "Incluye respuestas",
"Include Reposts": "Incluye reposts",
"Search Query": "Buscar consulta",
"Language Filter": "Filtro de idioma",
"Filter by Images": "Filtrar por imágenes",
"Filter by Videos": "Filtrar por Videos",
"Sort Order": "Ordenar",
"Choose how to select the author": "Elija cómo seleccionar el autor",
"Choose from accounts you follow": "Elige entre las cuentas que sigues",
"Enter the Bluesky username (e.g., username.bsky.social)": "Introduzca el nombre de usuario de Bluesky (por ejemplo, username.bsky.social)",
"Include reply posts by this author": "Incluye respuestas de este autor",
"Include posts that this author reposted": "Incluye mensajes que este autor ha vuelto a publicar",
"Keywords, hashtags (#example), or mentions (@handle) to find": "Palabras clave, hashtags (#ejemplo), o menciones (@handle) a encontrar",
"Filter by language": "Filtrar por idioma",
"Only posts with/without images": "Sólo publicaciones con/sin imágenes",
"Only posts with/without videos": "Sólo publicaciones con/sin vídeos",
"How to sort results": "Cómo ordenar los resultados",
"From my following list": "De mi siguiente lista",
"Enter handle manually": "Introducir handle manualmente",
"Latest First": "Último primero",
"Most Popular": "Más Popular"
}

View File

@@ -0,0 +1,136 @@
{
"PDS Host": "Hôte PDS",
"Identifier": "Identifier",
"Password": "Password",
"The Personal Data Server host. Leave empty for default Bluesky network (https://bsky.social)": "L'hôte du serveur de données personnelles. Laissez vide pour le réseau Bluesky par défaut (https://bsky.social)",
"Your Bluesky handle or email address": "Votre identifiant Bluesky ou votre adresse e-mail",
"Your Bluesky account password or app password": "Mot de passe de votre compte Bluesky ou mot de passe de l'application",
"\nTo authenticate with Bluesky:\n\n1. **PDS Host**: The Personal Data Server host. Leave empty to use the default Bluesky network (https://bsky.social).\n2. **Identifier**: Your Bluesky handle (e.g., yourhandle.bsky.social) or email address.\n3. **Password**: Your Bluesky account password or app password.\n\nFor enhanced security, consider using an app password from your Bluesky account settings.\n": "\nTo authenticate with Bluesky:\n\n1. **PDS Host**: The Personal Data Server host. Leave empty to use the default Bluesky network (https://bsky.social).\n2. **Identifier**: Your Bluesky handle (e.g., yourhandle.bsky.social) or email address.\n3. **Password**: Your Bluesky account password or app password.\n\nFor enhanced security, consider using an app password from your Bluesky account settings.\n",
"Create Post": "Créer un message",
"Like Post": "J'aime la publication",
"Repost Post": "Republier le post",
"Find Post": "Rechercher une publication",
"Find Thread": "Trouver un fil de discussion",
"Create a new post on Bluesky": "Créer un nouveau message sur Bluesky",
"Like a post on Bluesky": "Aimer un message sur Bluesky",
"Share someone else's post to your timeline": "Partagez le message de quelqu'un d'autre sur votre chronologie",
"Get detailed information about a specific post": "Obtenir des informations détaillées sur un message spécifique",
"Get a full conversation thread with replies": "Obtenir un fil de conversation complet avec des réponses",
"Post Type": "Type de publication",
"Post Text": "Poster le texte",
"Post Language": "Langue de la publication",
"Image URLs": "URL de l'image",
"Image Descriptions": "Descriptions de l'image",
"Video URL": "URL de la vidéo",
"Video Description": "Description de la vidéo",
"Video Captions": "Légendes vidéo",
"Link to Share": "Lien pour partager",
"Reply to Post": "Répondre au message",
"Thread Posts": "Messages de discussion",
"Hashtags": "Hashtags",
"Content Warnings": "Avertissements de contenu",
"Audience": "Audience",
"Select Method": "Sélectionnez la méthode",
"Select Post": "Sélectionner une publication",
"Post URL": "URL de la publication",
"Thread Depth": "Profondeur du fil",
"Parent Height": "Hauteur du parent",
"Type of content you're sharing": "Type de contenu que vous partagez",
"What do you want to post? (Max 300 characters)": "Que voulez-vous poster ? (Max 300 caractères)",
"Language of your post": "Langue de votre publication",
"Add up to 4 images by URL": "Ajouter jusqu'à 4 images par URL",
"Describe each image for accessibility": "Décrire chaque image pour l'accessibilité",
"Link to video file (MP4, max 100MB)": "Lien vers un fichier vidéo (MP4, max 100MB)",
"Describe the video for accessibility": "Décrire la vidéo pour l'accessibilité",
"Caption file URLs (optional)": "URL du fichier de légende (facultatif)",
"URL to share with your post": "URL à partager avec votre message",
"URL of post to reply to": "URL du message à répondre à",
"Create additional connected posts": "Créer des publications connectées supplémentaires",
"Add hashtags (e.g., tech,bluesky)": "Ajouter des hashtags (par exemple, tech, bluesky)",
"Add warnings for sensitive content": "Ajouter des avertissements pour le contenu sensible",
"Who can see this post": "Qui peut voir ce message",
"How to choose the post": "Comment choisir la publication",
"Choose from your recent timeline posts (only when \"From my timeline\" is selected above)": "Choisissez parmi vos derniers messages de la chronologie (uniquement lorsque \"À partir de ma chronologie\" est sélectionné ci-dessus)",
"Paste the Bluesky post URL": "Coller l'URL du message Bluesky",
"Paste the Bluesky post URL (e.g., https://bsky.app/profile/username.bsky.social/post/xxx)": "Collez l'URL du message Bluesky (par exemple, https://bsky.app/profile/username.bsky.social/post/xxx)",
"How many levels deep to retrieve replies": "Combien de niveaux de profondeur pour récupérer les réponses",
"How many parent posts to retrieve": "Combien de messages parents récupérer",
"Text Post": "Texte de la publication",
"Photo Post": "Publication de photo",
"Link Share": "Partager le lien",
"Reply": "Répondre",
"Repost with Comment": "Republier avec un commentaire",
"English": "Anglais",
"Spanish": "Espagnol",
"French": "Français",
"German": "Allemand",
"Italian": "Italien",
"Portuguese": "Portugais",
"Japanese": "Japonais",
"Korean": "Coréen",
"Chinese": "Chinois",
"Russian": "Russe",
"Arabic": "Arabe",
"Hindi": "Hindi",
"Dutch": "Néerlandais",
"Swedish": "Suédois",
"Other": "Autres",
"Adult Content": "Contenu adulte",
"Graphic Content": "Contenu Graphique",
"Sensitive Topic": "Sujet sensible",
"Violence": "Violence",
"Spam/Promotional": "Spam/Promotionnel",
"Everyone (Public)": "Tout le monde (public)",
"Followers only": "Abonnés seulement",
"Private/Unlisted": "Privé/Non listé",
"From my timeline": "Depuis ma chronologie",
"Enter URL manually": "Entrez l'URL manuellement",
"1 level": "1 niveau",
"2 levels": "2 niveaux",
"3 levels": "3 niveaux",
"5 levels": "5 niveaux",
"10 levels": "10 niveaux",
"20 levels": "20 niveaux",
"50 levels": "50 niveaux",
"100 levels (max)": "100 niveaux (max)",
"No parents": "Aucun parent",
"1 parent": "1 parent",
"2 parents": "2 parents",
"3 parents": "3 parents",
"5 parents": "5 parents",
"10 parents": "10 parents",
"20 parents": "20 parents",
"All parents (80 max)": "Tous les parents (80 max)",
"New Posts by Author": "Nouveaux messages par auteur",
"New Follower on Account": "Nouveau Suiveur sur le Compte",
"New Timeline Posts": "Nouvelles publications de la timeline",
"New Post (with Search Options)": "Nouvelle publication (avec options de recherche)",
"Triggers when a selected author creates a new post": "Déclenche lorsqu'un auteur sélectionné crée un nouveau message",
"Triggers when someone new follows your Bluesky account": "Déclenche quand quelqu'un de nouveau suit votre compte Bluesky",
"Triggers when new posts appear in your timeline": "Déclenche quand de nouveaux messages apparaissent dans votre chronologie",
"Triggers when posts match your search criteria": "Déclenche lorsque les messages correspondent à vos critères de recherche",
"How to select author?": "Comment sélectionner un auteur?",
"Select Author": "Sélectionner l'auteur",
"Author Handle": "Descripteur de l'auteur",
"Include Replies": "Inclure les réponses",
"Include Reposts": "Inclure les republications",
"Search Query": "Requête de recherche",
"Language Filter": "Filtre de langue",
"Filter by Images": "Filtrer par Images",
"Filter by Videos": "Filtrer par Vidéos",
"Sort Order": "Ordre de tri",
"Choose how to select the author": "Choisir comment sélectionner l'auteur",
"Choose from accounts you follow": "Choisissez parmi les comptes que vous suivez",
"Enter the Bluesky username (e.g., username.bsky.social)": "Entrez le nom d'utilisateur Bluesky (par exemple, username.bsky.social)",
"Include reply posts by this author": "Inclure les messages de réponse de cet auteur",
"Include posts that this author reposted": "Inclure les messages que cet auteur a publiés",
"Keywords, hashtags (#example), or mentions (@handle) to find": "Mots-clés, hashtags (#exemple), ou mentions (@handle) pour trouver",
"Filter by language": "Filtrer par langue",
"Only posts with/without images": "Seulement les messages avec/sans images",
"Only posts with/without videos": "Seulement les messages avec/sans vidéos",
"How to sort results": "Comment trier les résultats",
"From my following list": "De ma liste suivante",
"Enter handle manually": "Saisir manuellement le gestionnaire",
"Latest First": "Dernier en premier",
"Most Popular": "Les plus populaires"
}

View File

@@ -0,0 +1,136 @@
{
"PDS Host": "PDS ホスト",
"Identifier": "Identifier",
"Password": "Password",
"The Personal Data Server host. Leave empty for default Bluesky network (https://bsky.social)": "個人データサーバーのホスト。デフォルトのBlueskyネットワーク(https://bsky.social)の場合は空のままにしてください。",
"Your Bluesky handle or email address": "Blueskyハンドルまたはメールアドレス",
"Your Bluesky account password or app password": "Blueskyアカウントのパスワードまたはアプリのパスワード",
"\nTo authenticate with Bluesky:\n\n1. **PDS Host**: The Personal Data Server host. Leave empty to use the default Bluesky network (https://bsky.social).\n2. **Identifier**: Your Bluesky handle (e.g., yourhandle.bsky.social) or email address.\n3. **Password**: Your Bluesky account password or app password.\n\nFor enhanced security, consider using an app password from your Bluesky account settings.\n": "\nTo authenticate with Bluesky:\n\n1. **PDS Host**: The Personal Data Server host. Leave empty to use the default Bluesky network (https://bsky.social).\n2. **Identifier**: Your Bluesky handle (e.g., yourhandle.bsky.social) or email address.\n3. **Password**: Your Bluesky account password or app password.\n\nFor enhanced security, consider using an app password from your Bluesky account settings.\n",
"Create Post": "投稿を作成",
"Like Post": "投稿にいいね!",
"Repost Post": "Repost Post",
"Find Post": "投稿を検索",
"Find Thread": "スレッドを検索",
"Create a new post on Bluesky": "Blueskyに新しい投稿を作成",
"Like a post on Bluesky": "Blueskyの投稿のように",
"Share someone else's post to your timeline": "自分のタイムラインに他の人の投稿をシェアする",
"Get detailed information about a specific post": "特定の投稿に関する詳細情報を取得する",
"Get a full conversation thread with replies": "返信付きの完全な会話スレッドを取得",
"Post Type": "投稿の種類",
"Post Text": "投稿テキスト",
"Post Language": "投稿の言語",
"Image URLs": "画像のURL",
"Image Descriptions": "画像の説明",
"Video URL": "動画の URL",
"Video Description": "ビデオの説明",
"Video Captions": "ビデオの図表番号",
"Link to Share": "共有へのリンク",
"Reply to Post": "投稿に返信",
"Thread Posts": "スレッドの投稿",
"Hashtags": "ハッシュタグ",
"Content Warnings": "コンテンツ警告",
"Audience": "オーディエンス:",
"Select Method": "メソッドを選択",
"Select Post": "投稿を選択",
"Post URL": "投稿URL",
"Thread Depth": "スレッドの深さ",
"Parent Height": "親の高さ",
"Type of content you're sharing": "共有しているコンテンツの種類",
"What do you want to post? (Max 300 characters)": "何を投稿しますか最大300文字",
"Language of your post": "あなたの投稿の言語",
"Add up to 4 images by URL": "URLで最大4枚の画像を追加",
"Describe each image for accessibility": "アクセシビリティのためにそれぞれの画像を説明してください",
"Link to video file (MP4, max 100MB)": "ビデオファイルへのリンク (MP4, max 100MB)",
"Describe the video for accessibility": "アクセシビリティのためのビデオを説明してください",
"Caption file URLs (optional)": "キャプションファイルのURL (任意)",
"URL to share with your post": "投稿と共有するURL",
"URL of post to reply to": "返信する投稿の URL",
"Create additional connected posts": "追加の接続された投稿を作成",
"Add hashtags (e.g., tech,bluesky)": "ハッシュタグを追加tech,bluesky",
"Add warnings for sensitive content": "機密コンテンツに警告を追加する",
"Who can see this post": "誰がこの投稿を閲覧できますか?",
"How to choose the post": "投稿の選択方法",
"Choose from your recent timeline posts (only when \"From my timeline\" is selected above)": "最近のタイムライン投稿から選択してください(上記「タイムラインから」が選択されている場合のみ)",
"Paste the Bluesky post URL": "Blueskyの投稿URLを貼り付け",
"Paste the Bluesky post URL (e.g., https://bsky.app/profile/username.bsky.social/post/xxx)": "Bluesky post URL (例: https://bsky.app/profile/username.bsky.social/post/xxx) を貼り付けてください",
"How many levels deep to retrieve replies": "返信を取得する深さのレベル",
"How many parent posts to retrieve": "取得する親ポストの数",
"Text Post": "テキスト投稿",
"Photo Post": "写真の投稿",
"Link Share": "リンクの共有",
"Reply": "返信",
"Repost with Comment": "コメント付きのrepost",
"English": "日本語",
"Spanish": "スペイン語",
"French": "フランス語",
"German": "ドイツ語",
"Italian": "イタリア語",
"Portuguese": "ポルトガル語",
"Japanese": "日本語",
"Korean": "Korean",
"Chinese": "中国語",
"Russian": "ロシア語",
"Arabic": "アラビア文字",
"Hindi": "ヒンディー語",
"Dutch": "オランダ語",
"Swedish": "スウェーデン語",
"Other": "その他",
"Adult Content": "アダルトコンテンツ",
"Graphic Content": "グラフィックコンテンツ",
"Sensitive Topic": "敏感なトピック",
"Violence": "暴力行為(暴力)",
"Spam/Promotional": "スパム/プロモーション",
"Everyone (Public)": "すべてのユーザー(公開)",
"Followers only": "フォロワーのみ",
"Private/Unlisted": "非公開/非公開にする",
"From my timeline": "タイムラインから",
"Enter URL manually": "URLを手動で入力",
"1 level": "1 レベル",
"2 levels": "2 レベル",
"3 levels": "3レベル",
"5 levels": "5レベル",
"10 levels": "10レベル",
"20 levels": "20レベル",
"50 levels": "50レベル",
"100 levels (max)": "100レベル最大",
"No parents": "保護者なし",
"1 parent": "1 つの親",
"2 parents": "2人の親",
"3 parents": "3人の親",
"5 parents": "5人の親",
"10 parents": "親10名",
"20 parents": "20人の親",
"All parents (80 max)": "すべての保護者最大80名",
"New Posts by Author": "投稿者による新規投稿",
"New Follower on Account": "アカウントの新しいフォロワー数",
"New Timeline Posts": "新しいタイムライン投稿",
"New Post (with Search Options)": "新規投稿 (検索オプション付き)",
"Triggers when a selected author creates a new post": "選択した著者が新しい投稿を作成したときにトリガーします",
"Triggers when someone new follows your Bluesky account": "新しい誰かがあなたのBlueskyアカウントをフォローしたときにトリガーします",
"Triggers when new posts appear in your timeline": "タイムラインに新しい投稿が表示されたときにトリガーします",
"Triggers when posts match your search criteria": "検索条件に一致する投稿をトリガーする",
"How to select author?": "著者を選択する方法は?",
"Select Author": "投稿者を選択",
"Author Handle": "著者のハンドル:",
"Include Replies": "返信を含める",
"Include Reposts": "リポジトリを含める",
"Search Query": "検索クエリ",
"Language Filter": "言語フィルタ",
"Filter by Images": "画像でフィルター",
"Filter by Videos": "動画でフィルター",
"Sort Order": "並び順",
"Choose how to select the author": "作成者を選択する方法を選択",
"Choose from accounts you follow": "フォロー中のアカウントから選択",
"Enter the Bluesky username (e.g., username.bsky.social)": "Blueskyユーザー名を入力してくださいusername.bsky.social",
"Include reply posts by this author": "この著者による返信投稿を含める",
"Include posts that this author reposted": "この投稿者が再投稿した投稿を含める",
"Keywords, hashtags (#example), or mentions (@handle) to find": "キーワード、ハッシュタグ (#example)、メンション(@handle) で検索",
"Filter by language": "言語でフィルター",
"Only posts with/without images": "画像の有無の投稿のみ",
"Only posts with/without videos": "動画の有無の投稿のみ",
"How to sort results": "結果を並べ替える方法",
"From my following list": "以下のリストから",
"Enter handle manually": "手動でハンドルを入力",
"Latest First": "最新の順",
"Most Popular": "最も人気"
}

View File

@@ -0,0 +1,136 @@
{
"PDS Host": "PDS host",
"Identifier": "Identifier",
"Password": "Wachtwoord",
"The Personal Data Server host. Leave empty for default Bluesky network (https://bsky.social)": "De persoonlijke gegevens server host. Laat leeg voor het standaard Bluesky netwerk (https://bsky.social)",
"Your Bluesky handle or email address": "Jouw Bluesky handle of e-mailadres",
"Your Bluesky account password or app password": "Uw Bluesky account wachtwoord of app-wachtwoord",
"\nTo authenticate with Bluesky:\n\n1. **PDS Host**: The Personal Data Server host. Leave empty to use the default Bluesky network (https://bsky.social).\n2. **Identifier**: Your Bluesky handle (e.g., yourhandle.bsky.social) or email address.\n3. **Password**: Your Bluesky account password or app password.\n\nFor enhanced security, consider using an app password from your Bluesky account settings.\n": "\nOm te verifiëren met Bluesky:\n\n1. **PDS Host**: De persoonlijke gegevens server host. Laat leeg om gebruik te maken van het standaard Bluesky netwerk (https://bsky.social).\n2. **Identificeren**: Uw Bluesky handgreep (bijv. uwhandle.bsky.social) of e-mailadres.\n3. **Wachtwoord**: Uw Bluesky account wachtwoord of app-wachtwoord.\n\nVoor verbeterde beveiliging, overweeg om een app-wachtwoord te gebruiken vanuit je Bluesky accountinstellingen.\n",
"Create Post": "Post aanmaken",
"Like Post": "Vind bericht leuk",
"Repost Post": "Bericht opnieuw plaatsen",
"Find Post": "Zoek bericht",
"Find Thread": "Discussie zoeken",
"Create a new post on Bluesky": "Maak een nieuw bericht op Bluesky",
"Like a post on Bluesky": "Als een bericht op Bluesky",
"Share someone else's post to your timeline": "Deel het bericht van iemand anders op je tijdlijn",
"Get detailed information about a specific post": "Krijg gedetailleerde informatie over een specifiek bericht",
"Get a full conversation thread with replies": "Krijg een volledig gesprek met reacties",
"Post Type": "Type post",
"Post Text": "Plaats tekst",
"Post Language": "Post taal",
"Image URLs": "Afbeelding URLs",
"Image Descriptions": "Afbeelding omschrijvingen",
"Video URL": "Video URL",
"Video Description": "Video beschrijving",
"Video Captions": "Video bijschriften",
"Link to Share": "Link naar delen",
"Reply to Post": "Reageer op bericht",
"Thread Posts": "Thread Berichten",
"Hashtags": "Hashtags",
"Content Warnings": "Waarschuwingen inhoud",
"Audience": "Auditie",
"Select Method": "Selecteer methode",
"Select Post": "Selecteer bericht",
"Post URL": "Post URL",
"Thread Depth": "Thread diepte",
"Parent Height": "Bovenliggende hoogte",
"Type of content you're sharing": "Soort inhoud die u deelt",
"What do you want to post? (Max 300 characters)": "Wat wil je doen? (Max 300 tekens)",
"Language of your post": "Taal van uw bericht",
"Add up to 4 images by URL": "Voeg tot 4 afbeeldingen via URL toe",
"Describe each image for accessibility": "Beschrijf elke afbeelding voor toegankelijkheid",
"Link to video file (MP4, max 100MB)": "Link naar videobestand (MP4, max 100MB)",
"Describe the video for accessibility": "Beschrijf de video voor toegankelijkheid",
"Caption file URLs (optional)": "URL's bijschrift (optioneel)",
"URL to share with your post": "URL om te delen met uw bericht",
"URL of post to reply to": "URL van bericht om te beantwoorden",
"Create additional connected posts": "Extra verbonden berichten aanmaken",
"Add hashtags (e.g., tech,bluesky)": "Voeg hashtags toe (bijv. tech,bluesky)",
"Add warnings for sensitive content": "Waarschuwingen voor gevoelige inhoud toevoegen",
"Who can see this post": "Wie kan dit bericht zien",
"How to choose the post": "Hoe het bericht te kiezen",
"Choose from your recent timeline posts (only when \"From my timeline\" is selected above)": "Kies uit je recente tijdlijnberichten (alleen wanneer \"Van mijn tijdlijn\" hierboven is geselecteerd)",
"Paste the Bluesky post URL": "Plak de Bluesky post URL",
"Paste the Bluesky post URL (e.g., https://bsky.app/profile/username.bsky.social/post/xxx)": "Plak de Bluesky post URL (bijv. https://bsky.app/profile/username.bsky.social/post/xxx)",
"How many levels deep to retrieve replies": "Hoeveel levels diep om reacties op te halen",
"How many parent posts to retrieve": "Hoeveel bovenliggende berichten op te halen",
"Text Post": "Tekst bericht",
"Photo Post": "Foto plaatsen",
"Link Share": "Deel koppeling",
"Reply": "Beantwoorden",
"Repost with Comment": "Herpost met reactie",
"English": "Nederlands",
"Spanish": "Spaans",
"French": "Frans",
"German": "Duits",
"Italian": "Italiaans",
"Portuguese": "Portugees",
"Japanese": "Afrikaans",
"Korean": "Koreaans",
"Chinese": "Chinees",
"Russian": "Russisch",
"Arabic": "Arabisch",
"Hindi": "Hindoestani",
"Dutch": "Nederlands",
"Swedish": "Zweeds",
"Other": "anders",
"Adult Content": "Inhoud Volwassenen",
"Graphic Content": "Grafische inhoud",
"Sensitive Topic": "Gevoelig onderwerp",
"Violence": "Geweld",
"Spam/Promotional": "Spam/Promotionele",
"Everyone (Public)": "Iedereen (Public)",
"Followers only": "Alleen volgers",
"Private/Unlisted": "Privé/niet-genoteerd",
"From my timeline": "Van mijn tijdlijn",
"Enter URL manually": "Voer handmatig een URL in",
"1 level": "1 niveau",
"2 levels": "2 niveaus",
"3 levels": "3 niveaus",
"5 levels": "5 niveaus",
"10 levels": "10 niveaus",
"20 levels": "20 niveaus",
"50 levels": "50 levels",
"100 levels (max)": "100 levels (max)",
"No parents": "Geen ouders",
"1 parent": "1 bovenliggende",
"2 parents": "2 ouders",
"3 parents": "3 ouders",
"5 parents": "5 ouders",
"10 parents": "10 ouders",
"20 parents": "20 ouders",
"All parents (80 max)": "Alle ouders (80 max)",
"New Posts by Author": "Nieuwe posts door auteur",
"New Follower on Account": "Nieuwe volger op account",
"New Timeline Posts": "Nieuwe tijdlijn posts",
"New Post (with Search Options)": "Nieuw bericht (met zoekopties)",
"Triggers when a selected author creates a new post": "Triggert wanneer een geselecteerde auteur een nieuw bericht maakt",
"Triggers when someone new follows your Bluesky account": "Triggert wanneer iemand nieuw jouw Bluesky account volgt",
"Triggers when new posts appear in your timeline": "Triggert wanneer nieuwe berichten op je tijdlijn verschijnen",
"Triggers when posts match your search criteria": "Activeert wanneer berichten voldoen aan uw zoekcriteria",
"How to select author?": "Hoe selecteert u auteur?",
"Select Author": "Selecteer auteur",
"Author Handle": "Auteur Handel",
"Include Replies": "Inclusief antwoorden",
"Include Reposts": "Inclusief Reposts",
"Search Query": "Zoek query",
"Language Filter": "Taal filter",
"Filter by Images": "Filter op afbeeldingen",
"Filter by Videos": "Filter op video's",
"Sort Order": "Sorteren bestelling",
"Choose how to select the author": "Kies hoe u de auteur wilt selecteren",
"Choose from accounts you follow": "Kies uit accounts die je volgt",
"Enter the Bluesky username (e.g., username.bsky.social)": "Vul de Bluesky gebruikersnaam in (bijv. username.bsky.social)",
"Include reply posts by this author": "Antwoordberichten van deze auteur toevoegen",
"Include posts that this author reposted": "Voeg berichten toe die deze auteur opnieuw heeft geplaatst",
"Keywords, hashtags (#example), or mentions (@handle) to find": "Trefwoorden, hashtags (#example), of noemt (@handle) om te vinden",
"Filter by language": "Filter op taal",
"Only posts with/without images": "posts alleen met/zonder afbeeldingen",
"Only posts with/without videos": "Alleen berichten met/zonder video's",
"How to sort results": "Hoe de resultaten te sorteren",
"From my following list": "Van mijn volgende lijst",
"Enter handle manually": "Handgreep handmatig invoeren",
"Latest First": "Nieuwste eerst",
"Most Popular": "Meest populair"
}

View File

@@ -0,0 +1,136 @@
{
"PDS Host": "Host PDS",
"Identifier": "Identifier",
"Password": "Senha",
"The Personal Data Server host. Leave empty for default Bluesky network (https://bsky.social)": "O host de servidor de dados pessoais. Deixe em branco para a rede padrão (https://bsky.social)",
"Your Bluesky handle or email address": "Seu identificador de usuário ou endereço de e-mail",
"Your Bluesky account password or app password": "Sua senha de conta Bluesky ou senha do aplicativo",
"\nTo authenticate with Bluesky:\n\n1. **PDS Host**: The Personal Data Server host. Leave empty to use the default Bluesky network (https://bsky.social).\n2. **Identifier**: Your Bluesky handle (e.g., yourhandle.bsky.social) or email address.\n3. **Password**: Your Bluesky account password or app password.\n\nFor enhanced security, consider using an app password from your Bluesky account settings.\n": "\nPara autenticar com Bluesky:\n\n1. **PDS Host**: O Host do banco do seu Servidor Pessoal. Deixe em branco para utilizar o endereço padrão Bluesky Network(https://bsky.social)\n2. **Identificador**: Seu domínio controlador Bluesky (ex. yourhandle.bsky.social) ou endereço de e-mail.\n3. **Password**: Senha da sua conta ou aplicativo Bluesky.\n\nPara maior segurança, considere utilizar uma senha do aplicativo gerador nas Configurações da sua conta Bluesky.",
"Create Post": "Criar publicação",
"Like Post": "Curtir Postagem",
"Repost Post": "Postagem republicada",
"Find Post": "Encontrar publicação",
"Find Thread": "Encontrar tópico",
"Create a new post on Bluesky": "Criar um novo post no Bluesky",
"Like a post on Bluesky": "Curta um post no Bluesky",
"Share someone else's post to your timeline": "Compartilhe a publicação de alguém na sua linha do tempo",
"Get detailed information about a specific post": "Obter informações detalhadas sobre uma postagem específica",
"Get a full conversation thread with replies": "Obter um tópico completo de conversa com respostas",
"Post Type": "Tipo de publicação",
"Post Text": "Texto da postagem",
"Post Language": "Idioma da postagem",
"Image URLs": "URLs da imagem",
"Image Descriptions": "Descrições da imagem",
"Video URL": "URL do vídeo",
"Video Description": "Descrição do Vídeo",
"Video Captions": "Legendas de Vídeo",
"Link to Share": "Link para Compartilhar",
"Reply to Post": "Responder à publicação",
"Thread Posts": "Postagens do tópico",
"Hashtags": "Palavras-chave",
"Content Warnings": "Avisos de conteúdo",
"Audience": "Público",
"Select Method": "Selecione o Método",
"Select Post": "Selecionar Postagem",
"Post URL": "URL da postagem",
"Thread Depth": "Profundidade do tópico",
"Parent Height": "Altura Pai",
"Type of content you're sharing": "Tipo de conteúdo que você está compartilhando",
"What do you want to post? (Max 300 characters)": "O que você quer publicar? (Máximo de 300 caracteres)",
"Language of your post": "Idioma da sua publicação",
"Add up to 4 images by URL": "Adicione até 4 imagens por URL",
"Describe each image for accessibility": "Descrever cada imagem para acessibilidade",
"Link to video file (MP4, max 100MB)": "Link para o arquivo de vídeo (MP4, máx. 100MB)",
"Describe the video for accessibility": "Descreva o vídeo para acessibilidade",
"Caption file URLs (optional)": "URLs do arquivo de legenda (opcional)",
"URL to share with your post": "URL para compartilhar com sua publicação",
"URL of post to reply to": "URL da postagem para responder a",
"Create additional connected posts": "Crie publicações adicionais conectadas",
"Add hashtags (e.g., tech,bluesky)": "Adicionar hashtags (por exemplo, tecnologia, bluesky)",
"Add warnings for sensitive content": "Adicionar avisos para conteúdo confidencial",
"Who can see this post": "Quem pode ver esta publicação",
"How to choose the post": "Como escolher o post",
"Choose from your recent timeline posts (only when \"From my timeline\" is selected above)": "Escolha entre suas postagens recentes de linha do tempo (somente quando \"Da minha linha do tempo\" estiver selecionado acima)",
"Paste the Bluesky post URL": "Cole a URL do post do Bluesky",
"Paste the Bluesky post URL (e.g., https://bsky.app/profile/username.bsky.social/post/xxx)": "Cole a URL do post do Bluesky (por exemplo, https://bsky.app/profile/username.bsky.social/post/xxx)",
"How many levels deep to retrieve replies": "Quantos níveis profundas para recuperar as respostas",
"How many parent posts to retrieve": "Quantas publicações pai para recuperar",
"Text Post": "Post de texto",
"Photo Post": "Publicação de Fotos",
"Link Share": "Link de Compartilhamento",
"Reply": "Responder",
"Repost with Comment": "Republicar com Comentário",
"English": "Portuguese-Brazil",
"Spanish": "espanhol",
"French": "francês",
"German": "alemão",
"Italian": "italiano",
"Portuguese": "Português",
"Japanese": "japonês",
"Korean": "coreano",
"Chinese": "chinês",
"Russian": "Russo",
"Arabic": "Arábico",
"Hindi": "hindi",
"Dutch": "Neerlandês",
"Swedish": "sueco",
"Other": "Outros",
"Adult Content": "Conteúdo adulto",
"Graphic Content": "Conteúdo gráfico",
"Sensitive Topic": "Tópico Sensível",
"Violence": "Violência",
"Spam/Promotional": "Spam/Promocional",
"Everyone (Public)": "Todos (Publicados)",
"Followers only": "Apenas seguidores",
"Private/Unlisted": "Privado/Não listado",
"From my timeline": "Da minha linha do tempo",
"Enter URL manually": "Inserir URL manualmente",
"1 level": "1 nível",
"2 levels": "2 níveis",
"3 levels": "3 níveis",
"5 levels": "5 níveis",
"10 levels": "10 níveis",
"20 levels": "20 níveis",
"50 levels": "50 níveis",
"100 levels (max)": "100 níveis (máximo)",
"No parents": "Sem pais",
"1 parent": "1 pai",
"2 parents": "2 pais",
"3 parents": "3 pais",
"5 parents": "5 pais",
"10 parents": "10 pais",
"20 parents": "20 pais",
"All parents (80 max)": "Todos os pais (80 máx)",
"New Posts by Author": "Novas publicações do autor",
"New Follower on Account": "Novo seguidor em conta",
"New Timeline Posts": "Novas Postagens na Linha Temporal",
"New Post (with Search Options)": "Novo Post (com opções de pesquisa)",
"Triggers when a selected author creates a new post": "Dispara quando um autor selecionado cria uma nova postagem",
"Triggers when someone new follows your Bluesky account": "Dispara quando alguém novo segue a sua conta Bluesky",
"Triggers when new posts appear in your timeline": "Dispara quando novas postagens aparecem em sua linha do tempo",
"Triggers when posts match your search criteria": "Dispara quando as mensagens correspondem aos seus critérios de pesquisa",
"How to select author?": "Como selecionar o autor?",
"Select Author": "Selecionar Autor",
"Author Handle": "Manipulador do Autor",
"Include Replies": "Incluir respostas",
"Include Reposts": "Incluir Reposts",
"Search Query": "Consulta de Pesquisa",
"Language Filter": "Filtro de Idioma",
"Filter by Images": "Filtrar por imagens",
"Filter by Videos": "Filtrar por vídeos",
"Sort Order": "Ordem de classificação",
"Choose how to select the author": "Escolha como selecionar o autor",
"Choose from accounts you follow": "Escolha das contas que você segue",
"Enter the Bluesky username (e.g., username.bsky.social)": "Digite o nome de usuário Bluesky (por exemplo, username.bsky.social)",
"Include reply posts by this author": "Incluir postagens de resposta deste autor",
"Include posts that this author reposted": "Incluir postagens que este autor repostou",
"Keywords, hashtags (#example), or mentions (@handle) to find": "Palavras-chave, hashtags (#exemplo), ou menções (@handle) para encontrar",
"Filter by language": "Filtrar por idioma",
"Only posts with/without images": "Apenas postagens com/sem imagens",
"Only posts with/without videos": "Apenas postagens com/sem vídeos",
"How to sort results": "Como classificar os resultados",
"From my following list": "Da minha lista seguinte",
"Enter handle manually": "Inserir identificador manualmente",
"Latest First": "Últimos Primeiro",
"Most Popular": "Por Popularidade"
}

View File

@@ -0,0 +1,137 @@
{
"Bluesky": "Синески",
"PDS Host": "Сервер PDS",
"Identifier": "Identifier",
"Password": "Password",
"The Personal Data Server host. Leave empty for default Bluesky network (https://bsky.social)": "Хост сервера персональных данных. Оставьте пустым для стандартной сети Bluesky (https://bsky.social)",
"Your Bluesky handle or email address": "Ваш Bluesky аккаунт или адрес электронной почты",
"Your Bluesky account password or app password": "Ваш Bluesky пароль или пароль от приложения",
"\nTo authenticate with Bluesky:\n\n1. **PDS Host**: The Personal Data Server host. Leave empty to use the default Bluesky network (https://bsky.social).\n2. **Identifier**: Your Bluesky handle (e.g., yourhandle.bsky.social) or email address.\n3. **Password**: Your Bluesky account password or app password.\n\nFor enhanced security, consider using an app password from your Bluesky account settings.\n": "\nДля аутентификации с помощью Bluesky:\n\n1. **PDS Host**: сервер персональных данных. Оставьте пустым, чтобы использовать сеть по умолчанию Bluesky (https://bsky.social).\n2. **Identifier**: Ваш ручной кодек (напр. yourhandle.bsky.social) или email адрес.\n3. **Пароль**: Ваш пароль или пароль от учетной записи синего пользователя или приложения.\n\nДля повышенной безопасности попробуйте использовать пароль приложения из настроек вашего Bluesky аккаунта.\n",
"Create Post": "Создать запись",
"Like Post": "Лайкнуть пост",
"Repost Post": "Репост",
"Find Post": "Найти пост",
"Find Thread": "Найти тему",
"Create a new post on Bluesky": "Создать новый пост на Bluesky",
"Like a post on Bluesky": "Лайкнуть пост на синески",
"Share someone else's post to your timeline": "Поделитесь чужим сообщением на вашу ленту времени",
"Get detailed information about a specific post": "Получить подробную информацию об отдельном сообщении",
"Get a full conversation thread with replies": "Получить полное обсуждение с ответами",
"Post Type": "Тип записи",
"Post Text": "Текст сообщения",
"Post Language": "Язык записи",
"Image URLs": "URL-адреса изображений",
"Image Descriptions": "Описание изображения",
"Video URL": "URL видео",
"Video Description": "Описание видео",
"Video Captions": "Подписи видео",
"Link to Share": "Ссылка на публикацию",
"Reply to Post": "Ответить на сообщение",
"Thread Posts": "Посты в теме",
"Hashtags": "Хэштеги",
"Content Warnings": "Предупреждения содержимого",
"Audience": "Аудитория",
"Select Method": "Выберите метод",
"Select Post": "Выбрать пост",
"Post URL": "URL записи",
"Thread Depth": "Глубина потока",
"Parent Height": "Родительская высота",
"Type of content you're sharing": "Тип контента, который вы передаете",
"What do you want to post? (Max 300 characters)": "Что вы хотите отправить сообщение? (Макс. 300 символов)",
"Language of your post": "Язык вашего сообщения",
"Add up to 4 images by URL": "Добавить до 4 изображений по URL",
"Describe each image for accessibility": "Опишите каждое изображение для доступности",
"Link to video file (MP4, max 100MB)": "Ссылка на видео файл (MP4, максимум 100MB)",
"Describe the video for accessibility": "Опишите видео для доступности",
"Caption file URLs (optional)": "URL файла заголовка (необязательно)",
"URL to share with your post": "URL-адрес для отправки с вашим сообщением",
"URL of post to reply to": "URL сообщения для ответа на",
"Create additional connected posts": "Создать дополнительные подключенные сообщения",
"Add hashtags (e.g., tech,bluesky)": "Добавить хэштеги (например, тех,блюски)",
"Add warnings for sensitive content": "Добавить предупреждения о конфиденциальном контенте",
"Who can see this post": "Кто может видеть этот пост",
"How to choose the post": "Как выбрать сообщение",
"Choose from your recent timeline posts (only when \"From my timeline\" is selected above)": "Выбрать из последних сообщений (только когда выбрано \"С моего времени\" выше)",
"Paste the Bluesky post URL": "Вставить Bluesky пост URL",
"Paste the Bluesky post URL (e.g., https://bsky.app/profile/username.bsky.social/post/xxx)": "Вставьте URL записи Bluesky (например, https://bsky.app/profile/username.bsky.social/post/xxx)",
"How many levels deep to retrieve replies": "Количество уровней для получения ответов",
"How many parent posts to retrieve": "Количество родительских записей для получения",
"Text Post": "Текстовое сообщение",
"Photo Post": "Фото пост",
"Link Share": "Ссылка поделиться",
"Reply": "Ответ",
"Repost with Comment": "Переписать с комментарием",
"English": "Russian",
"Spanish": "Испанский",
"French": "Французский",
"German": "Немецкий",
"Italian": "Итальянский",
"Portuguese": "Португальский",
"Japanese": "Японский",
"Korean": "Корейский",
"Chinese": "Китайский",
"Russian": "Русский",
"Arabic": "Арабский",
"Hindi": "Хинди",
"Dutch": "Голландский",
"Swedish": "Шведский",
"Other": "Прочие вопросы",
"Adult Content": "Взрослый контент",
"Graphic Content": "Графический контент",
"Sensitive Topic": "Чувствительная тема",
"Violence": "Насилие",
"Spam/Promotional": "Спам/Реклама",
"Everyone (Public)": "Все (публичные)",
"Followers only": "Только подписчики",
"Private/Unlisted": "Приватная/Нет в списке",
"From my timeline": "Из моего графика",
"Enter URL manually": "Введите URL вручную",
"1 level": "1 уровень",
"2 levels": "2 уровня",
"3 levels": "3 уровня",
"5 levels": "5 уровней",
"10 levels": "10 уровней",
"20 levels": "20 уровней",
"50 levels": "50 уровней",
"100 levels (max)": "100 уровней (макс)",
"No parents": "Нет родителей",
"1 parent": "1 родитель",
"2 parents": "2 родителя",
"3 parents": "3 родителя",
"5 parents": "5 родителей",
"10 parents": "10 родителей",
"20 parents": "20 родителей",
"All parents (80 max)": "Все родители (80 макс)",
"New Posts by Author": "Новые сообщения автора",
"New Follower on Account": "Новый подписчик на аккаунте",
"New Timeline Posts": "Новые сообщения Ленты",
"New Post (with Search Options)": "Новый пост (с параметрами поиска)",
"Triggers when a selected author creates a new post": "Триггеры, когда выбранный автор создает новое сообщение",
"Triggers when someone new follows your Bluesky account": "Включает когда кто-то новый подписался на ваш Bluesky аккаунт",
"Triggers when new posts appear in your timeline": "Триггеры при появлении новых сообщений на вашей шкале времени",
"Triggers when posts match your search criteria": "Триггеры, когда сообщения соответствуют вашим критериям поиска",
"How to select author?": "Как выбрать автора?",
"Select Author": "Выбрать автора",
"Author Handle": "Авторский обработчик",
"Include Replies": "Включить ответы",
"Include Reposts": "Включить репосты",
"Search Query": "Поисковый запрос",
"Language Filter": "Фильтр языков",
"Filter by Images": "Фильтр по изображениям",
"Filter by Videos": "Фильтр по видео",
"Sort Order": "Порядок сортировки",
"Choose how to select the author": "Выберите, как выбрать автора",
"Choose from accounts you follow": "Выберите из аккаунтов, на которые вы подписаны",
"Enter the Bluesky username (e.g., username.bsky.social)": "Введите имя пользователя Bluesky (например, username.bsky.social)",
"Include reply posts by this author": "Включить сообщения об ответах от этого автора",
"Include posts that this author reposted": "Включать сообщения, которые этот автор хранил",
"Keywords, hashtags (#example), or mentions (@handle) to find": "Ключевые слова, хэштеги (#example) или упоминания (@handle), чтобы найти",
"Filter by language": "Фильтр по языку",
"Only posts with/without images": "Только записи с изображениями или без изображений",
"Only posts with/without videos": "Только записи с видео или без видео",
"How to sort results": "Как сортировать результаты",
"From my following list": "Из моего следующего списка",
"Enter handle manually": "Введите дескриптор вручную",
"Latest First": "Сначала последние",
"Most Popular": "Самые популярные"
}

View File

@@ -0,0 +1,136 @@
{
"PDS Host": "PDS Host",
"Identifier": "Identifier",
"Password": "Password",
"The Personal Data Server host. Leave empty for default Bluesky network (https://bsky.social)": "The Personal Data Server host. Leave empty for default Bluesky network (https://bsky.social)",
"Your Bluesky handle or email address": "Your Bluesky handle or email address",
"Your Bluesky account password or app password": "Your Bluesky account password or app password",
"\nTo authenticate with Bluesky:\n\n1. **PDS Host**: The Personal Data Server host. Leave empty to use the default Bluesky network (https://bsky.social).\n2. **Identifier**: Your Bluesky handle (e.g., yourhandle.bsky.social) or email address.\n3. **Password**: Your Bluesky account password or app password.\n\nFor enhanced security, consider using an app password from your Bluesky account settings.\n": "\nTo authenticate with Bluesky:\n\n1. **PDS Host**: The Personal Data Server host. Leave empty to use the default Bluesky network (https://bsky.social).\n2. **Identifier**: Your Bluesky handle (e.g., yourhandle.bsky.social) or email address.\n3. **Password**: Your Bluesky account password or app password.\n\nFor enhanced security, consider using an app password from your Bluesky account settings.\n",
"Create Post": "Create Post",
"Like Post": "Like Post",
"Repost Post": "Repost Post",
"Find Post": "Find Post",
"Find Thread": "Find Thread",
"Create a new post on Bluesky": "Create a new post on Bluesky",
"Like a post on Bluesky": "Like a post on Bluesky",
"Share someone else's post to your timeline": "Share someone else's post to your timeline",
"Get detailed information about a specific post": "Get detailed information about a specific post",
"Get a full conversation thread with replies": "Get a full conversation thread with replies",
"Post Type": "Post Type",
"Post Text": "Post Text",
"Post Language": "Post Language",
"Image URLs": "Image URLs",
"Image Descriptions": "Image Descriptions",
"Video URL": "Video URL",
"Video Description": "Video Description",
"Video Captions": "Video Captions",
"Link to Share": "Link to Share",
"Reply to Post": "Reply to Post",
"Thread Posts": "Thread Posts",
"Hashtags": "Hashtags",
"Content Warnings": "Content Warnings",
"Audience": "Audience",
"Select Method": "Select Method",
"Select Post": "Select Post",
"Post URL": "Post URL",
"Thread Depth": "Thread Depth",
"Parent Height": "Parent Height",
"Type of content you're sharing": "Type of content you're sharing",
"What do you want to post? (Max 300 characters)": "What do you want to post? (Max 300 characters)",
"Language of your post": "Language of your post",
"Add up to 4 images by URL": "Add up to 4 images by URL",
"Describe each image for accessibility": "Describe each image for accessibility",
"Link to video file (MP4, max 100MB)": "Link to video file (MP4, max 100MB)",
"Describe the video for accessibility": "Describe the video for accessibility",
"Caption file URLs (optional)": "Caption file URLs (optional)",
"URL to share with your post": "URL to share with your post",
"URL of post to reply to": "URL of post to reply to",
"Create additional connected posts": "Create additional connected posts",
"Add hashtags (e.g., tech,bluesky)": "Add hashtags (e.g., tech,bluesky)",
"Add warnings for sensitive content": "Add warnings for sensitive content",
"Who can see this post": "Who can see this post",
"How to choose the post": "How to choose the post",
"Choose from your recent timeline posts (only when \"From my timeline\" is selected above)": "Choose from your recent timeline posts (only when \"From my timeline\" is selected above)",
"Paste the Bluesky post URL": "Paste the Bluesky post URL",
"Paste the Bluesky post URL (e.g., https://bsky.app/profile/username.bsky.social/post/xxx)": "Paste the Bluesky post URL (e.g., https://bsky.app/profile/username.bsky.social/post/xxx)",
"How many levels deep to retrieve replies": "How many levels deep to retrieve replies",
"How many parent posts to retrieve": "How many parent posts to retrieve",
"Text Post": "Text Post",
"Photo Post": "Photo Post",
"Link Share": "Link Share",
"Reply": "Reply",
"Repost with Comment": "Repost with Comment",
"English": "English",
"Spanish": "Spanish",
"French": "French",
"German": "German",
"Italian": "Italian",
"Portuguese": "Portuguese",
"Japanese": "Japanese",
"Korean": "Korean",
"Chinese": "Chinese",
"Russian": "Russian",
"Arabic": "Arabic",
"Hindi": "Hindi",
"Dutch": "Dutch",
"Swedish": "Swedish",
"Other": "Other",
"Adult Content": "Adult Content",
"Graphic Content": "Graphic Content",
"Sensitive Topic": "Sensitive Topic",
"Violence": "Violence",
"Spam/Promotional": "Spam/Promotional",
"Everyone (Public)": "Everyone (Public)",
"Followers only": "Followers only",
"Private/Unlisted": "Private/Unlisted",
"From my timeline": "From my timeline",
"Enter URL manually": "Enter URL manually",
"1 level": "1 level",
"2 levels": "2 levels",
"3 levels": "3 levels",
"5 levels": "5 levels",
"10 levels": "10 levels",
"20 levels": "20 levels",
"50 levels": "50 levels",
"100 levels (max)": "100 levels (max)",
"No parents": "No parents",
"1 parent": "1 parent",
"2 parents": "2 parents",
"3 parents": "3 parents",
"5 parents": "5 parents",
"10 parents": "10 parents",
"20 parents": "20 parents",
"All parents (80 max)": "All parents (80 max)",
"New Posts by Author": "New Posts by Author",
"New Follower on Account": "New Follower on Account",
"New Timeline Posts": "New Timeline Posts",
"New Post (with Search Options)": "New Post (with Search Options)",
"Triggers when a selected author creates a new post": "Triggers when a selected author creates a new post",
"Triggers when someone new follows your Bluesky account": "Triggers when someone new follows your Bluesky account",
"Triggers when new posts appear in your timeline": "Triggers when new posts appear in your timeline",
"Triggers when posts match your search criteria": "Triggers when posts match your search criteria",
"How to select author?": "How to select author?",
"Select Author": "Select Author",
"Author Handle": "Author Handle",
"Include Replies": "Include Replies",
"Include Reposts": "Include Reposts",
"Search Query": "Search Query",
"Language Filter": "Language Filter",
"Filter by Images": "Filter by Images",
"Filter by Videos": "Filter by Videos",
"Sort Order": "Sort Order",
"Choose how to select the author": "Choose how to select the author",
"Choose from accounts you follow": "Choose from accounts you follow",
"Enter the Bluesky username (e.g., username.bsky.social)": "Enter the Bluesky username (e.g., username.bsky.social)",
"Include reply posts by this author": "Include reply posts by this author",
"Include posts that this author reposted": "Include posts that this author reposted",
"Keywords, hashtags (#example), or mentions (@handle) to find": "Keywords, hashtags (#example), or mentions (@handle) to find",
"Filter by language": "Filter by language",
"Only posts with/without images": "Only posts with/without images",
"Only posts with/without videos": "Only posts with/without videos",
"How to sort results": "How to sort results",
"From my following list": "From my following list",
"Enter handle manually": "Enter handle manually",
"Latest First": "Latest First",
"Most Popular": "Most Popular"
}

View File

@@ -0,0 +1,137 @@
{
"Bluesky": "Bluesky",
"PDS Host": "PDS Host",
"Identifier": "Identifier",
"Password": "Password",
"The Personal Data Server host. Leave empty for default Bluesky network (https://bsky.social)": "The Personal Data Server host. Leave empty for default Bluesky network (https://bsky.social)",
"Your Bluesky handle or email address": "Your Bluesky handle or email address",
"Your Bluesky account password or app password": "Your Bluesky account password or app password",
"\nTo authenticate with Bluesky:\n\n1. **PDS Host**: The Personal Data Server host. Leave empty to use the default Bluesky network (https://bsky.social).\n2. **Identifier**: Your Bluesky handle (e.g., yourhandle.bsky.social) or email address.\n3. **Password**: Your Bluesky account password or app password.\n\nFor enhanced security, consider using an app password from your Bluesky account settings.\n": "\nTo authenticate with Bluesky:\n\n1. **PDS Host**: The Personal Data Server host. Leave empty to use the default Bluesky network (https://bsky.social).\n2. **Identifier**: Your Bluesky handle (e.g., yourhandle.bsky.social) or email address.\n3. **Password**: Your Bluesky account password or app password.\n\nFor enhanced security, consider using an app password from your Bluesky account settings.\n",
"Create Post": "Create Post",
"Like Post": "Like Post",
"Repost Post": "Repost Post",
"Find Post": "Find Post",
"Find Thread": "Find Thread",
"Create a new post on Bluesky": "Create a new post on Bluesky",
"Like a post on Bluesky": "Like a post on Bluesky",
"Share someone else's post to your timeline": "Share someone else's post to your timeline",
"Get detailed information about a specific post": "Get detailed information about a specific post",
"Get a full conversation thread with replies": "Get a full conversation thread with replies",
"Post Type": "Post Type",
"Post Text": "Post Text",
"Post Language": "Post Language",
"Image URLs": "Image URLs",
"Image Descriptions": "Image Descriptions",
"Video URL": "Video URL",
"Video Description": "Video Description",
"Video Captions": "Video Captions",
"Link to Share": "Link to Share",
"Reply to Post": "Reply to Post",
"Thread Posts": "Thread Posts",
"Hashtags": "Hashtags",
"Content Warnings": "Content Warnings",
"Audience": "Audience",
"Select Method": "Select Method",
"Select Post": "Select Post",
"Post URL": "Post URL",
"Thread Depth": "Thread Depth",
"Parent Height": "Parent Height",
"Type of content you're sharing": "Type of content you're sharing",
"What do you want to post? (Max 300 characters)": "What do you want to post? (Max 300 characters)",
"Language of your post": "Language of your post",
"Add up to 4 images by URL": "Add up to 4 images by URL",
"Describe each image for accessibility": "Describe each image for accessibility",
"Link to video file (MP4, max 100MB)": "Link to video file (MP4, max 100MB)",
"Describe the video for accessibility": "Describe the video for accessibility",
"Caption file URLs (optional)": "Caption file URLs (optional)",
"URL to share with your post": "URL to share with your post",
"URL of post to reply to": "URL of post to reply to",
"Create additional connected posts": "Create additional connected posts",
"Add hashtags (e.g., tech,bluesky)": "Add hashtags (e.g., tech,bluesky)",
"Add warnings for sensitive content": "Add warnings for sensitive content",
"Who can see this post": "Who can see this post",
"How to choose the post": "How to choose the post",
"Choose from your recent timeline posts (only when \"From my timeline\" is selected above)": "Choose from your recent timeline posts (only when \"From my timeline\" is selected above)",
"Paste the Bluesky post URL": "Paste the Bluesky post URL",
"Paste the Bluesky post URL (e.g., https://bsky.app/profile/username.bsky.social/post/xxx)": "Paste the Bluesky post URL (e.g., https://bsky.app/profile/username.bsky.social/post/xxx)",
"How many levels deep to retrieve replies": "How many levels deep to retrieve replies",
"How many parent posts to retrieve": "How many parent posts to retrieve",
"Text Post": "Text Post",
"Photo Post": "Photo Post",
"Link Share": "Link Share",
"Reply": "Reply",
"Repost with Comment": "Repost with Comment",
"English": "English",
"Spanish": "Spanish",
"French": "French",
"German": "German",
"Italian": "Italian",
"Portuguese": "Portuguese",
"Japanese": "Japanese",
"Korean": "Korean",
"Chinese": "Chinese",
"Russian": "Russian",
"Arabic": "Arabic",
"Hindi": "Hindi",
"Dutch": "Dutch",
"Swedish": "Swedish",
"Other": "Other",
"Adult Content": "Adult Content",
"Graphic Content": "Graphic Content",
"Sensitive Topic": "Sensitive Topic",
"Violence": "Violence",
"Spam/Promotional": "Spam/Promotional",
"Everyone (Public)": "Everyone (Public)",
"Followers only": "Followers only",
"Private/Unlisted": "Private/Unlisted",
"From my timeline": "From my timeline",
"Enter URL manually": "Enter URL manually",
"1 level": "1 level",
"2 levels": "2 levels",
"3 levels": "3 levels",
"5 levels": "5 levels",
"10 levels": "10 levels",
"20 levels": "20 levels",
"50 levels": "50 levels",
"100 levels (max)": "100 levels (max)",
"No parents": "No parents",
"1 parent": "1 parent",
"2 parents": "2 parents",
"3 parents": "3 parents",
"5 parents": "5 parents",
"10 parents": "10 parents",
"20 parents": "20 parents",
"All parents (80 max)": "All parents (80 max)",
"New Posts by Author": "New Posts by Author",
"New Follower on Account": "New Follower on Account",
"New Timeline Posts": "New Timeline Posts",
"New Post (with Search Options)": "New Post (with Search Options)",
"Triggers when a selected author creates a new post": "Triggers when a selected author creates a new post",
"Triggers when someone new follows your Bluesky account": "Triggers when someone new follows your Bluesky account",
"Triggers when new posts appear in your timeline": "Triggers when new posts appear in your timeline",
"Triggers when posts match your search criteria": "Triggers when posts match your search criteria",
"How to select author?": "How to select author?",
"Select Author": "Select Author",
"Author Handle": "Author Handle",
"Include Replies": "Include Replies",
"Include Reposts": "Include Reposts",
"Search Query": "Search Query",
"Language Filter": "Language Filter",
"Filter by Images": "Filter by Images",
"Filter by Videos": "Filter by Videos",
"Sort Order": "Sort Order",
"Choose how to select the author": "Choose how to select the author",
"Choose from accounts you follow": "Choose from accounts you follow",
"Enter the Bluesky username (e.g., username.bsky.social)": "Enter the Bluesky username (e.g., username.bsky.social)",
"Include reply posts by this author": "Include reply posts by this author",
"Include posts that this author reposted": "Include posts that this author reposted",
"Keywords, hashtags (#example), or mentions (@handle) to find": "Keywords, hashtags (#example), or mentions (@handle) to find",
"Filter by language": "Filter by language",
"Only posts with/without images": "Only posts with/without images",
"Only posts with/without videos": "Only posts with/without videos",
"How to sort results": "How to sort results",
"From my following list": "From my following list",
"Enter handle manually": "Enter handle manually",
"Latest First": "Latest First",
"Most Popular": "Most Popular"
}

View File

@@ -0,0 +1,136 @@
{
"PDS Host": "PDS Host",
"Identifier": "Identifier",
"Password": "密码",
"The Personal Data Server host. Leave empty for default Bluesky network (https://bsky.social)": "The Personal Data Server host. Leave empty for default Bluesky network (https://bsky.social)",
"Your Bluesky handle or email address": "Your Bluesky handle or email address",
"Your Bluesky account password or app password": "Your Bluesky account password or app password",
"\nTo authenticate with Bluesky:\n\n1. **PDS Host**: The Personal Data Server host. Leave empty to use the default Bluesky network (https://bsky.social).\n2. **Identifier**: Your Bluesky handle (e.g., yourhandle.bsky.social) or email address.\n3. **Password**: Your Bluesky account password or app password.\n\nFor enhanced security, consider using an app password from your Bluesky account settings.\n": "\nTo authenticate with Bluesky:\n\n1. **PDS Host**: The Personal Data Server host. Leave empty to use the default Bluesky network (https://bsky.social).\n2. **Identifier**: Your Bluesky handle (e.g., yourhandle.bsky.social) or email address.\n3. **Password**: Your Bluesky account password or app password.\n\nFor enhanced security, consider using an app password from your Bluesky account settings.\n",
"Create Post": "Create Post",
"Like Post": "Like Post",
"Repost Post": "Repost Post",
"Find Post": "Find Post",
"Find Thread": "Find Thread",
"Create a new post on Bluesky": "Create a new post on Bluesky",
"Like a post on Bluesky": "Like a post on Bluesky",
"Share someone else's post to your timeline": "Share someone else's post to your timeline",
"Get detailed information about a specific post": "Get detailed information about a specific post",
"Get a full conversation thread with replies": "Get a full conversation thread with replies",
"Post Type": "Post Type",
"Post Text": "Post Text",
"Post Language": "Post Language",
"Image URLs": "Image URLs",
"Image Descriptions": "Image Descriptions",
"Video URL": "Video URL",
"Video Description": "Video Description",
"Video Captions": "Video Captions",
"Link to Share": "Link to Share",
"Reply to Post": "Reply to Post",
"Thread Posts": "Thread Posts",
"Hashtags": "Hashtags",
"Content Warnings": "Content Warnings",
"Audience": "Audience",
"Select Method": "Select Method",
"Select Post": "Select Post",
"Post URL": "Post URL",
"Thread Depth": "Thread Depth",
"Parent Height": "Parent Height",
"Type of content you're sharing": "Type of content you're sharing",
"What do you want to post? (Max 300 characters)": "What do you want to post? (Max 300 characters)",
"Language of your post": "Language of your post",
"Add up to 4 images by URL": "Add up to 4 images by URL",
"Describe each image for accessibility": "Describe each image for accessibility",
"Link to video file (MP4, max 100MB)": "Link to video file (MP4, max 100MB)",
"Describe the video for accessibility": "Describe the video for accessibility",
"Caption file URLs (optional)": "Caption file URLs (optional)",
"URL to share with your post": "URL to share with your post",
"URL of post to reply to": "URL of post to reply to",
"Create additional connected posts": "Create additional connected posts",
"Add hashtags (e.g., tech,bluesky)": "Add hashtags (e.g., tech,bluesky)",
"Add warnings for sensitive content": "Add warnings for sensitive content",
"Who can see this post": "Who can see this post",
"How to choose the post": "How to choose the post",
"Choose from your recent timeline posts (only when \"From my timeline\" is selected above)": "Choose from your recent timeline posts (only when \"From my timeline\" is selected above)",
"Paste the Bluesky post URL": "Paste the Bluesky post URL",
"Paste the Bluesky post URL (e.g., https://bsky.app/profile/username.bsky.social/post/xxx)": "Paste the Bluesky post URL (e.g., https://bsky.app/profile/username.bsky.social/post/xxx)",
"How many levels deep to retrieve replies": "How many levels deep to retrieve replies",
"How many parent posts to retrieve": "How many parent posts to retrieve",
"Text Post": "Text Post",
"Photo Post": "Photo Post",
"Link Share": "Link Share",
"Reply": "Reply",
"Repost with Comment": "Repost with Comment",
"English": "English",
"Spanish": "Spanish",
"French": "French",
"German": "German",
"Italian": "Italian",
"Portuguese": "Portuguese",
"Japanese": "Japanese",
"Korean": "Korean",
"Chinese": "Chinese",
"Russian": "Russian",
"Arabic": "Arabic",
"Hindi": "Hindi",
"Dutch": "Dutch",
"Swedish": "Swedish",
"Other": "Other",
"Adult Content": "Adult Content",
"Graphic Content": "Graphic Content",
"Sensitive Topic": "Sensitive Topic",
"Violence": "Violence",
"Spam/Promotional": "Spam/Promotional",
"Everyone (Public)": "Everyone (Public)",
"Followers only": "Followers only",
"Private/Unlisted": "Private/Unlisted",
"From my timeline": "From my timeline",
"Enter URL manually": "Enter URL manually",
"1 level": "1 level",
"2 levels": "2 levels",
"3 levels": "3 levels",
"5 levels": "5 levels",
"10 levels": "10 levels",
"20 levels": "20 levels",
"50 levels": "50 levels",
"100 levels (max)": "100 levels (max)",
"No parents": "No parents",
"1 parent": "1 parent",
"2 parents": "2 parents",
"3 parents": "3 parents",
"5 parents": "5 parents",
"10 parents": "10 parents",
"20 parents": "20 parents",
"All parents (80 max)": "All parents (80 max)",
"New Posts by Author": "New Posts by Author",
"New Follower on Account": "New Follower on Account",
"New Timeline Posts": "New Timeline Posts",
"New Post (with Search Options)": "New Post (with Search Options)",
"Triggers when a selected author creates a new post": "Triggers when a selected author creates a new post",
"Triggers when someone new follows your Bluesky account": "Triggers when someone new follows your Bluesky account",
"Triggers when new posts appear in your timeline": "Triggers when new posts appear in your timeline",
"Triggers when posts match your search criteria": "Triggers when posts match your search criteria",
"How to select author?": "How to select author?",
"Select Author": "Select Author",
"Author Handle": "Author Handle",
"Include Replies": "Include Replies",
"Include Reposts": "Include Reposts",
"Search Query": "Search Query",
"Language Filter": "Language Filter",
"Filter by Images": "Filter by Images",
"Filter by Videos": "Filter by Videos",
"Sort Order": "Sort Order",
"Choose how to select the author": "Choose how to select the author",
"Choose from accounts you follow": "Choose from accounts you follow",
"Enter the Bluesky username (e.g., username.bsky.social)": "Enter the Bluesky username (e.g., username.bsky.social)",
"Include reply posts by this author": "Include reply posts by this author",
"Include posts that this author reposted": "Include posts that this author reposted",
"Keywords, hashtags (#example), or mentions (@handle) to find": "Keywords, hashtags (#example), or mentions (@handle) to find",
"Filter by language": "Filter by language",
"Only posts with/without images": "Only posts with/without images",
"Only posts with/without videos": "Only posts with/without videos",
"How to sort results": "How to sort results",
"From my following list": "From my following list",
"Enter handle manually": "Enter handle manually",
"Latest First": "Latest First",
"Most Popular": "Most Popular"
}

View File

@@ -0,0 +1,25 @@
import { createPiece, PieceAuth } from '@activepieces/pieces-framework';
import { PieceCategory } from '@activepieces/shared';
import { blueskyAuth } from './lib/common/auth';
import { createPost } from './lib/actions/create-post';
import { likePost } from './lib/actions/like-post';
import { repostPost } from './lib/actions/repost';
import { findPost } from './lib/actions/find-post';
import { findThread } from './lib/actions/find-thread';
import { newPostsByAuthor } from './lib/triggers/new-posts-by-author';
import { newFollowerOnAccount } from './lib/triggers/new-follower-on-account';
import { newTimelinePosts } from './lib/triggers/new-timeline-posts';
import { newPost } from './lib/triggers/new-post';
export { blueskyAuth } from './lib/common/auth';
export { createBlueskyAgent } from './lib/common/client';
export const bluesky = createPiece({
displayName: 'Bluesky',
auth: blueskyAuth,
minimumSupportedRelease: '0.36.1',
logoUrl: 'https://cdn.activepieces.com/pieces/bluesky.png',
authors: ['Sanket6652'],
categories: [PieceCategory.COMMUNICATION],
actions: [createPost, likePost, repostPost, findPost, findThread],
triggers: [newPostsByAuthor, newFollowerOnAccount, newTimelinePosts, newPost],
});

View File

@@ -0,0 +1,439 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { blueskyAuth } from '../common/auth';
import { createBlueskyAgent } from '../common/client';
import { RichText } from '@atproto/api';
import {
postTextProperty,
postTypeDropdown,
simpleLanguageDropdown,
imageUrlsProperty,
imageDescriptionsProperty,
linkUrlProperty,
replyToPostProperty,
contentWarningDropdown,
audienceDropdown
} from '../common/props';
const videoUrlProperty = Property.ShortText({
displayName: 'Video URL',
description: 'Link to video file (MP4, max 100MB)',
required: false,
});
const videoAltTextProperty = Property.LongText({
displayName: 'Video Description',
description: 'Describe the video for accessibility',
required: false,
});
const videoCaptionsProperty = Property.Array({
displayName: 'Video Captions',
description: 'Caption file URLs (optional)',
required: false,
});
const threadContentProperty = Property.Array({
displayName: 'Thread Posts',
description: 'Create additional connected posts',
required: false,
});
const additionalHashtagsProperty = Property.ShortText({
displayName: 'Hashtags',
description: 'Add hashtags (e.g., tech,bluesky)',
required: false,
});
async function parsePostUrl(url: string, agent: any): Promise<string> {
if (url.startsWith('at://')) {
return url;
}
const urlMatch = url.match(/https?:\/\/bsky\.app\/profile\/([^/]+)\/post\/([^/?]+)/);
if (urlMatch) {
const handle = urlMatch[1];
const postId = urlMatch[2];
const didDoc = await agent.resolveHandle({ handle });
return `at://${didDoc.data.did}/app.bsky.feed.post/${postId}`;
}
throw new Error('Invalid post URL format. Please use a valid Bluesky post URL.');
}
async function getReplyRefs(postUri: string, agent: any): Promise<any> {
try {
const postThread = await agent.getPostThread({ uri: postUri });
const post = postThread.thread.post;
if (!post) {
throw new Error('Post not found');
}
const parentReply = post.record.reply;
return {
root: parentReply?.root || { uri: post.uri, cid: post.cid },
parent: { uri: post.uri, cid: post.cid },
};
} catch (error) {
throw new Error(`Failed to resolve reply: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
async function createEnhancedLinkEmbed(url: string, agent: any): Promise<any> {
try {
const response = await fetch(url, {
headers: {
'User-Agent': 'Mozilla/5.0 (compatible; Activepieces-Bot/1.0)',
},
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const html = await response.text();
const titleMatch = html.match(/<meta\s+property="og:title"\s+content="([^"]*)"[^>]*>/i) ||
html.match(/<title[^>]*>([^<]*)<\/title>/i);
const title = titleMatch ? titleMatch[1].trim() : url;
const descMatch = html.match(/<meta\s+property="og:description"\s+content="([^"]*)"[^>]*>/i) ||
html.match(/<meta\s+name="description"\s+content="([^"]*)"[^>]*>/i);
const description = descMatch ? descMatch[1].trim() : '';
const imageMatch = html.match(/<meta\s+property="og:image"\s+content="([^"]*)"[^>]*>/i);
const external: any = {
uri: url,
title: title.length > 0 ? title : url,
description: description.length > 0 ? description : 'Shared link',
};
if (imageMatch && imageMatch[1]) {
try {
let imageUrl = imageMatch[1];
if (imageUrl.startsWith('/')) {
const urlObj = new URL(url);
imageUrl = `${urlObj.protocol}//${urlObj.host}${imageUrl}`;
} else if (!imageUrl.startsWith('http')) {
imageUrl = new URL(imageUrl, url).href;
}
const imageResponse = await fetch(imageUrl);
if (imageResponse.ok) {
const imageBlob = await imageResponse.blob();
if (imageBlob.size <= 1000000 && imageBlob.type.startsWith('image/')) {
const thumbResponse = await agent.uploadBlob(imageBlob, {
encoding: imageBlob.type,
});
external.thumb = thumbResponse.data.blob;
}
}
} catch (thumbError) {
console.warn('Failed to upload link thumbnail:', thumbError);
}
}
return {
$type: 'app.bsky.embed.external',
external,
};
} catch (error) {
return {
$type: 'app.bsky.embed.external',
external: {
uri: url,
title: url,
description: 'Shared link',
},
};
}
}
export const createPost = createAction({
auth: blueskyAuth,
name: 'createPost',
displayName: 'Create Post',
description: 'Create a new post on Bluesky',
props: {
postType: postTypeDropdown,
text: postTextProperty,
language: simpleLanguageDropdown,
imageUrls: imageUrlsProperty,
imageDescriptions: imageDescriptionsProperty,
videoUrl: videoUrlProperty,
videoAltText: videoAltTextProperty,
videoCaptions: videoCaptionsProperty,
linkUrl: linkUrlProperty,
replyToPost: replyToPostProperty,
threadContent: threadContentProperty,
additionalHashtags: additionalHashtagsProperty,
contentWarnings: contentWarningDropdown,
audience: audienceDropdown,
},
async run({ auth, propsValue }) {
const {
text,
language,
imageUrls,
imageDescriptions,
videoUrl,
videoAltText,
videoCaptions,
linkUrl,
replyToPost,
threadContent,
additionalHashtags,
contentWarnings,
audience
} = propsValue;
try {
const agent = await createBlueskyAgent(auth.props);
let processedText = text;
if (additionalHashtags && additionalHashtags.trim()) {
const hashtags = additionalHashtags
.split(',')
.map(tag => tag.trim().startsWith('#') ? tag.trim() : `#${tag.trim()}`)
.filter(tag => tag.length > 1)
.join(' ');
if (hashtags) {
processedText += ` ${hashtags}`;
}
}
const richText = new RichText({ text: processedText });
await richText.detectFacets(agent);
if (richText.length > 300) {
throw new Error('Post text cannot exceed 300 characters');
}
const postRecord: any = {
text: richText.text,
facets: richText.facets,
createdAt: new Date().toISOString(),
};
if (additionalHashtags && additionalHashtags.trim()) {
const tagList = additionalHashtags
.split(',')
.map(tag => tag.trim().replace(/^#/, ''))
.filter(tag => tag.length > 0);
if (tagList.length > 0) {
postRecord.tags = tagList;
}
}
if (contentWarnings && contentWarnings.length > 0) {
postRecord.labels = {
$type: 'com.atproto.label.defs#selfLabels',
values: contentWarnings.map((warning: string) => ({ val: warning })),
};
}
if (language && language !== 'other') {
postRecord.langs = [language];
}
if (replyToPost && replyToPost.trim()) {
const postUri = await parsePostUrl(replyToPost, agent);
postRecord.reply = await getReplyRefs(postUri, agent);
}
if (videoUrl && videoUrl.trim()) {
try {
const videoResponse = await fetch(videoUrl);
if (!videoResponse.ok) {
throw new Error(`Failed to fetch video from ${videoUrl}`);
}
const videoBlob = await videoResponse.blob();
if (!videoBlob.type.startsWith('video/')) {
throw new Error('File must be a video format');
}
if (videoBlob.size > 100000000) {
throw new Error(`Video is too large (max 100MB), got ${Math.round(videoBlob.size / 1000000)}MB`);
}
const videoBlobResponse = await agent.uploadBlob(videoBlob, {
encoding: videoBlob.type,
});
const captionObjects = [];
if (videoCaptions && videoCaptions.length > 0) {
for (let i = 0; i < videoCaptions.length; i++) {
const captionUrl = videoCaptions[i] as string;
try {
const captionResponse = await fetch(captionUrl);
if (!captionResponse.ok) continue;
const captionBlob = await captionResponse.blob();
const captionBlobResponse = await agent.uploadBlob(captionBlob, {
encoding: 'text/vtt',
});
captionObjects.push({
lang: 'en',
file: captionBlobResponse.data.blob,
});
} catch (error) {
console.warn(`Failed to upload caption ${i + 1}:`, error);
}
}
}
const videoEmbed: any = {
$type: 'app.bsky.embed.video',
video: videoBlobResponse.data.blob,
};
if (videoAltText && videoAltText.trim()) {
videoEmbed.alt = videoAltText.trim();
}
if (captionObjects.length > 0) {
videoEmbed.captions = captionObjects;
}
postRecord.embed = videoEmbed;
} catch (error) {
throw new Error(`Failed to process video: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
else if (imageUrls && imageUrls.length > 0) {
if (imageUrls.length > 4) {
throw new Error('Maximum of 4 images allowed per post');
}
const images = [];
for (let i = 0; i < imageUrls.length; i++) {
const imageUrl = imageUrls[i] as string;
const altText = (imageDescriptions?.[i] as string) || '';
try {
const imageResponse = await fetch(imageUrl);
if (!imageResponse.ok) {
throw new Error(`Failed to fetch image from ${imageUrl}`);
}
const imageBlob = await imageResponse.blob();
if (imageBlob.size > 1000000) {
throw new Error(`Image at ${imageUrl} is too large (max 1MB)`);
}
const blobResponse = await agent.uploadBlob(imageBlob, {
encoding: imageBlob.type,
});
images.push({
alt: altText,
image: blobResponse.data.blob,
});
} catch (error) {
throw new Error(`Failed to upload image ${i + 1}: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
postRecord.embed = {
$type: 'app.bsky.embed.images',
images,
};
}
else if (linkUrl && linkUrl.trim()) {
try {
const linkEmbed = await createEnhancedLinkEmbed(linkUrl, agent);
postRecord.embed = linkEmbed;
} catch (error) {
console.warn('Failed to fetch link metadata, using basic embed:', error);
postRecord.embed = {
$type: 'app.bsky.embed.external',
external: {
uri: linkUrl,
title: linkUrl,
description: 'Shared link',
},
};
}
}
const mainPostResponse = await agent.post(postRecord);
const createdPosts = [mainPostResponse];
if (threadContent && threadContent.length > 0) {
let previousPost = mainPostResponse;
for (let i = 0; i < threadContent.length; i++) {
const threadText = threadContent[i] as string;
if (!threadText || !threadText.trim()) continue;
try {
const threadRichText = new RichText({ text: threadText });
await threadRichText.detectFacets(agent);
if (threadRichText.length > 300) {
console.warn(`Thread post ${i + 1} exceeds 300 characters, skipping`);
continue;
}
const threadPostRecord: any = {
text: threadRichText.text,
facets: threadRichText.facets,
createdAt: new Date().toISOString(),
reply: {
root: { uri: mainPostResponse.uri, cid: mainPostResponse.cid },
parent: { uri: previousPost.uri, cid: previousPost.cid },
},
};
if (language && language !== 'other') {
threadPostRecord.langs = [language];
}
if (contentWarnings && contentWarnings.length > 0) {
threadPostRecord.labels = {
$type: 'com.atproto.label.defs#selfLabels',
values: contentWarnings.map((warning: string) => ({ val: warning })),
};
}
const threadResponse = await agent.post(threadPostRecord);
createdPosts.push(threadResponse);
previousPost = threadResponse;
await new Promise(resolve => setTimeout(resolve, 100));
} catch (threadError) {
console.warn(`Failed to create thread post ${i + 1}:`, threadError);
}
}
}
return {
success: true,
mainPost: {
uri: mainPostResponse.uri,
cid: mainPostResponse.cid,
},
threadPosts: createdPosts.slice(1).map(post => ({
uri: post.uri,
cid: post.cid,
})),
totalPosts: createdPosts.length,
record: postRecord,
};
} catch (error) {
throw new Error(`Failed to create post: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
},
});

View File

@@ -0,0 +1,71 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { blueskyAuth } from '../common/auth';
import { createBlueskyAgent } from '../common/client';
import { postUrlProperty, extractPostInfoFromUrl } from '../common/props';
export const findPost = createAction({
auth: blueskyAuth,
name: 'findPost',
displayName: 'Find Post',
description: 'Get detailed information about a specific post',
props: {
postUrl: postUrlProperty,
},
async run({ auth, propsValue }) {
const { postUrl } = propsValue;
try {
const agent = await createBlueskyAgent(auth.props);
const postInfo = extractPostInfoFromUrl(postUrl);
let atUri = postInfo.uri;
if (!atUri && postInfo.handle && postInfo.postId) {
const didDoc = await agent.resolveHandle({ handle: postInfo.handle });
if (didDoc.data?.did) {
atUri = `at://${didDoc.data.did}/app.bsky.feed.post/${postInfo.postId}`;
} else {
throw new Error(`Could not resolve handle: ${postInfo.handle}`);
}
}
if (!atUri) {
throw new Error('Could not parse the post URL. Please make sure you are using a valid Bluesky post URL like: https://bsky.app/profile/username.bsky.social/post/xxx');
}
if (!atUri.startsWith('at://')) {
throw new Error('Invalid URI format. Must be an AT-URI starting with "at://" or a valid Bluesky URL');
}
const response = await agent.getPosts({ uris: [atUri] });
if (!response.data?.posts || response.data.posts.length === 0) {
throw new Error('Post not found or not accessible');
}
const post = response.data.posts[0];
return {
success: true,
uri: atUri,
cid: post.cid,
record: post.record,
author: post.author,
indexedAt: post.indexedAt,
replyCount: post.replyCount || 0,
repostCount: post.repostCount || 0,
likeCount: post.likeCount || 0,
quoteCount: post.quoteCount || 0,
embed: post.embed,
labels: post.labels,
threadgate: post.threadgate,
viewer: post.viewer,
retrievedAt: new Date().toISOString(),
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
throw new Error(`Failed to find post: ${errorMessage}`);
}
},
});

View File

@@ -0,0 +1,116 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { blueskyAuth } from '../common/auth';
import { createBlueskyAgent } from '../common/client';
import { postUrlProperty, threadDepthDropdown, parentHeightDropdown, extractPostInfoFromUrl } from '../common/props';
export const findThread = createAction({
auth: blueskyAuth,
name: 'findThread',
displayName: 'Find Thread',
description: 'Get a full conversation thread with replies',
props: {
postUrl: postUrlProperty,
depth: threadDepthDropdown,
parentHeight: parentHeightDropdown,
},
async run({ auth, propsValue }) {
const { postUrl, depth = 6, parentHeight = 80 } = propsValue;
try {
const agent = await createBlueskyAgent(auth.props);
const postInfo = extractPostInfoFromUrl(postUrl);
let atUri = postInfo.uri;
if (!atUri && postInfo.handle && postInfo.postId) {
const didDoc = await agent.resolveHandle({ handle: postInfo.handle });
if (didDoc.data?.did) {
atUri = `at://${didDoc.data.did}/app.bsky.feed.post/${postInfo.postId}`;
} else {
throw new Error(`Could not resolve handle: ${postInfo.handle}`);
}
}
if (!atUri) {
throw new Error('Could not parse the post URL. Please make sure you are using a valid Bluesky post URL like: https://bsky.app/profile/username.bsky.social/post/xxx');
}
if (!atUri.startsWith('at://')) {
throw new Error('Invalid URI format. Must be an AT-URI starting with "at://" or a valid Bluesky URL');
}
const depthNum = parseInt(depth.toString(), 10);
const parentHeightNum = parseInt(parentHeight.toString(), 10);
if (isNaN(depthNum) || depthNum < 0 || depthNum > 1000) {
throw new Error('Depth must be a number between 0 and 1000');
}
if (isNaN(parentHeightNum) || parentHeightNum < 0 || parentHeightNum > 1000) {
throw new Error('Parent height must be a number between 0 and 1000');
}
const response = await agent.getPostThread({
uri: atUri,
depth: depthNum,
parentHeight: parentHeightNum,
});
const stats = {
totalPosts: 0,
parentPosts: 0,
replyPosts: 0,
notFoundPosts: 0,
blockedPosts: 0,
};
const countPosts = (threadPost: any): void => {
if (!threadPost) return;
if (threadPost.$type === 'app.bsky.feed.defs#notFoundPost') {
stats.notFoundPosts++;
} else if (threadPost.$type === 'app.bsky.feed.defs#blockedPost') {
stats.blockedPosts++;
} else if (threadPost.post) {
stats.totalPosts++;
}
if (threadPost.parent) {
stats.parentPosts++;
countPosts(threadPost.parent);
}
if (threadPost.replies && Array.isArray(threadPost.replies)) {
stats.replyPosts += threadPost.replies.length;
threadPost.replies.forEach((reply: any) => countPosts(reply));
}
};
if (response.data.thread) {
countPosts(response.data.thread);
if (
response.data.thread.$type === 'app.bsky.feed.defs#threadViewPost' &&
'post' in response.data.thread &&
response.data.thread.post
) {
stats.totalPosts = 1;
}
}
return {
success: true,
thread: response.data.thread,
requestedUri: atUri,
parameters: {
depth: depthNum,
parentHeight: parentHeightNum,
},
statistics: stats,
retrievedAt: new Date().toISOString(),
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
throw new Error(`Failed to find thread: ${errorMessage}`);
}
},
});

View File

@@ -0,0 +1,123 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { blueskyAuth, BlueSkyAuthType } from '../common/auth';
import { createBlueskyAgent } from '../common/client';
import { parseBlueskyUrl } from '../common/props';
export const likePost = createAction({
auth: blueskyAuth,
name: 'likePost',
displayName: 'Like Post',
description: 'Like a post on Bluesky',
props: {
selectionMethod: Property.StaticDropdown({
displayName: 'Select Method',
description: 'How to choose the post',
required: true,
defaultValue: 'timeline',
options: {
options: [
{ label: 'From my timeline', value: 'timeline' },
{ label: 'Enter URL manually', value: 'manual' },
],
},
}),
postSelection: Property.Dropdown({
auth: blueskyAuth,
displayName: 'Select Post',
description: 'Choose from your recent timeline posts (only when "From my timeline" is selected above)',
required: false,
refreshers: ['auth'],
options: async ({ auth }) => {
try {
if (!auth) return { options: [] };
const agent = await createBlueskyAgent(auth.props);
const timeline = await agent.getTimeline({ limit: 50 });
return {
options: timeline.data.feed.map(item => ({
label: `@${item.post.author.handle}: ${item.post.record['text'] ? String(item.post.record['text']).substring(0, 80) : 'Media post'}${String(item.post.record['text'] || '').length > 80 ? '...' : ''} (${new Date(item.post.indexedAt).toLocaleDateString()})`,
value: item.post.uri
}))
};
} catch (error) {
return {
options: [{ label: 'Error loading posts', value: '' }]
};
}
}
}),
postUrl: Property.ShortText({
displayName: 'Post URL',
description: 'Paste the Bluesky post URL',
required: false,
}),
},
async run({ auth, propsValue }) {
const { selectionMethod, postSelection, postUrl } = propsValue;
let postUri: string;
if (selectionMethod === 'timeline') {
if (!postSelection) {
throw new Error('Please select a post from your timeline dropdown');
}
postUri = postSelection as string;
} else if (selectionMethod === 'manual') {
if (!postUrl || !postUrl.trim()) {
throw new Error('Post URL is required when using manual entry method');
}
try {
const agent = await createBlueskyAgent(auth.props);
postUri = await parseBlueskyUrl(postUrl.trim(), agent);
} catch (error) {
throw new Error(`Invalid post URL: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
} else {
throw new Error('Please select a post selection method');
}
try {
const agent = await createBlueskyAgent(auth.props);
const postsResponse = await agent.getPosts({ uris: [postUri] });
if (!postsResponse.data.posts || postsResponse.data.posts.length === 0) {
throw new Error('Post not found');
}
const post = postsResponse.data.posts[0];
const response = await agent.like(postUri, post.cid);
return {
success: true,
likeUri: response.uri,
likeCid: response.cid,
postUri: postUri,
postCid: post.cid,
postAuthor: post.author.handle,
postText: post.record['text'] ? String(post.record['text']).substring(0, 100) + (String(post.record['text']).length > 100 ? '...' : '') : 'No text available',
selectionMethod: selectionMethod,
likedAt: new Date().toISOString(),
};
} catch (error) {
if (error instanceof Error) {
if (error.message.includes('Post not found')) {
throw new Error('Post not found. Please check the URL and try again.');
}
if (error.message.includes('Invalid post URL format')) {
throw new Error('Invalid post URL format. Please use a valid Bluesky post URL or AT-URI.');
}
if (error.message.includes('Authentication')) {
throw new Error('Authentication failed. Please check your credentials.');
}
throw new Error(`Failed to like post: ${error.message}`);
}
throw new Error('Failed to like post: Unknown error occurred');
}
},
});

View File

@@ -0,0 +1,127 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { blueskyAuth, BlueSkyAuthType } from '../common/auth';
import { createBlueskyAgent } from '../common/client';
import { parseBlueskyUrl } from '../common/props';
export const repostPost = createAction({
auth: blueskyAuth,
name: 'repostPost',
displayName: 'Repost Post',
description: 'Share someone else\'s post to your timeline',
props: {
selectionMethod: Property.StaticDropdown({
displayName: 'Select Method',
description: 'How to choose the post',
required: true,
defaultValue: 'timeline',
options: {
options: [
{ label: 'From my timeline', value: 'timeline' },
{ label: 'Enter URL manually', value: 'manual' },
],
},
}),
postSelection: Property.Dropdown({
auth: blueskyAuth,
displayName: 'Select Post',
description: 'Choose from your recent timeline posts (only when "From my timeline" is selected above)',
required: false,
refreshers: ['auth'],
options: async ({ auth }) => {
try {
if (!auth) return { options: [] };
const agent = await createBlueskyAgent(auth.props);
const timeline = await agent.getTimeline({ limit: 50 });
return {
options: timeline.data.feed.map(item => ({
label: `@${item.post.author.handle}: ${item.post.record['text'] ? String(item.post.record['text']).substring(0, 80) : 'Media post'}${String(item.post.record['text'] || '').length > 80 ? '...' : ''} (${new Date(item.post.indexedAt).toLocaleDateString()})`,
value: item.post.uri
}))
};
} catch (error) {
return {
options: [{ label: 'Error loading posts', value: '' }]
};
}
}
}),
postUrl: Property.ShortText({
displayName: 'Post URL',
description: 'Paste the Bluesky post URL',
required: false,
}),
},
async run({ auth, propsValue }) {
const { selectionMethod, postSelection, postUrl } = propsValue;
let postUri: string;
if (selectionMethod === 'timeline') {
if (!postSelection) {
throw new Error('Please select a post from your timeline dropdown');
}
postUri = postSelection as string;
} else if (selectionMethod === 'manual') {
if (!postUrl || !postUrl.trim()) {
throw new Error('Post URL is required when using manual entry method');
}
try {
const agent = await createBlueskyAgent(auth.props);
postUri = await parseBlueskyUrl(postUrl.trim(), agent);
} catch (error) {
throw new Error(`Invalid post URL: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
} else {
throw new Error('Please select a post selection method');
}
try {
const agent = await createBlueskyAgent(auth.props);
const postsResponse = await agent.getPosts({ uris: [postUri] });
if (!postsResponse.data.posts || postsResponse.data.posts.length === 0) {
throw new Error('Post not found');
}
const post = postsResponse.data.posts[0];
const response = await agent.repost(postUri, post.cid);
return {
success: true,
repostUri: response.uri,
repostCid: response.cid,
originalPost: {
uri: postUri,
cid: post.cid,
author: post.author.handle,
text: post.record['text'] ? String(post.record['text']).substring(0, 100) + (String(post.record['text']).length > 100 ? '...' : '') : 'No text available',
createdAt: post.record['createdAt'] || post.indexedAt,
},
selectionMethod: selectionMethod,
repostedAt: new Date().toISOString(),
};
} catch (error) {
if (error instanceof Error) {
if (error.message.includes('Post not found')) {
throw new Error('Post not found. Please check the URL and try again.');
}
if (error.message.includes('Invalid post URL format')) {
throw new Error('Invalid post URL format. Please use a valid Bluesky post URL or AT-URI.');
}
if (error.message.includes('Authentication')) {
throw new Error('Authentication failed. Please check your credentials.');
}
throw new Error(`Failed to repost: ${error.message}`);
}
throw new Error('Failed to repost: Unknown error occurred');
}
},
});

View File

@@ -0,0 +1,74 @@
import { PieceAuth, Property } from '@activepieces/pieces-framework';
import { AtpAgent } from '@atproto/api';
export interface BlueSkyAuthType {
pdsHost?: string;
identifier: string;
password: string;
}
const description = `
To authenticate with Bluesky:
1. **PDS Host**: The Personal Data Server host. Leave empty to use the default Bluesky network (https://bsky.social).
2. **Identifier**: Your Bluesky handle (e.g., yourhandle.bsky.social) or email address.
3. **Password**: Your Bluesky account password or app password.
For enhanced security, consider using an app password from your Bluesky account settings.
`;
export const blueskyAuth = PieceAuth.CustomAuth({
description: description,
required: true,
props: {
pdsHost: Property.ShortText({
displayName: 'PDS Host',
description: 'The Personal Data Server host. Leave empty for default Bluesky network (https://bsky.social)',
required: false,
defaultValue: 'https://bsky.social',
}),
identifier: Property.ShortText({
displayName: 'Identifier',
description: 'Your Bluesky handle or email address',
required: true,
}),
password: PieceAuth.SecretText({
displayName: 'Password',
description: 'Your Bluesky account password or app password',
required: true,
}),
},
validate: async ({ auth }) => {
try {
const agent = new AtpAgent({
service: auth.pdsHost || 'https://bsky.social',
});
await agent.login({
identifier: auth.identifier,
password: auth.password,
});
return {
valid: true,
};
} catch (error: any) {
if (error.message?.includes('Invalid identifier or password')) {
return {
valid: false,
error: 'Invalid credentials. Please check your identifier and password.',
};
} else if (error.message?.includes('Invalid request')) {
return {
valid: false,
error: 'Invalid request. Please check your identifier format.',
};
} else {
return {
valid: false,
error: `Authentication failed: ${error.message || 'Unknown error'}`,
};
}
}
},
});

View File

@@ -0,0 +1,42 @@
import { AtpAgent } from '@atproto/api';
import { BlueSkyAuthType } from './auth';
const agentCache = new Map<string, { agent: AtpAgent; expires: number }>();
export async function createBlueskyAgent(auth: BlueSkyAuthType): Promise<AtpAgent> {
const cacheKey = `${auth.identifier}:${auth.pdsHost || 'https://bsky.social'}`;
const cached = agentCache.get(cacheKey);
if (cached && Date.now() < cached.expires) {
return cached.agent;
}
try {
const agent = new AtpAgent({
service: auth.pdsHost || 'https://bsky.social',
});
await agent.login({
identifier: auth.identifier,
password: auth.password,
});
agentCache.set(cacheKey, {
agent,
expires: Date.now() + 50 * 60 * 1000,
});
return agent;
} catch (error: any) {
agentCache.delete(cacheKey);
throw new Error(`Failed to create Bluesky agent: ${error.message || 'Unknown error'}`);
}
}
export async function getCurrentSession(auth: BlueSkyAuthType) {
const agent = await createBlueskyAgent(auth);
return {
did: agent.session?.did,
handle: agent.session?.handle,
};
}

View File

@@ -0,0 +1,392 @@
import { Property } from '@activepieces/pieces-framework';
import { blueskyAuth } from './auth';
export const languageOptions = [
{ label: 'English', value: 'en' },
{ label: 'Spanish', value: 'es' },
{ label: 'French', value: 'fr' },
{ label: 'German', value: 'de' },
{ label: 'Italian', value: 'it' },
{ label: 'Portuguese', value: 'pt' },
{ label: 'Russian', value: 'ru' },
{ label: 'Japanese', value: 'ja' },
{ label: 'Korean', value: 'ko' },
{ label: 'Chinese (Simplified)', value: 'zh' },
{ label: 'Chinese (Traditional)', value: 'zh-TW' },
{ label: 'Arabic', value: 'ar' },
{ label: 'Hindi', value: 'hi' },
{ label: 'Dutch', value: 'nl' },
{ label: 'Swedish', value: 'sv' },
{ label: 'Norwegian', value: 'no' },
{ label: 'Danish', value: 'da' },
{ label: 'Finnish', value: 'fi' },
{ label: 'Polish', value: 'pl' },
{ label: 'Czech', value: 'cs' },
{ label: 'Hungarian', value: 'hu' },
{ label: 'Romanian', value: 'ro' },
{ label: 'Greek', value: 'el' },
{ label: 'Turkish', value: 'tr' },
{ label: 'Hebrew', value: 'he' },
{ label: 'Thai', value: 'th' },
{ label: 'Vietnamese', value: 'vi' },
{ label: 'Indonesian', value: 'id' },
{ label: 'Malay', value: 'ms' },
{ label: 'Filipino', value: 'fil' },
];
export const languageDropdown = Property.StaticDropdown({
displayName: 'Language',
description: 'Select the language for the post',
required: false,
options: {
options: languageOptions,
},
});
export const multiLanguageDropdown = Property.StaticMultiSelectDropdown({
displayName: 'Languages',
description: 'Select one or more languages for the post',
required: false,
options: {
options: languageOptions,
},
});
export const threadDepthDropdown = Property.StaticDropdown({
displayName: 'Thread Depth',
description: 'How many levels deep to retrieve replies',
required: false,
defaultValue: '10',
options: {
options: [
{ label: '1 level', value: '1' },
{ label: '2 levels', value: '2' },
{ label: '3 levels', value: '3' },
{ label: '5 levels', value: '5' },
{ label: '10 levels', value: '10' },
{ label: '20 levels', value: '20' },
{ label: '50 levels', value: '50' },
{ label: '100 levels (max)', value: '100' },
],
},
});
export const parentHeightDropdown = Property.StaticDropdown({
displayName: 'Parent Height',
description: 'How many parent posts to retrieve',
required: false,
defaultValue: '3',
options: {
options: [
{ label: 'No parents', value: '0' },
{ label: '1 parent', value: '1' },
{ label: '2 parents', value: '2' },
{ label: '3 parents', value: '3' },
{ label: '5 parents', value: '5' },
{ label: '10 parents', value: '10' },
{ label: '20 parents', value: '20' },
{ label: 'All parents (80 max)', value: '80' },
],
},
});
export const visibilityDropdown = Property.StaticDropdown({
displayName: 'Visibility',
description: 'Post visibility setting',
required: false,
defaultValue: 'public',
options: {
options: [
{ label: 'Public', value: 'public' },
{ label: 'Unlisted', value: 'unlisted' },
],
},
});
export const moderationLabelDropdown = Property.StaticMultiSelectDropdown({
displayName: 'Content Labels',
description: 'Apply content moderation labels to the post',
required: false,
options: {
options: [
{ label: 'Adult Content', value: 'adult' },
{ label: 'Graphic Media', value: 'graphic-media' },
{ label: 'Nudity', value: 'nudity' },
{ label: 'Sexual', value: 'sexual' },
{ label: 'Violence', value: 'violence' },
{ label: 'Self Harm', value: 'self-harm' },
{ label: 'Spam', value: 'spam' },
{ label: 'Impersonation', value: 'impersonation' },
],
},
});
export const postUrlProperty = Property.ShortText({
displayName: 'Post URL',
description: 'Paste the Bluesky post URL (e.g., https://bsky.app/profile/username.bsky.social/post/xxx)',
required: true,
});
export const userHandleProperty = Property.ShortText({
displayName: 'User Handle',
description: 'Bluesky username (e.g., username.bsky.social)',
required: true,
});
export const authorSelectionProperty = Property.Dropdown({
displayName: 'Select Author',
description: 'Choose from accounts you follow',
required: false,
auth: blueskyAuth,
refreshers: ['auth'],
options: async ({ auth }) => {
try {
const { createBlueskyAgent } = await import('./client');
if (!auth) return { options: [] };
const agent = await createBlueskyAgent(auth.props);
const session = agent.session;
if (!session?.did) {
return { options: [{ label: 'Please authenticate first', value: '' }] };
}
const followingResponse = await agent.getFollows({
actor: session.did,
limit: 100
});
const options = followingResponse.data.follows.map((follow: any) => ({
label: `${follow.displayName || follow.handle} (@${follow.handle})`,
value: follow.handle
}));
options.unshift({ label: 'Enter handle manually', value: 'manual' });
return { options };
} catch (error) {
return {
options: [{ label: 'Error loading following list', value: '' }]
};
}
}
});
export const postSearchProperty = Property.ShortText({
displayName: 'Search Posts',
description: 'Search for posts by keywords, hashtags, or content',
required: false,
});
export const postTextProperty = Property.LongText({
displayName: 'Post Text',
description: 'What do you want to post? (Max 300 characters)',
required: true,
});
export const imageUrlsProperty = Property.Array({
displayName: 'Image URLs',
description: 'Add up to 4 images by URL',
required: false,
});
export const imageDescriptionsProperty = Property.Array({
displayName: 'Image Descriptions',
description: 'Describe each image for accessibility',
required: false,
});
export const linkUrlProperty = Property.ShortText({
displayName: 'Link to Share',
description: 'URL to share with your post',
required: false,
});
export const replyToPostProperty = Property.ShortText({
displayName: 'Reply to Post',
description: 'URL of post to reply to',
required: false,
});
export const postTypeDropdown = Property.StaticDropdown({
displayName: 'Post Type',
description: 'Type of content you\'re sharing',
required: false,
defaultValue: 'text',
options: {
options: [
{ label: 'Text Post', value: 'text' },
{ label: 'Photo Post', value: 'photo' },
{ label: 'Link Share', value: 'link' },
{ label: 'Reply', value: 'reply' },
{ label: 'Repost with Comment', value: 'quote' },
],
},
});
export const simpleLanguageDropdown = Property.StaticDropdown({
displayName: 'Post Language',
description: 'Language of your post',
required: false,
defaultValue: 'en',
options: {
options: [
{ label: 'English', value: 'en' },
{ label: 'Spanish', value: 'es' },
{ label: 'French', value: 'fr' },
{ label: 'German', value: 'de' },
{ label: 'Italian', value: 'it' },
{ label: 'Portuguese', value: 'pt' },
{ label: 'Japanese', value: 'ja' },
{ label: 'Korean', value: 'ko' },
{ label: 'Chinese', value: 'zh' },
{ label: 'Russian', value: 'ru' },
{ label: 'Arabic', value: 'ar' },
{ label: 'Hindi', value: 'hi' },
{ label: 'Dutch', value: 'nl' },
{ label: 'Swedish', value: 'sv' },
{ label: 'Other', value: 'other' },
],
},
});
export const replyDepthDropdown = Property.StaticDropdown({
displayName: 'How many replies to show?',
description: 'Choose how deep to go into the conversation',
required: false,
defaultValue: '10',
options: {
options: [
{ label: 'Just direct replies (1 level)', value: '1' },
{ label: 'Short conversation (3 levels)', value: '3' },
{ label: 'Full conversation (10 levels)', value: '10' },
{ label: 'Entire thread (50+ levels)', value: '50' },
],
},
});
export const parentPostsDropdown = Property.StaticDropdown({
displayName: 'Show conversation context?',
description: 'Include previous posts in the thread for context',
required: false,
defaultValue: '3',
options: {
options: [
{ label: 'No context - just this post', value: '0' },
{ label: 'Recent context (3 previous posts)', value: '3' },
{ label: 'Full context (10 previous posts)', value: '10' },
{ label: 'Complete thread history', value: '80' },
],
},
});
export const contentWarningDropdown = Property.StaticMultiSelectDropdown({
displayName: 'Content Warnings',
description: 'Add warnings for sensitive content',
required: false,
options: {
options: [
{ label: 'Adult Content', value: 'adult' },
{ label: 'Graphic Content', value: 'graphic-media' },
{ label: 'Sensitive Topic', value: 'sensitive' },
{ label: 'Violence', value: 'violence' },
{ label: 'Spam/Promotional', value: 'spam' },
],
},
});
export const audienceDropdown = Property.StaticDropdown({
displayName: 'Audience',
description: 'Who can see this post',
required: false,
defaultValue: 'public',
options: {
options: [
{ label: 'Everyone (Public)', value: 'public' },
{ label: 'Followers only', value: 'followers' },
{ label: 'Private/Unlisted', value: 'unlisted' },
],
},
});
export function extractPostInfoFromUrl(url: string): { uri?: string; handle?: string; postId?: string } {
if (url.startsWith('at://')) {
return { uri: url };
}
const urlMatch = url.match(/https?:\/\/bsky\.app\/profile\/([^/]+)\/post\/([^/?]+)/);
if (urlMatch) {
const handle = urlMatch[1];
const postId = urlMatch[2];
return {
handle: handle,
postId: postId,
};
}
return {};
}
export function normalizeHandle(handle: string): string {
let cleanHandle = handle.replace(/^@/, '');
if (!cleanHandle.includes('.')) {
cleanHandle += '.bsky.social';
}
return cleanHandle;
}
export function createSimpleExternalLink(url: string): { uri: string; title: string; description?: string } {
return {
uri: url,
title: url,
description: 'Shared link'
};
}
export const postUriProperty = Property.ShortText({
displayName: 'Post URI (Advanced)',
description: 'Technical AT-URI format (at://did:plc:xxx/app.bsky.feed.post/xxx) - use Post URL field instead',
required: false,
});
export const externalLinkProperty = Property.Object({
displayName: 'External Link (Advanced)',
description: 'Manual link object - use "Link to Share" field instead',
required: false,
});
export const replyToProperty = Property.Object({
displayName: 'Reply To (Advanced)',
description: 'Manual reply object - use "Reply to Post" field instead',
required: false,
});
export async function parseBlueskyUrl(url: string, agent: any): Promise<string> {
if (url.startsWith('at://')) {
if (!url.match(/^at:\/\/did:plc:[a-z0-9]+\/app\.bsky\.feed\.post\/[a-z0-9]+$/)) {
throw new Error('Invalid AT-URI format');
}
return url;
}
const urlMatch = url.match(/https?:\/\/bsky\.app\/profile\/([^/]+)\/post\/([^/?]+)/);
if (urlMatch) {
const handle = urlMatch[1];
const postId = urlMatch[2];
if (!handle.includes('.') && !handle.includes('@')) {
throw new Error('Invalid handle format in URL');
}
const didDoc = await agent.resolveHandle({ handle: handle.replace('@', '') });
return `at://${didDoc.data.did}/app.bsky.feed.post/${postId}`;
}
throw new Error('Invalid post URL format. Please use a valid Bluesky post URL or AT-URI.');
}

View File

@@ -0,0 +1,109 @@
import { createTrigger, TriggerStrategy, PiecePropValueSchema, AppConnectionValueForAuthProperty } from '@activepieces/pieces-framework';
import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common';
import { blueskyAuth } from '../common/auth';
import { createBlueskyAgent } from '../common/client';
import dayjs from 'dayjs';
const polling: Polling<AppConnectionValueForAuthProperty<typeof blueskyAuth>, Record<string, never>> = {
strategy: DedupeStrategy.TIMEBASED,
items: async ({ auth, lastFetchEpochMS }) => {
try {
const agent = await createBlueskyAgent(auth.props);
const session = agent.session;
if (!session?.did) {
throw new Error('Could not get user DID from session');
}
const userDid = session.did;
const response = await agent.getFollowers({
actor: userDid,
limit: 100
});
if (!response.data?.followers || !Array.isArray(response.data.followers)) {
return [];
}
const currentTime = Date.now();
const cutoffTime = lastFetchEpochMS || 0;
return response.data.followers
.filter((follower: any) => {
const profileTime = follower.indexedAt ? dayjs(follower.indexedAt).valueOf() : currentTime;
return profileTime > cutoffTime - (24 * 60 * 60 * 1000);
})
.map((follower: any) => ({
epochMilliSeconds: follower.indexedAt ? dayjs(follower.indexedAt).valueOf() : currentTime,
data: {
did: follower.did,
handle: follower.handle,
displayName: follower.displayName || follower.handle,
description: follower.description || '',
avatar: follower.avatar || '',
banner: follower.banner || '',
followersCount: follower.followersCount || 0,
followsCount: follower.followsCount || 0,
postsCount: follower.postsCount || 0,
indexedAt: follower.indexedAt || new Date().toISOString(),
viewer: follower.viewer || {},
labels: follower.labels || [],
createdAt: follower.createdAt || null
}
}))
.sort((a: any, b: any) => b.epochMilliSeconds - a.epochMilliSeconds);
} catch (error) {
console.error('Error fetching followers:', error);
return [];
}
}
};
export const newFollowerOnAccount = createTrigger({
auth: blueskyAuth,
name: 'newFollowerOnAccount',
displayName: 'New Follower on Account',
description: 'Triggers when someone new follows your Bluesky account',
props: {},
sampleData: {
did: 'did:plc:example123',
handle: 'newfollower.bsky.social',
displayName: 'New Follower',
description: 'A new user who just followed your account',
avatar: 'https://cdn.bsky.app/img/avatar/plain/did:plc:example123/example@jpeg',
banner: 'https://cdn.bsky.app/img/banner/plain/did:plc:example123/example@jpeg',
followersCount: 42,
followsCount: 156,
postsCount: 78,
indexedAt: '2024-01-01T12:00:00.000Z',
viewer: {
muted: false,
blockedBy: false,
following: 'at://did:plc:example123/app.bsky.graph.follow/example456',
followedBy: 'at://did:plc:example456/app.bsky.graph.follow/example789'
},
labels: [],
createdAt: '2023-06-01T12:00:00.000Z'
},
type: TriggerStrategy.POLLING,
async test(context) {
return await pollingHelper.test(polling, context);
},
async onEnable(context) {
const { store, auth, propsValue } = context;
await pollingHelper.onEnable(polling, { store, auth, propsValue });
},
async onDisable(context) {
const { store, auth, propsValue } = context;
await pollingHelper.onDisable(polling, { store, auth, propsValue });
},
async run(context) {
return await pollingHelper.poll(polling, context);
},
});

View File

@@ -0,0 +1,222 @@
import { createTrigger, TriggerStrategy, PiecePropValueSchema, Property, AppConnectionValueForAuthProperty } from '@activepieces/pieces-framework';
import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common';
import { blueskyAuth } from '../common/auth';
import { createBlueskyAgent } from '../common/client';
import { simpleLanguageDropdown } from '../common/props';
import dayjs from 'dayjs';
const polling: Polling<AppConnectionValueForAuthProperty<typeof blueskyAuth>, {
searchQuery: string;
searchLanguage?: string;
includeImages?: boolean;
includeVideos?: boolean;
sortBy?: string;
}> = {
strategy: DedupeStrategy.TIMEBASED,
items: async ({ auth, propsValue, lastFetchEpochMS }) => {
const { searchQuery, searchLanguage, includeImages, includeVideos, sortBy } = propsValue;
try {
if (!searchQuery || searchQuery.trim().length === 0) {
return [];
}
const agent = await createBlueskyAgent(auth.props);
const searchParams: any = {
q: searchQuery.trim(),
limit: 50,
sort: sortBy || 'latest'
};
if (searchLanguage && searchLanguage !== 'other') {
searchParams.lang = searchLanguage;
}
const response = await agent.api.app.bsky.feed.searchPosts(searchParams);
if (!response.data?.posts || !Array.isArray(response.data.posts)) {
return [];
}
const cutoffTime = lastFetchEpochMS || 0;
return response.data.posts
.filter((post: any) => {
if (!post.indexedAt) return false;
const postTime = dayjs(post.indexedAt).valueOf();
if (postTime <= cutoffTime) return false;
if (includeImages === false && post.embed?.images) return false;
if (includeVideos === false && post.embed?.video) return false;
if (includeImages === true && !post.embed?.images) return false;
if (includeVideos === true && !post.embed?.video) return false;
return true;
})
.map((post: any) => ({
epochMilliSeconds: dayjs(post.indexedAt).valueOf(),
data: {
uri: post.uri,
cid: post.cid,
author: post.author,
record: post.record,
indexedAt: post.indexedAt,
replyCount: post.replyCount || 0,
repostCount: post.repostCount || 0,
likeCount: post.likeCount || 0,
quoteCount: post.quoteCount || 0,
labels: post.labels || [],
viewer: post.viewer || {},
embed: post.embed || null,
searchContext: {
query: searchQuery,
language: searchLanguage || null,
matchedTerms: extractMatchedTerms(post.record?.text || '', searchQuery),
hasImages: !!(post.embed?.images),
hasVideo: !!(post.embed?.video),
hasExternalLink: !!(post.embed?.external)
}
}
}))
.sort((a: any, b: any) => b.epochMilliSeconds - a.epochMilliSeconds);
} catch (error) {
console.warn('Failed to search posts:', error instanceof Error ? error.message : 'Unknown error');
return [];
}
}
};
function extractMatchedTerms(text: string, query: string): string[] {
if (!text || !query) return [];
const cleanQuery = query.toLowerCase()
.replace(/["']/g, '')
.replace(/\s+(or|and)\s+/gi, ' ');
const terms = cleanQuery.split(/\s+/).filter(term => term.length > 0 && !['or', 'and'].includes(term.toLowerCase()));
const matchedTerms: string[] = [];
const lowerText = text.toLowerCase();
terms.forEach(term => {
if (term.startsWith('#') || term.startsWith('@')) {
if (lowerText.includes(term)) {
matchedTerms.push(term);
}
} else if (lowerText.includes(term)) {
matchedTerms.push(term);
}
});
return [...new Set(matchedTerms)];
}
export const newPost = createTrigger({
auth: blueskyAuth,
name: 'newPost',
displayName: 'New Post (with Search Options)',
description: 'Triggers when posts match your search criteria',
props: {
searchQuery: Property.ShortText({
displayName: 'Search Query',
description: 'Keywords, hashtags (#example), or mentions (@handle) to find',
required: true
}),
searchLanguage: {
...simpleLanguageDropdown,
displayName: 'Language Filter',
description: 'Filter by language',
required: false
},
includeImages: Property.Checkbox({
displayName: 'Filter by Images',
description: 'Only posts with/without images',
required: false,
defaultValue: undefined
}),
includeVideos: Property.Checkbox({
displayName: 'Filter by Videos',
description: 'Only posts with/without videos',
required: false,
defaultValue: undefined
}),
sortBy: Property.StaticDropdown({
displayName: 'Sort Order',
description: 'How to sort results',
required: false,
defaultValue: 'latest',
options: {
options: [
{ label: 'Latest First', value: 'latest' },
{ label: 'Most Popular', value: 'top' }
]
}
})
},
sampleData: {
uri: 'at://did:plc:example123/app.bsky.feed.post/example456',
cid: 'bafyreib2rxk3vcfbqij7y6kzgy4knknc7ff4t5jn2m5fbn6jdl7czfqyqe',
author: {
did: 'did:plc:example123',
handle: 'searchauthor.bsky.social',
displayName: 'Search Result Author',
avatar: 'https://cdn.bsky.app/img/avatar/plain/did:plc:example123/example@jpeg',
viewer: {
muted: false,
blockedBy: false
}
},
record: {
$type: 'app.bsky.feed.post',
createdAt: '2024-01-01T12:00:00.000Z',
text: 'This post matches your search criteria! #automation #activepieces',
langs: ['en']
},
indexedAt: '2024-01-01T12:00:00.000Z',
replyCount: 2,
repostCount: 5,
likeCount: 12,
quoteCount: 1,
labels: [],
viewer: {
repost: null,
like: null
},
embed: null,
searchContext: {
query: 'automation',
language: 'en',
matchedTerms: ['automation'],
hasImages: false,
hasVideo: false,
hasExternalLink: false
}
},
type: TriggerStrategy.POLLING,
async test(context) {
return await pollingHelper.test(polling, context);
},
async onEnable(context) {
const { store, auth, propsValue } = context;
await pollingHelper.onEnable(polling, { store, auth, propsValue });
},
async onDisable(context) {
const { store, auth, propsValue } = context;
await pollingHelper.onDisable(polling, { store, auth, propsValue });
},
async run(context) {
return await pollingHelper.poll(polling, context);
},
});

View File

@@ -0,0 +1,249 @@
import { createTrigger, TriggerStrategy, PiecePropValueSchema, Property, AppConnectionValueForAuthProperty } from '@activepieces/pieces-framework';
import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common';
import { blueskyAuth, BlueSkyAuthType } from '../common/auth';
import { createBlueskyAgent } from '../common/client';
import dayjs from 'dayjs';
const polling: Polling<AppConnectionValueForAuthProperty<typeof blueskyAuth>, {
authorSelection: string;
authorFromFollowing?: string;
authorHandle?: string;
includeReplies?: boolean;
includeReposts?: boolean;
}> = {
strategy: DedupeStrategy.TIMEBASED,
items: async ({ auth, propsValue, lastFetchEpochMS }) => {
const { authorSelection, authorFromFollowing, authorHandle, includeReplies = false, includeReposts = false } = propsValue;
let selectedAuthorHandle: string;
if (authorSelection === 'following') {
if (!authorFromFollowing) {
return [];
}
selectedAuthorHandle = authorFromFollowing;
} else if (authorSelection === 'manual') {
if (!authorHandle) {
return [];
}
selectedAuthorHandle = authorHandle;
} else {
return [];
}
try {
if (!selectedAuthorHandle || selectedAuthorHandle.trim().length === 0) {
return [];
}
const agent = await createBlueskyAgent(auth.props);
const normalizedHandle = selectedAuthorHandle.replace('@', '').trim();
const response = await agent.getAuthorFeed({
actor: normalizedHandle,
limit: 50,
filter: 'posts_with_replies'
});
if (!response.data?.feed || !Array.isArray(response.data.feed)) {
return [];
}
const cutoffTime = lastFetchEpochMS || 0;
return response.data.feed
.filter((feedItem: any) => {
const post = feedItem.post;
if (!post || !post.indexedAt) return false;
const postTime = dayjs(post.indexedAt).valueOf();
if (postTime <= cutoffTime) return false;
const isReply = post.record?.reply !== undefined;
const isRepost = feedItem.reason?.$type === 'app.bsky.feed.defs#reasonRepost';
if (isReply && !includeReplies) return false;
if (isRepost && !includeReposts) return false;
return post.author.handle === normalizedHandle || post.author.did === normalizedHandle;
})
.map((feedItem: any) => {
const post = feedItem.post;
return {
epochMilliSeconds: dayjs(post.indexedAt).valueOf(),
data: {
uri: post.uri,
cid: post.cid,
author: post.author,
record: post.record,
indexedAt: post.indexedAt,
replyCount: post.replyCount || 0,
repostCount: post.repostCount || 0,
likeCount: post.likeCount || 0,
quoteCount: post.quoteCount || 0,
labels: post.labels || [],
viewer: post.viewer || {},
embed: post.embed || null,
postContext: {
authorHandle: normalizedHandle,
isReply: post.record?.reply !== undefined,
isRepost: feedItem.reason?.$type === 'app.bsky.feed.defs#reasonRepost',
replyTo: post.record?.reply?.parent?.uri || null,
hasImages: !!(post.embed?.images),
hasVideo: !!(post.embed?.video),
hasExternalLink: !!(post.embed?.external)
}
}
};
})
.sort((a: any, b: any) => b.epochMilliSeconds - a.epochMilliSeconds);
} catch (error) {
console.warn('Failed to fetch author posts:', error instanceof Error ? error.message : 'Unknown error');
return [];
}
}
};
export const newPostsByAuthor = createTrigger({
auth: blueskyAuth,
name: 'newPostsByAuthor',
displayName: 'New Posts by Author',
description: 'Triggers when a selected author creates a new post',
props: {
authorSelection: Property.StaticDropdown({
displayName: 'How to select author?',
description: 'Choose how to select the author',
required: true,
defaultValue: 'following',
options: {
options: [
{ label: 'From my following list', value: 'following' },
{ label: 'Enter handle manually', value: 'manual' },
],
},
}),
authorFromFollowing: Property.Dropdown({
auth: blueskyAuth,
displayName: 'Select Author',
description: 'Choose from accounts you follow',
required: false,
refreshers: ['auth'],
options: async ({ auth }) => {
try {
if (!auth) return { options: [] };
const agent = await createBlueskyAgent(auth.props);
const session = agent.session;
if (!session?.did) {
return { options: [{ label: 'Please authenticate first', value: '' }] };
}
const followingResponse = await agent.getFollows({
actor: session.did,
limit: 100
});
return {
options: followingResponse.data.follows.map(follow => ({
label: `${follow.displayName || follow.handle} (@${follow.handle})`,
value: follow.handle
}))
};
} catch (error) {
return {
options: [{ label: 'Error loading following list', value: '' }]
};
}
}
}),
authorHandle: Property.ShortText({
displayName: 'Author Handle',
description: 'Enter the Bluesky username (e.g., username.bsky.social)',
required: false
}),
includeReplies: Property.Checkbox({
displayName: 'Include Replies',
description: 'Include reply posts by this author',
required: false,
defaultValue: false
}),
includeReposts: Property.Checkbox({
displayName: 'Include Reposts',
description: 'Include posts that this author reposted',
required: false,
defaultValue: false
})
},
sampleData: {
uri: 'at://did:plc:example123/app.bsky.feed.post/example456',
cid: 'bafyreib2rxk3vcfbqij7y6kzgy4knknc7ff4t5jn2m5fbn6jdl7czfqyqe',
author: {
did: 'did:plc:example123',
handle: 'author.bsky.social',
displayName: 'Example Author',
avatar: 'https://cdn.bsky.app/img/avatar/plain/did:plc:example123/example@jpeg',
followersCount: 1500,
followsCount: 300,
postsCount: 250,
viewer: {
muted: false,
blockedBy: false,
following: 'at://following-record-uri'
}
},
record: {
$type: 'app.bsky.feed.post',
createdAt: '2024-01-01T12:00:00.000Z',
text: 'Just posted something new! Excited to share this with everyone.',
langs: ['en']
},
indexedAt: '2024-01-01T12:00:00.000Z',
replyCount: 3,
repostCount: 8,
likeCount: 25,
quoteCount: 2,
labels: [],
viewer: {
repost: null,
like: null
},
embed: null,
postContext: {
authorHandle: 'author.bsky.social',
isReply: false,
isRepost: false,
replyTo: null,
hasImages: false,
hasVideo: false,
hasExternalLink: false
}
},
type: TriggerStrategy.POLLING,
async test(context) {
return await pollingHelper.test(polling, context);
},
async onEnable(context) {
const { store, auth, propsValue } = context;
await pollingHelper.onEnable(polling, { store, auth, propsValue });
},
async onDisable(context) {
const { store, auth, propsValue } = context;
await pollingHelper.onDisable(polling, { store, auth, propsValue });
},
async run(context) {
return await pollingHelper.poll(polling, context);
},
});

View File

@@ -0,0 +1,138 @@
import { createTrigger, TriggerStrategy, PiecePropValueSchema, AppConnectionValueForAuthProperty } from '@activepieces/pieces-framework';
import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common';
import { blueskyAuth } from '../common/auth';
import { createBlueskyAgent } from '../common/client';
import dayjs from 'dayjs';
const polling: Polling<AppConnectionValueForAuthProperty<typeof blueskyAuth>, Record<string, never>> = {
strategy: DedupeStrategy.TIMEBASED,
items: async ({ auth, lastFetchEpochMS }) => {
try {
const agent = await createBlueskyAgent(auth.props);
const response = await agent.getTimeline({
limit: 50
});
if (!response.data?.feed || !Array.isArray(response.data.feed)) {
return [];
}
const cutoffTime = lastFetchEpochMS || 0;
return response.data.feed
.filter((item: any) => {
if (!item.post || !item.post.indexedAt) return false;
const postTime = dayjs(item.post.indexedAt).valueOf();
return postTime > cutoffTime;
})
.map((item: any) => ({
epochMilliSeconds: dayjs(item.post.indexedAt).valueOf(),
data: {
uri: item.post.uri,
cid: item.post.cid,
author: item.post.author,
record: item.post.record,
indexedAt: item.post.indexedAt,
replyCount: item.post.replyCount || 0,
repostCount: item.post.repostCount || 0,
likeCount: item.post.likeCount || 0,
quoteCount: item.post.quoteCount || 0,
labels: item.post.labels || [],
viewer: item.post.viewer || {},
embed: item.post.embed || null,
reason: item.reason || null,
reply: item.reply || null,
feedContext: {
isRepost: !!item.reason,
repostBy: item.reason?.by || null,
isReply: !!item.reply,
replyToPost: item.reply?.parent || null,
replyToRoot: item.reply?.root || null
}
}
}))
.sort((a: any, b: any) => b.epochMilliSeconds - a.epochMilliSeconds);
} catch (error) {
console.warn('Failed to fetch timeline posts:', error instanceof Error ? error.message : 'Unknown error');
return [];
}
}
};
export const newTimelinePosts = createTrigger({
auth: blueskyAuth,
name: 'newTimelinePosts',
displayName: 'New Timeline Posts',
description: 'Triggers when new posts appear in your timeline',
props: {},
sampleData: {
uri: 'at://did:plc:example123/app.bsky.feed.post/example456',
cid: 'bafyreib2rxk3vcfbqij7y6kzgy4knknc7ff4t5jn2m5fbn6jdl7czfqyqe',
author: {
did: 'did:plc:example123',
handle: 'author.bsky.social',
displayName: 'Example Author',
avatar: 'https://cdn.bsky.app/img/avatar/plain/did:plc:example123/example@jpeg',
viewer: {
muted: false,
blockedBy: false,
following: 'at://did:plc:user456/app.bsky.graph.follow/example789'
}
},
record: {
$type: 'app.bsky.feed.post',
createdAt: '2024-01-01T12:00:00.000Z',
text: 'This is a new post in your timeline!',
langs: ['en']
},
indexedAt: '2024-01-01T12:00:00.000Z',
replyCount: 3,
repostCount: 8,
likeCount: 15,
quoteCount: 2,
labels: [],
viewer: {
repost: null,
like: null
},
embed: null,
reason: null,
reply: null,
feedContext: {
isRepost: false,
repostBy: null,
isReply: false,
replyToPost: null,
replyToRoot: null
}
},
type: TriggerStrategy.POLLING,
async test(context) {
return await pollingHelper.test(polling, context);
},
async onEnable(context) {
const { store, auth, propsValue } = context;
await pollingHelper.onEnable(polling, { store, auth, propsValue });
},
async onDisable(context) {
const { store, auth, propsValue } = context;
await pollingHelper.onDisable(polling, { store, auth, propsValue });
},
async run(context) {
return await pollingHelper.poll(polling, context);
},
});

View File

@@ -0,0 +1,20 @@
{
"extends": "../../../../tsconfig.base.json",
"compilerOptions": {
"module": "commonjs",
"forceConsistentCasingInFileNames": true,
"strict": true,
"importHelpers": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noPropertyAccessFromIndexSignature": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}

View File

@@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../../dist/out-tsc",
"declaration": true,
"types": ["node"]
},
"include": ["src/**/*.ts"]
}