diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dd8b429 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea/ +node_modules/ +package-lock.json diff --git a/LICENSE b/LICENSE index d55abd0..365ce30 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -MIT License +The MIT License (MIT) Copyright (c) 2019 Blacksmoke16 diff --git a/README.md b/README.md index e27a21c..c1baa35 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Google Sheets add-on for interacting with EVE ESI API. ## Setup: -1. Install the add-on [HERE](https://chrome.google.com/webstore/detail/gesi/haaceilfjgofjglobglglnafgnjbekoc?utm_source=permalink). +1. Install the add-on [HERE](https://gsuite.google.com/marketplace/app/gesi/337869328721). 2. Give the script access to what it needs. 3. There will now be a GESI option under the `Add-Ons` option in the menu bar. Click it and then click `GESI => Authorize Characters`. 4. Click the EVE SSO button in the modal. Login => select what character you want to authorize => Authorize. diff --git a/bin/GESI-linux-x86_64 b/bin/GESI-linux-x86_64 index 74248b3..8fcc98e 100755 Binary files a/bin/GESI-linux-x86_64 and b/bin/GESI-linux-x86_64 differ diff --git a/src/parser/eve_swagger.cr b/src/parser/eve_swagger.cr index d7800fb..2079806 100644 --- a/src/parser/eve_swagger.cr +++ b/src/parser/eve_swagger.cr @@ -87,7 +87,7 @@ module EveSwagger # Extract the endpoint version and replace with placeholder to allow user to define what version they wish to use version = path_url.match(/\/(v\d)\//).not_nil![1] - path_url = path_url.sub(/v\d/,"{version}") + path_url = path_url.sub(/v\d/, "{version}") @endpoints[endpoint_name] = EndpointObj.new( responses.get.nil? ? "POST" : "GET", @@ -123,7 +123,7 @@ module EveSwagger str << " * @customfunction\n" str << " */\n" str << "function #{endpoint_name}(#{endpoint_data.parameters.map { |p| "#{p.name}: #{p.type}" }.join(", ")}): any[][] {\n" - endpoint_data.parameters.each { |p| str << " if(!#{p.name}) throw '#{p.name} is required';\n" if p.required } + endpoint_data.parameters.each { |p| str << " if(!#{p.name}) throw buildError_({body: '#{p.name} is required', code: 400, method: '#{endpoint_name}'});\n" if p.required } str << " return parseData_('#{endpoint_name}',{#{endpoint_data.parameters.map { |p| "#{p.name}:#{p.name}" }.join(',')}})\n" str << "}\n\n" end @@ -291,7 +291,7 @@ module EveSwagger @parameters << Parameter.from_json({name: "version", in: "path", type: "string", description: "Which ESI version to use for the request. Default: Current ESI latest stable version."}.to_json) unless EveSwagger.rejected_params.includes? "version" end - private def get_headers(schema : Schema | Nil) + private def get_headers(schema : Schema?) headers = [] of Header return headers if schema.nil? @@ -309,7 +309,6 @@ module EveSwagger end else # of single type - title = items.title if title = items.title headers << Header.new(title.includes?('_') ? title.match(/.*_(.*)_200_ok/).not_nil![1].chomp('s') + "_ids" : title + 's') end @@ -323,6 +322,14 @@ module EveSwagger sub_headers = v.properties.not_nil!.keys if v.type == "object" headers << Header.new(k, sub_headers) end + else + if schema.description == "200 ok integer" + title = schema.title.match(/.*_(\w+_\w+)_ok$/).not_nil![1] + title = title.sub("s_", '_') + else + title = schema.description.underscore.sub(' ', '_') + end + headers << Header.new(title) end headers.sort_by! { |h| h.name } end diff --git a/src/script/endpoints.ts b/src/script/endpoints.ts index ab762ea..d82025b 100644 --- a/src/script/endpoints.ts +++ b/src/script/endpoints.ts @@ -1903,7 +1903,11 @@ const ENDPOINTS = { }, "characters_character_wallet": { "description": "Returns a character's wallet balance", - "headers": [], + "headers": [ + { + "name": "wallet_balance" + } + ], "method": "GET", "path": "/{version}/characters/{character_id}/wallet/", "parameters": [ @@ -3368,7 +3372,11 @@ const ENDPOINTS = { }, "corporations_corporation_members_limit": { "description": "Return a corporation's member limit, not including CEO himself", - "headers": [], + "headers": [ + { + "name": "member_limit" + } + ], "method": "GET", "path": "/{version}/corporations/{corporation_id}/members/limit/", "parameters": [ diff --git a/src/script/functions.ts b/src/script/functions.ts index 449ef99..3054b88 100644 --- a/src/script/functions.ts +++ b/src/script/functions.ts @@ -52,7 +52,7 @@ function alliances_alliance_icons(opt_headers: boolean, version: string): any[][ * @customfunction */ function characters_affiliation(characters: number[], opt_headers: boolean, version: string): any[][] { - if(!characters) throw 'characters is required'; + if(!characters) throw buildError_({body: 'characters is required', code: 400, method: 'characters_affiliation'}); return parseData_('characters_affiliation',{characters:characters,opt_headers:opt_headers,version:version}) } @@ -78,7 +78,7 @@ function characters_character_agents_research(name: string, opt_headers: boolean * @customfunction */ function characters_character_assets_names(item_ids: number[], name: string, opt_headers: boolean, version: string): any[][] { - if(!item_ids) throw 'item_ids is required'; + if(!item_ids) throw buildError_({body: 'item_ids is required', code: 400, method: 'characters_character_assets_names'}); return parseData_('characters_character_assets_names',{item_ids:item_ids,name:name,opt_headers:opt_headers,version:version}) } @@ -117,7 +117,7 @@ function characters_character_calendar(from_event: number, name: string, opt_hea * @customfunction */ function characters_character_calendar_event_attendees(event_id: number, name: string, opt_headers: boolean, version: string): any[][] { - if(!event_id) throw 'event_id is required'; + if(!event_id) throw buildError_({body: 'event_id is required', code: 400, method: 'characters_character_calendar_event_attendees'}); return parseData_('characters_character_calendar_event_attendees',{event_id:event_id,name:name,opt_headers:opt_headers,version:version}) } @@ -156,7 +156,7 @@ function characters_character_contracts(name: string, page: number, opt_headers: * @customfunction */ function characters_character_contracts_contract_bids(contract_id: number, name: string, opt_headers: boolean, version: string): any[][] { - if(!contract_id) throw 'contract_id is required'; + if(!contract_id) throw buildError_({body: 'contract_id is required', code: 400, method: 'characters_character_contracts_contract_bids'}); return parseData_('characters_character_contracts_contract_bids',{contract_id:contract_id,name:name,opt_headers:opt_headers,version:version}) } @@ -170,7 +170,7 @@ function characters_character_contracts_contract_bids(contract_id: number, name: * @customfunction */ function characters_character_contracts_contract_items(contract_id: number, name: string, opt_headers: boolean, version: string): any[][] { - if(!contract_id) throw 'contract_id is required'; + if(!contract_id) throw buildError_({body: 'contract_id is required', code: 400, method: 'characters_character_contracts_contract_items'}); return parseData_('characters_character_contracts_contract_items',{contract_id:contract_id,name:name,opt_headers:opt_headers,version:version}) } @@ -183,7 +183,7 @@ function characters_character_contracts_contract_items(contract_id: number, name * @customfunction */ function characters_character_corporationhistory(character_id: number, opt_headers: boolean, version: string): any[][] { - if(!character_id) throw 'character_id is required'; + if(!character_id) throw buildError_({body: 'character_id is required', code: 400, method: 'characters_character_corporationhistory'}); return parseData_('characters_character_corporationhistory',{character_id:character_id,opt_headers:opt_headers,version:version}) } @@ -333,7 +333,7 @@ function characters_character_mail_lists(name: string, opt_headers: boolean, ver * @customfunction */ function characters_character_mail_mail(mail_id: number, name: string, opt_headers: boolean, version: string): any[][] { - if(!mail_id) throw 'mail_id is required'; + if(!mail_id) throw buildError_({body: 'mail_id is required', code: 400, method: 'characters_character_mail_mail'}); return parseData_('characters_character_mail_mail',{mail_id:mail_id,name:name,opt_headers:opt_headers,version:version}) } @@ -482,7 +482,7 @@ function characters_character_wallet_transactions(from_id: number, name: string, * @customfunction */ function contracts_public_bids_contract(contract_id: number, page: number, opt_headers: boolean, version: string): any[][] { - if(!contract_id) throw 'contract_id is required'; + if(!contract_id) throw buildError_({body: 'contract_id is required', code: 400, method: 'contracts_public_bids_contract'}); return parseData_('contracts_public_bids_contract',{contract_id:contract_id,page:page,opt_headers:opt_headers,version:version}) } @@ -496,7 +496,7 @@ function contracts_public_bids_contract(contract_id: number, page: number, opt_h * @customfunction */ function contracts_public_items_contract(contract_id: number, page: number, opt_headers: boolean, version: string): any[][] { - if(!contract_id) throw 'contract_id is required'; + if(!contract_id) throw buildError_({body: 'contract_id is required', code: 400, method: 'contracts_public_items_contract'}); return parseData_('contracts_public_items_contract',{contract_id:contract_id,page:page,opt_headers:opt_headers,version:version}) } @@ -510,7 +510,7 @@ function contracts_public_items_contract(contract_id: number, page: number, opt_ * @customfunction */ function contracts_public_region(region_id: number, page: number, opt_headers: boolean, version: string): any[][] { - if(!region_id) throw 'region_id is required'; + if(!region_id) throw buildError_({body: 'region_id is required', code: 400, method: 'contracts_public_region'}); return parseData_('contracts_public_region',{region_id:region_id,page:page,opt_headers:opt_headers,version:version}) } @@ -551,7 +551,7 @@ function corporation_corporation_mining_observers(name: string, page: number, op * @customfunction */ function corporation_corporation_mining_observers_observer(observer_id: number, name: string, page: number, opt_headers: boolean, version: string): any[][] { - if(!observer_id) throw 'observer_id is required'; + if(!observer_id) throw buildError_({body: 'observer_id is required', code: 400, method: 'corporation_corporation_mining_observers_observer'}); return parseData_('corporation_corporation_mining_observers_observer',{observer_id:observer_id,name:name,page:page,opt_headers:opt_headers,version:version}) } @@ -576,7 +576,7 @@ function corporations_npccorps(opt_headers: boolean, version: string): any[][] { * @customfunction */ function corporations_corporation_assets_names(item_ids: number[], name: string, opt_headers: boolean, version: string): any[][] { - if(!item_ids) throw 'item_ids is required'; + if(!item_ids) throw buildError_({body: 'item_ids is required', code: 400, method: 'corporations_corporation_assets_names'}); return parseData_('corporations_corporation_assets_names',{item_ids:item_ids,name:name,opt_headers:opt_headers,version:version}) } @@ -642,7 +642,7 @@ function corporations_corporation_contracts(name: string, page: number, opt_head * @customfunction */ function corporations_corporation_contracts_contract_bids(contract_id: number, name: string, page: number, opt_headers: boolean, version: string): any[][] { - if(!contract_id) throw 'contract_id is required'; + if(!contract_id) throw buildError_({body: 'contract_id is required', code: 400, method: 'corporations_corporation_contracts_contract_bids'}); return parseData_('corporations_corporation_contracts_contract_bids',{contract_id:contract_id,name:name,page:page,opt_headers:opt_headers,version:version}) } @@ -656,7 +656,7 @@ function corporations_corporation_contracts_contract_bids(contract_id: number, n * @customfunction */ function corporations_corporation_contracts_contract_items(contract_id: number, name: string, opt_headers: boolean, version: string): any[][] { - if(!contract_id) throw 'contract_id is required'; + if(!contract_id) throw buildError_({body: 'contract_id is required', code: 400, method: 'corporations_corporation_contracts_contract_items'}); return parseData_('corporations_corporation_contracts_contract_items',{contract_id:contract_id,name:name,opt_headers:opt_headers,version:version}) } @@ -884,8 +884,8 @@ function corporations_corporation_starbases(name: string, page: number, opt_head * @customfunction */ function corporations_corporation_starbases_starbase(starbase_id: number, system_id: number, name: string, opt_headers: boolean, version: string): any[][] { - if(!starbase_id) throw 'starbase_id is required'; - if(!system_id) throw 'system_id is required'; + if(!starbase_id) throw buildError_({body: 'starbase_id is required', code: 400, method: 'corporations_corporation_starbases_starbase'}); + if(!system_id) throw buildError_({body: 'system_id is required', code: 400, method: 'corporations_corporation_starbases_starbase'}); return parseData_('corporations_corporation_starbases_starbase',{starbase_id:starbase_id,system_id:system_id,name:name,opt_headers:opt_headers,version:version}) } @@ -924,7 +924,7 @@ function corporations_corporation_wallets(name: string, opt_headers: boolean, ve * @customfunction */ function corporations_corporation_wallets_division_transactions(division: number, from_id: number, name: string, opt_headers: boolean, version: string): any[][] { - if(!division) throw 'division is required'; + if(!division) throw buildError_({body: 'division is required', code: 400, method: 'corporations_corporation_wallets_division_transactions'}); return parseData_('corporations_corporation_wallets_division_transactions',{division:division,from_id:from_id,name:name,opt_headers:opt_headers,version:version}) } @@ -948,7 +948,7 @@ function dogma_attributes(opt_headers: boolean, version: string): any[][] { * @customfunction */ function dogma_attributes_attribute(attribute_id: number, opt_headers: boolean, version: string): any[][] { - if(!attribute_id) throw 'attribute_id is required'; + if(!attribute_id) throw buildError_({body: 'attribute_id is required', code: 400, method: 'dogma_attributes_attribute'}); return parseData_('dogma_attributes_attribute',{attribute_id:attribute_id,opt_headers:opt_headers,version:version}) } @@ -962,8 +962,8 @@ function dogma_attributes_attribute(attribute_id: number, opt_headers: boolean, * @customfunction */ function dogma_dynamic_items_type_item(item_id: number, type_id: number, opt_headers: boolean, version: string): any[][] { - if(!item_id) throw 'item_id is required'; - if(!type_id) throw 'type_id is required'; + if(!item_id) throw buildError_({body: 'item_id is required', code: 400, method: 'dogma_dynamic_items_type_item'}); + if(!type_id) throw buildError_({body: 'type_id is required', code: 400, method: 'dogma_dynamic_items_type_item'}); return parseData_('dogma_dynamic_items_type_item',{item_id:item_id,type_id:type_id,opt_headers:opt_headers,version:version}) } @@ -988,7 +988,7 @@ function dogma_effects(opt_headers: boolean, version: string): any[][] { * @customfunction */ function fleets_fleet(fleet_id: number, name: string, opt_headers: boolean, version: string): any[][] { - if(!fleet_id) throw 'fleet_id is required'; + if(!fleet_id) throw buildError_({body: 'fleet_id is required', code: 400, method: 'fleets_fleet'}); return parseData_('fleets_fleet',{fleet_id:fleet_id,name:name,opt_headers:opt_headers,version:version}) } @@ -1003,7 +1003,7 @@ function fleets_fleet(fleet_id: number, name: string, opt_headers: boolean, vers * @customfunction */ function fleets_fleet_members(fleet_id: number, language: string, name: string, opt_headers: boolean, version: string): any[][] { - if(!fleet_id) throw 'fleet_id is required'; + if(!fleet_id) throw buildError_({body: 'fleet_id is required', code: 400, method: 'fleets_fleet_members'}); return parseData_('fleets_fleet_members',{fleet_id:fleet_id,language:language,name:name,opt_headers:opt_headers,version:version}) } @@ -1018,7 +1018,7 @@ function fleets_fleet_members(fleet_id: number, language: string, name: string, * @customfunction */ function fleets_fleet_wings(fleet_id: number, language: string, name: string, opt_headers: boolean, version: string): any[][] { - if(!fleet_id) throw 'fleet_id is required'; + if(!fleet_id) throw buildError_({body: 'fleet_id is required', code: 400, method: 'fleets_fleet_wings'}); return parseData_('fleets_fleet_wings',{fleet_id:fleet_id,language:language,name:name,opt_headers:opt_headers,version:version}) } @@ -1132,8 +1132,8 @@ function insurance_prices(language: string, opt_headers: boolean, version: strin * @customfunction */ function killmails_killmail_killmail_hash(killmail_hash: string, killmail_id: number, opt_headers: boolean, version: string): any[][] { - if(!killmail_hash) throw 'killmail_hash is required'; - if(!killmail_id) throw 'killmail_id is required'; + if(!killmail_hash) throw buildError_({body: 'killmail_hash is required', code: 400, method: 'killmails_killmail_killmail_hash'}); + if(!killmail_id) throw buildError_({body: 'killmail_id is required', code: 400, method: 'killmails_killmail_killmail_hash'}); return parseData_('killmails_killmail_killmail_hash',{killmail_hash:killmail_hash,killmail_id:killmail_id,opt_headers:opt_headers,version:version}) } @@ -1146,7 +1146,7 @@ function killmails_killmail_killmail_hash(killmail_hash: string, killmail_id: nu * @customfunction */ function loyalty_stores_corporation_offers(corporation_id: number, opt_headers: boolean, version: string): any[][] { - if(!corporation_id) throw 'corporation_id is required'; + if(!corporation_id) throw buildError_({body: 'corporation_id is required', code: 400, method: 'loyalty_stores_corporation_offers'}); return parseData_('loyalty_stores_corporation_offers',{corporation_id:corporation_id,opt_headers:opt_headers,version:version}) } @@ -1171,7 +1171,7 @@ function markets_groups(opt_headers: boolean, version: string): any[][] { * @customfunction */ function markets_groups_market_group(market_group_id: number, language: string, opt_headers: boolean, version: string): any[][] { - if(!market_group_id) throw 'market_group_id is required'; + if(!market_group_id) throw buildError_({body: 'market_group_id is required', code: 400, method: 'markets_groups_market_group'}); return parseData_('markets_groups_market_group',{market_group_id:market_group_id,language:language,opt_headers:opt_headers,version:version}) } @@ -1197,7 +1197,7 @@ function markets_prices(opt_headers: boolean, version: string): any[][] { * @customfunction */ function markets_structures_structure(structure_id: number, name: string, page: number, opt_headers: boolean, version: string): any[][] { - if(!structure_id) throw 'structure_id is required'; + if(!structure_id) throw buildError_({body: 'structure_id is required', code: 400, method: 'markets_structures_structure'}); return parseData_('markets_structures_structure',{structure_id:structure_id,name:name,page:page,opt_headers:opt_headers,version:version}) } @@ -1211,8 +1211,8 @@ function markets_structures_structure(structure_id: number, name: string, page: * @customfunction */ function markets_region_history(region_id: number, type_id: number, opt_headers: boolean, version: string): any[][] { - if(!region_id) throw 'region_id is required'; - if(!type_id) throw 'type_id is required'; + if(!region_id) throw buildError_({body: 'region_id is required', code: 400, method: 'markets_region_history'}); + if(!type_id) throw buildError_({body: 'type_id is required', code: 400, method: 'markets_region_history'}); return parseData_('markets_region_history',{region_id:region_id,type_id:type_id,opt_headers:opt_headers,version:version}) } @@ -1228,8 +1228,8 @@ function markets_region_history(region_id: number, type_id: number, opt_headers: * @customfunction */ function markets_region_orders(order_type: string, region_id: number, page: number, type_id: number, opt_headers: boolean, version: string): any[][] { - if(!order_type) throw 'order_type is required'; - if(!region_id) throw 'region_id is required'; + if(!order_type) throw buildError_({body: 'order_type is required', code: 400, method: 'markets_region_orders'}); + if(!region_id) throw buildError_({body: 'region_id is required', code: 400, method: 'markets_region_orders'}); return parseData_('markets_region_orders',{order_type:order_type,region_id:region_id,page:page,type_id:type_id,opt_headers:opt_headers,version:version}) } @@ -1243,7 +1243,7 @@ function markets_region_orders(order_type: string, region_id: number, page: numb * @customfunction */ function markets_region_types(region_id: number, page: number, opt_headers: boolean, version: string): any[][] { - if(!region_id) throw 'region_id is required'; + if(!region_id) throw buildError_({body: 'region_id is required', code: 400, method: 'markets_region_types'}); return parseData_('markets_region_types',{region_id:region_id,page:page,opt_headers:opt_headers,version:version}) } @@ -1268,7 +1268,7 @@ function opportunities_groups(opt_headers: boolean, version: string): any[][] { * @customfunction */ function opportunities_groups_group(group_id: number, language: string, opt_headers: boolean, version: string): any[][] { - if(!group_id) throw 'group_id is required'; + if(!group_id) throw buildError_({body: 'group_id is required', code: 400, method: 'opportunities_groups_group'}); return parseData_('opportunities_groups_group',{group_id:group_id,language:language,opt_headers:opt_headers,version:version}) } @@ -1292,7 +1292,7 @@ function opportunities_tasks(opt_headers: boolean, version: string): any[][] { * @customfunction */ function opportunities_tasks_task(task_id: number, opt_headers: boolean, version: string): any[][] { - if(!task_id) throw 'task_id is required'; + if(!task_id) throw buildError_({body: 'task_id is required', code: 400, method: 'opportunities_tasks_task'}); return parseData_('opportunities_tasks_task',{task_id:task_id,opt_headers:opt_headers,version:version}) } @@ -1309,8 +1309,8 @@ function opportunities_tasks_task(task_id: number, opt_headers: boolean, version * @customfunction */ function route_origin_destination(destination: number, origin: number, avoid: number[], connections: number[], flag: string, opt_headers: boolean, version: string): any[][] { - if(!destination) throw 'destination is required'; - if(!origin) throw 'origin is required'; + if(!destination) throw buildError_({body: 'destination is required', code: 400, method: 'route_origin_destination'}); + if(!origin) throw buildError_({body: 'origin is required', code: 400, method: 'route_origin_destination'}); return parseData_('route_origin_destination',{destination:destination,origin:origin,avoid:avoid,connections:connections,flag:flag,opt_headers:opt_headers,version:version}) } @@ -1379,7 +1379,7 @@ function universe_ancestries(language: string, opt_headers: boolean, version: st * @customfunction */ function universe_asteroid_belts_asteroid_belt(asteroid_belt_id: number, opt_headers: boolean, version: string): any[][] { - if(!asteroid_belt_id) throw 'asteroid_belt_id is required'; + if(!asteroid_belt_id) throw buildError_({body: 'asteroid_belt_id is required', code: 400, method: 'universe_asteroid_belts_asteroid_belt'}); return parseData_('universe_asteroid_belts_asteroid_belt',{asteroid_belt_id:asteroid_belt_id,opt_headers:opt_headers,version:version}) } @@ -1416,7 +1416,7 @@ function universe_categories(opt_headers: boolean, version: string): any[][] { * @customfunction */ function universe_categories_category(category_id: number, language: string, opt_headers: boolean, version: string): any[][] { - if(!category_id) throw 'category_id is required'; + if(!category_id) throw buildError_({body: 'category_id is required', code: 400, method: 'universe_categories_category'}); return parseData_('universe_categories_category',{category_id:category_id,language:language,opt_headers:opt_headers,version:version}) } @@ -1441,7 +1441,7 @@ function universe_constellations(opt_headers: boolean, version: string): any[][] * @customfunction */ function universe_constellations_constellation(constellation_id: number, language: string, opt_headers: boolean, version: string): any[][] { - if(!constellation_id) throw 'constellation_id is required'; + if(!constellation_id) throw buildError_({body: 'constellation_id is required', code: 400, method: 'universe_constellations_constellation'}); return parseData_('universe_constellations_constellation',{constellation_id:constellation_id,language:language,opt_headers:opt_headers,version:version}) } @@ -1465,7 +1465,7 @@ function universe_graphics(opt_headers: boolean, version: string): any[][] { * @customfunction */ function universe_graphics_graphic(graphic_id: number, opt_headers: boolean, version: string): any[][] { - if(!graphic_id) throw 'graphic_id is required'; + if(!graphic_id) throw buildError_({body: 'graphic_id is required', code: 400, method: 'universe_graphics_graphic'}); return parseData_('universe_graphics_graphic',{graphic_id:graphic_id,opt_headers:opt_headers,version:version}) } @@ -1491,7 +1491,7 @@ function universe_groups(page: number, opt_headers: boolean, version: string): a * @customfunction */ function universe_groups_group(group_id: number, language: string, opt_headers: boolean, version: string): any[][] { - if(!group_id) throw 'group_id is required'; + if(!group_id) throw buildError_({body: 'group_id is required', code: 400, method: 'universe_groups_group'}); return parseData_('universe_groups_group',{group_id:group_id,language:language,opt_headers:opt_headers,version:version}) } @@ -1505,7 +1505,7 @@ function universe_groups_group(group_id: number, language: string, opt_headers: * @customfunction */ function universe_ids(names: string[], language: string, opt_headers: boolean, version: string): any[][] { - if(!names) throw 'names is required'; + if(!names) throw buildError_({body: 'names is required', code: 400, method: 'universe_ids'}); return parseData_('universe_ids',{names:names,language:language,opt_headers:opt_headers,version:version}) } @@ -1518,7 +1518,7 @@ function universe_ids(names: string[], language: string, opt_headers: boolean, v * @customfunction */ function universe_moons_moon(moon_id: number, opt_headers: boolean, version: string): any[][] { - if(!moon_id) throw 'moon_id is required'; + if(!moon_id) throw buildError_({body: 'moon_id is required', code: 400, method: 'universe_moons_moon'}); return parseData_('universe_moons_moon',{moon_id:moon_id,opt_headers:opt_headers,version:version}) } @@ -1531,7 +1531,7 @@ function universe_moons_moon(moon_id: number, opt_headers: boolean, version: str * @customfunction */ function universe_planets_planet(planet_id: number, opt_headers: boolean, version: string): any[][] { - if(!planet_id) throw 'planet_id is required'; + if(!planet_id) throw buildError_({body: 'planet_id is required', code: 400, method: 'universe_planets_planet'}); return parseData_('universe_planets_planet',{planet_id:planet_id,opt_headers:opt_headers,version:version}) } @@ -1568,7 +1568,7 @@ function universe_regions(opt_headers: boolean, version: string): any[][] { * @customfunction */ function universe_regions_region(region_id: number, language: string, opt_headers: boolean, version: string): any[][] { - if(!region_id) throw 'region_id is required'; + if(!region_id) throw buildError_({body: 'region_id is required', code: 400, method: 'universe_regions_region'}); return parseData_('universe_regions_region',{region_id:region_id,language:language,opt_headers:opt_headers,version:version}) } @@ -1581,7 +1581,7 @@ function universe_regions_region(region_id: number, language: string, opt_header * @customfunction */ function universe_schematics_schematic(schematic_id: number, opt_headers: boolean, version: string): any[][] { - if(!schematic_id) throw 'schematic_id is required'; + if(!schematic_id) throw buildError_({body: 'schematic_id is required', code: 400, method: 'universe_schematics_schematic'}); return parseData_('universe_schematics_schematic',{schematic_id:schematic_id,opt_headers:opt_headers,version:version}) } @@ -1594,7 +1594,7 @@ function universe_schematics_schematic(schematic_id: number, opt_headers: boolea * @customfunction */ function universe_stargates_stargate(stargate_id: number, opt_headers: boolean, version: string): any[][] { - if(!stargate_id) throw 'stargate_id is required'; + if(!stargate_id) throw buildError_({body: 'stargate_id is required', code: 400, method: 'universe_stargates_stargate'}); return parseData_('universe_stargates_stargate',{stargate_id:stargate_id,opt_headers:opt_headers,version:version}) } @@ -1607,7 +1607,7 @@ function universe_stargates_stargate(stargate_id: number, opt_headers: boolean, * @customfunction */ function universe_stars_star(star_id: number, opt_headers: boolean, version: string): any[][] { - if(!star_id) throw 'star_id is required'; + if(!star_id) throw buildError_({body: 'star_id is required', code: 400, method: 'universe_stars_star'}); return parseData_('universe_stars_star',{star_id:star_id,opt_headers:opt_headers,version:version}) } @@ -1678,7 +1678,7 @@ function wars(max_war_id: number, opt_headers: boolean, version: string): any[][ * @customfunction */ function wars_war(war_id: number, opt_headers: boolean, version: string): any[][] { - if(!war_id) throw 'war_id is required'; + if(!war_id) throw buildError_({body: 'war_id is required', code: 400, method: 'wars_war'}); return parseData_('wars_war',{war_id:war_id,opt_headers:opt_headers,version:version}) } @@ -1692,7 +1692,7 @@ function wars_war(war_id: number, opt_headers: boolean, version: string): any[][ * @customfunction */ function wars_war_killmails(war_id: number, page: number, opt_headers: boolean, version: string): any[][] { - if(!war_id) throw 'war_id is required'; + if(!war_id) throw buildError_({body: 'war_id is required', code: 400, method: 'wars_war_killmails'}); return parseData_('wars_war_killmails',{war_id:war_id,page:page,opt_headers:opt_headers,version:version}) } @@ -1719,7 +1719,7 @@ function alliances_alliance_contacts(name: string, page: number, opt_headers: bo * @customfunction */ function characters_character_assets_locations(item_ids: number[], name: string, opt_headers: boolean, version: string): any[][] { - if(!item_ids) throw 'item_ids is required'; + if(!item_ids) throw buildError_({body: 'item_ids is required', code: 400, method: 'characters_character_assets_locations'}); return parseData_('characters_character_assets_locations',{item_ids:item_ids,name:name,opt_headers:opt_headers,version:version}) } @@ -1855,7 +1855,7 @@ function characters_character_stats(name: string, opt_headers: boolean, version: * @customfunction */ function corporations_corporation_alliancehistory(corporation_id: number, opt_headers: boolean, version: string): any[][] { - if(!corporation_id) throw 'corporation_id is required'; + if(!corporation_id) throw buildError_({body: 'corporation_id is required', code: 400, method: 'corporations_corporation_alliancehistory'}); return parseData_('corporations_corporation_alliancehistory',{corporation_id:corporation_id,opt_headers:opt_headers,version:version}) } @@ -1869,7 +1869,7 @@ function corporations_corporation_alliancehistory(corporation_id: number, opt_he * @customfunction */ function corporations_corporation_assets_locations(item_ids: number[], name: string, opt_headers: boolean, version: string): any[][] { - if(!item_ids) throw 'item_ids is required'; + if(!item_ids) throw buildError_({body: 'item_ids is required', code: 400, method: 'corporations_corporation_assets_locations'}); return parseData_('corporations_corporation_assets_locations',{item_ids:item_ids,name:name,opt_headers:opt_headers,version:version}) } @@ -1934,7 +1934,7 @@ function corporations_corporation_orders_history(name: string, page: number, opt * @customfunction */ function dogma_effects_effect(effect_id: number, opt_headers: boolean, version: string): any[][] { - if(!effect_id) throw 'effect_id is required'; + if(!effect_id) throw buildError_({body: 'effect_id is required', code: 400, method: 'dogma_effects_effect'}); return parseData_('dogma_effects_effect',{effect_id:effect_id,opt_headers:opt_headers,version:version}) } @@ -1961,8 +1961,8 @@ function fw_systems(opt_headers: boolean, version: string): any[][] { * @customfunction */ function eve_search(categories: string[], search: string, language: string, strict: boolean, opt_headers: boolean, version: string): any[][] { - if(!categories) throw 'categories is required'; - if(!search) throw 'search is required'; + if(!categories) throw buildError_({body: 'categories is required', code: 400, method: 'eve_search'}); + if(!search) throw buildError_({body: 'search is required', code: 400, method: 'eve_search'}); return parseData_('eve_search',{categories:categories,search:search,language:language,strict:strict,opt_headers:opt_headers,version:version}) } @@ -1987,7 +1987,7 @@ function universe_factions(language: string, opt_headers: boolean, version: stri * @customfunction */ function universe_names(ids: number[], opt_headers: boolean, version: string): any[][] { - if(!ids) throw 'ids is required'; + if(!ids) throw buildError_({body: 'ids is required', code: 400, method: 'universe_names'}); return parseData_('universe_names',{ids:ids,opt_headers:opt_headers,version:version}) } @@ -2000,7 +2000,7 @@ function universe_names(ids: number[], opt_headers: boolean, version: string): a * @customfunction */ function universe_stations_station(station_id: number, opt_headers: boolean, version: string): any[][] { - if(!station_id) throw 'station_id is required'; + if(!station_id) throw buildError_({body: 'station_id is required', code: 400, method: 'universe_stations_station'}); return parseData_('universe_stations_station',{station_id:station_id,opt_headers:opt_headers,version:version}) } @@ -2014,7 +2014,7 @@ function universe_stations_station(station_id: number, opt_headers: boolean, ver * @customfunction */ function universe_structures_structure(structure_id: number, name: string, opt_headers: boolean, version: string): any[][] { - if(!structure_id) throw 'structure_id is required'; + if(!structure_id) throw buildError_({body: 'structure_id is required', code: 400, method: 'universe_structures_structure'}); return parseData_('universe_structures_structure',{structure_id:structure_id,name:name,opt_headers:opt_headers,version:version}) } @@ -2038,7 +2038,7 @@ function universe_system_kills(opt_headers: boolean, version: string): any[][] { * @customfunction */ function alliances_alliance(alliance_id: number, opt_headers: boolean, version: string): any[][] { - if(!alliance_id) throw 'alliance_id is required'; + if(!alliance_id) throw buildError_({body: 'alliance_id is required', code: 400, method: 'alliances_alliance'}); return parseData_('alliances_alliance',{alliance_id:alliance_id,opt_headers:opt_headers,version:version}) } @@ -2065,7 +2065,7 @@ function characters_character_assets(name: string, page: number, opt_headers: bo * @customfunction */ function characters_character_calendar_event(event_id: number, name: string, opt_headers: boolean, version: string): any[][] { - if(!event_id) throw 'event_id is required'; + if(!event_id) throw buildError_({body: 'event_id is required', code: 400, method: 'characters_character_calendar_event'}); return parseData_('characters_character_calendar_event',{event_id:event_id,name:name,opt_headers:opt_headers,version:version}) } @@ -2103,7 +2103,7 @@ function characters_character_mail_labels(name: string, opt_headers: boolean, ve * @customfunction */ function characters_character_planets_planet(planet_id: number, name: string, opt_headers: boolean, version: string): any[][] { - if(!planet_id) throw 'planet_id is required'; + if(!planet_id) throw buildError_({body: 'planet_id is required', code: 400, method: 'characters_character_planets_planet'}); return parseData_('characters_character_planets_planet',{planet_id:planet_id,name:name,opt_headers:opt_headers,version:version}) } @@ -2120,8 +2120,8 @@ function characters_character_planets_planet(planet_id: number, name: string, op * @customfunction */ function characters_character_search(categories: string[], search: string, language: string, strict: boolean, name: string, opt_headers: boolean, version: string): any[][] { - if(!categories) throw 'categories is required'; - if(!search) throw 'search is required'; + if(!categories) throw buildError_({body: 'categories is required', code: 400, method: 'characters_character_search'}); + if(!search) throw buildError_({body: 'search is required', code: 400, method: 'characters_character_search'}); return parseData_('characters_character_search',{categories:categories,search:search,language:language,strict:strict,name:name,opt_headers:opt_headers,version:version}) } @@ -2187,7 +2187,7 @@ function corporations_corporation_structures(language: string, name: string, pag * @customfunction */ function universe_types_type(type_id: number, language: string, opt_headers: boolean, version: string): any[][] { - if(!type_id) throw 'type_id is required'; + if(!type_id) throw buildError_({body: 'type_id is required', code: 400, method: 'universe_types_type'}); return parseData_('universe_types_type',{type_id:type_id,language:language,opt_headers:opt_headers,version:version}) } @@ -2200,7 +2200,7 @@ function universe_types_type(type_id: number, language: string, opt_headers: boo * @customfunction */ function characters_character(character_id: number, opt_headers: boolean, version: string): any[][] { - if(!character_id) throw 'character_id is required'; + if(!character_id) throw buildError_({body: 'character_id is required', code: 400, method: 'characters_character'}); return parseData_('characters_character',{character_id:character_id,opt_headers:opt_headers,version:version}) } @@ -2237,7 +2237,7 @@ function characters_character_skills(name: string, opt_headers: boolean, version * @customfunction */ function corporations_corporation(corporation_id: number, opt_headers: boolean, version: string): any[][] { - if(!corporation_id) throw 'corporation_id is required'; + if(!corporation_id) throw buildError_({body: 'corporation_id is required', code: 400, method: 'corporations_corporation'}); return parseData_('corporations_corporation',{corporation_id:corporation_id,opt_headers:opt_headers,version:version}) } @@ -2252,7 +2252,7 @@ function corporations_corporation(corporation_id: number, opt_headers: boolean, * @customfunction */ function corporations_corporation_wallets_division_journal(division: number, name: string, page: number, opt_headers: boolean, version: string): any[][] { - if(!division) throw 'division is required'; + if(!division) throw buildError_({body: 'division is required', code: 400, method: 'corporations_corporation_wallets_division_journal'}); return parseData_('corporations_corporation_wallets_division_journal',{division:division,name:name,page:page,opt_headers:opt_headers,version:version}) } @@ -2266,7 +2266,7 @@ function corporations_corporation_wallets_division_journal(division: number, nam * @customfunction */ function universe_systems_system(system_id: number, language: string, opt_headers: boolean, version: string): any[][] { - if(!system_id) throw 'system_id is required'; + if(!system_id) throw buildError_({body: 'system_id is required', code: 400, method: 'universe_systems_system'}); return parseData_('universe_systems_system',{system_id:system_id,language:language,opt_headers:opt_headers,version:version}) } diff --git a/src/script/gesi.ts b/src/script/gesi.ts index df61d1b..d20f9f8 100644 --- a/src/script/gesi.ts +++ b/src/script/gesi.ts @@ -5,10 +5,11 @@ * @Blacksmoke16#0016 @ Discord * https://discord.gg/eEAH2et */ -import URLFetchRequestOptions = GoogleAppsScript.URL_Fetch.URLFetchRequestOptions; import HtmlOutput = GoogleAppsScript.HTML.HtmlOutput; import User = GoogleAppsScript.Base.User; -import Range = GoogleAppsScript.Spreadsheet.Range; +import HTTPResponse = GoogleAppsScript.URL_Fetch.HTTPResponse; + +const HEADERS: Object = { 'User-Agent': 'GESI V2', 'Content-Type': 'application/json' }; function onInstall(): void { onOpen(); @@ -16,9 +17,9 @@ function onInstall(): void { function onOpen(): void { SpreadsheetApp.getUi() - .createAddonMenu() - .addItem('Authorize Characters', 'showSSOModal') - .addToUi(); + .createAddonMenu() + .addItem('Authorize Characters', 'showSSOModal') + .addToUi(); } /** @@ -81,17 +82,6 @@ function setMainCharacter(character_name: string): string { return 'Done! Delete me.'; } -/** - * Gets the raw response from the given ESI endpoint - * @param {string} endpoint_name (Required) Name of the endpoint to fetch data from. - * @param {string} params params to use for the request. - * @return Hash of JSON data and headers - * @customfunction - */ -function getRawData(endpoint_name: string, params: IFunctionParam = {} as IFunctionParam): IRequestResponse { - return getData_(endpoint_name, params); -} - /** * Refreshes the access token for a given character and returns a new access token * @param {string} character_name (Required) Name of the character to refresh the token. @@ -99,13 +89,13 @@ function getRawData(endpoint_name: string, params: IFunctionParam = {} as IFunct * @customfunction */ function refreshToken(character_name: string): string { - return refreshToken_(character_name); + return refreshToken_(getCharacterRowData_(character_name)); } function showSSOModal(): void { const redirectUrl = `https://script.google.com/macros/d/${ScriptApp.getScriptId()}/usercallback`; const stateToken = ScriptApp.newStateToken().withMethod('authCallback').withTimeout(1200).createToken(); - const authorizationUrl = 'https://login.eveonline.com/v2/oauth/authorize/?response_type=code&redirect_uri=' + redirectUrl + '&client_id=' + PropertiesService.getScriptProperties().getProperty('CLIENT_ID') + '&scope=' + SCOPES.join('+') + '&state=' + stateToken; + const authorizationUrl = `https://login.eveonline.com/v2/oauth/authorize/?response_type=code&redirect_uri=${redirectUrl}&client_id=${PropertiesService.getScriptProperties().getProperty('CLIENT_ID')}&scope=${SCOPES.join('+')}&state=${stateToken}`; const template = HtmlService.createTemplate(`Click the link below to auth a character for use in GESI

Authorize with EVE SSO`); template.authorizationUrl = authorizationUrl; SpreadsheetApp.getUi().showModalDialog(template.evaluate().setWidth(400).setHeight(250), 'GESI EVE SSO'); @@ -117,70 +107,22 @@ function authCallback(request): HtmlOutput { const affiliationData = getCharacterAffiliation_(characterData.character_id); const userData = Object.assign(tokenData, characterData, affiliationData); saveCharacter_([userData.character_name, userData.character_id, userData.corporation_id, userData.alliance_id, userData.refresh_token], userData.access_token); - return HtmlService.createHtmlOutput('Thank you for using GESI ' + userData.character_name + '. You can close this tab.'); -} - -function parseData_(endpoint_name: string, params: IFunctionParam): any[][] { - const endpoint: IEndpoint = ENDPOINTS[endpoint_name]; - let result = []; - let data: any = []; - - if (params.opt_headers || undefined === params.opt_headers) result.push(endpoint.headers.map((h: IEndpointHeader) => h.name)); - - if (-1 === params.page) { - params.page = 1; - let response = getData_(endpoint_name, params); - data = data.concat(response.data); - let page = 2; - while (response.data.length > 0) { - params.page = page; - response = getData_(endpoint_name, params); - data = data.concat(response.data); - page++; - } - } else { - data = getData_(endpoint_name, params).data; - } - - if (Array.isArray(data) && typeof(data[0]) === 'number') { - result = result.concat(data); - } else if (Array.isArray(data) && typeof(data[0]) === 'object') { - result = result.concat( - data.map((obj) => { - return endpoint.headers.map((header: IEndpointHeader) => typeof(obj[header.name]) === 'object' ? JSON.stringify(obj[header.name]) : obj[header.name]) - }) - ) - } else if (typeof(data) === 'object') { - result.push(endpoint.headers.map((header: IEndpointHeader) => typeof(data[header.name]) === 'object' ? JSON.stringify(data[header.name]) : data[header.name])) - } else if (typeof(data) === 'number') { - result = [data]; - } else { - throw typeof(data) + ' is an unexpected type.'; - } - - return result; + return HtmlService.createHtmlOutput(`Thank you for using GESI ${userData.character_name}. You can close this tab.`); } -function getData_(endpoint_name: string, params: IFunctionParam): IRequestResponse { - const endpoint: IEndpoint = ENDPOINTS[endpoint_name]; - const character_name = params.name || PropertiesService.getDocumentProperties().getProperty('MAIN_CHARACTER'); - const character = getCharacterRow_(character_name); - if (!character && endpoint.scope) throw character_name + ' is not authed, or is misspelled.'; - if (!params.version) params.version = endpoint.version; - +function buildRequest_(endpoint: IEndpoint, character: ICharacterRowData, params: IFunctionParam, token: string, data: any = null): IRequest { let path = endpoint.path; - let data = null; endpoint.parameters.forEach((param: IParameter) => { if (param.in === 'path' && params[param.name]) { - path = path.replace('{' + param.name + '}', params[param.name]) + path = path.replace('{' + param.name + '}', params[param.name]); } else if (param.in === 'query' && params[param.name]) { path += path.indexOf('?') !== -1 ? '&' : '?'; path += param.name + '=' + (Array.isArray(params[param.name]) ? params[param.name].join(',') : params[param.name]); } else if (param.in === 'body' && params[param.name]) { if (param.type.indexOf('[]') !== -1) { data = Array.isArray(params[param.name]) ? params[param.name].filter(function (id) { - return id[0] + return id[0]; }).map(function (id) { return id[0]; }) : [params[param.name]]; @@ -190,76 +132,117 @@ function getData_(endpoint_name: string, params: IFunctionParam): IRequestRespon } }); - if (path.indexOf('{character_id}') !== -1) path = path.replace('{character_id}', getProperty_(character, CharacterRowData.character_id).getValue() as string); - if (path.indexOf('{alliance_id}') !== -1) path = path.replace('{alliance_id}', getProperty_(character, CharacterRowData.alliance_id).getValue() as string); - if (path.indexOf('{corporation_id}') !== -1) path = path.replace('{corporation_id}', getProperty_(character, CharacterRowData.corporation_id).getValue() as string); - - let token = CacheService.getDocumentCache().get(character_name + '_access_token'); - if (!token && endpoint.scope) token = refreshToken_(character_name); - - const hash = Utilities.base64Encode(Utilities.computeDigest(Utilities.DigestAlgorithm.MD5, character_name + endpoint_name + JSON.stringify(params) + JSON.stringify(data))); - let cached_data = CacheService.getDocumentCache().get(hash + '_0'); - if (cached_data) { - let result = []; - let idx = 1; - while (cached_data) { - result = result.concat(JSON.parse(cached_data)); - cached_data = CacheService.getDocumentCache().get(hash + `_${idx}`); - idx++; + if (path.indexOf('{character_id}') !== -1) path = path.replace('{character_id}', character.character_id.toString()); + if (path.indexOf('{alliance_id}') !== -1) path = path.replace('{alliance_id}', character.alliance_id.toString()); + if (path.indexOf('{corporation_id}') !== -1) path = path.replace('{corporation_id}', character.corporation_id.toString()); + + const request = { + method: endpoint.method, + url: `${PropertiesService.getScriptProperties().getProperty('BASE_URL')}${path}`, + headers: HEADERS, + muteHttpExceptions: true, + } as IRequest; + + if (data) request['payload'] = JSON.stringify(data); + if (endpoint.scope) request.headers['authorization'] = `Bearer ${token}`; + return request; +} + +function parseData_(endpoint_name: string, params: IFunctionParam): any[][] { + const endpoint: IEndpoint = ENDPOINTS[endpoint_name]; + const character: ICharacterRowData = getCharacterRowData_((params.name || PropertiesService.getDocumentProperties().getProperty('MAIN_CHARACTER'))); + if (!character && endpoint.scope) throw `${character.character_name} is not authed, or is misspelled.`; + let result: any[][] = []; + let data: any = []; + + // Add the header row if its not set or set to true + if (params.opt_headers || undefined === params.opt_headers) result.push(endpoint.headers.map((h: IEndpointHeader) => h.name)); + + // Set the token. Refresh it if it's expired. + let token = CacheService.getDocumentCache().get(`${character.character_name}_access_token`); + if (!token && endpoint.scope) token = refreshToken_(character); + if (!params.version) params.version = endpoint.version; + + if (params.page === -1) { + params.page = 1; + const response = doRequests_([buildRequest_(endpoint, character, params, token)]); + data = data.concat(response.data); + const requests: IRequest[] = []; + const pages = response.headers['x-pages']; + + for (let p = 2; p <= pages; p++) { + params.page = p; + requests.push(buildRequest_(endpoint, character, params, token)); } - return {data: result, headers: {}} as IRequestResponse; + data = data.concat(doRequests_(requests).data); + } else { + data = doRequests_([buildRequest_(endpoint, character, params, token)]).data; } - const response = doRequest_(PropertiesService.getScriptProperties().getProperty('BASE_URL') + path, endpoint.method, token, data); - const date_expires = new Date(response.headers['Expires']); - const cache_time = Math.min(21600, Math.ceil((date_expires.getTime() - (new Date()).getTime()) / 1000)); - try { - CacheService.getDocumentCache().put(hash + '_0', JSON.stringify(response.data), cache_time); - } catch (e) { - try { - const chunked_data = chunkArray_(response.data as any[], 350); - for (let i = 0; i < chunked_data.length; i++) { - CacheService.getDocumentCache().put(hash + `_${i}`, JSON.stringify(chunked_data[i]), cache_time); - } - } catch (e) { - const chunked_data = chunkArray_(response.data as any[], 75); - for (let i = 0; i < chunked_data.length; i++) { - CacheService.getDocumentCache().put(hash + `_${i}`, JSON.stringify(chunked_data[i]), cache_time); - } - } finally { - return response; - } - } finally { - return response; + if (Array.isArray(data) && typeof (data[0]) === 'number') { + result = result.concat(data); + } else if (Array.isArray(data) && typeof (data[0]) === 'object') { + result = result.concat( + data.map((obj) => { + return endpoint.headers.map((header: IEndpointHeader) => typeof (obj[header.name]) === 'object' ? JSON.stringify(obj[header.name]) : obj[header.name]); + }), + ); + } else if (typeof (data) === 'object') { + result.push(endpoint.headers.map((header: IEndpointHeader) => typeof (data[header.name]) === 'object' ? JSON.stringify(data[header.name]) : data[header.name])); + } else if (typeof (data) === 'number') { + result = [data]; + } else { + throw typeof (data) + ' is an unexpected type.'; } + + return result; } function getAccessToken_(code: string): IAccessToken { - return doRequest_('https://login.eveonline.com/v2/oauth/token', 'post', null, { - grant_type: "authorization_code", - code - }).data as IAccessToken; + const headers = HEADERS; + headers['authorization'] = 'Basic ' + Utilities.base64EncodeWebSafe(PropertiesService.getScriptProperties().getProperty('CLIENT_ID') + ':' + PropertiesService.getScriptProperties().getProperty('CLIENT_SECRET')); + const request: IRequest = { + url: 'https://login.eveonline.com/v2/oauth/token', + method: 'POST', + headers: headers, + payload: JSON.stringify({ grant_type: 'authorization_code', code }), + muteHttpExceptions: true, + }; + + return doRequests_([request]).data[0] as IAccessToken; } function getCharacterAffiliation_(character_id: number): ICharacterAffiliation { - return doRequest_(PropertiesService.getScriptProperties().getProperty('BASE_URL') + '/v1/characters/affiliation/', 'post', null, [character_id]).data[0] as ICharacterAffiliation; + const request: IRequest = { + url: `${PropertiesService.getScriptProperties().getProperty('BASE_URL')}/v1/characters/affiliation/`, + method: 'POST', + headers: HEADERS, + payload: JSON.stringify([character_id]), + muteHttpExceptions: true, + + }; + return doRequests_([request]).data[0] as ICharacterAffiliation; } function parseJWT_(access_token: string): ICharacterData { const jwt = JSON.parse(Utilities.newBlob(Utilities.base64DecodeWebSafe(access_token.split('.')[1])).getDataAsString()); if (jwt.iss !== 'login.eveonline.com') throw 'Access token validation error: invalid issuer'; if (jwt.azp !== PropertiesService.getScriptProperties().getProperty('CLIENT_ID')) throw 'Access token validation error: invalid authorized party'; - return {character_name: jwt.name, character_id: jwt.sub.split(':')[2]} + return { character_name: jwt.name, character_id: jwt.sub.split(':')[2] }; } -function refreshToken_(character_name: string): string { - const refresh_token = getProperty_(getCharacterRow_(character_name), CharacterRowData.refresh_token); - const response = doRequest_('https://login.eveonline.com/v2/oauth/token', 'post', null, { - grant_type: "refresh_token", - refresh_token: refresh_token.getValue() - }).data as IAccessToken; - // !TODO Rotate refresh_tokens refresh_token.setValue(response.refresh_token); - CacheService.getDocumentCache().put(character_name + '_access_token', response.access_token, 1080); +function refreshToken_(character: ICharacterRowData): string { + const headers = HEADERS; + headers['authorization'] = 'Basic ' + Utilities.base64EncodeWebSafe(PropertiesService.getScriptProperties().getProperty('CLIENT_ID') + ':' + PropertiesService.getScriptProperties().getProperty('CLIENT_SECRET')); + const request: IRequest = { + url: 'https://login.eveonline.com/v2/oauth/token', + method: 'POST', + headers: headers, + payload: JSON.stringify({ grant_type: 'refresh_token', refresh_token: character.refresh_token }), + muteHttpExceptions: true, + }; + const response = doRequests_([request]).data[0] as IAccessToken; + CacheService.getDocumentCache().put(character.character_name + '_access_token', response.access_token, 1080); return response.access_token; } @@ -281,10 +264,21 @@ function saveCharacter_(character_data: any[], access_token: string): void { character_row ? character_row.setValues([character_data]) : authSheet.appendRow(character_data); authSheet.autoResizeColumns(1, 5); if (!getMainCharacter()) setMainCharacter(character_data[0]); - CacheService.getDocumentCache().put(character_data[0] + '_access_token', access_token, 1080) + CacheService.getDocumentCache().put(character_data[0] + '_access_token', access_token, 1080); } -function getCharacterRow_(character_name: string): Range { +function getCharacterRowData_(character_name: string): ICharacterRowData { + const character_range = getCharacterRow_(character_name); + return { + character_id: character_range.getCell(1, CharacterRowData.character_id + 1).getValue() as number, + character_name: character_range.getCell(1, CharacterRowData.character_name + 1).getValue() as string, + corporation_id: character_range.getCell(1, CharacterRowData.corporation_id + 1).getValue() as number, + alliance_id: character_range.getCell(1, CharacterRowData.alliance_id + 1).getValue() as number, + refresh_token: character_range.getCell(1, CharacterRowData.refresh_token + 1).getValue() as string, + }; +} + +function getCharacterRow_(character_name: string): GoogleAppsScript.Spreadsheet.Range { const auth_sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Auth Data'); if (null === auth_sheet) return null; const row_index = auth_sheet.getDataRange().getValues().map(function (r) { @@ -294,34 +288,32 @@ function getCharacterRow_(character_name: string): Range { return auth_sheet.getRange(row_index + 1, 1, 1, 5); } -function getProperty_(character: Range, property: number): Range { - return character.getCell(1, property + 1); -} +function doRequests_(requests: IRequest[]): any | any[] { + let data = []; + const responses = UrlFetchApp.fetchAll(requests); + + responses.forEach((response: HTTPResponse, idx: number) => { + if (response.getResponseCode() !== 200) { + throw buildError_({ + body: response.getContentText(), + code: response.getResponseCode(), + path: requests[idx].url, + }); + } -function chunkArray_(array: any[], chunk_size: number): any[][] { - return array.reduce((a, b, i, g) => !(i % chunk_size) ? a.concat([g.slice(i, i + chunk_size)]) : a, []); + data = data.concat(JSON.parse(response.getContentText())); + }); + + return { + headers: responses[0] === undefined ? null : responses[0].getAllHeaders(), + data, + }; } -function doRequest_(path: string, method: string, token: string, data: any): IRequestResponse { - const auth = token ? 'Bearer ' + token : 'Basic ' + Utilities.base64EncodeWebSafe(PropertiesService.getScriptProperties().getProperty('CLIENT_ID') + ':' + PropertiesService.getScriptProperties().getProperty('CLIENT_SECRET')); - const options = { - method, - muteHttpExceptions: true, - headers: {'User-Agent': 'GESI V2', 'Content-Type': 'application/json', 'Authorization': auth} - } as URLFetchRequestOptions; - if (data) options['payload'] = JSON.stringify(data); - const response = UrlFetchApp.fetch(path, options); - const response_body = JSON.parse(response.getContentText()); - if (response.getResponseCode() !== 200) { - throw JSON.stringify({ - body: response_body, - code: response.getResponseCode(), - character: getMainCharacter(), - sheet_id: SpreadsheetApp.getActiveSpreadsheet().getId(), - path - }); - } - return {data: response_body, headers: response.getHeaders()}; +function buildError_(error: Partial): string { + error.sheet_id = SpreadsheetApp.getActiveSpreadsheet().getId(); + error.character = getMainCharacter(); + return JSON.stringify(error); } // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -330,6 +322,15 @@ function doRequest_(path: string, method: string, token: string, data: any): IRe enum CharacterRowData { character_name, character_id, corporation_id, alliance_id, refresh_token } +interface IError { + body: string; + code: number; + character: string; + sheet_id: string; + path?: string; + method?: string; +} + interface IEndpoint { description: string; headers: IEndpointHeader[]; @@ -354,11 +355,6 @@ interface IEndpointHeader { sub_headers?: string[]; } -interface IRequestResponse { - data: IAccessToken | ICharacterAffiliation | string; - headers: object -} - interface IAccessToken { access_token: string; token_type: string; @@ -386,3 +382,19 @@ interface IFunctionParam { [param: string]: any; } + +interface ICharacterRowData { + character_id: number; + character_name: string; + corporation_id: number; + alliance_id: number; + refresh_token: string; +} + +interface IRequest { + url: string; + method: string; + payload?: string; + headers: Object; + muteHttpExceptions: boolean; +}