Twitter に見栄え良くコード投稿したかった
from Qiita: Twitterにコードを身えばよく投稿したい
はじめに
きっかけ(こんな呟きを見かけた
できたもの
作成の過程で収穫物
- ActiveRecord Storage などの Rails 5.2
- Twitter Login 方法と仕組みなど
- JS の基礎(getElementById や setAttribute、文字カウントなど)
- AWS S3 関連
- XSS 対策
作成の前に
作成要件

- マークダウン投稿、シンタックスハイライト
- gem: redcarpet, rouge
- 投稿から画像生成
- html2canvas
- 参照:html2canvas
- 参照:JSでhtmlを画像化する方法(html2canvasの使い方) from 湧くべく
- AWS S3 に og:image 用の画像を保存
作成の流れ:予定
- rails new codr, Git init, heroku create、Active Storage
- AWS S3 あれこれ
- Twitter 登録、ログイン機能作成
開発環境
- vm : Linux Ubuntu (virtualbox + vagrant)
- Ruby 2.5.1p57
- Rails 5.2.3
- Postgresql
実作業
rails new codr -d postgresql
DB 設定等は割愛
Gem
gem 'mini_racer'gem 'rails-i18n'
gem 'devise'gem 'omniauth'gem 'omniauth-twitter' # twitter logingem 'devise-i18n' # make devise japanizegem 'devise-i18n-views'
gem 'redcarpet' # markdown processorgem 'rouge' # highlighter
gem 'meta-tags'
gem 'aws-sdk-s3' # aws s3
- 参照: kpumuk/meta-tags
rails.credentials.yml
当初は.gitignore
と dotenv
などを使っていたが、作成途中で Rails 5.2 からの rails.credentials.yml
を利用した。復号化には /config/master.key
を利用。
# editor setting EDITOR="vim" rails credentials:edit# edit credentials.ymlrails credentials:edit# show credential.ymlrails credentials.yml:show
# herokuにmaster.keyを環境変数として指定# heroku config:set ENV_VAR="環境変数" --app "アプリ名"
# 追加した変数を使用するにはRails.application.credentials.dig(:twitter, :API_Key)
rails gあれこれ
# deviserails g devise:installrails g devise User name:String
# Add Admin column to Userrails g migration AddAdminToUsers# add setting at /db/migrate/20191103141531_add_admin_to_users.rbadd_column :users, :admin, :boolean, default: false
# add views and controllers to modify deviserails g devise:controllers usersrails g devise:views users
# japanize# add at /config/application.rbconfig.i18n.default_locale = :ja=> create /config/locale/devise.view.ja.yml
# scaffold postrails g scaffold Post user:references name:string content:text date:datetime
ActiveRecord Associations関連付け
# userhas_many :posts
# postbelongs_to :user
投稿関連
マークダウン投稿
-
基本:
Redcarpet::Markdown.new(renderer, extensions = {}).render(@post.content)
オプションや XSS 対策などを追加したく、helper メソッドを作成した。
Module PostsHelper require 'rouge/plugins/redcarpet' class RougeRedcarpetRenderer < Redcarpet::Render::HTML include Rouge::Plugins::Redcarpet
def header(text, level) # make # => h2, ## => h3 level += 1 "<h#{level}>#{text}</h#{level}>" end end
def markdown(text) render_options = { # do not allow any user-inputted HTML in the output. filter_html: true, hard_wrap: true, }
extensions = { # <>で囲まれていない時は、リンクとして認識しない autolink: true, # ```/m```をコードとする fenced_code_blocks: true, lax_spacing: true, no_intra_emphasis: true, strikethrough: true, superscript: true, tables: false, highlight: true, disable_indented_code_blocks: true, # #の後にスペースが無くても良いか space_after_headers: false } renderer = RougeRedcarpetRenderer.new(render_options) Redcarpet::Markdown.new(renderer, extensions).render(text).html_safe endend
html_safe => sanitize
ホワイトリスト方式の sanitizeヘルパー を使用した。
# sanitize(html, options = {}) <div id="capture" class="content"> <%= sanitize(markdown(@post.content), tags: %w(div img h1 h2 h3 h4 h5 strong em a p pre code ), attributes: %w(class href)) %> </div>
投稿内容のデータ化、AWSへの画像保存
Heroku では画像保持がされないので、作成画像を AWS S3 に保存し、og:image に添付する形を取った。
- Web アプリ内で通常投稿
- show ページ表示(同時に html2canvas で Base64 としてデータ取得、hidden_field に格納)
- ツイートボタン押す(Post され、post モデル内で base64 をデコード)
- Active Storage を通して、AWS S3 に保存
Active Storage
Rail5.2 からの機能で、今までの carrievave や paperclip などを使わずに、クラウドストレージ等へのアップロードが容易になる。今回は AWS S3 を使った。
- 参照: Active Storage
# set uprails active_storage:install# rails g resource comment content:textrails db:migrate
class Post < ApplicationRecord# 今回は1つの投稿につき、1枚の画像なので。# 複数なら => has_many_attached :prtscs has_one_attached :prtscend
# ファイル保存先変更# development.rbconfig.active_storage.service = :local# production.rbconfig.active_storage.service = :amazon
rails credentials:edit
で AWS アクセスキーとシークレットキーを追加。
aws: access_key_id: secret_access_key:
test: service: Disk root: <%= Rails.root.join("tmp/storage") %>local: service: Disk root: <%= Rails.root.join("storage") %>amazon: service: S3 access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> region: ap-northeast-1 bucket: codr0
# Gemfile# gemが必要gem 'aws-sdk-s3', require: false# 今回は不要だったので、入れず。gem 'mini_magick'
html2canvas
- ツイートボタン押下時に画像を Post するためのフォーム
hidden_field
を用意 html2canvas.js
をapp/assets/javascripts
ディレクトリ配下に保存- html 上に置く script コードを改修
<%= form_with(model: @post, local: true) do |form| %> <%= form.hidden_field :id, value: @post.id %> <%= form.hidden_field :prtsc, value: "" %> # idはpost_prtscになる。 <%= form.submit "Post", class:"btn btn-outline-dark", id:"tweet", value:"tweet" %><% end %>
<script type="text/javascript"> html2canvas(document.querySelector("#capture"),{scale:1, width:600}).then(canvas => { var base64 = canvas.toDataURL('image/jpeg', 1.0); document.getElementById('post_prtsc').setAttribute('value', base64); });</script>
Base64デコード
attr_accessor :img
def parse_base64(img) if img.present? # data:image/jpeg;base64,/9j/4AAQSkZJRgABA・・・から/9j/4AA以降を選択取得 content = img.split(',')[1] # 今回は、ユーザによる画像アップロード投稿ではなく、拡張子が決まっている filename = Time.zone.now.to_s + '.jpg' decoded_data = Base64.decode64(content) # String.IO.newにより、アプリ内に一時ファイルを作成しなくて済む prtsc.attach(io: StringIO.new(decoded_data), filename: filename) endend
あとは posts_controller で、params から受け取った Base64 データを上の parse_base64(img)
で変換し、保存すれば完了。
AWS S3
- refferrence
AWS 上での登録、設定、バケット作成等は割愛。
ツイート Share Button
<script> var base = 'https://twitter.com/intent/tweet?url='; var pageUrl = 'https://codr0.herokuapp.com/posts/' + document.getElementById('post_id').value; var option = '&button_hashtag=Codr0&ref_src=twsrc%5Etfw'; var href = base + pageUrl + option; var twit = document.getElementById('tweet'); twit.addEventListener('click', function() { window.open( href ); });</script>
og:imageに画像添付
なお、head の meta 情報セットには、gem 'meta-tags'
を使用
service_url()とurl_for()
基本的にはどちらも、ActiveStorage に保存したデータの Url を取得するメソッドの様だ。 どちらもセキュリティのためにリンクの有効期限が短いみたいだが、違いがわからなかった。今回はツイートボタン押下し、Tweet した際に og:image として表示されればいい。
# 画像がActive StorageでAWS S3に保存されて入れば<% if @post.prtsc.attached? %> <% set_meta_tags og:{image: @post.prtsc.service_url} %><% end %>
Twitterログイン
TwitterDeveloperAccount が必要。割愛。
# 参考ページと同じ基礎的な所は割愛する。class User < ApplicationRecord def self.from_omniauth(auth) find_or_create_by!(provider: auth['provider'], uid: auth['uid']) do |user| # 一部割愛 user.username = auth['info']['nickname'] # SNS登録時は、ダミーメールを登録 user.email = User.dummy_email(auth) end end
# SNS登録(providerが存在する)時は、パスワード要求をしない def password_required? super && provider.blank? end
def self.new_with_session(params, session) if session['devise.user_attributes'] new(session['devise.user_attributes']) do |user| user.attributes = params end else super end end
private
def self.dummy_email(auth) "#{auth.uid}-#{auth.provider}@example.com" endend
Twitter のニックネームが取得できるようになったので、元からある User の name テーブルは削除した。
改修(加筆
メディアクエリ
想定ユーザーは殆どスマートフォンなのに、パソコンで作成し、CSS をパソコンの見た目でやってた。折角 SCSS でやってるので、変数を利用した。
// ディスプレイサイズが680pxまでなら。$tab: 680px;@mixin tab { @media (max-width: ($tab)) { @content; }}
最後に
gist などがコードスクショを og:image で表示してくれたらすべて済むのでは