このコミットが含まれているのは:
@@ -0,0 +1,59 @@
|
||||
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
|
||||
raise ArgumentError, "スプレッドシートを取得できませんでした.ウェブ公開 URL を確認するか、ファイル保存又はコピー貼付けを使用してください: #{ e.message }"
|
||||
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') || response.body.valid_encoding?
|
||||
end
|
||||
end
|
||||
新しい課題から参照
ユーザをブロックする