upload files

This commit is contained in:
2026-03-22 08:17:25 +00:00
parent 718ad3b483
commit 5ead7eaeda
5 changed files with 299 additions and 0 deletions

196
.gitignore vendored Normal file
View File

@@ -0,0 +1,196 @@
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
# Environment variables and secrets
.env
.env.local
.env.*.local
cookies.json
# Data and posts tracking
posts.json
# Temporary video processing files
temp/
*.mp4
*.webm
*.mov
*.mpeg
# Python cache
__pycache__/
*.py[cod]
*$py.class
*.so
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Caches
.cache
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# Runtime data
pids
_.pid
_.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# IntelliJ based IDEs
.idea
# Finder (MacOS) folder config
.DS_Store

BIN
bun.lockb Normal file

Binary file not shown.

4
cookies.json Normal file
View File

@@ -0,0 +1,4 @@
{
"ct0": "",
"auth_token": ""
}

10
example.env Normal file
View File

@@ -0,0 +1,10 @@
# Bluesky Credentials
# Use an app password from https://bsky.app/settings/app-passwords
BSKY_USERNAME=example.bsky.social
BSKY_PASSWORD=
# Twitter User to Monitor
TWITTER_USER=exampleuser
# Check interval in milliseconds (60000 = 1 minute)
CHECK_INTERVAL=60000

89
get_tweets.py Normal file
View File

@@ -0,0 +1,89 @@
import sys
import json
import asyncio
from twikit import Client
async def get_user_tweets(username, count=10):
client = Client('en-US')
try:
client.load_cookies('cookies.json')
except:
print(json.dumps({"error": "Not logged in. Run login script first."}))
return
try:
user = await client.get_user_by_screen_name(username)
user_id = user.id
tweets_data = []
tweets = await client.get_user_tweets(user_id, 'Tweets', count=count)
for tweet in tweets:
if tweet.text.startswith('RT @'):
continue
# Get full text - twikit truncates long tweets at 280 chars
# Full text is available on the tweet object under different attributes
full_text = None
# Try note_tweet first (X Premium long posts are stored as "note tweets")
if hasattr(tweet, 'note_tweet') and tweet.note_tweet:
note = tweet.note_tweet
if hasattr(note, 'text'):
full_text = note.text
elif isinstance(note, dict):
full_text = note.get('text') or note.get('note_tweet_results', {}).get('result', {}).get('text')
# Fall back to full_text attribute if available
if not full_text and hasattr(tweet, 'full_text') and tweet.full_text:
full_text = tweet.full_text
# Fall back to regular text
if not full_text:
full_text = tweet.text
# Strip trailing ellipsis if still truncated (shouldn't happen but just in case)
if full_text.endswith(''):
full_text = full_text[:-1]
media = []
if tweet.media:
for m in tweet.media:
media_url = None
media_type = m.type
if hasattr(m, 'media_url'):
media_url = m.media_url
if media_type == 'video' and hasattr(m, 'streams') and m.streams:
best_stream = max(m.streams, key=lambda s: s.bitrate if hasattr(s, 'bitrate') and s.bitrate else 0)
if hasattr(best_stream, 'url'):
media_url = best_stream.url
if media_url:
media.append({
'type': media_type,
'url': media_url
})
tweets_data.append({
'id': tweet.id,
'text': full_text,
'media': media,
'created_at': tweet.created_at
})
print(json.dumps(tweets_data))
except Exception as e:
print(json.dumps({"error": str(e)}))
if __name__ == '__main__':
username = sys.argv[1] if len(sys.argv) > 1 else None
count = int(sys.argv[2]) if len(sys.argv) > 2 else 10
if not username:
print(json.dumps({"error": "Username required"}))
sys.exit(1)
asyncio.run(get_user_tweets(username, count))