|
import OAuth from "oauth"; |
|
import "dotenv/config"; |
|
import { setTimeout } from "timers/promises"; |
|
import Koa from "koa"; |
|
|
|
const { API_KEY, API_SECRET, BEARER_TOKEN, ACCESS_TOKEN, ACCESS_TOKEN_SECRET } = process.env; |
|
|
|
function getAuthHeader(oauth: OAuth.OAuth, url: string) { |
|
return oauth.authHeader(url, ACCESS_TOKEN as string, ACCESS_TOKEN_SECRET as string, "post"); |
|
} |
|
|
|
const client = new OAuth.OAuth( |
|
"https://api.twitter.com/oauth/request_token", |
|
"https://api.twitter.com/oauth/access_token", |
|
API_KEY as string, |
|
API_SECRET as string, |
|
"1.0A", |
|
null, |
|
"HMAC-SHA1" |
|
); |
|
|
|
const BOT_ID = "1612094318906417152"; |
|
|
|
interface TweetMentions { |
|
data: Array<{ id: string; text: string }>; |
|
meta: { |
|
result_count: number; |
|
newest_id: string; |
|
oldest_id: string; |
|
}; |
|
} |
|
|
|
interface TweetLookups { |
|
data: Array<{ |
|
id: string; |
|
conversation_id: "string"; |
|
text: string; |
|
created_at: string; |
|
attachments?: { media_keys: string[] }; |
|
}>; |
|
includes: { media: Array<{ media_key: string; url: string }> }; |
|
} |
|
|
|
async function ff(url: string) { |
|
const resp = await fetch(`https://api.twitter.com/2/${url}`, { |
|
headers: { Authorization: `Bearer ${BEARER_TOKEN}` }, |
|
}); |
|
|
|
if (resp.status !== 200) { |
|
throw new Error("invalid status: " + resp.status + "- " + (await resp.text())); |
|
} |
|
|
|
return await resp.json(); |
|
} |
|
|
|
let lastCreatedAt = ""; |
|
let lastMentionedTweet = ""; |
|
|
|
async function lookupTweets() { |
|
const data: TweetMentions = await ff( |
|
`users/${BOT_ID}/mentions?${ |
|
lastCreatedAt && `start_time=${new Date(new Date(lastCreatedAt).getTime() + 1).toJSON()}` |
|
}` |
|
); |
|
|
|
if (!data.data || !data.data.length) { |
|
return; |
|
} |
|
|
|
let lookups: TweetLookups = await ff( |
|
`tweets?ids=${data.data |
|
.map((t) => t.id) |
|
.join( |
|
"," |
|
)}&tweet.fields=created_at&expansions=attachments.media_keys&media.fields=duration_ms,height,media_key,preview_image_url,public_metrics,type,url,width,alt_text` |
|
); |
|
|
|
if (!lastCreatedAt) { |
|
console.log("added mention", lookups.data[0].created_at, lookups.data[0].id); |
|
lastCreatedAt = lookups.data[0].created_at; |
|
lastMentionedTweet = lookups.data[0].id; |
|
return lookupTweets(); |
|
} |
|
|
|
const tweets = lookups.data.filter( |
|
(tweet) => tweet.attachments?.media_keys.length === 1 && tweet.id > lastMentionedTweet |
|
); |
|
if (tweets.length) { |
|
lastCreatedAt = lookups.data[0].created_at; |
|
lastMentionedTweet = lookups.data[0].id; |
|
} |
|
console.log(lastCreatedAt); |
|
|
|
for (const tweet of tweets) { |
|
const imageUrl = lookups.includes.media.find((media) => media.media_key === tweet?.attachments!.media_keys[0]) |
|
?.url!; |
|
|
|
console.log("imageUrl", imageUrl); |
|
|
|
const imageResp = await fetch(imageUrl); |
|
const contentType = imageResp.headers.get("Content-Type"); |
|
const image = await imageResp.arrayBuffer(); |
|
|
|
console.log(contentType, image); |
|
|
|
const altText = await fetch("https://olivierdehaene-git-large-coco.hf.space/run/predict", { |
|
method: "POST", |
|
headers: { "Content-Type": "application/json" }, |
|
body: JSON.stringify({ |
|
data: [`data:${contentType};base64,${Buffer.from(image).toString("base64")}`], |
|
}), |
|
}) |
|
.then((r) => r.json()) |
|
.then((r) => r.data); |
|
|
|
console.log(altText[0]); |
|
|
|
const header = getAuthHeader(client, "https://api.twitter.com/2/tweets"); |
|
|
|
const r = await fetch("https://api.twitter.com/2/tweets", { |
|
headers: { |
|
Authorization: header, |
|
"user-agent": "v3CreateTweetJS", |
|
"content-type": "application/json", |
|
accept: "application/json", |
|
}, |
|
body: JSON.stringify({ |
|
text: altText[0], |
|
reply: { in_reply_to_tweet_id: tweet!.id, conversation_id: tweet!.conversation_id }, |
|
}), |
|
method: "post", |
|
}); |
|
|
|
try { |
|
console.log("end", await r.json()); |
|
} catch {} |
|
} |
|
} |
|
|
|
process.on("unhandledRejection", async (err) => { |
|
console.error("unhandled rejection", err); |
|
}); |
|
|
|
process.on("uncaughtException", console.error); |
|
|
|
async function run() { |
|
while (1) { |
|
console.log("looking up"); |
|
try { |
|
await lookupTweets(); |
|
} catch (err) { |
|
console.error(err); |
|
} |
|
await setTimeout(5_000); |
|
} |
|
} |
|
|
|
run(); |
|
|
|
const SPACE_ID = process.env.SPACE_ID || "huggingface-projects/twitter-alt-image-bot"; |
|
|
|
|
|
|
|
|
|
|
|
|
|
const app = new Koa(); |
|
|
|
app.use((ctx) => { |
|
ctx.body = "Check out the README!"; |
|
}); |
|
|
|
app.listen(7860); |
|
|