agoras 2.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.
agoras/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Namespace package for agoras
3
+ __path__ = __import__('pkgutil').extend_path(__path__, __name__)
agoras/cli/__init__.py ADDED
@@ -0,0 +1,27 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Please refer to AUTHORS.rst for a complete list of Copyright holders.
4
+ # Copyright (C) 2022-2026, Agoras Developers.
5
+
6
+ # This program is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
18
+ """
19
+ Agoras CLI Package.
20
+
21
+ This package provides the command-line interface for agoras, enabling
22
+ interaction with various social media platforms through a unified CLI.
23
+ """
24
+
25
+ from agoras.common.version import __author__, __email__, __version__
26
+
27
+ __all__ = ['__version__', '__author__', '__email__']
agoras/cli/base.py ADDED
@@ -0,0 +1,71 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Please refer to AUTHORS.rst for a complete list of Copyright holders.
4
+ # Copyright (C) 2022-2026, Agoras Developers.
5
+
6
+ # This program is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
18
+ """
19
+ Base utilities for CLI.
20
+
21
+ This module provides common helper functions for argument parsing and
22
+ CLI utilities shared across platform commands.
23
+ """
24
+
25
+ from argparse import ArgumentParser
26
+
27
+
28
+ def add_common_content_options(parser: ArgumentParser, images: int = 0):
29
+ """
30
+ Add common content options (text, link, images).
31
+
32
+ Args:
33
+ parser: ArgumentParser to add options to
34
+ images: Number of image options to add (0-4)
35
+ """
36
+ content = parser.add_argument_group('Content Options')
37
+
38
+ content.add_argument('--text',
39
+ help='Text content of the post')
40
+ content.add_argument('--link',
41
+ help='URL to include in post')
42
+
43
+ if images > 0:
44
+ for i in range(1, images + 1):
45
+ content.add_argument(f'--image-{i}',
46
+ help=f'Image URL #{i}')
47
+
48
+
49
+ def add_video_options(parser: ArgumentParser, platform: str = None):
50
+ """
51
+ Add video-specific options.
52
+
53
+ Args:
54
+ parser: ArgumentParser to add options to
55
+ platform: Optional platform key for contract-based --video-url help
56
+ """
57
+ from .media_help import video_url_help
58
+
59
+ help_text = video_url_help(platform) if platform else 'URL of video file to upload'
60
+ video = parser.add_argument_group('Video Options')
61
+ video.add_argument(
62
+ '--video-url',
63
+ required=True,
64
+ metavar='<url>',
65
+ help=help_text,
66
+ )
67
+ video.add_argument(
68
+ '--video-title',
69
+ metavar='<title>',
70
+ help='Video title/description',
71
+ )
@@ -0,0 +1,17 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Please refer to AUTHORS.rst for a complete list of Copyright holders.
4
+ # Copyright (C) 2022-2026, Agoras Developers.
5
+
6
+ # This program is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
@@ -0,0 +1,68 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Please refer to AUTHORS.rst for a complete list of Copyright holders.
4
+ # Copyright (C) 2022-2026, Agoras Developers.
5
+
6
+ # This program is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
18
+
19
+ from agoras.platforms.discord.wrapper import main as discord
20
+ from agoras.platforms.facebook.wrapper import main as facebook
21
+ from agoras.platforms.instagram.wrapper import main as instagram
22
+ from agoras.platforms.linkedin.wrapper import main as linkedin
23
+ from agoras.platforms.telegram.wrapper import main as telegram
24
+ from agoras.platforms.threads.wrapper import main as threads
25
+ from agoras.platforms.tiktok.wrapper import main as tiktok
26
+ from agoras.platforms.whatsapp.wrapper import main as whatsapp
27
+ from agoras.platforms.x.wrapper import main as x
28
+ from agoras.platforms.youtube.wrapper import main as youtube
29
+
30
+
31
+ def main(**kwargs):
32
+ import sys
33
+ import warnings
34
+
35
+ network = kwargs.get('network')
36
+
37
+ if network == 'x':
38
+ x(kwargs)
39
+ elif network == 'twitter':
40
+ print("Warning: The 'twitter' network name is deprecated. Use 'x' instead.", file=sys.stderr)
41
+ warnings.warn(
42
+ "The 'twitter' network name is deprecated. Use 'x' instead.",
43
+ DeprecationWarning,
44
+ stacklevel=2
45
+ )
46
+ x(kwargs)
47
+ elif network == 'facebook':
48
+ facebook(kwargs)
49
+ elif network == 'instagram':
50
+ instagram(kwargs)
51
+ elif network == 'linkedin':
52
+ linkedin(kwargs)
53
+ elif network == 'discord':
54
+ discord(kwargs)
55
+ elif network == 'youtube':
56
+ youtube(kwargs)
57
+ elif network == 'tiktok':
58
+ tiktok(kwargs)
59
+ elif network == 'threads':
60
+ threads(kwargs)
61
+ elif network == 'telegram':
62
+ telegram(kwargs)
63
+ elif network == 'whatsapp':
64
+ whatsapp(kwargs)
65
+ elif network == '':
66
+ raise Exception('--network is a required argument.')
67
+ else:
68
+ raise Exception(f'"{network}" network not supported.')
@@ -0,0 +1,409 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Please refer to AUTHORS.rst for a complete list of Copyright holders.
4
+ # Copyright (C) 2022-2026, Agoras Developers.
5
+
6
+ # This program is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
18
+ """
19
+ Parameter converter for CLI.
20
+
21
+ This module converts between new and legacy parameter formats.
22
+ This is a skeleton implementation to be fully populated in later phases.
23
+ """
24
+
25
+ from argparse import Namespace
26
+ from typing import Any, Dict
27
+
28
+
29
+ class ParameterConverter:
30
+ """Convert between new and legacy parameter formats."""
31
+
32
+ # Platform-specific parameter mappings
33
+ # To be fully populated in Phase 3 (Week 5)
34
+ PLATFORM_MAPPINGS: Dict[str, Dict[str, str]] = {
35
+ 'x': {
36
+ # Auth
37
+ 'consumer_key': 'twitter_consumer_key',
38
+ 'consumer_secret': 'twitter_consumer_secret',
39
+ 'oauth_token': 'twitter_oauth_token',
40
+ 'oauth_secret': 'twitter_oauth_secret',
41
+ # Content
42
+ 'post_id': 'tweet_id',
43
+ 'video_url': 'twitter_video_url',
44
+ 'video_title': 'twitter_video_title',
45
+ },
46
+ 'twitter': {
47
+ # Auth
48
+ 'consumer_key': 'twitter_consumer_key',
49
+ 'consumer_secret': 'twitter_consumer_secret',
50
+ 'oauth_token': 'twitter_oauth_token',
51
+ 'oauth_secret': 'twitter_oauth_secret',
52
+ # Content
53
+ 'post_id': 'tweet_id',
54
+ 'video_url': 'twitter_video_url',
55
+ 'video_title': 'twitter_video_title',
56
+ },
57
+ 'facebook': {
58
+ 'client_id': 'facebook_client_id',
59
+ 'client_secret': 'facebook_client_secret',
60
+ 'app_id': 'facebook_app_id',
61
+ 'object_id': 'facebook_object_id',
62
+ 'post_id': 'facebook_post_id',
63
+ 'profile_id': 'facebook_profile_id',
64
+ 'video_url': 'facebook_video_url',
65
+ 'video_title': 'facebook_video_title',
66
+ 'video_description': 'facebook_video_description',
67
+ 'video_type': 'facebook_video_type',
68
+ },
69
+ 'instagram': {
70
+ 'client_id': 'instagram_client_id',
71
+ 'client_secret': 'instagram_client_secret',
72
+ 'object_id': 'instagram_object_id',
73
+ 'post_id': 'instagram_post_id',
74
+ 'video_url': 'instagram_video_url',
75
+ 'video_caption': 'instagram_video_caption',
76
+ 'video_type': 'instagram_video_type',
77
+ },
78
+ 'linkedin': {
79
+ 'client_id': 'linkedin_client_id',
80
+ 'client_secret': 'linkedin_client_secret',
81
+ 'object_id': 'linkedin_object_id',
82
+ 'post_id': 'linkedin_post_id',
83
+ 'video_url': 'linkedin_video_url',
84
+ 'video_title': 'linkedin_video_title',
85
+ },
86
+ 'discord': {
87
+ 'bot_token': 'discord_bot_token',
88
+ 'server_name': 'discord_server_name',
89
+ 'channel_name': 'discord_channel_name',
90
+ 'post_id': 'discord_post_id',
91
+ 'video_url': 'discord_video_url',
92
+ 'video_title': 'discord_video_title',
93
+ },
94
+ 'youtube': {
95
+ 'client_id': 'youtube_client_id',
96
+ 'client_secret': 'youtube_client_secret',
97
+ 'video_id': 'youtube_video_id',
98
+ 'video_url': 'youtube_video_url',
99
+ 'title': 'youtube_title',
100
+ 'description': 'youtube_description',
101
+ 'category_id': 'youtube_category_id',
102
+ 'privacy': 'youtube_privacy_status',
103
+ 'keywords': 'youtube_keywords',
104
+ },
105
+ 'tiktok': {
106
+ 'client_key': 'tiktok_client_key',
107
+ 'client_secret': 'tiktok_client_secret',
108
+ 'username': 'tiktok_username',
109
+ 'video_url': 'tiktok_video_url',
110
+ 'title': 'tiktok_title',
111
+ 'privacy': 'tiktok_privacy_status',
112
+ 'allow_comments': 'tiktok_allow_comments',
113
+ 'allow_duet': 'tiktok_allow_duet',
114
+ 'allow_stitch': 'tiktok_allow_stitch',
115
+ 'auto_add_music': 'tiktok_auto_add_music',
116
+ 'description': 'tiktok_description',
117
+ },
118
+ 'threads': {
119
+ 'app_id': 'threads_app_id',
120
+ 'app_secret': 'threads_app_secret',
121
+ 'post_id': 'threads_post_id',
122
+ 'video_url': 'threads_video_url',
123
+ 'video_title': 'threads_video_title',
124
+ },
125
+ 'telegram': {
126
+ 'bot_token': 'telegram_bot_token',
127
+ 'chat_id': 'telegram_chat_id',
128
+ 'parse_mode': 'telegram_parse_mode',
129
+ 'message_id': 'telegram_message_id',
130
+ 'post_id': 'telegram_message_id',
131
+ 'video_url': 'video_url',
132
+ 'video_title': 'video_title',
133
+ },
134
+ 'whatsapp': {
135
+ 'access_token': 'whatsapp_access_token',
136
+ 'phone_number_id': 'whatsapp_phone_number_id',
137
+ 'business_account_id': 'whatsapp_business_account_id',
138
+ 'recipient': 'whatsapp_recipient',
139
+ 'message_id': 'whatsapp_message_id',
140
+ 'template_name': 'whatsapp_template_name',
141
+ 'language_code': 'whatsapp_template_language',
142
+ 'template_components': 'whatsapp_template_components',
143
+ 'video_url': 'video_url',
144
+ 'video_title': 'video_title',
145
+ },
146
+ }
147
+
148
+ # Common parameter mappings (apply to all platforms)
149
+ COMMON_MAPPINGS = {
150
+ # Content parameters
151
+ 'text': 'status_text',
152
+ 'link': 'status_link',
153
+ 'image_1': 'status_image_url_1',
154
+ 'image_2': 'status_image_url_2',
155
+ 'image_3': 'status_image_url_3',
156
+ 'image_4': 'status_image_url_4',
157
+ # Feed parameters
158
+ 'feed_url': 'feed_url',
159
+ 'max_count': 'feed_max_count',
160
+ 'post_lookback': 'feed_post_lookback',
161
+ 'max_post_age': 'feed_max_post_age',
162
+ # Google Sheets parameters
163
+ 'sheets_id': 'google_sheets_id',
164
+ 'sheets_name': 'google_sheets_name',
165
+ 'sheets_client_email': 'google_sheets_client_email',
166
+ 'sheets_private_key': 'google_sheets_private_key',
167
+ # System parameters
168
+ 'loglevel': 'loglevel',
169
+ }
170
+
171
+ def __init__(self, platform: str):
172
+ """
173
+ Initialize parameter converter for a specific platform.
174
+
175
+ Args:
176
+ platform: Platform name
177
+ """
178
+ self.platform = platform
179
+ self.platform_mapping = self.PLATFORM_MAPPINGS.get(platform, {})
180
+
181
+ def convert_to_legacy(self, args: Namespace) -> Dict[str, Any]:
182
+ """
183
+ Convert new CLI args to legacy format for core modules.
184
+
185
+ Args:
186
+ args: Parsed arguments from new CLI
187
+
188
+ Returns:
189
+ Dictionary of legacy-format arguments
190
+ """
191
+ legacy_args = {
192
+ 'network': self.platform,
193
+ 'action': getattr(args, 'action', None),
194
+ }
195
+
196
+ # Convert platform-specific parameters
197
+ for new_param, value in vars(args).items():
198
+ # Skip None values
199
+ if value is None:
200
+ continue
201
+
202
+ # Skip empty strings (edge case handling)
203
+ if isinstance(value, str) and value.strip() == '':
204
+ continue
205
+
206
+ # Check platform-specific mapping
207
+ if new_param in self.platform_mapping:
208
+ legacy_param = self.platform_mapping[new_param]
209
+ legacy_args[legacy_param] = value
210
+ # Check common mapping
211
+ elif new_param in self.COMMON_MAPPINGS:
212
+ legacy_param = self.COMMON_MAPPINGS[new_param]
213
+ legacy_args[legacy_param] = value
214
+ # Pass through unchanged
215
+ else:
216
+ legacy_args[new_param] = value
217
+
218
+ return legacy_args
219
+
220
+ def convert_from_legacy(self, legacy_args: Dict[str, Any]) -> Dict[str, Any]:
221
+ """
222
+ Convert legacy args to new format.
223
+
224
+ Args:
225
+ legacy_args: Legacy format arguments
226
+
227
+ Returns:
228
+ Dictionary of new-format arguments
229
+ """
230
+ new_args = {}
231
+
232
+ # Create reverse mappings
233
+ platform_reverse = {v: k for k, v in self.platform_mapping.items()}
234
+ common_reverse = {v: k for k, v in self.COMMON_MAPPINGS.items()}
235
+
236
+ for legacy_param, value in legacy_args.items():
237
+ # Skip None values
238
+ if value is None:
239
+ continue
240
+
241
+ # Skip empty strings (edge case handling)
242
+ if isinstance(value, str) and value.strip() == '':
243
+ continue
244
+
245
+ # Check platform-specific reverse mapping
246
+ if legacy_param in platform_reverse:
247
+ new_param = platform_reverse[legacy_param]
248
+ new_args[new_param] = value
249
+ # Check common reverse mapping
250
+ elif legacy_param in common_reverse:
251
+ new_param = common_reverse[legacy_param]
252
+ new_args[new_param] = value
253
+ # Pass through
254
+ else:
255
+ new_args[legacy_param] = value
256
+
257
+ return new_args
258
+
259
+ def convert_to_legacy_with_validation(self, args: Namespace) -> Dict[str, Any]:
260
+ """
261
+ Convert new CLI args to legacy format with validation.
262
+
263
+ Args:
264
+ args: Parsed arguments from new CLI
265
+
266
+ Returns:
267
+ Dictionary of legacy-format arguments
268
+
269
+ Raises:
270
+ ValueError: If validation fails
271
+ """
272
+ legacy_args = self.convert_to_legacy(args)
273
+ self.validate_legacy_args(legacy_args)
274
+ return legacy_args
275
+
276
+ def validate_legacy_args(self, legacy_args: Dict[str, Any]) -> None:
277
+ """
278
+ Validate that required legacy parameters are present.
279
+
280
+ Args:
281
+ legacy_args: Legacy format arguments
282
+
283
+ Raises:
284
+ ValueError: If required parameters are missing
285
+ """
286
+ # Check that network and action are present
287
+ if not legacy_args.get('network'):
288
+ raise ValueError("Missing required parameter: network")
289
+ if not legacy_args.get('action'):
290
+ raise ValueError("Missing required parameter: action")
291
+
292
+ def get_unmapped_parameters(self, args: Namespace) -> list:
293
+ """
294
+ Get list of parameters that weren't converted (pass-through).
295
+
296
+ Args:
297
+ args: Parsed arguments from new CLI
298
+
299
+ Returns:
300
+ List of parameter names that weren't mapped
301
+ """
302
+ unmapped = []
303
+
304
+ for param, value in vars(args).items():
305
+ if value is None:
306
+ continue
307
+
308
+ # Skip special parameters
309
+ if param in ['handler', 'action', 'network']:
310
+ continue
311
+
312
+ # Check if parameter was mapped
313
+ is_platform_mapped = param in self.platform_mapping
314
+ is_common_mapped = param in self.COMMON_MAPPINGS
315
+
316
+ if not is_platform_mapped and not is_common_mapped:
317
+ unmapped.append(param)
318
+
319
+ return unmapped
320
+
321
+ @classmethod
322
+ def get_all_mappings(cls, platform: str = None) -> Dict[str, Any]:
323
+ """
324
+ Get complete mapping reference for debugging.
325
+
326
+ Args:
327
+ platform: Optional platform to get specific mappings
328
+
329
+ Returns:
330
+ Dictionary with all mapping information
331
+ """
332
+ if platform:
333
+ return {
334
+ 'platform': platform,
335
+ 'platform_mappings': cls.PLATFORM_MAPPINGS.get(platform, {}),
336
+ 'common_mappings': cls.COMMON_MAPPINGS,
337
+ }
338
+ else:
339
+ return {
340
+ 'all_platforms': cls.PLATFORM_MAPPINGS,
341
+ 'common_mappings': cls.COMMON_MAPPINGS,
342
+ }
343
+
344
+ def log_conversion(self, args: Namespace, legacy_args: Dict[str, Any]) -> None:
345
+ """
346
+ Log parameter conversion details for debugging.
347
+
348
+ Args:
349
+ args: Original new-format arguments
350
+ legacy_args: Converted legacy-format arguments
351
+ """
352
+ print(f"\n=== Parameter Conversion Debug ({self.platform}) ===")
353
+ print(f"Action: {args.action}")
354
+ print("\nNew format parameters:")
355
+ for key, value in vars(args).items():
356
+ if value is not None and key not in ['handler']:
357
+ print(f" {key}: {value}")
358
+ print("\nConverted to legacy format:")
359
+ for key, value in legacy_args.items():
360
+ if value is not None:
361
+ print(f" {key}: {value}")
362
+ print("=" * 50)
363
+
364
+ def get_conversion_report(self, args: Namespace) -> Dict[str, Any]:
365
+ """
366
+ Generate detailed conversion report for debugging.
367
+
368
+ Args:
369
+ args: Parsed arguments from new CLI
370
+
371
+ Returns:
372
+ Dictionary with conversion details
373
+ """
374
+ legacy_args = self.convert_to_legacy(args)
375
+ unmapped = self.get_unmapped_parameters(args)
376
+
377
+ # Count conversions
378
+ platform_conversions = []
379
+ common_conversions = []
380
+ pass_through = []
381
+
382
+ for param, value in vars(args).items():
383
+ if value is None or param in ['handler', 'action', 'network']:
384
+ continue
385
+
386
+ if param in self.platform_mapping:
387
+ platform_conversions.append({
388
+ 'new': param,
389
+ 'legacy': self.platform_mapping[param],
390
+ 'value': value
391
+ })
392
+ elif param in self.COMMON_MAPPINGS:
393
+ common_conversions.append({
394
+ 'new': param,
395
+ 'legacy': self.COMMON_MAPPINGS[param],
396
+ 'value': value
397
+ })
398
+ else:
399
+ pass_through.append({'param': param, 'value': value})
400
+
401
+ return {
402
+ 'platform': self.platform,
403
+ 'action': getattr(args, 'action', None),
404
+ 'platform_conversions': platform_conversions,
405
+ 'common_conversions': common_conversions,
406
+ 'pass_through': pass_through,
407
+ 'unmapped_params': unmapped,
408
+ 'legacy_args': legacy_args,
409
+ }