Skip to content

Commit

Permalink
feat(analytics): Google Analytics・同意モード・一部機能のトラッキング実装
Browse files Browse the repository at this point in the history
  • Loading branch information
u1-liquid committed Nov 4, 2024
1 parent c9dca51 commit ab0c1fc
Show file tree
Hide file tree
Showing 51 changed files with 788 additions and 93 deletions.
2 changes: 1 addition & 1 deletion .config/docker_example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -195,4 +195,4 @@ signToActivityPubGet: true
#maxFileSize: 262144000

# Value of Content-Security-Policy header
#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';"
#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/gtag/; base-uri 'self'; object-src 'self';"
6 changes: 3 additions & 3 deletions .config/example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ redis:
# ┌───────────────────────────┐
#───┘ MeiliSearch configuration └─────────────────────────────

# You can set scope to local (default value) or global
# You can set scope to local (default value) or global
# (include notes from remote).

#meilisearch:
Expand Down Expand Up @@ -214,7 +214,7 @@ proxyRemoteFiles: true
signToActivityPubGet: true

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

# Value of Content-Security-Policy header
#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';"
#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/gtag/; base-uri 'self'; object-src 'self';"
14 changes: 14 additions & 0 deletions locales/en-US.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1273,6 +1273,20 @@ here: "here"
mutualLink: "Mutual Link"
saveThisFile: "Save this file to Drive"
changeUserName: "Change name"
gtagConsentCustomize: "Data Collection and Privacy Settings"
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."
gtagConsentAnalytics: "Collection of Statistical Information"
gtagConsentAnalyticsDescription: "Enable the storage (cookies, etc.) of analytics-related information such as site visit duration."
gtagConsentFunctionality: "Collection of Feature and Setting Usage"
gtagConsentFunctionalityDescription: "Enable the storage of information that supports website or app features, such as language settings."
gtagConsentPersonalization: "Collection of Personalized Information"
gtagConsentPersonalizationDescription: "Enable the storage of personalization-related information such as recommended posts."
helpUsImproveUserExperience: "To build the future of Misskey,\nplease help us by agreeing to data collection!"
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."
consentEssential: "Allow Essential Items"
consentAll: "Allow All Items"
consentSelected: "Allow Selected Items"

_bubbleGame:
howToPlay: "How to play"
hold: "Hold"
Expand Down
56 changes: 56 additions & 0 deletions locales/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5150,6 +5150,62 @@ export interface Locale extends ILocale {
* 名前を変更
*/
"changeUserName": string;
/**
* データ収集とプライバシー設定
*/
"gtagConsentCustomize": string;
/**
* {host}が収集するデータの範囲をカスタマイズできます。
* ただし、認証機能、不正行為防止、その他のユーザー保護など、セキュリティに関連する情報の収集は無効化できません。
*/
"gtagConsentCustomizeDescription": ParameterizedString<"host">;
/**
* 統計情報の収集
*/
"gtagConsentAnalytics": string;
/**
* サイトの滞在時間など、分析に関連する情報の保存(Cookie など)を有効にします。
*/
"gtagConsentAnalyticsDescription": string;
/**
* 機能・設定の利用状況の収集
*/
"gtagConsentFunctionality": string;
/**
* 言語設定など、ウェブサイトやアプリの機能をサポートする情報の保存を有効にします。
*/
"gtagConsentFunctionalityDescription": string;
/**
* パーソナライズされた情報の収集
*/
"gtagConsentPersonalization": string;
/**
* おすすめの投稿など、パーソナライズに関連する情報の保存を有効にします。
*/
"gtagConsentPersonalizationDescription": string;
/**
* Misskeyの明日を作るために、
* データ収集にご協力ください!
*/
"helpUsImproveUserExperience": string;
/**
* {host}は[プライバシーポリシー]({privacyPolicyUrl})に基づき、サービスの提供・運営・ユーザー体験の向上のためにご利用中のIPアドレス、利用状況、デバイス情報等、個人情報を含む可能性のある情報を収集することがあります。
*
* 収集されたデータは今後の機能の開発、運営の方針の決定、サービスの改善点の特定に利用されます。
*/
"pleaseConsentToTracking": ParameterizedString<"host" | "privacyPolicyUrl">;
/**
* 必須項目のみ許可
*/
"consentEssential": string;
/**
* 全て許可
*/
"consentAll": string;
/**
* 選択した項目のみ許可
*/
"consentSelected": string;
"_bubbleGame": {
/**
* 遊び方
Expand Down
13 changes: 13 additions & 0 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1282,6 +1282,19 @@ here: "こちら"
mutualLink: "相互リンク"
saveThisFile: "このファイルをドライブに保存する"
changeUserName: "名前を変更"
gtagConsentCustomize: "データ収集とプライバシー設定"
gtagConsentCustomizeDescription: "{host}が収集するデータの範囲をカスタマイズできます。\nただし、認証機能、不正行為防止、その他のユーザー保護など、セキュリティに関連する情報の収集は無効化できません。"
gtagConsentAnalytics: "統計情報の収集"
gtagConsentAnalyticsDescription: "サイトの滞在時間など、分析に関連する情報の保存(Cookie など)を有効にします。"
gtagConsentFunctionality: "機能・設定の利用状況の収集"
gtagConsentFunctionalityDescription: "言語設定など、ウェブサイトやアプリの機能をサポートする情報の保存を有効にします。"
gtagConsentPersonalization: "パーソナライズされた情報の収集"
gtagConsentPersonalizationDescription: "おすすめの投稿など、パーソナライズに関連する情報の保存を有効にします。"
helpUsImproveUserExperience: "Misskeyの明日を作るために、\nデータ収集にご協力ください!"
pleaseConsentToTracking: "{host}は[プライバシーポリシー]({privacyPolicyUrl})に基づき、サービスの提供・運営・ユーザー体験の向上のためにご利用中のIPアドレス、利用状況、デバイス情報等、個人情報を含む可能性のある情報を収集することがあります。\n\n収集されたデータは今後の機能の開発、運営の方針の決定、サービスの改善点の特定に利用されます。"
consentEssential: "必須項目のみ許可"
consentAll: "全て許可"
consentSelected: "選択した項目のみ許可"

_bubbleGame:
howToPlay: "遊び方"
Expand Down
14 changes: 14 additions & 0 deletions locales/ko-KR.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1266,6 +1266,20 @@ here: "여기"
mutualLink: "서로링크"
saveThisFile: "이 파일을 드라이브에 저장"
changeUserName: "이름 변경"
gtagConsentCustomize: "데이터 수집 및 개인정보 설정"
gtagConsentCustomizeDescription: "{host}에서 수집하는 데이터 범위를 사용자 지정할 수 있습니다.\n다만, 인증 기능, 부정 행위 방지, 기타 사용자 보호 등 보안과 관련된 정보 수집은 비활성화할 수 없습니다."
gtagConsentAnalytics: "통계 정보 수집"
gtagConsentAnalyticsDescription: "사이트 체류 시간 등 분석 관련 정보 저장(쿠키 등)을 활성화합니다."
gtagConsentFunctionality: "기능 및 설정 사용 정보 수집"
gtagConsentFunctionalityDescription: "언어 설정 등 웹사이트나 앱의 기능을 지원하는 정보 저장을 활성화합니다."
gtagConsentPersonalization: "개인 맞춤형 정보 수집"
gtagConsentPersonalizationDescription: "추천 게시물 등 개인화 관련 정보 저장을 활성화합니다."
helpUsImproveUserExperience: "Misskey의 미래를 위해,\n데이터 수집에 협조해 주세요!"
pleaseConsentToTracking: "{host}는 [개인정보 처리방침]({privacyPolicyUrl})에 따라 서비스 제공, 운영, 사용자 경험 향상을 위해 사용 중인 IP 주소, 이용 현황, 디바이스 정보 등 개인 정보를 포함할 수 있는 정보를 수집할 수 있습니다.\n\n수집된 데이터는 향후 기능 개발, 운영 방침 결정, 서비스 개선점 파악에 활용됩니다."
consentEssential: "필수 항목만 허용"
consentAll: "모두 허용"
consentSelected: "선택한 항목만 허용"

_bubbleGame:
howToPlay: "설명"
hold: "홀드"
Expand Down
11 changes: 11 additions & 0 deletions packages/backend/migration/1730629332694-GoogleAnalyticsId.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export class GoogleAnalyticsId1730629332694 {
name = 'GoogleAnalyticsId1730629332694'

async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "googleAnalyticsId" character varying(32)`);
}

async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "googleAnalyticsId"`);
}
}
5 changes: 2 additions & 3 deletions packages/backend/src/NestLogger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@
*/

import { LoggerService } from '@nestjs/common';
import Logger from '@/logger.js';
import { coreLogger } from '@/logger.js';

const logger = new Logger('core', 'cyan');
const nestLogger = logger.createSubLogger('nest', 'green', false);
const nestLogger = coreLogger.createSubLogger('nest', 'green', false);

export class NestLogger implements LoggerService {
/**
Expand Down
5 changes: 2 additions & 3 deletions packages/backend/src/boot/entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { EventEmitter } from 'node:events';
import process from 'node:process';
import chalk from 'chalk';
import Xev from 'xev';
import Logger from '@/logger.js';
import { coreLogger } from '@/logger.js';
import { envOption } from '../env.js';
import { masterMain } from './master.js';
import { workerMain } from './worker.js';
Expand All @@ -24,8 +24,7 @@ process.title = `Misskey (${cluster.isPrimary ? 'master' : 'worker'})`;
Error.stackTraceLimit = Infinity;
EventEmitter.defaultMaxListeners = 128;

const logger = new Logger('core', 'cyan');
const clusterLogger = logger.createSubLogger('cluster', 'orange', false);
const clusterLogger = coreLogger.createSubLogger('cluster', 'orange', false);
const ev = new Xev();

//#region Events
Expand Down
5 changes: 2 additions & 3 deletions packages/backend/src/boot/master.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import * as os from 'node:os';
import cluster from 'node:cluster';
import chalk from 'chalk';
import chalkTemplate from 'chalk-template';
import Logger from '@/logger.js';
import { coreLogger } from '@/logger.js';
import { loadConfig } from '@/config.js';
import type { Config } from '@/config.js';
import { showMachineInfo } from '@/misc/show-machine-info.js';
Expand All @@ -22,8 +22,7 @@ const _dirname = dirname(_filename);

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

const logger = new Logger('core', 'cyan');
const bootLogger = logger.createSubLogger('boot', 'magenta', false);
const bootLogger = coreLogger.createSubLogger('boot', 'magenta', false);

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

Expand Down
4 changes: 3 additions & 1 deletion packages/backend/src/core/DriveService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import { RoleService } from '@/core/RoleService.js';
import { correctFilename } from '@/misc/correct-filename.js';
import { isMimeImage } from '@/misc/is-mime-image.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import { LoggerService } from '@/core/LoggerService.js';

type AddFileArgs = {
/** User who wish to add file */
Expand Down Expand Up @@ -123,12 +124,13 @@ export class DriveService {
private globalEventService: GlobalEventService,
private queueService: QueueService,
private roleService: RoleService,
private loggerService: LoggerService,
private moderationLogService: ModerationLogService,
private driveChart: DriveChart,
private perUserDriveChart: PerUserDriveChart,
private instanceChart: InstanceChart,
) {
const logger = new Logger('drive', 'blue');
const logger = this.loggerService.getLogger('drive', 'blue');
this.registerLogger = logger.createSubLogger('register', 'yellow');
this.downloaderLogger = logger.createSubLogger('downloader');
this.deleteLogger = logger.createSubLogger('delete');
Expand Down
2 changes: 1 addition & 1 deletion packages/backend/src/core/FetchInstanceMetadataService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ export class FetchInstanceMetadataService {
throw err.statusCode ?? err.message;
});

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

return info as NodeInfo;
} catch (err) {
Expand Down
4 changes: 2 additions & 2 deletions packages/backend/src/core/LoggerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

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

Expand All @@ -16,6 +16,6 @@ export class LoggerService {

@bindThis
public getLogger(domain: string, color?: KEYWORD | undefined, store?: boolean) {
return new Logger(domain);
return rootLogger.createSubLogger(domain, color, store);
}
}
2 changes: 1 addition & 1 deletion packages/backend/src/core/UserBlockingService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export class UserBlockingService implements OnModuleInit {
private apRendererService: ApRendererService,
private loggerService: LoggerService,
) {
this.logger = this.loggerService.getLogger('user-block');
this.logger = this.loggerService.getLogger('user:block');
}

onModuleInit() {
Expand Down
8 changes: 5 additions & 3 deletions packages/backend/src/core/UserFollowingService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,8 @@ import { AccountMoveService } from '@/core/AccountMoveService.js';
import { UtilityService } from '@/core/UtilityService.js';
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
import type { ThinUser } from '@/queue/types.js';
import Logger from '../logger.js';

const logger = new Logger('following/create');
import { LoggerService } from '@/core/LoggerService.js';
import Logger from '@/logger.js';

type Local = MiLocalUser | {
id: MiLocalUser['id'];
Expand All @@ -50,6 +49,7 @@ type Both = Local | Remote;
@Injectable()
export class UserFollowingService implements OnModuleInit {
private userBlockingService: UserBlockingService;
private readonly logger: Logger;

constructor(
private moduleRef: ModuleRef,
Expand All @@ -73,6 +73,7 @@ export class UserFollowingService implements OnModuleInit {
private instancesRepository: InstancesRepository,

private cacheService: CacheService,
private loggerService: LoggerService,
private utilityService: UtilityService,
private userEntityService: UserEntityService,
private idService: IdService,
Expand All @@ -88,6 +89,7 @@ export class UserFollowingService implements OnModuleInit {
private perUserFollowingChart: PerUserFollowingChart,
private instanceChart: InstanceChart,
) {
this.logger = this.loggerService.getLogger("user:following");
}

onModuleInit() {
Expand Down
3 changes: 1 addition & 2 deletions packages/backend/src/core/activitypub/ApRequestService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,7 @@ export class ApRequestService {
private httpRequestService: HttpRequestService,
private loggerService: LoggerService,
) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
this.logger = this.loggerService?.getLogger('ap-request'); // なぜか TypeError: Cannot read properties of undefined (reading 'getLogger') と言われる
this.logger = this.loggerService.getLogger('ap:request');
}

@bindThis
Expand Down
2 changes: 1 addition & 1 deletion packages/backend/src/core/activitypub/ApResolverService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export class Resolver {
private recursionLimit = 100,
) {
this.history = new Set();
this.logger = this.loggerService.getLogger('ap-resolve');
this.logger = this.loggerService.getLogger('ap:resolve');
}

@bindThis
Expand Down
1 change: 1 addition & 0 deletions packages/backend/src/core/entities/MetaEntityService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export class MetaEntityService {
recaptchaSiteKey: instance.recaptchaSiteKey,
enableTurnstile: instance.enableTurnstile,
turnstileSiteKey: instance.turnstileSiteKey,
googleAnalyticsId: instance.googleAnalyticsId,
swPublickey: instance.swPublicKey,
themeColor: instance.themeColor,
mascotImageUrl: instance.mascotImageUrl ?? '/assets/ai.png',
Expand Down
18 changes: 12 additions & 6 deletions packages/backend/src/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,16 @@ const pinoPrettyStream = pinoPretty({

// eslint-disable-next-line import/no-default-export
export default class Logger {
private readonly domain: string;
private logger: pino.Logger;
private readonly domain: string | undefined;
private readonly logger: pino.Logger;
private context: Record<string, any> = {};

constructor(domain: string, _color?: KEYWORD, _store = true, parentLogger?: Logger) {
constructor(domain: string | undefined, _color?: KEYWORD, _store = true, parentLogger?: Logger) {
if (parentLogger) {
this.domain = parentLogger.domain + '.' + domain;
this.domain = [parentLogger.domain, domain].filter(x => x).join('.') || undefined;
this.context = { ...parentLogger.context };
this.logger = parentLogger.logger.child({ name: this.domain });
return;
} else {
this.domain = domain;
}
Expand All @@ -55,8 +58,8 @@ export default class Logger {
}

@bindThis
public createSubLogger(domain: string, _color?: KEYWORD, _store = true): Logger {
return new Logger(domain, undefined, false, this);
public createSubLogger(domain?: string, _color?: KEYWORD, _store = true): Logger {
return new Logger(domain, _color, _store, this);
}

@bindThis
Expand Down Expand Up @@ -130,3 +133,6 @@ export default class Logger {
this.logger.info({ context, important }, message);
}
}

export const rootLogger = new Logger(undefined, undefined, false, undefined);
export const coreLogger = rootLogger.createSubLogger('core', 'cyan');
6 changes: 6 additions & 0 deletions packages/backend/src/models/Meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,12 @@ export class MiMeta {

// chaptcha系を追加した際にはnodeinfoのレスポンスに追加するのを忘れないようにすること

@Column('varchar', {
length: 32,
nullable: true,
})
public googleAnalyticsId: string | null;

@Column('enum', {
enum: ['none', 'all', 'local', 'remote'],
default: 'none',
Expand Down
2 changes: 1 addition & 1 deletion packages/backend/src/models/Poll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { noteVisibilities } from '@/types.js';
import { id } from './util/id.js';
import { MiNote } from './Note.js';
import type { MiUser } from './User.js';
import type { MiChannel } from "@/models/Channel.js";
import type { MiChannel } from '@/models/Channel.js';

@Entity('poll')
export class MiPoll {
Expand Down
Loading

0 comments on commit ab0c1fc

Please sign in to comment.