// ==UserScript==
// @name deai prompt generator
// @namespace http://tampermonkey.net/
// @version 1.0.4
// @description with.isとpairs.lvとmarrish.comのユーザーページにコピーボタンを追加し、AI対話プロンプトを生成します。marrish.comのチャットページでメッセージをコピーできます。
// @author Your Name
// @match https://with.is/users/*
// @match https://pairs.lv/message/detail/*
// @match https://marrish.com/profile/detail/partner/*
// @match https://marrish.com/message/index/*
// @grant GM_setClipboard
// @license MIT
// @supportURL https://github.com/thelastfantasy/with-profile-copy/issues
// ==/UserScript==
"use strict";
(function () {
'use strict';
const CONFIG = {
MESSAGE_DISPLAY_TIME: 3000,
PAIRS_MODAL_TIMEOUT: 10000
};
const TEMPLATES = {
WITH_IS: {
header: 'with.isで以下ユーザーとマッチしました。相手の情報は以下になります',
nickname: 'ユーザー名',
age: '年齢',
location: '居住地',
introduction: '自己紹介文',
additional: '俺との共通点',
basicInfo: '相手の基本情報',
footer: '以上情報常に忘れず、相手と会話で送るメッセージを提案してみてください。'
},
PAIRS: {
header: 'pairs.lvで以下ユーザーとマッチしました。相手の情報は以下になります',
nickname: 'ユーザー名',
age: '年齢',
location: '居住地',
introduction: '自己紹介',
additional: 'マイタグ',
basicInfo: '相手の基本情報',
footer: '以上情報常に忘れず、相手と会話で送るメッセージを提案してみてください。'
},
MARRISH: {
header: 'marrish.comで以下ユーザーとマッチしました。相手の情報は以下になります',
nickname: 'ユーザー名',
age: '年齢',
location: '居住地',
introduction: '自己PR',
additional: '参加グループ',
basicInfo: '相手の基本情報',
footer: '以上情報常に忘れず、相手と会話で送るメッセージを提案してみてください。'
}
};
const CSS_SELECTORS = {
WITH_IS: {
NICKNAME: '.profile_main-nickname',
AGE_ADDRESS: '.profile_main-age-address',
INTRODUCTION: '.profile-introduction',
COMMON_POINTS: '.profile-affinities_list.on-user-detail li',
BASIC_INFO_TABLE: '.profile-detail table',
BASIC_INFO_ROW: 'tr',
BASIC_INFO_HEADER: 'th',
BASIC_INFO_DATA: 'td'
},
PAIRS: {
NICKNAME: '#dialog-root > div > div > div > div:nth-child(2) > div > div > div:nth-child(3) > div:nth-child(1) > div > div:nth-child(1) > div:nth-child(3) > p',
AGE_LOCATION: '#dialog-root > div > div > div > div:nth-child(2) > div > div > div:nth-child(3) > div:nth-child(1) > div > div:nth-child(1) > div:nth-child(4) > span',
MY_TAGS: '#dialog-root > div > div > div > div:nth-child(2) > div > div > div:nth-child(3) > div:nth-child(2) > div:nth-child(1) > div > ul > li > a',
INTRODUCTION: '#dialog-root > div > div > div > div:nth-child(2) > div > div > div:nth-child(3) > div:nth-child(2) > div:nth-child(2) > div > p',
PROFILE_CONTAINER: '#dialog-root > div > div > div > div:nth-child(2) > div > div > div:nth-child(3) > div:nth-child(2) > div:nth-child(3) > div',
BUTTON_INSERT: '#dialog-root > div > div > div > div:nth-child(2) > div > div > div:nth-child(3) > div:nth-child(1) > div > div:nth-child(1) > div:nth-child(3) > p'
},
MARRISH: {
BASE_INFO: '.as-profile__baseinfo-pc',
NAME: '.as-profile__name',
AGE: '.as-profile__age',
AREA: '.as-profile__area',
GROUP_LIST: '.as-prof-group-list',
GROUP_ITEMS: '.as-prof-group-list__item',
GROUP_TITLE: '.as-prof-group-list__title',
GROUP_MEMBER: '.as-prof-group-list__member',
SELF_PR: '.as-profile-text-contents',
DETAIL_WRAP: '.as-profile-detail-wrap',
DETAIL_GROUP: '.as-profile-detail-item-group',
DETAIL_SUB_TITLE: '.as-profile-detail-sub-title',
DETAIL_ITEM: '.as-profile-detail-item',
DETAIL_ITEM_TITLE: '.as-profile-detail-item-title',
DETAIL_ITEM_DATE: '.as-profile-detail-item-date',
MESSAGE_BUBBLE: '.yi-message-form-text-body-bg1, .yi-message-form-text-body-bg1-me',
MESSAGE_CONTENT: 'p',
SPEAKER_NAME: '.yi-message-form-phone_head_name_textover'
}
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
}
else {
init();
}
function init() {
if (window.location.href.includes('with.is/users/')) {
addCopyButton('WITH_IS');
}
else if (window.location.href.includes('pairs.lv/message/detail/')) {
waitForPairsModal();
}
else if (window.location.href.includes('marrish.com/profile/detail/partner/')) {
waitForMarrishBaseInfo();
}
else if (window.location.href.includes('marrish.com/message/index/')) {
waitForMarrishMessages();
}
else {
return;
}
}
function waitForPairsModal() {
console.log('等待pairs.lv模态框加载...');
if (tryAddPairsButton()) {
return;
}
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type === 'childList') {
if (tryAddPairsButton()) {
observer.disconnect();
console.log('pairs.lv模态框已加载,按钮已添加');
return;
}
}
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
setTimeout(() => {
observer.disconnect();
console.log('pairs.lv模态框加载超时');
}, CONFIG.PAIRS_MODAL_TIMEOUT);
}
function tryAddPairsButton() {
const buttonContainer = document.querySelector(CSS_SELECTORS.PAIRS.BUTTON_INSERT);
if (buttonContainer) {
addCopyButton('PAIRS');
return true;
}
return false;
}
function waitForMarrishBaseInfo() {
console.log('等待marrish.com基本信息区域加载...');
if (tryAddMarrishButton()) {
return;
}
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type === 'childList') {
if (tryAddMarrishButton()) {
observer.disconnect();
console.log('marrish.com基本信息区域已加载,按钮已添加');
return;
}
}
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
setTimeout(() => {
observer.disconnect();
console.log('marrish.com基本信息区域加载超时');
}, CONFIG.PAIRS_MODAL_TIMEOUT);
}
function tryAddMarrishButton() {
const buttonContainer = document.querySelector(CSS_SELECTORS.MARRISH.AREA);
if (buttonContainer) {
addCopyButton('MARRISH');
return true;
}
return false;
}
function waitForMarrishMessages() {
console.log('等待marrish.com聊天消息加载...');
if (tryAddMessageButtons()) {
addCopyAllChatButton();
return;
}
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type === 'childList') {
if (tryAddMessageButtons()) {
console.log('marrish.com聊天消息已加载,按钮已添加');
addCopyAllChatButton();
}
}
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
function tryAddMessageButtons() {
const messageBubbles = document.querySelectorAll(CSS_SELECTORS.MARRISH.MESSAGE_BUBBLE);
let addedButtons = false;
messageBubbles.forEach(bubble => {
if (!bubble.querySelector('.message-copy-button')) {
addMessageCopyButton(bubble);
addedButtons = true;
}
});
return addedButtons;
}
function addMessageCopyButton(bubble) {
const copyButton = document.createElement('button');
copyButton.textContent = '📋';
copyButton.className = 'message-copy-button';
copyButton.style.cssText = `
position: absolute;
top: 5px;
left: 5px;
padding: 2px 6px;
background: rgba(0, 123, 255, 0.8);
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
font-size: 10px;
z-index: 10;
opacity: 0.7;
transition: opacity 0.2s;
`;
copyButton.addEventListener('mouseenter', () => {
copyButton.style.opacity = '1';
});
copyButton.addEventListener('mouseleave', () => {
copyButton.style.opacity = '0.7';
});
copyButton.addEventListener('click', (e) => {
e.stopPropagation();
copyMessageContent(bubble);
});
if (getComputedStyle(bubble).position === 'static') {
bubble.style.position = 'relative';
}
bubble.appendChild(copyButton);
}
function copyMessageContent(bubble) {
try {
const messageContent = bubble.querySelector(CSS_SELECTORS.MARRISH.MESSAGE_CONTENT);
if (messageContent) {
const htmlContent = messageContent.innerHTML;
const textContent = htmlContent
.replace(/<br\s*\/?>/gi, '\n')
.replace(/<[^>]*>/g, '')
.replace(/\n{3,}/g, '\n\n')
.trim();
const speakerNameElement = document.querySelector(CSS_SELECTORS.MARRISH.SPEAKER_NAME);
let speakerName = '見つかりません';
if (speakerNameElement) {
speakerName = speakerNameElement.textContent?.trim() || '見つかりません';
}
const isMyMessage = bubble.classList.contains('yi-message-form-text-body-bg1-me');
const speakerPrefix = isMyMessage ? '俺' : speakerName;
const formattedContent = `${speakerPrefix}:\n${textContent}`;
GM_setClipboard(formattedContent, 'text');
showMessage('✅ メッセージをコピーしました!', 'success');
}
else {
showMessage('❌ メッセージ内容が見つかりません', 'error');
}
}
catch (error) {
console.error('メッセージコピーに失敗しました:', error);
showMessage('❌ コピーに失敗しました', 'error');
}
}
function addCopyAllChatButton() {
const readUnreadButton = document.getElementById('read_unread_func_off');
if (!readUnreadButton) {
console.log('既読機能OFF按钮が見つかりません');
return;
}
if (readUnreadButton.parentNode?.querySelector('.copy-all-chat-button')) {
return;
}
const copyAllButton = document.createElement('button');
copyAllButton.textContent = '📋 チャット履歴をコピー';
copyAllButton.className = 'copy-all-chat-button';
copyAllButton.style.cssText = `
margin-right: 10px;
padding: 6px 12px;
background: #28a745;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
`;
copyAllButton.addEventListener('click', copyAllChatHistory);
readUnreadButton.parentNode?.insertBefore(copyAllButton, readUnreadButton);
}
function copyAllChatHistory() {
try {
const messageBubbles = document.querySelectorAll(CSS_SELECTORS.MARRISH.MESSAGE_BUBBLE);
if (messageBubbles.length === 0) {
showMessage('❌ チャット履歴が見つかりません', 'error');
return;
}
const speakerNameElement = document.querySelector(CSS_SELECTORS.MARRISH.SPEAKER_NAME);
const speakerName = speakerNameElement?.textContent?.trim() || '相手';
const messages = [];
messages.push('チャット履歴');
messageBubbles.forEach(bubble => {
const messageContent = bubble.querySelector(CSS_SELECTORS.MARRISH.MESSAGE_CONTENT);
if (messageContent) {
const htmlContent = messageContent.innerHTML;
const textContent = htmlContent
.replace(/<br\s*\/?>/gi, '\n')
.replace(/<[^>]*>/g, '')
.replace(/\n{3,}/g, '\n\n')
.trim();
const isMyMessage = bubble.classList.contains('yi-message-form-text-body-bg1-me');
const speakerPrefix = isMyMessage ? '俺' : speakerName;
messages.push(`${speakerPrefix}:`);
messages.push(textContent);
messages.push('');
}
});
const fullChatHistory = messages.join('\n').trim();
GM_setClipboard(fullChatHistory, 'text');
showMessage('✅ チャット履歴をコピーしました!', 'success');
}
catch (error) {
console.error('チャット履歴コピーに失敗しました:', error);
showMessage('❌ チャット履歴のコピーに失敗しました', 'error');
}
}
function addCopyButton(site) {
let buttonContainer = null;
let buttonText = '📋 ユーザー情報をコピー';
if (site === 'WITH_IS') {
buttonContainer = document.querySelector(CSS_SELECTORS.WITH_IS.NICKNAME);
}
else if (site === 'PAIRS') {
buttonContainer = document.querySelector(CSS_SELECTORS.PAIRS.BUTTON_INSERT);
buttonText = '📋 プロフィールをコピー';
}
else if (site === 'MARRISH') {
buttonContainer = document.querySelector(CSS_SELECTORS.MARRISH.AREA);
buttonText = '📋 プロフィールをコピー';
}
if (!buttonContainer) {
console.log('ボタン追加位置が見つかりません:', site, 'selector:', site === 'WITH_IS' ? CSS_SELECTORS.WITH_IS.NICKNAME :
site === 'PAIRS' ? CSS_SELECTORS.PAIRS.BUTTON_INSERT :
CSS_SELECTORS.MARRISH.AREA);
return;
}
createCopyButton(buttonContainer, buttonText);
}
function createCopyButton(container, buttonText) {
const copyButton = document.createElement('button');
copyButton.textContent = buttonText;
copyButton.style.cssText = `
margin-left: 10px;
padding: 4px 8px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
`;
copyButton.addEventListener('click', handleCopy);
container.parentNode?.insertBefore(copyButton, container.nextSibling);
}
function handleCopy() {
try {
const userData = extractUserData();
const promptText = generatePrompt(userData);
GM_setClipboard(promptText, 'text');
showMessage('✅ ユーザー情報をクリップボードにコピーしました!', 'success');
}
catch (error) {
console.error('コピーに失敗しました:', error);
showMessage('❌ コピーに失敗しました。コンソールを確認してください', 'error');
}
}
function extractUserData() {
let selectors;
let site = 'WITH_IS';
if (window.location.href.includes('with.is/users/')) {
selectors = CSS_SELECTORS.WITH_IS;
site = 'WITH_IS';
}
else if (window.location.href.includes('pairs.lv/message/detail/')) {
selectors = CSS_SELECTORS.PAIRS;
site = 'PAIRS';
}
else if (window.location.href.includes('marrish.com/profile/detail/partner/')) {
selectors = CSS_SELECTORS.MARRISH;
site = 'MARRISH';
}
else {
throw new Error('サポートされていないサイトです');
}
if (site === 'WITH_IS') {
return extractWithIsData(selectors);
}
else if (site === 'PAIRS') {
return extractPairsData(selectors);
}
else {
return extractMarrishData(selectors);
}
}
function extractWithIsData(selectors) {
const nickname = document.querySelector(selectors.NICKNAME)?.textContent?.trim() || '見つかりません';
const ageAddressElement = document.querySelector(selectors.AGE_ADDRESS);
let age = '見つかりません';
let location = '見つかりません';
if (ageAddressElement) {
const text = ageAddressElement.textContent?.trim() || '';
const parts = text.split('\n').filter((part) => part.trim());
if (parts.length >= 1)
age = parts[0].trim();
if (parts.length >= 2)
location = parts[1].trim();
}
let introduction = document.querySelector(selectors.INTRODUCTION)?.textContent?.trim() || '見つかりません';
if (introduction.startsWith('自己紹介文')) {
introduction = introduction.replace(/^自己紹介文\s*/, '');
}
const commonPoints = [];
const commonPointElements = document.querySelectorAll(selectors.COMMON_POINTS);
commonPointElements.forEach(el => {
const text = el.textContent?.trim();
if (text)
commonPoints.push(text);
});
const basicInfo = {};
const basicInfoTable = document.querySelector(selectors.BASIC_INFO_TABLE);
if (basicInfoTable) {
const rows = basicInfoTable.querySelectorAll(selectors.BASIC_INFO_ROW);
rows.forEach((row) => {
const th = row.querySelector(selectors.BASIC_INFO_HEADER)?.textContent?.trim();
const td = row.querySelector(selectors.BASIC_INFO_DATA)?.textContent?.trim();
if (th && td) {
basicInfo[th] = td;
}
});
}
return {
nickname,
age,
location,
introduction,
commonPoints,
basicInfo,
site: 'WITH_IS'
};
}
function extractPairsData(selectors) {
const nickname = document.querySelector(selectors.NICKNAME)?.textContent?.trim() || '見つかりません';
const ageLocationElement = document.querySelector(selectors.AGE_LOCATION);
let age = '見つかりません';
let location = '見つかりません';
if (ageLocationElement) {
const text = ageLocationElement.textContent?.trim() || '';
const parts = text.split(' ').filter((part) => part.trim());
if (parts.length >= 1)
age = parts[0].trim();
if (parts.length >= 2)
location = parts.slice(1).join(' ').trim();
}
const introduction = document.querySelector(selectors.INTRODUCTION)?.textContent?.trim() || '見つかりません';
const myTags = [];
const myTagElements = document.querySelectorAll(selectors.MY_TAGS);
myTagElements.forEach(el => {
const title = el.getAttribute('title');
if (title) {
myTags.push(title);
}
});
const basicInfo = {};
const profileContainer = document.querySelector(CSS_SELECTORS.PAIRS.PROFILE_CONTAINER);
if (profileContainer) {
console.log('找到プロフィール容器');
const allH3Elements = profileContainer.querySelectorAll('h3');
console.log('找到的h3元素数量:', allH3Elements.length);
const allDlElements = profileContainer.querySelectorAll('dl');
console.log('找到的dl元素数量:', allDlElements.length);
allDlElements.forEach((dl) => {
const dtElements = dl.querySelectorAll('dt');
const ddElements = dl.querySelectorAll('dd');
dtElements.forEach((dt, index) => {
const key = dt.textContent?.trim();
const value = ddElements[index]?.textContent?.trim();
if (key && value) {
basicInfo[key] = value;
}
});
});
console.log('提取的基本信息数量:', Object.keys(basicInfo).length);
console.log('提取的键:', Object.keys(basicInfo));
}
else {
console.log('未找到プロフィール容器');
}
return {
nickname,
age,
location,
introduction,
myTags,
basicInfo,
site: 'PAIRS'
};
}
function extractMarrishData(selectors) {
const name = document.querySelector(selectors.NAME)?.textContent?.trim() || '見つかりません';
const age = document.querySelector(selectors.AGE)?.textContent?.trim() || '見つかりません';
const location = document.querySelector(selectors.AREA)?.textContent?.trim() || '見つかりません';
const groups = [];
const groupElements = document.querySelectorAll(selectors.GROUP_ITEMS);
groupElements.forEach(el => {
const title = el.querySelector(selectors.GROUP_TITLE)?.textContent?.trim();
const member = el.querySelector(selectors.GROUP_MEMBER)?.textContent?.trim();
if (title) {
groups.push({
title,
member: member || '不明'
});
}
});
const selfPrElement = document.querySelector(selectors.SELF_PR);
let selfPr = '見つかりません';
if (selfPrElement) {
const htmlContent = selfPrElement.innerHTML;
selfPr = htmlContent
.replace(/<br\s*\/?>/gi, '\n')
.replace(/<[^>]*>/g, '')
.replace(/\n{3,}/g, '\n\n')
.trim();
}
const basicInfo = {};
const detailGroups = document.querySelectorAll(selectors.DETAIL_GROUP);
detailGroups.forEach(group => {
const subTitle = group.querySelector(selectors.DETAIL_SUB_TITLE)?.textContent?.trim();
const detailItems = group.querySelectorAll(selectors.DETAIL_ITEM);
detailItems.forEach((item) => {
const title = item.querySelector(selectors.DETAIL_ITEM_TITLE)?.textContent?.trim();
const dateElement = item.querySelector(selectors.DETAIL_ITEM_DATE);
if (title && dateElement) {
let date = title === '活動エリア' ?
dateElement.innerHTML
.replace(/<br\s*\/?>/gi, ',')
.replace(/<[^>]*>/g, '')
.trim()
.replace(/,\s*,/g, ',')
.replace(/,$/, '') :
dateElement.textContent?.trim();
if (title && date) {
const key = title;
basicInfo[key] = {
value: date,
group: subTitle || 'その他'
};
}
}
});
});
return {
nickname: name,
age,
location,
groups,
selfPr,
basicInfo,
site: 'MARRISH'
};
}
function generatePrompt(data) {
const template = TEMPLATES[data.site];
if (!template) {
throw new Error(`未知的网站类型: ${data.site}`);
}
let basicInfoText;
if (data.site === 'MARRISH') {
basicInfoText = formatMarrishBasicInfo(data.basicInfo);
}
else {
basicInfoText = formatBasicInfo(data.basicInfo);
}
const additionalText = formatAdditionalData(data);
return buildPrompt(template, data, basicInfoText, additionalText);
}
function formatBasicInfo(basicInfo) {
if (Object.entries(basicInfo).length === 0) {
return 'なし';
}
return Object.entries(basicInfo)
.map(([key, value]) => `${key}: ${value}`)
.join('\n');
}
function formatMarrishBasicInfo(basicInfo) {
if (Object.entries(basicInfo).length === 0) {
return 'なし';
}
const groupedData = {};
Object.entries(basicInfo).forEach(([key, data]) => {
const group = data.group;
if (!groupedData[group]) {
groupedData[group] = [];
}
groupedData[group].push({ key, value: data.value });
});
const sections = [];
Object.entries(groupedData).forEach(([group, items]) => {
sections.push(group);
items.forEach(item => {
sections.push(` ${item.key}: ${item.value}`);
});
sections.push('');
});
return sections.join('\n').trim();
}
function formatAdditionalData(data) {
switch (data.site) {
case 'WITH_IS':
return data.commonPoints.length > 0
? data.commonPoints.map(point => `- ${point}`).join('\n')
: 'なし';
case 'PAIRS':
return data.myTags.length > 0
? data.myTags.map(tag => `- ${tag}`).join('\n')
: 'なし';
case 'MARRISH':
return data.groups.length > 0
? data.groups.map(group => `- ${group.title} (${group.member})`).join('\n')
: 'なし';
default:
return 'なし';
}
}
function buildPrompt(template, data, basicInfoText, additionalText) {
const introductionField = data.site === 'MARRISH' ? 'selfPr' : 'introduction';
return `${template.header}
${template.nickname}:${data.nickname}
${template.age}:${data.age}
${template.location}:${data.location}
${template.introduction}:
${data[introductionField]}
${template.additional}:
${additionalText}
${template.basicInfo}:
${basicInfoText}
${template.footer}`;
}
function showMessage(message, type) {
const messageDiv = document.createElement('div');
messageDiv.textContent = message;
messageDiv.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
padding: 10px 15px;
background: ${type === 'success' ? '#28a745' : '#dc3545'};
color: white;
border-radius: 4px;
z-index: 10000;
font-size: 14px;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
`;
document.body.appendChild(messageDiv);
setTimeout(() => {
if (messageDiv.parentNode) {
messageDiv.parentNode.removeChild(messageDiv);
}
}, CONFIG.MESSAGE_DISPLAY_TIME);
}
})();