Compare commits
15 Commits
Author | SHA1 | Date |
---|---|---|
|
ab52dcd13d | |
|
9cf70a74ba | |
|
663a05be75 | |
|
aaf143e5b5 | |
|
72ce72fa2c | |
|
91be33ac99 | |
|
6881206ca1 | |
|
50b8b57065 | |
|
51904f3808 | |
|
011d0c16b8 | |
|
d51c885fec | |
|
40ed5f4414 | |
|
966b7554a2 | |
|
99ac0f1be4 | |
|
35b7477bac |
|
@ -1 +1 @@
|
|||
/Gemfile.lock
|
||||
/vendor/bundle/
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -2,5 +2,7 @@ source 'https://rubygems.org'
|
|||
|
||||
gem 'mastodon-api', '~> 1.1.0', require: 'mastodon', git: 'https://github.com/tootsuite/mastodon-api.git'
|
||||
gem 'awesome_print'
|
||||
gem 'dotenv'
|
||||
gem 'pry'
|
||||
|
||||
gemspec
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
GIT
|
||||
remote: https://github.com/tootsuite/mastodon-api.git
|
||||
revision: a3ff60a872191aa2f499a2b4c7a85045ead14e64
|
||||
specs:
|
||||
mastodon-api (1.1.0)
|
||||
addressable (~> 2.4)
|
||||
buftok
|
||||
http (~> 2.0)
|
||||
|
||||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
cross-post (0.2.1)
|
||||
launchy (~> 2.4, >= 2.4.3)
|
||||
mastodon-api (~> 1.1, >= 1.1.0)
|
||||
oauth (~> 0.5, >= 0.5.3)
|
||||
oauth2 (~> 1.4, >= 1.4.0)
|
||||
sanitize (~> 4.5, >= 4.5.0)
|
||||
twitter (~> 6.1, >= 6.1.0)
|
||||
twitter-text (~> 1.14, >= 1.14.7)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
addressable (2.5.2)
|
||||
public_suffix (>= 2.0.2, < 4.0)
|
||||
awesome_print (1.8.0)
|
||||
buftok (0.2.0)
|
||||
coderay (1.1.2)
|
||||
crass (1.0.3)
|
||||
diff-lcs (1.3)
|
||||
domain_name (0.5.20170404)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (2.2.1)
|
||||
equalizer (0.0.11)
|
||||
faraday (0.11.0)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
http (2.2.2)
|
||||
addressable (~> 2.3)
|
||||
http-cookie (~> 1.0)
|
||||
http-form_data (~> 1.0.1)
|
||||
http_parser.rb (~> 0.6.0)
|
||||
http-cookie (1.0.3)
|
||||
domain_name (~> 0.5)
|
||||
http-form_data (1.0.3)
|
||||
http_parser.rb (0.6.0)
|
||||
jwt (1.5.6)
|
||||
launchy (2.4.3)
|
||||
addressable (~> 2.3)
|
||||
memoizable (0.4.2)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
method_source (0.9.0)
|
||||
mini_portile2 (2.3.0)
|
||||
multi_json (1.12.2)
|
||||
multi_xml (0.6.0)
|
||||
multipart-post (2.0.0)
|
||||
naught (1.1.0)
|
||||
nokogiri (1.8.1)
|
||||
mini_portile2 (~> 2.3.0)
|
||||
nokogumbo (1.4.13)
|
||||
nokogiri
|
||||
oauth (0.5.4)
|
||||
oauth2 (1.4.0)
|
||||
faraday (>= 0.8, < 0.13)
|
||||
jwt (~> 1.0)
|
||||
multi_json (~> 1.3)
|
||||
multi_xml (~> 0.5)
|
||||
rack (>= 1.2, < 3)
|
||||
pry (0.11.3)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.9.0)
|
||||
public_suffix (3.0.1)
|
||||
rack (2.0.3)
|
||||
rspec (3.6.0)
|
||||
rspec-core (~> 3.6.0)
|
||||
rspec-expectations (~> 3.6.0)
|
||||
rspec-mocks (~> 3.6.0)
|
||||
rspec-core (3.6.0)
|
||||
rspec-support (~> 3.6.0)
|
||||
rspec-expectations (3.6.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.6.0)
|
||||
rspec-mocks (3.6.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.6.0)
|
||||
rspec-support (3.6.0)
|
||||
sanitize (4.5.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.4.4)
|
||||
nokogumbo (~> 1.4.1)
|
||||
simple_oauth (0.3.1)
|
||||
thread_safe (0.3.6)
|
||||
twitter (6.1.0)
|
||||
addressable (~> 2.5)
|
||||
buftok (~> 0.2.0)
|
||||
equalizer (= 0.0.11)
|
||||
faraday (~> 0.11.0)
|
||||
http (~> 2.1)
|
||||
http_parser.rb (~> 0.6.0)
|
||||
memoizable (~> 0.4.2)
|
||||
naught (~> 1.1)
|
||||
simple_oauth (~> 0.3.1)
|
||||
twitter-text (1.14.7)
|
||||
unf (~> 0.1.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.7.4)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
awesome_print
|
||||
bundler (~> 1.15, >= 1.15.4)
|
||||
cross-post!
|
||||
dotenv
|
||||
mastodon-api (~> 1.1.0)!
|
||||
pry
|
||||
rspec (~> 3.6.0, >= 3.6.0)
|
||||
|
||||
BUNDLED WITH
|
||||
1.16.1
|
|
@ -8,7 +8,7 @@ To use it:
|
|||
|
||||
* Clone this repository somewhere (`git clone https://git.imirhil.fr/aeris/cross-post/`)
|
||||
* Install dependencies with Bundler (`bundler install`)
|
||||
* Create a `$HOME/.cross-post.yml` configuration file, based on the example available [here](https://git.imirhil.fr/aeris/cross-post/src/master/config.yml)
|
||||
* Create a `$HOME/.config/cross-post/config.yml` configuration file, based on the example available [here](https://git.imirhil.fr/aeris/cross-post/src/master/config.yml)
|
||||
* Register the app on Twitter (`bundle exec bin/twitter-register`)
|
||||
* You can reuse my Twitter app OAuth credentials, or register a new app from scratch [here](https://apps.twitter.com/)
|
||||
* Register the app on Mastodon (`bundle exec bin/mastodon-register`)
|
||||
|
@ -18,7 +18,3 @@ To use it:
|
|||
* Enjoy
|
||||
|
||||
If needed, a SystemD unit example is available [here](https://git.imirhil.fr/aeris/cross-post/src/master/mastodon-twitter.service)
|
||||
|
||||
# Todo
|
||||
|
||||
* Publishing on [RubyGems](https://rubygems.org/)
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
#!/usr/bin/env ruby
|
||||
require 'dotenv/load'
|
||||
require 'cross-post'
|
||||
require 'awesome_print'
|
||||
require 'securerandom'
|
||||
|
||||
class ::Twitter::REST::Client
|
||||
def upload(*args, **kargs)
|
||||
ap type: :upload, args: args, kargs: kargs
|
||||
SecureRandom.uuid
|
||||
end
|
||||
|
||||
def update(*args, **kargs)
|
||||
ap type: :update, args: args, kargs: kargs
|
||||
SecureRandom.uuid
|
||||
end
|
||||
end
|
||||
|
||||
CrossPost.feed
|
|
@ -1,3 +1,3 @@
|
|||
#!/usr/bin/env ruby
|
||||
require 'cross-post'
|
||||
CrossPost.feed
|
||||
loop { CrossPost.feed rescue nil }
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
#!/usr/bin/env ruby
|
||||
require 'cross-post'
|
||||
CrossPost.feed
|
|
@ -3,31 +3,59 @@ require 'cross-post'
|
|||
require 'mastodon'
|
||||
require 'oauth2'
|
||||
require 'launchy'
|
||||
require 'uri'
|
||||
|
||||
config = CrossPost::Config.new
|
||||
print 'Mastodon URL ? '
|
||||
url = gets.strip
|
||||
url = "https://#{url}" unless url.start_with? 'https://'
|
||||
config['mastodon.url'] = url
|
||||
config = CrossPost::Config.new
|
||||
settings = config[:settings]
|
||||
|
||||
client_id, client_secret = unless config['mastodon.consumer']
|
||||
token = SecureRandom.hex 64
|
||||
redirect_url = 'urn:ietf:wg:oauth:2.0:oob'
|
||||
url = settings['mastodon.url']
|
||||
unless url
|
||||
print 'Mastodon URL? '
|
||||
url = gets.chomp
|
||||
url = "https://#{url}" if URI.parse(url).class == URI::Generic
|
||||
settings['mastodon.url'] = url
|
||||
end
|
||||
|
||||
user = settings['mastodon.user']
|
||||
unless user
|
||||
print 'Mastodon username? '
|
||||
user = gets.chomp
|
||||
settings['mastodon.user'] = user
|
||||
end
|
||||
|
||||
APP_NAME = 'CrossPost'
|
||||
APP_URL = 'https://git.imirhil.fr/aeris/cross-post/'
|
||||
REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob'
|
||||
SCOPES = 'read'
|
||||
|
||||
client_id, client_secret = unless settings['mastodon.consumer']
|
||||
puts 'Creating new app'
|
||||
token = SecureRandom.hex 64
|
||||
client = Mastodon::REST::Client.new base_url: url, bearer_token: token
|
||||
app = client.create_app 'CrossPost', redirect_url,
|
||||
'read write', 'https://git.imirhil.fr/aeris/cross-post/'
|
||||
app = client.create_app APP_NAME, REDIRECT_URI, SCOPES, APP_URL
|
||||
|
||||
settings['mastodon.consumer.key'] = app.client_id
|
||||
settings['mastodon.consumer.secret'] = app.client_secret
|
||||
[app.client_id, app.client_secret]
|
||||
else
|
||||
[config['mastodon.consumer.key'], config['mastodon.consumer.secret']]
|
||||
[settings['mastodon.consumer.key'], settings['mastodon.consumer.secret']]
|
||||
end
|
||||
|
||||
client = OAuth2::Client.new client_id, client_secret, site: url
|
||||
url = client.auth_code.authorize_url redirect_uri: redirect_url
|
||||
Launchy.open url
|
||||
url = client.auth_code.authorize_url redirect_uri: REDIRECT_URI
|
||||
puts url
|
||||
begin
|
||||
Launchy.open url
|
||||
rescue
|
||||
end
|
||||
|
||||
print 'Token ? '
|
||||
token = gets.chomp
|
||||
config['mastodon.token'] = token
|
||||
print 'Code? '
|
||||
code = gets.chomp
|
||||
|
||||
config.save
|
||||
token = client.auth_code.get_token code, scopes: SCOPES, redirect_uri: REDIRECT_URI
|
||||
token = token.token
|
||||
puts "Token: #{token}"
|
||||
|
||||
settings['mastodon.token'] = token
|
||||
|
||||
settings.save
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/env ruby
|
||||
require 'cross-post'
|
||||
require 'awesome_print'
|
||||
|
||||
class Twitter::REST::Client
|
||||
def update(*args, **kargs)
|
||||
ap args
|
||||
ap kargs
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
config = CrossPost::Config.new
|
||||
url = config['mastodon.url']
|
||||
token = config['mastodon.token']
|
||||
client = ::Mastodon::REST::Client.new base_url: url, bearer_token: token
|
||||
status = client.status 439490
|
||||
ap status
|
||||
|
||||
twitter = CrossPost::Twitter.new config
|
||||
twitter.post_status status
|
||||
|
||||
# media = '/home/aeris/Images/tux-debian.png'
|
||||
# media = client.upload_media media
|
||||
# status = client.create_status 'Test', nil, [media.id]
|
||||
# ap status
|
||||
# sleep 5
|
||||
# client.destroy_status status.id
|
|
@ -0,0 +1,15 @@
|
|||
#!/usr/bin/env ruby
|
||||
require 'cross-post'
|
||||
require 'awesome_print'
|
||||
|
||||
config = CrossPost::Config.new
|
||||
url = config['mastodon.url']
|
||||
token = config['mastodon.token']
|
||||
client = ::Mastodon::REST::Client.new base_url: url, bearer_token: token
|
||||
|
||||
media = '/home/aeris/Images/tux-debian.png'
|
||||
media = client.upload_media media
|
||||
status = client.create_status 'Test', nil, [media.id]
|
||||
ap status
|
||||
sleep 5
|
||||
client.destroy_status status.id
|
|
@ -0,0 +1,3 @@
|
|||
#!/usr/bin/env ruby
|
||||
require 'cross-post'
|
||||
CrossPost.new.twitter.post File.read ARGV[0]
|
|
@ -6,9 +6,10 @@ require 'launchy'
|
|||
require 'awesome_print'
|
||||
|
||||
config = CrossPost::Config.new
|
||||
settings = config[:settings]
|
||||
|
||||
consumer_key = config['twitter.consumer.key']
|
||||
consumer_secret = config['twitter.consumer.secret']
|
||||
consumer_key = settings['twitter.consumer.key']
|
||||
consumer_secret = settings['twitter.consumer.secret']
|
||||
|
||||
client = OAuth::Consumer.new consumer_key,
|
||||
consumer_secret,
|
||||
|
@ -16,15 +17,18 @@ client = OAuth::Consumer.new consumer_key,
|
|||
request_token = client.get_request_token
|
||||
url = request_token.authorize_url
|
||||
puts url
|
||||
Launchy.open url
|
||||
begin
|
||||
Launchy.open url
|
||||
rescue
|
||||
end
|
||||
|
||||
print 'PIN ? '
|
||||
pin = gets.chomp
|
||||
|
||||
access_token = request_token.get_access_token oauth_verifier: pin
|
||||
|
||||
config['twitter.access.token'] = access_token.token
|
||||
config['twitter.access.secret'] = access_token.secret
|
||||
settings['twitter.access.token'] = access_token.token
|
||||
settings['twitter.access.secret'] = access_token.secret
|
||||
|
||||
config.save
|
||||
settings.save
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
|
|||
spec.version = CrossPost::VERSION
|
||||
spec.authors = ['aeris']
|
||||
spec.email = ['aeris@imirhil.fr']
|
||||
spec.summary = "Cross post to Mastodon and Twitter"
|
||||
spec.summary = 'Cross post to Mastodon and Twitter'
|
||||
spec.homepage = 'https://git.imirhil.fr/aeris/cross-post/'
|
||||
spec.license = 'AGPL-3.0+'
|
||||
|
||||
|
@ -16,13 +16,14 @@ Gem::Specification.new do |spec|
|
|||
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename f }
|
||||
spec.test_files = spec.files.grep %r{^(test|spec|features)/}
|
||||
|
||||
spec.add_development_dependency 'bundler', '~> 1.15.4'
|
||||
spec.add_development_dependency 'bundler', '~> 1.15', '>= 1.15.4'
|
||||
spec.add_development_dependency 'rspec', '~> 3.6.0', '>= 3.6.0'
|
||||
|
||||
spec.add_dependency 'twitter', '~> 6.1.0'
|
||||
spec.add_dependency 'mastodon-api', '~> 1.1.0'
|
||||
spec.add_dependency 'twitter-text', '~> 1.14.7'
|
||||
spec.add_dependency 'oauth', '~> 0.5.3'
|
||||
spec.add_dependency 'oauth2', '~> 1.4.0'
|
||||
spec.add_dependency 'launchy', '~> 2.4.3'
|
||||
spec.add_dependency 'sanitize', '~> 4.5.0'
|
||||
spec.add_dependency 'twitter', '~> 6.1', '>= 6.1.0'
|
||||
spec.add_dependency 'mastodon-api', '~> 1.1', '>= 1.1.0'
|
||||
spec.add_dependency 'twitter-text', '~> 1.14', '>= 1.14.7'
|
||||
spec.add_dependency 'oauth', '~> 0.5', '>= 0.5.3'
|
||||
spec.add_dependency 'oauth2', '~> 1.4', '>= 1.4.0'
|
||||
spec.add_dependency 'launchy', '~> 2.4', '>= 2.4.3'
|
||||
spec.add_dependency 'sanitize', '~> 4.5', '>= 4.5.0'
|
||||
end
|
||||
|
|
|
@ -2,6 +2,7 @@ require 'cross-post/config'
|
|||
require 'cross-post/mastodon'
|
||||
require 'cross-post/twitter'
|
||||
require 'open-uri'
|
||||
require 'logger'
|
||||
|
||||
# Force OpenURI#open to return a TempFile and not a StringIO
|
||||
OpenURI::Buffer.send :remove_const, 'StringMax'
|
||||
|
@ -9,6 +10,15 @@ OpenURI::Buffer.const_set 'StringMax', 0
|
|||
|
||||
|
||||
class CrossPost
|
||||
LOGGER = Logger.new STDERR
|
||||
LOGGER.level = Logger.const_get ENV.fetch('LOG', 'INFO').upcase
|
||||
LOGGER.formatter = proc do |severity, time, _, msg|
|
||||
time = time.strftime '%Y-%m-%dT%H:%M:%S.%6N'.freeze
|
||||
"#{time} #{severity} #{msg}\n"
|
||||
end
|
||||
|
||||
attr_reader :mastodon, :twitter
|
||||
|
||||
def initialize
|
||||
@config = Config.new
|
||||
@mastodon = Mastodon.new @config
|
||||
|
|
|
@ -1,40 +1,120 @@
|
|||
require 'yaml'
|
||||
require 'fileutils'
|
||||
|
||||
class CrossPost
|
||||
class Config
|
||||
def initialize
|
||||
@file = ENV.fetch 'CROSS_POST_CONFIG', File.join(Dir.home, '.cross-post.yml')
|
||||
File.open(@file) { |f| @config = YAML.safe_load f }
|
||||
end
|
||||
DEFAULT_CONFIG_FOLDER = File.join Dir.home, '.config/cross-post'
|
||||
DEFAULT_CONFIG_FILE = 'config.yml'
|
||||
|
||||
def [](key)
|
||||
current = @config
|
||||
key.split(/\./).each do |k|
|
||||
current = current[k]
|
||||
return nil if current.nil?
|
||||
class SubConfig
|
||||
def initialize(config = {})
|
||||
@config = config
|
||||
end
|
||||
current
|
||||
end
|
||||
|
||||
def []=(key, value)
|
||||
*key, last = key.split(/\./)
|
||||
current = @config
|
||||
key.each do |k|
|
||||
next_ = current[k]
|
||||
case next_
|
||||
when nil
|
||||
next_ = current[k] = {}
|
||||
when Hash
|
||||
def each(&block)
|
||||
@config.each &block
|
||||
end
|
||||
|
||||
def [](key)
|
||||
case key
|
||||
when String
|
||||
current = @config
|
||||
key.split(/\./).each do |k|
|
||||
current = current[k]
|
||||
return nil if current.nil?
|
||||
end
|
||||
current
|
||||
else
|
||||
raise "Invalid entry, Hash expected, had #{next_.class} (#{next_})"
|
||||
@config[key]
|
||||
end
|
||||
end
|
||||
|
||||
def fetch(key, default = nil)
|
||||
self[key] || default
|
||||
end
|
||||
|
||||
def []=(key, value)
|
||||
case key
|
||||
when String
|
||||
*key, last = key.to_s.split(/\./)
|
||||
current = @config
|
||||
key.each do |k|
|
||||
next_ = current[k]
|
||||
case next_
|
||||
when nil
|
||||
next_ = current[k] = {}
|
||||
when Hash
|
||||
else
|
||||
raise "Invalid entry, Hash expected, had #{next_.class} (#{next_})"
|
||||
end
|
||||
current = next_
|
||||
end
|
||||
current[last] = value
|
||||
else
|
||||
@config[key] = value
|
||||
end
|
||||
current = next_
|
||||
end
|
||||
current[last] = value
|
||||
end
|
||||
|
||||
def save
|
||||
File.write @file, YAML.dump(@config)
|
||||
class FifoSubConfig < SubConfig
|
||||
def initialize(size = 100)
|
||||
@size = size
|
||||
@keys = []
|
||||
super({})
|
||||
end
|
||||
|
||||
def []=(key, value)
|
||||
@keys.delete key
|
||||
value = super key, value
|
||||
@keys << key
|
||||
while @keys.size > @size
|
||||
key = @keys.delete_at 0
|
||||
@config.delete key
|
||||
end
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
class FileSubConfig < SubConfig
|
||||
def initialize(file)
|
||||
@file = file
|
||||
super YAML.load_file @file
|
||||
end
|
||||
|
||||
def put(key, value, save: false)
|
||||
self[key] = value
|
||||
self.save if save
|
||||
end
|
||||
|
||||
def save
|
||||
LOGGER.debug "Saving #{@file}"
|
||||
yaml = YAML.dump @config
|
||||
File.write @file, yaml
|
||||
end
|
||||
end
|
||||
|
||||
def initialize
|
||||
@configs = {}
|
||||
@dir = ENV.fetch 'CONFIG_FOLDER', DEFAULT_CONFIG_FOLDER
|
||||
file = ENV.fetch 'CONFIG_FILE', DEFAULT_CONFIG_FILE
|
||||
self.load :settings, file
|
||||
self.load :users
|
||||
self[:posts] = FifoSubConfig.new
|
||||
end
|
||||
|
||||
def [](name)
|
||||
@configs[name]
|
||||
end
|
||||
|
||||
def []=(name, value)
|
||||
@configs[name] = value
|
||||
end
|
||||
|
||||
def load(name, file = nil)
|
||||
file ||= "#{name}.yml"
|
||||
file = File.join @dir, file
|
||||
File.write(file, YAML.dump({})) unless File.exist? file
|
||||
self[name] = FileSubConfig.new file
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,15 +1,26 @@
|
|||
require 'mastodon'
|
||||
require 'sanitize'
|
||||
require 'awesome_print'
|
||||
|
||||
class CrossPost
|
||||
class Mastodon
|
||||
def initialize(config)
|
||||
url = config['mastodon.url']
|
||||
token = config['mastodon.token']
|
||||
user = config['mastodon.user']
|
||||
@user_url = URI.join(url, "/@#{user}").to_s
|
||||
@client = ::Mastodon::REST::Client.new base_url: url, bearer_token: token
|
||||
@stream = ::Mastodon::Streaming::Client.new base_url: url, bearer_token: token
|
||||
settings = config[:settings]
|
||||
@posts = config[:posts]
|
||||
|
||||
url = settings['mastodon.url']
|
||||
token = settings['mastodon.token']
|
||||
user = settings['mastodon.user']
|
||||
|
||||
LOGGER.debug "Mastodon base URL: #{url}"
|
||||
@client = ::Mastodon::REST::Client.new base_url: url, bearer_token: token
|
||||
|
||||
stream_url = settings.fetch 'mastodon.stream_url', url
|
||||
LOGGER.debug "Mastodon stream URL: #{stream_url}"
|
||||
@stream = ::Mastodon::Streaming::Client.new base_url: stream_url, bearer_token: token
|
||||
|
||||
@user_url = URI.join(ENV.fetch('BASE_USER_URL', url), "/@#{user}").to_s
|
||||
LOGGER.debug "Mastodon user URL: #{@user_url}"
|
||||
end
|
||||
|
||||
def feed(twitter)
|
||||
|
@ -17,11 +28,13 @@ class CrossPost
|
|||
begin
|
||||
case object
|
||||
when ::Mastodon::Status
|
||||
LOGGER.info { 'Receiving status' }
|
||||
LOGGER.debug { object.ai }
|
||||
next if reject? object
|
||||
twitter.post object
|
||||
twitter.post_status object
|
||||
end
|
||||
rescue => e
|
||||
#$stderr.puts e
|
||||
LOGGER.error e
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
@ -30,9 +43,11 @@ class CrossPost
|
|||
private
|
||||
|
||||
def reject?(status)
|
||||
status.account.url != @user_url or
|
||||
status.visibility != 'public' or
|
||||
status.in_reply_to_id
|
||||
return true if status.account.url != @user_url or
|
||||
status.visibility != 'public'
|
||||
reply = status.in_reply_to_id
|
||||
return true if reply and !@posts[reply]
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,40 +1,72 @@
|
|||
require 'twitter'
|
||||
require 'twitter-text'
|
||||
require 'sanitize'
|
||||
require 'cgi'
|
||||
require 'ostruct'
|
||||
|
||||
::Twitter::Validation::MAX_LENGTH = 280
|
||||
|
||||
class CrossPost
|
||||
class Twitter
|
||||
def initialize(config)
|
||||
settings = config[:settings]
|
||||
@posts = config[:posts]
|
||||
@users = config[:users]
|
||||
|
||||
config = {
|
||||
consumer_key: config['twitter.consumer.key'],
|
||||
consumer_secret: config['twitter.consumer.secret'],
|
||||
access_token: config['twitter.access.token'],
|
||||
access_token_secret: config['twitter.access.secret']
|
||||
consumer_key: settings['twitter.consumer.key'],
|
||||
consumer_secret: settings['twitter.consumer.secret'],
|
||||
access_token: settings['twitter.access.token'],
|
||||
access_token_secret: settings['twitter.access.secret']
|
||||
}
|
||||
@client = ::Twitter::REST::Client.new config
|
||||
@stream = ::Twitter::Streaming::Client.new config
|
||||
end
|
||||
|
||||
def post(status)
|
||||
content = Sanitize.clean status.content
|
||||
last = nil
|
||||
parts = split content
|
||||
|
||||
attachments = status.media_attachments
|
||||
media = attachments.collect do |f|
|
||||
f = open f.url
|
||||
begin
|
||||
@client.upload f
|
||||
ensure
|
||||
f.close
|
||||
f.unlink
|
||||
end
|
||||
end
|
||||
def post(content, media = [], id:, reply_to:)
|
||||
reply_to = OpenStruct.new id: reply_to unless reply_to.respond_to? :id
|
||||
|
||||
media = media.collect { |f| @client.upload f }
|
||||
parts = split content
|
||||
unless media.empty?
|
||||
first, *parts = parts
|
||||
last = @client.update first, media_ids: media.join(',')
|
||||
reply_to = @client.update first, media_ids: media.join(','), in_reply_to_status: reply_to
|
||||
end
|
||||
parts.each { |p| reply_to = @client.update p, in_reply_to_status: reply_to }
|
||||
|
||||
reply_to = reply_to.id if reply_to.respond_to? :id
|
||||
@posts[id] = reply_to
|
||||
end
|
||||
|
||||
WHITESPACE_TAGS = {
|
||||
'br' => { before: "\n", after: '' },
|
||||
'div' => { before: "\n", after: "\n" },
|
||||
'p' => { before: "\n", after: "\n" }
|
||||
}.freeze
|
||||
|
||||
def post_status(status)
|
||||
content = status.content
|
||||
content = Sanitize.clean(content, whitespace_elements: WHITESPACE_TAGS).strip
|
||||
content = CGI.unescape_html content
|
||||
|
||||
@users.each do |mastodon, twitter|
|
||||
content = content.gsub /@\b#{mastodon}\b/, "@#{twitter}"
|
||||
end
|
||||
|
||||
media = status.media_attachments.collect { |f| open f.url }
|
||||
|
||||
LOGGER.info { 'Sending to twitter' }
|
||||
LOGGER.debug { " Content: #{content}" }
|
||||
LOGGER.debug { " Attachments: #{media.size}" }
|
||||
|
||||
reply = status.in_reply_to_id
|
||||
reply_to = reply ? @posts[reply] : nil
|
||||
self.post content, media, id: status.id, reply_to: reply_to
|
||||
|
||||
media.each do |f|
|
||||
f.close
|
||||
f.unlink
|
||||
end
|
||||
parts.each { |p| last = @client.update p, in_reply_to_status: last }
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -42,7 +74,7 @@ class CrossPost
|
|||
def split(text)
|
||||
parts = []
|
||||
part = ''
|
||||
words = text.split ' '
|
||||
words = text.split /\ /
|
||||
words.each do |word|
|
||||
old_part = part
|
||||
part += ' ' unless part == ''
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
class CrossPost
|
||||
VERSION = '0.1.1'.freeze
|
||||
VERSION = '0.2.1'.freeze
|
||||
end
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
require 'cross-post'
|
||||
|
||||
RSpec.describe CrossPost::Config::FifoSubConfig do
|
||||
it 'must remove first value in case of overflow' do
|
||||
config = CrossPost::Config::FifoSubConfig.new 2
|
||||
config[:foo] = :foo
|
||||
config[:bar] = :bar
|
||||
|
||||
expect(config[:foo]).to be :foo
|
||||
expect(config[:bar]).to be :bar
|
||||
expect(config[:baz]).to be_nil
|
||||
|
||||
config[:baz] = :baz
|
||||
|
||||
expect(config[:foo]).to be_nil
|
||||
expect(config[:bar]).to be :bar
|
||||
expect(config[:baz]).to be :baz
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue