-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: sync message from telegram to twitter
- Loading branch information
Showing
9 changed files
with
209 additions
and
170 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
// telegram.js | ||
import * as twitter from './twitter' | ||
import { Env, TelegramUpdate } from './type' | ||
|
||
// Process the Telegram update received | ||
export async function handleTelegramUpdate(update: TelegramUpdate, env: Env) { | ||
const allowedUserIds = env.ALLOW_USER_IDS | ||
const fromUserId = update.message?.from?.id.toString() || '' | ||
const fromUsername = update.message?.from?.username || '' | ||
|
||
// Exit if there is no message text | ||
if (!update.message?.text) return | ||
let replyText = '' | ||
|
||
// Check if the user is allowed to interact with the bot | ||
if (!allowedUserIds.includes(fromUsername) && !allowedUserIds.includes(fromUserId)) { | ||
return | ||
} else if (update.message.text.startsWith('/sync_twitter')) { | ||
replyText = await processSyncTwitterCommand(update, env) | ||
} else { | ||
replyText = `Echo: ${update.message.text}` | ||
} | ||
|
||
// Send a reply message to Telegram | ||
await sendReplyToTelegram(update.message.chat.id, replyText, update.message.message_id, env) | ||
} | ||
|
||
// Process the '/sync_twitter' command | ||
async function processSyncTwitterCommand(update: TelegramUpdate, env: Env): Promise<string> { | ||
if (!update.message?.reply_to_message?.photo?.length) { | ||
return 'No photo found to sync with Twitter.' | ||
} | ||
|
||
const bestPhoto = update.message.reply_to_message.photo[update.message.reply_to_message.photo.length - 1] | ||
const photoUrl = await getTelegramFileUrl(bestPhoto.file_id, env.TELEGRAM_BOT_SECRET) | ||
const mediaData = await fetch(photoUrl).then(res => res.arrayBuffer()) | ||
|
||
try { | ||
const media = await twitter.uploadMediaToTwitter(mediaData, env) | ||
const tweetContent = `${update.message.reply_to_message.caption} #sync_from_telegram` | ||
const tweet = await twitter.postTweet(tweetContent, [media.media_id_string], env) | ||
|
||
return `Your message has been posted to Twitter. Id: ${tweet.data.id}` | ||
} catch (error) { | ||
return `Failed to post tweet: ${error}` | ||
} | ||
} | ||
|
||
|
||
// Fetch the file URL from Telegram using the file_id | ||
async function getTelegramFileUrl(fileId: string, botSecret: string): Promise<string> { | ||
const fileResponse = await fetch(`https://api.telegram.org/bot${botSecret}/getFile?file_id=${fileId}`) | ||
if (!fileResponse.ok) { | ||
throw new Error(`Telegram API getFile responded with status ${fileResponse.status}`) | ||
} | ||
const fileData = await fileResponse.json() as { ok: boolean, result: { file_path: string } } | ||
const filePath = fileData.result.file_path | ||
return `https://api.telegram.org/file/bot${botSecret}/${filePath}` | ||
} | ||
|
||
// Send a message back to Telegram chat | ||
async function sendReplyToTelegram(chatId: number, text: string, messageId: number, env: Env) { | ||
const response = await fetch(`https://api.telegram.org/bot${env.TELEGRAM_BOT_SECRET}/sendMessage`, { | ||
method: 'POST', | ||
headers: { 'Content-Type': 'application/json' }, | ||
body: JSON.stringify({ | ||
chat_id: chatId, | ||
text, | ||
reply_to_message_id: messageId, | ||
}), | ||
}) | ||
|
||
if (!response.ok) { | ||
throw new Error(`Telegram API sendMessage responded with status ${response.status}`) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import OAuth from 'oauth-1.0a' | ||
import { HmacSHA1, enc } from 'crypto-js' | ||
import { Buffer } from 'node:buffer' | ||
import { Env } from "./type" | ||
|
||
export async function uploadMediaToTwitter(mediaData: ArrayBuffer, env: Env): Promise<any> { | ||
const oauth = new OAuth({ | ||
consumer: { key: env.TWITTER_API_KEY, secret: env.TWITTER_API_SECRET }, | ||
signature_method: 'HMAC-SHA1', | ||
hash_function(baseString, key) { | ||
return HmacSHA1(baseString, key).toString(enc.Base64) | ||
}, | ||
}) | ||
|
||
const oauthToken = { | ||
key: env.TWITTER_ACCESS_TOKEN, | ||
secret: env.TWITTER_ACCESS_TOKEN_SECRET, | ||
} | ||
|
||
const requestData = { | ||
url: 'https://upload.twitter.com/1.1/media/upload.json?media_category=tweet_image', | ||
method: 'POST', | ||
data: { | ||
media_data: Buffer.from(mediaData).toString('base64'), | ||
}, | ||
} | ||
|
||
// 初始化媒体上传以获取media_id | ||
const response = await fetch(requestData.url, { | ||
method: 'POST', | ||
headers: { | ||
...oauth.toHeader(oauth.authorize(requestData, oauthToken)), | ||
'content-type': 'application/x-www-form-urlencoded', | ||
}, | ||
body: new URLSearchParams({ media_data: Buffer.from(mediaData).toString('base64') }), | ||
}) | ||
|
||
return await response.json() | ||
} | ||
|
||
export async function postTweet(text: string, mediaList: string[], env: Env): Promise<any> { | ||
const oauth = new OAuth({ | ||
consumer: { key: env.TWITTER_API_KEY, secret: env.TWITTER_API_SECRET }, | ||
signature_method: 'HMAC-SHA1', | ||
hash_function(baseString, key) { | ||
return HmacSHA1(baseString, key).toString(enc.Base64) | ||
}, | ||
}) | ||
|
||
const oauthToken = { | ||
key: env.TWITTER_ACCESS_TOKEN, | ||
secret: env.TWITTER_ACCESS_TOKEN_SECRET, | ||
} | ||
|
||
const requestData = { | ||
url: "https://api.twitter.com/2/tweets", | ||
method: 'POST', | ||
} | ||
|
||
const response = await fetch(requestData.url, { | ||
method: 'POST', | ||
headers: { | ||
...oauth.toHeader(oauth.authorize(requestData, oauthToken)), | ||
'content-type': "application/json", | ||
}, | ||
body: JSON.stringify({ text, media: { media_ids: mediaList } }), | ||
}) | ||
|
||
return await response.json() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
export interface Env { | ||
TELEGRAM_BOT_SECRET: string | ||
TWITTER_API_KEY: string | ||
TWITTER_API_SECRET: string | ||
TWITTER_ACCESS_TOKEN: string | ||
TWITTER_ACCESS_TOKEN_SECRET: string | ||
ALLOW_USER_IDS: string[] | ||
|
||
// Example binding to KV. Learn more at https://developers.cloudflare.com/workers/runtime-apis/kv/ | ||
// MY_KV_NAMESPACE: KVNamespace | ||
// | ||
// Example binding to Durable Object. Learn more at https://developers.cloudflare.com/workers/runtime-apis/durable-objects/ | ||
// MY_DURABLE_OBJECT: DurableObjectNamespace | ||
// | ||
// Example binding to R2. Learn more at https://developers.cloudflare.com/workers/runtime-apis/r2/ | ||
// MY_BUCKET: R2Bucket | ||
// | ||
// Example binding to a Service. Learn more at https://developers.cloudflare.com/workers/runtime-apis/service-bindings/ | ||
// MY_SERVICE: Fetcher | ||
// | ||
// Example binding to a Queue. Learn more at https://developers.cloudflare.com/queues/javascript-apis/ | ||
// MY_QUEUE: Queue | ||
} | ||
|
||
interface TelegramChat { | ||
id: number // Chat ID | ||
// 添加更多的聊天相关字段 | ||
} | ||
|
||
interface TelegramPhoto { | ||
file_id: string // 可用于获取文件内容 | ||
file_unique_id: string // 文件的唯一标识符 | ||
width: number // 图片宽度 | ||
height: number // 图片高度 | ||
file_size?: number // 文件大小(可选) | ||
} | ||
|
||
interface TelegramMessage { | ||
message_id: number // Message ID | ||
chat: TelegramChat // Chat object | ||
text?: string // Received message text, optional | ||
reply_to_message?: TelegramMessage // 添加这个字段来获取回复的消息 | ||
from: { | ||
username: string // 发送者的用户名 | ||
id: string // 发送者的ID | ||
}, | ||
caption?: string | ||
photo?: TelegramPhoto[] // TelegramPhoto需要根据API定义 | ||
// 添加更多的消息相关字段 | ||
} | ||
|
||
export interface TelegramUpdate { | ||
update_id: number // Update ID from Telegram | ||
message?: TelegramMessage // Message object, optional | ||
// 添加更多的更新相关字段 | ||
} |
Oops, something went wrong.