Skip to content

Commit fcfd004

Browse files
authored
feat(analytics): Google Analytics・同意モード・一部機能のトラッキング実装 (#784)
1 parent 2f4c48b commit fcfd004

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+805
-113
lines changed

.config/docker_example.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -195,4 +195,4 @@ signToActivityPubGet: true
195195
#maxFileSize: 262144000
196196

197197
# Value of Content-Security-Policy header
198-
#contentSecurityPolicy: "script-src 'self' 'unsafe-eval' https://challenges.cloudflare.com https://hcaptcha.com https://*.hcaptcha.com https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/ https://www.recaptcha.net/recaptcha/; base-uri 'self'; object-src 'self';"
198+
#contentSecurityPolicy: "script-src 'self' 'unsafe-eval' https://challenges.cloudflare.com https://hcaptcha.com https://*.hcaptcha.com https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/ https://www.recaptcha.net/recaptcha/ https://www.googletagmanager.com/; base-uri 'self'; object-src 'self';"

.config/example.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ redis:
118118
# ┌───────────────────────────┐
119119
#───┘ MeiliSearch configuration └─────────────────────────────
120120

121-
# You can set scope to local (default value) or global
121+
# You can set scope to local (default value) or global
122122
# (include notes from remote).
123123

124124
#meilisearch:
@@ -214,7 +214,7 @@ proxyRemoteFiles: true
214214
signToActivityPubGet: true
215215

216216
# For security reasons, uploading attachments from the intranet is prohibited,
217-
# but exceptions can be made from the following settings. Default value is "undefined".
217+
# but exceptions can be made from the following settings. Default value is "undefined".
218218
# Read changelog to learn more (Improvements of 12.90.0 (2021/09/04)).
219219
#allowedPrivateNetworks: [
220220
# '127.0.0.1/32'
@@ -227,4 +227,4 @@ signToActivityPubGet: true
227227
#pidFile: /tmp/misskey.pid
228228

229229
# Value of Content-Security-Policy header
230-
#contentSecurityPolicy: "script-src 'self' 'unsafe-eval' https://challenges.cloudflare.com https://hcaptcha.com https://*.hcaptcha.com https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/ https://www.recaptcha.net/recaptcha/; base-uri 'self'; object-src 'self';"
230+
#contentSecurityPolicy: "script-src 'self' 'unsafe-eval' https://challenges.cloudflare.com https://hcaptcha.com https://*.hcaptcha.com https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/ https://www.recaptcha.net/recaptcha/ https://www.googletagmanager.com/; base-uri 'self'; object-src 'self';"

locales/en-US.yml

+14
Original file line numberDiff line numberDiff line change
@@ -1273,6 +1273,20 @@ here: "here"
12731273
mutualLink: "Mutual Link"
12741274
saveThisFile: "Save this file to Drive"
12751275
changeUserName: "Change name"
1276+
gtagConsentCustomize: "Data Collection and Privacy Settings"
1277+
gtagConsentCustomizeDescription: "You can customize the scope of data collected by {host}.\nHowever, you cannot disable the collection of security-related information such as authentication features, fraud prevention, and other user protections."
1278+
gtagConsentAnalytics: "Collection of Statistical Information"
1279+
gtagConsentAnalyticsDescription: "Enable the storage (cookies, etc.) of analytics-related information such as site visit duration."
1280+
gtagConsentFunctionality: "Collection of Feature and Setting Usage"
1281+
gtagConsentFunctionalityDescription: "Enable the storage of information that supports website or app features, such as language settings."
1282+
gtagConsentPersonalization: "Collection of Personalized Information"
1283+
gtagConsentPersonalizationDescription: "Enable the storage of personalization-related information such as recommended posts."
1284+
helpUsImproveUserExperience: "To build the future of Misskey,\nplease help us by agreeing to data collection!"
1285+
pleaseConsentToTracking: "{host} may collect information that may include personal data such as your IP address, usage data, and device information during your use, based on our [Privacy Policy]({privacyPolicyUrl}), for the purpose of providing and operating the service and improving the user experience.\n\nThe collected data will be used for future feature development, operational policy decisions, and identifying areas for service improvement."
1286+
consentEssential: "Allow Essential Items"
1287+
consentAll: "Allow All Items"
1288+
consentSelected: "Allow Selected Items"
1289+
12761290
_bubbleGame:
12771291
howToPlay: "How to play"
12781292
hold: "Hold"

locales/index.d.ts

+56
Original file line numberDiff line numberDiff line change
@@ -5150,6 +5150,62 @@ export interface Locale extends ILocale {
51505150
* 名前を変更
51515151
*/
51525152
"changeUserName": string;
5153+
/**
5154+
* データ収集とプライバシー設定
5155+
*/
5156+
"gtagConsentCustomize": string;
5157+
/**
5158+
* {host}が収集するデータの範囲をカスタマイズできます。
5159+
* ただし、認証機能、不正行為防止、その他のユーザー保護など、セキュリティに関連する情報の収集は無効化できません。
5160+
*/
5161+
"gtagConsentCustomizeDescription": ParameterizedString<"host">;
5162+
/**
5163+
* 統計情報の収集
5164+
*/
5165+
"gtagConsentAnalytics": string;
5166+
/**
5167+
* サイトの滞在時間など、分析に関連する情報の保存(Cookie など)を有効にします。
5168+
*/
5169+
"gtagConsentAnalyticsDescription": string;
5170+
/**
5171+
* 機能・設定の利用状況の収集
5172+
*/
5173+
"gtagConsentFunctionality": string;
5174+
/**
5175+
* 言語設定など、ウェブサイトやアプリの機能をサポートする情報の保存を有効にします。
5176+
*/
5177+
"gtagConsentFunctionalityDescription": string;
5178+
/**
5179+
* パーソナライズされた情報の収集
5180+
*/
5181+
"gtagConsentPersonalization": string;
5182+
/**
5183+
* おすすめの投稿など、パーソナライズに関連する情報の保存を有効にします。
5184+
*/
5185+
"gtagConsentPersonalizationDescription": string;
5186+
/**
5187+
* Misskeyの明日を作るために、
5188+
* データ収集にご協力ください!
5189+
*/
5190+
"helpUsImproveUserExperience": string;
5191+
/**
5192+
* {host}は[プライバシーポリシー]({privacyPolicyUrl})に基づき、サービスの提供・運営・ユーザー体験の向上のためにご利用中のIPアドレス、利用状況、デバイス情報等、個人情報を含む可能性のある情報を収集することがあります。
5193+
*
5194+
* 収集されたデータは今後の機能の開発、運営の方針の決定、サービスの改善点の特定に利用されます。
5195+
*/
5196+
"pleaseConsentToTracking": ParameterizedString<"host" | "privacyPolicyUrl">;
5197+
/**
5198+
* 必須項目のみ許可
5199+
*/
5200+
"consentEssential": string;
5201+
/**
5202+
* 全て許可
5203+
*/
5204+
"consentAll": string;
5205+
/**
5206+
* 選択した項目のみ許可
5207+
*/
5208+
"consentSelected": string;
51535209
"_bubbleGame": {
51545210
/**
51555211
* 遊び方

locales/ja-JP.yml

+13
Original file line numberDiff line numberDiff line change
@@ -1282,6 +1282,19 @@ here: "こちら"
12821282
mutualLink: "相互リンク"
12831283
saveThisFile: "このファイルをドライブに保存する"
12841284
changeUserName: "名前を変更"
1285+
gtagConsentCustomize: "データ収集とプライバシー設定"
1286+
gtagConsentCustomizeDescription: "{host}が収集するデータの範囲をカスタマイズできます。\nただし、認証機能、不正行為防止、その他のユーザー保護など、セキュリティに関連する情報の収集は無効化できません。"
1287+
gtagConsentAnalytics: "統計情報の収集"
1288+
gtagConsentAnalyticsDescription: "サイトの滞在時間など、分析に関連する情報の保存(Cookie など)を有効にします。"
1289+
gtagConsentFunctionality: "機能・設定の利用状況の収集"
1290+
gtagConsentFunctionalityDescription: "言語設定など、ウェブサイトやアプリの機能をサポートする情報の保存を有効にします。"
1291+
gtagConsentPersonalization: "パーソナライズされた情報の収集"
1292+
gtagConsentPersonalizationDescription: "おすすめの投稿など、パーソナライズに関連する情報の保存を有効にします。"
1293+
helpUsImproveUserExperience: "Misskeyの明日を作るために、\nデータ収集にご協力ください!"
1294+
pleaseConsentToTracking: "{host}は[プライバシーポリシー]({privacyPolicyUrl})に基づき、サービスの提供・運営・ユーザー体験の向上のためにご利用中のIPアドレス、利用状況、デバイス情報等、個人情報を含む可能性のある情報を収集することがあります。\n\n収集されたデータは今後の機能の開発、運営の方針の決定、サービスの改善点の特定に利用されます。"
1295+
consentEssential: "必須項目のみ許可"
1296+
consentAll: "全て許可"
1297+
consentSelected: "選択した項目のみ許可"
12851298

12861299
_bubbleGame:
12871300
howToPlay: "遊び方"

locales/ko-KR.yml

+14
Original file line numberDiff line numberDiff line change
@@ -1266,6 +1266,20 @@ here: "여기"
12661266
mutualLink: "서로링크"
12671267
saveThisFile: "이 파일을 드라이브에 저장"
12681268
changeUserName: "이름 변경"
1269+
gtagConsentCustomize: "데이터 수집 및 개인정보 설정"
1270+
gtagConsentCustomizeDescription: "{host}에서 수집하는 데이터 범위를 사용자 지정할 수 있습니다.\n다만, 인증 기능, 부정 행위 방지, 기타 사용자 보호 등 보안과 관련된 정보 수집은 비활성화할 수 없습니다."
1271+
gtagConsentAnalytics: "통계 정보 수집"
1272+
gtagConsentAnalyticsDescription: "사이트 체류 시간 등 분석 관련 정보 저장(쿠키 등)을 활성화합니다."
1273+
gtagConsentFunctionality: "기능 및 설정 사용 정보 수집"
1274+
gtagConsentFunctionalityDescription: "언어 설정 등 웹사이트나 앱의 기능을 지원하는 정보 저장을 활성화합니다."
1275+
gtagConsentPersonalization: "개인 맞춤형 정보 수집"
1276+
gtagConsentPersonalizationDescription: "추천 게시물 등 개인화 관련 정보 저장을 활성화합니다."
1277+
helpUsImproveUserExperience: "Misskey의 미래를 위해,\n데이터 수집에 협조해 주세요!"
1278+
pleaseConsentToTracking: "{host}는 [개인정보 처리방침]({privacyPolicyUrl})에 따라 서비스 제공, 운영, 사용자 경험 향상을 위해 사용 중인 IP 주소, 이용 현황, 디바이스 정보 등 개인 정보를 포함할 수 있는 정보를 수집할 수 있습니다.\n\n수집된 데이터는 향후 기능 개발, 운영 방침 결정, 서비스 개선점 파악에 활용됩니다."
1279+
consentEssential: "필수 항목만 허용"
1280+
consentAll: "모두 허용"
1281+
consentSelected: "선택한 항목만 허용"
1282+
12691283
_bubbleGame:
12701284
howToPlay: "설명"
12711285
hold: "홀드"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export class GoogleAnalyticsId1730629332694 {
2+
name = 'GoogleAnalyticsId1730629332694'
3+
4+
async up(queryRunner) {
5+
await queryRunner.query(`ALTER TABLE "meta" ADD "googleAnalyticsId" character varying(32)`);
6+
}
7+
8+
async down(queryRunner) {
9+
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "googleAnalyticsId"`);
10+
}
11+
}

packages/backend/src/NestLogger.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44
*/
55

66
import { LoggerService } from '@nestjs/common';
7-
import Logger from '@/logger.js';
7+
import { coreLogger } from '@/logger.js';
88

9-
const logger = new Logger('core', 'cyan');
10-
const nestLogger = logger.createSubLogger('nest', 'green', false);
9+
const nestLogger = coreLogger.createSubLogger('nest', 'green', false);
1110

1211
export class NestLogger implements LoggerService {
1312
/**

packages/backend/src/boot/entry.ts

+7-8
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { EventEmitter } from 'node:events';
1212
import process from 'node:process';
1313
import chalk from 'chalk';
1414
import Xev from 'xev';
15-
import Logger from '@/logger.js';
15+
import { coreLogger } from '@/logger.js';
1616
import { envOption } from '../env.js';
1717
import { masterMain } from './master.js';
1818
import { workerMain } from './worker.js';
@@ -24,8 +24,7 @@ process.title = `Misskey (${cluster.isPrimary ? 'master' : 'worker'})`;
2424
Error.stackTraceLimit = Infinity;
2525
EventEmitter.defaultMaxListeners = 128;
2626

27-
const logger = new Logger('core', 'cyan');
28-
const clusterLogger = logger.createSubLogger('cluster', 'orange', false);
27+
const clusterLogger = coreLogger.createSubLogger('cluster', 'orange', false);
2928
const ev = new Xev();
3029

3130
//#region Events
@@ -53,12 +52,12 @@ if (cluster.isPrimary && !envOption.disableClustering) {
5352
});
5453

5554
process.on('SIGINT', () => {
56-
logger.warn(chalk.yellow('Process received SIGINT'));
55+
coreLogger.warn(chalk.yellow('Process received SIGINT'));
5756
isShuttingDown = true;
5857
});
5958

6059
process.on('SIGTERM', () => {
61-
logger.warn(chalk.yellow('Process received SIGTERM'));
60+
coreLogger.warn(chalk.yellow('Process received SIGTERM'));
6261
isShuttingDown = true;
6362
});
6463
}
@@ -71,18 +70,18 @@ if (!envOption.quiet) {
7170
// Display detail of uncaught exception
7271
process.on('uncaughtException', err => {
7372
try {
74-
logger.error(`Uncaught exception: ${err.message}`, { error: err });
73+
coreLogger.error(`Uncaught exception: ${err.message}`, { error: err });
7574
} catch { }
7675
});
7776

7877
// Dying away...
7978
process.on('exit', code => {
80-
logger.warn(chalk.yellow(`The process is going to exit with code ${code}`));
79+
coreLogger.warn(chalk.yellow(`The process is going to exit with code ${code}`));
8180
});
8281

8382
process.on('warning', warning => {
8483
if ((warning as never)['code'] !== 'MISSKEY_SHUTDOWN') return;
85-
logger.warn(chalk.yellow(`${warning.message}: ${(warning as never)['detail']}`));
84+
coreLogger.warn(chalk.yellow(`${warning.message}: ${(warning as never)['detail']}`));
8685
for (const id in cluster.workers) cluster.workers[id]?.process.kill('SIGTERM');
8786
process.exit();
8887
});

packages/backend/src/boot/master.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import * as os from 'node:os';
1010
import cluster from 'node:cluster';
1111
import chalk from 'chalk';
1212
import chalkTemplate from 'chalk-template';
13-
import Logger from '@/logger.js';
13+
import { coreLogger } from '@/logger.js';
1414
import { loadConfig } from '@/config.js';
1515
import type { Config } from '@/config.js';
1616
import { showMachineInfo } from '@/misc/show-machine-info.js';
@@ -22,8 +22,7 @@ const _dirname = dirname(_filename);
2222

2323
const meta = JSON.parse(fs.readFileSync(`${_dirname}/../../../../built/meta.json`, 'utf-8'));
2424

25-
const logger = new Logger('core', 'cyan');
26-
const bootLogger = logger.createSubLogger('boot', 'magenta', false);
25+
const bootLogger = coreLogger.createSubLogger('boot', 'magenta', false);
2726

2827
const themeColor = chalk.hex('#86b300');
2928

packages/backend/src/core/DriveService.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import { RoleService } from '@/core/RoleService.js';
4343
import { correctFilename } from '@/misc/correct-filename.js';
4444
import { isMimeImage } from '@/misc/is-mime-image.js';
4545
import { ModerationLogService } from '@/core/ModerationLogService.js';
46+
import { LoggerService } from '@/core/LoggerService.js';
4647

4748
type AddFileArgs = {
4849
/** User who wish to add file */
@@ -123,12 +124,13 @@ export class DriveService {
123124
private globalEventService: GlobalEventService,
124125
private queueService: QueueService,
125126
private roleService: RoleService,
127+
private loggerService: LoggerService,
126128
private moderationLogService: ModerationLogService,
127129
private driveChart: DriveChart,
128130
private perUserDriveChart: PerUserDriveChart,
129131
private instanceChart: InstanceChart,
130132
) {
131-
const logger = new Logger('drive', 'blue');
133+
const logger = this.loggerService.getLogger('drive', 'blue');
132134
this.registerLogger = logger.createSubLogger('register', 'yellow');
133135
this.downloaderLogger = logger.createSubLogger('downloader');
134136
this.deleteLogger = logger.createSubLogger('delete');

packages/backend/src/core/FetchInstanceMetadataService.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ export class FetchInstanceMetadataService {
159159
throw err.statusCode ?? err.message;
160160
});
161161

162-
this.logger.succ(`Successfuly fetched nodeinfo of ${instance.host}`);
162+
this.logger.succ(`Successfully fetched nodeinfo of ${instance.host}`);
163163

164164
return info as NodeInfo;
165165
} catch (err) {

packages/backend/src/core/LoggerService.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55

66
import { Injectable } from '@nestjs/common';
7-
import Logger from '@/logger.js';
7+
import { rootLogger } from '@/logger.js';
88
import { bindThis } from '@/decorators.js';
99
import type { KEYWORD } from 'color-convert/conversions.js';
1010

@@ -16,6 +16,6 @@ export class LoggerService {
1616

1717
@bindThis
1818
public getLogger(domain: string, color?: KEYWORD | undefined, store?: boolean) {
19-
return new Logger(domain);
19+
return rootLogger.createSubLogger(domain, color, store);
2020
}
2121
}

packages/backend/src/core/NoteCreateService.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -257,15 +257,15 @@ export class NoteCreateService implements OnApplicationShutdown {
257257
const policies = await this.roleService.getUserPolicies(user.id);
258258

259259
if (!policies.canCreateContent) {
260-
this.logger.error('Request rejected because user has no permission to create content', { user: user.id, note: data });
260+
this.logger.error('Request rejected because user has no permission to create content', { userId: user.id, note: data });
261261
throw new IdentifiableError('5b1c2b67-50a6-4a8a-a59c-0ede40890de3', 'User has no permission to create content.');
262262
}
263263

264264
if (data.visibility === 'public' && data.channel == null) {
265265
const sensitiveWords = meta.sensitiveWords;
266266
if (this.utilityService.isKeyWordIncluded(data.cw ?? this.utilityService.concatNoteContentsForKeyWordCheck({ text: data.text, pollChoices: data.poll?.choices }), sensitiveWords)) {
267267
data.visibility = 'home';
268-
this.logger.warn('Visibility changed to home because sensitive words are included', { user: user.id, note: data });
268+
this.logger.warn('Visibility changed to home because sensitive words are included', { userId: user.id, note: data });
269269
} else if (!policies.canPublicNote) {
270270
data.visibility = 'home';
271271
}
@@ -281,7 +281,7 @@ export class NoteCreateService implements OnApplicationShutdown {
281281
);
282282

283283
if (hasProhibitedWords) {
284-
this.logger.error('Request rejected because prohibited words are included', { user: user.id, note: data });
284+
this.logger.error('Request rejected because prohibited words are included', { userId: user.id, note: data });
285285
throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', 'Notes including prohibited words are not allowed.');
286286
}
287287

@@ -384,7 +384,7 @@ export class NoteCreateService implements OnApplicationShutdown {
384384
if (process.env.MISSKEY_BLOCK_MENTIONS_FROM_UNFAMILIAR_REMOTE_USERS === 'true' && user.host !== null && willCauseNotification) {
385385
const userEntity = await this.usersRepository.findOneBy({ id: user.id });
386386
if ((userEntity?.followersCount ?? 0) === 0) {
387-
this.logger.error('Request rejected because user has no local followers', { user: user.id, note: data });
387+
this.logger.error('Request rejected because user has no local followers', { userId: user.id, note: data });
388388
throw new IdentifiableError('e11b3a16-f543-4885-8eb1-66cad131dbfd', 'Notes including mentions, replies, or renotes from remote users are not allowed until user has at least one local follower.');
389389
}
390390
}
@@ -396,7 +396,7 @@ export class NoteCreateService implements OnApplicationShutdown {
396396
|| (data.visibility === 'specified' && data.visibleUsers?.some(u => u.id !== user.id))
397397
|| (this.isQuote(data) && data.renote.userId !== user.id)
398398
) {
399-
this.logger.error('Request rejected because user has no permission to initiate conversation', { user: user.id, note: data });
399+
this.logger.error('Request rejected because user has no permission to initiate conversation', { userId: user.id, note: data });
400400
throw new IdentifiableError('332dd91b-6a00-430a-ac39-620cf60ad34b', 'Notes including mentions, replies, or renotes are not allowed.');
401401
}
402402
}

packages/backend/src/core/UserBlockingService.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export class UserBlockingService implements OnModuleInit {
5050
private apRendererService: ApRendererService,
5151
private loggerService: LoggerService,
5252
) {
53-
this.logger = this.loggerService.getLogger('user-block');
53+
this.logger = this.loggerService.getLogger('user:block');
5454
}
5555

5656
onModuleInit() {

0 commit comments

Comments
 (0)