def test_profiles_manager_filter_method_empty(): """ Should filter if profile manager is None. """ mocked_api = MagicMock() mocked_api.get.return_value = [{"a": "b"}, {"a": "c"}] profiles = Profiles(api=mocked_api) assert profiles.filter(a="b") == [Profile(mocked_api, {"a": "b"})]
def test_profiles_manager_filter_method(): ''' Test basic profiles filtering based on some minimal criteria ''' mocked_api = MagicMock() profiles = Profiles(mocked_api, [{'a':'b'}, {'a': 'c'}]) eq_(profiles.filter(a='b'), [{'a': 'b'}])
def test_profiles_manager_filter_method(): ''' Test basic profiles filtering based on some minimal criteria ''' mocked_api = MagicMock() profiles = Profiles(mocked_api, [{'a': 'b'}, {'a': 'c'}]) eq_(profiles.filter(a='b'), [{'a': 'b'}])
def test_profiles_manager_filter_method_empty(): ''' Test basic profiles filtering when the manager is empty ''' mocked_api = MagicMock() mocked_api.get.return_value = [{'a': 'b'}, {'a': 'c'}] profiles = Profiles(api=mocked_api) eq_(profiles.filter(a='b'), [Profile(mocked_api, {'a': 'b'})])
def test_profiles_manager_filter_method_empty(): ''' Test basic profiles filtering when the manager is empty ''' mocked_api = MagicMock() mocked_api.get.return_value = [{'a':'b'}, {'a': 'c'}] profiles = Profiles(api=mocked_api) eq_(profiles.filter(a='b'), [Profile(mocked_api, {'a': 'b'})])
def cmd_buffer(self, msg, args): """post messages to social networks in one command !buffer post <network|all> <message> - add new post to deliver to social nework(s) network can be 'all' to publish to all social networks configured, or you can specify one of them, i.e.: facebook, twitter, gplus... """ if len(args) < 3: return "usage: !buffer post <network|all> <message>" cmd = args[0] net = args[1] if net != "all" and net not in self.profiles: return "Network '%s' is not defined" % (net, ) if cmd == "post": # instantiate the api object api = API(client_id=self.cid, client_secret=self.cis, access_token=self.apitoken) profiles = Profiles(api=api).all() for p in profiles: if net == "all" or p.id == self.profiles[net]: p.updates.new(" ".join(args[2:]), now=True) return "Done. Published!" else: return "Sorry, I don't understand that command."
def setProfile(self, service=""): logging.info(" Checking services...") self.profile = None if (service == ""): logging.info(" All available in Buffer") try: profiles = Profiles(api=self.client).all() self.profile = profiles[0] except: logging.info(" Something went wrong") else: logging.info(" Profile %s" % service) try: profiles = Profiles(api=self.client).filter(service=service) self.profile = profiles[0] except: logging.info(" Something went wrong")
def __init__(self, buffer_client_id, buffer_client_secret, buffer_access_token, buffer_slack_channel, slack_token): super(BufferBot, self).__init__(slack_token) self.buffer_channel_name = buffer_slack_channel self.buffer_client = API(client_id=buffer_client_id, client_secret=buffer_client_secret, access_token=buffer_access_token) self.twitter_profile = Profiles(api=self.buffer_client)\ .filter(service='twitter')[0] self.buffer_channel = None
def test_profiles_manager_all_method(): """ Should retrieve profile info. """ mocked_api = MagicMock() mocked_api.get.return_value = [{"a": "b"}] with patch("buffpy.managers.profiles.Profile", return_value=1) as mocked_profile: profiles = Profiles(api=mocked_api).all() assert profiles == [1] mocked_api.get.assert_called_once_with(url=PATHS["GET_PROFILES"]) mocked_profile.assert_called_once_with(mocked_api, {"a": "b"})
def test_profiles_manager_all_method(): ''' Test basic profiles retrieving ''' mocked_api = MagicMock() mocked_api.get.return_value = [{'a': 'b'}] with patch('buffpy.managers.profiles.Profile') as mocked_profile: mocked_profile.return_value = 1 profiles = Profiles(api=mocked_api).all() eq_(profiles, [1]) mocked_api.get.assert_called_once_with(url=PATHS['GET_PROFILES']) mocked_profile.assert_called_once_with(mocked_api, {'a': 'b'})
def test_buffer(config): client = get_buffer_auth(config.buffer) profiles = Profiles(api=client).filter(service='twitter') if not len(profiles): raise Exception('Your twitter account is not configured') profile = profiles[0] print profile print pending = profile.updates.pending for item in pending: print item print item.id print item.text print item.scheduled_at print datetime.datetime.fromtimestamp(item.scheduled_at)
def checkLimitPosts(api): # We can put as many items as the service with most items allow # The limit is ten. # Get all pending updates of a social network profile lenMax = 0 logging.info("Checking services...") profileList = Profiles(api=api).all() for profile in profileList: lenProfile = len(profile.updates.pending) if (lenProfile > lenMax): lenMax = lenProfile logging.info("%s ok" % profile['service']) logging.info("There are %d in some buffer, we can put %d" % (lenMax, 10 - lenMax)) return (lenMax, profileList)
def checkPendingPosts(api): # We can put as many items as the service with most items allow # The limit is ten. # Get all pending updates of a social network profile serviceList = ['twitter', 'facebook', 'linkedin'] profileList = {} lenMax = 0 logging.info("Checking services...") for service in serviceList: profileList[service] = Profiles(api=api).filter(service=service)[0] if (len(profileList[service].updates.pending) > lenMax): lenMax = len(profileList[service].updates.pending) logging.info("%s ok" % service) logging.info("There are %d in some buffer, we can put %d" % (lenMax, 10 - lenMax)) return (lenMax, profileList)
def test_profiles_manager_filter_method(): """ Should filter based on criteria. """ mocked_api = MagicMock() profiles = Profiles(mocked_api, [{"a": "b"}, {"a": "c"}]) assert profiles.filter(a="b") == [{"a": "b"}]
from colorama import Fore from buffpy.managers.profiles import Profiles from buffpy.api import API # check http://bufferapp.com/developers/apps to retrieve a token # or generate one with the example token = 'awesome_token' # instantiate the api object api = API(client_id='client_id', client_secret='client_secret', access_token=token) # get all profiles profiles = Profiles(api=api) print profiles.all() # filter profiles using some criteria profile = Profiles(api=api).filter(service='twitter')[0] print profile # get schedules of my twitter profile profile = Profiles(api=api).filter(service='twitter')[0] print profile.schedules # update schedules times for my twitter profile profile = Profiles(api=api).filter(service='twitter')[0] profile.schedules = { 'days': ['tue', 'thu'],
def test_code(): profiles = Profiles(api=api) print(profiles.all)
- Other categories as necessary, show them how to add one ''' from buffpy.managers.profiles import Profiles from buffpy.managers.updates import Updates from buffpy.api import API from bufferinfo import * from quote_bot_quotes import * import random api = API(client_id=CLIENTID, client_secret=CLIENTSECRET, access_token=ACCESSTOKEN) profile = Profiles(api=api).filter(service='twitter')[0] def test_code(): profiles = Profiles(api=api) print(profiles.all) if __name__ == "__main__": test_code() ''' def add_to_buffer(count): bufferQuotes = random.sample(quotes, count) for j in bufferQuotes: try:
from buffpy.api import API from buffpy.managers.profiles import Profiles # check http://bufferapp.com/developers/apps to retrieve a token # or generate one with the example token = "awesome_token" # instantiate the api object api = API(client_id="client_id", client_secret="client_secret", access_token=token) # get all profiles profiles = Profiles(api=api) print(profiles.all()) # filter profiles using some criteria profile = Profiles(api=api).filter(service="twitter")[0] print(profile) # get schedules of my twitter profile profile = Profiles(api=api).filter(service="twitter")[0] print(profile.schedules) # update schedules times for my twitter profile profile = Profiles(api=api).filter(service="twitter")[0] profile.schedules = { "days": ["tue", "thu"],
def copy_todays_events(events, streams): # Filter to streams in the next 7 days now = pacific_now() def soon(stream): delta = stream['scheduledStartTime'] - now return delta > datetime.timedelta(minutes=5) and \ delta < datetime.timedelta(days=7) upcoming_streams = filter(soon, streams) # Filter to events in the next 7 days def soon_event(event): try: # We always have a date, we might not know what time were speaking like at DDTX delta = event['date'] - now.date() if 'start' in event and event['start'] is not None: try: delta = event['start'] - now except: pass return delta > datetime.timedelta(minutes=5) and \ delta < datetime.timedelta(days=7) except Exception as e: logger.error("Event {0} had error {1}".format(event, e)) raise e upcoming_events = filter(soon_event, events) twitch_link = "https://www.twitch.tv/holdenkarau" # Update buffer posts logger.debug("Updating posts...") buffer_clientid = os.getenv("BUFFER_CLIENTID") buffer_client_secret = os.getenv("BUFFER_CLIENT_SECRET") buffer_token = os.getenv("BUFFER_CODE") buffer_api = buffpy.API( client_id=buffer_clientid, client_secret=buffer_client_secret, access_token=buffer_token) user = User(api=buffer_api) profiles = Profiles(api=buffer_api).all() # TODO(holden): Import talks from a special calendar # TODO(holden): Create a meta post of the weeks events def cleanup_event_title(title): cleaned_title = title[:1].lower() + title[1:] # Cut the text for twitter if needed short_title = cleaned_title # swap in at mentions on twitter short_title = short_title.replace("Apache Spark", "@ApacheSpark") \ .replace("Apache Airflow (Incubating)", "@ApacheAirflow") \ .replace("Apache (Incubating) Airflow", "@ApacheAirflow") \ .replace("Apache Airflow", "@ApacheAirflow") \ .replace("Apache Beam", "@ApacheBeam") \ .replace("Kubernetes", "@kubernetesio") \ .replace("Apache Arrow", "@ApacheArrow") short_title = re.sub(" [sS]cala(\.| |\,)", r" @scala_lang\1", short_title) short_title = re.sub("^[sS]cala(\.| |\,)", r"@scala_lang\1", short_title) short_title = re.sub("[jJ]upyter( |)[cC]on", "@JupyterCon", short_title) short_title = re.sub("[sS]trata( |)[cC]onf", "@strataconf", short_title) short_title = short_title.replace("@@", "@") if len(short_title) > 150: short_title = cleaned_title[:150] + "..." return (cleaned_title, short_title) def format_event_post(event): """Create posts for a given event.""" # TODO(holden): Format the event post title, short_title = cleanup_event_title(event['title']) city_name = None if event['location'] is not None: city_name = event['location'] if "," in city_name: city_name = city_name.split(",")[0] # Add the tags field with a space so it doesn't join the link tag_text = "" if 'tags' in event and event['tags']: if type(event['tags']) is str: tag_text = " / {0}".format(event['tags']) else: tag_text = " / {0}".format(" ".join(event['tags'])) # We handle future events & past events differently def format_future(format_time_func, delta): hey_friends = "" if city_name is not None: hey_friends = "Hey {0} friends, ".format(city_name) # Figure out the join on text who = "me" if 'copresenters' in event and event['copresenters'] is not None: if len(event['copresenters']) == 1: who = "me and {0} ".format(event['copresenters'][0]) else: who = "{0} and myself".format(", ".join(event['copresenters'])) join_on = "join {0}".format(who) if 'event_name' in event and event['event_name'] is not None: event_name = event['event_name'] # For event names that are twitter handles don't dupe the @ if "@" in event_name: join_on = "join {0} {1} ".format(who, event['event_name']) else: join_on = "join {0} @ {1} ".format(who, event['event_name']) # We often have a time, always have a date join_at = " {0}".format(format_time_func(event)) link_text = "" if event['short_post_link'] is not None: link_text = " {0}".format(event["short_post_link"]) elif event['short_talk_link'] is not None: link_text = " {0}".format(event['short_talk_link']) full_text = "{0}{1}{2} for {3}{4}".format( hey_friends, join_on, join_at, title, link_text) short_text = "{0}{1}{2} for {3}{4}{5}".format( hey_friends, join_on, join_at, short_title, link_text, tag_text) # tweet lenght fall back if len(short_text) > 250: short_string = "{0}{1}{2}{3}".format( join_on, join_at, short_title, link_text) deflink = event['short_post_link'] or event['short_talk_link'] post_time = event['date'] - delta if 'start' in event and event['start'] is not None: post_time = event['start'] - delta return (full_text, short_text, post_time, None, deflink, short_title) def format_past(): # Don't post slides multiple times if "changed" not in event or not event['changed']: return [] # TODO(holden): Figure out media links for past talks if event['slides_link'] and event['video_link']: mini_link = "{short_slides_link} and {short_video_link}" if event['short_post_link']: mini_link = "{short_post_link} (or direct {short_slides_link} / {short_video_link})" mini_link = mini_link.format(**event) full_text = "Slides and video now up from {title} at {mini_link}".format( title=title, mini_link=mini_link) short_text = "Slides and video now up from {short_title} at {mini_link}{tag_text}".format( short_title=short_title, mini_link=mini_link, tag_text=tag_text) if len(short_text) > 230: if event['short_post_link']: short_text = "Slides & video from {0} at {1}".format( short_title, event['short_post_link']) else: short_text = "Slides & video from {0} at {1} & {2}".format( short_title, event['short_slides_link'], event['short_video_link']) return (full_text, short_text, None, None, None, event['short_video_link'], short_title) # TODO(holden): Add a function to check if the slides have been linked on video. elif event['slides_link']: mini_link = "{short_slides_link}" if event['short_post_link']: mini_link = "{short_post_link} (or direct {short_slides_link})" mini_link = mini_link.format(**event) full_text = "Slides now up from {title} at {mini_link} :)".format( title=title, mini_link=mini_link) short_text = "Slides now up from {short_title} at {mini_link}{tag_text}:)".format( short_title=short_title, mini_link=mini_link, tag_text=tag_text) if len(short_text) > 230: short_text = "Slides from {short_title} @ {short_slides_link}".format( short_title=short_title, short_slides_link=event['short_slides_link']) return (full_text, short_text, None, None, None, event['short_slides_link'], short_title) else: return None if (event['start'] is not None and event['start'] > now) or (event['date'] > now.date()): # Post for join me today def format_time_join_me_today(event): has_time = 'start' in event and event['start'] is not None if not has_time: return "today" time = event['start'] if time.minute == 0: return time.strftime("today @ %-I%p") else: return time.strftime("today @ %-I:%M%p") todaydelta = datetime.timedelta(hours=4, minutes=55) today_post = format_future(format_time_join_me_today, todaydelta) # Skip everything else if we're already at today if event['date'] == now.date(): return [today_post] # Post for join me this week def format_time_join_me_this_week(event): has_time = 'start' in event and event['start'] is not None if not has_time: return event['date'].strftime("%A") time = event['start'] if time.minute == 0: return time.strftime("%A @ %-I%p") else: return time.strftime("%A @ %-I:%M%p") thisweekdelta = datetime.timedelta(days=5, minutes=55) this_week = format_future(format_time_join_me_this_week, thisweekdelta) return [today_post, this_week] else: past = format_past() if past: return [past] else: return [] def format_stream_post(stream): """Create posts for a given stream. Returns the short text, long text, and tuple of schedule time.""" # Munge the text to fit within our sentence structure cleaned_title, short_title = cleanup_event_title(stream['title']) # Compute how far out this event is delta = stream['scheduledStartTime'] - now yt_link = stream['url'] def create_post_func(time_format_func, delta, format_string): def create_post(stream): tweet_time = stream['scheduledStartTime'] - delta media_img = stream['image_url'] stream_time = time_format_func(stream['scheduledStartTime']) coming = "" if stream['scheduledStartTime'].isocalendar()[1] != tweet_time.isocalendar()[1]: coming = " coming" full_text = format_string.format( stream_time, cleaned_title, yt_link, twitch_link, coming) short_text = format_string.format( stream_time, short_title, yt_link, twitch_link, coming) return (full_text, short_text, tweet_time, media_img, yt_link, cleaned_title) return create_post def format_time_same_day(time): if time.minute == 0: return time.strftime("%-I%p") else: return time.strftime("%-I:%M%p") create_join_in_less_than_an_hour = create_post_func( format_time_same_day, datetime.timedelta(minutes=random.randrange(39, 55, step=1)), "Join me in less than an hour @ {0} pacific for {1} on {2} @YouTube or {3} twitch") def format_time_tomorrow(time): if time.minute == 0: return time.strftime("%a %-I%p") else: return time.strftime("%a %-I:%M%p") create_join_tomorrow = create_post_func( format_time_tomorrow, datetime.timedelta(hours=23, minutes=55), "Join me tomorrow @ {0} pacific for {1} on {2} @YouTube") def format_time_future(time): if time.minute == 0: return time.strftime("%A @ %-I%p") else: return time.strftime("%A @ %-I:%M%p") create_join_me_on_day_x = create_post_func( format_time_future, datetime.timedelta( days=5, hours=random.randrange(20, 24, step=1), minutes=random.randrange(0, 55, step=1)), "Join me this{4} {0} pacific for {1} on {2} @YouTube") if stream['scheduledStartTime'].day == now.day: # Special case stream on the same day return [create_join_in_less_than_an_hour(stream)] else: # All possible posts leave it up to scheduler return [create_join_in_less_than_an_hour(stream), create_join_me_on_day_x(stream), create_join_tomorrow(stream)] possible_stream_posts = list(flatMap(format_stream_post, upcoming_streams)) possible_event_posts = list(flatMap(format_event_post, events)) possible_posts = [] possible_posts.extend(possible_stream_posts) possible_posts.extend(possible_event_posts) # Only schedule posts in < 36 hours and < - 12 hours def is_reasonable_time(post): # If we don't have a time to schedule always a good time if post[2] is None: return True delta_from_now = None print("Doing {0}-{1}".format(post[2], now)) try: delta_from_now = post[2] - now except: delta_from_now = post[2] - now.date() return delta_from_now < datetime.timedelta(hours=25, minutes=55) and \ delta_from_now > datetime.timedelta(days=-5) desired_posts = filter(is_reasonable_time, possible_posts) def post_as_needed_to_profile(profile): # Special case twitter for short text posts = [] logger.debug(profile.formatted_service) if profile.formatted_service == u"Twitter": posts = map(lambda post: (post[1], post[2], post[3], post[4], post[5]), desired_posts) else: posts = map(lambda post: (post[0], post[2], post[3], post[4], post[5]), desired_posts) updates = profile.updates pending = updates.pending sent = updates.sent all_updates = [] all_updates.extend(pending) all_updates.extend(sent) # Get the raw text of the posts to de-duplicate def extract_text_from_update(update): return unicode(BeautifulSoup( update.text_formatted, features="html.parser").get_text()) # Allow comparison after munging def clean_odd_text(text): text = re.sub("http(s|)://[^\s]+", "", text) return mini_clean_text(text) def mini_clean_text(text): # media tag text = text.replace(u"\xa0\xa0", "") # Spaces get screwy :( text = text.replace(" ", "") # And +s... text = text.replace("+", "") # Something something text = text.replace("\t", "") text = text.replace(" ", "") text = text.replace('["', "") text = text.replace(']', "") text = text.replace('("', "") text = text.replace(')', "") text = text.replace('&', "") text = text.replace(':', "") return unicode(text.lower()) # Get the text and link def extract_special(update): text = BeautifulSoup( update.text_formatted, features="html.parser").get_text() text = clean_odd_text(text) media_link = None if hasattr(update, "media"): media_link = update.media.get("link", None) return (unicode(text), unicode(media_link)) all_updates_text = sets.Set(map(extract_text_from_update, all_updates)) all_updates_partial_text = sets.Set( map(mini_clean_text, map(extract_text_from_update, all_updates))) # Kind of a hack cause of how media links is handled all_updates_special = sets.Set(map(extract_special, all_updates)) logger.debug("***********************************************") logger.debug("Existing posts text: {0}".format(all_updates_text)) logger.debug("Specials: {0}".format(all_updates_special)) def allready_published(post): in_all_updates_text = unicode(post[0]) in all_updates_text in_partial_text = unicode(mini_clean_text(post[0])) in all_updates_partial_text logger.debug("Special: {0}".format((unicode(clean_odd_text(post[0])), unicode(post[3]), unicode(post[4])))) in_special = (unicode(clean_odd_text(post[0])), unicode(post[3])) in all_updates_special in_special_ish = (unicode(clean_odd_text(post[0])), unicode(post[4])) in all_updates_special return in_all_updates_text or in_partial_text or in_special or in_special_ish unpublished_posts = filter( lambda post: not allready_published(post), posts) logger.debug("Prepairing to update with new posts:") logger.debug(unpublished_posts) updates = profile.updates for post in unpublished_posts: # Note: even though we set shorten the backend seems to use the # user's per-profile settings instead. media = None if post[2] is not None: media = {"thumbnail": post[2], "link": post[3], "picture": post[2], "description": post[4]} try: if post[1] is None: updates.new(post[0], shorten=False) elif post[1] > now: target_time_in_utc = post[1].astimezone(pytz.UTC) updates.new(post[0], shorten=False, media=media, when=unix_time_seconds(target_time_in_utc)) else: updates.new(post[0], shorten=False, now=True) except Exception as e: logger.warn("Skipping update {0}".format(e)) logger.warn(post) for profile in profiles: post_as_needed_to_profile(profile) def update_twitch(): """Update twitch. Broken until client lib switches to new API.""" # Set up twitch posts twitch_client = TwitchClient( client_id=os.getenv("TWITCH_CLIENT_ID"), oauth_token=os.getenv("TWITCH_OAUTH")) channel_info = twitch_client.channels.get() channel_id = channel_info.id logger.debug(channel_id) # Get existing updates posts = twitch_client.channel_feed.get_posts( channel_id=channel_id, comments=None)
from buffpy.managers.profiles import Profiles from buffpy.managers.updates import Updates from buffpy.api import API # check http://bufferapp.com/developers/apps to retrieve a token # or generate one with the example token = "awesome_token" # instantiate the api object api = API(client_id="client_id", client_secret="client_secret", access_token=token) # get all pending updates of a social network profile profile = Profiles(api=api).filter(service="twitter")[0] print(profile.updates.pending) # get all sent updates of a social network profile print(profile.updates.sent) # retrieve all update"s interactions print(profile.updates.sent[0].interactions) # shuffle updates print(profile.updates.shuffle(count=10)) # reorder updates print(profile.updates.reorder(["update_id"])) # create an update
from colorama import Fore from buffpy.api import API from buffpy.managers.profiles import Profiles # check http://bufferapp.com/developers/apps to retrieve a token # or generate one with the example token = "awesome_token" # instantiate the api object api = API(client_id="client_id", client_secret="client_secret", access_token=token) # get all profiles profiles = Profiles(api=api) print(profiles.all()) # filter profiles using some criteria profile = Profiles(api=api).filter(service="twitter")[0] print(profile) # get schedules of my twitter profile profile = Profiles(api=api).filter(service="twitter")[0] print(profile.schedules) # update schedules times for my twitter profile profile = Profiles(api=api).filter(service="twitter")[0] profile.schedules = {"days": ["tue", "thu"], "times": ["13:45"]}