176 lines
3.9 KiB
TypeScript
176 lines
3.9 KiB
TypeScript
|
import {
|
||
|
Plugin,
|
||
|
Configurable,
|
||
|
EventListener,
|
||
|
DependencyLoad,
|
||
|
} from '@squeebot/core/lib/plugin';
|
||
|
|
||
|
import { IMessage } from '@squeebot/core/lib/types';
|
||
|
import { httpGET, sanitizeEscapedText } from '@squeebot/core/lib/common';
|
||
|
|
||
|
interface ATRecordEmbed {
|
||
|
$type: string;
|
||
|
images: [
|
||
|
{
|
||
|
alt: string;
|
||
|
aspectRatio: {
|
||
|
height: number;
|
||
|
width: number;
|
||
|
};
|
||
|
image: {
|
||
|
$type: string;
|
||
|
ref: {
|
||
|
$link: string;
|
||
|
};
|
||
|
mimeType: string;
|
||
|
size: number;
|
||
|
};
|
||
|
}
|
||
|
];
|
||
|
}
|
||
|
|
||
|
interface ATRecord {
|
||
|
$type: string;
|
||
|
createdAt: string;
|
||
|
langs: string[];
|
||
|
text: string;
|
||
|
embed?: ATRecordEmbed;
|
||
|
facets?: unknown[];
|
||
|
}
|
||
|
|
||
|
interface ATEmbed {
|
||
|
$type: string;
|
||
|
images?: [
|
||
|
{
|
||
|
thumb: string;
|
||
|
fullsize: string;
|
||
|
alt: string;
|
||
|
aspectRatio: {
|
||
|
height: number;
|
||
|
width: number;
|
||
|
};
|
||
|
}
|
||
|
];
|
||
|
record?: ATRecord;
|
||
|
}
|
||
|
|
||
|
interface ATThread {
|
||
|
thread: {
|
||
|
$type: 'app.bsky.feed.defs#threadViewPost';
|
||
|
post: {
|
||
|
uri: string;
|
||
|
cid: string;
|
||
|
author: {
|
||
|
did: string;
|
||
|
handle: string;
|
||
|
displayName: string;
|
||
|
avatar: string;
|
||
|
labels: unknown[];
|
||
|
createdAt: string;
|
||
|
};
|
||
|
record: ATRecord;
|
||
|
embed?: ATEmbed;
|
||
|
replyCount: number;
|
||
|
repostCount: number;
|
||
|
likeCount: number;
|
||
|
quoteCount: number;
|
||
|
indexedAt: string;
|
||
|
labels: unknown[];
|
||
|
};
|
||
|
replies: ATThread['thread'][];
|
||
|
};
|
||
|
}
|
||
|
|
||
|
@Configurable({})
|
||
|
class TwitterURLPlugin extends Plugin {
|
||
|
@EventListener('pluginUnload')
|
||
|
public unloadEventHandler(plugin: string | Plugin): void {
|
||
|
if (plugin === this.name || plugin === this) {
|
||
|
this.emit('pluginUnloaded', this);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
async handlePost(
|
||
|
data: ATThread['thread']['post'],
|
||
|
msg: IMessage
|
||
|
): Promise<boolean> {
|
||
|
const keys = [];
|
||
|
const end = sanitizeEscapedText(data.record.text);
|
||
|
|
||
|
keys.push(['field', 'Bluesky', { color: 'cyan', type: 'title' }]);
|
||
|
keys.push([
|
||
|
'field',
|
||
|
data.likeCount.toString(),
|
||
|
{ color: 'red', label: ['♥', 'Likes'], type: 'metric' },
|
||
|
]);
|
||
|
keys.push([
|
||
|
'field',
|
||
|
data.repostCount.toString(),
|
||
|
{ color: 'green', label: ['↱↲', 'Reposts'], type: 'metric' },
|
||
|
]);
|
||
|
keys.push(['bold', '@' + data.author.handle + ':']);
|
||
|
keys.push(['field', end, { type: 'content' }]);
|
||
|
|
||
|
if (data.embed?.images?.length) {
|
||
|
const amount = data.embed.images.length;
|
||
|
keys.push([
|
||
|
'field',
|
||
|
`[${amount} attachment${amount !== 1 ? 's' : ''}]`,
|
||
|
{ color: 'brown' },
|
||
|
]);
|
||
|
}
|
||
|
|
||
|
if (data.embed?.record) {
|
||
|
keys.push(['field', '[quoted post]', { color: 'brown' }]);
|
||
|
}
|
||
|
|
||
|
msg.resolve(keys);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
async atprotoGetThread(url: URL, msg: IMessage): Promise<boolean> {
|
||
|
const urlString = url.toString();
|
||
|
const det = urlString.match(
|
||
|
/^(https?):\/\/((?:[\w\d-]+\.)*[\w\d-]+\.\w{2,16})\/(?:profile\/)?@?([\d\w-_.]+)\/(?:post\/)?([\w\d]+[^&#?\s/])/i
|
||
|
);
|
||
|
if (!det) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
const [_, proto, webviewHost, atHost, postId] = det;
|
||
|
|
||
|
if (proto !== 'https' || webviewHost !== 'bsky.app') {
|
||
|
// no other webviews atm
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
const postInfo = await httpGET(
|
||
|
`https://public.api.bsky.app/xrpc/app.bsky.feed.getPostThread?depth=1&parentHeight=1&uri=at://${atHost}/app.bsky.feed.post/${postId}`
|
||
|
);
|
||
|
|
||
|
const data = JSON.parse(postInfo) as ATThread;
|
||
|
if (data?.thread) {
|
||
|
return this.handlePost(data.thread.post, msg);
|
||
|
}
|
||
|
} catch (e) {
|
||
|
msg.resolve('Could not fetch Bluesky post, it may be private.');
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
@DependencyLoad('urlreply')
|
||
|
addURLHandler(urlreply: any): void {
|
||
|
urlreply.registerHandler(
|
||
|
this.name,
|
||
|
'bsky.app/',
|
||
|
(url: URL, msg: IMessage) => {
|
||
|
return this.atprotoGetThread(url, msg);
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
module.exports = TwitterURLPlugin;
|