diff --git a/.gitignore b/.gitignore index 6704566..f18ac09 100644 --- a/.gitignore +++ b/.gitignore @@ -102,3 +102,5 @@ dist # TernJS port file .tern-port + +output.txt \ No newline at end of file diff --git a/connectors/arena.js b/connectors/arena/v6.js similarity index 65% rename from connectors/arena.js rename to connectors/arena/v6.js index 5fa9055..801d547 100644 --- a/connectors/arena.js +++ b/connectors/arena/v6.js @@ -1,21 +1,19 @@ +// HTTP Header: +// Liferay-Portal: Liferay Portal Community Edition 6.2 CE GA6 (Newton / Build 6205 / January 6, 2016) + const xml2js = require('xml2js') const cheerio = require('cheerio') const querystring = require('querystring') const request = require('superagent') -const common = require('../connectors/common') +const common = require('../../connectors/common') console.log('arena connector loading...') -const LIBRARIES_URL_PORTLET = '?p_p_id=extendedSearch_WAR_arenaportlet&p_p_lifecycle=2&p_p_state=normal&p_p_mode=view&p_p_resource_id=/extendedSearch/?wicket:interface=:0:extendedSearchPanel:extendedSearchForm:organisationHierarchyPanel:organisationContainer:organisationChoice::IBehaviorListener:0:&p_p_cacheability=cacheLevelPage&random=0.08709241788681465extended-search?p_p_id=extendedSearch_WAR_arenaportlet&p_p_lifecycle=2&p_p_state=normal&p_p_mode=view&p_p_resource_id=/extendedSearch/?wicket:interface=:0:extendedSearchPanel:extendedSearchForm:organisationHierarchyPanel:organisationContainer:organisationChoice::IBehaviorListener:0:&p_p_cacheability=cacheLevelPage&random=0.08709241788681465' const LIBRARIES_URL_PORTLETS = '?p_p_id=extendedSearch_WAR_arenaportlets&p_p_lifecycle=2&p_p_state=normal&p_p_mode=view&p_p_resource_id=/extendedSearch/?wicket:interface=:0:extendedSearchPanel:extendedSearchForm:organisationHierarchyPanel:organisationContainer:organisationChoice::IBehaviorListener:0:&p_p_cacheability=cacheLevelPage&random=0.08709241788681465extended-search?p_p_id=extendedSearch_WAR_arenaportlet&p_p_lifecycle=2&p_p_state=normal&p_p_mode=view&p_p_resource_id=/extendedSearch/?wicket:interface=:0:extendedSearchPanel:extendedSearchForm:organisationHierarchyPanel:organisationContainer:organisationChoice::IBehaviorListener:0:&p_p_cacheability=cacheLevelPage&random=0.08709241788681465' -const SEARCH_URL_PORTLET = 'search?p_p_id=searchResult_WAR_arenaportlet&p_p_lifecycle=1&p_p_state=normal&p_r_p_arena_urn:arena_facet_queries=&p_r_p_arena_urn:arena_search_type=solr&p_r_p_arena_urn:arena_search_query=[BOOKQUERY]' const SEARCH_URL_PORTLETS = 'search?p_p_id=searchResult_WAR_arenaportlets&p_p_lifecycle=1&p_p_state=normal&p_p_mode=view&p_r_p_687834046_facet_queries=&p_r_p_687834046_search_type=solr&p_r_p_687834046_search_query=[BOOKQUERY]' -const ITEM_URL_PORTLET = 'results?p_p_id=crDetailWicket_WAR_arenaportlet&p_p_lifecycle=1&p_p_state=normal&p_r_p_arena_urn:arena_search_item_id=[ITEMID]&p_r_p_arena_urn:arena_facet_queries=&p_r_p_arena_urn:arena_agency_name=[ARENANAME]&p_r_p_arena_urn:arena_search_item_no=0&p_r_p_arena_urn:arena_search_type=solr' const ITEM_URL_PORTLETS = 'results?p_p_id=crDetailWicket_WAR_arenaportlets&p_p_lifecycle=1&p_p_state=normal&p_p_mode=view&p_p_col_id=column-2&p_p_col_pos=2&p_p_col_count=4&p_r_p_687834046_facet_queries=&p_r_p_687834046_search_item_no=0&p_r_p_687834046_sort_advice=field%3DRelevance%26direction%3DDescending&p_r_p_687834046_search_type=solr&p_r_p_687834046_search_item_id=[ITEMID]&p_r_p_687834046_agency_name=[ARENANAME]' -const HOLDINGS_URL_PORTLET = 'results?p_p_id=crDetailWicket_WAR_arenaportlet&p_p_lifecycle=2&p_p_state=normal&p_p_mode=view&p_p_resource_id=/crDetailWicket/?wicket:interface=:0:recordPanel:holdingsPanel::IBehaviorListener:0:&p_p_cacheability=cacheLevelPage&p_p_col_id=column-2&p_p_col_pos=1&p_p_col_count=3' const HOLDINGS_URL_PORTLETS = 'results?p_p_id=crDetailWicket_WAR_arenaportlets&p_p_lifecycle=2&p_p_state=normal&p_p_mode=view&p_p_resource_id=/crDetailWicket/?wicket:interface=:0:recordPanel:holdingsPanel::IBehaviorListener:0:&p_p_cacheability=cacheLevelPage&p_p_col_id=column-2&p_p_col_pos=1&p_p_col_count=3' -const HOLDINGSDETAIL_URL_PORTLET = 'results?p_p_id=crDetailWicket_WAR_arenaportlet&p_p_lifecycle=2&p_p_state=normal&p_p_mode=view&p_p_resource_id=[RESOURCEID]&p_p_cacheability=' const HOLDINGSDETAIL_URL_PORTLETS = 'results?p_p_id=crDetailWicket_WAR_arenaportlets&p_p_lifecycle=2&p_p_state=normal&p_p_mode=view&p_p_resource_id=[RESOURCEID]&p_p_cacheability=' /** @@ -30,18 +28,34 @@ exports.getService = (service) => common.getService(service) */ exports.getLibraries = async function (service) { const responseLibraries = common.initialiseGetLibrariesResponse(service) + let $ = null; try { const agent = request.agent() - if (responseLibraries.libraries.length > 0) return common.endResponse(responseLibraries) + + if (service.PreLoad) + // Get necessary session cookies + await agent.get(service.Url); + + if (service.SignupUrl) { + // This service needs to be loaded using the signup page rather + // than the advanced search page. + let signupResponse = await agent.get(service.SignupUrl); + $ = cheerio.load(signupResponse.text); + + if ($('select[name="choiceBranch"] option').length > 1) { + $('select[name="choiceBranch"] option').each(function () { + if (common.isLibrary($(this).text())) responseLibraries.libraries.push($(this).text()) + }) + return common.endResponse(responseLibraries) + } + } // Get the advanced search page - let advancedSearchResponse = null - // The AdvancedUrl tends to either be advanced-search or extended-search - advancedSearchResponse = await agent.get(service.Url + service.AdvancedUrl) + let advancedSearchResponse = await agent.get(service.Url + service.AdvancedUrl) // The advanced search page may have libraries listed on it - let $ = cheerio.load(advancedSearchResponse.text) + $ = cheerio.load(advancedSearchResponse.text) if ($('.arena-extended-search-branch-choice option').length > 1) { $('.arena-extended-search-branch-choice option').each(function () { if (common.isLibrary($(this).text())) responseLibraries.libraries.push($(this).text()) @@ -51,7 +65,7 @@ exports.getLibraries = async function (service) { // If not we'll need to call a portlet to get the data const headers = { Accept: 'text/xml', 'Wicket-Ajax': true, 'Wicket-FocusedElementId': 'id__extendedSearch__WAR__arenaportlet____e', 'Content-Type': 'application/x-www-form-urlencoded' } - const url = service.Url + service.AdvancedUrl + (service.portlets ? LIBRARIES_URL_PORTLETS : LIBRARIES_URL_PORTLET) + const url = service.Url + service.AdvancedUrl + LIBRARIES_URL_PORTLETS const responseHeaderRequest = await agent.post(url).send(querystring.stringify({ 'organisationHierarchyPanel:organisationContainer:organisationChoice': service.OrganisationId })).set(headers) const js = await xml2js.parseStringPromise(responseHeaderRequest.text) @@ -81,10 +95,11 @@ exports.searchByISBN = async function (isbn, service) { try { const agent = request.agent() - let bookQuery = (service.SearchType !== 'Keyword' ? service.ISBNAlias + '_index:' + isbn : isbn) + //let bookQuery = (service.SearchType !== 'Keyword' ? service.ISBNAlias + '_index:' + isbn : isbn) + let bookQuery = `number_index:${isbn}`; if (service.OrganisationId) bookQuery = 'organisationId_index:' + service.OrganisationId + '+AND+' + bookQuery - const searchUrl = (service.Portlets ? SEARCH_URL_PORTLETS : SEARCH_URL_PORTLET).replace('[BOOKQUERY]', bookQuery) + const searchUrl = SEARCH_URL_PORTLETS.replace('[BOOKQUERY]', bookQuery) responseHoldings.url = service.Url + searchUrl let searchResponse = await agent.get(responseHoldings.url).timeout(20000) @@ -98,7 +113,7 @@ exports.searchByISBN = async function (isbn, service) { itemId = itemId.substring(0, itemId.indexOf('&')) responseHoldings.id = itemId - const itemDetailsUrl = (service.Portlets ? ITEM_URL_PORTLETS : ITEM_URL_PORTLET).replace('[ARENANAME]', service.ArenaName).replace('[ITEMID]', itemId) + const itemDetailsUrl = ITEM_URL_PORTLETS.replace('[ARENANAME]', service.ArenaName).replace('[ITEMID]', itemId) const itemUrl = service.Url + itemDetailsUrl const itemPageResponse = await agent.get(itemUrl).set({ Connection: 'keep-alive' }).timeout(20000) @@ -109,14 +124,18 @@ exports.searchByISBN = async function (isbn, service) { var libName = $(this).find('.arena-branch-name span').text() var totalAvailable = $(this).find('.arena-availability-info span').eq(0).text().replace('Total ', '') var checkedOut = $(this).find('.arena-availability-info span').eq(1).text().replace('On loan ', '') - if (libName) responseHoldings.availability.push({ library: libName, available: ((totalAvailable ? parseInt(totalAvailable) : 0) - (checkedOut ? parseInt(checkedOut) : 0)), unavailable: (checkedOut !== '' ? parseInt(checkedOut) : 0) }) + var av = ((totalAvailable ? parseInt(totalAvailable) : 0) - (checkedOut ? parseInt(checkedOut) : 0)); + var nav = (checkedOut !== '' ? parseInt(checkedOut) : 0); + + if (libName && ((av + nav) > 0)) + responseHoldings.availability.push({ library: libName, available: av, unavailable: nav }) }) return common.endResponse(responseHoldings) } // Get the item holdings widget const holdingsPanelHeader = { Accept: 'text/xml', 'Wicket-Ajax': true } - const holdingsPanelUrl = service.Url + (service.Portlets ? HOLDINGS_URL_PORTLETS : HOLDINGS_URL_PORTLET) + const holdingsPanelUrl = service.Url + HOLDINGS_URL_PORTLETS var holdingsPanelPortletResponse = await agent.get(holdingsPanelUrl).set(holdingsPanelHeader).timeout(20000) var js = await xml2js.parseStringPromise(holdingsPanelPortletResponse.text) @@ -124,11 +143,16 @@ exports.searchByISBN = async function (isbn, service) { $ = cheerio.load(js['ajax-response'].component[0]._) if ($('.arena-holding-nof-total, .arena-holding-nof-checked-out, .arena-holding-nof-available-for-loan').length > 0) { - $('.arena-holding-child-container').each(function () { - var libName = $(this).find('span.arena-holding-link').text() - var totalAvailable = $(this).find('td.arena-holding-nof-total span.arena-value').text() || (parseInt($(this).find('td.arena-holding-nof-available-for-loan span.arena-value').text() || 0) + parseInt($(this).find('td.arena-holding-nof-checked-out span.arena-value').text() || 0)) - var checkedOut = $(this).find('td.arena-holding-nof-checked-out span.arena-value').text() - if (libName) responseHoldings.availability.push({ library: libName, available: (parseInt(totalAvailable) - (checkedOut ? parseInt(checkedOut) : 0)), unavailable: (checkedOut !== '' ? parseInt(checkedOut) : 0) }) + $('.arena-holding-child-container').each(function (idx, container) { + var libName = $(container).find('span.arena-holding-link').text() + var totalAvailable = $(container).find('.arena-holding-nof-total span.arena-value').text() || (parseInt($(container).find('td.arena-holding-nof-available-for-loan span.arena-value').text() || 0) + parseInt($(container).find('td.arena-holding-nof-checked-out span.arena-value').text() || 0)) + var checkedOut = $(container).find('.arena-holding-nof-checked-out span.arena-value').text() + + var av = ((totalAvailable ? parseInt(totalAvailable) : 0) - (checkedOut ? parseInt(checkedOut) : 0)); + var nav = (checkedOut !== '' ? parseInt(checkedOut) : 0); + + if (libName && ((av + nav) > 0)) + responseHoldings.availability.push({ library: libName, available: av, unavailable: nav }) }) return common.endResponse(responseHoldings) } @@ -140,7 +164,7 @@ exports.searchByISBN = async function (isbn, service) { var holdingsHeaders = { Accept: 'text/xml', 'Wicket-Ajax': true } holdingsHeaders['Wicket-FocusedElementId'] = 'id__crDetailWicket__WAR__arenaportlets____2a' var resourceId = '/crDetailWicket/?wicket:interface=:0:recordPanel:holdingsPanel:content:holdingsView:' + (currentOrg + 1) + ':holdingContainer:togglableLink::IBehaviorListener:0:' - var holdingsUrl = service.Url + (service.Portlets ? HOLDINGSDETAIL_URL_PORTLETS : HOLDINGSDETAIL_URL_PORTLET).replace('[RESOURCEID]', resourceId) + var holdingsUrl = service.Url + HOLDINGSDETAIL_URL_PORTLETS.replace('[RESOURCEID]', resourceId) var holdingsResponse = await agent.get(holdingsUrl).set(holdingsHeaders).timeout(20000) var holdingsJs = await xml2js.parseStringPromise(holdingsResponse.text) $ = cheerio.load(holdingsJs['ajax-response'].component[0]._) @@ -152,7 +176,7 @@ exports.searchByISBN = async function (isbn, service) { const availabilityRequests = [] libsData.each(function (i) { resourceId = '/crDetailWicket/?wicket:interface=:0:recordPanel:holdingsPanel:content:holdingsView:' + (currentOrg + 1) + ':childContainer:childView:' + i + ':holdingPanel:holdingContainer:togglableLink::IBehaviorListener:0:' - const libUrl = service.Url + (service.Portlets ? HOLDINGSDETAIL_URL_PORTLETS : HOLDINGSDETAIL_URL_PORTLET).replace('[RESOURCEID]', resourceId) + const libUrl = service.Url + HOLDINGSDETAIL_URL_PORTLETS.replace('[RESOURCEID]', resourceId) var headers = { Accept: 'text/xml', 'Wicket-Ajax': true } availabilityRequests.push(agent.get(libUrl).set(headers).timeout(20000)) }) @@ -163,10 +187,15 @@ exports.searchByISBN = async function (isbn, service) { var availabilityJs = await xml2js.parseStringPromise(response.text) if (availabilityJs && availabilityJs['ajax-response']) { $ = cheerio.load(availabilityJs['ajax-response'].component[0]._) - var totalAvailable = $('td.arena-holding-nof-total span.arena-value').text() - var checkedOut = $('td.arena-holding-nof-checked-out span.arena-value').text() + var totalAvailable = $('.arena-holding-nof-total span.arena-value').text() + var checkedOut = $('.arena-holding-nof-checked-out span.arena-value').text() $ = cheerio.load(availabilityJs['ajax-response'].component[2]._) - responseHoldings.availability.push({ library: $('span.arena-holding-link').text(), available: ((totalAvailable ? parseInt(totalAvailable) : 0) - (checkedOut ? parseInt(checkedOut) : 0)), unavailable: (checkedOut ? parseInt(checkedOut) : 0) }) + + var av = ((totalAvailable ? parseInt(totalAvailable) : 0) - (checkedOut ? parseInt(checkedOut) : 0)); + var nav = (checkedOut ? parseInt(checkedOut) : 0); + + if ((av + nav) > 0) + responseHoldings.availability.push({ library: $('span.arena-holding-link').text(), available: av, unavailable: nav }) } }) } diff --git a/connectors/arena/v7.js b/connectors/arena/v7.js new file mode 100644 index 0000000..426e726 --- /dev/null +++ b/connectors/arena/v7.js @@ -0,0 +1,212 @@ +// HTTP Header: +// Liferay-Portal: Liferay Community Edition Portal 7.0.6 GA7 (Wilberforce / Build 7006 / April 17, 2018) + +const xml2js = require('xml2js') +const cheerio = require('cheerio') +const querystring = require('querystring') +const request = require('superagent') + +const common = require('../../connectors/common') + +console.log('arena connector loading...') + +const LIBRARIES_URL_PORTLET = '?p_p_id=extendedSearch_WAR_arenaportlet&p_p_lifecycle=2&p_p_state=normal&p_p_mode=view&p_p_resource_id=/extendedSearch/?wicket:interface=:0:extendedSearchPanel:extendedSearchForm:organisationHierarchyPanel:organisationContainer:organisationChoice::IBehaviorListener:0:&p_p_cacheability=cacheLevelPage&random=0.08709241788681465extended-search?p_p_id=extendedSearch_WAR_arenaportlet&p_p_lifecycle=2&p_p_state=normal&p_p_mode=view&p_p_resource_id=/extendedSearch/?wicket:interface=:0:extendedSearchPanel:extendedSearchForm:organisationHierarchyPanel:organisationContainer:organisationChoice::IBehaviorListener:0:&p_p_cacheability=cacheLevelPage&random=0.08709241788681465' +const SEARCH_URL_PORTLET = 'search?p_p_id=searchResult_WAR_arenaportlet&p_p_lifecycle=1&p_p_state=normal&p_r_p_arena_urn:arena_facet_queries=&p_r_p_arena_urn:arena_search_type=solr&p_r_p_arena_urn:arena_search_query=[BOOKQUERY]' +const ITEM_URL_PORTLET = 'results?p_p_id=crDetailWicket_WAR_arenaportlet&p_p_lifecycle=1&p_p_state=normal&p_r_p_arena_urn:arena_search_item_id=[ITEMID]&p_r_p_arena_urn:arena_facet_queries=&p_r_p_arena_urn:arena_agency_name=[ARENANAME]&p_r_p_arena_urn:arena_search_item_no=0&p_r_p_arena_urn:arena_search_type=solr' +const HOLDINGS_URL_PORTLET = 'results?p_p_id=crDetailWicket_WAR_arenaportlet&p_p_lifecycle=2&p_p_state=normal&p_p_mode=view&p_p_resource_id=/crDetailWicket/?wicket:interface=:0:recordPanel:holdingsPanel::IBehaviorListener:0:&p_p_cacheability=cacheLevelPage&p_p_col_id=column-2&p_p_col_pos=1&p_p_col_count=3' +const HOLDINGSDETAIL_URL_PORTLET = 'results?p_p_id=crDetailWicket_WAR_arenaportlet&p_p_lifecycle=2&p_p_state=normal&p_p_mode=view&p_p_resource_id=[RESOURCEID]&p_p_cacheability=' + +/** + * Gets the object representing the service + * @param {object} service + */ +exports.getService = (service) => common.getService(service) + +/** + * Gets the libraries in the service based upon possible search and filters within the library catalogue + * @param {object} service + */ +exports.getLibraries = async function (service) { + const responseLibraries = common.initialiseGetLibrariesResponse(service) + + try { + const agent = request.agent() + let $ = null; + //if (responseLibraries.libraries.length > 0) return common.endResponse(responseLibraries) + + if (service.SignupUrl) { + // This service needs to be loaded using the signup page rather + // than the advanced search page. + let signupResponse = await agent.get(service.SignupUrl); + $ = cheerio.load(signupResponse.text); + + if ($('select[name="branches-div:choiceBranch"] option').length > 1) { + $('select[name="branches-div:choiceBranch"] option').each(function () { + if (common.isLibrary($(this).text())) responseLibraries.libraries.push($(this).text()) + }) + return common.endResponse(responseLibraries) + } + } + + // Get the advanced search page + let advancedSearchResponse = await agent.get(service.Url + service.AdvancedUrl) + + // The advanced search page may have libraries listed on it + $ = cheerio.load(advancedSearchResponse.text) + if ($('.arena-extended-search-branch-choice option').length > 1) { + $('.arena-extended-search-branch-choice option').each(function () { + if (common.isLibrary($(this).text())) responseLibraries.libraries.push($(this).text()) + }) + return common.endResponse(responseLibraries) + } + + // If not we'll need to call a portlet to get the data + const headers = { Accept: 'text/xml', 'Wicket-Ajax': true, 'Wicket-FocusedElementId': 'id__extendedSearch__WAR__arenaportlet____e', 'Content-Type': 'application/x-www-form-urlencoded' } + const url = service.Url + service.AdvancedUrl + LIBRARIES_URL_PORTLET + const responseHeaderRequest = await agent.post(url).send(querystring.stringify({ 'organisationHierarchyPanel:organisationContainer:organisationChoice': service.OrganisationId })).set(headers) + const js = await xml2js.parseStringPromise(responseHeaderRequest.text) + + // Parse the results of the request + if (js && js !== 'Undeployed' && js['ajax-response']?.component) { + $ = cheerio.load(js['ajax-response'].component[0]._) + $('option').each(function () { + if (common.isLibrary($(this).text())) responseLibraries.libraries.push($(this).text()) + }) + } + } + catch(e) { + responseLibraries.exception = e + } + + return common.endResponse(responseLibraries) +} + +/** + * Retrieves the availability summary of an ISBN by library + * @param {string} isbn + * @param {object} service + */ +exports.searchByISBN = async function (isbn, service) { + const responseHoldings = common.initialiseSearchByISBNResponse(service) + + try { + const agent = request.agent() + + let bookQuery = `number_index:${isbn}`; + if (service.OrganisationId) bookQuery = 'organisationId_index:' + service.OrganisationId + '+AND+' + bookQuery + + const searchUrl = SEARCH_URL_PORTLET.replace('[BOOKQUERY]', bookQuery) + responseHoldings.url = service.Url + searchUrl + + let searchResponse = await agent.get(responseHoldings.url).timeout(20000) + + // No item found + if (!searchResponse || !searchResponse.text || (searchResponse.text && searchResponse.text.lastIndexOf('search_item_id') === -1)) return common.endResponse(responseHoldings) + + // Call to the item page + const pageText = searchResponse.text.replace(/\\x3d/g, '=').replace(/\\x26/g, '&') + let itemId = pageText.substring(pageText.lastIndexOf('search_item_id=') + 15) + itemId = itemId.substring(0, itemId.indexOf('&')) + responseHoldings.id = itemId + + const itemDetailsUrl = ITEM_URL_PORTLET.replace('[ARENANAME]', service.ArenaName).replace('[ITEMID]', itemId) + const itemUrl = service.Url + itemDetailsUrl + + const itemPageResponse = await agent.get(itemUrl).set({ Connection: 'keep-alive' }).timeout(20000) + let $ = cheerio.load(itemPageResponse.text) + + if ($('.arena-availability-viewbranch').length > 0) { // If the item holdings are available immediately on the page + $('.arena-availability-viewbranch').each(function () { + var libName = $(this).find('.arena-branch-name span').text() + var totalAvailable = $(this).find('.arena-availability-info span').eq(0).text().replace('Total ', '') + var checkedOut = $(this).find('.arena-availability-info span').eq(1).text().replace('On loan ', '') + var av = ((totalAvailable ? parseInt(totalAvailable) : 0) - (checkedOut ? parseInt(checkedOut) : 0)); + var nav = (checkedOut !== '' ? parseInt(checkedOut) : 0); + + if (libName && ((av + nav) > 0)) + responseHoldings.availability.push({ library: libName, available: av, unavailable: nav }) + }) + return common.endResponse(responseHoldings) + } + + // Attempts to fix Bexley; seems very picky on the order of cookies (alphabetical) and insists + // that there is a space after a semi-colon. However, even after that, it is still unreliable. + //const cookie = /Cookie:([^\n]+)/gi.exec(itemPageResponse.req._header)[1].trim().split(';').sort().join('; '); + + // Get the item holdings widget + const holdingsPanelHeader = { + Accept: 'text/xml', + 'Wicket-Ajax': true, + //Cookie: cookie + } + const holdingsPanelUrl = service.Url + HOLDINGS_URL_PORTLET + + var holdingsPanelPortletResponse = await agent.get(holdingsPanelUrl).set(holdingsPanelHeader).timeout(20000) + var js = await xml2js.parseStringPromise(holdingsPanelPortletResponse.text) + + if (!js['ajax-response'] || !js['ajax-response'].component) return common.endResponse(responseHoldings) + $ = cheerio.load(js['ajax-response'].component[0]._) + + if ($('.arena-holding-nof-total, .arena-holding-nof-checked-out, .arena-holding-nof-available-for-loan').length > 0) { + $('.arena-holding-child-container').each(function (idx, container) { + var libName = $(container).find('span.arena-holding-link').text() + var totalAvailable = $(container).find('.arena-holding-nof-total span.arena-value').text() || (parseInt($(container).find('td.arena-holding-nof-available-for-loan span.arena-value').text() || 0) + parseInt($(container).find('td.arena-holding-nof-checked-out span.arena-value').text() || 0)) + var checkedOut = $(container).find('.arena-holding-nof-checked-out span.arena-value').text() + + var av = ((totalAvailable ? parseInt(totalAvailable) : 0) - (checkedOut ? parseInt(checkedOut) : 0)); + var nav = (checkedOut !== '' ? parseInt(checkedOut) : 0); + + if (libName && ((av + nav) > 0)) + responseHoldings.availability.push({ library: libName, available: av, unavailable: nav }) + }) + return common.endResponse(responseHoldings) + } + + let currentOrg = null + $('.arena-holding-hyper-container .arena-holding-container a span').each(function (i) { if ($(this).text().trim() === (service.OrganisationName || service.Name)) currentOrg = i }) + if (currentOrg == null) return common.endResponse(responseHoldings) + + var holdingsHeaders = { Accept: 'text/xml', 'Wicket-Ajax': true } + holdingsHeaders['Wicket-FocusedElementId'] = 'id__crDetailWicket__WAR__arenaportlets____2a' + var resourceId = '/crDetailWicket/?wicket:interface=:0:recordPanel:holdingsPanel:content:holdingsView:' + (currentOrg + 1) + ':holdingContainer:togglableLink::IBehaviorListener:0:' + var holdingsUrl = service.Url + HOLDINGSDETAIL_URL_PORTLET.replace('[RESOURCEID]', resourceId) + var holdingsResponse = await agent.get(holdingsUrl).set(holdingsHeaders).timeout(20000) + var holdingsJs = await xml2js.parseStringPromise(holdingsResponse.text) + $ = cheerio.load(holdingsJs['ajax-response'].component[0]._) + + var libsData = $('.arena-holding-container') + const numLibs = libsData.length + if (!numLibs || numLibs === 0) return common.endResponse(responseHoldings) + + const availabilityRequests = [] + libsData.each(function (i) { + resourceId = '/crDetailWicket/?wicket:interface=:0:recordPanel:holdingsPanel:content:holdingsView:' + (currentOrg + 1) + ':childContainer:childView:' + i + ':holdingPanel:holdingContainer:togglableLink::IBehaviorListener:0:' + const libUrl = service.Url + HOLDINGSDETAIL_URL_PORTLET.replace('[RESOURCEID]', resourceId) + var headers = { Accept: 'text/xml', 'Wicket-Ajax': true } + availabilityRequests.push(agent.get(libUrl).set(headers).timeout(20000)) + }) + + let responses = await Promise.all(availabilityRequests); + + responses.forEach(async (response) => { + var availabilityJs = await xml2js.parseStringPromise(response.text) + if (availabilityJs && availabilityJs['ajax-response']) { + $ = cheerio.load(availabilityJs['ajax-response'].component[0]._) + var totalAvailable = $('.arena-holding-nof-total span.arena-value').text() + var checkedOut = $('.arena-holding-nof-checked-out span.arena-value').text() + $ = cheerio.load(availabilityJs['ajax-response'].component[2]._) + + var av = ((totalAvailable ? parseInt(totalAvailable) : 0) - (checkedOut ? parseInt(checkedOut) : 0)); + var nav = (checkedOut ? parseInt(checkedOut) : 0); + + if ((av + nav) > 0) + responseHoldings.availability.push({ library: $('span.arena-holding-link').text(), available: av, unavailable: nav }) + } + }) + } + catch(e) { + responseHoldings.exception = e + } + + return common.endResponse(responseHoldings) +} diff --git a/connectors/blackpool.js b/connectors/blackpool.js index 7fa1454..188875a 100644 --- a/connectors/blackpool.js +++ b/connectors/blackpool.js @@ -1,5 +1,6 @@ const request = require('superagent') const common = require('./common') +const cheerio = require('cheerio') console.log('blackpool connector loading...') @@ -9,12 +10,45 @@ console.log('blackpool connector loading...') */ exports.getService = (service) => common.getService(service) +getLibrariesInternal = async function (service) { + const agent = request.agent() + const response = { + libraries: [] + }; + + try { + const libraries = await agent.get(service.Url).timeout(20000) + + // HTML is malformed. Grab a sub-section using a regex. + const select = /\