#!/usr/bin/env python # -*- coding: utf-8 -*- # Mitter, a client for Twitter. # Copyright (C) 2007, 2008 The Mitter Contributors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . """ .. moduleauthor:: Julio Biason The :mod:`networkbase` module defines the base classes for all networks. """ import logging import gettext from mitterlib.constants import version # logging _log = logging.getLogger('mitterlib.network.Network') def auth_options(namespace, options, auths): """Convert the auth fields into options for the command line.""" for option in auths: options.add_option(group=namespace, option=option['name'], default=None, help=option['help'], *(option['flags'])) #-------------------------------------------------------------------- # i18n #-------------------------------------------------------------------- t = gettext.translation('networks', fallback=True) _ = t.gettext N_ = t.ngettext #-------------------------------------------------------------------- # Exceptions #-------------------------------------------------------------------- class NetworkError(Exception): """Base class for all network related exceptions.""" pass class NetworkUnknownError(NetworkError): """Some non-expected error occurred.""" pass class NetworkLimitExceededError(NetworkError): """The number of requests available was exceeded.""" def __init__(self, network_name): self.name = network_name def __str__(self): return _('No more available requests on %s') % (self.name) pass class NetworkDNSError(NetworkError): """A DNS failure prevented the request to continue.""" def __init__(self, network_name): self.name = network_name def __str__(self): return _('DNS failure on a request to %s') % (self.name) pass class NetworkInvalidResponseError(NetworkError): """The server returned the information in an unexpected way.""" def __init__(self, network_name): self.name = network_name def __str__(self): return _('The server on %s return the information in an ' \ 'unexpected way.') % (self.name) pass class NetworkLowLevelError(NetworkError): """A low level error occurred in the network layer.""" def __init__(self, network_name): self.name = network_name def __str__(self): return _('Low level error in the network layer when talking ' \ 'to %s') % (self.name) pass class NetworkBadStatusLineError(NetworkError): """Bad status line exception.""" def __init__(self, network_name): self.name = network_name def __str__(self): return _('Bad status line in network %s') % (self.name) pass class NetworkAuthorizationFailError(NetworkError): """Authorization failure.""" def __init__(self, network_name): self.name = network_name def __str__(self): return _('Autorization failed for %s') % (self.name) pass class NetworkPermissionDeniedError(NetworkError): """Permission denied when accessing the message/list.""" def __init__(self, network_name): self.name = network_name def __str__(self): return _('Permission denied received on %s') % (self.name) pass #-------------------------------------------------------------------- # Warnings #-------------------------------------------------------------------- class NetworkWarning(Warning): """Base warning for networks.""" pass class MessageTooLongWarning(NetworkWarning): """The message is too long for the network.""" def __init__(self, network_name): self.name = network_name def __str__(self): return _('Message too long for %s') % (self.name) pass #-------------------------------------------------------------------- # The classes #-------------------------------------------------------------------- class NetworkUser(object): """Provides an uniform way to access information about users.""" def __init__(self): self.name = '' """The name to be displayed as author of the message. *Required*""" self.username = '' """The message author username in the network. *Required*""" self.avatar = None """URL to the author avatar. *Optional*""" def __repr__(self): return "%s (%s)" % (self.name, self.username) class NetworkData(object): """Provides an uniform way to access information about posts.""" def __init__(self): self.id = '' """The message identification. *Optional*""" self.author = None """Author of the message. A :class:`NetworkUser` object. *Required*""" self.message = '' """The message. *Required*""" self.message_time = None """Message timestamp (as a datetime object). Defaults to None. *Optional*""" self.favorite = False """Boolean indicating if the message was marked as "favorite" or not. *Optional*""" self.parent = None """The parent of this message, in case of a reply. *Optional*""" self.parent_owner = None """Owner of the parent message (in other words, "in reply to".) It will be a :class:`NetworkUser` object. *Optional*""" self.reposted_by = None """Author of the reposted message. A :class:`NetworkUser` object. Some networks will return the original user in a separate table (Twitter, in this case, returns the original message with the original user in a "retweeted_status" structure.) In this case, the network layer must return the original user in *author* while the friend will be in *reposted_by*. *Optional*""" self.protected = False """Boolean indicating if the message is protected/private or not.""" self.deletable = False """Boolean indicating if the message can be deleted.""" self.replyable = False """Boolean indicating if the message can be replied.""" self.repostable = False """Boolean indicating if the message can be reposted.""" self.favoritable = False """Boolean indicating if the message can be favorited.""" self.link = None """Link to the original message.""" self.reply_prefix = '' """Prefix to be used in messages when replying to this message.""" self.network = '' """The network id source of the message. Network classes don't need to worry about this field themselves; :class:`Networks` will set it when merging information from all networks.""" self.network_name = '' """The network name, source of the message. Again, network classes don't need to worry about this field themselves 'cause :class:`Networks` will set it when merging information from all networks.""" return class NetworkBase(object): """Base class for all networks.""" _user_agent = 'Mitter %s' % (version) # TODO: We'll probably need a ICON attribute in the future. #: Namespace of the network, used to identify options. NAMESPACE = 'Meta' AUTH = [] """List of fields the interface must request to the user in order to retrieve information from the network. It's a list of dictionaries, containing: *name* Name of the option, used in ConfigOpt (for the name in the config file and to access it through the options variable); *flags* The list of command line options for this option (as in OptParse); *prompt* The prompt to be used by interfaces when requesting the variable; *help* Description for the value; it's used by ConfigOpt to show the description of the paramater in the command line options and can be used by interfaces to show tooltips about the field; *type* The type of the option; valid values are: **str** A string; **passwd** Password; string, but interfaces should hide the information if possible. """ def __init__(self): self.is_setup = False """Boolean indication if the network have all necessary options set up so it can retrieve the messages. :class:`Networks` will not send requests to networks that return False to this function.""" self.user = NetworkUser() """A :class:`NetworkUser` with the information about the currently logged user.""" def messages(self): """Return a list of :class:`NetworkData` objects for the main "timeline" (the default list presented to the user.)""" return [] def update(self, status, reply_to=None): """Update the user status. *status* should be the string with the status update; *reply_to* should be used in case the message is a reply to another message, it could be a simple id or the :class:`NetworkData` object of the original data. Must return the id for the new status.""" # TODO: All networks will return an id? If so, what we do with it # anyway? return None def repost(self, message): """Repost a message in your current timeline. *message* must be a valid :class:`NetworkData` object. Must return the id of the new message.""" # TODO: All networks will return an id? If so, what we do with it # anyway? return None def favorite(self, message): """Toggle the favorite status of a message. *message* must be a valid :class:`NetworkData` object. Returns True if the request was successful or False otherwise.""" # TODO: Again, if errors appear as exceptions, why return something? return False def delete_message(self, message): """Delete an update. Must return True if the message was deleted or False if not. *message* can be either an id or a :class:`NetworkData` object with the information about the message to be deleted.""" return False def message(self, message_id): """Return a single :class:`NetworkData` object for a specified message.""" return None def link(self, message): """Returns a link (as string) to the message in the network site. If the network doesn't provide a link directly to the message, None shall be returned. *message* is a :class:`NetworkData` object.""" return None def replies(self): """Return a list of :class:`NetworkData` objects for the replies for the user messages.""" return [] def available_requests(self): """Return the number of requests the user can request before being capped. If such limitation doesn't exist for the network, a negative number should be returned.""" return -1