this repo has no description
1import logging 2 3import apsw 4import apsw.ext 5 6from . import BaseFeed 7 8class RatioFeed(BaseFeed): 9 FEED_URI = 'at://did:plc:4nsduwlpivpuur4mqkbfvm6a/app.bsky.feed.generator/ratio' 10 SERVE_FEED_QUERY = """ 11 with served as ( 12 select 13 uri, 14 create_ts, 15 ( unixepoch('now') - create_ts ) as age_seconds, 16 replies, 17 quoteposts, 18 likes, 19 reposts, 20 ( replies + quoteposts ) / ( likes + reposts + 1 ) as ratio, 21 exp( -1 * ( ( unixepoch('now') - create_ts ) / ( 3600.0 * 16 ) ) ) as decay 22 from posts 23 ) 24 select 25 *, 26 ( ratio * decay ) as score 27 from served 28 where replies > 15 and ratio > 2.5 29 order by score desc 30 limit :limit offset :offset 31 """ 32 DELETE_OLD_POSTS_QUERY = """ 33 delete from posts 34 where 35 create_ts < unixepoch('now', '-5 days') 36 """ 37 38 def __init__(self): 39 self.db_cnx = apsw.Connection('db/ratio.db') 40 self.db_cnx.pragma('journal_mode', 'WAL') 41 self.db_cnx.pragma('wal_autocheckpoint', '0') 42 43 with self.db_cnx: 44 self.db_cnx.execute(""" 45 create table if not exists posts ( 46 uri text, create_ts timestamp, 47 replies float, likes float, reposts float, quoteposts float 48 ); 49 create unique index if not exists uri_idx on posts(uri); 50 """) 51 52 self.logger = logging.getLogger('feeds.ratio') 53 54 def process_commit(self, commit): 55 if commit['opType'] != 'c': 56 return 57 58 subject_uri = None 59 is_reply = False 60 is_quotepost = False 61 62 if commit['collection'] in {'app.bsky.feed.like', 'app.bsky.feed.repost'}: 63 record = commit.get('record') 64 ts = self.safe_timestamp(record.get('createdAt')).timestamp() 65 try: 66 subject_uri = record['subject']['uri'] 67 except KeyError: 68 return 69 elif commit['collection'] == 'app.bsky.feed.post': 70 record = commit.get('record') 71 ts = self.safe_timestamp(record.get('createdAt')).timestamp() 72 if record.get('reply') is not None: 73 is_reply = True 74 try: 75 subject_uri = record['reply']['parent']['uri'] 76 except KeyError: 77 return 78 79 # only count non-OP replies 80 if subject_uri.startswith('at://' + commit['did']): 81 return 82 83 elif record.get('embed') is not None: 84 is_quotepost = True 85 t = record['embed']['$type'] 86 if t == 'app.bsky.embed.record': 87 try: 88 subject_uri = record['embed']['record']['uri'] 89 except KeyError: 90 return 91 elif t == 'app.bsky.embed.recordWithMedia': 92 try: 93 subject_uri = record['embed']['record']['record']['uri'] 94 except KeyError: 95 return 96 97 if subject_uri is None: 98 return 99 100 params = { 101 'uri': subject_uri, 102 'ts': ts, 103 'is_reply': int(is_reply), 104 'is_like': int(commit['collection'] == 'app.bsky.feed.like'), 105 'is_repost': int(commit['collection'] == 'app.bsky.feed.repost'), 106 'is_quotepost': int(is_quotepost), 107 } 108 109 self.transaction_begin(self.db_cnx) 110 111 self.db_cnx.execute(""" 112 insert into posts(uri, create_ts, replies, likes, reposts, quoteposts) 113 values (:uri, :ts, 114 case when :is_reply then 1 else 0 end, 115 case when :is_like then 1 else 0 end, 116 case when :is_repost then 1 else 0 end, 117 case when :is_quotepost then 1 else 0 end) 118 on conflict(uri) 119 do update set 120 replies = replies + case when :is_reply then 1 else 0 end, 121 likes = likes + case when :is_like then 1 else 0 end, 122 reposts = reposts + case when :is_repost then 1 else 0 end, 123 quoteposts = quoteposts + case when :is_quotepost then 1 else 0 end 124 """, params) 125 126 def delete_old_posts(self): 127 self.db_cnx.execute(self.DELETE_OLD_POSTS_QUERY) 128 129 def commit_changes(self): 130 self.logger.debug('committing changes') 131 self.delete_old_posts() 132 self.transaction_commit(self.db_cnx) 133 self.wal_checkpoint(self.db_cnx, 'RESTART') 134 135 def serve_feed(self, limit, offset, langs): 136 cur = self.db_cnx.execute(self.SERVE_FEED_QUERY, dict(limit=limit, offset=offset)) 137 return [row[0] for row in cur] 138 139 def serve_feed_debug(self, limit, offset, langs): 140 bindings = dict(limit=limit, offset=offset) 141 return apsw.ext.format_query_table( 142 self.db_cnx, self.SERVE_FEED_QUERY, bindings, 143 string_sanitize=2, text_width=9999, use_unicode=True 144 )