・SIMカードを指す
・LAMケーブルを指す。
・WiFiは使わないのでボタンでオフにする
192.168.1.1にアクセスする。
パスワードを求められるので決める。
・設定画面が開く
(今回はau)
詳細設定>ネットワーク>インターネット
プロファイル名:au
PDPタイプ:IPv4
APNタイプ:静的
APN:uad5gn.au-net.ne.jp
ユーザー名:au@uad5gn.au-net.ne.jp
パスワード:au
認証タイプ:CHAP
・SIMカードを指す
・LAMケーブルを指す。
・WiFiは使わないのでボタンでオフにする
192.168.1.1にアクセスする。
パスワードを求められるので決める。
・設定画面が開く
(今回はau)
詳細設定>ネットワーク>インターネット
プロファイル名:au
PDPタイプ:IPv4
APNタイプ:静的
APN:uad5gn.au-net.ne.jp
ユーザー名:au@uad5gn.au-net.ne.jp
パスワード:au
認証タイプ:CHAP
Vaultwardenで利用したVPSに追加の別サブドメインでMeshCentralをインストールしてみる。
・Azure DNSゾーン
対象のドメインを選択
>+子ゾーン
>確認および作成
>作成
作成されたゾーンを選択
Aレコードに、sub.domainを追加する。
>レコードセット
>+追加
種類:A
IPアドレス:対象のアドレス
として作成。
・caddy
1 |
sudo vim /etc/caddy/Caddyfile |
1 2 3 4 5 6 |
※ここにはVaulwardenの設定がある sub.domain { reverse_proxy localhost:8081 tls メールアドレス } |
1 |
sudo systemctl restart caddy |
取得できているかどうか
1 |
sudo ls -la /var/lib/caddy/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory |
・Docker
1 2 3 |
sudo mkdir meshcentral cd meshcentral sudo vim docker-compose.yml |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
services: meshcentral: container_name: meshcentral image: ghcr.io/gurucomputing/meshcentral-docker:latest restart: "always" volumes: - ./container-data/meshcentral-data:/meshcentral/meshcentral-data - ./container-data/meshcentral-files:/meshcentral/meshcentral-files - ./container-data/meshcentral-backup:/meshcentral/meshcentral-backup - /etc/localtime:/etc/localtime:ro environment: - MONGODB_URL=mongodb://meshcentral-db:27017 - MONGODB_NAME=meshcentral - DB_ENCRYPT_KEY=ランダム文字列 - REVERSE_PROXY=true - REVERSE_PROXY_TLS_TERMINATION=true ports: - "8081:443" networks: - meshcentral-nw meshcentral-db: container_name: meshcentral-db image: mongo:latest restart: "always" volumes: - ./container-data/db:/data/db - /etc/localtime:/etc/localtime:ro networks: - meshcentral-nw networks: meshcentral-nw: |
1 |
sudo docker compose up -d |
・MeshCentral
1 |
sudo vim container-data/meshcentral-data/config.json |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
{ "settings": { "Port": 443, "AgentPort": 443, "TlsOffload": "127.0.0.1", "Cert": "sub.domain", "mongoDb": "mongodb://meshcentral-db:27017", "mongoDbName": "meshcentral" }, "domains": { "": { "certUrl":"https://sub.domain" } } } |
・ダウンロードしたAgentのwss://domain:port/agent.ashxが以下で変わる。
AgentPortは、portの部分、Certはdomainの部分。
ここでCertを指定しないとLANだけの動作になる。
●Agent
・MeshCentral>ログインしてアカウントを作成
・デバイスグループを作成>デバイスを作成
・Agentインストーラーをダウンロード>実行
・プログラムフォルダに作成されたプログラムを起動し、インストール
※インストーラーが起動しない場合、
システム>オプション機能>機能を表示>
WMICを追加する
●メモ
再起動する場合
sudo docker compose restart meshcentral
sudo docker compose restart meshcentral-db
・エラー発生した場合
コンテナID確認
sudo docker ps
コンテナ停止
sudo docker container stop コンテナID
コンテナを削除
sudo docker rm コンテナID
ログ
sudo docker logs コンテナID
・Indigoで新規インスタンスを生成
Ubuntu 24.04
2vCPU/2GB/40GB
・TeraTermからSSHアクセス
・Docker
1 2 3 4 5 6 7 |
sudo apt update sudo apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" sudo apt-get update sudo apt install docker-ce docker-ce-cli containerd.io docker --version |
・Docker Compose
1 2 |
sudo apt install docker-compose-plugin docker compose version |
・DNS設定
Azure>DNSゾーン>+子ゾーン
名前のところにサブドメインにしたい値を入れて、確認及び作成
作成したサブドメイン付きのドメインのレコードセットを編集
AレコードでVPSのIPアドレスを指定
・Caddy
1 2 |
sudo apt install -y caddy sudo vim /etc/caddy/Caddyfile |
1 2 3 4 5 |
ドメイン { reverse_proxy /notifications/hub localhost:3012 reverse_proxy localhost:8080 tls メールアドレス } |
1 |
sudo systemctl start caddy |
・Vaultwarden
1 2 3 |
sudo mkdir vaultwarden cd vaultwarden sudo vim docker-compose.yml |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
services: vaultwarden: image: vaultwarden/server:latest container_name: vaultwarden environment: - TZ=Asia/Tokyo env_file: - ./vaultwarden.env volumes: - ./vw-data:/data ports: - 8080:80 - 3012:3012 restart: always |
1 2 |
sudo vim vaultwarden.env ADMIN_TOKEN='40文字のランダム文字列' |
1 |
sudo docker compose up -d |
これでhttps://sub.domainでアクセスできる。
・ufw
1 2 3 4 |
sudo ufw allow 22/tcp sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw enable |
sudo vim /etc/default/ufw
IPV6=no
1 2 |
sudo ufw reload sudo ufw status |
・ssh
sudo vim /etc/ssh/sshd_config
・PasswordAuthentication
・ChallengeResponseAuthentication
・GSSAPIAuthentication
3つともnoへ
1 |
sudo service ssh restart |
・Dockerコマンドメモ
全てのコンテナ
sudo docker compose ps -a
全てのコンテナストップ
sudo docker stop $(sudo docker ps -q)
全てのコンテナ削除
sudo docker container prune
・Caddyメモ
caddy設定ファイル
1 2 3 |
sudo ls -la /var/lib/caddy/.local/share/caddy/acme/acme-v02.api.letsencrypt.org-directory/users/ ※表示されたメールアドレスを入力して実行 sudo cat /var/lib/caddy/.local/share/caddy/acme/acme-v02.api.letsencrypt.org-directory/users/メールアドレス/メールアドレス@より前.json |
Microsoftの職場・学校用アカウントで使えるExchange Onlineに余っていたドメインを設定してみる。
今回はMuuMuuで取得したドメイン→AzureDNSゾーンだった。
Microsoft 365 管理センター>ドメイン
https://admin.microsoft.com/adminportal/home#/Domains
から対象ドメインを追加する。
所有者の確認があるので、
AzureのDNSゾーン>レコードセット>追加
から以下の設定
指定のTXTレコードを追加
名前:空白
TTL:1時間
値:指定の値
Azureで操作しているユーザーのロール不足だったので、DNSゾーン>アクセス制御の画面から、ユーザーに所有者ロールを付与する。
表示されたCNAME,MX,TXTをAzure>DNSゾーンから追加する。このドメインはConoHaのMattermostテンプレートで利用していたので、Aレコードはすでに設定済みだったが問題なかった。
このタイミングで、以前からAzureでユーザーに利用していたドメインも新しいドメインに変更するため、新しいドメインでユーザーを作成し、グローバル管理者のロールを追加する。
DNSゾーンなど設定が新しいユーザーからでも見えるように、リソースグループ、サブスクリプショングループのページのアクセス制御から、新しいユーザーを追加する。
更新プログラムの画面で”このPCでの更新プログラムは組織が管理しています”と表示される場合、
gpedit.msc>コンピュータの構成>管理用テンプレート>Windowsコンポーネント>WindowsUpdate
以下のサブフォルダの設定をすべて未構成にしてあるかどうか確認。
復元処理中、容量不足でエラー表示。
まず、
compmgmt.msc>記憶域>ディスク管理>C:>ボリュームの拡張
を実施(環境によって、通常効果はない)
次に、2回に分けて復元し、圧縮を実施した。(1度目の復元後に下記実施し、残りを復元)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
USE master; -- master データベースに接続。sys.databases ビューを参照するため。 GO -- データベース名を格納する変数を宣言 DECLARE @DBName NVARCHAR(MAX); -- カーソルを宣言して、システムデータベースを除外したすべてのデータベース名を取得 DECLARE db_cursor CURSOR FOR SELECT name FROM sys.databases WHERE name NOT IN ('master', 'model', 'msdb', 'tempdb') -- システムデータベースを除外 -- カーソルを開く(データベースのリストを準備) OPEN db_cursor; -- 最初のデータベース名をカーソルから取得 FETCH NEXT FROM db_cursor INTO @DBName; -- カーソルで取得したすべてのデータベースに対して繰り返し処理を実行 WHILE @@FETCH_STATUS = 0 BEGIN -- 圧縮コマンドを格納する変数を宣言 DECLARE @SQL NVARCHAR(MAX); -- DBCC SHRINKDATABASE コマンドを動的SQLとして構築 -- ここでは、10% の空き領域を残して圧縮する設定(必要に応じて変更可能) SET @SQL = 'DBCC SHRINKDATABASE ("' + @DBName + '", 10);'; -- 実行されるコマンドを確認するために出力(オプション) PRINT @SQL; -- 実際の圧縮コマンドが何か確認したい場合に役立つ -- 構築した SQL コマンドを実行 EXEC sp_executesql @SQL; -- 次のデータベース名をカーソルから取得 FETCH NEXT FROM db_cursor INTO @DBName; END; -- カーソル操作が完了したら閉じる CLOSE db_cursor; -- カーソルのメモリを解放 DEALLOCATE db_cursor; GO |
もし一括でDBを削除する場合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
USE master; -- master データベースに接続 GO DECLARE @DatabaseName NVARCHAR(128); DECLARE @SQL NVARCHAR(MAX); -- カーソルを宣言して、削除対象のデータベース名を取得 DECLARE db_cursor CURSOR FOR SELECT name FROM sys.databases WHERE name NOT IN ('master', 'model', 'msdb', 'tempdb') -- システムデータベースを除外 -- カーソルを開く OPEN db_cursor; -- 最初のデータベース名を取得 FETCH NEXT FROM db_cursor INTO @DatabaseName; -- すべてのデータベースに対して繰り返し処理 WHILE @@FETCH_STATUS = 0 BEGIN -- データベースをシングルユーザーモードに設定し、接続を強制切断 SET @SQL = 'ALTER DATABASE "' + @DatabaseName + '" SET SINGLE_USER WITH ROLLBACK IMMEDIATE;'; PRINT @SQL; -- 実行されるコマンドを確認(オプション) EXEC sp_executesql @SQL; -- データベースを削除 SET @SQL = 'DROP DATABASE "' + @DatabaseName + '";'; PRINT @SQL; -- 実行されるコマンドを確認(オプション) EXEC sp_executesql @SQL; -- 次のデータベース名を取得 FETCH NEXT FROM db_cursor INTO @DatabaseName; END; -- カーソルを閉じて解放 CLOSE db_cursor; DEALLOCATE db_cursor; GO |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
function deleteTriggers() { writeLog("trigger削除"); let triggers = ScriptApp.getProjectTriggers(); for(let i = 0; i < triggers.length; i++){ ScriptApp.deleteTrigger(triggers[i]); } } function writeLog(x){ let ss = SpreadsheetApp.getActiveSheet(); let range = ss.getRange("B1:C100"); let values = range.getValues(); for(let r = 0; r < values.length; r++){ if (values[r][0] == ""){ range.getCell(r+1,1).setValue(x); range.getCell(r+1,2).setValue( new Date().toTimeString().slice(0, 8) ); break; } } } function main() { // クリア let ss = SpreadsheetApp.getActiveSheet(); ss.clear(); ss.getRange("A1").setValue(0); writeLog("main開始"); deleteTriggers(); ScriptApp.newTrigger('trigger').timeBased().after(1000).create(); writeLog("trigger登録"); writeLog("main終了"); } function trigger() { writeLog("trigger開始"); let startTime = new Date().getTime(); while (SpreadsheetApp.getActiveSheet().getRange("A1").getValue() <= 20) { Utilities.sleep(1000); // 1秒 SpreadsheetApp.getActiveSheet().getRange("A1").setValue( SpreadsheetApp.getActiveSheet().getRange("A1").getValue() + 1 ); writeLog(SpreadsheetApp.getActiveSheet().getRange("A1").getValue()); let elapsedTime = (new Date().getTime() - startTime) / 1000; if (elapsedTime > 5){ deleteTriggers(); ScriptApp.newTrigger('trigger').timeBased().after(1000).create(); writeLog("trigger登録"); return; } } deleteTriggers(); writeLog("trigger終了"); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
sudo systemctl stop mattermost cd /opt/ mv mattermost 20250102_mattermost wget https://releases.mattermost.com/10.3.1/mattermost-team-10.3.1-linux-amd64.tar.gz tar -xvzf mattermost*.gz cp /opt/20250102_mattermost/config/config.json /opt/mattermost/config/config.json cp -r /opt/20250102_mattermost/data /opt/mattermost/ sudo chown -R mattermost:mattermost mattermost sudo systemctl start mattermost sudo systemctl status mattermost |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 |
ProcessSetPriority "High" SetMouseDelay -1 SendMode "Input" A_MaxHotkeysPerInterval := 200 InstallKeybdHook #UseHook CoordMode "Mouse", "Window" ;-------------------------------------------------------- ; "C:\Program Files (x86)\Google\Google Japanese Input\GoogleIMEJaTool.exe" -mode=config_dialog ; Hiragana>IMEを有効化 ; Muhenkan>IMEを無効化 ; エントリーを削除 ; Henkan ; Shift Henkan ; Shift Muhenkan ;-------------------------------------------------------- >+4::Send "{Enter}" >+5::Send "{Tab}" >+7::Send "{Home}" >+8::Send "{End}" >+q::Send "{&}" >+w::Send "{'}" >+a::Send "{#}" >+s::Send "{~}" >+z::Send "{^}" >+x::Send "{$}" >+u::Send "{|}" >+i::Send "{\}" >+o::Send "{@}" >+p::Send "{`%}" >+n::Send "{(}" >+m::Send "{)}" >+h::Send "{Left}" >+j::Send "{Down}" >+k::Send "{Up}" >+l::Send "{Right}" >+Backspace::Send "{Delete}" ;-------------------------------------------------------- ~sc07B:: { If (A_PriorHotkey = A_ThisHotkey && 500 > A_TimeSincePriorHotkey) { Send "{sc070}" } } sc07B & 1::Send "{F12}" sc07B & 2::Send "{F2}" sc07B & q::Send "{Esc}" sc07B & w::Send "{LWin}" sc07B & e::Send "#{r}" ;-------------------------------------------------------- ~^c:: { If (A_PriorHotkey = A_ThisHotkey && 500 > A_TimeSincePriorHotkey) { Send "{End}+{Home}^c" } } ;-------------------------------------------------------- sc079::Ctrl ; SpreadSheet対策 sc079 & 3::Send "^{Home}" ; Ctrl+Home sc079 & 4::Send "^{End}" ; Ctrl+End sc079 & q::Send "!{Left}" ; 戻る sc079 & w::Send "!{Right}" ; 進む sc079 & e::Send "^w" ; 閉じる sc079 & r::Send "!+r" ; Alt+Shift+R / TabsToWindow sc079 & Ctrl::AltTab Ctrl & sc079::Send "#{Tab}" sc079 & a::Send "^+{Tab}" ; 前のタブ sc079 & s::Send "^{Tab}" ; 次のタブ sc079 & d::Send "^+t" ; 再び開く sc079 & f::Send "^{F5}" ; 更新 sc079 & g::Send "!+g" ; Alt+Shift+G / MergeTabs sc079 & z::Send "!+z" ; Alt+Shift+Z / CloseRightTabs mm := [] Loop MonitorGetCount() { if MonitorGetWorkArea(A_Index,&L,&T,&R,&B) { mm.Push({L:L,T:T,R:R,B:B}) } } sc079 & c:: { global mm static keyDownCount1 If (A_PriorHotkey = A_ThisHotkey && 500 > A_TimeSincePriorHotkey) { keyDownCount1 += 1 If (keyDownCount1 > MonitorGetCount() - 1) { keyDownCount1 := 0 } } Else { keyDownCount1 := 0 } x := keyDownCount1 + 1 this_id := WinGetID("A") If (WinGetMinMax(this_id) = 1) { WinRestore(this_id) } WinMove((mm[x].L),(mm[x].T),,,this_id) ; 拡大縮小対策 WinMove(,,(mm[x].R - mm[x].L),(mm[x].B - mm[x].T),this_id) } sc079 & v:: { static keyDownCount2 If (A_PriorHotkey = A_ThisHotkey && 500 > A_TimeSincePriorHotkey) { keyDownCount2 += 1 If (keyDownCount2 > 1) { keyDownCount2 := 0 } } Else { keyDownCount2 := 0 } this_id := WinGetID("A") If (keyDownCount2 = 0) { If (WinGetMinMax(this_id) = 1) { WinRestore(this_id) WinMove(,,1100,750,this_id) } WinGetPos(&x,&y,,,this_id) if (x = -SysGet(16) or x = 0) { WinMove(,,1100,750,this_id) } WinGetPos(,,&w,&h,this_id) MouseMove(w/2, 9) ; 150% } Else If (keyDownCount2 = 1) { WinGetPos(,,&w,&h,this_id) MouseMove(w-5, h-5) } } ;-------------------------------------------------------- sc079 & WheelUp::Send "{PgUp}" sc079 & WheelDown::Send "{PgDn}" +WheelUp::WheelLeft +WheelDown::WheelRight MButton:: { MouseGetPos(&mx,&my,&id) if InStr(WinGetClass(id), "Chrome") { MouseClick "R" } else { MouseClick "M" } } RButton:: { MouseGetPos(&mx,&my,&id) if InStr(WinGetClass(id), "Chrome") { MouseClick "M" } else { MouseClick "R" } } ;-------------------------------------------------------- #HotIf WinActive("ahk_exe chrome.exe") +Space:: { this_id := WinGetID("A") imestate := DllCall("user32.dll\SendMessageW", "Ptr", DllCall("imm32.dll\ImmGetDefaultIMEWnd", "Ptr", this_id), "Ptr", 0x0283, "Ptr", 0x0005, "Ptr", 0) if (imestate = 1) { Send "{sc07B}" } Send "+{Space}" } ;-------------------------------------------------------- #HotIf WinActive("ahk_exe excel.exe") sc079 & a::Send "^{PgUp}" ; 前のタブ sc079 & s::Send "^{PgDn}" ; 次のタブ sc079 & f::Send "^+{L}" ; オートフィルタ +Space:: { Send "{sc07B}" Send "+{Space}" } MButton:: { MouseClick "L" Send "{F4}" } ;-------------------------------------------------------- #HotIf WinActive("ahk_exe mpc-be64.exe") sc079 & a::Send "^{PgUp}" ; 前のタブ sc079 & s::Send "^{PgDn}" ; 次のタブ w::Send "{Numpad1 10}" e::Send "^{Numpad2 10}" r::Send "{Numpad9 10}" s::Send "^{Numpad6 10}" d::Send "^{Numpad8 10}" f::Send "^{Numpad4 10}" c::Send "{Numpad5}" v::Send "!{Numpad3}" #HotIf |
複合機が受信したFAXをMattermostに転送してみる実験。
複合機が送信したPDF付きメールをGmailで受信し、GASでGoogleDriveからMattermostに投稿してみたが、PDFがサムネイル表示されなかった。ファイル名が自動採番なのでサムネイル表示は必須であり、そこでPDFからPNGを生成するCloud Runを作成することにした。
Cloud Runを利用し、PDFはGoogleDriveに保存したままPNGをMattermostに投稿、PNGのリンク先にGoogleDriveのPDFを指定することにした。
●Cloud Run
・プロジェクトを作成。
※ここでは、PDF2PNGというプロジェクトを作成した
・Cloud Runというプロダクトを選択し、サービスを作成する
・請求アカウントを作成(請求アカウントがないとサービスが作成できない)
※ここでは、個人クレジットカードというアカウントを作成した
・インラインエディタで関数を作成する
・サービス名:pdf2pngとした
・リージョン:そのまま
・ランタイム:Node.js 20
・承認:未認証の呼び出しを許可
・課金:リクエストベース
・インスタンスの最小数:0
・Ingress:すべて
※Cloud Build APIを有効にする必要があるとでるのでここで有効
・エディタが開くのでindex.js、package.jsonを記入
ベースイメージ: Node.js 20 (Ubuntu 22)を選択
・関数のエントリポイントを空白のまま
※ここを空白にしないとエラーになる
・保存して再デプロイを実施
・URLが表示されるので、変換できるかcurlでテスト
curl -X POST -F “file=@./xxx.pdf” https://xxx.run.app -o output.png
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
const express = require("express"); const multer = require("multer"); const { pdfToPng } = require("pdf-to-png-converter"); const app = express(); const upload = multer({ storage: multer.memoryStorage() }); app.post("/convert", upload.single("file"), async (req, res) => { if (!req.file) return; const pdfBuffer = req.file.buffer; const pngPages = await pdfToPng(pdfBuffer, { viewportScale: 1.0, pages: [1], }); res.setHeader("Content-Type", "image/png"); res.send(pngPages[0].content); }); app.listen(8080, '0.0.0.0'); |
package.json
1 2 3 4 5 6 7 8 9 10 11 12 13 |
{ "name": "pdf-to-png", "version": "1.0.0", "main": "index.js", "scripts": { "start": "node index.js" }, "dependencies": { "express": "^4.18.2", "multer": "^1.4.5-lts.1", "pdf-to-png-converter": "^3.6.3" } } |
GAS(時間トリガーで実行)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
let mattermostUrl = "https://xxx"; let token = "xxx"; let channelId = "xxx"; let folderId = "xxx"; let folder = DriveApp.getFolderById(folderId); let pdf2png = "https://xxx.run.app/convert"; function Main() { let threads = GmailApp.search("is:unread label:inbox subject:Attached Image"); for (let i = 0; i < threads.length; i++) { let messages = threads[i].getMessages(); for (let j = 0; j < messages.length; j++) { let message = messages[j]; if (message.isUnread()){ let attachments = message.getAttachments(); if (attachments.length > 0){ let attachment = attachments[0]; if (attachment.getContentType() == "application/pdf") { CreatePng(attachment); } } message.markRead(); } } } } function CreatePng(attachment){ let file = folder.createFile(attachment); file.setName(attachment.getName()); let fileUrl = file.getUrl(); let formData = { file: file.getBlob() }; const options = { method: "post", payload: formData, muteHttpExceptions: true }; let response = UrlFetchApp.fetch(pdf2png, options); let responseCode = response.getResponseCode(); if (responseCode == 200){ let png = response.getBlob(); PngUploadToMattermost(png,fileUrl) } } function PngUploadToMattermost(attachment,fileUrl) { let formData = { files: attachment } let endpoint = `${mattermostUrl}/api/v4/files?channel_id=${channelId}`; let options = { method: "post", headers: { Authorization: `Bearer ${token}`, }, payload: formData, muteHttpExceptions: true, }; let res = UrlFetchApp.fetch(endpoint, options); PostToMattermost(res,fileUrl); } function PostToMattermost(res,fileUrl){ let info = JSON.parse(res.getContentText()); let fileId = info.file_infos[0].id; let endpoint = `${mattermostUrl}/api/v4/posts`; let payload = { channel_id: channelId, message: `[](${fileUrl})` } let options = { method: "post", headers: { Authorization: `Bearer ${token}`, }, payload: JSON.stringify(payload), muteHttpExceptions: true, }; UrlFetchApp.fetch(endpoint, options); } |
※追記
Markdownの画像リンクはモバイルアプリで表示されないという不具合があるようだったのでfile_idsに変更。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
function PostToMattermost(res,fileUrl){ let info = JSON.parse(res.getContentText()); let fileId = info.file_infos[0].id; let endpoint = `${mattermostUrl}/api/v4/posts`; let payload = { channel_id: channelId, file_ids: [fileId], message: "--- \n" + `[PDFを開く](${fileUrl})`, } let options = { method: "post", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, payload: JSON.stringify(payload), muteHttpExceptions: true, }; UrlFetchApp.fetch(endpoint, options); } |