direct-cli 0.0.0__py3-none-any.whl
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.
- direct_cli/__init__.py +14 -0
- direct_cli/api.py +94 -0
- direct_cli/auth.py +58 -0
- direct_cli/cli.py +85 -0
- direct_cli/commands/__init__.py +61 -0
- direct_cli/commands/adextensions.py +96 -0
- direct_cli/commands/adgroups.py +189 -0
- direct_cli/commands/adimages.py +63 -0
- direct_cli/commands/ads.py +306 -0
- direct_cli/commands/agencyclients.py +64 -0
- direct_cli/commands/audiencetargets.py +187 -0
- direct_cli/commands/bidmodifiers.py +110 -0
- direct_cli/commands/bids.py +108 -0
- direct_cli/commands/businesses.py +61 -0
- direct_cli/commands/campaigns.py +311 -0
- direct_cli/commands/changes.py +97 -0
- direct_cli/commands/clients.py +98 -0
- direct_cli/commands/creatives.py +68 -0
- direct_cli/commands/dictionaries.py +64 -0
- direct_cli/commands/dynamicads.py +104 -0
- direct_cli/commands/feeds.py +99 -0
- direct_cli/commands/keywordbids.py +111 -0
- direct_cli/commands/keywords.py +309 -0
- direct_cli/commands/keywordsresearch.py +71 -0
- direct_cli/commands/leads.py +65 -0
- direct_cli/commands/negativekeywordsharedsets.py +97 -0
- direct_cli/commands/reports.py +128 -0
- direct_cli/commands/retargeting.py +104 -0
- direct_cli/commands/sitelinks.py +92 -0
- direct_cli/commands/smartadtargets.py +104 -0
- direct_cli/commands/turbopages.py +97 -0
- direct_cli/commands/vcards.py +93 -0
- direct_cli/output.py +143 -0
- direct_cli/utils.py +120 -0
- direct_cli-0.0.0.dist-info/METADATA +393 -0
- direct_cli-0.0.0.dist-info/RECORD +39 -0
- direct_cli-0.0.0.dist-info/WHEEL +5 -0
- direct_cli-0.0.0.dist-info/entry_points.txt +2 -0
- direct_cli-0.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Keywords commands
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import click
|
|
7
|
+
|
|
8
|
+
from ..api import create_client
|
|
9
|
+
from ..output import format_output, print_error
|
|
10
|
+
from ..utils import parse_ids, get_default_fields
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.group()
|
|
14
|
+
def keywords():
|
|
15
|
+
"""Manage keywords"""
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@keywords.command()
|
|
20
|
+
@click.option("--ids", help="Comma-separated keyword IDs")
|
|
21
|
+
@click.option("--adgroup-ids", help="Comma-separated ad group IDs")
|
|
22
|
+
@click.option("--campaign-ids", help="Comma-separated campaign IDs")
|
|
23
|
+
@click.option("--status", help="Filter by status")
|
|
24
|
+
@click.option("--limit", type=int, help="Limit number of results")
|
|
25
|
+
@click.option("--fetch-all", is_flag=True, help="Fetch all pages")
|
|
26
|
+
@click.option("--format", "output_format", default="json", help="Output format")
|
|
27
|
+
@click.option("--output", help="Output file")
|
|
28
|
+
@click.option("--fields", help="Comma-separated field names")
|
|
29
|
+
@click.pass_context
|
|
30
|
+
def get(
|
|
31
|
+
ctx,
|
|
32
|
+
ids,
|
|
33
|
+
adgroup_ids,
|
|
34
|
+
campaign_ids,
|
|
35
|
+
status,
|
|
36
|
+
limit,
|
|
37
|
+
fetch_all,
|
|
38
|
+
output_format,
|
|
39
|
+
output,
|
|
40
|
+
fields,
|
|
41
|
+
):
|
|
42
|
+
"""Get keywords"""
|
|
43
|
+
try:
|
|
44
|
+
client = create_client(
|
|
45
|
+
token=ctx.obj.get("token"),
|
|
46
|
+
login=ctx.obj.get("login"),
|
|
47
|
+
sandbox=ctx.obj.get("sandbox"),
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
field_names = fields.split(",") if fields else get_default_fields("keywords")
|
|
51
|
+
|
|
52
|
+
criteria = {}
|
|
53
|
+
if ids:
|
|
54
|
+
criteria["Ids"] = parse_ids(ids)
|
|
55
|
+
if adgroup_ids:
|
|
56
|
+
criteria["AdGroupIds"] = parse_ids(adgroup_ids)
|
|
57
|
+
if campaign_ids:
|
|
58
|
+
criteria["CampaignIds"] = parse_ids(campaign_ids)
|
|
59
|
+
if status:
|
|
60
|
+
criteria["Statuses"] = [status]
|
|
61
|
+
|
|
62
|
+
params = {"SelectionCriteria": criteria, "FieldNames": field_names}
|
|
63
|
+
|
|
64
|
+
if limit:
|
|
65
|
+
params["Page"] = {"Limit": limit}
|
|
66
|
+
|
|
67
|
+
body = {"method": "get", "params": params}
|
|
68
|
+
|
|
69
|
+
result = client.keywords().post(data=body)
|
|
70
|
+
|
|
71
|
+
if fetch_all:
|
|
72
|
+
items = []
|
|
73
|
+
for item in result().iter_items():
|
|
74
|
+
items.append(item)
|
|
75
|
+
format_output(items, output_format, output)
|
|
76
|
+
else:
|
|
77
|
+
data = result().extract()
|
|
78
|
+
format_output(data, output_format, output)
|
|
79
|
+
|
|
80
|
+
except Exception as e:
|
|
81
|
+
print_error(str(e))
|
|
82
|
+
raise click.Abort()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@keywords.command()
|
|
86
|
+
@click.option("--adgroup-id", required=True, type=int, help="Ad group ID")
|
|
87
|
+
@click.option("--keyword", required=True, help="Keyword text")
|
|
88
|
+
@click.option("--bid", type=float, help="Search bid")
|
|
89
|
+
@click.option("--context-bid", type=float, help="Context bid")
|
|
90
|
+
@click.option("--user-param-1", help="User parameter 1")
|
|
91
|
+
@click.option("--user-param-2", help="User parameter 2")
|
|
92
|
+
@click.option("--json", "extra_json", help="Additional JSON parameters")
|
|
93
|
+
@click.option("--dry-run", is_flag=True, help="Show request without sending")
|
|
94
|
+
@click.pass_context
|
|
95
|
+
def add(
|
|
96
|
+
ctx,
|
|
97
|
+
adgroup_id,
|
|
98
|
+
keyword,
|
|
99
|
+
bid,
|
|
100
|
+
context_bid,
|
|
101
|
+
user_param_1,
|
|
102
|
+
user_param_2,
|
|
103
|
+
extra_json,
|
|
104
|
+
dry_run,
|
|
105
|
+
):
|
|
106
|
+
"""Add new keyword"""
|
|
107
|
+
try:
|
|
108
|
+
keyword_data = {"AdGroupId": adgroup_id, "Keyword": keyword}
|
|
109
|
+
|
|
110
|
+
if bid:
|
|
111
|
+
keyword_data["Bid"] = int(bid * 1000000)
|
|
112
|
+
if context_bid:
|
|
113
|
+
keyword_data["ContextBid"] = int(context_bid * 1000000)
|
|
114
|
+
if user_param_1:
|
|
115
|
+
keyword_data["UserParam1"] = user_param_1
|
|
116
|
+
if user_param_2:
|
|
117
|
+
keyword_data["UserParam2"] = user_param_2
|
|
118
|
+
|
|
119
|
+
if extra_json:
|
|
120
|
+
extra = json.loads(extra_json)
|
|
121
|
+
keyword_data.update(extra)
|
|
122
|
+
|
|
123
|
+
body = {"method": "add", "params": {"Keywords": [keyword_data]}}
|
|
124
|
+
|
|
125
|
+
if dry_run:
|
|
126
|
+
format_output(body, "json", None)
|
|
127
|
+
return
|
|
128
|
+
|
|
129
|
+
client = create_client(
|
|
130
|
+
token=ctx.obj.get("token"),
|
|
131
|
+
login=ctx.obj.get("login"),
|
|
132
|
+
sandbox=ctx.obj.get("sandbox"),
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
result = client.keywords().post(data=body)
|
|
136
|
+
format_output(result().extract(), "json", None)
|
|
137
|
+
|
|
138
|
+
except Exception as e:
|
|
139
|
+
print_error(str(e))
|
|
140
|
+
raise click.Abort()
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@keywords.command()
|
|
144
|
+
@click.option("--id", "keyword_id", required=True, type=int, help="Keyword ID")
|
|
145
|
+
@click.option("--bid", type=float, help="Search bid")
|
|
146
|
+
@click.option("--context-bid", type=float, help="Context bid")
|
|
147
|
+
@click.option("--status", help="New status")
|
|
148
|
+
@click.option("--json", "extra_json", help="Additional JSON parameters")
|
|
149
|
+
@click.option("--dry-run", is_flag=True, help="Show request without sending")
|
|
150
|
+
@click.pass_context
|
|
151
|
+
def update(ctx, keyword_id, bid, context_bid, status, extra_json, dry_run):
|
|
152
|
+
"""Update keyword"""
|
|
153
|
+
try:
|
|
154
|
+
keyword_data = {"Id": keyword_id}
|
|
155
|
+
|
|
156
|
+
if bid:
|
|
157
|
+
keyword_data["Bid"] = int(bid * 1000000)
|
|
158
|
+
if context_bid:
|
|
159
|
+
keyword_data["ContextBid"] = int(context_bid * 1000000)
|
|
160
|
+
if status:
|
|
161
|
+
keyword_data["Status"] = status
|
|
162
|
+
|
|
163
|
+
if extra_json:
|
|
164
|
+
extra = json.loads(extra_json)
|
|
165
|
+
keyword_data.update(extra)
|
|
166
|
+
|
|
167
|
+
body = {"method": "update", "params": {"Keywords": [keyword_data]}}
|
|
168
|
+
|
|
169
|
+
if dry_run:
|
|
170
|
+
format_output(body, "json", None)
|
|
171
|
+
return
|
|
172
|
+
|
|
173
|
+
client = create_client(
|
|
174
|
+
token=ctx.obj.get("token"),
|
|
175
|
+
login=ctx.obj.get("login"),
|
|
176
|
+
sandbox=ctx.obj.get("sandbox"),
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
result = client.keywords().post(data=body)
|
|
180
|
+
format_output(result().extract(), "json", None)
|
|
181
|
+
|
|
182
|
+
except Exception as e:
|
|
183
|
+
print_error(str(e))
|
|
184
|
+
raise click.Abort()
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@keywords.command()
|
|
188
|
+
@click.option("--id", "keyword_id", required=True, type=int, help="Keyword ID")
|
|
189
|
+
@click.pass_context
|
|
190
|
+
def delete(ctx, keyword_id):
|
|
191
|
+
"""Delete keyword"""
|
|
192
|
+
try:
|
|
193
|
+
client = create_client(
|
|
194
|
+
token=ctx.obj.get("token"),
|
|
195
|
+
login=ctx.obj.get("login"),
|
|
196
|
+
sandbox=ctx.obj.get("sandbox"),
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
body = {
|
|
200
|
+
"method": "delete",
|
|
201
|
+
"params": {"SelectionCriteria": {"Ids": [keyword_id]}},
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
result = client.keywords().post(data=body)
|
|
205
|
+
format_output(result().extract(), "json", None)
|
|
206
|
+
|
|
207
|
+
except Exception as e:
|
|
208
|
+
print_error(str(e))
|
|
209
|
+
raise click.Abort()
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
@keywords.command()
|
|
213
|
+
@click.option("--id", "keyword_id", required=True, type=int, help="Keyword ID")
|
|
214
|
+
@click.pass_context
|
|
215
|
+
def archive(ctx, keyword_id):
|
|
216
|
+
"""Archive keyword"""
|
|
217
|
+
try:
|
|
218
|
+
client = create_client(
|
|
219
|
+
token=ctx.obj.get("token"),
|
|
220
|
+
login=ctx.obj.get("login"),
|
|
221
|
+
sandbox=ctx.obj.get("sandbox"),
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
body = {
|
|
225
|
+
"method": "archive",
|
|
226
|
+
"params": {"SelectionCriteria": {"Ids": [keyword_id]}},
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
result = client.keywords().post(data=body)
|
|
230
|
+
format_output(result().extract(), "json", None)
|
|
231
|
+
|
|
232
|
+
except Exception as e:
|
|
233
|
+
print_error(str(e))
|
|
234
|
+
raise click.Abort()
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
@keywords.command()
|
|
238
|
+
@click.option("--id", "keyword_id", required=True, type=int, help="Keyword ID")
|
|
239
|
+
@click.pass_context
|
|
240
|
+
def unarchive(ctx, keyword_id):
|
|
241
|
+
"""Unarchive keyword"""
|
|
242
|
+
try:
|
|
243
|
+
client = create_client(
|
|
244
|
+
token=ctx.obj.get("token"),
|
|
245
|
+
login=ctx.obj.get("login"),
|
|
246
|
+
sandbox=ctx.obj.get("sandbox"),
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
body = {
|
|
250
|
+
"method": "unarchive",
|
|
251
|
+
"params": {"SelectionCriteria": {"Ids": [keyword_id]}},
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
result = client.keywords().post(data=body)
|
|
255
|
+
format_output(result().extract(), "json", None)
|
|
256
|
+
|
|
257
|
+
except Exception as e:
|
|
258
|
+
print_error(str(e))
|
|
259
|
+
raise click.Abort()
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
@keywords.command()
|
|
263
|
+
@click.option("--id", "keyword_id", required=True, type=int, help="Keyword ID")
|
|
264
|
+
@click.pass_context
|
|
265
|
+
def suspend(ctx, keyword_id):
|
|
266
|
+
"""Suspend keyword"""
|
|
267
|
+
try:
|
|
268
|
+
client = create_client(
|
|
269
|
+
token=ctx.obj.get("token"),
|
|
270
|
+
login=ctx.obj.get("login"),
|
|
271
|
+
sandbox=ctx.obj.get("sandbox"),
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
body = {
|
|
275
|
+
"method": "suspend",
|
|
276
|
+
"params": {"SelectionCriteria": {"Ids": [keyword_id]}},
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
result = client.keywords().post(data=body)
|
|
280
|
+
format_output(result().extract(), "json", None)
|
|
281
|
+
|
|
282
|
+
except Exception as e:
|
|
283
|
+
print_error(str(e))
|
|
284
|
+
raise click.Abort()
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
@keywords.command()
|
|
288
|
+
@click.option("--id", "keyword_id", required=True, type=int, help="Keyword ID")
|
|
289
|
+
@click.pass_context
|
|
290
|
+
def resume(ctx, keyword_id):
|
|
291
|
+
"""Resume keyword"""
|
|
292
|
+
try:
|
|
293
|
+
client = create_client(
|
|
294
|
+
token=ctx.obj.get("token"),
|
|
295
|
+
login=ctx.obj.get("login"),
|
|
296
|
+
sandbox=ctx.obj.get("sandbox"),
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
body = {
|
|
300
|
+
"method": "resume",
|
|
301
|
+
"params": {"SelectionCriteria": {"Ids": [keyword_id]}},
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
result = client.keywords().post(data=body)
|
|
305
|
+
format_output(result().extract(), "json", None)
|
|
306
|
+
|
|
307
|
+
except Exception as e:
|
|
308
|
+
print_error(str(e))
|
|
309
|
+
raise click.Abort()
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""
|
|
2
|
+
KeywordsResearch commands
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from ..api import create_client
|
|
8
|
+
from ..output import format_output, print_error
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.group()
|
|
12
|
+
def keywordsresearch():
|
|
13
|
+
"""Keyword research tools"""
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@keywordsresearch.command()
|
|
18
|
+
@click.option("--keywords", required=True, help="Comma-separated keywords")
|
|
19
|
+
@click.option("--limit", type=int, help="Limit number of results")
|
|
20
|
+
@click.option("--format", "output_format", default="json", help="Output format")
|
|
21
|
+
@click.option("--output", help="Output file")
|
|
22
|
+
@click.pass_context
|
|
23
|
+
def get(ctx, keywords, limit, output_format, output):
|
|
24
|
+
"""Get keywords research data"""
|
|
25
|
+
try:
|
|
26
|
+
client = create_client(
|
|
27
|
+
token=ctx.obj.get("token"),
|
|
28
|
+
login=ctx.obj.get("login"),
|
|
29
|
+
sandbox=ctx.obj.get("sandbox"),
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
params = {"Keywords": [k.strip() for k in keywords.split(",")]}
|
|
33
|
+
|
|
34
|
+
if limit:
|
|
35
|
+
params["Limit"] = limit
|
|
36
|
+
|
|
37
|
+
body = {"method": "get", "params": params}
|
|
38
|
+
|
|
39
|
+
result = client.keywordsresearch().post(data=body)
|
|
40
|
+
format_output(result.data, output_format, output)
|
|
41
|
+
|
|
42
|
+
except Exception as e:
|
|
43
|
+
print_error(str(e))
|
|
44
|
+
raise click.Abort()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@keywordsresearch.command()
|
|
48
|
+
@click.option("--keywords", required=True, help="Comma-separated keywords")
|
|
49
|
+
@click.option("--format", "output_format", default="json", help="Output format")
|
|
50
|
+
@click.option("--output", help="Output file")
|
|
51
|
+
@click.pass_context
|
|
52
|
+
def has_search_volume(ctx, keywords, output_format, output):
|
|
53
|
+
"""Check if keywords have search volume"""
|
|
54
|
+
try:
|
|
55
|
+
client = create_client(
|
|
56
|
+
token=ctx.obj.get("token"),
|
|
57
|
+
login=ctx.obj.get("login"),
|
|
58
|
+
sandbox=ctx.obj.get("sandbox"),
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
body = {
|
|
62
|
+
"method": "HasSearchVolume",
|
|
63
|
+
"params": {"Keywords": [k.strip() for k in keywords.split(",")]},
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
result = client.keywordsresearch().post(data=body)
|
|
67
|
+
format_output(result.data, output_format, output)
|
|
68
|
+
|
|
69
|
+
except Exception as e:
|
|
70
|
+
print_error(str(e))
|
|
71
|
+
raise click.Abort()
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Leads commands
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from ..api import create_client
|
|
8
|
+
from ..output import format_output, print_error
|
|
9
|
+
from ..utils import parse_ids
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@click.group()
|
|
13
|
+
def leads():
|
|
14
|
+
"""Manage leads"""
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@leads.command()
|
|
19
|
+
@click.option("--campaign-ids", help="Comma-separated campaign IDs")
|
|
20
|
+
@click.option("--limit", type=int, help="Limit number of results")
|
|
21
|
+
@click.option("--fetch-all", is_flag=True, help="Fetch all pages")
|
|
22
|
+
@click.option("--format", "output_format", default="json", help="Output format")
|
|
23
|
+
@click.option("--output", help="Output file")
|
|
24
|
+
@click.option("--fields", help="Comma-separated field names")
|
|
25
|
+
@click.pass_context
|
|
26
|
+
def get(ctx, campaign_ids, limit, fetch_all, output_format, output, fields):
|
|
27
|
+
"""Get leads"""
|
|
28
|
+
try:
|
|
29
|
+
client = create_client(
|
|
30
|
+
token=ctx.obj.get("token"),
|
|
31
|
+
login=ctx.obj.get("login"),
|
|
32
|
+
sandbox=ctx.obj.get("sandbox"),
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
field_names = (
|
|
36
|
+
fields.split(",")
|
|
37
|
+
if fields
|
|
38
|
+
else ["Date", "LeadId", "CampaignId", "AdGroupId", "AdId"]
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
criteria = {}
|
|
42
|
+
if campaign_ids:
|
|
43
|
+
criteria["CampaignIds"] = parse_ids(campaign_ids)
|
|
44
|
+
|
|
45
|
+
params = {"SelectionCriteria": criteria, "FieldNames": field_names}
|
|
46
|
+
|
|
47
|
+
if limit:
|
|
48
|
+
params["Page"] = {"Limit": limit}
|
|
49
|
+
|
|
50
|
+
body = {"method": "get", "params": params}
|
|
51
|
+
|
|
52
|
+
result = client.leads().post(data=body)
|
|
53
|
+
|
|
54
|
+
if fetch_all:
|
|
55
|
+
items = []
|
|
56
|
+
for item in result().iter_items():
|
|
57
|
+
items.append(item)
|
|
58
|
+
format_output(items, output_format, output)
|
|
59
|
+
else:
|
|
60
|
+
data = result().extract()
|
|
61
|
+
format_output(data, output_format, output)
|
|
62
|
+
|
|
63
|
+
except Exception as e:
|
|
64
|
+
print_error(str(e))
|
|
65
|
+
raise click.Abort()
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""
|
|
2
|
+
NegativeKeywordSharedSets commands
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import click
|
|
7
|
+
|
|
8
|
+
from ..api import create_client
|
|
9
|
+
from ..output import format_output, print_error
|
|
10
|
+
from ..utils import parse_ids
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.group()
|
|
14
|
+
def negativekeywordsharedsets():
|
|
15
|
+
"""Manage negative keyword shared sets"""
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@negativekeywordsharedsets.command()
|
|
20
|
+
@click.option("--ids", help="Comma-separated set IDs")
|
|
21
|
+
@click.option("--limit", type=int, help="Limit number of results")
|
|
22
|
+
@click.option("--fetch-all", is_flag=True, help="Fetch all pages")
|
|
23
|
+
@click.option("--format", "output_format", default="json", help="Output format")
|
|
24
|
+
@click.option("--output", help="Output file")
|
|
25
|
+
@click.option("--fields", help="Comma-separated field names")
|
|
26
|
+
@click.pass_context
|
|
27
|
+
def get(ctx, ids, limit, fetch_all, output_format, output, fields):
|
|
28
|
+
"""Get negative keyword shared sets"""
|
|
29
|
+
try:
|
|
30
|
+
client = create_client(
|
|
31
|
+
token=ctx.obj.get("token"),
|
|
32
|
+
login=ctx.obj.get("login"),
|
|
33
|
+
sandbox=ctx.obj.get("sandbox"),
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
field_names = (
|
|
37
|
+
fields.split(",") if fields else ["Id", "Name", "NegativeKeywords"]
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
criteria = {}
|
|
41
|
+
if ids:
|
|
42
|
+
criteria["Ids"] = parse_ids(ids)
|
|
43
|
+
|
|
44
|
+
params = {"SelectionCriteria": criteria, "FieldNames": field_names}
|
|
45
|
+
|
|
46
|
+
if limit:
|
|
47
|
+
params["Page"] = {"Limit": limit}
|
|
48
|
+
|
|
49
|
+
body = {"method": "get", "params": params}
|
|
50
|
+
|
|
51
|
+
result = client.negativekeywordsharedsets().post(data=body)
|
|
52
|
+
|
|
53
|
+
if fetch_all:
|
|
54
|
+
items = []
|
|
55
|
+
for item in result().iter_items():
|
|
56
|
+
items.append(item)
|
|
57
|
+
format_output(items, output_format, output)
|
|
58
|
+
else:
|
|
59
|
+
data = result().extract()
|
|
60
|
+
format_output(data, output_format, output)
|
|
61
|
+
|
|
62
|
+
except Exception as e:
|
|
63
|
+
print_error(str(e))
|
|
64
|
+
raise click.Abort()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@negativekeywordsharedsets.command()
|
|
68
|
+
@click.option("--name", required=True, help="Set name")
|
|
69
|
+
@click.option("--keywords", required=True, help="Comma-separated negative keywords")
|
|
70
|
+
@click.option("--dry-run", is_flag=True, help="Show request without sending")
|
|
71
|
+
@click.pass_context
|
|
72
|
+
def add(ctx, name, keywords, dry_run):
|
|
73
|
+
"""Add negative keyword shared set"""
|
|
74
|
+
try:
|
|
75
|
+
set_data = {
|
|
76
|
+
"Name": name,
|
|
77
|
+
"NegativeKeywords": [{"Keyword": k.strip()} for k in keywords.split(",")],
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
body = {"method": "add", "params": {"NegativeKeywordSharedSets": [set_data]}}
|
|
81
|
+
|
|
82
|
+
if dry_run:
|
|
83
|
+
format_output(body, "json", None)
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
client = create_client(
|
|
87
|
+
token=ctx.obj.get("token"),
|
|
88
|
+
login=ctx.obj.get("login"),
|
|
89
|
+
sandbox=ctx.obj.get("sandbox"),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
result = client.negativekeywordsharedsets().post(data=body)
|
|
93
|
+
format_output(result().extract(), "json", None)
|
|
94
|
+
|
|
95
|
+
except Exception as e:
|
|
96
|
+
print_error(str(e))
|
|
97
|
+
raise click.Abort()
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Reports commands
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import click
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
|
|
9
|
+
from ..api import create_client
|
|
10
|
+
from ..output import format_output, print_error
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
REPORT_TYPES = [
|
|
14
|
+
"CAMPAIGN_PERFORMANCE_REPORT",
|
|
15
|
+
"ADGROUP_PERFORMANCE_REPORT",
|
|
16
|
+
"AD_PERFORMANCE_REPORT",
|
|
17
|
+
"CRITERIA_PERFORMANCE_REPORT",
|
|
18
|
+
"CUSTOM_REPORT",
|
|
19
|
+
"REACH_AND_FREQUENCY_CAMPAIGN_REPORT",
|
|
20
|
+
"SEARCH_QUERY_PERFORMANCE_REPORT",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@click.group()
|
|
25
|
+
def reports():
|
|
26
|
+
"""Generate and manage reports"""
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@reports.command()
|
|
31
|
+
@click.option(
|
|
32
|
+
"--type",
|
|
33
|
+
"report_type",
|
|
34
|
+
required=True,
|
|
35
|
+
help="Report type (CAMPAIGN_PERFORMANCE_REPORT, etc.)",
|
|
36
|
+
)
|
|
37
|
+
@click.option("--from", "date_from", required=True, help="Start date (YYYY-MM-DD)")
|
|
38
|
+
@click.option("--to", "date_to", required=True, help="End date (YYYY-MM-DD)")
|
|
39
|
+
@click.option("--name", required=True, help="Report name")
|
|
40
|
+
@click.option("--fields", required=True, help="Comma-separated field names")
|
|
41
|
+
@click.option("--campaign-ids", help="Comma-separated campaign IDs")
|
|
42
|
+
@click.option("--adgroup-ids", help="Comma-separated ad group IDs")
|
|
43
|
+
@click.option(
|
|
44
|
+
"--format",
|
|
45
|
+
"output_format",
|
|
46
|
+
default="json",
|
|
47
|
+
help="Output format (json/table/csv/tsv)",
|
|
48
|
+
)
|
|
49
|
+
@click.option("--output", help="Output file")
|
|
50
|
+
@click.option("--mode", default="auto", help="Processing mode (online/offline/auto)")
|
|
51
|
+
@click.pass_context
|
|
52
|
+
def get(
|
|
53
|
+
ctx,
|
|
54
|
+
report_type,
|
|
55
|
+
date_from,
|
|
56
|
+
date_to,
|
|
57
|
+
name,
|
|
58
|
+
fields,
|
|
59
|
+
campaign_ids,
|
|
60
|
+
adgroup_ids,
|
|
61
|
+
output_format,
|
|
62
|
+
output,
|
|
63
|
+
mode,
|
|
64
|
+
):
|
|
65
|
+
"""Get report"""
|
|
66
|
+
try:
|
|
67
|
+
client = create_client(
|
|
68
|
+
token=ctx.obj.get("token"),
|
|
69
|
+
login=ctx.obj.get("login"),
|
|
70
|
+
sandbox=ctx.obj.get("sandbox"),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
field_names = [f.strip() for f in fields.split(",")]
|
|
74
|
+
|
|
75
|
+
selection_criteria = {"DateFrom": date_from, "DateTo": date_to}
|
|
76
|
+
|
|
77
|
+
if campaign_ids:
|
|
78
|
+
selection_criteria["Filter"] = [
|
|
79
|
+
{
|
|
80
|
+
"Field": "CampaignId",
|
|
81
|
+
"Operator": "IN",
|
|
82
|
+
"Values": campaign_ids.split(","),
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
elif adgroup_ids:
|
|
86
|
+
selection_criteria["Filter"] = [
|
|
87
|
+
{
|
|
88
|
+
"Field": "AdGroupId",
|
|
89
|
+
"Operator": "IN",
|
|
90
|
+
"Values": adgroup_ids.split(","),
|
|
91
|
+
}
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
body = {
|
|
95
|
+
"params": {
|
|
96
|
+
"SelectionCriteria": selection_criteria,
|
|
97
|
+
"FieldNames": field_names,
|
|
98
|
+
"ReportName": name,
|
|
99
|
+
"ReportType": report_type,
|
|
100
|
+
"DateRangeType": "CUSTOM_DATE",
|
|
101
|
+
"Format": "TSV" if output_format in ["tsv", "csv"] else "TSV",
|
|
102
|
+
"IncludeVAT": "YES",
|
|
103
|
+
"IncludeDiscount": "YES",
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
result = client.reports().post(data=body)
|
|
108
|
+
|
|
109
|
+
if output_format == "json":
|
|
110
|
+
format_output(result().to_dicts(), "json", output)
|
|
111
|
+
elif output_format == "table":
|
|
112
|
+
format_output(result().to_dicts(), "table", output)
|
|
113
|
+
elif output_format == "csv":
|
|
114
|
+
format_output(result().to_values(), "csv", output, headers=result.columns)
|
|
115
|
+
elif output_format == "tsv":
|
|
116
|
+
format_output(result().to_values(), "tsv", output, headers=result.columns)
|
|
117
|
+
else:
|
|
118
|
+
format_output(result.data, "json", output)
|
|
119
|
+
|
|
120
|
+
except Exception as e:
|
|
121
|
+
print_error(str(e))
|
|
122
|
+
raise click.Abort()
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@reports.command()
|
|
126
|
+
def list_types():
|
|
127
|
+
"""List available report types"""
|
|
128
|
+
format_output(REPORT_TYPES, "json", None)
|