Implement API to verify tweet data

This commit is contained in:
IshaVenikar 2025-02-05 17:43:25 +05:30
parent 297a4c7ed8
commit a7f255a33d
8 changed files with 172 additions and 7 deletions

View File

@ -14,3 +14,4 @@ PINATA_GATEWAY=
# Change to your website URL
# For development: set to http://localhost:3000
SITE_URL=https://memes.markto.market
ACCOUNT_HANDLE=mark_2_market1

View File

@ -0,0 +1,47 @@
import { NextRequest, NextResponse } from 'next/server'
import { verifySignatureInTweet } from '../../../utils/verifyTweet';
export async function POST(req: NextRequest): Promise<NextResponse> {
try {
const { tweetUrl } = await req.json();
const url = `https://publish.twitter.com/oembed?url=${tweetUrl}&maxwidth=600`;
const response = await fetch(url);
const data = await response.json();
const { handle, memeUrl, txHash } = extractData(data.html);
if (!handle || !memeUrl || !txHash) {
return NextResponse.json(
{ error: 'Verification failed' },
{ status: 500 }
)
}
const isVerified = await verifySignatureInTweet(txHash);
const isHandleCorrect = handle === process.env.ACCOUNT_HANDLE;
return NextResponse.json({isVerified: isVerified && isHandleCorrect})
} catch (error) {
console.error('Error while verifying tweet:', error)
return NextResponse.json(
{ error: 'Failed to verify tweet' },
{ status: 500 }
)
}
}
const extractData = (tweet: string | object) => {
const tweetText = typeof tweet === 'string' ? tweet : JSON.stringify(tweet);
const decodedTweet = tweetText.replace(/&#39;/g, "'").replace(/&quot;/g, '"');
const urlMatch = decodedTweet.match(/<a href="(https:\/\/t.co\/[^"]+)">/);
const txHashMatch = decodedTweet.match(/TX Hash: '([^']+)'/);
const handleMatch = decodedTweet.match(/@([A-Za-z0-9_]+)/);
return {
memeUrl: urlMatch ? urlMatch[1] : null,
txHash: txHashMatch ? txHashMatch[1].trim() : null,
handle: handleMatch ? handleMatch[1] : null,
};
};

View File

@ -4,11 +4,13 @@ import React, { useState } from 'react'
import BN from 'bn.js';
import Big from 'big.js';
import TweetUrlForm from './TweetForm';
interface AIServiceCardProps {
title: string
description: string
isWalletConnected: boolean
onGenerate: (prompt: string) => Promise<{ imageUrl?: string, error?: string }>
onGenerate: (prompt: string) => Promise<{ imageUrl?: string, transactionSignature?: string, error?: string }>
priceMTM: BN
}
@ -16,6 +18,7 @@ interface GenerationState {
loading: boolean
processing: boolean
imageUrl: string | null
transactionSignature: string | null
error: string | null
}
@ -38,6 +41,7 @@ const AIServiceCard: React.FC<AIServiceCardProps> = ({
loading: false,
processing: false,
imageUrl: null,
transactionSignature: null,
error: null,
})
@ -67,11 +71,12 @@ const AIServiceCard: React.FC<AIServiceCardProps> = ({
return
}
if (result.imageUrl) {
if (result.imageUrl && result.transactionSignature) {
setGenerationState({
loading: false,
processing: false,
imageUrl: result.imageUrl,
transactionSignature: result.transactionSignature,
error: null,
})
} else {
@ -86,7 +91,7 @@ const AIServiceCard: React.FC<AIServiceCardProps> = ({
}
}
const generateTwitterShareUrl = (imageUrl: string): string => {
const generateTwitterShareUrl = (imageUrl: string, transactionSignature: string): string => {
const baseUrl = window.location.href;
const ipfsImageUrl = imageUrl.split("/ipfs/")[1];
const memeUrl = `${baseUrl}/memes/${ipfsImageUrl}`;
@ -94,6 +99,17 @@ const AIServiceCard: React.FC<AIServiceCardProps> = ({
return `https://twitter.com/intent/tweet?text=Check%20out%20this%20meme%20I%20generated!&url=${encodeURIComponent(memeUrl)}`;
};
// const generateTwitterShareUrl = (imageUrl: string, transactionSignature: string): string => {
// const baseUrl = window.location.href;
// const ipfsImageUrl = imageUrl.split("/ipfs/")[1];
// const memeUrl = `${baseUrl}/memes/${ipfsImageUrl}`;
// // Ensure the entire tweet text is properly URL-encoded
// const tweetText = `Check out this meme that I generated! TX Hash: ${transactionSignature} @mark_2_market1`;
// return `https://twitter.com/intent/tweet?text=${encodeURIComponent(tweetText)}&url=${encodeURIComponent(memeUrl)}`;
// };
return (
<div className="w-full bg-gray-800/50 backdrop-blur-lg rounded-2xl shadow-xl border border-gray-700/50 mb-8">
<div className="p-6">
@ -137,7 +153,7 @@ const AIServiceCard: React.FC<AIServiceCardProps> = ({
</div>
)}
{generationState.imageUrl && (
{generationState.imageUrl && generationState.transactionSignature && (
<div className="mt-4">
<img
src={generationState.imageUrl}
@ -146,7 +162,7 @@ const AIServiceCard: React.FC<AIServiceCardProps> = ({
/>
<div className="mt-4 text-center">
<a
href={generateTwitterShareUrl(generationState.imageUrl)}
href={generateTwitterShareUrl(generationState.imageUrl, generationState.transactionSignature)}
target="_blank"
rel="noopener noreferrer"
className="inline-block w-full bg-blue-500 hover:bg-blue-600 text-white font-semibold py-4 px-6 rounded-xl transition-all duration-200 shadow-lg hover:shadow-blue-500/25"

View File

@ -0,0 +1,61 @@
'use client'
import React, { useState } from 'react'
const TweetUrlForm: React.FC = () => {
const [inputText, setInputText] = useState<string>('')
const handleGenerate = async (): Promise<void> => {
try {
const response = await fetch('/api/tweet', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ tweetUrl: inputText }),
})
if (!response.ok) {
throw new Error(`Failed to verify tweet: ${response.statusText}`);
}
} catch (error) {
console.error('Failed to fetch price:', error);
}
}
return (
<div className="w-full bg-gray-800/50 backdrop-blur-lg rounded-2xl shadow-xl border border-gray-700/50 mb-8">
<div className="p-6">
<div className="mb-4">
</div>
<div className="space-y-4">
<textarea
value={inputText}
onChange={(e) => setInputText(e.target.value)}
placeholder="Enter the tweet URL here..."
className="w-full bg-gray-900/50 text-gray-100 border border-gray-700 rounded-xl p-4
placeholder-gray-500 focus:border-green-500 focus:ring-2 focus:ring-green-500/20
focus:outline-none min-h-[120px] transition-all duration-200
disabled:opacity-50 disabled:cursor-not-allowed"
rows={4}
/>
<button
onClick={handleGenerate}
className="w-full bg-gradient-to-r from-green-500 to-emerald-500 hover:from-green-600
hover:to-emerald-600 text-white font-semibold py-4 px-6 rounded-xl
transition-all duration-200 shadow-lg hover:shadow-green-500/25
disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:shadow-none"
>
Verify
</button>
</div>
</div>
</div>
)
}
export default TweetUrlForm

View File

@ -1,13 +1,14 @@
import { DataSource } from 'typeorm';
import { Payment } from './entity/Payment';
import { Tweet } from './entity/Tweets';
export const AppDataSource = new DataSource({
type: 'sqlite',
database: './database.sqlite',
synchronize: true,
logging: false,
entities: [Payment],
entities: [Payment, Tweet],
migrations: [],
subscribers: [],
});

13
src/entity/Tweets.ts Normal file
View File

@ -0,0 +1,13 @@
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity()
export class Tweet {
@PrimaryGeneratedColumn()
id!: number;
@Column({ unique: true })
url!: string;
@Column({ unique: true })
transactionSignature!: string;
}

View File

@ -1,6 +1,7 @@
export interface FluxGenerationResult {
imageUrl?: string
error?: string
transactionSignature?: string
}
export interface FluxModelConfig {
@ -58,7 +59,7 @@ export async function generateWithFlux(
console.log('Raw Flux response:', data)
if (data.imageUrl) {
return { imageUrl: data.imageUrl }
return { imageUrl: data.imageUrl, transactionSignature }
} else {
console.error('Unexpected response structure:', data)
throw new Error('Invalid response format from Flux API')

25
src/utils/verifyTweet.ts Normal file
View File

@ -0,0 +1,25 @@
import assert from 'assert';
import { AppDataSource } from '../data-source';
import { Payment } from '../entity/Payment';
import { Tweet } from '../entity/Tweets';
assert(process.env.NEXT_PUBLIC_SOLANA_RPC_URL, 'SOLANA_RPC_URL is required');
export async function verifySignatureInTweet(transactionSignature: string): Promise<boolean> {
const paymentRepository = AppDataSource.getRepository(Payment);
const payment = await paymentRepository.findOneBy({ transactionSignature });
const tweetRepository = AppDataSource.getRepository(Tweet);
const tweet = await tweetRepository.findOneBy({ transactionSignature });
if (!payment) {
return false;
}
if (tweet) {
return false;
}
return true;
}