// ==UserScript==
// @name 领英批量加人
// @namespace ༺黑白༻
// @version 1.6
// @description 领英批量加人功能
// @author Paul
// @connect *
// @match *linkedin.com/search/results/people*
// @include *linkedin.com/search/results/people*
// @match *linkedin.com/in/*?local_name=*
// @include *linkedin.com/in/*?local_name=*
// @require https://cdn.bootcss.com/vue/2.6.11/vue.min.js
// @require https://cdn.bootcss.com/element-ui/2.13.0/index.js
// @resource elementui https://cdn.bootcss.com/element-ui/2.13.0/theme-chalk/index.css
// @grant GM_addStyle
// @grant GM_openInTab
// @grant GM_addValueChangeListener
// @grant GM_removeValueChangeListener
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_getResourceText
// @run-at document-end
// @noframes true
// @downloadURL none
// ==/UserScript==
(function () {
({
commonUtils: {
queueStorageName: 'local_name', appendBodyHtml: function (html) {
var temp = document.createElement('div'); temp.innerHTML = html; var frag = document.createDocumentFragment(); while ((temp = temp.firstChild)) { frag.appendChild(temp); }
document.body.appendChild(frag);
}, append: function (dom, html) {
var temp = document.createElement('div'); temp.innerHTML = html; var frag = document.createDocumentFragment(); while ((temp = temp.firstChild)) { frag.appendChild(temp); }
dom.appendChild(frag);
}, execByPromiseAsync: function (scope, fn) {
var args = Array.prototype.slice.call(arguments); args.splice(0, 2)
return new Promise((resolve, reject) => { args.unshift({ resolve: resolve, reject: reject }); fn.apply(scope, args); });
}, chkAsync(chkFn, ts) {
ts = ts || 1000; if (typeof chkFn != 'function') chkFn = () => true; var setTimeoutFn = dfd => { var isok = chkFn(); if (isok) dfd.resolve(); else setTimeout(setTimeoutFn, ts, dfd); }
return this.execByPromiseAsync(this, setTimeoutFn);
}, sleepAsync(ts) { ts = ts || 1000; return this.chkAsync(null, ts); }, getRandom(n, m) { return parseInt(Math.random() * (m - n + 1) + n); }, log(msg) { console.log(msg); }, getQueryVariable: function (variable) {
var query = window.location.search.substring(1); var vars = query.split("&"); for (var i = 0; i < vars.length; i++) { var pair = vars[i].split("="); if (pair[0] == variable) { return pair[1]; } }
return "";
}, fireKeyEvent(el, evtType, keyCode) {
var evtObj; if (document.createEvent) {
if (unsafeWindow.KeyEvent) { evtObj = document.createEvent('KeyEvents'); evtObj.initKeyEvent(evtType, true, true, unsafeWindow, true, false, false, false, keyCode, 0); } else {
evtObj = document.createEvent('UIEvents'); evtObj.initUIEvent(evtType, true, true, unsafeWindow, 1); delete evtObj.keyCode; if (typeof evtObj.keyCode === "undefined") { Object.defineProperty(evtObj, "keyCode", { value: keyCode }); } else { evtObj.key = String.fromCharCode(keyCode); }
if (typeof evtObj.ctrlKey === 'undefined') { Object.defineProperty(evtObj, "ctrlKey", { value: true }); } else { evtObj.ctrlKey = true; }
}
el.dispatchEvent(evtObj);
} else if (document.createEventObject) {
evtObj = document.createEventObject(); evtObj.keyCode = keyCode
el.fireEvent('on' + evtType, evtObj);
}
}
}, listPageMethods: function () {
GM_addStyle(GM_getResourceText("elementui")); GM_addStyle('@font-face{font-family:element-icons;src:url(https://cdn.bootcss.com/element-ui/2.13.0/theme-chalk/fonts/element-icons.woff) format("woff"),url(https://cdn.bootcss.com/element-ui/2.13.0/theme-chalk/fonts/element-icons.ttf) format("truetype");font-weight:400;font-display:"auto";font-style:normal}'); GM_addStyle(`
input.people_ck{ position: absolute;top: 20px;opacity: 100;cursor: pointer;pointer-events:all;}
.ellipsisText{overflow: hidden;white-space: nowrap;text-overflow: ellipsis;line-height: 23px; }
.ctrpanel{ position:fixed;top:0;right:0;height:100%;z-index:999;background-color:#fff; }
.ctrpanel .el-main { width:220px; }
.ctrpanel .hide { display:none; }
`); ({
vueExt() {
Vue.directive('dialogdrag', {
bind(el, binding, vnode, oldVnode) {
const dialogHeaderEl = el.querySelector('.el-dialog__header')
const dragDom = el.querySelector('.el-dialog')
dialogHeaderEl.style.cursor = 'move'
const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null)
dialogHeaderEl.onmousedown = (e) => {
const disX = e.clientX - dialogHeaderEl.offsetLeft
const disY = e.clientY - dialogHeaderEl.offsetTop
let styL, styT
if (sty.left.includes('%')) {
styL = +document.body.clientWidth * (+sty.left.replace(/\%/g, '') / 100)
styT = +document.body.clientHeight * (+sty.top.replace(/\%/g, '') / 100)
} else {
styL = +sty.left.replace(/\px/g, '')
styT = +sty.top.replace(/\px/g, '')
}
document.onmousemove = function (e) {
const l = e.clientX - disX
const t = e.clientY - disY
dragDom.style.left = `${l + styL}px`
dragDom.style.top = `${t + styT}px`
}
document.onmouseup = function (e) {
document.onmousemove = null
document.onmouseup = null
}
}
}
})
}, run: function (commonUtils) {
this.vueExt(); unsafeWindow.App = new Vue({
el: '#vue', data: function () { this.commonUtils = commonUtils; this.selectedCheckboxList = []; this.sendList = []; this.sendTextListKey = "linkin_SendTextList"; this.editorIdx = -1; return { loading: false, textListDialogVisible: false, textListData: [], textAddDialogVisible: false, sendingDialogVisible: false, isExecuting: false, isCompleted: false, currentPeopleName: '', connectSeconds: 0, totalConnectPeopleCount: 0, currentConnectPeopleCount: 0, toggleHidePanel: false, textAddForm: { content: "" }, textAddFormRules: { content: [{ required: true, message: '请输入内容', trigger: 'blur' }, { min: 1, max: 290, message: '长度在 1 到 290 个字符', trigger: 'blur' }] } }; }, beforeCreate: function () {
commonUtils.appendBodyHtml(`
软件、网页工具定制,请联系1292956082@qq.com
正在加载数据...
全选
刷新
下一步
添加模板
{{scope.row.content}}
启用中
已禁用
编辑
禁用
启用
移除
开始联系
确定
取消
{{executedMessage}}
进度({{totalConnectPeopleCount}}):
关闭
取消
`);
}, mounted: function () { this.initPeopleAsync().then(() => { var dom = document.querySelector('artdeco-pagination ul'); if (dom) { dom.parentElement.addEventListener('click', async e => { if (e && e.target) { if (e.target.tagName.toLowerCase() != 'ul') { document.querySelectorAll('.search-results__list li').forEach(o => { o.parentNode.removeChild(o); }); await this.commonUtils.chkAsync(() => { return document.querySelectorAll('.search-results__list li').length > 0; }, 500); this.initPeopleAsync(); } } }); } }); }, computed: { executedMessage() { return this.isExecuting ? `正在联系 ${this.currentPeopleName}...` : this.isCompleted ? "联系完成!" : `${this.connectSeconds}秒后联系下一个人。`; }, progress() { return Math.round((this.currentConnectPeopleCount / this.totalConnectPeopleCount) * 100); } }, methods: {
_initPeopleAsync: async function (dfd) {
this.loading = true; var domlis, scrollY = 0; await this.commonUtils.chkAsync(() => {
if (document.querySelectorAll('.search-results__list li.search-result__occlusion-hint').length <= 0) { return true; }
scrollY += 100; if (scrollY > document.body.scrollHeight) scrollY = 0; unsafeWindow.scrollBy(0, scrollY); return false;
}, 20); await this.commonUtils.sleepAsync(2000); domlis = document.querySelectorAll('.search-results__list li'); domlis.forEach(item => {
var btn = item.querySelector('button')
if (btn) { item.style.position = "relative"; this.commonUtils.append(item, ''); }
}); dfd.resolve(); this.loading = false;
}, initPeopleAsync: function () { return this.commonUtils.execByPromiseAsync(this, this._initPeopleAsync); }, sellectedAll() { var cks = document.querySelectorAll('input.people_ck'); cks.forEach(item => { if (!item.checked) item.checked = true }); }, refreshInitPage() { var cks = document.querySelectorAll('input.people_ck'); if (cks.length <= 0) { this.initPeopleAsync(); } else { this.$message({ type: 'success', message: '已刷新选择框!' }); } }, getCheckedPeopleList() { return document.querySelectorAll('input.people_ck:checked'); }, selectedSendText() {
var cks = this.getCheckedPeopleList(); if (cks.length <= 0) {
alert("请选择要联系的人员!")
return;
}
this.refreshSendTextList(); this.textListDialogVisible = true;
}, getSendTextListByCahce() { return GM_getValue(this.sendTextListKey, '[]'); }, setSendTextListToCache() { GM_setValue(this.sendTextListKey, JSON.stringify(this.textListData)); }, deleteSendTextRow(index, rows) { this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { this.$message({ type: 'success', message: '删除成功!' }); rows.splice(index, 1); this.setSendTextListToCache(); }).catch(() => { }); }, toggleSendTextRowStatus(index, enabled) { this.textListData[index].enabled = enabled; }, refreshSendTextList() { var dataJson = this.getSendTextListByCahce(); this.textListData = JSON.parse(dataJson); }, showAddTextContent(isAdd) { if (isAdd) this.editorIdx = -1; this.textAddDialogVisible = true; }, editTextContent(index) { this.editorIdx = index; this.textAddForm.content = this.textListData[index].content; this.showAddTextContent(false); }, updateTextContent(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
if (this.editorIdx != -1) { this.textListData[this.editorIdx].content = this.textAddForm.content; } else { this.textListData.push({ content: this.textAddForm.content, enabled: true }); }
this.setSendTextListToCache(); this.closeTextAddDialog(formName);
} else { return false; }
});
}, closeTextAddDialog(formName) { this.$refs[formName].resetFields(); this.textAddDialogVisible = false; }, closeSelectSendTextDialog() { this.textListDialogVisible = false; }, closeSendingDialog() { this.sendingDialogVisible = false; }, stopSending() { this.$confirm('此操作将终止本轮联系发送, 是否继续?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { this.$message({ type: 'success', message: '取消成功!' }); this.isCompleted = true; }).catch(() => { }); }, beginConnectedPeople() {
var sendList = this.textListData.filter(o => o.enabled); if (sendList.length <= 0) { alert("没有可发送文本"); return; }
var cks = this.getCheckedPeopleList(); if (cks.length <= 0) {
alert("没有可以联系的人员!")
return;
}
this.selectedCheckboxList = Array.prototype.slice.call(cks); this.sendList = sendList; this.closeSelectSendTextDialog(); this.totalConnectPeopleCount = this.selectedCheckboxList.length; this.currentConnectPeopleCount = 0; this.sendingDialogVisible = true; this.connect();
}, connect() {
if (this.selectedCheckboxList.length <= 0) { this.isCompleted = true; return; }
this.isCompleted = false; this.currentConnectPeopleCount += 1; this.currentPeopleName = ''; this.isExecuting = true; setTimeout(this.doConnectAsync.bind(this, this.selectedCheckboxList.shift()), 100);
}, async doConnectAsync(checkbox) {
var item = checkbox.parentElement; var btn = item.querySelector('button.search-result__action-button'); var idx = this.commonUtils.getRandom(0, this.sendList.length - 1); var sendText = this.sendList[idx].content; if (sendText.indexOf("{{Name}}") != -1) {
var replaceText = ''; var actorNameDom = item.querySelector('span.actor-name'); if (actorNameDom) { replaceText = actorNameDom.innerHTML.split(' ')[0]; }
this.currentPeopleName = replaceText; sendText = sendText.replace('{{Name}}', replaceText);
}
this.commonUtils.log(sendText); if (btn && btn.innerHTML.toLowerCase().indexOf('connect') != -1) { await this.doSimpleConnectAsync(btn, sendText); } else { await this.doDetailConnectAsync(item, sendText); }
this.isExecuting = false; if (this.selectedCheckboxList.length > 0) { this.connectSeconds = this.commonUtils.getRandom(10, 60); await this.commonUtils.chkAsync(() => { if (this.isCompleted) return true; this.connectSeconds -= 1; return this.connectSeconds <= 0; }, 1000); }
if (!this.isCompleted) { setTimeout(this.connect.bind(this), 100); }
}, async doSimpleConnectAsync(connectBtn, sendText) {
connectBtn.click(); await this.commonUtils.sleepAsync(1000); var addNoteBtn = document.querySelector('button[aria-label="Add a note"]'); if (addNoteBtn) {
this.commonUtils.log("simple addNoteBtn!")
addNoteBtn.click(); await this.commonUtils.sleepAsync(1000); var textdom = document.querySelector('#custom-message'); textdom.value = sendText; this.commonUtils.fireKeyEvent(textdom, "keyup"); await this.commonUtils.sleepAsync(1000); var doneBtn = document.querySelector('button[aria-label="Done"]'); if (doneBtn) {
this.commonUtils.log("simple doneBtn!")
doneBtn.click(); await this.commonUtils.chkAsync(() => { var chkDom = document.querySelector('#artdeco-modal-outlet'); return !chkDom || chkDom.innerHTML.trim().length <= 0 }, 1000);
}
}
}, async doDetailConnectAsync(item, sendText) { var a, href; if ((a = item.querySelector('a')) && (href = a.getAttribute('href')) && href.indexOf('/in/') != -1) { GM_setValue("linkin_sendtext", sendText); await this.openLoadPageAsync(unsafeWindow.location.origin + href); } else { this.commonUtils.log("无法跳转详情!"); } }, _valueChangeListener: function (dfd, listennerName, listennerTabName, name, old_v, new_v, remote) { if (new_v && remote) { this.commonUtils.log(new_v); GM_deleteValue(name); GM_removeValueChangeListener(this[listennerName]); delete this[listennerName]; if (this[listennerTabName]) this[listennerTabName].close(); delete this[listennerTabName]; dfd.resolve(); } }, openLoadPageAsync: async function (url) {
return this.commonUtils.execByPromiseAsync(this, dfd => {
var index = 0; var name = +new Date() + '_' + index, listennerName = "listener_" + index, listennerTabName = 'listener_tab_' + index; if (url.indexOf('?') == -1) { url += "?" } else { url += "&" }
url += `${this.commonUtils.queueStorageName}=${name}`; this[listennerName] = GM_addValueChangeListener(name, this._valueChangeListener.bind(this, dfd, listennerName, listennerTabName)); this[listennerTabName] = GM_openInTab(url);
});
},
}
});
}
}).run(this.commonUtils);
}, UserInfoFn() {
({
async _getInfoAsync(dfd) {
this.commonUtils.log("_getInfoAsync"); await this.commonUtils.chkAsync(() => document.querySelectorAll('artdeco-dropdown-item.pv-s-profile-actions').length > 0); this.commonUtils.log("_getInfoAsync wait complete"); var items = document.querySelectorAll('artdeco-dropdown-item.pv-s-profile-actions')
var item = Array.prototype.find.call(items, o => { var span = o.querySelector('span.pv-s-profile-actions__label'); if (!span) return false; return span.innerHTML.toLowerCase().indexOf('connect') != -1; }); var message = "not"; if (item) {
this.commonUtils.log("item"); item.click(); await this.commonUtils.sleepAsync(1000); var addNoteBtn = document.querySelector('button[aria-label="Add a note"]'); if (addNoteBtn) { this.commonUtils.log("addNoteBtn"); addNoteBtn.click(); await this.commonUtils.sleepAsync(1000); var textdom = document.querySelector('#custom-message'); textdom = GM_getValue("linkin_sendtext", ''); this.commonUtils.fireKeyEvent(textdom, "keyup"); await this.commonUtils.sleepAsync(1000); var doneBtn = document.querySelector('button[aria-label="Done"]'); if (doneBtn) { this.commonUtils.log("doneBtn"); doneBtn.click(); await this.commonUtils.chkAsync(() => { var chkDom = document.querySelector('#artdeco-modal-outlet'); return !chkDom || chkDom.innerHTML.trim().length <= 0 }, 1000); } }
message = "ok";
}
dfd.resolve(message);
}, getInfoAsync() { return this.commonUtils.execByPromiseAsync(this, this._getInfoAsync); }, run(commonUtils) { this.commonUtils = commonUtils; var name = this.commonUtils.getQueryVariable(this.commonUtils.queueStorageName); this.getInfoAsync().then(function (rs) { this.commonUtils.log("name:" + name); this.commonUtils.log("getmessage:" + rs); GM_setValue(name, rs); }.bind(this)); }
}).run(this.commonUtils);
}, run: function () { if (unsafeWindow.location.href.toLowerCase().indexOf('linkedin.com/search/results/people') != -1) { this.listPageMethods(); } else { this.UserInfoFn(); } }
}).run();
})();