models.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. from __future__ import unicode_literals
  2. from future.builtins import str
  3. from datetime import datetime
  4. import re
  5. try:
  6. from urllib.parse import quote
  7. except ImportError:
  8. # Python 2
  9. from urllib import quote
  10. from django.db import models
  11. from django.utils.encoding import python_2_unicode_compatible
  12. from django.utils.html import urlize
  13. from django.utils.timezone import make_aware, utc
  14. from django.utils.translation import ugettext_lazy as _
  15. from requests_oauthlib import OAuth1
  16. import requests
  17. from mezzanine.conf import settings
  18. from mezzanine.twitter import QUERY_TYPE_CHOICES, QUERY_TYPE_USER, \
  19. QUERY_TYPE_LIST, QUERY_TYPE_SEARCH
  20. from mezzanine.twitter import get_auth_settings
  21. from mezzanine.twitter.managers import TweetManager
  22. re_usernames = re.compile("(^|\W)@([0-9a-zA-Z+_]+)", re.IGNORECASE)
  23. re_hashtags = re.compile("#([0-9a-zA-Z+_]+)", re.IGNORECASE)
  24. replace_hashtags = "<a href=\"http://twitter.com/search?q=%23\\1\">#\\1</a>"
  25. replace_usernames = "\\1<a href=\"http://twitter.com/\\2\">@\\2</a>"
  26. class TwitterQueryException(Exception):
  27. pass
  28. @python_2_unicode_compatible
  29. class Query(models.Model):
  30. type = models.CharField(_("Type"), choices=QUERY_TYPE_CHOICES,
  31. max_length=10)
  32. value = models.CharField(_("Value"), max_length=140)
  33. interested = models.BooleanField("Interested", default=True)
  34. class Meta:
  35. verbose_name = _("Twitter query")
  36. verbose_name_plural = _("Twitter queries")
  37. ordering = ("-id",)
  38. def __str__(self):
  39. return "%s: %s" % (self.get_type_display(), self.value)
  40. def run(self):
  41. """
  42. Request new tweets from the Twitter API.
  43. """
  44. try:
  45. value = quote(self.value)
  46. except KeyError:
  47. value = self.value
  48. urls = {
  49. QUERY_TYPE_USER: ("https://api.twitter.com/1.1/statuses/"
  50. "user_timeline.json?screen_name=%s"
  51. "&include_rts=true" % value.lstrip("@")),
  52. QUERY_TYPE_LIST: ("https://api.twitter.com/1.1/lists/statuses.json"
  53. "?list_id=%s&include_rts=true" % value),
  54. QUERY_TYPE_SEARCH: "https://api.twitter.com/1.1/search/tweets.json"
  55. "?q=%s" % value,
  56. }
  57. try:
  58. url = urls[self.type]
  59. except KeyError:
  60. raise TwitterQueryException("Invalid query type: %s" % self.type)
  61. auth_settings = get_auth_settings()
  62. if not auth_settings:
  63. from mezzanine.conf import registry
  64. if self.value == registry["TWITTER_DEFAULT_QUERY"]["default"]:
  65. # These are some read-only keys and secrets we use
  66. # for the default query (eg nothing has been configured)
  67. auth_settings = (
  68. "KxZTRD3OBft4PP0iQW0aNQ",
  69. "sXpQRSDUVJ2AVPZTfh6MrJjHfOGcdK4wRb1WTGQ",
  70. "1368725588-ldWCsd54AJpG2xcB5nyTHyCeIC3RJcNVUAkB1OI",
  71. "r9u7qS18t8ad4Hu9XVqmCGxlIpzoCN3e1vx6LOSVgyw3R",
  72. )
  73. else:
  74. raise TwitterQueryException("Twitter OAuth settings missing")
  75. try:
  76. tweets = requests.get(url, auth=OAuth1(*auth_settings)).json()
  77. except Exception as e:
  78. raise TwitterQueryException("Error retrieving: %s" % e)
  79. try:
  80. raise TwitterQueryException(tweets["errors"][0]["message"])
  81. except (IndexError, KeyError, TypeError):
  82. pass
  83. if self.type == "search":
  84. tweets = tweets["statuses"]
  85. for tweet_json in tweets:
  86. remote_id = str(tweet_json["id"])
  87. tweet, created = self.tweets.get_or_create(remote_id=remote_id)
  88. if not created:
  89. continue
  90. if "retweeted_status" in tweet_json:
  91. user = tweet_json['user']
  92. tweet.retweeter_user_name = user["screen_name"]
  93. tweet.retweeter_full_name = user["name"]
  94. tweet.retweeter_profile_image_url = user["profile_image_url"]
  95. tweet_json = tweet_json["retweeted_status"]
  96. if self.type == QUERY_TYPE_SEARCH:
  97. tweet.user_name = tweet_json['user']['screen_name']
  98. tweet.full_name = tweet_json['user']['name']
  99. tweet.profile_image_url = \
  100. tweet_json['user']["profile_image_url"]
  101. date_format = "%a %b %d %H:%M:%S +0000 %Y"
  102. else:
  103. user = tweet_json["user"]
  104. tweet.user_name = user["screen_name"]
  105. tweet.full_name = user["name"]
  106. tweet.profile_image_url = user["profile_image_url"]
  107. date_format = "%a %b %d %H:%M:%S +0000 %Y"
  108. tweet.text = urlize(tweet_json["text"])
  109. tweet.text = re_usernames.sub(replace_usernames, tweet.text)
  110. tweet.text = re_hashtags.sub(replace_hashtags, tweet.text)
  111. if getattr(settings, 'TWITTER_STRIP_HIGH_MULTIBYTE', False):
  112. chars = [ch for ch in tweet.text if ord(ch) < 0x800]
  113. tweet.text = ''.join(chars)
  114. d = datetime.strptime(tweet_json["created_at"], date_format)
  115. tweet.created_at = make_aware(d, utc)
  116. try:
  117. tweet.save()
  118. except Warning:
  119. pass
  120. tweet.save()
  121. self.interested = False
  122. self.save()
  123. class Tweet(models.Model):
  124. remote_id = models.CharField(_("Twitter ID"), max_length=50)
  125. created_at = models.DateTimeField(_("Date/time"), null=True)
  126. text = models.TextField(_("Message"), null=True)
  127. profile_image_url = models.URLField(_("Profile image URL"), null=True)
  128. user_name = models.CharField(_("User name"), max_length=100, null=True)
  129. full_name = models.CharField(_("Full name"), max_length=100, null=True)
  130. retweeter_profile_image_url = models.URLField(
  131. _("Profile image URL (Retweeted by)"), null=True)
  132. retweeter_user_name = models.CharField(
  133. _("User name (Retweeted by)"), max_length=100, null=True)
  134. retweeter_full_name = models.CharField(
  135. _("Full name (Retweeted by)"), max_length=100, null=True)
  136. query = models.ForeignKey("Query", related_name="tweets")
  137. objects = TweetManager()
  138. class Meta:
  139. verbose_name = _("Tweet")
  140. verbose_name_plural = _("Tweets")
  141. ordering = ("-created_at",)
  142. def __str__(self):
  143. return "%s: %s" % (self.user_name, self.text)
  144. def is_retweet(self):
  145. return self.retweeter_user_name is not None