Skip to content

Commit

Permalink
Add twitter status update on puzzle created
Browse files Browse the repository at this point in the history
commit 913aaa8
Author: heyrict <[email protected]>
Date:   Tue Jun 12 10:39:57 2018 +0800

    add bottom border

commit 9d8cf70
Author: heyrict <[email protected]>
Date:   Tue Jun 12 10:16:16 2018 +0800

    Drawing with canvas

commit 3c8ef3d
Author: heyrict <[email protected]>
Date:   Mon Jun 11 08:42:29 2018 +0800

    Fix rendering disorder

commit a914832
Author: heyrict <[email protected]>
Date:   Mon Jun 11 08:20:23 2018 +0800

    Add textify markdown filter

commit c865032
Author: heyrict <[email protected]>
Date:   Sun Jun 10 21:04:12 2018 +0800

    Add imaging lib
  • Loading branch information
heyrict committed Jun 12, 2018
1 parent 4b42fdc commit f5595fa
Show file tree
Hide file tree
Showing 8 changed files with 247 additions and 6 deletions.
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Cindy-Realtime
==============
<img align="right" height="192" width="192" src="https://github.com/heyrict/cindy-realtime/blob/master/react-boilerplate/app/images/icon-192x192.png" />
<img align="right" height="192" width="192" src="https://github.com/heyrict/cindy-realtime/blob/master/react-boilerplate/app/images/favicon.png" />

This is a project started in homage to [latethin](http://sui-hei.net) created by [kamisugi(上杉)](http://sui-hei.net/mondai/profile/1).

Expand Down Expand Up @@ -59,6 +59,12 @@ Requisitories
# Use npm (bower is somewhat alike)
```

- Python Image Library and its dependencies (optional, if you want to enable `TWEET_WITH_IMAGE`)

```bash
cd ./imaging && make setup && pip install -r requirements.txt
```

Develop
-------
1. Clone this repo.
Expand Down Expand Up @@ -167,6 +173,9 @@ Also, note that all the configuration files need to be adjusted to you system (e
service prerender start
```
5. (Optionally, if you want to enable twitter bot, change settings in `# Twitter Bot` in [security.py](./cindy/security.py).
Special [requisitories](#requisitories) are also needed for enabling `TWEET_WITH_PICTURE`.
Contributers
------------
Expand Down
3 changes: 2 additions & 1 deletion cindy/security.py.template
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,10 @@ DEBUG = False

# Twitter Bot
ENABLE_TWITTERBOT = False
TWEET_WITH_PICTURE = True
TOKEN = ''
TOKEN_SECRET = ''
CONSUMER_KEY = ''
CONSUMER_SECRET = ''
TWEET_MESSAGE = '%(user_nickname)sさんがCindyにて新しい問題『%(title)s』を出題しました。\n'\
'https://www.cindythink.com/puzzle/show/%(id)d'
'https://www.cindythink.com/puzzle/show/%(id)d\n#ウミガメのスープ'
4 changes: 4 additions & 0 deletions imaging/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

setup:
# Install dependencies (needs sudo priviledge)
apt install libfreetype6-dev libjpeg62-dev fonts-noto-cjk
1 change: 1 addition & 0 deletions imaging/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .puzzle_rendering import *
95 changes: 95 additions & 0 deletions imaging/puzzle_rendering.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import os
import re

import markdown
from bs4 import BeautifulSoup
from PIL import Image, ImageDraw, ImageFont

from .wcstring import wcstr

BASE_DIR = os.path.split(os.path.abspath(__file__))[0]
FONT_PATH = "NotoSansCJK-Bold.ttc"
CANVAS_WIDTH = 600
TITLE_FONTSIZE = 18
TITLE_SPLIT = 63
CONTENT_FONTSIZE = 16
CONTENT_SPLIT = 72
LINE_HEIGHT = 25
OUTPUT_IMAGE_NAME = '/tmp/puzzle_render_output.png'
APPENDS = 'ウミガメのスープ出題サイトCindy:www.cindythink.com'
APPENDS_INDENT = 200
APPENDS_FONTSIZE = 14


def _split_lines(text, stop):
output = []
text = re.split(r'[\r\n]+', text)
for part in text:
if not part:
continue
part = wcstr(part)
for i in range(0, len(part), stop):
output.append(part[i:i + stop])

return output


def render(title,
content,
appends=APPENDS,
canvas_width=CANVAS_WIDTH,
font_path=FONT_PATH,
title_fontsize=TITLE_FONTSIZE,
title_split=TITLE_SPLIT,
content_fontsize=CONTENT_FONTSIZE,
content_split=CONTENT_SPLIT,
appends_indent=APPENDS_INDENT,
appends_fontsize=APPENDS_FONTSIZE,
line_height=LINE_HEIGHT,
output_image_name=OUTPUT_IMAGE_NAME):
title = _split_lines(title, title_split)
content = _split_lines(content, content_split)
hTitle = len(title) * line_height + line_height // 2
hContent = len(content) * line_height + line_height // 2
hAppends = int(line_height * 1.2)
img = Image.new('RGB', (canvas_width, hTitle + hContent + hAppends), "#fcf4dc")
draw = ImageDraw.Draw(img)
title_font = ImageFont.truetype(font_path, title_fontsize)
content_font = ImageFont.truetype(font_path, content_fontsize)
appends_font = ImageFont.truetype(font_path, appends_fontsize)

# Drawing border
draw.rectangle((0, 0, 2, hTitle + hContent), fill="#c6aa4b")
draw.rectangle(
(canvas_width - 3, 0, canvas_width - 1, hTitle + hContent),
fill="#c6aa4b")
draw.rectangle((0, hTitle + hContent, canvas_width, hTitle + hContent + 2), fill="#c6aa4b")

# Drawing Title background
draw.rectangle((0, 0, canvas_width, hTitle), fill="#c6aa4b")

for i, txt in enumerate(title):
draw.text(
(5, i * line_height + 5), txt, font=title_font, fill="#fcf4dc")

for i, txt in enumerate(content):
draw.text(
(5, i * line_height + hTitle + 5),
txt,
font=content_font,
fill="#c6aa4b")

draw.text((appends_indent, hTitle + hContent + 5), appends, font=appends_font, fill="#888")

img.save(output_image_name)
return output_image_name


def textify(md):
html = markdown.markdown(md, [
'markdown.extensions.extra',
'markdown.extensions.nl2br',
'markdown.extensions.tables'
]) # yapf: disable
soup = BeautifulSoup(html, 'html.parser')
return soup.get_text()
2 changes: 2 additions & 0 deletions imaging/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Pillow>3.0
beautifulsoup4==4.5.3
114 changes: 114 additions & 0 deletions imaging/wcstring.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# -*- coding: utf-8 -*-


class wcstr(str):
def __new__(*args, **kwargs):
return str.__new__(*args, **kwargs)

def __init__(self, *args, **kwargs):
self._update()

def _update(self):
self.bitindex = []
for i, j in zip(str(self), range(len(str(self)))):
iwidth = 1 if len(i.encode('utf8')) <= 2 else 2
self.bitindex += [j] * iwidth

def __len__(self):
return len(self.bitindex)

def __getitem__(self, y):
if type(y) == int:
return wcstr(super(wcstr, self).__getitem__(self.bitindex[y]))
elif type(y) == slice:
start = self.bitindex[y.start] if y.start else None
if y.stop and y.stop < len(self.bitindex):
stop = self.bitindex[y.stop] if y.stop else None
else:
stop = None
step = y.step
return wcstr(
super(wcstr, self).__getitem__(slice(start, stop, step)))
else:
return

def dupstr(self):
# return a duplicated string with every element
# indicating one-width character
return ''.join([self[i] for i in range(len(self))])

# alias for other str methods

def __add__(self, *args, **kwargs):
return wcstr(super(wcstr, self).__add__(*args, **kwargs))

def __mul__(self, value):
return wcstr(super(wcstr, self).__mul__(value))

def __rmul__(self, *args, **kwargs):
return wcstr(super(wcstr, self).__rmul__(*args, **kwargs))

def __format__(self, *args, **kwargs):
return wcstr(super(wcstr, self).__format__(*args, **kwargs))

def center(self, width, fillchar=' '):
filllen = (width - len(self)) // 2
return wcstr(fillchar * filllen + self + fillchar *
(width - len(self) - filllen))

#return super(wcstr, self).center(width - len(self) +
# len(str(self)))

def casefold(self, *args, **kwargs):
return wcstr(super(wcstr, self).casefold(*args, **kwargs))

def capitalize(self, *args, **kwargs):
return wcstr(super(wcstr, self).capitalize(*args, **kwargs))

def expandtabs(self, *args, **kwargs):
return wcstr(super(wcstr, self).expandtabs(*args, **kwargs))

def format(self, *args, **kwargs):
return wcstr(super(wcstr, self).format(*args, **kwargs))

def format_map(self, *args, **kwargs):
return wcstr(super(wcstr, self).format_map(*args, **kwargs))

def join(self, *args, **kwargs):
return wcstr(super(wcstr, self).join(*args, **kwargs))

def ljust(self, *args, **kwargs):
return wcstr(super(wcstr, self).ljust(*args, **kwargs))

def lower(self, *args, **kwargs):
return wcstr(super(wcstr, self).lower(*args, **kwargs))

def lstrip(self, *args, **kwargs):
return wcstr(super(wcstr, self).lstrip(*args, **kwargs))

def replace(self, *args, **kwargs):
return wcstr(super(wcstr, self).replace(*args, **kwargs))

def rjust(self, *args, **kwargs):
return wcstr(super(wcstr, self).rjust(*args, **kwargs))

def rstrip(self, *args, **kwargs):
return wcstr(super(wcstr, self).rstrip(*args, **kwargs))

def strip(self, *args, **kwargs):
return wcstr(super(wcstr, self).strip(*args, **kwargs))

def swapcase(self, *args, **kwargs):
return wcstr(super(wcstr, self).swapcase(*args, **kwargs))

def title(self, *args, **kwargs):
return wcstr(super(wcstr, self).title(*args, **kwargs))

def translate(self, *args, **kwargs):
return wcstr(super(wcstr, self).translate(*args, **kwargs))

def upper(self, *args, **kwargs):
return wcstr(super(wcstr, self).upper(*args, **kwargs))

def zfill(self, *args, **kwargs):
return wcstr(super(wcstr, self).zfill(*args, **kwargs))
23 changes: 19 additions & 4 deletions sui_hei/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
CONSUMER_KEY = settings.CONSUMER_KEY
CONSUMER_SECRET = settings.CONSUMER_SECRET
TWEET_MESSAGE = settings.TWEET_MESSAGE
TWEET_WITH_PICTURE = settings.TWEET_WITH_PICTURE

logger = logging.Logger(__name__)

Expand All @@ -23,11 +24,25 @@ def add_twitter_on_puzzle_created(sender, instance, created, **kwargs):
try:
auth = OAuth(TOKEN, TOKEN_SECRET, CONSUMER_KEY, CONSUMER_SECRET)
t = Twitter(auth=auth)
t.statuses.update(
status=TWEET_MESSAGE % {

params = {
'status': TWEET_MESSAGE % {
'user_nickname': instance.user.nickname,
'title': instance.title,
'id': instance.id
})
except e:
},
}

if TWEET_WITH_PICTURE:
from imaging.puzzle_rendering import render, textify

imgpath = render(instance.title, textify(instance.content))
with open(imgpath, 'rb') as f:
imgdata = f.read()
params['media[]'] = imgdata
t.statuses.update_with_media(**params)
else:
t.statuses.update(**params)

except Exception as e:
logger.warning("Error update twitter status: %s" % e)

0 comments on commit f5595fa

Please sign in to comment.