diff --git a/app/logical/deviant_art_api_client.rb b/app/logical/deviant_art_api_client.rb new file mode 100644 index 000000000..252e68ce5 --- /dev/null +++ b/app/logical/deviant_art_api_client.rb @@ -0,0 +1,76 @@ +# Authentication is via OAuth2 with the client credentials grant. Register a +# new app at https://www.deviantart.com/developers/ to obtain a client_id and +# client_secret. The app doesn't need to be published. +# +# API requests must send a user agent and must use gzip compression, otherwise +# 403 errors will be returned. +# +# API calls operate on UUIDs. The deviation ID in the URL is not the UUID. UUIDs +# are obtained by scraping the HTML page for the element. +# +# * https://www.deviantart.com/developers/ +# * https://www.deviantart.com/developers/authentication +# * https://www.deviantart.com/developers/errors +# * https://www.deviantart.com/developers/http/v1/20160316 + +class DeviantArtApiClient + class Error < StandardError; end + BASE_URL = "https://www.deviantart.com/api/v1/oauth2" + + attr_reader :client_id, :client_secret, :httparty_options + + def initialize(client_id, client_secret, httparty_options = {}) + @client_id, @client_secret, @httparty_options = client_id, client_secret, httparty_options + end + + # https://www.deviantart.com/developers/http/v1/20160316/deviation_single/bcc296bdf3b5e40636825a942a514816 + def deviation(uuid) + request("/deviation/#{uuid}") + end + + # https://www.deviantart.com/developers/http/v1/20160316/deviation_download/bed6982b88949bdb08b52cd6763fcafd + def download(uuid, mature_content: "1") + request("/deviation/download/#{uuid}", mature_content: mature_content) + end + + # https://www.deviantart.com/developers/http/v1/20160316/deviation_metadata/7824fc14d6fba6acbacca1cf38c24158 + def metadata(*uuids, mature_content: "1", ext_submission: "1", ext_camera: "1", ext_stats: "1") + params = { + deviationids: uuids.flatten, + mature_content: mature_content, + ext_submission: ext_submission, + ext_camera: ext_camera, + ext_stats: ext_stats, + } + + request("/deviation/metadata", params) + end + + def request(url, **params) + options = httparty_options.deep_merge( + base_uri: BASE_URL, + query: { access_token: access_token.token, **params }, + headers: { "Accept-Encoding" => "gzip" }, + format: :plain, + ) + + resp = HTTParty.get(url, **options) + json = JSON.parse(Zlib.gunzip(resp.body), symbolize_names: true) + + raise Error, "HTTP error #{resp.code}: #{json}" unless resp.success? + json + end + + def oauth + OAuth2::Client.new(client_id, client_secret, site: "https://www.deviantart.com", token_url: "/oauth2/token") + end + + def access_token + @access_token = oauth.client_credentials.get_token if @access_token.nil? || @access_token.expired? + @access_token + end + + def access_token=(hash) + @access_token = OAuth2::AccessToken.from_hash(oauth, hash) + end +end diff --git a/config/danbooru_default_config.rb b/config/danbooru_default_config.rb index 4282ea08a..7211482ae 100644 --- a/config/danbooru_default_config.rb +++ b/config/danbooru_default_config.rb @@ -487,11 +487,12 @@ module Danbooru nil end - def deviantart_login + # Register at https://www.deviantart.com/developers/. + def deviantart_client_id nil end - def deviantart_password + def deviantart_client_secret nil end