61 行
2.4 KiB
Ruby
61 行
2.4 KiB
Ruby
class PostImportGoogleSheetsFetcher
|
|
HOST = 'docs.google.com'.freeze
|
|
PUBLICATION_ID = /\A[A-Za-z0-9_-]+\z/
|
|
GID = /\A\d+\z/
|
|
RATE_LIMIT = 10
|
|
RATE_WINDOW = 1.minute
|
|
|
|
def self.fetch! raw_url, rate_key:
|
|
new(raw_url, rate_key:).fetch!
|
|
end
|
|
|
|
def initialize raw_url, rate_key:
|
|
@raw_url = raw_url.to_s
|
|
@rate_key = rate_key.to_s
|
|
end
|
|
|
|
def fetch!
|
|
publication_id, gid = parse!
|
|
throttle!
|
|
csv_url = "https://#{ HOST }/spreadsheets/d/e/#{ publication_id }/pub?gid=#{ gid }&single=true&output=csv"
|
|
response = Preview::HttpFetcher.fetch(csv_url,
|
|
max_bytes: PostImportSourceParser::MAX_BYTES,
|
|
allowed_hosts: [HOST])
|
|
raise ArgumentError, 'スプレッドシートを CSV として取得できませんでした.' unless csv?(response)
|
|
|
|
response.body
|
|
rescue Preview::UrlSafety::UnsafeUrl, Preview::HttpFetcher::FetchFailed => e
|
|
Rails.logger.info("post_import_google_sheets_fetch_failure #{ { error: e.class.name, message: e.message }.to_json }")
|
|
raise ArgumentError, 'スプレッドシートを取得できませんでした.ウェブ公開 URL を確認するか、ファイル保存又はコピー貼付けを使用してください.'
|
|
end
|
|
|
|
private
|
|
|
|
def parse!
|
|
uri = URI.parse(@raw_url)
|
|
unless uri.scheme == 'https' && uri.host&.downcase == HOST && uri.query.blank?
|
|
raise ArgumentError, '公開 Google スプレッドシート URL を指定してください.'
|
|
end
|
|
match = uri.path.match(%r{\A/spreadsheets/d/e/([^/]+)/pubhtml\z})
|
|
gid = URI.decode_www_form(uri.fragment.to_s).to_h['gid']
|
|
unless match && PUBLICATION_ID.match?(match[1]) && GID.match?(gid.to_s)
|
|
raise ArgumentError, '公開 Google スプレッドシート URL の publication_id 又は gid が不正です.'
|
|
end
|
|
|
|
[match[1], gid]
|
|
rescue URI::InvalidURIError
|
|
raise ArgumentError, '公開 Google スプレッドシート URL が不正です.'
|
|
end
|
|
|
|
def throttle!
|
|
key = "post-import-google-sheets:#{ @rate_key }"
|
|
count = Rails.cache.increment(key, 1, expires_in: RATE_WINDOW, initial: 0)
|
|
raise ArgumentError, 'スプレッドシート取得の回数が多すぎます.しばらくしてから再試行してください.' if count > RATE_LIMIT
|
|
end
|
|
|
|
def csv? response
|
|
type = response.content_type.to_s.downcase
|
|
type.include?('text/csv') || type.include?('application/csv')
|
|
end
|
|
end
|