Skip to content

Instantly share code, notes, and snippets.

@s-nt-s
Last active February 14, 2025 22:26
Show Gist options
  • Save s-nt-s/fd24c8c331ac4e6265b528550814a3c0 to your computer and use it in GitHub Desktop.
Save s-nt-s/fd24c8c331ac4e6265b528550814a3c0 to your computer and use it in GitHub Desktop.
Filtrar chats de Idealista para que solo aparezcan personas con perfil relleno según nuestras necesidades
// ==UserScript==
// @name Idealista chat
// @description Filtrar el chat de Idealista
// @version 1.4
// @include https://www.idealista.com/conversations
// @run-at document-end
// @grant none
// ==/UserScript==
function my_hack_ajax() {
const CHAT_URL = "/conversations/list.ajax";
class Cache {
static set(key, obj) {
sessionStorage.setItem(key, JSON.stringify(obj));
console.log("Cache.SET "+key);
}
static get(key) {
const v = sessionStorage.getItem(key);
if (v==null) return null;
console.log("Cache.GET "+key);
return JSON.parse(v);
}
}
(function(XHR) {
const open = XHR.prototype.open;
XHR.prototype.open = function(method, url, async, user, pass) {
if (url.startsWith(CHAT_URL)) url = url.replace(/\bsize=\d+/, "size=200");
open.call(this, method, url, async, user, pass);
};
})(XMLHttpRequest);
const toVCard = (contacts) => {
return contacts.map(c => {
const uid = 'uid-' + c.phone.replace(/\D/g, '');
return [
`BEGIN:VCARD`,
`VERSION:3.0`,
`FN:${c.name}`,
c.phone==null?null:`TEL:${c.phone}`,
c.email.map(e=>`EMAIL:${e}`),
c.image==null?null:`PHOTO;TYPE=JPEG;VALUE=URI:${c.image}`,
`NOTE:${c.note.replace(/\n/g, '\n ')}`,
`UID:${uid}`,
`END:VCARD`
].flat().filter(x=>x!=null).join("\n")+"\n\n"
}).join("\n")
}
const waitForGlobal = function(key, callback) {
const gvar = window[key];
if (gvar == null)
setTimeout(waitForGlobal, 100, arguments);
else
callback.apply(gvar, Array.from(arguments).slice(2));
};
const gJSON = function(obj) {
if (typeof obj === "string") {
obj = {
dataType: "json",
url: obj,
async: false
}
}
const r = $.ajax(obj)
if (r.responseJSON == null) {
console.error("gJSON() == null", obj);
return null;
}
return r.responseJSON;
}
const dwnLoad = (file, txt) => {
if (typeof txt != "string") txt = JSON.stringify(txt, null, 2);
const a = document.createElement("a");
const b = new Blob([txt], {
'type': 'text/plain'
});
a.href = URL.createObjectURL(b);
a.download = file;
a.click();
}
const gChat = function(id, size) {
if (size==null) size = 100;
return gJSON({
contentType: "application/json; charset=utf-8",
dataType: "json",
type: "POST",
url: "https://www.idealista.com/conversations/messages/summary.ajax",
async: false,
data: JSON.stringify({
"conversationId": id,
"readSize": size,
"unreadSize": size,
})
}).messages;
}
const addMethods = function(obj, flds) {
Object.entries(flds).forEach(([key, value]) => {
if (key.startsWith("_")) {
obj[key] = value.bind(obj);
return;
}
obj[key] = (function(cached, fnc) {
if (this[cached] == null) {
this[cached] = fnc.apply(this, Array.from(arguments).slice(2));
}
return this[cached];
}).bind(obj, "_cached_" + key, value);
});
}
const partition = function(array, isValid) {
return array.reduce(([pass, fail], elem) => {
return isValid(elem) ? [
[...pass, elem], fail
] : [pass, [...fail, elem]];
}, [
[],
[]
]);
}
const safeJsonParse = function(s, url) {
if (s==null) return null;
try {
return JSON.parse(s);
} catch (error) {
console.error(url+" is not a json", s);
return null;
}
}
const findAll = function () {
const txt = arguments[0];
if (txt==null) return [];
const arr = [];
for (let i=1; i<arguments.length; i++) {
let mth = txt.match(arguments[i]);
(mth||[]).forEach(m=>{
if (m.length>0 && !arr.includes(m)) arr.push(m);
})
}
return arr;
}
class CHAT {
constructor(file) {
this._conversations = [];
this._file = file;
}
get chats() {
return this._conversations;
}
add(chats) {
if (chats.conversations) chats = chats.conversations;
const ids = chats.map(c => c.id);
this._conversations = this._conversations.filter(c => !ids.includes(c.id)).concat(chats);
}
getConversations(full) {
if (full) {
this._conversations.forEach(c => {
c.getMessages();
c.hasMe();
c.getProfile();
});
}
if (full!=false) return this._conversations;
const arr = this._conversations.map(c=>{
const a = {};
Object.entries(c).forEach(([k, v])=>{
if (!k.startsWith('_cached_')) a[k]=v;
})
return a;
});
return arr;
}
save(full) {
dwnLoad(this._file+'.json', this.getConversations(full===true));
}
getContacts() {
return this.getConversations().flatMap(c=>{
if (c.role != "ADVERTISER") return [];
const ct = c._getContact();
if (ct==null || ct.email==null || ct.email.length==0) return [];
return ct;
})
}
vCard() {
const vCard = toVCard(this.getContacts());
dwnLoad(this._file+'.vCard', vCard);
}
csv() {
let head = "First Name,Phone 1 - Value,Photo,Notes";
let mls = 0;
const lines = this.getContacts().map(c=>{
const note = (c.note||'').replace('"', "'").replace(/\n+/g, ". ");
let ln = `"${c.name}",${c.phone},${c.image},"${note}",${c.email[0]}`;
c.email.forEach(m=>ln+`,"${m}"`);
mls = Math.max(mls, c.email.length);
return ln.replace(/"(null|undefined)"/g, "").replace(/,(null|undefined),/g, ",,");
}).join("\n");
for (let i=1; i<=mls; i++) head = head + `,E-mail ${i} - Value`;
dwnLoad(this._file+'.csv', head+"\n"+lines+"\n");
}
}
window.DEBUG_CONVERSATIONS = {
"ok": new CHAT("idealista-ok"),
"ko": new CHAT("idealista-ko")
}
waitForGlobal(
"jQuery",
(chat_filter) => {
jQuery.ajaxSetup({
dataFilter: function(data, type) {
if (!this.url.startsWith(CHAT_URL)) return data;
const js = safeJsonParse(data, this.url);
if (js==null) return data;
if (!js.conversations) return data;
/* Añadir funcionalidad al objeto */
js.conversations.forEach(c => {
addMethods(c, {
getMessages: function() {
const key = 'chat_'+this.id;
let chat = Cache.get(key);
if (chat!=null && this.lastMessage == chat.text && this.isFromMe == chat.fromMe) {
return chat;
}
chat = gChat(this.id);
Cache.set(key, chat);
return chat;
},
hasMe: function() {
if (this.lastMessage && this.lastMessage.isFromMe) return true;
return this.getMessages().filter(m => m.fromMe).length > 0;
},
getProfile: function () {
let prf = null;
const url = "/conversations/" + this.id + "/seeker-profile.ajax";
if ((prf = Cache.get(url))==null) {
prf = gJSON(url).body;
Cache.set(url, prf);
}
return prf;
},
getTxt: function() {
return this.getMessages().reverse().flatMap(m=>{
if (m.fromMe) return [];
if (m.text==null) return [];
const t = m.text.trim();
return t.length==0?[]:t;
}).join("\n\n").trim();
},
_getPlainTxt: function(prf) {
const coverLetter = (prf!=null && (typeof prf.coverLetter == "string"))?prf.coverLetter:'';
return (coverLetter+'\n\n'+this.getTxt()).toLowerCase()
.replace(/á/g, "a")
.replace(/é/g, "e")
.replace(/í/g, "i")
.replace(/ó/g, "o")
.replace(/ú/g, "i")
.trim();
},
_getIncome: function(prf) {
if (prf!=null && prf.income!=null) return prf.income;
if (c.hasMe()) return Infinity;
const txt = c._getPlainTxt(prf);
const num = findAll(
txt,
/[\d\.\,]+\s*€/g,
/[\d\.\,]+\s*euros/g,
/€[\d\.\,]+/g
).flat().flatMap(x=>{
x = x.replace(/\D/, "").trim();
if (x.length == 0) return [];
const n = parseInt(x);
if (isNaN(n)) return [];
if (n<1000) return [];
if (n>5000) return [];
return n;
})
if (num.length==0) return Infinity;
return Math.min(...num);
},
_getContact: function() {
const txt = this._getPlainTxt();
const email = findAll(
txt,
/(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})/g
).sort();
const name = this.userName.toLowerCase().split(/\s+/).map(s=>s.length<2?s:s.charAt(0).toUpperCase() + s.slice(1)).join(" ");
const prf = this.getProfile();
const income = this._getIncome(prf);
return {
name: name,
phone: prf!=null && prf.phone==null?null:prf.phone.number,
email: email,
image: this.userPhoto,
note: [
prf==null?null:prf==null?null:prf.preview,
prf.coverLetter,
prf!=null && (prf.kids || prf.teenagers)?"con niños":null,
prf!=null && prf.ownsPet?"con mascota":null,
income!=null && income!=Infinity?income+" €/mes":null,
txt
].filter(x=>x!=null).join("\n"),
}
}
});
});
/* Filtrar */
const [ok, ko] = partition(js.conversations, chat_filter);
if (ok.length != js.conversations.length) {
console.log("conversations", js.conversations.length, "->", ok.length);
js.conversations = ok;
data = JSON.stringify(js);
console.log(js);
}
window.DEBUG_CONVERSATIONS.ok.add(ok);
window.DEBUG_CONVERSATIONS.ko.add(ko);
return data;
}
})
},
(c) => {
/* Only filter my ads */
if (c.role != "ADVERTISER") return true;
/* Only filter not starred chats */
if (c.isStarred) return true;
const doBan = (...arr) => {
const d = new Date(c.date);
if ((d.getFullYear()+2)<(new Date()).getFullYear()) return false;
const s = d.toISOString().split('T')[0];
console.log(c.id, s, ...arr);
return false;
}
/* NO fraud, agency, counteroffer or empty profile */
if (c.isFraudWarning) return doBan("isFraudWarning");
if (c.agencyName && c.agencyName.length) return doBan("isAgency");
if (c.lastMessage && c.lastMessage.counterOffer != null) return doBan("isCounteroffer");
if (!(c.userProfilePreview && c.seekerHasUserProfile)) return doBan("isNotProfile");
/* NO pet */
if (/con mascota/.test(c.userProfilePreview)) return doBan("isPet", c.userProfilePreview);
/* NO minors */
if (/con menores/.test(c.userProfilePreview)) return doBan("hasMinors", c.userProfilePreview);
const people = (()=>{
if (c.userProfilePreview==null) return Infinity;
const userProfilePreview = c.userProfilePreview.trim().toLowerCase();
const w1 = userProfilePreview.replace(/[, ].*$/, "");
if (w1 == "pareja") return 2;
const fam = userProfilePreview.match(/familia de (\d+)/);
if (fam!=null && fam.length>0) {
const f = parseInt(fam[fam.length-1], 10);
if (!isNaN(f)) return -f;
}
const n1 = parseInt(w1, 10);
if (!isNaN(n1)) return n1;
if (w1 == "familia") return 3;
return Infinity;
})();
/* NO too many people */
if (Math.abs(people) > 2) return doBan("tooManyPeople", people, c.userProfilePreview);
/* Family of 2 persons usually has minors */
if (people<0) return doBan("isFamily", people, c.userProfilePreview);
const prf = c.getProfile();
/* NO in Spain yet */
if (prf.phone && prf.phone.prefix && prf.phone.prefix != "34") return doBan("isNotResident", prf.phone.prefix);
const banIfMath = (msg, ...re) => {
const mth = findAll(c._getPlainTxt(prf), ...re);
if (mth == null || mth.length == 0) return false;
doBan(msg, ...mth);
return true;
}
/* NO minors */
if (banIfMath(
"hasMinors",
/con una? niñ[oa]/g,
/con una? bebe/g,
)) return false;
if (banIfMath(
"isFamily",
/para mi madre y yo/g,
)) return false;
if (banIfMath(
"needAval",
/\baval\b/g,
/\bavalistas?\b/g,
)) return false;
const income = c._getIncome(prf);
/* NO low income */
const rent = parseInt(c.ads[0].priceInfo.split()[0], 10);
if (income!=null) {
if (rent > (income * 0.40)) return doBan("isLowIncome", income);
if (people>1) {
if (income == Infinity) return doBan("dontHaveIncomeInfo", people);
if ((rent/people) > ((income/people) * 0.35)) return doBan("isLowIncomeForPeogle", people, income);
}
}
/*
if (banIfMath(
"notSingleMatch",
/para mi y mi pareja/g,
/para mi y mi marido/g,
/para mi y mi mujer/g,
/para mi y mi esposo/g,
/para mi y mi esposa/g,
/somos un matrimonio/g,
)) return false;
*/
return true;
}
);
}
const script = document.createElement("script");
script.textContent = "(" + my_hack_ajax.toString() + ")();";
document.head.appendChild(script);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment