#コラボチャット: クロススレッド投稿

#コラボチャット: クロススレッド投稿

translate #Collab-Chat: Cross-Thread Post

任意のテキストをコラボチャット(Collab Chat: Questetraワークフロー基盤の社内チャット)にクロス投稿します。複数のチャンネルまたはスレッドを指定できます。組織チャンネル、アプリチャンネル、ケースチャンネルに投稿する際は ID(例: “g12”, “m12”, “p123″)を指定し、ユーザ作成チャンネルに投稿する際はチャンネル名(例: “general”)を指定します。

Auto Step icon
Configs for this Auto Step
BoolConfA0
A0: Markdownテキストとして投稿する Off ⇔ On
StrConfA1
A1: 投稿するテキストをセットしてください#{EL}
SelectConfA2
A2: 添付ファイルが格納されているファイル群型データを選択してください (最大10)
StrConfA3
A3: リンクURLを各行にセットしてください (添付ファイル数含めて最大10)#{EL}
StrConfB1
B1: クロス投稿先のチャンネル-スレッドをTSVでセットしてください (例: “g12\t123\ngeneral”) *#{EL}
SelectConfC1
C1: クロス投稿したチャンネル-スレッドTSVを格納する文字列型データ項目を指定してください
BoolConfB0
B0: チャンネル自動作成 Off ⇔ On
Script (click to open)
// Script Example of Business Process Automation
// for 'engine type: 3' ("GraalJS standard mode")
// Some functions require Questetra BPM Suite v18.0 or later

//////// START "main()" ////////////////////////////////////////////////////////////////
main();
function main(){ 

//// == Config Retrieving / 工程コンフィグの参照 ==
const strBoolMarkdown    = configs.get       ( "BoolConfA0" );   // "true" or "false"
const strText            = configs.get       ( "StrConfA1" );    // NotRequired
const filesPocketAttach  = configs.getObject ( "SelectConfA2" ); // NotRequired
const strUrls            = configs.get       ( "StrConfA3" );    // NotRequired
const strBoolCreateChannel = configs.get     ( "BoolConfB0" );   // "true" or "false"
const tsvChannelThreads  = configs.get       ( "StrConfB1" );    /// REQUIRED
  if( tsvChannelThreads === "" ){
    throw new Error( "\n AutomatedTask ConfigError:" +
                     " Config {B1: Channel-Threads TSV} is empty \n" );
  }
  const arr2dChannelThreads = parseAsRectangular( tsvChannelThreads );  // [row,col]
  for ( let i = 0; i < arr2dChannelThreads.length; i++ ){
    if ( arr2dChannelThreads[i].length === 1 || arr2dChannelThreads[i]?.[1] === "" ){
      engine.log( " AutomatedTask: Post to the Channel as a new Thread - " + arr2dChannelThreads[i][0] );
    } else if ( parseInt(arr2dChannelThreads[i][1], 10) ){
      engine.log( " AutomatedTask: Post to the Channel/MessageID - " + arr2dChannelThreads[i][0] + "/" + arr2dChannelThreads[i][1] );
    }
  }
const strPocketChannelThreadsNew = configs.getObject( "SelectConfC1" );  // NotRequired



//// == Data Retrieving / ワークフローデータの参照 ==
let numFilesSize = 0;
let filesAttach = null;
if( filesPocketAttach !== null ){
  filesAttach = engine.findData( filesPocketAttach );
    if (filesAttach !== null) {
        numFilesSize = filesAttach.size() - 0;
    }
}
if( strText === "" && numFilesSize === 0 && strUrls === "" ){
  throw new Error( "\n AutomatedTask ConfigError:" +
                   " Config {A1} {A2} {A3} are all empty \n" );
}


//// == Calculating / 演算 ==
/// create Channels
if ( strBoolCreateChannel === "true" ){
  for ( let i = 0; i < arr2dChannelThreads.length; i++ ){
    const strChannel = arr2dChannelThreads[i][0];
    if( isCreatableUserChannelName( strChannel ) ){
      try {
        feedService.createPublicChannel( strChannel );
        engine.log( " AutomatedTask: Channel created - " + strChannel );
      } catch(e) {
        engine.log( " AutomatedTask: Channel Creation " + strChannel + " - " + e.getMessage() );
      }
    }
  }
}


/// --- 1. prepare Collab-Chat Message
// --- 1.1. Construct Footer

// --- 1.1. Construct Footer
let strFooter = "";
if ( strBoolMarkdown === "true" ){
  strFooter  = '\n\n<span style="display: block; border-left: 3px solid #7cb342;';
  strFooter += ' padding: 2px 0 2px 10px; margin-top: 16px; font-size: 0.85em; color: #777;">';
  strFooter += '📢 <b>Crosspost:</b> [#' + arr2dChannelThreads[0][0] + "](@t)";
  if ( arr2dChannelThreads.length > 1 ){
    for ( let i = 1; i < arr2dChannelThreads.length; i++ ){
      if( arr2dChannelThreads[i][0] !== "" ){
        strFooter += " | [#" + arr2dChannelThreads[i][0] + "](@t)";
      }
    }
  }
  strFooter += '</span>';
} else {
  strFooter = "\n\n--\n📢 Crosspost: #" + arr2dChannelThreads[0][0];
  if ( arr2dChannelThreads.length > 1 ){
    for ( let i = 1; i < arr2dChannelThreads.length; i++ ){
      if( arr2dChannelThreads[i][0] !== "" ){
        strFooter += " | #" + arr2dChannelThreads[i][0];
      }
    }
  }
}

// --- 1.2. Truncate Body Text considering Footer length
const NUM_MAX_TOTAL_LENGTH = 8000;
let numMaxBodyLength = NUM_MAX_TOTAL_LENGTH - strFooter.length;
if ( numMaxBodyLength < 0 ) { numMaxBodyLength = 0; } // Safety check
let strPostText = strText;
if( strPostText.length > numMaxBodyLength ){
  engine.log(
    " AutomatedTask: Post text truncated. (Body:" + strPostText.length + 
    " + Footer:" + strFooter.length + " > " + NUM_MAX_TOTAL_LENGTH + ")"
  );
  strPostText = strPostText.substring( 0, numMaxBodyLength );
}



/// --- 2. Cross Post to all channels
let arrChannelThreadsForNext = [];

for ( let i = 0; i < arr2dChannelThreads.length; i++ ){
  if( arr2dChannelThreads[i][0] !== "" ){

    let chatMsg = feedService.begin(); // FeedServiceWrapper
    // --- 2.1. Set Message Body
    if ( strBoolMarkdown === "true" ){
      chatMsg = chatMsg.setMarkdown( strPostText + strFooter );
    } else {
      chatMsg = chatMsg.setMessage( strPostText + strFooter );
    }

    // --- 2.2. Set Attachments
    if( filesAttach !== null ){
      for( let j = 0; j < numFilesSize && j < 10; j++ ){
        chatMsg = chatMsg.attachFile( filesAttach.get(j) );
        engine.log(
          " AutomatedTask AttachedFile:" +
          " '" + filesAttach.get(j).getName()        + "'"      +
          " (" + filesAttach.get(j).getLength()      + " byte)" +
          " '" + filesAttach.get(j).getContentType() + "'"
        );
      }
    }
    if( strUrls !== "" ){
      const arrUrls = strUrls.split(/\r?\n/);
      for( let j = 0; j < arrUrls.length && j < 10 - numFilesSize; j++ ){
        if( arrUrls[j] !== "" ){
          chatMsg = chatMsg.attachLink( arrUrls[j] );
          engine.log(
            " AutomatedTask AttachedLink:" +
            " '" + arrUrls[j] + "'"
          );
        }
      }
    }

    // --- 2.3. Post Message
    if( parseInt(arr2dChannelThreads[i]?.[1], 10) > 0 ){
      try {
        engine.log( " AutomatedTask Collab-Chat Cross: Post as Thread Comment" );
        const numPostedMsgCross = chatMsg.setChannel( arr2dChannelThreads[i][0] )
                                  .setMessageId( parseInt(arr2dChannelThreads[i][1], 10) )
                                  .post() - 0; // Java Long to Javascript number
        engine.log( " AutomatedTask Collab-Chat Comment ID: "  + numPostedMsgCross );
        arrChannelThreadsForNext.push( arr2dChannelThreads[i][0] + "\t" + arr2dChannelThreads[i][1] );
      } catch(e) { // if error, channel already exists, etc
        engine.log( " AutomatedTask Warning Channel/Thread not found: "  + arr2dChannelThreads[i][0] +
                    "/" + arr2dChannelThreads[i][1] + " - " + e.toString() );
      }
    } else {
      try {
        engine.log( " AutomatedTask Collab-Chat Cross: Post as new Thread" );
        const numPostedMsgCross = chatMsg.setChannel( arr2dChannelThreads[i][0] )
                                  .post() - 0; // Java Long to Javascript number
        engine.log( " AutomatedTask Collab-Chat Message ID: "  + numPostedMsgCross );
        arrChannelThreadsForNext.push( arr2dChannelThreads[i][0] + "\t" + numPostedMsgCross );
      } catch(e) { // if error, channel already exists, etc
        engine.log( " AutomatedTask Warning Channel not found: "  + arr2dChannelThreads[i][0]  +
        " - " + e.toString() );
      }
    }
  }
}


//// == Data Updating / ワークフローデータへの代入 ==
if ( strPocketChannelThreadsNew !== null ){ 
  engine.setData( strPocketChannelThreadsNew, arrChannelThreadsForNext.join("\n") );
}

} //////// END "main()" /////////////////////////////////////////////////////////////////



// Parses TSV string as two-dimensional rectangular data matrix and creates a 2D array.
/**
 * TSV文字列を2次元配列へ変換し、すべての行を同じ列数に揃える。
 *
 * - 改行コードは LF / CRLF の両方に対応
 * - 空行は無視される
 * - 列数が不足している行は空文字 `""` で補完される
 * - 最大列数(MaxWidth)に合わせて矩形化される
 *
 * 例:
 * ```text
 * A\tB\tC
 * 1\t2
 * x
 * ```
 *
 * ↓
 *
 * ```javascript
 * [
 *   ["A", "B", "C"],
 *   ["1", "2", ""],
 *   ["x", "", ""]
 * ]
 * ```
 *
 * @param {string} strTsv
 *   TSV形式の文字列。
 *
 * @returns {string[][]}
 *   矩形化されたTSVデータ。
 *   各要素は行配列であり、すべて同じ列数になる。
 */
function parseAsRectangular( strTsv ){
  const arrTsv = strTsv.split(/\r?\n/);

  /// Get numMinWidth and numMaxWidth (blank lines are excluded)
  let numMinWidth   = Infinity; // cf. String-Type Max: 1 million
  let numMaxWidth   = 0;
  let numBlanklines = 0;
  for( let i = 0; i < arrTsv.length; i++ ){
    if( arrTsv[i] === "" ){ // Skip blank lines
      numBlanklines += 1;
      continue;
    }
    let arrCells = arrTsv[i].split("\t");
    if( numMinWidth > arrCells.length ){ numMinWidth = arrCells.length; }
    if( numMaxWidth < arrCells.length ){ numMaxWidth = arrCells.length; }
  }
  engine.log( " AutomatedTask TsvDataCheck:" + 
              " MinWidth:" + numMinWidth +
              " MaxWidth:" + numMaxWidth +
              " Lines:" + arrTsv.length +
              " (BlankLines:" + numBlanklines + ")" );

  /// Get numMinWidth and numMaxWidth (blank lines are excluded)
  let arr2dTsv      = [];
  for( let i = 0; i < arrTsv.length; i++ ){
    if( arrTsv[i] === "" ){ // Skip blank lines
      continue;
    }
    let arrTmp = [];
    let arrCells = arrTsv[i].split("\t");
    for( let j = 0; j < numMaxWidth; j++ ){
      if( j < arrCells.length ){
        arrTmp[j] = arrCells[j];
      }else{
        arrTmp[j] = "";
      }
    }
    arr2dTsv.push( arrTmp );
  }

  return arr2dTsv;
}



/**
 * ユーザ作成可能なチャネル名かどうかを判定する。
 *
 * 次の場合は `false` を返す:
 * - 空文字
 * - システム予約形式のチャネル名
 *   - `g` + 数字
 *   - `m` + 数字
 *   - `p` + 数字
 *
 * 例:
 * ```javascript
 * isCreatableUserChannelName("sales"); // true
 * isCreatableUserChannelName("g123");  // false
 * isCreatableUserChannelName("");      // false
 * ```
 *
 * @param {string} strChannel
 *   判定対象のチャネル名。
 *
 * @returns {boolean}
 *   ユーザが作成可能なチャネル名の場合は `true`、
 *   予約済みまたは禁止形式の場合は `false`。
 */
function isCreatableUserChannelName( strChannel ){
  if( strChannel === "" ){ return false; }
  if( /^[gmp]\d+$/.test( strChannel ) ){ return false; }
  return true;
}



/*
### Notes-en:
* When the case (process instance) reaches the automated step, the text specified in "A1: Post Text" is automatically posted. (CollabChat Post)
    * It is posted with the special contributor icon "Questetra".
    * It is posted either as Markdown text or as Plain text.
* Multiple posting destinations can be specified in "Channel-Thread TSV".
    * Set the channel name in the first column, and the thread ID (Message ID) in the second cell.
    * If no thread ID (Message ID) is set, the text will be posted as a new thread.
        * eg: `general`
        * eg: `g123`
    * If a thread ID (Message ID) is set, the text will be posted as a comment in the existing thread.
        * eg: `general	123456`
        * eg: `g123	234567`
* Specify the posting destination channel by its channel name, such as `general`. However:
    * For an organization channel, set it in an ID format such as `g1` or `g123`.
    * For an app channel, set it in an ID format such as `m1` or `m123`.
    * For a case channel, set it in an ID format such as `p1` or `p123`.
    * To post to the case channel of the current case itself, set `p#{processInstanceId}`.
        * It will be set to `p12345` at runtime.
    * The channel mark `#` is not needed in either case. (Use `g1` instead of `#g1`)
* Specify the posting destination thread by its thread ID, such as `123`.
    * The thread ID is the ID of the first post in the thread, that is, the Message ID.
    * It may be the Message ID obtained when a new thread was posted.
* The threads that were actually cross-posted can be saved as “Cross-Posted Channel-Thread TSV”.
    * To cross-post to the same set of threads in downstream steps, refer to the saved TSV as “Channel-Thread TSV”.

### APPENDIX-en:
* No authentication settings, such as credentials or OAuth2 authorization, are required when modeling.
    * There is no way to post on behalf of a specific user. (As of April 2026)
* The maximum number of file attachments and link URL attachments is 10 in total.
    * Files and link URLs exceeding the limit of 10 will not be attached.
* Set each link URL as `https://...`, one URL per line.
    * `^(https:\/\/\S+\r?\n)*https:\/\/\S+$`
* If the total length of the posted message, including the post body and footer, exceeds 8000 characters,
    * the post body will be automatically truncated so that the entire message is within 8000 characters.
* Posts made by the special contributor "Questetra" cannot be deleted through UI operations. (As of April 2026)
    * `POST /API/Feed/Message/{messageId}/delete`
    * `POST /API/Feed/Message/{messageId}/Comment/{commentId}/delete`

### Best Practice — Sales Opportunity Report Process:
* Create a `#SalesOpportunity` channel as a comprehensive discussion space for all sales opportunities.
    * Create it as a public user-created channel. This is recommended from the perspective of internal control and mutual checks.
* Prepare customer channels, such as `karasuma-oike-000`, as discussion spaces for each customers.
    * For example, "Lead Acquisition Process".
* Place the automated step "#Collab-Chat: Cross-Thread Post" immediately after the upstream human task "Report Scheduled Sales Opportunity" in the "Sales Opportunity Report Process".
    * Set the following two channels in the "Destination Channel-Thread" Config:
        * `SalesOpportunity`
        * `#{#q_customer_id}`
    * When a sales opportunity schedule is confirmed, the scheduled opportunity will be shared in Collab-Chat.
        * Data item: "Posted Channel-Thread" (`#{#q_channel_threads}`)
* Place the automated step "#Collab-Chat: Cross-Thread Post" immediately after the downstream human task "Report Sales Opportunity Result" in the "Sales Opportunity Report Process".
    * Set "Posted Channel-Thread" (`#{#q_channel_threads}`) in the "Destination Channel-Thread" Config.
        * `#{#q_channel_threads}`
            * `商談報告	123456`
            * `karasuma-oike-000	234567`
    * This posts the sales opportunity result as a comment to the same threads where the scheduled opportunity was originally shared.



### Notes-ja:
- 案件(ケース)が自動工程に到達した際に「A1:投稿テキスト」が自動投稿されます。 (CollabChat Post)
    - 特別な投稿者「Questetra」のアイコンで投稿されます。
    - Markdown テキストとして、もしくは、Plain テキストとして投稿されます。
- 投稿先は「チャンネル-スレッドTSV」で複数の指定が可能です。
    - 1列目にチャンネル名をセットし、2つ目のセルにスレッド(MessageID)をセットします。
    - スレッド(MessageID)がセットされていない場合は、新規スレッドとして投稿されます。
        - eg: `general`
        - eg: `g123`
    - スレッド(MessageID)がセットされている場合は、既存スレッドにコメントとして投稿されます。
        - eg: `general	123456`
        - eg: `g123	234567`
- 投稿先チャンネルは `general` のように「チャンネル名」で指定します。ただし、
    - 組織チャンネルの場合、`g1` や `g123` のようなID書式でセットします。
    - アプリチャンネルの場合、`m1` や `m123` のようなID書式でセットします。
    - ケースチャンネルの場合、`p1` や `p123` のようなID書式でセットします。
    - もし当該ケース自身のケースチャンネルに投稿されるように設定したい場合、`p#{processInstanceId}` とセットします。
        - 実行時に `p12345` とセットされます。
    - いずれもチャンネルマーク `#` は不要です。( `#g1` ではなく `g1` )
- 投稿先スレッドは `123` のように「スレッドID」で指定します。
    - スレッドIDは、スレッド先頭投稿のID(MessageID)を指定します。
    - 新規スレッド投稿の際に取得された MessageID
- 実際にクロス投稿されたスレッドは「クロス投稿したチャンネル-スレッドTSV」として保存可能です。
    - 下流にて同じスレッドセットにクロス投稿されるように設定したい場合、保存したTSVを「チャンネル-スレッドTSV」として参照させます。

### APPENDIX-ja:
- モデリング時の認証設定(認証情報や認可OAuth2)は不要です。
    - 特定ユーザの立場で投稿する方法はありません。(2026年4月現在)
- ファイル添付およびリンクURL添付の上限は「合計で10」です。
    - 10を超えるファイルおよびリンクURLは添付されません
- リンクURLは各行に `https://...` をセットします
    - `^(https:\/\/\S+\r?\n)*https:\/\/\S+$`
- 投稿されるメッセージ(投稿文+フッター)の長さが8000文字を超える場合、
    メッセージ全体が8000文字以内になるように投稿文部分が自動的に切り詰められます。
- 特別な投稿者「Questetra」の投稿は UI 操作では削除できません。(2026年4月現在)
    - `POST /API/Feed/Message/{messageId}/delete`
    - `POST /API/Feed/Message/{messageId}/Comment/{commentId}/delete`

### Best Practice (「商談報告プロセス」の場合):
- 全ての商談に関する包括的な議論の場として `#商談報告` チャンネルを作成しておく。
    - [ユーザ作成チャンネル](パブリック)として作成する。(内部統制/相互牽制の視点)
- 個別顧客に関する議論の場として顧客チャンネル( `karasuma-oike-000` など)が予め作成されるようにしておく。
    - 商談開始時のワークフローアプリ(例:「リード獲得プロセス」)で作成されるようにしておく。
- 「商談報告プロセス」内の上流ヒューマン工程「商談予定を報告」の直後に自動工程『#コラボチャット: クロススレッド投稿』を配置する。
    - "投稿先チャンネル-スレッド" の Config に2つのチャンネルをセットする。
        - `商談報告`
        - `#{#q_customer_id}`
    - 商談予定が決定された際に「商談予定」がコラボチャットで共有されるようになる。
        - データ項目「投稿したチャンネル-スレッド」( `#{#q_channel_threads}` )
            - `商談報告	123456`
            - `karasuma-oike-000	234567`
- 「商談報告プロセス」の下流ヒューマン工程「商談結果を報告」の直後に自動工程『#コラボチャット: クロススレッド投稿』を配置する。
    - "投稿先チャンネル-スレッド" の Config に「投稿したチャンネル-スレッド」( `#{#q_channel_threads}` )をセットする。
        - `#{#q_channel_threads}`
    - 商談結果が「商談予定」のスレッドとしてコラボチャット投稿されるようになる。

*/

Download

warning 自由改変可能な JavaScript (ECMAScript) コードです。いかなる保証もありません。
(アドオン自動工程のインストールは Professional editionでのみ可能です)

Notes

  • 案件(ケース)が自動工程に到達した際に「A1:投稿テキスト」が自動投稿されます。 (CollabChat Post)
    • 特別な投稿者「Questetra」のアイコンで投稿されます。
    • Markdown テキストとして、もしくは、Plain テキストとして投稿されます。
  • 投稿先は「チャンネル-スレッドTSV」で複数の指定が可能です。
    • 1列目にチャンネル名をセットし、2つ目のセルにスレッド(MessageID)をセットします。
    • スレッド(MessageID)がセットされていない場合は、新規スレッドとして投稿されます。
      • eg: general
      • eg: g123
    • スレッド(MessageID)がセットされている場合は、既存スレッドにコメントとして投稿されます。
      • eg: general 123456
      • eg: g123 234567
  • 投稿先チャンネルは general のように「チャンネル名」で指定します。ただし、
    • 組織チャンネルの場合、g1g123 のようなID書式でセットします。
    • アプリチャンネルの場合、m1m123 のようなID書式でセットします。
    • ケースチャンネルの場合、p1p123 のようなID書式でセットします。
    • もし当該ケース自身のケースチャンネルに投稿されるように設定したい場合、p#{processInstanceId} とセットします。
      • 実行時に p12345 とセットされます。
    • いずれもチャンネルマーク # は不要です。( #g1 ではなく g1
  • 投稿先スレッドは 123 のように「スレッドID」で指定します。
    • スレッドIDは、スレッド先頭投稿のID(MessageID)を指定します。
    • 新規スレッド投稿の際に取得された MessageID
  • 実際にクロス投稿されたスレッドは「クロス投稿したチャンネル-スレッドTSV」として保存可能です。
    • 下流にて同じスレッドセットにクロス投稿されるように設定したい場合、保存したTSVを「チャンネル-スレッドTSV」として参照させます。

Capture

Appendix

  • モデリング時の認証設定(認証情報や認可OAuth2)は不要です。
    • 特定ユーザの立場で投稿する方法はありません。(2026年4月現在)
  • ファイル添付およびリンクURL添付の上限は「合計で10」です。
    • 10を超えるファイルおよびリンクURLは添付されません
  • リンクURLは各行に https://... をセットします
    • ^(https:\/\/\S+\r?\n)*https:\/\/\S+$
  • 投稿されるメッセージ(投稿文+フッター)の長さが8000文字を超える場合、
    メッセージ全体が8000文字以内になるように投稿文部分が自動的に切り詰められます。
  • 特別な投稿者「Questetra」の投稿は UI 操作では削除できません。(2026年4月現在)
    • POST /API/Feed/Message/{messageId}/delete
    • POST /API/Feed/Message/{messageId}/Comment/{commentId}/delete

Best Practice (「商談報告プロセス」の場合):

  • 全ての商談に関する包括的な議論の場として #商談報告 チャンネルを作成しておく。
    • [ユーザ作成チャンネル](パブリック)として作成する。(内部統制/相互牽制の視点)
  • 個別顧客に関する議論の場として顧客チャンネル( karasuma-oike-000 など)が予め作成されるようにしておく。
    • 商談開始時のワークフローアプリ(例:「リード獲得プロセス」)で作成されるようにしておく。
  • 「商談報告プロセス」内の上流ヒューマン工程「商談予定を報告」の直後に自動工程『#コラボチャット: クロススレッド投稿』を配置する。
    • “投稿先チャンネル-スレッド” の Config に2つのチャンネルをセットする。
      • 商談報告
      • #{#q_customer_id}
    • 商談予定が決定された際に「商談予定」がコラボチャットで共有されるようになる。
      • データ項目「投稿したチャンネル-スレッド」( #{#q_channel_threads}
        • 商談報告 123456
        • karasuma-oike-000 234567
  • 「商談報告プロセス」の下流ヒューマン工程「商談結果を報告」の直後に自動工程『#コラボチャット: クロススレッド投稿』を配置する。
    • “投稿先チャンネル-スレッド” の Config に「投稿したチャンネル-スレッド」( #{#q_channel_threads} )をセットする。
      • #{#q_channel_threads}
    • 商談結果が「商談予定」のスレッドとしてコラボチャット投稿されるようになる。

See Also

上部へスクロール

Questetra Supportをもっと見る

今すぐ購読し、続きを読んで、すべてのアーカイブにアクセスしましょう。

続きを読む