tag:crieit.net,2005:https://crieit.net/tags/GoogleAppsScript/feed 「GoogleAppsScript」の記事 - Crieit Crieitでタグ「GoogleAppsScript」に投稿された最近の記事 2024-04-16T23:38:37+09:00 https://crieit.net/tags/GoogleAppsScript/feed tag:crieit.net,2005:PublicArticle/18812 2024-04-16T23:38:37+09:00 2024-04-16T23:38:37+09:00 https://crieit.net/posts/JS-Google-Apps-Script-vite 【JS】Google Apps Scriptのコードをviteでバンドルする <p>こんにちは、しきゆらです。<br /> 今回は、Google Apps Script(GAS)をViteでビルドしてclaspでプッシュする環境を作っていきます。<br /> ・・・久々のプログラム関連の記事な気がする。</p> <p>個人的に、GASのコードをローカルで書き、JSのバンドラー?ビルダー?を使ってビルドしてclaspでアップロードする、というのがGASを書く上でのお作法だと思っています。</p> <p>claspに関しては、こちらをご覧ください。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/google/clasp">GitHub - google/clasp: 🔗 Command Line Apps Script Projects https://github.com/google/clasp</a></p> <p>ビルドツールも様々あります。<br /> Webpackやparcel、esbuildなどいろいろあるうえに、時代によって流行り廃りが速いのであまり追い切れてません。<br /> ビルドツールについてはいかがいろいろまとまっててよさそう。</p> <p><a target="_blank" rel="nofollow noopener" href="https://zenn.dev/nakaakist/articles/86457bf2908379">JavaScriptビルドツールの整理 各ツールの機能と依存関係</a><br /> <a target="_blank" rel="nofollow noopener" href="https://zenn.dev/mizchi/articles/native-esm-age">Native ESM 時代のフロントエンドビルドツールの動向</a></p> <p>また、過去のツールの流行り廃りは以下あたりが参考になるかと。</p> <p><a target="_blank" rel="nofollow noopener" href="https://stateofjs.com/en-US">State of JavaScript</a></p> <p>ということで、いろいろあるわけですが<br /> 今回は、tauriでちょっと使って動作が速かったviteを使ってGASをビルドする環境を作ってみようと思います。</p> <p><a target="_blank" rel="nofollow noopener" href="https://ja.vitejs.dev/">Vite | 次世代フロントエンドツール</a></p> <p>Tauriを触ったときの記事はこの辺をご覧ください。</p> <p><a target="_blank" rel="nofollow noopener" href="https://shikiyura.com/2023/08/create_simple_app_with_tauri/">【Rust】Tauriで簡易的なアプリを作ってみる</a></p> <p>今回、調べてみるとviteを使ってGASを書く記事はHTMLを作ってWebアプリとしての環境構築ばかりでした。<br /> 欲しいのはJSやTSをビルドするだけの環境です。<br /> 色々調べているとviteの裏側にあるRollupのプラグインでGAS向けのものがあったのでこれを使ってみます。</p> <h2 id="環境構築"><a href="#%E7%92%B0%E5%A2%83%E6%A7%8B%E7%AF%89">環境構築</a></h2> <p>適当なフォルダで以下のツールをインストール。<br /> ここではyarn(v4.1.1)を使ってインストールしていますが、他のツールを使っている場合も同じような感じかと思うので読み替えていただければと。</p> <p><code>yarn add clasp vite rollup-plugin-google-apps-script</code></p> <p>それぞれのリポジトリをいかにおいておきます。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/google/clasp">GitHub - google/clasp: 🔗 Command Line Apps Script Projects</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/vitejs/vite">GitHub - vitejs/vite: Next generation frontend tooling. It's fast!</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/mato533/rollup-plugin-gas#readme">GitHub - mato533/rollup-plugin-gas: Rollup plugin for Google Apps Script. This plugin supports local development of applications that run on Google Apps Script.</a></p> <h2 id="vite/rollupの設定"><a href="#vite%2Frollup%E3%81%AE%E8%A8%AD%E5%AE%9A">vite/rollupの設定</a></h2> <p>viteは設定ファイルが必要です。<br /> しかし、必要なものは以下に参考としてあるので、これをもとにちょっと手直しします。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/mato533/rollup-plugin-gas?tab=readme-ov-file#vite">GitHub - mato533/rollup-plugin-gas: Rollup plugin for Google Apps Script. This plugin supports local development of applications that run on Google Apps Script.</a></p> <pre><code class="javascript">// vite.config.js import { defineConfig } from "vite"; import rollupPluginGas from "rollup-plugin-google-apps-script"; import path from "path"; export default defineConfig({ plugins: [rollupPluginGas()], build: { rollupOptions: { input: "src/main.js", output: { dir: "dist", entryFileNames: "main.js", }, }, minify: false, // trueにすると関数名が消えるのでfalse必須 }, resolve: { alias: { "@": path.resolve(__dirname, "./src"), }, }, }); </code></pre> <p>ここでは<code>src/main.js</code>というファイルを作り、これを<code>dist/main.js</code>へビルドする形を作っていきます。</p> <h2 id="GASのコードを書く"><a href="#GAS%E3%81%AE%E3%82%B3%E3%83%BC%E3%83%89%E3%82%92%E6%9B%B8%E3%81%8F">GASのコードを書く</a></h2> <p>では、<code>src/main.js</code>へ適当なコードを書きます。<br /> 単一ファイルだけでなく、せっかくなので分割してimportする形で書いてみます。</p> <pre><code class="javascript">// src/main.js import parts from "./parts"; const greet = () => { Logger.log("hello"); }; global.greet = greet; global.parts = parts; </code></pre> <pre><code class="javascript">// src/parts.js export default parts = () => { Logger.log("parts"); }; </code></pre> <p>では、ビルドします。<br /> ビルドは以下のコマンドでできます。</p> <p><code>yarn run vite build</code></p> <p>ビルドすると以下のようなコードが吐き出されるはず。</p> <pre><code class="javascript">// dist/main.js var global = this; function greet() { } ; function parts() { } ; (function(factory) { typeof define === "function" && define.amd ? define(factory) : factory(); })(function() { "use strict"; const parts$1 = parts = () => { Logger.log("parts"); }; const greet2 = () => { Logger.log("hello"); }; global.greet = greet2; global.parts = parts$1; }); </code></pre> <p>では、コードをGASへアップしましょう。</p> <h2 id="Google Apps Scriptへプッシュする"><a href="#Google+Apps+Script%E3%81%B8%E3%83%97%E3%83%83%E3%82%B7%E3%83%A5%E3%81%99%E3%82%8B">Google Apps Scriptへプッシュする</a></h2> <p>まずは、claspからログインしましょう。<br /> 以下のコマンドを実行すると、ブラウザが立ち上がるので、画面に従ってGoogleアカウントへログインしましょう。</p> <p><code>clasp login</code></p> <p>ログイン後、以下のコマンドでApps Scriptを作成します。 なお、すでに作成済みの場合は場合はcloneでもよいです。</p> <pre><code class="bash">> clasp create ? Create which script? standalon reated new standalone script: <https://script.google.com/d/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/edit> Warning: files in subfolder are not accounted for unless you set a '/path/to/project/.claspignore' file. Cloned 1 file. └─ /path/to/project/appsscript.json </code></pre> <p><code>clasp create</code>を実行するとどんなスクリプトか聞かれるので、今回はstandaloneを指定してます。</p> <p>完了すると、URLが表示されます。<br /> これを開くと、作成されたApps Scriptへアクセスできます。<br /> また、<code>clasp open</code>でも開けます。</p> <p>そして、<code>appsscript.json</code>、<code>.clasp.json</code>の2ファイルが生成されます。<br /> <code>.clasp.json</code>ファイルを開き、rootDirを<code>vite.config.js</code>で設定したdistフォルダを指定します。</p> <p><code>// .clasp.json { "scriptId":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "rootDir":"/path/to/project/dist" }</code></p> <p>そして、<code>appsscript.json</code>ファイルをdistフォルダへコピーします。</p> <p>では、<code>clasp push</code>でコードをApps Scriptへアップロードします。</p> <p><code>clasp push</code></p> <p>Apps Scriptを開くと、以下のようにコードが反映されているはずです。<br /> では、関数名を指定して実行ボタンを押して動作確認しましょう。<br /> <a href="https://crieit.now.sh/upload_images/7a5da27598b0ac7c7bde3fb86338c228661e8d0c5f2ec.webp" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7a5da27598b0ac7c7bde3fb86338c228661e8d0c5f2ec.webp?mw=700" alt="Untitled_result.webp" /></a></p> <p>greetを実行すると以下のように表示されるはずです。<br /> <a href="https://crieit.now.sh/upload_images/c069e96e53f499d91294f7f327e3c24f661e8d1ce2c95.webp" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/c069e96e53f499d91294f7f327e3c24f661e8d1ce2c95.webp?mw=700" alt="Untitled2_result.webp" /></a></p> <p>はい、これにてviteでGASを書くための環境ができました。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>今回はローカルでGASを書くためviteでビルドする環境を作ってみました。<br /> viteは裏側がrollupであり、プラグインもrollupのものが使えるようなので、プラグインを見てから判断してもよいかもしれません。</p> <p><a target="_blank" rel="nofollow noopener" href="https://ja.vitejs.dev/plugins/">プラグイン | Vite https://ja.vitejs.dev/plugins/</a></p> <p>今回は、ここまで。</p> <p>おわり</p> しきゆら tag:crieit.net,2005:PublicArticle/16026 2020-08-09T13:31:47+09:00 2020-08-12T11:33:36+09:00 https://crieit.net/posts/spreadsheet-scraping Googleスプレッドシートでスクレイピング <p>スクレイピングがやりたかったんだけど、サーバの管理がめんどくさくなったので、Googleスプレッドシートの上でスクレイピングを出来るようにした。</p> <p><a href="https://crieit.now.sh/upload_images/9d08c6d37e5274805ec35413f543d5ed5f2fb0bc27cbc.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/9d08c6d37e5274805ec35413f543d5ed5f2fb0bc27cbc.png?mw=700" alt="image.png" /></a></p> <h1 id="やりたいこと"><a href="#%E3%82%84%E3%82%8A%E3%81%9F%E3%81%84%E3%81%93%E3%81%A8">やりたいこと</a></h1> <p>献血で、400mlAB型の血液が不足してます、A型は今大丈夫です、みたいな情報が、献血センターのwebサイトに掲載されるようになった。<br /> たとえば <a target="_blank" rel="nofollow noopener" href="https://www.bs.jrc.or.jp/th/miyagi/">宮城県赤十字血液センター</a>の今週の献血状況。AB型の人は成分献血にするか、次の機会にしたほうが良いらしい。<br /> <a href="https://crieit.now.sh/upload_images/9d08c6d37e5274805ec35413f543d5ed5f2f346e54241.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/9d08c6d37e5274805ec35413f543d5ed5f2f346e54241.png?mw=700" alt="image.png" /></a></p> <p>これ以外の県も、<a target="_blank" rel="nofollow noopener" href="http://www.jrc.or.jp/search/bloodcenter/index.html">各県の献血センター</a>に掲載されている<br /> こういう情報をオープンにしてくれたのはとてもうれしいことなんだけど、webページに掲載されたって、ぼくらがわざわざ見に行かない限り気が付かない。できればこう、SNSで「今週の献血状況」みたいなことを発表してくれるといいよね。そしたら、「あ、AB型足りない?今週は余裕があるからじゃあぼく行くね」みたいなことが出来ていいと思うのよ。</p> <p>ないなら作れの精神で、とりあえずWebページ上にあるデータを毎日取得して蓄積してみることにした。あとでグラフとか需要の波が見られたら面白そうじゃない?</p> <p>でもそのためにサーバとか管理するのもなあ。もうちょっと気軽にやれる方法はないの?</p> <h1 id="GoogleAppsScript"><a href="#GoogleAppsScript">GoogleAppsScript</a></h1> <p>Googleスプレッドシートには、GoogleAppsScript(gas)というスクリプト機能があって、JavaScriptでスプレッドシートのセルの値を読み書きしたり、ボタンを押すとメソッドを実行するようにしたりすることが出来る。<br /> <a target="_blank" rel="nofollow noopener" href="https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app">UrlFetchApp</a>というライブラリがあってWebページをダウンロードすることも出来る。あと時刻を設定して指定した関数を起動したりする機能もある。ということは、タイマーで定期的にWebページを取得させることが出来る。サーバレスでスクレイピングができるとかなり嬉しい。</p> <p>ただDomパーサーがないので、取ってきたHTMLは正規表現で切り出さないといけない。サーバの管理すらしたくない駄目人間にそれはちょっと厳しい。</p> <h1 id="ImportXML"><a href="#ImportXML">ImportXML</a></h1> <p>スプレッドシートには、<a target="_blank" rel="nofollow noopener" href="https://support.google.com/docs/answer/3093342?hl=ja">importXML</a>というメソッドがある。<br /> これを使うと、URLとXPathを書くだけでそのページの該当箇所を取ってきてセルに入れてくれるすごいメソッド。</p> <p><a href="https://crieit.now.sh/upload_images/9d08c6d37e5274805ec35413f543d5ed5f2f4e4747b9c.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/9d08c6d37e5274805ec35413f543d5ed5f2f4e4747b9c.png?mw=700" alt="image.png" /></a></p> <p>ただこれ簡単メソッドだけに色々問題があって、<br /> ・厳密にXMLとして解釈しようとするので、乱暴なHTMLのページは取得できないことが多い<br /> ・JSの解釈もしてくれないぽい<br /> ・XPath書くの面倒w<br /> ・取得タイミングが制御できない</p> <p>最後のがわりと致命的で、キャッシュ制御がブラックボックスなので、ページが更新されても内容が更新されなかったり、予想されないタイミングでキャッシュが破棄されて内容が更新されていたりする。</p> <p>現時点の内容がわかれば十分な用途には便利なんだけど、毎日データを取ってくるような用途にはちょっと使いにくい。</p> <h1 id="cheeriogs"><a href="#cheeriogs">cheeriogs</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/cheeriojs/cheerio">cheerio</a>は、jQueryぽい文法でHTMLをパースすることが出来るnode.jsライブラリ。ブラウザじゃなくてもjQueryぽくHTMLを解析できるので、スクレイピングのときに便利。<br /> 残念ながらgasにはサイズ制限があるので、npm install cheerio みたいに気軽に使うことは出来ないんだけど、代わりにつかえるgas library でcheerioを使えるようにしてくれた <a target="_blank" rel="nofollow noopener" href="https://github.com/tani/cheeriogs">cheeriogs</a> という神がいるので、これを使うとスプレッドシートのスクリプト内でもHTMLをパースして内容を取ってくることが出来る。これですべての材料は揃った。</p> <h1 id="実装"><a href="#%E5%AE%9F%E8%A3%85">実装</a></h1> <p>スプレッドシートのメニューからスクリプトエディタを立ち上げる。<br /> <a href="https://crieit.now.sh/upload_images/9d08c6d37e5274805ec35413f543d5ed5f2f759697cfa.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/9d08c6d37e5274805ec35413f543d5ed5f2f759697cfa.png?mw=700" alt="image.png" /></a></p> <p>リソース- ライブラリ でライブラリ画面を開いて、<a target="_blank" rel="nofollow noopener" href="https://github.com/tani/cheeriogs">cheeriogs</a> に書いてあるIDを入力してcheerioライブラリをインポート。</p> <p><a href="https://crieit.now.sh/upload_images/9d08c6d37e5274805ec35413f543d5ed5f2f7639e92dd.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/9d08c6d37e5274805ec35413f543d5ed5f2f7639e92dd.png?mw=700" alt="image.png" /></a></p> <p>あとは、こんな感じで書けばページの内容を取得できる。</p> <pre><code>const content = UrlFetchApp.fetch(url).getContentText(); const $ = Cheerio.load(content); const title = $("title").first().text(); ret["title"] = title; ret["description"] = $("meta[name='description']").first().attr("content"); ret["pref"] = title.replace("赤十字血液センター|日本赤十字社","") ret["demand_date"] = $(".center-main-today-timedate").first().text(); ret["demands"] = []; $(".center-main-today-types-state").each((index, element) => { ret["demands"][index] = $(element).text(); }) : </code></pre> <p>まんまjQueryでしょ?</p> <p>このあと、スプレッドシートに載っている前回の結果を右にずらして左端に今回の結果を追加したりする処理があるんだけど、そこはたんなるgasのスプレッドシート操作なので省略。</p> <h1 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/tani/cheeriogs">cheeriogs</a> をつかうことで、献血の需給状況をスクレイピングすることが出来るようになった。タイマーで毎日起動するようにしたので、現在は放置しておいても毎日夜中にその日の情報をとってきてくれるようになっている。</p> <p><a href="https://crieit.now.sh/upload_images/9d08c6d37e5274805ec35413f543d5ed5f2f78ff6c048.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/9d08c6d37e5274805ec35413f543d5ed5f2f78ff6c048.png?mw=700" alt="image.png" /></a></p> <p>単なるスプレッドシートなので、条件付き書式(セルの色塗り)とかはスクリプトと無関係に出来てしまうのも便利。</p> <p>gasはrubyでいうirbみたいな即時実行ツールがないし、デバッガも貧弱なので、そんなに開発体験が良いわけではない。<br /> でも無料で使えるし、タイマーとか便利だし、なによりスプレッドシートという強力なアウトプット窓口を使えるので、サービスの管理画面なんかはこれで作るのもありだなあ、と思った。</p> <p>あとはこれをtwitter botにすれば良いんだけど、例の感染症騒ぎで献血が逼迫した結果、今はどのエリアもみんな「成分献血:非常に困っています」しか出なくなってしまっている。この状況だと、献血を知っている人は献血したら次回の予約をして行く感じになっていて、Twitterで状況を知らせてくれると嬉しいとかいう感じじゃない。<br /> 今必要なのは、献血についてあんま知らない人が「えーそんな足りないの?」ということで献血にチャレンジしてくれることなので、この記事を見た人、ぜひご協力をお願いします。献血は不要不急じゃなくて必要なタスクです!</p> daisuke furukawa tag:crieit.net,2005:PublicArticle/15457 2019-10-07T09:13:01+09:00 2019-10-07T09:31:08+09:00 https://crieit.net/posts/GAS-5d9a830d875a5 GASでいいねしたツイートをいいねの数とリツイートの数と一緒に記録するやつ <p>特定のユーザーのいいねしたツイート一覧を、ツイートに付いたいいねの数とリツイートの数も一緒に記録したくなって作りました。トリガー機能を使うと、定期的にいいねが記録されるようになります。</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/yanagiha/items/40c7f0cc140eace11bd8">GASで自分のツイートを取得してスプレッドシートに記録するやつ<br /> </a><br /> この記事も合わせて読むとわかりやすいかもしれません。</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406061/d311791c-7655-7d87-1dba-cb3ee543444d.png" alt="スクリーンショット 2019-10-07 3.40.44.png" /></p> <p>このように記録されます。</p> <pre><code class="javascript"><br /><br />function getFav(){ var service = twitter.getService(); var json = service.fetch("https://api.twitter.com/1.1/favorites/list.json?screen_name=いいねを取得したいアカウントのscreen name&count=100"); var array = JSON.parse(json); var ss = SpreadsheetApp.getActiveSpreadsheet(); var sheet = ss.getSheetByName('いいねを記録したいシートの名前'); var lastRow = sheet.getLastRow() +1; var lastId = sheet.getRange("F2").getValue(); for(var i = 0; i <= array.length -1; i++) { var int = parseInt(i); if(i === 0){ var recId = array[int]["id"]; sheet.getRange("G2").setValue(recId); } var id = array[int]["id"]; if(id > lastId){ var time = array[int]["created_at"]; var userId = array[int]["user"]["id_str"]; var json = service.fetch("https://api.twitter.com/1.1/users/show.json?user_id="+userId+"&include_entities=false"); var array2 = JSON.parse(json); var screenName = array2["screen_name"]; var text = array[int]["text"]; var favorite_count = array[int]["favorite_count"]; var retweet_count = array[int]["retweet_count"]; sheet.getRange(lastRow,1).setValue(time); sheet.getRange(lastRow,2).setValue(screenName); sheet.getRange(lastRow,3).setValue(text); sheet.getRange(lastRow,4).setValue(favorite_count); sheet.getRange(lastRow,5).setValue(retweet_count); sheet.getRange(lastRow,6).setValue(id); } lastRow = lastRow + 1; } </code></pre> <p>いいねを100件取得して、idが以前記録したツイートより大きければ記録する…といった感じです。<br /> 何かあったら気軽にコメントください。</p> yanagiha tag:crieit.net,2005:PublicArticle/15456 2019-10-07T09:10:43+09:00 2019-10-07T09:10:43+09:00 https://crieit.net/posts/GAS GASで自分のツイートを取得してスプレッドシートに記録するやつ <p>急にツイ消ししたくなった場合に備えて、自分のツイートをグーグルスプレッドシートに記録しておくことにしました。</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/golyat/items/6b430986dbfd8dd1c239">GASでTwitterの投稿とタイムライン取得</a><br /> こちらの記事を参考にして書いてみました。</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406061/43b4b722-3b85-bb5e-be81-133be3f4d987.jpeg" alt="20190828150010.jpg" /><br /> 画像のように記録されます。</p> <pre><code class="javaScript"><br /> var twitter = TwitterWebService.getInstance( '**********', // 作成したアプリケーションのConsumer Key '**********' // 作成したアプリケーションのConsumer Secret ); // 認証を行う(必須) function authorize() { twitter.authorize(); } // 認証をリセット function reset() { twitter.reset(); } // 認証後のコールバック(必須) function authCallback(request) { return twitter.authCallback(request); } function getMyTweets() { var service = twitter.getService(); var json = service.fetch("https://api.twitter.com/1.1/statuses/user_timeline.json?screen_name=ツイートを取得したいユーザーのid&count=30"); var array = JSON.parse(json); var ss = SpreadsheetApp.getActiveSpreadsheet(); var sheet = ss.getSheetByName('ツイートを保存したいシートの名前'); var lastRow = sheet.getLastRow() +1; var lastId = sheet.getRange("D2").getValue(); for(var i = 0; i <= array.length -1; i++) { var int = parseInt(i); if(i === 0){ var recId = array[int]["id"]; sheet.getRange("D2").setValue(recId); } var id = array[int]["id"]; if(id > lastId){ var time = array[int]["created_at"]; var text = array[int]["text"]; sheet.getRange(lastRow,1).setValue(time); sheet.getRange(lastRow,2).setValue(text); sheet.getRange(lastRow,3).setValue(id); } lastRow = lastRow + 1; } } </code></pre> <p>自分のツイートを最新のやつから30件まで取得して、idが以前取得したツイートより大きければスプレッドシートに書き込む……という感じです。</p> <p>ここ違うよ〜とかもっと良い書き方あるよ〜って場合は気軽にコメントください。</p> yanagiha