Skip to content

Commit

Permalink
Merge pull request #2 from joshuatz/browser-extension
Browse files Browse the repository at this point in the history
Bookmarklet to Browser Extension Conversion
  • Loading branch information
joshuatz authored Aug 4, 2019
2 parents c1ade43 + f28b74e commit dba003b
Show file tree
Hide file tree
Showing 19 changed files with 1,217 additions and 565 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
node_modules
build
build-bookmarklet
build-browserext
webstore-zips
!build/.gitignore
.vscode
*.pem
*.crx
62 changes: 50 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,73 @@
# LinkedIn Profile to JSON Resume Bookmarklet

## Breaking issue - see [this](https://github.com/joshuatz/linkedin-to-jsonresume-bookmarklet/issues/1) for details.
## Chrome Extension - [Here](https://chrome.google.com/webstore/detail/jcaldklkmnjfpjaboilcejindjejbklh/)

## My LinkedIn Profile :) - [https://www.linkedin.com/in/joshuatzucker/](https://www.linkedin.com/in/joshuatzucker/)

![Demo GIF](demo.gif "Demo Gif")

# Updates:
## Usage / Installation Options:
There are a few different options for how to use this:
- **Fast and simple**: Chrome Extension - [Get it here](https://chrome.google.com/webstore/detail/jcaldklkmnjfpjaboilcejindjejbklh/)
- Feel free to install, use, and then immediately uninstall if you just need a single export
- No data is collected
- **Fast, but manual**: Using browser dev console
- Step 1: Copy the code from [`main.js`](https://github.com/joshuatz/linkedin-to-jsonresume/blob/master/src/main.js) into your clipboard
- Step 2: Navigate to a LinkedIn profile page, then paste the code into your console and run it
- Step 3: Copy and run the following code:
```javascript
(new LinkedinToResumeJson(false,false)).parseAndShowOutput();
```
- [***Deprecated***] (at least for now): Bookmarklet
- This was originally how this tool worked, but had to be retired as a valid method when LinkedIn added a stricter CSP that prevented it from working
- Code to generate the bookmarklet is still in this repo if LI ever loosens the CSP

## Troubleshooting
When in doubt, refresh the profile page before using this tool.

---

## Updates:
### 8/3/2019:
> Rewrote this tool as a browser extension instead of a bookmarklet to get around the CSP issue. Seems to work great!
### 7/22/2019:
> ***ALERT***: This bookmarklet is currently broken, thanks to LinkedIn adding a new restrictive CSP (Content Security Policy) header to the site. [I've opened an issue](https://github.com/joshuatz/linkedin-to-jsonresume-bookmarklet/issues/1) to discuss this, and both short-term (requires using the console) and long-term (browser extension) solutions.
### 6/21/2019:
> I saw the bookmarklet was broken depending on how you came to the profile page, so I refactored a bunch of code and found a much better way to pull the data. Should be much more reliable!
# Installation:
You can either build it yourself, or grab the bookmarklet off my project page, [here](https://joshuatz.com/projects/web-stuff/linkedin-profile-to-json-resume-exporter-bookmarklet).

Note that because of LinkedIn's CORs/injection policies, I can't make an auto-updated version of this bookmarklet. If something breaks, you will have to come back here to grab an updated version (if I release it).
---
## What is JSON Resume?
"JSON Resume" is an open-source standard / schema, currently gaining in adoption, that standardizes the content of a resume into a shared underlying structure that others can use in automated resume formatters, parsers, etc. Read more about it [here](https://jsonresume.org/), or on [GitHub](https://github.com/jsonresume).
## What is this bookmarklet?
I made this because I wanted a way to quickly generate a JSON Resume export from my LinkedIn profile, and got frustrated with how locked down the LinkedIn APIs are and how slow it is to request your data export (up to 72 hours). "Install" the bookmarklet to your browser, then click to run it while looking at a LinkedIn profile (preferably your own), and my code will grab the various pieces of information off the page and then show a popup with the full JSON resume export that you can copy and paste to wherever you would like.
## What is this tool?
I made this because I wanted a way to quickly generate a JSON Resume export from my LinkedIn profile, and got frustrated with how locked down the LinkedIn APIs are and how slow it is to request your data export (up to 72 hours). "Install" the tool to your browser, then click to run it while looking at a LinkedIn profile (preferably your own), and my code will grab the various pieces of information off the page and then show a popup with the full JSON resume export that you can copy and paste to wherever you would like.
# Development
---
## Development
> With the rewrite to a browser extension, I actually configured the build scripts to be able to still create a bookmarklet from the same codebase, in case the bookmarklet ever becomes a viable option again.
### Building the browser extension
`npm run build-browserext` will transpile and copy all the right files to `./build-browserext`, which you can then sideload into your browser. If you want to produce a single ZIP archive for the extension, `npm run package-browserext` will do that.
### Building the bookmarklet version
Currently, the build process looks like this:
- `src/main.js` -> (`webpack + babel`) -> `build/main.js` -> [`mrcoles/bookmarklet`](https://github.com/mrcoles/bookmarklet) -> `build/bookmarklet_export.js` -> `build/install-page.html`
- The bookmark can then be dragged to your bookmarks from the final `build/install-page.html`
All of the above should happen automatically when you do `npm run build`.
All of the above should happen automatically when you do `npm run build-bookmarklet`.
If this ever garners enough interest and needs to be updated, I will probably want to re-write it with TypeScript to make it more maintainable.
# DISCLAIMER:
This tool is not affiliated with LinkedIn in any manner. Intended use is to export your own profile data, and you, as the user, are responsible for using it within the terms and services set out by LinkedIn. I am not resonsible for any misuse, or reprecussions of said misuse.
---
## DISCLAIMER:
This tool is not affiliated with LinkedIn in any manner. Intended use is to export your own profile data, and you, as the user, are responsible for using it within the terms and services set out by LinkedIn. I am not resonsible for any misuse, or reprecussions of said misuse.
## Attribution:
Icon for browser extension:
- [https://www.iconfinder.com/icons/2470406/brand_linkedin_online_icon](https://www.iconfinder.com/icons/2470406/brand_linkedin_online_icon)
- Creative Commons (Attribution 3.0 Unported)
- [Full license](https://creativecommons.org/licenses/by/3.0/)
File renamed without changes.
18 changes: 18 additions & 0 deletions browser-ext/background.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* === Handle Toggling of Button Action based on domain match ===
* This is only necessary because we are using `page_action` instead of `browser_action`
*/
chrome.runtime.onInstalled.addListener(function(){
chrome.declarativeContent.onPageChanged.removeRules(undefined,function(){
chrome.declarativeContent.onPageChanged.addRules([{
conditions: [
new chrome.declarativeContent.PageStateMatcher({
pageUrl: {
hostContains: 'linkedin.com'
},
})
],
actions: [new chrome.declarativeContent.ShowPageAction()]
}]);
});
});
Binary file added browser-ext/icon-128.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added browser-ext/icon-16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added browser-ext/icon-48.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions browser-ext/popup.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>LinkedIn to JSON Resume Extension</title>
<link rel="stylesheet" href="styles.css">
</head>

<body>
<div class="buttonWrapper">
<div id="liToJsonButton" class="mainExportButton">LinkedIn Profile to JSON</div>
</div>
<div class="fullCenter bottomButtonsWrapper">
<a class="bottomButton" href="https://github.com/joshuatz/linkedin-to-jsonresume" target="_blank"><div class="">Info</div></a>
<a class="bottomButton" href="https://github.com/joshuatz/linkedin-to-jsonresume/issues/new" target="_blank"><div class="">Report Issue</div></a>
</div>
<script src="popup.js"></script>
</body>

</html>
14 changes: 14 additions & 0 deletions browser-ext/popup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
document.getElementById('liToJsonButton').addEventListener('click',function(){
chrome.tabs.executeScript({
file: 'main.js'
},function(){
chrome.tabs.executeScript({
code: '(new LinkedinToResumeJson(false,false)).parseAndShowOutput();'
},function(){
setTimeout(function(){
// Close popup
window.close();
},700);
});
});
});
70 changes: 70 additions & 0 deletions browser-ext/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
body {
min-width: 350px;
min-height: 100px;
}
.mainExportButton {
font-size: 21px;
-moz-box-shadow:inset 0px 1px 0px 0px #97c4fe;
-webkit-box-shadow:inset 0px 1px 0px 0px #97c4fe;
box-shadow:inset 0px 1px 0px 0px #97c4fe;
background:-webkit-gradient(linear, left top, left bottom, color-stop(0.05, #3d94f6), color-stop(1, #1e62d0));
background:-moz-linear-gradient(top, #3d94f6 5%, #1e62d0 100%);
background:-webkit-linear-gradient(top, #3d94f6 5%, #1e62d0 100%);
background:-o-linear-gradient(top, #3d94f6 5%, #1e62d0 100%);
background:-ms-linear-gradient(top, #3d94f6 5%, #1e62d0 100%);
background:linear-gradient(to bottom, #3d94f6 5%, #1e62d0 100%);
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#3d94f6', endColorstr='#1e62d0',GradientType=0);
background-color:#3d94f6;
-moz-border-radius:6px;
-webkit-border-radius:6px;
border-radius:6px;
border:1px solid #337fed;
display:inline-block;
cursor:pointer;
color:#ffffff;
font-family:Arial;
font-weight:bold;
padding:6px 24px;
text-decoration:none;
text-shadow:0px 1px 0px #1570cd;
}
.mainExportButton:hover {
background:-webkit-gradient(linear, left top, left bottom, color-stop(0.05, #1e62d0), color-stop(1, #3d94f6));
background:-moz-linear-gradient(top, #1e62d0 5%, #3d94f6 100%);
background:-webkit-linear-gradient(top, #1e62d0 5%, #3d94f6 100%);
background:-o-linear-gradient(top, #1e62d0 5%, #3d94f6 100%);
background:-ms-linear-gradient(top, #1e62d0 5%, #3d94f6 100%);
background:linear-gradient(to bottom, #1e62d0 5%, #3d94f6 100%);
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#1e62d0', endColorstr='#3d94f6',GradientType=0);
background-color:#1e62d0;
}
.mainExportButton:active {
position:relative;
top:1px;
}

.bottomButtonsWrapper {
margin-top: 12px;
}

.buttonWrapper, .fullCenter {
width: 100%;
text-align: center;
}

.bottomButton {
width: 40%;
margin-right: 1%;
background-color: #283E4A;
color: white;
font-size: 1.1rem;
text-decoration: none;
display: inline-block;
padding: 2%;
border-radius: 10px;
border: 1px solid #283E4A;
}
.bottomButton:hover {
color: #283E4A;
background-color: white;
}
31 changes: 31 additions & 0 deletions build-bookmarklet.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Dependencies
const replace = require('replace');
const fse = require('fs-extra');
const childProc = require('child_process');

// Paths
const buildFolder = './build-bookmarklet/';
const installFileHtml = buildFolder + 'install-page.html';
const srcFolder = './src/';

// Copy src to build and then append some JS that will auto-execute when ran
fse.copyFileSync(srcFolder + 'main.js', buildFolder + 'main.js');
fse.appendFileSync(buildFolder + 'main.js','window.linkedinToResumeJsonConverter = new LinkedinToResumeJson(null,true);\nwindow.linkedinToResumeJsonConverter.parseAndShowOutput();');

// Run bookmarklet converter
childProc.execSync('bookmarklet ./build/main.js ./build-bookmarklet/bookmarklet_export.js');

// Get entire contents of processed bookmarklet code as var
var bookmarkletContent = fse.readFileSync(buildFolder + 'bookmarklet_export.js');

// Copy template install page to build folder
fse.copyFileSync('./bookmarklet-resources/install-page-template.html',installFileHtml);

// Replace placeholder variable in install HTML file with raw contents
replace({
regex : "{{bookmarklet_code}}",
replacement : bookmarkletContent,
paths : [installFileHtml],
recursive : true,
silent : false
});
23 changes: 0 additions & 23 deletions build.js

This file was deleted.

21 changes: 21 additions & 0 deletions manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "LinkedIn JSON Resume Exporter",
"description": "Export a LinkedIn profile page to JSON Resume",
"manifest_version": 2,
"icons": {
"16": "icon-16.png",
"48": "icon-48.png",
"128": "icon-128.png"
},
"page_action": {
"default_popup": "popup.html"
},
"background": {
"scripts": ["background.js"],
"persistent": false
},
"permissions": [
"declarativeContent",
"activeTab"
]
}
33 changes: 33 additions & 0 deletions package-browserext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* @file Creates a ZIP file that contains everything necessary to publish as a browser extension (publish to Chrome webstore)
*/
const fse = require('fs-extra');
const archiver = require('archiver');

// Get version info
const versionString = require("./package.json").version.toString();

var output = fse.createWriteStream("./webstore-zips/build_"+versionString+".zip");
var archive = archiver("zip", {
zlib : {level : 6} // compression level
});

// listen for all archive data to be written
output.on("close", function() {
console.log(archive.pointer() + " total bytes");
console.log("archiver has been finalized and the output file descriptor has closed.");
});

// good practice to catch this error explicitly
archive.on("error", function(err) {
throw err;
});

// pipe archive data to the file
archive.pipe(output);

// append files from a directory
archive.directory("./build-browserext/","");

// finalize the archive (ie we are done appending files but streams have to finish yet)
archive.finalize();
Loading

0 comments on commit dba003b

Please sign in to comment.