Compare commits

..

No commits in common. "e2ed14784a8c4f2fad2b0a58d0c06498e3fa4560" and "fe77a53322a09496e01d5223f868b6ae2cf57621" have entirely different histories.

34 changed files with 9896 additions and 11342 deletions

3
.gitignore vendored
View File

@ -6,9 +6,6 @@ __pycache__/
# C extensions # C extensions
*.so *.so
# settings
dsite/settings.py
# Distribution / packaging # Distribution / packaging
.Python .Python
build/ build/

17
Jenkinsfile vendored
View File

@ -1,17 +0,0 @@
pipeline {
agent {
docker {
image 'node:6-alpine'
args '-p 4000:4000'
}
}
stages {
stage('Build') {
steps {
sh 'npm install'
}
}
}
}

0
a.txt
View File

View File

@ -7,8 +7,8 @@ django-cors-headers==3.8.0
django-filter==2.4.0 django-filter==2.4.0
djangorestframework==3.12.4 djangorestframework==3.12.4
importlib-metadata==4.6.4 importlib-metadata==4.6.4
ipdb ipdb==0.13.9
ipython ipython==7.16.1
ipython-genutils==0.2.0 ipython-genutils==0.2.0
jedi==0.17.2 jedi==0.17.2
Markdown==3.3.4 Markdown==3.3.4
@ -30,4 +30,3 @@ ua-parser==0.10.0
user-agents==2.2.0 user-agents==2.2.0
wcwidth==0.2.5 wcwidth==0.2.5
zipp==3.5.0 zipp==3.5.0
redis==4.1.0

45
dsite/settings.py Normal file
View 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')

View File

@ -155,7 +155,3 @@ REST_FRAMEWORK = {
CORS_ALLOWED_ORIGINS = [] CORS_ALLOWED_ORIGINS = []
CORS_ALLOW_CREDENTIALS = True # 允许携带cookie CORS_ALLOW_CREDENTIALS = True # 允许携带cookie
# CORS_ALLOW_ALL_ORIGINS = False # If this is used then `CORS_ALLOWED_ORIGINS` will not have any effect # CORS_ALLOW_ALL_ORIGINS = False # If this is used then `CORS_ALLOWED_ORIGINS` will not have any effect
LARK_WEBHOOK_URL = ''
LARK_WEBHOOK_SECRET = ''

View File

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

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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

File diff suppressed because it is too large Load Diff

View File

@ -14,9 +14,9 @@
"axios": "^0.21.2", "axios": "^0.21.2",
"bootstrap": "^5.1.1", "bootstrap": "^5.1.1",
"core-js": "^3.6.5", "core-js": "^3.6.5",
"element-plus": "^1.3.0-beta.5", "element-plus": "^1.0.2-beta.28",
"vant": "^3.2.4", "vant": "^3.2.4",
"vue": "^3.1", "vue": "^3.2.2",
"vue-router": "^4.0.11" "vue-router": "^4.0.11"
}, },
"devDependencies": { "devDependencies": {

View File

@ -1,124 +1,105 @@
<template> <template>
<!-- <van-pull-refresh <van-pull-refresh
v-model="loading" v-model="loading"
@refresh="onRefresh" @refresh="onRefresh"
pulling-text="下拉重新生成每周菜谱" pulling-text="下拉重新生成每周菜谱"
loosing-text="可以松手了..." loosing-text="可以松手了..."
pull-distance="100" >
:head-height="100" <div v-for:="(daily_recipe, recipe_index) in daily_recipes">
> --> <van-cell-group
<div v-for:="(daily_recipe, recipe_index) in daily_recipes"> inset
<van-cell-group inset class="daily_recipes"> :title="formatDate(daily_recipe.date)"
<template #title> class="daily_recipes"
<span> {{ formatDate(daily_recipe.date) }}</span> >
<van-tag <van-swipe-cell>
type="danger" <van-row>
class="today-tag" <van-col span="3" class="recipe_type">
v-if="isToday(daily_recipe.date)" <van-grid :column-num="1">
>今天</van-tag <van-grid-item text="肉" class="recipe_type"></van-grid-item>
> </van-grid>
</template> </van-col>
<van-swipe-cell> <van-col span="21">
<van-row v-if="daily_recipe.meat"> <van-grid :column-num="3" clickable>
<van-col span="3" class="recipe_type"> <van-grid-item
<van-grid :column-num="1"> v-for:="recipe in daily_recipe.meat"
<van-grid-item text="肉" class="recipe_type"></van-grid-item> :text="recipe.name"
</van-grid> :to="{
</van-col> name: 'RecipeMobileRecipeDetail',
<van-col span="21"> params: { id: recipe.id },
<van-grid :column-num="3" clickable> }"
<van-grid-item />
v-for:="recipe in daily_recipe.meat" <van-grid-item
:text="recipe.name" v-for="value in 3 - (daily_recipe.meat.length % 3)"
:to="{ :key="value"
name: 'RecipeMobileRecipeDetail', />
params: { id: recipe.id }, </van-grid>
}" </van-col>
/> </van-row>
<van-grid-item <van-row>
v-for="value in 3 - (daily_recipe.meat.length % 3)" <van-col span="3" class="recipe_type">
:key="value" <van-grid :column-num="1">
/> <van-grid-item text="菜" class="recipe_type"></van-grid-item>
</van-grid> </van-grid>
</van-col> </van-col>
</van-row> <van-col span="21">
<van-row v-if="daily_recipe.vegetable"> <van-grid :column-num="3" clickable>
<van-col span="3" class="recipe_type"> <van-grid-item
<van-grid :column-num="1"> v-for:="recipe in daily_recipe.vegetable"
<van-grid-item text="菜" class="recipe_type"></van-grid-item> :text="recipe.name"
</van-grid> :to="{
</van-col> name: 'RecipeMobileRecipeDetail',
<van-col span="21"> params: { id: recipe.id },
<van-grid :column-num="3" clickable> }"
<van-grid-item />
v-for:="recipe in daily_recipe.vegetable" <van-grid-item
:text="recipe.name" v-for="value in 3 - (daily_recipe.vegetable.length % 3)"
:to="{ :key="value"
name: 'RecipeMobileRecipeDetail', />
params: { id: recipe.id }, </van-grid>
}" </van-col>
/> </van-row>
<van-grid-item <van-row v-if="daily_recipe.soup.length > 0">
v-for="value in 3 - (daily_recipe.vegetable.length % 3)" <van-col span="3" class="recipe_type">
:key="value" <van-grid :column-num="1">
/> <van-grid-item text="汤" class="recipe_type"></van-grid-item>
</van-grid> </van-grid>
</van-col> </van-col>
</van-row> <van-col span="21">
<van-row <van-grid :column-num="1" clickable>
v-if="daily_recipe.soup !== undefined && daily_recipe.soup.length > 0" <van-grid-item
> v-for:="recipe in daily_recipe.soup"
<van-col span="3" class="recipe_type"> :text="recipe.name"
<van-grid :column-num="1"> :to="{
<van-grid-item text="汤" class="recipe_type"></van-grid-item> name: 'RecipeMobileRecipeDetail',
</van-grid> params: { id: recipe.id },
</van-col> }"
<van-col span="21"> />
<van-grid :column-num="1" clickable> </van-grid>
<van-grid-item </van-col>
v-for:="recipe in daily_recipe.soup" </van-row>
:text="recipe.name" <template #right>
:to="{ <van-button
name: 'RecipeMobileRecipeDetail', square
params: { id: recipe.id }, text="编辑"
}" type="primary"
/> class="action-button"
</van-grid> :to="{
</van-col> name: 'RecipeMobileDailyRecipeDetail',
</van-row> params: { id: daily_recipe.id },
<template #right> }"
<van-button />
square <van-button
text="编辑" square
type="primary" text="刷新"
class="action-button" type="danger"
:to="{ class="action-button"
name: 'RecipeMobileDailyRecipeDetail', @click="reGenerateRecipe(daily_recipe.id, recipe_index)"
params: { id: daily_recipe.id }, />
}" </template>
/> </van-swipe-cell>
<van-button </van-cell-group>
square </div>
text="刷新" </van-pull-refresh>
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>
</template> </template>
<script> <script>
@ -132,8 +113,6 @@ import {
PullRefresh, PullRefresh,
Row, Row,
SwipeCell, SwipeCell,
Tag,
Sticky,
} from 'vant'; } from 'vant';
import axios from 'axios'; import axios from 'axios';
import config from '@/config/index'; import config from '@/config/index';
@ -141,7 +120,6 @@ import config from '@/config/index';
export default { export default {
name: 'RecipeMobileWeekRecipe', name: 'RecipeMobileWeekRecipe',
components: { components: {
[Tag.name]: Tag,
[Button.name]: Button, [Button.name]: Button,
[Cell.name]: Cell, [Cell.name]: Cell,
[CellGroup.name]: CellGroup, [CellGroup.name]: CellGroup,
@ -151,7 +129,6 @@ export default {
[PullRefresh.name]: PullRefresh, [PullRefresh.name]: PullRefresh,
[Row.name]: Row, [Row.name]: Row,
[SwipeCell.name]: SwipeCell, [SwipeCell.name]: SwipeCell,
[Sticky.name]: Sticky,
}, },
data() { data() {
return { return {
@ -163,29 +140,13 @@ export default {
this.getWeekRecipe(false); this.getWeekRecipe(false);
}, },
methods: { 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) { formatDate(date) {
if (date === undefined) { if (date === undefined) {
return; return;
} }
var date_ = new Date(date * 1000); var date_ = new Date(date * 1000);
var days = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']; var days = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
var date_str = days[date_.getDay()]; return days[date_.getDay()];
if (this.isToday(date_)) {
date_str += ' 🌟';
}
return date_str;
}, },
onRefresh() { onRefresh() {
this.getWeekRecipe(true); this.getWeekRecipe(true);
@ -236,11 +197,4 @@ export default {
height: 100%; height: 100%;
width: 50%; width: 50%;
} }
.today-tag {
margin-left: 10px;
}
.refresh-button {
float: right;
margin-right: 20px;
}
</style> </style>

View File

@ -2,19 +2,12 @@
<el-row justify="center"> <el-row justify="center">
<el-col> <el-col>
<el-table border stripe :data="daily_recipes" max-height="500"> <el-table border stripe :data="daily_recipes" max-height="500">
<el-table-column label="日期"> <el-table-column
<template #default="scope"> prop="date"
{{ formatDate(scope.row, scope.column, scope.row.date) }} label="日期"
<el-tag :formatter="formatDate"
v-if:="isToday(scope.row.date)" width="50px"
:key="scope" ></el-table-column>
size="small"
type="danger"
effect="dark"
>今天</el-tag
>
</template>
</el-table-column>
<el-table-column prop="meat" label="肉"> <el-table-column prop="meat" label="肉">
<template #default="scope"> <template #default="scope">
<el-tag <el-tag
@ -94,25 +87,11 @@ export default {
}; };
}, },
mounted() { mounted() {
console.log(config.publicPath);
console.log(process.env);
axios axios
.get(config.publicPath + '/recipe/week-recipe/') .get(config.publicPath + '/recipe/week-recipe/')
.then((response) => (this.daily_recipes = response.data)); .then((response) => (this.daily_recipes = response.data));
}, },
methods: { 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() { reGenerateRecipe() {
axios axios
.post(config.publicPath + '/recipe/week-recipe/') .post(config.publicPath + '/recipe/week-recipe/')

View File

@ -1,5 +1,5 @@
import ElementPlus from 'element-plus' 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' import locale from 'element-plus/lib/locale/lang/zh-cn'
export default (app) => { export default (app) => {

File diff suppressed because it is too large Load Diff

View File

@ -51,8 +51,8 @@ class WeekRecipeListAPI(rest_framework.generics.ListAPIView,
daily_recipe, __ = recipe.models.DailyRecipe.objects.get_or_create( daily_recipe, __ = recipe.models.DailyRecipe.objects.get_or_create(
date=today + datetime.timedelta(days=x) date=today + datetime.timedelta(days=x)
) )
daily_recipe.generate_recipe(recipes) daily_recipe.generate_recipe()
recipes.extend(daily_recipe.recipes.values_list('id', flat=True)) recipes.append(daily_recipe.recipes.values_list('id', flat=True))
return Response(recipe.models.DailyRecipe.get_week_recipe_data(), return Response(recipe.models.DailyRecipe.get_week_recipe_data(),
status=status.HTTP_201_CREATED, headers={}) status=status.HTTP_201_CREATED, headers={})

View File

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

View File

View File

@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.

View File

@ -1,6 +0,0 @@
from django.apps import AppConfig
class TootConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'toot'

View File

@ -1 +0,0 @@
from django.db import models

View File

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

@ -1,3 +0,0 @@
from django.shortcuts import render
# Create your views here.

View File

@ -19,6 +19,3 @@ RECIPE_TYPE_CHOICE = [
FILTER_EXACT = ['exact'] FILTER_EXACT = ['exact']
FILTER_GTE_LTE = ['exact', 'gte', 'gt', 'lte', 'lt'] FILTER_GTE_LTE = ['exact', 'gte', 'gt', 'lte', 'lt']
LARK_WEBHOOK_MSG_TYPE_TEXT = 'text'

View File

@ -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}))

View File

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