blue_factory 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 80b991e6702487fe63c56386b86d181093e88abbe3bc3c30360a4fca7977350f
4
- data.tar.gz: d4fe4bfd4a4cb2dea7dd165eb6dbff45bd3e55eb72144b82669dd5bab212b1d8
3
+ metadata.gz: 10e968b7723f9f1244cc006195c899ef5152d8aca0393787fbf0ac39901c7a2a
4
+ data.tar.gz: 0d020d91890d1afdac88a993cc6f659a29b6790b6dc031c7464f93ca347c263a
5
5
  SHA512:
6
- metadata.gz: 3c591080e8fb94fb8ecbc8db0178b08a4d98b018ece55975ab672dd81ea05d5b7aaa18aebedd3b1514d7e134b2d77794e98026a9bcfcc06d73fe71bf67d74df0
7
- data.tar.gz: 3c2dcb624de1429e6eee265727c5a2368ce4cbb999e74d07016ae485b568c34cfdea39c9cb5640d32730f597e548bac48255c7e04a8cf8359d360e9db4d88ef4
6
+ metadata.gz: 90304d80cc54255f1550bdf42403e699334f92a1a1bf6d0b0ecea43a66e109f1f1fbcee9600f6ff38198093a7bbf9d189c3e31d226d0db7f8ae956521a051967
7
+ data.tar.gz: 70180d3c1fde075b0e088882b47a18716dc7488cdb669bf28808d2b7a9b55917053ce7adf4418a1e2ce00eade2eabef49dda211a93cbf6c6dc92918351587d7a
data/README.md CHANGED
@@ -1,31 +1,172 @@
1
- # BlueFactory
1
+ # BlueFactory 🏭
2
2
 
3
- TODO: Delete this and the text below, and describe your gem
3
+ A Ruby gem for hosting custom feeds for Bluesky
4
4
 
5
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/blue_factory`. To experiment with that code, run `bin/console` for an interactive prompt.
6
5
 
7
- ## Installation
6
+ ## What does it do
7
+
8
+ BlueFactory is a Ruby library which helps you build a web service that hosts custom feeds a.k.a. "[feed generators](https://github.com/bluesky-social/feed-generator)" for the Bluesky social network. It implements a simple HTTP server based on [Sinatra](https://sinatrarb.com) which provides the required endpoints for the feed generator interface. You need to provide the content for the feed by making a query to your preferred local database.
8
9
 
9
- TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
10
+ A feed server will usually be run together with a second piece of code that streams posts from the Bluesky "firehose" stream, runs them through some kind of filter and saves some or all of them to the database. To build that part, you can use my other Ruby gem [Skyfall](https://github.com/mackuba/skyfall).
10
11
 
11
- Install the gem and add to the application's Gemfile by executing:
12
12
 
13
- $ bundle add UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
13
+ ## Installation
14
14
 
15
- If bundler is not being used to manage dependencies, install the gem by executing:
15
+ gem install blue_factory
16
16
 
17
- $ gem install UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
18
17
 
19
18
  ## Usage
20
19
 
21
- TODO: Write usage instructions here
20
+ The server is configured through the `BlueFactory` module. The two required settings are:
21
+
22
+ - `publisher_did` - DID identifier of the account that you will publish the feed on (the string that starts with `did:plc:...`)
23
+ - `hostname` - the hostname on which the feed service will be run
24
+
25
+ You also need to configure at least one feed by passing a feed key and a feed object. The key is the identifier that will appear at the end of the feed URI - ideally something short and lowercase. The object is anything that implements the single required method `get_posts` (could be a class, a module or an instance).
26
+
27
+ So a simple setup could look like this:
28
+
29
+ ```rb
30
+ require 'blue_factory'
31
+
32
+ BlueFactory.set :publisher_did, 'did:plc:loremipsumqwerty'
33
+ BlueFactory.set :hostname, 'feeds.example.com'
34
+
35
+ BlueFactory.add_feed 'starwars', StarWarsFeed.new
36
+ ```
37
+
38
+
39
+ ### The feed object
40
+
41
+ The `get_posts` method of the feed object should:
42
+
43
+ - accept a single `params` argument which is a hash with fields: `:feed`, `:cursor` and `:limit` (the last two are optional)
44
+ - return a hash with two fields: `:cursor` and `:posts`
45
+
46
+ The `:feed` is the `at://` URI of the feed. The `:cursor` param, if included, should be a cursor returned by your feed from one of the previous requests, so it should be in the format used by the same function - but anyone can call the endpoint with any params, so you should validate it. The cursor is used for pagination to provide more pages further down in the feed (the first request to load the top of the feed doesn't include a cursor).
47
+
48
+ The `:limit`, if included, should be a numeric value specifying the number of posts to return, and you should return at most that many posts in response. According to the spec, the maximum allowed value for the limit is 100, but again, you should verify this. The default limit is 50.
49
+
50
+ The `:cursor` that you return is some kind of string that encodes the offset in the feed for a request for the next page. The structure of the cursor is something for you to decide, and it could possibly be a very long string (the actual length limit is uncertain). See the readme of the official [feed-generator repo](https://github.com/bluesky-social/feed-generator#pagination) for some guidelines on how to construct cursor strings.
51
+
52
+ And finally, the `:posts` value should be an array of posts, returned as `at://` URI strings only. The Bluesky server that makes the request for the feed will provide all the other data for the posts based on the URIs you return.
53
+
54
+ If you determine that the request is somehow invalid (e.g. the cursor doesn't match what you expect), you can also raise a `BlueFactory::InvalidRequestError` error, which will return a JSON error message with status 400.
55
+
56
+ An example implementation could look like this:
57
+
58
+ ```rb
59
+ require 'time'
60
+
61
+ class StarWarsFeed
62
+ def get_posts(params)
63
+ limit = check_query_limit(params)
64
+ query = Post.select('uri, time').order('time DESC').limit(limit)
65
+
66
+ if params[:cursor].to_s != ""
67
+ time = Time.at(params[:cursor].to_i)
68
+ query = query.where("time < ?", time)
69
+ end
70
+
71
+ posts = query.to_a
72
+ last = posts.last
73
+ cursor = last && last.time.to_i.to_s
74
+
75
+ { cursor: cursor, posts: posts.map(&:uri) }
76
+ end
77
+
78
+ def check_query_limit(params)
79
+ if params[:limit]
80
+ limit = params[:limit].to_i
81
+ (limit < 0) ? 0 : (limit > MAX_LIMIT ? MAX_LIMIT : limit)
82
+ else
83
+ DEFAULT_LIMIT
84
+ end
85
+ end
86
+ end
87
+ ```
88
+
89
+ ### Starting the server
90
+
91
+ The server itself is run using the `BlueFactory::Server` class, which is a subclass of `Sinatra::Base` and is used as described in the [Sinatra documentation](https://sinatrarb.com/intro.html) (as a "modular application").
92
+
93
+ In development, you can launch it using:
94
+
95
+ ```rb
96
+ BlueFactory::Server.run!
97
+ ```
98
+
99
+ In production, you will probably want to create a `config.ru` file that instead runs it from the Rack interface:
100
+
101
+ ```rb
102
+ run BlueFactory::Server
103
+ ```
104
+
105
+ Then, you would configure your preferred Ruby app server like Passenger, Unicorn or Puma to run the server using that config file and configure the main HTTP server (Nginx, Apache) to route requests on the given hostname to that app server.
106
+
107
+ As an example, an Nginx configuration for a site that runs the server via Passenger could look something like this:
108
+
109
+ ```
110
+ server {
111
+ server_name feeds.example.com;
112
+ listen 443 ssl;
113
+
114
+ passenger_enabled on;
115
+ root /var/www/feeds/current/public;
116
+
117
+ ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
118
+ ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
119
+
120
+ access_log /var/log/nginx/feeds-access.log combined buffer=16k flush=10s;
121
+ error_log /var/log/nginx/feeds-error.log;
122
+ }
123
+ ```
124
+
125
+ ### Additional configuration & customizing
126
+
127
+ You can use the [Sinatra API](https://sinatrarb.com/intro.html#configuration) to do any additional configuration, like changing the server port, enabling/disabling logging and so on.
128
+
129
+ For example, you can change the port used in development with:
130
+
131
+ ```rb
132
+ BlueFactory::Server.set :port, 7777
133
+ ```
134
+
135
+ You can also add additional routes, e.g. to make a redirect or print something on the root URL:
136
+
137
+ ```rb
138
+ BlueFactory::Server.get '/' do
139
+ redirect 'https://github.com/mackuba/blue_factory'
140
+ end
141
+ ```
142
+
143
+
144
+ ## Publishing the feed
145
+
146
+ When your feed server is ready and deployed to the production server, you can use the included `bluesky:publish` Rake task to upload the feed configuration to the Bluesky network. To do that, add this line to your `Rakefile`:
147
+
148
+ ```rb
149
+ require 'blue_factory/rake'
150
+ ```
151
+
152
+ You also need to load your `BlueFactory` configuration and your feed classes here, so it's recommended that you extract this configuration code to some kind of init file that can be included in the `Rakefile`, `config.ru` and elsewhere if needed.
153
+
154
+ To publish the feed, you will need to provide some additional info about the feed, like its public name, through a few more methods in the feed object (the same one that responds to `#get_posts`):
155
+
156
+ - `display_name` (required) - the publicly visible name of your feed, e.g. "WWDC 23" (should be something short)
157
+ - `description` (optional) - a longer (~1-2 lines) description of what the feed does, displayed on the feed page as the "bio"
158
+ - `avatar_file` (optional) - path to an avatar image from the project's root (PNG or JPG)
159
+
160
+ When you're ready, run the rake task passing the feed key (you will be asked for the uploader account's password):
22
161
 
23
- ## Development
162
+ ```
163
+ bundle exec rake bluesky:publish KEY=wwdc
164
+ ```
24
165
 
25
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
166
+ ## Credits
26
167
 
27
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
168
+ Copyright © 2023 Kuba Suder ([@mackuba.eu](https://bsky.app/profile/mackuba.eu)).
28
169
 
29
- ## Contributing
170
+ The code is available under the terms of the [zlib license](https://choosealicense.com/licenses/zlib/) (permissive, similar to MIT).
30
171
 
31
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/blue_factory.
172
+ Bug reports and pull requests are welcome 😎
@@ -43,7 +43,7 @@ namespace :bluesky do
43
43
  if feed.respond_to?(:avatar_file) && feed.avatar_file.to_s.strip != ''
44
44
  avatar_file = feed.avatar_file
45
45
 
46
- if !File.exist?
46
+ if !File.exist?(avatar_file)
47
47
  puts "Avatar file #{avatar_file} not found."
48
48
  exit 1
49
49
  end
@@ -72,6 +72,13 @@ namespace :bluesky do
72
72
 
73
73
  access_token = json['accessJwt']
74
74
 
75
+ if avatar_data
76
+ json = BlueFactory::Net.post_request(server, 'com.atproto.repo.uploadBlob', avatar_data,
77
+ content_type: encoding, auth: access_token)
78
+
79
+ avatar_ref = json['blob']
80
+ end
81
+
75
82
  record = {
76
83
  did: BlueFactory.service_did,
77
84
  displayName: feed_display_name,
@@ -79,6 +86,8 @@ namespace :bluesky do
79
86
  createdAt: Time.now.iso8601,
80
87
  }
81
88
 
89
+ record[:avatar] = avatar_ref if avatar_ref
90
+
82
91
  json = BlueFactory::Net.post_request(server, 'com.atproto.repo.putRecord', {
83
92
  repo: BlueFactory.publisher_did,
84
93
  collection: BlueFactory::FEED_GENERATOR_TYPE,
@@ -86,6 +95,6 @@ namespace :bluesky do
86
95
  record: record
87
96
  }, auth: access_token)
88
97
 
89
- p json
98
+ puts "Feed published ✓"
90
99
  end
91
100
  end
@@ -12,8 +12,6 @@ module BlueFactory
12
12
 
13
13
  body = data.is_a?(String) ? data : data.to_json
14
14
 
15
- puts body unless data.is_a?(String)
16
-
17
15
  response = ::Net::HTTP.post(URI("#{server}/xrpc/#{method}"), body, headers)
18
16
  raise ResponseError, "Invalid response: #{response.code} #{response.body}" if response.code.to_i / 100 != 2
19
17
 
@@ -1,3 +1,3 @@
1
1
  module BlueFactory
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: blue_factory
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kuba Suder
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-06-12 00:00:00.000000000 Z
11
+ date: 2023-06-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sinatra