Adding post to twitter/facebook during share. Also adding email for new followers, which includes common followers and common followings.

This commit is contained in:
Samuel Clay 2012-06-27 23:57:57 -07:00
parent 63988e9e15
commit 16781c6450
12 changed files with 234 additions and 24 deletions

View file

@ -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,
}

View file

@ -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()

View file

@ -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

View file

@ -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%;

View file

@ -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) {

View file

@ -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();

View file

@ -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({
<div class="NB-sideoption-share-wrapper">\
<div class="NB-sideoption-share">\
<div class="NB-sideoption-share-wordcount"></div>\
<div class="NB-sideoption-share-optional">Optional</div>\
<div class="NB-sideoption-share-crosspost">\
<% if (social_services.twitter.twitter_uid) { %>\
<div class="NB-sideoption-share-crosspost-twitter"></div>\
<% } %>\
<% if (social_services.facebook.facebook_uid) { %>\
<div class="NB-sideoption-share-crosspost-facebook"></div>\
<% } %>\
</div>\
<div class="NB-sideoption-share-title">Comments:</div>\
<textarea class="NB-sideoption-share-comments"><%= story.get("shared_comments") %></textarea>\
<div class="NB-menu-manage-story-share-save NB-modal-submit-green NB-sideoption-share-save NB-modal-submit-button">Share</div>\
@ -41,6 +51,8 @@ NEWSBLUR.Views.StoryShareView = Backbone.View.extend({
var $comment_input = this.$('.NB-sideoption-share-comments');
var $story_comments = this.$('.NB-feed-story-comments');
var $unshare_button = this.$('.NB-sideoption-share-unshare');
var $twitter_button = this.$('.NB-sideoption-share-crosspost-twitter');
var $facebook_button = this.$('.NB-sideoption-share-crosspost-facebook');
if (options.close ||
($sideoption.hasClass('NB-active') && !options.resize_open)) {
@ -71,8 +83,11 @@ NEWSBLUR.Views.StoryShareView = Backbone.View.extend({
}
} else {
// Open/resize
this.$('.NB-error').remove();
$sideoption.addClass('NB-active');
$unshare_button.toggleClass('NB-hidden', !this.model.get("shared"));
$twitter_button.toggleClass('NB-active', !!NEWSBLUR.assets.preference('post_to_twitter'));
$facebook_button.toggleClass('NB-active', !!NEWSBLUR.assets.preference('post_to_facebook'));
var $share_clone = $share.clone();
var full_height = $share_clone.css({
'height': 'auto',
@ -135,10 +150,14 @@ NEWSBLUR.Views.StoryShareView = Backbone.View.extend({
var comments = _.string.trim((options.source == 'menu' ? $comments_menu : $comments_sideoptions).val());
var feed = NEWSBLUR.assets.get_feed(NEWSBLUR.reader.active_feed);
var source_user_id = feed && feed.get('user_id');
var post_to_services = _.compact([
NEWSBLUR.assets.preference('post_to_twitter') && 'twitter',
NEWSBLUR.assets.preference('post_to_facebook') && 'facebook'
]);
$share_button.addClass('NB-saving').addClass('NB-disabled').text('Sharing...');
$share_button_menu.addClass('NB-saving').addClass('NB-disabled').text('Sharing...');
NEWSBLUR.assets.mark_story_as_shared(this.model.id, this.model.get('story_feed_id'), comments, source_user_id, _.bind(this.post_share_story, this, true), _.bind(function(data) {
NEWSBLUR.assets.mark_story_as_shared(this.model.id, this.model.get('story_feed_id'), comments, source_user_id, post_to_services, _.bind(this.post_share_story, this, true), _.bind(function(data) {
this.post_share_error(data, true);
}, this));
@ -242,6 +261,30 @@ NEWSBLUR.Views.StoryShareView = Backbone.View.extend({
count_selected_words_when_sharing_story: function($feed_story) {
var $wordcount = $('.NB-sideoption-share-wordcount', $feed_story);
},
toggle_twitter: function() {
var $twitter_button = this.$('.NB-sideoption-share-crosspost-twitter');
if (NEWSBLUR.assets.preference('post_to_twitter')) {
NEWSBLUR.assets.preference('post_to_twitter', false);
} else {
NEWSBLUR.assets.preference('post_to_twitter', true);
}
$twitter_button.toggleClass('NB-active', NEWSBLUR.assets.preference('post_to_twitter'));
},
toggle_facebook: function() {
var $facebook_button = this.$('.NB-sideoption-share-crosspost-facebook');
if (NEWSBLUR.assets.preference('post_to_facebook')) {
NEWSBLUR.assets.preference('post_to_facebook', false);
} else {
NEWSBLUR.assets.preference('post_to_facebook', true);
}
$facebook_button.toggleClass('NB-active', NEWSBLUR.assets.preference('post_to_facebook'));
}
});

View file

@ -1,16 +1,17 @@
{% block body %}{% endblock body %}
- Samuel Clay, @samuelclay
- Samuel & Roy
-----------------------------------------------------------------------------
Stay up to date and in touch with me, yr. developer, in a few different ways:
NewsBlur is your social news reader with intelligence. A new sound of an old instrument.
Stay up to date and in touch with us, yr. developers, in a few different ways:
* Follow @samuelclay on Twitter: http://twitter.com/samuelclay/
* Follow @newsblur on Twitter: http://twitter.com/newsblur/
* Follow @samuelclay on GitHub: http://github.com/samuelclay/
{% block resources_header %}There are a few resources you can use if you end up loving NewsBlur:{% endblock resources_header %}
{% block resources_header %}To get the most out of NewsBlur, here are a few resources:{% endblock resources_header %}
* Read the NewsBlur Blog: http://blog.newsblur.com
* Get support on NewsBlur's Get Satisfaction: http://getsatisfaction.com/newsblur/

View file

@ -20,7 +20,7 @@
{% block body %}{% endblock %}
<p style="line-height:20px;">- Samuel Clay, <a href="http://twitter.com/samuelclay" style="text-decoration:none">@samuelclay</a></p>
<p style="line-height:20px;clear: both;">- Samuel &amp; Roy</p>
</td>
</tr>
<tr>
@ -28,22 +28,22 @@
<table>
<tr>
<td>
<p style="line-height:20px;">Stay up to date and in touch with me, yr. developer, in a few different ways:</p>
<p style="line-height:20px;">NewsBlur is your social news reader with intelligence. A new sound of an old instrument.</p>
<p style="line-height:20px;">Stay up to date and in touch with us, yr. developers, in a few different ways:</p>
<p style="line-height: 20px;">
<ul style="list-style: none;">
<li style="line-height:22px;"><a href="http://twitter.com/samuelclay/" style="text-decoration:none"><img src="http://www.newsblur.com/media/img/reader/twitter_icon.png" style="width:16px;height:16px;vertical-align:top;padding-top:3px;"> Follow @samuelclay on Twitter</a>.</li>
<li style="line-height:22px;"><a href="http://twitter.com/newsblur/" style="text-decoration:none"><img src="http://www.newsblur.com/media/img/reader/twitter.png" style="width:16px;height:16px;vertical-align:top;padding-top:3px;"> Follow @newsblur on Twitter</a>.</li>
<li style="line-height:22px;"><a href="http://github.com/samuelclay/" style="text-decoration:none"><img src="http://www.newsblur.com/media/img/reader/github_icon.png" style="width:16px;height:16px;vertical-align:top;padding-top:3px;"> Follow @samuelclay on GitHub</a>. &larr; I live on props.</li>
<li style="line-height:22px;"><a href="http://github.com/samuelclay/" style="text-decoration:none"><img src="http://www.newsblur.com/media/img/reader/github_icon.png" style="width:16px;height:16px;vertical-align:top;padding-top:3px;"> Follow @samuelclay on GitHub</a>.</li>
</ul>
</p>
<p style="line-height: 20px;">{% block resources_header %}There are a couple resources you can use if you end up loving NewsBlur:{% endblock resources_header %}</p>
<p style="line-height: 20px;">{% block resources_header %}To get the most out of NewsBlur, here are a few resources:{% endblock resources_header %}</p>
<p style="line-height: 20px;">
<ul style="list-style: none;">
<li style="line-height:22px;"><a href="http://blog.newsblur.com" style="text-decoration:none"><img src="http://www.newsblur.com/media/img/favicon.png" style="width:16px;height:16px;vertical-align:top;padding-top:3px;"> Read the NewsBlur Blog</a>.</li>
<li style="line-height:22px;"><a href="http://getsatisfaction.com/newsblur/" style="text-decoration:none"><img src="http://www.newsblur.com/media/img/reader/getsatisfaction.png" style="width:16px;height:16px;vertical-align:top;padding-top:3px;"> Get support on NewsBlur's Get Satisfaction</a>.</li>
</ul>
</p>
<p style="line-height: 20px;">There's plenty of ways to use NewsBlur beyond the website:</p>
<p style="line-height: 20px;">There's plenty of ways to use NewsBlur beyond the web:</p>
<p style="line-height: 20px;">
<ul style="list-style: none;">
<li style="line-height:22px;"><a href="http://www.newsblur.com/iphone/" style="text-decoration:none"><img src="http://www.newsblur.com/media/img/reader/iphone_icon.png" style="width:16px;height:16px;vertical-align:top;padding-top:3px;"> Download the free iPhone App</a>.</li>

View file

@ -6,3 +6,5 @@
<p style="line-height: 20px;">Spend a few days trying out NewsBlur. I hope you end up loving it.</p>
<p style="line-height: 20px;">If you really do love using NewsBlur you should purchase a fancy <b>premium account</b> for only $12/year (nights and weekends and all major public holidays included). You get to support an independent developer, help feed his dog, and contribute to the long-term health of NewsBlur.</p>
{% endblock %}
{% block resources_header %}There are a couple resources you can use if you end up loving NewsBlur:{% endblock resources_header %}

View file

@ -337,6 +337,10 @@
user_id of the original sharer.
optional: true
example: "128"
- key: post_to_services
desc: "List of services to cross-post to. Can be 'twitter' and/or 'facebook'."
optional: true
example: "['twitter', 'facebook']"
- url: /social/unshare_story
method: POST
short_desc: "Remove a shared story from user's blurblog."

View file

@ -146,7 +146,6 @@ class bunch(dict):
else:
self.__setitem__(item, value)
class MLStripper(HTMLParser):
def __init__(self):
self.reset()
@ -159,4 +158,16 @@ class MLStripper(HTMLParser):
def strip_tags(html):
s = MLStripper()
s.feed(html)
return s.get_data()
return s.get_data()
def truncate_chars(value, max_length):
if len(value) <= max_length:
return value
truncd_val = value[:max_length]
if value[max_length] != " ":
rightmost_space = truncd_val.rfind(" ")
if rightmost_space != -1:
truncd_val = truncd_val[:rightmost_space]
return truncd_val + "..."