Compare commits
No commits in common. "e2ed14784a8c4f2fad2b0a58d0c06498e3fa4560" and "fe77a53322a09496e01d5223f868b6ae2cf57621" have entirely different histories.
e2ed14784a
...
fe77a53322
3
.gitignore
vendored
3
.gitignore
vendored
@ -6,9 +6,6 @@ __pycache__/
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# settings
|
||||
dsite/settings.py
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
|
||||
17
Jenkinsfile
vendored
17
Jenkinsfile
vendored
@ -1,17 +0,0 @@
|
||||
pipeline {
|
||||
agent {
|
||||
docker {
|
||||
image 'node:6-alpine'
|
||||
args '-p 4000:4000'
|
||||
}
|
||||
|
||||
}
|
||||
stages {
|
||||
stage('Build') {
|
||||
steps {
|
||||
sh 'npm install'
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -7,8 +7,8 @@ django-cors-headers==3.8.0
|
||||
django-filter==2.4.0
|
||||
djangorestframework==3.12.4
|
||||
importlib-metadata==4.6.4
|
||||
ipdb
|
||||
ipython
|
||||
ipdb==0.13.9
|
||||
ipython==7.16.1
|
||||
ipython-genutils==0.2.0
|
||||
jedi==0.17.2
|
||||
Markdown==3.3.4
|
||||
@ -30,4 +30,3 @@ ua-parser==0.10.0
|
||||
user-agents==2.2.0
|
||||
wcwidth==0.2.5
|
||||
zipp==3.5.0
|
||||
redis==4.1.0
|
||||
|
||||
45
dsite/settings.py
Normal file
45
dsite/settings.py
Normal file
@ -0,0 +1,45 @@
|
||||
from .settings_default import *
|
||||
|
||||
ALLOWED_HOSTS = ['*']
|
||||
|
||||
CORS_ALLOW_CREDENTIALS = True # 允许携带cookie
|
||||
# CORS_ALLOW_ALL_ORIGINS = True # If this is used then `CORS_ALLOWED_ORIGINS` will not have any effect
|
||||
CORS_ALLOWED_ORIGINS = [
|
||||
'http://localhost:8080',
|
||||
'http://127.0.0.1:8080',
|
||||
'http://192.168.1.101:8080'
|
||||
]
|
||||
|
||||
CORS_ALLOW_METHODS = [
|
||||
"DELETE",
|
||||
"GET",
|
||||
"OPTIONS",
|
||||
"PATCH",
|
||||
"POST",
|
||||
"PUT",
|
||||
]
|
||||
|
||||
CORS_ALLOW_HEADERS = [
|
||||
"accept",
|
||||
"accept-encoding",
|
||||
"authorization",
|
||||
"content-type",
|
||||
"dnt",
|
||||
"origin",
|
||||
"user-agent",
|
||||
"x-csrftoken",
|
||||
"x-requested-with",
|
||||
]
|
||||
|
||||
CORS_ALLOW_HEADERS = ('*')
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
PROJECT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
|
||||
|
||||
STATICFILES_DIRS = [
|
||||
os.path.join(BASE_DIR, "frontend/dist/"),
|
||||
]
|
||||
|
||||
MEDIA_URL = '/media/'
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
||||
@ -155,7 +155,3 @@ REST_FRAMEWORK = {
|
||||
CORS_ALLOWED_ORIGINS = []
|
||||
CORS_ALLOW_CREDENTIALS = True # 允许携带cookie
|
||||
# CORS_ALLOW_ALL_ORIGINS = False # If this is used then `CORS_ALLOWED_ORIGINS` will not have any effect
|
||||
|
||||
|
||||
LARK_WEBHOOK_URL = ''
|
||||
LARK_WEBHOOK_SECRET = ''
|
||||
|
||||
@ -1 +1 @@
|
||||
.summit-recipe[data-v-16b31338]{width:100%}.el-pagination{margin:10px 0 0 0}.el-row{margin-bottom:20px;&:last-child{margin-bottom:0}}.el-col,.grid-content{border-radius:4px}.grid-content{min-height:36px}.row-bg{padding:10px 0;background-color:#f9fafc}.content{padding:20px 10px}.re-generate{margin:20px 0;width:100%}.el-tag#meal a:link,.el-tag#meal a:visited{text-decoration:none}.el-tag#meal a:active,.el-tag#meal a:hover{text-decoration:underline}.el-tag{margin:0 5px 0 0}.el-tag+.el-tag{margin:5px 0 0 0}.recipe-type-tag{margin:0 10px 0 0;padding-bottom:0}.recipe-list{height:50%}body{background-color:#f7f8fa}.recipe-create{margin:20px 16px}.recipe_type,.recipe_type .van-grid-item__content{background-color:#f2f3f5}.daily_recipes{margin-bottom:10px}.daily_recipes:last-child{margin-bottom:40px}.action-button{height:100%;width:50%}.today-tag{margin-left:10px}.refresh-button{float:right;margin-right:20px}.daily_recipe{margin-top:20px}.delete-button{width:100%;height:100%}.delete-icon{font-size:16px;line-height:inherit;padding:0 5px}
|
||||
.summit-recipe[data-v-16b31338]{width:100%}.el-pagination{margin:10px 0 0 0}.el-row{margin-bottom:20px;&:last-child{margin-bottom:0}}.el-col,.grid-content{border-radius:4px}.grid-content{min-height:36px}.row-bg{padding:10px 0;background-color:#f9fafc}.content{padding:20px 10px}.re-generate{margin:20px 0;width:100%}.el-tag#meal a:link,.el-tag#meal a:visited{text-decoration:none}.el-tag#meal a:active,.el-tag#meal a:hover{text-decoration:underline}.el-tag{margin:0 5px 0 0}.el-tag+.el-tag{margin:5px 0 0 0}.recipe-type-tag{margin:0 10px 0 0;padding-bottom:0}.recipe-list{height:50%}body{background-color:#f7f8fa}.recipe-create{margin:20px 16px}.recipe_type,.recipe_type .van-grid-item__content{background-color:#f2f3f5}.daily_recipes{margin-bottom:10px}.daily_recipes:last-child{margin-bottom:40px}.action-button{height:100%;width:50%}.daily_recipe{margin-top:20px}.delete-button{width:100%;height:100%}.delete-icon{font-size:16px;line-height:inherit;padding:0 5px}
|
||||
File diff suppressed because one or more lines are too long
30
frontend/dist/index.html
vendored
30
frontend/dist/index.html
vendored
@ -1 +1,29 @@
|
||||
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"><title>frontend</title><link href="/css/app.54db996e.css" rel="preload" as="style"><link href="/css/chunk-vendors.920d8cf8.css" rel="preload" as="style"><link href="/js/app.30780f00.js" rel="preload" as="script"><link href="/js/chunk-vendors.6cd9f85d.js" rel="preload" as="script"><link href="/css/chunk-vendors.920d8cf8.css" rel="stylesheet"><link href="/css/app.54db996e.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but frontend doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/js/chunk-vendors.6cd9f85d.js"></script><script src="/js/app.30780f00.js"></script></body></html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width,initial-scale=1.0,maximum-scale=1.0, user-scalable=0"
|
||||
/>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<title>frontend</title>
|
||||
<link href="/css/app.12f77eb5.css" rel="preload" as="style" />
|
||||
<link href="/css/chunk-vendors.b9153273.css" rel="preload" as="style" />
|
||||
<link href="/js/app.4a8d2f87.js" rel="preload" as="script" />
|
||||
<link href="/js/chunk-vendors.4d54ba89.js" rel="preload" as="script" />
|
||||
<link href="/css/chunk-vendors.b9153273.css" rel="stylesheet" />
|
||||
<link href="/css/app.12f77eb5.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<noscript
|
||||
><strong
|
||||
>We're sorry but frontend doesn't work properly without JavaScript
|
||||
enabled. Please enable it to continue.</strong
|
||||
></noscript
|
||||
>
|
||||
<div id="app"></div>
|
||||
<script src="/js/chunk-vendors.4d54ba89.js"></script>
|
||||
<script src="/js/app.4a8d2f87.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
2
frontend/dist/js/app.30780f00.js
vendored
2
frontend/dist/js/app.30780f00.js
vendored
File diff suppressed because one or more lines are too long
1
frontend/dist/js/app.30780f00.js.map
vendored
1
frontend/dist/js/app.30780f00.js.map
vendored
File diff suppressed because one or more lines are too long
2
frontend/dist/js/app.4a8d2f87.js
vendored
Normal file
2
frontend/dist/js/app.4a8d2f87.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
frontend/dist/js/app.4a8d2f87.js.map
vendored
Normal file
1
frontend/dist/js/app.4a8d2f87.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
frontend/dist/js/chunk-vendors.4d54ba89.js.map
vendored
Normal file
1
frontend/dist/js/chunk-vendors.4d54ba89.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
16263
frontend/package-lock.json
generated
16263
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -14,9 +14,9 @@
|
||||
"axios": "^0.21.2",
|
||||
"bootstrap": "^5.1.1",
|
||||
"core-js": "^3.6.5",
|
||||
"element-plus": "^1.3.0-beta.5",
|
||||
"element-plus": "^1.0.2-beta.28",
|
||||
"vant": "^3.2.4",
|
||||
"vue": "^3.1",
|
||||
"vue": "^3.2.2",
|
||||
"vue-router": "^4.0.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@ -1,124 +1,105 @@
|
||||
<template>
|
||||
<!-- <van-pull-refresh
|
||||
<van-pull-refresh
|
||||
v-model="loading"
|
||||
@refresh="onRefresh"
|
||||
pulling-text="下拉重新生成每周菜谱"
|
||||
loosing-text="可以松手了..."
|
||||
pull-distance="100"
|
||||
:head-height="100"
|
||||
> -->
|
||||
<div v-for:="(daily_recipe, recipe_index) in daily_recipes">
|
||||
<van-cell-group inset class="daily_recipes">
|
||||
<template #title>
|
||||
<span> {{ formatDate(daily_recipe.date) }}</span>
|
||||
<van-tag
|
||||
type="danger"
|
||||
class="today-tag"
|
||||
v-if="isToday(daily_recipe.date)"
|
||||
>今天</van-tag
|
||||
>
|
||||
</template>
|
||||
<van-swipe-cell>
|
||||
<van-row v-if="daily_recipe.meat">
|
||||
<van-col span="3" class="recipe_type">
|
||||
<van-grid :column-num="1">
|
||||
<van-grid-item text="肉" class="recipe_type"></van-grid-item>
|
||||
</van-grid>
|
||||
</van-col>
|
||||
<van-col span="21">
|
||||
<van-grid :column-num="3" clickable>
|
||||
<van-grid-item
|
||||
v-for:="recipe in daily_recipe.meat"
|
||||
:text="recipe.name"
|
||||
:to="{
|
||||
name: 'RecipeMobileRecipeDetail',
|
||||
params: { id: recipe.id },
|
||||
}"
|
||||
/>
|
||||
<van-grid-item
|
||||
v-for="value in 3 - (daily_recipe.meat.length % 3)"
|
||||
:key="value"
|
||||
/>
|
||||
</van-grid>
|
||||
</van-col>
|
||||
</van-row>
|
||||
<van-row v-if="daily_recipe.vegetable">
|
||||
<van-col span="3" class="recipe_type">
|
||||
<van-grid :column-num="1">
|
||||
<van-grid-item text="菜" class="recipe_type"></van-grid-item>
|
||||
</van-grid>
|
||||
</van-col>
|
||||
<van-col span="21">
|
||||
<van-grid :column-num="3" clickable>
|
||||
<van-grid-item
|
||||
v-for:="recipe in daily_recipe.vegetable"
|
||||
:text="recipe.name"
|
||||
:to="{
|
||||
name: 'RecipeMobileRecipeDetail',
|
||||
params: { id: recipe.id },
|
||||
}"
|
||||
/>
|
||||
<van-grid-item
|
||||
v-for="value in 3 - (daily_recipe.vegetable.length % 3)"
|
||||
:key="value"
|
||||
/>
|
||||
</van-grid>
|
||||
</van-col>
|
||||
</van-row>
|
||||
<van-row
|
||||
v-if="daily_recipe.soup !== undefined && daily_recipe.soup.length > 0"
|
||||
>
|
||||
<van-col span="3" class="recipe_type">
|
||||
<van-grid :column-num="1">
|
||||
<van-grid-item text="汤" class="recipe_type"></van-grid-item>
|
||||
</van-grid>
|
||||
</van-col>
|
||||
<van-col span="21">
|
||||
<van-grid :column-num="1" clickable>
|
||||
<van-grid-item
|
||||
v-for:="recipe in daily_recipe.soup"
|
||||
:text="recipe.name"
|
||||
:to="{
|
||||
name: 'RecipeMobileRecipeDetail',
|
||||
params: { id: recipe.id },
|
||||
}"
|
||||
/>
|
||||
</van-grid>
|
||||
</van-col>
|
||||
</van-row>
|
||||
<template #right>
|
||||
<van-button
|
||||
square
|
||||
text="编辑"
|
||||
type="primary"
|
||||
class="action-button"
|
||||
:to="{
|
||||
name: 'RecipeMobileDailyRecipeDetail',
|
||||
params: { id: daily_recipe.id },
|
||||
}"
|
||||
/>
|
||||
<van-button
|
||||
square
|
||||
text="刷新"
|
||||
type="danger"
|
||||
class="action-button"
|
||||
@click="reGenerateRecipe(daily_recipe.id, recipe_index)"
|
||||
/>
|
||||
</template>
|
||||
</van-swipe-cell>
|
||||
</van-cell-group>
|
||||
</div>
|
||||
<!-- </van-pull-refresh> -->
|
||||
<van-sticky :offset-bottom="60" position="bottom" class="refresh-button">
|
||||
<van-button
|
||||
type="primary"
|
||||
color="linear-gradient(to right, #ff6034, #ee0a24)"
|
||||
hairline
|
||||
round
|
||||
@click="onRefresh"
|
||||
>刷新</van-button
|
||||
>
|
||||
</van-sticky>
|
||||
>
|
||||
<div v-for:="(daily_recipe, recipe_index) in daily_recipes">
|
||||
<van-cell-group
|
||||
inset
|
||||
:title="formatDate(daily_recipe.date)"
|
||||
class="daily_recipes"
|
||||
>
|
||||
<van-swipe-cell>
|
||||
<van-row>
|
||||
<van-col span="3" class="recipe_type">
|
||||
<van-grid :column-num="1">
|
||||
<van-grid-item text="肉" class="recipe_type"></van-grid-item>
|
||||
</van-grid>
|
||||
</van-col>
|
||||
<van-col span="21">
|
||||
<van-grid :column-num="3" clickable>
|
||||
<van-grid-item
|
||||
v-for:="recipe in daily_recipe.meat"
|
||||
:text="recipe.name"
|
||||
:to="{
|
||||
name: 'RecipeMobileRecipeDetail',
|
||||
params: { id: recipe.id },
|
||||
}"
|
||||
/>
|
||||
<van-grid-item
|
||||
v-for="value in 3 - (daily_recipe.meat.length % 3)"
|
||||
:key="value"
|
||||
/>
|
||||
</van-grid>
|
||||
</van-col>
|
||||
</van-row>
|
||||
<van-row>
|
||||
<van-col span="3" class="recipe_type">
|
||||
<van-grid :column-num="1">
|
||||
<van-grid-item text="菜" class="recipe_type"></van-grid-item>
|
||||
</van-grid>
|
||||
</van-col>
|
||||
<van-col span="21">
|
||||
<van-grid :column-num="3" clickable>
|
||||
<van-grid-item
|
||||
v-for:="recipe in daily_recipe.vegetable"
|
||||
:text="recipe.name"
|
||||
:to="{
|
||||
name: 'RecipeMobileRecipeDetail',
|
||||
params: { id: recipe.id },
|
||||
}"
|
||||
/>
|
||||
<van-grid-item
|
||||
v-for="value in 3 - (daily_recipe.vegetable.length % 3)"
|
||||
:key="value"
|
||||
/>
|
||||
</van-grid>
|
||||
</van-col>
|
||||
</van-row>
|
||||
<van-row v-if="daily_recipe.soup.length > 0">
|
||||
<van-col span="3" class="recipe_type">
|
||||
<van-grid :column-num="1">
|
||||
<van-grid-item text="汤" class="recipe_type"></van-grid-item>
|
||||
</van-grid>
|
||||
</van-col>
|
||||
<van-col span="21">
|
||||
<van-grid :column-num="1" clickable>
|
||||
<van-grid-item
|
||||
v-for:="recipe in daily_recipe.soup"
|
||||
:text="recipe.name"
|
||||
:to="{
|
||||
name: 'RecipeMobileRecipeDetail',
|
||||
params: { id: recipe.id },
|
||||
}"
|
||||
/>
|
||||
</van-grid>
|
||||
</van-col>
|
||||
</van-row>
|
||||
<template #right>
|
||||
<van-button
|
||||
square
|
||||
text="编辑"
|
||||
type="primary"
|
||||
class="action-button"
|
||||
:to="{
|
||||
name: 'RecipeMobileDailyRecipeDetail',
|
||||
params: { id: daily_recipe.id },
|
||||
}"
|
||||
/>
|
||||
<van-button
|
||||
square
|
||||
text="刷新"
|
||||
type="danger"
|
||||
class="action-button"
|
||||
@click="reGenerateRecipe(daily_recipe.id, recipe_index)"
|
||||
/>
|
||||
</template>
|
||||
</van-swipe-cell>
|
||||
</van-cell-group>
|
||||
</div>
|
||||
</van-pull-refresh>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@ -132,8 +113,6 @@ import {
|
||||
PullRefresh,
|
||||
Row,
|
||||
SwipeCell,
|
||||
Tag,
|
||||
Sticky,
|
||||
} from 'vant';
|
||||
import axios from 'axios';
|
||||
import config from '@/config/index';
|
||||
@ -141,7 +120,6 @@ import config from '@/config/index';
|
||||
export default {
|
||||
name: 'RecipeMobileWeekRecipe',
|
||||
components: {
|
||||
[Tag.name]: Tag,
|
||||
[Button.name]: Button,
|
||||
[Cell.name]: Cell,
|
||||
[CellGroup.name]: CellGroup,
|
||||
@ -151,7 +129,6 @@ export default {
|
||||
[PullRefresh.name]: PullRefresh,
|
||||
[Row.name]: Row,
|
||||
[SwipeCell.name]: SwipeCell,
|
||||
[Sticky.name]: Sticky,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -163,29 +140,13 @@ export default {
|
||||
this.getWeekRecipe(false);
|
||||
},
|
||||
methods: {
|
||||
isToday(date) {
|
||||
const today = new Date();
|
||||
var date_ = new Date(date * 1000);
|
||||
if (
|
||||
date_.getDate() == today.getDate() &&
|
||||
date_.getMonth() == today.getMonth() &&
|
||||
date_.getFullYear() == today.getFullYear()
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
formatDate(date) {
|
||||
if (date === undefined) {
|
||||
return;
|
||||
}
|
||||
var date_ = new Date(date * 1000);
|
||||
var days = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
|
||||
var date_str = days[date_.getDay()];
|
||||
if (this.isToday(date_)) {
|
||||
date_str += ' 🌟';
|
||||
}
|
||||
return date_str;
|
||||
return days[date_.getDay()];
|
||||
},
|
||||
onRefresh() {
|
||||
this.getWeekRecipe(true);
|
||||
@ -236,11 +197,4 @@ export default {
|
||||
height: 100%;
|
||||
width: 50%;
|
||||
}
|
||||
.today-tag {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.refresh-button {
|
||||
float: right;
|
||||
margin-right: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -2,19 +2,12 @@
|
||||
<el-row justify="center">
|
||||
<el-col>
|
||||
<el-table border stripe :data="daily_recipes" max-height="500">
|
||||
<el-table-column label="日期">
|
||||
<template #default="scope">
|
||||
{{ formatDate(scope.row, scope.column, scope.row.date) }}
|
||||
<el-tag
|
||||
v-if:="isToday(scope.row.date)"
|
||||
:key="scope"
|
||||
size="small"
|
||||
type="danger"
|
||||
effect="dark"
|
||||
>今天</el-tag
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="date"
|
||||
label="日期"
|
||||
:formatter="formatDate"
|
||||
width="50px"
|
||||
></el-table-column>
|
||||
<el-table-column prop="meat" label="肉">
|
||||
<template #default="scope">
|
||||
<el-tag
|
||||
@ -94,25 +87,11 @@ export default {
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
console.log(config.publicPath);
|
||||
console.log(process.env);
|
||||
axios
|
||||
.get(config.publicPath + '/recipe/week-recipe/')
|
||||
.then((response) => (this.daily_recipes = response.data));
|
||||
},
|
||||
methods: {
|
||||
isToday(date) {
|
||||
const today = new Date();
|
||||
var date_ = new Date(date * 1000);
|
||||
if (
|
||||
date_.getDate() == today.getDate() &&
|
||||
date_.getMonth() == today.getMonth() &&
|
||||
date_.getFullYear() == today.getFullYear()
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
reGenerateRecipe() {
|
||||
axios
|
||||
.post(config.publicPath + '/recipe/week-recipe/')
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import 'element-plus/lib/theme-chalk/index.css'
|
||||
import locale from 'element-plus/lib/locale/lang/zh-cn'
|
||||
|
||||
export default (app) => {
|
||||
|
||||
4202
frontend/yarn.lock
4202
frontend/yarn.lock
File diff suppressed because it is too large
Load Diff
@ -51,8 +51,8 @@ class WeekRecipeListAPI(rest_framework.generics.ListAPIView,
|
||||
daily_recipe, __ = recipe.models.DailyRecipe.objects.get_or_create(
|
||||
date=today + datetime.timedelta(days=x)
|
||||
)
|
||||
daily_recipe.generate_recipe(recipes)
|
||||
recipes.extend(daily_recipe.recipes.values_list('id', flat=True))
|
||||
daily_recipe.generate_recipe()
|
||||
recipes.append(daily_recipe.recipes.values_list('id', flat=True))
|
||||
return Response(recipe.models.DailyRecipe.get_week_recipe_data(),
|
||||
status=status.HTTP_201_CREATED, headers={})
|
||||
|
||||
|
||||
285
scripts/dodo.py
285
scripts/dodo.py
@ -1,285 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# --coding:utf-8--
|
||||
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
import json
|
||||
from urllib import request
|
||||
import hashlib
|
||||
import base64
|
||||
from Crypto.Cipher import AES
|
||||
from mastodon import Mastodon
|
||||
import logging
|
||||
import redis
|
||||
import requests
|
||||
import time
|
||||
import subprocess
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
APP_VERIFICATION_TOKEN = 'uKQQiOVMYg2cTgrjkyBmodrHTUaCXzG3'
|
||||
APP_ID = 'cli_a115fe8b83f9100c'
|
||||
APP_SECRET = 'yuSQenId0VfvwdZ3qL9wMd8FpCMEUL0u'
|
||||
ENCRYPT_KEY = '4XfjcA5xou3pztBD4g5V7dgHtr0BBYDE'
|
||||
EVENT_TYPE = ['im.message.receive_v1']
|
||||
ADD_GROUP_NAME = True
|
||||
KEDAI_ID = '107263380636355825'
|
||||
|
||||
logging.basicConfig(filename='/root/develop/log/dodo.log', level=logging.INFO)
|
||||
logger = logging.getLogger('/root/develop/log/dodo.log')
|
||||
|
||||
mastodon = Mastodon(
|
||||
access_token = 'Ug_bUMWCk3RLamOnqYIytmeB0nO6aNfjdmf06mAj2bE',
|
||||
api_base_url = 'https://nofan.xyz'
|
||||
)
|
||||
|
||||
pool = redis.ConnectionPool(host='localhost', port=6379, decode_responses=True)
|
||||
redis_cli = redis.Redis(host='localhost', port=6379, decode_responses=True)
|
||||
|
||||
class AESCipher(object):
|
||||
def __init__(self, key):
|
||||
self.bs = AES.block_size
|
||||
self.key=hashlib.sha256(AESCipher.str_to_bytes(key)).digest()
|
||||
@staticmethod
|
||||
def str_to_bytes(data):
|
||||
u_type = type(b"".decode('utf8'))
|
||||
if isinstance(data, u_type):
|
||||
return data.encode('utf8')
|
||||
return data
|
||||
@staticmethod
|
||||
def _unpad(s):
|
||||
return s[:-ord(s[len(s) - 1:])]
|
||||
def decrypt(self, enc):
|
||||
iv = enc[:AES.block_size]
|
||||
cipher = AES.new(self.key, AES.MODE_CBC, iv)
|
||||
return self._unpad(cipher.decrypt(enc[AES.block_size:]))
|
||||
def decrypt_string(self, enc):
|
||||
enc = base64.b64decode(enc)
|
||||
return self.decrypt(enc).decode('utf8')
|
||||
|
||||
def get_tenant_access_token(): # 获取token
|
||||
token = redis_cli.get('tenant_access_token_%s' % APP_ID)
|
||||
if token:
|
||||
return token
|
||||
|
||||
url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal/"
|
||||
headers = {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
req_body = {
|
||||
"app_id": APP_ID,
|
||||
"app_secret": APP_SECRET
|
||||
}
|
||||
|
||||
data = bytes(json.dumps(req_body), encoding='utf8')
|
||||
req = request.Request(url=url, data=data, headers=headers, method='POST')
|
||||
try:
|
||||
response = request.urlopen(req)
|
||||
except Exception as e:
|
||||
logger.error('get tenant token error: %s', e.read().decode())
|
||||
return ""
|
||||
|
||||
rsp_body = response.read().decode('utf-8')
|
||||
rsp_dict = json.loads(rsp_body)
|
||||
code = rsp_dict.get("code", -1)
|
||||
if code != 0:
|
||||
logger.error("get tenant_access_token error, code =%s", code)
|
||||
return ""
|
||||
token = redis_cli.set('tenant_access_token_%s' % APP_ID,
|
||||
rsp_dict.get("tenant_access_token", ""),
|
||||
ex=60*30)
|
||||
|
||||
return token
|
||||
|
||||
def get_group_name(chat_id):
|
||||
group_name = redis_cli.get('group_name_%s' % chat_id)
|
||||
if not group_name:
|
||||
url = "https://open.feishu.cn/open-apis/im/v1/chats/"
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": "Bearer " + get_tenant_access_token()
|
||||
}
|
||||
try:
|
||||
resp = requests.get(url+chat_id, headers=headers)
|
||||
resp_data = resp.json()
|
||||
code = resp_data.get("code", -1)
|
||||
if code == 0:
|
||||
group_name = resp_data.get('data', {}).get('name')
|
||||
redis_cli.set('group_name_%s' % chat_id,
|
||||
group_name,
|
||||
ex=60*60*24)
|
||||
except:
|
||||
# todo: log
|
||||
return
|
||||
return group_name
|
||||
|
||||
|
||||
class RequestHandler(BaseHTTPRequestHandler):
|
||||
def do_POST(self):
|
||||
# 解析请求 body
|
||||
req_body = self.rfile.read(int(self.headers['content-length']))
|
||||
obj = json.loads(req_body.decode("utf-8"))
|
||||
cipher = AESCipher(ENCRYPT_KEY)
|
||||
obj = json.loads(cipher.decrypt_string(obj['encrypt']))
|
||||
logger.info('lark request body: %s', obj)
|
||||
|
||||
# 校验 verification token 是否匹配,token 不匹配说明该回调并非来自开发平台
|
||||
token = obj.get("token", "")
|
||||
if not token:
|
||||
token = obj.get('header', {}).get('token', '')
|
||||
if token != APP_VERIFICATION_TOKEN:
|
||||
logger.error("verification token not match, token =%s", token)
|
||||
self.response("")
|
||||
return
|
||||
|
||||
# 根据 type 处理不同类型事件
|
||||
type = obj.get("type", "")
|
||||
if not type:
|
||||
type = obj.get('header', {}).get('event_type')
|
||||
if "url_verification" == type: # 验证请求 URL 是否有效
|
||||
self.handle_request_url_verify(obj)
|
||||
elif type in EVENT_TYPE: # 事件回调
|
||||
# 获取事件内容和类型,并进行相应处理,此处只关注给机器人推送的消息事件
|
||||
event_id = obj.get('header', {}).get('event_id', '')
|
||||
# 重复收到的事件不处理
|
||||
if event_id and redis_cli.get(event_id):
|
||||
self.response("")
|
||||
return
|
||||
event = obj.get("event")
|
||||
if event.get("message"):
|
||||
self.handle_message(event, event_id)
|
||||
return
|
||||
return
|
||||
|
||||
def handle_request_url_verify(self, post_obj):
|
||||
# 原样返回 challenge 字段内容
|
||||
challenge = post_obj.get("challenge", "")
|
||||
rsp = {'challenge': challenge}
|
||||
self.response(json.dumps(rsp))
|
||||
return
|
||||
|
||||
def handle_message(self, event, event_id=None):
|
||||
# 此处只处理 text 类型消息,其他类型消息忽略
|
||||
msg = event.get('message', {})
|
||||
msg_type = msg.get("message_type", "")
|
||||
if msg_type == "text":
|
||||
# 调用发消息 API 之前,先要获取 API 调用凭证:tenant_access_token
|
||||
access_token = get_tenant_access_token()
|
||||
if access_token == "":
|
||||
self.response("")
|
||||
return
|
||||
|
||||
# 机器人回复收到的消息
|
||||
text = json.loads(msg.get('content')).get('text')
|
||||
orig_text = text
|
||||
if msg.get('chat_type') == 'group' and msg.get('mentions'):
|
||||
open_id = {"open_chat_id": msg.get("chat_id")}
|
||||
for mention in msg.get('mentions'):
|
||||
text = text.replace(mention['key'], '')
|
||||
text = text.lstrip()
|
||||
orig_text = text
|
||||
if ADD_GROUP_NAME:
|
||||
group_name = get_group_name(msg.get("chat_id"))
|
||||
text = '%s #%s' % (text, group_name)
|
||||
else:
|
||||
open_id = {"open_id": event.get("sender", {}).get(
|
||||
'sender_id', {}).get('open_id')}
|
||||
self.response("")
|
||||
if orig_text.startswith('/'):
|
||||
redis_cli.set(event_id, int(time.time()), ex=60*60*7)
|
||||
if orig_text not in ['/last', '/del']:
|
||||
if not orig_text.startswith('/deploy '):
|
||||
self.msg_compoment(access_token, open_id, '指令错误')
|
||||
return
|
||||
if orig_text == '/last':
|
||||
try:
|
||||
statuses = mastodon.account_statuses(KEDAI_ID, limit=1)
|
||||
s_text = BeautifulSoup(statuses[0]['content'], 'html.parser')
|
||||
self.msg_compoment(access_token, open_id,
|
||||
s_text.get_text(''))
|
||||
except Exception as exc:
|
||||
logger.error('operation error: %s', str(exc))
|
||||
elif orig_text == '/del':
|
||||
try:
|
||||
statuses = mastodon.account_statuses(KEDAI_ID, limit=1)
|
||||
Mastodon.status_delete(statuses[0]['id'])
|
||||
s_text = BeautifulSoup(statuses[0]['content'], 'html.parser')
|
||||
self.msg_compoment(access_token, open_id,
|
||||
'已删除: ' + s_text.get_text(''))
|
||||
except Exception as exc:
|
||||
logger.error('operation error: %s', str(exc))
|
||||
elif orig_text.startswith('/deploy '):
|
||||
site_ = orig_text.split('/deploy ')[1]
|
||||
if site_ == 'dsite':
|
||||
self.msg_compoment(access_token, open_id, '🚧 %s 开始部署 🚧' % site_)
|
||||
subprocess.call("/root/deploy/dsite_prepare.sh")
|
||||
subprocess.run(["supervisorctl", "restart", "dsite"])
|
||||
self.msg_compoment(access_token, open_id, '🎉 %s 部署成功 🎉' % site_)
|
||||
else:
|
||||
self.msg_compoment(access_token, open_id, '⚠️ %s 不存在 ⚠️' % site_)
|
||||
return
|
||||
|
||||
try:
|
||||
toot_resp = mastodon.status_post(text)
|
||||
if toot_resp.get('id'):
|
||||
self.msg_compoment(access_token, open_id, '📟 dodo 📟')
|
||||
redis_cli.set(event_id, int(time.time()), ex=60*60*7)
|
||||
else:
|
||||
self.msg_compoment(access_token, open_id, """⚠️ didi ⚠️
|
||||
%s
|
||||
""" % json.loads(toot_resp))
|
||||
except Exception as exc:
|
||||
logger.error('send toot error: %s', str(exc))
|
||||
|
||||
return
|
||||
elif msg_type == "image":
|
||||
self.response("")
|
||||
return
|
||||
|
||||
def response(self, body):
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', 'application/json')
|
||||
self.end_headers()
|
||||
self.wfile.write(body.encode())
|
||||
|
||||
def send_message(self, token, open_id, text):
|
||||
url = "https://open.feishu.cn/open-apis/message/v4/send/"
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": "Bearer " + token
|
||||
}
|
||||
req_body = {
|
||||
"msg_type": "text",
|
||||
"content": {
|
||||
"text": text
|
||||
}
|
||||
}
|
||||
req_body = dict(req_body, **open_id) # 根据open_id判断返回域
|
||||
|
||||
data = bytes(json.dumps(req_body), encoding='utf8')
|
||||
req = request.Request(url=url, data=data, headers=headers, method='POST')
|
||||
try:
|
||||
response = request.urlopen(req)
|
||||
except Exception as e:
|
||||
logger.error('send message error: %s', e.read().decode())
|
||||
return
|
||||
|
||||
rsp_body = response.read().decode('utf-8')
|
||||
rsp_dict = json.loads(rsp_body)
|
||||
code = rsp_dict.get("code", -1)
|
||||
if code != 0:
|
||||
logger.error("send message error, code = %s, msg =%s",
|
||||
code,
|
||||
rsp_dict.get("msg", ""))
|
||||
|
||||
def msg_compoment(self, token, open_id, text):
|
||||
self.send_message(token, open_id, text)
|
||||
|
||||
def run():
|
||||
port = 5000
|
||||
server_address = ('', port)
|
||||
httpd = HTTPServer(server_address, RequestHandler)
|
||||
logger.info("start...")
|
||||
httpd.serve_forever()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
run()
|
||||
@ -1,3 +0,0 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
@ -1,6 +0,0 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class TootConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'toot'
|
||||
@ -1 +0,0 @@
|
||||
from django.db import models
|
||||
@ -1,3 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@ -1,3 +0,0 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
@ -19,6 +19,3 @@ RECIPE_TYPE_CHOICE = [
|
||||
|
||||
FILTER_EXACT = ['exact']
|
||||
FILTER_GTE_LTE = ['exact', 'gte', 'gt', 'lte', 'lt']
|
||||
|
||||
|
||||
LARK_WEBHOOK_MSG_TYPE_TEXT = 'text'
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
# -*- coding: UTF-8 -*-
|
||||
import sys
|
||||
import os
|
||||
from imp import reload
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
sys.path.append('/Users/ching/develop/dsite')
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "../dsite.settings")
|
||||
reload(sys)
|
||||
|
||||
import utils.lark
|
||||
import git
|
||||
import ipdb
|
||||
|
||||
if __name__ == '__main__':
|
||||
# def notify():
|
||||
repo = git.Repo(search_parent_directories=True)
|
||||
commit = repo.head.commit
|
||||
rev, branch = commit.name_rev.split(' ')
|
||||
msg = 'rev: %s\n\nauther: %s\n\nbranch: %s\n\nmessage: %s' % (
|
||||
rev,
|
||||
commit.author.name,
|
||||
branch,
|
||||
commit.summary
|
||||
)
|
||||
print(utils.lark.request({'text': msg}))
|
||||
@ -1,37 +0,0 @@
|
||||
# -*- coding: UTF-8 -*-
|
||||
|
||||
from dsite import settings
|
||||
import utils
|
||||
from utils import const
|
||||
|
||||
import hashlib
|
||||
import base64
|
||||
import hmac
|
||||
import json
|
||||
import requests
|
||||
from django.utils.timezone import now
|
||||
|
||||
|
||||
def gen_sign(timestamp, secret):
|
||||
# 拼接timestamp和secret
|
||||
string_to_sign = '{}\n{}'.format(timestamp, secret)
|
||||
hmac_code = hmac.new(string_to_sign.encode("utf-8"), digestmod=hashlib.sha256).digest()
|
||||
|
||||
# 对结果进行base64处理
|
||||
sign = base64.b64encode(hmac_code).decode('utf-8')
|
||||
|
||||
return sign
|
||||
|
||||
def request(content, msg_type=const.LARK_WEBHOOK_MSG_TYPE_TEXT):
|
||||
""" content: {'text': 'xxxxx}
|
||||
"""
|
||||
timestamp = utils.timestamp_of(now())
|
||||
data = {
|
||||
"timestamp": timestamp,
|
||||
"sign": gen_sign(timestamp, settings.LARK_WEBHOOK_SECRET),
|
||||
"msg_type": msg_type,
|
||||
"content": content}
|
||||
resp = requests.post(settings.LARK_WEBHOOK_URL, data=json.dumps(data))
|
||||
if resp.status_code == 200 and resp.json().get('StatusCode') == 0:
|
||||
return True
|
||||
return False
|
||||
Loading…
x
Reference in New Issue
Block a user