comments.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. # -*- coding: utf-8 -*-
  2. import codecs
  3. import json
  4. import sys
  5. import time
  6. import os
  7. from socket import error as SocketError
  8. from socket import timeout
  9. from ssl import SSLError
  10. try:
  11. # py2
  12. from urllib2 import URLError
  13. from httplib import HTTPException
  14. except ImportError:
  15. # py3
  16. from urllib.error import URLError
  17. from http.client import HTTPException
  18. from .logger import log_seperator, supports_color, log_info_blue, log_info_green, log_warn, log_error, log_whiteline, log_plain
  19. from instagram_private_api import ClientError
  20. """
  21. This feature of PyInstaLive was originally written by https://github.com/taengstagram
  22. The code below and in downloader.py that's related to the comment downloading
  23. feature is modified by https://github.com/notcammy
  24. """
  25. class CommentsDownloader(object):
  26. def __init__(self, api, broadcast, destination_file):
  27. self.api = api
  28. self.broadcast = broadcast
  29. self.destination_file = destination_file
  30. self.comments = []
  31. def get_live(self, first_comment_created_at=0):
  32. comments_collected = self.comments
  33. before_count = len(comments_collected)
  34. try:
  35. comments_res = self.api.broadcast_comments(
  36. self.broadcast.get('id'), last_comment_ts=first_comment_created_at)
  37. comments = comments_res.get('comments', [])
  38. first_comment_created_at = (
  39. comments[0]['created_at_utc'] if comments else int(time.time() - 5))
  40. comments_collected.extend(comments)
  41. after_count = len(comments_collected)
  42. if after_count > before_count:
  43. broadcast = self.broadcast.copy()
  44. broadcast.pop('segments', None) # save space
  45. broadcast['comments'] = comments_collected
  46. with open(self.destination_file, 'w') as outfile:
  47. json.dump(broadcast, outfile, indent=2)
  48. self.comments = comments_collected
  49. except (SSLError, timeout, URLError, HTTPException, SocketError) as e:
  50. log_warn('Comment downloading error: %s' % e)
  51. except ClientError as e:
  52. if e.code == 500:
  53. log_warn('Comment downloading ClientError: %d %s' % (e.code, e.error_response))
  54. elif e.code == 400 and not e.msg:
  55. log_warn('Comment downloading ClientError: %d %s' % (e.code, e.error_response))
  56. else:
  57. raise e
  58. finally:
  59. try:
  60. time.sleep(4)
  61. except KeyboardInterrupt:
  62. return first_comment_created_at
  63. return first_comment_created_at
  64. def get_replay(self):
  65. comments_collected = []
  66. starting_offset = 0
  67. encoding_tag = self.broadcast.get('encoding_tag')
  68. while True:
  69. try:
  70. comments_res = self.api.replay_broadcast_comments(
  71. self.broadcast.get('id'), starting_offset=starting_offset, encoding_tag=encoding_tag)
  72. starting_offset = comments_res.get('ending_offset', 0)
  73. comments = comments_res.get('comments', [])
  74. comments_collected.extend(comments)
  75. if not comments_res.get('comments') or not starting_offset:
  76. break
  77. time.sleep(4)
  78. except:
  79. pass
  80. if comments_collected:
  81. self.broadcast['comments'] = comments_collected
  82. self.broadcast['initial_buffered_duration'] = 0
  83. with open(self.destination_file, 'w') as outfile:
  84. json.dump(self.broadcast, outfile, indent=2)
  85. self.comments = comments_collected
  86. def save(self):
  87. broadcast = self.broadcast.copy()
  88. broadcast.pop('segments', None)
  89. broadcast['comments'] = self.comments
  90. with open(self.destination_file, 'w') as outfile:
  91. json.dump(broadcast, outfile, indent=2)
  92. @staticmethod
  93. def generate_log(comments, download_start_time, log_file, comments_delay=10.0):
  94. comment_log_save_path = os.path.dirname(os.path.dirname(log_file))
  95. comment_log_file_name = os.path.basename(log_file)
  96. log_file = os.path.join(comment_log_save_path, comment_log_file_name)
  97. python_version = sys.version.split(' ')[0]
  98. subtitles_timeline = {}
  99. wide_build = sys.maxunicode > 65536
  100. for i, c in enumerate(comments):
  101. if 'offset' in c:
  102. for k in c.get('comment').keys():
  103. c[k] = c.get('comment', {}).get(k)
  104. c['created_at_utc'] = download_start_time + c.get('offset')
  105. created_at_utc = str(2 * (c.get('created_at_utc') // 2))
  106. comment_list = subtitles_timeline.get(created_at_utc) or []
  107. comment_list.append(c)
  108. subtitles_timeline[created_at_utc] = comment_list
  109. if subtitles_timeline:
  110. comment_errors = 0
  111. total_comments = 0
  112. timestamps = sorted(subtitles_timeline.keys())
  113. mememe = False
  114. subs = []
  115. for i, tc in enumerate(timestamps):
  116. t = subtitles_timeline[tc]
  117. clip_start = int(tc) - int(download_start_time) + int(comments_delay)
  118. if clip_start < 0:
  119. clip_start = 0
  120. comments_log = ''
  121. for c in t:
  122. try:
  123. if python_version.startswith('3'):
  124. if (c.get('user', {}).get('is_verified')):
  125. comments_log+= '{}{}\n\n'.format(time.strftime('%H:%M:%S\n', time.gmtime(clip_start)), '{} {}: {}'.format(c.get('user', {}).get('username'), "(v)", c.get('text')))
  126. else:
  127. comments_log += '{}{}\n\n'.format(time.strftime('%H:%M:%S\n', time.gmtime(clip_start)), '{}: {}'.format(c.get('user', {}).get('username'), c.get('text')))
  128. else:
  129. if not wide_build:
  130. if (c.get('user', {}).get('is_verified')):
  131. comments_log += '{}{}\n\n'.format(time.strftime('%H:%M:%S\n', time.gmtime(clip_start)), '{} {}: {}'.format(c.get('user', {}).get('username'), "(v)", c.get('text').encode('ascii', 'ignore')))
  132. else:
  133. comments_log += '{}{}\n\n'.format(time.strftime('%H:%M:%S\n', time.gmtime(clip_start)), '{}: {}'.format(c.get('user', {}).get('username'), c.get('text').encode('ascii', 'ignore')))
  134. else:
  135. if (c.get('user', {}).get('is_verified')):
  136. comments_log += '{}{}\n\n'.format(time.strftime('%H:%M:%S\n', time.gmtime(clip_start)), '{} {}: {}'.format(c.get('user', {}).get('username'), "(v)", c.get('text')))
  137. else:
  138. comments_log += '{}{}\n\n'.format(time.strftime('%H:%M:%S\n', time.gmtime(clip_start)), '{}: {}'.format(c.get('user', {}).get('username'), c.get('text')))
  139. except:
  140. comment_errors += 1
  141. try:
  142. if (c.get('user', {}).get('is_verified')):
  143. comments_log += '{}{}\n\n'.format(time.strftime('%H:%M:%S\n', time.gmtime(clip_start)), '{} {}: {}'.format(c.get('user', {}).get('username'), "(v)", c.get('text').encode('ascii', 'ignore')))
  144. else:
  145. comments_log += '{}{}\n\n'.format(time.strftime('%H:%M:%S\n', time.gmtime(clip_start)), '{}: {}'.format(c.get('user', {}).get('username'), c.get('text').encode('ascii', 'ignore')))
  146. except:
  147. pass
  148. total_comments += 1
  149. subs.append(comments_log)
  150. with codecs.open(log_file, 'w', 'utf-8-sig') as log_outfile:
  151. if python_version.startswith('2') and not wide_build:
  152. log_outfile.write('This log was generated using Python {:s} without wide unicode support. This means characters such as emojis are not saved.\nUser comments without any text usually are comments that only had emojis.\nBuild Python 2 with the --enable-unicode=ucs4 argument or use Python 3 for full unicode support.\n\n'.format(python_version) + ''.join(subs))
  153. else:
  154. log_outfile.write(''.join(subs))
  155. return comment_errors, total_comments