opengraphplus 0.1.5 → 0.1.7
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/README.md +155 -1
- data/lib/generators/opengraphplus/base_generator.rb +21 -0
- data/lib/generators/opengraphplus/credentials/credentials_generator.rb +50 -0
- data/lib/generators/opengraphplus/credentials/templates/initializer.rb.tt +5 -0
- data/lib/generators/opengraphplus/env/env_generator.rb +61 -0
- data/lib/generators/opengraphplus/env/templates/initializer.rb.tt +5 -0
- data/lib/generators/opengraphplus/install/templates/README +2 -42
- data/lib/generators/opengraphplus/install/templates/initializer.rb +3 -1
- data/lib/opengraphplus/minitest.rb +61 -0
- data/lib/opengraphplus/parser.rb +66 -0
- data/lib/opengraphplus/rspec.rb +75 -0
- data/lib/opengraphplus/verifier.rb +46 -0
- data/lib/opengraphplus/version.rb +1 -1
- data/lib/opengraphplus.rb +7 -4
- data/lib/rails/commands/opengraph/opengraph_command.rb +63 -0
- metadata +12 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 76feb6f71feac815baf6dc136ba5653801e84048adb3ca77d9dffe59d76004be
|
|
4
|
+
data.tar.gz: a7177af1e7c9f4ec5844ddc66602c75420b07c15a9f3b266e772bfdd093984f6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7b8be6792864e32d690507fc03afb246e4a0ffa5ddf0caf75289352a9b54070d2de89845ea0236edad762c562174fa68c4b46ebc567d71fa8a223726a9a7af06
|
|
7
|
+
data.tar.gz: 678c88ffc101a8720d29c8dbbc6156f16adf8fb2a28c263b8f025fd431dc61da4167bc5719aa533a87a66412fcff37d26944f9e80c94623f1e48bb4249431efd
|
data/README.md
CHANGED
|
@@ -18,7 +18,161 @@ gem install opengraphplus
|
|
|
18
18
|
|
|
19
19
|
## Usage
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
### Get your API key
|
|
22
|
+
|
|
23
|
+
Sign up at [og.plus](https://og.plus) to get your API key.
|
|
24
|
+
|
|
25
|
+
### Configuration
|
|
26
|
+
|
|
27
|
+
#### Using environment variables
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
rails g opengraphplus:env ogp_live_████████████████████
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
This will:
|
|
34
|
+
- Append `OPENGRAPHPLUS__API_KEY=ogp_live_████████████████████` to your `.env` file (or the first env file found)
|
|
35
|
+
- Create `config/initializers/opengraphplus.rb`
|
|
36
|
+
|
|
37
|
+
To specify a different env file:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
rails g opengraphplus:env ogp_live_████████████████████ -e .envrc
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
#### Using Rails credentials
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
rails g opengraphplus:credentials ogp_live_████████████████████
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
This will:
|
|
50
|
+
- Add `opengraphplus.api_key` to your encrypted `credentials.yml.enc`
|
|
51
|
+
- Create `config/initializers/opengraphplus.rb`
|
|
52
|
+
|
|
53
|
+
#### Manual configuration
|
|
54
|
+
|
|
55
|
+
Run the basic install generator for a commented template:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
rails g opengraphplus:install
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Then configure manually in `config/initializers/opengraphplus.rb`:
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
OpenGraphPlus.configure do |config|
|
|
65
|
+
config.api_key = ENV["OPENGRAPHPLUS__API_KEY"]
|
|
66
|
+
# or
|
|
67
|
+
config.api_key = Rails.application.credentials.opengraphplus.api_key
|
|
68
|
+
end
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Verifying OpenGraph Tags
|
|
72
|
+
|
|
73
|
+
You can verify that your pages have the required OpenGraph tags using the included command:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
rails opengraph:verify http://localhost:3000
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
This will fetch the URL and check for required tags (`og:title`, `og:type`, `og:image`, `og:url`), displaying any missing required or recommended tags:
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
Verifying OpenGraph tags at http://localhost:3000...
|
|
83
|
+
|
|
84
|
+
Found tags:
|
|
85
|
+
og:title → "My Awesome Site"
|
|
86
|
+
og:type → "website"
|
|
87
|
+
og:image → "https://example.com/image.png"
|
|
88
|
+
og:url → "http://localhost:3000"
|
|
89
|
+
|
|
90
|
+
✓ All required OpenGraph tags present
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The command exits with code 0 on success, or 1 if required tags are missing.
|
|
94
|
+
|
|
95
|
+
## Testing
|
|
96
|
+
|
|
97
|
+
OpenGraphPlus provides test helpers for both RSpec and Minitest to verify OpenGraph tags in your test suite.
|
|
98
|
+
|
|
99
|
+
### RSpec
|
|
100
|
+
|
|
101
|
+
Add to your `spec/spec_helper.rb` or `spec/rails_helper.rb`:
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
require 'opengraphplus/rspec'
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Then use the matchers in your specs:
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
RSpec.describe "Home page", type: :request do
|
|
111
|
+
it "has all required OpenGraph tags" do
|
|
112
|
+
get "/"
|
|
113
|
+
expect(response.body).to have_open_graph_tags
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it "has the correct title" do
|
|
117
|
+
get "/"
|
|
118
|
+
expect(response.body).to have_og_tag("og:title").with_content("My Site")
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it "has an og:image tag" do
|
|
122
|
+
get "/"
|
|
123
|
+
expect(response.body).to have_og_tag("og:image")
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Available matchers:
|
|
129
|
+
- `have_open_graph_tags` - Passes if all required OG tags are present
|
|
130
|
+
- `have_og_tag("og:title")` - Passes if the specified tag exists
|
|
131
|
+
- `have_og_tag("og:title").with_content("My Title")` - Passes if the tag has the expected content
|
|
132
|
+
|
|
133
|
+
### Minitest
|
|
134
|
+
|
|
135
|
+
Add to your test helper:
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
require 'opengraphplus/minitest'
|
|
139
|
+
|
|
140
|
+
class ActiveSupport::TestCase
|
|
141
|
+
include OpenGraphPlus::Minitest
|
|
142
|
+
end
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Then use the assertions in your tests:
|
|
146
|
+
|
|
147
|
+
```ruby
|
|
148
|
+
class HomePageTest < ActionDispatch::IntegrationTest
|
|
149
|
+
test "has all required OpenGraph tags" do
|
|
150
|
+
get "/"
|
|
151
|
+
assert_open_graph_tags(response.body)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
test "has the correct title" do
|
|
155
|
+
get "/"
|
|
156
|
+
assert_og_tag(response.body, "og:title", "My Site")
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
test "has an og:image tag" do
|
|
160
|
+
get "/"
|
|
161
|
+
assert_og_tag(response.body, "og:image")
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
test "does not have private tags" do
|
|
165
|
+
get "/"
|
|
166
|
+
refute_og_tag(response.body, "og:private")
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Available assertions:
|
|
172
|
+
- `assert_open_graph_tags(html)` - Passes if all required OG tags are present
|
|
173
|
+
- `assert_og_tag(html, "og:title")` - Passes if the specified tag exists
|
|
174
|
+
- `assert_og_tag(html, "og:title", "My Title")` - Passes if the tag has the expected content
|
|
175
|
+
- `refute_og_tag(html, "og:private")` - Passes if the specified tag does not exist
|
|
22
176
|
|
|
23
177
|
## Development
|
|
24
178
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
|
|
5
|
+
module Opengraphplus
|
|
6
|
+
module Generators
|
|
7
|
+
class BaseGenerator < Rails::Generators::Base
|
|
8
|
+
API_KEY_PREFIX = "ogp_"
|
|
9
|
+
|
|
10
|
+
argument :api_key, type: :string, required: true,
|
|
11
|
+
desc: "Your OpenGraphPlus API key"
|
|
12
|
+
|
|
13
|
+
def validate_api_key
|
|
14
|
+
unless api_key.start_with?(API_KEY_PREFIX)
|
|
15
|
+
say_status :error, "Invalid API key: must start with '#{API_KEY_PREFIX}'", :red
|
|
16
|
+
raise SystemExit
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "active_support/core_ext/hash/keys"
|
|
5
|
+
require_relative "../base_generator"
|
|
6
|
+
|
|
7
|
+
module Opengraphplus
|
|
8
|
+
module Generators
|
|
9
|
+
class CredentialsGenerator < BaseGenerator
|
|
10
|
+
source_root File.expand_path("templates", __dir__)
|
|
11
|
+
|
|
12
|
+
desc "Configures OpenGraphPlus using Rails encrypted credentials"
|
|
13
|
+
|
|
14
|
+
def add_to_credentials
|
|
15
|
+
credentials = Rails.application.credentials
|
|
16
|
+
|
|
17
|
+
unless credentials.key?
|
|
18
|
+
say_status :error, "No credentials key found. Run `rails credentials:edit` first.", :red
|
|
19
|
+
return
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Read existing content, merge, write back
|
|
23
|
+
yaml_content = credentials.read.presence || ""
|
|
24
|
+
config = parse_yaml(yaml_content)
|
|
25
|
+
|
|
26
|
+
config["opengraphplus"] ||= {}
|
|
27
|
+
config["opengraphplus"]["api_key"] = api_key
|
|
28
|
+
|
|
29
|
+
credentials.write(yaml_dump(config))
|
|
30
|
+
say_status :insert, "credentials.yml.enc (opengraphplus.api_key)", :green
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def create_initializer
|
|
34
|
+
template "initializer.rb.tt", "config/initializers/opengraphplus.rb"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def parse_yaml(content)
|
|
40
|
+
return {} if content.blank?
|
|
41
|
+
YAML.safe_load(content, permitted_classes: [Symbol], aliases: true) || {}
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def yaml_dump(config)
|
|
45
|
+
# Preserve nice formatting
|
|
46
|
+
YAML.dump(config.deep_stringify_keys)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../base_generator"
|
|
4
|
+
|
|
5
|
+
module Opengraphplus
|
|
6
|
+
module Generators
|
|
7
|
+
class EnvGenerator < BaseGenerator
|
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
|
9
|
+
|
|
10
|
+
desc "Configures OpenGraphPlus using environment variables"
|
|
11
|
+
|
|
12
|
+
class_option :envfile, type: :string, aliases: "-e",
|
|
13
|
+
desc: "Specific env file to write to (e.g., .env, .envrc)"
|
|
14
|
+
|
|
15
|
+
ENV_FILES = %w[.env .env.local .env.development .env.development.local .envrc].freeze
|
|
16
|
+
ENV_VAR_NAME = "OPENGRAPHPLUS__API_KEY"
|
|
17
|
+
|
|
18
|
+
def append_to_env_file
|
|
19
|
+
if options[:envfile]
|
|
20
|
+
write_to_env_file(options[:envfile], create: true)
|
|
21
|
+
else
|
|
22
|
+
env_file = detect_env_file
|
|
23
|
+
if env_file
|
|
24
|
+
write_to_env_file(env_file)
|
|
25
|
+
else
|
|
26
|
+
say_status :skip, "No env file found (create one or use -e)", :yellow
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def create_initializer
|
|
32
|
+
template "initializer.rb.tt", "config/initializers/opengraphplus.rb"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def detect_env_file
|
|
38
|
+
ENV_FILES.find { |f| File.exist?(f) }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def write_to_env_file(env_file, create: false)
|
|
42
|
+
env_line = "#{ENV_VAR_NAME}=#{api_key}"
|
|
43
|
+
|
|
44
|
+
unless File.exist?(env_file)
|
|
45
|
+
if create
|
|
46
|
+
create_file env_file, "#{env_line}\n"
|
|
47
|
+
end
|
|
48
|
+
return
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
if File.read(env_file).include?(ENV_VAR_NAME)
|
|
52
|
+
say_status :skip, "#{env_file} (#{ENV_VAR_NAME} already defined)", :yellow
|
|
53
|
+
else
|
|
54
|
+
content = File.read(env_file)
|
|
55
|
+
prefix = content.end_with?("\n") || content.empty? ? "" : "\n"
|
|
56
|
+
append_to_file env_file, "#{prefix}#{env_line}\n"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -1,46 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
OpenGraphPlus has been installed!
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
1. Add your API key to config/initializers/opengraphplus.rb
|
|
7
|
-
Get your API key at: https://opengraphplus.com/dashboard
|
|
8
|
-
|
|
9
|
-
2. Add the helper to your layout file (e.g., app/views/layouts/application.html.erb):
|
|
10
|
-
|
|
11
|
-
<head>
|
|
12
|
-
<%%= open_graph_meta_tags %>
|
|
13
|
-
</head>
|
|
14
|
-
|
|
15
|
-
3. Customize the default Open Graph tags in ApplicationController:
|
|
16
|
-
|
|
17
|
-
class ApplicationController < ActionController::Base
|
|
18
|
-
open_graph do |og|
|
|
19
|
-
og.type = "website"
|
|
20
|
-
og.url = request.original_url
|
|
21
|
-
og.site_name = "Your Site Name"
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
4. Override tags in specific controllers (inherits from parent):
|
|
26
|
-
|
|
27
|
-
class ArticlesController < ApplicationController
|
|
28
|
-
before_action { @article = Article.find(params[:id]) }
|
|
29
|
-
|
|
30
|
-
open_graph do |og|
|
|
31
|
-
og.title = @article.title
|
|
32
|
-
og.description = @article.excerpt
|
|
33
|
-
og.type = "article"
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
5. Set tags in views:
|
|
38
|
-
|
|
39
|
-
<%% open_graph do |og|
|
|
40
|
-
og.title = "My Page Title"
|
|
41
|
-
og.description = "My Page Description"
|
|
42
|
-
end %>
|
|
43
|
-
|
|
44
|
-
6. Verify it works by checking your page source for og:image meta tags,
|
|
45
|
-
or use the preview tool at: https://opengraphplus.com/previews/new
|
|
4
|
+
For setup instructions, visit https://opengraphplus.com/guides/rails
|
|
46
5
|
|
|
6
|
+
For a website API key, visit https://opengraphplus.com/dashboard
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# Get your API key at https://opengraphplus.com/dashboard
|
|
3
4
|
OpenGraphPlus.configure do |config|
|
|
4
|
-
#
|
|
5
|
+
# Use Rails credentials.
|
|
5
6
|
config.api_key = Rails.application.credentials.opengraphplus_api_key
|
|
7
|
+
|
|
6
8
|
# Or use ENV:
|
|
7
9
|
# config.api_key = ENV["OPENGRAPHPLUS_API_KEY"]
|
|
8
10
|
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "opengraphplus"
|
|
4
|
+
|
|
5
|
+
module OpenGraphPlus
|
|
6
|
+
module Minitest
|
|
7
|
+
# Assert that all required OpenGraph tags are present
|
|
8
|
+
#
|
|
9
|
+
# @param html [String] The HTML to check
|
|
10
|
+
# @param message [String, nil] Custom failure message
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# assert_open_graph_tags(response.body)
|
|
14
|
+
#
|
|
15
|
+
def assert_open_graph_tags(html, message = nil)
|
|
16
|
+
parser = OpenGraphPlus::Parser.new(html)
|
|
17
|
+
message ||= "Expected HTML to have all required OpenGraph tags, but missing: #{parser.errors.join(', ')}"
|
|
18
|
+
assert parser.valid?, message
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Assert that a specific OpenGraph/Twitter tag is present
|
|
22
|
+
#
|
|
23
|
+
# @param html [String] The HTML to check
|
|
24
|
+
# @param property [String] The tag property (e.g., "og:title")
|
|
25
|
+
# @param content [String, nil] Expected content (optional)
|
|
26
|
+
# @param message [String, nil] Custom failure message
|
|
27
|
+
#
|
|
28
|
+
# @example
|
|
29
|
+
# assert_og_tag(response.body, "og:title")
|
|
30
|
+
# assert_og_tag(response.body, "og:title", "My Title")
|
|
31
|
+
#
|
|
32
|
+
def assert_og_tag(html, property, content = nil, message = nil)
|
|
33
|
+
parser = OpenGraphPlus::Parser.new(html)
|
|
34
|
+
actual_content = parser[property]
|
|
35
|
+
|
|
36
|
+
if content.nil?
|
|
37
|
+
message ||= "Expected HTML to have #{property} tag, but it was not found"
|
|
38
|
+
assert !actual_content.nil?, message
|
|
39
|
+
else
|
|
40
|
+
message ||= "Expected #{property} to have content #{content.inspect}, but got #{actual_content.inspect}"
|
|
41
|
+
assert_equal content, actual_content, message
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Assert that a specific OpenGraph/Twitter tag is NOT present
|
|
46
|
+
#
|
|
47
|
+
# @param html [String] The HTML to check
|
|
48
|
+
# @param property [String] The tag property (e.g., "og:title")
|
|
49
|
+
# @param message [String, nil] Custom failure message
|
|
50
|
+
#
|
|
51
|
+
# @example
|
|
52
|
+
# refute_og_tag(response.body, "og:private")
|
|
53
|
+
#
|
|
54
|
+
def refute_og_tag(html, property, message = nil)
|
|
55
|
+
parser = OpenGraphPlus::Parser.new(html)
|
|
56
|
+
actual_content = parser[property]
|
|
57
|
+
message ||= "Expected HTML not to have #{property} tag, but it was found with content #{actual_content.inspect}"
|
|
58
|
+
assert actual_content.nil?, message
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "nokogiri"
|
|
4
|
+
|
|
5
|
+
module OpenGraphPlus
|
|
6
|
+
class Parser
|
|
7
|
+
REQUIRED_TAGS = %w[og:title og:type og:image og:url].freeze
|
|
8
|
+
RECOMMENDED_TAGS = %w[og:description og:site_name twitter:card twitter:title twitter:image].freeze
|
|
9
|
+
|
|
10
|
+
attr_reader :html
|
|
11
|
+
|
|
12
|
+
def initialize(html)
|
|
13
|
+
@html = html.to_s
|
|
14
|
+
@tags = parse_meta_tags
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def og_tags
|
|
18
|
+
@tags.select { |key, _| key.start_with?("og:") }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def twitter_tags
|
|
22
|
+
@tags.select { |key, _| key.start_with?("twitter:") }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def all_tags
|
|
26
|
+
@tags.dup
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def [](property)
|
|
30
|
+
@tags[property]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def valid?
|
|
34
|
+
errors.empty?
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def errors
|
|
38
|
+
REQUIRED_TAGS.reject { |tag| @tags.key?(tag) }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def warnings
|
|
42
|
+
RECOMMENDED_TAGS.reject { |tag| @tags.key?(tag) }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def parse_meta_tags
|
|
48
|
+
doc = Nokogiri::HTML(@html)
|
|
49
|
+
tags = {}
|
|
50
|
+
|
|
51
|
+
doc.css("meta[property^='og:'], meta[name^='og:']").each do |meta|
|
|
52
|
+
property = meta["property"] || meta["name"]
|
|
53
|
+
content = meta["content"]
|
|
54
|
+
tags[property] = content if property && content
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
doc.css("meta[property^='twitter:'], meta[name^='twitter:']").each do |meta|
|
|
58
|
+
property = meta["property"] || meta["name"]
|
|
59
|
+
content = meta["content"]
|
|
60
|
+
tags[property] = content if property && content
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
tags
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "opengraphplus"
|
|
4
|
+
require "rspec/expectations"
|
|
5
|
+
|
|
6
|
+
module OpenGraphPlus
|
|
7
|
+
module RSpec
|
|
8
|
+
extend ::RSpec::Matchers::DSL
|
|
9
|
+
|
|
10
|
+
# Matches if all required OpenGraph tags are present
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# expect(response.body).to have_open_graph_tags
|
|
14
|
+
#
|
|
15
|
+
matcher :have_open_graph_tags do
|
|
16
|
+
match do |html|
|
|
17
|
+
@parser = OpenGraphPlus::Parser.new(html)
|
|
18
|
+
@parser.valid?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
failure_message do
|
|
22
|
+
"expected HTML to have all required OpenGraph tags, but missing: #{@parser.errors.join(', ')}"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
failure_message_when_negated do
|
|
26
|
+
"expected HTML not to have all required OpenGraph tags, but all were present"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Matches if a specific OpenGraph/Twitter tag is present
|
|
31
|
+
#
|
|
32
|
+
# @example
|
|
33
|
+
# expect(response.body).to have_og_tag("og:title")
|
|
34
|
+
# expect(response.body).to have_og_tag("og:title").with_content("My Title")
|
|
35
|
+
#
|
|
36
|
+
matcher :have_og_tag do |property|
|
|
37
|
+
chain :with_content do |expected_content|
|
|
38
|
+
@expected_content = expected_content
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
match do |html|
|
|
42
|
+
@parser = OpenGraphPlus::Parser.new(html)
|
|
43
|
+
@actual_content = @parser[property]
|
|
44
|
+
|
|
45
|
+
if @actual_content.nil?
|
|
46
|
+
false
|
|
47
|
+
elsif @expected_content
|
|
48
|
+
@actual_content == @expected_content
|
|
49
|
+
else
|
|
50
|
+
true
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
failure_message do
|
|
55
|
+
if @actual_content.nil?
|
|
56
|
+
"expected HTML to have #{property} tag, but it was not found"
|
|
57
|
+
else
|
|
58
|
+
"expected #{property} to have content #{@expected_content.inspect}, but got #{@actual_content.inspect}"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
failure_message_when_negated do
|
|
63
|
+
if @expected_content
|
|
64
|
+
"expected #{property} not to have content #{@expected_content.inspect}, but it did"
|
|
65
|
+
else
|
|
66
|
+
"expected HTML not to have #{property} tag, but it was found with content #{@actual_content.inspect}"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
RSpec.configure do |config|
|
|
74
|
+
config.include OpenGraphPlus::RSpec
|
|
75
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "uri"
|
|
5
|
+
|
|
6
|
+
module OpenGraphPlus
|
|
7
|
+
class Verifier
|
|
8
|
+
class FetchError < Error; end
|
|
9
|
+
|
|
10
|
+
MAX_REDIRECTS = 5
|
|
11
|
+
|
|
12
|
+
attr_reader :url
|
|
13
|
+
|
|
14
|
+
def initialize(url)
|
|
15
|
+
@url = url
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def verify
|
|
19
|
+
html = fetch_html
|
|
20
|
+
Parser.new(html)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def fetch_html(redirect_count = 0)
|
|
26
|
+
raise FetchError, "Too many redirects" if redirect_count > MAX_REDIRECTS
|
|
27
|
+
|
|
28
|
+
uri = URI.parse(@url)
|
|
29
|
+
response = Net::HTTP.get_response(uri)
|
|
30
|
+
|
|
31
|
+
case response
|
|
32
|
+
when Net::HTTPSuccess
|
|
33
|
+
response.body
|
|
34
|
+
when Net::HTTPRedirection
|
|
35
|
+
@url = response["location"]
|
|
36
|
+
fetch_html(redirect_count + 1)
|
|
37
|
+
else
|
|
38
|
+
raise FetchError, "HTTP #{response.code}: #{response.message}"
|
|
39
|
+
end
|
|
40
|
+
rescue URI::InvalidURIError => e
|
|
41
|
+
raise FetchError, "Invalid URL: #{e.message}"
|
|
42
|
+
rescue SocketError, Errno::ECONNREFUSED, Errno::ETIMEDOUT => e
|
|
43
|
+
raise FetchError, "Connection failed: #{e.message}"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
data/lib/opengraphplus.rb
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "opengraphplus/version"
|
|
4
|
+
|
|
5
|
+
module OpenGraphPlus
|
|
6
|
+
class Error < StandardError; end
|
|
7
|
+
end
|
|
8
|
+
|
|
4
9
|
require_relative "opengraphplus/api_key"
|
|
5
10
|
require_relative "opengraphplus/configuration"
|
|
6
11
|
require_relative "opengraphplus/signature"
|
|
7
12
|
require_relative "opengraphplus/tag"
|
|
8
13
|
require_relative "opengraphplus/namespace"
|
|
9
14
|
require_relative "opengraphplus/image_generator"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class Error < StandardError; end
|
|
13
|
-
end
|
|
15
|
+
require_relative "opengraphplus/parser"
|
|
16
|
+
require_relative "opengraphplus/verifier"
|
|
14
17
|
|
|
15
18
|
require_relative "opengraphplus/rails" if defined?(::Rails::Railtie)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/command"
|
|
4
|
+
|
|
5
|
+
module Rails
|
|
6
|
+
module Command
|
|
7
|
+
class OpengraphCommand < Base
|
|
8
|
+
namespace "opengraph"
|
|
9
|
+
|
|
10
|
+
desc "verify URL", "Verify OpenGraph tags at a URL"
|
|
11
|
+
def verify(url)
|
|
12
|
+
require "opengraphplus"
|
|
13
|
+
|
|
14
|
+
say "Verifying OpenGraph tags at #{url}..."
|
|
15
|
+
say ""
|
|
16
|
+
|
|
17
|
+
begin
|
|
18
|
+
parser = OpenGraphPlus::Verifier.new(url).verify
|
|
19
|
+
rescue OpenGraphPlus::Verifier::FetchError => e
|
|
20
|
+
say_error e.message
|
|
21
|
+
exit 1
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
tags = parser.all_tags
|
|
25
|
+
|
|
26
|
+
if tags.any?
|
|
27
|
+
say "Found tags:"
|
|
28
|
+
max_key_length = tags.keys.map(&:length).max
|
|
29
|
+
tags.each do |property, content|
|
|
30
|
+
say " #{property.ljust(max_key_length)} → #{content.inspect}"
|
|
31
|
+
end
|
|
32
|
+
say ""
|
|
33
|
+
else
|
|
34
|
+
say "No OpenGraph or Twitter tags found."
|
|
35
|
+
say ""
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
if parser.errors.any?
|
|
39
|
+
say "Missing required tags:"
|
|
40
|
+
parser.errors.each do |tag|
|
|
41
|
+
say " ✗ #{tag}"
|
|
42
|
+
end
|
|
43
|
+
say ""
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
if parser.warnings.any?
|
|
47
|
+
say "Missing recommended tags:"
|
|
48
|
+
parser.warnings.each do |tag|
|
|
49
|
+
say " - #{tag}"
|
|
50
|
+
end
|
|
51
|
+
say ""
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
if parser.valid?
|
|
55
|
+
say "✓ All required OpenGraph tags present"
|
|
56
|
+
else
|
|
57
|
+
say "Verification failed: #{parser.errors.length} required tag(s) missing"
|
|
58
|
+
exit 1
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: opengraphplus
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brad Gessler
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-01-
|
|
10
|
+
date: 2026-01-08 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: activesupport
|
|
@@ -34,6 +34,11 @@ files:
|
|
|
34
34
|
- LICENSE.txt
|
|
35
35
|
- README.md
|
|
36
36
|
- Rakefile
|
|
37
|
+
- lib/generators/opengraphplus/base_generator.rb
|
|
38
|
+
- lib/generators/opengraphplus/credentials/credentials_generator.rb
|
|
39
|
+
- lib/generators/opengraphplus/credentials/templates/initializer.rb.tt
|
|
40
|
+
- lib/generators/opengraphplus/env/env_generator.rb
|
|
41
|
+
- lib/generators/opengraphplus/env/templates/initializer.rb.tt
|
|
37
42
|
- lib/generators/opengraphplus/install/install_generator.rb
|
|
38
43
|
- lib/generators/opengraphplus/install/templates/README
|
|
39
44
|
- lib/generators/opengraphplus/install/templates/initializer.rb
|
|
@@ -42,7 +47,9 @@ files:
|
|
|
42
47
|
- lib/opengraphplus/api_key.rb
|
|
43
48
|
- lib/opengraphplus/configuration.rb
|
|
44
49
|
- lib/opengraphplus/image_generator.rb
|
|
50
|
+
- lib/opengraphplus/minitest.rb
|
|
45
51
|
- lib/opengraphplus/namespace.rb
|
|
52
|
+
- lib/opengraphplus/parser.rb
|
|
46
53
|
- lib/opengraphplus/rails.rb
|
|
47
54
|
- lib/opengraphplus/rails/controller.rb
|
|
48
55
|
- lib/opengraphplus/rails/helper.rb
|
|
@@ -50,13 +57,16 @@ files:
|
|
|
50
57
|
- lib/opengraphplus/rails/signature.rb
|
|
51
58
|
- lib/opengraphplus/rails/signature/routes.rb
|
|
52
59
|
- lib/opengraphplus/rails/signature/scope.rb
|
|
60
|
+
- lib/opengraphplus/rspec.rb
|
|
53
61
|
- lib/opengraphplus/signature.rb
|
|
54
62
|
- lib/opengraphplus/signature/generator.rb
|
|
55
63
|
- lib/opengraphplus/signature/url.rb
|
|
56
64
|
- lib/opengraphplus/signature/verifier.rb
|
|
57
65
|
- lib/opengraphplus/tag.rb
|
|
58
66
|
- lib/opengraphplus/tags.rb
|
|
67
|
+
- lib/opengraphplus/verifier.rb
|
|
59
68
|
- lib/opengraphplus/version.rb
|
|
69
|
+
- lib/rails/commands/opengraph/opengraph_command.rb
|
|
60
70
|
- sig/open_graph_plus.rbs
|
|
61
71
|
homepage: https://opengraphplus.com/ruby
|
|
62
72
|
licenses:
|