n8n-nodes-bluesky
Version:
⚠️ PROOF OF CONCEPT - Bluesky nodes for n8n (unmaintained, use at own risk)
243 lines (242 loc) • 10.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.BlueskySearch = void 0;
class BlueskySearch {
constructor() {
this.description = {
displayName: 'Bluesky Search',
name: 'blueskySearch',
group: ['transform'],
version: 1,
description: 'Search Bluesky posts using the AT Protocol',
defaults: { name: 'Bluesky Search' },
icon: 'file:bluesky.svg',
credentials: [
{ name: 'blueskyApi', required: true },
],
inputs: ['main'],
outputs: ['main'],
properties: [
{
displayName: 'Query',
name: 'query',
type: 'string',
default: '',
placeholder: 'search terms',
description: 'Search query (supports Lucene syntax)',
required: true,
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
typeOptions: { minValue: 1, maxValue: 100 },
default: 25,
description: 'Maximum number of results to return',
},
{
displayName: 'Sort',
name: 'sort',
type: 'options',
options: [
{ name: 'Latest', value: 'latest' },
{ name: 'Top', value: 'top' },
],
default: 'latest',
description: 'Sort order for results',
},
{
displayName: 'Additional Filters',
name: 'additionalFilters',
type: 'collection',
placeholder: 'Add Filter',
default: {},
options: [
{
displayName: 'Author',
name: 'author',
type: 'string',
default: '',
placeholder: 'handle.bsky.social',
description: 'Filter to posts by specific author',
},
{
displayName: 'Domain',
name: 'domain',
type: 'string',
default: '',
placeholder: 'example.com',
description: 'Filter to posts linking to domain',
},
{
displayName: 'Language',
name: 'lang',
type: 'string',
default: '',
placeholder: 'en',
description: 'Filter by language code',
},
{
displayName: 'Since',
name: 'since',
type: 'string',
default: '',
placeholder: '2025-01-01T00:00:00Z',
description: 'Posts after this datetime (ISO 8601)',
},
{
displayName: 'Until',
name: 'until',
type: 'string',
default: '',
placeholder: '2025-12-31T23:59:59Z',
description: 'Posts before this datetime (ISO 8601)',
},
{
displayName: 'Mentions',
name: 'mentions',
type: 'string',
default: '',
placeholder: 'handle.bsky.social',
description: 'Filter by mentioned account',
},
{
displayName: 'Tags',
name: 'tag',
type: 'string',
default: '',
placeholder: 'hashtag1,hashtag2',
description: 'Comma-separated hashtags (without #)',
},
],
},
{
displayName: 'Return Format',
name: 'returnFormat',
type: 'options',
options: [
{ name: 'Individual Posts', value: 'items' },
{ name: 'Summary', value: 'summary' },
],
default: 'items',
description: 'How to format the output',
},
],
};
}
async execute() {
var _a, _b, _c, _d, _e, _f, _g;
const returnData = [];
// Get credentials
const creds = await this.getCredentials('blueskyApi');
const identifier = String(creds.identifier);
const password = String(creds.password);
const host = creds.host || 'https://bsky.social';
// Get node parameters
const query = this.getNodeParameter('query', 0);
const limit = this.getNodeParameter('limit', 0);
const sort = this.getNodeParameter('sort', 0);
const additionalFilters = this.getNodeParameter('additionalFilters', 0);
const returnFormat = this.getNodeParameter('returnFormat', 0);
if (!query.trim()) {
throw new Error('Search query is required.');
}
// 1) Create session (Bearer token)
const session = await this.helpers.httpRequest({
method: 'POST',
url: `${host}/xrpc/com.atproto.server.createSession`,
body: { identifier, password },
json: true,
});
const token = session === null || session === void 0 ? void 0 : session.accessJwt;
if (!token)
throw new Error('Failed to obtain Bluesky access token');
// 2) Build query params for searchPosts
const queryParams = {
q: query,
limit,
sort,
};
// Add optional filters
if (additionalFilters.author)
queryParams.author = additionalFilters.author;
if (additionalFilters.domain)
queryParams.domain = additionalFilters.domain;
if (additionalFilters.lang)
queryParams.lang = additionalFilters.lang;
if (additionalFilters.since)
queryParams.since = additionalFilters.since;
if (additionalFilters.until)
queryParams.until = additionalFilters.until;
if (additionalFilters.mentions)
queryParams.mentions = additionalFilters.mentions;
if (additionalFilters.tag) {
// Convert comma-separated tags to array
queryParams.tag = additionalFilters.tag.split(',').map((t) => t.trim());
}
// 3) Call searchPosts
const res = await this.helpers.httpRequest({
method: 'GET',
url: `${host}/xrpc/app.bsky.feed.searchPosts`,
qs: queryParams,
headers: { Authorization: `Bearer ${token}` },
json: true,
});
const posts = (res === null || res === void 0 ? void 0 : res.posts) || [];
const hitsTotal = (res === null || res === void 0 ? void 0 : res.hitsTotal) || posts.length;
const cursor = (res === null || res === void 0 ? void 0 : res.cursor) || null;
// 4) Format output
if (returnFormat === 'summary') {
// Return summary
returnData.push({
json: {
query,
hits_total: hitsTotal,
returned: posts.length,
cursor,
posts: posts.map((p) => {
var _a, _b, _c;
return ({
uri: p.uri,
author: ((_a = p.author) === null || _a === void 0 ? void 0 : _a.handle) || 'unknown',
text: ((_b = p.record) === null || _b === void 0 ? void 0 : _b.text) || '',
indexedAt: p.indexedAt,
createdAt: ((_c = p.record) === null || _c === void 0 ? void 0 : _c.createdAt) || '',
});
}),
},
});
}
else {
// Return individual posts as items
for (const p of posts) {
const postId = p.uri.split('/').pop();
const handle = ((_a = p.author) === null || _a === void 0 ? void 0 : _a.handle) || 'unknown';
const webLink = `https://bsky.app/profile/${handle}/post/${postId}`;
returnData.push({
json: {
uri: p.uri,
cid: p.cid,
author: {
did: (_b = p.author) === null || _b === void 0 ? void 0 : _b.did,
handle: (_c = p.author) === null || _c === void 0 ? void 0 : _c.handle,
displayName: (_d = p.author) === null || _d === void 0 ? void 0 : _d.displayName,
avatar: (_e = p.author) === null || _e === void 0 ? void 0 : _e.avatar,
},
text: ((_f = p.record) === null || _f === void 0 ? void 0 : _f.text) || '',
createdAt: (_g = p.record) === null || _g === void 0 ? void 0 : _g.createdAt,
indexedAt: p.indexedAt,
replyCount: p.replyCount || 0,
repostCount: p.repostCount || 0,
likeCount: p.likeCount || 0,
quoteCount: p.quoteCount || 0,
webLink,
rawPost: p,
},
});
}
}
return [returnData];
}
}
exports.BlueskySearch = BlueskySearch;