diff options
author | Eugen Rochko <eugen@zeonfederated.com> | 2016-02-20 22:53:20 +0100 |
---|---|---|
committer | Eugen Rochko <eugen@zeonfederated.com> | 2016-02-20 22:53:20 +0100 |
commit | 9c4856bdb11fc9311ab30a97224cee3dfaec492f (patch) | |
tree | 37fd831e505f040bbd3c583f56d3502ebd75e9c8 /app |
Initial commit
Diffstat (limited to 'app')
-rw-r--r-- | app/api/mastodon/api.rb | 8 | ||||
-rw-r--r-- | app/api/mastodon/entities.rb | 21 | ||||
-rw-r--r-- | app/api/mastodon/ostatus.rb | 63 | ||||
-rw-r--r-- | app/api/mastodon/rest.rb | 13 | ||||
-rw-r--r-- | app/assets/images/.keep | 0 | ||||
-rw-r--r-- | app/assets/javascripts/application.js | 16 | ||||
-rw-r--r-- | app/assets/stylesheets/application.css | 15 | ||||
-rw-r--r-- | app/controllers/application_controller.rb | 5 | ||||
-rw-r--r-- | app/controllers/concerns/.keep | 0 | ||||
-rw-r--r-- | app/helpers/application_helper.rb | 2 | ||||
-rw-r--r-- | app/mailers/.keep | 0 | ||||
-rw-r--r-- | app/models/.keep | 0 | ||||
-rw-r--r-- | app/models/account.rb | 7 | ||||
-rw-r--r-- | app/models/concerns/.keep | 0 | ||||
-rw-r--r-- | app/models/status.rb | 3 | ||||
-rw-r--r-- | app/services/fetch_feed_service.rb | 5 | ||||
-rw-r--r-- | app/services/follow_remote_user_service.rb | 60 | ||||
-rw-r--r-- | app/services/process_feed_update_service.rb | 20 | ||||
-rw-r--r-- | app/views/layouts/application.html.erb | 14 |
19 files changed, 252 insertions, 0 deletions
diff --git a/app/api/mastodon/api.rb b/app/api/mastodon/api.rb new file mode 100644 index 000000000..39469dc65 --- /dev/null +++ b/app/api/mastodon/api.rb @@ -0,0 +1,8 @@ +module Mastodon + class API < Grape::API + rescue_from :all + + mount Mastodon::Ostatus + mount Mastodon::Rest + end +end diff --git a/app/api/mastodon/entities.rb b/app/api/mastodon/entities.rb new file mode 100644 index 000000000..a3f40ec48 --- /dev/null +++ b/app/api/mastodon/entities.rb @@ -0,0 +1,21 @@ +module Mastodon + module Entities + class Account < Grape::Entity + expose :username + expose :domain + end + + class Status < Grape::Entity + format_with(:iso_timestamp) { |dt| dt.iso8601 } + + expose :uri + expose :text + expose :account, using: Mastodon::Entities::Account + + with_options(format_with: :iso_timestamp) do + expose :created_at + expose :updated_at + end + end + end +end diff --git a/app/api/mastodon/ostatus.rb b/app/api/mastodon/ostatus.rb new file mode 100644 index 000000000..fcde980f7 --- /dev/null +++ b/app/api/mastodon/ostatus.rb @@ -0,0 +1,63 @@ +module Mastodon + class Ostatus < Grape::API + format :txt + + before do + @account = Account.find(params[:id]) + end + + resource :subscriptions do + helpers do + def subscription_url(account) + "https://649841dc.ngrok.io/api#{subscriptions_path(id: account.id)}" + end + end + + desc 'Receive updates from a feed' + + params do + requires :id, type: String, desc: 'Account ID' + end + + post ':id' do + body = request.body.read + + if @account.subscription(subscription_url(@account)).verify(body, env['HTTP_X_HUB_SIGNATURE']) + ProcessFeedUpdateService.new.(body, @account) + status 201 + else + status 202 + end + end + + desc 'Confirm PuSH subscription to a feed' + + params do + requires :id, type: String, desc: 'Account ID' + requires 'hub.topic', type: String, desc: 'Topic URL' + requires 'hub.verify_token', type: String, desc: 'Verification token' + requires 'hub.challenge', type: String, desc: 'Hub challenge' + end + + get ':id' do + if @account.subscription(subscription_url(@account)).valid?(params['hub.topic'], params['hub.verify_token']) + params['hub.challenge'] + else + error! :not_found, 404 + end + end + end + + resource :salmon do + desc 'Receive Salmon updates' + + params do + requires :id, type: String, desc: 'Account ID' + end + + post ':id' do + # todo + end + end + end +end diff --git a/app/api/mastodon/rest.rb b/app/api/mastodon/rest.rb new file mode 100644 index 000000000..e011ab34d --- /dev/null +++ b/app/api/mastodon/rest.rb @@ -0,0 +1,13 @@ +module Mastodon + class Rest < Grape::API + version 'v1', using: :path + format :json + + resource :statuses do + desc 'Return a public timeline' + get :all do + present Status.all, with: Mastodon::Entities::Status + end + end + end +end diff --git a/app/assets/images/.keep b/app/assets/images/.keep new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/app/assets/images/.keep diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js new file mode 100644 index 000000000..e07c5a830 --- /dev/null +++ b/app/assets/javascripts/application.js @@ -0,0 +1,16 @@ +// This is a manifest file that'll be compiled into application.js, which will include all the files +// listed below. +// +// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, +// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. +// +// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the +// compiled file. +// +// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details +// about supported directives. +// +//= require jquery +//= require jquery_ujs +//= require turbolinks +//= require_tree . diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css new file mode 100644 index 000000000..f9cd5b348 --- /dev/null +++ b/app/assets/stylesheets/application.css @@ -0,0 +1,15 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, + * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any styles + * defined in the other CSS/SCSS files in this directory. It is generally better to create a new + * file per style scope. + * + *= require_tree . + *= require_self + */ diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb new file mode 100644 index 000000000..d83690e1b --- /dev/null +++ b/app/controllers/application_controller.rb @@ -0,0 +1,5 @@ +class ApplicationController < ActionController::Base + # Prevent CSRF attacks by raising an exception. + # For APIs, you may want to use :null_session instead. + protect_from_forgery with: :exception +end diff --git a/app/controllers/concerns/.keep b/app/controllers/concerns/.keep new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/app/controllers/concerns/.keep diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb new file mode 100644 index 000000000..de6be7945 --- /dev/null +++ b/app/helpers/application_helper.rb @@ -0,0 +1,2 @@ +module ApplicationHelper +end diff --git a/app/mailers/.keep b/app/mailers/.keep new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/app/mailers/.keep diff --git a/app/models/.keep b/app/models/.keep new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/app/models/.keep diff --git a/app/models/account.rb b/app/models/account.rb new file mode 100644 index 000000000..c0b153794 --- /dev/null +++ b/app/models/account.rb @@ -0,0 +1,7 @@ +class Account < ActiveRecord::Base + has_many :statuses, inverse_of: :account + + def subscription(webhook_url) + @subscription ||= OStatus2::Subscription.new(self.remote_url, secret: self.secret, token: self.verify_token, webhook: webhook_url, hub: self.hub_url) + end +end diff --git a/app/models/concerns/.keep b/app/models/concerns/.keep new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/app/models/concerns/.keep diff --git a/app/models/status.rb b/app/models/status.rb new file mode 100644 index 000000000..a1278ccaa --- /dev/null +++ b/app/models/status.rb @@ -0,0 +1,3 @@ +class Status < ActiveRecord::Base + belongs_to :account, inverse_of: :statuses +end diff --git a/app/services/fetch_feed_service.rb b/app/services/fetch_feed_service.rb new file mode 100644 index 000000000..3b8efbe3b --- /dev/null +++ b/app/services/fetch_feed_service.rb @@ -0,0 +1,5 @@ +class FetchFeedService + def call(account) + # todo + end +end diff --git a/app/services/follow_remote_user_service.rb b/app/services/follow_remote_user_service.rb new file mode 100644 index 000000000..f3c0e68df --- /dev/null +++ b/app/services/follow_remote_user_service.rb @@ -0,0 +1,60 @@ +class FollowRemoteUserService + include GrapeRouteHelpers::NamedRouteMatcher + + def call(user) + username, domain = user.split('@') + account = Account.where(username: username, domain: domain).first + + return account unless account.nil? + + account = Account.new(username: username, domain: domain) + data = Goldfinger.finger("acct:#{user}") + + account.remote_url = data.link('http://schemas.google.com/g/2010#updates-from').href + account.salmon_url = data.link('salmon').href + account.public_key = magic_key_to_pem(data.link('magic-public-key').href) + account.private_key = nil + + account.secret = SecureRandom.hex + account.verify_token = SecureRandom.hex + + feed = get_feed(account.remote_url) + hubs = feed.xpath('//xmlns:link[@rel="hub"]') + + return false if hubs.empty? || hubs.first.attribute('href').nil? + + account.hub_url = hubs.first.attribute('href').value + account.save! + + subscription = account.subscription(subscription_url(account)) + subscription.subscribe + rescue Goldfinger::Error, HTTP::Error => e + false + end + + private + + def get_feed(url) + response = http_client.get(Addressable::URI.parse(url)) + Nokogiri::XML(response) + end + + def magic_key_to_pem(magic_key) + _, modulus, exponent = magic_key.split('.') + modulus, exponent = [modulus, exponent].map { |n| Base64.urlsafe_decode64(n).bytes.inject(0) { |num, byte| (num << 8) | byte } } + + key = OpenSSL::PKey::RSA.new + key.n = modulus + key.d = exponent + + key.to_pem + end + + def http_client + HTTP + end + + def subscription_url(account) + "https://649841dc.ngrok.io/api#{subscriptions_path(id: account.id)}" + end +end diff --git a/app/services/process_feed_update_service.rb b/app/services/process_feed_update_service.rb new file mode 100644 index 000000000..0585fad7a --- /dev/null +++ b/app/services/process_feed_update_service.rb @@ -0,0 +1,20 @@ +class ProcessFeedUpdateService + def call(body, account) + xml = Nokogiri::XML(body) + + xml.xpath('/xmlns:feed/xmlns:entry').each do |entry| + uri = entry.at_xpath('./xmlns:id').content + status = Status.find_by(uri: uri) + + next unless status.nil? + + status = Status.new + status.account = account + status.uri = uri + status.text = entry.at_xpath('./xmlns:content').content + status.created_at = entry.at_xpath('./xmlns:published').content + status.updated_at = entry.at_xpath('./xmlns:updated').content + status.save! + end + end +end diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb new file mode 100644 index 000000000..ff0d4c865 --- /dev/null +++ b/app/views/layouts/application.html.erb @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html> +<head> + <title>Mastodon</title> + <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> + <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> + <%= csrf_meta_tags %> +</head> +<body> + +<%= yield %> + +</body> +</html> |