Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: #55 User configuration for relay controls #57

Merged
merged 6 commits into from
Dec 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions lib/constraint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export class Constraint {
relayCriteria: string;
relayMarker: string;

constructor(relayCriteria: string, relayMarker: string) {
this.relayCriteria = relayCriteria;
this.relayMarker = relayMarker;
}
}
20 changes: 19 additions & 1 deletion lib/mastodon.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { MegalodonInterface, Mastodon } from 'megalodon'
import { Status } from 'megalodon/lib/src/entities/status';
import { Constraint } from "./constraint";
import { convert } from 'html-to-text';

export function initMastodonAgent() {
return new Mastodon('mastodon',
Expand Down Expand Up @@ -31,7 +33,7 @@ function verifyThread(uid: string, status: Status, searchSpace: Status[], initia
}
}

export async function getNewToots(client: Mastodon, uid: string, lastTootTime: Date) {
export async function getNewToots(client: Mastodon, uid: string, lastTootTime: Date, constraint: Constraint) {
const statuses = await client.getAccountStatuses(uid, {
limit: 50,
exclude_reblogs: true,
Expand All @@ -43,6 +45,22 @@ export async function getNewToots(client: Mastodon, uid: string, lastTootTime: D
const newPost = new Date(status.created_at) > lastTootTime;
const isPublic = status.visibility === 'public';
const isNotMention = status.mentions.length === 0;
const text = convert(status.content ?? '', { wordwrap: false, preserveNewlines: false });
const regex = new RegExp(`${constraint.relayMarker}`, 'm');
const containsMarker = text.match(regex) !== null;
const isSelfFaved = status.favourited;

if (constraint.relayCriteria === 'favedBySelf' && !isSelfFaved) {
return false;
}

if (constraint.relayCriteria === 'containsMarker' && !containsMarker) {
return false;
}

if (constraint.relayCriteria === 'notContainsMarker' && containsMarker) {
return false;
}

// due to the way some mastodon clients handle threads, we need to check if the status may be a thread
const isThread = verifyThread(uid, status, statuses_data, true);
Expand Down
8 changes: 7 additions & 1 deletion lib/tasks/mastodonToBluesky.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Mastodon } from "megalodon";
import { getNewToots } from "../mastodon";
import { generateBlueskyPostsFromMastodon, intiBlueskyAgent } from "../bluesky";
import { domainToUrl, getBlueskyApiWaittime, logSchedulerEvent } from "../utils";
import { Constraint } from "../constraint";
import {
db,
updateLastPostTime,
Expand Down Expand Up @@ -34,10 +35,15 @@ export default async function taskMastodonToBluesky() {
domainToUrl(user.mastodonInstance.url),
user.mastodonToken
);
const constraint = new Constraint(
user.relayCriteria ?? "all",
user.relayMarker ?? ""
);
let posts = await getNewToots(
userClient,
user.mastodonUid,
user.lastTootTime
user.lastTootTime,
constraint
);

if (posts.length === 0) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-- CreateEnum
CREATE TYPE "RelayCriteria" AS ENUM ('all', 'favedBySelf', 'containsMarker', 'notContainsMarker');

-- AlterTable
ALTER TABLE "User" ADD COLUMN "relayCriteria" "RelayCriteria" NOT NULL DEFAULT 'all',
ADD COLUMN "relayMarker" TEXT NOT NULL DEFAULT '';
9 changes: 9 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ enum StatusVisibility {
direct
}

enum RelayCriteria {
all
favedBySelf
containsMarker
notContainsMarker
}

model MastodonInstance {
id String @id @default(uuid())
urlEncoded String @unique
Expand All @@ -43,6 +50,8 @@ model User {
blueskySession Json?
blueskySessionEvent String?
mastodonInstanceId String
relayCriteria RelayCriteria @default(all)
relayMarker String @default("")
UserSettings UserSettings[]
Repost Repost[]
}
Expand Down
12 changes: 9 additions & 3 deletions routes/root.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,19 @@ export const routesRoot = async (app: FastifyInstance, options: Object) => {
blueskyHandle: user?.blueskyHandle,
blueskyPDS: user?.blueskyPDS,
hasBlueskyToken: user?.blueskyToken ? true : false,
pollingInterval: parseInt(process.env.POLL_INTERVAL ?? '60')
pollingInterval: parseInt(process.env.POLL_INTERVAL ?? '60'),
relayCriteria: user?.relayCriteria,
relayMarker: user?.relayMarker,
})
})

app.post<{
Body: {
blueskyHandle: string,
blueskyToken: string,
blueskyPDS: string
blueskyPDS: string,
relayCriteria: any,
relayMarker: string
}
}>('/', { onRequest: [authenticateJWT] }, async (req, res) => {
const user = await db.user.findFirst({ where: { id: req.user.id }, include: { mastodonInstance: true } })
Expand All @@ -48,7 +52,7 @@ export const routesRoot = async (app: FastifyInstance, options: Object) => {
...response_data,
err: 'Invalid Bluesky PDS'
})

if(!(await validateBlueskyCredentials(req.body.blueskyPDS, req.body.blueskyHandle, req.body.blueskyToken))) return res.status(400).view("index", {
...response_data,
err: 'Invalid Bluesky Credentials, could not authenticate'
Expand All @@ -62,6 +66,8 @@ export const routesRoot = async (app: FastifyInstance, options: Object) => {
blueskyHandle: req.body.blueskyHandle,
blueskyToken: req.body.blueskyToken,
blueskyPDS: req.body.blueskyPDS,
relayCriteria: req.body.relayCriteria,
relayMarker: req.body.relayMarker,
}
})

Expand Down
32 changes: 32 additions & 0 deletions views/index.liquid
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,38 @@
<input type="password" placeholder="***********" type="text"
name="blueskyToken" id="blueskyToken" required class="input input-bordered w-full" />
</div>

<div class="space-y-2">
<p class="mb-4 mt-4">
Which Toots for this account should be relayed from Mastodon to BlueSky?
</p>
<label class="flex items-center">
<input type="radio" name="relayCriteria" value="all" class="radio radio-primary mr-2" {% if relayCriteria == 'all' %}checked{% endif %}/>
<span>All posts</span>
</label>
<label class="flex items-center">
<input type="radio" name="relayCriteria" value="favedBySelf" class="radio radio-primary mr-2" {% if relayCriteria == 'favedBySelf' %}checked{% endif %}/>
<span>Posts which the user ⭐'d</span>
</label>
<label class="flex items-center">
<input type="radio" name="relayCriteria" value="containsMarker" class="radio radio-primary mr-2" {% if relayCriteria == 'containsMarker' %}checked{% endif %}/>
<span>Posts which include the marker below</span>
</label>
<label class="flex items-center">
<input type="radio" name="relayCriteria" value="notContainsMarker" class="radio radio-primary mr-2" {% if relayCriteria == 'notContainsMarker' %}checked{% endif %}/>
<span>Posts which do not include the marker below</span>
</label>
<div class="form-control mt-4">
<label class="label">
<span class="label-text">Marker for relay</span>
</label>
<p class="text-sm text-gray-400 mb-2">
This text can be any string contained in the toot, for example '#xp' (for crosspost) or 'cc:bluesky'.
</p>
<input type="text" name="relayMarker" placeholder="Enter a marker, eg: #xp" class="input input-bordered w-full" id="relayMarker" value="{{ relayMarker }}"/>
</div>
</div>

<div class="card-actions justify-end mt-4">
<input class="btn btn-outline btn-success" type="submit" value="Update!" />
</div>
Expand Down