2012年3月23日金曜日

自分用のdropbox automatorが作れる!dropbox+herokuでtwitterに写真を自動投稿する方法

dropboxにファイルを上げると、自動的にfacebookやflickrに投稿するようにできるdropbox automatorというサービスがあります。
このサービスはtwitterへのphoto投稿が対応していないので、dropbox apiとherokuを使って、できるようにしてみたいと思います。
今回のファイルはgithubにあげておきました。

とりあえずgemをインストール
# vim Gemfile

下記を記述。
  1. source "http://rubygems.org"  
  2. gem "clockwork"  
  3. gem 'dropbox-sdk'  
  4. gem 'twitter'  


# bundle install

次にdropbox apiを使えるようにこちらから登録してください。
ログイン後、アプリ登録画面で「create an App」をクリック
適当にアプリ名と説明を入れて、「App folder」の方をチェックして、登録。

アプリの詳細画面でApp keyとApp secretを確認してください。
apiを使うためには、コレ以外にrequest_tokenとaccess_tokenが必要なのですが、
そのためにはブラウザでフォルダへの許可を取る必要があります。

なので、まずはその取得用のスクリプトを作成。

  1. # -*- coding: utf-8 -*-  
  2. require 'dropbox_sdk'  
  3.   
  4. APP_KEY = 'INSERT-APP-KEY-HERE'  
  5. APP_SECRET = 'INSERT-APP-SECRET-HERE'  
  6. ACCESS_TYPE = :app_folder  
  7. session = DropboxSession.new(APP_KEY, APP_SECRET)  
  8.   
  9. request_token = session.get_request_token  
  10.   
  11. authorize_url = session.get_authorize_url  
  12. puts "AUTHORIZING", authorize_url  
  13. gets  
  14.   
  15. access_token = session.get_access_token  
  16.   
  17. "request_token:", request_token  
  18. "access_token:", access_token  



上のスクリプトを一端起動してください。
途中urlが表示されると思いますので、それをブラウザに入力。
認証完了後、ターミナルに戻り、エンターを押してください。

request_tokenとaccess_tokenのkeyとsecretがそれぞれ表示されると思いますので、メモしておきます。

次に実際に動かすスクリプトの作成。

herokuのcedar stackでは、webの代わりにスクリプトを動かしっぱなしにできるので、それを利用します。
具体的には、clockworkというライブラリを使い、一定ごとにdropboxのapiを叩き、新規ファイルが登録されていれば、それを投稿という流れ。

設定用のファイルを作成。
# vim Procfile
  1. cron: bundle exec clockwork clock.rb  


コードは下記の通り。
  1. # -*- coding: utf-8 -*-  
  2. require 'clockwork'  
  3. require 'twitter'  
  4. require 'dropbox_sdk'  
  5. include Clockwork  
  6. @time = Time.now  
  7.   
  8. Twitter.configure do |config|  
  9.   config.consumer_key = 'XXXXXXXXXXXXXXXX'  
  10.   config.consumer_secret = 'XXXXXXXXXXXXXXXX'  
  11.   config.oauth_token = 'XXXXXXXXXXXXXXXX'  
  12.   config.oauth_token_secret = 'XXXXXXXXXXXXXXXX'  
  13. end  
  14.   
  15. APP_KEY = 'INSERT-APP-KEY-HERE'  
  16. APP_SECRET = 'INSERT-APP-SECRET-HERE'  
  17. ACCESS_TYPE = :app_folder  
  18. session = DropboxSession.new(APP_KEY, APP_SECRET)  
  19. session.set_request_token('REQUEST_TOKEN_KEY''REQUEST_TOKEN_SECRET')  
  20. session.set_access_token('ACCESS_TOKEN_KEY''ACCESS_TOKEN_SECRET')  
  21. client = DropboxClient.new(session, ACCESS_TYPE)  
  22.   
  23. handler do |job|  
  24.   filedata = nil  
  25.   file_metadata = client.metadata('/')  
  26.   filedata = file_metadata["contents"].map { |x| x if Time.parse( x["modified"]) > @time }  
  27.   filedata.each do |f|  
  28.    unless f == nil  
  29.     file = client.get_file(f["path"])  
  30.     filename = Time.now.to_i.to_s  
  31.     File.open("/tmp/" + filename, "w") {|f| f.write file}  
  32.     tmp = File.open("/tmp/" + filename, "rb")  
  33.     p tmp  
  34.     Twitter.update_with_media(@time.strftime("%F %H:%M"), tmp)  
  35.    end  
  36.   end  
  37.   @time = Time.now  
  38. end  
  39.   
  40. every(1.minutes, 'check')  


apiのキーはそれぞれ先程メモったものなどを入力してください。

あとはherokuに上げるだけ。
# git init
# git add .
# git commit -m 'first commit'

# heroku create --stack cedar
# git push heroku master
# heroku scale cron=1

ちゃんと動いているかどうか確認します。
# heroku ps
起動しているプロセスがcron.1だけのはず。

# heroku logs --tail
Triggering checkみたいなログがでているはずです。

あとは、dropboxに先程登録したアプリ名のファイルができているので、画像をコピーしてみます。(なぜかgitがうまく投稿できませんでした。。。)
twitterに自動投稿されていれば成功です。

ファイル名や種類、アップするフォルダで処理を変えるようにすれば、自分なりの「dropbox automator」がつくれます。

2012年3月19日月曜日

iphoneで撮った写真をherokuにアップロードする(iosアプリ)

herokuで無料のimage uploaderを作るの続き。

mongolabを使って、写真のアップローダを作れるようにしましたが、せっかくなのでiphoneから写真を直接あげられるようにしておきます。
写真をアップロードはhttpを使ってサーバにpostします。

postするサンプルを作っている人がいたので、これをベースにして使うことに。
https://github.com/tochi/HTTPFileUploadSample

HTTPFileUploadSampleViewController.hを以下のように修正
  1. @interface HTTPFileUploadSampleViewController : UIViewController <httpfileuploaddelegate, uitextfielddelegate="">  
  2. {  
  3.     IBOutlet UITextField *codeTextField;  
  4.     IBOutlet UIImageView *_imageView;  
  5. }  
  6.   
  7. - (IBAction)postButtonClicked:(id)sender;  
  8. - (IBAction)showCameraSheet:(id)sender;  
  9. @property (retain, nonatomic) IBOutlet UITextField *codeTextField;  
  10. </httpfileuploaddelegate,>  


HTTPFileUploadSampleViewController.mを以下のように修正
httpFileUpload postWithUriは自分のherokuのURLに修正してください。

  1. #import "HTTPFileUploadSampleViewController.h"  
  2.   
  3. @implementation HTTPFileUploadSampleViewController  
  4. @synthesize codeTextField = _codeTextField;  
  5.   
  6. - (void)dealloc  
  7. {  
  8.     [_imageView release];  
  9.     [_codeTextField release];  
  10.   [super dealloc];  
  11. }  
  12.   
  13. - (void)didReceiveMemoryWarning  
  14. {  
  15.   [super didReceiveMemoryWarning];  
  16. }  
  17.   
  18. #pragma mark - View lifecycle  
  19. - (void)viewDidLoad  
  20. {  
  21.   [super viewDidLoad];  
  22.     _codeTextField.returnKeyType = UIReturnKeyDone;  
  23.     _codeTextField.delegate = self;  
  24. }  
  25.   
  26. - (BOOL)textFieldShouldReturn:(UITextField *)textField {  
  27.     [_codeTextField resignFirstResponder];  
  28.     return YES;  
  29. }  
  30.   
  31. - (void)viewDidUnload  
  32. {  
  33.     [_imageView release];  
  34.     _imageView = nil;  
  35.     [codeTextField release];  
  36.     codeTextField = nil;  
  37.     [self setCodeTextField:nil];  
  38.   [super viewDidUnload];  
  39. }  
  40.   
  41. - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation  
  42. {  
  43.   return (interfaceOrientation == UIInterfaceOrientationPortrait);  
  44. }  
  45.   
  46. - (IBAction)postButtonClicked:(id)sender  
  47. {  
  48.   // Get image data.  
  49.   //UIImage *image1 = [UIImage imageNamed:@"Icon.png"];  
  50.     
  51.   // File upload.  
  52.   HTTPFileUpload *httpFileUpload = [[HTTPFileUpload alloc] init];  
  53.   httpFileUpload.delegate = self;  
  54.   [httpFileUpload setPostString:self.codeTextField.text withPostName:@"name"];  
  55.   [httpFileUpload setPostImage:_imageView.image withPostName:@"photo" fileName:@"Icon.png"];  
  56.   [httpFileUpload postWithUri:@"http://XXXX.herokuapp.com/users/photo.json"];  
  57.   [httpFileUpload release], httpFileUpload = nil;  
  58. }  
  59.   
  60. - (IBAction)showCameraSheet:(id)sender {  
  61.     // アクションシートを作る  
  62.     UIActionSheet*  sheet;  
  63.     sheet = [[UIActionSheet alloc]   
  64.              initWithTitle:@"Select Soruce Type"   
  65.              delegate:self   
  66.              cancelButtonTitle:@"Cancel"   
  67.              destructiveButtonTitle:nil   
  68.              otherButtonTitles:@"Photo Library", @"Camera", @"Saved Photos", nil];  
  69.     [sheet autorelease];  
  70.       
  71.     // アクションシートを表示する  
  72.     [sheet showInView:self.view];  
  73. }  
  74.   
  75. - (void)actionSheet:(UIActionSheet*)actionSheet   
  76. clickedButtonAtIndex:(NSInteger)buttonIndex  
  77. {  
  78.     // ボタンインデックスをチェックする  
  79.     if (buttonIndex >= 3) {  
  80.         return;  
  81.     }  
  82.       
  83.     // ソースタイプを決定する  
  84.     UIImagePickerControllerSourceType   sourceType = 0;  
  85.     switch (buttonIndex) {  
  86.         case 0: {  
  87.             sourceType = UIImagePickerControllerSourceTypePhotoLibrary;  
  88.             break;  
  89.         }  
  90.         case 1: {  
  91.             sourceType = UIImagePickerControllerSourceTypeCamera;  
  92.             break;  
  93.         }  
  94.         case 2: {  
  95.             sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;  
  96.             break;  
  97.         }  
  98.     }  
  99.       
  100.     // 使用可能かどうかチェックする  
  101.     if (![UIImagePickerController isSourceTypeAvailable:sourceType]) {    
  102.         return;  
  103.     }  
  104.       
  105.     // イメージピッカーを作る  
  106.     UIImagePickerController*    imagePicker;  
  107.     imagePicker = [[UIImagePickerController alloc] init];  
  108.     [imagePicker autorelease];  
  109.     imagePicker.sourceType = sourceType;  
  110.     imagePicker.allowsImageEditing = YES;  
  111.     imagePicker.delegate = self;  
  112.       
  113.     // イメージピッカーを表示する  
  114.     [self presentModalViewController:imagePicker animated:YES];  
  115. }  
  116.   
  117. - (void)imagePickerController:(UIImagePickerController*)picker   
  118.         didFinishPickingImage:(UIImage*)image   
  119.                   editingInfo:(NSDictionary*)editingInfo  
  120. {  
  121.     // イメージピッカーを隠す  
  122.     [self dismissModalViewControllerAnimated:YES];  
  123.     // オリジナル画像を取得する  
  124.     UIImage*    originalImage;  
  125.     originalImage = [editingInfo objectForKey:UIImagePickerControllerOriginalImage];  
  126.       
  127.     // グラフィックスコンテキストを作る  
  128.     CGSize  size = { 300, 400 };  
  129.     UIGraphicsBeginImageContext(size);  
  130.       
  131.     // 画像を縮小して描画する  
  132.     CGRect  rect;  
  133.     rect.origin = CGPointZero;  
  134.     rect.size = size;  
  135.     [originalImage drawInRect:rect];  
  136.       
  137.     // 描画した画像を取得する  
  138.     UIImage*    shrinkedImage;  
  139.     shrinkedImage = UIGraphicsGetImageFromCurrentImageContext();  
  140.     UIGraphicsEndImageContext();  
  141.       
  142.     // 画像を表示する  
  143.     _imageView.image = shrinkedImage;  
  144. }  
  145.   
  146. - (void)imagePickerControllerDidCancel:(UIImagePickerController*)picker  
  147. {  
  148.     // イメージピッカーを隠す  
  149.     [self dismissModalViewControllerAnimated:YES];  
  150. }  
  151.   
  152. - (void)httpFileUpload:(NSURLConnection *)connection  
  153.       didFailWithError:(NSError *)error  
  154. {  
  155.   NSLog(@"%@", error);  
  156. }  
  157.   
  158. - (void)httpFileUploadDidFinishLoading:(NSURLConnection *)connection  
  159.                                 result:(NSString *)result  
  160. {  
  161.   NSLog(@"%@", result);  
  162.     UIAlertView *alert = [[UIAlertView alloc]  
  163.                           initWithTitle:@""   
  164.                           message:@"投稿完了しました。"   
  165.                           delegate:nil   
  166.                           cancelButtonTitle:@"OK"   
  167.                           otherButtonTitles:nil, nil];  
  168.     [alert show];  
  169.     [alert release];  
  170. }  
  171. @end  


次にここを参考にxibを作成。アクションシートを追加してください。(コードは上のものにすでに入ってます。)
http://news.mynavi.jp/column/iphone/001/index.html
同じような感じで、textfieldを追加してください。(codeTextField)

次にrails側。
こちらをベースに修正します。
https://github.com/face-do/heroku-image-uploader
UsersController.rbを以下の用に修正。
  1. def photo  
  2.   @user = User.new(:name => params[:name], :photo => params[:photo] )  
  3.   
  4.   respond_to do |format|  
  5.     if @user.save  
  6.       format.html { redirect_to @user, notice: 'User was successfully created.' }  
  7.       format.json { render json: @user, status: :created, location: @user }  
  8.     else  
  9.       format.html { render action: "new" }  
  10.       format.json { render json: @user.errors, status: :unprocessable_entity }  
  11.     end  
  12.   end  
  13. end  

config/routes.rbに以下を追記
post "users/photo" => 'users#photo'

app/controllers/application_controller.rbを以下のように修正。
  1. protect_from_forgery  :except => :photo  


で、herokuにあげて、iosアプリをiphoneに転送すれば、できます。
一応今回のファイルをgithubにあげておきました。
rails アプリのほう
iphoneクライアントの方

2012年3月18日日曜日

herokuで無料のimage uploaderを作る

herokuで無料のimage uploaderを作る

herokuは非常に便利ですが、read onlyなのでアップローダーを作ったりできません。
もしやろうとするとS3を使った方法が一般的のようですが、若干利用料金がかかってしまいます。
なので無料で作れる方法を考えてみました。

herokuでは画像を直接アップすることはできませんが、DBに直接保存することができます。
しかしherokuのデフォルトのものは、5MBしかありません。
そこでmongolabという、mongodbを240MBまで無料提供してくれるサービスを利用します。
なおmongolabはherokuにadd-onとして提供されているため、セットアップは簡単です。

githubにファイルをあげておいたので、参考にしてください。
https://github.com/face-do/heroku-image-uploader

まずはrailsプロジェクトを作成します。
ただしmongodbを使うため、active recordを切るようにしておきます。

# rails new uploader -O

次に、gemfileに以下を追記

  1. gem 'mongoid''~>2.1'  
  2.   
  3. gem 'bson_ext''~>1.3'  
  4.   
  5. gem 'carrierwave'  
  6.   
  7. gem 'carrierwave-mongoid':require => 'carrierwave/mongoid'  


# bundle install

で、mongodbに接続するための設定のひな形を作る。
# rails generate mongoid:config

config/mongoid.ymlのproductionを以下のように修正
  1. production:  
  2.   
  3.   uri: <%= ENV['MONGOLAB_URI'] %>  



carrierwaveの設定をする
#vim config/initializers/carrierwave.rb

以下を記述
  1. CarrierWave.configure do |config|  
  2.   
  3.   config.storage = :grid_fs  
  4.   
  5.   config.grid_fs_connection = Mongoid.database  
  6.   
  7.   config.grid_fs_access_url = "/images"  
  8.   
  9. end  


アップローダのひな形を作る
# rails g scaffold page title:string
# rails g uploader photo

app/models/user.rbは以下のように

  1. class User  
  2.   
  3.   include Mongoid::Document  
  4.   
  5.   field :title:type => String  
  6.   
  7.   mount_uploader :photo, PhotoUploader  
  8.   
  9. end  


app/uploaders/photo_uploader.rbは以下のように修正

  1. # encoding: utf-8  
  2.   
  3. class PhotoUploader < CarrierWave::Uploader::Base  
  4.   
  5.   include CarrierWave::RMagick  
  6.   
  7.   storage :grid_fs  
  8.   
  9.   def store_dir  
  10.   
  11.     "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"  
  12.   
  13.   end  
  14.   
  15.   version :thumb do  
  16.   
  17.     process :resize_to_limit => [200, 200]  
  18.   
  19.   end  
  20.   
  21. end  


app/views/users/_form.html.erbの
の上に以下を追記

  1. <div class="field">  
  2.   
  3.   <%= f.label :photo %>  
  4.   
  5.   
  6.   <%= image_tag( @user.photo_url ) if @user.photo? %>  
  7.   
  8.   <%= f.file_field :photo %>  
  9.   
  10.   <%= f.hidden_field :photo_cache %>  
  11.   
  12. </div>  


このままだと画像が表示されないので、画像表示用のメソッドを作る。
まずはapp/controllers/users_controller.rbに以下を追記

  1. require 'mongo'  
  2.   
  3. class UsersController < ApplicationController  
  4.   
  5.   def serve  
  6.   
  7.     gridfs_path = env["PATH_INFO"].gsub("/images/""")  
  8.   
  9.     begin  
  10.   
  11.       gridfs_file = Mongo::GridFileSystem.new(Mongoid.database).open(gridfs_path, 'r')  
  12.   
  13.       self.response_body = gridfs_file.read  
  14.   
  15.       self.content_type = gridfs_file.content_type  
  16.   
  17.     rescue  
  18.   
  19.       self.status = :file_not_found  
  20.   
  21.       self.content_type = 'text/plain'  
  22.   
  23.       self.response_body = ''  
  24.   
  25.     end  
  26.   
  27.   end  


config/routes.rbに以下を追記
match "/images/uploads/*path" => "users#serve"

これでアプリ側の設定は終了。git でcommitしておきます。
# git add .
# git commit -m 'first commit'

次にheroku側の設定をします。
#heroku create --stack cedar
#heroku addons:add mongolab:starter

あとはherokuにdeployするだけ
#git push heroku master