remove legacy python/go

This commit is contained in:
Emil Lerch 2026-01-05 10:04:26 -08:00
parent f98e11e773
commit 7b7fc5cd6c
Signed by: lobo
GPG key ID: A7B62D657EF764F8
166 changed files with 0 additions and 20233 deletions

View file

@ -1,70 +0,0 @@
# Build stage
FROM golang:1-alpine as builder
WORKDIR /app
COPY ./share/we-lang/we-lang.go /app
RUN apk add --no-cache git
RUN go get -u github.com/mattn/go-colorable && \
go get -u github.com/klauspost/lctime && \
go get -u github.com/mattn/go-runewidth && \
CGO_ENABLED=0 go build /app/we-lang.go
# Results in /app/we-lang
FROM alpine:3
WORKDIR /app
COPY ./requirements.txt /app
ENV LLVM_CONFIG=/usr/bin/llvm9-config
RUN apk add --no-cache --virtual .build \
autoconf \
automake \
g++ \
gcc \
jpeg-dev \
llvm9-dev\
make \
zlib-dev \
&& apk add --no-cache \
python3 \
py3-pip \
py3-scipy \
py3-wheel \
py3-gevent \
zlib \
jpeg \
llvm9 \
libtool \
supervisor \
py3-numpy-dev \
python3-dev && \
mkdir -p /app/cache && \
mkdir -p /var/log/supervisor && \
mkdir -p /etc/supervisor/conf.d && \
chmod -R o+rw /var/log/supervisor && \
chmod -R o+rw /var/run && \
pip install -r requirements.txt && \
apk del --no-cache -r .build
COPY --from=builder /app/we-lang /app/bin/we-lang
COPY ./bin /app/bin
COPY ./lib /app/lib
COPY ./share /app/share
COPY share/docker/supervisord.conf /etc/supervisor/supervisord.conf
ENV WTTR_MYDIR="/app"
ENV WTTR_GEOLITE="/app/GeoLite2-City.mmdb"
ENV WTTR_WEGO="/app/bin/we-lang"
ENV WTTR_LISTEN_HOST="0.0.0.0"
ENV WTTR_LISTEN_PORT="8002"
EXPOSE 8002
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"]

View file

@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -1,612 +0,0 @@
*wttr.in — the right way to check the weather!*
wttr.in is a console-oriented weather forecast service that supports various information
representation methods like terminal-oriented ANSI-sequences for console HTTP clients
(curl, httpie, or wget), HTML for web browsers, or PNG for graphical viewers.
wttr.in uses [wego](http://github.com/schachmat/wego) for visualization
and various data sources for weather forecast information.
You can see it running here: [wttr.in](http://wttr.in).
## Usage
You can access the service from a shell or from a Web browser like this:
$ curl wttr.in
Weather for City: Paris, France
\ / Clear
.-. 10 11 °C
― ( ) ― ↑ 11 km/h
`- 10 km
/ \ 0.0 mm
Here is an actual weather report for your location (it's live!):
![Weather Report](http://wttr.in/MyLocation.png?)
(It's not your actual location - GitHub's CDN hides your real IP address with its own IP address,
but it's still a live weather report in your language.)
Or in PowerShell:
```PowerShell
Invoke-RestMethod http://wttr.in
```
Want to get the weather information for a specific location? You can add the desired location to the URL in your
request like this:
$ curl wttr.in/London
$ curl wttr.in/Moscow
$ curl wttr.in/Salt+Lake+City
If you omit the location name, you will get the report for your current location based on your IP address.
Use 3-letter airport codes in order to get the weather information at a certain airport:
$ curl wttr.in/muc # Weather for IATA: muc, Munich International Airport, Germany
$ curl wttr.in/ham # Weather for IATA: ham, Hamburg Airport, Germany
Let's say you'd like to get the weather for a geographical location other than a town or city - maybe an attraction
in a city, a mountain name, or some special location. Add the character `~` before the name to look up that special
location name before the weather is then retrieved:
$ curl wttr.in/~Vostok+Station
$ curl wttr.in/~Eiffel+Tower
$ curl wttr.in/~Kilimanjaro
For these examples, you'll see a line below the weather forecast output that shows the geolocation
results of looking up the location:
Location: Vostok Station, станция Восток, AAT, Antarctica [-78.4642714,106.8364678]
Location: Tour Eiffel, 5, Avenue Anatole France, Gros-Caillou, 7e, Paris, Île-de-France, 75007, France [48.8582602,2.29449905432]
Location: Kilimanjaro, Northern, Tanzania [-3.4762789,37.3872648]
You can also use IP-addresses (direct) or domain names (prefixed with `@`) to specify a location:
$ curl wttr.in/@github.com
$ curl wttr.in/@msu.ru
To get detailed information online, you can access the [/:help](http://wttr.in/:help) page:
$ curl wttr.in/:help
### Weather Units
By default the USCS units are used for the queries from the USA and the metric system for the rest of the world.
You can override this behavior by adding `?u` or `?m` to a URL like this:
$ curl wttr.in/Amsterdam?u
$ curl wttr.in/Amsterdam?m
## Supported output formats and views
wttr.in currently supports five output formats:
* ANSI for the terminal;
* Plain-text for the terminal and scripts;
* HTML for the browser;
* PNG for the graphical viewers;
* JSON for scripts and APIs;
* Prometheus metrics for scripts and APIs.
The ANSI and HTML formats are selected basing on the User-Agent string.
The PNG format can be forced by adding `.png` to the end of the query:
$ wget wttr.in/Paris.png
You can use all of the options with the PNG-format like in an URL, but you have
to separate them with `_` instead of `?` and `&`:
$ wget wttr.in/Paris_0tqp_lang=fr.png
Useful options for the PNG format:
* `t` for transparency (`transparency=150`);
* transparency=0..255 for a custom transparency level.
Transparency is a useful feature when weather PNGs are used to add weather data to pictures:
$ convert source.jpg <( curl wttr.in/Oymyakon_tqp0.png ) -geometry +50+50 -composite target.jpg
In this example:
* `source.jpg` - source file;
* `target.jpg` - target file;
* `Oymyakon` - name of the location;
* `tqp0` - options (recommended).
![Picture with weather data](https://pbs.twimg.com/media/C69-wsIW0AAcAD5.jpg)
You can embed a special wttr.in widget, that displays the weather condition for the current or a selected location, into a HTML page using the [wttr-switcher](https://github.com/midzer/wttr-switcher). That is how it looks like: [wttr-switcher-example](https://midzer.github.io/wttr-switcher/) or on a real world web site: https://feuerwehr-eisolzried.de/.
![Embedded wttr.in example at feuerwehr-eisolzried.de](https://user-images.githubusercontent.com/3875145/65265457-50eac180-db11-11e9-8f9b-2e1711dfc436.png)
## One-line output
For one-line output format, specify additional URL parameter `format`:
```
$ curl wttr.in/Nuremberg?format=3
Nuremberg: 🌦 +11⁰C
```
Available preconfigured formats: 1, 2, 3, 4 and the custom format using the percent notation (see below).
You can specify multiple locations separated with `:` (for repeating queries):
```
$ curl wttr.in/Nuremberg:Hamburg:Berlin?format=3
Nuremberg: 🌦 +11⁰C
```
Or to process all this queries at once:
```
$ curl -s 'wttr.in/{Nuremberg,Hamburg,Berlin}?format=3'
Nuremberg: 🌦 +11⁰C
Hamburg: 🌦 +8⁰C
Berlin: 🌦 +8⁰C
```
To specify your own custom output format, use the special `%`-notation:
```
c Weather condition,
C Weather condition textual name,
h Humidity,
t Temperature (Actual),
f Temperature (Feels Like),
w Wind,
l Location,
m Moonphase 🌑🌒🌓🌔🌕🌖🌗🌘,
M Moonday,
p precipitation (mm),
o Probability of Precipitation,
P pressure (hPa),
D Dawn*,
S Sunrise*,
z Zenith*,
s Sunset*,
d Dusk*.
(*times are shown in the local timezone)
```
So, these two calls are the same:
```
$ curl wttr.in/London?format=3
London: ⛅️ +7⁰C
$ curl wttr.in/London?format="%l:+%c+%t\n"
London: ⛅️ +7⁰C
```
Keep in mind, that when using in `tmux.conf`, you have to escape `%` with `%`, i.e. write there `%%` instead of `%`.
In programs, that are querying the service automatically (such as tmux), it is better to use some reasonable update interval. In tmux, you can configure it with `status-interval`.
If several, `:` separated locations, are specified in the query, specify update period
as an additional query parameter `period=`:
```
set -g status-interval 60
WEATHER='#(curl -s wttr.in/London:Stockholm:Moscow\?format\="%%l:+%%c%%20%%t%%60%%w&period=60")'
set -g status-right "$WEATHER ..."
```
![wttr.in in tmux status bar](https://wttr.in/files/example-tmux-status-line.png)
To see emojis in terminal, you need:
1. Terminal support for emojis (was added to Cairo 1.15.8);
2. Font with emojis support.
For the Emoji font, we recommend *Noto Color Emoji*, and a good alternative option would be the *Emoji One* font;
both of them support all necessary emoji glyphs.
Font configuration:
```xml
$ cat ~/.config/fontconfig/fonts.conf
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<alias>
<family>serif</family>
<prefer>
<family>Noto Color Emoji</family>
</prefer>
</alias>
<alias>
<family>sans-serif</family>
<prefer>
<family>Noto Color Emoji</family>
</prefer>
</alias>
<alias>
<family>monospace</family>
<prefer>
<family>Noto Color Emoji</family>
</prefer>
</alias>
</fontconfig>
```
(to apply the configuration, run `fc-cache -f -v`)
## Data-rich output format
In the experimental data-rich output format, that is available under the view code `v2`,
a lot of additional weather and astronomical information is available:
* Temperature, and precepetation changes forecast throughout the days;
* Moonphase for today and the next three days;
* The current weather condition, temperature, humidity, windspeed and direction, pressure;
* Timezone;
* Dawn, sunrise, noon, sunset, dusk time for he selected location;
* Precise geographical coordinates for the selected location.
```
$ curl v2.wttr.in/München
```
or
```
$ curl wttr.in/München?format=v2
```
or, if you prefer Nerd Fonts instead of Emoji, `v2d` (day) or `v2n` (night):
```
$ curl v2d.wttr.in/München
```
![data-reach output format](https://wttr.in/files/example-wttr-v2.png)
(The mode is experimental, and it has several limitations currently:
* It works only in terminal;
* Only English is supported).
Currently, you need some tweaks for some terminals, to get the best possible visualization.
### URXVT
Depending on your configuration you might be taking all steps, or only a few. URXVT currently doesn't support emoji related fonts, but we can get almost the same effect using *Font-Symbola*. So add to your `.Xresources` file the following line:
```
xft:symbola:size=10:minspace=False
```
You can add it _after_ your preferred font and it will only show up when required.
Then, if you see or feel like you're having spacing issues, add this: `URxvt.letterSpace: 0`
For some reason URXVT sometimes stops deciding right the word spacing and we need to force it this way.
The result, should look like:
![URXVT Emoji line](https://user-images.githubusercontent.com/24360204/63842949-1d36d480-c975-11e9-81dd-998d1329bd8a.png)
## Different output formats
### JSON output
The JSON format is a feature providing access to *wttr.in* data through an easy-to-parse format, without requiring the user to create a complex script to reinterpret wttr.in's graphical output.
To fetch information in JSON format, use the following syntax:
$ curl wttr.in/Detroit?format=j1
This will fetch information on the Detroit region in JSON format. The j1 format code is used to allow for the use of other layouts for the JSON output.
The result will look something like the following:
```json
{
"current_condition": [
{
"FeelsLikeC": "25",
"FeelsLikeF": "76",
"cloudcover": "100",
"humidity": "76",
"observation_time": "04:08 PM",
"precipMM": "0.2",
"pressure": "1019",
"temp_C": "22",
"temp_F": "72",
"uvIndex": 5,
"visibility": "16",
"weatherCode": "122",
"weatherDesc": [
{
"value": "Overcast"
}
],
"weatherIconUrl": [
{
"value": ""
}
],
"winddir16Point": "NNE",
"winddirDegree": "20",
"windspeedKmph": "7",
"windspeedMiles": "4"
}
],
...
```
Most of these values are self-explanatory, aside from `weatherCode`. The `weatherCode` is an enumeration which you can find at either [the WorldWeatherOnline website](https://www.worldweatheronline.com/developer/api/docs/weather-icons.aspx) or [in the wttr.in source code](https://github.com/chubin/wttr.in/blob/master/lib/constants.py).
### Prometheus Metrics Output
The [Prometheus](https://github.com/prometheus/prometheus) Metrics format is a feature providing access to *wttr.in* data through an easy-to-parse format for monitoring systems, without requiring the user to create a complex script to reinterpret wttr.in's graphical output.
To fetch information in Prometheus format, use the following syntax:
$ curl wttr.in/Detroit?format=p1
This will fetch information on the Detroit region in Prometheus Metrics format. The `p1` format code is used to allow for the use of other layouts for the Prometheus Metrics output.
A possible configuration for Prometheus could look like this:
```yaml
- job_name: 'wttr_in_detroit'
static_configs:
- targets: ['wttr.in']
metrics_path: '/Detroit'
params:
format: ['p1']
```
The result will look something like the following:
# HELP temperature_feels_like_celsius Feels Like Temperature in Celsius
temperature_feels_like_celsius{forecast="current"} 7
# HELP temperature_feels_like_fahrenheit Feels Like Temperature in Fahrenheit
temperature_feels_like_fahrenheit{forecast="current"} 45
[truncated]
...
## Moon phases
wttr.in can also be used to check the phase of the Moon. This example shows how to see the current Moon phase
in the full-output mode:
$ curl wttr.in/Moon
Get the Moon phase for a particular date by adding `@YYYY-MM-DD`:
$ curl wttr.in/Moon@2016-12-25
The Moon phase information uses [pyphoon](https://github.com/chubin/pyphoon) as its backend.
To get the moon phase information in the online mode, use `%m`:
$ curl wttr.in/London?format=%m
🌖
Keep in mind that the Unicode representation of moonphases suffers 2 caveats:
- With some fonts, the representation `🌘` is ambiguous, for it either seem
almost-shadowed or almost-lit, depending on whether your terminal is in
light mode or dark mode. Relying on colored fonts like `noto-fonts` works
around this problem.
- The representation `🌘` is also ambiguous, for it means "last quarter" in
northern hemisphere, but "first quarter" in souther hemisphere. It also means
nothing in tropical zones. This is a limitation that
[Unicode](https://www.unicode.org/L2/L2017/17304-moon-var.pdf) is aware about.
But it has not been worked around at `wttr.in` yet.
See #247, #364 for the corresponding tracking issues,
and [pyphoon#1](https://github.com/chubin/pyphoon/issues/1) for pyphoon. Any help is welcome.
## Internationalization and localization
wttr.in supports multilingual locations names that can be specified in any language in the world
(it may be surprising, but many locations in the world don't have an English name).
The query string should be specified in Unicode (hex-encoded or not). Spaces in the query string
must be replaced with `+`:
$ curl wttr.in/станция+Восток
Weather report: станция Восток
Overcast
.--. -65 -47 °C
.-( ). ↑ 23 km/h
(___.__)__) 15 km
0.0 mm
The language used for the output (except the location name) does not depend on the input language
and it is either English (by default) or the preferred language of the browser (if the query
was issued from a browser) that is specified in the query headers (`Accept-Language`).
The language can be set explicitly when using console clients by using command-line options like this:
curl -H "Accept-Language: fr" wttr.in
http GET wttr.in Accept-Language:ru
The preferred language can be forced using the `lang` option:
$ curl wttr.in/Berlin?lang=de
The third option is to choose the language using the DNS name used in the query:
$ curl de.wttr.in/Berlin
wttr.in is currently translated into 54 languages, and the number of supported languages is constantly growing.
See [/:translation](http://wttr.in/:translation) to learn more about the translation process,
to see the list of supported languages and contributors, or to know how you can help to translate wttr.in
in your language.
![Queries to wttr.in in various languages](https://pbs.twimg.com/media/C7hShiDXQAES6z1.jpg)
## Windows Users
There are currently two Windows related issues that prevent the examples found on this page from working exactly as expected out of the box. Until Microsoft fixes the issues, there are a few workarounds. To circumvent both issues you may use a shell such as `bash` on the [Windows Subsystem for Linux (WSL)](https://docs.microsoft.com/en-us/windows/wsl/install-win10) or read on for alternative solutions.
### Garbage characters in the output
There is a limitation of the current Win32 version of `curl`. Until the [Win32 curl issue](https://github.com/chubin/wttr.in/issues/18#issuecomment-474145551) is resolved and rolled out in a future Windows release, it is recommended that you use Powershells `Invoke-Web-Request` command instead:
- `(Invoke-WebRequest http://wttr.in).Content`
### Missing or double wide diagonal wind direction characters
The second issue is regarding the width of the diagonal arrow glyphs that some Windows Terminal Applications such as the default `conhost.exe` use. At the time of writing this, `ConEmu.exe`, `ConEmu64.exe` and Terminal Applications built on top of ConEmu such as Cmder (`cmder.exe`) use these double-wide glyphs by default. The result is the same with all of these programs, either a missing character for certain wind directions or a broken table in the output or both. Some third-party Terminal Applications have addressed the wind direction glyph issue but that fix depends on the font and the Terminal Application you are using.
One way to display the diagonal wind direction glyphs in your Terminal Application is to use [Windows Terminal](https://www.microsoft.com/en-us/p/windows-terminal-preview/9n0dx20hk701?activetab=pivot:overviewtab) which is currently available in the [Microsoft Store](https://www.microsoft.com/en-us/p/windows-terminal-preview/9n0dx20hk701?activetab=pivot:overviewtab). Windows Terminal is currently a preview release and will be rolled out as the default Terminal Application in an upcoming release. If your output is still skewed after using Windows Terminal then try maximizing the terminal window.
Another way you can display the diagonal wind direction is to swap out the problematic characters with forward and backward slashes as shown [here](https://github.com/chubin/wttr.in/issues/18#issuecomment-405640892).
## Installation
To install the application:
1. Install external dependencies
2. Install Python dependencies used by the service
3. Configure IP2Location (optional)
4. Get a WorldWeatherOnline API and configure wego
5. Configure wttr.in
6. Configure the HTTP-frontend service
### Install external dependencies
wttr.in has the following external dependencies:
* [golang](https://golang.org/doc/install), wego dependency
* [wego](https://github.com/schachmat/wego), weather client for terminal
After you install [golang](https://golang.org/doc/install), install `wego`:
$ go get -u github.com/schachmat/wego
$ go install github.com/schachmat/wego
### Install Python dependencies
Python requirements:
* Flask
* geoip2
* geopy
* requests
* gevent
If you want to get weather reports as PNG files, you'll also need to install:
* PIL
* pyte (>=0.6)
* necessary fonts
You can install most of them using `pip`.
Some python package use LLVM, so install it first:
$ apt-get install llvm-7 llvm-7-dev
If `virtualenv` is used:
$ virtualenv -p python3 ve
$ ve/bin/pip3 install -r requirements.txt
$ ve/bin/python3 bin/srv.py
Also, you need to install the geoip2 database.
You can use a free database GeoLite2 that can be downloaded from (http://dev.maxmind.com/geoip/geoip2/geolite2/).
### Configure IP2Location (optional)
If you want to use the IP2location service for IP-addresses that are not covered by GeoLite2,
you have to obtain a API key of that service, and after that save into the `~/.ip2location.key` file:
```
$ echo 'YOUR_IP2LOCATION_KEY' > ~/.ip2location.key
```
If you don't have this file, the service will be silently skipped (it is not a big problem,
because the MaxMind database is pretty good).
### Installation with Docker
* Install Docker
* Build Docker Image
* These files should be mounted by the user at runtime:
```
/root/.wegorc
/root/.ip2location.key (optional)
/app/airports.dat
/app/GeoLite2-City.mmdb
```
### Get a WorldWeatherOnline key and configure wego
To get a WorldWeatherOnline API key, you must register here:
https://developer.worldweatheronline.com/auth/register
After you have a WorldWeatherOnline key, you can save it into the
WWO key file: `~/.wwo.key`
Also, you have to specify the key in the `wego` configuration:
```json
$ cat ~/.wegorc
{
"APIKey": "00XXXXXXXXXXXXXXXXXXXXXXXXXXX",
"City": "London",
"Numdays": 3,
"Imperial": false,
"Lang": "en"
}
```
The `City` parameter in `~/.wegorc` is ignored.
### Configure wttr.in
Configure the following environment variables that define the path to the local `wttr.in`
installation, to the GeoLite database, and to the `wego` installation. For example:
```bash
export WTTR_MYDIR="/home/igor/wttr.in"
export WTTR_GEOLITE="/home/igor/wttr.in/GeoLite2-City.mmdb"
export WTTR_WEGO="/home/igor/go/bin/wego"
export WTTR_LISTEN_HOST="0.0.0.0"
export WTTR_LISTEN_PORT="8002"
```
### Configure the HTTP-frontend service
It's recommended that you also configure the web server that will be used to access the service:
```nginx
server {
listen [::]:80;
server_name wttr.in *.wttr.in;
access_log /var/log/nginx/wttr.in-access.log main;
error_log /var/log/nginx/wttr.in-error.log;
location / {
proxy_pass http://127.0.0.1:8002;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
client_max_body_size 10m;
client_body_buffer_size 128k;
proxy_connect_timeout 90;
proxy_send_timeout 90;
proxy_read_timeout 90;
proxy_buffer_size 4k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;
proxy_temp_file_write_size 64k;
expires off;
}
}
```

View file

@ -1,114 +0,0 @@
import gevent
from gevent.pywsgi import WSGIServer
from gevent.queue import Queue
from gevent.monkey import patch_all
from gevent.subprocess import Popen, PIPE, STDOUT
patch_all()
import sys
import os
import json
from flask import Flask, request, render_template, send_from_directory, send_file, make_response, jsonify, Response
app = Flask(__name__)
MYDIR = os.path.abspath(os.path.dirname('__file__'))
sys.path.append(os.path.join(MYDIR, 'lib'))
CACHEDIR = os.path.join(MYDIR, 'cache')
from geopy.geocoders import Nominatim #, Mapzen
#geomapzen = Mapzen("mapzen-RBNbmcZ") # Nominatim()
geoosm = Nominatim(timeout=7, user_agent="wttrin-geo/0.0.2")
import airports
# from tzwhere import tzwhere
import timezonefinder
tf = timezonefinder.TimezoneFinder()
def load_cache(location_string):
try:
location_string = location_string.replace('/', '_')
cachefile = os.path.join(CACHEDIR, location_string)
return json.loads(open(cachefile, 'r').read())
except:
return None
def shorten_full_address(address):
parts = address.split(',')
if len(parts) > 6:
parts = parts[:2] + [x for x in parts[-4:] if len(x) < 20]
return ','.join(parts)
return address
def save_cache(location_string, answer):
location_string = location_string.replace('/', '_')
cachefile = os.path.join(CACHEDIR, location_string)
open(cachefile, 'w').write(json.dumps(answer))
def query_osm(location_string):
try:
location = geoosm.geocode(location_string)
return {
'address': location.address,
'latitude': location.latitude,
'longitude':location.longitude,
}
except Exception as e:
print(e)
return None
def add_timezone_information(geo_data):
# tzwhere_ = tzwhere.tzwhere()
# timezone_str = tzwhere_.tzNameAt(geo_data["latitude"], geo_data["longitude"])
timezone_str = tf.certain_timezone_at(lat=geo_data["latitude"], lng=geo_data["longitude"])
answer = geo_data.copy()
answer["timezone"] = timezone_str
return answer
@app.route("/<string:location>")
def find_location(location):
airport_gps_location = airports.get_airport_gps_location(location.upper().lstrip('~'))
is_airport = False
if airport_gps_location is not None:
location = airport_gps_location
is_airport = True
location = location.replace('+', ' ')
answer = load_cache(location)
loaded_answer = None
if answer is not None:
loaded_answer = answer.copy()
print("cache found: %s" % location)
if answer is None:
answer = query_osm(location)
if is_airport:
answer['address'] = shorten_full_address(answer['address'])
if "timezone" not in answer:
answer = add_timezone_information(answer)
if answer is not None and loaded_answer != answer:
save_cache(location, answer)
if answer is None:
return ""
else:
r = Response(json.dumps(answer))
r.headers["Content-Type"] = "text/json; charset=utf-8"
return r
app.config['JSON_AS_ASCII'] = False
server = WSGIServer(("127.0.0.1", 8004), app)
server.serve_forever()

View file

@ -1,291 +0,0 @@
#vim: fileencoding=utf-8
"""
The proxy server acts as a backend for the wttr.in service.
It caches the answers and handles various data sources transforming their
answers into format supported by the wttr.in service.
If WTTRIN_TEST is specified, it works in a special test mode:
it does not fetch and does not store the data in the cache,
but is using the fake data from "test/proxy-data".
"""
from __future__ import print_function
from gevent.pywsgi import WSGIServer
from gevent.monkey import patch_all
patch_all()
# pylint: disable=wrong-import-position,wrong-import-order
import sys
import os
import time
import json
import hashlib
import requests
import cyrtranslit
from flask import Flask, request
APP = Flask(__name__)
MYDIR = os.path.abspath(
os.path.dirname(os.path.dirname('__file__')))
sys.path.append("%s/lib/" % MYDIR)
from globals import PROXY_CACHEDIR, PROXY_HOST, PROXY_PORT, USE_METNO, USER_AGENT
from metno import create_standard_json_from_metno, metno_request
from translations import PROXY_LANGS
# pylint: enable=wrong-import-position
def is_testmode():
"""Server is running in the wttr.in test mode"""
return "WTTRIN_TEST" in os.environ
def load_translations():
"""
load all translations
"""
translations = {}
for f_name in PROXY_LANGS:
f_name = 'share/translations/%s.txt' % f_name
translation = {}
lang = f_name.split('/')[-1].split('.', 1)[0]
with open(f_name, "r") as f_file:
for line in f_file:
if ':' not in line:
continue
if line.count(':') == 3:
_, trans, orig, _ = line.strip().split(':', 4)
else:
_, trans, orig = line.strip().split(':', 3)
trans = trans.strip()
orig = orig.strip()
translation[orig] = trans
translations[lang] = translation
return translations
TRANSLATIONS = load_translations()
def _is_metno():
return USE_METNO
def _find_srv_for_query(path, query): # pylint: disable=unused-argument
if _is_metno():
return 'https://api.met.no'
return 'http://api.worldweatheronline.com'
def _cache_file(path, query):
"""Return cache file name for specified `path` and `query`
and for the current time.
Do smooth load on the server, expiration time
is slightly varied basing on the path+query sha1 hash digest.
"""
digest = hashlib.sha1(("%s %s" % (path, query)).encode("utf-8")).hexdigest()
digest_number = ord(digest[0].upper())
expiry_interval = 60*(digest_number+40)
timestamp = "%010d" % (int(time.time())//expiry_interval*expiry_interval)
filename = os.path.join(PROXY_CACHEDIR, timestamp, path, query)
return filename
def _load_content_and_headers(path, query):
if is_testmode():
cache_file = "test/proxy-data/data1"
else:
cache_file = _cache_file(path, query)
try:
return (open(cache_file, 'r').read(),
json.loads(open(cache_file+".headers", 'r').read()))
except IOError:
return None, None
def _touch_empty_file(path, query):
cache_file = _cache_file(path, query)
cache_dir = os.path.dirname(cache_file)
if not os.path.exists(cache_dir):
os.makedirs(cache_dir)
open(cache_file, 'w').write("")
def _save_content_and_headers(path, query, content, headers):
cache_file = _cache_file(path, query)
cache_dir = os.path.dirname(cache_file)
if not os.path.exists(cache_dir):
os.makedirs(cache_dir)
open(cache_file + ".headers", 'w').write(json.dumps(headers))
open(cache_file, 'wb').write(content)
def translate(text, lang):
"""
Translate `text` into `lang`
"""
translated = TRANSLATIONS.get(lang, {}).get(text, text)
if text == translated:
print("%s: %s" % (lang, text))
return translated
def cyr(to_translate):
"""
Transliterate `to_translate` from latin into cyrillic
"""
return cyrtranslit.to_cyrillic(to_translate)
def _patch_greek(original):
return original.replace(u"Ηλιόλουστη/ο", u"Ηλιόλουστη")
def add_translations(content, lang):
"""
Add `lang` translation to `content` (JSON)
returned by the data source
"""
if content == "{}":
return {}
languages_to_translate = TRANSLATIONS.keys()
try:
d = json.loads(content) # pylint: disable=invalid-name
except (ValueError, TypeError) as exception:
print("---")
print(exception)
print("---")
return {}
try:
weather_condition = d['data']['current_condition'
][0]['weatherDesc'][0]['value'].capitalize()
d['data']['current_condition'][0]['weatherDesc'][0]['value'] = \
weather_condition
if lang in languages_to_translate:
d['data']['current_condition'][0]['lang_%s' % lang] = \
[{'value': translate(weather_condition, lang)}]
elif lang == 'sr':
d['data']['current_condition'][0]['lang_%s' % lang] = \
[{'value': cyr(
d['data']['current_condition'][0]['lang_%s' % lang][0]['value']\
)}]
elif lang == 'el':
d['data']['current_condition'][0]['lang_%s' % lang] = \
[{'value': _patch_greek(
d['data']['current_condition'][0]['lang_%s' % lang][0]['value']\
)}]
elif lang == 'sr-lat':
d['data']['current_condition'][0]['lang_%s' % lang] = \
[{'value':d['data']['current_condition'][0]['lang_sr'][0]['value']\
}]
fixed_weather = []
for w in d['data']['weather']: # pylint: disable=invalid-name
fixed_hourly = []
for h in w['hourly']: # pylint: disable=invalid-name
weather_condition = h['weatherDesc'][0]['value']
if lang in languages_to_translate:
h['lang_%s' % lang] = \
[{'value': translate(weather_condition, lang)}]
elif lang == 'sr':
h['lang_%s' % lang] = \
[{'value': cyr(h['lang_%s' % lang][0]['value'])}]
elif lang == 'el':
h['lang_%s' % lang] = \
[{'value': _patch_greek(h['lang_%s' % lang][0]['value'])}]
elif lang == 'sr-lat':
h['lang_%s' % lang] = \
[{'value': h['lang_sr'][0]['value']}]
fixed_hourly.append(h)
w['hourly'] = fixed_hourly
fixed_weather.append(w)
d['data']['weather'] = fixed_weather
content = json.dumps(d)
except (IndexError, ValueError) as exception:
print(exception)
return content
def _fetch_content_and_headers(path, query_string, **kwargs):
content, headers = _load_content_and_headers(path, query_string)
if content is None:
srv = _find_srv_for_query(path, query_string)
url = '%s/%s?%s' % (srv, path, query_string)
attempts = 10
response = None
while attempts:
try:
response = requests.get(url, timeout=2, **kwargs)
except requests.ReadTimeout:
attempts -= 1
continue
try:
json.loads(response.content)
break
except ValueError:
attempts -= 1
_touch_empty_file(path, query_string)
if response:
headers = {}
headers['Content-Type'] = response.headers['content-type']
_save_content_and_headers(path, query_string, response.content, headers)
content = response.content
else:
content = "{}"
else:
print("cache found")
return content, headers
@APP.route("/<path:path>")
def proxy(path):
"""
Main proxy function. Handles incoming HTTP queries.
"""
lang = request.args.get('lang', 'en')
query_string = request.query_string.decode("utf-8")
query_string = query_string.replace('sr-lat', 'sr')
query_string = query_string.replace('lang=None', 'lang=en')
content = ""
headers = ""
if _is_metno():
path, query, days = metno_request(path, query_string)
if USER_AGENT == '':
raise ValueError('User agent must be set to adhere to metno ToS: https://api.met.no/doc/TermsOfService')
content, headers = _fetch_content_and_headers(path, query, headers={
'User-Agent': USER_AGENT
})
content = create_standard_json_from_metno(content, days)
else:
# WWO tweaks
query_string += "&extra=localObsTime"
query_string += "&includelocation=yes"
content, headers = _fetch_content_and_headers(path, query)
content = add_translations(content, lang)
return content, 200, headers
if __name__ == "__main__":
#app.run(host='0.0.0.0', port=5001, debug=False)
#app.debug = True
if len(sys.argv) == 1:
bind_addr = "0.0.0.0"
SERVER = WSGIServer((bind_addr, PROXY_PORT), APP)
SERVER.serve_forever()
else:
print('running single request from command line arg')
APP.testing = True
with APP.test_client() as c:
resp = c.get(sys.argv[1])
print('Status: ' + resp.status)
# print('Headers: ' + dumps(resp.headers))
print(resp.data.decode('utf-8'))

View file

@ -1,56 +0,0 @@
#!/usr/bin/env python
# vim: set encoding=utf-8
from gevent.pywsgi import WSGIServer
from gevent.monkey import patch_all
patch_all()
# pylint: disable=wrong-import-position,wrong-import-order
import sys
import os
import jinja2
from flask import Flask, request, send_from_directory
APP = Flask(__name__)
MYDIR = os.path.abspath(
os.path.dirname(os.path.dirname('__file__')))
sys.path.append("%s/lib/" % MYDIR)
import wttr_srv
from globals import TEMPLATES, STATIC, LISTEN_HOST, LISTEN_PORT
# pylint: enable=wrong-import-position,wrong-import-order
MY_LOADER = jinja2.ChoiceLoader([
APP.jinja_loader,
jinja2.FileSystemLoader(TEMPLATES),
])
APP.jinja_loader = MY_LOADER
@APP.route('/files/<path:path>')
def send_static(path):
"Send any static file located in /files/"
return send_from_directory(STATIC, path)
@APP.route('/favicon.ico')
def send_favicon():
"Send static file favicon.ico"
return send_from_directory(STATIC, 'favicon.ico')
@APP.route('/malformed-response.html')
def send_malformed():
"Send static file malformed-response.html"
return send_from_directory(STATIC, 'malformed-response.html')
@APP.route("/")
@APP.route("/<string:location>")
def wttr(location=None):
"Main function wrapper"
return wttr_srv.wttr(location, request)
SERVER = WSGIServer(
(LISTEN_HOST, int(os.environ.get('WTTRIN_SRV_PORT', LISTEN_PORT))),
APP)
SERVER.serve_forever()

View file

@ -1,79 +0,0 @@
package main
import (
"log"
"net/http"
"sync"
"time"
"github.com/robfig/cron"
)
var peakRequest30 sync.Map
var peakRequest60 sync.Map
func initPeakHandling() {
c := cron.New()
// cronTime := fmt.Sprintf("%d,%d * * * *", 30-prefetchInterval/60, 60-prefetchInterval/60)
c.AddFunc("24 * * * *", prefetchPeakRequests30)
c.AddFunc("54 * * * *", prefetchPeakRequests60)
c.Start()
}
func savePeakRequest(cacheDigest string, r *http.Request) {
_, min, _ := time.Now().Clock()
if min == 30 {
peakRequest30.Store(cacheDigest, *r)
} else if min == 0 {
peakRequest60.Store(cacheDigest, *r)
}
}
func prefetchRequest(r *http.Request) {
processRequest(r)
}
func syncMapLen(sm *sync.Map) int {
count := 0
f := func(key, value interface{}) bool {
// Not really certain about this part, don't know for sure
// if this is a good check for an entry's existence
if key == "" {
return false
}
count++
return true
}
sm.Range(f)
return count
}
func prefetchPeakRequests(peakRequestMap *sync.Map) {
peakRequestLen := syncMapLen(peakRequestMap)
log.Printf("PREFETCH: Prefetching %d requests\n", peakRequestLen)
if peakRequestLen == 0 {
return
}
sleepBetweenRequests := time.Duration(prefetchInterval*1000/peakRequestLen) * time.Millisecond
peakRequestMap.Range(func(key interface{}, value interface{}) bool {
go func(r http.Request) {
prefetchRequest(&r)
}(value.(http.Request))
peakRequestMap.Delete(key)
time.Sleep(sleepBetweenRequests)
return true
})
}
func prefetchPeakRequests30() {
prefetchPeakRequests(&peakRequest30)
}
func prefetchPeakRequests60() {
prefetchPeakRequests(&peakRequest60)
}

View file

@ -1,147 +0,0 @@
package main
import (
"fmt"
"io/ioutil"
"log"
"math/rand"
"net"
"net/http"
"strings"
"time"
)
func processRequest(r *http.Request) responseWithHeader {
var response responseWithHeader
if dontCache(r) {
return get(r)
}
cacheDigest := getCacheDigest(r)
foundInCache := false
savePeakRequest(cacheDigest, r)
cacheBody, ok := lruCache.Get(cacheDigest)
if ok {
cacheEntry := cacheBody.(responseWithHeader)
// if after all attempts we still have no answer,
// we try to make the query on our own
for attempts := 0; attempts < 300; attempts++ {
if !ok || !cacheEntry.InProgress {
break
}
time.Sleep(30 * time.Millisecond)
cacheBody, ok = lruCache.Get(cacheDigest)
cacheEntry = cacheBody.(responseWithHeader)
}
if cacheEntry.InProgress {
log.Printf("TIMEOUT: %s\n", cacheDigest)
}
if ok && !cacheEntry.InProgress && cacheEntry.Expires.After(time.Now()) {
response = cacheEntry
foundInCache = true
}
}
if !foundInCache {
lruCache.Add(cacheDigest, responseWithHeader{InProgress: true})
response = get(r)
if response.StatusCode == 200 || response.StatusCode == 304 {
lruCache.Add(cacheDigest, response)
} else {
log.Printf("REMOVE: %d response for %s from cache\n", response.StatusCode, cacheDigest)
lruCache.Remove(cacheDigest)
}
}
return response
}
func get(req *http.Request) responseWithHeader {
client := &http.Client{}
queryURL := fmt.Sprintf("http://%s%s", req.Host, req.RequestURI)
proxyReq, err := http.NewRequest(req.Method, queryURL, req.Body)
if err != nil {
log.Printf("Request: %s\n", err)
}
// proxyReq.Header.Set("Host", req.Host)
// proxyReq.Header.Set("X-Forwarded-For", req.RemoteAddr)
for header, values := range req.Header {
for _, value := range values {
proxyReq.Header.Add(header, value)
}
}
res, err := client.Do(proxyReq)
if err != nil {
panic(err)
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
log.Println(err)
}
return responseWithHeader{
InProgress: false,
Expires: time.Now().Add(time.Duration(randInt(1000, 1500)) * time.Second),
Body: body,
Header: res.Header,
StatusCode: res.StatusCode,
}
}
// implementation of the cache.get_signature of original wttr.in
func getCacheDigest(req *http.Request) string {
userAgent := req.Header.Get("User-Agent")
queryHost := req.Host
queryString := req.RequestURI
clientIPAddress := readUserIP(req)
lang := req.Header.Get("Accept-Language")
return fmt.Sprintf("%s:%s%s:%s:%s", userAgent, queryHost, queryString, clientIPAddress, lang)
}
// return true if request should not be cached
func dontCache(req *http.Request) bool {
// dont cache cyclic requests
loc := strings.Split(req.RequestURI, "?")[0]
if strings.Contains(loc, ":") {
return true
}
return false
}
func readUserIP(r *http.Request) string {
IPAddress := r.Header.Get("X-Real-Ip")
if IPAddress == "" {
IPAddress = r.Header.Get("X-Forwarded-For")
}
if IPAddress == "" {
IPAddress = r.RemoteAddr
var err error
IPAddress, _, err = net.SplitHostPort(IPAddress)
if err != nil {
log.Printf("ERROR: userip: %q is not IP:port\n", IPAddress)
}
}
return IPAddress
}
func randInt(min int, max int) int {
return min + rand.Intn(max-min)
}

View file

@ -1,69 +0,0 @@
package main
import (
"context"
"log"
"net"
"net/http"
"time"
lru "github.com/hashicorp/golang-lru"
)
const uplinkSrvAddr = "127.0.0.1:9002"
const uplinkTimeout = 30
const prefetchInterval = 300
const lruCacheSize = 12800
var lruCache *lru.Cache
type responseWithHeader struct {
InProgress bool // true if the request is being processed
Expires time.Time // expiration time of the cache entry
Body []byte
Header http.Header
StatusCode int // e.g. 200
}
func init() {
var err error
lruCache, err = lru.New(lruCacheSize)
if err != nil {
panic(err)
}
dialer := &net.Dialer{
Timeout: uplinkTimeout * time.Second,
KeepAlive: uplinkTimeout * time.Second,
DualStack: true,
}
http.DefaultTransport.(*http.Transport).DialContext = func(ctx context.Context, network, _ string) (net.Conn, error) {
return dialer.DialContext(ctx, network, uplinkSrvAddr)
}
initPeakHandling()
}
func copyHeader(dst, src http.Header) {
for k, vv := range src {
for _, v := range vv {
dst.Add(k, v)
}
}
}
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// printStat()
response := processRequest(r)
copyHeader(w.Header(), response.Header)
w.Header().Set("Access-Control-Allow-Origin", "*")
w.WriteHeader(response.StatusCode)
w.Write(response.Body)
})
log.Fatal(http.ListenAndServe(":8082", nil))
}

View file

@ -1,40 +0,0 @@
package main
import (
"log"
"sync"
"time"
)
type safeCounter struct {
v map[int]int
mux sync.Mutex
}
func (c *safeCounter) inc(key int) {
c.mux.Lock()
c.v[key]++
c.mux.Unlock()
}
// func (c *safeCounter) val(key int) int {
// c.mux.Lock()
// defer c.mux.Unlock()
// return c.v[key]
// }
//
// func (c *safeCounter) reset(key int) int {
// c.mux.Lock()
// defer c.mux.Unlock()
// result := c.v[key]
// c.v[key] = 0
// return result
// }
var queriesPerMinute safeCounter
func printStat() {
_, min, _ := time.Now().Clock()
queriesPerMinute.inc(min)
log.Printf("Processed %d requests\n", min)
}

View file

@ -1,22 +0,0 @@
import csv
AIRPORTS_DAT_FILE = '/home/igor/wttrin-geo/share/airports.dat'
def load_aiports_index():
file_ = open(AIRPORTS_DAT_FILE, "r")
reader = csv.reader(file_)
airport_index = {}
for line in reader:
airport_index[line[4]] = line
return airport_index
AIRPORTS_INDEX = load_aiports_index()
def get_airport_gps_location(iata_code):
if iata_code in AIRPORTS_INDEX:
airport = AIRPORTS_INDEX[iata_code]
return '%s,%s airport' % (airport[6], airport[7]) #, airport[1])
return None

View file

@ -1,34 +0,0 @@
TWITTER_BUTTON = """
<a href="https://twitter.com/igor_chubin?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-show-count="false">Follow @igor_chubin</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
"""
GITHUB_BUTTON = """
<a aria-label="Star chubin/wttr.in on GitHub" data-count-aria-label="# stargazers on GitHub" data-count-api="/repos/chubin/wttr.in#stargazers_count" data-count-href="/chubin/wttr.in/stargazers" data-show-count="true" data-icon="octicon-star" href="https://github.com/chubin/wttr.in" class="github-button">wttr.in</a>
"""
GITHUB_BUTTON_2 = """
<!-- Place this tag where you want the button to render. -->
<a aria-label="Star schachmat/wego on GitHub" data-count-aria-label="# stargazers on GitHub" data-count-api="/repos/schachmat/wego#stargazers_count" data-count-href="/schachmat/wego/stargazers" data-show-count="true" data-icon="octicon-star" href="https://github.com/schachmat/wego" class="github-button">wego</a>
"""
GITHUB_BUTTON_3 = """
<!-- Place this tag where you want the button to render. -->
<a aria-label="Star chubin/pyphoon on GitHub" data-count-aria-label="# stargazers on GitHub" data-count-api="/repos/chubin/pyphoon#stargazers_count" data-count-href="/chubin/pyphoon/stargazers" data-show-count="true" data-icon="octicon-star" href="https://github.com/chubin/pyphoon" class="github-button">pyphoon</a>
"""
GITHUB_BUTTON_FOOTER = """
<!-- Place this tag right after the last button or just before your close body tag. -->
<script async defer id="github-bjs" src="https://buttons.github.io/buttons.js"></script>
"""
def add_buttons(output):
"""
Add buttons to html output
"""
return output.replace('</body>',
(TWITTER_BUTTON
+ GITHUB_BUTTON
+ GITHUB_BUTTON_3
+ GITHUB_BUTTON_2
+ GITHUB_BUTTON_FOOTER) + '</body>')

View file

@ -1,156 +0,0 @@
"""
LRU-Cache implementation for formatted (`format=`) answers
"""
import datetime
import re
import time
import os
import hashlib
import random
import pytz
import pylru
from globals import LRU_CACHE
CACHE_SIZE = 10000
CACHE = pylru.lrucache(CACHE_SIZE)
# strings longer than this are stored not in ram
# but in the file cache
MIN_SIZE_FOR_FILECACHE = 80
def _update_answer(answer):
def _now_in_tz(timezone):
return datetime.datetime.now(pytz.timezone(timezone)).strftime("%H:%M:%S%z")
if isinstance(answer, str) and "%{{NOW(" in answer:
answer = re.sub(r"%{{NOW\(([^}]*)\)}}", lambda x: _now_in_tz(x.group(1)), answer)
return answer
def get_signature(user_agent, query_string, client_ip_address, lang):
"""
Get cache signature based on `user_agent`, `url_string`,
`lang`, and `client_ip_address`
Return `None` if query should not be cached.
"""
if "?" in query_string:
location = query_string.split("?", 1)[0]
else:
location = query_string
if location.startswith("http://"):
location = location[7:]
elif location.startswith("https://"):
location = location[8:]
if ":" in location:
return None
signature = "%s:%s:%s:%s" % \
(user_agent, query_string, client_ip_address, lang)
print(signature)
return signature
def get(signature):
"""
If `update_answer` is not True, return answer as it is
stored in the cache. Otherwise update it, using
the `_update_answer` function.
"""
if not signature:
return None
value_record = CACHE.get(signature)
if not value_record:
return None
value = value_record["val"]
expiry = value_record["expiry"]
if value and time.time() < expiry:
if value.startswith("file:") or value.startswith("bfile:"):
value = _read_from_file(signature, sighash=value)
if not value:
return None
return _update_answer(value)
return None
def _randint(minimum, maximum):
return random.randrange(maximum - minimum)
def store(signature, value):
"""
Store in cache `value` for `signature`
"""
if not signature:
return _update_answer(value)
if len(value) >= MIN_SIZE_FOR_FILECACHE:
value_to_store = _store_in_file(signature, value)
else:
value_to_store = value
value_record = {
"val": value_to_store,
"expiry": time.time() + _randint(1000, 2000),
}
CACHE[signature] = value_record
return _update_answer(value)
def _hash(signature):
return hashlib.md5(signature.encode("utf-8")).hexdigest()
def _store_in_file(signature, value):
"""Store `value` for `signature` in cache file.
Return file name (signature_hash) as the result.
`value` can be string as well as bytes.
Returned filename is prefixed with "file:" (for text files)
or "bfile:" (for binary files).
"""
signature_hash = _hash(signature)
filename = os.path.join(LRU_CACHE, signature_hash)
if not os.path.exists(LRU_CACHE):
os.makedirs(LRU_CACHE)
if isinstance(value, bytes):
mode = "wb"
signature_hash = "bfile:%s" % signature_hash
else:
mode = "w"
signature_hash = "file:%s" % signature_hash
with open(filename, mode) as f_cache:
f_cache.write(value)
return signature_hash
def _read_from_file(signature, sighash=None):
"""Read value for `signature` from cache file,
or return None if file is not found.
If `sighash` is specified, do not calculate file name
from signature, but use `sighash` instead.
`sigash` can be prefixed with "file:" (for text files)
or "bfile:" (for binary files).
"""
mode = "r"
if sighash:
if sighash.startswith("file:"):
sighash = sighash[5:]
elif sighash.startswith("bfile:"):
sighash = sighash[6:]
mode = "rb"
else:
sighash = _hash(signature)
filename = os.path.join(LRU_CACHE, sighash)
if not os.path.exists(filename):
return None
with open(filename, mode) as f_cache:
return f_cache.read()

View file

@ -1,298 +0,0 @@
#vim: fileencoding=utf-8
WWO_CODE = {
"113": "Sunny",
"116": "PartlyCloudy",
"119": "Cloudy",
"122": "VeryCloudy",
"143": "Fog",
"176": "LightShowers",
"179": "LightSleetShowers",
"182": "LightSleet",
"185": "LightSleet",
"200": "ThunderyShowers",
"227": "LightSnow",
"230": "HeavySnow",
"248": "Fog",
"260": "Fog",
"263": "LightShowers",
"266": "LightRain",
"281": "LightSleet",
"284": "LightSleet",
"293": "LightRain",
"296": "LightRain",
"299": "HeavyShowers",
"302": "HeavyRain",
"305": "HeavyShowers",
"308": "HeavyRain",
"311": "LightSleet",
"314": "LightSleet",
"317": "LightSleet",
"320": "LightSnow",
"323": "LightSnowShowers",
"326": "LightSnowShowers",
"329": "HeavySnow",
"332": "HeavySnow",
"335": "HeavySnowShowers",
"338": "HeavySnow",
"350": "LightSleet",
"353": "LightShowers",
"356": "HeavyShowers",
"359": "HeavyRain",
"362": "LightSleetShowers",
"365": "LightSleetShowers",
"368": "LightSnowShowers",
"371": "HeavySnowShowers",
"374": "LightSleetShowers",
"377": "LightSleet",
"386": "ThunderyShowers",
"389": "ThunderyHeavyRain",
"392": "ThunderySnowShowers",
"395": "HeavySnowShowers",
}
WEATHER_SYMBOL = {
"Unknown": "",
"Cloudy": "☁️",
"Fog": "🌫",
"HeavyRain": "🌧",
"HeavyShowers": "🌧",
"HeavySnow": "❄️",
"HeavySnowShowers": "❄️",
"LightRain": "🌦",
"LightShowers": "🌦",
"LightSleet": "🌧",
"LightSleetShowers": "🌧",
"LightSnow": "🌨",
"LightSnowShowers": "🌨",
"PartlyCloudy": "⛅️",
"Sunny": "☀️",
"ThunderyHeavyRain": "🌩",
"ThunderyShowers": "",
"ThunderySnowShowers": "",
"VeryCloudy": "☁️",
}
WEATHER_SYMBOL_WIDTH_VTE = {
"": 2,
"☁️": 1,
"🌫": 2,
"🌧": 2,
"🌧": 2,
"❄️": 1,
"❄️": 1,
"🌦": 1,
"🌦": 1,
"🌧": 1,
"🌧": 1,
"🌨": 2,
"🌨": 2,
"⛅️": 2,
"☀️": 1,
"🌩": 2,
"": 1,
"": 1,
"☁️": 1,
}
WIND_DIRECTION = [
"", "", "", "", "", "", "", "",
]
MOON_PHASES = (
"🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘"
)
WEATHER_SYMBOL_WI_DAY = {
"Unknown": "",
"Cloudy": "",
"Fog": "",
"HeavyRain": "",
"HeavyShowers": "",
"HeavySnow": "",
"HeavySnowShowers": "",
"LightRain": "",
"LightShowers": "",
"LightSleet": "",
"LightSleetShowers": "",
"LightSnow": "",
"LightSnowShowers": "",
"PartlyCloudy": "",
"Sunny": "",
"ThunderyHeavyRain": "",
"ThunderyShowers": "",
"ThunderySnowShowers": "",
"VeryCloudy": "",
}
WEATHER_SYMBOL_WI_NIGHT = {
"Unknown": "",
"Cloudy": "",
"Fog": "",
"HeavyRain": "",
"HeavyShowers": "",
"HeavySnow": "",
"HeavySnowShowers": "",
"LightRain": "",
"LightShowers": "",
"LightSleet": "",
"LightSleetShowers": "",
"LightSnow": "",
"LightSnowShowers": "",
"PartlyCloudy": "",
"Sunny": "",
"ThunderyHeavyRain": "",
"ThunderyShowers": "",
"ThunderySnowShowers": "",
"VeryCloudy": "",
}
WEATHER_SYMBOL_WIDTH_VTE_WI = {
}
WIND_DIRECTION_WI = [
"", "", "", "", "", "", "", "",
]
WIND_SCALE_WI = [
"", "", "", "", "", "", "", "", "", "", "", "", "",
]
MOON_PHASES_WI = (
"", "", "", "", "", "", "",
"", "", "", "", "", "", "",
"", "", "", "", "", "", "",
"", "", "", "", "", "", "",
)
WEATHER_SYMBOL_WEGO = {
"Unknown": [
" .-. ",
" __) ",
" ( ",
" `- ",
""],
"Sunny": [
"\033[38;5;226m \\ / \033[0m",
"\033[38;5;226m .-. \033[0m",
"\033[38;5;226m ― ( ) ― \033[0m",
"\033[38;5;226m `- \033[0m",
"\033[38;5;226m / \\ \033[0m"],
"PartlyCloudy": [
"\033[38;5;226m \\ /\033[0m ",
"\033[38;5;226m _ /\"\"\033[38;5;250m.-. \033[0m",
"\033[38;5;226m \\_\033[38;5;250m( ). \033[0m",
"\033[38;5;226m /\033[38;5;250m(___(__) \033[0m",
" "],
"Cloudy": [
" ",
"\033[38;5;250m .--. \033[0m",
"\033[38;5;250m .-( ). \033[0m",
"\033[38;5;250m (___.__)__) \033[0m",
" "],
"VeryCloudy": [
" ",
"\033[38;5;240;1m .--. \033[0m",
"\033[38;5;240;1m .-( ). \033[0m",
"\033[38;5;240;1m (___.__)__) \033[0m",
" "],
"LightShowers": [
"\033[38;5;226m _`/\"\"\033[38;5;250m.-. \033[0m",
"\033[38;5;226m ,\\_\033[38;5;250m( ). \033[0m",
"\033[38;5;226m /\033[38;5;250m(___(__) \033[0m",
"\033[38;5;111m \033[0m",
"\033[38;5;111m \033[0m"],
"HeavyShowers": [
"\033[38;5;226m _`/\"\"\033[38;5;240;1m.-. \033[0m",
"\033[38;5;226m ,\\_\033[38;5;240;1m( ). \033[0m",
"\033[38;5;226m /\033[38;5;240;1m(___(__) \033[0m",
"\033[38;5;21;1m \033[0m",
"\033[38;5;21;1m \033[0m"],
"LightSnowShowers": [
"\033[38;5;226m _`/\"\"\033[38;5;250m.-. \033[0m",
"\033[38;5;226m ,\\_\033[38;5;250m( ). \033[0m",
"\033[38;5;226m /\033[38;5;250m(___(__) \033[0m",
"\033[38;5;255m * * * \033[0m",
"\033[38;5;255m * * * \033[0m"],
"HeavySnowShowers": [
"\033[38;5;226m _`/\"\"\033[38;5;240;1m.-. \033[0m",
"\033[38;5;226m ,\\_\033[38;5;240;1m( ). \033[0m",
"\033[38;5;226m /\033[38;5;240;1m(___(__) \033[0m",
"\033[38;5;255;1m * * * * \033[0m",
"\033[38;5;255;1m * * * * \033[0m"],
"LightSleetShowers": [
"\033[38;5;226m _`/\"\"\033[38;5;250m.-. \033[0m",
"\033[38;5;226m ,\\_\033[38;5;250m( ). \033[0m",
"\033[38;5;226m /\033[38;5;250m(___(__) \033[0m",
"\033[38;5;111m \033[38;5;255m*\033[38;5;111m \033[38;5;255m* \033[0m",
"\033[38;5;255m *\033[38;5;111m \033[38;5;255m*\033[38;5;111m \033[0m"],
"ThunderyShowers": [
"\033[38;5;226m _`/\"\"\033[38;5;250m.-. \033[0m",
"\033[38;5;226m ,\\_\033[38;5;250m( ). \033[0m",
"\033[38;5;226m /\033[38;5;250m(___(__) \033[0m",
"\033[38;5;228;5m ⚡\033[38;5;111;25m \033[38;5;228;5m⚡\033[38;5;111;25m \033[0m",
"\033[38;5;111m \033[0m"],
"ThunderyHeavyRain": [
"\033[38;5;240;1m .-. \033[0m",
"\033[38;5;240;1m ( ). \033[0m",
"\033[38;5;240;1m (___(__) \033[0m",
"\033[38;5;21;1m \033[38;5;228;5m⚡\033[38;5;21;25m\033[38;5;228;5m⚡\033[38;5;21;25m \033[0m",
"\033[38;5;21;1m \033[38;5;228;5m⚡\033[38;5;21;25m \033[0m"],
"ThunderySnowShowers": [
"\033[38;5;226m _`/\"\"\033[38;5;250m.-. \033[0m",
"\033[38;5;226m ,\\_\033[38;5;250m( ). \033[0m",
"\033[38;5;226m /\033[38;5;250m(___(__) \033[0m",
"\033[38;5;255m *\033[38;5;228;5m⚡\033[38;5;255;25m*\033[38;5;228;5m⚡\033[38;5;255;25m* \033[0m",
"\033[38;5;255m * * * \033[0m"],
"LightRain": [
"\033[38;5;250m .-. \033[0m",
"\033[38;5;250m ( ). \033[0m",
"\033[38;5;250m (___(__) \033[0m",
"\033[38;5;111m \033[0m",
"\033[38;5;111m \033[0m"],
"HeavyRain": [
"\033[38;5;240;1m .-. \033[0m",
"\033[38;5;240;1m ( ). \033[0m",
"\033[38;5;240;1m (___(__) \033[0m",
"\033[38;5;21;1m \033[0m",
"\033[38;5;21;1m \033[0m"],
"LightSnow": [
"\033[38;5;250m .-. \033[0m",
"\033[38;5;250m ( ). \033[0m",
"\033[38;5;250m (___(__) \033[0m",
"\033[38;5;255m * * * \033[0m",
"\033[38;5;255m * * * \033[0m"],
"HeavySnow": [
"\033[38;5;240;1m .-. \033[0m",
"\033[38;5;240;1m ( ). \033[0m",
"\033[38;5;240;1m (___(__) \033[0m",
"\033[38;5;255;1m * * * * \033[0m",
"\033[38;5;255;1m * * * * \033[0m"],
"LightSleet": [
"\033[38;5;250m .-. \033[0m",
"\033[38;5;250m ( ). \033[0m",
"\033[38;5;250m (___(__) \033[0m",
"\033[38;5;111m \033[38;5;255m*\033[38;5;111m \033[38;5;255m* \033[0m",
"\033[38;5;255m *\033[38;5;111m \033[38;5;255m*\033[38;5;111m \033[0m"],
"Fog": [
" ",
"\033[38;5;251m _ - _ - _ - \033[0m",
"\033[38;5;251m _ - _ - _ \033[0m",
"\033[38;5;251m _ - _ - _ - \033[0m",
" "],
}
LOCALE = {
"af": "af_ZA", "ar": "ar_TN", "az": "az_AZ", "be": "be_BY", "bg": "bg_BG",
"bs": "bs_BA", "ca": "ca_ES", "cs": "cs_CZ", "cy": "cy_GB", "da": "da_DK",
"de": "de_DE", "el": "el_GR", "eo": "eo", "es": "es_ES", "et": "et_EE",
"fa": "fa_IR", "fi": "fi_FI", "fr": "fr_FR", "fy": "fy_NL", "ga": "ga_IE",
"he": "he_IL", "hr": "hr_HR", "hu": "hu_HU", "hy": "hy_AM", "ia": "ia",
"id": "id_ID", "is": "is_IS", "it": "it_IT", "ja": "ja_JP", "jv": "en_US",
"ka": "ka_GE", "ko": "ko_KR", "kk": "kk_KZ", "ky": "ky_KG", "lt": "lt_LT",
"lv": "lv_LV", "mk": "mk_MK", "ml": "ml_IN", "nb": "nb_NO", "nl": "nl_NL",
"nn": "nn_NO", "pt": "pt_PT", "pt-br":"pt_BR", "pl": "pl_PL", "ro": "ro_RO",
"ru": "ru_RU", "sv": "sv_SE", "sk": "sk_SK", "sl": "sl_SI", "sr": "sr_RS",
"sr-lat": "sr_RS@latin", "sw": "sw_KE", "th": "th_TH", "tr": "tr_TR", "uk": "uk_UA",
"uz": "uz_UZ", "vi": "vi_VN", "zh": "zh_TW", "zu": "zu_ZA",
}

View file

@ -1,62 +0,0 @@
#!/usr/bin/env python
#vim: fileencoding=utf-8
"""
At the moment, Pillow library does not support colorful emojis,
that is why emojis must be extracted to external files first,
and then they must be handled as usual graphical objects
and not as text.
The files are extracted using Imagemagick.
Usage:
ve/bi/python lib/extract_emoji.py
"""
import subprocess
EMOJIS = [
"",
"☁️",
"🌫",
"🌧",
"🌧",
"❄️",
"❄️",
"🌦",
"🌦",
"🌧",
"🌧",
"🌨",
"🌨",
"⛅️",
"☀️",
"🌩",
"",
"",
"☁️",
"🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘"
]
def extract_emojis_to_directory(dirname):
"""
Extract emoji from an emoji font, to separate files.
"""
emoji_font = "Noto Color Emoji"
emoji_size = 30
for emoji in EMOJIS:
filename = "%s/%s.png" % (dirname, emoji)
convert_string = [
"convert", "-background", "black", "-size", "%sx%s" % (emoji_size, emoji_size),
"-set", "colorspace", "sRGB",
"pango:<span font=\"%s\" size=\"20000\">%s</span>" % (emoji_font, emoji),
filename
]
subprocess.Popen(convert_string)
if __name__ == '__main__':
extract_emojis_to_directory("share/emoji")

View file

@ -1,105 +0,0 @@
"""
Human readable description of the available data fields
describing current weather, weather forecast, and astronomical data
"""
DESCRIPTION = {
# current condition fields
"FeelsLikeC": (
"Feels Like Temperature in Celsius",
"temperature_feels_like_celsius"),
"FeelsLikeF": (
"Feels Like Temperature in Fahrenheit",
"temperature_feels_like_fahrenheit"),
"cloudcover": (
"Cloud Coverage in Percent",
"cloudcover_percentage"),
"humidity": (
"Humidity in Percent",
"humidity_percentage"),
"precipMM": (
"Precipitation (Rainfall) in mm",
"precipitation_mm"),
"pressure": (
"Air pressure in hPa",
"pressure_hpa"),
"temp_C": (
"Temperature in Celsius",
"temperature_celsius"),
"temp_F": (
"Temperature in Fahrenheit",
"temperature_fahrenheit"),
"uvIndex": (
"Ultaviolet Radiation Index",
"uv_index"),
"visibility": (
"Visible Distance in Kilometres",
"visibility"),
"weatherCode": (
"Code to describe Weather Condition",
"weather_code"),
"winddirDegree": (
"Wind Direction in Degree",
"winddir_degree"),
"windspeedKmph": (
"Wind Speed in Kilometres per Hour",
"windspeed_kmph"),
"windspeedMiles": (
"Wind Speed in Miles per Hour",
"windspeed_mph"),
"observation_time": (
"Minutes since start of the day the observation happened",
"observation_time"),
# fields with `description`
"weatherDesc": (
"Weather Description",
"weather_desc"),
"winddir16Point": (
"Wind Direction on a 16-wind compass rose",
"winddir_16_point"),
# forecast fields
"maxtempC": (
"Maximum Temperature in Celsius",
"temperature_celsius_maximum"),
"maxtempF": (
"Maximum Temperature in Fahrenheit",
"temperature_fahrenheit_maximum"),
"mintempC": (
"Minimum Temperature in Celsius",
"temperature_celsius_minimum"),
"mintempF": (
"Minimum Temperature in Fahrenheit",
"temperature_fahrenheit_minimum"),
"sunHour":(
"Hours of sunlight",
"sun_hour"),
"totalSnow_cm":(
"Total snowfall in cm",
"snowfall_cm"),
# astronomy fields
"moon_illumination": (
"Percentage of the moon illuminated",
"astronomy_moon_illumination"),
# astronomy fields with description
"moon_phase": (
"Phase of the moon",
"astronomy_moon_phase"),
# astronomy fields with time
"moonrise": (
"Minutes since start of the day untill the moon appears above the horizon",
"astronomy_moonrise_min"),
"moonset": (
"Minutes since start of the day untill the moon disappears below the horizon",
"astronomy_moonset_min"),
"sunrise": (
"Minutes since start of the day untill the sun appears above the horizon",
"astronomy_sunrise_min"),
"sunset": (
"Minutes since start of the day untill the moon disappears below the horizon",
"astronomy_sunset_min"),
}

View file

@ -1,283 +0,0 @@
#!/usr/bin/python
#vim: encoding=utf-8
# pylint: disable=wrong-import-position,wrong-import-order,redefined-builtin
"""
This module is used to generate png-files for wttr.in queries.
The only exported function is:
* render_ansi(png_file, text, options=None)
`render_ansi` is the main function of the module,
which does rendering of stream into a PNG-file.
The module uses PIL for graphical tasks, and pyte for rendering
of ANSI stream into terminal representation.
"""
from __future__ import print_function
import sys
import io
import os
import glob
from PIL import Image, ImageFont, ImageDraw
import pyte.screens
import emoji
import grapheme
from . import unicodedata2
sys.path.insert(0, "..")
import constants
import globals
COLS = 180
ROWS = 100
CHAR_WIDTH = 8
CHAR_HEIGHT = 14
FONT_SIZE = 13
FONT_CAT = {
'default': "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf",
'Cyrillic': "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf",
'Greek': "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf",
'Arabic': "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf",
'Hebrew': "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf",
'Han': "/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc",
'Hiragana': "/usr/share/fonts/truetype/motoya-l-cedar/MTLc3m.ttf",
'Katakana': "/usr/share/fonts/truetype/motoya-l-cedar/MTLc3m.ttf",
'Hangul': "/usr/share/fonts/truetype/lexi/LexiGulim.ttf",
'Braille': "/usr/share/fonts/truetype/ancient-scripts/Symbola_hint.ttf",
'Emoji': "/usr/share/fonts/truetype/ancient-scripts/Symbola_hint.ttf",
}
#
# How to find font for non-standard scripts:
#
# $ fc-list :lang=ja
#
# GNU/Debian packages, that the fonts come from:
#
# * fonts-dejavu-core
# * fonts-wqy-zenhei (Han)
# * fonts-motoya-l-cedar (Hiragana/Katakana)
# * fonts-lexi-gulim (Hangul)
# * fonts-symbola (Braille/Emoji)
#
def render_ansi(text, options=None):
"""Render `text` (terminal sequence) in a PNG file
paying attention to passed command line `options`.
Return: file content
"""
screen = pyte.screens.Screen(COLS, ROWS)
screen.set_mode(pyte.modes.LNM)
stream = pyte.Stream(screen)
text, graphemes = _fix_graphemes(text)
stream.feed(text)
buf = sorted(screen.buffer.items(), key=lambda x: x[0])
buf = [[x[1] for x in sorted(line[1].items(), key=lambda x: x[0])] for line in buf]
return _gen_term(buf, graphemes, options=options)
def _color_mapping(color):
"""Convert pyte color to PIL color
Return: tuple of color values (R,G,B)
"""
if color == 'default':
return 'lightgray'
if color in ['green', 'black', 'cyan', 'blue', 'brown']:
return color
try:
return (
int(color[0:2], 16),
int(color[2:4], 16),
int(color[4:6], 16))
except (ValueError, IndexError):
# if we do not know this color and it can not be decoded as RGB,
# print it and return it as it is (will be displayed as black)
# print color
return color
return color
def _strip_buf(buf):
"""Strips empty spaces from behind and from the right side.
(from the right side is not yet implemented)
"""
def empty_line(line):
"Returns True if the line consists from spaces"
return all(x.data == ' ' for x in line)
def line_len(line):
"Returns len of the line excluding spaces from the right"
last_pos = len(line)
while last_pos > 0 and line[last_pos-1].data == ' ':
last_pos -= 1
return last_pos
number_of_lines = 0
for line in buf[::-1]:
if not empty_line(line):
break
number_of_lines += 1
if number_of_lines:
buf = buf[:-number_of_lines]
max_len = max(line_len(x) for x in buf)
buf = [line[:max_len] for line in buf]
return buf
def _script_category(char):
"""Returns category of a Unicode character
Possible values:
default, Cyrillic, Greek, Han, Hiragana
"""
if char in emoji.UNICODE_EMOJI:
return "Emoji"
cat = unicodedata2.script_cat(char)[0]
if char == u'':
return 'Han'
if cat in ['Latin', 'Common']:
return 'default'
return cat
def _load_emojilib():
"""Load known emojis from a directory, and return dictionary
of PIL Image objects correspodent to the loaded emojis.
Each emoji is resized to the CHAR_HEIGHT size.
"""
emojilib = {}
for filename in glob.glob("share/emoji/*.png"):
character = os.path.basename(filename)[:-4]
emojilib[character] = \
Image.open(filename).resize((CHAR_HEIGHT, CHAR_HEIGHT))
return emojilib
# pylint: disable=too-many-locals,too-many-branches,too-many-statements
def _gen_term(buf, graphemes, options=None):
"""Renders rendered pyte buffer `buf` and list of workaround `graphemes`
to a PNG file, and return its content
"""
if not options:
options = {}
current_grapheme = 0
buf = _strip_buf(buf)
cols = max(len(x) for x in buf)
rows = len(buf)
image = Image.new('RGB', (cols * CHAR_WIDTH, rows * CHAR_HEIGHT))
buf = buf[-ROWS:]
draw = ImageDraw.Draw(image)
font = {}
for cat in FONT_CAT:
font[cat] = ImageFont.truetype(FONT_CAT[cat], FONT_SIZE)
emojilib = _load_emojilib()
x_pos = 0
y_pos = 0
for line in buf:
x_pos = 0
for char in line:
current_color = _color_mapping(char.fg)
if char.bg != 'default':
draw.rectangle(
((x_pos, y_pos),
(x_pos+CHAR_WIDTH, y_pos+CHAR_HEIGHT)),
fill=_color_mapping(char.bg))
if char.data == "!":
try:
data = graphemes[current_grapheme]
except IndexError:
pass
current_grapheme += 1
else:
data = char.data
if data:
cat = _script_category(data[0])
if cat not in font:
globals.log("Unknown font category: %s" % cat)
if cat == 'Emoji' and emojilib.get(data):
image.paste(emojilib.get(data), (x_pos, y_pos))
else:
draw.text(
(x_pos, y_pos),
data,
font=font.get(cat, font.get('default')),
fill=current_color)
x_pos += CHAR_WIDTH * constants.WEATHER_SYMBOL_WIDTH_VTE.get(data, 1)
y_pos += CHAR_HEIGHT
if 'transparency' in options:
transparency = options.get('transparency', '255')
try:
transparency = int(transparency)
except ValueError:
transparency = 255
if transparency < 0:
transparency = 0
if transparency > 255:
transparency = 255
image = image.convert("RGBA")
datas = image.getdata()
new_data = []
for item in datas:
new_item = tuple(list(item[:3]) + [transparency])
new_data.append(new_item)
image.putdata(new_data)
img_bytes = io.BytesIO()
image.save(img_bytes, format="png")
return img_bytes.getvalue()
def _fix_graphemes(text):
"""
Extract long graphemes sequences that can't be handled
by pyte correctly because of the bug pyte#131.
Graphemes are omited and replaced with placeholders,
and returned as a list.
Return:
text_without_graphemes, graphemes
"""
output = ""
graphemes = []
for gra in grapheme.graphemes(text):
if len(gra) > 1:
character = "!"
graphemes.append(gra)
else:
character = gra
output += character
return output, graphemes

View file

@ -1,612 +0,0 @@
# downloaded from https://gist.github.com/2204527
# described/recommended here:
#
# http://stackoverflow.com/questions/9868792/find-out-the-unicode-script-of-a-character
#
from __future__ import print_function
from unicodedata import *
script_data = {
"names":['Common', 'Latin', 'Greek', 'Cyrillic', 'Armenian', 'Hebrew', 'Arabic',
'Syriac', 'Thaana', 'Devanagari', 'Bengali', 'Gurmukhi', 'Gujarati', 'Oriya',
'Tamil', 'Telugu', 'Kannada', 'Malayalam', 'Sinhala', 'Thai', 'Lao', 'Tibetan',
'Myanmar', 'Georgian', 'Hangul', 'Ethiopic', 'Cherokee', 'Canadian_Aboriginal',
'Ogham', 'Runic', 'Khmer', 'Mongolian', 'Hiragana', 'Katakana', 'Bopomofo',
'Han', 'Yi', 'Old_Italic', 'Gothic', 'Deseret', 'Inherited', 'Tagalog',
'Hanunoo', 'Buhid', 'Tagbanwa', 'Limbu', 'Tai_Le', 'Linear_B', 'Ugaritic',
'Shavian', 'Osmanya', 'Cypriot', 'Braille', 'Buginese', 'Coptic', 'New_Tai_Lue',
'Glagolitic', 'Tifinagh', 'Syloti_Nagri', 'Old_Persian', 'Kharoshthi',
'Balinese', 'Cuneiform', 'Phoenician', 'Phags_Pa', 'Nko', 'Sundanese', 'Lepcha',
'Ol_Chiki', 'Vai', 'Saurashtra', 'Kayah_Li', 'Rejang', 'Lycian', 'Carian',
'Lydian', 'Cham', 'Tai_Tham', 'Tai_Viet', 'Avestan', 'Egyptian_Hieroglyphs',
'Samaritan', 'Lisu', 'Bamum', 'Javanese', 'Meetei_Mayek', 'Imperial_Aramaic',
'Old_South_Arabian', 'Inscriptional_Parthian', 'Inscriptional_Pahlavi',
'Old_Turkic', 'Kaithi', 'Batak', 'Brahmi', 'Mandaic', 'Chakma',
'Meroitic_Cursive', 'Meroitic_Hieroglyphs', 'Miao', 'Sharada', 'Sora_Sompeng',
'Takri'],
"cats":['Cc', 'Zs', 'Po', 'Sc', 'Ps', 'Pe', 'Sm', 'Pd', 'Nd', 'Sk', 'Pc', 'So', 'Pi',
'Cf', 'No', 'L', 'Pf', 'Lm', 'Mc', 'Lo', 'Zl', 'Zp', 'Nl', 'Mn', 'Me'],
"idx":[
(0x0,0x1f,0,0), (0x20,0x20,0,1), (0x21,0x23,0,2), (0x24,0x24,0,3),
(0x25,0x27,0,2), (0x28,0x28,0,4), (0x29,0x29,0,5), (0x2a,0x2a,0,2),
(0x2b,0x2b,0,6), (0x2c,0x2c,0,2), (0x2d,0x2d,0,7), (0x2e,0x2f,0,2),
(0x30,0x39,0,8), (0x3a,0x3b,0,2), (0x3c,0x3e,0,6), (0x3f,0x40,0,2),
(0x41,0x5a,1,15), (0x5b,0x5b,0,4), (0x5c,0x5c,0,2), (0x5d,0x5d,0,5),
(0x5e,0x5e,0,9), (0x5f,0x5f,0,10), (0x60,0x60,0,9), (0x61,0x7a,1,15),
(0x7b,0x7b,0,4), (0x7c,0x7c,0,6), (0x7d,0x7d,0,5), (0x7e,0x7e,0,6),
(0x7f,0x9f,0,0), (0xa0,0xa0,0,1), (0xa1,0xa1,0,2), (0xa2,0xa5,0,3),
(0xa6,0xa6,0,11), (0xa7,0xa7,0,2), (0xa8,0xa8,0,9), (0xa9,0xa9,0,11),
(0xaa,0xaa,1,19), (0xab,0xab,0,12), (0xac,0xac,0,6), (0xad,0xad,0,13),
(0xae,0xae,0,11), (0xaf,0xaf,0,9), (0xb0,0xb0,0,11), (0xb1,0xb1,0,6),
(0xb2,0xb3,0,14), (0xb4,0xb4,0,9), (0xb5,0xb5,0,15), (0xb6,0xb7,0,2),
(0xb8,0xb8,0,9), (0xb9,0xb9,0,14), (0xba,0xba,1,19), (0xbb,0xbb,0,16),
(0xbc,0xbe,0,14), (0xbf,0xbf,0,2), (0xc0,0xd6,1,15), (0xd7,0xd7,0,6),
(0xd8,0xf6,1,15), (0xf7,0xf7,0,6), (0xf8,0x1ba,1,15), (0x1bb,0x1bb,1,19),
(0x1bc,0x1bf,1,15), (0x1c0,0x1c3,1,19), (0x1c4,0x293,1,15), (0x294,0x294,1,19),
(0x295,0x2af,1,15), (0x2b0,0x2b8,1,17), (0x2b9,0x2c1,0,17), (0x2c2,0x2c5,0,9),
(0x2c6,0x2d1,0,17), (0x2d2,0x2df,0,9), (0x2e0,0x2e4,1,17), (0x2e5,0x2e9,0,9),
(0x2ea,0x2eb,34,9), (0x2ec,0x2ec,0,17), (0x2ed,0x2ed,0,9), (0x2ee,0x2ee,0,17),
(0x2ef,0x2ff,0,9), (0x300,0x36f,40,23), (0x370,0x373,2,15), (0x374,0x374,0,17),
(0x375,0x375,2,9), (0x376,0x377,2,15), (0x37a,0x37a,2,17), (0x37b,0x37d,2,15),
(0x37e,0x37e,0,2), (0x384,0x384,2,9), (0x385,0x385,0,9), (0x386,0x386,2,15),
(0x387,0x387,0,2), (0x388,0x38a,2,15), (0x38c,0x38c,2,15), (0x38e,0x3a1,2,15),
(0x3a3,0x3e1,2,15), (0x3e2,0x3ef,54,15), (0x3f0,0x3f5,2,15), (0x3f6,0x3f6,2,6),
(0x3f7,0x3ff,2,15), (0x400,0x481,3,15), (0x482,0x482,3,11), (0x483,0x484,3,23),
(0x485,0x486,40,23), (0x487,0x487,3,23), (0x488,0x489,3,24), (0x48a,0x527,3,15),
(0x531,0x556,4,15), (0x559,0x559,4,17), (0x55a,0x55f,4,2), (0x561,0x587,4,15),
(0x589,0x589,0,2), (0x58a,0x58a,4,7), (0x58f,0x58f,4,3), (0x591,0x5bd,5,23),
(0x5be,0x5be,5,7), (0x5bf,0x5bf,5,23), (0x5c0,0x5c0,5,2), (0x5c1,0x5c2,5,23),
(0x5c3,0x5c3,5,2), (0x5c4,0x5c5,5,23), (0x5c6,0x5c6,5,2), (0x5c7,0x5c7,5,23),
(0x5d0,0x5ea,5,19), (0x5f0,0x5f2,5,19), (0x5f3,0x5f4,5,2), (0x600,0x604,6,13),
(0x606,0x608,6,6), (0x609,0x60a,6,2), (0x60b,0x60b,6,3), (0x60c,0x60c,0,2),
(0x60d,0x60d,6,2), (0x60e,0x60f,6,11), (0x610,0x61a,6,23), (0x61b,0x61b,0,2),
(0x61e,0x61e,6,2), (0x61f,0x61f,0,2), (0x620,0x63f,6,19), (0x640,0x640,0,17),
(0x641,0x64a,6,19), (0x64b,0x655,40,23), (0x656,0x65e,6,23),
(0x65f,0x65f,40,23), (0x660,0x669,0,8), (0x66a,0x66d,6,2), (0x66e,0x66f,6,19),
(0x670,0x670,40,23), (0x671,0x6d3,6,19), (0x6d4,0x6d4,6,2), (0x6d5,0x6d5,6,19),
(0x6d6,0x6dc,6,23), (0x6dd,0x6dd,0,13), (0x6de,0x6de,6,11), (0x6df,0x6e4,6,23),
(0x6e5,0x6e6,6,17), (0x6e7,0x6e8,6,23), (0x6e9,0x6e9,6,11), (0x6ea,0x6ed,6,23),
(0x6ee,0x6ef,6,19), (0x6f0,0x6f9,6,8), (0x6fa,0x6fc,6,19), (0x6fd,0x6fe,6,11),
(0x6ff,0x6ff,6,19), (0x700,0x70d,7,2), (0x70f,0x70f,7,13), (0x710,0x710,7,19),
(0x711,0x711,7,23), (0x712,0x72f,7,19), (0x730,0x74a,7,23), (0x74d,0x74f,7,19),
(0x750,0x77f,6,19), (0x780,0x7a5,8,19), (0x7a6,0x7b0,8,23), (0x7b1,0x7b1,8,19),
(0x7c0,0x7c9,65,8), (0x7ca,0x7ea,65,19), (0x7eb,0x7f3,65,23),
(0x7f4,0x7f5,65,17), (0x7f6,0x7f6,65,11), (0x7f7,0x7f9,65,2),
(0x7fa,0x7fa,65,17), (0x800,0x815,81,19), (0x816,0x819,81,23),
(0x81a,0x81a,81,17), (0x81b,0x823,81,23), (0x824,0x824,81,17),
(0x825,0x827,81,23), (0x828,0x828,81,17), (0x829,0x82d,81,23),
(0x830,0x83e,81,2), (0x840,0x858,94,19), (0x859,0x85b,94,23),
(0x85e,0x85e,94,2), (0x8a0,0x8a0,6,19), (0x8a2,0x8ac,6,19), (0x8e4,0x8fe,6,23),
(0x900,0x902,9,23), (0x903,0x903,9,18), (0x904,0x939,9,19), (0x93a,0x93a,9,23),
(0x93b,0x93b,9,18), (0x93c,0x93c,9,23), (0x93d,0x93d,9,19), (0x93e,0x940,9,18),
(0x941,0x948,9,23), (0x949,0x94c,9,18), (0x94d,0x94d,9,23), (0x94e,0x94f,9,18),
(0x950,0x950,9,19), (0x951,0x952,40,23), (0x953,0x957,9,23), (0x958,0x961,9,19),
(0x962,0x963,9,23), (0x964,0x965,0,2), (0x966,0x96f,9,8), (0x970,0x970,9,2),
(0x971,0x971,9,17), (0x972,0x977,9,19), (0x979,0x97f,9,19), (0x981,0x981,10,23),
(0x982,0x983,10,18), (0x985,0x98c,10,19), (0x98f,0x990,10,19),
(0x993,0x9a8,10,19), (0x9aa,0x9b0,10,19), (0x9b2,0x9b2,10,19),
(0x9b6,0x9b9,10,19), (0x9bc,0x9bc,10,23), (0x9bd,0x9bd,10,19),
(0x9be,0x9c0,10,18), (0x9c1,0x9c4,10,23), (0x9c7,0x9c8,10,18),
(0x9cb,0x9cc,10,18), (0x9cd,0x9cd,10,23), (0x9ce,0x9ce,10,19),
(0x9d7,0x9d7,10,18), (0x9dc,0x9dd,10,19), (0x9df,0x9e1,10,19),
(0x9e2,0x9e3,10,23), (0x9e6,0x9ef,10,8), (0x9f0,0x9f1,10,19),
(0x9f2,0x9f3,10,3), (0x9f4,0x9f9,10,14), (0x9fa,0x9fa,10,11),
(0x9fb,0x9fb,10,3), (0xa01,0xa02,11,23), (0xa03,0xa03,11,18),
(0xa05,0xa0a,11,19), (0xa0f,0xa10,11,19), (0xa13,0xa28,11,19),
(0xa2a,0xa30,11,19), (0xa32,0xa33,11,19), (0xa35,0xa36,11,19),
(0xa38,0xa39,11,19), (0xa3c,0xa3c,11,23), (0xa3e,0xa40,11,18),
(0xa41,0xa42,11,23), (0xa47,0xa48,11,23), (0xa4b,0xa4d,11,23),
(0xa51,0xa51,11,23), (0xa59,0xa5c,11,19), (0xa5e,0xa5e,11,19),
(0xa66,0xa6f,11,8), (0xa70,0xa71,11,23), (0xa72,0xa74,11,19),
(0xa75,0xa75,11,23), (0xa81,0xa82,12,23), (0xa83,0xa83,12,18),
(0xa85,0xa8d,12,19), (0xa8f,0xa91,12,19), (0xa93,0xaa8,12,19),
(0xaaa,0xab0,12,19), (0xab2,0xab3,12,19), (0xab5,0xab9,12,19),
(0xabc,0xabc,12,23), (0xabd,0xabd,12,19), (0xabe,0xac0,12,18),
(0xac1,0xac5,12,23), (0xac7,0xac8,12,23), (0xac9,0xac9,12,18),
(0xacb,0xacc,12,18), (0xacd,0xacd,12,23), (0xad0,0xad0,12,19),
(0xae0,0xae1,12,19), (0xae2,0xae3,12,23), (0xae6,0xaef,12,8),
(0xaf0,0xaf0,12,2), (0xaf1,0xaf1,12,3), (0xb01,0xb01,13,23),
(0xb02,0xb03,13,18), (0xb05,0xb0c,13,19), (0xb0f,0xb10,13,19),
(0xb13,0xb28,13,19), (0xb2a,0xb30,13,19), (0xb32,0xb33,13,19),
(0xb35,0xb39,13,19), (0xb3c,0xb3c,13,23), (0xb3d,0xb3d,13,19),
(0xb3e,0xb3e,13,18), (0xb3f,0xb3f,13,23), (0xb40,0xb40,13,18),
(0xb41,0xb44,13,23), (0xb47,0xb48,13,18), (0xb4b,0xb4c,13,18),
(0xb4d,0xb4d,13,23), (0xb56,0xb56,13,23), (0xb57,0xb57,13,18),
(0xb5c,0xb5d,13,19), (0xb5f,0xb61,13,19), (0xb62,0xb63,13,23),
(0xb66,0xb6f,13,8), (0xb70,0xb70,13,11), (0xb71,0xb71,13,19),
(0xb72,0xb77,13,14), (0xb82,0xb82,14,23), (0xb83,0xb83,14,19),
(0xb85,0xb8a,14,19), (0xb8e,0xb90,14,19), (0xb92,0xb95,14,19),
(0xb99,0xb9a,14,19), (0xb9c,0xb9c,14,19), (0xb9e,0xb9f,14,19),
(0xba3,0xba4,14,19), (0xba8,0xbaa,14,19), (0xbae,0xbb9,14,19),
(0xbbe,0xbbf,14,18), (0xbc0,0xbc0,14,23), (0xbc1,0xbc2,14,18),
(0xbc6,0xbc8,14,18), (0xbca,0xbcc,14,18), (0xbcd,0xbcd,14,23),
(0xbd0,0xbd0,14,19), (0xbd7,0xbd7,14,18), (0xbe6,0xbef,14,8),
(0xbf0,0xbf2,14,14), (0xbf3,0xbf8,14,11), (0xbf9,0xbf9,14,3),
(0xbfa,0xbfa,14,11), (0xc01,0xc03,15,18), (0xc05,0xc0c,15,19),
(0xc0e,0xc10,15,19), (0xc12,0xc28,15,19), (0xc2a,0xc33,15,19),
(0xc35,0xc39,15,19), (0xc3d,0xc3d,15,19), (0xc3e,0xc40,15,23),
(0xc41,0xc44,15,18), (0xc46,0xc48,15,23), (0xc4a,0xc4d,15,23),
(0xc55,0xc56,15,23), (0xc58,0xc59,15,19), (0xc60,0xc61,15,19),
(0xc62,0xc63,15,23), (0xc66,0xc6f,15,8), (0xc78,0xc7e,15,14),
(0xc7f,0xc7f,15,11), (0xc82,0xc83,16,18), (0xc85,0xc8c,16,19),
(0xc8e,0xc90,16,19), (0xc92,0xca8,16,19), (0xcaa,0xcb3,16,19),
(0xcb5,0xcb9,16,19), (0xcbc,0xcbc,16,23), (0xcbd,0xcbd,16,19),
(0xcbe,0xcbe,16,18), (0xcbf,0xcbf,16,23), (0xcc0,0xcc4,16,18),
(0xcc6,0xcc6,16,23), (0xcc7,0xcc8,16,18), (0xcca,0xccb,16,18),
(0xccc,0xccd,16,23), (0xcd5,0xcd6,16,18), (0xcde,0xcde,16,19),
(0xce0,0xce1,16,19), (0xce2,0xce3,16,23), (0xce6,0xcef,16,8),
(0xcf1,0xcf2,16,19), (0xd02,0xd03,17,18), (0xd05,0xd0c,17,19),
(0xd0e,0xd10,17,19), (0xd12,0xd3a,17,19), (0xd3d,0xd3d,17,19),
(0xd3e,0xd40,17,18), (0xd41,0xd44,17,23), (0xd46,0xd48,17,18),
(0xd4a,0xd4c,17,18), (0xd4d,0xd4d,17,23), (0xd4e,0xd4e,17,19),
(0xd57,0xd57,17,18), (0xd60,0xd61,17,19), (0xd62,0xd63,17,23),
(0xd66,0xd6f,17,8), (0xd70,0xd75,17,14), (0xd79,0xd79,17,11),
(0xd7a,0xd7f,17,19), (0xd82,0xd83,18,18), (0xd85,0xd96,18,19),
(0xd9a,0xdb1,18,19), (0xdb3,0xdbb,18,19), (0xdbd,0xdbd,18,19),
(0xdc0,0xdc6,18,19), (0xdca,0xdca,18,23), (0xdcf,0xdd1,18,18),
(0xdd2,0xdd4,18,23), (0xdd6,0xdd6,18,23), (0xdd8,0xddf,18,18),
(0xdf2,0xdf3,18,18), (0xdf4,0xdf4,18,2), (0xe01,0xe30,19,19),
(0xe31,0xe31,19,23), (0xe32,0xe33,19,19), (0xe34,0xe3a,19,23),
(0xe3f,0xe3f,0,3), (0xe40,0xe45,19,19), (0xe46,0xe46,19,17),
(0xe47,0xe4e,19,23), (0xe4f,0xe4f,19,2), (0xe50,0xe59,19,8), (0xe5a,0xe5b,19,2),
(0xe81,0xe82,20,19), (0xe84,0xe84,20,19), (0xe87,0xe88,20,19),
(0xe8a,0xe8a,20,19), (0xe8d,0xe8d,20,19), (0xe94,0xe97,20,19),
(0xe99,0xe9f,20,19), (0xea1,0xea3,20,19), (0xea5,0xea5,20,19),
(0xea7,0xea7,20,19), (0xeaa,0xeab,20,19), (0xead,0xeb0,20,19),
(0xeb1,0xeb1,20,23), (0xeb2,0xeb3,20,19), (0xeb4,0xeb9,20,23),
(0xebb,0xebc,20,23), (0xebd,0xebd,20,19), (0xec0,0xec4,20,19),
(0xec6,0xec6,20,17), (0xec8,0xecd,20,23), (0xed0,0xed9,20,8),
(0xedc,0xedf,20,19), (0xf00,0xf00,21,19), (0xf01,0xf03,21,11),
(0xf04,0xf12,21,2), (0xf13,0xf13,21,11), (0xf14,0xf14,21,2),
(0xf15,0xf17,21,11), (0xf18,0xf19,21,23), (0xf1a,0xf1f,21,11),
(0xf20,0xf29,21,8), (0xf2a,0xf33,21,14), (0xf34,0xf34,21,11),
(0xf35,0xf35,21,23), (0xf36,0xf36,21,11), (0xf37,0xf37,21,23),
(0xf38,0xf38,21,11), (0xf39,0xf39,21,23), (0xf3a,0xf3a,21,4),
(0xf3b,0xf3b,21,5), (0xf3c,0xf3c,21,4), (0xf3d,0xf3d,21,5), (0xf3e,0xf3f,21,18),
(0xf40,0xf47,21,19), (0xf49,0xf6c,21,19), (0xf71,0xf7e,21,23),
(0xf7f,0xf7f,21,18), (0xf80,0xf84,21,23), (0xf85,0xf85,21,2),
(0xf86,0xf87,21,23), (0xf88,0xf8c,21,19), (0xf8d,0xf97,21,23),
(0xf99,0xfbc,21,23), (0xfbe,0xfc5,21,11), (0xfc6,0xfc6,21,23),
(0xfc7,0xfcc,21,11), (0xfce,0xfcf,21,11), (0xfd0,0xfd4,21,2),
(0xfd5,0xfd8,0,11), (0xfd9,0xfda,21,2), (0x1000,0x102a,22,19),
(0x102b,0x102c,22,18), (0x102d,0x1030,22,23), (0x1031,0x1031,22,18),
(0x1032,0x1037,22,23), (0x1038,0x1038,22,18), (0x1039,0x103a,22,23),
(0x103b,0x103c,22,18), (0x103d,0x103e,22,23), (0x103f,0x103f,22,19),
(0x1040,0x1049,22,8), (0x104a,0x104f,22,2), (0x1050,0x1055,22,19),
(0x1056,0x1057,22,18), (0x1058,0x1059,22,23), (0x105a,0x105d,22,19),
(0x105e,0x1060,22,23), (0x1061,0x1061,22,19), (0x1062,0x1064,22,18),
(0x1065,0x1066,22,19), (0x1067,0x106d,22,18), (0x106e,0x1070,22,19),
(0x1071,0x1074,22,23), (0x1075,0x1081,22,19), (0x1082,0x1082,22,23),
(0x1083,0x1084,22,18), (0x1085,0x1086,22,23), (0x1087,0x108c,22,18),
(0x108d,0x108d,22,23), (0x108e,0x108e,22,19), (0x108f,0x108f,22,18),
(0x1090,0x1099,22,8), (0x109a,0x109c,22,18), (0x109d,0x109d,22,23),
(0x109e,0x109f,22,11), (0x10a0,0x10c5,23,15), (0x10c7,0x10c7,23,15),
(0x10cd,0x10cd,23,15), (0x10d0,0x10fa,23,19), (0x10fb,0x10fb,0,2),
(0x10fc,0x10fc,23,17), (0x10fd,0x10ff,23,19), (0x1100,0x11ff,24,19),
(0x1200,0x1248,25,19), (0x124a,0x124d,25,19), (0x1250,0x1256,25,19),
(0x1258,0x1258,25,19), (0x125a,0x125d,25,19), (0x1260,0x1288,25,19),
(0x128a,0x128d,25,19), (0x1290,0x12b0,25,19), (0x12b2,0x12b5,25,19),
(0x12b8,0x12be,25,19), (0x12c0,0x12c0,25,19), (0x12c2,0x12c5,25,19),
(0x12c8,0x12d6,25,19), (0x12d8,0x1310,25,19), (0x1312,0x1315,25,19),
(0x1318,0x135a,25,19), (0x135d,0x135f,25,23), (0x1360,0x1368,25,2),
(0x1369,0x137c,25,14), (0x1380,0x138f,25,19), (0x1390,0x1399,25,11),
(0x13a0,0x13f4,26,19), (0x1400,0x1400,27,7), (0x1401,0x166c,27,19),
(0x166d,0x166e,27,2), (0x166f,0x167f,27,19), (0x1680,0x1680,28,1),
(0x1681,0x169a,28,19), (0x169b,0x169b,28,4), (0x169c,0x169c,28,5),
(0x16a0,0x16ea,29,19), (0x16eb,0x16ed,0,2), (0x16ee,0x16f0,29,22),
(0x1700,0x170c,41,19), (0x170e,0x1711,41,19), (0x1712,0x1714,41,23),
(0x1720,0x1731,42,19), (0x1732,0x1734,42,23), (0x1735,0x1736,0,2),
(0x1740,0x1751,43,19), (0x1752,0x1753,43,23), (0x1760,0x176c,44,19),
(0x176e,0x1770,44,19), (0x1772,0x1773,44,23), (0x1780,0x17b3,30,19),
(0x17b4,0x17b5,30,23), (0x17b6,0x17b6,30,18), (0x17b7,0x17bd,30,23),
(0x17be,0x17c5,30,18), (0x17c6,0x17c6,30,23), (0x17c7,0x17c8,30,18),
(0x17c9,0x17d3,30,23), (0x17d4,0x17d6,30,2), (0x17d7,0x17d7,30,17),
(0x17d8,0x17da,30,2), (0x17db,0x17db,30,3), (0x17dc,0x17dc,30,19),
(0x17dd,0x17dd,30,23), (0x17e0,0x17e9,30,8), (0x17f0,0x17f9,30,14),
(0x1800,0x1801,31,2), (0x1802,0x1803,0,2), (0x1804,0x1804,31,2),
(0x1805,0x1805,0,2), (0x1806,0x1806,31,7), (0x1807,0x180a,31,2),
(0x180b,0x180d,31,23), (0x180e,0x180e,31,1), (0x1810,0x1819,31,8),
(0x1820,0x1842,31,19), (0x1843,0x1843,31,17), (0x1844,0x1877,31,19),
(0x1880,0x18a8,31,19), (0x18a9,0x18a9,31,23), (0x18aa,0x18aa,31,19),
(0x18b0,0x18f5,27,19), (0x1900,0x191c,45,19), (0x1920,0x1922,45,23),
(0x1923,0x1926,45,18), (0x1927,0x1928,45,23), (0x1929,0x192b,45,18),
(0x1930,0x1931,45,18), (0x1932,0x1932,45,23), (0x1933,0x1938,45,18),
(0x1939,0x193b,45,23), (0x1940,0x1940,45,11), (0x1944,0x1945,45,2),
(0x1946,0x194f,45,8), (0x1950,0x196d,46,19), (0x1970,0x1974,46,19),
(0x1980,0x19ab,55,19), (0x19b0,0x19c0,55,18), (0x19c1,0x19c7,55,19),
(0x19c8,0x19c9,55,18), (0x19d0,0x19d9,55,8), (0x19da,0x19da,55,14),
(0x19de,0x19df,55,11), (0x19e0,0x19ff,30,11), (0x1a00,0x1a16,53,19),
(0x1a17,0x1a18,53,23), (0x1a19,0x1a1b,53,18), (0x1a1e,0x1a1f,53,2),
(0x1a20,0x1a54,77,19), (0x1a55,0x1a55,77,18), (0x1a56,0x1a56,77,23),
(0x1a57,0x1a57,77,18), (0x1a58,0x1a5e,77,23), (0x1a60,0x1a60,77,23),
(0x1a61,0x1a61,77,18), (0x1a62,0x1a62,77,23), (0x1a63,0x1a64,77,18),
(0x1a65,0x1a6c,77,23), (0x1a6d,0x1a72,77,18), (0x1a73,0x1a7c,77,23),
(0x1a7f,0x1a7f,77,23), (0x1a80,0x1a89,77,8), (0x1a90,0x1a99,77,8),
(0x1aa0,0x1aa6,77,2), (0x1aa7,0x1aa7,77,17), (0x1aa8,0x1aad,77,2),
(0x1b00,0x1b03,61,23), (0x1b04,0x1b04,61,18), (0x1b05,0x1b33,61,19),
(0x1b34,0x1b34,61,23), (0x1b35,0x1b35,61,18), (0x1b36,0x1b3a,61,23),
(0x1b3b,0x1b3b,61,18), (0x1b3c,0x1b3c,61,23), (0x1b3d,0x1b41,61,18),
(0x1b42,0x1b42,61,23), (0x1b43,0x1b44,61,18), (0x1b45,0x1b4b,61,19),
(0x1b50,0x1b59,61,8), (0x1b5a,0x1b60,61,2), (0x1b61,0x1b6a,61,11),
(0x1b6b,0x1b73,61,23), (0x1b74,0x1b7c,61,11), (0x1b80,0x1b81,66,23),
(0x1b82,0x1b82,66,18), (0x1b83,0x1ba0,66,19), (0x1ba1,0x1ba1,66,18),
(0x1ba2,0x1ba5,66,23), (0x1ba6,0x1ba7,66,18), (0x1ba8,0x1ba9,66,23),
(0x1baa,0x1baa,66,18), (0x1bab,0x1bab,66,23), (0x1bac,0x1bad,66,18),
(0x1bae,0x1baf,66,19), (0x1bb0,0x1bb9,66,8), (0x1bba,0x1bbf,66,19),
(0x1bc0,0x1be5,92,19), (0x1be6,0x1be6,92,23), (0x1be7,0x1be7,92,18),
(0x1be8,0x1be9,92,23), (0x1bea,0x1bec,92,18), (0x1bed,0x1bed,92,23),
(0x1bee,0x1bee,92,18), (0x1bef,0x1bf1,92,23), (0x1bf2,0x1bf3,92,18),
(0x1bfc,0x1bff,92,2), (0x1c00,0x1c23,67,19), (0x1c24,0x1c2b,67,18),
(0x1c2c,0x1c33,67,23), (0x1c34,0x1c35,67,18), (0x1c36,0x1c37,67,23),
(0x1c3b,0x1c3f,67,2), (0x1c40,0x1c49,67,8), (0x1c4d,0x1c4f,67,19),
(0x1c50,0x1c59,68,8), (0x1c5a,0x1c77,68,19), (0x1c78,0x1c7d,68,17),
(0x1c7e,0x1c7f,68,2), (0x1cc0,0x1cc7,66,2), (0x1cd0,0x1cd2,40,23),
(0x1cd3,0x1cd3,0,2), (0x1cd4,0x1ce0,40,23), (0x1ce1,0x1ce1,0,18),
(0x1ce2,0x1ce8,40,23), (0x1ce9,0x1cec,0,19), (0x1ced,0x1ced,40,23),
(0x1cee,0x1cf1,0,19), (0x1cf2,0x1cf3,0,18), (0x1cf4,0x1cf4,40,23),
(0x1cf5,0x1cf6,0,19), (0x1d00,0x1d25,1,15), (0x1d26,0x1d2a,2,15),
(0x1d2b,0x1d2b,3,15), (0x1d2c,0x1d5c,1,17), (0x1d5d,0x1d61,2,17),
(0x1d62,0x1d65,1,17), (0x1d66,0x1d6a,2,17), (0x1d6b,0x1d77,1,15),
(0x1d78,0x1d78,3,17), (0x1d79,0x1d9a,1,15), (0x1d9b,0x1dbe,1,17),
(0x1dbf,0x1dbf,2,17), (0x1dc0,0x1de6,40,23), (0x1dfc,0x1dff,40,23),
(0x1e00,0x1eff,1,15), (0x1f00,0x1f15,2,15), (0x1f18,0x1f1d,2,15),
(0x1f20,0x1f45,2,15), (0x1f48,0x1f4d,2,15), (0x1f50,0x1f57,2,15),
(0x1f59,0x1f59,2,15), (0x1f5b,0x1f5b,2,15), (0x1f5d,0x1f5d,2,15),
(0x1f5f,0x1f7d,2,15), (0x1f80,0x1fb4,2,15), (0x1fb6,0x1fbc,2,15),
(0x1fbd,0x1fbd,2,9), (0x1fbe,0x1fbe,2,15), (0x1fbf,0x1fc1,2,9),
(0x1fc2,0x1fc4,2,15), (0x1fc6,0x1fcc,2,15), (0x1fcd,0x1fcf,2,9),
(0x1fd0,0x1fd3,2,15), (0x1fd6,0x1fdb,2,15), (0x1fdd,0x1fdf,2,9),
(0x1fe0,0x1fec,2,15), (0x1fed,0x1fef,2,9), (0x1ff2,0x1ff4,2,15),
(0x1ff6,0x1ffc,2,15), (0x1ffd,0x1ffe,2,9), (0x2000,0x200a,0,1),
(0x200b,0x200b,0,13), (0x200c,0x200d,40,13), (0x200e,0x200f,0,13),
(0x2010,0x2015,0,7), (0x2016,0x2017,0,2), (0x2018,0x2018,0,12),
(0x2019,0x2019,0,16), (0x201a,0x201a,0,4), (0x201b,0x201c,0,12),
(0x201d,0x201d,0,16), (0x201e,0x201e,0,4), (0x201f,0x201f,0,12),
(0x2020,0x2027,0,2), (0x2028,0x2028,0,20), (0x2029,0x2029,0,21),
(0x202a,0x202e,0,13), (0x202f,0x202f,0,1), (0x2030,0x2038,0,2),
(0x2039,0x2039,0,12), (0x203a,0x203a,0,16), (0x203b,0x203e,0,2),
(0x203f,0x2040,0,10), (0x2041,0x2043,0,2), (0x2044,0x2044,0,6),
(0x2045,0x2045,0,4), (0x2046,0x2046,0,5), (0x2047,0x2051,0,2),
(0x2052,0x2052,0,6), (0x2053,0x2053,0,2), (0x2054,0x2054,0,10),
(0x2055,0x205e,0,2), (0x205f,0x205f,0,1), (0x2060,0x2064,0,13),
(0x206a,0x206f,0,13), (0x2070,0x2070,0,14), (0x2071,0x2071,1,17),
(0x2074,0x2079,0,14), (0x207a,0x207c,0,6), (0x207d,0x207d,0,4),
(0x207e,0x207e,0,5), (0x207f,0x207f,1,17), (0x2080,0x2089,0,14),
(0x208a,0x208c,0,6), (0x208d,0x208d,0,4), (0x208e,0x208e,0,5),
(0x2090,0x209c,1,17), (0x20a0,0x20b9,0,3), (0x20d0,0x20dc,40,23),
(0x20dd,0x20e0,40,24), (0x20e1,0x20e1,40,23), (0x20e2,0x20e4,40,24),
(0x20e5,0x20f0,40,23), (0x2100,0x2101,0,11), (0x2102,0x2102,0,15),
(0x2103,0x2106,0,11), (0x2107,0x2107,0,15), (0x2108,0x2109,0,11),
(0x210a,0x2113,0,15), (0x2114,0x2114,0,11), (0x2115,0x2115,0,15),
(0x2116,0x2117,0,11), (0x2118,0x2118,0,6), (0x2119,0x211d,0,15),
(0x211e,0x2123,0,11), (0x2124,0x2124,0,15), (0x2125,0x2125,0,11),
(0x2126,0x2126,2,15), (0x2127,0x2127,0,11), (0x2128,0x2128,0,15),
(0x2129,0x2129,0,11), (0x212a,0x212b,1,15), (0x212c,0x212d,0,15),
(0x212e,0x212e,0,11), (0x212f,0x2131,0,15), (0x2132,0x2132,1,15),
(0x2133,0x2134,0,15), (0x2135,0x2138,0,19), (0x2139,0x2139,0,15),
(0x213a,0x213b,0,11), (0x213c,0x213f,0,15), (0x2140,0x2144,0,6),
(0x2145,0x2149,0,15), (0x214a,0x214a,0,11), (0x214b,0x214b,0,6),
(0x214c,0x214d,0,11), (0x214e,0x214e,1,15), (0x214f,0x214f,0,11),
(0x2150,0x215f,0,14), (0x2160,0x2182,1,22), (0x2183,0x2184,1,15),
(0x2185,0x2188,1,22), (0x2189,0x2189,0,14), (0x2190,0x2194,0,6),
(0x2195,0x2199,0,11), (0x219a,0x219b,0,6), (0x219c,0x219f,0,11),
(0x21a0,0x21a0,0,6), (0x21a1,0x21a2,0,11), (0x21a3,0x21a3,0,6),
(0x21a4,0x21a5,0,11), (0x21a6,0x21a6,0,6), (0x21a7,0x21ad,0,11),
(0x21ae,0x21ae,0,6), (0x21af,0x21cd,0,11), (0x21ce,0x21cf,0,6),
(0x21d0,0x21d1,0,11), (0x21d2,0x21d2,0,6), (0x21d3,0x21d3,0,11),
(0x21d4,0x21d4,0,6), (0x21d5,0x21f3,0,11), (0x21f4,0x22ff,0,6),
(0x2300,0x2307,0,11), (0x2308,0x230b,0,6), (0x230c,0x231f,0,11),
(0x2320,0x2321,0,6), (0x2322,0x2328,0,11), (0x2329,0x2329,0,4),
(0x232a,0x232a,0,5), (0x232b,0x237b,0,11), (0x237c,0x237c,0,6),
(0x237d,0x239a,0,11), (0x239b,0x23b3,0,6), (0x23b4,0x23db,0,11),
(0x23dc,0x23e1,0,6), (0x23e2,0x23f3,0,11), (0x2400,0x2426,0,11),
(0x2440,0x244a,0,11), (0x2460,0x249b,0,14), (0x249c,0x24e9,0,11),
(0x24ea,0x24ff,0,14), (0x2500,0x25b6,0,11), (0x25b7,0x25b7,0,6),
(0x25b8,0x25c0,0,11), (0x25c1,0x25c1,0,6), (0x25c2,0x25f7,0,11),
(0x25f8,0x25ff,0,6), (0x2600,0x266e,0,11), (0x266f,0x266f,0,6),
(0x2670,0x26ff,0,11), (0x2701,0x2767,0,11), (0x2768,0x2768,0,4),
(0x2769,0x2769,0,5), (0x276a,0x276a,0,4), (0x276b,0x276b,0,5),
(0x276c,0x276c,0,4), (0x276d,0x276d,0,5), (0x276e,0x276e,0,4),
(0x276f,0x276f,0,5), (0x2770,0x2770,0,4), (0x2771,0x2771,0,5),
(0x2772,0x2772,0,4), (0x2773,0x2773,0,5), (0x2774,0x2774,0,4),
(0x2775,0x2775,0,5), (0x2776,0x2793,0,14), (0x2794,0x27bf,0,11),
(0x27c0,0x27c4,0,6), (0x27c5,0x27c5,0,4), (0x27c6,0x27c6,0,5),
(0x27c7,0x27e5,0,6), (0x27e6,0x27e6,0,4), (0x27e7,0x27e7,0,5),
(0x27e8,0x27e8,0,4), (0x27e9,0x27e9,0,5), (0x27ea,0x27ea,0,4),
(0x27eb,0x27eb,0,5), (0x27ec,0x27ec,0,4), (0x27ed,0x27ed,0,5),
(0x27ee,0x27ee,0,4), (0x27ef,0x27ef,0,5), (0x27f0,0x27ff,0,6),
(0x2800,0x28ff,52,11), (0x2900,0x2982,0,6), (0x2983,0x2983,0,4),
(0x2984,0x2984,0,5), (0x2985,0x2985,0,4), (0x2986,0x2986,0,5),
(0x2987,0x2987,0,4), (0x2988,0x2988,0,5), (0x2989,0x2989,0,4),
(0x298a,0x298a,0,5), (0x298b,0x298b,0,4), (0x298c,0x298c,0,5),
(0x298d,0x298d,0,4), (0x298e,0x298e,0,5), (0x298f,0x298f,0,4),
(0x2990,0x2990,0,5), (0x2991,0x2991,0,4), (0x2992,0x2992,0,5),
(0x2993,0x2993,0,4), (0x2994,0x2994,0,5), (0x2995,0x2995,0,4),
(0x2996,0x2996,0,5), (0x2997,0x2997,0,4), (0x2998,0x2998,0,5),
(0x2999,0x29d7,0,6), (0x29d8,0x29d8,0,4), (0x29d9,0x29d9,0,5),
(0x29da,0x29da,0,4), (0x29db,0x29db,0,5), (0x29dc,0x29fb,0,6),
(0x29fc,0x29fc,0,4), (0x29fd,0x29fd,0,5), (0x29fe,0x2aff,0,6),
(0x2b00,0x2b2f,0,11), (0x2b30,0x2b44,0,6), (0x2b45,0x2b46,0,11),
(0x2b47,0x2b4c,0,6), (0x2b50,0x2b59,0,11), (0x2c00,0x2c2e,56,15),
(0x2c30,0x2c5e,56,15), (0x2c60,0x2c7b,1,15), (0x2c7c,0x2c7d,1,17),
(0x2c7e,0x2c7f,1,15), (0x2c80,0x2ce4,54,15), (0x2ce5,0x2cea,54,11),
(0x2ceb,0x2cee,54,15), (0x2cef,0x2cf1,54,23), (0x2cf2,0x2cf3,54,15),
(0x2cf9,0x2cfc,54,2), (0x2cfd,0x2cfd,54,14), (0x2cfe,0x2cff,54,2),
(0x2d00,0x2d25,23,15), (0x2d27,0x2d27,23,15), (0x2d2d,0x2d2d,23,15),
(0x2d30,0x2d67,57,19), (0x2d6f,0x2d6f,57,17), (0x2d70,0x2d70,57,2),
(0x2d7f,0x2d7f,57,23), (0x2d80,0x2d96,25,19), (0x2da0,0x2da6,25,19),
(0x2da8,0x2dae,25,19), (0x2db0,0x2db6,25,19), (0x2db8,0x2dbe,25,19),
(0x2dc0,0x2dc6,25,19), (0x2dc8,0x2dce,25,19), (0x2dd0,0x2dd6,25,19),
(0x2dd8,0x2dde,25,19), (0x2de0,0x2dff,3,23), (0x2e00,0x2e01,0,2),
(0x2e02,0x2e02,0,12), (0x2e03,0x2e03,0,16), (0x2e04,0x2e04,0,12),
(0x2e05,0x2e05,0,16), (0x2e06,0x2e08,0,2), (0x2e09,0x2e09,0,12),
(0x2e0a,0x2e0a,0,16), (0x2e0b,0x2e0b,0,2), (0x2e0c,0x2e0c,0,12),
(0x2e0d,0x2e0d,0,16), (0x2e0e,0x2e16,0,2), (0x2e17,0x2e17,0,7),
(0x2e18,0x2e19,0,2), (0x2e1a,0x2e1a,0,7), (0x2e1b,0x2e1b,0,2),
(0x2e1c,0x2e1c,0,12), (0x2e1d,0x2e1d,0,16), (0x2e1e,0x2e1f,0,2),
(0x2e20,0x2e20,0,12), (0x2e21,0x2e21,0,16), (0x2e22,0x2e22,0,4),
(0x2e23,0x2e23,0,5), (0x2e24,0x2e24,0,4), (0x2e25,0x2e25,0,5),
(0x2e26,0x2e26,0,4), (0x2e27,0x2e27,0,5), (0x2e28,0x2e28,0,4),
(0x2e29,0x2e29,0,5), (0x2e2a,0x2e2e,0,2), (0x2e2f,0x2e2f,0,17),
(0x2e30,0x2e39,0,2), (0x2e3a,0x2e3b,0,7), (0x2e80,0x2e99,35,11),
(0x2e9b,0x2ef3,35,11), (0x2f00,0x2fd5,35,11), (0x2ff0,0x2ffb,0,11),
(0x3000,0x3000,0,1), (0x3001,0x3003,0,2), (0x3004,0x3004,0,11),
(0x3005,0x3005,35,17), (0x3006,0x3006,0,19), (0x3007,0x3007,35,22),
(0x3008,0x3008,0,4), (0x3009,0x3009,0,5), (0x300a,0x300a,0,4),
(0x300b,0x300b,0,5), (0x300c,0x300c,0,4), (0x300d,0x300d,0,5),
(0x300e,0x300e,0,4), (0x300f,0x300f,0,5), (0x3010,0x3010,0,4),
(0x3011,0x3011,0,5), (0x3012,0x3013,0,11), (0x3014,0x3014,0,4),
(0x3015,0x3015,0,5), (0x3016,0x3016,0,4), (0x3017,0x3017,0,5),
(0x3018,0x3018,0,4), (0x3019,0x3019,0,5), (0x301a,0x301a,0,4),
(0x301b,0x301b,0,5), (0x301c,0x301c,0,7), (0x301d,0x301d,0,4),
(0x301e,0x301f,0,5), (0x3020,0x3020,0,11), (0x3021,0x3029,35,22),
(0x302a,0x302d,40,23), (0x302e,0x302f,24,18), (0x3030,0x3030,0,7),
(0x3031,0x3035,0,17), (0x3036,0x3037,0,11), (0x3038,0x303a,35,22),
(0x303b,0x303b,35,17), (0x303c,0x303c,0,19), (0x303d,0x303d,0,2),
(0x303e,0x303f,0,11), (0x3041,0x3096,32,19), (0x3099,0x309a,40,23),
(0x309b,0x309c,0,9), (0x309d,0x309e,32,17), (0x309f,0x309f,32,19),
(0x30a0,0x30a0,0,7), (0x30a1,0x30fa,33,19), (0x30fb,0x30fb,0,2),
(0x30fc,0x30fc,0,17), (0x30fd,0x30fe,33,17), (0x30ff,0x30ff,33,19),
(0x3105,0x312d,34,19), (0x3131,0x318e,24,19), (0x3190,0x3191,0,11),
(0x3192,0x3195,0,14), (0x3196,0x319f,0,11), (0x31a0,0x31ba,34,19),
(0x31c0,0x31e3,0,11), (0x31f0,0x31ff,33,19), (0x3200,0x321e,24,11),
(0x3220,0x3229,0,14), (0x322a,0x3247,0,11), (0x3248,0x324f,0,14),
(0x3250,0x3250,0,11), (0x3251,0x325f,0,14), (0x3260,0x327e,24,11),
(0x327f,0x327f,0,11), (0x3280,0x3289,0,14), (0x328a,0x32b0,0,11),
(0x32b1,0x32bf,0,14), (0x32c0,0x32cf,0,11), (0x32d0,0x32fe,33,11),
(0x3300,0x3357,33,11), (0x3358,0x33ff,0,11), (0x3400,0x4db5,35,19),
(0x4dc0,0x4dff,0,11), (0x4e00,0x9fcc,35,19), (0xa000,0xa014,36,19),
(0xa015,0xa015,36,17), (0xa016,0xa48c,36,19), (0xa490,0xa4c6,36,11),
(0xa4d0,0xa4f7,82,19), (0xa4f8,0xa4fd,82,17), (0xa4fe,0xa4ff,82,2),
(0xa500,0xa60b,69,19), (0xa60c,0xa60c,69,17), (0xa60d,0xa60f,69,2),
(0xa610,0xa61f,69,19), (0xa620,0xa629,69,8), (0xa62a,0xa62b,69,19),
(0xa640,0xa66d,3,15), (0xa66e,0xa66e,3,19), (0xa66f,0xa66f,3,23),
(0xa670,0xa672,3,24), (0xa673,0xa673,3,2), (0xa674,0xa67d,3,23),
(0xa67e,0xa67e,3,2), (0xa67f,0xa67f,3,17), (0xa680,0xa697,3,15),
(0xa69f,0xa69f,3,23), (0xa6a0,0xa6e5,83,19), (0xa6e6,0xa6ef,83,22),
(0xa6f0,0xa6f1,83,23), (0xa6f2,0xa6f7,83,2), (0xa700,0xa716,0,9),
(0xa717,0xa71f,0,17), (0xa720,0xa721,0,9), (0xa722,0xa76f,1,15),
(0xa770,0xa770,1,17), (0xa771,0xa787,1,15), (0xa788,0xa788,0,17),
(0xa789,0xa78a,0,9), (0xa78b,0xa78e,1,15), (0xa790,0xa793,1,15),
(0xa7a0,0xa7aa,1,15), (0xa7f8,0xa7f9,1,17), (0xa7fa,0xa7fa,1,15),
(0xa7fb,0xa7ff,1,19), (0xa800,0xa801,58,19), (0xa802,0xa802,58,23),
(0xa803,0xa805,58,19), (0xa806,0xa806,58,23), (0xa807,0xa80a,58,19),
(0xa80b,0xa80b,58,23), (0xa80c,0xa822,58,19), (0xa823,0xa824,58,18),
(0xa825,0xa826,58,23), (0xa827,0xa827,58,18), (0xa828,0xa82b,58,11),
(0xa830,0xa835,0,14), (0xa836,0xa837,0,11), (0xa838,0xa838,0,3),
(0xa839,0xa839,0,11), (0xa840,0xa873,64,19), (0xa874,0xa877,64,2),
(0xa880,0xa881,70,18), (0xa882,0xa8b3,70,19), (0xa8b4,0xa8c3,70,18),
(0xa8c4,0xa8c4,70,23), (0xa8ce,0xa8cf,70,2), (0xa8d0,0xa8d9,70,8),
(0xa8e0,0xa8f1,9,23), (0xa8f2,0xa8f7,9,19), (0xa8f8,0xa8fa,9,2),
(0xa8fb,0xa8fb,9,19), (0xa900,0xa909,71,8), (0xa90a,0xa925,71,19),
(0xa926,0xa92d,71,23), (0xa92e,0xa92f,71,2), (0xa930,0xa946,72,19),
(0xa947,0xa951,72,23), (0xa952,0xa953,72,18), (0xa95f,0xa95f,72,2),
(0xa960,0xa97c,24,19), (0xa980,0xa982,84,23), (0xa983,0xa983,84,18),
(0xa984,0xa9b2,84,19), (0xa9b3,0xa9b3,84,23), (0xa9b4,0xa9b5,84,18),
(0xa9b6,0xa9b9,84,23), (0xa9ba,0xa9bb,84,18), (0xa9bc,0xa9bc,84,23),
(0xa9bd,0xa9c0,84,18), (0xa9c1,0xa9cd,84,2), (0xa9cf,0xa9cf,84,17),
(0xa9d0,0xa9d9,84,8), (0xa9de,0xa9df,84,2), (0xaa00,0xaa28,76,19),
(0xaa29,0xaa2e,76,23), (0xaa2f,0xaa30,76,18), (0xaa31,0xaa32,76,23),
(0xaa33,0xaa34,76,18), (0xaa35,0xaa36,76,23), (0xaa40,0xaa42,76,19),
(0xaa43,0xaa43,76,23), (0xaa44,0xaa4b,76,19), (0xaa4c,0xaa4c,76,23),
(0xaa4d,0xaa4d,76,18), (0xaa50,0xaa59,76,8), (0xaa5c,0xaa5f,76,2),
(0xaa60,0xaa6f,22,19), (0xaa70,0xaa70,22,17), (0xaa71,0xaa76,22,19),
(0xaa77,0xaa79,22,11), (0xaa7a,0xaa7a,22,19), (0xaa7b,0xaa7b,22,18),
(0xaa80,0xaaaf,78,19), (0xaab0,0xaab0,78,23), (0xaab1,0xaab1,78,19),
(0xaab2,0xaab4,78,23), (0xaab5,0xaab6,78,19), (0xaab7,0xaab8,78,23),
(0xaab9,0xaabd,78,19), (0xaabe,0xaabf,78,23), (0xaac0,0xaac0,78,19),
(0xaac1,0xaac1,78,23), (0xaac2,0xaac2,78,19), (0xaadb,0xaadc,78,19),
(0xaadd,0xaadd,78,17), (0xaade,0xaadf,78,2), (0xaae0,0xaaea,85,19),
(0xaaeb,0xaaeb,85,18), (0xaaec,0xaaed,85,23), (0xaaee,0xaaef,85,18),
(0xaaf0,0xaaf1,85,2), (0xaaf2,0xaaf2,85,19), (0xaaf3,0xaaf4,85,17),
(0xaaf5,0xaaf5,85,18), (0xaaf6,0xaaf6,85,23), (0xab01,0xab06,25,19),
(0xab09,0xab0e,25,19), (0xab11,0xab16,25,19), (0xab20,0xab26,25,19),
(0xab28,0xab2e,25,19), (0xabc0,0xabe2,85,19), (0xabe3,0xabe4,85,18),
(0xabe5,0xabe5,85,23), (0xabe6,0xabe7,85,18), (0xabe8,0xabe8,85,23),
(0xabe9,0xabea,85,18), (0xabeb,0xabeb,85,2), (0xabec,0xabec,85,18),
(0xabed,0xabed,85,23), (0xabf0,0xabf9,85,8), (0xac00,0xd7a3,24,19),
(0xd7b0,0xd7c6,24,19), (0xd7cb,0xd7fb,24,19), (0xf900,0xfa6d,35,19),
(0xfa70,0xfad9,35,19), (0xfb00,0xfb06,1,15), (0xfb13,0xfb17,4,15),
(0xfb1d,0xfb1d,5,19), (0xfb1e,0xfb1e,5,23), (0xfb1f,0xfb28,5,19),
(0xfb29,0xfb29,5,6), (0xfb2a,0xfb36,5,19), (0xfb38,0xfb3c,5,19),
(0xfb3e,0xfb3e,5,19), (0xfb40,0xfb41,5,19), (0xfb43,0xfb44,5,19),
(0xfb46,0xfb4f,5,19), (0xfb50,0xfbb1,6,19), (0xfbb2,0xfbc1,6,9),
(0xfbd3,0xfd3d,6,19), (0xfd3e,0xfd3e,0,4), (0xfd3f,0xfd3f,0,5),
(0xfd50,0xfd8f,6,19), (0xfd92,0xfdc7,6,19), (0xfdf0,0xfdfb,6,19),
(0xfdfc,0xfdfc,6,3), (0xfdfd,0xfdfd,0,11), (0xfe00,0xfe0f,40,23),
(0xfe10,0xfe16,0,2), (0xfe17,0xfe17,0,4), (0xfe18,0xfe18,0,5),
(0xfe19,0xfe19,0,2), (0xfe20,0xfe26,40,23), (0xfe30,0xfe30,0,2),
(0xfe31,0xfe32,0,7), (0xfe33,0xfe34,0,10), (0xfe35,0xfe35,0,4),
(0xfe36,0xfe36,0,5), (0xfe37,0xfe37,0,4), (0xfe38,0xfe38,0,5),
(0xfe39,0xfe39,0,4), (0xfe3a,0xfe3a,0,5), (0xfe3b,0xfe3b,0,4),
(0xfe3c,0xfe3c,0,5), (0xfe3d,0xfe3d,0,4), (0xfe3e,0xfe3e,0,5),
(0xfe3f,0xfe3f,0,4), (0xfe40,0xfe40,0,5), (0xfe41,0xfe41,0,4),
(0xfe42,0xfe42,0,5), (0xfe43,0xfe43,0,4), (0xfe44,0xfe44,0,5),
(0xfe45,0xfe46,0,2), (0xfe47,0xfe47,0,4), (0xfe48,0xfe48,0,5),
(0xfe49,0xfe4c,0,2), (0xfe4d,0xfe4f,0,10), (0xfe50,0xfe52,0,2),
(0xfe54,0xfe57,0,2), (0xfe58,0xfe58,0,7), (0xfe59,0xfe59,0,4),
(0xfe5a,0xfe5a,0,5), (0xfe5b,0xfe5b,0,4), (0xfe5c,0xfe5c,0,5),
(0xfe5d,0xfe5d,0,4), (0xfe5e,0xfe5e,0,5), (0xfe5f,0xfe61,0,2),
(0xfe62,0xfe62,0,6), (0xfe63,0xfe63,0,7), (0xfe64,0xfe66,0,6),
(0xfe68,0xfe68,0,2), (0xfe69,0xfe69,0,3), (0xfe6a,0xfe6b,0,2),
(0xfe70,0xfe74,6,19), (0xfe76,0xfefc,6,19), (0xfeff,0xfeff,0,13),
(0xff01,0xff03,0,2), (0xff04,0xff04,0,3), (0xff05,0xff07,0,2),
(0xff08,0xff08,0,4), (0xff09,0xff09,0,5), (0xff0a,0xff0a,0,2),
(0xff0b,0xff0b,0,6), (0xff0c,0xff0c,0,2), (0xff0d,0xff0d,0,7),
(0xff0e,0xff0f,0,2), (0xff10,0xff19,0,8), (0xff1a,0xff1b,0,2),
(0xff1c,0xff1e,0,6), (0xff1f,0xff20,0,2), (0xff21,0xff3a,1,15),
(0xff3b,0xff3b,0,4), (0xff3c,0xff3c,0,2), (0xff3d,0xff3d,0,5),
(0xff3e,0xff3e,0,9), (0xff3f,0xff3f,0,10), (0xff40,0xff40,0,9),
(0xff41,0xff5a,1,15), (0xff5b,0xff5b,0,4), (0xff5c,0xff5c,0,6),
(0xff5d,0xff5d,0,5), (0xff5e,0xff5e,0,6), (0xff5f,0xff5f,0,4),
(0xff60,0xff60,0,5), (0xff61,0xff61,0,2), (0xff62,0xff62,0,4),
(0xff63,0xff63,0,5), (0xff64,0xff65,0,2), (0xff66,0xff6f,33,19),
(0xff70,0xff70,0,17), (0xff71,0xff9d,33,19), (0xff9e,0xff9f,0,17),
(0xffa0,0xffbe,24,19), (0xffc2,0xffc7,24,19), (0xffca,0xffcf,24,19),
(0xffd2,0xffd7,24,19), (0xffda,0xffdc,24,19), (0xffe0,0xffe1,0,3),
(0xffe2,0xffe2,0,6), (0xffe3,0xffe3,0,9), (0xffe4,0xffe4,0,11),
(0xffe5,0xffe6,0,3), (0xffe8,0xffe8,0,11), (0xffe9,0xffec,0,6),
(0xffed,0xffee,0,11), (0xfff9,0xfffb,0,13), (0xfffc,0xfffd,0,11),
(0x10000,0x1000b,47,19), (0x1000d,0x10026,47,19), (0x10028,0x1003a,47,19),
(0x1003c,0x1003d,47,19), (0x1003f,0x1004d,47,19), (0x10050,0x1005d,47,19),
(0x10080,0x100fa,47,19), (0x10100,0x10102,0,2), (0x10107,0x10133,0,14),
(0x10137,0x1013f,0,11), (0x10140,0x10174,2,22), (0x10175,0x10178,2,14),
(0x10179,0x10189,2,11), (0x1018a,0x1018a,2,14), (0x10190,0x1019b,0,11),
(0x101d0,0x101fc,0,11), (0x101fd,0x101fd,40,23), (0x10280,0x1029c,73,19),
(0x102a0,0x102d0,74,19), (0x10300,0x1031e,37,19), (0x10320,0x10323,37,14),
(0x10330,0x10340,38,19), (0x10341,0x10341,38,22), (0x10342,0x10349,38,19),
(0x1034a,0x1034a,38,22), (0x10380,0x1039d,48,19), (0x1039f,0x1039f,48,2),
(0x103a0,0x103c3,59,19), (0x103c8,0x103cf,59,19), (0x103d0,0x103d0,59,2),
(0x103d1,0x103d5,59,22), (0x10400,0x1044f,39,15), (0x10450,0x1047f,49,19),
(0x10480,0x1049d,50,19), (0x104a0,0x104a9,50,8), (0x10800,0x10805,51,19),
(0x10808,0x10808,51,19), (0x1080a,0x10835,51,19), (0x10837,0x10838,51,19),
(0x1083c,0x1083c,51,19), (0x1083f,0x1083f,51,19), (0x10840,0x10855,86,19),
(0x10857,0x10857,86,2), (0x10858,0x1085f,86,14), (0x10900,0x10915,63,19),
(0x10916,0x1091b,63,14), (0x1091f,0x1091f,63,2), (0x10920,0x10939,75,19),
(0x1093f,0x1093f,75,2), (0x10980,0x1099f,97,19), (0x109a0,0x109b7,96,19),
(0x109be,0x109bf,96,19), (0x10a00,0x10a00,60,19), (0x10a01,0x10a03,60,23),
(0x10a05,0x10a06,60,23), (0x10a0c,0x10a0f,60,23), (0x10a10,0x10a13,60,19),
(0x10a15,0x10a17,60,19), (0x10a19,0x10a33,60,19), (0x10a38,0x10a3a,60,23),
(0x10a3f,0x10a3f,60,23), (0x10a40,0x10a47,60,14), (0x10a50,0x10a58,60,2),
(0x10a60,0x10a7c,87,19), (0x10a7d,0x10a7e,87,14), (0x10a7f,0x10a7f,87,2),
(0x10b00,0x10b35,79,19), (0x10b39,0x10b3f,79,2), (0x10b40,0x10b55,88,19),
(0x10b58,0x10b5f,88,14), (0x10b60,0x10b72,89,19), (0x10b78,0x10b7f,89,14),
(0x10c00,0x10c48,90,19), (0x10e60,0x10e7e,6,14), (0x11000,0x11000,93,18),
(0x11001,0x11001,93,23), (0x11002,0x11002,93,18), (0x11003,0x11037,93,19),
(0x11038,0x11046,93,23), (0x11047,0x1104d,93,2), (0x11052,0x11065,93,14),
(0x11066,0x1106f,93,8), (0x11080,0x11081,91,23), (0x11082,0x11082,91,18),
(0x11083,0x110af,91,19), (0x110b0,0x110b2,91,18), (0x110b3,0x110b6,91,23),
(0x110b7,0x110b8,91,18), (0x110b9,0x110ba,91,23), (0x110bb,0x110bc,91,2),
(0x110bd,0x110bd,91,13), (0x110be,0x110c1,91,2), (0x110d0,0x110e8,100,19),
(0x110f0,0x110f9,100,8), (0x11100,0x11102,95,23), (0x11103,0x11126,95,19),
(0x11127,0x1112b,95,23), (0x1112c,0x1112c,95,18), (0x1112d,0x11134,95,23),
(0x11136,0x1113f,95,8), (0x11140,0x11143,95,2), (0x11180,0x11181,99,23),
(0x11182,0x11182,99,18), (0x11183,0x111b2,99,19), (0x111b3,0x111b5,99,18),
(0x111b6,0x111be,99,23), (0x111bf,0x111c0,99,18), (0x111c1,0x111c4,99,19),
(0x111c5,0x111c8,99,2), (0x111d0,0x111d9,99,8), (0x11680,0x116aa,101,19),
(0x116ab,0x116ab,101,23), (0x116ac,0x116ac,101,18), (0x116ad,0x116ad,101,23),
(0x116ae,0x116af,101,18), (0x116b0,0x116b5,101,23), (0x116b6,0x116b6,101,18),
(0x116b7,0x116b7,101,23), (0x116c0,0x116c9,101,8), (0x12000,0x1236e,62,19),
(0x12400,0x12462,62,22), (0x12470,0x12473,62,2), (0x13000,0x1342e,80,19),
(0x16800,0x16a38,83,19), (0x16f00,0x16f44,98,19), (0x16f50,0x16f50,98,19),
(0x16f51,0x16f7e,98,18), (0x16f8f,0x16f92,98,23), (0x16f93,0x16f9f,98,17),
(0x1b000,0x1b000,33,19), (0x1b001,0x1b001,32,19), (0x1d000,0x1d0f5,0,11),
(0x1d100,0x1d126,0,11), (0x1d129,0x1d164,0,11), (0x1d165,0x1d166,0,18),
(0x1d167,0x1d169,40,23), (0x1d16a,0x1d16c,0,11), (0x1d16d,0x1d172,0,18),
(0x1d173,0x1d17a,0,13), (0x1d17b,0x1d182,40,23), (0x1d183,0x1d184,0,11),
(0x1d185,0x1d18b,40,23), (0x1d18c,0x1d1a9,0,11), (0x1d1aa,0x1d1ad,40,23),
(0x1d1ae,0x1d1dd,0,11), (0x1d200,0x1d241,2,11), (0x1d242,0x1d244,2,23),
(0x1d245,0x1d245,2,11), (0x1d300,0x1d356,0,11), (0x1d360,0x1d371,0,14),
(0x1d400,0x1d454,0,15), (0x1d456,0x1d49c,0,15), (0x1d49e,0x1d49f,0,15),
(0x1d4a2,0x1d4a2,0,15), (0x1d4a5,0x1d4a6,0,15), (0x1d4a9,0x1d4ac,0,15),
(0x1d4ae,0x1d4b9,0,15), (0x1d4bb,0x1d4bb,0,15), (0x1d4bd,0x1d4c3,0,15),
(0x1d4c5,0x1d505,0,15), (0x1d507,0x1d50a,0,15), (0x1d50d,0x1d514,0,15),
(0x1d516,0x1d51c,0,15), (0x1d51e,0x1d539,0,15), (0x1d53b,0x1d53e,0,15),
(0x1d540,0x1d544,0,15), (0x1d546,0x1d546,0,15), (0x1d54a,0x1d550,0,15),
(0x1d552,0x1d6a5,0,15), (0x1d6a8,0x1d6c0,0,15), (0x1d6c1,0x1d6c1,0,6),
(0x1d6c2,0x1d6da,0,15), (0x1d6db,0x1d6db,0,6), (0x1d6dc,0x1d6fa,0,15),
(0x1d6fb,0x1d6fb,0,6), (0x1d6fc,0x1d714,0,15), (0x1d715,0x1d715,0,6),
(0x1d716,0x1d734,0,15), (0x1d735,0x1d735,0,6), (0x1d736,0x1d74e,0,15),
(0x1d74f,0x1d74f,0,6), (0x1d750,0x1d76e,0,15), (0x1d76f,0x1d76f,0,6),
(0x1d770,0x1d788,0,15), (0x1d789,0x1d789,0,6), (0x1d78a,0x1d7a8,0,15),
(0x1d7a9,0x1d7a9,0,6), (0x1d7aa,0x1d7c2,0,15), (0x1d7c3,0x1d7c3,0,6),
(0x1d7c4,0x1d7cb,0,15), (0x1d7ce,0x1d7ff,0,8), (0x1ee00,0x1ee03,6,19),
(0x1ee05,0x1ee1f,6,19), (0x1ee21,0x1ee22,6,19), (0x1ee24,0x1ee24,6,19),
(0x1ee27,0x1ee27,6,19), (0x1ee29,0x1ee32,6,19), (0x1ee34,0x1ee37,6,19),
(0x1ee39,0x1ee39,6,19), (0x1ee3b,0x1ee3b,6,19), (0x1ee42,0x1ee42,6,19),
(0x1ee47,0x1ee47,6,19), (0x1ee49,0x1ee49,6,19), (0x1ee4b,0x1ee4b,6,19),
(0x1ee4d,0x1ee4f,6,19), (0x1ee51,0x1ee52,6,19), (0x1ee54,0x1ee54,6,19),
(0x1ee57,0x1ee57,6,19), (0x1ee59,0x1ee59,6,19), (0x1ee5b,0x1ee5b,6,19),
(0x1ee5d,0x1ee5d,6,19), (0x1ee5f,0x1ee5f,6,19), (0x1ee61,0x1ee62,6,19),
(0x1ee64,0x1ee64,6,19), (0x1ee67,0x1ee6a,6,19), (0x1ee6c,0x1ee72,6,19),
(0x1ee74,0x1ee77,6,19), (0x1ee79,0x1ee7c,6,19), (0x1ee7e,0x1ee7e,6,19),
(0x1ee80,0x1ee89,6,19), (0x1ee8b,0x1ee9b,6,19), (0x1eea1,0x1eea3,6,19),
(0x1eea5,0x1eea9,6,19), (0x1eeab,0x1eebb,6,19), (0x1eef0,0x1eef1,6,6),
(0x1f000,0x1f02b,0,11), (0x1f030,0x1f093,0,11), (0x1f0a0,0x1f0ae,0,11),
(0x1f0b1,0x1f0be,0,11), (0x1f0c1,0x1f0cf,0,11), (0x1f0d1,0x1f0df,0,11),
(0x1f100,0x1f10a,0,14), (0x1f110,0x1f12e,0,11), (0x1f130,0x1f16b,0,11),
(0x1f170,0x1f19a,0,11), (0x1f1e6,0x1f1ff,0,11), (0x1f200,0x1f200,32,11),
(0x1f201,0x1f202,0,11), (0x1f210,0x1f23a,0,11), (0x1f240,0x1f248,0,11),
(0x1f250,0x1f251,0,11), (0x1f300,0x1f320,0,11), (0x1f330,0x1f335,0,11),
(0x1f337,0x1f37c,0,11), (0x1f380,0x1f393,0,11), (0x1f3a0,0x1f3c4,0,11),
(0x1f3c6,0x1f3ca,0,11), (0x1f3e0,0x1f3f0,0,11), (0x1f400,0x1f43e,0,11),
(0x1f440,0x1f440,0,11), (0x1f442,0x1f4f7,0,11), (0x1f4f9,0x1f4fc,0,11),
(0x1f500,0x1f53d,0,11), (0x1f540,0x1f543,0,11), (0x1f550,0x1f567,0,11),
(0x1f5fb,0x1f640,0,11), (0x1f645,0x1f64f,0,11), (0x1f680,0x1f6c5,0,11),
(0x1f700,0x1f773,0,11), (0x20000,0x2a6d6,35,19), (0x2a700,0x2b734,35,19),
(0x2b740,0x2b81d,35,19), (0x2f800,0x2fa1d,35,19), (0xe0001,0xe0001,0,13),
(0xe0020,0xe007f,0,13), (0xe0100,0xe01ef,40,23)
]}
def script_cat(chr):
""" For the unicode character chr return a tuple (Scriptname, Category). """
l = 0
r = len(script_data['idx']) - 1
c = ord(chr)
while r >= l:
m = (l + r) >> 1
if c < script_data['idx'][m][0]:
r = m - 1
elif c > script_data['idx'][m][1]:
l = m + 1
else:
return (
script_data['names'][script_data['idx'][m][2]],
script_data['cats'][script_data['idx'][m][3]])
return 'Unknown', 'Zzzz'
def script(chr):
a, _ = script_cat(chr)
return a
def category(chr):
_, a = script_cat(chr)
return a
def _compile_scripts_txt():
# build indexes from 'scripts.txt'
idx = []
names = []
cats = []
import urllib2, re, textwrap
url = 'http://www.unicode.org/Public/UNIDATA/Scripts.txt'
f = urllib2.urlopen(url)
for ln in f:
p = re.findall(r'([0-9A-F]+)(?:\.\.([0-9A-F]+))?\W+(\w+)\s*#\s*(\w+)', ln)
if p:
a, b, name, cat = p[0]
if name not in names:
names.append(name)
if cat not in cats:
cats.append(cat)
idx.append((int(a, 16), int(b or a, 16), names.index(name), cats.index(cat)))
idx.sort()
print('script_data = {\n"names":%s,\n"cats":%s,\n"idx":[\n%s\n]}' % (
'\n'.join(textwrap.wrap(repr(names), 80)),
'\n'.join(textwrap.wrap(repr(cats), 80)),
'\n'.join(textwrap.wrap(', '.join('(0x%x,0x%x,%d,%d)' % c for c in idx), 80))))

View file

@ -1,149 +0,0 @@
"""
global configuration of the project
External environment variables:
WTTR_MYDIR
WTTR_GEOLITE
WTTR_WEGO
WTTR_LISTEN_HOST
WTTR_LISTEN_PORT
WTTR_USER_AGENT
"""
from __future__ import print_function
import logging
import os
import re
MYDIR = os.path.abspath(os.path.dirname(os.path.dirname('__file__')))
if "WTTR_GEOLITE" in os.environ:
GEOLITE = os.environ["WTTR_GEOLITE"]
else:
GEOLITE = os.path.join(MYDIR, 'data', "GeoLite2-City.mmdb")
WEGO = os.environ.get("WTTR_WEGO", "/home/igor/go/bin/we-lang")
PYPHOON = "pyphoon-lolcat"
_DATADIR = "/wttr.in"
_LOGDIR = "/wttr.in/log"
IP2LCACHE = os.path.join(_DATADIR, "cache/ip2l/")
PNG_CACHE = os.path.join(_DATADIR, "cache/png")
LRU_CACHE = os.path.join(_DATADIR, "cache/lru")
LOG_FILE = os.path.join(_LOGDIR, 'main.log')
ALIASES = os.path.join(MYDIR, "share/aliases")
ANSI2HTML = os.path.join(MYDIR, "share/ansi2html.sh")
BLACKLIST = os.path.join(MYDIR, "share/blacklist")
HELP_FILE = os.path.join(MYDIR, 'share/help.txt')
BASH_FUNCTION_FILE = os.path.join(MYDIR, 'share/bash-function.txt')
TRANSLATION_FILE = os.path.join(MYDIR, 'share/translation.txt')
IATA_CODES_FILE = os.path.join(MYDIR, 'share/list-of-iata-codes.txt')
TEMPLATES = os.path.join(MYDIR, 'share/templates')
STATIC = os.path.join(MYDIR, 'share/static')
NOT_FOUND_LOCATION = "not found"
DEFAULT_LOCATION = "oymyakon"
MALFORMED_RESPONSE_HTML_PAGE = open(os.path.join(STATIC, 'malformed-response.html')).read()
GEOLOCATOR_SERVICE = 'http://localhost:8004'
# number of queries from the same IP address is limited
# (minute, hour, day) limitations:
QUERY_LIMITS = (300, 3600, 24*3600)
LISTEN_HOST = os.environ.get("WTTR_LISTEN_HOST", "")
try:
LISTEN_PORT = int(os.environ.get("WTTR_LISTEN_PORT"))
except (TypeError, ValueError):
LISTEN_PORT = 8002
PROXY_HOST = "127.0.0.1"
PROXY_PORT = 5001
PROXY_CACHEDIR = os.path.join(_DATADIR, "cache/proxy-wwo/")
MY_EXTERNAL_IP = '5.9.243.187'
PLAIN_TEXT_AGENTS = [
"curl",
"httpie",
"lwp-request",
"wget",
"python-requests",
"openbsd ftp",
"powershell",
]
PLAIN_TEXT_PAGES = [':help', ':bash.function', ':translation', ':iterm2']
_IPLOCATION_ORDER = os.environ.get(
"WTTR_IPLOCATION_ORDER",
'geoip,ip2location,ipinfo')
IPLOCATION_ORDER = _IPLOCATION_ORDER.split(',')
_IP2LOCATION_KEY_FILE = os.environ.get(
"WTTR_IP2LOCATION_KEY_FILE",
os.environ['HOME'] + '/.ip2location.key')
IP2LOCATION_KEY = None
if os.path.exists(_IP2LOCATION_KEY_FILE):
IP2LOCATION_KEY = open(_IP2LOCATION_KEY_FILE, 'r').read().strip()
_IPINFO_KEY_FILE = os.environ.get(
"WTTR_IPINFO_KEY_FILE",
os.environ['HOME'] + '/.ipinfo.key')
IPINFO_TOKEN = None
if os.path.exists(_IPINFO_KEY_FILE):
IPINFO_TOKEN = open(_IPINFO_KEY_FILE, 'r').read().strip()
_WWO_KEY_FILE = os.environ.get(
"WTTR_WWO_KEY_FILE",
os.environ['HOME'] + '/.wwo.key')
WWO_KEY = "key-is-not-specified"
USE_METNO = True
USER_AGENT = os.environ.get("WTTR_USER_AGENT", "")
if os.path.exists(_WWO_KEY_FILE):
WWO_KEY = open(_WWO_KEY_FILE, 'r').read().strip()
USE_METNO = False
def error(text):
"log error `text` and raise a RuntimeError exception"
if not text.startswith('Too many queries'):
print(text)
logging.error("ERROR %s", text)
raise RuntimeError(text)
def log(text):
"log error `text` and do not raise any exceptions"
if not text.startswith('Too many queries'):
print(text)
logging.info(text)
def debug_log(text):
"""
Write `text` to the debug log
"""
with open('/tmp/wttr.in-debug.log', 'a') as f_debug:
f_debug.write(text+'\n')
def get_help_file(lang):
"Return help file for `lang`"
help_file = os.path.join(MYDIR, 'share/translations/%s-help.txt' % lang)
if os.path.exists(help_file):
return help_file
return HELP_FILE
def remove_ansi(sometext):
ansi_escape = re.compile(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]')
return ansi_escape.sub('', sometext)

View file

@ -1,108 +0,0 @@
"""
Connection limitation.
Number of connections from one IP is limited.
We have nothing against scripting and automated queries.
Even the opposite, we encourage them. But there are some
connection limits that even we can't handle.
Currently the limits are quite restrictive, but they will be relaxed
in the future.
Usage:
limits = Limits()
not_allowed = limits.check_ip(ip_address)
if not_allowed:
return "ERROR: %s" % not_allowed
[Taken from github.com/chubin/cheat.sh]
"""
import time
from globals import log
def _time_caps(minutes, hours, days):
return {
'min': minutes,
'hour': hours,
'day': days,
}
class Limits(object):
"""
Queries limitation (by IP).
Exports:
check_ip(ip_address)
"""
def __init__(self, whitelist=None, limits=None):
self.intervals = ['min', 'hour', 'day']
self.divisor = _time_caps(60, 3600, 86400)
self.last_update = _time_caps(0, 0, 0)
if limits:
self.limit = _time_caps(*limits)
else:
self.limit = _time_caps(30, 600, 1000)
if whitelist:
self.whitelist = whitelist[:]
else:
self.whitelist = []
self.counter = {
'min': {},
'hour': {},
'day': {},
}
self._clear_counters_if_needed()
def _log_visit(self, interval, ip_address):
if ip_address not in self.counter[interval]:
self.counter[interval][ip_address] = 0
self.counter[interval][ip_address] += 1
def _limit_exceeded(self, interval, ip_address):
visits = self.counter[interval][ip_address]
limit = self._get_limit(interval)
return visits > limit
def _get_limit(self, interval):
return self.limit[interval]
def _report_excessive_visits(self, interval, ip_address):
log("%s LIMITED [%s for %s]" % (ip_address, self._get_limit(interval), interval))
def check_ip(self, ip_address):
"""
Check if `ip_address` is allowed, and if not raise an RuntimeError exception.
Return True otherwise
"""
if ip_address in self.whitelist:
return None
self._clear_counters_if_needed()
for interval in self.intervals:
self._log_visit(interval, ip_address)
if self._limit_exceeded(interval, ip_address):
self._report_excessive_visits(interval, ip_address)
return ("Not so fast! Number of queries per %s is limited to %s"
% (interval, self._get_limit(interval)))
return None
def reset(self):
"""
Reset all counters for all IPs
"""
for interval in self.intervals:
self.counter[interval] = {}
def _clear_counters_if_needed(self):
current_time = int(time.time())
for interval in self.intervals:
if current_time // self.divisor[interval] != self.last_update[interval]:
self.counter[interval] = {}
self.last_update[interval] = current_time / self.divisor[interval]

View file

@ -1,354 +0,0 @@
"""
All location related functions and converters.
The main entry point is `location_processing`
which gets `location` and `source_ip_address`
and basing on this information generates
precise location description.
"""
from __future__ import print_function
import sys
import os
import json
import socket
import requests
import geoip2.database
from globals import GEOLITE, GEOLOCATOR_SERVICE, IP2LCACHE, IP2LOCATION_KEY, NOT_FOUND_LOCATION, \
ALIASES, BLACKLIST, IATA_CODES_FILE, IPLOCATION_ORDER, IPINFO_TOKEN
GEOIP_READER = geoip2.database.Reader(GEOLITE)
def ascii_only(string):
"Check if `string` contains only ASCII symbols"
try:
for _ in range(5):
string = string.encode('utf-8')
return True
except UnicodeDecodeError:
return False
def is_ip(ip_addr):
"""
Check if `ip_addr` looks like an IP Address
"""
if sys.version_info[0] < 3:
ip_addr = ip_addr.encode("utf-8")
try:
socket.inet_pton(socket.AF_INET, ip_addr)
return True
except socket.error:
try:
socket.inet_pton(socket.AF_INET6, ip_addr)
return True
except socket.error:
return False
def location_normalize(location):
"""
Normalize location name `location`
"""
#translation_table = dict.fromkeys(map(ord, '!@#$*;'), None)
def _remove_chars(chars, string):
return ''.join(x for x in string if x not in chars)
location = location.lower().replace('_', ' ').replace('+', ' ').strip()
if not location.startswith('moon@'):
location = _remove_chars(r'!@#$*;:\\', location)
return location
def geolocator(location):
"""
Return a GPS pair for specified `location` or None
if nothing can't be found
"""
try:
geo = requests.get('%s/%s' % (GEOLOCATOR_SERVICE, location)).text
except requests.exceptions.ConnectionError as exception:
print("ERROR: %s" % exception)
return None
if geo == "":
return None
try:
answer = json.loads(geo.encode('utf-8'))
return answer
except ValueError as exception:
print("ERROR: %s" % exception)
return None
return None
def ipcachewrite(ip_addr, location):
cached = os.path.join(IP2LCACHE, ip_addr)
if not os.path.exists(IP2LCACHE):
os.makedirs(IP2LCACHE)
with open(cached, 'w') as file:
file.write(location[0] + ';' + location[1])
def ipcache(ip_addr):
cached = os.path.join(IP2LCACHE, ip_addr)
if not os.path.exists(IP2LCACHE):
os.makedirs(IP2LCACHE)
location = None
if os.path.exists(cached):
location = open(cached, 'r').read().split(';')
if len(location) > 3:
return location[3], location[1]
elif len(location) > 1:
return location[0], location[1]
else:
return location[0], None
return None, None
def ip2location(ip_addr):
"Convert IP address `ip_addr` to a location name"
location = ipcache(ip_addr)
if location:
return location
# if IP2LOCATION_KEY is not set, do not the query,
# because the query wont be processed anyway
if IP2LOCATION_KEY:
try:
location = requests\
.get('http://api.ip2location.com/?ip=%s&key=%s&package=WS3' \
% (ip_addr, IP2LOCATION_KEY)).text
except requests.exceptions.ConnectionError:
pass
if location and ';' in location:
ipcachewrite(ip_addr, location)
location = location.split(';')[3], location.split(';')[1]
else:
location = location, None
return location
def ipinfo(ip_addr):
location = ipcache(ip_addr)
if location:
return location
if IPINFO_TOKEN:
r = requests.get('https://ipinfo.io/%s/json?token=%s' %
(ip_addr, IPINFO_TOKEN))
if r.status_code == 200:
location = r.json()["city"], r.json()["country"]
if location:
ipcachewrite(ip_addr, location)
return location
def geoip(ip_addr):
try:
response = GEOIP_READER.city(ip_addr)
country = response.country.name
city = response.city.name
except geoip2.errors.AddressNotFoundError:
country = None
city = None
return city, country
def workaround(city, country):
# workaround for the strange bug with the country name
# maybe some other countries has this problem too
#
# Having these in a separate function will help if this gets to
# be a problem
if country == 'Russian Federation':
country = 'Russia'
return city, country
def get_location(ip_addr):
"""
Return location pair (CITY, COUNTRY) for `ip_addr`
"""
for method in IPLOCATION_ORDER:
if method == 'geoip':
city, country = geoip(ip_addr)
elif method == 'ip2location':
city, country = ip2location(ip_addr)
elif method == 'ipinfo':
city, country = ipinfo(ip_addr)
else:
print("ERROR: invalid iplocation method speficied: %s" % method)
if city is not None:
city, country = workaround(city, country)
return city, country
#
# temporary disabled it because of geoip services capcacity
#
#if city is None and response.location:
# coord = "%s, %s" % (response.location.latitude, response.location.longitude)
# try:
# location = geolocator.reverse(coord, language='en')
# city = location.raw.get('address', {}).get('city')
# except:
# pass
# No methods resulted in a location - return default
return NOT_FOUND_LOCATION, None
def location_canonical_name(location):
"Find canonical name for `location`"
location = location_normalize(location)
if location.lower() in LOCATION_ALIAS:
return LOCATION_ALIAS[location.lower()]
return location
def load_aliases(aliases_filename):
"""
Load aliases from the aliases file
"""
aliases_db = {}
with open(aliases_filename, 'r') as f_aliases:
for line in f_aliases.readlines():
try:
from_, to_ = line.decode('utf-8').split(':', 1)
except AttributeError:
from_, to_ = line.split(':', 1)
aliases_db[location_normalize(from_)] = location_normalize(to_)
return aliases_db
def load_iata_codes(iata_codes_filename):
"""
Load IATA codes from the IATA codes file
"""
with open(iata_codes_filename, 'r') as f_iata_codes:
result = []
for line in f_iata_codes.readlines():
result.append(line.strip())
return set(result)
LOCATION_ALIAS = load_aliases(ALIASES)
LOCATION_BLACK_LIST = [x.strip() for x in open(BLACKLIST, 'r').readlines()]
IATA_CODES = load_iata_codes(IATA_CODES_FILE)
def is_location_blocked(location):
"""
Return True if this location is blocked
or False if it is allowed
"""
return location is not None and location.lower() in LOCATION_BLACK_LIST
def get_hemisphere(location):
"""
Return hemisphere of the location (True = North, False = South).
Assume North and return True if location can't be found.
"""
location_string = location[0]
if location[1] is not None:
location_string += ",%s" % location[1]
geolocation = geolocator(location_string)
if geolocation is None:
return True
return geolocation["latitude"] > 0
def location_processing(location, ip_addr):
"""
"""
# if location is starting with ~
# or has non ascii symbols
# it should be handled like a search term (for geolocator)
override_location_name = None
full_address = None
hide_full_address = False
force_show_full_address = location is not None and location.startswith('~')
# location ~ means that it should be detected automatically,
# and shown in the location line below the report
if location == '~':
location = None
if location and location.lstrip('~ ').startswith('@'):
try:
location, country = get_location(
socket.gethostbyname(
location.lstrip('~ ')[1:]))
location = '~' + location
if country:
location += ", %s" % country
hide_full_address = not force_show_full_address
except:
location, country = NOT_FOUND_LOCATION, None
query_source_location = get_location(ip_addr)
# For moon queries, hemisphere must be found
# True for North, False for South
hemisphere = False
if location is not None and (location.lower()+"@").startswith("moon@"):
hemisphere = get_hemisphere(query_source_location)
country = None
if not location or location == 'MyLocation':
location = ip_addr
if is_ip(location):
location, country = get_location(location)
# here too
if location:
location = '~' + location
if country:
location += ", %s" % country
hide_full_address = not force_show_full_address
if location and not location.startswith('~'):
tmp_location = location_canonical_name(location)
if tmp_location != location:
override_location_name = location
location = tmp_location
# up to this point it is possible that the name
# contains some unicode symbols
# here we resolve them
if location is not None: # and not ascii_only(location):
location = "~" + location.lstrip('~ ')
if not override_location_name:
override_location_name = location.lstrip('~')
# if location is not None and location.upper() in IATA_CODES:
# location = '~%s' % location
if location is not None and not location.startswith("~-,") and location.startswith('~'):
geolocation = geolocator(location_canonical_name(location[1:]))
if geolocation is not None:
if not override_location_name:
override_location_name = location[1:].replace('+', ' ')
location = "%s,%s" % (geolocation['latitude'], geolocation['longitude'])
country = None
if not hide_full_address:
full_address = geolocation['address']
else:
full_address = None
else:
location = NOT_FOUND_LOCATION #location[1:]
return location, \
override_location_name, \
full_address, \
country, \
query_source_location, \
hemisphere

View file

@ -1,462 +0,0 @@
#!/bin/env python
# vim: fileencoding=utf-8
from datetime import datetime, timedelta
import json
import logging
import os
import re
import sys
import timezonefinder
from pytz import timezone
from constants import WWO_CODE
logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO"))
logger = logging.getLogger(__name__)
def metno_request(path, query_string):
# We'll need to sanitize the inbound request - ideally the
# premium/v1/weather.ashx portion would have always been here, though
# it seems as though the proxy was built after the majority of the app
# and not refactored. For WAPI we'll strip this and the API key out,
# then manage it on our own.
logger.debug('Original path: ' + path)
logger.debug('Original query: ' + query_string)
path = path.replace('premium/v1/weather.ashx',
'weatherapi/locationforecast/2.0/complete')
query_string = re.sub(r'key=[^&]*&', '', query_string)
query_string = re.sub(r'format=[^&]*&', '', query_string)
days = int(re.search(r'num_of_days=([0-9]+)&', query_string).group(1))
query_string = re.sub(r'num_of_days=[0-9]+&', '', query_string)
# query_string = query_string.replace('key=', '?key=' + WAPI_KEY)
# TP is for hourly forecasting, which isn't available in the free api.
query_string = re.sub(r'tp=[0-9]*&', '', query_string)
# This assumes lang=... is at the end. Also note that the API doesn't
# localize, and we're not either. TODO: add language support
query_string = re.sub(r'lang=[^&]*$', '', query_string)
query_string = re.sub(r'&$', '', query_string)
logger.debug('qs: ' + query_string)
# Deal with coordinates. Need to be rounded to 4 decimals for metno ToC
# and in a different query string format
coord_match = re.search(r'q=[^&]*', query_string)
coords_str = coord_match.group(0)
coords = re.findall(r'[-0-9.]+', coords_str)
lat = str(round(float(coords[0]), 4))
lng = str(round(float(coords[1]), 4))
logger.debug('lat: ' + lat)
logger.debug('lng: ' + lng)
query_string = re.sub(r'q=[^&]*', 'lat=' + lat + '&lon=' + lng + '&',
query_string)
logger.debug('Return path: ' + path)
logger.debug('Return query: ' + query_string)
return path, query_string, days
def celsius_to_f(celsius):
return round((1.8 * celsius) + 32, 1)
def to_weather_code(symbol_code):
logger.debug(symbol_code)
code = re.sub(r'_.*', '', symbol_code)
logger.debug(code)
# symbol codes: https://api.met.no/weatherapi/weathericon/2.0/documentation
# they also have _day, _night and _polartwilight variants
# See json from https://api.met.no/weatherapi/weathericon/2.0/legends
# WWO codes: https://github.com/chubin/wttr.in/blob/master/lib/constants.py
# http://www.worldweatheronline.com/feed/wwoConditionCodes.txt
weather_code_map = {
"clearsky": 113,
"cloudy": 119,
"fair": 116,
"fog": 143,
"heavyrain": 302,
"heavyrainandthunder": 389,
"heavyrainshowers": 305,
"heavyrainshowersandthunder": 386,
"heavysleet": 314, # There's a ton of 'LightSleet' in WWO_CODE...
"heavysleetandthunder": 377,
"heavysleetshowers": 362,
"heavysleetshowersandthunder": 374,
"heavysnow": 230,
"heavysnowandthunder": 392,
"heavysnowshowers": 371,
"heavysnowshowersandthunder": 392,
"lightrain": 266,
"lightrainandthunder": 200,
"lightrainshowers": 176,
"lightrainshowersandthunder": 386,
"lightsleet": 281,
"lightsleetandthunder": 377,
"lightsleetshowers": 284,
"lightsnow": 320,
"lightsnowandthunder": 392,
"lightsnowshowers": 368,
"lightssleetshowersandthunder": 365,
"lightssnowshowersandthunder": 392,
"partlycloudy": 116,
"rain": 293,
"rainandthunder": 389,
"rainshowers": 299,
"rainshowersandthunder": 386,
"sleet": 185,
"sleetandthunder": 392,
"sleetshowers": 263,
"sleetshowersandthunder": 392,
"snow": 329,
"snowandthunder": 392,
"snowshowers": 230,
"snowshowersandthunder": 392,
}
if code not in weather_code_map:
logger.debug('not found')
return -1 # not found
logger.debug(weather_code_map[code])
return weather_code_map[code]
def to_description(symbol_code):
desc = WWO_CODE[str(to_weather_code(symbol_code))]
logger.debug(desc)
return desc
def to_16_point(degrees):
# 360 degrees / 16 = 22.5 degrees of arc or 11.25 degrees around the point
if degrees > (360 - 11.25) or degrees <= 11.25:
return 'N'
if degrees > 11.25 and degrees <= (11.25 + 22.5):
return 'NNE'
if degrees > (11.25 + (22.5 * 1)) and degrees <= (11.25 + (22.5 * 2)):
return 'NE'
if degrees > (11.25 + (22.5 * 2)) and degrees <= (11.25 + (22.5 * 3)):
return 'ENE'
if degrees > (11.25 + (22.5 * 3)) and degrees <= (11.25 + (22.5 * 4)):
return 'E'
if degrees > (11.25 + (22.5 * 4)) and degrees <= (11.25 + (22.5 * 5)):
return 'ESE'
if degrees > (11.25 + (22.5 * 5)) and degrees <= (11.25 + (22.5 * 6)):
return 'SE'
if degrees > (11.25 + (22.5 * 6)) and degrees <= (11.25 + (22.5 * 7)):
return 'SSE'
if degrees > (11.25 + (22.5 * 7)) and degrees <= (11.25 + (22.5 * 8)):
return 'S'
if degrees > (11.25 + (22.5 * 8)) and degrees <= (11.25 + (22.5 * 9)):
return 'SSW'
if degrees > (11.25 + (22.5 * 9)) and degrees <= (11.25 + (22.5 * 10)):
return 'SW'
if degrees > (11.25 + (22.5 * 10)) and degrees <= (11.25 + (22.5 * 11)):
return 'WSW'
if degrees > (11.25 + (22.5 * 11)) and degrees <= (11.25 + (22.5 * 12)):
return 'W'
if degrees > (11.25 + (22.5 * 12)) and degrees <= (11.25 + (22.5 * 13)):
return 'WNW'
if degrees > (11.25 + (22.5 * 13)) and degrees <= (11.25 + (22.5 * 14)):
return 'NW'
if degrees > (11.25 + (22.5 * 14)) and degrees <= (11.25 + (22.5 * 15)):
return 'NNW'
def meters_to_miles(meters):
return round(meters * 0.00062137, 2)
def mm_to_inches(mm):
return round(mm / 25.4, 2)
def hpa_to_mb(hpa):
return hpa
def hpa_to_in(hpa):
return round(hpa * 0.02953, 2)
def group_hours_to_days(lat, lng, hourlies, days_to_return):
tf = timezonefinder.TimezoneFinder()
timezone_str = tf.certain_timezone_at(lat=lat, lng=lng)
logger.debug('got TZ: ' + timezone_str)
tz = timezone(timezone_str)
start_day_gmt = datetime.fromisoformat(hourlies[0]['time']
.replace('Z', '+00:00'))
start_day_local = start_day_gmt.astimezone(tz)
end_day_local = (start_day_local + timedelta(days=days_to_return - 1)).date()
logger.debug('series starts at gmt time: ' + str(start_day_gmt))
logger.debug('series starts at local time: ' + str(start_day_local))
logger.debug('series ends on day: ' + str(end_day_local))
days = {}
for hour in hourlies:
current_day_gmt = datetime.fromisoformat(hour['time']
.replace('Z', '+00:00'))
current_local = current_day_gmt.astimezone(tz)
current_day_local = current_local.date()
if current_day_local > end_day_local:
continue
if current_day_local not in days:
days[current_day_local] = {'hourly': []}
hour['localtime'] = current_local.time()
days[current_day_local]['hourly'].append(hour)
# Need a second pass to build the min/max/avg data
for date, day in days.items():
minTempC = -999
maxTempC = 1000
avgTempC = None
n = 0
maxUvIndex = 0
for hour in day['hourly']:
temp = hour['data']['instant']['details']['air_temperature']
if temp > minTempC:
minTempC = temp
if temp < maxTempC:
maxTempC = temp
if avgTempC is None:
avgTempC = temp
n = 1
else:
avgTempC = ((avgTempC * n) + temp) / (n + 1)
n = n + 1
uv = hour['data']['instant']['details']
if 'ultraviolet_index_clear_sky' in uv:
if uv['ultraviolet_index_clear_sky'] > maxUvIndex:
maxUvIndex = uv['ultraviolet_index_clear_sky']
day["maxtempC"] = str(maxTempC)
day["maxtempF"] = str(celsius_to_f(maxTempC))
day["mintempC"] = str(minTempC)
day["mintempF"] = str(celsius_to_f(minTempC))
day["avgtempC"] = str(round(avgTempC, 1))
day["avgtempF"] = str(celsius_to_f(avgTempC))
# day["totalSnow_cm": "not implemented",
# day["sunHour": "12", # This would come from astonomy data
day["uvIndex"] = str(maxUvIndex)
return days
def _convert_hour(hour):
# Whatever is upstream is expecting data in the shape of WWO. This method will
# morph from metno to hourly WWO response format.
# Note that WWO is providing data every 3 hours. Metno provides every hour
# {
# "time": "0",
# "tempC": "19",
# "tempF": "66",
# "windspeedMiles": "6",
# "windspeedKmph": "9",
# "winddirDegree": "276",
# "winddir16Point": "W",
# "weatherCode": "119",
# "weatherIconUrl": [
# {
# "value": "http://cdn.worldweatheronline.com/images/wsymbols01_png_64/wsymbol_0003_white_cloud.png"
# }
# ],
# "weatherDesc": [
# {
# "value": "Cloudy"
# }
# ],
# "precipMM": "0.0",
# "precipInches": "0.0",
# "humidity": "62",
# "visibility": "10",
# "visibilityMiles": "6",
# "pressure": "1017",
# "pressureInches": "31",
# "cloudcover": "66",
# "HeatIndexC": "19",
# "HeatIndexF": "66",
# "DewPointC": "12",
# "DewPointF": "53",
# "WindChillC": "19",
# "WindChillF": "66",
# "WindGustMiles": "8",
# "WindGustKmph": "13",
# "FeelsLikeC": "19",
# "FeelsLikeF": "66",
# "chanceofrain": "0",
# "chanceofremdry": "93",
# "chanceofwindy": "0",
# "chanceofovercast": "89",
# "chanceofsunshine": "18",
# "chanceoffrost": "0",
# "chanceofhightemp": "0",
# "chanceoffog": "0",
# "chanceofsnow": "0",
# "chanceofthunder": "0",
# "uvIndex": "1"
details = hour['data']['instant']['details']
if 'next_1_hours' in hour['data']:
next_hour = hour['data']['next_1_hours']
elif 'next_6_hours' in hour['data']:
next_hour = hour['data']['next_6_hours']
elif 'next_12_hours' in hour['data']:
next_hour = hour['data']['next_12_hours']
else:
next_hour = {}
# Need to dig out symbol_code and precipitation_amount
symbol_code = 'clearsky_day' # Default to sunny
if 'summary' in next_hour and 'symbol_code' in next_hour['summary']:
symbol_code = next_hour['summary']['symbol_code']
precipitation_amount = 0 # Default to no rain
if 'details' in next_hour and 'precipitation_amount' in next_hour['details']:
precipitation_amount = next_hour['details']['precipitation_amount']
uvIndex = 0 # default to 0 index
if 'ultraviolet_index_clear_sky' in details:
uvIndex = details['ultraviolet_index_clear_sky']
localtime = ''
if 'localtime' in hour:
localtime = "{h:02.0f}".format(h=hour['localtime'].hour) + \
"{m:02.0f}".format(m=hour['localtime'].minute)
logger.debug(str(hour['localtime']))
# time property is local time, 4 digit 24 hour, with no :, e.g. 2100
return {
'time': localtime,
'observation_time': hour['time'], # Need to figure out WWO TZ
# temp_C is used in we-lang.go calcs in such a way
# as to expect a whole number
'temp_C': str(int(round(details['air_temperature'], 0))),
# temp_F can be more precise - not used in we-lang.go calcs
'temp_F': str(celsius_to_f(details['air_temperature'])),
'weatherCode': str(to_weather_code(symbol_code)),
'weatherIconUrl': [{
'value': 'not yet implemented',
}],
'weatherDesc': [{
'value': to_description(symbol_code),
}],
# similiarly, windspeedMiles is not used by we-lang.go, but kmph is
"windspeedMiles": str(meters_to_miles(details['wind_speed'])),
"windspeedKmph": str(int(round(details['wind_speed'], 0))),
"winddirDegree": str(details['wind_from_direction']),
"winddir16Point": to_16_point(details['wind_from_direction']),
"precipMM": str(precipitation_amount),
"precipInches": str(mm_to_inches(precipitation_amount)),
"humidity": str(details['relative_humidity']),
"visibility": 'not yet implemented', # str(details['vis_km']),
"visibilityMiles": 'not yet implemented', # str(details['vis_miles']),
"pressure": str(hpa_to_mb(details['air_pressure_at_sea_level'])),
"pressureInches": str(hpa_to_in(details['air_pressure_at_sea_level'])),
"cloudcover": 'not yet implemented', # Convert from cloud_area_fraction?? str(details['cloud']),
# metno doesn't have FeelsLikeC, but we-lang.go is using it in calcs,
# so we shall set it to temp_C
"FeelsLikeC": str(int(round(details['air_temperature'], 0))),
"FeelsLikeF": 'not yet implemented', # str(details['feelslike_f']),
"uvIndex": str(uvIndex),
}
def _convert_hourly(hours):
converted_hours = []
for hour in hours:
converted_hours.append(_convert_hour(hour))
return converted_hours
# Whatever is upstream is expecting data in the shape of WWO. This method will
# morph from metno to WWO response format.
def create_standard_json_from_metno(content, days_to_return):
try:
forecast = json.loads(content) # pylint: disable=invalid-name
except (ValueError, TypeError) as exception:
logger.error("---")
logger.error(exception)
logger.error("---")
return {}, ''
hourlies = forecast['properties']['timeseries']
current = hourlies[0]
# We are assuming these units:
# "units": {
# "air_pressure_at_sea_level": "hPa",
# "air_temperature": "celsius",
# "air_temperature_max": "celsius",
# "air_temperature_min": "celsius",
# "cloud_area_fraction": "%",
# "cloud_area_fraction_high": "%",
# "cloud_area_fraction_low": "%",
# "cloud_area_fraction_medium": "%",
# "dew_point_temperature": "celsius",
# "fog_area_fraction": "%",
# "precipitation_amount": "mm",
# "relative_humidity": "%",
# "ultraviolet_index_clear_sky": "1",
# "wind_from_direction": "degrees",
# "wind_speed": "m/s"
# }
content = {
'data': {
'request': [{
'type': 'feature',
'query': str(forecast['geometry']['coordinates'][1]) + ',' +
str(forecast['geometry']['coordinates'][0])
}],
'current_condition': [
_convert_hour(current)
],
'weather': []
}
}
days = group_hours_to_days(forecast['geometry']['coordinates'][1],
forecast['geometry']['coordinates'][0],
hourlies, days_to_return)
# TODO: Astronomy needs to come from this:
# https://api.met.no/weatherapi/sunrise/2.0/.json?lat=40.7127&lon=-74.0059&date=2020-10-07&offset=-05:00
# and obviously can be cached for a while
# https://api.met.no/weatherapi/sunrise/2.0/documentation
# Note that full moon/new moon/first quarter/last quarter aren't returned
# and the moonphase value should match these from WWO:
# New Moon
# Waxing Crescent
# First Quarter
# Waxing Gibbous
# Full Moon
# Waning Gibbous
# Last Quarter
# Waning Crescent
for date, day in days.items():
content['data']['weather'].append({
"date": str(date),
"astronomy": [],
"maxtempC": day['maxtempC'],
"maxtempF": day['maxtempF'],
"mintempC": day['mintempC'],
"mintempF": day['mintempF'],
"avgtempC": day['avgtempC'],
"avgtempF": day['avgtempF'],
"totalSnow_cm": "not implemented",
"sunHour": "12", # This would come from astonomy data
"uvIndex": day['uvIndex'],
'hourly': _convert_hourly(day['hourly']),
})
# for day in forecast.
return json.dumps(content)
if __name__ == "__main__":
# if len(sys.argv) == 1:
# for deg in range(0, 360):
# print('deg: ' + str(deg) + '; 16point: ' + to_16_point(deg))
if len(sys.argv) == 2:
req = sys.argv[1].split('?')
# to_description(sys.argv[1])
metno_request(req[0], req[1])
elif len(sys.argv) == 3:
with open(sys.argv[1], 'r') as contentf:
content = create_standard_json_from_metno(contentf.read(),
int(sys.argv[2]))
print(content)
else:
print('usage: metno <content file> <days>')

View file

@ -1,172 +0,0 @@
import re
import json
import zlib
import base64
def serialize(parsed_query):
return base64.b64encode(
zlib.compress(
json.dumps(parsed_query).encode("utf-8")),
altchars=b"-_").decode("utf-8")
def deserialize(url):
string = url[2:]
extension = None
if "." in string:
string, extension = string.split(".", 1)
try:
result = json.loads(
zlib.decompress(
base64.b64decode(string, altchars=b"-_")).decode("utf-8"))
except zlib.error:
return None
if extension == "png":
result["png_filename"] = url
result["html_output"] = False
return result
def metric_or_imperial(query, lang, us_ip=False):
"""
"""
# what units should be used
# metric or imperial
# based on query and location source (imperial for US by default)
if query.get('use_metric', False) and not query.get('use_imperial', False):
query['use_imperial'] = False
query['use_metric'] = True
elif query.get('use_imperial', False) and not query.get('use_metric', False):
query['use_imperial'] = True
query['use_metric'] = False
elif lang == 'us':
# slack uses m by default, to override it speciy us.wttr.in
query['use_imperial'] = True
query['use_metric'] = False
else:
if us_ip:
query['use_imperial'] = True
query['use_metric'] = False
else:
query['use_imperial'] = False
query['use_metric'] = True
return query
def parse_query(args):
result = {}
reserved_args = ["lang"]
q = ""
for key, val in args.items():
if len(val) == 0:
q += key
continue
if val == 'True':
val = True
if val == 'False':
val = False
result[key] = val
if q is None:
return result
if 'A' in q:
result['force-ansi'] = True
if 'n' in q:
result['narrow'] = True
if 'm' in q:
result['use_metric'] = True
if 'M' in q:
result['use_ms_for_wind'] = True
if 'u' in q:
result['use_imperial'] = True
if 'I' in q:
result['inverted_colors'] = True
if 't' in q:
result['transparency'] = '150'
if 'T' in q:
result['no-terminal'] = True
if 'p' in q:
result['padding'] = True
for days in "0123":
if days in q:
result['days'] = days
if 'q' in q:
result['no-caption'] = True
if 'Q' in q:
result['no-city'] = True
if 'F' in q:
result['no-follow-line'] = True
for key, val in args.items():
if val == 'True':
val = True
if val == 'False':
val = False
if val:
result[key] = val
# currently `view` is alias for `format`
if "format" in result and not result.get("view"):
result["view"] = result["format"]
del result["format"]
return result
def parse_wttrin_png_name(name):
"""
Parse the PNG filename and return the result as a dictionary.
For example:
input = City_200x_lang=ru.png
output = {
"lang": "ru",
"width": "200",
"filetype": "png",
"location": "City"
}
"""
parsed = {}
to_be_parsed = {}
if name.lower()[-4:] == '.png':
parsed['filetype'] = 'png'
name = name[:-4]
parts = name.split('_')
parsed['location'] = parts[0]
one_letter_options = ""
for part in parts[1:]:
if re.match('(?:[0-9]+)x', part):
parsed['width'] = part[:-1]
elif re.match('x(?:[0-9]+)', part):
parsed['height'] = part[1:]
elif re.match(part, '(?:[0-9]+)x(?:[0-9]+)'):
parsed['width'], parsed['height'] = part.split('x', 1)
elif '=' in part:
arg, val = part.split('=', 1)
to_be_parsed[arg] = val
else:
one_letter_options += part
for letter in one_letter_options:
to_be_parsed[letter] = ''
parsed.update(parse_query(to_be_parsed))
# currently `view` is alias for `format`
if "format" in parsed and not parsed.get("view"):
parsed["view"] = parsed["format"]
del parsed["format"]
return parsed

View file

@ -1,851 +0,0 @@
# vim: fileencoding=utf-8
"""
Translation of almost everything.
"""
FULL_TRANSLATION = [
"ar", "af", "be", "ca", "da", "de", "el", "et",
"fr", "fa", "hi", "hu", "ia", "id", "it",
"nb", "nl", "oc", "pl", "pt-br", "ro",
"ru", "tr", "th", "uk", "vi", "zh-cn", "zh-tw"
]
PARTIAL_TRANSLATION = [
"az", "bg", "bs", "cy", "cs",
"eo", "es", "eu", "fi", "ga", "hi", "hr",
"hy", "is", "ja", "jv", "ka", "kk",
"ko", "ky", "lt", "lv", "mk", "ml", "nl", "fy",
"nn", "pt", "pt-br", "sk", "sl", "sr", "sr-lat",
"sv", "sw", "te", "uz",
"zh", "zu", "he",
]
PROXY_LANGS = [
"af", "ar", "az", "be", "bs", "ca",
"cy", "de", "el", "eo", "et", "eu", "fa", "fr",
"fy", "ga", "he", "hr", "hu", "hy",
"ia", "id", "is", "it", "ja", "kk",
"lv", "mk", "nb", "nn", "oc", "ro",
"ru", "sl", "th", "pt-br", "uk", "uz",
"vi", "zh-cn", "zh-tw",
]
SUPPORTED_LANGS = FULL_TRANSLATION + PARTIAL_TRANSLATION
MESSAGE = {
'NOT_FOUND_MESSAGE': {
'en': u"""
We were unable to find your location
so we have brought you to Oymyakon,
one of the coldest permanently inhabited locales on the planet.
""",
'af': u"""
Ons kon nie u ligging opspoor nie
gevolglik het ons vir u na Oymyakon geneem,
een van die koudste permanent bewoonde plekke op aarde.
""",
'ar': u"""
تعذر علينا العثور على موقعك
لذلك قمنا بجلبك إلي أويمياكون,
إحدى الأماكن المَأْهُولة الأكثر برودة علي الإطلاق في هذا الكوكب.
""",
'be': u"""
Ваша месцазнаходжанне вызначыць не атрымалася,
таму мы пакажам вам надвор'е ў Аймяконе,
самым халодным населеным пункце на планеце.
Будзем спадзявацца, што ў вас сёння надвор'е лепей!
""",
'bg':u"""
Не успяхме да открием вашето местоположение
така че ви доведохме в Оймякон,
едно от най-студените постоянно обитавани места на планетата.
""",
'bs': u"""
Nismo mogli pronaći vašu lokaciju,
tako da smo te doveli do Oymyakon,
jedan od najhladnijih stalno naseljena mjesta na planeti.
Nadamo se da ćete imati bolje vreme!
""",
'ca': u"""
Hem estat incapaços de trobar la seva ubicació,
per això l'hem portat fins Oymyakon,
un dels llocs més freds i permanentment deshabitats del planeta.
""",
'cs': u"""
Nepodařilo se nám najít vaši polohu,
takže jsme vás přivedl do Ojmjakonu.
Je to jedno z nejchladnějších trvale obydlených míst na planetě.
Doufáme, že budete mít lepší počasí!
""",
'cy': u"""
Ni darganfyddwyd eich lleoliad,
felly rydym wedi dod â chi i Oymyakon,
un o'r llefydd oeraf ar y blaned ble mae pobl yn dal i fyw!
""",
'de': u"""
Wir konnten Ihren Standort nicht finden,
also haben wir Sie nach Oimjakon gebracht,
einer der kältesten dauerhaft bewohnten Orte auf dem Planeten.
Wir hoffen, dass Sie besseres Wetter haben!
""",
'el': u"""
Δεν μπορέσαμε να βρούμε την τοποθεσία σου,
για αυτό διαλέξαμε το Οϊμιάκον για εσένα,
μία από τις πιο κρύες μόνιμα κατοικημένες περιοχές στον πλανήτη.
Ελπίζουμε να έχεις καλύτερο καιρό!
""",
'es': u"""
No hemos logrado encontrar tu ubicación,
asi que hemos decidido enseñarte el tiempo en Oymyakon,
uno de los sitios más fríos y permanentemente deshabitados del planeta.
""",
'eu': u"""
Ezin izan dugu zure kokapena aurkitu,
beraz eguraldia Oymyakonen erakustea erabaki dugu,
munduko lekurik hotz eta hutseneraiko bat
""",
'fa': u"""
ما نتونستیم مکان شما رو پیدا کنیم. به همین خاطر شما رو به اویمیاکن بردیم
، یکی از سردترین مکان های روی زمین که اصلا قابل سکونت نیست!
""",
'fi': u"""
Emme löytänyt sijaintiasi, joten toimme sinut Oimjakoniin,
yhteen maailman kylmimmistä pysyvästi asutetuista paikoista.
Toivottavasti sinulla on parempi sää!
""",
'fr': u"""
Nous n'avons pas pu déterminer votre position,
Nous vous avons donc amenés à Oïmiakon,
l'un des endroits les plus froids habités en permanence sur la planète.
Nous espérons qu'il fait meilleur chez vous !
""",
'ga': u"""
rabhamar ábalta do cheantar a aimsiú
mar sin thugamar go dtí Oymyakon,
ceann do na ceantair bhuanáitrithe is fuaire ar domhan.
""",
'hi': u"""
हम आपक जन असमर ,
इसलि हम आपक ओयमय पर आए ,
रह सबस एक |
""",
'hu': u"""
Nem sikerült megtalálni a pozíciódat,
így elhoztunk Ojmjakonba;
az egyik leghidegebb állandóan lakott településre a bolygón.
""",
'hy': u"""
Ձեր գտնվելու վայրը չհաջողվեց որոշել,
այդ պատճառով մենք ձեզ կցուցադրենք եղանակը Օյմյակոնում.
երկրագնդի ամենասառը բնակավայրում։
Հույս ունենք որ ձեր եղանակը այսօր ավելի լավն է։
""",
'ia': u"""
Nos non trovate su location,
assi nos su apporte a Oymyakon,
un del plus frigide locos habita super le planeta!
""",
'id': u"""
Kami tidak dapat menemukan lokasi anda,
jadi kami membawa anda ke Oymyakon,
salah satu tempat terdingin yang selalu dihuni di planet ini!
""",
'is': u"""
Við finnum ekki staðsetninguna þína og vísum þér þar með á Ojmjakon,
ein af köldustu byggðum jarðar.
Vonandi er betra veður hjá þér.
""",
'it': u"""
Non siamo riusciti a trovare la sua posizione
quindi la abbiamo portato a Oymyakon,
uno dei luoghi abitualmente abitati più freddi del pianeta.
Ci auguriamo che le condizioni dove lei si trova siano migliori!
""",
'ja': u"""
指定された場所が見つかりませんでした
代わりにオイミャコンの天気予報を表示しています
オイミャコンは地球上で最も寒い居住地の一つです
""",
'ko': u"""
지정된 장소를 찾을 없습니다,
대신 오이먀콘의 일기 예보를 표시합니다,
오이먀콘은 지구상에서 가장 추운 곳에 위치한 마을입니다!
""",
'lv': u"""
Mēs nevarējām atrast jūsu atrašanās vietu tādēļ nogādājām jūs Oimjakonā,
vienā no aukstākajām apdzīvotajām vietām uz planētas.
""",
'mk': u"""
Неможевме да ја пронајдеме вашата локација,
затоа ве однесовме во Ојмајкон,
еден од најладните трајно населени места на планетата.
""",
'nb': u"""
Vi kunne ikke finne din lokasjon,
her får du Ojmjakon, et av de kaldeste bebodde stedene planeten.
Vi håper været er bedre hos deg!
""",
'nl': u"""
Wij konden uw locatie niet vaststellen
dus hebben we u naar Ojmjakon gebracht,
één van de koudste permanent bewoonde gebieden op deze planeet.
""",
'fy': u"""
Wy koenen jo lokaasje net fêststelle
dus wy ha jo nei Ojmjakon brocht,
ien fan de kâldste permanent bewenbere plakken op ierde.
""",
'oc': u"""
Avèm pas pogut determinar vòstra posicion,
Vos avèm doncas menat a Oïmiakon,
un dels endreches mai freds abitat permanéncia del monde.
Esperam que fa melhor en çò vòstre !
""",
'pt': u"""
Não conseguimos encontrar a sua localização,
então decidimos te mostrar o tempo em Oymyakon,
um dos lugares mais frios e permanentemente desabitados do planeta.
""",
'pt-br': u"""
Não conseguimos encontrar a sua localização,
então decidimos te mostrar o tempo em Oymyakon,
um dos lugares mais frios e permanentemente desabitados do planeta.
""",
'pl': u"""
Nie udało nam się znaleźć podanej przez Ciebie lokalizacji,
więc zabraliśmy Cię do Ojmiakonu,
jednego z najzimniejszych, stale zamieszkanych miejsc na Ziemi.
Mamy nadzieję, że u Ciebie jest cieplej!
""",
'ro': u"""
Nu v-am putut identifica localitatea, prin urmare arătăm vremea din Oimiakon,
una dintre cele mai reci localități permanent locuite de pe planetă.
Sperăm aveți vreme mai bună!
""",
'ru': u"""
Ваше местоположение определить не удалось,
поэтому мы покажем вам погоду в Оймяконе,
самом холодном населённом пункте на планете.
Будем надеяться, что у вас сегодня погода лучше!
""",
'sk': u"""
Nepodarilo sa nám nájsť vašu polohu,
takže sme vás priviedli do Ojmiakonu.
Je to jedno z najchladnejších trvale obývaných miest na planéte.
Dúfame, že budete mať lepšie počasie!
""",
'sr': u"""
Нисмо успели да пронађемо Вашу локацију,
па смо Вас довели у Ојмјакон,
једно од најхладнијих стално насељених места на планети.
Надамо се да је време код Вас боље него што је то случај овде!
""",
'sv': u"""
Vi lyckades inte hitta er plats vi har istället tagit er till Ojmjakon,
en av planetens kallaste platser med permanent bosättning.
Vi hoppas att vädret är bättre hos dig!
""",
'tr': u"""
Aradığınız konum bulunamadı. O yüzden sizi dünyadaki en soğuk sürekli
yerleşim yerlerinden biri olan Oymyakon'e getirdik.
Umarız sizin olduğunuz yerde havalar daha iyidir!
""",
'te': u"""
ి కననలకప
కన మన "ఓమాయకాన్కు" ి వచ,
ి అతయల వత ివసి ి రదలల ఒకటి.
""",
'th': u"""
เราไมสามารถหาตำแหนงของคณไดเราจงนำคณไปส Oymyakon หมานทหนาวทดในโลก!
""",
'uk': u"""
Ми не змогли визначити Ваше місцезнаходження,
тому покажемо Вам погоду в Оймяконі
найхолоднішому населеному пункті на планеті.
Будемо сподіватися, що у Вас сьогодні погода краще!
""",
'uz': u"""
Sizning joylashuvingizni aniqlay olmadik,
shuning uchun sizga sayyoramizning eng sovuq aholi punkti - Oymyakondagi ob-havo haqida ma'lumot beramiz.
Umid qilamizki, sizda bugungi ob-havo bundan yaxshiroq!
""",
'zh': u"""
我们无法找到您的位置,
当前显示奥伊米亚康(Oymyakon)这个星球上最冷的人类定居点
""",
'da': u"""
Vi kunne desværre ikke finde din lokation
vi har bragt dig til Oymyakon,
En af koldeste og helt ubolige lokationer planeten.
""",
'et': u"""
Me ei suutnud tuvastada teie asukohta
ning seetõttu paigutasime teid Oymyakoni,
mis on üks kõige külmemaid püsivalt asustatud paiku planeedil.
""",
'vi': u"""
Chúng tôi không tìm thấy địa điểm của bạn
vậy chúng tôi đưa bạn đến Oymyakon,
một trong những nơi lạnh nhất người sinh sống trên trái đất.
""",
'zh-tw': u"""
我們找不到您的位置
所以我們帶您到奧伊米亞康
這個星球上有人類定居最冷之處
""",
},
'UNKNOWN_LOCATION': {
'en': u'Unknown location',
'af': u'Onbekende ligging',
'ar': u'موقع غير معروف',
'be': u'Невядомае месцазнаходжанне',
'bg': u'Неизвестно местоположение',
'bs': u'Nepoznatoja lokacija',
'ca': u'Ubicació desconeguda',
'cs': u'Neznámá poloha',
'cy': u'Lleoliad anhysbys',
'de': u'Unbekannter Ort',
'da': u'Ukendt lokation',
'el': u'Άνγωστη τοποθεσία',
'es': u'Ubicación desconocida',
'et': u'Tundmatu asukoht',
'eu': u'Kokapen ezezaguna',
'fa': u'مکان نامعلوم',
'fi': u'Tuntematon sijainti',
'fr': u'Emplacement inconnu',
'ga': u'Ceantar anaithnid',
'hi': u'अज्ञात स्थान',
'hu': u'Ismeretlen lokáció',
'hy': u'Անհայտ գտնվելու վայր',
'id': u'Lokasi tidak diketahui',
'ia': u'Location incognite',
'is': u'Óþekkt staðsetning',
'it': u'Località sconosciuta',
'ja': u'未知の場所です',
'ko': u'알 수 없는 장소',
'kk': u'',
'lv': u'Nezināma atrašanās vieta',
'mk': u'Непозната локација',
'nb': u'Ukjent sted',
'nl': u'Onbekende locatie',
'oc': u'Emplaçament desconegut',
'fy': u'Ûnbekende lokaasje',
'pl': u'Nieznana lokalizacja',
'pt': u'Localização desconhecida',
'pt-br': u'Localização desconhecida',
'ro': u'Localitate necunoscută',
'ru': u'Неизвестное местоположение',
'sk': u'Neznáma poloha',
'sl': u'Neznano lokacijo',
'sr': u'Непозната локација',
'sv': u'Okänd plats',
'te': u'తెలియని ప్రదేశం',
'tr': u'Bilinmeyen konum',
'th': u'ไม่สามารถระบุตำแหน่งได้',
'uk': u'Невідоме місце',
'uz': u'Аникланмаган худуд',
'zh': u'未知地点',
'vi': u'Địa điểm không xác định',
'zh-tw': u'未知位置',
},
'LOCATION': {
'en': u'Location',
'af': u'Ligging',
'ar': u'الموقع',
'be': u'Месцазнаходжанне',
'bg': u'Местоположение',
'bs': u'Lokacija',
'ca': u'Ubicació',
'cs': u'Poloha',
'cy': u'Lleoliad',
'de': u'Ort',
'da': u'Lokation',
'el': u'Τοποθεσία',
'es': u'Ubicación',
'et': u'Asukoht',
'eu': u'Kokaena',
'fa': u'مکان',
'fi': u'Tuntematon sijainti',
'fr': u'Emplacement',
'ga': u'Ceantar',
'hi': u'स्थान',
'hu': u'Lokáció',
'hy': u'Դիրք',
'ia': u'Location',
'id': u'Lokasi',
'is': u'Staðsetning',
'it': u'Località',
'ja': u'位置情報',
'ko': u'위치',
'kk': u'',
'lv': u'Atrašanās vieta',
'mk': u'Локација',
'nb': u'Sted',
'nl': u'Locatie',
'oc': u'Emplaçament',
'fy': u'Lokaasje',
'pl': u'Lokalizacja',
'pt': u'Localização',
'pt-br': u'Localização',
'ro': u'Localitate',
'ru': u'Местоположение',
'sk': u'Poloha',
'sl': u'Lokacijo',
'sr': u'Локација',
'sv': u'Plats',
'zh': u'地点',
'te': u'స్థానము',
'tr': u'Konum',
'th': u'ตำแหน่ง',
'uk': u'Місцезнаходження',
'vi': u'Địa điểm',
'zh-tw': u'位置',
},
'CAPACITY_LIMIT_REACHED': {
'en': u"""
Sorry, we are running out of queries to the weather service at the moment.
Here is the weather report for the default city (just to show you what it looks like).
We will get new queries as soon as possible.
You can follow https://twitter.com/igor_chubin for the updates.
======================================================================================
""",
'af': u"""
Verskoning, ons oorskry tans die vermoë om navrae aan die weerdiens te rig.
Hier is die weerberig van 'n voorbeeld ligging (bloot om aan u te wys hoe dit lyk).
Ons sal weereens nuwe navrae kan hanteer so gou as moontlik.
U kan vir https://twitter.com/igor_chubin volg vir opdaterings.
======================================================================================
""",
'ar': u"""
نأسف, نفذت منا طلبات إستعلام خدمة الطقس في هذه اللحظة.
هذا التقرير الجوي للمدينة الإفتراضية (فقط لنريك, الشكل الذي تبدو عليه).
سوف نحصل علي طلبات إستعلام جديدة في أقرب وقت ممكن.
يمكنك متابعة https://twitter.com/igor_chubin من أجل الحصول علي أخر المستجدات.
======================================================================================
""",
'be': u"""
Прабачце, мы выйшлі за ліміты колькасці запытаў да службы надвор'я ў дадзены момант.
Вось прагноз надвор'я для горада па змаўчанні (толькі, каб паказаць вам, як гэта выглядае).
Мы вернемся як мага хутчэй.
Вы можаце сачыць на https://twitter.com/igor_chubin за абнаўленнямі.
======================================================================================
""",
'bg': u"""
Съжаляваме, количеството заявки към услугата за предсказване на време е почти изчерпано.
Ето доклад за града по подразбиране (просто да видите как изглежда).
Ще осогурим допълнителни заявки максимално бързо.
Може да последвате https://twitter.com/igor_chubin за обновления.
""",
'bs': u"""
Žao mi je, mi ponestaje upita i vremenska prognoza u ovom trenutku.
Ovdje je izvještaj o vremenu za default grada (samo da vam pokažem kako to izgleda).
Mi ćemo dobiti nove upite u najkraćem mogućem roku.
Možete pratiti https://twitter.com/igor_chubin za ažuriranja.
======================================================================================
""",
'ca': u"""
Disculpa'ns, ens hem quedat momentàniament sense consultes al servei meteorològic.
Aquí t'oferim l'informe del temps a la ciutat per defecte (només per mostrar quin aspecte ).
Obtindrem noves consultes tan aviat com ens sigui possible.
Pots seguir https://twitter.com/igor_chubin per noves actualitzacions.
======================================================================================
""",
'de': u"""
Entschuldigung, wir können momentan den Wetterdienst nicht erreichen.
Dafür zeigen wir Ihnen das Wetter an einem Beispielort, damit Sie sehen, wie die Seite das Wetter anzeigt.
Wir werden versuchen das Problem so schnell wie möglich zu beheben.
Folgen Sie https://twitter.com/igor_chubin für Updates.
======================================================================================
""",
'cy': u"""
Rydym yn brin o ymholiadau i'r gwasanaeth tywydd ar hyn o bryd.
Felly dyma'r adroddiad tywydd ar gyfer y ddinas ragosod (er mwyn arddangos sut mae'n edrych).
Byddwn gyda ymholiadau newydd yn fuan.
Gellir dilyn https://twitter.com/igor_chubin i gael newyddion pellach.
======================================================================================
""",
'es': u"""
Lo siento, hemos alcanzado el límite de peticiones al servicio de previsión del tiempo en este momento.
A continuación, la previsión del tiempo para una ciudad estándar (solo para que puedas ver el formato del reporte).
Muy pronto volveremos a tener acceso a las peticiones.
Puedes seguir https://twitter.com/igor_chubin para estar al tanto de la situación.
======================================================================================
""",
'fa': u"""
متأسفانه در حال حاضر ظرفیت ما برای درخواست به سرویس هواشناسی به اتمام رسیده.
اینجا می تونید گزارش هواشناسی برای شهر پیش فرض رو ببینید (فقط برای اینه که بهتون نشون بدیم چه شکلی هست)
ما تلاش میکنیم در اسرع وقت ظرفیت جدید به دست بیاریم.
برای دنبال کردن اخبار جدید میتونید https://twitter.com/igor_chubin رو فالو کنید.
======================================================================================
""",
'fr': u"""
Désolé, nous avons épuisé les requêtes vers le service météo.
Voici un bulletin météo de l'emplacement par défaut (pour vous donner un aperçu).
Nous serons très bientôt en mesure de faire de nouvelles requêtes.
Vous pouvez suivre https://twitter.com/igor_chubin pour rester informé.
======================================================================================
""",
'ga': u"""
brón orainn, níl mórán iarratas le fail chuig seirbhís na haimsire faoi láthair.
Seo duit réamhaisnéis na haimsire don chathair réamhshocraithe (chun é a thaispeaint duit).
Gheobhaimid iarratais nua chomh luath agus is feidir.
Lean orainn ar https://twitter.com/igor_chubin don eolas is déanaí.
======================================================================================
""",
'hi': u"""
षम कर, इस समय हम सम पर नह कर रह
यह िि शहर ि सम नक (आपक यह ि ि ि यह िखत )
हम जल जल सम पर करन नई ि कर
आप अपड ि https://twitter.com/igor_chubin अनसरण कर सकत
======================================================================================
""",
'hu': u"""
Sajnáljuk, kifogytunk az időjárási szolgáltatásra fordított erőforrásokból.
Itt van az alapértelmezett város időjárási jelentése (hogy lásd, hogyan néz ki).
A lehető leghamarabb új erőforrásokat fogunk kapni.
A frissítésekért tekintsd meg a https://twitter.com/igor_chubin oldalt.
======================================================================================
""",
'hy': u"""
Կներեք, այս պահին մենք գերազանցել ենք եղանակային տեսության կայանին հարցումների քանակը.
Կարող եք տեսնել տիպային եղանակը զեկուցում հիմնական քաղաքի համար (Ուղղակի որպես նմուշ):
Մենք մշտապես աշխատում ենք հարցումների քանակը բարելավելու ուղղությամբ:
Կարող եք հետևել մեզ https://twitter.com/igor_chubin թարմացումների համար.
======================================================================================
""",
'ia': u"""
Pardono, nos ha exhaurite inquisitione del servicio tempore alora.
Ecce es le reporto tempore por qualque citate (demonstrar le reporte).
Nos recipera inquisitione nove tosto.
Tu pote abonar a https://twitter.com/igor_chubin por nove information.
======================================================================================
""",
'id': u"""
Maaf, kami kehabian permintaan ke layanan cuaca saat ini.
Ini adalah laporan cuaca dari kota standar (hanya untuk menunjukkan kepada anda bagaimana tampilannya).
Kami akan mencoba permintaan baru lagi sesegera mungkin.
Anda dapat mengikuti https://twitter.com/igor_chubin untuk informasi terbaru.
======================================================================================
""",
'it': u"""
Scusate, attualmente stiamo esaurendo le risorse a disposizione del servizio meteo.
Qui trovate il bollettino del tempo per la città di default (solo per mostrarvi come si presenta).
Potremo elaborare nuove richieste appena possibile.
Potete seguire https://twitter.com/igor_chubin per gli aggiornamenti.
======================================================================================
""",
'ko': u"""
죄송합니다. 현재 날씨 정보를 가져오는 쿼리 요청이 한도에 도달했습니다.
대신 기본으로 설정된 도시에 대한 일기 예보를 보여드리겠습니다. (이는 단지 어떻게 보이는지 알려주기 위함입니다).
쿼리 요청이 가능한 빨리 이루어질 있도록 하겠습니다.
업데이트 소식을 원하신다면 https://twitter.com/igor_chubin 팔로우 해주세요.
======================================================================================
""",
'lv': u"""
Atvainojiet, uz doto brīdi mēs esam mazliet noslogoti.
Šeit ir laika ziņas noklusējuma pilsētai (lai parādītu jums, izskatās izveidotais ziņojums).
Mēs atsāksim darbu cik ātri vien varēsim.
Jūs varat sekot https://twitter.com/igor_chubin lai redzētu visus jaunumus.
======================================================================================
""",
'mk': u"""
Извинете, ни снемуваат барања за до сервисот кој ни нуди временска прогноза во моментот.
Еве една временска прогноза за град (за да видите како изгледа).
Ќе добиеме нови барања најбрзо што можеме.
Следете го https://twitter.com/igor_chubin за известувања
======================================================================================
""",
'nb': u"""
Beklager, vi kan ikke værtjenesten for øyeblikket.
Her er værmeldingen for standardbyen du får se hvordan tjenesten ser ut.
Vi vil forsøke å fikse problemet snart som mulig.
Du kan følge https://twitter.com/igor_chubin for oppdateringer.
======================================================================================
""",
'zh': u"""
抱歉当前天气服务不可用
以下显示默认城市只对您可见
我们将会尽快获取新数据
您可以通过 https://twitter.com/igor_chubin 获取最新动态
======================================================================================
""",
'nl': u"""
Excuse, wij kunnen u op dit moment dit weerbericht niet laten zien.
Hier is het weerbericht voor de standaard stad(zodat u weet hoe het er uitziet)
Wij lossen dit probleem zo snel mogelijk op.
voor updates kunt u ons op https://twitter.com/igor_chubin volgen.
======================================================================================
""",
'oc': u"""
O planhèm, avèm pas mai de requèstas cap al servici metèo.
Vaquí las prevision metèo de l'emplaçament per defaut (per vos donar un apercebut).
Poirem lèu ne far de novèlas.
Podètz seguir https://twitter.com/igor_chubin per demorar informat.
======================================================================================
""",
'fy': u"""
Excuses, wy kinne op dit moment 't waarberjocht net sjin litte.
Hjir is 't waarberjocht foar de standaard stêd.
Wy losse dit probleem sa gau mooglik op.
Foar updates kinne jo ús op https://twitter.com/igor_chubin folgje.
======================================================================================
""",
'pl': u"""
Bardzo nam przykro, ale chwilowo wykorzystaliśmy limit zapytań do serwisu pogodowego.
To, co widzisz jest przykładowym raportem pogodowym dla domyślnego miasta.
Postaramy się przywrócić funkcjonalność tak szybko, jak to tylko możliwe.
Możesz śledzić https://twitter.com/igor_chubin na Twitterze, aby być na bieżąco.
======================================================================================
""",
'pt': u"""
Desculpe-nos, estamos atingindo o limite de consultas ao serviço de previsão do tempo neste momento.
Veja a seguir a previsão do tempo para uma cidade padrão (apenas para você ver que aspecto o relatório tem).
Em breve voltaremos a ter acesso às consultas.
Você pode seguir https://twitter.com/igor_chubin para acompanhar a situação.
======================================================================================
""",
'pt-br': u"""
Desculpe-nos, atingimos o limite de consultas ao serviço de previsão do tempo neste momento.
Veja a seguir a previsão do tempo para uma cidade padrão (apenas para você ver que aspecto o relatório tem).
Em breve voltaremos a ter acesso às consultas.
Você pode seguir https://twitter.com/igor_chubin para acompanhar a situação.
======================================================================================
""",
'ro': u"""
Ne pare rău, momentan am epuizat cererile alocate de către serviciul de prognoză meteo.
arătăm prognoza meteo pentru localitatea implicită (ca exemplu, vedeți cum arată).
Vom obține alocarea de cereri noi cât de curând posibil.
Puteți urmări https://twitter.com/igor_chubin pentru actualizări.
======================================================================================
""",
'te': u"""
షమిి, రసిి వరణ వక రశనలన గడ.
ఇకకడ ి నగర వరణ ిి (వల ిచడిి, ఇది ఎల కనిిి).
యమ వరల రశనలన .
నవకరణల https://twitter.com/igor_chubin అనసరిచవచ.
======================================================================================
""",
'tr': u"""
Üzgünüz, an itibariyle hava durumu servisine yapabileceğimiz sorgu limitine ulaştık.
Varsayılan şehir için hava durumu bilgisini görüyorsunuz (neye benzediğini gösterebilmek için).
Mümkün olan en kısa sürede servise yeniden sorgu yapmaya başlayacağız.
Gelişmeler için https://twitter.com/igor_chubin adresini takip edebilirsiniz.
======================================================================================
""",
'th': u"""
ขออภ การเรยกคนหาระบบสภาพอากาศของเราหมดชวคราว
เราจงแสดงขอมลของเมองตวอยาง (เพอทจะแสดงใหณเหนวาหนาตาเปนยงไง)
เราจะเรยกคนหาใหมโดยเรวท
ณสามารถตดตามการอพเดทได https://twitter.com/igor_chubin
======================================================================================
""",
'da': u"""
Beklager, men vi er ved at løbe tør for forespørgsler til vejr-servicen lige nu.
Her er vejr rapporten for standard byen (bare du ved hvordan det kan se ud).
Vi får nye forespørsler hurtigst muligt.
Du kan følge https://twitter.com/igor_chubin for at opdateringer.
======================================================================================
""",
'et': u"""
Vabandage, kuid hetkel on päringud ilmateenusele piiratud.
Selle asemel kuvame hetkel näidislinna ilmaprognoosi (näitamaks, kuidas see välja näeb).
Üritame probleemi lahendada niipea kui võimalik.
Jälgige https://twitter.com/igor_chubin värskenduste jaoks.
======================================================================================
""",
'uk': u"""
Вибачте, ми перевищили максимальну кількість запитів до сервісу погоди.
Ось прогноз погоди у нашому місті (просто показати Вам як це виглядає).
Ми відновимо роботу як тільки зможемо.
Ви можете підписатися на https://twitter.com/igor_chubin для отримання новин.
======================================================================================
""",
'vi': u"""
Xin lỗi, hiện tại chúng tôi đã hết lượt yêu cầu thông tin thời tiết.
Đây dự báo thời tiết cho thành phố mặc định (chỉ để cho bạn thấy trông như thế nào).
Chung tôi sẽ thêm lượt truy vấn sớm nhất thể
Bạn thể theo dõi https://twitter.com/igor_chubin để cập nhật thông tin mới nhất.
======================================================================================
""",
'zh-tw': u"""
抱歉目前天氣服務的查詢請求太多了
這裡是預設城市的天氣報告只是為您展示它的外觀
我們將盡快取得新的資料
您可以追蹤 https://twitter.com/igor_chubin 以取得更新
======================================================================================
""",
},
# Historical messages:
# 'Check new Feature: \033[92mwttr.in/Moon\033[0m or \033[92mwttr.in/Moon@2016-Mar-23\033[0m to see the phase of the Moon'
# 'New feature: \033[92mwttr.in/Rome?lang=it\033[0m or \033[92mcurl -H "Accept-Language: it" wttr.in/Rome\033[0m for the localized version. Your lang instead of "it"'
'NEW_FEATURE': {
'en': u'New feature: multilingual location names \033[92mwttr.in/станция+Восток\033[0m (in UTF-8) and location search \033[92mwttr.in/~Kilimanjaro\033[0m (just add ~ before)',
'ar': u'ميزة جديدة : أسماء الأماكن بلغات متعددة \033[92mwttr.in/станция+Восток\033[0m (in UTF-8) والبحث عن الأماكن \033[92mwttr.in/~Kilimanjaro\033[0m (فقط أضف ~ قبل)',
'af': u'Nuwe eienskap: veeltalige name vir liggings \033[92mwttr.in/станция+Восток\033[0m (in UTF-8) en ligging soek \033[92mwttr.in/~Kilimanjaro\033[0m (plaas net ~ vooraan)',
'be': u'Новыя магчымасці: назвы месц на любой мове \033[92mwttr.in/станция+Восток\033[0m (в UTF-8) i пошук месц \033[92mwttr.in/~Kilimanjaro\033[0m (трэба дадаць ~ ў пачатак)',
'bg': u'Нова функционалност: многоезични имена на места\033[92mwttr.in/станция+Восток\033[0m (в UTF-8) и в търсенето \033[92mwttr.in/~Kilimanjaro\033[0m (добавете ~ преди)',
'bs': u'XXXXXXXXXXXXXXXXXXXX: XXXXXXXXXXXXXXXXXXXXXXXXXXXXX\033[92mwttr.in/станция+Восток\033[0m (XX UTF-8) XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
'ca': u'Noves funcionalitats: noms d\'ubicació multilingües \033[92mwttr.in/станция+Восток\033[0m (en UTF-8) i la ubicació de recerca \033[92mwttr.in/~Kilimanjaro\033[0m (només cal afegir ~ abans)',
'es': u'Nuevas funcionalidades: los nombres de las ubicaciones en varios idiomas \033[92mwttr.in/станция+Восток\033[0m (em UTF-8) y la búsqueda por ubicaciones \033[92mwttr.in/~Kilimanjaro\033[0m (tan solo inserte ~ en frente)',
'fa': u'قابلیت جدید: پشتیبانی از نام چند زبانه مکانها \033[92mwttr.in/станция+Восток\033[0m (در فرمت UTF-8) و جسجتوی مکان ها \033[92mwttr.in/~Kilimanjaro\033[0m (فقط قبل از اون ~ اضافه کنید)',
'fr': u'Nouvelles fonctionnalités: noms d\'emplacements multilingues \033[92mwttr.in/станция+Восток\033[0m (en UTF-8) et recherche d\'emplacement \033[92mwttr.in/~Kilimanjaro\033[0m (ajouter ~ devant)',
'mk': u'Нова функција: повеќе јазично локациски имиња \033[92mwttr.in/станция+Восток\033[0m (во UTF-8) и локациско пребарување \033[92mwttr.in/~Kilimanjaro\033[0m (just add ~ before)',
'nb': u'Ny funksjon: flerspråklige stedsnavn \033[92mwttr.in/станция+Восток\033[0m (i UTF-8) og lokasjonssøk \033[92mwttr.in/~Kilimanjaro\033[0m (bare legg til ~ foran)',
'nl': u'Nieuwe functie: tweetalige locatie namen \033[92mwttr.in/станция+Восток\033[0m (in UTF-8) en locatie zoeken \033[92mwttr.in/~Kilimanjaro\033[0m (zet er gewoon een ~ voor)',
'fy': u'Nije funksje: twatalige lokaasje nammen \033[92mwttr.in/станция+Восток\033[0m (in UTF-8) en lokaasje sykjen \033[92mwttr.in/~Kilimanjaro\033[0m (set er gewoan in ~ foar)',
'cy': u'Nodwedd newydd: enwau lleoliadau amlieithog \033[92mwttr.in/станция+Восток\033[0m (yn UTF-8) a chwilio am leoliad \033[92mwttr.in/~Kilimanjaro\033[0m (ychwanegwch ~ yn gyntaf)',
'de': u'Neue Funktion: mehrsprachige Ortsnamen \033[92mwttr.in/станция+Восток\033[0m (in UTF-8) und Ortssuche \033[92mwttr.in/~Kilimanjaro\033[0m (fügen Sie ein ~ vor dem Ort ein)',
'hi': u'नई सुविधा: बहुभाषी स्थान के नाम \033[92mwttr.in/станция+Восток\033[0m (in UTF-8) और स्थान खोज \033[92mwttr.in/~Kilimanjaro\033[0m (बस ~ आगे लगाये)',
'hu': u'Új funkcinalitás: többnyelvű helynevek \033[92mwttr.in/станция+Восток\033[0m (UTF-8-ban) és pozíció keresés \033[92mwttr.in/~Kilimanjaro\033[0m (csak adj egy ~ jelet elé)',
'hy': u'Փորձարկեք: տեղամասերի անունները կամայական լեզվով \033[92mwttr.in/Դիլիջան\033[0m (в UTF-8) և տեղանքի որոնում \033[92mwttr.in/~Kilimanjaro\033[0m (հարկավոր է ~ ավելացնել դիմացից)',
'ia': u'Nove functione: location nomine multilingue \033[92mwttr.in/станция+Восток\033[0m (a UTF-8) e recerca de location\033[92mwttr.in/~Kilimanjaro\033[0m (solo adde ~ ante)',
'id': u'Fitur baru: nama lokasi dalam multibahasa \033[92mwttr.in/станция+Восток\033[0m (in UTF-8) dan pencarian lokasi \033[92mwttr.in/~Kilimanjaro\033[0m (hanya tambah tanda ~ sebelumnya)',
'it': u'Nuove funzionalità: nomi delle località multilingue \033[92mwttr.in/станция+Восток\033[0m (in UTF-8) e ricerca della località \033[92mwttr.in/~Kilimanjaro\033[0m (basta premettere ~)',
'ko': u'새로운 기능: 다국어로 대응된 위치 \033[92mwttr.in/서울\033[0m (UTF-8에서) 장소 검색 \033[92mwttr.in/~Kilimanjaro\033[0m (앞에 ~를 붙이세요)',
'kk': u'',
'lv': u'Jaunums: Daudzvalodu atrašanās vietu nosaukumi \033[92mwttr.in/станция+Восток\033[0m (in UTF-8) un dabas objektu meklēšana \033[92mwttr.in/~Kilimanjaro\033[0m (tikai priekšā pievieno ~)',
'mk': u'Нова функција: повеќе јазично локациски имиња \033[92mwttr.in/станция+Восток\033[0m (во UTF-8) и локациско пребарување \033[92mwttr.in/~Kilimanjaro\033[0m (just add ~ before)',
'oc': u'Novèla foncionalitat : nom de lòc multilenga \033[92mwttr.in/станция+Восток\033[0m (en UTF-8) e recèrca de lòc \033[92mwttr.in/~Kilimanjaro\033[0m (solament ajustatz ~ abans)',
'pl': u'Nowa funkcjonalność: wielojęzyczne nazwy lokalizacji \033[92mwttr.in/станция+Восток\033[0m (w UTF-8) i szukanie lokalizacji \033[92mwttr.in/~Kilimanjaro\033[0m (poprzedź zapytanie ~ - znakiem tyldy)',
'pt': u'Nova funcionalidade: nomes de localidades em várias línguas \033[92mwttr.in/станция+Восток\033[0m (em UTF-8) e procura por localidades \033[92mwttr.in/~Kilimanjaro\033[0m (é só colocar ~ antes)',
'pt-br': u'Nova funcionalidade: nomes de localidades em várias línguas \033[92mwttr.in/станция+Восток\033[0m (em UTF-8) e procura por localidades \033[92mwttr.in/~Kilimanjaro\033[0m (é só colocar ~ antes)',
'ro': u'Funcționalitate nouă: nume de localități multilingve \033[92mwttr.in/станция+Восток\033[0m (in UTF-8) și căutare de localități \033[92mwttr.in/~Kilimanjaro\033[0m (adăuați ~ în față)',
'ru': u'Попробуйте: названия мест на любом языке \033[92mwttr.in/станция+Восток\033[0m (в UTF-8) и поиск мест \033[92mwttr.in/~Kilimanjaro\033[0m (нужно добавить ~ спереди)',
'zh': u'新功能:多语言地点名称 \033[92mwttr.in/станция+Восток\033[0m (in UTF-8) 及地点搜索\033[92mwttr.in/~Kilimanjaro\033[0m (只需在名称前加~',
'tr': u'Yeni özellik: çok dilli konum isimleri \033[92mwttr.in/станция+Восток\033[0m (UTF-8 ile) ve konum arama \033[92mwttr.in/~Kilimanjaro\033[0m (sadece önüne ~ ekleyin)',
'te': u'క్రొత్త లక్షణం: బహుభాషా స్థాన పేర్లు \ 033 [92mwttr.in/stancelя+Vostок\033 [0 U (UTF-8 లో) మరియు స్థానం శోధన \ 033 [92mwttr.in/~kilimanjaro\033 [0m (కేవలం ~ ముందుకి జోడించండి)',
'th': u'ฟีเจอร์ใหม่: แสดงที่ตั้งได้หลายภาษา \033[92mwttr.in/станция+Восток\033[0m (ใน UTF-8) และการค้นหาที่ตั้ง \033[92mwttr.in/~Kilimanjaro\033[0m (เพียงเพิ่ม ~ ข้างหน้า)',
'da': u'Ny funktion: flersprogede lokationsnavne \033[92mwttr.in/станция+Восток\033[0m (som UTF-8) og lokations søgning \033[92mwttr.in/~Kilimanjaro\033[0m (bare tilføj ~ inden)',
'et': u'Uus funktsioon: mitmekeelsed asukohanimed \033[92mwttr.in/станция+Восток\033[0m (UTF-8 vormingus) ja asukoha otsing \033[92mwttr.in/~Kilimanjaro\033[0m (lisa ~ enne)',
'uk': u'Спробуйте: назви місць будь-якою мовою \033[92mwttr.in/станція+Восток\033[0m (в UTF-8) та пошук місць \033[92mwttr.in/~Kilimanjaro\033[0m (потрібно додати ~ спочатку)',
'vi': u'Chức năng mới: tên địa điểm đa ngôn ngữ \033[92mwttr.in/станция+Восток\033[0m (dùng UTF-8) và tìm kiếm địa điểm \033[92mwttr.in/~Kilimanjaro\033[0m (chỉ cần thêm ~ phía trước)',
'zh-tw': u'新功能:多語言地點名稱 \033[92mwttr.in/станция+Восток\033[0m (使用 UTF-8 編碼)與位置搜尋 \033[92mwttr.in/~Kilimanjaro\033[0m (只要在地點前加 ~ 就可以了)',
},
'FOLLOW_ME': {
'en': u'Follow \033[46m\033[30m@igor_chubin\033[0m for wttr.in updates',
'ar': u'تابع \033[46m\033[30m@igor_chubin\033[0m من أجل wttr.in أخر مستجدات',
'af': u'Volg \033[46m\033[30m@igor_chubin\033[0m vir wttr.in opdaterings',
'be': u'Сачыце за \033[46m\033[30m@igor_chubin\033[0m за навінамі wttr.in',
'bg': u'Последвай \033[46m\033[30m@igor_chubin\033[0m за обновления свързани с wttr.in',
'bs': u'XXXXXX \033[46m\033[30m@igor_chubin\033[0m XXXXXXXXXXXXXXXXXXX',
'ca': u'Segueix \033[46m\033[30m@igor_chubin\033[0m per actualitzacions de wttr.in',
'es': u'Seguir \033[46m\033[30m@igor_chubin\033[0m para recibir las novedades de wttr.in',
'eu': u'\033[46m\033[30m@igor_chubin\033[0m jarraitu wttr.in berriak jasotzeko',
'cy': u'Dilyner \033[46m\033[30m@igor_Chubin\033[0m am diweddariadau wttr.in',
'fa': u'برای دنبال کردن خبرهای wttr.in شناسه \033[46m\033[30m@igor_chubin\033[0m رو فالو کنید.',
'fr': u'Suivez \033[46m\033[30m@igor_Chubin\033[0m pour rester informé sur wttr.in',
'de': u'Folgen Sie \033[46m\033[30mhttps://twitter.com/igor_chubin\033[0m für wttr.in Updates',
'ga': u'Lean \033[46m\033[30m@igor_chubin\033[0m don wttr.in eolas is deanaí',
'hi': u'अपडेट के लिए फॉलो करें \033[46m\033[30m@igor_chubin\033[0m',
'hu': u'Kövesd \033[46m\033[30m@igor_chubin\033[0m-t további wttr.in információkért',
'hy': u'Նոր ֆիչռների համար հետևեք՝ \033[46m\033[30m@igor_chubin\033[0m',
'ia': u'Seque \033[46m\033[30m@igor_chubin\033[0m por nove information de wttr.in',
'id': u'Ikuti \033[46m\033[30m@igor_chubin\033[0m untuk informasi wttr.in terbaru',
'it': u'Seguite \033[46m\033[30m@igor_chubin\033[0m per aggiornamenti a wttr.in',
'ko': u'wttr.in의 업데이트 소식을 원하신다면 \033[46m\033[30m@igor_chubin\033[0m 을 팔로우 해주세요',
'kk': u'',
'lv': u'Seko \033[46m\033[30m@igor_chubin\033[0m , lai uzzinātu wttr.in jaunumus',
'mk': u'Следете \033[46m\033[30m@igor_chubin\033[0m за wttr.in новости',
'nb': u'Følg \033[46m\033[30m@igor_chubin\033[0m for wttr.in oppdateringer',
'nl': u'Volg \033[46m\033[30m@igor_chubin\033[0m voor wttr.in updates',
'oc': u'Seguissètz \033[46m\033[30m@igor_Chubin\033[0m per demorar informat sus wttr.in',
'fy': u'Folgje \033[46m\033[30m@igor_chubin\033[0m foar wttr.in updates',
'pl': u'Śledź \033[46m\033[30m@igor_chubin\033[0m aby być na bieżąco z nowościami dotyczącymi wttr.in',
'pt': u'Seguir \033[46m\033[30m@igor_chubin\033[0m para as novidades de wttr.in',
'pt-br': u'Seguir \033[46m\033[30m@igor_chubin\033[0m para as novidades de wttr.in',
'ro': u'Urmăriți \033[46m\033[30m@igor_chubin\033[0m pentru actualizări despre wttr.in',
'ru': u'Все новые фичи публикуются здесь: \033[46m\033[30m@igor_chubin\033[0m',
'zh': u'关注 \033[46m\033[30m@igor_chubin\033[0m 获取 wttr.in 动态',
'te': u'అనుసరించండి \ 033 [46m \ 033 [30m @ igor_chubin \ 033 [wttr.in నవీకరణలను కోసం',
'tr': u'wttr.in ile ilgili gelişmeler için \033[46m\033[30m@igor_chubin\033[0m adresini takip edin',
'th': u'ติดตาม \033[46m\033[30m@igor_chubin\033[0m สำหรับการอัพเดท wttr.in',
'da': u'Følg \033[46m\033[30m@igor_chubin\033[0m for at få wttr.in opdateringer',
'et': u'Jälgi \033[46m\033[30m@igor_chubin\033[0m wttr.in uudiste tarbeks',
'uk': u'Нові можливості wttr.in публікуються тут: \033[46m\033[30m@igor_chubin\033[0m',
'vi': u'Theo dõi \033[46m\033[30m@igor_chubin\033[0m để cập nhật thông tin về wttr.in',
'zh-tw': u'追蹤 \033[46m\033[30m@igor_chubin\033[0m 以取得更多 wttr.in 的動態',
},
}
CAPTION = {
"af": u"Weer verslag vir:",
"ar": u"تقرير جوي",
"az": u"Hava proqnozu:",
"be": u"Прагноз надвор'я для:",
"bg": u"Прогноза за времето в:",
"bs": u"Vremenske prognoze za:",
"ca": u"Informe del temps per a:",
"cs": u"Předpověď počasí pro:",
"cy": u"Adroddiad tywydd ar gyfer:",
"da": u"Vejret i:",
"de": u"Wetterbericht für:",
"el": u"Πρόγνωση καιρού για:",
"en": u"weather report for:",
"eo": u"Veterprognozo por:",
"es": u"El pronóstico del tiempo en:",
"et": u"Ilmaprognoos:",
"eu": u"Eguraldiaren iragarpena:",
"fa": u"گزارش آب و هئا برای شما:",
"fi": u"Säätiedotus:",
"fr": u"Prévisions météo pour:",
"fy": u"Waarberjocht foar:",
"ga": u"Réamhaisnéis na haimsire do:",
"he": u":ריוואה גזמ תיזחת",
"hi": u"मौसम की जानकारी",
"hr": u"Vremenska prognoza za:",
"hu": u"Időjárás előrejelzés:",
"hy": u"Եղանակի տեսություն:",
"ia": u"Reporto tempore por:",
"id": u"Prakiraan cuaca:",
"it": u"Previsioni meteo:",
"is": u"Veðurskýrsla fyrir:",
"ja": u"天気予報:",
"jv": u"Weather forecast for:",
"ka": u"ამინდის პროგნოზი:",
"kk": u"Ауа райы:",
"ko": u"일기 예보:",
"ky": u"Аба ырайы:",
"lt": u"Orų prognozė:",
"lv": u"Laika ziņas:",
"mk": u"Прогноза за времето во:",
"ml": u"കാലാവസ്ഥ റിപ്പോർട്ട്:",
"nb": u"Værmelding for:",
"nl": u"Weerbericht voor:",
"nn": u"Vêrmelding for:",
"oc": u"Previsions metèo per :",
"pl": u"Pogoda w:",
"pt": u"Previsão do tempo para:",
"pt-br": u"Previsão do tempo para:",
"ro": u"Prognoza meteo pentru:",
"ru": u"Прогноз погоды:",
"sk": u"Predpoveď počasia pre:",
"sl": u"Vremenska napoved za",
"sr": u"Временска прогноза за:",
"sr-lat": u"Vremenska prognoza za:",
"sv": u"Väderleksprognos för:",
"sw": u"Ripoti ya hali ya hewa, jiji la:",
"te": u"వాతావరణ సమాచారము:",
"th": u"รายงานสภาพอากาศ:",
"tr": u"Hava beklentisi:",
"uk": u"Прогноз погоди для:",
"uz": u"Ob-havo bashorati:",
"vi": u"Báo cáo thời tiết:",
"zh": u"天气预报:",
"zu": u"Isimo sezulu:",
"zh-tw": u"天氣報告:",
}
def get_message(message_name, lang):
if lang == 'zh-cn':
lang = 'zh'
if message_name not in MESSAGE:
return ''
message_dict = MESSAGE[message_name]
return message_dict.get(lang, message_dict.get('en', ''))

View file

@ -1,73 +0,0 @@
# vim: fileencoding=utf-8
"""
Translation of v2
"""
# pylint: disable=line-too-long,bad-whitespace
V2_TRANSLATION = {
"en": ("Weather report for:", "Weather", "Timezone", "Now", "Dawn", "Sunrise", "Zenith", "Sunset", "Dusk"),
"af": ("Weer verslag vir:", "", "", "", "", "", "", "", ""),
"ar": ("تقرير جوي:", "حالة الجو", "المنطقة الزمنية", "الآن ", "الفجر", "شروق الشمس", "الذروة", "غروب الشمس", "الغسق"),
"az": ("Hava proqnozu:", "Hava", "Saat zonası", "İndi", "Şəfəq", "Günəş çıxdı", "Zenit", "Gün batımı", "Toran"),
"be": ("Прагноз надвор'я для:", "Надвор'е", "Часавая зона", "Цяпер", "Світанак", "Усход сонца", "Зеніт", "Захад сонца", "Змярканне"),
"bg": ("Прогноза за времето в:", "", "", "", "", "", "", "", ""),
"bs": ("Vremenske prognoze za:", "", "", "", "", "", "", "", ""),
"ca": ("Informe del temps per a:", "", "", "", "", "", "", "", ""),
"cs": ("Předpověď počasí pro:", "", "", "", "", "", "", "", ""),
"cy": ("Adroddiad tywydd ar gyfer:", "", "", "", "", "", "", "", ""),
"da": ("Vejret i:", "Vejret", "Tidszone", "Nu", "Daggry", "Solopgang", "Zenit", "Solnedgang", "Skumring"),
"de": ("Wetterbericht für:", "Wetter", "Zeitzone", "Jetzt", "Morgendämmerung", "Sonnenaufgang","Zenit", "Sonnenuntergang", "Abenddämmerung"),
"el": ("Πρόγνωση καιρού για:", "", "", "", "", "", "", "", ""),
"eo": ("Veterprognozo por:", "Vetero", "Horzono", "Nun", "Tagiĝo", "Sunleviĝo", "Zenito", "Sunsubiro", "Krepusko"),
"es": ("El tiempo en:", "Clima", "Zona horaria", "Ahora", "Alborada", "Amanecer", "Cenit", "Atardecer", "Anochecer"),
"et": ("Ilmaprognoos:", "Ilm", "Ajatsoon", "Hetkel", "Koit", "Päikesetõus", "Seniit", "Päikeseloojang", "Eha"),
"fa": ("اوه و بآ تیعضو شرازگ", "", "", "", "", "", "", "", ""),
"fi": ("Säätiedotus:", "", "", "", "", "", "", "", ""),
"fr": ("Prévisions météo pour :", "Météo", "Fuseau Horaire", "Heure", "Aube", "Lever du Soleil", "Zénith", "Coucher du Soleil", "Crépuscule"),
"fy": ("Waarberjocht foar:", "", "", "", "", "", "", "", ""),
"ga": ("Réamhaisnéis na haimsire do:", "", "", "", "", "", "", "", ""),
"he": (":ריוואה גזמ תיזחת", "", "", "", "", "", "", "", ""),
"hi": ("मौसम की जानकारी", "मौसम", "समय मण्डल", "अभी", "उदय", "सूर्योदय", "चरम बिन्दु", "सूर्यास्त", "संध्याकाल"),
"hr": ("Vremenska prognoza za:", "", "", "", "", "", "", "", ""),
"hu": ("Időjárás előrejelzés:", "Időjárás", "időzóna", "aktuális", "hajnal", "napkelte", "dél", "naplemente", "szürkület"),
"hy": ("Եղանակի տեսություն:", "", "", "", "", "", "", "", ""),
"ia": ("Reporto tempore pro:", "Tempore", "Fuso Horari", "Alora", "Alba", "Aurora", "Zenit", "Poner del sol", "Crepusculo"),
"id": ("Prakiraan cuaca:", "", "", "", "", "", "", "", ""),
"it": ("Previsioni meteo:", "Tempo", "Fuso orario", "Ora", "Alba", "Sorgere del Sole", "Zenit", "Tramonto", "Crepuscolo"),
"is": ("Veðurskýrsla fyrir:", "", "", "", "", "", "", "", ""),
"ja": ("天気予報:", "天気", "タイムゾーン", "", "夜明け", "日の出", "天頂", "日の入", "日暮れ"),
"jv": ("Weather forecast for:", "", "", "", "", "", "", "", ""),
"ka": ("ამინდის პროგნოზი:", "", "", "", "", "", "", "", ""),
"kk": ("Ауа райы:", "", "", "", "", "", "", "", ""),
"ko": ("일기 예보:", "날씨", "시간대", "현재", "새벽", "일출", "정오", "일몰", "황혼"),
"ky": ("Аба ырайы:", "", "", "", "", "", "", "", ""),
"lt": ("Orų prognozė:", "", "", "", "", "", "", "", ""),
"lv": ("Laika ziņas:", "", "", "", "", "", "", "", ""),
"mk": ("Прогноза за времето во:", "", "", "", "", "", "", "", ""),
"ml": ("കാലാവസ്ഥ റിപ്പോർട്ട്:", "", "", "", "", "", "", "", ""),
"nb": ("Værmelding for:", "", "", "", "", "", "", "", ""),
"nl": ("Weerbericht voor:", "Weer", "Tijdzone", "Nu", "Dageraad", "Zonsopkomst", "Zenit", "Zonsondergang", "Schemering"),
"nn": ("Vêrmelding for:", "", "", "", "", "", "", "", ""),
"oc": ("Previsions metèo per:" "Metèo", "Zòna orària", "Ara", "Auròra", "Alba", "Zenit", "A solelh colc", "Entrelutz"),
"pl": ("Pogoda w:", "", "", "", "", "", "", "", ""),
"pt": ("Previsão do tempo para:", "Tempo", "Fuso horário", "Agora", "Alvorada", "Nascer do sol", "Zénite", "Pôr do sol", "Crepúsculo"),
"pt-br": ("Previsão do tempo para:", "Tempo", "Fuso horário", "Agora", "Alvorada", "Nascer do sol", "Zénite", "Pôr do sol", "Crepúsculo"),
"ro": ("Prognoza meteo pentru:", "", "", "", "", "", "", "", ""),
"ru": ("Прогноз погоды:", "Погода", "Часовой пояс", "Сейчас", "Рассвет", "Восход", "Зенит", "Закат", "Сумерки"),
"sk": ("Predpoveď počasia pre:", "", "", "", "", "", "", "", ""),
"sl": ("Vremenska napoved za", "", "", "", "", "", "", "", ""),
"sr": ("Временска прогноза за:", "", "", "", "", "", "", "", ""),
"sr-lat": ("Vremenska prognoza za:", "", "", "", "", "", "", "", ""),
"sv": ("Väderleksprognos för:", "", "", "", "", "", "", "", ""),
"sw": ("Ripoti ya hali ya hewa, jiji la:", "", "", "", "", "", "", "", ""),
"te": ("వాతావరణ సమాచారము:", "", "", "", "", "", "", "", ""),
"th": ("รายงานสภาพอากาศ:", "", "", "", "", "", "", "", ""),
"tr": ("Hava beklentisi:", "Hava Durumu", "Zaman Dilimi", "Şimdi", "Şafak", "Gün Doğumu", "Doruk", "Gün Batımı", "Akşam"),
"uk": ("Прогноз погоди для:", "Погода", "Часовий пояс", "Зараз", "Світанок", "Схід сонця", "Зеніт", "Захід сонця", "Сутінки"),
"uz": ("Ob-havo bashorati:", "", "", "", "", "", "", "", ""),
"vi": ("Báo cáo thời tiết:", "", "", "", "", "", "", "", ""),
"zh": ("天气预报:", "天气", "时区", "当前", "黎明", "日出", "正午", "日落", "黄昏"),
"zh-tw": ("天氣預報:", "天氣", "時區", "目前", "黎明", "日出", "日正當中", "日落", "黃昏"),
"zu": ("Isimo sezulu:", "", "", "", "", "", "", "", ""),
}

View file

@ -1,382 +0,0 @@
#vim: fileencoding=utf-8
"""
One-line output mode.
Initial implementation of one-line output mode.
[ ] forecast
[ ] spark
[ ] several locations
[ ] location handling
[ ] more preconfigured format lines
[ ] add information about this mode to /:help
"""
import sys
import re
import datetime
import json
import requests
from astral import LocationInfo
from astral import moon
from astral.sun import sun
import pytz
from constants import WWO_CODE, WEATHER_SYMBOL, WIND_DIRECTION, WEATHER_SYMBOL_WIDTH_VTE
from weather_data import get_weather_data
from . import v2
from . import prometheus
PRECONFIGURED_FORMAT = {
'1': r'%c %t\n',
'2': r'%c 🌡️%t 🌬️%w\n',
'3': r'%l: %c %t\n',
'4': r'%l: %c 🌡️%t 🌬️%w\n',
}
MOON_PHASES = (
u"🌑", u"🌒", u"🌓", u"🌔", u"🌕", u"🌖", u"🌗", u"🌘"
)
def convert_to_fahrenheit(temp):
"Convert Celcius `temp` to Fahrenheit"
return (temp*9.0/5)+32
def render_temperature(data, query):
"""
temperature (t)
"""
if query.get('use_imperial', False):
temperature = u'%s°F' % data['temp_F']
else:
temperature = u'%s°C' % data['temp_C']
if temperature[0] != '-':
temperature = '+' + temperature
return temperature
def render_feel_like_temperature(data, query):
"""
feel like temperature (f)
"""
if query.get('use_imperial', False):
temperature = u'%s°F' % data['FeelsLikeF']
else:
temperature = u'%s°C' % data['FeelsLikeC']
if temperature[0] != '-':
temperature = '+' + temperature
return temperature
def render_condition(data, query):
"""Emoji encoded weather condition (c)
"""
weather_condition = WEATHER_SYMBOL[WWO_CODE[data['weatherCode']]]
spaces = " "*(WEATHER_SYMBOL_WIDTH_VTE.get(weather_condition) - 1)
return weather_condition + spaces
def render_condition_fullname(data, query):
"""
condition_fullname (C)
"""
found = None
for key, val in data.items():
if key.startswith('lang_'):
found = val
break
if not found:
found = data['weatherDesc']
try:
weather_condition = found[0]['value']
except KeyError:
weather_condition = ''
return weather_condition
def render_humidity(data, query):
"""
humidity (h)
"""
humidity = data.get('humidity', '')
if humidity:
humidity += '%'
return humidity
def render_precipitation(data, query):
"""
precipitation (p)
"""
answer = data.get('precipMM', '')
if answer:
answer += 'mm'
return answer
def render_precipitation_chance(data, query):
"""
precipitation chance (o)
"""
answer = data.get('chanceofrain', '')
if answer:
answer += '%'
return answer
def render_pressure(data, query):
"""
pressure (P)
"""
answer = data.get('pressure', '')
if answer:
answer += 'hPa'
return answer
def render_wind(data, query):
"""
wind (w)
"""
try:
degree = data["winddirDegree"]
except KeyError:
degree = ""
try:
degree = int(degree)
except ValueError:
degree = ""
if degree:
wind_direction = WIND_DIRECTION[int(((degree+22.5)%360)/45.0)]
else:
wind_direction = ""
if query.get('use_ms_for_wind', False):
unit = 'm/s'
wind = u'%s%.1f%s' % (wind_direction, float(data['windspeedKmph'])/36.0*10.0, unit)
elif query.get('use_imperial', False):
unit = 'mph'
wind = u'%s%s%s' % (wind_direction, data['windspeedMiles'], unit)
else:
unit = 'km/h'
wind = u'%s%s%s' % (wind_direction, data['windspeedKmph'], unit)
return wind
def render_location(data, query):
"""
location (l)
"""
return (data['override_location'] or data['location'])
def render_moonphase(_, query):
"""moonpahse(m)
A symbol describing the phase of the moon
"""
moon_phase = moon.phase(date=datetime.datetime.today())
moon_index = int(int(32.0*moon_phase/28+2)%32/4)
return MOON_PHASES[moon_index]
def render_moonday(_, query):
"""moonday(M)
An number describing the phase of the moon (days after the New Moon)
"""
moon_phase = moon.phase(date=datetime.datetime.today())
return str(int(moon_phase))
##################################
# this part should be rewritten
# this is just a temporary solution
def get_geodata(location):
text = requests.get("http://localhost:8004/%s" % location).text
return json.loads(text)
def render_dawn(data, query, local_time_of):
"""dawn (D)
Local time of dawn"""
return local_time_of("dawn")
def render_dusk(data, query, local_time_of):
"""dusk (d)
Local time of dusk"""
return local_time_of("dusk")
def render_sunrise(data, query, local_time_of):
"""sunrise (S)
Local time of sunrise"""
return local_time_of("sunrise")
def render_sunset(data, query, local_time_of):
"""sunset (s)
Local time of sunset"""
return local_time_of("sunset")
def render_zenith(data, query, local_time_of):
"""zenith (z)
Local time of zenith"""
return local_time_of("noon")
##################################
FORMAT_SYMBOL = {
'c': render_condition,
'C': render_condition_fullname,
'h': render_humidity,
't': render_temperature,
'f': render_feel_like_temperature,
'w': render_wind,
'l': render_location,
'm': render_moonphase,
'M': render_moonday,
'p': render_precipitation,
'o': render_precipitation_chance,
'P': render_pressure,
}
FORMAT_SYMBOL_ASTRO = {
'D': render_dawn,
'd': render_dusk,
'S': render_sunrise,
's': render_sunset,
'z': render_zenith,
}
def render_line(line, data, query):
"""
Render format `line` using `data`
"""
def get_local_time_of():
location = data["location"]
geo_data = get_geodata(location)
city = LocationInfo()
city.latitude = geo_data["latitude"]
city.longitude = geo_data["longitude"]
city.timezone = geo_data["timezone"]
timezone = city.timezone
local_tz = pytz.timezone(timezone)
datetime_day_start = datetime.datetime.now()\
.replace(hour=0, minute=0, second=0, microsecond=0)
current_sun = sun(city.observer, date=datetime_day_start)
local_time_of = lambda x: current_sun[x]\
.replace(tzinfo=pytz.utc)\
.astimezone(local_tz)\
.strftime("%H:%M:%S")
return local_time_of
def render_symbol(match):
"""
Render one format symbol from re `match`
using `data` from external scope.
"""
symbol_string = match.group(0)
symbol = symbol_string[-1]
if symbol in FORMAT_SYMBOL:
render_function = FORMAT_SYMBOL[symbol]
return render_function(data, query)
if symbol in FORMAT_SYMBOL_ASTRO and local_time_of is not None:
render_function = FORMAT_SYMBOL_ASTRO[symbol]
return render_function(data, query, local_time_of)
return ''
template_regexp = r'%[a-zA-Z]'
for template_code in re.findall(template_regexp, line):
if template_code.lstrip("%") in FORMAT_SYMBOL_ASTRO:
local_time_of = get_local_time_of()
break
return re.sub(template_regexp, render_symbol, line)
def render_json(data):
output = json.dumps(data, indent=4, sort_keys=True, ensure_ascii=False)
output = "\n".join(
re.sub('"[^"]*worldweatheronline[^"]*"', '""', line) if "worldweatheronline" in line else line
for line in output.splitlines()) + "\n"
return output
def format_weather_data(query, parsed_query, data):
"""
Format information about current weather `data` for `location`
with specified in `format_line` format
"""
if 'data' not in data:
return 'Unknown location; please try ~%s' % parsed_query["location"]
format_line = parsed_query.get("view", "")
if format_line in PRECONFIGURED_FORMAT:
format_line = PRECONFIGURED_FORMAT[format_line]
if format_line == "j1":
return render_json(data['data'])
if format_line == "p1":
return prometheus.render_prometheus(data['data'])
if format_line[:2] == "v2":
return v2.main(query, parsed_query, data)
current_condition = data['data']['current_condition'][0]
current_condition['location'] = parsed_query["location"]
current_condition['override_location'] = parsed_query["override_location_name"]
output = render_line(format_line, current_condition, query)
return output
def wttr_line(query, parsed_query):
"""
Return 1line weather information for `location`
in format `line_format`
"""
location = parsed_query['location']
lang = parsed_query['lang']
data = get_weather_data(location, lang)
output = format_weather_data(query, parsed_query, data)
return output.rstrip("\n").replace(r"\n", "\n")
def main():
"""
Function for standalone module usage
"""
location = sys.argv[1]
query = {
'line': sys.argv[2],
}
parsed_query = {
"location": location,
"orig_location": location,
"language": "en",
"format": "v2",
}
sys.stdout.write(wttr_line(query, parsed_query))
if __name__ == '__main__':
main()

View file

@ -1,57 +0,0 @@
import sys
import os
import dateutil.parser
from gevent.subprocess import Popen, PIPE
sys.path.insert(0, "..")
import constants
import parse_query
import globals
def get_moon(parsed_query):
location = parsed_query['orig_location']
html = parsed_query['html_output']
lang = parsed_query['lang']
hemisphere = parsed_query['hemisphere']
date = None
if '@' in location:
date = location[location.index('@')+1:]
location = location[:location.index('@')]
cmd = [globals.PYPHOON]
if lang:
cmd += ["-l", lang]
if not hemisphere:
cmd += ["-s", "south"]
if date:
try:
dateutil.parser.parse(date)
except Exception as e:
print("ERROR: %s" % e)
else:
cmd += [date]
p = Popen(cmd, stdout=PIPE, stderr=PIPE)
stdout = p.communicate()[0]
stdout = stdout.decode("utf-8")
if parsed_query.get('no-terminal', False):
stdout = globals.remove_ansi(stdout)
if html:
p = Popen(
["bash", globals.ANSI2HTML, "--palette=solarized", "--bg=dark"],
stdin=PIPE, stdout=PIPE, stderr=PIPE)
stdout, stderr = p.communicate(stdout.encode("utf-8"))
stdout = stdout.decode("utf-8")
stderr = stderr.decode("utf-8")
if p.returncode != 0:
globals.error(stdout + stderr)
return stdout

View file

@ -1,69 +0,0 @@
"""
Rendering weather data in the Prometheus format.
"""
from datetime import datetime
from fields import DESCRIPTION
def _render_current(data, for_day="current", already_seen=[]):
"Converts data into prometheus style format"
output = []
for field_name, val in DESCRIPTION.items():
help, name = val
try:
value = data[field_name]
if field_name == "weatherDesc":
value = value[0]["value"]
except (IndexError, KeyError):
try:
value = data["astronomy"][0][field_name]
if value.endswith(" AM") or value.endswith(" PM"):
value = _convert_time_to_minutes(value)
except (IndexError, KeyError, ValueError):
continue
try:
if name == "observation_time":
value = _convert_time_to_minutes(value)
except ValueError:
continue
description = ""
try:
float(value)
except ValueError:
description = f", description=\"{value}\""
value = "1"
if name not in already_seen:
output.append(f"# HELP {name} {help}")
already_seen.append(name)
output.append(f"{name}{{forecast=\"{for_day}\"{description}}} {value}")
return "\n".join(output)+"\n"
def _convert_time_to_minutes(time_str):
"Convert time from midnight to minutes"
return int((datetime.strptime(time_str, "%I:%M %p")
- datetime.strptime("12:00 AM", "%I:%M %p")).total_seconds())//60
def render_prometheus(data):
"""
Convert `data` into Prometheus format
and return it as string.
"""
already_seen = []
answer = _render_current(
data["current_condition"][0], already_seen=already_seen)
for i in range(3):
answer += _render_current(
data["weather"][i], for_day="%sd" % i, already_seen=already_seen)
return answer

View file

@ -1,633 +0,0 @@
# vim: fileencoding=utf-8
# vim: foldmethod=marker foldenable:
"""
[X] emoji
[ ] wego icon
[ ] v2.wttr.in
[X] astronomical (sunset)
[X] time
[X] frames
[X] colorize rain data
[ ] date + locales
[X] wind color
[ ] highlight current date
[ ] bind to real site
[ ] max values: temperature
[X] max value: rain
[ ] comment github
[ ] commit
"""
import sys
import re
import math
import json
import datetime
import io
import requests
import diagram
import pyjq
import pytz
import numpy as np
from astral import LocationInfo
from astral import moon, sun
from scipy.interpolate import interp1d
from babel.dates import format_datetime
from globals import WWO_KEY, remove_ansi
import constants
import translations
import parse_query
from . import line as wttr_line
if not sys.version_info >= (3, 0):
reload(sys) # noqa: F821
sys.setdefaultencoding("utf-8")
# data processing {{{
def get_data(config):
"""
Fetch data for `query_string`
"""
url = (
'http://'
'localhost:5001/premium/v1/weather.ashx'
'?key=%s'
'&q=%s&format=json&num_of_days=3&tp=3&lang=None'
) % (WWO_KEY, config["location"])
text = requests.get(url).text
parsed_data = json.loads(text)
return parsed_data
def interpolate_data(input_data, max_width):
"""
Resample `input_data` to number of `max_width` counts
"""
input_data = list(input_data)
input_data_len = len(input_data)
x = list(range(input_data_len))
y = input_data
xvals = np.linspace(0, input_data_len-1, max_width)
yinterp = interp1d(x, y, kind='cubic')
return yinterp(xvals)
def jq_query(query, data_parsed):
"""
Apply `query` to structued data `data_parsed`
"""
pyjq_data = pyjq.all(query, data_parsed)
data = list(map(float, pyjq_data))
return data
# }}}
# utils {{{
def colorize(string, color_code, html_output=False):
if html_output:
return "<font color='#777777'>%s</font>" % (string)
else:
return "\033[%sm%s\033[0m" % (color_code, string)
# }}}
# draw_spark {{{
def draw_spark(data, height, width, color_data):
"""
Spark-style visualize `data` in a region `height` x `width`
"""
_BARS = u' _▁▂▃▄▅▇█'
def _box(height, row, value, max_value):
row_height = 1.0 * max_value / height
if row_height * row >= value:
return _BARS[0]
if row_height * (row+1) <= value:
return _BARS[-1]
return _BARS[int(1.0*(value - row_height*row)/(row_height*1.0)*len(_BARS))]
max_value = max(data)
output = ""
color_code = 20
for i in range(height):
for j in range(width):
character = _box(height, height-i-1, data[j], max_value)
if data[j] != 0:
chance_of_rain = color_data[j]/100.0 * 2
if chance_of_rain > 1:
chance_of_rain = 1
color_index = int(5*chance_of_rain)
color_code = 16 + color_index # int(math.floor((20-16) * 1.0 * (height-1-i)/height*(max_value/data[j])))
output += "\033[38;5;%sm%s\033[0m" % (color_code, character)
output += "\n"
# labeling max value
if max_value == 0:
max_line = " "*width
else:
max_line = ""
for j in range(width):
if data[j] == max_value:
max_line = "%3.2fmm|%s%%" % (max_value, int(color_data[j]))
orig_max_line = max_line
# aligning it
if len(max_line)//2 < j and len(max_line)//2 + j < width:
spaces = " "*(j - len(max_line)//2)
max_line = spaces + max_line # + spaces
max_line = max_line + " "*(width - len(max_line))
elif len(max_line)//2 + j >= width:
max_line = " "*(width - len(max_line)) + max_line
max_line = max_line.replace(orig_max_line, colorize(orig_max_line, "38;5;33"))
break
if max_line:
output = "\n" + max_line + "\n" + output + "\n"
return output
# }}}
# draw_diagram {{{
def draw_diagram(data, height, width):
option = diagram.DOption()
option.size = diagram.Point([width, height])
option.mode = 'g'
stream = io.BytesIO()
gram = diagram.DGWrapper(
data=[list(data), range(len(data))],
dg_option=option,
ostream=stream)
gram.show()
return stream.getvalue().decode("utf-8")
# }}}
# draw_date {{{
def draw_date(config, geo_data):
"""
"""
tzinfo = pytz.timezone(geo_data["timezone"])
locale = config.get("locale", "en_US")
datetime_day_start = datetime.datetime.utcnow()
answer = ""
for day in range(3):
datetime_ = datetime_day_start + datetime.timedelta(hours=24*day)
date = format_datetime(datetime_, "EEE dd MMM", locale=locale, tzinfo=tzinfo)
spaces = ((24-len(date))//2)*" "
date = spaces + date + spaces
date = " "*(24-len(date)) + date
answer += date
answer += "\n"
for _ in range(3):
answer += " "*23 + u""
return answer[:-1] + " "
# }}}
# draw_time {{{
def draw_time(geo_data):
"""
"""
tzinfo = pytz.timezone(geo_data["timezone"])
line = ["", ""]
for _ in range(3):
part = u""*5 + u"" + u""*5
line[0] += part + u"" + part + u""
line[0] += "\n"
for _ in range(3):
line[1] += " 6 12 18 "
line[1] += "\n"
# highlight current time
hour_number = \
(datetime.datetime.now(tzinfo)
- datetime.datetime.now(tzinfo).replace(hour=0, minute=0, second=0, microsecond=0)
).seconds//3600
for line_number, _ in enumerate(line):
line[line_number] = \
line[line_number][:hour_number] \
+ colorize(line[line_number][hour_number], "46") \
+ line[line_number][hour_number+1:]
return "".join(line)
# }}}
# draw_astronomical {{{
def draw_astronomical(city_name, geo_data, config):
datetime_day_start = datetime.datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
city = LocationInfo()
city.latitude = geo_data["latitude"]
city.longitude = geo_data["longitude"]
city.timezone = geo_data["timezone"]
answer = ""
moon_line = ""
for time_interval in range(72):
current_date = (
datetime_day_start
+ datetime.timedelta(hours=1*time_interval)).replace(tzinfo=pytz.timezone(geo_data["timezone"]))
try:
dawn = sun.dawn(city.observer, date=current_date)
except ValueError:
dawn = current_date
try:
dusk = sun.dusk(city.observer, date=current_date)
except ValueError:
dusk = current_date + datetime.timedelta(hours=24)
try:
sunrise = sun.sunrise(city.observer, date=current_date)
except ValueError:
sunrise = current_date
try:
sunset = sun.sunset(city.observer, date=current_date)
except ValueError:
sunset = current_date + datetime.timedelta(hours=24)
char = "."
if current_date < dawn:
char = " "
elif current_date > dusk:
char = " "
elif dawn <= current_date and current_date <= sunrise:
char = u""
elif sunset <= current_date and current_date <= dusk:
char = u""
elif sunrise <= current_date and current_date <= sunset:
char = u""
answer += char
if config.get("view") in ["v2n", "v2d"]:
moon_phases = constants.MOON_PHASES_WI
moon_phases = [" %s" % x for x in moon_phases]
else:
moon_phases = constants.MOON_PHASES
# moon
if time_interval in [0,23,47,69]: # time_interval % 3 == 0:
moon_phase = moon.phase(
date=datetime_day_start + datetime.timedelta(hours=time_interval))
moon_phase_emoji = moon_phases[
int(math.floor(moon_phase*1.0/28.0*8+0.5)) % len(moon_phases)]
# if time_interval in [0, 24, 48, 69]:
moon_line += moon_phase_emoji # + " "
elif time_interval % 3 == 0:
if time_interval not in [24,28]: #se:
moon_line += " "
else:
moon_line += " "
answer = moon_line + "\n" + answer + "\n"
answer += "\n"
return answer
# }}}
# draw_emoji {{{
def draw_emoji(data, config):
answer = ""
if config.get("view") == "v2n":
weather_symbol = constants.WEATHER_SYMBOL_WI_NIGHT
weather_symbol_width_vte = constants.WEATHER_SYMBOL_WIDTH_VTE_WI
elif config.get("view") == "v2d":
weather_symbol = constants.WEATHER_SYMBOL_WI_NIGHT
weather_symbol_width_vte = constants.WEATHER_SYMBOL_WIDTH_VTE_WI
else:
weather_symbol = constants.WEATHER_SYMBOL
weather_symbol_width_vte = constants.WEATHER_SYMBOL_WIDTH_VTE
for i in data:
emoji = weather_symbol.get(
constants.WWO_CODE.get(
str(int(i)), "Unknown"))
space = " "*(3-weather_symbol_width_vte.get(emoji, 1))
answer += space[:1] + emoji + space[1:]
answer += "\n"
return answer
# }}}
# draw_wind {{{
def draw_wind(data, color_data, config):
def _color_code_for_wind_speed(wind_speed):
color_codes = [
(3, 241), # 82
(6, 242), # 118
(9, 243), # 154
(12, 246), # 190
(15, 250), # 226
(19, 253), # 220
(23, 214),
(27, 208),
(31, 202),
(-1, 196)
]
for this_wind_speed, this_color_code in color_codes:
if wind_speed <= this_wind_speed:
return this_color_code
return color_codes[-1][1]
answer = ""
answer_line2 = ""
if config.get("view") in ["v2n", "v2d"]:
wind_direction_list = constants.WIND_DIRECTION_WI
else:
wind_direction_list = constants.WIND_DIRECTION
for j, degree in enumerate(data):
degree = int(degree)
if degree:
wind_direction = wind_direction_list[int(((degree+22.5)%360)/45.0)]
else:
wind_direction = ""
color_code = "38;5;%s" % _color_code_for_wind_speed(int(color_data[j]))
answer += " %s " % colorize(wind_direction, color_code)
# wind_speed
wind_speed = int(color_data[j])
wind_speed_str = colorize(str(wind_speed), color_code)
if wind_speed < 10:
wind_speed_str = " " + wind_speed_str + " "
elif wind_speed < 100:
wind_speed_str = " " + wind_speed_str
answer_line2 += wind_speed_str
answer += "\n"
answer += answer_line2 + "\n"
return answer
# }}}
# panel implementation {{{
def add_frame(output, width, config):
"""
Add frame arond `output` that has width `width`
"""
empty_line = " "*width
output = "\n".join(u""+(x or empty_line)+u"" for x in output.splitlines()) + "\n"
weather_report = \
translations.CAPTION[config.get("lang") or "en"] \
+ " " \
+ (config["override_location_name"] or config["location"])
caption = u"" + " " + weather_report + " " + u""
output = u"" + caption + u""*(width-len(caption)) + u"\n" \
+ output + \
u"" + u""*width + u"\n"
return output
def generate_panel(data_parsed, geo_data, config):
"""
"""
max_width = 72
precip_mm_query = "[.data.weather[] | .hourly[]] | .[].precipMM"
precip_chance_query = "[.data.weather[] | .hourly[]] | .[].chanceofrain"
feels_like_query = "[.data.weather[] | .hourly[]] | .[].FeelsLikeC"
weather_code_query = "[.data.weather[] | .hourly[]] | .[].weatherCode"
wind_direction_query = "[.data.weather[] | .hourly[]] | .[].winddirDegree"
wind_speed_query = "[.data.weather[] | .hourly[]] | .[].windspeedKmph"
output = ""
output += "\n\n"
output += draw_date(config, geo_data)
output += "\n"
output += "\n"
output += "\n"
data = jq_query(feels_like_query, data_parsed)
data_interpolated = interpolate_data(data, max_width)
output += draw_diagram(data_interpolated, 10, max_width)
output += "\n"
output += draw_time(geo_data)
data = jq_query(precip_mm_query, data_parsed)
color_data = jq_query(precip_chance_query, data_parsed)
data_interpolated = interpolate_data(data, max_width)
color_data_interpolated = interpolate_data(color_data, max_width)
output += draw_spark(data_interpolated, 5, max_width, color_data_interpolated)
output += "\n"
data = jq_query(weather_code_query, data_parsed)
output += draw_emoji(data, config)
data = jq_query(wind_direction_query, data_parsed)
color_data = jq_query(wind_speed_query, data_parsed)
output += draw_wind(data, color_data, config)
output += "\n"
output += draw_astronomical(config["location"], geo_data, config)
output += "\n"
output = add_frame(output, max_width, config)
return output
# }}}
# textual information {{{
def textual_information(data_parsed, geo_data, config, html_output=False):
"""
Add textual information about current weather and
astronomical conditions
"""
def _shorten_full_location(full_location, city_only=False):
def _count_runes(string):
return len(string.encode('utf-16-le')) // 2
words = full_location.split(",")
output = words[0]
if city_only:
return output
for word in words[1:]:
if _count_runes(output + "," + word) > 50:
return output
output += "," + word
return output
def _colorize(text, color):
return colorize(text, color, html_output=html_output)
city = LocationInfo()
city.latitude = geo_data["latitude"]
city.longitude = geo_data["longitude"]
city.timezone = geo_data["timezone"]
output = []
timezone = city.timezone
datetime_day_start = datetime.datetime.now()\
.replace(hour=0, minute=0, second=0, microsecond=0)
format_line = "%c %C, %t, %h, %w, %P"
current_condition = data_parsed['data']['current_condition'][0]
query = {}
weather_line = wttr_line.render_line(format_line, current_condition, query)
output.append('Weather: %s' % weather_line)
output.append('Timezone: %s' % timezone)
local_tz = pytz.timezone(timezone)
def _get_local_time_of(what):
_sun = {
"dawn": sun.dawn,
"sunrise": sun.sunrise,
"noon": sun.noon,
"sunset": sun.sunset,
"dusk": sun.dusk,
}[what]
current_time_of_what = _sun(city.observer, date=datetime_day_start)
return current_time_of_what\
.replace(tzinfo=pytz.utc)\
.astimezone(local_tz)\
.strftime("%H:%M:%S")
local_time_of = {}
for what in ["dawn", "sunrise", "noon", "sunset", "dusk"]:
try:
local_time_of[what] = _get_local_time_of(what)
except ValueError:
local_time_of[what] = "-"*8
tmp_output = []
tmp_output.append(' Now: %%{{NOW(%s)}}' % timezone)
tmp_output.append('Dawn: %s' % local_time_of["dawn"])
tmp_output.append('Sunrise: %s' % local_time_of["sunrise"])
tmp_output.append(' Zenith: %s ' % local_time_of["noon"])
tmp_output.append('Sunset: %s' % local_time_of["sunset"])
tmp_output.append('Dusk: %s' % local_time_of["dusk"])
tmp_output = [
re.sub("^([A-Za-z]*:)", lambda m: _colorize(m.group(1), "2"), x)
for x in tmp_output]
output.append(
"%20s" % tmp_output[0] \
+ " | %20s " % tmp_output[1] \
+ " | %20s" % tmp_output[2])
output.append(
"%20s" % tmp_output[3] \
+ " | %20s " % tmp_output[4] \
+ " | %20s" % tmp_output[5])
city_only = False
suffix = ""
if "Simferopol" in timezone:
city_only = True
suffix = ", Крым"
if config["full_address"]:
output.append('Location: %s%s [%5.4f,%5.4f]' \
% (
_shorten_full_location(config["full_address"], city_only=city_only),
suffix,
geo_data["latitude"],
geo_data["longitude"],
))
output = [
re.sub("^( *[A-Za-z]*:)", lambda m: _colorize(m.group(1), "2"),
re.sub("^( +[A-Za-z]*:)", lambda m: _colorize(m.group(1), "2"),
re.sub(r"(\|)", lambda m: _colorize(m.group(1), "2"), x)))
for x in output]
return "".join("%s\n" % x for x in output)
# }}}
# get_geodata {{{
def get_geodata(location):
text = requests.get("http://localhost:8004/%s" % location).text
return json.loads(text)
# }}}
def main(query, parsed_query, data):
parsed_query["locale"] = "en_US"
location = parsed_query["location"]
html_output = parsed_query["html_output"]
geo_data = get_geodata(location)
if data is None:
data_parsed = get_data(parsed_query)
else:
data_parsed = data
if html_output:
parsed_query["text"] = "no"
filename = "b_" + parse_query.serialize(parsed_query) + ".png"
output = """
<html>
<head>
<title>Weather report for {orig_location}</title>
<link rel="stylesheet" type="text/css" href="/files/style.css" />
</head>
<body>
<img src="/{filename}" width="592" height="532"/>
<pre>
{textual_information}
</pre>
</body>
</html>
""".format(
filename=filename, orig_location=parsed_query["orig_location"],
textual_information=textual_information(
data_parsed, geo_data, parsed_query, html_output=True))
else:
output = generate_panel(data_parsed, geo_data, parsed_query)
if query.get("text") != "no" and parsed_query.get("text") != "no":
output += textual_information(data_parsed, geo_data, parsed_query)
if parsed_query.get('no-terminal', False):
output = remove_ansi(output)
return output
if __name__ == '__main__':
sys.stdout.write(main(sys.argv[1]))

View file

@ -1,169 +0,0 @@
# vim: set encoding=utf-8
# pylint: disable=wrong-import-position
"""
Main view (wttr.in) implementation.
The module is a wrapper for the modified Wego program.
"""
import sys
import re
from gevent.subprocess import Popen, PIPE
sys.path.insert(0, "..")
from translations import get_message, SUPPORTED_LANGS
from globals import WEGO, NOT_FOUND_LOCATION, DEFAULT_LOCATION, ANSI2HTML, \
error, remove_ansi
def get_wetter(parsed_query):
location = parsed_query['location']
html = parsed_query['html_output']
lang = parsed_query['lang']
location_not_found = False
if location == NOT_FOUND_LOCATION:
location_not_found = True
stderr = ""
returncode = 0
if not location_not_found:
stdout, stderr, returncode = _wego_wrapper(location, parsed_query)
if location_not_found or \
(returncode != 0 \
and ('Unable to find any matching weather'
' location to the parsed_query submitted') in stderr):
stdout, stderr, returncode = _wego_wrapper(NOT_FOUND_LOCATION, parsed_query)
location_not_found = True
stdout += get_message('NOT_FOUND_MESSAGE', lang)
if "\n" in stdout:
first_line, stdout = _wego_postprocessing(location, parsed_query, stdout)
else:
first_line = ""
if html:
return _htmlize(stdout, first_line, parsed_query)
return stdout
def _wego_wrapper(location, parsed_query):
lang = parsed_query['lang']
location_name = parsed_query['override_location_name']
cmd = [WEGO, '--city=%s' % location]
if parsed_query.get('inverted_colors'):
cmd += ['-inverse']
if parsed_query.get('use_ms_for_wind'):
cmd += ['-wind_in_ms']
if parsed_query.get('narrow'):
cmd += ['-narrow']
if lang and lang in SUPPORTED_LANGS:
cmd += ['-lang=%s'%lang]
if parsed_query.get('use_imperial', False):
cmd += ['-imperial']
if location_name:
cmd += ['-location_name', location_name]
proc = Popen(cmd, stdout=PIPE, stderr=PIPE)
stdout, stderr = proc.communicate()
stdout = stdout.decode("utf-8")
stderr = stderr.decode("utf-8")
return stdout, stderr, proc.returncode
def _wego_postprocessing(location, parsed_query, stdout):
full_address = parsed_query['full_address']
lang = parsed_query['lang']
if 'days' in parsed_query:
if parsed_query['days'] == '0':
stdout = "\n".join(stdout.splitlines()[:7]) + "\n"
if parsed_query['days'] == '1':
stdout = "\n".join(stdout.splitlines()[:17]) + "\n"
if parsed_query['days'] == '2':
stdout = "\n".join(stdout.splitlines()[:27]) + "\n"
first = stdout.splitlines()[0]
rest = stdout.splitlines()[1:]
if parsed_query.get('no-caption', False):
if ':' in first:
first = first.split(":", 1)[1]
stdout = "\n".join([first.strip()] + rest) + "\n"
if parsed_query.get('no-terminal', False):
stdout = remove_ansi(stdout)
if parsed_query.get('no-city', False):
stdout = "\n".join(stdout.splitlines()[2:]) + "\n"
if full_address \
and parsed_query.get('format', 'txt') != 'png' \
and (not parsed_query.get('no-city')
and not parsed_query.get('no-caption')
and not parsed_query.get('days') == '0'):
line = "%s: %s [%s]\n" % (
get_message('LOCATION', lang),
full_address,
location)
stdout += line
if parsed_query.get('padding', False):
lines = [x.rstrip() for x in stdout.splitlines()]
max_l = max(len(remove_ansi(x)) for x in lines)
last_line = " "*max_l + " .\n"
stdout = " \n" + "\n".join(" %s " %x for x in lines) + "\n" + last_line
return first, stdout
def _htmlize(ansi_output, title, parsed_query):
"""Return HTML representation of `ansi_output`.
Use `title` as the title of the page.
Format page according to query parameters from `parsed_query`."""
cmd = ["bash", ANSI2HTML, "--palette=solarized"]
if not parsed_query.get('inverted_colors'):
cmd += ["--bg=dark"]
proc = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
stdout, stderr = proc.communicate(ansi_output.encode("utf-8"))
stdout = stdout.decode("utf-8")
stderr = stderr.decode("utf-8")
if proc.returncode != 0:
error(stdout + stderr)
if parsed_query.get('inverted_colors'):
stdout = stdout.replace(
'<body class="">', '<body class="" style="background:white;color:#777777">')
title = "<title>%s</title>" % title
opengraph = _get_opengraph(parsed_query)
stdout = re.sub("<head>", "<head>" + title + opengraph, stdout)
return stdout
def _get_opengraph(parsed_query):
"""Return OpenGraph data for `parsed_query`"""
url = parsed_query['request_url'] or ""
pic_url = url.replace('?', '_')
return (
'<meta property="og:image" content="%(pic_url)s_0pq.png" />'
'<meta property="og:site_name" content="wttr.in" />'
'<meta property="og:type" content="profile" />'
'<meta property="og:url" content="%(url)s" />'
) % {
'pic_url': pic_url,
'url': url,
}

View file

@ -1,24 +0,0 @@
"""
Weather data source
"""
import json
import requests
from globals import WWO_KEY
def get_weather_data(location, lang):
"""
Get weather data for `location`
"""
key = WWO_KEY
url = ('/premium/v1/weather.ashx'
'?key=%s&q=%s&format=json'
'&num_of_days=3&tp=3&lang=%s') % (key, location, lang)
url = 'http://127.0.0.1:5001' + url
response = requests.get(url, timeout=10)
try:
data = json.loads(response.content)
except ValueError:
data = {}
return data

View file

@ -1,388 +0,0 @@
#!/usr/bin/env python
# vim: set encoding=utf-8
"""
Main wttr.in rendering function implementation
"""
import logging
import io
import os
import time
from gevent.threadpool import ThreadPool
from flask import render_template, send_file, make_response
import fmt.png
import parse_query
from translations import get_message, FULL_TRANSLATION, PARTIAL_TRANSLATION, SUPPORTED_LANGS
from buttons import add_buttons
from globals import get_help_file, remove_ansi, \
BASH_FUNCTION_FILE, TRANSLATION_FILE, LOG_FILE, \
NOT_FOUND_LOCATION, \
MALFORMED_RESPONSE_HTML_PAGE, \
PLAIN_TEXT_AGENTS, PLAIN_TEXT_PAGES, \
MY_EXTERNAL_IP, QUERY_LIMITS
from location import is_location_blocked, location_processing
from limits import Limits
from view.wttr import get_wetter
from view.moon import get_moon
from view.line import wttr_line
import cache
if not os.path.exists(os.path.dirname(LOG_FILE)):
os.makedirs(os.path.dirname(LOG_FILE))
logging.basicConfig(filename=LOG_FILE, level=logging.INFO, format='%(asctime)s %(message)s')
LIMITS = Limits(whitelist=[MY_EXTERNAL_IP], limits=QUERY_LIMITS)
TASKS = ThreadPool(25)
def show_text_file(name, lang):
"""
show static file `name` for `lang`
"""
text = ""
if name == ":help":
text = open(get_help_file(lang), 'r').read()
text = text.replace('FULL_TRANSLATION', ' '.join(FULL_TRANSLATION))
text = text.replace('PARTIAL_TRANSLATION', ' '.join(PARTIAL_TRANSLATION))
elif name == ":bash.function":
text = open(BASH_FUNCTION_FILE, 'r').read()
elif name == ":iterm2":
text = open("share/iterm2.txt", 'r').read()
elif name == ":translation":
text = open(TRANSLATION_FILE, 'r').read()
text = text\
.replace('NUMBER_OF_LANGUAGES', str(len(SUPPORTED_LANGS)))\
.replace('SUPPORTED_LANGUAGES', ' '.join(SUPPORTED_LANGS))
return text
def _client_ip_address(request):
"""Return client ip address for flask `request`.
"""
if request.headers.getlist("X-PNG-Query-For"):
ip_addr = request.headers.getlist("X-PNG-Query-For")[0]
if ip_addr.startswith('::ffff:'):
ip_addr = ip_addr[7:]
elif request.headers.getlist("X-Forwarded-For"):
ip_addr = request.headers.getlist("X-Forwarded-For")[0]
if ip_addr.startswith('::ffff:'):
ip_addr = ip_addr[7:]
else:
ip_addr = request.remote_addr
return ip_addr
def _parse_language_header(header):
"""
>>> _parse_language_header("en-US,en;q=0.9")
>>> _parse_language_header("en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7")
>>> _parse_language_header("xx, fr-CA;q=0.8, da-DK;q=0.9")
'da'
"""
def _parse_accept_language(accept_language):
languages = accept_language.split(",")
locale_q_pairs = []
for language in languages:
try:
if language.split(";")[0] == language:
# no q => q = 1
locale_q_pairs.append((language.strip(), 1))
else:
locale = language.split(";")[0].strip()
weight = float(language.split(";")[1].split("=")[1])
locale_q_pairs.append((locale, weight))
except (IndexError, ValueError):
pass
return locale_q_pairs
def _find_supported_language(accepted_languages):
def supported_langs():
"""Yields all pairs in the Accept-Language header
supported in SUPPORTED_LANGS or None if 'en' is the preferred"""
for lang_tuple in accepted_languages:
lang = lang_tuple[0]
if '-' in lang:
lang = lang.split('-', 1)[0]
if lang in SUPPORTED_LANGS:
yield lang, lang_tuple[1]
elif lang == 'en':
yield None, lang_tuple[1]
try:
return max(supported_langs(), key=lambda lang_tuple: lang_tuple[1])[0]
except ValueError:
return None
return _find_supported_language(_parse_accept_language(header))
def get_answer_language_and_view(request):
"""
Return preferred answer language based on
domain name, query arguments and headers
"""
lang = None
view_name = None
hostname = request.headers['Host']
if hostname != 'wttr.in' and hostname.endswith('.wttr.in'):
lang = hostname[:-8]
if lang.startswith("v2"):
view_name = lang
lang = None
if 'lang' in request.args:
lang = request.args.get('lang')
if lang.lower() == 'none':
lang = None
header_accept_language = request.headers.get('Accept-Language', '')
if lang is None and header_accept_language:
lang = _parse_language_header(header_accept_language)
return lang, view_name
def get_output_format(query, parsed_query):
"""
Return preferred output format: ansi, text, html or png
based on arguments and headers in `request`.
Return new location (can be rewritten)
"""
if ('view' in query and not query["view"].startswith("v2")) \
or parsed_query.get("png_filename") \
or query.get('force-ansi'):
return False
user_agent = parsed_query.get("user_agent", "").lower()
html_output = not any(agent in user_agent for agent in PLAIN_TEXT_AGENTS)
return html_output
def _cyclic_location_selection(locations, period):
"""Return one of `locations` (: separated list)
basing on the current time and query interval `period`
"""
locations = locations.split(':')
max_len = max(len(x) for x in locations)
locations = [x.rjust(max_len) for x in locations]
try:
period = int(period)
except ValueError:
period = 1
index = int(time.time()/period) % len(locations)
return locations[index]
def _response(parsed_query, query, fast_mode=False):
"""Create response text based on `parsed_query` and `query` data.
If `fast_mode` is True, process only requests that can
be handled very fast (cached and static files).
"""
answer = None
cache_signature = cache.get_signature(
parsed_query["user_agent"],
parsed_query["request_url"],
parsed_query["ip_addr"],
parsed_query["lang"])
answer = cache.get(cache_signature)
if parsed_query['orig_location'] in PLAIN_TEXT_PAGES:
answer = show_text_file(parsed_query['orig_location'], parsed_query['lang'])
if parsed_query['html_output']:
answer = render_template('index.html', body=answer)
if answer or fast_mode:
return answer
# at this point, we could not handle the query fast,
# so we handle it with all available logic
loc = (parsed_query['orig_location'] or "").lower()
if parsed_query.get("view"):
output = wttr_line(query, parsed_query)
elif loc == 'moon' or loc.startswith('moon@'):
output = get_moon(parsed_query)
else:
output = get_wetter(parsed_query)
if parsed_query.get('png_filename'):
# originally it was just a usual function call,
# but it was a blocking call, so it was moved
# to separate threads:
#
# output = fmt.png.render_ansi(
# output, options=parsed_query)
result = TASKS.spawn(fmt.png.render_ansi, cache._update_answer(output), options=parsed_query)
output = result.get()
else:
if query.get('days', '3') != '0' \
and not query.get('no-follow-line') \
and ((parsed_query.get("view") or "v2")[:2] in ["v2"]):
if parsed_query['html_output']:
output = add_buttons(output)
else:
message = get_message('FOLLOW_ME', parsed_query['lang'])
if parsed_query.get('no-terminal', False):
message = remove_ansi(message)
output += '\n' + message + '\n'
return cache.store(cache_signature, output)
def parse_request(location, request, query, fast_mode=False):
"""Parse request and provided extended information for the query,
including location data, language, output format, view, etc.
Incoming data:
`location` location name extracted from the query url
`request.args`
`request.headers`
`request.remote_addr`
`request.referrer`
`request.query_string`
`query` parsed command line arguments
Parameters priorities (from low to high):
* HTTP-header
* Domain name
* URL
* Filename
Return: dictionary with parsed parameters
"""
if location and location.startswith("b_"):
result = parse_query.deserialize(location)
result["request_url"] = request.url
if result:
return result
png_filename = None
if location is not None and location.lower().endswith(".png"):
png_filename = location
location = location[:-4]
if location and ':' in location and location[0] != ":":
location = _cyclic_location_selection(location, query.get('period', 1))
parsed_query = {
'ip_addr': _client_ip_address(request),
'user_agent': request.headers.get('User-Agent', '').lower(),
'request_url': request.url,
}
if png_filename:
parsed_query["png_filename"] = png_filename
parsed_query.update(parse_query.parse_wttrin_png_name(png_filename))
lang, _view = get_answer_language_and_view(request)
parsed_query["view"] = parsed_query.get("view", query.get("view", _view))
parsed_query["location"] = parsed_query.get("location", location)
parsed_query["orig_location"] = parsed_query["location"]
parsed_query["lang"] = parsed_query.get("lang", lang)
parsed_query["html_output"] = get_output_format(query, parsed_query)
if not fast_mode: # not png_filename and not fast_mode:
location, override_location_name, full_address, country, query_source_location, hemisphere = \
location_processing(parsed_query["location"], parsed_query["ip_addr"])
us_ip = query_source_location[1] == 'United States' \
and 'slack' not in parsed_query['user_agent']
query = parse_query.metric_or_imperial(query, lang, us_ip=us_ip)
if country and location != NOT_FOUND_LOCATION:
location = "%s,%s" % (location, country)
parsed_query.update({
'location': location,
'override_location_name': override_location_name,
'full_address': full_address,
'country': country,
'query_source_location': query_source_location,
'hemisphere': hemisphere})
parsed_query.update(query)
return parsed_query
def wttr(location, request):
"""Main rendering function, it processes incoming weather queries,
and depending on the User-Agent string and other paramters of the query
it returns output in HTML, ANSI or other format.
"""
def _wrap_response(response_text, html_output, png_filename=None):
if not isinstance(response_text, str) and \
not isinstance(response_text, bytes):
return response_text
if png_filename:
response = make_response(send_file(
io.BytesIO(response_text),
attachment_filename=png_filename,
mimetype='image/png'))
for key, value in {
'Cache-Control': 'no-cache, no-store, must-revalidate',
'Pragma': 'no-cache',
'Expires': '0',
}.items():
response.headers[key] = value
else:
response = make_response(response_text)
response.mimetype = 'text/html' if html_output else 'text/plain'
return response
if is_location_blocked(location):
return ("", 403) # Forbidden
try:
LIMITS.check_ip(_client_ip_address(request))
except RuntimeError as exception:
return (str(exception), 429) # Too many requests
query = parse_query.parse_query(request.args)
# first, we try to process the query as fast as possible
# (using the cache and static files),
# and only if "fast_mode" was unsuccessful,
# use the full track
parsed_query = parse_request(location, request, query, fast_mode=True)
response = _response(parsed_query, query, fast_mode=True)
http_code = 200
try:
if not response:
parsed_query = parse_request(location, request, query)
response = _response(parsed_query, query)
# pylint: disable=broad-except
except Exception as exception:
logging.error("Exception has occured", exc_info=1)
if parsed_query['html_output']:
response = MALFORMED_RESPONSE_HTML_PAGE
http_code = 500 # Internal Server Error
else:
response = get_message('CAPACITY_LIMIT_REACHED', parsed_query['lang'])
http_code = 503 # Service Unavailable
# if exception is occured, we return not a png file but text
if "png_filename" in parsed_query:
del parsed_query["png_filename"]
return (_wrap_response(
response, parsed_query['html_output'],
png_filename=parsed_query.get('png_filename')), http_code)
if __name__ == "__main__":
import doctest
doctest.testmod()

View file

@ -1,53 +0,0 @@
## Remaining Components
### 1. Location Resolver (Critical)
Currently we only support coordinates. Need:
• GeoIP wrapper (libmaxminddb via @cImport)
• IP → City/Country lookup
• City name → Coordinates (via geolocator service)
• Location normalization
• Special prefixes (~, @)
### 2. Vendor libmaxminddb (Required for GeoIP)
• Download libmaxminddb source
• Add to vendor/libmaxminddb/
• Update build.zig to compile it
• Wrap in clean Zig interface
### 3. Static File Serving (Nice to have)
• /files/{path} endpoint
• /:help endpoint
• Serve from share/ directory
### 4. Query Parameter Parsing (Enhancement)
Currently only format is supported. Need:
• Single-letter options (T, n, m, u, etc.)
• Days parameter (0-3)
• Imperial/metric units
### 5. Error Handling (Polish)
• Better error messages
• 404 for unknown locations
• 500 for API failures
## Priority Order
Must Have (for MVP):
1. Location Resolver with GeoIP - Without this, only coordinates work
2. Vendor libmaxminddb - Required for #1
Should Have:
3. Query parameter parsing - Makes it feature-complete
4. Static file serving - For help pages
Nice to Have:
5. Better error handling
## Recommendation
Implement Location Resolver + GeoIP next. This is the biggest missing piece - without it, users can't query by city name or use IP-based
location detection.
Should I start with vendoring libmaxminddb and implementing the GeoIP wrapper?
[default] > yes. I'm concerned you're going to screw this up. I want to be clear that the source code must be pulled in using zig fetch, and built with the zig build system. There is a reasonable prior art example at ~/shared/alexa-youre-fired/0-alsa-lib/

View file

@ -1,25 +0,0 @@
flask
geoip2
geopy
requests
gevent
dnspython
pylint
cyrtranslit
astral
timezonefinder==2.1.2
pytz
pyte
python-dateutil
diagram
pyjq
scipy
numpy
pillow
babel
pylru
pysocks
supervisor
numba
emoji
grapheme

View file

@ -1,51 +0,0 @@
Msk : Moscow
Moskva : Moscow
Moskau : Moscow
Kyiv : Kiev,Ukraine
Kiew : Kiev,Ukraine
Kiev : Kiev,Ukraine
Kijev : Kiev
Kharkov : Kharkiv
spb : Saint Petersburg
Dnipro : Dnipropetrovsk
st.petersburg: Saint Petersburg
sanfrancisco: San Francisco
san fransisco:San Francisco
San+Francisco:San Francisco
San-Francisco:San Francisco
Dnipropetrovsk:Dnepropetrovsk
Dnepr : Dnipropetrovsk
sanjose : San Jose
san josé : San Jose
newyork : New York
newy-yourk : New York
new-york : New York
Saint-Petersburg: Saint Petersburg
Krakow : Krakow,pl
indonesia : Jakarta
hanoi,vietnam:Hanoi
nuernberg : Nuremberg
nürnberg : Nuremberg
norway : Oslo
ville de bruxelles - stad brussel: Brussels
frankfurt am main:Frankfurt, Hessen
frankfurt :Frankfurt, Hessen
frankfurt oder : Frankfurt, Brandenburg
frankfurt (oder): Frankfurt, Brandenburg
tel-aviv : Tel Aviv
sao paulo : São Paulo
los-angeles : Los Angeles
Sevastopol : Sevastopol, Ukraine
Simferopol : Simferopol, Ukraine
Beersheva : Beersheba
Be'ersheva : Beersheba
Be'er Sheva : Beersheba
Lugansk : Luhansk
Bjalistoko : Białystok
Chicago : Chicago,IL
Paris : Paris,France
Giessen : Giessen, Germany
Braga : Braga, Portugal
Kashan : ~Kashan,Iran
Baku : Baku,Az
Rome : Rome, Italia

View file

@ -1,499 +0,0 @@
#!/bin/sh
# Convert ANSI (terminal) colours and attributes to HTML
# Licence: LGPLv2
# Author:
# http://www.pixelbeat.org/docs/terminal_colours/
# Examples:
# ls -l --color=always | ansi2html.sh > ls.html
# git show --color | ansi2html.sh > last_change.html
# Generally one can use the `script` util to capture full terminal output.
# Changes:
# V0.1, 24 Apr 2008, Initial release
# V0.2, 01 Jan 2009, Phil Harnish <philharnish@gmail.com>
# Support `git diff --color` output by
# matching ANSI codes that specify only
# bold or background colour.
# P@draigBrady.com
# Support `ls --color` output by stripping
# redundant leading 0s from ANSI codes.
# Support `grep --color=always` by stripping
# unhandled ANSI codes (specifically ^[[K).
# V0.3, 20 Mar 2009, http://eexpress.blog.ubuntu.org.cn/
# Remove cat -v usage which mangled non ascii input.
# Cleanup regular expressions used.
# Support other attributes like reverse, ...
# P@draigBrady.com
# Correctly nest <span> tags (even across lines).
# Add a command line option to use a dark background.
# Strip more terminal control codes.
# V0.4, 17 Sep 2009, P@draigBrady.com
# Handle codes with combined attributes and color.
# Handle isolated <bold> attributes with css.
# Strip more terminal control codes.
# V0.23, 22 Dec 2015
# http://github.com/pixelb/scripts/commits/master/scripts/ansi2html.sh
gawk --version >/dev/null || exit 1
if [ "$1" = "--version" ]; then
printf '0.22\n' && exit
fi
if [ "$1" = "--help" ]; then
printf '%s\n' \
'This utility converts ANSI codes in data passed to stdin
It has 2 optional parameters:
--bg=dark --palette=linux|solarized|tango|xterm
E.g.: ls -l --color=always | ansi2html.sh --bg=dark > ls.html' >&2
exit
fi
[ "$1" = "--bg=dark" ] && { dark_bg=yes; shift; }
if [ "$1" = "--palette=solarized" ]; then
# See http://ethanschoonover.com/solarized
P0=073642; P1=D30102; P2=859900; P3=B58900;
P4=268BD2; P5=D33682; P6=2AA198; P7=EEE8D5;
P8=002B36; P9=CB4B16; P10=586E75; P11=657B83;
P12=839496; P13=6C71C4; P14=93A1A1; P15=FDF6E3;
shift;
elif [ "$1" = "--palette=solarized-xterm" ]; then
# Above mapped onto the xterm 256 color palette
P0=262626; P1=AF0000; P2=5F8700; P3=AF8700;
P4=0087FF; P5=AF005F; P6=00AFAF; P7=E4E4E4;
P8=1C1C1C; P9=D75F00; P10=585858; P11=626262;
P12=808080; P13=5F5FAF; P14=8A8A8A; P15=FFFFD7;
shift;
elif [ "$1" = "--palette=tango" ]; then
# Gnome default
P0=000000; P1=CC0000; P2=4E9A06; P3=C4A000;
P4=3465A4; P5=75507B; P6=06989A; P7=D3D7CF;
P8=555753; P9=EF2929; P10=8AE234; P11=FCE94F;
P12=729FCF; P13=AD7FA8; P14=34E2E2; P15=EEEEEC;
shift;
elif [ "$1" = "--palette=xterm" ]; then
P0=000000; P1=CD0000; P2=00CD00; P3=CDCD00;
P4=0000EE; P5=CD00CD; P6=00CDCD; P7=E5E5E5;
P8=7F7F7F; P9=FF0000; P10=00FF00; P11=FFFF00;
P12=5C5CFF; P13=FF00FF; P14=00FFFF; P15=FFFFFF;
shift;
else # linux console
P0=000000; P1=AA0000; P2=00AA00; P3=AA5500;
P4=0000AA; P5=AA00AA; P6=00AAAA; P7=AAAAAA;
P8=555555; P9=FF5555; P10=55FF55; P11=FFFF55;
P12=5555FF; P13=FF55FF; P14=55FFFF; P15=FFFFFF;
[ "$1" = "--palette=linux" ] && shift
fi
[ "$1" = "--bg=dark" ] && { dark_bg=yes; shift; }
# Mac OSX's GNU sed is installed as gsed
# use e.g. homebrew 'gnu-sed' to get it
if ! sed --version >/dev/null 2>&1; then
if gsed --version >/dev/null 2>&1; then
alias sed=gsed
else
echo "Error, can't find an acceptable GNU sed." >&2
exit 1
fi
fi
printf '%s' "<html>
<head>
<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
<link rel=\"stylesheet\" type=\"text/css\" href=\"https://adobe-fonts.github.io/source-code-pro/source-code-pro.css\">
<link rel=\"stylesheet\" type=\"text/css\" href=\"/files/style.css\" />
<style type=\"text/css\">
.ef0,.f0 { color: #$P0; } .eb0,.b0 { background-color: #$P0; }
.ef1,.f1 { color: #$P1; } .eb1,.b1 { background-color: #$P1; }
.ef2,.f2 { color: #$P2; } .eb2,.b2 { background-color: #$P2; }
.ef3,.f3 { color: #$P3; } .eb3,.b3 { background-color: #$P3; }
.ef4,.f4 { color: #$P4; } .eb4,.b4 { background-color: #$P4; }
.ef5,.f5 { color: #$P5; } .eb5,.b5 { background-color: #$P5; }
.ef6,.f6 { color: #$P6; } .eb6,.b6 { background-color: #$P6; }
.ef7,.f7 { color: #$P7; } .eb7,.b7 { background-color: #$P7; }
.ef8, .f0 > .bold,.bold > .f0 { color: #$P8; font-weight: normal; }
.ef9, .f1 > .bold,.bold > .f1 { color: #$P9; font-weight: normal; }
.ef10,.f2 > .bold,.bold > .f2 { color: #$P10; font-weight: normal; }
.ef11,.f3 > .bold,.bold > .f3 { color: #$P11; font-weight: normal; }
.ef12,.f4 > .bold,.bold > .f4 { color: #$P12; font-weight: normal; }
.ef13,.f5 > .bold,.bold > .f5 { color: #$P13; font-weight: normal; }
.ef14,.f6 > .bold,.bold > .f6 { color: #$P14; font-weight: normal; }
.ef15,.f7 > .bold,.bold > .f7 { color: #$P15; font-weight: normal; }
.eb8 { background-color: #$P8; }
.eb9 { background-color: #$P9; }
.eb10 { background-color: #$P10; }
.eb11 { background-color: #$P11; }
.eb12 { background-color: #$P12; }
.eb13 { background-color: #$P13; }
.eb14 { background-color: #$P14; }
.eb15 { background-color: #$P15; }
"
# The default xterm 256 colour palette
for red in 0 1 2 3 4 5 ; do
for green in 0 1 2 3 4 5 ; do
for blue in 0 1 2 3 4 5 ; do
c=$((16 + ($red * 36) + ($green * 6) + $blue))
r=$((($red * 40 + 55) * ($red > 0)))
g=$((($green * 40 + 55) * ($green > 0)))
b=$((($blue * 40 + 55) * ($blue > 0)))
printf ".ef%d { color: #%2.2x%2.2x%2.2x; } " $c $r $g $b
printf ".eb%d { background-color: #%2.2x%2.2x%2.2x; }\n" $c $r $g $b
done
done
done
for gray in $(seq 0 23); do
c=$(($gray+232))
l=$(($gray*10 + 8))
printf ".ef%d { color: #%2.2x%2.2x%2.2x; } " $c $l $l $l
printf ".eb%d { background-color: #%2.2x%2.2x%2.2x; }\n" $c $l $l $l
done
printf '%s' '
.f9 { color: '`[ "$dark_bg" ] && printf "#$P7;" || printf "#$P0;"`' }
.b9 { background-color: #'`[ "$dark_bg" ] && printf $P0 || printf $P15`'; }
.f9 > .bold,.bold > .f9, body.f9 > pre > .bold {
/* Bold is heavy black on white, or bright white
depending on the default background */
color: '`[ "$dark_bg" ] && printf "#$P15;" || printf "#$P0;"`'
font-weight: '`[ "$dark_bg" ] && printf 'normal;' || printf 'bold;'`'
}
.reverse {
/* CSS does not support swapping fg and bg colours unfortunately,
so just hardcode something that will look OK on all backgrounds. */
'"color: #$P0; background-color: #$P7;"'
}
.underline { text-decoration: underline; }
.line-through { text-decoration: line-through; }
.blink { text-decoration: blink; }
/* Avoid pixels between adjacent span elements.
Note this only works for lines less than 80 chars
where we close span elements on the same line.
span { display: inline-block; }
*/
</style>
</head>
<body class="">
<pre>
'
p='\x1b\[' #shortcut to match escape codes
# Handle various xterm control sequences.
# See /usr/share/doc/xterm-*/ctlseqs.txt
sed "
# escape ampersand and quote
s#&#\&amp;#g; s#\"#\&quot;#g;
s#\x1b[^\x1b]*\x1b\\\##g # strip anything between \e and ST
s#\x1b][0-9]*;[^\a]*\a##g # strip any OSC (xterm title etc.)
s#\r\$## # strip trailing \r
# strip other non SGR escape sequences
s#[\x07]##g
s#\x1b[]>=\][0-9;]*##g
s#\x1bP+.\{5\}##g
# Mark cursor positioning codes \"Jr;c;
s#${p}\([0-9]\{1,2\}\)G#\"J;\1;#g
s#${p}\([0-9]\{1,2\}\);\([0-9]\{1,2\}\)H#\"J\1;\2;#g
# Mark clear as \"Cn where n=1 is screen and n=0 is to end-of-line
s#${p}H#\"C1;#g
s#${p}K#\"C0;#g
# Mark Cursor move columns as \"Mn where n is +ve for right, -ve for left
s#${p}C#\"M1;#g
s#${p}\([0-9]\{1,\}\)C#\"M\1;#g
s#${p}\([0-9]\{1,\}\)D#\"M-\1;#g
s#${p}\([0-9]\{1,\}\)P#\"X\1;#g
s#${p}[0-9;?]*[^0-9;?m]##g
" |
# Normalize the input before transformation
sed "
# escape HTML (ampersand and quote done above)
s#>#\&gt;#g; s#<#\&lt;#g;
# normalize SGR codes a little
# split 256 colors out and mark so that they're not
# recognised by the following 'split combined' line
:e
s#${p}\([0-9;]\{1,\}\);\([34]8;5;[0-9]\{1,3\}\)m#${p}\1m${p}¬\2m#g; t e
s#${p}\([34]8;5;[0-9]\{1,3\}\)m#${p}¬\1m#g;
:c
s#${p}\([0-9]\{1,\}\);\([0-9;]\{1,\}\)m#${p}\1m${p}\2m#g; t c # split combined
s#${p}0\([0-7]\)#${p}\1#g #strip leading 0
s#${p}1m\(\(${p}[4579]m\)*\)#\1${p}1m#g #bold last (with clr)
s#${p}m#${p}0m#g #add leading 0 to norm
# undo any 256 color marking
s#${p}¬\([34]8;5;[0-9]\{1,3\}\)m#${p}\1m#g;
# map 16 color codes to color + bold
s#${p}9\([0-7]\)m#${p}3\1m${p}1m#g;
s#${p}10\([0-7]\)m#${p}4\1m${p}1m#g;
# change 'reset' code to \"R
s#${p}0m#\"R;#g
" |
# Convert SGR sequences to HTML
sed "
# common combinations to minimise html (optional)
:f
s#${p}3[0-7]m${p}3\([0-7]\)m#${p}3\1m#g; t f
:b
s#${p}4[0-7]m${p}4\([0-7]\)m#${p}4\1m#g; t b
s#${p}3\([0-7]\)m${p}4\([0-7]\)m#<span class=\"f\1 b\2\">#g
s#${p}4\([0-7]\)m${p}3\([0-7]\)m#<span class=\"f\2 b\1\">#g
s#${p}1m#<span class=\"bold\">#g
s#${p}4m#<span class=\"underline\">#g
s#${p}5m#<span class=\"blink\">#g
s#${p}7m#<span class=\"reverse\">#g
s#${p}9m#<span class=\"line-through\">#g
s#${p}3\([0-9]\)m#<span class=\"f\1\">#g
s#${p}4\([0-9]\)m#<span class=\"b\1\">#g
s#${p}38;5;\([0-9]\{1,3\}\)m#<span class=\"ef\1\">#g
s#${p}48;5;\([0-9]\{1,3\}\)m#<span class=\"eb\1\">#g
s#${p}[0-9;]*m##g # strip unhandled codes
" |
# Convert alternative character set and handle cursor movement codes
# Note we convert here, as if we do at start we have to worry about avoiding
# conversion of SGR codes etc., whereas doing here we only have to
# avoid conversions of stuff between &...; or <...>
#
# Note we could use sed to do this based around:
# sed 'y/abcdefghijklmnopqrstuvwxyz{}`~/▒␉␌␍␊°±␤␋┘┐┌└┼⎺⎻─⎼⎽├┤┴┬│≤≥π£◆·/'
# However that would be very awkward as we need to only conv some input.
# The basic scheme that we do in the awk script below is:
# 1. enable transliterate once "T1; is seen
# 2. disable once "T0; is seen (may be on diff line)
# 3. never transliterate between &; or <> chars
# 4. track x,y movements and active display mode at each position
# 5. buffer line/screen and dump when required
sed "
# change 'smacs' and 'rmacs' to \"T1 and \"T0 to simplify matching.
s#\x1b(0#\"T1;#g;
s#\x0E#\"T1;#g;
s#\x1b(B#\"T0;#g
s#\x0F#\"T0;#g
" |
(
gawk '
function dump_line(l,del,c,blanks,ret) {
for(c=1;c<maxX;c++) {
if ((c SUBSEP l) in attr || length(cur)) {
ret = ret blanks fixas(cur,attr[c,l])
if(del) delete attr[c,l]
blanks=""
}
if ((c SUBSEP l) in dump) {
ret=ret blanks dump[c,l]
if(del) delete dump[c,l]
blanks=""
} else blanks=blanks " "
}
if(length(cur)) ret=ret blanks
return ret
}
function dump_screen(l,ret) {
for(l=1;l<=maxY;l++)
ret=ret dump_line(l,0) "\n"
return ret fixas(cur, "")
}
function atos(a,i,ret) {
for(i=1;i<=length(a);i++) if(i in a) ret=ret a[i]
return ret
}
function fixas(a,s,spc,i,attr,rm,ret) {
spc=length(a)
l=split(s,attr,">")
for(i=1;i<=spc;i++) {
rm=rm?rm:(a[i]!=attr[i]">")
if(rm) {
ret=ret "</span>"
delete a[i];
}
}
for(i=1;i<l;i++) {
attr[i]=attr[i]">"
if(a[i]!=attr[i]) {
a[i]=attr[i]
ret = ret attr[i]
}
}
return ret
}
function encode(string,start,end,i,ret,pos,sc,buf) {
if(!end) end=length(string);
if(!start) start=1;
state=3
for(i=1;i<=length(string);i++) {
c=substr(string,i,1)
if(state==2) {
sc=sc c
if(c==";") {
c=sc
state=last_mode
} else continue
} else {
if(c=="\r") { x=1; continue }
if(c=="<") {
# Change attributes - store current active
# attributes in span array
split(substr(string,i),cord,">");
i+=length(cord[1])
span[++spc]=cord[1] ">"
continue
}
else if(c=="&") {
# All goes to single position till we see a semicolon
sc=c
state=2
continue
}
else if(c=="\b") {
# backspace move insertion point back 1
if(spc) attr[x,y]=atos(span)
x=x>1?x-1:1
continue
}
else if(c=="\"") {
split(substr(string,i+2),cord,";")
cc=substr(string,i+1,1);
if(cc=="T") {
# Transliterate on/off
if(cord[1]==1&&state==3) last_mode=state=4
if(cord[1]==0&&state==4) last_mode=state=3
}
else if(cc=="C") {
# Clear
if(cord[1]+0) {
# Screen - if Recording dump screen
if(dumpStatus==dsActive) ret=ret dump_screen()
dumpStatus=dsActive
delete dump
delete attr
x=y=1
} else {
# To end of line
for(pos=x;pos<maxX;pos++) {
dump[pos,y]=" "
if (!spc) delete attr[pos,y]
else attr[pos,y]=atos(span)
}
}
}
else if(cc=="J") {
# Jump to x,y
i+=length(cord[2])+1
# If line is higher - dump previous screen
if(dumpStatus==dsActive&&cord[1]<y) {
ret=ret dump_screen();
dumpStatus=dsNew;
}
x=cord[2]
if(length(cord[1]) && y!=cord[1]){
y=cord[1]
if(y>maxY) maxY=y
# Change y - start recording
dumpStatus=dumpStatus?dumpStatus:dsReset
}
}
else if(cc=="M") {
# Move left/right on current line
x+=cord[1]
}
else if(cc=="X") {
# delete on right
for(pos=x;pos<=maxX;pos++) {
nx=pos+cord[1]
if(nx<maxX) {
if((nx SUBSEP y) in attr) attr[pos,y] = attr[nx,y]
else delete attr[pos,y]
if((nx SUBSEP y) in dump) dump[pos,y] = dump[nx,y]
else delete dump[pos,y]
} else if(spc) {
attr[pos,y]=atos(span)
dump[pos,y]=" "
}
}
}
else if(cc=="R") {
# Reset attributes
while(spc) delete span[spc--]
}
i+=length(cord[1])+2
continue
}
else if(state==4&&i>=start&&i<=end&&c in Trans) c=Trans[c]
}
if(dumpStatus==dsReset) {
delete dump
delete attr
ret=ret"\n"
dumpStatus=dsActive
}
if(dumpStatus==dsNew) {
# After moving/clearing we are now ready to write
# somthing to the screen so start recording now
ret=ret"\n"
dumpStatus=dsActive
}
if(dumpStatus==dsActive||dumpStatus==dsOff) {
dump[x,y] = c
if(!spc) delete attr[x,y]
else attr[x,y] = atos(span)
if(++x>maxX) maxX=x;
}
}
# End of line if dumping increment y and set x back to first col
x=1
if(!dumpStatus) return ret dump_line(y,1);
else if(++y>maxY) maxY=y;
return ret
}
BEGIN{
OFS=FS
# dump screen status
dsOff=0 # Not dumping screen contents just write output direct
dsNew=1 # Just after move/clear waiting for activity to start recording
dsReset=2 # Screen cleared build new empty buffer and record
dsActive=3 # Currently recording
F="abcdefghijklmnopqrstuvwxyz{}`~"
T="▒␉␌␍␊°±␤␋┘┐┌└┼⎺⎻─⎼⎽├┤┴┬│≤≥π£◆·"
maxX=80
delete cur;
x=y=1
for(i=1;i<=length(F);i++)Trans[substr(F,i,1)]=substr(T,i,1);
}
{ $0=encode($0) }
1
END {
if(dumpStatus) {
print dump_screen();
}
}'
)
printf '</pre>
</body>
</html>\n'

View file

@ -1,7 +0,0 @@
wttr()
{
# change Paris to your default location
local request="wttr.in/${1-Paris}"
[ "$(tput cols)" -lt 125 ] && request+='?n'
curl -H "Accept-Language: ${LANG%_*}" --compressed "$request"
}

View file

@ -1,5 +0,0 @@
NOT_FOUND
apple-touch-icon.png
apple-touch-icon-precomposed.png
apple-touch-icon-152x152-precomposed.png

View file

@ -1,22 +0,0 @@
[supervisord]
nodaemon=true
logfile=/var/log/supervisor/supervisord.log
pidfile=/var/run/supervisord.pid
[program:srv]
command=python3 /app/bin/srv.py
stderr_logfile=/var/log/supervisor/srv-stderr.log
stdout_logfile=/var/log/supervisor/srv-stdout.log
[program:proxy]
command=python3 /app/bin/proxy.py
stderr_logfile=/var/log/supervisor/proxy-stderr.log
stdout_logfile=/var/log/supervisor/proxy-stdout.log
[program:geoproxy]
command=python3 /app/bin/geo-proxy.py
stderr_logfile=/var/log/supervisor/geoproxy-stderr.log
stdout_logfile=/var/log/supervisor/geoproxy-stdout.log
[include]
files=/etc/supervisor/conf.d/*.conf

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 725 B

View file

@ -1,69 +0,0 @@
Usage:
$ curl wttr.in # current location
$ curl wttr.in/muc # weather in the Munich airport
Supported location types:
/paris # city name
/~Eiffel+tower # any location (+ for spaces)
/Москва # Unicode name of any location in any language
/muc # airport code (3 letters)
/@stackoverflow.com # domain name
/94107 # area codes
/-78.46,106.79 # GPS coordinates
Special locations:
/moon # Moon phase (add ,+US or ,+France for these cities)
/moon@2016-10-25 # Moon phase for the date (@2016-10-25)
Units:
m # metric (SI) (used by default everywhere except US)
u # USCS (used by default in US)
M # show wind speed in m/s
View options:
0 # only current weather
1 # current weather + today's forecast
2 # current weather + today's + tomorrow's forecast
A # ignore User-Agent and force ANSI output format (terminal)
F # do not show the "Follow" line
n # narrow version (only day and night)
q # quiet version (no "Weather report" text)
Q # superquiet version (no "Weather report", no city name)
T # switch terminal sequences off (no colors)
PNG options:
/paris.png # generate a PNG file
p # add frame around the output
t # transparency 150
transparency=... # transparency from 0 to 255 (255 = not transparent)
Options can be combined:
/Paris?0pq
/Paris?0pq&lang=fr
/Paris_0pq.png # in PNG the file mode are specified after _
/Rome_0pq_lang=it.png # long options are separated with underscore
Localization:
$ curl fr.wttr.in/Paris
$ curl wttr.in/paris?lang=fr
$ curl -H "Accept-Language: fr" wttr.in/paris
Supported languages:
FULL_TRANSLATION (supported)
PARTIAL_TRANSLATION (in progress)
Special URLs:
/:help # show this page
/:bash.function # show recommended bash function wttr()
/:translation # show the information about the translators

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -1,11 +0,0 @@
# Opinionated example of deployment via Salt Stack
## Assumptions:
* user & group srv:srv exist, this is used as a generic service runner
* you want to run the service on port 80, directly exposed to the interwebs (you really want to add a reverse SSL proxy in between)
* You have, or are willing to deploy Salt Stack.
* A bit of assembly is required since you need to move pillar.sls into your saltroot/pillar/ and the rest into saltroot/wttr/
* You want metric-sm units. Just roll your own wegorc to change this
## Caveats:
* Doesn't do enough to make a recent master checkout work, i.e. needs further improvement. Latest known working revision is 0d76ba4a3e112694665af6653040807835883b22

View file

@ -1,112 +0,0 @@
wttr:
service.running:
- enable: True
- watch:
- file: /srv/ephemeral/start.sh
- git: wttr-repo
- require:
- pkg: wttr-dependencies
- git: wttr-repo
- cmd: wego
- archive: geolite-db
# package names are from Ubuntu 18.04, you may need to adjust if on a different distribution
wttr-dependencies:
pkg.installed:
- pkgs:
- golang
- gawk
- python-setuptools
- python-dev
- python-dnspython
- python-geoip2
- python-geopy
- python-gevent
- python-flask
- python-pil
- authbind
wttr-repo:
git.latest:
- name: https://github.com/chubin/wttr.in
- rev: master
- target: /srv/ephemeral/wttr.in
- require:
- /srv/ephemeral
wttr-start:
file.managed:
- name: /srv/ephemeral/start.sh
- source: salt://wttr/start.sh
- mode: '0770'
- user: srv
- group: srv
wegorc:
file.managed:
- name: /srv/ephemeral/.wegorc
- user: srv
- group: srv
- source: salt://wttr/wegorc
- template: jinja
- context:
apikey: {{ pillar['wttr']['apikey'] }}
geolite-db:
archive.extracted:
- name: /srv/ephemeral
- source: http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz
- source_hash: http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz.md5
- keep_source: True
- options: --strip-components=1 # flatten directory structure
- enforce_toplevel: False
# Could benefit from improvement, won't get updated automatically at all
wego:
cmd.run:
- onlyif: 'test ! -e /srv/ephemeral/bin/wego'
- env:
- GOPATH: /srv/ephemeral
- name: go get -u github.com/schachmat/wego && go install github.com/schachmat/wego
- cwd: /srv/ephemeral/
- require:
- pkg: wttr-dependencies
- file: wegorc
/srv/ephemeral:
file.directory:
- makedirs: True
{% for dir in '/srv/ephemeral/wttr.in/log','/srv/ephemeral/wttr.in/cache' %}
{{ dir }}:
file.directory:
- user: srv
- group: srv
- makedirs: True
- recurse:
- user
- group
- require_in:
- service: wttr
{% endfor %}
/etc/systemd/system/wttr.service:
file:
- managed
- source: salt://wttr/wttr.service
- require:
- file: wttr-start
- file: authbind-80
- require_in:
- service: wttr
authbind-80:
file:
- managed
- name: /etc/authbind/byport/80
- user: srv
- group: srv
- mode: 770
- replace: False
- require:
- pkg: wttr-dependencies

View file

@ -1,2 +0,0 @@
wttr:
apikey: insert-api-key-here-and-make-this-pillar-available-to-salt

View file

@ -1,12 +0,0 @@
#!/bin/sh
export WEGORC="/srv/ephemeral/.wegorc"
export GOPATH="/srv/ephemeral"
export WTTR_MYDIR="/srv/ephemeral/wttr.in"
export WTTR_GEOLITE="/srv/ephemeral/GeoLite2-City.mmdb"
export WTTR_WEGO="$GOPATH/bin/wego"
export WTTR_LISTEN_HOST="0.0.0.0"
export WTTR_LISTEN_PORT="80"
python $WTTR_MYDIR/bin/srv.py

View file

@ -1,58 +0,0 @@
# wego configuration
#
# This config has https://github.com/schachmat/ingo syntax.
# Empty lines or lines starting with # will be ignored.
# All other lines must look like "KEY=VALUE" (without the quotes).
# The VALUE must not be enclosed in quotes as well!
# aat-frontend: Show geo coordinates (default false)
aat-coords=false
# aat-frontend: Monochrome output (default false)
aat-monochrome=false
# BACKEND to be used (default forecast.io)
backend=forecast.io
# NUMBER of days of weather forecast to be displayed (default 3)
days=3
# forecast backend: the api KEY to use (default )
forecast-api-key={{ apikey }}
# forecast backend: print raw requests and responses (default false)
forecast-debug=false
# forecast backend: the LANGUAGE to request from forecast.io (default en)
forecast-lang=en
# FRONTEND to be used (default ascii-art-table)
frontend=ascii-art-table
# json frontend: do not indent the output (default false)
jsn-no-indent=false
# LOCATION to be queried (default 40.748,-73.985)
location=40.748,-73.985
# openweathermap backend: the api KEY to use (default )
owm-api-key=
# openweathermap backend: print raw requests and responses (default false)
owm-debug=false
# openweathermap backend: the LANGUAGE to request from openweathermap (default en)
owm-lang=en
# UNITSYSTEM to use for output.
# Choices are: metric, imperial, si, metric-ms (default metric)
units=metric-ms
# worldweatheronline backend: the api KEY to use (default )
wwo-api-key=
# worldweatheronline backend: print raw requests and responses (default false)
wwo-debug=false
# worldweatheronline backend: the LANGUAGE to request from worldweatheronline (default en)
wwo-lang=en

View file

@ -1,9 +0,0 @@
[Unit]
Description=Wttr weather service
[Service]
ExecStart=/usr/bin/authbind --deep /srv/ephemeral/start.sh
Restart=always
[Install]
WantedBy=multi-user.target

View file

@ -1,2 +0,0 @@
screen -t srv.py bash -c "cd ~/wttr.in; ve/bin/python bin/srv.py; bash -i"
screen -t proxy.py bash -c "cd ~/wttr.in; ve/bin/python bin/proxy.py; bash -i"

View file

@ -1,15 +0,0 @@
#!/bin/bash
CACHEDIR="/wttr.in/cache"
for dir in wego proxy-wwo png
do
mv "${CACHEDIR}/${dir}" "${CACHEDIR}/${dir}.old"
mkdir "${CACHEDIR}/${dir}"
rm -rf "${CACHEDIR}/${dir}.old"
done
cd /wttr.in/log
mv main.log main.log.1
touch main.log

View file

@ -1,7 +0,0 @@
#!/bin/bash
SESSION_NAME=wttr.in
SCREENRC_PATH=$(dirname $(dirname "$0"))/screenrc
screen -dmS "$SESSION_NAME" -c "$SCREENRC_PATH"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

View file

@ -1,24 +0,0 @@
<html>
<title>wttr.in</title>
<head>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</head>
<body style='background:black'>
<pre style='color:#cccccc'>
Sorry, we processed more than 1M requests today and we ran out of our datasource capacity.
We hope to solve the problem as soon as possible, so you can enjoy
your favourite weather service 24x365 even if it rains or snows.
We will solve the problem as soon as possible.
<a href="https://twitter.com/igor_chubin" class="twitter-follow-button" data-show-count="false" data-button="grey">Follow @igor_chubin</a> <script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script><span style='position: relative; bottom: 6px;'>for the updates. </span>
If you like to code (and you surely do), you can check the <span style='position: relative; bottom: -6px;'><a aria-label="Star chubin/wttr.in on GitHub" data-count-aria-label="# stargazers on GitHub" data-count-api="/repos/chubin/wttr.in#stargazers_count" data-count-href="/chubin/wttr.in/stargazers" data-icon="octicon-star" href="https://github.com/chubin/wttr.in" class="github-button">wttr.in</a></span> repository
to see how the scalability problem is (not yet) solved.
</pre>
<blockquote class="twitter-tweet" data-theme="dark" data-lang="en"><p lang="en" dir="ltr">How do you check the weather? curl wttr.in — Sure thing! <a href="https://twitter.com/hashtag/wttrin?src=hash">#wttrin</a> <a href="https://t.co/mgYzW2ajyq">pic.twitter.com/mgYzW2ajyq</a></p>&mdash; Igor Chubin (@igor_chubin) <a href="https://twitter.com/igor_chubin/status/700846126467944448">February 20, 2016</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
<script async defer id="github-bjs" src="https://buttons.github.io/buttons.js"></script>
</body>
</html>

View file

@ -1,18 +0,0 @@
body {
background: black;
color: #bbbbbb;
}
pre {
/* font-family: source_code_proregular; */
/*
font-family: Courier New,Courier,Lucida Sans Typewriter,Lucida Typewriter,monospace;
font-size: 70%;
*/
/*font-family: Lucida Console,Lucida Sans Typewriter,monaco,Bitstream Vera Sans Mono,monospace; */
/*Droid Sans Mono*/
font-family: "Source Code Pro", "DejaVu Sans Mono", Menlo, "Lucida Sans Typewriter", "Lucida Console", monaco, "Bitstream Vera Sans Mono", monospace;
/*font-family: bitstream_vera_sans_monoroman;*/
font-size: 75%;
}

View file

@ -1,11 +0,0 @@
<html>
<head>
<link rel="stylesheet" type="text/css" href="https://adobe-fonts.github.io/source-code-pro/source-code-pro.css">
<link rel="stylesheet" type="text/css" href="/files/style.css" />
</head>
<body>
<pre>
{{ body }}
</pre>
</body>
</html>

View file

@ -1,37 +0,0 @@
Prévisions météo pour: Test-Milon, France
 _`/"".-.  Averse de pluie légère
 ,\_( ).  17 °C
 /(___(__)  ↙ 5 km/h
  14 km
  3.6 mm
┌─────────────┐
┌──────────────────────────────┬───────────────────────┤ sam. 09 juin├───────────────────────┬──────────────────────────────┐
│ Matin │ Après-midi └──────┬──────┘ Soir │ Nuit │
├──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤
│ Brume │ Nuageux │  _`/"".-.  Foyers orageux │  _`/"".-.  Averse de plui…│
 _ - _ - _ -  21 °C │  .--.  25-26 °C │  ,\_( ).  25-27 °C │  ,\_( ).  22 °C │
 _ - _ - _  ← 5-6 km/h │  .-( ).  ← 4-5 km/h │  /(___(__)  ↑ 4-6 km/h │  /(___(__)  → 5-9 km/h │
 _ - _ - _ -  19 km │  (___.__)__)  18 km │  ⚡⚡  16 km │   15 km │
│ 0.0 mm | 0% │ 0.0 mm | 0% │   0.7 mm | 46% │   2.4 mm | 82% │
└──────────────────────────────┴──────────────────────────────┴──────────────────────────────┴──────────────────────────────┘
┌─────────────┐
┌──────────────────────────────┬───────────────────────┤ dim. 10 juin├───────────────────────┬──────────────────────────────┐
│ Matin │ Après-midi └──────┬──────┘ Soir │ Nuit │
├──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤
 \ / Partiellement …│  \ / Partiellement …│  _`/"".-.  Averse de plui…│  _`/"".-.  Averse de plui…│
 _ /"".-.  22-24 °C │  _ /"".-.  25-27 °C │  ,\_( ).  24-26 °C │  ,\_( ).  21 °C │
 \_( ).  ↙ 9-10 km/h │  \_( ).  ↙ 13-19 km/h │  /(___(__)  ↙ 17-30 km/h │  /(___(__)  ↙ 15-31 km/h │
 /(___(__)  20 km │  /(___(__)  20 km │   17 km │   18 km │
│ 0.0 mm | 0% │ 1.8 mm | 56% │   4.1 mm | 82% │   1.6 mm | 72% │
└──────────────────────────────┴──────────────────────────────┴──────────────────────────────┴──────────────────────────────┘
┌─────────────┐
┌──────────────────────────────┬───────────────────────┤ lun. 11 juin├───────────────────────┬──────────────────────────────┐
│ Matin │ Après-midi └──────┬──────┘ Soir │ Nuit │
├──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤
 _`/"".-.  Pluie éparse à │ Nuageux │  _`/"".-.  Averse de plui…│  _`/"".-.  Averse de plui…│
 ,\_( ).  21 °C │  .--.  24-26 °C │  ,\_( ).  23-25 °C │  ,\_( ).  20 °C │
 /(___(__)  ↑ 14-23 km/h │  .-( ).  ↑ 18-22 km/h │  /(___(__)  ↗ 13-23 km/h │  /(___(__)  ↗ 10-21 km/h │
  18 km │  (___.__)__)  18 km │   14 km │   14 km │
  0.1 mm | 22% │ 0.0 mm | 0% │   5.2 mm | 73% │   5.3 mm | 72% │
└──────────────────────────────┴──────────────────────────────┴──────────────────────────────┴──────────────────────────────┘

View file

@ -1,76 +0,0 @@
wttr.in is translated in NUMBER_OF_LANGUAGES languages:
SUPPORTED_LANGUAGES
Translated/improved/corrected by:
* Afrikaans: Casper Labuschage @casperl (on github)
* Arabic Besher Aladdam @akai54 (on github)
* Armenian: Aram Bayadyan @aramix, Mikayel Ghazaryan @mkdotam,
Grigor Khachatryan @grigortw
* Azerbaijani: Dmytro Nikitiuk, Elsevar Abbasov,
Eldar Velibekov (@welibekov on github)
* Basque: Iker Sagasti (@isagasti on github)
* Belarusian: Igor Chubin, Anton Zhavoronkov @edogby (on github)
* Bosnian: Ismar Kunc @ismarkunc
* Bulgarian: Vladimir Vitkov @zeridon (on github)
* Brazilian-PT: Tupã Negreiros @TupaNegreiros (on github)
* Catalan: Angel Jarabo @legna29A
* Chinese(Traditional): Jeff Huang
* Chinese(Simplified): YouthLin Chen(@YouthLin on github), Junhan (@junhan-z on github),
Felix Wong (@gnowxilef on github)
* Croatian: Siniša Kusić @ku5ic
* Czech: Juraj Kakody
* Danish: Kim Schulz @kimusan (on github)
* Dutch: Youri Claes
* Esperanto: Igor Chubin
* Estonian: Jaan Jänesmäe @janesmae (on github)
* Finnish: @Maxifi
* French: Igor Chubin, @daftaupe, @iago-lito
* Frisian: Anne Douwe Bouma @anned20 (on github)
* German: Igor Chubin, @MAGICC (https://kthx.at)
* Greek: Panayotis Vryonis and @Petewg (on github)
* Hebrew: E.R.
* Hindi: Aakash Gajjar @skyme5 (gh)
* Hungarian: Mark Petruska
* Icelandic: Óli G. @dvergur, Skúli Arnlaugsson @Arnlaugsson
* Indonesian: Andria Arisal @andria009
* Interlingua: Dustin Redmond @dustinkredmond (on github)
* Irish: Robert Devereux @RobertDev (gh), Conor O'Callaghan @ivernus (gh)
* Italian: Diego Maniacco
* Japanese: @ryunix
* Kazakh: Akku Tutkusheva, Oleg Tropinin
* Korean: Jeremy Bae @opt9, Jung Winter @res_tin
* Latvian: Gunārs Danovskis
* Macedonian: Matej Plavevski @MatejMecka
* Norwegian: Fredrik Fjeld @fredrikfjeld
* Nynorsk: Kevin Brubeck Unhammer (https://unhammer.org/k/)
* Occitan: Quentin PAGÈS @Quenty-tolosan (gh)
* Persian: Javad @threadripper_
* Polish: Wojtek Łukasiewicz @wojtuch (on github)
* Portuguese: Fernando Bitti Loureiro @fbitti (on github)
* Romanian: Gabriel Moruz
* Russian: Igor Chubin
* Serbian: Milan Stevanović @FathVader
* Slovak: Juraj Kakody
* Slovenian: B.S.
* Spanish: Fernando Bitti Loureiro @fbitti (on github)
* Swedish: John Eriksson
* Swahili: Joel Mukuthu
* Turkish: Atabey Kaygun, Yilmaz @edigu, Volkan Tokmak(@volkanto)
* Thai: Vatunyoo Suwannapisit @kerlos
* Ukrainian: Igor Chubin, Serhiy @pavse
* Uzbek: Shukhrat Mukimov
* Vietnamese: Lưu Vĩnh Phúc @phuclv90 (on github)
* Welsh: Daniel Thomas
and many many others.
In the not likely case wttr.in is not yet translated in your language
you can help with the translation.
Please start with one of the following steps:
* Open a GitHub issue: github.com/chubin/wttr.in
* Write an email to igor@chub.in
* Write PM or tweet to igor_chubin

View file

@ -1,68 +0,0 @@
Gebruik:
$ curl wttr.in # huidige ligging
$ cur wttr.in/muc # weerberig in München se lughawe
Ondersteunde soorte ligging:
/paris # stadsnaam
/~Eiffel+tower # enige ligging
/Москва # Unikode naam van enige ligging in enige taal
/muc # lughawe kode (3 letters)
/@stackoverflow.com # domeinnaam
/94107 # gebiedskodes
/-78.46,106.79 # GPS koördinate
Besondere liggings:
/moon # Fase van die maan (voeg +US of ,+France by vir daardie stede)
/moon@2016-10-25 # Fase van die maan vir die datum (@2016-10-25)
Eenhede:
m # metrike (SI) (word orals gebruik behalwe vir die VSA)
u # USCS (gebruik in die VSA)
M # vertoon die spoed van die wind in m/s
Besigtig keuses:
0 # slegs die huidige weer
1 # huidige weer + 1 dag
2 # huidige weer + 2 dae
F # moet nie die "Volg" lyn wys nie
n # smal weergawe (slegs dag en nag)
q # stil weergawe (geen "Weerberig" teks nie)
Q # baie stil weergawe (geen "Weerberig), geen stadsnaam
T # skakel terminaal kodes af (geen kleur)
PNG keuses:
/paris.png # skep 'n PNG leêr
p # voeg 'n raam by rondom die uitset
t # deursigtigheid 150
transparency=... # deursigtigheid vanaf 0 tot 255 (255 = nie deursigtig nie)
Keuses can saamgevoeg word
/Paris?0pq
/Paris?0pq&lang=fr
/Paris_0pq.png # met PNG word die lêer mode daarna gegee
/Rome_0pq_lang=it.png # lang keuses word met 'n onderstreping verdeel
Lokalisering:
$ curl fr.wttr.in/Paris
$ curl wttr.in/paris?lang=fr
$ curl -H "Accept-Language: fr" wttr.in/paris
Ondersteunde tale:
FULL_TRANSLATION (word ondersteun)
PARTIAL_TRANSLATION (in wording)
Spesiale URLe:
/:help # vertoon hierdie bladsy
/:bash.function # vertoon aanbeveling vir bash funksie wttr()
/:translation # vertoon die besonderhede van die vertalers

View file

@ -1,47 +0,0 @@
113: Helder : Clear
113: Sonnig : Sunny
116: Gedeeltelik bewolk : Partly cloudy
119: Bewolk : Cloudy
122: Oortrokke : Overcast
143: Mis toestande : Mist
176: Gedeeltelike reën moontlik : Patchy rain possible
179: Gedeeltelike sneeu moontlik : Patchy snow possible
182: Gedeeltelike ysreën moontlik : Patchy sleet possible
185: Gedeeltelik ysige motreën moontlik : Patchy freezing drizzle possible
200: Donderbuie moontlik : Thundery outbreaks possible
227: Waaiende sneeu : Blowing snow
230: Sneeustorm : Blizzard
248: Mis toestande : Fog
260: Vriesende mis toestande : Freezing fog
263: Gedeeltelike ligte motreën : Patchy light drizzle
266: Ligte motreën : Light drizzle
281: Vriesende motreën : Freezing drizzle
284: Erg vriesende motreën : Heavy freezing drizzle
293: Gedeeltelike ligte reën : Patchy light rain
296: Ligte reën : Light rain
299: Matige reën by tye : Moderate rain at times
302: Matige reën : Moderate rain
305: Swaar reën by tye : Heavy rain at times
308: Swaar reën : Heavy rain
311: Ligte vriesende reën : Light freezing rain
314: Matige tot swaar vriesende reën : Moderate or heavy freezing rain
317: Ligte ysreën : Light sleet
320: Matige tot swaar ysreën : Moderate or heavy sleet
323: Verspreide ligte sneeu : Patchy light snow
326: Ligte sneeu : Light snow
329: Verspreide ligte sneeu : Patchy moderate snow
332: Matige sneeu : Moderate snow
335: Verspreide swaar sneeu : Patchy heavy snow
338: Swaar sneeu : Heavy snow
350: Haelkorrels : Ice pellets
353: Ligte bui reën : Light rain shower
356: Matige tot swaar bui reënbui : Moderate or heavy rain shower
359: Stortreën : Torrential rain shower
362: Ligte buie ysreën : Light sleet showers
365: Ligte tot swaar buie ysreën : Moderate or heavy sleet showers
368: Ligte buie sneeu : Light snow showers
371: Ligte tot swaar buie sneeu : Moderate or heavy snow showers
386: Verspreide ligte buie en donderbuie : Patchy light rain with thunder
389: Ligte tot swaar buie en donderbuie : Moderate or heavy rain with thunder
392: Verspreide ligte sneeu met donderbuie : Patchy light snow with thunder
395: Matige of swaar sneeu met donderbuie : Moderate or heavy snow with thunder

View file

@ -1,69 +0,0 @@
الإستخدام:
$ curl wttr.in # الموقع الحالي
$ curl wttr.in/muc # الطقس في مطار ميونخ
أنواع الأماكن المدعومة:
/paris # أسم المدينة
/~Eiffel+tower # أي مكان
/Москва # أسم يونيكود ﻷي مكان بأي لغة
/muc # airport code (3 letters)
/@stackoverflow.com # أسم النطاق
/94107 # رمز المنطقة
/-78.46,106.79 # GPS إحداثيات الـ
الأماكن الخاصة:
/moon # مرحلة القمر (أضف ,+US أو ,+France لهؤلاء المدن)
/moon@2016-10-25 # مرحلة القمر بتاريخ (@2016-10-25)
الوحدات:
m # المتريّ (SI) (يستخدم في العادة في كل الأماكن ما عدا الولايات المتحدة)
u # وحدات القياس العرفية الأمريكية (يستخدم في العادة في الولايات المتحدة)
M # إظهار سرعة الرياح بوحدة م/ث
خيارات العرض:
0 # فقط الطقس الحالي
1 # الطقس الحالي + 1 يوم
2 # الطقس الحالي + 2 يوم
A # تجاهل الوكيل المستخدم وقم بإجبار تنسيق المعهد القومى الأمريكى للتنميط (الطرفية)
F # "لا تظهر سطر "المتابعة
n # النسخة الضيقة (النهار والليل فقط)
q # النسخة الصامتة (من غير عبارة "تقرير جوي")
Q # النسخة الصامتة كليا (من غير عبارة "تقرير جوي", من غير أسم المدينة)
T # إطفاء تسلسل الطرفية (من غير ألوان)
PNG خيارات:
/paris.png # png إنشاء صورة بصيغة
p # إضافة إطار حول المخرج
t # الشفافية 150
transparency=... # الشفافية من 0 إلي 255 (255 = غير شفاف)
يمكن جمع الخيارات:
/Paris?0pq
/Paris?0pq&lang=fr
/Paris_0pq.png # خيار الملف يتم تحديده في ما بعد PNG في
/Rome_0pq_lang=it.png # الخيارات الطويلة يتم فصلهم عن طريق شرطة سفلية
حصر المكان:
$ curl fr.wttr.in/Paris
$ curl wttr.in/paris?lang=fr
$ curl -H "Accept-Language: fr" wttr.in/paris
اللغات المدعومة:
FULL_TRANSLATION (مدعومة)
PARTIAL_TRANSLATION (جار العمل)
روابط URLs خاصة :
/:help # إظهار هذه الصفحة
/:bash.function # wttr() bash إظهار الميزة الخاصةبـ
/:translation # إظهار المعلومات حول المترجمين

View file

@ -1,80 +0,0 @@
113: صَافٍ : Clear
113: مُشْمِسٌ : Sunny
116: غَائِمٌ جزئياً‏ : Partly cloudy
119: غَائِمٌ : Cloudy
122: مُلبَّد بالغُيُوم : Overcast
143: ضَبَاب خَفيف : Mist
176: مِنَ الْمُمْكِنِ هُطول أمطار متفرِّقة : Patchy rain possible
179: مِنَ الْمُمْكِنِ هُطول ثُلُوج متفرِّقة : Patchy snow possible
182: مِنَ الْمُمْكِنِ هُطول المطر الثلجي متفرِّقة : Patchy sleet possible
185: مِنَ الْمُمْكِنِ هُطول أمطار متفرِّقة : Patchy freezing drizzle possible
200: مِنَ الْمُمْكِنِ ظُهورَ الرَعد : Thundery outbreaks possible
227: ثُلُوج غَزِيْرَةُ : Blowing snow
230: عاصِفة ثلجية : Blizzard
248: ضَّبَابُ كَثيف : Fog
260: ضَّبَابُ جامِد : Freezing fog
263: رَّذاذُ مَطَرِ متفرِّق : Patchy light drizzle
266: رَّذاذُ مَطَرِ : Light drizzle
281: رَّذاذُ مُتَجمَّد : Freezing drizzle
284: رَّذاذُ مُتَجمَّد غَزير : Heavy freezing drizzle
293: أمطار خَفيفة متفرِّقة : Patchy light rain
296: أمطار خَفيفة : Light rain
299: أمطار مُعْتَدِلَةُ في بَعْضِ الأوْقات : Moderate rain at times
302: أمطار مُعْتَدِلَةُ : Moderate rain
305: أمطار كَثيفة في بَعْضِ الأوْقات : Heavy rain at times
308: أمطار كَثيفة : Heavy rain
311: أمطار مُتَجمَّدة خَفيفة : Light freezing rain
314: أمطار مُتَجمَّدة مُعْتَدِلَةُ أو كَثيفة : Moderate or heavy freezing rain
317: مطر ثلجي خَفيف : Light sleet
320: مطر ثلجي مُعْتَدِلَ أو كَثيف : Moderate or heavy sleet
323: ثُلُوج خَفيفة متفرِّقة : Patchy light snow
326: ثُلُوج خَفيفة : Light snow
329: ثُلُوج مُعْتَدِلَةُ متفرِّقة : Patchy moderate snow
332: ثُلُوج مُعْتَدِلَةُ : Moderate snow
335: ثُلُوج كَثيفة متفرِّقة : Patchy heavy snow
338: ثُلُوج كَثيفة : Heavy snow
350: حُبَيْبات جليدية : Ice pellets
353: رَّذاذُ مَطَرٌ خَفِيف : Light rain shower
356: رَّذاذُ مَطَرٌ مُعْتَدِلَ أو كَثيف : Moderate or heavy rain shower
359: رَّذاذُ مطر غَزِيْرَ : Torrential rain shower
362: رَّذاذُ مطر ثلجي خَفيف : Light sleet showers
365: رَّذاذُ مطر ثلجي مُعْتَدِلَ أو كَثيف : Moderate or heavy sleet showers
368: رَّذاذُ ثلجي خَفيف : Light snow showers
371: رَّذاذُ ثلجي خَفيف : Moderate or heavy snow showers
386: أمطار خَفيفة متفرِّقة مَصحوبة بالرَعد : Patchy light rain with thunder
389: أمطار مُعْتَدِلَةُ أو كَثيفة متفرِّقة مَصحوبة بالرَعد : Moderate or heavy rain with thunder
392: ثُلُوج خَفيفة متفرِّقة مَصحوبة بالرَعد : Patchy light snow with thunder
395: ثُلُوج مُعْتَدِلَةُ أو كَثيفة متفرِّقة مَصحوبة بالرَعد : Moderate or heavy snow with thunder

View file

@ -1,48 +0,0 @@
113: Aydınlı : Clear : Ясно
113: Günəşli : Sunny : Солнечно
116: Dəyişkən buludlu : Partly cloudy : Переменная облачность
119: Buludlu : Cloudy : Облачно
122: Buludlu : Overcast : Пасмурно
143: Çən : Mist : Дымка
176: Bəzi yerlərdə yagış : Patchy rain possible : Местами дождь
179: Bəzi yerlərdə qar : Patchy snow possible : Местами снег
182: Bəzi yerlərdə qar və yagış : Patchy sleet possible : Местами дождь со снегом
185: Bəzi yerlərdə soyuq çisklinli hava : Patchy freezing drizzle possible : Местами замерзающая морось
200: Bəzi yerlərdə tufan : Thundery outbreaks possible : Местами грозы
227: Yüngül küləkli qar : Blowing snow : Поземок
230: Çovqun : Blizzard : Метель
248: Duman : Fog : Туман
260: Güclü soyuq duman : Freezing fog : Переохлажденный туман
263: Bəzi yerlərdə zəif çiskin : Patchy light drizzle : Местами слабая морось
266: zəif çiskin : Light drizzle : Слабая морось
281: Soyuq çiskin : Freezing drizzle : Замерзающая морось
284: Güclü soyuq çiskin : Heavy freezing drizzle : Сильная замерзающая морось
293: Bəzi yerlərdə yüngül yagış : Patchy light rain : Местами небольшой дождь
296: Yüngül yagiş : Light rain : Небольшой дождь
299: Bəzi vaxtlarda mülayim yagış : Moderate rain at times : Временами умеренный дождь
302: Mülayim yagış : Moderate rain : Умеренный дождь
305: Bəzi vaxtlarda güclü yagış : Heavy rain at times : Временами сильный дождь
308: Güclü yagış : Heavy rain : Сильный дождь
311: Zəif soyuq yagış : Light freezing rain : Слабый переохлажденный дождь
314: Mülayim yaxud güclü soyuq yagış : Moderate or heavy freezing rain : Умеренный или сильный переохлажденный дождь
317: Yüngül yagış və qar : Light sleet : Небольшой дождь со снегом
320: Mülayim yaxud güclü yagış və qar : Moderate or heavy sleet : Умеренный или сильный дождь со снегом
323: Bəzi yerlərdə yüngül qar : Patchy light snow : Местами небольшой снег
326: Yüngül qar : Light snow : Небольшой снег
329: Bəzi yerlərdə mülayim qar : Patchy moderate snow : Местами умеренный снег
332: Mülayim qar : Moderate snow : Умеренный снег
335: Bəzi yerlərdə güclü qar : Patchy heavy snow : Местами сильный снег
338: Güclü qar : Heavy snow : Сильный снег
350: Buzlu yagış : Ice pellets : Ледяной дождь
353: Yüngül leysanlı yagış : Light rain shower : Небольшой ливневый дождь
356: Mülayim yaxud güclü leysanlı yagış : Moderate or heavy rain shower : Умеренный или сильный ливневый дождь
359: Çox güclü leysan : Torrential rain shower : Очень сильный ливень
362: Yüngül leysanlı yagış və sulu qar : Light sleet showers : Небольшой ливневый дождь со снегом
365: Yüngül qar : Moderate or heavy sleet showers : Небольшой снег
368: Yüngül leysanlı yagış və qar : Light snow showers : Небольшой ливневый дождь со снегом
371: Mülayim yaxud güclü qarlı leysan : Moderate or heavy snow showers : Умеренный или сильный снежный ливень
386: Bəzi yerlərdə yüngül tufan və yagış: Patchy light rain with thunder : Местами небольшой дождь с грозой
389: Mülayim yaxud güclü yagış və tufan : Moderate or heavy rain with thunder : Умеренный или сильный дождь с грозой
392: Bəzi yerlərdə yüngül tufan və qar : Patchy light snow with thunder : Местами небольшой снег с грозой
395: Mülayim yaxud güclü qar və tufan : Moderate or heavy snow with thunder : Умеренный или сильный снег с грозой

View file

@ -1,69 +0,0 @@
Выкарыстанне:
$ curl wttr.in # надвор'е ў вашым месцы
$ curl wttr.in/msq # надвор'е ў аэрапорце Мінск
Магчымыя тыпы месцазнаходжання:
/novaja-mysz # населены пункт
/~dipgorodok # якое-небудзь месца
/жабінка # назва якога-небудзь месца юнікодам на нейкай мове
/gme # трохлітарны код аэрапорта
/@osm.by # даменнае імя
/220121 # паштовы індэкс
/53.953994,27.537774 # каардынаты GPS
Адмысловыя месцазнаходжанні:
/moon # Фаза месяца (дадайце ,+US або ,+France для горада Moon у ЗША або ў Францыі)
/moon@2016-10-25 # Фаза Месяца для гэтай даты (@2016-10-25)
Адзінкі вымярэння:
m # метрычная (СІ) (звычайна ўжываецца паўсюль акрамя ЗША)
u # USCS (ўжываецца ў ЗША)
M # паказваць хуткасць ветру ў м/с
Опцыі прагляду:
0 # толькі надвор'е цяпер
1 # надвор'е цяпер + 1 дзень
2 # надвор'е цяпер + 2 дні
A # не зважаць на User-Agent і мусова ўжываць фармат вываду ANSI (як у тэрмінале)
F # не паказваць радок "Сачыце за навінамі"
n # вузкая версія (толькі дзень і ноч)
q # ціхая версія (без тэксту "Прагноз надвор'я для")
Q # надта ціхая версія (без "Прагноз надвор'я для", без назвы месца)
T # выключыць паслядоўнасці тэрміналу (без колераў)
Опцыі PNG:
/szklow.png # выдаць файл PNG
p # дадаць рамку вакол
t # празрыстасць 150
transparency=... # празрыстасць ад 0 да 255 (255 = непразрыста)
Опцыі можна спалучаць:
/iuje?pq
/dzvinsk?0pq&lang=be
/bielastok_0pq.png # для PNG опцыі падаюцца пасля знаку _
/turau_0pq_lang=be.png # доўгія опцыі адасабляюцца знакам _
Лакалізацыя:
$ curl be.wttr.in/Minsk
$ curl wttr.in/minsk?lang=be
$ curl -H "Accept-Language: be" wttr.in/minsk
Даступныя мовы:
FULL_TRANSLATION (падтрымліваюцца)
PARTIAL_TRANSLATION (не цалкам)
Адмысловыя адрасы:
/:help # паказаць гэтую старонку
/:bash.function # паказаць рэкамендаваную функцыю bash wttr()
/:translation # паказаць спіс перакладаў

View file

@ -1,47 +0,0 @@
113: Ясна : Clear :
113: Сонечна : Sunny :
116: Пераменная воблачнасць : Partly cloudy :
119: Воблачнасць : Cloudy :
122: Хмарна : Overcast :
143: Смуга : Mist :
176: Месцамі дождж : Patchy rain possible :
179: Месцамі снег : Patchy snow possible :
182: Месцамі дождж са снегам : Patchy sleet possible :
185: Месцамі сцюдзёная імжа : Patchy freezing drizzle possible :
200: Месцамі навальніцы : Thundery outbreaks possible :
227: Нізавая завіруха : Blowing snow :
230: Мяцеліца : Blizzard :
248: Туман : Fog :
260: Сцюдзёны туман : Freezing fog :
263: Месцамі невялікая імжа : Patchy light drizzle :
266: Невялікая імжа : Light drizzle :
281: Сцюдзёная імжа : Freezing drizzle :
284: Моцная сцюдзёная імжа : Heavy freezing drizzle :
293: Месцамі невялікі дождж : Patchy light rain :
296: Невялікі дождж : Light rain :
299: Часам умераны дождж : Moderate rain at times :
302: Умераны дождж : Moderate rain :
305: Часам моцны дождж : Heavy rain at times :
308: Моцны дождж : Heavy rain :
311: Невялікі сцюдзёны дождж : Light freezing rain :
314: Умераны ці моцны сцюдзёны дождж : Moderate or heavy freezing rain :
317: Невялікі дождж са снегам : Light sleet :
320: Умераны ці моцны дождж са снегам : Moderate or heavy sleet :
323: Месцамі невялікі снег : Patchy light snow :
326: Невялікі снег : Light snow :
329: Месцамі умераны снег : Patchy moderate snow :
332: Умераны снег : Moderate snow :
335: Месцамі моцны снег : Patchy heavy snow :
338: Моцны снег : Heavy snow :
350: Ледзяны дождж : Ice pellets :
353: Слабы ліўневы дождж : Light rain shower :
356: Умераны ці моцны ліўневы дождж : Moderate or heavy rain shower :
359: Вельмі моцны ліўневы дождж : Torrential rain shower :
362: Невялікі ліўневы дождж са снегам : Light sleet showers :
365: Умераны ці моцны ліўневы дождж са снегам : Moderate or heavy sleet showers :
368: Невялікі ліўневы снег : Light snow showers :
371: Умераны ці моцны ліўневы снег : Moderate or heavy snow showers :
386: Месцамі невялікі дождж з навальніцай : Patchy light rain with thunder :
389: Умераны ці моцны дождж з навальніцай : Moderate or heavy rain with thunder :
392: Месцамі невялікі снег з навальніцай : Patchy light snow with thunder :
395: Умераны ці моцны снег з навальніцай : Moderate or heavy snow with thunder :

View file

@ -1,69 +0,0 @@
Употреба:
$ curl wttr.in # текущо местоположение
$ curl wttr.in/sof # времето на Софийското летище
Поддържани типове местоположения:
/paris # име на град
/~Eiffel+tower # място/забележителност
/Москва # Юникод име на местоположение
/sof # Код на летище (3 букви)
/@stackoverflow.com # име на домейн
/94107 # пощенски код
/-78.46,106.79 # GPS координати
Специални местоположение:
/moon # Фаза на луната (добавете ,+US or ,+France за тези места)
/moon@2016-10-25 # Фаза на луната за дата (@2016-10-25)
Мерни единици:
m # метрични (SI) (по подразбиране навсякъде извън US)
u # USCS (използвани по подразбиране в US)
M # покажи скоростта на вятъра в m/s
Възможности на изгледа:
0 # само текущото време
1 # текущо време + 1 ден
2 # текущо време + 2 дни
A # игнорирай User-Agent и форсирай ANSI формат (за терминал)
F # не показвай реда "Следвай"
n # тясна версия (само ден и нощ)
q # тиха версия (без текст "Прогноза за времето")
Q # свръх тиха версия(без "Прогноза за времето", без име на местоположение)
T # изключи терминалните настройки (без цветове)
PNG възможности:
/paris.png # генериране на PNG
p # Сложи рамка около резултата
t # полупрозрачен фон 150
transparency=... # полупрозрачен фон от 0 до 255 (255 = непрозрачно)
Опциите могат да се комбинират:
/Paris?0pq
/Paris?0pq&lang=bg
/Paris_0pq.png # при генериране на PNG възможностите са отделени с _
/Rome_0pq_lang=bg.png # дългите опции са разделени с подчертавка
Локализация:
$ curl fr.wttr.in/Paris
$ curl wttr.in/paris?lang=bg
$ curl -H "Accept-Language: bg" wttr.in/paris
Поддържани преводи:
FULL_TRANSLATION (Напълно преведени)
PARTIAL_TRANSLATION (В процес на превод)
Специални Адреси (URL):
/:help # тази страница
/:bash.function # препоръчителна bash функция wttr()
/:translation # покажи информация за преводачите

View file

@ -1,47 +0,0 @@
113 : Vedro : Clear
113 : Sunčano : Sunny
116 : Djelomična naoblaka : Partly cloudy
119 : Oblačno : Cloudy
122 : Tmurno : Overcast
143 : Magla : Mist
176 : Moguća je slaba kiša : Patchy rain possible
179 : Moguć slab snijeg : Patchy snow possible
182 : Moguća je slaba susnježiva : Patchy sleet possible
185 : Moguća je ledena sitna kiša : Patchy freezing drizzle possible
200 : Moguća je grmljavina : Thundery outbreaks possible
227 : Topljenje snijega : Blowing snow
230 : Snježna mećava : Blizzard
248 : Magla : Fog
260 : Ledena magla : Freezing fog
263 : Mjestimično sitna kiša : Patchy light drizzle
266 : Sitna kiša : Light drizzle
281 : Ledena sitna kiša : Freezing drizzle
284 : Teško zamrzavanje : Heavy freezing drizzle
293 : Mjestimično slaba kiša : Patchy light rain
296 : Blaga kiša : Light rain
299 : Ponekad umjerena kiša : Moderate rain at times
302 : Umjerena kiša : Moderate rain
305 : Ponekad je kiša : Heavy rain at times
308 : Pljusak : Heavy rain
311 : Kruta kiša : Light freezing rain
314 : Umjerena ili teška hladna kiša : Moderate or heavy freezing rain
317 : Lagana susnježica : Light sleet
320 : Umjerena ili jaka susnježica : Moderate or heavy sleet
323 : Mjestimično slab snijeg : Patchy light snow
326 : Slab snijeg : Light snow
329 : Mjestimično blag snijeg : Patchy moderate snow
332 : Umjeren snijeg : Moderate snow
335 : Umjereno jak snijeg : Patchy heavy snow
338 : Jak snijeg : Heavy snow
350 : Grad/tuča : Ice pellets
353 : Lagani pljusak : Light rain shower
356 : Umjeren ili jak pljusak : Moderate or heavy rain shower
359 : Prolom oblaka : Torrential rain shower
362 : Slaba kiša sa gradom : Light sleet showers
365 : Umjerena ili jaka kiša sa gradom : Moderate or heavy sleet showers
368 : Blaga susnježica : Light snow showers
371 : Umjerena ili jaka susnježica : Moderate or heavy snow showers
386 : Slaba kiša s grmljavinom : Patchy light rain with thunder
389 : Umjerena ili jaka kiša s grmljavinom : Moderate or heavy rain with thunder
392 : Slabi snijeg s grmljavinom : Patchy light snow with thunder
395 : Umjereni ili teški snijeg s grmljavinom : Moderate or heavy snow with thunder

View file

@ -1,66 +0,0 @@
Instruccions:
$ curl wttr.in # el clima de la ubicació actual
$ curl wttr.in/muc # el clima de l'aeroport de Múnich
Tipus d'ubicacions suportades:
/paris # el nom d'una ciutat
/~Eiffel+tower # el nom de qualsevol lloc famós
/Москва # el nom Unicode de qualsevol lloc en qualsevol idioma
/muc # el codi d'un aeroport (3 lletres)
/@stackoverflow.com # el nom d'un domini web
/94107 # un codi d'àrea
/-78.46,106.79 # coordenades de GPS
Llocs especials:
/moon # la fase de la lluna (afegeix ,+US o ,+France per a aquests països)
/moon@2016-10-25 # la fase de la lluna en una data especfica (@2016-10-25)
Unitats:
?m # mètriques (SI) (l'estàndard a tots els llocs excepte als EEUU)
?u # Sistema Unificat de Classificació del Sòl o USCS (l'estàndard als EEUU)
?M # mostrar la velocitat del vent en m/s
Opcions de visualització:
?0 # noms el clima actual
?1 # el clima actual + la previsió d'1 dia
?2 # el clima actual + la previsió de 2 dies
?n # versió curta (només el dia i la nit)
?q # versió silenciosa (sense el text de "El temps a")
?Q # versió supersilenciosa (ni "El temps a" ni el nom de la ciutat)
?T # desactiva les seqüències del terminal (sense colors)
Opcions de PNG:
/paris.png # genera una imatge PNG
?p # afegeix una vora al voltant de la imatge
?t # transparència 150
transparency=... # transparència de 0 a 255 (255 = sense transparència)
Les opcions es poden utilitzar conjuntament:
/Paris?0pq
/Paris?0pq&lang=fr
/Paris_0pq.png # amb PNG les opcions s'especifiquen després del caràcter _
/Rome_0pq_lang=it.png # una llarga seqüència d'opcions es poden separar amb el caràcter _
Ubicació:
$ curl fr.wttr.in/Paris
$ curl wttr.in/paris?lang=fr
$ curl -H "Accept-Language: fr" wttr.in/paris
Idiomes suportats:
FULL_TRANSLATION (soportados)
PARTIAL_TRANSLATION (en progreso)
URLs especials:
/:help # mostra aquesta pàgina
/:bash.function # suggereix una funció wttr() en bash
/:translation # mostra informació sobre els traductors

View file

@ -1,47 +0,0 @@
113 : Clar : Clear
113 : Assolellat : Sunny
116 : Parcialment ennuvolat : Partly cloudy
119 : Ennuvolat : Cloudy
122 : Molt ennuvolat : Overcast
143 : Boirina : Mist
176 : Possible pluja intermitent : Patchy rain possible
179 : Possible neu intermitent : Patchy snow possible
182 : Possible aiguaneu intermitent : Patchy sleet possible
185 : Possible plugim gelat intermitent : Patchy freezing drizzle possible
200 : Possible tempesta elèctrica : Thundery outbreaks possible
227 : Tempesta de neu : Blowing snow
230 : Torb : Blizzard
248 : Boira : Fog
260 : Boira gebradora : Freezing fog
263 : Plugim suau intermitent : Patchy light drizzle
266 : Plugim suau : Light drizzle
281 : Plugim gelat : Freezing drizzle
284 : Plugim gelat intens : Heavy freezing drizzle
293 : Pluja suau intermitent : Patchy light rain
296 : Pluja suau : Light rain
299 : Pluja moderada puntual : Moderate rain at times
302 : Pluja moderada : Moderate rain
305 : Pluja forta puntual : Heavy rain at times
308 : Pluja forta : Heavy rain
311 : Calamarsa : Light freezing rain
314 : Pedra o granís : Moderate or heavy freezing rain
317 : Aiguaneu suau : Light sleet
320 : Aiguaneu moderada o forta : Moderate or heavy sleet
323 : Nevada suau intermitent : Patchy light snow
326 : Nevada suau : Light snow
329 : Nevada moderada intermitent : Patchy moderate snow
332 : Nevada moderada : Moderate snow
335 : Nevada forta intermitent : Patchy heavy snow
338 : Nevada forta : Heavy snow
350 : Gebra : Ice pellets
353 : Ruixats de pluja suau : Light rain shower
356 : Ruixats de pluja moderada o forta : Moderate or heavy rain shower
359 : Ruixats de pluja torrencial : Torrential rain shower
362 : Ruixats d'aiguaneu suau : Light sleet showers
365 : Ruixats d'aiguaneu moderada o forta : Moderate or heavy sleet showers
368 : Ruixats de neu suau : Light snow showers
371 : Nevada moderada o forta : Moderate or heavy snow showers
386 : Intervals de pluges amb trons : Patchy light rain with thunder
389 : Pluja moderada o forta amb trons : Moderate or heavy rain with thunder
392 : Nevada lleugera intermitent amb trons : Patchy light snow with thunder
395 : Nevada moderada o forta amb trons : Moderate or heavy snow with thunder

View file

@ -1,82 +0,0 @@
113: Glir : Clear
113: Heulog : Sunny
116: Rhannol gymylog : Partly cloudy
119: Cymylog : Cloudy
122: Cymylog : Overcast
143: Niwlog : Mist
176: Glaw anghyson yn bosib : Patchy rain possible
179: Eira anghyson yn bosib : Patchy snow possible
182: Eirlaw anghyson yn bosib : Patchy sleet possible
185: Rhewi a glaw mân anghyson yn bosib : Patchy freezing drizzle possible
200: Mellt yn bosib : Thundery outbreaks possible
227: Eira yn cael ei chwythu : Blowing snow
230: Storm eira : Blizzard
248: Niwl : Fog
260: Niwl rhewllyd : Freezing fog
263: Glaw mân ysgafn anghyson : Patchy light drizzle
266: Glaw mân ysgafn : Light drizzle
281: Glaw mân rhewllyd : Freezing drizzle
284: Glaw mân rhewllyd trwm : Heavy freezing drizzle
293: Glaw ysgafn anghyson : Patchy light rain
296: Glaw ysgafn : Light rain
299: Glaw cymedrol ar adegau : Moderate rain at times
302: Glaw cymedrol : Moderate rain
305: Glaw trwm ar adegau : Heavy rain at times
308: Glaw trwm : Heavy rain
311: Glaw rhewllyd ysgafn : Light freezing rain
314: Glaw rhewllyd trwm neu cymedrol : Moderate or heavy freezing rain
317: Eirlaw ysgafn : Light sleet
320: Eirlaw cymedrol neu trwn : Moderate or heavy sleet
323: Eira ysgafn anghyson : Patchy light snow
326: Eira ysgafn : Light snow
329: Eira cymedrol anghyson : Patchy moderate snow
332: Eira cymedrol : Moderate snow
335: Eira trwm anghyson : Patchy heavy snow
338: Eira trwm : Heavy snow
350: Pelenni iâ : Ice pellets
353: Cawod o law ysgafn : Light rain shower
356: Cawod o law cymedrol neu trwm : Moderate or heavy rain shower
359: Cawod o law trwm : Torrential rain shower
362: Cawodydd o eirlaw ysgafn : Light sleet showers
365: Cawodydd o eirlaw cymedrol new trwm: Moderate or heavy sleet showers
368: Cawodydd o eira ysgafn : Light snow showers
368: Cawod o law ac eira ysgafn, Niwl : Light Rain And Snow Shower, Mist
371: Cawodydd o eira cymedrol neu trwm : Moderate or heavy snow showers
386: Glaw ysgafn anghyson gyda mellt : Patchy light rain with thunder
389: Glaw cymedrol neu trwm gyda mellt : Moderate or heavy rain with thunder
392: Eira ysgafn anghyson gyda mellt : Patchy light snow with thunder
395: Eira cymedrol neu trwm gyda mellt : Moderate or heavy snow with thunder

View file

@ -1,66 +0,0 @@
Brugsanvisning:
$ curl wttr.in # Nuværende lokation
$ curl wttr.in/aarhus # Vejret i Aarhus
Understøttede lokationsspecifikationer:
/Skanderborg # Bynavn
/~Aarhus+stadion # Valgfri lokation
/Москва # Unicode-navn på lokation på alle sprog
/cph # Flyvepladskode (3 bogstaver)
/@stackoverflow.com # Domænenavn
/94107 # Postnummer (kun USA)
/-78.46,106.79 # GPS-koordinater
Særlige lokationer:
/moon # Månefase (brug med ,+US eller ,+France for disse lokationer)
/moon@2014-10-26 # Månefase for specifik dato (@2014-10-26)
Enheder:
?m # Metrisk (SI) (standard alle steder undtagen i USA)
?u # USCS (standard i USA)
?M # Vindstyrke i meter per sekund
Visningsvalg:
?0 # Øjebliksvejr
?1 # Øjebliksvejr + 1 dag
?2 # Øjebliksvejr + 2 dage
?n # Simplificeret visning (kun dag og nat)
?q # Begrænset visning (ingen "Vejrmelding"-tekst)
?Q # Meget begrænset visning (ingen "Vejrmelding", ingen bynavn)
?T # Ingen terminalsekvenser (ingen farver)
PNG valg:
/paris.png # Generér en PNG-fil
?p # Tilføj ramme
?t # Sæt gennemsigtighed til 150
transparency=... # Tilpas gennemsigtighed fra 0 til 255 (255 = ikke gennemsigtigt)
Kombinationsmuligheder:
/Paris?0pq
/Paris?0pq&lang=fr
/Paris_0pq.png # For .PNG er fil-modus specificeret efter _
/Rome_0pq_lang=it.png # Sprog defineres efter underscore (_)
Oversættelser:
$ curl da.wttr.in/Aarhus
$ curl wttr.in/aarhus?lang=da
$ curl -H "Accept-Language: da" wttr.in/aarhus
Understøttede sprog:
FULL_TRANSLATION (understøttet)
PARTIAL_TRANSLATION (under udarbejdelse)
Særlige URL'er:
/:help # Vis denne side
/:bash.function # Vis den foreslåede bash-funktion wttr()
/:translation # Vis information om oversætterne

View file

@ -1,47 +0,0 @@
113: Skyfrit : Clear
113: Sol : Sunny
116: Delvist skyet : Partly cloudy
119: Skyet : Cloudy
122: Overskyet : Overcast
143: Diset : Mist
176: Skiftende regn : Patchy rain possible
179: Skiftende sne : Patchy snow possible
182: Skiftende slud : Patchy sleet possible
185: Skiftende kold småregn : Patchy freezing drizzle possible
200: Fare for torden : Thundery outbreaks possible
227: Snefygning : Blowing snow
230: Snestorm : Blizzard
248: Tåge : Fog
260: Frysende tåge : Freezing fog
263: Skiftende og let støvregn : Patchy light drizzle
266: Let støvregn : Light drizzle
281: Kold støvregn : Freezing drizzle
284: Kraftig kold regn : Heavy freezing drizzle
293: Skiftende let regn : Patchy light rain
296: Let regn : Light rain
299: Skiftevist en smule regn : Moderate rain at times
302: En smule regn : Moderate rain
305: Skiftevist kraftig regn : Heavy rain at times
308: Kraftig regn : Heavy rain
311: Let og frysende regn : Light freezing rain
314: Moderat eller kraftig frysende regn : Moderate or heavy freezing rain
317: Let slud : Light sleet
320: Moderat eller kraftig slud : Moderate or heavy sleet
323: Byger af let sne : Patchy light snow
326: Let sne : Light snow
329: Byger med moderat sne : Patchy moderate snow
332: Moderat sne : Moderate snow
335: Byger med kraftig sne : Patchy heavy snow
338: Kraftig sne : Heavy snow
350: Haglbyger : Ice pellets
353: Lette regnbyger : Light rain shower
356: Moderate eller kraftige regnbyger : Moderate or heavy rain shower
359: Skybrud : Torrential rain shower
362: Lette sludbyger : Light sleet showers
365: Moderate eller kraftige sludbyger : Moderate or heavy sleet showers
368: Lette snebyger : Light snow showers
371: Moderate eller kraftige snebyger : Moderate or heavy snow showers
386: Byger af let regn med torden : Patchy light rain with thunder
389: Moderat eller krafig regn med torden : Moderate or heavy rain with thunder
392: Byger af let sne med torden : Patchy light snow with thunder
395: Moderat eller kraftig sne med torden : Moderate or heavy snow with thunder

View file

@ -1,67 +0,0 @@
Benutzung:
$ curl wttr.in # aktuelle Position
$ curl wttr.in/muc # Wetter, Flughafen München
Unterstütze Ortstypen:
/paris # Stadtname
/~Eiffel+tower # wählbarer Ort
/Москва # Unicode Name von einem Ort in irgendeiner Sprache
/muc # IATA-Flughafencode (3 Buchstaben)
/@stackoverflow.com # Domainname
/94107 # Area code (nur für USA)
/-78.46,106.79 # GPS Koordinaten
Spezielle Orte:
/moon # Mondphase (bei Benutzung von z.B. ,+US oder ,+France wird die Phase des jeweiligen Ortes angezeigt)
/moon@2016-10-25 # Mondphase eines Tages (@2016-10-25)
Maßeinheiten:
?m # metrisch (SI) (standard überall außer bei Orten in den USA)
?u # USCS (standard in den USA)
?M # Windgeschwindigkeiten in m/s
Ansichteinstellungen:
?0 # Zeige nur aktuelles Wetter
?1 # Zeige aktuelles Wetter + 1 Tag
?2 # Zeige aktuelles Wetter + 2 Tage
?n # Kleine Version (nur Tag & Nacht)
?q # Schmale Version (kein 'Wetter Report' Text)
?Q # Superschmale Version (kein 'Wetter Report' Text und Ortsname)
?T # Keine Farben
PNG optionen:
/paris.png # generiert eine PNG Datei
?p # fügt einen Rahmen hinzu
?t # Transparenz von 150
transparency=... # Transparenz von 0 bis 255 (255 = nicht transparent)
Optionen können kombiniert werden:
/Paris?0pq
/Paris?0pq&lang=fr
/Paris_0pq.png # wird eine PNG benutzt, dann werden diese Optionen nach einem _ (Unterstrich) hinzugefügt
/Rome_0pq_lang=it.png # einzelne Optionen werden mit einem _ (Unterstrich) getrennt
Lokalisierung:
$ curl fr.wttr.in/Paris
$ curl wttr.in/paris?lang=fr
$ curl -H "Accept-Language: fr" wttr.in/paris
Unterstützte Sprachen:
FULL_TRANSLATION (supported)
PARTIAL_TRANSLATION (in progress)
Spezialseiten:
/:help # zeigt diese Seite an
/:bash.function # zeigt empfehlenswerte bash Funktion wttr() an
/:translation # zeigt Informationen der Übersetzer an

View file

@ -1,50 +0,0 @@
: Regen : Rain
: leichter Regen, Regenschauer : Light Rain, Rain Shower
: Regenschauer : Rain Shower
113: Wolkenlos : Clear
113: Sonnig : Sunny
116: Leicht Bewölkt : Partly cloudy
119: Wolkig : Cloudy
122: Bedeckt : Overcast
143: Nebel : Mist
176: Stellenweise Regen möglich : Patchy rain possible
179: Stellenweise Schnee möglich : Patchy snow possible
182: Stellenweise Schneeregen möglich : Patchy sleet possible
185: Stellenweise gefrierende Nässe möglich : Patchy freezing drizzle possible
200: Gewitter möglich : Thundery outbreaks possible
227: Schneesturm : Blowing snow
230: Blizzard : Blizzard
248: Nebel : Fog
260: Gefrierender Nebel : Freezing fog
263: Stellenweise Nieselregen : Patchy light drizzle
266: Leichter Nieselregen : Light drizzle
281: Gefrierender Nieselregen : Freezing drizzle
284: Starker gefrierender Nieselregen : Heavy freezing drizzle
293: Stellenweise leichter Regen : Patchy light rain
296: Leichter Regen : Light rain
299: Stellenweise gemäßigter Regen : Moderate rain at times
302: Gemäßigter Regen : Moderate rain
305: Stellenweise starker Regen : Heavy rain at times
308: Starker Regen : Heavy rain
311: Leichter gefrierender Regen : Light freezing rain
314: Gemäßigter oder starker gefrierender Regen : Moderate or heavy freezing rain
317: Leichter Schneeregen : Light sleet
320: Gemäßigter oder starker Schneeregen : Moderate or heavy sleet
323: Stellenweise leichter Schneefall : Patchy light snow
326: Leichter Schneefall : Light snow
329: Stellenweise gemäßigter Schneefall : Patchy moderate snow
332: Gemäßigter Schneefall : Moderate snow
335: Stellenweise starker Schneefall : Patchy heavy snow
338: Starker Schneefall : Heavy snow
350: Eiskörner : Ice pellets
353: Leichter Regen : Light rain shower
356: Gemäßigter oder starker Regen : Moderate or heavy rain shower
359: Wolkenbruch : Torrential rain shower
362: Leichter Schneeregen : Light sleet showers
365: Gemäßigter oder starker Schneeregen : Moderate or heavy sleet showers
368: Leichter Schneefall : Light snow showers
371: Gemäßigter oder starker Schneefall : Moderate or heavy snow showers
386: Stellenweise leichter Regen mit Gewitter : Patchy light rain with thunder
389: Gemäßigter oder starker Regen mit Gewitter : Moderate or heavy rain with thunder
392: Stellenweise leichter Schnefall mit Gewitter : Patchy light snow with thunder
395: Gemäßigter oder starker Schnefall mit Gewitter : Moderate or heavy snow with thunder

View file

@ -1,66 +0,0 @@
Brugsanvisning:
$ curl wttr.in # nuværende lokation
$ curl wttr.in/osl # vejret på Gardermoen flyveplads
Understøttede lokationsspecifikationer:
/gistrup # bynavn
/~Aalborg+stadion # valgfri lokation
/Москва # Unicode navn på lokation på alle sprog
/cph # flyvepladskode (3 bogstaver)
/@stackoverflow.com # domønenavn
/94107 # postnummer (kun USA)
/-78.46,106.79 # GPS-koordinater
Specielle lokationer:
/moon # Månefase (brug med ,+US eller ,+France for disse lokationer)
/moon@2014-10-26 # Månefase for specifik dato (@2014-10-26)
Enheder:
?m # metrisk (SI) (standard alle steder undtaget i USA)
?u # USCS (standard i USA)
?M # vindstyrke i meter per sekund
Visningsvalg:
?0 # kun vejret nu
?1 # vejret nu + 1 dag
?2 # vejret nu + 2 dage
?n # smal visning (kun dag og nat)
?q # stille visning (ingen "Vejrmelding"-tekst)
?Q # superstille visning (ingen "Vejrmelding", ingen bynavn)
?T # ingen terminalsekvenser (ingen farver)
PNG valg:
/paris.png # generer en PNG-fil
?p # tegn ramme på
?t # gennemsigtighed 150
transparency=... # gennemsigtighed fra 0 til 255 (255 = ikke gennemsigtigt)
Tilvalg kan kombineres:
/Paris?0pq
/Paris?0pq&lang=fr
/Paris_0pq.png # for PNG er filmodus specificeret efter _
/Rome_0pq_lang=it.png # lange tilvalg separeres med underscore (_)
Oversættelser:
$ curl fr.wttr.in/Paris
$ curl wttr.in/paris?lang=fr
$ curl -H "Accept-Language: fr" wttr.in/paris
Understøttede sprog:
FULL_TRANSLATION (understøttet)
PARTIAL_TRANSLATION (under udarbejdning)
Specielle URLer:
/:help # vis denne side
/:bash.function # vis den foreslåede bash-funktion wttr()
/:translation # vis information om oversætterne

View file

@ -1,72 +0,0 @@
Χρήση:
$ curl wttr.in # καιρός τρέχουσας τοποθεσίας (κατά προσέγγιση, βάσει IP)
$ curl wttr.in/ath # ο καιρός στο Διεθνή Αερολιμένα Αθηνών «Ελ. Βενιζέλος»
Υποστηριζόμενες μορφές τοποθεσιών:
/paris # όνομα πόλης
/~Eiffel+tower # κάποια τοποθεσία
/Москва # Unicode όνομα οποιασδήποτε τοποθεσίας σε οποιαδήποτε γλώσσα
/ath # κωδικός αεροδρομίου (3 γράμματα)
/@stackoverflow.com # (διαδικτυακό) όνομα τομέα (domain)
/94107 # κωδικός περιοχής
/-78.46,106.79 # γεωγραφικές συντεταγμένες (GPS)
Εξειδικευμένες τοποθεσίες:
/moon # Φάση Σελήνης (προσθέστε ,+US ή ,+France για τις πόλεις)
/moon@2016-10-25 # Φάση Σελήνης για την ημερομηνία (@2016-10-25)
/moon@2019-06-18.png?T -omoon.png # Φάση Σελήνης για την ημερομηνία (@2019-06-18), όχι ANSI, εξοδος στο αρχείο `moon.png`
Σύστημα μέτρησης:
m # μετρικό (SI) (προεπιλογή για οπουδήποτε εκτός ΗΠΑ)
u # USCS (προεπιλογή για τις ΗΠΑ)
M # εμφάνιση ταχύτητας ανέμου σε m/s
Επιλογές Προβολής:
0 # καιρός τρέχουσας ημέρας μόνο
1 # καιρός τρέχουσας ημέρας + 1 ημέρα
2 # καιρός τρέχουσας ημέρας + 2 ημέρες
A # παραβλέπει τον User-Agent και επιβάλει μορφή εξόδου ANSI (τερματικό)
F # μη εμφάνιση της γραμμής "Follow"
n # συνοπτική εκδοχή (ημέρα και νύχτα μόνο)
q # σιωπηλή εκδοχή (όχι κείμενο "Weather report")
Q # υπερσιωπηλή εκδοχή (όχι κείμενο "Weather report", όχι όνομα πόλης)
T # απενεργοποίηση ακολουθιών τερματικού (όχι χρώματα)
Επιλογές PNG:
/paris.png # δημιουργία PNG αρχείου
p # προσθήκη πλαισίου γύρω από τα περιέχομενα εξόδου
t # διαφάνεια 150
transparency=nnn # διαφάνεια nnn (από 0 έως 255 - 255 = αδιαφανές)
Οι επιλογές μπορούν να συνδυαστούν:
/Paris?0pq
/Paris?0pq&lang=fr
/Paris?_0pq.png # η έξοδος σε PNG αρχείο ορίζεται μετά από το _
/Rome?_0pq_lang=it.png # μακροσκελείς επιλογές διαχωριζόμενες με κάτω παύλα (underscore)
/Athens?"1pTng&lang=el" # συνδυασμένες επιλογές μπορούν να περικλείονται σε εισαγωγικά!
Τοπικοποίηση:
$ curl fr.wttr.in/Paris
$ curl wttr.in/paris?lang=fr
$ curl -H "Accept-Language: fr" wttr.in/paris
Υποστηριζόμενες γλώσσες:
FULL_TRANSLATION (πλήρως υποστηριζόμενες)
PARTIAL_TRANSLATION (εργασία σε εξέλιξη)
Ειδικά URLs:
/:help # εμφάνιση της παρούσας βοήθειας
/:bash.function # εμφάνιση συνιστώμενων bash λειτουργιών wttr()
/:translation # εμφάνιση πληροφοριών για τους μεταφραστές

View file

@ -1,47 +0,0 @@
113: Καθαρός : Clear
113: Λιακάδα : Sunny
116: Αραιή συννεφιά : Partly cloudy
119: Συννεφιά : Cloudy
122: Πυκνή νέφωση : Overcast
143: Αραιή ομίχλη : Mist
176: Πιθανή βροχή τοπικά : Patchy rain possible
179: Πιθανή χιονόπτωση τοπικά : Patchy snow possible
182: Πιθανή χαλαζόπτωση τοπικά : Patchy sleet possible
185: Πιθανό παγωμ. ψιλόβροχο τοπικά : Patchy freezing drizzle possible
200: Πιθανή πτώση κεραυνών : Thundery outbreaks possible
227: Ριπές χιονιού : Blowing snow
230: Χιονοθύελλα : Blizzard
248: Ομίχλη : Fog
260: Παγωμένη ομίχλη : Freezing fog
263: Ασθενής ψιχάλα τοπικά : Patchy light drizzle
266: Ασθενής ψιχάλα : Light drizzle
281: Παγωμένο ψιλόβροχο : Freezing drizzle
284: Πυκνό παγωμένο ψιλόβροχο : Heavy freezing drizzle
293: Ασθενής όμβρος τοπικά : Patchy light rain
296: Ασθενής βροχόπτωση : Light rain
299: Μέτρια βροχή παροδικά : Moderate rain at times
302: Μέτρια βροχόπτωση : Moderate rain
305: Έντονη βροχή παροδικά : Heavy rain at times
308: Έντονη βροχόπτωση : Heavy rain
311: Αραιό χιονόνερο : Light freezing rain
314: Μέτριο ή έντονο χιονόνερο : Moderate or heavy freezing rain
317: Ασθενής χαλαζόπτωση : Light sleet
320: Μέτρια ή έντονη χαλαζόπτωση : Moderate or heavy sleet
323: Αραιή χιονόπτωση κατά τόπους : Patchy light snow
326: Αραιή χιονόπτωση : Light snow
329: Μέτρια χιονόπτωση τοπικά : Patchy moderate snow
332: Μέτρια χιονόπτωση : Moderate snow
335: Πυκνή χιονόπτωση τοπικά : Patchy heavy snow
338: Πυκνή χιονόπτωση : Heavy snow
350: Χιονόνερο : Ice pellets
353: Ασθενής βροχή παροδικά : Light rain shower
356: Μέτρια ή έντονη βροχή παροδικά : Moderate or heavy rain shower
359: Καταρρακτώδης βροχή παροδικά : Torrential rain shower
362: Ασθενής χαλαζόπτωση παροδικά : Light sleet showers
365: Μέτρια/έντονη χαλαζόπτωση παροδικά : Moderate or heavy sleet showers
368: Αραιή χιονόπτωση παροδικά : Light snow showers
371: Μέτρια ή πυκνή χιονόπτωση παροδικά : Moderate or heavy snow showers
386: Ασθενής βροχή με κεραυνούς τοπικά : Patchy light rain with thunder
389: Μέτρια ή δυνατή βροχή με κεραυνούς : Moderate or heavy rain with thunder
392: Ασθενής χιονόπτωση-κεραυνοί τοπικά : Patchy light snow with thunder
395: Μέτρια ή πυκνή χιονόπτωση-κεραυνοί : Moderate or heavy snow with thunder

View file

@ -1,81 +0,0 @@
113: : Clear
113: : Sunny
116: : Partly cloudy
119: : Cloudy
122: : Overcast
143: : Mist
176: : Patchy rain possible
179: : Patchy snow possible
182: : Patchy sleet possible
185: : Patchy freezing drizzle possible
200: : Thundery outbreaks possible
227: : Blowing snow
230: : Blizzard
248: : Fog
260: : Freezing fog
263: : Patchy light drizzle
266: : Light drizzle
281: : Freezing drizzle
284: : Heavy freezing drizzle
293: : Patchy light rain
296: : Light rain
299: : Moderate rain at times
302: : Moderate rain
305: : Heavy rain at times
308: : Heavy rain
311: : Light freezing rain
314: : Moderate or heavy freezing rain
317: : Light sleet
320: : Moderate or heavy sleet
323: : Patchy light snow
326: : Light snow
329: : Patchy moderate snow
332: : Moderate snow
335: : Patchy heavy snow
338: : Heavy snow
350: : Ice pellets
353: : Light rain shower
356: : Moderate or heavy rain shower
359: : Torrential rain shower
362: : Light sleet showers
365: : Moderate or heavy sleet showers
368: : Light snow showers
371: : Moderate or heavy snow showers
386: : Patchy light rain with thunder
389: : Moderate or heavy rain with thunder
392: : Patchy light snow with thunder
395: : Moderate or heavy snow with thunder

Some files were not shown because too many files have changed in this diff Show more