feat: lemmy

This commit is contained in:
Nevo David 2024-12-13 00:17:40 +07:00
parent 763e0cdf11
commit ad90d8d146
5 changed files with 528 additions and 502 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -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(),
];

View File

@ -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;
}
}

702
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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",