Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix: Exporting your own multi-lingual profile, overriding defaultLocale #37

Merged
merged 4 commits into from
Dec 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
"browser": true
},
"rules": {
"no-self-assign": "off",
"no-param-reassign": "off",
"no-underscore-dangle": "off",
"no-plusplus": "off",
"no-useless-escape": "off",
Expand Down
33 changes: 31 additions & 2 deletions LinkedIn-Notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ Back to main README: [click here](./README.md)
- V2 Docs:
- https://docs.microsoft.com/en-us/linkedin/
- https://developer.linkedin.com/docs/guide/v2
- Another project that uses the unofficial Voyager API: [tomquirk/linkedin-api](https://github.com/tomquirk/linkedin-api)
- Other projects that use the unofficial Voyager API:
- [tomquirk/linkedin-api](https://github.com/tomquirk/linkedin-api)
- [eilonmore/linkedin-private-api](https://github.com/eilonmore/linkedin-private-api)
- LinkedIn DataHub (this powers a lot of the backend)
- [Blog Post](https://engineering.linkedin.com/blog/2019/data-hub)
- [Github Repo](https://github.com/linkedin/datahub)
Expand Down Expand Up @@ -76,5 +78,32 @@ Here are some quick notes on Voyager responses and how data is grouped / nested:
- LI has limits on certain endpoints, and the amount of nested elements it will return
- See [PR #23](https://github.com/joshuatz/linkedin-to-jsonresume/pull/23) for an example of how this was implemented

### Voyager - Misc Notes
- Make sure you always include the `Host` header if making requests outside a web browser (browsers will automatically include this for you)
- Value should be: `www.linkedin.com`
- If you forget it, you will get 400 error (`invalid hostname`)
- For inline data, `<code></code>` with request payload usually ***follows*** `<img><code></code>` with *response* payload
- It appears as though whatever language the profile was ***first*** created with sticks as the "principal language", regardless if user changes language settings (more on this below).
- You can find this under the main profile object, where you would find `supportedLocales` - the default / initial locale is under - `defaultLocale`

### Voyager - Multilingual and Locales Support
> LI seems to be making changes related to this; this section might not be 100% up-to-date.

There are some really strange quirks around multi-locale profiles. When a multi-locale user is logged in and requesting *their own* profile, LI will *refuse* to let the `x-li-lang` header override the `defaultLocale` as specified by the profile (see [issue #35](https://github.com/joshuatz/linkedin-to-jsonresume/issues/35)). However, if *someone else* exports their profile, the same exact endpoints will respect the header and will return the correct data for the requested locale (assuming creator made a version of their profile with the requested locale).

Even stranger, this quirk only seems to apply to *certain* endpoints; e.g. `/me` respects the requested language, but `/profileView` does not (and *always* returns data corresponding with `defaultLocale`) 🙃

Furthermore, the `/dash` subset of endpoints does not ever (AFAIK) change the main key-value pairs based on `x-li-lang`; instead, it nests multi-locale data under `multiLocale` prefixed keys. For example:

```json
{
"firstName": "Алексе́й",
"multiLocaleFirstName": {
"ru_RU": "Алексе́й",
"en_US": "Alexey"
}
}
```

## LinkedIn TS Types
I've put some basics LI types in my `global.d.ts`. Eventually, it would be nice to re-write the core of this project as TS, as opposed to the current VSCode-powered typed JS approached.
I've put some basics LI types in my `global.d.ts`. Eventually, it would be nice to re-write the core of this project as TS, as opposed to the current VSCode-powered typed JS approached.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# LinkedIn Profile to JSON Resume Browser Tool ![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/joshuatz/linkedin-to-jsonresume)

> An extremely easy-to-use browser extension for exporting your full LinkedIn Profile to a JSON Resume file or string.

## Chrome Extension 📦 - [Webstore Link](https://chrome.google.com/webstore/detail/json-resume-exporter/caobgmmcpklomkcckaenhjlokpmfbdec)

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

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

Expand Down
24 changes: 15 additions & 9 deletions browser-ext/popup.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ const STORAGE_KEYS = {
const SPEC_SELECT = /** @type {HTMLSelectElement} */ (document.getElementById('specSelect'));
/** @type {SchemaVersion[]} */
const SPEC_OPTIONS = ['beta', 'stable', 'latest'];
/** @type {HTMLSelectElement} */
const LANG_SELECT = document.querySelector('.langSelect');

/**
* Generate injectable code for capturing a value from the contentScript scope and passing back via message
Expand Down Expand Up @@ -41,13 +43,20 @@ const getLangStringsCode = `(async () => {
})();
`;

/**
* Get the currently selected lang locale in the selector
*/
const getSelectedLang = () => {
return LANG_SELECT.value;
};

/**
* Get JS string that can be eval'ed to get the program to run and show output
* Note: Be careful of strings versus vars, escaping, etc.
* @param {SchemaVersion} version
*/
const getRunAndShowCode = (version) => {
return `liToJrInstance.parseAndShowOutput('${version}');`;
return `liToJrInstance.preferLocale = '${getSelectedLang()}';liToJrInstance.parseAndShowOutput('${version}');`;
};

/**
Expand All @@ -66,14 +75,12 @@ const toggleEnabled = (isEnabled) => {
* @param {string[]} langs
*/
const loadLangs = (langs) => {
/** @type {HTMLSelectElement} */
const selectElem = document.querySelector('.langSelect');
selectElem.innerHTML = '';
LANG_SELECT.innerHTML = '';
langs.forEach((lang) => {
const option = document.createElement('option');
option.value = lang;
option.innerText = lang;
selectElem.appendChild(option);
LANG_SELECT.appendChild(option);
});
toggleEnabled(langs.length > 0);
};
Expand Down Expand Up @@ -171,13 +178,12 @@ document.getElementById('liToJsonButton').addEventListener('click', async () =>

document.getElementById('liToJsonDownloadButton').addEventListener('click', () => {
chrome.tabs.executeScript({
code: `liToJrInstance.parseAndDownload();`
code: `liToJrInstance.preferLocale = '${getSelectedLang()}';liToJrInstance.parseAndDownload();`
});
});

document.getElementById('langSelect').addEventListener('change', (evt) => {
const updatedLang = /** @type {HTMLSelectElement} */ (evt.target).value;
setLang(updatedLang);
LANG_SELECT.addEventListener('change', () => {
setLang(getSelectedLang());
});

document.getElementById('vcardExportButton').addEventListener('click', () => {
Expand Down
11 changes: 7 additions & 4 deletions global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ declare global {
interface LiEntity {
$type: string;
entityUrn: LiUrn;
objectUrn?: LiUrn;
[key: string]: any;
paging?: LiPaging;
}
Expand Down Expand Up @@ -82,9 +83,9 @@ declare global {
// Methods
getElementKeys: () => string[];
getElements: () => Array<LiEntity & {key: LIUrn}>;
getValueByKey: (key: string) => LiEntity;
getValuesByKey: (key: LiUrn, optTocValModifier?: TocValModifier) => LiEntity[];
getElementsByType: (typeStr: string) => LiEntity[];
getValueByKey: (key: string | string[]) => LiEntity;
getValuesByKey: (key: LiUrn | LiUrn[], optTocValModifier?: TocValModifier) => LiEntity[];
getElementsByType: (typeStr: string | string []) => LiEntity[];
getElementByUrn: (urn: string) => LiEntity | undefined;
/**
* Get multiple elements by URNs
Expand Down Expand Up @@ -116,7 +117,9 @@ declare global {
type CaptureResult = 'success' | 'fail' | 'incomplete' | 'empty';

interface ParseProfileSchemaResultSummary {
profileObj: LiResponse;
liResponse: LiResponse;
profileInfoObj?: LiEntity;
profileSrc: 'profileView' | 'dashFullProfileWithEntities';
pageUrl: string;
localeStr?: string;
parseSuccess: boolean;
Expand Down
Loading