This commit is contained in:
mguschin
2026-02-02 19:52:58 +03:00
parent f74a5041f8
commit 053fa62395
26 changed files with 866 additions and 0 deletions

15
.gitignore vendored Normal file
View File

@@ -0,0 +1,15 @@
*.*~$
^#*
^.#*
*~$
api.credentials*
evo/products/*
evo/groups/*
evo/stores/*
vk/categories/*
vk/albums/*
vk/products/*
run/test.log
vk/whitelist
logs/
passwords.txt

BIN
5393364294319597854.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

20
CHANGELOG.md Normal file
View File

@@ -0,0 +1,20 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.6.0] - 2025-06-15
### Added
- Initial changelog implementation
- Version tracking system
### Changed
- Minor version bump from 1.5.2 to 1.6.0
## [1.5.2] - Previous Release
### Notes
- Historical version before changelog implementation

23
Dockerfile Normal file
View File

@@ -0,0 +1,23 @@
FROM alpine:latest
RUN apk add --update \
curl \
git \
openrc \
bash \
jq \
yq
RUN mkdir -p /var/www/
RUN git config --system --add safe.directory '*'
COPY ./cronjobs /etc/cron.d/cronjobs
RUN chmod 0644 /etc/cron.d/cronjobs
RUN /usr/bin/crontab /etc/cron.d/cronjobs
WORKDIR /var/www/
COPY ./ /var/www/
EXPOSE 80
CMD ["/usr/sbin/crond", "-f", "-l", "8"]

View File

@@ -1,2 +1,3 @@
# evo-sync
evo-sync is a command-line synchronization tool that fetches product, group, and store data from the Evo platform and syncs it with VK (VKontakte).

1
cronjobs Normal file
View File

@@ -0,0 +1 @@
0 * * * * /var/www/run/run.sh

View File

@@ -0,0 +1,27 @@
{
"uuid": "0291602f-8de3-4df6-90d3-78917b51b6b5",
"group": true,
"hasVariants": null,
"type": null,
"name": "Чай с добавками",
"code": null,
"barCodes": null,
"price": null,
"costPrice": null,
"quantity": null,
"measureName": null,
"tax": null,
"allowToSell": null,
"description": null,
"articleNumber": null,
"parentUuid": null,
"alcoCodes": null,
"alcoholByVolume": null,
"alcoholProductKindCode": null,
"tareVolume": null,
"classificationCode": null,
"allowPartialSell": null,
"quantityInPackage": null,
"isExcisable": null,
"isAgeLimited": null
}

34
run/evo/clear.sh Executable file
View File

@@ -0,0 +1,34 @@
#!/usr/bin/env bash
# This script cleans all EVO-related directories
# It removes all files from products, groups and stores directories
# Get the directory where this script is located
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
# Load constants and functions
source $SCRIPT_DIR/constants.sh
source $SCRIPT_DIR/functions.sh
# Initialize logging system
setup_logging
# Ensure we clean up properly even if script is interrupted
trap 'cleanup 1' HUP INT QUIT TERM
# Begin cleanup process
echo "$(timestamp) [INFO] Starting EVO cleanup process" >> "$LOG_FILE"
# Clean EVO content directories
echo "$(timestamp) [INFO] Cleaning EVO directories" >> "$LOG_FILE"
# Remove product files
echo "$(timestamp) [INFO] Removing product files" >> "$LOG_FILE"
echo "rm -f $EVO_PRODUCTS_PATH/*" && rm -rf $EVO_PRODUCTS_PATH/*
# Remove group files
echo "$(timestamp) [INFO] Removing group files" >> "$LOG_FILE"
echo "rm -f $EVO_GROUPS_PATH/*" && rm -rf $EVO_GROUPS_PATH/*
# Remove store files
echo "$(timestamp) [INFO] Removing store files" >> "$LOG_FILE"
echo "rm -rf $EVO_STORES_PATH/*" && rm -rf $EVO_STORES_PATH/*
echo "$(timestamp) [INFO] EVO cleanup completed successfully" >> "$LOG_FILE"
cleanup 0

21
run/evo/constants.sh Executable file
View File

@@ -0,0 +1,21 @@
#!/usr/bin/env bash
ROOT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
ROOT_DIR+='/../..'
EVO_STORE_PERIOD_HOURS=2
EVO_STORE_PERIOD_MINUTES=$((EVO_STORE_PERIOD_HOURS * 60))
EVO_PRODUCTS_PATH="$ROOT_DIR/evo/products"
EVO_GROUPS_PATH="$ROOT_DIR/evo/groups"
EVO_STORES_PATH="$ROOT_DIR/evo/stores"
EVO_EXAMPLE_PATH="$ROOT_DIR/evo/examples"
EVO_RESPONSE_FILE_NAME_FORMAT="%(%Y-%m-%d_%H:%M:%S)T"
EVO_RESPONSE_FILE_NAME_EXT="json"
EVO_API_HOST="https://api.evotor.ru"
EVO_API_TOKEN=`cat "$ROOT_DIR/evo/api.credentials.token"`
EVO_API_STORE_ID=`cat "$ROOT_DIR/evo/api.credentials.store_id"`
EVO_API_ACCEPT=`cat "$ROOT_DIR/evo/api.credentials.accept"`
EVO_API_CONTENT_TYPE=`cat "$ROOT_DIR/evo/api.credentials.content_type"`
EVO_API_GET_PRODUCTS="$EVO_API_HOST/stores/$EVO_API_STORE_ID/products"
EVO_API_GET_GROUPS="$EVO_API_HOST/stores/$EVO_API_STORE_ID/product-groups"
EVO_API_GET_STORES="$EVO_API_HOST/stores"

45
run/evo/functions.sh Executable file
View File

@@ -0,0 +1,45 @@
#!/usr/bin/env bash
# Setup timestamp function
timestamp() {
date "+%Y-%m-%d %H:%M:%S"
}
# Setup process cleanup
cleanup() {
# Kill all child processes of this script
pkill -P $$
exit $1
}
# Function to handle API requests and save response
handle_request() {
local request_type=$1
local api_endpoint=$2
local path=$3
local fileName=$4
echo "$(timestamp) [REQUEST] Getting $request_type" >> "$LOG_FILE"
response=$(curl -s -w "%{http_code}" -H "Accept: $EVO_API_ACCEPT" \
-H "Content-Type: $EVO_API_CONTENT_TYPE" \
-H "Authorization: $EVO_API_TOKEN" \
-X GET \
$api_endpoint)
http_code=${response: -3}
response_body=${response:0:-3}
if [ "$http_code" = "200" ]; then
touch "$path/$fileName"
echo "$response_body" > "$path/$fileName"
fi
echo "$(timestamp) [RESPONSE] code=$http_code" >> "$LOG_FILE"
# Delete old files (files older than configured period)
find $path ! -type d -mmin +$EVO_STORE_PERIOD_MINUTES -delete
}
# Setup logging function
setup_logging() {
LOG_DIR="$ROOT_DIR/logs"
mkdir -p "$LOG_DIR"
LOG_FILE="$LOG_DIR/$(date +%Y%m%d).log"
}

21
run/evo/get-groups.sh Executable file
View File

@@ -0,0 +1,21 @@
#!/usr/bin/env bash
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source $SCRIPT_DIR/constants.sh
source $SCRIPT_DIR/functions.sh
# Setup logging
setup_logging
# Trap signals to ensure proper cleanup
trap 'cleanup 1' HUP INT QUIT TERM
path=$EVO_GROUPS_PATH/$EVO_API_STORE_ID
printf -v fileName "$EVO_RESPONSE_FILE_NAME_FORMAT.$EVO_RESPONSE_FILE_NAME_EXT" -1
mkdir -p $path
# Handle request for groups
handle_request "groups" "$EVO_API_GET_GROUPS" "$path" "$fileName"
# Use the cleanup function instead of directly exiting
cleanup 0

21
run/evo/get-products.sh Executable file
View File

@@ -0,0 +1,21 @@
#!/usr/bin/env bash
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source $SCRIPT_DIR/constants.sh
source $SCRIPT_DIR/functions.sh
# Setup logging
setup_logging
# Trap signals to ensure proper cleanup
trap 'cleanup 1' HUP INT QUIT TERM
path=$EVO_PRODUCTS_PATH/$EVO_API_STORE_ID
printf -v fileName "$EVO_RESPONSE_FILE_NAME_FORMAT.$EVO_RESPONSE_FILE_NAME_EXT" -1
mkdir -p $path
# Handle request for products
handle_request "products" "$EVO_API_GET_PRODUCTS" "$path" "$fileName"
# Use the cleanup function instead of directly exiting
cleanup 0

21
run/evo/get-stores.sh Executable file
View File

@@ -0,0 +1,21 @@
#!/usr/bin/env bash
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source $SCRIPT_DIR/constants.sh
source $SCRIPT_DIR/functions.sh
# Setup logging
setup_logging
# Trap signals to ensure proper cleanup
trap 'cleanup 1' HUP INT QUIT TERM
path=$EVO_STORES_PATH/$EVO_API_STORE_ID
printf -v fileName "$EVO_RESPONSE_FILE_NAME_FORMAT.$EVO_RESPONSE_FILE_NAME_EXT" -1
mkdir -p $path
# Handle request for stores
handle_request "stores" "$EVO_API_GET_STORES" "$path" "$fileName"
# Use the cleanup function instead of directly exiting
cleanup 0

13
run/run.sh Executable file
View File

@@ -0,0 +1,13 @@
#!/usr/bin/env bash
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
$SCRIPT_DIR/evo/get-groups.sh
$SCRIPT_DIR/evo/get-products.sh
$SCRIPT_DIR/vk/get-albums.sh
$SCRIPT_DIR/vk/send-albums.sh
$SCRIPT_DIR/vk/get-albums.sh
$SCRIPT_DIR/vk/get-products.sh
$SCRIPT_DIR/vk/send-products.sh
$SCRIPT_DIR/vk/get-products.sh
$SCRIPT_DIR/vk/delete-products.sh

5
run/test.sh Executable file
View File

@@ -0,0 +1,5 @@
#!/usr/bin/env bash
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
echo "Test [OK]." >> $SCRIPT_DIR/test.res

37
run/vk/clear.sh Executable file
View File

@@ -0,0 +1,37 @@
#!/usr/bin/env bash
# This script cleans all VK-related directories and logs
# It removes all files from categories, albums, products and logs directories
# Get the directory where this script is located
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
# Load constants and functions
source $SCRIPT_DIR/constants.sh
source $SCRIPT_DIR/functions.sh
# Initialize logging system
setup_logging
# Ensure we clean up properly even if script is interrupted
trap 'cleanup 1' HUP INT QUIT TERM
# Begin cleanup process
echo "$(timestamp) [INFO] Starting cleanup process" >> "$LOG_FILE"
# Clean VK content directories
echo "$(timestamp) [INFO] Cleaning VK directories" >> "$LOG_FILE"
# Remove category files
echo "$(timestamp) [INFO] Removing category files" >> "$LOG_FILE"
echo "rm -f $VK_CATEGORIES_PATH/*" && rm -rf $VK_CATEGORIES_PATH/*
# Remove album files
echo "$(timestamp) [INFO] Removing album files" >> "$LOG_FILE"
echo "rm -f $VK_ALBUMS_PATH/*" && rm -rf $VK_ALBUMS_PATH/*
# Remove product files
echo "$(timestamp) [INFO] Removing product files" >> "$LOG_FILE"
echo "rm -f $VK_PRODUCTS_PATH/*" && rm -rf $VK_PRODUCTS_PATH/*
# Clean logs directory
echo "$(timestamp) [INFO] Cleaning logs directory" >> "$LOG_FILE"
echo "rm -f $LOG_DIR/*" && rm -rf $LOG_DIR/*
echo "$(timestamp) [INFO] Cleanup completed successfully" >> "$LOG_FILE"
cleanup 0

30
run/vk/constants.sh Executable file
View File

@@ -0,0 +1,30 @@
#!/usr/bin/env bash
ROOT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
ROOT_DIR+='/../..'
VK_STORE_PERIOD_MINUTES=120
VK_API_CONTENT_TYPE_MULTIPART="multipart/form-data"
VK_API_HOST="https://api.vk.ru/method"
VK_API_USER_TOKEN=`cat "$ROOT_DIR/vk/api.credentials.user_token"`
VK_API_VERSION="5.199"
VK_API_ADD_PRODUCT="$VK_API_HOST/market.add?v=$VK_API_VERSION"
VK_API_EDIT_PRODUCT="$VK_API_HOST/market.edit?v=$VK_API_VERSION"
VK_API_ADD_PRODUCT_TO_ALBUM="$VK_API_HOST/market.addToAlbum?v=$VK_API_VERSION"
VK_API_ADD_ALBUM="$VK_API_HOST/market.addAlbum?v=$VK_API_VERSION"
VK_API_GET_CATEGORIES="$VK_API_HOST/market.getCategories?v=$VK_API_VERSION"
VK_API_GET_ALBUMS="$VK_API_HOST/market.getAlbums?v=$VK_API_VERSION"
VK_API_GET_PRODUCTS="$VK_API_HOST/market.get?v=$VK_API_VERSION"
VK_API_GROUP_ID=`cat "$ROOT_DIR/vk/api.credentials.group_id"`
VK_API_PARAM_OWNER_ID="-$VK_API_GROUP_ID"
VK_API_DELETE_PRODUCT="$VK_API_HOST/market.delete?v=$VK_API_VERSION"
VK_CATEGORIES_PATH="$ROOT_DIR/vk/categories"
VK_ALBUMS_PATH="$ROOT_DIR/vk/albums"
VK_PRODUCTS_PATH="$ROOT_DIR/vk/products"
VK_RESPONSE_FILE_NAME_FORMAT="%(%Y-%m-%d_%H:%M:%S)T"
VK_RESPONSE_FILE_NAME_EXT="json"
VK_API_CATEGORY_ID="40932"
VK_STOCK_AMOUNT=1000
VK_API_GET_PHOTO_UPLOAD_URL="$VK_API_HOST/market.getProductPhotoUploadServer?v=$VK_API_VERSION"
VK_API_PHOTO_PATH="$ROOT_DIR/5393364294319597854.png"
VK_API_UPLOAD_PHOTO_URL="$VK_API_HOST/market.saveProductPhoto"

92
run/vk/delete-products.sh Executable file
View File

@@ -0,0 +1,92 @@
#!/usr/bin/env bash
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source $SCRIPT_DIR/constants.sh
source $ROOT_DIR/run/evo/constants.sh
source $SCRIPT_DIR/functions.sh
# Setup logging
setup_logging
# Trap signals to ensure proper cleanup
trap 'cleanup 1' HUP INT QUIT TERM
# Delete products from vk which were deleted from evo.
# loop across vk products.
vkPath=$VK_PRODUCTS_PATH/$VK_API_GROUP_ID
vkFileName=`ls $vkPath -Art | tail -1`
vkFilePath=$vkPath/$vkFileName
evoPath=$EVO_PRODUCTS_PATH/$EVO_API_STORE_ID
evoFileName=`ls $evoPath -Art | tail -1`
evoFilePath=$evoPath/$evoFileName
hasEvoItems=`yq '. | has("items")' $evoFilePath`
if ! $hasEvoItems; then
cleanup 1
fi
# Build associative array of EVO product names (transformed)
declare -A evoNames
while IFS= read -r line; do
if [[ -n "$line" ]]; then
item_name=$(echo "$line" | yq -r '.name')
if [[ -n "$item_name" && "$item_name" != "null" ]]; then
transformed_name=$(echo "$item_name" | xargs)
transformed_name="${transformed_name//;/,}"
evoNames["$transformed_name"]=1
fi
fi
done < <(yq -o=j -I=0 '.items[]' $evoFilePath)
# Build associative array of VK items by name (transformed)
declare -A vkItemsByName
readarray vkItems < <(yq -o=j -I=0 '.response.items[]' $vkFilePath )
for vkItem in "${vkItems[@]}"; do
vkItemName=$(echo $vkItem | yq .title | xargs)
vkItemName="${vkItemName//;/,}"
vkItemId=$(echo $vkItem | yq .id)
# Store VK item IDs by name (append to comma-separated list)
if [[ -n "${vkItemsByName[$vkItemName]}" ]]; then
vkItemsByName[$vkItemName]="${vkItemsByName[$vkItemName]},$vkItemId"
else
vkItemsByName[$vkItemName]="$vkItemId"
fi
done
# For each VK name, check if it exists in EVO
for vkName in "${!vkItemsByName[@]}"; do
IFS=',' read -ra ids <<< "${vkItemsByName[$vkName]}"
if [[ -n "${evoNames[$vkName]}" ]]; then
# If multiple VK items for this name, keep the oldest (first), delete the rest
if (( ${#ids[@]} > 1 )); then
for ((i=1; i<${#ids[@]}; i++)); do
vkItemId="${ids[$i]}"
echo "$(timestamp) [REQUEST] Deleting duplicate product: $vkName (id=$vkItemId)" >> "$LOG_FILE"
response=$(curl -s -w "%{http_code}" -H "Authorization: Bearer $VK_API_USER_TOKEN" \
-F owner_id=$VK_API_PARAM_OWNER_ID \
-F item_id=$vkItemId \
$VK_API_DELETE_PRODUCT)
http_code=${response: -3}
response_body=${response:0:-3}
echo "$(timestamp) [RESPONSE] code=$http_code body=$response_body" >> "$LOG_FILE"
done
fi
else
# If VK name not in EVO, delete all VK items for this name
for vkItemId in "${ids[@]}"; do
echo "$(timestamp) [REQUEST] Deleting product not in EVO: $vkName (id=$vkItemId)" >> "$LOG_FILE"
response=$(curl -s -w "%{http_code}" -H "Authorization: Bearer $VK_API_USER_TOKEN" \
-F owner_id=$VK_API_PARAM_OWNER_ID \
-F item_id=$vkItemId \
$VK_API_DELETE_PRODUCT)
http_code=${response: -3}
response_body=${response:0:-3}
echo "$(timestamp) [RESPONSE] code=$http_code body=$response_body" >> "$LOG_FILE"
done
fi
done
# Use the cleanup function instead of directly exiting
cleanup 0

52
run/vk/functions.sh Executable file
View File

@@ -0,0 +1,52 @@
#!/usr/bin/env bash
# Setup logging
function setup_logging() {
LOG_DIR="$ROOT_DIR/logs"
mkdir -p "$LOG_DIR"
SCRIPT_NAME=$(basename "${BASH_SOURCE[0]}" .sh)
LOG_FILE="$LOG_DIR/$(date +%Y%m%d).log"
}
function timestamp() {
date "+%Y-%m-%d %H:%M:%S"
}
# Function to handle cleanup and exit
function cleanup() {
# Kill all child processes of this script
pkill -P $$
exit $1
}
# Function to handle requests and responses
function handle_vk_request() {
local request_name=$1
local request_url=$2
local path=$3
local fileName=$4
local additional_params=$5
local debug_response=${6:-true}
echo "$(timestamp) [REQUEST] Getting $request_name" >> "$LOG_FILE"
response=$(curl -s -w "%{http_code}" -H "Authorization: Bearer $VK_API_USER_TOKEN" \
-X GET \
"$request_url$additional_params")
http_code=${response: -3}
response_body=${response:0:-3}
if [ "$http_code" = "200" ]; then
if ! echo "$response_body" | jq -e 'has("error")' > /dev/null; then
touch "$path/$fileName"
echo "$response_body" > "$path/$fileName"
else
error_msg=$(echo "$response_body" | jq -r '.error.error_msg')
echo "$(timestamp) [ERROR] $error_msg" >> "$LOG_FILE"
fi
fi
if [ "$debug_response" = true ]; then
echo "$(timestamp) [RESPONSE] code=$http_code body=$response_body" >> "$LOG_FILE"
else
echo "$(timestamp) [RESPONSE] code=$http_code" >> "$LOG_FILE"
fi
}

24
run/vk/get-albums.sh Executable file
View File

@@ -0,0 +1,24 @@
#!/usr/bin/env bash
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source $SCRIPT_DIR/constants.sh
source $SCRIPT_DIR/functions.sh
# Setup logging
setup_logging
# Trap signals to ensure proper cleanup
trap 'cleanup 1' HUP INT QUIT TERM
path=$VK_ALBUMS_PATH/$VK_API_GROUP_ID
mkdir -p $path
printf -v fileName "$VK_RESPONSE_FILE_NAME_FORMAT.$VK_RESPONSE_FILE_NAME_EXT" -1
# Handle request for albums
handle_vk_request "albums list" "$VK_API_GET_ALBUMS" "$path" "$fileName" "&owner_id=$VK_API_PARAM_OWNER_ID" false
# Clean up old files
find $path ! -type d -mmin +$VK_STORE_PERIOD_MINUTES -delete
# Use the cleanup function instead of directly exiting
cleanup 0

24
run/vk/get-categories.sh Executable file
View File

@@ -0,0 +1,24 @@
#!/usr/bin/env bash
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source $SCRIPT_DIR/constants.sh
source $SCRIPT_DIR/functions.sh
# Setup logging
setup_logging
# Trap signals to ensure proper cleanup
trap 'cleanup 1' HUP INT QUIT TERM
path=$VK_CATEGORIES_PATH/$VK_API_GROUP_ID
mkdir -p $path
printf -v fileName "$VK_RESPONSE_FILE_NAME_FORMAT.$VK_RESPONSE_FILE_NAME_EXT" -1
# Handle request for categories
handle_vk_request "categories list" "$VK_API_GET_CATEGORIES" "$path" "$fileName" "" false
# Clean up old files
find $path ! -type d -mmin +$VK_STORE_PERIOD_MINUTES -delete
# Use the cleanup function instead of directly exiting
cleanup 0

24
run/vk/get-products.sh Executable file
View File

@@ -0,0 +1,24 @@
#!/usr/bin/env bash
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source $SCRIPT_DIR/constants.sh
source $SCRIPT_DIR/functions.sh
# Setup logging
setup_logging
# Trap signals to ensure proper cleanup
trap 'cleanup 1' HUP INT QUIT TERM
path=$VK_PRODUCTS_PATH/$VK_API_GROUP_ID
mkdir -p $path
printf -v fileName "$VK_RESPONSE_FILE_NAME_FORMAT.$VK_RESPONSE_FILE_NAME_EXT" -1
# Handle request for products
handle_vk_request "products list" "$VK_API_GET_PRODUCTS" "$path" "$fileName" "&owner_id=$VK_API_PARAM_OWNER_ID&extended=1&with_disabled=1&count=200" false
# Clean up old files
find $path ! -type d -mmin +$VK_STORE_PERIOD_MINUTES -delete
# Use the cleanup function instead of directly exiting
cleanup 0

47
run/vk/send-albums.sh Executable file
View File

@@ -0,0 +1,47 @@
#!/usr/bin/env bash
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source $SCRIPT_DIR/constants.sh
source $ROOT_DIR/run/evo/constants.sh
source $SCRIPT_DIR/functions.sh
# Setup logging
setup_logging
# Trap signals to ensure proper cleanup
trap 'cleanup 1' HUP INT QUIT TERM
evoPath=$EVO_GROUPS_PATH/$EVO_API_STORE_ID
evoFileName=`ls $evoPath -Art | tail -1`
evoFilePath=$evoPath/$evoFileName
vkPath=$VK_ALBUMS_PATH/$VK_API_GROUP_ID
vkFileName=`ls $vkPath -Art | tail -1`
vkFilePath=$vkPath/$vkFileName
# Load whitelist
readarray -t whitelist < "$ROOT_DIR/vk/whitelist"
# Filter items by whitelist
readarray items < <(yq -o=j -I=0 ".items[]" $evoFilePath)
for item in "${items[@]}"; do
evoTitle=`echo $item | yq .name`
# Check if group is whitelisted
if [[ ! " ${whitelist[@]} " =~ " ${evoTitle} " ]]; then
continue
fi
found=`evoTitle="$evoTitle" yq '.response.items[] | select(.title==strenv(evoTitle))' $vkFilePath`
if [[ ! -n "$found" ]]; then
echo "$(timestamp) [REQUEST] Creating album: $evoTitle" >> "$LOG_FILE"
response=$(curl -s -w "%{http_code}" -H "Authorization: Bearer $VK_API_USER_TOKEN" \
-F owner_id=$VK_API_PARAM_OWNER_ID \
-F title="$evoTitle" \
$VK_API_ADD_ALBUM)
http_code=${response: -3}
response_body=${response:0:-3}
echo "$(timestamp) [RESPONSE] code=$http_code body=$response_body" >> "$LOG_FILE"
fi
done
# Use the cleanup function instead of directly exiting
cleanup 0

257
run/vk/send-products.sh Executable file
View File

@@ -0,0 +1,257 @@
#!/usr/bin/env bash
# Rename to better reflect purpose - only applied to weight measures (г, г., грамм, etc)
WEIGHT_PRICE_MULTIPLIER=${WEIGHT_PRICE_MULTIPLIER:-10}
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source $SCRIPT_DIR/constants.sh
source $ROOT_DIR/run/evo/constants.sh
source $SCRIPT_DIR/functions.sh
# Setup logging
setup_logging
# Trap signals to ensure proper cleanup
trap 'cleanup 1' HUP INT QUIT TERM
# Function to check if a measure is a weight measure
is_weight_measure() {
local measure="$1"
# Convert to lowercase for case-insensitive comparison
local measure_lower=$(echo "$measure" | tr '[:upper:]' '[:lower:]')
# Check for various weight measure formats
if [[ "$measure_lower" == "г" ||
"$measure_lower" == "г." ||
"$measure_lower" == "грамм" ||
"$measure_lower" == "граммов" ||
"$measure_lower" == "гр" ||
"$measure_lower" == "гр." ]]; then
return 0 # True - it is a weight measure
else
return 1 # False - it is not a weight measure
fi
}
function uploadPhoto() {
getUploadPhotoResp=$(curl -s -w "%{http_code}" -H "Authorization: Bearer $VK_API_USER_TOKEN" \
"$VK_API_GET_PHOTO_UPLOAD_URL&group_id=$VK_API_GROUP_ID")
http_code=${getUploadPhotoResp: -3}
response_body=${getUploadPhotoResp:0:-3}
echo "$(timestamp) [RESPONSE] code=$http_code body=$response_body" >> "$LOG_FILE"
# Check if the first request was successful
if [[ "$http_code" != "200" ]]; then
echo "$(timestamp) [ERROR] Failed to get photo upload URL" >> "$LOG_FILE"
return 1
fi
uploadPhotoUrl=`echo $response_body | yq -pj '.response.upload_url'`
uploadPhotoObj=`curl -s -X POST --header "Content-Type: $VK_API_CONTENT_TYPE_MULTIPART" $uploadPhotoUrl -F "file=@$VK_API_PHOTO_PATH"`
uploadPhotoResp=$(curl -s -w "%{http_code}" -H "Authorization: Bearer $VK_API_USER_TOKEN" \
-F upload_response=$uploadPhotoObj \
-F v=$VK_API_VERSION \
$VK_API_UPLOAD_PHOTO_URL)
http_code=${uploadPhotoResp: -3}
response_body=${uploadPhotoResp:0:-3}
echo "$(timestamp) [RESPONSE] code=$http_code body=$response_body" >> "$LOG_FILE"
# Check if the second request was successful
if [[ "$http_code" != "200" ]]; then
echo "$(timestamp) [ERROR] Failed to upload photo" >> "$LOG_FILE"
return 1
fi
# Extract and return the photo ID - update to use the correct JSON path
local photoId=$(echo "$response_body" | yq -pj '.response.photo_id')
echo "$(timestamp) [INFO] Uploaded photo with ID: $photoId" >> "$LOG_FILE"
echo $photoId
}
evoPath=$EVO_PRODUCTS_PATH/$EVO_API_STORE_ID
evoFileName=`ls $evoPath -Art | tail -1`
evoFilePath=$evoPath/$evoFileName
evoGroupsPath=$EVO_GROUPS_PATH/$EVO_API_STORE_ID
evoGroupsFileName=`ls $evoGroupsPath -Art | tail -1`
evoGroupsFilePath=$evoGroupsPath/$evoGroupsFileName
vkPath=$VK_PRODUCTS_PATH/$VK_API_GROUP_ID
vkFileName=`ls $vkPath -Art | tail -1`
vkFilePath=$vkPath/$vkFileName
vkAlbumPath=$VK_ALBUMS_PATH/$VK_API_GROUP_ID
vkAlbumFileName=`ls $vkAlbumPath -Art | tail -1`
vkAlbumFilePath=$vkAlbumPath/$vkAlbumFileName
# Load whitelist
readarray -t whitelist < "$ROOT_DIR/vk/whitelist"
readarray items < <(yq -o=j -I=0 '.items[]' $evoFilePath )
for item in "${items[@]}"; do
evoTitle=`echo $item | yq .name`
evoGroupId=`echo $item | yq .parent_id`
evoGroupName=`evoGroupId="$evoGroupId" yq -r '.items[] | select(.id==strenv(evoGroupId)) | .name' $evoGroupsFilePath`
# Check if group is whitelisted
if [[ ! " ${whitelist[@]} " =~ " ${evoGroupName} " ]]; then
continue
fi
vkAlbumId=`albumName="$evoGroupName" yq '.response.items[] | select(.title==strenv(albumName)) | .id' $vkAlbumFilePath`
name=`echo $item | yq .name | xargs`
# Replace semicolons with commas for VK API compatibility
name_for_vk="${name//;/,}"
measure=`echo $item | yq .measure_name`
# Calculate price based on measure type
base_price=$(echo $item | yq .price)
if is_weight_measure "$measure"; then
# Apply multiplier for weight measures
price=$((base_price * WEIGHT_PRICE_MULTIPLIER))
price_info="${WEIGHT_PRICE_MULTIPLIER}$measure"
else
# No multiplier for non-weight measures
price=$base_price
price_info="$measure"
fi
quantity=`echo $item | yq .quantity`
allow_to_sell=`echo $item | yq .allow_to_sell`
# Set stock amount based on allow_to_sell flag
if [[ "$allow_to_sell" = "true" ]]; then
stock_amount=$VK_STOCK_AMOUNT
else
stock_amount=0
fi
desc="$name (цена за ${price_info}.)
"
description=$(echo $item | yq '.description // ""')
if [[ -n "$description" ]]; then
desc+="$description"
fi
article=`echo $item | yq .article_number`
found=0
readarray vkItems < <(yq -o=j -I=0 '.response.items[]' $vkFilePath )
for vkItem in "${vkItems[@]}"; do
vkTitle=`echo $vkItem | yq .title`
vkTitleTrimmed="$(echo "$vkTitle" | xargs)"
evoTitleTrimmed="$(echo "$evoTitle" | xargs)"
# For comparison, transform EVO title the same way (replace ; with ,)
evoTitleForVk="${evoTitleTrimmed//;/,}"
if [[ "$vkTitleTrimmed" = "$evoTitleForVk" ]]; then
found=1
originalName=$(echo "$vkItem" | yq .title | xargs)
originalDesc=$(echo "$vkItem" | yq .description | xargs)
originalPrice=$(echo "$vkItem" | yq .price.amount)
originalPrice=$((originalPrice / 100))
originalArticle=$(echo "$vkItem" | yq .sku | xargs)
originalStockAmount=$(echo "$vkItem" | yq .stock_amount)
# Debug output to log file
echo "$(timestamp) [DEBUG] name='$name' originalName='$originalName'" >> "$LOG_FILE"
echo "$(timestamp) [DEBUG] desc='$desc' originalDesc='$originalDesc'" >> "$LOG_FILE"
echo "$(timestamp) [DEBUG] price='$price' originalPrice='$originalPrice'" >> "$LOG_FILE"
echo "$(timestamp) [DEBUG] stock_amount='$stock_amount' allow_to_sell='$allow_to_sell'" >> "$LOG_FILE"
# Normalize descriptions more carefully to maintain proper spacing
# Replace newlines with spaces first, then normalize spaces and trim
normalized_desc=$(echo "$desc" | sed 's/\n/ /g' | tr -s ' ' | xargs)
normalized_orig_desc=$(echo "$originalDesc" | sed 's/\n/ /g' | tr -s ' ' | xargs)
# Apply semicolon-to-comma transformation for consistent comparison
normalized_desc="${normalized_desc//;/,}"
normalized_orig_desc="${normalized_orig_desc//;/,}"
# Check for changes
if [[ "$(echo "$name_for_vk" | xargs)" != "$originalName" ]]; then
name_changed="true"
else
name_changed="false"
fi
if [[ "$price" != "$originalPrice" ]]; then
price_changed="true"
else
price_changed="false"
fi
if [[ "$normalized_desc" != "$normalized_orig_desc" ]]; then
desc_changed="true"
else
desc_changed="false"
fi
if [[ "$stock_amount" != "$originalStockAmount" ]]; then
stock_changed="true"
else
stock_changed="false"
fi
# Check if update is needed
if [[ "$name_changed" == "true" || "$price_changed" == "true" || "$desc_changed" == "true" || "$stock_changed" == "true" ]]; then
productId=$(echo "$vkItem" | yq .id)
echo "$(timestamp) [REQUEST] Updating product: $name_for_vk (name_changed=$name_changed, price_changed=$price_changed, desc_changed=$desc_changed, stock_changed=$stock_changed)" >> "$LOG_FILE"
response=$(curl -s -w "%{http_code}" -H "Authorization: Bearer $VK_API_USER_TOKEN" \
-F owner_id="$VK_API_PARAM_OWNER_ID" \
-F item_id=$productId \
-F name="$name_for_vk" \
-F description="$normalized_desc" \
-F category_id=$VK_API_CATEGORY_ID \
-F price="$price" \
-F stock_amount="$stock_amount" \
$VK_API_EDIT_PRODUCT)
http_code=${response: -3}
response_body=${response:0:-3}
echo "$(timestamp) [RESPONSE] code=$http_code body=$response_body" >> "$LOG_FILE"
fi
fi
done
if [[ "$found" = 0 ]] && [[ "$allow_to_sell" = "true" ]] ; then
photoId=$(uploadPhoto)
# Check if photo upload was successful
if [[ -z "$photoId" || "$photoId" == "null" ]]; then
echo "$(timestamp) [ERROR] Failed to get valid photo ID, skipping product: $name" >> "$LOG_FILE"
continue
fi
echo "$(timestamp) [REQUEST] Creating product: $name_for_vk" >> "$LOG_FILE"
response=$(curl -s -w "%{http_code}" -H "Authorization: Bearer $VK_API_USER_TOKEN" \
-F owner_id=$VK_API_PARAM_OWNER_ID \
-F name="$name_for_vk" \
-F description="$desc" \
-F category_id=$VK_API_CATEGORY_ID \
-F price=$price \
-F main_photo_id=$photoId \
-F stock_amount="$stock_amount" \
$VK_API_ADD_PRODUCT)
http_code=${response: -3}
response_body=${response:0:-3}
echo "$(timestamp) [RESPONSE] code=$http_code body=$response_body" >> "$LOG_FILE"
# Check if product creation was successful
if [[ "$http_code" != "200" ]] || [[ $(echo $response_body | grep -c "error") -gt 0 ]]; then
echo "$(timestamp) [ERROR] Failed to create product: $name" >> "$LOG_FILE"
continue
fi
productId=$(echo $response_body | yq .response.market_item_id)
# Only proceed if we got a valid product ID
if [[ -n "$productId" && "$productId" != "null" ]]; then
resp=$(curl -s -w "%{http_code}" -H "Authorization: Bearer $VK_API_USER_TOKEN" \
"$VK_API_ADD_PRODUCT_TO_ALBUM&owner_id=$VK_API_PARAM_OWNER_ID&item_ids=$productId&album_ids=$vkAlbumId")
http_code=${resp: -3}
response_body=${resp:0:-3}
echo "$(timestamp) [RESPONSE] code=$http_code body=$response_body" >> "$LOG_FILE"
else
echo "$(timestamp) [ERROR] Invalid product ID, skipping album addition for: $name" >> "$LOG_FILE"
fi
fi
done
# Use the cleanup function instead of directly exiting
cleanup 0

1
version Normal file
View File

@@ -0,0 +1 @@
1.7.2

10
vk/whitelist-old Normal file
View File

@@ -0,0 +1,10 @@
Белый
Желтый
Зеленый
Красный
Улуны светлые
Улуны тёмные
Чай с добавками
Чёрный
Шупуэр
Шэнпуэр