diff --git a/apps/reader/views.py b/apps/reader/views.py index 75bcc05c2..ef63562a1 100644 --- a/apps/reader/views.py +++ b/apps/reader/views.py @@ -34,7 +34,8 @@ try: from apps.rss_feeds.models import Feed, MFeedPage, DuplicateFeed, MStory, MStarredStory, FeedLoadtime except: pass -from apps.social.models import MSharedStory, MSocialProfile, MSocialSubscription, MActivity +from apps.social.models import MSharedStory, MSocialProfile, MSocialServices +from apps.social.models import MSocialSubscription, MActivity from apps.social.views import load_social_page from utils import json_functions as json from utils.user_functions import get_user, ajax_login_required @@ -223,6 +224,7 @@ def load_feeds(request): } social_feeds = MSocialSubscription.feeds(**social_params) social_profile = MSocialProfile.profile(user.pk) + social_services = MSocialServices.profile(user.pk) user.profile.dashboard_date = datetime.datetime.now() user.profile.save() @@ -231,6 +233,7 @@ def load_feeds(request): 'feeds': feeds.values() if version == 2 else feeds, 'social_feeds': social_feeds, 'social_profile': social_profile, + 'social_services': social_services, 'folders': json.decode(folders.folders), 'starred_count': starred_count, } diff --git a/apps/social/models.py b/apps/social/models.py index 7719f2a8a..867a35f12 100644 --- a/apps/social/models.py +++ b/apps/social/models.py @@ -24,6 +24,7 @@ from vendor import facebook from vendor import tweepy from utils import log as logging from utils.feed_functions import relative_timesince +from utils.story_functions import truncate_chars from utils import json_functions as json RECOMMENDATIONS_LIMIT = 5 @@ -271,6 +272,15 @@ class MSocialProfile(mongo.Document): if self.photo_url: return self.photo_url return settings.MEDIA_URL + 'img/reader/default_profile_photo.png' + + @property + def email_photo_url(self): + if self.photo_url: + if self.photo_url.startswith('//'): + self.photo_url = 'http:' + self.photo_url + return self.photo_url + domain = Site.objects.get_current().domain + return 'http://' + domain + settings.MEDIA_URL + 'img/reader/default_profile_photo.png' def to_json(self, compact=False, include_follows=False, common_follows_with_user=None): # domain = Site.objects.get_current().domain @@ -375,6 +385,9 @@ class MSocialProfile(mongo.Document): subscription_user_id=user_id) socialsub.needs_unread_recalc = True socialsub.save() + + from apps.social.tasks import EmailNewFollower + EmailNewFollower.delay(follower_user_id=self.user_id, followee_user_id=user_id) def is_following_user(self, user_id): return user_id in self.following_user_ids @@ -425,6 +438,43 @@ class MSocialProfile(mongo.Document): follows_diff.remove(user_id) return follows_inter, follows_diff + + def send_email_for_new_follower(self, follower_user_id): + user = User.objects.get(pk=self.user_id) + if not user.email or not user.profile.send_emails: + return + + follower_profile = MSocialProfile.objects.get(user_id=follower_user_id) + photo_url = follower_profile.profile_photo_url + if 'graph.facebook.com' in photo_url: + follower_profile.photo_url = photo_url + '?type=large' + elif 'twimg' in photo_url: + follower_profile.photo_url = photo_url.replace('_normal', '') + + common_followers, _ = self.common_follows(follower_user_id, direction='followers') + common_followings, _ = self.common_follows(follower_user_id, direction='following') + common_followers.remove(self.user_id) + common_followings.remove(self.user_id) + common_followers = MSocialProfile.profiles(common_followers) + common_followings = MSocialProfile.profiles(common_followings) + + data = { + 'user': user, + 'follower_profile': follower_profile, + 'common_followers': common_followers, + 'common_followings': common_followings, + } + + text = render_to_string('mail/email_new_follower.txt', data) + html = render_to_string('mail/email_new_follower.xhtml', data) + subject = "%s is now following your Blurblog on NewsBlur!" % follower_profile.username + msg = EmailMultiAlternatives(subject, text, + from_email='NewsBlur <%s>' % settings.HELLO_EMAIL, + to=['%s <%s>' % (user.username, user.email)]) + msg.attach_alternative(html, "text/html") + msg.send() + + logging.user(user, "~BB~FM~SBSending email for new follower: %s" % follower_profile.username) def save_feed_story_history_statistics(self): """ @@ -870,6 +920,7 @@ class MSharedStory(mongo.Document): story_permalink = mongo.StringField() story_guid = mongo.StringField(unique_with=('user_id',)) story_tags = mongo.ListField(mongo.StringField(max_length=250)) + posted_to_services = mongo.ListField(mongo.StringField(max_length=20)) meta = { 'collection': 'shared_stories', @@ -1129,6 +1180,40 @@ class MSharedStory(mongo.Document): profiles = [profile.to_json(compact=True) for profile in profiles] return stories, profiles + + def blurblog_permalink(self): + profile = MSocialProfile.objects.get(user_id=self.user_id) + return "http://%s.%s/story/%s" % ( + profile.username_slug, + Site.objects.get_current().domain.replace('www', 'dev'), + self.guid_hash[:6] + ) + + def generate_post_to_service_message(self): + message = self.comments + if not message or len(message) < 1: + message = self.story_title + + message = truncate_chars(message, 116) + message += " " + self.blurblog_permalink() + print message + + return message + + def post_to_service(self, service): + if service in self.posted_to_services: + return + + message = self.generate_post_to_service_message() + social_service = MSocialServices.objects.get(user_id=self.user_id) + if service == 'twitter': + posted = social_service.post_to_twitter(message) + elif service == 'facebook': + posted = social_service.post_to_facebook(message) + + if posted: + self.posted_to_services.append(service) + self.save() class MSocialServices(mongo.Document): @@ -1179,6 +1264,14 @@ class MSocialServices(mongo.Document): } } + @classmethod + def profile(cls, user_id): + try: + profile = cls.objects.get(user_id=user_id) + except cls.DoesNotExist: + return {} + return profile.to_json() + def twitter_api(self): twitter_consumer_key = settings.TWITTER_CONSUMER_KEY twitter_consumer_secret = settings.TWITTER_CONSUMER_SECRET @@ -1325,7 +1418,26 @@ class MSocialServices(mongo.Document): hashlib.md5(user.email).hexdigest() profile.save() return profile + + def post_to_twitter(self, message): + try: + api = self.twitter_api() + api.update_status(status=message) + except tweepy.TweepError, e: + print e + return + return True + + def post_to_facebook(self, message): + try: + api = self.facebook_api() + api.put_wall_post(message=message) + except facebook.GraphAPIError, e: + print e + return + + return True class MInteraction(mongo.Document): user_id = mongo.IntField() diff --git a/apps/social/views.py b/apps/social/views.py index 84f35f9d8..4ecc6fd60 100644 --- a/apps/social/views.py +++ b/apps/social/views.py @@ -10,6 +10,7 @@ from django.conf import settings from apps.rss_feeds.models import MStory, Feed, MStarredStory from apps.social.models import MSharedStory, MSocialServices, MSocialProfile, MSocialSubscription, MCommentReply from apps.social.models import MRequestInvite, MInteraction, MActivity +from apps.social.tasks import PostToService from apps.analyzer.models import MClassifierTitle, MClassifierAuthor, MClassifierFeed, MClassifierTag from apps.analyzer.models import apply_classifier_titles, apply_classifier_feeds, apply_classifier_authors, apply_classifier_tags from apps.analyzer.models import get_classifiers_for_user, sort_classifiers_by_feed @@ -239,6 +240,7 @@ def mark_story_as_shared(request): story_id = request.POST['story_id'] comments = request.POST.get('comments', '') source_user_id = request.POST.get('source_user_id') + post_to_services = request.POST.getlist('post_to_services') story = MStory.objects(story_feed_id=feed_id, story_guid=story_id).limit(1).first() if not story: @@ -279,6 +281,11 @@ def mark_story_as_shared(request): story = stories[0] story['shared_comments'] = shared_story['comments'] or "" + if post_to_services: + for service in post_to_services: + if service not in shared_story.posted_to_services: + PostToService.delay(shared_story_id=shared_story.id, service=service) + return {'code': code, 'story': story, 'user_profiles': profiles} @ajax_login_required diff --git a/media/css/reader.css b/media/css/reader.css index e2f475313..7b6e8d845 100644 --- a/media/css/reader.css +++ b/media/css/reader.css @@ -2428,12 +2428,34 @@ background: transparent; text-shadow: 0 1px 0 #F6F6F6; color: #202020; } -.NB-sideoption-share .NB-sideoption-share-optional { - text-transform: uppercase; +.NB-sideoption-share .NB-sideoption-share-crosspost { + margin-right: -4px; +} +.NB-sideoption-share .NB-sideoption-share-crosspost-twitter, +.NB-sideoption-share .NB-sideoption-share-crosspost-facebook { float: right; - color: #808080; - font-size: 10px; - text-shadow: 0 1px 0 #F6F6F6; + width: 16px; + height: 16px; + margin: 0 0 0 6px; + opacity: .4; + cursor: pointer; + -webkit-filter: grayscale(100%); +} +.NB-sideoption-share .NB-sideoption-share-crosspost-twitter:hover, +.NB-sideoption-share .NB-sideoption-share-crosspost-facebook:hover { + opacity: .7; + -webkit-filter: none; +} +.NB-sideoption-share .NB-sideoption-share-crosspost-twitter.NB-active, +.NB-sideoption-share .NB-sideoption-share-crosspost-facebook.NB-active { + opacity: 1; + -webkit-filter: none; +} +.NB-sideoption-share .NB-sideoption-share-crosspost-twitter { + background: transparent url('/media/embed/reader/twitter_icon.png') no-repeat 0 0; +} +.NB-sideoption-share .NB-sideoption-share-crosspost-facebook { + background: transparent url('/media/embed/reader/facebook_icon.png') no-repeat 0 0; } .NB-sideoption-share .NB-sideoption-share-comments { width: 100%; diff --git a/media/js/newsblur/common/assetmodel.js b/media/js/newsblur/common/assetmodel.js index f9520ed62..dc6916e43 100644 --- a/media/js/newsblur/common/assetmodel.js +++ b/media/js/newsblur/common/assetmodel.js @@ -19,6 +19,7 @@ NEWSBLUR.AssetModel = Backbone.Router.extend({ this.friends = {}; this.profile = {}; this.user_profile = new NEWSBLUR.Models.User(); + this.social_services = {}; this.user_profiles = new NEWSBLUR.Collections.Users(); this.follower_profiles = new NEWSBLUR.Collections.Users(); this.following_profiles = new NEWSBLUR.Collections.Users(); @@ -216,7 +217,8 @@ NEWSBLUR.AssetModel = Backbone.Router.extend({ }, callback); }, - mark_story_as_shared: function(story_id, feed_id, comments, source_user_id, callback, error_callback) { + mark_story_as_shared: function(story_id, feed_id, comments, source_user_id, post_to_services, + callback, error_callback) { var pre_callback = _.bind(function(data) { if (data.user_profiles) { this.add_user_profiles(data.user_profiles); @@ -231,7 +233,8 @@ NEWSBLUR.AssetModel = Backbone.Router.extend({ story_id: story_id, feed_id: feed_id, comments: comments, - source_user_id: source_user_id + source_user_id: source_user_id, + post_to_services: post_to_services }, pre_callback, error_callback); } else { error_callback(); @@ -292,6 +295,7 @@ NEWSBLUR.AssetModel = Backbone.Router.extend({ self.starred_count = subscriptions.starred_count; self.social_feeds.reset(subscriptions.social_feeds); self.user_profile.set(subscriptions.social_profile); + self.social_services = subscriptions.social_services; if (!_.isEqual(self.favicons, {})) { self.feeds.each(function(feed) { diff --git a/media/js/newsblur/views/story_detail_view.js b/media/js/newsblur/views/story_detail_view.js index fc097e198..0a221124c 100644 --- a/media/js/newsblur/views/story_detail_view.js +++ b/media/js/newsblur/views/story_detail_view.js @@ -47,7 +47,8 @@ NEWSBLUR.Views.StoryDetailView = Backbone.View.extend({ model: this.model, el: this.el }).template({ - story: this.model + story: this.model, + social_services: NEWSBLUR.assets.social_services }); this.$el.html(this.template(params)); this.toggle_classes(); diff --git a/media/js/newsblur/views/story_share_view.js b/media/js/newsblur/views/story_share_view.js index ed1f673e3..6c2aa43aa 100644 --- a/media/js/newsblur/views/story_share_view.js +++ b/media/js/newsblur/views/story_share_view.js @@ -4,6 +4,8 @@ NEWSBLUR.Views.StoryShareView = Backbone.View.extend({ "click .NB-feed-story-share" : "toggle_feed_story_share_dialog", "click .NB-sideoption-share-save" : "mark_story_as_shared", "click .NB-sideoption-share-unshare" : "mark_story_as_unshared", + "click .NB-sideoption-share-crosspost-twitter" : "toggle_twitter", + "click .NB-sideoption-share-crosspost-facebook" : "toggle_facebook", "keyup .NB-sideoption-share-comments" : "update_share_button_label" }, @@ -13,7 +15,8 @@ NEWSBLUR.Views.StoryShareView = Backbone.View.extend({ render: function() { this.$el.html(this.template({ - story: this.model + story: this.model, + social_services: NEWSBLUR.assets.social_services })); return this; @@ -23,7 +26,14 @@ NEWSBLUR.Views.StoryShareView = Backbone.View.extend({