@@ -2064,3 +2064,109 @@ def from_zeckendorf(self, space_delim=".", byte_delim="|") -> EncryptionEncoding
20642064
20652065 self .state = bytes (decoded_bytes )
20662066 return self
2067+
2068+ @ChepyDecorators .call_stack
2069+ def sms_encode_multitap (self ):
2070+ """
2071+ Encode text to multi-tap SMS format.
2072+ Example: 'sun' -> '7778866'
2073+ Preserves special characters that aren't on phone keys.
2074+
2075+ Returns:
2076+ Chepy: The Chepy object.
2077+ """
2078+ KEY_MAP = {
2079+ "2" : "ABC" ,
2080+ "3" : "DEF" ,
2081+ "4" : "GHI" ,
2082+ "5" : "JKL" ,
2083+ "6" : "MNO" ,
2084+ "7" : "PQRS" ,
2085+ "8" : "TUV" ,
2086+ "9" : "WXYZ" ,
2087+ "0" : " " ,
2088+ }
2089+ REVERSE_MAP = {}
2090+
2091+ for key , letters in KEY_MAP .items ():
2092+ for i , letter in enumerate (letters ):
2093+ REVERSE_MAP [letter ] = key * (i + 1 )
2094+
2095+ result = []
2096+
2097+ for char in self ._convert_to_str ():
2098+ upper_char = char .upper ()
2099+
2100+ # Check if character can be encoded
2101+ if upper_char in REVERSE_MAP :
2102+ result .append (REVERSE_MAP [upper_char ])
2103+ else :
2104+ # Preserve special characters as-is
2105+ result .append (char )
2106+
2107+ self .state = "" .join (result )
2108+ return self
2109+
2110+ @ChepyDecorators .call_stack
2111+ def sms_decode_multitap (self ):
2112+ """
2113+ Decode multi-tap SMS format to text.
2114+ Example: '7778866' -> 'SUN'
2115+ Preserves special characters that aren't numeric keypresses.
2116+
2117+ Returns:
2118+ Chepy: The Chepy object.
2119+ """
2120+ KEY_MAP = {
2121+ "2" : "ABC" ,
2122+ "3" : "DEF" ,
2123+ "4" : "GHI" ,
2124+ "5" : "JKL" ,
2125+ "6" : "MNO" ,
2126+ "7" : "PQRS" ,
2127+ "8" : "TUV" ,
2128+ "9" : "WXYZ" ,
2129+ "0" : " " ,
2130+ }
2131+
2132+ result = []
2133+ i = 0
2134+
2135+ encoded = self ._convert_to_str ()
2136+
2137+ while i < len (encoded ):
2138+ current_char = encoded [i ]
2139+
2140+ # If it's not a digit, preserve it as-is
2141+ if not current_char .isdigit ():
2142+ result .append (current_char )
2143+ i += 1
2144+ continue
2145+
2146+ # If it's not a valid key, preserve it
2147+ if current_char not in KEY_MAP : # pragma: no cover
2148+ result .append (current_char )
2149+ i += 1
2150+ continue
2151+
2152+ letters = KEY_MAP [current_char ]
2153+ letters_count = len (letters )
2154+
2155+ # Count consecutive same digits
2156+ count = 0
2157+ while i < len (encoded ) and encoded [i ] == current_char :
2158+ count += 1
2159+ i += 1
2160+
2161+ # Decode groups of presses, handling wrapping
2162+ while count > 0 :
2163+ presses_for_this_letter = count % letters_count
2164+ if presses_for_this_letter == 0 :
2165+ presses_for_this_letter = letters_count
2166+
2167+ letter_index = presses_for_this_letter - 1
2168+ result .append (letters [letter_index ])
2169+ count -= presses_for_this_letter
2170+
2171+ self .state = "" .join (result )
2172+ return self
0 commit comments