adloop 0.34.0__tar.gz → 0.34.1__tar.gz
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.
- {adloop-0.34.0 → adloop-0.34.1}/PKG-INFO +1 -1
- {adloop-0.34.0 → adloop-0.34.1}/pyproject.toml +1 -1
- {adloop-0.34.0 → adloop-0.34.1}/pyproject.toml.orig +1 -1
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/ads/assets.py +27 -10
- adloop-0.34.1/src/adloop/ads/snippet_headers.py +723 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/ads/write.py +0 -16
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/rules/adloop.md +1 -1
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/server.py +10 -6
- {adloop-0.34.0 → adloop-0.34.1}/README.md +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/__init__.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/__main__.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/_mcp_patches.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/ads/__init__.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/ads/ai_max.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/ads/audiences.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/ads/brands.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/ads/client.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/ads/conversion_actions.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/ads/currency.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/ads/custom_conversion_goals.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/ads/enums.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/ads/forecast.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/ads/gaql.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/ads/image_fetch.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/ads/pmax.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/ads/read.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/ads/shopping.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/ads/validate_only.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/auth.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/cli.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/config.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/crossref.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/diagnostics.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/ga4/__init__.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/ga4/client.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/ga4/reports.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/ga4/tracking.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/ga4/write.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/gsc/__init__.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/gsc/client.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/gsc/reports.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/gtm/__init__.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/gtm/client.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/gtm/entities.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/gtm/read.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/gtm/write.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/merchant/__init__.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/merchant/client.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/merchant/read.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/pagespeed.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/reddit/__init__.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/reddit/auth.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/reddit/client.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/reddit/read.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/reddit/schedule.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/reddit/write.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/rules/__init__.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/rules/commands/analyze-performance.md +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/rules/commands/budget-plan.md +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/rules/commands/create-ad.md +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/rules/commands/create-campaign.md +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/rules/commands/diagnose-tracking.md +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/rules/commands/optimize-campaign.md +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/rules_install.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/runtime.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/safety/__init__.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/safety/approval.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/safety/approval_card.html +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/safety/audit.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/safety/guards.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/safety/preview.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/tracking.py +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/upload_card.html +0 -0
- {adloop-0.34.0 → adloop-0.34.1}/src/adloop/uploads.py +0 -0
|
@@ -151,8 +151,22 @@ def _validate_callout_texts(callouts: list[str]) -> tuple[list[str], list[str]]:
|
|
|
151
151
|
return validated, errors
|
|
152
152
|
|
|
153
153
|
|
|
154
|
-
def
|
|
155
|
-
from adloop.ads.
|
|
154
|
+
def _header_error(index: int, header: str) -> str:
|
|
155
|
+
from adloop.ads.snippet_headers import ENGLISH_HEADERS
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
f"Structured snippet {index + 1}: header '{header}' is not one of Google's snippet "
|
|
159
|
+
f"headers. English: {', '.join(ENGLISH_HEADERS)}. Google's translations are "
|
|
160
|
+
"accepted too, in the language of the values (German 'Typen', 'Marken', "
|
|
161
|
+
"'Dienstleistungen'): https://developers.google.com/google-ads/api/data/structured-snippet-headers"
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _validate_snippet_items(
|
|
166
|
+
snippets: list[dict], *, keep_header: str = ""
|
|
167
|
+
) -> tuple[list[dict], list[str]]:
|
|
168
|
+
"""Check snippets; ``keep_header`` is an existing asset's header, accepted as is."""
|
|
169
|
+
from adloop.ads.snippet_headers import canonical_header
|
|
156
170
|
|
|
157
171
|
errors: list[str] = []
|
|
158
172
|
validated: list[dict] = []
|
|
@@ -161,11 +175,12 @@ def _validate_snippet_items(snippets: list[dict]) -> tuple[list[dict], list[str]
|
|
|
161
175
|
for index, snippet in enumerate(snippets):
|
|
162
176
|
header = _clean(snippet.get("header", ""))
|
|
163
177
|
values = [_clean(value) for value in snippet.get("values", []) or []]
|
|
164
|
-
if header
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
178
|
+
if keep_header and header == keep_header:
|
|
179
|
+
pass # Google already holds this header; it is not ours to reject.
|
|
180
|
+
elif canonical := canonical_header(header):
|
|
181
|
+
header = canonical
|
|
182
|
+
else:
|
|
183
|
+
errors.append(_header_error(index, header))
|
|
169
184
|
if len(values) < 3 or len(values) > 10:
|
|
170
185
|
errors.append(f"Structured snippet {index + 1}: values must contain 3-10 items")
|
|
171
186
|
for value_index, value in enumerate(values):
|
|
@@ -543,9 +558,11 @@ def update_structured_snippet(
|
|
|
543
558
|
current, error = _asset_for_edit(config, customer_id, asset_id, _SNIPPET_FIELDS, "STRUCTURED_SNIPPET")
|
|
544
559
|
if error:
|
|
545
560
|
return error
|
|
546
|
-
# An empty header keeps the current one
|
|
547
|
-
|
|
548
|
-
|
|
561
|
+
# An empty header keeps the current one, accepted as Google stores it;
|
|
562
|
+
# values are always the full list.
|
|
563
|
+
kept = (current or {}).get("header", "")
|
|
564
|
+
snippet = {"header": _clean(header) or kept, "values": values or []}
|
|
565
|
+
validated, snippet_errors = _validate_snippet_items([snippet], keep_header=kept)
|
|
549
566
|
errors.extend(snippet_errors)
|
|
550
567
|
if errors:
|
|
551
568
|
return {"error": "Validation failed", "details": errors}
|
|
@@ -0,0 +1,723 @@
|
|
|
1
|
+
"""Structured snippet headers Google accepts: the English names and their translations.
|
|
2
|
+
|
|
3
|
+
A snippet header has to be one of these strings, in the language of the
|
|
4
|
+
snippet's values. Generated from Google's table "Structured Snippet Header
|
|
5
|
+
Translations":
|
|
6
|
+
https://developers.google.com/google-ads/api/data/structured-snippet-headers
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
# The English headers, in the column order of the table below.
|
|
12
|
+
ENGLISH_HEADERS = (
|
|
13
|
+
"Brands",
|
|
14
|
+
"Amenities",
|
|
15
|
+
"Styles",
|
|
16
|
+
"Types",
|
|
17
|
+
"Destinations",
|
|
18
|
+
"Services",
|
|
19
|
+
"Courses",
|
|
20
|
+
"Neighborhoods",
|
|
21
|
+
"Shows",
|
|
22
|
+
"Insurance coverage",
|
|
23
|
+
"Degree programs",
|
|
24
|
+
"Featured Hotels",
|
|
25
|
+
"Models",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# locale -> (language, headers in ENGLISH_HEADERS order; "" where Google lists none).
|
|
29
|
+
LOCALIZED_HEADERS: dict[str, tuple[str, tuple[str, ...]]] = {
|
|
30
|
+
"ar": ("Arabic", (
|
|
31
|
+
'العلامات التجارية',
|
|
32
|
+
'وسائل الراحة',
|
|
33
|
+
'الأنماط',
|
|
34
|
+
'الأنواع',
|
|
35
|
+
'الوجهات',
|
|
36
|
+
'الخدمات',
|
|
37
|
+
'الدورات التدريبية',
|
|
38
|
+
'الأحياء',
|
|
39
|
+
'العروض',
|
|
40
|
+
'التغطية التأمينية',
|
|
41
|
+
'برامج الشهادات',
|
|
42
|
+
'الفنادق المميّزة',
|
|
43
|
+
'النماذج',
|
|
44
|
+
)),
|
|
45
|
+
"bg": ("Bulgarian", (
|
|
46
|
+
'Марки',
|
|
47
|
+
'Удобства',
|
|
48
|
+
'Стилове',
|
|
49
|
+
'Типове',
|
|
50
|
+
'Дестинации',
|
|
51
|
+
'Услуги',
|
|
52
|
+
'Курсове',
|
|
53
|
+
'Квартали',
|
|
54
|
+
'Предавания',
|
|
55
|
+
'Застраховка',
|
|
56
|
+
'Специалности',
|
|
57
|
+
'Представени хотели',
|
|
58
|
+
'Модели',
|
|
59
|
+
)),
|
|
60
|
+
"ca": ("Catalan", (
|
|
61
|
+
'Marques',
|
|
62
|
+
'Serveis addicionals',
|
|
63
|
+
'Estils',
|
|
64
|
+
'Tipus',
|
|
65
|
+
'Destinacions',
|
|
66
|
+
'Serveis',
|
|
67
|
+
'Cursos',
|
|
68
|
+
'Barris',
|
|
69
|
+
'Programes',
|
|
70
|
+
'Assegurança',
|
|
71
|
+
'Titulacions',
|
|
72
|
+
'Hotels destacats',
|
|
73
|
+
'Models',
|
|
74
|
+
)),
|
|
75
|
+
"zh-HK": ("Chinese (Hong Kong)", (
|
|
76
|
+
'品牌',
|
|
77
|
+
'設施',
|
|
78
|
+
'款式',
|
|
79
|
+
'類型',
|
|
80
|
+
'目的地',
|
|
81
|
+
'服務',
|
|
82
|
+
'課程',
|
|
83
|
+
'社區',
|
|
84
|
+
'節目',
|
|
85
|
+
'保障範圍',
|
|
86
|
+
'學位課程',
|
|
87
|
+
'特色酒店',
|
|
88
|
+
'型號',
|
|
89
|
+
)),
|
|
90
|
+
"zh-CN": ("Chinese (Simplified)", (
|
|
91
|
+
'品牌',
|
|
92
|
+
'设施',
|
|
93
|
+
'款式',
|
|
94
|
+
'类型',
|
|
95
|
+
'目的地',
|
|
96
|
+
'服务',
|
|
97
|
+
'课程',
|
|
98
|
+
'社区',
|
|
99
|
+
'节目',
|
|
100
|
+
'险种',
|
|
101
|
+
'学位课程',
|
|
102
|
+
'精选酒店',
|
|
103
|
+
'型号',
|
|
104
|
+
)),
|
|
105
|
+
"zh-TW": ("Chinese (Traditional)", (
|
|
106
|
+
'品牌',
|
|
107
|
+
'設施',
|
|
108
|
+
'款式',
|
|
109
|
+
'類型',
|
|
110
|
+
'目的地',
|
|
111
|
+
'服務',
|
|
112
|
+
'課程',
|
|
113
|
+
'社區',
|
|
114
|
+
'節目',
|
|
115
|
+
'承保範圍',
|
|
116
|
+
'學位課程',
|
|
117
|
+
'精選飯店',
|
|
118
|
+
'型號',
|
|
119
|
+
)),
|
|
120
|
+
"hr": ("Croatian", (
|
|
121
|
+
'Robne marke',
|
|
122
|
+
'Sadržaji',
|
|
123
|
+
'Stilovi',
|
|
124
|
+
'Vrste',
|
|
125
|
+
'Odredišta',
|
|
126
|
+
'Usluge',
|
|
127
|
+
'Tečajevi',
|
|
128
|
+
'Četvrti',
|
|
129
|
+
'Emisije',
|
|
130
|
+
'Osiguranje',
|
|
131
|
+
'Studijski programi',
|
|
132
|
+
'Istaknuti hoteli',
|
|
133
|
+
'Modeli',
|
|
134
|
+
)),
|
|
135
|
+
"cs": ("Czech", (
|
|
136
|
+
'Značky',
|
|
137
|
+
'Vybavení',
|
|
138
|
+
'Styly',
|
|
139
|
+
'Typy',
|
|
140
|
+
'Cíle',
|
|
141
|
+
'Služby',
|
|
142
|
+
'Kurzy',
|
|
143
|
+
'Známé oblasti',
|
|
144
|
+
'Pořady',
|
|
145
|
+
'Pojištění',
|
|
146
|
+
'Studijní programy',
|
|
147
|
+
'Vybrané hotely',
|
|
148
|
+
'Modely',
|
|
149
|
+
)),
|
|
150
|
+
"da": ("Danish", (
|
|
151
|
+
'Brands',
|
|
152
|
+
'Faciliteter',
|
|
153
|
+
'Design',
|
|
154
|
+
'Typer',
|
|
155
|
+
'Destinationer',
|
|
156
|
+
'Tjenester',
|
|
157
|
+
'Kurser',
|
|
158
|
+
'Nabolag',
|
|
159
|
+
'Serier',
|
|
160
|
+
'Forsikringsdækning',
|
|
161
|
+
'Uddannelser',
|
|
162
|
+
'Udvalgte hoteller',
|
|
163
|
+
'Modeller',
|
|
164
|
+
)),
|
|
165
|
+
"nl": ("Dutch", (
|
|
166
|
+
'Merken',
|
|
167
|
+
'Voorzieningen',
|
|
168
|
+
'Stijlen',
|
|
169
|
+
'Typen',
|
|
170
|
+
'Bestemmingen',
|
|
171
|
+
'Services',
|
|
172
|
+
'Cursussen',
|
|
173
|
+
'Buurten',
|
|
174
|
+
'Shows',
|
|
175
|
+
'Dekking',
|
|
176
|
+
"Studieprogramma's",
|
|
177
|
+
'Aanbevolen hotels',
|
|
178
|
+
'Modellen',
|
|
179
|
+
)),
|
|
180
|
+
"en-AU": ("English (Australia)", (
|
|
181
|
+
'Brands',
|
|
182
|
+
'Amenities',
|
|
183
|
+
'Styles',
|
|
184
|
+
'Types',
|
|
185
|
+
'Destinations',
|
|
186
|
+
'Services',
|
|
187
|
+
'Courses',
|
|
188
|
+
'Neighbourhoods',
|
|
189
|
+
'Shows',
|
|
190
|
+
'Insurance coverage',
|
|
191
|
+
'Degree programmes',
|
|
192
|
+
'Featured hotels',
|
|
193
|
+
'Models',
|
|
194
|
+
)),
|
|
195
|
+
"en-GB": ("English (UK)", (
|
|
196
|
+
'Brands',
|
|
197
|
+
'Amenities',
|
|
198
|
+
'Styles',
|
|
199
|
+
'Types',
|
|
200
|
+
'Destinations',
|
|
201
|
+
'Services',
|
|
202
|
+
'Courses',
|
|
203
|
+
'Neighbourhoods',
|
|
204
|
+
'Shows',
|
|
205
|
+
'Insurance coverage',
|
|
206
|
+
'Degree programmes',
|
|
207
|
+
'Featured hotels',
|
|
208
|
+
'Models',
|
|
209
|
+
)),
|
|
210
|
+
"et": ("Estonian", (
|
|
211
|
+
'Brändid',
|
|
212
|
+
'Mugavusteenused',
|
|
213
|
+
'Stiilid',
|
|
214
|
+
'Tüübid',
|
|
215
|
+
'Sihtkohad',
|
|
216
|
+
'Teenused',
|
|
217
|
+
'Kursused',
|
|
218
|
+
'Ümbruskonnad',
|
|
219
|
+
'Saated ja etendused',
|
|
220
|
+
'Kindlustuskaitse',
|
|
221
|
+
'Kraadiõppeprogrammid',
|
|
222
|
+
'Soovitatud hotellid',
|
|
223
|
+
'Mudelid',
|
|
224
|
+
)),
|
|
225
|
+
"fil": ("Filipino", (
|
|
226
|
+
'Mga Brand',
|
|
227
|
+
'Mga Amenity',
|
|
228
|
+
'Mga Istilo',
|
|
229
|
+
'Mga Uri',
|
|
230
|
+
'Mga Destinasyon',
|
|
231
|
+
'Mga Serbisyo',
|
|
232
|
+
'Mga Kurso',
|
|
233
|
+
'Mga Komunidad',
|
|
234
|
+
'Mga Palabas',
|
|
235
|
+
'Sakop ng insurance',
|
|
236
|
+
'Mga degree program',
|
|
237
|
+
'Itinatampok na hotel',
|
|
238
|
+
'Mga Modelo',
|
|
239
|
+
)),
|
|
240
|
+
"fi": ("Finnish", (
|
|
241
|
+
'Brändit',
|
|
242
|
+
'Lisäpalvelut',
|
|
243
|
+
'Tyylit',
|
|
244
|
+
'Tyypit',
|
|
245
|
+
'Kohteet',
|
|
246
|
+
'Palvelut',
|
|
247
|
+
'Kurssit',
|
|
248
|
+
'Kaupunginosat',
|
|
249
|
+
"Show't",
|
|
250
|
+
'Vakuutukset',
|
|
251
|
+
'Oppiaineet',
|
|
252
|
+
'Suositellut hotellit',
|
|
253
|
+
'Automerkit',
|
|
254
|
+
)),
|
|
255
|
+
"fr": ("French (France)", (
|
|
256
|
+
'Marques',
|
|
257
|
+
'Équipements',
|
|
258
|
+
'Styles',
|
|
259
|
+
'Types',
|
|
260
|
+
'Destinations',
|
|
261
|
+
'Services',
|
|
262
|
+
'Cours',
|
|
263
|
+
'Quartiers',
|
|
264
|
+
'Émissions',
|
|
265
|
+
"Couverture d'assurance",
|
|
266
|
+
"Programmes d'études",
|
|
267
|
+
"Sélection d'hôtels",
|
|
268
|
+
'Modèles',
|
|
269
|
+
)),
|
|
270
|
+
"de": ("German", (
|
|
271
|
+
'Marken',
|
|
272
|
+
'Ausstattung',
|
|
273
|
+
'Stile',
|
|
274
|
+
'Typen',
|
|
275
|
+
'Ziele',
|
|
276
|
+
'Dienstleistungen',
|
|
277
|
+
'Kurse',
|
|
278
|
+
'Viertel',
|
|
279
|
+
'Serien',
|
|
280
|
+
'Versicherungsleistung',
|
|
281
|
+
'Studiengänge',
|
|
282
|
+
'Vorgestellte Hotels',
|
|
283
|
+
'Modelle',
|
|
284
|
+
)),
|
|
285
|
+
"el": ("Greek", (
|
|
286
|
+
'Επωνυμίες',
|
|
287
|
+
'Παροχές',
|
|
288
|
+
'Στυλ',
|
|
289
|
+
'Τύποι',
|
|
290
|
+
'Προορισμοί',
|
|
291
|
+
'Υπηρεσίες',
|
|
292
|
+
'Μαθήματα',
|
|
293
|
+
'Γειτονιές',
|
|
294
|
+
'Εκπομπές',
|
|
295
|
+
'Ασφαλιστική κάλυψη',
|
|
296
|
+
'Προγράμματα πτυχίων',
|
|
297
|
+
'Ξενοδοχεία',
|
|
298
|
+
'Μοντέλα',
|
|
299
|
+
)),
|
|
300
|
+
"iw": ("Hebrew", (
|
|
301
|
+
'מותגים',
|
|
302
|
+
'שירותי המקום',
|
|
303
|
+
'סגנונות',
|
|
304
|
+
'סוגים',
|
|
305
|
+
'יעדים גיאוגרפיים',
|
|
306
|
+
'שירותים',
|
|
307
|
+
'קורסים',
|
|
308
|
+
'שכונות',
|
|
309
|
+
'הופעות ותוכניות',
|
|
310
|
+
'כיסוי ביטוחי',
|
|
311
|
+
'תוכניות ללימודי תואר',
|
|
312
|
+
'מלונות מומלצים',
|
|
313
|
+
'דגמים',
|
|
314
|
+
)),
|
|
315
|
+
"hi": ("Hindi", (
|
|
316
|
+
'ब्रांड',
|
|
317
|
+
'सुविधाएं',
|
|
318
|
+
'शैलियां',
|
|
319
|
+
'प्रकार',
|
|
320
|
+
'गंतव्य',
|
|
321
|
+
'सेवाएं',
|
|
322
|
+
'पाठ्\u200dयक्रम',
|
|
323
|
+
'आस-पड़ोस',
|
|
324
|
+
'शो',
|
|
325
|
+
'बीमा कवरेज',
|
|
326
|
+
'डिग्री कार्यक्रम',
|
|
327
|
+
'प्रदर्शित होटल',
|
|
328
|
+
'मॉडल',
|
|
329
|
+
)),
|
|
330
|
+
"hu": ("Hungarian", (
|
|
331
|
+
'Márkák',
|
|
332
|
+
'Szolgáltatások',
|
|
333
|
+
'Stílusok',
|
|
334
|
+
'Típusok',
|
|
335
|
+
'Úti célok',
|
|
336
|
+
'Szolgáltatások',
|
|
337
|
+
'Tanfolyamok',
|
|
338
|
+
'Városrészek',
|
|
339
|
+
'Műsorok',
|
|
340
|
+
'Biztosítási módozat',
|
|
341
|
+
'Diplomaprogramok',
|
|
342
|
+
'Kiemelt szállodák',
|
|
343
|
+
'Modellek',
|
|
344
|
+
)),
|
|
345
|
+
"id": ("Indonesian", (
|
|
346
|
+
'Merek',
|
|
347
|
+
'Fasilitas',
|
|
348
|
+
'Gaya',
|
|
349
|
+
'Jenis',
|
|
350
|
+
'Destinasi',
|
|
351
|
+
'Layanan',
|
|
352
|
+
'Mata pelajaran',
|
|
353
|
+
'Kawasan',
|
|
354
|
+
'Acara',
|
|
355
|
+
'Cakupan asuransi',
|
|
356
|
+
'Program sarjana',
|
|
357
|
+
'Hotel pilihan',
|
|
358
|
+
'Model',
|
|
359
|
+
)),
|
|
360
|
+
"it": ("Italian", (
|
|
361
|
+
'Brand',
|
|
362
|
+
'Servizi',
|
|
363
|
+
'Stili',
|
|
364
|
+
'Tipi',
|
|
365
|
+
'Destinazioni',
|
|
366
|
+
'Servizi',
|
|
367
|
+
'Corsi',
|
|
368
|
+
'Quartieri',
|
|
369
|
+
'Programmi',
|
|
370
|
+
'Coperture assicurativa',
|
|
371
|
+
'Corsi di laurea',
|
|
372
|
+
'Hotel consigliati',
|
|
373
|
+
'Modelli',
|
|
374
|
+
)),
|
|
375
|
+
"ja": ("Japanese", (
|
|
376
|
+
'ブランド',
|
|
377
|
+
'設備',
|
|
378
|
+
'スタイル',
|
|
379
|
+
'タイプ',
|
|
380
|
+
'到着地',
|
|
381
|
+
'サービス',
|
|
382
|
+
'コース',
|
|
383
|
+
'周辺地域',
|
|
384
|
+
'番組',
|
|
385
|
+
'保険の保障',
|
|
386
|
+
'学位プログラム',
|
|
387
|
+
'おすすめのホテル',
|
|
388
|
+
'モデル',
|
|
389
|
+
)),
|
|
390
|
+
"ko": ("Korean", (
|
|
391
|
+
'브랜드',
|
|
392
|
+
'편의 시설',
|
|
393
|
+
'스타일',
|
|
394
|
+
'유형',
|
|
395
|
+
'목적지',
|
|
396
|
+
'서비스',
|
|
397
|
+
'과정',
|
|
398
|
+
'인근 지역',
|
|
399
|
+
'프로그램',
|
|
400
|
+
'보험 보상 범위',
|
|
401
|
+
'학위 취득 프로그램',
|
|
402
|
+
'추천 호텔',
|
|
403
|
+
'모델',
|
|
404
|
+
)),
|
|
405
|
+
"lv": ("Latvian", (
|
|
406
|
+
'Zīmoli',
|
|
407
|
+
'Ērtības',
|
|
408
|
+
'Stili',
|
|
409
|
+
'Veidi',
|
|
410
|
+
'Galamērķi',
|
|
411
|
+
'Pakalpojumi',
|
|
412
|
+
'Kursi',
|
|
413
|
+
'Apkaimes',
|
|
414
|
+
'Pārraides',
|
|
415
|
+
'Apdrošināšana',
|
|
416
|
+
'Studiju programmas',
|
|
417
|
+
'Piedāvātās viesnīcas',
|
|
418
|
+
'Modeļi',
|
|
419
|
+
)),
|
|
420
|
+
"lt": ("Lithuanian", (
|
|
421
|
+
'Prekių ženklai',
|
|
422
|
+
'Patogumai',
|
|
423
|
+
'Stiliai',
|
|
424
|
+
'Tipai',
|
|
425
|
+
'Paskirties vietos',
|
|
426
|
+
'Paslaugos',
|
|
427
|
+
'Kursai',
|
|
428
|
+
'Namų apylinkės',
|
|
429
|
+
'Laidos',
|
|
430
|
+
'Draudimo aprėptis',
|
|
431
|
+
'Moksliniai laipsniai',
|
|
432
|
+
'Siūlomi viešbučiai',
|
|
433
|
+
'Modeliai',
|
|
434
|
+
)),
|
|
435
|
+
"ms": ("Malay", (
|
|
436
|
+
'Jenama',
|
|
437
|
+
'Kemudahan',
|
|
438
|
+
'Gaya',
|
|
439
|
+
'Jenis',
|
|
440
|
+
'Destinasi',
|
|
441
|
+
'Perkhidmatan',
|
|
442
|
+
'Kursus',
|
|
443
|
+
'Kejiranan',
|
|
444
|
+
'Rancangan',
|
|
445
|
+
'Liputan insurans',
|
|
446
|
+
'Program ijazah',
|
|
447
|
+
'Hotel ditampilkan',
|
|
448
|
+
'Model',
|
|
449
|
+
)),
|
|
450
|
+
"no": ("Norwegian", (
|
|
451
|
+
'Merkevarer',
|
|
452
|
+
'Fasiliteter',
|
|
453
|
+
'Stiler',
|
|
454
|
+
'Typer',
|
|
455
|
+
'Destinasjoner',
|
|
456
|
+
'Tjenester',
|
|
457
|
+
'Kurs',
|
|
458
|
+
'Lokalområder',
|
|
459
|
+
'Programmer',
|
|
460
|
+
'Forsikringsdekning',
|
|
461
|
+
'Utdanningsprogrammer',
|
|
462
|
+
'Utvalgte hoteller',
|
|
463
|
+
'Modeller',
|
|
464
|
+
)),
|
|
465
|
+
"pl": ("Polish", (
|
|
466
|
+
'Marki',
|
|
467
|
+
'Udogodnienia',
|
|
468
|
+
'Style',
|
|
469
|
+
'Typy',
|
|
470
|
+
'Miejsca',
|
|
471
|
+
'Usługi',
|
|
472
|
+
'Kursy',
|
|
473
|
+
'Dzielnice',
|
|
474
|
+
'Programy',
|
|
475
|
+
'Ubezpieczenia',
|
|
476
|
+
'Kierunki studiów',
|
|
477
|
+
'Polecane hotele',
|
|
478
|
+
'Modele',
|
|
479
|
+
)),
|
|
480
|
+
"pt-BR": ("Portuguese (Brazil)", (
|
|
481
|
+
'Marcas',
|
|
482
|
+
'Comodidades',
|
|
483
|
+
'Estilos',
|
|
484
|
+
'Tipos',
|
|
485
|
+
'Destinos',
|
|
486
|
+
'Serviços',
|
|
487
|
+
'Cursos',
|
|
488
|
+
'Bairros',
|
|
489
|
+
'Programas',
|
|
490
|
+
'Cobertura do seguro',
|
|
491
|
+
'Programas de graduação',
|
|
492
|
+
'Hotéis em destaque',
|
|
493
|
+
'Modelos',
|
|
494
|
+
)),
|
|
495
|
+
"pt-PT": ("Portuguese (Portugal)", (
|
|
496
|
+
'Marcas',
|
|
497
|
+
'Comodidades',
|
|
498
|
+
'Estilos',
|
|
499
|
+
'Tipos',
|
|
500
|
+
'Destinos',
|
|
501
|
+
'Serviços',
|
|
502
|
+
'Cursos',
|
|
503
|
+
'Arredores',
|
|
504
|
+
'Programas',
|
|
505
|
+
'Cobertura do seguro',
|
|
506
|
+
'Licenciaturas',
|
|
507
|
+
'Hotéis em destaque',
|
|
508
|
+
'Modelos',
|
|
509
|
+
)),
|
|
510
|
+
"ro": ("Romanian", (
|
|
511
|
+
'Mărci',
|
|
512
|
+
'Dotări',
|
|
513
|
+
'Stiluri',
|
|
514
|
+
'Tipuri',
|
|
515
|
+
'Destinații',
|
|
516
|
+
'Servicii',
|
|
517
|
+
'Cursuri',
|
|
518
|
+
'Cartiere',
|
|
519
|
+
'Emisiuni',
|
|
520
|
+
'Asigurare',
|
|
521
|
+
'Programe de studiu',
|
|
522
|
+
'Hoteluri prezentate',
|
|
523
|
+
'Modele',
|
|
524
|
+
)),
|
|
525
|
+
"ru": ("Russian", (
|
|
526
|
+
'Бренды',
|
|
527
|
+
'Удобства',
|
|
528
|
+
'Стили',
|
|
529
|
+
'Типы',
|
|
530
|
+
'Места',
|
|
531
|
+
'Услуги',
|
|
532
|
+
'Курсы',
|
|
533
|
+
'Районы',
|
|
534
|
+
'Шоу',
|
|
535
|
+
'Страховая защита',
|
|
536
|
+
'Программы высшего образования',
|
|
537
|
+
'Рекомендуемые отели',
|
|
538
|
+
'Модели',
|
|
539
|
+
)),
|
|
540
|
+
"sr": ("Serbian", (
|
|
541
|
+
'брендови',
|
|
542
|
+
'садржаји',
|
|
543
|
+
'стилови',
|
|
544
|
+
'типови',
|
|
545
|
+
'дестинације',
|
|
546
|
+
'услуге',
|
|
547
|
+
'курсеви',
|
|
548
|
+
'делови града',
|
|
549
|
+
'емисије',
|
|
550
|
+
'осигурање',
|
|
551
|
+
'дипломе',
|
|
552
|
+
'истакнути хотели',
|
|
553
|
+
'модели',
|
|
554
|
+
)),
|
|
555
|
+
"sk": ("Slovak", (
|
|
556
|
+
'Značky',
|
|
557
|
+
'Vybavenie',
|
|
558
|
+
'Štýly',
|
|
559
|
+
'Typy',
|
|
560
|
+
'Destinácie',
|
|
561
|
+
'Služby',
|
|
562
|
+
'Kurzy',
|
|
563
|
+
'Štvrte',
|
|
564
|
+
'Relácie',
|
|
565
|
+
'Poistenie',
|
|
566
|
+
'Vzdelávacie programy',
|
|
567
|
+
'Odporúčané hotely',
|
|
568
|
+
'Modely',
|
|
569
|
+
)),
|
|
570
|
+
"sl": ("Slovenian", (
|
|
571
|
+
'Blagovne znamke',
|
|
572
|
+
'Ponudba',
|
|
573
|
+
'Slogi',
|
|
574
|
+
'Vrste',
|
|
575
|
+
'Cilji',
|
|
576
|
+
'Storitve',
|
|
577
|
+
'Tečaji',
|
|
578
|
+
'Soseske',
|
|
579
|
+
'Oddaje',
|
|
580
|
+
'Kritje zavarovanj',
|
|
581
|
+
'Študijski programi',
|
|
582
|
+
'Predstavljeni hoteli',
|
|
583
|
+
'Modeli',
|
|
584
|
+
)),
|
|
585
|
+
"es-419": ("Spanish (Latin America)", (
|
|
586
|
+
'Marcas',
|
|
587
|
+
'Servicios adicionales',
|
|
588
|
+
'Estilos',
|
|
589
|
+
'Tipos',
|
|
590
|
+
'Destinos',
|
|
591
|
+
'Servicios',
|
|
592
|
+
'Cursos',
|
|
593
|
+
'Barrios',
|
|
594
|
+
'Programas',
|
|
595
|
+
'Cobertura de seguro',
|
|
596
|
+
'Carreras universitarias',
|
|
597
|
+
'Hoteles destacados',
|
|
598
|
+
'Modelos',
|
|
599
|
+
)),
|
|
600
|
+
"es": ("Spanish (Spain)", (
|
|
601
|
+
'Marcas',
|
|
602
|
+
'Servicios adicionales',
|
|
603
|
+
'Estilos',
|
|
604
|
+
'Tipos',
|
|
605
|
+
'Destinos',
|
|
606
|
+
'Servicios',
|
|
607
|
+
'Cursos',
|
|
608
|
+
'',
|
|
609
|
+
'Espectáculos',
|
|
610
|
+
'Cobertura de seguro',
|
|
611
|
+
'Carreras universitarias',
|
|
612
|
+
'Hoteles destacados',
|
|
613
|
+
'Modelos',
|
|
614
|
+
)),
|
|
615
|
+
"sv": ("Swedish", (
|
|
616
|
+
'Varumärken',
|
|
617
|
+
'Faciliteter',
|
|
618
|
+
'Stilar',
|
|
619
|
+
'Typer',
|
|
620
|
+
'Resmål',
|
|
621
|
+
'Tjänster',
|
|
622
|
+
'Kurser',
|
|
623
|
+
'Barrios',
|
|
624
|
+
'Program',
|
|
625
|
+
'Försäkringstyper',
|
|
626
|
+
'Utbildningsprogram',
|
|
627
|
+
'Utvalda hotell',
|
|
628
|
+
'Modeller',
|
|
629
|
+
)),
|
|
630
|
+
"th": ("Thai", (
|
|
631
|
+
'แบรนด์',
|
|
632
|
+
'สิ่งอำนวยความสะดวก',
|
|
633
|
+
'สไตล์',
|
|
634
|
+
'ประเภท',
|
|
635
|
+
'ปลายทาง',
|
|
636
|
+
'บริการ',
|
|
637
|
+
'หลักสูตร',
|
|
638
|
+
'ย่านใกล้เคียง',
|
|
639
|
+
'โชว์',
|
|
640
|
+
'ความคุ้มครองในประกัน',
|
|
641
|
+
'หลักสูตรปริญญา',
|
|
642
|
+
'โรงแรมเด่น',
|
|
643
|
+
'รุ่น',
|
|
644
|
+
)),
|
|
645
|
+
"tr": ("Turkish", (
|
|
646
|
+
'Markalar',
|
|
647
|
+
'Sunulan olanaklar',
|
|
648
|
+
'Stiller',
|
|
649
|
+
'Türler',
|
|
650
|
+
'destinasyonlar',
|
|
651
|
+
'Hizmetler',
|
|
652
|
+
'Kurslar',
|
|
653
|
+
'Mahalleler',
|
|
654
|
+
'Programlar',
|
|
655
|
+
'Sigorta kapsamı',
|
|
656
|
+
'Lisans programları',
|
|
657
|
+
'Öne çıkan oteller',
|
|
658
|
+
'Modeller',
|
|
659
|
+
)),
|
|
660
|
+
"uk": ("Ukrainian", (
|
|
661
|
+
'Бренди',
|
|
662
|
+
'Зручності',
|
|
663
|
+
'Стилі',
|
|
664
|
+
'Типи',
|
|
665
|
+
'Місця',
|
|
666
|
+
'Послуги',
|
|
667
|
+
'Курси',
|
|
668
|
+
'Квартали',
|
|
669
|
+
'Шоу',
|
|
670
|
+
'Страховий захист',
|
|
671
|
+
'Освітні програми',
|
|
672
|
+
'Рекомендовані готелі',
|
|
673
|
+
'Моделі',
|
|
674
|
+
)),
|
|
675
|
+
"vi": ("Vietnamese", (
|
|
676
|
+
'Thương hiệu',
|
|
677
|
+
'Tiện nghi',
|
|
678
|
+
'Kiểu',
|
|
679
|
+
'Loại',
|
|
680
|
+
'Điểm đến',
|
|
681
|
+
'Dịch vụ',
|
|
682
|
+
'Khóa học',
|
|
683
|
+
'Vùng lân cận',
|
|
684
|
+
'Chương trình',
|
|
685
|
+
'Phạm vi bảo hiểm',
|
|
686
|
+
'Cấp bằng',
|
|
687
|
+
'Khách sạn nổi bật',
|
|
688
|
+
'Model',
|
|
689
|
+
)),
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
|
|
693
|
+
ALL_HEADERS = frozenset(ENGLISH_HEADERS) | frozenset(
|
|
694
|
+
header for _language, headers in LOCALIZED_HEADERS.values() for header in headers if header
|
|
695
|
+
)
|
|
696
|
+
|
|
697
|
+
# casefolded header -> a spelling Google lists. Only a fallback: two listed
|
|
698
|
+
# headers differ by case alone ("Featured Hotels" / en-GB "Featured hotels",
|
|
699
|
+
# Bulgarian "Модели" / Serbian "модели"), so an exact match always wins.
|
|
700
|
+
_CASEFOLDED: dict[str, str] = {}
|
|
701
|
+
for _header in sorted(ALL_HEADERS, key=lambda h: (h not in ENGLISH_HEADERS, h)):
|
|
702
|
+
_CASEFOLDED.setdefault(_header.casefold(), _header)
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
def canonical_header(header: str) -> str | None:
|
|
706
|
+
"""The listed spelling of ``header`` (exact first, then any case), or None."""
|
|
707
|
+
header = (header or "").strip()
|
|
708
|
+
if header in ALL_HEADERS:
|
|
709
|
+
return header
|
|
710
|
+
return _CASEFOLDED.get(header.casefold())
|
|
711
|
+
|
|
712
|
+
|
|
713
|
+
def english_header(header: str) -> str | None:
|
|
714
|
+
"""The English header a listed header translates (``"Typen"`` -> ``"Types"``)."""
|
|
715
|
+
canonical = canonical_header(header)
|
|
716
|
+
if canonical is None:
|
|
717
|
+
return None
|
|
718
|
+
if canonical in ENGLISH_HEADERS:
|
|
719
|
+
return canonical
|
|
720
|
+
for _language, headers in LOCALIZED_HEADERS.values():
|
|
721
|
+
if canonical in headers:
|
|
722
|
+
return ENGLISH_HEADERS[headers.index(canonical)]
|
|
723
|
+
return None
|
|
@@ -16,22 +16,6 @@ if TYPE_CHECKING:
|
|
|
16
16
|
from adloop.config import AdLoopConfig
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
_STRUCTURED_SNIPPET_HEADERS = {
|
|
20
|
-
"Amenities",
|
|
21
|
-
"Brands",
|
|
22
|
-
"Courses",
|
|
23
|
-
"Degree programs",
|
|
24
|
-
"Destinations",
|
|
25
|
-
"Featured Hotels",
|
|
26
|
-
"Insurance coverage",
|
|
27
|
-
"Models",
|
|
28
|
-
"Neighborhoods",
|
|
29
|
-
"Services",
|
|
30
|
-
"Shows",
|
|
31
|
-
"Styles",
|
|
32
|
-
"Types",
|
|
33
|
-
}
|
|
34
|
-
|
|
35
19
|
_VALID_IMAGE_MIME_TYPES = {
|
|
36
20
|
"image/gif": "IMAGE_GIF",
|
|
37
21
|
"image/jpeg": "IMAGE_JPEG",
|
|
@@ -287,7 +287,7 @@ Conversions that happen offline (a sales call, a lead that turned into a deal) g
|
|
|
287
287
|
| `update_ad_group` | Update ad group name and/or MANUAL_CPC `max_cpc` | `ad_group_id`, optional `ad_group_name`, optional `max_cpc` |
|
|
288
288
|
| `draft_responsive_search_ad` | Create RSA preview (does NOT publish) | 3-15 headlines (≤30 chars), 2-4 descriptions (≤90 chars), final_url required, path1/path2 (≤15 chars each). Each headline/description may be a plain string (unpinned) or `{"text": "...", "pinned_field": "HEADLINE_1"}` (pinned). Valid pin slots: `HEADLINE_1/2/3`, `DESCRIPTION_1/2`. Several assets may pin to one slot (they rotate there), but every slot must stay fillable: by an asset pinned to it or by a distinct unpinned asset. |
|
|
289
289
|
| `draft_callouts` | Create callout assets for an ad group, a campaign or the account (does NOT publish) | `campaign_id` or `ad_group_id` (the scope follows the id), or `scope="account"`; `callouts` list with 1-25 chars each |
|
|
290
|
-
| `draft_structured_snippets` | Create structured snippet assets for an ad group, a campaign or the account (does NOT publish) | `campaign_id` or `ad_group_id`, or `scope="account"`; `snippets` list of `{header, values}
|
|
290
|
+
| `draft_structured_snippets` | Create structured snippet assets for an ad group, a campaign or the account (does NOT publish) | `campaign_id` or `ad_group_id`, or `scope="account"`; `snippets` list of `{header, values}`: an official header in the language of the values (English `Types`, or Google's translation such as German `Typen`) and 3-10 values |
|
|
291
291
|
| `draft_business_name_asset` | Create the business name shown with the ads, for a campaign or the account | `business_name` (≤25), `campaign_id` or `scope="account"` |
|
|
292
292
|
| `link_asset_to_customer` | Link EXISTING assets account-wide (CustomerAsset) | `links`: `{asset_id, field_type}` (CALLOUT, SITELINK, STRUCTURED_SNIPPET, CALL, PRICE, PROMOTION, BUSINESS_NAME, BUSINESS_LOGO, MOBILE_APP, HOTEL_CALLOUT); type is checked |
|
|
293
293
|
| `update_callout` / `update_sitelink` / `update_structured_snippet` | Edit an existing asset IN PLACE: it keeps its ID and history and changes everywhere it is linked | `asset_id` plus the new values (sitelink: only the fields passed; snippet: the full `values` list, empty `header` keeps it) |
|
|
@@ -4239,10 +4239,13 @@ def draft_structured_snippets(
|
|
|
4239
4239
|
|
|
4240
4240
|
Args:
|
|
4241
4241
|
snippets: At least one dict with "header" and "values". header is one of
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4242
|
+
Google's snippet headers, in the language of the values: Amenities,
|
|
4243
|
+
Brands, Courses, Degree programs, Destinations, Featured Hotels,
|
|
4244
|
+
Insurance coverage, Models, Neighborhoods, Services, Shows, Styles,
|
|
4245
|
+
Types, or Google's translation of one (German: Typen, Marken,
|
|
4246
|
+
Dienstleistungen, Modelle, Kurse, ...; case does not matter).
|
|
4247
|
+
values is a list of 3-10 non-empty strings, each at most 25
|
|
4248
|
+
characters.
|
|
4246
4249
|
campaign_id: Numeric ID of the campaign to link the snippets to.
|
|
4247
4250
|
ad_group_id: Numeric ID of the ad group to link them to, instead of a
|
|
4248
4251
|
campaign.
|
|
@@ -4412,8 +4415,9 @@ def update_structured_snippet(
|
|
|
4412
4415
|
asset_id: Numeric ID of the structured snippet asset.
|
|
4413
4416
|
values: The complete new list of 3-10 values, each at most 25
|
|
4414
4417
|
characters.
|
|
4415
|
-
header: New header (
|
|
4416
|
-
keeps the current one
|
|
4418
|
+
header: New header (English or translated, as in
|
|
4419
|
+
draft_structured_snippets); empty keeps the current one as Google
|
|
4420
|
+
stores it, whatever its language.
|
|
4417
4421
|
customer_id: Google Ads customer ID (digits, dashes optional). Defaults to
|
|
4418
4422
|
the configured account.
|
|
4419
4423
|
"""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|