自動マッチング
買いニーズと売り物件を物件種別・エリア・価格帯で照合します
80点以上
「マッチング実行」ボタンを押すか
データを同期すると照合結果が表示されます
買主ニーズリスト
登録されている買いニーズ一覧
0件
| 氏名 / 会社 ⇅ | 連絡先 | 希望エリア ⇅ | 予算 ⇅ | 物件種別 ⇅ | 希望面積 / 条件 |
|---|
売り物件リスト
登録されている売り物件一覧
0件
| 登録者 / 会社 ⇅ | 物件種別 ⇅ | 物件名 / 所在地 ⇅ | 価格 ⇅ | 土地面積 | 建物面積 | 築年 ⇅ | 備考 | 資料 |
|---|
成約リスト
成立した取引の記録
成約データはまだありません
連携・通知 設定
Google スプレッドシート連携・LINE通知・メール通知を設定します
🔗 Google スプレッドシート連携(Google Apps Script)
GAS WEB APP URL
自動同期間隔(分)
① スプレッドシートを開き「拡張機能 → Apps Script」を選択
② 以下のコードを全て貼り付けて保存 → 「デプロイ → 新しいデプロイ」→ 種類: ウェブアプリ → アクセス: 全員
③ 発行されたURLを上の「GAS WEB APP URL」に設定 → スクリプトプロパティに LINE_CHANNEL_ACCESS_TOKEN と LINE_RECIPIENT_ID を追加
Code.gs — Google Apps Script
// =====================================================
// JCI大阪不動産クラブ — GAS連携スクリプト (LINE Messaging API版)
// 【スクリプトプロパティに設定する値】
// LINE_CHANNEL_ACCESS_TOKEN : チャンネルアクセストークン(長期)
// LINE_RECIPIENT_ID : 送信先グループID(下記で自動取得)
// SCORE_THRESHOLD : 通知スコア閾値(例: 60)
// =====================================================
function doGet(e) {
try {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const buyerSheet = ss.getSheetByName('買主リスト');
const sellerSheet = ss.getSheetByName('売主リスト');
const buyers = buyerSheet ? parseSheet(buyerSheet, 'buyer') : [];
const sellers = sellerSheet ? parseSheet(sellerSheet, 'seller') : [];
return jsonOut({ buyers, sellers,
updated: Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy/MM/dd HH:mm:ss'),
counts: { buyers: buyers.length, sellers: sellers.length } });
} catch(err) { return jsonOut({ error: err.message }); }
}
function doPost(e) {
try {
const body = JSON.parse(e.postData.contents);
// LINEウェブフックイベント → グループIDを自動保存
if (body.events) { captureRecipientId(body.events); return jsonOut({ ok: true }); }
// HTMLからの通知リクエスト → LINE送信
if (body.message) return jsonOut(pushLineMessage(body.message));
return jsonOut({ error: '不明なリクエスト' });
} catch(err) { return jsonOut({ success: false, error: err.message }); }
}
// LINE Messaging API でプッシュ送信
function pushLineMessage(text) {
const props = PropertiesService.getScriptProperties();
const token = props.getProperty('LINE_CHANNEL_ACCESS_TOKEN');
const toId = props.getProperty('LINE_RECIPIENT_ID');
if (!token) return { success: false, error: 'LINE_CHANNEL_ACCESS_TOKEN未設定' };
if (!toId) return { success: false, error: 'LINE_RECIPIENT_ID未設定(グループにボットを招待してメッセージを送ってください)' };
const res = UrlFetchApp.fetch('https://api.line.me/v2/bot/message/push', {
method: 'post', contentType: 'application/json',
headers: { Authorization: 'Bearer ' + token },
payload: JSON.stringify({ to: toId, messages: [{ type: 'text', text: text }] }),
muteHttpExceptions: true
});
const status = res.getResponseCode();
return status === 200 ? { success: true } : { success: false, lineStatus: status, lineError: res.getContentText() };
}
// ウェブフックでグループIDを自動取得・保存
function captureRecipientId(events) {
const props = PropertiesService.getScriptProperties();
if (props.getProperty('LINE_RECIPIENT_ID')) return;
for (const ev of events) {
const id = ev.source && (ev.source.groupId || ev.source.roomId || ev.source.userId);
if (id) { props.setProperty('LINE_RECIPIENT_ID', id); break; }
}
}
// スプレッドシート変更時トリガー(トリガー登録: onSheetEdit / 変更時)
function onSheetEdit() {
const props = PropertiesService.getScriptProperties();
const threshold = parseInt(props.getProperty('SCORE_THRESHOLD') || '60');
if (!props.getProperty('LINE_CHANNEL_ACCESS_TOKEN')) return;
const ss = SpreadsheetApp.getActiveSpreadsheet();
const buyers = parseSheet(ss.getSheetByName('買主リスト'), 'buyer');
const sellers = parseSheet(ss.getSheetByName('売主リスト'), 'seller');
const highs = [];
for (const b of buyers) for (const s of sellers) {
const sc = quickScore(b, s);
if (sc >= threshold) highs.push({ buyer: b, seller: s, score: sc });
}
if (!highs.length) return;
// 重複排除
const key = 'sent_pairs_cache';
const sent = new Set(JSON.parse(props.getProperty(key) || '[]'));
const newHighs = highs.filter(r => !sent.has(pairKey(r)));
if (!newHighs.length) return;
const msg = '【JCI大阪不動産クラブ】\n📊 スプレッドシート更新\n高スコアマッチング ' + newHighs.length + '件検出!\n\n' +
newHighs.slice(0,5).map(r => '●' + r.score + '点\n買主: ' + r.buyer.company +
'\nエリア: ' + r.buyer.area + '\n売主: ' + r.seller.company +
'\n物件: ' + r.seller.location + ' / ' + r.seller.price).join('\n\n') +
(newHighs.length > 5 ? '\n\n…他' + (newHighs.length - 5) + '件' : '');
pushLineMessage(msg);
newHighs.forEach(r => sent.add(pairKey(r)));
props.setProperty(key, JSON.stringify([...sent].slice(-500)));
}
function pairKey(r) {
return [r.buyer.company,r.buyer.area,r.buyer.type,r.seller.company,r.seller.location].join('|');
}
function parseSheet(sheet, mode) {
if (!sheet) return [];
const rows = sheet.getDataRange().getValues();
if (rows.length < 2) return [];
// ヘッダー行を自動検出(1〜3行目を候補として検索)
const fmt = v => !v && v !== 0 ? '' : v instanceof Date
? Utilities.formatDate(v,'Asia/Tokyo','yyyy/MM/dd') : String(v).trim();
const KEY_BUYER = ['日付','氏名','会社','連絡','エリア','予算','種別','面積','その他','備考','条件','希望'];
const KEY_SELLER = ['日付','氏名','会社','連絡','態様','種別','物件','所在','価格','土地','建物','築','その他','備考'];
const keys = mode === 'buyer' ? KEY_BUYER : KEY_SELLER;
let headerRow = 0;
let bestScore = 0;
for (let i = 0; i < Math.min(4, rows.length); i++) {
const score = rows[i].filter(h => keys.some(k => String(h).includes(k))).length;
if (score > bestScore) { bestScore = score; headerRow = i; }
}
const headers = rows[headerRow].map(h => String(h).trim());
const col = (...candidates) => {
for (const k of candidates) {
const idx = headers.findIndex(h => h.includes(k));
if (idx >= 0) return idx;
}
return -1;
};
const res = [];
if (mode === 'buyer') {
const cD=col('日付','Date'), cN=col('氏名','名前','Name'), cC=col('会社','Company'),
cT=col('連絡','電話','Tel','Mail'), cA=col('エリア','地域','Area','所在','場所'),
cB=col('予算','Budget','金額'), cTy=col('種別','種類','Type'),
cS=col('面積','坪','㎡','Size'), cNo=col('その他','備考','条件','Note','希望');
for (let i=headerRow+1; i=0?r[cA]:''), nm=fmt(r[cN]);
if(!co && !ar && !nm) continue;
res.push({date:fmt(r[cD]),name:nm,company:co,
contact:fmt(cT>=0?r[cT]:''),area:ar,
budget:fmt(cB>=0?r[cB]:''),type:fmt(cTy>=0?r[cTy]:''),
size:fmt(cS>=0?r[cS]:''),note:fmt(cNo>=0?r[cNo]:'')});
}
} else {
const cD=col('日付','Date'), cN=col('氏名','名前','Name'), cC=col('会社','Company'),
cT=col('連絡','電話','Tel','Mail'), cR=col('態様','役割','Role'),
cTy=col('種別','種類','Type'), cP=col('物件名','物件','Property'),
cL=col('所在','住所','地','Location'), cPr=col('価格','売価','Price','金額'),
cLa=col('土地','地積','Land'), cBl=col('建物','延床','Building'),
cBu=col('築年','築','Built'), cNo=col('その他','備考','Note');
for (let i=headerRow+1; i=0?r[cL]:''), nm=fmt(r[cN]);
if(!co && !lo && !nm) continue;
res.push({date:fmt(r[cD]),name:nm,company:co,
contact:fmt(cT>=0?r[cT]:''),role:fmt(cR>=0?r[cR]:''),
type:fmt(cTy>=0?r[cTy]:''),propName:fmt(cP>=0?r[cP]:''),
location:lo,price:fmt(cPr>=0?r[cPr]:''),
land:fmt(cLa>=0?r[cLa]:''),building:fmt(cBl>=0?r[cBl]:''),
built:fmt(cBu>=0?r[cBu]:''),note:fmt(cNo>=0?r[cNo]:'')});
}
}
return res;
}
function quickScore(b, s) {
let sc=0;
const bT=(b.type||'').toLowerCase(), sT=(s.type||'').toLowerCase();
if(bT&&sT&&(bT===sT||sT.includes(bT)||bT.includes(sT))) sc+=40;
const bA=(b.area||'').toLowerCase(), sL=(s.location||'').toLowerCase();
for(const w of bA.replace(/[・、。]/g,' ').split(/\s+/).filter(w=>w.length>1))
if(sL.includes(w)){sc+=30;break;}
if(sc<30&&bA.includes('大阪')&&sL.includes('大阪')) sc+=20;
if(bA.includes('日本全国')) sc+=15;
return sc;
}
function jsonOut(obj) {
return ContentService.createTextOutput(JSON.stringify(obj))
.setMimeType(ContentService.MimeType.JSON);
}
💬 LINE通知設定(Messaging API)
📋 LINE Messaging API セットアップ手順
① LINE Developersコンソールへ:developers.line.biz にログイン
② プロバイダーを作成:「プロバイダーを作成」→ 名前を入力
③ チャンネルを作成:「チャンネル作成」→「Messaging API」→ 必要事項入力
④ チャンネルアクセストークン発行:作成したチャンネル →「Messaging API設定」→「チャンネルアクセストークン(長期)」→「発行」
⑤ ボットをグループ招待:「Messaging API設定」の QRコードで通知先グループへ招待
⑥ グループIDを自動取得:「Messaging API設定」→「ウェブフックURL」に
GAS WEB APP URL を設定 → グループ内で誰かがメッセージ送信 → GASがグループIDを自動保存⑦ スクリプトプロパティに設定:Apps Script →「プロジェクトの設定」→「スクリプトプロパティ」に追加:
LINE_CHANNEL_ACCESS_TOKEN = (④で発行したトークン)
LINE_RECIPIENT_ID = (⑥で自動取得されたグループID)
📧 EmailJS 設定 ← emailjs.com で無料登録してIDを取得
PUBLIC KEY
SERVICE ID
TEMPLATE ID
👤 通知先担当者リスト(メール)
📋 通知ルール
📬 通知送信履歴
0件
通知履歴はありません