diff --git a/discover_overlay/discord_connector.py b/discover_overlay/discord_connector.py
index 20d9131..6cba8a2 100644
--- a/discover_overlay/discord_connector.py
+++ b/discover_overlay/discord_connector.py
@@ -34,7 +34,6 @@ class DiscordConnector:
self.last_connection = ""
self.text = []
self.authed = False
- self.timeregex = re.compile(r"\.\d+")
def get_access_token_stage1(self):
global oauth_token
@@ -87,12 +86,8 @@ class DiscordConnector:
self.in_room.remove(userid)
def add_text(self, message):
- # Remove micro time
- time_input = message["timestamp"]
- time_input = self.timeregex.sub("", time_input)
- print(time_input)
utc_time = time.strptime(
- time_input, "%Y-%m-%dT%H:%M:%S%z")
+ message["timestamp"], "%Y-%m-%dT%H:%M:%S.%f%z")
t = time.time()
epoch_time = calendar.timegm(utc_time)
un = message["author"]["username"]
@@ -107,6 +102,7 @@ class DiscordConnector:
'nick': un,
'nick_col': ac,
'time': epoch_time,
+ 'attach': self.get_attachment_from_message(message),
})
self.text_altered = True
@@ -118,7 +114,8 @@ class DiscordConnector:
'content': self.get_message_from_message(message_in),
'nick': message['nick'],
'nick_col': message['nick_col'],
- 'time': message['time']}
+ 'time': message['time'],
+ 'attach': message['attach']}
self.text[idx] = new_message
self.text_altered = True
return
@@ -133,7 +130,9 @@ class DiscordConnector:
return
def get_message_from_message(self, message):
- if "content" in message and len(message["content"]) > 0:
+ if "content_parsed" in message:
+ return message["content_parsed"]
+ elif "content" in message and len(message["content"]) > 0:
return message["content"]
elif len(message["embeds"]) == 1:
if "rawDescription" in message["embeds"][0]:
@@ -141,10 +140,14 @@ class DiscordConnector:
if "author" in message["embeds"][0]:
return message["embeds"][0]["author"]["name"]
elif len(message["attachments"]) == 1:
- # Need to care
- return "-- attachment --"
+ return ""
return ""
+ def get_attachment_from_message(self, message):
+ if len(message["attachments"]) == 1:
+ return message["attachments"]
+ return None
+
def update_user(self, user):
if user["id"] in self.userlist:
if not "mute" in user and "mute" in self.userlist[user["id"]]:
diff --git a/discover_overlay/image_getter.py b/discover_overlay/image_getter.py
index a9a11ae..674c7b8 100644
--- a/discover_overlay/image_getter.py
+++ b/discover_overlay/image_getter.py
@@ -3,10 +3,14 @@ gi.require_version("Gtk", "3.0")
gi.require_version('PangoCairo', '1.0')
gi.require_version('GdkPixbuf', '2.0')
import urllib
+import requests
import threading
from gi.repository.GdkPixbuf import Pixbuf
from gi.repository import Gtk, GLib, Gio, GdkPixbuf, Gdk, Pango, PangoCairo
+import cairo
import logging
+import PIL.Image as Image
+from io import BytesIO
class Image_Getter():
@@ -26,16 +30,69 @@ class Image_Getter():
input_stream = Gio.MemoryInputStream.new_from_data(
response.read(), None)
pixbuf = Pixbuf.new_from_stream(input_stream, None)
- pixbuf = pixbuf.scale_simple(self.size, self.size,
- GdkPixbuf.InterpType.BILINEAR)
+ if self.size:
+ pixbuf = pixbuf.scale_simple(self.size, self.size,
+ GdkPixbuf.InterpType.BILINEAR)
+ # elif self.limit_x or self.limit_y:
+ # px = pixbuf.width()
+ # py = pixbuf.height()
+ # aspect = px / py
+ # scale = 1.0
+ # if self.limit_x and self.limit_y:
+ # scale = min(self.limit_x / px, self.limit_y / py, 1.0)
+ # elif self.limit_x:
+ # scale = min(self.limit_x / px, 1.0)
+ # elif self.limit_y:
+ # scale = min(self.limit_y / py, 1.0)##
+#
+# pixbuf = pixbuf.scale_simple(int(px * scale), int(py * scale),
+# GdkPixbuf.InterpType.BILINEAR)
+
self.func(self.id, pixbuf)
except Exception as e:
logging.error(
- "Could not access : %s" % (url))
+ "Could not access : %s" % (self.url))
logging.error(e)
+class Surface_Getter():
+ def __init__(self, func, url, id, size):
+ self.func = func
+ self.id = id
+ self.url = url
+ self.size = size
+
+ def get_url(self):
+
+ im = Image.open(requests.get(self.url, stream=True, headers={
+ 'Referer': 'https://streamkit.discord.com/overlay/voice', 'User-Agent': 'Mozilla/5.0'}).raw)
+ surf = self.from_pil(im)
+
+ self.func(self.id, surf)
+
+ def from_pil(self, im, alpha=1.0, format=cairo.FORMAT_ARGB32):
+ """
+ :param im: Pillow Image
+ :param alpha: 0..1 alpha to add to non-alpha images
+ :param format: Pixel format for output surface
+ """
+ assert format in (
+ cairo.FORMAT_RGB24, cairo.FORMAT_ARGB32), "Unsupported pixel format: %s" % format
+ if 'A' not in im.getbands():
+ im.putalpha(int(alpha * 256.))
+ arr = bytearray(im.tobytes('raw', 'BGRa'))
+ surface = cairo.ImageSurface.create_for_data(
+ arr, format, im.width, im.height)
+ return surface
+
+
def get_image(func, id, ava, size):
image_getter = Image_Getter(func, id, ava, size)
t = threading.Thread(target=image_getter.get_url, args=())
t.start()
+
+
+def get_surface(func, id, ava, size):
+ image_getter = Surface_Getter(func, id, ava, size)
+ t = threading.Thread(target=image_getter.get_url, args=())
+ t.start()
diff --git a/discover_overlay/text_overlay.py b/discover_overlay/text_overlay.py
index 77af430..3b40616 100644
--- a/discover_overlay/text_overlay.py
+++ b/discover_overlay/text_overlay.py
@@ -9,6 +9,9 @@ from gi.repository import Gtk, GLib, Gio, GdkPixbuf, Gdk, Pango, PangoCairo
import cairo
import logging
import time
+import sys
+import re
+from .image_getter import get_surface
class TextOverlayWindow(OverlayWindow):
@@ -18,10 +21,18 @@ class TextOverlayWindow(OverlayWindow):
self.content = []
self.text_font = None
self.text_size = 13
+ # 0, 0, self.text_size, self.text_size)
+ self.pango_rect = Pango.Rectangle()
+ self.pango_rect.width = self.text_size * Pango.SCALE
+ self.pango_rect.height = self.text_size * Pango.SCALE
+
self.connected = True
self.bg_col = [0.0, 0.6, 0.0, 0.1]
self.fg_col = [1.0, 1.0, 1.0, 1.0]
- #self.text_time = 30
+ self.attachment = {}
+
+ self.imgList = []
+ self.imgFinder = re.compile(r"`")
def set_text_time(self, t):
self.text_time = t
@@ -45,40 +56,128 @@ class TextOverlayWindow(OverlayWindow):
self.bg_col = bg_col
self.redraw()
+ def set_show_attach(self, att):
+ self.show_attach = att
+ self.redraw()
+
def set_popup_style(self, b):
self.popup_style = b
+ def set_font(self, name, size):
+ self.text_font = name
+ self.text_size = size
+ self.pango_rect = Pango.Rectangle()
+ self.pango_rect.width = self.text_size * Pango.SCALE
+ self.pango_rect.height = self.text_size * Pango.SCALE
+ self.redraw()
+
+ def make_line(self, msg):
+ ret = ""
+ if isinstance(msg, list):
+ for a in msg:
+ ret = "%s%s" % (ret, self.make_line(a))
+ elif isinstance(msg, str):
+ ret = msg
+ elif msg['type'] == 'strong':
+ ret = "%s" % (self.make_line(msg['content']))
+ elif msg['type'] == 'text':
+ ret = self.santize_string(msg['content'])
+ elif msg['type'] == 'link':
+ ret = "%s" % (self.make_line(msg['content']))
+ elif msg['type'] == 'emoji':
+ if 'surrogate' in msg:
+ # ['src'] is SVG URL
+ ret = msg
+ # ret = msg['surrogate']
+ else:
+ ### Add Image ###
+ url = ("https://cdn.discordapp.com/emojis/%s.png?v=1" %
+ (msg['emojiId']))
+ img = {"url": url}
+ self.imgList.append(img)
+ ret = "`"
+ elif msg['type'] == 'inlineCode' or msg['type'] == 'codeBlock' or msg['type'] == 'blockQuote':
+ ret = "%s" % (
+ msg['content'])
+ elif msg['type'] == 'u':
+ ret = "%s" % (self.make_line(msg['content']))
+ elif msg['type'] == 'em':
+ ret = "%s" % (self.make_line(msg['content']))
+ elif msg['type'] == 's':
+ ret = "%s" % (self.make_line(msg['content']))
+ elif msg['type'] == 'channel':
+ ret = self.make_line(msg['content'])
+ elif msg['type'] == 'mention':
+ ret = self.make_line(msg['content'])
+ elif msg['type'] == 'br':
+ ret = '\n'
+ else:
+ logging.warning("Unknown text type : %s" % (msg["type"]))
+ return ret
+
+ def recv_attach(self, id, pix):
+ self.attachment[id] = pix
+ self.redraw()
+
def do_draw(self, context):
self.context = context
- context.set_antialias(self.compositing)
+ context.set_antialias(cairo.ANTIALIAS_GOOD)
(w, h) = self.get_size()
# Make background transparent
context.set_source_rgba(0.0, 0.0, 0.0, 0.0)
context.paint()
- if not self.connected:
- return
- long_string = ""
- sep = ""
- tnow = time.time()
- for line in self.content:
- if not self.popup_style or tnow - line['time'] < self.text_time:
- col = "#fff"
- if 'nick_col' in line and line['nick_col']:
- col = line['nick_col']
- long_string = "%s%s%s: %s" % (
- long_string,
- sep,
- self.santize_string(col),
- self.santize_string(line["nick"]),
- self.santize_string(line["content"]))
- sep = '\n'
- if len(long_string) == 0:
- return
- layout = self.create_pango_layout(long_string)
- layout.set_markup(long_string, -1)
- attr = Pango.AttrList()
+ cy = h
+ for line in reversed(self.content):
+ out_line = ""
+ self.imgList = []
+
+ col = "#fff"
+ if 'nick_col' in line and line['nick_col']:
+ col = line['nick_col']
+ for in_line in line['content']:
+ out_line = "%s%s" % (out_line, self.make_line(in_line))
+ if line['attach'] and self.show_attach:
+ at = line['attach'][0]
+ url = at['url']
+ if url in self.attachment:
+ cy = self.draw_attach(cy, url)
+ else:
+ get_surface(self.recv_attach,
+ url,
+ url, None)
+ # cy = self.draw_text(cy, "%s" % (line['attach']))
+ cy = self.draw_text(cy, "%s: %s" % (self.santize_string(col),
+ self.santize_string(line["nick"]), out_line))
+ if cy <= 0:
+ # We've done enough
+ break
+
+ def draw_attach(self, y, url):
+ (w, h) = self.get_size()
+ if self.attachment[url]:
+ pix = self.attachment[url]
+ h = pix.get_height()
+ self.col(self.bg_col)
+ self.context.rectangle(0, y - h, w, h)
+ self.context.fill()
+ self.context.set_operator(cairo.OPERATOR_OVER)
+ self.col([1, 1, 1, 1])
+ self.context.set_source_surface(pix, 0, y - h)
+ self.context.rectangle(0, y - h, w, h)
+ self.context.fill()
+ # Gdk.cairo_set_source_pixbuf(self.context, pix, 0, y - h)
+ # self.context.paint()
+ return y - h
+ return y
+
+ def draw_text(self, y, text):
+ (w, h) = self.get_size()
+
+ layout = self.create_pango_layout(text)
+ layout.set_markup(text, -1)
+ attr = layout.get_attributes()
layout.set_width(Pango.SCALE * w)
layout.set_spacing(Pango.SCALE * 3)
@@ -87,16 +186,62 @@ class TextOverlayWindow(OverlayWindow):
"%s %s" % (self.text_font, self.text_size))
layout.set_font_description(font)
tw, th = layout.get_pixel_size()
-
- # Don't layer drawing over each other, always replace
self.col(self.bg_col)
- context.rectangle(0, -th + h, w, th)
- context.fill()
- context.set_operator(cairo.OPERATOR_OVER)
+ self.context.rectangle(0, y - th, w, th)
+ self.context.fill()
+ self.context.set_operator(cairo.OPERATOR_OVER)
self.col(self.fg_col)
- context.move_to(0, -th + h)
- PangoCairo.show_layout(context, layout)
+ self.context.move_to(0, y - th)
+ PangoCairo.context_set_shape_renderer(
+ self.get_pango_context(), self.render_custom, None)
+
+ text = layout.get_text()
+ count = 0
+
+ for loc in self.imgFinder.finditer(text):
+ idx = loc.start()
+
+ if len(self.imgList) <= count:
+ break # We fucked up. Who types ` anyway
+ url = self.imgList[count]
+
+ at = Pango.attr_shape_new_with_data(
+ self.pango_rect, self.pango_rect, count, None)
+ at.start_index = idx
+ at.end_index = idx + 1
+ attr.insert(at)
+ count += 1
+ layout.set_attributes(attr)
+
+ PangoCairo.show_layout(self.context, layout)
+ return y - th
+
+ def render_custom(self, ctx, shape, path, data):
+ key = self.imgList[shape.data]['url']
+ if key not in self.attachment:
+ get_surface(self.recv_attach,
+ key,
+ key, None)
+ return
+ pix = self.attachment[key]
+ (x, y) = ctx.get_current_point()
+ px = pix.get_width()
+ py = pix.get_height()
+ ctx.save()
+ ctx.translate(x, y - self.text_size)
+ ctx.scale(self.text_size, self.text_size)
+ ctx.scale(1 / px, 1 / py)
+ ctx.set_source_surface(pix, 0, 0)
+
+ ctx.rectangle(0, 0, px, py)
+ if not path:
+ ctx.fill()
+ ctx.restore()
+
+ ctx.move_to(x + self.text_size, y)
+
+ return True
def santize_string(self, string):
# I hate that Pango has nothing for this.
diff --git a/discover_overlay/text_settings.py b/discover_overlay/text_settings.py
index 7df0a5f..157f8ab 100644
--- a/discover_overlay/text_settings.py
+++ b/discover_overlay/text_settings.py
@@ -109,6 +109,8 @@ class TextSettingsWindow(SettingsWindow):
self.popup_style = config.getboolean(
"text", "popup_style", fallback=False)
self.text_time = config.getint("text", "text_time", fallback=30)
+ self.show_attach = config.getboolean(
+ "text", "show_attach", fallback=True)
logging.info(
"Loading saved channel %s" % (self.channel))
@@ -124,6 +126,7 @@ class TextSettingsWindow(SettingsWindow):
self.overlay.set_fg(self.fg_col)
self.overlay.set_popup_style(self.popup_style)
self.overlay.set_text_time(self.text_time)
+ self.overlay.set_show_attach(self.show_attach)
def save_config(self):
config = ConfigParser(interpolation=None)
@@ -145,6 +148,7 @@ class TextSettingsWindow(SettingsWindow):
config.set("text", "fg_col", json.dumps(self.fg_col))
config.set("text", "popup_style", "%s" % (int(self.popup_style)))
config.set("text", "text_time", "%s" % (int(self.text_time)))
+ config.set("text", "show_attach", "%s" % (int(self.show_attach)))
if self.font:
config.set("text", "font", self.font)
@@ -255,6 +259,12 @@ class TextSettingsWindow(SettingsWindow):
channel.add_attribute(rt, "text", 0)
channel.add_attribute(rt, 'sensitive', 1)
+ # Show Attachments
+ show_attach_label = Gtk.Label.new("Show Attachments")
+ show_attach = Gtk.CheckButton.new()
+ show_attach.set_active(self.show_attach)
+ show_attach.connect("toggled", self.change_show_attach)
+
self.align_x_widget = align_x
self.align_y_widget = align_y
self.align_monitor_widget = monitor
@@ -283,6 +293,8 @@ class TextSettingsWindow(SettingsWindow):
box.attach(align_x, 1, 9, 1, 1)
box.attach(align_y, 1, 10, 1, 1)
box.attach(align_placement_button, 1, 11, 1, 1)
+ box.attach(show_attach_label, 0, 12, 1, 1)
+ box.attach(show_attach, 1, 12, 1, 1)
self.add(box)
@@ -413,3 +425,9 @@ class TextSettingsWindow(SettingsWindow):
self.fg_col = c
self.save_config()
+
+ def change_show_attach(self, button):
+ self.overlay.set_show_attach(button.get_active())
+
+ self.show_attach = button.get_active()
+ self.save_config()
diff --git a/discover_overlay/voice_overlay.py b/discover_overlay/voice_overlay.py
index 13d1f66..8591965 100644
--- a/discover_overlay/voice_overlay.py
+++ b/discover_overlay/voice_overlay.py
@@ -104,9 +104,9 @@ class VoiceOverlayWindow(OverlayWindow):
def reset_avatar(self):
self.avatars = {}
- self.def_avatar = get_image(self.recv_avatar,
- "https://cdn.discordapp.com/embed/avatars/3.png",
- 'def', self.avatar_size)
+ get_image(self.recv_avatar,
+ "https://cdn.discordapp.com/embed/avatars/3.png",
+ 'def', self.avatar_size)
def set_user_list(self, userlist, alt):
self.userlist = userlist
@@ -263,7 +263,7 @@ class VoiceOverlayWindow(OverlayWindow):
def draw_avatar_pix(self, context, pixbuf, x, y, c, alpha):
if not pixbuf:
pixbuf = self.def_avatar
-
+
if not pixbuf:
return
context.move_to(x, y)