A simple tool which lets you scrape twitter accounts and crosspost them to bluesky accounts! Comes with a CLI and a webapp for managing profiles! Works with images/videos/link embeds/threads.
13
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add verbose PM2 and browser debug logging

jack 5b5c1ff3 05843d88

+164 -20
+16 -7
install.sh
··· 299 299 300 300 local stopped=0 301 301 302 + echo "[pm2] Inspecting existing PM2 processes..." 303 + 302 304 if pm2 describe "$APP_NAME" >/dev/null 2>&1; then 303 - pm2 delete "$APP_NAME" >/dev/null 2>&1 || true 305 + echo "[pm2] Deleting existing process: $APP_NAME" 306 + pm2 delete "$APP_NAME" || true 304 307 stopped=1 305 308 fi 306 309 307 310 if pm2 describe "$LEGACY_APP_NAME" >/dev/null 2>&1; then 308 - pm2 delete "$LEGACY_APP_NAME" >/dev/null 2>&1 || true 311 + echo "[pm2] Deleting legacy process: $LEGACY_APP_NAME" 312 + pm2 delete "$LEGACY_APP_NAME" || true 309 313 stopped=1 310 314 fi 311 315 312 316 if [[ "$stopped" -eq 1 ]]; then 313 - pm2 save >/dev/null 2>&1 || true 317 + echo "[pm2] Saving PM2 process list" 318 + pm2 save || true 314 319 return 0 315 320 fi 316 321 ··· 340 345 echo "Starting with PM2" 341 346 342 347 if pm2 describe "$LEGACY_APP_NAME" >/dev/null 2>&1; then 343 - pm2 delete "$LEGACY_APP_NAME" >/dev/null 2>&1 || true 348 + echo "[pm2] Removing legacy process before start: $LEGACY_APP_NAME" 349 + pm2 delete "$LEGACY_APP_NAME" || true 344 350 fi 345 351 346 352 if pm2 describe "$APP_NAME" >/dev/null 2>&1; then 347 - pm2 restart "$APP_NAME" --update-env >/dev/null 2>&1 353 + echo "[pm2] Restarting existing process with updated env: $APP_NAME" 354 + pm2 restart "$APP_NAME" --update-env 348 355 else 349 - pm2 start dist/index.js --name "$APP_NAME" --cwd "$SCRIPT_DIR" --update-env >/dev/null 2>&1 356 + echo "[pm2] Starting new process: $APP_NAME (cwd=$SCRIPT_DIR, script=dist/index.js)" 357 + pm2 start dist/index.js --name "$APP_NAME" --cwd "$SCRIPT_DIR" --update-env 350 358 fi 351 359 352 - pm2 save >/dev/null 2>&1 || true 360 + echo "[pm2] Saving PM2 process list" 361 + pm2 save || true 353 362 } 354 363 355 364 start_background() {
+21 -13
update.sh
··· 382 382 383 383 if [[ "$has_app" -eq 1 && "$has_legacy" -eq 1 ]]; then 384 384 echo "ℹ️ Found both PM2 processes ($APP_NAME and $LEGACY_APP_NAME). Consolidating to $APP_NAME..." 385 - pm2 restart "$APP_NAME" --update-env >/dev/null 2>&1 || { 386 - pm2 delete "$APP_NAME" >/dev/null 2>&1 || true 387 - pm2 start dist/index.js --name "$APP_NAME" --cwd "$SCRIPT_DIR" --update-env >/dev/null 2>&1 385 + echo "[pm2] Restarting $APP_NAME with updated environment" 386 + pm2 restart "$APP_NAME" --update-env || { 387 + echo "[pm2] Restart failed. Recreating $APP_NAME" 388 + pm2 delete "$APP_NAME" || true 389 + pm2 start dist/index.js --name "$APP_NAME" --cwd "$SCRIPT_DIR" --update-env 388 390 } 389 - pm2 delete "$LEGACY_APP_NAME" >/dev/null 2>&1 || true 390 - pm2 save >/dev/null 2>&1 || true 391 + echo "[pm2] Removing duplicate legacy process $LEGACY_APP_NAME" 392 + pm2 delete "$LEGACY_APP_NAME" || true 393 + echo "[pm2] Saving PM2 process list" 394 + pm2 save || true 391 395 echo "✅ Restarted PM2 process: $APP_NAME" 392 396 return 0 393 397 fi 394 398 395 399 if [[ "$has_app" -eq 1 ]]; then 396 - pm2 restart "$APP_NAME" --update-env >/dev/null 2>&1 || { 400 + echo "[pm2] Restarting $APP_NAME with updated environment" 401 + pm2 restart "$APP_NAME" --update-env || { 397 402 echo "⚠️ PM2 restart failed for $APP_NAME. Recreating process..." 398 - pm2 delete "$APP_NAME" >/dev/null 2>&1 || true 399 - pm2 start dist/index.js --name "$APP_NAME" --cwd "$SCRIPT_DIR" --update-env >/dev/null 2>&1 403 + pm2 delete "$APP_NAME" || true 404 + pm2 start dist/index.js --name "$APP_NAME" --cwd "$SCRIPT_DIR" --update-env 400 405 } 401 - pm2 save >/dev/null 2>&1 || true 406 + echo "[pm2] Saving PM2 process list" 407 + pm2 save || true 402 408 echo "✅ Restarted PM2 process: $APP_NAME" 403 409 return 0 404 410 fi 405 411 406 412 if [[ "$has_legacy" -eq 1 ]]; then 407 - pm2 restart "$LEGACY_APP_NAME" --update-env >/dev/null 2>&1 || { 413 + echo "[pm2] Restarting legacy process $LEGACY_APP_NAME with updated environment" 414 + pm2 restart "$LEGACY_APP_NAME" --update-env || { 408 415 echo "⚠️ PM2 restart failed for $LEGACY_APP_NAME. Recreating it..." 409 - pm2 delete "$LEGACY_APP_NAME" >/dev/null 2>&1 || true 410 - pm2 start dist/index.js --name "$LEGACY_APP_NAME" --cwd "$SCRIPT_DIR" --update-env >/dev/null 2>&1 416 + pm2 delete "$LEGACY_APP_NAME" || true 417 + pm2 start dist/index.js --name "$LEGACY_APP_NAME" --cwd "$SCRIPT_DIR" --update-env 411 418 } 412 - pm2 save >/dev/null 2>&1 || true 419 + echo "[pm2] Saving PM2 process list" 420 + pm2 save || true 413 421 echo "✅ Restarted PM2 process: $LEGACY_APP_NAME" 414 422 return 0 415 423 fi
+42
web/src/App.tsx
··· 768 768 const hasCurrentEmail = Boolean(me?.email && me.email.trim().length > 0); 769 769 const authHeaders = useMemo(() => (token ? { Authorization: `Bearer ${token}` } : undefined), [token]); 770 770 771 + useEffect(() => { 772 + const requestInterceptor = axios.interceptors.request.use((config) => { 773 + console.debug('[tweets-2-bsky:web] api-request', { 774 + method: config.method?.toUpperCase(), 775 + url: config.url, 776 + params: config.params, 777 + hasData: config.data !== undefined, 778 + timestamp: new Date().toISOString(), 779 + }); 780 + return config; 781 + }); 782 + 783 + const responseInterceptor = axios.interceptors.response.use( 784 + (response) => { 785 + console.debug('[tweets-2-bsky:web] api-response', { 786 + method: response.config.method?.toUpperCase(), 787 + url: response.config.url, 788 + status: response.status, 789 + timestamp: new Date().toISOString(), 790 + }); 791 + return response; 792 + }, 793 + (error) => { 794 + if (axios.isAxiosError(error)) { 795 + console.debug('[tweets-2-bsky:web] api-error', { 796 + method: error.config?.method?.toUpperCase(), 797 + url: error.config?.url, 798 + status: error.response?.status, 799 + message: error.message, 800 + timestamp: new Date().toISOString(), 801 + }); 802 + } 803 + return Promise.reject(error); 804 + }, 805 + ); 806 + 807 + return () => { 808 + axios.interceptors.request.eject(requestInterceptor); 809 + axios.interceptors.response.eject(responseInterceptor); 810 + }; 811 + }, []); 812 + 771 813 const showNotice = useCallback((tone: Notice['tone'], message: string) => { 772 814 setNotice({ tone, message }); 773 815 if (noticeTimerRef.current) {
+82
web/src/lib/debug-logger.ts
··· 1 + const DEBUG_PREFIX = '[tweets-2-bsky:web]'; 2 + 3 + function describeTarget(target: EventTarget | null): string { 4 + if (!(target instanceof Element)) { 5 + return 'unknown-target'; 6 + } 7 + 8 + const tagName = target.tagName.toLowerCase(); 9 + const id = target.id ? `#${target.id}` : ''; 10 + const className = 11 + typeof target.className === 'string' && target.className.trim().length > 0 12 + ? `.${target.className 13 + .trim() 14 + .split(/\s+/) 15 + .filter(Boolean) 16 + .slice(0, 3) 17 + .join('.')}` 18 + : ''; 19 + 20 + return `${tagName}${id}${className}`; 21 + } 22 + 23 + function eventToPayload(event: Event): Record<string, unknown> { 24 + const target = event.target; 25 + const payload: Record<string, unknown> = { 26 + type: event.type, 27 + target: describeTarget(target), 28 + timestamp: new Date().toISOString(), 29 + path: window.location.pathname, 30 + }; 31 + 32 + if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement) { 33 + payload.name = target.name || undefined; 34 + payload.value = target.value; 35 + } 36 + 37 + if (event instanceof MouseEvent) { 38 + payload.button = event.button; 39 + payload.clientX = event.clientX; 40 + payload.clientY = event.clientY; 41 + } 42 + 43 + return payload; 44 + } 45 + 46 + export function setupBrowserDebugLogging(): void { 47 + const events = ['click', 'change', 'input', 'submit']; 48 + 49 + events.forEach((eventName) => { 50 + document.addEventListener( 51 + eventName, 52 + (event) => { 53 + console.debug(`${DEBUG_PREFIX} ui-event`, eventToPayload(event)); 54 + }, 55 + true, 56 + ); 57 + }); 58 + 59 + window.addEventListener('focus', () => { 60 + console.debug(`${DEBUG_PREFIX} window-focus`, { timestamp: new Date().toISOString() }); 61 + }); 62 + 63 + window.addEventListener('blur', () => { 64 + console.debug(`${DEBUG_PREFIX} window-blur`, { timestamp: new Date().toISOString() }); 65 + }); 66 + 67 + document.addEventListener('visibilitychange', () => { 68 + console.debug(`${DEBUG_PREFIX} visibility`, { 69 + state: document.visibilityState, 70 + timestamp: new Date().toISOString(), 71 + }); 72 + }); 73 + 74 + window.addEventListener('popstate', () => { 75 + console.debug(`${DEBUG_PREFIX} popstate`, { 76 + path: window.location.pathname, 77 + timestamp: new Date().toISOString(), 78 + }); 79 + }); 80 + 81 + console.info(`${DEBUG_PREFIX} verbose browser event logging enabled`); 82 + }
+3
web/src/main.tsx
··· 1 1 import { createRoot } from 'react-dom/client'; 2 2 import App from './App'; 3 3 import './index.css'; 4 + import { setupBrowserDebugLogging } from './lib/debug-logger'; 5 + 6 + setupBrowserDebugLogging(); 4 7 5 8 createRoot(document.getElementById('root')!).render(<App />);