feed2gram 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0f8b1df08d5636342c7517691d0f4bce4cc4d89f261368f730d62d9392a6d8a7
4
- data.tar.gz: e617c168c1c999be12ce2d263ca823b90777639f7d51003ac809e7660e9ef0c9
3
+ metadata.gz: c053ce1eafec6ed200dc7ed549fb25e7bc22fcd78fa6b92c773673d47aeba056
4
+ data.tar.gz: 767a90bd8534d43f0e9bef8ef73e8e577bd959ade67ebabe639ac96d6bde4828
5
5
  SHA512:
6
- metadata.gz: 9af47b0dadb5db2cbaa1c0208fd1873fd40e310646433c416635dbad2657ed05e591d212bc13819c6038a144c7d3523b9b61e932d91bb8bc012237cac6df0938
7
- data.tar.gz: f96cfbc1ba2e994b56943179906130211bd9f992828059d59b2da197f7607ad7402ae5699bb67270a944775feccd3cae3a5f184b3d6ec8b87493271fcaaeb428
6
+ metadata.gz: 57c2490cf9aee77fa40d5dffceaeddb3ac71e2d23d8b44d962b87e10caa295079c9e8772d7986b0db37f2a5a826a838c5ec7cdd7355d2c0bb6704acc0b58493d
7
+ data.tar.gz: e6e39cc6033dab9c32e958c413790b2d0c8eacbe1e6d7175108df34ed2894ad2ecbb305e98ab88891fd3e8600243849dbb22dda4b73fbe3c4f0ed76096125055
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.0.4]
4
+
5
+ * Optimize the size of the Docker image (about 85% reduction)
6
+
7
+ ## [0.0.3]
8
+
9
+ * Fix the `--populate-cache` so that all entries are marked `skipped`
10
+ * Include a `bin/daemon` script
11
+ * Ship a Dockerfile to build an image that can run feed2gram on a schedule
12
+
3
13
  ## [0.0.2] - 2023-10-29
4
14
 
5
15
  - Initial release
data/Dockerfile ADDED
@@ -0,0 +1,17 @@
1
+ FROM ruby:3.2.2-alpine
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 apk update && \
11
+ apk add autoconf bash git gcc make musl-dev && \
12
+ bundle install && \
13
+ apk del --purge --rdepends git gcc autoconf make musl-dev
14
+ ADD . .
15
+ VOLUME /config
16
+ CMD ["--config", "/config/feed2gram.yml"]
17
+ 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
@@ -87,7 +87,7 @@ run.
87
87
  For available options, run `feed2gram --help`:
88
88
 
89
89
  ```
90
- $ ./script/run --help
90
+ $ feed2gram --help
91
91
  Usage: feed2gram [options]
92
92
  --config PATH Path of feed2gram YAML configuration (default: feed2gram.yml)
93
93
  --cache-path PATH Path of feed2gram's cache file to track processed entries (default: feed2gram.cache.yml)
@@ -147,6 +147,58 @@ See more at http://localhost:1313/
147
147
  </entry>
148
148
  ```
149
149
 
150
+ ## Running continuously with Docker
151
+
152
+ We publish a Docker image [using GitHub
153
+ actions](https://github.com/searls/feed2gram/blob/main/.github/workflows/main.yml)
154
+ tagged as `latest` for every new commit to the `main` branch, as well as with a
155
+ release tag tracking every release of the gem on
156
+ [rubygems.org](https://rubygems.org). The images are hosted [here on GitHub's
157
+ container
158
+ registry](https://github.com/searls/feed2gram/pkgs/container/feed2gram)
159
+
160
+
161
+ You can also use Docker to run this on your own automation platform like Proxmox or Kubernetes.
162
+
163
+ ```
164
+ $ docker run --rm -it \
165
+ -v ./your_config_dir:/srv/config
166
+ ghcr.io/searls/feed2gram
167
+ ```
168
+
169
+ To configure the container, there are just four things to know:
170
+
171
+ 1. A volume containing your configuration and cache files must be mounted to `/config`
172
+ 2. By default, feed2gram will run with `--config /config/feed2gram.yml`, but you can
173
+ customize this by configuring the command value as needed
174
+ 3. By default, feed2gram is run as a daemon every 60 seconds, and that duration can be overridden
175
+ by setting a `SLEEP_TIME` environment variable to the number of seconds you'd like
176
+ to wait between runs
177
+ 4. If you'd rather run `feed2gram` as ad hoc as opposed to via the included daemon
178
+ (presumably to handle scheduling it yourself), simply change the entrypoint to
179
+ `/srv/exe/feed2gram`
180
+
181
+ ### Running the docker image specifically on your Synology NAS
182
+
183
+ I run this on my [Synology DS 920+](https://www.pcmag.com/reviews/synology-diskstation-ds920-plus), using the [DSM's Container Manager](https://www.synology.com/en-global/dsm/feature/container-manager) app.
184
+
185
+ There are just a few things to know to set this up:
186
+
187
+ At the time of this writing, the `Action > Import > Add from URL` feature of the Container Manager's
188
+ "Image" tab does not support GitHub Container Registry URLs. However, if you connect [via SSH](https://kb.synology.com/en-my/DSM/tutorial/How_to_login_to_DSM_with_root_permission_via_SSH_Telnet):
189
+
190
+ ```
191
+ $ sudo -s
192
+ # Enter your user password.
193
+ $ docker pull ghcr.io/searls/feed2gram:latest
194
+ ```
195
+
196
+ Once downloaded, the image will appear in the app. From there, select
197
+ `ghcr.io/searls/feed2gram`, hit Run, and complete the wizard, setting any custom
198
+ command line flags (once the container is created, this cannot be edited), as
199
+ well as choosing a location to mount the `/config` volume and setting a
200
+ `SLEEP_TIME` environment variable (these can be changed after the fact).
201
+
150
202
  ## Frequently Asked Questions
151
203
 
152
204
  ### Why didn't my post show up?
@@ -166,3 +218,4 @@ The submitted image with aspect ratio ('719/194',) cannot be published. Please s
166
218
  It means your photo is too avant garde for a mainstream normie platform like
167
219
  Instagram. Make sure all images' aspect ratiosa re between 4:5 and 1.91:1 or
168
220
  else the post will fail.
221
+
@@ -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.4"
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.4
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-07 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.21
76
77
  signing_key:
77
78
  specification_version: 4
78
79
  summary: Reads an Atom feed and posts its entries to Instagram