feed2gram 0.0.2 → 0.0.3

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: 0f8b1df08d5636342c7517691d0f4bce4cc4d89f261368f730d62d9392a6d8a7
4
- data.tar.gz: e617c168c1c999be12ce2d263ca823b90777639f7d51003ac809e7660e9ef0c9
3
+ metadata.gz: c86578cd42608fcc00119f206582ff6c2812f8ba50a92a47a8e11360a635210e
4
+ data.tar.gz: 4a322434134e5bd515b4985feb53cd03054a36d6df2f9399ec2a08e3518a3816
5
5
  SHA512:
6
- metadata.gz: 9af47b0dadb5db2cbaa1c0208fd1873fd40e310646433c416635dbad2657ed05e591d212bc13819c6038a144c7d3523b9b61e932d91bb8bc012237cac6df0938
7
- data.tar.gz: f96cfbc1ba2e994b56943179906130211bd9f992828059d59b2da197f7607ad7402ae5699bb67270a944775feccd3cae3a5f184b3d6ec8b87493271fcaaeb428
6
+ metadata.gz: 025aa21dc8973cf0cc69d9d84cf2b019cf88bdf0cf5e02db48dc7fd69a3d3a9ed1a04f3290f0cb8c4fce8761cb2a4af5c76ed363221f4b4b6b442bbb4b432fbb
7
+ data.tar.gz: 1a257665bfa5ccf815c3c97039aa16b73d4f9dae3471792828ab0e4345733dd380fe2262f8034122e35efac6ee20140d9a7cbb99765706f52d89ae2f0d2559b0
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.0.3]
4
+
5
+ * Fix the `--populate-cache` so that all entries are marked `skipped`
6
+ * Include a `bin/daemon` script
7
+ * Ship a Dockerfile to build an image that can run feed2gram on a schedule
8
+
3
9
  ## [0.0.2] - 2023-10-29
4
10
 
5
11
  - Initial release
data/Dockerfile ADDED
@@ -0,0 +1,14 @@
1
+ FROM ruby:3.2.2
2
+
3
+ LABEL org.opencontainers.image.source=https://github.com/searls/feed2gram
4
+ LABEL org.opencontainers.image.description="Reads an Atom feed and posts its entries to Instagram (basically feed2toot, but for Instagram)"
5
+ LABEL org.opencontainers.image.licenses=GPLv3
6
+
7
+ WORKDIR /srv
8
+ COPY Gemfile Gemfile.lock feed2gram.gemspec .
9
+ COPY lib/feed2gram/version.rb lib/feed2gram/
10
+ RUN bundle install
11
+ ADD . .
12
+ VOLUME /config
13
+ CMD ["--config", "/config/feed2gram.yml"]
14
+ ENTRYPOINT ["/srv/bin/daemon"]
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Feed2Gram
1
+ # feed2gram
2
2
 
3
3
  I've joined the [POSSE](https://indieweb.org/POSSE) and publish as much as I can
4
4
  to [justin.searls.co](https://justin.searls.co) and syndicate it elsewhere. I'm
@@ -82,12 +82,23 @@ directory. This file is used internally by feed2gram to keep track of which
82
82
  entry URLs in the atom feed have been processed and can be ignored on the next
83
83
  run.
84
84
 
85
+ ## Docker
86
+
87
+ You can also use Docker to run this on your own automation platform like Proxmox or Kubernetes.
88
+
89
+ ```
90
+ docker run --rm -it \
91
+ -v ./feed2gram.yml:/srv/feed2gram.yml \
92
+ -v ./feed2gram.cache.yml:/srv/feed2gram.cache.yml \
93
+ ghcr.io/searls/feed2gram
94
+ ```
95
+
85
96
  ## Options
86
97
 
87
98
  For available options, run `feed2gram --help`:
88
99
 
89
100
  ```
90
- $ ./script/run --help
101
+ $ feed2gram --help
91
102
  Usage: feed2gram [options]
92
103
  --config PATH Path of feed2gram YAML configuration (default: feed2gram.yml)
93
104
  --cache-path PATH Path of feed2gram's cache file to track processed entries (default: feed2gram.cache.yml)
@@ -147,6 +158,32 @@ See more at http://localhost:1313/
147
158
  </entry>
148
159
  ```
149
160
 
161
+ ## Running continuously with Docker
162
+
163
+ We publish a Docker image [using GitHub
164
+ actions](https://github.com/searls/feed2gram/blob/main/.github/workflows/main.yml)
165
+ tagged as `latest` for every new commit to the `main` branch, as well as with a
166
+ release tag tracking every release of the gem on
167
+ [rubygems.org](https://rubygems.org). The images are hosted [here on GitHub's
168
+ container
169
+ registry](https://github.com/searls/feed2gram/pkgs/container/feed2gram)
170
+
171
+ ```
172
+ $ docker pull ghcr.io/searls/feed2gram:latest
173
+ ```
174
+
175
+ To configure your container, there are just three things to know:
176
+
177
+ 1. A volume containing your configuration and cache files must be mounted to `/config`
178
+ 2. By default, feed2gram will run with `--config /config/feed2gram.yml`, but you can
179
+ customize this by configuring the command value as needed
180
+ 3. By default, feed2gram is run as a daemon every 60 seconds, and that duration can be overridden
181
+ by setting a `SLEEP_TIME` environment variable to the number of seconds you'd like
182
+ to wait between runs
183
+ 4. If you'd rather run `feed2gram` as ad hoc as opposed to via the included daemon
184
+ (presumably to handle scheduling it yourself), simply change the entrypoint to
185
+ `/srv/exe/feed2gram`
186
+
150
187
  ## Frequently Asked Questions
151
188
 
152
189
  ### Why didn't my post show up?
@@ -166,3 +203,4 @@ The submitted image with aspect ratio ('719/194',) cannot be published. Please s
166
203
  It means your photo is too avant garde for a mainstream normie platform like
167
204
  Instagram. Make sure all images' aspect ratiosa re between 4:5 and 1.91:1 or
168
205
  else the post will fail.
206
+
@@ -6,11 +6,13 @@ module Feed2Gram
6
6
  end
7
7
 
8
8
  class LoadsCache
9
- def load(cache_path)
10
- if File.exist?(cache_path)
11
- yaml = YAML.load_file(cache_path, permitted_classes: [Time])
9
+ def load(options)
10
+ if File.exist?(options.cache_path)
11
+ puts "Loading cache from: #{options.cache_path}" if options.verbose
12
+ yaml = YAML.load_file(options.cache_path, permitted_classes: [Time])
12
13
  Cache.new(**yaml)
13
14
  else
15
+ puts "No cache found (looked at '#{options.cache_path}'), initializing a new one" if options.verbose
14
16
  Cache.new(posted: [], failed: [], skipped: [])
15
17
  end
16
18
  end
@@ -17,8 +17,9 @@ module Feed2Gram
17
17
  end
18
18
 
19
19
  class LoadsConfig
20
- def load(config_path)
21
- yaml = YAML.load_file(config_path, permitted_classes: [Time])
20
+ def load(options)
21
+ puts "Loading config from: #{options.config_path}" if options.verbose
22
+ yaml = YAML.load_file(options.config_path, permitted_classes: [Time])
22
23
  Config.new(**yaml)
23
24
  end
24
25
  end
@@ -1,7 +1,7 @@
1
1
  require "optparse"
2
2
 
3
3
  module Feed2Gram
4
- Options = Struct.new(:config_path, :cache_path, :limit, :skip_token_refresh, :populate_cache, keyword_init: true) do
4
+ Options = Struct.new(:config_path, :cache_path, :limit, :skip_token_refresh, :populate_cache, :verbose, keyword_init: true) do
5
5
  undef_method :cache_path
6
6
  def cache_path
7
7
  @cache_path || config_path.sub(/\.yml$/, ".cache.yml")
@@ -36,6 +36,10 @@ module Feed2Gram
36
36
  opts.on "--populate-cache", "Populate the cache file with any posts found in the feed WITHOUT posting them to Instagram" do
37
37
  options.populate_cache = true
38
38
  end
39
+
40
+ opts.on "-v", "--verbose", "Enable verbose output" do
41
+ options.verbose = true
42
+ end
39
43
  end.parse!(argv)
40
44
 
41
45
  options
@@ -2,13 +2,18 @@ module Feed2Gram
2
2
  Result = Struct.new(:post, :status, keyword_init: true)
3
3
 
4
4
  class PublishesPosts
5
- def publish(posts, config, limit)
5
+ def publish(posts, config, options)
6
+ post_limit = options.limit || posts.size
7
+ puts "Publishing #{post_limit} posts to Instagram" if options.verbose
8
+
6
9
  # reverse to post oldest first (most Atom feeds are reverse-chronological)
7
- posts.reverse.take(limit || posts.size).map { |post|
10
+ posts.reverse.take(post_limit).map { |post|
8
11
  begin
9
12
  if post.images.size == 1
13
+ puts "Publishing single image post for: #{post.url}" if options.verbose
10
14
  publish_single_image(post, config)
11
15
  else
16
+ puts "Publishing carousel post for: #{post.url}" if options.verbose
12
17
  publish_carousel(post, config)
13
18
  end
14
19
  rescue => e
@@ -1,9 +1,10 @@
1
1
  module Feed2Gram
2
2
  class RefreshesToken
3
- def refresh!(config, config_path)
3
+ def refresh!(config, options)
4
4
  return unless config.access_token_refreshed_at.nil? ||
5
5
  config.access_token_refreshed_at < Time.now - (60 * 60)
6
6
 
7
+ puts "Refreshing Facebook OAuth token" if options.verbose
7
8
  data = Http.get("/oauth/access_token", {
8
9
  grant_type: "fb_exchange_token",
9
10
  client_id: config.facebook_app_id,
@@ -14,7 +15,8 @@ module Feed2Gram
14
15
  config.access_token = data[:access_token]
15
16
  config.access_token_refreshed_at = Time.now.utc
16
17
 
17
- File.write(config_path, config.as_yaml)
18
+ puts "Updating Facebook OAuth token in: #{options.config_path}" if options.verbose
19
+ File.write(options.config_path, config.as_yaml)
18
20
  end
19
21
  end
20
22
  end
@@ -1,6 +1,6 @@
1
1
  module Feed2Gram
2
2
  class UpdatesCache
3
- def update!(cache, results, cache_path)
3
+ def update!(cache, results, options)
4
4
  cache.updated_at = Time.now
5
5
  results.group_by { |result| result.status }
6
6
  .transform_values { |results| results.map { |result| result.post.url } }
@@ -8,7 +8,8 @@ module Feed2Gram
8
8
  cache[status] += urls
9
9
  end
10
10
 
11
- File.write(cache_path, cache.as_yaml)
11
+ puts "Writing updated cache to: #{options.cache_path}" if options.verbose
12
+ File.write(options.cache_path, cache.as_yaml)
12
13
  end
13
14
  end
14
15
  end
@@ -1,3 +1,3 @@
1
1
  module Feed2Gram
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
data/lib/feed2gram.rb CHANGED
@@ -18,16 +18,20 @@ module Feed2Gram
18
18
  end
19
19
 
20
20
  def self.run(options)
21
- config = LoadsConfig.new.load(options.config_path)
22
- RefreshesToken.new.refresh!(config, options.config_path) unless options.skip_token_refresh
21
+ config = LoadsConfig.new.load(options)
22
+ RefreshesToken.new.refresh!(config, options) unless options.skip_token_refresh
23
23
 
24
- cache = LoadsCache.new.load(options.cache_path)
25
- posts = FiltersPosts.new.filter(ParsesEntries.new.parse(config.feed_url), cache)
24
+ cache = LoadsCache.new.load(options)
25
+ puts "Loading entries from feed: #{config.feed_url}" if options.verbose
26
+ entries = ParsesEntries.new.parse(config.feed_url)
27
+ puts "Found #{entries.size} entries in feed" if options.verbose
28
+ posts = FiltersPosts.new.filter(entries, cache)
26
29
  results = if options.populate_cache
27
- posts.map { |post| Result.new(post: post, status: [:skipped, :failed, :posted].sample) }
30
+ puts "Populating cache, marking #{posts.size} posts as skipped" if options.verbose
31
+ posts.map { |post| Result.new(post: post, status: :skipped) }
28
32
  else
29
- PublishesPosts.new.publish(posts, config, options.limit)
33
+ PublishesPosts.new.publish(posts, config, options)
30
34
  end
31
- UpdatesCache.new.update!(cache, results, options.cache_path)
35
+ UpdatesCache.new.update!(cache, results, options)
32
36
  end
33
37
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: feed2gram
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Searls
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-10-30 00:00:00.000000000 Z
11
+ date: 2023-11-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -34,6 +34,7 @@ extra_rdoc_files: []
34
34
  files:
35
35
  - ".standard.yml"
36
36
  - CHANGELOG.md
37
+ - Dockerfile
37
38
  - LICENSE.txt
38
39
  - README.md
39
40
  - Rakefile
@@ -72,7 +73,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
72
73
  - !ruby/object:Gem::Version
73
74
  version: '0'
74
75
  requirements: []
75
- rubygems_version: 3.4.6
76
+ rubygems_version: 3.4.17
76
77
  signing_key:
77
78
  specification_version: 4
78
79
  summary: Reads an Atom feed and posts its entries to Instagram