Merge branch 'develop' into feature/misskey-2024.07

This commit is contained in:
dakkar 2024-08-06 17:51:51 +01:00
commit 94dceb9e15
28 changed files with 315 additions and 130 deletions

View file

@ -198,8 +198,13 @@ proxyRemoteFiles: true
# https://example.com/thumbnail.webp?thumbnail=1&url=https%3A%2F%2Fstorage.example.com%2Fpath%2Fto%2Fvideo.mp4 # https://example.com/thumbnail.webp?thumbnail=1&url=https%3A%2F%2Fstorage.example.com%2Fpath%2Fto%2Fvideo.mp4
#videoThumbnailGenerator: https://example.com #videoThumbnailGenerator: https://example.com
# Sign to ActivityPub GET request (default: true) # Sign outgoing ActivityPub GET request (default: true)
signToActivityPubGet: true signToActivityPubGet: true
# Sign outgoing ActivityPub Activities (default: true)
# Linked Data signatures are cryptographic signatures attached to each activity to provide proof of authenticity.
# When using authorized fetch, this is often undesired as any signed activity can be forwarded to a blocked instance by relays and other instances.
# This setting allows admins to disable LD signatures for increased privacy, at the expense of fewer relayed activities and additional inbound fetch (GET) requests.
attachLdSignatureForRelays: true
# check that inbound ActivityPub GET requests are signed ("authorized fetch") # check that inbound ActivityPub GET requests are signed ("authorized fetch")
checkActivityPubGetSignature: false checkActivityPubGetSignature: false

View file

@ -273,8 +273,13 @@ proxyRemoteFiles: true
# https://example.com/thumbnail.webp?thumbnail=1&url=https%3A%2F%2Fstorage.example.com%2Fpath%2Fto%2Fvideo.mp4 # https://example.com/thumbnail.webp?thumbnail=1&url=https%3A%2F%2Fstorage.example.com%2Fpath%2Fto%2Fvideo.mp4
#videoThumbnailGenerator: https://example.com #videoThumbnailGenerator: https://example.com
# Sign to ActivityPub GET request (default: true) # Sign outgoing ActivityPub GET request (default: true)
signToActivityPubGet: true signToActivityPubGet: true
# Sign outgoing ActivityPub Activities (default: true)
# Linked Data signatures are cryptographic signatures attached to each activity to provide proof of authenticity.
# When using authorized fetch, this is often undesired as any signed activity can be forwarded to a blocked instance by relays and other instances.
# This setting allows admins to disable LD signatures for increased privacy, at the expense of fewer relayed activities and additional inbound fetch (GET) requests.
attachLdSignatureForRelays: true
# check that inbound ActivityPub GET requests are signed ("authorized fetch") # check that inbound ActivityPub GET requests are signed ("authorized fetch")
checkActivityPubGetSignature: false checkActivityPubGetSignature: false

View file

@ -285,8 +285,13 @@ proxyRemoteFiles: true
# https://example.com/thumbnail.webp?thumbnail=1&url=https%3A%2F%2Fstorage.example.com%2Fpath%2Fto%2Fvideo.mp4 # https://example.com/thumbnail.webp?thumbnail=1&url=https%3A%2F%2Fstorage.example.com%2Fpath%2Fto%2Fvideo.mp4
#videoThumbnailGenerator: https://example.com #videoThumbnailGenerator: https://example.com
# Sign to ActivityPub GET request (default: true) # Sign outgoing ActivityPub GET request (default: true)
signToActivityPubGet: true signToActivityPubGet: true
# Sign outgoing ActivityPub Activities (default: true)
# Linked Data signatures are cryptographic signatures attached to each activity to provide proof of authenticity.
# When using authorized fetch, this is often undesired as any signed activity can be forwarded to a blocked instance by relays and other instances.
# This setting allows admins to disable LD signatures for increased privacy, at the expense of fewer relayed activities and additional inbound fetch (GET) requests.
attachLdSignatureForRelays: true
# check that inbound ActivityPub GET requests are signed ("authorized fetch") # check that inbound ActivityPub GET requests are signed ("authorized fetch")
checkActivityPubGetSignature: false checkActivityPubGetSignature: false

View file

@ -111,6 +111,15 @@ If your language is not listed in Crowdin, please open an issue.
![Crowdin](https://d322cqt584bo4o.cloudfront.net/misskey/localized.svg) ![Crowdin](https://d322cqt584bo4o.cloudfront.net/misskey/localized.svg)
## Icon Font (Shark Font)
Sharkey has its own Icon Font called Shark Font which can be found at https://activitypub.software/TransFem-org/shark-font
Build Instructions can all be found over there in the `README`.
If you have an Icon Suggestion or want to add an Icon please open an issue/merge request over at that repo.
When Updating the Font make sure to copy **all generated files** from the `dest` folder into `packages/backend/assets/fonts/sharkey-icons`
For the CSS simply copy the file content and replace the old content in `style.css` and for the WOFF, TTF and SVG simply replace them.
## Development ## Development
### Setup ### Setup
Before developing, you have to set up environment. Misskey requires Redis, PostgreSQL, and FFmpeg. Before developing, you have to set up environment. Misskey requires Redis, PostgreSQL, and FFmpeg.

View file

@ -208,8 +208,13 @@ id: "aidx"
# Media Proxy # Media Proxy
#mediaProxy: https://example.com/proxy #mediaProxy: https://example.com/proxy
# Sign to ActivityPub GET request (default: true) # Sign outgoing ActivityPub GET request (default: true)
signToActivityPubGet: true signToActivityPubGet: true
# Sign outgoing ActivityPub Activities (default: true)
# Linked Data signatures are cryptographic signatures attached to each activity to provide proof of authenticity.
# When using authorized fetch, this is often undesired as any signed activity can be forwarded to a blocked instance by relays and other instances.
# This setting allows admins to disable LD signatures for increased privacy, at the expense of fewer relayed activities and additional inbound fetch (GET) requests.
attachLdSignatureForRelays: true
# check that inbound ActivityPub GET requests are signed ("authorized fetch") # check that inbound ActivityPub GET requests are signed ("authorized fetch")
checkActivityPubGetSignature: false checkActivityPubGetSignature: false

View file

@ -1,11 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg">
<defs>
<font id="custom-sharkey-icons" horiz-adv-x="512">
<font-face font-family="custom-sharkey-icons" units-per-em="512" ascent="480" descent="-32"/>
<missing-glyph horiz-adv-x="512" />
<glyph glyph-name="shark" unicode="&#97;" d="M469 171l0-43-42 0c-30 0-60 9-86 21-53-27-117-27-170 0-26-12-56-21-86-21l-42 0 0 43 42 0c30 0 60 10 86 27 51-36 119-36 170 0 26-17 56-27 86-27z m-356 47c11 3 23 9 34 16l24 16c14 49 16 107-9 174 93-16 177-97 209-193 16-10 32-16 48-17-30 140-155 255-291 255-7 0-14-4-18-10-4-6-4-14-1-21 46-92 32-167 4-220m228-105c-51-36-119-36-170 0-26-17-56-28-86-28l-42 0 0-42 42 0c30 0 60 8 86 21 53-28 117-28 170 0 26-13 56-21 86-21l42 0 0 42-42 0c-30 0-60 11-86 28z"/>
<glyph glyph-name="misskey" unicode="&#98;" d="M190 152c-22 0-41 13-50 29-5 7-14 9-15 0l0-43c0-17-6-32-18-45-12-12-27-18-45-18-17 0-31 6-44 18-12 13-18 28-18 45l0 236c0 13 4 26 11 36 8 11 18 19 30 24 7 2 14 3 21 3 20 0 36-7 49-22l63-75c2-1 6-10 16-10 10 0 15 9 16 10l64 75c13 15 29 22 48 22 7 0 14-1 22-3 12-5 21-12 29-24 8-10 12-23 12-36l0-236c0-17-6-32-19-45-12-12-27-18-44-18-17 0-32 6-45 18-12 13-18 28-18 45l0 43c-1 12-11 4-15 0-9-18-28-29-50-29z m268 176c-15 0-28 6-39 16-10 11-15 24-15 39 0 15 5 28 15 38 11 11 24 16 39 16 14 0 27-5 38-16 11-10 16-23 16-38 0-15-5-28-16-39-11-10-24-16-38-16z m0-10c15 0 28-6 38-17 11-10 16-23 16-39l0-133c0-15-5-28-16-39-10-10-23-15-38-15-15 0-28 5-38 15-11 11-16 24-16 39l0 133c0 16 5 29 16 39 10 11 23 17 38 17z"/>
</font></defs></svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,30 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<defs>
<font id="shark-font" horiz-adv-x="512">
<font-face font-family="shark-font"
units-per-em="512" ascent="512"
descent="0" />
<missing-glyph horiz-adv-x="0" />
<glyph glyph-name="foldermove"
unicode="&#xEA01;"
horiz-adv-x="512" d="M74 429C63 425 54 415 52 403C52 400 51 369 52 323V248L68 249H83V292L84 335H431V114H84V207H52V157C52 108 52 107 54 102C56 95 64 87 71 85L76 83H441L445 85Q455.5 91 460 100L462 105V344L459 349C456 356 451 361 444 364L439 367H264L235 396C211 420 205 425 201 427L196 430H137C91 430 76 430 74 429M203 383L219 367H84V399H187zM166 303C160 300 157 294 159 289C160 287 168 277 185 261L209 237H122C40 237 34 237 32 236S28 233 27 232C25 229 25 229 25 126C25 26 25 23 27 21C32 13 44 14 47 24C47 26 48 62 48 121V215H128C172 215 208 215 208 214C208 214 197 203 184 189C157 162 156 162 160 155C162 150 168 147 173 149C174 150 191 166 211 185C249 223 248 223 247 229C246 232 177 302 173 303C171 304 168 304 166 303" />
<glyph glyph-name="foldermove-1"
unicode="&#x66;&#x6F;&#x6C;&#x64;&#x65;&#x72;&#x6D;&#x6F;&#x76;&#x65;"
horiz-adv-x="512" d="M74 429C63 425 54 415 52 403C52 400 51 369 52 323V248L68 249H83V292L84 335H431V114H84V207H52V157C52 108 52 107 54 102C56 95 64 87 71 85L76 83H441L445 85Q455.5 91 460 100L462 105V344L459 349C456 356 451 361 444 364L439 367H264L235 396C211 420 205 425 201 427L196 430H137C91 430 76 430 74 429M203 383L219 367H84V399H187zM166 303C160 300 157 294 159 289C160 287 168 277 185 261L209 237H122C40 237 34 237 32 236S28 233 27 232C25 229 25 229 25 126C25 26 25 23 27 21C32 13 44 14 47 24C47 26 48 62 48 121V215H128C172 215 208 215 208 214C208 214 197 203 184 189C157 162 156 162 160 155C162 150 168 147 173 149C174 150 191 166 211 185C249 223 248 223 247 229C246 232 177 302 173 303C171 304 168 304 166 303" />
<glyph glyph-name="misskey"
unicode="&#xEA02;"
horiz-adv-x="512" d="M190 152C168 152 149 165 140 181C135 188 126 190 125 181V138Q125 112.5 107 93Q89 75 62 75C45 75 31 81 18 93Q0 112.5 0 138V374C0 387 4 400 11 410Q23 426.5 41 434Q51.5 437 62 437C82 437 98 430 111 415L174 340C176 339 180 330 190 330S205 339 206 340L270 415C283 430 299 437 318 437C325 437 332 436 340 434C352 429 361 422 369 410C377 400 381 387 381 374V138C381 121 375 106 362 93C350 81 335 75 318 75Q292.5 75 273 93Q255 112.5 255 138V181C254 193 244 185 240 181C231 163 212 152 190 152M458 328C443 328 430 334 419 344Q404 360.5 404 383C404 398 409 411 419 421C430 432 443 437 458 437C472 437 485 432 496 421C507 411 512 398 512 383S507 355 496 344C485 334 472 328 458 328M458 318C473 318 486 312 496 301C507 291 512 278 512 262V129C512 114 507 101 496 90C486 80 473 75 458 75S430 80 420 90C409 101 404 114 404 129V262C404 278 409 291 420 301C430 312 443 318 458 318" />
<glyph glyph-name="misskey-1"
unicode="&#x6D;&#x69;&#x73;&#x73;&#x6B;&#x65;&#x79;"
horiz-adv-x="512" d="M190 152C168 152 149 165 140 181C135 188 126 190 125 181V138Q125 112.5 107 93Q89 75 62 75C45 75 31 81 18 93Q0 112.5 0 138V374C0 387 4 400 11 410Q23 426.5 41 434Q51.5 437 62 437C82 437 98 430 111 415L174 340C176 339 180 330 190 330S205 339 206 340L270 415C283 430 299 437 318 437C325 437 332 436 340 434C352 429 361 422 369 410C377 400 381 387 381 374V138C381 121 375 106 362 93C350 81 335 75 318 75Q292.5 75 273 93Q255 112.5 255 138V181C254 193 244 185 240 181C231 163 212 152 190 152M458 328C443 328 430 334 419 344Q404 360.5 404 383C404 398 409 411 419 421C430 432 443 437 458 437C472 437 485 432 496 421C507 411 512 398 512 383S507 355 496 344C485 334 472 328 458 328M458 318C473 318 486 312 496 301C507 291 512 278 512 262V129C512 114 507 101 496 90C486 80 473 75 458 75S430 80 420 90C409 101 404 114 404 129V262C404 278 409 291 420 301C430 312 443 318 458 318" />
<glyph glyph-name="shark"
unicode="&#xEA03;"
horiz-adv-x="512" d="M469 171V128H427C397 128 367 137 341 149C288 122 224 122 171 149C145 137 115 128 85 128H43V171H85C115 171 145 181 171 198C222 162 290 162 341 198C367 181 397 171 427 171zM113 218C124 221 136 227 147 234L171 250C185 299 187 357 162 424C255 408 339 327 371 231C387 221 403 215 419 214C389 354 264 469 128 469C121 469 114 465 110 459S106 445 109 438C155 346 141 271 113 218M341 113C290 77 222 77 171 113C145 96 115 85 85 85H43V43H85C115 43 145 51 171 64C224 36 288 36 341 64C367 51 397 43 427 43H469V85H427C397 85 367 96 341 113" />
<glyph glyph-name="shark-1"
unicode="&#x73;&#x68;&#x61;&#x72;&#x6B;"
horiz-adv-x="512" d="M469 171V128H427C397 128 367 137 341 149C288 122 224 122 171 149C145 137 115 128 85 128H43V171H85C115 171 145 181 171 198C222 162 290 162 341 198C367 181 397 171 427 171zM113 218C124 221 136 227 147 234L171 250C185 299 187 357 162 424C255 408 339 327 371 231C387 221 403 215 419 214C389 354 264 469 128 469C121 469 114 465 110 459S106 445 109 438C155 346 141 271 113 218M341 113C290 77 222 77 171 113C145 96 115 85 85 85H43V43H85C115 43 145 51 171 64C224 36 288 36 341 64C367 51 397 43 427 43H469V85H427C397 85 367 96 341 113" />
</font>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 5.1 KiB

View file

@ -1,31 +1,114 @@
@charset "UTF-8";
@font-face { @font-face {
font-family: "custom-sharkey-icons"; font-display: auto;
src: url("./custom-sharkey-icons.woff") format("woff"), font-family: "shark-font";
url("./custom-sharkey-icons.ttf") format("truetype"),
url("./custom-sharkey-icons.svg#custom-sharkey-icons") format("svg");
font-weight: normal;
font-style: normal; font-style: normal;
font-display: block; font-weight: normal;
src: url("./shark-font.woff?1722899913909") format("woff"), url("./shark-font.ttf?1722899913909") format("truetype"), url("./shark-font.svg?1722899913909#shark-font") format("svg");
} }
.sk-icons { .sk-icons {
font-family: "custom-sharkey-icons" !important; display: inline-block;
font-style: normal; font-family: "shark-font";
font-weight: normal; font-weight: normal;
font-style: normal;
font-variant: normal; font-variant: normal;
text-transform: none; text-rendering: auto;
line-height: 1; line-height: 1;
speak: none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
} }
.sk-icons.sk-shark:before { .sk-icons-lg {
content: "\61"; font-size: 1.33333em;
line-height: 0.75em;
vertical-align: -0.0667em;
} }
.sk-icons.sk-misskey:before { .sk-icons-xs {
content: "\62"; font-size: 0.75em;
}
.sk-icons-sm {
font-size: 0.875em;
}
.sk-icons-1x {
font-size: 1em;
}
.sk-icons-2x {
font-size: 2em;
}
.sk-icons-3x {
font-size: 3em;
}
.sk-icons-4x {
font-size: 4em;
}
.sk-icons-5x {
font-size: 5em;
}
.sk-icons-6x {
font-size: 6em;
}
.sk-icons-7x {
font-size: 7em;
}
.sk-icons-8x {
font-size: 8em;
}
.sk-icons-9x {
font-size: 9em;
}
.sk-icons-10x {
font-size: 10em;
}
.sk-icons-fw {
text-align: center;
width: 1.25em;
}
.sk-icons-border {
border: solid 0.08em #eee;
border-radius: 0.1em;
padding: 0.2em 0.25em 0.15em;
}
.sk-icons-pull-left {
float: left;
}
.sk-icons-pull-right {
float: right;
}
.sk-icons.sk-icons-pull-left {
margin-right: 0.3em;
}
.sk-icons.sk-icons-pull-right {
margin-left: 0.3em;
}
.sk-icons.sk-foldermove::before {
content: "\ea01";
}
.sk-icons.sk-misskey::before {
content: "\ea02";
}
.sk-icons.sk-shark::before {
content: "\ea03";
} }

View file

@ -5,11 +5,33 @@
import Redis from 'ioredis'; import Redis from 'ioredis';
import { loadConfig } from '../built/config.js'; import { loadConfig } from '../built/config.js';
import { createPostgresDataSource } from '../built/postgres.js';
const config = loadConfig(); const config = loadConfig();
const redis = new Redis(config.redis);
// createPostgresDataSource handels primaries and replicas automatically.
// usually, it only opens connections first use, so we force it using
// .initialize()
createPostgresDataSource(config)
.initialize()
.then(c => { c.destroy() })
.catch(e => { throw e });
// Connect to all redis servers
function connectToRedis(redisOptions) {
const redis = new Redis(redisOptions);
redis.on('connect', () => redis.disconnect()); redis.on('connect', () => redis.disconnect());
redis.on('error', (e) => { redis.on('error', (e) => {
throw e; throw e;
}); });
}
// If not all of these are defined, the default one gets reused.
// so we use a Set to only try connecting once to each **uniq** redis.
(new Set([
config.redis,
config.redisForPubsub,
config.redisForJobQueue,
config.redisForTimelines,
])).forEach(connectToRedis);

View file

@ -96,6 +96,7 @@ type Source = {
customMOTD?: string[]; customMOTD?: string[];
signToActivityPubGet?: boolean; signToActivityPubGet?: boolean;
attachLdSignatureForRelays?: boolean;
checkActivityPubGetSignature?: boolean; checkActivityPubGetSignature?: boolean;
perChannelMaxNoteCacheCount?: number; perChannelMaxNoteCacheCount?: number;
@ -162,6 +163,7 @@ export type Config = {
proxyRemoteFiles: boolean | undefined; proxyRemoteFiles: boolean | undefined;
customMOTD: string[] | undefined; customMOTD: string[] | undefined;
signToActivityPubGet: boolean; signToActivityPubGet: boolean;
attachLdSignatureForRelays: boolean;
checkActivityPubGetSignature: boolean | undefined; checkActivityPubGetSignature: boolean | undefined;
version: string; version: string;
@ -303,6 +305,7 @@ export function loadConfig(): Config {
proxyRemoteFiles: config.proxyRemoteFiles, proxyRemoteFiles: config.proxyRemoteFiles,
customMOTD: config.customMOTD, customMOTD: config.customMOTD,
signToActivityPubGet: config.signToActivityPubGet ?? true, signToActivityPubGet: config.signToActivityPubGet ?? true,
attachLdSignatureForRelays: config.attachLdSignatureForRelays ?? true,
checkActivityPubGetSignature: config.checkActivityPubGetSignature, checkActivityPubGetSignature: config.checkActivityPubGetSignature,
mediaProxy: externalMediaProxy ?? internalMediaProxy, mediaProxy: externalMediaProxy ?? internalMediaProxy,
externalMediaProxyEnabled: externalMediaProxy !== null && externalMediaProxy !== internalMediaProxy, externalMediaProxyEnabled: externalMediaProxy !== null && externalMediaProxy !== internalMediaProxy,

View file

@ -29,7 +29,7 @@ export class AvatarDecorationService implements OnApplicationShutdown {
private moderationLogService: ModerationLogService, private moderationLogService: ModerationLogService,
private globalEventService: GlobalEventService, private globalEventService: GlobalEventService,
) { ) {
this.cache = new MemorySingleCache<MiAvatarDecoration[]>(1000 * 60 * 30); this.cache = new MemorySingleCache<MiAvatarDecoration[]>(1000 * 60 * 30); // 30s
this.redisForSub.on('message', this.onMessage); this.redisForSub.on('message', this.onMessage);
} }

View file

@ -56,10 +56,10 @@ export class CacheService implements OnApplicationShutdown {
) { ) {
//this.onMessage = this.onMessage.bind(this); //this.onMessage = this.onMessage.bind(this);
this.userByIdCache = new MemoryKVCache<MiUser>(Infinity); this.userByIdCache = new MemoryKVCache<MiUser>(1000 * 60 * 5); // 5m
this.localUserByNativeTokenCache = new MemoryKVCache<MiLocalUser | null>(Infinity); this.localUserByNativeTokenCache = new MemoryKVCache<MiLocalUser | null>(1000 * 60 * 5); // 5m
this.localUserByIdCache = new MemoryKVCache<MiLocalUser>(Infinity); this.localUserByIdCache = new MemoryKVCache<MiLocalUser>(1000 * 60 * 5); // 5m
this.uriPersonCache = new MemoryKVCache<MiUser | null>(Infinity); this.uriPersonCache = new MemoryKVCache<MiUser | null>(1000 * 60 * 5); // 5m
this.userProfileCache = new RedisKVCache<MiUserProfile>(this.redisClient, 'userProfile', { this.userProfileCache = new RedisKVCache<MiUserProfile>(this.redisClient, 'userProfile', {
lifetime: 1000 * 60 * 30, // 30m lifetime: 1000 * 60 * 30, // 30m
@ -135,14 +135,14 @@ export class CacheService implements OnApplicationShutdown {
if (user == null) { if (user == null) {
this.userByIdCache.delete(body.id); this.userByIdCache.delete(body.id);
this.localUserByIdCache.delete(body.id); this.localUserByIdCache.delete(body.id);
for (const [k, v] of this.uriPersonCache.cache.entries()) { for (const [k, v] of this.uriPersonCache.entries) {
if (v.value?.id === body.id) { if (v.value?.id === body.id) {
this.uriPersonCache.delete(k); this.uriPersonCache.delete(k);
} }
} }
} else { } else {
this.userByIdCache.set(user.id, user); this.userByIdCache.set(user.id, user);
for (const [k, v] of this.uriPersonCache.cache.entries()) { for (const [k, v] of this.uriPersonCache.entries) {
if (v.value?.id === user.id) { if (v.value?.id === user.id) {
this.uriPersonCache.set(k, user); this.uriPersonCache.set(k, user);
} }

View file

@ -26,7 +26,7 @@ const parseEmojiStrRegexp = /^([-\w]+)(?:@([\w.-]+))?$/;
@Injectable() @Injectable()
export class CustomEmojiService implements OnApplicationShutdown { export class CustomEmojiService implements OnApplicationShutdown {
private cache: MemoryKVCache<MiEmoji | null>; private emojisCache: MemoryKVCache<MiEmoji | null>;
public localEmojisCache: RedisSingleCache<Map<string, MiEmoji>>; public localEmojisCache: RedisSingleCache<Map<string, MiEmoji>>;
constructor( constructor(
@ -49,7 +49,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
private globalEventService: GlobalEventService, private globalEventService: GlobalEventService,
private driveService: DriveService, private driveService: DriveService,
) { ) {
this.cache = new MemoryKVCache<MiEmoji | null>(1000 * 60 * 60 * 12); this.emojisCache = new MemoryKVCache<MiEmoji | null>(1000 * 60 * 60 * 12); // 12h
this.localEmojisCache = new RedisSingleCache<Map<string, MiEmoji>>(this.redisClient, 'localEmojis', { this.localEmojisCache = new RedisSingleCache<Map<string, MiEmoji>>(this.redisClient, 'localEmojis', {
lifetime: 1000 * 60 * 30, // 30m lifetime: 1000 * 60 * 30, // 30m
@ -142,6 +142,13 @@ export class CustomEmojiService implements OnApplicationShutdown {
this.localEmojisCache.refresh(); this.localEmojisCache.refresh();
if (data.driveFile != null) {
const file = await this.driveFilesRepository.findOneBy({ url: emoji.originalUrl, userHost: emoji.host ? emoji.host : IsNull() });
if (file && file.id != data.driveFile.id) {
await this.driveService.deleteFile(file, false, moderator ? moderator : undefined);
}
}
const packed = await this.emojiEntityService.packDetailed(emoji.id); const packed = await this.emojiEntityService.packDetailed(emoji.id);
if (emoji.name === data.name) { if (emoji.name === data.name) {
@ -357,7 +364,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
host: newHost ?? IsNull(), host: newHost ?? IsNull(),
})) ?? null; })) ?? null;
const emoji = await this.cache.fetch(`${name} ${host}`, queryOrNull); const emoji = await this.emojisCache.fetch(`${name} ${host}`, queryOrNull);
if (emoji == null) return null; if (emoji == null) return null;
return emoji.publicUrl || emoji.originalUrl; // || emoji.originalUrl してるのは後方互換性のためpublicUrlはstringなので??はだめ) return emoji.publicUrl || emoji.originalUrl; // || emoji.originalUrl してるのは後方互換性のためpublicUrlはstringなので??はだめ)
@ -384,7 +391,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
*/ */
@bindThis @bindThis
public async prefetchEmojis(emojis: { name: string; host: string | null; }[]): Promise<void> { public async prefetchEmojis(emojis: { name: string; host: string | null; }[]): Promise<void> {
const notCachedEmojis = emojis.filter(emoji => this.cache.get(`${emoji.name} ${emoji.host}`) == null); const notCachedEmojis = emojis.filter(emoji => this.emojisCache.get(`${emoji.name} ${emoji.host}`) == null);
const emojisQuery: any[] = []; const emojisQuery: any[] = [];
const hosts = new Set(notCachedEmojis.map(e => e.host)); const hosts = new Set(notCachedEmojis.map(e => e.host));
for (const host of hosts) { for (const host of hosts) {
@ -399,7 +406,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
select: ['name', 'host', 'originalUrl', 'publicUrl'], select: ['name', 'host', 'originalUrl', 'publicUrl'],
}) : []; }) : [];
for (const emoji of _emojis) { for (const emoji of _emojis) {
this.cache.set(`${emoji.name} ${emoji.host}`, emoji); this.emojisCache.set(`${emoji.name} ${emoji.host}`, emoji);
} }
} }
@ -424,7 +431,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
@bindThis @bindThis
public dispose(): void { public dispose(): void {
this.cache.dispose(); this.emojisCache.dispose();
} }
@bindThis @bindThis

View file

@ -6,7 +6,7 @@
import { URL } from 'node:url'; import { URL } from 'node:url';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import * as parse5 from 'parse5'; import * as parse5 from 'parse5';
import { Window, XMLSerializer } from 'happy-dom'; import { Window, DocumentFragment, XMLSerializer } from 'happy-dom';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import { intersperse } from '@/misc/prelude/array.js'; import { intersperse } from '@/misc/prelude/array.js';
@ -478,6 +478,8 @@ export class MfmService {
const doc = window.document; const doc = window.document;
const body = doc.createElement('p');
async function appendChildren(children: mfm.MfmNode[], targetElement: any): Promise<void> { async function appendChildren(children: mfm.MfmNode[], targetElement: any): Promise<void> {
if (children) { if (children) {
for (const child of await Promise.all(children.map(async (x) => await (handlers as any)[x.type](x)))) targetElement.appendChild(child); for (const child of await Promise.all(children.map(async (x) => await (handlers as any)[x.type](x)))) targetElement.appendChild(child);
@ -656,7 +658,7 @@ export class MfmService {
}, },
}; };
await appendChildren(nodes, doc.body); await appendChildren(nodes, body);
if (quoteUri !== null) { if (quoteUri !== null) {
const a = doc.createElement('a'); const a = doc.createElement('a');
@ -670,9 +672,15 @@ export class MfmService {
quote.innerHTML += 'RE: '; quote.innerHTML += 'RE: ';
quote.appendChild(a); quote.appendChild(a);
doc.body.appendChild(quote); body.appendChild(quote);
} }
return inline ? doc.body.innerHTML : `<p>${doc.body.innerHTML}</p>`; let result = new XMLSerializer().serializeToString(body);
if (inline) {
result = result.replace(/^<p>/,'').replace(/<\/p>$/,'');
}
return result;
} }
} }

View file

@ -35,7 +35,7 @@ export class RelayService {
private createSystemUserService: CreateSystemUserService, private createSystemUserService: CreateSystemUserService,
private apRendererService: ApRendererService, private apRendererService: ApRendererService,
) { ) {
this.relaysCache = new MemorySingleCache<MiRelay[]>(1000 * 60 * 10); this.relaysCache = new MemorySingleCache<MiRelay[]>(1000 * 60 * 10); // 10m
} }
@bindThis @bindThis

View file

@ -131,10 +131,8 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
private moderationLogService: ModerationLogService, private moderationLogService: ModerationLogService,
private fanoutTimelineService: FanoutTimelineService, private fanoutTimelineService: FanoutTimelineService,
) { ) {
//this.onMessage = this.onMessage.bind(this); this.rolesCache = new MemorySingleCache<MiRole[]>(1000 * 60 * 60); // 1h
this.roleAssignmentByUserIdCache = new MemoryKVCache<MiRoleAssignment[]>(1000 * 60 * 5); // 5m
this.rolesCache = new MemorySingleCache<MiRole[]>(1000 * 60 * 60 * 1);
this.roleAssignmentByUserIdCache = new MemoryKVCache<MiRoleAssignment[]>(1000 * 60 * 60 * 1);
this.redisForSub.on('message', this.onMessage); this.redisForSub.on('message', this.onMessage);
} }

View file

@ -25,7 +25,7 @@ export class UserKeypairService implements OnApplicationShutdown {
) { ) {
this.cache = new RedisKVCache<MiUserKeypair>(this.redisClient, 'userKeypair', { this.cache = new RedisKVCache<MiUserKeypair>(this.redisClient, 'userKeypair', {
lifetime: 1000 * 60 * 60 * 24, // 24h lifetime: 1000 * 60 * 60 * 24, // 24h
memoryCacheLifetime: Infinity, memoryCacheLifetime: 1000 * 60 * 60, // 1h
fetcher: (key) => this.userKeypairsRepository.findOneByOrFail({ userId: key }), fetcher: (key) => this.userKeypairsRepository.findOneByOrFail({ userId: key }),
toRedisConverter: (value) => JSON.stringify(value), toRedisConverter: (value) => JSON.stringify(value),
fromRedisConverter: (value) => JSON.parse(value), fromRedisConverter: (value) => JSON.parse(value),

View file

@ -54,8 +54,8 @@ export class ApDbResolverService implements OnApplicationShutdown {
private cacheService: CacheService, private cacheService: CacheService,
private apPersonService: ApPersonService, private apPersonService: ApPersonService,
) { ) {
this.publicKeyCache = new MemoryKVCache<MiUserPublickey | null>(Infinity); this.publicKeyCache = new MemoryKVCache<MiUserPublickey | null>(1000 * 60 * 60 * 12); // 12h
this.publicKeyByUserIdCache = new MemoryKVCache<MiUserPublickey | null>(Infinity); this.publicKeyByUserIdCache = new MemoryKVCache<MiUserPublickey | null>(1000 * 60 * 60 * 12); // 12h
} }
@bindThis @bindThis

View file

@ -792,6 +792,13 @@ export class ApRendererService {
@bindThis @bindThis
public async attachLdSignature(activity: any, user: { id: MiUser['id']; host: null; }): Promise<IActivity> { public async attachLdSignature(activity: any, user: { id: MiUser['id']; host: null; }): Promise<IActivity> {
// Linked Data signatures are cryptographic signatures attached to each activity to provide proof of authenticity.
// When using authorized fetch, this is often undesired as any signed activity can be forwarded to a blocked instance by relays and other instances.
// This setting allows admins to disable LD signatures for increased privacy, at the expense of fewer relayed activities and additional inbound fetch (GET) requests.
if (!this.config.attachLdSignatureForRelays) {
return activity;
}
const keypair = await this.userKeypairService.getUserKeypair(user.id); const keypair = await this.userKeypairService.getUserKeypair(user.id);
const jsonLd = this.jsonLdService.use(); const jsonLd = this.jsonLdService.use();

View file

@ -7,23 +7,23 @@ import * as Redis from 'ioredis';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
export class RedisKVCache<T> { export class RedisKVCache<T> {
private redisClient: Redis.Redis; private readonly lifetime: number;
private name: string; private readonly memoryCache: MemoryKVCache<T>;
private lifetime: number; private readonly fetcher: (key: string) => Promise<T>;
private memoryCache: MemoryKVCache<T>; private readonly toRedisConverter: (value: T) => string;
private fetcher: (key: string) => Promise<T>; private readonly fromRedisConverter: (value: string) => T | undefined;
private toRedisConverter: (value: T) => string;
private fromRedisConverter: (value: string) => T | undefined;
constructor(redisClient: RedisKVCache<T>['redisClient'], name: RedisKVCache<T>['name'], opts: { constructor(
private redisClient: Redis.Redis,
private name: string,
opts: {
lifetime: RedisKVCache<T>['lifetime']; lifetime: RedisKVCache<T>['lifetime'];
memoryCacheLifetime: number; memoryCacheLifetime: number;
fetcher: RedisKVCache<T>['fetcher']; fetcher: RedisKVCache<T>['fetcher'];
toRedisConverter: RedisKVCache<T>['toRedisConverter']; toRedisConverter: RedisKVCache<T>['toRedisConverter'];
fromRedisConverter: RedisKVCache<T>['fromRedisConverter']; fromRedisConverter: RedisKVCache<T>['fromRedisConverter'];
}) { },
this.redisClient = redisClient; ) {
this.name = name;
this.lifetime = opts.lifetime; this.lifetime = opts.lifetime;
this.memoryCache = new MemoryKVCache(opts.memoryCacheLifetime); this.memoryCache = new MemoryKVCache(opts.memoryCacheLifetime);
this.fetcher = opts.fetcher; this.fetcher = opts.fetcher;
@ -55,7 +55,13 @@ export class RedisKVCache<T> {
const cached = await this.redisClient.get(`kvcache:${this.name}:${key}`); const cached = await this.redisClient.get(`kvcache:${this.name}:${key}`);
if (cached == null) return undefined; if (cached == null) return undefined;
return this.fromRedisConverter(cached);
const value = this.fromRedisConverter(cached);
if (value !== undefined) {
this.memoryCache.set(key, value);
}
return value;
} }
@bindThis @bindThis
@ -77,14 +83,14 @@ export class RedisKVCache<T> {
// Cache MISS // Cache MISS
const value = await this.fetcher(key); const value = await this.fetcher(key);
this.set(key, value); await this.set(key, value);
return value; return value;
} }
@bindThis @bindThis
public async refresh(key: string) { public async refresh(key: string) {
const value = await this.fetcher(key); const value = await this.fetcher(key);
this.set(key, value); await this.set(key, value);
// TODO: イベント発行して他プロセスのメモリキャッシュも更新できるようにする // TODO: イベント発行して他プロセスのメモリキャッシュも更新できるようにする
} }
@ -101,23 +107,23 @@ export class RedisKVCache<T> {
} }
export class RedisSingleCache<T> { export class RedisSingleCache<T> {
private redisClient: Redis.Redis; private readonly lifetime: number;
private name: string; private readonly memoryCache: MemorySingleCache<T>;
private lifetime: number; private readonly fetcher: () => Promise<T>;
private memoryCache: MemorySingleCache<T>; private readonly toRedisConverter: (value: T) => string;
private fetcher: () => Promise<T>; private readonly fromRedisConverter: (value: string) => T | undefined;
private toRedisConverter: (value: T) => string;
private fromRedisConverter: (value: string) => T | undefined;
constructor(redisClient: RedisSingleCache<T>['redisClient'], name: RedisSingleCache<T>['name'], opts: { constructor(
lifetime: RedisSingleCache<T>['lifetime']; private redisClient: Redis.Redis,
private name: string,
opts: {
lifetime: number;
memoryCacheLifetime: number; memoryCacheLifetime: number;
fetcher: RedisSingleCache<T>['fetcher']; fetcher: RedisSingleCache<T>['fetcher'];
toRedisConverter: RedisSingleCache<T>['toRedisConverter']; toRedisConverter: RedisSingleCache<T>['toRedisConverter'];
fromRedisConverter: RedisSingleCache<T>['fromRedisConverter']; fromRedisConverter: RedisSingleCache<T>['fromRedisConverter'];
}) { },
this.redisClient = redisClient; ) {
this.name = name;
this.lifetime = opts.lifetime; this.lifetime = opts.lifetime;
this.memoryCache = new MemorySingleCache(opts.memoryCacheLifetime); this.memoryCache = new MemorySingleCache(opts.memoryCacheLifetime);
this.fetcher = opts.fetcher; this.fetcher = opts.fetcher;
@ -149,7 +155,13 @@ export class RedisSingleCache<T> {
const cached = await this.redisClient.get(`singlecache:${this.name}`); const cached = await this.redisClient.get(`singlecache:${this.name}`);
if (cached == null) return undefined; if (cached == null) return undefined;
return this.fromRedisConverter(cached);
const value = this.fromRedisConverter(cached);
if (value !== undefined) {
this.memoryCache.set(value);
}
return value;
} }
@bindThis @bindThis
@ -171,14 +183,14 @@ export class RedisSingleCache<T> {
// Cache MISS // Cache MISS
const value = await this.fetcher(); const value = await this.fetcher();
this.set(value); await this.set(value);
return value; return value;
} }
@bindThis @bindThis
public async refresh() { public async refresh() {
const value = await this.fetcher(); const value = await this.fetcher();
this.set(value); await this.set(value);
// TODO: イベント発行して他プロセスのメモリキャッシュも更新できるようにする // TODO: イベント発行して他プロセスのメモリキャッシュも更新できるようにする
} }
@ -187,22 +199,12 @@ export class RedisSingleCache<T> {
// TODO: メモリ節約のためあまり参照されないキーを定期的に削除できるようにする? // TODO: メモリ節約のためあまり参照されないキーを定期的に削除できるようにする?
export class MemoryKVCache<T> { export class MemoryKVCache<T> {
/** private readonly cache = new Map<string, { date: number; value: T; }>();
* private readonly gcIntervalHandle = setInterval(() => this.gc(), 1000 * 60 * 3); // 3m
* @deprecated
*/
public cache: Map<string, { date: number; value: T; }>;
private lifetime: number;
private gcIntervalHandle: NodeJS.Timeout;
constructor(lifetime: MemoryKVCache<never>['lifetime']) { constructor(
this.cache = new Map(); private readonly lifetime: number,
this.lifetime = lifetime; ) {}
this.gcIntervalHandle = setInterval(() => {
this.gc();
}, 1000 * 60 * 3);
}
@bindThis @bindThis
/** /**
@ -287,27 +289,34 @@ export class MemoryKVCache<T> {
@bindThis @bindThis
public gc(): void { public gc(): void {
const now = Date.now(); const now = Date.now();
for (const [key, { date }] of this.cache.entries()) { for (const [key, { date }] of this.cache.entries()) {
if ((now - date) > this.lifetime) { // The map is ordered from oldest to youngest.
// We can stop once we find an entry that's still active, because all following entries must *also* be active.
const age = now - date;
if (age < this.lifetime) break;
this.cache.delete(key); this.cache.delete(key);
} }
} }
}
@bindThis @bindThis
public dispose(): void { public dispose(): void {
clearInterval(this.gcIntervalHandle); clearInterval(this.gcIntervalHandle);
} }
public get entries() {
return this.cache.entries();
}
} }
export class MemorySingleCache<T> { export class MemorySingleCache<T> {
private cachedAt: number | null = null; private cachedAt: number | null = null;
private value: T | undefined; private value: T | undefined;
private lifetime: number;
constructor(lifetime: MemorySingleCache<never>['lifetime']) { constructor(
this.lifetime = lifetime; private lifetime: number,
} ) {}
@bindThis @bindThis
public set(value: T): void { public set(value: T): void {

View file

@ -45,7 +45,7 @@ export class DeliverProcessorService {
private queueLoggerService: QueueLoggerService, private queueLoggerService: QueueLoggerService,
) { ) {
this.logger = this.queueLoggerService.logger.createSubLogger('deliver'); this.logger = this.queueLoggerService.logger.createSubLogger('deliver');
this.suspendedHostsCache = new MemorySingleCache<MiInstance[]>(1000 * 60 * 60); this.suspendedHostsCache = new MemorySingleCache<MiInstance[]>(1000 * 60 * 60); // 1h
} }
@bindThis @bindThis

View file

@ -135,7 +135,7 @@ export class NodeinfoServerService {
return document; return document;
}; };
const cache = new MemorySingleCache<Awaited<ReturnType<typeof nodeinfo2>>>(1000 * 60 * 10); const cache = new MemorySingleCache<Awaited<ReturnType<typeof nodeinfo2>>>(1000 * 60 * 10); // 10m
fastify.get(nodeinfo2_1path, async (request, reply) => { fastify.get(nodeinfo2_1path, async (request, reply) => {
const base = await cache.fetch(() => nodeinfo2(21)); const base = await cache.fetch(() => nodeinfo2(21));

View file

@ -37,7 +37,7 @@ export class AuthenticateService implements OnApplicationShutdown {
private cacheService: CacheService, private cacheService: CacheService,
) { ) {
this.appCache = new MemoryKVCache<MiApp>(Infinity); this.appCache = new MemoryKVCache<MiApp>(1000 * 60 * 60 * 24 * 7); // 1w
} }
@bindThis @bindThis

View file

@ -38,8 +38,8 @@ export class UrlPreviewService {
) { ) {
this.logger = this.loggerService.getLogger('url-preview'); this.logger = this.loggerService.getLogger('url-preview');
this.previewCache = new RedisKVCache<SummalyResult>(this.redisClient, 'summaly', { this.previewCache = new RedisKVCache<SummalyResult>(this.redisClient, 'summaly', {
lifetime: 1000 * 86400, lifetime: 1000 * 60 * 60 * 24, // 1d
memoryCacheLifetime: 1000 * 10 * 60, memoryCacheLifetime: 1000 * 60 * 10, // 10m
fetcher: (key: string) => { throw new Error('the UrlPreview cache should never fetch'); }, fetcher: (key: string) => { throw new Error('the UrlPreview cache should never fetch'); },
toRedisConverter: (value) => JSON.stringify(value), toRedisConverter: (value) => JSON.stringify(value),
fromRedisConverter: (value) => JSON.parse(value), fromRedisConverter: (value) => JSON.parse(value),