Compare commits
No commits in common. "306e54bcd530afa79702eca3c7bc7d70583cc423" and "e2ed14784a8c4f2fad2b0a58d0c06498e3fa4560" have entirely different histories.
306e54bcd5
...
e2ed14784a
13
.vscode/settings.json
vendored
13
.vscode/settings.json
vendored
@ -1,15 +1,2 @@
|
|||||||
{
|
{
|
||||||
"python.formatting.provider": "black",
|
|
||||||
"python.formatting.blackArgs": [
|
|
||||||
"-S",
|
|
||||||
"-C",
|
|
||||||
"-l 120"
|
|
||||||
],
|
|
||||||
"editor.formatOnPaste": true,
|
|
||||||
"editor.formatOnSave": true,
|
|
||||||
"editor.rulers": [
|
|
||||||
120
|
|
||||||
],
|
|
||||||
"editor.bracketPairColorization.enabled": true,
|
|
||||||
"editor.guides.bracketPairs": "active"
|
|
||||||
}
|
}
|
||||||
@ -2,11 +2,12 @@
|
|||||||
from rest_framework import pagination
|
from rest_framework import pagination
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
|
||||||
class PagePaginationWithPageCount(pagination.PageNumberPagination):
|
class PagePaginationWithPageCount(pagination.PageNumberPagination):
|
||||||
page_size_query_param = 'page_size'
|
page_size_query_param = 'page_size'
|
||||||
|
|
||||||
|
def get_paginated_response(self, data):
|
||||||
|
response = super().get_paginated_response(data)
|
||||||
|
response.data['page_count'] = self.page.paginator.num_pages
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
def get_paginated_response(self, data):
|
|
||||||
response = super().get_paginated_response(data)
|
|
||||||
response.data['page_count'] = self.page.paginator.num_pages
|
|
||||||
return response
|
|
||||||
|
|||||||
@ -31,5 +31,3 @@ 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
|
redis==4.1.0
|
||||||
black
|
|
||||||
instagram_private_api
|
|
||||||
|
|||||||
@ -37,11 +37,14 @@ INSTALLED_APPS = [
|
|||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
|
|
||||||
|
|
||||||
# third party
|
# third party
|
||||||
'corsheaders',
|
'corsheaders',
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
'rest_framework.authtoken',
|
'rest_framework.authtoken',
|
||||||
'django_filters',
|
'django_filters',
|
||||||
|
|
||||||
# user apps
|
# user apps
|
||||||
'timer',
|
'timer',
|
||||||
'recipe',
|
'recipe',
|
||||||
@ -71,9 +74,9 @@ TEMPLATES = [
|
|||||||
'django.template.context_processors.request',
|
'django.template.context_processors.request',
|
||||||
'django.contrib.auth.context_processors.auth',
|
'django.contrib.auth.context_processors.auth',
|
||||||
'django.contrib.messages.context_processors.messages',
|
'django.contrib.messages.context_processors.messages',
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
WSGI_APPLICATION = 'dsite.wsgi.application'
|
WSGI_APPLICATION = 'dsite.wsgi.application'
|
||||||
@ -82,17 +85,30 @@ WSGI_APPLICATION = 'dsite.wsgi.application'
|
|||||||
# Database
|
# Database
|
||||||
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
|
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
|
||||||
|
|
||||||
DATABASES = {'default': {'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3')}}
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
|
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# Password validation
|
# Password validation
|
||||||
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
|
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
AUTH_PASSWORD_VALIDATORS = [
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
{'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
|
{
|
||||||
{'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator'},
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||||
{'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'},
|
},
|
||||||
{'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -124,10 +140,11 @@ REST_FRAMEWORK = {
|
|||||||
# 'DEFAULT_PERMISSION_CLASSES': [
|
# 'DEFAULT_PERMISSION_CLASSES': [
|
||||||
# 'rest_framework.permissions.IsAuthenticatedOrReadOnly'
|
# 'rest_framework.permissions.IsAuthenticatedOrReadOnly'
|
||||||
# ],
|
# ],
|
||||||
|
|
||||||
# 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
|
# 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
|
||||||
'DEFAULT_PAGINATION_CLASS': 'core.pagination.PagePaginationWithPageCount',
|
'DEFAULT_PAGINATION_CLASS': 'core.pagination.PagePaginationWithPageCount',
|
||||||
'PAGE_SIZE': 10,
|
'PAGE_SIZE': 10,
|
||||||
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],
|
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend']
|
||||||
}
|
}
|
||||||
|
|
||||||
# CORS
|
# CORS
|
||||||
@ -136,16 +153,9 @@ REST_FRAMEWORK = {
|
|||||||
# 'http://localhost:8080',
|
# 'http://localhost:8080',
|
||||||
# )
|
# )
|
||||||
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_URL = ''
|
||||||
LARK_WEBHOOK_SECRET = ''
|
LARK_WEBHOOK_SECRET = ''
|
||||||
|
|
||||||
MASTODON_SYNCED_IMAGES_LOG = ''
|
|
||||||
IG_PRIVATE_API_SETTINGS = ''
|
|
||||||
IG_LOGIN_USERNAME = ''
|
|
||||||
IG_LOGIN_PASSWORD = ''
|
|
||||||
|
|
||||||
MASTODON_NOFAN_ACCESS_TOKEN = ''
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-row justify="left" gutter="10">
|
<el-row justify="left">
|
||||||
<el-col>
|
<el-col>
|
||||||
<el-form :rules="rules" ref="form" :model="form" label-position="left">
|
<el-form :rules="rules" ref="form" :model="form" label-position="left">
|
||||||
<el-form-item label="名字" prop="name">
|
<el-form-item label="名字" prop="name">
|
||||||
@ -54,33 +54,13 @@
|
|||||||
<el-input type="textarea" v-model="form.note"></el-input>
|
<el-input type="textarea" v-model="form.note"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-col :span="8" v-if="recipe_id">
|
<el-button
|
||||||
<el-button
|
type="primary"
|
||||||
type="danger"
|
plain
|
||||||
plain
|
class="summit-recipe"
|
||||||
class="summit-recipe"
|
@click="onSubmit(recipe_id)"
|
||||||
@click="onSubmitDelete(recipe_id)"
|
>提交</el-button
|
||||||
>删除</el-button
|
>
|
||||||
>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="16" v-if="recipe_id">
|
|
||||||
<el-button
|
|
||||||
type="primary"
|
|
||||||
plain
|
|
||||||
class="summit-recipe"
|
|
||||||
@click="onSubmit(recipe_id)"
|
|
||||||
>提交</el-button
|
|
||||||
>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="24" v-else>
|
|
||||||
<el-button
|
|
||||||
type="primary"
|
|
||||||
plain
|
|
||||||
class="summit-recipe"
|
|
||||||
@click="onSubmit(recipe_id)"
|
|
||||||
>提交</el-button
|
|
||||||
>
|
|
||||||
</el-col>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</el-col>
|
</el-col>
|
||||||
@ -89,8 +69,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import config from '@/config/index';
|
import config from '@/config/index';
|
||||||
import { ElMessage } from 'element-plus';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['recipe_'],
|
props: ['recipe_'],
|
||||||
watch: {
|
watch: {
|
||||||
@ -99,7 +77,7 @@ export default {
|
|||||||
this.recipe_id = val.id;
|
this.recipe_id = val.id;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data: function () {
|
data: function() {
|
||||||
return {
|
return {
|
||||||
form: {
|
form: {
|
||||||
name: null,
|
name: null,
|
||||||
@ -126,41 +104,23 @@ export default {
|
|||||||
if (!recipe_id) {
|
if (!recipe_id) {
|
||||||
axios
|
axios
|
||||||
.post(config.publicPath + '/recipe/recipe/', data)
|
.post(config.publicPath + '/recipe/recipe/', data)
|
||||||
.then(function () {
|
.then(function() {
|
||||||
ElMessage({
|
|
||||||
message: '创建成功.',
|
|
||||||
type: 'success',
|
|
||||||
});
|
|
||||||
location.reload();
|
location.reload();
|
||||||
})
|
})
|
||||||
.catch(function (error) {
|
.catch(function(error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
axios
|
axios
|
||||||
.put(config.publicPath + '/recipe/recipe/' + recipe_id, data)
|
.put(config.publicPath + '/recipe/recipe/' + recipe_id, data)
|
||||||
.then(function () {
|
.then(function() {
|
||||||
ElMessage({
|
location.reload();
|
||||||
message: '修改成功.',
|
|
||||||
type: 'success',
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
.catch(function (error) {
|
.catch(function(error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onSubmitDelete(recipe_id) {
|
|
||||||
axios
|
|
||||||
.delete(config.publicPath + '/recipe/recipe/' + recipe_id)
|
|
||||||
.then(function () {
|
|
||||||
ElMessage.error('删除成功.');
|
|
||||||
location.reload();
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
console.log(error);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@ -168,6 +128,5 @@ export default {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.summit-recipe {
|
.summit-recipe {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -43,64 +43,23 @@
|
|||||||
/>
|
/>
|
||||||
</van-cell-group>
|
</van-cell-group>
|
||||||
<div class="recipe-create">
|
<div class="recipe-create">
|
||||||
<van-row gutter="20">
|
<van-button
|
||||||
<van-col span="8" v-if="recipe_id">
|
round
|
||||||
<van-button
|
type="primary"
|
||||||
class="submit-button"
|
block
|
||||||
round
|
plain
|
||||||
type="danger"
|
hairline
|
||||||
plain
|
:disabled="disable_submit"
|
||||||
hairline
|
@click="onSubmit(recipe_id)"
|
||||||
:disabled="disable_submit"
|
:loading="loading"
|
||||||
@click="onSubmitDelete(recipe_id)"
|
>提交</van-button
|
||||||
:loading="loading"
|
>
|
||||||
>删除</van-button
|
|
||||||
>
|
|
||||||
</van-col>
|
|
||||||
<van-col span="16" v-if="recipe_id">
|
|
||||||
<van-button
|
|
||||||
class="submit-button"
|
|
||||||
round
|
|
||||||
type="primary"
|
|
||||||
plain
|
|
||||||
hairline
|
|
||||||
:disabled="disable_submit"
|
|
||||||
@click="onSubmit(recipe_id)"
|
|
||||||
:loading="loading"
|
|
||||||
>提交</van-button
|
|
||||||
>
|
|
||||||
</van-col>
|
|
||||||
<van-col span="24" v-else>
|
|
||||||
<van-button
|
|
||||||
class="submit-button"
|
|
||||||
round
|
|
||||||
type="primary"
|
|
||||||
plain
|
|
||||||
hairline
|
|
||||||
:disabled="disable_submit"
|
|
||||||
@click="onSubmit(recipe_id)"
|
|
||||||
:loading="loading"
|
|
||||||
>提交</van-button
|
|
||||||
>
|
|
||||||
</van-col>
|
|
||||||
</van-row>
|
|
||||||
</div>
|
</div>
|
||||||
</van-form>
|
</van-form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {
|
import { Form, Field, CellGroup, Radio, RadioGroup, Rate, Button } from 'vant';
|
||||||
Form,
|
|
||||||
Field,
|
|
||||||
CellGroup,
|
|
||||||
Radio,
|
|
||||||
RadioGroup,
|
|
||||||
Rate,
|
|
||||||
Button,
|
|
||||||
Toast,
|
|
||||||
Col,
|
|
||||||
Row,
|
|
||||||
} from 'vant';
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import config from '@/config/index';
|
import config from '@/config/index';
|
||||||
import router from '@/router/index';
|
import router from '@/router/index';
|
||||||
@ -122,8 +81,6 @@ export default {
|
|||||||
[RadioGroup.name]: RadioGroup,
|
[RadioGroup.name]: RadioGroup,
|
||||||
[Rate.name]: Rate,
|
[Rate.name]: Rate,
|
||||||
[Button.name]: Button,
|
[Button.name]: Button,
|
||||||
[Col.name]: Col,
|
|
||||||
[Row.name]: Row,
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -161,30 +118,11 @@ export default {
|
|||||||
(response) => (response, router.push({ name: 'RecipeMobileHome' }))
|
(response) => (response, router.push({ name: 'RecipeMobileHome' }))
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
axios.put(config.publicPath + '/recipe/recipe/' + recipe_id, data).then(
|
axios
|
||||||
(Toast.success({
|
.put(config.publicPath + '/recipe/recipe/' + recipe_id, data)
|
||||||
message: '修改成功',
|
.then((this.loading = false));
|
||||||
forbidClick: true,
|
|
||||||
duration: 500,
|
|
||||||
}),
|
|
||||||
(this.loading = false))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onSubmitDelete(recipe_id) {
|
|
||||||
if (!this.form.name) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.loading = true;
|
|
||||||
axios.delete(config.publicPath + '/recipe/recipe/' + recipe_id).then(
|
|
||||||
(Toast.success({
|
|
||||||
message: '删除成功',
|
|
||||||
forbidClick: true,
|
|
||||||
duration: 500,
|
|
||||||
}),
|
|
||||||
(this.loading = false))
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@ -192,7 +130,4 @@ export default {
|
|||||||
.recipe-create {
|
.recipe-create {
|
||||||
margin: 20px 16px;
|
margin: 20px 16px;
|
||||||
}
|
}
|
||||||
.submit-button {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -7,7 +7,8 @@ class Migration(migrations.Migration):
|
|||||||
|
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = []
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
@ -20,5 +21,5 @@ class Migration(migrations.Migration):
|
|||||||
('rate', models.IntegerField(default=0)),
|
('rate', models.IntegerField(default=0)),
|
||||||
('difficulty', models.IntegerField(default=0)),
|
('difficulty', models.IntegerField(default=0)),
|
||||||
],
|
],
|
||||||
)
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -5,11 +5,15 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [('recipe', '0001_initial')]
|
dependencies = [
|
||||||
|
('recipe', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='recipe', name='recipe_type', field=models.CharField(default='meat', max_length=32)
|
model_name='recipe',
|
||||||
|
name='recipe_type',
|
||||||
|
field=models.CharField(default='meat', max_length=32),
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='DailyRecipe',
|
name='DailyRecipe',
|
||||||
|
|||||||
@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 3.2.6 on 2022-02-04 16:13
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('recipe', '0002_auto_20211002_1926'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='recipe',
|
|
||||||
name='status',
|
|
||||||
field=models.CharField(default='active', max_length=32),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
274
recipe/models.py
274
recipe/models.py
@ -6,195 +6,117 @@ from utils import const
|
|||||||
import utils
|
import utils
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Recipe(models.Model):
|
class Recipe(models.Model):
|
||||||
name = models.CharField(max_length=128)
|
name = models.CharField(max_length=128)
|
||||||
recipe_type = models.CharField(max_length=32, default=const.RECIPE_TYPE_MEAT)
|
recipe_type = models.CharField(max_length=32,
|
||||||
status = models.CharField(max_length=32, default=const.RECIPE_STATUS_ACTIVE)
|
default=const.RECIPE_TYPE_MEAT)
|
||||||
note = models.TextField(null=True)
|
note = models.TextField(null=True)
|
||||||
rate = models.IntegerField(default=0)
|
rate = models.IntegerField(default=0)
|
||||||
difficulty = models.IntegerField(default=0)
|
difficulty = models.IntegerField(default=0)
|
||||||
|
|
||||||
def serialize(self, verbose=False):
|
def serialize(self, verbose=False):
|
||||||
data = {'id': self.id, 'name': self.name, 'recipe_type': self.recipe_type}
|
data = {
|
||||||
if verbose:
|
'id': self.id,
|
||||||
data.update({'difficulty': self.difficulty, 'rate': self.rate, 'note': self.note})
|
'name': self.name,
|
||||||
|
'recipe_type': self.recipe_type,
|
||||||
|
}
|
||||||
|
if verbose:
|
||||||
|
data.update({
|
||||||
|
'difficulty': self.difficulty,
|
||||||
|
'rate': self.rate,
|
||||||
|
'note': self.note,
|
||||||
|
})
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def create_from_str(cls, content):
|
|
||||||
content = content.strip()
|
|
||||||
if not content:
|
|
||||||
return
|
|
||||||
name, *data = content.split(' ')
|
|
||||||
recipe_type = rate = difficulty = None
|
|
||||||
keys = [recipe_type, rate, difficulty]
|
|
||||||
for x in range(len(data)):
|
|
||||||
keys[x] = data[x]
|
|
||||||
recipe_type, rate, difficulty = keys
|
|
||||||
|
|
||||||
if recipe_type == '肉':
|
|
||||||
recipe_type = const.RECIPE_TYPE_MEAT
|
|
||||||
elif recipe_type == '菜':
|
|
||||||
recipe_type = const.RECIPE_TYPE_VEGETABLE
|
|
||||||
elif recipe_type == '汤':
|
|
||||||
recipe_type = const.RECIPE_TYPE_SOUP
|
|
||||||
else:
|
|
||||||
recipe_type = const.RECIPE_TYPE_MEAT
|
|
||||||
if rate:
|
|
||||||
try:
|
|
||||||
rate = int(rate)
|
|
||||||
except:
|
|
||||||
rate = 0
|
|
||||||
else:
|
|
||||||
rate = 0
|
|
||||||
if difficulty:
|
|
||||||
try:
|
|
||||||
difficulty = int(difficulty)
|
|
||||||
except:
|
|
||||||
difficulty = 0
|
|
||||||
else:
|
|
||||||
difficulty = 0
|
|
||||||
recipe = cls.objects.create(
|
|
||||||
name=name, recipe_type=recipe_type, rate=rate, difficulty=difficulty, status=const.RECIPE_STATUS_ACTIVE
|
|
||||||
)
|
|
||||||
return recipe
|
|
||||||
|
|
||||||
@property
|
|
||||||
def display_recipe_type(self):
|
|
||||||
if self.recipe_type == const.RECIPE_TYPE_VEGETABLE:
|
|
||||||
return '菜'
|
|
||||||
elif self.recipe_type == const.RECIPE_TYPE_MEAT:
|
|
||||||
return '肉'
|
|
||||||
elif self.recipe_type == const.RECIPE_TYPE_SOUP:
|
|
||||||
return '汤'
|
|
||||||
return '肉'
|
|
||||||
|
|
||||||
@property
|
|
||||||
def display_rate(self):
|
|
||||||
return '🍚' * self.rate
|
|
||||||
|
|
||||||
@property
|
|
||||||
def display_difficult(self):
|
|
||||||
return '⭐️' * self.difficulty
|
|
||||||
|
|
||||||
def construct_lart_card(self):
|
|
||||||
|
|
||||||
data = {
|
|
||||||
"config": {"wide_screen_mode": True},
|
|
||||||
"header": {"title": {"tag": "plain_text", "content": self.name}, "template": "blue"},
|
|
||||||
"elements": [
|
|
||||||
{
|
|
||||||
"tag": "markdown",
|
|
||||||
"content": "**类型**:%s\n**评分**:%s\n**难度**:%s"
|
|
||||||
% (self.display_recipe_type, self.display_rate, self.display_difficult),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"tag": "action",
|
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"tag": "button",
|
|
||||||
"text": {"tag": "plain_text", "content": "查看"},
|
|
||||||
"multi_url": {
|
|
||||||
"url": "https://recipe.tunpok.com/recipe/%s" % self.id,
|
|
||||||
"android_url": "https://recipe.tunpok.com/recipe-mobile/recipe/%s" % self.id,
|
|
||||||
"ios_url": "https://recipe.tunpok.com/recipe-mobile/recipe/%s" % self.id,
|
|
||||||
"pc_url": "https://recipe.tunpok.com/recipe/%s" % self.id,
|
|
||||||
},
|
|
||||||
"type": "primary",
|
|
||||||
},
|
|
||||||
{"tag": "button", "text": {"tag": "plain_text", "content": "删除"}, "type": "danger"},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
class DailyRecipe(models.Model):
|
class DailyRecipe(models.Model):
|
||||||
recipes = models.ManyToManyField(Recipe)
|
recipes = models.ManyToManyField(Recipe)
|
||||||
date = models.DateField()
|
date = models.DateField()
|
||||||
meal_type = models.CharField(max_length=32, default=const.DAILY_RECIPE_MEAL_TYPE_SUPPER)
|
meal_type = models.CharField(
|
||||||
|
max_length=32,
|
||||||
|
default=const.DAILY_RECIPE_MEAL_TYPE_SUPPER,)
|
||||||
|
|
||||||
def generate_recipe(self, prev_recipes=None, ignore_prev=False):
|
def generate_recipe(self, prev_recipes=None, ignore_prev=False):
|
||||||
if not prev_recipes:
|
if not prev_recipes:
|
||||||
prev_recipes = []
|
prev_recipes = []
|
||||||
if ignore_prev:
|
if ignore_prev:
|
||||||
prev_recipes = []
|
prev_recipes = []
|
||||||
|
|
||||||
recipes = []
|
recipes = []
|
||||||
retry = 5
|
retry = 5
|
||||||
|
|
||||||
# meat
|
# meat
|
||||||
for x in range(0, 2):
|
for x in range(0,2):
|
||||||
while True:
|
while True:
|
||||||
recipe = (
|
recipe = Recipe.objects.filter(
|
||||||
Recipe.objects.filter(recipe_type=const.RECIPE_TYPE_MEAT)
|
recipe_type=const.RECIPE_TYPE_MEAT,
|
||||||
.exclude(status=const.RECIPE_STATUS_DELETED)
|
).order_by('?').first()
|
||||||
.order_by('?')
|
if recipe and recipe.id not in recipes and recipe.id not in prev_recipes:
|
||||||
.first()
|
recipes.append(recipe.id)
|
||||||
)
|
break
|
||||||
if recipe and recipe.id not in recipes and recipe.id not in prev_recipes:
|
else:
|
||||||
recipes.append(recipe.id)
|
retry -= 1
|
||||||
break
|
if retry <= 0:
|
||||||
else:
|
retry = 5
|
||||||
retry -= 1
|
break
|
||||||
if retry <= 0:
|
|
||||||
retry = 5
|
|
||||||
break
|
|
||||||
|
|
||||||
# vegetable
|
# vegetable
|
||||||
for x in range(0, 1):
|
for x in range(0, 1):
|
||||||
while True:
|
while True:
|
||||||
recipe = (
|
recipe = Recipe.objects.filter(
|
||||||
Recipe.objects.filter(recipe_type=const.RECIPE_TYPE_VEGETABLE)
|
recipe_type=const.RECIPE_TYPE_VEGETABLE,
|
||||||
.exclude(status=const.RECIPE_STATUS_DELETED)
|
).order_by('?').first()
|
||||||
.order_by('?')
|
if recipe and recipe.id not in recipes and recipe.id not in prev_recipes:
|
||||||
.first()
|
recipes.append(recipe.id)
|
||||||
)
|
break
|
||||||
if recipe and recipe.id not in recipes and recipe.id not in prev_recipes:
|
else:
|
||||||
recipes.append(recipe.id)
|
retry -= 1
|
||||||
break
|
if retry <= 0:
|
||||||
else:
|
retry = 5
|
||||||
retry -= 1
|
break
|
||||||
if retry <= 0:
|
|
||||||
retry = 5
|
|
||||||
break
|
|
||||||
|
|
||||||
# soup
|
# soup
|
||||||
if random.randint(0, 2):
|
if random.randint(0,2):
|
||||||
recipe = (
|
recipe = Recipe.objects.filter(
|
||||||
Recipe.objects.filter(recipe_type=const.RECIPE_TYPE_SOUP)
|
recipe_type=const.RECIPE_TYPE_SOUP,
|
||||||
.exclude(status=const.RECIPE_STATUS_DELETED)
|
).order_by('?').first()
|
||||||
.order_by('?')
|
# if recipe not in recipes and recipe not in prev_recipes:
|
||||||
.first()
|
if recipe:
|
||||||
)
|
recipes.append(recipe.id)
|
||||||
# if recipe not in recipes and recipe not in prev_recipes:
|
|
||||||
if recipe:
|
|
||||||
recipes.append(recipe.id)
|
|
||||||
|
|
||||||
self.recipes.set(Recipe.objects.filter(id__in=recipes))
|
self.recipes.set(Recipe.objects.filter(id__in=recipes))
|
||||||
|
|
||||||
def serialize(self):
|
def serialize(self):
|
||||||
data = {const.RECIPE_TYPE_MEAT: [], const.RECIPE_TYPE_VEGETABLE: [], const.RECIPE_TYPE_SOUP: []}
|
data = {
|
||||||
for key_, value_ in data.items():
|
const.RECIPE_TYPE_MEAT: [],
|
||||||
for recipe in self.recipes.filter(recipe_type=key_).order_by('id'):
|
const.RECIPE_TYPE_VEGETABLE: [],
|
||||||
value_.append(recipe.serialize())
|
const.RECIPE_TYPE_SOUP: [],
|
||||||
|
}
|
||||||
|
for key_, value_ in data.items():
|
||||||
|
for recipe in self.recipes.filter(
|
||||||
|
recipe_type=key_).order_by('id'):
|
||||||
|
value_.append(recipe.serialize())
|
||||||
|
|
||||||
date = now()
|
date = now()
|
||||||
date = date.replace(year=self.date.year, month=self.date.month, day=self.date.day, hour=0, minute=0)
|
date = date.replace(year=self.date.year, month=self.date.month,
|
||||||
data['date'] = utils.timestamp_of(utils.day_start(date))
|
day=self.date.day, hour=0, minute=0)
|
||||||
data['id'] = self.id
|
data['date'] = utils.timestamp_of(utils.day_start(date))
|
||||||
return data
|
data['id'] = self.id
|
||||||
|
return data
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_week_recipe_data(cls):
|
def get_week_recipe_data(cls):
|
||||||
today = localtime()
|
today = localtime()
|
||||||
week_start = (today - datetime.timedelta(days=today.weekday())).date()
|
week_start = (today - datetime.timedelta(days=today.weekday())).date()
|
||||||
week_end = week_start + datetime.timedelta(days=6)
|
week_end = week_start + datetime.timedelta(days=6)
|
||||||
daily_recipes = cls.objects.filter(date__gte=week_start, date__lte=week_end).order_by('date')
|
daily_recipes = cls.objects.filter(
|
||||||
data = [{}] * (7 - len(daily_recipes))
|
date__gte=week_start,
|
||||||
|
date__lte=week_end,
|
||||||
|
).order_by('date')
|
||||||
|
data = [{}] * (7 - len(daily_recipes))
|
||||||
|
|
||||||
for daily_recipe in daily_recipes:
|
for daily_recipe in daily_recipes:
|
||||||
data.append(daily_recipe.serialize())
|
data.append(daily_recipe.serialize())
|
||||||
return data
|
return data
|
||||||
|
|||||||
@ -4,25 +4,23 @@ from rest_framework import serializers
|
|||||||
|
|
||||||
import recipe.models
|
import recipe.models
|
||||||
|
|
||||||
|
|
||||||
class RecipeSerializer(serializers.ModelSerializer):
|
class RecipeSerializer(serializers.ModelSerializer):
|
||||||
id = serializers.IntegerField(read_only=True)
|
id = serializers.IntegerField(read_only=True)
|
||||||
|
class Meta:
|
||||||
class Meta:
|
model = recipe.models.Recipe
|
||||||
model = recipe.models.Recipe
|
fields = '__all__'
|
||||||
fields = '__all__'
|
|
||||||
|
|
||||||
|
|
||||||
class WeekRecipeSerializer(serializers.ModelSerializer):
|
class WeekRecipeSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
|
||||||
model = recipe.models.DailyRecipe
|
class Meta:
|
||||||
fields = '__all__'
|
model = recipe.models.DailyRecipe
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
class DailyRecipeSerializer(serializers.ModelSerializer):
|
class DailyRecipeSerializer(serializers.ModelSerializer):
|
||||||
id = serializers.IntegerField(read_only=True)
|
id = serializers.IntegerField(read_only=True)
|
||||||
recipes = RecipeSerializer(many=True)
|
recipes = RecipeSerializer(many=True)
|
||||||
|
class Meta:
|
||||||
class Meta:
|
model = recipe.models.DailyRecipe
|
||||||
model = recipe.models.DailyRecipe
|
fields = '__all__'
|
||||||
fields = '__all__'
|
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
from django.conf.urls import include, url
|
from django.conf.urls import include, url
|
||||||
|
|
||||||
# from django.core.urlresolvers import reverse
|
# from django.core.urlresolvers import reverse
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from rest_framework import routers
|
from rest_framework import routers
|
||||||
@ -12,5 +11,6 @@ urlpatterns = [
|
|||||||
url(r'^recipe/(?P<pk>\d+)$', views.RecipeAPI.as_view(), name='recipe-detail'),
|
url(r'^recipe/(?P<pk>\d+)$', views.RecipeAPI.as_view(), name='recipe-detail'),
|
||||||
url(r'^recipe/$', views.RecipeListAPI.as_view()),
|
url(r'^recipe/$', views.RecipeListAPI.as_view()),
|
||||||
url(r'^week-recipe/$', views.WeekRecipeListAPI.as_view()),
|
url(r'^week-recipe/$', views.WeekRecipeListAPI.as_view()),
|
||||||
url(r'^daily-recipe/(?P<pk>\d+)$', views.DailyRecipeAPI.as_view(), name='dailyrecipe-detail'),
|
url(r'^daily-recipe/(?P<pk>\d+)$', views.DailyRecipeAPI.as_view(),
|
||||||
|
name='dailyrecipe-detail'),
|
||||||
]
|
]
|
||||||
|
|||||||
107
recipe/views.py
107
recipe/views.py
@ -11,70 +11,73 @@ import recipe.models
|
|||||||
import recipe.serializers
|
import recipe.serializers
|
||||||
from utils import const
|
from utils import const
|
||||||
|
|
||||||
|
class RecipeAPI(rest_framework.generics.RetrieveUpdateAPIView):
|
||||||
|
|
||||||
class RecipeAPI(rest_framework.generics.RetrieveUpdateDestroyAPIView):
|
# authentication_classes = (authentication.TokenAuthentication,
|
||||||
|
# authentication.SessionAuthentication,
|
||||||
# authentication_classes = (authentication.TokenAuthentication,
|
# authentication.BasicAuthentication)
|
||||||
# authentication.SessionAuthentication,
|
# permission_classes = (permissions.IsAuthenticated,)
|
||||||
# authentication.BasicAuthentication)
|
queryset = recipe.models.Recipe.objects.all()
|
||||||
# permission_classes = (permissions.IsAuthenticated,)
|
serializer_class = recipe.serializers.RecipeSerializer
|
||||||
queryset = recipe.models.Recipe.objects.all()
|
|
||||||
serializer_class = recipe.serializers.RecipeSerializer
|
|
||||||
|
|
||||||
def perform_destroy(self, instance):
|
|
||||||
instance.status = const.RECIPE_STATUS_DELETED
|
|
||||||
instance.save(update_fields=['status'])
|
|
||||||
|
|
||||||
|
|
||||||
class RecipeListAPI(rest_framework.generics.ListAPIView, rest_framework.generics.CreateAPIView):
|
class RecipeListAPI(rest_framework.generics.ListAPIView,
|
||||||
|
rest_framework.generics.CreateAPIView):
|
||||||
|
|
||||||
# authentication_classes = (authentication.TokenAuthentication,
|
# authentication_classes = (authentication.TokenAuthentication,
|
||||||
# authentication.SessionAuthentication,
|
# authentication.SessionAuthentication,
|
||||||
# authentication.BasicAuthentication)
|
# authentication.BasicAuthentication)
|
||||||
# permission_classes = (permissions.IsAuthenticated,)
|
# permission_classes = (permissions.IsAuthenticated,)
|
||||||
queryset = recipe.models.Recipe.objects.exclude(status=const.RECIPE_STATUS_DELETED)
|
queryset = recipe.models.Recipe.objects.all()
|
||||||
serializer_class = recipe.serializers.RecipeSerializer
|
serializer_class = recipe.serializers.RecipeSerializer
|
||||||
filterset_fields = {
|
filterset_fields = {
|
||||||
'recipe_type': const.FILTER_EXACT,
|
'recipe_type': const.FILTER_EXACT,
|
||||||
'difficulty': const.FILTER_GTE_LTE,
|
'difficulty': const.FILTER_GTE_LTE,
|
||||||
'rate': const.FILTER_GTE_LTE,
|
'rate': const.FILTER_GTE_LTE,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class WeekRecipeListAPI(rest_framework.generics.ListAPIView, rest_framework.generics.CreateAPIView):
|
class WeekRecipeListAPI(rest_framework.generics.ListAPIView,
|
||||||
|
rest_framework.generics.CreateAPIView):
|
||||||
|
|
||||||
queryset = recipe.models.DailyRecipe.objects.all()
|
queryset = recipe.models.DailyRecipe.objects.all()
|
||||||
serializer_class = recipe.serializers.WeekRecipeSerializer
|
serializer_class = recipe.serializers.WeekRecipeSerializer
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
# Monday == 0 ... Sunday == 6
|
# Monday == 0 ... Sunday == 6
|
||||||
today = localtime()
|
today = localtime()
|
||||||
recipes = []
|
recipes = []
|
||||||
for x in range(0, 7 - today.weekday()):
|
for x in range(0, 7-today.weekday()):
|
||||||
daily_recipe, __ = recipe.models.DailyRecipe.objects.get_or_create(date=today + datetime.timedelta(days=x))
|
daily_recipe, __ = recipe.models.DailyRecipe.objects.get_or_create(
|
||||||
daily_recipe.generate_recipe(recipes)
|
date=today + datetime.timedelta(days=x)
|
||||||
recipes.extend(daily_recipe.recipes.values_list('id', flat=True))
|
)
|
||||||
return Response(recipe.models.DailyRecipe.get_week_recipe_data(), status=status.HTTP_201_CREATED, headers={})
|
daily_recipe.generate_recipe(recipes)
|
||||||
|
recipes.extend(daily_recipe.recipes.values_list('id', flat=True))
|
||||||
|
return Response(recipe.models.DailyRecipe.get_week_recipe_data(),
|
||||||
|
status=status.HTTP_201_CREATED, headers={})
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
data = recipe.models.DailyRecipe.get_week_recipe_data()
|
data = recipe.models.DailyRecipe.get_week_recipe_data()
|
||||||
return Response(data)
|
return Response(data)
|
||||||
|
|
||||||
|
|
||||||
class DailyRecipeAPI(rest_framework.generics.RetrieveUpdateAPIView):
|
class DailyRecipeAPI(rest_framework.generics.RetrieveUpdateAPIView):
|
||||||
|
|
||||||
queryset = recipe.models.DailyRecipe.objects.all()
|
queryset = recipe.models.DailyRecipe.objects.all()
|
||||||
serializer_class = recipe.serializers.DailyRecipeSerializer
|
serializer_class = recipe.serializers.DailyRecipeSerializer
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
daily_recipe = recipe.models.DailyRecipe.objects.get(id=kwargs['pk'])
|
daily_recipe = recipe.models.DailyRecipe.objects.get(id=kwargs['pk'])
|
||||||
daily_recipe.generate_recipe()
|
daily_recipe.generate_recipe()
|
||||||
return Response(daily_recipe.serialize(), status=status.HTTP_201_CREATED, headers={})
|
return Response(daily_recipe.serialize(), status=status.HTTP_201_CREATED,
|
||||||
|
headers={})
|
||||||
|
|
||||||
def put(self, request, *args, **kwargs):
|
def put(self, request, *args, **kwargs):
|
||||||
daily_recipe = recipe.models.DailyRecipe.objects.get(id=kwargs['pk'])
|
daily_recipe = recipe.models.DailyRecipe.objects.get(id=kwargs['pk'])
|
||||||
recipes = request.data.get('meat', [])
|
recipes = request.data.get('meat',[])
|
||||||
recipes.extend(request.data.get('vegetable', []))
|
recipes.extend(request.data.get('vegetable', []))
|
||||||
recipes.extend(request.data.get('soup', []))
|
recipes.extend(request.data.get('soup', []))
|
||||||
daily_recipe.recipes.set(recipe.models.Recipe.objects.filter(id__in=recipes))
|
daily_recipe.recipes.set(recipe.models.Recipe.objects.filter(
|
||||||
return Response(daily_recipe.serialize(), status=status.HTTP_201_CREATED, headers={})
|
id__in=recipes))
|
||||||
|
return Response(daily_recipe.serialize(), status=status.HTTP_201_CREATED,
|
||||||
|
headers={})
|
||||||
|
|||||||
@ -1,83 +0,0 @@
|
|||||||
# coding: utf-8
|
|
||||||
from datetime import datetime
|
|
||||||
from dateutil import tz
|
|
||||||
from email import encoders
|
|
||||||
from email.header import Header
|
|
||||||
from email.mime.base import MIMEBase
|
|
||||||
from email.mime.multipart import MIMEMultipart
|
|
||||||
from email.mime.text import MIMEText
|
|
||||||
from email.utils import parseaddr, formataddr
|
|
||||||
import os, tarfile
|
|
||||||
import smtplib
|
|
||||||
|
|
||||||
|
|
||||||
to_zone = tz.gettz('Asia/Shanghai')
|
|
||||||
localtime = datetime.now(tz=to_zone)
|
|
||||||
|
|
||||||
|
|
||||||
def make_targz(output_filename, source_dir):
|
|
||||||
"""
|
|
||||||
一次性打包目录为tar.gz
|
|
||||||
:param output_filename: 压缩文件名
|
|
||||||
:param source_dir: 需要打包的目录
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
with tarfile.open(output_filename, "w:gz") as tar:
|
|
||||||
tar.add(source_dir, arcname=os.path.basename(source_dir))
|
|
||||||
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def send_email(file_name):
|
|
||||||
def _format_addr(s):
|
|
||||||
name, addr = parseaddr(s)
|
|
||||||
return formataddr((Header(name, 'utf-8').encode(), addr))
|
|
||||||
|
|
||||||
smtp_server = 'smtp.gmail.com'
|
|
||||||
smtp_port = 587
|
|
||||||
server = smtplib.SMTP(smtp_server, smtp_port)
|
|
||||||
server.starttls()
|
|
||||||
# 剩下的代码和前面的一模一样:
|
|
||||||
# server.set_debuglevel(1)
|
|
||||||
|
|
||||||
from_addr = ''
|
|
||||||
to_addr = ''
|
|
||||||
to_zone = tz.gettz('Asia/Shanghai')
|
|
||||||
localtime = datetime.now(tz=to_zone)
|
|
||||||
|
|
||||||
msg = MIMEMultipart()
|
|
||||||
msg['From'] = _format_addr('Vaultwarden <%s>' % from_addr)
|
|
||||||
msg['To'] = _format_addr('<%s>' % to_addr)
|
|
||||||
msg['Subject'] = Header('bitwarden 备份 %s' % localtime.strftime('%Y-%m-%d'), 'utf-8').encode()
|
|
||||||
|
|
||||||
# 邮件正文是MIMEText:
|
|
||||||
msg.attach(MIMEText('backup file attached', 'plain', 'utf-8'))
|
|
||||||
|
|
||||||
# 添加附件就是加上一个MIMEBase,从本地读取一个图片:
|
|
||||||
with open(file_name, 'rb') as f:
|
|
||||||
# 设置附件的MIME和文件名
|
|
||||||
mime = MIMEBase('tar.gz', 'tar.gz', filename=file_name)
|
|
||||||
# 加上必要的头信息:
|
|
||||||
mime.add_header('Content-Disposition', 'attachment', filename=file_name)
|
|
||||||
mime.add_header('Content-ID', '<0>')
|
|
||||||
mime.add_header('X-Attachment-Id', '0')
|
|
||||||
# 把附件的内容读进来:
|
|
||||||
mime.set_payload(f.read())
|
|
||||||
# 用Base64编码:
|
|
||||||
encoders.encode_base64(mime)
|
|
||||||
# 添加到MIMEMultipart:
|
|
||||||
msg.attach(mime)
|
|
||||||
|
|
||||||
server.login(from_email, password)
|
|
||||||
server.sendmail(from_addr, [to_addr], msg.as_string())
|
|
||||||
server.quit()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
file_name = '/root/develop/vaultwarden/backup/bitwarden-%s.tar.gz' % localtime.strftime('%Y-%m-%d')
|
|
||||||
if make_targz(file_name, '/root/develop/vaultwarden/vw-data'):
|
|
||||||
send_email(file_name)
|
|
||||||
240
scripts/dodo.py
240
scripts/dodo.py
@ -1,14 +1,5 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# --coding:utf-8--
|
# --coding:utf-8--
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dsite.settings")
|
|
||||||
sys.path.insert(0, '../')
|
|
||||||
sys.path.insert(0, './')
|
|
||||||
from django.core.wsgi import get_wsgi_application
|
|
||||||
|
|
||||||
get_wsgi_application()
|
|
||||||
|
|
||||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||||
import json
|
import json
|
||||||
@ -24,9 +15,6 @@ import time
|
|||||||
import subprocess
|
import subprocess
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
import recipe.models
|
|
||||||
from utils import const
|
|
||||||
|
|
||||||
APP_VERIFICATION_TOKEN = 'uKQQiOVMYg2cTgrjkyBmodrHTUaCXzG3'
|
APP_VERIFICATION_TOKEN = 'uKQQiOVMYg2cTgrjkyBmodrHTUaCXzG3'
|
||||||
APP_ID = 'cli_a115fe8b83f9100c'
|
APP_ID = 'cli_a115fe8b83f9100c'
|
||||||
APP_SECRET = 'yuSQenId0VfvwdZ3qL9wMd8FpCMEUL0u'
|
APP_SECRET = 'yuSQenId0VfvwdZ3qL9wMd8FpCMEUL0u'
|
||||||
@ -38,46 +26,48 @@ KEDAI_ID = '107263380636355825'
|
|||||||
logging.basicConfig(filename='/root/develop/log/dodo.log', level=logging.INFO)
|
logging.basicConfig(filename='/root/develop/log/dodo.log', level=logging.INFO)
|
||||||
logger = logging.getLogger('/root/develop/log/dodo.log')
|
logger = logging.getLogger('/root/develop/log/dodo.log')
|
||||||
|
|
||||||
mastodon_cli = Mastodon(access_token='Ug_bUMWCk3RLamOnqYIytmeB0nO6aNfjdmf06mAj2bE', api_base_url='https://nofan.xyz')
|
mastodon = Mastodon(
|
||||||
|
access_token = 'Ug_bUMWCk3RLamOnqYIytmeB0nO6aNfjdmf06mAj2bE',
|
||||||
|
api_base_url = 'https://nofan.xyz'
|
||||||
|
)
|
||||||
|
|
||||||
pool = redis.ConnectionPool(host='localhost', port=6379, decode_responses=True)
|
pool = redis.ConnectionPool(host='localhost', port=6379, decode_responses=True)
|
||||||
redis_cli = redis.Redis(host='localhost', port=6379, decode_responses=True)
|
redis_cli = redis.Redis(host='localhost', port=6379, decode_responses=True)
|
||||||
|
|
||||||
|
class AESCipher(object):
|
||||||
class AESCipher(object):
|
|
||||||
def __init__(self, key):
|
def __init__(self, key):
|
||||||
self.bs = AES.block_size
|
self.bs = AES.block_size
|
||||||
self.key = hashlib.sha256(AESCipher.str_to_bytes(key)).digest()
|
self.key=hashlib.sha256(AESCipher.str_to_bytes(key)).digest()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def str_to_bytes(data):
|
def str_to_bytes(data):
|
||||||
u_type = type(b"".decode('utf8'))
|
u_type = type(b"".decode('utf8'))
|
||||||
if isinstance(data, u_type):
|
if isinstance(data, u_type):
|
||||||
return data.encode('utf8')
|
return data.encode('utf8')
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _unpad(s):
|
def _unpad(s):
|
||||||
return s[: -ord(s[len(s) - 1 :])]
|
return s[:-ord(s[len(s) - 1:])]
|
||||||
|
|
||||||
def decrypt(self, enc):
|
def decrypt(self, enc):
|
||||||
iv = enc[: AES.block_size]
|
iv = enc[:AES.block_size]
|
||||||
cipher = AES.new(self.key, AES.MODE_CBC, iv)
|
cipher = AES.new(self.key, AES.MODE_CBC, iv)
|
||||||
return self._unpad(cipher.decrypt(enc[AES.block_size :]))
|
return self._unpad(cipher.decrypt(enc[AES.block_size:]))
|
||||||
|
|
||||||
def decrypt_string(self, enc):
|
def decrypt_string(self, enc):
|
||||||
enc = base64.b64decode(enc)
|
enc = base64.b64decode(enc)
|
||||||
return self.decrypt(enc).decode('utf8')
|
return self.decrypt(enc).decode('utf8')
|
||||||
|
|
||||||
|
|
||||||
def get_tenant_access_token(): # 获取token
|
def get_tenant_access_token(): # 获取token
|
||||||
token = redis_cli.get('tenant_access_token_%s' % APP_ID)
|
token = redis_cli.get('tenant_access_token_%s' % APP_ID)
|
||||||
if token:
|
if token:
|
||||||
return token
|
return token
|
||||||
|
|
||||||
url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal/"
|
url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal/"
|
||||||
headers = {"Content-Type": "application/json"}
|
headers = {
|
||||||
req_body = {"app_id": APP_ID, "app_secret": APP_SECRET}
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
req_body = {
|
||||||
|
"app_id": APP_ID,
|
||||||
|
"app_secret": APP_SECRET
|
||||||
|
}
|
||||||
|
|
||||||
data = bytes(json.dumps(req_body), encoding='utf8')
|
data = bytes(json.dumps(req_body), encoding='utf8')
|
||||||
req = request.Request(url=url, data=data, headers=headers, method='POST')
|
req = request.Request(url=url, data=data, headers=headers, method='POST')
|
||||||
@ -93,35 +83,36 @@ def get_tenant_access_token(): # 获取token
|
|||||||
if code != 0:
|
if code != 0:
|
||||||
logger.error("get tenant_access_token error, code =%s", code)
|
logger.error("get tenant_access_token error, code =%s", code)
|
||||||
return ""
|
return ""
|
||||||
token = rsp_dict.get("tenant_access_token", "")
|
token = redis_cli.set('tenant_access_token_%s' % APP_ID,
|
||||||
redis_cli.set('tenant_access_token_%s' % APP_ID, rsp_dict.get("tenant_access_token", ""), ex=60 * 30)
|
rsp_dict.get("tenant_access_token", ""),
|
||||||
|
ex=60*30)
|
||||||
|
|
||||||
return token
|
return token
|
||||||
|
|
||||||
|
|
||||||
def get_group_name(chat_id):
|
def get_group_name(chat_id):
|
||||||
group_name = redis_cli.get('group_name_%s' % chat_id)
|
group_name = redis_cli.get('group_name_%s' % chat_id)
|
||||||
if not group_name:
|
if not group_name:
|
||||||
url = "https://open.feishu.cn/open-apis/im/v1/chats/"
|
url = "https://open.feishu.cn/open-apis/im/v1/chats/"
|
||||||
headers = {"Content-Type": "application/json", "Authorization": "Bearer " + get_tenant_access_token()}
|
headers = {
|
||||||
try:
|
"Content-Type": "application/json",
|
||||||
resp = requests.get(url + chat_id, headers=headers)
|
"Authorization": "Bearer " + get_tenant_access_token()
|
||||||
resp_data = resp.json()
|
}
|
||||||
code = resp_data.get("code", -1)
|
try:
|
||||||
if code == 0:
|
resp = requests.get(url+chat_id, headers=headers)
|
||||||
group_name = resp_data.get('data', {}).get('name')
|
resp_data = resp.json()
|
||||||
redis_cli.set('group_name_%s' % chat_id, group_name, ex=60 * 60 * 24)
|
code = resp_data.get("code", -1)
|
||||||
except:
|
if code == 0:
|
||||||
# todo: log
|
group_name = resp_data.get('data', {}).get('name')
|
||||||
return
|
redis_cli.set('group_name_%s' % chat_id,
|
||||||
return group_name
|
group_name,
|
||||||
|
ex=60*60*24)
|
||||||
|
except:
|
||||||
|
# todo: log
|
||||||
|
return
|
||||||
|
return group_name
|
||||||
|
|
||||||
|
|
||||||
class RequestHandler(BaseHTTPRequestHandler):
|
class RequestHandler(BaseHTTPRequestHandler):
|
||||||
def do_GET(self):
|
|
||||||
"""Serve a GET request."""
|
|
||||||
self.response("")
|
|
||||||
|
|
||||||
def do_POST(self):
|
def do_POST(self):
|
||||||
# 解析请求 body
|
# 解析请求 body
|
||||||
req_body = self.rfile.read(int(self.headers['content-length']))
|
req_body = self.rfile.read(int(self.headers['content-length']))
|
||||||
@ -150,12 +141,12 @@ class RequestHandler(BaseHTTPRequestHandler):
|
|||||||
event_id = obj.get('header', {}).get('event_id', '')
|
event_id = obj.get('header', {}).get('event_id', '')
|
||||||
# 重复收到的事件不处理
|
# 重复收到的事件不处理
|
||||||
if event_id and redis_cli.get(event_id):
|
if event_id and redis_cli.get(event_id):
|
||||||
self.response("")
|
self.response("")
|
||||||
return
|
return
|
||||||
event = obj.get("event")
|
event = obj.get("event")
|
||||||
if event.get("message"):
|
if event.get("message"):
|
||||||
self.handle_message(event, event_id)
|
self.handle_message(event, event_id)
|
||||||
return
|
return
|
||||||
return
|
return
|
||||||
|
|
||||||
def handle_request_url_verify(self, post_obj):
|
def handle_request_url_verify(self, post_obj):
|
||||||
@ -186,82 +177,57 @@ class RequestHandler(BaseHTTPRequestHandler):
|
|||||||
text = text.lstrip()
|
text = text.lstrip()
|
||||||
orig_text = text
|
orig_text = text
|
||||||
if ADD_GROUP_NAME:
|
if ADD_GROUP_NAME:
|
||||||
group_name = get_group_name(msg.get("chat_id"))
|
group_name = get_group_name(msg.get("chat_id"))
|
||||||
text = '%s #%s' % (text, group_name)
|
text = '%s #%s' % (text, group_name)
|
||||||
else:
|
else:
|
||||||
open_id = {"open_id": event.get("sender", {}).get('sender_id', {}).get('open_id')}
|
open_id = {"open_id": event.get("sender", {}).get(
|
||||||
|
'sender_id', {}).get('open_id')}
|
||||||
self.response("")
|
self.response("")
|
||||||
if orig_text.startswith('/'):
|
if orig_text.startswith('/'):
|
||||||
redis_cli.set(event_id, int(time.time()), ex=60 * 60 * 7)
|
redis_cli.set(event_id, int(time.time()), ex=60*60*7)
|
||||||
if orig_text not in ['/last', '/del']:
|
if orig_text not in ['/last', '/del']:
|
||||||
flag = False
|
if not orig_text.startswith('/deploy '):
|
||||||
for action_ in ['/deploy ', '/菜谱 ']:
|
self.msg_compoment(access_token, open_id, '指令错误')
|
||||||
if orig_text.startswith(action_):
|
return
|
||||||
flag = True
|
if orig_text == '/last':
|
||||||
break
|
try:
|
||||||
if not flag:
|
statuses = mastodon.account_statuses(KEDAI_ID, limit=1)
|
||||||
self.msg_compoment(access_token, open_id, '指令错误')
|
s_text = BeautifulSoup(statuses[0]['content'], 'html.parser')
|
||||||
return
|
self.msg_compoment(access_token, open_id,
|
||||||
if orig_text == '/last':
|
s_text.get_text(''))
|
||||||
try:
|
except Exception as exc:
|
||||||
statuses = mastodon_cli.account_statuses(KEDAI_ID, limit=1)
|
logger.error('operation error: %s', str(exc))
|
||||||
s_text = BeautifulSoup(statuses[0]['content'], 'html.parser')
|
elif orig_text == '/del':
|
||||||
self.msg_compoment(access_token, open_id, s_text.get_text(''))
|
try:
|
||||||
except Exception as exc:
|
statuses = mastodon.account_statuses(KEDAI_ID, limit=1)
|
||||||
logger.error('operation error: %s', str(exc))
|
Mastodon.status_delete(statuses[0]['id'])
|
||||||
elif orig_text == '/del':
|
s_text = BeautifulSoup(statuses[0]['content'], 'html.parser')
|
||||||
try:
|
self.msg_compoment(access_token, open_id,
|
||||||
statuses = mastodon_cli.account_statuses(KEDAI_ID, limit=1)
|
'已删除: ' + s_text.get_text(''))
|
||||||
mastodon_cli.status_delete(statuses[0]['id'])
|
except Exception as exc:
|
||||||
s_text = BeautifulSoup(statuses[0]['content'], 'html.parser')
|
logger.error('operation error: %s', str(exc))
|
||||||
self.msg_compoment(access_token, open_id, '已删除: ' + s_text.get_text(''))
|
elif orig_text.startswith('/deploy '):
|
||||||
except Exception as exc:
|
site_ = orig_text.split('/deploy ')[1]
|
||||||
logger.error('operation error: %s', str(exc))
|
if site_ == 'dsite':
|
||||||
elif orig_text.startswith('/deploy '):
|
self.msg_compoment(access_token, open_id, '🚧 %s 开始部署 🚧' % site_)
|
||||||
site_ = orig_text.split('/deploy ')[1]
|
subprocess.call("/root/deploy/dsite_prepare.sh")
|
||||||
if site_ == 'dsite':
|
subprocess.run(["supervisorctl", "restart", "dsite"])
|
||||||
self.msg_compoment(access_token, open_id, '🚧 %s 开始部署 🚧' % site_)
|
self.msg_compoment(access_token, open_id, '🎉 %s 部署成功 🎉' % site_)
|
||||||
subprocess.call("/root/deploy/dsite_prepare.sh")
|
else:
|
||||||
subprocess.run(["supervisorctl", "restart", "dsite"])
|
self.msg_compoment(access_token, open_id, '⚠️ %s 不存在 ⚠️' % site_)
|
||||||
self.msg_compoment(access_token, open_id, '🎉 %s 部署成功 🎉' % site_)
|
|
||||||
elif site_ == 'dodo':
|
|
||||||
self.msg_compoment(access_token, open_id, '🚧 %s 开始部署 🚧' % site_)
|
|
||||||
subprocess.run(["git", "pull"])
|
|
||||||
self.msg_compoment(access_token, open_id, '🎉 %s 部署成功 🎉' % site_)
|
|
||||||
subprocess.run(["supervisorctl", "restart", "dodo"])
|
|
||||||
else:
|
|
||||||
self.msg_compoment(access_token, open_id, '⚠️ %s 不存在 ⚠️' % site_)
|
|
||||||
elif orig_text.startswith('/菜谱 '):
|
|
||||||
content = orig_text.split('/菜谱 ')[1]
|
|
||||||
recipe_ = recipe.models.Recipe.create_from_str(content)
|
|
||||||
if recipe_:
|
|
||||||
self.msg_compoment(
|
|
||||||
access_token,
|
|
||||||
open_id,
|
|
||||||
None,
|
|
||||||
const.LARK_WEBHOOK_MSG_TYPE_INTERACTIVE,
|
|
||||||
recipe_.construct_lart_card(),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.msg_compoment(access_token, open_id, '⚠️ 创建失败 ⚠️')
|
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
toot_resp = mastodon_cli.status_post(text)
|
toot_resp = mastodon.status_post(text)
|
||||||
if toot_resp.get('id'):
|
if toot_resp.get('id'):
|
||||||
self.msg_compoment(access_token, open_id, '📟 dodo 📟')
|
self.msg_compoment(access_token, open_id, '📟 dodo 📟')
|
||||||
redis_cli.set(event_id, int(time.time()), ex=60 * 60 * 7)
|
redis_cli.set(event_id, int(time.time()), ex=60*60*7)
|
||||||
else:
|
else:
|
||||||
self.msg_compoment(
|
self.msg_compoment(access_token, open_id, """⚠️ didi ⚠️
|
||||||
access_token,
|
|
||||||
open_id,
|
|
||||||
"""⚠️ didi ⚠️
|
|
||||||
%s
|
%s
|
||||||
"""
|
""" % json.loads(toot_resp))
|
||||||
% json.loads(toot_resp),
|
|
||||||
)
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error('send toot error: %s', str(exc))
|
logger.error('send toot error: %s', str(exc))
|
||||||
|
|
||||||
return
|
return
|
||||||
elif msg_type == "image":
|
elif msg_type == "image":
|
||||||
@ -274,17 +240,18 @@ class RequestHandler(BaseHTTPRequestHandler):
|
|||||||
self.end_headers()
|
self.end_headers()
|
||||||
self.wfile.write(body.encode())
|
self.wfile.write(body.encode())
|
||||||
|
|
||||||
def send_message(self, token, open_id, text, msg_type=None, content=None):
|
def send_message(self, token, open_id, text):
|
||||||
url = "https://open.feishu.cn/open-apis/message/v4/send/"
|
url = "https://open.feishu.cn/open-apis/message/v4/send/"
|
||||||
headers = {"Content-Type": "application/json", "Authorization": "Bearer " + token}
|
headers = {
|
||||||
if not msg_type:
|
"Content-Type": "application/json",
|
||||||
msg_type = const.LARK_WEBHOOK_MSG_TYPE_TEXT
|
"Authorization": "Bearer " + token
|
||||||
req_body = {"msg_type": msg_type}
|
}
|
||||||
if msg_type == const.LARK_WEBHOOK_MSG_TYPE_TEXT:
|
req_body = {
|
||||||
req_body['content'] = {'text': text}
|
"msg_type": "text",
|
||||||
elif msg_type == const.LARK_WEBHOOK_MSG_TYPE_INTERACTIVE:
|
"content": {
|
||||||
req_body['card'] = content
|
"text": text
|
||||||
|
}
|
||||||
|
}
|
||||||
req_body = dict(req_body, **open_id) # 根据open_id判断返回域
|
req_body = dict(req_body, **open_id) # 根据open_id判断返回域
|
||||||
|
|
||||||
data = bytes(json.dumps(req_body), encoding='utf8')
|
data = bytes(json.dumps(req_body), encoding='utf8')
|
||||||
@ -299,11 +266,12 @@ class RequestHandler(BaseHTTPRequestHandler):
|
|||||||
rsp_dict = json.loads(rsp_body)
|
rsp_dict = json.loads(rsp_body)
|
||||||
code = rsp_dict.get("code", -1)
|
code = rsp_dict.get("code", -1)
|
||||||
if code != 0:
|
if code != 0:
|
||||||
logger.error("send message error, code = %s, msg =%s", code, rsp_dict.get("msg", ""))
|
logger.error("send message error, code = %s, msg =%s",
|
||||||
|
code,
|
||||||
def msg_compoment(self, token, open_id, text, msg_type=None, content=None):
|
rsp_dict.get("msg", ""))
|
||||||
self.send_message(token, open_id, text, msg_type, content)
|
|
||||||
|
|
||||||
|
def msg_compoment(self, token, open_id, text):
|
||||||
|
self.send_message(token, open_id, text)
|
||||||
|
|
||||||
def run():
|
def run():
|
||||||
port = 5000
|
port = 5000
|
||||||
|
|||||||
@ -1,5 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
/root/.pyenv/versions/py37/bin/pip install -r /root/deploy/dsite/develop_requirements.txt
|
|
||||||
/root/.pyenv/versions/py37/bin/python /root/deploy/dsite/manage.py collectstatic --noinput
|
|
||||||
/root/.pyenv/versions/py37/bin/python /root/deploy/dsite/manage.py migrate
|
|
||||||
@ -1,50 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
import sys
|
|
||||||
import numpy as np
|
|
||||||
import cv2 # opencv-python
|
|
||||||
|
|
||||||
# 引入Python的可视化工具包 matplotlib
|
|
||||||
from matplotlib import pyplot as plt
|
|
||||||
|
|
||||||
|
|
||||||
def print_img_info(img):
|
|
||||||
print("================打印一下图像的属性================")
|
|
||||||
print("图像对象的类型 {}".format(type(img)))
|
|
||||||
print(img.shape)
|
|
||||||
print("图像宽度: {} pixels".format(img.shape[1]))
|
|
||||||
print("图像高度: {} pixels".format(img.shape[0]))
|
|
||||||
# GRAYScale 没有第三个维度哦, 所以这样会报错
|
|
||||||
# print("通道: {}".format(img.shape[2]))
|
|
||||||
print("图像分辨率: {}".format(img.size))
|
|
||||||
print("数据类型: {}".format(img.dtype))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# 导入一张图像 模式为彩色图片
|
|
||||||
img_path = sys.argv[1]
|
|
||||||
img = cv2.imread(img_path, cv2.IMREAD_COLOR)
|
|
||||||
|
|
||||||
# 将色彩空间转变为灰度图并展示
|
|
||||||
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
|
||||||
|
|
||||||
# 打印图片信息
|
|
||||||
# print_img_info(gray)
|
|
||||||
|
|
||||||
# 打印图片的局部
|
|
||||||
# print("打印图片局部")
|
|
||||||
# print(gray[100:105, 100:105])
|
|
||||||
|
|
||||||
# plt.imshow(gray)
|
|
||||||
# 需要添加colormap 颜色映射函数为gray
|
|
||||||
plt.imshow(gray, cmap="gray")
|
|
||||||
|
|
||||||
# 隐藏坐标系
|
|
||||||
plt.axis('off')
|
|
||||||
# 展示图片
|
|
||||||
|
|
||||||
# plt.show()
|
|
||||||
# 你也可以保存图片, 填入图片路径就好
|
|
||||||
new_name = '%s-gray.%s' % (img_path.split('.')[0], img_path.split('.')[-1])
|
|
||||||
plt.savefig(new_name)
|
|
||||||
print(new_name)
|
|
||||||
plt.close()
|
|
||||||
@ -1,85 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# scraper instagram posts
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dsite.settings")
|
|
||||||
sys.path.insert(0, '../')
|
|
||||||
sys.path.insert(0, './')
|
|
||||||
from django.core.wsgi import get_wsgi_application
|
|
||||||
|
|
||||||
get_wsgi_application()
|
|
||||||
|
|
||||||
import pickle
|
|
||||||
from urllib.parse import urlparse
|
|
||||||
import time
|
|
||||||
|
|
||||||
from mastodon import Mastodon
|
|
||||||
from instagram_private_api import Client
|
|
||||||
import requests
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from dsite import settings
|
|
||||||
|
|
||||||
logging.basicConfig(filename='/root/develop/log/ins2mastodon.log', level=logging.INFO)
|
|
||||||
logger = logging.getLogger('/root/develop/log/ins2mastodon.log')
|
|
||||||
mastodon_cli = Mastodon(access_token=settings.MASTODON_NOFAN_ACCESS_TOKEN, api_base_url='https://nofan.xyz')
|
|
||||||
|
|
||||||
|
|
||||||
def send_image_to_mastodon(image_url, text):
|
|
||||||
resp = requests.get(image_url)
|
|
||||||
mime_type = 'image/jpeg'
|
|
||||||
url = urlparse(image_url)
|
|
||||||
ext = url.path.split('.')[-1]
|
|
||||||
if ext == 'gif':
|
|
||||||
mime_type = 'image/gif'
|
|
||||||
toot_resp = mastodon_cli.media_post(resp.content, mime_type)
|
|
||||||
if toot_resp.get('id'):
|
|
||||||
media_ids = [toot_resp['id']]
|
|
||||||
mastodon_cli.status_post(text, media_ids=media_ids)
|
|
||||||
logger.info('send %s', text)
|
|
||||||
image_name = url.path.split('/')[-1]
|
|
||||||
with open(settings.MASTODON_SYNCED_IMAGES_LOG, 'a') as f:
|
|
||||||
f.write(image_name + '\n')
|
|
||||||
|
|
||||||
|
|
||||||
# write binary file with api.settings
|
|
||||||
def writeSettings(user, pwd, settings_file):
|
|
||||||
api = Client(user, pwd)
|
|
||||||
with open(settings_file, "wb") as FileObj:
|
|
||||||
pickle.dump(api.settings, FileObj)
|
|
||||||
|
|
||||||
|
|
||||||
# read binary file to api.settings
|
|
||||||
def readSettings(settings_file):
|
|
||||||
cache = None
|
|
||||||
with open(settings_file, "rb") as FileObj:
|
|
||||||
cache = pickle.load(FileObj)
|
|
||||||
return cache
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
|
|
||||||
if not os.path.exists(settings.IG_PRIVATE_API_SETTINGS):
|
|
||||||
writeSettings(settings.IG_LOGIN_USERNAME, settings.IG_LOGIN_PASSWORD, settings.IG_PRIVATE_API_SETTINGS)
|
|
||||||
|
|
||||||
cache_settings = readSettings(settings.IG_PRIVATE_API_SETTINGS)
|
|
||||||
api = Client(settings.IG_LOGIN_USERNAME, settings.IG_LOGIN_PASSWORD, settings=cache_settings)
|
|
||||||
|
|
||||||
while True:
|
|
||||||
results = api.self_feed()
|
|
||||||
logger.info('getting %s posts', len(results['items']))
|
|
||||||
for item in results['items']:
|
|
||||||
text = item['caption']['text']
|
|
||||||
image_url = item['image_versions2']['candidates'][0]['url']
|
|
||||||
try:
|
|
||||||
with open(settings.MASTODON_SYNCED_IMAGES_LOG, 'r') as f:
|
|
||||||
send_images = f.readlines()
|
|
||||||
send_images = [x.strip() for x in send_images]
|
|
||||||
image_name = urlparse(image_url).path.split('/')[-1]
|
|
||||||
if image_name not in send_images:
|
|
||||||
send_image_to_mastodon(image_url, text)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(e)
|
|
||||||
time.sleep(60)
|
|
||||||
@ -1,42 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# scraper instagram posts
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dsite.settings")
|
|
||||||
sys.path.insert(0, '../')
|
|
||||||
sys.path.insert(0, './')
|
|
||||||
from django.core.wsgi import get_wsgi_application
|
|
||||||
|
|
||||||
get_wsgi_application()
|
|
||||||
|
|
||||||
import instagram_scraper
|
|
||||||
import time
|
|
||||||
from dsite import settings
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
args = {
|
|
||||||
'login_user': settings.IG_LOGIN_USERNAME,
|
|
||||||
'login_pass': settings.IG_LOGIN_PASSWORD,
|
|
||||||
'cookiejar': settings.IG_SCRAPER_COOKIE_FILE,
|
|
||||||
'latest_stamps': settings.IG_SCRAPER_LAST_STAMP_FILE,
|
|
||||||
'usernames': settings.IG_SCRAPER_USERNAMES,
|
|
||||||
'destination': settings.IG_SCRAPER_DESTINATION,
|
|
||||||
}
|
|
||||||
|
|
||||||
scraper = instagram_scraper.InstagramScraper(**args)
|
|
||||||
scraper.authenticate_with_login()
|
|
||||||
scraper.save_cookies()
|
|
||||||
while True:
|
|
||||||
time.sleep(10)
|
|
||||||
args_ = {
|
|
||||||
'cookiejar': settings.IG_SCRAPER_COOKIE_FILE,
|
|
||||||
'latest_stamps': settings.IG_SCRAPER_LAST_STAMP_FILE,
|
|
||||||
'usernames': settings.IG_SCRAPER_USERNAMES,
|
|
||||||
'destination': settings.IG_SCRAPER_DESTINATION,
|
|
||||||
'template': '{username}-{datetime}-{shortcode}',
|
|
||||||
}
|
|
||||||
scraper = instagram_scraper.InstagramScraper(**args_)
|
|
||||||
scraper.scrape()
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
supervisorctl restart dodo && \
|
|
||||||
curl -X POST -H "Content-Type: application/json" \
|
|
||||||
-d '{"msg_type":"text","content":{"text":"🎉 dodo 部署成功 🎉"}}' \
|
|
||||||
https://open.feishu.cn/open-apis/bot/v2/hook/57cfa603-6154-4055-a739-210028171d10
|
|
||||||
@ -4,23 +4,27 @@ import timer.models
|
|||||||
|
|
||||||
|
|
||||||
class OfficeHoursForm(forms.ModelForm):
|
class OfficeHoursForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = timer.models.OfficeHours
|
model = timer.models.OfficeHours
|
||||||
fields = ['user', 'begins_at', 'ends_at']
|
fields = ['user', 'begins_at', 'ends_at']
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.user = kwargs.pop('user')
|
self.user = kwargs.pop('user')
|
||||||
return super().__init__(*args, **kwargs)
|
return super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def full_clean(self):
|
||||||
|
if not self.user.is_authenticated:
|
||||||
|
raise forms.ValidationError('Invalid User.')
|
||||||
|
return
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
begins_at = timer.models.OfficeHours.parse_time_str(self.data.get('begins_at'))
|
||||||
|
user = self.user
|
||||||
|
obj = timer.models.OfficeHours.objects.create(
|
||||||
|
user=user,
|
||||||
|
begins_at=begins_at,
|
||||||
|
ends_at=timer.models.OfficeHours.get_ends_at(begins_at))
|
||||||
|
return obj
|
||||||
|
|
||||||
def full_clean(self):
|
|
||||||
if not self.user.is_authenticated:
|
|
||||||
raise forms.ValidationError('Invalid User.')
|
|
||||||
return
|
|
||||||
|
|
||||||
def save(self):
|
|
||||||
begins_at = timer.models.OfficeHours.parse_time_str(self.data.get('begins_at'))
|
|
||||||
user = self.user
|
|
||||||
obj = timer.models.OfficeHours.objects.create(
|
|
||||||
user=user, begins_at=begins_at, ends_at=timer.models.OfficeHours.get_ends_at(begins_at)
|
|
||||||
)
|
|
||||||
return obj
|
|
||||||
|
|||||||
@ -9,7 +9,9 @@ class Migration(migrations.Migration):
|
|||||||
|
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)]
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
@ -20,5 +22,5 @@ class Migration(migrations.Migration):
|
|||||||
('ends_at', models.DateTimeField()),
|
('ends_at', models.DateTimeField()),
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
],
|
],
|
||||||
)
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -6,22 +6,22 @@ import datetime
|
|||||||
|
|
||||||
|
|
||||||
class OfficeHours(models.Model):
|
class OfficeHours(models.Model):
|
||||||
begins_at = models.DateTimeField()
|
begins_at = models.DateTimeField()
|
||||||
ends_at = models.DateTimeField()
|
ends_at = models.DateTimeField()
|
||||||
user = models.ForeignKey(User, on_delete=django.db.models.deletion.CASCADE)
|
user = models.ForeignKey(User, on_delete=django.db.models.deletion.CASCADE)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse_time_str(cls, time_srt):
|
def parse_time_str(cls, time_srt):
|
||||||
return datetime.datetime.strptime(time_srt, '%Y-%m-%d %H:%M')
|
return datetime.datetime.strptime(time_srt, '%Y-%m-%d %H:%M')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_ends_at(cls, begins_at):
|
def get_ends_at(cls, begins_at):
|
||||||
return begins_at + datetime.timedelta(hours=9.5)
|
return begins_at + datetime.timedelta(hours=9.5)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def get_begins_at_str(self):
|
def get_begins_at_str(self):
|
||||||
return localtime(self.begins_at).strftime('%Y-%m-%d %H:%M')
|
return localtime(self.begins_at).strftime('%Y-%m-%d %H:%M')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def get_ends_at_str(self):
|
def get_ends_at_str(self):
|
||||||
return localtime(self.ends_at).strftime('%H:%M')
|
return localtime(self.ends_at).strftime('%H:%M')
|
||||||
|
|||||||
@ -5,17 +5,17 @@ import timer.models
|
|||||||
|
|
||||||
|
|
||||||
class OfficeHoursSerializer(serializers.HyperlinkedModelSerializer):
|
class OfficeHoursSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = timer.models.OfficeHours
|
model = timer.models.OfficeHours
|
||||||
|
|
||||||
|
|
||||||
class UserSerializer(serializers.HyperlinkedModelSerializer):
|
class UserSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = ('url', 'username', 'email', 'groups')
|
fields = ('url', 'username', 'email', 'groups')
|
||||||
|
|
||||||
|
|
||||||
class GroupSerializer(serializers.HyperlinkedModelSerializer):
|
class GroupSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Group
|
model = Group
|
||||||
fields = ('url', 'name')
|
fields = ('url', 'name')
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
from django.conf.urls import include, url
|
from django.conf.urls import include, url
|
||||||
|
|
||||||
# from django.core.urlresolvers import reverse
|
# from django.core.urlresolvers import reverse
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from rest_framework import routers
|
from rest_framework import routers
|
||||||
|
|||||||
@ -19,57 +19,59 @@ from django.urls import reverse
|
|||||||
|
|
||||||
class OfficeHoursAPI(CreateAPIView):
|
class OfficeHoursAPI(CreateAPIView):
|
||||||
|
|
||||||
authentication_classes = (
|
authentication_classes = (authentication.TokenAuthentication,
|
||||||
authentication.TokenAuthentication,
|
authentication.SessionAuthentication,
|
||||||
authentication.SessionAuthentication,
|
authentication.BasicAuthentication)
|
||||||
authentication.BasicAuthentication,
|
permission_classes = (permissions.IsAuthenticated,)
|
||||||
)
|
|
||||||
permission_classes = (permissions.IsAuthenticated,)
|
|
||||||
|
|
||||||
queryset = timer.models.OfficeHours.objects.all()
|
queryset = timer.models.OfficeHours.objects.all()
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
begins_at = request.data.get('begins_at')
|
begins_at = request.data.get('begins_at')
|
||||||
if not begins_at:
|
if not begins_at:
|
||||||
raise
|
raise
|
||||||
try:
|
try:
|
||||||
begins_at = timer.models.OfficeHours.parse_time_str(begins_at)
|
begins_at = timer.models.OfficeHours.parse_time_str(begins_at)
|
||||||
ends_at = timer.models.OfficeHours.get_ends_at(begins_at)
|
ends_at = timer.models.OfficeHours.get_ends_at(begins_at)
|
||||||
oh, __ = timer.models.OfficeHours.objects.get_or_create(
|
oh, __ = timer.models.OfficeHours.objects.get_or_create(
|
||||||
begins_at=begins_at, ends_at=ends_at, user=request.user
|
begins_at=begins_at,
|
||||||
)
|
ends_at=ends_at,
|
||||||
except ValueError:
|
user=request.user)
|
||||||
raise
|
except ValueError:
|
||||||
oh.refresh_from_db()
|
raise
|
||||||
resp_data = {'begins_at': oh.get_begins_at_str, 'ends_at': oh.get_ends_at_str}
|
oh.refresh_from_db()
|
||||||
|
resp_data = {
|
||||||
|
'begins_at': oh.get_begins_at_str,
|
||||||
|
'ends_at': oh.get_ends_at_str,
|
||||||
|
}
|
||||||
|
|
||||||
return Response(resp_data, status=status.HTTP_201_CREATED)
|
return Response(resp_data, status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
|
||||||
class OfficeHoursFormView(django.views.generic.FormView):
|
class OfficeHoursFormView(django.views.generic.FormView):
|
||||||
template_name = 'index.html'
|
template_name = 'index.html'
|
||||||
form_class = timer.forms.OfficeHoursForm
|
form_class = timer.forms.OfficeHoursForm
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
def get_form_kwargs(self):
|
||||||
kwargs = super().get_form_kwargs()
|
kwargs = super().get_form_kwargs()
|
||||||
kwargs['user'] = self.request.user
|
kwargs['user'] = self.request.user
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
form = self.form_class(**self.get_form_kwargs())
|
form = self.form_class(**self.get_form_kwargs())
|
||||||
try:
|
try:
|
||||||
form.is_valid()
|
form.is_valid()
|
||||||
form.save()
|
form.save()
|
||||||
except django.forms.ValidationError as e:
|
except django.forms.ValidationError as e:
|
||||||
return JsonResponse({'error': e.message}, status=status.HTTP_400_BAD_REQUEST)
|
return JsonResponse({'error': e.message}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
return super().post(request, *args, **kwargs)
|
return super().post(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return reverse('office-hours-page')
|
return reverse('office-hours-page')
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
today_oh = timer.models.OfficeHours.objects.filter(begins_at__date=localtime().date()).last()
|
today_oh = timer.models.OfficeHours.objects.filter(begins_at__date=localtime().date()).last()
|
||||||
context['today_oh'] = today_oh
|
context['today_oh'] = today_oh
|
||||||
return context
|
return context
|
||||||
|
|||||||
@ -6,113 +6,113 @@ from django.utils import timezone
|
|||||||
|
|
||||||
|
|
||||||
def timestamp_of(d):
|
def timestamp_of(d):
|
||||||
if hasattr(d, 'isoformat'):
|
if hasattr(d, 'isoformat'):
|
||||||
return calendar.timegm(d.utctimetuple())
|
return calendar.timegm(d.utctimetuple())
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def now():
|
def now():
|
||||||
return timezone.localtime(timezone.now())
|
return timezone.localtime(timezone.now())
|
||||||
|
|
||||||
|
|
||||||
def midnight():
|
def midnight():
|
||||||
return now().replace(hour=0, minute=0, second=0, microsecond=0)
|
return now().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
|
||||||
|
|
||||||
def is_today(tme):
|
def is_today(tme):
|
||||||
if day_of(tme) != day_of(midnight()):
|
if day_of(tme) != day_of(midnight()):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def current_time_n(n):
|
def current_time_n(n):
|
||||||
return now() - timedelta(days=n)
|
return now() - timedelta(days=n)
|
||||||
|
|
||||||
|
|
||||||
def day_n(n):
|
def day_n(n):
|
||||||
"""Midnight of N days ago."""
|
"""Midnight of N days ago."""
|
||||||
return midnight() - timedelta(days=n)
|
return midnight() - timedelta(days=n)
|
||||||
|
|
||||||
|
|
||||||
def day_of(d):
|
def day_of(d):
|
||||||
"""Returns date part."""
|
"""Returns date part."""
|
||||||
return str(d.date())
|
return str(d.date())
|
||||||
|
|
||||||
|
|
||||||
def midnight_of(d):
|
def midnight_of(d):
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
d : datetime object
|
d : datetime object
|
||||||
Return:
|
Return:
|
||||||
Midnight of datetime.
|
Midnight of datetime.
|
||||||
"""
|
"""
|
||||||
d = timezone.localtime(d)
|
d = timezone.localtime(d)
|
||||||
return d.replace(hour=0, minute=0, second=0, microsecond=0)
|
return d.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
|
||||||
|
|
||||||
def day_start(date):
|
def day_start(date):
|
||||||
"""返回 date 这天的开始时间
|
""" 返回 date 这天的开始时间
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
date: `Datetime` 对象
|
date: `Datetime` 对象
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
返回 date 这天的开始时间(0 点),`Datetime` 对象
|
返回 date 这天的开始时间(0 点),`Datetime` 对象
|
||||||
"""
|
"""
|
||||||
return timezone.localtime(date).replace(hour=0, minute=0, second=0, microsecond=0)
|
return timezone.localtime(date).replace(hour=0, minute=0, second=0,
|
||||||
|
microsecond=0)
|
||||||
|
|
||||||
|
|
||||||
def week_start(date):
|
def week_start(date):
|
||||||
"""返回 date 这天所在周的开始时间
|
""" 返回 date 这天所在周的开始时间
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
date: `Datetime` 对象
|
date: `Datetime` 对象
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
返回 date 这天所在周开始时间(周一),`Datetime` 对象
|
返回 date 这天所在周开始时间(周一),`Datetime` 对象
|
||||||
"""
|
"""
|
||||||
return timezone.localtime(date) + dateutil.relativedelta.relativedelta(
|
return timezone.localtime(date) + dateutil.relativedelta.relativedelta(
|
||||||
weekday=dateutil.relativedelta.MO(-1), hour=0, minute=0, second=0, microsecond=0
|
weekday=dateutil.relativedelta.MO(-1), hour=0, minute=0, second=0,
|
||||||
)
|
microsecond=0)
|
||||||
|
|
||||||
|
|
||||||
def month_start(d):
|
def month_start(d):
|
||||||
"""返回 date 这天所在月的开始时间
|
""" 返回 date 这天所在月的开始时间
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
date: `Datetime` 对象
|
date: `Datetime` 对象
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
返回 date 这天所在月的开始时间(1号),`Datetime` 对象
|
返回 date 这天所在月的开始时间(1号),`Datetime` 对象
|
||||||
"""
|
"""
|
||||||
return timezone.localtime(d) + dateutil.relativedelta.relativedelta(
|
return timezone.localtime(d) + dateutil.relativedelta.relativedelta(
|
||||||
day=1, hour=0, minute=0, second=0, microsecond=0
|
day=1, hour=0, minute=0, second=0, microsecond=0)
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def month_end(d):
|
def month_end(d):
|
||||||
"""返回 date 这天所在月的结束时间,即最后一天的 23:59:59
|
"""返回 date 这天所在月的结束时间,即最后一天的 23:59:59
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
data: `Datetime` 对象
|
data: `Datetime` 对象
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
返回 date 这天所在月的最后一天的 23:59:59 的时间
|
返回 date 这天所在月的最后一天的 23:59:59 的时间
|
||||||
"""
|
"""
|
||||||
start = month_start(d)
|
start = month_start(d)
|
||||||
month_days = calendar.monthrange(start.year, start.month)[1]
|
month_days = calendar.monthrange(start.year, start.month)[1]
|
||||||
return start + timedelta(days=month_days, seconds=-1)
|
return start + timedelta(days=month_days, seconds=-1)
|
||||||
|
|
||||||
|
|
||||||
def year_start(d):
|
def year_start(d):
|
||||||
"""返回 date 这天所在年的开始时间
|
""" 返回 date 这天所在年的开始时间
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
date: `Datetime` 对象
|
date: `Datetime` 对象
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
返回 date 这天所在年的开始时间(1月 1号),`Datetime` 对象
|
||||||
|
"""
|
||||||
|
return timezone.localtime(d) + dateutil.relativedelta.relativedelta(
|
||||||
|
month=1, day=1, hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
|
||||||
Returns:
|
|
||||||
返回 date 这天所在年的开始时间(1月 1号),`Datetime` 对象
|
|
||||||
"""
|
|
||||||
return timezone.localtime(d) + dateutil.relativedelta.relativedelta(
|
|
||||||
month=1, day=1, hour=0, minute=0, second=0, microsecond=0
|
|
||||||
)
|
|
||||||
|
|||||||
@ -2,23 +2,23 @@ DAILY_RECIPE_MEAL_TYPE_BREAKFAST = 'breakfast'
|
|||||||
DAILY_RECIPE_MEAL_TYPE_LUNCH = 'lunch'
|
DAILY_RECIPE_MEAL_TYPE_LUNCH = 'lunch'
|
||||||
DAILY_RECIPE_MEAL_TYPE_SUPPER = 'supper'
|
DAILY_RECIPE_MEAL_TYPE_SUPPER = 'supper'
|
||||||
DAILY_RECIPE_MEAL_TYPE_CHOICE = [
|
DAILY_RECIPE_MEAL_TYPE_CHOICE = [
|
||||||
DAILY_RECIPE_MEAL_TYPE_BREAKFAST,
|
DAILY_RECIPE_MEAL_TYPE_BREAKFAST,
|
||||||
DAILY_RECIPE_MEAL_TYPE_LUNCH,
|
DAILY_RECIPE_MEAL_TYPE_LUNCH,
|
||||||
DAILY_RECIPE_MEAL_TYPE_SUPPER,
|
DAILY_RECIPE_MEAL_TYPE_SUPPER,
|
||||||
]
|
]
|
||||||
|
|
||||||
RECIPE_TYPE_MEAT = 'meat'
|
RECIPE_TYPE_MEAT = 'meat'
|
||||||
RECIPE_TYPE_VEGETABLE = 'vegetable'
|
RECIPE_TYPE_VEGETABLE = 'vegetable'
|
||||||
RECIPE_TYPE_SOUP = 'soup'
|
RECIPE_TYPE_SOUP = 'soup'
|
||||||
|
|
||||||
RECIPE_STATUS_ACTIVE = 'active'
|
RECIPE_TYPE_CHOICE = [
|
||||||
RECIPE_STATUS_DELETED = 'deleted'
|
RECIPE_TYPE_MEAT,
|
||||||
|
RECIPE_TYPE_VEGETABLE,
|
||||||
RECIPE_TYPE_CHOICE = [RECIPE_TYPE_MEAT, RECIPE_TYPE_VEGETABLE, RECIPE_TYPE_SOUP]
|
RECIPE_TYPE_SOUP,
|
||||||
|
]
|
||||||
|
|
||||||
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'
|
LARK_WEBHOOK_MSG_TYPE_TEXT = 'text'
|
||||||
LARK_WEBHOOK_MSG_TYPE_INTERACTIVE = 'interactive'
|
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
from imp import reload
|
from imp import reload
|
||||||
|
|
||||||
sys.path.insert(0, os.path.abspath('..'))
|
sys.path.insert(0, os.path.abspath('..'))
|
||||||
sys.path.append('/Users/ching/develop/dsite')
|
sys.path.append('/Users/ching/develop/dsite')
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "../dsite.settings")
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "../dsite.settings")
|
||||||
@ -13,9 +12,14 @@ import git
|
|||||||
import ipdb
|
import ipdb
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# def notify():
|
# def notify():
|
||||||
repo = git.Repo(search_parent_directories=True)
|
repo = git.Repo(search_parent_directories=True)
|
||||||
commit = repo.head.commit
|
commit = repo.head.commit
|
||||||
rev, branch = commit.name_rev.split(' ')
|
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)
|
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}))
|
print(utils.lark.request({'text': msg}))
|
||||||
|
|||||||
@ -13,26 +13,25 @@ from django.utils.timezone import now
|
|||||||
|
|
||||||
|
|
||||||
def gen_sign(timestamp, secret):
|
def gen_sign(timestamp, secret):
|
||||||
# 拼接timestamp和secret
|
# 拼接timestamp和secret
|
||||||
string_to_sign = '{}\n{}'.format(timestamp, secret)
|
string_to_sign = '{}\n{}'.format(timestamp, secret)
|
||||||
hmac_code = hmac.new(string_to_sign.encode("utf-8"), digestmod=hashlib.sha256).digest()
|
hmac_code = hmac.new(string_to_sign.encode("utf-8"), digestmod=hashlib.sha256).digest()
|
||||||
|
|
||||||
# 对结果进行base64处理
|
# 对结果进行base64处理
|
||||||
sign = base64.b64encode(hmac_code).decode('utf-8')
|
sign = base64.b64encode(hmac_code).decode('utf-8')
|
||||||
|
|
||||||
return sign
|
|
||||||
|
|
||||||
|
return sign
|
||||||
|
|
||||||
def request(content, msg_type=const.LARK_WEBHOOK_MSG_TYPE_TEXT):
|
def request(content, msg_type=const.LARK_WEBHOOK_MSG_TYPE_TEXT):
|
||||||
"""content: {'text': 'xxxxx}"""
|
""" content: {'text': 'xxxxx}
|
||||||
timestamp = utils.timestamp_of(now())
|
"""
|
||||||
data = {
|
timestamp = utils.timestamp_of(now())
|
||||||
"timestamp": timestamp,
|
data = {
|
||||||
"sign": gen_sign(timestamp, settings.LARK_WEBHOOK_SECRET),
|
"timestamp": timestamp,
|
||||||
"msg_type": msg_type,
|
"sign": gen_sign(timestamp, settings.LARK_WEBHOOK_SECRET),
|
||||||
"content": content,
|
"msg_type": msg_type,
|
||||||
}
|
"content": content}
|
||||||
resp = requests.post(settings.LARK_WEBHOOK_URL, data=json.dumps(data))
|
resp = requests.post(settings.LARK_WEBHOOK_URL, data=json.dumps(data))
|
||||||
if resp.status_code == 200 and resp.json().get('StatusCode') == 0:
|
if resp.status_code == 200 and resp.json().get('StatusCode') == 0:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user