Skip to content

Instantly share code, notes, and snippets.

@junjanjon
Last active July 8, 2025 06:17
Show Gist options
  • Select an option

  • Save junjanjon/8ce62c1cb90e817b1247de672bf93116 to your computer and use it in GitHub Desktop.

Select an option

Save junjanjon/8ce62c1cb90e817b1247de672bf93116 to your computer and use it in GitHub Desktop.
コンソールを使って指定のチャットワーク部屋のメッセージをすべて取得する。

使い方記事: https://qiita.com/JunkiHiroi/items/4fb577e8a8912a7ffada

使い方

以下を書き換えればどの部屋でも利用できます。

// 取得したいチャットルームのルームID。先頭のridを除いた数字のみ。
// [] を指定すると、アクセスできる全ルームの情報をダウンロードします。
var TARGET_ROOM_IDS = []
// var TARGET_ROOM_IDS = [123456789, 987654321]

// 取得したくないチャットルームのルームID。
// ログ用ルーム等のダウンロードを除外するのに利用します
var EXCEPT_ROOM_IDS = []
// var EXCEPT_ROOM_IDS = [123456789]

// 通信間隔[ミリ秒]。ダウンロードするデータの規模が大きい場合は、1000(1秒)ぐらいに指定してください。
var INTERVAL_TIME = 300;

// チャットワークのホスト. 企業用の場合は適宜修正ください
var HOST_URL = "www.chatwork.com"
// var HOST_URL = "kcw.kddi.ne.jp"

どうやって使うの?

Google Chrome のデベロッパーツールの中の機能、コンソールを利用します。ほかのブラウザにも同等の機能があるので読み替えてください。

image1.png

image2.png

20件のメッセージごとにINTERVAL_TIME[ms]待ちます。

どんな結果が来るの?

ダウンロードディレクトリに {部屋ID}_messages.json がダウンロードされます。

ダウンロードディレクトリに添付ファイルが {部屋ID}_{ファイル名} でダウンロードされます。

_messages.json の内容について

最上位が配列のデータです。配列内の各要素がメッセージです。

普通のテキストメッセージの例を以下に表します。

[
  ...,
  {
    "id": "123451234512345",
    # ユーザID
    "aid": 1234567,
    # メッセージ内容
    "msg": "Hello World",
    # メッセージタイプ: テキストメッセージ、部屋作成、部屋参加、アップロードなどがある
    "type": "text_message_type",
    "tm": 1234567,
    "utm": 0,
    # おそらく部屋内のメッセージのインクリメントの値
    "index": 12,
    # リアクション: 絵文字とリアクションしたユーザIDが記録される
    "reactions": [],
    # 投稿時間
    "datetime": "2024/12/12 12:12:12",
    # ユーザ名
    "aid_name": "田中太郎"
  },

Special Thanks

https://qiita.com/JunkiHiroi/items/4fb577e8a8912a7ffada#comment-fd37f83676fae9543a2d

// Chrome 132 相当で動作確認
// 新しめのブラウザでないと動作しません
// 取得したいチャットルームのルームID。先頭のridを除いた数字のみ。
// [] を指定すると、アクセスできる全ルームの情報をダウンロードします。
var TARGET_ROOM_IDS = []
// var TARGET_ROOM_IDS = [123456789, 987654321]
// 取得したくないチャットルームのルームID。
// ログ用ルーム等のダウンロードを除外するのに利用します
var EXCEPT_ROOM_IDS = []
// var EXCEPT_ROOM_IDS = [123456789]
// 通信間隔[ミリ秒]。ダウンロードするデータの規模が大きい場合は、1000(1秒)ぐらいに指定してください。
var INTERVAL_TIME = 300;
// チャットワークのホスト. 企業用の場合は適宜修正ください
var HOST_URL = "www.chatwork.com"
// var HOST_URL = "kcw.kddi.ne.jp"
// カスタマイズ1:各メッセージデータにYYYY/MM/DD HH:MM:SS形式の時刻を追加する
var APPEND_DATE = true
// カスタマイズ2:各メッセージデータに、発言者のユーザー名を追加する(標準ではアカウントIDのみ)
var APPEND_USERNAME = true
// カスタマイズ3:リアクション情報を削除する(不要な人もいるようなので)
var DELETE_REACTIONS = false
// カスタマイズ4: 添付ファイルをダウンロードする
var DOWNLOAD_ATTACHMENTS = true
//====================================
// ここ以降は修正不要
var debug = false
var token = ACCESS_TOKEN
var myid = MYID
if(debug === false){
console.debug = () => {}
}
async function sleep(ms){
return new Promise(resolve => setTimeout(resolve, ms));
}
async function do_fetch({url, formData}){
const _formData = new FormData();
Object.entries(formData).forEach(([key, value]) => {
_formData.append(key, value)
})
const resp = await fetch(url, {
method: 'POST',
mode: 'cors',
cache: 'no-cache',
credentials: 'same-origin',
redirect: 'follow',
referrerPolicy: 'same-origin',
body: _formData,
})
return resp
}
async function saveAs(filename, content){
console.debug("START saveAs", filename, content)
let blob
if(typeof content === 'string'){
console.debug("content is String")
blob = new Blob([content], {
type: "application/force-download"
})
} else if(content instanceof Blob){
console.debug("content is Blob")
// type変更のために詰め替えることで、タブを開かせずに確実にダウンロードさせる
blob = new Blob([ content ], {
type: "application/force-download"
})
} else {
console.debug("content is object")
blob = new Blob([ JSON.stringify(content, null, 2) ], {
type: "application/force-download"
})
}
console.debug(" download blob", blob)
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.dataType = "binary";
link.download = filename
link.click()
link.remove()
setTimeout(() => {
URL.revokeObjectURL(url);
}, 1E4);
}
async function init_load() {
console.debug("START init_load")
const url = `https://${HOST_URL}/gateway/init_load.php?myid=${myid}&_v=1.80a&_av=5&ln=en&rid=0&with_unconnected_in_organization=1`
const resp = await do_fetch({
url: url,
formData: {
'pdata': JSON.stringify({
_t: token,
})
},
})
return resp.json()
}
async function get_account_info(aids){
console.debug("START get_account_info", aids)
const url = `https://${HOST_URL}/gateway/get_account_info.php?myid=${myid}&_v=1.80a&_av=5&ln=en&get_private_data=0`
const resp = await do_fetch({
url: url,
formData: {
'pdata': JSON.stringify({
"aid": aids,
"_t":token,
})
},
})
return resp.json()
}
async function load_chat(rid){
console.debug("START load_chat", rid)
const url = `https://${HOST_URL}/gateway/load_chat.php?myid=${myid}&_v=1.80a&_av=5&ln=en&room_id=${rid}&last_chat_id=0&unread_num=0&bookmark=1&file=1&desc=1`
const resp = await do_fetch({
url: url,
formData: {
'pdata': JSON.stringify({
"load_file_version":"2",
"_t": token,
})
},
})
return resp.json()
}
// first_chat_id よりも古い(chat_idが小さい)メッセージを取得する(first_chat_idは含まない)
// first_chat_id == 0 のときはidが昇順で、そうでない場合はidが降順で返ってくる
// 1回のリクエストで40メッセージまで
async function load_old_chat(rid, first_chat_id){
console.debug("START load_old_chat", rid, first_chat_id)
const url = `https://${HOST_URL}/gateway/load_old_chat.php?myid=${myid}&_v=1.80a&_av=5&ln=en&room_id=${rid}&first_chat_id=${first_chat_id}`
const resp = await do_fetch({
url: url,
formData: {
'pdata': JSON.stringify({
_t: token,
})
},
})
return resp.json()
}
async function get_attachment_as_blob(file_id){
console.info("START get_attachment_as_blob", file_id)
const resp = await fetch(`https://${HOST_URL}/gateway/download_file.php?bin=1&file_id=${file_id}&preview=0`)
return resp.blob()
}
async function get_messages(room_id) {
console.info("START get_messages", room_id)
const sort_by_id = (a,b) => { return Number(a.id) - Number(b.id) } // 昇順
let messages = []
let oldest_msg_id = 0
do {
console.info(" DO-WHILE-LOOP oldest_msg_id: ", oldest_msg_id)
const load_old_chat_json = await load_old_chat(room_id, oldest_msg_id)
load_old_chat_json.result.chat_list.sort(sort_by_id)
messages.unshift(...load_old_chat_json.result.chat_list)
oldest_msg_id = load_old_chat_json.result.chat_list?.[0]?.id
await sleep(INTERVAL_TIME)
} while(oldest_msg_id !== undefined)
return messages
}
async function customize_messages(messages, aids){
const yyyymmddhhmmss = new Intl.DateTimeFormat(
undefined,
{
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
fractionalSecondDigits: undefined,
}
)
for(const message of messages){
if(APPEND_DATE){
message.datetime = yyyymmddhhmmss.format(new Date(message.tm * 1000))
}
if(APPEND_USERNAME){
message.aid_name = aids.result.account_dat[message.aid].name || "ユーザー名情報なし"
}
if(DELETE_REACTIONS){
delete message.reactions
}
}
}
async function downloadChatRoom(room_id){
console.debug("START downloadChatRoom", room_id)
// チャットルーム情報を取得
{
const load_chat_json = await load_chat(room_id)
await saveAs(`${room_id}_load_chat.json`, load_chat_json)
}
{
// 全メッセージを取得
console.debug("get_messages")
const messages = await get_messages(room_id)
// チャット内で発言したすべてのメンバーの情報を取得・保存
console.debug("get_account_info")
const aids = messages
.reduce((prev, message) => {
return prev.add(message.aid)
}, new Set())
const account_info_json = await get_account_info([...aids])
await saveAs(`${room_id}_account_info.json`, account_info_json)
// メッセージのカスタマイズ(日付時刻付与など)
customize_messages(messages, account_info_json)
console.debug(messages)
// メッセージの保存
await saveAs(`${room_id}_messages.json`, messages)
}
// 全添付ファイルをダウンロード
if (DOWNLOAD_ATTACHMENTS) {
console.debug("get_attachment")
const load_chat_json = await load_chat(room_id)
await saveAs(`${room_id}_load_chat.json`, load_chat_json)
for(const file of load_chat_json.result?.file_list){
const file_id = file.id
const file_name = `${room_id}_${file_id}_${file.fn}`
await saveAs(file_name, await get_attachment_as_blob(file_id))
await sleep(INTERVAL_TIME)
}
}
}
async function downloadChatRooms(){
console.debug("START downloadChatRooms")
const init_load_json = await init_load()
await saveAs("init_load.json", init_load_json)
for(const [room_id, room_obj] of Object.entries(init_load_json.result.room_dat)){
console.debug(1, "FOR-LOOP downloadChatRooms: ", room_id, room_obj)
const room_name = room_obj?.n
|| Object.entries(init_load_json.result?.contact_dat)?.filter(([k,v]) => v.rid == room_id)?.[0]?.[1]?.name
|| "ルーム名なし"
if(
(EXCEPT_ROOM_IDS.includes(Number(room_id))) ||
(TARGET_ROOM_IDS.length !== 0 && !TARGET_ROOM_IDS.includes(Number(room_id)))
){
console.log("skip: ", room_id, room_name)
continue
}
console.log("downloading: ", room_id, room_name)
// わかりやすさのため、ルーム名の空ファイルを作成
await saveAs(`${room_id}_${room_name}.txt`, " ")
await downloadChatRoom(room_id)
}
}
await downloadChatRooms()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment