Compare commits
10 commits
b2b48c2ea1
...
a2c4582a0c
Author | SHA1 | Date | |
---|---|---|---|
a2c4582a0c | |||
e322994842 | |||
5daa97af3d | |||
8d8226a5fa | |||
538f50847e | |||
821389605a | |||
ca2b4f4d59 | |||
be894106a3 | |||
6ca8f3cd3c | |||
6367ab7ffe |
9 changed files with 293 additions and 237 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,7 +1,6 @@
|
|||
__pycache__
|
||||
*.swp
|
||||
.token
|
||||
.oauth
|
||||
.secret
|
||||
server.conf
|
||||
.*.log
|
||||
|
|
28
README.md
28
README.md
|
@ -3,6 +3,34 @@ 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.
|
||||
- Unique filter for streams.
|
||||
|
||||
#### Added
|
||||
- Runtime ouath token generation.
|
||||
|
||||
#### Changed
|
||||
- Use threshold on amount of returned streams to resend requests.
|
||||
|
||||
#### Removed
|
||||
- v5 (kraken) API support.
|
||||
- Daemonizer component.
|
||||
|
||||
### 1.8
|
||||
#### Fixed
|
||||
- Search queries for v5 and v6 API
|
||||
|
||||
### 1.7
|
||||
- Fixed bug with displaying special symbols in stream name
|
||||
### 1.6
|
||||
|
|
10
main.py
10
main.py
|
@ -4,7 +4,7 @@
|
|||
import cherrypy
|
||||
from twitch import TwitchClient
|
||||
|
||||
ver = '1.9.0-pre-3'
|
||||
ver = '1.10.0'
|
||||
|
||||
|
||||
class FleastServer(object):
|
||||
|
@ -12,8 +12,8 @@ class FleastServer(object):
|
|||
try:
|
||||
with open('.token', 'r') as reader:
|
||||
self.twitch_token = reader.read().strip()
|
||||
with open('.oauth', 'r') as reader:
|
||||
self.oauth_token = reader.read().strip()
|
||||
with open('.secret', 'r') as reader:
|
||||
self.secret = reader.read().strip()
|
||||
with open('./web/fl.html', 'r') as reader:
|
||||
self.index_page = reader.read()
|
||||
with open('./web/fl_template_main.html', 'r') as reader:
|
||||
|
@ -22,7 +22,7 @@ class FleastServer(object):
|
|||
self.templ_stream = reader.read()
|
||||
with open('./web/fl_template_lang.html', 'r') as reader:
|
||||
self.templ_lang = reader.read().splitlines()
|
||||
self.client = TwitchClient(self.twitch_token, self.oauth_token, freq=1)
|
||||
self.client = TwitchClient(self.twitch_token, self.secret, freq=1)
|
||||
except:
|
||||
print("Cannot read token for twitch app or templates, abort.")
|
||||
exit(1)
|
||||
|
@ -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,
|
||||
|
|
85
twitch.py
85
twitch.py
|
@ -2,21 +2,54 @@ import cherrypy
|
|||
import requests
|
||||
import threading
|
||||
import time
|
||||
|
||||
from oauthlib.oauth2 import BackendApplicationClient
|
||||
from requests_oauthlib import OAuth2Session
|
||||
from urllib.parse import quote
|
||||
|
||||
|
||||
class TwitchClient:
|
||||
def __init__(self, token, oauth, freq=2):
|
||||
def __init__(self, token, secret, freq=2):
|
||||
self.token = token
|
||||
self.oauth = oauth
|
||||
self.lock = threading.Lock()
|
||||
|
||||
self.header_v6 = {'Client-ID': self.token, 'Authorization': 'Bearer ' + self.oauth}
|
||||
self.header_v6 = {'Client-ID': self.token}
|
||||
self.urlbase_v6 = 'https://api.twitch.tv/helix'
|
||||
|
||||
self.last_q = time.time()
|
||||
self.delay = 1 / freq
|
||||
|
||||
# authentication
|
||||
self.oauth = ''
|
||||
self.oauth_token_url = 'https://id.twitch.tv/oauth2/token'
|
||||
self.auth_secret = secret
|
||||
oauth_clint = BackendApplicationClient(client_id=self.token)
|
||||
self.oauth_session = OAuth2Session(client=oauth_clint)
|
||||
self.update_oauth()
|
||||
|
||||
def update_oauth(self):
|
||||
"""
|
||||
Update self.oauth token based on client id and secret.
|
||||
:return: nothing
|
||||
"""
|
||||
token = self.oauth_session.fetch_token(token_url=self.oauth_token_url,
|
||||
client_secret=self.auth_secret,
|
||||
include_client_id=True)
|
||||
self.oauth = token['access_token']
|
||||
|
||||
def do_q_auth_v6(self, base, header):
|
||||
"""
|
||||
Do query with v6 authentication header and single retry.
|
||||
:param base: string with requesting URL
|
||||
:param header: dictionary of http headers
|
||||
:return: string with response or None
|
||||
"""
|
||||
result = self.do_q(base, header | {'Authorization': 'Bearer ' + self.oauth})
|
||||
if result is not None:
|
||||
return result
|
||||
self.update_oauth()
|
||||
return self.do_q(base, header | {'Authorization': 'Bearer ' + self.oauth})
|
||||
|
||||
def do_q(self, base, header):
|
||||
"""
|
||||
Do query for twitch server
|
||||
|
@ -31,8 +64,13 @@ class TwitchClient:
|
|||
if delta < self.delay:
|
||||
time.sleep(delta) # Sleep remaining time
|
||||
r = requests.get(base, headers=header).json()
|
||||
error_message = r.get("error", "")
|
||||
if len(error_message) > 0:
|
||||
cherrypy.log('Request: fail with error "%s"' % error_message)
|
||||
r = None
|
||||
else:
|
||||
cherrypy.log('Request: OK')
|
||||
self.last_q = time.time()
|
||||
cherrypy.log('Request: OK')
|
||||
except requests.exceptions.RequestException as e:
|
||||
cherrypy.log('Request: FAIL')
|
||||
cherrypy.log('Error: {}'.format(e))
|
||||
|
@ -62,7 +100,7 @@ class TwitchClient:
|
|||
:return: string with get query result or None
|
||||
"""
|
||||
header, base = self.get_base('v6')
|
||||
return self.do_q(base + q, header)
|
||||
return self.do_q_auth_v6(base + q, header)
|
||||
|
||||
def get_game_id_v6(self, name):
|
||||
"""
|
||||
|
@ -72,7 +110,7 @@ class TwitchClient:
|
|||
"""
|
||||
|
||||
header, base = self.get_base('v6')
|
||||
r = self.do_q('{}/games?name={}'.format(base, name), header)
|
||||
r = self.do_q_auth_v6('{}/games?name={}'.format(base, name), header)
|
||||
if r and r.get('data'):
|
||||
return r['data'][0]['id'], r['data'][0]['name']
|
||||
|
||||
|
@ -92,11 +130,11 @@ class TwitchClient:
|
|||
header, base = self.get_base('v6')
|
||||
init_q_template = "{}/streams?language={}&first={}&game_id={}"
|
||||
q_template = "{}/streams?language={}&first={}&after={}&game_id={}"
|
||||
data = self.do_q(init_q_template.format(base, lang, 100, game_id[0]), header)
|
||||
data = self.do_q_auth_v6(init_q_template.format(base, lang, 100, game_id[0]), 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'])
|
||||
data = self.do_q(q_template.format(base, lang, 100, data['pagination']['cursor'], game_id[0]), header)
|
||||
data = self.do_q_auth_v6(q_template.format(base, lang, 100, data['pagination']['cursor'], game_id[0]), header)
|
||||
return self.unique_streams_v6(result)
|
||||
|
||||
def get_irl_live_streams_v6(self, lang):
|
||||
|
@ -105,17 +143,38 @@ 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(init_q_template.format(base, lang, 100, game_id), header)
|
||||
result['streams'].extend(data['data'])
|
||||
data = self.do_q_auth_v6(init_q_template.format(base, lang, 100, game_id), header)
|
||||
while len(data.get('data', [])) > 0: # there must be non zero value, but search is kinda broken now
|
||||
result['streams'].extend(data['data'])
|
||||
data = self.do_q(q_template.format(base, lang, 100, data['pagination']['cursor'], game_id), header)
|
||||
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)
|
||||
|
||||
def unique_streams_v6(self, 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://github.com/AlexVanin/fleast" target="_blank">github</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://github.com/AlexVanin/fleast" target="_blank">github</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…
Reference in a new issue