Compare commits
9 commits
Author | SHA1 | Date | |
---|---|---|---|
33346bc546 | |||
a2c4582a0c | |||
e322994842 | |||
5daa97af3d | |||
8d8226a5fa | |||
538f50847e | |||
821389605a | |||
ca2b4f4d59 | |||
be894106a3 |
8 changed files with 224 additions and 224 deletions
|
@ -3,6 +3,15 @@ Least-favourite twitch streamers here
|
|||
|
||||
|
||||
## Change Log
|
||||
### 1.10.0
|
||||
#### Changed
|
||||
- Refresh website design
|
||||
|
||||
### 1.9.1
|
||||
#### Fixed
|
||||
- Server does not fail when processing API response withour `cursor` field.
|
||||
- String formatting for older python3 versions.
|
||||
|
||||
### 1.9
|
||||
#### Fixed
|
||||
- Char escaping and various typos.
|
||||
|
|
4
main.py
4
main.py
|
@ -4,7 +4,7 @@
|
|||
import cherrypy
|
||||
from twitch import TwitchClient
|
||||
|
||||
ver = '1.9.0'
|
||||
ver = '1.10.0'
|
||||
|
||||
|
||||
class FleastServer(object):
|
||||
|
@ -90,7 +90,7 @@ class FleastServer(object):
|
|||
s['thumbnail_url'].format(width=320, height=180),
|
||||
self.to_html(s['title']),
|
||||
s['user_name'],
|
||||
s['viewer_count']) + '\n'
|
||||
s['viewer_count'])
|
||||
|
||||
return self.templ_main.format(_stream_num_=len(data['streams']),
|
||||
_game_name_=game,
|
||||
|
|
29
twitch.py
29
twitch.py
|
@ -66,7 +66,7 @@ class TwitchClient:
|
|||
r = requests.get(base, headers=header).json()
|
||||
error_message = r.get("error", "")
|
||||
if len(error_message) > 0:
|
||||
cherrypy.log(f'Request: fail with error "{error_message}"')
|
||||
cherrypy.log('Request: fail with error "%s"' % error_message)
|
||||
r = None
|
||||
else:
|
||||
cherrypy.log('Request: OK')
|
||||
|
@ -143,16 +143,37 @@ class TwitchClient:
|
|||
q_template = "{}/streams?language={}&first={}&after={}{}"
|
||||
|
||||
game_id = ''
|
||||
irl_ids = ["509660", "509673", "509667", "509669", "509670", "509658",
|
||||
"509672", "509671", "509664", "509663", "417752", "509659"]
|
||||
"""
|
||||
417752: Talk Shows & Podcasts
|
||||
509658: Just Chatting
|
||||
509659: ASMR
|
||||
509660: Art
|
||||
509663: Special Events
|
||||
509664: Tabletop RPGs
|
||||
509667: Food & Drink
|
||||
509669: Beauty & Body Art
|
||||
509670: Science & Technology
|
||||
509671: Fitness & Health [exclude]
|
||||
509672: Travel & Outdoors
|
||||
509673: Makers & Crafting
|
||||
515214: Politics [exclude]
|
||||
518203: Sports
|
||||
116747788: Pools, Hot Tubs, and Beaches
|
||||
272263131: Animals, Aquariums, and Zoos [exclude]
|
||||
1469308723: Software and Game Development
|
||||
"""
|
||||
irl_ids = ["417752", "509658", "509659", "509660", "509663", "509664",
|
||||
"509667", "509669", "509670", "509672", "509673", "518203",
|
||||
"116747788", "1469308723"]
|
||||
for irl_id in irl_ids:
|
||||
game_id += '&game_id={}'.format(irl_id)
|
||||
|
||||
result = {'_total': 0, 'streams': []}
|
||||
data = self.do_q_auth_v6(init_q_template.format(base, lang, 100, game_id), header)
|
||||
result['streams'].extend(data['data'])
|
||||
while len(data.get('data', [])) > 0: # there must be non zero value, but search is kinda broken now
|
||||
result['streams'].extend(data['data'])
|
||||
if data['pagination'].get("cursor", None) is None: # sometimes server return results without cursor
|
||||
break
|
||||
data = self.do_q_auth_v6(q_template.format(base, lang, 100, data['pagination']['cursor'], game_id), header)
|
||||
return self.unique_streams_v6(result)
|
||||
|
||||
|
|
141
web/fl.html
141
web/fl.html
|
@ -3,64 +3,101 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>FLeast</title>
|
||||
<link rel="stylesheet" href="./style.css">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="shortcut icon" href="./favicon.ico" type="image/png">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<section id="header">
|
||||
<strong>
|
||||
<h1>FLeast - your least favourite streamers here</h1>
|
||||
</strong>
|
||||
</section>
|
||||
<section id="pageContent">
|
||||
<article>
|
||||
<h2>Usage:</h2>
|
||||
<p>
|
||||
Write the name of the game and select the language.<br>
|
||||
The name must _exactly_ match the name of the game on twitch.tv
|
||||
i.e. csgo must be "Counter-Strike: Global Offensive".<br><br>
|
||||
<b>IRL is back!</b> Sort of.<br>
|
||||
You can type <b>IRL</b> in game field and it will output streams from
|
||||
<i>Just Chatting</i>, <i>Travel & Outdoors</i>, etc...
|
||||
</p>
|
||||
<form method="get" action="./">
|
||||
<table class="Input">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<label>Game </label>
|
||||
</td>
|
||||
<td>
|
||||
<label>Language </label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<input name="game" type="text" maxlength="255" value=""/>
|
||||
</td>
|
||||
<td>
|
||||
<select class="element select medium" name="lang">
|
||||
{_opt_langs_}
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<input type="submit" value="Search" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
</article>
|
||||
<style>
|
||||
.container-sm {{
|
||||
max-width: 1450px;
|
||||
}}
|
||||
|
||||
</section>
|
||||
<footer>
|
||||
body {{
|
||||
background-color: #333333;
|
||||
}}
|
||||
|
||||
footer {{
|
||||
background-color: #AEC6CF;
|
||||
}}
|
||||
|
||||
.strname {{
|
||||
font-weight: bold;
|
||||
word-break: break-word;
|
||||
}}
|
||||
|
||||
.struser {{
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
color: #0f0
|
||||
}}
|
||||
|
||||
.strviews {{
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
color: #f00
|
||||
}}
|
||||
|
||||
.grid {{
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(320px, 340px));
|
||||
justify-content: space-around;
|
||||
row-gap: 30px;
|
||||
}}
|
||||
</style>
|
||||
|
||||
<body>
|
||||
<div class="container-sm text-white">
|
||||
<br>
|
||||
<h1>Fleast — find new favourite small streamer</h1>
|
||||
<hr>
|
||||
<p>
|
||||
Developed by Alex Vanin | <a href="https://twitter.com/AlexVanin" target="_blank">twitter</a> |
|
||||
<a href="https://github.com/AlexVanin/fleast" target="_blank">github</a><br>
|
||||
Version: {_version_}
|
||||
Search streams by specifying category from twitch.tv and the
|
||||
language of the stream.<br>Streams will be sorted by <b>ascending</b> order.
|
||||
</p>
|
||||
</footer>
|
||||
<p>
|
||||
The category must be specified by <b>exact name</b>.<br>For example,
|
||||
if you are looking for <i>Counter-Strike</i>, then you should write
|
||||
<i>Counter-Strike: Global Offensive</i>.
|
||||
</p>
|
||||
<p>
|
||||
You can use <b>IRL</b> like in good old days.<br>The output will
|
||||
combine <i>Just Chatting</i>, <i>Travel & Outdoors</i>, and other categories.
|
||||
</p>
|
||||
<form method="get" action="./">
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
Category
|
||||
</td>
|
||||
<td>
|
||||
Language
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<input name="game" type="text" class="form-control-sm" maxlength="255" value="" placeholder="Specify category"/>
|
||||
</td>
|
||||
<td>
|
||||
<select name="lang" class="form-control-sm">
|
||||
{_opt_langs_}
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<input type="submit" value="Search" class="btn btn-primary btn-sm"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
<hr>
|
||||
<footer>
|
||||
<div class="text-end text-black p-2">
|
||||
Developed by Alexey Vanin | <a href="https://twitter.com/AlexVanin" target="_blank">twitter</a> |
|
||||
<a href="https://git.alexvan.in/alexvanin/fleast" target="_blank">source code</a><br>
|
||||
Version: {_version_}
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
<option value="ru" {}>russian</option>
|
||||
<option value="en" {}>english</option>
|
||||
<option value="de" {}>german</option>
|
||||
<option value="zh-tw" {}>chinese (TW)</option>
|
||||
<option value="fi" {}>finnish</option>
|
||||
<option value="fr" {}>french</option>
|
||||
<option value="it" {}>italian</option>
|
||||
<option value="ja" {}>japanese</option>
|
||||
<option value="ko" {}>korean</option>
|
||||
<option value="no" {}>norwegian</option>
|
||||
<option value="pl" {}>polish</option>
|
||||
<option value="es" {}>spanish</option>
|
||||
<option value="sv" {}>swedish</option>
|
||||
<option value="ru" {}>🇷🇺Russian</option>
|
||||
<option value="en" {}>🇬🇧English </option>
|
||||
<option value="de" {}>🇩🇪German </option>
|
||||
<option value="es" {}>🇪🇸Spanish</option>
|
||||
<option value="fr" {}>🇫🇷French</option>
|
||||
<option value="pl" {}>🇵🇱Polish</option>
|
||||
<option value="ja" {}>🇯🇵Japanese</option>
|
||||
<option value="zh-tw" {}>🇨🇳Chinese</option>
|
||||
<option value="ko" {}>🇰🇷Korean</option>
|
||||
<option value="fi" {}>🇫🇮Finnish</option>
|
||||
<option value="no" {}>🇳🇴Norwegian</option>
|
||||
<option value="sv" {}>🇸🇪Swedish</option>
|
|
@ -3,66 +3,99 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>FLeast</title>
|
||||
<link rel="stylesheet" href="./style.css">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="shortcut icon" href="./favicon.ico" type="image/png">
|
||||
</head>
|
||||
|
||||
<style>
|
||||
.container-sm {{
|
||||
max-width: 1450px;
|
||||
}}
|
||||
|
||||
body {{
|
||||
background-color: #333333;
|
||||
}}
|
||||
|
||||
footer {{
|
||||
background-color: #AEC6CF;
|
||||
}}
|
||||
|
||||
.strname {{
|
||||
font-weight: bold;
|
||||
word-break: break-word;
|
||||
}}
|
||||
|
||||
.struser {{
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
color: #0f0
|
||||
}}
|
||||
|
||||
.strviews {{
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
color: #f00
|
||||
}}
|
||||
|
||||
.grid {{
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(320px, 340px));
|
||||
justify-content: space-around;
|
||||
row-gap: 30px;
|
||||
}}
|
||||
</style>
|
||||
|
||||
<body>
|
||||
<section id="header">
|
||||
<strong><h1>FLeast - your least favourite streamers here</h1></strong>
|
||||
</section>
|
||||
<section id="pageContent">
|
||||
<article>
|
||||
<h2>Usage:</h2>
|
||||
<p>
|
||||
Write the name of the game and select the language.<br>
|
||||
The name must _exactly_ match the name of the game on twitch.tv
|
||||
i.e. csgo must be "Counter-Strike: Global Offensive".<br><br>
|
||||
You can type <b>IRL</b> in game field and it will output streams from
|
||||
<i>Just Chatting</i>, <i>Travel & Outdoors</i>, etc...
|
||||
</p>
|
||||
<form method="get" action="./">
|
||||
<table class="Input">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<label>Game </label>
|
||||
</td>
|
||||
<td>
|
||||
<label>Language </label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<input name="game" type="text" maxlength="255" value=""/>
|
||||
</td>
|
||||
<td>
|
||||
<select class="element select medium" name="lang">
|
||||
<div class="container-sm text-white">
|
||||
<br>
|
||||
<h1>Fleast — find new favourite small streamer</h1>
|
||||
<hr>
|
||||
<p>
|
||||
The category must be specified by <b>exact name</b>.<br>Search
|
||||
<b>IRL</b> to find streams from <i>Just Chatting</i>,
|
||||
<i>Travel & Outdoors</i>, and other categories.
|
||||
</p>
|
||||
<form method="get" action="./">
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
Category
|
||||
</td>
|
||||
<td>
|
||||
Language
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<input name="game" type="text" class="form-control-sm" maxlength="255" value="{_game_name_}" placeholder="Specify category"/>
|
||||
</td>
|
||||
<td>
|
||||
<select name="lang" class="form-control-sm">
|
||||
{_opt_langs_}
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<input type="submit" value="Search" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<input type="submit" value="Search" class="btn btn-primary btn-sm"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
</article>
|
||||
<article>
|
||||
<h2>Found {_stream_num_} streams: </h2><br>
|
||||
<div class="container">
|
||||
</form>
|
||||
<hr>
|
||||
<h4> Found {_stream_num_} streams:</h4>
|
||||
<div class="grid">
|
||||
{_stream_list_}
|
||||
</div>
|
||||
<hr>
|
||||
<footer>
|
||||
<div class="text-end text-black p-2">
|
||||
Developed by Alexey Vanin | <a href="https://twitter.com/AlexVanin" target="_blank">twitter</a> |
|
||||
<a href="https://git.alexvan.in/alexvanin/fleast" target="_blank">source code</a><br>
|
||||
Version: {_version_}
|
||||
</p>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
<footer>
|
||||
<p>
|
||||
Developed by Alex Vanin | <a href="https://twitter.com/AlexVanin" target="_blank">twitter</a> |
|
||||
<a href="https://github.com/AlexVanin/fleast" target="_blank">github</a><br>
|
||||
Version: {_version_}
|
||||
</p>
|
||||
</footer>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="inner">
|
||||
<a href="{0}" target="_blank"><img src={1}></a><br>
|
||||
<div>
|
||||
<a href="{0}" target="_blank"><img src={1}></a>
|
||||
<div class="strname">{2}</div>
|
||||
<span class="struser">{3}</span>
|
||||
<span class="strviews"> :{4}</span>
|
||||
|
|
|
@ -1,99 +0,0 @@
|
|||
body{background: #333333; line-height:1; font-family: arial;}
|
||||
h1{font-size: 25px;}h2{font-size: 21px;}h3{font-size: 18px;}h4{font-size: 16px;}
|
||||
table{border-collapse:collapse;border-spacing:0}
|
||||
hr{display:block;height:1px;border:0;border-top:1px solid #ccc;margin:1em 0;padding:0}
|
||||
|
||||
#pageContent {
|
||||
margin:0;padding:0;border:0;outline:0
|
||||
max-width: 1000px;
|
||||
margin: auto;
|
||||
border: none;
|
||||
}
|
||||
|
||||
#header {
|
||||
padding:10px;
|
||||
|
||||
}
|
||||
|
||||
main {
|
||||
float: left;
|
||||
width: 60%;
|
||||
}
|
||||
aside {
|
||||
float: right;
|
||||
width: 30%;
|
||||
}
|
||||
article {
|
||||
border-bottom: 2px dotted #999;
|
||||
padding-bottom: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
article h2 {
|
||||
font-weight: normal;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
article p {
|
||||
|
||||
}
|
||||
main section {
|
||||
|
||||
}
|
||||
footer {
|
||||
background: #AEC6CF;
|
||||
max-width: 1000px;
|
||||
margin: auto;
|
||||
clear: both;
|
||||
text-align: right;
|
||||
}
|
||||
footer p {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
aside > div {
|
||||
margin: 10px auto;
|
||||
background: #AEC6CF;
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
body > section {
|
||||
max-width: 1000px;
|
||||
margin: auto;
|
||||
padding: 30px 0px;
|
||||
border-bottom: 1px solid #999;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
table.Input td, table.Input th {
|
||||
border: 0px solid #AAAAAA;
|
||||
padding: 2px 2px;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 0px 0px 0px 10px
|
||||
}
|
||||
|
||||
.inner {
|
||||
overflow: auto;
|
||||
word-wrap: break-word;
|
||||
background-color: transparent;
|
||||
width: 330px;
|
||||
padding: 0px 0px 30px 0px
|
||||
}
|
||||
|
||||
.strname {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.struser {
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
color: #0f0
|
||||
}
|
||||
.strviews{
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
color: #f00
|
||||
}
|
||||
|
Loading…
Add table
Reference in a new issue