Don't forget to lycansubscribe
1require 'json' 2require 'sinatra/base' 3 4require_relative 'init' 5require_relative 'authenticator' 6require_relative 'models/user' 7require_relative 'query_parser' 8 9class Server < Sinatra::Application 10 register Sinatra::ActiveRecordExtension 11 set :port, 3000 12 13 PAGE_LIMIT = 25 14 HOSTNAME = 'lycan.feeds.blue' 15 16 helpers do 17 def json_response(data) 18 content_type :json 19 JSON.generate(data) 20 end 21 22 def json_error(name, message, status: 400) 23 content_type :json 24 [status, JSON.generate({ error: name, message: message })] 25 end 26 end 27 28 before do 29 @authenticator = Authenticator.new(hostname: HOSTNAME) 30 end 31 32 get '/xrpc/blue.feeds.lycan.searchPosts' do 33 headers['access-control-allow-origin'] = '*' 34 35 if settings.development? 36 user = User.find_by(did: params[:user]) 37 return json_error('UserNotFound', 'Missing "user" parameter') if user.nil? 38 else 39 begin 40 auth_header = env['HTTP_AUTHORIZATION'] 41 did = @authenticator.decode_user_from_jwt(auth_header, 'blue.feeds.lycan.searchPosts') 42 rescue StandardError => e 43 p e 44 end 45 46 user = did && User.find_by(did: did) 47 return json_error('UserNotFound', 'Missing authentication header') if user.nil? 48 end 49 50 if params[:query].to_s.strip.empty? 51 return json_error('MissingParameter', 'Missing "query" parameter') 52 end 53 54 if params[:collection].to_s.strip.empty? 55 return json_error('MissingParameter', 'Missing "collection" parameter') 56 end 57 58 query = QueryParser.new(params[:query]) 59 60 collection = case params[:collection] 61 when 'likes' then user.likes 62 when 'pins' then user.pins 63 when 'quotes' then user.quotes 64 when 'reposts' then user.reposts 65 else return json_error('InvalidParameter', 'Invalid search collection') 66 end 67 68 if query.terms.empty? 69 return json_response(terms: [], posts: [], cursor: nil) 70 end 71 72 items = collection 73 .joins(:post) 74 .includes(:post => :user) 75 .matching_terms(query.terms) 76 .excluding_terms(query.exclusions) 77 .reverse_chronologically 78 .after_cursor(params[:cursor]) 79 .limit(PAGE_LIMIT) 80 81 post_uris = case params[:collection] 82 when 'quotes' 83 items.map { |x| "at://#{user.did}/app.bsky.feed.post/#{x.rkey}" } 84 else 85 items.map(&:post).map(&:at_uri) 86 end 87 88 json_response(terms: query.terms, posts: post_uris, cursor: items.last&.cursor) 89 end 90 91 get '/.well-known/did.json' do 92 json_response({ 93 '@context': ['https://www.w3.org/ns/did/v1'], 94 id: "did:web:#{HOSTNAME}", 95 service: [ 96 { 97 id: '#lycan', 98 type: 'LycanServer', 99 serviceEndpoint: "https://#{HOSTNAME}" 100 } 101 ] 102 }) 103 end 104end