feat: lemmy
This commit is contained in:
parent
763e0cdf11
commit
ad90d8d146
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
|
|
@ -21,6 +21,7 @@ import { DiscordProvider } from '@gitroom/nestjs-libraries/integrations/social/d
|
|||
import { SlackProvider } from '@gitroom/nestjs-libraries/integrations/social/slack.provider';
|
||||
import { MastodonProvider } from '@gitroom/nestjs-libraries/integrations/social/mastodon.provider';
|
||||
import { BlueskyProvider } from '@gitroom/nestjs-libraries/integrations/social/bluesky.provider';
|
||||
import { LemmyProvider } from '@gitroom/nestjs-libraries/integrations/social/lemmy.provider';
|
||||
// import { MastodonCustomProvider } from '@gitroom/nestjs-libraries/integrations/social/mastodon.custom.provider';
|
||||
|
||||
const socialIntegrationList: SocialProvider[] = [
|
||||
|
|
@ -39,6 +40,7 @@ const socialIntegrationList: SocialProvider[] = [
|
|||
new SlackProvider(),
|
||||
new MastodonProvider(),
|
||||
new BlueskyProvider(),
|
||||
new LemmyProvider(),
|
||||
// new MastodonCustomProvider(),
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,325 @@
|
|||
import {
|
||||
AuthTokenDetails,
|
||||
PostDetails,
|
||||
PostResponse,
|
||||
SocialProvider,
|
||||
} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';
|
||||
import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
|
||||
import {
|
||||
SocialAbstract,
|
||||
} from '@gitroom/nestjs-libraries/integrations/social.abstract';
|
||||
import { BskyAgent, RichText } from '@atproto/api';
|
||||
import dayjs from 'dayjs';
|
||||
import { Integration } from '@prisma/client';
|
||||
import { AuthService } from '@gitroom/helpers/auth/auth.service';
|
||||
import sharp from 'sharp';
|
||||
import { Plug } from '@gitroom/helpers/decorators/plug.decorator';
|
||||
import { timer } from '@gitroom/helpers/utils/timer';
|
||||
import { LemmyHttp } from 'lemmy-js-client';
|
||||
|
||||
export class LemmyProvider extends SocialAbstract implements SocialProvider {
|
||||
identifier = 'lemmy';
|
||||
name = 'Lemmy';
|
||||
isBetweenSteps = false;
|
||||
scopes = [];
|
||||
|
||||
async customFields() {
|
||||
return [
|
||||
{
|
||||
key: 'service',
|
||||
label: 'Service',
|
||||
defaultValue: 'https://lemmy.world',
|
||||
validation: `/^(https?:\\/\\/)?((([a-zA-Z0-9\\-_]{1,256}\\.[a-zA-Z]{2,6})|(([0-9]{1,3}\\.){3}[0-9]{1,3}))(:[0-9]{1,5})?)(\\/[^\\s]*)?$/`,
|
||||
type: 'text' as const,
|
||||
},
|
||||
{
|
||||
key: 'identifier',
|
||||
label: 'Identifier',
|
||||
validation: `/^.{3,}$/`,
|
||||
type: 'text' as const,
|
||||
},
|
||||
{
|
||||
key: 'password',
|
||||
label: 'Password',
|
||||
validation: `/^.{3,}$/`,
|
||||
type: 'password' as const,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
async refreshToken(refreshToken: string): Promise<AuthTokenDetails> {
|
||||
return {
|
||||
refreshToken: '',
|
||||
expiresIn: 0,
|
||||
accessToken: '',
|
||||
id: '',
|
||||
name: '',
|
||||
picture: '',
|
||||
username: '',
|
||||
};
|
||||
}
|
||||
|
||||
async generateAuthUrl() {
|
||||
const state = makeId(6);
|
||||
return {
|
||||
url: '',
|
||||
codeVerifier: makeId(10),
|
||||
state,
|
||||
};
|
||||
}
|
||||
|
||||
async authenticate(params: {
|
||||
code: string;
|
||||
codeVerifier: string;
|
||||
refresh?: string;
|
||||
}) {
|
||||
const body = JSON.parse(Buffer.from(params.code, 'base64').toString());
|
||||
|
||||
try {
|
||||
const client: LemmyHttp = new LemmyHttp(body.service);
|
||||
const { jwt } = await client.login({
|
||||
username_or_email: body.identifier,
|
||||
password: body.password,
|
||||
});
|
||||
|
||||
client.setHeaders({ Authorization: `Bearer ${jwt}` });
|
||||
const user = await client.getMyUser();
|
||||
|
||||
return {
|
||||
refreshToken: jwt!,
|
||||
expiresIn: dayjs().add(100, 'years').unix() - dayjs().unix(),
|
||||
accessToken: jwt!,
|
||||
id: String(user.local_user_view.person.id),
|
||||
name: user.local_user_view.person.name || '',
|
||||
picture: user.local_user_view.person.avatar || '',
|
||||
username: user.local_user_view.person.display_name || '',
|
||||
};
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
return 'Invalid credentials';
|
||||
}
|
||||
}
|
||||
|
||||
async post(
|
||||
id: string,
|
||||
accessToken: string,
|
||||
postDetails: PostDetails[],
|
||||
integration: Integration
|
||||
): Promise<PostResponse[]> {
|
||||
const body = JSON.parse(
|
||||
AuthService.fixedDecryption(integration.customInstanceDetails!)
|
||||
);
|
||||
const agent = new BskyAgent({
|
||||
service: body.service,
|
||||
});
|
||||
|
||||
await agent.login({
|
||||
identifier: body.identifier,
|
||||
password: body.password,
|
||||
});
|
||||
|
||||
let loadCid = '';
|
||||
let loadUri = '';
|
||||
const cidUrl = [] as { cid: string; url: string; rev: string }[];
|
||||
for (const post of postDetails) {
|
||||
const images = await Promise.all(
|
||||
post.media?.map(async (p) => {
|
||||
const a = await fetch(p.url);
|
||||
console.log(p.url);
|
||||
return await agent.uploadBlob(
|
||||
new Blob([
|
||||
await sharp(await (await fetch(p.url)).arrayBuffer())
|
||||
.resize({ width: 400 })
|
||||
.toBuffer(),
|
||||
])
|
||||
);
|
||||
}) || []
|
||||
);
|
||||
|
||||
const rt = new RichText({
|
||||
text: post.message,
|
||||
});
|
||||
|
||||
await rt.detectFacets(agent);
|
||||
|
||||
// @ts-ignore
|
||||
const { cid, uri, commit } = await agent.post({
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
createdAt: new Date().toISOString(),
|
||||
...(images.length
|
||||
? {
|
||||
embed: {
|
||||
$type: 'app.bsky.embed.images',
|
||||
images: images.map((p) => ({
|
||||
// can be an array up to 4 values
|
||||
alt: 'image', // the alt text
|
||||
image: p.data.blob,
|
||||
})),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(loadCid
|
||||
? {
|
||||
reply: {
|
||||
root: {
|
||||
uri: loadUri,
|
||||
cid: loadCid,
|
||||
},
|
||||
parent: {
|
||||
uri: loadUri,
|
||||
cid: loadCid,
|
||||
},
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
|
||||
loadCid = loadCid || cid;
|
||||
loadUri = loadUri || uri;
|
||||
|
||||
cidUrl.push({ cid, url: uri, rev: commit.rev });
|
||||
}
|
||||
|
||||
return postDetails.map((p, index) => ({
|
||||
id: p.id,
|
||||
postId: cidUrl[index].url,
|
||||
status: 'completed',
|
||||
releaseURL: `https://bsky.app/profile/${id}/post/${cidUrl[index].url
|
||||
.split('/')
|
||||
.pop()}`,
|
||||
}));
|
||||
}
|
||||
|
||||
@Plug({
|
||||
identifier: 'bluesky-autoRepostPost',
|
||||
title: 'Auto Repost Posts',
|
||||
description:
|
||||
'When a post reached a certain number of likes, repost it to increase engagement (1 week old posts)',
|
||||
runEveryMilliseconds: 21600000,
|
||||
totalRuns: 3,
|
||||
fields: [
|
||||
{
|
||||
name: 'likesAmount',
|
||||
type: 'number',
|
||||
placeholder: 'Amount of likes',
|
||||
description: 'The amount of likes to trigger the repost',
|
||||
validation: /^\d+$/,
|
||||
},
|
||||
],
|
||||
})
|
||||
async autoRepostPost(
|
||||
integration: Integration,
|
||||
id: string,
|
||||
fields: { likesAmount: string }
|
||||
) {
|
||||
const body = JSON.parse(
|
||||
AuthService.fixedDecryption(integration.customInstanceDetails!)
|
||||
);
|
||||
const agent = new BskyAgent({
|
||||
service: body.service,
|
||||
});
|
||||
|
||||
await agent.login({
|
||||
identifier: body.identifier,
|
||||
password: body.password,
|
||||
});
|
||||
|
||||
const getThread = await agent.getPostThread({
|
||||
uri: id,
|
||||
depth: 0,
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
if (getThread.data.thread.post?.likeCount >= +fields.likesAmount) {
|
||||
await timer(2000);
|
||||
await agent.repost(
|
||||
// @ts-ignore
|
||||
getThread.data.thread.post?.uri,
|
||||
// @ts-ignore
|
||||
getThread.data.thread.post?.cid
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Plug({
|
||||
identifier: 'bluesky-autoPlugPost',
|
||||
title: 'Auto plug post',
|
||||
description:
|
||||
'When a post reached a certain number of likes, add another post to it so you followers get a notification about your promotion',
|
||||
runEveryMilliseconds: 21600000,
|
||||
totalRuns: 3,
|
||||
fields: [
|
||||
{
|
||||
name: 'likesAmount',
|
||||
type: 'number',
|
||||
placeholder: 'Amount of likes',
|
||||
description: 'The amount of likes to trigger the repost',
|
||||
validation: /^\d+$/,
|
||||
},
|
||||
{
|
||||
name: 'post',
|
||||
type: 'richtext',
|
||||
placeholder: 'Post to plug',
|
||||
description: 'Message content to plug',
|
||||
validation: /^[\s\S]{3,}$/g,
|
||||
},
|
||||
],
|
||||
})
|
||||
async autoPlugPost(
|
||||
integration: Integration,
|
||||
id: string,
|
||||
fields: { likesAmount: string; post: string }
|
||||
) {
|
||||
const body = JSON.parse(
|
||||
AuthService.fixedDecryption(integration.customInstanceDetails!)
|
||||
);
|
||||
const agent = new BskyAgent({
|
||||
service: body.service,
|
||||
});
|
||||
|
||||
await agent.login({
|
||||
identifier: body.identifier,
|
||||
password: body.password,
|
||||
});
|
||||
|
||||
const getThread = await agent.getPostThread({
|
||||
uri: id,
|
||||
depth: 0,
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
if (getThread.data.thread.post?.likeCount >= +fields.likesAmount) {
|
||||
await timer(2000);
|
||||
const rt = new RichText({
|
||||
text: fields.post,
|
||||
});
|
||||
|
||||
await agent.post({
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
createdAt: new Date().toISOString(),
|
||||
reply: {
|
||||
root: {
|
||||
// @ts-ignore
|
||||
uri: getThread.data.thread.post?.uri,
|
||||
// @ts-ignore
|
||||
cid: getThread.data.thread.post?.cid,
|
||||
},
|
||||
parent: {
|
||||
// @ts-ignore
|
||||
uri: getThread.data.thread.post?.uri,
|
||||
// @ts-ignore
|
||||
cid: getThread.data.thread.post?.cid,
|
||||
},
|
||||
},
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -109,6 +109,7 @@
|
|||
"ioredis": "^5.3.2",
|
||||
"json-to-graphql-query": "^2.2.5",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"lemmy-js-client": "^0.20.0-reports-combined.3",
|
||||
"lodash": "^4.17.21",
|
||||
"md5": "^2.3.0",
|
||||
"mime": "^3.0.0",
|
||||
|
|
|
|||
Loading…
Reference in New Issue