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

Telegram bot #80

Merged
merged 5 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
some important housekeeping, crushed all build errors
  • Loading branch information
Dhravya committed Jun 24, 2024
commit 75585fd97d7abaf68e0c314f41d0cef414a57b1e
2 changes: 1 addition & 1 deletion apps/web/app/(landing)/twitterLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export function HoverBorderGradient({
if (!directions[nextIndex]) {
return directions[0]!;
}
return directions[nextIndex];
return directions[nextIndex]!;
};

const movingMap: Record<Direction, string> = {
Expand Down
114 changes: 0 additions & 114 deletions apps/web/app/api/store/route.ts

This file was deleted.

18 changes: 8 additions & 10 deletions apps/web/app/api/telegram/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { db } from "@/server/db";
import { storedContent, users } from "@/server/db/schema";
import { cipher, decipher } from "@/server/encrypt";
import { cipher } from "@/server/encrypt";
import { eq } from "drizzle-orm";
import { Bot, webhookCallback } from "grammy";
import { User } from "grammy/types";
Expand All @@ -16,16 +16,9 @@ const token = process.env.TELEGRAM_BOT_TOKEN;

const bot = new Bot(token);

const getUserByTelegramId = async (telegramId: string) => {
return await db.query.users
.findFirst({
where: eq(users.telegramId, telegramId),
})
.execute();
};

bot.command("start", async (ctx) => {
const user: User = (await ctx.getAuthor()).user;

const cipherd = cipher(user.id.toString());
await ctx.reply(
`Welcome to Supermemory bot. I am here to help you remember things better. Click here to create and link your accont: http:https://localhost:3000/signin?telegramUser=${cipherd}`,
Expand All @@ -34,9 +27,14 @@ bot.command("start", async (ctx) => {

bot.on("message", async (ctx) => {
const user: User = (await ctx.getAuthor()).user;

const cipherd = cipher(user.id.toString());

const dbUser = await getUserByTelegramId(user.id.toString());
const dbUser = await db.query.users
.findFirst({
where: eq(users.telegramId, user.id.toString()),
})
.execute();

if (!dbUser) {
await ctx.reply(
Expand Down
162 changes: 92 additions & 70 deletions apps/web/app/api/unfirlsite/route.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,65 @@
import { load } from 'cheerio'
import { load } from "cheerio";
import { AwsClient } from "aws4fetch";

import type { NextRequest } from "next/server";
import { ensureAuth } from "../ensureAuth";

export const runtime = "edge";

const r2 = new AwsClient({
accessKeyId: process.env.R2_ACCESS_KEY_ID,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
});
export async function POST(request: NextRequest) {
const r2 = new AwsClient({
accessKeyId: process.env.R2_ACCESS_KEY_ID,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
});

async function unfurl(url: string) {
const response = await fetch(url);
if (response.status >= 400) {
throw new Error(`Error fetching url: ${response.status}`);
}
const contentType = response.headers.get("content-type");
if (!contentType?.includes("text/html")) {
throw new Error(`Content-type not right: ${contentType}`);
}

const content = await response.text();
const $ = load(content);

const og: { [key: string]: string | undefined } = {};
const twitter: { [key: string]: string | undefined } = {};

// @ts-ignore, it just works so why care of type safety if someone has better way go ahead
$("meta[property^=og:]").each(
(_, el) => (og[$(el).attr("property")!] = $(el).attr("content")),
);
// @ts-ignore
$("meta[name^=twitter:]").each(
(_, el) => (twitter[$(el).attr("name")!] = $(el).attr("content")),
);

const title =
og["og:title"] ??
twitter["twitter:title"] ??
$("title").text() ??
undefined;
const description =
og["og:description"] ??
twitter["twitter:description"] ??
$('meta[name="description"]').attr("content") ??
undefined;
const image =
og["og:image:secure_url"] ??
og["og:image"] ??
twitter["twitter:image"] ??
undefined;

return {
title,
description,
image,
};
}

export async function POST(request: NextRequest) {

const d = await ensureAuth(request);
if (!d) {
return new Response("Unauthorized", { status: 401 });
Expand All @@ -32,7 +78,7 @@ export async function POST(request: NextRequest) {
}

const website = new URL(request.url).searchParams.get("website");

if (!website) {
return new Response("Missing website", { status: 400 });
}
Expand All @@ -41,27 +87,33 @@ export async function POST(request: NextRequest) {
const encodeWebsite = `${encodeURIComponent(website)}${salt()}`;

try {
// this returns the og image, description and title of website
// this returns the og image, description and title of website
const response = await unfurl(website);

if (!response.image){
return new Response(JSON.stringify(response))
if (!response.image) {
return new Response(JSON.stringify(response));
}

const imageUrl = await process.env.DEV_IMAGES.get(encodeWebsite)
if (imageUrl){
return new Response(JSON.stringify({
image: imageUrl,
title: response.title,
description: response.description,
}))
if (!process.env.DEV_IMAGES) {
return new Response("Missing DEV_IMAGES namespace.", { status: 500 });
}

const res = await fetch(`${response.image}`)
const imageUrl = await process.env.DEV_IMAGES!.get(encodeWebsite);
if (imageUrl) {
return new Response(
JSON.stringify({
image: imageUrl,
title: response.title,
description: response.description,
}),
);
}

const res = await fetch(`${response.image}`);
const image = await res.blob();

const url = new URL(
`https://${process.env.R2_BUCKET_NAME}.${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`
`https://${process.env.R2_BUCKET_NAME}.${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
);

url.pathname = encodeWebsite;
Expand All @@ -73,62 +125,32 @@ export async function POST(request: NextRequest) {
}),
{
aws: { signQuery: true },
}
},
);
await fetch(signedPuturl.url, {
method: 'PUT',
method: "PUT",
body: image,
});

await process.env.DEV_IMAGES.put(encodeWebsite, `${process.env.R2_PUBLIC_BUCKET_ADDRESS}/${encodeWebsite}`)

return new Response(JSON.stringify({
image: `${process.env.R2_PUBLIC_BUCKET_ADDRESS}/${encodeWebsite}`,
title: response.title,
description: response.description,
}));
await process.env.DEV_IMAGES.put(
encodeWebsite,
`${process.env.R2_PUBLIC_BUCKET_ADDRESS}/${encodeWebsite}`,
);

return new Response(
JSON.stringify({
image: `${process.env.R2_PUBLIC_BUCKET_ADDRESS}/${encodeWebsite}`,
title: response.title,
description: response.description,
}),
);
} catch (error) {
console.log(error)
return new Response(JSON.stringify({
status: 500,
error: error,
}))
}
console.log(error);
return new Response(
JSON.stringify({
status: 500,
error: error,
}),
);
}

export async function unfurl(url: string) {
const response = await fetch(url)
if (response.status >= 400) {
throw new Error(`Error fetching url: ${response.status}`)
}
const contentType = response.headers.get('content-type')
if (!contentType?.includes('text/html')) {
throw new Error(`Content-type not right: ${contentType}`)
}

const content = await response.text()
const $ = load(content)

const og: { [key: string]: string | undefined } = {}
const twitter: { [key: string]: string | undefined } = {}

// @ts-ignore, it just works so why care of type safety if someone has better way go ahead
$('meta[property^=og:]').each((_, el) => (og[$(el).attr('property')!] = $(el).attr('content')))
// @ts-ignore
$('meta[name^=twitter:]').each((_, el) => (twitter[$(el).attr('name')!] = $(el).attr('content')))

const title = og['og:title'] ?? twitter['twitter:title'] ?? $('title').text() ?? undefined
const description =
og['og:description'] ??
twitter['twitter:description'] ??
$('meta[name="description"]').attr('content') ??
undefined
const image = og['og:image:secure_url'] ?? og['og:image'] ?? twitter['twitter:image'] ?? undefined

return {
title,
description,
image,
}
}
1 change: 1 addition & 0 deletions apps/web/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
interface CloudflareEnv {
STORAGE: R2Bucket;
DATABASE: D1Database;
DEV_IMAGES: R2Bucket;
}
3 changes: 3 additions & 0 deletions apps/web/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { setupDevPlatform } from "@cloudflare/next-on-pages/next-dev";
const nextConfig = {
transpilePackages: ["@repo/ui"],
reactStrictMode: false,
env: {
TELEGRAM_BOT_TOKEN: process.env.TELEGRAM_BOT_TOKEN,
},
};
export default MillionLint.next({
rsc: true,
Expand Down
Loading