Skip to content

Commit

Permalink
Merge pull request #8 from nacika-ins/fix/remove-notes-for-misskey
Browse files Browse the repository at this point in the history
fix(misskey): Changed to detect spam from global timeline
  • Loading branch information
nacika-ins authored Feb 19, 2024
2 parents 0ebe3fb + 867b4d0 commit 639e892
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 24 deletions.
65 changes: 41 additions & 24 deletions features/batch/misskey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import axios from 'axios';
import { defaultRetryConfig, retry } from 'ts-retry-promise';
import { MisskeyNotifications } from '@/features/batch/response/misskey-notification';
import { MisskeyReport } from '@/features/batch/response/misskey-report';
import { MisskeyNotes } from '@/features/batch/response/misskey-notes';

defaultRetryConfig.retries = 3;
defaultRetryConfig.backoff = 'LINEAR';
Expand All @@ -15,6 +16,23 @@ defaultRetryConfig.logger = (log) => {
console.log('[retry]', log);
};

const getGlobalTimeline = async ({ provider, sinceId }: { provider: TargetProvider, sinceId: string | undefined | null }) =>
axios.post<MisskeyNotes>(`${provider.apiEndpoint}/notes/global-timeline`?.replace('//', '/'), {
i: provider.apiToken,
limit: 40,
sinceId,
}, {
headers: {
Authorization: `Bearer ${provider.apiToken}`,
'Content-Type': 'application/json',
},
}).then((res) =>
res.data.sort((a: { createdAt: string }, b: { createdAt: string }) => dayjs(a.createdAt).isBefore(dayjs(b.createdAt)) ? 1 : -1),
).catch((err) => {
console.debug('err =', err.response?.data);
return [] as MisskeyNotes;
});

const getNotifications = async ({ provider, sinceId }: { provider: TargetProvider, sinceId: string | undefined | null }) =>
axios.post<MisskeyNotifications>(`${provider.apiEndpoint}/i/notifications`?.replace('//', '/'), {
i: provider.apiToken,
Expand Down Expand Up @@ -117,56 +135,55 @@ const deleteNote = async ({ provider, noteId }: { provider: TargetProvider, note
export const execMisskey = async (provider: TargetProvider, spamTexts: string[], lastChecked: Date | null | undefined) => {

// const offsetDate = dayjs((lastChecked ? dayjs(lastChecked)
// .add(-48, 'h') : null) ?? new Date(Date.now() - 48 * 60 * 60 * 1000));
const offsetDate = dayjs(lastChecked ?? new Date(Date.now() - 48 * 60 * 60 * 1000));
// .add(-72, 'h') : null) ?? new Date(Date.now() - 48 * 60 * 60 * 1000));
const offsetDate = dayjs(lastChecked ?? new Date(Date.now() - 72 * 60 * 60 * 1000));

let sinceId: string | null | undefined;

for (const _ of Array(999).fill(null)) {

// eslint-disable-next-line no-loop-func
const notifications = await retry(() => getNotifications({ provider, sinceId }));
console.debug('notifications =', notifications);
const notes = await retry(() => getGlobalTimeline({ provider, sinceId }));

console.debug('[old] sinceId =', sinceId);
sinceId = notifications[notifications.length - 1]?.id as string | null | undefined;
sinceId = notes[notes.length - 1]?.id as string | null | undefined;
console.debug('[new] sinceId =', sinceId);

if (sinceId === null || sinceId === undefined) {
console.debug('No more notifications');
break;
}

if (notifications.every((notification) => dayjs(notification.createdAt).isBefore(offsetDate))) {
if (notes.every((notification) => dayjs(notification.createdAt).isBefore(offsetDate))) {
console.debug('No more notifications');
break;
}

for (const notification of notifications) {
for (const note of notes) {

if (dayjs(notification.createdAt).isBefore(offsetDate)) {
if (dayjs(note.createdAt).isBefore(offsetDate)) {
// eslint-disable-next-line no-continue
continue;
}

// eslint-disable-next-line no-continue
if (!notification.userId) continue;
if (!note.userId) continue;

console.debug('------------------------------------------------------------------------------------');
console.debug('notification.status.context =', notification?.note?.text);
console.debug('notification?.note?.files =', notification?.note?.files);
console.debug('notification.status.context =', note?.text);
console.debug('notification?.note?.files =', note?.files);

// Get image md5
const imageMD5s = notification?.note?.files?.map((media) =>
const imageMD5s = note?.files?.map((media) =>
media.md5).filter((md5) => md5) ?? [];
console.debug('md5 =', imageMD5s);

try {
const found = await spamTexts.some((spamText) =>
// Check text
notification.note?.text?.includes(spamText) ||
note?.text?.includes(spamText) ||
// Check media url
notification.note?.files?.some((media) => media.url?.includes(spamText)) ||
note?.files?.some((media) => media.url?.includes(spamText)) ||
// Check image md5
imageMD5s.includes(spamText),
);
Expand All @@ -176,16 +193,16 @@ export const execMisskey = async (provider: TargetProvider, spamTexts: string[],
if (!found) continue;

// report target
console.debug('[spam found] notification =', notification.note?.text);
console.debug('notification.userId =', notification.userId);
console.debug('notification.user?.id =', notification.user?.id);
console.debug('notification.user?.name =', notification.user?.name);
console.debug('notification.user?.username =', notification.user?.username);
console.debug('notification.note?.id =', notification.note?.id);
console.debug('[spam found] notification =', note?.text);
console.debug('notification.userId =', note.userId);
console.debug('notification.user?.id =', note.user?.id);
console.debug('notification.user?.name =', note.user?.name);
console.debug('notification.user?.username =', note.user?.username);
console.debug('notification.note?.id =', note?.id);

// Report spam ( When 200OK, no value is returned )
console.debug('[reportAbuse] notification.userId =', notification.userId);
await retry(() => reportAbuse({ provider, userId: notification.userId, comment: 'spam' }));
console.debug('[reportAbuse] notification.userId =', note.userId);
await retry(() => reportAbuse({ provider, userId: note.userId, comment: 'spam' }));

// Reports
console.debug('[abuseUserReports]');
Expand All @@ -202,8 +219,8 @@ export const execMisskey = async (provider: TargetProvider, spamTexts: string[],
}

// Delete note ( When 200OK, no value is returned )
console.debug('[deleteNote] noteId =', notification.note?.id);
await retry(() => deleteNote({ provider, noteId: notification.note?.id }));
console.debug('[deleteNote] noteId =', note?.id);
await retry(() => deleteNote({ provider, noteId: note?.id }));

// Suspend User ( When 200OK, no value is returned )
console.debug('[suspendUser] targetUserId =', targetUserId);
Expand Down
130 changes: 130 additions & 0 deletions features/batch/response/misskey-notes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/* eslint-disable no-use-before-define */
export type MisskeyNotes = Root2[]

export interface Root2 {
id: string
createdAt: string
text: string
cw: string
userId: string
user: User
replyId: string
renoteId: string
reply: Reply
renote: Renote
isHidden: boolean
visibility: string
mentions: string[]
visibleUserIds: string[]
fileIds: string[]
files: File[]
tags: string[]
poll: Poll
channelId: string
channel: Channel
localOnly: boolean
emojis: Emoji3[]
reactions: Reactions
renoteCount: number
repliesCount: number
uri: string
url: string
myReaction: MyReaction
}

export interface User {
id: string
name: string
username: string
host: string
avatarUrl: string
avatarBlurhash: any
avatarColor: any
isAdmin: boolean
isModerator: boolean
isBot: boolean
isCat: boolean
emojis: Emoji[]
onlineStatus: string
}

export interface Emoji {
name: string
url: string
}

export interface Reply {}

export interface Renote {}

export interface File {
id: string
createdAt: string
name: string
type: string
md5: string
size: number
isSensitive: boolean
blurhash: string
properties: Properties
url: string
thumbnailUrl: string
comment: string
folderId: string
folder: Folder
userId: string
user: User2
}

export interface Properties {
width: number
height: number
orientation: number
avgColor: string
}

export interface Folder {
id: string
createdAt: string
name: string
foldersCount: number
filesCount: number
parentId: string
parent: Parent
}

export interface Parent {}

export interface User2 {
id: string
name: string
username: string
host: string
avatarUrl: string
avatarBlurhash: any
avatarColor: any
isAdmin: boolean
isModerator: boolean
isBot: boolean
isCat: boolean
emojis: Emoji2[]
onlineStatus: string
}

export interface Emoji2 {
name: string
url: string
}

export interface Poll {}

export interface Channel {}

export interface Emoji3 {
name: string
url: string
}

export interface Reactions {}

export interface MyReaction {}

0 comments on commit 639e892

Please sign in to comment.