-
-
Notifications
You must be signed in to change notification settings - Fork 3
emoji suggestions #39
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
Closed
Closed
Changes from all commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
396a170
fix(backend): purge user profile cache when updating settings
PrivateGER df5b09c
fix(frontend): refresh stale instance meta within 10s instead of 101s
PrivateGER e582a9c
fix(backend): restore RSS feeds for legacy unapproved local users
PrivateGER cd1bcd4
fix(backend): bust user cache after admin avatar/banner unset
PrivateGER 3d813c3
Add Lua MRF policy runtime
PrivateGER a1e9799
Add DB-backed MRF policy management
PrivateGER caa14a8
Run inbox MRF policies from Lua database rules
PrivateGER e92ca3f
fix(backend): harden Lua MRF policy basics
PrivateGER 7fde99a
fix(backend): scope Lua MRF policy execution
PrivateGER d0a886e
fix(backend): tolerate stale Lua MRF params
PrivateGER 7d28d56
fix(backend): make Lua MRF failures fail open
PrivateGER ddcff09
perf(backend): pool Lua MRF policy engines
PrivateGER 1a77146
feat(backend): warn on Lua MRF persistent globals
PrivateGER d53fb46
fix(activitypub): correctly access JsonLd instance when signing relay…
claude bcde1f2
Merge pull request #35 from PrivateGER/claude/eloquent-franklin-jmw7ci
PrivateGER 6949092
fix(backend): remove Lua MRF threads from the engine stack after use
PrivateGER fd443e9
fix(backend): do not discard Lua MRF decisions on snapshot failure
PrivateGER c00bcc1
fix(backend): reject non-JSON Lua MRF rewrite payloads
PrivateGER 280da98
fix(backend): require a filter function in Lua MRF policy sources
PrivateGER 826e3d0
refactor(backend): provide MrfLuaPolicyService as a DI singleton
PrivateGER db786d9
fix(backend): return structured errors for invalid Lua MRF sources
PrivateGER 391f79e
feat(backend): log MRF policy mutations to the moderation log
PrivateGER bcd6764
perf(backend): cache enabled MRF policies for 10s
PrivateGER e2e73a3
fix(backend): correct Lua MRF unlist helper and local-host prefix check
PrivateGER 81e8faf
refactor(backend): tighten MRF endpoint typing and document Lua limits
PrivateGER 14bf0f9
feat(backend): add response schemas to MRF policy endpoints
PrivateGER d7bde53
fix(backend): satisfy typecheck and lint in MRF policy endpoints
PrivateGER dc7dbc5
merge: Lua MRF hardening (fix/lua-mrf-hardening)
PrivateGER 8ed1d03
merge: origin/develop (relay signing fix)
PrivateGER d2f511a
Add emoji suggestion backend workflow
PrivateGER d514a6d
Add emoji suggestion review interface
PrivateGER File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,198 @@ | ||
| /* | ||
| * SPDX-FileCopyrightText: syuilo and misskey-project | ||
| * SPDX-License-Identifier: AGPL-3.0-only | ||
| */ | ||
|
|
||
| export class MrfPolicy1781706597000 { | ||
| name = 'MrfPolicy1781706597000' | ||
|
|
||
| async up(queryRunner) { | ||
| await queryRunner.query(`CREATE TABLE "mrf_policy" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "name" character varying(256) NOT NULL, "enabled" boolean NOT NULL DEFAULT true, "priority" integer NOT NULL DEFAULT '1000', "source" text NOT NULL, "timeoutMs" integer NOT NULL DEFAULT '50', "scope" jsonb NOT NULL DEFAULT '{"activityTypes":["Create"],"objectTypes":["Note"]}', "isBuiltin" boolean NOT NULL DEFAULT false, "builtinPolicyId" character varying(128), "paramsSchema" jsonb NOT NULL DEFAULT '{}', "params" jsonb NOT NULL DEFAULT '{}', CONSTRAINT "PK_mrf_policy" PRIMARY KEY ("id"))`); | ||
| await queryRunner.query(`CREATE INDEX "IDX_mrf_policy_enabled_priority" ON "mrf_policy" ("enabled", "priority")`); | ||
| await queryRunner.query(`CREATE UNIQUE INDEX "IDX_mrf_policy_builtinPolicyId" ON "mrf_policy" ("builtinPolicyId")`); | ||
| await queryRunner.query(`INSERT INTO "mrf_policy" ("id", "name", "enabled", "priority", "source", "timeoutMs", "isBuiltin", "builtinPolicyId", "paramsSchema", "params", "scope") VALUES ($1, $2, true, 10, $3, 50, true, $4, $5::jsonb, $6::jsonb, $7::jsonb)`, [ | ||
| 'mrfbuiltinkeyword001', | ||
| 'Keyword filter', | ||
| ` | ||
| policy = { | ||
| params = { | ||
| keywords = { | ||
| type = "string_array", | ||
| default = { | ||
| "https://discord.gg/ctkpaarr", | ||
| "@ap12@mastodon-japan.net", | ||
| "ctkpaarr", | ||
| }, | ||
| label = "Blocked keywords", | ||
| }, | ||
| }, | ||
| } | ||
|
|
||
| function filter(ctx) | ||
| local note = mrf.activity.note(ctx.activity) | ||
| if note == nil then | ||
| return mrf.accept() | ||
| end | ||
|
|
||
| local content = mrf.note.content(note) | ||
| if type(content) ~= "string" then | ||
| return mrf.accept() | ||
| end | ||
|
|
||
| for _, keyword in ipairs(ctx.params.keywords) do | ||
| if string.find(content, keyword, 1, true) ~= nil then | ||
| return mrf.reject("keyword filter matched: " .. keyword) | ||
| end | ||
| end | ||
|
|
||
| return mrf.accept() | ||
| end | ||
| `, | ||
| 'keyword-filter', | ||
| JSON.stringify({ | ||
| keywords: { | ||
| type: 'string_array', | ||
| default: [ | ||
| 'https://discord.gg/ctkpaarr', | ||
| '@ap12@mastodon-japan.net', | ||
| 'ctkpaarr', | ||
| ], | ||
| label: 'Blocked keywords', | ||
| }, | ||
| }), | ||
| '{}', | ||
| JSON.stringify({ | ||
| activityTypes: ['Create'], | ||
| objectTypes: ['Note'], | ||
| }), | ||
| ]); | ||
| await queryRunner.query(`INSERT INTO "mrf_policy" ("id", "name", "enabled", "priority", "source", "timeoutMs", "isBuiltin", "builtinPolicyId", "paramsSchema", "params", "scope") VALUES ($1, $2, true, 20, $3, 50, true, $4, $5::jsonb, $6::jsonb, $7::jsonb)`, [ | ||
| 'mrfbuiltinnewuserspam001', | ||
| 'New user spam mention filter', | ||
| ` | ||
| policy = { | ||
| params = { | ||
| maxFollowers = { | ||
| type = "integer", | ||
| default = 0, | ||
| label = "Maximum followers", | ||
| }, | ||
| maxFollowing = { | ||
| type = "integer", | ||
| default = 0, | ||
| label = "Maximum following", | ||
| }, | ||
| onlyTopLevelPosts = { | ||
| type = "boolean", | ||
| default = true, | ||
| label = "Only top-level posts", | ||
| }, | ||
| }, | ||
| } | ||
|
|
||
| function filter(ctx) | ||
| local note = mrf.activity.note(ctx.activity) | ||
| if note == nil then | ||
| return mrf.accept() | ||
| end | ||
|
|
||
| local mentions = mrf.note.mentions(note) | ||
| if #mentions == 0 then | ||
| return mrf.accept() | ||
| end | ||
|
|
||
| local local_prefix = "https://" .. ctx.localHost .. "/" | ||
| local has_local_mention = false | ||
| for _, mention in ipairs(mentions) do | ||
| if type(mention.href) == "string" and string.sub(mention.href, 1, #local_prefix) == local_prefix then | ||
| has_local_mention = true | ||
| break | ||
| end | ||
| end | ||
|
|
||
| if not has_local_mention then | ||
| return mrf.accept() | ||
| end | ||
|
|
||
| if (ctx.actor.followersCount or 0) <= ctx.params.maxFollowers and (ctx.actor.followingCount or 0) <= ctx.params.maxFollowing and (not ctx.params.onlyTopLevelPosts or mrf.is_nil(note.inReplyTo)) then | ||
| mrf.note.remove_mentions(note) | ||
| return mrf.rewrite(ctx.activity, "stripped unsolicited local mentions from new remote actor") | ||
| end | ||
|
|
||
| return mrf.accept() | ||
| end | ||
| `, | ||
| 'new-user-spam', | ||
| JSON.stringify({ | ||
| maxFollowers: { | ||
| type: 'integer', | ||
| default: 0, | ||
| label: 'Maximum followers', | ||
| }, | ||
| maxFollowing: { | ||
| type: 'integer', | ||
| default: 0, | ||
| label: 'Maximum following', | ||
| }, | ||
| onlyTopLevelPosts: { | ||
| type: 'boolean', | ||
| default: true, | ||
| label: 'Only top-level posts', | ||
| }, | ||
| }), | ||
| '{}', | ||
| JSON.stringify({ | ||
| activityTypes: ['Create'], | ||
| objectTypes: ['Note'], | ||
| }), | ||
| ]); | ||
| await queryRunner.query(`INSERT INTO "mrf_policy" ("id", "name", "enabled", "priority", "source", "timeoutMs", "isBuiltin", "builtinPolicyId", "paramsSchema", "params", "scope") VALUES ($1, $2, true, 30, $3, 50, true, $4, $5::jsonb, $6::jsonb, $7::jsonb)`, [ | ||
| 'mrfbuiltinhellthread001', | ||
| 'Hellthread mention filter', | ||
| ` | ||
| policy = { | ||
| params = { | ||
| mentionThreshold = { | ||
| type = "integer", | ||
| default = 15, | ||
| label = "Mention threshold", | ||
| }, | ||
| }, | ||
| } | ||
|
|
||
| function filter(ctx) | ||
| local note = mrf.activity.note(ctx.activity) | ||
| if note == nil then | ||
| return mrf.accept() | ||
| end | ||
|
|
||
| if mrf.note.mention_count(note) >= ctx.params.mentionThreshold then | ||
| mrf.note.remove_mentions(note) | ||
| return mrf.rewrite(ctx.activity, "stripped hellthread mentions") | ||
| end | ||
|
|
||
| return mrf.accept() | ||
| end | ||
| `, | ||
| 'hellthread', | ||
| JSON.stringify({ | ||
| mentionThreshold: { | ||
| type: 'integer', | ||
| default: 15, | ||
| label: 'Mention threshold', | ||
| }, | ||
| }), | ||
| '{}', | ||
| JSON.stringify({ | ||
| activityTypes: ['Create'], | ||
| objectTypes: ['Note'], | ||
| }), | ||
| ]); | ||
| } | ||
|
|
||
| async down(queryRunner) { | ||
| await queryRunner.query(`DROP INDEX "IDX_mrf_policy_builtinPolicyId"`); | ||
| await queryRunner.query(`DROP INDEX "IDX_mrf_policy_enabled_priority"`); | ||
| await queryRunner.query(`DROP TABLE "mrf_policy"`); | ||
| } | ||
| } |
34 changes: 34 additions & 0 deletions
34
packages/backend/migration/1786507200000-EmojiSuggestion.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| /* | ||
| * SPDX-FileCopyrightText: Sharkey contributors | ||
| * SPDX-License-Identifier: AGPL-3.0-only | ||
| */ | ||
|
|
||
| /** | ||
| * @typedef {import('typeorm').MigrationInterface} MigrationInterface | ||
| * @typedef {import('typeorm').QueryRunner} QueryRunner | ||
| */ | ||
|
|
||
| /** | ||
| * @class | ||
| * @implements {MigrationInterface} | ||
| */ | ||
| export class EmojiSuggestion1786507200000 { | ||
| name = 'EmojiSuggestion1786507200000' | ||
|
|
||
| /** | ||
| * @param {QueryRunner} queryRunner | ||
| */ | ||
| async up(queryRunner) { | ||
| await queryRunner.query(`CREATE TABLE "emoji_suggestion" ("id" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, "fileId" character varying(32) NOT NULL, "name" character varying(128) NOT NULL, "category" character varying(128), "aliases" character varying(128) array NOT NULL DEFAULT '{}', "license" character varying(1024), "localOnly" boolean NOT NULL DEFAULT false, "isSensitive" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_emoji_suggestion" PRIMARY KEY ("id"), CONSTRAINT "FK_emoji_suggestion_user" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_emoji_suggestion_file" FOREIGN KEY ("fileId") REFERENCES "drive_file"("id") ON DELETE CASCADE ON UPDATE NO ACTION)`); | ||
| await queryRunner.query(`CREATE INDEX "IDX_emoji_suggestion_user" ON "emoji_suggestion" ("userId")`); | ||
| await queryRunner.query(`CREATE UNIQUE INDEX "IDX_emoji_suggestion_file" ON "emoji_suggestion" ("fileId")`); | ||
| await queryRunner.query(`CREATE UNIQUE INDEX "IDX_emoji_suggestion_name" ON "emoji_suggestion" ("name")`); | ||
| } | ||
|
|
||
| /** | ||
| * @param {QueryRunner} queryRunner | ||
| */ | ||
| async down(queryRunner) { | ||
| await queryRunner.query(`DROP TABLE "emoji_suggestion"`); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Add the missing semicolon.
ESLint reports
@stylistic/semiat Line 16. Lint will fail in CI.🔧 Proposed fix
📝 Committable suggestion
🧰 Tools
🪛 ESLint
[error] 16-17: Missing semicolon.
(
@stylistic/semi)🤖 Prompt for AI Agents
Source: Linters/SAST tools