Qiitaに投稿するRubyスクリプト

# 概要
linuxOS上で書いたテキストをQiitaに投稿したかったので、Qiita APIを使った投稿スクリプトをRubyで書いた。好きなエディタで書いて、このツールを使って投稿して、ブラウザで結果確認、という作業を想定しているので投稿機能しかない。ちなみにこの投稿もそうして書いている。さらにちなむとQiita gemというものの存在は今はじめて知った。まいっか。

# 動作環境
ruby製。手元では2.0.0で動作させてるけど、1.9でも動くんじゃないかな。標準環境に加えて、簡単にコマンドラインを作るthorライブラリと、例によってIDとパスワードを分離してくれるpitライブラリが必要。
ディレクトリ構造などはUnixライクなものを想定したコードになっているのでWindowsではそのままだと動かないかも。
あとQiita APIを使っているのでgithubやtwitterの認証だけでは駄目で、投稿したいQiitaユーザーにパスワードを設定しておく必要がある。

# 動作の説明
## 起動
qiita.thorファイルのある場所で`thor qiita コマンド`するなり`thor install qiita.thor`しておいて好きな場所で`thor qiita コマンド`するなり。
## コマンド
コマンドは`new`と`edit`しかなくて`new`は新規作成、`edit`は指定したファイルを編集、もしくは、無指定なら直前に編集したファイルを編集。どちらもエディタが自動で立ちあがる(環境変数 EDITOR必須)ので編集して閉じるとuploadするかどうかを尋ねてくる。作成したファイルは決めうちで~/qiita以下に置かれる。初回アップロード時にはpitの機能でQiitaのユーザー名とパスワードの入力を求められる。ホームディレクトリ直下に平文で保存されて後は使いまわされるので、気になる人は適当に置き換えてください。

## 投稿設定
タグや公開範囲の設定は、各々のファイルの頭にYAML Front-matterの形で記述しておくとupload時に解釈する。upload時には(新規投稿時に自動設定される)uuidが存在するかどうかで新規か更新かも自動判別。
uploadのみを行う機能はないけど、自分の用途としてはeditでファイル指定して一回開いてからupload
すれば十分なので。

# 制約
thorを使ってしまったので本来ライブラリに分ける部分も1ファイルにせざるを得なかった。200行足らずなので、まあいいっちゃいいんですけど。thorで外部ファイルのrequireと、システムへのインストールを両立させる方法をご存知の方は教えてください。ただし、画像の投稿はできない。まあしょうがない。

# ソース
```ruby:qiita.thor
# -*- coding: utf-8 -*-
require 'yaml'
require 'pp'
require 'net/http'
require 'net/https'
require 'json'
require 'pit'

QiitaDir = "~/qiita"
QiitaLatest = "#{QiitaDir}/latest"

class Qiita < Thor
  include Thor::Actions

  desc "new", "新規エントリを作成してエディタで開く"
  def new
    file_path = make_entry_file
    edit_and_upload(file_path)
  end

  desc "edit", "ファイル名を指定してエントリを編集する。指定なしなら直前に編集したエントリ"
  def edit(file_path=nil)
    file_path = File.read(File.expand_path(QiitaLatest)).chomp unless file_path
    edit_and_upload(file_path)
  end

  no_commands do
    def make_entry_file
      qiita_dir = QiitaDir
      now = Time.now
      file_title = now.strftime("%Y-%m-%d-%H%M%S")
      year = now.strftime("%Y")
      month = now.strftime("%m")
      sep = '---'
      body = <<EOS
#{sep}
uuid: 
title: 
tags:
- name: 
private: true
#{sep}
EOS
      file_path = "#{qiita_dir}/#{year}/#{month}/#{file_title}.md"
      create_file file_path, body
      file_path
    end

    def qiita_agent
      config = Pit.get("qiita",:require=>{
                         "user" => "user id",
                         "pass" => "password"
                       })
      agent = QiitaAPI.new
      agent.auth(config["user"],config["pass"])
      agent
    end

    def edit_and_upload(file_path)
      system "#{ENV["EDITOR"]} #{file_path}"
      save_latest(file_path)
      puts "edit done"
      if yes?("#{file_path} upload? (y/N)")
        upload(file_path)
        puts "uploaded"
      end
    end

    def upload(file_path)
      path = File.expand_path(file_path)
      str = File.read(path)
      ar = str.split("---\n")
      yaml_str = ar[1]
      body = ar[2]
      config = YAML.load(yaml_str)
      puts yaml_str
      pp config
      config["body"] = body
      qa = qiita_agent
      
      unless config["uuid"]
        result = qa.post_entry(config)
        config["uuid"] = result["uuid"]
        config.delete("body")
        yaml_str = config.to_yaml
        File.open(path,"w") do |f|
          f.puts yaml_str
          f.puts "---"
          f.puts body
        end
      else
        qa.put_entry(config)
      end

    end

    def save_latest(file_path)
      open(File.expand_path(QiitaLatest),"w") do |f|
        f.puts file_path
      end
    end
  end
end

class QiitaAPI

  ### JSON HTTPS

  def http_setup(url_str)
    url = URI.parse(url_str)
    http = Net::HTTP.new(url.host,url.port)
    http.use_ssl = true
    http.verify_mode = OpenSSL::SSL::VERIFY_NONE
    # http.set_debug_output $stderr
    [url,http]
  end

  def get(url_str)
    url, http = http_setup(url_str)
    res = http.get(url.request_uri)
    JSON.parse(res.body)
  end

  def post(url_str,data)
    url, http = http_setup(url_str)
    json_str = JSON.generate(data)
    res = http.post(url.request_uri,
                    json_str,{'Content-Type' =>'application/json'})
    JSON.parse(res.body)
  end
  
  def put(url_str,data)
    url, http = http_setup(url_str)
    json_str = JSON.generate(data)
    res = http.put(url.request_uri,
                   json_str,{'Content-Type' =>'application/json'})
    JSON.parse(res.body)
  end

  ### main

  def initialize
    @base_url = 'https://qiita.com/api/v1'
  end

  def auth?
    @token != nil
  end

  def auth(user,pass)
    r = post("#{@base_url}/auth",{url_name: user,password: pass})
    @token = r["token"]
  end

  def make_url(child_path)
    path = "#{@base_url}/#{child_path}"
    unless auth?
      path
    else
      "#{path}?token=#{@token}"
    end
  end

  ### methods

  def rate_limit
    get(make_url('rate_limit'))
  end

  def user
    raise "require auth" unless auth?
    get(make_url('user'))
  end

  def users(name)
    get(make_url("users/#{name}"))
  end

  def items
    raise "require auth" unless auth?
    get(make_url('items'))
  end

  def post_entry(data)
    raise "require auth" unless auth?
    post(make_url("items"),data)
  end
  
  def put_entry(data)
    raise "require auth" unless auth?
    uuid = data["uuid"]
    put(make_url("items/#{uuid}"),data)
  end
end
```