basecamp-sdk 0.25.0 → 0.27.0
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 +4 -4
- data/README.md +2 -0
- data/lib/basecamp/campfire_index.rb +19 -13
- data/lib/basecamp/client.rb +9 -0
- data/lib/basecamp/generated/metadata.json +84 -1
- data/lib/basecamp/generated/services/base_service.rb +4 -0
- data/lib/basecamp/generated/services/boosts_service.rb +10 -0
- data/lib/basecamp/generated/services/cards_service.rb +3 -2
- data/lib/basecamp/generated/services/embeds_service.rb +29 -0
- data/lib/basecamp/generated/services/pings_service.rb +41 -0
- data/lib/basecamp/generated/services/schedules_service.rb +1 -1
- data/lib/basecamp/generated/services/wormholes_service.rb +3 -3
- data/lib/basecamp/generated/types.rb +116 -1
- data/lib/basecamp/services/recordings_extensions.rb +55 -18
- data/lib/basecamp/version.rb +2 -2
- data/scripts/generate-services.rb +8 -5
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 69a94db94f0ff533b6160519f9ed6e41089ef70639d36e7042668f8ba5c819e9
|
|
4
|
+
data.tar.gz: a19a2cf4cff6180e1671e826bc6a9e93794576c05af574c0cd0ab64296a361e7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bec94397b02003677dd55ce96cb2786c4d0f8b502097f564086187c74ba767e6191b0104ca9d67eaece577664646a1f6828e877b9407e2f6c7613ad9c8c36c5d
|
|
7
|
+
data.tar.gz: 5523532f3233eecefc27ec7ca6c0048513f66fee6b53986be5458c120f83f75be65509de87eaeb22e84f2814356f30758d64fb1df392cc63dc5a76bb2bb7d8b5
|
data/README.md
CHANGED
|
@@ -348,6 +348,8 @@ The SDK provides the full account-scoped service set documented in SPEC §5. The
|
|
|
348
348
|
| `card_columns` | Card table columns |
|
|
349
349
|
| `card_steps` | Card workflow steps |
|
|
350
350
|
| `subtasks` | Subtasks (checklist items) on to-dos and cards |
|
|
351
|
+
| `pings` | Pings (direct and group chats) |
|
|
352
|
+
| `embeds` | Embeds for rich text (create, get) |
|
|
351
353
|
| `wormholes` | Card table wormholes (cross-project moves) |
|
|
352
354
|
| `lineup` | Card lineup view |
|
|
353
355
|
| `tools` | Project dock tools |
|
|
@@ -8,12 +8,15 @@ module Basecamp
|
|
|
8
8
|
# Candidates come from two sources, tried in order and each cached {TTL}
|
|
9
9
|
# seconds:
|
|
10
10
|
#
|
|
11
|
-
# 1. The bucket's project dock
|
|
12
|
-
#
|
|
13
|
-
# project.
|
|
11
|
+
# 1. The bucket's own Campfire. For a project, its dock's "chat" tool: one
|
|
12
|
+
# project read per bucket, and the answer for every line posted in a
|
|
13
|
+
# project. For a bucket that is not a project, its Ping's Campfire, since
|
|
14
|
+
# a Ping's bucket type is "Circle". Cached per bucket.
|
|
14
15
|
# 2. The account-wide Campfire listing (BC3 has no per-bucket one), filtered
|
|
15
|
-
# to the bucket, for buckets that are
|
|
16
|
-
# hold the line.
|
|
16
|
+
# to the bucket, for buckets that are neither or whose own Campfire did not
|
|
17
|
+
# hold the line. BC3 lists only dock-positioned Campfires there, so a
|
|
18
|
+
# ping's is never in it. Cached per account, so a burst of lines costs one
|
|
19
|
+
# listing.
|
|
17
20
|
#
|
|
18
21
|
# This class holds the caching only; the reads themselves are passed in as
|
|
19
22
|
# blocks by +RecordingsExtensions+, so every request still goes through a
|
|
@@ -28,7 +31,7 @@ module Basecamp
|
|
|
28
31
|
# buckets and accounts consulted within the last TTL, and never more than the
|
|
29
32
|
# bound, whatever the Client has seen.
|
|
30
33
|
class CampfireIndex
|
|
31
|
-
# How long a cached discovery source — a bucket's
|
|
34
|
+
# How long a cached discovery source — a bucket's own Campfire, the
|
|
32
35
|
# account's Campfire listing — is reused before it is read again.
|
|
33
36
|
TTL = 600.0
|
|
34
37
|
|
|
@@ -40,12 +43,13 @@ module Basecamp
|
|
|
40
43
|
|
|
41
44
|
# Caps the account-wide Campfire listing the fallback source reads. A
|
|
42
45
|
# listing that overflows it is not cached and the call reports
|
|
43
|
-
# {CampfireDiscoveryIncompleteError}: the
|
|
44
|
-
# listing only ever serves the leftover, and
|
|
45
|
-
# than this should not pay a full walk per
|
|
46
|
+
# {CampfireDiscoveryIncompleteError}: the bucket's own Campfire covers every
|
|
47
|
+
# project and every ping, so the listing only ever serves the leftover, and
|
|
48
|
+
# an account with more Campfires than this should not pay a full walk per
|
|
49
|
+
# TTL for it.
|
|
46
50
|
MAX_LISTING = 1000
|
|
47
51
|
|
|
48
|
-
# Bounds each cache's entry count. The
|
|
52
|
+
# Bounds each cache's entry count. The bucket cache holds one snapshot per
|
|
49
53
|
# bucket consulted within the TTL: a connector listening across every
|
|
50
54
|
# project an agent can see touches hundreds of buckets, not thousands, and a
|
|
51
55
|
# snapshot is a handful of ids, so this is generous headroom at a few
|
|
@@ -75,12 +79,13 @@ module Basecamp
|
|
|
75
79
|
@listings = TTLCache.new(ttl: TTL, floor: MIN_REFRESH, max_items: MAX_ITEMS, clock: clock)
|
|
76
80
|
end
|
|
77
81
|
|
|
78
|
-
# The Campfire ids a bucket's project dock
|
|
82
|
+
# The Campfire ids a bucket's own source names: a project's dock, or a
|
|
83
|
+
# Ping's room.
|
|
79
84
|
#
|
|
80
85
|
# @param account_id [String]
|
|
81
86
|
# @param bucket_id [Integer]
|
|
82
87
|
# @param refresh [Boolean] re-read a cached snapshot older than {MIN_REFRESH}
|
|
83
|
-
# @yieldreturn [Array<Integer>] the
|
|
88
|
+
# @yieldreturn [Array<Integer>] the bucket's own Campfire ids
|
|
84
89
|
# @return [SourceRead]
|
|
85
90
|
def dock_campfires(account_id:, bucket_id:, refresh: false, &load)
|
|
86
91
|
hit = @docks.get([ account_id, bucket_id ], refresh: refresh, &load)
|
|
@@ -92,7 +97,8 @@ module Basecamp
|
|
|
92
97
|
#
|
|
93
98
|
# It exists so a caller can consult what the source already holds before
|
|
94
99
|
# deciding whether to pay for the listing fetch — the expensive, slow
|
|
95
|
-
# request, which the
|
|
100
|
+
# request, which the bucket's own source (including its refresh) should get
|
|
101
|
+
# to pre-empt.
|
|
96
102
|
#
|
|
97
103
|
# @param account_id [String]
|
|
98
104
|
# @param bucket_id [Integer]
|
data/lib/basecamp/client.rb
CHANGED
|
@@ -612,6 +612,15 @@ module Basecamp
|
|
|
612
612
|
service(:subtasks) { Services::SubtasksService.new(self) }
|
|
613
613
|
end
|
|
614
614
|
|
|
615
|
+
# @return [Services::PingsService]
|
|
616
|
+
def pings
|
|
617
|
+
service(:pings) { Services::PingsService.new(self) }
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
def embeds
|
|
621
|
+
service(:embeds) { Services::EmbedsService.new(self) }
|
|
622
|
+
end
|
|
623
|
+
|
|
615
624
|
# @return [Services::AccountService]
|
|
616
625
|
def account
|
|
617
626
|
service(:account) { Services::AccountService.new(self) }
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://basecamp.com/schemas/sdk-metadata.json",
|
|
3
3
|
"version": "1.0.0",
|
|
4
|
-
"generated": "2026-10-
|
|
4
|
+
"generated": "2026-10-20T15:47:49Z",
|
|
5
5
|
"operations": {
|
|
6
6
|
"GetAccount": {
|
|
7
7
|
"retry": {
|
|
@@ -78,6 +78,20 @@
|
|
|
78
78
|
]
|
|
79
79
|
}
|
|
80
80
|
},
|
|
81
|
+
"UpdateBoost": {
|
|
82
|
+
"retry": {
|
|
83
|
+
"maxAttempts": 3,
|
|
84
|
+
"baseDelayMs": 1000,
|
|
85
|
+
"backoff": "exponential",
|
|
86
|
+
"retryOn": [
|
|
87
|
+
429,
|
|
88
|
+
503
|
|
89
|
+
]
|
|
90
|
+
},
|
|
91
|
+
"idempotent": {
|
|
92
|
+
"natural": true
|
|
93
|
+
}
|
|
94
|
+
},
|
|
81
95
|
"DeleteBoost": {
|
|
82
96
|
"retry": {
|
|
83
97
|
"maxAttempts": 3,
|
|
@@ -919,6 +933,28 @@
|
|
|
919
933
|
"maxPageSize": 50
|
|
920
934
|
}
|
|
921
935
|
},
|
|
936
|
+
"ListPings": {
|
|
937
|
+
"retry": {
|
|
938
|
+
"maxAttempts": 3,
|
|
939
|
+
"baseDelayMs": 1000,
|
|
940
|
+
"backoff": "exponential",
|
|
941
|
+
"retryOn": [
|
|
942
|
+
429,
|
|
943
|
+
503
|
|
944
|
+
]
|
|
945
|
+
}
|
|
946
|
+
},
|
|
947
|
+
"CreatePing": {
|
|
948
|
+
"retry": {
|
|
949
|
+
"maxAttempts": 2,
|
|
950
|
+
"baseDelayMs": 1000,
|
|
951
|
+
"backoff": "exponential",
|
|
952
|
+
"retryOn": [
|
|
953
|
+
429,
|
|
954
|
+
503
|
|
955
|
+
]
|
|
956
|
+
}
|
|
957
|
+
},
|
|
922
958
|
"ListPingablePeople": {
|
|
923
959
|
"retry": {
|
|
924
960
|
"maxAttempts": 3,
|
|
@@ -935,6 +971,17 @@
|
|
|
935
971
|
"maxPageSize": 50
|
|
936
972
|
}
|
|
937
973
|
},
|
|
974
|
+
"GetPing": {
|
|
975
|
+
"retry": {
|
|
976
|
+
"maxAttempts": 3,
|
|
977
|
+
"baseDelayMs": 1000,
|
|
978
|
+
"backoff": "exponential",
|
|
979
|
+
"retryOn": [
|
|
980
|
+
429,
|
|
981
|
+
503
|
|
982
|
+
]
|
|
983
|
+
}
|
|
984
|
+
},
|
|
938
985
|
"GetClientApproval": {
|
|
939
986
|
"retry": {
|
|
940
987
|
"maxAttempts": 3,
|
|
@@ -1087,6 +1134,30 @@
|
|
|
1087
1134
|
"natural": true
|
|
1088
1135
|
}
|
|
1089
1136
|
},
|
|
1137
|
+
"CreateEmbed": {
|
|
1138
|
+
"retry": {
|
|
1139
|
+
"maxAttempts": 3,
|
|
1140
|
+
"baseDelayMs": 1000,
|
|
1141
|
+
"backoff": "exponential",
|
|
1142
|
+
"retryOn": [
|
|
1143
|
+
503
|
|
1144
|
+
]
|
|
1145
|
+
},
|
|
1146
|
+
"idempotent": {
|
|
1147
|
+
"natural": true
|
|
1148
|
+
}
|
|
1149
|
+
},
|
|
1150
|
+
"GetEmbed": {
|
|
1151
|
+
"retry": {
|
|
1152
|
+
"maxAttempts": 3,
|
|
1153
|
+
"baseDelayMs": 1000,
|
|
1154
|
+
"backoff": "exponential",
|
|
1155
|
+
"retryOn": [
|
|
1156
|
+
429,
|
|
1157
|
+
503
|
|
1158
|
+
]
|
|
1159
|
+
}
|
|
1160
|
+
},
|
|
1090
1161
|
"PollEvents": {
|
|
1091
1162
|
"retry": {
|
|
1092
1163
|
"maxAttempts": 3,
|
|
@@ -3730,6 +3801,9 @@
|
|
|
3730
3801
|
"CreateMessage": [
|
|
3731
3802
|
"creator"
|
|
3732
3803
|
],
|
|
3804
|
+
"CreatePing": [
|
|
3805
|
+
"people.[]"
|
|
3806
|
+
],
|
|
3733
3807
|
"CreateQuestion": [
|
|
3734
3808
|
"creator"
|
|
3735
3809
|
],
|
|
@@ -4077,6 +4151,9 @@
|
|
|
4077
4151
|
"events.[].creator",
|
|
4078
4152
|
"person"
|
|
4079
4153
|
],
|
|
4154
|
+
"GetPing": [
|
|
4155
|
+
"people.[]"
|
|
4156
|
+
],
|
|
4080
4157
|
"GetProgressReport": [
|
|
4081
4158
|
"[].attachments.[].creator",
|
|
4082
4159
|
"[].creator"
|
|
@@ -4251,6 +4328,9 @@
|
|
|
4251
4328
|
"ListPingablePeople": [
|
|
4252
4329
|
"[]"
|
|
4253
4330
|
],
|
|
4331
|
+
"ListPings": [
|
|
4332
|
+
"[].people.[]"
|
|
4333
|
+
],
|
|
4254
4334
|
"ListProjectPeople": [
|
|
4255
4335
|
"[]"
|
|
4256
4336
|
],
|
|
@@ -4340,6 +4420,9 @@
|
|
|
4340
4420
|
"Subscribe": [
|
|
4341
4421
|
"subscribers.[]"
|
|
4342
4422
|
],
|
|
4423
|
+
"UpdateBoost": [
|
|
4424
|
+
"booster"
|
|
4425
|
+
],
|
|
4343
4426
|
"UpdateCard": [
|
|
4344
4427
|
"assignees.[]",
|
|
4345
4428
|
"completer",
|
|
@@ -162,6 +162,10 @@ module Basecamp
|
|
|
162
162
|
kwargs.compact
|
|
163
163
|
end
|
|
164
164
|
|
|
165
|
+
def path_segment(value)
|
|
166
|
+
CGI.escapeURIComponent(value.to_s)
|
|
167
|
+
end
|
|
168
|
+
|
|
165
169
|
# Like compact_params, but also drops empty arrays. For query filters an
|
|
166
170
|
# empty array means "no filter" and must be omitted entirely: Faraday's
|
|
167
171
|
# nested encoder would otherwise emit a bare `bucket_ids[]`, which Rails
|
|
@@ -16,6 +16,16 @@ module Basecamp
|
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
# Replace the content of a boost. Only the boost's creator may update it: an
|
|
20
|
+
# @param boost_id [Integer] boost id ID
|
|
21
|
+
# @param content [String] content
|
|
22
|
+
# @return [Hash] response data
|
|
23
|
+
def update_boost(boost_id:, content:)
|
|
24
|
+
with_operation(service: "boosts", operation: "update_boost", is_mutation: true, resource_id: boost_id) do
|
|
25
|
+
http_put("/boosts/#{boost_id}", body: compact_params(content: content)).json(operation: "UpdateBoost")
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
19
29
|
# Delete a boost
|
|
20
30
|
# @param boost_id [Integer] boost id ID
|
|
21
31
|
# @return [void]
|
|
@@ -29,9 +29,10 @@ module Basecamp
|
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
-
# Move a card to a
|
|
32
|
+
# Move a card to any column in its project, including on another card table there, or through a linked wormhole to another project (queued: the card gets a new id, and position, if sent, must still be positive but doesn't set placement).
|
|
33
33
|
# @param card_id [Integer] card id ID
|
|
34
|
-
# @param column_id [Integer] column
|
|
34
|
+
# @param column_id [Integer] Any column in the card's project, on this or another card table, or a
|
|
35
|
+
# wormhole id to send the card to another project.
|
|
35
36
|
# @param position [Integer, nil] 1-indexed position within the destination column. Defaults to 1 (top).
|
|
36
37
|
# @return [void]
|
|
37
38
|
def move(card_id:, column_id:, position: nil)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Basecamp
|
|
4
|
+
module Services
|
|
5
|
+
# Service for Embeds operations
|
|
6
|
+
#
|
|
7
|
+
# @generated from OpenAPI spec
|
|
8
|
+
class EmbedsService < BaseService
|
|
9
|
+
|
|
10
|
+
# Create an embed for a URL, or return the one Basecamp already has for it.
|
|
11
|
+
# @param url [String] The URL of the content to embed.
|
|
12
|
+
# @return [Hash] response data
|
|
13
|
+
def create(url:)
|
|
14
|
+
with_operation(service: "embeds", operation: "create", is_mutation: true) do
|
|
15
|
+
http_post("/embeds.json", body: compact_params(url: url)).json(operation: "CreateEmbed")
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Get an embed by its sgid.
|
|
20
|
+
# @param sgid [String] An embed's signed global id, an opaque string.
|
|
21
|
+
# @return [Hash] response data
|
|
22
|
+
def get(sgid:)
|
|
23
|
+
with_operation(service: "embeds", operation: "get", is_mutation: false) do
|
|
24
|
+
http_get("/embeds/#{path_segment(sgid)}", operation: "GetEmbed").json(operation: "GetEmbed")
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Basecamp
|
|
4
|
+
module Services
|
|
5
|
+
# Service for Pings operations
|
|
6
|
+
#
|
|
7
|
+
# @generated from OpenAPI spec
|
|
8
|
+
class PingsService < BaseService
|
|
9
|
+
|
|
10
|
+
# Get Pings: the caller's active Pings that have a line in them, most
|
|
11
|
+
# @return [Array<Hash>] response data
|
|
12
|
+
def list()
|
|
13
|
+
with_operation(service: "pings", operation: "list", is_mutation: false) do
|
|
14
|
+
http_get("/circles.json", operation: "ListPings").json(operation: "ListPings")
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Create a Ping with people, or return the one that already holds exactly
|
|
19
|
+
# @param user_ids [Array] Person ids of the people to ping. People the caller cannot reach are
|
|
20
|
+
# dropped.
|
|
21
|
+
# @param new_group [Boolean, nil] Create a new group even when a Ping with exactly these people exists.
|
|
22
|
+
# @return [Hash] response data
|
|
23
|
+
def create(user_ids:, new_group: nil)
|
|
24
|
+
with_operation(service: "pings", operation: "create", is_mutation: true) do
|
|
25
|
+
http_post("/circles.json", body: compact_params(user_ids: user_ids, new_group: new_group)).json(operation: "CreatePing")
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Get a Ping by its id, which is its bucket's id.
|
|
30
|
+
# @param ping_id [Integer] A Ping's id is the id of the bucket that holds it (the bucket type is
|
|
31
|
+
# "Circle", which is also the wire path), so the `bucket.id` a Ping's
|
|
32
|
+
# recordings carry is the id GetPing takes.
|
|
33
|
+
# @return [Hash] response data
|
|
34
|
+
def get(ping_id:)
|
|
35
|
+
with_operation(service: "pings", operation: "get", is_mutation: false, resource_id: ping_id) do
|
|
36
|
+
http_get("/circles/#{ping_id}", operation: "GetPing").json(operation: "GetPing")
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -72,7 +72,7 @@ module Basecamp
|
|
|
72
72
|
# @return [Hash] response data
|
|
73
73
|
def get_entry_occurrence(entry_id:, date:)
|
|
74
74
|
with_operation(service: "schedules", operation: "get_entry_occurrence", is_mutation: false, resource_id: entry_id) do
|
|
75
|
-
http_get("/schedule_entries/#{entry_id}/occurrences/#{date}", operation: "GetScheduleEntryOccurrence").json(operation: "GetScheduleEntryOccurrence")
|
|
75
|
+
http_get("/schedule_entries/#{entry_id}/occurrences/#{path_segment(date)}", operation: "GetScheduleEntryOccurrence").json(operation: "GetScheduleEntryOccurrence")
|
|
76
76
|
end
|
|
77
77
|
end
|
|
78
78
|
|
|
@@ -7,7 +7,7 @@ module Basecamp
|
|
|
7
7
|
# @generated from OpenAPI spec
|
|
8
8
|
class WormholesService < BaseService
|
|
9
9
|
|
|
10
|
-
#
|
|
10
|
+
# Point a card table's wormhole, its shortcut for sending cards to another card table, at a different destination column
|
|
11
11
|
# @param bucket_id [Integer] bucket id ID
|
|
12
12
|
# @param wormhole_id [Integer] wormhole id ID
|
|
13
13
|
# @param destination_recording_id [Integer] Id of the new destination column (on another accessible card table).
|
|
@@ -18,7 +18,7 @@ module Basecamp
|
|
|
18
18
|
end
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
#
|
|
21
|
+
# Permanently delete a card table's wormhole, the shortcut that sends cards to another card table.
|
|
22
22
|
# @param bucket_id [Integer] bucket id ID
|
|
23
23
|
# @param wormhole_id [Integer] wormhole id ID
|
|
24
24
|
# @return [void]
|
|
@@ -29,7 +29,7 @@ module Basecamp
|
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
-
#
|
|
32
|
+
# Add a wormhole to a card table: a standing shortcut that sends cards to a column on another card table, usually in another project.
|
|
33
33
|
# @param bucket_id [Integer] bucket id ID
|
|
34
34
|
# @param card_table_id [Integer] card table id ID
|
|
35
35
|
# @param destination_recording_id [Integer] Id of the destination column (on another accessible card table) to link to.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Auto-generated from OpenAPI spec. Do not edit manually.
|
|
4
|
-
# Generated: 2026-10-
|
|
4
|
+
# Generated: 2026-10-20T15:47:49Z
|
|
5
5
|
|
|
6
6
|
require "json"
|
|
7
7
|
require "time"
|
|
@@ -1685,6 +1685,43 @@ module Basecamp
|
|
|
1685
1685
|
end
|
|
1686
1686
|
end
|
|
1687
1687
|
|
|
1688
|
+
# Embed
|
|
1689
|
+
class Embed
|
|
1690
|
+
include TypeHelpers
|
|
1691
|
+
attr_accessor :sgid, :status, :url, :content, :content_type, :image_url, :type
|
|
1692
|
+
|
|
1693
|
+
# @return [Array<Symbol>]
|
|
1694
|
+
def self.required_fields
|
|
1695
|
+
%i[sgid status url].freeze
|
|
1696
|
+
end
|
|
1697
|
+
|
|
1698
|
+
def initialize(data = {})
|
|
1699
|
+
@sgid = data["sgid"]
|
|
1700
|
+
@status = data["status"]
|
|
1701
|
+
@url = data["url"]
|
|
1702
|
+
@content = data["content"]
|
|
1703
|
+
@content_type = data["content_type"]
|
|
1704
|
+
@image_url = data["image_url"]
|
|
1705
|
+
@type = data["type"]
|
|
1706
|
+
end
|
|
1707
|
+
|
|
1708
|
+
def to_h
|
|
1709
|
+
{
|
|
1710
|
+
"sgid" => @sgid,
|
|
1711
|
+
"status" => @status,
|
|
1712
|
+
"url" => @url,
|
|
1713
|
+
"content" => @content,
|
|
1714
|
+
"content_type" => @content_type,
|
|
1715
|
+
"image_url" => @image_url,
|
|
1716
|
+
"type" => @type,
|
|
1717
|
+
}.compact
|
|
1718
|
+
end
|
|
1719
|
+
|
|
1720
|
+
def to_json(*args)
|
|
1721
|
+
to_h.to_json(*args)
|
|
1722
|
+
end
|
|
1723
|
+
end
|
|
1724
|
+
|
|
1688
1725
|
# Event
|
|
1689
1726
|
class Event
|
|
1690
1727
|
include TypeHelpers
|
|
@@ -3208,6 +3245,84 @@ module Basecamp
|
|
|
3208
3245
|
end
|
|
3209
3246
|
end
|
|
3210
3247
|
|
|
3248
|
+
# Ping
|
|
3249
|
+
class Ping
|
|
3250
|
+
include TypeHelpers
|
|
3251
|
+
attr_accessor :app_url, :avatar_url, :bookmark_url, :created_at, :id, :links, :named, :people, :room_url, :title, :updated_at, :url, :edit_title_url, :image_url
|
|
3252
|
+
|
|
3253
|
+
# @return [Array<Symbol>]
|
|
3254
|
+
def self.required_fields
|
|
3255
|
+
%i[app_url avatar_url bookmark_url created_at id links named people room_url title updated_at url].freeze
|
|
3256
|
+
end
|
|
3257
|
+
|
|
3258
|
+
def initialize(data = {})
|
|
3259
|
+
@app_url = data["app_url"]
|
|
3260
|
+
@avatar_url = data["avatar_url"]
|
|
3261
|
+
@bookmark_url = data["bookmark_url"]
|
|
3262
|
+
@created_at = parse_datetime(data["created_at"])
|
|
3263
|
+
@id = parse_integer(data["id"])
|
|
3264
|
+
@links = parse_type(data["links"], "PingLinks")
|
|
3265
|
+
@named = parse_boolean(data["named"])
|
|
3266
|
+
@people = parse_array(data["people"], "Person")
|
|
3267
|
+
@room_url = data["room_url"]
|
|
3268
|
+
@title = data["title"]
|
|
3269
|
+
@updated_at = parse_datetime(data["updated_at"])
|
|
3270
|
+
@url = data["url"]
|
|
3271
|
+
@edit_title_url = data["edit_title_url"]
|
|
3272
|
+
@image_url = data["image_url"]
|
|
3273
|
+
end
|
|
3274
|
+
|
|
3275
|
+
def to_h
|
|
3276
|
+
{
|
|
3277
|
+
"app_url" => @app_url,
|
|
3278
|
+
"avatar_url" => @avatar_url,
|
|
3279
|
+
"bookmark_url" => @bookmark_url,
|
|
3280
|
+
"created_at" => @created_at,
|
|
3281
|
+
"id" => @id,
|
|
3282
|
+
"links" => @links,
|
|
3283
|
+
"named" => @named,
|
|
3284
|
+
"people" => @people,
|
|
3285
|
+
"room_url" => @room_url,
|
|
3286
|
+
"title" => @title,
|
|
3287
|
+
"updated_at" => @updated_at,
|
|
3288
|
+
"url" => @url,
|
|
3289
|
+
"edit_title_url" => @edit_title_url,
|
|
3290
|
+
"image_url" => @image_url,
|
|
3291
|
+
}.compact
|
|
3292
|
+
end
|
|
3293
|
+
|
|
3294
|
+
def to_json(*args)
|
|
3295
|
+
to_h.to_json(*args)
|
|
3296
|
+
end
|
|
3297
|
+
end
|
|
3298
|
+
|
|
3299
|
+
# PingLinks
|
|
3300
|
+
class PingLinks
|
|
3301
|
+
include TypeHelpers
|
|
3302
|
+
attr_accessor :files, :lines
|
|
3303
|
+
|
|
3304
|
+
# @return [Array<Symbol>]
|
|
3305
|
+
def self.required_fields
|
|
3306
|
+
%i[files lines].freeze
|
|
3307
|
+
end
|
|
3308
|
+
|
|
3309
|
+
def initialize(data = {})
|
|
3310
|
+
@files = data["files"]
|
|
3311
|
+
@lines = data["lines"]
|
|
3312
|
+
end
|
|
3313
|
+
|
|
3314
|
+
def to_h
|
|
3315
|
+
{
|
|
3316
|
+
"files" => @files,
|
|
3317
|
+
"lines" => @lines,
|
|
3318
|
+
}.compact
|
|
3319
|
+
end
|
|
3320
|
+
|
|
3321
|
+
def to_json(*args)
|
|
3322
|
+
to_h.to_json(*args)
|
|
3323
|
+
end
|
|
3324
|
+
end
|
|
3325
|
+
|
|
3211
3326
|
# Preferences
|
|
3212
3327
|
class Preferences
|
|
3213
3328
|
include TypeHelpers
|
|
@@ -49,6 +49,8 @@ module Basecamp
|
|
|
49
49
|
"The recording summary reads this field to decide what the recording is and which " \
|
|
50
50
|
"project it belongs to, so a value of the wrong type cannot be used."
|
|
51
51
|
|
|
52
|
+
ROOM_URL_CAMPFIRE = %r{/chats/([0-9]+)(?:\.json)?\z}
|
|
53
|
+
|
|
52
54
|
DISCOVERY_HINT =
|
|
53
55
|
"The recording summary reads this field to decide which Campfires a chat line could " \
|
|
54
56
|
"be in, so a value of the wrong type cannot be used."
|
|
@@ -133,10 +135,10 @@ module Basecamp
|
|
|
133
135
|
RICH_TEXT_CHAT_LINE_TYPES = [ "Chat::Lines::RichText", "Chat::Lines::Integration" ].freeze
|
|
134
136
|
|
|
135
137
|
# Bounds how many Campfires one {#summarize} call tries, across both
|
|
136
|
-
# discovery sources and the refresh. A project
|
|
137
|
-
#
|
|
138
|
-
#
|
|
139
|
-
#
|
|
138
|
+
# discovery sources and the refresh. A project or a Ping has one
|
|
139
|
+
# Campfire; a bucket past this bound is not a shape BC3 produces, and the
|
|
140
|
+
# call reports {Basecamp::CampfireDiscoveryIncompleteError} rather than
|
|
141
|
+
# calling the rest absent.
|
|
140
142
|
MAX_CAMPFIRE_CANDIDATES = 50
|
|
141
143
|
|
|
142
144
|
# Where #project leaves the validated bucket id for the cross-bucket
|
|
@@ -1029,14 +1031,14 @@ module Basecamp
|
|
|
1029
1031
|
account_id = @client.account_id
|
|
1030
1032
|
search = ChatLineSearch.new(campfires: @client.campfires, line_id: line_id)
|
|
1031
1033
|
|
|
1032
|
-
# Pass 1: what the sources already hold — the
|
|
1033
|
-
# then the listing only if it is cached. A listing
|
|
1034
|
-
# expensive, slow request, and it is not made until the
|
|
1035
|
-
# its refresh — has had its say, so a
|
|
1036
|
-
# or stalled never stands between a
|
|
1037
|
-
# read that finds it.
|
|
1034
|
+
# Pass 1: what the sources already hold — the bucket's own Campfire
|
|
1035
|
+
# (read if it must be), then the listing only if it is cached. A listing
|
|
1036
|
+
# fetch is the expensive, slow request, and it is not made until the
|
|
1037
|
+
# bucket's own source — including its refresh — has had its say, so a
|
|
1038
|
+
# listing that is down, over its cap, or stalled never stands between a
|
|
1039
|
+
# project's line and the one project read that finds it.
|
|
1038
1040
|
dock = index.dock_campfires(account_id: account_id, bucket_id: bucket_id) do
|
|
1039
|
-
|
|
1041
|
+
bucket_campfire_ids(bucket_id)
|
|
1040
1042
|
end
|
|
1041
1043
|
found = search.try(dock.ids)
|
|
1042
1044
|
return found if found
|
|
@@ -1048,7 +1050,7 @@ module Basecamp
|
|
|
1048
1050
|
return found if found
|
|
1049
1051
|
end
|
|
1050
1052
|
|
|
1051
|
-
# Pass 2: re-read the
|
|
1053
|
+
# Pass 2: re-read the bucket's own source if it was served from cache (the floor may
|
|
1052
1054
|
# decline), then fetch or refresh the listing. Whatever comes back is the
|
|
1053
1055
|
# current snapshot of that source, whoever loaded it — another caller may
|
|
1054
1056
|
# have populated or refreshed it in the meantime — so it always replaces
|
|
@@ -1071,7 +1073,7 @@ module Basecamp
|
|
|
1071
1073
|
|
|
1072
1074
|
if search.budget_left? && dock.cached?
|
|
1073
1075
|
again = index.dock_campfires(account_id: account_id, bucket_id: bucket_id, refresh: true) do
|
|
1074
|
-
|
|
1076
|
+
bucket_campfire_ids(bucket_id)
|
|
1075
1077
|
end
|
|
1076
1078
|
refreshed = true if again.fetched > dock.fetched || !again.cached?
|
|
1077
1079
|
dock = again
|
|
@@ -1119,17 +1121,52 @@ module Basecamp
|
|
|
1119
1121
|
)
|
|
1120
1122
|
end
|
|
1121
1123
|
|
|
1122
|
-
# The Campfire ids a
|
|
1123
|
-
#
|
|
1124
|
-
# read is raised
|
|
1125
|
-
|
|
1124
|
+
# The bucket's own Campfire ids: a project's dock "chat" tools or, when
|
|
1125
|
+
# the project read 404s, its Ping's Campfire. A bucket that is neither
|
|
1126
|
+
# has none; any other failure of either read is raised, so a 403 "Pings
|
|
1127
|
+
# have been disabled" is a failed read and never an unresolved line.
|
|
1128
|
+
def bucket_campfire_ids(bucket_id)
|
|
1126
1129
|
project = begin
|
|
1127
1130
|
@client.projects.get(project_id: bucket_id)
|
|
1131
|
+
rescue NotFoundError
|
|
1132
|
+
return ping_campfire_ids(bucket_id)
|
|
1133
|
+
end
|
|
1134
|
+
|
|
1135
|
+
dock_campfire_ids(read_record(project))
|
|
1136
|
+
end
|
|
1137
|
+
|
|
1138
|
+
# The one Campfire a Ping holds, named by its +room_url+. A Ping whose
|
|
1139
|
+
# room_url names no Campfire has none.
|
|
1140
|
+
def ping_campfire_ids(bucket_id)
|
|
1141
|
+
ping = begin
|
|
1142
|
+
@client.pings.get(ping_id: bucket_id)
|
|
1128
1143
|
rescue NotFoundError
|
|
1129
1144
|
return []
|
|
1130
1145
|
end
|
|
1131
1146
|
|
|
1132
|
-
|
|
1147
|
+
room_url = read_record(ping)["room_url"]
|
|
1148
|
+
return [] if room_url.nil?
|
|
1149
|
+
|
|
1150
|
+
unless room_url.is_a?(String)
|
|
1151
|
+
raise malformed_response("the Ping's \"room_url\" is #{MergeSafe.describe(room_url)}, not a string",
|
|
1152
|
+
hint: DISCOVERY_HINT)
|
|
1153
|
+
end
|
|
1154
|
+
|
|
1155
|
+
id = campfire_id_from_room_url(room_url)
|
|
1156
|
+
id ? [ id ] : []
|
|
1157
|
+
end
|
|
1158
|
+
|
|
1159
|
+
# Strict: the path must end in +/chats/{digits}+, optionally +.json+, so a
|
|
1160
|
+
# Campfire's lines, a to-do or a garbled URL name no Campfire.
|
|
1161
|
+
def campfire_id_from_room_url(room_url)
|
|
1162
|
+
match = ROOM_URL_CAMPFIRE.match(room_url.split(/[?#]/, 2).first)
|
|
1163
|
+
return nil if match.nil?
|
|
1164
|
+
|
|
1165
|
+
id = Ids.bounded_decimal(match[1], signed: false)
|
|
1166
|
+
id.is_a?(Integer) && id.positive? ? id : nil
|
|
1167
|
+
end
|
|
1168
|
+
|
|
1169
|
+
def dock_campfire_ids(project)
|
|
1133
1170
|
dock = project["dock"]
|
|
1134
1171
|
# An absent dock is genuinely none — a project need not have one, and the
|
|
1135
1172
|
# reference reads its zero value. Anything else that is not an array is a
|
data/lib/basecamp/version.rb
CHANGED
|
@@ -83,6 +83,8 @@ class ServiceGenerator
|
|
|
83
83
|
'ClientFeatures' => 'ClientFeatures',
|
|
84
84
|
'Boosts' => 'Boosts',
|
|
85
85
|
'Subtasks' => 'Subtasks',
|
|
86
|
+
'Pings' => 'Pings',
|
|
87
|
+
'Embeds' => 'Embeds',
|
|
86
88
|
'Untagged' => 'Miscellaneous'
|
|
87
89
|
}.freeze
|
|
88
90
|
|
|
@@ -363,8 +365,8 @@ class ServiceGenerator
|
|
|
363
365
|
lineupmarker clientapproval clientapprovals clientcorrespondence
|
|
364
366
|
clientcorrespondences clientreply clientreplies forwardreply
|
|
365
367
|
forwardreplies campfireline campfirelines todolistgroup todolistgroups
|
|
366
|
-
todolistorgroup uploadversions hillchart hillcharts subtask subtasks
|
|
367
|
-
wormhole wormholes
|
|
368
|
+
todolistorgroup uploadversions hillchart hillcharts subtask subtasks ping pings
|
|
369
|
+
wormhole wormholes embed embeds
|
|
368
370
|
].freeze
|
|
369
371
|
|
|
370
372
|
def initialize(openapi_path)
|
|
@@ -570,7 +572,7 @@ class ServiceGenerator
|
|
|
570
572
|
operation_id: operation_id,
|
|
571
573
|
method_name: method_name,
|
|
572
574
|
http_method: http_method,
|
|
573
|
-
path: convert_path(path),
|
|
575
|
+
path: convert_path(path, path_params),
|
|
574
576
|
description: description,
|
|
575
577
|
path_params: path_params,
|
|
576
578
|
query_params: query_params,
|
|
@@ -678,14 +680,15 @@ class ServiceGenerator
|
|
|
678
680
|
SIMPLE_RESOURCES.include?(resource.downcase.gsub('_', ''))
|
|
679
681
|
end
|
|
680
682
|
|
|
681
|
-
def convert_path(path)
|
|
683
|
+
def convert_path(path, path_params = [])
|
|
682
684
|
# Remove /{accountId} prefix
|
|
683
685
|
path = path.sub(%r{^/\{accountId\}}, '')
|
|
686
|
+
string_params = path_params.select { |p| p[:type] == 'String' }.map { |p| p[:name] }
|
|
684
687
|
# Convert {camelCaseParam} to #{snake_case_param}
|
|
685
688
|
path.gsub(/\{(\w+)\}/) do |_match|
|
|
686
689
|
param = ::Regexp.last_match(1)
|
|
687
690
|
snake_param = to_snake_case(param)
|
|
688
|
-
"\#{#{snake_param}}"
|
|
691
|
+
string_params.include?(param) ? "\#{path_segment(#{snake_param})}" : "\#{#{snake_param}}"
|
|
689
692
|
end
|
|
690
693
|
end
|
|
691
694
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: basecamp-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.27.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Basecamp
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|
|
@@ -202,6 +202,7 @@ files:
|
|
|
202
202
|
- lib/basecamp/generated/services/comments_service.rb
|
|
203
203
|
- lib/basecamp/generated/services/documents_service.rb
|
|
204
204
|
- lib/basecamp/generated/services/drafts_service.rb
|
|
205
|
+
- lib/basecamp/generated/services/embeds_service.rb
|
|
205
206
|
- lib/basecamp/generated/services/event_feed_service.rb
|
|
206
207
|
- lib/basecamp/generated/services/events_service.rb
|
|
207
208
|
- lib/basecamp/generated/services/everything_service.rb
|
|
@@ -218,6 +219,7 @@ files:
|
|
|
218
219
|
- lib/basecamp/generated/services/my_notes_service.rb
|
|
219
220
|
- lib/basecamp/generated/services/my_notifications_service.rb
|
|
220
221
|
- lib/basecamp/generated/services/people_service.rb
|
|
222
|
+
- lib/basecamp/generated/services/pings_service.rb
|
|
221
223
|
- lib/basecamp/generated/services/projects_service.rb
|
|
222
224
|
- lib/basecamp/generated/services/recordings_service.rb
|
|
223
225
|
- lib/basecamp/generated/services/reports_service.rb
|