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 terms = QueryParser.new(params[:query]).terms
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 items = collection
69 .joins(:post)
70 .includes(:post => :user)
71 .matching_terms(terms)
72 .reverse_chronologically
73 .after_cursor(params[:cursor])
74 .limit(PAGE_LIMIT)
75
76 post_uris = case params[:collection]
77 when 'quotes'
78 items.map { |x| "at://#{user.did}/app.bsky.feed.post/#{x.rkey}" }
79 else
80 items.map(&:post).map(&:at_uri)
81 end
82
83 json_response(terms: terms, posts: post_uris, cursor: items.last&.cursor)
84 end
85
86 get '/.well-known/did.json' do
87 json_response({
88 '@context': ['https://www.w3.org/ns/did/v1'],
89 id: "did:web:#{HOSTNAME}",
90 service: [
91 {
92 id: '#lycan',
93 type: 'LycanServer',
94 serviceEndpoint: "https://#{HOSTNAME}"
95 }
96 ]
97 })
98 end
99end