Implement posthog
This commit is contained in:
@@ -1,5 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { trackEvent } from "@aptabase/web";
|
|
||||||
import type { FormSubmitEvent } from "@nuxt/ui";
|
import type { FormSubmitEvent } from "@nuxt/ui";
|
||||||
|
|
||||||
const { user } = useUserSession();
|
const { user } = useUserSession();
|
||||||
@@ -35,10 +34,6 @@ const validate = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const submit = async (event: FormSubmitEvent<typeof formData>) => {
|
const submit = async (event: FormSubmitEvent<typeof formData>) => {
|
||||||
trackEvent("rewrite_activity", {
|
|
||||||
activityUrl: event.data.activityUrl,
|
|
||||||
});
|
|
||||||
|
|
||||||
await $fetch("/api/rewrite", {
|
await $fetch("/api/rewrite", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
query: {
|
query: {
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ export default defineNuxtConfig({
|
|||||||
openaiApiKey: "",
|
openaiApiKey: "",
|
||||||
databaseUrl: "",
|
databaseUrl: "",
|
||||||
public: {
|
public: {
|
||||||
aptabaseAppKey: "",
|
posthogPublicKey: "",
|
||||||
|
posthogHost: "",
|
||||||
|
posthogDefaults: "2025-05-24",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
future: { compatibilityVersion: 4 },
|
future: { compatibilityVersion: 4 },
|
||||||
|
|||||||
75
package-lock.json
generated
75
package-lock.json
generated
@@ -7,7 +7,6 @@
|
|||||||
"name": "nuxt-app",
|
"name": "nuxt-app",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aptabase/web": "^0.4.3",
|
|
||||||
"@formkit/tempo": "^0.1.2",
|
"@formkit/tempo": "^0.1.2",
|
||||||
"@google/genai": "^1.5.1",
|
"@google/genai": "^1.5.1",
|
||||||
"@neondatabase/serverless": "^1.0.0",
|
"@neondatabase/serverless": "^1.0.0",
|
||||||
@@ -21,6 +20,8 @@
|
|||||||
"nuxt": "^3.16.2",
|
"nuxt": "^3.16.2",
|
||||||
"nuxt-auth-utils": "0.5.18",
|
"nuxt-auth-utils": "0.5.18",
|
||||||
"openai": "^4.95.1",
|
"openai": "^4.95.1",
|
||||||
|
"posthog-js": "^1.256.2",
|
||||||
|
"posthog-node": "^5.1.1",
|
||||||
"radash": "^12.1.0",
|
"radash": "^12.1.0",
|
||||||
"ts-pattern": "^5.7.1",
|
"ts-pattern": "^5.7.1",
|
||||||
"url": "^0.11.4",
|
"url": "^0.11.4",
|
||||||
@@ -106,12 +107,6 @@
|
|||||||
"url": "https://github.com/sponsors/antfu"
|
"url": "https://github.com/sponsors/antfu"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@aptabase/web": {
|
|
||||||
"version": "0.4.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/@aptabase/web/-/web-0.4.3.tgz",
|
|
||||||
"integrity": "sha512-IcFGvgEcc26chrRDmnOxzH/HLeNexrAoDx2DjNFAoTjfZCSJmxrABqseVLlTI7JeGKfDdIVI+IC3AWj5v+2J0A==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
"version": "7.26.2",
|
"version": "7.26.2",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
|
||||||
@@ -5430,6 +5425,17 @@
|
|||||||
"url": "https://github.com/sponsors/mesqueeb"
|
"url": "https://github.com/sponsors/mesqueeb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/core-js": {
|
||||||
|
"version": "3.43.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.43.0.tgz",
|
||||||
|
"integrity": "sha512-N6wEbTTZSYOY2rYAn85CuvWWkCK6QweMn7/4Nr3w+gDBeBhk/x4EJeY6FPo4QzDoJZxVTv8U7CMvgWk6pOHHqA==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/core-js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/core-util-is": {
|
"node_modules/core-util-is": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
||||||
@@ -7041,6 +7047,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fflate": {
|
||||||
|
"version": "0.4.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz",
|
||||||
|
"integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/file-uri-to-path": {
|
"node_modules/file-uri-to-path": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||||
@@ -10388,6 +10400,49 @@
|
|||||||
"integrity": "sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==",
|
"integrity": "sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/posthog-js": {
|
||||||
|
"version": "1.256.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.256.2.tgz",
|
||||||
|
"integrity": "sha512-ypepnUHr33i5a1Uk39mozZXXTENRPC17HCG3WHKK6aRcpNwNs8uEqXaIKICGNM+qre+totKeTgl0WoaUFYmyoQ==",
|
||||||
|
"license": "SEE LICENSE IN LICENSE",
|
||||||
|
"dependencies": {
|
||||||
|
"core-js": "^3.38.1",
|
||||||
|
"fflate": "^0.4.8",
|
||||||
|
"preact": "^10.19.3",
|
||||||
|
"web-vitals": "^4.2.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@rrweb/types": "2.0.0-alpha.17",
|
||||||
|
"rrweb-snapshot": "2.0.0-alpha.17"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@rrweb/types": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"rrweb-snapshot": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/posthog-node": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-6VISkNdxO24ehXiDA4dugyCSIV7lpGVaEu5kn/dlAj+SJ1lgcDru9PQ8p/+GSXsXVxohd1t7kHL2JKc9NoGb0w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/preact": {
|
||||||
|
"version": "10.26.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/preact/-/preact-10.26.9.tgz",
|
||||||
|
"integrity": "sha512-SSjF9vcnF27mJK1XyFMNJzFd5u3pQiATFqoaDy03XuN00u4ziveVVEGt5RKJrDR8MHE/wJo9Nnad56RLzS2RMA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/preact"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/prebuild-install": {
|
"node_modules/prebuild-install": {
|
||||||
"version": "7.1.3",
|
"version": "7.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
|
||||||
@@ -13061,6 +13116,12 @@
|
|||||||
"node": ">= 14"
|
"node": ">= 14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/web-vitals": {
|
||||||
|
"version": "4.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz",
|
||||||
|
"integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
"node_modules/webidl-conversions": {
|
"node_modules/webidl-conversions": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
"postinstall": "nuxt prepare"
|
"postinstall": "nuxt prepare"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aptabase/web": "^0.4.3",
|
|
||||||
"@formkit/tempo": "^0.1.2",
|
"@formkit/tempo": "^0.1.2",
|
||||||
"@google/genai": "^1.5.1",
|
"@google/genai": "^1.5.1",
|
||||||
"@neondatabase/serverless": "^1.0.0",
|
"@neondatabase/serverless": "^1.0.0",
|
||||||
@@ -23,6 +22,8 @@
|
|||||||
"nuxt": "^3.16.2",
|
"nuxt": "^3.16.2",
|
||||||
"nuxt-auth-utils": "0.5.18",
|
"nuxt-auth-utils": "0.5.18",
|
||||||
"openai": "^4.95.1",
|
"openai": "^4.95.1",
|
||||||
|
"posthog-js": "^1.256.2",
|
||||||
|
"posthog-node": "^5.1.1",
|
||||||
"radash": "^12.1.0",
|
"radash": "^12.1.0",
|
||||||
"ts-pattern": "^5.7.1",
|
"ts-pattern": "^5.7.1",
|
||||||
"url": "^0.11.4",
|
"url": "^0.11.4",
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { trackEvent } from "@aptabase/web";
|
|
||||||
|
|
||||||
useHead({ title: "Ghostwriter" });
|
useHead({ title: "Ghostwriter" });
|
||||||
|
|
||||||
const { user } = useUserSession();
|
const { user } = useUserSession();
|
||||||
@@ -39,10 +37,6 @@ onMounted(() => {
|
|||||||
const saveOp = watchPausable(
|
const saveOp = watchPausable(
|
||||||
preferences,
|
preferences,
|
||||||
async (newData) => {
|
async (newData) => {
|
||||||
trackEvent("update_preferences", {
|
|
||||||
enabled: newData.enabled,
|
|
||||||
});
|
|
||||||
|
|
||||||
await $fetch("/api/preferences", {
|
await $fetch("/api/preferences", {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
body: toValue(preferences),
|
body: toValue(preferences),
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
import { init, trackEvent } from "@aptabase/web";
|
|
||||||
|
|
||||||
export default defineNuxtPlugin((nuxtApp) => {
|
|
||||||
const config = useRuntimeConfig();
|
|
||||||
|
|
||||||
nuxtApp.hook("app:mounted", () => {
|
|
||||||
init(config.public.aptabaseAppKey, { isDebug: import.meta.dev });
|
|
||||||
});
|
|
||||||
|
|
||||||
nuxtApp.hook("page:finish", () => {
|
|
||||||
const route = useRoute();
|
|
||||||
|
|
||||||
trackEvent("page_view", {
|
|
||||||
path: route.path,
|
|
||||||
name: String(route.name),
|
|
||||||
redirectedFrom: String(route.redirectedFrom),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
20
plugins/posthog.client.ts
Normal file
20
plugins/posthog.client.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import posthog from "posthog-js";
|
||||||
|
|
||||||
|
export default defineNuxtPlugin(() => {
|
||||||
|
const runtimeConfig = useRuntimeConfig();
|
||||||
|
|
||||||
|
const posthogClient = posthog.init(runtimeConfig.public.posthogPublicKey, {
|
||||||
|
api_host: runtimeConfig.public.posthogHost,
|
||||||
|
//@ts-expect-error typing is more explicit than what it should
|
||||||
|
defaults: runtimeConfig.public.posthogDefaults,
|
||||||
|
loaded: (posthog) => {
|
||||||
|
if (import.meta.env.MODE === "development") posthog.debug();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
provide: {
|
||||||
|
posthog: () => posthogClient,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
|
import { get } from "radash";
|
||||||
import { createActivityContent } from "~~/server/utils/create-content";
|
import { createActivityContent } from "~~/server/utils/create-content";
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
await validateHookdeck(event);
|
await validateHookdeck(event);
|
||||||
|
|
||||||
|
const posthog = event.context.posthog;
|
||||||
|
|
||||||
const body = await readBody(event);
|
const body = await readBody(event);
|
||||||
const db = useDrizzle();
|
const db = useDrizzle();
|
||||||
|
|
||||||
@@ -40,11 +43,33 @@ export default defineEventHandler(async (event) => {
|
|||||||
|
|
||||||
await strava!(`activities/${body.object_id}`, {
|
await strava!(`activities/${body.object_id}`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
body: stravaRequestBody,
|
body: {
|
||||||
|
name: stravaRequestBody.name,
|
||||||
|
description: stravaRequestBody.description,
|
||||||
|
},
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: 500,
|
statusCode: 500,
|
||||||
message: `Strava API: ${error.message}`,
|
message: `Strava API: ${error.message}`,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
posthog.identify({
|
||||||
|
distinctId: String(user.id),
|
||||||
|
properties: {
|
||||||
|
name: user.name,
|
||||||
|
country: user.country,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
posthog.capture({
|
||||||
|
distinctId: String(user.id),
|
||||||
|
event: "content generated",
|
||||||
|
properties: {
|
||||||
|
activity: currentActivity.id,
|
||||||
|
activityType: get(currentActivity, "sport_type", "unknown"),
|
||||||
|
highlight: stravaRequestBody.meta.highlight,
|
||||||
|
tone: stravaRequestBody.meta.tone,
|
||||||
|
},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { eq } from "drizzle-orm";
|
|||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
await validateHookdeck(event);
|
await validateHookdeck(event);
|
||||||
|
|
||||||
|
const posthog = event.context.posthog;
|
||||||
|
|
||||||
const body = await readBody(event);
|
const body = await readBody(event);
|
||||||
const db = useDrizzle();
|
const db = useDrizzle();
|
||||||
|
|
||||||
@@ -11,9 +13,29 @@ export default defineEventHandler(async (event) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const user = await db.query.users.findFirst({
|
||||||
|
where: (f, o) => o.eq(f.id, get(body, "object_id")),
|
||||||
|
with: {
|
||||||
|
preferences: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
posthog.identify({
|
||||||
|
distinctId: String(user!.id),
|
||||||
|
properties: {
|
||||||
|
name: user!.name,
|
||||||
|
country: user!.country,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
await db
|
await db
|
||||||
.delete(tables.users)
|
.delete(tables.users)
|
||||||
.where(eq(tables.users.id, get(body, "object_id")));
|
.where(eq(tables.users.id, get(body, "object_id")));
|
||||||
|
|
||||||
|
posthog.capture({
|
||||||
|
distinctId: get(body, "object_id"),
|
||||||
|
event: "user deleted",
|
||||||
|
});
|
||||||
|
|
||||||
sendNoContent(event);
|
sendNoContent(event);
|
||||||
});
|
});
|
||||||
|
|||||||
28
server/plugins/posthog.ts
Normal file
28
server/plugins/posthog.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { PostHog } from "posthog-node";
|
||||||
|
|
||||||
|
export default defineNitroPlugin((nitroApp) => {
|
||||||
|
const runtimeConfig = useRuntimeConfig();
|
||||||
|
|
||||||
|
const posthog = new PostHog(runtimeConfig.public.posthogPublicKey, {
|
||||||
|
host: runtimeConfig.public.posthogHost,
|
||||||
|
defaults: runtimeConfig.public.posthogDefaults,
|
||||||
|
});
|
||||||
|
|
||||||
|
nitroApp.hooks.hook("request", (event) => {
|
||||||
|
event.context.posthog = posthog;
|
||||||
|
});
|
||||||
|
|
||||||
|
nitroApp.hooks.hook("beforeResponse", async () => {
|
||||||
|
await posthog.shutdown();
|
||||||
|
});
|
||||||
|
|
||||||
|
nitroApp.hooks.hook("close", async () => {
|
||||||
|
await posthog.shutdown();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
declare module "h3" {
|
||||||
|
interface H3EventContext {
|
||||||
|
posthog: PostHog;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,6 +24,8 @@ export default defineOAuthStravaEventHandler({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const posthog = event.context.posthog;
|
||||||
|
|
||||||
const userPayload = {
|
const userPayload = {
|
||||||
id: auth.user.id,
|
id: auth.user.id,
|
||||||
name: `${auth.user.firstname} ${auth.user.lastname}`,
|
name: `${auth.user.firstname} ${auth.user.lastname}`,
|
||||||
@@ -80,6 +82,19 @@ export default defineOAuthStravaEventHandler({
|
|||||||
user: userPayload,
|
user: userPayload,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
posthog.identify({
|
||||||
|
distinctId: String(user!.id),
|
||||||
|
properties: {
|
||||||
|
name: user!.name,
|
||||||
|
country: user!.country,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
posthog.capture({
|
||||||
|
distinctId: String(user!.id),
|
||||||
|
event: "user logged in",
|
||||||
|
});
|
||||||
|
|
||||||
sendRedirect(event, "/");
|
sendRedirect(event, "/");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { chain, draw, get, isEmpty, omit, pick, tryit } from "radash";
|
import { chain, draw, get, isEmpty, omit, tryit } from "radash";
|
||||||
import { safeDestr } from "destr";
|
import { safeDestr } from "destr";
|
||||||
import { match } from "ts-pattern";
|
import { match } from "ts-pattern";
|
||||||
import { User } from "./drizzle";
|
import { User } from "./drizzle";
|
||||||
@@ -191,6 +191,10 @@ export const createActivityContent = async ({
|
|||||||
const stravaRequestBody = {
|
const stravaRequestBody = {
|
||||||
name: responseObject!.title,
|
name: responseObject!.title,
|
||||||
description: responseObject!.description,
|
description: responseObject!.description,
|
||||||
|
meta: {
|
||||||
|
highlight,
|
||||||
|
tone,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return [aiError || parseError, stravaRequestBody] as const;
|
return [aiError || parseError, stravaRequestBody] as const;
|
||||||
|
|||||||
20
server/utils/posthog-client.ts
Normal file
20
server/utils/posthog-client.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { PostHog } from "posthog-node";
|
||||||
|
|
||||||
|
let client: PostHog;
|
||||||
|
|
||||||
|
export const usePosthog = () => {
|
||||||
|
const runtimeConfig = useRuntimeConfig();
|
||||||
|
|
||||||
|
client =
|
||||||
|
client ??
|
||||||
|
new PostHog(runtimeConfig.public.posthogPublicKey, {
|
||||||
|
host: runtimeConfig.public.posthogHost,
|
||||||
|
defaults: runtimeConfig.public.posthogDefaults,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (process.dev) {
|
||||||
|
client.debug();
|
||||||
|
}
|
||||||
|
|
||||||
|
return client;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user