mastodon-social 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 73810aa00a8d27299a5c9446c3cc0a239d5b0076a104688255bac6d70ee203a4
4
+ data.tar.gz: dde89d4d59683de3b0c971dce6139cab466135dbb2c77f4d6265d0422b5468e8
5
+ SHA512:
6
+ metadata.gz: 656f21b8a566eac6f97802ad179521a05710ff071abcfd2066b778821f170362b2ca342ab7cbda02e65847d47dc046aac23cf35eea1fb323ae5209d32d878b7e
7
+ data.tar.gz: 43587680e329dc2b0fd6ed335fd70d8b39a65091109211af7fb0c5c29a13cfa7f050fcaaa6be46ad9957335e06672e50150348397eb4ead81939b42425967e2d
data/.rubocop.yml ADDED
@@ -0,0 +1,13 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.6
3
+
4
+ Style/StringLiterals:
5
+ Enabled: true
6
+ EnforcedStyle: double_quotes
7
+
8
+ Style/StringLiteralsInInterpolation:
9
+ Enabled: true
10
+ EnforcedStyle: double_quotes
11
+
12
+ Layout/LineLength:
13
+ Max: 120
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in mastodon-social.gemspec
6
+ gemspec
7
+
8
+ # We require the latest mastodon-api, since it updated its http dependency to 4.0, but it hasn't been published
9
+ gem "mastodon-api", :git => "git://github.com/mastodon/mastodon-api"
10
+
11
+ gem "rake", "~> 13.0"
12
+
13
+ gem "rubocop", "~> 1.21"
14
+ gem "debug"
data/Gemfile.lock ADDED
@@ -0,0 +1,51 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ mastodon-social (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ ast (2.4.2)
10
+ debug (1.7.1)
11
+ irb (>= 1.5.0)
12
+ reline (>= 0.3.1)
13
+ io-console (0.6.0)
14
+ irb (1.6.2)
15
+ reline (>= 0.3.0)
16
+ json (2.6.3)
17
+ parallel (1.22.1)
18
+ parser (3.2.0.0)
19
+ ast (~> 2.4.1)
20
+ rainbow (3.1.1)
21
+ rake (13.0.6)
22
+ regexp_parser (2.6.1)
23
+ reline (0.3.2)
24
+ io-console (~> 0.5)
25
+ rexml (3.2.5)
26
+ rubocop (1.43.0)
27
+ json (~> 2.3)
28
+ parallel (~> 1.10)
29
+ parser (>= 3.2.0.0)
30
+ rainbow (>= 2.2.2, < 4.0)
31
+ regexp_parser (>= 1.8, < 3.0)
32
+ rexml (>= 3.2.5, < 4.0)
33
+ rubocop-ast (>= 1.24.1, < 2.0)
34
+ ruby-progressbar (~> 1.7)
35
+ unicode-display_width (>= 2.4.0, < 3.0)
36
+ rubocop-ast (1.24.1)
37
+ parser (>= 3.1.1.0)
38
+ ruby-progressbar (1.11.0)
39
+ unicode-display_width (2.4.2)
40
+
41
+ PLATFORMS
42
+ x86_64-darwin-21
43
+
44
+ DEPENDENCIES
45
+ debug
46
+ mastodon-social!
47
+ rake (~> 13.0)
48
+ rubocop (~> 1.21)
49
+
50
+ BUNDLED WITH
51
+ 2.3.15
data/README.md ADDED
@@ -0,0 +1,151 @@
1
+ # Mastodon::Social
2
+
3
+ A Jekyll plugin to create links to Mastodon for your posts.
4
+
5
+ ## Installation
6
+
7
+ In your `Gemfile` add:
8
+ ```ruby
9
+ group :jekyll_plugins do
10
+ # (other jekyll plugins)
11
+ gem 'mastodon-api'
12
+ end
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ The plugin will add a new `mastodon` command to your jekyll setup:
18
+ ```sh
19
+ $ be jekyll help
20
+ jekyll 3.9.2 -- Jekyll is a blog-aware, static site generator in Ruby
21
+
22
+ Usage:
23
+
24
+ jekyll <subcommand> [options]
25
+
26
+ ...
27
+
28
+ Subcommands:
29
+ ...
30
+ mastodon Connect your blog with the Fediverse
31
+ $
32
+ ```
33
+
34
+ Running `jekyll mastodon help` shows the subcommands:
35
+ ```sh
36
+ jekyll mastodon [command]
37
+
38
+ Commands:
39
+ setup: Initial setup authorizing this plugin to post to your account
40
+ authorize: Enter your password to authorize this plugin to post to your Mastodon account
41
+ mark: Mark all posts as published (to Mastodon)
42
+ post: Publish any new posts to Mastodon
43
+ clean: Erase all cached mastodon data
44
+ ```
45
+
46
+ To use the plugin, you need to add a Mastodon tag somewhere in your page layout. Then use the `setup` and `authorize`
47
+ commands to link the plugin to your Mastodon account. Those are all one-time actions.
48
+
49
+ Whenever you rebuild your blog, you will then need to run the `post` command to send a new status post to Mastodon
50
+ with a link to your new entry.
51
+
52
+ ### Mastodon Link
53
+
54
+ The plugin creates a new tag called `{% mastodon_social %}` You can add that tag to any page and it will create
55
+ a link to the Mastodon status post for this page (assuming it exists). To put a text anchor for the link, add text in
56
+ the block, like so:
57
+ ```html
58
+ {% mastodon_social %}
59
+ Boost this on Mastodon!
60
+ {% end_mastodon_social %}
61
+ ```
62
+
63
+ ### Setup and Authorization
64
+
65
+ Next, you need to tell the plugin which Mastodon instance you use as your home instance. It will connect there
66
+ to make the new status posts. In your `_config.yml` file, add this section:
67
+
68
+ ```yaml
69
+ mastodon-syndication:
70
+ server: https://your.mastodon-server.org
71
+ ```
72
+
73
+ Once you have the gem installed and your config set up, run:
74
+ ```
75
+ jekyll mastodon setup
76
+ ```
77
+ This establishes the plugin as an application on your Mastodon instance.
78
+
79
+ Next, run:
80
+ ```
81
+ jekyll mastodon authorize
82
+ ```
83
+ This will prompt you for your Mastodon password, to authorize the plugin to post on your behalf. The plugin does
84
+ not save your password anywhere; however, it does save the token it uses to login. It's saved in
85
+ `.jekyll-cache/mastodon.yml`. You should protect this; in theory anyone who has that token can use it to post
86
+ mastodon toots as you. You can see the token by loggin in to to Mastodon, then going to
87
+ Preferences > Account > Authorized Apps. You can revoke the token if it is compromised, but then you will need
88
+ to redo the setup and authorization steps again. As long as you don't revoke the token, it's permanent and
89
+ you don't need to run the setup or authorization again.
90
+
91
+ ### Posting to Mastodon
92
+
93
+ There are three commands used to sync your blog with mastodon: mark, clear, and post. For the most part, you
94
+ will only use post.
95
+
96
+ ```
97
+ jekyll mastodon post
98
+ ```
99
+ will sync your mastodon account and your blog. The plugin keeps track of every post that's been sent to Mastodon.
100
+ When you issue the `post` command, it checks for new posts on your blog and makes a toot for each one. That means
101
+ you can run `post` again and if you haven't made any new posts since last time, it won't do anything.
102
+
103
+ ```
104
+ jekyll mastodon clear
105
+ ```
106
+ This will cause the plugin to forget all the Mastodon posts its made. The next time you run `post` it will send
107
+ everything. Unless your blog is new you probably don't want to do this.
108
+
109
+ ```
110
+ jekyll mastodon mark
111
+ ```
112
+ This will mark every post on your blog as having been sent. If you have a lot of posts on your blog you will want
113
+ to do this before your very first sync; otherwise, the plugin will sent a toot for every one of your old posts (and
114
+ probably hit a rate limit).
115
+
116
+ ### Chicken and Egg Problem
117
+
118
+ There's one issue with posting to Mastodon. When you have a link in your toot like the ones the plugin creates,
119
+ Mastodon will visit the link to create a Link Card. This is a nicely-formatted card with things like a title,
120
+ description, and even an image if you have the Open Graph metadata in your post. However, Mastodon can't generate
121
+ the card if your post doesn't exist yet (obviously).
122
+
123
+ This leads to a chicken-and-egg problem. The jekyll plugin that renders links to your mastodon post won't render
124
+ properly if the post isn't there on mastodon, but the Mastodon post won't have its Link Card if the post isn't
125
+ there yet. Each one needs the other to exist.
126
+
127
+ If you aren't using the `mastodon_social` tag in your layout, this isn't a problem. You can post a link
128
+ every time you build your blog by adding one step into your normal posting routine:
129
+
130
+ 1. Create a post and build your blog with `jekyll build`
131
+ 2. Make your new post visible online.
132
+ 3. Post it to Mastodon with `jekyll mastodon post`
133
+
134
+ However, if you're using the `mastodon_social` tag to create links to your mastodon toot (so people can click it
135
+ and boost your post) then you'll need to build your blog *twice*:
136
+
137
+ 1. Create a post and build your blog with `jekyll build`
138
+ 2. Make your new post visible online (so the link card will render properly)
139
+ 3. Post it to Mastodon with `jekyll mastodon post`
140
+ 4. Build your blog again with `jekyll build`. This build will have the proper link.
141
+ 5. Make the new build visible.
142
+
143
+ ## Development
144
+
145
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
146
+
147
+ 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).
148
+
149
+ ## Contributing
150
+
151
+ Bug reports and pull requests are welcome on GitHub at https://github.com/reiterate-app/mastodon-social.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rubocop/rake_task"
5
+
6
+ RuboCop::RakeTask.new
7
+
8
+ task default: :rubocop
@@ -0,0 +1,132 @@
1
+ require 'mastodon'
2
+ require 'io/console'
3
+ require 'net/http'
4
+ require 'uri'
5
+ require 'json'
6
+ require 'debug'
7
+
8
+ module Jekyll
9
+ module MastodonSocial
10
+ class MastodonSetup < Jekyll::Command
11
+ class << self
12
+ def init_with_program(prog)
13
+ prog.command(:mastodon) do |c|
14
+ c.syntax('mastodon')
15
+ c.description("Connect your blog with the Fediverse")
16
+ c.action do |args, opts|
17
+ if args.empty?
18
+ do_help
19
+ else
20
+ case args[0]
21
+ when /setup/
22
+ create_client_id opts
23
+ when /mark/
24
+ mark opts
25
+ when /post/
26
+ post opts
27
+ when /clean/
28
+ clean
29
+ when /help/
30
+ do_help
31
+ when /auth/
32
+ get_token
33
+ else
34
+ puts "Error, try 'mastodon help'"
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ def create_client_id(options = {})
42
+ client = get_client()
43
+ app = client.create_app("jekyll-social", MastodonSocial.site.config["url"], 'read write')
44
+ MastodonSocial.client_id = app.client_id
45
+ MastodonSocial.client_secret = app.client_secret
46
+ MastodonSocial.save_config()
47
+ end
48
+
49
+ def get_token
50
+ puts "Enter your Mastodon credentials for the account you want to post to"
51
+ print "Account email: "
52
+ email = STDIN.cooked(&:gets).chomp
53
+ print "Account password: "
54
+ password = STDIN.noecho(&:gets).chomp
55
+
56
+ client = get_client()
57
+ auth_uri = URI.parse("#{MastodonSocial.config['server']}/oauth/token")
58
+ Net::HTTP.start(auth_uri.host, auth_uri.port, :use_ssl => auth_uri.scheme == 'https') do |http|
59
+ request = Net::HTTP::Post.new(auth_uri, 'Content-Type' => 'application/json')
60
+ request.body = {
61
+ 'client_id': MastodonSocial.client_id,
62
+ 'client_secret': MastodonSocial.client_secret,
63
+ 'grant_type': 'password',
64
+ 'username': email,
65
+ 'password': password,
66
+ 'scope': 'read write'
67
+ }.to_json
68
+ response = http.request request
69
+ json_response = JSON.parse(response.body)
70
+ MastodonSocial.bearer_token = json_response['access_token']
71
+ end
72
+ MastodonSocial.save_config()
73
+ end
74
+
75
+ def mark(options = {})
76
+ client = get_client()
77
+ # Scan through all posts and see which ones need to be posted to Mastodon
78
+ for post_url in MastodonSocial.mastodon_status.keys
79
+ MastodonSocial.mark_as_published(post_url, true)
80
+ end
81
+ MastodonSocial.save_config()
82
+ end
83
+
84
+ def clean
85
+ client = get_client()
86
+ MastodonSocial.clear_status()
87
+ MastodonSocial.save_config()
88
+ end
89
+
90
+ def get_client(options = {})
91
+ options = configuration_from_options(options)
92
+ site = Jekyll::Site.new(options)
93
+ MastodonSocial.setup(site)
94
+ return Mastodon::REST::Client.new(base_url: MastodonSocial.config["server"], bearer_token: MastodonSocial.bearer_token)
95
+ end
96
+
97
+ # Look for any blog posts that haven't been sent to Mastodon, and post a status for each
98
+ def post(options = {})
99
+ client = get_client()
100
+ for post_url, status in MastodonSocial.mastodon_status
101
+ next if status[:mastodon_status]
102
+ puts "Publishing #{post_url} to Mastodon"
103
+ msg_text = <<~ENDMSG
104
+ #{status[:excerpt].gsub("\n", '')}
105
+
106
+ #{MastodonSocial.site.config["url"]}#{post_url}
107
+ ENDMSG
108
+ msg_text += ("\n#" + status[:hashtags].join(' #')) if status[:hashtags]
109
+ status_result = client.create_status(msg_text)
110
+ new_status = {id: status_result.id, url: status_result.url}
111
+ MastodonSocial.mark_as_published(post_url, new_status)
112
+ end
113
+ MastodonSocial.save_config()
114
+ end
115
+
116
+ def do_help
117
+ puts <<-END_HELP
118
+ jekyll mastodon [command]
119
+
120
+ Commands:
121
+ setup: Initial setup authorizing this plugin to post to your account
122
+ authorize: Enter your password to authorize this plugin to post to your Mastodon account
123
+ mark: Mark all posts as published (to Mastodon)
124
+ post: Publish any new posts to Mastodon
125
+ clean: Erase all cached mastodon data
126
+ END_HELP
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
132
+
@@ -0,0 +1,16 @@
1
+ require "debug"
2
+
3
+ module Jekyll
4
+ module MastodonSocial
5
+ class GatherPosse < Generator
6
+ def generate(site)
7
+ MastodonSocial.setup(site)
8
+ for post in site.posts.docs
9
+ # If we've never processed this post, add it to the db as unpublished
10
+ MastodonSocial.mark_as_published(post, nil) unless MastodonSocial.mastodon_status[post.url]
11
+ end
12
+ MastodonSocial.save_config()
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,14 @@
1
+ require 'mastodon-social'
2
+
3
+ module Jekyll
4
+ class MastodonLinkTagBlock < Liquid::Block
5
+
6
+ def render(context)
7
+ text = super
8
+ url = MastodonSocial.syndication_info(context["page"]["url"])
9
+ "<a href='#{url}'>#{text}</a>"
10
+ end
11
+ end
12
+ end
13
+
14
+ Liquid::Template.register_tag('mastodon_social', Jekyll::MastodonLinkTagBlock)
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "jekyll"
4
+ require_relative "version"
5
+ require 'nokogiri'
6
+
7
+ module Jekyll
8
+ module MastodonSocial
9
+ class Error < StandardError; end
10
+
11
+ class << self
12
+
13
+ attr_accessor :client_id, :client_secret, :bearer_token
14
+ attr_reader :config, :site, :mastodon_status
15
+
16
+ # Load state from cache dir. Setup cache dir if it doesn't exist
17
+ def setup(site)
18
+ @site = site
19
+ @jekyll_config = site.config
20
+ @config = @jekyll_config["mastodon-syndication"] || {}
21
+
22
+ # Set up the cache folder & files
23
+ setup_config
24
+ cache_data = YAML.load_file(@mastodon_cachefile)
25
+ @mastodon_status = cache_data[:posts]
26
+ @bearer_token = cache_data[:bearer_token]
27
+ @client_id = cache_data[:client_id]
28
+ @client_secret = cache_data[:client_secret]
29
+
30
+ @mastodon_client = Mastodon::REST::Client.new(base_url: @config["server"],
31
+ bearer_token: @bearer_token)
32
+ end
33
+
34
+ def setup_config
35
+ @cache_folder = @site.in_source_dir(@config["cache-folder"] || ".jekyll-cache")
36
+ unless File.exist?(@cache_folder)
37
+ Dir.mkdir(@cache_folder)
38
+ end
39
+ file = Jekyll.sanitized_path(@cache_folder, "mastodon.yml")
40
+ File.open(file, "wb") { |f| f.puts YAML.dump(Hash.new) } unless File.exist? file
41
+ @mastodon_cachefile = file
42
+ end
43
+
44
+ def save_config
45
+ state = {
46
+ posts: @mastodon_status || {},
47
+ bearer_token: @bearer_token,
48
+ client_id: @client_id,
49
+ client_secret: @client_secret
50
+ }
51
+ File.open(@mastodon_cachefile, "wb") { |f|
52
+ f.puts YAML.dump(state)
53
+ }
54
+ end
55
+
56
+ # mastodon_status is one of:
57
+ # nil: this post has not been sent to mastodon
58
+ # true: This post has been sent as a status, but we have no url
59
+ # url: The Mastodon URL for the status linking this post
60
+ def mark_as_published(post, mastodon_status)
61
+ if post.kind_of? String
62
+ post_url = post
63
+ excerpt = ''
64
+ hashtags = nil
65
+ else
66
+ # Don't do anything with posts that are in _drafts
67
+ return if post.path.include? '_drafts'
68
+
69
+ post_url = post.url
70
+ excerpt_html = post.data['excerpt'].to_s
71
+ excerpt = Nokogiri::HTML(excerpt_html).text.strip
72
+ hashtags = post.data['hashtags']
73
+ hashtags = hashtags.split if hashtags.is_a? String
74
+ end
75
+ status = @mastodon_status[post_url]
76
+ if status.nil?
77
+ @mastodon_status[post_url] = {
78
+ mastodon_status: mastodon_status,
79
+ excerpt: excerpt,
80
+ hashtags: hashtags
81
+ }
82
+ else
83
+ status[:mastodon_status] = mastodon_status
84
+ @mastodon_status[post_url] = status
85
+ end
86
+ end
87
+
88
+ def clear_status
89
+ @mastodon_status = {}
90
+ end
91
+
92
+ def syndication_info(url)
93
+ status = @mastodon_status[url]
94
+ return nil unless (status.kind_of? Hash and status[:mastodon_status].kind_of? Hash)
95
+ status[:mastodon_status]
96
+ end
97
+ end
98
+
99
+ end
100
+ end
101
+
102
+ def require_all(group)
103
+ Dir[File.expand_path("#{group}/*.rb", __dir__)].each do |file|
104
+ require file
105
+ end
106
+ end
107
+
108
+ require_all "jekyll/commands"
109
+ require_all "jekyll/generators"
110
+ require_all "jekyll/tags"
111
+
112
+ Jekyll::Hooks.register :site, :after_init do |site|
113
+ Jekyll::MastodonSocial.setup(site)
114
+ end
data/lib/version.rb ADDED
@@ -0,0 +1,3 @@
1
+ module MastodonSocial
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,6 @@
1
+ module Mastodon
2
+ module Social
3
+ VERSION: String
4
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
+ end
6
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mastodon-social
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Michael Meckler
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2023-07-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: mastodon-api
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ description: Add mastodon social links
28
+ email:
29
+ - rattroupe@reiterate-app.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - ".rubocop.yml"
35
+ - Gemfile
36
+ - Gemfile.lock
37
+ - README.md
38
+ - Rakefile
39
+ - lib/jekyll/commands/mastodon-social.rb
40
+ - lib/jekyll/generators/gather_posse.rb
41
+ - lib/jekyll/tags/mastodon-link.rb
42
+ - lib/mastodon-social.rb
43
+ - lib/version.rb
44
+ - sig/mastodon-social.rbs
45
+ homepage:
46
+ licenses: []
47
+ metadata: {}
48
+ post_install_message:
49
+ rdoc_options: []
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: 2.6.0
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ requirements: []
63
+ rubygems_version: 3.4.10
64
+ signing_key:
65
+ specification_version: 4
66
+ summary: Create links for a Jekyll blog that allow reposting on Mastodon
67
+ test_files: []