remove legacy python/go
|
|
@ -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"]
|
||||
202
legacy/LICENSE
|
|
@ -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.
|
||||
|
||||
612
legacy/README.md
|
|
@ -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!):
|
||||
|
||||

|
||||
|
||||
(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).
|
||||
|
||||

|
||||
|
||||
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/.
|
||||
|
||||

|
||||
|
||||
## 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 ..."
|
||||
```
|
||||

|
||||
|
||||
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
|
||||
```
|
||||
|
||||
|
||||

|
||||
|
||||
(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:
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
## 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 Powershell’s `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;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -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()
|
||||
|
|
@ -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'))
|
||||
|
|
@ -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()
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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>')
|
||||
|
|
@ -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()
|
||||
|
|
@ -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",
|
||||
}
|
||||
|
|
@ -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")
|
||||
|
|
@ -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"),
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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))))
|
||||
|
|
@ -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)
|
||||
|
|
@ -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]
|
||||
|
|
@ -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
|
||||
|
|
@ -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>')
|
||||
|
|
@ -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
|
||||
|
|
@ -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"""
|
||||
Ní rabhamar ábalta do cheantar a aimsiú
|
||||
mar sin thugamar go dtí Oymyakon,
|
||||
tú 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,
|
||||
så her får du Ojmjakon, et av de kaldeste bebodde stedene på 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 vă arătăm vremea din Oimiakon,
|
||||
una dintre cele mai reci localități permanent locuite de pe planetă.
|
||||
Sperăm că 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 så 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
|
||||
så vi har bragt dig til Oymyakon,
|
||||
En af koldeste og helt ubolige lokationer på 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ì vậy chúng tôi đưa bạn đến Oymyakon,
|
||||
một trong những nơi lạnh nhất có 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 té).
|
||||
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"""
|
||||
Tá 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, kā 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 nå værtjenesten for øyeblikket.
|
||||
Her er værmeldingen for standardbyen så du får se hvordan tjenesten ser ut.
|
||||
Vi vil forsøke å fikse problemet så 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.
|
||||
Vă arătăm prognoza meteo pentru localitatea implicită (ca exemplu, să 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 så 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 få 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 là dự báo thời tiết cho thành phố mặc định (chỉ để cho bạn thấy nó trông như thế nào).
|
||||
Chung tôi sẽ có thêm lượt truy vấn sớm nhất có thể
|
||||
Bạn có 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', ''))
|
||||
|
|
@ -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:", "", "", "", "", "", "", "", ""),
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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]))
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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/
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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#&#\&#g; s#\"#\"#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#>#\>#g; s#<#\<#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'
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
NOT_FOUND
|
||||
apple-touch-icon.png
|
||||
apple-touch-icon-precomposed.png
|
||||
apple-touch-icon-152x152-precomposed.png
|
||||
|
||||
|
|
@ -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
|
||||
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 725 B |
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
wttr:
|
||||
apikey: insert-api-key-here-and-make-this-pillar-available-to-salt
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
SESSION_NAME=wttr.in
|
||||
SCREENRC_PATH=$(dirname $(dirname "$0"))/screenrc
|
||||
|
||||
screen -dmS "$SESSION_NAME" -c "$SCREENRC_PATH"
|
||||
|
||||
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 9.4 KiB |
|
|
@ -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>— 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>
|
||||
|
|
@ -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%;
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
Prévisions météo pour: Test-Milon, France
|
||||
|
||||
[38;5;226m _`/""[38;5;250m.-. [0m Averse de pluie légère
|
||||
[38;5;226m ,\_[38;5;250m( ). [0m [38;5;154m17[0m °C[0m
|
||||
[38;5;226m /[38;5;250m(___(__) [0m [1m↙[0m [38;5;118m5[0m km/h[0m
|
||||
[38;5;111m ‘ ‘ ‘ ‘ [0m 14 km[0m
|
||||
[38;5;111m ‘ ‘ ‘ ‘ [0m 3.6 mm[0m
|
||||
┌─────────────┐
|
||||
┌──────────────────────────────┬───────────────────────┤ sam. 09 juin├───────────────────────┬──────────────────────────────┐
|
||||
│ Matin │ Après-midi └──────┬──────┘ Soir │ Nuit │
|
||||
├──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤
|
||||
│ Brume │ Nuageux │ [38;5;226m _`/""[38;5;250m.-. [0m Foyers orageux │ [38;5;226m _`/""[38;5;250m.-. [0m Averse de plui…│
|
||||
│ [38;5;251m _ - _ - _ - [0m [38;5;190m21[0m °C[0m │ [38;5;250m .--. [0m [38;5;220m25[0m-[38;5;220m26[0m °C[0m │ [38;5;226m ,\_[38;5;250m( ). [0m [38;5;220m25[0m-[38;5;220m27[0m °C[0m │ [38;5;226m ,\_[38;5;250m( ). [0m [38;5;226m22[0m °C[0m │
|
||||
│ [38;5;251m _ - _ - _ [0m [1m←[0m [38;5;118m5[0m-[38;5;118m6[0m km/h[0m │ [38;5;250m .-( ). [0m [1m←[0m [38;5;118m4[0m-[38;5;118m5[0m km/h[0m │ [38;5;226m /[38;5;250m(___(__) [0m [1m↑[0m [38;5;118m4[0m-[38;5;118m6[0m km/h[0m │ [38;5;226m /[38;5;250m(___(__) [0m [1m→[0m [38;5;118m5[0m-[38;5;154m9[0m km/h[0m │
|
||||
│ [38;5;251m _ - _ - _ - [0m 19 km[0m │ [38;5;250m (___.__)__) [0m 18 km[0m │ [38;5;228;5m ⚡[38;5;111;25m‘‘[38;5;228;5m⚡[38;5;111;25m‘‘ [0m 16 km[0m │ [38;5;111m ‘ ‘ ‘ ‘ [0m 15 km[0m │
|
||||
│ 0.0 mm | 0%[0m │ 0.0 mm | 0%[0m │ [38;5;111m ‘ ‘ ‘ ‘ [0m 0.7 mm | 46%[0m │ [38;5;111m ‘ ‘ ‘ ‘ [0m 2.4 mm | 82%[0m │
|
||||
└──────────────────────────────┴──────────────────────────────┴──────────────────────────────┴──────────────────────────────┘
|
||||
┌─────────────┐
|
||||
┌──────────────────────────────┬───────────────────────┤ dim. 10 juin├───────────────────────┬──────────────────────────────┐
|
||||
│ Matin │ Après-midi └──────┬──────┘ Soir │ Nuit │
|
||||
├──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤
|
||||
│ [38;5;226m \ /[0m Partiellement …│ [38;5;226m \ /[0m Partiellement …│ [38;5;226m _`/""[38;5;240;1m.-. [0m Averse de plui…│ [38;5;226m _`/""[38;5;240;1m.-. [0m Averse de plui…│
|
||||
│ [38;5;226m _ /""[38;5;250m.-. [0m [38;5;226m22[0m-[38;5;226m24[0m °C[0m │ [38;5;226m _ /""[38;5;250m.-. [0m [38;5;220m25[0m-[38;5;220m27[0m °C[0m │ [38;5;226m ,\_[38;5;240;1m( ). [0m [38;5;226m24[0m-[38;5;220m26[0m °C[0m │ [38;5;226m ,\_[38;5;240;1m( ). [0m [38;5;190m21[0m °C[0m │
|
||||
│ [38;5;226m \_[38;5;250m( ). [0m [1m↙[0m [38;5;154m9[0m-[38;5;190m10[0m km/h[0m │ [38;5;226m \_[38;5;250m( ). [0m [1m↙[0m [38;5;226m13[0m-[38;5;220m19[0m km/h[0m │ [38;5;226m /[38;5;240;1m(___(__) [0m [1m↙[0m [38;5;220m17[0m-[38;5;202m30[0m km/h[0m │ [38;5;226m /[38;5;240;1m(___(__) [0m [1m↙[0m [38;5;226m15[0m-[38;5;202m31[0m km/h[0m │
|
||||
│ [38;5;226m /[38;5;250m(___(__) [0m 20 km[0m │ [38;5;226m /[38;5;250m(___(__) [0m 20 km[0m │ [38;5;21;1m ‚‘‚‘‚‘‚‘ [0m 17 km[0m │ [38;5;21;1m ‚‘‚‘‚‘‚‘ [0m 18 km[0m │
|
||||
│ 0.0 mm | 0%[0m │ 1.8 mm | 56%[0m │ [38;5;21;1m ‚’‚’‚’‚’ [0m 4.1 mm | 82%[0m │ [38;5;21;1m ‚’‚’‚’‚’ [0m 1.6 mm | 72%[0m │
|
||||
└──────────────────────────────┴──────────────────────────────┴──────────────────────────────┴──────────────────────────────┘
|
||||
┌─────────────┐
|
||||
┌──────────────────────────────┬───────────────────────┤ lun. 11 juin├───────────────────────┬──────────────────────────────┐
|
||||
│ Matin │ Après-midi └──────┬──────┘ Soir │ Nuit │
|
||||
├──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤
|
||||
│ [38;5;226m _`/""[38;5;250m.-. [0m Pluie éparse à │ Nuageux │ [38;5;226m _`/""[38;5;240;1m.-. [0m Averse de plui…│ [38;5;226m _`/""[38;5;240;1m.-. [0m Averse de plui…│
|
||||
│ [38;5;226m ,\_[38;5;250m( ). [0m [38;5;190m21[0m °C[0m │ [38;5;250m .--. [0m [38;5;226m24[0m-[38;5;220m26[0m °C[0m │ [38;5;226m ,\_[38;5;240;1m( ). [0m [38;5;226m23[0m-[38;5;220m25[0m °C[0m │ [38;5;226m ,\_[38;5;240;1m( ). [0m [38;5;190m20[0m °C[0m │
|
||||
│ [38;5;226m /[38;5;250m(___(__) [0m [1m↑[0m [38;5;226m14[0m-[38;5;214m23[0m km/h[0m │ [38;5;250m .-( ). [0m [1m↑[0m [38;5;220m18[0m-[38;5;214m22[0m km/h[0m │ [38;5;226m /[38;5;240;1m(___(__) [0m [1m↗[0m [38;5;226m13[0m-[38;5;214m23[0m km/h[0m │ [38;5;226m /[38;5;240;1m(___(__) [0m [1m↗[0m [38;5;190m10[0m-[38;5;214m21[0m km/h[0m │
|
||||
│ [38;5;111m ‘ ‘ ‘ ‘ [0m 18 km[0m │ [38;5;250m (___.__)__) [0m 18 km[0m │ [38;5;21;1m ‚‘‚‘‚‘‚‘ [0m 14 km[0m │ [38;5;21;1m ‚‘‚‘‚‘‚‘ [0m 14 km[0m │
|
||||
│ [38;5;111m ‘ ‘ ‘ ‘ [0m 0.1 mm | 22%[0m │ 0.0 mm | 0%[0m │ [38;5;21;1m ‚’‚’‚’‚’ [0m 5.2 mm | 73%[0m │ [38;5;21;1m ‚’‚’‚’‚’ [0m 5.3 mm | 72%[0m │
|
||||
└──────────────────────────────┴──────────────────────────────┴──────────────────────────────┴──────────────────────────────┘
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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 # إظهار المعلومات حول المترجمين
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -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 : Умеренный или сильный снег с грозой
|
||||
|
||||
|
|
@ -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 # паказаць спіс перакладаў
|
||||
|
||||
|
|
@ -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 :
|
||||
|
|
@ -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 # покажи информация за преводачите
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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 # εμφάνιση πληροφοριών για τους μεταφραστές
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||