Full support for expiring memberships

This commit is contained in:
Ben 2020-01-02 23:11:27 +00:00
parent 82a3753d0d
commit f255ad112d
7 changed files with 102 additions and 12 deletions

View File

@ -119,6 +119,9 @@ if __name__ == '__main__':
client_group.add_argument('-kt', '--auth-ttl', action='store', type=int, default=3000,
help='Auth key TTL (seconds)')
membership_group = parser.add_argument_group('membership')
membership_group.add_argument('--expire-membership', action='store_true', help='Should membership expire?')
args = parser.parse_args()
args.port = args.port if args.port else 9875 if args.type == 'world' else 6112

View File

@ -1371,13 +1371,17 @@ COMMENT ON TABLE penguin_stamp IS 'Penguin earned stamps';
COMMENT ON COLUMN penguin_stamp.penguin_id IS 'Stamp penguin ID';
COMMENT ON COLUMN penguin_stamp.stamp_id IS 'Stamp ID';
COMMENT ON COLUMN penguin_stamp.recent IS 'Is recently earned?';
COMMENT ON COLUMN penguin_stamp.recent IS 'Is recently earned?';
DROP TABLE IF EXISTS penguin_membership;
CREATE TABLE penguin_membership (
penguin_id INT NOT NULL,
start TIMESTAMP NOT NULL,
expires TIMESTAMP NOT NULL,
PRIMARY KEY(penguin_id, start, expires),
expires TIMESTAMP DEFAULT NULL,
start_aware BOOLEAN DEFAULT FALSE,
expires_aware BOOLEAN DEFAULT FALSE,
expired_aware BOOLEAN DEFAULT FALSE,
PRIMARY KEY(penguin_id, start),
CONSTRAINT penguin_membership_ibfk_1 FOREIGN KEY (penguin_id) REFERENCES penguin (id) ON DELETE RESTRICT ON UPDATE CASCADE
);

View File

@ -84,7 +84,7 @@ class PenguinStringCompiler(OrderedDict):
'Y': PenguinStringCompiler.attribute_by_name('y'),
'Frame': PenguinStringCompiler.attribute_by_name('frame'),
'Member': PenguinStringCompiler.attribute_by_name('member'),
'MemberDays': PenguinStringCompiler.attribute_by_name('membership_days'),
'MemberDays': PenguinStringCompiler.attribute_by_name('membership_days_total'),
'Avatar': PenguinStringCompiler.attribute_by_name('avatar'),
'PenguinState': PenguinStringCompiler.attribute_by_name('penguin_state'),
'PartyState': PenguinStringCompiler.attribute_by_name('party_state'),

View File

@ -148,7 +148,10 @@ class PenguinMembership(db.Model):
penguin_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True,
nullable=False)
start = db.Column(db.DateTime, primary_key=True, nullable=False)
expires = db.Column(db.DateTime, primary_key=True, nullable=False)
expires = db.Column(db.DateTime)
start_aware = db.Column(db.Boolean, server_default=db.text("false"))
expires_aware = db.Column(db.Boolean, server_default=db.text("false"))
expired_aware = db.Column(db.Boolean, server_default=db.text("false"))
class Login(db.Model):

View File

@ -43,7 +43,7 @@ async def handle_join_server(p, penguin_id: int, login_key: str):
await p.send_xt('lp', await p.string, p.coins, int(p.safe_chat), p.egg_timer_minutes,
penguin_standard_time, p.age, 0, p.minutes_played,
"membership_days", server_time_offset, int(p.opened_playercard),
p.membership_days_remain, server_time_offset, int(p.opened_playercard),
p.map_category, p.status_field)
spawn = random.choice(p.server.spawn_rooms)

View File

@ -1,11 +1,14 @@
from houdini import handlers
from houdini.converters import SeparatorConverter
from houdini.handlers import XTPacket
from houdini.data.penguin import Penguin
from houdini.handlers.play.navigation import handle_join_server
from houdini.data import db
from houdini.data.penguin import Penguin, PenguinMembership
from houdini.data.mail import PenguinPostcard
from houdini.constants import ClientType
from aiocache import cached
from datetime import datetime
from datetime import datetime, timedelta
import random
import asyncio
import time
@ -77,6 +80,78 @@ async def server_egg_timer(server):
await p.send_error_and_disconnect(910)
MemberWarningDaysToExpiry = 14
MemberWarningPostcardsVanilla = [122, 123]
MemberWarningPostcardsLegacy = [163]
MemberExpiredPostcard = 124
MemberStartPostcardVanilla = 121
MemberStartPostcardLegacy = 164
@handlers.handler(XTPacket('j', 'js'), pre_login=True, before=handle_join_server)
@handlers.allow_once
async def handle_setup_membership(p):
if not p.server.config.expire_membership or p.moderator or p.character:
p.is_member = True
p.membership_days_total = p.age
return
membership_history = PenguinMembership.query.where(PenguinMembership.penguin_id == p.id)
current_timestamp = datetime.now()
postcards = []
warning_postcards = MemberWarningPostcardsVanilla if p.is_vanilla_client else MemberWarningPostcardsLegacy
start_postcard = MemberStartPostcardVanilla if p.is_vanilla_client else MemberStartPostcardLegacy
async with db.transaction():
async for membership_record in membership_history.gino.iterate():
membership_recurring = membership_record.expires is None
membership_active = membership_recurring or membership_record.expires >= current_timestamp
if membership_record.start < current_timestamp:
if membership_active:
p.is_member = True
if not membership_recurring:
days_to_expiry = (membership_record.expires.date() - datetime.now().date()).days
p.membership_days_remain = days_to_expiry
if days_to_expiry <= MemberWarningDaysToExpiry and not membership_record.expires_aware:
postcards.append(dict(
penguin_id=p.id,
postcard_id=random.choice(warning_postcards),
send_date=membership_record.expires - timedelta(days=MemberWarningDaysToExpiry)
))
await membership_record.update(expires_aware=True).apply()
else:
if p.membership_days_remain < 0:
days_since_expiry = (membership_record.expires.date() - datetime.now().date()).days
p.membership_days_remain = min(p.membership_days_remain, days_since_expiry)
if not membership_record.expired_aware:
if p.is_vanilla_client:
postcards.append(dict(
penguin_id=p.id,
postcard_id=MemberExpiredPostcard,
send_date=membership_record.expires
))
await membership_record.update(expired_aware=True).apply()
if not membership_record.start_aware:
postcards.append(dict(
penguin_id=p.id,
postcard_id=start_postcard,
send_date=membership_record.start
))
await membership_record.update(start_aware=True).apply()
membership_end_date = current_timestamp if membership_active else membership_record.expires
p.membership_days_total += (membership_end_date - membership_record.start).days
if postcards:
await PenguinPostcard.insert().values(postcards).gino.status()
@handlers.handler(XTPacket('u', 'h'))
@handlers.cooldown(59)
async def handle_heartbeat(p):

View File

@ -7,9 +7,9 @@ from houdini.data import penguin
class Penguin(Spheniscidae, penguin.Penguin):
__slots__ = ['x', 'y', 'frame', 'toy', 'room', 'waddle', 'table',
'data', 'muted', 'login_key', 'member', 'membership_days',
'avatar', 'walking_puffle', 'permissions', 'active_quests',
'buddy_requests', 'heartbeat', 'login_timestamp',
'data', 'muted', 'login_key', 'is_member', 'membership_days_total',
'membership_days_remain', 'avatar', 'walking_puffle', 'permissions',
'active_quests', 'legacy_buddy_requests', 'heartbeat', 'login_timestamp',
'egg_timer_minutes']
def __init__(self, *args):
@ -25,8 +25,9 @@ class Penguin(Spheniscidae, penguin.Penguin):
self.login_key = None
self.member = 1
self.membership_days = 1
self.is_member = False
self.membership_days_total = 0
self.membership_days_remain = -1
self.avatar = None
self.walking_puffle = None
@ -58,6 +59,10 @@ class Penguin(Spheniscidae, penguin.Penguin):
def safe_name(self):
return self.safe_nickname(self.server.config.lang)
@property
def member(self):
return int(self.is_member)
async def join_room(self, room):
await room.add_penguin(self)