universal-mcp-applications 0.1.30__py3-none-any.whl → 0.1.36rc1__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.
Potentially problematic release.
This version of universal-mcp-applications might be problematic. Click here for more details.
- universal_mcp/applications/ahrefs/app.py +52 -198
- universal_mcp/applications/airtable/app.py +23 -122
- universal_mcp/applications/apollo/app.py +111 -464
- universal_mcp/applications/asana/app.py +417 -1567
- universal_mcp/applications/aws_s3/app.py +33 -100
- universal_mcp/applications/bill/app.py +546 -1957
- universal_mcp/applications/box/app.py +1068 -3981
- universal_mcp/applications/braze/app.py +364 -1430
- universal_mcp/applications/browser_use/app.py +2 -8
- universal_mcp/applications/cal_com_v2/app.py +207 -625
- universal_mcp/applications/calendly/app.py +61 -200
- universal_mcp/applications/canva/app.py +45 -110
- universal_mcp/applications/clickup/app.py +207 -674
- universal_mcp/applications/coda/app.py +146 -426
- universal_mcp/applications/confluence/app.py +310 -1098
- universal_mcp/applications/contentful/app.py +36 -151
- universal_mcp/applications/crustdata/app.py +28 -107
- universal_mcp/applications/dialpad/app.py +283 -756
- universal_mcp/applications/digitalocean/app.py +1766 -5777
- universal_mcp/applications/domain_checker/app.py +3 -54
- universal_mcp/applications/e2b/app.py +14 -64
- universal_mcp/applications/elevenlabs/app.py +9 -47
- universal_mcp/applications/exa/app.py +6 -17
- universal_mcp/applications/falai/app.py +23 -100
- universal_mcp/applications/figma/app.py +53 -137
- universal_mcp/applications/file_system/app.py +2 -13
- universal_mcp/applications/firecrawl/app.py +51 -152
- universal_mcp/applications/fireflies/app.py +59 -281
- universal_mcp/applications/fpl/app.py +91 -528
- universal_mcp/applications/fpl/utils/fixtures.py +15 -49
- universal_mcp/applications/fpl/utils/helper.py +25 -89
- universal_mcp/applications/fpl/utils/league_utils.py +20 -64
- universal_mcp/applications/ghost_content/app.py +52 -161
- universal_mcp/applications/github/app.py +19 -56
- universal_mcp/applications/gong/app.py +88 -248
- universal_mcp/applications/google_calendar/app.py +16 -68
- universal_mcp/applications/google_docs/app.py +88 -188
- universal_mcp/applications/google_drive/app.py +140 -462
- universal_mcp/applications/google_gemini/app.py +12 -64
- universal_mcp/applications/google_mail/app.py +28 -157
- universal_mcp/applications/google_searchconsole/app.py +15 -48
- universal_mcp/applications/google_sheet/app.py +101 -578
- universal_mcp/applications/google_sheet/helper.py +10 -37
- universal_mcp/applications/hashnode/app.py +57 -269
- universal_mcp/applications/heygen/app.py +44 -122
- universal_mcp/applications/http_tools/app.py +10 -32
- universal_mcp/applications/hubspot/api_segments/crm_api.py +460 -1573
- universal_mcp/applications/hubspot/api_segments/marketing_api.py +74 -262
- universal_mcp/applications/hubspot/app.py +23 -87
- universal_mcp/applications/jira/app.py +2071 -7986
- universal_mcp/applications/klaviyo/app.py +494 -1376
- universal_mcp/applications/linkedin/README.md +9 -2
- universal_mcp/applications/linkedin/app.py +392 -212
- universal_mcp/applications/mailchimp/app.py +450 -1605
- universal_mcp/applications/markitdown/app.py +8 -20
- universal_mcp/applications/miro/app.py +217 -699
- universal_mcp/applications/ms_teams/app.py +64 -186
- universal_mcp/applications/neon/app.py +86 -192
- universal_mcp/applications/notion/app.py +21 -36
- universal_mcp/applications/onedrive/app.py +14 -36
- universal_mcp/applications/openai/app.py +42 -165
- universal_mcp/applications/outlook/app.py +16 -76
- universal_mcp/applications/perplexity/app.py +4 -19
- universal_mcp/applications/pipedrive/app.py +832 -3142
- universal_mcp/applications/posthog/app.py +163 -432
- universal_mcp/applications/reddit/app.py +40 -139
- universal_mcp/applications/resend/app.py +41 -107
- universal_mcp/applications/retell/app.py +14 -41
- universal_mcp/applications/rocketlane/app.py +221 -934
- universal_mcp/applications/scraper/README.md +7 -4
- universal_mcp/applications/scraper/app.py +216 -102
- universal_mcp/applications/semanticscholar/app.py +22 -64
- universal_mcp/applications/semrush/app.py +43 -77
- universal_mcp/applications/sendgrid/app.py +512 -1262
- universal_mcp/applications/sentry/app.py +271 -906
- universal_mcp/applications/serpapi/app.py +40 -143
- universal_mcp/applications/sharepoint/app.py +15 -37
- universal_mcp/applications/shopify/app.py +1551 -4287
- universal_mcp/applications/shortcut/app.py +155 -417
- universal_mcp/applications/slack/app.py +50 -101
- universal_mcp/applications/spotify/app.py +126 -325
- universal_mcp/applications/supabase/app.py +104 -213
- universal_mcp/applications/tavily/app.py +1 -1
- universal_mcp/applications/trello/app.py +693 -2656
- universal_mcp/applications/twilio/app.py +14 -50
- universal_mcp/applications/twitter/api_segments/compliance_api.py +4 -14
- universal_mcp/applications/twitter/api_segments/dm_conversations_api.py +6 -18
- universal_mcp/applications/twitter/api_segments/likes_api.py +1 -3
- universal_mcp/applications/twitter/api_segments/lists_api.py +5 -15
- universal_mcp/applications/twitter/api_segments/trends_api.py +1 -3
- universal_mcp/applications/twitter/api_segments/tweets_api.py +9 -31
- universal_mcp/applications/twitter/api_segments/usage_api.py +1 -5
- universal_mcp/applications/twitter/api_segments/users_api.py +14 -42
- universal_mcp/applications/whatsapp/app.py +35 -186
- universal_mcp/applications/whatsapp/audio.py +2 -6
- universal_mcp/applications/whatsapp/whatsapp.py +17 -51
- universal_mcp/applications/whatsapp_business/app.py +70 -283
- universal_mcp/applications/wrike/app.py +45 -118
- universal_mcp/applications/yahoo_finance/app.py +19 -65
- universal_mcp/applications/youtube/app.py +75 -261
- universal_mcp/applications/zenquotes/app.py +2 -2
- {universal_mcp_applications-0.1.30.dist-info → universal_mcp_applications-0.1.36rc1.dist-info}/METADATA +2 -2
- {universal_mcp_applications-0.1.30.dist-info → universal_mcp_applications-0.1.36rc1.dist-info}/RECORD +105 -105
- {universal_mcp_applications-0.1.30.dist-info → universal_mcp_applications-0.1.36rc1.dist-info}/WHEEL +0 -0
- {universal_mcp_applications-0.1.30.dist-info → universal_mcp_applications-0.1.36rc1.dist-info}/licenses/LICENSE +0 -0
|
@@ -56,9 +56,7 @@ def analyze_sheet_for_tables(
|
|
|
56
56
|
return tables
|
|
57
57
|
|
|
58
58
|
|
|
59
|
-
def analyze_table_schema(
|
|
60
|
-
get_values_func, spreadsheet_id: str, table_info: dict, sample_size: int = 50
|
|
61
|
-
) -> dict[str, Any]:
|
|
59
|
+
def analyze_table_schema(get_values_func, spreadsheet_id: str, table_info: dict, sample_size: int = 50) -> dict[str, Any]:
|
|
62
60
|
"""
|
|
63
61
|
Analyze table structure and infer column names, types, and constraints.
|
|
64
62
|
|
|
@@ -115,9 +113,7 @@ def analyze_columns(sample_values: list[list[Any]]) -> list[dict]:
|
|
|
115
113
|
columns = []
|
|
116
114
|
|
|
117
115
|
for col_idx in range(len(headers)):
|
|
118
|
-
column_name = (
|
|
119
|
-
str(headers[col_idx]) if col_idx < len(headers) else f"Column_{col_idx + 1}"
|
|
120
|
-
)
|
|
116
|
+
column_name = str(headers[col_idx]) if col_idx < len(headers) else f"Column_{col_idx + 1}"
|
|
121
117
|
|
|
122
118
|
# Extract column values
|
|
123
119
|
column_values = []
|
|
@@ -134,12 +130,8 @@ def analyze_columns(sample_values: list[list[Any]]) -> list[dict]:
|
|
|
134
130
|
"type": column_type,
|
|
135
131
|
"constraints": constraints,
|
|
136
132
|
"sample_values": column_values[:5], # First 5 sample values
|
|
137
|
-
"null_count": sum(
|
|
138
|
-
|
|
139
|
-
),
|
|
140
|
-
"unique_count": len(
|
|
141
|
-
set(str(val) for val in column_values if val and str(val).strip())
|
|
142
|
-
),
|
|
133
|
+
"null_count": sum(1 for val in column_values if not val or str(val).strip() == ""),
|
|
134
|
+
"unique_count": len(set(str(val) for val in column_values if val and str(val).strip())),
|
|
143
135
|
}
|
|
144
136
|
|
|
145
137
|
columns.append(column_info)
|
|
@@ -159,11 +151,7 @@ def infer_column_type(values: list[Any]) -> tuple[str, dict]:
|
|
|
159
151
|
return "TEXT", {}
|
|
160
152
|
|
|
161
153
|
# Check for boolean values
|
|
162
|
-
boolean_count = sum(
|
|
163
|
-
1
|
|
164
|
-
for val in non_empty_values
|
|
165
|
-
if str(val).lower() in ["true", "false", "yes", "no", "1", "0"]
|
|
166
|
-
)
|
|
154
|
+
boolean_count = sum(1 for val in non_empty_values if str(val).lower() in ["true", "false", "yes", "no", "1", "0"])
|
|
167
155
|
if boolean_count / len(non_empty_values) >= 0.8:
|
|
168
156
|
return "BOOLEAN", {}
|
|
169
157
|
|
|
@@ -215,9 +203,7 @@ def infer_column_type(values: list[Any]) -> tuple[str, dict]:
|
|
|
215
203
|
return "TEXT", {}
|
|
216
204
|
|
|
217
205
|
|
|
218
|
-
def find_table_regions(
|
|
219
|
-
values: list[list], min_rows: int, min_columns: int
|
|
220
|
-
) -> list[dict]:
|
|
206
|
+
def find_table_regions(values: list[list], min_rows: int, min_columns: int) -> list[dict]:
|
|
221
207
|
"""Find potential table regions in the data."""
|
|
222
208
|
regions = []
|
|
223
209
|
|
|
@@ -290,9 +276,7 @@ def calculate_table_confidence(values: list[list], region: dict) -> float:
|
|
|
290
276
|
|
|
291
277
|
# Calculate confidence based on data consistency
|
|
292
278
|
total_cells = sum(len(row) for row in region_data)
|
|
293
|
-
non_empty_cells = sum(
|
|
294
|
-
sum(1 for cell in row if cell and str(cell).strip()) for row in region_data
|
|
295
|
-
)
|
|
279
|
+
non_empty_cells = sum(sum(1 for cell in row if cell and str(cell).strip()) for row in region_data)
|
|
296
280
|
|
|
297
281
|
if total_cells == 0:
|
|
298
282
|
return 0.0
|
|
@@ -328,11 +312,7 @@ def has_header_row(data: list[list]) -> bool:
|
|
|
328
312
|
|
|
329
313
|
# Check if header row has mostly text values
|
|
330
314
|
header_text_count = sum(
|
|
331
|
-
1
|
|
332
|
-
for cell in header_row
|
|
333
|
-
if cell
|
|
334
|
-
and isinstance(cell, str)
|
|
335
|
-
and not cell.replace(".", "").replace("-", "").isdigit()
|
|
315
|
+
1 for cell in header_row if cell and isinstance(cell, str) and not cell.replace(".", "").replace("-", "").isdigit()
|
|
336
316
|
)
|
|
337
317
|
|
|
338
318
|
# Check if data rows have different data types than header
|
|
@@ -358,18 +338,11 @@ def has_consistent_columns(data: list[list]) -> bool:
|
|
|
358
338
|
column_values = [row[col] for row in data if col < len(row) and row[col]]
|
|
359
339
|
if len(column_values) >= 2:
|
|
360
340
|
# Check if column has consistent type
|
|
361
|
-
numeric_count = sum(
|
|
362
|
-
1
|
|
363
|
-
for val in column_values
|
|
364
|
-
if str(val).replace(".", "").replace("-", "").isdigit()
|
|
365
|
-
)
|
|
341
|
+
numeric_count = sum(1 for val in column_values if str(val).replace(".", "").replace("-", "").isdigit())
|
|
366
342
|
text_count = len(column_values) - numeric_count
|
|
367
343
|
|
|
368
344
|
# If 80% of values are same type, consider consistent
|
|
369
|
-
if (
|
|
370
|
-
numeric_count / len(column_values) >= 0.8
|
|
371
|
-
or text_count / len(column_values) >= 0.8
|
|
372
|
-
):
|
|
345
|
+
if numeric_count / len(column_values) >= 0.8 or text_count / len(column_values) >= 0.8:
|
|
373
346
|
consistent_columns += 1
|
|
374
347
|
|
|
375
348
|
return consistent_columns / total_columns >= 0.6 if total_columns > 0 else False
|
|
@@ -8,7 +8,7 @@ class HashnodeApp(GraphQLApplication):
|
|
|
8
8
|
super().__init__(name="hashnode", base_url="https://gql.hashnode.com", **kwargs)
|
|
9
9
|
self.integration = integration
|
|
10
10
|
|
|
11
|
-
def publish_post(
|
|
11
|
+
async def publish_post(
|
|
12
12
|
self,
|
|
13
13
|
publication_id: str,
|
|
14
14
|
title: str,
|
|
@@ -38,42 +38,22 @@ class HashnodeApp(GraphQLApplication):
|
|
|
38
38
|
Tags:
|
|
39
39
|
publish, post, hashnode, api, important
|
|
40
40
|
"""
|
|
41
|
-
publish_post_mutation = gql(
|
|
42
|
-
mutation PublishPost($input: PublishPostInput!) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
url
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
""")
|
|
50
|
-
|
|
51
|
-
variables = {
|
|
52
|
-
"input": {
|
|
53
|
-
"publicationId": publication_id,
|
|
54
|
-
"title": title,
|
|
55
|
-
"contentMarkdown": content,
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
41
|
+
publish_post_mutation = gql(
|
|
42
|
+
"\n mutation PublishPost($input: PublishPostInput!) {\n publishPost(input: $input) {\n post {\n url\n }\n }\n }\n "
|
|
43
|
+
)
|
|
44
|
+
variables = {"input": {"publicationId": publication_id, "title": title, "contentMarkdown": content}}
|
|
59
45
|
if tags:
|
|
60
|
-
variables["input"]["tags"] = [
|
|
61
|
-
{"name": tag, "slug": tag.replace(" ", "-").lower()} for tag in tags
|
|
62
|
-
]
|
|
63
|
-
|
|
46
|
+
variables["input"]["tags"] = [{"name": tag, "slug": tag.replace(" ", "-").lower()} for tag in tags]
|
|
64
47
|
if slug:
|
|
65
48
|
variables["input"]["slug"] = slug
|
|
66
|
-
|
|
67
49
|
if subtitle:
|
|
68
50
|
variables["input"]["subtitle"] = subtitle
|
|
69
|
-
|
|
70
51
|
if cover_image:
|
|
71
52
|
variables["input"]["coverImageOptions"] = {"coverImageURL": cover_image}
|
|
72
|
-
|
|
73
53
|
result = self.mutate(publish_post_mutation, variables)
|
|
74
54
|
return result["publishPost"]["post"]["url"]
|
|
75
55
|
|
|
76
|
-
def create_draft(
|
|
56
|
+
async def create_draft(
|
|
77
57
|
self,
|
|
78
58
|
publication_id: str,
|
|
79
59
|
title: str,
|
|
@@ -104,39 +84,22 @@ class HashnodeApp(GraphQLApplication):
|
|
|
104
84
|
Tags:
|
|
105
85
|
create, draft, post, hashnode, api, important
|
|
106
86
|
"""
|
|
107
|
-
create_draft_mutation = gql(
|
|
108
|
-
mutation CreateDraft($input: CreateDraftInput!) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
id
|
|
112
|
-
slug
|
|
113
|
-
title
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
""")
|
|
118
|
-
variables = {
|
|
119
|
-
"input": {
|
|
120
|
-
"publicationId": publication_id,
|
|
121
|
-
"title": title,
|
|
122
|
-
"contentMarkdown": content,
|
|
123
|
-
}
|
|
124
|
-
}
|
|
87
|
+
create_draft_mutation = gql(
|
|
88
|
+
"\n mutation CreateDraft($input: CreateDraftInput!) {\n createDraft(input: $input) {\n draft {\n id\n slug\n title\n }\n }\n }\n "
|
|
89
|
+
)
|
|
90
|
+
variables = {"input": {"publicationId": publication_id, "title": title, "contentMarkdown": content}}
|
|
125
91
|
if tags:
|
|
126
|
-
variables["input"]["tags"] = [
|
|
127
|
-
{"name": tag, "slug": tag.replace(" ", "-").lower()} for tag in tags
|
|
128
|
-
]
|
|
92
|
+
variables["input"]["tags"] = [{"name": tag, "slug": tag.replace(" ", "-").lower()} for tag in tags]
|
|
129
93
|
if slug:
|
|
130
94
|
variables["input"]["slug"] = slug
|
|
131
95
|
if subtitle:
|
|
132
96
|
variables["input"]["subtitle"] = subtitle
|
|
133
97
|
if cover_image:
|
|
134
98
|
variables["input"]["coverImageOptions"] = {"coverImageURL": cover_image}
|
|
135
|
-
|
|
136
99
|
result = self.mutate(create_draft_mutation, variables)
|
|
137
100
|
return result["createDraft"]["draft"]
|
|
138
101
|
|
|
139
|
-
def publish_draft(self, post_id: str) -> str:
|
|
102
|
+
async def publish_draft(self, post_id: str) -> str:
|
|
140
103
|
"""
|
|
141
104
|
Publishes a draft post.
|
|
142
105
|
|
|
@@ -152,20 +115,14 @@ class HashnodeApp(GraphQLApplication):
|
|
|
152
115
|
Tags:
|
|
153
116
|
publish, draft, post, hashnode, api, important
|
|
154
117
|
"""
|
|
155
|
-
publish_draft_mutation = gql(
|
|
156
|
-
mutation PublishDraft($input: PublishDraftInput!) {
|
|
157
|
-
|
|
158
|
-
post {
|
|
159
|
-
url
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
""")
|
|
118
|
+
publish_draft_mutation = gql(
|
|
119
|
+
"\n mutation PublishDraft($input: PublishDraftInput!) {\n publishDraft(input: $input) {\n post {\n url\n }\n }\n }\n "
|
|
120
|
+
)
|
|
164
121
|
variables = {"input": {"draftId": post_id}}
|
|
165
122
|
result = self.mutate(publish_draft_mutation, variables)
|
|
166
123
|
return result["publishDraft"]["post"]["url"]
|
|
167
124
|
|
|
168
|
-
def get_me(self) -> dict:
|
|
125
|
+
async def get_me(self) -> dict:
|
|
169
126
|
"""
|
|
170
127
|
Fetches details about the authenticated user.
|
|
171
128
|
|
|
@@ -178,58 +135,16 @@ class HashnodeApp(GraphQLApplication):
|
|
|
178
135
|
Tags:
|
|
179
136
|
get, me, hashnode, api, query, important
|
|
180
137
|
"""
|
|
181
|
-
get_me_query = gql(
|
|
182
|
-
query Me {
|
|
183
|
-
|
|
184
|
-
id
|
|
185
|
-
username
|
|
186
|
-
name
|
|
187
|
-
bio {
|
|
188
|
-
markdown
|
|
189
|
-
html
|
|
190
|
-
text
|
|
191
|
-
}
|
|
192
|
-
profilePicture
|
|
193
|
-
socialMediaLinks {
|
|
194
|
-
website
|
|
195
|
-
github
|
|
196
|
-
twitter
|
|
197
|
-
instagram
|
|
198
|
-
facebook
|
|
199
|
-
stackoverflow
|
|
200
|
-
linkedin
|
|
201
|
-
youtube
|
|
202
|
-
bluesky
|
|
203
|
-
}
|
|
204
|
-
emailNotificationPreferences {
|
|
205
|
-
weeklyNewsletterEmails
|
|
206
|
-
activityNotifications
|
|
207
|
-
generalAnnouncements
|
|
208
|
-
monthlyBlogStats
|
|
209
|
-
newFollowersWeekly
|
|
210
|
-
}
|
|
211
|
-
followersCount
|
|
212
|
-
followingsCount
|
|
213
|
-
tagline
|
|
214
|
-
dateJoined
|
|
215
|
-
location
|
|
216
|
-
availableFor
|
|
217
|
-
email
|
|
218
|
-
unverifiedEmail
|
|
219
|
-
role
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
""")
|
|
223
|
-
|
|
138
|
+
get_me_query = gql(
|
|
139
|
+
"\n query Me {\n me {\n id\n username\n name\n bio {\n markdown\n html\n text\n }\n profilePicture\n socialMediaLinks {\n website\n github\n twitter\n instagram\n facebook\n stackoverflow\n linkedin\n youtube\n bluesky\n }\n emailNotificationPreferences {\n weeklyNewsletterEmails\n activityNotifications\n generalAnnouncements\n monthlyBlogStats\n newFollowersWeekly\n }\n followersCount\n followingsCount\n tagline\n dateJoined\n location\n availableFor\n email\n unverifiedEmail\n role\n }\n }\n "
|
|
140
|
+
)
|
|
224
141
|
result = self.query(get_me_query)
|
|
225
|
-
|
|
226
|
-
# It's good practice to check if 'me' exists in the result
|
|
227
142
|
if "me" in result:
|
|
228
143
|
return result.get("me")
|
|
229
144
|
else:
|
|
230
145
|
raise Exception("Failed to retrieve 'me' data from Hashnode API response.")
|
|
231
146
|
|
|
232
|
-
def get_publication(self, host: str = None, publication_id: str = None) -> dict:
|
|
147
|
+
async def get_publication(self, host: str = None, publication_id: str = None) -> dict:
|
|
233
148
|
"""
|
|
234
149
|
Fetches details about a publication by host or ID. Only one of host or publication_id should be provided.
|
|
235
150
|
|
|
@@ -247,40 +162,18 @@ class HashnodeApp(GraphQLApplication):
|
|
|
247
162
|
Tags:
|
|
248
163
|
get, publication, hashnode, api, query, important
|
|
249
164
|
"""
|
|
250
|
-
if not host and not publication_id:
|
|
165
|
+
if not host and (not publication_id):
|
|
251
166
|
raise ValueError("Either host or publication_id must be provided.")
|
|
252
|
-
|
|
253
|
-
query_string = """
|
|
254
|
-
query Publication($host: String, $id: ObjectId) {
|
|
255
|
-
publication(host: $host, id: $id) {
|
|
256
|
-
id
|
|
257
|
-
title
|
|
258
|
-
url
|
|
259
|
-
isTeam
|
|
260
|
-
posts(first: 5) {
|
|
261
|
-
edges {
|
|
262
|
-
node {
|
|
263
|
-
id
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
"""
|
|
167
|
+
query_string = "\n query Publication($host: String, $id: ObjectId) {\n publication(host: $host, id: $id) {\n id\n title\n url\n isTeam\n posts(first: 5) {\n edges {\n node {\n id\n }\n }\n }\n }\n }\n "
|
|
270
168
|
get_publication_query = gql(query_string)
|
|
271
|
-
|
|
272
|
-
# Construct variables such that only one of host or id is included
|
|
273
169
|
if host:
|
|
274
170
|
variables = {"host": host}
|
|
275
171
|
else:
|
|
276
172
|
variables = {"id": publication_id}
|
|
277
|
-
|
|
278
173
|
result = self.query(get_publication_query, variables)
|
|
279
174
|
return result.get("publication")
|
|
280
175
|
|
|
281
|
-
def list_posts(
|
|
282
|
-
self, publication_id: str, first: int = 10, after: str = None
|
|
283
|
-
) -> dict:
|
|
176
|
+
async def list_posts(self, publication_id: str, first: int = 10, after: str = None) -> dict:
|
|
284
177
|
"""
|
|
285
178
|
Lists posts from a publication.
|
|
286
179
|
|
|
@@ -298,36 +191,16 @@ class HashnodeApp(GraphQLApplication):
|
|
|
298
191
|
Tags:
|
|
299
192
|
list, posts, hashnode, api, query, important
|
|
300
193
|
"""
|
|
301
|
-
list_posts_query = gql(
|
|
302
|
-
query Publication($id: ObjectId!, $first: Int!, $after: String) {
|
|
303
|
-
|
|
304
|
-
posts(first: $first, after: $after) {
|
|
305
|
-
edges {
|
|
306
|
-
node {
|
|
307
|
-
id
|
|
308
|
-
title
|
|
309
|
-
slug
|
|
310
|
-
url
|
|
311
|
-
}
|
|
312
|
-
cursor
|
|
313
|
-
}
|
|
314
|
-
pageInfo {
|
|
315
|
-
endCursor
|
|
316
|
-
hasNextPage
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
""")
|
|
194
|
+
list_posts_query = gql(
|
|
195
|
+
"\n query Publication($id: ObjectId!, $first: Int!, $after: String) {\n publication(id: $id) {\n posts(first: $first, after: $after) {\n edges {\n node {\n id\n title\n slug\n url\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n "
|
|
196
|
+
)
|
|
322
197
|
variables = {"id": publication_id, "first": first}
|
|
323
198
|
if after:
|
|
324
199
|
variables["after"] = after
|
|
325
200
|
result = self.query(list_posts_query, variables)
|
|
326
201
|
return result.get("publication", {}).get("posts")
|
|
327
202
|
|
|
328
|
-
def get_post(
|
|
329
|
-
self, post_id: str = None, slug: str = None, hostname: str = None
|
|
330
|
-
) -> dict:
|
|
203
|
+
async def get_post(self, post_id: str = None, slug: str = None, hostname: str = None) -> dict:
|
|
331
204
|
"""
|
|
332
205
|
Fetches details of a single post by ID, or by slug and hostname.
|
|
333
206
|
|
|
@@ -346,45 +219,21 @@ class HashnodeApp(GraphQLApplication):
|
|
|
346
219
|
Tags:
|
|
347
220
|
get, post, hashnode, api, query, important
|
|
348
221
|
"""
|
|
349
|
-
if not post_id and not (slug and hostname):
|
|
350
|
-
raise ValueError(
|
|
351
|
-
|
|
352
|
-
)
|
|
353
|
-
|
|
354
|
-
get_post_query = gql("""
|
|
355
|
-
query Post($id: ID, $slug: String, $hostname: String) {
|
|
356
|
-
post(id: $id, slug: $slug, hostname: $hostname) {
|
|
357
|
-
id
|
|
358
|
-
slug
|
|
359
|
-
previousSlugs
|
|
360
|
-
title
|
|
361
|
-
subtitle
|
|
362
|
-
author {
|
|
363
|
-
id
|
|
364
|
-
username
|
|
365
|
-
name
|
|
366
|
-
}
|
|
367
|
-
comments(first: 5) {
|
|
368
|
-
edges {
|
|
369
|
-
node {
|
|
370
|
-
id
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
""")
|
|
222
|
+
if not post_id and (not (slug and hostname)):
|
|
223
|
+
raise ValueError("Either post_id or both slug and hostname must be provided.")
|
|
224
|
+
get_post_query = gql(
|
|
225
|
+
"\n query Post($id: ID, $slug: String, $hostname: String) {\n post(id: $id, slug: $slug, hostname: $hostname) {\n id\n slug\n previousSlugs\n title\n subtitle\n \t\t\tauthor {\n id\n username\n name\n }\n comments(first: 5) {\n edges {\n node {\n id\n }\n }\n }\n }\n }\n "
|
|
226
|
+
)
|
|
377
227
|
variables = {}
|
|
378
228
|
if post_id:
|
|
379
229
|
variables["id"] = post_id
|
|
380
230
|
else:
|
|
381
231
|
variables["slug"] = slug
|
|
382
232
|
variables["hostname"] = hostname
|
|
383
|
-
|
|
384
233
|
result = self.query(get_post_query, variables)
|
|
385
234
|
return result.get("post")
|
|
386
235
|
|
|
387
|
-
def modify_post(
|
|
236
|
+
async def modify_post(
|
|
388
237
|
self,
|
|
389
238
|
post_id: str,
|
|
390
239
|
title: str = None,
|
|
@@ -415,41 +264,26 @@ class HashnodeApp(GraphQLApplication):
|
|
|
415
264
|
Tags:
|
|
416
265
|
modify, post, hashnode, api, important
|
|
417
266
|
"""
|
|
418
|
-
update_post_mutation = gql(
|
|
419
|
-
mutation UpdatePost($input: UpdatePostInput!) {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
url
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
""")
|
|
427
|
-
|
|
428
|
-
variables = {
|
|
429
|
-
"input": {
|
|
430
|
-
"id": post_id,
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
|
|
267
|
+
update_post_mutation = gql(
|
|
268
|
+
"\n mutation UpdatePost($input: UpdatePostInput!) {\n updatePost(input: $input) {\n post {\n url\n }\n }\n }\n "
|
|
269
|
+
)
|
|
270
|
+
variables = {"input": {"id": post_id}}
|
|
434
271
|
if title:
|
|
435
272
|
variables["input"]["title"] = title
|
|
436
273
|
if content:
|
|
437
274
|
variables["input"]["contentMarkdown"] = content
|
|
438
275
|
if tags:
|
|
439
|
-
variables["input"]["tags"] = [
|
|
440
|
-
{"name": tag, "slug": tag.replace(" ", "-").lower()} for tag in tags
|
|
441
|
-
]
|
|
276
|
+
variables["input"]["tags"] = [{"name": tag, "slug": tag.replace(" ", "-").lower()} for tag in tags]
|
|
442
277
|
if slug:
|
|
443
278
|
variables["input"]["slug"] = slug
|
|
444
279
|
if subtitle:
|
|
445
280
|
variables["input"]["subtitle"] = subtitle
|
|
446
281
|
if cover_image:
|
|
447
282
|
variables["input"]["coverImageOptions"] = {"coverImageURL": cover_image}
|
|
448
|
-
|
|
449
283
|
result = self.mutate(update_post_mutation, variables)
|
|
450
284
|
return result["updatePost"]["post"]["url"]
|
|
451
285
|
|
|
452
|
-
def delete_post(self, post_id: str) -> str:
|
|
286
|
+
async def delete_post(self, post_id: str) -> str:
|
|
453
287
|
"""
|
|
454
288
|
Deletes a post using the GraphQL API.
|
|
455
289
|
|
|
@@ -465,24 +299,14 @@ class HashnodeApp(GraphQLApplication):
|
|
|
465
299
|
Tags:
|
|
466
300
|
delete, post, hashnode, api
|
|
467
301
|
"""
|
|
468
|
-
delete_post_mutation = gql(
|
|
469
|
-
mutation RemovePost($input: RemovePostInput!) {
|
|
470
|
-
|
|
471
|
-
post {
|
|
472
|
-
id
|
|
473
|
-
slug
|
|
474
|
-
previousSlugs
|
|
475
|
-
title
|
|
476
|
-
subtitle
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
""")
|
|
302
|
+
delete_post_mutation = gql(
|
|
303
|
+
"\n mutation RemovePost($input: RemovePostInput!) {\n removePost(input: $input) {\n post {\n id\n slug\n previousSlugs\n title\n subtitle\n }\n }\n }\n "
|
|
304
|
+
)
|
|
481
305
|
variables = {"input": {"id": post_id}}
|
|
482
306
|
result = self.mutate(delete_post_mutation, variables)
|
|
483
307
|
return result.get("removePost", {}).get("post", "Post deleted successfully")
|
|
484
308
|
|
|
485
|
-
def add_comment(self, post_id: str, content: str) -> dict:
|
|
309
|
+
async def add_comment(self, post_id: str, content: str) -> dict:
|
|
486
310
|
"""
|
|
487
311
|
Adds a comment to a post using the GraphQL API.
|
|
488
312
|
|
|
@@ -499,32 +323,14 @@ class HashnodeApp(GraphQLApplication):
|
|
|
499
323
|
Tags:
|
|
500
324
|
add, comment, post, hashnode, api, important
|
|
501
325
|
"""
|
|
502
|
-
add_comment_mutation = gql(
|
|
503
|
-
mutation AddComment($input: AddCommentInput!) {
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
id
|
|
507
|
-
content {
|
|
508
|
-
markdown
|
|
509
|
-
}
|
|
510
|
-
author {
|
|
511
|
-
id
|
|
512
|
-
username
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
""")
|
|
518
|
-
variables = {
|
|
519
|
-
"input": {
|
|
520
|
-
"postId": post_id,
|
|
521
|
-
"contentMarkdown": content,
|
|
522
|
-
}
|
|
523
|
-
}
|
|
326
|
+
add_comment_mutation = gql(
|
|
327
|
+
"\n mutation AddComment($input: AddCommentInput!) {\n addComment(input: $input) {\n comment {\n id\n content {\n markdown\n }\n author {\n id\n username\n }\n }\n }\n }\n "
|
|
328
|
+
)
|
|
329
|
+
variables = {"input": {"postId": post_id, "contentMarkdown": content}}
|
|
524
330
|
result = self.mutate(add_comment_mutation, variables)
|
|
525
331
|
return result.get("addComment", {}).get("comment")
|
|
526
332
|
|
|
527
|
-
def delete_comment(self, comment_id: str) -> str:
|
|
333
|
+
async def delete_comment(self, comment_id: str) -> str:
|
|
528
334
|
"""
|
|
529
335
|
Deletes a comment using the GraphQL API.
|
|
530
336
|
|
|
@@ -540,22 +346,14 @@ class HashnodeApp(GraphQLApplication):
|
|
|
540
346
|
Tags:
|
|
541
347
|
delete, comment, hashnode, api
|
|
542
348
|
"""
|
|
543
|
-
delete_comment_mutation = gql(
|
|
544
|
-
mutation RemoveComment($input: RemoveCommentInput!) {
|
|
545
|
-
|
|
546
|
-
comment {
|
|
547
|
-
id
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
""")
|
|
349
|
+
delete_comment_mutation = gql(
|
|
350
|
+
"\n mutation RemoveComment($input: RemoveCommentInput!) {\n removeComment(input: $input) {\n comment {\n id\n }\n }\n }\n "
|
|
351
|
+
)
|
|
552
352
|
variables = {"input": {"id": comment_id}}
|
|
553
353
|
result = self.mutate(delete_comment_mutation, variables)
|
|
554
|
-
return result.get("removeComment", {}).get(
|
|
555
|
-
"comment", "Comment deleted successfully"
|
|
556
|
-
)
|
|
354
|
+
return result.get("removeComment", {}).get("comment", "Comment deleted successfully")
|
|
557
355
|
|
|
558
|
-
def get_user(self, username: str) -> dict:
|
|
356
|
+
async def get_user(self, username: str) -> dict:
|
|
559
357
|
"""
|
|
560
358
|
Fetches details about a user by username.
|
|
561
359
|
|
|
@@ -571,19 +369,9 @@ class HashnodeApp(GraphQLApplication):
|
|
|
571
369
|
Tags:
|
|
572
370
|
get, user, hashnode, api, query
|
|
573
371
|
"""
|
|
574
|
-
get_user_query = gql(
|
|
575
|
-
query User($username: String!) {
|
|
576
|
-
|
|
577
|
-
id
|
|
578
|
-
username
|
|
579
|
-
name
|
|
580
|
-
tagline
|
|
581
|
-
profilePicture
|
|
582
|
-
followersCount
|
|
583
|
-
followingsCount
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
""")
|
|
372
|
+
get_user_query = gql(
|
|
373
|
+
"\n query User($username: String!) {\n user(username: $username) {\n id\n username\n name\n tagline\n profilePicture\n followersCount\n followingsCount\n }\n }\n "
|
|
374
|
+
)
|
|
587
375
|
variables = {"username": username}
|
|
588
376
|
result = self.query(get_user_query, variables)
|
|
589
377
|
return result.get("user")
|