添付ファイルのファイル名が文字化けする

下記のコードでgoogle driveにあるファイルを添付ファイルとしてレコードの追加を行おうとしています。
添付ファイルも、他のフィールドの値も登録はできているのですが、アップされた際に添付ファイル名の漢字部分が文字化けしてしまいます。どのように修正すればよいかアドバイスをいただければありがたいです。


「12 34_5678_000-000-0000_999-9999.pdf」
ー>「?? ??_???_000-000-0000_999-9999.pdf」

// メインの該当部分
var fileName = file.getName();
var fileID = file.getId();

let kintoneRecord = '[{';

let fileKey = uploadFile(fileID)
if ( fileKey !== null ) {
  kintoneRecord += Utilities.formatString('"添付ファイル":{"value": [%s]},',fileKey);
}


//呼び出される側
function uploadFile(fileId){
 const file = DriveApp.getFileById(fileId);
 const blob = file.getBlob();
  
 const url = 'https://' + subdomain + '.cybozu.com/k/v1/file.json';
 
 const pdfData = { 'file': blob };
 const fileHeader = {
   'X-Requested-With'  : 'XMLHttpRequest',
   'X-Cybozu-API-Token': appToken
 };
 const options = {
   'method': 'post',
   'headers': fileHeader,
   'payload': pdfData
 };
 return UrlFetchApp.fetch(url, options);
}

googleドライブ側のコードでってことですよね。
実際使用して確認してないのですが、エンコードしてからでどうですかね。

// ファイル名をUTF-8エンコード
var utf8FileName = Utilities.base64EncodeWebSafe(fileName, Utilities.Charset.UTF_8);

ありがとうございます。

おっしゃる通りGASで作ろうとしています。
google drive上のあるフォルダにpdfファイルがあれば、そのファイルをアップロードしたいのです。

uploadFileのところで、名前の問題は決まってしまっているという理解なのですが、uploadFileにはfileIDを渡していて、ファイル名を指定する部分が(表面上は)見当たりません。
教えていただいたコードをどの部分に入れ込めばよいか教えていただけませんか?

アドバイスをいただいて、わからぬまま(!)メインの部分を下記のように書き換えてみましたが、やはりだめでした。

var fileName = file.getName();
  var utf8FileName = Utilities.base64EncodeWebSafe(fileName, Utilities.Charset.UTF_8);
  var fileID = file.getId();

  let kintoneRecord = '[{';

  let fileKey = uploadFile(fileID)
  if ( fileKey !== null ) {
    kintoneRecord += Utilities.formatString('"添付ファイル":{"value": [%s], "name": "%s"},',fileKey, utf8FileName);
  }

upload関数の第2引数にencodeFileNameを与えて、

const pdfData = { 'file': blob, 'name': encodedFileName }; 

としてpostしてfileKey取得してでいかがでしょうか?

あと、記載時にコード全体像(大事な部分は書き換えて)があるといいと思います。file.NameのfieやKintoneRecord,subdomain,appTokenなど唐突に出てきているので。

pomoさん、たびたびありがとうございます。下記のように書き換えてみましたがやはりレコードの追加はできるものの、添付ファイル名は文字化けでした。

全体をアップします。Google driveのフォルダを監視して、PDFファイルがあればkintoneに追加します。ファイル名に氏名や会社名があるので、それをフィールドの値にして、添付ファイルとしてPDFファイルそのものも対象です。

追加が成功すれば違うフォルダにファイルを移動させて終了です。

よろしくお願いいたします。

const folderIdToWatch = 'watchwatch'; // Google Driveの監視するフォルダID
const targetFolderId = 'targettarget'; // 移動先のフォルダID
const subdomain = 'subdsubd';// サブドメイン名
const appToken = 'tokentokentoken';
const appID = '999';

function monitorDriveFolder() {

  var folder = DriveApp.getFolderById(folderIdToWatch);
  var files = folder.getFiles();

  while (files.hasNext()) {
    var file = files.next();
    if (file.getName().toLowerCase().endsWith(".pdf")) {
      processPDFFile(file);
    }
  }
}

function processPDFFile(file) {
  const apps = {
    YOUR_APPLICATION1: {appid: appID, name: '展_名刺リスト', token: appToken }
  };

  var fileName = file.getName();
  var fileID = file.getId();

  let kintoneRecord = '[{';

  let fileKey = uploadFile(fileID)
  if ( fileKey !== null ) {
    kintoneRecord += Utilities.formatString('"名刺PDF":{"value": [%s]},',fileKey);
  }

  var fileNameParts = fileName.replace(".pdf", "").split("_");
  
  kintoneRecord += Utilities.formatString('"お名前":{"value":"%s"},',fileNameParts[0].replace(/[\s ]/g, '').replace(/[\s ]/g, ''));
  kintoneRecord += Utilities.formatString('"会社名":{"value":"%s"},',fileNameParts[1]);
  kintoneRecord += Utilities.formatString('"お電話番号":{"value":"%s"},',fileNameParts[2]);
  kintoneRecord += Utilities.formatString('"郵便番号":{"value":"%s"}',fileNameParts[3]);
  kintoneRecord += '}]';

  Logger.log('Response JSON is "%s"', kintoneRecord);
  
  const manager = new KintoneManager.KintoneManager(subdomain + '.cybozu.com', apps); // ライブラリの初期化

  const records = JSON.parse(kintoneRecord); // レコードをJSON形式に変換

  // レコードを追加
  const createResponse = manager.create('YOUR_APPLICATION1', records);

  // ステータスコード 成功すれば200になる
  const code = createResponse.getResponseCode();
  Logger.log('Response code is "%s"', code);

  if (code === 200) {
    file.moveTo(DriveApp.getFolderById(targetFolderId));
  }
 }

function uploadFile(fileId){
 const file = DriveApp.getFileById(fileId);
 const blob = file.getBlob();
 var utf8FileName = Utilities.base64EncodeWebSafe(file.getName(), Utilities.Charset.UTF_8);
 const url = 'https://' + subdomain + '.cybozu.com/k/v1/file.json';

 const pdfData = { 'file': blob, 'name' : utf8FileName};
 const fileHeader = {
  'X-Requested-With': 'XMLHttpRequest',
  'X-Cybozu-API-Token': appToken
 };
 const options = {
  'method': 'post',
  'headers': fileHeader,
  'payload': pdfData
 };

 return UrlFetchApp.fetch(url, options);
}

@osawa さん
単純なエンコードで済む話かと思いましたが、だいぶ苦戦しました。すみません。以前VBAでkintoneにファイルアップロードする際に同様の文字化けで悩んだのを思い出しました・・・

payload部分がポイントなんですがboundary生成して送るあたりについて気になればお調べいただければと思います(私自身完全に理解できてると言えず、説明不足になりますので)

下記コードでフォルダ内の1つの日本語pdfをkintoneへアップしつつ、ドライブ別フォルダ移動は確認できました。
fileKeyの精査とレコード数分回すしてkintoneRecords変数への書き込みをしていないので一度テストしていただいたあと、追加してもらえればと思います。

※追記-編集
このままkintoneに上がったPDF開いたところ、ファイルが開けないので再度確認してみますね。

const folderIdToWatch = ''; // Google Driveの監視するフォルダID
const targetFolderId = ''; // 移動先のフォルダID
const subdomain = ''; // サブドメイン名
const appToken = '';
const appID = '';
const appName = 'test'; //アプリ名

function monitorDriveFolder() {
  var folder = DriveApp.getFolderById(folderIdToWatch);
  var files = folder.getFiles();

  while (files.hasNext()) {
    var file = files.next();
    if (file.getName().toLowerCase().endsWith(".pdf")) {
      processPDFFile(file);
    }
  }
}

function processPDFFile(file) {
  const apps = {
    YOUR_APPLICATION1: { appid: appID, name: appName, token: appToken },
  };

  var fileName = file.getName();
  var fileID = file.getId();
  let fileKey = uploadFile(fileID);

  var fileNameParts = fileName.replace(".pdf", "").split("_");

  let kintoneRecords = [
    {
      "名刺PDF": {
        "value": [
          {
            "fileKey": fileKey,
          },
        ],
      },
      "お名前": {
        "value": fileNameParts[0].replace(/[\s ]/g, '').replace(/[\s ]/g, ''),
      },
      "会社名": {
        "value": fileNameParts[1],
      },
      "お電話番号": {
        "value": fileNameParts[2],
      },
      "郵便番号": {
        "value": fileNameParts[3],
      },
    },
  ];

  const manager = new KintoneManager.KintoneManager(subdomain + '.cybozu.com', apps); // ライブラリの初期化
  const createResponse = manager.create('YOUR_APPLICATION1', kintoneRecords);

  const code = createResponse.getResponseCode();
  Logger.log(createResponse)
  Logger.log('Response code is "%s"', code);

  if (code === 200) {
     file.moveTo(DriveApp.getFolderById(targetFolderId));
  }
}

function uploadFile(fileId) {
  const file = DriveApp.getFileById(fileId);
  const blob = file.getBlob();
  const boundary = 'my_boundary'; // マルチパートデータの境界
  const url = 'https://' + subdomain + '.cybozu.com/k/v1/file.json';

   const bodyText =
    `--${boundary}\r\n
      Content-Disposition: form-data; name="file"; filename="${blob.getName()}"\r\n
      Content-Type: ${blob.getContentType()}\r\n\r\n`;

    const requestBody = Utilities.newBlob(bodyText).getBytes()
    .concat(blob.getBytes())
    .concat(Utilities.newBlob('\r\n--' + boundary + '--\r\n').getBytes());

  const headers = {
    'Content-Type': `multipart/form-data; boundary=${boundary}`,
    'X-Requested-With': 'XMLHttpRequest',
    'X-Cybozu-API-Token': appToken,
    'Content-Transfer-Encoding': 'base64',
    'Content-Disposition': `form-data; name="file"; filename*=${Utilities.base64EncodeWebSafe(file.getName(), Utilities.Charset.UTF_8)}`,
  };

  const options = {
    'method': 'post',
    'headers': headers,
    'payload': requestBody,
  };

  const response = UrlFetchApp.fetch(url, options);
  const responseData = response.getContentText();
  const jsonResponse = JSON.parse(responseData);
  Logger.log(jsonResponse);
  return jsonResponse.fileKey;
}

}

@pomo さん、ありがとうございます。

添付ファイル名の文字化けは解消、複数ファイルでもOKでした。
しかし、末尾に書いておられる通り、ファイルの中身が違うようで見ることができませんでした。

教えていただいたboundaryあたりのところは難しく、さらに参考ページはもっと難しく、と完全に能力越えを痛感してます。

なにか全面的に頼ってしまって申し訳ありません。近づいては来ているので試行錯誤をしばらく続けようと思います。

@osawa さん
長くなってしまうんで、上記投稿のコードを編集しました。
bodyTextとrequestBodyを追加し、payloadにrequestBodyを読んでます。(先人の情報参考にしました)
私もcontent-type周りも変更してみたもののうーんという感じでした。。

こちらで日本語ファイルでPDFも開くようになったので、ご確認いただけるとです。知識不足で変に助言してしまい申し訳ないばかりです。

Google Drive → kintoneにアップロードでお困りですか?
ざっくりしか読めてなくてゴメンナサイ!

手前味噌ですがお役に立てば・・・:eyes::sweat_drops:

3 Likes

@jurippe
あ!まさに参考にさせていただきました。(というよりrequestBody部分ままじゃんなんですけど)わかりやすかったです。本当にありがとうございました。

きゃーーー!!なんと!
ありがとうございます!!!!

2 Likes

@pomo さん、ありがとうございました。
@jurippe さん(ありがとうございます!)がご紹介くださったページを見ながら自分なりにやろうとしてましたが、まったく歯が立たなかったので、アドバイス(というよりすべて作っていただいた)がなければあきらめていました。本当にありがとうございました。
uploadFileの内容は自分なりに少し変えてみました。(無意味の可能性あり)

blob.getName() → file.getName()
${blob.getContentType()} → ${file.getMimeType()}
headerの
‘Content-Transfer-Encoding’: ‘base64’,
‘Content-Disposition’:
2行はなくても文字化けはしないようでした。

作っていただいたものを自分のもののように上げるのも気が引けるのですが、まとめとして、下に全体をあげておきます。本当にありがとうございました。

// GASエディターにライブラリ"KintoneManager"の追加が必要。
const targetExtension = '.pdf'                                // 監視対象の拡張子
const folderIdToWatch = 'watchwatchwatchwatchwatchwatchwat';  // Google Driveの監視するフォルダID
const targetFolderId = 'targettargettargettargettargettar';   // Google Driveの移動先のフォルダID
const subdomain = 'subdomain';                                 // kintone サブドメイン名
const appToken = 'tokentokentokentokentokentokentokentoken';  // kintone アプリのAPIトークン
const appID = '999';                                          // アプリID
const appName = 'アプリ名';                               // アプリ名

// Google Apps Script関数: Driveフォルダを監視して対象ファイルを処理する
function monitorDriveFolder() {
  var folder = DriveApp.getFolderById(folderIdToWatch); // 監視するフォルダを取得
  var files = folder.getFiles(); // フォルダ内のファイルを取得

  while (files.hasNext()) {
    var file = files.next();
    if (file.getName().toLowerCase().endsWith(targetExtension)) { // PDFファイルの場合に処理を実行
      processPDFFile(file);
    }
  }
}

// PDFファイルを処理する関数
function processPDFFile(file) {
  // キントーンに追加するデータを作成
  let records = '[{';

  let fileKey = uploadFile(file); // ファイルをアップロードしてファイルキーを取得
  if (fileKey !== null) {
    records += Utilities.formatString('"添付フィールド":{"value": [%s]},', fileKey);
  }

  // 添付フィールド以外のフィールドのIDとValueを追加
  const fieldIDs = ['field1','field2','field3','field4'];
  const fieldValues = file.getName().replace(".pdf", "").replace(/[\s ]/g, '').replace(/[\s ]/g, '').split("_");

  for ( var i = 0; i < fieldIDs.length; i++) {
    records += Utilities.formatString('"%s":{"value":"%s"},', fieldIDs[i],fieldValues[i]);
  };
  
  records = records.slice(0, -1) + '}]';    // 最後の','を削除してカッコを閉じる

  let returnCode = addRecord(records);  // キントーンにレコードを追加

  if (returnCode === 200) {     // ステータスコードが200なら、ファイルを移動
    file.moveTo(DriveApp.getFolderById(targetFolderId));  
  }
}

// ファイルをレコード追加前にあらかじめアップロードする関数
function uploadFile(file) {
  const blob = file.getBlob(); // ファイルをBlob形式に変換

  const boundary = 'my_boundary'; // マルチパートフォームの境界線

  const url = 'https://' + subdomain + '.cybozu.com/k/v1/file.json'; // アップロード先のURL

  // リクエストボディのテキストを作成
  const bodyText =
    `--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="${file.getName()}"\r\nContent-Type: ${file.getMimeType()}\r\n\r\n`;

  // リクエストボディをバイトの配列に変換
  const requestBody = Utilities.newBlob(bodyText).getBytes()
    .concat(blob.getBytes()) // ファイルのバイトデータを追加
    .concat(Utilities.newBlob('\r\n--' + boundary + '--\r\n').getBytes()); // マルチパートフォームの終了を示すバイトデータを追加

  const headers = {
    'Content-Type': `multipart/form-data; boundary=${boundary}`, // マルチパートフォームのヘッダー
    'X-Requested-With': 'XMLHttpRequest', // XMLHttpRequestのヘッダー
    'X-Cybozu-API-Token': appToken, // サブドメインに関連するAPIトークン
  };

  const options = {
    'method': 'post', // POSTリクエスト
    'headers': headers, // リクエストヘッダー
    'payload': requestBody, // リクエストボディ
  };

  const response = UrlFetchApp.fetch(url, options); // ファイルのアップロードを実行
  Logger.log(response); // レスポンスをログに記録
  return response; // レスポンスを返す (ファイルキーではなく、実際のレスポンスが返されていることに注意)
}

// キントーンにレコードを追加
function addRecord(records) {
  const apps = { YOUR_APPLICATION1: { appid: appID, name: appName, token: appToken } }; // kintoneアプリを指定

  const manager = new KintoneManager.KintoneManager(subdomain + '.cybozu.com', apps); // kintoneマネージャの初期化

  const kintoneRecords = JSON.parse(records); // レコードをJSON形式に変換
  Logger.log('Response JSON is "%s"', records);

  // レコードをKintoneに追加
  const createResponse = manager.create('YOUR_APPLICATION1', kintoneRecords);

  const code = createResponse.getResponseCode();
  Logger.log('Response code is "%s"', code);
  return code;  // ステータスコードを返す
}

このトピックはベストアンサーに選ばれた返信から 3 日が経過したので自動的にクローズされました。新たに返信することはできません。