1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
|
/*
* download record comments as CSV sample program
* Copyright (c) 2025 Cybozu
*
* Licensed under the MIT License
* https://opensource.org/license/mit/
*/
(() => {
'use strict';
// エスケープ
const escapeStr = (value) => {
return '"' + (value ? value.replace(/"/g, '""') : '') + '"';
};
// CSVファイルをダウンロード
const downloadCSV = (csv) => {
const csvBuffer = csv.map((e) => {
return e.join(',');
}).join('\r\n');
const bom = new Uint8Array([0xEF, 0xBB, 0xBF]);
const blob = new Blob([bom, csvBuffer], {type: 'text/csv'});
const url = (window.URL || window.webkitURL).createObjectURL(blob);
// ファイル名:アプリ番号_comments.csv
const appId = kintone.app.getId();
const fileName = `${appId}_comments.csv`;
const link = document.createElement('a');
link.id = 'csvDownLoad';
const e = new MouseEvent('click', {view: window, bubbles: true, cancelable: true});
link.download = fileName;
link.href = url;
link.dispatchEvent(e);
// メモリリーク防止のためURLを解放
(window.URL || window.webkitURL).revokeObjectURL(url);
};
// レコード一覧を取得する
const fetchRecords = async (appId, opt_offset, opt_limit, opt_records) => {
const offset = opt_offset || 0;
const limit = opt_limit || 100;
let allRecords = opt_records || [];
const params = {app: appId, query: `order by $id asc limit ${limit} offset ${offset}`};
try {
const resp = await kintone.api(kintone.api.url('/k/v1/records', true), 'GET', params);
allRecords = allRecords.concat(resp.records);
if (resp.records.length === limit) {
return await fetchRecords(appId, offset + limit, limit, allRecords);
}
return allRecords;
} catch (error) {
console.error('レコード取得エラー:', error);
throw error;
}
};
// 単一レコードのコメントを処理してCSV行を作成
const processCommentsForRecord = async (recordId) => {
const comments = [];
let offset = 0;
let hasMore = true;
while (hasMore) {
try {
const params = {
app: kintone.app.getId(),
record: recordId,
offset: offset
};
const resp = await kintone.api(
kintone.api.url('/k/v1/record/comments', true), 'GET', params
);
// CSVデータの作成
for (const comment of resp.comments) {
const csvRow = createCommentCsvRow(recordId, comment);
comments.push(csvRow);
}
hasMore = resp.older;
offset += 10; // デフォルトで10件ずつ取得されるため、10を加算
// API制限を考慮した待機時間(必須ではないが、大量処理の場合、このコードを入れるのをおすすめ)
if (hasMore) {
await new Promise(resolve => setTimeout(resolve, 100));
}
} catch (error) {
console.error(`レコードID ${recordId} のコメント取得エラー:`, error);
// エラーが発生しても他のレコードの処理は継続
hasMore = false;
}
}
return comments;
};
// コメントオブジェクトからCSV行を作成
const createCommentCsvRow = (recordId, comment) => {
const row = [];
const mentionsCodes = [];
const mentionsTypes = [];
// mentionsが存在し、かつ配列の場合のみ処理
if (Array.isArray(comment.mentions) && comment.mentions.length > 0) {
for (const mention of comment.mentions) {
if (mention) {
mentionsCodes.push(mention.code || '');
mentionsTypes.push(mention.type || '');
}
}
}
row.push(escapeStr(recordId)); // レコードID
row.push(escapeStr(comment.id)); // コメントID
row.push(escapeStr(comment.text)); // コメント内容
row.push(escapeStr(comment.createdAt)); // 投稿日時
row.push(escapeStr(comment.creator?.code)); // 投稿者ログイン名
row.push(escapeStr(comment.creator?.name)); // 投稿者表示名
row.push(escapeStr(mentionsCodes.join(','))); // メンション宛先
row.push(escapeStr(mentionsTypes.join(','))); // メンションタイプ
return row;
};
// レコード一覧からコメント情報を取得する
const getCommentCsv = async (records) => {
const allComments = [];
// 進捗表示用
const totalRecords = records.length;
let processedRecords = 0;
for (const record of records) {
const recordId = record.$id.value;
// 進捗表示を更新
processedRecords++;
console.log(`処理中: ${processedRecords}/${totalRecords} レコード`);
// 単一レコードのコメントを処理
const recordComments = await processCommentsForRecord(recordId);
allComments.push(...recordComments);
// レコード間の待機時間(必須ではないが、大量処理の場合、このコードを入れるのをおすすめ)
await new Promise(resolve => setTimeout(resolve, 50));
}
return allComments;
};
// コメント一覧のCSVファイルを作成
const createCSVData = async (records) => {
try {
const comments = await getCommentCsv(records);
const commentsCSV = [];
// CSVファイルの列名
const columnRow = ['レコードID', 'コメントID', 'コメント内容',
'投稿日時', '投稿者ログイン名', '投稿者表示名',
'メンション宛先', 'メンションタイプ'];
if (comments.length === 0) {
alert('コメントが登録されていません');
return;
}
commentsCSV.push(columnRow);
for (const comment of comments) {
commentsCSV.push(comment);
}
// BOM付でダウンロード
downloadCSV(commentsCSV);
} catch (error) {
console.error('CSV作成エラー:', error);
alert('コメントの取得に失敗しました。');
}
};
// レコード一覧画面
kintone.events.on(['app.record.index.show'], (event) => {
// 増殖バグを防ぐ
if (document.getElementById('download-comment-csv') !== null) {
return;
}
// ヘッダの要素にボタンを作成
const headerElement = kintone.app.getHeaderMenuSpaceElement();
const csvButton = document.createElement('button');
csvButton.id = 'download-comment-csv';
csvButton.innerText = 'コメントをCSVでダウンロード';
csvButton.onclick = async function() {
// ボタンを無効化して重複実行を防止
csvButton.disabled = true;
csvButton.innerText = 'データ取得中...';
try {
const records = await fetchRecords(kintone.app.getId());
if (records.length === 0) {
alert('レコードが登録されていません');
return;
}
csvButton.innerText = 'コメント処理中...';
// CSVデータを作成
await createCSVData(records);
} catch (error) {
console.error('処理エラー:', error);
alert('処理中にエラーが発生しました。');
} finally {
// ボタンを有効化
csvButton.disabled = false;
csvButton.innerText = 'コメントをCSVでダウンロード';
}
};
headerElement.appendChild(csvButton);
});
})();
|