Cloud PrintをGoogle Apps Scriptから使う【GAS】

世界ではすでにFAXは骨董品扱いになり、また日本でもペーパーレスやデータのみでの仕事が一般化してきました。しかし一方でまだプリンターが死滅することなく生きているのは、やはりそこには需要があるから。必ずしも全てがスマフォやPC上だけで済むことはなく、やはり印刷物は必要という事実は超えられません(領収書やらメールの使えない環境、PCが苦手な人などなど)。

とはいえ、FAXは通信料金が掛かる上に一度プリントアウトして送るといった面倒があるのも事実ですし、遠隔地の支店などに自動で印刷を送り込むには仕掛けが必要になります。

ところが、Google Apps Scriptには印刷に関するメソッドが用意されていないので、印刷は手動で行わなければなりません。せっかくトリガーといった便利なものがあるのにもったいない。そこで使うのがGoogle Cloud Print。これをGASから利用できれば、印刷までを自動化出来ます。また、Cloud Printなので電話回線ではなくインターネット越しに相手のプリンターに印刷を送り込め、FAX通信料金の削減が出来ます。

※2019年11月、10年続いたこのCloud Printが「2020/12/31に廃止される」と発表されました。GASから印刷を実行する唯一の手段でしたが、残念ですね。

※2020年2月、Microsoftから似たようなサービス「Universal Print」が発表されました。未対応プリンタでも可のようで、Azure ADのOAuth2.0認証からも使えるっぽい。現在プレビュー登録募集中

今回使用する資料・ファイル

今回のスレッドとは直接関係はありませんが、スマフォからGoogle Cloud Printを使う場合には、予めアプリを入れておく必要があります。AndroidであればGoogle Playから、iOSは専用アプリは公式からは出ていませんがサードパーティから出ています。

自分はAndroidから印刷をすると、自分のPCのデスクトップにVipriser経由でPDFが自動生成されるようにしています。Cloud Printは使い方次第ではすごく便利なのに、あまり広まっていないのが残念。

図:Androidから印刷実行画面

事前準備

プリンターの登録

Cloud Printはいつでもどこでも自宅や会社のプリンターに印刷ジョブを送り込めます。各社からもCloud Print対応のプリンタが出ていたりします。一方で既存の未対応プリンタは使えないのか?といったらそうではなく、PCにつないだ状態でオンライン且つ電源が入っていれば、Cloud Print対応にする事が可能です。

事前準備として、まず自分の手持ちのプリンタをCloud Printに登録しましょう。登録手順は以下の通り。

  1. Cloud Printに自分のアカウントでログインします。この画面はプリンタ管理画面でもあります。
  2. Google Chromeにて「chrome://devices」へアクセスする
  3.  「従来のプリンタ」をクリックする
  4. 自分のPCに登録済みのプリンタ一覧が検出されます。Cloud Printに追加したいものだけ選んで「プリンタ追加」をクリック
  5. これで自分のアカウントのCloud Printにプリンターが登録されました。
  6. プリンタ管理画面に戻るとプリンタが一覧に出てきます。
  7. このプリンタを他の人にも使えるようにするには、共有ボタンをクリックします。
  8. ドキュメントの共有と同じく、アクセス権限をGoogleアカウントで追加します。リンクを知っていれば誰でも使える設定にも可能です。
  9. 自分は今回、PDF化するVipRiser仮想プリンタを対象に追加しました。

図:プリンタを追加する画面

図:プリンタ登録画面

図:登録されたプリンタ一覧

図:クラウドプリント共有画面

プロジェクトの作成

Google Cloud Print APIはGoogle Cloud Platformに登録のあるAPIではないのですが、OAuth2.0認証が必要なので、クライアントIDとシークレットが必要になります。取得手順は以下の通り。これはソースコード内に記述が必要ですので、控えておきます。

  1. スクリプトエディタより「ファイル」⇒「プロジェクトのプロパティ」を開き、スクリプトIDを控えておく
  2. スクリプトエディタより「リソース」⇒「Googleの拡張サービス」をクリック
  3.  Google Cloud Platform API ダッシュボードをクリックします。
  4. GCPの管理画面になるので、左サイドの認証情報をクリックします。
  5. 認証情報を作成をクリックします。
  6. OAuthクライアントIDを選択します。
  7. 次の画面では、「ウェブアプリケーション」を選択します。
  8. 承認済みの JavaScript 生成元」では、https://script.google.comを入力
  9. 「承認済みのリダイレクト URI」には、控えておいた1.の「スクリプトIDをつなげた、値(例:https://script.google.com/macros/d/スクリプトID/usercallback)を入力して作成をクリック
  10. クライアントIDとクライアントシークレットが表示されるので、控えておく。
  11. ソースコード内に10.の内容を記述する。

図:スクリプトIDを取得しておきます。

図:認証情報を作成中の画面

OAuth2.0認証ライブラリの追加

今回のサービスは、OAuth2.0認証が必要です。以下の手順でOAuth2 for Apps Scriptライブラリを追加しましょう。

  1. スクリプトエディタを開きます。
  2. メニューより「リソース」⇒「ライブラリ」を開きます。
  3. ライブラリを追加欄に「1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF」を追加します。
  4. 現時点ではバージョンは30が最新ですので、それを選択しておきます。
  5. 保存ボタンを押して完了

これで、OAuth2.0認証にまつわる様々な関数を手軽に利用できるようになります。

図:ライブラリを追加した様子

ソースコード

GAS側コード

メニュー用のGASコード

function onOpen() {
    var ui = SpreadsheetApp.getUi();
    ui.createMenu('▶セッティング')
    .addItem('印刷設定', 'openSidebar')
    .addItem('OAuth認証実行', 'startoauth')
    .addToUi();
    
    //シートのIDを取得する
    getMySheetId();
}

//PDF作成用コンソールをsidebarで表示する
function openSidebar() {
  var ui = SpreadsheetApp.getUi();
  var html = HtmlService.createHtmlOutputFromFile('sidebar').setTitle('PDF作成設定').setSandboxMode(HtmlService.SandboxMode.IFRAME);
  ui.showSidebar(html);
} 

//自分自身のIDを取得するコード
function getMySheetId(){
  var sheet = SpreadsheetApp.getActiveSpreadsheet();
  var myid = sheet.getId();

  var Properties = PropertiesService.getScriptProperties();
  Properties.setProperty("mysheetid", myid);
  return myid;
}
  • 起動時にgetMySheetId関数を実行し、スプレッドシートのIDをスクリプトプロパティに格納しています。

OAuth認証用のスクリプト

//認証用各種変数
var tokenurl = "https://accounts.google.com/o/oauth2/token"
var authurl = "https://accounts.google.com/o/oauth2/auth"
var clientid = 'ここにクライアントIDを入れてください。';
var clientsecret='ここにクライアントシークレットを入れてください。';
var scope = "https://www.googleapis.com/auth/cloudprint"

function startoauth(){
  //UIを取得する
  var ui = SpreadsheetApp.getUi();
  
  //認証済みかチェックする
  var service = checkOAuth();
  if (!service.hasAccess()) {
    //認証画面を出力
    var output = HtmlService.createHtmlOutputFromFile('template').setHeight(310).setWidth(500).setSandboxMode(HtmlService.SandboxMode.IFRAME);
    ui.showModalDialog(output, 'OAuth2.0認証');
  } else {
    //認証済みなので終了する
    ui.alert("すでに認証済みです。");
  }
}

//アクセストークンURLを含んだHTMLを返す関数
function authpage(){
  var service = checkOAuth();
  var authorizationUrl = service.getAuthorizationUrl();
  var html = "<center><b><a href='" + authorizationUrl + "' target='_blank' onclick='closeMe();'>アクセス承認</a></b></center>"
  return html;
}

//認証チェック
function checkOAuth() {
  return OAuth2.createService("CloudPrint")
    .setAuthorizationBaseUrl(authurl)
    .setTokenUrl(tokenurl)
    .setClientId(clientid)
    .setClientSecret(clientsecret)
    .setCallbackFunction("authCallback") //認証を受けたら受け取る関数を指定する
    .setPropertyStore(PropertiesService.getScriptProperties())  //スクリプトプロパティに保存する
    .setScope(scope)
    .setParam('login_hint', Session.getActiveUser().getEmail())
    .setParam('access_type', 'offline')
    .setParam('approval_prompt', 'force');
}

//認証コールバック
function authCallback(request) {
  var service = checkOAuth();
  Logger.log(request);
  var isAuthorized = service.handleCallback(request);
  if (isAuthorized) {
    return HtmlService.createHtmlOutput("認証に成功しました。ページを閉じてください。");
  } else {
    return HtmlService.createHtmlOutput("認証に失敗しました。");
  }
}

//ログアウト
function reset() {
  checkOAuth().reset();
  SpreadsheetApp.getUi().alert("ログアウトしました。")
}
  • startoauthから認証のコードが実行されます。OAuth認証実行時のスコープは、https://www.googleapis.com/auth/cloudprintです。

クラウドプリント実行用スクリプト

//セット済みプリンターを取得
function getSetPrinter(){
  var service = checkOAuth();
  if (service.hasAccess()) {
    var Properties = PropertiesService.getScriptProperties();
    var myPrin = Properties.getProperty("myPrinter");
  
    if(myPrin == null){
      return "NO";
    }else{
      return myPrin;
    }
  }else{
    //エラーを返す(認証が実行されていない場合)
    return "error";
  }
}

//プリンターリストを取得する
function getPrinterList() {
  var service = checkOAuth();
  if (service.hasAccess()) {
    //プリンターリストを取得
    var header = {
                    headers: {
                      Authorization: 'Bearer ' + service.getAccessToken()
                    },
                    muteHttpExceptions: true
                 }              
    var response = UrlFetchApp.fetch('https://www.google.com/cloudprint/search', header).getContentText();    

    //エラーコードを取得
    try{
      var Regexp = /<H2>([\s\S]*?)<\/H2>/i;
      var match = Regexp.exec(response);
      var code = match[1];
    }catch(e){
      Logger.log(e.message);
    }
    
    //エラーコードが返ってくる場合
    if(code == "Error 403"){
      //リフレッシュトークンでトークンを取得して、再度実行
      checkOAuth();
      getPrinterList();
    }else{
      //プリンターリストを色々どうにかするルーチン
      var printman = JSON.parse(response);
      var prinlist = printman.printers;
      var priArray = [];
      
      //ステータスがONLINEのものだけピックアップ
      for(var i in prinlist) {
        var status = prinlist[i].connectionStatus;
        if(status == "ONLINE"){
          priArray.push([prinlist[i].name,prinlist[i].id]);
        }
      }
      
      if(priArray.length == 0){
        return JSON.stringify(["NULL",0])
      }else{
        return JSON.stringify(["OK",priArray]);
      }
    }
  
  }else{
    //エラーを返す(認証が実行されていない場合)
    return "error";
  }
}

//デフォルトプリンタにセット
function setDefPrinter(value){
  var Properties = PropertiesService.getScriptProperties();
  Properties.setProperty("myPrinter", value);
  return "OK";
}

//テストプリントをする
function testprint(){
  var Properties = PropertiesService.getScriptProperties();
  var defPrinter = Properties.getProperty("myPrinter");
  var mysheet = Properties.getProperty("mysheetid");

  var docID = mysheet;
  var printerid = defPrinter;
  var docName = "prinout.pdf";
  
  var blob = DriveApp.getFileById(docID).getBlob();
  
  var ret = printGoogleDocument(blob,printerid,docName);
  
  //HTML側へ結果を返す
  return JSON.stringify(ret);
}

//プリント実行するメソッド
function printGoogleDocument(blobman, printerID, docName) {
  var service = checkOAuth();
  if (service.hasAccess()) {
    //プリンター設定
    var ticket = {
      version: "1.0",
      print: {
        color: {
          type: "STANDARD_COLOR",
          vendor_id: "Color"
        },
        duplex: {
          type: "NO_DUPLEX"
        }
      }
    };  
    
    //プリント情報を構築する
    var payload = {
      "printerid" : printerID,
      "title"     : docName,
      "content"   : blobman,
      "contentType": "application/pdf",     //今回はPDFのMIMETYPEを指定
      "ticket"    : JSON.stringify(ticket)
    };
    
    //CloudPrintに送信
    var response = UrlFetchApp.fetch('https://www.google.com/cloudprint/submit', {
      method: "POST",
      payload: payload,
      headers: {
        Authorization: 'Bearer ' + service.getAccessToken()
      },
      "muteHttpExceptions": true
    });
    
    //送信結果を受け取る
    response = JSON.parse(response);
    
    //結果を分析する
    if (response.success) {
      var ret = ["OK",response.message];
    } else {
      var ret = ["NG",response.errorCode + "¥n" + response.message];
    }
    
    //結果を返す
    return ret;

  }else{
    //エラーを返す(認証が実行されていない場合)
    return "error";
  }
}
  • プリンターリストを作成する関数、現在のデフォルトプリンタを返す関数、セットする関数、テスト印刷する関数などを用意しておきます。
  • プリントを実行する関数におけるpayloadのcontentTypeはMIMETYPEを指定しますが、一部は省略したMIMETYPEでも可になっています。(例:google documentならばgoogle.kixでOK)
  • 画像などを印刷する場合には、Base64でエンコードしてから送ってあげる必要があります。PDFやその他バイナリファイルは必ず、getblobで取得したものをcontentに入れます(Google Drive内のファイルの場合)。
  • contentには直URLを入れても大丈夫です。
  • 今回、プリンター設定はシンプルなものだけです。他にも印刷枚数やその他いろいろなオプション設定を追加できます。

HTML側コード

認証ダイアログ用のHTML(template.html)

<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css">
<script type="text/javascript" src="https://apis.google.com/js/api.js"></script>
<script>
  google.script.run.withSuccessHandler(onSuccess).authpage();
  
  function onSuccess(data){
    document.getElementById("kinoko").innerHTML = data;
  }

</script>

<style type="text/css">
/* --- ボックス --- */
div.section {
width: 480px; /* ボックスの幅 */
background-color: #ffffff; /* ボックスの背景色 */
border: 1px #c0c0c0 solid; /* ボックスの境界線 */
font-size: 100%; /* ボックスの文字サイズ */
}

/* --- 見出し --- */
div.section h3 {
margin: 0; /* 見出しのマージン */
padding: 6px 10px; /* 見出しのパディング(上下、左右) */
background-color: #f5f5f5; /* 見出しの背景色 */
border-bottom: 1px #c0c0c0 solid; /* 見出しの下境界線 */
font-size: 120%; /* 見出しの文字サイズ */
}

/* --- ボックス内の段落 --- */
div.section p {
margin: 1em 10px; /* 段落のマージン(上下、左右) */
}
</style>


<div class='section'>
<img border="0" src="http://blog.movereem.nl/images/oauth.png" alt="oauth2">
<h3 id='header'>OAuth認証の許可が必要です。</h3>
<hr>
<div id="info">
<p>
このスクリプトは、Google Cloud Printにアクセスするために、特別なログイン処理を利用しています。<br>
既に特別なログインに関する設定はなされており、承認がされるとプログラムを実行することが出来ます。この承認がなされない場合、プログラムの実行に制限が掛かり、
処理が続行できません。<br><br>

<div id="kinoko"></div>
</div>
<p>
<script>
function closeMe(){
  if(google && google.script && google.script.host){
      google.script.host.close();
  } else if(window && window.close){
      window.close();
  } 
}
</script>
</div>
  • 認証用ダイアログで、GAS側のauthpage()関数で生成した認証用URLをリンクに与えています。
  • リンクをクリックするとCloud Printの認証が実行されて、Access Tokenなどが取得される仕組みです。

印刷設定用のサイドバー(sidebar.html)

<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css">
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script>
    google.load("visualization", "1", {packages:["corechart"]});
    google.setOnLoadCallback(test);
    
    var setPrinterID = "";
    
    function test(){
      google.script.run.withSuccessHandler(myPrinter).getSetPrinter();
      google.script.run.withSuccessHandler(onPrinter).getPrinterList();
      $('#btnPrint').click(function(){
        google.script.run.withSuccessHandler(prinresult).testprint();
      });
    }
    
    function myPrinter(ret){
      setPrinterID = ret;
    }
    
    //有効なプリンターリストがあればメニューを作る
    function onPrinter(data){
      var json = JSON.parse(data);
      var status = json[0];
      var html = "<p>";
      
      switch(status){
        case "NG":
          html = "OAuth認証ができていません。";
          break;
        case "NULL":
          html = "オンラインなプリンターがありません";
          break;
        case "OK":
          var array = json[1];
          var alength = array.length;
          html = "<select id='prinid'><option>プリンターを選択</option>";
          
          for(var i = 0;i<alength;i++){
            if(array[i][1] == setPrinterID){
              html += "<option value='" + array[i][1] + "' selected>" + array[i][0] + "</option>";
            }else{
              html += "<option value='" + array[i][1] +  "'>" + array[i][0] + "</option>";
            }
          }
          break;
      }
      
      html += "</select></p>"
      
      //ボタンを追加する
      html += "<p><button class='action' onClick='prinSet();'>選択</button></p>"
    
      //htmlを反映する
      document.getElementById("printman").innerHTML = html;
      
    }
    
    function prinSet(){
      var value = document.getElementById("prinid").value;
      
      if(value == "プリンターを選択"){
        alert("プリンターを選んでくださいよ。");
      }else{
        google.script.run.withSuccessHandler(retHello).setDefPrinter(value);
      }
    }
    
    function retHello(data){
      alert(data);
    }
    
    function prinresult(data){
      var json = JSON.parse(data);
      
      if(json[0] == "OK"){
        alert("印刷されましたよ");
      }else{
        alert(json[1]);
      }
    }

    function clickfunc(obj) {
      var num = Number(obj.title);
      google.script.run.insertPara(num);
      return false;
    }
</script>
<style>
    div.wasabi{
      padding:3px 5px;
      border-color:#0B0099;
      border-width:0 0 1px 7px;
      border-style:solid;
      background:#F8F8F8;
      vertical-align: middle;
    }
    div.kousin {
      overflow-y: auto;
      width:95%; height:200px;
      padding:3px;
      border:1px solid #000;
      color:#000000;
      background-color:#ffffff;
      line-height:1.0em;
      margin-left: auto;
      margin-right: auto;
    }
    div.boxContainer {
      overflow: hidden;
      width:100%;
    }
    .box {
      float: left;
      width: 100%;
      margin-right:5px;
    }
    #linkArea{
      position: relative;
      width: 100%;
      height: 100%;
      border-color: #999999;
      border-style: none;
    }
    #linkArea a{
      display: block;
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
    }
</style>
<div class="wasabi"><img src='https://dl.dropboxusercontent.com/u/3688633/icons/printer_icon.gif'>&nbsp;&nbsp;プリンタの選択</div><p>
<div id="printman"></div>

<div class="wasabi"><img src='https://dl.dropboxusercontent.com/u/3688633/icons/icon_pdf.gif'>&nbsp;&nbsp;テスト印刷</div><p>
<p><button id="btnPrint" class="create">印刷を実行する</button></p>
きちんと認証を完了していれば、対象のプリンターからこのシートが印刷がされますよ。
  • サイドバーオープン時にGAS側のgetsetprinter()関数でセット済みプリンタを取得しています。
  • サイドバーオープン時にGAS側のgetPrinterlist()関数で登録済みプリンターの一覧を取得しています。
  • 印刷を実行するには、GAS側testprint()関数を実行するようにアクションを追加しています。

実行結果

クライアントIDやシークレットを入力後は、まず、OAuth認証をしておく必要があります。

  1. スプレッドシートのメニューより「セッティング」⇒「OAuth認証実行」をクリック
  2. GASの認証が表示されたら、普通に認証をする。
  3. 続けて、OAuth2.0認証のダイアログが出るので、許可をクリック。
  4. これで完了。スクリプトプロパティにアクセストークンなどが格納されています。

図:プリンタの管理のリクエストが表示されます。

続けて、スプレッドシートのメニューより「セッティング」⇒「印刷設定」を開くとサイドバーが開かれます。登録済みのプリンター一覧からプリンタを選び、選択をクリックします。続けて印刷を実行するをクリックすると、プリンタに現在のシートのデータが印刷されます。

図:登録済みプリンターの一覧が呼び出されて表示されます。

※今回は単純なテスト用の印刷ルーチンまででしたが、本格的に他のプログラムにこれらを準備の上組み込んでおけば、自動印刷が実現出来ます。使いみちは結構広いので、ぜひ組み込んでみましょう。

関連リンク

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)