Skip to content

Commit

Permalink
Merge pull request #22 from Blacksmoke16/5.0.0
Browse files Browse the repository at this point in the history
5.0.0
  • Loading branch information
Blacksmoke16 authored Mar 6, 2018
2 parents 41c55b5 + 6f3b9c2 commit a10baa4
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 86 deletions.
140 changes: 80 additions & 60 deletions GESI.gs
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,16 @@
//
// /u/blacksmoke16 @ Reddit
// @Blacksmoke16#0016 @ Discord
app_version = '4.1.2';
app_version = '5.0.0';
BASE_URL = 'https://esi.tech.ccp.is'

// Your email address
// This is used and sent in the User-Agent header on ESI requests so that CCP know who that request came from.
// In the case of a users' specific script is putting too much load on their servers they have a way to contact you.
// I do not see this or use it in any way.
EMAIL = 'YOUR_EMAIL';

// Setup variables used throughout script
// From your dev app https://developers.eveonline.com/applications
CLIENT_ID = 'YOUR_CLIENT_ID';
CLIENT_SECRET = 'YOUR_CLIENT_SECRET';

// From the script editor -> file -> project properties -> Script ID
SCRIPT_ID = 'YOUR_SCRIPT_ID';

// Array of character names
// Used for authing
CHARACTERS = ['YOUR_CHARACTER_NAME'];

// Current 'main' character used when a function is called without a character name as a param
// Also acts as what character from the CHARACTERS array will be authed
AUTHING_CHARACTER = CHARACTERS[0];
// Name of your 'main' character to use when a function is called without a character name as a param
MAIN_CHARACTER = 'YOUR_MAIN_CHARACTER_NAME';

// List of scopes to request
SCOPES = [
Expand Down Expand Up @@ -100,15 +86,14 @@ SCOPES = [
"esi-wallet.read_character_wallet.v1",
"esi-wallet.read_corporation_wallets.v1"
];

DOCUMENT_PROPERTIES = PropertiesService.getDocumentProperties();
CACHE = CacheService.getDocumentCache();


CACHE = CacheService.getDocumentCache();

function onOpen() {
SpreadsheetApp.getUi().createMenu('GESI')
.addItem('Authorize Sheet', 'showSidebar')
.addSeparator()
.addItem('Reset Auth', 'resetAuth')
.addItem('Check for updates', 'checkForUpdates')
.addToUi();
}

Expand All @@ -124,7 +109,7 @@ function onOpen() {
function parseArray(endpoint_name, column_name, array, opt_headers) {
var headers = [];
var result = [];
var endpoint = findObjectByKey(ENDPOINTS[endpoint_name].headers, 'name', column_name);
var endpoint = findObjectByKey_(ENDPOINTS[endpoint_name].headers, 'name', column_name);
if (opt_headers || undefined === opt_headers) result.push(endpoint.sub_headers.map(function(h) { return h }));

JSON.parse(array).forEach(function(a) {
Expand All @@ -140,13 +125,12 @@ function parseArray(endpoint_name, column_name, array, opt_headers) {
// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

function getData_(endpoint_name, params) {
var authed = ENDPOINTS[endpoint_name].authed;
var path = ENDPOINTS[endpoint_name].path;
var endpoint = ENDPOINTS[endpoint_name]
var path = endpoint.path;
var name = params.name;
if (!name) name = AUTHING_CHARACTER;
if (params.page === -1) params.page = 1;
if (!name) name = MAIN_CHARACTER;

ENDPOINTS[endpoint_name].parameters.forEach(function(param) {
endpoint.parameters.forEach(function(param) {
if (param['in'] === 'path' && params[param.name]) {
path = path.replace('{' + param.name + '}', params[param.name])
} else if (param['in'] === 'query' && params[param.name]) {
Expand All @@ -155,24 +139,24 @@ function getData_(endpoint_name, params) {
}
});

if (path.indexOf('{character_id}') !== -1) path = path.replace('{character_id}', parseInt(DOCUMENT_PROPERTIES.getProperty(name + '_character_id')));
if (path.indexOf('{alliance_id}') !== -1) path = path.replace('{alliance_id}', parseInt(DOCUMENT_PROPERTIES.getProperty(name + '_alliance_id')));
if (path.indexOf('{corporation_id}') !== -1) path = path.replace('{corporation_id}', parseInt(DOCUMENT_PROPERTIES.getProperty(name + '_corporation_id')));

var token = CACHE.get(name + '_access_token');
if (!token) token = refreshToken_(name);
if (path.indexOf('{character_id}') !== -1) path = path.replace('{character_id}', getProperty_(name, 'character_id'));
if (path.indexOf('{alliance_id}') !== -1) path = path.replace('{alliance_id}', getProperty_(name, 'alliance_id'));
if (path.indexOf('{corporation_id}') !== -1) path = path.replace('{corporation_id}', getProperty_(name, 'corporation_id'));

var token = CACHE.get(name + '_access_token');
if (!token && endpoint.authed) token = refreshToken_(name);

return doRequest_(BASE_URL + path, 'get', token);
}

function parseData_(endpoint_name, params) {
var endpoint = ENDPOINTS[endpoint_name];
var data = [];
var result = [];

var opt_headers = params.opt_headers;
if (opt_headers || undefined === opt_headers) result.push(endpoint.headers.map(function(h) { return h.name }));

if (params.page === -1) {
params.page = 1;
var response = getData_(endpoint_name, params)
Expand All @@ -185,7 +169,7 @@ function parseData_(endpoint_name, params) {
} else {
data = getData_(endpoint_name, params).data;
}

if (endpoint.response_type === 'array' && endpoint.item_type === 'object') {
data.forEach(function(obj) {
var temp = [];
Expand Down Expand Up @@ -213,9 +197,10 @@ function parseData_(endpoint_name, params) {

function doRequest_(path, method, token, data) {
var auth = token ? 'Bearer ' + token : 'Basic ' + Utilities.base64EncodeWebSafe(CLIENT_ID + ':' + CLIENT_SECRET)
var options = {'method': method, headers: {'User-Agent': 'GESI user ' + EMAIL,'Content-Type': 'application/json','Authorization': auth}};
var options = {'method': method, 'muteHttpExceptions': true, headers: {'User-Agent': 'GESI user ' + SpreadsheetApp.getActiveSpreadsheet().getOwner().getEmail(),'Content-Type': 'application/json','Authorization': auth}};
if (data) options['payload'] = JSON.stringify(data)
var response = UrlFetchApp.fetch(path, options);
if (response.getResponseCode() !== 200) throw 'ESI response error: ' + JSON.parse(response.getContentText())['error'];
return {data: JSON.parse(response), headers: response.getHeaders()};
}

Expand All @@ -230,6 +215,14 @@ function parseObject_(source, header) {
}
}

function getProperty_(character_name, property) {
return SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Auth Data').getDataRange().getValues().filter(function(r) { return r[0] === character_name })[0][getValue_(property)];
}

function getValue_(property) {
return {character_name: 0, character_id: 1, corporation_id: 2, alliance_id: 3, refresh_token: 4}[property];
}

function extend_(obj, src) {
for (var key in src) {
if (src.hasOwnProperty(key)) obj[key] = src[key];
Expand All @@ -241,12 +234,10 @@ function flatten_(ob) {
var toReturn = {};
for (var i in ob) {
if (!ob.hasOwnProperty(i)) continue;

if ((typeof ob[i]) == 'object') {
var flatObject = flatten_(ob[i]);
for (var x in flatObject) {
if (!flatObject.hasOwnProperty(x)) continue;

toReturn[i + '-' + x] = flatObject[x];
}
} else {
Expand All @@ -256,7 +247,13 @@ function flatten_(ob) {
return toReturn;
};

function findObjectByKey(array, key, value) {
function uniqArray_(arrArg) {
return arrArg.filter(function(elem, pos, arr) {
return arr.indexOf(elem) == pos;
});
};

function findObjectByKey_(array, key, value) {
for (var i = 0; i < array.length; i++) {
if (array[i][key] === value) {
return array[i];
Expand All @@ -274,18 +271,17 @@ function authCallback(request) {
var characterData = extend_(tokenData, getCharacterDetails_(tokenData['access_token']));
var affiliationData = getCharacterAffiliation_(characterData['CharacterID'])[0];
var userData = extend_(characterData, affiliationData);
cacheData_(userData);
cacheData_({character_name: userData['CharacterName'],character_id: userData['character_id'],corporation_id: userData['corporation_id'],alliance_id: userData['alliance_id'],refresh_token: userData['refresh_token']}, userData['access_token']);
return HtmlService.createHtmlOutput('Thank you for using GESI ' + userData['CharacterName']);
}

function showSidebar() {
var scriptUrl = 'https://script.google.com/macros/d/' + SCRIPT_ID + '/usercallback';
var stateToken = ScriptApp.newStateToken().withMethod('authCallback').withTimeout(3600).createToken();
var scriptUrl = 'https://script.google.com/macros/d/' + ScriptApp.getScriptId() + '/usercallback';
var stateToken = ScriptApp.newStateToken().withMethod('authCallback').withTimeout(120).createToken();
var authorizationUrl = 'https://login.eveonline.com/oauth/authorize/?response_type=code&redirect_uri=' + scriptUrl + '&client_id=' + CLIENT_ID + '&scope=' + SCOPES.join('+') + '&state=' + stateToken;
var template = HtmlService.createTemplate('<br><a href="<?= authorizationUrl ?>" target="_blank">Authorize: <?= character ?></a>.');
var template = HtmlService.createTemplate('Click the link below to auth a character for use in GESI<br><br><a href="<?= authorizationUrl ?>" target="_blank">Authorize with EVE SSO</a>.');
template.authorizationUrl = authorizationUrl;
template.character = AUTHING_CHARACTER;
SpreadsheetApp.getUi().showSidebar(template.evaluate());
SpreadsheetApp.getUi().showModalDialog(template.evaluate().setWidth(400).setHeight(250), 'GESI EVE SSO');
}

function getAccessToken_(code) {
Expand All @@ -302,23 +298,47 @@ function getCharacterAffiliation_(character_id) {
}

function refreshToken_(name) {
var response = doRequest_('https://login.eveonline.com/oauth/token', 'post', null, {"grant_type":"refresh_token", "refresh_token": DOCUMENT_PROPERTIES.getProperty(name + '_refresh_token')}).data;
var response = doRequest_('https://login.eveonline.com/oauth/token', 'post', null, {"grant_type":"refresh_token", "refresh_token": getProperty_(name, 'refresh_token')}).data;
CACHE.put(name + '_access_token', response['access_token'], 900);
return response['access_token'];
}

function cacheData_(userData) {
var userProperties = {};
prefix = userData['CharacterName'] + '_';
['character_id', 'corporation_id', 'alliance_id', 'CharacterName', 'refresh_token']
.forEach(function(param) { userProperties[prefix + param] = userData[param]; });
CACHE.put(prefix + 'access_token', userData['access_token'], 900);
DOCUMENT_PROPERTIES.setProperties(userProperties );
function cacheData_(userData, access_token) {
if (!Object.keys(userData).every(function(k) { return ['character_id','corporation_id','alliance_id','character_name','refresh_token', 'access_token'].indexOf(k) !== -1 })) throw 'Required data is missing.';
var user_data = Object.keys(userData).map(function(p) { return userData[p] });

var ss = SpreadsheetApp.getActiveSpreadsheet();
var authSheet = ss.getSheetByName('Auth Data');
if (authSheet === null) {
authSheet = ss.insertSheet('Auth Data').hideSheet();
authSheet.deleteRows(1, authSheet.getMaxRows()-1);
authSheet.deleteColumns(6, authSheet.getMaxColumns()-5);
var protectedData = authSheet.protect().setDescription('Only sheet owner can view auth data');
protectedData.removeEditors(protectedData.getEditors());
protectedData.addEditor(ss.getOwner().getEmail());
}

var savedChars = authSheet.getDataRange().getValues().map(function(r) { return r[0] });
var character_name = userData['character_name'];
savedChars.indexOf(character_name) === -1 ? authSheet.appendRow(user_data) : authSheet.getRange((savedChars.indexOf(character_name) + 1), 1, 1, 5).setValues([user_data]);
[1,2,3,4,5].forEach(function(c) { authSheet.autoResizeColumn(c) });
CACHE.put(character_name + '_access_token', access_token, 900);
}

function resetAuth() {
DOCUMENT_PROPERTIES.deleteAllProperties();
CHARACTERS.forEach(function(character) {
CACHE.remove(character + '_access_token');
});
function checkForUpdates()
{
var newVersion = JSON.parse(UrlFetchApp.fetch('https://api.github.com/repos/Blacksmoke16/GESI/releases/latest'))['tag_name'];
if (newVersion != null) {
var message = 'You are using the latest version of GESI.';
var title = 'No updates found';
if (newVersion > APP_VERSION) {
message = 'A new ';
var newSplit = newVersion.split('.');
var currentSplit = APP_VERSION.split('.');
if (newSplit[0] > currentSplit[0]) { message += 'major'; } else if (newSplit[1] > currentSplit[1]) { message += 'minor'; } else if (newSplit[2] > currentSplit[2]) { message += 'patch'; }
message += ' version of GESI is available on GitHub.';
title = 'GESI version ' + newVersion + ' is available!';
}
SpreadsheetApp.getActiveSpreadsheet().toast(message, title, 5);
}
}
48 changes: 22 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# GESI
Google Sheets Script for interacting with EVE ESI

### Setup for single character use:
## Setup:
1. Create a new google sheet or go to the one you want to use the script on.
2. In the menu bar go to Tools -> Script Editor.
3. Copy the GESI files. You may need to name the project to save it, so use any name.
Expand All @@ -14,48 +14,41 @@ Google Sheets Script for interacting with EVE ESI
* Update the SCRIPT_ID variable towards the top with your copied value.
5. Make a new app on the devsite https://developers.eveonline.com/applications/create.
* Content Type: Authentication & API Access
* PERMISSIONS: Select all ESI endpoints.
* PERMISSIONS: Select all esi-* endpoints.
* CALLBACK URL: https://script.google.com/macros/d/{SCRIPT_ID_COPIED_IN_STEP_FOUR}/usercallback
* Be sure to replace the `{SCRIPT_ID_COPIED_IN_STEP_FOUR}` in the URL with YOUR script ID!
* Also be sure to replace the `{` and `}` as well. Url should look something like this, but with your Script ID:
* `https://script.google.com/macros/d/15lw-cjwWYnHgLU_tmx6KnyHtZ9aR9Q/usercallback`
6. Replace the CLIENT_ID and CLIENT_SECRET variables towards the top with your info from the dev app, and save the script.
7. Replace `YOUR_CHARACTER_NAME` with the name of your character in the CHARACTERS array, and save the script.
7. Replace `YOUR_MAIN_CHARACTER_NAME` with the name of your main (the character to default to if no name is given with a function) character in the MAIN_CHARACTER constant, and save the script.
8. Close the script and refresh the spreadsheet.
9. There will now be a GESI option in the menu bar. Click it and then click 'Authorize Sheet'.
10. Give the script permission to do what it needs.
11. Click 'Authorize Sheet' in the sidenav that opens -> login -> select what character you want to authorize -> Authorize.
12. Close the sidenav.
11. Click 'Authorize with EVE SSO' in the modal that opens -> login -> select what character you want to authorize -> Authorize.
12. Close the modal.
13. (Optional) Repeat step 11 to authorize other characters.
13. Done.

### Setup for multiple character use:
1. Complete steps 1-8 in single character use.
2. In the script there is a constant variable called `CHARACTERS` towards the top which is an array of strings.
* Add the names of the other characters you want to auth to the contents of the array with the names and corporation_ids of the characters you wish to authorize.
* E.x. `CHARACTERS = ['character1', 'character2', 'character3'];`
3. To auth the characters:
* In the script there is another constant variable `AUTHING_CHARACTER` below the `CHARACTERS` constant.
* Complete steps 9-13 in single character use.
* Add one (1) to the number in brackets.
* E.x. `AUTHING_CHARACTER = CHARACTERS[1];`
4. Repeat step 3 until all characters are authorized.
* `AUTHING_CHARACTER = CHARACTERS[1];` -> authorize `character2` using steps 9-13 in single character use.
* `AUTHING_CHARACTER = CHARACTERS[2];` -> authorize `character3` using steps 9-13 in single character use.

### Usage Tips
## Usage Tips

##### Parameter datatype samples
### Checking the version
Use the GESI option in the menu bar and click the `Check for updates` option. Explanations of the types of updates are below:
* New major version - Indicates a major rework of the script, there will most likely be breaking changes that require reauth/edits of your sheet.
* New minor version - New features/additions to the script that will work with current auths. Just copy paste new code in.
* New patch version - Bug fixes and minor improvements that will work with current auths. Just copy paste new code in.

### Parameter datatype samples
| Type | Description | Sample |
|---------|--------------------------------------|------------------------|
| array | A string with comma seperated values | "value1,value2,value3" |
| boolean | True or False | true |
| integer | An integer | 16 |
| string | A string | "value" |

##### Using the parseArray function
### Using the parseArray function
As of now if an endpoint returns a property that is an array of objects nested inside the response, I am JSON stringifying it and displaying it in the column. The `parseArray` allows you to parse that array of values and output it like an endpoint function does, wherever you want to. You supply it with the name of the function the data is from, the column you are parsing, and the cell with the array data. See the documentation above the function in GESI.gs right above the private utility functions header below the scopes list.

##### Changing order of column headers
### Changing order of column headers
1. Find the corresponding object in the ENDPOINTS array in the `endpoints.gs` file
* E.x.
```
Expand Down Expand Up @@ -113,12 +106,15 @@ As of now if an endpoint returns a property that is an array of objects nested i
* The first object in the array is the first column on the sheet
3. If a header has a `sub_headers` array, the first value in that array is first as well.

### Note: In order to use functions that use new scopes, you will have to re-auth your character(s).
## Troubleshooting

### Login failed. Possible reasons can be: Your login session has expired. Please try again.
* Remove scopes you are not using and try again. Happens because sometime the state token + scopes is too long and causes issues.

### Contact Info
## Contact Info
In-game: Blacksmoke16
Discord: Blacksmoke16#0016

### Copyright
## Copyright
EVE Online and the EVE logo are the registered trademarks of CCP hf. All rights are reserved worldwide. All other
trademarks are the property of their respective owners. EVE Online, the EVE logo, EVE and all associated logos and designs are the intellectual property of CCP hf. All artwork, screenshots, characters, vehicles, storylines, world facts or other recognizable features of the intellectual property relating to these trademarks are likewise the intellectual property of CCP hf. CCP hf. has granted permission to GESI to use EVE Online and all associated logos and designs for promotional and information purposes on its website but does not endorse, and is not in any way affiliated with, the GESI. CCP is in no way responsible for the content on or functioning of this website, nor can it be liable for any damage arising from the use of this website.

0 comments on commit a10baa4

Please sign in to comment.