#!/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.""" default_values = { 'str': '', 'passwd': ''} for option in auths: options.add_option(group=namespace, option=option['name'], default=default_values[option['type']], 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) 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) 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 returned the information in an ' \ 'unexpected way.') % (self.name) 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) 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) class NetworkAuthorizationFailError(NetworkError): """Authorization failure.""" def __init__(self, network_name): self.name = network_name def __str__(self): return _('Autorization failed for %s') % (self.name) 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) class NetworkMessageDuplicatedError(NetworkError): """Network pointed that the message is duplicated.""" def __init__(self, network_name): self.name = network_name def __str__(self): return _('%s refused the message due being duplicated') % (self.name) #-------------------------------------------------------------------- # 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) #-------------------------------------------------------------------- # 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.user_regexp = None """Regular expression used to identify usernames in the message. If the network doesn't support an easy way to identify usernames, it should be set to None.""" self.group_regexp = None """Regular expression used to identify groups in the message. If the network doesn't support groups or doesn't have an easy way to identify groups, it should be set to None.""" self.tag_regexps = None """Regular expression used to identify tags (hashtags) in the message. If the network doesn't support tags or doesn't have an easy way to identify groups, it should be set no None.""" 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