zotero-rb 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +18 -0
- data/.yardopts +12 -0
- data/CHANGELOG.md +22 -0
- data/LICENSE.txt +21 -0
- data/README.md +76 -0
- data/Rakefile +21 -0
- data/TASKS.md +209 -0
- data/lib/zotero/client.rb +139 -0
- data/lib/zotero/error.rb +13 -0
- data/lib/zotero/fields.rb +20 -0
- data/lib/zotero/file_upload.rb +41 -0
- data/lib/zotero/fulltext.rb +17 -0
- data/lib/zotero/http_errors.rb +43 -0
- data/lib/zotero/item_types.rb +30 -0
- data/lib/zotero/library.rb +129 -0
- data/lib/zotero/library_file_operations.rb +79 -0
- data/lib/zotero/syncing.rb +19 -0
- data/lib/zotero/version.rb +5 -0
- data/lib/zotero.rb +28 -0
- data/sig/zotero/rb.rbs +6 -0
- metadata +100 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 4726b345f1c3ad888f6ea08fd69fefc997527aa509972db1f0aa3c38ed6e58c6
|
|
4
|
+
data.tar.gz: a4b6623d56034130a91b8067c1927120d60731aae6d71d7855c253df2bd57aa0
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: fc34f7ac321a12c9cda48ca5615cbabd617f8981dea664a87aa9243e5e7d677b0b6acde40108bd2813f44e34048cafb668afaa4385dfe8ecdd27eb171782b583
|
|
7
|
+
data.tar.gz: 611cd7bc28d93c0c4365aea57e833d687d65571d147a6d14197b8cab0194defdb9f29383958aa3ff87ebbc985050d82a02c35f6111c74ed83180bb52a38f2097
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
TargetRubyVersion: 3.2
|
|
3
|
+
NewCops: enable
|
|
4
|
+
SuggestExtensions: false
|
|
5
|
+
|
|
6
|
+
Layout/LineLength:
|
|
7
|
+
Max: 120
|
|
8
|
+
|
|
9
|
+
Style/StringLiterals:
|
|
10
|
+
EnforcedStyle: double_quotes
|
|
11
|
+
|
|
12
|
+
Style/Documentation:
|
|
13
|
+
Enabled: false
|
|
14
|
+
|
|
15
|
+
Metrics/BlockLength:
|
|
16
|
+
Exclude:
|
|
17
|
+
- "spec/**/*"
|
|
18
|
+
- "*.gemspec"
|
data/.yardopts
ADDED
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0](https://github.com/andrewhwaller/zotero-rb/compare/v0.0.0...v0.1.0) (2025-09-04)
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Initial release of zotero-rb gem
|
|
14
|
+
- Full Zotero Web API v3 client with API key authentication
|
|
15
|
+
- Complete CRUD operations for items, collections, tags, and searches
|
|
16
|
+
- File upload and download support
|
|
17
|
+
- Fulltext content access
|
|
18
|
+
- Library synchronization features
|
|
19
|
+
- Metadata retrieval (item types, fields, creator types)
|
|
20
|
+
- Comprehensive error handling with custom exception classes
|
|
21
|
+
- Support for both user and group libraries
|
|
22
|
+
- Ruby 3.2+ compatibility
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Andrew Waller
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Zotero Ruby Gem
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/rb/zotero-rb)
|
|
4
|
+
[](https://github.com/andrewhwaller/zotero-rb/actions/workflows/main.yml)
|
|
5
|
+
|
|
6
|
+
A comprehensive Ruby client for the [Zotero Web API v3](https://www.zotero.org/support/dev/web_api/v3/start).
|
|
7
|
+
|
|
8
|
+
NOTE: This gem is experimental and has not been fully tested with real data. So far, the gem has been set up to cover Zotero's web API documentation as much as possible, but testing is still ongoing. Do not use this gem for production applications without exercising due caution.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
gem install zotero-rb
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
require 'zotero'
|
|
20
|
+
|
|
21
|
+
# Create a client with your API key
|
|
22
|
+
client = Zotero.new(api_key: 'your-api-key')
|
|
23
|
+
|
|
24
|
+
# Get a library (user or group)
|
|
25
|
+
library = client.user_library(12345)
|
|
26
|
+
group_library = client.group_library(67890)
|
|
27
|
+
|
|
28
|
+
# Work with items
|
|
29
|
+
items = library.items
|
|
30
|
+
new_item = library.create_item(itemType: 'book', title: 'My Book')
|
|
31
|
+
library.update_item('ITEM123', { title: 'Updated Title' }, version: 150)
|
|
32
|
+
library.delete_item('ITEM123', version: 151)
|
|
33
|
+
|
|
34
|
+
# Work with collections
|
|
35
|
+
collections = library.collections
|
|
36
|
+
new_collection = library.create_collection(name: 'My Collection')
|
|
37
|
+
|
|
38
|
+
# Upload files
|
|
39
|
+
library.upload_file('ITEM123', '/path/to/file.pdf')
|
|
40
|
+
|
|
41
|
+
# Access metadata
|
|
42
|
+
item_types = client.item_types
|
|
43
|
+
book_fields = client.item_type_fields('book')
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Authentication
|
|
47
|
+
|
|
48
|
+
1. Create a new Zotero API key or use an existing one in your [Zotero settings](https://www.zotero.org/settings/security)
|
|
49
|
+
2. Ensure your key has the appropriate permissions (read library, write library, etc.)
|
|
50
|
+
3. Pass it to the client as shown above
|
|
51
|
+
|
|
52
|
+
## Development
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
bundle install
|
|
56
|
+
bundle exec rake spec
|
|
57
|
+
bundle exec rubocop
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Releases
|
|
61
|
+
|
|
62
|
+
This project uses [Release Please](https://github.com/googleapis/release-please) for automated releases:
|
|
63
|
+
|
|
64
|
+
1. **Use conventional commits**: `feat: add new feature`, `fix: resolve bug`, etc.
|
|
65
|
+
2. **Release Please creates PRs** automatically with version bumps and changelog updates
|
|
66
|
+
3. **Merge the release PR** when ready to publish
|
|
67
|
+
4. **Automatic publication** to RubyGems happens after merge
|
|
68
|
+
|
|
69
|
+
### Repository Setup (for maintainers)
|
|
70
|
+
|
|
71
|
+
To enable automated publishing, add this secret to the GitHub repository:
|
|
72
|
+
- `RUBYGEMS_API_KEY`: Your RubyGems API token from https://rubygems.org/profile/edit
|
|
73
|
+
|
|
74
|
+
## License
|
|
75
|
+
|
|
76
|
+
[MIT License](LICENSE.txt)
|
data/Rakefile
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "rspec/core/rake_task"
|
|
5
|
+
require "rubocop/rake_task"
|
|
6
|
+
require "yard"
|
|
7
|
+
|
|
8
|
+
RSpec::Core::RakeTask.new(:spec)
|
|
9
|
+
RuboCop::RakeTask.new
|
|
10
|
+
YARD::Rake::YardocTask.new(:doc)
|
|
11
|
+
|
|
12
|
+
desc "Start an interactive console"
|
|
13
|
+
task :console do
|
|
14
|
+
require "bundler/setup"
|
|
15
|
+
require "irb"
|
|
16
|
+
require "zotero"
|
|
17
|
+
ARGV.clear
|
|
18
|
+
IRB.start
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
task default: %i[spec rubocop]
|
data/TASKS.md
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# Zotero Ruby Gem Development Tasks
|
|
2
|
+
|
|
3
|
+
This document tracks the development tasks for building a Ruby gem client for the Zotero Web API v3.
|
|
4
|
+
|
|
5
|
+
## Phase 1: Project Setup and Foundation
|
|
6
|
+
|
|
7
|
+
### 1.1 Initialize Ruby gem structure
|
|
8
|
+
- [ ] Set up basic gem skeleton with `bundle gem zotero-rb`
|
|
9
|
+
- [ ] Configure gemspec with proper metadata, dependencies, and Ruby version requirements
|
|
10
|
+
- [ ] Set up lib/ directory structure with main module and version file
|
|
11
|
+
- [ ] Create basic executable/CLI structure if needed
|
|
12
|
+
|
|
13
|
+
### 1.2 Configure development dependencies
|
|
14
|
+
- [ ] Add RSpec for testing framework
|
|
15
|
+
- [ ] Add RuboCop for code linting and style enforcement
|
|
16
|
+
- [ ] Add development gems: pry, byebug, yard for documentation
|
|
17
|
+
- [ ] Configure .rubocop.yml with appropriate rules
|
|
18
|
+
- [ ] Set up Rake tasks for common development workflows
|
|
19
|
+
|
|
20
|
+
### 1.3 Set up CI/CD pipeline
|
|
21
|
+
- [ ] Create GitHub Actions workflow for testing
|
|
22
|
+
- [ ] Configure matrix testing across Ruby versions (3.1, 3.2, 3.3)
|
|
23
|
+
- [ ] Add code coverage reporting with SimpleCov
|
|
24
|
+
- [ ] Set up automated RuboCop checks in CI
|
|
25
|
+
- [ ] Configure automatic gem publishing on release
|
|
26
|
+
|
|
27
|
+
### 1.4 Create basic project documentation
|
|
28
|
+
- [ ] Write comprehensive README with installation and basic usage
|
|
29
|
+
- [ ] Create CHANGELOG.md following keepachangelog.com format
|
|
30
|
+
- [ ] Add proper LICENSE file (MIT recommended)
|
|
31
|
+
- [ ] Set up YARD documentation configuration
|
|
32
|
+
- [ ] Create CONTRIBUTING.md with development guidelines
|
|
33
|
+
|
|
34
|
+
## Phase 2: Core API Client Infrastructure
|
|
35
|
+
|
|
36
|
+
### 2.1 Implement HTTP client foundation
|
|
37
|
+
- [ ] Create base `Zotero::Client` class
|
|
38
|
+
- [ ] Choose HTTP library (Net::HTTP, Faraday, or HTTParty) and implement adapter pattern
|
|
39
|
+
- [ ] Implement request/response wrapper classes
|
|
40
|
+
- [ ] Add JSON parsing and serialization
|
|
41
|
+
- [ ] Create configuration class for API settings
|
|
42
|
+
|
|
43
|
+
### 2.2 Add authentication support
|
|
44
|
+
- [ ] Implement API key authentication via `Zotero-API-Key` header
|
|
45
|
+
- [ ] Add Bearer token authentication support
|
|
46
|
+
- [ ] Implement OAuth 1.0a flow for getting API keys
|
|
47
|
+
- [ ] Create OAuth client for handling authorization workflow
|
|
48
|
+
- [ ] Add configuration for client credentials and callback URLs
|
|
49
|
+
|
|
50
|
+
### 2.3 Implement rate limiting and retries
|
|
51
|
+
- [ ] Add `Backoff` header handling
|
|
52
|
+
- [ ] Implement exponential backoff for rate limit responses (429)
|
|
53
|
+
- [ ] Create configurable retry policy
|
|
54
|
+
- [ ] Add request queuing/throttling mechanism
|
|
55
|
+
- [ ] Implement circuit breaker pattern for API failures
|
|
56
|
+
|
|
57
|
+
### 2.4 Create error handling system
|
|
58
|
+
- [ ] Define custom exception hierarchy (`Zotero::Error` base class)
|
|
59
|
+
- [ ] Create specific exceptions for different HTTP status codes
|
|
60
|
+
- [ ] Add authentication error handling (`Zotero::AuthenticationError`)
|
|
61
|
+
- [ ] Implement rate limit exception (`Zotero::RateLimitError`)
|
|
62
|
+
- [ ] Add validation error handling for malformed requests
|
|
63
|
+
|
|
64
|
+
### 2.5 Add request/response logging
|
|
65
|
+
- [ ] Implement configurable logging system
|
|
66
|
+
- [ ] Add request logging with sanitized sensitive data
|
|
67
|
+
- [ ] Create response logging with configurable detail levels
|
|
68
|
+
- [ ] Add performance timing logs
|
|
69
|
+
- [ ] Support for different log levels and custom loggers
|
|
70
|
+
|
|
71
|
+
## Phase 3: Core Zotero API Features
|
|
72
|
+
|
|
73
|
+
### 3.1 Implement library access
|
|
74
|
+
- [ ] Create `Zotero::Library` class for library operations
|
|
75
|
+
- [ ] Add user library access (`/users/<userID>`)
|
|
76
|
+
- [ ] Implement group library access (`/groups/<groupID>`)
|
|
77
|
+
- [ ] Add library metadata retrieval
|
|
78
|
+
- [ ] Create library permissions checking
|
|
79
|
+
|
|
80
|
+
### 3.2 Add items management
|
|
81
|
+
- [ ] Create `Zotero::Item` model class with proper attributes
|
|
82
|
+
- [ ] Implement item creation (POST) with validation
|
|
83
|
+
- [ ] Add item retrieval (GET) with include parameters
|
|
84
|
+
- [ ] Implement item updates (PUT/PATCH)
|
|
85
|
+
- [ ] Add item deletion with proper error handling
|
|
86
|
+
- [ ] Support for item versions and conditional requests
|
|
87
|
+
|
|
88
|
+
### 3.3 Implement collections support
|
|
89
|
+
- [ ] Create `Zotero::Collection` model class
|
|
90
|
+
- [ ] Add collection CRUD operations
|
|
91
|
+
- [ ] Implement nested collection handling
|
|
92
|
+
- [ ] Add collection membership management for items
|
|
93
|
+
- [ ] Support for collection ordering and hierarchy
|
|
94
|
+
|
|
95
|
+
### 3.4 Add tags functionality
|
|
96
|
+
- [ ] Create `Zotero::Tag` model class
|
|
97
|
+
- [ ] Implement tag creation and management
|
|
98
|
+
- [ ] Add tag filtering and searching
|
|
99
|
+
- [ ] Support for colored tags
|
|
100
|
+
- [ ] Implement tag assignment to items
|
|
101
|
+
|
|
102
|
+
### 3.5 Implement search functionality
|
|
103
|
+
- [ ] Create `Zotero::Search` class for saved searches
|
|
104
|
+
- [ ] Add search query building with proper parameter encoding
|
|
105
|
+
- [ ] Implement result filtering and sorting
|
|
106
|
+
- [ ] Add search result pagination
|
|
107
|
+
- [ ] Support for different search formats (json, keys, etc.)
|
|
108
|
+
|
|
109
|
+
## Phase 4: Advanced Features
|
|
110
|
+
|
|
111
|
+
### 4.1 Add file upload support
|
|
112
|
+
- [ ] Implement file attachment upload workflow
|
|
113
|
+
- [ ] Add support for different file types and validation
|
|
114
|
+
- [ ] Create progress tracking for large file uploads
|
|
115
|
+
- [ ] Implement file metadata handling
|
|
116
|
+
- [ ] Add file download capabilities
|
|
117
|
+
|
|
118
|
+
### 4.2 Implement syncing capabilities
|
|
119
|
+
- [ ] Add support for library version checking
|
|
120
|
+
- [ ] Implement incremental sync with version tracking
|
|
121
|
+
- [ ] Create conflict resolution strategies
|
|
122
|
+
- [ ] Add sync status reporting
|
|
123
|
+
- [ ] Support for partial syncing of specific resources
|
|
124
|
+
|
|
125
|
+
### 4.3 Add full-text content access
|
|
126
|
+
- [ ] Implement full-text content retrieval for items
|
|
127
|
+
- [ ] Add full-text search capabilities
|
|
128
|
+
- [ ] Support for different content formats
|
|
129
|
+
- [ ] Add content indexing status checking
|
|
130
|
+
- [ ] Implement content caching strategies
|
|
131
|
+
|
|
132
|
+
### 4.4 Create streaming API support
|
|
133
|
+
- [ ] Research and implement Zotero streaming API endpoints
|
|
134
|
+
- [ ] Add real-time update notifications
|
|
135
|
+
- [ ] Create event-based update handling
|
|
136
|
+
- [ ] Implement connection management for streaming
|
|
137
|
+
- [ ] Add streaming API error recovery
|
|
138
|
+
|
|
139
|
+
### 4.5 Add pagination handling
|
|
140
|
+
- [ ] Implement automatic pagination with `Link` header parsing
|
|
141
|
+
- [ ] Create iterator pattern for paginated results
|
|
142
|
+
- [ ] Add configurable page size limits
|
|
143
|
+
- [ ] Support for cursor-based pagination where available
|
|
144
|
+
- [ ] Implement efficient pagination caching
|
|
145
|
+
|
|
146
|
+
## Phase 5: Developer Experience & Polish
|
|
147
|
+
|
|
148
|
+
### 5.1 Create comprehensive test suite
|
|
149
|
+
- [ ] Write unit tests for all major classes and methods
|
|
150
|
+
- [ ] Add integration tests using VCR for API mocking
|
|
151
|
+
- [ ] Create test fixtures for different Zotero data types
|
|
152
|
+
- [ ] Implement contract tests for API compatibility
|
|
153
|
+
- [ ] Add performance benchmarks and regression tests
|
|
154
|
+
- [ ] Achieve >90% test coverage
|
|
155
|
+
|
|
156
|
+
### 5.2 Add configuration management
|
|
157
|
+
- [ ] Create global configuration system (`Zotero.configure`)
|
|
158
|
+
- [ ] Add environment variable support for common settings
|
|
159
|
+
- [ ] Implement per-client configuration overrides
|
|
160
|
+
- [ ] Add validation for configuration options
|
|
161
|
+
- [ ] Create configuration presets for common use cases
|
|
162
|
+
|
|
163
|
+
### 5.3 Write detailed documentation
|
|
164
|
+
- [ ] Generate comprehensive API reference with YARD
|
|
165
|
+
- [ ] Create usage guides and tutorials
|
|
166
|
+
- [ ] Add code examples for common patterns
|
|
167
|
+
- [ ] Document authentication setup and OAuth flow
|
|
168
|
+
- [ ] Create troubleshooting guide and FAQ
|
|
169
|
+
|
|
170
|
+
### 5.4 Performance optimization
|
|
171
|
+
- [ ] Implement HTTP connection pooling
|
|
172
|
+
- [ ] Add request batching where possible
|
|
173
|
+
- [ ] Optimize JSON parsing and object creation
|
|
174
|
+
- [ ] Implement intelligent caching strategies
|
|
175
|
+
- [ ] Add memory usage profiling and optimization
|
|
176
|
+
|
|
177
|
+
### 5.5 Add Ruby 3+ compatibility
|
|
178
|
+
- [ ] Ensure compatibility with Ruby 3.1+ features
|
|
179
|
+
- [ ] Add support for keyword arguments
|
|
180
|
+
- [ ] Update code to use modern Ruby idioms
|
|
181
|
+
- [ ] Test with Ruby 3.3+ and handle deprecations
|
|
182
|
+
- [ ] Add Ractor safety where applicable
|
|
183
|
+
|
|
184
|
+
## Quality Gates
|
|
185
|
+
|
|
186
|
+
Each major phase should meet these criteria before proceeding:
|
|
187
|
+
|
|
188
|
+
- [ ] All tests passing
|
|
189
|
+
- [ ] RuboCop violations resolved
|
|
190
|
+
- [ ] Documentation updated
|
|
191
|
+
- [ ] CHANGELOG updated
|
|
192
|
+
- [ ] Version bumped appropriately
|
|
193
|
+
- [ ] Manual testing completed
|
|
194
|
+
|
|
195
|
+
## Future Considerations
|
|
196
|
+
|
|
197
|
+
- **GraphQL Support**: If Zotero adds GraphQL endpoints
|
|
198
|
+
- **WebSocket Integration**: For real-time updates
|
|
199
|
+
- **Bulk Operations**: Optimized bulk import/export
|
|
200
|
+
- **Plugin System**: Allow third-party extensions
|
|
201
|
+
- **CLI Tool**: Command-line interface for common operations
|
|
202
|
+
- **Rails Integration**: ActiveRecord-style models and associations
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
**Last Updated**: 2025-08-31
|
|
207
|
+
**Gem Name**: zotero-rb
|
|
208
|
+
**Target Ruby Version**: 3.1+
|
|
209
|
+
**Zotero API Version**: v3
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "httparty"
|
|
4
|
+
require_relative "item_types"
|
|
5
|
+
require_relative "fields"
|
|
6
|
+
require_relative "file_upload"
|
|
7
|
+
require_relative "http_errors"
|
|
8
|
+
require_relative "syncing"
|
|
9
|
+
|
|
10
|
+
module Zotero
|
|
11
|
+
# The main HTTP client for interacting with the Zotero Web API v3.
|
|
12
|
+
# Provides authentication, request handling, and access to library operations.
|
|
13
|
+
#
|
|
14
|
+
# @example Create a client with API key
|
|
15
|
+
# client = Zotero::Client.new(api_key: 'your-api-key-here')
|
|
16
|
+
# library = client.user_library(12345)
|
|
17
|
+
#
|
|
18
|
+
class Client
|
|
19
|
+
include HTTParty
|
|
20
|
+
include ItemTypes
|
|
21
|
+
include Fields
|
|
22
|
+
include FileUpload
|
|
23
|
+
include HTTPErrors
|
|
24
|
+
include Syncing
|
|
25
|
+
|
|
26
|
+
base_uri "https://api.zotero.org"
|
|
27
|
+
|
|
28
|
+
# Initialize a new Zotero API client.
|
|
29
|
+
#
|
|
30
|
+
# @param api_key [String] Your Zotero API key from https://www.zotero.org/settings/keys
|
|
31
|
+
def initialize(api_key:)
|
|
32
|
+
@api_key = api_key
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def get(path, params: {})
|
|
36
|
+
response = self.class.get(path,
|
|
37
|
+
headers: auth_headers.merge(default_headers),
|
|
38
|
+
query: params)
|
|
39
|
+
handle_response(response, params[:format])
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def post(path, data:, version: nil, write_token: nil, params: {})
|
|
43
|
+
headers = build_write_headers(version: version, write_token: write_token)
|
|
44
|
+
response = self.class.post(path,
|
|
45
|
+
headers: headers,
|
|
46
|
+
body: data,
|
|
47
|
+
query: params)
|
|
48
|
+
handle_write_response(response)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def patch(path, data:, version: nil, params: {})
|
|
52
|
+
headers = build_write_headers(version: version)
|
|
53
|
+
response = self.class.patch(path,
|
|
54
|
+
headers: headers,
|
|
55
|
+
body: data,
|
|
56
|
+
query: params)
|
|
57
|
+
handle_write_response(response)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def put(path, data:, version: nil, params: {})
|
|
61
|
+
headers = build_write_headers(version: version)
|
|
62
|
+
response = self.class.put(path,
|
|
63
|
+
headers: headers,
|
|
64
|
+
body: data,
|
|
65
|
+
query: params)
|
|
66
|
+
handle_write_response(response)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def delete(path, version: nil, params: {})
|
|
70
|
+
headers = build_write_headers(version: version)
|
|
71
|
+
response = self.class.delete(path,
|
|
72
|
+
headers: headers,
|
|
73
|
+
query: params)
|
|
74
|
+
handle_write_response(response)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Get a Library instance for a specific user.
|
|
78
|
+
#
|
|
79
|
+
# @param user_id [Integer, String] The Zotero user ID
|
|
80
|
+
# @return [Library] A Library instance for the specified user
|
|
81
|
+
def user_library(user_id)
|
|
82
|
+
Library.new(client: self, type: :user, id: user_id)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Get a Library instance for a specific group.
|
|
86
|
+
#
|
|
87
|
+
# @param group_id [Integer, String] The Zotero group ID
|
|
88
|
+
# @return [Library] A Library instance for the specified group
|
|
89
|
+
def group_library(group_id)
|
|
90
|
+
Library.new(client: self, type: :group, id: group_id)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
attr_reader :api_key
|
|
96
|
+
|
|
97
|
+
def auth_headers
|
|
98
|
+
{ "Zotero-API-Key" => api_key }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def default_headers
|
|
102
|
+
{ "Zotero-API-Version" => "3" }
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def build_write_headers(version: nil, write_token: nil)
|
|
106
|
+
headers = auth_headers.merge(default_headers)
|
|
107
|
+
headers["Content-Type"] = "application/json"
|
|
108
|
+
headers["If-Unmodified-Since-Version"] = version.to_s if version
|
|
109
|
+
headers["Zotero-Write-Token"] = write_token if write_token
|
|
110
|
+
headers
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def handle_response(response, format = nil)
|
|
114
|
+
return parse_response_body(response, format) if response.code.between?(200, 299)
|
|
115
|
+
|
|
116
|
+
raise_error_for_status(response)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def handle_write_response(response)
|
|
120
|
+
case response.code
|
|
121
|
+
when 200
|
|
122
|
+
response.parsed_response
|
|
123
|
+
when 204
|
|
124
|
+
true
|
|
125
|
+
else
|
|
126
|
+
raise_error_for_status(response)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def parse_response_body(response, format)
|
|
131
|
+
case format&.to_s
|
|
132
|
+
when "json", nil
|
|
133
|
+
response.parsed_response
|
|
134
|
+
else
|
|
135
|
+
response.body
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
data/lib/zotero/error.rb
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Zotero
|
|
4
|
+
class Error < StandardError; end
|
|
5
|
+
class AuthenticationError < Error; end
|
|
6
|
+
class RateLimitError < Error; end
|
|
7
|
+
class NotFoundError < Error; end
|
|
8
|
+
class BadRequestError < Error; end
|
|
9
|
+
class ServerError < Error; end
|
|
10
|
+
class ConflictError < Error; end
|
|
11
|
+
class PreconditionFailedError < Error; end
|
|
12
|
+
class PreconditionRequiredError < Error; end
|
|
13
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Zotero
|
|
4
|
+
# Field discovery methods
|
|
5
|
+
module Fields
|
|
6
|
+
def item_fields(locale: nil)
|
|
7
|
+
get("/itemFields", params: build_locale_params(locale))
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def creator_fields(locale: nil)
|
|
11
|
+
get("/creatorFields", params: build_locale_params(locale))
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def build_locale_params(locale)
|
|
17
|
+
locale ? { locale: locale } : {}
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Zotero
|
|
4
|
+
# File upload methods for handling attachment uploads
|
|
5
|
+
module FileUpload
|
|
6
|
+
def post_form(path, form_data:, if_match: nil, if_none_match: nil, params: {})
|
|
7
|
+
headers = auth_headers.merge(default_headers)
|
|
8
|
+
headers["Content-Type"] = "application/x-www-form-urlencoded"
|
|
9
|
+
headers["If-Match"] = if_match if if_match
|
|
10
|
+
headers["If-None-Match"] = if_none_match if if_none_match
|
|
11
|
+
|
|
12
|
+
response = self.class.post(path, headers: headers, body: form_data, query: params)
|
|
13
|
+
handle_response(response)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def external_post(url, multipart_data:)
|
|
17
|
+
response = self.class.post(url, body: multipart_data, multipart: true, format: :plain)
|
|
18
|
+
|
|
19
|
+
case response.code
|
|
20
|
+
when 200..299
|
|
21
|
+
response.body
|
|
22
|
+
else
|
|
23
|
+
raise Error, "External upload failed: HTTP #{response.code} - #{response.message}"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def request_upload_authorization(path, filename:, md5: nil, mtime: nil, existing_file: false)
|
|
28
|
+
form_data = { upload: filename, md5: md5, mtime: mtime }.compact
|
|
29
|
+
|
|
30
|
+
if existing_file
|
|
31
|
+
post_form(path, form_data: form_data, if_match: md5.to_s)
|
|
32
|
+
else
|
|
33
|
+
post_form(path, form_data: form_data, if_none_match: "*")
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def register_upload(path, upload_key:)
|
|
38
|
+
post_form(path, form_data: { upload: upload_key })
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Zotero
|
|
4
|
+
module Fulltext
|
|
5
|
+
def fulltext_since(since:)
|
|
6
|
+
@client.get("#{@base_path}/fulltext", params: { since: since })
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def item_fulltext(item_key)
|
|
10
|
+
@client.get("#{@base_path}/items/#{item_key}/fulltext")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def set_item_fulltext(item_key, content_data, version: nil)
|
|
14
|
+
@client.put("#{@base_path}/items/#{item_key}/fulltext", data: content_data, version: version)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Zotero
|
|
4
|
+
# HTTP error handling methods
|
|
5
|
+
module HTTPErrors
|
|
6
|
+
def raise_error_for_status(response)
|
|
7
|
+
case response.code
|
|
8
|
+
when 400..428 then raise_client_error(response)
|
|
9
|
+
when 429 then raise_rate_limit_error(response)
|
|
10
|
+
else raise_server_or_unknown_error(response)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def raise_client_error(response)
|
|
15
|
+
case response.code
|
|
16
|
+
when 400, 413 then raise BadRequestError, "Bad request: #{response.body}"
|
|
17
|
+
when 401, 403 then raise AuthenticationError, "Authentication failed - check your API key"
|
|
18
|
+
when 404 then raise NotFoundError, "Resource not found: #{response.request.path}"
|
|
19
|
+
when 409 then raise ConflictError, "Conflict: #{response.body}"
|
|
20
|
+
when 412 then raise PreconditionFailedError, "Precondition failed: #{response.body}"
|
|
21
|
+
when 428 then raise PreconditionRequiredError, "Precondition required: #{response.body}"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def raise_rate_limit_error(response)
|
|
26
|
+
backoff = response.headers["backoff"]&.to_i
|
|
27
|
+
retry_after = response.headers["retry-after"]&.to_i
|
|
28
|
+
message = "Rate limited."
|
|
29
|
+
message += " Backoff: #{backoff}s" if backoff
|
|
30
|
+
message += " Retry after: #{retry_after}s" if retry_after
|
|
31
|
+
raise RateLimitError, message
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def raise_server_or_unknown_error(response)
|
|
35
|
+
case response.code
|
|
36
|
+
when 500..599
|
|
37
|
+
raise ServerError, "Server error: HTTP #{response.code} - #{response.message}"
|
|
38
|
+
else
|
|
39
|
+
raise Error, "Unexpected response: HTTP #{response.code} - #{response.message}"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Zotero
|
|
4
|
+
# Item type discovery and template methods
|
|
5
|
+
module ItemTypes
|
|
6
|
+
def item_types(locale: nil)
|
|
7
|
+
get("/itemTypes", params: build_locale_params(locale))
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def item_type_fields(item_type, locale: nil)
|
|
11
|
+
params = { itemType: item_type }
|
|
12
|
+
params.merge!(build_locale_params(locale))
|
|
13
|
+
get("/itemTypeFields", params: params)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def item_type_creator_types(item_type)
|
|
17
|
+
get("/itemTypeCreatorTypes", params: { itemType: item_type })
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def new_item_template(item_type)
|
|
21
|
+
get("/items/new", params: { itemType: item_type })
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def build_locale_params(locale)
|
|
27
|
+
locale ? { locale: locale } : {}
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "library_file_operations"
|
|
4
|
+
require_relative "fulltext"
|
|
5
|
+
require_relative "syncing"
|
|
6
|
+
|
|
7
|
+
module Zotero
|
|
8
|
+
# Represents a Zotero library (user or group) and provides methods for
|
|
9
|
+
# managing items, collections, tags, searches, and file operations.
|
|
10
|
+
#
|
|
11
|
+
# @example Working with a user library
|
|
12
|
+
# client = Zotero.new(api_key: 'your-key')
|
|
13
|
+
# library = client.user_library(12345)
|
|
14
|
+
# items = library.items
|
|
15
|
+
# collections = library.collections
|
|
16
|
+
#
|
|
17
|
+
class Library
|
|
18
|
+
# TODO: rename this module, LibraryFileOperations sounds weird
|
|
19
|
+
include LibraryFileOperations
|
|
20
|
+
include Fulltext
|
|
21
|
+
include Syncing
|
|
22
|
+
|
|
23
|
+
VALID_TYPES = %w[user group].freeze
|
|
24
|
+
|
|
25
|
+
# Initialize a new Library instance.
|
|
26
|
+
#
|
|
27
|
+
# @param client [Client] The Zotero client instance
|
|
28
|
+
# @param type [String, Symbol] The library type (:user or :group)
|
|
29
|
+
# @param id [Integer, String] The library ID (user ID or group ID)
|
|
30
|
+
def initialize(client:, type:, id:)
|
|
31
|
+
@client = client
|
|
32
|
+
@type = validate_type(type)
|
|
33
|
+
@id = id
|
|
34
|
+
@base_path = "/#{@type}s/#{@id}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Get collections in this library.
|
|
38
|
+
#
|
|
39
|
+
# @param params [Hash] Query parameters for the request
|
|
40
|
+
# @return [Array, Hash] Collections data from the API
|
|
41
|
+
def collections(**params)
|
|
42
|
+
@client.get("#{@base_path}/collections", params: params)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Get items in this library.
|
|
46
|
+
#
|
|
47
|
+
# @param params [Hash] Query parameters for the request
|
|
48
|
+
# @return [Array, Hash] Items data from the API
|
|
49
|
+
def items(**params)
|
|
50
|
+
@client.get("#{@base_path}/items", params: params)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def searches(**params)
|
|
54
|
+
@client.get("#{@base_path}/searches", params: params)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def tags(**params)
|
|
58
|
+
@client.get("#{@base_path}/tags", params: params)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Create a new item in this library.
|
|
62
|
+
#
|
|
63
|
+
# @param item_data [Hash] The item data
|
|
64
|
+
# @param version [Integer] Optional version for conditional requests
|
|
65
|
+
# @param write_token [String] Optional write token for batch operations
|
|
66
|
+
# @return [Hash] The API response
|
|
67
|
+
def create_item(item_data, version: nil, write_token: nil)
|
|
68
|
+
create_single("items", item_data, version: version, write_token: write_token)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def create_items(items_array, version: nil, write_token: nil)
|
|
72
|
+
create_multiple("items", items_array, version: version, write_token: write_token)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def update_item(item_key, item_data, version: nil)
|
|
76
|
+
@client.patch("#{@base_path}/items/#{item_key}", data: item_data, version: version)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def delete_item(item_key, version: nil)
|
|
80
|
+
@client.delete("#{@base_path}/items/#{item_key}", version: version)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def delete_items(item_keys, version: nil)
|
|
84
|
+
@client.delete("#{@base_path}/items", version: version, params: { itemKey: item_keys.join(",") })
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def create_collection(collection_data, version: nil, write_token: nil)
|
|
88
|
+
create_single("collections", collection_data, version: version, write_token: write_token)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def create_collections(collections_array, version: nil, write_token: nil)
|
|
92
|
+
create_multiple("collections", collections_array, version: version, write_token: write_token)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def update_collection(collection_key, collection_data, version: nil)
|
|
96
|
+
@client.patch("#{@base_path}/collections/#{collection_key}", data: collection_data, version: version)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def delete_collection(collection_key, version: nil)
|
|
100
|
+
@client.delete("#{@base_path}/collections/#{collection_key}", version: version)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def delete_collections(collection_keys, version: nil)
|
|
104
|
+
@client.delete("#{@base_path}/collections", version: version,
|
|
105
|
+
params: { collectionKey: collection_keys.join(",") })
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
private
|
|
109
|
+
|
|
110
|
+
attr_reader :client, :type, :id, :base_path
|
|
111
|
+
|
|
112
|
+
def create_single(resource, data, version: nil, write_token: nil)
|
|
113
|
+
@client.post("#{@base_path}/#{resource}", data: [data], version: version, write_token: write_token)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def create_multiple(resource, data_array, version: nil, write_token: nil)
|
|
117
|
+
@client.post("#{@base_path}/#{resource}", data: data_array, version: version, write_token: write_token)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def validate_type(type)
|
|
121
|
+
type_str = type.to_s
|
|
122
|
+
unless VALID_TYPES.include?(type_str)
|
|
123
|
+
raise ArgumentError, "Invalid library type: #{type_str}. Must be one of: #{VALID_TYPES.join(', ')}"
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
type_str
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Zotero
|
|
4
|
+
# File upload operations for library items
|
|
5
|
+
module LibraryFileOperations
|
|
6
|
+
def create_attachment(attachment_data, version: nil, write_token: nil)
|
|
7
|
+
create_single("items", attachment_data, version: version, write_token: write_token)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def get_file_info(item_key)
|
|
11
|
+
@client.get("#{@base_path}/items/#{item_key}/file")
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def upload_file(item_key, file_path)
|
|
15
|
+
perform_file_upload(item_key, file_path, existing_file: false)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def update_file(item_key, file_path)
|
|
19
|
+
perform_file_upload(item_key, file_path, existing_file: true)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def perform_file_upload(item_key, file_path, existing_file:)
|
|
25
|
+
file_metadata = extract_file_metadata(file_path)
|
|
26
|
+
upload_path = file_upload_path(item_key)
|
|
27
|
+
|
|
28
|
+
# Step 1: Request upload authorization
|
|
29
|
+
auth_response = @client.request_upload_authorization(
|
|
30
|
+
upload_path,
|
|
31
|
+
**file_metadata,
|
|
32
|
+
existing_file: existing_file
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
perform_external_upload(auth_response, file_path, upload_path)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def extract_file_metadata(file_path)
|
|
39
|
+
require "digest"
|
|
40
|
+
|
|
41
|
+
{
|
|
42
|
+
filename: File.basename(file_path),
|
|
43
|
+
md5: Digest::MD5.file(file_path).hexdigest,
|
|
44
|
+
mtime: File.mtime(file_path).to_i * 1000 # Convert to milliseconds
|
|
45
|
+
}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def file_upload_path(item_key)
|
|
49
|
+
"#{@base_path}/items/#{item_key}/file"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def perform_external_upload(auth_response, file_path, upload_path)
|
|
53
|
+
if auth_response["url"]
|
|
54
|
+
upload_params = build_upload_params(auth_response, file_path)
|
|
55
|
+
@client.external_post(auth_response["url"], multipart_data: upload_params)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
if auth_response["uploadKey"]
|
|
59
|
+
@client.register_upload(upload_path, upload_key: auth_response["uploadKey"])
|
|
60
|
+
else
|
|
61
|
+
true
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def build_upload_params(auth_response, file_path)
|
|
66
|
+
file_data = File.open(file_path, "rb")
|
|
67
|
+
|
|
68
|
+
if auth_response["params"]
|
|
69
|
+
auth_response["params"].merge("file" => file_data)
|
|
70
|
+
else
|
|
71
|
+
{
|
|
72
|
+
"prefix" => auth_response["prefix"],
|
|
73
|
+
"file" => file_data,
|
|
74
|
+
"suffix" => auth_response["suffix"]
|
|
75
|
+
}
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Zotero
|
|
4
|
+
module Syncing
|
|
5
|
+
def verify_api_key
|
|
6
|
+
@client ? @client.get("/keys/current") : get("/keys/current")
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def user_groups(user_id, format: "versions")
|
|
10
|
+
client = @client || self
|
|
11
|
+
client.get("/users/#{user_id}/groups", params: { format: format })
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def deleted_items(since: nil)
|
|
15
|
+
params = since ? { since: since } : {}
|
|
16
|
+
@client.get("#{@base_path}/deleted", params: params)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/zotero.rb
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "zotero/version"
|
|
4
|
+
require_relative "zotero/client"
|
|
5
|
+
require_relative "zotero/library"
|
|
6
|
+
require_relative "zotero/error"
|
|
7
|
+
|
|
8
|
+
# Ruby client library for the Zotero Web API v3.
|
|
9
|
+
#
|
|
10
|
+
# Provides a comprehensive interface for interacting with Zotero libraries,
|
|
11
|
+
# including full CRUD operations for items, collections, tags, searches,
|
|
12
|
+
# file uploads, and synchronization.
|
|
13
|
+
#
|
|
14
|
+
# @example Basic usage
|
|
15
|
+
# client = Zotero.new(api_key: 'your-api-key')
|
|
16
|
+
# library = client.user_library(12345)
|
|
17
|
+
# items = library.items
|
|
18
|
+
#
|
|
19
|
+
# @see https://www.zotero.org/support/dev/web_api/v3/start Zotero Web API v3 Documentation
|
|
20
|
+
module Zotero
|
|
21
|
+
# Create a new Zotero API client.
|
|
22
|
+
#
|
|
23
|
+
# @param api_key [String] Your Zotero API key
|
|
24
|
+
# @return [Client] A new Zotero client instance
|
|
25
|
+
def self.new(api_key:)
|
|
26
|
+
Client.new(api_key: api_key)
|
|
27
|
+
end
|
|
28
|
+
end
|
data/sig/zotero/rb.rbs
ADDED
metadata
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: zotero-rb
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Andrew Waller
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: httparty
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 0.21.0
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 0.21.0
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: csv
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
description: A feature-complete Ruby client for the Zotero Web API v3, providing full
|
|
41
|
+
CRUD operations for items, collections, tags, and searches, plus file uploads, fulltext
|
|
42
|
+
content access, and library synchronization. Built with a modular architecture and
|
|
43
|
+
comprehensive error handling.
|
|
44
|
+
email:
|
|
45
|
+
- 48367637+andrewhwaller@users.noreply.github.com
|
|
46
|
+
executables: []
|
|
47
|
+
extensions: []
|
|
48
|
+
extra_rdoc_files: []
|
|
49
|
+
files:
|
|
50
|
+
- ".rspec"
|
|
51
|
+
- ".rubocop.yml"
|
|
52
|
+
- ".yardopts"
|
|
53
|
+
- CHANGELOG.md
|
|
54
|
+
- LICENSE.txt
|
|
55
|
+
- README.md
|
|
56
|
+
- Rakefile
|
|
57
|
+
- TASKS.md
|
|
58
|
+
- lib/zotero.rb
|
|
59
|
+
- lib/zotero/client.rb
|
|
60
|
+
- lib/zotero/error.rb
|
|
61
|
+
- lib/zotero/fields.rb
|
|
62
|
+
- lib/zotero/file_upload.rb
|
|
63
|
+
- lib/zotero/fulltext.rb
|
|
64
|
+
- lib/zotero/http_errors.rb
|
|
65
|
+
- lib/zotero/item_types.rb
|
|
66
|
+
- lib/zotero/library.rb
|
|
67
|
+
- lib/zotero/library_file_operations.rb
|
|
68
|
+
- lib/zotero/syncing.rb
|
|
69
|
+
- lib/zotero/version.rb
|
|
70
|
+
- sig/zotero/rb.rbs
|
|
71
|
+
homepage: https://github.com/andrewhwaller/zotero-rb
|
|
72
|
+
licenses:
|
|
73
|
+
- MIT
|
|
74
|
+
metadata:
|
|
75
|
+
allowed_push_host: https://rubygems.org
|
|
76
|
+
bug_tracker_uri: https://github.com/andrewhwaller/zotero-rb/issues
|
|
77
|
+
documentation_uri: https://rubydoc.info/gems/zotero-rb
|
|
78
|
+
wiki_uri: https://github.com/andrewhwaller/zotero-rb/wiki
|
|
79
|
+
homepage_uri: https://github.com/andrewhwaller/zotero-rb
|
|
80
|
+
source_code_uri: https://github.com/andrewhwaller/zotero-rb
|
|
81
|
+
changelog_uri: https://github.com/andrewhwaller/zotero-rb/blob/main/CHANGELOG.md
|
|
82
|
+
rubygems_mfa_required: 'true'
|
|
83
|
+
rdoc_options: []
|
|
84
|
+
require_paths:
|
|
85
|
+
- lib
|
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
87
|
+
requirements:
|
|
88
|
+
- - ">="
|
|
89
|
+
- !ruby/object:Gem::Version
|
|
90
|
+
version: 3.2.0
|
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0'
|
|
96
|
+
requirements: []
|
|
97
|
+
rubygems_version: 3.6.9
|
|
98
|
+
specification_version: 4
|
|
99
|
+
summary: A comprehensive Ruby client for the Zotero Web API v3
|
|
100
|
+
test_files: []
|