diff --git a/Daily Times.js b/Daily Times.js new file mode 100644 index 0000000000..d6b6501668 --- /dev/null +++ b/Daily Times.js @@ -0,0 +1,426 @@ +{ + "translatorID": "7e4502b0-38e4-45e1-a372-2b7c784e52a8", + "label": "Daily Times", + "creator": "VWF", + "target": "^https?://www\\.?.dailytimesng\\.com/", + "minVersion": "5.0", + "maxVersion": "", + "priority": 100, + "inRepository": true, + "translatorType": 4, + "browserSupport": "gcsibv", + "lastUpdated": "2025-12-15 13:05:28" +} + +/* + ***** BEGIN LICENSE BLOCK ***** + + Copyright © 2025 VWF + + This file is part of Zotero. + + Zotero is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Zotero is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with Zotero. If not, see . + + ***** END LICENSE BLOCK ***** +*/ + +function meta(doc, nameOrProp) { + let m = doc.querySelector('meta[property="' + nameOrProp + '"]') + || doc.querySelector('meta[name="' + nameOrProp + '"]'); + return m ? m.getAttribute('content') : ''; +} + +function isMultiWordAuthor(name) { + return name && name.trim().split(/\s+/).length > 1; +} + +function parseJSONLD(doc) { + let nodes = doc.querySelectorAll('script[type="application/ld+json"]'); + for (let node of nodes) { + let txt = node.textContent.trim(); + if (!txt) continue; + try { + let parsed = JSON.parse(txt); + let candidates = []; + if (Array.isArray(parsed)) { + candidates = parsed; + } + else if (parsed['@graph'] && Array.isArray(parsed['@graph'])) { + candidates = parsed['@graph']; + } + else if (parsed.mainEntity) { + candidates = [parsed.mainEntity, parsed]; + } + else { + candidates = [parsed]; + } + + for (let cand of candidates) { + if (!cand) continue; + let t = cand['@type'] || cand.type; + if (!t) continue; + if (typeof t === 'string') { + if (t.includes('Article')) { + return cand; + } + } + else if (Array.isArray(t)) { + for (let tt of t) { + if (typeof tt === 'string' && tt.includes('Article')) { + return cand; + } + } + } + } + } + catch (e) { + // ignore malformed JSON-LD + } + } + return null; +} + +function getSearchResults(doc, checkOnly) { + let items = {}; + let found = false; + // generic pattern in path for links + let rows = doc.querySelectorAll('a[href*="/"]'); + for (let row of rows) { + let href = row.href; + let title = ZU.trimInternal(row.textContent || row.title || ''); + if (!href || !title) continue; + if (checkOnly) return true; + found = true; + items[href] = title; + } + return found ? items : false; +} + +function isIndexURL(url) { + return url && (url.includes('/tag/') || url.includes('/section/')); +} + +function detectWeb(doc, url) { + // 1) JSON-LD Article -> single article + let j = parseJSONLD(doc); + if (j) { + return 'newspaperArticle'; + } + + // 2) explicit index/list page via URL pattern + if (isIndexURL(url)) { + return 'multiple'; + } + + // 3) meta-based hints of an article + if (meta(doc, 'article:published_time')) { + return 'newspaperArticle'; + } + let ogType = (meta(doc, 'og:type') || '').toLowerCase(); + if (ogType === 'article') { + return 'newspaperArticle'; + } + + // 4) fallback headline selector strongly suggesting article page + if (text(doc, 'h1.h4.sm:h2.xl:display-8.text-primary')) { + return 'newspaperArticle'; + } + + // 5) Only at this stage, test for listing + if (getSearchResults(doc, true)) { + return 'multiple'; + } + + return false; +} + +async function doWeb(doc, url) { + url = url || doc.location.href; + let mode = detectWeb(doc, url); + if (mode === 'multiple') { + let items = getSearchResults(doc, false); + if (!items) return; + let selected = await Zotero.selectItems(items); + if (!selected) return; + for (let u of Object.keys(selected)) { + await scrape(await requestDocument(u)); + } + } + else if (mode === 'newspaperArticle') { + await scrape(doc, url); + } + // else do nothing +} + +async function scrape(doc, url) { + url = url || doc.location.href; + + // Support splitting multiple author names from meta tags + function splitAuthors(nameStr) { + if (!nameStr) return []; + let s = nameStr.trim(); + s = s.replace(/^\s*by\s+/i, '').trim(); + s = s.replace(/\s*\([^)]*\)\s*$/, '').trim(); + s = s.replace(/,\s*[A-Z][a-z]+(?:[\s-][A-Z][a-z]+)*$/, '').trim(); + if (s.includes('|')) s = s.split('|')[0].trim(); + let parts = s.split(/\s+(?:and|&)\s+|;\s*/i); + if (parts.length === 1 && s.includes(',') !== -1) { + parts = s.split(/\s*,\s*/).map(p => p.trim()).filter(Boolean); + } + let cleaned = []; + for (let p of parts) { + let np = (p || '').trim(); + np = np.replace(/^\s*by\s+/i, '').trim(); + np = np.replace(/\s*\([^)]*\)\s*$/, '').trim(); + np = np.replace(/,\s*[A-Z][a-z]+(?:[\s-][A-Z][a-z]+)*$/, '').trim(); + if (np && !/^(agency|news desk|agency reporter|times|our reporter|editor|nigeria|staff|bureau)$/i.test(np)) { + cleaned.push(np); + } + } + return cleaned; + } + + let item = new Zotero.Item('newspaperArticle'); + + let data = parseJSONLD(doc); + + // If JSON-LD present, prefer it + if (data) { + item.title = ZU.unescapeHTML( + data.headline + || meta(doc, 'og:title') + || text(doc, 'h1.h4.sm:h2.xl:display-8.text-primary') + || '' + ); + + // Remove trailing "- Daily Times" or variants + item.title = item.title.replace(/\s*[-|–—]\s*Daily\s*Times\s*$/i, '').trim(); + + item.abstractNote = ZU.unescapeHTML( + data.description + || meta(doc, 'og:description') + || meta(doc, 'description') + || '' + ); + + item.url = data.url || meta(doc, 'og:url') || url; + item.language = data.inLanguage || meta(doc, 'og:locale') || 'en'; + + let rawJsonDate = data.datePublished || ''; + if (rawJsonDate) { + let isoFromZU = ZU.strToISO(rawJsonDate); + item.date = isoFromZU || rawJsonDate; + } + + // --- JSON-LD authors --- + if (data.author) { + let authors = Array.isArray(data.author) ? data.author : [data.author]; + let graph = []; + + try { + let nodes = doc.querySelectorAll('script[type="application/ld+json"]'); + for (let node of nodes) { + let txt = node.textContent.trim(); + if (!txt) continue; + let parsed = JSON.parse(txt); + if (parsed['@graph'] && Array.isArray(parsed['@graph'])) { + graph = parsed['@graph']; + break; + } + } + } + catch (e) { + // ignore malformed ld+json + } + + for (let a of authors) { + let name = ''; + + if (typeof a === 'string') { + name = a; + } + else if (a && typeof a === 'object') { + if (a.name) { + name = a.name; + } + else if (a['@id']) { + let match = graph.find(obj => obj['@id'] === a['@id']); + if (match && match.name) name = match.name; + } + } + + name = (name || '').trim(); + + if (name.includes('|')) { + name = name.split('|')[0].trim(); + } + + name = name.replace(/,\s*[A-Z][a-z]+(?:[\s-][A-Z][a-z]+)*$/, '').trim(); + + if ( + name + && !/agency|news desk|agency reporter|times|our reporter|editor|nigeria|staff|bureau/i.test(name.toLowerCase()) + && isMultiWordAuthor(name) + ) { + item.creators.push(ZU.cleanAuthor(name, 'author')); + } + } + } + } + + // --- Fallbacks --- + if (!item.title || !item.title.trim()) { + item.title = ZU.unescapeHTML( + meta(doc, 'og:title') + || text(doc, 'h1.h4.sm:h2.xl:display-8.text-primary') + || '' + ); + } + + if (!item.abstractNote || !item.abstractNote.trim()) { + item.abstractNote = ZU.unescapeHTML( + meta(doc, 'og:description') + || meta(doc, 'description') + || '' + ); + } + + if (!item.date || !item.date.trim()) { + let metaDate = meta(doc, 'article:published_time'); + if (metaDate) { + let isoDate = ZU.strToISO(metaDate); + item.date = isoDate || metaDate; + } + } + + if (!item.url || !item.url.trim()) { + item.url = meta(doc, 'og:url') || url; + } + + if (!item.publicationTitle) { + item.publicationTitle = 'Daily Times'; + } + + if (!item.ISSN) { + item.ISSN = '0331-2739'; + } + + // --- Fallback authors in sequence --- + if (item.creators.length === 0) { + let cand1 = meta(doc, 'author'); + let cand2 = text(doc, 'div.author.hstack.gap-1 a'); + + let candidates = []; + + // Priority: use ONLY cand1 if present + if (cand1) { + candidates = splitAuthors(cand1); + } + // If cand1 missing, use ONLY cand2 + else if (cand2) { + candidates = splitAuthors(cand2); + } + + for (let name of candidates) { + if ( + name + && !/agency|news desk|agency reporter|times|our reporter|editor|nigeria|staff|bureau/i.test(name.toLowerCase()) + && isMultiWordAuthor(name) + ) { + item.creators.push(ZU.cleanAuthor(name, 'author')); + } + } + } + + item.attachments.push({ + document: doc, + title: 'Snapshot' + }); + + item.place = 'Nigeria'; + item.complete(); +} + +/** BEGIN TEST CASES **/ +var testCases = [ + { + "type": "web", + "url": "https://dailytimesng.com/nis-announces-temporary-disruptions-on-passport-digital-platform/", + "items": [ + { + "itemType": "newspaperArticle", + "title": "NIS announces temporary disruptions on passport digital platform", + "creators": [ + { + "firstName": "Promise", + "lastName": "Eze", + "creatorType": "author" + } + ], + "date": "2025-12-12", + "ISSN": "0331-2739", + "abstractNote": "The Nigeria Immigration Service (NIS) has announced that its Passport Digital Platform will experience temporary disruptions due to routine system maintenance scheduled to take place from 12:00 a.m. on 12 December to 6:00 a.m. on 14 December 2025 (GMT).", + "language": "en-GB", + "libraryCatalog": "Daily Times", + "place": "Nigeria", + "publicationTitle": "Daily Times", + "url": "https://dailytimesng.com/nis-announces-temporary-disruptions-on-passport-digital-platform/", + "attachments": [ + { + "title": "Snapshot", + "mimeType": "text/html" + } + ], + "tags": [], + "notes": [], + "seeAlso": [] + } + ] + }, + { + "type": "web", + "url": "https://dailytimesng.com/nysc-redesigns-saed-introduces-ai-digital-skills-to-tackle-youth-unemployment/", + "items": [ + { + "itemType": "newspaperArticle", + "title": "NYSC redesigns SAED, introduces AI, digital skills to tackle youth unemployment", + "creators": [ + { + "firstName": "Ukpono", + "lastName": "Ukpong", + "creatorType": "author" + } + ], + "date": "2025-12-14", + "ISSN": "0331-2739", + "abstractNote": "Daily Times is Nigeria trusted source for breaking news, politics, business, sports, entertainment, and analysis bringing you stories that matter.", + "language": "en-GB", + "libraryCatalog": "Daily Times", + "place": "Nigeria", + "publicationTitle": "Daily Times", + "url": "https://dailytimesng.com/nysc-redesigns-saed-introduces-ai-digital-skills-to-tackle-youth-unemployment/", + "attachments": [ + { + "title": "Snapshot", + "mimeType": "text/html" + } + ], + "tags": [], + "notes": [], + "seeAlso": [] + } + ] + } +] +/** END TEST CASES **/