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