def doUserSetStarId(self, userId, gameId, clientId, starId): userdata.setAttr(userId, 'starid', starId) mo = MsgPack() mo.setCmd('set_star_id') mo.setResult('gameId', gameId) mo.setResult('starId', starId) router.sendToUser(mo, userId)
def testQuickStart(self): '''测试快速开始''' gameId = 6 userId = random.randint(10000, 20000) roomId = 0 tableId = 0 chip = 800 clientId = "Android_3.501_tuyoo.YDJD.0-hall6.apphui.happy" playMode = "happy" onlinedata.setOnlineState(userId, onlinedata.ONLINE) onlinedata.cleanOnlineLoc(userId) userdata.setAttr(userId, "sessionClientId", clientId) # datas = sessiondata._getUserSessionValues(userId) # ftlog.debug("|userId, session:", userId, datas) oldChip = userchip.getChip(userId) userchip.incrChip(userId, gameId, chip - oldChip, 0, "GM_ADJUST_COIN", 0, clientId) msg = MsgPack() msg.setCmd("quick_start") msg.setParam("gameId", gameId) msg.setParam("userId", userId) # msg.setParam("roomId", roomId) # msg.setParam("tableId", tableId) msg.setParam("clientId", clientId) print '='*30 print msg BaseQuickStartDispatcher.dispatchQuickStart(msg, userId, gameId, roomId, tableId, playMode, clientId) print '='*30
def testQuickStart(self): '''测试快速开始''' gameId = 6 userId = 1234 roomId = 0 tableId = 0 chip = 800 clientId = "Android_3.501_tuyoo.YDJD.0-hall6.apphui.happy" onlinedata.setOnlineState(userId, onlinedata.ONLINE) onlinedata.cleanOnlineLoc(userId) userdata.setAttr(userId, "sessionClientId", clientId) # datas = sessiondata._getUserSessionValues(userId) # ftlog.debug("|userId, session:", userId, datas) oldChip = userchip.getChip(userId) userchip.incrChip(userId, gameId, chip - oldChip, 0, "GM_ADJUST_COIN", 0, 0) msg = MsgPack() msg.setCmd("quick_start") msg.setParam("gameId", gameId) msg.setParam("userId", userId) msg.setParam("roomId", roomId) msg.setParam("tableId", tableId) msg.setParam("clientId", clientId) print '=' * 30 print msg DizhuQuickStartDispatcher.dispatchQuickStart(msg) print '=' * 30
def getUserHeadUrl(userId, clientId, purl=None, beauty=None): ''' 取得当前的用户的头像 取得当前的用户是否是美女认证账户 ''' # 自定义头像, 美女认证 if purl == None: purl, beauty = userdata.getAttrs(userId, ['purl', 'beauty']) if purl: purl = unicode(purl) else: purl = '' purl = _filter360QihuImage(userId, clientId, purl) if isinstance(purl, (str, unicode)): if (purl.find('http://') < 0) and (purl.find('https://') < 0): purl = '' if purl == '' or purl == None: heads = getUserHeadPics(clientId) purl = random.choice(heads) userdata.setAttr(userId, 'purl', purl) isBeauty = False if beauty: isBeauty = True if (beauty & 1) != 0 else False return purl, isBeauty
def fixCoupon(): userId = 11293 target = 640 nowCount = userchip.getCoupon(userId) if target < nowCount: userchip.incrCoupon(userId, HALL_GAMEID, target - nowCount, ChipNotEnoughOpMode.NOOP, 'HALL_INVITEE_TASK_REWARD', 0, None) userdata.setAttr(userId, 'exchangedCoupon', 0)
def _filter360QihuImage(userId, clientId, headurl): ''' 过滤360SNS账户的恶心的头像图标 ''' headurl = unicode(headurl) if headurl.find('qhimg.com') > 0: heads = getUserHeadPics(clientId) purl = random.choice(heads) userdata.setAttr(userId, 'purl', purl) return purl return headurl
def modifyUserName(userId): """ iOS下游客登录及苹果登录用户修改昵称为g_xxxx """ clientIdNum = util.getClientIdNum(userId) if clientIdNum in config.getCommonValueByKey("randomNickNameClientIds", []): snsId = userdata.getAttr(userId, "snsId") if not snsId or str(snsId).startswith("ios"): nickName = "g_" + ''.join( random.choice(string.ascii_letters + string.digits) for _ in range(7)) userdata.setAttr(userId, "name", nickName)
def testOrderDelivery(self): userdata.setAttr(self.userId, 'chip', 100) TGHall.getEventBus().publishEvent(ChargeNotifyEvent(self.userId, self.gameId, 10, 100, 'T50K', self.clientId)) self.assertEqual(userdata.getAttr(self.userId, 'chip'), 100) userAssets = hallitem.itemSystem.loadUserAssets(self.userId) userAssets.addAsset(self.gameId, 'item:4347', 1, int(time.time()), 'TEST', 0) TGHall.getEventBus().publishEvent(ChargeNotifyEvent(self.userId, self.gameId, 10, 100, 'T50K', self.clientId)) self.assertEqual(userdata.getAttr(self.userId, 'chip'), 100) TGHall.getEventBus().publishEvent(ChargeNotifyEvent(self.userId, self.gameId, 30, 100, 'T50K', self.clientId)) self.assertEqual(userdata.getAttr(self.userId, 'chip'), 200000+100) print userdata.getAttr(self.userId, 'chip')
def asyncTest3PlayerMatchWithRobot(self): ftlog.debug("-" * 30) ctrlRoomId = 60121000 # clientId = "Android_3.501_tuyoo.YDJD.0-hall6.apphui.happy" clientId = "Android_3.372_momo.momo.0-hall6.momo.momo" userId1 = random.randint(10000, 20000) onlinedata.setOnlineState(userId1, onlinedata.ONLINE) onlinedata.cleanOnlineLoc(userId1) userdata.setAttr(userId1, "sessionClientId", clientId) bigMatchRoom = gdata.rooms()[ctrlRoomId] bigMatchRoom.doEnter(userId1) bigMatchRoom.doSignin(userId1)
def sendChipExpNotify(gameId, userId, chip_exp, before_chip_exp=0, now_chip_level=0, level=0): ftlog.debug('sendChipExpNotify userId:', userId, 'chip_exp:', chip_exp) mo = makeChipExpNotifyMsg(gameId, userId, chip_exp, before_chip_exp, now_chip_level, level) # mo是返回 userdata.setAttr(userId, 'chip_level', level) ftlog.debug('sendChipExpNotify mo:', mo) router.sendToUser(mo, userId) sendLevelUpToHallTask(userId, level, now_chip_level)
def _rename(gameId, userId, newName): if SDK_UNIQUE_NAME: params = {'userId': userId, 'userName': newName} sdkres = sdkclient._requestSdk('/open/v4/user/changeName', params, needresponse=1) ftlog.debug('hallrename._rename->', sdkres) if isinstance(sdkres, dict): result = sdkres.get('result', {}) code = result.get('code', -1) info = result.get('info', '改名失败,请重试') return code, info return -1, '改名失败,请重试.' else: userdata.setAttr(userId, 'name', newName) return 0, ''
def doGetUserState(cls, gameId, userId, clientId, action): ''' 获取用户信息,不存在则,验证用户状态,写入用户活动状态表,并返回用户状态 ''' conf = hallconf.getNeiTuiGuangConf(clientId) mo = MsgPack() if not conf: ftlog.error('neituiguang doGetUserState conf not found gameId=', gameId, 'userId=', userId, 'clientId=', clientId, 'action=', action) return mo state = gamedata.getGameAttr(userId, gameId, cls.attrname_state) createTime = userdata.getAttr(userId, 'createTime') if state == None: state = cls.initUserState(userId, gameId, createTime, conf) userdata.setAttr(userId, cls.attrname_state, state) inviteetip = cls.inviteeTip(userId, gameId, createTime, conf) else: if cls.isOldUser(userId, gameId, createTime, conf): state = 2 userdata.setAttr(userId, cls.attrname_state, state) # 老用户状态 if state == 0: NeiTuiGuangProtocolBuilder.buildNewUserState( gameId, userId, action, state, inviteetip, mo) elif state == 1: bindmobile = userdata.getAttr(userId, 'bindMobile') bindok = cls.bindOk(bindmobile) NeiTuiGuangProtocolBuilder.buildBindUserState( gameId, userId, action, state, bindok, mo) elif state == 2: bindmobile = userdata.getAttr(userId, 'bindMobile') bindok = cls.bindOk(bindmobile) shareId = conf.get('shareId', -1) prizeinfo = conf.get('prize_info', []) weixintip = cls.getWeiXinTip(userId, conf) url = cls.getDownUrl(userId, conf) smstip = cls.getSMSTip(gameId, userId, conf) rules = conf.get('rules', []) NeiTuiGuangProtocolBuilder.buildOldUserState( gameId, userId, action, state, bindok, shareId, prizeinfo, weixintip, url, smstip, rules, mo) else: pass return mo
def doGetUserState(cls, gameId, userId, clientId, action): ''' 获取用户信息,不存在则,验证用户状态,写入用户活动状态表,并返回用户状态 ''' conf = hallconf.getNeiTuiGuangConf(clientId) mo = MsgPack() if not conf: ftlog.error('neituiguang doGetUserState conf not found gameId=', gameId, 'userId=', userId, 'clientId=', clientId, 'action=', action) return mo state = gamedata.getGameAttr(userId, gameId, cls.attrname_state) createTime = userdata.getAttr(userId, 'createTime') if state == None: state = cls.initUserState(userId, gameId, createTime, conf) userdata.setAttr(userId, cls.attrname_state, state) inviteetip = cls.inviteeTip(userId, gameId, createTime, conf) else: if cls.isOldUser(userId, gameId, createTime, conf): state = 2 userdata.setAttr(userId, cls.attrname_state, state) # 老用户状态 if state == 0: NeiTuiGuangProtocolBuilder.buildNewUserState(gameId, userId, action, state, inviteetip, mo) elif state == 1: bindmobile = userdata.getAttr(userId, 'bindMobile') bindok = cls.bindOk(bindmobile) NeiTuiGuangProtocolBuilder.buildBindUserState(gameId, userId, action, state, bindok, mo) elif state == 2: bindmobile = userdata.getAttr(userId, 'bindMobile') bindok = cls.bindOk(bindmobile) shareId = conf.get('shareId', -1) prizeinfo = conf.get('prize_info', []) weixintip = cls.getWeiXinTip(userId, conf) url = cls.getDownUrl(userId, conf) smstip = cls.getSMSTip(gameId, userId, conf) rules = conf.get('rules', []) NeiTuiGuangProtocolBuilder.buildOldUserState(gameId, userId, action, state, bindok, shareId, prizeinfo, weixintip, url, smstip, rules, mo) else: pass return mo
def testGetPrize(self): handler = NeiTuiGuangTcpHandler() createTime = datetime.now( ) # datetime.strptime('2015-12-22 18:20:00.0', '%Y-%m-%d %H:%M:%S.%f') userdata.setAttr(self.userId, 'createTime', createTime.strftime('%Y-%m-%d %H:%M:%S.%f')) userdata.setAttr(self.inviteeUserId, 'createTime', createTime.strftime('%Y-%m-%d %H:%M:%S.%f')) createTime -= timedelta(days=8) userdata.setAttr(self.userId, 'createTime', createTime.strftime('%Y-%m-%d %H:%M:%S.%f')) gamedata.delGameAttr(self.userId, self.gameId, 'neituiguang') gamedata.delGameAttr(self.inviteeUserId, self.gameId, 'neituiguang') handler.doQueryPrize(self.gameId, self.userId, self.clientId) timestamp = pktimestamp.getCurrentTimestamp() handler.doCheckCode(self.gameId, self.inviteeUserId, self.clientId, self.userId) neituiguang_remote.onInvitationAccepted(self.userId, self.inviteeUserId) status = neituiguang.loadStatus(self.userId, timestamp) neituiguang.onNotifyInviterOk(status) handler.doQueryPrize(self.gameId, self.userId, self.clientId) handler.doGetPrize(self.gameId, self.userId, self.clientId)
def renameNickname(userId, clientId, nickname): """ 修改玩家昵称 """ lang = util.getLanguage(userId, clientId) surplusCount = util.balanceItem(userId, config.RENAME_KINDID) try: if surplusCount == 0: code = 1 info = config.getMultiLangTextConf("ID_RENAME_CODE_INFO_1", lang=lang) # u"昵称修改失败" elif len(nickname) == 0: code = 2 info = config.getMultiLangTextConf("ID_RENAME_CODE_INFO_2", lang=lang) # u"昵称不能为空,请重新输入!" elif len(nickname.decode("utf-8").encode("gbk", "ignore")) > 16: code = 3 info = config.getMultiLangTextConf("ID_RENAME_CODE_INFO_3", lang=lang) # u"昵称过长,请重新输入!" elif re.search(u"[^\w\u4e00-\u9fff]+", nickname.decode("utf-8")): code = 4 info = config.getMultiLangTextConf("ID_RENAME_CODE_INFO_4", lang=lang) # u"昵称不能含有特殊字符,请重新输入!" elif util.isTextCensorLimit(nickname): code = 5 info = config.getMultiLangTextConf("ID_RENAME_CODE_INFO_5", lang=lang) # u"昵称含有违规内容,请重新输入!" else: _consume = [{"name": config.RENAME_KINDID, "count": 1}] util.consumeItems(userId, _consume, "ITEM_USE") gamedata.setGameAttr(userId, FISH_GAMEID, GameData.nickname, nickname) userdata.setAttr(userId, "name", nickname) code = 0 info = config.getMultiLangTextConf("ID_RENAME_CODE_INFO_0", lang=lang) # u"昵称修改成功" except: ftlog.error() code = 1 info = config.getMultiLangTextConf("ID_RENAME_CODE_INFO_1", lang=lang) # u"昵称修改失败" message = MsgPack() message.setCmd("rename_nickname") message.setResult("gameId", FISH_GAMEID) message.setResult("userId", userId) message.setResult("info", info) message.setResult("code", code) router.sendToUser(message, userId)
def setUp(self): self.testContext.startMock() self.redisClient = StrictRedis('127.0.0.1', 6379, 0) daobase.executeMixCmd = self.runRedisCmd daobase._executePayDataCmd = self.runRedisCmd daobase.executeUserCmd = self.runUserRedisCmd daobase.sendUserCmd = self.runUserRedisCmd daobase.executeUserCmd(self.userId, 'del', 'yyb.gifts:%s' % (self.userId)) self.testContext.configure.setJson('poker:global', {'config.game.ids':[6,9999]}, None) self.testContext.configure.setJson('game:9999:map.clientid', clientIdMap, 0) self.testContext.configure.setJson('poker:map.clientid', clientIdMap, None) self.testContext.configure.setJson('game:9999:item', item_conf, 0) self.testContext.configure.setJson('game:9999:yyb.gifts', conf, 0) self.timestamp = pktimestamp.getCurrentTimestamp() self.pktimestampPatcher = patch('poker.util.timestamp.getCurrentTimestamp', self.getCurrentTimestamp) self.pktimestampPatcher.start() userdata.setAttr(self.userId, 'lastAuthorTime', '2017-10-27 00:00:00.000') userdata.setAttr(self.userId, 'authorTime', '2017-11-28 00:00:00.000') hallitem._initialize() hall_yyb_gifts._initialize()
def testGetPrize(self): handler = NeiTuiGuangTcpHandler() createTime = datetime.now()# datetime.strptime('2015-12-22 18:20:00.0', '%Y-%m-%d %H:%M:%S.%f') userdata.setAttr(self.userId, 'createTime', createTime.strftime('%Y-%m-%d %H:%M:%S.%f')) userdata.setAttr(self.inviteeUserId, 'createTime', createTime.strftime('%Y-%m-%d %H:%M:%S.%f')) createTime -= timedelta(days=8) userdata.setAttr(self.userId, 'createTime', createTime.strftime('%Y-%m-%d %H:%M:%S.%f')) gamedata.delGameAttr(self.userId, self.gameId, 'neituiguang') gamedata.delGameAttr(self.inviteeUserId, self.gameId, 'neituiguang') handler.doQueryPrize(self.gameId, self.userId, self.clientId) timestamp = pktimestamp.getCurrentTimestamp() handler.doCheckCode(self.gameId, self.inviteeUserId, self.clientId, self.userId) neituiguang_remote.onInvitationAccepted(self.userId, self.inviteeUserId) status = neituiguang.loadStatus(self.userId, timestamp) neituiguang.onNotifyInviterOk(status) handler.doQueryPrize(self.gameId, self.userId, self.clientId) handler.doGetPrize(self.gameId, self.userId, self.clientId)
def saveStatus(actId, status): d = status.toDict() jstr = strutil.dumps(d) userdata.setAttr(status.userId, 'act:%s' % (actId), jstr)
def fixCoupon(): ls = [{ "userId": 10130, "target": 640 }, { "userId": 10061, "target": 740 }, { "userId": 10003, "target": 740 }, { "userId": 10093, "target": 640 }, { "userId": 10133, "target": 640 }, { "userId": 11434, "target": 640 }, { "userId": 11426, "target": 640 }, { "userId": 11395, "target": 640 }, { "userId": 11146, "target": 640 }, { "userId": 11345, "target": 640 }, { "userId": 10831, "target": 640 }, { "userId": 11252, "target": 640 }, { "userId": 11370, "target": 640 }, { "userId": 11359, "target": 640 }, { "userId": 11316, "target": 640 }, { "userId": 11302, "target": 640 }, { "userId": 10968, "target": 980 }, { "userId": 11292, "target": 640 }, { "userId": 11258, "target": 640 }, { "userId": 10186, "target": 640 }, { "userId": 11251, "target": 640 }, { "userId": 10064, "target": 980 }, { "userId": 10071, "target": 1420 }, { "userId": 10121, "target": 640 }, { "userId": 10271, "target": 870 }, { "userId": 10151, "target": 870 }, { "userId": 10571, "target": 740 }, { "userId": 10330, "target": 640 }, { "userId": 11010, "target": 640 }, { "userId": 10369, "target": 870 }, { "userId": 10552, "target": 640 }, { "userId": 10058, "target": 870 }, { "userId": 10340, "target": 640 }, { "userId": 10305, "target": 750 }, { "userId": 11180, "target": 640 }, { "userId": 10359, "target": 640 }, { "userId": 11170, "target": 640 }, { "userId": 11167, "target": 640 }, { "userId": 12367, "target": 530 }, { "userId": 12156, "target": 530 }, { "userId": 12670, "target": 530 }, { "userId": 12433, "target": 530 }, { "userId": 12583, "target": 530 }, { "userId": 12635, "target": 530 }, { "userId": 12492, "target": 530 }, { "userId": 12463, "target": 530 }, { "userId": 12434, "target": 530 }, { "userId": 12394, "target": 530 }, { "userId": 12197, "target": 530 }, { "userId": 12291, "target": 530 }, { "userId": 12177, "target": 530 }, { "userId": 11974, "target": 760 }, { "userId": 12466, "target": 530 }, { "userId": 12134, "target": 530 }, { "userId": 12306, "target": 530 }, { "userId": 12339, "target": 530 }, { "userId": 12178, "target": 530 }, { "userId": 12152, "target": 530 }, { "userId": 11730, "target": 530 }, { "userId": 12135, "target": 530 }, { "userId": 12135, "target": 530 }, { "userId": 11835, "target": 760 }, { "userId": 11709, "target": 760 }, { "userId": 10110, "target": 760 }, { "userId": 11508, "target": 760 }, { "userId": 10154, "target": 530 }, { "userId": 11932, "target": 530 }, { "userId": 11877, "target": 530 }, { "userId": 11463, "target": 530 }, { "userId": 11787, "target": 530 }, { "userId": 11765, "target": 530 }, { "userId": 11776, "target": 530 }, { "userId": 10500, "target": 760 }, { "userId": 11387, "target": 760 }, { "userId": 10583, "target": 530 }, { "userId": 11668, "target": 530 }, { "userId": 11607, "target": 530 }, { "userId": 11641, "target": 530 }, { "userId": 11666, "target": 530 }, { "userId": 11483, "target": 530 }, { "userId": 11627, "target": 530 }, { "userId": 10286, "target": 530 }, { "userId": 11270, "target": 530 }, { "userId": 10949, "target": 530 }, { "userId": 10398, "target": 530 }] for lNode in ls: userId = lNode['userId'] target = lNode['target'] nowCount = userchip.getCoupon(userId) if target > nowCount: continue userchip.incrCoupon(userId, HALL_GAMEID, target - nowCount, ChipNotEnoughOpMode.NOOP, 'HALL_INVITEE_TASK_REWARD', 0, None) userdata.setAttr(userId, 'exchangedCoupon', 0)
def _doGamePublicLed(cls, mi): ''' 这个方法在CO进程中由game_public_led的消息handler进行调用, 不允许在其它进程中调用 ''' assert (gdata.serverType() == gdata.SRV_TYPE_CONN) ftlog.debug("<< |msgPackIn:", mi, caller=cls) # 如果从 result 中获取不到数据,从 param 中获取,还取不到,用默认值 gameId = mi.getResult('gameId', 0) or mi.getParam('gameId') or 0 receivers = mi.getResult('receivers') or mi.getParam('receivers') or [] excludeUsers = mi.getResult('excludeUsers') or mi.getParam( 'excludeUsers') or set() msg = mi.getResult('msg') or mi.getParam('msg') timelimit = mi.getResult('timelimit') or mi.getResult( 'timelimit') or {} force = mi.getResult('force') or mi.getParam('force') or [ ] # 这里指定的用户不能过滤,必须收 intervals = timelimit.get('timeLimitIntervals') # 这个时间内收过led的不再收 limitName = timelimit.get('timeLimitName') # 时间间隔的种类,反射机制设置到 user 对象中去 msg = cls._tryParseRichTextLed(msg) leds = [[0, gameId, msg]] ftlog.debug("|leds:", leds, caller=cls) popWinInfo = mi.getResult('popWin') ledWithTodoTask = mi.getResult('ledWithTodoTask') or mi.getParam( 'ledWithTodoTask') # if not ledWithTodoTask: # ledWithTodoTask = Message._richText2Todotask(mi, msg) #TODO: if gameId <= 0: ftlog.error( "doGamePublicLed: error: game not found or gameid is 0", gameId) return mo_cache = {} # 缓存不同版本的 led消息,避免每次生成,浪费CPU def mo_cache_key(gameId, clientVer, clientId): if gameId == 8 and clientVer == 3.37 and 'hall8' in clientId: return gameId, clientVer, 'hall8' return gameId, clientVer def make_led_mo(newleds, clientVer, clientId): mo = MsgPack() mo.setCmd('led') if clientVer >= 3.6: if ledWithTodoTask: mo.setKey('result', ledWithTodoTask) return mo.pack() return None if newleds['origLeds']: mo.setKey('result', newleds['origLeds']) if newleds['richLeds']: mo.setKey('result', newleds['richLeds']) mo.setKey('richText', newleds['richLeds']) mo.setResult('gameId', gameId) if gameId == 8: if clientVer < 3.0: # 德州老单包有问题,所有格式都转为没有按钮的LED (也就是type='led') newleds['richLeds']['type'] = 'led' elif newleds['richLeds'][ 'type'] == 'vip' and clientVer == 3.37 and 'hall8' in clientId: # 德州大厅3.37版本BUG: 如果消息中有 richText,则收不到消息 mo.rmKey('richText') if popWinInfo and clientId.startswith('Winpc'): mo.setResult('popWin', popWinInfo) return mo.pack() def sendled(): newleds = cls._getLedMsgLists(leds, gameId, userId) clientVer = sessiondata.getClientIdVer(userId) clientId = sessiondata.getClientId(userId) if newleds['richLeds'] or newleds['origLeds']: key = mo_cache_key(gameId, clientVer, clientId) if key not in mo_cache: mo_cache[key] = make_led_mo(newleds, clientVer, clientId) mo = mo_cache[key] if mo: protocols.sendCarryMessage(mo, [userId]) if receivers: receivers = receivers + force else: receivers = protocols._ONLINE_USERS.keys() #需求:两次同类型led之间需要相隔intervals now = time.time() sc = 0 for userId in receivers: sc += 1 if sc % 20 == 0: FTTasklet.getCurrentFTTasklet().sleepNb(0.1) if userId in force: if limitName: # setattr(user, limitName, now) userdata.setAttr(userId, limitName, now) sendled() continue if userId not in excludeUsers: if limitName and intervals: # 时间限制 # lastSendTime = getattr(user, limitName, 0) lastSendTime = userdata.getAttr(userId, limitName) if not lastSendTime: lastSendTime = 0 ftlog.debug('|userId, limitName, intervals, lastSendTime:', userId, limitName, intervals, lastSendTime, caller=cls) if lastSendTime + intervals <= now: userdata.setAttr(userId, limitName, now) sendled() else: sendled()
def saveStatusData(gameId, userId, actId, data): userdata.setAttr(userId, 'act:%s' % (actId), data)