├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── __tests__ ├── __data__ │ ├── extraFieldExtractorData.js │ ├── gsResponseHasCitation.js │ ├── gsResponseHasCitationJuly2023GSUpdate.js │ ├── gsResponseHasCitationJuly2023GSUpdateAltSearchCase.js │ ├── gsResponseHasPaperNoCitations.js │ ├── gsResponseHasRecaptcha.js │ ├── gsResponseNoCitation.js │ ├── helpers.js │ ├── zoteroItemsList.js │ ├── zoteroItemsList10set.js │ ├── zoteroItemsListSingleItemWithCount.js │ ├── zoteroItemsListSingleItemWithCountLegacyFormat.js │ ├── zoteroItemsListSingleItemWithHtmlTitle.js │ ├── zoteroItemsListSingleItemWithNoCount.js │ ├── zoteroItemsListSingleItemWithNoCreators.js │ └── zoteroItemsListSingleItemWithNoTitle.js ├── __setup__ │ ├── env.js │ └── setupJest.js ├── gsCitationCount.test.js └── utils.test.js ├── build.sh ├── jest.config.js ├── package-lock.json ├── package.json ├── src ├── bootstrap.js ├── gscc.js ├── icons │ ├── favicon.png │ └── faviconX2.png ├── locale │ ├── en-US │ │ ├── gscc-prefs.ftl │ │ └── gscc.ftl │ ├── es-ES │ │ ├── gscc-prefs.ftl │ │ └── gscc.ftl │ ├── fr-FR │ │ ├── gscc-prefs.ftl │ │ └── gscc.ftl │ ├── ja-JP │ │ ├── gscc-prefs.ftl │ │ └── gscc.ftl │ └── zh-CH │ │ ├── gscc-prefs.ftl │ │ └── gscc.ftl ├── manifest.json ├── prefs.js └── prefs.xhtml └── updates.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [justinribeiro] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .vscode 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # Optional npm cache directory 48 | .npm 49 | 50 | # Optional eslint cache 51 | .eslintcache 52 | 53 | # Optional REPL history 54 | .node_repl_history 55 | 56 | # Output of 'npm pack' 57 | *.tgz 58 | 59 | # Yarn Integrity file 60 | .yarn-integrity 61 | 62 | # dotenv environment variables file 63 | .env 64 | .env.test 65 | 66 | # parcel-bundler cache (https://parceljs.org/) 67 | .cache 68 | 69 | # next.js build output 70 | .next 71 | 72 | # nuxt.js build output 73 | .nuxt 74 | 75 | # vuepress build output 76 | .vuepress/dist 77 | 78 | # Serverless directories 79 | .serverless/ 80 | 81 | # FuseBox cache 82 | .fusebox/ 83 | 84 | # DynamoDB Local files 85 | .dynamodb/ 86 | 87 | # Custom Misc 88 | build/ 89 | builds/ 90 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | 375 | You may add additional accurate notices of copyright ownership. 376 | 377 | ## Exhibit B - “Incompatible With Secondary Licenses” Notice 378 | 379 | This Source Code Form is "Incompatible With Secondary Licenses", as 380 | defined by the Mozilla Public License, v. 2.0. 381 | 382 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Google Scholar Citation Count for Zotero 2 | 3 | > Add-on that fetches numbers of citations of your Zotero collection items from Google Scholar, adding the citation count to the extra column for reference and sorting. 4 | 5 | [![Zotero +v7.0](https://img.shields.io/badge/Zotero-%3E%3D%205.x-brightgreen)](https://www.zotero.org/) 6 | [![License: MPL 2.0](https://img.shields.io/badge/License-MPL%202.0-brightgreen.svg)](https://opensource.org/licenses/MPL-2.0) 7 | ![Test Coverage - Statements](https://img.shields.io/badge/statements-69.23%25-red.svg) 8 | ![Test Coverage - Branches](https://img.shields.io/badge/branches-59.79%25-red.svg) 9 | ![Test Coverage - Functions](https://img.shields.io/badge/functions-67.30%25-red.svg) 10 | ![Test Coverage - Lines](https://img.shields.io/badge/lines-70.14%25-red.svg) 11 | 12 | ## Download Latest Version 13 | 14 | Zotero 7 - [![v4.3.0](https://img.shields.io/badge/Download-v4.3.0-orange?style=for-the-badge)](https://github.com/justinribeiro/zotero-scholar-citations/releases/download/v4.3.0/zotero-google-scholar-citation-count-4.3.0.xpi) 15 | 16 | Zotero 6 - [![v3.2.2](https://img.shields.io/badge/Download-v3.2.2-orange?style=for-the-badge)](https://github.com/justinribeiro/zotero-scholar-citations/releases/download/v3.2.2/zotero-google-scholar-citation-count-3.2.2.xpi) 17 | 18 | ## What's New 19 | _v4.3.0_ - Automatically pull citations on item addition (enabled via preference); adds new relative relevance score column (see [issue #28](https://github.com/justinribeiro/zotero-google-scholar-citation-count/issues/28)); plugin now shows small popup when pulling citations. 20 | 21 | ![New Preferences for auto-pulling citation count on add](https://github.com/user-attachments/assets/5eb3bc94-f0e1-42a9-bf3e-6f72f07ed595) 22 | 23 | _v4.2.0_ - Adds ability to set custom Google Scholar endpoint; removes HTML from title search strings; saves column width state per Zotero documentation. 24 | 25 | ![New Preferences for setting custom Google Scholar API endpoint](https://github.com/user-attachments/assets/1e499d4d-6fc5-4c2e-aa23-440df4554fa5) 26 | 27 | _v4.1.0_ - New translations, new preferences panel, and new search options! Matching is hard (see the Citation Counts note), but we can make it better. The preferences panel now let's your tighten or loosen some of the search parameters to Google Scholar for you use case. This is the first pass, more options to come! 28 | 29 | ![New Preferences Panel for Citation Count](https://github.com/user-attachments/assets/4f640d7a-4b3b-42fe-b5ac-bf51dcd7f68d) 30 | 31 | _v4.0.0_ - This release is specifically adding the initial support for the upcoming Zotero 7. The most significant feature addition is that there is now a custom data column available that allows seeing Citation Count. See [the sample video](https://www.youtube.com/watch?v=wgW74lL_tgI) for usage. If you find issues in Zotero 7 beta, please let me know so we can squash some bugs! 32 | 33 | ## A Note About Citation Counts 34 | 35 | A common reported issue in the tracker is "Justin, it doesn't report the correct number of citations". I understand the frustration. Many people offer varying solutions that often fit for very limited cases or papers, but I cannot on the whole edge case them all. However, if you're looking for a definitive number of citations for a given resource, Google Scholar is not the tool for that usage. GS has known issues with this (it'll report wildly in some cases, see https://fediscience.org/@ct_bergstrom/111303567826479298 for one such case). 36 | 37 | This plugin does its best to try to use what Google Scholar returns as a guidepost and nothing more. There isn't much I can do plugin-wise to resolve this without using making it very very opinionated (which I have tried to keep in check, because that has it's own problems for maintenance and viability). 38 | 39 | Feel free to report this issue as you see fit; I will always try to resolve as best I can! 40 | 41 | ## Demo 42 | 43 | Version 4.x: 44 | https://www.youtube.com/watch?v=wgW74lL_tgI 45 | 46 | Version 3.x: 47 | https://user-images.githubusercontent.com/643503/135680344-1887a48f-07e6-424f-aa9a-540092041baa.mp4 48 | 49 | ## Install / Update 50 | Install via `Tools > Add-Ons` within Zotero and use the `Install Add-On from file...` from the settings icon menu as shown in the screenshot below: 51 | 52 | ![image](https://user-images.githubusercontent.com/643503/135676188-7ab92614-9376-4271-9277-7b3a5c2a8768.png) 53 | 54 | Make sure to restart Zotero for the plug-in to take effect. 55 | 56 | ## Using the Plugin 57 | 58 | Right click on an item or collection and select `Update Google Scholar citation count...`, which will then update the item(s) field `extra` with a `GSCC: NNNNNNN`: 59 | 60 | ![image](https://user-images.githubusercontent.com/643503/135185125-060d1951-5b20-40b6-98f0-8783d9846ad3.png) 61 | 62 | ## The Robot Problem 63 | 64 | Google Scholar is a pain in the neck. Google makes no API available, so sometimes you'll get a message asking you to confirm you're not a robot via a recaptcha: 65 | 66 | ![the dreaded recaptcha](https://user-images.githubusercontent.com/643503/135678671-86d15772-c187-4043-9bc1-2f3725e1f0a5.png) 67 | 68 | In this case, complete the recaptcha and close the resulting window and run your citation count update again. 69 | 70 | ## I can't get past the recaptcha! 71 | 72 | Depending on how many items you're trying to update, Google Scholar might temporarily block you from such requests (and the plugin will continue prompting). In this case, restart Zotero (usually resolves issues) or let it cool down (go get cup of coffee, try citation count update again later). 73 | 74 | This is an imperfect science until I do...some other things. :-) 75 | 76 | ## Brief Note From Justin 77 | 78 | If you've stumbling here, you're probably in my Doctorate cohort at [Case Western Reserve University](https://www.zotero.org/groups/4418982/cwru_dbap_2024) and are using Zotero. 79 | 80 | If you're not, the gist is this: there are a _lot_ of versions of this plugin from various folks with specific patches. This is my fork, of a fork, of a fork, that originally was from [Anton Beloglazov](https://beloglazov.info/) back in 2011. 81 | 82 | Regardless of those various patchwork forks, I've decided to do a quick rewrite to clean up and resolve some oddness I had noticed in other variations. I'm basically mixing a lot of old knowledge (hello Firefox Add-On system...[those were the days my old friend](https://www.youtube.com/watch?v=Iu2aOk6b_Gs)) and a lot of new knowledge (ECMAScript and the web platform, I can't quit you). 83 | 84 | 85 | -------------------------------------------------------------------------------- /__tests__/__data__/extraFieldExtractorData.js: -------------------------------------------------------------------------------- 1 | const data = [ 2 | { 3 | string: 'GSCC:00001001', 4 | expectedResult: { 5 | citationCount: 1001, 6 | lastUpdated: '', 7 | relevanceScore: 0, 8 | }, 9 | }, 10 | { 11 | string: 'GSCC: 00001000', 12 | expectedResult: { 13 | citationCount: 1000, 14 | lastUpdated: '', 15 | relevanceScore: 0, 16 | }, 17 | }, 18 | 19 | { 20 | string: 'badstartdata GSCC: 00001001', 21 | expectedResult: { 22 | citationCount: 0, 23 | lastUpdated: '', 24 | relevanceScore: 0, 25 | }, 26 | }, 27 | { 28 | string: 'GSCC: 0000010 2025-01-01T08:00:00.000Z \n', 29 | expectedResult: { 30 | citationCount: 10, 31 | lastUpdated: '1/1/2025, 12:00:00 AM', 32 | relevanceScore: 0, 33 | }, 34 | }, 35 | { 36 | string: 37 | 'GSCC: 0000400 2025-01-01T08:00:00.000Z \nPublisher: SAGE Publications Inc', 38 | expectedResult: { 39 | citationCount: 400, 40 | lastUpdated: '1/1/2025, 12:00:00 AM', 41 | relevanceScore: 0, 42 | }, 43 | }, 44 | { 45 | string: 46 | 'some custom data on top\nGSCC: 0000401 2025-01-01T08:00:00.000Z \nPublisher: SAGE Publications Inc', 47 | expectedResult: { 48 | citationCount: 401, 49 | lastUpdated: '1/1/2025, 12:00:00 AM', 50 | relevanceScore: 0, 51 | }, 52 | }, 53 | { 54 | string: 55 | 'some custom data on top\nGSCC: 0000401 2025-01-01T08:00:00.000Z 2.2\nPublisher: SAGE Publications Inc', 56 | expectedResult: { 57 | citationCount: 401, 58 | lastUpdated: '1/1/2025, 12:00:00 AM', 59 | relevanceScore: 2.2, 60 | }, 61 | }, 62 | { 63 | string: 64 | 'GSCC: 0010401 2025-01-01T08:00:00.000Z 2.4\nPublisher: SAGE Publications Inc', 65 | expectedResult: { 66 | citationCount: 10401, 67 | lastUpdated: '1/1/2025, 12:00:00 AM', 68 | relevanceScore: 2.4, 69 | }, 70 | }, 71 | { 72 | string: 73 | 'GSCC: 0010433 2025-01-01T08:00:00.000Z 2.5 \n', 74 | expectedResult: { 75 | citationCount: 10433, 76 | lastUpdated: '1/1/2025, 12:00:00 AM', 77 | relevanceScore: 2.5, 78 | }, 79 | }, 80 | { 81 | string: 82 | 'GSCC: 0000433 2025-01-01T08:00:00.000Z 1.5', 83 | expectedResult: { 84 | citationCount: 433, 85 | lastUpdated: '1/1/2025, 12:00:00 AM', 86 | relevanceScore: 1.5, 87 | }, 88 | }, 89 | { 90 | string: 91 | 'GSCC:0000433 2025-01-01T08:00:00.000Z 1.5 ', 92 | expectedResult: { 93 | citationCount: 433, 94 | lastUpdated: '1/1/2025, 12:00:00 AM', 95 | relevanceScore: 1.5, 96 | }, 97 | }, 98 | { 99 | string: 100 | 'GSCC:0000433 2025-01-01T08:00:00.000Z ', 101 | expectedResult: { 102 | citationCount: 433, 103 | lastUpdated: '1/1/2025, 12:00:00 AM', 104 | relevanceScore: 0, 105 | }, 106 | }, 107 | ]; 108 | 109 | module.exports = data; 110 | -------------------------------------------------------------------------------- /__tests__/__data__/gsResponseHasCitation.js: -------------------------------------------------------------------------------- 1 | const data = `
Articles
Scholar

Bridging research and practice: models for dissemination and implementation research

RG Tabak, EC Khoong, DA Chambers… - American journal of …, 2012 - Elsevier
Context Theories and frameworks (hereafter called models) enhance dissemination and
implementation (D&I) research by making the spread of evidence-based interventions more
likely. This work organizes and synthesizes these models by (1) developing an inventory of
models used in D&I research;(2) synthesizing this information; and (3) providing guidance
on how to select a model to inform study design and execution. Evidence acquisition This
review began with commonly cited models and model developers and used snowball …
Showing the best result for this search. See all results
`; 2 | 3 | module.exports = { data }; 4 | -------------------------------------------------------------------------------- /__tests__/__data__/gsResponseHasPaperNoCitations.js: -------------------------------------------------------------------------------- 1 | const data = `
Articles
Scholar

Bridging research and practice: models for dissemination and implementation research

RG Tabak, EC Khoong, DA Chambers… - American journal of …, 2012 - Elsevier
Context Theories and frameworks (hereafter called models) enhance dissemination and
implementation (D&I) research by making the spread of evidence-based interventions more
likely. This work organizes and synthesizes these models by (1) developing an inventory of
models used in D&I research;(2) synthesizing this information; and (3) providing guidance
on how to select a model to inform study design and execution. Evidence acquisition This
review began with commonly cited models and model developers and used snowball …
Showing the best result for this search. See all results
`; 2 | 3 | module.exports = { data }; 4 | -------------------------------------------------------------------------------- /__tests__/__data__/gsResponseHasRecaptcha.js: -------------------------------------------------------------------------------- 1 | const data = `
Articles
Scholar

Bridging research and practice: models for dissemination and implementation research

RG Tabak, EC Khoong, DA Chambers… - American journal of …, 2012 - Elsevier
Context Theories and frameworks (hereafter called models) enhance dissemination and
implementation (D&I) research by making the spread of evidence-based interventions more
likely. This work organizes and synthesizes these models by (1) developing an inventory of
models used in D&I research;(2) synthesizing this information; and (3) providing guidance
on how to select a model to inform study design and execution. Evidence acquisition This
review began with commonly cited models and model developers and used snowball …
Showing the best result for this search. See all results
`; 2 | 3 | module.exports = { data }; 4 | -------------------------------------------------------------------------------- /__tests__/__data__/gsResponseNoCitation.js: -------------------------------------------------------------------------------- 1 | const data = `
Articles
Scholar
Did you mean: asdfasdfasdfasdfasdf
Your search - asdfasdfsdfasdfasdf - did not match any articles published between 2010 and 2014.

Suggestions:
Try searching over a larger date range.
Make sure all words are spelled correctly.
Try different keywords.
Try more general keywords.
Try your query on the entire web
`; 2 | 3 | module.exports = { data }; 4 | -------------------------------------------------------------------------------- /__tests__/__data__/helpers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Patch am Item record from Zotero with some of the methods we use to verify 3 | * behavior in tests 4 | * @param {object} data 5 | * @returns {ZoteroGenericItem} 6 | */ 7 | function createItem(data) { 8 | const methods = { 9 | getField: function (field) { 10 | if (field === 'year') { 11 | // this is cheeky but fits the profile of the field 12 | return this.date.substring(this.date.length - 4); 13 | } 14 | 15 | return this[field]; 16 | }, 17 | getCreators: function () { 18 | return this.creators; 19 | }, 20 | setField: function (field, info) { 21 | this[field] = info; 22 | }, 23 | saveTx: function () { 24 | return; 25 | }, 26 | }; 27 | return { ...data, ...methods }; 28 | } 29 | 30 | module.exports = { createItem }; 31 | -------------------------------------------------------------------------------- /__tests__/__data__/zoteroItemsList.js: -------------------------------------------------------------------------------- 1 | const getField = function (key) { 2 | return this[key]; 3 | }; 4 | 5 | const getCreators = function () { 6 | return this.creators; 7 | }; 8 | 9 | const data = [ 10 | { 11 | key: 'LHX8PRC3', 12 | version: 246, 13 | itemType: 'journalArticle', 14 | title: 15 | 'From transactional to transformational leadership: Learning to share the vision', 16 | date: 'December 1, 1990', 17 | language: 'en', 18 | shortTitle: 'From transactional to transformational leadership', 19 | libraryCatalog: 'ScienceDirect', 20 | url: 'https://www.sciencedirect.com/science/article/pii/009026169090061S', 21 | accessDate: '2021-09-22T20:55:33Z', 22 | extra: '', 23 | volume: '18', 24 | pages: '19-31', 25 | publicationTitle: 'Organizational Dynamics', 26 | DOI: '10.1016/0090-2616(90)90061-S', 27 | issue: '3', 28 | journalAbbreviation: 'Organizational Dynamics', 29 | ISSN: '0090-2616', 30 | creators: [ 31 | { 32 | firstName: 'Bernard M.', 33 | lastName: 'Bass', 34 | creatorType: 'author', 35 | }, 36 | ], 37 | tags: [], 38 | collections: ['I39PBJTI'], 39 | relations: {}, 40 | dateAdded: '2021-09-22T20:55:33Z', 41 | dateModified: '2021-09-29T00:42:27Z', 42 | getField, 43 | getCreators, 44 | }, 45 | ]; 46 | 47 | module.exports = data; 48 | -------------------------------------------------------------------------------- /__tests__/__data__/zoteroItemsList10set.js: -------------------------------------------------------------------------------- 1 | const getField = function (key) { 2 | return this[key]; 3 | }; 4 | 5 | const getCreators = function () { 6 | return this.creators; 7 | }; 8 | 9 | const data = [ 10 | { 11 | key: 'LHX8PRC3', 12 | version: 246, 13 | itemType: 'journalArticle', 14 | title: 15 | 'From transactional to transformational leadership: Learning to share the vision', 16 | date: 'December 1, 1990', 17 | language: 'en', 18 | shortTitle: 'From transactional to transformational leadership', 19 | libraryCatalog: 'ScienceDirect', 20 | url: 'https://www.sciencedirect.com/science/article/pii/009026169090061S', 21 | accessDate: '2021-09-22T20:55:33Z', 22 | extra: '', 23 | volume: '18', 24 | pages: '19-31', 25 | publicationTitle: 'Organizational Dynamics', 26 | DOI: '10.1016/0090-2616(90)90061-S', 27 | issue: '3', 28 | journalAbbreviation: 'Organizational Dynamics', 29 | ISSN: '0090-2616', 30 | creators: [ 31 | { 32 | firstName: 'Bernard M.', 33 | lastName: 'Bass', 34 | creatorType: 'author', 35 | }, 36 | ], 37 | tags: [], 38 | collections: ['I39PBJTI'], 39 | relations: {}, 40 | dateAdded: '2021-09-22T20:55:33Z', 41 | dateModified: '2021-09-29T00:42:27Z', 42 | getField, 43 | getCreators, 44 | }, 45 | { 46 | key: 'KAZLLGBU', 47 | version: 247, 48 | itemType: 'journalArticle', 49 | title: 50 | 'Predicting unit performance by assessing transformational and transactional leadership.', 51 | date: '2003', 52 | language: 'en', 53 | libraryCatalog: 'DOI.org (Crossref)', 54 | url: 'http://doi.apa.org/getdoi.cfm?doi=10.1037/0021-9010.88.2.207', 55 | accessDate: '2021-09-24T00:41:46Z', 56 | extra: 'GSCC: 0005185', 57 | volume: '88', 58 | pages: '207-218', 59 | publicationTitle: 'Journal of Applied Psychology', 60 | DOI: '10.1037/0021-9010.88.2.207', 61 | issue: '2', 62 | journalAbbreviation: 'Journal of Applied Psychology', 63 | ISSN: '1939-1854, 0021-9010', 64 | creators: [ 65 | { 66 | firstName: 'Bernard M.', 67 | lastName: 'Bass', 68 | creatorType: 'author', 69 | }, 70 | { 71 | firstName: 'Bruce J.', 72 | lastName: 'Avolio', 73 | creatorType: 'author', 74 | }, 75 | { 76 | firstName: 'Dong I.', 77 | lastName: 'Jung', 78 | creatorType: 'author', 79 | }, 80 | { 81 | firstName: 'Yair', 82 | lastName: 'Berson', 83 | creatorType: 'author', 84 | }, 85 | ], 86 | tags: [], 87 | collections: ['I39PBJTI'], 88 | relations: {}, 89 | dateAdded: '2021-09-24T00:41:46Z', 90 | dateModified: '2021-09-29T00:41:58Z', 91 | getField, 92 | getCreators, 93 | }, 94 | { 95 | key: 'YTXC4I3K', 96 | version: 247, 97 | itemType: 'journalArticle', 98 | title: 99 | 'Personality and Transformational and Transactional Leadership: A Meta-Analysis', 100 | date: '2004', 101 | language: 'en', 102 | libraryCatalog: 'Zotero', 103 | extra: '', 104 | volume: '89', 105 | pages: '901-910', 106 | publicationTitle: 'Journal of Applied Psychology', 107 | DOI: '10.1037/0021-9010.89.5.901', 108 | issue: '5', 109 | ISSN: '0021-9010', 110 | creators: [ 111 | { 112 | firstName: 'Joyce E', 113 | lastName: 'Bono', 114 | creatorType: 'author', 115 | }, 116 | { 117 | firstName: 'Timothy A', 118 | lastName: 'Judge', 119 | creatorType: 'author', 120 | }, 121 | ], 122 | tags: [], 123 | collections: ['I39PBJTI'], 124 | relations: {}, 125 | dateAdded: '2021-09-24T00:42:25Z', 126 | dateModified: '2021-09-29T00:42:02Z', 127 | getField, 128 | getCreators, 129 | }, 130 | { 131 | key: 'VMNJLHPI', 132 | version: 247, 133 | itemType: 'journalArticle', 134 | title: 135 | 'Situational, Transformational, and Transactional Leadership and Leadership Development', 136 | abstractNote: 137 | 'In order to advance our knowledge of leadership, it is necessary to understand where the study of leadership has been. McCleskey (2014) argued that the study of leadership spans more than 100 years. This manuscript describes three seminal leadership theories and their development. Analysis of a sampling of recent articles in each theory is included. The manuscript also discusses the concept of leadership development in light of those three seminal theories and offers suggestions for moving forward both the academic study of leadership and the practical application of research findings on the field.', 138 | date: '2014', 139 | language: 'en', 140 | libraryCatalog: 'Zotero', 141 | extra: '', 142 | volume: '5', 143 | pages: '14', 144 | publicationTitle: 'Journal of Business Studies Quarterly', 145 | issue: '4', 146 | ISSN: '2152-1034', 147 | creators: [ 148 | { 149 | firstName: 'Jim Allen', 150 | lastName: 'McCleskey', 151 | creatorType: 'author', 152 | }, 153 | ], 154 | tags: [], 155 | collections: ['I39PBJTI'], 156 | relations: {}, 157 | dateAdded: '2021-09-24T00:42:01Z', 158 | dateModified: '2021-09-29T00:42:05Z', 159 | getField, 160 | getCreators, 161 | }, 162 | { 163 | key: '3ZBCBKUH', 164 | version: 246, 165 | itemType: 'journalArticle', 166 | title: 167 | 'Potential Biases in Leadership Measures: How Prototypes, Leniency, and General Satisfaction Relate to Ratings and Rankings of Transformational and Transactional Leadership Constructs', 168 | abstractNote: 169 | "Eighty-seven respondents completed either a graphic rating or a forced ranking questionnaire describing their immediate superior. Five leadership scales were embedded in each questionnaire. Three represented transformational leadership constructs (charismatic leadership, individualized consideration, and intellectual stimulation); two reflected transactional leadership constructs (contingent reward and management-by-exception). Appended to each questionnaire were five additional scales. The items constituting these scales measured two outcomes-satisfaction with the leader and effectiveness of the leader. The remaining scales measured each participant's leadership prototype, the participant's tendency to be lenient in his/her ratings, and a general measure of satisfaction. As expected, the intercorrelations among the factor scores representing the transformational and transactional leadership constructs were reduced substantially by using the forced rankings as compared with the graphic ratings. Also, the magnitude of the relationships among leadership and outcome factor scores was reduced, on average, when using the forced rankings. Prototypicality factor scores were more highly correlated with factor scores reflecting transformational than were factor scores portraying transactional leadership. The tendency of participants to be more or less lenient in their ratings or rankings and their general level of satisfaction were of little or no consequence to the intercorrelations among the leadership and outcome factor scales.", 170 | date: 'September 1, 1989', 171 | url: 'https://doi.org/10.1177/001316448904900302', 172 | accessDate: '2021-09-22', 173 | extra: 'GSCC: 0000505 \nPublisher: SAGE Publications Inc', 174 | volume: '49', 175 | pages: '509-527', 176 | publicationTitle: 'Educational and Psychological Measurement', 177 | DOI: '10.1177/001316448904900302', 178 | issue: '3', 179 | journalAbbreviation: 'Educational and Psychological Measurement', 180 | ISSN: '0013-1644', 181 | creators: [ 182 | { 183 | firstName: 'Bernard M.', 184 | lastName: 'Bass', 185 | creatorType: 'author', 186 | }, 187 | { 188 | firstName: 'Bruce J.', 189 | lastName: 'Avolio', 190 | creatorType: 'author', 191 | }, 192 | ], 193 | tags: [], 194 | collections: ['I39PBJTI'], 195 | relations: {}, 196 | dateAdded: '2021-09-22T20:25:22Z', 197 | dateModified: '2021-09-29T00:41:26Z', 198 | getField, 199 | getCreators, 200 | }, 201 | ]; 202 | 203 | module.exports = data; 204 | -------------------------------------------------------------------------------- /__tests__/__data__/zoteroItemsListSingleItemWithCount.js: -------------------------------------------------------------------------------- 1 | const helpers = require('./helpers.js'); 2 | 3 | const data = helpers.createItem({ 4 | key: '3ZBCBKUH', 5 | version: 246, 6 | itemType: 'journalArticle', 7 | title: 8 | 'Potential Biases in Leadership Measures: How Prototypes, Leniency, and General Satisfaction Relate to Ratings and Rankings of Transformational and Transactional Leadership Constructs', 9 | abstractNote: 10 | "Eighty-seven respondents completed either a graphic rating or a forced ranking questionnaire describing their immediate superior. Five leadership scales were embedded in each questionnaire. Three represented transformational leadership constructs (charismatic leadership, individualized consideration, and intellectual stimulation); two reflected transactional leadership constructs (contingent reward and management-by-exception). Appended to each questionnaire were five additional scales. The items constituting these scales measured two outcomes-satisfaction with the leader and effectiveness of the leader. The remaining scales measured each participant's leadership prototype, the participant's tendency to be lenient in his/her ratings, and a general measure of satisfaction. As expected, the intercorrelations among the factor scores representing the transformational and transactional leadership constructs were reduced substantially by using the forced rankings as compared with the graphic ratings. Also, the magnitude of the relationships among leadership and outcome factor scores was reduced, on average, when using the forced rankings. Prototypicality factor scores were more highly correlated with factor scores reflecting transformational than were factor scores portraying transactional leadership. The tendency of participants to be more or less lenient in their ratings or rankings and their general level of satisfaction were of little or no consequence to the intercorrelations among the leadership and outcome factor scales.", 11 | date: 'September 1, 1989', 12 | url: 'https://doi.org/10.1177/001316448904900302', 13 | accessDate: '2021-09-22', 14 | extra: 15 | 'GSCC: 0000505 2025-01-01T08:00:00.000Z 0.54 \nPublisher: SAGE Publications Inc', 16 | volume: '49', 17 | pages: '509-527', 18 | publicationTitle: 'Educational and Psychological Measurement', 19 | DOI: '10.1177/001316448904900302', 20 | issue: '3', 21 | journalAbbreviation: 'Educational and Psychological Measurement', 22 | ISSN: '0013-1644', 23 | creators: [ 24 | { 25 | firstName: 'Bernard M.', 26 | lastName: 'Bass', 27 | creatorType: 'author', 28 | }, 29 | { 30 | firstName: 'Bruce J.', 31 | lastName: 'Avolio', 32 | creatorType: 'author', 33 | }, 34 | ], 35 | tags: [], 36 | collections: ['I39PBJTI'], 37 | relations: {}, 38 | dateAdded: '2021-09-22T20:25:22Z', 39 | dateModified: '2021-09-29T00:41:26Z', 40 | }); 41 | 42 | module.exports = { data }; 43 | -------------------------------------------------------------------------------- /__tests__/__data__/zoteroItemsListSingleItemWithCountLegacyFormat.js: -------------------------------------------------------------------------------- 1 | const helpers = require('./helpers.js'); 2 | 3 | const data = helpers.createItem({ 4 | key: '3ZBCBKUH', 5 | version: 246, 6 | itemType: 'journalArticle', 7 | title: 8 | 'Potential Biases in Leadership Measures: How Prototypes, Leniency, and General Satisfaction Relate to Ratings and Rankings of Transformational and Transactional Leadership Constructs', 9 | abstractNote: 10 | "Eighty-seven respondents completed either a graphic rating or a forced ranking questionnaire describing their immediate superior. Five leadership scales were embedded in each questionnaire. Three represented transformational leadership constructs (charismatic leadership, individualized consideration, and intellectual stimulation); two reflected transactional leadership constructs (contingent reward and management-by-exception). Appended to each questionnaire were five additional scales. The items constituting these scales measured two outcomes-satisfaction with the leader and effectiveness of the leader. The remaining scales measured each participant's leadership prototype, the participant's tendency to be lenient in his/her ratings, and a general measure of satisfaction. As expected, the intercorrelations among the factor scores representing the transformational and transactional leadership constructs were reduced substantially by using the forced rankings as compared with the graphic ratings. Also, the magnitude of the relationships among leadership and outcome factor scores was reduced, on average, when using the forced rankings. Prototypicality factor scores were more highly correlated with factor scores reflecting transformational than were factor scores portraying transactional leadership. The tendency of participants to be more or less lenient in their ratings or rankings and their general level of satisfaction were of little or no consequence to the intercorrelations among the leadership and outcome factor scales.", 11 | date: 'September 1, 1989', 12 | url: 'https://doi.org/10.1177/001316448904900302', 13 | accessDate: '2021-09-22', 14 | extra: 'GSCC: 0000505 \nPublisher: SAGE Publications Inc', 15 | volume: '49', 16 | pages: '509-527', 17 | publicationTitle: 'Educational and Psychological Measurement', 18 | DOI: '10.1177/001316448904900302', 19 | issue: '3', 20 | journalAbbreviation: 'Educational and Psychological Measurement', 21 | ISSN: '0013-1644', 22 | creators: [ 23 | { 24 | firstName: 'Bernard M.', 25 | lastName: 'Bass', 26 | creatorType: 'author', 27 | }, 28 | { 29 | firstName: 'Bruce J.', 30 | lastName: 'Avolio', 31 | creatorType: 'author', 32 | }, 33 | ], 34 | tags: [], 35 | collections: ['I39PBJTI'], 36 | relations: {}, 37 | dateAdded: '2021-09-22T20:25:22Z', 38 | dateModified: '2021-09-29T00:41:26Z', 39 | }); 40 | 41 | module.exports = { data }; 42 | -------------------------------------------------------------------------------- /__tests__/__data__/zoteroItemsListSingleItemWithHtmlTitle.js: -------------------------------------------------------------------------------- 1 | const helpers = require('./helpers.js'); 2 | 3 | const data = helpers.createItem({ 4 | key: '7HLUVS5G', 5 | version: 14873, 6 | itemType: 'journalArticle', 7 | title: 8 | '(Y0.25Yb0.25Er0.25Lu0.25)2(Zr0.5Hf0.5)2O7: a defective fluorite structured high entropy ceramic with low thermal conductivity and close thermal expansion coefficient to Al2O3', 9 | creators: [ 10 | { creatorType: 'author', firstName: 'Zifan', lastName: 'Zhao' }, 11 | { creatorType: 'author', firstName: 'Heng', lastName: 'Chen' }, 12 | { creatorType: 'author', firstName: 'Huimin', lastName: 'Xiang' }, 13 | { creatorType: 'author', firstName: 'Fu-Zhi', lastName: 'Dai' }, 14 | { creatorType: 'author', firstName: 'Xiaohui', lastName: 'Wang' }, 15 | { creatorType: 'author', firstName: 'Wei', lastName: 'Xu' }, 16 | { creatorType: 'author', firstName: 'Kuang', lastName: 'Sun' }, 17 | { creatorType: 'author', firstName: 'Zhijian', lastName: 'Peng' }, 18 | { creatorType: 'author', firstName: 'Yanchun', lastName: 'Zhou' }, 19 | ], 20 | abstractNote: '', 21 | publicationTitle: 'Journal of Materials Science & Technology', 22 | volume: '39', 23 | issue: '', 24 | pages: '167-172', 25 | date: '02/2020', 26 | series: '', 27 | seriesTitle: '', 28 | seriesText: '', 29 | journalAbbreviation: 'Journal of Materials Science & Technology', 30 | language: 'en', 31 | DOI: '10.1016/j.jmst.2019.08.018', 32 | ISSN: '10050302', 33 | shortTitle: '(Y0.25Yb0.25Er0.25Lu0.25)2(Zr0.5Hf0.5)2O7', 34 | url: 'https://linkinghub.elsevier.com/retrieve/pii/S1005030219303329', 35 | accessDate: '2024-11-21T17:07:44Z', 36 | archive: '', 37 | archiveLocation: '', 38 | libraryCatalog: 'DOI.org (Crossref)', 39 | callNumber: '', 40 | rights: '', 41 | extra: 'GSCC: 0000071', 42 | tags: [], 43 | collections: ['27YH8FR3'], 44 | relations: {}, 45 | dateAdded: '2024-11-21T17:07:44Z', 46 | dateModified: '2024-12-11T17:57:43Z', 47 | }); 48 | 49 | module.exports = { data }; 50 | -------------------------------------------------------------------------------- /__tests__/__data__/zoteroItemsListSingleItemWithNoCount.js: -------------------------------------------------------------------------------- 1 | const helpers = require('./helpers.js'); 2 | 3 | const data = helpers.createItem({ 4 | key: '3ZBCBKUH', 5 | version: 246, 6 | itemType: 'journalArticle', 7 | title: 8 | 'Potential Biases in Leadership Measures: How Prototypes, Leniency, and General Satisfaction Relate to Ratings and Rankings of Transformational and Transactional Leadership Constructs', 9 | abstractNote: 10 | "Eighty-seven respondents completed either a graphic rating or a forced ranking questionnaire describing their immediate superior. Five leadership scales were embedded in each questionnaire. Three represented transformational leadership constructs (charismatic leadership, individualized consideration, and intellectual stimulation); two reflected transactional leadership constructs (contingent reward and management-by-exception). Appended to each questionnaire were five additional scales. The items constituting these scales measured two outcomes-satisfaction with the leader and effectiveness of the leader. The remaining scales measured each participant's leadership prototype, the participant's tendency to be lenient in his/her ratings, and a general measure of satisfaction. As expected, the intercorrelations among the factor scores representing the transformational and transactional leadership constructs were reduced substantially by using the forced rankings as compared with the graphic ratings. Also, the magnitude of the relationships among leadership and outcome factor scores was reduced, on average, when using the forced rankings. Prototypicality factor scores were more highly correlated with factor scores reflecting transformational than were factor scores portraying transactional leadership. The tendency of participants to be more or less lenient in their ratings or rankings and their general level of satisfaction were of little or no consequence to the intercorrelations among the leadership and outcome factor scales.", 11 | date: 'September 1, 1989', 12 | url: 'https://doi.org/10.1177/001316448904900302', 13 | accessDate: '2021-09-22', 14 | extra: '', 15 | volume: '49', 16 | pages: '509-527', 17 | publicationTitle: 'Educational and Psychological Measurement', 18 | DOI: '10.1177/001316448904900302', 19 | issue: '3', 20 | journalAbbreviation: 'Educational and Psychological Measurement', 21 | ISSN: '0013-1644', 22 | creators: [ 23 | { 24 | firstName: 'Bernard M.', 25 | lastName: 'Bass', 26 | creatorType: 'author', 27 | }, 28 | { 29 | firstName: 'Bruce J.', 30 | lastName: 'Avolio', 31 | creatorType: 'author', 32 | }, 33 | ], 34 | tags: [], 35 | collections: ['I39PBJTI'], 36 | relations: {}, 37 | dateAdded: '2021-09-22T20:25:22Z', 38 | dateModified: '2021-09-29T00:41:26Z', 39 | }); 40 | 41 | module.exports = { data }; 42 | -------------------------------------------------------------------------------- /__tests__/__data__/zoteroItemsListSingleItemWithNoCreators.js: -------------------------------------------------------------------------------- 1 | const helpers = require('./helpers.js'); 2 | 3 | const data = helpers.createItem({ 4 | key: '3ZBCBKUH', 5 | version: 246, 6 | itemType: 'journalArticle', 7 | title: 8 | 'Potential Biases in Leadership Measures: How Prototypes, Leniency, and General Satisfaction Relate to Ratings and Rankings of Transformational and Transactional Leadership Constructs', 9 | abstractNote: 10 | "Eighty-seven respondents completed either a graphic rating or a forced ranking questionnaire describing their immediate superior. Five leadership scales were embedded in each questionnaire. Three represented transformational leadership constructs (charismatic leadership, individualized consideration, and intellectual stimulation); two reflected transactional leadership constructs (contingent reward and management-by-exception). Appended to each questionnaire were five additional scales. The items constituting these scales measured two outcomes-satisfaction with the leader and effectiveness of the leader. The remaining scales measured each participant's leadership prototype, the participant's tendency to be lenient in his/her ratings, and a general measure of satisfaction. As expected, the intercorrelations among the factor scores representing the transformational and transactional leadership constructs were reduced substantially by using the forced rankings as compared with the graphic ratings. Also, the magnitude of the relationships among leadership and outcome factor scores was reduced, on average, when using the forced rankings. Prototypicality factor scores were more highly correlated with factor scores reflecting transformational than were factor scores portraying transactional leadership. The tendency of participants to be more or less lenient in their ratings or rankings and their general level of satisfaction were of little or no consequence to the intercorrelations among the leadership and outcome factor scales.", 11 | date: 'September 1, 1989', 12 | url: 'https://doi.org/10.1177/001316448904900302', 13 | accessDate: '2021-09-22', 14 | extra: '', 15 | volume: '49', 16 | pages: '509-527', 17 | publicationTitle: 'Educational and Psychological Measurement', 18 | DOI: '10.1177/001316448904900302', 19 | issue: '3', 20 | journalAbbreviation: 'Educational and Psychological Measurement', 21 | ISSN: '0013-1644', 22 | creators: [], 23 | tags: [], 24 | collections: ['I39PBJTI'], 25 | relations: {}, 26 | dateAdded: '2021-09-22T20:25:22Z', 27 | dateModified: '2021-09-29T00:41:26Z', 28 | }); 29 | 30 | module.exports = { data }; 31 | -------------------------------------------------------------------------------- /__tests__/__data__/zoteroItemsListSingleItemWithNoTitle.js: -------------------------------------------------------------------------------- 1 | const helpers = require('./helpers.js'); 2 | 3 | const data = helpers.createItem({ 4 | key: '3ZBCBKUH', 5 | version: 246, 6 | itemType: 'journalArticle', 7 | title: '', 8 | abstractNote: 9 | "Eighty-seven respondents completed either a graphic rating or a forced ranking questionnaire describing their immediate superior. Five leadership scales were embedded in each questionnaire. Three represented transformational leadership constructs (charismatic leadership, individualized consideration, and intellectual stimulation); two reflected transactional leadership constructs (contingent reward and management-by-exception). Appended to each questionnaire were five additional scales. The items constituting these scales measured two outcomes-satisfaction with the leader and effectiveness of the leader. The remaining scales measured each participant's leadership prototype, the participant's tendency to be lenient in his/her ratings, and a general measure of satisfaction. As expected, the intercorrelations among the factor scores representing the transformational and transactional leadership constructs were reduced substantially by using the forced rankings as compared with the graphic ratings. Also, the magnitude of the relationships among leadership and outcome factor scores was reduced, on average, when using the forced rankings. Prototypicality factor scores were more highly correlated with factor scores reflecting transformational than were factor scores portraying transactional leadership. The tendency of participants to be more or less lenient in their ratings or rankings and their general level of satisfaction were of little or no consequence to the intercorrelations among the leadership and outcome factor scales.", 10 | date: 'September 1, 1989', 11 | url: 'https://doi.org/10.1177/001316448904900302', 12 | accessDate: '2021-09-22', 13 | extra: '', 14 | volume: '49', 15 | pages: '509-527', 16 | publicationTitle: 'Educational and Psychological Measurement', 17 | DOI: '10.1177/001316448904900302', 18 | issue: '3', 19 | journalAbbreviation: 'Educational and Psychological Measurement', 20 | ISSN: '0013-1644', 21 | creators: [ 22 | { 23 | firstName: 'Bernard M.', 24 | lastName: 'Bass', 25 | creatorType: 'author', 26 | }, 27 | { 28 | firstName: 'Bruce J.', 29 | lastName: 'Avolio', 30 | creatorType: 'author', 31 | }, 32 | ], 33 | tags: [], 34 | collections: ['I39PBJTI'], 35 | relations: {}, 36 | dateAdded: '2021-09-22T20:25:22Z', 37 | dateModified: '2021-09-29T00:41:26Z', 38 | }); 39 | 40 | module.exports = { data }; 41 | -------------------------------------------------------------------------------- /__tests__/__setup__/env.js: -------------------------------------------------------------------------------- 1 | const Environment = require('jest-environment-jsdom').default; 2 | const { JSDOM } = require('jsdom'); 3 | const dom = new JSDOM(); 4 | 5 | module.exports = class CustomTestEnvironment extends Environment { 6 | async setup() { 7 | await super.setup(); 8 | this.global.TextEncoder = TextEncoder; 9 | this.global.TextDecoder = TextDecoder; 10 | this.global.Response = Response; 11 | this.global.Request = Request; 12 | this.global.document = dom.window.document; 13 | this.global.window = dom.window; 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /__tests__/__setup__/setupJest.js: -------------------------------------------------------------------------------- 1 | jest.setTimeout(60000); 2 | 3 | global.Zotero = { 4 | Debug: { 5 | // eslint-disable-next-line no-unused-vars 6 | log: (message, level, maxDepth, stack) => { 7 | return message; 8 | }, 9 | }, 10 | openInViewer: (targetUrl) => { 11 | if (!targetUrl) { 12 | throw new Error('missing params'); 13 | } 14 | Zotero.viewerOpen = true; 15 | return; 16 | }, 17 | launchURL: (targetUrl) => { 18 | if (!targetUrl) { 19 | throw new Error('missing params'); 20 | } 21 | Zotero.viewerOpen = true; 22 | return; 23 | }, 24 | ScholarCitations: () => { 25 | return {}; 26 | }, 27 | getMainWindow: () => { 28 | return global.window; 29 | }, 30 | viewerOpen: false, 31 | Prefs: { 32 | get: (a, b) => { 33 | return b; 34 | }, 35 | }, 36 | ItemTreeManager: { 37 | registerColumns: () => {}, 38 | unregisterColumns: () => {}, 39 | }, 40 | ProgressWindow: jest.fn().mockImplementation(() => ({ 41 | changeHeadline: jest.fn(), 42 | addDescription: jest.fn(), 43 | show: jest.fn(), 44 | startCloseTimer: jest.fn(), 45 | })), 46 | Notifier: { 47 | registerObserver: jest.fn(), 48 | }, 49 | }; 50 | 51 | global.document.l10n = { 52 | formatValue: (a) => { 53 | return a; 54 | }, 55 | }; 56 | 57 | global.window.MozXULElement = { 58 | insertFTLIfNeeded: () => {}, 59 | }; 60 | 61 | // cheeky, but we're not testing Google Scholar here 62 | global.XMLHttpRequest = jest.fn().mockImplementation(() => { 63 | return { 64 | readyState: 4, 65 | status: 200, 66 | responseText: JSON.stringify({ message: 'Justin mocking response' }), 67 | open: jest.fn(), 68 | send: jest.fn().mockImplementation(function () { 69 | this.onreadystatechange(); 70 | }), 71 | setRequestHeader: jest.fn(), 72 | onreadystatechange: jest.fn(), 73 | }; 74 | }); 75 | 76 | global.gBrowser = { 77 | loadOneTab: (targetUrl = '', obj = {}) => { 78 | if (targetUrl === '' || Object.keys(obj).length === 0) { 79 | throw new Error('missing params'); 80 | } 81 | Zotero.viewerOpen = true; 82 | return; 83 | }, 84 | }; 85 | 86 | global.alert = jest.fn(); 87 | 88 | global.Services = { 89 | prefs: { 90 | getBranch: function () { 91 | return { 92 | getPrefType: function (val) { 93 | return 'number'; 94 | }, 95 | setBoolPref: function () { 96 | return true; 97 | }, 98 | setCharPref: function () { 99 | return true; 100 | }, 101 | setIntPref: function () { 102 | return true; 103 | }, 104 | getBoolPref: function () {}, 105 | getCharPref: function () {}, 106 | getIntPref: function () {}, 107 | clearUserPref: function () { 108 | return true; 109 | }, 110 | PREF_BOOL: 'boolean', 111 | PREF_STRING: 'string', 112 | PREF_INT: 'number', 113 | }; 114 | }, 115 | }, 116 | }; 117 | 118 | global.Components = { 119 | interfaces: { 120 | nsIWindowWatcher: '', 121 | }, 122 | utils: { 123 | import: function (val) { 124 | return true; 125 | }, 126 | }, 127 | classes: { 128 | '@mozilla.org/embedcomp/window-watcher;1': { 129 | getService: function () { 130 | return { 131 | openWindow: function () { 132 | return { 133 | closed: false, 134 | }; 135 | }, 136 | }; 137 | }, 138 | }, 139 | }, 140 | }; 141 | -------------------------------------------------------------------------------- /__tests__/gsCitationCount.test.js: -------------------------------------------------------------------------------- 1 | const base = require('../src/gscc.js'); 2 | const hasCitation = require('./__data__/gsResponseHasCitation.js'); 3 | const hasCitation2023Version = require('./__data__/gsResponseHasCitationJuly2023GSUpdate.js'); 4 | const hasCitation2023VersionAltReturn = require('./__data__/gsResponseHasCitationJuly2023GSUpdateAltSearchCase.js'); 5 | const noCitation = require('./__data__/gsResponseNoCitation.js'); 6 | const hasPaperNoCitations = require('./__data__/gsResponseHasPaperNoCitations.js'); 7 | const hasRecaptcha = require('./__data__/gsResponseHasRecaptcha.js'); 8 | const singleItemWithCount = require('./__data__/zoteroItemsListSingleItemWithCount.js'); 9 | const singleItemWithCountLegacyFormat = require('./__data__/zoteroItemsListSingleItemWithCountLegacyFormat.js'); 10 | const singleItemNoCount = require('./__data__/zoteroItemsListSingleItemWithNoCount.js'); 11 | const singleItemNoTitle = require('./__data__/zoteroItemsListSingleItemWithNoTitle.js'); 12 | const singleItemHtmlTitle = require('./__data__/zoteroItemsListSingleItemWithHtmlTitle.js'); 13 | const singleItemNoCreators = require('./__data__/zoteroItemsListSingleItemWithNoCreators.js'); 14 | const itemsList = require('./__data__/zoteroItemsList.js'); 15 | const extraFieldTester = require('./__data__/extraFieldExtractorData.js'); 16 | 17 | window.alert = jest.fn(); 18 | jest.useRealTimers(); 19 | 20 | describe('Verify $__gscc.app sanity', () => { 21 | beforeEach(() => { 22 | jest.clearAllMocks(); 23 | jest.useFakeTimers('modern'); 24 | jest.setSystemTime(new Date(2024, 12, 1)); 25 | }); 26 | 27 | it('init() should set app', () => { 28 | const id = 'gscc'; 29 | const version = '4.0.0'; 30 | const rootURI = 'justinribeiro.com'; 31 | base.$__gscc.app.init({ id, version, rootURI }); 32 | 33 | expect(base.$__gscc.app.__initialized).toBe(true); 34 | }); 35 | 36 | it('getCiteCount() should return number', () => { 37 | const test = base.$__gscc.app.getCiteCount(hasCitation.data); 38 | expect(test).toBe(1028); 39 | }); 40 | 41 | it('getCiteCount() should return number from July 2023 GS UI Update', () => { 42 | const test = base.$__gscc.app.getCiteCount(hasCitation2023Version.data); 43 | expect(test).toBe(2468); 44 | }); 45 | 46 | it('getCiteCount() should return number from July 2023 GS UI Update - Alt Case!', () => { 47 | const test = base.$__gscc.app.getCiteCount( 48 | hasCitation2023VersionAltReturn.data, 49 | ); 50 | expect(test).toBe(2468); 51 | }); 52 | 53 | it('getCiteCount() should return -1, no data', () => { 54 | const test = base.$__gscc.app.getCiteCount(noCitation.data); 55 | expect(test).toBe(-1); 56 | }); 57 | 58 | it('getCiteCount() should return 0, no count', () => { 59 | const test = base.$__gscc.app.getCiteCount(hasPaperNoCitations.data); 60 | expect(test).toBe(0); 61 | }); 62 | 63 | it('buildcitecountstring() string + count', () => { 64 | const count = base.$__gscc.app.getCiteCount(hasCitation.data); 65 | const test = base.$__gscc.app.buildCiteCountString(count); 66 | expect(test).toEqual('GSCC: 0001028 2025-01-01T08:00:00.000Z 0'); 67 | }); 68 | 69 | it('buildcitecountstring() string + no data', () => { 70 | const count = base.$__gscc.app.getCiteCount(noCitation.data); 71 | const test = base.$__gscc.app.buildCiteCountString(count); 72 | expect(test).toEqual('GSCC: NoCitationData 2025-01-01T08:00:00.000Z 0'); 73 | }); 74 | 75 | it('generateItemUrl() should output string', async () => { 76 | const string = await base.$__gscc.app.generateItemUrl( 77 | singleItemNoCount.data, 78 | ); 79 | expect(string).toEqual( 80 | 'https://scholar.google.com/scholar?hl=en&q=%22Potential%20Biases%20in%20Leadership%20Measures:%20How%20Prototypes,%20Leniency,%20and%20General%20Satisfaction%20Relate%20to%20Ratings%20and%20Rankings%20of%20Transformational%20and%20Transactional%20Leadership%20Constructs%22&as_epq=&as_occt=title&num=1&as_sauthors=Bass+Avolio', 81 | ); 82 | }); 83 | 84 | it('generateItemUrl() should handle HTML in title', async () => { 85 | const string = await base.$__gscc.app.generateItemUrl( 86 | singleItemHtmlTitle.data, 87 | ); 88 | expect(string).toEqual( 89 | 'https://scholar.google.com/scholar?hl=en&q=%22(Y0.25Yb0.25Er0.25Lu0.25)2(Zr0.5Hf0.5)2O7:%20a%20defective%20fluorite%20structured%20high%20entropy%20ceramic%20with%20low%20thermal%20conductivity%20and%20close%20thermal%20expansion%20coefficient%20to%20Al2O3%22&as_epq=&as_occt=title&num=1&as_sauthors=Zhao+Chen+Xiang+Dai+Wang', 90 | ); 91 | }); 92 | 93 | it('updateItem() should change extra field when legacy data present', () => { 94 | const item = singleItemWithCountLegacyFormat.data; 95 | const extra = jest.spyOn(item, 'setField'); 96 | const tx = jest.spyOn(item, 'saveTx'); 97 | base.$__gscc.app.updateItem(item, 400); 98 | expect(extra).toHaveBeenCalled(); 99 | expect(tx).toHaveBeenCalled(); 100 | expect(item.getField('extra')).toEqual( 101 | 'GSCC: 0000400 2025-01-01T08:00:00.000Z 0.22 \nPublisher: SAGE Publications Inc', 102 | ); 103 | }); 104 | 105 | it('updateItem() should change extra field when data present', () => { 106 | const item = singleItemWithCount.data; 107 | const extra = jest.spyOn(item, 'setField'); 108 | const tx = jest.spyOn(item, 'saveTx'); 109 | base.$__gscc.app.updateItem(item, 1000); 110 | expect(extra).toHaveBeenCalled(); 111 | expect(tx).toHaveBeenCalled(); 112 | expect(item.getField('extra')).toEqual( 113 | 'GSCC: 0001000 2025-01-01T08:00:00.000Z 0.54 \nPublisher: SAGE Publications Inc', 114 | ); 115 | }); 116 | 117 | it('updateItem() should change extra field when no data present', () => { 118 | const item = { ...singleItemNoCount.data }; 119 | const extra = jest.spyOn(item, 'setField'); 120 | const tx = jest.spyOn(item, 'saveTx'); 121 | base.$__gscc.app.updateItem(item, 10); 122 | expect(extra).toHaveBeenCalled(); 123 | expect(tx).toHaveBeenCalled(); 124 | expect(item.getField('extra')).toEqual( 125 | 'GSCC: 0000010 2025-01-01T08:00:00.000Z 0.01 \n', 126 | ); 127 | }); 128 | 129 | it('hasRequiredFields() should return true with sane data', () => { 130 | const item = { ...singleItemNoCount.data }; 131 | const test = base.$__gscc.app.hasRequiredFields(item); 132 | expect(test).toBe(true); 133 | }); 134 | 135 | it('hasRequiredFields() should return false with no title', () => { 136 | const item = singleItemNoTitle.data; 137 | const test = base.$__gscc.app.hasRequiredFields(item); 138 | expect(test).toBe(false); 139 | }); 140 | 141 | it('hasRequiredFields() should return false with no creators', () => { 142 | const item = singleItemNoCreators.data; 143 | const test = base.$__gscc.app.hasRequiredFields(item); 144 | expect(test).toBe(false); 145 | }); 146 | 147 | it('processCitationResponse() 200 should set item data', () => { 148 | const item = { ...singleItemNoCount.data }; 149 | const targetUrl = base.$__gscc.app.generateItemUrl(singleItemNoCount.data); 150 | base.$__gscc.app.processCitationResponse( 151 | 200, 152 | hasCitation.data, 153 | null, 154 | targetUrl, 155 | item, 156 | (item, citeCount) => { 157 | base.$__gscc.app.updateItem(item, citeCount); 158 | }, 159 | ); 160 | expect(item.getField('extra')).toEqual( 161 | 'GSCC: 0001028 2025-01-01T08:00:00.000Z 0.56 \n', 162 | ); 163 | }); 164 | 165 | it('processCitationResponse() 200 should warn on console when item not found', () => { 166 | const warn = jest.spyOn(base.$__gscc.debugger, 'warn'); 167 | const item = { ...singleItemNoCount.data }; 168 | const targetUrl = base.$__gscc.app.generateItemUrl(singleItemNoCount.data); 169 | base.$__gscc.app.processCitationResponse( 170 | 200, 171 | noCitation.data, 172 | null, 173 | targetUrl, 174 | item, 175 | (item, citeCount) => { 176 | base.$__gscc.app.updateItem(item, citeCount); 177 | }, 178 | ); 179 | expect(warn).toHaveBeenCalled(); 180 | }); 181 | 182 | it('processCitationResponse() 200 should open window on recaptcha', () => { 183 | const warn = jest.spyOn(base.$__gscc.debugger, 'warn'); 184 | const openWindow = jest.spyOn(base.$__gscc.util, 'openRecaptchaWindow'); 185 | const item = { ...singleItemNoCount.data }; 186 | const targetUrl = base.$__gscc.app.generateItemUrl(singleItemNoCount.data); 187 | base.$__gscc.app.processCitationResponse( 188 | 200, 189 | hasRecaptcha.data, 190 | null, 191 | targetUrl, 192 | item, 193 | (item, citeCount) => { 194 | base.$__gscc.app.updateItem(item, citeCount); 195 | }, 196 | ); 197 | expect(warn).toHaveBeenCalled(); 198 | expect(openWindow).toHaveBeenCalled(); 199 | }); 200 | 201 | it('processCitationResponse() 404 should console error', () => { 202 | const warn = jest.spyOn(base.$__gscc.debugger, 'error'); 203 | const item = { ...singleItemNoCount.data }; 204 | const targetUrl = base.$__gscc.app.generateItemUrl(singleItemNoCount.data); 205 | base.$__gscc.app.processCitationResponse( 206 | 404, 207 | hasRecaptcha.data, 208 | null, 209 | targetUrl, 210 | item, 211 | (item, citeCount) => { 212 | base.$__gscc.app.updateItem(item, citeCount); 213 | }, 214 | ); 215 | expect(warn).toHaveBeenCalled(); 216 | }); 217 | 218 | it('processCitationResponse() 429 should not console warn if retry set', () => { 219 | const warn = jest.spyOn(base.$__gscc.debugger, 'warn'); 220 | const item = { ...singleItemNoCount.data }; 221 | const targetUrl = base.$__gscc.app.generateItemUrl(singleItemNoCount.data); 222 | base.$__gscc.app.processCitationResponse( 223 | 429, 224 | hasRecaptcha.data, 225 | null, 226 | targetUrl, 227 | item, 228 | (item, citeCount) => { 229 | base.$__gscc.app.updateItem(item, citeCount); 230 | }, 231 | ); 232 | expect(warn).toHaveBeenCalledTimes(0); 233 | }); 234 | 235 | it('processCitationResponse() 429 should console warn if retry set', () => { 236 | const warn = jest.spyOn(base.$__gscc.debugger, 'warn'); 237 | const item = { ...singleItemNoCount.data }; 238 | const targetUrl = base.$__gscc.app.generateItemUrl(singleItemNoCount.data); 239 | base.$__gscc.app.processCitationResponse( 240 | 429, 241 | hasRecaptcha.data, 242 | 2000, 243 | targetUrl, 244 | item, 245 | (item, citeCount) => { 246 | base.$__gscc.app.updateItem(item, citeCount); 247 | }, 248 | ); 249 | expect(warn).toHaveBeenCalledTimes(1); 250 | }); 251 | 252 | it('processCitationResponse() 500 should console error', () => { 253 | const error = jest.spyOn(base.$__gscc.debugger, 'error'); 254 | const item = { ...singleItemNoCount.data }; 255 | const targetUrl = base.$__gscc.app.generateItemUrl(singleItemNoCount.data); 256 | base.$__gscc.app.processCitationResponse( 257 | 500, 258 | hasRecaptcha.data, 259 | null, 260 | targetUrl, 261 | item, 262 | (item, citeCount) => { 263 | base.$__gscc.app.updateItem(item, citeCount); 264 | }, 265 | ); 266 | expect(error).toHaveBeenCalledTimes(1); 267 | }); 268 | 269 | it('addToWindow sets up world', async () => { 270 | const info = jest.spyOn(base.$__gscc.debugger, 'info'); 271 | 272 | // there's no menu in JSDOM, so we make one temp wise 273 | const ele = global.document.createElement('div'); 274 | ele.id = 'zotero-itemmenu'; 275 | global.document.body.appendChild(ele); 276 | 277 | await base.$__gscc.app.addToWindow(global.window); 278 | 279 | // expect(base.$__gscc.app.__citedByPrefix).toBe('gscc-citedByPrefix'); 280 | expect(info).toHaveBeenCalledTimes(1); 281 | }); 282 | 283 | it('removeFromWindow runs failsafe in case unregister fails', async () => { 284 | const info = jest.spyOn(base.$__gscc.debugger, 'info'); 285 | 286 | // there's no menu in JSDOM, so we make one temp wise 287 | const ele = global.document.createElement('div'); 288 | ele.id = 'gscc-get-count'; 289 | global.document.body.appendChild(ele); 290 | 291 | await base.$__gscc.app.removeFromWindow(global.window); 292 | 293 | expect(info).toHaveBeenCalledTimes(1); 294 | }); 295 | 296 | it('processItems burns correctly', async () => { 297 | const info = jest.spyOn(base.$__gscc.debugger, 'info'); 298 | jest.spyOn($__gscc.app, 'retrieveCitationData'); 299 | jest.spyOn($__gscc.app, 'processCitationResponse'); 300 | await base.$__gscc.app.processItems(itemsList); 301 | 302 | expect(info).toHaveBeenCalledTimes(6); 303 | }); 304 | 305 | it('getApiEndpoint handles bad data URL', async () => { 306 | const alert = jest.spyOn(global.window, 'alert'); 307 | base.$__gscc.app.__preferenceDefaults.defaultGsApiEndpoint = 'gibbgerish'; 308 | await base.$__gscc.app.getApiEndpoint(); 309 | 310 | expect(alert).toHaveBeenCalledTimes(1); 311 | }); 312 | 313 | it('verify field key lookup in setColumnData', () => { 314 | const item = singleItemWithCount.data; 315 | const citeCount = base.$__gscc.app.setColumnData(item, 'citationCount'); 316 | expect(citeCount).toBe(1000); 317 | 318 | const lastUpdated = base.$__gscc.app.setColumnData(item, 'lastUpdated'); 319 | expect(lastUpdated).toBe('1/1/2025, 12:00:00 AM'); 320 | 321 | const relevanceScore = base.$__gscc.app.setColumnData( 322 | item, 323 | 'relevanceScore', 324 | ); 325 | expect(relevanceScore).toBe(0.54); 326 | }); 327 | 328 | it('extra Field Extractor tests', () => { 329 | for (const test of extraFieldTester) { 330 | const verify = base.$__gscc.app.extraFieldExtractor(test.string); 331 | expect(verify).toEqual(test.expectedResult); 332 | } 333 | }); 334 | }); 335 | -------------------------------------------------------------------------------- /__tests__/utils.test.js: -------------------------------------------------------------------------------- 1 | const base = require('../src/gscc.js'); 2 | const hasCitation = require('./__data__/gsResponseHasCitation.js'); 3 | const noCitation = require('./__data__/gsResponseNoCitation.js'); 4 | const hasRecaptcha = require('./__data__/gsResponseHasRecaptcha'); 5 | 6 | describe('Verify $__gscc.util', () => { 7 | it('check randomInteger() for proper output ', async () => { 8 | const test = base.$__gscc.util.randomInteger(1000, 2000); 9 | expect(test).toBeGreaterThan(1000); 10 | expect(test).toBeLessThan(2000); 11 | }); 12 | it('check padCountWithZeros() length ', async () => { 13 | const test = base.$__gscc.util.padCountWithZeros('1234', 7); 14 | expect(test.length).toEqual(7); 15 | }); 16 | it('hasCitationResults() should return true with result data in response ', async () => { 17 | const test = base.$__gscc.util.hasCitationResults(`${hasCitation.data}`); 18 | expect(test).toBe(true); 19 | }); 20 | it('hasCitationResults() should return false with no result data in response ', async () => { 21 | const test = base.$__gscc.util.hasCitationResults(`${noCitation.data}`); 22 | expect(test).toBe(false); 23 | }); 24 | it('hasRecaptcha() should return false with result data in response ', async () => { 25 | const test = base.$__gscc.util.hasRecaptcha(`${hasCitation.data}`); 26 | expect(test).toBe(false); 27 | }); 28 | it('hasRecaptcha() should return true with recaptcha data in response ', async () => { 29 | const test = base.$__gscc.util.hasRecaptcha(`${hasRecaptcha.data}`); 30 | expect(test).toBe(true); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # set the version for our file 4 | # requires NPM 7.20+ 5 | version=$(npm pkg get version | tr -d '"') 6 | 7 | rm -rf build 8 | mkdir -p build 9 | cd src 10 | zip -r ../build/zotero-google-scholar-citation-count-${version}.xpi * 11 | cd .. 12 | 13 | # patch the JSON file version 14 | jq --arg version "$version" '.addons."justin@justinribeiro.com".updates.[0].version |= "\($version)"' updates.json | sponge updates.json 15 | 16 | # patch the update link 17 | updatelink="https://github.com/justinribeiro/zotero-google-scholar-citation-count/releases/download/v${version}/zotero-google-scholar-citation-count-${version}.xpi" 18 | jq --arg updatelink "$updatelink" '.addons."justin@justinribeiro.com".updates.[0].update_link |= "\($updatelink)"' updates.json | sponge updates.json 19 | 20 | # patch the hash for the XPI 21 | hash=$(sh -c 'sha256sum < "$1" | cut -d" " -f1' -- ./build/zotero-google-scholar-citation-count-${version}.xpi) 22 | jq --arg hash "$hash" '.addons."justin@justinribeiro.com".updates.[0].update_hash |= "sha256:\($hash)"' updates.json | sponge updates.json 23 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * For a detailed explanation regarding each configuration property, visit: 3 | * https://jestjs.io/docs/configuration 4 | */ 5 | 6 | export default { 7 | collectCoverage: true, 8 | 9 | // An array of glob patterns indicating a set of files for which coverage information should be collected 10 | // collectCoverageFrom: undefined, 11 | 12 | // The directory where Jest should output its coverage files 13 | coverageDirectory: 'coverage', 14 | 15 | // An array of regexp pattern strings used to skip coverage collection 16 | coveragePathIgnorePatterns: ['/node_modules/', '__data__'], 17 | 18 | // Indicates which provider should be used to instrument code for coverage 19 | // coverageProvider: "babel", 20 | 21 | // A list of reporter names that Jest uses when writing coverage reports 22 | coverageReporters: ['json-summary', 'text', 'lcov', 'html'], 23 | 24 | coverageThreshold: { 25 | global: { 26 | branches: 80, 27 | functions: 85, 28 | lines: 85, 29 | statements: 85, 30 | }, 31 | }, 32 | 33 | setupFiles: ['core-js', './__tests__/__setup__/setupJest.js'], 34 | 35 | testEnvironment: './__tests__/__setup__/env.js', 36 | 37 | testMatch: ['**/__tests__/**/*?(*.)+(spec|test).[tj]s?(x)'], 38 | 39 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped 40 | // testPathIgnorePatterns: [ 41 | // "/node_modules/" 42 | // ], 43 | 44 | // The regexp pattern or array of patterns that Jest uses to detect test files 45 | // testRegex: [], 46 | 47 | // This option allows the use of a custom results processor 48 | // testResultsProcessor: undefined, 49 | 50 | // This option allows use of a custom test runner 51 | // testRunner: "jest-circus/runner", 52 | 53 | // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href 54 | // testURL: "http://localhost", 55 | 56 | // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" 57 | // timers: "real", 58 | 59 | // A map from regular expressions to paths to transformers 60 | // transform: undefined, 61 | 62 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation 63 | // transformIgnorePatterns: [ 64 | // "/node_modules/", 65 | // "\\.pnp\\.[^\\/]+$" 66 | // ], 67 | 68 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them 69 | // unmockedModulePathPatterns: undefined, 70 | 71 | // Indicates whether each individual test should be reported during the run 72 | verbose: false, 73 | 74 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode 75 | // watchPathIgnorePatterns: [], 76 | 77 | // Whether to use watchman for file crawling 78 | // watchman: true, 79 | }; 80 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zotero-google-scholar-citation-count", 3 | "version": "4.3.0", 4 | "bugs": { 5 | "url": "https://github.com/justinribeiro/zotero-google-scholar-citation-count/issues" 6 | }, 7 | "license": "MPLv2", 8 | "repository": { 9 | "type": "git", 10 | "url": "git@github.com:justinribeiro/zotero-google-scholar-citation-count.git" 11 | }, 12 | "contributors": [ 13 | { 14 | "name": "Justin Ribeiro", 15 | "email": "justin@justinribeiro.com", 16 | "url": "https://justinribeiro.com" 17 | }, 18 | { 19 | "name": "Anton Beloglazov", 20 | "url": "https://beloglazov.info/" 21 | }, 22 | { 23 | "name": "MaxKuehn", 24 | "url": "https://github.com/MaxKuehn" 25 | }, 26 | { 27 | "name": "tete1030", 28 | "url": "https://github.com/tete1030" 29 | }, 30 | { 31 | "name": "Nico", 32 | "url": "https://github.com/nico-zck" 33 | }, 34 | { 35 | "name": "bitnikrbt", 36 | "url": "https://github.com/bitnikrbt" 37 | }, 38 | { 39 | "name": "drevicko", 40 | "url": "https://github.com/drevicko" 41 | } 42 | ], 43 | "scripts": { 44 | "package": "npm run clean && bash -c ./build.sh", 45 | "clean": "rm -rf coverage build", 46 | "test": "jest", 47 | "make-badges": "istanbul-badges-readme" 48 | }, 49 | "devDependencies": { 50 | "@types/jest": "^29.5.14", 51 | "assert": ">=2.1.0", 52 | "core-js": "^3.39.0", 53 | "eslint": "^9.14.0", 54 | "eslint-config-prettier": "^9.1.0", 55 | "eslint-plugin-jest": "^28.8.3", 56 | "istanbul-badges-readme": "^1.9.0", 57 | "jest": "^29.7.0", 58 | "jest-environment-jsdom": "^29.7.0", 59 | "mocha": "^10.8.2", 60 | "nyc": "^17.1.0", 61 | "prettier": "3.3.3", 62 | "vitest": "^2.1.4" 63 | }, 64 | "prettier": { 65 | "singleQuote": true 66 | }, 67 | "eslintConfig": { 68 | "extends": [ 69 | "eslint:recommended", 70 | "prettier" 71 | ], 72 | "plugins": [ 73 | "jest" 74 | ], 75 | "parserOptions": { 76 | "ecmaVersion": 2020, 77 | "sourceType": "module" 78 | }, 79 | "env": { 80 | "browser": true, 81 | "node": true, 82 | "jest/globals": true, 83 | "es6": true 84 | }, 85 | "globals": { 86 | "$__gscc": false, 87 | "Components": false, 88 | "Zotero": false, 89 | "ZoteroPane": false, 90 | "ZoteroStandalone": false, 91 | "Services": false 92 | } 93 | }, 94 | "private": true 95 | } -------------------------------------------------------------------------------- /src/bootstrap.js: -------------------------------------------------------------------------------- 1 | var $__gscc; 2 | 3 | function log(msg) { 4 | Zotero.debug('GSCC:' + msg); 5 | } 6 | 7 | function install() { 8 | log('Installed GSCC 4.0.0'); 9 | } 10 | 11 | async function startup({ id, version, rootURI }) { 12 | log('Starting GSCC 4.0.0'); 13 | 14 | const filePath = `${rootURI}/gscc.js`; 15 | Services.scriptloader.loadSubScript(filePath); 16 | 17 | Zotero.PreferencePanes.register({ 18 | pluginID: 'justin@justinribeiro.com', 19 | src: `${rootURI}prefs.xhtml`, 20 | }); 21 | 22 | $__gscc.app.init({ id, version, rootURI }); 23 | $__gscc.app.addToAllWindows(); 24 | await $__gscc.app.main(); 25 | } 26 | 27 | function onMainWindowLoad({ window }) { 28 | $__gscc.app.addToWindow(window); 29 | } 30 | 31 | function onMainWindowUnload({ window }) { 32 | $__gscc.app.removeFromWindow(window); 33 | } 34 | 35 | function shutdown() { 36 | $__gscc.app.removeFromAllWindows(); 37 | } 38 | 39 | function uninstall() { 40 | $__gscc.app.removeFromAllWindows(); 41 | } 42 | -------------------------------------------------------------------------------- /src/icons/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justinribeiro/zotero-google-scholar-citation-count/521f278e39ded0bfb2e072de2a014a93b2286b63/src/icons/favicon.png -------------------------------------------------------------------------------- /src/icons/faviconX2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justinribeiro/zotero-google-scholar-citation-count/521f278e39ded0bfb2e072de2a014a93b2286b63/src/icons/faviconX2.png -------------------------------------------------------------------------------- /src/locale/en-US/gscc-prefs.ftl: -------------------------------------------------------------------------------- 1 | preferences-gscc-enable-random-wait-timing = Set Google Scholar Request Random Wait Intervals 2 | preferences-gscc-random-wait-timing-explain = To attempt to not trigger the IP shadow ban that Google Scholar implements, GSCC uses a random interval per HTTP request. You can change the window by revising the milliseconds below. 3 | preferences-gscc-randomWaitMinMs = Minimum Request Wait (milliseconds) 4 | preferences-gscc-randomWaitMaxMs = Maximum Request Wait (milliseconds) 5 | preferences-gscc-search-params = Custom Search Parameters 6 | preferences-gscc-search-params-explain = Depending on the types of papers you import, sometimes finding matches can be hard. The latest v4.1 of GSCC allows changing the search behavior through flags to help when you need it. In most cases, you shouldn't need the flags below, but if you're having issues, different combinations in different global regions can sometimes help. 7 | preferences-gscc-useSearchTitleFuzzyMatch= Use Fuzzy Title Match 8 | preferences-gscc-useSearchTitleFuzzyMatch-explain = Changes the title search behavior to be less strict. 9 | preferences-gscc-useSearchTitleFuzzyMatch-cb = 10 | .label = Use Fuzzy Title Match (default: false) 11 | preferences-gscc-useDateRangeMatch = Use Date Range Param 12 | preferences-gscc-useDateRangeMatch-explain = Changes the search behavior to add a fuzzy date range if the paper has a year associated (+/- 2 years, which is a safe spot based on testing). 13 | preferences-gscc-useDateRangeMatch-cb = 14 | .label = Use Date Range Search Param (default: false) 15 | preferences-gscc-useSearchAuthorsMatch = Use Authors Param 16 | preferences-gscc-useSearchAuthorsMatch-explain = Changes the search behavior to add authors names to the search params. 17 | preferences-gscc-useSearchAuthorsMatch-cb = 18 | .label = Use Authors Search Param (default: true) 19 | preferences-gscc-api-endpoint = Google Scholar API Endpoint 20 | preferences-gscc-api-endpoint-explain = If you cannot access Google Scholar due to restrictions in your locale, change this to a proxy or regional Google Scholar domain. 21 | preferences-gscc-defaultGsApiEndpoint = Google Scholar API Endpoint (default: https://scholar.google.com/) 22 | preferences-gscc-useAutoSearch = Automatically Update Citation Count on Add 23 | preferences-gscc-useAutoSearch-explain = Automatically search and update the citation count when an item is added to the library. 24 | preferences-gscc-useAutoSearch-cb = 25 | .label = Add Citation Count Automatically when Item added to Library (default: false) 26 | 27 | -------------------------------------------------------------------------------- /src/locale/en-US/gscc.ftl: -------------------------------------------------------------------------------- 1 | gscc-menuitem = 2 | .label = Update Google Scholar citation count 3 | gscc-column-name = Citation Count 4 | gscc-lastupdated-column-name = Citation Count Last Updated Date 5 | gscc-relevancescore-column-name = Citation Count Relative Relevance Score 6 | gscc-update-all = 7 | .label = Update All Google Scholar citation counts. 8 | gscc-recapatcha-alert = Please enter the Captcha on the page that will now open and then re-try updating the citations, or wait a while to get unblocked by Google if the Captcha is not present. 9 | gscc-citedByPrefix = Cited by 10 | gscc-lackPermissions = You lack the permission to make edit to this library. 11 | gscc-unSupportedEntryType = Updating citations for this type of entry is not supported. 12 | gscc-unSupportedGroupCollection = 13 | .label = Updating a Group is not yet implemented. 14 | gscc-invalidGoogleScholarURL = The Google Scholar URL appears malformed; please check "Settings > Google Scholar Citation Count" to verify your Google Scholar URL. 15 | gscc-progresswindow-title = Citation Count Updated 16 | gscc-progresswindow-desc = Citation Total -------------------------------------------------------------------------------- /src/locale/es-ES/gscc-prefs.ftl: -------------------------------------------------------------------------------- 1 | preferences-gscc-enable-random-wait-timing = Establecer intervalos de espera aleatorios para las solicitudes de Google Scholar 2 | preferences-gscc-random-wait-timing-explain = Para intentar no activar la prohibición de IP que implementa Google Scholar, GSCC utiliza un intervalo aleatorio por cada solicitud HTTP. Puede cambiar la ventana modificando los milisegundos a continuación. 3 | preferences-gscc-randomWaitMinMs = Espera mínima de solicitud (milisegundos) 4 | preferences-gscc-randomWaitMaxMs = Espera máxima de solicitud (milisegundos) 5 | preferences-gscc-search-params = Parámetros de búsqueda personalizados 6 | preferences-gscc-search-params-explain = Dependiendo de los tipos de documentos que importe, a veces puede ser difícil encontrar coincidencias. La última versión v4.1 de GSCC permite cambiar el comportamiento de búsqueda mediante opciones para ayudarle cuando lo necesite. En la mayoría de los casos, no necesitará las opciones a continuación, pero si tiene problemas, diferentes combinaciones en distintas regiones globales pueden ayudar. 7 | preferences-gscc-useSearchTitleFuzzyMatch = Usar coincidencia de título difusa 8 | preferences-gscc-useSearchTitleFuzzyMatch-explain = Cambia el comportamiento de la búsqueda de títulos para que sea menos estricta. 9 | preferences-gscc-useSearchTitleFuzzyMatch-cb = 10 | .label = Usar coincidencia de título difusa (por defecto: false) 11 | preferences-gscc-useDateRangeMatch = Usar parámetro de rango de fechas 12 | preferences-gscc-useDateRangeMatch-explain = Cambia el comportamiento de búsqueda para agregar un rango de fechas difuso si el documento tiene un año asociado (+/- 2 años, que es un margen seguro basado en pruebas). 13 | preferences-gscc-useDateRangeMatch-cb = 14 | .label = Usar parámetro de búsqueda por rango de fechas (por defecto: false) 15 | preferences-gscc-useSearchAuthorsMatch = Usar parámetro de autores 16 | preferences-gscc-useSearchAuthorsMatch-explain = Cambia el comportamiento de búsqueda para agregar los nombres de los autores a los parámetros de búsqueda. 17 | preferences-gscc-useSearchAuthorsMatch-cb = 18 | .label = Usar parámetro de búsqueda por autores (por defecto: true) 19 | preferences-gscc-api-endpoint = Punto de acceso API de Google Scholar 20 | preferences-gscc-api-endpoint-explain = Si no puede acceder a Google Scholar debido a restricciones en su región, cambie esto a un proxy o un dominio regional de Google Scholar. 21 | preferences-gscc-defaultGsApiEndpoint = Punto de acceso API de Google Scholar (predeterminado: https://scholar.google.com/) 22 | preferences-gscc-useAutoSearch = Actualizar automáticamente el recuento de citas al añadir 23 | preferences-gscc-useAutoSearch-explain = Buscar y actualizar automáticamente el recuento de citas cuando se añade un elemento a la biblioteca. 24 | preferences-gscc-useAutoSearch-cb = 25 | .label = Añadir recuento de citas automáticamente al añadir un elemento a la biblioteca (por defecto: falso) -------------------------------------------------------------------------------- /src/locale/es-ES/gscc.ftl: -------------------------------------------------------------------------------- 1 | gscc-menuitem = 2 | .label = Actualizar el recuento de citas de Google Scholar 3 | gscc-column-name = Recuento de citas 4 | gscc-lastupdated-column-name = Recuento de cotizaciones Fecha de última actualización 5 | gscc-relevancescore-column-name = Recuento de citas Puntuación de relevancia relativa 6 | gscc-update-all = 7 | .label = Actualizar todos los recuentos de citas de Google Scholar. 8 | gscc-recapatcha-alert = Por favor, introduzca el Captcha en la página que se abrirá ahora y luego intente actualizar las citas nuevamente, o espere un momento para que Google desbloquee si el Captcha no está presente. 9 | gscc-citedByPrefix = Citado por 10 | gscc-lackPermissions = No tiene permiso para editar esta biblioteca. 11 | gscc-unSupportedEntryType = No se admite la actualización de citas para este tipo de entrada. 12 | gscc-unSupportedGroupCollection = 13 | .label = La actualización de un grupo aún no está implementada. 14 | gscc-invalidGoogleScholarURL = La URL de Google Scholar parece estar mal formada; por favor, revise "Configuración > Recuento de citas de Google Scholar" para verificar su URL de Google Scholar. 15 | gscc-progresswindow-title = Recuento de citas actualizado 16 | gscc-progresswindow-desc = Total de citas 17 | -------------------------------------------------------------------------------- /src/locale/fr-FR/gscc-prefs.ftl: -------------------------------------------------------------------------------- 1 | preferences-gscc-enable-random-wait-timing = Définir des intervalles d'attente aléatoires pour les requêtes Google Scholar 2 | preferences-gscc-random-wait-timing-explain = Pour tenter de ne pas déclencher l'interdiction IP imposée par Google Scholar, GSCC utilise un intervalle aléatoire pour chaque requête HTTP. Vous pouvez modifier la fenêtre en révisant les millisecondes ci-dessous. 3 | preferences-gscc-randomWaitMinMs = Temps d'attente minimum (millisecondes) 4 | preferences-gscc-randomWaitMaxMs = Temps d'attente maximum (millisecondes) 5 | preferences-gscc-search-params = Paramètres de recherche personnalisés 6 | preferences-gscc-search-params-explain = En fonction des types de documents que vous importez, il peut parfois être difficile de trouver des correspondances. La dernière version v4.1 de GSCC permet de modifier le comportement de recherche via des options pour vous aider en cas de besoin. Dans la plupart des cas, vous n'aurez pas besoin de ces options, mais si vous rencontrez des problèmes, différentes combinaisons dans différentes régions peuvent parfois aider. 7 | preferences-gscc-useSearchTitleFuzzyMatch = Utiliser la correspondance floue des titres 8 | preferences-gscc-useSearchTitleFuzzyMatch-explain = Modifie le comportement de recherche des titres pour le rendre moins strict. 9 | preferences-gscc-useSearchTitleFuzzyMatch-cb = 10 | .label = Utiliser la correspondance floue des titres (par défaut : false) 11 | preferences-gscc-useDateRangeMatch = Utiliser le paramètre de plage de dates 12 | preferences-gscc-useDateRangeMatch-explain = Modifie le comportement de recherche pour ajouter une plage de dates floue si le document comporte une année associée (+/- 2 ans, basé sur des tests, c'est une plage sûre). 13 | preferences-gscc-useDateRangeMatch-cb = 14 | .label = Utiliser le paramètre de recherche par plage de dates (par défaut : false) 15 | preferences-gscc-useSearchAuthorsMatch = Utiliser le paramètre des auteurs 16 | preferences-gscc-useSearchAuthorsMatch-explain = Modifie le comportement de recherche pour ajouter les noms des auteurs aux paramètres de recherche. 17 | preferences-gscc-useSearchAuthorsMatch-cb = 18 | .label = Utiliser le paramètre de recherche par auteurs (par défaut : true) 19 | preferences-gscc-api-endpoint = Point de terminaison API de Google Scholar 20 | preferences-gscc-api-endpoint-explain = Si vous ne pouvez pas accéder à Google Scholar en raison de restrictions dans votre région, modifiez ceci pour utiliser un proxy ou un domaine régional de Google Scholar. 21 | preferences-gscc-defaultGsApiEndpoint = Point de terminaison API de Google Scholar (par défaut : https://scholar.google.com/) 22 | preferences-gscc-useAutoSearch = Mettre à jour automatiquement le nombre de citations à l’ajout 23 | preferences-gscc-useAutoSearch-explain = Rechercher et mettre à jour automatiquement le nombre de citations lorsqu’un élément est ajouté à la bibliothèque. 24 | preferences-gscc-useAutoSearch-cb = 25 | .label = Ajouter automatiquement le nombre de citations lors de l’ajout à la bibliothèque (par défaut : faux) 26 | -------------------------------------------------------------------------------- /src/locale/fr-FR/gscc.ftl: -------------------------------------------------------------------------------- 1 | gscc-menuitem = 2 | .label = Mettre à jour le nombre de citations Google Scholar 3 | gscc-column-name = Nombre de citations 4 | gscc-lastupdated-column-name = Date de dernière mise à jour du nombre de citations 5 | gscc-relevancescore-column-name = Nombre de citations Score de pertinence relative 6 | gscc-update-all = 7 | .label = Mettre à jour tous les nombres de citations Google Scholar. 8 | gscc-recapatcha-alert = Veuillez entrer le Captcha sur la page qui va s'ouvrir, puis réessayez de mettre à jour les citations, ou attendez un moment pour être débloqué par Google si le Captcha n'est pas présent. 9 | gscc-citedByPrefix = Cité par 10 | gscc-lackPermissions = Vous n'avez pas la permission de modifier cette bibliothèque. 11 | gscc-unSupportedEntryType = La mise à jour des citations pour ce type d'entrée n'est pas prise en charge. 12 | gscc-unSupportedGroupCollection = 13 | .label = La mise à jour d'un groupe n'est pas encore implémentée. 14 | gscc-invalidGoogleScholarURL = L'URL de Google Scholar semble malformée ; veuillez vérifier "Paramètres > Nombre de citations Google Scholar" pour confirmer votre URL Google Scholar. 15 | gscc-progresswindow-title = Nombre de citations mis à jour 16 | gscc-progresswindow-desc = Nombre total de citations 17 | -------------------------------------------------------------------------------- /src/locale/ja-JP/gscc-prefs.ftl: -------------------------------------------------------------------------------- 1 | preferences-gscc-enable-random-wait-timing = Google Scholar リクエストのランダム待機間隔を設定 2 | preferences-gscc-random-wait-timing-explain = Google Scholar が実施する IP ブロックを回避するため、GSCC は各 HTTP リクエストにランダムな間隔を使用します。以下のミリ秒単位の設定を変更してウィンドウを調整できます。 3 | preferences-gscc-randomWaitMinMs = 最小リクエスト待機時間(ミリ秒) 4 | preferences-gscc-randomWaitMaxMs = 最大リクエスト待機時間(ミリ秒) 5 | preferences-gscc-search-params = カスタム検索パラメータ 6 | preferences-gscc-search-params-explain = インポートする論文の種類によっては、マッチを見つけるのが難しい場合があります。最新の GSCC v4.1 では、必要に応じてフラグを使用して検索動作を変更することができます。ほとんどの場合、以下のフラグは必要ありませんが、問題が発生した場合、異なる地域で異なる組み合わせが役立つことがあります。 7 | preferences-gscc-useSearchTitleFuzzyMatch = タイトルのファジーマッチを使用 8 | preferences-gscc-useSearchTitleFuzzyMatch-explain = タイトル検索の動作を変更し、厳格さを緩和します。 9 | preferences-gscc-useSearchTitleFuzzyMatch-cb = 10 | .label = タイトルのファジーマッチを使用(デフォルト:false) 11 | preferences-gscc-useDateRangeMatch = 日付範囲パラメータを使用 12 | preferences-gscc-useDateRangeMatch-explain = 論文に関連する年がある場合、検索動作を変更してファジーな日付範囲(+/- 2 年)を追加します。これはテストに基づいて安全な範囲です。 13 | preferences-gscc-useDateRangeMatch-cb = 14 | .label = 日付範囲検索パラメータを使用(デフォルト:false) 15 | preferences-gscc-useSearchAuthorsMatch = 著者パラメータを使用 16 | preferences-gscc-useSearchAuthorsMatch-explain = 検索パラメータに著者名を追加するように検索動作を変更します。 17 | preferences-gscc-useSearchAuthorsMatch-cb = 18 | .label = 著者検索パラメータを使用(デフォルト:true) 19 | preferences-gscc-api-endpoint = Google Scholar API エンドポイント 20 | preferences-gscc-api-endpoint-explain = 地域の制限により Google Scholar にアクセスできない場合は、これをプロキシまたは地域の Google Scholar ドメインに変更してください。 21 | preferences-gscc-defaultGsApiEndpoint = Google Scholar API エンドポイント(デフォルト: https://scholar.google.com/) 22 | preferences-gscc-useAutoSearch = 追加時に引用数を自動更新 23 | preferences-gscc-useAutoSearch-explain = アイテムがライブラリに追加されたときに、引用数を自動で検索・更新します。 24 | preferences-gscc-useAutoSearch-cb = 25 | .label = アイテム追加時に引用数を自動で追加する(デフォルト:false) 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/locale/ja-JP/gscc.ftl: -------------------------------------------------------------------------------- 1 | gscc-menuitem = 2 | .label = Google Scholar の引用数を更新 3 | gscc-column-name = 引用数 4 | gscc-lastupdated-column-name = 引用数の最終更新日 5 | gscc-relevancescore-column-name = 引用数相対関連性スコア 6 | gscc-update-all = 7 | .label = すべての Google Scholar の引用数を更新。 8 | gscc-recapatcha-alert = 現在開くページでキャプチャを入力し、その後引用の更新を再試行してください。キャプチャが表示されていない場合は、Google によるブロック解除を待ってください。 9 | gscc-citedByPrefix = 引用元 10 | gscc-lackPermissions = このライブラリを編集する権限がありません。 11 | gscc-unSupportedEntryType = この種類のエントリの引用数更新はサポートされていません。 12 | gscc-unSupportedGroupCollection = 13 | .label = グループの更新はまだ実装されていません。 14 | gscc-invalidGoogleScholarURL = Google Scholar の URL が不正な形式のようです。"設定 > Google Scholar 引用数" で Google Scholar の URL を確認してください。 15 | gscc-progresswindow-title = 被引用数を更新しました 16 | gscc-progresswindow-desc = 総被引用数 17 | 18 | -------------------------------------------------------------------------------- /src/locale/zh-CH/gscc-prefs.ftl: -------------------------------------------------------------------------------- 1 | preferences-gscc-enable-random-wait-timing = 设置 Google 学术请求随机等待时间间隔 2 | preferences-gscc-random-wait-timing-explain = 为了尝试避免触发 Google 学术实施的 IP 阴影禁令,GSCC 对每个 HTTP 请求使用随机间隔。您可以通过修改下面的毫秒数来更改窗口。 3 | preferences-gscc-randomWaitMinMs = 最小请求等待时间(毫秒) 4 | preferences-gscc-randomWaitMaxMs = 最大请求等待时间(毫秒) 5 | preferences-gscc-search-params = 自定义搜索参数 6 | preferences-gscc-search-params-explain = 根据您导入的论文类型,有时找到匹配项会很困难。最新的 GSCC v4.1 允许通过标志改变搜索行为,以便在需要时提供帮助。在大多数情况下,您不需要使用以下标志,但如果遇到问题,不同全球区域的不同组合有时会有所帮助。 7 | preferences-gscc-useSearchTitleFuzzyMatch = 使用模糊标题匹配 8 | preferences-gscc-useSearchTitleFuzzyMatch-explain = 更改标题搜索行为,使其不那么严格。 9 | preferences-gscc-useSearchTitleFuzzyMatch-cb = 10 | .label = 使用模糊标题匹配(默认:false) 11 | preferences-gscc-useDateRangeMatch = 使用日期范围参数 12 | preferences-gscc-useDateRangeMatch-explain = 更改搜索行为,如果论文有相关年份,将添加一个模糊日期范围(+/- 2 年,根据测试,这是一个安全范围)。 13 | preferences-gscc-useDateRangeMatch-cb = 14 | .label = 使用日期范围搜索参数(默认:false) 15 | preferences-gscc-useSearchAuthorsMatch = 使用作者参数 16 | preferences-gscc-useSearchAuthorsMatch-explain = 更改搜索行为,将作者姓名添加到搜索参数中。 17 | preferences-gscc-useSearchAuthorsMatch-cb = 18 | .label = 使用作者搜索参数(默认:true) 19 | preferences-gscc-api-endpoint = Google 学术 API 端点 20 | preferences-gscc-api-endpoint-explain = 如果由于您所在地区的限制无法访问 Google 学术,请将其更改为代理或区域性 Google 学术域名。 21 | preferences-gscc-defaultGsApiEndpoint = Google 学术 API 端点(默认: https://scholar.google.com/) 22 | preferences-gscc-useAutoSearch = 添加时自动更新引用次数 23 | preferences-gscc-useAutoSearch-explain = 当项目添加到资料库时,自动搜索并更新引用次数。 24 | preferences-gscc-useAutoSearch-cb = 25 | .label = 项目添加到资料库时自动添加引用次数(默认值:false) 26 | -------------------------------------------------------------------------------- /src/locale/zh-CH/gscc.ftl: -------------------------------------------------------------------------------- 1 | gscc-menuitem = 2 | .label = 更新 Google 学术引用次数 3 | gscc-column-name = 引用次数 4 | gscc-lastupdated-column-name = 引用计数上次更新日期 5 | gscc-relevancescore-column-name = 引用计数相对相关性得分 6 | gscc-update-all = 7 | .label = 更新所有 Google 学术引用次数。 8 | gscc-recapatcha-alert = 请在现在打开的页面上输入验证码,然后重试更新引用,或者如果没有出现验证码,请等待一段时间以解除 Google 的限制。 9 | gscc-citedByPrefix = 引用自 10 | gscc-lackPermissions = 您没有权限编辑此库。 11 | gscc-unSupportedEntryType = 不支持更新此类型条目的引用。 12 | gscc-unSupportedGroupCollection = 13 | .label = 尚未实现对组的更新。 14 | gscc-invalidGoogleScholarURL = Google 学术的 URL 格式似乎不正确;请检查“设置 > Google 学术引用计数”以验证您的 Google 学术 URL。 15 | gscc-progresswindow-title = 引用次数已更新 16 | gscc-progresswindow-desc = 引用总数 17 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "Google Scholar Citation Count", 4 | "version": "4.3.0", 5 | "description": "Zotero plugin for fetching numbers of citations from Google Scholar.", 6 | "homepage_url": "https://github.com/justinribeiro/zotero-google-scholar-citation-count", 7 | "author": "Justin Ribeiro", 8 | "icons": { 9 | "48": "icons/favicon.png", 10 | "96": "icons/faviconX2.png" 11 | }, 12 | "applications": { 13 | "zotero": { 14 | "id": "justin@justinribeiro.com", 15 | "update_url": "https://raw.githubusercontent.com/justinribeiro/zotero-google-scholar-citation-count/master/update.json", 16 | "strict_min_version": "6.999", 17 | "strict_max_version": "7.*.*" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/prefs.js: -------------------------------------------------------------------------------- 1 | pref('extensions.zotero.gscc.useAutoCountUpdate', false); 2 | pref('extensions.zotero.gscc.useRandomWait', true); 3 | pref('extensions.zotero.gscc.randomWaitMinMs', 1000); 4 | pref('extensions.zotero.gscc.randomWaitMaxMs', 5000); 5 | pref('extensions.zotero.gscc.useSearchTitleFuzzyMatch', false); 6 | pref('extensions.zotero.gscc.useSearchAuthorsMatch', true); 7 | pref('extensions.zotero.gscc.useDateRangeMatch', false); 8 | pref( 9 | 'extensions.zotero.gscc.defaultGsApiEndpoint', 10 | 'https://scholar.google.com', 11 | ); 12 | -------------------------------------------------------------------------------- /src/prefs.xhtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 12 | 13 | 17 | 18 | 24 | 28 | 29 | 30 | 36 | 37 | 41 | 42 | 48 | 52 | 58 | 62 | 63 | 64 | 65 | 71 | 75 | 76 | 77 | 83 | 87 | 88 | 94 | 95 | 101 | 105 | 106 | 112 | 113 | 119 | 123 | 124 | 130 | 131 | 137 | 141 | 142 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /updates.json: -------------------------------------------------------------------------------- 1 | { 2 | "addons": { 3 | "justin@justinribeiro.com": { 4 | "updates": [ 5 | { 6 | "version": "4.3.0", 7 | "update_link": "https://github.com/justinribeiro/zotero-google-scholar-citation-count/releases/download/v4.3.0/zotero-google-scholar-citation-count-4.3.0.xpi", 8 | "update_hash": "sha256:36b02a09da29ecd245c7a83f7f5ae296a7cb2377ed45b2d36e2090c2b03285e0", 9 | "applications": { 10 | "zotero": { 11 | "strict_min_version": "6.999", 12 | "strict_max_version": "7.0.*" 13 | } 14 | } 15 | } 16 | ] 17 | } 18 | } 19 | } 20 | --------------------------------------------------------------------------------