├── .gitignore
├── Dockerfile
├── LICENSE.txt
├── MANIFEST.in
├── README.rst
├── docs
├── Makefile
├── conf.py
├── index.rst
└── make.bat
├── rainbowstream
├── __init__.py
├── c_image.py
├── colors.py
├── colorset
│ ├── base16.json
│ ├── config
│ ├── larapaste.json
│ ├── monokai.json
│ ├── solarized.json
│ └── tomorrow_night.json
├── config.py
├── draw.py
├── emoji.py
├── image.c
├── interactive.py
├── pure_image.py
├── py3patch.py
├── rainbow.py
└── util.py
├── release.sh
├── screenshot
├── RainbowStreamAll.png
├── rs.gif
└── themes
│ ├── Default.png
│ ├── Monokai.png
│ ├── Solarized.png
│ ├── TomorrowNight.png
│ └── larapaste.png
├── setup.CFG
├── setup.py
└── theme.md
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | .DS_Store
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Pyenv
10 | .python-version
11 |
12 | # Distribution / packaging
13 | .Python
14 | env/
15 | bin/
16 | build/
17 | _build/
18 | develop-eggs/
19 | dist/
20 | eggs/
21 | lib/
22 | lib64/
23 | parts/
24 | sdist/
25 | var/
26 | *.egg-info/
27 | .installed.cfg
28 | *.egg
29 |
30 | # Installer logs
31 | pip-log.txt
32 | pip-delete-this-directory.txt
33 |
34 | # Unit test / coverage reports
35 | htmlcov/
36 | .tox/
37 | .coverage
38 | .cache
39 | nosetests.xml
40 | coverage.xml
41 |
42 | # Translations
43 | *.mo
44 |
45 | # Mr Developer
46 | .mr.developer.cfg
47 | .project
48 | .pydevproject
49 |
50 | # Rope
51 | .ropeproject
52 |
53 | # Django stuff:
54 | *.log
55 | *.pot
56 |
57 | # Sphinx documentation
58 | docs/_build/
59 |
60 | # DB files
61 | *.db
62 |
63 | # Editor
64 | *sublime*
65 | .tag*
66 | *.swp*
67 | *.swo*
68 |
69 | # Virtualenv
70 | /venv*
71 |
72 | # History completer
73 | completer.hist
74 |
75 | # Consumer
76 | rainbowstream/consumer.py
77 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:2.7.9
2 |
3 | RUN pip install rainbowstream
4 | CMD rainbowstream
5 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 Vu Nhat Minh
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.rst LICENSE.txt
2 | include rainbowstream/image.c
3 | recursive-include rainbowstream/colorset *
4 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | Rainbow Stream
2 | --------------
3 |
4 | .. image:: http://img.shields.io/pypi/l/rainbowstream.svg?style=flat-square
5 | :target: https://github.com/DTVD/rainbowstream/blob/master/LICENSE.txt
6 |
7 | .. image:: http://img.shields.io/pypi/v/rainbowstream.svg?style=flat-square
8 | :target: https://pypi.python.org/pypi/rainbowstream
9 |
10 | Terminal-based full-fledged Twitter client, built upon `Python Twitter Tools`.
11 |
12 | Showcase
13 | --------
14 |
15 | .. figure:: https://raw.githubusercontent.com/DTVD/rainbowstream/master/screenshot/rs.gif
16 | :alt: gif
17 |
18 | Installation
19 | ------------
20 |
21 | Direct installation
22 | ^^^^^^^^^^^^^^^^^^^
23 |
24 | .. code:: bash
25 |
26 | sudo pip3 install rainbowstream
27 |
28 | Virtualenv (Recommended)
29 | ^^^^^^^^^^^^^^^^^^^^^^^^
30 |
31 | .. code:: bash
32 |
33 | virtualenv -p /usr/bin/python3 venv
34 | source venv/bin/activate
35 | pip install rainbowstream
36 |
37 | Installation Troubleshooting
38 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
39 | If you run into dependency issues, you may want to install additional libraries
40 |
41 | Debian-based distros:
42 |
43 | .. code:: bash
44 |
45 | sudo apt-get install python-dev libjpeg-dev libfreetype6 libfreetype6-dev zlib1g-dev
46 |
47 | CentOS:
48 |
49 | .. code:: bash
50 |
51 | sudo yum install python-devel libjpeg-devel
52 |
53 | Mac OSX
54 | Mac has a `clang unknown argument`_
55 | problem with the ``Pillow`` package—a dependency of this
56 | app. Please see the workaround in `Issue #10`_
57 |
58 | .. code:: bash
59 |
60 | export ARCHFLAGS=-Wno-error=unused-command-line-argument-hard-error-in-future
61 |
62 | If you still experience issues:
63 |
64 | - ``sudo pip uninstall rainbowstream``
65 | - Use the *virtualenv installation*
66 | - `Create an issue`_ and provide:
67 | - Your OS
68 | - Your Python version
69 |
70 | Usage
71 | -----
72 |
73 | The Stream
74 | ^^^^^^^^^^
75 |
76 | Simply run ``rainbowstream`` to start the application, or enjoy its ASCII images with ``rainbowstream -iot`` or set ``IMAGE_ON_TERM`` to ``True`` in your config.
77 |
78 | If your terminal supports 24-bit colors, run ``rainbowstream -p24`` instead to utilize 24 bit ASCII images.
79 |
80 | If your terminal supports sixel, ie. wezterm or MLTerm, change the ``IMAGE_ON_TERM`` config to ``sixel`` and enjoy high-quality images.
81 |
82 | You might want to change ``IMAGE_SHIFT`` to set the image's margin (relative to your terminal's
83 | width), and ``IMAGE_MAX_HEIGHT`` to control the max height of every image (see
84 | `Config Management`_).
85 |
86 | You will be asked for Twitter authorization the first time you run Rainbow
87 | Stream. Just click the "Authorize access" button, paste the PIN to the
88 | terminal, and the application will start.
89 |
90 | You might want to use Rainbow Stream with an **HTTP/SOCKS proxy**. Proxy
91 | settings are specified as follows:
92 |
93 | .. code:: bash
94 |
95 | rainbowstream --proxy-host localhost --proxy-port 1337 --proxy-type HTTP
96 | # or the short form:
97 | rainbowstream -ph localhost -pp 1337 -pt HTTP
98 |
99 | Both ``--proxy-port`` and ``--proxy-type`` are optional. The default proxy port
100 | is ``8080`` and the default proxy type is ``SOCKS5``.
101 |
102 | Interactive Mode
103 | ^^^^^^^^^^^^^^^^
104 |
105 | While your stream is continued, you are also ready to tweet, search,
106 | reply, retweet, etc. directly from your console. Simply type ``h`` and hit the
107 | Enter key to see the help.
108 |
109 | Input is in interactive mode. It means that you can use the arrow keys to move
110 | up and down through the history, tab-autocomplete, or double-tab to view
111 | available suggestions. Input history from the previous run is also available.
112 |
113 | `Read the docs`_ for available commands.
114 |
115 | Theme Customization
116 | ^^^^^^^^^^^^^^^^^^^
117 |
118 | Rainbow Stream is shipped with some default themes. You can switch themes with
119 | the ``theme`` command. You can also customize themes as you please.
120 |
121 | Theme screenshots:
122 |
123 | - Monokai
124 |
125 | .. figure:: https://raw.githubusercontent.com/DTVD/rainbowstream/master/screenshot/themes/Monokai.png
126 | :alt: monokai
127 |
128 | - Solarized
129 |
130 | .. figure:: https://raw.githubusercontent.com/DTVD/rainbowstream/master/screenshot/themes/Solarized.png
131 | :alt: solarized
132 |
133 | - Tomorrow Night
134 |
135 | .. figure:: https://raw.githubusercontent.com/DTVD/rainbowstream/master/screenshot/themes/TomorrowNight.png
136 | :alt: tomorrownight
137 |
138 | - Larapaste
139 |
140 | .. figure:: https://raw.githubusercontent.com/DTVD/rainbowstream/master/screenshot/themes/larapaste.png
141 | :alt: larapaste
142 |
143 | See `Theme Usage and Customization`_ for detailed information.
144 |
145 | A Note about Twitter API Change
146 | -------------------------------
147 |
148 | Since Twitter discontinued supporting Stream API, RainbowStream is now using a [Polling Strategy](https://github.com/orakaro/rainbowstream/issues/271) that utilizes the `home` command to poll for your tweets every 90 seconds. This `home` command is rate limited by 15 times per 15 minutes, so don't run it too frequently to leave space for the polling stream.
149 |
150 | Bug and Feature Requests
151 | ------------------------
152 |
153 | Found a bug or a feature request? Please `create an issue`_ or contact me at
154 | `@orakaro`_.
155 |
156 | Development
157 | -----------
158 |
159 | If you want to build a runnable version yourself, follow these simple steps:
160 |
161 | - `Create your Twitter Application`_
162 | - Get your Twitter application’s API key and secret
163 | - `Create your own Pocket Application`_ (platform: Web)
164 | - Get your Pocket application’s key
165 | - Fork this repo and ``git clone`` it
166 | - Create a ``consumer.py`` file in the `rainbowstream` directory containing:
167 |
168 | .. code:: python
169 |
170 | # Consumer information
171 | CONSUMER_KEY = 'APIKey' # Your Twitter application's API key
172 | CONSUMER_SECRET = 'APISecret' # Your Twitter application's API secret
173 | PCKT_CONSUMER_KEY = 'PocketAPIKey' # Your Pocket application's API key
174 |
175 | - Use pip to install it locally
176 |
177 | .. code:: bash
178 |
179 | # cd to directory which contains setup.py (cloned directory)
180 | virtualenv venv # Python3 users: use -p to specify python3
181 | source venv/bin/activate
182 | pip install -e .
183 | which rainbowstream # /this-directory/venv/bin/rainbowstream
184 | # Remove ~/.rainbow_oauth if it exists
185 | rainbowstream # local version of rainbowstream
186 |
187 |
188 | Contributing
189 | ------------
190 |
191 | I appreciate any help and support. Feel free to `fork`_ and `create a pull
192 | request`_.
193 |
194 | License
195 | -------
196 |
197 | Rainbow Stream is released under an MIT License. See LICENSE.txt for details.
198 |
199 |
200 | .. _Python Twitter Tools: http://mike.verdone.ca/twitter/
201 | .. _Twitter API: https://dev.twitter.com/docs/api/1.1
202 | .. _Create an issue: https://github.com/DTVD/rainbowstream/issues/new
203 | .. _@orakaro: https://twitter.com/dtvd88
204 | .. _fork: https://github.com/DTVD/rainbowstream/fork
205 | .. _create a pull request: https://github.com/DTVD/rainbowstream/compare/
206 | .. _Read the docs: http://rainbowstream.readthedocs.org/en/latest/
207 | .. _config guide: https://github.com/DTVD/rainbowstream/blob/master/theme.md
208 | .. _Theme Usage and Customization: https://github.com/DTVD/rainbowstream/blob/master/theme.md
209 | .. _Create your Twitter Application: https://apps.twitter.com/app/new
210 | .. _Create your own Pocket Application: https://getpocket.com/developer/apps/new
211 | .. _Config Management: http://rainbowstream.readthedocs.org/en/latest/#config-explanation
212 | .. _clang unknown argument: http://kaspermunck.github.io/2014/03/fixing-clang-error/
213 | .. _Issue #10: https://github.com/DTVD/rainbowstream/issues/10
214 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | PAPER =
8 | BUILDDIR = _build
9 |
10 | # User-friendly check for sphinx-build
11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $?), 1)
12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
13 | endif
14 |
15 | # Internal variables.
16 | PAPEROPT_a4 = -D latex_paper_size=a4
17 | PAPEROPT_letter = -D latex_paper_size=letter
18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
19 | # the i18n builder cannot share the environment and doctrees with the others
20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
21 |
22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
23 |
24 | help:
25 | @echo "Please use \`make <target>' where <target> is one of"
26 | @echo " html to make standalone HTML files"
27 | @echo " dirhtml to make HTML files named index.html in directories"
28 | @echo " singlehtml to make a single large HTML file"
29 | @echo " pickle to make pickle files"
30 | @echo " json to make JSON files"
31 | @echo " htmlhelp to make HTML files and a HTML help project"
32 | @echo " qthelp to make HTML files and a qthelp project"
33 | @echo " devhelp to make HTML files and a Devhelp project"
34 | @echo " epub to make an epub"
35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
36 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
38 | @echo " text to make text files"
39 | @echo " man to make manual pages"
40 | @echo " texinfo to make Texinfo files"
41 | @echo " info to make Texinfo files and run them through makeinfo"
42 | @echo " gettext to make PO message catalogs"
43 | @echo " changes to make an overview of all changed/added/deprecated items"
44 | @echo " xml to make Docutils-native XML files"
45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes"
46 | @echo " linkcheck to check all external links for integrity"
47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
48 |
49 | clean:
50 | rm -rf $(BUILDDIR)/*
51 |
52 | html:
53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
54 | @echo
55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
56 |
57 | dirhtml:
58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
59 | @echo
60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
61 |
62 | singlehtml:
63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
64 | @echo
65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
66 |
67 | pickle:
68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
69 | @echo
70 | @echo "Build finished; now you can process the pickle files."
71 |
72 | json:
73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
74 | @echo
75 | @echo "Build finished; now you can process the JSON files."
76 |
77 | htmlhelp:
78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
79 | @echo
80 | @echo "Build finished; now you can run HTML Help Workshop with the" \
81 | ".hhp project file in $(BUILDDIR)/htmlhelp."
82 |
83 | qthelp:
84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
85 | @echo
86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/RainbowStream.qhcp"
89 | @echo "To view the help file:"
90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/RainbowStream.qhc"
91 |
92 | devhelp:
93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
94 | @echo
95 | @echo "Build finished."
96 | @echo "To view the help file:"
97 | @echo "# mkdir -p $HOME/.local/share/devhelp/RainbowStream"
98 | @echo "# ln -s $(BUILDDIR)/devhelp $HOME/.local/share/devhelp/RainbowStream"
99 | @echo "# devhelp"
100 |
101 | epub:
102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
103 | @echo
104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
105 |
106 | latex:
107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
108 | @echo
109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
111 | "(use \`make latexpdf' here to do that automatically)."
112 |
113 | latexpdf:
114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
115 | @echo "Running LaTeX files through pdflatex..."
116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf
117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
118 |
119 | latexpdfja:
120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
121 | @echo "Running LaTeX files through platex and dvipdfmx..."
122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
124 |
125 | text:
126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
127 | @echo
128 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
129 |
130 | man:
131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
132 | @echo
133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
134 |
135 | texinfo:
136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
137 | @echo
138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
139 | @echo "Run \`make' in that directory to run these through makeinfo" \
140 | "(use \`make info' here to do that automatically)."
141 |
142 | info:
143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
144 | @echo "Running Texinfo files through makeinfo..."
145 | make -C $(BUILDDIR)/texinfo info
146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
147 |
148 | gettext:
149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
150 | @echo
151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
152 |
153 | changes:
154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
155 | @echo
156 | @echo "The overview file is in $(BUILDDIR)/changes."
157 |
158 | linkcheck:
159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
160 | @echo
161 | @echo "Link check complete; look for any errors in the above output " \
162 | "or in $(BUILDDIR)/linkcheck/output.txt."
163 |
164 | doctest:
165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
166 | @echo "Testing of doctests in the sources finished, look at the " \
167 | "results in $(BUILDDIR)/doctest/output.txt."
168 |
169 | xml:
170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
171 | @echo
172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml."
173 |
174 | pseudoxml:
175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
176 | @echo
177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
178 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # RainbowStream documentation build configuration file, created by
4 | # sphinx-quickstart on Wed Jul 23 12:26:18 2014.
5 | #
6 | # This file is execfile()d with the current directory set to its
7 | # containing dir.
8 | #
9 | # Note that not all possible configuration values are present in this
10 | # autogenerated file.
11 | #
12 | # All configuration values have a default; values that are commented out
13 | # serve to show the default.
14 |
15 | # If extensions (or modules to document with autodoc) are in another directory,
16 | # add these directories to sys.path here. If the directory is relative to the
17 | # documentation root, use os.path.abspath to make it absolute, like shown here.
18 | #sys.path.insert(0, os.path.abspath('.'))
19 |
20 | # -- General configuration ------------------------------------------------
21 |
22 | # If your documentation needs a minimal Sphinx version, state it here.
23 | #needs_sphinx = '1.0'
24 |
25 | # Add any Sphinx extension module names here, as strings. They can be
26 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
27 | # ones.
28 | extensions = []
29 |
30 | # Add any paths that contain templates here, relative to this directory.
31 | templates_path = ['_templates']
32 |
33 | # The suffix of source filenames.
34 | source_suffix = '.rst'
35 |
36 | # The encoding of source files.
37 | #source_encoding = 'utf-8-sig'
38 |
39 | # The master toctree document.
40 | master_doc = 'index'
41 |
42 | # General information about the project.
43 | project = u'RainbowStream'
44 | copyright = u'2014, Vu Nhat Minh'
45 |
46 | # The version info for the project you're documenting, acts as replacement for
47 | # |version| and |release|, also used in various other places throughout the
48 | # built documents.
49 | #
50 | # The short X.Y version.
51 | version = '1.6.0'
52 | # The full version, including alpha/beta/rc tags.
53 | release = '1.6.0'
54 |
55 | # The language for content autogenerated by Sphinx. Refer to documentation
56 | # for a list of supported languages.
57 | #language = None
58 |
59 | # There are two options for replacing |today|: either, you set today to some
60 | # non-false value, then it is used:
61 | #today = ''
62 | # Else, today_fmt is used as the format for a strftime call.
63 | #today_fmt = '%B %d, %Y'
64 |
65 | # List of patterns, relative to source directory, that match files and
66 | # directories to ignore when looking for source files.
67 | exclude_patterns = ['_build']
68 |
69 | # The reST default role (used for this markup: `text`) to use for all
70 | # documents.
71 | #default_role = None
72 |
73 | # If true, '()' will be appended to :func: etc. cross-reference text.
74 | #add_function_parentheses = True
75 |
76 | # If true, the current module name will be prepended to all description
77 | # unit titles (such as .. function::).
78 | #add_module_names = True
79 |
80 | # If true, sectionauthor and moduleauthor directives will be shown in the
81 | # output. They are ignored by default.
82 | #show_authors = False
83 |
84 | # The name of the Pygments (syntax highlighting) style to use.
85 | pygments_style = 'sphinx'
86 |
87 | # A list of ignored prefixes for module index sorting.
88 | #modindex_common_prefix = []
89 |
90 | # If true, keep warnings as "system message" paragraphs in the built documents.
91 | #keep_warnings = False
92 |
93 |
94 | # -- Options for HTML output ----------------------------------------------
95 |
96 | # The theme to use for HTML and HTML Help pages. See the documentation for
97 | # a list of builtin themes.
98 | html_theme = 'nature'
99 |
100 | # Theme options are theme-specific and customize the look and feel of a theme
101 | # further. For a list of options available for each theme, see the
102 | # documentation.
103 | #html_theme_options = {}
104 |
105 | # Add any paths that contain custom themes here, relative to this directory.
106 | #html_theme_path = []
107 |
108 | # The name for this set of Sphinx documents. If None, it defaults to
109 | # "<project> v<release> documentation".
110 | #html_title = None
111 |
112 | # A shorter title for the navigation bar. Default is the same as html_title.
113 | #html_short_title = None
114 |
115 | # The name of an image file (relative to this directory) to place at the top
116 | # of the sidebar.
117 | #html_logo = None
118 |
119 | # The name of an image file (within the static path) to use as favicon of the
120 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
121 | # pixels large.
122 | #html_favicon = None
123 |
124 | # Add any paths that contain custom static files (such as style sheets) here,
125 | # relative to this directory. They are copied after the builtin static files,
126 | # so a file named "default.css" will overwrite the builtin "default.css".
127 | html_static_path = ['_static']
128 |
129 | # Add any extra paths that contain custom files (such as robots.txt or
130 | # .htaccess) here, relative to this directory. These files are copied
131 | # directly to the root of the documentation.
132 | #html_extra_path = []
133 |
134 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
135 | # using the given strftime format.
136 | #html_last_updated_fmt = '%b %d, %Y'
137 |
138 | # If true, SmartyPants will be used to convert quotes and dashes to
139 | # typographically correct entities.
140 | #html_use_smartypants = True
141 |
142 | # Custom sidebar templates, maps document names to template names.
143 | #html_sidebars = {}
144 |
145 | # Additional templates that should be rendered to pages, maps page names to
146 | # template names.
147 | #html_additional_pages = {}
148 |
149 | # If false, no module index is generated.
150 | #html_domain_indices = True
151 |
152 | # If false, no index is generated.
153 | #html_use_index = True
154 |
155 | # If true, the index is split into individual pages for each letter.
156 | #html_split_index = False
157 |
158 | # If true, links to the reST sources are added to the pages.
159 | #html_show_sourcelink = True
160 |
161 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
162 | #html_show_sphinx = True
163 |
164 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
165 | #html_show_copyright = True
166 |
167 | # If true, an OpenSearch description file will be output, and all pages will
168 | # contain a <link> tag referring to it. The value of this option must be the
169 | # base URL from which the finished HTML is served.
170 | #html_use_opensearch = ''
171 |
172 | # This is the file name suffix for HTML files (e.g. ".xhtml").
173 | #html_file_suffix = None
174 |
175 | # Output file base name for HTML help builder.
176 | htmlhelp_basename = 'RainbowStreamdoc'
177 |
178 |
179 | # -- Options for LaTeX output ---------------------------------------------
180 |
181 | latex_elements = {
182 | # The paper size ('letterpaper' or 'a4paper').
183 | #'papersize': 'letterpaper',
184 |
185 | # The font size ('10pt', '11pt' or '12pt').
186 | #'pointsize': '10pt',
187 |
188 | # Additional stuff for the LaTeX preamble.
189 | #'preamble': '',
190 | }
191 |
192 | # Grouping the document tree into LaTeX files. List of tuples
193 | # (source start file, target name, title,
194 | # author, documentclass [howto, manual, or own class]).
195 | latex_documents = [
196 | ('index', 'RainbowStream.tex', u'RainbowStream Documentation',
197 | u'Vu Nhat Minh', 'manual'),
198 | ]
199 |
200 | # The name of an image file (relative to this directory) to place at the top of
201 | # the title page.
202 | #latex_logo = None
203 |
204 | # For "manual" documents, if this is true, then toplevel headings are parts,
205 | # not chapters.
206 | #latex_use_parts = False
207 |
208 | # If true, show page references after internal links.
209 | #latex_show_pagerefs = False
210 |
211 | # If true, show URL addresses after external links.
212 | #latex_show_urls = False
213 |
214 | # Documents to append as an appendix to all manuals.
215 | #latex_appendices = []
216 |
217 | # If false, no module index is generated.
218 | #latex_domain_indices = True
219 |
220 |
221 | # -- Options for manual page output ---------------------------------------
222 |
223 | # One entry per manual page. List of tuples
224 | # (source start file, name, description, authors, manual section).
225 | man_pages = [
226 | ('index', 'rainbowstream', u'RainbowStream Documentation',
227 | [u'Vu Nhat Minh'], 1)
228 | ]
229 |
230 | # If true, show URL addresses after external links.
231 | #man_show_urls = False
232 |
233 |
234 | # -- Options for Texinfo output -------------------------------------------
235 |
236 | # Grouping the document tree into Texinfo files. List of tuples
237 | # (source start file, target name, title, author,
238 | # dir menu entry, description, category)
239 | texinfo_documents = [
240 | ('index', 'RainbowStream', u'RainbowStream Documentation',
241 | u'Vu Nhat Minh', 'RainbowStream', 'One line description of project.',
242 | 'Miscellaneous'),
243 | ]
244 |
245 | # Documents to append as an appendix to all manuals.
246 | #texinfo_appendices = []
247 |
248 | # If false, no module index is generated.
249 | #texinfo_domain_indices = True
250 |
251 | # How to display URL addresses: 'footnote', 'no', or 'inline'.
252 | #texinfo_show_urls = 'footnote'
253 |
254 | # If true, do not generate a @detailmenu in the "Top" node's menu.
255 | #texinfo_no_detailmenu = False
256 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | Rainbow Stream
2 | --------------
3 |
4 | .. image:: http://img.shields.io/pypi/l/rainbowstream.svg?style=flat-square
5 | :target: https://github.com/DTVD/rainbowstream/blob/master/LICENSE.txt
6 |
7 | .. image:: http://img.shields.io/pypi/v/rainbowstream.svg?style=flat-square
8 | :target: https://pypi.python.org/pypi/rainbowstream
9 |
10 | Terminal-based Twitter Client. Realtime tweetstream, compose, search ,
11 | favorite … and much more fun directly from terminal.
12 |
13 | This package is built on the top of `Python Twitter Tool`_ and `Twitter API`_,
14 | can run on Python 2.7.x and 3.x .
15 |
16 | Home page : http://www.rainbowstream.org/
17 |
18 | Source code : https://github.com/DTVD/rainbowstream
19 |
20 | Install
21 | -------
22 |
23 | The quick way
24 | ^^^^^^^^^^^^^
25 |
26 | You will need Python and pip (2.7.x or 3.x).
27 |
28 | .. code:: bash
29 |
30 | sudo pip install rainbowstream
31 | # Python 3 users: sudo pip3 install rainbowstream
32 |
33 | The recommended way
34 | ^^^^^^^^^^^^^^^^^^^
35 |
36 | Use `virtualenv`_
37 |
38 | .. code:: bash
39 |
40 | virtualenv venv
41 | # Python 3 users : use -p to specify your Python 3 localtion as below
42 | # virtualenv -p /usr/bin/python3 venv
43 | source venv/bin/activate
44 | pip install rainbowstream
45 |
46 | Troubleshooting
47 | ^^^^^^^^^^^^^^^
48 |
49 | If you use Linux, you might need to install some packages if you haven't already.
50 | For debian-based distros, these can be installed with
51 |
52 | .. code:: bash
53 |
54 | sudo apt-get install python-dev libjpeg libjpeg-dev libfreetype6 libfreetype6-dev zlib1g-dev
55 |
56 | Besides, Mac OSX Maverick with Xcode 5.1 has a well-known `clang unknown argument`_ problem with
57 | the ``Pillow`` package installation - a dependency of this app.
58 | If you are in this case, I recommend taking a look at `Issue #10`_ and let me know if this workaround doesn't work for you.
59 |
60 | .. code:: bash
61 |
62 | export ARCHFLAGS=-Wno-error=unused-command-line-argument-hard-error-in-future
63 |
64 | If installation in *the quick way* doesn't work:
65 |
66 | - ``sudo pip uninstall rainbowstream``
67 | - use the *virtualenv way* above
68 | - `create an issue`_ and provide:
69 |
70 | + Your OS
71 | + Your Python version
72 |
73 | Usage
74 | -----
75 |
76 | The stream
77 | ^^^^^^^^^^
78 |
79 | Just type
80 |
81 | .. code:: bash
82 |
83 | rainbowstream
84 |
85 | and see your stream.
86 |
87 | I shipped a feature which can display **tweet's images directly on terminal**.
88 | You can try it with:
89 |
90 | .. code:: bash
91 |
92 | rainbowstream -iot # Or rainbowstream --image-on-term
93 |
94 | You also can change the config key ``IMAGE_ON_TERM`` to ``True`` inside the app
95 | to enable above feature,
96 | change ``IMAGE_SHIFT`` to set image's margin (relative to your terminal's width)
97 | or ``IMAGE_MAX_HEIGHT`` to control max height of every image.
98 | (see `config management`_ section).
99 |
100 | In the first time you will be asked for authorization of Rainbow Stream
101 | app at Twitter. Just click the “Authorize access” button and paste PIN
102 | number to the terminal, the rainbow will start.
103 |
104 | You might want to use rainbowstream via an **HTTP/SOCKS proxy**. Proxy settings are
105 | provided as follows:
106 |
107 | .. code:: bash
108 |
109 | rainbowstream --proxy-host localhost --proxy-port 1337 --proxy-type HTTP
110 | # or using the short form:
111 | rainbowstream -ph localhost -pp 1337 -pt HTTP
112 |
113 | Both ``--proxy-port`` and ``--proxy-type`` can be omitted. In this case default
114 | proxy port ``8080`` and default proxy type ``SOCKS5`` are used.
115 |
116 | If you would like to specify an alternate location for storing the OAuth credential files for both twitter and pocket you can do so with the ``--twitter-auth`` and ``--pocket-auth`` settings. This is also useful if you wish to have multiple accounts for rainbowstream. Specify a location as follows:
117 |
118 | .. code:: bash
119 |
120 | rainbowstream --twitter-auth /path/to/twitter_oauth
121 | # or using the short form:
122 | rainbowstream -ta /path/to/twitter_oauth
123 |
124 | If the oauth file doesn't exist at the location you specified then a new one will be created and you'll be required to autenticate with Twitter again.
125 |
126 | The interactive mode
127 | ^^^^^^^^^^^^^^^^^^^^
128 |
129 | While your personal stream is continued, you are also ready to tweet,
130 | search, reply, retweet… directly from console. Simply type “h” and hit
131 | the Enter key to see the help.
132 |
133 | Input is in interactive mode. It means that you can use arrow key to
134 | move up and down history, tab-autocomplete or 2 tab to view available
135 | suggestion. Input history from previous run is available as well.
136 |
137 | Here is full list of supported command:
138 |
139 | **Explore Commands**
140 |
141 | - ``trend`` will show global trending topics. ``trend US`` will show trends in United States while ``trend JP Tokyo`` will show trends in Tokyo/Japan.
142 |
143 | - ``home`` will show your timeline. ``home 10`` will print exactly 10 tweets.
144 |
145 | - ``me`` will show your latest tweets. ``me 2`` will show your last 2 tweets.
146 |
147 | - ``notification`` will show your notification from the time you started RainbowStream.
148 |
149 | - ``mentions`` will show mentions timeline. ``mentions 7`` will show 7 mention tweets.
150 |
151 | - ``whois @dtvd88`` will show profile of @dtvd88.
152 |
153 | - ``view @mdo`` will show @mdo ’s timeline. ``view @dmo 9`` will print exactly 9 tweets.
154 |
155 | - ``s noah`` will search the word *‘noah’*. Result will come back with highlight. Search can be performed with or without hashtag.
156 |
157 | **Tweet Commands**
158 |
159 | - ``t the rainbow is god's promise to noah`` will tweet exactly *‘the rainbow is god’s promise to noah’*.
160 |
161 | - ``rt 12`` will retweet the tweet with *[id=12]*. You can see id of each tweet beside the time.
162 |
163 | - ``quote 12`` will quote the tweet with *[id=12]*. If no extra text is added, the quote will be cancelled.
164 |
165 | - ``allrt 12 20`` will list 20 newest retweets of the tweet with *[id=12]*. If the number of retweets is not specified, 5 newest retweets will be listed instead.
166 |
167 | - ``conversation 12`` will show the chain of replies prior to the tweet with *[id=12]*.
168 |
169 | - ``rep 12 Really`` will reply *‘Really’* to the owner of the tweet with *[id=12]*.
170 |
171 | - ``repall 12 Really`` will reply *‘Really’* to all people in the tweet with *[id=12]*.
172 |
173 | - ``fav 12`` will favorite the tweet with *[id=12]*.
174 |
175 | - ``ufav 12`` will unfavorite tweet with *[id=12]*.
176 |
177 | - ``share 12`` will copy link to tweet with *[id=12]* to your clipboard if you are on a Mac, or display it directly if you are on Linux.
178 |
179 | - ``del 12`` will delete tweet with *[id=12]*.
180 |
181 | - ``show image 12`` will show the image in tweet with *[id=12]* in your OS’s image viewer.
182 |
183 | - ``open 12`` will open url in tweet with *[id=12]* in your OS’s default browser.
184 |
185 | - ``pt 12`` will add tweet with *[id=12]* in your Pocket list.
186 |
187 | **Direct Messages Commands**
188 |
189 | - ``inbox`` will show inbox messages. ``inbox 7`` will show newest 7 messages.
190 |
191 | - ``thread 2`` will show full thread with [id=2].
192 |
193 | - ``mes @dtvd88 hi`` will send a ``hi`` message to @dtvd88.
194 |
195 | - ``trash 5`` will remove message with *[message\_id=5]*
196 |
197 | **Friends and followers Commands**
198 |
199 | - ``ls fl`` will list all your followers (people who are following you).
200 |
201 | - ``ls fr`` will list all your friends (people who you are following).
202 |
203 | - ``fl @dtvd88`` will follow @dtvd88.
204 |
205 | - ``ufl @dtvd88`` will unfollow @dtvd88.
206 |
207 | - ``mute @dtvd88`` will mute @dtvd88.
208 |
209 | - ``unmute @dtvd88`` will unmute @dtvd88.
210 |
211 | - ``muting`` will list muting users.
212 |
213 | - ``block @dtvd88`` will block @dtvd88.
214 |
215 | - ``unblock @dtvd88`` will unblock @dtvd88.
216 |
217 | - ``report @dtvd88`` will report @dtvd88 as a spam account.
218 |
219 | **Twitter list**
220 |
221 | - ``list`` will show all lists you are belong to.
222 |
223 | - ``list home`` will show timeline of list. You will be asked for list's name.
224 |
225 | - ``list all_mem`` will show list's all members.
226 |
227 | - ``list all_sub`` will show list's all subscribers.
228 |
229 | - ``list add`` will add specific person to a list owned by you.
230 |
231 | - ``list rm`` will remove specific person from a list owned by you.
232 |
233 | - ``list sub`` will subscribe you to a specific list.
234 |
235 | - ``list unsub`` will unsubscribe you from a specific list.
236 |
237 | - ``list own`` will show all list owned by you.
238 |
239 | - ``list new`` will create a new list.
240 |
241 | - ``list update`` will update a list owned by you.
242 |
243 | - ``list del`` will delete a list owned by you.
244 |
245 | **Switching Stream Commands**
246 |
247 | - ``switch public #AKB48`` will switch current stream to public stream and track keyword ``AKB48``
248 |
249 | - ``switch public #AKB48 -f`` will do exactly as above but will ask you to provide 2 list:
250 |
251 | ``Only nicks`` decide what nicks will be include only.
252 |
253 | ``Ignore nicks``\ decide what nicks will be exclude.
254 |
255 | - ``switch mine`` will switch current stream to personal stream. ``-f`` will work as well.
256 |
257 | - ``switch list`` will switch to a Twitter list's stream. You will be asked for list name.
258 |
259 | **Smart shell**
260 |
261 | - Put anything to terminal, the app will try to eval and display result as a python interactive shell.
262 |
263 | + ``142857*2`` or ``101**3`` like a calculator.
264 | + Even ``cal`` will show the calendar for current month.
265 | + Put ``order_rainbow('anything')`` or ``random_rainbow('wahahaha')`` will make more fun :)
266 |
267 | **Config Management**
268 |
269 | - ``theme`` will list available themes.
270 |
271 | + ``theme monokai`` will apply *monokai* theme immediately.
272 | + Changed theme will be remember as the next time's default theme.
273 |
274 | - ``config`` will list all config key.
275 |
276 | + ``config ASCII_ART`` will output current value of *ASCII_ART* config key.
277 | + ``config TREND_MAX default`` will output default value of *TREND_MAX* config key.
278 | + ``config CUSTOM_CONFIG drop`` will drop *CUSTOM_CONFIG* config key.
279 | + ``config IMAGE_ON_TERM = true`` will set value of *IMAGE_ON_TERM* config key to *True*.
280 |
281 | **Screening Commands**
282 |
283 | - ``h`` will show the help.
284 |
285 | - ``p`` will pause the stream.
286 |
287 | - ``r`` will unpause the stream.
288 |
289 | - ``c`` will clear the screen.
290 |
291 | - ``v`` will show version info.
292 |
293 | - ``q`` will quit.
294 |
295 | Theme customization
296 | ^^^^^^^^^^^^^^^^^^^
297 |
298 | Rainbow Stream is shipped with some default themes.
299 | You can either change theme by ``theme`` command or create your favorite one.
300 |
301 | Theme’s screenshot:
302 |
303 | - Monokai
304 |
305 | .. figure:: https://raw.githubusercontent.com/DTVD/rainbowstream/master/screenshot/themes/Monokai.png
306 | :alt: monokai
307 |
308 | - Solarized
309 |
310 | .. figure:: https://raw.githubusercontent.com/DTVD/rainbowstream/master/screenshot/themes/Solarized.png
311 | :alt: solarized
312 |
313 | - Tomorrow Night
314 |
315 | .. figure:: https://raw.githubusercontent.com/DTVD/rainbowstream/master/screenshot/themes/TomorrowNight.png
316 | :alt: tomorrownight
317 |
318 | - Larapaste
319 |
320 | .. figure:: https://raw.githubusercontent.com/DTVD/rainbowstream/master/screenshot/themes/larapaste.png
321 | :alt: larapaste
322 |
323 | For detaile information, see `theme usage and customization`_.
324 |
325 | Config explanation
326 | ^^^^^^^^^^^^^^^^^^
327 |
328 | Rainbow Stream has a custom config file located at ``~/.rainbow_config.json`` which will be loaded **after** its `default config`_. You are free to change anything on your custom config. If you mess up the JSON format the app will still work but you'll need to overwrite your custom config with the `default config`_ to solve format problems.
329 |
330 | If you would like to specify a different location for your custom config you can set the ``RAINBOW_CONFIG`` environment variable to the location of your custom config.
331 |
332 | You also can view or set a new value of every config key with the ``config`` command (See **Interactive mode** section above).
333 |
334 | - ``POLLING_TIME``: Time in seconds between each automatic poll. Most Twitter accounts have a limit of 15 requests each 15 minutes. If in doubt, set it to 90 or more.
335 |
336 | - ``HEARTBEAT_TIMEOUT``: after this timeout (count by minutes), the stream will automatically hangup.
337 |
338 | - ``IMAGE_ON_TERM``: display tweet's image directly on terminal.
339 |
340 | - ``IMAGE_RESIZE_TO_FIT``: display tweet's image fit inside terminal view (width and height).
341 |
342 | - ``THEME``: current theme.
343 |
344 | - ``ASCII_ART``: display your twitter name by ascii art at stream begin or not.
345 |
346 | - ``HIDE_PROMPT``: hide prompt after receiving a tweet or not.
347 |
348 | - ``PREFIX``: display formated string of prompt.
349 |
350 | + ``#me``: Your username with '@', only available in personal stream.
351 | + ``#place``: List name, only available in list stream.
352 | + ``#owner``: Owner of list name, only available in list stream.
353 | + ``#keyword``: Keyword, only available on public stream.
354 |
355 | - ``SEARCH_TYPE``: search type in 'search' command ('mixed','recent','popular').
356 |
357 | - ``SEARCH_MAX_RECORD``: max tweets can display on 'search' command.
358 |
359 | - ``HOME_TWEET_NUM``: default tweets to display on 'home' command.
360 |
361 | - ``RETWEETS_SHOW_NUM``: default tweets to display on 'allrt' command.
362 |
363 | - ``CONVERSATION_MAX``: max tweet in a 'conversation' thread.
364 |
365 | - ``QUOTE_FORMAT``: format when quote a tweet
366 |
367 | + ``#comment``: Your own comment about the tweet
368 | + ``#owner``: owner's username *without* '@'
369 | + ``#tweet``: original tweet
370 | + ``#tid``: the tweet id on Twitter
371 |
372 | - ``THREAD_META_LEFT``: format for meta information of messages from partner which is display in the left of screen.
373 |
374 | - ``THREAD_META_RIGHT``: format for meta information of messages from you which is display in the right of screen.
375 |
376 | - ``THREAD_MIN_WIDTH``: minimum width of a message frame.
377 |
378 | - ``NOTIFY_FORMAT``: format of a notification.
379 |
380 | - ``MESSAGES_DISPLAY``: default messages to display on 'inbox' or 'sent' command.
381 |
382 | - ``TREND_MAX``: default trends to display on 'trend' command.
383 |
384 | - ``LIST_MAX``: default tweets to display on 'list home' command.
385 |
386 | - ``ONLY_LIST``: filter list on 'switch' command. Eg: ["@fat","mdo"]
387 |
388 | - ``IGNORE_LIST``: ignore list on 'switch' command. Eg: ["@fat"]
389 |
390 | - ``HISTORY_FILENAME``: name of file which stores input history.
391 |
392 | - ``IMAGE_SHIFT``: left and right margin of image in '-iot'/'--image-on-term' mode.
393 |
394 | - ``IMAGE_MAX_HEIGHT``: max height of image in '-iot'/'--image-on-term' mode.
395 |
396 | - ``STREAM_DELAY``: seconds to wait before displaying another tweet, will drop all tweets while waiting. This value can be used to slow down the stream.
397 |
398 | - ``USER_DOMAIN``: user URL of Twitter Streaming API.
399 |
400 | - ``PUBLIC_DOMAIN``: public URL of Twitter Streaming API.
401 |
402 | - ``SITE_DOMAIN``: site URL of Twitter Streaming API.
403 |
404 | - ``FORMAT``: display format for tweet and message.
405 |
406 | + ``CLOCK_FORMAT``: time format, see `Python's strftime format`_.
407 | + ``DISPLAY``: decide how tweet will be printed.
408 |
409 | + ``#name``: Twitter's name
410 | + ``#nick``: Twitter's screen name
411 | + ``#clock``: Datetime
412 | + ``#rt_count``: retweets count
413 | + ``#fa_count``: favorites count
414 | + ``#id``: ID
415 | + ``#fav``: favorited symbol
416 | + ``#fav``: favorited symbol
417 | + ``#tweet``: Tweet's content
418 | + ``#sender_name``: Message's sender name
419 | + ``#sender_nick``: Message's sender screen name
420 | + ``#to``: '>>>' symbol
421 | + ``#recipient_name``: Message's recipient name
422 | + ``#recipient_nick``: Message's recipient screen name
423 |
424 | - ``POCKET_SUPPORT`` : enable Pocket support.
425 |
426 | In every format, you can use unicode characters like ``\u2665``.
427 | Mac users also can use emoji characters as well (Ex: ``::zap::``).
428 | See `Emoji cheatsheet`_ for details.
429 |
430 | Development
431 | -----------
432 |
433 | If you want to build a runnable version yourself, follow these simple
434 | steps
435 |
436 | - `Create your own Twitter Application`_
437 | - Get your Twitter application’s API key and secret
438 | - Fork github's repo and clone in your system.
439 | - Create a file ``consumer.py`` in `rainbowstream`_ folder with
440 | following content
441 |
442 | .. code:: python
443 |
444 | # Consumer information
445 | CONSUMER_KEY = 'APIKey' # Your Twitter application's API key
446 | CONSUMER_SECRET = 'APISecret' # Your Twitter application's API secret
447 | PCKT_CONSUMER_KEY = 'PocketAPIKey' # Your Pocket application's API key
448 |
449 | - Use pip to install in local
450 |
451 | .. code:: bash
452 |
453 | # cd to directory which contains setup.py (cloned directory)
454 | virtualenv venv # Python3 users: use -p to specify python3
455 | source venv/bin/activate
456 | pip install -e .
457 | which rainbowstream # /this-directory/venv/bin/rainbowstream
458 | # Remove ~/.rainbow_oauth if exists
459 | rainbowstream # local version of rainbowstream
460 |
461 | .. _Python Twitter Tool: http://mike.verdone.ca/twitter/
462 | .. _Twitter API: https://dev.twitter.com/docs/api/1.1
463 | .. _Create your own Twitter Application: https://apps.twitter.com/app/new
464 | .. _Create your own Pocket Application: https://getpocket.com/developer/apps/new
465 | .. _rainbowstream: https://github.com/DTVD/rainbowstream/tree/master/rainbowstream
466 | .. _Python Twitter Tool: http://mike.verdone.ca/twitter/
467 | .. _Twitter API: https://dev.twitter.com/docs/api/1.1
468 | .. _theme usage and customization: https://github.com/DTVD/rainbowstream/blob/master/theme.md
469 | .. _virtualenv: http://docs.python-guide.org/en/latest/dev/virtualenvs/
470 | .. _config management: http://rainbowstream.readthedocs.org/en/latest/#config-explanation
471 | .. _Python's strftime format: https://docs.python.org/2/library/time.html#time.strftime
472 | .. _clang unknown argument: http://kaspermunck.github.io/2014/03/fixing-clang-error/
473 | .. _Issue #10: https://github.com/DTVD/rainbowstream/issues/10
474 | .. _default config: https://github.com/DTVD/rainbowstream/blob/master/rainbowstream/colorset/config
475 | .. _Emoji cheatsheet: http://www.emoji-cheat-sheet.com/
476 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | REM Command file for Sphinx documentation
4 |
5 | if "%SPHINXBUILD%" == "" (
6 | set SPHINXBUILD=sphinx-build
7 | )
8 | set BUILDDIR=_build
9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
10 | set I18NSPHINXOPTS=%SPHINXOPTS% .
11 | if NOT "%PAPER%" == "" (
12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
14 | )
15 |
16 | if "%1" == "" goto help
17 |
18 | if "%1" == "help" (
19 | :help
20 | echo.Please use `make ^<target^>` where ^<target^> is one of
21 | echo. html to make standalone HTML files
22 | echo. dirhtml to make HTML files named index.html in directories
23 | echo. singlehtml to make a single large HTML file
24 | echo. pickle to make pickle files
25 | echo. json to make JSON files
26 | echo. htmlhelp to make HTML files and a HTML help project
27 | echo. qthelp to make HTML files and a qthelp project
28 | echo. devhelp to make HTML files and a Devhelp project
29 | echo. epub to make an epub
30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
31 | echo. text to make text files
32 | echo. man to make manual pages
33 | echo. texinfo to make Texinfo files
34 | echo. gettext to make PO message catalogs
35 | echo. changes to make an overview over all changed/added/deprecated items
36 | echo. xml to make Docutils-native XML files
37 | echo. pseudoxml to make pseudoxml-XML files for display purposes
38 | echo. linkcheck to check all external links for integrity
39 | echo. doctest to run all doctests embedded in the documentation if enabled
40 | goto end
41 | )
42 |
43 | if "%1" == "clean" (
44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
45 | del /q /s %BUILDDIR%\*
46 | goto end
47 | )
48 |
49 |
50 | %SPHINXBUILD% 2> nul
51 | if errorlevel 9009 (
52 | echo.
53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
54 | echo.installed, then set the SPHINXBUILD environment variable to point
55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
56 | echo.may add the Sphinx directory to PATH.
57 | echo.
58 | echo.If you don't have Sphinx installed, grab it from
59 | echo.http://sphinx-doc.org/
60 | exit /b 1
61 | )
62 |
63 | if "%1" == "html" (
64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
65 | if errorlevel 1 exit /b 1
66 | echo.
67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html.
68 | goto end
69 | )
70 |
71 | if "%1" == "dirhtml" (
72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
73 | if errorlevel 1 exit /b 1
74 | echo.
75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
76 | goto end
77 | )
78 |
79 | if "%1" == "singlehtml" (
80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
81 | if errorlevel 1 exit /b 1
82 | echo.
83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
84 | goto end
85 | )
86 |
87 | if "%1" == "pickle" (
88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
89 | if errorlevel 1 exit /b 1
90 | echo.
91 | echo.Build finished; now you can process the pickle files.
92 | goto end
93 | )
94 |
95 | if "%1" == "json" (
96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
97 | if errorlevel 1 exit /b 1
98 | echo.
99 | echo.Build finished; now you can process the JSON files.
100 | goto end
101 | )
102 |
103 | if "%1" == "htmlhelp" (
104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
105 | if errorlevel 1 exit /b 1
106 | echo.
107 | echo.Build finished; now you can run HTML Help Workshop with the ^
108 | .hhp project file in %BUILDDIR%/htmlhelp.
109 | goto end
110 | )
111 |
112 | if "%1" == "qthelp" (
113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
114 | if errorlevel 1 exit /b 1
115 | echo.
116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^
117 | .qhcp project file in %BUILDDIR%/qthelp, like this:
118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\RainbowStream.qhcp
119 | echo.To view the help file:
120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\RainbowStream.ghc
121 | goto end
122 | )
123 |
124 | if "%1" == "devhelp" (
125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
126 | if errorlevel 1 exit /b 1
127 | echo.
128 | echo.Build finished.
129 | goto end
130 | )
131 |
132 | if "%1" == "epub" (
133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
134 | if errorlevel 1 exit /b 1
135 | echo.
136 | echo.Build finished. The epub file is in %BUILDDIR%/epub.
137 | goto end
138 | )
139 |
140 | if "%1" == "latex" (
141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
142 | if errorlevel 1 exit /b 1
143 | echo.
144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
145 | goto end
146 | )
147 |
148 | if "%1" == "latexpdf" (
149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
150 | cd %BUILDDIR%/latex
151 | make all-pdf
152 | cd %BUILDDIR%/..
153 | echo.
154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex.
155 | goto end
156 | )
157 |
158 | if "%1" == "latexpdfja" (
159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
160 | cd %BUILDDIR%/latex
161 | make all-pdf-ja
162 | cd %BUILDDIR%/..
163 | echo.
164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex.
165 | goto end
166 | )
167 |
168 | if "%1" == "text" (
169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
170 | if errorlevel 1 exit /b 1
171 | echo.
172 | echo.Build finished. The text files are in %BUILDDIR%/text.
173 | goto end
174 | )
175 |
176 | if "%1" == "man" (
177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
178 | if errorlevel 1 exit /b 1
179 | echo.
180 | echo.Build finished. The manual pages are in %BUILDDIR%/man.
181 | goto end
182 | )
183 |
184 | if "%1" == "texinfo" (
185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
186 | if errorlevel 1 exit /b 1
187 | echo.
188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
189 | goto end
190 | )
191 |
192 | if "%1" == "gettext" (
193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
194 | if errorlevel 1 exit /b 1
195 | echo.
196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
197 | goto end
198 | )
199 |
200 | if "%1" == "changes" (
201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
202 | if errorlevel 1 exit /b 1
203 | echo.
204 | echo.The overview file is in %BUILDDIR%/changes.
205 | goto end
206 | )
207 |
208 | if "%1" == "linkcheck" (
209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
210 | if errorlevel 1 exit /b 1
211 | echo.
212 | echo.Link check complete; look for any errors in the above output ^
213 | or in %BUILDDIR%/linkcheck/output.txt.
214 | goto end
215 | )
216 |
217 | if "%1" == "doctest" (
218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
219 | if errorlevel 1 exit /b 1
220 | echo.
221 | echo.Testing of doctests in the sources finished, look at the ^
222 | results in %BUILDDIR%/doctest/output.txt.
223 | goto end
224 | )
225 |
226 | if "%1" == "xml" (
227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
228 | if errorlevel 1 exit /b 1
229 | echo.
230 | echo.Build finished. The XML files are in %BUILDDIR%/xml.
231 | goto end
232 | )
233 |
234 | if "%1" == "pseudoxml" (
235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
236 | if errorlevel 1 exit /b 1
237 | echo.
238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
239 | goto end
240 | )
241 |
242 | :end
243 |
--------------------------------------------------------------------------------
/rainbowstream/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/orakaro/rainbowstream/8b2efa52574865646adfb157f4563a72d9e37d96/rainbowstream/__init__.py
--------------------------------------------------------------------------------
/rainbowstream/c_image.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from PIL import Image
3 | from os.path import join, dirname, getmtime, exists, expanduser
4 | from .config import *
5 | from .py3patch import *
6 |
7 | import ctypes
8 | import sys
9 | import os
10 |
11 |
12 | def call_c():
13 | """
14 | Call the C program for converting RGB to Ansi colors
15 | """
16 | library = expanduser('~/.image.so')
17 | sauce = join(dirname(__file__), 'image.c')
18 | if not exists(library) or getmtime(sauce) > getmtime(library):
19 | build = "cc -fPIC -shared -o %s %s" % (library, sauce)
20 | os.system(build + " >/dev/null 2>&1")
21 | image_c = ctypes.cdll.LoadLibrary(library)
22 | image_c.init()
23 | return image_c.rgb_to_ansi
24 |
25 | rgb2short = call_c()
26 |
27 |
28 | def pixel_print(pixel):
29 | """
30 | Print a pixel with given Ansi color
31 | """
32 | r, g, b = pixel[:3]
33 |
34 | if c['24BIT'] is True:
35 | sys.stdout.write('\033[48;2;%d;%d;%dm \033[0m'
36 | % (r, g, b))
37 | else:
38 | ansicolor = rgb2short(r, g, b)
39 | sys.stdout.write('\033[48;5;%sm \033[0m' % (ansicolor))
40 |
41 |
42 | def block_print(higher, lower):
43 | """
44 | Print two pixels arranged above each other with Ansi color.
45 | Abuses Unicode to print two pixels in the space of one terminal block.
46 | """
47 | r0, g0, b0 = lower[:3]
48 | r1, g1, b1 = higher[:3]
49 |
50 | if c['24BIT'] is True:
51 | sys.stdout.write('\033[38;2;%d;%d;%dm\033[48;2;%d;%d;%dm▄\033[0m'
52 | % (r1, g1, b1, r0, g0, b0))
53 | else:
54 | i0 = rgb2short(r0, g0, b0)
55 | i1 = rgb2short(r1, g1, b1)
56 | sys.stdout.write('\033[38;5;%sm\033[48;5;%sm▄\033[0m' % (i1, i0))
57 |
58 |
59 | def image_to_display(path, start=None, length=None):
60 | """
61 | Display an image
62 | """
63 | rows, columns = os.popen('stty size', 'r').read().split()
64 | if not start:
65 | start = c['IMAGE_SHIFT']
66 | if not length:
67 | length = int(columns) - 2 * start
68 | i = Image.open(path)
69 | i = i.convert('RGBA')
70 | w, h = i.size
71 | i.load()
72 | width = min(w, length)
73 | height = int(float(h) * (float(width) / float(w)))
74 |
75 | if c['IMAGE_RESIZE_TO_FIT'] is True:
76 | # If it image won't fit in the terminal without scrolling shrink it
77 | # Subtract 3 from rows so the tweet message fits in too.
78 | h = 2 * (int(rows) - 3)
79 | if height >= h:
80 | width = int(float(width) * (float(h) / float(height)))
81 | height = h
82 | if (height <= 0) or (width <= 0):
83 | raise ValueError("image has negative dimensions")
84 |
85 | height = min(height, c['IMAGE_MAX_HEIGHT'])
86 |
87 | # Sixel
88 | if c['IMAGE_ON_TERM'] == 'sixel':
89 | import fcntl, struct, termios
90 | from io import BytesIO
91 | from libsixel import sixel_dither_new, sixel_dither_initialize, sixel_encode, sixel_output_new, SIXEL_PIXELFORMAT_RGBA8888
92 | from resizeimage import resizeimage
93 |
94 | # FIXME: rows and columns are gotten a second time. Maybe use this at
95 | # the begining of function instead of the call to stty size
96 | farg = struct.pack("HHHH", 0, 0, 0, 0)
97 | fd_stdout = sys.stdout.fileno()
98 | fretint = fcntl.ioctl(fd_stdout, termios.TIOCGWINSZ, farg)
99 | rows, columns, xpixels, ypixels = struct.unpack("HHHH", fretint)
100 | max_width_pixels = width * (xpixels // columns)
101 | max_height_pixels = height * (ypixels // rows)
102 |
103 | # FIXME: This way is preferable to avoid an addition dependency, but it doesn't work correctly
104 | # i = i.resize((max_width_pixels, max_height_pixels), Image.ANTIALIAS)
105 | i = resizeimage.resize_thumbnail(i, [max_width_pixels, max_height_pixels])
106 |
107 | sixel = BytesIO()
108 | dither = sixel_dither_new(256)
109 | sixel_dither_initialize(dither, i.tobytes(), i.width, i.height, SIXEL_PIXELFORMAT_RGBA8888)
110 | sixel_encode(i.tobytes(), i.width, i.height, 1, dither,
111 | sixel_output_new(lambda imgdata, sixel: sixel.write(imgdata), sixel))
112 | sys.stdout.write('%s%s' % (' ' * start, sixel.getvalue().decode('ascii')))
113 |
114 | # SGR
115 | else:
116 | i = i.resize((width, height), Image.ANTIALIAS)
117 |
118 | for real_y in xrange(height // 2):
119 | sys.stdout.write(' ' * start)
120 | for x in xrange(width):
121 | y = real_y * 2
122 | p0 = i.getpixel((x, y))
123 | p1 = i.getpixel((x, y + 1))
124 | block_print(p1, p0)
125 | sys.stdout.write('\n')
126 |
127 | """
128 | For direct using purpose
129 | """
130 | if __name__ == '__main__':
131 | image_to_display(sys.argv[1])
132 |
--------------------------------------------------------------------------------
/rainbowstream/colors.py:
--------------------------------------------------------------------------------
1 | def basic_color(code):
2 | """
3 | 16 colors supported
4 | """
5 | def inner(text, rl=False):
6 | """
7 | Every raw_input with color sequences should be called with
8 | rl=True to avoid readline messed up the length calculation
9 | """
10 | c = code
11 | if rl:
12 | return "\001\033[%sm\002%s\001\033[0m\002" % (c, text)
13 | else:
14 | return "\033[%sm%s\033[0m" % (c, text)
15 | return inner
16 |
17 |
18 | def term_color(code):
19 | """
20 | 256 colors supported
21 | """
22 | def inner(text, rl=False):
23 | """
24 | Every raw_input with color sequences should be called with
25 | rl=True to avoid readline messed up the length calculation
26 | """
27 | c = code
28 | if rl:
29 | return "\001\033[38;5;%sm\002%s\001\033[0m\002" % (c, text)
30 | else:
31 | return "\033[38;5;%sm%s\033[0m" % (c, text)
32 | return inner
33 |
34 |
35 | """
36 | 16 basic colors
37 | """
38 | default = basic_color('39')
39 | black = basic_color('30')
40 | red = basic_color('31')
41 | green = basic_color('32')
42 | yellow = basic_color('33')
43 | blue = basic_color('34')
44 | magenta = basic_color('35')
45 | cyan = basic_color('36')
46 | grey = basic_color('90')
47 | light_red = basic_color('91')
48 | light_green = basic_color('92')
49 | light_yellow = basic_color('93')
50 | light_blue = basic_color('94')
51 | light_magenta = basic_color('95')
52 | light_cyan = basic_color('96')
53 | white = basic_color('97')
54 |
55 | """
56 | 16 basic colors on background
57 | """
58 | on_default = basic_color('49')
59 | on_black = basic_color('40')
60 | on_red = basic_color('41')
61 | on_green = basic_color('42')
62 | on_yellow = basic_color('43')
63 | on_blue = basic_color('44')
64 | on_magenta = basic_color('45')
65 | on_cyan = basic_color('46')
66 | on_grey = basic_color('100')
67 | on_light_red = basic_color('101')
68 | on_light_green = basic_color('102')
69 | on_light_yellow = basic_color('103')
70 | on_light_blue = basic_color('104')
71 | on_light_magenta = basic_color('105')
72 | on_light_cyan = basic_color('106')
73 | on_white = basic_color('107')
74 |
--------------------------------------------------------------------------------
/rainbowstream/colorset/base16.json:
--------------------------------------------------------------------------------
1 | {
2 | "DECORATED_NAME": 7,
3 | "CYCLE_COLOR": [1,2,3,4,5,6,16,17,20,21],
4 | "TWEET": {
5 | "mynick": 3,
6 | "nick": 4,
7 | "clock": 8,
8 | "id": 4,
9 | "client": 7,
10 | "favorited": 1,
11 | "retweet_count": 2,
12 | "favorite_count": 1,
13 | "rt": 2,
14 | "link": 4,
15 | "hashtag": 3,
16 | "mytweet": 20,
17 | "keyword": "on_light_green"
18 | },
19 | "NOTIFICATION": {
20 | "source_nick": 3,
21 | "notify": 1,
22 | "clock": 8
23 | },
24 | "MESSAGE": {
25 | "partner": 2,
26 | "me": 7,
27 | "partner_frame": 2,
28 | "me_frame": 7,
29 | "sender": 2,
30 | "recipient": 6,
31 | "to": 3,
32 | "clock": 8,
33 | "id": 4
34 | },
35 | "PROFILE": {
36 | "statuses_count": 6,
37 | "friends_count": 7,
38 | "followers_count": 7,
39 | "nick": 2,
40 | "profile_image_url": 4,
41 | "description": 7,
42 | "location": 7,
43 | "url": 4,
44 | "clock": 8
45 | },
46 | "TREND": {
47 | "url": 6
48 | },
49 | "CAL": {
50 | "days": 1,
51 | "today": "on_light_blue"
52 | },
53 | "GROUP": {
54 | "name": 6,
55 | "member": 1,
56 | "subscriber": 3,
57 | "mode": 6,
58 | "description": 2,
59 | "clock": 8
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/rainbowstream/colorset/config:
--------------------------------------------------------------------------------
1 | {
2 | //Time in seconds between each automatic poll. Most Twitter accounts have a limit of 15 requests each 15 minutes. If in doubt, set it to 90 or more.
3 | "POLLING_TIME" : 90,
4 | // Turn to 'true' in order to disable extended tweets display (legacy mode)
5 | "DISABLE_EXTENDED_TWEETS" : false,
6 | // After 120 minutes, the stream will automatically hangup
7 | "HEARTBEAT_TIMEOUT" : 120,
8 | // Image on term
9 | "IMAGE_ON_TERM" : false,
10 | // Resize image to fit on terminal view
11 | "IMAGE_RESIZE_TO_FIT" : false,
12 | // Themes
13 | "THEME" : "monokai",
14 | // Ascii Art
15 | "ASCII_ART" : true,
16 | // Hide promt when receive a tweet
17 | "HIDE_PROMPT" : true,
18 | // Prefix
19 | "PREFIX" : "#owner#place#me#keyword",
20 | // 'search': search type ('mixed','recent','popular')
21 | "SEARCH_TYPE" : "mixed",
22 | // 'search': search max result, number over 100 will fallback to 100
23 | "SEARCH_MAX_RECORD" : 5,
24 | // 'home': default number of home's tweets
25 | "HOME_TWEET_NUM" : 5,
26 | // 'allrt': default number of retweets
27 | "RETWEETS_SHOW_NUM" : 5,
28 | // 'conversation': max tweet in a thread
29 | "CONVERSATION_MAX" : 30,
30 | // 'quote' format
31 | "QUOTE_FORMAT" : "#comment https://twitter.com/#owner/status/#tid",
32 | // 'thread' meta format
33 | "THREAD_META_LEFT" : "(#id) #clock",
34 | "THREAD_META_RIGHT" : "#clock (#id)",
35 | // 'thread' frame's minimum width
36 | "THREAD_MIN_WIDTH" : 20,
37 | // 'Notification' format
38 | "NOTIFY_FORMAT" : " #source_user #notify #clock",
39 | // 'inbox','sent': default number of direct message
40 | "MESSAGES_DISPLAY" : 5,
41 | // 'trend': max trending topics
42 | "TREND_MAX" : 10,
43 | // List home timeline max
44 | "LIST_MAX" : 5,
45 | // 'switch': Filter and Ignore list ex: ['@fat','@mdo']
46 | "ONLY_LIST" : [],
47 | "IGNORE_LIST" : [],
48 | // Autocomplete history file name
49 | "HISTORY_FILENAME" : "completer.hist",
50 | // Image margin
51 | "IMAGE_SHIFT" : 2,
52 | // Image max height
53 | "IMAGE_MAX_HEIGHT" : 90,
54 | // Seconds to wait before displaying another tweet, will drop all tweets while waiting.
55 | "STREAM_DELAY" : 0,
56 | // Stream config
57 | "USER_DOMAIN" : "userstream.twitter.com",
58 | "PUBLIC_DOMAIN" : "stream.twitter.com",
59 | "SITE_DOMAIN" : "sitestream.twitter.com",
60 | // Format
61 | "FORMAT": {
62 | "TWEET": {
63 | "CLOCK_FORMAT" : "%Y/%m/%d %H:%M:%S",
64 | "DISPLAY" : "\n #name #nick #clock \n \u267A:#rt_count \u2665:#fa_count id:#id via #client #fav\n #tweet"
65 | },
66 | "MESSAGE": {
67 | "CLOCK_FORMAT" : "%Y/%m/%d %H:%M:%S",
68 | "DISPLAY" : "\n #sender_name #sender_nick #to #recipient_name #recipient_nick :\n #clock message_id:#id\n #message"
69 | }
70 | },
71 | "POCKET_SUPPORT": false
72 | }
73 |
--------------------------------------------------------------------------------
/rainbowstream/colorset/larapaste.json:
--------------------------------------------------------------------------------
1 | {
2 | /* Color config
3 | There are 16 basic colors supported :
4 | * default
5 | * black
6 | * red
7 | * green
8 | * yellow
9 | * blue
10 | * magenta
11 | * cyan
12 | * grey
13 | * light_red
14 | * light_green
15 | * light_yellow
16 | * light_blue
17 | * light_magenta
18 | * light_cyan
19 | * white
20 | and 256 terminal's colors from 0 to 255
21 | */
22 |
23 | "DECORATED_NAME" : 37,
24 | "CYCLE_COLOR" :[37,184,202,154,59,230],
25 | "TWEET" : {
26 | "mynick" : 202,
27 | "nick" : 37,
28 | "clock" : 184,
29 | "id" : 184,
30 | "client" : 154,
31 | "favorited" : 154,
32 | "retweet_count" : 154,
33 | "favorite_count" : 202,
34 | "rt" : 202,
35 | "link" : 154,
36 | "hashtag" : 37,
37 | "mytweet" : 202,
38 | "keyword" : "on_light_green"
39 | },
40 |
41 | "NOTIFICATION":{
42 | "source_nick" : 37,
43 | "notify" : 202,
44 | "clock" : 184
45 | },
46 |
47 | "MESSAGE" : {
48 | "partner" : 37,
49 | "me" : 37,
50 | "partner_frame" : 154,
51 | "me_frame" : 202,
52 | "sender" : 37,
53 | "recipient" : 37,
54 | "to" : 154,
55 | "clock" : 184,
56 | "id" : 202
57 | },
58 |
59 | "PROFILE" : {
60 | "statuses_count" : 202,
61 | "friends_count" : 37,
62 | "followers_count" : 184,
63 | "nick" : 37,
64 | "profile_image_url" : 154,
65 | "description" : 230,
66 | "location" : 59,
67 | "url" : 154,
68 | "clock" : 184
69 | },
70 |
71 | "TREND" : {
72 | "url": 154
73 | },
74 |
75 | "CAL" : {
76 | "days": 202,
77 | "today": "on_light_magenta"
78 | },
79 |
80 | "GROUP" : {
81 | "name": 37,
82 | "member": 184,
83 | "subscriber": 37,
84 | "mode": 59,
85 | "description": 230,
86 | "clock": 184
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/rainbowstream/colorset/monokai.json:
--------------------------------------------------------------------------------
1 | {
2 | /* Color config
3 | There are 16 basic colors supported :
4 | * default
5 | * black
6 | * red
7 | * green
8 | * yellow
9 | * blue
10 | * magenta
11 | * cyan
12 | * grey
13 | * light_red
14 | * light_green
15 | * light_yellow
16 | * light_blue
17 | * light_magenta
18 | * light_cyan
19 | * white
20 | and 256 terminal's colors from 0 to 255
21 | */
22 |
23 | "DECORATED_NAME" : 198,
24 | "CYCLE_COLOR" :[198,57,166,50,179,74,112],
25 | "TWEET" : {
26 | "mynick" : 179,
27 | "nick" : 112,
28 | "clock" : 57,
29 | "id" : 166,
30 | "client" : 74,
31 | "favorited" : 50,
32 | "retweet_count" : 50,
33 | "favorite_count" : 198,
34 | "rt" : 179,
35 | "link" : 74,
36 | "hashtag" : 198,
37 | "mytweet" : 179,
38 | "keyword" : "on_light_green"
39 | },
40 |
41 | "NOTIFICATION":{
42 | "source_nick" : 112,
43 | "notify" : 179,
44 | "clock" : 57
45 | },
46 |
47 | "MESSAGE" : {
48 | "partner" : 112,
49 | "me" : 112,
50 | "partner_frame" : 198,
51 | "me_frame" : 74,
52 | "sender" : 112,
53 | "recipient" : 112,
54 | "to" : 50,
55 | "clock" : 57,
56 | "id" : 166
57 | },
58 |
59 | "PROFILE" : {
60 | "statuses_count" : 112,
61 | "friends_count" : 198,
62 | "followers_count" : 57,
63 | "nick" : 198,
64 | "profile_image_url" : 74,
65 | "description" : 166,
66 | "location" : 112,
67 | "url" : 74,
68 | "clock" : 57
69 | },
70 |
71 | "TREND" : {
72 | "url": 74
73 | },
74 |
75 | "CAL" : {
76 | "days": 57,
77 | "today": "on_light_blue"
78 | },
79 |
80 | "GROUP" : {
81 | "name": 112,
82 | "member": 57,
83 | "subscriber": 198,
84 | "mode": 112,
85 | "description": 166,
86 | "clock": 57
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/rainbowstream/colorset/solarized.json:
--------------------------------------------------------------------------------
1 | {
2 | /* Color config
3 | There are 16 basic colors supported :
4 | * default
5 | * black
6 | * red
7 | * green
8 | * yellow
9 | * blue
10 | * magenta
11 | * cyan
12 | * grey
13 | * light_red
14 | * light_green
15 | * light_yellow
16 | * light_blue
17 | * light_magenta
18 | * light_cyan
19 | * white
20 | and 256 terminal's colors from 0 to 255
21 | */
22 |
23 | "DECORATED_NAME" : 64,
24 | "CYCLE_COLOR" :[124,32,64,66,130,23],
25 | "TWEET" : {
26 | "mynick" : 66,
27 | "nick" : 64,
28 | "clock" : 32,
29 | "id" : 130,
30 | "client" : 23,
31 | "favorited" : 64,
32 | "retweet_count" : 64,
33 | "favorite_count" : 124,
34 | "rt" : 66,
35 | "link" : 23,
36 | "hashtag" : 64,
37 | "mytweet" : 66,
38 | "keyword" : "on_light_green"
39 | },
40 |
41 | "NOTIFICATION":{
42 | "source_nick" : 64,
43 | "notify" : 66,
44 | "clock" : 32
45 | },
46 |
47 | "MESSAGE" : {
48 | "partner" : 64,
49 | "me" : 64,
50 | "partner_frame" : 124,
51 | "me_frame" : 23,
52 | "sender" : 64,
53 | "recipient" : 64,
54 | "to" : 130,
55 | "clock" : 32,
56 | "id" : 124
57 | },
58 |
59 | "PROFILE" : {
60 | "statuses_count" : 124,
61 | "friends_count" : 32,
62 | "followers_count" : 130,
63 | "nick" : 64,
64 | "profile_image_url" : 23,
65 | "description" : 66,
66 | "location" : 64,
67 | "url" : 23,
68 | "clock" : 32
69 | },
70 |
71 | "TREND" : {
72 | "url": 23
73 | },
74 |
75 | "CAL" : {
76 | "days": 64,
77 | "today": "light_green"
78 | },
79 |
80 | "GROUP" : {
81 | "name": 64,
82 | "member": 130,
83 | "subscriber": 32,
84 | "mode": 164,
85 | "description": 66,
86 | "clock": 32
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/rainbowstream/colorset/tomorrow_night.json:
--------------------------------------------------------------------------------
1 | {
2 | /* Color config
3 | There are 16 basic colors supported :
4 | * default
5 | * black
6 | * red
7 | * green
8 | * yellow
9 | * blue
10 | * magenta
11 | * cyan
12 | * grey
13 | * light_red
14 | * light_green
15 | * light_yellow
16 | * light_blue
17 | * light_magenta
18 | * light_cyan
19 | * white
20 | and 256 terminal's colors from 0 to 255
21 | */
22 |
23 | "DECORATED_NAME" : 67,
24 | "CYCLE_COLOR" :[67,166,30,97,58,179,145],
25 | "TWEET" : {
26 | "mynick" : 145,
27 | "nick" : 67,
28 | "clock" : 58,
29 | "id" : 58,
30 | "client" : 58,
31 | "favorited" : 97,
32 | "retweet_count" : 97,
33 | "favorite_count" : 166,
34 | "rt" : 145,
35 | "link" : 30,
36 | "hashtag" : 67,
37 | "mytweet" : 145,
38 | "keyword" : "on_light_blue"
39 | },
40 |
41 | "NOTIFICATION":{
42 | "source_nick" : 67,
43 | "notify" : 145,
44 | "clock" : 58
45 | },
46 |
47 | "MESSAGE" : {
48 | "partner" : 67,
49 | "me" : 67,
50 | "partner_frame" : 166,
51 | "me_frame" : 30,
52 | "sender" : 67,
53 | "recipient" : 67,
54 | "to" : 97,
55 | "clock" : 58,
56 | "id" : 166
57 | },
58 |
59 | "PROFILE" : {
60 | "statuses_count" : 166,
61 | "friends_count" : 30,
62 | "followers_count" : 179,
63 | "nick" : 67,
64 | "profile_image_url" : 30,
65 | "description" : 58,
66 | "location" : 97,
67 | "url" : 30,
68 | "clock" : 58
69 | },
70 |
71 | "TREND" : {
72 | "url": 30
73 | },
74 |
75 | "CAL" : {
76 | "days": 67,
77 | "today": "on_light_blue"
78 | },
79 |
80 | "GROUP" : {
81 | "name": 67,
82 | "member": 179,
83 | "subscriber": 30,
84 | "mode": 97,
85 | "description": 58,
86 | "clock": 166
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/rainbowstream/config.py:
--------------------------------------------------------------------------------
1 | import json
2 | import re
3 | import os
4 | import os.path
5 | from collections import OrderedDict
6 |
7 | # Regular expression for comments in config file
8 | comment_re = re.compile(
9 | '(^)[^\S\n]*/(?:\*(.*?)\*/[^\S\n]*|/[^\n]*)($)?',
10 | re.DOTALL | re.MULTILINE
11 | )
12 |
13 | # Config dictionary
14 | c = {}
15 |
16 | def user_filepath():
17 | """
18 | Determine the user's config file location
19 | """
20 | if 'RAINBOW_CONFIG' in os.environ:
21 | return os.environ['RAINBOW_CONFIG']
22 |
23 | return os.path.expanduser("~") + os.sep + '.rainbow_config.json'
24 |
25 | def fixup(adict, k, v):
26 | """
27 | Fix up a key in json format
28 | """
29 | for key in adict.keys():
30 | if key == k:
31 | adict[key] = v
32 | elif isinstance(adict[key], dict):
33 | fixup(adict[key], k, v)
34 |
35 |
36 | def load_config(filepath):
37 | """
38 | Load config from filepath
39 | """
40 | with open(filepath) as f:
41 | content = ''.join(f.readlines())
42 | match = comment_re.search(content)
43 | while match:
44 | content = content[:match.start()] + content[match.end():]
45 | match = comment_re.search(content)
46 | return json.loads(content, object_pairs_hook=OrderedDict)
47 |
48 |
49 | def get_all_config():
50 | """
51 | Get all config
52 | """
53 | try:
54 | path = user_filepath()
55 | data = load_config(path)
56 | # Hard to set from prompt
57 | data.pop('ONLY_LIST', None)
58 | data.pop('IGNORE_LIST', None)
59 | data.pop('FORMAT', None)
60 | data.pop('QUOTE_FORMAT', None)
61 | data.pop('NOTIFY_FORMAT', None)
62 | return data
63 | except:
64 | return []
65 |
66 |
67 | def get_default_config(key):
68 | """
69 | Get default value of a config key
70 | """
71 | try:
72 | path = os.path.dirname(__file__) + '/colorset/config'
73 | data = load_config(path)
74 | return data[key]
75 | except:
76 | raise Exception('This config key does not exist in default.')
77 |
78 |
79 | def get_config(key):
80 | """
81 | Get current value of a config key
82 | """
83 | return c[key]
84 |
85 |
86 | def set_config(key, value):
87 | """
88 | Set a config key with specific value
89 | """
90 | # Modify value
91 | if value.isdigit():
92 | value = int(value)
93 | elif value.lower() == 'true':
94 | value = True
95 | elif value.lower() == 'false':
96 | value = False
97 | # Update global config
98 | c[key] = value
99 | # Load current user config
100 | path = user_filepath()
101 | data = {}
102 | try:
103 | data = load_config(path)
104 | except:
105 | return
106 | # Update config file
107 | if key in data:
108 | fixup(data, key, value)
109 | else:
110 | data[key] = value
111 | # Save
112 | with open(path, 'w') as out:
113 | json.dump(data, out, indent=4)
114 | os.system('chmod 777 ' + path)
115 |
116 |
117 | def delete_config(key):
118 | """
119 | Delete a config key
120 | """
121 | path = user_filepath()
122 | try:
123 | data = load_config(path)
124 | except:
125 | raise Exception('Config file is messed up.')
126 | # Drop key
127 | if key in data and key in c:
128 | data.pop(key)
129 | c.pop(key)
130 | try:
131 | data[key] = c[key] = get_default_config(key)
132 | except:
133 | pass
134 | else:
135 | raise Exception('No such config key.')
136 | # Save
137 | with open(path, 'w') as out:
138 | json.dump(data, out, indent=4)
139 | os.system('chmod 777 ' + path)
140 |
141 |
142 | def reload_config():
143 | """
144 | Reload config
145 | """
146 | try:
147 | rainbow_config = user_filepath()
148 | data = load_config(rainbow_config)
149 | for d in data:
150 | c[d] = data[d]
151 | except:
152 | raise Exception('Can not reload config file with wrong format.')
153 |
154 |
155 | def init_config():
156 | """
157 | Init configuration
158 | """
159 | # Load the initial config
160 | config = os.path.dirname(__file__) + \
161 | '/colorset/config'
162 | try:
163 | data = load_config(config)
164 | for d in data:
165 | c[d] = data[d]
166 | except:
167 | pass
168 | # Load user's config
169 | rainbow_config = user_filepath()
170 | try:
171 | data = load_config(rainbow_config)
172 | for d in data:
173 | c[d] = data[d]
174 | except (IOError, ValueError) as e:
175 | c['USER_JSON_ERROR'] = str(e)
176 | # Load default theme
177 | theme_file = os.path.dirname(__file__) + \
178 | '/colorset/' + c['THEME'] + '.json'
179 | try:
180 | data = load_config(theme_file)
181 | for d in data:
182 | c[d] = data[d]
183 | except:
184 | pass
185 |
186 |
187 | # Init config
188 | init_config()
189 |
--------------------------------------------------------------------------------
/rainbowstream/draw.py:
--------------------------------------------------------------------------------
1 | import random
2 | import textwrap
3 | import itertools
4 | import requests
5 | import locale
6 | import arrow
7 | import re
8 | import os
9 |
10 | from io import BytesIO
11 | from twitter.util import printNicely
12 | from functools import wraps
13 | from pyfiglet import figlet_format
14 | from dateutil import parser
15 | from .c_image import *
16 | from .colors import *
17 | from .config import *
18 | from .py3patch import *
19 | from .emoji import *
20 |
21 | # Draw global variables
22 | dg = {}
23 |
24 |
25 | def init_cycle():
26 | """
27 | Init the cycle
28 | """
29 | colors_shuffle = [globals()[i.encode('utf8')]
30 | if not str(i).isdigit()
31 | else term_color(int(i))
32 | for i in c['CYCLE_COLOR']]
33 | return itertools.cycle(colors_shuffle)
34 |
35 |
36 | def start_cycle():
37 | """
38 | Notify from rainbow
39 | """
40 | dg['cyc'] = init_cycle()
41 | dg['cache'] = {}
42 | dg['humanize_unsupported'] = False
43 |
44 |
45 | def order_rainbow(s):
46 | """
47 | Print a string with ordered color with each character
48 | """
49 | colors_shuffle = [globals()[i.encode('utf8')]
50 | if not str(i).isdigit()
51 | else term_color(int(i))
52 | for i in c['CYCLE_COLOR']]
53 | colored = [colors_shuffle[i % 7](s[i]) for i in xrange(len(s))]
54 | return ''.join(colored)
55 |
56 |
57 | def random_rainbow(s):
58 | """
59 | Print a string with random color with each character
60 | """
61 | colors_shuffle = [globals()[i.encode('utf8')]
62 | if not str(i).isdigit()
63 | else term_color(int(i))
64 | for i in c['CYCLE_COLOR']]
65 | colored = [random.choice(colors_shuffle)(i) for i in s]
66 | return ''.join(colored)
67 |
68 |
69 | def Memoize(func):
70 | """
71 | Memoize decorator
72 | """
73 | @wraps(func)
74 | def wrapper(*args):
75 | if args not in dg['cache']:
76 | dg['cache'][args] = func(*args)
77 | return dg['cache'][args]
78 | return wrapper
79 |
80 |
81 | @Memoize
82 | def cycle_color(s):
83 | """
84 | Cycle the colors_shuffle
85 | """
86 | return next(dg['cyc'])(s)
87 |
88 |
89 | def ascii_art(text):
90 | """
91 | Draw the Ascii Art
92 | """
93 | fi = figlet_format(text, font='doom')
94 | print('\n'.join(
95 | [next(dg['cyc'])(i) for i in fi.split('\n')]
96 | ))
97 |
98 |
99 | def check_config():
100 | """
101 | Check if config is changed
102 | """
103 | changed = False
104 | data = get_all_config()
105 | for key in c:
106 | if key in data:
107 | if data[key] != c[key]:
108 | changed = True
109 | if changed:
110 | reload_config()
111 |
112 |
113 | def validate_theme(theme):
114 | """
115 | Validate a theme exists or not
116 | """
117 | # Theme changed check
118 | files = os.listdir(os.path.dirname(__file__) + '/colorset')
119 | themes = [f.split('.')[0] for f in files if f.split('.')[-1] == 'json']
120 | return theme in themes
121 |
122 |
123 | def reload_theme(value, prev):
124 | """
125 | Check current theme and update if necessary
126 | """
127 | if value != prev:
128 | config = os.path.dirname(
129 | __file__) + '/colorset/' + value + '.json'
130 | # Load new config
131 | data = load_config(config)
132 | if data:
133 | for d in data:
134 | c[d] = data[d]
135 | # Restart color cycle and update config
136 | start_cycle()
137 | set_config('THEME', value)
138 | return value
139 | return prev
140 |
141 |
142 | def color_func(func_name):
143 | """
144 | Call color function base on name
145 | """
146 | if str(func_name).isdigit():
147 | return term_color(int(func_name))
148 | return globals()[func_name]
149 |
150 |
151 | def fallback_humanize(date, fallback_format=None, use_fallback=False):
152 | """
153 | Format date with arrow and a fallback format
154 | """
155 | # Convert to local timezone
156 | date = arrow.get(date).to('local')
157 | # Set default fallback format
158 | if not fallback_format:
159 | fallback_format = '%Y/%m/%d %H:%M:%S'
160 | # Determine using fallback format or not by a variable
161 | if use_fallback:
162 | return date.datetime.strftime(fallback_format)
163 | try:
164 | # Use Arrow's humanize function
165 | lang, encode = locale.getdefaultlocale()
166 | clock = date.humanize(locale=lang)
167 | except:
168 | # Notice at the 1st time only
169 | if not dg['humanize_unsupported']:
170 | dg['humanize_unsupported'] = True
171 | printNicely(
172 | light_magenta('Humanized date display method does not support your $LC_ALL.'))
173 | # Fallback when LC_ALL is not supported
174 | clock = date.datetime.strftime(fallback_format)
175 | return clock
176 |
177 |
178 | def get_full_text(t):
179 | """Handle RTs and extended tweets to always display all the available text"""
180 |
181 | if t.get('retweeted_status'):
182 | rt_status = t['retweeted_status']
183 | if rt_status.get('extended_tweet'):
184 | elem = rt_status['extended_tweet']
185 | else:
186 | elem = rt_status
187 | rt_text = elem.get('full_text', elem.get('text'))
188 | t['full_text'] = 'RT @' + rt_status['user']['screen_name'] + ': ' + rt_text
189 | elif t.get('extended_tweet'):
190 | t['full_text'] = t['extended_tweet']['full_text']
191 |
192 | return t.get('full_text', t.get('text'))
193 |
194 |
195 | def draw(t, keyword=None, humanize=True, noti=False, fil=[], ig=[]):
196 | """
197 | Draw the rainbow
198 | """
199 | # Check config
200 | check_config()
201 |
202 | # Retrieve tweet
203 | tid = t['id']
204 |
205 | text = get_full_text(t)
206 | screen_name = t['user']['screen_name']
207 | name = t['user']['name']
208 | created_at = t['created_at']
209 | favorited = t['favorited']
210 | retweet_count = t['retweet_count']
211 | favorite_count = t['favorite_count']
212 | client = t['source']
213 | date = parser.parse(created_at)
214 | try:
215 | clock_format = c['FORMAT']['TWEET']['CLOCK_FORMAT']
216 | except:
217 | clock_format = '%Y/%m/%d %H:%M:%S'
218 | clock = fallback_humanize(date, clock_format, not humanize)
219 |
220 | # Pull extended retweet text
221 | try:
222 | # Display as a notification
223 | target = t['retweeted_status']['user']['screen_name']
224 | if all([target == c['original_name'], not noti]):
225 | # Add to evens for 'notification' command
226 | t['event'] = 'retweet'
227 | c['events'].append(t)
228 | notify_retweet(t)
229 | return
230 | except:
231 | pass
232 |
233 | # Unescape HTML character
234 | text = unescape(text)
235 |
236 | # Get client name
237 | try:
238 | client = client.split('>')[-2].split('<')[0]
239 | except:
240 | client = None
241 |
242 | # Get expanded url
243 | try:
244 | expanded_url = []
245 | url = []
246 | urls = t['entities']['urls']
247 | for u in urls:
248 | expanded_url.append(u['expanded_url'])
249 | url.append(u['url'])
250 | except:
251 | expanded_url = None
252 | url = None
253 |
254 | # Get media
255 | try:
256 | media_url = []
257 | media = t['entities']['media']
258 | for m in media:
259 | media_url.append(m['media_url'])
260 | except:
261 | media_url = None
262 |
263 | # Filter and ignore
264 | mytweet = screen_name == c['original_name']
265 | screen_name = '@' + screen_name
266 | fil = list(set((fil or []) + c['ONLY_LIST']))
267 | ig = list(set((ig or []) + c['IGNORE_LIST']))
268 | if fil and screen_name not in fil:
269 | return
270 | if ig and screen_name in ig:
271 | return
272 |
273 | # Get rainbow id
274 | if tid not in c['tweet_dict']:
275 | c['tweet_dict'].append(tid)
276 | rid = len(c['tweet_dict']) - 1
277 | else:
278 | rid = c['tweet_dict'].index(tid)
279 |
280 | # Format info
281 | name = cycle_color(name)
282 | if mytweet:
283 | nick = color_func(c['TWEET']['mynick'])(screen_name)
284 | else:
285 | nick = color_func(c['TWEET']['nick'])(screen_name)
286 | clock = clock
287 | id = str(rid)
288 | fav = ''
289 | if favorited:
290 | fav = color_func(c['TWEET']['favorited'])(u'\u2605')
291 |
292 | tweet = text.split(' ')
293 | tweet = [x for x in tweet if x != '']
294 | # Replace url
295 | if expanded_url:
296 | for index in xrange(len(expanded_url)):
297 | tweet = lmap(
298 | lambda x: expanded_url[index]
299 | if x == url[index]
300 | else x,
301 | tweet)
302 | # Highlight RT
303 | tweet = lmap(
304 | lambda x: color_func(c['TWEET']['rt'])(x)
305 | if x == 'RT'
306 | else x,
307 | tweet)
308 | # Highlight screen_name
309 | tweet = lmap(
310 | lambda x: cycle_color(x) if x.lstrip().startswith('@') else x, tweet)
311 | # Highlight link
312 | tweet = lmap(
313 | lambda x: color_func(c['TWEET']['link'])(x)
314 | if x.lstrip().startswith('http')
315 | else x,
316 | tweet)
317 | # Highlight hashtag
318 | tweet = lmap(
319 | lambda x: color_func(c['TWEET']['hashtag'])(x)
320 | if x.lstrip().startswith('#')
321 | else x,
322 | tweet)
323 | # Highlight my tweet
324 | if mytweet:
325 | tweet = [color_func(c['TWEET']['mytweet'])(x)
326 | for x in tweet
327 | if not any([
328 | x == 'RT',
329 | x.lstrip().startswith('http'),
330 | x.lstrip().startswith('#')])
331 | ]
332 | # Highlight keyword
333 | tweet = ' '.join(tweet)
334 | tweet = '\n '.join(tweet.split('\n'))
335 | if keyword:
336 | roj = re.search(keyword, tweet, re.IGNORECASE)
337 | if roj:
338 | occur = roj.group()
339 | ary = tweet.split(occur)
340 | delimiter = color_func(c['TWEET']['keyword'])(occur)
341 | tweet = delimiter.join(ary)
342 |
343 | # Load config formater
344 | formater = ''
345 | try:
346 | formater = c['FORMAT']['TWEET']['DISPLAY']
347 | formater = name.join(formater.split('#name'))
348 | formater = nick.join(formater.split('#nick'))
349 | formater = fav.join(formater.split('#fav'))
350 | formater = tweet.join(formater.split('#tweet'))
351 | formater = emojize(formater)
352 | # Change clock word
353 | word = [wo for wo in formater.split() if '#clock' in wo][0]
354 | delimiter = color_func(c['TWEET']['clock'])(
355 | clock.join(word.split('#clock')))
356 | formater = delimiter.join(formater.split(word))
357 | # Change id word
358 | word = [wo for wo in formater.split() if '#id' in wo][0]
359 | delimiter = color_func(c['TWEET']['id'])(id.join(word.split('#id')))
360 | formater = delimiter.join(formater.split(word))
361 | # Change retweet count word
362 | word = [wo for wo in formater.split() if '#rt_count' in wo][0]
363 | delimiter = color_func(c['TWEET']['retweet_count'])(
364 | str(retweet_count).join(word.split('#rt_count')))
365 | formater = delimiter.join(formater.split(word))
366 | # Change favorites count word
367 | word = [wo for wo in formater.split() if '#fa_count' in wo][0]
368 | delimiter = color_func(c['TWEET']['favorite_count'])(
369 | str(favorite_count).join(word.split('#fa_count')))
370 | formater = delimiter.join(formater.split(word))
371 | # Change client word
372 | word = [wo for wo in formater.split() if '#client' in wo][0]
373 | delimiter = color_func(c['TWEET']['client'])(
374 | client.join(word.split('#client')))
375 | formater = delimiter.join(formater.split(word))
376 | except:
377 | pass
378 |
379 | # Add spaces in begining of line if this is inside a notification
380 | if noti:
381 | formater = '\n '.join(formater.split('\n'))
382 | # Reformat
383 | if formater.startswith('\n'):
384 | formater = formater[1:]
385 |
386 | # Draw
387 | printNicely(formater)
388 |
389 | # Display Image
390 | if c['IMAGE_ON_TERM'] and media_url:
391 | for mu in media_url:
392 | try:
393 | response = requests.get(mu)
394 | image_to_display(BytesIO(response.content))
395 | except Exception:
396 | printNicely(red('Sorry, image link is broken'))
397 |
398 |
399 | def print_threads(d):
400 | """
401 | Print threads of messages
402 | """
403 | id = 1
404 | rel = {}
405 | for partner in d:
406 | messages = d[partner]
407 | count = len(messages)
408 | screen_name = '@' + partner[0]
409 | name = partner[1]
410 | screen_name = color_func(c['MESSAGE']['partner'])(screen_name)
411 | name = cycle_color(name)
412 | thread_id = color_func(c['MESSAGE']['id'])('thread_id:' + str(id))
413 | line = ' ' * 2 + name + ' ' + screen_name + \
414 | ' (' + str(count) + ' message) ' + thread_id
415 | printNicely(line)
416 | rel[id] = partner
417 | id += 1
418 | dg['thread'] = d
419 | return rel
420 |
421 |
422 | def print_thread(partner, me_nick, me_name):
423 | """
424 | Print a thread of messages
425 | """
426 | # Sort messages by time
427 | messages = dg['thread'][partner]
428 | messages.sort(key=lambda x: int(x['created_at']))
429 | # Use legacy display on non-ascii text message
430 | ms = [m['text'] for m in messages]
431 | ums = [m for m in ms if not all(ord(c) < 128 for c in m)]
432 | if ums:
433 | for m in messages:
434 | print_message(m)
435 | printNicely('')
436 | return
437 | # Print the first line
438 | dg['frame_margin'] = margin = 2
439 | partner_nick = partner[0]
440 | partner_name = partner[1]
441 | left_size = len(partner_nick) + len(partner_name) + 2
442 | right_size = len(me_nick) + len(me_name) + 2
443 | partner_nick = color_func(c['MESSAGE']['partner'])('@' + partner_nick)
444 | partner_name = cycle_color(partner_name)
445 | me_screen_name = color_func(c['MESSAGE']['me'])('@' + me_nick)
446 | me_name = cycle_color(me_name)
447 | left = ' ' * margin + partner_name + ' ' + partner_nick
448 | right = me_name + ' ' + me_screen_name + ' ' * margin
449 | h, w = os.popen('stty size', 'r').read().split()
450 | w = int(w)
451 | line = '{}{}{}'.format(
452 | left, ' ' * (w - left_size - right_size - 2 * margin), right)
453 | printNicely('')
454 | printNicely(line)
455 | printNicely('')
456 | # Print messages
457 | for m in messages:
458 | if m['sender_screen_name'] == me_nick:
459 | print_right_message(m)
460 | elif m['recipient_screen_name'] == me_nick:
461 | print_left_message(m)
462 |
463 |
464 | def print_right_message(m):
465 | """
466 | Print a message on the right of screen
467 | """
468 | h, w = os.popen('stty size', 'r').read().split()
469 | w = int(w)
470 | frame_width = w // 3 - dg['frame_margin']
471 | frame_width = max(c['THREAD_MIN_WIDTH'], frame_width)
472 | step = frame_width - 2 * dg['frame_margin']
473 | slicing = textwrap.wrap(m['text'], step)
474 | spaces = w - frame_width - dg['frame_margin']
475 | dotline = ' ' * spaces + '-' * frame_width
476 | dotline = color_func(c['MESSAGE']['me_frame'])(dotline)
477 | # Draw the frame
478 | printNicely(dotline)
479 | for line in slicing:
480 | fill = step - len(line)
481 | screen_line = ' ' * spaces + '| ' + line + ' ' * fill + ' '
482 | if slicing[-1] == line:
483 | screen_line = screen_line + ' >'
484 | else:
485 | screen_line = screen_line + '|'
486 | screen_line = color_func(c['MESSAGE']['me_frame'])(screen_line)
487 | printNicely(screen_line)
488 | printNicely(dotline)
489 | # Format clock
490 | date = arrow.get(int(m['created_at'])/1000).to('local').datetime # Read Unixtime in miliseconds
491 | clock_format = '%Y/%m/%d %H:%M:%S'
492 | try:
493 | clock_format = c['FORMAT']['MESSAGE']['CLOCK_FORMAT']
494 | except:
495 | pass
496 | clock = date.strftime(clock_format)
497 | # Format id
498 | if m['id'] not in c['message_dict']:
499 | c['message_dict'].append(m['id'])
500 | rid = len(c['message_dict']) - 1
501 | else:
502 | rid = c['message_dict'].index(m['id'])
503 | id = str(rid)
504 | # Print meta
505 | formater = ''
506 | try:
507 | virtual_meta = formater = c['THREAD_META_RIGHT']
508 | virtual_meta = clock.join(virtual_meta.split('#clock'))
509 | virtual_meta = id.join(virtual_meta.split('#id'))
510 | # Change clock word
511 | word = [wo for wo in formater.split() if '#clock' in wo][0]
512 | delimiter = color_func(c['MESSAGE']['clock'])(
513 | clock.join(word.split('#clock')))
514 | formater = delimiter.join(formater.split(word))
515 | # Change id word
516 | word = [wo for wo in formater.split() if '#id' in wo][0]
517 | delimiter = color_func(c['MESSAGE']['id'])(id.join(word.split('#id')))
518 | formater = delimiter.join(formater.split(word))
519 | formater = emojize(formater)
520 | except Exception:
521 | printNicely(red('Wrong format in config.'))
522 | return
523 | meta = formater
524 | line = ' ' * (w - len(virtual_meta) - dg['frame_margin']) + meta
525 | printNicely(line)
526 |
527 |
528 | def print_left_message(m):
529 | """
530 | Print a message on the left of screen
531 | """
532 | h, w = os.popen('stty size', 'r').read().split()
533 | w = int(w)
534 | frame_width = w // 3 - dg['frame_margin']
535 | frame_width = max(c['THREAD_MIN_WIDTH'], frame_width)
536 | step = frame_width - 2 * dg['frame_margin']
537 | slicing = textwrap.wrap(m['text'], step)
538 | spaces = dg['frame_margin']
539 | dotline = ' ' * spaces + '-' * frame_width
540 | dotline = color_func(c['MESSAGE']['partner_frame'])(dotline)
541 | # Draw the frame
542 | printNicely(dotline)
543 | for line in slicing:
544 | fill = step - len(line)
545 | screen_line = ' ' + line + ' ' * fill + ' |'
546 | if slicing[-1] == line:
547 | screen_line = ' ' * (spaces - 1) + '< ' + screen_line
548 | else:
549 | screen_line = ' ' * spaces + '|' + screen_line
550 | screen_line = color_func(c['MESSAGE']['partner_frame'])(screen_line)
551 | printNicely(screen_line)
552 | printNicely(dotline)
553 | # Format clock
554 | date = parser.parse(m['created_at'])
555 | date = arrow.get(date).to('local').datetime
556 | clock_format = '%Y/%m/%d %H:%M:%S'
557 | try:
558 | clock_format = c['FORMAT']['MESSAGE']['CLOCK_FORMAT']
559 | except:
560 | pass
561 | clock = date.strftime(clock_format)
562 | # Format id
563 | if m['id'] not in c['message_dict']:
564 | c['message_dict'].append(m['id'])
565 | rid = len(c['message_dict']) - 1
566 | else:
567 | rid = c['message_dict'].index(m['id'])
568 | id = str(rid)
569 | # Print meta
570 | formater = ''
571 | try:
572 | virtual_meta = formater = c['THREAD_META_LEFT']
573 | virtual_meta = clock.join(virtual_meta.split('#clock'))
574 | virtual_meta = id.join(virtual_meta.split('#id'))
575 | # Change clock word
576 | word = [wo for wo in formater.split() if '#clock' in wo][0]
577 | delimiter = color_func(c['MESSAGE']['clock'])(
578 | clock.join(word.split('#clock')))
579 | formater = delimiter.join(formater.split(word))
580 | # Change id word
581 | word = [wo for wo in formater.split() if '#id' in wo][0]
582 | delimiter = color_func(c['MESSAGE']['id'])(id.join(word.split('#id')))
583 | formater = delimiter.join(formater.split(word))
584 | formater = emojize(formater)
585 | except Exception:
586 | printNicely(red('Wrong format in config.'))
587 | return
588 | meta = formater
589 | line = ' ' * dg['frame_margin'] + meta
590 | printNicely(line)
591 |
592 |
593 | def print_message(m):
594 | """
595 | Print direct message
596 | """
597 | # Retrieve message
598 | sender_screen_name = '@' + m['sender_screen_name']
599 | sender_name = m['sender_name']
600 | text = unescape(m['text'])
601 | recipient_screen_name = '@' + m['recipient_screen_name']
602 | recipient_name = m['recipient_name']
603 | mid = m['id']
604 | date = parser.parse(m['created_at'])
605 | date = arrow.get(date).to('local').datetime
606 | clock_format = '%Y/%m/%d %H:%M:%S'
607 | try:
608 | clock_format = c['FORMAT']['MESSAGE']['CLOCK_FORMAT']
609 | except:
610 | pass
611 | clock = date.strftime(clock_format)
612 |
613 | # Get rainbow id
614 | if mid not in c['message_dict']:
615 | c['message_dict'].append(mid)
616 | rid = len(c['message_dict']) - 1
617 | else:
618 | rid = c['message_dict'].index(mid)
619 |
620 | # Draw
621 | sender_name = cycle_color(sender_name)
622 | sender_nick = color_func(c['MESSAGE']['sender'])(sender_screen_name)
623 | recipient_name = cycle_color(recipient_name)
624 | recipient_nick = color_func(
625 | c['MESSAGE']['recipient'])(recipient_screen_name)
626 | to = color_func(c['MESSAGE']['to'])('>>>')
627 | clock = clock
628 | id = str(rid)
629 |
630 | text = ''.join(lmap(lambda x: x + ' ' if x == '\n' else x, text))
631 |
632 | # Load config formater
633 | try:
634 | formater = c['FORMAT']['MESSAGE']['DISPLAY']
635 | formater = sender_name.join(formater.split("#sender_name"))
636 | formater = sender_nick.join(formater.split("#sender_nick"))
637 | formater = to.join(formater.split("#to"))
638 | formater = recipient_name.join(formater.split("#recipient_name"))
639 | formater = recipient_nick.join(formater.split("#recipient_nick"))
640 | formater = text.join(formater.split("#message"))
641 | # Change clock word
642 | word = [wo for wo in formater.split() if '#clock' in wo][0]
643 | delimiter = color_func(c['MESSAGE']['clock'])(
644 | clock.join(word.split('#clock')))
645 | formater = delimiter.join(formater.split(word))
646 | # Change id word
647 | word = [wo for wo in formater.split() if '#id' in wo][0]
648 | delimiter = color_func(c['MESSAGE']['id'])(id.join(word.split('#id')))
649 | formater = delimiter.join(formater.split(word))
650 | formater = emojize(formater)
651 | except:
652 | printNicely(red('Wrong format in config.'))
653 | return
654 |
655 | # Draw
656 | printNicely(formater)
657 |
658 |
659 | def notify_retweet(t):
660 | """
661 | Notify a retweet
662 | """
663 | source = t['user']
664 | created_at = t['created_at']
665 | # Format
666 | source_user = cycle_color(source['name']) + \
667 | color_func(c['NOTIFICATION']['source_nick'])(
668 | ' @' + source['screen_name'])
669 | notify = color_func(c['NOTIFICATION']['notify'])(
670 | 'retweeted your tweet')
671 | date = parser.parse(created_at)
672 | clock = fallback_humanize(date)
673 | clock = color_func(c['NOTIFICATION']['clock'])(clock)
674 | meta = c['NOTIFY_FORMAT']
675 | meta = source_user.join(meta.split('#source_user'))
676 | meta = notify.join(meta.split('#notify'))
677 | meta = clock.join(meta.split('#clock'))
678 | meta = emojize(meta)
679 | # Output
680 | printNicely('')
681 | printNicely(meta)
682 | draw(t=t['retweeted_status'], noti=True)
683 |
684 |
685 | def notify_favorite(e):
686 | """
687 | Notify a favorite event
688 | """
689 | # Retrieve info
690 | target = e['target']
691 | if target['screen_name'] != c['original_name']:
692 | return
693 | source = e['source']
694 | target_object = e['target_object']
695 | created_at = e['created_at']
696 | # Format
697 | source_user = cycle_color(source['name']) + \
698 | color_func(c['NOTIFICATION']['source_nick'])(
699 | ' @' + source['screen_name'])
700 | notify = color_func(c['NOTIFICATION']['notify'])(
701 | 'favorited your tweet')
702 | date = parser.parse(created_at)
703 | clock = fallback_humanize(date)
704 | clock = color_func(c['NOTIFICATION']['clock'])(clock)
705 | meta = c['NOTIFY_FORMAT']
706 | meta = source_user.join(meta.split('#source_user'))
707 | meta = notify.join(meta.split('#notify'))
708 | meta = clock.join(meta.split('#clock'))
709 | meta = emojize(meta)
710 | # Output
711 | printNicely('')
712 | printNicely(meta)
713 | draw(t=target_object, noti=True)
714 |
715 |
716 | def notify_unfavorite(e):
717 | """
718 | Notify a unfavorite event
719 | """
720 | # Retrieve info
721 | target = e['target']
722 | if target['screen_name'] != c['original_name']:
723 | return
724 | source = e['source']
725 | target_object = e['target_object']
726 | created_at = e['created_at']
727 | # Format
728 | source_user = cycle_color(source['name']) + \
729 | color_func(c['NOTIFICATION']['source_nick'])(
730 | ' @' + source['screen_name'])
731 | notify = color_func(c['NOTIFICATION']['notify'])(
732 | 'unfavorited your tweet')
733 | date = parser.parse(created_at)
734 | clock = fallback_humanize(date)
735 | clock = color_func(c['NOTIFICATION']['clock'])(clock)
736 | meta = c['NOTIFY_FORMAT']
737 | meta = source_user.join(meta.split('#source_user'))
738 | meta = notify.join(meta.split('#notify'))
739 | meta = clock.join(meta.split('#clock'))
740 | meta = emojize(meta)
741 | # Output
742 | printNicely('')
743 | printNicely(meta)
744 | draw(t=target_object, noti=True)
745 |
746 |
747 | def notify_follow(e):
748 | """
749 | Notify a follow event
750 | """
751 | # Retrieve info
752 | target = e['target']
753 | if target['screen_name'] != c['original_name']:
754 | return
755 | source = e['source']
756 | created_at = e['created_at']
757 | # Format
758 | source_user = cycle_color(source['name']) + \
759 | color_func(c['NOTIFICATION']['source_nick'])(
760 | ' @' + source['screen_name'])
761 | notify = color_func(c['NOTIFICATION']['notify'])(
762 | 'followed you')
763 | date = parser.parse(created_at)
764 | clock = fallback_humanize(date)
765 | clock = color_func(c['NOTIFICATION']['clock'])(clock)
766 | meta = c['NOTIFY_FORMAT']
767 | meta = source_user.join(meta.split('#source_user'))
768 | meta = notify.join(meta.split('#notify'))
769 | meta = clock.join(meta.split('#clock'))
770 | meta = emojize(meta)
771 | # Output
772 | printNicely('')
773 | printNicely(meta)
774 |
775 |
776 | def notify_list_member_added(e):
777 | """
778 | Notify a list_member_added event
779 | """
780 | # Retrieve info
781 | target = e['target']
782 | if target['screen_name'] != c['original_name']:
783 | return
784 | source = e['source']
785 | target_object = [e['target_object']] # list of Twitter list
786 | created_at = e['created_at']
787 | # Format
788 | source_user = cycle_color(source['name']) + \
789 | color_func(c['NOTIFICATION']['source_nick'])(
790 | ' @' + source['screen_name'])
791 | notify = color_func(c['NOTIFICATION']['notify'])(
792 | 'added you to a list')
793 | date = parser.parse(created_at)
794 | clock = fallback_humanize(date)
795 | clock = color_func(c['NOTIFICATION']['clock'])(clock)
796 | meta = c['NOTIFY_FORMAT']
797 | meta = source_user.join(meta.split('#source_user'))
798 | meta = notify.join(meta.split('#notify'))
799 | meta = clock.join(meta.split('#clock'))
800 | meta = emojize(meta)
801 | # Output
802 | printNicely('')
803 | printNicely(meta)
804 | print_list(target_object, noti=True)
805 |
806 |
807 | def notify_list_member_removed(e):
808 | """
809 | Notify a list_member_removed event
810 | """
811 | # Retrieve info
812 | target = e['target']
813 | if target['screen_name'] != c['original_name']:
814 | return
815 | source = e['source']
816 | target_object = [e['target_object']] # list of Twitter list
817 | created_at = e['created_at']
818 | # Format
819 | source_user = cycle_color(source['name']) + \
820 | color_func(c['NOTIFICATION']['source_nick'])(
821 | ' @' + source['screen_name'])
822 | notify = color_func(c['NOTIFICATION']['notify'])(
823 | 'removed you from a list')
824 | date = parser.parse(created_at)
825 | clock = fallback_humanize(date)
826 | clock = color_func(c['NOTIFICATION']['clock'])(clock)
827 | meta = c['NOTIFY_FORMAT']
828 | meta = source_user.join(meta.split('#source_user'))
829 | meta = notify.join(meta.split('#notify'))
830 | meta = clock.join(meta.split('#clock'))
831 | meta = emojize(meta)
832 | # Output
833 | printNicely('')
834 | printNicely(meta)
835 | print_list(target_object, noti=True)
836 |
837 |
838 | def notify_list_user_subscribed(e):
839 | """
840 | Notify a list_user_subscribed event
841 | """
842 | # Retrieve info
843 | target = e['target']
844 | if target['screen_name'] != c['original_name']:
845 | return
846 | source = e['source']
847 | target_object = [e['target_object']] # list of Twitter list
848 | created_at = e['created_at']
849 | # Format
850 | source_user = cycle_color(source['name']) + \
851 | color_func(c['NOTIFICATION']['source_nick'])(
852 | ' @' + source['screen_name'])
853 | notify = color_func(c['NOTIFICATION']['notify'])(
854 | 'subscribed to your list')
855 | date = parser.parse(created_at)
856 | clock = fallback_humanize(date)
857 | clock = color_func(c['NOTIFICATION']['clock'])(clock)
858 | meta = c['NOTIFY_FORMAT']
859 | meta = source_user.join(meta.split('#source_user'))
860 | meta = notify.join(meta.split('#notify'))
861 | meta = clock.join(meta.split('#clock'))
862 | meta = emojize(meta)
863 | # Output
864 | printNicely('')
865 | printNicely(meta)
866 | print_list(target_object, noti=True)
867 |
868 |
869 | def notify_list_user_unsubscribed(e):
870 | """
871 | Notify a list_user_unsubscribed event
872 | """
873 | # Retrieve info
874 | target = e['target']
875 | if target['screen_name'] != c['original_name']:
876 | return
877 | source = e['source']
878 | target_object = [e['target_object']] # list of Twitter list
879 | created_at = e['created_at']
880 | # Format
881 | source_user = cycle_color(source['name']) + \
882 | color_func(c['NOTIFICATION']['source_nick'])(
883 | ' @' + source['screen_name'])
884 | notify = color_func(c['NOTIFICATION']['notify'])(
885 | 'unsubscribed from your list')
886 | date = parser.parse(created_at)
887 | clock = fallback_humanize(date)
888 | clock = color_func(c['NOTIFICATION']['clock'])(clock)
889 | meta = c['NOTIFY_FORMAT']
890 | meta = source_user.join(meta.split('#source_user'))
891 | meta = notify.join(meta.split('#notify'))
892 | meta = clock.join(meta.split('#clock'))
893 | meta = emojize(meta)
894 | # Output
895 | printNicely('')
896 | printNicely(meta)
897 | print_list(target_object, noti=True)
898 |
899 |
900 | def print_event(e):
901 | """
902 | Notify an event
903 | """
904 | event_dict = {
905 | 'retweet': notify_retweet,
906 | 'favorite': notify_favorite,
907 | 'unfavorite': notify_unfavorite,
908 | 'follow': notify_follow,
909 | 'list_member_added': notify_list_member_added,
910 | 'list_member_removed': notify_list_member_removed,
911 | 'list_user_subscribed': notify_list_user_subscribed,
912 | 'list_user_unsubscribed': notify_list_user_unsubscribed,
913 | }
914 | event_dict.get(e['event'], lambda *args: None)(e)
915 |
916 |
917 | def show_profile(u):
918 | """
919 | Show a profile
920 | """
921 | # Retrieve info
922 | name = u['name']
923 | screen_name = u['screen_name']
924 | description = u['description']
925 | profile_image_url = u['profile_image_url']
926 | location = u['location']
927 | url = u['url']
928 | created_at = u['created_at']
929 | statuses_count = u['statuses_count']
930 | friends_count = u['friends_count']
931 | followers_count = u['followers_count']
932 |
933 | # Create content
934 | statuses_count = color_func(
935 | c['PROFILE']['statuses_count'])(
936 | str(statuses_count) +
937 | ' tweets')
938 | friends_count = color_func(
939 | c['PROFILE']['friends_count'])(
940 | str(friends_count) +
941 | ' following')
942 | followers_count = color_func(
943 | c['PROFILE']['followers_count'])(
944 | str(followers_count) +
945 | ' followers')
946 | count = statuses_count + ' ' + friends_count + ' ' + followers_count
947 | user = cycle_color(
948 | name) + color_func(c['PROFILE']['nick'])(' @' + screen_name + ' : ') + count
949 | profile_image_raw_url = 'Profile photo: ' + \
950 | color_func(c['PROFILE']['profile_image_url'])(profile_image_url)
951 | description = ''.join(
952 | lmap(lambda x: x + ' ' * 4 if x == '\n' else x, description))
953 | description = color_func(c['PROFILE']['description'])(description)
954 | location = 'Location : ' + color_func(c['PROFILE']['location'])(location)
955 | url = 'URL : ' + (color_func(c['PROFILE']['url'])(url) if url else '')
956 | date = parser.parse(created_at)
957 | clock = fallback_humanize(date)
958 | clock = 'Joined ' + color_func(c['PROFILE']['clock'])(clock)
959 |
960 | # Format
961 | line1 = u"{u:>{uw}}".format(
962 | u=user,
963 | uw=len(user) + 2,
964 | )
965 | line2 = u"{p:>{pw}}".format(
966 | p=profile_image_raw_url,
967 | pw=len(profile_image_raw_url) + 4,
968 | )
969 | line3 = u"{d:>{dw}}".format(
970 | d=description,
971 | dw=len(description) + 4,
972 | )
973 | line4 = u"{l:>{lw}}".format(
974 | l=location,
975 | lw=len(location) + 4,
976 | )
977 | line5 = u"{u:>{uw}}".format(
978 | u=url,
979 | uw=len(url) + 4,
980 | )
981 | line6 = u"{c:>{cw}}".format(
982 | c=clock,
983 | cw=len(clock) + 4,
984 | )
985 |
986 | # Display
987 | printNicely('')
988 | printNicely(line1)
989 | if c['IMAGE_ON_TERM']:
990 | try:
991 | response = requests.get(profile_image_url)
992 | image_to_display(BytesIO(response.content))
993 | except:
994 | pass
995 | else:
996 | printNicely(line2)
997 | for line in [line3, line4, line5, line6]:
998 | printNicely(line)
999 | printNicely('')
1000 |
1001 |
1002 | def print_trends(trends):
1003 | """
1004 | Display topics
1005 | """
1006 | for topic in trends[:c['TREND_MAX']]:
1007 | name = topic['name']
1008 | url = topic['url']
1009 | line = cycle_color(name) + ': ' + color_func(c['TREND']['url'])(url)
1010 | printNicely(line)
1011 | printNicely('')
1012 |
1013 |
1014 | def print_list(group, noti=False):
1015 | """
1016 | Display a list
1017 | """
1018 | for grp in group:
1019 | # Format
1020 | name = grp['full_name']
1021 | name = color_func(c['GROUP']['name'])(name + ' : ')
1022 | member = str(grp['member_count'])
1023 | member = color_func(c['GROUP']['member'])(member + ' member')
1024 | subscriber = str(grp['subscriber_count'])
1025 | subscriber = color_func(
1026 | c['GROUP']['subscriber'])(
1027 | subscriber +
1028 | ' subscriber')
1029 | description = grp['description'].strip()
1030 | description = color_func(c['GROUP']['description'])(description)
1031 | mode = grp['mode']
1032 | mode = color_func(c['GROUP']['mode'])('Type: ' + mode)
1033 | created_at = grp['created_at']
1034 | date = parser.parse(created_at)
1035 | clock = fallback_humanize(date)
1036 | clock = 'Created at ' + color_func(c['GROUP']['clock'])(clock)
1037 |
1038 | prefix = ' ' * 2
1039 | # Add spaces in begining of line if this is inside a notification
1040 | if noti:
1041 | prefix = ' ' * 2 + prefix
1042 | # Create lines
1043 | line1 = prefix + name + member + ' ' + subscriber
1044 | line2 = prefix + ' ' * 2 + description
1045 | line3 = prefix + ' ' * 2 + mode
1046 | line4 = prefix + ' ' * 2 + clock
1047 |
1048 | # Display
1049 | printNicely('')
1050 | printNicely(line1)
1051 | printNicely(line2)
1052 | printNicely(line3)
1053 | printNicely(line4)
1054 |
1055 | if not noti:
1056 | printNicely('')
1057 |
1058 |
1059 | def show_calendar(month, date, rel):
1060 | """
1061 | Show the calendar in rainbow mode
1062 | """
1063 | month = random_rainbow(month)
1064 | date = ' '.join([cycle_color(i) for i in date.split(' ')])
1065 | today = str(int(os.popen('date +\'%d\'').read().strip()))
1066 | # Display
1067 | printNicely(month)
1068 | printNicely(date)
1069 | for line in rel:
1070 | ary = line.split(' ')
1071 | ary = lmap(
1072 | lambda x: color_func(c['CAL']['today'])(x)
1073 | if x == today
1074 | else color_func(c['CAL']['days'])(x),
1075 | ary)
1076 | printNicely(' '.join(ary))
1077 |
1078 |
1079 | def format_quote(tweet):
1080 | """
1081 | Quoting format
1082 | """
1083 | # Retrieve info
1084 | screen_name = tweet['user']['screen_name']
1085 | text = get_full_text(tweet)
1086 | tid = str( tweet['id'] )
1087 |
1088 | # Validate quote format
1089 | if '#owner' not in c['QUOTE_FORMAT']:
1090 | printNicely(light_magenta('Quote should contains #owner'))
1091 | return False
1092 | if '#comment' not in c['QUOTE_FORMAT']:
1093 | printNicely(light_magenta('Quote format should have #comment'))
1094 | return False
1095 |
1096 | # Build formater
1097 | formater = ''
1098 | try:
1099 | formater = c['QUOTE_FORMAT']
1100 |
1101 | formater = formater.replace('#owner', screen_name)
1102 | formater = formater.replace('#tweet', text)
1103 | formater = formater.replace('#tid', tid)
1104 |
1105 | formater = emojize(formater)
1106 | except:
1107 | pass
1108 | # Highlight like a tweet
1109 | notice = formater.split()
1110 | notice = lmap(
1111 | lambda x: light_green(x)
1112 | if x == '#comment'
1113 | else x,
1114 | notice)
1115 | notice = lmap(
1116 | lambda x: color_func(c['TWEET']['rt'])(x)
1117 | if x == 'RT'
1118 | else x,
1119 | notice)
1120 | notice = lmap(lambda x: cycle_color(x) if x[0] == '@' else x, notice)
1121 | notice = lmap(
1122 | lambda x: color_func(c['TWEET']['link'])(x)
1123 | if x[0:4] == 'http'
1124 | else x,
1125 | notice)
1126 | notice = lmap(
1127 | lambda x: color_func(c['TWEET']['hashtag'])(x)
1128 | if x.startswith('#')
1129 | else x,
1130 | notice)
1131 | notice = ' '.join(notice)
1132 | # Notice
1133 | notice = light_magenta('Quoting: "') + notice + light_magenta('"')
1134 | printNicely(notice)
1135 | return formater
1136 |
1137 |
1138 | # Start the color cycle
1139 | start_cycle()
1140 |
--------------------------------------------------------------------------------
/rainbowstream/emoji.py:
--------------------------------------------------------------------------------
1 | # This file is based on emoji (https:://github.com/kyokomi/emoji).
2 | # and (https://github.com/carpedm20/emoji)
3 | #
4 | # The MIT License (MIT)
5 | #
6 | # Copyright (c) 2014 kyokomi
7 | #
8 | # Permission is hereby granted, free of charge, to any person obtaining a copy
9 | # of this software and associated documentation files (the "Software"), to deal
10 | # in the Software without restriction, including without limitation the rights
11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | # copies of the Software, and to permit persons to whom the Software is
13 | # furnished to do so, subject to the following conditions::
14 | #
15 | # The above copyright notice and this permission notice shall be included in all
16 | # copies or substantial portions of the Software.
17 | #
18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | # SOFTWARE.
25 |
26 | import re
27 |
28 | emojiCodeDict = {
29 | "::capricorn::": u"\U00002651",
30 | "::end::": u"\U0001f51a",
31 | "::no_mobile_phones::": u"\U0001f4f5",
32 | "::couple::": u"\U0001f46b",
33 | "::snowman::": u"\U000026c4",
34 | "::sunrise_over_mountains::": u"\U0001f304",
35 | "::suspension_railway::": u"\U0001f69f",
36 | "::arrows_counterclockwise::": u"\U0001f504",
37 | "::bug::": u"\U0001f41b",
38 | "::confused::": u"\U0001f615",
39 | "::dress::": u"\U0001f457",
40 | "::honeybee::": u"\U0001f41d",
41 | "::waning_crescent_moon::": u"\U0001f318",
42 | "::balloon::": u"\U0001f388",
43 | "::bus::": u"\U0001f68c",
44 | "::package::": u"\U0001f4e6",
45 | "::pencil2::": u"\U0000270f",
46 | "::rage::": u"\U0001f621",
47 | "::space_invader::": u"\U0001f47e",
48 | "::white_medium_small_square::": u"\U000025fd",
49 | "::fast_forward::": u"\U000023e9",
50 | "::rice_cracker::": u"\U0001f358",
51 | "::incoming_envelope::": u"\U0001f4e8",
52 | "::sa::": u"\U0001f202",
53 | "::womens::": u"\U0001f6ba",
54 | "::arrow_right::": u"\U000027a1",
55 | "::construction_worker::": u"\U0001f477",
56 | "::notes::": u"\U0001f3b6",
57 | "::goat::": u"\U0001f410",
58 | "::grey_question::": u"\U00002754",
59 | "::lantern::": u"\U0001f3ee",
60 | "::rice_scene::": u"\U0001f391",
61 | "::running::": u"\U0001f3c3",
62 | "::ferris_wheel::": u"\U0001f3a1",
63 | "::musical_score::": u"\U0001f3bc",
64 | "::sparkle::": u"\U00002747",
65 | "::wink::": u"\U0001f609",
66 | "::art::": u"\U0001f3a8",
67 | "::clock330::": u"\U0001f55e",
68 | "::minidisc::": u"\U0001f4bd",
69 | "::no_entry_sign::": u"\U0001f6ab",
70 | "::wind_chime::": u"\U0001f390",
71 | "::cyclone::": u"\U0001f300",
72 | "::herb::": u"\U0001f33f",
73 | "::leopard::": u"\U0001f406",
74 | "::banana::": u"\U0001f34c",
75 | "::handbag::": u"\U0001f45c",
76 | "::honey_pot::": u"\U0001f36f",
77 | "::ok::": u"\U0001f197",
78 | "::hearts::": u"\U00002665",
79 | "::passport_control::": u"\U0001f6c2",
80 | "::moyai::": u"\U0001f5ff",
81 | "::smile::": u"\U0001f604",
82 | "::tiger2::": u"\U0001f405",
83 | "::twisted_rightwards_arrows::": u"\U0001f500",
84 | "::children_crossing::": u"\U0001f6b8",
85 | "::cow::": u"\U0001f42e",
86 | "::point_up::": u"\U0000261d",
87 | "::house::": u"\U0001f3e0",
88 | "::man_with_turban::": u"\U0001f473",
89 | "::mountain_railway::": u"\U0001f69e",
90 | "::vibration_mode::": u"\U0001f4f3",
91 | "::blowfish::": u"\U0001f421",
92 | "::it::": u"\U0001f1ee\U0001f1f9",
93 | "::oden::": u"\U0001f362",
94 | "::clock3::": u"\U0001f552",
95 | "::lollipop::": u"\U0001f36d",
96 | "::train::": u"\U0001f68b",
97 | "::scissors::": u"\U00002702",
98 | "::triangular_ruler::": u"\U0001f4d0",
99 | "::wedding::": u"\U0001f492",
100 | "::flashlight::": u"\U0001f526",
101 | "::secret::": u"\U00003299",
102 | "::sushi::": u"\U0001f363",
103 | "::blue_car::": u"\U0001f699",
104 | "::cd::": u"\U0001f4bf",
105 | "::milky_way::": u"\U0001f30c",
106 | "::mortar_board::": u"\U0001f393",
107 | "::crown::": u"\U0001f451",
108 | "::speech_balloon::": u"\U0001f4ac",
109 | "::bento::": u"\U0001f371",
110 | "::grey_exclamation::": u"\U00002755",
111 | "::hotel::": u"\U0001f3e8",
112 | "::keycap_ten::": u"\U0001f51f",
113 | "::newspaper::": u"\U0001f4f0",
114 | "::outbox_tray::": u"\U0001f4e4",
115 | "::racehorse::": u"\U0001f40e",
116 | "::laughing::": u"\U0001f606",
117 | "::black_large_square::": u"\U00002b1b",
118 | "::books::": u"\U0001f4da",
119 | "::eight_spoked_asterisk::": u"\U00002733",
120 | "::heavy_check_mark::": u"\U00002714",
121 | "::m::": u"\U000024c2",
122 | "::wave::": u"\U0001f44b",
123 | "::bicyclist::": u"\U0001f6b4",
124 | "::cocktail::": u"\U0001f378",
125 | "::european_castle::": u"\U0001f3f0",
126 | "::point_down::": u"\U0001f447",
127 | "::tokyo_tower::": u"\U0001f5fc",
128 | "::battery::": u"\U0001f50b",
129 | "::dancer::": u"\U0001f483",
130 | "::repeat::": u"\U0001f501",
131 | "::ru::": u"\U0001f1f7\U0001f1fa",
132 | "::new_moon::": u"\U0001f311",
133 | "::church::": u"\U000026ea",
134 | "::date::": u"\U0001f4c5",
135 | "::earth_americas::": u"\U0001f30e",
136 | "::footprints::": u"\U0001f463",
137 | "::libra::": u"\U0000264e",
138 | "::mountain_cableway::": u"\U0001f6a0",
139 | "::small_red_triangle_down::": u"\U0001f53b",
140 | "::top::": u"\U0001f51d",
141 | "::sunglasses::": u"\U0001f60e",
142 | "::abcd::": u"\U0001f521",
143 | "::cl::": u"\U0001f191",
144 | "::ski::": u"\U0001f3bf",
145 | "::book::": u"\U0001f4d6",
146 | "::hourglass_flowing_sand::": u"\U000023f3",
147 | "::stuck_out_tongue_closed_eyes::": u"\U0001f61d",
148 | "::cold_sweat::": u"\U0001f630",
149 | "::headphones::": u"\U0001f3a7",
150 | "::confetti_ball::": u"\U0001f38a",
151 | "::gemini::": u"\U0000264a",
152 | "::new::": u"\U0001f195",
153 | "::pray::": u"\U0001f64f",
154 | "::watch::": u"\U0000231a",
155 | "::coffee::": u"\U00002615",
156 | "::ghost::": u"\U0001f47b",
157 | "::on::": u"\U0001f51b",
158 | "::pouch::": u"\U0001f45d",
159 | "::taxi::": u"\U0001f695",
160 | "::hocho::": u"\U0001f52a",
161 | "::yum::": u"\U0001f60b",
162 | "::heavy_plus_sign::": u"\U00002795",
163 | "::tada::": u"\U0001f389",
164 | "::arrow_heading_down::": u"\U00002935",
165 | "::clock530::": u"\U0001f560",
166 | "::poultry_leg::": u"\U0001f357",
167 | "::elephant::": u"\U0001f418",
168 | "::gb::": u"\U0001f1ec\U0001f1e7",
169 | "::mahjong::": u"\U0001f004",
170 | "::rice::": u"\U0001f35a",
171 | "::musical_note::": u"\U0001f3b5",
172 | "::beginner::": u"\U0001f530",
173 | "::small_red_triangle::": u"\U0001f53a",
174 | "::tomato::": u"\U0001f345",
175 | "::clock1130::": u"\U0001f566",
176 | "::japanese_castle::": u"\U0001f3ef",
177 | "::sun_with_face::": u"\U0001f31e",
178 | "::four::": u"\U00000034\U000020e3",
179 | "::microphone::": u"\U0001f3a4",
180 | "::tennis::": u"\U0001f3be",
181 | "::arrow_up_down::": u"\U00002195",
182 | "::cn::": u"\U0001f1e8\U0001f1f3",
183 | "::horse_racing::": u"\U0001f3c7",
184 | "::no_bicycles::": u"\U0001f6b3",
185 | "::snail::": u"\U0001f40c",
186 | "::free::": u"\U0001f193",
187 | "::beetle::": u"\U0001f41e",
188 | "::black_small_square::": u"\U000025aa",
189 | "::file_folder::": u"\U0001f4c1",
190 | "::hushed::": u"\U0001f62f",
191 | "::skull::": u"\U0001f480",
192 | "::ab::": u"\U0001f18e",
193 | "::rocket::": u"\U0001f680",
194 | "::sweet_potato::": u"\U0001f360",
195 | "::guitar::": u"\U0001f3b8",
196 | "::poodle::": u"\U0001f429",
197 | "::tulip::": u"\U0001f337",
198 | "::large_orange_diamond::": u"\U0001f536",
199 | "::-1::": u"\U0001f44e",
200 | "::chart_with_upwards_trend::": u"\U0001f4c8",
201 | "::de::": u"\U0001f1e9\U0001f1ea",
202 | "::grapes::": u"\U0001f347",
203 | "::ideograph_advantage::": u"\U0001f250",
204 | "::japanese_ogre::": u"\U0001f479",
205 | "::telephone::": u"\U0000260e",
206 | "::clock230::": u"\U0001f55d",
207 | "::hourglass::": u"\U0000231b",
208 | "::leftwards_arrow_with_hook::": u"\U000021a9",
209 | "::sparkler::": u"\U0001f387",
210 | "::black_joker::": u"\U0001f0cf",
211 | "::clock730::": u"\U0001f562",
212 | "::first_quarter_moon_with_face::": u"\U0001f31b",
213 | "::man::": u"\U0001f468",
214 | "::clock4::": u"\U0001f553",
215 | "::fishing_pole_and_fish::": u"\U0001f3a3",
216 | "::tophat::": u"\U0001f3a9",
217 | "::white_medium_square::": u"\U000025fb",
218 | "::mega::": u"\U0001f4e3",
219 | "::spaghetti::": u"\U0001f35d",
220 | "::dart::": u"\U0001f3af",
221 | "::girl::": u"\U0001f467",
222 | "::womans_hat::": u"\U0001f452",
223 | "::bullettrain_front::": u"\U0001f685",
224 | "::department_store::": u"\U0001f3ec",
225 | "::heartbeat::": u"\U0001f493",
226 | "::palm_tree::": u"\U0001f334",
227 | "::swimmer::": u"\U0001f3ca",
228 | "::yellow_heart::": u"\U0001f49b",
229 | "::arrow_upper_right::": u"\U00002197",
230 | "::clock2::": u"\U0001f551",
231 | "::high_heel::": u"\U0001f460",
232 | "::arrow_double_up::": u"\U000023eb",
233 | "::cry::": u"\U0001f622",
234 | "::dvd::": u"\U0001f4c0",
235 | "::e-mail::": u"\U0001f4e7",
236 | "::baby_bottle::": u"\U0001f37c",
237 | "::cool::": u"\U0001f192",
238 | "::floppy_disk::": u"\U0001f4be",
239 | "::iphone::": u"\U0001f4f1",
240 | "::minibus::": u"\U0001f690",
241 | "::rooster::": u"\U0001f413",
242 | "::three::": u"\U00000033\U000020e3",
243 | "::white_small_square::": u"\U000025ab",
244 | "::cancer::": u"\U0000264b",
245 | "::question::": u"\U00002753",
246 | "::sake::": u"\U0001f376",
247 | "::birthday::": u"\U0001f382",
248 | "::dog2::": u"\U0001f415",
249 | "::loudspeaker::": u"\U0001f4e2",
250 | "::arrow_up_small::": u"\U0001f53c",
251 | "::camel::": u"\U0001f42b",
252 | "::koala::": u"\U0001f428",
253 | "::mag_right::": u"\U0001f50e",
254 | "::soccer::": u"\U000026bd",
255 | "::bike::": u"\U0001f6b2",
256 | "::ear_of_rice::": u"\U0001f33e",
257 | "::shit::": u"\U0001f4a9",
258 | "::u7981::": u"\U0001f232",
259 | "::bath::": u"\U0001f6c0",
260 | "::baby::": u"\U0001f476",
261 | "::lock_with_ink_pen::": u"\U0001f50f",
262 | "::necktie::": u"\U0001f454",
263 | "::bikini::": u"\U0001f459",
264 | "::blush::": u"\U0001f60a",
265 | "::heartpulse::": u"\U0001f497",
266 | "::pig_nose::": u"\U0001f43d",
267 | "::straight_ruler::": u"\U0001f4cf",
268 | "::u6e80::": u"\U0001f235",
269 | "::gift::": u"\U0001f381",
270 | "::traffic_light::": u"\U0001f6a5",
271 | "::hibiscus::": u"\U0001f33a",
272 | "::couple_with_heart::": u"\U0001f491",
273 | "::pushpin::": u"\U0001f4cc",
274 | "::u6709::": u"\U0001f236",
275 | "::walking::": u"\U0001f6b6",
276 | "::grinning::": u"\U0001f600",
277 | "::hash::": u"\U00000023\U000020e3",
278 | "::radio_button::": u"\U0001f518",
279 | "::raised_hand::": u"\U0000270b",
280 | "::shaved_ice::": u"\U0001f367",
281 | "::barber::": u"\U0001f488",
282 | "::cat::": u"\U0001f431",
283 | "::heavy_exclamation_mark::": u"\U00002757",
284 | "::ice_cream::": u"\U0001f368",
285 | "::mask::": u"\U0001f637",
286 | "::pig2::": u"\U0001f416",
287 | "::triangular_flag_on_post::": u"\U0001f6a9",
288 | "::arrow_upper_left::": u"\U00002196",
289 | "::bee::": u"\U0001f41d",
290 | "::beer::": u"\U0001f37a",
291 | "::black_nib::": u"\U00002712",
292 | "::exclamation::": u"\U00002757",
293 | "::dog::": u"\U0001f436",
294 | "::fire::": u"\U0001f525",
295 | "::ant::": u"\U0001f41c",
296 | "::broken_heart::": u"\U0001f494",
297 | "::chart::": u"\U0001f4b9",
298 | "::clock1::": u"\U0001f550",
299 | "::bomb::": u"\U0001f4a3",
300 | "::virgo::": u"\U0000264d",
301 | "::a::": u"\U0001f170",
302 | "::fork_and_knife::": u"\U0001f374",
303 | "::copyright::": u"\U000000a9",
304 | "::curly_loop::": u"\U000027b0",
305 | "::full_moon::": u"\U0001f315",
306 | "::shoe::": u"\U0001f45e",
307 | "::european_post_office::": u"\U0001f3e4",
308 | "::ng::": u"\U0001f196",
309 | "::office::": u"\U0001f3e2",
310 | "::raising_hand::": u"\U0001f64b",
311 | "::revolving_hearts::": u"\U0001f49e",
312 | "::aquarius::": u"\U00002652",
313 | "::electric_plug::": u"\U0001f50c",
314 | "::meat_on_bone::": u"\U0001f356",
315 | "::mens::": u"\U0001f6b9",
316 | "::briefcase::": u"\U0001f4bc",
317 | "::ship::": u"\U0001f6a2",
318 | "::anchor::": u"\U00002693",
319 | "::ballot_box_with_check::": u"\U00002611",
320 | "::bear::": u"\U0001f43b",
321 | "::beers::": u"\U0001f37b",
322 | "::dromedary_camel::": u"\U0001f42a",
323 | "::nut_and_bolt::": u"\U0001f529",
324 | "::construction::": u"\U0001f6a7",
325 | "::golf::": u"\U000026f3",
326 | "::toilet::": u"\U0001f6bd",
327 | "::blue_book::": u"\U0001f4d8",
328 | "::boom::": u"\U0001f4a5",
329 | "::deciduous_tree::": u"\U0001f333",
330 | "::kissing_closed_eyes::": u"\U0001f61a",
331 | "::smiley_cat::": u"\U0001f63a",
332 | "::fuelpump::": u"\U000026fd",
333 | "::kiss::": u"\U0001f48b",
334 | "::clock10::": u"\U0001f559",
335 | "::sheep::": u"\U0001f411",
336 | "::white_flower::": u"\U0001f4ae",
337 | "::boar::": u"\U0001f417",
338 | "::currency_exchange::": u"\U0001f4b1",
339 | "::facepunch::": u"\U0001f44a",
340 | "::flower_playing_cards::": u"\U0001f3b4",
341 | "::person_frowning::": u"\U0001f64d",
342 | "::poop::": u"\U0001f4a9",
343 | "::satisfied::": u"\U0001f606",
344 | "::8ball::": u"\U0001f3b1",
345 | "::disappointed_relieved::": u"\U0001f625",
346 | "::panda_face::": u"\U0001f43c",
347 | "::ticket::": u"\U0001f3ab",
348 | "::us::": u"\U0001f1fa\U0001f1f8",
349 | "::waxing_crescent_moon::": u"\U0001f312",
350 | "::dragon::": u"\U0001f409",
351 | "::gun::": u"\U0001f52b",
352 | "::mount_fuji::": u"\U0001f5fb",
353 | "::new_moon_with_face::": u"\U0001f31a",
354 | "::star2::": u"\U0001f31f",
355 | "::grimacing::": u"\U0001f62c",
356 | "::confounded::": u"\U0001f616",
357 | "::congratulations::": u"\U00003297",
358 | "::custard::": u"\U0001f36e",
359 | "::frowning::": u"\U0001f626",
360 | "::maple_leaf::": u"\U0001f341",
361 | "::police_car::": u"\U0001f693",
362 | "::cloud::": u"\U00002601",
363 | "::jeans::": u"\U0001f456",
364 | "::fish::": u"\U0001f41f",
365 | "::wavy_dash::": u"\U00003030",
366 | "::clock5::": u"\U0001f554",
367 | "::santa::": u"\U0001f385",
368 | "::japan::": u"\U0001f5fe",
369 | "::oncoming_taxi::": u"\U0001f696",
370 | "::whale::": u"\U0001f433",
371 | "::arrow_forward::": u"\U000025b6",
372 | "::kissing_heart::": u"\U0001f618",
373 | "::bullettrain_side::": u"\U0001f684",
374 | "::fearful::": u"\U0001f628",
375 | "::moneybag::": u"\U0001f4b0",
376 | "::runner::": u"\U0001f3c3",
377 | "::mailbox::": u"\U0001f4eb",
378 | "::sandal::": u"\U0001f461",
379 | "::zzz::": u"\U0001f4a4",
380 | "::apple::": u"\U0001f34e",
381 | "::arrow_heading_up::": u"\U00002934",
382 | "::family::": u"\U0001f46a",
383 | "::heavy_minus_sign::": u"\U00002796",
384 | "::saxophone::": u"\U0001f3b7",
385 | "::u5272::": u"\U0001f239",
386 | "::black_square_button::": u"\U0001f532",
387 | "::bouquet::": u"\U0001f490",
388 | "::love_letter::": u"\U0001f48c",
389 | "::metro::": u"\U0001f687",
390 | "::small_blue_diamond::": u"\U0001f539",
391 | "::thought_balloon::": u"\U0001f4ad",
392 | "::arrow_up::": u"\U00002b06",
393 | "::no_pedestrians::": u"\U0001f6b7",
394 | "::smirk::": u"\U0001f60f",
395 | "::blue_heart::": u"\U0001f499",
396 | "::large_blue_diamond::": u"\U0001f537",
397 | "::vs::": u"\U0001f19a",
398 | "::v::": u"\U0000270c",
399 | "::wheelchair::": u"\U0000267f",
400 | "::couplekiss::": u"\U0001f48f",
401 | "::tent::": u"\U000026fa",
402 | "::purple_heart::": u"\U0001f49c",
403 | "::relaxed::": u"\U0000263a",
404 | "::accept::": u"\U0001f251",
405 | "::green_heart::": u"\U0001f49a",
406 | "::pouting_cat::": u"\U0001f63e",
407 | "::tram::": u"\U0001f68a",
408 | "::bangbang::": u"\U0000203c",
409 | "::collision::": u"\U0001f4a5",
410 | "::convenience_store::": u"\U0001f3ea",
411 | "::person_with_blond_hair::": u"\U0001f471",
412 | "::uk::": u"\U0001f1ec\U0001f1e7",
413 | "::peach::": u"\U0001f351",
414 | "::tired_face::": u"\U0001f62b",
415 | "::bread::": u"\U0001f35e",
416 | "::mailbox_closed::": u"\U0001f4ea",
417 | "::open_mouth::": u"\U0001f62e",
418 | "::pig::": u"\U0001f437",
419 | "::put_litter_in_its_place::": u"\U0001f6ae",
420 | "::u7a7a::": u"\U0001f233",
421 | "::bulb::": u"\U0001f4a1",
422 | "::clock9::": u"\U0001f558",
423 | "::envelope_with_arrow::": u"\U0001f4e9",
424 | "::pisces::": u"\U00002653",
425 | "::baggage_claim::": u"\U0001f6c4",
426 | "::egg::": u"\U0001f373",
427 | "::sweat_smile::": u"\U0001f605",
428 | "::boat::": u"\U000026f5",
429 | "::fr::": u"\U0001f1eb\U0001f1f7",
430 | "::heavy_division_sign::": u"\U00002797",
431 | "::muscle::": u"\U0001f4aa",
432 | "::paw_prints::": u"\U0001f43e",
433 | "::arrow_left::": u"\U00002b05",
434 | "::black_circle::": u"\U000026ab",
435 | "::kissing_smiling_eyes::": u"\U0001f619",
436 | "::star::": u"\U00002b50",
437 | "::steam_locomotive::": u"\U0001f682",
438 | "::1234::": u"\U0001f522",
439 | "::clock130::": u"\U0001f55c",
440 | "::kr::": u"\U0001f1f0\U0001f1f7",
441 | "::monorail::": u"\U0001f69d",
442 | "::school::": u"\U0001f3eb",
443 | "::seven::": u"\U00000037\U000020e3",
444 | "::baby_chick::": u"\U0001f424",
445 | "::bridge_at_night::": u"\U0001f309",
446 | "::hotsprings::": u"\U00002668",
447 | "::rose::": u"\U0001f339",
448 | "::love_hotel::": u"\U0001f3e9",
449 | "::princess::": u"\U0001f478",
450 | "::ramen::": u"\U0001f35c",
451 | "::scroll::": u"\U0001f4dc",
452 | "::tropical_fish::": u"\U0001f420",
453 | "::heart_eyes_cat::": u"\U0001f63b",
454 | "::information_desk_person::": u"\U0001f481",
455 | "::mouse::": u"\U0001f42d",
456 | "::no_smoking::": u"\U0001f6ad",
457 | "::post_office::": u"\U0001f3e3",
458 | "::stars::": u"\U0001f320",
459 | "::arrow_double_down::": u"\U000023ec",
460 | "::unlock::": u"\U0001f513",
461 | "::arrow_backward::": u"\U000025c0",
462 | "::hand::": u"\U0000270b",
463 | "::hospital::": u"\U0001f3e5",
464 | "::ocean::": u"\U0001f30a",
465 | "::mountain_bicyclist::": u"\U0001f6b5",
466 | "::octopus::": u"\U0001f419",
467 | "::sos::": u"\U0001f198",
468 | "::dizzy_face::": u"\U0001f635",
469 | "::tongue::": u"\U0001f445",
470 | "::train2::": u"\U0001f686",
471 | "::checkered_flag::": u"\U0001f3c1",
472 | "::orange_book::": u"\U0001f4d9",
473 | "::sound::": u"\U0001f509",
474 | "::aerial_tramway::": u"\U0001f6a1",
475 | "::bell::": u"\U0001f514",
476 | "::dragon_face::": u"\U0001f432",
477 | "::flipper::": u"\U0001f42c",
478 | "::ok_woman::": u"\U0001f646",
479 | "::performing_arts::": u"\U0001f3ad",
480 | "::postal_horn::": u"\U0001f4ef",
481 | "::clock1030::": u"\U0001f565",
482 | "::email::": u"\U00002709",
483 | "::green_book::": u"\U0001f4d7",
484 | "::point_up_2::": u"\U0001f446",
485 | "::high_brightness::": u"\U0001f506",
486 | "::running_shirt_with_sash::": u"\U0001f3bd",
487 | "::bookmark::": u"\U0001f516",
488 | "::sob::": u"\U0001f62d",
489 | "::arrow_lower_right::": u"\U00002198",
490 | "::point_left::": u"\U0001f448",
491 | "::purse::": u"\U0001f45b",
492 | "::sparkles::": u"\U00002728",
493 | "::black_medium_small_square::": u"\U000025fe",
494 | "::pound::": u"\U0001f4b7",
495 | "::rabbit::": u"\U0001f430",
496 | "::woman::": u"\U0001f469",
497 | "::negative_squared_cross_mark::": u"\U0000274e",
498 | "::open_book::": u"\U0001f4d6",
499 | "::smiling_imp::": u"\U0001f608",
500 | "::spades::": u"\U00002660",
501 | "::baseball::": u"\U000026be",
502 | "::fountain::": u"\U000026f2",
503 | "::joy::": u"\U0001f602",
504 | "::lipstick::": u"\U0001f484",
505 | "::partly_sunny::": u"\U000026c5",
506 | "::ram::": u"\U0001f40f",
507 | "::red_circle::": u"\U0001f534",
508 | "::cop::": u"\U0001f46e",
509 | "::green_apple::": u"\U0001f34f",
510 | "::registered::": u"\U000000ae",
511 | "::+1::": u"\U0001f44d",
512 | "::crying_cat_face::": u"\U0001f63f",
513 | "::innocent::": u"\U0001f607",
514 | "::mobile_phone_off::": u"\U0001f4f4",
515 | "::underage::": u"\U0001f51e",
516 | "::dolphin::": u"\U0001f42c",
517 | "::busts_in_silhouette::": u"\U0001f465",
518 | "::umbrella::": u"\U00002614",
519 | "::angel::": u"\U0001f47c",
520 | "::small_orange_diamond::": u"\U0001f538",
521 | "::sunflower::": u"\U0001f33b",
522 | "::link::": u"\U0001f517",
523 | "::notebook::": u"\U0001f4d3",
524 | "::oncoming_bus::": u"\U0001f68d",
525 | "::bookmark_tabs::": u"\U0001f4d1",
526 | "::calendar::": u"\U0001f4c6",
527 | "::izakaya_lantern::": u"\U0001f3ee",
528 | "::mans_shoe::": u"\U0001f45e",
529 | "::name_badge::": u"\U0001f4db",
530 | "::closed_lock_with_key::": u"\U0001f510",
531 | "::fist::": u"\U0000270a",
532 | "::id::": u"\U0001f194",
533 | "::ambulance::": u"\U0001f691",
534 | "::musical_keyboard::": u"\U0001f3b9",
535 | "::ribbon::": u"\U0001f380",
536 | "::seedling::": u"\U0001f331",
537 | "::tv::": u"\U0001f4fa",
538 | "::football::": u"\U0001f3c8",
539 | "::nail_care::": u"\U0001f485",
540 | "::seat::": u"\U0001f4ba",
541 | "::alarm_clock::": u"\U000023f0",
542 | "::money_with_wings::": u"\U0001f4b8",
543 | "::relieved::": u"\U0001f60c",
544 | "::womans_clothes::": u"\U0001f45a",
545 | "::lips::": u"\U0001f444",
546 | "::clubs::": u"\U00002663",
547 | "::house_with_garden::": u"\U0001f3e1",
548 | "::sunrise::": u"\U0001f305",
549 | "::monkey::": u"\U0001f412",
550 | "::six::": u"\U00000036\U000020e3",
551 | "::smiley::": u"\U0001f603",
552 | "::feet::": u"\U0001f43e",
553 | "::waning_gibbous_moon::": u"\U0001f316",
554 | "::yen::": u"\U0001f4b4",
555 | "::baby_symbol::": u"\U0001f6bc",
556 | "::signal_strength::": u"\U0001f4f6",
557 | "::boy::": u"\U0001f466",
558 | "::busstop::": u"\U0001f68f",
559 | "::computer::": u"\U0001f4bb",
560 | "::night_with_stars::": u"\U0001f303",
561 | "::older_woman::": u"\U0001f475",
562 | "::parking::": u"\U0001f17f",
563 | "::trumpet::": u"\U0001f3ba",
564 | "::100::": u"\U0001f4af",
565 | "::sweat_drops::": u"\U0001f4a6",
566 | "::wc::": u"\U0001f6be",
567 | "::b::": u"\U0001f171",
568 | "::cupid::": u"\U0001f498",
569 | "::five::": u"\U00000035\U000020e3",
570 | "::part_alternation_mark::": u"\U0000303d",
571 | "::snowboarder::": u"\U0001f3c2",
572 | "::warning::": u"\U000026a0",
573 | "::white_large_square::": u"\U00002b1c",
574 | "::zap::": u"\U000026a1",
575 | "::arrow_down_small::": u"\U0001f53d",
576 | "::clock430::": u"\U0001f55f",
577 | "::expressionless::": u"\U0001f611",
578 | "::phone::": u"\U0000260e",
579 | "::roller_coaster::": u"\U0001f3a2",
580 | "::lemon::": u"\U0001f34b",
581 | "::one::": u"\U00000031\U000020e3",
582 | "::christmas_tree::": u"\U0001f384",
583 | "::hankey::": u"\U0001f4a9",
584 | "::hatched_chick::": u"\U0001f425",
585 | "::u7533::": u"\U0001f238",
586 | "::large_blue_circle::": u"\U0001f535",
587 | "::up::": u"\U0001f199",
588 | "::wine_glass::": u"\U0001f377",
589 | "::x::": u"\U0000274c",
590 | "::nose::": u"\U0001f443",
591 | "::rewind::": u"\U000023ea",
592 | "::two_hearts::": u"\U0001f495",
593 | "::envelope::": u"\U00002709",
594 | "::oncoming_automobile::": u"\U0001f698",
595 | "::ophiuchus::": u"\U000026ce",
596 | "::ring::": u"\U0001f48d",
597 | "::tropical_drink::": u"\U0001f379",
598 | "::turtle::": u"\U0001f422",
599 | "::crescent_moon::": u"\U0001f319",
600 | "::koko::": u"\U0001f201",
601 | "::microscope::": u"\U0001f52c",
602 | "::rugby_football::": u"\U0001f3c9",
603 | "::smoking::": u"\U0001f6ac",
604 | "::anger::": u"\U0001f4a2",
605 | "::aries::": u"\U00002648",
606 | "::city_sunset::": u"\U0001f306",
607 | "::clock1230::": u"\U0001f567",
608 | "::mailbox_with_no_mail::": u"\U0001f4ed",
609 | "::movie_camera::": u"\U0001f3a5",
610 | "::pager::": u"\U0001f4df",
611 | "::zero::": u"\U00000030\U000020e3",
612 | "::bank::": u"\U0001f3e6",
613 | "::eight_pointed_black_star::": u"\U00002734",
614 | "::knife::": u"\U0001f52a",
615 | "::u7121::": u"\U0001f21a",
616 | "::customs::": u"\U0001f6c3",
617 | "::melon::": u"\U0001f348",
618 | "::rowboat::": u"\U0001f6a3",
619 | "::corn::": u"\U0001f33d",
620 | "::eggplant::": u"\U0001f346",
621 | "::heart_decoration::": u"\U0001f49f",
622 | "::rotating_light::": u"\U0001f6a8",
623 | "::round_pushpin::": u"\U0001f4cd",
624 | "::cat2::": u"\U0001f408",
625 | "::chocolate_bar::": u"\U0001f36b",
626 | "::no_bell::": u"\U0001f515",
627 | "::radio::": u"\U0001f4fb",
628 | "::droplet::": u"\U0001f4a7",
629 | "::hamburger::": u"\U0001f354",
630 | "::fire_engine::": u"\U0001f692",
631 | "::heart::": u"\U00002764",
632 | "::potable_water::": u"\U0001f6b0",
633 | "::telephone_receiver::": u"\U0001f4de",
634 | "::dash::": u"\U0001f4a8",
635 | "::globe_with_meridians::": u"\U0001f310",
636 | "::guardsman::": u"\U0001f482",
637 | "::heavy_multiplication_x::": u"\U00002716",
638 | "::chart_with_downwards_trend::": u"\U0001f4c9",
639 | "::imp::": u"\U0001f47f",
640 | "::earth_asia::": u"\U0001f30f",
641 | "::mouse2::": u"\U0001f401",
642 | "::notebook_with_decorative_cover::": u"\U0001f4d4",
643 | "::telescope::": u"\U0001f52d",
644 | "::trolleybus::": u"\U0001f68e",
645 | "::card_index::": u"\U0001f4c7",
646 | "::euro::": u"\U0001f4b6",
647 | "::dollar::": u"\U0001f4b5",
648 | "::fax::": u"\U0001f4e0",
649 | "::mailbox_with_mail::": u"\U0001f4ec",
650 | "::raised_hands::": u"\U0001f64c",
651 | "::disappointed::": u"\U0001f61e",
652 | "::foggy::": u"\U0001f301",
653 | "::person_with_pouting_face::": u"\U0001f64e",
654 | "::statue_of_liberty::": u"\U0001f5fd",
655 | "::dolls::": u"\U0001f38e",
656 | "::light_rail::": u"\U0001f688",
657 | "::pencil::": u"\U0001f4dd",
658 | "::speak_no_evil::": u"\U0001f64a",
659 | "::calling::": u"\U0001f4f2",
660 | "::clock830::": u"\U0001f563",
661 | "::cow2::": u"\U0001f404",
662 | "::hear_no_evil::": u"\U0001f649",
663 | "::scream_cat::": u"\U0001f640",
664 | "::smile_cat::": u"\U0001f638",
665 | "::tractor::": u"\U0001f69c",
666 | "::clock11::": u"\U0001f55a",
667 | "::doughnut::": u"\U0001f369",
668 | "::hammer::": u"\U0001f528",
669 | "::loop::": u"\U000027bf",
670 | "::moon::": u"\U0001f314",
671 | "::soon::": u"\U0001f51c",
672 | "::cinema::": u"\U0001f3a6",
673 | "::factory::": u"\U0001f3ed",
674 | "::flushed::": u"\U0001f633",
675 | "::mute::": u"\U0001f507",
676 | "::neutral_face::": u"\U0001f610",
677 | "::scorpius::": u"\U0000264f",
678 | "::wolf::": u"\U0001f43a",
679 | "::clapper::": u"\U0001f3ac",
680 | "::joy_cat::": u"\U0001f639",
681 | "::pensive::": u"\U0001f614",
682 | "::sleeping::": u"\U0001f634",
683 | "::credit_card::": u"\U0001f4b3",
684 | "::leo::": u"\U0000264c",
685 | "::man_with_gua_pi_mao::": u"\U0001f472",
686 | "::open_hands::": u"\U0001f450",
687 | "::tea::": u"\U0001f375",
688 | "::arrow_down::": u"\U00002b07",
689 | "::nine::": u"\U00000039\U000020e3",
690 | "::punch::": u"\U0001f44a",
691 | "::slot_machine::": u"\U0001f3b0",
692 | "::clap::": u"\U0001f44f",
693 | "::information_source::": u"\U00002139",
694 | "::tiger::": u"\U0001f42f",
695 | "::city_sunrise::": u"\U0001f307",
696 | "::dango::": u"\U0001f361",
697 | "::thumbsdown::": u"\U0001f44e",
698 | "::u6307::": u"\U0001f22f",
699 | "::curry::": u"\U0001f35b",
700 | "::cherries::": u"\U0001f352",
701 | "::clock6::": u"\U0001f555",
702 | "::clock7::": u"\U0001f556",
703 | "::older_man::": u"\U0001f474",
704 | "::oncoming_police_car::": u"\U0001f694",
705 | "::syringe::": u"\U0001f489",
706 | "::heavy_dollar_sign::": u"\U0001f4b2",
707 | "::open_file_folder::": u"\U0001f4c2",
708 | "::arrow_right_hook::": u"\U000021aa",
709 | "::articulated_lorry::": u"\U0001f69b",
710 | "::dancers::": u"\U0001f46f",
711 | "::kissing_cat::": u"\U0001f63d",
712 | "::rainbow::": u"\U0001f308",
713 | "::u5408::": u"\U0001f234",
714 | "::boot::": u"\U0001f462",
715 | "::carousel_horse::": u"\U0001f3a0",
716 | "::fried_shrimp::": u"\U0001f364",
717 | "::lock::": u"\U0001f512",
718 | "::non-potable_water::": u"\U0001f6b1",
719 | "::o::": u"\U00002b55",
720 | "::persevere::": u"\U0001f623",
721 | "::diamond_shape_with_a_dot_inside::": u"\U0001f4a0",
722 | "::fallen_leaf::": u"\U0001f342",
723 | "::massage::": u"\U0001f486",
724 | "::volcano::": u"\U0001f30b",
725 | "::gem::": u"\U0001f48e",
726 | "::shower::": u"\U0001f6bf",
727 | "::speaker::": u"\U0001f508",
728 | "::last_quarter_moon_with_face::": u"\U0001f31c",
729 | "::mag::": u"\U0001f50d",
730 | "::anguished::": u"\U0001f627",
731 | "::monkey_face::": u"\U0001f435",
732 | "::sunny::": u"\U00002600",
733 | "::tangerine::": u"\U0001f34a",
734 | "::point_right::": u"\U0001f449",
735 | "::railway_car::": u"\U0001f683",
736 | "::triumph::": u"\U0001f624",
737 | "::two::": u"\U00000032\U000020e3",
738 | "::gift_heart::": u"\U0001f49d",
739 | "::ledger::": u"\U0001f4d2",
740 | "::sagittarius::": u"\U00002650",
741 | "::snowflake::": u"\U00002744",
742 | "::abc::": u"\U0001f524",
743 | "::horse::": u"\U0001f434",
744 | "::ok_hand::": u"\U0001f44c",
745 | "::video_camera::": u"\U0001f4f9",
746 | "::sparkling_heart::": u"\U0001f496",
747 | "::taurus::": u"\U00002649",
748 | "::frog::": u"\U0001f438",
749 | "::hamster::": u"\U0001f439",
750 | "::helicopter::": u"\U0001f681",
751 | "::fries::": u"\U0001f35f",
752 | "::mushroom::": u"\U0001f344",
753 | "::penguin::": u"\U0001f427",
754 | "::truck::": u"\U0001f69a",
755 | "::bar_chart::": u"\U0001f4ca",
756 | "::evergreen_tree::": u"\U0001f332",
757 | "::bow::": u"\U0001f647",
758 | "::clock12::": u"\U0001f55b",
759 | "::four_leaf_clover::": u"\U0001f340",
760 | "::inbox_tray::": u"\U0001f4e5",
761 | "::smirk_cat::": u"\U0001f63c",
762 | "::two_men_holding_hands::": u"\U0001f46c",
763 | "::water_buffalo::": u"\U0001f403",
764 | "::alien::": u"\U0001f47d",
765 | "::video_game::": u"\U0001f3ae",
766 | "::candy::": u"\U0001f36c",
767 | "::page_facing_up::": u"\U0001f4c4",
768 | "::watermelon::": u"\U0001f349",
769 | "::white_check_mark::": u"\U00002705",
770 | "::blossom::": u"\U0001f33c",
771 | "::crocodile::": u"\U0001f40a",
772 | "::no_mouth::": u"\U0001f636",
773 | "::o2::": u"\U0001f17e",
774 | "::shirt::": u"\U0001f455",
775 | "::clock8::": u"\U0001f557",
776 | "::eyes::": u"\U0001f440",
777 | "::rabbit2::": u"\U0001f407",
778 | "::tanabata_tree::": u"\U0001f38b",
779 | "::wrench::": u"\U0001f527",
780 | "::es::": u"\U0001f1ea\U0001f1f8",
781 | "::trophy::": u"\U0001f3c6",
782 | "::two_women_holding_hands::": u"\U0001f46d",
783 | "::clock630::": u"\U0001f561",
784 | "::pineapple::": u"\U0001f34d",
785 | "::stuck_out_tongue::": u"\U0001f61b",
786 | "::angry::": u"\U0001f620",
787 | "::athletic_shoe::": u"\U0001f45f",
788 | "::cookie::": u"\U0001f36a",
789 | "::flags::": u"\U0001f38f",
790 | "::game_die::": u"\U0001f3b2",
791 | "::bird::": u"\U0001f426",
792 | "::jack_o_lantern::": u"\U0001f383",
793 | "::ox::": u"\U0001f402",
794 | "::paperclip::": u"\U0001f4ce",
795 | "::sleepy::": u"\U0001f62a",
796 | "::astonished::": u"\U0001f632",
797 | "::back::": u"\U0001f519",
798 | "::closed_book::": u"\U0001f4d5",
799 | "::hatching_chick::": u"\U0001f423",
800 | "::arrows_clockwise::": u"\U0001f503",
801 | "::car::": u"\U0001f697",
802 | "::ear::": u"\U0001f442",
803 | "::haircut::": u"\U0001f487",
804 | "::icecream::": u"\U0001f366",
805 | "::bust_in_silhouette::": u"\U0001f464",
806 | "::diamonds::": u"\U00002666",
807 | "::no_good::": u"\U0001f645",
808 | "::pizza::": u"\U0001f355",
809 | "::chicken::": u"\U0001f414",
810 | "::eyeglasses::": u"\U0001f453",
811 | "::see_no_evil::": u"\U0001f648",
812 | "::earth_africa::": u"\U0001f30d",
813 | "::fireworks::": u"\U0001f386",
814 | "::page_with_curl::": u"\U0001f4c3",
815 | "::rice_ball::": u"\U0001f359",
816 | "::white_square_button::": u"\U0001f533",
817 | "::cake::": u"\U0001f370",
818 | "::red_car::": u"\U0001f697",
819 | "::tm::": u"\U00002122",
820 | "::unamused::": u"\U0001f612",
821 | "::fish_cake::": u"\U0001f365",
822 | "::key::": u"\U0001f511",
823 | "::speedboat::": u"\U0001f6a4",
824 | "::closed_umbrella::": u"\U0001f302",
825 | "::pear::": u"\U0001f350",
826 | "::satellite::": u"\U0001f4e1",
827 | "::scream::": u"\U0001f631",
828 | "::first_quarter_moon::": u"\U0001f313",
829 | "::jp::": u"\U0001f1ef\U0001f1f5",
830 | "::repeat_one::": u"\U0001f502",
831 | "::shell::": u"\U0001f41a",
832 | "::interrobang::": u"\U00002049",
833 | "::trident::": u"\U0001f531",
834 | "::u55b6::": u"\U0001f23a",
835 | "::atm::": u"\U0001f3e7",
836 | "::door::": u"\U0001f6aa",
837 | "::kissing::": u"\U0001f617",
838 | "::six_pointed_star::": u"\U0001f52f",
839 | "::thumbsup::": u"\U0001f44d",
840 | "::u6708::": u"\U0001f237",
841 | "::do_not_litter::": u"\U0001f6af",
842 | "::whale2::": u"\U0001f40b",
843 | "::school_satchel::": u"\U0001f392",
844 | "::cactus::": u"\U0001f335",
845 | "::clipboard::": u"\U0001f4cb",
846 | "::dizzy::": u"\U0001f4ab",
847 | "::waxing_gibbous_moon::": u"\U0001f314",
848 | "::camera::": u"\U0001f4f7",
849 | "::capital_abcd::": u"\U0001f520",
850 | "::leaves::": u"\U0001f343",
851 | "::left_luggage::": u"\U0001f6c5",
852 | "::bamboo::": u"\U0001f38d",
853 | "::bowling::": u"\U0001f3b3",
854 | "::eight::": u"\U00000038\U000020e3",
855 | "::kimono::": u"\U0001f458",
856 | "::left_right_arrow::": u"\U00002194",
857 | "::stuck_out_tongue_winking_eye::": u"\U0001f61c",
858 | "::surfer::": u"\U0001f3c4",
859 | "::sweat::": u"\U0001f613",
860 | "::violin::": u"\U0001f3bb",
861 | "::postbox::": u"\U0001f4ee",
862 | "::bride_with_veil::": u"\U0001f470",
863 | "::recycle::": u"\U0000267b",
864 | "::station::": u"\U0001f689",
865 | "::vhs::": u"\U0001f4fc",
866 | "::crossed_flags::": u"\U0001f38c",
867 | "::memo::": u"\U0001f4dd",
868 | "::no_entry::": u"\U000026d4",
869 | "::white_circle::": u"\U000026aa",
870 | "::arrow_lower_left::": u"\U00002199",
871 | "::chestnut::": u"\U0001f330",
872 | "::crystal_ball::": u"\U0001f52e",
873 | "::last_quarter_moon::": u"\U0001f317",
874 | "::loud_sound::": u"\U0001f50a",
875 | "::strawberry::": u"\U0001f353",
876 | "::worried::": u"\U0001f61f",
877 | "::circus_tent::": u"\U0001f3aa",
878 | "::weary::": u"\U0001f629",
879 | "::bathtub::": u"\U0001f6c1",
880 | "::snake::": u"\U0001f40d",
881 | "::grin::": u"\U0001f601",
882 | "::symbols::": u"\U0001f523",
883 | "::airplane::": u"\U00002708",
884 | "::heart_eyes::": u"\U0001f60d",
885 | "::sailboat::": u"\U000026f5",
886 | "::stew::": u"\U0001f372",
887 | "::tshirt::": u"\U0001f455",
888 | "::rat::": u"\U0001f400",
889 | "::black_medium_square::": u"\U000025fc",
890 | "::clock930::": u"\U0001f564",
891 | "::full_moon_with_face::": u"\U0001f31d",
892 | "::japanese_goblin::": u"\U0001f47a",
893 | "::restroom::": u"\U0001f6bb",
894 | "::vertical_traffic_light::": u"\U0001f6a6",
895 | "::basketball::": u"\U0001f3c0",
896 | "::cherry_blossom::": u"\U0001f338",
897 | "::low_brightness::": u"\U0001f505",
898 | "::pill::": u"\U0001f48a",
899 | }
900 |
901 |
902 | def emojize(text):
903 | """
904 | Emoji regex
905 | """
906 | pattern = re.compile('(::[a-z0-9\+\-_]+::)')
907 |
908 | def emorepl(match):
909 | value = match.group(1)
910 | if value in emojiCodeDict:
911 | return emojiCodeDict[value]
912 | return pattern.sub(emorepl, text)
913 |
--------------------------------------------------------------------------------
/rainbowstream/image.c:
--------------------------------------------------------------------------------
1 | /*
2 | * This source is borrowed from following link
3 | * https://github.com/jart/fabulous/blob/master/fabulous/_xterm256.c
4 | * I make a slightly change to fit my module here
5 | */
6 | typedef struct {
7 | int r;
8 | int g;
9 | int b;
10 | } rgb_t;
11 | int CUBE_STEPS[] = { 0x00, 0x5F, 0x87, 0xAF, 0xD7, 0xFF };
12 | rgb_t BASIC16[] =
13 | { { 0, 0, 0 }, { 205, 0, 0}, { 0, 205, 0 },
14 | { 205, 205, 0 }, { 0, 0, 238}, { 205, 0, 205 },
15 | { 0, 205, 205 }, { 229, 229, 229}, { 127, 127, 127 },
16 | { 255, 0, 0 }, { 0, 255, 0}, { 255, 255, 0 },
17 | { 92, 92, 255 }, { 255, 0, 255}, { 0, 255, 255 },
18 | { 255, 255, 255 } };
19 | rgb_t COLOR_TABLE[256];
20 |
21 |
22 | rgb_t ansi_to_rgb(int xcolor)
23 | {
24 | rgb_t res;
25 | if (xcolor < 16) {
26 | res = BASIC16[xcolor];
27 | } else if (16 <= xcolor && xcolor <= 231) {
28 | xcolor -= 16;
29 | res.r = CUBE_STEPS[(xcolor / 36) % 6];
30 | res.g = CUBE_STEPS[(xcolor / 6) % 6];
31 | res.b = CUBE_STEPS[xcolor % 6];
32 | } else if (232 <= xcolor && xcolor <= 255) {
33 | res.r = res.g = res.b = 8 + (xcolor - 232) * 0x0A;
34 | }
35 | return res;
36 | }
37 |
38 | int init()
39 | {
40 | int c;
41 | for (c = 0; c < 256; c++) {
42 | COLOR_TABLE[c] = ansi_to_rgb(c);
43 | }
44 | return 0;
45 | }
46 |
47 | int rgb_to_ansi(int r, int g, int b)
48 | {
49 | int best_match = 0;
50 | int smallest_distance = 1000000000;
51 | int c, d;
52 | for (c = 16; c < 256; c++) {
53 | d = (COLOR_TABLE[c].r - r)*(COLOR_TABLE[c].r - r) +
54 | (COLOR_TABLE[c].g - g)*(COLOR_TABLE[c].g - g) +
55 | (COLOR_TABLE[c].b - b)*(COLOR_TABLE[c].b - b);
56 | if (d < smallest_distance) {
57 | smallest_distance = d;
58 | best_match = c;
59 | }
60 | }
61 | return best_match;
62 | }
63 |
--------------------------------------------------------------------------------
/rainbowstream/interactive.py:
--------------------------------------------------------------------------------
1 | import readline
2 | import os.path
3 |
4 | from .config import *
5 |
6 |
7 | class RainbowCompleter(object):
8 |
9 | def __init__(self, options):
10 | """
11 | Init
12 | """
13 | self.options = options
14 | self.current_candidates = []
15 | return
16 |
17 | def complete(self, text, state):
18 | """
19 | Complete
20 | """
21 | response = None
22 | if state == 0:
23 | origline = readline.get_line_buffer()
24 | begin = readline.get_begidx()
25 | end = readline.get_endidx()
26 | being_completed = origline[begin:end]
27 | words = origline.split()
28 |
29 | if not words:
30 | self.current_candidates = sorted([c for c in self.options])
31 | else:
32 | try:
33 | if begin == 0:
34 | candidates = [c for c in self.options]
35 | elif words[-1] in self.options[words[0]]:
36 | candidates = []
37 | else:
38 | first = words[0]
39 | candidates = self.options[first]
40 |
41 | if being_completed:
42 | self.current_candidates = [w for w in candidates
43 | if w.startswith(being_completed)]
44 | else:
45 | self.current_candidates = candidates
46 |
47 | except (KeyError, IndexError):
48 | self.current_candidates = []
49 |
50 | try:
51 | response = self.current_candidates[state]
52 | except IndexError:
53 | response = None
54 | return response
55 |
56 |
57 | def get_history_items():
58 | """
59 | Get all history item
60 | """
61 | return [
62 | readline.get_history_item(i)
63 | for i in xrange(1, readline.get_current_history_length() + 1)
64 | ]
65 |
66 |
67 | def read_history():
68 | """
69 | Read history file
70 | """
71 | try:
72 | readline.read_history_file(os.path.expanduser(c['HISTORY_FILENAME']))
73 | except:
74 | pass
75 |
76 |
77 | def save_history():
78 | """
79 | Save history to file
80 | """
81 | try:
82 | readline.write_history_file(os.path.expanduser(c['HISTORY_FILENAME']))
83 | except:
84 | pass
85 |
86 |
87 | def init_interactive_shell(d):
88 | """
89 | Init the rainbow shell
90 | """
91 | readline.set_completer(RainbowCompleter(d).complete)
92 | readline.parse_and_bind('set skip-completed-text on')
93 | if 'libedit' in readline.__doc__:
94 | readline.parse_and_bind("bind ^I rl_complete")
95 | else:
96 | readline.parse_and_bind("tab: complete")
97 |
--------------------------------------------------------------------------------
/rainbowstream/pure_image.py:
--------------------------------------------------------------------------------
1 | from PIL import Image
2 | from functools import partial
3 | from .config import *
4 | from .py3patch import *
5 |
6 | import sys
7 | import os
8 |
9 | """
10 | This file is borrowed from following gist:
11 | https://gist.github.com/MicahElliott/719710
12 | It's too slow in compare with C program.
13 | """
14 |
15 | CLUT = [ # color look-up table
16 | # 8-bit, RGB hex
17 |
18 | # Primary 3-bit (8 colors). Unique representation!
19 | ('00', '000000'),
20 | ('01', '800000'),
21 | ('02', '008000'),
22 | ('03', '808000'),
23 | ('04', '000080'),
24 | ('05', '800080'),
25 | ('06', '008080'),
26 | ('07', 'c0c0c0'),
27 |
28 | # Equivalent "bright" versions of original 8 colors.
29 | ('08', '808080'),
30 | ('09', 'ff0000'),
31 | ('10', '00ff00'),
32 | ('11', 'ffff00'),
33 | ('12', '0000ff'),
34 | ('13', 'ff00ff'),
35 | ('14', '00ffff'),
36 | ('15', 'ffffff'),
37 |
38 | # Strictly ascending.
39 | ('16', '000000'),
40 | ('17', '00005f'),
41 | ('18', '000087'),
42 | ('19', '0000af'),
43 | ('20', '0000d7'),
44 | ('21', '0000ff'),
45 | ('22', '005f00'),
46 | ('23', '005f5f'),
47 | ('24', '005f87'),
48 | ('25', '005faf'),
49 | ('26', '005fd7'),
50 | ('27', '005fff'),
51 | ('28', '008700'),
52 | ('29', '00875f'),
53 | ('30', '008787'),
54 | ('31', '0087af'),
55 | ('32', '0087d7'),
56 | ('33', '0087ff'),
57 | ('34', '00af00'),
58 | ('35', '00af5f'),
59 | ('36', '00af87'),
60 | ('37', '00afaf'),
61 | ('38', '00afd7'),
62 | ('39', '00afff'),
63 | ('40', '00d700'),
64 | ('41', '00d75f'),
65 | ('42', '00d787'),
66 | ('43', '00d7af'),
67 | ('44', '00d7d7'),
68 | ('45', '00d7ff'),
69 | ('46', '00ff00'),
70 | ('47', '00ff5f'),
71 | ('48', '00ff87'),
72 | ('49', '00ffaf'),
73 | ('50', '00ffd7'),
74 | ('51', '00ffff'),
75 | ('52', '5f0000'),
76 | ('53', '5f005f'),
77 | ('54', '5f0087'),
78 | ('55', '5f00af'),
79 | ('56', '5f00d7'),
80 | ('57', '5f00ff'),
81 | ('58', '5f5f00'),
82 | ('59', '5f5f5f'),
83 | ('60', '5f5f87'),
84 | ('61', '5f5faf'),
85 | ('62', '5f5fd7'),
86 | ('63', '5f5fff'),
87 | ('64', '5f8700'),
88 | ('65', '5f875f'),
89 | ('66', '5f8787'),
90 | ('67', '5f87af'),
91 | ('68', '5f87d7'),
92 | ('69', '5f87ff'),
93 | ('70', '5faf00'),
94 | ('71', '5faf5f'),
95 | ('72', '5faf87'),
96 | ('73', '5fafaf'),
97 | ('74', '5fafd7'),
98 | ('75', '5fafff'),
99 | ('76', '5fd700'),
100 | ('77', '5fd75f'),
101 | ('78', '5fd787'),
102 | ('79', '5fd7af'),
103 | ('80', '5fd7d7'),
104 | ('81', '5fd7ff'),
105 | ('82', '5fff00'),
106 | ('83', '5fff5f'),
107 | ('84', '5fff87'),
108 | ('85', '5fffaf'),
109 | ('86', '5fffd7'),
110 | ('87', '5fffff'),
111 | ('88', '870000'),
112 | ('89', '87005f'),
113 | ('90', '870087'),
114 | ('91', '8700af'),
115 | ('92', '8700d7'),
116 | ('93', '8700ff'),
117 | ('94', '875f00'),
118 | ('95', '875f5f'),
119 | ('96', '875f87'),
120 | ('97', '875faf'),
121 | ('98', '875fd7'),
122 | ('99', '875fff'),
123 | ('100', '878700'),
124 | ('101', '87875f'),
125 | ('102', '878787'),
126 | ('103', '8787af'),
127 | ('104', '8787d7'),
128 | ('105', '8787ff'),
129 | ('106', '87af00'),
130 | ('107', '87af5f'),
131 | ('108', '87af87'),
132 | ('109', '87afaf'),
133 | ('110', '87afd7'),
134 | ('111', '87afff'),
135 | ('112', '87d700'),
136 | ('113', '87d75f'),
137 | ('114', '87d787'),
138 | ('115', '87d7af'),
139 | ('116', '87d7d7'),
140 | ('117', '87d7ff'),
141 | ('118', '87ff00'),
142 | ('119', '87ff5f'),
143 | ('120', '87ff87'),
144 | ('121', '87ffaf'),
145 | ('122', '87ffd7'),
146 | ('123', '87ffff'),
147 | ('124', 'af0000'),
148 | ('125', 'af005f'),
149 | ('126', 'af0087'),
150 | ('127', 'af00af'),
151 | ('128', 'af00d7'),
152 | ('129', 'af00ff'),
153 | ('130', 'af5f00'),
154 | ('131', 'af5f5f'),
155 | ('132', 'af5f87'),
156 | ('133', 'af5faf'),
157 | ('134', 'af5fd7'),
158 | ('135', 'af5fff'),
159 | ('136', 'af8700'),
160 | ('137', 'af875f'),
161 | ('138', 'af8787'),
162 | ('139', 'af87af'),
163 | ('140', 'af87d7'),
164 | ('141', 'af87ff'),
165 | ('142', 'afaf00'),
166 | ('143', 'afaf5f'),
167 | ('144', 'afaf87'),
168 | ('145', 'afafaf'),
169 | ('146', 'afafd7'),
170 | ('147', 'afafff'),
171 | ('148', 'afd700'),
172 | ('149', 'afd75f'),
173 | ('150', 'afd787'),
174 | ('151', 'afd7af'),
175 | ('152', 'afd7d7'),
176 | ('153', 'afd7ff'),
177 | ('154', 'afff00'),
178 | ('155', 'afff5f'),
179 | ('156', 'afff87'),
180 | ('157', 'afffaf'),
181 | ('158', 'afffd7'),
182 | ('159', 'afffff'),
183 | ('160', 'd70000'),
184 | ('161', 'd7005f'),
185 | ('162', 'd70087'),
186 | ('163', 'd700af'),
187 | ('164', 'd700d7'),
188 | ('165', 'd700ff'),
189 | ('166', 'd75f00'),
190 | ('167', 'd75f5f'),
191 | ('168', 'd75f87'),
192 | ('169', 'd75faf'),
193 | ('170', 'd75fd7'),
194 | ('171', 'd75fff'),
195 | ('172', 'd78700'),
196 | ('173', 'd7875f'),
197 | ('174', 'd78787'),
198 | ('175', 'd787af'),
199 | ('176', 'd787d7'),
200 | ('177', 'd787ff'),
201 | ('178', 'd7af00'),
202 | ('179', 'd7af5f'),
203 | ('180', 'd7af87'),
204 | ('181', 'd7afaf'),
205 | ('182', 'd7afd7'),
206 | ('183', 'd7afff'),
207 | ('184', 'd7d700'),
208 | ('185', 'd7d75f'),
209 | ('186', 'd7d787'),
210 | ('187', 'd7d7af'),
211 | ('188', 'd7d7d7'),
212 | ('189', 'd7d7ff'),
213 | ('190', 'd7ff00'),
214 | ('191', 'd7ff5f'),
215 | ('192', 'd7ff87'),
216 | ('193', 'd7ffaf'),
217 | ('194', 'd7ffd7'),
218 | ('195', 'd7ffff'),
219 | ('196', 'ff0000'),
220 | ('197', 'ff005f'),
221 | ('198', 'ff0087'),
222 | ('199', 'ff00af'),
223 | ('200', 'ff00d7'),
224 | ('201', 'ff00ff'),
225 | ('202', 'ff5f00'),
226 | ('203', 'ff5f5f'),
227 | ('204', 'ff5f87'),
228 | ('205', 'ff5faf'),
229 | ('206', 'ff5fd7'),
230 | ('207', 'ff5fff'),
231 | ('208', 'ff8700'),
232 | ('209', 'ff875f'),
233 | ('210', 'ff8787'),
234 | ('211', 'ff87af'),
235 | ('212', 'ff87d7'),
236 | ('213', 'ff87ff'),
237 | ('214', 'ffaf00'),
238 | ('215', 'ffaf5f'),
239 | ('216', 'ffaf87'),
240 | ('217', 'ffafaf'),
241 | ('218', 'ffafd7'),
242 | ('219', 'ffafff'),
243 | ('220', 'ffd700'),
244 | ('221', 'ffd75f'),
245 | ('222', 'ffd787'),
246 | ('223', 'ffd7af'),
247 | ('224', 'ffd7d7'),
248 | ('225', 'ffd7ff'),
249 | ('226', 'ffff00'),
250 | ('227', 'ffff5f'),
251 | ('228', 'ffff87'),
252 | ('229', 'ffffaf'),
253 | ('230', 'ffffd7'),
254 | ('231', 'ffffff'),
255 |
256 | # Gray-scale range.
257 | ('232', '080808'),
258 | ('233', '121212'),
259 | ('234', '1c1c1c'),
260 | ('235', '262626'),
261 | ('236', '303030'),
262 | ('237', '3a3a3a'),
263 | ('238', '444444'),
264 | ('239', '4e4e4e'),
265 | ('240', '585858'),
266 | ('241', '626262'),
267 | ('242', '6c6c6c'),
268 | ('243', '767676'),
269 | ('244', '808080'),
270 | ('245', '8a8a8a'),
271 | ('246', '949494'),
272 | ('247', '9e9e9e'),
273 | ('248', 'a8a8a8'),
274 | ('249', 'b2b2b2'),
275 | ('250', 'bcbcbc'),
276 | ('251', 'c6c6c6'),
277 | ('252', 'd0d0d0'),
278 | ('253', 'dadada'),
279 | ('254', 'e4e4e4'),
280 | ('255', 'eeeeee'),
281 | ]
282 |
283 |
284 | def _create_dicts():
285 | """
286 | Create dictionary
287 | """
288 | short2rgb_dict = dict(CLUT)
289 | rgb2short_dict = {}
290 | for k, v in short2rgb_dict.items():
291 | rgb2short_dict[v] = k
292 | return rgb2short_dict, short2rgb_dict
293 |
294 | RGB2SHORT_DICT, SHORT2RGB_DICT = _create_dicts()
295 |
296 |
297 | def short2rgb(short):
298 | """
299 | Short to RGB
300 | """
301 | return SHORT2RGB_DICT[short]
302 |
303 |
304 | def pixel_print(ansicolor):
305 | """
306 | Print a pixel with given Ansi color
307 | """
308 | sys.stdout.write('\033[48;5;%sm \033[0m' % (ansicolor))
309 |
310 |
311 | def hex_to_rgb(value):
312 | """
313 | Hex to RGB
314 | """
315 | value = value.lstrip('#')
316 | lv = len(value)
317 | return tuple(int(value[i:i + lv / 3], 16) for i in xrange(0, lv, lv / 3))
318 |
319 |
320 | def rgb_to_hex(rgb):
321 | """
322 | RGB to Hex
323 | """
324 | return '%02x%02x%02x' % rgb
325 |
326 |
327 | def rgb2short(r, g, b):
328 | """
329 | RGB to short
330 | """
331 | dist = lambda s, d: (s[0] - d[0]) ** 2 + \
332 | (s[1] - d[1]) ** 2 + (s[2] - d[2]) ** 2
333 | ary = [hex_to_rgb(hex) for hex in RGB2SHORT_DICT]
334 | m = min(ary, key=partial(dist, (r, g, b)))
335 | return RGB2SHORT_DICT[rgb_to_hex(m)]
336 |
337 |
338 | def image_to_display(path, start=None, length=None):
339 | """
340 | Display an image
341 | """
342 | rows, columns = os.popen('stty size', 'r').read().split()
343 | if not start:
344 | start = IMAGE_SHIFT
345 | if not length:
346 | length = int(columns) - 2 * start
347 | i = Image.open(path)
348 | i = i.convert('RGBA')
349 | w, h = i.size
350 | i.load()
351 | width = min(w, length)
352 | height = int(float(h) * (float(width) / float(w)))
353 | height //= 2
354 | i = i.resize((width, height), Image.BICUBIC)
355 | height = min(height, IMAGE_MAX_HEIGHT)
356 |
357 | for y in xrange(height):
358 | sys.stdout.write(' ' * start)
359 | for x in xrange(width):
360 | p = i.getpixel((x, y))
361 | r, g, b = p[:3]
362 | pixel_print(rgb2short(r, g, b))
363 | sys.stdout.write('\n')
364 |
365 |
366 | """
367 | For direct using purpose
368 | """
369 | if __name__ == '__main__':
370 | image_to_display(sys.argv[1])
371 |
--------------------------------------------------------------------------------
/rainbowstream/py3patch.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | # Library compatibility
4 | if sys.version[0] == "2":
5 | from HTMLParser import HTMLParser
6 | from urllib2 import URLError
7 | unescape = HTMLParser().unescape
8 | else:
9 | from html.parser import HTMLParser
10 | from urllib.error import URLError
11 | # HTMLParser().unescape is deprecated since Python 3.4 and is unsafe.
12 | # you should use html.unescape instead, like you've said
13 | from html import unescape
14 |
15 | # unescape = HTMLParser().unescape
16 | # According to https://github.com/python/cpython/blob/master/Lib/html/parser.py#L547 ,
17 | # in python 3.5 maybe I should use
18 | # from html import unescape
19 | # ~~but it is a far-future story:)~~ no longer.
20 |
21 |
22 |
23 | # Function compatibility
24 | # xrange, raw_input, map ,unicde
25 | if sys.version[0] == "2":
26 | lmap = lambda f, a: map(f, a)
27 | str2u = lambda x: x.decode('utf-8')
28 | u2str = lambda x: x.encode('utf-8')
29 | else:
30 | xrange = range
31 | raw_input = input
32 | lmap = lambda f, a: list(map(f, a))
33 | str2u = u2str = lambda x: x
34 |
--------------------------------------------------------------------------------
/rainbowstream/util.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | from twitter.util import printNicely
4 | from .colors import *
5 | from .config import *
6 |
7 |
8 | def detail_twitter_error(twitterException):
9 | """
10 | Display Twitter Errors nicely
11 | """
12 | data = twitterException.response_data
13 | try:
14 | for m in data.get('errors', dict()):
15 | printNicely(yellow(m.get('message')))
16 | except:
17 | printNicely(yellow(data))
18 |
19 |
20 | def format_prefix(listname='', keyword=''):
21 | """
22 | Format the custom prefix
23 | """
24 | formattedPrefix = c['PREFIX']
25 | owner = '@' + c['original_name']
26 | place = ''
27 | # Public stream
28 | if keyword:
29 | formattedPrefix = ''.join(formattedPrefix.split('#owner'))
30 | formattedPrefix = ''.join(formattedPrefix.split('#place'))
31 | formattedPrefix = ''.join(formattedPrefix.split('#me'))
32 | # List stream
33 | elif listname:
34 | formattedPrefix = ''.join(formattedPrefix.split('#keyword'))
35 | formattedPrefix = ''.join(formattedPrefix.split('#me'))
36 | owner, place = listname.split('/')
37 | place = '/' + place
38 | # Personal stream
39 | else:
40 | formattedPrefix = ''.join(formattedPrefix.split('#keyword'))
41 | formattedPrefix = ''.join(formattedPrefix.split('#owner'))
42 | formattedPrefix = ''.join(formattedPrefix.split('#place'))
43 |
44 | formattedPrefix = formattedPrefix.replace('#owner', owner)
45 | formattedPrefix = formattedPrefix.replace('#place', place)
46 | formattedPrefix = formattedPrefix.replace('#keyword', keyword)
47 | formattedPrefix = formattedPrefix.replace('#me', '@' + c['original_name'])
48 |
49 | return formattedPrefix
50 |
51 |
52 | def add_tweetmode_parameter(kwargs):
53 | """
54 | Add support for extended mode to Twitter API calls unless explicitly stated in config
55 | """
56 | if not c.get('DISABLE_EXTENDED_TWEETS'):
57 | kwargs['tweet_mode'] = 'extended'
58 | return kwargs
59 |
--------------------------------------------------------------------------------
/release.sh:
--------------------------------------------------------------------------------
1 | # Packaging
2 | python setup.py sdist bdist_wheel
3 | # Check distribution package
4 | twine check dist/*
5 | # Upload to PyPi
6 | twine upload dist/*
7 | # Announce the latest version to Github
8 | git push origin master
9 |
--------------------------------------------------------------------------------
/screenshot/RainbowStreamAll.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/orakaro/rainbowstream/8b2efa52574865646adfb157f4563a72d9e37d96/screenshot/RainbowStreamAll.png
--------------------------------------------------------------------------------
/screenshot/rs.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/orakaro/rainbowstream/8b2efa52574865646adfb157f4563a72d9e37d96/screenshot/rs.gif
--------------------------------------------------------------------------------
/screenshot/themes/Default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/orakaro/rainbowstream/8b2efa52574865646adfb157f4563a72d9e37d96/screenshot/themes/Default.png
--------------------------------------------------------------------------------
/screenshot/themes/Monokai.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/orakaro/rainbowstream/8b2efa52574865646adfb157f4563a72d9e37d96/screenshot/themes/Monokai.png
--------------------------------------------------------------------------------
/screenshot/themes/Solarized.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/orakaro/rainbowstream/8b2efa52574865646adfb157f4563a72d9e37d96/screenshot/themes/Solarized.png
--------------------------------------------------------------------------------
/screenshot/themes/TomorrowNight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/orakaro/rainbowstream/8b2efa52574865646adfb157f4563a72d9e37d96/screenshot/themes/TomorrowNight.png
--------------------------------------------------------------------------------
/screenshot/themes/larapaste.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/orakaro/rainbowstream/8b2efa52574865646adfb157f4563a72d9e37d96/screenshot/themes/larapaste.png
--------------------------------------------------------------------------------
/setup.CFG:
--------------------------------------------------------------------------------
1 | [metadata]
2 | description-file = README.rst
3 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 | import os
3 | import os.path
4 | import sys
5 |
6 | if sys.version[0] == "2":
7 | from pipes import quote
8 | else:
9 | from shlex import quote
10 |
11 | # Bumped version
12 | version = '1.6.0'
13 |
14 | # Require
15 | install_requires = [
16 | "python-dateutil",
17 | "arrow",
18 | "requests",
19 | "pyfiglet",
20 | "twitter",
21 | "Pillow",
22 | "PySocks",
23 | "pocket",
24 | "libsixel-python",
25 | "resize-image"
26 | ]
27 |
28 | # Default user (considers non virtualenv method)
29 | user = os.environ.get('SUDO_USER', os.environ.get('USER', None))
30 |
31 | # Copy default config if not exists
32 | default = os.path.expanduser("~") + os.sep + '.rainbow_config.json'
33 | if not os.path.isfile(default):
34 | cmd = 'cp rainbowstream/colorset/config ' + default
35 | os.system(cmd)
36 | if user:
37 | cmd = 'chown ' + quote(user) + ' ' + default
38 | os.system(cmd)
39 | cmd = 'chmod 777 ' + default
40 | os.system(cmd)
41 |
42 | # Setup
43 | setup(name='rainbowstream',
44 | version=version,
45 | description="A smart and nice Twitter client on terminal.",
46 | long_description=open("./README.rst", "r").read(),
47 | classifiers=[
48 | "Development Status :: 5 - Production/Stable",
49 | "Environment :: Console",
50 | "Intended Audience :: End Users/Desktop",
51 | "Natural Language :: English",
52 | "Operating System :: OS Independent",
53 | "Programming Language :: Python :: 2.7",
54 | "Programming Language :: Python :: 3.2",
55 | "Programming Language :: Python :: 3.3",
56 | "Programming Language :: Python :: 3.4",
57 | "Programming Language :: Python :: 3.5",
58 | "Programming Language :: Python :: 3.6",
59 | "Programming Language :: Python :: 3.7",
60 | "Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries",
61 | "Topic :: Utilities",
62 | "License :: OSI Approved :: MIT License",
63 | ],
64 | keywords='twitter, command-line tools, stream API',
65 | author='Vu Nhat Minh',
66 | author_email='nhatminh179@gmail.com',
67 | url='http://www.rainbowstream.org/',
68 | license='MIT License',
69 | packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
70 | include_package_data=True,
71 | zip_safe=True,
72 | install_requires=install_requires,
73 | long_description_content_type='text/markdown',
74 | entry_points="""
75 | # -*- Entry points: -*-
76 | [console_scripts]
77 | rainbowstream=rainbowstream.rainbow:fly
78 | """,
79 | )
80 |
--------------------------------------------------------------------------------
/theme.md:
--------------------------------------------------------------------------------
1 | ## Available themes
2 | #### Monokai (`monokai`)
3 | 
4 | #### Solarized (`solarized`)
5 | 
6 | #### Tomorrow Night (`tomorrow_night`)
7 | 
8 | #### Larapaste (`larapaste`)
9 | 
10 |
11 | ## Customization
12 | Modify `~/.rainbow_config.json` and follow next instruction.
13 |
14 | Examples are available in
15 | [Themes folder](https://github.com/DTVD/rainbowstream/blob/master/rainbowstream/colorset)
16 |
17 | ### Custom config
18 | * There is a file named exactly `.rainbow_config.json` and is placed at your home directory.
19 | * Add color configurations to above file and follow json format.
20 | * Comments as `//` or `/*...*/` are allowed.
21 | * Here is an example
22 |
23 | ```json
24 | {
25 | "DECORATED_NAME" : 198,
26 | "CYCLE_COLOR" :[198,57,166,50,179,74,112],
27 | "TWEET" : {
28 | "mynick" : 179,
29 | "nick" : 112,
30 | "clock" : 57,
31 | "id" : 166,
32 | "client" : 74,
33 | "favorited" : 50,
34 | "retweet_count" : 50,
35 | "favorite_count" : 198,
36 | "rt" : 179,
37 | "link" : 74,
38 | "hashtag" : 198,
39 | "mytweet" : 179,
40 | "keyword" : "on_light_green"
41 | },
42 |
43 | "NOTIFICATION":{
44 | "source_nick" : 112,
45 | "notify" : 179,
46 | "clock" : 57
47 | },
48 |
49 | "MESSAGE" : {
50 | "partner" : 112,
51 | "me" : 112,
52 | "partner_frame" : 198,
53 | "me_frame" : 74,
54 | "sender" : 112,
55 | "recipient" : 112,
56 | "to" : 50,
57 | "clock" : 57,
58 | "id" : 166
59 | },
60 |
61 | "PROFILE" : {
62 | "statuses_count" : 112,
63 | "friends_count" : 198,
64 | "followers_count" : 57,
65 | "nick" : 198,
66 | "profile_image_url" : 74,
67 | "description" : 166,
68 | "location" : 112,
69 | "url" : 74,
70 | "clock" : 57
71 | },
72 |
73 | "TREND" : {
74 | "url": 74
75 | },
76 |
77 | "CAL" : {
78 | "days": 57,
79 | "today": "on_light_blue"
80 | },
81 |
82 | "GROUP" : {
83 | "name": 112,
84 | "member": 57,
85 | "subscriber": 198,
86 | "mode": 112,
87 | "description": 166,
88 | "clock": 57
89 | }
90 | }
91 | ```
92 |
93 | ### Available Colors
94 |
95 | There are 16 basic colors:
96 | * default
97 | * black
98 | * red
99 | * green
100 | * yellow
101 | * blue
102 | * magenta
103 | * cyan
104 | * grey
105 | * light_red
106 | * light_green
107 | * light_yellow
108 | * light_blue
109 | * light_magenta
110 | * light_cyan
111 | * white
112 |
113 | These colors will be enough for almost terminals.
114 | But if your terminal can support 256 colors (check your `$TERM` variable!),
115 | you can even use 0 to 255 as the example above.
116 |
117 | There are also background highlight colors like:
118 | * on_default
119 | * on_black
120 | * on_red
121 | * on_green
122 | * on_yellow
123 | * on_blue
124 | * on_magenta
125 | * on_cyan
126 | * on_grey
127 | * on_light_red
128 | * on_light_green
129 | * on_light_yellow
130 | * on_light_blue
131 | * on_light_magenta
132 | * on_light_cyan
133 | * on_white
134 |
135 |
136 | Color reference can be found at
137 | [bash colors](http://misc.flogisoft.com/bash/tip_colors_and_formatting) or
138 | [256 xterm colors](http://www.calmar.ws/vim/256-xterm-24bit-rgb-color-chart.html).
139 |
140 | ### Available options
141 | * `DECORATED_NAME`: color of your Twitter's __username__ which is placed at every line's begin.
142 | * `CYCLE_COLOR`: list of colors from which Twitter __real name__ 's color is selected.
143 | * Color selection is cycle through this list but with _memoization_.
144 | * It's means that same names will appear in same colors.
145 | * `TWEET`: colors of parts in a tweet's ouput.
146 | * `mynick` : color for your Twitter __username__.
147 | * `nick` : color for other Twitter __username__.
148 | * `clock`: color for time of tweet.
149 | * `id`: color for tweet's id.
150 | * `client`: color for used Twitter client.
151 | * `favorite`: color for the star symbol when a tweet is favorited by you.
152 | * `retweet_count`: color for retweets count.
153 | * `favorite_count`: color for favorites count.
154 | * `rt`: color for `RT` word in tweet's content.
155 | * `link`: color for an url.
156 | * `hashtag`: color for a hashtag.
157 | * `mytweet`: color for tweet's text from yourself.
158 | * `keyword`: color for highlighted keyword (in tweets search).
159 | * `NOTIFICATION`: colors of notification events.
160 | * `source_nick`: color for user's __username__.
161 | * `notify`: color for notification message.
162 | * `clock`: color for time of notification event.
163 | * `MESSAGE`: colors of parts in message's output.
164 | * `partner`: color for __partner__.
165 | * `me`: color for __authenticated user__.
166 | * `partner_frame`: color for __partner's frame__.
167 | * `me_frame`: color for __authenticated user's frame__.
168 | * `sender`: color for sender's __username__.
169 | * `recipient`: color for recipient's __username__.
170 | * `to`: color for the `>>>` symbol.
171 | * `clock`: color for time of message.
172 | * `id`: color for message's id.
173 | * `PROFILE`: colors for parts in profile's ouput.
174 | * `statuses_count`: color for statuses count.
175 | * `friends_count`: color for friends count.
176 | * `followers_count`: color for followers count.
177 | * `nick`: color for Twitter __username__.
178 | * `profile_image_url`: color for profile image url.
179 | * `description`: color for description.
180 | * `location`: color for location.
181 | * `url`: color for url.
182 | * `clock`: color for joined time.
183 | * `TREND`: colors for trend's output:
184 | * `url`: color for trend's url.
185 | * `CAL`: colors for calendar's output:
186 | * `days`: color for days in current month.
187 | * `today`: color for today.
188 | * `GROUP`: colors for twitter list output:
189 | * `name`: color for twitter list's name.
190 | * `member`: color member count.
191 | * `subscriber`: color subscriber count.
192 | * `mode`: color twitter list's mode.
193 | * `description`: color twitter list's description.
194 | * `clock`: color twitter list's created time.
195 |
196 | ### Theme usage
197 | While entered Rainbow Stream:
198 | * `theme` and hit ENTER to see which is available.
199 | * `theme` + TAB twice will show themes list instantly.
200 | * `theme monokai` will apply `monokai` theme immediately. You can use TAB key for theme's name autocompletion.
201 |
202 | ### Theme contribution
203 | I appreciate any contribution for themes for this app.
204 | Please add a file to [themes folder](https://github.com/DTVD/rainbowstream/tree/master/rainbowstream/colorset)
205 | (json format!) and create a [pull request](https://github.com/DTVD/rainbowstream/compare/) with a screenshot.
206 |
--------------------------------------------------------------------------------