contentful-social 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +4 -0
- data/Guardfile +16 -0
- data/LICENSE.txt +21 -0
- data/README.md +204 -0
- data/Rakefile +6 -0
- data/contentful-social.gemspec +37 -0
- data/example/config.yml +35 -0
- data/exe/contentful_social +35 -0
- data/lib/contentful/social.rb +45 -0
- data/lib/contentful/social/config.rb +67 -0
- data/lib/contentful/social/controller.rb +58 -0
- data/lib/contentful/social/facebook_handler.rb +38 -0
- data/lib/contentful/social/support.rb +11 -0
- data/lib/contentful/social/template.rb +36 -0
- data/lib/contentful/social/twitter_handler.rb +61 -0
- data/lib/contentful/social/version.rb +5 -0
- metadata +253 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 61f585a87e9e82dc0eee1527fa93346a686c2866
|
4
|
+
data.tar.gz: 96d64a8b293f976901b54d0b003d69e9df661479
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: dbb105190860ce1f4ff8d0faf0a7d374b68c7b643c9f1da3d1a4ec2bc1075edbe001e324be63157cab70bc3823d6494280187520f656a3f4b6fe094c424fcbf4
|
7
|
+
data.tar.gz: ced25695778145ab840daa7fe372cfd845a6e6b8ef8a265f82e1caffe5192a4666f2036c792425993f1d2bbf69dc8d02206495a5800daad59920935df95cc3e9
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# Contributor Code of Conduct
|
2
|
+
|
3
|
+
As contributors and maintainers of this project, and in the interest of
|
4
|
+
fostering an open and welcoming community, we pledge to respect all people who
|
5
|
+
contribute through reporting issues, posting feature requests, updating
|
6
|
+
documentation, submitting pull requests or patches, and other activities.
|
7
|
+
|
8
|
+
We are committed to making participation in this project a harassment-free
|
9
|
+
experience for everyone, regardless of level of experience, gender, gender
|
10
|
+
identity and expression, sexual orientation, disability, personal appearance,
|
11
|
+
body size, race, ethnicity, age, religion, or nationality.
|
12
|
+
|
13
|
+
Examples of unacceptable behavior by participants include:
|
14
|
+
|
15
|
+
* The use of sexualized language or imagery
|
16
|
+
* Personal attacks
|
17
|
+
* Trolling or insulting/derogatory comments
|
18
|
+
* Public or private harassment
|
19
|
+
* Publishing other's private information, such as physical or electronic
|
20
|
+
addresses, without explicit permission
|
21
|
+
* Other unethical or unprofessional conduct
|
22
|
+
|
23
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
24
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
25
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
26
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
27
|
+
threatening, offensive, or harmful.
|
28
|
+
|
29
|
+
By adopting this Code of Conduct, project maintainers commit themselves to
|
30
|
+
fairly and consistently applying these principles to every aspect of managing
|
31
|
+
this project. Project maintainers who do not follow or enforce the Code of
|
32
|
+
Conduct may be permanently removed from the project team.
|
33
|
+
|
34
|
+
This code of conduct applies both within project spaces and in public spaces
|
35
|
+
when an individual is representing the project or its community.
|
36
|
+
|
37
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
38
|
+
reported by contacting a project maintainer at david.litvakb@gmail.com. All
|
39
|
+
complaints will be reviewed and investigated and will result in a response that
|
40
|
+
is deemed necessary and appropriate to the circumstances. Maintainers are
|
41
|
+
obligated to maintain confidentiality with regard to the reporter of an
|
42
|
+
incident.
|
43
|
+
|
44
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
45
|
+
version 1.3.0, available at
|
46
|
+
[http://contributor-covenant.org/version/1/3/0/][version]
|
47
|
+
|
48
|
+
[homepage]: http://contributor-covenant.org
|
49
|
+
[version]: http://contributor-covenant.org/version/1/3/0/
|
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
guard :rspec, cmd: "bundle exec rspec" do
|
2
|
+
require "guard/rspec/dsl"
|
3
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
4
|
+
|
5
|
+
# Feel free to open issues for suggestions and improvements
|
6
|
+
|
7
|
+
# RSpec files
|
8
|
+
rspec = dsl.rspec
|
9
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
10
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
11
|
+
watch(rspec.spec_files)
|
12
|
+
|
13
|
+
# Ruby files
|
14
|
+
ruby = dsl.ruby
|
15
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
16
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Contentful GmbH
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,204 @@
|
|
1
|
+
# Contentful Social
|
2
|
+
|
3
|
+
Social Server listens for incoming publish webhooks from Contentful to manage social media publishing of entries.
|
4
|
+
|
5
|
+
## Contentful
|
6
|
+
[Contentful](http://www.contentful.com) is a content management platform for web applications,
|
7
|
+
mobile apps and connected devices. It allows you to create, edit & manage content in the cloud
|
8
|
+
and publish it anywhere via powerful API. Contentful offers tools for managing editorial
|
9
|
+
teams and enabling cooperation between organizations.
|
10
|
+
|
11
|
+
## What does `contentful-social` do?
|
12
|
+
The aim of `contentful-social` is to have developers setting up their Contentful
|
13
|
+
entries for publishing to multiple social media services.
|
14
|
+
|
15
|
+
### How does it work?
|
16
|
+
|
17
|
+
We'll explain this with a step-by-step example:
|
18
|
+
|
19
|
+
1. Create your content type in Contentful
|
20
|
+
2. Set up your social media templates (more on this later)
|
21
|
+
3. Set up your social media credentials
|
22
|
+
4. Start the `contentful_social` server
|
23
|
+
5. Create your entries in Contentful
|
24
|
+
6. Publish them
|
25
|
+
7. Watch your social accounts get updated
|
26
|
+
|
27
|
+
`contentful-social` provides a web endpoint to receive webhook calls from Contentful.
|
28
|
+
|
29
|
+
Every time the endpoint recieves an `Entry.publish` call it looks for you configured accounts and publishes to them.
|
30
|
+
|
31
|
+
### Writing Social Media Templates
|
32
|
+
|
33
|
+
This tool uses a very simplified templating system for handling your Contentful entries and delivering them to your social accounts.
|
34
|
+
|
35
|
+
Imagine you have the following (simplified) Contentful entry:
|
36
|
+
|
37
|
+
```json
|
38
|
+
{
|
39
|
+
"fields": {
|
40
|
+
"title": "MyTitle",
|
41
|
+
"body": "Some well written text",
|
42
|
+
"interestingLink": "http://veryinteresting.com"
|
43
|
+
}
|
44
|
+
}
|
45
|
+
```
|
46
|
+
|
47
|
+
We want to send a tweet or Facebook post displaying `"MyTitle: Some well written text. More info on: http://veryinteresting.com"`.
|
48
|
+
|
49
|
+
You could very well write directly that in your template field, and every single one of your published posts will be THAT! But we don't
|
50
|
+
want that. We want to be able to follow that same pattern but for multiple entries with varying content.
|
51
|
+
|
52
|
+
To do so, we can use the templates:
|
53
|
+
|
54
|
+
```
|
55
|
+
{{title}}: {{body}}. More info on: {{interesting_link}}
|
56
|
+
```
|
57
|
+
|
58
|
+
As you might have noticed, it's simply a matter of surrounding the field name you want in place with double braces `{{field_name}}`. And
|
59
|
+
it is exactly that.
|
60
|
+
|
61
|
+
**Note:** `camelCased` field names need to be referenced as `snake_cased`. For Example, `interestingLink` will be referenced as `interesting_link`
|
62
|
+
inside our template.
|
63
|
+
|
64
|
+
#### Dealing with related entries
|
65
|
+
|
66
|
+
Many times in Contentful you will like to have linked entries, and you may want to pull content from them inside your templates.
|
67
|
+
|
68
|
+
Lets consider the following (simplified and with links resolved inside) entry:
|
69
|
+
|
70
|
+
```json
|
71
|
+
{
|
72
|
+
"fields": {
|
73
|
+
"title": "MyTitle",
|
74
|
+
"body": "Some well written text",
|
75
|
+
"interestingLink": "https://veryinteresting.com",
|
76
|
+
"relatedThing": {
|
77
|
+
"fields": {
|
78
|
+
"name": "AmazingTitle"
|
79
|
+
}
|
80
|
+
}
|
81
|
+
}
|
82
|
+
}
|
83
|
+
```
|
84
|
+
|
85
|
+
We can call fields on related entries too! (Only up to 3 levels of nesting). To do so:
|
86
|
+
|
87
|
+
```
|
88
|
+
{{title}}: {{body}}. Have you heard about {{related_thing.name}}?
|
89
|
+
```
|
90
|
+
|
91
|
+
#### Dealing with Arrays
|
92
|
+
|
93
|
+
Currently the template engine does not support Arrays, but you can still access the first element as if it were a related entry:
|
94
|
+
|
95
|
+
```
|
96
|
+
{{my_array.first}}
|
97
|
+
```
|
98
|
+
|
99
|
+
Or if the array is of related entries:
|
100
|
+
|
101
|
+
```
|
102
|
+
{{related_entries.first.some_field}}
|
103
|
+
```
|
104
|
+
|
105
|
+
## Requirements
|
106
|
+
|
107
|
+
At least one of the following:
|
108
|
+
|
109
|
+
* [Facebook Developer account](https://developers.facebook.com/)
|
110
|
+
* [Twitter Developer account](https://dev.twitter.com/)
|
111
|
+
|
112
|
+
## Installation
|
113
|
+
|
114
|
+
Add this line to your application's Gemfile:
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
gem 'contentful-social'
|
118
|
+
```
|
119
|
+
|
120
|
+
And then execute:
|
121
|
+
|
122
|
+
$ bundle
|
123
|
+
|
124
|
+
Or install it yourself as:
|
125
|
+
|
126
|
+
$ gem install contentful-social
|
127
|
+
|
128
|
+
## Usage
|
129
|
+
|
130
|
+
* Create your configuration file:
|
131
|
+
|
132
|
+
You can base your configuration file from [the example `config.yml`](./example/config.yml)
|
133
|
+
|
134
|
+
```yml
|
135
|
+
---
|
136
|
+
|
137
|
+
port: 34123 # defaults to 34123
|
138
|
+
endpoint: '/social' # defaults to /social
|
139
|
+
|
140
|
+
contentful: # your contentful spaces
|
141
|
+
my_space_id: 'my_production_access_token'
|
142
|
+
# you can add multiple spaces here
|
143
|
+
# at least one is required
|
144
|
+
|
145
|
+
twitter: # your twitter config
|
146
|
+
# required
|
147
|
+
access_token: 'your_access_token'
|
148
|
+
access_token_secret: 'your_access_token_secret'
|
149
|
+
consumer_key: 'your_consumer_key'
|
150
|
+
consumer_secret: 'your_consumer_secret'
|
151
|
+
|
152
|
+
template: '{{body}} {{interesting_link}} {{related.title}}'
|
153
|
+
|
154
|
+
# optional
|
155
|
+
media: 'my_media_field' # defaults to nil
|
156
|
+
possibly_sensitive: false # defaults to false
|
157
|
+
location: #defaults to {}
|
158
|
+
lat: 10.12341
|
159
|
+
lon: -25.123123
|
160
|
+
|
161
|
+
facebook: # your facebook config
|
162
|
+
# required
|
163
|
+
access_token: 'your_access_token'
|
164
|
+
|
165
|
+
template: '{{body}} {{interesting_link}} {{related.title}}'
|
166
|
+
|
167
|
+
# optional
|
168
|
+
app_secret: 'your_app_secret' # defaults to nil (the access token has an app_id already)
|
169
|
+
post_to: 'feed_id' # defaults to 'me' which is your own timeline (can be a page's id or other user's id)
|
170
|
+
```
|
171
|
+
|
172
|
+
* Run the server:
|
173
|
+
|
174
|
+
```bash
|
175
|
+
$ contentful_social config.yml
|
176
|
+
```
|
177
|
+
|
178
|
+
* Configure the webhook in Contentful:
|
179
|
+
|
180
|
+
Under the space settings menu choose webhook and add a new webhook pointing to `http://YOUR_SERVER:33123/social`.
|
181
|
+
|
182
|
+
Keep in mind that if you modify the defaults, the URL should be changed to the values specified in the configuration.
|
183
|
+
|
184
|
+
## Running in Heroku
|
185
|
+
|
186
|
+
* Create a `Procfile` containing:
|
187
|
+
|
188
|
+
```
|
189
|
+
web: PORT=$PORT env bundle exec contentful_social config.yml
|
190
|
+
```
|
191
|
+
|
192
|
+
That will allow Heroku to set it's own Port according to their policy.
|
193
|
+
|
194
|
+
Then proceed to `git push heroku master`.
|
195
|
+
|
196
|
+
The URL for the webhook then will be on port 80, so you should change it to: `http://YOUR_APPLICATION/social`.
|
197
|
+
|
198
|
+
## Contributing
|
199
|
+
|
200
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/contentful/contentful-social.rb. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
201
|
+
|
202
|
+
## License
|
203
|
+
|
204
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'contentful/social/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "contentful-social"
|
8
|
+
spec.version = Contentful::Social::VERSION
|
9
|
+
spec.authors = ["Contentful GmbH (David Litvak Bruno)"]
|
10
|
+
spec.email = ["david.litvak@contentful.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Contentful Social Publishing Gem}
|
13
|
+
spec.description = %q{Contentful Social Publishing Gem}
|
14
|
+
spec.homepage = "https://www.contentful.com"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_runtime_dependency 'contentful-webhook-listener', '~> 0.2'
|
23
|
+
spec.add_runtime_dependency 'contentful', '~> 0.9'
|
24
|
+
spec.add_runtime_dependency 'hashie', '~> 3.4'
|
25
|
+
|
26
|
+
spec.add_runtime_dependency 'twitter', '~> 5.0'
|
27
|
+
spec.add_runtime_dependency 'koala', '~> 2.2'
|
28
|
+
|
29
|
+
spec.add_development_dependency "bundler", "~> 1.11"
|
30
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
31
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
32
|
+
spec.add_development_dependency "guard"
|
33
|
+
spec.add_development_dependency "guard-rspec"
|
34
|
+
spec.add_development_dependency "pry"
|
35
|
+
spec.add_development_dependency 'vcr'
|
36
|
+
spec.add_development_dependency 'webmock', '~> 1', '>= 1.17.3'
|
37
|
+
end
|
data/example/config.yml
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
---
|
2
|
+
|
3
|
+
port: 34123 # defaults to 34123
|
4
|
+
endpoint: '/social' # defaults to /social
|
5
|
+
|
6
|
+
contentful: # your contentful spaces
|
7
|
+
my_space_id: 'my_production_access_token'
|
8
|
+
# you can add multiple spaces here
|
9
|
+
# at least one is required
|
10
|
+
|
11
|
+
twitter: # your twitter config
|
12
|
+
# required
|
13
|
+
access_token: 'your_access_token'
|
14
|
+
access_token_secret: 'your_access_token_secret'
|
15
|
+
consumer_key: 'your_consumer_key'
|
16
|
+
consumer_secret: 'your_consumer_secret'
|
17
|
+
|
18
|
+
template: '{{body}} {{interesting_link}} {{related.title}}'
|
19
|
+
|
20
|
+
# optional
|
21
|
+
media: 'my_media_field' # defaults to nil
|
22
|
+
possibly_sensitive: false # defaults to false
|
23
|
+
location: #defaults to {}
|
24
|
+
lat: 10.12341
|
25
|
+
lon: -25.123123
|
26
|
+
|
27
|
+
facebook: # your facebook config
|
28
|
+
# required
|
29
|
+
access_token: 'your_access_token'
|
30
|
+
|
31
|
+
template: '{{body}} {{interesting_link}} {{related.title}}'
|
32
|
+
|
33
|
+
# optional
|
34
|
+
app_secret: 'your_app_secret' # defaults to nil (the access token has an app_id already)
|
35
|
+
post_to: 'feed_id' # defaults to 'me' which is your own timeline (can be a page's id or other user's id)
|
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'contentful/social'
|
4
|
+
|
5
|
+
STDOUT.sync = true
|
6
|
+
|
7
|
+
trap('TERM') do
|
8
|
+
puts "Graceful shutdown"
|
9
|
+
exit
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
def usage
|
14
|
+
puts "Usage: contentful_social <configuration_file>"
|
15
|
+
end
|
16
|
+
|
17
|
+
if ARGV.empty?
|
18
|
+
usage
|
19
|
+
exit(1)
|
20
|
+
end
|
21
|
+
|
22
|
+
if ['-h', '-H', '--help'].include?(ARGV.first)
|
23
|
+
usage
|
24
|
+
exit(0)
|
25
|
+
end
|
26
|
+
|
27
|
+
if File.file?(ARGV.first)
|
28
|
+
Contentful::Social.start(
|
29
|
+
Contentful::Social::Config.load(ARGV.first)
|
30
|
+
)
|
31
|
+
else
|
32
|
+
puts "File provided not found!\n"
|
33
|
+
usage
|
34
|
+
exit(1)
|
35
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'hashie'
|
2
|
+
require 'logger'
|
3
|
+
require 'contentful'
|
4
|
+
require 'contentful/webhook/listener'
|
5
|
+
require 'contentful/social/version'
|
6
|
+
require 'contentful/social/config'
|
7
|
+
require 'contentful/social/controller'
|
8
|
+
require 'contentful/social/support'
|
9
|
+
require 'contentful/social/template'
|
10
|
+
|
11
|
+
module Contentful
|
12
|
+
module Social
|
13
|
+
@@config = nil
|
14
|
+
|
15
|
+
def self.config=(config)
|
16
|
+
@@config ||= (config.is_a? ::Contentful::Social::Config) ? config : ::Contentful::Social::Config.new(config)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.config
|
20
|
+
@@config
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.start(config = {})
|
24
|
+
fail "Social not configured" if config.nil? && !block_given?
|
25
|
+
|
26
|
+
if block_given?
|
27
|
+
yield(config) if block_given?
|
28
|
+
end
|
29
|
+
self.config = config
|
30
|
+
|
31
|
+
logger = Logger.new(STDOUT)
|
32
|
+
::Contentful::Webhook::Listener::Server.start do |server_config|
|
33
|
+
server_config[:port] = config.port
|
34
|
+
server_config[:logger] = logger
|
35
|
+
server_config[:endpoints] = [
|
36
|
+
{
|
37
|
+
endpoint: config.endpoint,
|
38
|
+
controller: ::Contentful::Social::Controller,
|
39
|
+
timeout: 0
|
40
|
+
}
|
41
|
+
]
|
42
|
+
end.join
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'hashie'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module Contentful
|
5
|
+
module Social
|
6
|
+
class Config
|
7
|
+
DEFAULT_PORT = 34123
|
8
|
+
DEFAULT_ENDPOINT = '/social'
|
9
|
+
|
10
|
+
attr_reader :config
|
11
|
+
|
12
|
+
def self.load(path)
|
13
|
+
new(Hashie::Mash.load(path))
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(config = {})
|
17
|
+
@config = Hashie::Mash.new(config)
|
18
|
+
|
19
|
+
@config.port = (ENV.key?('PORT') ? ENV['PORT'].to_i : DEFAULT_PORT) unless @config.port?
|
20
|
+
@config.endpoint = DEFAULT_ENDPOINT unless @config.endpoint?
|
21
|
+
|
22
|
+
fail 'Contentful Access Token not Configured' unless contentful_configured?
|
23
|
+
fail 'No Social Media Configured' unless twitter_configured? || facebook_configured?
|
24
|
+
end
|
25
|
+
|
26
|
+
def port
|
27
|
+
@config.port
|
28
|
+
end
|
29
|
+
|
30
|
+
def endpoint
|
31
|
+
@config.endpoint
|
32
|
+
end
|
33
|
+
|
34
|
+
class_eval do
|
35
|
+
[:contentful, :twitter, :facebook].each do |name|
|
36
|
+
define_method(name) do
|
37
|
+
@config.public_send(name)
|
38
|
+
end
|
39
|
+
|
40
|
+
define_method("#{name}?") do
|
41
|
+
@config.public_send("#{name}?")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def twitter_configured?
|
47
|
+
twitter? &&
|
48
|
+
twitter.template? &&
|
49
|
+
twitter.access_token? &&
|
50
|
+
twitter.access_token_secret? &&
|
51
|
+
twitter.consumer_key? &&
|
52
|
+
twitter.consumer_secret?
|
53
|
+
end
|
54
|
+
|
55
|
+
def facebook_configured?
|
56
|
+
facebook? &&
|
57
|
+
facebook.template? &&
|
58
|
+
facebook.access_token? # && facebook.app_secret? # not required
|
59
|
+
end
|
60
|
+
|
61
|
+
def contentful_configured?
|
62
|
+
contentful? &&
|
63
|
+
contentful.all? { |space_id, access_token| access_token }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'contentful/webhook/listener/controllers/webhook_aware'
|
2
|
+
require 'contentful/social/twitter_handler'
|
3
|
+
require 'contentful/social/facebook_handler'
|
4
|
+
|
5
|
+
module Contentful
|
6
|
+
module Social
|
7
|
+
class Controller < ::Contentful::Webhook::Listener::Controllers::WebhookAware
|
8
|
+
def publish
|
9
|
+
return unless webhook.entry?
|
10
|
+
return unless can_publish?(webhook)
|
11
|
+
|
12
|
+
publish_to_twitter(webhook)
|
13
|
+
publish_to_facebook(webhook)
|
14
|
+
end
|
15
|
+
|
16
|
+
def publish_to_twitter(webhook)
|
17
|
+
return unless config.twitter_configured?
|
18
|
+
|
19
|
+
::Contentful::Social::TwitterHandler.new(config.twitter, contentful_client, webhook).tweet
|
20
|
+
|
21
|
+
puts 'Successfully published on Twitter'
|
22
|
+
rescue StandardError => e
|
23
|
+
puts "Error while trying to publish to Twitter: #{e}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def publish_to_facebook(webhook)
|
27
|
+
return unless config.facebook_configured?
|
28
|
+
|
29
|
+
::Contentful::Social::FacebookHandler.new(config.facebook, contentful_client, webhook).post
|
30
|
+
|
31
|
+
puts 'Successfully published on Facebook'
|
32
|
+
rescue StandardError => e
|
33
|
+
puts "Error while trying to publish to Facebook: #{e}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def config
|
37
|
+
::Contentful::Social.config
|
38
|
+
end
|
39
|
+
|
40
|
+
def contentful_client
|
41
|
+
if config.contentful[webhook.space_id]
|
42
|
+
::Contentful::Client.new(
|
43
|
+
access_token: config.contentful[webhook.space_id],
|
44
|
+
space: webhook.space_id,
|
45
|
+
dynamic_entries: :auto,
|
46
|
+
raise_errors: true
|
47
|
+
)
|
48
|
+
else
|
49
|
+
fail "Space '#{webhook.space_id}' not configured"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def can_publish?(webhook)
|
54
|
+
config.contentful[webhook.space_id]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'koala'
|
2
|
+
|
3
|
+
module Contentful
|
4
|
+
module Social
|
5
|
+
class FacebookHandler
|
6
|
+
attr_reader :template, :facebook, :contentful, :webhook
|
7
|
+
|
8
|
+
def initialize(facebook_config, contentful_client, webhook)
|
9
|
+
@template = facebook_config.template
|
10
|
+
@post_to = facebook_config.post_to || 'me'
|
11
|
+
|
12
|
+
@webhook = webhook
|
13
|
+
@contentful = contentful_client
|
14
|
+
@facebook = create_facebook_client(facebook_config)
|
15
|
+
end
|
16
|
+
|
17
|
+
def post
|
18
|
+
body = ::Contentful::Social::Template.new(
|
19
|
+
contentful,
|
20
|
+
webhook,
|
21
|
+
template
|
22
|
+
).render
|
23
|
+
|
24
|
+
facebook.put_connections(@post_to, 'feed', message: body)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def create_facebook_client(facebook_config)
|
30
|
+
Koala.config.api_version = 'v2.6'
|
31
|
+
::Koala::Facebook::API.new(
|
32
|
+
facebook_config.access_token,
|
33
|
+
facebook_config.app_secret
|
34
|
+
)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Contentful
|
2
|
+
module Social
|
3
|
+
module Support
|
4
|
+
def self.find_entry(contentful_client, webhook, include_level = 3)
|
5
|
+
contentful_client.entries('sys.id' => webhook.id, include: include_level).find do |e|
|
6
|
+
e.id == webhook.id
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Contentful
|
2
|
+
module Social
|
3
|
+
class Template
|
4
|
+
attr_reader :contentful, :webhook, :template
|
5
|
+
|
6
|
+
def initialize(contentful_client, webhook, template)
|
7
|
+
@contentful = contentful_client
|
8
|
+
@webhook = webhook
|
9
|
+
@template = template
|
10
|
+
end
|
11
|
+
|
12
|
+
def render
|
13
|
+
template.gsub(/\{\{([\w|\.]+)\}\}/) do |match|
|
14
|
+
contentful_find(match.gsub('{{', '').gsub('}}', ''))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
def contentful_find(field)
|
21
|
+
entry = Support.find_entry(contentful, webhook)
|
22
|
+
|
23
|
+
if field.include?('.')
|
24
|
+
result = entry
|
25
|
+
field.split('.').each do |partial|
|
26
|
+
result = result.public_send(partial)
|
27
|
+
end
|
28
|
+
|
29
|
+
result
|
30
|
+
else
|
31
|
+
entry.public_send(field)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'twitter'
|
2
|
+
require 'open-uri'
|
3
|
+
|
4
|
+
module Contentful
|
5
|
+
module Social
|
6
|
+
class TwitterHandler
|
7
|
+
attr_reader :template, :twitter, :contentful, :webhook
|
8
|
+
|
9
|
+
def initialize(twitter_config, contentful_client, webhook)
|
10
|
+
@possibly_sensitive = twitter_config.possibly_sensitive || false
|
11
|
+
@location = twitter_config.location || Hashie::Mash.new
|
12
|
+
@media = twitter_config.media || nil
|
13
|
+
@template = twitter_config.template
|
14
|
+
|
15
|
+
@webhook = webhook
|
16
|
+
@contentful = contentful_client
|
17
|
+
@twitter = create_twitter_client(twitter_config)
|
18
|
+
end
|
19
|
+
|
20
|
+
def tweet
|
21
|
+
body = ::Contentful::Social::Template.new(
|
22
|
+
contentful,
|
23
|
+
webhook,
|
24
|
+
template
|
25
|
+
).render
|
26
|
+
|
27
|
+
options = {
|
28
|
+
possibly_sensitive: @possibly_sensitive,
|
29
|
+
}
|
30
|
+
|
31
|
+
unless @location.empty?
|
32
|
+
options[:lat] = @location.lat
|
33
|
+
options[:lon] = @location.lon
|
34
|
+
end
|
35
|
+
|
36
|
+
if @media.nil?
|
37
|
+
twitter.update(body, options)
|
38
|
+
else
|
39
|
+
twitter.update_with_media(body, fetch_media, options)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def fetch_media
|
44
|
+
media = Support.find_entry(contentful, webhook).public_send(@media) unless @media.nil?
|
45
|
+
|
46
|
+
open("https:#{media.image_url}") if media
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def create_twitter_client(twitter_config)
|
52
|
+
::Twitter::REST::Client.new do |config|
|
53
|
+
config.consumer_key = twitter_config.consumer_key
|
54
|
+
config.consumer_secret = twitter_config.consumer_secret
|
55
|
+
config.access_token = twitter_config.access_token
|
56
|
+
config.access_token_secret = twitter_config.access_token_secret
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
metadata
ADDED
@@ -0,0 +1,253 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: contentful-social
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Contentful GmbH (David Litvak Bruno)
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-06-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: contentful-webhook-listener
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: contentful
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.9'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.9'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: hashie
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.4'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.4'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: twitter
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '5.0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '5.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: koala
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '2.2'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '2.2'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: bundler
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.11'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.11'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rake
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '10.0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '10.0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rspec
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '3.0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '3.0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: guard
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: guard-rspec
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: pry
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: vcr
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: webmock
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - "~>"
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '1'
|
188
|
+
- - ">="
|
189
|
+
- !ruby/object:Gem::Version
|
190
|
+
version: 1.17.3
|
191
|
+
type: :development
|
192
|
+
prerelease: false
|
193
|
+
version_requirements: !ruby/object:Gem::Requirement
|
194
|
+
requirements:
|
195
|
+
- - "~>"
|
196
|
+
- !ruby/object:Gem::Version
|
197
|
+
version: '1'
|
198
|
+
- - ">="
|
199
|
+
- !ruby/object:Gem::Version
|
200
|
+
version: 1.17.3
|
201
|
+
description: Contentful Social Publishing Gem
|
202
|
+
email:
|
203
|
+
- david.litvak@contentful.com
|
204
|
+
executables:
|
205
|
+
- contentful_social
|
206
|
+
extensions: []
|
207
|
+
extra_rdoc_files: []
|
208
|
+
files:
|
209
|
+
- ".gitignore"
|
210
|
+
- ".rspec"
|
211
|
+
- ".travis.yml"
|
212
|
+
- CODE_OF_CONDUCT.md
|
213
|
+
- Gemfile
|
214
|
+
- Guardfile
|
215
|
+
- LICENSE.txt
|
216
|
+
- README.md
|
217
|
+
- Rakefile
|
218
|
+
- contentful-social.gemspec
|
219
|
+
- example/config.yml
|
220
|
+
- exe/contentful_social
|
221
|
+
- lib/contentful/social.rb
|
222
|
+
- lib/contentful/social/config.rb
|
223
|
+
- lib/contentful/social/controller.rb
|
224
|
+
- lib/contentful/social/facebook_handler.rb
|
225
|
+
- lib/contentful/social/support.rb
|
226
|
+
- lib/contentful/social/template.rb
|
227
|
+
- lib/contentful/social/twitter_handler.rb
|
228
|
+
- lib/contentful/social/version.rb
|
229
|
+
homepage: https://www.contentful.com
|
230
|
+
licenses:
|
231
|
+
- MIT
|
232
|
+
metadata: {}
|
233
|
+
post_install_message:
|
234
|
+
rdoc_options: []
|
235
|
+
require_paths:
|
236
|
+
- lib
|
237
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
238
|
+
requirements:
|
239
|
+
- - ">="
|
240
|
+
- !ruby/object:Gem::Version
|
241
|
+
version: '0'
|
242
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
243
|
+
requirements:
|
244
|
+
- - ">="
|
245
|
+
- !ruby/object:Gem::Version
|
246
|
+
version: '0'
|
247
|
+
requirements: []
|
248
|
+
rubyforge_project:
|
249
|
+
rubygems_version: 2.5.0
|
250
|
+
signing_key:
|
251
|
+
specification_version: 4
|
252
|
+
summary: Contentful Social Publishing Gem
|
253
|
+
test_files: []
|