epii-1 commited on
Commit
64fee2d
·
1 Parent(s): 5112131

修改node

Browse files
Files changed (10) hide show
  1. Dockerfile +57 -7
  2. README.md +1 -3
  3. app.py +0 -162
  4. login.js +161 -0
  5. package.json +14 -0
  6. requirements.txt +0 -6
  7. server.js +116 -0
  8. shared.py +0 -6
  9. styles.css +0 -12
  10. tips.csv +0 -245
Dockerfile CHANGED
@@ -1,13 +1,63 @@
1
- FROM python:3.12
2
 
3
- WORKDIR /code
4
 
5
- COPY ./requirements.txt /code/requirements.txt
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
- RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
 
8
 
9
- COPY . .
 
10
 
11
- EXPOSE 7860
12
 
13
- CMD ["shiny", "run", "app.py", "--host", "0.0.0.0", "--port", "7860"]
 
 
 
 
 
 
 
1
+ FROM node:20-slim
2
 
3
+ WORKDIR /app
4
 
5
+ RUN apt-get update && apt-get install -y \
6
+ wget \
7
+ gnupg \
8
+ ca-certificates \
9
+ libgconf-2-4 \
10
+ libxss1 \
11
+ libappindicator1 \
12
+ libasound2 \
13
+ libatk1.0-0 \
14
+ libc6 \
15
+ libcairo2 \
16
+ libcups2 \
17
+ libdbus-1-3 \
18
+ libexpat1 \
19
+ libfontconfig1 \
20
+ libgcc1 \
21
+ libgdk-pixbuf2.0-0 \
22
+ libglib2.0-0 \
23
+ libgtk-3-0 \
24
+ libnspr4 \
25
+ libpango-1.0-0 \
26
+ libpangocairo-1.0-0 \
27
+ libstdc++6 \
28
+ libx11-6 \
29
+ libx11-xcb1 \
30
+ libxcb1 \
31
+ libxcomposite1 \
32
+ libxcursor1 \
33
+ libxdamage1 \
34
+ libxext6 \
35
+ libxfixes3 \
36
+ libxi6 \
37
+ libxrandr2 \
38
+ libxrender1 \
39
+ libxss1 \
40
+ libxtst6 \
41
+ lsb-release \
42
+ xdg-utils \
43
+ && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | gpg --dearmor -o /usr/share/keyrings/google-chrome.gpg \
44
+ && echo "deb [arch=amd64 signed-by=/usr/share/keyrings/google-chrome.gpg] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list \
45
+ && apt-get update \
46
+ && apt-get install -y google-chrome-stable \
47
+ && rm -rf /var/lib/apt/lists/*
48
 
49
+ # 创建 logs 目录并更改其权限
50
+ RUN mkdir -p /app/logs && chown -R node:node /app
51
 
52
+ # 切换到非 root 用户
53
+ USER node
54
 
55
+ COPY --chown=node:node package*.json ./
56
 
57
+ RUN npm install
58
+
59
+ COPY --chown=node:node . .
60
+
61
+ EXPOSE 8080
62
+
63
+ CMD ["node", "server.js"]
README.md CHANGED
@@ -6,6 +6,7 @@ colorTo: indigo
6
  sdk: docker
7
  pinned: false
8
  license: apache-2.0
 
9
  ---
10
 
11
  This is a templated Space for [Shiny for Python](https://shiny.rstudio.com/py/).
@@ -13,8 +14,5 @@ This is a templated Space for [Shiny for Python](https://shiny.rstudio.com/py/).
13
 
14
  To get started with a new app do the following:
15
 
16
- 1) Install Shiny with `pip install shiny`
17
- 2) Create a new app with `shiny create`
18
- 3) Then run the app with `shiny run --reload`
19
 
20
  To learn more about this framework please see the [Documentation](https://shiny.rstudio.com/py/docs/overview.html).
 
6
  sdk: docker
7
  pinned: false
8
  license: apache-2.0
9
+ app_port: 8080
10
  ---
11
 
12
  This is a templated Space for [Shiny for Python](https://shiny.rstudio.com/py/).
 
14
 
15
  To get started with a new app do the following:
16
 
 
 
 
17
 
18
  To learn more about this framework please see the [Documentation](https://shiny.rstudio.com/py/docs/overview.html).
app.py DELETED
@@ -1,162 +0,0 @@
1
- import faicons as fa
2
- import plotly.express as px
3
-
4
- # Load data and compute static values
5
- from shared import app_dir, tips
6
- from shinywidgets import render_plotly
7
-
8
- from shiny import reactive, render
9
- from shiny.express import input, ui
10
-
11
- bill_rng = (min(tips.total_bill), max(tips.total_bill))
12
-
13
- # Add page title and sidebar
14
- ui.page_opts(title="Restaurant tipping", fillable=True)
15
-
16
- with ui.sidebar(open="desktop"):
17
- ui.input_slider(
18
- "total_bill",
19
- "Bill amount",
20
- min=bill_rng[0],
21
- max=bill_rng[1],
22
- value=bill_rng,
23
- pre="$",
24
- )
25
- ui.input_checkbox_group(
26
- "time",
27
- "Food service",
28
- ["Lunch", "Dinner"],
29
- selected=["Lunch", "Dinner"],
30
- inline=True,
31
- )
32
- ui.input_action_button("reset", "Reset filter")
33
-
34
- # Add main content
35
- ICONS = {
36
- "user": fa.icon_svg("user", "regular"),
37
- "wallet": fa.icon_svg("wallet"),
38
- "currency-dollar": fa.icon_svg("dollar-sign"),
39
- "ellipsis": fa.icon_svg("ellipsis"),
40
- }
41
-
42
- with ui.layout_columns(fill=False):
43
- with ui.value_box(showcase=ICONS["user"]):
44
- "Total tippers"
45
-
46
- @render.express
47
- def total_tippers():
48
- tips_data().shape[0]
49
-
50
- with ui.value_box(showcase=ICONS["wallet"]):
51
- "Average tip"
52
-
53
- @render.express
54
- def average_tip():
55
- d = tips_data()
56
- if d.shape[0] > 0:
57
- perc = d.tip / d.total_bill
58
- f"{perc.mean():.1%}"
59
-
60
- with ui.value_box(showcase=ICONS["currency-dollar"]):
61
- "Average bill"
62
-
63
- @render.express
64
- def average_bill():
65
- d = tips_data()
66
- if d.shape[0] > 0:
67
- bill = d.total_bill.mean()
68
- f"${bill:.2f}"
69
-
70
-
71
- with ui.layout_columns(col_widths=[6, 6, 12]):
72
- with ui.card(full_screen=True):
73
- ui.card_header("Tips data")
74
-
75
- @render.data_frame
76
- def table():
77
- return render.DataGrid(tips_data())
78
-
79
- with ui.card(full_screen=True):
80
- with ui.card_header(class_="d-flex justify-content-between align-items-center"):
81
- "Total bill vs tip"
82
- with ui.popover(title="Add a color variable", placement="top"):
83
- ICONS["ellipsis"]
84
- ui.input_radio_buttons(
85
- "scatter_color",
86
- None,
87
- ["none", "sex", "smoker", "day", "time"],
88
- inline=True,
89
- )
90
-
91
- @render_plotly
92
- def scatterplot():
93
- color = input.scatter_color()
94
- return px.scatter(
95
- tips_data(),
96
- x="total_bill",
97
- y="tip",
98
- color=None if color == "none" else color,
99
- trendline="lowess",
100
- )
101
-
102
- with ui.card(full_screen=True):
103
- with ui.card_header(class_="d-flex justify-content-between align-items-center"):
104
- "Tip percentages"
105
- with ui.popover(title="Add a color variable"):
106
- ICONS["ellipsis"]
107
- ui.input_radio_buttons(
108
- "tip_perc_y",
109
- "Split by:",
110
- ["sex", "smoker", "day", "time"],
111
- selected="day",
112
- inline=True,
113
- )
114
-
115
- @render_plotly
116
- def tip_perc():
117
- from ridgeplot import ridgeplot
118
-
119
- dat = tips_data()
120
- dat["percent"] = dat.tip / dat.total_bill
121
- yvar = input.tip_perc_y()
122
- uvals = dat[yvar].unique()
123
-
124
- samples = [[dat.percent[dat[yvar] == val]] for val in uvals]
125
-
126
- plt = ridgeplot(
127
- samples=samples,
128
- labels=uvals,
129
- bandwidth=0.01,
130
- colorscale="viridis",
131
- colormode="row-index",
132
- )
133
-
134
- plt.update_layout(
135
- legend=dict(
136
- orientation="h", yanchor="bottom", y=1.02, xanchor="center", x=0.5
137
- )
138
- )
139
-
140
- return plt
141
-
142
-
143
- ui.include_css(app_dir / "styles.css")
144
-
145
- # --------------------------------------------------------
146
- # Reactive calculations and effects
147
- # --------------------------------------------------------
148
-
149
-
150
- @reactive.calc
151
- def tips_data():
152
- bill = input.total_bill()
153
- idx1 = tips.total_bill.between(bill[0], bill[1])
154
- idx2 = tips.time.isin(input.time())
155
- return tips[idx1 & idx2]
156
-
157
-
158
- @reactive.effect
159
- @reactive.event(input.reset)
160
- def _():
161
- ui.update_slider("total_bill", value=bill_rng)
162
- ui.update_checkbox_group("time", selected=["Lunch", "Dinner"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
login.js ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const puppeteer = require('puppeteer');
2
+ const axios = require('axios');
3
+
4
+ // Telegram 配置
5
+ let telegramConfig;
6
+ try {
7
+ telegramConfig = JSON.parse(process.env.TELEGRAM_JSON || '{}');
8
+ } catch (error) {
9
+ console.error('Error parsing TELEGRAM_JSON:', error);
10
+ }
11
+
12
+ function formatToISO(date) {
13
+ return date.toISOString().replace('T', ' ').replace('Z', '').replace(/\.\d{3}Z/, '');
14
+ }
15
+
16
+ async function delayTime(ms) {
17
+ return new Promise(resolve => setTimeout(resolve, ms));
18
+ }
19
+
20
+ async function sendTelegramMessage(message) {
21
+ if (!telegramConfig || !telegramConfig.cloudflareWorkerUrl || !telegramConfig.telegramBotToken || !telegramConfig.telegramBotUserId || !telegramConfig.customAuthKey) {
22
+ console.log('Telegram configuration not set or incomplete, skipping notification');
23
+ return;
24
+ }
25
+
26
+ console.log('Attempting to send Telegram message...');
27
+ const url = `${telegramConfig.cloudflareWorkerUrl}/${telegramConfig.telegramBotToken}/sendMessage`;
28
+ try {
29
+ const response = await axios.post(url, {
30
+ chat_id: telegramConfig.telegramBotUserId,
31
+ text: message
32
+ }, {
33
+ headers: {
34
+ 'X-Custom-Auth': telegramConfig.customAuthKey
35
+ }
36
+ });
37
+ console.log('Telegram notification sent successfully');
38
+ } catch (error) {
39
+ console.error('Error sending Telegram notification:', error.response ? error.response.data : error.message);
40
+ }
41
+ }
42
+
43
+ async function loginAccount(account, browser) {
44
+ const { username, password, panelnum, type } = account;
45
+ const page = await browser.newPage();
46
+
47
+ let url = type === 'ct8'
48
+ ? 'https://panel.ct8.pl/login/?next=/'
49
+ : `https://panel${panelnum}.serv00.com/login/?next=/`;
50
+
51
+ try {
52
+ // 设置自定义 headers 来绕过 Cloudflare
53
+ await page.setExtraHTTPHeaders({
54
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
55
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
56
+ 'Accept-Language': 'en-US,en;q=0.5',
57
+ 'Accept-Encoding': 'gzip, deflate, br',
58
+ 'Connection': 'keep-alive',
59
+ 'Upgrade-Insecure-Requests': '1',
60
+ 'Sec-Fetch-Dest': 'document',
61
+ 'Sec-Fetch-Mode': 'navigate',
62
+ 'Sec-Fetch-Site': 'none',
63
+ 'Sec-Fetch-User': '?1',
64
+ 'Cache-Control': 'max-age=0',
65
+ });
66
+
67
+ await page.goto(url, { waitUntil: 'networkidle0' });
68
+
69
+ const usernameInput = await page.$('#id_username');
70
+ if (usernameInput) {
71
+ await usernameInput.click({ clickCount: 3 });
72
+ await usernameInput.press('Backspace');
73
+ }
74
+
75
+ await page.type('#id_username', username);
76
+ await page.type('#id_password', password);
77
+
78
+ const loginButton = await page.$('#submit');
79
+ if (loginButton) {
80
+ await loginButton.click();
81
+ } else {
82
+ throw new Error('无法找到登录按钮');
83
+ }
84
+
85
+ await page.waitForNavigation({ timeout: 30000 });
86
+
87
+ const isLoggedIn = await page.evaluate(() => {
88
+ const logoutButton = document.querySelector('a[href="/logout/"]');
89
+ return logoutButton !== null;
90
+ });
91
+
92
+ const nowUtc = formatToISO(new Date());
93
+ const nowBeijing = formatToISO(new Date(new Date().getTime() + 8 * 60 * 60 * 1000));
94
+
95
+ if (isLoggedIn) {
96
+ const message = `账号 ${username} (${type}) 于北京时间 ${nowBeijing}(UTC时间 ${nowUtc})登录成功!`;
97
+ console.log(message);
98
+ await sendTelegramMessage(`${type}, [${nowBeijing.split(' ')[1].split('.')[0]}]\n${message}`);
99
+ return { success: true, message };
100
+ } else {
101
+ const message = `账号 ${username} (${type}) 登录失败,请检查账号和密码是否正确。`;
102
+ console.error(message);
103
+ await sendTelegramMessage(`${type}, [${nowBeijing.split(' ')[1].split('.')[0]}]\n${message}`);
104
+ return { success: false, message };
105
+ }
106
+ } catch (error) {
107
+ const message = `账号 ${username} (${type}) 登录时出现错误: ${error}`;
108
+ console.error(message);
109
+ await sendTelegramMessage(`${type}, [${formatToISO(new Date()).split(' ')[1].split('.')[0]}]\n${message}`);
110
+ return { success: false, message };
111
+ } finally {
112
+ await page.close();
113
+ }
114
+ }
115
+
116
+ (async () => {
117
+ const accountsJson = process.env.ACCOUNTS_JSON;
118
+ const accounts = JSON.parse(accountsJson);
119
+
120
+ const browser = await puppeteer.launch({
121
+ headless: true,
122
+ args: ['--no-sandbox', '--disable-setuid-sandbox'],
123
+ executablePath: '/usr/bin/google-chrome-stable'
124
+ });
125
+
126
+ const results = [];
127
+ for (const account of accounts) {
128
+ const result = await loginAccount(account, browser);
129
+ results.push({ ...account, ...result });
130
+
131
+ const delay = Math.floor(Math.random() * 8000) + 1000;
132
+ await delayTime(delay);
133
+ }
134
+
135
+ await browser.close();
136
+
137
+ const successfulLogins = results.filter(r => r.success);
138
+ const failedLogins = results.filter(r => !r.success);
139
+
140
+ // 生成表格形式的输出
141
+ console.log('\n登录结果汇总:');
142
+ console.log('| 账号 | 类型 | 状态 | 消息 |');
143
+ console.log('|------|------|------|------|');
144
+ results.forEach(({ username, type, success, message }) => {
145
+ console.log(`| ${username} | ${type} | ${success ? '成功' : '失败'} | ${message} |`);
146
+ });
147
+
148
+ let summaryMessage = '\n登录结果统计:\n';
149
+ summaryMessage += `成功登录的账号:${successfulLogins.length}\n`;
150
+ summaryMessage += `登录失败的账号:${failedLogins.length}\n`;
151
+
152
+ if (failedLogins.length > 0) {
153
+ summaryMessage += '\n登录失败的账号列表:\n';
154
+ failedLogins.forEach(({ username, type }) => {
155
+ summaryMessage += `- ${username} (${type})\n`;
156
+ });
157
+ }
158
+
159
+ console.log(summaryMessage);
160
+ await sendTelegramMessage(`${accounts[0].type}, [${formatToISO(new Date()).split(' ')[1].split('.')[0]}]\n${summaryMessage}`);
161
+ })();
package.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "account-keep-alive",
3
+ "version": "1.0.0",
4
+ "description": "Auto-login account keep-alive script",
5
+ "main": "server.js",
6
+ "dependencies": {
7
+ "puppeteer": "^10.0.0",
8
+ "express": "^4.17.1",
9
+ "axios": "^0.21.1"
10
+ },
11
+ "scripts": {
12
+ "start": "node server.js"
13
+ }
14
+ }
requirements.txt DELETED
@@ -1,6 +0,0 @@
1
- faicons
2
- shiny
3
- shinywidgets
4
- plotly
5
- pandas
6
- ridgeplot
 
 
 
 
 
 
 
server.js ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const { exec } = require('child_process');
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const app = express();
6
+ const port = process.env.PORT || 8080;
7
+
8
+ // 使用 HOME 环境变量来创建日志目录
9
+ const logDir = path.join(process.env.HOME, 'logs');
10
+ if (!fs.existsSync(logDir)) {
11
+ fs.mkdirSync(logDir, { recursive: true });
12
+ }
13
+
14
+ let lastRunTime = null;
15
+ let nextRunTime = null;
16
+ let lastRunResults = null;
17
+
18
+ function log(message) {
19
+ const timestamp = new Date().toISOString();
20
+ const logMessage = `${timestamp}: ${message}\n`;
21
+ console.log(logMessage);
22
+ fs.appendFileSync(path.join(logDir, 'app.log'), logMessage);
23
+ }
24
+
25
+ app.get('/', (req, res) => {
26
+ let tableHtml = '';
27
+ let summaryHtml = '';
28
+
29
+ if (lastRunResults) {
30
+ tableHtml = '<h2>上次登录结果:</h2>';
31
+ tableHtml += '<table border="1"><tr><th>账号</th><th>类型</th><th>状态</th><th>消息</th></tr>';
32
+ lastRunResults.forEach(result => {
33
+ tableHtml += `<tr><td>${result.username}</td><td>${result.type}</td><td>${result.success ? '成功' : '失败'}</td><td>${result.message}</td></tr>`;
34
+ });
35
+ tableHtml += '</table>';
36
+
37
+ const successfulLogins = lastRunResults.filter(r => r.success);
38
+ const failedLogins = lastRunResults.filter(r => !r.success);
39
+
40
+ summaryHtml = '<h2>登录结果统计:</h2>';
41
+ summaryHtml += `<p>成功登录的账号:${successfulLogins.length}</p>`;
42
+ summaryHtml += `<p>登录失败的账号:${failedLogins.length}</p>`;
43
+
44
+ if (failedLogins.length > 0) {
45
+ summaryHtml += '<h3>登录失败的账号列表:</h3><ul>';
46
+ failedLogins.forEach(({ username, type }) => {
47
+ summaryHtml += `<li>${username} (${type})</li>`;
48
+ });
49
+ summaryHtml += '</ul>';
50
+ }
51
+ }
52
+
53
+ res.send(`
54
+ <h1>登录脚本状态</h1>
55
+ <p>上次执行时间:${lastRunTime || '尚未执行'}</p>
56
+ <p>下次执行时间:${nextRunTime || '未设置'}</p>
57
+ <a href="/run">立即执行脚本</a>
58
+ <br><br>
59
+ <a href="/logs">查看日志</a>
60
+ ${tableHtml}
61
+ ${summaryHtml}
62
+ `);
63
+ });
64
+
65
+ app.get('/logs', (req, res) => {
66
+ const logPath = path.join(logDir, 'app.log');
67
+ fs.readFile(logPath, 'utf8', (err, data) => {
68
+ if (err) {
69
+ res.status(500).send('无法读取日志文件');
70
+ return;
71
+ }
72
+ res.send(`<pre>${data}</pre>`);
73
+ });
74
+ });
75
+
76
+ function runLoginScript() {
77
+ log('开始执行登录脚本');
78
+ exec('node login.js', (error, stdout, stderr) => {
79
+ if (error) {
80
+ log(`执行错误: ${error.message}`);
81
+ return;
82
+ }
83
+ if (stderr) {
84
+ log(`脚本错误输出: ${stderr}`);
85
+ return;
86
+ }
87
+ log(`脚本输出:\n${stdout}`);
88
+
89
+ // 解析输出以获取结果
90
+ const lines = stdout.split('\n');
91
+ const resultStartIndex = lines.findIndex(line => line.includes('| 账号 | 类型 | 状态 | 消息 |'));
92
+ if (resultStartIndex !== -1) {
93
+ lastRunResults = lines.slice(resultStartIndex + 2)
94
+ .filter(line => line.trim().startsWith('|'))
95
+ .map(line => {
96
+ const [, username, type, status, message] = line.split('|').map(item => item.trim());
97
+ return { username, type, success: status === '成功', message };
98
+ });
99
+ }
100
+ });
101
+ lastRunTime = new Date().toISOString();
102
+ nextRunTime = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString();
103
+ }
104
+
105
+ app.get('/run', (req, res) => {
106
+ runLoginScript();
107
+ res.send('脚本执行已启动,请稍后刷新页面查看结果。');
108
+ });
109
+
110
+ // 每7天自动运行一次脚本
111
+ setInterval(runLoginScript, 7 * 24 * 60 * 60 * 1000);
112
+
113
+ app.listen(port, () => {
114
+ log(`服务器运行在 http://localhost:${port}`);
115
+ runLoginScript(); // 启动时立即运行一次
116
+ });
shared.py DELETED
@@ -1,6 +0,0 @@
1
- from pathlib import Path
2
-
3
- import pandas as pd
4
-
5
- app_dir = Path(__file__).parent
6
- tips = pd.read_csv(app_dir / "tips.csv")
 
 
 
 
 
 
 
styles.css DELETED
@@ -1,12 +0,0 @@
1
- :root {
2
- --bslib-sidebar-main-bg: #f8f8f8;
3
- }
4
-
5
- .popover {
6
- --bs-popover-header-bg: #222;
7
- --bs-popover-header-color: #fff;
8
- }
9
-
10
- .popover .btn-close {
11
- filter: var(--bs-btn-close-white-filter);
12
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
tips.csv DELETED
@@ -1,245 +0,0 @@
1
- total_bill,tip,sex,smoker,day,time,size
2
- 16.99,1.01,Female,No,Sun,Dinner,2
3
- 10.34,1.66,Male,No,Sun,Dinner,3
4
- 21.01,3.5,Male,No,Sun,Dinner,3
5
- 23.68,3.31,Male,No,Sun,Dinner,2
6
- 24.59,3.61,Female,No,Sun,Dinner,4
7
- 25.29,4.71,Male,No,Sun,Dinner,4
8
- 8.77,2.0,Male,No,Sun,Dinner,2
9
- 26.88,3.12,Male,No,Sun,Dinner,4
10
- 15.04,1.96,Male,No,Sun,Dinner,2
11
- 14.78,3.23,Male,No,Sun,Dinner,2
12
- 10.27,1.71,Male,No,Sun,Dinner,2
13
- 35.26,5.0,Female,No,Sun,Dinner,4
14
- 15.42,1.57,Male,No,Sun,Dinner,2
15
- 18.43,3.0,Male,No,Sun,Dinner,4
16
- 14.83,3.02,Female,No,Sun,Dinner,2
17
- 21.58,3.92,Male,No,Sun,Dinner,2
18
- 10.33,1.67,Female,No,Sun,Dinner,3
19
- 16.29,3.71,Male,No,Sun,Dinner,3
20
- 16.97,3.5,Female,No,Sun,Dinner,3
21
- 20.65,3.35,Male,No,Sat,Dinner,3
22
- 17.92,4.08,Male,No,Sat,Dinner,2
23
- 20.29,2.75,Female,No,Sat,Dinner,2
24
- 15.77,2.23,Female,No,Sat,Dinner,2
25
- 39.42,7.58,Male,No,Sat,Dinner,4
26
- 19.82,3.18,Male,No,Sat,Dinner,2
27
- 17.81,2.34,Male,No,Sat,Dinner,4
28
- 13.37,2.0,Male,No,Sat,Dinner,2
29
- 12.69,2.0,Male,No,Sat,Dinner,2
30
- 21.7,4.3,Male,No,Sat,Dinner,2
31
- 19.65,3.0,Female,No,Sat,Dinner,2
32
- 9.55,1.45,Male,No,Sat,Dinner,2
33
- 18.35,2.5,Male,No,Sat,Dinner,4
34
- 15.06,3.0,Female,No,Sat,Dinner,2
35
- 20.69,2.45,Female,No,Sat,Dinner,4
36
- 17.78,3.27,Male,No,Sat,Dinner,2
37
- 24.06,3.6,Male,No,Sat,Dinner,3
38
- 16.31,2.0,Male,No,Sat,Dinner,3
39
- 16.93,3.07,Female,No,Sat,Dinner,3
40
- 18.69,2.31,Male,No,Sat,Dinner,3
41
- 31.27,5.0,Male,No,Sat,Dinner,3
42
- 16.04,2.24,Male,No,Sat,Dinner,3
43
- 17.46,2.54,Male,No,Sun,Dinner,2
44
- 13.94,3.06,Male,No,Sun,Dinner,2
45
- 9.68,1.32,Male,No,Sun,Dinner,2
46
- 30.4,5.6,Male,No,Sun,Dinner,4
47
- 18.29,3.0,Male,No,Sun,Dinner,2
48
- 22.23,5.0,Male,No,Sun,Dinner,2
49
- 32.4,6.0,Male,No,Sun,Dinner,4
50
- 28.55,2.05,Male,No,Sun,Dinner,3
51
- 18.04,3.0,Male,No,Sun,Dinner,2
52
- 12.54,2.5,Male,No,Sun,Dinner,2
53
- 10.29,2.6,Female,No,Sun,Dinner,2
54
- 34.81,5.2,Female,No,Sun,Dinner,4
55
- 9.94,1.56,Male,No,Sun,Dinner,2
56
- 25.56,4.34,Male,No,Sun,Dinner,4
57
- 19.49,3.51,Male,No,Sun,Dinner,2
58
- 38.01,3.0,Male,Yes,Sat,Dinner,4
59
- 26.41,1.5,Female,No,Sat,Dinner,2
60
- 11.24,1.76,Male,Yes,Sat,Dinner,2
61
- 48.27,6.73,Male,No,Sat,Dinner,4
62
- 20.29,3.21,Male,Yes,Sat,Dinner,2
63
- 13.81,2.0,Male,Yes,Sat,Dinner,2
64
- 11.02,1.98,Male,Yes,Sat,Dinner,2
65
- 18.29,3.76,Male,Yes,Sat,Dinner,4
66
- 17.59,2.64,Male,No,Sat,Dinner,3
67
- 20.08,3.15,Male,No,Sat,Dinner,3
68
- 16.45,2.47,Female,No,Sat,Dinner,2
69
- 3.07,1.0,Female,Yes,Sat,Dinner,1
70
- 20.23,2.01,Male,No,Sat,Dinner,2
71
- 15.01,2.09,Male,Yes,Sat,Dinner,2
72
- 12.02,1.97,Male,No,Sat,Dinner,2
73
- 17.07,3.0,Female,No,Sat,Dinner,3
74
- 26.86,3.14,Female,Yes,Sat,Dinner,2
75
- 25.28,5.0,Female,Yes,Sat,Dinner,2
76
- 14.73,2.2,Female,No,Sat,Dinner,2
77
- 10.51,1.25,Male,No,Sat,Dinner,2
78
- 17.92,3.08,Male,Yes,Sat,Dinner,2
79
- 27.2,4.0,Male,No,Thur,Lunch,4
80
- 22.76,3.0,Male,No,Thur,Lunch,2
81
- 17.29,2.71,Male,No,Thur,Lunch,2
82
- 19.44,3.0,Male,Yes,Thur,Lunch,2
83
- 16.66,3.4,Male,No,Thur,Lunch,2
84
- 10.07,1.83,Female,No,Thur,Lunch,1
85
- 32.68,5.0,Male,Yes,Thur,Lunch,2
86
- 15.98,2.03,Male,No,Thur,Lunch,2
87
- 34.83,5.17,Female,No,Thur,Lunch,4
88
- 13.03,2.0,Male,No,Thur,Lunch,2
89
- 18.28,4.0,Male,No,Thur,Lunch,2
90
- 24.71,5.85,Male,No,Thur,Lunch,2
91
- 21.16,3.0,Male,No,Thur,Lunch,2
92
- 28.97,3.0,Male,Yes,Fri,Dinner,2
93
- 22.49,3.5,Male,No,Fri,Dinner,2
94
- 5.75,1.0,Female,Yes,Fri,Dinner,2
95
- 16.32,4.3,Female,Yes,Fri,Dinner,2
96
- 22.75,3.25,Female,No,Fri,Dinner,2
97
- 40.17,4.73,Male,Yes,Fri,Dinner,4
98
- 27.28,4.0,Male,Yes,Fri,Dinner,2
99
- 12.03,1.5,Male,Yes,Fri,Dinner,2
100
- 21.01,3.0,Male,Yes,Fri,Dinner,2
101
- 12.46,1.5,Male,No,Fri,Dinner,2
102
- 11.35,2.5,Female,Yes,Fri,Dinner,2
103
- 15.38,3.0,Female,Yes,Fri,Dinner,2
104
- 44.3,2.5,Female,Yes,Sat,Dinner,3
105
- 22.42,3.48,Female,Yes,Sat,Dinner,2
106
- 20.92,4.08,Female,No,Sat,Dinner,2
107
- 15.36,1.64,Male,Yes,Sat,Dinner,2
108
- 20.49,4.06,Male,Yes,Sat,Dinner,2
109
- 25.21,4.29,Male,Yes,Sat,Dinner,2
110
- 18.24,3.76,Male,No,Sat,Dinner,2
111
- 14.31,4.0,Female,Yes,Sat,Dinner,2
112
- 14.0,3.0,Male,No,Sat,Dinner,2
113
- 7.25,1.0,Female,No,Sat,Dinner,1
114
- 38.07,4.0,Male,No,Sun,Dinner,3
115
- 23.95,2.55,Male,No,Sun,Dinner,2
116
- 25.71,4.0,Female,No,Sun,Dinner,3
117
- 17.31,3.5,Female,No,Sun,Dinner,2
118
- 29.93,5.07,Male,No,Sun,Dinner,4
119
- 10.65,1.5,Female,No,Thur,Lunch,2
120
- 12.43,1.8,Female,No,Thur,Lunch,2
121
- 24.08,2.92,Female,No,Thur,Lunch,4
122
- 11.69,2.31,Male,No,Thur,Lunch,2
123
- 13.42,1.68,Female,No,Thur,Lunch,2
124
- 14.26,2.5,Male,No,Thur,Lunch,2
125
- 15.95,2.0,Male,No,Thur,Lunch,2
126
- 12.48,2.52,Female,No,Thur,Lunch,2
127
- 29.8,4.2,Female,No,Thur,Lunch,6
128
- 8.52,1.48,Male,No,Thur,Lunch,2
129
- 14.52,2.0,Female,No,Thur,Lunch,2
130
- 11.38,2.0,Female,No,Thur,Lunch,2
131
- 22.82,2.18,Male,No,Thur,Lunch,3
132
- 19.08,1.5,Male,No,Thur,Lunch,2
133
- 20.27,2.83,Female,No,Thur,Lunch,2
134
- 11.17,1.5,Female,No,Thur,Lunch,2
135
- 12.26,2.0,Female,No,Thur,Lunch,2
136
- 18.26,3.25,Female,No,Thur,Lunch,2
137
- 8.51,1.25,Female,No,Thur,Lunch,2
138
- 10.33,2.0,Female,No,Thur,Lunch,2
139
- 14.15,2.0,Female,No,Thur,Lunch,2
140
- 16.0,2.0,Male,Yes,Thur,Lunch,2
141
- 13.16,2.75,Female,No,Thur,Lunch,2
142
- 17.47,3.5,Female,No,Thur,Lunch,2
143
- 34.3,6.7,Male,No,Thur,Lunch,6
144
- 41.19,5.0,Male,No,Thur,Lunch,5
145
- 27.05,5.0,Female,No,Thur,Lunch,6
146
- 16.43,2.3,Female,No,Thur,Lunch,2
147
- 8.35,1.5,Female,No,Thur,Lunch,2
148
- 18.64,1.36,Female,No,Thur,Lunch,3
149
- 11.87,1.63,Female,No,Thur,Lunch,2
150
- 9.78,1.73,Male,No,Thur,Lunch,2
151
- 7.51,2.0,Male,No,Thur,Lunch,2
152
- 14.07,2.5,Male,No,Sun,Dinner,2
153
- 13.13,2.0,Male,No,Sun,Dinner,2
154
- 17.26,2.74,Male,No,Sun,Dinner,3
155
- 24.55,2.0,Male,No,Sun,Dinner,4
156
- 19.77,2.0,Male,No,Sun,Dinner,4
157
- 29.85,5.14,Female,No,Sun,Dinner,5
158
- 48.17,5.0,Male,No,Sun,Dinner,6
159
- 25.0,3.75,Female,No,Sun,Dinner,4
160
- 13.39,2.61,Female,No,Sun,Dinner,2
161
- 16.49,2.0,Male,No,Sun,Dinner,4
162
- 21.5,3.5,Male,No,Sun,Dinner,4
163
- 12.66,2.5,Male,No,Sun,Dinner,2
164
- 16.21,2.0,Female,No,Sun,Dinner,3
165
- 13.81,2.0,Male,No,Sun,Dinner,2
166
- 17.51,3.0,Female,Yes,Sun,Dinner,2
167
- 24.52,3.48,Male,No,Sun,Dinner,3
168
- 20.76,2.24,Male,No,Sun,Dinner,2
169
- 31.71,4.5,Male,No,Sun,Dinner,4
170
- 10.59,1.61,Female,Yes,Sat,Dinner,2
171
- 10.63,2.0,Female,Yes,Sat,Dinner,2
172
- 50.81,10.0,Male,Yes,Sat,Dinner,3
173
- 15.81,3.16,Male,Yes,Sat,Dinner,2
174
- 7.25,5.15,Male,Yes,Sun,Dinner,2
175
- 31.85,3.18,Male,Yes,Sun,Dinner,2
176
- 16.82,4.0,Male,Yes,Sun,Dinner,2
177
- 32.9,3.11,Male,Yes,Sun,Dinner,2
178
- 17.89,2.0,Male,Yes,Sun,Dinner,2
179
- 14.48,2.0,Male,Yes,Sun,Dinner,2
180
- 9.6,4.0,Female,Yes,Sun,Dinner,2
181
- 34.63,3.55,Male,Yes,Sun,Dinner,2
182
- 34.65,3.68,Male,Yes,Sun,Dinner,4
183
- 23.33,5.65,Male,Yes,Sun,Dinner,2
184
- 45.35,3.5,Male,Yes,Sun,Dinner,3
185
- 23.17,6.5,Male,Yes,Sun,Dinner,4
186
- 40.55,3.0,Male,Yes,Sun,Dinner,2
187
- 20.69,5.0,Male,No,Sun,Dinner,5
188
- 20.9,3.5,Female,Yes,Sun,Dinner,3
189
- 30.46,2.0,Male,Yes,Sun,Dinner,5
190
- 18.15,3.5,Female,Yes,Sun,Dinner,3
191
- 23.1,4.0,Male,Yes,Sun,Dinner,3
192
- 15.69,1.5,Male,Yes,Sun,Dinner,2
193
- 19.81,4.19,Female,Yes,Thur,Lunch,2
194
- 28.44,2.56,Male,Yes,Thur,Lunch,2
195
- 15.48,2.02,Male,Yes,Thur,Lunch,2
196
- 16.58,4.0,Male,Yes,Thur,Lunch,2
197
- 7.56,1.44,Male,No,Thur,Lunch,2
198
- 10.34,2.0,Male,Yes,Thur,Lunch,2
199
- 43.11,5.0,Female,Yes,Thur,Lunch,4
200
- 13.0,2.0,Female,Yes,Thur,Lunch,2
201
- 13.51,2.0,Male,Yes,Thur,Lunch,2
202
- 18.71,4.0,Male,Yes,Thur,Lunch,3
203
- 12.74,2.01,Female,Yes,Thur,Lunch,2
204
- 13.0,2.0,Female,Yes,Thur,Lunch,2
205
- 16.4,2.5,Female,Yes,Thur,Lunch,2
206
- 20.53,4.0,Male,Yes,Thur,Lunch,4
207
- 16.47,3.23,Female,Yes,Thur,Lunch,3
208
- 26.59,3.41,Male,Yes,Sat,Dinner,3
209
- 38.73,3.0,Male,Yes,Sat,Dinner,4
210
- 24.27,2.03,Male,Yes,Sat,Dinner,2
211
- 12.76,2.23,Female,Yes,Sat,Dinner,2
212
- 30.06,2.0,Male,Yes,Sat,Dinner,3
213
- 25.89,5.16,Male,Yes,Sat,Dinner,4
214
- 48.33,9.0,Male,No,Sat,Dinner,4
215
- 13.27,2.5,Female,Yes,Sat,Dinner,2
216
- 28.17,6.5,Female,Yes,Sat,Dinner,3
217
- 12.9,1.1,Female,Yes,Sat,Dinner,2
218
- 28.15,3.0,Male,Yes,Sat,Dinner,5
219
- 11.59,1.5,Male,Yes,Sat,Dinner,2
220
- 7.74,1.44,Male,Yes,Sat,Dinner,2
221
- 30.14,3.09,Female,Yes,Sat,Dinner,4
222
- 12.16,2.2,Male,Yes,Fri,Lunch,2
223
- 13.42,3.48,Female,Yes,Fri,Lunch,2
224
- 8.58,1.92,Male,Yes,Fri,Lunch,1
225
- 15.98,3.0,Female,No,Fri,Lunch,3
226
- 13.42,1.58,Male,Yes,Fri,Lunch,2
227
- 16.27,2.5,Female,Yes,Fri,Lunch,2
228
- 10.09,2.0,Female,Yes,Fri,Lunch,2
229
- 20.45,3.0,Male,No,Sat,Dinner,4
230
- 13.28,2.72,Male,No,Sat,Dinner,2
231
- 22.12,2.88,Female,Yes,Sat,Dinner,2
232
- 24.01,2.0,Male,Yes,Sat,Dinner,4
233
- 15.69,3.0,Male,Yes,Sat,Dinner,3
234
- 11.61,3.39,Male,No,Sat,Dinner,2
235
- 10.77,1.47,Male,No,Sat,Dinner,2
236
- 15.53,3.0,Male,Yes,Sat,Dinner,2
237
- 10.07,1.25,Male,No,Sat,Dinner,2
238
- 12.6,1.0,Male,Yes,Sat,Dinner,2
239
- 32.83,1.17,Male,Yes,Sat,Dinner,2
240
- 35.83,4.67,Female,No,Sat,Dinner,3
241
- 29.03,5.92,Male,No,Sat,Dinner,3
242
- 27.18,2.0,Female,Yes,Sat,Dinner,2
243
- 22.67,2.0,Male,Yes,Sat,Dinner,2
244
- 17.82,1.75,Male,No,Sat,Dinner,2
245
- 18.78,3.0,Female,No,Thur,Dinner,2