misarseo 1.0.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 +7 -0
- data/CHANGELOG.md +25 -0
- data/LICENSE +21 -0
- data/README.md +257 -0
- data/lib/misarseo/client.rb +29 -0
- data/lib/misarseo/errors.rb +100 -0
- data/lib/misarseo/generated/account.rb +30 -0
- data/lib/misarseo/generated/activity.rb +22 -0
- data/lib/misarseo/generated/ahrefs.rb +35 -0
- data/lib/misarseo/generated/ai_search.rb +87 -0
- data/lib/misarseo/generated/backlinks.rb +34 -0
- data/lib/misarseo/generated/baidu.rb +35 -0
- data/lib/misarseo/generated/bing.rb +95 -0
- data/lib/misarseo/generated/brave_search.rb +20 -0
- data/lib/misarseo/generated/competitor_monitor.rb +37 -0
- data/lib/misarseo/generated/competitors.rb +20 -0
- data/lib/misarseo/generated/content.rb +25 -0
- data/lib/misarseo/generated/crawl.rb +140 -0
- data/lib/misarseo/generated/dashboard.rb +20 -0
- data/lib/misarseo/generated/direct_vendor_ai.rb +20 -0
- data/lib/misarseo/generated/disavow.rb +35 -0
- data/lib/misarseo/generated/domain.rb +37 -0
- data/lib/misarseo/generated/ga4.rb +50 -0
- data/lib/misarseo/generated/google_business_profile.rb +77 -0
- data/lib/misarseo/generated/gsc.rb +70 -0
- data/lib/misarseo/generated/index_now.rb +20 -0
- data/lib/misarseo/generated/keywords.rb +71 -0
- data/lib/misarseo/generated/link_building.rb +60 -0
- data/lib/misarseo/generated/local.rb +30 -0
- data/lib/misarseo/generated/local_pack_rank.rb +40 -0
- data/lib/misarseo/generated/local_rank_grid.rb +40 -0
- data/lib/misarseo/generated/log_analyzer.rb +42 -0
- data/lib/misarseo/generated/mentions.rb +25 -0
- data/lib/misarseo/generated/patches.rb +55 -0
- data/lib/misarseo/generated/portfolios.rb +50 -0
- data/lib/misarseo/generated/projects.rb +40 -0
- data/lib/misarseo/generated/rank_tracking.rb +144 -0
- data/lib/misarseo/generated/report_builder.rb +67 -0
- data/lib/misarseo/generated/resources.rb +85 -0
- data/lib/misarseo/generated/team.rb +20 -0
- data/lib/misarseo/generated/usage.rb +20 -0
- data/lib/misarseo/generated/web_analytics.rb +35 -0
- data/lib/misarseo/generated/yandex.rb +45 -0
- data/lib/misarseo/pagination.rb +38 -0
- data/lib/misarseo/transport.rb +192 -0
- data/lib/misarseo/version.rb +5 -0
- data/lib/misarseo.rb +13 -0
- data/manifest.json +2850 -0
- metadata +114 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# AUTO-GENERATED by scripts/generate-sdks.mjs from sdks/manifest.json.
|
|
2
|
+
# Do not edit by hand: changes are overwritten. To change this surface,
|
|
3
|
+
# change the API route and its registry entry, run `pnpm sdk:sync`, then
|
|
4
|
+
# `pnpm sdk:generate`.
|
|
5
|
+
|
|
6
|
+
module MisarSEO
|
|
7
|
+
module Generated
|
|
8
|
+
# +client.rank_tracking+ — 23 endpoint(s).
|
|
9
|
+
class RankTrackingResource
|
|
10
|
+
def initialize(transport)
|
|
11
|
+
@transport = transport
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# POST /rank-tracking/annotations
|
|
15
|
+
def add_annotation(body = nil, query: nil, **fields)
|
|
16
|
+
@transport.request("POST", "/rank-tracking/annotations", query: query, body: Transport.body(body, fields))
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# POST /rank-tracking/competitors
|
|
20
|
+
def add_competitor(body = nil, query: nil, **fields)
|
|
21
|
+
@transport.request("POST", "/rank-tracking/competitors", query: query, body: Transport.body(body, fields))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# POST /rank-tracking/keywords
|
|
25
|
+
def add_keywords(body = nil, query: nil, **fields)
|
|
26
|
+
@transport.request("POST", "/rank-tracking/keywords", query: query, body: Transport.body(body, fields))
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# POST /rank-tracking/configs
|
|
30
|
+
def create(body = nil, query: nil, **fields)
|
|
31
|
+
@transport.request("POST", "/rank-tracking/configs", query: query, body: Transport.body(body, fields))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# DELETE /rank-tracking/configs
|
|
35
|
+
def delete(body = nil, query: nil, **fields)
|
|
36
|
+
@transport.request("DELETE", "/rank-tracking/configs", query: query, body: Transport.body(body, fields))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# GET /rank-tracking/keywords
|
|
40
|
+
#
|
|
41
|
+
# Optional query parameters: +page+, +pageSize+.
|
|
42
|
+
def get(config_id, project_id, query: nil)
|
|
43
|
+
@transport.request("GET", "/rank-tracking/keywords", query: Transport.merge(query, "configId" => config_id, "projectId" => project_id), body: nil)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# GET /rank-tracking/ai-overview-trend
|
|
47
|
+
#
|
|
48
|
+
# Optional query parameters: +device+, +sinceDays+.
|
|
49
|
+
def get_ai_overview_trend(config_id, project_id, query: nil)
|
|
50
|
+
@transport.request("GET", "/rank-tracking/ai-overview-trend", query: Transport.merge(query, "configId" => config_id, "projectId" => project_id), body: nil)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# GET /rank-tracking/trend
|
|
54
|
+
#
|
|
55
|
+
# Optional query parameters: +device+, +sinceDays+.
|
|
56
|
+
def get_config_trend(config_id, project_id, query: nil)
|
|
57
|
+
@transport.request("GET", "/rank-tracking/trend", query: Transport.merge(query, "configId" => config_id, "projectId" => project_id), body: nil)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# GET /rank-tracking/keyword-history
|
|
61
|
+
#
|
|
62
|
+
# Optional query parameters: +sinceDays+.
|
|
63
|
+
def get_keyword_history(config_id, project_id, tracking_keyword_id, query: nil)
|
|
64
|
+
@transport.request("GET", "/rank-tracking/keyword-history", query: Transport.merge(query, "configId" => config_id, "projectId" => project_id, "trackingKeywordId" => tracking_keyword_id), body: nil)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# GET /rank-tracking/runs
|
|
68
|
+
def get_latest_run(config_id, project_id, query: nil)
|
|
69
|
+
@transport.request("GET", "/rank-tracking/runs", query: Transport.merge(query, "configId" => config_id, "projectId" => project_id), body: nil)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# GET /rank-tracking/movers
|
|
73
|
+
#
|
|
74
|
+
# Optional query parameters: +minGain+, +sinceDays+.
|
|
75
|
+
def get_rank_movers(config_id, project_id, query: nil)
|
|
76
|
+
@transport.request("GET", "/rank-tracking/movers", query: Transport.merge(query, "configId" => config_id, "projectId" => project_id), body: nil)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# GET /rank-tracking/volatility
|
|
80
|
+
#
|
|
81
|
+
# Optional query parameters: +device+, +runLimit+.
|
|
82
|
+
def get_volatility_index(config_id, project_id, query: nil)
|
|
83
|
+
@transport.request("GET", "/rank-tracking/volatility", query: Transport.merge(query, "configId" => config_id, "projectId" => project_id), body: nil)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# GET /rank-tracking/annotations
|
|
87
|
+
def list_annotations(config_id, project_id, query: nil)
|
|
88
|
+
@transport.request("GET", "/rank-tracking/annotations", query: Transport.merge(query, "configId" => config_id, "projectId" => project_id), body: nil)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# GET /rank-tracking/cannibalisation
|
|
92
|
+
def list_cannibalisation(config_id, project_id, query: nil)
|
|
93
|
+
@transport.request("GET", "/rank-tracking/cannibalisation", query: Transport.merge(query, "configId" => config_id, "projectId" => project_id), body: nil)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# GET /rank-tracking/competitors
|
|
97
|
+
#
|
|
98
|
+
# Optional query parameters: +device+.
|
|
99
|
+
def list_competitors(config_id, project_id, query: nil)
|
|
100
|
+
@transport.request("GET", "/rank-tracking/competitors", query: Transport.merge(query, "configId" => config_id, "projectId" => project_id), body: nil)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# GET /rank-tracking/configs
|
|
104
|
+
def list_configs(project_id, query: nil)
|
|
105
|
+
@transport.request("GET", "/rank-tracking/configs", query: Transport.merge(query, "projectId" => project_id), body: nil)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# GET /rank-tracking/keyword-tags
|
|
109
|
+
def list_keyword_tags(config_id, project_id, query: nil)
|
|
110
|
+
@transport.request("GET", "/rank-tracking/keyword-tags", query: Transport.merge(query, "configId" => config_id, "projectId" => project_id), body: nil)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# DELETE /rank-tracking/annotations
|
|
114
|
+
def remove_annotation(body = nil, query: nil, **fields)
|
|
115
|
+
@transport.request("DELETE", "/rank-tracking/annotations", query: query, body: Transport.body(body, fields))
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# DELETE /rank-tracking/competitors
|
|
119
|
+
def remove_competitor(body = nil, query: nil, **fields)
|
|
120
|
+
@transport.request("DELETE", "/rank-tracking/competitors", query: query, body: Transport.body(body, fields))
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# DELETE /rank-tracking/keywords
|
|
124
|
+
def remove_keywords(body = nil, query: nil, **fields)
|
|
125
|
+
@transport.request("DELETE", "/rank-tracking/keywords", query: query, body: Transport.body(body, fields))
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# POST /rank-tracking/keyword-target-url
|
|
129
|
+
def set_keyword_target_url(body = nil, query: nil, **fields)
|
|
130
|
+
@transport.request("POST", "/rank-tracking/keyword-target-url", query: query, body: Transport.body(body, fields))
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# PATCH /rank-tracking/configs
|
|
134
|
+
def update(body = nil, query: nil, **fields)
|
|
135
|
+
@transport.request("PATCH", "/rank-tracking/configs", query: query, body: Transport.body(body, fields))
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# POST /rank-tracking/keyword-tags
|
|
139
|
+
def update_keyword_tag(body = nil, query: nil, **fields)
|
|
140
|
+
@transport.request("POST", "/rank-tracking/keyword-tags", query: query, body: Transport.body(body, fields))
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# AUTO-GENERATED by scripts/generate-sdks.mjs from sdks/manifest.json.
|
|
2
|
+
# Do not edit by hand: changes are overwritten. To change this surface,
|
|
3
|
+
# change the API route and its registry entry, run `pnpm sdk:sync`, then
|
|
4
|
+
# `pnpm sdk:generate`.
|
|
5
|
+
|
|
6
|
+
module MisarSEO
|
|
7
|
+
module Generated
|
|
8
|
+
# +client.report_builder+ — 10 endpoint(s).
|
|
9
|
+
class ReportBuilderResource
|
|
10
|
+
def initialize(transport)
|
|
11
|
+
@transport = transport
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# POST /reports/definitions
|
|
15
|
+
def create_definition(body = nil, query: nil, **fields)
|
|
16
|
+
@transport.request("POST", "/reports/definitions", query: query, body: Transport.body(body, fields))
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# DELETE /reports/definitions/:reportDefinitionId
|
|
20
|
+
def delete_definition(report_definition_id, project_id, query: nil)
|
|
21
|
+
@transport.request("DELETE", "/reports/definitions/#{Transport.seg(report_definition_id)}", query: Transport.merge(query, "projectId" => project_id), body: nil)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# DELETE /reports/definitions/:reportDefinitionId/schedule
|
|
25
|
+
def delete_schedule(report_definition_id, project_id, query: nil)
|
|
26
|
+
@transport.request("DELETE", "/reports/definitions/#{Transport.seg(report_definition_id)}/schedule", query: Transport.merge(query, "projectId" => project_id), body: nil)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# GET /reports/definitions/:reportDefinitionId
|
|
30
|
+
def get_definition(report_definition_id, project_id, query: nil)
|
|
31
|
+
@transport.request("GET", "/reports/definitions/#{Transport.seg(report_definition_id)}", query: Transport.merge(query, "projectId" => project_id), body: nil)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# GET /reports/definitions/:reportDefinitionId/schedule
|
|
35
|
+
def get_schedule(report_definition_id, project_id, query: nil)
|
|
36
|
+
@transport.request("GET", "/reports/definitions/#{Transport.seg(report_definition_id)}/schedule", query: Transport.merge(query, "projectId" => project_id), body: nil)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# GET /reports/definitions
|
|
40
|
+
def list_definitions(project_id, query: nil)
|
|
41
|
+
@transport.request("GET", "/reports/definitions", query: Transport.merge(query, "projectId" => project_id), body: nil)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# PATCH /reports/definitions/:reportDefinitionId
|
|
45
|
+
def rename_definition(report_definition_id, body = nil, query: nil, **fields)
|
|
46
|
+
@transport.request("PATCH", "/reports/definitions/#{Transport.seg(report_definition_id)}", query: query, body: Transport.body(body, fields))
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# GET /reports/definitions/:reportDefinitionId/render
|
|
50
|
+
#
|
|
51
|
+
# Optional query parameters: +periodDays+.
|
|
52
|
+
def render_definition(report_definition_id, project_id, query: nil)
|
|
53
|
+
@transport.request("GET", "/reports/definitions/#{Transport.seg(report_definition_id)}/render", query: Transport.merge(query, "projectId" => project_id), body: nil)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# POST /reports/definitions/:reportDefinitionId/widgets
|
|
57
|
+
def set_widgets(report_definition_id, body = nil, query: nil, **fields)
|
|
58
|
+
@transport.request("POST", "/reports/definitions/#{Transport.seg(report_definition_id)}/widgets", query: query, body: Transport.body(body, fields))
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# POST /reports/definitions/:reportDefinitionId/schedule
|
|
62
|
+
def upsert_schedule(report_definition_id, body = nil, query: nil, **fields)
|
|
63
|
+
@transport.request("POST", "/reports/definitions/#{Transport.seg(report_definition_id)}/schedule", query: query, body: Transport.body(body, fields))
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# AUTO-GENERATED by scripts/generate-sdks.mjs from sdks/manifest.json.
|
|
2
|
+
# Do not edit by hand: changes are overwritten. To change this surface,
|
|
3
|
+
# change the API route and its registry entry, run `pnpm sdk:sync`, then
|
|
4
|
+
# `pnpm sdk:generate`.
|
|
5
|
+
|
|
6
|
+
require_relative "account"
|
|
7
|
+
require_relative "activity"
|
|
8
|
+
require_relative "ahrefs"
|
|
9
|
+
require_relative "ai_search"
|
|
10
|
+
require_relative "backlinks"
|
|
11
|
+
require_relative "baidu"
|
|
12
|
+
require_relative "bing"
|
|
13
|
+
require_relative "brave_search"
|
|
14
|
+
require_relative "competitor_monitor"
|
|
15
|
+
require_relative "competitors"
|
|
16
|
+
require_relative "content"
|
|
17
|
+
require_relative "crawl"
|
|
18
|
+
require_relative "dashboard"
|
|
19
|
+
require_relative "direct_vendor_ai"
|
|
20
|
+
require_relative "disavow"
|
|
21
|
+
require_relative "domain"
|
|
22
|
+
require_relative "ga4"
|
|
23
|
+
require_relative "google_business_profile"
|
|
24
|
+
require_relative "gsc"
|
|
25
|
+
require_relative "index_now"
|
|
26
|
+
require_relative "keywords"
|
|
27
|
+
require_relative "link_building"
|
|
28
|
+
require_relative "local"
|
|
29
|
+
require_relative "local_pack_rank"
|
|
30
|
+
require_relative "local_rank_grid"
|
|
31
|
+
require_relative "log_analyzer"
|
|
32
|
+
require_relative "mentions"
|
|
33
|
+
require_relative "patches"
|
|
34
|
+
require_relative "portfolios"
|
|
35
|
+
require_relative "projects"
|
|
36
|
+
require_relative "rank_tracking"
|
|
37
|
+
require_relative "report_builder"
|
|
38
|
+
require_relative "team"
|
|
39
|
+
require_relative "usage"
|
|
40
|
+
require_relative "web_analytics"
|
|
41
|
+
require_relative "yandex"
|
|
42
|
+
|
|
43
|
+
module MisarSEO
|
|
44
|
+
module Generated
|
|
45
|
+
# Client attribute name => resource class.
|
|
46
|
+
RESOURCES = {
|
|
47
|
+
account: AccountResource,
|
|
48
|
+
activity: ActivityResource,
|
|
49
|
+
ahrefs: AhrefsResource,
|
|
50
|
+
ai_search: AiSearchResource,
|
|
51
|
+
backlinks: BacklinksResource,
|
|
52
|
+
baidu: BaiduResource,
|
|
53
|
+
bing: BingResource,
|
|
54
|
+
brave_search: BraveSearchResource,
|
|
55
|
+
competitor_monitor: CompetitorMonitorResource,
|
|
56
|
+
competitors: CompetitorsResource,
|
|
57
|
+
content: ContentResource,
|
|
58
|
+
crawl: CrawlResource,
|
|
59
|
+
dashboard: DashboardResource,
|
|
60
|
+
direct_vendor_ai: DirectVendorAiResource,
|
|
61
|
+
disavow: DisavowResource,
|
|
62
|
+
domain: DomainResource,
|
|
63
|
+
ga4: Ga4Resource,
|
|
64
|
+
google_business_profile: GoogleBusinessProfileResource,
|
|
65
|
+
gsc: GscResource,
|
|
66
|
+
index_now: IndexNowResource,
|
|
67
|
+
keywords: KeywordsResource,
|
|
68
|
+
link_building: LinkBuildingResource,
|
|
69
|
+
local: LocalResource,
|
|
70
|
+
local_pack_rank: LocalPackRankResource,
|
|
71
|
+
local_rank_grid: LocalRankGridResource,
|
|
72
|
+
log_analyzer: LogAnalyzerResource,
|
|
73
|
+
mentions: MentionsResource,
|
|
74
|
+
patches: PatchesResource,
|
|
75
|
+
portfolios: PortfoliosResource,
|
|
76
|
+
projects: ProjectsResource,
|
|
77
|
+
rank_tracking: RankTrackingResource,
|
|
78
|
+
report_builder: ReportBuilderResource,
|
|
79
|
+
team: TeamResource,
|
|
80
|
+
usage: UsageResource,
|
|
81
|
+
web_analytics: WebAnalyticsResource,
|
|
82
|
+
yandex: YandexResource,
|
|
83
|
+
}.freeze
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# AUTO-GENERATED by scripts/generate-sdks.mjs from sdks/manifest.json.
|
|
2
|
+
# Do not edit by hand: changes are overwritten. To change this surface,
|
|
3
|
+
# change the API route and its registry entry, run `pnpm sdk:sync`, then
|
|
4
|
+
# `pnpm sdk:generate`.
|
|
5
|
+
|
|
6
|
+
module MisarSEO
|
|
7
|
+
module Generated
|
|
8
|
+
# +client.team+ — 1 endpoint(s).
|
|
9
|
+
class TeamResource
|
|
10
|
+
def initialize(transport)
|
|
11
|
+
@transport = transport
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# GET /team/members
|
|
15
|
+
def list_members(query: nil)
|
|
16
|
+
@transport.request("GET", "/team/members", query: query, body: nil)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# AUTO-GENERATED by scripts/generate-sdks.mjs from sdks/manifest.json.
|
|
2
|
+
# Do not edit by hand: changes are overwritten. To change this surface,
|
|
3
|
+
# change the API route and its registry entry, run `pnpm sdk:sync`, then
|
|
4
|
+
# `pnpm sdk:generate`.
|
|
5
|
+
|
|
6
|
+
module MisarSEO
|
|
7
|
+
module Generated
|
|
8
|
+
# +client.usage+ — 1 endpoint(s).
|
|
9
|
+
class UsageResource
|
|
10
|
+
def initialize(transport)
|
|
11
|
+
@transport = transport
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# GET /usage
|
|
15
|
+
def get(query: nil)
|
|
16
|
+
@transport.request("GET", "/usage", query: query, body: nil)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# AUTO-GENERATED by scripts/generate-sdks.mjs from sdks/manifest.json.
|
|
2
|
+
# Do not edit by hand: changes are overwritten. To change this surface,
|
|
3
|
+
# change the API route and its registry entry, run `pnpm sdk:sync`, then
|
|
4
|
+
# `pnpm sdk:generate`.
|
|
5
|
+
|
|
6
|
+
module MisarSEO
|
|
7
|
+
module Generated
|
|
8
|
+
# +client.web_analytics+ — 4 endpoint(s).
|
|
9
|
+
class WebAnalyticsResource
|
|
10
|
+
def initialize(transport)
|
|
11
|
+
@transport = transport
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# POST /web-analytics/devices
|
|
15
|
+
def devices(body = nil, query: nil, **fields)
|
|
16
|
+
@transport.request("POST", "/web-analytics/devices", query: query, body: Transport.body(body, fields))
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# POST /web-analytics/sources
|
|
20
|
+
def sources(body = nil, query: nil, **fields)
|
|
21
|
+
@transport.request("POST", "/web-analytics/sources", query: query, body: Transport.body(body, fields))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# POST /web-analytics/summary
|
|
25
|
+
def summary(body = nil, query: nil, **fields)
|
|
26
|
+
@transport.request("POST", "/web-analytics/summary", query: query, body: Transport.body(body, fields))
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# POST /web-analytics/top-pages
|
|
30
|
+
def top_pages(body = nil, query: nil, **fields)
|
|
31
|
+
@transport.request("POST", "/web-analytics/top-pages", query: query, body: Transport.body(body, fields))
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# AUTO-GENERATED by scripts/generate-sdks.mjs from sdks/manifest.json.
|
|
2
|
+
# Do not edit by hand: changes are overwritten. To change this surface,
|
|
3
|
+
# change the API route and its registry entry, run `pnpm sdk:sync`, then
|
|
4
|
+
# `pnpm sdk:generate`.
|
|
5
|
+
|
|
6
|
+
module MisarSEO
|
|
7
|
+
module Generated
|
|
8
|
+
# +client.yandex+ — 6 endpoint(s).
|
|
9
|
+
class YandexResource
|
|
10
|
+
def initialize(transport)
|
|
11
|
+
@transport = transport
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# GET /yandex/connection
|
|
15
|
+
def connect(project_id, query: nil)
|
|
16
|
+
@transport.request("GET", "/yandex/connection", query: Transport.merge(query, "projectId" => project_id), body: nil)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# DELETE /yandex/connection
|
|
20
|
+
def disconnect(body = nil, query: nil, **fields)
|
|
21
|
+
@transport.request("DELETE", "/yandex/connection", query: query, body: Transport.body(body, fields))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# GET /yandex/hosts
|
|
25
|
+
def list_hosts(project_id, query: nil)
|
|
26
|
+
@transport.request("GET", "/yandex/hosts", query: Transport.merge(query, "projectId" => project_id), body: nil)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# POST /yandex/reindex-url
|
|
30
|
+
def reindex_url(body = nil, query: nil, **fields)
|
|
31
|
+
@transport.request("POST", "/yandex/reindex-url", query: query, body: Transport.body(body, fields))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# POST /yandex/connection
|
|
35
|
+
def set_host(body = nil, query: nil, **fields)
|
|
36
|
+
@transport.request("POST", "/yandex/connection", query: query, body: Transport.body(body, fields))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# POST /yandex/site-overview
|
|
40
|
+
def site_overview(body = nil, query: nil, **fields)
|
|
41
|
+
@transport.request("POST", "/yandex/site-overview", query: query, body: Transport.body(body, fields))
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MisarSEO
|
|
4
|
+
# The API's two pagination shapes. Offset pages (rows, hasMore, page)
|
|
5
|
+
# address rows by position; keyset pages (rows, hasMore, nextCursor) by key,
|
|
6
|
+
# so they never skip or repeat when rows are inserted mid-walk.
|
|
7
|
+
module Pagination
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
# Every row of an offset-paginated endpoint, page by page.
|
|
11
|
+
def collect_offset_pages(start_page = 1)
|
|
12
|
+
rows = []
|
|
13
|
+
page = start_page
|
|
14
|
+
loop do
|
|
15
|
+
result = yield(page) || {}
|
|
16
|
+
batch = Array(result["rows"])
|
|
17
|
+
rows.concat(batch)
|
|
18
|
+
return rows if !result["hasMore"] || batch.empty?
|
|
19
|
+
|
|
20
|
+
page += 1
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Every row following nextCursor; stops when it is absent or repeats.
|
|
25
|
+
def collect_cursor_pages(start_cursor = nil)
|
|
26
|
+
rows = []
|
|
27
|
+
cursor = start_cursor
|
|
28
|
+
loop do
|
|
29
|
+
result = yield(cursor) || {}
|
|
30
|
+
rows.concat(Array(result["rows"]))
|
|
31
|
+
nxt = result["nextCursor"]
|
|
32
|
+
return rows if !result["hasMore"] || nxt.nil? || nxt == cursor
|
|
33
|
+
|
|
34
|
+
cursor = nxt
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "net/http"
|
|
5
|
+
require "uri"
|
|
6
|
+
|
|
7
|
+
module MisarSEO
|
|
8
|
+
# One Server-Sent Events frame.
|
|
9
|
+
Event = Struct.new(:event, :data)
|
|
10
|
+
|
|
11
|
+
# Request plumbing the generated resources are written against.
|
|
12
|
+
#
|
|
13
|
+
# Retry policy, identical in every MisarSEO SDK: 429 is retried for every
|
|
14
|
+
# verb unless the body says plan_limit_exceeded; 502/503/504 and network
|
|
15
|
+
# failures are retried for GET only (a POST may have started a billed
|
|
16
|
+
# crawl); backoff starts at 250 ms with jitter and honours Retry-After up to
|
|
17
|
+
# the configured cap.
|
|
18
|
+
#
|
|
19
|
+
# +adapter+ is anything responding to
|
|
20
|
+
# +call(method, url, headers, body) -> [status, headers, body_string]+;
|
|
21
|
+
# the default is Net::HTTP. Tests inject a recorder.
|
|
22
|
+
class Transport
|
|
23
|
+
DEFAULT_BASE_URL = "https://api.misar.io/seo"
|
|
24
|
+
|
|
25
|
+
def self.seg(value)
|
|
26
|
+
URI.encode_www_form_component(value.to_s).gsub("+", "%20")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# The JSON body of a call. Ruby 3 binds a brace-less trailing hash
|
|
30
|
+
# (+create("name" => "x")+) to keyword arguments, not to the positional
|
|
31
|
+
# +body+, so every body method also takes +**fields+ and they land here.
|
|
32
|
+
# Both spellings, and +create(name: "x")+, therefore send the same body.
|
|
33
|
+
def self.body(body, fields)
|
|
34
|
+
return body if fields.empty?
|
|
35
|
+
unless body.nil? || body.is_a?(Hash)
|
|
36
|
+
raise ArgumentError, "pass the request body as one Hash or as keyword fields, not both"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
(body || {}).transform_keys(&:to_s).merge(fields.transform_keys(&:to_s))
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Required query parameters first; an explicit key in +query+ wins.
|
|
43
|
+
def self.merge(query, required)
|
|
44
|
+
required.merge((query || {}).transform_keys(&:to_s))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def initialize(api_key:, base_url: DEFAULT_BASE_URL, timeout: 30, max_retries: 2,
|
|
48
|
+
retry_base_delay: 0.25, max_retry_delay: 60, adapter: nil)
|
|
49
|
+
@api_key = api_key
|
|
50
|
+
@base_url = base_url.sub(%r{/+\z}, "")
|
|
51
|
+
@timeout = timeout
|
|
52
|
+
@max_retries = max_retries
|
|
53
|
+
@retry_base_delay = retry_base_delay
|
|
54
|
+
@max_retry_delay = max_retry_delay
|
|
55
|
+
@adapter = adapter || method(:net_http)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def headers(accept = "application/json")
|
|
59
|
+
{
|
|
60
|
+
"Authorization" => "Bearer #{@api_key}",
|
|
61
|
+
"Content-Type" => "application/json",
|
|
62
|
+
"Accept" => accept,
|
|
63
|
+
"User-Agent" => "misarseo-ruby/#{VERSION}"
|
|
64
|
+
}
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def url(path, query)
|
|
68
|
+
q = (query || {}).reject { |_, v| v.nil? }
|
|
69
|
+
return "#{@base_url}#{path}" if q.empty?
|
|
70
|
+
|
|
71
|
+
"#{@base_url}#{path}?" + q.map { |k, v| "#{Transport.seg(k)}=#{Transport.seg(v)}" }.join("&")
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def request(method, path, query: nil, body: nil)
|
|
75
|
+
target = url(path, query)
|
|
76
|
+
payload = body.nil? ? nil : JSON.generate(body)
|
|
77
|
+
attempt = 0
|
|
78
|
+
loop do
|
|
79
|
+
begin
|
|
80
|
+
status, resp_headers, raw = @adapter.call(method, target, headers, payload)
|
|
81
|
+
rescue StandardError => e
|
|
82
|
+
raise NetworkError.new(0, "NETWORK_ERROR", "could not reach the MisarSEO API: #{e.message}") \
|
|
83
|
+
unless method == "GET" && attempt < @max_retries
|
|
84
|
+
|
|
85
|
+
sleep(backoff(attempt, nil))
|
|
86
|
+
attempt += 1
|
|
87
|
+
next
|
|
88
|
+
end
|
|
89
|
+
return decode(raw) if status.between?(200, 299)
|
|
90
|
+
|
|
91
|
+
error = MisarSEO.error_from(status, resp_headers, decode(raw))
|
|
92
|
+
retryable = status == 429 || (method == "GET" && [502, 503, 504].include?(status))
|
|
93
|
+
raise error if error.is_a?(PlanLimitError) || !retryable || attempt >= @max_retries
|
|
94
|
+
|
|
95
|
+
wait = backoff(attempt, error.retry_after)
|
|
96
|
+
raise error if wait.nil?
|
|
97
|
+
|
|
98
|
+
sleep(wait)
|
|
99
|
+
attempt += 1
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# GET a text/event-stream endpoint, yielding Event frames until a terminal one.
|
|
104
|
+
def stream(path, query: nil, terminal: %w[complete error])
|
|
105
|
+
buffer = +""
|
|
106
|
+
done = false
|
|
107
|
+
@adapter.call("GET", url(path, query), headers("text/event-stream"), nil) do |status, resp_headers, chunk|
|
|
108
|
+
next if done
|
|
109
|
+
|
|
110
|
+
unless status.between?(200, 299)
|
|
111
|
+
raise MisarSEO.error_from(status, resp_headers, decode(chunk))
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
buffer << chunk.to_s.delete("\r")
|
|
115
|
+
while (idx = buffer.index("\n\n"))
|
|
116
|
+
frame = buffer.slice!(0, idx + 2)
|
|
117
|
+
event = Transport.parse_frame(frame)
|
|
118
|
+
next unless event
|
|
119
|
+
|
|
120
|
+
yield event
|
|
121
|
+
if terminal.include?(event.event)
|
|
122
|
+
done = true
|
|
123
|
+
break
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
event = Transport.parse_frame(buffer) unless done
|
|
128
|
+
yield event if event
|
|
129
|
+
nil
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Decode one SSE frame; nil for keep-alive comments and frames without an event line.
|
|
133
|
+
def self.parse_frame(frame)
|
|
134
|
+
name = nil
|
|
135
|
+
data = []
|
|
136
|
+
frame.to_s.split("\n").each do |line|
|
|
137
|
+
next if line.empty? || line.start_with?(":")
|
|
138
|
+
|
|
139
|
+
if line.start_with?("event:")
|
|
140
|
+
name = line[6..-1].strip
|
|
141
|
+
elsif line.start_with?("data:")
|
|
142
|
+
data << line[5..-1].sub(/\A +/, "")
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
return nil unless name
|
|
146
|
+
|
|
147
|
+
text = data.join("\n")
|
|
148
|
+
value = begin
|
|
149
|
+
text.empty? ? text : JSON.parse(text)
|
|
150
|
+
rescue JSON::ParserError
|
|
151
|
+
text
|
|
152
|
+
end
|
|
153
|
+
Event.new(name, value)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
private
|
|
157
|
+
|
|
158
|
+
def decode(text)
|
|
159
|
+
return nil if text.nil? || text.strip.empty?
|
|
160
|
+
|
|
161
|
+
JSON.parse(text)
|
|
162
|
+
rescue JSON::ParserError
|
|
163
|
+
text
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def backoff(attempt, retry_after)
|
|
167
|
+
return (retry_after > @max_retry_delay ? nil : retry_after) if retry_after
|
|
168
|
+
|
|
169
|
+
base = @retry_base_delay * (2**attempt)
|
|
170
|
+
[@max_retry_delay, base / 2.0 + rand * base / 2.0].min
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def net_http(method, target, headers, payload, &block)
|
|
174
|
+
uri = URI(target)
|
|
175
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
176
|
+
http.use_ssl = uri.scheme == "https"
|
|
177
|
+
http.open_timeout = @timeout
|
|
178
|
+
http.read_timeout = block ? nil : @timeout
|
|
179
|
+
klass = Net::HTTP.const_get(method.capitalize)
|
|
180
|
+
req = klass.new(uri.request_uri, headers)
|
|
181
|
+
req.body = payload if payload
|
|
182
|
+
if block
|
|
183
|
+
http.request(req) do |resp|
|
|
184
|
+
resp.read_body { |chunk| block.call(resp.code.to_i, resp.to_hash, chunk) }
|
|
185
|
+
end
|
|
186
|
+
return
|
|
187
|
+
end
|
|
188
|
+
resp = http.request(req)
|
|
189
|
+
[resp.code.to_i, resp.to_hash, resp.body]
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
data/lib/misarseo.rb
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "misarseo/version"
|
|
4
|
+
require_relative "misarseo/errors"
|
|
5
|
+
require_relative "misarseo/transport"
|
|
6
|
+
require_relative "misarseo/pagination"
|
|
7
|
+
require_relative "misarseo/generated/resources"
|
|
8
|
+
require_relative "misarseo/client"
|
|
9
|
+
|
|
10
|
+
# MisarSEO API client for Ruby: keyword research, site audits, rank tracking,
|
|
11
|
+
# backlinks, AI search visibility, Search Console, GA4 and Business Profile.
|
|
12
|
+
module MisarSEO
|
|
13
|
+
end
|