Compare commits

..

3 Commits

Author SHA1 Message Date
Kenichiro NOGI
06c28e8b5a oldCode: 旧LINEWORKS/Pleasanter連携コード一式を追加
express/LINEWORKS_Pleasanter/LINEWORKS_sendMessage、いずれも過去の
Express実装。Gitea移行に伴い保管。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 10:42:21 +09:00
Kenichiro NOGI
5abf932fa6 docs(org-master-sync): n8n production移行の設計メモを追加
模倣(Mock)Users/Depts/Groupsテーブルの設計意図と実態乖離、
staging/production分離、config値修正、①③④④-1④-2④-3の
標準API化(models/groups/{id}/update等の実機検証結果込み)を記録。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 10:40:17 +09:00
Kenichiro NOGI
8e8bbcde6e 健康診断管理LINEWORKSアンケートn8n化の設計書を追加
Express実装(ケアセブン向け)をn8nへ作り直すにあたり、健康診断管理
プロジェクト固有の要件(508971へ統合、既存Bot/Usersマスタ流用、
n8n Data Tableでのセッション管理)を反映した設計をbrainstormingで
まとめた。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 10:30:00 +09:00
37 changed files with 5553 additions and 0 deletions

View File

@ -0,0 +1,123 @@
# org-master-sync n8n production移行 設計メモ
2026-09-04〜進行中。org-syncのn8nワークフロー群をproduction本番プリザンター `nextoffice.next-hd.co.jp`)向けに構築する作業の設計意図・進捗記録。適宜追記していく。
## 1. 背景: 「模倣Mockテーブル」の設計意図
n8nの`org-sync`系ワークフロー(①③④④-1④-2④-3は、Pleasanter標準のUsers/Depts/Groups API`/api/users/get`等ではなく、staging環境に用意した「模倣テーブル」SiteId 504737=ユーザー, 504736=組織, 504738=グループ、いずれも`ReferenceType: Results`の通常テーブル)を参照して構築されていた。
**意図本人確認、2026-09-05**: 開発当時は本番データを直接いじれなかったため、模倣テーブルでフロー構築を先に進め、「宛先SiteId/BASE_URLを変更するだけで本番化できる」ことを狙った設計だった。
**判明した実態2026-09-05調査**: この狙いは実現できていなかった。
- 模倣テーブルはPleasanterのitems/export形式表示名キーのJSON/CSV、例: `ログインID`, `組織`を返すが、本物の標準API`/api/users/get`, `/api/depts/get`, `/api/groups/get`)はシステムフィールド名(`UserId`/`LoginId`/`DeptId`/`GroupId`等Offset/TotalCountページング必須という全く別の形式。「宛先を変えるだけ」では動かず、レスポンス変換層の追加が必要だった
- LWグループマッピングテーブルSiteId 504411、66件: 組織48・役職8・職級6・利用権限タイプ4の「グループID(」列は模倣Groupsテーブル内の独自連番1〜135+で、本物のPleasanter GroupsGroupId実在範囲5〜69、全59件とは無関係。名前での突合でも一致0件本物のGroupsには1件も対応物が無い
## 2. n8nワークフロー構成2026-09-04時点で判明した正式版一覧
Proto系`org-master-sync prototype`フォルダに格納済み、検証用として維持、変更しない)とは別に、正式版として以下が存在:
| ワークフロー | ID |
|---|---|
| org-sync: ① LINEWORKSマスタ→プリザンターマスタ情報更新 | pJBj07V5p4Av8f6E |
| org-sync: ② バックアップ | HuKpZQEQeU4iUCUf |
| org-sync: ③ マージマスターメイン処理 | ScoUpIshCsdO2KdI |
| org-sync: ④ プリザンターマスタ更新(④-0〜④-3の統合版 | 82y3S515PbEBLSDv |
| org-sync: ④-0 状態判定 | UQ7vtaajy9O93jhW |
| org-sync: ④-1 新規ユーザー作成 | 6NwZXgXY2eK1Wt4g |
| org-sync: ④-2 ユーザー更新・退職処理 | I9NimeonT8rM51KW |
| org-sync: ④-3 グループ更新 | QPt4JRHOgSuN0nnS |
## 3. staging/production分離2026-09-04実施
上記8本を複製し、`org-sync: staging: ...`既存の8本をリネームと`org-sync: production: ...`新規複製8本に分離。
- production版はdataTable取得ードのconfigKeyを`_TEST`→`_PROD`へ全置換(④-0〜④-3内の「設定値統合」Codeードにハードコードされてた`_TEST`参照も含む)
- production版のWebhookパスは全部`-prod`サフィックスで一意化(例: `org-sync-01-lw-master-update-prod`
- production版8本は全部`active: false`(未有効化)で作成。**稼働テストは今回のスコープ外、明示許可があるまで有効化しない**
- フォルダ構成: `org-master-sync`production版8本/ `org-master-sync staging`staging版8本/ `org-master-sync prototype`Proto系旧版15本/ `LINEWORKS`通知系2本
## 4. `workflow_config_values`n8n Data TableのPROD値修正
旧`_PROD`値(`PLEASANTER_MASTER_SITE_ID_PROD=502552`等はいずれも404で存在しない廃止済みSiteIdだった。TESTが既に`504406〜504412`系列を使っていたのに対応させ、以下へ更新2026-09-04、UI手動編集で完了:
| キー | 更新後の値 |
|---|---|
| PLEASANTER_MASTER_SITE_ID_PROD | 504412社員・組織マスタ管理テーブル |
| PLEASANTER_POSITION_MASTER_SITE_ID_PROD | 504408LINEWORKS:役職マスタ) |
| PLEASANTER_LEVEL_MASTER_SITE_ID_PROD | 504410LINEWORKS:職級マスタ) |
| PLEASANTER_USERTYPE_MASTER_SITE_ID_PROD | 504409LINEWORKS:利用権限マスタ) |
| PLEASANTER_BACKUP_SITE_ID_PROD | 504407社員・組織マスタ管理(副・バックアップ用) |
| PLEASANTER_LW_GROUP_MAP_SITE_ID_PROD | 504411LWグループマッピング、新規追加 |
n8n Data Table Public APIは行のupdate/delete専用エンドポイントが存在しないPATCH/PUT/DELETEいずれも404か405、POSTのみ実装。UI側の値セルはダブルクリックで編集・削除可能と判明削除もUIから実施できた
## 5. 模倣テーブル→本物API移行の対応状況
| ワークフロー | 参照する模倣テーブル | 対応状況 |
|---|---|---|
| ③ マージマスターメイン処理production版のみ | Users(504737), Depts(504736) | **完了(2026-09-05)**。下記5-1参照 |
| ① LINEWORKSマスタ→プリザンターマスタ情報更新production版のみ | Groups(504738) | **完了(2026-09-05)**。下記5-2参照 |
| ④ プリザンターマスタ更新(統合版) | Users, Depts, Groups全部 | **完了(2026-09-05)**。下記5-3参照 |
| ④-1 新規ユーザー作成 | Depts, Groups, Users(create先も504737) | **完了(2026-09-05)**。下記5-4参照 |
| ④-2 ユーザー更新・退職処理 | Depts, Users, Groups | **完了(2026-09-05)**。下記5-4参照 |
| ④-3 グループ更新 | Groups | **完了(2026-09-05)**。下記5-4参照 |
これで①②③④④-0④-1④-2④-3、production版8本すべての模倣テーブル参照が解消された。
### 5-1. ③の対応内容production版のみ、staging版は変更していない
- `プリザンターUsers取得`ノード: `POST items/504737/export``POST api/users/get``View:{ApiGetMailAddresses:true}`付き、Offset+TotalCountページング設定済み
- `プリザンターDepts取得`ノード: `POST items/504736/export``POST api/depts/get`(同上ページング設定)
- 「マージ計算」Codeード: 標準APIの生データ`UserId`/`LoginId`/`DeptId`等)を、旧・模倣テーブルと同じキー名(`ユーザID`/`ログインID`/`組織`等)の中間形式へ変換する処理を先頭に追加。`組織`は`DeptId`から`プリザンターDepts取得`の結果で逆引き(`deptIdToName`マップ)。これにより後続のマージ判定ロジック(`pleasanterUserToFields`等)は無改造で流用できている
- `SecretKey`・`Manager`は本番データ上使用者ゼロを確認済み(該当者が出ても空文字扱いになるだけで実害小)
### 5-2. ①の対応内容production版のみ、2026-09-05完了
当初は「LWグループマッピング66件が本物のGroupsに1件も対応物を持たないため本物化には書込み移行が前提になり、構築のみでは完結しない」と判断していたが、①のワークフロー自体が元々「本物のGroupsに存在しないグループを自動で新規作成する」設計差分計算→groupsToCreate→グループ作成実行だったため、**ワークフロー定義の書き換え自体は構築の範囲で完結できる**と整理し直した(実際の新規作成・無効化という書込みは、このワークフローを実際に実行したときに初めて発生する=稼働テスト禁止と同じ制約の範囲内)。
- `模倣Groups取得`ノード → `Groups取得`へ改名、`items/504738/export` → `api/groups/get`Offset+TotalCountページング設定込み
- `グループ作成実行`ノード: `items/504738/create``ClassA`=独自連番, `ClassB`=グループ名) → `api/groups/create``GroupName`のみ送信。Pleasanterが自動採番するGroupIdを使う設計に変更、独自連番の`nextGroupId`計算は廃止)
- `グループ無効化更新実行`ノード: `items/{itemId}/update``CheckA``api/groups/{groupId}/update`body`{Disabled}`のみ、GroupIdはURLパス側。**実機検証済み2026-09-05、本人許可の下stagingのGroupId=40「テスト用グループ」で確認**。当初`api/groups/update`bodyにGroupIdで試したところ404nginxレベルで存在しないパス、`items`系と同じ「IDをURLパスに含める」パターンが正解だった
- `作成結果ペアリング`ノード: 独自連番をそのまま通していた処理から、`グループ作成実行`のレスポンス(`Id`)を実際に取得して使うよう変更
- `差分計算`ノード: 標準APIから取得した本物のGroups一覧`GroupName`→`GroupId`のマップを使い、LWグループマッピング66件の各行について「グループ名が本物のGroupsに実在するか」を追加でチェック。実在しなければ`groupsToCreate`に回して作り直し、実在するがグループID列がズレていれば`groupsToFixId`として修正対象に追加。これにより66件の移行問題もワークフロー実行時に自動的に解消される設計にした
- `新規マッピングCSV組立`ノード: `groupsToFixId`も統合。ここでCSVの列を共通ヘッダーにする際、fixId行が持たない列分類・sourceKey等を空文字で送ると既存値を上書き消去してしまう危険があった[[org-master-sync-n8n-mock-tables]]、8月の実データ事故と同種のパターンため、`groupsToFixId`側にも`category`/`sourceKey`/`groupName`の元の値を保持させて対応
**未検証・要注意点**: `グループ作成実行`のレスポンス形式(`Id`をトップレベルで返す前提、`createGroup`関数の実装から類推、SplitInBatches`グループ作成Loop`内での入出力アイテムの対応順序、いずれも稼働テストなしでは確証が持てない。①のproduction版を初めて実行する際は、必ず少数件1件に絞ってから全体を回すこと。
2026-09-05追記`グループ更新`のURL`/api/groups/{groupId}/update`は本人許可の下stagingのGroupId=40「テスト用グループ」で実機検証済み。詳細は8-2節参照。
### 5-3. ④統合版の対応内容production版のみ、2026-09-05完了
`NodeSrv/apps/org-master-sync/sample/sampleアカウント作成スクリプト.js`本番プリザンターのフィールドスクリプトとして稼働実績ありから、Users/Groups標準APIの正確な使用例が得られたため、これをベースに実装した。
- `プリザンターUsers取得`/`プリザンターGroups取得`/`プリザンターDepts取得`: ①③と同様に標準API化`/api/users/get`, `/api/groups/get`, `/api/depts/get`、Pagination込み
- `users_create実行`: `items/504737/create``/api/users/create`。サンプルスクリプトの`createPleasanterUser`関数のbody構造をそのまま採用`LoginId`/`Name`/`Password`/`DeptId`/`DeptCode`/`MailAddresses`等、フラットなフィールド名でHashネスト不要
- `users_update実行`: `items/{itemId}/update``/api/users/{userId}/update`。**このURL自体はサンプルスクリプトに例が無く実機未検証**。Groups update`/api/groups/{groupId}/update`と同じ「IDをURLパスに含める」パターンからの類推
- `グループ取得`(メンバー判定用): `items/504738/export`全件取得して対象1件をfind`/api/groups/{groupId}/get`単体取得、対象1件だけ取得するよう効率化
- `グループ更新実行`: `items/{itemId}/update``/api/groups/{groupId}/update`。サンプルスクリプトの`addUserGroup`関数を参考に、`GroupName`/`Body`/`GroupMembers`をまとめて送る形に変更(`Disabled`には触れない)
- **重要な発見**: 模倣Groupsの「グループメンバー(種別,キー,管理者フラグ 1行1メンバー)」というテキスト行形式は、本物の標準API`GroupMembers`配列の各要素(`'User,{userId},False'`という文字列)と**完全に同じフォーマット**だった(サンプルスクリプトで確認)。改行区切りテキスト⇔配列という違いだけで、行内容自体の変換は不要だった
- `対象判定`ロジックの変更点:
- 新規ユーザー作成時のメールアドレスを、旧実装の`社員マスタのメールアドレスのローカル部 + '@nexthd.jp'`固定ドメインから、**LINEWORKS側のメールアドレスをそのまま使う**方式に変更本人指示、2026-09-05。ドメイン統一済みのため固定ドメイン付け替えは不要と判断
- `DeptId`はUsers create時に必須のため、Depts標準APIの結果から`DeptName→DeptId`マップを新設(旧実装は`DeptCode`しか計算していなかった)
- 退職処理・組織変更の`userUpdateTasks.fields`を、模倣テーブルの内部列名(`無効`/`ロック`/`組織コード`等から標準APIのフィールド名`Disabled`/`Lockout`/`DeptCode`/`DeptId`/`MailAddresses`)へ変更
**未検証点**: `/api/users/{userId}/update`の実在ダミーユーザー作成テストは本人許可が得られていないため確認できていない。④のproduction版を実行する前に、退職処理か組織変更いずれかの経路で必ず1件だけ実行して確認すること。
### 5-4. ④-1・④-2・④-3の対応内容production版のみ、2026-09-05完了
④(統合版)を機能別に分割した部分抜粋版のため、同じ修正パターンを適用した。
- ④-1新規ユーザー作成: ④と同じくUsers/Depts/Groups取得を標準API化、`users_create実行`を`/api/users/create`化。新規ユーザーの初期グループ所属反映(`書き戻しグループ反映組立`もGroupMembers配列ベースに変更
- ④-2ユーザー更新・退職処理: 同様にDepts/Users/Groups取得を標準API化。`対象展開`(退職処理・組織変更)を`/api/users/{userId}/update`化。模倣Usersテーブル固有の「itemId」概念`userIdToItemId`によるUserId→模倣テーブル内部ID変換は不要になった標準APIはUserIdそのものが更新のキーになるため
- ④-3グループ更新: Groups取得を標準API化、グループメンバー反映を`/api/groups/{groupId}/update`化
**重要な発見(バグ)**: ④(統合版)の対象判定コードに`const NEXT_GROUP_ALL_ID = '4'`という定義があったが、模倣Groupsテーブルにも本物のGroupsにも`GroupId=4`は存在しない誤った値だった(未使用の変数だったため実害はなかった)。④-1/④-2/④-3では同じ定数が正しく`'27'`(実在する「ネクストグループ全社員」グループ)になっていたため、そちらを正として全ワークフローで統一した。
**検証結果2026-09-05追記**: `/api/users/{userId}/update`は本人許可の下stagingのUserId=705「野木テストユーザ」で実機検証済み`Disabled:false`送信、200 OK「"野木テストユーザ"を更新しました。」。Groups/Depts系と同じ「IDをURLパスに含める」パターンで、④④④-1④-2の実装通りで問題なかった。①③④④-1④-2④-3、production版8本すべて未検証エンドポイントなしの状態になった。
## 6. 今回の制約2026-09-05確認
- n8nワークフロー定義の編集PUT/POST /workflows、Data Table操作含むは確認不要で全自動
- 本番プリザンター・LINEWORKSへの**読み取り**GET系API、`items/{id}/export`、`items/{id}/get`含む)は許可
- 本番プリザンターへの**書き込み**create/update/import/bulkupsert等は不可。特に①のグループ作成、③のマージ結果書き込みは明示的に禁止
- ワークフローの稼働テストWebhookを実際に叩く、Manual Trigger実行は不可

View File

@ -0,0 +1,120 @@
# 健康診断管理×LINEWORKSアンケート n8n化 設計書
- 作成日: 2026-09-05
- 対象: プリザンター「健康診断管理」プロジェクトSiteId 508971へLINEWORKS Bot経由アンケート機能を実装
- 位置付け: 既存Express実装`OldCode/express/modules/lineworksSurvey.js`、仕様書`Pleasanter/LINEWORKSアンケート管理/docs/lineworks-survey-scheme.md`と同等機能をn8nで新規構築。Expressの流用・移植ではなく作り直し
## 1. 背景・既存Express実装との違い
Express版ケアセブンプロジェクト向けは以下の構成だった。
- 「アンケート実行管理」を独立サイト484186として持ち、対象テーブル例: 車両点検報告484182へ回答を新規レコードcreateする
- Bot・対象者マスタも専用サイト484184/484181を新設
- セッション状態はExpressプロセスのメモリ`Map`)で保持
今回の健康診断管理版は要件が異なるため、以下の方針で作り直す。
- **アンケート実行管理を独立サイトにせず、508971自体に統合**Process列追加。回答は新規レコードcreateではなく、発行元の508971レコード自体へupdateで書き戻す
- **Botマスタは新設せず、既存の「LINEWORKSアンケート管理」プロジェクトのSiteId 484184をそのまま参照**
- **対象者マスタは新設せず、プリザンター標準Usersのメールアドレスをそのまま LINEWORKS の宛先IDuserIdとして使う**LINEWORKS IDメールアドレスという前提。484181のような変換用マスタは不要
- **サーバー実装はExpressでなくn8n**。セッション状態はn8nプロセスのメモリではなく、n8n Data Table新規テーブル `survey_sessions`)で保持する
n8n環境自体の詳細URL・API・既存ワークフロー・過去の罠は`NodeSrv/apps/n8n/docs/n8n-guide.md`参照。以下の設計はこのガイドの制約コンテナメモリ768MB、Data Table操作の罠、Schedule Trigger運用方針等を踏まえている。
## 2. 全体アーキテクチャ
```
プリザンター(508971 健康診断管理データ本体)
└ Process「アンケート発行」ボタンflowIdごとに1つ
│ クライアントスクリプト fetch
n8n: W1「健診アンケート開始」(Webhook)
└ resultId取得 → ClassC(Users参照列)からPleasanterUserId解決
└ POST api/users/getView:{ApiGetMailAddresses:true})でメールアドレス取得
→ LINEWORKS userIdとしてそのまま使用
└ 508971自身のColumnsからflowId該当設問を抽出
└ n8n Data Table「survey_sessions」へセッション行Insert
└ 484184(LINEWORKS Bot)から該当BotId取得 → LINEWORKS Bot APIで第1問送信
LINEWORKS トークBot⇔対象者
│ 回答
n8n: W2「健診アンケート回答受信」(Webhook, 署名検証)
└ targetId(=メール)でsurvey_sessions検索 → 型検証 → 回答蓄積
└ 未完了 → 次設問送信 / セッション更新
└ 完了 → 508971へupdateHashネスト→ アンケート進捗列更新 → セッション削除
n8n: W3「健診アンケートタイムアウト監視」(Schedule Trigger、数分毎)
└ survey_sessions走査 → 期限切れセッションに終了通知 → 削除
```
## 3. プリザンター側508971変更点
| 追加項目 | 内容 |
|---|---|
| 設問対象列 | アンケートで使う列の`ExtendedControlCss`に`survey survey-<flowId>`トークンを付与。1列が複数flowに属してもよいスペース区切りで複数トークン |
| アンケート進捗列(新規、列名は実装時に採番) | アンケートの発行・回答状況を管理する専用列。既存業務Status100申込中〜910対象外とは別軸で持つ |
| Process「アンケート発行」 | flowIdごとに1つずつ追加。`OnClick`でn8n WebhookのW1へPOST`resultId`・`flowId`を送信) |
| ClassCUsers参照、既存 | そのまま流用。ここからPleasanterUserIdを取得し、n8n側でメールアドレスを解決する |
新規アンケートflowIdを追加する際の運用手順:
1. 該当列の`ExtendedControlCss`に`survey-<新flowId>`トークンを追記(既存列流用 or 新規列追加はflow内容次第で都度判断
2. 508971にProcess「◯◯アンケート発行」を追加、`flowId`を固定値でn8n Webhookへ渡すよう設定
3. n8n側の設問抽出ロジックは`ExtendedControlCss`のトークンでflowIdを判定するため、n8nワークフロー自体の改修は不要列追加・Process追加のみで新flowを増やせる設計
アンケート進捗列の具体的な値・遷移業務Statusとの整合を取るか等は、実際のアンケートフロー内容を設計する際に個別に確定する。本設計書では「既存業務Statusとは独立した列で管理する」という方針のみを定める。
## 4. n8n側詳細
### Data Table「survey_sessions」新規作成
| 列 | 内容 |
|---|---|
| sessionId | UUID |
| resultId | 508971のResultId |
| flowId | アンケート種別識別子 |
| botId | 484184から取得したBotId |
| targetEmail | 対象者メールアドレスLINEWORKS userId |
| currentIndex | 現在の設問番号 |
| answersJson | ここまでの回答JSON文字列 |
| expiresAt | 現在設問のタイムアウト時刻 |
| startedAt | 開始日時(無応答タイムアウト判定用) |
| firstAnswerAt | 初回応答有無判定用 |
n8n-guide.md 7-1Clear出力の握り潰し・7-2複数行が後続へそのまま渡ると行数分繰り返し実行されるの罠を踏まえ、1セッション=1行の読み書きに限定し、複数セッションを一括で展開・集約する処理は入れない。
### ワークフロー構成
- **W1 開始**: Webhookトリガー`X-API-Key`ヘッダー認証、508971のクライアントスクリプト専用
- **W2 回答受信**: WebhookトリガーLINEWORKS本体からの直接コールバック、`x-works-signature`をHMAC-SHA256検証。Express版`verifyWebhookSignature`と同等ロジック)
- **W3 タイムアウト監視**: Schedule Trigger数分間隔。n8n-guide.md 9章では「Schedule Trigger等の自動化は今回のスコープ外」としているが、**本機能ではセッションタイムアウト監視のために自動実行を解禁する**(今回の設計で追加する例外)
### 認証まわり
- LINEWORKS Bot APIメッセージ送信: 既存Credential「LINEWORKS Bot Private Key (v4)」(`Hw0qlEaGfLPnQWp1`)が流用できるか、実装時にBotIdとの対応関係を確認する
- 対象者メール解決: `POST api/users/get``View:{ApiGetMailAddresses:true}`、Offset+TotalCountページング。org-master-sync③で実績のある実装パターンをそのまま使う
## 5. 設問の型・検証ルール
Express版選択式数値自由記述の3種、日付は和暦・月日省略に対応と同等の検証ロジックをn8n上Codeードに再実装する。列名プレフィックスによる型判定`Class*`→選択式 or 短文、`Num*`→数値、`Date*`→日付、`Description*`→長文もExpress版を踏襲する。
## 6. エラーハンドリング・異常系
- キャンセルキーワード(取消/キャンセル/終了等)受信 → セッション削除508971のアンケート進捗列を「キャンセル」相当へ
- 必須設問での無回答・スキップ要求 → 再送(同じ設問を再送信)
- 型検証失敗(数値・日付フォーマット不正)→ エラーメッセージ+再送
- セッション未検出(期限切れ後の回答等)→ 「アンケートが開始されていません」を返信
- LINEWORKS送信失敗・508971 update失敗 → n8n Execution HistoryPostgres保存に残す。追加のログ実装はしない
## 7. 導入・検証方針
- n8n運用ルールn8n-guide.md 9章に従い、ワークフローの構築・編集自体は確認不要。**Webhook実行によるプリザンター書き込み・LINEWORKS送信を伴うテストは都度事前確認**
- 初回検証は1flow・1件のみで実施し、疎通確認後に複数flow展開へ進める
- 508971は本番の健診データそのものなので、検証は既存レコードを壊さない捨てレコードを用意して行う
## 8. 未確定事項今後のflow設計時に個別確定
- アンケートflowの具体的な設問内容・対象列・flowId命名は本設計書のスコープ外。flow追加のたびに3章の運用手順に従って個別設計する
- アンケート進捗列の具体的な値・遷移
- LINEWORKS Bot Private Key CredentialとBotIdの対応関係の実機確認

View File

@ -0,0 +1,836 @@
{
"name": "LINEWORKS_Pleasanter",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"body-parser": "^2.2.0",
"cors": "^2.8.5",
"express": "^5.1.0"
}
},
"node_modules/accepts": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
"integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
"license": "MIT",
"dependencies": {
"mime-types": "^3.0.0",
"negotiator": "^1.0.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/body-parser": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
"integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
"license": "MIT",
"dependencies": {
"bytes": "^3.1.2",
"content-type": "^1.0.5",
"debug": "^4.4.0",
"http-errors": "^2.0.0",
"iconv-lite": "^0.6.3",
"on-finished": "^2.4.1",
"qs": "^6.14.0",
"raw-body": "^3.0.0",
"type-is": "^2.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/call-bound": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"get-intrinsic": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/content-disposition": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
"integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==",
"license": "MIT",
"dependencies": {
"safe-buffer": "5.2.1"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/content-type": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie-signature": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
"integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
"license": "MIT",
"engines": {
"node": ">=6.6.0"
}
},
"node_modules/cors": {
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
"license": "MIT",
"dependencies": {
"object-assign": "^4",
"vary": "^1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/debug": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"license": "MIT"
},
"node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"license": "MIT"
},
"node_modules/etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/express": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
"integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
"license": "MIT",
"dependencies": {
"accepts": "^2.0.0",
"body-parser": "^2.2.0",
"content-disposition": "^1.0.0",
"content-type": "^1.0.5",
"cookie": "^0.7.1",
"cookie-signature": "^1.2.1",
"debug": "^4.4.0",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"etag": "^1.8.1",
"finalhandler": "^2.1.0",
"fresh": "^2.0.0",
"http-errors": "^2.0.0",
"merge-descriptors": "^2.0.0",
"mime-types": "^3.0.0",
"on-finished": "^2.4.1",
"once": "^1.4.0",
"parseurl": "^1.3.3",
"proxy-addr": "^2.0.7",
"qs": "^6.14.0",
"range-parser": "^1.2.1",
"router": "^2.2.0",
"send": "^1.1.0",
"serve-static": "^2.2.0",
"statuses": "^2.0.1",
"type-is": "^2.0.1",
"vary": "^1.1.2"
},
"engines": {
"node": ">= 18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/finalhandler": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
"integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
"license": "MIT",
"dependencies": {
"debug": "^4.4.0",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"on-finished": "^2.4.1",
"parseurl": "^1.3.3",
"statuses": "^2.0.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/fresh": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
"integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/http-errors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
"license": "MIT",
"dependencies": {
"depd": "2.0.0",
"inherits": "2.0.4",
"setprototypeof": "1.2.0",
"statuses": "2.0.1",
"toidentifier": "1.0.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
"license": "MIT",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/is-promise": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
"license": "MIT"
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/media-typer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
"integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/merge-descriptors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
"integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/mime-db": {
"version": "1.54.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
"integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
"license": "MIT",
"dependencies": {
"mime-db": "^1.54.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/negotiator": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/object-inspect": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
"license": "MIT",
"dependencies": {
"ee-first": "1.1.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"license": "ISC",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/path-to-regexp": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
"integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
"license": "MIT",
"engines": {
"node": ">=16"
}
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
"license": "MIT",
"dependencies": {
"forwarded": "0.2.0",
"ipaddr.js": "1.9.1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/qs": {
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/raw-body": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
"integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
"license": "MIT",
"dependencies": {
"bytes": "3.1.2",
"http-errors": "2.0.0",
"iconv-lite": "0.6.3",
"unpipe": "1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/router": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
"integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
"license": "MIT",
"dependencies": {
"debug": "^4.4.0",
"depd": "^2.0.0",
"is-promise": "^4.0.0",
"parseurl": "^1.3.3",
"path-to-regexp": "^8.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/send": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
"integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
"license": "MIT",
"dependencies": {
"debug": "^4.3.5",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"etag": "^1.8.1",
"fresh": "^2.0.0",
"http-errors": "^2.0.0",
"mime-types": "^3.0.1",
"ms": "^2.1.3",
"on-finished": "^2.4.1",
"range-parser": "^1.2.1",
"statuses": "^2.0.1"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/serve-static": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
"integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
"license": "MIT",
"dependencies": {
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"parseurl": "^1.3.3",
"send": "^1.2.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC"
},
"node_modules/side-channel": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3",
"side-channel-list": "^1.0.0",
"side-channel-map": "^1.0.1",
"side-channel-weakmap": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-list": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-map": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-weakmap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3",
"side-channel-map": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
"license": "MIT",
"engines": {
"node": ">=0.6"
}
},
"node_modules/type-is": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
"integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
"license": "MIT",
"dependencies": {
"content-type": "^1.0.5",
"media-typer": "^1.1.0",
"mime-types": "^3.0.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
}
}
}

View File

@ -0,0 +1,7 @@
{
"dependencies": {
"body-parser": "^2.2.0",
"cors": "^2.8.5",
"express": "^5.1.0"
}
}

View File

@ -0,0 +1,45 @@
const express = require('express');
const cors = require('cors');
const https = require('https');
const fs = require('fs');
const API_KEY = 'pgqhLFWbDFu4Byz#4afNYX2F6Fa1&$KPjved$8%sUdTQV52caip#EpIKxYUkdd4S'; // 任意のAPIキーを設定
const app = express();
app.use(cors({
origin: [
'https://neo999.next-hd.net',
'https://nextoffice.next-hd.co.jp',
], // 許可するオリジン
optionsSuccessStatus: 200
}));
// APIエンドポイント
app.post('/server', (req, res) => {
let rawBody = '';
req.on('data', (chunk) => {
rawBody += chunk; // リクエストデータをそのまま取得
});
req.on('end', () => {
// APIキーの検証
const apiKey = req.headers['x-api-key'];
if (!apiKey || apiKey !== API_KEY) {
return res.status(401).json({ error: 'APIキーが不正です' });
}
console.log('Headers:', req.headers); // ヘッダーを出力
console.log('Raw Body:', rawBody); // パースせずにそのまま出力
res.json({ message: 'リクエストを受け付けました', rawBody });
});
});
// HTTPSサーバー起動
const PORT = 30399;
const sslOptions = {
cert: fs.readFileSync('/etc/letsencrypt/live/neo999.next-hd.net/fullchain.pem'),
key: fs.readFileSync('/etc/letsencrypt/live/neo999.next-hd.net/privkey.pem')
};
https.createServer(sslOptions, app).listen(PORT, () => {
console.log(`APIサーバー起動: https://neo999.next-hd.net:${PORT}/server`);
});

View File

@ -0,0 +1,36 @@
//lwSendMessage.jsにAPIリクエストを送信するクライアントサンプル
//node実行する場合は下記が必要
const fetch = require('node-fetch');
//ブラウザスクリプトにする場合は下記から
//API-KEYは下記固定
const API_KEY = 'pgqhLFWbDFu4Byz#4afNYX2F6Fa1&$KPjved$8%sUdTQV52caip#EpIKxYUkdd4S'; // 任意のAPIキーを設定
//CORSには本番用・開発用の両方のプリザンターアドレスを登録してあるので
//どちらの環境からでも実行可能
async function sendMessageToLineworks() {
const apiUrl = 'https://neo999.next-hd.net:30309/lwSendMessage';
const body = {
botId: '10130468', //新しいBotを作ったらここでIDを変更
userId: 'k.nogi@works-nextgroup2',
messageText: 'テストメッセージ'
};
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': API_KEY // ここにAPIキーを追加
},
body: JSON.stringify(body)
});
if (response.ok) {
const result = await response.json();
console.log('APIレスポンス:', result);
} else {
console.error('APIリクエスト失敗:', await response.text());
}
}
sendMessageToLineworks();

View File

@ -0,0 +1,72 @@
//
exports.sendLineworksMessage = async (botId, userId, messageText) => {
//アクセストークンの取得
const accessToken = await getAccessToken();
if (!accessToken) return;
const apiUrl = `https://www.worksapis.com/v1.0/bots/${botId}/users/${userId}/messages`;
const body = {
'content': {
'type': 'text',
'text': messageText
}
};
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=UTF-8',
'Authorization': `Bearer ${accessToken}`
},
body: JSON.stringify(body)
});
if (response.ok) {
console.log('送信成功');
} else {
console.error('送信失敗', await response.text());
}
};
// アクセストークン取得
async function getAccessToken() {
const clientId = 'V58hNobcAqRcQPerySMz';
const clientSecret = 'gJ84XQtigD';
const serviceAccount = 'x94xh.serviceaccount@works-nextgroup2';
const privateKey = fs.readFileSync('./private_20250530180606.key', 'utf8');
const apiUrl = 'https://auth.worksmobile.com/oauth2/v2.0/token';
const now = Math.floor(Date.now() / 1000);
// JWT生成
const payload = {
'iss': clientId,
'sub': serviceAccount,
'iat': now,
'exp': now + 60 * 5,
'aud': apiUrl
};
const assertion = jwt.sign(payload, privateKey, { 'algorithm': 'RS256' });
const body = new URLSearchParams({
'assertion': assertion,
'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
'client_id': clientId,
'client_secret': clientSecret,
'scope': 'bot'
});
const response = await fetch(apiUrl, {
'method': 'POST',
'headers': { 'Content-Type': 'application/x-www-form-urlencoded' },
'body': body.toString()
});
if (response.ok) {
const data = await response.json();
return data.access_token;
} else {
console.error('アクセストークン取得失敗:', await response.text());
return null;
}
}

View File

@ -0,0 +1,142 @@
// Node.jsでLINE WORKSのBot APIを使って特定ユーザーにメッセージを送信するサンプルHTTPS対応
const express = require('express');
const bodyParser = require('body-parser');
const fetch = require('node-fetch');
const jwt = require('jsonwebtoken');
const fs = require('fs');
const https = require('https');
const cors = require('cors'); // 追加
const app = express();
app.use(cors({
origin: [
'https://neo999.next-hd.net',
'https://nextoffice.next-hd.co.jp',
], // 許可するオリジン
optionsSuccessStatus: 200
}));
//app.use(bodyParser.json());
const API_KEY = 'pgqhLFWbDFu4Byz#4afNYX2F6Fa1&$KPjved$8%sUdTQV52caip#EpIKxYUkdd4S'; // 任意のAPIキーを設定
// アクセストークン取得
async function getAccessToken() {
const clientId = 'V58hNobcAqRcQPerySMz';
const clientSecret = 'gJ84XQtigD';
const serviceAccount = 'x94xh.serviceaccount@works-nextgroup2';
const privateKey = fs.readFileSync('./private_20250530180606.key', 'utf8');
const apiUrl = 'https://auth.worksmobile.com/oauth2/v2.0/token';
const now = Math.floor(Date.now() / 1000);
// JWT生成
const payload = {
'iss': clientId,
'sub': serviceAccount,
'iat': now,
'exp': now + 60 * 5,
'aud': apiUrl
};
const assertion = jwt.sign(payload, privateKey, { 'algorithm': 'RS256' });
const body = new URLSearchParams({
'assertion': assertion,
'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
'client_id': clientId,
'client_secret': clientSecret,
'scope': 'bot'
});
const response = await fetch(apiUrl, {
'method': 'POST',
'headers': { 'Content-Type': 'application/x-www-form-urlencoded' },
'body': body.toString()
});
if (response.ok) {
const data = await response.json();
return data.access_token;
} else {
console.error('アクセストークン取得失敗:', await response.text());
return null;
}
}
// メッセージ送信
async function sendLineworksMessage(botId, userId, messageText) {
const accessToken = await getAccessToken();
if (!accessToken) return;
const apiUrl = `https://www.worksapis.com/v1.0/bots/${botId}/users/${userId}/messages`;
const body = {
'content': {
'type': 'text',
'text': messageText
}
};
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=UTF-8',
'Authorization': `Bearer ${accessToken}`
},
body: JSON.stringify(body)
});
if (response.ok) {
console.log('送信成功');
} else {
console.error('送信失敗', await response.text());
}
}
// APIエンドポイント
app.post('/lwSendMessage', async (req, res) => {
let rawBody = '';
req.on('data', (chunk) => {
rawBody += chunk; // リクエストデータをそのまま取得
});
req.on('end', () => {
// APIキーの検証
const apiKey = req.headers['x-api-key'];
if (!apiKey || apiKey !== API_KEY) {
return res.status(401).json({ error: 'APIキーが不正です' });
}
// botIdとuserIdをヘッダーから取得
const botId = req.headers['x-bot-id'];
const userId = req.headers['x-user-id'];
if (!botId || !userId) {
return res.status(400).json({ error: 'x-bot-id, x-user-idは必須です' });
}
// リクエストボディ全体をmessageTextとして使用
const messageText = rawBody;
console.log('messageText:', messageText);
if (!messageText) {
return res.status(400).json({ error: 'messageTextは必須です' });
}
try {
sendLineworksMessage(botId, userId, messageText);
res.json({ result: 'ok' });
} catch (e) {
res.status(500).json({ error: e.message });
}
});
});
// HTTPSサーバー起動
const PORT = 30309;
const sslOptions = {
cert: fs.readFileSync('/etc/letsencrypt/live/neo999.next-hd.net/fullchain.pem'),
key: fs.readFileSync('/etc/letsencrypt/live/neo999.next-hd.net/privkey.pem')
};
https.createServer(sslOptions, app).listen(PORT, () => {
console.log(`APIサーバー起動: https://neo999.next-hd.net:${PORT}/lwSendMessage`);
});

View File

@ -0,0 +1,19 @@
const { sendLineworksMessage } = require('./lineworksCore');
const API_KEY = 'pgqhLFWbDFu4Byz#4afNYX2F6Fa1&$KPjved$8%sUdTQV52caip#EpIKxYUkdd4S';
exports.lwSendMessageHandler = async (req, res) => {
const apiKey = req.headers['x-api-key'];
if (!apiKey || apiKey !== API_KEY) {
return res.status(401).json({ error: 'APIキーが不正です' });
}
const { botId, userId, messageText } = req.body;
if (!botId || !userId || !messageText) {
return res.status(400).json({ error: 'botId, userId, messageTextは必須です' });
}
try {
await sendLineworksMessage(botId, userId, messageText);
res.json({ result: 'ok' });
} catch (e) {
res.status(500).json({ error: e.message });
}
};

View File

@ -0,0 +1,197 @@
{
"name": "lineworks_sendmessage",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "lineworks_sendmessage",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"jsonwebtoken": "^9.0.2",
"node-fetch": "^2.7.0"
}
},
"node_modules/buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
"license": "BSD-3-Clause"
},
"node_modules/ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"license": "Apache-2.0",
"dependencies": {
"safe-buffer": "^5.0.1"
}
},
"node_modules/jsonwebtoken": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
"integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
"license": "MIT",
"dependencies": {
"jws": "^3.2.2",
"lodash.includes": "^4.3.0",
"lodash.isboolean": "^3.0.3",
"lodash.isinteger": "^4.0.4",
"lodash.isnumber": "^3.0.3",
"lodash.isplainobject": "^4.0.6",
"lodash.isstring": "^4.0.1",
"lodash.once": "^4.0.0",
"ms": "^2.1.1",
"semver": "^7.5.4"
},
"engines": {
"node": ">=12",
"npm": ">=6"
}
},
"node_modules/jwa": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz",
"integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==",
"license": "MIT",
"dependencies": {
"buffer-equal-constant-time": "^1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"node_modules/jws": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
"license": "MIT",
"dependencies": {
"jwa": "^1.4.1",
"safe-buffer": "^5.0.1"
}
},
"node_modules/lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
"license": "MIT"
},
"node_modules/lodash.isboolean": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
"license": "MIT"
},
"node_modules/lodash.isinteger": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
"license": "MIT"
},
"node_modules/lodash.isnumber": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
"license": "MIT"
},
"node_modules/lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
"license": "MIT"
},
"node_modules/lodash.isstring": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
"license": "MIT"
},
"node_modules/lodash.once": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
"license": "MIT"
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"license": "MIT",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/semver": {
"version": "7.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"license": "MIT"
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"license": "BSD-2-Clause"
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"license": "MIT",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
}
}
}

View File

@ -0,0 +1,16 @@
{
"name": "lineworks_sendmessage",
"version": "1.0.0",
"main": "lwSendMessageBot.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"jsonwebtoken": "^9.0.2",
"node-fetch": "^2.7.0"
}
}

View File

@ -0,0 +1,36 @@
//lwSendMessage.jsにAPIリクエストを送信するクライアントサンプル
//node実行する場合は下記が必要
const fetch = require('node-fetch');
//ブラウザスクリプトにする場合は下記から
//API-KEYは下記固定
const API_KEY = 'pgqhLFWbDFu4Byz#4afNYX2F6Fa1&$KPjved$8%sUdTQV52caip#EpIKxYUkdd4S'; // 任意のAPIキーを設定
//CORSには本番用・開発用の両方のプリザンターアドレスを登録してあるので
//どちらの環境からでも実行可能
async function sendMessageToLineworks() {
const apiUrl = 'https://neo999.next-hd.net:30309/lwSendMessage';
const body = {
botId: '10130468', //新しいBotを作ったらここでIDを変更
userId: 'k.nogi@works-nextgroup2',
messageText: 'テストメッセージ'
};
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': API_KEY // ここにAPIキーを追加
},
body: JSON.stringify(body)
});
if (response.ok) {
const result = await response.json();
console.log('APIレスポンス:', result);
} else {
console.error('APIリクエスト失敗:', await response.text());
}
}
sendMessageToLineworks();

View File

@ -0,0 +1,27 @@
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const https = require('https');
const fs = require('fs');
const { lwSendMessageHandler } = require('./lwSendMessageLogic');
const app = express();
app.use(cors({
origin: [
'https://neo999.next-hd.net',
'https://nextoffice.next-hd.co.jp',
],
optionsSuccessStatus: 200
}));
app.use(bodyParser.json());
app.post('/lwSendMessage', lwSendMessageHandler);
const sslOptions = {
cert: fs.readFileSync('/etc/letsencrypt/live/neo999.next-hd.net/fullchain.pem'),
key: fs.readFileSync('/etc/letsencrypt/live/neo999.next-hd.net/privkey.pem')
};
https.createServer(sslOptions, app).listen(30309, () => {
console.log('APIサーバー起動: https://neo999.next-hd.net:30309/lwSendMessage');
});

3
oldCode/express/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"git.ignoreLimitWarning": true
}

15
oldCode/express/Mail.json Normal file
View File

@ -0,0 +1,15 @@
{
"SmtpHost": "smtp.sendgrid.net",
"SmtpPort": "0",
"SmtpUserName": "apikey",
"SmtpPassword": "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",
"SmtpEnableSsl": false,
"ServerCertificateValidationCallback": false,
"SecureSocketOptions": null,
"FixedFrom": null,
"AllowedFrom": null,
"SupportFrom": "\"NEXTOFFICE\" <systemadmin@next-hd.co.jp>",
"InternalDomains": "",
"Encoding": null,
"ContentEncoding": null
}

20
oldCode/express/config.js Normal file
View File

@ -0,0 +1,20 @@
const fs = require('fs');
module.exports = {
API_KEY: 'pgqhLFWbDFu4Byz#4afNYX2F6Fa1&$KPjved$8%sUdTQV52caip#EpIKxYUkdd4S',
PORT: 30309,
SSL_OPTIONS: {
cert: fs.readFileSync('/etc/letsencrypt/live/neo999.next-hd.net/fullchain.pem'),
key: fs.readFileSync('/etc/letsencrypt/live/neo999.next-hd.net/privkey.pem')
},
CORS_ORIGIN: [
'https://neo999.next-hd.net',
'https://nextoffice.next-hd.co.jp'
],
PLEASANTER: {
// HOST: 'https://neo999.next-hd.net/pleasanter',
HOST: 'https://nextoffice.next-hd.co.jp/pleasanter',
API_KEY: '6504c8a807677a3a576e10327f3c19876c55736ee45d4a845796b9e7f5e087bfd4bd0d8184863da8cf1733ca635111432ab334aea59102b06a96ee6d2c05190d'
},
LINEWORKS_BOT_SECRET: 'P/06DLKvwTMyn10QffjZv4rU+Mq/Pi'
};

View File

@ -0,0 +1,14 @@
const fs = require('fs');
module.exports = {
API_KEY: 'pgqhLFWbDFu4Byz#4afNYX2F6Fa1&$KPjved$8%sUdTQV52caip#EpIKxYUkdd4S',
PORT: 30309,
SSL_OPTIONS: {
cert: fs.readFileSync('/etc/letsencrypt/live/neo999.next-hd.net/fullchain.pem'),
key: fs.readFileSync('/etc/letsencrypt/live/neo999.next-hd.net/privkey.pem')
},
CORS_ORIGIN: [
'https://neo999.next-hd.net',
'https://nextoffice.next-hd.co.jp'
]
};

View File

@ -0,0 +1,31 @@
const express = require('express');
const https = require('https');
const cors = require('cors');
const { API_KEY, PORT, SSL_OPTIONS, CORS_ORIGIN } = require('./config.js'); // config.jsから設定をインポート
// expressとCORSの設定
const app = express();
app.use(cors({
origin: CORS_ORIGIN, // config.jsから読み込み
optionsSuccessStatus: 200
}));
// lwSendMessageモジュールの読み込み
require('./modules/lwSendMessage.js')(app);
// lineworksSurveyモジュールの読み込み
require('./modules/lineworksSurvey.js')(app);
// pdfConvertモジュールの読み込み
require('./modules/pdfConvert.js')(app);
// pdfMergeモジュールの読み込み
require('./modules/pdfMerge.js')(app);
// pdfToTextモジュールの読み込み
require('./modules/pdfToText.js')(app);
// HTTPSサーバーの起動
https.createServer(SSL_OPTIONS, app).listen(PORT, () => {
console.log(`HTTPS server is running on https://localhost:${PORT}`);
});

32
oldCode/express/index.js Normal file
View File

@ -0,0 +1,32 @@
const express = require('express');
const http = require('http');
const cors = require('cors');
const { API_KEY, PORT, SSL_OPTIONS, CORS_ORIGIN } = require('./config.js'); // config.jsから設定をインポート
// expressとCORSの設定
const app = express();
app.use(cors({
origin: CORS_ORIGIN, // config.jsから読み込み
optionsSuccessStatus: 200
}));
// lwSendMessageモジュールの読み込み
require('./modules/lwSendMessage.js')(app);
// lineworksSurveyモジュールの読み込み
require('./modules/lineworksSurvey.js')(app);
// pdfConvertモジュールの読み込み
require('./modules/pdfConvert.js')(app);
// pdfMergeモジュールの読み込み
require('./modules/pdfMerge.js')(app);
// pdfToTextモジュールの読み込み
require('./modules/pdfToText.js')(app);
// HTTPSサーバーの起動
// const PORT = PORT;
http.createServer(app).listen(PORT, '127.0.0.1', () => {
console.log(`Server running on http://127.0.0.1:${PORT}`);
});

View File

@ -0,0 +1,77 @@
const { getAccessToken } = require('./modules/lineworksAuth');
const USER_INFO_SCOPES = ['user.read', 'directory.read'];
async function main() {
const [, , identifier, domainArg] = process.argv;
if (!identifier) {
console.error('使い方: node lookupLineworksUser.js <userId|email|externalKey:xxx> [domainId]');
process.exit(1);
}
const domainId = domainArg || process.env.LINEWORKS_DOMAIN_ID || '';
const encodedIdentifier = encodeURIComponent(identifier);
const accessToken = await getAccessToken(USER_INFO_SCOPES);
const baseUrl = `https://www.worksapis.com/v1.0/users/${encodedIdentifier}`;
const apiUrl = domainId ? `${baseUrl}?domainId=${encodeURIComponent(domainId)}` : baseUrl;
const response = await fetch(apiUrl, {
method: 'GET',
headers: {
Authorization: `Bearer ${accessToken}`
}
});
const text = await response.text();
let data;
try {
data = JSON.parse(text);
} catch {
console.error('LINE WORKS APIレスポンス解析に失敗しました:', text);
process.exit(1);
}
if (!response.ok) {
console.error('LINE WORKS APIエラー:', JSON.stringify(data, null, 2));
process.exit(1);
}
printUserInfo(data);
}
function printUserInfo(user) {
console.log('--- User Info ---');
console.log(`domainId : ${user.domainId}`);
console.log(`userId : ${user.userId}`);
console.log(`userExternalKey : ${user.userExternalKey ?? 'N/A'}`);
console.log(`email : ${user.email}`);
console.log(`name : ${formatName(user.userName)}`);
console.log(`nickName : ${user.nickName ?? 'N/A'}`);
console.log(`employeeNumber : ${user.employeeNumber ?? 'N/A'}`);
console.log(`isAdministrator : ${user.isAdministrator}`);
console.log(`isSuspended : ${user.isSuspended}`);
console.log(`isDeleted : ${user.isDeleted}`);
if (Array.isArray(user.organizations)) {
console.log('organizations:');
for (const org of user.organizations) {
console.log(` - domainId : ${org.domainId}, primary: ${org.primary}, email: ${org.email ?? 'N/A'}`);
}
}
if (Array.isArray(user.aliasEmails) && user.aliasEmails.length > 0) {
console.log(`aliasEmails : ${user.aliasEmails.join(', ')}`);
}
}
function formatName(userName = {}) {
const last = userName.lastName ?? '';
const first = userName.firstName ?? '';
return `${last} ${first}`.trim() || 'N/A';
}
main().catch((error) => {
console.error('ユーザー照会でエラーが発生しました:', error.message);
process.exit(1);
});

View File

@ -0,0 +1,77 @@
const fs = require('fs');
const path = require('path');
const jwt = require('jsonwebtoken');
let cachedToken = null;
let cachedExpiry = 0;
let cachedScopeKey = '';
const CLIENT_ID = 'tre8J_Tk8RblfsMSyZRh';
const CLIENT_SECRET = 'O5_R5gcHxg';
const SERVICE_ACCOUNT = 'wh4k8.serviceaccount@nexthd.jp';
const TOKEN_ENDPOINT = 'https://auth.worksmobile.com/oauth2/v2.0/token';
const PRIVATE_KEY_PATH = path.join(__dirname, 'private_20260307184804.key');
const TOKEN_TTL_BUFFER = 30; // seconds
const DEFAULT_SCOPES = ['bot', 'contact'];
function readPrivateKey() {
return fs.readFileSync(PRIVATE_KEY_PATH, 'utf8');
}
function makeScopeKey(scopes) {
return scopes.slice().sort().join(' ');
}
function isTokenValid(scopeKey) {
return Boolean(cachedToken)
&& cachedScopeKey === scopeKey
&& cachedExpiry > Math.floor(Date.now() / 1000) + TOKEN_TTL_BUFFER;
}
async function getAccessToken(scopes = DEFAULT_SCOPES) {
const normalizedScopes = Array.isArray(scopes) && scopes.length > 0 ? scopes : DEFAULT_SCOPES;
const scopeKey = makeScopeKey(normalizedScopes);
if (isTokenValid(scopeKey)) {
return cachedToken;
}
const now = Math.floor(Date.now() / 1000);
const payload = {
iss: CLIENT_ID,
sub: SERVICE_ACCOUNT,
iat: now,
exp: now + 5 * 60,
aud: TOKEN_ENDPOINT
};
const assertion = jwt.sign(payload, readPrivateKey(), { algorithm: 'RS256' });
const body = new URLSearchParams({
assertion,
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
scope: normalizedScopes.join(' ')
});
const response = await fetch(TOKEN_ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: body.toString()
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`LINE WORKSアクセストークン取得失敗: ${errorText}`);
}
const data = await response.json();
cachedToken = data.access_token;
cachedExpiry = now + (data.expires_in || 300);
cachedScopeKey = scopeKey;
return cachedToken;
}
module.exports = {
getAccessToken
};

View File

@ -0,0 +1,77 @@
const fs = require('fs');
const path = require('path');
const jwt = require('jsonwebtoken');
let cachedToken = null;
let cachedExpiry = 0;
let cachedScopeKey = '';
const CLIENT_ID = 'V58hNobcAqRcQPerySMz';
const CLIENT_SECRET = 'gJ84XQtigD';
const SERVICE_ACCOUNT = 'x94xh.serviceaccount@works-nextgroup2';
const TOKEN_ENDPOINT = 'https://auth.worksmobile.com/oauth2/v2.0/token';
const PRIVATE_KEY_PATH = path.join(__dirname, 'private_20250530180606.key');
const TOKEN_TTL_BUFFER = 30; // seconds
const DEFAULT_SCOPES = ['bot', 'contact'];
function readPrivateKey() {
return fs.readFileSync(PRIVATE_KEY_PATH, 'utf8');
}
function makeScopeKey(scopes) {
return scopes.slice().sort().join(' ');
}
function isTokenValid(scopeKey) {
return Boolean(cachedToken)
&& cachedScopeKey === scopeKey
&& cachedExpiry > Math.floor(Date.now() / 1000) + TOKEN_TTL_BUFFER;
}
async function getAccessToken(scopes = DEFAULT_SCOPES) {
const normalizedScopes = Array.isArray(scopes) && scopes.length > 0 ? scopes : DEFAULT_SCOPES;
const scopeKey = makeScopeKey(normalizedScopes);
if (isTokenValid(scopeKey)) {
return cachedToken;
}
const now = Math.floor(Date.now() / 1000);
const payload = {
iss: CLIENT_ID,
sub: SERVICE_ACCOUNT,
iat: now,
exp: now + 5 * 60,
aud: TOKEN_ENDPOINT
};
const assertion = jwt.sign(payload, readPrivateKey(), { algorithm: 'RS256' });
const body = new URLSearchParams({
assertion,
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
scope: normalizedScopes.join(' ')
});
const response = await fetch(TOKEN_ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: body.toString()
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`LINE WORKSアクセストークン取得失敗: ${errorText}`);
}
const data = await response.json();
cachedToken = data.access_token;
cachedExpiry = now + (data.expires_in || 300);
cachedScopeKey = scopeKey;
return cachedToken;
}
module.exports = {
getAccessToken
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,58 @@
require('dotenv').config();
const express = require('express');
const bodyParser = require('body-parser');
const axios = require('axios');
app.use(bodyParser.json());
module.exports = (app) => {
// LINE WORKS Webhookエンドポイント
app.post('/nextgpt', async (req, res) => {
try {
const { source, content } = req.body;
// LINE WORKSからのメッセージ
const userMessage = content.text;
const userId = source.accountId;
console.log(`Received message: "${userMessage}" from User: "${userId}"`);
// anythingLLMにメッセージを送信して応答を取得
const llmResponse = await axios.post(process.env.ANYTHING_LLM_API, {
question: userMessage
});
const llmAnswer = llmResponse.data.answer || "申し訳ありませんが、応答を生成できませんでした。";
// LINE WORKSに応答を送信
await axios.post(
`https://apis.worksmobile.com/r/${process.env.LINEWORKS_BOT_ID}/message/push`,
{
botNo: process.env.LINEWORKS_BOT_ID,
accountId: userId,
content: {
type: "text",
text: llmAnswer
}
},
{
headers: {
"Content-Type": "application/json",
"consumerKey": process.env.LINEWORKS_API_KEY
}
}
);
console.log(`Reply sent to User: "${userId}"`);
res.status(200).send('OK');
} catch (error) {
console.error('Error:', error.message);
res.status(500).send('Internal Server Error');
}
});
};
$p.apiUpdate({ id: 186849, data: { ApiVersion: 1.1, ClassHash: { ClassU: ["遠藤 晃太", "(退職) 横井 英治"], ClassV: ["原田 朋保"], ClassW: ["宮倉 由紀"] } }, done: function (data) { console.log(data); }, });

View File

@ -0,0 +1,99 @@
const { API_KEY } = require('../config.js'); // API_KEYをインポート
const { getAccessToken } = require('./lineworksAuth');
module.exports = (app) => {
app.post('/lineworks/sendMessage', async (req, res) => {
let rawBody = '';
req.on('data', (chunk) => {
rawBody += chunk; // リクエストデータをそのまま取得
});
req.on('end', async () => {
// reqのすべてのパラメータを表示
// console.log('Request Headers:', req.headers);
// console.log('Request Body:', rawBody);
const apiKey = req.headers['x-api-key'];
if (!apiKey || apiKey !== API_KEY) {
console.log('APIキーが不正です');
return res.status(401).json({ error: 'APIキーが不正です' });
}
// botIdとuserIdをヘッダーから取得
const botId = req.headers['x-bot-id'];
const userId = req.headers['x-user-id'];
if (!botId || !userId) {
console.log('x-bot-id, x-user-idは必須です');
return res.status(400).json({ error: 'x-bot-id, x-user-idは必須です' });
}
const userIds = userId.split(';').map(id => id.trim()).filter(id => id);
// リクエストボディからメッセージテキストを取得
const messageText = rawBody;
if (!messageText) {
console.log('messageTextは必須です');
return res.status(400).json({ error: 'messageTextは必須です' });
}
try {
for (const uid of userIds) {
const result = await sendLineworksMessage(botId, uid, messageText);
if (result.success) {
console.log(`送信成功: ${uid}:`, result.message);
} else {
console.log(`送信失敗: ${uid}:`, result.message);
}
}
res.json({ result: 'ok', message: '全ユーザーに送信処理完了' });
return;
} catch (e) {
console.log('エラーが発生しました:', e.message);
res.status(500).json({ error: e.message });
}
});
});
};
// メッセージ送信
async function sendLineworksMessage(botId, userId, messageText) {
const accessToken = await getAccessToken();
if (!accessToken) {
console.log('アクセストークンの取得に失敗しました');
throw new Error('アクセストークンの取得に失敗しました');
}
// userIdに@が含まれない場合はchannelIdとして扱う
let apiUrl;
if (userId.includes('@')) {
apiUrl = `https://www.worksapis.com/v1.0/bots/${botId}/users/${userId}/messages`;
} else {
apiUrl = `https://www.worksapis.com/v1.0/bots/${botId}/channels/${userId}/messages`;
}
const body = {
'content': {
'type': 'text',
'text': messageText
}
};
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=UTF-8',
'Authorization': `Bearer ${accessToken}`
},
body: JSON.stringify(body)
});
if (response.ok) {
console.log('メッセージ送信成功');
return { success: true, message: '送信成功' };
} else {
const errorText = await response.text();
console.log('メッセージ送信失敗:', errorText);
return { success: false, message: `送信失敗: ${errorText}` };
}
}

View File

@ -0,0 +1,100 @@
const fs = require('fs');
const path = require('path');
const { execFile } = require('child_process');
const { v4: uuidv4 } = require('uuid');
const express = require('express'); // JSON解析用にexpressをインポート
const { API_KEY } = require('../config.js'); // API_KEYをインポート
// LibreOffice AppImageのパス
const libreofficeAppImage = './modules/LibreOffice-still.standard-x86_64.AppImage';
// tmpディレクトリのパス
const tmpDir = path.join(__dirname, 'tmp');
// tmpディレクトリが存在しない場合は作成
if (!fs.existsSync(tmpDir)) {
fs.mkdirSync(tmpDir);
}
// POSTエンドポイント
module.exports = (app) => {
app.post('/pdfConvert', express.json({ limit: '30mb' }), async (req, res) => {
try {
// APIキーのチェック
const apiKey = req.headers['x-api-key'];
if (!apiKey || apiKey !== API_KEY) {
console.log('APIキーが不正です');
return res.status(401).json({ error: 'APIキーが不正です' });
}
const { base64, fileName } = req.body;
if (!base64 || !fileName) {
return res.status(400).json({ error: 'base64とfileNameは必須です' });
}
const pdfBuffer = await convertBase64ToPdf(base64, fileName);
const safeFileName = encodeURIComponent(path.parse(fileName).name); // ファイル名をエスケープ
res.set({
'Content-Type': 'application/pdf',
'Content-Disposition': `attachment; filename="${safeFileName}.pdf"`
});
res.send(pdfBuffer);
} catch (err) {
console.error('変換エラー:', err.message);
res.status(500).json({ error: err.message });
}
});
};
// PDF変換処理
async function convertBase64ToPdf(base64Data, originalFileName) {
const tempId = uuidv4(); // 一意のIDを生成
const ext = path.extname(originalFileName) || '.tmp'; // 拡張子を取得、なければ.tmpを使用
const orgPath = path.join(tmpDir, `${tempId}${ext}`); // 一時的なファイルのパス
const pdfPath = path.join(tmpDir, `${tempId}.pdf`);
// base64データをファイルに保存
fs.writeFileSync(orgPath, Buffer.from(base64Data, 'base64'));
// LibreOfficeでPDF変換タイムアウト60秒
await new Promise((resolve, reject) => {
const child = execFile(
libreofficeAppImage,
[
'--headless',
'--convert-to', 'pdf',
'--outdir', tmpDir,
orgPath
],
(error, stdout, stderr) => {
if (error) {
console.error('stderr:', stderr);
reject(new Error(`LibreOfficeエラー: ${stderr}`));
} else {
resolve();
}
}
);
const timeout = setTimeout(() => {
child.kill('SIGTERM'); // SIGTERMを使用してプロセスを終了
reject(new Error('PDF変換がタイムアウトしました60秒経過'));
}, 60000);
child.on('exit', () => clearTimeout(timeout));
});
// PDFファイルを読み込む
if (!fs.existsSync(pdfPath)) {
throw new Error('PDF変換に失敗しました');
}
const pdfBuffer = fs.readFileSync(pdfPath);
// cleanup
try {
if (fs.existsSync(orgPath)) fs.unlinkSync(orgPath);
if (fs.existsSync(pdfPath)) fs.unlinkSync(pdfPath);
} catch (cleanupError) {
console.error('クリーンアップエラー:', cleanupError.message);
}
return pdfBuffer;
}

View File

@ -0,0 +1,97 @@
const fs = require('fs');
const path = require('path');
const { execFile } = require('child_process');
const { v4: uuidv4 } = require('uuid');
const express = require('express'); // JSON解析用にexpressをインポート
const { API_KEY } = require('../config.js'); // API_KEYをインポート
// tmpディレクトリのパス
const tmpDir = path.join(__dirname, 'tmp');
// tmpディレクトリが存在しない場合は作成
if (!fs.existsSync(tmpDir)) {
fs.mkdirSync(tmpDir);
}
// POSTエンドポイント
module.exports = (app) => {
app.post('/pdfMerge', express.json({ limit: '30mb' }), async (req, res) => {
try {
// APIキーのチェック
const apiKey = req.headers['x-api-key'];
if (!apiKey || apiKey !== API_KEY) {
console.log('APIキーが不正です');
return res.status(401).json({ error: 'APIキーが不正です' });
}
// base64_1, base64_2, fileName の3つをチェック
const { base64_1, base64_2, fileName } = req.body;
if (!base64_1 || !base64_2 || !fileName) {
return res.status(400).json({ error: 'base64_1, base64_2, fileNameは必須です' });
}
const pdfBuffer = await mergeBase64ToPdf(base64_1, base64_2, fileName);
const safeFileName = encodeURIComponent(path.parse(fileName).name); // ファイル名をエスケープ
res.set({
'Content-Type': 'application/pdf',
'Content-Disposition': `attachment; filename="${safeFileName}.pdf"`
});
res.send(pdfBuffer);
} catch (err) {
console.error('変換エラー:', err.message);
res.status(500).json({ error: err.message });
}
});
};
// PDF結合処理
async function mergeBase64ToPdf(base64Data1, base64Data2) {
const tempId = uuidv4(); // 一意のIDを生成
const orgPath1 = path.join(tmpDir, `${tempId}_1.pdf`);
const orgPath2 = path.join(tmpDir, `${tempId}_2.pdf`);
const mergedPath = path.join(tmpDir, `${tempId}_merged.pdf`);
// base64データをファイルに保存
fs.writeFileSync(orgPath1, Buffer.from(base64Data1, 'base64'));
fs.writeFileSync(orgPath2, Buffer.from(base64Data2, 'base64'));
// pdfuniteでPDF結合
await new Promise((resolve, reject) => {
const child = execFile(
'pdfunite',
[orgPath1, orgPath2, mergedPath],
(error, stdout, stderr) => {
if (error) {
console.error('stderr:', stderr);
reject(new Error(`pdfuniteエラー: ${stderr}`));
} else {
resolve();
}
}
);
const timeout = setTimeout(() => {
child.kill('SIGTERM'); // SIGTERMを使用してプロセスを終了
reject(new Error('PDF変換がタイムアウトしました60秒経過'));
}, 60000);
child.on('exit', () => clearTimeout(timeout));
});
// 結合されたPDFを読み込む
if (!fs.existsSync(mergedPath)) {
throw new Error('PDF結合に失敗しました');
}
const pdfBuffer = fs.readFileSync(mergedPath);
// cleanup
try {
if (fs.existsSync(orgPath1)) fs.unlinkSync(orgPath1);
if (fs.existsSync(orgPath2)) fs.unlinkSync(orgPath2);
if (fs.existsSync(mergedPath)) fs.unlinkSync(mergedPath);
} catch (cleanupError) {
console.error('クリーンアップエラー:', cleanupError.message);
}
return pdfBuffer;
}

View File

@ -0,0 +1,98 @@
const fs = require('fs');
const path = require('path');
const { execFile } = require('child_process');
const { v4: uuidv4 } = require('uuid');
const express = require('express'); // JSON解析用にexpressをインポート
const { API_KEY } = require('../config.js'); // API_KEYをインポート
// tmpディレクトリのパス
const tmpDir = path.join(__dirname, 'tmp');
// tmpディレクトリが存在しない場合は作成
if (!fs.existsSync(tmpDir)) {
fs.mkdirSync(tmpDir);
}
// POSTエンドポイント
module.exports = (app) => {
app.post('/pdfToText', express.json({ limit: '30mb' }), async (req, res) => {
try {
// APIキーのチェック
const apiKey = req.headers['x-api-key'];
if (!apiKey || apiKey !== API_KEY) {
console.log('APIキーが不正です');
return res.status(401).json({ error: 'APIキーが不正です' });
}
// base64, fileName の3つをチェック
const { base64, fileName } = req.body;
if (!base64 || !fileName) {
return res.status(400).json({ error: 'base64とfileNameは必須です' });
}
const txtBuffer = await pdfToTextConvert(base64, fileName);
const safeFileName = encodeURIComponent(path.parse(fileName).name); // ファイル名をエスケープ
res.set({
'Content-Type': 'text/plain',
'Content-Disposition': `attachment; filename="${safeFileName}.txt"`
});
res.send(txtBuffer);
} catch (err) {
console.error('変換エラー:', err.message);
res.status(500).json({ error: err.message });
}
});
};
// PDF テキスト変換処理
async function pdfToTextConvert(base64) {
const tempId = uuidv4(); // 一意のIDを生成
const pdfPath = path.join(tmpDir, `${tempId}.pdf`);
const outputPath = path.join(tmpDir, `${tempId}.txt`);
// base64データをファイルに保存
fs.writeFileSync(pdfPath, Buffer.from(base64, 'base64'));
// pdfuniteでテキストに変換
await new Promise((resolve, reject) => {
const child = execFile(
'pdftotext',
[
'-layout',
'-nopgbrk',
pdfPath,
outputPath
],
(error, stdout, stderr) => {
if (error) {
console.error('stderr:', stderr);
reject(error);
} else {
resolve();
}
}
);
// 60秒タイムアウト
const timeout = setTimeout(() => {
child.kill('SIGTERM'); // SIGTERMを使用してプロセスを終了
reject(new Error('テキストへの変換がタイムアウトしました60秒経過'));
}, 60000);
child.on('exit', () => clearTimeout(timeout));
});
// 変換されたテキストファイルを読み込み
if (!fs.existsSync(outputPath)) {
// cleanup
fs.unlinkSync(pdfPath);;
throw new Error('テキストへの変換に失敗しました');
}
const txtBuffer = fs.readFileSync(outputPath);
// cleanup
fs.unlinkSync(pdfPath);
fs.unlinkSync(outputPath);
return txtBuffer;
}

1154
oldCode/express/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,14 @@
{
"dependencies": {
"body-parser": "^2.2.0",
"cors": "^2.8.5",
"express": "^5.1.0",
"fetch": "^1.1.0",
"fs": "^0.0.1-security",
"https": "^1.0.0",
"jsonwebtoken": "^9.0.2",
"node-fetch": "^3.3.2",
"url": "^0.11.4",
"uuid": "^11.1.0"
}
}

View File

@ -0,0 +1,170 @@
//参考スクリプト startLineworksSurvey.js
// botId = $('#Results_Class001').val();
// surveyId = $('#Results_Class002').val();
// targetId = $('#Results_Class003').val();
const WEBHOOK_URL = 'https://neo999.next-hd.net/lineworksSurvey/webhook';
const API_KEY = 'pgqhLFWbDFu4Byz#4afNYX2F6Fa1&$KPjved$8%sUdTQV52caip#EpIKxYUkdd4S';
$p.events.on_grid_load = function () {
// アンケート一括発行ボタンの作成
createSurveyAllButton();
}
function createSurveyAllButton() {
if ($('#surveyAll').length > 0) {
return;
}
//処理内容
let button = document.createElement('button');
button.type = 'button';
button.id = 'surveyAll';
button.name = 'surveyAll';
button.innerText = 'アンケート一括発行';
button.setAttribute('onclick', 'sendSurveyAll()');
$("#MainCommands button:last-child").after(button);
$('#surveyAll').button({
icon: 'ui-icon-disk'
});
}
async function sendStartRequest(botId, surveyId, targetId, resultId) {
// const botId = $('#Results_Class001').val();
// const surveyId = $('#Results_Class002').val();
// const targetId = $('#Results_Class003').val();
// const resultId = $p.id();
if (!surveyId || !targetId || !botId) {
console.log('surveyId, targetId, botId をソース内で設定してください。');
return false;
}
if (!API_KEY) {
console.log('API_KEY is empty. Check config.js / environment.');
return false;
}
const payload = { surveyId, targetId, botId, resultId };
const res = await fetch(WEBHOOK_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': API_KEY
},
body: JSON.stringify(payload)
});
const text = await res.text();
let body;
try {
body = JSON.parse(text);
} catch {
body = { raw: text };
}
if (!res.ok) {
console.log('Start request failed:', res.status, body);
process.exit(1);
}
/*
Start request success: {
result: 'ok',
sessionId: 'eef7af18-065a-4486-b5a5-9f65bca61d5a',
message: 'アンケートを開始しました'
}
*/
console.log('Start request success:', body);
return true;
}
/*
アンケート発行処理
*/
async function sendSurveyAll() {
let siteId = $p.siteId();
/*#Grid .grid-check 一覧のチェックボックスがオンのものの、data-idの値を全て取得し、配列に格納 */
let checkedIds = [];
$('#Grid .grid-check:checked').each(function () {
checkedIds.push($(this).data('id'));
}
);
console.log('Checked IDs:', checkedIds);
if (checkedIds.length === 0) {
console.log('No records selected. Please check at least one record.');
$p.clearMessage();
$p.setMessage('#Message', JSON.stringify({
Css: 'alert-warning',
Text: 'アンケートを一括発行するレコードの左端のチェックボックスをオンにしてください。'
}));
//return;
} else {
$p.apiGet({
'id': siteId,
'data': {
'View': {
'ColumnFilterHash': {
'Status': "['100']"
}
}
},
'done': function (data) {
console.log('API data:', data);
if (data.Response.TotalCount > 0) {
let records = data.Response.Data.filter(record => checkedIds.includes(record.ResultId));
$.each(records, async function (index, record) {
let resultId = record.ResultId;;
let botId = record.ClassHash.Class001;
let surveyId = record.ClassHash.Class002;
let targetId = record.ClassHash.Class003;
console.log(`Processing record ${index + 1}/${records.length}:`, { resultId, botId, surveyId, targetId });
// アンケート開始リクエストを送信
if (await sendStartRequest(botId, surveyId, targetId, resultId)) {
console.log(`Survey started for record ${resultId}. Updating status...`);
await updateSurveyRecord(resultId);
}
});
$p.clearMessage();
$p.setMessage('#Message', JSON.stringify({
Css: 'alert-success',
Text: 'アンケートを一括発行しました'
}));
setTimeout(() => {
location.reload();
}, 3000);
}
}
});
}
}
// アンケート開始後、ステータスを150に更新する関数
async function updateSurveyRecord(resultId) {
$p.apiUpdate({
'id': resultId,
'data': {
'ApiVersion': 1.1,
'Status': '150'
},
'done': function (data) {
console.log(data);
}
});
}

View File

@ -0,0 +1,86 @@
//参考スクリプト startLineworksSurvey.js
// botId = $('#Results_Class001').val();
// surveyId = $('#Results_Class002').val();
// targetId = $('#Results_Class003').val();
const WEBHOOK_URL = 'https://neo999.next-hd.net/lineworksSurvey/webhook';
const API_KEY = 'pgqhLFWbDFu4Byz#4afNYX2F6Fa1&$KPjved$8%sUdTQV52caip#EpIKxYUkdd4S';
async function sendStartRequest() {
const botId = $('#Results_Class001').val();
const surveyId = $('#Results_Class002').val();
const targetId = $('#Results_Class003').val();
const resultId = $p.id();
if (!surveyId || !targetId || !botId) {
console.error('surveyId, targetId, botId をソース内で設定してください。');
return false;
}
if (!API_KEY) {
console.error('API_KEY is empty. Check config.js / environment.');
return false;
}
const payload = { surveyId, targetId, botId, resultId };
const res = await fetch(WEBHOOK_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': API_KEY
},
body: JSON.stringify(payload)
});
const text = await res.text();
let body;
try {
body = JSON.parse(text);
} catch {
body = { raw: text };
}
if (!res.ok) {
console.error('Start request failed:', res.status, body);
process.exit(1);
}
/*
Start request success: {
result: 'ok',
sessionId: 'eef7af18-065a-4486-b5a5-9f65bca61d5a',
message: 'アンケートを開始しました'
}
*/
console.log('Start request success:', body);
return true;
}
/*
アンケート発行処理
*/
async function sendSurvey(proccessButton) {
let procNum = proccessButton[0].id.replace('Process_', '');
console.log('Process number:', procNum);
if (await sendStartRequest()) {
// プロセス実行
$p.setMessage('#Message', JSON.stringify({
Css: 'alert-success',
Text: 'アンケートを発行しました'
}));
//$p.set($('#Results_Status'), 150);
//プロセスを継続
$p.execProcess($('#Process_' + procNum));
return true;
} else {
$p.setMessage('#Message', JSON.stringify({
Css: 'alert-error',
Text: 'アンケートの発行に失敗しました'
}));
return false;
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,55 @@
'use strict';
const { API_KEY } = require('./config.js');
const WEBHOOK_URL =
process.env.LINEWORKS_SURVEY_WEBHOOK_URL ||
'https://neo999.next-hd.net/lineworksSurvey/webhook';
// ここで固定値を指定
const surveyId = '335338';
const targetId = 'kenichiro.nogi@works-nextgroup2';
const botId = '11747661';
if (!surveyId || !targetId || !botId) {
console.error('surveyId, targetId, botId をソース内で設定してください。');
process.exit(1);
}
if (!API_KEY) {
console.error('API_KEY is empty. Check config.js / environment.');
process.exit(1);
}
async function main() {
const payload = { surveyId, targetId, botId };
const res = await fetch(WEBHOOK_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': API_KEY
},
body: JSON.stringify(payload)
});
const text = await res.text();
let body;
try {
body = JSON.parse(text);
} catch {
body = { raw: text };
}
if (!res.ok) {
console.error('Start request failed:', res.status, body);
process.exit(1);
}
console.log('Start request success:', body);
}
main().catch((err) => {
console.error('Unexpected error:', err);
process.exit(1);
});

25
oldCode/express/style.css Normal file
View File

@ -0,0 +1,25 @@
#Results_ClassAField,
#Results_NumAField,
#Results_NumBField,
#Results_Class001Field,
#Results_Class002Field,
#Results_Class003Field,
#Results_Class004Field,
#Results_Class005Field,
#Results_Class006Field,
#Results_Class007Field,
#Results_Class008Field,
#Results_Class009Field,
#Results_Class010Field,
#Results_Class011Field {
width: 500px;
.field-label {
width: 300px;
margin-right: -300px;
}
.field-control>.container-normal {
margin-left: 300px;
}
}

View File

@ -0,0 +1,34 @@
const express = require('express');
const https = require('https');
const { SSL_OPTIONS } = require('./config.js');
const app = express();
app.use((req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.originalUrl}`);
next();
});
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.get('/webhook', (req, res) => {
console.log('GET /webhook headers:', req.headers);
res.status(200).send('ok');
});
app.head('/webhook', (req, res) => {
console.log('HEAD /webhook headers:', req.headers);
res.status(200).end();
});
app.post('/webhook', (req, res) => {
console.log('POST /webhook headers:', req.headers);
console.log('POST /webhook body:', req.body);
res.status(200).json({ message: 'Webhook received' });
});
const PORT = 30399;
https.createServer(SSL_OPTIONS, app).listen(PORT, '0.0.0.0', () => {
console.log(`Webhook HTTPS server running on https://neo999.next-hd.net:${PORT}`);
});

View File

@ -0,0 +1,25 @@
lineworksでトーク画面でアンケートを取りたい。アンケートの内容は事前にプリザンターで定義を登録できるようにしたい。
アンケートは選択式、数字入力、自由入力の3種類を選べるようにする。
アンケート結果は事前に指定したプリザンターのテーブルにレコードを登録したい。
スクリプトはNode.jsを使用。Expressでサーバーを利用。
sendMessage.jsでLineworksにトークを送る関数とAccessTyokenの取得関数は準備済み。
アンケートIDと宛先IDを指定して、最初にアンケートの開始を受け付ける処理を作成。
ユーザーからの回答をすべて受信したら、まとめてプリザンターのレコードに保存する。
スクリプト名
lineworksSurvey.js modules内に配置
関数名
getSurvey : プリザンターからアンケートの元となるデータを取得する処理
sendSurvey : ユーザーにアンケートを送る処理
getAnswer : 回答を受け取る処理
sendAnswer : プリザンターに回答を保存する処理
lwSendMessage.js sendLineworksMessage を参考にアンケートを宛先のトークに送信する処理を作成する。
index.js に追加したモジュールを追加してexpressサーバーで稼働させる
プリザンターPleasanterからデータを取得するときはFetchを利用
APIとURLはConfig.jsから取得
テーブルIDはスクリプト内で定数で指定する