Example #1
0
class Bot:
    """ The twitter bot class """
    def __init__(self, bot):
        self.twython_obj = Twython(app_key=bot.consumer_key,
                                   app_secret=bot.consumer_secret,
                                   oauth_token=bot.access_token,
                                   oauth_token_secret=bot.access_token_secret)
        self.keywords = bot.keywords
        self.source_user_ids = bot.source_user_ids
        self.source_followers_ids = bot.source_followers_ids
        self.followers_ids = bot.followers_ids


    def get_source_user_ids(self):
        """ Get some users to initiate the twitter bot for the given keywords"""
        source_user_ids = []
        for keyword in self.keywords:
            # get a list of user objects by searching for the keyword
            userlist = self.twython_obj.searchUsers(q=keyword)
            # select 3 random user objects
            random_list = random.sample(userlist, 3)
            # store the screen_name of the 5 users in a list
            ids = [user['id'] for user in random_list]
            #add screen_names to the list of our tweet sources
            source_user_ids = source_user_ids + ids

        for keyword in self.keywords:
            # get a list of recent tweets
            tweetlist = self.twython_obj.search(q=keyword, result_type="recent", lang='en')['results']
            # select 7 random tweets
            random_list = random.sample(tweetlist, 7)
            # store screen_names of users who made the tweets
            ids = [tweet['from_user_id'] for tweet in random_list]
            #add screen_names to the list of our tweet sources
            source_user_ids = source_user_ids + ids

        self.source_user_ids = source_user_ids

        return source_user_ids

    def get_source_followers_ids(self):
        """ Returns a list of all followers of the users in source_users"""
        source_followers_ids = []
        for source_user_id in self.source_user_ids:
            follower_ids = self.twython_obj.getFollowersIDs(user_id=source_user_id)['ids']
            source_followers_ids = source_followers_ids + follower_ids

        self.source_followers_ids = source_followers_ids

        return source_followers_ids

    def find_and_follow(self):
        """ Find users to follow and follow them """
        # Randomly select one keyword from the list of keywords
        keyword = random.choice(self.keywords)
        # Seacrh for tweets with that keyword
        tweets = self.twython_obj.search(q=keyword, result_type="recent", lang='en')['results']

        done_following = False
        while not done_following:
            # Randomly select a tweet
            tweet = random.choice(tweets)
            user_id = tweet['from_user_id']
            if user_id not in self.source_user_ids and user_id not in self.source_followers_ids:
                self.twython_obj.createFriendship(user_id=user_id)
                done_following = True

        return done_following

    def get_follwers(self):
        """ Returns a list of ids of all followers of the bot """
        followers_ids = self.twython_obj.getFollowersIDs(user_id=user_id)['ids']
        self.followers_ids = followers
        return followers_ids

    def copy_and_tweet(self):
        """ Copy a tweet and tweet it """
Example #2
0
class TwitterServer:
    def __init__(self):
        # You can choose to latch tweets topics
        latch = False
        if rospy.has_param('latch'):
            latch = rospy.get_param('latch')

        # In case you can't direct message a user, replace DM with a public
        #	'@user text' tweet.
        self.replace_dm = False
        if rospy.has_param('replace_dm'):
            self.replace_dm = rospy.get_param('replace_dm')

        # Publish mentions, home timeline and direct messages
        self.pub_home = rospy.Publisher('home_timeline', Tweets, latch = latch)
        self.pub_mentions = rospy.Publisher('mentions', Tweets, latch = latch)
        self.pub_dm = rospy.Publisher('direct_messages', Tweets, latch = latch)

        # Create a bridge for images conversions
        self.bridge = CvBridge()

        # Last Tweets (init values are twitter API default)
        self.last_mention = 12345
        self.last_timeline = 12345
        self.last_dm = 12345

        oauth_token = None
        oauth_token_secret = None

        # Get OAuth info through parameter server
        if rospy.has_param('~token'):
            oauth_token = rospy.get_param('~token')
        if rospy.has_param('~token_secret'):
            oauth_token_secret = rospy.get_param('~token_secret')

        # OAuth token creation (see get_access_token.py from python-twitter)
        if oauth_token is None or oauth_token_secret is None:
            rospy.loginfo("No OAuth information given, trying to create...")

            t = Twython( app_key =  'HbAfkrfiw0s7Es4TVrpSuw',
                    app_secret = 'oIjEOsEbHprUa7EOi3Mo8rNBdQlHjTGPEpGrItZj8c')

            # Get AUth URL. Use for login for security, 
            url = t.get_authentication_tokens()

            t = Twython(app_key = 'HbAfkrfiw0s7Es4TVrpSuw',
                    app_secret = 'oIjEOsEbHprUa7EOi3Mo8rNBdQlHjTGPEpGrItZj8c',
                    oauth_token = url['oauth_token'],
                    oauth_token_secret = url['oauth_token_secret'])

            # Open web browser on given url
            import webbrowser
            webbrowser.open( url['auth_url'] )
            rospy.logwarn("Log your twitter, allow TwitROS and copy pincode.")

            # Wait to avoid webbrowser to corrupt raw_input
            rospy.sleep( rospy.Duration( 5 ) )

            # Enter pincode
            pincode = raw_input('Pincode: ').strip()

            auth_props = t.get_authorized_tokens(oauth_verifier = pincode)

            oauth_token = auth_props['oauth_token']
            oauth_token_secret = auth_props['oauth_token_secret']

            rospy.loginfo("Using the following parameters for oauth: "
                    + 'key: [{key}], '.format(key = oauth_token)
                    + 'secret: [{secret}]'.format(secret = oauth_token_secret))

        # Consumer key and secret are specific to this App.
        # Access token are given through OAuth for those consumer params
        rospy.loginfo('Trying to log into Twitter API...')

        # Twython
        self.t = Twython(app_key = 'HbAfkrfiw0s7Es4TVrpSuw',
                app_secret = 'oIjEOsEbHprUa7EOi3Mo8rNBdQlHjTGPEpGrItZj8c',
                oauth_token = oauth_token,
                oauth_token_secret = oauth_token_secret)

        result = self.t.verifyCredentials();
        rospy.loginfo("Twitter connected as {name} (@{user})!"
                .format(name = result['name'], user = result['screen_name']))

        # Stock screen name (used to show friendships)
        self.name = result['screen_name']

        # Advertise services
        self.post = rospy.Service('post_tweet', Post, self.post_cb)
        self.retweet = rospy.Service('retweet', Id, self.retweet_cb)

        self.follow = rospy.Service('follow', User, self.follow_cb)
        self.unfollow = rospy.Service('unfollow', User, self.unfollow_cb)

        self.post_dm = rospy.Service('post_dm', DirectMessage, self.post_dm_cb)
        self.destroy = rospy.Service('destroy_dm', Id, self.destroy_cb)

        self.timeline = rospy.Service(
                'user_timeline', Timeline, self.user_timeline_cb)

        # Create timers for tweet retrieval. Use oneshot and retrigger
        timer_home = rospy.Timer(
                rospy.Duration(1), self.timer_home_cb, oneshot = True )
        timer_mentions = rospy.Timer(
                rospy.Duration(2), self.timer_mentions_cb, oneshot = True )
        timer_dm = rospy.Timer(
                rospy.Duration(3), self.timer_dm_cb, oneshot = True )

    # Tweet callback
    def post_cb(self, req):
        txt = req.text
        rospy.logdebug("Received a tweet: " + txt)

        # If only one picture, use twitter upload
        if len(req.images) == 1:
            path = self.save_image( req.images[0] )

            first = True
            for tweet in self.split_tweet( txt ):
                if (req.reply_id == 0):
                    if first:
                        result = self.t.updateStatusWithMedia( 
                                file_ = path, status = tweet )
                        first = False
                    else:
                        result = self.t.updateStatus( status = tweet )
                else:
                    if first:
                        result = self.t.updateStatusWithMedia( file_ = path, 
                            status = tweet, in_reply_status_id = req.reply_id )
                        first = False
                    else:
                        result = self.t.updateStatus( status = tweet,
                                in_reply_to_status_id = req.reply_id )
                # Check response for each update.        
                if self.t.get_lastfunction_header('status') != '200 OK':
                    return None

            os.system('rm -f ' + path)

        elif len(req.images) != 0:
            txt +=  upload( req.images )

        # Publish after splitting.
        for tweet in self.split_tweet( txt ):
            if (req.reply_id == 0):
                result = self.t.updateStatus( status = tweet )
            else:
                result = self.t.updateStatus( 
                        status = tweet, in_reply_to_status_id = req.reply_id )

            if self.t.get_lastfunction_header('status') != '200 OK':
                return None

        return PostResponse(id = result['id'])

    def retweet_cb(self, req):
        result = self.t.retweet( id = req.id )
        if self.t.get_lastfunction_header('status') != '200 OK':
            return None
        else: 
            return IdResponse()

    # Does not raise an error if you are already following the user
    def follow_cb(self, req):
        rospy.logdebug("Asked to follow:" + req.user)
        result = self.t.createFriendship( screen_name = req.user )
        if self.t.get_lastfunction_header('status') != '200 OK':
            return None
        else: 
            return UserResponse()

    # Does not raise an error if you are not following the user
    def unfollow_cb(self, req):
        rospy.logdebug("Asked to unfollow:" + req.user)
        result = self.t.destroyFriendship( screen_name = req.user )
        if self.t.get_lastfunction_header('status') != '200 OK':
            return None
        else: 
            return UserResponse()

    # Send direct message.
    def post_dm_cb(self, req):
        rospy.logdebug("Received DM to " + req.user + ": " + req.text)

        # First, check if you can dm the user
        relation = self.t.showFriendship( 
                source_screen_name = self.name, target_screen_name = req.user )

        if self.t.get_lastfunction_header('status') != '200 OK':
            rospy.logerr("Failed to get friendship information.")
            return None

        # CASE 1: If can, send a direct message
        if relation['relationship']['source']['can_dm']:
            txt = req.text

            # Upload image to postimage.org using requests
            if len(req.images):
                txt += self.upload( req.images )

            for dm in self.split_tweet( txt ):
                result = self.t.sendDirectMessage( 
                        screen_name = req.user, text = dm )
                if self.t.get_lastfunction_header('status') != '200 OK':
                    return None
            # Return the id of the last DM posted.
            return DirectMessageResponse(id = result['id'])

        # CASE 2: If Cant dm but allowed to tweet instead, tweet with mention
        elif self.replace_dm:
            rospy.logwarn("You can't send a direct message to " 
                    + req.user + ". Sending a public tweet instead...")
            # One image ---> Twitter
            if len(req.images) == 1 :
                path = self.save_image( req.images[0] )

                first = True
                for tweet in self.split_tweet( req.text ):
                    if first:
                        result = self.t.updateStatusWithMedia( file_ = path, 
                            status = tweet )
                        first = False
                    else:
                        result = self.t.updateStatus( status = tweet )

                    if self.t.get_lastfunction_header('status') != '200 OK':
                        return None

                os.system('rm -rf ' + path)
                # Return the id of the last DM posted.
                return DirectMessageResponse(id = result['id'])
            else:
                status = '@' + req.user + ' ' + req.text
                # Many images ---> postimage.org
                if len(req.images) != 0:
                    status +=  upload( req.images )

            for tweet in self.split_tweet( status ):
                result = self.t.updateStatus( status = tweet )
                if self.t.get_lastfunction_header('status') != '200 OK':
                    return None
            # Return the id of the last DM posted.
            return DirectMessageResponse(id = result['id'])

        # CASE 3: If can't.
        else:
            rospy.logwarn("You can't send a direct message to " + req.user)
            return None

    def destroy_cb(self, req):
        result = self.t.destroyDirectMessage( id = req.id )

        if self.t.get_lastfunction_header('status') != '200 OK':
            return None
        else: 
            return IdResponse()

    def user_timeline_cb(self, req):
        result = self.t.getUserTimeline( screen_name = req.user )
        if self.t.get_lastfunction_header('status') != '200 OK':
            return None
        else: 
            msg = self.process_tweets( result )
            if msg:
                return TimelineResponse( tweets = msg )
            else:
                rospy.logwarn(req.user + " has no tweets in its timeline.")
        return TimelineResponse( )

    # Split a long text into 140 chars tweets
    def split_tweet(self, text):
        tweet = ""
        tweets = []

        words =  text.split(' ')
        for word in words:
            if len(tweet + word + " ") > 137:
                tweets.append(tweet.strip() + "...")
                # If tweets is intended to a user, begin all tweets with @user
                if text.startswith('@'):
                    tweet = words[0] + " " + word + " "
                else:
                    tweet = "..."
            else:
                tweet = tweet + word + " "

        tweets.append( tweet.strip() )
        return tweets

    # Upload array of sensor_msgs/Image to postimage.org and return link.
    # Link is shortened if possible.
    def upload(self, images):
        files = {}
        paths = [] # Keep paths stored to remove files later
        # Construct files dict
        i = 0
        for image in images:
            paths.append( self.save_image( image ) )
            files['upload[{count}]'.format(count = i)] = open(paths[i], 'rb')
            i += 1

        # Post using requests
        request = requests.post('http://postimage.org/index.php', 
                files = files, params = {'optsize': 0, 'adult': 'no'})

        # Cleanup saved files
        for path in paths:
            os.system('rm -rf ' + path)

        if not request.status_code in [301, 201, 200]:
            return " [FAILED UPLOAD]"

        # Parse HTML page returned using beautifulsoup
        soup = BeautifulSoup(request.text, 'html.parser')

        # Find the image link: Hacked that by looking at raw html file
        url = ""
        if len(images) == 1:
            # 1 image : find first solo link containg 'postimg'
            for link in soup.find_all('a'):
                if link.get('href').find('postimg') != -1:
                    url = link.get('href')
                    break
        else:
            # Many images: find the option field for gallery url
            for option in soup.find_all('option'):
                if option.get('value').find('gallery') != -1:
                    url = option.get('value')
                break

        if not len(url):
            return " [FAILED PARSING OR UPLOAD]"

        # Shorten URL
        request = requests.get( 'http://is.gd/create.php',
                params = {'format': 'simple', 'url': url} )

        if request.status_code in [301, 201, 200]:
            return "  [" + request.text + "]"
        else:
            return "  [" + url + "]"

    # Save a sensor_msgs/Image on /tmp and return the path
    def save_image(self, image):
        # Convert from ROS message using cv_bridge.
        try:
            cv_image = self.bridge.imgmsg_to_cv( 
                    image, desired_encoding = 'passthrough')
        except CvBridgeError, e:
            rospy.logerr(e)

        # Write to JPG with OpenCV
        path = "/tmp/pic_ul_{time}.png".format( time = time() )
        cv.SaveImage( path, cv_image )
        return path