|
|
|
#!/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 <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
"""
|
|
|
|
.. moduleauthor:: Julio Biason <julio.biason@gmail.com>
|
|
|
|
|
|
|
|
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)
|
|
|
|
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 returned 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.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
|