viz: commit jetstream-firehose logger -- with time-based color, filtering on event/post.type and event/post.commit.type & on message keywords
This commit is contained in:
154
bluesky-firehose-viz.orig.py
Normal file
154
bluesky-firehose-viz.orig.py
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import websockets
|
||||||
|
import curses
|
||||||
|
import random
|
||||||
|
import colorsys
|
||||||
|
import time
|
||||||
|
from collections import deque
|
||||||
|
|
||||||
|
class BlueskyFirehoseVisualizer:
|
||||||
|
def __init__(self, stdscr, websocket_url):
|
||||||
|
self.stdscr = stdscr
|
||||||
|
self.websocket_url = websocket_url
|
||||||
|
|
||||||
|
# Initialize color pairs
|
||||||
|
curses.start_color()
|
||||||
|
curses.use_default_colors()
|
||||||
|
|
||||||
|
# Set up screen
|
||||||
|
curses.curs_set(0)
|
||||||
|
self.stdscr.clear()
|
||||||
|
|
||||||
|
# Track posts with their display details
|
||||||
|
self.posts = {}
|
||||||
|
self.max_posts = 1000 # Limit to prevent memory growth
|
||||||
|
|
||||||
|
# Generate color palette
|
||||||
|
self.color_palette = self._generate_color_palette()
|
||||||
|
|
||||||
|
def _generate_color_palette(self, num_colors=256):
|
||||||
|
"""Generate a diverse color palette."""
|
||||||
|
colors = []
|
||||||
|
for i in range(num_colors):
|
||||||
|
# Use HSV color space to generate visually distinct colors
|
||||||
|
hue = (i / num_colors) % 1.0
|
||||||
|
saturation = 0.7 + (random.random() * 0.3) # 70-100% saturation
|
||||||
|
value = 0.7 + (random.random() * 0.3) # 70-100% brightness
|
||||||
|
|
||||||
|
# Convert HSV to RGB
|
||||||
|
rgb = colorsys.hsv_to_rgb(hue, saturation, value)
|
||||||
|
|
||||||
|
# Scale RGB to curses color range (0-1000)
|
||||||
|
r, g, b = [int(x * 1000) for x in rgb]
|
||||||
|
|
||||||
|
# Initialize color pair
|
||||||
|
try:
|
||||||
|
color_index = len(colors) + 1 # Start from 1
|
||||||
|
curses.init_color(color_index, r, g, b)
|
||||||
|
curses.init_pair(color_index, color_index, -1)
|
||||||
|
colors.append(color_index)
|
||||||
|
except Exception:
|
||||||
|
# If we run out of color pairs, wrap around
|
||||||
|
color_index = (len(colors) % 256) + 1
|
||||||
|
colors.append(color_index)
|
||||||
|
|
||||||
|
return colors
|
||||||
|
|
||||||
|
def _display_post(self, post_id, text):
|
||||||
|
"""Display a post with a unique color, fading out over time."""
|
||||||
|
# Assign a unique color
|
||||||
|
color_index = self.color_palette[hash(post_id) % len(self.color_palette)]
|
||||||
|
|
||||||
|
# Get screen dimensions
|
||||||
|
max_y, max_x = self.stdscr.getmaxyx()
|
||||||
|
|
||||||
|
# Trim text to fit screen width
|
||||||
|
text = text[:max_x-1]
|
||||||
|
|
||||||
|
# Track post details
|
||||||
|
if post_id not in self.posts:
|
||||||
|
# Remove oldest post if we've reached max
|
||||||
|
if len(self.posts) >= self.max_posts:
|
||||||
|
oldest_id = min(self.posts, key=lambda k: self.posts[k]['timestamp'])
|
||||||
|
del self.posts[oldest_id]
|
||||||
|
|
||||||
|
# Find a free vertical position
|
||||||
|
used_y = set(post['y'] for post in self.posts.values())
|
||||||
|
y = next(i for i in range(max_y) if i not in used_y)
|
||||||
|
|
||||||
|
self.posts[post_id] = {
|
||||||
|
'text': text,
|
||||||
|
'color': color_index,
|
||||||
|
'y': y,
|
||||||
|
'timestamp': time.time(),
|
||||||
|
'fade_count': 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Render posts
|
||||||
|
for pid, post_info in list(self.posts.items()):
|
||||||
|
# Calculate fade effect
|
||||||
|
age = time.time() - post_info['timestamp']
|
||||||
|
fade_speed = 0.5 # Adjust for desired fade speed
|
||||||
|
|
||||||
|
if age > fade_speed * post_info['fade_count']:
|
||||||
|
try:
|
||||||
|
# Gradually reduce text intensity
|
||||||
|
intensity = max(0, 1 - (post_info['fade_count'] / 10))
|
||||||
|
color = curses.color_pair(post_info['color'])
|
||||||
|
|
||||||
|
# Render text at constant horizontal position
|
||||||
|
self.stdscr.addstr(
|
||||||
|
post_info['y'],
|
||||||
|
post_info['fade_count'],
|
||||||
|
post_info['text'][:max_x-1],
|
||||||
|
color
|
||||||
|
)
|
||||||
|
|
||||||
|
post_info['fade_count'] += 1
|
||||||
|
except curses.error:
|
||||||
|
# If we can't write (e.g., screen boundaries), remove post
|
||||||
|
del self.posts[pid]
|
||||||
|
|
||||||
|
# Refresh display
|
||||||
|
self.stdscr.refresh()
|
||||||
|
|
||||||
|
async def connect_and_visualize(self):
|
||||||
|
"""Connect to Bluesky firehose and visualize posts."""
|
||||||
|
try:
|
||||||
|
async with websockets.connect(self.websocket_url) as websocket:
|
||||||
|
while True:
|
||||||
|
message = await websocket.recv()
|
||||||
|
post = json.loads(message)
|
||||||
|
|
||||||
|
# Extract meaningful text (adjust based on actual Bluesky JSON structure)
|
||||||
|
post_id = post.get('id', str(random.random()))
|
||||||
|
text = post.get('text', 'Unknown post')
|
||||||
|
|
||||||
|
# Display the post
|
||||||
|
self._display_post(post_id, text)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.stdscr.addstr(0, 0, f"Error: {str(e)}")
|
||||||
|
self.stdscr.refresh()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""Run the visualizer."""
|
||||||
|
asyncio.run(self.connect_and_visualize())
|
||||||
|
|
||||||
|
def main(stdscr):
|
||||||
|
# Replace with actual Bluesky firehose websocket URL
|
||||||
|
BLUESKY_FIREHOSE_WS = "ws://example.com/bluesky-firehose"
|
||||||
|
|
||||||
|
visualizer = BlueskyFirehoseVisualizer(stdscr, BLUESKY_FIREHOSE_WS)
|
||||||
|
visualizer.run()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Wrap main in curses wrapper to handle terminal setup/teardown
|
||||||
|
curses.wrapper(main)
|
||||||
|
|
||||||
|
# Dependencies (install with pip):
|
||||||
|
# websockets
|
||||||
|
#
|
||||||
|
# Note: You'll need to replace the websocket URL with the actual
|
||||||
|
# Bluesky firehose websocket endpoint when available.
|
||||||
168
bluesky-firehose-viz.py
Normal file
168
bluesky-firehose-viz.py
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import websockets
|
||||||
|
import curses
|
||||||
|
import random
|
||||||
|
import colorsys
|
||||||
|
import time
|
||||||
|
from collections import deque
|
||||||
|
import logging
|
||||||
|
FORMAT = '%(asctime)s:%(loglevel)s:%(name)s %(message)s'
|
||||||
|
#'%(asctime)s %(clientip)-15s %(user)-8s %(message)s'
|
||||||
|
logging.basicConfig(level=logging.DEBUG, filename="bviz.log", format=FORMAT)
|
||||||
|
logger = logging.getLogger("bviz")
|
||||||
|
|
||||||
|
class BlueskyFirehoseVisualizer:
|
||||||
|
def __init__(self, stdscr, websocket_url):
|
||||||
|
logger.info("init-start")
|
||||||
|
self.stdscr = stdscr
|
||||||
|
self.websocket_url = websocket_url
|
||||||
|
|
||||||
|
# Initialize color pairs
|
||||||
|
curses.start_color()
|
||||||
|
curses.use_default_colors()
|
||||||
|
|
||||||
|
# Set up screen
|
||||||
|
curses.curs_set(0)
|
||||||
|
#self.stdscr.clear()
|
||||||
|
|
||||||
|
# Track posts with their display details
|
||||||
|
self.posts = {}
|
||||||
|
self.max_posts = 1000 # Limit to prevent memory growth
|
||||||
|
|
||||||
|
# Generate color palette
|
||||||
|
self.color_palette = self._generate_color_palette()
|
||||||
|
logger.info("init-end")
|
||||||
|
|
||||||
|
def _generate_color_palette(self, num_colors=256):
|
||||||
|
"""Generate a diverse color palette."""
|
||||||
|
colors = []
|
||||||
|
for i in range(num_colors):
|
||||||
|
# Use HSV color space to generate visually distinct colors
|
||||||
|
hue = (i / num_colors) % 1.0
|
||||||
|
saturation = 0.7 + (random.random() * 0.3) # 70-100% saturation
|
||||||
|
value = 0.7 + (random.random() * 0.3) # 70-100% brightness
|
||||||
|
|
||||||
|
# Convert HSV to RGB
|
||||||
|
rgb = colorsys.hsv_to_rgb(hue, saturation, value)
|
||||||
|
|
||||||
|
# Scale RGB to curses color range (0-1000)
|
||||||
|
r, g, b = [int(x * 1000) for x in rgb]
|
||||||
|
|
||||||
|
# Initialize color pair
|
||||||
|
try:
|
||||||
|
color_index = len(colors) + 1 # Start from 1
|
||||||
|
curses.init_color(color_index, r, g, b)
|
||||||
|
curses.init_pair(color_index, color_index, -1)
|
||||||
|
colors.append(color_index)
|
||||||
|
except Exception:
|
||||||
|
# If we run out of color pairs, wrap around
|
||||||
|
color_index = (len(colors) % 256) + 1
|
||||||
|
colors.append(color_index)
|
||||||
|
|
||||||
|
return colors
|
||||||
|
|
||||||
|
def _display_post(self, post_id, text):
|
||||||
|
"""Display a post with a unique color, fading out over time."""
|
||||||
|
# Assign a unique color
|
||||||
|
color_index = self.color_palette[hash(post_id) % len(self.color_palette)]
|
||||||
|
|
||||||
|
# Get screen dimensions
|
||||||
|
max_y, max_x = self.stdscr.getmaxyx()
|
||||||
|
|
||||||
|
# Trim text to fit screen width
|
||||||
|
text = text[:max_x-1]
|
||||||
|
|
||||||
|
# Track post details
|
||||||
|
if post_id not in self.posts:
|
||||||
|
# Remove oldest post if we've reached max
|
||||||
|
if len(self.posts) >= self.max_posts:
|
||||||
|
oldest_id = min(self.posts, key=lambda k: self.posts[k]['timestamp'])
|
||||||
|
del self.posts[oldest_id]
|
||||||
|
|
||||||
|
# Find a free vertical position
|
||||||
|
used_y = set(post['y'] for post in self.posts.values())
|
||||||
|
y = next(i for i in range(max_y) if i not in used_y)
|
||||||
|
|
||||||
|
self.posts[post_id] = {
|
||||||
|
'text': text,
|
||||||
|
'color': color_index,
|
||||||
|
'y': y,
|
||||||
|
'timestamp': time.time(),
|
||||||
|
'fade_count': 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Render posts
|
||||||
|
for pid, post_info in list(self.posts.items()):
|
||||||
|
# Calculate fade effect
|
||||||
|
age = time.time() - post_info['timestamp']
|
||||||
|
fade_speed = 0.5 # Adjust for desired fade speed
|
||||||
|
|
||||||
|
if age > fade_speed * post_info['fade_count']:
|
||||||
|
try:
|
||||||
|
# Gradually reduce text intensity
|
||||||
|
intensity = max(0, 1 - (post_info['fade_count'] / 10))
|
||||||
|
color = curses.color_pair(post_info['color'])
|
||||||
|
|
||||||
|
# Render text at constant horizontal position
|
||||||
|
self.stdscr.addstr(
|
||||||
|
post_info['y'],
|
||||||
|
post_info['fade_count'],
|
||||||
|
post_info['text'][:max_x-1],
|
||||||
|
color
|
||||||
|
)
|
||||||
|
|
||||||
|
post_info['fade_count'] += 1
|
||||||
|
except curses.error:
|
||||||
|
# If we can't write (e.g., screen boundaries), remove post
|
||||||
|
del self.posts[pid]
|
||||||
|
|
||||||
|
# Refresh display
|
||||||
|
self.stdscr.refresh()
|
||||||
|
|
||||||
|
async def connect_and_visualize(self):
|
||||||
|
"""Connect to Bluesky firehose and visualize posts."""
|
||||||
|
logger.info("connect_and_visualize")
|
||||||
|
try:
|
||||||
|
async with websockets.connect(self.websocket_url) as websocket:
|
||||||
|
while True:
|
||||||
|
message = await websocket.recv()
|
||||||
|
post = json.loads(message)
|
||||||
|
|
||||||
|
# Extract meaningful text (adjust based on actual Bluesky JSON structure)
|
||||||
|
post_id = post.get('did', "r:"+str(random.random()))
|
||||||
|
record = post.get('record', '---(no record in post)---')
|
||||||
|
text = record.get('text', '---(no text in post)---')
|
||||||
|
|
||||||
|
# Display the post
|
||||||
|
logger.info(f"{post_id=} {text=}")
|
||||||
|
self._display_post(post_id, text)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.stdscr.addstr(0, 0, f"Error: {str(e)}")
|
||||||
|
self.stdscr.refresh()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""Run the visualizer."""
|
||||||
|
asyncio.run(self.connect_and_visualize())
|
||||||
|
|
||||||
|
def main(stdscr):
|
||||||
|
logger.info("main()")
|
||||||
|
# Replace with actual Bluesky firehose websocket URL
|
||||||
|
BLUESKY_FIREHOSE_WS = "wss://jetstream2.us-east.bsky.network/subscribe?wantedCollections=app.bsky.feed.post"
|
||||||
|
|
||||||
|
visualizer = BlueskyFirehoseVisualizer(stdscr, BLUESKY_FIREHOSE_WS)
|
||||||
|
visualizer.run()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Wrap main in curses wrapper to handle terminal setup/teardown
|
||||||
|
try:
|
||||||
|
curses.wrapper(main)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e)
|
||||||
|
|
||||||
|
# Dependencies (install with pip):
|
||||||
|
# websockets
|
||||||
|
#
|
||||||
|
# Note: You'll need to replace the websocket URL with the actual
|
||||||
|
# Bluesky firehose websocket endpoint when available.
|
||||||
94
bluesky-simple-print.orig.py
Normal file
94
bluesky-simple-print.orig.py
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import websockets
|
||||||
|
import random
|
||||||
|
from colorama import init, Fore, Style
|
||||||
|
|
||||||
|
# Initialize colorama for cross-platform colored output
|
||||||
|
init(autoreset=True)
|
||||||
|
|
||||||
|
class BlueskyFirehosePrinter:
|
||||||
|
def __init__(self):
|
||||||
|
# Preset list of vibrant colors
|
||||||
|
self.colors = [
|
||||||
|
Fore.RED, Fore.GREEN, Fore.YELLOW, Fore.BLUE,
|
||||||
|
Fore.MAGENTA, Fore.CYAN, Fore.WHITE,
|
||||||
|
Fore.LIGHTRED_EX, Fore.LIGHTGREEN_EX,
|
||||||
|
Fore.LIGHTYELLOW_EX, Fore.LIGHTBLUE_EX,
|
||||||
|
Fore.LIGHTMAGENTA_EX, Fore.LIGHTCYAN_EX
|
||||||
|
]
|
||||||
|
|
||||||
|
def _get_post_color(self, post_id):
|
||||||
|
"""Deterministically select a color based on post ID"""
|
||||||
|
# Use hash of post ID to consistently select a color
|
||||||
|
return self.colors[hash(post_id) % len(self.colors)]
|
||||||
|
|
||||||
|
def _extract_post_text(self, post):
|
||||||
|
"""
|
||||||
|
Extract meaningful text from a post.
|
||||||
|
Modify this method based on the actual Bluesky post JSON structure.
|
||||||
|
"""
|
||||||
|
# Example of potential extraction, will need to be adapted
|
||||||
|
if isinstance(post, dict):
|
||||||
|
# Try different possible text fields
|
||||||
|
text = post.get('text') or \
|
||||||
|
post.get('content', {}).get('text') or \
|
||||||
|
str(post)
|
||||||
|
return text[:200] # Limit text length
|
||||||
|
return str(post)
|
||||||
|
|
||||||
|
async def connect_and_print(self, websocket_url):
|
||||||
|
"""Connect to websocket and print posts"""
|
||||||
|
try:
|
||||||
|
async with websockets.connect(websocket_url) as websocket:
|
||||||
|
print(f"Connected to {websocket_url}")
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
message = await websocket.recv()
|
||||||
|
|
||||||
|
# Parse message
|
||||||
|
try:
|
||||||
|
post = json.loads(message)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
post = message
|
||||||
|
|
||||||
|
# Generate a unique post ID
|
||||||
|
post_id = str(hash(json.dumps(post)))
|
||||||
|
|
||||||
|
# Extract and print text
|
||||||
|
text = self._extract_post_text(post)
|
||||||
|
|
||||||
|
# Select color based on post ID
|
||||||
|
color = self._get_post_color(post_id)
|
||||||
|
|
||||||
|
# Print colored post
|
||||||
|
print(f"{color}{text}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error processing message: {e}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Websocket connection error: {e}")
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
# Replace with actual Bluesky firehose websocket URL
|
||||||
|
BLUESKY_FIREHOSE_WS = "ws://example.com/bluesky-firehose"
|
||||||
|
|
||||||
|
printer = BlueskyFirehosePrinter()
|
||||||
|
await printer.connect_and_print(BLUESKY_FIREHOSE_WS)
|
||||||
|
|
||||||
|
def cli_main():
|
||||||
|
# Run the async main function
|
||||||
|
asyncio.run(main())
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
cli_main()
|
||||||
|
|
||||||
|
# Dependencies:
|
||||||
|
# pip install websockets colorama
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
# 1. Replace BLUESKY_FIREHOSE_WS with actual websocket URL
|
||||||
|
# 2. The post extraction method (_extract_post_text)
|
||||||
|
# will likely need customization based on the
|
||||||
|
# actual Bluesky firehose JSON structure
|
||||||
220
bluesky-simple-print.py
Normal file
220
bluesky-simple-print.py
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import websockets
|
||||||
|
import random
|
||||||
|
from colorama import init, Fore, Style
|
||||||
|
|
||||||
|
import munch
|
||||||
|
import colorsys
|
||||||
|
import math
|
||||||
|
import fire
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
level = os.environ.get("LOGLEVEL", logging.INFO)
|
||||||
|
try: level = int(level)
|
||||||
|
except: level = level.upper()
|
||||||
|
logging.basicConfig(level=level)
|
||||||
|
logger = logging.getLogger("bfire")
|
||||||
|
|
||||||
|
# Initialize colorama for cross-platform colored output
|
||||||
|
init(autoreset=True)
|
||||||
|
|
||||||
|
class BlueskyFirehosePrinter:
|
||||||
|
def __init__(self):
|
||||||
|
# Preset list of vibrant colors
|
||||||
|
self.colors = [
|
||||||
|
Fore.RED, Fore.GREEN, Fore.YELLOW, Fore.BLUE,
|
||||||
|
Fore.MAGENTA, Fore.CYAN, Fore.WHITE,
|
||||||
|
Fore.LIGHTRED_EX, Fore.LIGHTGREEN_EX,
|
||||||
|
Fore.LIGHTYELLOW_EX, Fore.LIGHTBLUE_EX,
|
||||||
|
Fore.LIGHTMAGENTA_EX, Fore.LIGHTCYAN_EX
|
||||||
|
]
|
||||||
|
|
||||||
|
def _get_post_color(self, ts):
|
||||||
|
"""Deterministically select a color based on post ID^W^W timestamp"""
|
||||||
|
# Use hash of post ID to consistently select a color
|
||||||
|
#return self.colors[hash(post_id) % len(self.colors)]
|
||||||
|
return self.colors[int(ts) % len(self.colors)]
|
||||||
|
|
||||||
|
def _extract_post_text(self, post):
|
||||||
|
"""
|
||||||
|
Extract meaningful text from a post.
|
||||||
|
Modify this method based on the actual Bluesky post JSON structure.
|
||||||
|
"""
|
||||||
|
# Example of potential extraction, will need to be adapted
|
||||||
|
if isinstance(post, dict):
|
||||||
|
# Try different possible text fields
|
||||||
|
text = post.get('text') or \
|
||||||
|
post.get('content', {}).get('text') or \
|
||||||
|
str(post)
|
||||||
|
return text[:200] # Limit text length
|
||||||
|
return str(post)
|
||||||
|
|
||||||
|
def _hsv_termcolor(h, s, v):
|
||||||
|
"""[0,1] h, s, v -> 256 color terminal codes"""
|
||||||
|
assert (h <= 1 and h >= 0), "h"
|
||||||
|
assert (s <= 1 and s >= 0), "s"
|
||||||
|
assert (v <= 1 and v >= 0), "v"
|
||||||
|
rgb1 = colorsys.hsv_to_rgb(h, s, v)
|
||||||
|
rgb256 = list(int(i*255) for i in rgb1)
|
||||||
|
colorstr = "\033[38;2;{};{};{}m".format(*rgb256)
|
||||||
|
return colorstr
|
||||||
|
|
||||||
|
|
||||||
|
async def connect_and_print(self, websocket_url, skips=[], onlys=[], count=None, cfilters={}, fkeeps=[], fdrops=[]):
|
||||||
|
"""Connect to websocket and print posts"""
|
||||||
|
n=0
|
||||||
|
try:
|
||||||
|
async with websockets.connect(websocket_url) as websocket:
|
||||||
|
print(f"Connected to {websocket_url}")
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
|
||||||
|
eventws = await websocket.recv()
|
||||||
|
|
||||||
|
# Parse event
|
||||||
|
try:
|
||||||
|
post = json.loads(eventws)
|
||||||
|
post = munch.munchify(post)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
post = "err:" + eventws
|
||||||
|
|
||||||
|
if post.type not in onlys: continue
|
||||||
|
if post.type in skips: continue
|
||||||
|
# type in ["com", "id", "acc"] # [com]mit, [id]entity, [acc]ount, ..? [del]ete? or commit type
|
||||||
|
ts = post.time_us//10e3
|
||||||
|
|
||||||
|
|
||||||
|
# Select color based on post ID
|
||||||
|
#color = self._get_post_color(post.time_us//10e4)
|
||||||
|
hsv1 = [ (ts % 255)/255, .8, .8]
|
||||||
|
|
||||||
|
# Generate a unique post ID
|
||||||
|
#post_id = str(hash(json.dumps(post)))
|
||||||
|
post_id = post.get("did", "r:"+str(random.random()))
|
||||||
|
|
||||||
|
# Extract and print text
|
||||||
|
#text = self._extract_post_text(post)
|
||||||
|
try:
|
||||||
|
if post.type in ["com"]:
|
||||||
|
if cfilters.get("-") and any(map(lambda w: w in post.commit.type, cfilters.get("-"))):
|
||||||
|
continue
|
||||||
|
if cfilters.get("+") and not any(map(lambda w: w in post.commit.type, cfilters.get("+"))):
|
||||||
|
continue
|
||||||
|
if fdrops and any(map(lambda w: w in post.commit.record.text, fdrops)):
|
||||||
|
continue
|
||||||
|
if fkeeps and not any(map(lambda w: w in post.commit.record.text, fkeeps)):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if post.commit.record.text:
|
||||||
|
text = post.commit.record.text
|
||||||
|
else:
|
||||||
|
text = f"post.commit.record={post.commit.record.toJSON()}"
|
||||||
|
hsv1[2] = 1-min(1, math.log(len(text))/math.log(256*16))
|
||||||
|
except Exception as e:
|
||||||
|
text = str(post.toJSON())
|
||||||
|
hsv1[1] = .8
|
||||||
|
#hsv1[2] = 1-min(1, math.log(len(text))/math.log(256*16)) ## ~ 80
|
||||||
|
hsv1[2] = 60/255
|
||||||
|
hsv1[2] = 120/255
|
||||||
|
#color = "\033[38;2;%s;%s;%sm" % (64,64,64) # /255 ea
|
||||||
|
# Red\033[0m
|
||||||
|
#Fore.LIGHTWHITE_EX
|
||||||
|
if count is not None:
|
||||||
|
n+=1
|
||||||
|
if n > count:
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
# 0 - 500 5 vs 30 vs 90
|
||||||
|
# [h,s,v]
|
||||||
|
#int(255 * math.log(len(text)))
|
||||||
|
#rgb256[2] = min(255, len(text))
|
||||||
|
#rgb256[1] = max(8, 255-len(text))
|
||||||
|
|
||||||
|
#hsv1[1] = max(8, 255-len(text))/256
|
||||||
|
#hsv1[2] = (max(0, 255-len(text)))/256
|
||||||
|
### hsv1[2] = 1-min(1, math.log(len(text))/math.log(256*16))
|
||||||
|
#(max(0, 255-len(text)))/256
|
||||||
|
|
||||||
|
|
||||||
|
rgb1 = colorsys.hsv_to_rgb(*hsv1)
|
||||||
|
rgb256 = list(int(i*255) for i in rgb1)
|
||||||
|
#logger.info(f"{rgb256=}")
|
||||||
|
|
||||||
|
color = "\033[38;2;{};{};{}m".format(*rgb256)
|
||||||
|
hsv1s = ":".join(f"{x:.1}" for x in hsv1)
|
||||||
|
|
||||||
|
ihsv1 = list(hsv1)
|
||||||
|
#ihsv1[1], ihsv1[2] *= .1, .4
|
||||||
|
|
||||||
|
ihsv1[2] *= .4 # v -- non- dark/black -ness
|
||||||
|
ihsv1[1] *= .1 # s -- color sat
|
||||||
|
ihsv1[2] = max(.3, ihsv1[2])
|
||||||
|
irgb1 = colorsys.hsv_to_rgb(*ihsv1)
|
||||||
|
irgb256 = list(int(i*255) for i in irgb1)
|
||||||
|
infocolor = "\033[38;2;{};{};{}m".format(*irgb256)
|
||||||
|
|
||||||
|
# Print colored post
|
||||||
|
try:
|
||||||
|
if post.type == "com":
|
||||||
|
print(f'{infocolor}{int(ts)}|type:{getattr(post,"type",None)}|{color}{text}{infocolor}|hsv:{hsv1s} type:{getattr(post,"type",None)} kind:{getattr(post,"kind",None)} {post.commit.type=} {post.commit.operation=}')
|
||||||
|
else:
|
||||||
|
ihsv1[1] *= .3
|
||||||
|
ihsv1[2] = 1
|
||||||
|
infocolor=_hsv_termcolor(*ihsv1)
|
||||||
|
print(f'{infocolor}{int(ts)}|type:{getattr(post,"type",None)}|{color}{text}{infocolor}|hsv:{hsv1s} type:{getattr(post,"type",None)} kind:{getattr(post,"kind",None)}')
|
||||||
|
except Exception as e:
|
||||||
|
print(f'{infocolor}{int(ts)}|{color}{text}{infocolor}|hsv:{hsv1s} type:{getattr(post,"type",None)} kind:{getattr(post,"kind",None)} -- no post commit')
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error processing event: {e}")
|
||||||
|
raise e
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Websocket connection error: {e}")
|
||||||
|
|
||||||
|
async def main(skips=[], onlys=[], count=None, cfilters={}, fkeeps=[], fdrops=[]):
|
||||||
|
# Replace with actual Bluesky firehose websocket URL
|
||||||
|
BLUESKY_FIREHOSE_WS = "wss://jetstream2.us-east.bsky.network/subscribe?wantedCollections=app.bsky.feed.post"
|
||||||
|
|
||||||
|
printer = BlueskyFirehosePrinter()
|
||||||
|
await printer.connect_and_print(BLUESKY_FIREHOSE_WS, skips=skips, onlys=onlys, count=count, cfilters=cfilters, fkeeps=fkeeps, fdrops=fdrops)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def cli_main(skips="", only="", cfilters="", filters="", count=None):
|
||||||
|
"""
|
||||||
|
run the async func.
|
||||||
|
--skip=[value][,value]*
|
||||||
|
--only=[value][,value]*
|
||||||
|
--filters=+include,-skip,+more,-nope
|
||||||
|
--cfilters= -- commit types: delete, create, (reply?), (post?)
|
||||||
|
--count=[n] -- stop after
|
||||||
|
"""
|
||||||
|
# Run the async main function
|
||||||
|
skips = skips.split(",")
|
||||||
|
onlys = only.split(",")
|
||||||
|
cfs = cfilters.split(",")
|
||||||
|
cfilters = {"+": [f[1:] for f in cfs if f[:1] == "+"],
|
||||||
|
"-": [f[1:] for f in cfs if f[:1] == "-"] }
|
||||||
|
fs = filters.split(",")
|
||||||
|
fkeeps = [f[1:] for f in fs if f[:1] == "+"]
|
||||||
|
fdrops = [f[1:] for f in fs if f[:1] == "-"]
|
||||||
|
try:
|
||||||
|
asyncio.run(main(skips=skips, onlys=onlys, count=count, cfilters=cfilters, fkeeps=fkeeps, fdrops=fdrops))
|
||||||
|
except KeyboardInterrupt as kb:
|
||||||
|
print("done")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
fire.Fire(cli_main)
|
||||||
|
|
||||||
|
# Dependencies:
|
||||||
|
# pip install websockets colorama
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
# 1. Replace BLUESKY_FIREHOSE_WS with actual websocket URL
|
||||||
|
# 2. The post extraction method (_extract_post_text)
|
||||||
|
# will likely need customization based on the
|
||||||
|
# actual Bluesky firehose JSON structure
|
||||||
1
bsky-event-1.json
Normal file
1
bsky-event-1.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"did":"did:plc:hdps5qzxhbhmgyfht7xbrf4u","time_us":1732139039346541,"type":"com","kind":"commit","commit":{"rev":"3lbfwa42a6326","type":"c","operation":"create","collection":"app.bsky.feed.post","rkey":"3lbfwa3w22c2d","record":{"$type":"app.bsky.feed.post","createdAt":"2024-11-20T21:43:58.933Z","langs":["en"],"reply":{"parent":{"cid":"bafyreidaivmfhldd4jzjxjztfxywkgszhovguvgd6hfr6qbumg73imerpe","uri":"at://did:plc:5ybjw77wuat2wpeq434hzyen/app.bsky.feed.post/3kwi7jcnbsf2s"},"root":{"cid":"bafyreigugwpm7vfdwltwyr4j6lb4bixdl3irdymasyy6ponm2u23sfp6aq","uri":"at://did:plc:dfdn5h5ejeloscqpou577jvy/app.bsky.feed.post/3kwhy3mlxte2o"}},"text":"I agree"},"cid":"bafyreifoxsw7x4tstgm5367zx2dauyrt4qt6j65amv2zvsqrq43tkve7mu"}}
|
||||||
Reference in New Issue
Block a user