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 +4 -4
- data/CHANGELOG.md +10 -0
- data/Dockerfile +17 -0
- data/README.md +55 -2
- data/lib/feed2gram/loads_cache.rb +5 -3
- data/lib/feed2gram/loads_config.rb +3 -2
- data/lib/feed2gram/parses_options.rb +5 -1
- data/lib/feed2gram/publishes_posts.rb +7 -2
- data/lib/feed2gram/refreshes_token.rb +4 -2
- data/lib/feed2gram/updates_cache.rb +3 -2
- data/lib/feed2gram/version.rb +1 -1
- data/lib/feed2gram.rb +11 -7
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c053ce1eafec6ed200dc7ed549fb25e7bc22fcd78fa6b92c773673d47aeba056
|
4
|
+
data.tar.gz: 767a90bd8534d43f0e9bef8ef73e8e577bd959ade67ebabe639ac96d6bde4828
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
#
|
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
|
-
$
|
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(
|
10
|
-
if File.exist?(cache_path)
|
11
|
-
|
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(
|
21
|
-
|
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,
|
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(
|
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,
|
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
|
-
|
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,
|
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
|
-
|
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
|
data/lib/feed2gram/version.rb
CHANGED
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
|
22
|
-
RefreshesToken.new.refresh!(config, options
|
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
|
25
|
-
|
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
|
-
|
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
|
33
|
+
PublishesPosts.new.publish(posts, config, options)
|
30
34
|
end
|
31
|
-
UpdatesCache.new.update!(cache, results, options
|
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.
|
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-
|
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.
|
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
|