Skip to content

Commit

Permalink
feat: allow to control post visibility (#73)
Browse files Browse the repository at this point in the history
* feat: allow to control post visibility

* fix: minor improvements
  • Loading branch information
thilobillerbeck authored Dec 24, 2024
1 parent ae25728 commit 54d1e24
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 34 deletions.
14 changes: 8 additions & 6 deletions lib/mastodon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { MegalodonInterface, Mastodon } from 'megalodon'
import { Status } from 'megalodon/lib/src/entities/status';
import { Constraint } from "./constraint";
import { convert } from 'html-to-text';
import { StatusVisibility } from '@prisma/client';

export function initMastodonAgent() {
return new Mastodon('mastodon',
Expand All @@ -15,15 +16,16 @@ export async function getUserIdFromMastodonHandle(handle: string, client: Megalo
return a_data[0].id;
}

function verifyThread(uid: string, status: Status, searchSpace: Status[], initialCall: boolean = false): boolean {
function verifyThread(uid: string, status: Status, searchSpace: Status[], relayVisibility: StatusVisibility[], initialCall: boolean = false): boolean {
if (!status) return false;
if (status.in_reply_to_account_id === uid && (
status.visibility === 'unlisted' || status.visibility === 'public'
status.visibility === 'unlisted' || relayVisibility.includes(status.visibility)
)) {
return verifyThread(
uid,
searchSpace.find((s) => s.id === status.in_reply_to_id),
searchSpace,
relayVisibility,
false
)
} else if (!status.in_reply_to_account_id && !initialCall && status.visibility === 'public') {
Expand All @@ -33,7 +35,7 @@ function verifyThread(uid: string, status: Status, searchSpace: Status[], initia
}
}

export async function getNewToots(client: Mastodon, uid: string, lastTootTime: Date, constraint: Constraint) {
export async function getNewToots(client: Mastodon, uid: string, lastTootTime: Date, constraint: Constraint, relayVisibility: StatusVisibility[]) {
const statuses = await client.getAccountStatuses(uid, {
limit: 50,
exclude_reblogs: true,
Expand All @@ -43,7 +45,7 @@ export async function getNewToots(client: Mastodon, uid: string, lastTootTime: D
const statuses_data = await statuses.data;
const statuses_filtered = statuses_data.filter((status) => {
const newPost = new Date(status.created_at) > lastTootTime;
const isPublic = status.visibility === 'public';
const isInVisibilityScope = relayVisibility.includes(status.visibility);
const isNotMention = status.mentions.length === 0;
const text = convert(status.content ?? '', { wordwrap: false, preserveNewlines: false });
const regex = new RegExp(`${constraint.relayMarker}`, 'm');
Expand All @@ -63,9 +65,9 @@ export async function getNewToots(client: Mastodon, uid: string, lastTootTime: D
}

// 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);
const isThread = verifyThread(uid, status, statuses_data, relayVisibility, true);

return newPost && (isPublic || isThread) && isNotMention;
return newPost && (isInVisibilityScope || isThread) && isNotMention;
});

return statuses_filtered;
Expand Down
3 changes: 2 additions & 1 deletion lib/tasks/mastodonToBluesky.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ export default async function taskMastodonToBluesky() {
userClient,
user.mastodonUid,
user.lastTootTime,
constraint
constraint,
user.relayVisibility
);

if (posts.length === 0) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
Warnings:
- You are about to drop the `UserSettings` table. If the table is not empty, all the data it contains will be lost.
*/
-- DropForeignKey
ALTER TABLE "UserSettings" DROP CONSTRAINT "UserSettings_userId_fkey";

-- AlterTable
ALTER TABLE "User" ADD COLUMN "relayVisibility" "StatusVisibility"[] DEFAULT ARRAY['public']::"StatusVisibility"[];

-- DropTable
DROP TABLE "UserSettings";
13 changes: 1 addition & 12 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ model User {
mastodonInstanceId String
relayCriteria RelayCriteria @default(all)
relayMarker String @default("")
UserSettings UserSettings[]
relayVisibility StatusVisibility[] @default([public])
Repost Repost[]
}

Expand All @@ -67,14 +67,3 @@ model Repost {
bsParentUri String
bsParentCid String
}

model UserSettings {
id String @id @default(uuid())
statusVisibility StatusVisibility @default(public)
excludeReplies Boolean @default(false)
excludeReblogs Boolean @default(false)
excludeMentions Boolean @default(false)
excludeSensitive Boolean @default(false)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String
}
27 changes: 26 additions & 1 deletion routes/root.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { intiBlueskyAgent, validateBlueskyAppPassword, validateBlueskyCredential
import { authenticateJWT, checkValidHttpsUrl } from './../lib/utils'
import { db } from './../lib/db'
import { FastifyInstance } from 'fastify'
import { StatusVisibility } from '@prisma/client'

export const routesRoot = async (app: FastifyInstance, options: Object) => {
app.get('/', { onRequest: [authenticateJWT] }, async (req, res) => {
Expand All @@ -16,6 +17,7 @@ export const routesRoot = async (app: FastifyInstance, options: Object) => {
pollingInterval: parseInt(process.env.POLL_INTERVAL ?? '60'),
relayCriteria: user?.relayCriteria,
relayMarker: user?.relayMarker,
relayVisibility: user?.relayVisibility,
})
})

Expand Down Expand Up @@ -75,16 +77,39 @@ export const routesRoot = async (app: FastifyInstance, options: Object) => {
app.post<{
Body: {
relayCriteria: any,
relayMarker: string
relayMarker: string,
relayVisibility: StatusVisibility[]
}
}>('/settings/repost', { onRequest: [authenticateJWT] }, async (req, res) => {
const user = await db.user.findFirst({ where: { id: req.user.id }, include: { mastodonInstance: true } })

let response_data: any = {
err: undefined,
blueskyPDS: user?.blueskyPDS,
userName: req.user.mastodonHandle,
instance: req.user.instance,
relayCriteria: user?.relayCriteria,
relayMarker: user?.relayMarker,
pollingInterval: parseInt(process.env.POLL_INTERVAL ?? '60')
};

if(req.body.relayVisibility === undefined) return res.status(400).view("index", {
...response_data,
err: 'Invalid Relay Visibility'
})

const relayVisibility = !Array.isArray(req.body.relayVisibility) ? [req.body.relayVisibility] : req.body.relayVisibility;

console.log(relayVisibility)

await db.user.update({
where: {
id: req.user.id
},
data: {
relayCriteria: req.body.relayCriteria,
relayMarker: req.body.relayMarker,
relayVisibility: relayVisibility
}
})

Expand Down
47 changes: 33 additions & 14 deletions views/index.liquid
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,6 @@
You are currently not authenticated to Bluesky, please enter your credentials below.
{% endif %}
</p>
<p>
<b>
Repost visibility control WIP - Skymoth will only repost public Toots that are not a reply.
With the exception of direct unlisted replies, which will counted as a thread.
</b>
</p>
</div>
</div>
<div class="card w-full mx-8 md:mx-0 md:w-1/3 bg-base-200 shadow-xl">
Expand Down Expand Up @@ -59,7 +53,7 @@
</div>

<div class="card-actions justify-end mt-4">
<input class="btn btn-outline btn-success" type="submit" value="Update!" />
<input class="btn btn-outline btn-success" type="submit" value="Update" />
</div>
</form>
</div>
Expand All @@ -69,12 +63,37 @@
<h2 class="card-title">Post Settings</h2>
<form action="/settings/repost" method="post">
<div class="space-y-2">
<p class="mb-4 mt-4">
Which Toots for this account should be relayed from Mastodon to BlueSky?
<p class="mt-4 font-bold">
Toot visibility
</p>
<p class="mb-4">
Choose the visibility of the toots that Skymoth will repost.
</p>
<div class="inline-flex gap-4 w-full">
<label class="flex items-center">
<input type="checkbox" name="relayVisibility" value="public" class="checkbox checkbox-primary mr-2" {% if relayVisibility contains 'public' %}checked{% endif %}/>
<span>Public</span>
</label>
<label class="flex items-center">
<input type="checkbox" name="relayVisibility" value="unlisted" class="checkbox checkbox-primary mr-2" {% if relayVisibility contains 'unlisted' %}checked{% endif %}/>
<span>Unlisted</span>
</label>
<label class="flex items-center">
<input type="checkbox" name="relayVisibility" value="private" class="checkbox checkbox-primary mr-2" {% if relayVisibility contains 'private' %}checked{% endif %}/>
<span>Private</span>
</label>
</div>
</div>
<div class="space-y-2">
<p class="mt-4 font-bold">
Toot markers
</p>
<p class="mb-4">
Choose if Skymoth should relay toots based on the presence of a marker.
</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>
<span>None</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 %}/>
Expand All @@ -89,18 +108,18 @@
<span>Posts which do not include the marker below</span>
</label>
<div class="form-control mt-4">
<label class="label">
<label class="mb-2">
<span class="label-text">Marker for relay</span>
</label>
<p class="text-sm text-gray-400 mb-2">
<input type="text" name="relayMarker" placeholder="Enter a marker, eg: #xp" class="input input-bordered w-full" id="relayMarker" value="{{ relayMarker }}"/>
<p class="text-sm text-gray-400 mt-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!" />
<input class="btn btn-outline btn-success" type="submit" value="Update" />
</div>
</form>
</div>
Expand Down

0 comments on commit 54d1e24

Please sign in to comment.