Greasy Fork is available in English.
基于#英好友列表界面,导出好友信息 excel
- // ==UserScript==
- // @name #英导出好友信息
- // @namespace ༺黑白༻
- // @version 1.3
- // @description 基于#英好友列表界面,导出好友信息 excel
- // @author Paul
- // @connect *
- // @match *linkedin.com/search/r###lts/people*
- // @include *linkedin.com/search/r###lts/people*
- // @match *linkedin.com/mynetwork/invite-connect/connections/*
- // @include *linkedin.com/mynetwork/invite-connect/connections/*
- // @match *linkedin.com/in/*/detail/contact-info/*
- // @include *linkedin.com/in/*/detail/contact-info/*
- // @require https://lib.baomitu.com/vue/2.6.11/vue.js
- // @require https://lib.baomitu.com/element-ui/2.12.0/index.js
- // @resource elementui https://lib.baomitu.com/element-ui/2.12.0/theme-chalk/index.css
- // @require https://cdn.staticfile.org/xlsx/0.15.1/xlsx.core.min.js
- // @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
- // @license MIT
- // ==/UserScript==
- (function () {
- 'use strict';
- class AntBase {
- constructor() {
- this.queueStorageName = 'local_name';
- this.ArrayPrototype = Array.prototype;
- }
- appendHtml(html, dom = document.body) {
- var temp = document.createElement('div');
- temp.innerHTML = html;
- var frag = document.createDocumentFragment();
- frag.appendChild(temp.firstElementChild);
- dom.appendChild(frag);
- }
- execByPromiseAsync(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);
- });
- }
- waitAsync(chkFn, ts = 1000) {
- var hasChkFn = typeof chkFn == 'function';
- var setTimeoutFn = hasChkFn ?
- async (dfd) => {
- var chkR###lt = chkFn();
- var resolve = chkR###lt == null ? false : typeof chkR###lt == 'object' ? chkR###lt.success : chkR###lt;
- if (resolve) {
- dfd.resolve(chkR###lt);
- }
- else setTimeout(setTimeoutFn, ts, dfd);
- }
- : (dfd) => {
- setTimeout(() => dfd.resolve(), ts);
- }
- return this.execByPromiseAsync(this, setTimeoutFn);
- }
- sleepAsync(ts = 1000) {
- return this.waitAsync(null, ts);
- }
- getRandom(n, m) {
- return parseInt(Math.random() * (m - n + 1) + n);
- }
- log(msg) {
- console.log(msg);
- }
- appendURLParam(url, name, val) {
- if (typeof url != 'string' || url.length <= 0) return url;
- if (url.indexOf('?') == -1) {
- url += "?"
- } else {
- url += "&"
- }
- return url += `${name}=${val}`;
- }
- getURLParam(name) {
- var query = unsafeWindow.location.search.substring(1);
- var vars = query.split("&");
- for (var i = 0; i < vars.length; i++) {
- var pair = vars[i].split("=");
- if (pair[0] == name) { return pair[1]; }
- }
- return "";
- }
- appendURLStorageParam(url, val) {
- return this.appendURLParam(url, this.queueStorageName, val);
- }
- getURLStorageParam() {
- return this.getURLParam(this.queueStorageName);
- }
- fireKeyEvent(el, evtType, keyCode) {
- var evtObj;
- if (document.createEvent) {
- if (unsafeWindow.KeyEvent) {//firefox 浏览器下模拟事件
- evtObj = document.createEvent('KeyEvents');
- evtObj.initKeyEvent(evtType, true, true, unsafeWindow, true, false, false, false, keyCode, 0);
- } else {//chrome 浏览器下模拟事件
- evtObj = document.createEvent('UIEvents');
- evtObj.initUIEvent(evtType, true, true, unsafeWindow, 1);
- delete evtObj.keyCode;
- if (typeof evtObj.keyCode === "undefined") {//为了模拟keycode
- Object.defineProperty(evtObj, "keyCode", { value: keyCode });
- } else {
- evtObj.key = String.fromCharCode(keyCode);
- }
- if (typeof evtObj.ctrlKey === 'undefined') {//为了模拟ctrl键
- Object.defineProperty(evtObj, "ctrlKey", { value: true });
- } else {
- evtObj.ctrlKey = true;
- }
- }
- el.dispatchEvent(evtObj);
- } else if (document.createEventObject) {//IE 浏览器下模拟事件
- evtObj = document.createEventObject();
- evtObj.keyCode = keyCode
- el.fireEvent('on' + evtType, evtObj);
- }
- }
- find(source, fn) {
- return this.ArrayPrototype.find.call(source, fn);
- }
- filter(source, fn) {
- return this.ArrayPrototype.filter.call(source, fn);
- }
- }
- class TMBase extends AntBase {
- constructor() {
- super();
- this.GM_getValue_old = GM_getValue;
- this.GM_setValue = GM_setValue;
- this.GM_deleteValue = GM_deleteValue;
- this.GM_addValueChangeListener = GM_addValueChangeListener;
- this.GM_openInTab = GM_openInTab;
- this.GM_removeValueChangeListener = GM_removeValueChangeListener;
- this.GM_addStyle = GM_addStyle;
- this.GM_getResourceText = GM_getResourceText;
- }
- GM_getValue(name, defaultValue = '') {
- return this.GM_getValue_old(name, defaultValue);
- }
- }
- class PersonListAnt extends TMBase {
- constructor() {
- super();
- this.App = null;
- this.GM_addStyle(this.GM_getResourceText("elementui"));
- // 加载 element 字体
- this.GM_addStyle('@font-face{font-family:element-icons;src:url(https://lib.baomitu.com/element-ui/2.12.0/theme-chalk/fonts/element-icons.woff) format("woff"),url(https://lib.baomitu.com/element-ui/2.12.0/theme-chalk/fonts/element-icons.ttf) format("truetype");font-weight:400;font-display:"auto";font-style:normal}');
- var id = `vue${Date.now()}`;
- this.GM_addStyle(`
- .${id}-drawerswitch{
- position: fixed;
- bottom: 0;
- left: 0;
- height: 60px;
- width: 60px;
- z-index: 999;
- border-radius: 50%;
- background-color: #fff; }
- `);
- // 创建Vue承载容器
- this._buildHtml(id);
- // // 创建Vue
- this.App = this._buildVue();
- this.App.instance = this;
- this.App.$mount(`#${id}`);
- }
- _buildHtml(id) {
- this.appendHtml(`
- <div id="${id}">
- <el-button class="${id}-drawerswitch" @click="drawer = true" ><i class="el-icon-thumb"></i></el-button>
- <el-drawer
- title="抓取用户信息"
- :visible.sync="drawer"
- :close-on-press-escape="false"
- :before-close="closeDrawer"
- direction="ltr"
- size="20%"
- >
- <el-container>
- <el-main>
- <el-row>
- <el-col :span="8"><el-button type="primary" @click="refresh">刷新</el-button></el-col>
- </el-row>
- <el-row style="margin-top:10px;">
- <el-col :span="8"><el-button type="primary" @click="starting" :disabled="!canExecute" >开始抓取</el-button></el-col>
- <el-col :span="5"> </el-col>
- <el-col :span="8"><el-button type="primary" @click="stop" :disabled="!this.isRunning" >停止</el-button></el-col>
- </el-row>
- <el-row style="margin-top:10px;">
- <el-col :span="8">进度(共{{progressTotal}} 个):</el-col>
- <el-col :span="14"><el-progress :text-inside="true" :stroke-width="20" :percentage="progress"></el-progress></el-col>
- </el-row>
- <el-row style="margin-top:10px;">
- <el-col :span="24"><el-button type="primary" :disabled="!canExprot" @click="exportData" >导出</el-button></el-col>
- </el-row>
- </el-main>
- </el-container>
- </el-drawer>
- </div>
- `)
- }
- _buildVue() {
- return new Vue({
- data() {
- this.currentDictionary = {};
- this.userStop = false;
- this.excelCfg = {
- 'firstName': 'firstName',
- 'lastName': 'lastName',
- '历任公司': 'company',
- '职位': 'headline',
- '地址': 'locationName',
- '邮箱': 'Email'
- };
- return {
- drawer: false,
- dataLinks: [],
- isRunning: false,
- progressTotal: 0,
- progressIdx: 0,
- };
- },
- computed: {
- progress: function () {
- if (this.progressTotal <= 0) return 0;
- return Math.round((this.progressIdx / this.progressTotal) * 100);
- },
- canExecute() {
- return !this.isRunning && this.progressIdx != this.progressTotal;
- },
- canExprot() {
- return !this.isRunning && this.progressIdx > 0 ;
- }
- },
- methods: {
- refresh() {
- var links = document.querySelectorAll('.mn-connection-card a.mn-connection-card__link');
- links.forEach(item => {
- var href = item.getAttribute('href');
- if (!this.currentDictionary.hasOwnProperty(href)) {
- this.currentDictionary[href] = null;
- }
- if (this.currentDictionary[href] === null) {
- this.currentDictionary[href] = {};
- this.dataLinks.push({ href });
- this.progressTotal++;
- }
- });
- },
- closeDrawer(done) {
- if (this.isRunning) return;
- done();
- },
- // 停止
- stop() {
- this.userStop = true;
- },
- // 开始抓取
- starting() {
- if (this.dataLinks.length <= 0) {
- this.$message.error('当前没有可执行的记录哦~~');
- return;
- }
- this.isRunning = true;
- this.userStop = false;
- this.singleExecuteAsync();
- },
- async singleExecuteAsync() {
- var obs = false;
- if (!this.userStop) {
- obs = this.dataLinks.shift();
- }
- if (!obs) {
- this.isRunning = false;
- this.$message({
- message: this.userStop ? '用户停止' : '执行完成!',
- type: 'success'
- });
- return;
- }
- var href = obs.href;
- if (Object.keys(this.currentDictionary[href]).length == 0) {
- this.currentDictionary[href] = await this.openLoadPageAsync(`${unsafeWindow.location.origin}${href}detail/contact-info/`);
- this.progressIdx++;
- }
- setTimeout(this.singleExecuteAsync.bind(this), 100);
- },
- openLoadPageAsync(link) {
- return this.instance.execByPromiseAsync(this, dfd => {
- var index = 0;
- var name = `${Date.now()}_${index}`,
- listennerName = `listener_${name}`,
- listennerTabName = `listener_tab_${name}`;
- link = this.instance.appendURLParam(link, this.instance.queueStorageName, name);
- this[listennerName] = this.instance.GM_addValueChangeListener(name, this._valueChangeListener.bind(this, dfd, listennerName, listennerTabName));
- this[listennerTabName] = this.instance.GM_openInTab(link);
- });
- },
- _valueChangeListener(dfd, listennerName, listennerTabName, name, old_v, new_v, remote) {
- if (new_v && remote) {
- this.instance.log(new_v);
- this.instance.GM_deleteValue(name);
- this.instance.GM_removeValueChangeListener(this[listennerName]);
- delete this[listennerName];
- if (this[listennerTabName]) this[listennerTabName].close();
- delete this[listennerTabName];
- dfd.resolve(JSON.parse(new_v));
- }
- },
- //调整公司数据
- adjustCompanyData: function (key,dataJson) {
- var companyArray =[],values = [];
- try{
- companyArray = JSON.parse(dataJson);
- }catch(e){
- console.log(key,'error');
- }
- companyArray.sort((item1, item2) => {
- if (!item1.hasOwnProperty('dateRange')) return -1;
- if (!item2.hasOwnProperty('dateRange')) return 1;
- if (!item1.dateRange.hasOwnProperty('start')) return -1;
- if (!item2.dateRange.hasOwnProperty('start')) return 1;
- if (!item1.dateRange.start.hasOwnProperty('year')) return -1;
- if (!item2.dateRange.start.hasOwnProperty('year')) return -1;
- var styear = item1.dateRange.start.year - item2.dateRange.start.year;
- if (styear != 0) return styear;
- if (!item1.dateRange.start.hasOwnProperty('month')) return -1;
- if (!item2.dateRange.start.hasOwnProperty('month')) return -1;
- var stmonth = item1.dateRange.start.month - item2.dateRange.start.month;
- if (stmonth != 0) return stmonth;
- if (!item1.dateRange.hasOwnProperty('end')) return -1;
- if (!item2.dateRange.hasOwnProperty('end')) return 1;
- if (!item1.dateRange.end.hasOwnProperty('year')) return -1;
- if (!item2.dateRange.end.hasOwnProperty('year')) return -1;
- var etyear = item1.dateRange.end.year - item2.dateRange.end.year;
- if (etyear != 0) return etyear;
- if (!item1.dateRange.end.hasOwnProperty('month')) return -1;
- if (!item2.dateRange.end.hasOwnProperty('month')) return -1;
- var etmonth = item1.dateRange.end.month - item2.dateRange.end.month;
- if (etmonth != 0) return etmonth;
- return 0;
- });
- companyArray.forEach(function (item) {
- var str = "头衔:";
- if (item.title) {
- str += item.title;
- }
- str += "\n";
- str += "公司名称:"
- if (item.companyName) {
- str += item.companyName;
- }
- str += "\n";
- str += "任期:";
- if (item.dateRange) {
- if (item.dateRange.start) {
- if (item.dateRange.start.year) {
- str += item.dateRange.start.year;
- } else {
- str += "****";
- }
- str += "-";
- if (item.dateRange.start.month) {
- str += item.dateRange.start.month;
- } else {
- str += "**";
- }
- } else {
- str += "****-**";
- }
- str += " 至 "
- if (item.dateRange.end) {
- if (item.dateRange.end.year) {
- str += item.dateRange.end.year;
- } else {
- str += "****";
- }
- str += "-";
- if (item.dateRange.end.month) {
- str += item.dateRange.end.month;
- } else {
- str += "**";
- }
- } else {
- str += "****-**";
- }
- }
- str += "\n-------------------------------";
- values.push(str);
- });
- return values.join('\n');
- },
- exportData: function () {
- var aoa = [], item, fileds = [], name = [], i, len, temp, key, j, jlen, values, filedName;
- for (item in this.excelCfg) {
- fileds.push(this.excelCfg[item]);
- name.push(item);
- }
- aoa.push(name);
- for (key in this.currentDictionary) {
- temp = this.currentDictionary[key];
- if (!temp || typeof (temp) != 'object' || Object.keys(temp).length <= 0) continue;
- values = [];
- for (j = 0, jlen = fileds.length; j < jlen; j++) {
- filedName = fileds[j];
- values.push(filedName == this.excelCfg["历任公司"] ? this.adjustCompanyData(key,temp[filedName]) : temp[filedName]);
- }
- aoa.push(values);
- }
- this.exportExcel(aoa);
- },
- exportExcel: function (aoa) {
- var sheet = XLSX.utils.aoa_to_sheet(aoa);
- this.openDownloadDialog(this.sheet2blob(sheet), "导出数据" + (+new Date()) + '.xlsx');
- },
- sheet2blob: function (sheet, sheetName) {
- sheetName = sheetName || 'sheet1';
- var workbook = {
- SheetNames: [sheetName],
- Sheets: {}
- };
- workbook.Sheets[sheetName] = sheet;
- // 生成excel的配置项
- var wopts = {
- bookType: 'xlsx', // 要生成的文件类型
- bookSST: false, // 是否生成Shared String Table,官方解释是,如果开启生成速度会下降,但在低版本IOS设备上有更好的兼容性
- type: 'binary'
- };
- var wbout = XLSX.write(workbook, wopts);
- var blob = new Blob([s2ab(wbout)], { type: "application/octet-stream" });
- // 字符串转ArrayBuffer
- function s2ab(s) {
- var buf = new ArrayBuffer(s.length);
- var view = new Uint8Array(buf);
- for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
- return buf;
- }
- return blob;
- },
- /**
- * 通用的打开下载对话框方法,没有测试过具体兼容性
- * @param url 下载地址,也可以是一个blob对象,必选
- * @param saveName 保存文件名,可选
- */
- openDownloadDialog: function (url, saveName) {
- if (typeof url == 'object' && url instanceof Blob) {
- url = URL.createObjectURL(url); // 创建blob地址
- }
- var aLink = document.createElement('a');
- aLink.href = url;
- aLink.download = saveName || ''; // HTML5新增的属性,指定保存文件名,可以不要后缀,注意,file:///模式下不会生效
- var event;
- if (window.MouseEvent) event = new MouseEvent('click');
- else {
- event = document.createEvent('MouseEvents');
- event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
- }
- aLink.dispatchEvent(event);
- },
- }
- });
- }
- }
- class UserInfoAnt extends TMBase {
- constructor() {
- super();
- var name = this.getURLParam(this.queueStorageName);
- this.getInfoAsync().then(rs => {
- this.log("name:" + name);
- this.log(rs);
- // 存入数据
- this.GM_setValue(name, JSON.stringify(rs));
- });
- }
- getInfoAsync() {
- return this.execByPromiseAsync(this, this._getInfoAsync);
- }
- async _getInfoAsync(dfd) {
- this.log("_getInfoAsync");
- var chkR###ltInfo = await this.waitAsync(() => {
- var domList = document.querySelectorAll('h2');
- var chkR###lt = this.find(domList, item => item.innerHTML.indexOf('Contact Info') != -1);
- if (!chkR###lt) return false;
- return { success: true, dom: chkR###lt }
- }, 500);
- this.log(chkR###ltInfo);
- var findObj = {};
- // /voyager/api/identity/dash/profiles
- var findDomCode = this.find(document.querySelectorAll('code'), item => item.innerHTML.indexOf('/voyager/api/identity/dash/profiles') != -1 && item.innerHTML.indexOf('FullProfileWithEntities') != -1);
- if (findDomCode) {
- // 找出
- var findCodeObj = JSON.parse(findDomCode.innerHTML);
- var targetCodeDom = document.getElementById(findCodeObj.body);
- if (targetCodeDom) {
- var tempObj = JSON.parse(targetCodeDom.innerHTML);
- // com.linkedin.voyager.dash.deco.identity.profile.FullProfileWithEntities
- var fullProfileWithEntities = this.find(tempObj.included, item => item.hasOwnProperty('$recipeTypes') && item['$recipeTypes'][0] == 'com.linkedin.voyager.dash.deco.identity.profile.FullProfileWithEntities');
- this.log(fullProfileWithEntities);
- if (fullProfileWithEntities) {
- Object.assign(findObj, {
- firstName: fullProfileWithEntities.firstName,
- lastName: fullProfileWithEntities.lastName,
- headline: fullProfileWithEntities.headline,
- locationName: fullProfileWithEntities.locationName
- });
- }
- // com.linkedin.voyager.dash.deco.identity.profile.FullProfilePosition
- var fullProfilePositionArray = this.filter(tempObj.included, item => item.hasOwnProperty('$recipeTypes') && item['$recipeTypes'][0] == 'com.linkedin.voyager.dash.deco.identity.profile.FullProfilePosition');
- this.log(fullProfilePositionArray);
- var companys = [];
- fullProfilePositionArray.forEach(position => {
- var dateRange = position.dateRange || {};
- companys.push({
- title: position.title,
- companyName: position.companyName,
- dateRange: dateRange
- });
- });
- if (companys.length > 0) findObj['company'] = JSON.stringify(companys);
- };
- }
- var findEmailDom = this.find(chkR###ltInfo.dom.parentElement.querySelectorAll('a'), item => item.getAttribute('href').indexOf('mailto:') != -1);
- findObj.Email = findEmailDom ? findEmailDom.innerText : "";
- dfd.resolve(findObj);
- }
- }
- unsafeWindow.Linkin = {};
- if (unsafeWindow.location.href.toLowerCase().indexOf('/detail/contact-info/') != -1) {
- unsafeWindow.Linkin.AutoCollectionAnt = new UserInfoAnt();
- } else {
- unsafeWindow.Linkin.AutoCollectionAnt = new PersonListAnt();
- }
- })();