def Bahadori_liquid(T, M):
    r'''Estimates the thermal conductivity of parafin liquid hydrocarbons.
    Fits their data well, and is useful as only MW is required.
    X is the Molecular weight, and Y the temperature.

    .. math::
        K = a + bY + CY^2 + dY^3

        a = A_1 + B_1 X + C_1 X^2 + D_1 X^3

        b = A_2 + B_2 X + C_2 X^2 + D_2 X^3

        c = A_3 + B_3 X + C_3 X^2 + D_3 X^3

        d = A_4 + B_4 X + C_4 X^2 + D_4 X^3

    Parameters
    ----------
    T : float
        Temperature of the fluid [K]
    M : float
        Molecular weight of the fluid [g/mol]

    Returns
    -------
    kl : float
        Estimated liquid thermal conductivity [W/m/k]

    Notes
    -----
    The accuracy of this equation has not been reviewed.

    Examples
    --------
    Data point from [1]_.

    >>> Bahadori_liquid(273.15, 170)
    0.14274278108272603

    References
    ----------
    .. [1] Bahadori, Alireza, and Saeid Mokhatab. "Estimating Thermal
       Conductivity of Hydrocarbons." Chemical Engineering 115, no. 13
       (December 2008): 52-54
    '''
    A = [-6.48326E-2, 2.715015E-3, -1.08580E-5, 9.853917E-9]
    B = [1.565612E-2, -1.55833E-4, 5.051114E-7, -4.68030E-10]
    C = [-1.80304E-4, 1.758693E-6, -5.55224E-9, 5.201365E-12]
    D = [5.880443E-7, -5.65898E-9, 1.764384E-11, -1.65944E-14]
    X, Y = M, T
    a = A[0] + B[0]*X + C[0]*X**2 + D[0]*X**3
    b = A[1] + B[1]*X + C[1]*X**2 + D[1]*X**3
    c = A[2] + B[2]*X + C[2]*X**2 + D[2]*X**3
    d = A[3] + B[3]*X + C[3]*X**2 + D[3]*X**3
    return a + b*Y + c*Y**2 + d*Y**3

r Estimates the thermal conductivity of parafin liquid hydrocarbons . Fits their data well and is useful as only MW is required . X is the Molecular weight and Y the temperature .


r'''Estimates the thermal conductivity of hydrocarbons gases at low P.
    Fits their data well, and is useful as only MW is required.
    Y is the Molecular weight, and X the temperature.

    .. math::
        K = a + bY + CY^2 + dY^3

        a = A_1 + B_1 X + C_1 X^2 + D_1 X^3

        b = A_2 + B_2 X + C_2 X^2 + D_2 X^3

        c = A_3 + B_3 X + C_3 X^2 + D_3 X^3

        d = A_4 + B_4 X + C_4 X^2 + D_4 X^3

    Parameters
    ----------
    T : float
        Temperature of the gas [K]
    MW : float
        Molecular weight of the gas [g/mol]

    Returns
    -------
    kg : float
        Estimated gas thermal conductivity [W/m/k]

    Notes
    -----
    The accuracy of this equation has not been reviewed.

    Examples
    --------
    >>> Bahadori_gas(40+273.15, 20) # Point from article
    0.031968165337873326

    References
    ----------
    .. [1] Bahadori, Alireza, and Saeid Mokhatab. "Estimating Thermal
       Conductivity of Hydrocarbons." Chemical Engineering 115, no. 13
       (December 2008): 52-54

r Estimates the thermal conductivity of hydrocarbons gases at low P . Fits their data well and is useful as only MW is required . Y is the Molecular weight and X the temperature .


def calculate(self, T, method):
        r'''Method to calculate low-pressure liquid thermal conductivity at
        tempearture `T` with a given method.

        This method has no exception handling; see `T_dependent_property`
        for that.

        Parameters
        ----------
        T : float
            Temperature of the liquid, [K]
        method : str
            Name of the method to use

        Returns
        -------
        kl : float
            Thermal conductivity of the liquid at T and a low pressure, [W/m/K]
        '''
        if method == SHEFFY_JOHNSON:
            kl = Sheffy_Johnson(T, self.MW, self.Tm)
        elif method == SATO_RIEDEL:
            kl = Sato_Riedel(T, self.MW, self.Tb, self.Tc)
        elif method == GHARAGHEIZI_L:
            kl = Gharagheizi_liquid(T, self.MW, self.Tb, self.Pc, self.omega)
        elif method == NICOLA:
            kl = Nicola(T, self.MW, self.Tc, self.Pc, self.omega)
        elif method == NICOLA_ORIGINAL:
            kl = Nicola_original(T, self.MW, self.Tc, self.omega, self.Hfus)
        elif method == LAKSHMI_PRASAD:
            kl = Lakshmi_Prasad(T, self.MW)
        elif method == BAHADORI_L:
            kl = Bahadori_liquid(T, self.MW)
        elif method == DIPPR_PERRY_8E:
            kl = EQ100(T, *self.Perrys2_315_coeffs)
        elif method == VDI_PPDS:
            kl = horner(self.VDI_PPDS_coeffs, T)
        elif method == COOLPROP:
            kl = CoolProp_T_dependent_property(T, self.CASRN, 'L', 'l')
        elif method in self.tabular_data:
            kl = self.interpolate(T, method)
        return kl

def calculate ( self , T , method ) : r if method == SHEFFY_JOHNSON : kl = Sheffy_Johnson ( T , self . MW , self . Tm ) elif method == SATO_RIEDEL : kl = Sato_Riedel ( T , self . MW , self . Tb , self . Tc ) elif method == GHARAGHEIZI_L : kl = Gharagheizi_liquid ( T , self . MW , self . Tb , self . Pc , self . omega ) elif method == NICOLA : kl = Nicola ( T , self . MW , self . Tc , self . Pc , self . omega ) elif method == NICOLA_ORIGINAL : kl = Nicola_original ( T , self . MW , self . Tc , self . omega , self . Hfus ) elif method == LAKSHMI_PRASAD : kl = Lakshmi_Prasad ( T , self . MW ) elif method == BAHADORI_L : kl = Bahadori_liquid ( T , self . MW ) elif method == DIPPR_PERRY_8E : kl = EQ100 ( T , * self . Perrys2_315_coeffs ) elif method == VDI_PPDS : kl = horner ( self . VDI_PPDS_coeffs , T ) elif method == COOLPROP : kl = CoolProp_T_dependent_property ( T , self . CASRN , 'L' , 'l' ) elif method in self . tabular_data : kl = self . interpolate ( T , method ) return kl

r Method to calculate low - pressure liquid thermal conductivity at tempearture T with a given method .


def calculate_P(self, T, P, method):
        r'''Method to calculate pressure-dependent liquid thermal conductivity
        at temperature `T` and pressure `P` with a given method.

        This method has no exception handling; see `TP_dependent_property`
        for that.

        Parameters
        ----------
        T : float
            Temperature at which to calculate liquid thermal conductivity, [K]
        P : float
            Pressure at which to calculate liquid thermal conductivity, [K]
        method : str
            Name of the method to use

        Returns
        -------
        kl : float
            Thermal conductivity of the liquid at T and P, [W/m/K]
        '''
        if method == DIPPR_9G:
            kl = self.T_dependent_property(T)
            kl = DIPPR9G(T, P, self.Tc, self.Pc, kl)
        elif method == MISSENARD:
            kl = self.T_dependent_property(T)
            kl = Missenard(T, P, self.Tc, self.Pc, kl)
        elif method == COOLPROP:
            kl = PropsSI('L', 'T', T, 'P', P, self.CASRN)
        elif method in self.tabular_data:
            kl = self.interpolate_P(T, P, method)
        return kl

def calculate_P ( self , T , P , method ) : r if method == DIPPR_9G : kl = self . T_dependent_property ( T ) kl = DIPPR9G ( T , P , self . Tc , self . Pc , kl ) elif method == MISSENARD : kl = self . T_dependent_property ( T ) kl = Missenard ( T , P , self . Tc , self . Pc , kl ) elif method == COOLPROP : kl = PropsSI ( 'L' , 'T' , T , 'P' , P , self . CASRN ) elif method in self . tabular_data : kl = self . interpolate_P ( T , P , method ) return kl

r Method to calculate pressure - dependent liquid thermal conductivity at temperature T and pressure P with a given method .


def calculate(self, T, P, zs, ws, method):
        r'''Method to calculate thermal conductivity of a liquid mixture at
        temperature `T`, pressure `P`, mole fractions `zs` and weight fractions
        `ws` with a given method.

        This method has no exception handling; see `mixture_property`
        for that.

        Parameters
        ----------
        T : float
            Temperature at which to calculate the property, [K]
        P : float
            Pressure at which to calculate the property, [Pa]
        zs : list[float]
            Mole fractions of all species in the mixture, [-]
        ws : list[float]
            Weight fractions of all species in the mixture, [-]
        method : str
            Name of the method to use

        Returns
        -------
        k : float
            Thermal conductivity of the liquid mixture, [W/m/K]
        '''
        if method == SIMPLE:
            ks = [i(T, P) for i in self.ThermalConductivityLiquids]
            return mixing_simple(zs, ks)
        elif method == DIPPR_9H:
            ks = [i(T, P) for i in self.ThermalConductivityLiquids]
            return DIPPR9H(ws, ks)
        elif method == FILIPPOV:
            ks = [i(T, P) for i in self.ThermalConductivityLiquids]
            return Filippov(ws, ks)
        elif method == MAGOMEDOV:
            k_w = self.ThermalConductivityLiquids[self.index_w](T, P)
            ws = list(ws) ; ws.pop(self.index_w)
            return thermal_conductivity_Magomedov(T, P, ws, self.wCASs, k_w)
        else:
            raise Exception('Method not valid')

def
calculate
(
self
,
T
,
P
,
zs
,
ws
,
method
)
:
r
if
method
==
SIMPLE
:
ks
=
[
i
(
T
,
P
)
for
i
in
self
.
ThermalConductivityLiquids
]
return
mixing_simple
(
zs
,
ks
)
elif
method
==
DIPPR_9H
:
ks
=
[
i
(
T
,
P
)
for
i
in
self
.
ThermalConductivityLiquids
]
return
DIPPR9H
(
ws
,
ks
)
elif
method
==
FILIPPOV
:
ks
=
[
i
(
T
,
P
)
for
i
in
self
.
ThermalConductivityLiquids
]
return
Filippov
(
ws
,
ks
)
elif
method
==
MAGOMEDOV
:
k_w
=
self
.
ThermalConductivityLiquids
[
self
.
index_w
]
(
T
,
P
)
ws
=
list
(
ws
)
ws
.
pop
(
self
.
index_w
)
return
thermal_conductivity_Magomedov
(
T
,
P
,
ws
,
self
.
wCASs
,
k_w
)
else
:
raise
Exception
(
'Method not valid'
)

r Method to calculate thermal conductivity of a liquid mixture at temperature T pressure P mole fractions zs and weight fractions ws with a given method .


def calculate(self, T, method):
        r'''Method to calculate low-pressure gas thermal conductivity at
        tempearture `T` with a given method.

        This method has no exception handling; see `T_dependent_property`
        for that.

        Parameters
        ----------
        T : float
            Temperature of the gas, [K]
        method : str
            Name of the method to use

        Returns
        -------
        kg : float
            Thermal conductivity of the gas at T and a low pressure, [W/m/K]
        '''
        if method == GHARAGHEIZI_G:
            kg = Gharagheizi_gas(T, self.MW, self.Tb, self.Pc, self.omega)
        elif method == DIPPR_9B:
            Cvgm = self.Cvgm(T) if hasattr(self.Cvgm, '__call__') else self.Cvgm
            mug = self.mug(T) if hasattr(self.mug, '__call__') else self.mug
            kg = DIPPR9B(T, self.MW, Cvgm, mug, self.Tc)
        elif method == CHUNG:
            Cvgm = self.Cvgm(T) if hasattr(self.Cvgm, '__call__') else self.Cvgm
            mug = self.mug(T) if hasattr(self.mug, '__call__') else self.mug
            kg = Chung(T, self.MW, self.Tc, self.omega, Cvgm, mug)
        elif method == ELI_HANLEY:
            Cvgm = self.Cvgm(T) if hasattr(self.Cvgm, '__call__') else self.Cvgm
            kg = eli_hanley(T, self.MW, self.Tc, self.Vc, self.Zc, self.omega, Cvgm)
        elif method == EUCKEN_MOD:
            Cvgm = self.Cvgm(T) if hasattr(self.Cvgm, '__call__') else self.Cvgm
            mug = self.mug(T) if hasattr(self.mug, '__call__') else self.mug
            kg = Eucken_modified(self.MW, Cvgm, mug)
        elif method == EUCKEN:
            Cvgm = self.Cvgm(T) if hasattr(self.Cvgm, '__call__') else self.Cvgm
            mug = self.mug(T) if hasattr(self.mug, '__call__') else self.mug
            kg = Eucken(self.MW, Cvgm, mug)
        elif method == DIPPR_PERRY_8E:
            kg = EQ102(T, *self.Perrys2_314_coeffs)
        elif method == VDI_PPDS:
            kg = horner(self.VDI_PPDS_coeffs, T)
        elif method == BAHADORI_G:
            kg = Bahadori_gas(T, self.MW)
        elif method == COOLPROP:
            kg = CoolProp_T_dependent_property(T, self.CASRN, 'L', 'g')
        elif method in self.tabular_data:
            kg = self.interpolate(T, method)
        return kg

def calculate ( self , T , method ) : r if method == GHARAGHEIZI_G : kg = Gharagheizi_gas ( T , self . MW , self . Tb , self . Pc , self . omega ) elif method == DIPPR_9B : Cvgm = self . Cvgm ( T ) if hasattr ( self . Cvgm , '__call__' ) else self . Cvgm mug = self . mug ( T ) if hasattr ( self . mug , '__call__' ) else self . mug kg = DIPPR9B ( T , self . MW , Cvgm , mug , self . Tc ) elif method == CHUNG : Cvgm = self . Cvgm ( T ) if hasattr ( self . Cvgm , '__call__' ) else self . Cvgm mug = self . mug ( T ) if hasattr ( self . mug , '__call__' ) else self . mug kg = Chung ( T , self . MW , self . Tc , self . omega , Cvgm , mug ) elif method == ELI_HANLEY : Cvgm = self . Cvgm ( T ) if hasattr ( self . Cvgm , '__call__' ) else self . Cvgm kg = eli_hanley ( T , self . MW , self . Tc , self . Vc , self . Zc , self . omega , Cvgm ) elif method == EUCKEN_MOD : Cvgm = self . Cvgm ( T ) if hasattr ( self . Cvgm , '__call__' ) else self . Cvgm mug = self . mug ( T ) if hasattr ( self . mug , '__call__' ) else self . mug kg = Eucken_modified ( self . MW , Cvgm , mug ) elif method == EUCKEN : Cvgm = self . Cvgm ( T ) if hasattr ( self . Cvgm , '__call__' ) else self . Cvgm mug = self . mug ( T ) if hasattr ( self . mug , '__call__' ) else self . mug kg = Eucken ( self . MW , Cvgm , mug ) elif method == DIPPR_PERRY_8E : kg = EQ102 ( T , * self . Perrys2_314_coeffs ) elif method == VDI_PPDS : kg = horner ( self . VDI_PPDS_coeffs , T ) elif method == BAHADORI_G : kg = Bahadori_gas ( T , self . MW ) elif method == COOLPROP : kg = CoolProp_T_dependent_property ( T , self . CASRN , 'L' , 'g' ) elif method in self . tabular_data : kg = self . interpolate ( T , method ) return kg

r Method to calculate low - pressure gas thermal conductivity at tempearture T with a given method .


def calculate_P(self, T, P, method):
        r'''Method to calculate pressure-dependent gas thermal conductivity
        at temperature `T` and pressure `P` with a given method.

        This method has no exception handling; see `TP_dependent_property`
        for that.

        Parameters
        ----------
        T : float
            Temperature at which to calculate gas thermal conductivity, [K]
        P : float
            Pressure at which to calculate gas thermal conductivity, [K]
        method : str
            Name of the method to use

        Returns
        -------
        kg : float
            Thermal conductivity of the gas at T and P, [W/m/K]
        '''
        if method == ELI_HANLEY_DENSE:
            Vmg = self.Vmg(T, P) if hasattr(self.Vmg, '__call__') else self.Vmg
            Cvgm = self.Cvgm(T) if hasattr(self.Cvgm, '__call__') else self.Cvgm
            kg = eli_hanley_dense(T, self.MW, self.Tc, self.Vc, self.Zc, self.omega, Cvgm, Vmg)
        elif method == CHUNG_DENSE:
            Vmg = self.Vmg(T, P) if hasattr(self.Vmg, '__call__') else self.Vmg
            Cvgm = self.Cvgm(T) if hasattr(self.Cvgm, '__call__') else self.Cvgm
            mug = self.mug(T, P) if hasattr(self.mug, '__call__') else self.mug
            kg = chung_dense(T, self.MW, self.Tc, self.Vc, self.omega, Cvgm, Vmg, mug, self.dipole)
        elif method == STIEL_THODOS_DENSE:
            kg = self.T_dependent_property(T)
            Vmg = self.Vmg(T, P) if hasattr(self.Vmg, '__call__') else self.Vmg
            kg = stiel_thodos_dense(T, self.MW, self.Tc, self.Pc, self.Vc, self.Zc, Vmg, kg)
        elif method == COOLPROP:
            kg = PropsSI('L', 'T', T, 'P', P, self.CASRN)
        elif method in self.tabular_data:
            kg = self.interpolate_P(T, P, method)
        return kg

def calculate_P ( self , T , P , method ) : r if method == ELI_HANLEY_DENSE : Vmg = self . Vmg ( T , P ) if hasattr ( self . Vmg , '__call__' ) else self . Vmg Cvgm = self . Cvgm ( T ) if hasattr ( self . Cvgm , '__call__' ) else self . Cvgm kg = eli_hanley_dense ( T , self . MW , self . Tc , self . Vc , self . Zc , self . omega , Cvgm , Vmg ) elif method == CHUNG_DENSE : Vmg = self . Vmg ( T , P ) if hasattr ( self . Vmg , '__call__' ) else self . Vmg Cvgm = self . Cvgm ( T ) if hasattr ( self . Cvgm , '__call__' ) else self . Cvgm mug = self . mug ( T , P ) if hasattr ( self . mug , '__call__' ) else self . mug kg = chung_dense ( T , self . MW , self . Tc , self . Vc , self . omega , Cvgm , Vmg , mug , self . dipole ) elif method == STIEL_THODOS_DENSE : kg = self . T_dependent_property ( T ) Vmg = self . Vmg ( T , P ) if hasattr ( self . Vmg , '__call__' ) else self . Vmg kg = stiel_thodos_dense ( T , self . MW , self . Tc , self . Pc , self . Vc , self . Zc , Vmg , kg ) elif method == COOLPROP : kg = PropsSI ( 'L' , 'T' , T , 'P' , P , self . CASRN ) elif method in self . tabular_data : kg = self . interpolate_P ( T , P , method ) return kg

r Method to calculate pressure - dependent gas thermal conductivity at temperature T and pressure P with a given method .


def calculate(self, T, P, zs, ws, method):
        r'''Method to calculate thermal conductivity of a gas mixture at
        temperature `T`, pressure `P`, mole fractions `zs` and weight fractions
        `ws` with a given method.

        This method has no exception handling; see `mixture_property`
        for that.

        Parameters
        ----------
        T : float
            Temperature at which to calculate the property, [K]
        P : float
            Pressure at which to calculate the property, [Pa]
        zs : list[float]
            Mole fractions of all species in the mixture, [-]
        ws : list[float]
            Weight fractions of all species in the mixture, [-]
        method : str
            Name of the method to use

        Returns
        -------
        kg : float
            Thermal conductivity of gas mixture, [W/m/K]
        '''
        if method == SIMPLE:
            ks = [i(T, P) for i in self.ThermalConductivityGases]
            return mixing_simple(zs, ks)
        elif method == LINDSAY_BROMLEY:
            ks = [i(T, P) for i in self.ThermalConductivityGases]
            mus = [i(T, P) for i in self.ViscosityGases]
            return Lindsay_Bromley(T=T, ys=zs, ks=ks, mus=mus, Tbs=self.Tbs, MWs=self.MWs)
        else:
            raise Exception('Method not valid')

def
calculate
(
self
,
T
,
P
,
zs
,
ws
,
method
)
:
r
if
method
==
SIMPLE
:
ks
=
[
i
(
T
,
P
)
for
i
in
self
.
ThermalConductivityGases
]
return
mixing_simple
(
zs
,
ks
)
elif
method
==
LINDSAY_BROMLEY
:
ks
=
[
i
(
T
,
P
)
for
i
in
self
.
ThermalConductivityGases
]
mus
=
[
i
(
T
,
P
)
for
i
in
self
.
ViscosityGases
]
return
Lindsay_Bromley
(
T
=
T
,
ys
=
zs
,
ks
=
ks
,
mus
=
mus
,
Tbs
=
self
.
Tbs
,
MWs
=
self
.
MWs
)
else
:
raise
Exception
(
'Method not valid'
)

r Method to calculate thermal conductivity of a gas mixture at temperature T pressure P mole fractions zs and weight fractions ws with a given method .


def nested_formula_parser(formula, check=True):
    r'''Improved formula parser which handles braces and their multipliers,
    as well as rational element counts.

    Strips charges from the end of a formula first. Accepts repeated chemical
    units. Performs no sanity checking that elements are actually elements.
    As it uses regular expressions for matching, errors are mostly just ignored.

    Parameters
    ----------
    formula : str
        Formula string, very simply formats only.
    check : bool
        If `check` is True, a simple check will be performed to determine if
        a formula is not a formula and an exception will be raised if it is
        not, [-]

    Returns
    -------
    atoms : dict
        dictionary of counts of individual atoms, indexed by symbol with
        proper capitalization, [-]

    Notes
    -----
    Inspired by the approach taken by CrazyMerlyn on a reddit DailyProgrammer
    challenge, at https://www.reddit.com/r/dailyprogrammer/comments/6eerfk/20170531_challenge_317_intermediate_counting/

    Examples
    --------
    >>> pprint(nested_formula_parser('Pd(NH3)4.0001+2'))
    {'H': 12.0003, 'N': 4.0001, 'Pd': 1}
    '''
    formula = formula.replace('[', '').replace(']', '')
    charge_splits = bracketed_charge_re.split(formula)
    if len(charge_splits) > 1:
        formula = charge_splits[0]
    else:
        formula = formula.split('+')[0].split('-')[0]

    stack = [[]]
    last = stack[0]
    tokens = formula_token_matcher_rational.findall(formula)
    # The set of letters in the tokens should match the set of letters
    if check:
        token_letters = set([j for i in tokens for j in i if j in letter_set])
        formula_letters = set(i for i in formula if i in letter_set)
        if formula_letters != token_letters:
            raise Exception('Input may not be a formula; extra letters were detected')

    for token in tokens:
        if token == "(":
            stack.append([])
            last = stack[-1]
        elif token == ")":
            temp_dict = {}
            for d in last:
                for ele, count in d.items():
                    if ele in temp_dict:
                        temp_dict[ele] = temp_dict[ele] + count
                    else:
                        temp_dict[ele] = count
            stack.pop()
            last = stack[-1]
            last.append(temp_dict)
        elif token.isalpha():
            last.append({token: 1})
        else:
            v = float(token)
            v_int = int(v)
            if v_int == v:
                v = v_int
            last[-1] = {ele: count*v for ele, count in last[-1].items()}
    ans = {}
    for d in last:
        for ele, count in d.items():
            if ele in ans:
                ans[ele] = ans[ele] + count
            else:
                ans[ele] = count
    return ans

r Improved formula parser which handles braces and their multipliers as well as rational element counts .


def charge_from_formula(formula):
    r'''Basic formula parser to determine the charge from a formula - given
    that the charge is already specified as one element of the formula.

    Performs no sanity checking that elements are actually elements.

    Parameters
    ----------
    formula : str
        Formula string, very simply formats only, ending in one of '+x',
        '-x', n*'+', or n*'-' or any of them surrounded by brackets but always
        at the end of a formula.

    Returns
    -------
    charge : int
        Charge of the molecule, [faraday]

    Notes
    -----

    Examples
    --------
    >>> charge_from_formula('Br3-')
    -1
    >>> charge_from_formula('Br3(-)')
    -1
    '''
    negative = '-' in formula
    positive = '+' in formula
    if positive and negative:
        raise ValueError('Both negative and positive signs were found in the formula; only one sign is allowed')
    elif not (positive or negative):
        return 0
    multiplier, sign = (-1, '-') if negative else (1, '+')

    hit = False
    if '(' in formula:
        hit = bracketed_charge_re.findall(formula)
        if hit:
            formula = hit[-1].replace('(', '').replace(')', '')

    count = formula.count(sign)
    if count == 1:
        splits = formula.split(sign)
        if splits[1] == '' or splits[1] == ')':
            return multiplier
        else:
            return multiplier*int(splits[1])
    else:
        return multiplier*count

r Basic formula parser to determine the charge from a formula - given that the charge is already specified as one element of the formula .


def serialize_formula(formula):
    r'''Basic formula serializer to construct a consistently-formatted formula.
    This is necessary for handling user-supplied formulas, which are not always
    well formatted.

    Performs no sanity checking that elements are actually elements.

    Parameters
    ----------
    formula : str
        Formula string as parseable by the method nested_formula_parser, [-]

    Returns
    -------
    formula : str
        A consistently formatted formula to describe a molecular formula, [-]

    Notes
    -----

    Examples
    --------
    >>> serialize_formula('Pd(NH3)4+3')
    'H12N4Pd+3'
    '''
    charge = charge_from_formula(formula)
    element_dict = nested_formula_parser(formula)
    base = atoms_to_Hill(element_dict)
    if charge  == 0:
        pass
    elif charge > 0:
        if charge == 1:
            base += '+'
        else:
            base += '+' + str(charge)
    elif charge < 0:
        if charge == -1:
            base += '-'
        else:
            base +=  str(charge)
    return base

def serialize_formula ( formula ) : r charge = charge_from_formula ( formula ) element_dict = nested_formula_parser ( formula ) base = atoms_to_Hill ( element_dict ) if charge == 0 : pass elif charge > 0 : if charge == 1 : base += '+' else : base += '+' + str ( charge ) elif charge < 0 : if charge == - 1 : base += '-' else : base += str ( charge ) return base

r Basic formula serializer to construct a consistently - formatted formula . This is necessary for handling user - supplied formulas which are not always well formatted .


async def connect(self):
        """Establish a connection to the chat server.

        Returns when an error has occurred, or :func:`disconnect` has been
        called.
        """
        proxy = os.environ.get('HTTP_PROXY')
        self._session = http_utils.Session(self._cookies, proxy=proxy)
        try:
            self._channel = channel.Channel(
                self._session, self._max_retries, self._retry_backoff_base
            )

            # Forward the Channel events to the Client events.
            self._channel.on_connect.add_observer(self.on_connect.fire)
            self._channel.on_reconnect.add_observer(self.on_reconnect.fire)
            self._channel.on_disconnect.add_observer(self.on_disconnect.fire)
            self._channel.on_receive_array.add_observer(self._on_receive_array)

            # Wrap the coroutine in a Future so it can be cancelled.
            self._listen_future = asyncio.ensure_future(self._channel.listen())
            # Listen for StateUpdate messages from the Channel until it
            # disconnects.
            try:
                await self._listen_future
            except asyncio.CancelledError:
                # If this task is cancelled, we need to cancel our child task
                # as well. We don't need an additional yield because listen
                # cancels immediately.
                self._listen_future.cancel()
            logger.info(
                'Client.connect returning because Channel.listen returned'
            )
        finally:
            await self._session.close()

Establish a connection to the chat server .


def get_request_header(self):
        """Return ``request_header`` for use when constructing requests.

        Returns:
            Populated request header.
        """
        # resource is allowed to be null if it's not available yet (the Chrome
        # client does this for the first getentitybyid call)
        if self._client_id is not None:
            self._request_header.client_identifier.resource = self._client_id
        return self._request_header

def get_request_header ( self ) : if self . _client_id is not None : self . _request_header . client_identifier . resource = self . _client_id return self . _request_header

Return request_header for use when constructing requests .


async def set_active(self):
        """Set this client as active.

        While a client is active, no other clients will raise notifications.
        Call this method whenever there is an indication the user is
        interacting with this client. This method may be called very
        frequently, and it will only make a request when necessary.
        """
        is_active = (self._active_client_state ==
                     hangouts_pb2.ACTIVE_CLIENT_STATE_IS_ACTIVE)
        timed_out = (time.time() - self._last_active_secs >
                     SETACTIVECLIENT_LIMIT_SECS)
        if not is_active or timed_out:
            # Update these immediately so if the function is called again
            # before the API request finishes, we don't start extra requests.
            self._active_client_state = (
                hangouts_pb2.ACTIVE_CLIENT_STATE_IS_ACTIVE
            )
            self._last_active_secs = time.time()

            # The first time this is called, we need to retrieve the user's
            # email address.
            if self._email is None:
                try:
                    get_self_info_request = hangouts_pb2.GetSelfInfoRequest(
                        request_header=self.get_request_header(),
                    )
                    get_self_info_response = await self.get_self_info(
                        get_self_info_request
                    )
                except exceptions.NetworkError as e:
                    logger.warning('Failed to find email address: {}'
                                   .format(e))
                    return
                self._email = (
                    get_self_info_response.self_entity.properties.email[0]
                )

            # If the client_id hasn't been received yet, we can't set the
            # active client.
            if self._client_id is None:
                logger.info(
                    'Cannot set active client until client_id is received'
                )
                return

            try:
                set_active_request = hangouts_pb2.SetActiveClientRequest(
                    request_header=self.get_request_header(),
                    is_active=True,
                    full_jid="{}/{}".format(self._email, self._client_id),
                    timeout_secs=ACTIVE_TIMEOUT_SECS,
                )
                await self.set_active_client(set_active_request)
            except exceptions.NetworkError as e:
                logger.warning('Failed to set active client: {}'.format(e))
            else:
                logger.info('Set active client for {} seconds'
                            .format(ACTIVE_TIMEOUT_SECS))


async def upload_image(self, image_file, filename=None, *,
                           return_uploaded_image=False):
        """Upload an image that can be later attached to a chat message.

        Args:
            image_file: A file-like object containing an image.
            filename (str): (optional) Custom name for the uploaded file.
            return_uploaded_image (bool): (optional) If True, return
                :class:`.UploadedImage` instead of image ID. Defaults to False.

        Raises:
            hangups.NetworkError: If the upload request failed.

        Returns:
            :class:`.UploadedImage` instance, or ID of the uploaded image.
        """
        image_filename = filename or os.path.basename(image_file.name)
        image_data = image_file.read()

        # request an upload URL
        res = await self._base_request(
            IMAGE_UPLOAD_URL,
            'application/x-www-form-urlencoded;charset=UTF-8', 'json',
            json.dumps({
                "protocolVersion": "0.8",
                "createSessionRequest": {
                    "fields": [{
                        "external": {
                            "name": "file",
                            "filename": image_filename,
                            "put": {},
                            "size": len(image_data)
                        }
                    }]
                }
            })
        )

        try:
            upload_url = self._get_upload_session_status(res)[
                'externalFieldTransfers'
            ][0]['putInfo']['url']
        except KeyError:
            raise exceptions.NetworkError(
                'image upload failed: can not acquire an upload url'
            )

        # upload the image data using the upload_url to get the upload info
        res = await self._base_request(
            upload_url, 'application/octet-stream', 'json', image_data
        )

        try:
            raw_info = (
                self._get_upload_session_status(res)['additionalInfo']
                ['uploader_service.GoogleRupioAdditionalInfo']
                ['completionInfo']['customerSpecificInfo']
            )
            image_id = raw_info['photoid']
            url = raw_info['url']
        except KeyError:
            raise exceptions.NetworkError(
                'image upload failed: can not fetch upload info'
            )

        result = UploadedImage(image_id=image_id, url=url)
        return result if return_uploaded_image else result.image_id

Upload an image that can be later attached to a chat message .


def _get_upload_session_status(res):
        """Parse the image upload response to obtain status.

        Args:
            res: http_utils.FetchResponse instance, the upload response

        Returns:
            dict, sessionStatus of the response

        Raises:
            hangups.NetworkError: If the upload request failed.
        """
        response = json.loads(res.body.decode())
        if 'sessionStatus' not in response:
            try:
                info = (
                    response['errorMessage']['additionalInfo']
                    ['uploader_service.GoogleRupioAdditionalInfo']
                    ['completionInfo']['customerSpecificInfo']
                )
                reason = '{} : {}'.format(info['status'], info['message'])
            except KeyError:
                reason = 'unknown reason'
            raise exceptions.NetworkError('image upload failed: {}'.format(
                reason
            ))
        return response['sessionStatus']

Parse the image upload response to obtain status .


async def _on_receive_array(self, array):
        """Parse channel array and call the appropriate events."""
        if array[0] == 'noop':
            pass  # This is just a keep-alive, ignore it.
        else:
            wrapper = json.loads(array[0]['p'])
            # Wrapper appears to be a Protocol Buffer message, but encoded via
            # field numbers as dictionary keys. Since we don't have a parser
            # for that, parse it ad-hoc here.
            if '3' in wrapper:
                # This is a new client_id.
                self._client_id = wrapper['3']['2']
                logger.info('Received new client_id: %r', self._client_id)
                # Once client_id is received, the channel is ready to have
                # services added.
                await self._add_channel_services()
            if '2' in wrapper:
                pblite_message = json.loads(wrapper['2']['2'])
                if pblite_message[0] == 'cbu':
                    # This is a (Client)BatchUpdate containing StateUpdate
                    # messages.
                    batch_update = hangouts_pb2.BatchUpdate()
                    pblite.decode(batch_update, pblite_message,
                                  ignore_first_item=True)
                    for state_update in batch_update.state_update:
                        logger.debug('Received StateUpdate:\n%s', state_update)
                        header = state_update.state_update_header
                        self._active_client_state = header.active_client_state
                        await self.on_state_update.fire(state_update)
                else:
                    logger.info('Ignoring message: %r', pblite_message[0])

async
def
_on_receive_array
(
self
,
array
)
:
if
array
[
0
]
==
'noop'
:
pass
else
:
wrapper
=
json
.
loads
(
array
[
0
]
[
'p'
]
)
if
'3'
in
wrapper
:
self
.
_client_id
=
wrapper
[
'3'
]
[
'2'
]
logger
.
info
(
'Received new client_id: %r'
,
self
.
_client_id
)
await
self
.
_add_channel_services
(
)
if
'2'
in
wrapper
:
pblite_message
=
json
.
loads
(
wrapper
[
'2'
]
[
'2'
]
)
if
pblite_message
[
0
]
==
'cbu'
:
batch_update
=
hangouts_pb2
.
BatchUpdate
(
)
pblite
.
decode
(
batch_update
,
pblite_message
,
ignore_first_item
=
True
)
for
state_update
in
batch_update
.
state_update
:
logger
.
debug
(
'Received StateUpdate:\n%s'
,
state_update
)
header
=
state_update
.
state_update_header
self
.
_active_client_state
=
header
.
active_client_state
await
self
.
on_state_update
.
fire
(
state_update
)
else
:
logger
.
info
(
'Ignoring message: %r'
,
pblite_message
[
0
]
)

Parse channel array and call the appropriate events .


async def _add_channel_services(self):
        """Add services to the channel.

        The services we add to the channel determine what kind of data we will
        receive on it.

        The "babel" service includes what we need for Hangouts. If this fails
        for some reason, hangups will never receive any events. The
        "babel_presence_last_seen" service is also required to receive presence
        notifications.

        This needs to be re-called whenever we open a new channel (when there's
        a new SID and client_id.
        """
        logger.info('Adding channel services...')
        # Based on what Hangouts for Chrome does over 2 requests, this is
        # trimmed down to 1 request that includes the bare minimum to make
        # things work.
        services = ["babel", "babel_presence_last_seen"]
        map_list = [
            dict(p=json.dumps({"3": {"1": {"1": service}}}))
            for service in services
        ]
        await self._channel.send_maps(map_list)
        logger.info('Channel services added')

async
def
_add_channel_services
(
self
)
:
logger
.
info
(
'Adding channel services...'
)
services
=
[
"babel"
,
"babel_presence_last_seen"
]
map_list
=
[
dict
(
p
=
json
.
dumps
(
{
"3"
:
{
"1"
:
{
"1"
:
service
}
}
}
)
)
for
service
in
services
]
await
self
.
_channel
.
send_maps
(
map_list
)
logger
.
info
(
'Channel services added'
)


async def _pb_request(self, endpoint, request_pb, response_pb):
        """Send a Protocol Buffer formatted chat API request.

        Args:
            endpoint (str): The chat API endpoint to use.
            request_pb: The request body as a Protocol Buffer message.
            response_pb: The response body as a Protocol Buffer message.

        Raises:
            NetworkError: If the request fails.
        """
        logger.debug('Sending Protocol Buffer request %s:\n%s', endpoint,
                     request_pb)
        res = await self._base_request(
            'https://clients6.google.com/chat/v1/{}'.format(endpoint),
            'application/x-protobuf',  # Request body is Protocol Buffer.
            'proto',  # Response body is Protocol Buffer.
            request_pb.SerializeToString()
        )
        try:
            response_pb.ParseFromString(base64.b64decode(res.body))
        except binascii.Error as e:
            raise exceptions.NetworkError(
                'Failed to decode base64 response: {}'.format(e)
            )
        except google.protobuf.message.DecodeError as e:
            raise exceptions.NetworkError(
                'Failed to decode Protocol Buffer response: {}'.format(e)
            )
        logger.debug('Received Protocol Buffer response:\n%s', response_pb)
        status = response_pb.response_header.status
        if status != hangouts_pb2.RESPONSE_STATUS_OK:
            description = response_pb.response_header.error_description
            raise exceptions.NetworkError(
                'Request failed with status {}: \'{}\''
                .format(status, description)
            )

Send a Protocol Buffer formatted chat API request .


async def _base_request(self, url, content_type, response_type, data):
        """Send a generic authenticated POST request.

        Args:
            url (str): URL of request.
            content_type (str): Request content type.
            response_type (str): The desired response format. Valid options
                are: 'json' (JSON), 'protojson' (pblite), and 'proto' (binary
                Protocol Buffer). 'proto' requires manually setting an extra
                header 'X-Goog-Encode-Response-If-Executable: base64'.
            data (str): Request body data.

        Returns:
            FetchResponse: Response containing HTTP code, cookies, and body.

        Raises:
            NetworkError: If the request fails.
        """
        headers = {
            'content-type': content_type,
            # This header is required for Protocol Buffer responses. It causes
            # them to be base64 encoded:
            'X-Goog-Encode-Response-If-Executable': 'base64',
        }
        params = {
            # "alternative representation type" (desired response format).
            'alt': response_type,
            # API key (required to avoid 403 Forbidden "Daily Limit for
            # Unauthenticated Use Exceeded. Continued use requires signup").
            'key': API_KEY,
        }
        res = await self._session.fetch(
            'post', url, headers=headers, params=params, data=data,
        )
        return res

async def _base_request ( self , url , content_type , response_type , data ) : headers = { 'content-type' : content_type , 'X-Goog-Encode-Response-If-Executable' : 'base64' , } params = { 'alt' : response_type , 'key' : API_KEY , } res = await self . _session . fetch ( 'post' , url , headers = headers , params = params , data = data , ) return res

Send a generic authenticated POST request .


async def add_user(self, add_user_request):
        """Invite users to join an existing group conversation."""
        response = hangouts_pb2.AddUserResponse()
        await self._pb_request('conversations/adduser',
                               add_user_request, response)
        return response

async def add_user ( self , add_user_request ) : response = hangouts_pb2 . AddUserResponse ( ) await self . _pb_request ( 'conversations/adduser' , add_user_request , response ) return response

Invite users to join an existing group conversation .


async def create_conversation(self, create_conversation_request):
        """Create a new conversation."""
        response = hangouts_pb2.CreateConversationResponse()
        await self._pb_request('conversations/createconversation',
                               create_conversation_request, response)
        return response

async def create_conversation ( self , create_conversation_request ) : response = hangouts_pb2 . CreateConversationResponse ( ) await self . _pb_request ( 'conversations/createconversation' , create_conversation_request , response ) return response


async def delete_conversation(self, delete_conversation_request):
        """Leave a one-to-one conversation.

        One-to-one conversations are "sticky"; they can't actually be deleted.
        This API clears the event history of the specified conversation up to
        ``delete_upper_bound_timestamp``, hiding it if no events remain.
        """
        response = hangouts_pb2.DeleteConversationResponse()
        await self._pb_request('conversations/deleteconversation',
                               delete_conversation_request, response)
        return response

async def delete_conversation ( self , delete_conversation_request ) : response = hangouts_pb2 . DeleteConversationResponse ( ) await self . _pb_request ( 'conversations/deleteconversation' , delete_conversation_request , response ) return response


async def easter_egg(self, easter_egg_request):
        """Send an easter egg event to a conversation."""
        response = hangouts_pb2.EasterEggResponse()
        await self._pb_request('conversations/easteregg',
                               easter_egg_request, response)
        return response

async def easter_egg ( self , easter_egg_request ) : response = hangouts_pb2 . EasterEggResponse ( ) await self . _pb_request ( 'conversations/easteregg' , easter_egg_request , response ) return response

Send an easter egg event to a conversation .


async def get_conversation(self, get_conversation_request):
        """Return conversation info and recent events."""
        response = hangouts_pb2.GetConversationResponse()
        await self._pb_request('conversations/getconversation',
                               get_conversation_request, response)
        return response

async def get_conversation ( self , get_conversation_request ) : response = hangouts_pb2 . GetConversationResponse ( ) await self . _pb_request ( 'conversations/getconversation' , get_conversation_request , response ) return response

Return conversation info and recent events .


async def get_entity_by_id(self, get_entity_by_id_request):
        """Return one or more user entities.

        Searching by phone number only finds entities when their phone number
        is in your contacts (and not always even then), and can't be used to
        find Google Voice contacts.
        """
        response = hangouts_pb2.GetEntityByIdResponse()
        await self._pb_request('contacts/getentitybyid',
                               get_entity_by_id_request, response)
        return response

async def get_entity_by_id ( self , get_entity_by_id_request ) : response = hangouts_pb2 . GetEntityByIdResponse ( ) await self . _pb_request ( 'contacts/getentitybyid' , get_entity_by_id_request , response ) return response


async def get_group_conversation_url(self,
                                         get_group_conversation_url_request):
        """Get URL to allow others to join a group conversation."""
        response = hangouts_pb2.GetGroupConversationUrlResponse()
        await self._pb_request('conversations/getgroupconversationurl',
                               get_group_conversation_url_request,
                               response)
        return response

async def get_group_conversation_url ( self , get_group_conversation_url_request ) : response = hangouts_pb2 . GetGroupConversationUrlResponse ( ) await self . _pb_request ( 'conversations/getgroupconversationurl' , get_group_conversation_url_request , response ) return response

Get URL to allow others to join a group conversation .


async def get_self_info(self, get_self_info_request):
        """Return info about the current user."""
        response = hangouts_pb2.GetSelfInfoResponse()
        await self._pb_request('contacts/getselfinfo',
                               get_self_info_request, response)
        return response

async def get_self_info ( self , get_self_info_request ) : response = hangouts_pb2 . GetSelfInfoResponse ( ) await self . _pb_request ( 'contacts/getselfinfo' , get_self_info_request , response ) return response


async def get_suggested_entities(self, get_suggested_entities_request):
        """Return suggested contacts."""
        response = hangouts_pb2.GetSuggestedEntitiesResponse()
        await self._pb_request('contacts/getsuggestedentities',
                               get_suggested_entities_request, response)
        return response

async def get_suggested_entities ( self , get_suggested_entities_request ) : response = hangouts_pb2 . GetSuggestedEntitiesResponse ( ) await self . _pb_request ( 'contacts/getsuggestedentities' , get_suggested_entities_request , response ) return response


async def query_presence(self, query_presence_request):
        """Return presence status for a list of users."""
        response = hangouts_pb2.QueryPresenceResponse()
        await self._pb_request('presence/querypresence',
                               query_presence_request, response)
        return response

async def query_presence ( self , query_presence_request ) : response = hangouts_pb2 . QueryPresenceResponse ( ) await self . _pb_request ( 'presence/querypresence' , query_presence_request , response ) return response

Return presence status for a list of users .


async def remove_user(self, remove_user_request):
        """Remove a participant from a group conversation."""
        response = hangouts_pb2.RemoveUserResponse()
        await self._pb_request('conversations/removeuser',
                               remove_user_request, response)
        return response

async def remove_user ( self , remove_user_request ) : response = hangouts_pb2 . RemoveUserResponse ( ) await self . _pb_request ( 'conversations/removeuser' , remove_user_request , response ) return response

Remove a participant from a group conversation .


async def rename_conversation(self, rename_conversation_request):
        """Rename a conversation.

        Both group and one-to-one conversations may be renamed, but the
        official Hangouts clients have mixed support for one-to-one
        conversations with custom names.
        """
        response = hangouts_pb2.RenameConversationResponse()
        await self._pb_request('conversations/renameconversation',
                               rename_conversation_request, response)
        return response

async def rename_conversation ( self , rename_conversation_request ) : response = hangouts_pb2 . RenameConversationResponse ( ) await self . _pb_request ( 'conversations/renameconversation' , rename_conversation_request , response ) return response


async def search_entities(self, search_entities_request):
        """Return user entities based on a query."""
        response = hangouts_pb2.SearchEntitiesResponse()
        await self._pb_request('contacts/searchentities',
                               search_entities_request, response)
        return response

async def search_entities ( self , search_entities_request ) : response = hangouts_pb2 . SearchEntitiesResponse ( ) await self . _pb_request ( 'contacts/searchentities' , search_entities_request , response ) return response


async def send_chat_message(self, send_chat_message_request):
        """Send a chat message to a conversation."""
        response = hangouts_pb2.SendChatMessageResponse()
        await self._pb_request('conversations/sendchatmessage',
                               send_chat_message_request, response)
        return response

async def send_chat_message ( self , send_chat_message_request ) : response = hangouts_pb2 . SendChatMessageResponse ( ) await self . _pb_request ( 'conversations/sendchatmessage' , send_chat_message_request , response ) return response


async def modify_otr_status(self, modify_otr_status_request):
        """Enable or disable message history in a conversation."""
        response = hangouts_pb2.ModifyOTRStatusResponse()
        await self._pb_request('conversations/modifyotrstatus',
                               modify_otr_status_request, response)
        return response

async def modify_otr_status ( self , modify_otr_status_request ) : response = hangouts_pb2 . ModifyOTRStatusResponse ( ) await self . _pb_request ( 'conversations/modifyotrstatus' , modify_otr_status_request , response ) return response

Enable or disable message history in a conversation .


async def send_offnetwork_invitation(
            self, send_offnetwork_invitation_request
    ):
        """Send an email to invite a non-Google contact to Hangouts."""
        response = hangouts_pb2.SendOffnetworkInvitationResponse()
        await self._pb_request('devices/sendoffnetworkinvitation',
                               send_offnetwork_invitation_request,
                               response)
        return response

async def send_offnetwork_invitation ( self , send_offnetwork_invitation_request ) : response = hangouts_pb2 . SendOffnetworkInvitationResponse ( ) await self . _pb_request ( 'devices/sendoffnetworkinvitation' , send_offnetwork_invitation_request , response ) return response

Send an email to invite a non - Google contact to Hangouts .


async def set_active_client(self, set_active_client_request):
        """Set the active client."""
        response = hangouts_pb2.SetActiveClientResponse()
        await self._pb_request('clients/setactiveclient',
                               set_active_client_request, response)
        return response

async def set_active_client ( self , set_active_client_request ) : response = hangouts_pb2 . SetActiveClientResponse ( ) await self . _pb_request ( 'clients/setactiveclient' , set_active_client_request , response ) return response


async def set_conversation_notification_level(
            self, set_conversation_notification_level_request
    ):
        """Set the notification level of a conversation."""
        response = hangouts_pb2.SetConversationNotificationLevelResponse()
        await self._pb_request(
            'conversations/setconversationnotificationlevel',
            set_conversation_notification_level_request, response
        )
        return response

Set the notification level of a conversation .


async def set_focus(self, set_focus_request):
        """Set focus to a conversation."""
        response = hangouts_pb2.SetFocusResponse()
        await self._pb_request('conversations/setfocus',
                               set_focus_request, response)
        return response

async def set_focus ( self , set_focus_request ) : response = hangouts_pb2 . SetFocusResponse ( ) await self . _pb_request ( 'conversations/setfocus' , set_focus_request , response ) return response


async def set_group_link_sharing_enabled(
            self, set_group_link_sharing_enabled_request
    ):
        """Set whether group link sharing is enabled for a conversation."""
        response = hangouts_pb2.SetGroupLinkSharingEnabledResponse()
        await self._pb_request('conversations/setgrouplinksharingenabled',
                               set_group_link_sharing_enabled_request,
                               response)
        return response

Set whether group link sharing is enabled for a conversation .


async def set_presence(self, set_presence_request):
        """Set the presence status."""
        response = hangouts_pb2.SetPresenceResponse()
        await self._pb_request('presence/setpresence',
                               set_presence_request, response)
        return response

async def set_presence ( self , set_presence_request ) : response = hangouts_pb2 . SetPresenceResponse ( ) await self . _pb_request ( 'presence/setpresence' , set_presence_request , response ) return response


async def set_typing(self, set_typing_request):
        """Set the typing status of a conversation."""
        response = hangouts_pb2.SetTypingResponse()
        await self._pb_request('conversations/settyping',
                               set_typing_request, response)
        return response

async def set_typing ( self , set_typing_request ) : response = hangouts_pb2 . SetTypingResponse ( ) await self . _pb_request ( 'conversations/settyping' , set_typing_request , response ) return response

Set the typing status of a conversation .


async def sync_all_new_events(self, sync_all_new_events_request):
        """List all events occurring at or after a timestamp."""
        response = hangouts_pb2.SyncAllNewEventsResponse()
        await self._pb_request('conversations/syncallnewevents',
                               sync_all_new_events_request, response)
        return response

async def sync_all_new_events ( self , sync_all_new_events_request ) : response = hangouts_pb2 . SyncAllNewEventsResponse ( ) await self . _pb_request ( 'conversations/syncallnewevents' , sync_all_new_events_request , response ) return response

List all events occurring at or after a timestamp .


async def sync_recent_conversations(
            self, sync_recent_conversations_request
    ):
        """Return info on recent conversations and their events."""
        response = hangouts_pb2.SyncRecentConversationsResponse()
        await self._pb_request('conversations/syncrecentconversations',
                               sync_recent_conversations_request,
                               response)
        return response

async def sync_recent_conversations ( self , sync_recent_conversations_request ) : response = hangouts_pb2 . SyncRecentConversationsResponse ( ) await self . _pb_request ( 'conversations/syncrecentconversations' , sync_recent_conversations_request , response ) return response

Return info on recent conversations and their events .


def from_timestamp(microsecond_timestamp):
    """Convert a microsecond timestamp to a UTC datetime instance."""
    # Create datetime without losing precision from floating point (yes, this
    # is actually needed):
    return datetime.datetime.fromtimestamp(
        microsecond_timestamp // 1000000, datetime.timezone.utc
    ).replace(microsecond=(microsecond_timestamp % 1000000))

def from_timestamp ( microsecond_timestamp ) : return datetime . datetime . fromtimestamp ( microsecond_timestamp // 1000000 , datetime . timezone . utc ) . replace ( microsecond = ( microsecond_timestamp % 1000000 ) )

Convert a microsecond timestamp to a UTC datetime instance .


def from_participantid(participant_id):
    """Convert hangouts_pb2.ParticipantId to UserID."""
    return user.UserID(
        chat_id=participant_id.chat_id,
        gaia_id=participant_id.gaia_id
    )

Convert hangouts_pb2 . ParticipantId to UserID .


def to_participantid(user_id):
    """Convert UserID to hangouts_pb2.ParticipantId."""
    return hangouts_pb2.ParticipantId(
        chat_id=user_id.chat_id,
        gaia_id=user_id.gaia_id
    )

Convert UserID to hangouts_pb2 . ParticipantId .


def parse_typing_status_message(p):
    """Return TypingStatusMessage from hangouts_pb2.SetTypingNotification.

    The same status may be sent multiple times consecutively, and when a
    message is sent the typing status will not change to stopped.
    """
    return TypingStatusMessage(
        conv_id=p.conversation_id.id,
        user_id=from_participantid(p.sender_id),
        timestamp=from_timestamp(p.timestamp),
        status=p.type,
    )

def parse_typing_status_message ( p ) : return TypingStatusMessage ( conv_id = p . conversation_id . id , user_id = from_participantid ( p . sender_id ) , timestamp = from_timestamp ( p . timestamp ) , status = p . type , )

Return TypingStatusMessage from hangouts_pb2 . SetTypingNotification .


def parse_watermark_notification(p):
    """Return WatermarkNotification from hangouts_pb2.WatermarkNotification."""
    return WatermarkNotification(
        conv_id=p.conversation_id.id,
        user_id=from_participantid(p.sender_id),
        read_timestamp=from_timestamp(
            p.latest_read_timestamp
        ),
    )

def parse_watermark_notification ( p ) : return WatermarkNotification ( conv_id = p . conversation_id . id , user_id = from_participantid ( p . sender_id ) , read_timestamp = from_timestamp ( p . latest_read_timestamp ) , )

Return WatermarkNotification from hangouts_pb2 . WatermarkNotification .


def _get_authorization_headers(sapisid_cookie):
    """Return authorization headers for API request."""
    # It doesn't seem to matter what the url and time are as long as they are
    # consistent.
    time_msec = int(time.time() * 1000)
    auth_string = '{} {} {}'.format(time_msec, sapisid_cookie, ORIGIN_URL)
    auth_hash = hashlib.sha1(auth_string.encode()).hexdigest()
    sapisidhash = 'SAPISIDHASH {}_{}'.format(time_msec, auth_hash)
    return {
        'authorization': sapisidhash,
        'x-origin': ORIGIN_URL,
        'x-goog-authuser': '0',
    }

def
_get_authorization_headers
(
sapisid_cookie
)
:
time_msec
=
int
(
time
.
time
(
)
*
1000
)
auth_string
=
'{} {} {}'
.
format
(
time_msec
,
sapisid_cookie
,
ORIGIN_URL
)
auth_hash
=
hashlib
.
sha1
(
auth_string
.
encode
(
)
)
.
hexdigest
(
)
sapisidhash
=
'SAPISIDHASH {}_{}'
.
format
(
time_msec
,
auth_hash
)
return
{
'authorization'
:
sapisidhash
,
'x-origin'
:
ORIGIN_URL
,
'x-goog-authuser'
:
'0'
,
}

Return authorization headers for API request .


async def fetch(self, method, url, params=None, headers=None, data=None):
        """Make an HTTP request.

        Automatically uses configured HTTP proxy, and adds Google authorization
        header and cookies.

        Failures will be retried MAX_RETRIES times before raising NetworkError.

        Args:
            method (str): Request method.
            url (str): Request URL.
            params (dict): (optional) Request query string parameters.
            headers (dict): (optional) Request headers.
            data: (str): (optional) Request body data.

        Returns:
            FetchResponse: Response data.

        Raises:
            NetworkError: If the request fails.
        """
        logger.debug('Sending request %s %s:\n%r', method, url, data)
        for retry_num in range(MAX_RETRIES):
            try:
                async with self.fetch_raw(method, url, params=params,
                                          headers=headers, data=data) as res:
                    async with async_timeout.timeout(REQUEST_TIMEOUT):
                        body = await res.read()
                logger.debug('Received response %d %s:\n%r',
                             res.status, res.reason, body)
            except asyncio.TimeoutError:
                error_msg = 'Request timed out'
            except aiohttp.ServerDisconnectedError as err:
                error_msg = 'Server disconnected error: {}'.format(err)
            except (aiohttp.ClientError, ValueError) as err:
                error_msg = 'Request connection error: {}'.format(err)
            else:
                break
            logger.info('Request attempt %d failed: %s', retry_num, error_msg)
        else:
            logger.info('Request failed after %d attempts', MAX_RETRIES)
            raise exceptions.NetworkError(error_msg)

        if res.status != 200:
            logger.info('Request returned unexpected status: %d %s',
                        res.status, res.reason)
            raise exceptions.NetworkError(
                'Request return unexpected status: {}: {}'
                .format(res.status, res.reason)
            )

        return FetchResponse(res.status, body)


def fetch_raw(self, method, url, params=None, headers=None, data=None):
        """Make an HTTP request using aiohttp directly.

        Automatically uses configured HTTP proxy, and adds Google authorization
        header and cookies.

        Args:
            method (str): Request method.
            url (str): Request URL.
            params (dict): (optional) Request query string parameters.
            headers (dict): (optional) Request headers.
            data: (str): (optional) Request body data.

        Returns:
            aiohttp._RequestContextManager: ContextManager for a HTTP response.

        Raises:
            See ``aiohttp.ClientSession.request``.
        """
        # Ensure we don't accidentally send the authorization header to a
        # non-Google domain:
        if not urllib.parse.urlparse(url).hostname.endswith('.google.com'):
            raise Exception('expected google.com domain')

        headers = headers or {}
        headers.update(self._authorization_headers)
        return self._session.request(
            method, url, params=params, headers=headers, data=data,
            proxy=self._proxy
        )

def
fetch_raw
(
self
,
method
,
url
,
params
=
,
headers
=
,
data
=
)
:
if
not
urllib
.
parse
.
urlparse
(
url
)
.
hostname
.
endswith
(
'.google.com'
)
:
raise
Exception
(
'expected google.com domain'
)
headers
=
headers
or
{
}
headers
.
update
(
self
.
_authorization_headers
)
return
self
.
_session
.
request
(
method
,
url
,
params
=
params
,
headers
=
headers
,
data
=
data
,
proxy
=
self
.
_proxy
)

Make an HTTP request using aiohttp directly .


async def lookup_entities(client, args):
    """Search for entities by phone number, email, or gaia_id."""
    lookup_spec = _get_lookup_spec(args.entity_identifier)
    request = hangups.hangouts_pb2.GetEntityByIdRequest(
        request_header=client.get_request_header(),
        batch_lookup_spec=[lookup_spec],
    )
    res = await client.get_entity_by_id(request)

    # Print the list of entities in the response.
    for entity_result in res.entity_result:
        for entity in entity_result.entity:
            print(entity)

async def lookup_entities ( client , args ) : lookup_spec = _get_lookup_spec ( args . entity_identifier ) request = hangups . hangouts_pb2 . GetEntityByIdRequest ( request_header = client . get_request_header ( ) , batch_lookup_spec = [ lookup_spec ] , ) res = await client . get_entity_by_id ( request ) for entity_result in res . entity_result : for entity in entity_result . entity : print ( entity )

Search for entities by phone number email or gaia_id .


def _get_lookup_spec(identifier):
    """Return EntityLookupSpec from phone number, email address, or gaia ID."""
    if identifier.startswith('+'):
        return hangups.hangouts_pb2.EntityLookupSpec(
            phone=identifier, create_offnetwork_gaia=True
        )
    elif '@' in identifier:
        return hangups.hangouts_pb2.EntityLookupSpec(
            email=identifier, create_offnetwork_gaia=True
        )
    else:
        return hangups.hangouts_pb2.EntityLookupSpec(gaia_id=identifier)

def _get_lookup_spec ( identifier ) : if identifier . startswith ( '+' ) : return hangups . hangouts_pb2 . EntityLookupSpec ( phone = identifier , create_offnetwork_gaia = True ) elif '@' in identifier : return hangups . hangouts_pb2 . EntityLookupSpec ( email = identifier , create_offnetwork_gaia = True ) else : return hangups . hangouts_pb2 . EntityLookupSpec ( gaia_id = identifier )

Return EntityLookupSpec from phone number email address or gaia ID .


def get_conv_name(conv, truncate=False, show_unread=False):
    """Return a readable name for a conversation.

    If the conversation has a custom name, use the custom name. Otherwise, for
    one-to-one conversations, the name is the full name of the other user. For
    group conversations, the name is a comma-separated list of first names. If
    the group conversation is empty, the name is "Empty Conversation".

    If truncate is true, only show up to two names in a group conversation.

    If show_unread is True, if there are unread chat messages, show the number
    of unread chat messages in parentheses after the conversation name.
    """
    num_unread = len([conv_event for conv_event in conv.unread_events if
                      isinstance(conv_event, hangups.ChatMessageEvent) and
                      not conv.get_user(conv_event.user_id).is_self])
    if show_unread and num_unread > 0:
        postfix = ' ({})'.format(num_unread)
    else:
        postfix = ''
    if conv.name is not None:
        return conv.name + postfix
    else:
        participants = sorted(
            (user for user in conv.users if not user.is_self),
            key=lambda user: user.id_
        )
        names = [user.first_name for user in participants]
        if not participants:
            return "Empty Conversation" + postfix
        if len(participants) == 1:
            return participants[0].full_name + postfix
        elif truncate and len(participants) > 2:
            return (', '.join(names[:2] + ['+{}'.format(len(names) - 2)]) +
                    postfix)
        else:
            return ', '.join(names) + postfix

def
get_conv_name
(
conv
,
truncate
=
False
,
show_unread
=
False
)
:
num_unread
=
len
(
[
conv_event
for
conv_event
in
conv
.
unread_events
if
isinstance
(
conv_event
,
hangups
.
ChatMessageEvent
)
and
not
conv
.
get_user
(
conv_event
.
user_id
)
.
is_self
]
)
if
show_unread
and
num_unread
>
0
:
postfix
=
' ({})'
.
format
(
num_unread
)
else
:
postfix
=
''
if
conv
.
name
is
not
:
return
conv
.
name
+
postfix
else
:
participants
=
sorted
(
(
user
for
user
in
conv
.
users
if
not
user
.
is_self
)
,
key
=
lambda
user
:
user
.
id_
)
names
=
[
user
.
first_name
for
user
in
participants
]
if
not
participants
:
return
"Empty Conversation"
+
postfix
if
len
(
participants
)
==
1
:
return
participants
[
0
]
.
full_name
+
postfix
elif
truncate
and
len
(
participants
)
>
2
:
return
(
', '
.
join
(
names
[
:
2
]
+
[
'+{}'
.
format
(
len
(
names
)
2
)
]
)
+
postfix
)
else
:
return
', '
.
join
(
names
)
+
postfix

Return a readable name for a conversation .


def add_color_to_scheme(scheme, name, foreground, background, palette_colors):
    """Add foreground and background colours to a color scheme"""
    if foreground is None and background is None:
        return scheme

    new_scheme = []
    for item in scheme:
        if item[0] == name:
            if foreground is None:
                foreground = item[1]
            if background is None:
                background = item[2]
            if palette_colors > 16:
                new_scheme.append((name, '', '', '', foreground, background))
            else:
                new_scheme.append((name, foreground, background))
        else:
            new_scheme.append(item)
    return new_scheme

def add_color_to_scheme ( scheme , name , foreground , background , palette_colors ) : if foreground is None and background is None : return scheme new_scheme = [ ] for item in scheme : if item [ 0 ] == name : if foreground is None : foreground = item [ 1 ] if background is None : background = item [ 2 ] if palette_colors > 16 : new_scheme . append ( ( name , '' , '' , '' , foreground , background ) ) else : new_scheme . append ( ( name , foreground , background ) ) else : new_scheme . append ( item ) return new_scheme


async def _sync_all_conversations(client):
    """Sync all conversations by making paginated requests.

    Conversations are ordered by ascending sort timestamp.

    Args:
        client (Client): Connected client.

    Raises:
        NetworkError: If the requests fail.

    Returns:
        tuple of list of ``ConversationState`` messages and sync timestamp
    """
    conv_states = []
    sync_timestamp = None
    request = hangouts_pb2.SyncRecentConversationsRequest(
        request_header=client.get_request_header(),
        max_conversations=CONVERSATIONS_PER_REQUEST,
        max_events_per_conversation=1,
        sync_filter=[
            hangouts_pb2.SYNC_FILTER_INBOX,
            hangouts_pb2.SYNC_FILTER_ARCHIVED,
        ]
    )
    for _ in range(MAX_CONVERSATION_PAGES):
        logger.info(
            'Requesting conversations page %s', request.last_event_timestamp
        )
        response = await client.sync_recent_conversations(request)
        conv_states = list(response.conversation_state) + conv_states
        sync_timestamp = parsers.from_timestamp(
            # SyncRecentConversations seems to return a sync_timestamp 4
            # minutes before the present. To prevent SyncAllNewEvents later
            # breaking requesting events older than what we already have, use
            # current_server_time instead.
            response.response_header.current_server_time
        )
        if response.continuation_end_timestamp == 0:
            logger.info('Reached final conversations page')
            break
        else:
            request.last_event_timestamp = response.continuation_end_timestamp
    else:
        logger.warning('Exceeded maximum number of conversation pages')
    logger.info('Synced %s total conversations', len(conv_states))
    return conv_states, sync_timestamp

Sync all conversations by making paginated requests .


def unread_events(self):
        """Loaded events which are unread sorted oldest to newest.

        Some Hangouts clients don't update the read timestamp for certain event
        types, such as membership changes, so this may return more unread
        events than these clients will show. There's also a delay between
        sending a message and the user's own message being considered read.

        (list of :class:`.ConversationEvent`).
        """
        return [conv_event for conv_event in self._events
                if conv_event.timestamp > self.latest_read_timestamp]

def unread_events ( self ) : return [ conv_event for conv_event in self . _events if conv_event . timestamp > self . latest_read_timestamp ]

Loaded events which are unread sorted oldest to newest .


def is_quiet(self):
        """``True`` if notification level for this conversation is quiet."""
        level = self._conversation.self_conversation_state.notification_level
        return level == hangouts_pb2.NOTIFICATION_LEVEL_QUIET

def is_quiet ( self ) : level = self . _conversation . self_conversation_state . notification_level return level == hangouts_pb2 . NOTIFICATION_LEVEL_QUIET

True if notification level for this conversation is quiet .


def _on_watermark_notification(self, notif):
        """Handle a watermark notification."""
        # Update the conversation:
        if self.get_user(notif.user_id).is_self:
            logger.info('latest_read_timestamp for {} updated to {}'
                        .format(self.id_, notif.read_timestamp))
            self_conversation_state = (
                self._conversation.self_conversation_state
            )
            self_conversation_state.self_read_state.latest_read_timestamp = (
                parsers.to_timestamp(notif.read_timestamp)
            )
        # Update the participants' watermarks:
        previous_timestamp = self._watermarks.get(
            notif.user_id,
            datetime.datetime.min.replace(tzinfo=datetime.timezone.utc)
        )
        if notif.read_timestamp > previous_timestamp:
            logger.info(('latest_read_timestamp for conv {} participant {}' +
                         ' updated to {}').format(self.id_,
                                                  notif.user_id.chat_id,
                                                  notif.read_timestamp))
            self._watermarks[notif.user_id] = notif.read_timestamp


def update_conversation(self, conversation):
        """Update the internal state of the conversation.

        This method is used by :class:`.ConversationList` to maintain this
        instance.

        Args:
            conversation: ``Conversation`` message.
        """
        # StateUpdate.conversation is actually a delta; fields that aren't
        # specified are assumed to be unchanged. Until this class is
        # refactored, hide this by saving and restoring previous values where
        # necessary.

        new_state = conversation.self_conversation_state
        old_state = self._conversation.self_conversation_state
        self._conversation = conversation

        # delivery_medium_option
        if not new_state.delivery_medium_option:
            new_state.delivery_medium_option.extend(
                old_state.delivery_medium_option
            )

        # latest_read_timestamp
        old_timestamp = old_state.self_read_state.latest_read_timestamp
        new_timestamp = new_state.self_read_state.latest_read_timestamp
        if new_timestamp == 0:
            new_state.self_read_state.latest_read_timestamp = old_timestamp

        # user_read_state(s)
        for new_entry in conversation.read_state:
            tstamp = parsers.from_timestamp(new_entry.latest_read_timestamp)
            if tstamp == 0:
                continue
            uid = parsers.from_participantid(new_entry.participant_id)
            if uid not in self._watermarks or self._watermarks[uid] < tstamp:
                self._watermarks[uid] = tstamp

def update_conversation ( self , conversation ) : new_state = conversation . self_conversation_state old_state = self . _conversation . self_conversation_state self . _conversation = conversation if not new_state . delivery_medium_option : new_state . delivery_medium_option . extend ( old_state . delivery_medium_option ) old_timestamp = old_state . self_read_state . latest_read_timestamp new_timestamp = new_state . self_read_state . latest_read_timestamp if new_timestamp == 0 : new_state . self_read_state . latest_read_timestamp = old_timestamp for new_entry in conversation . read_state : tstamp = parsers . from_timestamp ( new_entry . latest_read_timestamp ) if tstamp == 0 : continue uid = parsers . from_participantid ( new_entry . participant_id ) if uid not in self . _watermarks or self . _watermarks [ uid ] < tstamp : self . _watermarks [ uid ] = tstamp

Update the internal state of the conversation .


def _wrap_event(event_):
        """Wrap hangouts_pb2.Event in ConversationEvent subclass."""
        cls = conversation_event.ConversationEvent
        if event_.HasField('chat_message'):
            cls = conversation_event.ChatMessageEvent
        elif event_.HasField('otr_modification'):
            cls = conversation_event.OTREvent
        elif event_.HasField('conversation_rename'):
            cls = conversation_event.RenameEvent
        elif event_.HasField('membership_change'):
            cls = conversation_event.MembershipChangeEvent
        elif event_.HasField('hangout_event'):
            cls = conversation_event.HangoutEvent
        elif event_.HasField('group_link_sharing_modification'):
            cls = conversation_event.GroupLinkSharingModificationEvent
        return cls(event_)

def _wrap_event ( event_ ) : cls = conversation_event . ConversationEvent if event_ . HasField ( 'chat_message' ) : cls = conversation_event . ChatMessageEvent elif event_ . HasField ( 'otr_modification' ) : cls = conversation_event . OTREvent elif event_ . HasField ( 'conversation_rename' ) : cls = conversation_event . RenameEvent elif event_ . HasField ( 'membership_change' ) : cls = conversation_event . MembershipChangeEvent elif event_ . HasField ( 'hangout_event' ) : cls = conversation_event . HangoutEvent elif event_ . HasField ( 'group_link_sharing_modification' ) : cls = conversation_event . GroupLinkSharingModificationEvent return cls ( event_ )

Wrap hangouts_pb2 . Event in ConversationEvent subclass .


def add_event(self, event_):
        """Add an event to the conversation.

        This method is used by :class:`.ConversationList` to maintain this
        instance.

        Args:
            event_: ``Event`` message.

        Returns:
            :class:`.ConversationEvent` representing the event.
        """
        conv_event = self._wrap_event(event_)
        if conv_event.id_ not in self._events_dict:
            self._events.append(conv_event)
            self._events_dict[conv_event.id_] = conv_event
        else:
            # If this happens, there's probably a bug.
            logger.info('Conversation %s ignoring duplicate event %s',
                        self.id_, conv_event.id_)
            return None
        return conv_event


def _get_default_delivery_medium(self):
        """Return default DeliveryMedium to use for sending messages.

        Use the first option, or an option that's marked as the current
        default.
        """
        medium_options = (
            self._conversation.self_conversation_state.delivery_medium_option
        )
        try:
            default_medium = medium_options[0].delivery_medium
        except IndexError:
            logger.warning('Conversation %r has no delivery medium', self.id_)
            default_medium = hangouts_pb2.DeliveryMedium(
                medium_type=hangouts_pb2.DELIVERY_MEDIUM_BABEL
            )
        for medium_option in medium_options:
            if medium_option.current_default:
                default_medium = medium_option.delivery_medium
        return default_medium

Return default DeliveryMedium to use for sending messages .


def _get_event_request_header(self):
        """Return EventRequestHeader for conversation."""
        otr_status = (hangouts_pb2.OFF_THE_RECORD_STATUS_OFF_THE_RECORD
                      if self.is_off_the_record else
                      hangouts_pb2.OFF_THE_RECORD_STATUS_ON_THE_RECORD)
        return hangouts_pb2.EventRequestHeader(
            conversation_id=hangouts_pb2.ConversationId(id=self.id_),
            client_generated_id=self._client.get_client_generated_id(),
            expected_otr=otr_status,
            delivery_medium=self._get_default_delivery_medium(),
        )

def _get_event_request_header ( self ) : otr_status = ( hangouts_pb2 . OFF_THE_RECORD_STATUS_OFF_THE_RECORD if self . is_off_the_record else hangouts_pb2 . OFF_THE_RECORD_STATUS_ON_THE_RECORD ) return hangouts_pb2 . EventRequestHeader ( conversation_id = hangouts_pb2 . ConversationId ( id = self . id_ ) , client_generated_id = self . _client . get_client_generated_id ( ) , expected_otr = otr_status , delivery_medium = self . _get_default_delivery_medium ( ) , )

Return EventRequestHeader for conversation .


async def send_message(self, segments, image_file=None, image_id=None,
                           image_user_id=None):
        """Send a message to this conversation.

        A per-conversation lock is acquired to ensure that messages are sent in
        the correct order when this method is called multiple times
        asynchronously.

        Args:
            segments: List of :class:`.ChatMessageSegment` objects to include
                in the message.
            image_file: (optional) File-like object containing an image to be
                attached to the message.
            image_id: (optional) ID of an Picasa photo to be attached to the
                message. If you specify both ``image_file`` and ``image_id``
                together, ``image_file`` takes precedence and ``image_id`` will
                be ignored.
            image_user_id: (optional) Picasa user ID, required only if
                ``image_id`` refers to an image from a different Picasa user,
                such as Google's sticker user.

        Raises:
            .NetworkError: If the message cannot be sent.
        """
        async with self._send_message_lock:
            if image_file:
                try:
                    uploaded_image = await self._client.upload_image(
                        image_file, return_uploaded_image=True
                    )
                except exceptions.NetworkError as e:
                    logger.warning('Failed to upload image: {}'.format(e))
                    raise
                image_id = uploaded_image.image_id
            try:
                request = hangouts_pb2.SendChatMessageRequest(
                    request_header=self._client.get_request_header(),
                    event_request_header=self._get_event_request_header(),
                    message_content=hangouts_pb2.MessageContent(
                        segment=[seg.serialize() for seg in segments],
                    ),
                )
                if image_id is not None:
                    request.existing_media.photo.photo_id = image_id
                if image_user_id is not None:
                    request.existing_media.photo.user_id = image_user_id
                    request.existing_media.photo.is_custom_user_id = True
                await self._client.send_chat_message(request)
            except exceptions.NetworkError as e:
                logger.warning('Failed to send message: {}'.format(e))
                raise

async
def
send_message
(
self
,
segments
,
image_file
=
,
image_id
=
,
image_user_id
=
)
:
async
with
self
.
_send_message_lock
:
if
image_file
:
try
:
uploaded_image
=
await
self
.
_client
.
upload_image
(
image_file
,
return_uploaded_image
=
True
)
except
exceptions
.
NetworkError
as
e
:
logger
.
warning
(
'Failed to upload image: {}'
.
format
(
e
)
)
raise
image_id
=
uploaded_image
.
image_id
try
:
request
=
hangouts_pb2
.
SendChatMessageRequest
(
request_header
=
self
.
_client
.
get_request_header
(
)
,
event_request_header
=
self
.
_get_event_request_header
(
)
,
message_content
=
hangouts_pb2
.
MessageContent
(
segment
=
[
seg
.
serialize
(
)
for
seg
in
segments
]
,
)
,
)
if
image_id
is
not
:
request
.
existing_media
.
photo
.
photo_id
=
image_id
if
image_user_id
is
not
:
request
.
existing_media
.
photo
.
user_id
=
image_user_id
request
.
existing_media
.
photo
.
is_custom_user_id
=
True
await
self
.
_client
.
send_chat_message
(
request
)
except
exceptions
.
NetworkError
as
e
:
logger
.
warning
(
'Failed to send message: {}'
.
format
(
e
)
)
raise


async def leave(self):
        """Leave this conversation.

        Raises:
            .NetworkError: If conversation cannot be left.
        """
        is_group_conversation = (self._conversation.type ==
                                 hangouts_pb2.CONVERSATION_TYPE_GROUP)
        try:
            if is_group_conversation:
                await self._client.remove_user(
                    hangouts_pb2.RemoveUserRequest(
                        request_header=self._client.get_request_header(),
                        event_request_header=self._get_event_request_header(),
                    )
                )
            else:
                await self._client.delete_conversation(
                    hangouts_pb2.DeleteConversationRequest(
                        request_header=self._client.get_request_header(),
                        conversation_id=hangouts_pb2.ConversationId(
                            id=self.id_
                        ),
                        delete_upper_bound_timestamp=parsers.to_timestamp(
                            datetime.datetime.now(tz=datetime.timezone.utc)
                        )
                    )
                )
        except exceptions.NetworkError as e:
            logger.warning('Failed to leave conversation: {}'.format(e))
            raise

async
def
leave
(
self
)
:
is_group_conversation
=
(
self
.
_conversation
.
type
==
hangouts_pb2
.
CONVERSATION_TYPE_GROUP
)
try
:
if
is_group_conversation
:
await
self
.
_client
.
remove_user
(
hangouts_pb2
.
RemoveUserRequest
(
request_header
=
self
.
_client
.
get_request_header
(
)
,
event_request_header
=
self
.
_get_event_request_header
(
)
,
)
)
else
:
await
self
.
_client
.
delete_conversation
(
hangouts_pb2
.
DeleteConversationRequest
(
request_header
=
self
.
_client
.
get_request_header
(
)
,
conversation_id
=
hangouts_pb2
.
ConversationId
(
id
=
self
.
id_
)
,
delete_upper_bound_timestamp
=
parsers
.
to_timestamp
(
datetime
.
datetime
.
now
(
tz
=
datetime
.
timezone
.
utc
)
)
)
)
except
exceptions
.
NetworkError
as
e
:
logger
.
warning
(
'Failed to leave conversation: {}'
.
format
(
e
)
)
raise


async def rename(self, name):
        """Rename this conversation.

        Hangouts only officially supports renaming group conversations, so
        custom names for one-to-one conversations may or may not appear in all
        first party clients.

        Args:
            name (str): New name.

        Raises:
            .NetworkError: If conversation cannot be renamed.
        """
        await self._client.rename_conversation(
            hangouts_pb2.RenameConversationRequest(
                request_header=self._client.get_request_header(),
                new_name=name,
                event_request_header=self._get_event_request_header(),
            )
        )

async def rename ( self , name ) : await self . _client . rename_conversation ( hangouts_pb2 . RenameConversationRequest ( request_header = self . _client . get_request_header ( ) , new_name = name , event_request_header = self . _get_event_request_header ( ) , ) )


async def set_notification_level(self, level):
        """Set the notification level of this conversation.

        Args:
            level: ``NOTIFICATION_LEVEL_QUIET`` to disable notifications, or
                ``NOTIFICATION_LEVEL_RING`` to enable them.

        Raises:
            .NetworkError: If the request fails.
        """
        await self._client.set_conversation_notification_level(
            hangouts_pb2.SetConversationNotificationLevelRequest(
                request_header=self._client.get_request_header(),
                conversation_id=hangouts_pb2.ConversationId(id=self.id_),
                level=level,
            )
        )

async def set_notification_level ( self , level ) : await self . _client . set_conversation_notification_level ( hangouts_pb2 . SetConversationNotificationLevelRequest ( request_header = self . _client . get_request_header ( ) , conversation_id = hangouts_pb2 . ConversationId ( id = self . id_ ) , level = level , ) )

Set the notification level of this conversation .


async def set_typing(self, typing=hangouts_pb2.TYPING_TYPE_STARTED):
        """Set your typing status in this conversation.

        Args:
            typing: (optional) ``TYPING_TYPE_STARTED``, ``TYPING_TYPE_PAUSED``,
                or ``TYPING_TYPE_STOPPED`` to start, pause, or stop typing,
                respectively. Defaults to ``TYPING_TYPE_STARTED``.

        Raises:
            .NetworkError: If typing status cannot be set.
        """
        # TODO: Add rate-limiting to avoid unnecessary requests.
        try:
            await self._client.set_typing(
                hangouts_pb2.SetTypingRequest(
                    request_header=self._client.get_request_header(),
                    conversation_id=hangouts_pb2.ConversationId(id=self.id_),
                    type=typing,
                )
            )
        except exceptions.NetworkError as e:
            logger.warning('Failed to set typing status: {}'.format(e))
            raise

async
def
set_typing
(
self
,
typing
=
hangouts_pb2
.
TYPING_TYPE_STARTED
)
:
try
:
await
self
.
_client
.
set_typing
(
hangouts_pb2
.
SetTypingRequest
(
request_header
=
self
.
_client
.
get_request_header
(
)
,
conversation_id
=
hangouts_pb2
.
ConversationId
(
id
=
self
.
id_
)
,
type
=
typing
,
)
)
except
exceptions
.
NetworkError
as
e
:
logger
.
warning
(
'Failed to set typing status: {}'
.
format
(
e
)
)
raise

Set your typing status in this conversation .


async def update_read_timestamp(self, read_timestamp=None):
        """Update the timestamp of the latest event which has been read.

        This method will avoid making an API request if it will have no effect.

        Args:
            read_timestamp (datetime.datetime): (optional) Timestamp to set.
                Defaults to the timestamp of the newest event.

        Raises:
            .NetworkError: If the timestamp cannot be updated.
        """
        if read_timestamp is None:
            read_timestamp = (self.events[-1].timestamp if self.events else
                              datetime.datetime.now(datetime.timezone.utc))
        if read_timestamp > self.latest_read_timestamp:
            logger.info(
                'Setting {} latest_read_timestamp from {} to {}'
                .format(self.id_, self.latest_read_timestamp, read_timestamp)
            )
            # Prevent duplicate requests by updating the conversation now.
            state = self._conversation.self_conversation_state
            state.self_read_state.latest_read_timestamp = (
                parsers.to_timestamp(read_timestamp)
            )
            try:
                await self._client.update_watermark(
                    hangouts_pb2.UpdateWatermarkRequest(
                        request_header=self._client.get_request_header(),
                        conversation_id=hangouts_pb2.ConversationId(
                            id=self.id_
                        ),
                        last_read_timestamp=parsers.to_timestamp(
                            read_timestamp
                        ),
                    )
                )
            except exceptions.NetworkError as e:
                logger.warning('Failed to update read timestamp: {}'.format(e))
                raise

Update the timestamp of the latest event which has been read .


async def get_events(self, event_id=None, max_events=50):
        """Get events from this conversation.

        Makes a request to load historical events if necessary.

        Args:
            event_id (str): (optional) If provided, return events preceding
                this event, otherwise return the newest events.
            max_events (int): Maximum number of events to return. Defaults to
                50.

        Returns:
            List of :class:`.ConversationEvent` instances, ordered
            newest-first.

        Raises:
            KeyError: If ``event_id`` does not correspond to a known event.
            .NetworkError: If the events could not be requested.
        """
        if event_id is None:
            # If no event_id is provided, return the newest events in this
            # conversation.
            conv_events = self._events[-1 * max_events:]
        else:
            # If event_id is provided, return the events we have that are
            # older, or request older events if event_id corresponds to the
            # oldest event we have.
            conv_event = self.get_event(event_id)
            if self._events[0].id_ != event_id:
                conv_events = self._events[self._events.index(conv_event) + 1:]
            else:
                logger.info('Loading events for conversation {} before {}'
                            .format(self.id_, conv_event.timestamp))
                res = await self._client.get_conversation(
                    hangouts_pb2.GetConversationRequest(
                        request_header=self._client.get_request_header(),
                        conversation_spec=hangouts_pb2.ConversationSpec(
                            conversation_id=hangouts_pb2.ConversationId(
                                id=self.id_
                            )
                        ),
                        include_event=True,
                        max_events_per_conversation=max_events,
                        event_continuation_token=self._event_cont_token
                    )
                )
                # Certain fields of conversation_state are not populated by
                # SyncRecentConversations. This is the case with the
                # user_read_state fields which are all set to 0 but for the
                # 'self' user. Update here so these fields get populated on the
                # first call to GetConversation.
                if res.conversation_state.HasField('conversation'):
                    self.update_conversation(
                        res.conversation_state.conversation
                    )
                self._event_cont_token = (
                    res.conversation_state.event_continuation_token
                )
                conv_events = [self._wrap_event(event) for event
                               in res.conversation_state.event]
                logger.info('Loaded {} events for conversation {}'
                            .format(len(conv_events), self.id_))
                # Iterate though the events newest to oldest.
                for conv_event in reversed(conv_events):
                    # Add event as the new oldest event, unless we already have
                    # it.
                    if conv_event.id_ not in self._events_dict:
                        self._events.insert(0, conv_event)
                        self._events_dict[conv_event.id_] = conv_event
                    else:
                        # If this happens, there's probably a bug.
                        logger.info(
                            'Conversation %s ignoring duplicate event %s',
                            self.id_, conv_event.id_
                        )
        return conv_events


def next_event(self, event_id, prev=False):
        """Get the event following another event in this conversation.

        Args:
            event_id (str): ID of the event.
            prev (bool): If ``True``, return the previous event rather than the
                next event. Defaults to ``False``.

        Raises:
            KeyError: If no such :class:`.ConversationEvent` is known.

        Returns:
            :class:`.ConversationEvent` or ``None`` if there is no following
            event.
        """
        i = self.events.index(self._events_dict[event_id])
        if prev and i > 0:
            return self.events[i - 1]
        elif not prev and i + 1 < len(self.events):
            return self.events[i + 1]
        else:
            return None

def next_event ( self , event_id , prev = False ) : i = self . events . index ( self . _events_dict [ event_id ] ) if prev and i > 0 : return self . events [ i - 1 ] elif not prev and i + 1 < len ( self . events ) : return self . events [ i + 1 ] else : return None

Get the event following another event in this conversation .


def get_all(self, include_archived=False):
        """Get all the conversations.

        Args:
            include_archived (bool): (optional) Whether to include archived
                conversations. Defaults to ``False``.

        Returns:
            List of all :class:`.Conversation` objects.
        """
        return [conv for conv in self._conv_dict.values()
                if not conv.is_archived or include_archived]

def get_all ( self , include_archived = False ) : return [ conv for conv in self . _conv_dict . values ( ) if not conv . is_archived or include_archived ]


async def leave_conversation(self, conv_id):
        """Leave a conversation.

        Args:
            conv_id (str): ID of conversation to leave.
        """
        logger.info('Leaving conversation: {}'.format(conv_id))
        await self._conv_dict[conv_id].leave()
        del self._conv_dict[conv_id]

async
def
leave_conversation
(
self
,
conv_id
)
:
logger
.
info
(
'Leaving conversation: {}'
.
format
(
conv_id
)
)
await
self
.
_conv_dict
[
conv_id
]
.
leave
(
)
del
self
.
_conv_dict
[
conv_id
]


def _add_conversation(self, conversation, events=[],
                          event_cont_token=None):
        """Add new conversation from hangouts_pb2.Conversation"""
        # pylint: disable=dangerous-default-value
        conv_id = conversation.conversation_id.id
        logger.debug('Adding new conversation: {}'.format(conv_id))
        conv = Conversation(self._client, self._user_list, conversation,
                            events, event_cont_token)
        self._conv_dict[conv_id] = conv
        return conv

def
_add_conversation
(
self
,
conversation
,
events
=
[
]
,
event_cont_token
=
)
:
conv_id
=
conversation
.
conversation_id
.
id
logger
.
debug
(
'Adding new conversation: {}'
.
format
(
conv_id
)
)
conv
=
Conversation
(
self
.
_client
,
self
.
_user_list
,
conversation
,
events
,
event_cont_token
)
self
.
_conv_dict
[
conv_id
]
=
conv
return
conv

Add new conversation from hangouts_pb2 . Conversation