jekyll-webmention_io 2.0.1
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 +7 -0
- data/assets/webmention_io.js +549 -0
- data/lib/jekyll/commands/webmention.rb +48 -0
- data/lib/jekyll/generators/gather_webmentions.rb +239 -0
- data/lib/jekyll/generators/queue_webmentions.rb +50 -0
- data/lib/jekyll/tags/_.rb +132 -0
- data/lib/jekyll/tags/count.rb +26 -0
- data/lib/jekyll/tags/likes.rb +27 -0
- data/lib/jekyll/tags/links.rb +27 -0
- data/lib/jekyll/tags/posts.rb +27 -0
- data/lib/jekyll/tags/replies.rb +27 -0
- data/lib/jekyll/tags/reposts.rb +27 -0
- data/lib/jekyll/tags/webmentions.rb +23 -0
- data/lib/jekyll/webmention_io/version.rb +3 -0
- data/lib/jekyll/webmention_io.rb +207 -0
- data/lib/jekyll-webmention_io.rb +1 -0
- data/templates/count.html +1 -0
- data/templates/likes.html +17 -0
- data/templates/links.html +24 -0
- data/templates/posts.html +18 -0
- data/templates/replies.html +29 -0
- data/templates/reposts.html +17 -0
- data/templates/webmentions.html +48 -0
- metadata +152 -0
@@ -0,0 +1,48 @@
|
|
1
|
+
module Jekyll
|
2
|
+
module Commands
|
3
|
+
class WebmentionCommand < Command
|
4
|
+
def self.init_with_program( prog )
|
5
|
+
prog.command(:webmention) do |c|
|
6
|
+
c.syntax 'webmention'
|
7
|
+
c.description 'Sends queued webmentions'
|
8
|
+
|
9
|
+
c.action { |args, options| process args, options }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.process( args=[], options={} )
|
14
|
+
cached_outgoing = WebmentionIO.get_cache_file_path 'outgoing'
|
15
|
+
cached_sent = WebmentionIO.get_cache_file_path 'sent'
|
16
|
+
if File.exists?(cached_outgoing)
|
17
|
+
if File.exists?(cached_sent)
|
18
|
+
sent = open(cached_sent) { |f| YAML.load(f) }
|
19
|
+
else
|
20
|
+
sent = {}
|
21
|
+
end # file exists (sent)
|
22
|
+
outgoing = open(cached_outgoing) { |f| YAML.load(f) }
|
23
|
+
outgoing.each_pair do |source, targets|
|
24
|
+
if ! sent[source] or ! sent[source].kind_of? Array
|
25
|
+
sent[source] = Array.new
|
26
|
+
end
|
27
|
+
targets.each do |target|
|
28
|
+
if target and ! sent[source].find_index( target )
|
29
|
+
if target.index( "//" ) == 0
|
30
|
+
target = "http:#{target}"
|
31
|
+
end
|
32
|
+
endpoint = WebmentionIO.get_webmention_endpoint( target )
|
33
|
+
if endpoint
|
34
|
+
endpoint.scan(/href="([^"]+)"/) do |endpoint_url|
|
35
|
+
endpoint_url = endpoint_url[0]
|
36
|
+
WebmentionIO.webmention( source, target, endpoint )
|
37
|
+
end
|
38
|
+
sent[source].push( target )
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
File.open(cached_sent, 'w') { |f| YAML.dump(sent, f) }
|
44
|
+
end # file exists (outgoing)
|
45
|
+
end # def process
|
46
|
+
end # WebmentionCommand
|
47
|
+
end # Commands
|
48
|
+
end # Jekyll
|
@@ -0,0 +1,239 @@
|
|
1
|
+
# (c) Aaron Gustafson
|
2
|
+
# https://github.com/aarongustafson/jekyll-webmention_io
|
3
|
+
# Licence : MIT
|
4
|
+
#
|
5
|
+
# This generator gathers webmentions of your pages
|
6
|
+
#
|
7
|
+
|
8
|
+
module Jekyll
|
9
|
+
class GatherWebmentions < Generator
|
10
|
+
|
11
|
+
safe true
|
12
|
+
priority :high
|
13
|
+
|
14
|
+
def generate(site)
|
15
|
+
WebmentionIO.log 'info', 'Beginning to gather webmentions of your posts. This may take a while.'
|
16
|
+
|
17
|
+
WebmentionIO.set_api_endpoint('mentions')
|
18
|
+
# add an arbitrarily high perPage to trump pagination
|
19
|
+
WebmentionIO.set_api_suffix('&perPage=9999')
|
20
|
+
|
21
|
+
cache_file = WebmentionIO.get_cache_file_path 'incoming'
|
22
|
+
if File.exists?(cache_file)
|
23
|
+
@cached_webmentions = open(cache_file) { |f| YAML.load(f) }
|
24
|
+
else
|
25
|
+
@cached_webmentions = {}
|
26
|
+
end
|
27
|
+
|
28
|
+
if Jekyll::VERSION >= "3.0.0"
|
29
|
+
posts = site.posts.docs
|
30
|
+
else
|
31
|
+
posts = site.posts
|
32
|
+
end
|
33
|
+
|
34
|
+
# post Jekyll commit 0c0aea3
|
35
|
+
# https://github.com/jekyll/jekyll/commit/0c0aea3ad7d2605325d420a23d21729c5cf7cf88
|
36
|
+
if defined? site.find_converter_instance
|
37
|
+
@converter = site.find_converter_instance(::Jekyll::Converters::Markdown)
|
38
|
+
# Prior to Jekyll commit 0c0aea3
|
39
|
+
else
|
40
|
+
@converter = site.getConverterImpl(::Jekyll::Converters::Markdown)
|
41
|
+
end
|
42
|
+
|
43
|
+
posts.each do |post|
|
44
|
+
# Gather the URLs
|
45
|
+
targets = get_webmention_target_urls(site, post)
|
46
|
+
|
47
|
+
# execute the API
|
48
|
+
api_params = targets.collect { |v| "target[]=#{v}" }.join('&')
|
49
|
+
response = WebmentionIO.get_response(api_params)
|
50
|
+
# @webmention_io.log 'info', response.inspect
|
51
|
+
|
52
|
+
process_webmentions( post.url, response )
|
53
|
+
end # posts loop
|
54
|
+
|
55
|
+
File.open(cache_file, 'w') { |f| YAML.dump(@cached_webmentions, f) }
|
56
|
+
|
57
|
+
WebmentionIO.log 'info', 'Webmentions have been gathered and cached.'
|
58
|
+
end # generate
|
59
|
+
|
60
|
+
def get_webmention_target_urls(site, post)
|
61
|
+
targets = []
|
62
|
+
uri = "#{site.config['url']}#{post.url}"
|
63
|
+
targets.push( uri )
|
64
|
+
|
65
|
+
# Redirection?
|
66
|
+
redirected = false
|
67
|
+
if post.data.has_key? 'redirect_from'
|
68
|
+
redirected = uri.sub post.url, post.data['redirect_from']
|
69
|
+
targets.push( redirected )
|
70
|
+
end
|
71
|
+
|
72
|
+
# Domain changed?
|
73
|
+
if WebmentionIO.config.has_key? 'legacy_domains'
|
74
|
+
# WebmentionIO.log 'info', 'adding legacy URIs'
|
75
|
+
WebmentionIO.config['legacy_domains'].each do |domain|
|
76
|
+
legacy = uri.sub site.config['url'], domain
|
77
|
+
# WebmentionIO.log 'info', "adding URI #{legacy}"
|
78
|
+
targets.push(legacy)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
return targets
|
82
|
+
end
|
83
|
+
|
84
|
+
def markdownify( string )
|
85
|
+
string = @converter.convert("#{string}")
|
86
|
+
if ! string.start_with?('<p')
|
87
|
+
string = string.sub(/^<[^>]+>/, '<p>').sub(/<\/[^>]+>$/, '</p>')
|
88
|
+
end
|
89
|
+
string.strip
|
90
|
+
end
|
91
|
+
|
92
|
+
def process_webmentions( post_uri, response )
|
93
|
+
|
94
|
+
# Get cached webmentions
|
95
|
+
if @cached_webmentions.has_key? post_uri
|
96
|
+
webmentions = @cached_webmentions[post_uri]
|
97
|
+
else
|
98
|
+
webmentions = {}
|
99
|
+
end
|
100
|
+
|
101
|
+
if response and response['links']
|
102
|
+
|
103
|
+
response['links'].reverse_each do |link|
|
104
|
+
|
105
|
+
uri = link['data']['url'] || link['source']
|
106
|
+
|
107
|
+
# set the source
|
108
|
+
source = false
|
109
|
+
if uri.include? 'twitter.com/'
|
110
|
+
source = 'twitter'
|
111
|
+
elsif uri.include? '/googleplus/'
|
112
|
+
source = 'googleplus'
|
113
|
+
end
|
114
|
+
|
115
|
+
# set an id
|
116
|
+
id = link['id'].to_s
|
117
|
+
if source == 'twitter' and ! uri.include? '#favorited-by'
|
118
|
+
id = URI(uri).path.split('/').last.to_s
|
119
|
+
end
|
120
|
+
if ! id
|
121
|
+
time = Time.now();
|
122
|
+
id = time.strftime('%s').to_s
|
123
|
+
end
|
124
|
+
|
125
|
+
# Do we already have it?
|
126
|
+
if webmentions.has_key? id
|
127
|
+
next
|
128
|
+
end
|
129
|
+
|
130
|
+
# Get the mentioned URI, stripping fragments and query strings
|
131
|
+
#target = URI::parse( link['target'] )
|
132
|
+
#target.fragment = target.query = nil
|
133
|
+
#target = target.to_s
|
134
|
+
|
135
|
+
pubdate = link['data']['published_ts']
|
136
|
+
if pubdate
|
137
|
+
pubdate = Time.at(pubdate)
|
138
|
+
elsif link['verified_date']
|
139
|
+
pubdate = Time.parse(link['verified_date'])
|
140
|
+
end
|
141
|
+
#the_date = pubdate.strftime('%s')
|
142
|
+
|
143
|
+
# Make sure we have the date
|
144
|
+
# if ! webmentions.has_key? the_date
|
145
|
+
# webmentions[the_date] = {}
|
146
|
+
# end
|
147
|
+
|
148
|
+
# Make sure we have the webmention
|
149
|
+
if ! webmentions.has_key? id
|
150
|
+
|
151
|
+
# Scaffold the webmention
|
152
|
+
webmention = {
|
153
|
+
'id' => id,
|
154
|
+
'url' => uri,
|
155
|
+
'source' => source,
|
156
|
+
'pubdate' => pubdate,
|
157
|
+
'raw' => link
|
158
|
+
}
|
159
|
+
|
160
|
+
# Set the author
|
161
|
+
if link['data'].has_key? 'author'
|
162
|
+
webmention['author'] = link['data']['author']
|
163
|
+
end
|
164
|
+
|
165
|
+
# Set the type
|
166
|
+
type = link['activity']['type']
|
167
|
+
if ! type
|
168
|
+
if source == 'googleplus'
|
169
|
+
if uri.include? '/like/'
|
170
|
+
type = 'like'
|
171
|
+
elsif uri.include? '/repost/'
|
172
|
+
type = 'repost'
|
173
|
+
elsif uri.include? '/comment/'
|
174
|
+
type = 'reply'
|
175
|
+
else
|
176
|
+
type = 'link'
|
177
|
+
end
|
178
|
+
else
|
179
|
+
type = 'post'
|
180
|
+
end
|
181
|
+
end # if no type
|
182
|
+
webmention['type'] = type
|
183
|
+
|
184
|
+
# Posts
|
185
|
+
title = false
|
186
|
+
if type == 'post'
|
187
|
+
|
188
|
+
html_source = WebmentionIO.get_uri_source( uri )
|
189
|
+
if ! html_source
|
190
|
+
next
|
191
|
+
end
|
192
|
+
|
193
|
+
if ! html_source.valid_encoding?
|
194
|
+
html_source = html_source.encode('UTF-16be', :invalid=>:replace, :replace=>"?").encode('UTF-8')
|
195
|
+
end
|
196
|
+
|
197
|
+
# Check the `title` first
|
198
|
+
matches = /<title>(.*)<\/title>/.match( html_source )
|
199
|
+
if matches
|
200
|
+
title = matches[1].strip
|
201
|
+
else
|
202
|
+
# Fall back to the first `h1`
|
203
|
+
matches = /<h1>(.*)<\/h1>/.match( html_source )
|
204
|
+
if matches
|
205
|
+
title = matches[1].strip
|
206
|
+
else
|
207
|
+
# No title found
|
208
|
+
title = 'No title available'
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
# cleanup
|
213
|
+
title = title.gsub(%r{</?[^>]+?>}, '')
|
214
|
+
end # if no title
|
215
|
+
webmention['title'] = markdownify( title )
|
216
|
+
|
217
|
+
# Everything else
|
218
|
+
content = link['data']['content']
|
219
|
+
if type != 'post' && type != 'reply' && type != 'link'
|
220
|
+
content = link['activity']['sentence_html']
|
221
|
+
end
|
222
|
+
webmention['content'] = markdownify( content )
|
223
|
+
|
224
|
+
# Add it to the list
|
225
|
+
# @webmention_io.log 'info', webmention.inspect
|
226
|
+
webmentions[id] = webmention
|
227
|
+
|
228
|
+
end # if ID does not exist
|
229
|
+
|
230
|
+
end # each link
|
231
|
+
|
232
|
+
end # if response
|
233
|
+
|
234
|
+
@cached_webmentions[post_uri] = webmentions
|
235
|
+
|
236
|
+
end # process_webmentions
|
237
|
+
|
238
|
+
end
|
239
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# (c) Aaron Gustafson
|
2
|
+
# https://github.com/aarongustafson/jekyll-webmention_io
|
3
|
+
# Licence : MIT
|
4
|
+
#
|
5
|
+
# This generator caches sites you mention so they can be mentioned
|
6
|
+
#
|
7
|
+
|
8
|
+
module Jekyll
|
9
|
+
class QueueWebmentions < Generator
|
10
|
+
|
11
|
+
safe true
|
12
|
+
priority :low
|
13
|
+
|
14
|
+
def generate(site)
|
15
|
+
WebmentionIO.log 'info', 'Beginning to gather webmentions you’ve made. This may take a while.'
|
16
|
+
|
17
|
+
webmentions = {}
|
18
|
+
|
19
|
+
if Jekyll::VERSION >= "3.0.0"
|
20
|
+
posts = site.posts.docs
|
21
|
+
else
|
22
|
+
posts = site.posts
|
23
|
+
end
|
24
|
+
|
25
|
+
posts.each do |post|
|
26
|
+
uri = "#{site.config['url']}#{post.url}"
|
27
|
+
webmentions[uri] = get_mentioned_uris(post)
|
28
|
+
end
|
29
|
+
|
30
|
+
cache_file = WebmentionIO.get_cache_file_path 'outgoing'
|
31
|
+
File.open(cache_file, 'w') { |f| YAML.dump(webmentions, f) }
|
32
|
+
|
33
|
+
WebmentionIO.log 'info', 'Webmentions have been gathered and cached.'
|
34
|
+
end
|
35
|
+
|
36
|
+
def get_mentioned_uris(post)
|
37
|
+
uris = []
|
38
|
+
if post.data['in_reply_to']
|
39
|
+
uris.push(post.data['in_reply_to'])
|
40
|
+
end
|
41
|
+
post.content.scan(/(?:https?:)?\/\/[^\s)#"]+/) do |match|
|
42
|
+
if ! uris.find_index( match )
|
43
|
+
uris.push(match)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
return uris
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# (c) Aaron Gustafson
|
2
|
+
# https://github.com/aarongustafson/jekyll-webmention_io
|
3
|
+
# Licence : MIT
|
4
|
+
#
|
5
|
+
# Base webmention tag
|
6
|
+
#
|
7
|
+
|
8
|
+
require 'htmlbeautifier'
|
9
|
+
|
10
|
+
module Jekyll
|
11
|
+
using StringInflection
|
12
|
+
class WebmentionTag < Liquid::Tag
|
13
|
+
|
14
|
+
def initialize(tagName, text, tokens)
|
15
|
+
super
|
16
|
+
cache_file = WebmentionIO.get_cache_file_path 'incoming'
|
17
|
+
if File.exists?(cache_file)
|
18
|
+
@cached_webmentions = open(cache_file) { |f| YAML.load(f) }
|
19
|
+
else
|
20
|
+
@cached_webmentions = {}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def lookup(context, name)
|
25
|
+
lookup = context
|
26
|
+
name.split(".").each do |value|
|
27
|
+
lookup = lookup[value]
|
28
|
+
end
|
29
|
+
lookup
|
30
|
+
end
|
31
|
+
|
32
|
+
def set_template( template )
|
33
|
+
supported_templates = WebmentionIO.types + ['count', 'webmentions']
|
34
|
+
|
35
|
+
WebmentionIO.log 'error', "#{template} is not supported" if ! supported_templates.include? template
|
36
|
+
|
37
|
+
if WebmentionIO.config.has_key? 'templates' and WebmentionIO.config['templates'].has_key? template
|
38
|
+
# WebmentionIO.log 'info', "Using custom #{template} template"
|
39
|
+
template_file = WebmentionIO.config['templates'][template]
|
40
|
+
else
|
41
|
+
# WebmentionIO.log 'info', "Using default #{template} template"
|
42
|
+
template_file = File.join(File.dirname(File.expand_path(__FILE__)), "../../../templates/#{template}.html")
|
43
|
+
end
|
44
|
+
|
45
|
+
# WebmentionIO.log 'info', "Template file: #{template_file}"
|
46
|
+
handler = File.open(template_file, 'rb')
|
47
|
+
@template = handler.read
|
48
|
+
# WebmentionIO.log 'info', "template: #{@template}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def set_data(data)
|
52
|
+
@data = { 'webmentions' => data }
|
53
|
+
end
|
54
|
+
|
55
|
+
def extract_type( type, webmentions )
|
56
|
+
# WebmentionIO.log 'info', "Looking for #{type}"
|
57
|
+
keep = {}
|
58
|
+
if ! WebmentionIO.types.include? type
|
59
|
+
WebmentionIO.log 'warn', "#{type} are not extractable"
|
60
|
+
else
|
61
|
+
type = type.to_singular
|
62
|
+
# WebmentionIO.log 'info', "Searching #{webmentions.length} webmentions for type==#{type}"
|
63
|
+
if webmentions.is_a? Hash
|
64
|
+
webmentions = webmentions.values
|
65
|
+
end
|
66
|
+
webmentions.each do |webmention|
|
67
|
+
keep[webmention['id']] = webmention if webmention['type'] == type
|
68
|
+
end
|
69
|
+
end
|
70
|
+
keep
|
71
|
+
end
|
72
|
+
|
73
|
+
def sort_webmentions( webmentions )
|
74
|
+
return webmentions.sort_by { |webmention| webmention['pubdate'].to_i }
|
75
|
+
end
|
76
|
+
|
77
|
+
def render(context)
|
78
|
+
output = super
|
79
|
+
|
80
|
+
# Get the URI
|
81
|
+
args = @text.split(/\s+/).map(&:strip)
|
82
|
+
uri = args.shift
|
83
|
+
uri = lookup(context, uri)
|
84
|
+
|
85
|
+
if @cached_webmentions.has_key? uri
|
86
|
+
all_webmentions = @cached_webmentions[uri].clone
|
87
|
+
# WebmentionIO.log 'info', "#{all_webmentions.length} total webmentions for #{uri}"
|
88
|
+
if args.length > 0
|
89
|
+
# WebmentionIO.log 'info', "Requesting only #{args.inspect}"
|
90
|
+
webmentions = {}
|
91
|
+
args.each do |type|
|
92
|
+
extracted = extract_type( type, all_webmentions )
|
93
|
+
# WebmentionIO.log 'info', "Merging in #{extracted.length} #{type}"
|
94
|
+
webmentions = webmentions.merge( extracted )
|
95
|
+
end
|
96
|
+
else
|
97
|
+
# WebmentionIO.log 'info', 'Grabbing all webmentions'
|
98
|
+
webmentions = all_webmentions
|
99
|
+
end
|
100
|
+
|
101
|
+
if webmentions.is_a? Hash
|
102
|
+
webmentions = webmentions.values
|
103
|
+
end
|
104
|
+
|
105
|
+
webmentions = sort_webmentions( webmentions )
|
106
|
+
|
107
|
+
set_data( webmentions )
|
108
|
+
end
|
109
|
+
|
110
|
+
args = nil
|
111
|
+
|
112
|
+
if @template and @data
|
113
|
+
# WebmentionIO.log 'info', "Preparing to render\n\n#{@data.inspect}\n\ninto\n\n#{@template}"
|
114
|
+
template = Liquid::Template.parse(@template, :error_mode => :strict)
|
115
|
+
html = template.render(@data, { strict_variables: true, strict_filters: true })
|
116
|
+
template.errors.each do |error|
|
117
|
+
WebmentionIO.log 'error', error
|
118
|
+
end
|
119
|
+
# Clean up the output
|
120
|
+
HtmlBeautifier.beautify html.each_line.reject{|x| x.strip == ""}.join
|
121
|
+
else
|
122
|
+
if ! @template
|
123
|
+
WebmentionIO.log 'warn', "#{self.class} No template provided"
|
124
|
+
end
|
125
|
+
if ! @data
|
126
|
+
WebmentionIO.log 'warn', "#{self.class} No data provided"
|
127
|
+
end
|
128
|
+
""
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|