添加客服账号

@param string $kf_account
@param string $nickname
@param string $password

@return array(err, res)

Examples:
```
list($err, $res) = $api->add_kf('test1234@微信号', '客服昵称', '客服密码');
```
349572
public function add_kf ($kf_account, $nickname, $password)
    {
        $password = md5($password);
        $xml = sprintf('{'.
                '"kf_account" : "%s",'.
                '"nickname" : "%s",'.
                '"password" : "%s"}',
                $kf_account,
                $nickname,
                md5($password));
        $url = self::API_DOMAIN . 'customservice/kfaccount/add?access_token=' . $this->get_access_token();
        $res = HttpCurl::post($url, $xml, 'json');
        // 异常处理: 获取时网络错误
        if ($res === false) {
            return Error::code('ERR_GET');
        }
        // 判断是否调用成功
        if ($res->errcode == 0) {
            return array(null, true);
        } else {
            return array($res, null);
        }
    }
1275629
public function inviteworker($kf_account, $invite_wx)
    {
        $url = 'https://api.weixin.qq.com/customservice/kfaccount/inviteworker?access_token=ACCESS_TOKEN';
        $this->registerApi($url, __FUNCTION__, func_get_args());
        return $this->callPostApi($url, ['kf_account' => $kf_account, 'invite_wx' => $invite_wx]);
    }
1004058
function login($username, $password = '')
    {
        if (!parent::login($username, $password)) {
            return false;
        }

        $this->window_size_client_to_server[NET_SFTP_CHANNEL] = $this->window_size;

        $packet = pack('CNa*N3',
            NET_SSH2_MSG_CHANNEL_OPEN, strlen('session'), 'session', NET_SFTP_CHANNEL, $this->window_size, 0x4000);

        if (!$this->_send_binary_packet($packet)) {
            return false;
        }

        $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_OPEN;

        $response = $this->_get_channel_packet(NET_SFTP_CHANNEL);
        if ($response === false) {
            return false;
        }

        $packet = pack('CNNa*CNa*',
            NET_SSH2_MSG_CHANNEL_REQUEST, $this->server_channels[NET_SFTP_CHANNEL], strlen('subsystem'), 'subsystem', 1, strlen('sftp'), 'sftp');
        if (!$this->_send_binary_packet($packet)) {
            return false;
        }

        $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_REQUEST;

        $response = $this->_get_channel_packet(NET_SFTP_CHANNEL);
        if ($response === false) {
            return false;
        }

        $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_DATA;

        if (!$this->_send_sftp_packet(NET_SFTP_INIT, "\0\0\0\3")) {
            return false;
        }

        $response = $this->_get_sftp_packet();
        if ($this->packet_type != NET_SFTP_VERSION) {
            user_error('Expected SSH_FXP_VERSION', E_USER_NOTICE);
            return false;
        }

        extract(unpack('Nversion', $this->_string_shift($response, 4)));
        $this->version = $version;
        while (!empty($response)) {
            extract(unpack('Nlength', $this->_string_shift($response, 4)));
            $key = $this->_string_shift($response, $length);
            extract(unpack('Nlength', $this->_string_shift($response, 4)));
            $value = $this->_string_shift($response, $length);
            $this->extensions[$key] = $value;
        }

        /*
         SFTPv4+ defines a 'newline' extension.  SFTPv3 seems to have unofficial support for it via 'newline@vandyke.com',
         however, I'm not sure what 'newline@vandyke.com' is supposed to do (the fact that it's unofficial means that it's
         not in the official SFTPv3 specs) and 'newline@vandyke.com' / 'newline' are likely not drop-in substitutes for
         one another due to the fact that 'newline' comes with a SSH_FXF_TEXT bitmask whereas it seems unlikely that
         'newline@vandyke.com' would.
        */
        /*
        if (isset($this->extensions['newline@vandyke.com'])) {
            $this->extensions['newline'] = $this->extensions['newline@vandyke.com'];
            unset($this->extensions['newline@vandyke.com']);
        }
        */

        $this->request_id = 1;

        /*
         A Note on SFTPv4/5/6 support:
         <http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-5.1> states the following:

         "If the client wishes to interoperate with servers that support noncontiguous version
          numbers it SHOULD send '3'"

         Given that the server only sends its version number after the client has already done so, the above
         seems to be suggesting that v3 should be the default version.  This makes sense given that v3 is the
         most popular.

         <http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-5.5> states the following;

         "If the server did not send the "versions" extension, or the version-from-list was not included, the
          server MAY send a status response describing the failure, but MUST then close the channel without
          processing any further requests."

         So what do you do if you have a client whose initial SSH_FXP_INIT packet says it implements v3 and
         a server whose initial SSH_FXP_VERSION reply says it implements v4 and only v4?  If it only implements
         v4, the "versions" extension is likely not going to have been sent so version re-negotiation as discussed
         in draft-ietf-secsh-filexfer-13 would be quite impossible.  As such, what Net_SFTP would do is close the
         channel and reopen it with a new and updated SSH_FXP_INIT packet.
        */
        switch ($this->version) {
            case 2:
            case 3:
                break;
            default:
                return false;
        }

        $this->pwd = $this->_realpath('.', false);

        $this->_save_dir($this->pwd);

        return true;
    }
767990
def add_custom_service_account(self, account, nickname, password):
        
        return self.post(
            url="https://api.weixin.qq.com/customservice/kfaccount/add",
            data={
                "kf_account": account,
                "nickname": nickname,
                "password": password
            }
        )
358947
public function get_online_kf_list ()
    {
        $url = self::API_DOMAIN . 'cgi-bin/customservice/getonlinekflist?access_token=' . $this->get_access_token();
        $res = HttpCurl::get($url, 'json');
        // 异常处理: 获取时网络错误
        if ($res === false) {
            return Error::code('ERR_GET');
        }
        // 判断是否调用成功
        if (isset($res->kf_online_list)) {
            return array(null, $res->kf_online_list);
        } else {
            return array($res, null);
        }
    }
317636
public function addUser(string $login_name, string $login_password, array $roles = []) : self
    {
        $this->users[$login_name]   = [
            'login_name'            => $login_name,
            'login_password'        => $login_password,
            'roles'                 => $roles,
        ];

        return $this;
    }
884162
public function set_kf_avatar($kf_account, $path)
    {
        $url = self::API_DOMAIN . 'customservice/kfaccount/uploadheadimg?access_token=' . $this->get_access_token() . '&kf_account=' . $kf_account;
        $res = HttpCurl::post($url, array('media' => '@'.$path), 'json');
        // 异常处理: 获取时网络错误
        if ($res === false) {
            return Error::code('ERR_GET');
        }
        // 判断是否调用成功        
        if ($res->errcode == 0) {
            return array(null, true);
        } else {
            return array($res, null);
        }
    }
177770
public function getSaldo(SEPAAccount $account)
    {
        $dialog = $this->getDialog();
        $dialog->syncDialog();
        $dialog->initDialog();

        switch ((int) $dialog->getHksalMaxVersion()) {
            case 4:
            case 5:
                $hksalAccount = new Deg(
                    $account->getAccountNumber(),
                    $account->getSubAccount(),
                    static::DEFAULT_COUNTRY_CODE, $account->getBlz()
                );
                $hksalAccount->addDataElement($account->getAccountNumber());
                $hksalAccount->addDataElement($account->getSubAccount());
                $hksalAccount->addDataElement(static::DEFAULT_COUNTRY_CODE);
                $hksalAccount->addDataElement($account->getBlz());
                break;
            case 6:
                $hksalAccount = new Ktv(
                    $account->getAccountNumber(),
                    $account->getSubAccount(),
                    new Kik(280, $account->getBlz())
                );
                break;
            case 7:
                $hksalAccount = new Kti(
                    $account->getIban(),
                    $account->getBic(),
                    $account->getAccountNumber(),
                    $account->getSubAccount(),
                    new Kik(280, $account->getBlz())
                );
                break;
            default:
                throw new \Exception('Unsupported HKSAL version: ' . $dialog->getHksalMaxVersion());
        }

        $message = new Message(
            $this->bankCode,
            $this->username,
            $this->pin,
            $dialog->getSystemId(),
            $dialog->getDialogId(),
            $dialog->getMessageNumber(),
            array(
                new HKSAL($dialog->getHksalMaxVersion(), 3, $hksalAccount, HKSAL::ALL_ACCOUNTS_N)
            ),
            array(
                AbstractMessage::OPT_PINTAN_MECH => $dialog->getSupportedPinTanMechanisms()
            )
        );

        $response = $dialog->sendMessage($message);
        $response = new GetSaldo($response->rawResponse);

        return $response->getSaldoModel();
    }
564744
public function auth(callable $fn, $retried = false)
    {
        // 1. 获取Access token
        $token = $this->getAccessTokenByAuth();
        if (!$token) {
            $this->code = -1;
            $this->message = '很抱歉,网络繁忙,请再试一次';

            return false;
        }

        // 2. 调用接口,转换异常为错误信息
        try {
            /** @var Http $http */
            $http = $fn();
        } catch (\Exception $e) {
            $this->code = -abs($e->getCode());
            $this->message = $e->getMessage();

            $http = \Miaoxing\App\Callback\Http::$lastErrorHttp;
            if ($http) {
                $this->logApi($http);
            }

            return false;
        }

        // 3. 成功直接返回
        if (!$this->isError($http)) {
            $this->code = 1;
            $this->message = '操作成功';

            return $http;
        }

        // 4. 处理接口返回错误
        // 如果是Access token无效或过期,清除缓存数据,然后重试一次
        if ($http['errcode'] == 40001 || $http['errcode'] == 42001) {
            $this->removeAccessTokenByAuth();
            if (!$retried) {
                $this->statsD->increment('wechat.credentialInvalid');

                return $this->auth($fn, true);
            }
        }
        $credential = $this->cache->get($this->getAccessTokenCacheKey());

        return $this->prepareError($http, $credential);
    }
1150510
public function deleteKFAccount($account){
        if (!$this->access_token && !$this->checkAuth()) return false;
        $result = $this->http_get(self::API_BASE_URL_PREFIX.self::CS_KF_ACCOUNT_DEL_URL.'access_token='.$this->access_token.'&kf_account='.$account);
        if ($result)
        {
            $json = json_decode($result,true);
            if (!$json || !empty($json['errcode'])) {
                $this->errCode = $json['errcode'];
                $this->errMsg = $json['errmsg'];
                return false;
            }
            return $json;
        }
        return false;
    }
736553
public function login($username, $password, \Espo\Entities\AuthToken $authToken = null, $params = [], $request)
    {
        if (!$password) return;

        $isPortal = !empty($params['isPortal']);

        if ($authToken) {
            return $this->loginByToken($username, $authToken);
        }

        if ($isPortal) {
            $useLdapAuthForPortalUser = $this->getUtils()->getOption('portalUserLdapAuth');
            if (!$useLdapAuthForPortalUser) {
                return parent::login($username, $password, $authToken, $params, $request);
            }
        }

        $ldapClient = $this->getLdapClient();

        /* Login LDAP system user (ldapUsername, ldapPassword) */
        try {
            $ldapClient->bind();
        } catch (\Exception $e) {
            $options = $this->getUtils()->getLdapClientOptions();
            $GLOBALS['log']->error('LDAP: Could not connect to LDAP server ['.$options['host'].'], details: ' . $e->getMessage());

            $adminUser = $this->adminLogin($username, $password);
            if (!isset($adminUser)) {
                return null;
            }

            $GLOBALS['log']->info('LDAP: Administrator ['.$username.'] was logged in by Espo method.');
        }

        if (!isset($adminUser)) {
            try {
                $userDn = $this->findLdapUserDnByUsername($username);
            } catch (\Exception $e) {
                $GLOBALS['log']->error('Error while finding DN for ['.$username.'], details: ' . $e->getMessage() . '.');
            }

            if (!isset($userDn)) {
                $GLOBALS['log']->error('LDAP: Authentication failed for user ['.$username.'], details: user is not found.');

                $adminUser = $this->adminLogin($username, $password);
                if (!isset($adminUser)) {
                    return null;
                }

                $GLOBALS['log']->info('LDAP: Administrator ['.$username.'] was logged in by Espo method.');
            }

            $GLOBALS['log']->debug('User ['.$username.'] is found with this DN ['.$userDn.'].');

            try {
                $ldapClient->bind($userDn, $password);
            } catch (\Exception $e) {
                $GLOBALS['log']->error('LDAP: Authentication failed for user ['.$username.'], details: ' . $e->getMessage());
                return null;
            }
        }

        $user = $this->getEntityManager()->getRepository('User')->findOne([
            'whereClause' => [
                'userName' => $username,
                'type!=' => ['api', 'system']
            ]
        ]);

        if (!isset($user) && $this->getUtils()->getOption('createEspoUser')) {
            $userData = $ldapClient->getEntry($userDn);
            $user = $this->createUser($userData, $isPortal);
        }

        return $user;
    }
934356
public function signIn($msisdn = '', $password = '')
    {
        try {
            $inputParams = [
                'msisdn'   => $msisdn,
                'password' => $password
            ];
            $this->debug->info(__FUNCTION__, 'Input Params: ' . json_encode($inputParams));
            // Sử dụng API: /api/v1/utils/user/signin.html
            $loginUrl       = $this->hostname . $this->config[self::API_LOGIN]['path'];
            $loginToken     = $this->config[self::API_LOGIN]['token'];
            $loginPrefix    = $this->config[self::API_LOGIN]['prefix'];
            $loginSignature = md5($msisdn . $loginPrefix . $password . $loginPrefix . $loginToken);
            $loginParams    = [
                'msisdn'    => $msisdn,
                'password'  => $password,
                'signature' => $loginSignature
            ];
            $this->debug->debug(__FUNCTION__, '$loginUrl: ' . json_encode($loginUrl));
            $this->debug->debug(__FUNCTION__, '$loginToken: ' . json_encode($loginToken));
            $this->debug->debug(__FUNCTION__, '$loginPrefix: ' . json_encode($loginPrefix));
            $this->debug->debug(__FUNCTION__, '$loginSignature: ' . json_encode($loginSignature));
            $this->debug->debug(__FUNCTION__, '$loginParams: ' . json_encode($loginParams));
            $loginRequest = $this->requests->sendRequest($loginUrl, $loginParams, 'GET');
            $this->debug->info(__FUNCTION__, '$loginRequest: ' . json_encode($loginRequest));
            if ($loginRequest !== NULL) {
                if ($this->nativeSession === TRUE) {
                    $this->debug->info(__FUNCTION__, 'set native Session');
                    $phone_number = new Phone_number();
                    $phone_number->setDebugStatus($this->options['debugStatus']);
                    $phone_number->setLoggerPath($this->options['debugLoggerPath']);
                    $phone_number->__construct();
                    $this->debug->info(__FUNCTION__, '$phone_number: ' . json_encode($phone_number));
                    $isMsisdn = $phone_number->format($msisdn);
                    $this->debug->info(__FUNCTION__, '$isMsisdn: ' . json_encode($isMsisdn));
                    Session::setUserdata(self::SESSION_MSISDN_DEFAULT, $isMsisdn);
                }

                return json_decode(trim($loginRequest));
            } else {
                $this->debug->warning(__FUNCTION__, 'Request Response is Empty');

                return FALSE;
            }
        }
        catch (\Exception $e) {
            $message = 'Error File: ' . $e->getFile() . ' - Line: ' . $e->getLine() . ' - Code: ' . $e->getCode() . ' - Message: ' . $e->getMessage();
            $this->debug->error(__FUNCTION__, $message);

            return NULL;
        }
    }
1234371
def update_custom_service_account(self, account, nickname, password):
        
        return self.post(
            url="https://api.weixin.qq.com/customservice/kfaccount/update",
            data={
                "kf_account": account,
                "nickname": nickname,
                "password": password
            }
        )
408638
public function addKFAccount($account, $nickname, $password)
    {
        $data = array("kf_account" => $account, "nickname" => $nickname, "password" => md5($password));
        if (!$this->access_token && !$this->getAccessToken()) {
            return false;
        }
        $result = Tools::httpPost(self::API_BASE_URL_PREFIX . self::CS_KF_ACCOUNT_ADD_URL . "access_token={$this->access_token}", Tools::json_encode($data));
        if ($result) {
            $json = json_decode($result, true);
            if (empty($json) || !empty($json['errcode'])) {
                $this->errCode = isset($json['errcode']) ? $json['errcode'] : '505';
                $this->errMsg = isset($json['errmsg']) ? $json['errmsg'] : '无法解析接口返回内容！';
                return $this->checkRetry(__FUNCTION__, func_get_args());
            }
            return $json;
        }
        return false;
    }
1367923
public static function analyze($password)
	{
		$stats = array();

		$len = strlen($password);

		$stats['count'][0] = $len;
		$stats['uniqueCharacters'] = 0;
		$stats['uniqueClasses']    = 0;

		$classes   = array();
		$histogram = array();

		for ($i = 0; $i < $len; $i++)
		{
			$c = $password[$i];

			$cl = CharacterClass::match($c);

			foreach ($cl as $class)
			{
				if (empty($stats['count'][$class->name]))
				{
					$stats['count'][$class->name] = 1;
					if ($class->flag)
					{
						$stats['uniqueClasses']++;
					}
				}
				else
				{
					$stats['count'][$class->name]++;
				}
			}

			if (empty($histogram[$c]))
			{
				$histogram[$c] = 1;
				$stats['uniqueCharacters']++;
			}
			else
			{
				$histogram[$c]++;
			}
		}

		return $stats;
	}
821636
public function create(string $account, string $nickname)
    {
        $params = [
            'kf_account' => $account,
            'nickname' => $nickname,
        ];

        return $this->httpPostJson('customservice/kfaccount/add', $params);
    }
179704
function set_nickname($username , $nickname){
        if(empty($nickname) || empty($username)){
            return false;
        }
        $token = $this->get_token();
        $header = array('Authorization: Bearer '.$token);
        $path = 'users/'.$username;
        $request_body = array("nickname"=>$nickname);
        $result = $this->api_request($path, json_encode($request_body,JSON_UNESCAPED_UNICODE) , 'PUT', $header);
        return $this->result_handler($result);
    }