Skip to content
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

feat: add rss telemetry recorder implementation #30

Merged
merged 1 commit into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
plugins {
id "com.github.node-gradle.node" version "5.0.0"
id "run.halo.plugin.devtools" version "0.0.9"
id "run.halo.plugin.devtools" version "0.4.1"
id "io.freefair.lombok" version "8.0.1"
id 'java'
}
Expand All @@ -13,13 +13,15 @@ java {
}

repositories {
maven { url 'https://s01.oss.sonatype.org/content/repositories/releases' }
maven { url 'https://s01.oss.sonatype.org/content/repositories/snapshots' }
mavenCentral()
}

dependencies {
implementation platform('run.halo.tools.platform:plugin:2.17.0-SNAPSHOT')
implementation platform('run.halo.tools.platform:plugin:2.20.11')
compileOnly 'run.halo.app:api'
compileOnly "run.halo.feed:api:1.4.0"

testImplementation 'run.halo.app:api'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
Expand Down Expand Up @@ -54,5 +56,6 @@ build {
}

halo {
version = "2.17"
version = "2.20.11"
debug = true
}
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/run/halo/umami/BasicProp.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package run.halo.umami;

import lombok.Data;
import reactor.core.publisher.Mono;
import run.halo.app.plugin.ReactiveSettingFetcher;

@Data
public class BasicProp {
private String websiteId;
private String endpoint;
private String scriptName;
private String url;

public static Mono<BasicProp> fetch(ReactiveSettingFetcher settingFetcher) {
return settingFetcher.fetch("basic", BasicProp.class);
}
}
21 changes: 21 additions & 0 deletions src/main/java/run/halo/umami/RssTelemetryConfiguration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package run.halo.umami;

import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import run.halo.app.infra.ExternalUrlSupplier;
import run.halo.app.plugin.ReactiveSettingFetcher;

@Configuration
@ConditionalOnClass(name = "run.halo.feed.TelemetryRecorder")
@RequiredArgsConstructor
public class RssTelemetryConfiguration {
private final ExternalUrlSupplier externalUrlSupplier;
private final ReactiveSettingFetcher settingFetcher;

@Bean
RssTelemetryUmamiRecorder rssTelemetryUmamiRecorder() {
return new RssTelemetryUmamiRecorder(settingFetcher, externalUrlSupplier);
}
}
83 changes: 83 additions & 0 deletions src/main/java/run/halo/umami/RssTelemetryUmamiRecorder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package run.halo.umami;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import run.halo.app.infra.ExternalUrlSupplier;
import run.halo.app.plugin.ReactiveSettingFetcher;
import run.halo.feed.TelemetryEventInfo;
import run.halo.feed.TelemetryRecorder;

import java.net.URL;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;

import static org.apache.commons.lang3.StringUtils.defaultIfBlank;

@Slf4j
@RequiredArgsConstructor
public class RssTelemetryUmamiRecorder implements TelemetryRecorder {
private final WebClient webClient = WebClient.builder().build();
private final ReactiveSettingFetcher settingFetcher;
private final ExternalUrlSupplier externalUrlSupplier;

@Override
public void record(TelemetryEventInfo eventInfo) {
var propOpt = BasicProp.fetch(settingFetcher).blockOptional();
if (propOpt.isEmpty()) {
return;
}
var siteUrl = propOpt.get().getEndpoint();
var webSiteId = propOpt.get().getWebsiteId();
if (StringUtils.isBlank(siteUrl) || StringUtils.isBlank(webSiteId)) {
return;
}
// https://umami.is/docs/api/sending-stats
webClient.post()
.uri(StringUtils.removeEnd(siteUrl, "/") + "/api/send")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.header(HttpHeaders.USER_AGENT, genUserAgent())
.body(Mono.just(createBody(webSiteId, eventInfo)), Map.class)
.retrieve()
.bodyToMono(String.class)
.doOnNext(response -> log.debug("Umami send response: {}", response))
.doOnError(e -> log.debug("Failed to send telemetry event to Umami.", e))
.block();
}

private String genUserAgent() {
// umami has bot detection to avoid filtering page views
return "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) "
+ "Chrome/131.0.0.0 Safari/537.36";
}

private Map<String, Object> createBody(String webSiteId, TelemetryEventInfo eventInfo) {
Assert.notNull(eventInfo.getTitle(), "Title must not be null.");
Assert.notNull(eventInfo.getPageUrl(), "Page url must not be null.");

var hostname = Optional.ofNullable(externalUrlSupplier.getRaw())
.map(URL::getHost)
.orElse(StringUtils.EMPTY);
var payload = new HashMap<>(Map.of(
"hostname", hostname,
"language", defaultIfBlank(eventInfo.getLanguageRegion(), Locale.CHINESE.toLanguageTag()),
"referrer", StringUtils.defaultString(eventInfo.getReferrer()),
"screen", StringUtils.defaultString(eventInfo.getScreen()),
"title", eventInfo.getTitle(),
"url", eventInfo.getPageUrl(),
"website", webSiteId,
"tag", "RSS Telemetry"
));
if (StringUtils.isNotBlank(eventInfo.getIp())) {
payload.put("ip", eventInfo.getIp());
}
return Map.of("payload", payload, "type", "event");
}
}
15 changes: 3 additions & 12 deletions src/main/java/run/halo/umami/UmamiTrackerProcessor.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package run.halo.umami;

import lombok.Data;
import org.springframework.stereotype.Component;
import org.thymeleaf.context.ITemplateContext;
import org.thymeleaf.model.IModel;
Expand All @@ -22,10 +21,10 @@ public UmamiTrackerProcessor(ReactiveSettingFetcher settingFetcher) {
@Override
public Mono<Void> process(ITemplateContext context, IModel model,
IElementModelStructureHandler structureHandler) {
return settingFetcher.fetch("basic", BasicConfig.class)
.doOnNext(basicConfig -> {
return BasicProp.fetch(settingFetcher)
.doOnNext(prop -> {
final IModelFactory modelFactory = context.getModelFactory();
model.add(modelFactory.createText(trackerScript(basicConfig.getWebsiteId(), basicConfig.endpoint, basicConfig.scriptName)));
model.add(modelFactory.createText(trackerScript(prop.getWebsiteId(), prop.getEndpoint(), prop.getScriptName())));
})
.then();
}
Expand All @@ -35,12 +34,4 @@ private String trackerScript(String websiteId, String endpoint, String scriptNam
<script async defer data-website-id="%s" src="%s/%s"></script>
""".formatted(websiteId, endpoint, scriptName);
}

@Data
public static class BasicConfig {
String websiteId;
String endpoint;
String scriptName;
String url;
}
}
9 changes: 9 additions & 0 deletions src/main/resources/ext-definitions.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
apiVersion: plugin.halo.run/v1alpha1
kind: ExtensionDefinition
metadata:
name: feed-rss-telemetry-umami-recorder
spec:
className: run.halo.umami.RssTelemetryUmamiRecorder
extensionPointName: feed-telemetry-recorder
displayName: "Umami RSS 访问量记录器"
description: "将 RSS 的内容访问量上报到 Umami"
4 changes: 3 additions & 1 deletion src/main/resources/plugin.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ metadata:
spec:
enabled: true
version: 1.0.0
requires: ">=2.17.0"
requires: ">=2.20.11"
author:
name: Halo
website: https://github.com/halo-dev
Expand All @@ -19,6 +19,8 @@ spec:
issues: https://github.com/halo-sigs/plugin-umami/issues
displayName: "Umami"
description: "提供对 Umami 的集成"
pluginDependencies:
PluginFeed?: ">=1.4.0"
license:
- name: "GPL-3.0"
url: "https://github.com/halo-sigs/plugin-umami/blob/main/LICENSE"
Loading