5
5
6
6
import { Inject , Injectable , OnApplicationShutdown } from '@nestjs/common' ;
7
7
import * as Redis from 'ioredis' ;
8
- import type { AvatarDecorationsRepository , MiAvatarDecoration , MiUser } from '@/models/_.js' ;
8
+ import type { AvatarDecorationsRepository , InstancesRepository , UsersRepository , MiAvatarDecoration , MiUser } from '@/models/_.js' ;
9
9
import { IdService } from '@/core/IdService.js' ;
10
10
import { GlobalEventService } from '@/core/GlobalEventService.js' ;
11
11
import { DI } from '@/di-symbols.js' ;
12
12
import { bindThis } from '@/decorators.js' ;
13
13
import { MemorySingleCache } from '@/misc/cache.js' ;
14
14
import type { GlobalEvents } from '@/core/GlobalEventService.js' ;
15
15
import { ModerationLogService } from '@/core/ModerationLogService.js' ;
16
+ import { HttpRequestService } from "@/core/HttpRequestService.js" ;
17
+ import { appendQuery , query } from '@/misc/prelude/url.js' ;
18
+ import type { Config } from '@/config.js' ;
19
+ import { IsNull } from "typeorm" ;
16
20
17
21
@Injectable ( )
18
22
export class AvatarDecorationService implements OnApplicationShutdown {
19
23
public cache : MemorySingleCache < MiAvatarDecoration [ ] > ;
24
+ public cacheWithRemote : MemorySingleCache < MiAvatarDecoration [ ] > ;
20
25
21
26
constructor (
27
+ @Inject ( DI . config )
28
+ private config : Config ,
29
+
22
30
@Inject ( DI . redisForSub )
23
31
private redisForSub : Redis . Redis ,
24
32
25
33
@Inject ( DI . avatarDecorationsRepository )
26
34
private avatarDecorationsRepository : AvatarDecorationsRepository ,
27
35
36
+ @Inject ( DI . instancesRepository )
37
+ private instancesRepository : InstancesRepository ,
38
+
39
+ @Inject ( DI . usersRepository )
40
+ private usersRepository : UsersRepository ,
41
+
28
42
private idService : IdService ,
29
43
private moderationLogService : ModerationLogService ,
30
44
private globalEventService : GlobalEventService ,
45
+ private httpRequestService : HttpRequestService ,
31
46
) {
32
47
this . cache = new MemorySingleCache < MiAvatarDecoration [ ] > ( 1000 * 60 * 30 ) ; // 30s
48
+ this . cacheWithRemote = new MemorySingleCache < MiAvatarDecoration [ ] > ( 1000 * 60 * 30 ) ;
33
49
34
50
this . redisForSub . on ( 'message' , this . onMessage ) ;
35
51
}
@@ -94,6 +110,99 @@ export class AvatarDecorationService implements OnApplicationShutdown {
94
110
}
95
111
}
96
112
113
+ @bindThis
114
+ private getProxiedUrl ( url : string , mode ?: 'static' | 'avatar' ) : string {
115
+ return appendQuery (
116
+ `${ this . config . mediaProxy } /${ mode ?? 'image' } .webp` ,
117
+ query ( {
118
+ url,
119
+ ...( mode ? { [ mode ] : '1' } : { } ) ,
120
+ } ) ,
121
+ ) ;
122
+ }
123
+
124
+ @bindThis
125
+ public async remoteUserUpdate ( user : MiUser ) {
126
+ const userHost = user . host ?? '' ;
127
+ const instance = await this . instancesRepository . findOneBy ( { host : userHost } ) ;
128
+ const userHostUrl = `https://${ user . host } ` ;
129
+ const showUserApiUrl = `${ userHostUrl } /api/users/show` ;
130
+
131
+ if ( instance ?. softwareName !== 'misskey' && instance ?. softwareName !== 'cherrypick' ) {
132
+ return ;
133
+ }
134
+
135
+ const res = await this . httpRequestService . send ( showUserApiUrl , {
136
+ method : 'POST' ,
137
+ headers : { "Content-Type" : "application/json" } ,
138
+ body : JSON . stringify ( { "username" : user . username } ) ,
139
+ } ) ;
140
+
141
+ const userData : any = await res . json ( ) ;
142
+ const userAvatarDecorations = userData . avatarDecorations ?? undefined ;
143
+
144
+ if ( ! userAvatarDecorations || userAvatarDecorations . length === 0 ) {
145
+ const updates = { } as Partial < MiUser > ;
146
+ updates . avatarDecorations = [ ] ;
147
+ await this . usersRepository . update ( { id : user . id } , updates ) ;
148
+ return ;
149
+ }
150
+
151
+ const instanceHost = instance ?. host ;
152
+ const decorationApiUrl = `https://${ instanceHost } /api/get-avatar-decorations` ;
153
+ const allRes = await this . httpRequestService . send ( decorationApiUrl , {
154
+ method : 'POST' ,
155
+ headers : { "Content-Type" : "application/json" } ,
156
+ body : JSON . stringify ( { } ) ,
157
+ } ) ;
158
+ const allDecorations : any = await allRes . json ( ) ;
159
+ const updates = { } as Partial < MiUser > ;
160
+ updates . avatarDecorations = [ ] ;
161
+ for ( const avatarDecoration of userAvatarDecorations ) {
162
+ let name ;
163
+ let description ;
164
+ const avatarDecorationId = avatarDecoration . id
165
+ for ( const decoration of allDecorations ) {
166
+ if ( decoration . id == avatarDecorationId ) {
167
+ name = decoration . name ;
168
+ description = decoration . description ;
169
+ break ;
170
+ }
171
+ }
172
+ const existingDecoration = await this . avatarDecorationsRepository . findOneBy ( {
173
+ host : userHost ,
174
+ remoteId : avatarDecorationId
175
+ } ) ;
176
+ const decorationData = {
177
+ name : name ,
178
+ description : description ,
179
+ url : this . getProxiedUrl ( avatarDecoration . url , 'static' ) ,
180
+ remoteId : avatarDecorationId ,
181
+ host : userHost ,
182
+ } ;
183
+ if ( existingDecoration == null ) {
184
+ await this . create ( decorationData ) ;
185
+ this . cacheWithRemote . delete ( ) ;
186
+ } else {
187
+ await this . update ( existingDecoration . id , decorationData ) ;
188
+ this . cacheWithRemote . delete ( ) ;
189
+ }
190
+ const findDecoration = await this . avatarDecorationsRepository . findOneBy ( {
191
+ host : userHost ,
192
+ remoteId : avatarDecorationId
193
+ } ) ;
194
+
195
+ updates . avatarDecorations . push ( {
196
+ id : findDecoration ?. id ?? '' ,
197
+ angle : avatarDecoration . angle ?? 0 ,
198
+ flipH : avatarDecoration . flipH ?? false ,
199
+ offsetX : avatarDecoration . offsetX ?? 0 ,
200
+ offsetY : avatarDecoration . offsetY ?? 0 ,
201
+ } ) ;
202
+ }
203
+ await this . usersRepository . update ( { id : user . id } , updates ) ;
204
+ }
205
+
97
206
@bindThis
98
207
public async delete ( id : MiAvatarDecoration [ 'id' ] , moderator ?: MiUser ) : Promise < void > {
99
208
const avatarDecoration = await this . avatarDecorationsRepository . findOneByOrFail ( { id } ) ;
@@ -110,11 +219,16 @@ export class AvatarDecorationService implements OnApplicationShutdown {
110
219
}
111
220
112
221
@bindThis
113
- public async getAll ( noCache = false ) : Promise < MiAvatarDecoration [ ] > {
222
+ public async getAll ( noCache = false , withRemote = false ) : Promise < MiAvatarDecoration [ ] > {
114
223
if ( noCache ) {
115
224
this . cache . delete ( ) ;
225
+ this . cacheWithRemote . delete ( ) ;
226
+ }
227
+ if ( ! withRemote ) {
228
+ return this . cache . fetch ( ( ) => this . avatarDecorationsRepository . find ( { where : { host : IsNull ( ) } } ) ) ;
229
+ } else {
230
+ return this . cacheWithRemote . fetch ( ( ) => this . avatarDecorationsRepository . find ( ) ) ;
116
231
}
117
- return this . cache . fetch ( ( ) => this . avatarDecorationsRepository . find ( ) ) ;
118
232
}
119
233
120
234
@bindThis
0 commit comments